/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim: set ts=8 sts=4 et sw=4 tw=99: */ /* 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 "jsprf.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/Preferences.h" #include "nsIAddonInterposition.h" #include "AddonWrapper.h" #include "js/Class.h" using namespace mozilla; using namespace JS; /***************************************************************************/ // 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) { char* sz; XPCWrappedNative* wrapper = ccx.GetWrapper(); if (wrapper) sz = 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); JS_smprintf_free(sz); 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, JS_THIS_OBJECT(cx, vp)); if (!obj) return false; XPCCallContext ccx(JS_CALLER, cx, obj); if (!ccx.IsValid()) return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); ccx.SetName(ccx.GetRuntime()->GetStringID(XPCJSRuntime::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 const char empty[] = "({})"; JSString* str = JS_NewStringCopyN(cx, empty, sizeof(empty)-1); 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(JS_CALLER, 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(JS_GetNaNValue(cx)); return true; } MOZ_ASSERT(hint == JSTYPE_STRING || hint == JSTYPE_VOID); ccx.SetName(ccx.GetRuntime()->GetStringID(XPCJSRuntime::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. // // See the comment preceding nsIXPCWrappedJSObjectGetter in nsIXPConnect.idl. static JSObject* GetDoubleWrappedJSObject(XPCCallContext& ccx, XPCWrappedNative* wrapper) { RootedObject obj(ccx); nsCOMPtr underware = do_QueryInterface(wrapper->GetIdentityObject()); if (underware) { RootedObject mainObj(ccx, underware->GetJSObject()); if (mainObj) { RootedId id(ccx, ccx.GetRuntime()-> GetStringID(XPCJSRuntime::IDX_WRAPPED_JSOBJECT)); JSAutoCompartment ac(ccx, mainObj); RootedValue val(ccx); if (JS_GetPropertyById(ccx, mainObj, id, &val) && !val.isPrimitive()) { obj = val.toObjectOrNull(); } } } 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); RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); if (!obj) return false; XPCCallContext ccx(JS_CALLER, 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::IsCallerChrome())) { JS_ReportError(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* iface, XPCNativeMember* member, XPCWrappedNativeScope* scope, bool reflectToStringAndToSource, XPCWrappedNative* wrapperToReflectInterfaceNames, XPCWrappedNative* wrapperToReflectDoubleWrap, XPCNativeScriptableInfo* scriptableInfo, unsigned propFlags, bool* resolved) { RootedId id(ccx, idArg); XPCJSRuntime* rt = ccx.GetRuntime(); 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; uint32_t flags = 0; if (scriptableInfo) { nsCOMPtr classInfo = do_QueryInterface( scriptableInfo->GetCallback()); if (classInfo) { nsresult rv = classInfo->GetFlags(&flags); if (NS_FAILED(rv)) return Throw(rv, ccx); } } bool overwriteToString = !(flags & nsIClassInfo::DOM_OBJECT) || Preferences::GetBool("dom.XPCToStringForDOMClasses", false); if(id == rt->GetStringID(XPCJSRuntime::IDX_TO_STRING) && overwriteToString) { call = XPC_WN_Shared_ToString; name = rt->GetStringName(XPCJSRuntime::IDX_TO_STRING); } else if (id == rt->GetStringID(XPCJSRuntime::IDX_TO_SOURCE)) { call = XPC_WN_Shared_ToSource; name = rt->GetStringName(XPCJSRuntime::IDX_TO_SOURCE); } else if (id == SYMBOL_TO_JSID( JS::GetWellKnownSymbol(ccx, 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) { JSAutoByteString name; AutoMarkingNativeInterfacePtr iface2(ccx); XPCWrappedNativeTearOff* to; RootedObject jso(ccx); nsresult rv = NS_OK; if (JSID_IS_STRING(id) && name.encodeLatin1(ccx, JSID_TO_STRING(id)) && (iface2 = XPCNativeInterface::GetNewOrUsed(name.ptr()), iface2) && nullptr != (to = wrapperToReflectInterfaceNames-> FindTearOff(iface2, true, &rv)) && nullptr != (jso = to->GetJSObject())) { 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 == rt->GetStringID(XPCJSRuntime::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 = rt->GetStringID(XPCJSRuntime::IDX_WRAPPED_JSOBJECT); name = rt->GetStringName(XPCJSRuntime::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_GETTER | JSPROP_SHARED; propFlags &= ~JSPROP_ENUMERATE; AutoResolveName arn(ccx, id); if (resolved) *resolved = true; return JS_DefinePropertyById(ccx, obj, id, UndefinedHandleValue, propFlags, JS_DATA_TO_FUNC_PTR(JSNative, funobj.get()), nullptr); } if (resolved) *resolved = false; return true; } if (!member) { if (wrapperToReflectInterfaceNames) { XPCWrappedNativeTearOff* to = wrapperToReflectInterfaceNames->FindTearOff(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 (scope->HasInterposition()) { Rooted desc(ccx); if (!xpc::InterposeProperty(ccx, obj, iface->GetIID(), id, &desc)) return false; if (desc.object()) { AutoResolveName arn(ccx, id); if (resolved) *resolved = true; desc.attributesRef() |= JSPROP_RESOLVING; return JS_DefinePropertyById(ccx, obj, id, desc); } } if (id == rt->GetStringID(XPCJSRuntime::IDX_TO_STRING) || id == rt->GetStringID(XPCJSRuntime::IDX_TO_SOURCE) || (scriptableInfo && scriptableInfo->GetFlags().DontEnumQueryInterface() && id == rt->GetStringID(XPCJSRuntime::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_GETTER | JSPROP_SHARED; propFlags &= ~JSPROP_READONLY; JSObject* funobj = funval.toObjectOrNull(); JSNative getter = JS_DATA_TO_FUNC_PTR(JSNative, funobj); JSNative setter; if (member->IsWritableAttribute()) { propFlags |= JSPROP_SETTER; setter = JS_DATA_TO_FUNC_PTR(JSNative, funobj); } else { setter = nullptr; } AutoResolveName arn(ccx, id); if (resolved) *resolved = true; return JS_DefinePropertyById(ccx, obj, id, UndefinedHandleValue, propFlags, getter, setter); } /***************************************************************************/ /***************************************************************************/ static bool XPC_WN_OnlyIWrite_AddPropertyStub(JSContext* cx, HandleObject obj, HandleId id, HandleValue v) { XPCCallContext ccx(JS_CALLER, 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); } static bool XPC_WN_CannotModifyPropertyStub(JSContext* cx, HandleObject obj, HandleId id, HandleValue v) { return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); } static bool XPC_WN_CantDeletePropertyStub(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result) { return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); } static bool XPC_WN_CannotModifySetPropertyStub(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp, ObjectOpResult& result) { return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); } static bool XPC_WN_Shared_Enumerate(JSContext* cx, HandleObject obj) { XPCCallContext ccx(JS_CALLER, 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; if (!xpc_ForcePropertyResolve(cx, obj, name)) return false; } } return true; } /***************************************************************************/ enum WNHelperType { WN_NOHELPER, WN_HELPER }; static void WrappedNativeFinalize(js::FreeOp* fop, JSObject* obj, WNHelperType helperType) { const js::Class* clazz = js::GetObjectClass(obj); if (clazz->flags & JSCLASS_DOM_GLOBAL) { mozilla::dom::DestroyProtoAndIfaceCache(obj); } nsISupports* p = static_cast(xpc_GetJSPrivate(obj)); if (!p) return; XPCWrappedNative* wrapper = static_cast(p); if (helperType == WN_HELPER) wrapper->GetScriptableCallback()->Finalize(wrapper, js::CastToJSFreeOp(fop), obj); wrapper->FlatJSObjectFinalized(); } static void WrappedNativeObjectMoved(JSObject* obj, const JSObject* old) { nsISupports* p = static_cast(xpc_GetJSPrivate(obj)); if (!p) return; XPCWrappedNative* wrapper = static_cast(p); wrapper->FlatJSObjectMoved(obj, old); } static void XPC_WN_NoHelper_Finalize(js::FreeOp* fop, JSObject* obj) { WrappedNativeFinalize(fop, 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 MarkWrappedNative(JSTracer* trc, JSObject* obj) { const js::Class* clazz = js::GetObjectClass(obj); if (clazz->flags & JSCLASS_DOM_GLOBAL) { mozilla::dom::TraceProtoAndIfaceCache(trc, obj); } MOZ_ASSERT(IS_WN_CLASS(clazz)); XPCWrappedNative* wrapper = XPCWrappedNative::Get(obj); if (wrapper && wrapper->IsValid()) wrapper->TraceInside(trc); } /* static */ void XPCWrappedNative::Trace(JSTracer* trc, JSObject* obj) { MarkWrappedNative(trc, obj); } static bool XPC_WN_NoHelper_Resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) { XPCCallContext ccx(JS_CALLER, 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); } const XPCWrappedNativeJSClass XPC_WN_NoHelper_JSClass = { { // base "XPCWrappedNative_NoHelper", // name; WRAPPER_FLAGS | JSCLASS_PRIVATE_IS_NSISUPPORTS, // flags /* Mandatory non-null function pointer members. */ XPC_WN_OnlyIWrite_AddPropertyStub, // addProperty XPC_WN_CantDeletePropertyStub, // delProperty nullptr, // getProperty nullptr, // setProperty XPC_WN_Shared_Enumerate, // enumerate XPC_WN_NoHelper_Resolve, // resolve nullptr, // mayResolve XPC_WN_NoHelper_Finalize, // finalize /* Optionally non-null members start here. */ nullptr, // call nullptr, // construct nullptr, // hasInstance XPCWrappedNative::Trace, // trace JS_NULL_CLASS_SPEC, // ClassExtension { true, // isWrappedNative nullptr, // weakmapKeyDelegateOp WrappedNativeObjectMoved }, // ObjectOps { nullptr, // lookupProperty nullptr, // defineProperty nullptr, // hasProperty nullptr, // getProperty nullptr, // setProperty nullptr, // getOwnPropertyDescriptor nullptr, // deleteProperty nullptr, nullptr, // watch/unwatch nullptr, // getElements nullptr, // enumerate nullptr, // funToString } } }; /***************************************************************************/ static bool XPC_WN_MaybeResolvingPropertyStub(JSContext* cx, HandleObject obj, HandleId id, HandleValue v) { XPCCallContext ccx(JS_CALLER, 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); } static bool XPC_WN_MaybeResolvingSetPropertyStub(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp, ObjectOpResult& result) { result.succeed(); return XPC_WN_MaybeResolvingPropertyStub(cx, obj, id, vp); } static bool XPC_WN_MaybeResolvingDeletePropertyStub(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result) { XPCCallContext ccx(JS_CALLER, 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 \ JSObject* unwrapped = js::CheckedUnwrap(obj, false); \ if (!unwrapped) { \ JS_ReportError(cx, "Permission denied to operate on object."); \ return false; \ } \ if (!IS_WN_REFLECTOR(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->GetScriptableCallback()-> #define POST_HELPER_STUB \ if (NS_FAILED(rv)) \ return Throw(rv, cx); \ return retval; #define POST_HELPER_STUB_WITH_OBJECTOPRESULT(failMethod) \ if (NS_FAILED(rv)) \ return Throw(rv, cx); \ return retval ? result.succeed() : result.failMethod(); static bool XPC_WN_Helper_AddProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v) { PRE_HELPER_STUB AddProperty(wrapper, cx, obj, id, v, &retval); POST_HELPER_STUB } bool XPC_WN_Helper_GetProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp) { PRE_HELPER_STUB GetProperty(wrapper, cx, obj, id, vp.address(), &retval); POST_HELPER_STUB } bool XPC_WN_Helper_SetProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp, ObjectOpResult& result) { PRE_HELPER_STUB SetProperty(wrapper, cx, obj, id, vp.address(), &retval); POST_HELPER_STUB_WITH_OBJECTOPRESULT(failReadOnly) } static 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(JS_CALLER, cx, obj, nullptr, JSID_VOIDHANDLE, args.length(), args.array(), args.rval().address()); if (!ccx.IsValid()) return false; PRE_HELPER_STUB Call(wrapper, cx, obj, args, &retval); POST_HELPER_STUB } static 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(JS_CALLER, cx, obj, nullptr, JSID_VOIDHANDLE, 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, HandleObject obj, MutableHandleValue valp, bool* bp) { bool retval2; PRE_HELPER_STUB HasInstance(wrapper, cx, obj, valp, &retval2, &retval); *bp = retval2; POST_HELPER_STUB } static void XPC_WN_Helper_Finalize(js::FreeOp* fop, JSObject* obj) { WrappedNativeFinalize(fop, obj, WN_HELPER); } static 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(JS_CALLER, cx, obj); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); RootedId old(cx, ccx.SetResolveName(id)); XPCNativeScriptableInfo* si = wrapper->GetScriptableInfo(); if (si && si->GetFlags().WantResolve()) { XPCWrappedNative* oldResolvingWrapper; bool allowPropMods = si->GetFlags().AllowPropModsDuringResolve(); if (allowPropMods) oldResolvingWrapper = ccx.SetResolvingWrapper(wrapper); rv = si->GetCallback()->Resolve(wrapper, cx, obj, id, &resolved, &retval); if (allowPropMods) (void)ccx.SetResolvingWrapper(oldResolvingWrapper); } 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; XPCNativeInterface* iface; bool IsLocal; if (set->FindMember(id, &member, &iface, protoSet, &IsLocal) && IsLocal) { XPCWrappedNative* oldResolvingWrapper; XPCNativeScriptableFlags siFlags(0); if (si) siFlags = si->GetFlags(); XPCWrappedNative* wrapperForInterfaceNames = siFlags.DontReflectInterfaceNames() ? nullptr : wrapper; oldResolvingWrapper = ccx.SetResolvingWrapper(wrapper); retval = DefinePropertyIfFound(ccx, obj, id, set, iface, member, wrapper->GetScope(), false, wrapperForInterfaceNames, nullptr, si, JSPROP_ENUMERATE, resolvedp); (void)ccx.SetResolvingWrapper(oldResolvingWrapper); } } return retval; } static bool XPC_WN_Helper_Enumerate(JSContext* cx, HandleObject obj) { XPCCallContext ccx(JS_CALLER, cx, obj); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); XPCNativeScriptableInfo* si = wrapper->GetScriptableInfo(); if (!si || !si->GetFlags().WantEnumerate()) 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 = si->GetCallback()->Enumerate(wrapper, cx, obj, &retval); if (NS_FAILED(rv)) return Throw(rv, cx); return retval; } /***************************************************************************/ static bool XPC_WN_JSOp_Enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties, bool enumerableOnly) { XPCCallContext ccx(JS_CALLER, cx, obj); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); XPCNativeScriptableInfo* si = wrapper->GetScriptableInfo(); if (!si || !si->GetFlags().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 = si->GetCallback()->NewEnumerate(wrapper, cx, obj, properties, &retval); if (NS_FAILED(rv)) return Throw(rv, cx); return retval; } /***************************************************************************/ // static XPCNativeScriptableInfo* XPCNativeScriptableInfo::Construct(const XPCNativeScriptableCreateInfo* sci) { MOZ_ASSERT(sci, "bad param"); MOZ_ASSERT(sci->GetCallback(), "bad param"); XPCNativeScriptableInfo* newObj = new XPCNativeScriptableInfo(sci->GetCallback()); if (!newObj) return nullptr; char* name = nullptr; if (NS_FAILED(sci->GetCallback()->GetClassName(&name)) || !name) { delete newObj; return nullptr; } bool success; XPCJSRuntime* rt = XPCJSRuntime::Get(); XPCNativeScriptableSharedMap* map = rt->GetNativeScriptableSharedMap(); success = map->GetNewOrUsed(sci->GetFlags(), name, newObj); if (!success) { delete newObj; return nullptr; } return newObj; } void XPCNativeScriptableShared::PopulateJSClass() { MOZ_ASSERT(mJSClass.base.name, "bad state!"); mJSClass.base.flags = WRAPPER_FLAGS | JSCLASS_PRIVATE_IS_NSISUPPORTS; if (mFlags.IsGlobalObject()) mJSClass.base.flags |= XPCONNECT_GLOBAL_FLAGS; JSAddPropertyOp addProperty; if (mFlags.WantAddProperty()) addProperty = XPC_WN_Helper_AddProperty; else if (mFlags.UseJSStubForAddProperty()) addProperty = nullptr; else if (mFlags.AllowPropModsDuringResolve()) addProperty = XPC_WN_MaybeResolvingPropertyStub; else addProperty = XPC_WN_CannotModifyPropertyStub; mJSClass.base.addProperty = addProperty; JSDeletePropertyOp delProperty; if (mFlags.UseJSStubForDelProperty()) delProperty = nullptr; else if (mFlags.AllowPropModsDuringResolve()) delProperty = XPC_WN_MaybeResolvingDeletePropertyStub; else delProperty = XPC_WN_CantDeletePropertyStub; mJSClass.base.delProperty = delProperty; if (mFlags.WantGetProperty()) mJSClass.base.getProperty = XPC_WN_Helper_GetProperty; else mJSClass.base.getProperty = nullptr; JSSetterOp setProperty; if (mFlags.WantSetProperty()) setProperty = XPC_WN_Helper_SetProperty; else if (mFlags.UseJSStubForSetProperty()) setProperty = nullptr; else if (mFlags.AllowPropModsDuringResolve()) setProperty = XPC_WN_MaybeResolvingSetPropertyStub; else setProperty = XPC_WN_CannotModifySetPropertyStub; mJSClass.base.setProperty = setProperty; MOZ_ASSERT_IF(mFlags.WantEnumerate(), !mFlags.WantNewEnumerate()); MOZ_ASSERT_IF(mFlags.WantNewEnumerate(), !mFlags.WantEnumerate()); // We will use ops->enumerate set below for NewEnumerate if (mFlags.WantNewEnumerate()) mJSClass.base.enumerate = nullptr; else if (mFlags.WantEnumerate()) mJSClass.base.enumerate = XPC_WN_Helper_Enumerate; else mJSClass.base.enumerate = XPC_WN_Shared_Enumerate; // We have to figure out resolve strategy at call time mJSClass.base.resolve = XPC_WN_Helper_Resolve; if (mFlags.WantFinalize()) mJSClass.base.finalize = XPC_WN_Helper_Finalize; else mJSClass.base.finalize = XPC_WN_NoHelper_Finalize; js::ObjectOps* ops = &mJSClass.base.ops; if (mFlags.WantNewEnumerate()) ops->enumerate = XPC_WN_JSOp_Enumerate; if (mFlags.WantCall()) mJSClass.base.call = XPC_WN_Helper_Call; if (mFlags.WantConstruct()) mJSClass.base.construct = XPC_WN_Helper_Construct; if (mFlags.WantHasInstance()) mJSClass.base.hasInstance = XPC_WN_Helper_HasInstance; if (mFlags.IsGlobalObject()) mJSClass.base.trace = JS_GlobalObjectTraceHook; else mJSClass.base.trace = XPCWrappedNative::Trace; mJSClass.base.ext.isWrappedNative = true; mJSClass.base.ext.objectMovedOp = WrappedNativeObjectMoved; } /***************************************************************************/ /***************************************************************************/ // 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.base) #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 js::Class* parentClass = js::GetObjectClass(parentObj); if (MOZ_UNLIKELY((IS_NOHELPER_CLASS(parentClass) || IS_CU_CLASS(parentClass)) && (js::GetObjectClass(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, JS_THIS_OBJECT(cx, vp)); if (!obj) return false; obj = FixUpThisIfBroken(obj, funobj); XPCCallContext ccx(JS_CALLER, cx, obj, funobj, JSID_VOIDHANDLE, args.length(), args.array(), vp); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); XPCNativeInterface* 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()); RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); if (!obj) return false; obj = FixUpThisIfBroken(obj, funobj); XPCCallContext ccx(JS_CALLER, cx, obj, funobj, JSID_VOIDHANDLE, args.length(), args.array(), vp); XPCWrappedNative* wrapper = ccx.GetWrapper(); THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); XPCNativeInterface* 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 bool XPC_WN_Shared_Proto_Enumerate(JSContext* cx, HandleObject obj) { MOZ_ASSERT(js::GetObjectClass(obj) == &XPC_WN_ModsAllowed_WithCall_Proto_JSClass || js::GetObjectClass(obj) == &XPC_WN_ModsAllowed_NoCall_Proto_JSClass || js::GetObjectClass(obj) == &XPC_WN_NoMods_WithCall_Proto_JSClass || js::GetObjectClass(obj) == &XPC_WN_NoMods_NoCall_Proto_JSClass, "bad proto"); XPCWrappedNativeProto* self = (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj); if (!self) return false; XPCNativeSet* set = self->GetSet(); if (!set) return false; XPCCallContext ccx(JS_CALLER, 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++) { if (!xpc_ForcePropertyResolve(cx, obj, iface->GetMemberAt(k)->GetName())) return false; } } return true; } static void XPC_WN_Shared_Proto_Finalize(js::FreeOp* fop, JSObject* obj) { // This can be null if xpc shutdown has already happened XPCWrappedNativeProto* p = (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj); if (p) p->JSProtoObjectFinalized(fop, obj); } static void XPC_WN_Shared_Proto_ObjectMoved(JSObject* obj, const JSObject* old) { // This can be null if xpc shutdown has already happened XPCWrappedNativeProto* p = (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj); if (p) p->JSProtoObjectMoved(obj, old); } static void XPC_WN_Shared_Proto_Trace(JSTracer* trc, JSObject* obj) { // This can be null if xpc shutdown has already happened XPCWrappedNativeProto* p = (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj); if (p) p->TraceInside(trc); } /*****************************************************/ static bool XPC_WN_ModsAllowed_Proto_Resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvep) { MOZ_ASSERT(js::GetObjectClass(obj) == &XPC_WN_ModsAllowed_WithCall_Proto_JSClass || js::GetObjectClass(obj) == &XPC_WN_ModsAllowed_NoCall_Proto_JSClass, "bad proto"); XPCWrappedNativeProto* self = (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj); if (!self) return false; XPCCallContext ccx(JS_CALLER, cx); if (!ccx.IsValid()) return false; XPCNativeScriptableInfo* si = self->GetScriptableInfo(); return DefinePropertyIfFound(ccx, obj, id, self->GetSet(), nullptr, nullptr, self->GetScope(), true, nullptr, nullptr, si, JSPROP_ENUMERATE, resolvep); } #define XPC_WN_SHARED_PROTO_CLASS_EXT \ { \ false, /* isWrappedNative */ \ nullptr, /* weakmapKeyDelegateOp */ \ XPC_WN_Shared_Proto_ObjectMoved \ } const js::Class XPC_WN_ModsAllowed_WithCall_Proto_JSClass = { "XPC_WN_ModsAllowed_WithCall_Proto_JSClass", // name; WRAPPER_FLAGS, // flags; /* Function pointer members. */ nullptr, // addProperty; nullptr, // delProperty; nullptr, // getProperty; nullptr, // setProperty; XPC_WN_Shared_Proto_Enumerate, // enumerate; XPC_WN_ModsAllowed_Proto_Resolve, // resolve; nullptr, // mayResolve; XPC_WN_Shared_Proto_Finalize, // finalize; /* Optionally non-null members start here. */ nullptr, // call; nullptr, // construct; nullptr, // hasInstance; XPC_WN_Shared_Proto_Trace, // trace; JS_NULL_CLASS_SPEC, XPC_WN_SHARED_PROTO_CLASS_EXT, XPC_WN_WithCall_ObjectOps }; const js::Class XPC_WN_ModsAllowed_NoCall_Proto_JSClass = { "XPC_WN_ModsAllowed_NoCall_Proto_JSClass", // name; WRAPPER_FLAGS, // flags; /* Function pointer members. */ nullptr, // addProperty; nullptr, // delProperty; nullptr, // getProperty; nullptr, // setProperty; XPC_WN_Shared_Proto_Enumerate, // enumerate; XPC_WN_ModsAllowed_Proto_Resolve, // resolve; nullptr, // mayResolve; XPC_WN_Shared_Proto_Finalize, // finalize; /* Optionally non-null members start here. */ nullptr, // call; nullptr, // construct; nullptr, // hasInstance; XPC_WN_Shared_Proto_Trace, // trace; JS_NULL_CLASS_SPEC, XPC_WN_SHARED_PROTO_CLASS_EXT, XPC_WN_NoCall_ObjectOps }; /***************************************************************************/ static bool XPC_WN_OnlyIWrite_Proto_AddPropertyStub(JSContext* cx, HandleObject obj, HandleId id, HandleValue v) { MOZ_ASSERT(js::GetObjectClass(obj) == &XPC_WN_NoMods_WithCall_Proto_JSClass || js::GetObjectClass(obj) == &XPC_WN_NoMods_NoCall_Proto_JSClass, "bad proto"); XPCWrappedNativeProto* self = (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj); if (!self) return false; XPCCallContext ccx(JS_CALLER, 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_NoMods_Proto_Resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) { MOZ_ASSERT(js::GetObjectClass(obj) == &XPC_WN_NoMods_WithCall_Proto_JSClass || js::GetObjectClass(obj) == &XPC_WN_NoMods_NoCall_Proto_JSClass, "bad proto"); XPCWrappedNativeProto* self = (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj); if (!self) return false; XPCCallContext ccx(JS_CALLER, cx); if (!ccx.IsValid()) return false; XPCNativeScriptableInfo* si = self->GetScriptableInfo(); return DefinePropertyIfFound(ccx, obj, id, self->GetSet(), nullptr, nullptr, self->GetScope(), true, nullptr, nullptr, si, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE, resolvedp); } const js::Class XPC_WN_NoMods_WithCall_Proto_JSClass = { "XPC_WN_NoMods_WithCall_Proto_JSClass", // name; WRAPPER_FLAGS, // flags; /* Mandatory non-null function pointer members. */ XPC_WN_OnlyIWrite_Proto_AddPropertyStub, // addProperty; XPC_WN_CantDeletePropertyStub, // delProperty; nullptr, // getProperty; nullptr, // setProperty; XPC_WN_Shared_Proto_Enumerate, // enumerate; XPC_WN_NoMods_Proto_Resolve, // resolve; nullptr, // mayResolve; XPC_WN_Shared_Proto_Finalize, // finalize; /* Optionally non-null members start here. */ nullptr, // call; nullptr, // construct; nullptr, // hasInstance; XPC_WN_Shared_Proto_Trace, // trace; JS_NULL_CLASS_SPEC, XPC_WN_SHARED_PROTO_CLASS_EXT, XPC_WN_WithCall_ObjectOps }; const js::Class XPC_WN_NoMods_NoCall_Proto_JSClass = { "XPC_WN_NoMods_NoCall_Proto_JSClass", // name; WRAPPER_FLAGS, // flags; /* Mandatory non-null function pointer members. */ XPC_WN_OnlyIWrite_Proto_AddPropertyStub, // addProperty; XPC_WN_CantDeletePropertyStub, // delProperty; nullptr, // getProperty; nullptr, // setProperty; XPC_WN_Shared_Proto_Enumerate, // enumerate; XPC_WN_NoMods_Proto_Resolve, // resolve; nullptr, // mayResolve; XPC_WN_Shared_Proto_Finalize, // finalize; /* Optionally non-null members start here. */ nullptr, // call; nullptr, // construct; nullptr, // hasInstance; XPC_WN_Shared_Proto_Trace, // trace; JS_NULL_CLASS_SPEC, XPC_WN_SHARED_PROTO_CLASS_EXT, XPC_WN_NoCall_ObjectOps }; /***************************************************************************/ static bool XPC_WN_TearOff_Enumerate(JSContext* cx, HandleObject obj) { XPCCallContext ccx(JS_CALLER, 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++) { if (!xpc_ForcePropertyResolve(cx, obj, iface->GetMemberAt(k)->GetName())) return false; } return true; } static bool XPC_WN_TearOff_Resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) { XPCCallContext ccx(JS_CALLER, 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::FreeOp* fop, JSObject* obj) { XPCWrappedNativeTearOff* p = (XPCWrappedNativeTearOff*) xpc_GetJSPrivate(obj); if (!p) return; p->JSObjectFinalized(); } static void XPC_WN_TearOff_ObjectMoved(JSObject* obj, const JSObject* old) { XPCWrappedNativeTearOff* p = (XPCWrappedNativeTearOff*) xpc_GetJSPrivate(obj); if (!p) return; p->JSObjectMoved(obj, old); } // Make sure WRAPPER_FLAGS has no reserved slots, so our XPC_WN_TEAROFF_RESERVED_SLOTS value is OK. static_assert(((WRAPPER_FLAGS >> JSCLASS_RESERVED_SLOTS_SHIFT) & JSCLASS_RESERVED_SLOTS_MASK) == 0, "WRAPPER_FLAGS should not include any reserved slots"); const js::Class XPC_WN_Tearoff_JSClass = { "WrappedNative_TearOff", // name; WRAPPER_FLAGS | JSCLASS_HAS_RESERVED_SLOTS(XPC_WN_TEAROFF_RESERVED_SLOTS), // flags; XPC_WN_OnlyIWrite_AddPropertyStub, // addProperty; XPC_WN_CantDeletePropertyStub, // delProperty; nullptr, // getProperty; nullptr, // setProperty; XPC_WN_TearOff_Enumerate, // enumerate; XPC_WN_TearOff_Resolve, // resolve; nullptr, // mayResolve; XPC_WN_TearOff_Finalize, // finalize; /* Optionally non-null members start here. */ nullptr, // call nullptr, // construct nullptr, // hasInstance nullptr, // trace JS_NULL_CLASS_SPEC, // ClassExtension { false, // isWrappedNative nullptr, // weakmapKeyDelegateOp XPC_WN_TearOff_ObjectMoved }, };