Bug 1882127 - Use plain JS functions for WebIDL interface objects. r=saschanaz,devtools-reviewers,nchevobbe

Differential Revision: https://phabricator.services.mozilla.com/D202824
This commit is contained in:
Peter Van der Beken 2024-03-22 11:32:17 +00:00
parent 37b3f13f6a
commit 891eaf1ede
9 changed files with 227 additions and 275 deletions

View file

@ -310,7 +310,7 @@ add_task(async function () {
EventUtils.synthesizeKey("KEY_ArrowDown"); EventUtils.synthesizeKey("KEY_ArrowDown");
// Navigates to the XMLDocument item in the popup // Navigates to the XMLDocument item in the popup
await waitForEagerEvaluationResult(hud, `function ()`); await waitForEagerEvaluationResult(hud, `function XMLDocument()`);
onPopupClose = popup.once("popup-closed"); onPopupClose = popup.once("popup-closed");
EventUtils.sendString(" "); EventUtils.sendString(" ");

View file

@ -758,18 +758,9 @@ bool DefineLegacyUnforgeableAttributes(
return DefinePrefable(cx, obj, props); return DefinePrefable(cx, obj, props);
} }
// We should use JSFunction objects for interface objects, but we need a custom bool InterfaceObjectJSNative(JSContext* cx, unsigned argc, JS::Value* vp) {
// hasInstance hook because we have new interface objects on prototype chains of JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
// old (XPConnect-based) bindings. We also need Xrays and arbitrary numbers of return NativeHolderFromInterfaceObject(&args.callee())->mNative(cx, argc, vp);
// reserved slots (e.g. for legacy factory functions). So we define a custom
// funToString ObjectOps member for interface objects.
JSString* InterfaceObjectToString(JSContext* aCx, JS::Handle<JSObject*> aObject,
bool /* isToSource */) {
const JSClass* clasp = JS::GetClass(aObject);
MOZ_ASSERT(IsDOMIfaceAndProtoClass(clasp));
const DOMIfaceJSClass* ifaceJSClass = DOMIfaceJSClass::FromJSClass(clasp);
return JS_NewStringCopyZ(aCx, ifaceJSClass->mFunToString);
} }
bool LegacyFactoryFunctionJSNative(JSContext* cx, unsigned argc, bool LegacyFactoryFunctionJSNative(JSContext* cx, unsigned argc,
@ -843,11 +834,11 @@ static bool InterfaceIsInstance(JSContext* cx, unsigned argc, JS::Value* vp) {
return true; return true;
} }
// If "this" doesn't have a DOMIfaceAndProtoJSClass, it's not a DOM // If "this" is not an interface object, likewise return false (again, like
// constructor, so just fall back. But note that we should // OrdinaryHasInstance). But note that we should CheckedUnwrapStatic here,
// CheckedUnwrapStatic here, because otherwise we won't get the right answers. // because otherwise we won't get the right answers.
// The static version is OK, because we're looking for DOM constructors, which // The static version is OK, because we're looking for interface objects,
// are not cross-origin objects. // which are not cross-origin objects.
JS::Rooted<JSObject*> thisObj( JS::Rooted<JSObject*> thisObj(
cx, js::CheckedUnwrapStatic(&args.thisv().toObject())); cx, js::CheckedUnwrapStatic(&args.thisv().toObject()));
if (!thisObj) { if (!thisObj) {
@ -856,20 +847,16 @@ static bool InterfaceIsInstance(JSContext* cx, unsigned argc, JS::Value* vp) {
return true; return true;
} }
const JSClass* thisClass = JS::GetClass(thisObj); if (!IsInterfaceObject(thisObj)) {
if (!IsDOMIfaceAndProtoClass(thisClass)) {
args.rval().setBoolean(false); args.rval().setBoolean(false);
return true; return true;
} }
const DOMIfaceAndProtoJSClass* clasp = const DOMInterfaceInfo* interfaceInfo = InterfaceInfoFromObject(thisObj);
DOMIfaceAndProtoJSClass::FromJSClass(thisClass);
// If "this" isn't a DOM constructor or is a constructor for an interface // If "this" is a constructor for an interface without a prototype, just fall
// without a prototype, just fall back. // back.
if (clasp->mType != eInterface || if (interfaceInfo->mPrototypeID == prototypes::id::_ID_Count) {
clasp->mPrototypeID == prototypes::id::_ID_Count) {
args.rval().setBoolean(false); args.rval().setBoolean(false);
return true; return true;
} }
@ -878,13 +865,13 @@ static bool InterfaceIsInstance(JSContext* cx, unsigned argc, JS::Value* vp) {
const DOMJSClass* domClass = GetDOMClass( const DOMJSClass* domClass = GetDOMClass(
js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false)); js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false));
if (domClass && if (domClass && domClass->mInterfaceChain[interfaceInfo->mDepth] ==
domClass->mInterfaceChain[clasp->mDepth] == clasp->mPrototypeID) { interfaceInfo->mPrototypeID) {
args.rval().setBoolean(true); args.rval().setBoolean(true);
return true; return true;
} }
if (IsRemoteObjectProxy(instance, clasp->mPrototypeID)) { if (IsRemoteObjectProxy(instance, interfaceInfo->mPrototypeID)) {
args.rval().setBoolean(true); args.rval().setBoolean(true);
return true; return true;
} }
@ -937,32 +924,35 @@ bool InitInterfaceOrNamespaceObject(
// name must be an atom (or JS::PropertyKey::NonIntAtom will assert). // name must be an atom (or JS::PropertyKey::NonIntAtom will assert).
static JSObject* CreateInterfaceObject( static JSObject* CreateInterfaceObject(
JSContext* cx, JS::Handle<JSObject*> global, JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> constructorProto, JS::Handle<JSObject*> interfaceProto, const DOMInterfaceInfo* interfaceInfo,
const DOMIfaceJSClass* constructorClass, unsigned ctorNargs, unsigned ctorNargs,
const Span<const LegacyFactoryFunction>& legacyFactoryFunctions, const Span<const LegacyFactoryFunction>& legacyFactoryFunctions,
JS::Handle<JSObject*> proto, const NativeProperties* properties, JS::Handle<JSObject*> proto, const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties, JS::Handle<JSString*> name, const NativeProperties* chromeOnlyProperties, JS::Handle<JSString*> name,
bool isChrome, bool defineOnGlobal, bool isChrome, bool defineOnGlobal,
const char* const* legacyWindowAliases) { const char* const* legacyWindowAliases) {
MOZ_ASSERT(interfaceProto);
MOZ_ASSERT(interfaceInfo);
JS::Rooted<jsid> nameId(cx, JS::PropertyKey::NonIntAtom(name));
JS::Rooted<JSObject*> constructor(cx); JS::Rooted<JSObject*> constructor(cx);
MOZ_ASSERT(constructorProto); {
MOZ_ASSERT(constructorClass); JSFunction* fun = js::NewFunctionByIdWithReservedAndProto(
constructor = JS_NewObjectWithGivenProto(cx, constructorClass->ToJSClass(), cx, InterfaceObjectJSNative, interfaceProto, ctorNargs,
constructorProto); JSFUN_CONSTRUCTOR, nameId);
if (!constructor) { if (!fun) {
return nullptr; return nullptr;
} }
if (!JS_DefineProperty(cx, constructor, "length", ctorNargs, constructor = JS_GetFunctionObject(fun);
JSPROP_READONLY)) {
return nullptr;
} }
if (!JS_DefineProperty(cx, constructor, "name", name, JSPROP_READONLY)) { js::SetFunctionNativeReserved(
return nullptr; constructor, INTERFACE_OBJECT_INFO_RESERVED_SLOT,
} JS::PrivateValue(const_cast<DOMInterfaceInfo*>(interfaceInfo)));
if (constructorClass->wantsInterfaceIsInstance && isChrome && if (interfaceInfo->wantsInterfaceIsInstance && isChrome &&
!JS_DefineFunction(cx, constructor, "isInstance", InterfaceIsInstance, 1, !JS_DefineFunction(cx, constructor, "isInstance", InterfaceIsInstance, 1,
// Don't bother making it enumerable // Don't bother making it enumerable
0)) { 0)) {
@ -978,7 +968,6 @@ static JSObject* CreateInterfaceObject(
return nullptr; return nullptr;
} }
JS::Rooted<jsid> nameId(cx, JS::PropertyKey::NonIntAtom(name));
if (defineOnGlobal && !DefineConstructor(cx, global, nameId, constructor)) { if (defineOnGlobal && !DefineConstructor(cx, global, nameId, constructor)) {
return nullptr; return nullptr;
} }
@ -1010,7 +999,7 @@ static JSObject* CreateInterfaceObject(
!DefineConstructor(cx, global, nameId, legacyFactoryFunction))) { !DefineConstructor(cx, global, nameId, legacyFactoryFunction))) {
return nullptr; return nullptr;
} }
JS::SetReservedSlot(constructor, legacyFactoryFunctionSlot, js::SetFunctionNativeReserved(constructor, legacyFactoryFunctionSlot,
JS::ObjectValue(*legacyFactoryFunction)); JS::ObjectValue(*legacyFactoryFunction));
++legacyFactoryFunctionSlot; ++legacyFactoryFunctionSlot;
} }
@ -1110,15 +1099,15 @@ namespace binding_detail {
void CreateInterfaceObjects( void CreateInterfaceObjects(
JSContext* cx, JS::Handle<JSObject*> global, JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> protoProto, const DOMIfaceAndProtoJSClass* protoClass, JS::Handle<JSObject*> protoProto, const DOMIfaceAndProtoJSClass* protoClass,
JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> constructorProto, JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> interfaceProto,
const DOMIfaceJSClass* constructorClass, unsigned ctorNargs, const DOMInterfaceInfo* interfaceInfo, unsigned ctorNargs,
bool isConstructorChromeOnly, bool isConstructorChromeOnly,
const Span<const LegacyFactoryFunction>& legacyFactoryFunctions, const Span<const LegacyFactoryFunction>& legacyFactoryFunctions,
JS::Heap<JSObject*>* constructorCache, const NativeProperties* properties, JS::Heap<JSObject*>* constructorCache, const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties, const char* name, const NativeProperties* chromeOnlyProperties, const char* name,
bool defineOnGlobal, const char* const* unscopableNames, bool isGlobal, bool defineOnGlobal, const char* const* unscopableNames, bool isGlobal,
const char* const* legacyWindowAliases) { const char* const* legacyWindowAliases) {
MOZ_ASSERT(protoClass || constructorClass, "Need at least one class!"); MOZ_ASSERT(protoClass || interfaceInfo, "Need at least a class or info!");
MOZ_ASSERT( MOZ_ASSERT(
!((properties && !((properties &&
(properties->HasMethods() || properties->HasAttributes())) || (properties->HasMethods() || properties->HasAttributes())) ||
@ -1131,16 +1120,16 @@ void CreateInterfaceObjects(
(chromeOnlyProperties && (chromeOnlyProperties &&
(chromeOnlyProperties->HasStaticMethods() || (chromeOnlyProperties->HasStaticMethods() ||
chromeOnlyProperties->HasStaticAttributes()))) || chromeOnlyProperties->HasStaticAttributes()))) ||
constructorClass, interfaceInfo,
"Static methods but no constructorClass!"); "Static methods but no info!");
MOZ_ASSERT(!protoClass == !protoCache, MOZ_ASSERT(!protoClass == !protoCache,
"If, and only if, there is an interface prototype object we need " "If, and only if, there is an interface prototype object we need "
"to cache it"); "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 " "If, and only if, there is an interface object we need to cache "
"it"); "it");
MOZ_ASSERT(constructorProto || !constructorClass, MOZ_ASSERT(interfaceProto || !interfaceInfo,
"Must have a constructor proto if we plan to create a constructor " "Must have a interface proto if we plan to create an interface "
"object"); "object");
bool isChrome = nsContentUtils::ThreadsafeIsSystemCaller(cx); bool isChrome = nsContentUtils::ThreadsafeIsSystemCaller(cx);
@ -1166,9 +1155,9 @@ void CreateInterfaceObjects(
} }
JSObject* interface; JSObject* interface;
if (constructorClass) { if (interfaceInfo) {
interface = CreateInterfaceObject( interface = CreateInterfaceObject(
cx, global, constructorProto, constructorClass, cx, global, interfaceProto, interfaceInfo,
(isChrome || !isConstructorChromeOnly) ? ctorNargs : 0, (isChrome || !isConstructorChromeOnly) ? ctorNargs : 0,
legacyFactoryFunctions, proto, properties, chromeOnlyProperties, legacyFactoryFunctions, proto, properties, chromeOnlyProperties,
nameStr, isChrome, defineOnGlobal, legacyWindowAliases); nameStr, isChrome, defineOnGlobal, legacyWindowAliases);
@ -1508,7 +1497,7 @@ bool ThrowConstructorWithoutNew(JSContext* cx, const char* name) {
inline const NativePropertyHooks* GetNativePropertyHooksFromJSNative( inline const NativePropertyHooks* GetNativePropertyHooksFromJSNative(
JS::Handle<JSObject*> obj) { JS::Handle<JSObject*> obj) {
return NativeHolderFromLegacyFactoryFunction(obj)->mPropertyHooks; return NativeHolderFromObject(obj)->mPropertyHooks;
} }
inline const NativePropertyHooks* GetNativePropertyHooks( inline const NativePropertyHooks* GetNativePropertyHooks(
@ -1895,10 +1884,8 @@ static bool ResolvePrototypeOrConstructor(
} }
if (id.get() == GetJSIDByIndex(cx, XPCJSContext::IDX_ISINSTANCE)) { if (id.get() == GetJSIDByIndex(cx, XPCJSContext::IDX_ISINSTANCE)) {
const JSClass* objClass = JS::GetClass(obj); if (IsInterfaceObject(obj) &&
if (IsDOMIfaceAndProtoClass(objClass)) { InterfaceInfoFromObject(obj)->wantsInterfaceIsInstance) {
const DOMIfaceJSClass* clazz = DOMIfaceJSClass::FromJSClass(objClass);
if (clazz->wantsInterfaceIsInstance) {
cacheOnHolder = true; cacheOnHolder = true;
JSObject* funObj = XrayCreateFunction( JSObject* funObj = XrayCreateFunction(
@ -1913,7 +1900,6 @@ static bool ResolvePrototypeOrConstructor(
return true; return true;
} }
} }
}
} else if (type == eNamespace) { } else if (type == eNamespace) {
if (id.isWellKnownSymbol(JS::SymbolCode::toStringTag)) { if (id.isWellKnownSymbol(JS::SymbolCode::toStringTag)) {
JS::Rooted<JSString*> nameStr( JS::Rooted<JSString*> nameStr(
@ -2227,31 +2213,6 @@ NativePropertyHooks sEmptyNativePropertyHooks = {
constructors::id::_ID_Count, constructors::id::_ID_Count,
nullptr}; nullptr};
const JSClassOps sBoringInterfaceObjectClassClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* enumerate */
nullptr, /* newEnumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
nullptr, /* finalize */
ThrowingConstructor, /* call */
ThrowingConstructor, /* construct */
nullptr, /* trace */
};
const js::ObjectOps sInterfaceObjectClassObjectOps = {
nullptr, /* lookupProperty */
nullptr, /* defineProperty */
nullptr, /* hasProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* getOwnPropertyDescriptor */
nullptr, /* deleteProperty */
nullptr, /* getElements */
InterfaceObjectToString, /* funToString */
};
bool GetPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy, bool GetPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::Handle<JS::Value> receiver, JS::Handle<jsid> id, JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
bool* found, JS::MutableHandle<JS::Value> vp) { bool* found, JS::MutableHandle<JS::Value> vp) {
@ -3612,14 +3573,7 @@ bool ForEachHandler(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
static inline prototypes::ID GetProtoIdForNewtarget( static inline prototypes::ID GetProtoIdForNewtarget(
JS::Handle<JSObject*> aNewTarget) { JS::Handle<JSObject*> aNewTarget) {
const JSClass* newTargetClass = JS::GetClass(aNewTarget); if (IsDOMConstructor(aNewTarget)) {
if (IsDOMIfaceAndProtoClass(newTargetClass)) {
const DOMIfaceAndProtoJSClass* newTargetIfaceClass =
DOMIfaceAndProtoJSClass::FromJSClass(newTargetClass);
if (newTargetIfaceClass->mType == eInterface) {
return newTargetIfaceClass->mPrototypeID;
}
} else if (IsLegacyFactoryFunction(aNewTarget)) {
return GetNativePropertyHooksFromJSNative(aNewTarget)->mPrototypeID; return GetNativePropertyHooksFromJSNative(aNewTarget)->mPrototypeID;
} }

View file

@ -698,6 +698,23 @@ struct JSNativeHolder {
const NativePropertyHooks* mPropertyHooks; 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 { struct LegacyFactoryFunction {
const char* mName; const char* mName;
const JSNativeHolder mHolder; const JSNativeHolder mHolder;
@ -709,8 +726,8 @@ namespace binding_detail {
void CreateInterfaceObjects( void CreateInterfaceObjects(
JSContext* cx, JS::Handle<JSObject*> global, JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> protoProto, const DOMIfaceAndProtoJSClass* protoClass, JS::Handle<JSObject*> protoProto, const DOMIfaceAndProtoJSClass* protoClass,
JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> constructorProto, JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> interfaceProto,
const DOMIfaceJSClass* constructorClass, unsigned ctorNargs, const DOMInterfaceInfo* interfaceInfo, unsigned ctorNargs,
bool isConstructorChromeOnly, bool isConstructorChromeOnly,
const Span<const LegacyFactoryFunction>& legacyFactoryFunctions, const Span<const LegacyFactoryFunction>& legacyFactoryFunctions,
JS::Heap<JSObject*>* constructorCache, const NativeProperties* properties, JS::Heap<JSObject*>* constructorCache, const NativeProperties* properties,
@ -728,24 +745,20 @@ void CreateInterfaceObjects(
* global is used as the parent of the interface object and the interface * global is used as the parent of the interface object and the interface
* prototype object * prototype object
* protoProto is the prototype to use for 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. * protoClass is the JSClass to use for the interface prototype object.
* This is null if we should not create an interface prototype * This is null if we should not create an interface prototype
* object. * object.
* protoCache a pointer to a JSObject pointer where we should cache the * protoCache a pointer to a JSObject pointer where we should cache the
* interface prototype object. This must be null if protoClass is and * interface prototype object. This must be null if protoClass is and
* vice versa. * vice versa.
* constructorClass is the JSClass to use for the interface object. * interfaceProto is the prototype to use for the interface object. This can be
* This is null if we should not create an interface object or * null if interfaceInfo is null (as in, if we're not creating an
* if it should be a function object. * interface object at all).
* constructor holds the JSNative to back the interface object which should be a * interfaceInfo is the info to use for the interface object. This can be null
* Function, unless constructorClass is non-null in which case it is * if we're not creating an interface object.
* ignored. If this is null and constructorClass is also null then
* we should not create an interface object at all.
* ctorNargs is the length of the constructor function; 0 if no constructor * ctorNargs is the length of the constructor function; 0 if no constructor
* isConstructorChromeOnly if true, the constructor is ChromeOnly. * 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 * constructorCache a pointer to a JSObject pointer where we should cache the
* interface object. This must be null if both constructorClass * interface object. This must be null if both constructorClass
* and constructor are null, and non-null otherwise. * and constructor are null, and non-null otherwise.
@ -777,32 +790,33 @@ void CreateInterfaceObjects(
* char* names of the legacy window aliases for this * char* names of the legacy window aliases for this
* interface. * interface.
* *
* At least one of protoClass, constructorClass or constructor should be * At least one of protoClass or interfaceInfo should be non-null. If
* non-null. If constructorClass or constructor are non-null, the resulting * interfaceInfo is non-null, the resulting interface object will be defined on
* interface object will be defined on the given global with property name * the given global with property name |name|, which must also be non-null.
* |name|, which must also be non-null.
*/ */
// clang-format on // clang-format on
template <size_t N> template <size_t N>
inline void CreateInterfaceObjects( inline void CreateInterfaceObjects(
JSContext* cx, JS::Handle<JSObject*> global, JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> protoProto, const DOMIfaceAndProtoJSClass* protoClass, JS::Handle<JSObject*> protoProto, const DOMIfaceAndProtoJSClass* protoClass,
JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> constructorProto, JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> interfaceProto,
const DOMIfaceJSClass* constructorClass, unsigned ctorNargs, const DOMInterfaceInfo* interfaceInfo, unsigned ctorNargs,
bool isConstructorChromeOnly, bool isConstructorChromeOnly,
const Span<const LegacyFactoryFunction, N>& legacyFactoryFunctions, const Span<const LegacyFactoryFunction, N>& legacyFactoryFunctions,
JS::Heap<JSObject*>* constructorCache, const NativeProperties* properties, JS::Heap<JSObject*>* constructorCache, const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties, const char* name, const NativeProperties* chromeOnlyProperties, const char* name,
bool defineOnGlobal, const char* const* unscopableNames, bool isGlobal, bool defineOnGlobal, const char* const* unscopableNames, bool isGlobal,
const char* const* legacyWindowAliases) { const char* const* legacyWindowAliases) {
static_assert(N < 3); // 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( return binding_detail::CreateInterfaceObjects(
cx, global, protoProto, protoClass, protoCache, constructorProto, cx, global, protoProto, protoClass, protoCache, interfaceProto,
constructorClass, ctorNargs, isConstructorChromeOnly, interfaceInfo, ctorNargs, isConstructorChromeOnly, legacyFactoryFunctions,
legacyFactoryFunctions, constructorCache, properties, constructorCache, properties, chromeOnlyProperties, name, defineOnGlobal,
chromeOnlyProperties, name, defineOnGlobal, unscopableNames, isGlobal, unscopableNames, isGlobal, legacyWindowAliases);
legacyWindowAliases);
} }
/* /*
@ -2325,11 +2339,35 @@ inline bool AddStringToIDVector(JSContext* cx,
name); name);
} }
// 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<const DOMInterfaceInfo*>(v.toPrivate());
}
inline const JSNativeHolder* NativeHolderFromInterfaceObject(JSObject* obj) {
MOZ_ASSERT(IsInterfaceObject(obj));
return &InterfaceInfoFromObject(obj)->nativeHolder;
}
// We use one JSNative to represent all legacy factory functions (so we can // 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 // 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 // real JSNative and the NativeProperties in a JSNativeHolder in the
// LEGACY_FACTORY_FUNCTION_NATIVE_HOLDER_RESERVED_SLOT slot of the JSFunction // LEGACY_FACTORY_FUNCTION_NATIVE_HOLDER_RESERVED_SLOT slot of the JSFunction
// object. We also store the NativeProperties in the JSNativeHolder. // object.
bool LegacyFactoryFunctionJSNative(JSContext* cx, unsigned argc, JS::Value* vp); bool LegacyFactoryFunctionJSNative(JSContext* cx, unsigned argc, JS::Value* vp);
inline bool IsLegacyFactoryFunction(JSObject* obj) { inline bool IsLegacyFactoryFunction(JSObject* obj) {
@ -2344,6 +2382,11 @@ inline const JSNativeHolder* NativeHolderFromLegacyFactoryFunction(
return static_cast<const JSNativeHolder*>(v.toPrivate()); return static_cast<const JSNativeHolder*>(v.toPrivate());
} }
inline const JSNativeHolder* NativeHolderFromObject(JSObject* obj) {
return IsInterfaceObject(obj) ? NativeHolderFromInterfaceObject(obj)
: NativeHolderFromLegacyFactoryFunction(obj);
}
// Implementation of the bits that XrayWrapper needs // Implementation of the bits that XrayWrapper needs
/** /**
@ -2414,8 +2457,11 @@ inline bool XrayGetNativeProto(JSContext* cx, JS::Handle<JSObject*> obj,
protop.set(JS::GetRealmObjectPrototype(cx)); protop.set(JS::GetRealmObjectPrototype(cx));
} }
} else if (JS_ObjectIsFunction(obj)) { } else if (JS_ObjectIsFunction(obj)) {
MOZ_ASSERT(IsLegacyFactoryFunction(obj)); if (IsLegacyFactoryFunction(obj)) {
protop.set(JS::GetRealmFunctionPrototype(cx)); protop.set(JS::GetRealmFunctionPrototype(cx));
} else {
protop.set(InterfaceInfoFromObject(obj)->mGetParentProto(cx));
}
} else { } else {
const JSClass* clasp = JS::GetClass(obj); const JSClass* clasp = JS::GetClass(obj);
MOZ_ASSERT(IsDOMIfaceAndProtoClass(clasp)); MOZ_ASSERT(IsDOMIfaceAndProtoClass(clasp));
@ -2490,35 +2536,16 @@ inline JSObject* GetCachedSlotStorageObject(JSContext* cx,
extern NativePropertyHooks sEmptyNativePropertyHooks; extern NativePropertyHooks sEmptyNativePropertyHooks;
extern const JSClassOps sBoringInterfaceObjectClassClassOps; inline bool IsDOMConstructor(JSObject* obj) {
return IsInterfaceObject(obj) || IsLegacyFactoryFunction(obj);
extern const js::ObjectOps sInterfaceObjectClassObjectOps; }
inline bool UseDOMXray(JSObject* obj) { inline bool UseDOMXray(JSObject* obj) {
const JSClass* clasp = JS::GetClass(obj); const JSClass* clasp = JS::GetClass(obj);
return IsDOMClass(clasp) || IsLegacyFactoryFunction(obj) || return IsDOMClass(clasp) || IsDOMConstructor(obj) ||
IsDOMIfaceAndProtoClass(clasp); IsDOMIfaceAndProtoClass(clasp);
} }
inline bool IsDOMConstructor(JSObject* obj) {
if (IsLegacyFactoryFunction(obj)) {
// 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 IsLegacyFactoryFunction(obj) || JS::GetClass(obj)->getConstruct();
}
#endif
// Helpers for creating a const version of a type. // Helpers for creating a const version of a type.
template <typename T> template <typename T>
const T& Constify(T& arg) { const T& Constify(T& arg) {
@ -3270,10 +3297,6 @@ void DeprecationWarning(JSContext* aCx, JSObject* aObject,
void DeprecationWarning(const GlobalObject& aGlobal, void DeprecationWarning(const GlobalObject& aGlobal,
DeprecatedOperations aOperation); DeprecatedOperations aOperation);
// A callback to perform funToString on an interface object
JSString* InterfaceObjectToString(JSContext* aCx, JS::Handle<JSObject*> aObject,
unsigned /* indent */);
namespace binding_detail { namespace binding_detail {
// Get a JS global object that can be used for some temporary allocations. The // 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 // idea is that this should be used for situations when you need to operate in

View file

@ -967,7 +967,7 @@ class CGNamespaceObjectJSClass(CGThing):
) )
class CGInterfaceObjectJSClass(CGThing): class CGInterfaceObjectInfo(CGThing):
def __init__(self, descriptor): def __init__(self, descriptor):
CGThing.__init__(self) CGThing.__init__(self)
self.descriptor = descriptor self.descriptor = descriptor
@ -984,74 +984,25 @@ class CGInterfaceObjectJSClass(CGThing):
wantsIsInstance = self.descriptor.interface.hasInterfacePrototypeObject() wantsIsInstance = self.descriptor.interface.hasInterfacePrototypeObject()
prototypeID, depth = PrototypeIDAndDepth(self.descriptor) prototypeID, depth = PrototypeIDAndDepth(self.descriptor)
slotCount = "INTERFACE_OBJECT_FIRST_LEGACY_FACTORY_FUNCTION"
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) (protoGetter, _) = InterfaceObjectProtoGetter(self.descriptor, forXrays=True)
if ctorname == "ThrowingConstructor": return fill(
ret = ""
classOpsPtr = "&sBoringInterfaceObjectClassClassOps"
else:
ret = fill(
""" """
static const JSClassOps sInterfaceObjectClassOps = { static const DOMInterfaceInfo sInterfaceObjectInfo = {
nullptr, /* addProperty */ { ${ctorname}, ${hooks} },
nullptr, /* delProperty */ ${protoGetter},
nullptr, /* enumerate */
nullptr, /* newEnumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
nullptr, /* finalize */
${ctorname}, /* call */
${ctorname}, /* construct */
nullptr, /* trace */
};
""",
ctorname=ctorname,
)
classOpsPtr = "&sInterfaceObjectClassOps"
funToString = (
'"function %s() {\\n [native code]\\n}"'
% self.descriptor.interface.identifier.name
)
ret = ret + fill(
"""
static const DOMIfaceJSClass sInterfaceObjectClass = {
{
{
"Function",
JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}),
${classOpsPtr},
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
&sInterfaceObjectClassObjectOps
},
eInterface,
${prototypeID}, ${prototypeID},
${depth}, ${depth},
${hooks},
${protoGetter}
},
${wantsIsInstance}, ${wantsIsInstance},
${funToString}
}; };
""", """,
slotCount=slotCount, ctorname=ctorname,
classOpsPtr=classOpsPtr,
hooks=NativePropertyHooks(self.descriptor), hooks=NativePropertyHooks(self.descriptor),
protoGetter=protoGetter,
prototypeID=prototypeID, prototypeID=prototypeID,
depth=depth, depth=depth,
protoGetter=protoGetter,
wantsIsInstance=toStringBool(wantsIsInstance), wantsIsInstance=toStringBool(wantsIsInstance),
funToString=funToString,
) )
return ret
class CGList(CGThing): class CGList(CGThing):
@ -3576,7 +3527,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
getConstructorProto=getConstructorProto, getConstructorProto=getConstructorProto,
) )
interfaceClass = "&sInterfaceObjectClass" interfaceInfo = "&sInterfaceObjectInfo"
interfaceCache = ( interfaceCache = (
"&aProtoAndIfaceCache.EntrySlotOrCreate(constructors::id::%s)" "&aProtoAndIfaceCache.EntrySlotOrCreate(constructors::id::%s)"
% self.descriptor.name % self.descriptor.name
@ -3586,7 +3537,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
else: else:
# We don't have slots to store the legacy factory functions. # We don't have slots to store the legacy factory functions.
assert len(self.descriptor.interface.legacyFactoryFunctions) == 0 assert len(self.descriptor.interface.legacyFactoryFunctions) == 0
interfaceClass = "nullptr" interfaceInfo = "nullptr"
interfaceCache = "nullptr" interfaceCache = "nullptr"
getConstructorProto = None getConstructorProto = None
constructorProto = "nullptr" constructorProto = "nullptr"
@ -3641,7 +3592,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
self.descriptor.interface.hasInterfacePrototypeObject() self.descriptor.interface.hasInterfacePrototypeObject()
) )
# if we don't need to create anything, why are we generating this? # If we don't need to create anything, why are we generating this?
assert needInterfaceObject or needInterfacePrototypeObject assert needInterfaceObject or needInterfacePrototypeObject
if needInterfacePrototypeObject: if needInterfacePrototypeObject:
@ -3698,7 +3649,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
JS::Heap<JSObject*>* interfaceCache = ${interfaceCache}; JS::Heap<JSObject*>* interfaceCache = ${interfaceCache};
dom::CreateInterfaceObjects(aCx, aGlobal, ${parentProto}, dom::CreateInterfaceObjects(aCx, aGlobal, ${parentProto},
${protoClass}, protoCache, ${protoClass}, protoCache,
${constructorProto}, ${interfaceClass}, ${constructArgs}, ${isConstructorChromeOnly}, ${legacyFactoryFunctions}, ${constructorProto}, ${interfaceInfo}, ${constructArgs}, ${isConstructorChromeOnly}, ${legacyFactoryFunctions},
interfaceCache, interfaceCache,
${properties}, ${properties},
${chromeProperties}, ${chromeProperties},
@ -3711,7 +3662,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
parentProto=parentProto, parentProto=parentProto,
protoCache=protoCache, protoCache=protoCache,
constructorProto=constructorProto, constructorProto=constructorProto,
interfaceClass=interfaceClass, interfaceInfo=interfaceInfo,
constructArgs=constructArgs, constructArgs=constructArgs,
isConstructorChromeOnly=toStringBool(isConstructorChromeOnly), isConstructorChromeOnly=toStringBool(isConstructorChromeOnly),
legacyFactoryFunctions=legacyFactoryFunctions, legacyFactoryFunctions=legacyFactoryFunctions,
@ -3724,7 +3675,6 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
legacyWindowAliases="legacyWindowAliases" legacyWindowAliases="legacyWindowAliases"
if self.haveLegacyWindowAliases if self.haveLegacyWindowAliases
else "nullptr", else "nullptr",
isNamespace=toStringBool(self.descriptor.interface.isNamespace()),
) )
# If we fail after here, we must clear interface and prototype caches # If we fail after here, we must clear interface and prototype caches
@ -17002,7 +16952,7 @@ class CGDescriptor(CGThing):
cgThings.append(CGNamespaceObjectJSClass(descriptor)) cgThings.append(CGNamespaceObjectJSClass(descriptor))
elif descriptor.interface.hasInterfaceObject(): elif descriptor.interface.hasInterfaceObject():
cgThings.append(CGClassConstructor(descriptor, descriptor.interface.ctor())) cgThings.append(CGClassConstructor(descriptor, descriptor.interface.ctor()))
cgThings.append(CGInterfaceObjectJSClass(descriptor)) cgThings.append(CGInterfaceObjectInfo(descriptor))
cgThings.append(CGLegacyFactoryFunctions(descriptor)) cgThings.append(CGLegacyFactoryFunctions(descriptor))
cgThings.append(CGLegacyCallHook(descriptor)) cgThings.append(CGLegacyCallHook(descriptor))

View file

@ -570,7 +570,7 @@ struct DOMIfaceAndProtoJSClass {
// initialization for aggregate/POD types. // initialization for aggregate/POD types.
const JSClass mBase; const JSClass mBase;
// Either eInterface, eNamespace, eInterfacePrototype, // Either eNamespace, eInterfacePrototype,
// eGlobalInterfacePrototype or eNamedPropertiesObject. // eGlobalInterfacePrototype or eNamedPropertiesObject.
DOMObjectType mType; // uint8_t DOMObjectType mType; // uint8_t
@ -589,25 +589,6 @@ struct DOMIfaceAndProtoJSClass {
const JSClass* ToJSClass() const { return &mBase; } 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<const DOMIfaceJSClass*>(clazz);
}
};
class ProtoAndIfaceCache; class ProtoAndIfaceCache;
inline bool DOMGlobalHasProtoAndIFaceCache(JSObject* global) { inline bool DOMGlobalHasProtoAndIFaceCache(JSObject* global) {

View file

@ -22,11 +22,18 @@
// Specific objects may have more for storing cached values. // Specific objects may have more for storing cached values.
#define DOM_INSTANCE_RESERVED_SLOTS 1 #define DOM_INSTANCE_RESERVED_SLOTS 1
// Interface objects store a number of reserved slots equal to the number of // Interface objects store a number of reserved slots equal to
// legacy factory functions. // 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 { enum {
INTERFACE_OBJECT_FIRST_LEGACY_FACTORY_FUNCTION = 0, 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 functions store a JSNativeHolder in the
// LEGACY_FACTORY_FUNCTION_NATIVE_HOLDER_RESERVED_SLOT slot. // LEGACY_FACTORY_FUNCTION_NATIVE_HOLDER_RESERVED_SLOT slot.

View file

@ -38,18 +38,23 @@ static JSObject* FindNamedConstructorForXray(
return nullptr; return nullptr;
} }
if (IsInterfaceObject(interfaceObject)) {
// This is a call over Xrays, so we will actually use the return value // 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 // (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. // constructors with this id, in case that's what the caller is asking for.
for (unsigned slot = INTERFACE_OBJECT_FIRST_LEGACY_FACTORY_FUNCTION; for (unsigned slot = INTERFACE_OBJECT_FIRST_LEGACY_FACTORY_FUNCTION;
slot < JSCLASS_RESERVED_SLOTS(JS::GetClass(interfaceObject)); ++slot) { slot < INTERFACE_OBJECT_MAX_SLOTS; ++slot) {
JSObject* constructor = const JS::Value& v = js::GetFunctionNativeReserved(interfaceObject, slot);
&JS::GetReservedSlot(interfaceObject, slot).toObject(); if (!v.isObject()) {
break;
}
JSObject* constructor = &v.toObject();
if (JS_GetMaybePartialFunctionId(JS_GetObjectFunction(constructor)) == if (JS_GetMaybePartialFunctionId(JS_GetObjectFunction(constructor)) ==
aId.toString()) { aId.toString()) {
return constructor; return constructor;
} }
} }
}
// None of the legacy factory functions match, so the caller must want the // None of the legacy factory functions match, so the caller must want the
// interface object itself. // interface object itself.

View file

@ -176,6 +176,27 @@ function test() {
// ECMAScript-defined properties live on the prototype, overriding any named properties. // ECMAScript-defined properties live on the prototype, overriding any named properties.
checkXrayProperty(coll, "toString", [ undefined, undefined, win.Object.prototype.toString ]); 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. // Frozen arrays should come from our compartment, not the target one.
var languages1 = win.navigator.languages; var languages1 = win.navigator.languages;
isnot(languages1, undefined, "Must have .languages"); isnot(languages1, undefined, "Must have .languages");
@ -358,7 +379,7 @@ function test() {
// legacyCaller should work. // legacyCaller should work.
ok(win.HTMLAllCollection.isInstance(doc.all), ok(win.HTMLAllCollection.isInstance(doc.all),
"HTMLDocument.all should be an instance of HTMLAllCollection"); "HTMLDocument.all should be an instance of HTMLAllCollection");
let element, threw; let element;
try { try {
threw = false; threw = false;
element = doc.all(0); element = doc.all(0);

View file

@ -1799,6 +1799,11 @@ bool DOMXrayTraits::call(JSContext* cx, HandleObject wrapper,
// using "legacycaller". At this time for all the legacycaller users it makes // using "legacycaller". At this time for all the legacycaller users it makes
// more sense to invoke on the xray compartment, so we just go ahead and do // more sense to invoke on the xray compartment, so we just go ahead and do
// that for everything. // that for everything.
if (IsDOMConstructor(obj)) {
const JSNativeHolder* holder = NativeHolderFromObject(obj);
return holder->mNative(cx, args.length(), args.base());
}
if (js::IsProxy(obj)) { if (js::IsProxy(obj)) {
if (JS::IsCallable(obj)) { if (JS::IsCallable(obj)) {
// Passing obj here, but it doesn't really matter because legacycaller // Passing obj here, but it doesn't really matter because legacycaller
@ -1822,9 +1827,14 @@ bool DOMXrayTraits::construct(JSContext* cx, HandleObject wrapper,
const JS::CallArgs& args, const JS::CallArgs& args,
const js::Wrapper& baseInstance) { const js::Wrapper& baseInstance) {
RootedObject obj(cx, getTargetObject(wrapper)); RootedObject obj(cx, getTargetObject(wrapper));
MOZ_ASSERT(mozilla::dom::HasConstructor(obj));
const JSClass* clasp = JS::GetClass(obj);
// See comments in DOMXrayTraits::call() explaining what's going on here. // See comments in DOMXrayTraits::call() explaining what's going on here.
if (IsDOMConstructor(obj)) {
const JSNativeHolder* holder = NativeHolderFromObject(obj);
if (!holder->mNative(cx, args.length(), args.base())) {
return false;
}
} else {
const JSClass* clasp = JS::GetClass(obj);
if (clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS) { if (clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS) {
if (JSNative construct = clasp->getConstruct()) { if (JSNative construct = clasp->getConstruct()) {
if (!construct(cx, args.length(), args.base())) { if (!construct(cx, args.length(), args.base())) {
@ -1840,6 +1850,7 @@ bool DOMXrayTraits::construct(JSContext* cx, HandleObject wrapper,
return false; return false;
} }
} }
}
if (!args.rval().isObject() || !JS_WrapValue(cx, args.rval())) { if (!args.rval().isObject() || !JS_WrapValue(cx, args.rval())) {
return false; return false;
} }