michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set ts=8 sts=4 et sw=4 tw=99: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* Sharable code and data for wrapper around JSObjects. */ michael@0: michael@0: #include "xpcprivate.h" michael@0: #include "jsprf.h" michael@0: #include "nsArrayEnumerator.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsWrapperCache.h" michael@0: #include "AccessCheck.h" michael@0: #include "nsJSUtils.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: #include "mozilla/dom/DOMException.h" michael@0: #include "mozilla/dom/DOMExceptionBinding.h" michael@0: michael@0: #include "jsapi.h" michael@0: #include "jsfriendapi.h" michael@0: michael@0: using namespace xpc; michael@0: using namespace JS; michael@0: using namespace mozilla; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsXPCWrappedJSClass, nsIXPCWrappedJSClass) michael@0: michael@0: // the value of this variable is never used - we use its address as a sentinel michael@0: static uint32_t zero_methods_descriptor; michael@0: michael@0: bool AutoScriptEvaluate::StartEvaluating(HandleObject scope, JSErrorReporter errorReporter) michael@0: { michael@0: NS_PRECONDITION(!mEvaluated, "AutoScriptEvaluate::Evaluate should only be called once"); michael@0: michael@0: if (!mJSContext) michael@0: return true; michael@0: michael@0: mEvaluated = true; michael@0: if (!JS_GetErrorReporter(mJSContext)) { michael@0: JS_SetErrorReporter(mJSContext, errorReporter); michael@0: mErrorReporterSet = true; michael@0: } michael@0: michael@0: JS_BeginRequest(mJSContext); michael@0: mAutoCompartment.construct(mJSContext, scope); michael@0: michael@0: // Saving the exception state keeps us from interfering with another script michael@0: // that may also be running on this context. This occurred first with the michael@0: // js debugger, as described in michael@0: // http://bugzilla.mozilla.org/show_bug.cgi?id=88130 but presumably could michael@0: // show up in any situation where a script calls into a wrapped js component michael@0: // on the same context, while the context has a nonzero exception state. michael@0: mState.construct(mJSContext); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: AutoScriptEvaluate::~AutoScriptEvaluate() michael@0: { michael@0: if (!mJSContext || !mEvaluated) michael@0: return; michael@0: mState.ref().restore(); michael@0: michael@0: JS_EndRequest(mJSContext); michael@0: michael@0: if (mErrorReporterSet) michael@0: JS_SetErrorReporter(mJSContext, nullptr); michael@0: } michael@0: michael@0: // It turns out that some errors may be not worth reporting. So, this michael@0: // function is factored out to manage that. michael@0: bool xpc_IsReportableErrorCode(nsresult code) michael@0: { michael@0: if (NS_SUCCEEDED(code)) michael@0: return false; michael@0: michael@0: switch (code) { michael@0: // Error codes that we don't want to report as errors... michael@0: // These generally indicate bad interface design AFAIC. michael@0: case NS_ERROR_FACTORY_REGISTER_AGAIN: michael@0: case NS_BASE_STREAM_WOULD_BLOCK: michael@0: return false; michael@0: default: michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // static michael@0: already_AddRefed michael@0: nsXPCWrappedJSClass::GetNewOrUsed(JSContext* cx, REFNSIID aIID) michael@0: { michael@0: XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance(); michael@0: IID2WrappedJSClassMap* map = rt->GetWrappedJSClassMap(); michael@0: nsRefPtr clasp = map->Find(aIID); michael@0: michael@0: if (!clasp) { michael@0: nsCOMPtr info; michael@0: nsXPConnect::XPConnect()->GetInfoForIID(&aIID, getter_AddRefs(info)); michael@0: if (info) { michael@0: bool canScript, isBuiltin; michael@0: if (NS_SUCCEEDED(info->IsScriptable(&canScript)) && canScript && michael@0: NS_SUCCEEDED(info->IsBuiltinClass(&isBuiltin)) && !isBuiltin && michael@0: nsXPConnect::IsISupportsDescendant(info)) michael@0: { michael@0: clasp = new nsXPCWrappedJSClass(cx, aIID, info); michael@0: if (!clasp->mDescriptors) michael@0: clasp = nullptr; michael@0: } michael@0: } michael@0: } michael@0: return clasp.forget(); michael@0: } michael@0: michael@0: nsXPCWrappedJSClass::nsXPCWrappedJSClass(JSContext* cx, REFNSIID aIID, michael@0: nsIInterfaceInfo* aInfo) michael@0: : mRuntime(nsXPConnect::GetRuntimeInstance()), michael@0: mInfo(aInfo), michael@0: mName(nullptr), michael@0: mIID(aIID), michael@0: mDescriptors(nullptr) michael@0: { michael@0: mRuntime->GetWrappedJSClassMap()->Add(this); michael@0: michael@0: uint16_t methodCount; michael@0: if (NS_SUCCEEDED(mInfo->GetMethodCount(&methodCount))) { michael@0: if (methodCount) { michael@0: int wordCount = (methodCount/32)+1; michael@0: if (nullptr != (mDescriptors = new uint32_t[wordCount])) { michael@0: int i; michael@0: // init flags to 0; michael@0: for (i = wordCount-1; i >= 0; i--) michael@0: mDescriptors[i] = 0; michael@0: michael@0: for (i = 0; i < methodCount; i++) { michael@0: const nsXPTMethodInfo* info; michael@0: if (NS_SUCCEEDED(mInfo->GetMethodInfo(i, &info))) michael@0: SetReflectable(i, XPCConvert::IsMethodReflectable(*info)); michael@0: else { michael@0: delete [] mDescriptors; michael@0: mDescriptors = nullptr; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } else { michael@0: mDescriptors = &zero_methods_descriptor; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsXPCWrappedJSClass::~nsXPCWrappedJSClass() michael@0: { michael@0: if (mDescriptors && mDescriptors != &zero_methods_descriptor) michael@0: delete [] mDescriptors; michael@0: if (mRuntime) michael@0: mRuntime->GetWrappedJSClassMap()->Remove(this); michael@0: michael@0: if (mName) michael@0: nsMemory::Free(mName); michael@0: } michael@0: michael@0: JSObject* michael@0: nsXPCWrappedJSClass::CallQueryInterfaceOnJSObject(JSContext* cx, michael@0: JSObject* jsobjArg, michael@0: REFNSIID aIID) michael@0: { michael@0: RootedObject jsobj(cx, jsobjArg); michael@0: JSObject* id; michael@0: RootedValue retval(cx); michael@0: RootedObject retObj(cx); michael@0: bool success = false; michael@0: RootedValue fun(cx); michael@0: michael@0: // Don't call the actual function on a content object. We'll determine michael@0: // whether or not a content object is capable of implementing the michael@0: // interface (i.e. whether the interface is scriptable) and most content michael@0: // objects don't have QI implementations anyway. Also see bug 503926. michael@0: if (!xpc::AccessCheck::isChrome(js::GetObjectCompartment(jsobj))) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // OK, it looks like we'll be calling into JS code. michael@0: AutoScriptEvaluate scriptEval(cx); michael@0: michael@0: // XXX we should install an error reporter that will send reports to michael@0: // the JS error console service. michael@0: if (!scriptEval.StartEvaluating(jsobj)) michael@0: return nullptr; michael@0: michael@0: // check upfront for the existence of the function property michael@0: HandleId funid = mRuntime->GetStringID(XPCJSRuntime::IDX_QUERY_INTERFACE); michael@0: if (!JS_GetPropertyById(cx, jsobj, funid, &fun) || JSVAL_IS_PRIMITIVE(fun)) michael@0: return nullptr; michael@0: michael@0: // Ensure that we are asking for a scriptable interface. michael@0: // NB: It's important for security that this check is here rather michael@0: // than later, since it prevents untrusted objects from implementing michael@0: // some interfaces in JS and aggregating a trusted object to michael@0: // implement intentionally (for security) unscriptable interfaces. michael@0: // We so often ask for nsISupports that we can short-circuit the test... michael@0: if (!aIID.Equals(NS_GET_IID(nsISupports))) { michael@0: nsCOMPtr info; michael@0: nsXPConnect::XPConnect()->GetInfoForIID(&aIID, getter_AddRefs(info)); michael@0: if (!info) michael@0: return nullptr; michael@0: bool canScript, isBuiltin; michael@0: if (NS_FAILED(info->IsScriptable(&canScript)) || !canScript || michael@0: NS_FAILED(info->IsBuiltinClass(&isBuiltin)) || isBuiltin) michael@0: return nullptr; michael@0: } michael@0: michael@0: id = xpc_NewIDObject(cx, jsobj, aIID); michael@0: if (id) { michael@0: // Throwing NS_NOINTERFACE is the prescribed way to fail QI from JS. It michael@0: // is not an exception that is ever worth reporting, but we don't want michael@0: // to eat all exceptions either. michael@0: michael@0: { michael@0: AutoSaveContextOptions asco(cx); michael@0: ContextOptionsRef(cx).setDontReportUncaught(true); michael@0: RootedValue arg(cx, JS::ObjectValue(*id)); michael@0: success = JS_CallFunctionValue(cx, jsobj, fun, arg, &retval); michael@0: } michael@0: michael@0: if (!success && JS_IsExceptionPending(cx)) { michael@0: RootedValue jsexception(cx, NullValue()); michael@0: michael@0: if (JS_GetPendingException(cx, &jsexception)) { michael@0: nsresult rv; michael@0: if (jsexception.isObject()) { michael@0: // XPConnect may have constructed an object to represent a michael@0: // C++ QI failure. See if that is the case. michael@0: using namespace mozilla::dom; michael@0: Exception *e = nullptr; michael@0: UNWRAP_OBJECT(Exception, &jsexception.toObject(), e); michael@0: michael@0: if (e && michael@0: NS_SUCCEEDED(e->GetResult(&rv)) && michael@0: rv == NS_NOINTERFACE) { michael@0: JS_ClearPendingException(cx); michael@0: } michael@0: } else if (JSVAL_IS_NUMBER(jsexception)) { michael@0: // JS often throws an nsresult. michael@0: if (JSVAL_IS_DOUBLE(jsexception)) michael@0: // Visual Studio 9 doesn't allow casting directly from michael@0: // a double to an enumeration type, contrary to michael@0: // 5.2.9(10) of C++11, so add an intermediate cast. michael@0: rv = (nsresult)(uint32_t)(JSVAL_TO_DOUBLE(jsexception)); michael@0: else michael@0: rv = (nsresult)(JSVAL_TO_INT(jsexception)); michael@0: michael@0: if (rv == NS_NOINTERFACE) michael@0: JS_ClearPendingException(cx); michael@0: } michael@0: } michael@0: michael@0: // Don't report if reporting was disabled by someone else. michael@0: if (!ContextOptionsRef(cx).dontReportUncaught()) michael@0: JS_ReportPendingException(cx); michael@0: } else if (!success) { michael@0: NS_WARNING("QI hook ran OOMed - this is probably a bug!"); michael@0: } michael@0: } michael@0: michael@0: if (success) michael@0: success = JS_ValueToObject(cx, retval, &retObj); michael@0: michael@0: return success ? retObj.get() : nullptr; michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: static bool michael@0: GetNamedPropertyAsVariantRaw(XPCCallContext& ccx, michael@0: HandleObject aJSObj, michael@0: HandleId aName, michael@0: nsIVariant** aResult, michael@0: nsresult* pErr) michael@0: { michael@0: nsXPTType type = nsXPTType((uint8_t)TD_INTERFACE_TYPE); michael@0: RootedValue val(ccx); michael@0: michael@0: return JS_GetPropertyById(ccx, aJSObj, aName, &val) && michael@0: // Note that this always takes the T_INTERFACE path through michael@0: // JSData2Native, so the value passed for useAllocator michael@0: // doesn't really matter. We pass true for consistency. michael@0: XPCConvert::JSData2Native(aResult, val, type, true, michael@0: &NS_GET_IID(nsIVariant), pErr); michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: nsXPCWrappedJSClass::GetNamedPropertyAsVariant(XPCCallContext& ccx, michael@0: JSObject* aJSObjArg, michael@0: const nsAString& aName, michael@0: nsIVariant** aResult) michael@0: { michael@0: JSContext* cx = ccx.GetJSContext(); michael@0: RootedObject aJSObj(cx, aJSObjArg); michael@0: michael@0: AutoScriptEvaluate scriptEval(cx); michael@0: if (!scriptEval.StartEvaluating(aJSObj)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Wrap the string in a jsval after the AutoScriptEvaluate, so that the michael@0: // resulting value ends up in the correct compartment. michael@0: nsStringBuffer* buf; michael@0: RootedValue value(cx); michael@0: if (!XPCStringConvert::ReadableToJSVal(ccx, aName, &buf, &value)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: if (buf) michael@0: buf->AddRef(); michael@0: michael@0: RootedId id(cx); michael@0: nsresult rv = NS_OK; michael@0: if (!JS_ValueToId(cx, value, &id) || michael@0: !GetNamedPropertyAsVariantRaw(ccx, aJSObj, id, aResult, &rv)) { michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: // static michael@0: nsresult michael@0: nsXPCWrappedJSClass::BuildPropertyEnumerator(XPCCallContext& ccx, michael@0: JSObject* aJSObjArg, michael@0: nsISimpleEnumerator** aEnumerate) michael@0: { michael@0: JSContext* cx = ccx.GetJSContext(); michael@0: RootedObject aJSObj(cx, aJSObjArg); michael@0: michael@0: AutoScriptEvaluate scriptEval(cx); michael@0: if (!scriptEval.StartEvaluating(aJSObj)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: AutoIdArray idArray(cx, JS_Enumerate(cx, aJSObj)); michael@0: if (!idArray) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMArray propertyArray(idArray.length()); michael@0: RootedId idName(cx); michael@0: for (size_t i = 0; i < idArray.length(); i++) { michael@0: idName = idArray[i]; michael@0: michael@0: nsCOMPtr value; michael@0: nsresult rv; michael@0: if (!GetNamedPropertyAsVariantRaw(ccx, aJSObj, idName, michael@0: getter_AddRefs(value), &rv)) { michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: RootedValue jsvalName(cx); michael@0: if (!JS_IdToValue(cx, idName, &jsvalName)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: JSString* name = ToString(cx, jsvalName); michael@0: if (!name) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: size_t length; michael@0: const jschar *chars = JS_GetStringCharsAndLength(cx, name, &length); michael@0: if (!chars) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr property = michael@0: new xpcProperty(chars, (uint32_t) length, value); michael@0: michael@0: if (!propertyArray.AppendObject(property)) michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_NewArrayEnumerator(aEnumerate, propertyArray); michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: NS_IMPL_ISUPPORTS(xpcProperty, nsIProperty) michael@0: michael@0: xpcProperty::xpcProperty(const char16_t* aName, uint32_t aNameLen, michael@0: nsIVariant* aValue) michael@0: : mName(aName, aNameLen), mValue(aValue) michael@0: { michael@0: } michael@0: michael@0: /* readonly attribute AString name; */ michael@0: NS_IMETHODIMP xpcProperty::GetName(nsAString & aName) michael@0: { michael@0: aName.Assign(mName); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute nsIVariant value; */ michael@0: NS_IMETHODIMP xpcProperty::GetValue(nsIVariant * *aValue) michael@0: { michael@0: nsCOMPtr rval = mValue; michael@0: rval.forget(aValue); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /***************************************************************************/ michael@0: // This 'WrappedJSIdentity' class and singleton allow us to figure out if michael@0: // any given nsISupports* is implemented by a WrappedJS object. This is done michael@0: // using a QueryInterface call on the interface pointer with our ID. If michael@0: // that call returns NS_OK and the pointer is to our singleton, then the michael@0: // interface must be implemented by a WrappedJS object. NOTE: the michael@0: // 'WrappedJSIdentity' object is not a real XPCOM object and should not be michael@0: // used for anything else (hence it is declared in this implementation file). michael@0: michael@0: // {5C5C3BB0-A9BA-11d2-BA64-00805F8A5DD7} michael@0: #define NS_IXPCONNECT_WRAPPED_JS_IDENTITY_CLASS_IID \ michael@0: { 0x5c5c3bb0, 0xa9ba, 0x11d2, \ michael@0: { 0xba, 0x64, 0x0, 0x80, 0x5f, 0x8a, 0x5d, 0xd7 } } michael@0: michael@0: class WrappedJSIdentity michael@0: { michael@0: // no instance methods... michael@0: public: michael@0: NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXPCONNECT_WRAPPED_JS_IDENTITY_CLASS_IID) michael@0: michael@0: static void* GetSingleton() michael@0: { michael@0: static WrappedJSIdentity* singleton = nullptr; michael@0: if (!singleton) michael@0: singleton = new WrappedJSIdentity(); michael@0: return (void*) singleton; michael@0: } michael@0: }; michael@0: michael@0: NS_DEFINE_STATIC_IID_ACCESSOR(WrappedJSIdentity, michael@0: NS_IXPCONNECT_WRAPPED_JS_IDENTITY_CLASS_IID) michael@0: michael@0: /***************************************************************************/ michael@0: michael@0: // static michael@0: bool michael@0: nsXPCWrappedJSClass::IsWrappedJS(nsISupports* aPtr) michael@0: { michael@0: void* result; michael@0: NS_PRECONDITION(aPtr, "null pointer"); michael@0: return aPtr && michael@0: NS_OK == aPtr->QueryInterface(NS_GET_IID(WrappedJSIdentity), &result) && michael@0: result == WrappedJSIdentity::GetSingleton(); michael@0: } michael@0: michael@0: // NB: This will return the top JSContext on the JSContext stack if there is one, michael@0: // before attempting to get the context from the wrapped JS object. michael@0: static JSContext * michael@0: GetContextFromObjectOrDefault(nsXPCWrappedJS* wrapper) michael@0: { michael@0: // First, try the cx stack. michael@0: XPCJSContextStack* stack = XPCJSRuntime::Get()->GetJSContextStack(); michael@0: if (stack->Peek()) michael@0: return stack->Peek(); michael@0: michael@0: // If the cx stack is empty, try the wrapper's JSObject. michael@0: JSCompartment *c = js::GetObjectCompartment(wrapper->GetJSObject()); michael@0: XPCContext *xpcc = EnsureCompartmentPrivate(c)->scope->GetContext(); michael@0: if (xpcc) { michael@0: JSContext *cx = xpcc->GetJSContext(); michael@0: JS_AbortIfWrongThread(JS_GetRuntime(cx)); michael@0: return cx; michael@0: } michael@0: michael@0: // Fall back to the safe JSContext. michael@0: return stack->GetSafeJSContext(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXPCWrappedJSClass::DelegatedQueryInterface(nsXPCWrappedJS* self, michael@0: REFNSIID aIID, michael@0: void** aInstancePtr) michael@0: { michael@0: if (aIID.Equals(NS_GET_IID(nsIXPConnectJSObjectHolder))) { michael@0: NS_ADDREF(self); michael@0: *aInstancePtr = (void*) static_cast(self); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Objects internal to xpconnect are the only objects that even know *how* michael@0: // to ask for this iid. And none of them bother refcounting the thing. michael@0: if (aIID.Equals(NS_GET_IID(WrappedJSIdentity))) { michael@0: // asking to find out if this is a wrapper object michael@0: *aInstancePtr = WrappedJSIdentity::GetSingleton(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (aIID.Equals(NS_GET_IID(nsIPropertyBag))) { michael@0: // We only want to expose one implementation from our aggregate. michael@0: nsXPCWrappedJS* root = self->GetRootWrapper(); michael@0: michael@0: if (!root->IsValid()) { michael@0: *aInstancePtr = nullptr; michael@0: return NS_NOINTERFACE; michael@0: } michael@0: michael@0: NS_ADDREF(root); michael@0: *aInstancePtr = (void*) static_cast(root); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We can't have a cached wrapper. michael@0: if (aIID.Equals(NS_GET_IID(nsWrapperCache))) { michael@0: *aInstancePtr = nullptr; michael@0: return NS_NOINTERFACE; michael@0: } michael@0: michael@0: AutoPushJSContext context(GetContextFromObjectOrDefault(self)); michael@0: XPCCallContext ccx(NATIVE_CALLER, context); michael@0: if (!ccx.IsValid()) { michael@0: *aInstancePtr = nullptr; michael@0: return NS_NOINTERFACE; michael@0: } michael@0: michael@0: // We support nsISupportsWeakReference iff the root wrapped JSObject michael@0: // claims to support it in its QueryInterface implementation. michael@0: if (aIID.Equals(NS_GET_IID(nsISupportsWeakReference))) { michael@0: // We only want to expose one implementation from our aggregate. michael@0: nsXPCWrappedJS* root = self->GetRootWrapper(); michael@0: michael@0: // Fail if JSObject doesn't claim support for nsISupportsWeakReference michael@0: if (!root->IsValid() || michael@0: !CallQueryInterfaceOnJSObject(ccx, root->GetJSObject(), aIID)) { michael@0: *aInstancePtr = nullptr; michael@0: return NS_NOINTERFACE; michael@0: } michael@0: michael@0: NS_ADDREF(root); michael@0: *aInstancePtr = (void*) static_cast(root); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Checks for any existing wrapper explicitly constructed for this iid. michael@0: // This includes the current 'self' wrapper. This also deals with the michael@0: // nsISupports case (for which it returns mRoot). michael@0: // Also check if asking for an interface from which one of our wrappers inherits. michael@0: if (nsXPCWrappedJS* sibling = self->FindOrFindInherited(aIID)) { michael@0: NS_ADDREF(sibling); michael@0: *aInstancePtr = sibling->GetXPTCStub(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // else we do the more expensive stuff... michael@0: michael@0: // check if the JSObject claims to implement this interface michael@0: RootedObject jsobj(ccx, CallQueryInterfaceOnJSObject(ccx, self->GetJSObject(), michael@0: aIID)); michael@0: if (jsobj) { michael@0: // We can't use XPConvert::JSObject2NativeInterface() here michael@0: // since that can find a XPCWrappedNative directly on the michael@0: // proto chain, and we don't want that here. We need to find michael@0: // the actual JS object that claimed it supports the interface michael@0: // we're looking for or we'll potentially bypass security michael@0: // checks etc by calling directly through to a native found on michael@0: // the prototype chain. michael@0: // michael@0: // Instead, simply do the nsXPCWrappedJS part of michael@0: // XPConvert::JSObject2NativeInterface() here to make sure we michael@0: // get a new (or used) nsXPCWrappedJS. michael@0: nsXPCWrappedJS* wrapper; michael@0: nsresult rv = nsXPCWrappedJS::GetNewOrUsed(jsobj, aIID, &wrapper); michael@0: if (NS_SUCCEEDED(rv) && wrapper) { michael@0: // We need to go through the QueryInterface logic to make michael@0: // this return the right thing for the various 'special' michael@0: // interfaces; e.g. nsIPropertyBag. michael@0: rv = wrapper->QueryInterface(aIID, aInstancePtr); michael@0: NS_RELEASE(wrapper); michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: // else... michael@0: // no can do michael@0: *aInstancePtr = nullptr; michael@0: return NS_NOINTERFACE; michael@0: } michael@0: michael@0: JSObject* michael@0: nsXPCWrappedJSClass::GetRootJSObject(JSContext* cx, JSObject* aJSObjArg) michael@0: { michael@0: RootedObject aJSObj(cx, aJSObjArg); michael@0: JSObject* result = CallQueryInterfaceOnJSObject(cx, aJSObj, michael@0: NS_GET_IID(nsISupports)); michael@0: if (!result) michael@0: return aJSObj; michael@0: JSObject* inner = js::UncheckedUnwrap(result); michael@0: if (inner) michael@0: return inner; michael@0: return result; michael@0: } michael@0: michael@0: void michael@0: xpcWrappedJSErrorReporter(JSContext *cx, const char *message, michael@0: JSErrorReport *report) michael@0: { michael@0: if (report) { michael@0: // If it is an exception report, then we can just deal with the michael@0: // exception later (if not caught in the JS code). michael@0: if (JSREPORT_IS_EXCEPTION(report->flags)) { michael@0: // XXX We have a problem with error reports from uncaught exceptions. michael@0: // michael@0: // http://bugzilla.mozilla.org/show_bug.cgi?id=66453 michael@0: // michael@0: // The issue is... michael@0: // michael@0: // We can't assume that the exception will *stay* uncaught. So, if michael@0: // we build an nsIXPCException here and the underlying exception michael@0: // really is caught before our script is done running then we blow michael@0: // it by returning failure to our caller when the script didn't michael@0: // really fail. However, This report contains error location info michael@0: // that is no longer available after the script is done. So, if the michael@0: // exception really is not caught (and is a non-engine exception) michael@0: // then we've lost the oportunity to capture the script location michael@0: // info that we *could* have captured here. michael@0: // michael@0: // This is expecially an issue with nested evaluations. michael@0: // michael@0: // Perhaps we could capture an expception here and store it as michael@0: // 'provisional' and then later if there is a pending exception michael@0: // when the script is done then we could maybe compare that in some michael@0: // way with the 'provisional' one in which we captured location info. michael@0: // We would not want to assume that the one discovered here is the michael@0: // same one that is later detected. This could cause us to lie. michael@0: // michael@0: // The thing is. we do not currently store the right stuff to compare michael@0: // these two nsIXPCExceptions (triggered by the same exception jsval michael@0: // in the engine). Maybe we should store the jsval and compare that? michael@0: // Maybe without even rooting it since we will not dereference it. michael@0: // This is inexact, but maybe the right thing to do? michael@0: // michael@0: // if (report->errorNumber == JSMSG_UNCAUGHT_EXCEPTION)) ... michael@0: // michael@0: michael@0: return; michael@0: } michael@0: michael@0: if (JSREPORT_IS_WARNING(report->flags)) { michael@0: // XXX printf the warning (#ifdef DEBUG only!). michael@0: // XXX send the warning to the console service. michael@0: return; michael@0: } michael@0: } michael@0: michael@0: XPCCallContext ccx(NATIVE_CALLER, cx); michael@0: if (!ccx.IsValid()) michael@0: return; michael@0: michael@0: nsCOMPtr e; michael@0: XPCConvert::JSErrorToXPCException(message, nullptr, nullptr, report, michael@0: getter_AddRefs(e)); michael@0: if (e) michael@0: ccx.GetXPCContext()->SetException(e); michael@0: } michael@0: michael@0: bool michael@0: nsXPCWrappedJSClass::GetArraySizeFromParam(JSContext* cx, michael@0: const XPTMethodDescriptor* method, michael@0: const nsXPTParamInfo& param, michael@0: uint16_t methodIndex, michael@0: uint8_t paramIndex, michael@0: nsXPTCMiniVariant* nativeParams, michael@0: uint32_t* result) michael@0: { michael@0: uint8_t argnum; michael@0: nsresult rv; michael@0: michael@0: rv = mInfo->GetSizeIsArgNumberForParam(methodIndex, ¶m, 0, &argnum); michael@0: if (NS_FAILED(rv)) michael@0: return false; michael@0: michael@0: const nsXPTParamInfo& arg_param = method->params[argnum]; michael@0: michael@0: // This should be enforced by the xpidl compiler, but it's not. michael@0: // See bug 695235. michael@0: MOZ_ASSERT(arg_param.GetType().TagPart() == nsXPTType::T_U32, michael@0: "size_is references parameter of invalid type."); michael@0: michael@0: if (arg_param.IsIndirect()) michael@0: *result = *(uint32_t*)nativeParams[argnum].val.p; michael@0: else michael@0: *result = nativeParams[argnum].val.u32; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsXPCWrappedJSClass::GetInterfaceTypeFromParam(JSContext* cx, michael@0: const XPTMethodDescriptor* method, michael@0: const nsXPTParamInfo& param, michael@0: uint16_t methodIndex, michael@0: const nsXPTType& type, michael@0: nsXPTCMiniVariant* nativeParams, michael@0: nsID* result) michael@0: { michael@0: uint8_t type_tag = type.TagPart(); michael@0: michael@0: if (type_tag == nsXPTType::T_INTERFACE) { michael@0: if (NS_SUCCEEDED(GetInterfaceInfo()-> michael@0: GetIIDForParamNoAlloc(methodIndex, ¶m, result))) { michael@0: return true; michael@0: } michael@0: } else if (type_tag == nsXPTType::T_INTERFACE_IS) { michael@0: uint8_t argnum; michael@0: nsresult rv; michael@0: rv = mInfo->GetInterfaceIsArgNumberForParam(methodIndex, michael@0: ¶m, &argnum); michael@0: if (NS_FAILED(rv)) michael@0: return false; michael@0: michael@0: const nsXPTParamInfo& arg_param = method->params[argnum]; michael@0: const nsXPTType& arg_type = arg_param.GetType(); michael@0: michael@0: if (arg_type.TagPart() == nsXPTType::T_IID) { michael@0: if (arg_param.IsIndirect()) { michael@0: nsID** p = (nsID**) nativeParams[argnum].val.p; michael@0: if (!p || !*p) michael@0: return false; michael@0: *result = **p; michael@0: } else { michael@0: nsID* p = (nsID*) nativeParams[argnum].val.p; michael@0: if (!p) michael@0: return false; michael@0: *result = *p; michael@0: } michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsXPCWrappedJSClass::CleanupPointerArray(const nsXPTType& datum_type, michael@0: uint32_t array_count, michael@0: void** arrayp) michael@0: { michael@0: if (datum_type.IsInterfacePointer()) { michael@0: nsISupports** pp = (nsISupports**) arrayp; michael@0: for (uint32_t k = 0; k < array_count; k++) { michael@0: nsISupports* p = pp[k]; michael@0: NS_IF_RELEASE(p); michael@0: } michael@0: } else { michael@0: void** pp = (void**) arrayp; michael@0: for (uint32_t k = 0; k < array_count; k++) { michael@0: void* p = pp[k]; michael@0: if (p) nsMemory::Free(p); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXPCWrappedJSClass::CleanupPointerTypeObject(const nsXPTType& type, michael@0: void** pp) michael@0: { michael@0: MOZ_ASSERT(pp,"null pointer"); michael@0: if (type.IsInterfacePointer()) { michael@0: nsISupports* p = *((nsISupports**)pp); michael@0: if (p) p->Release(); michael@0: } else { michael@0: void* p = *((void**)pp); michael@0: if (p) nsMemory::Free(p); michael@0: } michael@0: } michael@0: michael@0: class AutoClearPendingException michael@0: { michael@0: public: michael@0: AutoClearPendingException(JSContext *cx) : mCx(cx) { } michael@0: ~AutoClearPendingException() { JS_ClearPendingException(mCx); } michael@0: private: michael@0: JSContext* mCx; michael@0: }; michael@0: michael@0: nsresult michael@0: nsXPCWrappedJSClass::CheckForException(XPCCallContext & ccx, michael@0: const char * aPropertyName, michael@0: const char * anInterfaceName, michael@0: bool aForceReport) michael@0: { michael@0: XPCContext * xpcc = ccx.GetXPCContext(); michael@0: JSContext * cx = ccx.GetJSContext(); michael@0: nsCOMPtr xpc_exception; michael@0: /* this one would be set by our error reporter */ michael@0: michael@0: xpcc->GetException(getter_AddRefs(xpc_exception)); michael@0: if (xpc_exception) michael@0: xpcc->SetException(nullptr); michael@0: michael@0: // get this right away in case we do something below to cause JS code michael@0: // to run on this JSContext michael@0: nsresult pending_result = xpcc->GetPendingResult(); michael@0: michael@0: RootedValue js_exception(cx); michael@0: bool is_js_exception = JS_GetPendingException(cx, &js_exception); michael@0: michael@0: /* JS might throw an expection whether the reporter was called or not */ michael@0: if (is_js_exception) { michael@0: if (!xpc_exception) michael@0: XPCConvert::JSValToXPCException(&js_exception, anInterfaceName, michael@0: aPropertyName, michael@0: getter_AddRefs(xpc_exception)); michael@0: michael@0: /* cleanup and set failed even if we can't build an exception */ michael@0: if (!xpc_exception) { michael@0: XPCJSRuntime::Get()->SetPendingException(nullptr); // XXX necessary? michael@0: } michael@0: } michael@0: michael@0: AutoClearPendingException acpe(cx); michael@0: michael@0: if (xpc_exception) { michael@0: nsresult e_result; michael@0: if (NS_SUCCEEDED(xpc_exception->GetResult(&e_result))) { michael@0: // Figure out whether or not we should report this exception. michael@0: bool reportable = xpc_IsReportableErrorCode(e_result); michael@0: if (reportable) { michael@0: // Always want to report forced exceptions and XPConnect's own michael@0: // errors. michael@0: reportable = aForceReport || michael@0: NS_ERROR_GET_MODULE(e_result) == NS_ERROR_MODULE_XPCONNECT; michael@0: michael@0: // See if an environment variable was set or someone has told us michael@0: // that a user pref was set indicating that we should report all michael@0: // exceptions. michael@0: if (!reportable) michael@0: reportable = nsXPConnect::ReportAllJSExceptions(); michael@0: michael@0: // Finally, check to see if this is the last JS frame on the michael@0: // stack. If so then we always want to report it. michael@0: if (!reportable) michael@0: reportable = !JS::DescribeScriptedCaller(cx); michael@0: michael@0: // Ugly special case for GetInterface. It's "special" in the michael@0: // same way as QueryInterface in that a failure is not michael@0: // exceptional and shouldn't be reported. We have to do this michael@0: // check here instead of in xpcwrappedjs (like we do for QI) to michael@0: // avoid adding extra code to all xpcwrappedjs objects. michael@0: if (reportable && e_result == NS_ERROR_NO_INTERFACE && michael@0: !strcmp(anInterfaceName, "nsIInterfaceRequestor") && michael@0: !strcmp(aPropertyName, "getInterface")) { michael@0: reportable = false; michael@0: } michael@0: michael@0: // More special case, see bug 877760. michael@0: if (e_result == NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED) { michael@0: reportable = false; michael@0: } michael@0: } michael@0: michael@0: // Try to use the error reporter set on the context to handle this michael@0: // error if it came from a JS exception. michael@0: if (reportable && is_js_exception && michael@0: JS_GetErrorReporter(cx) != xpcWrappedJSErrorReporter) michael@0: { michael@0: // If the error reporter ignores the error, it will call michael@0: // xpc->MarkErrorUnreported(). michael@0: xpcc->ClearUnreportedError(); michael@0: reportable = !JS_ReportPendingException(cx); michael@0: if (!xpcc->WasErrorReported()) michael@0: reportable = true; michael@0: } michael@0: michael@0: if (reportable) { michael@0: if (nsContentUtils::DOMWindowDumpEnabled()) { michael@0: static const char line[] = michael@0: "************************************************************\n"; michael@0: static const char preamble[] = michael@0: "* Call to xpconnect wrapped JSObject produced this error: *\n"; michael@0: static const char cant_get_text[] = michael@0: "FAILED TO GET TEXT FROM EXCEPTION\n"; michael@0: michael@0: fputs(line, stdout); michael@0: fputs(preamble, stdout); michael@0: nsCString text; michael@0: if (NS_SUCCEEDED(xpc_exception->ToString(text)) && michael@0: !text.IsEmpty()) { michael@0: fputs(text.get(), stdout); michael@0: fputs("\n", stdout); michael@0: } else michael@0: fputs(cant_get_text, stdout); michael@0: fputs(line, stdout); michael@0: } michael@0: michael@0: // Log the exception to the JS Console, so that users can do michael@0: // something with it. michael@0: nsCOMPtr consoleService michael@0: (do_GetService(XPC_CONSOLE_CONTRACTID)); michael@0: if (nullptr != consoleService) { michael@0: nsresult rv; michael@0: nsCOMPtr scriptError; michael@0: nsCOMPtr errorData; michael@0: rv = xpc_exception->GetData(getter_AddRefs(errorData)); michael@0: if (NS_SUCCEEDED(rv)) michael@0: scriptError = do_QueryInterface(errorData); michael@0: michael@0: if (nullptr == scriptError) { michael@0: // No luck getting one from the exception, so michael@0: // try to cook one up. michael@0: scriptError = do_CreateInstance(XPC_SCRIPT_ERROR_CONTRACTID); michael@0: if (nullptr != scriptError) { michael@0: nsCString newMessage; michael@0: rv = xpc_exception->ToString(newMessage); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // try to get filename, lineno from the first michael@0: // stack frame location. michael@0: int32_t lineNumber = 0; michael@0: nsString sourceName; michael@0: michael@0: nsCOMPtr location; michael@0: xpc_exception-> michael@0: GetLocation(getter_AddRefs(location)); michael@0: if (location) { michael@0: // Get line number w/o checking; 0 is ok. michael@0: location->GetLineNumber(&lineNumber); michael@0: michael@0: // get a filename. michael@0: rv = location->GetFilename(sourceName); michael@0: } michael@0: michael@0: rv = scriptError->InitWithWindowID(NS_ConvertUTF8toUTF16(newMessage), michael@0: sourceName, michael@0: EmptyString(), michael@0: lineNumber, 0, 0, michael@0: "XPConnect JavaScript", michael@0: nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx)); michael@0: if (NS_FAILED(rv)) michael@0: scriptError = nullptr; michael@0: } michael@0: } michael@0: } michael@0: if (nullptr != scriptError) michael@0: consoleService->LogMessage(scriptError); michael@0: } michael@0: } michael@0: // Whether or not it passes the 'reportable' test, it might michael@0: // still be an error and we have to do the right thing here... michael@0: if (NS_FAILED(e_result)) { michael@0: XPCJSRuntime::Get()->SetPendingException(xpc_exception); michael@0: return e_result; michael@0: } michael@0: } michael@0: } else { michael@0: // see if JS code signaled failure result without throwing exception michael@0: if (NS_FAILED(pending_result)) { michael@0: return pending_result; michael@0: } michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXPCWrappedJSClass::CallMethod(nsXPCWrappedJS* wrapper, uint16_t methodIndex, michael@0: const XPTMethodDescriptor* info_, michael@0: nsXPTCMiniVariant* nativeParams) michael@0: { michael@0: jsval* sp = nullptr; michael@0: jsval* argv = nullptr; michael@0: uint8_t i; michael@0: nsresult retval = NS_ERROR_FAILURE; michael@0: nsresult pending_result = NS_OK; michael@0: bool success; michael@0: bool readyToDoTheCall = false; michael@0: nsID param_iid; michael@0: const nsXPTMethodInfo* info = static_cast(info_); michael@0: const char* name = info->name; michael@0: bool foundDependentParam; michael@0: michael@0: // Make sure not to set the callee on ccx until after we've gone through michael@0: // the whole nsIXPCFunctionThisTranslator bit. That code uses ccx to michael@0: // convert natives to JSObjects, but we do NOT plan to pass those JSObjects michael@0: // to our real callee. michael@0: AutoPushJSContext context(GetContextFromObjectOrDefault(wrapper)); michael@0: XPCCallContext ccx(NATIVE_CALLER, context); michael@0: if (!ccx.IsValid()) michael@0: return retval; michael@0: michael@0: XPCContext *xpcc = ccx.GetXPCContext(); michael@0: JSContext *cx = ccx.GetJSContext(); michael@0: michael@0: if (!cx || !xpcc || !IsReflectable(methodIndex)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // [implicit_jscontext] and [optional_argc] have a different calling michael@0: // convention, which we don't support for JS-implemented components. michael@0: if (info->WantsOptArgc() || info->WantsContext()) { michael@0: const char *str = "IDL methods marked with [implicit_jscontext] " michael@0: "or [optional_argc] may not be implemented in JS"; michael@0: // Throw and warn for good measure. michael@0: JS_ReportError(cx, str); michael@0: NS_WARNING(str); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: RootedValue fval(cx); michael@0: RootedObject obj(cx, wrapper->GetJSObject()); michael@0: RootedObject thisObj(cx, obj); michael@0: michael@0: JSAutoCompartment ac(cx, obj); michael@0: michael@0: AutoValueVector args(cx); michael@0: AutoScriptEvaluate scriptEval(cx); michael@0: michael@0: // XXX ASSUMES that retval is last arg. The xpidl compiler ensures this. michael@0: uint8_t paramCount = info->num_args; michael@0: uint8_t argc = paramCount - michael@0: (paramCount && XPT_PD_IS_RETVAL(info->params[paramCount-1].flags) ? 1 : 0); michael@0: michael@0: if (!scriptEval.StartEvaluating(obj, xpcWrappedJSErrorReporter)) michael@0: goto pre_call_clean_up; michael@0: michael@0: xpcc->SetPendingResult(pending_result); michael@0: xpcc->SetException(nullptr); michael@0: XPCJSRuntime::Get()->SetPendingException(nullptr); michael@0: michael@0: // We use js_Invoke so that the gcthings we use as args will be rooted by michael@0: // the engine as we do conversions and prepare to do the function call. michael@0: michael@0: // setup stack michael@0: michael@0: // if this isn't a function call then we don't need to push extra stuff michael@0: if (!(XPT_MD_IS_SETTER(info->flags) || XPT_MD_IS_GETTER(info->flags))) { michael@0: // We get fval before allocating the stack to avoid gc badness that can michael@0: // happen if the GetProperty call leaves our request and the gc runs michael@0: // while the stack we allocate contains garbage. michael@0: michael@0: // If the interface is marked as a [function] then we will assume that michael@0: // our JSObject is a function and not an object with a named method. michael@0: michael@0: bool isFunction; michael@0: if (NS_FAILED(mInfo->IsFunction(&isFunction))) michael@0: goto pre_call_clean_up; michael@0: michael@0: // In the xpidl [function] case we are making sure now that the michael@0: // JSObject is callable. If it is *not* callable then we silently michael@0: // fallback to looking up the named property... michael@0: // (because jst says he thinks this fallback is 'The Right Thing'.) michael@0: // michael@0: // In the normal (non-function) case we just lookup the property by michael@0: // name and as long as the object has such a named property we go ahead michael@0: // and try to make the call. If it turns out the named property is not michael@0: // a callable object then the JS engine will throw an error and we'll michael@0: // pass this along to the caller as an exception/result code. michael@0: michael@0: fval = ObjectValue(*obj); michael@0: if (isFunction && michael@0: JS_TypeOfValue(ccx, fval) == JSTYPE_FUNCTION) { michael@0: michael@0: // We may need to translate the 'this' for the function object. michael@0: michael@0: if (paramCount) { michael@0: const nsXPTParamInfo& firstParam = info->params[0]; michael@0: if (firstParam.IsIn()) { michael@0: const nsXPTType& firstType = firstParam.GetType(); michael@0: michael@0: if (firstType.IsInterfacePointer()) { michael@0: nsIXPCFunctionThisTranslator* translator; michael@0: michael@0: IID2ThisTranslatorMap* map = michael@0: mRuntime->GetThisTranslatorMap(); michael@0: michael@0: translator = map->Find(mIID); michael@0: michael@0: if (translator) { michael@0: nsCOMPtr newThis; michael@0: if (NS_FAILED(translator-> michael@0: TranslateThis((nsISupports*)nativeParams[0].val.p, michael@0: getter_AddRefs(newThis)))) { michael@0: goto pre_call_clean_up; michael@0: } michael@0: if (newThis) { michael@0: RootedValue v(cx); michael@0: xpcObjectHelper helper(newThis); michael@0: bool ok = michael@0: XPCConvert::NativeInterface2JSObject( michael@0: &v, nullptr, helper, nullptr, michael@0: nullptr, false, nullptr); michael@0: if (!ok) { michael@0: goto pre_call_clean_up; michael@0: } michael@0: thisObj = JSVAL_TO_OBJECT(v); michael@0: if (!JS_WrapObject(cx, &thisObj)) michael@0: goto pre_call_clean_up; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } else { michael@0: if (!JS_GetProperty(cx, obj, name, &fval)) michael@0: goto pre_call_clean_up; michael@0: // XXX We really want to factor out the error reporting better and michael@0: // specifically report the failure to find a function with this name. michael@0: // This is what we do below if the property is found but is not a michael@0: // function. We just need to factor better so we can get to that michael@0: // reporting path from here. michael@0: michael@0: thisObj = obj; michael@0: } michael@0: } michael@0: michael@0: if (!args.resize(argc)) { michael@0: retval = NS_ERROR_OUT_OF_MEMORY; michael@0: goto pre_call_clean_up; michael@0: } michael@0: michael@0: argv = args.begin(); michael@0: sp = argv; michael@0: michael@0: // build the args michael@0: // NB: This assignment *looks* wrong because we haven't yet called our michael@0: // function. However, we *have* already entered the compartmen that we're michael@0: // about to call, and that's the global that we want here. In other words: michael@0: // we're trusting the JS engine to come up with a good global to use for michael@0: // our object (whatever it was). michael@0: for (i = 0; i < argc; i++) { michael@0: const nsXPTParamInfo& param = info->params[i]; michael@0: const nsXPTType& type = param.GetType(); michael@0: nsXPTType datum_type; michael@0: uint32_t array_count; michael@0: bool isArray = type.IsArray(); michael@0: RootedValue val(cx, NullValue()); michael@0: bool isSizedString = isArray ? michael@0: false : michael@0: type.TagPart() == nsXPTType::T_PSTRING_SIZE_IS || michael@0: type.TagPart() == nsXPTType::T_PWSTRING_SIZE_IS; michael@0: michael@0: michael@0: // verify that null was not passed for 'out' param michael@0: if (param.IsOut() && !nativeParams[i].val.p) { michael@0: retval = NS_ERROR_INVALID_ARG; michael@0: goto pre_call_clean_up; michael@0: } michael@0: michael@0: if (isArray) { michael@0: if (NS_FAILED(mInfo->GetTypeForParam(methodIndex, ¶m, 1, michael@0: &datum_type))) michael@0: goto pre_call_clean_up; michael@0: } else michael@0: datum_type = type; michael@0: michael@0: if (param.IsIn()) { michael@0: nsXPTCMiniVariant* pv; michael@0: michael@0: if (param.IsIndirect()) michael@0: pv = (nsXPTCMiniVariant*) nativeParams[i].val.p; michael@0: else michael@0: pv = &nativeParams[i]; michael@0: michael@0: if (datum_type.IsInterfacePointer() && michael@0: !GetInterfaceTypeFromParam(cx, info, param, methodIndex, michael@0: datum_type, nativeParams, michael@0: ¶m_iid)) michael@0: goto pre_call_clean_up; michael@0: michael@0: if (isArray || isSizedString) { michael@0: if (!GetArraySizeFromParam(cx, info, param, methodIndex, michael@0: i, nativeParams, &array_count)) michael@0: goto pre_call_clean_up; michael@0: } michael@0: michael@0: if (isArray) { michael@0: if (!XPCConvert::NativeArray2JS(&val, michael@0: (const void**)&pv->val, michael@0: datum_type, ¶m_iid, michael@0: array_count, nullptr)) michael@0: goto pre_call_clean_up; michael@0: } else if (isSizedString) { michael@0: if (!XPCConvert::NativeStringWithSize2JS(&val, michael@0: (const void*)&pv->val, michael@0: datum_type, michael@0: array_count, nullptr)) michael@0: goto pre_call_clean_up; michael@0: } else { michael@0: if (!XPCConvert::NativeData2JS(&val, &pv->val, type, michael@0: ¶m_iid, nullptr)) michael@0: goto pre_call_clean_up; michael@0: } michael@0: } michael@0: michael@0: if (param.IsOut() || param.IsDipper()) { michael@0: // create an 'out' object michael@0: RootedObject out_obj(cx, NewOutObject(cx, obj)); michael@0: if (!out_obj) { michael@0: retval = NS_ERROR_OUT_OF_MEMORY; michael@0: goto pre_call_clean_up; michael@0: } michael@0: michael@0: if (param.IsIn()) { michael@0: if (!JS_SetPropertyById(cx, out_obj, michael@0: mRuntime->GetStringID(XPCJSRuntime::IDX_VALUE), michael@0: val)) { michael@0: goto pre_call_clean_up; michael@0: } michael@0: } michael@0: *sp++ = OBJECT_TO_JSVAL(out_obj); michael@0: } else michael@0: *sp++ = val; michael@0: } michael@0: michael@0: readyToDoTheCall = true; michael@0: michael@0: pre_call_clean_up: michael@0: // clean up any 'out' params handed in michael@0: for (i = 0; i < paramCount; i++) { michael@0: const nsXPTParamInfo& param = info->params[i]; michael@0: if (!param.IsOut()) michael@0: continue; michael@0: michael@0: const nsXPTType& type = param.GetType(); michael@0: if (!type.deprecated_IsPointer()) michael@0: continue; michael@0: void* p; michael@0: if (!(p = nativeParams[i].val.p)) michael@0: continue; michael@0: michael@0: if (param.IsIn()) { michael@0: if (type.IsArray()) { michael@0: void** pp; michael@0: if (nullptr != (pp = *((void***)p))) { michael@0: michael@0: // we need to get the array length and iterate the items michael@0: uint32_t array_count; michael@0: nsXPTType datum_type; michael@0: michael@0: if (NS_SUCCEEDED(mInfo->GetTypeForParam(methodIndex, ¶m, michael@0: 1, &datum_type)) && michael@0: datum_type.deprecated_IsPointer() && michael@0: GetArraySizeFromParam(cx, info, param, methodIndex, michael@0: i, nativeParams, &array_count) && michael@0: array_count) { michael@0: michael@0: CleanupPointerArray(datum_type, array_count, pp); michael@0: } michael@0: michael@0: // always release the array if it is inout michael@0: nsMemory::Free(pp); michael@0: } michael@0: } else michael@0: CleanupPointerTypeObject(type, (void**)p); michael@0: } michael@0: *((void**)p) = nullptr; michael@0: } michael@0: michael@0: // Make sure "this" doesn't get deleted during this call. michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: if (!readyToDoTheCall) michael@0: return retval; michael@0: michael@0: // do the deed - note exceptions michael@0: michael@0: JS_ClearPendingException(cx); michael@0: michael@0: RootedValue rval(cx); michael@0: if (XPT_MD_IS_GETTER(info->flags)) { michael@0: success = JS_GetProperty(cx, obj, name, &rval); michael@0: } else if (XPT_MD_IS_SETTER(info->flags)) { michael@0: rval = *argv; michael@0: success = JS_SetProperty(cx, obj, name, rval); michael@0: } else { michael@0: if (!JSVAL_IS_PRIMITIVE(fval)) { michael@0: AutoSaveContextOptions asco(cx); michael@0: ContextOptionsRef(cx).setDontReportUncaught(true); michael@0: michael@0: success = JS_CallFunctionValue(cx, thisObj, fval, args, &rval); michael@0: } else { michael@0: // The property was not an object so can't be a function. michael@0: // Let's build and 'throw' an exception. michael@0: michael@0: static const nsresult code = michael@0: NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED; michael@0: static const char format[] = "%s \"%s\""; michael@0: const char * msg; michael@0: char* sz = nullptr; michael@0: michael@0: if (nsXPCException::NameAndFormatForNSResult(code, nullptr, &msg) && msg) michael@0: sz = JS_smprintf(format, msg, name); michael@0: michael@0: nsCOMPtr e; michael@0: michael@0: XPCConvert::ConstructException(code, sz, GetInterfaceName(), name, michael@0: nullptr, getter_AddRefs(e), nullptr, nullptr); michael@0: xpcc->SetException(e); michael@0: if (sz) michael@0: JS_smprintf_free(sz); michael@0: success = false; michael@0: } michael@0: } michael@0: michael@0: if (!success) { michael@0: bool forceReport; michael@0: if (NS_FAILED(mInfo->IsFunction(&forceReport))) michael@0: forceReport = false; michael@0: michael@0: // May also want to check if we're moving from content->chrome and force michael@0: // a report in that case. michael@0: michael@0: return CheckForException(ccx, name, GetInterfaceName(), forceReport); michael@0: } michael@0: michael@0: XPCJSRuntime::Get()->SetPendingException(nullptr); // XXX necessary? michael@0: michael@0: // convert out args and result michael@0: // NOTE: this is the total number of native params, not just the args michael@0: // Convert independent params only. michael@0: // When we later convert the dependent params (if any) we will know that michael@0: // the params upon which they depend will have already been converted - michael@0: // regardless of ordering. michael@0: michael@0: foundDependentParam = false; michael@0: for (i = 0; i < paramCount; i++) { michael@0: const nsXPTParamInfo& param = info->params[i]; michael@0: MOZ_ASSERT(!param.IsShared(), "[shared] implies [noscript]!"); michael@0: if (!param.IsOut() && !param.IsDipper()) michael@0: continue; michael@0: michael@0: const nsXPTType& type = param.GetType(); michael@0: if (type.IsDependent()) { michael@0: foundDependentParam = true; michael@0: continue; michael@0: } michael@0: michael@0: RootedValue val(cx); michael@0: uint8_t type_tag = type.TagPart(); michael@0: nsXPTCMiniVariant* pv; michael@0: michael@0: if (param.IsDipper()) michael@0: pv = (nsXPTCMiniVariant*) &nativeParams[i].val.p; michael@0: else michael@0: pv = (nsXPTCMiniVariant*) nativeParams[i].val.p; michael@0: michael@0: if (param.IsRetval()) michael@0: val = rval; michael@0: else if (argv[i].isPrimitive()) michael@0: break; michael@0: else { michael@0: RootedObject obj(cx, &argv[i].toObject()); michael@0: if (!JS_GetPropertyById(cx, obj, michael@0: mRuntime->GetStringID(XPCJSRuntime::IDX_VALUE), michael@0: &val)) michael@0: break; michael@0: } michael@0: michael@0: // setup allocator and/or iid michael@0: michael@0: if (type_tag == nsXPTType::T_INTERFACE) { michael@0: if (NS_FAILED(GetInterfaceInfo()-> michael@0: GetIIDForParamNoAlloc(methodIndex, ¶m, michael@0: ¶m_iid))) michael@0: break; michael@0: } michael@0: michael@0: // see bug #961488 michael@0: #if (defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(_AIX)) && \ michael@0: ((defined(__sparc) && !defined(__sparcv9) && !defined(__sparcv9__)) || \ michael@0: (defined(__powerpc__) && !defined (__powerpc64__))) michael@0: if (type_tag == nsXPTType::T_JSVAL) { michael@0: if (!XPCConvert::JSData2Native(*(void**)(&pv->val), val, type, michael@0: !param.IsDipper(), ¶m_iid, nullptr)) michael@0: break; michael@0: } else michael@0: #endif michael@0: { michael@0: if (!XPCConvert::JSData2Native(&pv->val, val, type, michael@0: !param.IsDipper(), ¶m_iid, nullptr)) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // if any params were dependent, then we must iterate again to convert them. michael@0: if (foundDependentParam && i == paramCount) { michael@0: for (i = 0; i < paramCount; i++) { michael@0: const nsXPTParamInfo& param = info->params[i]; michael@0: if (!param.IsOut()) michael@0: continue; michael@0: michael@0: const nsXPTType& type = param.GetType(); michael@0: if (!type.IsDependent()) michael@0: continue; michael@0: michael@0: RootedValue val(cx); michael@0: nsXPTCMiniVariant* pv; michael@0: nsXPTType datum_type; michael@0: uint32_t array_count; michael@0: bool isArray = type.IsArray(); michael@0: bool isSizedString = isArray ? michael@0: false : michael@0: type.TagPart() == nsXPTType::T_PSTRING_SIZE_IS || michael@0: type.TagPart() == nsXPTType::T_PWSTRING_SIZE_IS; michael@0: michael@0: pv = (nsXPTCMiniVariant*) nativeParams[i].val.p; michael@0: michael@0: if (param.IsRetval()) michael@0: val = rval; michael@0: else { michael@0: RootedObject obj(cx, &argv[i].toObject()); michael@0: if (!JS_GetPropertyById(cx, obj, michael@0: mRuntime->GetStringID(XPCJSRuntime::IDX_VALUE), michael@0: &val)) michael@0: break; michael@0: } michael@0: michael@0: // setup allocator and/or iid michael@0: michael@0: if (isArray) { michael@0: if (NS_FAILED(mInfo->GetTypeForParam(methodIndex, ¶m, 1, michael@0: &datum_type))) michael@0: break; michael@0: } else michael@0: datum_type = type; michael@0: michael@0: if (datum_type.IsInterfacePointer()) { michael@0: if (!GetInterfaceTypeFromParam(cx, info, param, methodIndex, michael@0: datum_type, nativeParams, michael@0: ¶m_iid)) michael@0: break; michael@0: } michael@0: michael@0: if (isArray || isSizedString) { michael@0: if (!GetArraySizeFromParam(cx, info, param, methodIndex, michael@0: i, nativeParams, &array_count)) michael@0: break; michael@0: } michael@0: michael@0: if (isArray) { michael@0: if (array_count && michael@0: !XPCConvert::JSArray2Native((void**)&pv->val, val, michael@0: array_count, datum_type, michael@0: ¶m_iid, nullptr)) michael@0: break; michael@0: } else if (isSizedString) { michael@0: if (!XPCConvert::JSStringWithSize2Native((void*)&pv->val, val, michael@0: array_count, datum_type, michael@0: nullptr)) michael@0: break; michael@0: } else { michael@0: if (!XPCConvert::JSData2Native(&pv->val, val, type, michael@0: true, ¶m_iid, michael@0: nullptr)) michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (i != paramCount) { michael@0: // We didn't manage all the result conversions! michael@0: // We have to cleanup any junk that *did* get converted. michael@0: michael@0: for (uint8_t k = 0; k < i; k++) { michael@0: const nsXPTParamInfo& param = info->params[k]; michael@0: if (!param.IsOut()) michael@0: continue; michael@0: const nsXPTType& type = param.GetType(); michael@0: if (!type.deprecated_IsPointer()) michael@0: continue; michael@0: void* p; michael@0: if (!(p = nativeParams[k].val.p)) michael@0: continue; michael@0: michael@0: if (type.IsArray()) { michael@0: void** pp; michael@0: if (nullptr != (pp = *((void***)p))) { michael@0: // we need to get the array length and iterate the items michael@0: uint32_t array_count; michael@0: nsXPTType datum_type; michael@0: michael@0: if (NS_SUCCEEDED(mInfo->GetTypeForParam(methodIndex, ¶m, michael@0: 1, &datum_type)) && michael@0: datum_type.deprecated_IsPointer() && michael@0: GetArraySizeFromParam(cx, info, param, methodIndex, michael@0: k, nativeParams, &array_count) && michael@0: array_count) { michael@0: michael@0: CleanupPointerArray(datum_type, array_count, pp); michael@0: } michael@0: nsMemory::Free(pp); michael@0: } michael@0: } else michael@0: CleanupPointerTypeObject(type, (void**)p); michael@0: *((void**)p) = nullptr; michael@0: } michael@0: } else { michael@0: // set to whatever the JS code might have set as the result michael@0: retval = pending_result; michael@0: } michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: const char* michael@0: nsXPCWrappedJSClass::GetInterfaceName() michael@0: { michael@0: if (!mName) michael@0: mInfo->GetName(&mName); michael@0: return mName; michael@0: } michael@0: michael@0: static void michael@0: FinalizeStub(JSFreeOp *fop, JSObject *obj) michael@0: { michael@0: } michael@0: michael@0: static const JSClass XPCOutParamClass = { michael@0: "XPCOutParam", michael@0: 0, michael@0: JS_PropertyStub, michael@0: JS_DeletePropertyStub, michael@0: JS_PropertyStub, michael@0: JS_StrictPropertyStub, michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: FinalizeStub, michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: nullptr /* trace */ michael@0: }; michael@0: michael@0: bool michael@0: xpc::IsOutObject(JSContext* cx, JSObject* obj) michael@0: { michael@0: return js::GetObjectJSClass(obj) == &XPCOutParamClass; michael@0: } michael@0: michael@0: JSObject* michael@0: xpc::NewOutObject(JSContext* cx, JSObject* scope) michael@0: { michael@0: RootedObject global(cx, JS_GetGlobalForObject(cx, scope)); michael@0: return JS_NewObject(cx, nullptr, NullPtr(), global); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsXPCWrappedJSClass::DebugDump(int16_t depth) michael@0: { michael@0: #ifdef DEBUG michael@0: depth-- ; michael@0: XPC_LOG_ALWAYS(("nsXPCWrappedJSClass @ %x with mRefCnt = %d", this, mRefCnt.get())); michael@0: XPC_LOG_INDENT(); michael@0: char* name; michael@0: mInfo->GetName(&name); michael@0: XPC_LOG_ALWAYS(("interface name is %s", name)); michael@0: if (name) michael@0: nsMemory::Free(name); michael@0: char * iid = mIID.ToString(); michael@0: XPC_LOG_ALWAYS(("IID number is %s", iid ? iid : "invalid")); michael@0: if (iid) michael@0: NS_Free(iid); michael@0: XPC_LOG_ALWAYS(("InterfaceInfo @ %x", mInfo.get())); michael@0: uint16_t methodCount = 0; michael@0: if (depth) { michael@0: uint16_t i; michael@0: nsCOMPtr parent; michael@0: XPC_LOG_INDENT(); michael@0: mInfo->GetParent(getter_AddRefs(parent)); michael@0: XPC_LOG_ALWAYS(("parent @ %x", parent.get())); michael@0: mInfo->GetMethodCount(&methodCount); michael@0: XPC_LOG_ALWAYS(("MethodCount = %d", methodCount)); michael@0: mInfo->GetConstantCount(&i); michael@0: XPC_LOG_ALWAYS(("ConstantCount = %d", i)); michael@0: XPC_LOG_OUTDENT(); michael@0: } michael@0: XPC_LOG_ALWAYS(("mRuntime @ %x", mRuntime)); michael@0: XPC_LOG_ALWAYS(("mDescriptors @ %x count = %d", mDescriptors, methodCount)); michael@0: if (depth && mDescriptors && methodCount) { michael@0: depth--; michael@0: XPC_LOG_INDENT(); michael@0: for (uint16_t i = 0; i < methodCount; i++) { michael@0: XPC_LOG_ALWAYS(("Method %d is %s%s", \ michael@0: i, IsReflectable(i) ? "":" NOT ","reflectable")); michael@0: } michael@0: XPC_LOG_OUTDENT(); michael@0: depth++; michael@0: } michael@0: XPC_LOG_OUTDENT(); michael@0: #endif michael@0: return NS_OK; michael@0: }