Use a null prototype for @@unscopables objects in the DOM.
bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1437255
gecko-commit: a6088a5f48ee299386a84d2f771902267d7355b1
gecko-integration-branch: mozilla-inbound
gecko-reviewers: qdot
diff --git a/interfaces/dom.idl b/interfaces/dom.idl
index 2532478..90419f0 100644
--- a/interfaces/dom.idl
+++ b/interfaces/dom.idl
@@ -340,7 +340,7 @@
            attribute DOMString id;
            attribute DOMString className;
   [SameObject, PutForwards=value] readonly attribute DOMTokenList classList;
-           attribute DOMString slot;
+  [Unscopable] attribute DOMString slot;
 
   boolean hasAttributes();
   [SameObject] readonly attribute NamedNodeMap attributes;
diff --git a/resources/idlharness.js b/resources/idlharness.js
index 47dd9fb..af17cee 100644
--- a/resources/idlharness.js
+++ b/resources/idlharness.js
@@ -1007,6 +1007,13 @@
 };
 //@}
 
+IdlInterface.prototype.get_unscopables = function()
+{
+    return this.members.filter(function(member) {
+        return member.isUnscopable;
+    });
+};
+
 IdlInterface.prototype.is_global = function()
 //@{
 {
@@ -1552,6 +1559,69 @@
         assert_equals(self[this.name].prototype.constructor, self[this.name],
                       this.name + '.prototype.constructor is not the same object as ' + this.name);
     }.bind(this), this.name + ' interface: existence and properties of interface prototype object\'s "constructor" property');
+
+
+    test(function()
+    {
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
+
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+
+        if (this.is_callback()) {
+            assert_false("prototype" in self[this.name],
+                         this.name + ' should not have a "prototype" property');
+            return;
+        }
+
+        assert_own_property(self[this.name], "prototype",
+                            'interface "' + this.name + '" does not have own property "prototype"');
+
+        // If the interface has any member declared with the [Unscopable] extended
+        // attribute, then there must be a property on the interface prototype object
+        // whose name is the @@unscopables symbol, which has the attributes
+        // { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true },
+        // and whose value is an object created as follows...
+        var unscopables = this.get_unscopables().map(m => m.name);
+        var proto = self[this.name].prototype;
+        if (unscopables.length != 0) {
+            assert_own_property(
+                proto, Symbol.unscopables,
+                this.name + '.prototype should have an @@unscopables property');
+            var desc = Object.getOwnPropertyDescriptor(proto, Symbol.unscopables);
+            assert_false("get" in desc,
+                         this.name + ".prototype[Symbol.unscopables] should not have a getter");
+            assert_false("set" in desc, this.name + ".prototype[Symbol.unscopables] should not have a setter");
+            assert_false(desc.writable, this.name + ".prototype[Symbol.unscopables] should not be writable");
+            assert_false(desc.enumerable, this.name + ".prototype[Symbol.unscopables] should not be enumerable");
+            assert_true(desc.configurable, this.name + ".prototype[Symbol.unscopables] should be configurable");
+            assert_equals(desc.value, proto[Symbol.unscopables],
+                          this.name + '.prototype[Symbol.unscopables] should be in the descriptor');
+            assert_equals(typeof desc.value, "object",
+                          this.name + '.prototype[Symbol.unscopables] should be an object');
+            assert_equals(Object.getPrototypeOf(desc.value), null,
+                          this.name + '.prototype[Symbol.unscopables] should have a null prototype');
+            assert_equals(Object.getOwnPropertySymbols(desc.value).length,
+                          0,
+                          this.name + '.prototype[Symbol.unscopables] should have the right number of symbol-named properties');
+
+            // Check that we do not have _extra_ unscopables.  Checking that we
+            // have all the ones we should will happen in the per-member tests.
+            var observed = Object.getOwnPropertyNames(desc.value);
+            for (var prop of observed) {
+                assert_not_equals(unscopables.indexOf(prop),
+                                  -1,
+                                  this.name + '.prototype[Symbol.unscopables] has unexpected property "' + prop + '"');
+            }
+        } else {
+            assert_equals(Object.getOwnPropertyDescriptor(self[this.name].prototype, Symbol.unscopables),
+                          undefined,
+                          this.name + '.prototype should not have @@unscopables');
+        }
+    }.bind(this), this.name + ' interface: existence and properties of interface prototype object\'s @@unscopables property');
+
 };
 
 //@}
@@ -1805,6 +1875,10 @@
 
         }
     }.bind(this));
+
+    test(function () {
+        this.do_member_unscopable_asserts(member);
+    }.bind(this), 'Unscopable handled correctly for ' + member.name + ' property on ' + this.name);
 };
 
 //@}
@@ -1869,6 +1943,41 @@
         }
         this.do_member_operation_asserts(memberHolderObject, member, a_test);
     }.bind(this));
+
+    test(function () {
+        this.do_member_unscopable_asserts(member);
+    }.bind(this),
+         'Unscopable handled correctly for ' + member.name + "(" +
+         member.arguments.map(
+             function(m) {return m.idlType.idlType; } ).join(", ")
+         + ")" + ' on ' + this.name);
+};
+
+IdlInterface.prototype.do_member_unscopable_asserts = function(member)
+{
+    // Check that if the member is unscopable then it's in the
+    // @@unscopables object properly.
+    if (!member.isUnscopable) {
+        return;
+    }
+
+    var unscopables = self[this.name].prototype[Symbol.unscopables];
+    var prop = member.name;
+    var propDesc = Object.getOwnPropertyDescriptor(unscopables, prop);
+    assert_equals(typeof propDesc, "object",
+                  this.name + '.prototype[Symbol.unscopables].' + prop + ' must exist')
+    assert_false("get" in propDesc,
+                 this.name + '.prototype[Symbol.unscopables].' + prop + ' must have no getter');
+    assert_false("set" in propDesc,
+                 this.name + '.prototype[Symbol.unscopables].' + prop + ' must have no setter');
+    assert_true(propDesc.writable,
+                this.name + '.prototype[Symbol.unscopables].' + prop + ' must be writable');
+    assert_true(propDesc.enumerable,
+                this.name + '.prototype[Symbol.unscopables].' + prop + ' must be enumerable');
+    assert_true(propDesc.configurable,
+                this.name + '.prototype[Symbol.unscopables].' + prop + ' must be configurable');
+    assert_equals(propDesc.value, true,
+                  this.name + '.prototype[Symbol.unscopables].' + prop + ' must have the value `true`');
 };
 
 //@}
@@ -2552,6 +2661,7 @@
     }
 
     this.isUnforgeable = this.has_extended_attribute("Unforgeable");
+    this.isUnscopable = this.has_extended_attribute("Unscopable");
 }
 
 //@}