Bug 1104955 part 3. Pass our unscopable names to CreateInterfaceObjects and have it define the right thing on the prototype. r=khuey
authorBoris Zbarsky <bzbarsky@mit.edu>
Tue, 29 Mar 2016 15:50:38 -0400
changeset 290962 2fde05270e2574ca3ccbf64108a1032e46d46e80
parent 290961 75898c3d4bb2ba2557ee90c895bebf110bdaa9dd
child 290963 ae8583e1bccfe89748404e8d70d72e6fa23f816c
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs1104955
milestone48.0a1
Bug 1104955 part 3. Pass our unscopable names to CreateInterfaceObjects and have it define the right thing on the prototype. r=khuey
dom/bindings/BindingUtils.cpp
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/dom/nodes/remove-unscopable.html
--- 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>