/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the IDispatch implementation for XPConnect. * * The Initial Developer of the Original Code is * David Bradley. * Portions created by the Initial Developer are Copyright (C) 2002 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /** * \file XPCDispConvert.cpp * Contains the implementation of the XPCDispConvert class */ #include "xpcprivate.h" VARTYPE XPCDispConvert::JSTypeToCOMType(XPCCallContext& ccx, jsval val) { if(JSVAL_IS_PRIMITIVE(val)) { if(JSVAL_IS_STRING(val)) { return VT_BSTR; } if(JSVAL_IS_INT(val)) { return VT_I4; } if(JSVAL_IS_DOUBLE(val)) { return VT_R8; } if(JSVAL_IS_BOOLEAN(val)) { return VT_BOOL; } if(JSVAL_IS_VOID(val)) { return VT_EMPTY; } if(JSVAL_IS_NULL(val)) { return VT_NULL; } } else { if(JS_IsArrayObject(ccx, JSVAL_TO_OBJECT(val))) return VT_ARRAY | VT_VARIANT; return VT_DISPATCH; } NS_ERROR("XPCDispConvert::JSTypeToCOMType was unable to identify the type of the jsval"); return VT_EMPTY; } JSBool XPCDispConvert::JSArrayToCOMArray(XPCCallContext& ccx, JSObject *obj, VARIANT & var, nsresult& err) { err = NS_OK; jsuint len; if(!JS_GetArrayLength(ccx, obj, &len)) { // TODO: I think we should create a specific error for this err = NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY; return PR_FALSE; } // Create the safe array of variants and populate it SAFEARRAY * array = nsnull; VARIANT* varArray = 0; for(jsuint index = 0; index < len; ++index) { jsval val; if(JS_GetElement(ccx, obj, index, &val)) { if(!JSVAL_IS_VOID(val)) { if(!array) { // Create an array that starts at index, and has len // elements array = SafeArrayCreateVector(VT_VARIANT, index, len - index); if(!array) { err = NS_ERROR_OUT_OF_MEMORY; return JS_FALSE; } if(FAILED(SafeArrayAccessData(array, NS_REINTERPRET_CAST(void**,&varArray)))) { err = NS_ERROR_FAILURE; return JS_FALSE; } } if(!JSToCOM(ccx, val, *varArray, err)) { SafeArrayUnaccessData(array); err = NS_ERROR_FAILURE; // This cleans up the elements as well SafeArrayDestroyData(array); return JS_FALSE; } } if(varArray) ++varArray; } } SafeArrayUnaccessData(array); var.vt = VT_ARRAY | VT_VARIANT; var.parray = array; return JS_TRUE; } #define XPC_ASSIGN(src, dest, data) *dest.p##data = src.data /** * Copies a variant to a by ref variant * NOTE: This does not perform any reference counting. It simply does * a copy of the values, it's up to the caller to manage any ownership issues * @param src the variant to be copied * @param dest the destination for the copy * @return JS_TRUE if the copy was performed JS_FALSE if it failed */ inline JSBool xpc_CopyVariantByRef(VARIANT & src, VARIANT & dest) { VARIANT temp; VARTYPE vt = dest.vt & ~(VT_BYREF); if(vt != src.vt) { // TODO: This fails more than I had hoped, we may want to // add some logic to handle more conversions if(FAILED(VariantChangeType(&temp, &src, VARIANT_ALPHABOOL, vt))) { return JS_FALSE; } } else temp = src; switch (vt) { case VT_I2: { XPC_ASSIGN(temp, dest, iVal); } break; case VT_I4: { XPC_ASSIGN(temp, dest, lVal); } break; case VT_R4: { XPC_ASSIGN(temp, dest, fltVal); } break; case VT_R8: { XPC_ASSIGN(temp, dest, dblVal); } break; case VT_CY: { XPC_ASSIGN(temp, dest, cyVal); } break; case VT_DATE: { XPC_ASSIGN(temp, dest, date); } break; case VT_BSTR: { XPC_ASSIGN(temp, dest, bstrVal); } break; case VT_DISPATCH: { XPC_ASSIGN(temp, dest, pdispVal); } break; case VT_ERROR: { XPC_ASSIGN(temp, dest, scode); } break; case VT_BOOL: { XPC_ASSIGN(temp, dest, boolVal); } break; case VT_VARIANT: { // Not Supported right now return JS_FALSE; } break; case VT_I1: { XPC_ASSIGN(temp, dest, cVal); } break; case VT_UI1: { XPC_ASSIGN(temp, dest, bVal); } break; case VT_UI2: { XPC_ASSIGN(temp, dest, iVal); } break; case VT_UI4: { XPC_ASSIGN(temp, dest, uiVal); } break; case VT_INT: { XPC_ASSIGN(temp, dest, intVal); } break; case VT_UINT: { XPC_ASSIGN(temp, dest, uintVal); } break; default: { return JS_FALSE; } } return JS_TRUE; } JSBool XPCDispConvert::JSToCOM(XPCCallContext& ccx, jsval src, VARIANT & dest, nsresult& err, JSBool isByRef) { err = NS_OK; VARIANT byRefVariant; VARIANT * varDest = isByRef ? &byRefVariant : &dest; varDest->vt = JSTypeToCOMType(ccx, src); switch (varDest->vt) { case VT_BSTR: { JSString* str = JSVAL_TO_STRING(src); jschar * chars = JS_GetStringChars(str); if(!chars) { err = NS_ERROR_XPC_BAD_CONVERT_NATIVE; // Avoid cleaning up garbage varDest->vt = VT_EMPTY; return JS_FALSE; } CComBSTR val(JS_GetStringLength(str), chars); varDest->bstrVal = val.Detach(); } break; case VT_I4: { varDest->vt = VT_I4; varDest->lVal = JSVAL_TO_INT(src); } break; case VT_R8: { varDest->vt = VT_R8; varDest->dblVal = *JSVAL_TO_DOUBLE(src); } break; case VT_EMPTY: case VT_NULL: break; case VT_ARRAY | VT_VARIANT: { JSObject * obj = JSVAL_TO_OBJECT(src); return JSArrayToCOMArray(ccx, obj, *varDest, err); } break; case VT_DISPATCH: { JSObject * obj = JSVAL_TO_OBJECT(src); IUnknown * pUnknown = nsnull; if(!XPCConvert::JSObject2NativeInterface( ccx, (void**)&pUnknown, obj, &NSID_IDISPATCH, nsnull, &err)) { // Avoid cleaning up garbage varDest->vt = VT_EMPTY; return JS_FALSE; } varDest->vt = VT_DISPATCH; pUnknown->QueryInterface(IID_IDispatch, NS_REINTERPRET_CAST(void**, &varDest->pdispVal)); NS_IF_RELEASE(pUnknown); } break; case VT_BOOL: { varDest->boolVal = JSVAL_TO_BOOLEAN(src) ? VARIANT_TRUE : VARIANT_FALSE; } break; default: { NS_ERROR("This is out of synce with XPCDispConvert::JSTypeToCOMType"); err = NS_ERROR_XPC_BAD_CONVERT_NATIVE; // Avoid cleaning up garbage varDest->vt = VT_EMPTY; return JS_FALSE; } break; } if(isByRef) { if(!xpc_CopyVariantByRef(byRefVariant, dest)) { // Avoid cleaning up garbage dest.vt = VT_EMPTY; } } return JS_TRUE; } JSBool XPCDispConvert::COMArrayToJSArray(XPCCallContext& ccx, const VARIANT & src, jsval & dest, nsresult& err) { err = NS_OK; // We only support one dimensional arrays for now if(SafeArrayGetDim(src.parray) != 1) { err = NS_ERROR_FAILURE; return JS_FALSE; } // Get the upper bound; long ubound; if(FAILED(SafeArrayGetUBound(src.parray, 1, &ubound))) { err = NS_ERROR_FAILURE; return JS_FALSE; } // Get the lower bound long lbound; if(FAILED(SafeArrayGetLBound(src.parray, 1, &lbound))) { err = NS_ERROR_FAILURE; return JS_FALSE; } // Create the JS Array JSObject * array = JS_NewArrayObject(ccx, ubound - lbound + 1, nsnull); if(!array) { err = NS_ERROR_OUT_OF_MEMORY; return JS_FALSE; } // Devine the type of our array VARTYPE vartype; if((src.vt & VT_ARRAY) != 0) { vartype = src.vt & ~VT_ARRAY; } else // This was maybe a VT_SAFEARRAY { if(FAILED(SafeArrayGetVartype(src.parray, &vartype))) return JS_FALSE; } jsval val; for(long index = lbound; index <= ubound; ++index) { // Devine the type of our array _variant_t var; var.vt = vartype; if(FAILED(SafeArrayGetElement(src.parray, &index, &var.byref))) { err = NS_ERROR_FAILURE; return JS_FALSE; } if(!COMToJS(ccx, var, val, err)) return JS_FALSE; JS_SetElement(ccx, array, index, &val); } dest = OBJECT_TO_JSVAL(array); return JS_TRUE; } /** * Converts a string to a jsval * @param cx the JSContext * @param str the unicode string to be converted * @param len the length of the string being converted * @return the jsval representing the string */ inline jsval StringToJSVal(JSContext* cx, const PRUnichar * str, PRUint32 len) { JSString * s = JS_NewUCStringCopyN(cx, str, len); if(s) return STRING_TO_JSVAL(s); else return JSVAL_NULL; } #define VALUE(val) (isPtr ? *src.p##val : src.val) JSBool XPCDispConvert::COMToJS(XPCCallContext& ccx, const VARIANT& src, jsval& dest, nsresult& err) { err = NS_OK; if(src.vt & VT_ARRAY || src.vt == VT_SAFEARRAY) { return COMArrayToJSArray(ccx, src, dest, err); } PRBool isPtr = src.vt & VT_BYREF; switch (src.vt & ~(VT_BYREF)) { case VT_UINT: { return JS_NewNumberValue(ccx, VALUE(uintVal), &dest); } break; case VT_UI4: { return JS_NewNumberValue(ccx, VALUE(ulVal), &dest); } break; case VT_INT: { return JS_NewNumberValue(ccx, VALUE(intVal), &dest); } break; case VT_I4: { return JS_NewNumberValue(ccx, VALUE(lVal), &dest); } break; case VT_UI1: { dest = INT_TO_JSVAL(VALUE(bVal)); } break; case VT_I1: { dest = INT_TO_JSVAL(VALUE(cVal)); } break; case VT_UI2: { dest = INT_TO_JSVAL(VALUE(uiVal)); } break; case VT_I2: { dest = INT_TO_JSVAL(VALUE(iVal)); } break; case VT_R4: { return JS_NewNumberValue(ccx, VALUE(fltVal), &dest); } break; case VT_R8: { return JS_NewNumberValue(ccx, VALUE(dblVal), &dest); } break; case VT_BOOL: { dest = BOOLEAN_TO_JSVAL(VALUE(boolVal) != VARIANT_FALSE ? JS_TRUE : JS_FALSE); } break; case VT_DISPATCH: { XPCDispObject::WrapIDispatch(VALUE(pdispVal), ccx, JS_GetGlobalObject(ccx), &dest); } break; case VT_DATE: { // Convert date to string and frees it when we're done _bstr_t str(src); dest = StringToJSVal(ccx, str, str.length()); } break; case VT_EMPTY: { dest = JSVAL_VOID; } break; case VT_NULL: { dest = JSVAL_NULL; } break; case VT_ERROR: { return JS_NewNumberValue(ccx, VALUE(scode), &dest); } break; case VT_CY: { return JS_NewNumberValue( ccx, NS_STATIC_CAST(double, isPtr ? src.pcyVal->int64 : src.cyVal.int64) / 100.0, &dest); } break; /** * Currently unsupported conversion types */ case VT_UNKNOWN: default: { // Last ditch effort to convert to string if(FAILED(VariantChangeType(NS_CONST_CAST(VARIANT*,&src), NS_CONST_CAST(VARIANT*,&src), VARIANT_ALPHABOOL, VT_BSTR))) { err = NS_ERROR_XPC_BAD_CONVERT_JS; return JS_FALSE; } isPtr = FALSE; } // Fall through on success case VT_BSTR: { dest = StringToJSVal(ccx, VALUE(bstrVal), SysStringLen(VALUE(bstrVal))); } break; } return JS_TRUE; }