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");
}
//@}