diff options
Diffstat (limited to 'dom/bindings/BindingUtils.cpp')
-rw-r--r-- | dom/bindings/BindingUtils.cpp | 377 |
1 files changed, 190 insertions, 187 deletions
diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index 4cccda7a4b..11d12dd364 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -758,42 +758,30 @@ bool DefineLegacyUnforgeableAttributes( return DefinePrefable(cx, obj, props); } -// We should use JSFunction objects for interface objects, but we need a custom -// hasInstance hook because we have new interface objects on prototype chains of -// old (XPConnect-based) bindings. We also need Xrays and arbitrary numbers of -// reserved slots (e.g. for legacy factory functions). So we define a custom -// funToString ObjectOps member for interface objects. -JSString* InterfaceObjectToString(JSContext* aCx, JS::Handle<JSObject*> aObject, - bool /* isToSource */) { - const JSClass* clasp = JS::GetClass(aObject); - MOZ_ASSERT(IsDOMIfaceAndProtoClass(clasp)); - - const DOMIfaceJSClass* ifaceJSClass = DOMIfaceJSClass::FromJSClass(clasp); - return JS_NewStringCopyZ(aCx, ifaceJSClass->mFunToString); +bool InterfaceObjectJSNative(JSContext* cx, unsigned argc, JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + return NativeHolderFromInterfaceObject(&args.callee())->mNative(cx, argc, vp); } -bool Constructor(JSContext* cx, unsigned argc, JS::Value* vp) { +bool LegacyFactoryFunctionJSNative(JSContext* cx, unsigned argc, + JS::Value* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - const JS::Value& v = js::GetFunctionNativeReserved( - &args.callee(), CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT); - const JSNativeHolder* nativeHolder = - static_cast<const JSNativeHolder*>(v.toPrivate()); - return (nativeHolder->mNative)(cx, argc, vp); -} - -static JSObject* CreateConstructor(JSContext* cx, JS::Handle<JSObject*> global, - const char* name, - const JSNativeHolder* nativeHolder, - unsigned ctorNargs) { - JSFunction* fun = js::NewFunctionWithReserved(cx, Constructor, ctorNargs, - JSFUN_CONSTRUCTOR, name); + return NativeHolderFromLegacyFactoryFunction(&args.callee()) + ->mNative(cx, argc, vp); +} + +static JSObject* CreateLegacyFactoryFunction(JSContext* cx, jsid name, + const JSNativeHolder* nativeHolder, + unsigned ctorNargs) { + JSFunction* fun = js::NewFunctionByIdWithReserved( + cx, LegacyFactoryFunctionJSNative, ctorNargs, JSFUN_CONSTRUCTOR, name); if (!fun) { return nullptr; } JSObject* constructor = JS_GetFunctionObject(fun); js::SetFunctionNativeReserved( - constructor, CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT, + constructor, LEGACY_FACTORY_FUNCTION_NATIVE_HOLDER_RESERVED_SLOT, JS::PrivateValue(const_cast<JSNativeHolder*>(nativeHolder))); return constructor; } @@ -846,11 +834,11 @@ static bool InterfaceIsInstance(JSContext* cx, unsigned argc, JS::Value* vp) { return true; } - // If "this" doesn't have a DOMIfaceAndProtoJSClass, it's not a DOM - // constructor, so just fall back. But note that we should - // CheckedUnwrapStatic here, because otherwise we won't get the right answers. - // The static version is OK, because we're looking for DOM constructors, which - // are not cross-origin objects. + // If "this" is not an interface object, likewise return false (again, like + // OrdinaryHasInstance). But note that we should CheckedUnwrapStatic here, + // because otherwise we won't get the right answers. + // The static version is OK, because we're looking for interface objects, + // which are not cross-origin objects. JS::Rooted<JSObject*> thisObj( cx, js::CheckedUnwrapStatic(&args.thisv().toObject())); if (!thisObj) { @@ -859,20 +847,16 @@ static bool InterfaceIsInstance(JSContext* cx, unsigned argc, JS::Value* vp) { return true; } - const JSClass* thisClass = JS::GetClass(thisObj); - - if (!IsDOMIfaceAndProtoClass(thisClass)) { + if (!IsInterfaceObject(thisObj)) { args.rval().setBoolean(false); return true; } - const DOMIfaceAndProtoJSClass* clasp = - DOMIfaceAndProtoJSClass::FromJSClass(thisClass); + const DOMInterfaceInfo* interfaceInfo = InterfaceInfoFromObject(thisObj); - // If "this" isn't a DOM constructor or is a constructor for an interface - // without a prototype, just fall back. - if (clasp->mType != eInterface || - clasp->mPrototypeID == prototypes::id::_ID_Count) { + // If "this" is a constructor for an interface without a prototype, just fall + // back. + if (interfaceInfo->mPrototypeID == prototypes::id::_ID_Count) { args.rval().setBoolean(false); return true; } @@ -881,13 +865,13 @@ static bool InterfaceIsInstance(JSContext* cx, unsigned argc, JS::Value* vp) { const DOMJSClass* domClass = GetDOMClass( js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false)); - if (domClass && - domClass->mInterfaceChain[clasp->mDepth] == clasp->mPrototypeID) { + if (domClass && domClass->mInterfaceChain[interfaceInfo->mDepth] == + interfaceInfo->mPrototypeID) { args.rval().setBoolean(true); return true; } - if (IsRemoteObjectProxy(instance, clasp->mPrototypeID)) { + if (IsRemoteObjectProxy(instance, interfaceInfo->mPrototypeID)) { args.rval().setBoolean(true); return true; } @@ -896,80 +880,85 @@ static bool InterfaceIsInstance(JSContext* cx, unsigned argc, JS::Value* vp) { return true; } -// name must be an atom (or JS::PropertyKey::NonIntAtom will assert). -static JSObject* CreateInterfaceObject( - JSContext* cx, JS::Handle<JSObject*> global, - JS::Handle<JSObject*> constructorProto, - const DOMIfaceJSClass* constructorClass, unsigned ctorNargs, - const LegacyFactoryFunction* legacyFactoryFunctions, - JS::Handle<JSObject*> proto, const NativeProperties* properties, - const NativeProperties* chromeOnlyProperties, JS::Handle<JSString*> name, - bool isChrome, bool defineOnGlobal, const char* const* legacyWindowAliases, - bool isNamespace) { - JS::Rooted<JSObject*> constructor(cx); - MOZ_ASSERT(constructorProto); - MOZ_ASSERT(constructorClass); - constructor = JS_NewObjectWithGivenProto(cx, constructorClass->ToJSClass(), - constructorProto); - if (!constructor) { - return nullptr; - } - - if (!isNamespace) { - if (!JS_DefineProperty(cx, constructor, "length", ctorNargs, - JSPROP_READONLY)) { - return nullptr; - } - - if (!JS_DefineProperty(cx, constructor, "name", name, JSPROP_READONLY)) { - return nullptr; - } - } - - if (constructorClass->wantsInterfaceIsInstance && isChrome && - !JS_DefineFunction(cx, constructor, "isInstance", InterfaceIsInstance, 1, - // Don't bother making it enumerable - 0)) { - return nullptr; - } - +bool InitInterfaceOrNamespaceObject( + JSContext* cx, JS::Handle<JSObject*> obj, + const NativeProperties* properties, + const NativeProperties* chromeOnlyProperties, bool isChrome) { if (properties) { if (properties->HasStaticMethods() && - !DefinePrefable(cx, constructor, properties->StaticMethods())) { - return nullptr; + !DefinePrefable(cx, obj, properties->StaticMethods())) { + return false; } if (properties->HasStaticAttributes() && - !DefinePrefable(cx, constructor, properties->StaticAttributes())) { - return nullptr; + !DefinePrefable(cx, obj, properties->StaticAttributes())) { + return false; } if (properties->HasConstants() && - !DefinePrefable(cx, constructor, properties->Constants())) { - return nullptr; + !DefinePrefable(cx, obj, properties->Constants())) { + return false; } } if (chromeOnlyProperties && isChrome) { if (chromeOnlyProperties->HasStaticMethods() && - !DefinePrefable(cx, constructor, - chromeOnlyProperties->StaticMethods())) { - return nullptr; + !DefinePrefable(cx, obj, chromeOnlyProperties->StaticMethods())) { + return false; } if (chromeOnlyProperties->HasStaticAttributes() && - !DefinePrefable(cx, constructor, - chromeOnlyProperties->StaticAttributes())) { - return nullptr; + !DefinePrefable(cx, obj, chromeOnlyProperties->StaticAttributes())) { + return false; } if (chromeOnlyProperties->HasConstants() && - !DefinePrefable(cx, constructor, chromeOnlyProperties->Constants())) { + !DefinePrefable(cx, obj, chromeOnlyProperties->Constants())) { + return false; + } + } + + return true; +} + +// name must be an atom (or JS::PropertyKey::NonIntAtom will assert). +static JSObject* CreateInterfaceObject( + JSContext* cx, JS::Handle<JSObject*> global, + JS::Handle<JSObject*> interfaceProto, const DOMInterfaceInfo* interfaceInfo, + unsigned ctorNargs, + const Span<const LegacyFactoryFunction>& legacyFactoryFunctions, + JS::Handle<JSObject*> proto, const NativeProperties* properties, + const NativeProperties* chromeOnlyProperties, JS::Handle<JSString*> name, + bool isChrome, bool defineOnGlobal, + const char* const* legacyWindowAliases) { + MOZ_ASSERT(interfaceProto); + MOZ_ASSERT(interfaceInfo); + + JS::Rooted<jsid> nameId(cx, JS::PropertyKey::NonIntAtom(name)); + + JS::Rooted<JSObject*> constructor(cx); + { + JSFunction* fun = js::NewFunctionByIdWithReservedAndProto( + cx, InterfaceObjectJSNative, interfaceProto, ctorNargs, + JSFUN_CONSTRUCTOR, nameId); + if (!fun) { return nullptr; } + + constructor = JS_GetFunctionObject(fun); } - if (isNamespace && !DefineToStringTag(cx, constructor, name)) { + js::SetFunctionNativeReserved( + constructor, INTERFACE_OBJECT_INFO_RESERVED_SLOT, + JS::PrivateValue(const_cast<DOMInterfaceInfo*>(interfaceInfo))); + + // Eagerly force creation of the .length and .name properties, because they + // need to be defined before the .prototype property (CreateBuiltinFunction + // called from the WebIDL spec sets them, and then the .prototype property is + // defined in the WebIDL spec itself). + bool unused; + if (!JS_HasProperty(cx, constructor, "length", &unused) || + !JS_HasProperty(cx, constructor, "name", &unused)) { return nullptr; } @@ -977,8 +966,19 @@ static JSObject* CreateInterfaceObject( return nullptr; } - JS::Rooted<jsid> nameStr(cx, JS::PropertyKey::NonIntAtom(name)); - if (defineOnGlobal && !DefineConstructor(cx, global, nameStr, constructor)) { + if (!InitInterfaceOrNamespaceObject(cx, constructor, properties, + chromeOnlyProperties, isChrome)) { + return nullptr; + } + + if (defineOnGlobal && !DefineConstructor(cx, global, nameId, constructor)) { + return nullptr; + } + + if (interfaceInfo->wantsInterfaceIsInstance && isChrome && + !JS_DefineFunction(cx, constructor, "isInstance", InterfaceIsInstance, 1, + // Don't bother making it enumerable + 0)) { return nullptr; } @@ -990,25 +990,28 @@ static JSObject* CreateInterfaceObject( } } - if (legacyFactoryFunctions) { - int legacyFactoryFunctionSlot = DOM_INTERFACE_SLOTS_BASE; - while (legacyFactoryFunctions->mName) { - JS::Rooted<JSObject*> legacyFactoryFunction( - cx, CreateConstructor(cx, global, legacyFactoryFunctions->mName, - &legacyFactoryFunctions->mHolder, - legacyFactoryFunctions->mNargs)); - if (!legacyFactoryFunction || - !JS_DefineProperty(cx, legacyFactoryFunction, "prototype", proto, - JSPROP_PERMANENT | JSPROP_READONLY) || - (defineOnGlobal && - !DefineConstructor(cx, global, legacyFactoryFunctions->mName, - legacyFactoryFunction))) { - return nullptr; - } - JS::SetReservedSlot(constructor, legacyFactoryFunctionSlot++, - JS::ObjectValue(*legacyFactoryFunction)); - ++legacyFactoryFunctions; + int legacyFactoryFunctionSlot = + INTERFACE_OBJECT_FIRST_LEGACY_FACTORY_FUNCTION; + for (const LegacyFactoryFunction& lff : legacyFactoryFunctions) { + JSString* fname = JS_AtomizeString(cx, lff.mName); + if (!fname) { + return nullptr; + } + + nameId = JS::PropertyKey::NonIntAtom(fname); + + JS::Rooted<JSObject*> legacyFactoryFunction( + cx, CreateLegacyFactoryFunction(cx, nameId, &lff.mHolder, lff.mNargs)); + if (!legacyFactoryFunction || + !JS_DefineProperty(cx, legacyFactoryFunction, "prototype", proto, + JSPROP_PERMANENT | JSPROP_READONLY) || + (defineOnGlobal && + !DefineConstructor(cx, global, nameId, legacyFactoryFunction))) { + return nullptr; } + js::SetFunctionNativeReserved(constructor, legacyFactoryFunctionSlot, + JS::ObjectValue(*legacyFactoryFunction)); + ++legacyFactoryFunctionSlot; } return constructor; @@ -1101,18 +1104,20 @@ bool DefineProperties(JSContext* cx, JS::Handle<JSObject*> obj, return true; } +namespace binding_detail { + void CreateInterfaceObjects( JSContext* cx, JS::Handle<JSObject*> global, JS::Handle<JSObject*> protoProto, const DOMIfaceAndProtoJSClass* protoClass, - JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> constructorProto, - const DOMIfaceJSClass* constructorClass, unsigned ctorNargs, + JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> interfaceProto, + const DOMInterfaceInfo* interfaceInfo, unsigned ctorNargs, bool isConstructorChromeOnly, - const LegacyFactoryFunction* legacyFactoryFunctions, + const Span<const LegacyFactoryFunction>& legacyFactoryFunctions, JS::Heap<JSObject*>* constructorCache, const NativeProperties* properties, const NativeProperties* chromeOnlyProperties, const char* name, bool defineOnGlobal, const char* const* unscopableNames, bool isGlobal, - const char* const* legacyWindowAliases, bool isNamespace) { - MOZ_ASSERT(protoClass || constructorClass, "Need at least one class!"); + const char* const* legacyWindowAliases) { + MOZ_ASSERT(protoClass || interfaceInfo, "Need at least a class or info!"); MOZ_ASSERT( !((properties && (properties->HasMethods() || properties->HasAttributes())) || @@ -1125,16 +1130,16 @@ void CreateInterfaceObjects( (chromeOnlyProperties && (chromeOnlyProperties->HasStaticMethods() || chromeOnlyProperties->HasStaticAttributes()))) || - constructorClass, - "Static methods but no constructorClass!"); + interfaceInfo, + "Static methods but no info!"); MOZ_ASSERT(!protoClass == !protoCache, "If, and only if, there is an interface prototype object we need " "to cache it"); - MOZ_ASSERT(bool(constructorClass) == bool(constructorCache), + MOZ_ASSERT(bool(interfaceInfo) == bool(constructorCache), "If, and only if, there is an interface object we need to cache " "it"); - MOZ_ASSERT(constructorProto || !constructorClass, - "Must have a constructor proto if we plan to create a constructor " + MOZ_ASSERT(interfaceProto || !interfaceInfo, + "Must have a interface proto if we plan to create an interface " "object"); bool isChrome = nsContentUtils::ThreadsafeIsSystemCaller(cx); @@ -1160,12 +1165,12 @@ void CreateInterfaceObjects( } JSObject* interface; - if (constructorClass) { + if (interfaceInfo) { interface = CreateInterfaceObject( - cx, global, constructorProto, constructorClass, + cx, global, interfaceProto, interfaceInfo, (isChrome || !isConstructorChromeOnly) ? ctorNargs : 0, legacyFactoryFunctions, proto, properties, chromeOnlyProperties, - nameStr, isChrome, defineOnGlobal, legacyWindowAliases, isNamespace); + nameStr, isChrome, defineOnGlobal, legacyWindowAliases); if (!interface) { if (protoCache) { // If we fail we need to make sure to clear the value of protoCache we @@ -1178,6 +1183,45 @@ void CreateInterfaceObjects( } } +} // namespace binding_detail + +void CreateNamespaceObject(JSContext* cx, JS::Handle<JSObject*> global, + JS::Handle<JSObject*> namespaceProto, + const DOMIfaceAndProtoJSClass& namespaceClass, + JS::Heap<JSObject*>* namespaceCache, + const NativeProperties* properties, + const NativeProperties* chromeOnlyProperties, + const char* name, bool defineOnGlobal) { + JS::Rooted<JSString*> nameStr(cx, JS_AtomizeString(cx, name)); + if (!nameStr) { + return; + } + JS::Rooted<jsid> nameId(cx, JS::PropertyKey::NonIntAtom(nameStr)); + + JS::Rooted<JSObject*> namespaceObj( + cx, JS_NewObjectWithGivenProto(cx, namespaceClass.ToJSClass(), + namespaceProto)); + if (!namespaceObj) { + return; + } + + if (!InitInterfaceOrNamespaceObject( + cx, namespaceObj, properties, chromeOnlyProperties, + nsContentUtils::ThreadsafeIsSystemCaller(cx))) { + return; + } + + if (defineOnGlobal && !DefineConstructor(cx, global, nameId, namespaceObj)) { + return; + } + + if (!DefineToStringTag(cx, namespaceObj, nameStr)) { + return; + } + + *namespaceCache = namespaceObj; +} + // Only set aAllowNativeWrapper to false if you really know you need it; if in // doubt use true. Setting it to false disables security wrappers. static bool NativeInterface2JSObjectAndThrowIfFailed( @@ -1461,14 +1505,9 @@ bool ThrowConstructorWithoutNew(JSContext* cx, const char* name) { return ThrowErrorMessage<MSG_CONSTRUCTOR_WITHOUT_NEW>(cx, name); } -inline const NativePropertyHooks* GetNativePropertyHooksFromConstructorFunction( +inline const NativePropertyHooks* GetNativePropertyHooksFromJSNative( JS::Handle<JSObject*> obj) { - MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor)); - const JS::Value& v = js::GetFunctionNativeReserved( - obj, CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT); - const JSNativeHolder* nativeHolder = - static_cast<const JSNativeHolder*>(v.toPrivate()); - return nativeHolder->mPropertyHooks; + return NativeHolderFromObject(obj)->mPropertyHooks; } inline const NativePropertyHooks* GetNativePropertyHooks( @@ -1484,7 +1523,7 @@ inline const NativePropertyHooks* GetNativePropertyHooks( if (JS_ObjectIsFunction(obj)) { type = eInterface; - return GetNativePropertyHooksFromConstructorFunction(obj); + return GetNativePropertyHooksFromJSNative(obj); } MOZ_ASSERT(IsDOMIfaceAndProtoClass(JS::GetClass(obj))); @@ -1855,23 +1894,20 @@ static bool ResolvePrototypeOrConstructor( } if (id.get() == GetJSIDByIndex(cx, XPCJSContext::IDX_ISINSTANCE)) { - const JSClass* objClass = JS::GetClass(obj); - if (IsDOMIfaceAndProtoClass(objClass)) { - const DOMIfaceJSClass* clazz = DOMIfaceJSClass::FromJSClass(objClass); - if (clazz->wantsInterfaceIsInstance) { - cacheOnHolder = true; - - JSObject* funObj = XrayCreateFunction( - cx, wrapper, {InterfaceIsInstance, nullptr}, 1, id); - if (!funObj) { - return false; - } - - desc.set(Some(JS::PropertyDescriptor::Data( - JS::ObjectValue(*funObj), {JS::PropertyAttribute::Configurable, - JS::PropertyAttribute::Writable}))); - return true; + if (IsInterfaceObject(obj) && + InterfaceInfoFromObject(obj)->wantsInterfaceIsInstance) { + cacheOnHolder = true; + + JSObject* funObj = XrayCreateFunction( + cx, wrapper, {InterfaceIsInstance, nullptr}, 1, id); + if (!funObj) { + return false; } + + desc.set(Some(JS::PropertyDescriptor::Data( + JS::ObjectValue(*funObj), {JS::PropertyAttribute::Configurable, + JS::PropertyAttribute::Writable}))); + return true; } } } else if (type == eNamespace) { @@ -2187,31 +2223,6 @@ NativePropertyHooks sEmptyNativePropertyHooks = { constructors::id::_ID_Count, nullptr}; -const JSClassOps sBoringInterfaceObjectClassClassOps = { - nullptr, /* addProperty */ - nullptr, /* delProperty */ - nullptr, /* enumerate */ - nullptr, /* newEnumerate */ - nullptr, /* resolve */ - nullptr, /* mayResolve */ - nullptr, /* finalize */ - ThrowingConstructor, /* call */ - ThrowingConstructor, /* construct */ - nullptr, /* trace */ -}; - -const js::ObjectOps sInterfaceObjectClassObjectOps = { - nullptr, /* lookupProperty */ - nullptr, /* defineProperty */ - nullptr, /* hasProperty */ - nullptr, /* getProperty */ - nullptr, /* setProperty */ - nullptr, /* getOwnPropertyDescriptor */ - nullptr, /* deleteProperty */ - nullptr, /* getElements */ - InterfaceObjectToString, /* funToString */ -}; - bool GetPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<JS::Value> receiver, JS::Handle<jsid> id, bool* found, JS::MutableHandle<JS::Value> vp) { @@ -3572,16 +3583,8 @@ bool ForEachHandler(JSContext* aCx, unsigned aArgc, JS::Value* aVp) { static inline prototypes::ID GetProtoIdForNewtarget( JS::Handle<JSObject*> aNewTarget) { - const JSClass* newTargetClass = JS::GetClass(aNewTarget); - if (IsDOMIfaceAndProtoClass(newTargetClass)) { - const DOMIfaceAndProtoJSClass* newTargetIfaceClass = - DOMIfaceAndProtoJSClass::FromJSClass(newTargetClass); - if (newTargetIfaceClass->mType == eInterface) { - return newTargetIfaceClass->mPrototypeID; - } - } else if (JS_IsNativeFunction(aNewTarget, Constructor)) { - return GetNativePropertyHooksFromConstructorFunction(aNewTarget) - ->mPrototypeID; + if (IsDOMConstructor(aNewTarget)) { + return GetNativePropertyHooksFromJSNative(aNewTarget)->mPrototypeID; } return prototypes::id::_ID_Count; |