diff --git a/dom/base/WindowNamedPropertiesHandler.cpp b/dom/base/WindowNamedPropertiesHandler.cpp index ebb179500268..bb7522ad7169 100644 --- a/dom/base/WindowNamedPropertiesHandler.cpp +++ b/dom/base/WindowNamedPropertiesHandler.cpp @@ -281,6 +281,7 @@ static const DOMIfaceAndProtoJSClass WindowNamedPropertiesClass = { PROXY_CLASS_DEF("WindowProperties", JSCLASS_IS_DOMIFACEANDPROTOJSCLASS), eNamedPropertiesObject, + false, prototypes::id::_ID_Count, 0, sWindowNamedPropertiesNativePropertyHooks, diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index 65672efc9fbd..c912afa83f74 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -744,6 +744,17 @@ CreateInterfaceObject(JSContext* cx, JS::Handle global, return nullptr; } + if (DOMIfaceAndProtoJSClass::FromJSClass(constructorClass)->wantsInterfaceHasInstance) { + JS::Rooted hasInstanceId(cx, + SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::hasInstance))); + if (!JS_DefineFunctionById(cx, constructor, hasInstanceId, + InterfaceHasInstance, 1, + // Flags match those of Function[Symbol.hasInstance] + JSPROP_READONLY | JSPROP_PERMANENT)) { + return nullptr; + } + } + if (properties) { if (properties->HasStaticMethods() && !DefinePrefable(cx, constructor, properties->StaticMethods())) { @@ -1232,7 +1243,15 @@ static JSObject* XrayCreateFunction(JSContext* cx, JS::Handle wrapper, JSNativeWrapper native, unsigned nargs, JS::Handle id) { - JSFunction* fun = js::NewFunctionByIdWithReserved(cx, native.op, nargs, 0, id); + JSFunction* fun; + if (JSID_IS_STRING(id)) { + fun = js::NewFunctionByIdWithReserved(cx, native.op, nargs, 0, id); + } else { + // Can't pass this id (probably a symbol) to NewFunctionByIdWithReserved; + // just use an empty name for lack of anything better. + fun = js::NewFunctionWithReserved(cx, native.op, nargs, 0, nullptr); + } + if (!fun) { return nullptr; } @@ -1651,6 +1670,26 @@ XrayResolveOwnProperty(JSContext* cx, JS::Handle wrapper, JSPROP_PERMANENT | JSPROP_READONLY, desc, cacheOnHolder); } + + if (id == SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::hasInstance)) && + DOMIfaceAndProtoJSClass::FromJSClass(js::GetObjectClass(obj))-> + wantsInterfaceHasInstance) { + cacheOnHolder = true; + JSNativeWrapper interfaceHasInstanceWrapper = { InterfaceHasInstance, + nullptr }; + JSObject* funObj = XrayCreateFunction(cx, wrapper, + interfaceHasInstanceWrapper, 1, id); + if (!funObj) { + return false; + } + + desc.value().setObject(*funObj); + desc.setAttributes(JSPROP_READONLY | JSPROP_PERMANENT); + desc.object().set(wrapper); + desc.setSetter(nullptr); + desc.setGetter(nullptr); + return true; + } } else { MOZ_ASSERT(IsInterfacePrototype(type)); @@ -1875,7 +1914,7 @@ const js::ClassOps sBoringInterfaceObjectClassClassOps = { nullptr, /* mayResolve */ nullptr, /* finalize */ ThrowingConstructor, /* call */ - InterfaceHasInstance, /* hasInstance */ + nullptr, /* hasInstance */ ThrowingConstructor, /* construct */ nullptr, /* trace */ }; @@ -2215,24 +2254,69 @@ GlobalObject::GetAsSupports() const return nullptr; } -bool -InterfaceHasInstance(JSContext* cx, JS::Handle obj, - JS::Handle instance, - bool* bp) +static bool +CallOrdinaryHasInstance(JSContext* cx, JS::CallArgs& args) { - const DOMIfaceAndProtoJSClass* clasp = - DOMIfaceAndProtoJSClass::FromJSClass(js::GetObjectClass(obj)); + JS::Rooted thisObj(cx, &args.thisv().toObject()); + bool isInstance; + if (!JS::OrdinaryHasInstance(cx, thisObj, args.get(0), &isInstance)) { + return false; + } + args.rval().setBoolean(isInstance); + return true; +} +bool +InterfaceHasInstance(JSContext* cx, unsigned argc, JS::Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + // If the thing we were passed is not an object, return false like + // OrdinaryHasInstance does. + if (!args.get(0).isObject()) { + args.rval().setBoolean(false); + return true; + } + + // If "this" is not an object, likewise return false (again, like + // OrdinaryHasInstance). + if (!args.thisv().isObject()) { + args.rval().setBoolean(false); + return true; + } + + // If "this" doesn't have a DOMIfaceAndProtoJSClass, it's not a DOM + // constructor, so just fall back to OrdinaryHasInstance. But note that we + // should CheckedUnwrap here, because otherwise we won't get the right + // answers. + JS::Rooted thisObj(cx, js::CheckedUnwrap(&args.thisv().toObject())); + if (!thisObj) { + // Just fall back on the normal thing, in case it still happens to work. + return CallOrdinaryHasInstance(cx, args); + } + + const js::Class* thisClass = js::GetObjectClass(thisObj); + + if (!IsDOMIfaceAndProtoClass(thisClass)) { + return CallOrdinaryHasInstance(cx, args); + } + + const DOMIfaceAndProtoJSClass* clasp = + DOMIfaceAndProtoJSClass::FromJSClass(thisClass); + + // If "this" isn't a DOM constructor or is a constructor for an interface + // without a prototype, just fall back to OrdinaryHasInstance. + if (clasp->mType != eInterface || + clasp->mPrototypeID == prototypes::id::_ID_Count) { + return CallOrdinaryHasInstance(cx, args); + } + + JS::Rooted instance(cx, &args[0].toObject()); const DOMJSClass* domClass = GetDOMClass(js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false)); - MOZ_ASSERT(!domClass || clasp->mPrototypeID != prototypes::id::_ID_Count, - "Why do we have a hasInstance hook if we don't have a prototype " - "ID?"); - if (domClass && domClass->mInterfaceChain[clasp->mDepth] == clasp->mPrototypeID) { - *bp = true; + args.rval().setBoolean(true); return true; } @@ -2242,49 +2326,11 @@ InterfaceHasInstance(JSContext* cx, JS::Handle obj, clasp->mDepth, &boolp)) { return false; } - *bp = boolp; + args.rval().setBoolean(boolp); return true; } - JS::Rooted protov(cx); - DebugOnly ok = JS_GetProperty(cx, obj, "prototype", &protov); - MOZ_ASSERT(ok, "Someone messed with our prototype property?"); - - JS::Rooted interfacePrototype(cx, &protov.toObject()); - MOZ_ASSERT(IsDOMIfaceAndProtoClass(js::GetObjectClass(interfacePrototype)), - "Someone messed with our prototype property?"); - - JS::Rooted proto(cx); - if (!JS_GetPrototype(cx, instance, &proto)) { - return false; - } - - while (proto) { - if (proto == interfacePrototype) { - *bp = true; - return true; - } - - if (!JS_GetPrototype(cx, proto, &proto)) { - return false; - } - } - - *bp = false; - return true; -} - -bool -InterfaceHasInstance(JSContext* cx, JS::Handle obj, JS::MutableHandle vp, - bool* bp) -{ - if (!vp.isObject()) { - *bp = false; - return true; - } - - JS::Rooted instanceObject(cx, &vp.toObject()); - return InterfaceHasInstance(cx, obj, instanceObject, bp); + return CallOrdinaryHasInstance(cx, args); } bool diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 49edb3b290f5..f405284ecf12 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -2677,17 +2677,11 @@ nsresult ReparentWrapper(JSContext* aCx, JS::Handle aObj); /** - * Used to implement the hasInstance hook of an interface object. - * - * instance should not be a security wrapper. + * Used to implement the Symbol.hasInstance property of an interface object. */ bool -InterfaceHasInstance(JSContext* cx, JS::Handle obj, - JS::Handle instance, - bool* bp); -bool -InterfaceHasInstance(JSContext* cx, JS::Handle obj, JS::MutableHandle vp, - bool* bp); +InterfaceHasInstance(JSContext* cx, unsigned argc, JS::Value* vp); + bool InterfaceHasInstance(JSContext* cx, int prototypeID, int depth, JS::Handle instance, diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 7834918f13e5..942a4b811f7f 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -602,6 +602,7 @@ class CGPrototypeJSClass(CGThing): JS_NULL_OBJECT_OPS }, ${type}, + false, ${prototypeID}, ${depth}, ${hooks}, @@ -673,12 +674,10 @@ class CGInterfaceObjectJSClass(CGThing): ctorname = "nullptr" else: ctorname = "ThrowingConstructor" - if NeedsGeneratedHasInstance(self.descriptor): - hasinstance = HASINSTANCE_HOOK_NAME - elif self.descriptor.interface.hasInterfacePrototypeObject(): - hasinstance = "InterfaceHasInstance" - else: - hasinstance = "nullptr" + needsHasInstance = ( + not NeedsGeneratedHasInstance(self.descriptor) and + self.descriptor.interface.hasInterfacePrototypeObject()) + prototypeID, depth = PrototypeIDAndDepth(self.descriptor) slotCount = "DOM_INTERFACE_SLOTS_BASE" if len(self.descriptor.interface.namedConstructors) > 0: @@ -687,10 +686,10 @@ class CGInterfaceObjectJSClass(CGThing): (protoGetter, _) = InterfaceObjectProtoGetter(self.descriptor, forXrays=True) - if ctorname == "ThrowingConstructor" and hasinstance == "InterfaceHasInstance": + if ctorname == "ThrowingConstructor": ret = "" classOpsPtr = "&sBoringInterfaceObjectClassClassOps" - elif ctorname == "nullptr" and hasinstance == "nullptr": + elif ctorname == "nullptr": ret = "" classOpsPtr = "JS_NULL_CLASS_OPS" else: @@ -706,14 +705,13 @@ class CGInterfaceObjectJSClass(CGThing): nullptr, /* mayResolve */ nullptr, /* finalize */ ${ctorname}, /* call */ - ${hasInstance}, /* hasInstance */ + nullptr, /* hasInstance */ ${ctorname}, /* construct */ nullptr, /* trace */ }; """, - ctorname=ctorname, - hasInstance=hasinstance) + ctorname=ctorname) classOpsPtr = "&sInterfaceObjectClassOps" if self.descriptor.interface.isNamespace(): @@ -744,6 +742,7 @@ class CGInterfaceObjectJSClass(CGThing): ${objectOps} }, eInterface, + ${needsHasInstance}, ${prototypeID}, ${depth}, ${hooks}, @@ -753,11 +752,10 @@ class CGInterfaceObjectJSClass(CGThing): """, classString=classString, slotCount=slotCount, - ctorname=ctorname, - hasInstance=hasinstance, classOpsPtr=classOpsPtr, hooks=NativePropertyHooks(self.descriptor), objectOps=objectOps, + needsHasInstance=toStringBool(needsHasInstance), prototypeID=prototypeID, depth=depth, toStringResult=toStringResult, @@ -1765,19 +1763,17 @@ class CGNamedConstructors(CGThing): namedConstructors=namedConstructors) -class CGClassHasInstanceHook(CGAbstractStaticMethod): +class CGHasInstanceHook(CGAbstractStaticMethod): def __init__(self, descriptor): args = [Argument('JSContext*', 'cx'), - Argument('JS::Handle', 'obj'), - Argument('JS::MutableHandle', 'vp'), - Argument('bool*', 'bp')] + Argument('unsigned', 'argc'), + Argument('JS::Value*', 'vp')] assert descriptor.interface.hasInterfaceObject() + assert NeedsGeneratedHasInstance(descriptor) CGAbstractStaticMethod.__init__(self, descriptor, HASINSTANCE_HOOK_NAME, 'bool', args) def define(self): - if not NeedsGeneratedHasInstance(self.descriptor): - return "" return CGAbstractStaticMethod.define(self) def definition_body(self): @@ -1785,12 +1781,13 @@ class CGClassHasInstanceHook(CGAbstractStaticMethod): def generate_code(self): header = dedent(""" - if (!vp.isObject()) { - *bp = false; + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (!args.get(0).isObject()) { + args.rval().setBoolean(false); return true; } - JS::Rooted instance(cx, &vp.toObject()); + JS::Rooted instance(cx, &args[0].toObject()); """) if self.descriptor.interface.hasInterfacePrototypeObject(): return ( @@ -1801,8 +1798,8 @@ class CGClassHasInstanceHook(CGAbstractStaticMethod): static_assert(IsBaseOf::value, "HasInstance only works for nsISupports-based classes."); - bool ok = InterfaceHasInstance(cx, obj, instance, bp); - if (!ok || *bp) { + bool ok = InterfaceHasInstance(cx, argc, vp); + if (!ok || args.rval().toBoolean()) { return ok; } @@ -1811,7 +1808,7 @@ class CGClassHasInstanceHook(CGAbstractStaticMethod): nsContentUtils::XPConnect()->GetNativeOfWrapper(cx, js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false)); nsCOMPtr qiResult = do_QueryInterface(native); - *bp = !!qiResult; + args.rval().setBoolean(!!qiResult); return true; """, nativeType=self.descriptor.nativeType, @@ -1820,16 +1817,16 @@ class CGClassHasInstanceHook(CGAbstractStaticMethod): hasInstanceCode = dedent(""" const DOMJSClass* domClass = GetDOMClass(js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false)); - *bp = false; if (!domClass) { // Not a DOM object, so certainly not an instance of this interface + args.rval().setBoolean(false); return true; } """) if self.descriptor.interface.identifier.name == "ChromeWindow": - setBp = "*bp = UnwrapDOMObject(js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false))->IsChromeWindow()" + setRval = "args.rval().setBoolean(UnwrapDOMObject(js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false))->IsChromeWindow())" else: - setBp = "*bp = true" + setRval = "args.rval().setBoolean(true)" # Sort interaces implementing self by name so we get stable output. for iface in sorted(self.descriptor.interface.interfacesImplementingSelf, key=lambda iface: iface.identifier.name): @@ -1837,13 +1834,14 @@ class CGClassHasInstanceHook(CGAbstractStaticMethod): """ if (domClass->mInterfaceChain[PrototypeTraits::Depth] == prototypes::id::${name}) { - ${setBp}; + ${setRval}; return true; } """, name=iface.identifier.name, - setBp=setBp) - hasInstanceCode += "return true;\n" + setRval=setRval) + hasInstanceCode += ("args.rval().setBoolean(false);\n" + "return true;\n") return header + hasInstanceCode @@ -2226,6 +2224,20 @@ class MethodDefiner(PropertyDefiner): "condition": MemberCondition() }) + if (static and + not unforgeable and + descriptor.interface.hasInterfaceObject() and + NeedsGeneratedHasInstance(descriptor)): + self.regular.append({ + "name": "@@hasInstance", + "methodInfo": False, + "nativeName": HASINSTANCE_HOOK_NAME, + "length": 1, + # Flags match those of Function[Symbol.hasInstance] + "flags": "JSPROP_READONLY | JSPROP_PERMANENT", + "condition": MemberCondition() + }) + # Generate the keys/values/entries aliases for value iterables. maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable if (not static and @@ -11954,6 +11966,12 @@ class CGDescriptor(CGThing): for m in clearableCachedAttrs(descriptor): cgThings.append(CGJSImplClearCachedValueMethod(descriptor, m)) + # Need to output our generated hasinstance bits before + # PropertyArrays tries to use them. + if (descriptor.interface.hasInterfaceObject() and + NeedsGeneratedHasInstance(descriptor)): + cgThings.append(CGHasInstanceHook(descriptor)) + properties = PropertyArrays(descriptor) cgThings.append(CGGeneric(define=str(properties))) cgThings.append(CGNativeProperties(descriptor, properties)) @@ -11974,7 +11992,6 @@ class CGDescriptor(CGThing): if descriptor.interface.hasInterfaceObject(): cgThings.append(CGClassConstructor(descriptor, descriptor.interface.ctor())) - cgThings.append(CGClassHasInstanceHook(descriptor)) cgThings.append(CGInterfaceObjectJSClass(descriptor, properties)) cgThings.append(CGNamedConstructors(descriptor)) diff --git a/dom/bindings/DOMJSClass.h b/dom/bindings/DOMJSClass.h index cf8922d16b84..48ba957cc629 100644 --- a/dom/bindings/DOMJSClass.h +++ b/dom/bindings/DOMJSClass.h @@ -386,9 +386,14 @@ struct DOMIfaceAndProtoJSClass // Either eInterface, eInterfacePrototype, eGlobalInterfacePrototype or // eNamedPropertiesObject. - DOMObjectType mType; + DOMObjectType mType; // uint8_t - const prototypes::ID mPrototypeID; + // Boolean indicating whether this object wants a @@hasInstance property + // pointing to InterfaceHasInstance defined on it. Only ever true for the + // eInterface case. + bool wantsInterfaceHasInstance; + + const prototypes::ID mPrototypeID; // uint16_t const uint32_t mDepth; const NativePropertyHooks* mNativeHooks; diff --git a/dom/promise/tests/file_promise_xrays.html b/dom/promise/tests/file_promise_xrays.html index edc3875bbb3c..73f9bf7d73ab 100644 --- a/dom/promise/tests/file_promise_xrays.html +++ b/dom/promise/tests/file_promise_xrays.html @@ -14,8 +14,10 @@ propNames = Object.getOwnPropertyNames(obj); propNames = propNames.concat(Object.getOwnPropertySymbols(obj)); for (var propName of propNames) { - if (propName == "prototype" && obj == Promise) { - // It's not configurable + if ((propName == "prototype" || + propName == Symbol.hasInstance) && + obj == Promise) { + // They're not configurable. continue; } Object.defineProperty(obj, propName,