Bug 1882127 - Use plain JS functions for WebIDL interface objects. r=saschanaz

Differential Revision: https://phabricator.services.mozilla.com/D202824
This commit is contained in:
Peter Van der Beken 2024-03-13 08:23:03 +00:00
Родитель 49d0d95335
Коммит 0f2cc8ce71
8 изменённых файлов: 226 добавлений и 274 удалений

Просмотреть файл

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

Просмотреть файл

@ -698,6 +698,23 @@ 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;
@ -709,8 +726,8 @@ namespace binding_detail {
void CreateInterfaceObjects(
JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> protoProto, const DOMIfaceAndProtoJSClass* protoClass,
JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> constructorProto,
const DOMIfaceJSClass* constructorClass, unsigned ctorNargs,
JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> interfaceProto,
const DOMInterfaceInfo* interfaceInfo, unsigned ctorNargs,
bool isConstructorChromeOnly,
const Span<const LegacyFactoryFunction>& legacyFactoryFunctions,
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
* 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.
@ -777,32 +790,33 @@ void CreateInterfaceObjects(
* 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
template <size_t N>
inline void CreateInterfaceObjects(
JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> protoProto, const DOMIfaceAndProtoJSClass* protoClass,
JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> constructorProto,
const DOMIfaceJSClass* constructorClass, unsigned ctorNargs,
JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> interfaceProto,
const DOMInterfaceInfo* interfaceInfo, unsigned ctorNargs,
bool isConstructorChromeOnly,
const Span<const LegacyFactoryFunction, N>& legacyFactoryFunctions,
JS::Heap<JSObject*>* constructorCache, const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties, const char* name,
bool defineOnGlobal, const char* const* unscopableNames, bool isGlobal,
const char* const* legacyWindowAliases) {
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(
cx, global, protoProto, protoClass, protoCache, constructorProto,
constructorClass, ctorNargs, isConstructorChromeOnly,
legacyFactoryFunctions, constructorCache, properties,
chromeOnlyProperties, name, defineOnGlobal, unscopableNames, isGlobal,
legacyWindowAliases);
cx, global, protoProto, protoClass, protoCache, interfaceProto,
interfaceInfo, ctorNargs, isConstructorChromeOnly, legacyFactoryFunctions,
constructorCache, properties, chromeOnlyProperties, name, defineOnGlobal,
unscopableNames, isGlobal, legacyWindowAliases);
}
/*
@ -2325,11 +2339,35 @@ inline bool AddStringToIDVector(JSContext* cx,
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
// 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
// object. We also store the NativeProperties in the JSNativeHolder.
// object.
bool LegacyFactoryFunctionJSNative(JSContext* cx, unsigned argc, JS::Value* vp);
inline bool IsLegacyFactoryFunction(JSObject* obj) {
@ -2344,6 +2382,11 @@ inline const JSNativeHolder* NativeHolderFromLegacyFactoryFunction(
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
/**
@ -2414,8 +2457,11 @@ inline bool XrayGetNativeProto(JSContext* cx, JS::Handle<JSObject*> obj,
protop.set(JS::GetRealmObjectPrototype(cx));
}
} else if (JS_ObjectIsFunction(obj)) {
MOZ_ASSERT(IsLegacyFactoryFunction(obj));
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));
@ -2490,35 +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) || IsLegacyFactoryFunction(obj) ||
return IsDOMClass(clasp) || IsDOMConstructor(obj) ||
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.
template <typename T>
const T& Constify(T& arg) {
@ -3270,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<JSObject*> 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

Просмотреть файл

@ -963,7 +963,7 @@ class CGNamespaceObjectJSClass(CGThing):
)
class CGInterfaceObjectJSClass(CGThing):
class CGInterfaceObjectInfo(CGThing):
def __init__(self, descriptor):
CGThing.__init__(self)
self.descriptor = descriptor
@ -980,74 +980,25 @@ class CGInterfaceObjectJSClass(CGThing):
wantsIsInstance = self.descriptor.interface.hasInterfacePrototypeObject()
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)
if ctorname == "ThrowingConstructor":
ret = ""
classOpsPtr = "&sBoringInterfaceObjectClassClassOps"
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"
funToString = (
'"function %s() {\\n [native code]\\n}"'
% self.descriptor.interface.identifier.name
)
ret = ret + fill(
return 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},
${depth},
${hooks},
${protoGetter}
},
static const DOMInterfaceInfo sInterfaceObjectInfo = {
{ ${ctorname}, ${hooks} },
${protoGetter},
${prototypeID},
${depth},
${wantsIsInstance},
${funToString}
};
""",
slotCount=slotCount,
classOpsPtr=classOpsPtr,
ctorname=ctorname,
hooks=NativePropertyHooks(self.descriptor),
protoGetter=protoGetter,
prototypeID=prototypeID,
depth=depth,
protoGetter=protoGetter,
wantsIsInstance=toStringBool(wantsIsInstance),
funToString=funToString,
)
return ret
class CGList(CGThing):
@ -3572,7 +3523,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
getConstructorProto=getConstructorProto,
)
interfaceClass = "&sInterfaceObjectClass"
interfaceInfo = "&sInterfaceObjectInfo"
interfaceCache = (
"&aProtoAndIfaceCache.EntrySlotOrCreate(constructors::id::%s)"
% self.descriptor.name
@ -3582,7 +3533,7 @@ 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"
@ -3637,7 +3588,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
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
if needInterfacePrototypeObject:
@ -3694,7 +3645,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
JS::Heap<JSObject*>* interfaceCache = ${interfaceCache};
dom::CreateInterfaceObjects(aCx, aGlobal, ${parentProto},
${protoClass}, protoCache,
${constructorProto}, ${interfaceClass}, ${constructArgs}, ${isConstructorChromeOnly}, ${legacyFactoryFunctions},
${constructorProto}, ${interfaceInfo}, ${constructArgs}, ${isConstructorChromeOnly}, ${legacyFactoryFunctions},
interfaceCache,
${properties},
${chromeProperties},
@ -3707,7 +3658,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
parentProto=parentProto,
protoCache=protoCache,
constructorProto=constructorProto,
interfaceClass=interfaceClass,
interfaceInfo=interfaceInfo,
constructArgs=constructArgs,
isConstructorChromeOnly=toStringBool(isConstructorChromeOnly),
legacyFactoryFunctions=legacyFactoryFunctions,
@ -3720,7 +3671,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
@ -16998,7 +16948,7 @@ class CGDescriptor(CGThing):
cgThings.append(CGNamespaceObjectJSClass(descriptor))
elif descriptor.interface.hasInterfaceObject():
cgThings.append(CGClassConstructor(descriptor, descriptor.interface.ctor()))
cgThings.append(CGInterfaceObjectJSClass(descriptor))
cgThings.append(CGInterfaceObjectInfo(descriptor))
cgThings.append(CGLegacyFactoryFunctions(descriptor))
cgThings.append(CGLegacyCallHook(descriptor))

Просмотреть файл

@ -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<const DOMIfaceJSClass*>(clazz);
}
};
class ProtoAndIfaceCache;
inline bool DOMGlobalHasProtoAndIFaceCache(JSObject* global) {

Просмотреть файл

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

Просмотреть файл

@ -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 = INTERFACE_OBJECT_FIRST_LEGACY_FACTORY_FUNCTION;
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;
}
}
}

Просмотреть файл

@ -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);

Просмотреть файл

@ -1799,6 +1799,11 @@ bool DOMXrayTraits::call(JSContext* cx, HandleObject wrapper,
// 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
// 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::IsCallable(obj)) {
// Passing obj here, but it doesn't really matter because legacycaller
@ -1822,22 +1827,28 @@ bool DOMXrayTraits::construct(JSContext* cx, HandleObject wrapper,
const JS::CallArgs& args,
const js::Wrapper& baseInstance) {
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.
if (clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS) {
if (JSNative construct = clasp->getConstruct()) {
if (!construct(cx, args.length(), args.base())) {
return false;
}
} else {
RootedValue v(cx, ObjectValue(*wrapper));
js::ReportIsNotFunction(cx, v);
if (IsDOMConstructor(obj)) {
const JSNativeHolder* holder = NativeHolderFromObject(obj);
if (!holder->mNative(cx, args.length(), args.base())) {
return false;
}
} else {
if (!baseInstance.construct(cx, wrapper, args)) {
return false;
const JSClass* clasp = JS::GetClass(obj);
if (clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS) {
if (JSNative construct = clasp->getConstruct()) {
if (!construct(cx, args.length(), args.base())) {
return false;
}
} else {
RootedValue v(cx, ObjectValue(*wrapper));
js::ReportIsNotFunction(cx, v);
return false;
}
} else {
if (!baseInstance.construct(cx, wrapper, args)) {
return false;
}
}
}
if (!args.rval().isObject() || !JS_WrapValue(cx, args.rval())) {