Bug 1104955 part 3. Pass our unscopable names to CreateInterfaceObjects and have it define the right thing on the prototype. r=khuey
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -761,25 +761,48 @@ CreateInterfaceObject(JSContext* cx, JS:
return constructor;
}
static JSObject*
CreateInterfacePrototypeObject(JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> parentProto,
const js::Class* protoClass,
const NativeProperties* properties,
- const NativeProperties* chromeOnlyProperties)
+ const NativeProperties* chromeOnlyProperties,
+ const char* const* unscopableNames)
{
JS::Rooted<JSObject*> ourProto(cx,
JS_NewObjectWithUniqueType(cx, Jsvalify(protoClass), parentProto));
if (!ourProto ||
!DefineProperties(cx, ourProto, properties, chromeOnlyProperties)) {
return nullptr;
}
+ if (unscopableNames) {
+ JS::Rooted<JSObject*> unscopableObj(cx, JS_NewPlainObject(cx));
+ if (!unscopableObj) {
+ return nullptr;
+ }
+
+ for (; *unscopableNames; ++unscopableNames) {
+ if (!JS_DefineProperty(cx, unscopableObj, *unscopableNames,
+ JS::TrueHandleValue, JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+ }
+
+ JS::Rooted<jsid> unscopableId(cx,
+ SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::unscopables)));
+ // Readonly and non-enumerable to match Array.prototype.
+ if (!JS_DefinePropertyById(cx, ourProto, unscopableId, unscopableObj,
+ JSPROP_READONLY)) {
+ return nullptr;
+ }
+ }
+
return ourProto;
}
bool
DefineProperties(JSContext* cx, JS::Handle<JSObject*> obj,
const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties)
{
@@ -825,17 +848,18 @@ CreateInterfaceObjects(JSContext* cx, JS
JS::Handle<JSObject*> protoProto,
const js::Class* protoClass, JS::Heap<JSObject*>* protoCache,
JS::Handle<JSObject*> constructorProto,
const js::Class* constructorClass, const JSNativeHolder* constructor,
unsigned ctorNargs, const NamedConstructor* namedConstructors,
JS::Heap<JSObject*>* constructorCache,
const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties,
- const char* name, bool defineOnGlobal)
+ const char* name, bool defineOnGlobal,
+ const char* const* unscopableNames)
{
MOZ_ASSERT(protoClass || constructorClass || constructor,
"Need at least one class or a constructor!");
MOZ_ASSERT(!((properties &&
(properties->methods || properties->attributes)) ||
(chromeOnlyProperties &&
(chromeOnlyProperties->methods ||
chromeOnlyProperties->attributes))) || protoClass,
@@ -856,17 +880,18 @@ CreateInterfaceObjects(JSContext* cx, JS
MOZ_ASSERT(!(constructorClass || constructor) == !constructorCache,
"If, and only if, there is an interface object we need to cache "
"it");
JS::Rooted<JSObject*> proto(cx);
if (protoClass) {
proto =
CreateInterfacePrototypeObject(cx, global, protoProto, protoClass,
- properties, chromeOnlyProperties);
+ properties, chromeOnlyProperties,
+ unscopableNames);
if (!proto) {
return;
}
*protoCache = proto;
}
else {
MOZ_ASSERT(!proto);
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -589,33 +589,36 @@ struct NamedConstructor
* interface doesn't have any ChromeOnly properties or if the
* object is being created in non-chrome compartment.
* defineOnGlobal controls whether properties should be defined on the given
* global for the interface object (if any) and named
* constructors (if any) for this interface. This can be
* false in situations where we want the properties to only
* appear on privileged Xrays but not on the unprivileged
* underlying global.
+ * unscopableNames if not null it points to a null-terminated list of const
+ * char* names of the unscopable properties 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.
*/
void
CreateInterfaceObjects(JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> protoProto,
const js::Class* protoClass, JS::Heap<JSObject*>* protoCache,
JS::Handle<JSObject*> interfaceProto,
const js::Class* constructorClass, const JSNativeHolder* constructor,
unsigned ctorNargs, const NamedConstructor* namedConstructors,
JS::Heap<JSObject*>* constructorCache,
const NativeProperties* regularProperties,
const NativeProperties* chromeOnlyProperties,
- const char* name, bool defineOnGlobal);
+ const char* name, bool defineOnGlobal,
+ const char* const* unscopableNames);
/**
* Define the properties (regular and chrome-only) on obj.
*
* obj the object to instal the properties on. This should be the interface
* prototype object for regular interfaces and the instance object for
* interfaces marked with Global.
* properties contains the methods, attributes and constants to be defined on
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -2740,23 +2740,24 @@ class CGJsonifyAttributesMethod(CGAbstra
class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
"""
Generate the CreateInterfaceObjects method for an interface descriptor.
properties should be a PropertyArrays instance.
"""
- def __init__(self, descriptor, properties):
+ def __init__(self, descriptor, properties, haveUnscopables):
args = [Argument('JSContext*', 'aCx'),
Argument('JS::Handle<JSObject*>', 'aGlobal'),
Argument('ProtoAndIfaceCache&', 'aProtoAndIfaceCache'),
Argument('bool', 'aDefineOnGlobal')]
CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', 'void', args)
self.properties = properties
+ self.haveUnscopables = haveUnscopables
def definition_body(self):
(protoGetter, protoHandleGetter) = InterfacePrototypeObjectProtoGetter(self.descriptor)
if protoHandleGetter is None:
parentProtoType = "Rooted"
getParentProto = "aCx, " + protoGetter
else:
parentProtoType = "Handle"
@@ -2886,29 +2887,31 @@ class CGCreateInterfaceObjectsMethod(CGA
JS::Heap<JSObject*>* protoCache = ${protoCache};
JS::Heap<JSObject*>* interfaceCache = ${interfaceCache};
dom::CreateInterfaceObjects(aCx, aGlobal, ${parentProto},
${protoClass}, protoCache,
constructorProto, ${interfaceClass}, ${constructHookHolder}, ${constructArgs}, ${namedConstructors},
interfaceCache,
${properties},
${chromeProperties},
- ${name}, aDefineOnGlobal);
+ ${name}, aDefineOnGlobal,
+ ${unscopableNames});
""",
protoClass=protoClass,
parentProto=parentProto,
protoCache=protoCache,
interfaceClass=interfaceClass,
constructHookHolder=constructHookHolder,
constructArgs=constructArgs,
namedConstructors=namedConstructors,
interfaceCache=interfaceCache,
properties=properties,
chromeProperties=chromeProperties,
- name='"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "nullptr")
+ name='"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "nullptr",
+ unscopableNames="unscopableNames" if self.haveUnscopables else "nullptr")
# If we fail after here, we must clear interface and prototype caches
# using this code: intermediate failure must not expose the interface in
# partially-constructed state. Note that every case after here needs an
# interface prototype object.
failureCode = dedent(
"""
*protoCache = nullptr;
@@ -12073,17 +12076,18 @@ class CGDescriptor(CGThing):
CGList([CGGeneric("static const char* const unscopableNames[] = {"),
CGIndenter(CGList([CGGeneric('"%s"' % name) for
name in unscopableNames] +
[CGGeneric("nullptr")], ",\n")),
CGGeneric("};\n")], "\n"))
# CGCreateInterfaceObjectsMethod needs to come after our
# CGDOMJSClass and unscopables, if any.
- cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties))
+ cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties,
+ haveUnscopables))
# CGGetProtoObjectMethod and CGGetConstructorObjectMethod need
# to come after CGCreateInterfaceObjectsMethod.
if descriptor.interface.hasInterfacePrototypeObject():
cgThings.append(CGGetProtoObjectHandleMethod(descriptor))
if descriptor.interface.hasChildInterfaces():
cgThings.append(CGGetProtoObjectMethod(descriptor))
if descriptor.interface.hasInterfaceObject():
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -34868,16 +34868,22 @@
},
"testharness": {
"dom/collections/HTMLCollection-as-proto-length-get-throws.html": [
{
"path": "dom/collections/HTMLCollection-as-proto-length-get-throws.html",
"url": "/dom/collections/HTMLCollection-as-proto-length-get-throws.html"
}
],
+ "dom/nodes/remove-unscopable.html": [
+ {
+ "path": "dom/nodes/remove-unscopable.html",
+ "url": "/dom/nodes/remove-unscopable.html"
+ }
+ ],
"js/builtins/Promise-incumbent-global.sub.html": [
{
"path": "js/builtins/Promise-incumbent-global.sub.html",
"url": "/js/builtins/Promise-incumbent-global.sub.html"
}
],
"selection/extend.html": [
{
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/remove-unscopable.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id="testDiv" onclick="result1 = remove; result2 = this.remove;"></div>
+<script>
+var remove = "Hello there";
+var result1;
+var result2;
+test(function() {
+ assert_true(Element.prototype[Symbol.unscopables].remove);
+ var div = document.querySelector("#testDiv");
+ div.dispatchEvent(new Event("click"));
+ assert_equals(typeof result1, "string");
+ assert_equals(typeof result2, "function");
+}, "remove() should be unscopable")
+</script>