/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* JavaScript JSClasses and JSOps for our Wrapped Native JS Objects. */ #include "xpcprivate.h" #include "xpc_make_class.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/Maybe.h" #include "mozilla/Preferences.h" #include "js/CharacterEncoding.h" #include "js/Class.h" #include "js/Object.h" // JS::GetClass #include "js/Printf.h" #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById, JS_GetProperty, JS_GetPropertyById #include "js/Symbol.h" #include using namespace mozilla; using namespace JS; using namespace xpc; /***************************************************************************/ // All of the exceptions thrown into JS from this file go through here. // That makes this a nice place to set a breakpoint. static bool Throw(nsresult errNum, JSContext* cx) { XPCThrower::Throw(errNum, cx); return false; } // Handy macro used in many callback stub below. #define THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper) \ PR_BEGIN_MACRO \ if (!wrapper) return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); \ if (!wrapper->IsValid()) return Throw(NS_ERROR_XPC_HAS_BEEN_SHUTDOWN, cx); \ PR_END_MACRO /***************************************************************************/ static bool ToStringGuts(XPCCallContext& ccx) { UniqueChars sz; XPCWrappedNative* wrapper = ccx.GetWrapper(); if (wrapper) { sz.reset(wrapper->ToString(ccx.GetTearOff())); } else { sz = JS_smprintf("[xpconnect wrapped native prototype]"); } if (!sz) { JS_ReportOutOfMemory(ccx); return false; } JSString* str = JS_NewStringCopyZ(ccx, sz.get()); if (!str) { return false; } ccx.SetRetVal(JS::StringValue(str)); return true; } /***************************************************************************/ static bool XPC_WN_Shared_ToString(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj(cx); if (!args.computeThis(cx, &obj)) { return false; } XPCCallContext ccx(cx, obj); if (!ccx.IsValid()) { return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); } ccx.SetName(ccx.GetContext()->GetStringID(XPCJSContext::IDX_TO_STRING)); ccx.SetArgsAndResultPtr(args.length(), args.array(), vp); return ToStringGuts(ccx); } static bool XPC_WN_Shared_ToSource(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); static constexpr std::string_view empty = "({})"; JSString* str = JS_NewStringCopyN(cx, empty.data(), empty.length()); if (!str) { return false; } args.rval().setString(str); return true; } static bool XPC_WN_Shared_toPrimitive(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedObject obj(cx); if (!JS_ValueToObject(cx, args.thisv(), &obj)) { return false; } XPCCallContext ccx(cx, obj); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); JSType hint; if (!GetFirstArgumentAsTypeHint(cx, args, &hint)) { return false; } if (hint == JSTYPE_NUMBER) { args.rval().set(NaNValue()); return true; } MOZ_ASSERT(hint == JSTYPE_STRING || hint == JSTYPE_UNDEFINED); ccx.SetName(ccx.GetContext()->GetStringID(XPCJSContext::IDX_TO_STRING)); ccx.SetArgsAndResultPtr(0, nullptr, args.rval().address()); XPCNativeMember* member = ccx.GetMember(); if (member && member->IsMethod()) { if (!XPCWrappedNative::CallMethod(ccx)) { return false; } if (args.rval().isPrimitive()) { return true; } } // else... return ToStringGuts(ccx); } /***************************************************************************/ // A "double wrapped object" is a user JSObject that has been wrapped as a // wrappedJS in order to be used by native code and then re-wrapped by a // wrappedNative wrapper to be used by JS code. One might think of it as: // wrappedNative(wrappedJS(underlying_JSObject)) // This is done (as opposed to just unwrapping the wrapped JS and automatically // returning the underlying JSObject) so that JS callers will see what looks // Like any other xpcom object - and be limited to use its interfaces. // /** * When JavaScript code uses a component that is itself implemented in * JavaScript then XPConnect will build a wrapper rather than directly * expose the JSObject of the component. This allows components implemented * in JavaScript to 'look' just like any other xpcom component (from the * perspective of the JavaScript caller). This insulates the component from * the caller and hides any properties or methods that are not part of the * interface as declared in xpidl. Usually this is a good thing. * * However, in some cases it is useful to allow the JS caller access to the * JS component's underlying implementation. In order to facilitate this * XPConnect supports the 'wrappedJSObject' property. This 'wrappedJSObject' * property is different than the XrayWrapper meaning. (The naming collision * avoids having more than one magic XPConnect property name, but is * confusing.) * * The caller code can do: * * // 'foo' is some xpcom component (that might be implemented in JS). * var bar = foo.wrappedJSObject; * if(bar) { * // bar is the underlying JSObject. Do stuff with it here. * } * * Recall that 'foo' above is an XPConnect wrapper, not the underlying JS * object. The property get "foo.wrappedJSObject" will only succeed if three * conditions are met: * * 1) 'foo' really is an XPConnect wrapper around a JSObject. * 3) The caller must be system JS and not content. Double-wrapped XPCWJS should * not be exposed to content except with a remote-XUL domain. * * Notes: * * a) If 'foo' above were the underlying JSObject and not a wrapper at all, * then this all just works and XPConnect is not part of the picture at all. * b) One might ask why 'foo' should not just implement an interface through * which callers might get at the underlying object. There are two reasons: * i) XPConnect would still have to do magic since JSObject is not a * scriptable type. * ii) Avoiding the explicit interface makes it easier for both the caller * and the component. */ static JSObject* GetDoubleWrappedJSObject(XPCCallContext& ccx, XPCWrappedNative* wrapper) { RootedObject obj(ccx); { nsCOMPtr underware = do_QueryInterface(wrapper->GetIdentityObject()); if (!underware) { return nullptr; } RootedObject mainObj(ccx, underware->GetJSObject()); if (mainObj) { JSAutoRealm ar(ccx, underware->GetJSObjectGlobal()); // We don't have to root this ID, as it's already rooted by our context. HandleId id = ccx.GetContext()->GetStringID(XPCJSContext::IDX_WRAPPED_JSOBJECT); // If the `wrappedJSObject` property is defined, use the result of getting // that property, otherwise fall back to the `mainObj` object which is // directly being wrapped. RootedValue val(ccx); if (JS_GetPropertyById(ccx, mainObj, id, &val) && !val.isPrimitive()) { obj = val.toObjectOrNull(); } else { obj = mainObj; } } } return obj; } // This is the getter native function we use to handle 'wrappedJSObject' for // double wrapped JSObjects. static bool XPC_WN_DoubleWrappedGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (!args.thisv().isObject()) { JS_ReportErrorASCII( cx, "xpconnect double wrapped getter called on incompatible non-object"); return false; } RootedObject obj(cx, &args.thisv().toObject()); XPCCallContext ccx(cx, obj); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); MOZ_ASSERT(JS_TypeOfValue(cx, args.calleev()) == JSTYPE_FUNCTION, "bad function"); RootedObject realObject(cx, GetDoubleWrappedJSObject(ccx, wrapper)); if (!realObject) { // This is pretty unexpected at this point. The object originally // responded to this get property call and now gives no object. // XXX Should this throw something at the caller? args.rval().setNull(); return true; } // It is a double wrapped object. This should really never appear in // content these days, but addons still do it - see bug 965921. if (MOZ_UNLIKELY(!nsContentUtils::IsSystemCaller(cx))) { JS_ReportErrorASCII(cx, "Attempt to use .wrappedJSObject in untrusted code"); return false; } args.rval().setObject(*realObject); return JS_WrapValue(cx, args.rval()); } /***************************************************************************/ // This is our shared function to define properties on our JSObjects. /* * NOTE: * We *never* set the tearoff names (e.g. nsIFoo) as JS_ENUMERATE. * We *never* set toString or toSource as JS_ENUMERATE. */ static bool DefinePropertyIfFound( XPCCallContext& ccx, HandleObject obj, HandleId idArg, XPCNativeSet* set, XPCNativeInterface* ifaceArg, XPCNativeMember* member, XPCWrappedNativeScope* scope, bool reflectToStringAndToSource, XPCWrappedNative* wrapperToReflectInterfaceNames, XPCWrappedNative* wrapperToReflectDoubleWrap, nsIXPCScriptable* scr, unsigned propFlags, bool* resolved) { RootedId id(ccx, idArg); RefPtr iface = ifaceArg; XPCJSContext* xpccx = ccx.GetContext(); bool found; const char* name; propFlags |= JSPROP_RESOLVING; if (set) { if (iface) { found = true; } else { found = set->FindMember(id, &member, &iface); } } else found = (nullptr != (member = iface->FindMember(id))); if (!found) { if (reflectToStringAndToSource) { JSNative call; if (id == xpccx->GetStringID(XPCJSContext::IDX_TO_STRING)) { call = XPC_WN_Shared_ToString; name = xpccx->GetStringName(XPCJSContext::IDX_TO_STRING); } else if (id == xpccx->GetStringID(XPCJSContext::IDX_TO_SOURCE)) { call = XPC_WN_Shared_ToSource; name = xpccx->GetStringName(XPCJSContext::IDX_TO_SOURCE); } else if (id.isWellKnownSymbol(JS::SymbolCode::toPrimitive)) { call = XPC_WN_Shared_toPrimitive; name = "[Symbol.toPrimitive]"; } else { call = nullptr; } if (call) { RootedFunction fun(ccx, JS_NewFunction(ccx, call, 0, 0, name)); if (!fun) { JS_ReportOutOfMemory(ccx); return false; } AutoResolveName arn(ccx, id); if (resolved) { *resolved = true; } RootedObject value(ccx, JS_GetFunctionObject(fun)); return JS_DefinePropertyById(ccx, obj, id, value, propFlags & ~JSPROP_ENUMERATE); } } // This *might* be a tearoff name that is not yet part of our // set. Let's lookup the name and see if it is the name of an // interface. Then we'll see if the object actually *does* this // interface and add a tearoff as necessary. if (wrapperToReflectInterfaceNames) { JS::UniqueChars name; RefPtr iface2; XPCWrappedNativeTearOff* to; RootedObject jso(ccx); nsresult rv = NS_OK; bool defineProperty = false; do { if (!id.isString()) { break; } name = JS_EncodeStringToLatin1(ccx, id.toString()); if (!name) { break; } iface2 = XPCNativeInterface::GetNewOrUsed(ccx, name.get()); if (!iface2) { break; } to = wrapperToReflectInterfaceNames->FindTearOff(ccx, iface2, true, &rv); if (!to) { break; } jso = to->GetJSObject(); if (!jso) { break; } defineProperty = true; } while (false); if (defineProperty) { AutoResolveName arn(ccx, id); if (resolved) { *resolved = true; } return JS_DefinePropertyById(ccx, obj, id, jso, propFlags & ~JSPROP_ENUMERATE); } else if (NS_FAILED(rv) && rv != NS_ERROR_NO_INTERFACE) { return Throw(rv, ccx); } } // This *might* be a double wrapped JSObject if (wrapperToReflectDoubleWrap && id == xpccx->GetStringID(XPCJSContext::IDX_WRAPPED_JSOBJECT) && GetDoubleWrappedJSObject(ccx, wrapperToReflectDoubleWrap)) { // We build and add a getter function. // A security check is done on a per-get basis. JSFunction* fun; id = xpccx->GetStringID(XPCJSContext::IDX_WRAPPED_JSOBJECT); name = xpccx->GetStringName(XPCJSContext::IDX_WRAPPED_JSOBJECT); fun = JS_NewFunction(ccx, XPC_WN_DoubleWrappedGetter, 0, 0, name); if (!fun) { return false; } RootedObject funobj(ccx, JS_GetFunctionObject(fun)); if (!funobj) { return false; } propFlags &= ~JSPROP_ENUMERATE; AutoResolveName arn(ccx, id); if (resolved) { *resolved = true; } return JS_DefinePropertyById(ccx, obj, id, funobj, nullptr, propFlags); } if (resolved) { *resolved = false; } return true; } if (!member) { if (wrapperToReflectInterfaceNames) { XPCWrappedNativeTearOff* to = wrapperToReflectInterfaceNames->FindTearOff(ccx, iface, true); if (!to) { return false; } RootedObject jso(ccx, to->GetJSObject()); if (!jso) { return false; } AutoResolveName arn(ccx, id); if (resolved) { *resolved = true; } return JS_DefinePropertyById(ccx, obj, id, jso, propFlags & ~JSPROP_ENUMERATE); } if (resolved) { *resolved = false; } return true; } if (member->IsConstant()) { RootedValue val(ccx); AutoResolveName arn(ccx, id); if (resolved) { *resolved = true; } return member->GetConstantValue(ccx, iface, val.address()) && JS_DefinePropertyById(ccx, obj, id, val, propFlags); } if (id == xpccx->GetStringID(XPCJSContext::IDX_TO_STRING) || id == xpccx->GetStringID(XPCJSContext::IDX_TO_SOURCE) || (scr && scr->DontEnumQueryInterface() && id == xpccx->GetStringID(XPCJSContext::IDX_QUERY_INTERFACE))) propFlags &= ~JSPROP_ENUMERATE; RootedValue funval(ccx); if (!member->NewFunctionObject(ccx, iface, obj, funval.address())) { return false; } if (member->IsMethod()) { AutoResolveName arn(ccx, id); if (resolved) { *resolved = true; } return JS_DefinePropertyById(ccx, obj, id, funval, propFlags); } // else... MOZ_ASSERT(member->IsAttribute(), "way broken!"); propFlags &= ~JSPROP_READONLY; RootedObject funobjGetter(ccx, funval.toObjectOrNull()); RootedObject funobjSetter(ccx); if (member->IsWritableAttribute()) { funobjSetter = funobjGetter; } AutoResolveName arn(ccx, id); if (resolved) { *resolved = true; } return JS_DefinePropertyById(ccx, obj, id, funobjGetter, funobjSetter, propFlags); } /***************************************************************************/ /***************************************************************************/ static bool XPC_WN_OnlyIWrite_AddPropertyStub(JSContext* cx, HandleObject obj, HandleId id, HandleValue v) { XPCCallContext ccx(cx, obj, nullptr, id); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); // Allow only XPConnect to add/set the property if (ccx.GetResolveName() == id) { return true; } return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); } bool XPC_WN_CannotModifyPropertyStub(JSContext* cx, HandleObject obj, HandleId id, HandleValue v) { return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); } bool XPC_WN_CannotDeletePropertyStub(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result) { return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); } bool XPC_WN_Shared_Enumerate(JSContext* cx, HandleObject obj) { XPCCallContext ccx(cx, obj); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); // Since we aren't going to enumerate tearoff names and the prototype // handles non-mutated members, we can do this potential short-circuit. if (!wrapper->HasMutatedSet()) { return true; } XPCNativeSet* set = wrapper->GetSet(); XPCNativeSet* protoSet = wrapper->HasProto() ? wrapper->GetProto()->GetSet() : nullptr; uint16_t interface_count = set->GetInterfaceCount(); XPCNativeInterface** interfaceArray = set->GetInterfaceArray(); for (uint16_t i = 0; i < interface_count; i++) { XPCNativeInterface* iface = interfaceArray[i]; uint16_t member_count = iface->GetMemberCount(); for (uint16_t k = 0; k < member_count; k++) { XPCNativeMember* member = iface->GetMemberAt(k); jsid name = member->GetName(); // Skip if this member is going to come from the proto. uint16_t index; if (protoSet && protoSet->FindMember(name, nullptr, &index) && index == i) continue; JS_MarkCrossZoneId(cx, name); if (!xpc_ForcePropertyResolve(cx, obj, name)) { return false; } } } return true; } /***************************************************************************/ enum WNHelperType { WN_NOHELPER, WN_HELPER }; static void WrappedNativeFinalize(JS::GCContext* gcx, JSObject* obj, WNHelperType helperType) { const JSClass* clazz = JS::GetClass(obj); if (clazz->flags & JSCLASS_DOM_GLOBAL) { mozilla::dom::DestroyProtoAndIfaceCache(obj); } XPCWrappedNative* wrapper = JS::GetObjectISupports(obj); if (!wrapper) { return; } if (helperType == WN_HELPER) { wrapper->GetScriptable()->Finalize(wrapper, gcx, obj); } wrapper->FlatJSObjectFinalized(); } static size_t WrappedNativeObjectMoved(JSObject* obj, JSObject* old) { XPCWrappedNative* wrapper = JS::GetObjectISupports(obj); if (!wrapper) { return 0; } wrapper->FlatJSObjectMoved(obj, old); return 0; } void XPC_WN_NoHelper_Finalize(JS::GCContext* gcx, JSObject* obj) { WrappedNativeFinalize(gcx, obj, WN_NOHELPER); } /* * General comment about XPConnect tracing: Given a C++ object |wrapper| and its * corresponding JS object |obj|, calling |wrapper->TraceSelf| will ask the JS * engine to mark |obj|. Eventually, this will lead to the trace hook being * called for |obj|. The trace hook should call |wrapper->TraceInside|, which * should mark any JS objects held by |wrapper| as members. */ /* static */ void XPCWrappedNative::Trace(JSTracer* trc, JSObject* obj) { const JSClass* clazz = JS::GetClass(obj); if (clazz->flags & JSCLASS_DOM_GLOBAL) { mozilla::dom::TraceProtoAndIfaceCache(trc, obj); } MOZ_ASSERT(clazz->isWrappedNative()); XPCWrappedNative* wrapper = XPCWrappedNative::Get(obj); if (wrapper && wrapper->IsValid()) { wrapper->TraceInside(trc); } } void XPCWrappedNative_Trace(JSTracer* trc, JSObject* obj) { XPCWrappedNative::Trace(trc, obj); } static bool XPC_WN_NoHelper_Resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) { XPCCallContext ccx(cx, obj, nullptr, id); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); XPCNativeSet* set = ccx.GetSet(); if (!set) { return true; } // Don't resolve properties that are on our prototype. if (ccx.GetInterface() && !ccx.GetStaticMemberIsLocal()) { return true; } return DefinePropertyIfFound( ccx, obj, id, set, nullptr, nullptr, wrapper->GetScope(), true, wrapper, wrapper, nullptr, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT, resolvedp); } static const JSClassOps XPC_WN_NoHelper_JSClassOps = { XPC_WN_OnlyIWrite_AddPropertyStub, // addProperty XPC_WN_CannotDeletePropertyStub, // delProperty XPC_WN_Shared_Enumerate, // enumerate nullptr, // newEnumerate XPC_WN_NoHelper_Resolve, // resolve nullptr, // mayResolve XPC_WN_NoHelper_Finalize, // finalize nullptr, // call nullptr, // construct XPCWrappedNative::Trace, // trace }; const js::ClassExtension XPC_WN_JSClassExtension = { WrappedNativeObjectMoved, // objectMovedOp }; const JSClass XPC_WN_NoHelper_JSClass = { "XPCWrappedNative_NoHelper", JSCLASS_IS_WRAPPED_NATIVE | JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_SLOT0_IS_NSISUPPORTS | JSCLASS_FOREGROUND_FINALIZE, &XPC_WN_NoHelper_JSClassOps, JS_NULL_CLASS_SPEC, &XPC_WN_JSClassExtension, JS_NULL_OBJECT_OPS}; /***************************************************************************/ bool XPC_WN_MaybeResolvingPropertyStub(JSContext* cx, HandleObject obj, HandleId id, HandleValue v) { XPCCallContext ccx(cx, obj); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); if (ccx.GetResolvingWrapper() == wrapper) { return true; } return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); } bool XPC_WN_MaybeResolvingDeletePropertyStub(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result) { XPCCallContext ccx(cx, obj); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); if (ccx.GetResolvingWrapper() == wrapper) { return result.succeed(); } return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); } // macro fun! #define PRE_HELPER_STUB \ /* It's very important for "unwrapped" to be rooted here. */ \ RootedObject unwrapped(cx, js::CheckedUnwrapDynamic(obj, cx, false)); \ if (!unwrapped) { \ JS_ReportErrorASCII(cx, "Permission denied to operate on object."); \ return false; \ } \ if (!IsWrappedNativeReflector(unwrapped)) { \ return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); \ } \ XPCWrappedNative* wrapper = XPCWrappedNative::Get(unwrapped); \ THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); \ bool retval = true; \ nsresult rv = wrapper->GetScriptable()-> #define POST_HELPER_STUB \ if (NS_FAILED(rv)) return Throw(rv, cx); \ return retval; bool XPC_WN_Helper_Call(JSContext* cx, unsigned argc, Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); // N.B. we want obj to be the callee, not JS_THIS(cx, vp) RootedObject obj(cx, &args.callee()); XPCCallContext ccx(cx, obj, nullptr, JS::VoidHandlePropertyKey, args.length(), args.array(), args.rval().address()); if (!ccx.IsValid()) { return false; } PRE_HELPER_STUB Call(wrapper, cx, obj, args, &retval); POST_HELPER_STUB } bool XPC_WN_Helper_Construct(JSContext* cx, unsigned argc, Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); RootedObject obj(cx, &args.callee()); if (!obj) { return false; } XPCCallContext ccx(cx, obj, nullptr, JS::VoidHandlePropertyKey, args.length(), args.array(), args.rval().address()); if (!ccx.IsValid()) { return false; } PRE_HELPER_STUB Construct(wrapper, cx, obj, args, &retval); POST_HELPER_STUB } static bool XPC_WN_Helper_HasInstance(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (!args.requireAtLeast(cx, "WrappedNative[Symbol.hasInstance]", 1)) { return false; } if (!args.thisv().isObject()) { JS_ReportErrorASCII( cx, "WrappedNative[Symbol.hasInstance]: unexpected this value"); return false; } RootedObject obj(cx, &args.thisv().toObject()); RootedValue val(cx, args.get(0)); bool retval2; PRE_HELPER_STUB HasInstance(wrapper, cx, obj, val, &retval2, &retval); args.rval().setBoolean(retval2); POST_HELPER_STUB } void XPC_WN_Helper_Finalize(JS::GCContext* gcx, JSObject* obj) { WrappedNativeFinalize(gcx, obj, WN_HELPER); } // RAII class used to store the wrapper in the context when resolving a lazy // property on its JS reflector. This is used by XPC_WN_MaybeResolving to allow // adding properties while resolving. class MOZ_RAII AutoSetResolvingWrapper { public: AutoSetResolvingWrapper(XPCCallContext& ccx, XPCWrappedNative* wrapper) : mCcx(ccx), mOldResolvingWrapper(ccx.SetResolvingWrapper(wrapper)) {} ~AutoSetResolvingWrapper() { (void)mCcx.SetResolvingWrapper(mOldResolvingWrapper); } private: XPCCallContext& mCcx; XPCWrappedNative* mOldResolvingWrapper; }; bool XPC_WN_Helper_Resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) { nsresult rv = NS_OK; bool retval = true; bool resolved = false; XPCCallContext ccx(cx, obj); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); RootedId old(cx, ccx.SetResolveName(id)); nsCOMPtr scr = wrapper->GetScriptable(); // Resolve a Symbol.hasInstance property if we want custom `instanceof` // behavior. if (scr && scr->WantHasInstance() && id.isWellKnownSymbol(SymbolCode::hasInstance)) { mozilla::Maybe asrw; if (scr->AllowPropModsDuringResolve()) { asrw.emplace(ccx, wrapper); } if (!JS_DefineFunctionById( cx, obj, id, XPC_WN_Helper_HasInstance, 1, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_RESOLVING)) { rv = NS_ERROR_FAILURE; } else { resolved = true; } } if (scr && scr->WantResolve()) { mozilla::Maybe asrw; if (scr->AllowPropModsDuringResolve()) { asrw.emplace(ccx, wrapper); } rv = scr->Resolve(wrapper, cx, obj, id, &resolved, &retval); } old = ccx.SetResolveName(old); MOZ_ASSERT(old == id, "bad nest"); if (NS_FAILED(rv)) { return Throw(rv, cx); } if (resolved) { *resolvedp = true; } else if (wrapper->HasMutatedSet()) { // We are here if scriptable did not resolve this property and // it *might* be in the instance set but not the proto set. XPCNativeSet* set = wrapper->GetSet(); XPCNativeSet* protoSet = wrapper->HasProto() ? wrapper->GetProto()->GetSet() : nullptr; XPCNativeMember* member = nullptr; RefPtr iface; bool IsLocal = false; if (set->FindMember(id, &member, &iface, protoSet, &IsLocal) && IsLocal) { XPCWrappedNative* wrapperForInterfaceNames = (scr && scr->DontReflectInterfaceNames()) ? nullptr : wrapper; AutoSetResolvingWrapper asrw(ccx, wrapper); retval = DefinePropertyIfFound( ccx, obj, id, set, iface, member, wrapper->GetScope(), false, wrapperForInterfaceNames, nullptr, scr, JSPROP_ENUMERATE, resolvedp); } } return retval; } /***************************************************************************/ bool XPC_WN_NewEnumerate(JSContext* cx, HandleObject obj, MutableHandleIdVector properties, bool enumerableOnly) { XPCCallContext ccx(cx, obj); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); nsCOMPtr scr = wrapper->GetScriptable(); if (!scr || !scr->WantNewEnumerate()) { return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); } if (!XPC_WN_Shared_Enumerate(cx, obj)) { return false; } bool retval = true; nsresult rv = scr->NewEnumerate(wrapper, cx, obj, properties, enumerableOnly, &retval); if (NS_FAILED(rv)) { return Throw(rv, cx); } return retval; } /***************************************************************************/ /***************************************************************************/ // Compatibility hack. // // XPConnect used to do all sorts of funny tricks to find the "correct" // |this| object for a given method (often to the detriment of proper // call/apply). When these tricks were removed, a fair amount of chrome // code broke, because it was relying on being able to grab methods off // some XPCOM object (like the nsITelemetry service) and invoke them without // a proper |this|. So, if it's quite clear that we're in this situation and // about to use a |this| argument that just won't work, fix things up. // // This hack is only useful for getters/setters if someone sets an XPCOM object // as the prototype for a vanilla JS object and expects the XPCOM attributes to // work on the derived object, which we really don't want to support. But we // handle it anyway, for now, to minimize regression risk on an already-risky // landing. // // This hack is mainly useful for the NoHelper JSClass. We also fix up // Components.utils because it implements nsIXPCScriptable (giving it a custom // JSClass) but not nsIClassInfo (which would put the methods on a prototype). #define IS_NOHELPER_CLASS(clasp) (clasp == &XPC_WN_NoHelper_JSClass) #define IS_CU_CLASS(clasp) \ (clasp->name[0] == 'n' && !strcmp(clasp->name, "nsXPCComponents_Utils")) MOZ_ALWAYS_INLINE JSObject* FixUpThisIfBroken(JSObject* obj, JSObject* funobj) { if (funobj) { JSObject* parentObj = &js::GetFunctionNativeReserved(funobj, XPC_FUNCTION_PARENT_OBJECT_SLOT) .toObject(); const JSClass* parentClass = JS::GetClass(parentObj); if (MOZ_UNLIKELY( (IS_NOHELPER_CLASS(parentClass) || IS_CU_CLASS(parentClass)) && (JS::GetClass(obj) != parentClass))) { return parentObj; } } return obj; } bool XPC_WN_CallMethod(JSContext* cx, unsigned argc, Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); MOZ_ASSERT(JS_TypeOfValue(cx, args.calleev()) == JSTYPE_FUNCTION, "bad function"); RootedObject funobj(cx, &args.callee()); RootedObject obj(cx); if (!args.computeThis(cx, &obj)) { return false; } obj = FixUpThisIfBroken(obj, funobj); XPCCallContext ccx(cx, obj, funobj, JS::VoidHandlePropertyKey, args.length(), args.array(), vp); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); RefPtr iface; XPCNativeMember* member; if (!XPCNativeMember::GetCallInfo(funobj, &iface, &member)) { return Throw(NS_ERROR_XPC_CANT_GET_METHOD_INFO, cx); } ccx.SetCallInfo(iface, member, false); return XPCWrappedNative::CallMethod(ccx); } bool XPC_WN_GetterSetter(JSContext* cx, unsigned argc, Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); MOZ_ASSERT(JS_TypeOfValue(cx, args.calleev()) == JSTYPE_FUNCTION, "bad function"); RootedObject funobj(cx, &args.callee()); if (!args.thisv().isObject()) { JS_ReportErrorASCII( cx, "xpconnect getter/setter called on incompatible non-object"); return false; } RootedObject obj(cx, &args.thisv().toObject()); obj = FixUpThisIfBroken(obj, funobj); XPCCallContext ccx(cx, obj, funobj, JS::VoidHandlePropertyKey, args.length(), args.array(), vp); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); RefPtr iface; XPCNativeMember* member; if (!XPCNativeMember::GetCallInfo(funobj, &iface, &member)) { return Throw(NS_ERROR_XPC_CANT_GET_METHOD_INFO, cx); } if (args.length() != 0 && member->IsWritableAttribute()) { ccx.SetCallInfo(iface, member, true); bool retval = XPCWrappedNative::SetAttribute(ccx); if (retval) { args.rval().set(args[0]); } return retval; } // else... ccx.SetCallInfo(iface, member, false); return XPCWrappedNative::GetAttribute(ccx); } /***************************************************************************/ /* static */ XPCWrappedNativeProto* XPCWrappedNativeProto::Get(JSObject* obj) { MOZ_ASSERT(JS::GetClass(obj) == &XPC_WN_Proto_JSClass); return JS::GetMaybePtrFromReservedSlot(obj, ProtoSlot); } /* static */ XPCWrappedNativeTearOff* XPCWrappedNativeTearOff::Get(JSObject* obj) { MOZ_ASSERT(JS::GetClass(obj) == &XPC_WN_Tearoff_JSClass); return JS::GetMaybePtrFromReservedSlot(obj, TearOffSlot); } static bool XPC_WN_Proto_Enumerate(JSContext* cx, HandleObject obj) { MOZ_ASSERT(JS::GetClass(obj) == &XPC_WN_Proto_JSClass, "bad proto"); XPCWrappedNativeProto* self = XPCWrappedNativeProto::Get(obj); if (!self) { return false; } XPCNativeSet* set = self->GetSet(); if (!set) { return false; } XPCCallContext ccx(cx); if (!ccx.IsValid()) { return false; } uint16_t interface_count = set->GetInterfaceCount(); XPCNativeInterface** interfaceArray = set->GetInterfaceArray(); for (uint16_t i = 0; i < interface_count; i++) { XPCNativeInterface* iface = interfaceArray[i]; uint16_t member_count = iface->GetMemberCount(); for (uint16_t k = 0; k < member_count; k++) { jsid name = iface->GetMemberAt(k)->GetName(); JS_MarkCrossZoneId(cx, name); if (!xpc_ForcePropertyResolve(cx, obj, name)) { return false; } } } return true; } static void XPC_WN_Proto_Finalize(JS::GCContext* gcx, JSObject* obj) { // This can be null if xpc shutdown has already happened XPCWrappedNativeProto* p = XPCWrappedNativeProto::Get(obj); if (p) { p->JSProtoObjectFinalized(gcx, obj); } } static size_t XPC_WN_Proto_ObjectMoved(JSObject* obj, JSObject* old) { // This can be null if xpc shutdown has already happened XPCWrappedNativeProto* p = XPCWrappedNativeProto::Get(obj); if (!p) { return 0; } p->JSProtoObjectMoved(obj, old); return 0; } /*****************************************************/ static bool XPC_WN_OnlyIWrite_Proto_AddPropertyStub(JSContext* cx, HandleObject obj, HandleId id, HandleValue v) { MOZ_ASSERT(JS::GetClass(obj) == &XPC_WN_Proto_JSClass, "bad proto"); XPCWrappedNativeProto* self = XPCWrappedNativeProto::Get(obj); if (!self) { return false; } XPCCallContext ccx(cx); if (!ccx.IsValid()) { return false; } // Allow XPConnect to add the property only if (ccx.GetResolveName() == id) { return true; } return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); } static bool XPC_WN_Proto_Resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) { MOZ_ASSERT(JS::GetClass(obj) == &XPC_WN_Proto_JSClass, "bad proto"); XPCWrappedNativeProto* self = XPCWrappedNativeProto::Get(obj); if (!self) { return false; } XPCCallContext ccx(cx); if (!ccx.IsValid()) { return false; } nsCOMPtr scr = self->GetScriptable(); return DefinePropertyIfFound( ccx, obj, id, self->GetSet(), nullptr, nullptr, self->GetScope(), true, nullptr, nullptr, scr, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE, resolvedp); } static const JSClassOps XPC_WN_Proto_JSClassOps = { XPC_WN_OnlyIWrite_Proto_AddPropertyStub, // addProperty XPC_WN_CannotDeletePropertyStub, // delProperty XPC_WN_Proto_Enumerate, // enumerate nullptr, // newEnumerate XPC_WN_Proto_Resolve, // resolve nullptr, // mayResolve XPC_WN_Proto_Finalize, // finalize nullptr, // call nullptr, // construct nullptr, // trace }; static const js::ClassExtension XPC_WN_Proto_ClassExtension = { XPC_WN_Proto_ObjectMoved, // objectMovedOp }; const JSClass XPC_WN_Proto_JSClass = { "XPC_WN_Proto_JSClass", JSCLASS_HAS_RESERVED_SLOTS(XPCWrappedNativeProto::SlotCount) | JSCLASS_FOREGROUND_FINALIZE, &XPC_WN_Proto_JSClassOps, JS_NULL_CLASS_SPEC, &XPC_WN_Proto_ClassExtension, JS_NULL_OBJECT_OPS}; /***************************************************************************/ static bool XPC_WN_TearOff_Enumerate(JSContext* cx, HandleObject obj) { XPCCallContext ccx(cx, obj); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); XPCWrappedNativeTearOff* to = ccx.GetTearOff(); XPCNativeInterface* iface; if (!to || nullptr == (iface = to->GetInterface())) { return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); } uint16_t member_count = iface->GetMemberCount(); for (uint16_t k = 0; k < member_count; k++) { jsid name = iface->GetMemberAt(k)->GetName(); JS_MarkCrossZoneId(cx, name); if (!xpc_ForcePropertyResolve(cx, obj, name)) { return false; } } return true; } static bool XPC_WN_TearOff_Resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) { XPCCallContext ccx(cx, obj); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); XPCWrappedNativeTearOff* to = ccx.GetTearOff(); XPCNativeInterface* iface; if (!to || nullptr == (iface = to->GetInterface())) { return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); } return DefinePropertyIfFound( ccx, obj, id, nullptr, iface, nullptr, wrapper->GetScope(), true, nullptr, nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE, resolvedp); } static void XPC_WN_TearOff_Finalize(JS::GCContext* gcx, JSObject* obj) { XPCWrappedNativeTearOff* p = XPCWrappedNativeTearOff::Get(obj); if (!p) { return; } p->JSObjectFinalized(); } static size_t XPC_WN_TearOff_ObjectMoved(JSObject* obj, JSObject* old) { XPCWrappedNativeTearOff* p = XPCWrappedNativeTearOff::Get(obj); if (!p) { return 0; } p->JSObjectMoved(obj, old); return 0; } static const JSClassOps XPC_WN_Tearoff_JSClassOps = { XPC_WN_OnlyIWrite_AddPropertyStub, // addProperty XPC_WN_CannotDeletePropertyStub, // delProperty XPC_WN_TearOff_Enumerate, // enumerate nullptr, // newEnumerate XPC_WN_TearOff_Resolve, // resolve nullptr, // mayResolve XPC_WN_TearOff_Finalize, // finalize nullptr, // call nullptr, // construct nullptr, // trace }; static const js::ClassExtension XPC_WN_Tearoff_JSClassExtension = { XPC_WN_TearOff_ObjectMoved, // objectMovedOp }; const JSClass XPC_WN_Tearoff_JSClass = { "WrappedNative_TearOff", JSCLASS_HAS_RESERVED_SLOTS(XPCWrappedNativeTearOff::SlotCount) | JSCLASS_FOREGROUND_FINALIZE, &XPC_WN_Tearoff_JSClassOps, JS_NULL_CLASS_SPEC, &XPC_WN_Tearoff_JSClassExtension};