зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
49d0d95335
Коммит
0f2cc8ce71
|
@ -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())) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче