From d8bbc7858622b6d9c278469aab701ca0b609cddf Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:35:49 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- dom/bindings/BindingUtils.cpp | 377 +++++++++++---------- dom/bindings/BindingUtils.h | 204 +++++++---- dom/bindings/Bindings.conf | 14 +- dom/bindings/CallbackObject.cpp | 2 +- dom/bindings/Codegen.py | 345 +++++++++---------- dom/bindings/Configuration.py | 91 ++--- dom/bindings/DOMJSClass.h | 21 +- dom/bindings/JSSlots.h | 17 +- dom/bindings/WebIDLGlobalNameHash.cpp | 25 +- dom/bindings/mozwebidlcodegen/__init__.py | 79 ++++- .../mozwebidlcodegen/test/test_mozwebidlcodegen.py | 9 +- dom/bindings/nsIScriptError.idl | 12 +- dom/bindings/parser/WebIDL.py | 234 ++++++++++++- dom/bindings/test/TestBindingHeader.h | 65 ++-- dom/bindings/test/TestCodeGen.webidl | 18 +- dom/bindings/test/TestExampleGen.webidl | 5 +- dom/bindings/test/test_dom_xrays.html | 23 +- 17 files changed, 995 insertions(+), 546 deletions(-) (limited to 'dom/bindings') 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 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(v.toPrivate()); - return (nativeHolder->mNative)(cx, argc, vp); -} - -static JSObject* CreateConstructor(JSContext* cx, JS::Handle 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(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 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 global, - JS::Handle constructorProto, - const DOMIfaceJSClass* constructorClass, unsigned ctorNargs, - const LegacyFactoryFunction* legacyFactoryFunctions, - JS::Handle proto, const NativeProperties* properties, - const NativeProperties* chromeOnlyProperties, JS::Handle name, - bool isChrome, bool defineOnGlobal, const char* const* legacyWindowAliases, - bool isNamespace) { - JS::Rooted 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 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 global, + JS::Handle interfaceProto, const DOMInterfaceInfo* interfaceInfo, + unsigned ctorNargs, + const Span& legacyFactoryFunctions, + JS::Handle proto, const NativeProperties* properties, + const NativeProperties* chromeOnlyProperties, JS::Handle name, + bool isChrome, bool defineOnGlobal, + const char* const* legacyWindowAliases) { + MOZ_ASSERT(interfaceProto); + MOZ_ASSERT(interfaceInfo); + + JS::Rooted nameId(cx, JS::PropertyKey::NonIntAtom(name)); + + JS::Rooted 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(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 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 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 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 obj, return true; } +namespace binding_detail { + void CreateInterfaceObjects( JSContext* cx, JS::Handle global, JS::Handle protoProto, const DOMIfaceAndProtoJSClass* protoClass, - JS::Heap* protoCache, JS::Handle constructorProto, - const DOMIfaceJSClass* constructorClass, unsigned ctorNargs, + JS::Heap* protoCache, JS::Handle interfaceProto, + const DOMInterfaceInfo* interfaceInfo, unsigned ctorNargs, bool isConstructorChromeOnly, - const LegacyFactoryFunction* legacyFactoryFunctions, + const Span& legacyFactoryFunctions, JS::Heap* 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 global, + JS::Handle namespaceProto, + const DOMIfaceAndProtoJSClass& namespaceClass, + JS::Heap* namespaceCache, + const NativeProperties* properties, + const NativeProperties* chromeOnlyProperties, + const char* name, bool defineOnGlobal) { + JS::Rooted nameStr(cx, JS_AtomizeString(cx, name)); + if (!nameStr) { + return; + } + JS::Rooted nameId(cx, JS::PropertyKey::NonIntAtom(nameStr)); + + JS::Rooted 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(cx, name); } -inline const NativePropertyHooks* GetNativePropertyHooksFromConstructorFunction( +inline const NativePropertyHooks* GetNativePropertyHooksFromJSNative( JS::Handle obj) { - MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor)); - const JS::Value& v = js::GetFunctionNativeReserved( - obj, CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT); - const JSNativeHolder* nativeHolder = - static_cast(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 proxy, JS::Handle receiver, JS::Handle id, bool* found, JS::MutableHandle vp) { @@ -3572,16 +3583,8 @@ bool ForEachHandler(JSContext* aCx, unsigned aArgc, JS::Value* aVp) { static inline prototypes::ID GetProtoIdForNewtarget( JS::Handle 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; diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index dd7a42b670..1e3049d456 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -698,12 +698,45 @@ struct JSNativeHolder { const NativePropertyHooks* mPropertyHooks; }; +// Struct for holding information for WebIDL interface objects (which are +// function objects). A pointer to this struct is held in the first reserved +// slot of the function object. +struct DOMInterfaceInfo { + JSNativeHolder nativeHolder; + + ProtoGetter mGetParentProto; + + const prototypes::ID mPrototypeID; // uint16_t + const uint32_t mDepth; + + // Boolean indicating whether this object wants a isInstance property + // pointing to InterfaceIsInstance defined on it. Only ever true for + // interfaces. + bool wantsInterfaceIsInstance; +}; + struct LegacyFactoryFunction { const char* mName; const JSNativeHolder mHolder; unsigned mNargs; }; +namespace binding_detail { + +void CreateInterfaceObjects( + JSContext* cx, JS::Handle global, + JS::Handle protoProto, const DOMIfaceAndProtoJSClass* protoClass, + JS::Heap* protoCache, JS::Handle interfaceProto, + const DOMInterfaceInfo* interfaceInfo, unsigned ctorNargs, + bool isConstructorChromeOnly, + const Span& legacyFactoryFunctions, + JS::Heap* constructorCache, const NativeProperties* properties, + const NativeProperties* chromeOnlyProperties, const char* name, + bool defineOnGlobal, const char* const* unscopableNames, bool isGlobal, + const char* const* legacyWindowAliases); + +} // namespace binding_detail + // clang-format off /* * Create a DOM interface object (if constructorClass is non-null) and/or a @@ -712,24 +745,20 @@ struct LegacyFactoryFunction { * global is used as the parent of the interface object and the interface * prototype object * protoProto is the prototype to use for the interface prototype object. - * interfaceProto is the prototype to use for the interface object. This can be - * null if both constructorClass and constructor are null (as in, - * if we're not creating an interface object at all). * protoClass is the JSClass to use for the interface prototype object. * This is null if we should not create an interface prototype * object. * protoCache a pointer to a JSObject pointer where we should cache the * interface prototype object. This must be null if protoClass is and * vice versa. - * constructorClass is the JSClass to use for the interface object. - * This is null if we should not create an interface object or - * if it should be a function object. - * constructor holds the JSNative to back the interface object which should be a - * Function, unless constructorClass is non-null in which case it is - * ignored. If this is null and constructorClass is also null then - * we should not create an interface object at all. + * interfaceProto is the prototype to use for the interface object. This can be + * null if interfaceInfo is null (as in, if we're not creating an + * interface object at all). + * interfaceInfo is the info to use for the interface object. This can be null + * if we're not creating an interface object. * ctorNargs is the length of the constructor function; 0 if no constructor * isConstructorChromeOnly if true, the constructor is ChromeOnly. + * legacyFactoryFunctions the legacy factory functions (can be empty) * constructorCache a pointer to a JSObject pointer where we should cache the * interface object. This must be null if both constructorClass * and constructor are null, and non-null otherwise. @@ -761,23 +790,66 @@ struct LegacyFactoryFunction { * char* names of the legacy window aliases for this * interface. * - * At least one of protoClass, constructorClass or constructor should be - * non-null. If constructorClass or constructor are non-null, the resulting - * interface object will be defined on the given global with property name - * |name|, which must also be non-null. + * At least one of protoClass or interfaceInfo should be non-null. If + * interfaceInfo is non-null, the resulting interface object will be defined on + * the given global with property name |name|, which must also be non-null. */ // clang-format on -void CreateInterfaceObjects( +template +inline void CreateInterfaceObjects( JSContext* cx, JS::Handle global, JS::Handle protoProto, const DOMIfaceAndProtoJSClass* protoClass, - JS::Heap* protoCache, JS::Handle constructorProto, - const DOMIfaceJSClass* constructorClass, unsigned ctorNargs, + JS::Heap* protoCache, JS::Handle interfaceProto, + const DOMInterfaceInfo* interfaceInfo, unsigned ctorNargs, bool isConstructorChromeOnly, - const LegacyFactoryFunction* legacyFactoryFunctions, + const Span& legacyFactoryFunctions, JS::Heap* constructorCache, const NativeProperties* properties, const NativeProperties* chromeOnlyProperties, const char* name, bool defineOnGlobal, const char* const* unscopableNames, bool isGlobal, - const char* const* legacyWindowAliases, bool isNamespace); + const char* const* legacyWindowAliases) { + // We're using 1 slot for the interface info already, so we only have + // INTERFACE_OBJECT_MAX_SLOTS - 1 slots for legacy factory functions. + static_assert(N <= INTERFACE_OBJECT_MAX_SLOTS - + INTERFACE_OBJECT_FIRST_LEGACY_FACTORY_FUNCTION); + + return binding_detail::CreateInterfaceObjects( + cx, global, protoProto, protoClass, protoCache, interfaceProto, + interfaceInfo, ctorNargs, isConstructorChromeOnly, legacyFactoryFunctions, + constructorCache, properties, chromeOnlyProperties, name, defineOnGlobal, + unscopableNames, isGlobal, legacyWindowAliases); +} + +/* + * Create a namespace object. + * + * global the global on which to install a property named with name pointing to + * the namespace object if defineOnGlobal is true. + * namespaceProto is the prototype to use for the namespace object. + * namespaceClass is the JSClass to use for the namespace object. + * namespaceCache a pointer to a JSObject pointer where we should cache the + * namespace object. + * properties contains the methods, attributes and constants to be defined on + * objects in any compartment. + * chromeProperties contains the methods, attributes and constants to be defined + * on objects in chrome compartments. This must be null if the + * namespace doesn't have any ChromeOnly properties or if the + * object is being created in non-chrome compartment. + * name the name to use for the WebIDL class string, which is the value + * that's used for @@toStringTag, and the name of the property on the + * global object that would be set to the namespace object. + * defineOnGlobal controls whether properties should be defined on the given + * global for the namespace object. This can be false in + * situations where we want the properties to only appear on + * privileged Xrays but not on the unprivileged underlying + * global. + */ +void CreateNamespaceObject(JSContext* cx, JS::Handle global, + JS::Handle namespaceProto, + const DOMIfaceAndProtoJSClass& namespaceClass, + JS::Heap* namespaceCache, + const NativeProperties* properties, + const NativeProperties* chromeOnlyProperties, + const char* name, bool defineOnGlobal); /** * Define the properties (regular and chrome-only) on obj. @@ -2267,18 +2339,53 @@ inline bool AddStringToIDVector(JSContext* cx, name); } -// We use one constructor JSNative to represent all DOM interface objects (so -// we can easily detect when we need to wrap them in an Xray wrapper). We store -// the real JSNative in the mNative member of a JSNativeHolder in the -// CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT slot of the JSFunction object for a -// specific interface object. We also store the NativeProperties in the -// JSNativeHolder. -// Note that some interface objects are not yet a JSFunction but a normal -// JSObject with a DOMJSClass, those do not use these slots. +// We use one JSNative to represent all DOM interface objects (so we can easily +// detect when we need to wrap them in an Xray wrapper). A pointer to the +// relevant DOMInterfaceInfo is stored in the +// INTERFACE_OBJECT_INFO_RESERVED_SLOT slot of the JSFunction object for a +// specific interface object. We store the real JSNative and the +// NativeProperties in a JSNativeHolder, held by the DOMInterfaceInfo. +bool InterfaceObjectJSNative(JSContext* cx, unsigned argc, JS::Value* vp); + +inline bool IsInterfaceObject(JSObject* obj) { + return JS_IsNativeFunction(obj, InterfaceObjectJSNative); +} + +inline const DOMInterfaceInfo* InterfaceInfoFromObject(JSObject* obj) { + MOZ_ASSERT(IsInterfaceObject(obj)); + const JS::Value& v = + js::GetFunctionNativeReserved(obj, INTERFACE_OBJECT_INFO_RESERVED_SLOT); + return static_cast(v.toPrivate()); +} -enum { CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT = 0 }; +inline const JSNativeHolder* NativeHolderFromInterfaceObject(JSObject* obj) { + MOZ_ASSERT(IsInterfaceObject(obj)); + return &InterfaceInfoFromObject(obj)->nativeHolder; +} -bool Constructor(JSContext* cx, unsigned argc, JS::Value* vp); +// We use one JSNative to represent all legacy factory functions (so we can +// easily detect when we need to wrap them in an Xray wrapper). We store the +// real JSNative and the NativeProperties in a JSNativeHolder in the +// LEGACY_FACTORY_FUNCTION_NATIVE_HOLDER_RESERVED_SLOT slot of the JSFunction +// object. +bool LegacyFactoryFunctionJSNative(JSContext* cx, unsigned argc, JS::Value* vp); + +inline bool IsLegacyFactoryFunction(JSObject* obj) { + return JS_IsNativeFunction(obj, LegacyFactoryFunctionJSNative); +} + +inline const JSNativeHolder* NativeHolderFromLegacyFactoryFunction( + JSObject* obj) { + MOZ_ASSERT(IsLegacyFactoryFunction(obj)); + const JS::Value& v = js::GetFunctionNativeReserved( + obj, LEGACY_FACTORY_FUNCTION_NATIVE_HOLDER_RESERVED_SLOT); + return static_cast(v.toPrivate()); +} + +inline const JSNativeHolder* NativeHolderFromObject(JSObject* obj) { + return IsInterfaceObject(obj) ? NativeHolderFromInterfaceObject(obj) + : NativeHolderFromLegacyFactoryFunction(obj); +} // Implementation of the bits that XrayWrapper needs @@ -2350,8 +2457,11 @@ inline bool XrayGetNativeProto(JSContext* cx, JS::Handle obj, protop.set(JS::GetRealmObjectPrototype(cx)); } } else if (JS_ObjectIsFunction(obj)) { - MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor)); - protop.set(JS::GetRealmFunctionPrototype(cx)); + if (IsLegacyFactoryFunction(obj)) { + protop.set(JS::GetRealmFunctionPrototype(cx)); + } else { + protop.set(InterfaceInfoFromObject(obj)->mGetParentProto(cx)); + } } else { const JSClass* clasp = JS::GetClass(obj); MOZ_ASSERT(IsDOMIfaceAndProtoClass(clasp)); @@ -2426,36 +2536,16 @@ inline JSObject* GetCachedSlotStorageObject(JSContext* cx, extern NativePropertyHooks sEmptyNativePropertyHooks; -extern const JSClassOps sBoringInterfaceObjectClassClassOps; - -extern const js::ObjectOps sInterfaceObjectClassObjectOps; +inline bool IsDOMConstructor(JSObject* obj) { + return IsInterfaceObject(obj) || IsLegacyFactoryFunction(obj); +} inline bool UseDOMXray(JSObject* obj) { const JSClass* clasp = JS::GetClass(obj); - return IsDOMClass(clasp) || JS_IsNativeFunction(obj, Constructor) || + return IsDOMClass(clasp) || IsDOMConstructor(obj) || IsDOMIfaceAndProtoClass(clasp); } -inline bool IsDOMConstructor(JSObject* obj) { - if (JS_IsNativeFunction(obj, dom::Constructor)) { - // LegacyFactoryFunction, like Image - return true; - } - - const JSClass* clasp = JS::GetClass(obj); - // Check for a DOM interface object. - return dom::IsDOMIfaceAndProtoClass(clasp) && - dom::DOMIfaceAndProtoJSClass::FromJSClass(clasp)->mType == - dom::eInterface; -} - -#ifdef DEBUG -inline bool HasConstructor(JSObject* obj) { - return JS_IsNativeFunction(obj, Constructor) || - JS::GetClass(obj)->getConstruct(); -} -#endif - // Helpers for creating a const version of a type. template const T& Constify(T& arg) { @@ -3207,10 +3297,6 @@ void DeprecationWarning(JSContext* aCx, JSObject* aObject, void DeprecationWarning(const GlobalObject& aGlobal, DeprecatedOperations aOperation); -// A callback to perform funToString on an interface object -JSString* InterfaceObjectToString(JSContext* aCx, JS::Handle aObject, - unsigned /* indent */); - namespace binding_detail { // Get a JS global object that can be used for some temporary allocations. The // idea is that this should be used for situations when you need to operate in diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 7eed2593aa..d735bcc2ee 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -382,8 +382,8 @@ DOMInterfaces = { }, 'IDBFactory': { - 'implicitJSContext': [ 'open', 'deleteDatabase', 'openForPrincipal', - 'deleteForPrincipal' ], + 'implicitJSContext': [ 'open', 'deleteDatabase', 'databases', + 'openForPrincipal', 'deleteForPrincipal' ], }, 'IDBKeyRange': { @@ -1890,6 +1890,16 @@ if buildconfig.substs.get("ENABLE_TESTS", False): 'register': False, }, + 'TestLegacyFactoryFunctionInterface' : { + 'headerFile': 'TestBindingHeader.h', + 'register': False, + }, + + 'TestLegacyFactoryFunctionInterface2' : { + 'headerFile': 'TestBindingHeader.h', + 'register': False, + }, + 'TestNamedDeleterInterface' : { 'headerFile': 'TestBindingHeader.h', 'register': False, diff --git a/dom/bindings/CallbackObject.cpp b/dom/bindings/CallbackObject.cpp index 484bba06c0..19370666fd 100644 --- a/dom/bindings/CallbackObject.cpp +++ b/dom/bindings/CallbackObject.cpp @@ -29,7 +29,7 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackObject) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(CallbackObject) -NS_IMPL_CYCLE_COLLECTING_RELEASE(CallbackObject) +NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(CallbackObject, Reset()) NS_IMPL_CYCLE_COLLECTION_CLASS(CallbackObject) diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 7a6a28bffc..a6a49f9b2f 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -926,11 +926,51 @@ def InterfaceObjectProtoGetter(descriptor, forXrays=False): return (protoGetter, protoHandleGetter) -class CGInterfaceObjectJSClass(CGThing): - def __init__(self, descriptor, properties): +class CGNamespaceObjectJSClass(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def declare(self): + # We're purely for internal consumption + return "" + + def define(self): + (protoGetter, _) = InterfaceObjectProtoGetter(self.descriptor, forXrays=True) + + classString = self.descriptor.interface.getExtendedAttribute("ClassString") + if classString is None: + classString = self.descriptor.interface.identifier.name + else: + classString = classString[0] + return fill( + """ + static const DOMIfaceAndProtoJSClass sNamespaceObjectClass = { + { + "${classString}", + JSCLASS_IS_DOMIFACEANDPROTOJSCLASS, + JS_NULL_CLASS_OPS, + JS_NULL_CLASS_SPEC, + JS_NULL_CLASS_EXT, + JS_NULL_OBJECT_OPS + }, + eNamespace, + prototypes::id::_ID_Count, + 0, + ${hooks}, + ${protoGetter} + }; + """, + classString=classString, + hooks=NativePropertyHooks(self.descriptor), + protoGetter=protoGetter, + ) + + +class CGInterfaceObjectInfo(CGThing): + def __init__(self, descriptor): CGThing.__init__(self) self.descriptor = descriptor - self.properties = properties def declare(self): # We're purely for internal consumption @@ -938,104 +978,31 @@ class CGInterfaceObjectJSClass(CGThing): def define(self): if self.descriptor.interface.ctor(): - assert not self.descriptor.interface.isNamespace() ctorname = CONSTRUCT_HOOK_NAME - elif self.descriptor.interface.isNamespace(): - ctorname = "nullptr" else: ctorname = "ThrowingConstructor" wantsIsInstance = self.descriptor.interface.hasInterfacePrototypeObject() prototypeID, depth = PrototypeIDAndDepth(self.descriptor) - slotCount = "DOM_INTERFACE_SLOTS_BASE" - if len(self.descriptor.interface.legacyFactoryFunctions) > 0: - slotCount += " + %i /* slots for the legacy factory functions */" % len( - self.descriptor.interface.legacyFactoryFunctions - ) (protoGetter, _) = InterfaceObjectProtoGetter(self.descriptor, forXrays=True) - if ctorname == "ThrowingConstructor": - ret = "" - classOpsPtr = "&sBoringInterfaceObjectClassClassOps" - elif ctorname == "nullptr": - ret = "" - classOpsPtr = "JS_NULL_CLASS_OPS" - else: - ret = fill( - """ - static const JSClassOps sInterfaceObjectClassOps = { - nullptr, /* addProperty */ - nullptr, /* delProperty */ - nullptr, /* enumerate */ - nullptr, /* newEnumerate */ - nullptr, /* resolve */ - nullptr, /* mayResolve */ - nullptr, /* finalize */ - ${ctorname}, /* call */ - ${ctorname}, /* construct */ - nullptr, /* trace */ - }; - - """, - ctorname=ctorname, - ) - classOpsPtr = "&sInterfaceObjectClassOps" - - if self.descriptor.interface.isNamespace(): - classString = self.descriptor.interface.getExtendedAttribute("ClassString") - if classString is None: - classString = self.descriptor.interface.identifier.name - else: - classString = classString[0] - funToString = "nullptr" - objectOps = "JS_NULL_OBJECT_OPS" - else: - classString = "Function" - funToString = ( - '"function %s() {\\n [native code]\\n}"' - % self.descriptor.interface.identifier.name - ) - # We need non-default ObjectOps so we can actually make - # use of our funToString. - objectOps = "&sInterfaceObjectClassObjectOps" - - ret = ret + fill( + return fill( """ - static const DOMIfaceJSClass sInterfaceObjectClass = { - { - { - "${classString}", - JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}), - ${classOpsPtr}, - JS_NULL_CLASS_SPEC, - JS_NULL_CLASS_EXT, - ${objectOps} - }, - ${type}, - ${prototypeID}, - ${depth}, - ${hooks}, - ${protoGetter} - }, + static const DOMInterfaceInfo sInterfaceObjectInfo = { + { ${ctorname}, ${hooks} }, + ${protoGetter}, + ${prototypeID}, + ${depth}, ${wantsIsInstance}, - ${funToString} }; """, - classString=classString, - slotCount=slotCount, - classOpsPtr=classOpsPtr, + ctorname=ctorname, hooks=NativePropertyHooks(self.descriptor), - objectOps=objectOps, - type="eNamespace" - if self.descriptor.interface.isNamespace() - else "eInterface", + protoGetter=protoGetter, prototypeID=prototypeID, depth=depth, - protoGetter=protoGetter, wantsIsInstance=toStringBool(wantsIsInstance), - funToString=funToString, ) - return ret class CGList(CGThing): @@ -2323,7 +2290,6 @@ class CGLegacyFactoryFunctions(CGThing): static const LegacyFactoryFunction legacyFactoryFunctions[] = { $*{legacyFactoryFunctions} - { nullptr, { nullptr, nullptr }, 0 } }; """, name=self.descriptor.name, @@ -3538,83 +3504,30 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): self.haveLegacyWindowAliases = haveLegacyWindowAliases def definition_body(self): - (protoGetter, protoHandleGetter) = InterfacePrototypeObjectProtoGetter( - self.descriptor - ) - if protoHandleGetter is None: - parentProtoType = "Rooted" - getParentProto = "aCx, " + protoGetter - else: - parentProtoType = "Handle" - getParentProto = protoHandleGetter - getParentProto = getParentProto + "(aCx)" - - (protoGetter, protoHandleGetter) = InterfaceObjectProtoGetter(self.descriptor) - if protoHandleGetter is None: - getConstructorProto = "aCx, " + protoGetter - constructorProtoType = "Rooted" - else: - getConstructorProto = protoHandleGetter - constructorProtoType = "Handle" - getConstructorProto += "(aCx)" - needInterfaceObject = self.descriptor.interface.hasInterfaceObject() - needInterfacePrototypeObject = ( - self.descriptor.interface.hasInterfacePrototypeObject() - ) - - # if we don't need to create anything, why are we generating this? - assert needInterfaceObject or needInterfacePrototypeObject - - getParentProto = fill( - """ - JS::${type} parentProto(${getParentProto}); - if (!parentProto) { - return; - } - """, - type=parentProtoType, - getParentProto=getParentProto, - ) - - getConstructorProto = fill( - """ - JS::${type} constructorProto(${getConstructorProto}); - if (!constructorProto) { - return; - } - """, - type=constructorProtoType, - getConstructorProto=getConstructorProto, - ) - - if self.descriptor.interface.ctor(): - constructArgs = methodLength(self.descriptor.interface.ctor()) - isConstructorChromeOnly = isChromeOnly(self.descriptor.interface.ctor()) - else: - constructArgs = 0 - isConstructorChromeOnly = False - if len(self.descriptor.interface.legacyFactoryFunctions) > 0: - legacyFactoryFunctions = "legacyFactoryFunctions" - else: - legacyFactoryFunctions = "nullptr" + if needInterfaceObject: + (protoGetter, protoHandleGetter) = InterfaceObjectProtoGetter( + self.descriptor + ) + if protoHandleGetter is None: + getConstructorProto = "aCx, " + protoGetter + constructorProtoType = "Rooted" + else: + getConstructorProto = protoHandleGetter + constructorProtoType = "Handle" - if needInterfacePrototypeObject: - protoClass = "&sPrototypeClass" - protoCache = ( - "&aProtoAndIfaceCache.EntrySlotOrCreate(prototypes::id::%s)" - % self.descriptor.name + getConstructorProto = fill( + """ + JS::${type} constructorProto(${getConstructorProto}(aCx)); + if (!constructorProto) { + return; + } + """, + type=constructorProtoType, + getConstructorProto=getConstructorProto, ) - parentProto = "parentProto" - getParentProto = CGGeneric(getParentProto) - else: - protoClass = "nullptr" - protoCache = "nullptr" - parentProto = "nullptr" - getParentProto = None - if needInterfaceObject: - interfaceClass = "&sInterfaceObjectClass" + interfaceInfo = "&sInterfaceObjectInfo" interfaceCache = ( "&aProtoAndIfaceCache.EntrySlotOrCreate(constructors::id::%s)" % self.descriptor.name @@ -3624,12 +3537,11 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): else: # We don't have slots to store the legacy factory functions. assert len(self.descriptor.interface.legacyFactoryFunctions) == 0 - interfaceClass = "nullptr" + interfaceInfo = "nullptr" interfaceCache = "nullptr" getConstructorProto = None constructorProto = "nullptr" - isGlobal = self.descriptor.isGlobal() is not None if self.properties.hasNonChromeOnly(): properties = "sNativeProperties.Upcast()" else: @@ -3648,31 +3560,117 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): name = self.descriptor.interface.getClassName() assert not (needInterfaceObject and " " in name) - call = fill( + if self.descriptor.interface.isNamespace(): + # If we don't need to create anything, why are we generating this? + assert needInterfaceObject + + call = fill( + """ + JS::Heap* interfaceCache = ${interfaceCache}; + dom::CreateNamespaceObject(aCx, aGlobal, ${constructorProto}, + sNamespaceObjectClass, + interfaceCache, + ${properties}, + ${chromeProperties}, + "${name}", aDefineOnGlobal); + """, + interfaceCache=interfaceCache, + constructorProto=constructorProto, + properties=properties, + chromeProperties=chromeProperties, + name=name, + ) + return CGList( + [ + getConstructorProto, + CGGeneric(call), + ], + "\n", + ).define() + + needInterfacePrototypeObject = ( + self.descriptor.interface.hasInterfacePrototypeObject() + ) + + # If we don't need to create anything, why are we generating this? + assert needInterfaceObject or needInterfacePrototypeObject + + if needInterfacePrototypeObject: + (protoGetter, protoHandleGetter) = InterfacePrototypeObjectProtoGetter( + self.descriptor + ) + if protoHandleGetter is None: + parentProtoType = "Rooted" + getParentProto = "aCx, " + protoGetter + else: + parentProtoType = "Handle" + getParentProto = protoHandleGetter + + getParentProto = fill( + """ + JS::${type} parentProto(${getParentProto}(aCx)); + if (!parentProto) { + return; + } + """, + type=parentProtoType, + getParentProto=getParentProto, + ) + + protoClass = "&sPrototypeClass" + protoCache = ( + "&aProtoAndIfaceCache.EntrySlotOrCreate(prototypes::id::%s)" + % self.descriptor.name + ) + parentProto = "parentProto" + getParentProto = CGGeneric(getParentProto) + else: + protoClass = "nullptr" + protoCache = "nullptr" + parentProto = "nullptr" + getParentProto = None + + if self.descriptor.interface.ctor(): + constructArgs = methodLength(self.descriptor.interface.ctor()) + isConstructorChromeOnly = isChromeOnly(self.descriptor.interface.ctor()) + else: + constructArgs = 0 + isConstructorChromeOnly = False + if len(self.descriptor.interface.legacyFactoryFunctions) > 0: + legacyFactoryFunctions = "Span(legacyFactoryFunctions)" + else: + legacyFactoryFunctions = "Span{}" + + isGlobal = self.descriptor.isGlobal() is not None + + ensureCaches = fill( """ JS::Heap* protoCache = ${protoCache}; JS::Heap* interfaceCache = ${interfaceCache}; + """, + protoCache=protoCache, + interfaceCache=interfaceCache, + ) + call = fill( + """ dom::CreateInterfaceObjects(aCx, aGlobal, ${parentProto}, ${protoClass}, protoCache, - ${constructorProto}, ${interfaceClass}, ${constructArgs}, ${isConstructorChromeOnly}, ${legacyFactoryFunctions}, + ${constructorProto}, ${interfaceInfo}, ${constructArgs}, ${isConstructorChromeOnly}, ${legacyFactoryFunctions}, interfaceCache, ${properties}, ${chromeProperties}, "${name}", aDefineOnGlobal, ${unscopableNames}, ${isGlobal}, - ${legacyWindowAliases}, - ${isNamespace}); + ${legacyWindowAliases}); """, protoClass=protoClass, parentProto=parentProto, - protoCache=protoCache, constructorProto=constructorProto, - interfaceClass=interfaceClass, + interfaceInfo=interfaceInfo, constructArgs=constructArgs, isConstructorChromeOnly=toStringBool(isConstructorChromeOnly), legacyFactoryFunctions=legacyFactoryFunctions, - interfaceCache=interfaceCache, properties=properties, chromeProperties=chromeProperties, name=name, @@ -3681,7 +3679,6 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): legacyWindowAliases="legacyWindowAliases" if self.haveLegacyWindowAliases else "nullptr", - isNamespace=toStringBool(self.descriptor.interface.isNamespace()), ) # If we fail after here, we must clear interface and prototype caches @@ -3896,8 +3893,14 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): ) else: defineProtoVar = None + + # ensureCaches needs to come first as it crashes on failure (like OOM). + # We want to make sure that the caches do exist before we try to return + # to the caller, so it can rely on that (and detect other failures by + # checking for null in the caches). return CGList( [ + CGGeneric(ensureCaches), getParentProto, getConstructorProto, CGGeneric(call), @@ -16955,9 +16958,11 @@ class CGDescriptor(CGThing): # done, set up our NativePropertyHooks. cgThings.append(CGNativePropertyHooks(descriptor, properties)) - if descriptor.interface.hasInterfaceObject(): + if descriptor.interface.isNamespace(): + cgThings.append(CGNamespaceObjectJSClass(descriptor)) + elif descriptor.interface.hasInterfaceObject(): cgThings.append(CGClassConstructor(descriptor, descriptor.interface.ctor())) - cgThings.append(CGInterfaceObjectJSClass(descriptor, properties)) + cgThings.append(CGInterfaceObjectInfo(descriptor)) cgThings.append(CGLegacyFactoryFunctions(descriptor)) cgThings.append(CGLegacyCallHook(descriptor)) diff --git a/dom/bindings/Configuration.py b/dom/bindings/Configuration.py index 354a450de6..cfcb2dcf39 100644 --- a/dom/bindings/Configuration.py +++ b/dom/bindings/Configuration.py @@ -43,6 +43,52 @@ class Configuration(DescriptorProvider): the configuration file. """ + class IDLAttrGetterOrSetterTemplate: + class TemplateAdditionalArg: + def __init__(self, type, name, value=None): + self.type = type + self.name = name + self.value = value + + def __init__(self, template, getter, setter, argument, attrName): + self.descriptor = None + self.usedInOtherInterfaces = False + self.getter = getter + self.setter = setter + self.argument = ( + Configuration.IDLAttrGetterOrSetterTemplate.TemplateAdditionalArg( + *argument + ) + ) + self.attrNameString = attrName + self.attr = None + + class TemplateIDLAttribute: + def __init__(self, attr): + assert attr.isAttr() + assert not attr.isMaplikeOrSetlikeAttr() + assert not attr.slotIndices + + self.identifier = attr.identifier + self.type = attr.type + self.extendedAttributes = attr.getExtendedAttributes() + self.slotIndices = None + + def getExtendedAttribute(self, name): + return self.extendedAttributes.get(name) + + def isAttr(self): + return True + + def isMaplikeOrSetlikeAttr(self): + return False + + def isMethod(self): + return False + + def isStatic(self): + return False + def __init__(self, filename, webRoots, parseData, generatedEvents=[]): DescriptorProvider.__init__(self) @@ -51,28 +97,12 @@ class Configuration(DescriptorProvider): exec(io.open(filename, encoding="utf-8").read(), glbl) config = glbl["DOMInterfaces"] - class IDLAttrGetterOrSetterTemplate: - def __init__(self, template, getter, setter, argument, attrName): - class TemplateAdditionalArg: - def __init__(self, type, name, value=None): - self.type = type - self.name = name - self.value = value - - self.descriptor = None - self.usedInOtherInterfaces = False - self.getter = getter - self.setter = setter - self.argument = TemplateAdditionalArg(*argument) - self.attrNameString = attrName - self.attr = None - self.attributeTemplates = dict() attributeTemplatesByInterface = dict() for interface, templates in glbl["TemplatedAttributes"].items(): for template in templates: name = template.get("template") - t = IDLAttrGetterOrSetterTemplate(**template) + t = Configuration.IDLAttrGetterOrSetterTemplate(**template) self.attributeTemplates[name] = t attributeTemplatesByInterface.setdefault(interface, list()).append(t) @@ -351,33 +381,8 @@ class Configuration(DescriptorProvider): ) # This mimics a real IDL attribute for templated bindings. - class TemplateIDLAttribute: - def __init__(self, attr): - assert attr.isAttr() - assert not attr.isMaplikeOrSetlikeAttr() - assert not attr.slotIndices - - self.identifier = attr.identifier - self.type = attr.type - self.extendedAttributes = attr.getExtendedAttributes() - self.slotIndices = None - - def getExtendedAttribute(self, name): - return self.extendedAttributes.get(name) - - def isAttr(self): - return True - - def isMaplikeOrSetlikeAttr(self): - return False - - def isMethod(self): - return False - - def isStatic(self): - return False - template.attr = TemplateIDLAttribute(firstAttribute) + template.attr = Configuration.TemplateIDLAttribute(firstAttribute) def filterExtendedAttributes(extendedAttributes): # These are the extended attributes that we allow to have diff --git a/dom/bindings/DOMJSClass.h b/dom/bindings/DOMJSClass.h index 12055fe6ba..3d8a734140 100644 --- a/dom/bindings/DOMJSClass.h +++ b/dom/bindings/DOMJSClass.h @@ -570,7 +570,7 @@ struct DOMIfaceAndProtoJSClass { // initialization for aggregate/POD types. const JSClass mBase; - // Either eInterface, eNamespace, eInterfacePrototype, + // Either eNamespace, eInterfacePrototype, // eGlobalInterfacePrototype or eNamedPropertiesObject. DOMObjectType mType; // uint8_t @@ -589,25 +589,6 @@ struct DOMIfaceAndProtoJSClass { const JSClass* ToJSClass() const { return &mBase; } }; -// Special JSClass for DOM interface objects. -struct DOMIfaceJSClass : public DOMIfaceAndProtoJSClass { - // Boolean indicating whether this object wants an isInstance property - // pointing to InterfaceIsInstance defined on it. Only ever true for the - // eInterface case. - bool wantsInterfaceIsInstance; - - // The value to return for Function.prototype.toString on this interface - // object. - const char* mFunToString; - - static const DOMIfaceJSClass* FromJSClass(const JSClass* base) { - const DOMIfaceAndProtoJSClass* clazz = - DOMIfaceAndProtoJSClass::FromJSClass(base); - MOZ_ASSERT(clazz->mType == eInterface || clazz->mType == eNamespace); - return static_cast(clazz); - } -}; - class ProtoAndIfaceCache; inline bool DOMGlobalHasProtoAndIFaceCache(JSObject* global) { diff --git a/dom/bindings/JSSlots.h b/dom/bindings/JSSlots.h index c9e9ffed07..54aed1289d 100644 --- a/dom/bindings/JSSlots.h +++ b/dom/bindings/JSSlots.h @@ -23,8 +23,21 @@ #define DOM_INSTANCE_RESERVED_SLOTS 1 // Interface objects store a number of reserved slots equal to -// DOM_INTERFACE_SLOTS_BASE + number of legacy factory functions. -#define DOM_INTERFACE_SLOTS_BASE 0 +// INTERFACE_OBJECT_INFO_RESERVED_SLOT + number of legacy factory functions, +// with a maximum of js::FunctionExtended::NUM_EXTENDED_SLOTS. +// INTERFACE_OBJECT_INFO_RESERVED_SLOT contains the DOMInterfaceInfo. +// INTERFACE_OBJECT_FIRST_LEGACY_FACTORY_FUNCTION and higher contain the +// JSObjects for the legacy factory functions. +enum { + INTERFACE_OBJECT_INFO_RESERVED_SLOT = 0, + INTERFACE_OBJECT_FIRST_LEGACY_FACTORY_FUNCTION, +}; +// See js::FunctionExtended::NUM_EXTENDED_SLOTS. +#define INTERFACE_OBJECT_MAX_SLOTS 3 + +// Legacy factory functions store a JSNativeHolder in the +// LEGACY_FACTORY_FUNCTION_NATIVE_HOLDER_RESERVED_SLOT slot. +enum { LEGACY_FACTORY_FUNCTION_NATIVE_HOLDER_RESERVED_SLOT = 0 }; // Interface prototype objects store a number of reserved slots equal to // DOM_INTERFACE_PROTO_SLOTS_BASE or DOM_INTERFACE_PROTO_SLOTS_BASE + 1 if a diff --git a/dom/bindings/WebIDLGlobalNameHash.cpp b/dom/bindings/WebIDLGlobalNameHash.cpp index 3201f71ffd..24e48a343f 100644 --- a/dom/bindings/WebIDLGlobalNameHash.cpp +++ b/dom/bindings/WebIDLGlobalNameHash.cpp @@ -38,16 +38,21 @@ static JSObject* FindNamedConstructorForXray( return nullptr; } - // This is a call over Xrays, so we will actually use the return value - // (instead of just having it defined on the global now). Check for named - // constructors with this id, in case that's what the caller is asking for. - for (unsigned slot = DOM_INTERFACE_SLOTS_BASE; - slot < JSCLASS_RESERVED_SLOTS(JS::GetClass(interfaceObject)); ++slot) { - JSObject* constructor = - &JS::GetReservedSlot(interfaceObject, slot).toObject(); - if (JS_GetMaybePartialFunctionId(JS_GetObjectFunction(constructor)) == - aId.toString()) { - return constructor; + if (IsInterfaceObject(interfaceObject)) { + // This is a call over Xrays, so we will actually use the return value + // (instead of just having it defined on the global now). Check for named + // constructors with this id, in case that's what the caller is asking for. + for (unsigned slot = INTERFACE_OBJECT_FIRST_LEGACY_FACTORY_FUNCTION; + slot < INTERFACE_OBJECT_MAX_SLOTS; ++slot) { + const JS::Value& v = js::GetFunctionNativeReserved(interfaceObject, slot); + if (!v.isObject()) { + break; + } + JSObject* constructor = &v.toObject(); + if (JS_GetMaybePartialFunctionId(JS_GetObjectFunction(constructor)) == + aId.toString()) { + return constructor; + } } } diff --git a/dom/bindings/mozwebidlcodegen/__init__.py b/dom/bindings/mozwebidlcodegen/__init__.py index 3e8c6aa420..bba95edb78 100644 --- a/dom/bindings/mozwebidlcodegen/__init__.py +++ b/dom/bindings/mozwebidlcodegen/__init__.py @@ -11,7 +11,8 @@ import io import json import logging import os -from copy import deepcopy +import sys +from multiprocessing import Pool import mozpack.path as mozpath from mach.mixin.logging import LoggingMixin @@ -22,6 +23,50 @@ from mozbuild.util import FileAvoidWrite # There are various imports in this file in functions to avoid adding # dependencies to config.status. See bug 949875. +# Limit the count on Windows, because of bug 1889842 and also the +# inefficiency of fork on Windows. +DEFAULT_PROCESS_COUNT = 4 if sys.platform == "win32" else os.cpu_count() + + +class WebIDLPool: + """ + Distribute generation load across several processes, avoiding redundant state + copies. + """ + + GeneratorState = None + + def __init__(self, GeneratorState, *, processes=None): + if processes is None: + processes = DEFAULT_PROCESS_COUNT + + # As a special case, don't spawn an extra process if processes=1 + if processes == 1: + WebIDLPool._init(GeneratorState) + + class SeqPool: + def map(self, *args): + return list(map(*args)) + + self.pool = SeqPool() + else: + self.pool = Pool( + initializer=WebIDLPool._init, + initargs=(GeneratorState,), + processes=processes, + ) + + def run(self, filenames): + return self.pool.map(WebIDLPool._run, filenames) + + @staticmethod + def _init(GeneratorState): + WebIDLPool.GeneratorState = GeneratorState + + @staticmethod + def _run(filename): + return WebIDLPool.GeneratorState._generate_build_files_for_webidl(filename) + class BuildResult(object): """Represents the result of processing WebIDL files. @@ -50,6 +95,9 @@ class WebIDLCodegenManagerState(dict): state should be considered a black box to everyone except WebIDLCodegenManager. But we'll still document it. + Any set stored in this dict should be copied and sorted in the `dump()` + method. + Fields: version @@ -117,12 +165,15 @@ class WebIDLCodegenManagerState(dict): def dump(self, fh): """Dump serialized state to a file handle.""" - normalized = deepcopy(self) + normalized = self.copy() + webidls = normalized["webidls"] = self["webidls"].copy() for k, v in self["webidls"].items(): + webidls_k = webidls[k] = v.copy() + # Convert sets to lists because JSON doesn't support sets. - normalized["webidls"][k]["outputs"] = sorted(v["outputs"]) - normalized["webidls"][k]["inputs"] = sorted(v["inputs"]) + webidls_k["outputs"] = sorted(v["outputs"]) + webidls_k["inputs"] = sorted(v["inputs"]) normalized["dictionaries_convertible_to_js"] = sorted( self["dictionaries_convertible_to_js"] @@ -251,7 +302,7 @@ class WebIDLCodegenManager(LoggingMixin): return self._config - def generate_build_files(self): + def generate_build_files(self, *, processes=None): """Generate files required for the build. This function is in charge of generating all the .h/.cpp files derived @@ -279,6 +330,9 @@ class WebIDLCodegenManager(LoggingMixin): file. 3. If an output file is missing, ensure it is present by performing necessary regeneration. + + if `processes` is set to None, run in parallel using the + multiprocess.Pool default. If set to 1, don't use extra processes. """ # Despite #1 above, we assume the build system is smart enough to not # invoke us if nothing has changed. Therefore, any invocation means @@ -311,11 +365,20 @@ class WebIDLCodegenManager(LoggingMixin): d.identifier.name for d in self._config.getDictionariesConvertibleFromJS() ) + # Distribute the generation load across several processes. This requires + # a) that `self' is serializable and b) that `self' is unchanged by + # _generate_build_files_for_webidl(...) + ordered_changed_inputs = sorted(changed_inputs) + pool = WebIDLPool(self, processes=processes) + generation_results = pool.run(ordered_changed_inputs) + # Generate bindings from .webidl files. - for filename in sorted(changed_inputs): + for filename, generation_result in zip( + ordered_changed_inputs, generation_results + ): basename = mozpath.basename(filename) result.inputs.add(filename) - written, deps = self._generate_build_files_for_webidl(filename) + written, deps = generation_result result.created |= written[0] result.updated |= written[1] result.unchanged |= written[2] @@ -560,6 +623,8 @@ class WebIDLCodegenManager(LoggingMixin): return paths + # Parallelization of the generation step relies on this method not changing + # the internal state of the object def _generate_build_files_for_webidl(self, filename): from Codegen import CGBindingRoot, CGEventRoot diff --git a/dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py b/dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py index cd822af538..46a3ab6239 100644 --- a/dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py +++ b/dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py @@ -247,18 +247,21 @@ class TestWebIDLCodegenManager(unittest.TestCase): args = self._get_manager_args() m1 = WebIDLCodegenManager(**args) with MockedOpen({fake_path: "# Original content"}): + # MockedOpen is not compatible with distributed filesystem + # access, so force the number of processes used to generate + # files to 1. try: - result = m1.generate_build_files() + result = m1.generate_build_files(processes=1) l = len(result.inputs) with io.open(fake_path, "wt", newline="\n") as fh: fh.write("# Modified content") m2 = WebIDLCodegenManager(**args) - result = m2.generate_build_files() + result = m2.generate_build_files(processes=1) self.assertEqual(len(result.inputs), l) - result = m2.generate_build_files() + result = m2.generate_build_files(processes=1) self.assertEqual(len(result.inputs), 0) finally: del sys.modules["mozwebidlcodegen.fakemodule"] diff --git a/dom/bindings/nsIScriptError.idl b/dom/bindings/nsIScriptError.idl index 3d7925a08d..b650de181c 100644 --- a/dom/bindings/nsIScriptError.idl +++ b/dom/bindings/nsIScriptError.idl @@ -91,7 +91,7 @@ interface nsIScriptError : nsIConsoleMessage // Error created from a Promise rejection. readonly attribute boolean isPromiseRejection; - [noscript] void initIsPromiseRejection(in bool isPromiseRejection); + [noscript] void initIsPromiseRejection(in boolean isPromiseRejection); // The "exception" value generated when an uncaught exception is thrown // by JavaScript. This can be any value, e.g. @@ -135,8 +135,8 @@ interface nsIScriptError : nsIConsoleMessage in uint32_t columnNumber, in uint32_t flags, in ACString category, - [optional] in bool fromPrivateWindow, - [optional] in bool fromChromeContext); + [optional] in boolean fromPrivateWindow, + [optional] in boolean fromChromeContext); /* This should be called instead of nsIScriptError.init to * initialize with a window id. The window id should be for the @@ -158,7 +158,7 @@ interface nsIScriptError : nsIConsoleMessage in uint32_t flags, in ACString category, in unsigned long long innerWindowID, - [optional] in bool fromChromeContext); + [optional] in boolean fromChromeContext); /* This is the same function as initWithWindowID, but it expects an already * sanitized sourceName. @@ -172,7 +172,7 @@ interface nsIScriptError : nsIConsoleMessage in uint32_t flags, in ACString category, in unsigned long long innerWindowID, - [optional] in bool fromChromeContext); + [optional] in boolean fromChromeContext); /* This is the same function as initWithWindowID with an uri as a source parameter. */ @@ -184,7 +184,7 @@ interface nsIScriptError : nsIConsoleMessage in uint32_t flags, in ACString category, in unsigned long long innerWindowID, - [optional] in bool fromChromeContext); + [optional] in boolean fromChromeContext); /* Initialize the script source ID in a new error. */ void initSourceId(in uint32_t sourceId); diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index 43f9ec12f1..9a3a77f075 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -121,6 +121,8 @@ class Location(object): class BuiltinLocation(object): + __slots__ = "msg", "filename" + def __init__(self, text): self.msg = text + "\n" self.filename = "" @@ -142,9 +144,11 @@ class BuiltinLocation(object): class IDLObject(object): + __slots__ = "location", "userData", "filename" + def __init__(self, location): self.location = location - self.userData = dict() + self.userData = {} self.filename = location and location.filename def isInterface(self): @@ -220,6 +224,8 @@ class IDLObject(object): class IDLScope(IDLObject): + __slots__ = "parentScope", "_name", "_dict", "globalNames", "globalNameMapping" + def __init__(self, location, parentScope, identifier): IDLObject.__init__(self, location) @@ -349,6 +355,8 @@ class IDLScope(IDLObject): class IDLIdentifier(IDLObject): + __slots__ = "name", "scope" + def __init__(self, location, scope, name): IDLObject.__init__(self, location) @@ -373,12 +381,14 @@ class IDLIdentifier(IDLObject): class IDLUnresolvedIdentifier(IDLObject): + __slots__ = ("name",) + def __init__( self, location, name, allowDoubleUnderscore=False, allowForbidden=False ): IDLObject.__init__(self, location) - assert len(name) > 0 + assert name if name == "__noSuchMethod__": raise WebIDLError("__noSuchMethod__ is deprecated", [location]) @@ -417,6 +427,7 @@ class IDLUnresolvedIdentifier(IDLObject): class IDLObjectWithIdentifier(IDLObject): + # no slots, incompatible with multiple inheritance def __init__(self, location, parentScope, identifier): IDLObject.__init__(self, location) @@ -434,6 +445,8 @@ class IDLObjectWithIdentifier(IDLObject): class IDLObjectWithScope(IDLObjectWithIdentifier, IDLScope): + __slots__ = () + def __init__(self, location, parentScope, identifier): assert isinstance(identifier, IDLUnresolvedIdentifier) @@ -442,6 +455,8 @@ class IDLObjectWithScope(IDLObjectWithIdentifier, IDLScope): class IDLIdentifierPlaceholder(IDLObjectWithIdentifier): + __slots__ = () + def __init__(self, location, identifier): assert isinstance(identifier, IDLUnresolvedIdentifier) IDLObjectWithIdentifier.__init__(self, location, None, identifier) @@ -459,6 +474,7 @@ class IDLIdentifierPlaceholder(IDLObjectWithIdentifier): class IDLExposureMixins: + # no slots, incompatible with multiple inheritance def __init__(self, location): # _exposureGlobalNames are the global names listed in our [Exposed] # extended attribute. exposureSet is the exposure set as defined in the @@ -541,6 +557,8 @@ class IDLExposureMixins: class IDLExternalInterface(IDLObjectWithIdentifier): + __slots__ = ("parent",) + def __init__(self, location, parentScope, identifier): assert isinstance(identifier, IDLUnresolvedIdentifier) assert isinstance(parentScope, IDLScope) @@ -591,6 +609,8 @@ class IDLExternalInterface(IDLObjectWithIdentifier): class IDLPartialDictionary(IDLObject): + __slots__ = "identifier", "members", "_nonPartialDictionary", "_finished" + def __init__(self, location, name, members, nonPartialDictionary): assert isinstance(name, IDLUnresolvedIdentifier) @@ -619,6 +639,15 @@ class IDLPartialDictionary(IDLObject): class IDLPartialInterfaceOrNamespace(IDLObject): + __slots__ = ( + "identifier", + "members", + "propagatedExtendedAttrs", + "_haveSecureContextExtendedAttribute", + "_nonPartialInterfaceOrNamespace", + "_finished", + ) + def __init__(self, location, name, members, nonPartialInterfaceOrNamespace): assert isinstance(name, IDLUnresolvedIdentifier) @@ -726,12 +755,22 @@ def globalNameSetToExposureSet(globalScope, nameSet, exposureSet): # we use a special class to be able to store them both in the scope for the # same identifier. class IDLOperations: + __slots__ = "static", "regular" + def __init__(self, static=None, regular=None): self.static = static self.regular = regular class IDLInterfaceOrInterfaceMixinOrNamespace(IDLObjectWithScope, IDLExposureMixins): + __slots__ = ( + "_finished", + "members", + "_partials", + "_extendedAttrDict", + "_isKnownNonPartial", + ) + def __init__(self, location, parentScope, name): assert isinstance(parentScope, IDLScope) assert isinstance(name, IDLUnresolvedIdentifier) @@ -897,10 +936,12 @@ class IDLInterfaceOrInterfaceMixinOrNamespace(IDLObjectWithScope, IDLExposureMix class IDLInterfaceMixin(IDLInterfaceOrInterfaceMixinOrNamespace): + __slots__ = ("actualExposureGlobalNames",) + def __init__(self, location, parentScope, name, members, isKnownNonPartial): self.actualExposureGlobalNames = set() - assert isKnownNonPartial or len(members) == 0 + assert isKnownNonPartial or not members IDLInterfaceOrInterfaceMixinOrNamespace.__init__( self, location, parentScope, name ) @@ -1001,9 +1042,27 @@ class IDLInterfaceMixin(IDLInterfaceOrInterfaceMixinOrNamespace): class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace): + __slots__ = ( + "parent", + "_callback", + "maplikeOrSetlikeOrIterable", + "legacyFactoryFunctions", + "legacyWindowAliases", + "includedMixins", + "interfacesBasedOnSelf", + "_hasChildInterfaces", + "_isOnGlobalProtoChain", + "totalMembersInSlots", + "_ownMembersInSlots", + "iterableInterface", + "asyncIterableInterface", + "hasCrossOriginMembers", + "hasDescendantWithCrossOriginMembers", + ) + def __init__(self, location, parentScope, name, parent, members, isKnownNonPartial): assert isKnownNonPartial or not parent - assert isKnownNonPartial or len(members) == 0 + assert isKnownNonPartial or not members self.parent = None self._callback = False @@ -1011,13 +1070,13 @@ class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace): # legacyFactoryFunctions needs deterministic ordering because bindings code # outputs the constructs in the order that legacyFactoryFunctions enumerates # them. - self.legacyFactoryFunctions = list() + self.legacyFactoryFunctions = [] self.legacyWindowAliases = [] self.includedMixins = set() # self.interfacesBasedOnSelf is the set of interfaces that inherit from # self, including self itself. # Used for distinguishability checking. - self.interfacesBasedOnSelf = set([self]) + self.interfacesBasedOnSelf = {self} self._hasChildInterfaces = False self._isOnGlobalProtoChain = False @@ -1885,6 +1944,8 @@ class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace): class IDLInterface(IDLInterfaceOrNamespace): + __slots__ = ("classNameOverride",) + def __init__( self, location, @@ -2103,6 +2164,8 @@ class IDLInterface(IDLInterfaceOrNamespace): class IDLNamespace(IDLInterfaceOrNamespace): + __slots__ = () + def __init__(self, location, parentScope, name, members, isKnownNonPartial): IDLInterfaceOrNamespace.__init__( self, location, parentScope, name, None, members, isKnownNonPartial @@ -2161,6 +2224,16 @@ class IDLNamespace(IDLInterfaceOrNamespace): class IDLDictionary(IDLObjectWithScope): + __slots__ = ( + "parent", + "_finished", + "members", + "_partialDictionaries", + "_extendedAttrDict", + "needsConversionToJS", + "needsConversionFromJS", + ) + def __init__(self, location, parentScope, name, parent, members): assert isinstance(parentScope, IDLScope) assert isinstance(name, IDLUnresolvedIdentifier) @@ -2388,6 +2461,8 @@ class IDLDictionary(IDLObjectWithScope): class IDLEnum(IDLObjectWithIdentifier): + __slots__ = ("_values",) + def __init__(self, location, parentScope, name, values): assert isinstance(parentScope, IDLScope) assert isinstance(name, IDLUnresolvedIdentifier) @@ -2462,6 +2537,16 @@ class IDLType(IDLObject): "observablearray", ) + __slots__ = ( + "name", + "builtin", + "legacyNullToEmptyString", + "_clamp", + "_enforceRange", + "_allowShared", + "_extendedAttrDict", + ) + def __init__(self, location, name): IDLObject.__init__(self, location) self.name = name @@ -2662,6 +2747,8 @@ class IDLUnresolvedType(IDLType): Unresolved types are interface types """ + __slots__ = ("extraTypeAttributes",) + def __init__(self, location, name, attrs=[]): IDLType.__init__(self, location, name) self.extraTypeAttributes = attrs @@ -2702,6 +2789,8 @@ class IDLUnresolvedType(IDLType): class IDLParametrizedType(IDLType): + __slots__ = "builtin", "inner" + def __init__(self, location, name, innerType): IDLType.__init__(self, location, name) self.builtin = False @@ -2725,6 +2814,8 @@ class IDLParametrizedType(IDLType): class IDLNullableType(IDLParametrizedType): + __slots__ = () + def __init__(self, location, innerType): assert not innerType == BuiltinTypes[IDLBuiltinType.Types.any] @@ -2900,6 +2991,8 @@ class IDLNullableType(IDLParametrizedType): class IDLSequenceType(IDLParametrizedType): + __slots__ = ("name",) + def __init__(self, location, parameterType): assert not parameterType.isUndefined() @@ -2960,6 +3053,8 @@ class IDLSequenceType(IDLParametrizedType): class IDLRecordType(IDLParametrizedType): + __slots__ = "keyType", "name" + def __init__(self, location, keyType, valueType): assert keyType.isString() assert keyType.isComplete() @@ -3038,6 +3133,8 @@ class IDLRecordType(IDLParametrizedType): class IDLObservableArrayType(IDLParametrizedType): + __slots__ = () + def __init__(self, location, innerType): assert not innerType.isUndefined() IDLParametrizedType.__init__(self, location, None, innerType) @@ -3104,6 +3201,14 @@ class IDLObservableArrayType(IDLParametrizedType): class IDLUnionType(IDLType): + __slots__ = ( + "memberTypes", + "hasNullableType", + "_dictionaryType", + "flatMemberTypes", + "builtin", + ) + def __init__(self, location, memberTypes): IDLType.__init__(self, location, "") self.memberTypes = memberTypes @@ -3247,6 +3352,8 @@ class IDLUnionType(IDLType): class IDLTypedefType(IDLType): + __slots__ = "inner", "builtin" + def __init__(self, location, innerType, name): IDLType.__init__(self, location, name) self.inner = innerType @@ -3354,6 +3461,8 @@ class IDLTypedefType(IDLType): class IDLTypedef(IDLObjectWithIdentifier): + __slots__ = ("innerType",) + def __init__(self, location, parentScope, innerType, name): # Set self.innerType first, because IDLObjectWithIdentifier.__init__ # will call our __str__, which wants to use it. @@ -3386,6 +3495,8 @@ class IDLTypedef(IDLObjectWithIdentifier): class IDLWrapperType(IDLType): + __slots__ = "inner", "_identifier", "builtin" + def __init__(self, location, inner): IDLType.__init__(self, location, inner.identifier.name) self.inner = inner @@ -3582,6 +3693,8 @@ class IDLWrapperType(IDLType): class IDLPromiseType(IDLParametrizedType): + __slots__ = () + def __init__(self, location, innerType): IDLParametrizedType.__init__(self, location, "Promise", innerType) @@ -3745,6 +3858,14 @@ class IDLBuiltinType(IDLType): Types.Float64Array: "Float64Array", } + __slots__ = ( + "_typeTag", + "_clamped", + "_rangeEnforced", + "_withLegacyNullToEmptyString", + "_withAllowShared", + ) + def __init__( self, location, @@ -4242,6 +4363,11 @@ class NoCoercionFoundError(WebIDLError): class IDLValue(IDLObject): + __slots__ = ( + "type", + "value", + ) + def __init__(self, location, type, value): IDLObject.__init__(self, location) self.type = type @@ -4374,6 +4500,8 @@ class IDLValue(IDLObject): class IDLNullValue(IDLObject): + __slots__ = "type", "value" + def __init__(self, location): IDLObject.__init__(self, location) self.type = None @@ -4403,6 +4531,8 @@ class IDLNullValue(IDLObject): class IDLEmptySequenceValue(IDLObject): + __slots__ = "type", "value" + def __init__(self, location): IDLObject.__init__(self, location) self.type = None @@ -4433,6 +4563,8 @@ class IDLEmptySequenceValue(IDLObject): class IDLDefaultDictionaryValue(IDLObject): + __slots__ = "type", "value" + def __init__(self, location): IDLObject.__init__(self, location) self.type = None @@ -4463,6 +4595,8 @@ class IDLDefaultDictionaryValue(IDLObject): class IDLUndefinedValue(IDLObject): + __slots__ = "type", "value" + def __init__(self, location): IDLObject.__init__(self, location) self.type = None @@ -4492,6 +4626,7 @@ class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins): AffectsValues = ("Nothing", "Everything") DependsOnValues = ("Nothing", "DOMState", "DeviceState", "Everything") + # no slots : multiple inheritance def __init__(self, location, identifier, tag, extendedAttrDict=None): IDLObjectWithIdentifier.__init__(self, location, None, identifier) IDLExposureMixins.__init__(self, location) @@ -4611,6 +4746,14 @@ class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins): class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember): + __slots__ = ( + "keyType", + "valueType", + "maplikeOrSetlikeOrIterableType", + "disallowedMemberNames", + "disallowedNonMethodNames", + ) + def __init__(self, location, identifier, ifaceType, keyType, valueType, ifaceKind): IDLInterfaceMember.__init__(self, location, identifier, ifaceKind) if keyType is not None: @@ -4827,6 +4970,8 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember): # Iterable adds ES6 iterator style functions and traits # (keys/values/entries/@@iterator) to an interface. class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase): + __slots__ = ("iteratorType",) + def __init__(self, location, identifier, keyType, valueType, scope): IDLMaplikeOrSetlikeOrIterableBase.__init__( self, @@ -4902,6 +5047,8 @@ class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase): class IDLAsyncIterable(IDLMaplikeOrSetlikeOrIterableBase): + __slots__ = "iteratorType", "argList" + def __init__(self, location, identifier, keyType, valueType, argList, scope): for arg in argList: if not arg.optional: @@ -4986,6 +5133,8 @@ class IDLAsyncIterable(IDLMaplikeOrSetlikeOrIterableBase): # MaplikeOrSetlike adds ES6 map-or-set-like traits to an interface. class IDLMaplikeOrSetlike(IDLMaplikeOrSetlikeOrIterableBase): + __slots__ = "readonly", "slotIndices", "prefix" + def __init__( self, location, identifier, maplikeOrSetlikeType, readonly, keyType, valueType ): @@ -5153,6 +5302,8 @@ class IDLMaplikeOrSetlike(IDLMaplikeOrSetlikeOrIterableBase): class IDLConst(IDLInterfaceMember): + __slots__ = "type", "value" + def __init__(self, location, identifier, type, value): IDLInterfaceMember.__init__( self, location, identifier, IDLInterfaceMember.Tags.Const @@ -5225,6 +5376,21 @@ class IDLConst(IDLInterfaceMember): class IDLAttribute(IDLInterfaceMember): + __slots__ = ( + "type", + "readonly", + "inherit", + "_static", + "legacyLenientThis", + "_legacyUnforgeable", + "stringifier", + "slotIndices", + "maplikeOrSetlike", + "dependsOn", + "affects", + "bindingAliases", + ) + def __init__( self, location, @@ -5827,6 +5993,18 @@ class IDLAttribute(IDLInterfaceMember): class IDLArgument(IDLObjectWithIdentifier): + __slots__ = ( + "type", + "optional", + "defaultValue", + "variadic", + "dictionaryMember", + "_isComplete", + "_allowTreatNonCallableAsNull", + "_extendedAttrDict", + "allowTypeAttributes", + ) + def __init__( self, location, @@ -5970,6 +6148,15 @@ class IDLArgument(IDLObjectWithIdentifier): class IDLCallback(IDLObjectWithScope): + __slots__ = ( + "_returnType", + "_arguments", + "_treatNonCallableAsNull", + "_treatNonObjectAsNull", + "_isRunScriptBoundary", + "_isConstructor", + ) + def __init__( self, location, parentScope, identifier, returnType, arguments, isConstructor ): @@ -6067,6 +6254,8 @@ class IDLCallback(IDLObjectWithScope): class IDLCallbackType(IDLType): + __slots__ = ("callback",) + def __init__(self, location, callback): IDLType.__init__(self, location, callback.identifier.name) self.callback = callback @@ -6109,6 +6298,8 @@ class IDLMethodOverload: the full set of overloads. """ + __slots__ = "returnType", "arguments", "location" + def __init__(self, returnType, arguments, location): self.returnType = returnType # Clone the list of arguments, just in case @@ -6131,6 +6322,25 @@ class IDLMethod(IDLInterfaceMember, IDLScope): NamedOrIndexed = enum("Neither", "Named", "Indexed") + __slots__ = ( + "_hasOverloads", + "_overloads", + "_static", + "_getter", + "_setter", + "_deleter", + "_legacycaller", + "_stringifier", + "maplikeOrSetlikeOrIterable", + "_htmlConstructor", + "underlyingAttr", + "_specialType", + "_legacyUnforgeable", + "dependsOn", + "affects", + "aliases", + ) + def __init__( self, location, @@ -6797,6 +7007,14 @@ class IDLMethod(IDLInterfaceMember, IDLScope): class IDLConstructor(IDLMethod): + __slots__ = ( + "_initLocation", + "_initArgs", + "_initName", + "_inited", + "_initExtendedAttrs", + ) + def __init__(self, location, args, name): # We can't actually init our IDLMethod yet, because we do not know the # return type yet. Just save the info we have for now and we will init @@ -6866,6 +7084,8 @@ class IDLConstructor(IDLMethod): class IDLIncludesStatement(IDLObject): + __slots__ = ("interface", "mixin", "_finished") + def __init__(self, location, interface, mixin): IDLObject.__init__(self, location) self.interface = interface @@ -6922,6 +7142,8 @@ class IDLExtendedAttribute(IDLObject): A class to represent IDL extended attributes so we can give them locations """ + __slots__ = ("_tuple",) + def __init__(self, location, tuple): IDLObject.__init__(self, location) self._tuple = tuple diff --git a/dom/bindings/test/TestBindingHeader.h b/dom/bindings/test/TestBindingHeader.h index 77053d9ba6..2e8c496758 100644 --- a/dom/bindings/test/TestBindingHeader.h +++ b/dom/bindings/test/TestBindingHeader.h @@ -131,29 +131,6 @@ class TestInterface : public nsISupports, public nsWrapperCache { JS::Handle, const Optional>&, const Optional>&, ErrorResult&); - static already_AddRefed Test3(const GlobalObject&, - const LongOrStringAnyRecord&, - ErrorResult&); - - static already_AddRefed Test4( - const GlobalObject&, const Record>&, - ErrorResult&); - - static already_AddRefed Test5( - const GlobalObject&, - const Record< - nsString, - Sequence>>>>>&, - ErrorResult&); - - static already_AddRefed Test6( - const GlobalObject&, - const Sequence>>>>>&, - ErrorResult&); - // Integer types int8_t ReadonlyByte(); int8_t WritableByte(); @@ -1394,6 +1371,48 @@ class TestInterface : public nsISupports, public nsWrapperCache { void PassString(OwningNonNull&) = delete; }; +class TestLegacyFactoryFunctionInterface : public nsISupports, + public nsWrapperCache { + public: + NS_DECL_ISUPPORTS + + // We need a GetParentObject to make binding codegen happy + virtual nsISupports* GetParentObject(); + + // And now our actual WebIDL API + static already_AddRefed Test3( + const GlobalObject&, const LongOrStringAnyRecord&, ErrorResult&); + + static already_AddRefed Test4( + const GlobalObject&, const Record>&, + ErrorResult&); +}; + +class TestLegacyFactoryFunctionInterface2 : public nsISupports, + public nsWrapperCache { + public: + NS_DECL_ISUPPORTS + + // We need a GetParentObject to make binding codegen happy + virtual nsISupports* GetParentObject(); + + // And now our actual WebIDL API + static already_AddRefed Test5( + const GlobalObject&, + const Record< + nsString, + Sequence>>>>>&, + ErrorResult&); + + static already_AddRefed Test6( + const GlobalObject&, + const Sequence>>>>>&, + ErrorResult&); +}; + class TestIndexedGetterInterface : public nsISupports, public nsWrapperCache { public: NS_DECL_ISUPPORTS diff --git a/dom/bindings/test/TestCodeGen.webidl b/dom/bindings/test/TestCodeGen.webidl index 827d9c35a7..0d186cd682 100644 --- a/dom/bindings/test/TestCodeGen.webidl +++ b/dom/bindings/test/TestCodeGen.webidl @@ -162,10 +162,6 @@ enum OnlyForUseInInnerUnion { LegacyFactoryFunction=Test2(DictForConstructor dict, any any1, object obj1, object? obj2, sequence seq, optional any any2, optional object obj3, optional object? obj4), - LegacyFactoryFunction=Test3((long or record) arg1), - LegacyFactoryFunction=Test4(record> arg1), - LegacyFactoryFunction=Test5(record>>>>> arg1), - LegacyFactoryFunction=Test6(sequence>>>>> arg1), Exposed=Window] interface TestInterface { constructor(); @@ -1087,7 +1083,19 @@ interface TestInterface { undefined passUnionArrayBuffer((DOMString or ArrayBuffer) foo); undefined passUnionAllowSharedArrayBuffer((DOMString or [AllowShared] ArrayBuffer) foo); - // If you add things here, add them to TestExampleGen and TestJSImplGen as well + // If you add things here, add them to TestExampleGen as well +}; + +[LegacyFactoryFunction=Test3((long or record) arg1), + LegacyFactoryFunction=Test4(record> arg1), + Exposed=Window] +interface TestLegacyFactoryFunctionInterface { +}; + +[LegacyFactoryFunction=Test5(record>>>>> arg1), + LegacyFactoryFunction=Test6(sequence>>>>> arg1), + Exposed=Window] +interface TestLegacyFactoryFunctionInterface2 { }; [Exposed=Window] diff --git a/dom/bindings/test/TestExampleGen.webidl b/dom/bindings/test/TestExampleGen.webidl index 3d90e2bf9b..65e9840cec 100644 --- a/dom/bindings/test/TestExampleGen.webidl +++ b/dom/bindings/test/TestExampleGen.webidl @@ -870,7 +870,10 @@ interface TestExampleInterface { undefined passUnionArrayBuffer((DOMString or ArrayBuffer) foo); undefined passUnionAllowSharedArrayBuffer((DOMString or [AllowShared] ArrayBuffer) foo); - // If you add things here, add them to TestCodeGen and TestJSImplGen as well + // If you add things here, add them to TestExampleGen. If they need to be + // supported in JS-implemented WebIDL then you need to add them to + // TestJSImplGen as well, if they are not supported in JS-implemented WebIDL + // then the codegen should throw for that specific case. }; [Exposed=Window] diff --git a/dom/bindings/test/test_dom_xrays.html b/dom/bindings/test/test_dom_xrays.html index 6d65ab7315..eafa0c632c 100644 --- a/dom/bindings/test/test_dom_xrays.html +++ b/dom/bindings/test/test_dom_xrays.html @@ -176,6 +176,27 @@ function test() { // ECMAScript-defined properties live on the prototype, overriding any named properties. checkXrayProperty(coll, "toString", [ undefined, undefined, win.Object.prototype.toString ]); + // Constructors + img = new win.Image(); + ok(win.HTMLImageElement.isInstance(img), "Constructor created the right type of object"); + + let threw; + try { + threw = false; + win.Image(); + } catch (e) { + threw = true; + } + ok(threw, "Constructors should throw when called without new"); + + try { + threw = false; + new win.Node(); + } catch (e) { + threw = true; + } + ok(threw, "Constructing an interface without a constructor should throw"); + // Frozen arrays should come from our compartment, not the target one. var languages1 = win.navigator.languages; isnot(languages1, undefined, "Must have .languages"); @@ -358,7 +379,7 @@ function test() { // legacyCaller should work. ok(win.HTMLAllCollection.isInstance(doc.all), "HTMLDocument.all should be an instance of HTMLAllCollection"); - let element, threw; + let element; try { threw = false; element = doc.all(0); -- cgit v1.2.3