summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webidl/ecmascript-binding
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webidl/ecmascript-binding')
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/allow-resizable.html31
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/attributes-accessors-unique-function-objects.html35
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/builtin-function-properties.any.js23
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/class-string-interface.any.js62
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/class-string-iterator-prototype-object.any.js51
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/class-string-named-properties-object.window.js23
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/constructors.html132
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/default-iterator-object.html27
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/default-toJSON-cross-realm.html26
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constants.any.js51
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constructor-and-prototype.any.js32
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constructor-behavior.any.js140
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-custom-bindings.any.js120
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/exceptions.html78
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/global-immutable-prototype.any.js25
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/global-mutable-prototype.any.js27
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/global-object-implicit-this-value-cross-realm.html97
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/global-object-implicit-this-value.any.js85
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/has-instance.html26
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/interface-object-set-receiver.html37
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/interface-object.html28
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/interface-prototype-constructor-set-receiver.html36
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/interface-prototype-object.html15
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/invalid-this-value-cross-realm.html45
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/iterator-invalidation-foreach.html40
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/iterator-prototype-object.html47
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/legacy-callback-interface-object.html69
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/legacy-factor-function-subclass.window.js13
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/DefineOwnProperty.html165
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/GetOwnProperty.html84
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/OwnPropertyKeys.html65
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/Set.html94
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/helper.js22
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/no-regexp-special-casing.any.js47
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/observable-array-no-leak-of-internals.window.js18
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/observable-array-ownkeys.window.js34
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/put-forwards.html148
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/replaceable-setter-throws-if-defineownproperty-fails.html38
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/sequence-conversion.html157
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/support/constructors-support.html8
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/support/create-realm.js12
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/support/dummy-iframe.html7
-rw-r--r--testing/web-platform/tests/webidl/ecmascript-binding/window-named-properties-object.html284
43 files changed, 2604 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/allow-resizable.html b/testing/web-platform/tests/webidl/ecmascript-binding/allow-resizable.html
new file mode 100644
index 0000000000..54daa57bce
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/allow-resizable.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/sab.js"></script>
+<script type="module">
+test(t => {
+ // Fixed-length ABs should not throw
+ const ab = new ArrayBuffer(16);
+ new Response(new Uint8Array(ab));
+
+ const rab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ // Response doesn't have [AllowResizable] or [AllowShared]
+ assert_throws_js(TypeError, () => {
+ new Response(new Uint8Array(rab));
+ });
+}, "APIs without [AllowResizable] throw when passed resizable ArrayBuffers");
+
+test(t => {
+ const enc = new TextEncoder();
+
+ // Fixed-length SABs should not throw
+ const sab = createBuffer('SharedArrayBuffer', 16);
+ enc.encodeInto("foobar", new Uint8Array(sab));
+
+ const gsab = createBuffer('SharedArrayBuffer', 16, { maxByteLength: 1024 });
+ // TextEncoder.encodeInto doesn't have [AllowResizable] but has [AllowShared]
+ assert_throws_js(TypeError, () => {
+ enc.encodeInto("foobar", new Uint8Array(gsab));
+ });
+}, "APIs with [AllowShared] but without [AllowResizable] throw when passed growable SharedArrayBuffers");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/attributes-accessors-unique-function-objects.html b/testing/web-platform/tests/webidl/ecmascript-binding/attributes-accessors-unique-function-objects.html
new file mode 100644
index 0000000000..167f55bcef
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/attributes-accessors-unique-function-objects.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>All attributes accessors are unique function objects</title>
+<link rel="help" href="https://webidl.spec.whatwg.org/#idl-interface-mixins">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+
+test(() => {
+ const seenPrototypes = new WeakSet([Function.prototype]);
+ const seenFunctions = new WeakMap();
+
+ for (const windowKey of Object.getOwnPropertyNames(window)) {
+ const windowValue = window[windowKey];
+ if (typeof windowValue !== "function") continue;
+
+ const {prototype} = windowValue;
+ if (!prototype || seenPrototypes.has(prototype)) continue;
+ seenPrototypes.add(prototype);
+
+ for (const key of Object.getOwnPropertyNames(prototype)) {
+ const assert_not_seen = (fn, field) => {
+ const fnInfo = `${windowKey}.${key}.${field}`;
+ assert_equals(seenFunctions.get(fn), undefined, fnInfo);
+ seenFunctions.set(fn, fnInfo);
+ };
+
+ const desc = Object.getOwnPropertyDescriptor(prototype, key);
+ if (desc.get) assert_not_seen(desc.get, "[[Get]]");
+ if (desc.set) assert_not_seen(desc.set, "[[Set]]");
+ }
+ }
+}, "For attributes, each copy of the accessor property has distinct built-in function objects for its getters and setters.");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/builtin-function-properties.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/builtin-function-properties.any.js
new file mode 100644
index 0000000000..885bb441ea
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/builtin-function-properties.any.js
@@ -0,0 +1,23 @@
+"use strict";
+
+test(() => {
+ const ownPropKeys = Reflect.ownKeys(Blob).slice(0, 3);
+ assert_array_equals(ownPropKeys, ["length", "name", "prototype"]);
+}, 'Constructor property enumeration order of "length", "name", and "prototype"');
+
+test(() => {
+ assert_own_property(Blob.prototype, "slice");
+
+ const ownPropKeys = Reflect.ownKeys(Blob.prototype.slice).slice(0, 2);
+ assert_array_equals(ownPropKeys, ["length", "name"]);
+}, 'Method property enumeration order of "length" and "name"');
+
+test(() => {
+ assert_own_property(Blob.prototype, "size");
+
+ const desc = Reflect.getOwnPropertyDescriptor(Blob.prototype, "size");
+ assert_equals(typeof desc.get, "function");
+
+ const ownPropKeys = Reflect.ownKeys(desc.get).slice(0, 2);
+ assert_array_equals(ownPropKeys, ["length", "name"]);
+}, 'Getter property enumeration order of "length" and "name"');
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/class-string-interface.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/class-string-interface.any.js
new file mode 100644
index 0000000000..ee792d5368
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/class-string-interface.any.js
@@ -0,0 +1,62 @@
+"use strict";
+
+test(() => {
+ assert_own_property(Blob.prototype, Symbol.toStringTag);
+
+ const propDesc = Object.getOwnPropertyDescriptor(Blob.prototype, Symbol.toStringTag);
+ assert_equals(propDesc.value, "Blob", "value");
+ assert_equals(propDesc.configurable, true, "configurable");
+ assert_equals(propDesc.enumerable, false, "enumerable");
+ assert_equals(propDesc.writable, false, "writable");
+}, "@@toStringTag exists on the prototype with the appropriate descriptor");
+
+test(() => {
+ assert_not_own_property(new Blob(), Symbol.toStringTag);
+}, "@@toStringTag must not exist on the instance");
+
+test(() => {
+ assert_equals(Object.prototype.toString.call(Blob.prototype), "[object Blob]");
+}, "Object.prototype.toString applied to the prototype");
+
+test(() => {
+ assert_equals(Object.prototype.toString.call(new Blob()), "[object Blob]");
+}, "Object.prototype.toString applied to an instance");
+
+test(t => {
+ assert_own_property(Blob.prototype, Symbol.toStringTag, "Precondition for this test: @@toStringTag on the prototype");
+
+ t.add_cleanup(() => {
+ Object.defineProperty(Blob.prototype, Symbol.toStringTag, { value: "Blob" });
+ });
+
+ Object.defineProperty(Blob.prototype, Symbol.toStringTag, { value: "NotABlob" });
+ assert_equals(Object.prototype.toString.call(Blob.prototype), "[object NotABlob]", "prototype");
+ assert_equals(Object.prototype.toString.call(new Blob()), "[object NotABlob]", "instance");
+}, "Object.prototype.toString applied after modifying the prototype's @@toStringTag");
+
+test(t => {
+ const instance = new Blob();
+ assert_not_own_property(instance, Symbol.toStringTag, "Precondition for this test: no @@toStringTag on the instance");
+
+ Object.defineProperty(instance, Symbol.toStringTag, { value: "NotABlob" });
+ assert_equals(Object.prototype.toString.call(instance), "[object NotABlob]");
+}, "Object.prototype.toString applied to the instance after modifying the instance's @@toStringTag");
+
+// Chrome had a bug (https://bugs.chromium.org/p/chromium/issues/detail?id=793406) where if there
+// was no @@toStringTag in the prototype, it would fall back to a magic class string. This tests
+// that the bug is fixed.
+
+test(() => {
+ const instance = new Blob();
+ Object.setPrototypeOf(instance, null);
+
+ assert_equals(Object.prototype.toString.call(instance), "[object Object]");
+}, "Object.prototype.toString applied to a null-prototype instance");
+
+// This test must be last.
+test(() => {
+ delete Blob.prototype[Symbol.toStringTag];
+
+ assert_equals(Object.prototype.toString.call(Blob.prototype), "[object Object]", "prototype");
+ assert_equals(Object.prototype.toString.call(new Blob()), "[object Object]", "instance");
+}, "Object.prototype.toString applied after deleting @@toStringTag");
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/class-string-iterator-prototype-object.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/class-string-iterator-prototype-object.any.js
new file mode 100644
index 0000000000..5ca549d69c
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/class-string-iterator-prototype-object.any.js
@@ -0,0 +1,51 @@
+"use strict";
+
+const iteratorProto = Object.getPrototypeOf((new URLSearchParams()).entries());
+
+test(() => {
+ assert_own_property(iteratorProto, Symbol.toStringTag);
+
+ const propDesc = Object.getOwnPropertyDescriptor(iteratorProto, Symbol.toStringTag);
+ assert_equals(propDesc.value, "URLSearchParams Iterator", "value");
+ assert_equals(propDesc.configurable, true, "configurable");
+ assert_equals(propDesc.enumerable, false, "enumerable");
+ assert_equals(propDesc.writable, false, "writable");
+}, "@@toStringTag exists with the appropriate descriptor");
+
+test(() => {
+ assert_equals(Object.prototype.toString.call(iteratorProto), "[object URLSearchParams Iterator]");
+}, "Object.prototype.toString");
+
+test(t => {
+ assert_own_property(iteratorProto, Symbol.toStringTag, "Precondition for this test: @@toStringTag exists");
+
+ t.add_cleanup(() => {
+ Object.defineProperty(iteratorProto, Symbol.toStringTag, { value: "URLSearchParams Iterator" });
+ });
+
+ Object.defineProperty(iteratorProto, Symbol.toStringTag, { value: "Not URLSearchParams Iterator" });
+ assert_equals(Object.prototype.toString.call(iteratorProto), "[object Not URLSearchParams Iterator]");
+}, "Object.prototype.toString applied after modifying @@toStringTag");
+
+// Chrome had a bug (https://bugs.chromium.org/p/chromium/issues/detail?id=793406) where if there
+// was no @@toStringTag, it would fall back to a magic class string. This tests that the bug is
+// fixed.
+
+test(() => {
+ const iterator = (new URLSearchParams()).keys();
+ assert_equals(Object.prototype.toString.call(iterator), "[object URLSearchParams Iterator]");
+
+ Object.setPrototypeOf(iterator, null);
+ assert_equals(Object.prototype.toString.call(iterator), "[object Object]");
+}, "Object.prototype.toString applied to a null-prototype instance");
+
+test(t => {
+ const proto = Object.getPrototypeOf(iteratorProto);
+ t.add_cleanup(() => {
+ Object.setPrototypeOf(iteratorProto, proto);
+ });
+
+ Object.setPrototypeOf(iteratorProto, null);
+
+ assert_equals(Object.prototype.toString.call(iteratorProto), "[object URLSearchParams Iterator]");
+}, "Object.prototype.toString applied after nulling the prototype");
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/class-string-named-properties-object.window.js b/testing/web-platform/tests/webidl/ecmascript-binding/class-string-named-properties-object.window.js
new file mode 100644
index 0000000000..a427a2f814
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/class-string-named-properties-object.window.js
@@ -0,0 +1,23 @@
+"use strict";
+
+const namedPropertiesObject = Object.getPrototypeOf(Window.prototype);
+
+test(() => {
+ assert_own_property(namedPropertiesObject, Symbol.toStringTag);
+
+ const propDesc = Object.getOwnPropertyDescriptor(namedPropertiesObject, Symbol.toStringTag);
+ assert_equals(propDesc.value, "WindowProperties", "value");
+ assert_equals(propDesc.configurable, true, "configurable");
+ assert_equals(propDesc.enumerable, false, "enumerable");
+ assert_equals(propDesc.writable, false, "writable");
+}, "@@toStringTag exists with the appropriate descriptor");
+
+test(() => {
+ assert_equals(Object.prototype.toString.call(namedPropertiesObject), "[object WindowProperties]");
+}, "Object.prototype.toString");
+
+// Chrome had a bug (https://bugs.chromium.org/p/chromium/issues/detail?id=793406) where if there
+// was no @@toStringTag, it would fall back to a magic class string. Tests for this are present in
+// the sibling class-string*.any.js tests. However, the named properties object always fails calls
+// to [[DefineOwnProperty]] or [[SetPrototypeOf]] per the Web IDL spec, so there is no way to
+// trigger the buggy behavior for it.
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/constructors.html b/testing/web-platform/tests/webidl/ecmascript-binding/constructors.html
new file mode 100644
index 0000000000..61993a6200
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/constructors.html
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Realm for constructed objects</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+function object_realm(dp) {
+ // Note that browsers use the URL of the relevant global object's associated
+ // Document.
+ // https://github.com/w3c/DOM-Parsing/issues/46
+ var url = DOMParser.prototype.parseFromString.call(dp, "x", "text/html").documentURI;
+ var file = url.slice(url.lastIndexOf("/") + 1);
+ switch (file) {
+ case "constructors.html":
+ return "parent window";
+ case "constructors-support.html":
+ return "child window";
+ default:
+ return "???";
+ }
+}
+
+async_test(function() {
+ var iframe = document.createElement("iframe");
+ iframe.onload = this.step_func_done(function() {
+ var child = iframe.contentWindow;
+ test(function() {
+ var dp = new DOMParser();
+ assert_equals(Object.getPrototypeOf(dp), DOMParser.prototype);
+ assert_equals(object_realm(dp), "parent window");
+ }, "Normal constructor in parent window");
+
+ test(function() {
+ var dp = new child.DOMParser();
+ assert_equals(Object.getPrototypeOf(dp), child.DOMParser.prototype);
+ assert_equals(object_realm(dp), "child window");
+ }, "Normal constructor in child window");
+
+ test(function() {
+ var dp = Reflect.construct(child.DOMParser, [], DOMParser);
+ assert_equals(Object.getPrototypeOf(dp), DOMParser.prototype);
+ assert_equals(object_realm(dp), "child window");
+ }, "Constructor in child window with normal NewTarget from parent window");
+
+ test(function() {
+ var dp = Reflect.construct(DOMParser, [], child.DOMParser);
+ assert_equals(Object.getPrototypeOf(dp), child.DOMParser.prototype);
+ assert_equals(object_realm(dp), "parent window");
+ }, "Constructor in parent window with normal NewTarget from child window");
+
+ test(function() {
+ class DOMParserSubclass extends DOMParser {}
+ var dp = new DOMParserSubclass();
+ assert_equals(Object.getPrototypeOf(dp), DOMParserSubclass.prototype);
+ assert_equals(object_realm(dp), "parent window");
+ }, "Subclass constructor in parent window");
+
+ test(function() {
+ var dp = new child.DOMParserSubclass();
+ assert_equals(Object.getPrototypeOf(dp), child.DOMParserSubclass.prototype);
+ assert_equals(object_realm(dp), "child window");
+ }, "Subclass constructor in child window");
+
+ test(function() {
+ class ForeignDOMParserSubclass extends child.DOMParser {}
+ var dp = new ForeignDOMParserSubclass();
+ assert_equals(Object.getPrototypeOf(dp), ForeignDOMParserSubclass.prototype);
+ assert_equals(object_realm(dp), "child window");
+ }, "Subclass constructor in parent window with parent class in child window");
+
+ test(function() {
+ var dp = new child.ForeignDOMParserSubclass();
+ assert_equals(Object.getPrototypeOf(dp), child.ForeignDOMParserSubclass.prototype);
+ assert_equals(object_realm(dp), "parent window");
+ }, "Subclass constructor in child window with parent class in parent window");
+
+ test(function() {
+ var badNewTarget = function() {};
+ badNewTarget.prototype = 7;
+
+ var dp = Reflect.construct(child.DOMParser, [], badNewTarget);
+ assert_equals(Object.getPrototypeOf(dp), DOMParser.prototype);
+ assert_equals(object_realm(dp), "child window");
+ }, "Constructor in child window with bad NewTarget from parent window");
+
+ test(function() {
+ var dp = Reflect.construct(DOMParser, [], child.badNewTarget);
+ assert_equals(Object.getPrototypeOf(dp), child.DOMParser.prototype);
+ assert_equals(object_realm(dp), "parent window");
+ }, "Constructor in parent window with bad NewTarget from child window");
+
+ test(function() {
+ var badNewTarget = Function.prototype.bind.call(new child.Function());
+ badNewTarget.prototype = 7;
+
+ var dp = Reflect.construct(DOMParser, [], badNewTarget);
+ assert_equals(Object.getPrototypeOf(dp), child.DOMParser.prototype);
+ assert_equals(object_realm(dp), "parent window");
+ }, "Constructor in parent window with bad NewTarget from parent window that's a bound child window function");
+
+ test(function() {
+ var badNewTarget = child.Function.prototype.bind.call(new Function());
+ badNewTarget.prototype = 7;
+
+ var dp = Reflect.construct(child.DOMParser, [], badNewTarget);
+ assert_equals(Object.getPrototypeOf(dp), DOMParser.prototype);
+ assert_equals(object_realm(dp), "child window");
+ }, "Constructor in child window with bad NewTarget from child window that's a bound parent window function");
+
+ test(function() {
+ var badNewTarget = new Proxy(new child.Function(), {});
+ badNewTarget.prototype = 7;
+
+ var dp = Reflect.construct(DOMParser, [], badNewTarget);
+ assert_equals(Object.getPrototypeOf(dp), child.DOMParser.prototype);
+ assert_equals(object_realm(dp), "parent window");
+ }, "Constructor in parent window with bad NewTarget from parent window that's a proxy for a child window function");
+
+ test(function() {
+ var badNewTarget = new child.Proxy(new Function(), {});
+ badNewTarget.prototype = 7;
+
+ var dp = Reflect.construct(child.DOMParser, [], badNewTarget);
+ assert_equals(Object.getPrototypeOf(dp), DOMParser.prototype);
+ assert_equals(object_realm(dp), "child window");
+ }, "Constructor in child window with bad NewTarget from child window that's a proxy for a parent window function");
+ });
+ iframe.src = "support/constructors-support.html";
+ document.body.appendChild(iframe);
+});
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/default-iterator-object.html b/testing/web-platform/tests/webidl/ecmascript-binding/default-iterator-object.html
new file mode 100644
index 0000000000..c7e9188521
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/default-iterator-object.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Default iterator objects</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+ const iterator1 = new URLSearchParams()[Symbol.iterator]();
+ const iterator2 = new URLSearchParams().keys();
+ const iterator3 = new URLSearchParams().values();
+ const iterator4 = new URLSearchParams().entries();
+ assert_equals(Object.getPrototypeOf(iterator1), Object.getPrototypeOf(iterator2));
+ assert_equals(Object.getPrototypeOf(iterator1), Object.getPrototypeOf(iterator3));
+ assert_equals(Object.getPrototypeOf(iterator1), Object.getPrototypeOf(iterator4));
+}, "Default iterator objects for an interface have the same prototype");
+
+test(() => {
+ const iterator = new URLSearchParams().entries();
+ assert_equals(Object.prototype.toString.call(iterator), "[object URLSearchParams Iterator]");
+}, "Object.prototype.toString returns correct value");
+
+test(() => {
+ const iterator = new URLSearchParams().entries();
+ assert_equals(iterator[Symbol.toStringTag], "URLSearchParams Iterator");
+ assert_equals(Object.getOwnPropertyDescriptor(iterator, Symbol.toStringTag), undefined);
+}, "@@toStringTag has correct value from prototype");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/default-toJSON-cross-realm.html b/testing/web-platform/tests/webidl/ecmascript-binding/default-toJSON-cross-realm.html
new file mode 100644
index 0000000000..79c3097f33
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/default-toJSON-cross-realm.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Cross-realm [Default] toJSON() creates result object in its realm</title>
+<link rel="help" href="https://webidl.spec.whatwg.org/#default-tojson-steps">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/create-realm.js"></script>
+
+<body>
+<script>
+promise_test(async t => {
+ const other = await createRealm(t);
+ const json = other.DOMRectReadOnly.prototype.toJSON.call(new DOMRectReadOnly());
+
+ assert_equals(Object.getPrototypeOf(json), other.Object.prototype);
+}, "Cross-realm [Default] toJSON() creates result object in its realm");
+
+promise_test(async t => {
+ const other = await createRealm(t);
+ const json = other.DOMQuad.prototype.toJSON.call(new DOMQuad());
+
+ assert_equals(Object.getPrototypeOf(json.p1), DOMPoint.prototype);
+}, "Cross-realm [Default] toJSON() converts its interface attributes to ECMAScript values of correct realm");
+</script>
+</body>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constants.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constants.any.js
new file mode 100644
index 0000000000..bb846a494e
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constants.any.js
@@ -0,0 +1,51 @@
+'use strict';
+
+test(function() {
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27732
+ var constants = [
+ "INDEX_SIZE_ERR",
+ "DOMSTRING_SIZE_ERR",
+ "HIERARCHY_REQUEST_ERR",
+ "WRONG_DOCUMENT_ERR",
+ "INVALID_CHARACTER_ERR",
+ "NO_DATA_ALLOWED_ERR",
+ "NO_MODIFICATION_ALLOWED_ERR",
+ "NOT_FOUND_ERR",
+ "NOT_SUPPORTED_ERR",
+ "INUSE_ATTRIBUTE_ERR",
+ "INVALID_STATE_ERR",
+ "SYNTAX_ERR",
+ "INVALID_MODIFICATION_ERR",
+ "NAMESPACE_ERR",
+ "INVALID_ACCESS_ERR",
+ "VALIDATION_ERR",
+ "TYPE_MISMATCH_ERR",
+ "SECURITY_ERR",
+ "NETWORK_ERR",
+ "ABORT_ERR",
+ "URL_MISMATCH_ERR",
+ "QUOTA_EXCEEDED_ERR",
+ "TIMEOUT_ERR",
+ "INVALID_NODE_TYPE_ERR",
+ "DATA_CLONE_ERR"
+ ]
+ var objects = [
+ [DOMException, "DOMException constructor object"],
+ [DOMException.prototype, "DOMException prototype object"]
+ ]
+ constants.forEach(function(name, i) {
+ objects.forEach(function(o) {
+ var object = o[0], description = o[1];
+ test(function() {
+ assert_equals(object[name], i + 1, name)
+ assert_own_property(object, name)
+ var pd = Object.getOwnPropertyDescriptor(object, name)
+ assert_false("get" in pd, "get")
+ assert_false("set" in pd, "set")
+ assert_false(pd.writable, "writable")
+ assert_true(pd.enumerable, "enumerable")
+ assert_false(pd.configurable, "configurable")
+ }, "Constant " + name + " on " + description)
+ })
+ })
+})
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constructor-and-prototype.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constructor-and-prototype.any.js
new file mode 100644
index 0000000000..a015470cad
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constructor-and-prototype.any.js
@@ -0,0 +1,32 @@
+test(function() {
+ assert_own_property(self, "DOMException", "property of global");
+
+ var desc = Object.getOwnPropertyDescriptor(self, "DOMException");
+ assert_false("get" in desc, "get");
+ assert_false("set" in desc, "set");
+ assert_true(desc.writable, "writable");
+ assert_false(desc.enumerable, "enumerable");
+ assert_true(desc.configurable, "configurable");
+}, "existence and property descriptor of DOMException");
+
+test(function() {
+ assert_own_property(self.DOMException, "prototype", "prototype property");
+
+ var desc = Object.getOwnPropertyDescriptor(self.DOMException, "prototype");
+ assert_false("get" in desc, "get");
+ assert_false("set" in desc, "set");
+ assert_false(desc.writable, "writable");
+ assert_false(desc.enumerable, "enumerable");
+ assert_false(desc.configurable, "configurable");
+}, "existence and property descriptor of DOMException.prototype");
+
+test(function() {
+ assert_own_property(self.DOMException.prototype, "constructor", "property of prototype");
+ var desc = Object.getOwnPropertyDescriptor(self.DOMException.prototype, "constructor");
+ assert_false("get" in desc, "get");
+ assert_false("set" in desc, "set");
+ assert_true(desc.writable, "writable");
+ assert_false(desc.enumerable, "enumerable");
+ assert_true(desc.configurable, "configurable");
+ assert_equals(self.DOMException.prototype.constructor, self.DOMException, "equality with actual constructor");
+}, "existence and property descriptor of DOMException.prototype.constructor");
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constructor-behavior.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constructor-behavior.any.js
new file mode 100644
index 0000000000..e9917af228
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-constructor-behavior.any.js
@@ -0,0 +1,140 @@
+'use strict';
+
+test(function() {
+ var ex = new DOMException();
+ assert_equals(ex.name, "Error",
+ "Not passing a name should end up with 'Error' as the name");
+ assert_equals(ex.message, "",
+ "Not passing a message should end up with empty string as the message");
+}, 'new DOMException()');
+
+test(function() {
+ var ex = new DOMException();
+ assert_false(ex.hasOwnProperty("name"),
+ "The name property should be inherited");
+ assert_false(ex.hasOwnProperty("message"),
+ "The message property should be inherited");
+}, 'new DOMException(): inherited-ness');
+
+test(function() {
+ var ex = new DOMException(null);
+ assert_equals(ex.name, "Error",
+ "Not passing a name should end up with 'Error' as the name");
+ assert_equals(ex.message, "null",
+ "Passing null as message should end up with stringified 'null' as the message");
+}, 'new DOMException(null)');
+
+test(function() {
+ var ex = new DOMException(undefined);
+ assert_equals(ex.name, "Error",
+ "Not passing a name should end up with 'Error' as the name");
+ assert_equals(ex.message, "",
+ "Not passing a message should end up with empty string as the message");
+}, 'new DOMException(undefined)');
+
+test(function() {
+ var ex = new DOMException(undefined);
+ assert_false(ex.hasOwnProperty("name"),
+ "The name property should be inherited");
+ assert_false(ex.hasOwnProperty("message"),
+ "The message property should be inherited");
+}, 'new DOMException(undefined): inherited-ness');
+
+test(function() {
+ var ex = new DOMException("foo");
+ assert_equals(ex.name, "Error",
+ "Not passing a name should still end up with 'Error' as the name");
+ assert_equals(ex.message, "foo", "Should be using passed-in message");
+}, 'new DOMException("foo")');
+
+test(function() {
+ var ex = new DOMException("foo");
+ assert_false(ex.hasOwnProperty("name"),
+ "The name property should be inherited");
+ assert_false(ex.hasOwnProperty("message"),
+ "The message property should be inherited");
+}, 'new DOMException("foo"): inherited-ness');
+
+test(function() {
+ var ex = new DOMException("bar", undefined);
+ assert_equals(ex.name, "Error",
+ "Passing undefined for name should end up with 'Error' as the name");
+ assert_equals(ex.message, "bar", "Should still be using passed-in message");
+}, 'new DOMException("bar", undefined)');
+
+test(function() {
+ var ex = new DOMException("bar", "NotSupportedError");
+ assert_equals(ex.name, "NotSupportedError", "Should be using the passed-in name");
+ assert_equals(ex.message, "bar", "Should still be using passed-in message");
+ assert_equals(ex.code, DOMException.NOT_SUPPORTED_ERR,
+ "Should have the right exception code");
+}, 'new DOMException("bar", "NotSupportedError")');
+
+test(function() {
+ var ex = new DOMException("bar", "NotSupportedError");
+ assert_false(ex.hasOwnProperty("name"),
+ "The name property should be inherited");
+ assert_false(ex.hasOwnProperty("message"),
+ "The message property should be inherited");
+}, 'new DOMException("bar", "NotSupportedError"): inherited-ness');
+
+test(function() {
+ var ex = new DOMException("bar", "foo");
+ assert_equals(ex.name, "foo", "Should be using the passed-in name");
+ assert_equals(ex.message, "bar", "Should still be using passed-in message");
+ assert_equals(ex.code, 0,
+ "Should have 0 for code for a name not in the exception names table");
+}, 'new DOMException("bar", "foo")');
+
+[
+ {name: "IndexSizeError", code: 1},
+ {name: "HierarchyRequestError", code: 3},
+ {name: "WrongDocumentError", code: 4},
+ {name: "InvalidCharacterError", code: 5},
+ {name: "NoModificationAllowedError", code: 7},
+ {name: "NotFoundError", code: 8},
+ {name: "NotSupportedError", code: 9},
+ {name: "InUseAttributeError", code: 10},
+ {name: "InvalidStateError", code: 11},
+ {name: "SyntaxError", code: 12},
+ {name: "InvalidModificationError", code: 13},
+ {name: "NamespaceError", code: 14},
+ {name: "InvalidAccessError", code: 15},
+ {name: "TypeMismatchError", code: 17},
+ {name: "SecurityError", code: 18},
+ {name: "NetworkError", code: 19},
+ {name: "AbortError", code: 20},
+ {name: "URLMismatchError", code: 21},
+ {name: "QuotaExceededError", code: 22},
+ {name: "TimeoutError", code: 23},
+ {name: "InvalidNodeTypeError", code: 24},
+ {name: "DataCloneError", code: 25},
+
+ // These were removed from the error names table.
+ // See https://github.com/heycam/webidl/pull/946.
+ {name: "DOMStringSizeError", code: 0},
+ {name: "NoDataAllowedError", code: 0},
+ {name: "ValidationError", code: 0},
+
+ // The error names which don't have legacy code values.
+ {name: "EncodingError", code: 0},
+ {name: "NotReadableError", code: 0},
+ {name: "UnknownError", code: 0},
+ {name: "ConstraintError", code: 0},
+ {name: "DataError", code: 0},
+ {name: "TransactionInactiveError", code: 0},
+ {name: "ReadOnlyError", code: 0},
+ {name: "VersionError", code: 0},
+ {name: "OperationError", code: 0},
+ {name: "NotAllowedError", code: 0}
+].forEach(function(test_case) {
+ test(function() {
+ var ex = new DOMException("msg", test_case.name);
+ assert_equals(ex.name, test_case.name,
+ "Should be using the passed-in name");
+ assert_equals(ex.message, "msg",
+ "Should be using the passed-in message");
+ assert_equals(ex.code, test_case.code,
+ "Should have matching legacy code from error names table");
+ },'new DOMexception("msg", "' + test_case.name + '")');
+});
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-custom-bindings.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-custom-bindings.any.js
new file mode 100644
index 0000000000..cd4e5b6341
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/DOMException-custom-bindings.any.js
@@ -0,0 +1,120 @@
+"use strict";
+
+test(() => {
+ assert_throws_js(TypeError, () => DOMException());
+}, "Cannot construct without new");
+
+test(() => {
+ assert_equals(Object.getPrototypeOf(DOMException.prototype), Error.prototype);
+}, "inherits from Error: prototype-side");
+
+test(() => {
+ assert_equals(Object.getPrototypeOf(DOMException), Function.prototype);
+}, "does not inherit from Error: class-side");
+
+test(() => {
+ const e = new DOMException("message", "name");
+ assert_false(e.hasOwnProperty("message"), "property is not own");
+
+ const propDesc = Object.getOwnPropertyDescriptor(DOMException.prototype, "message");
+ assert_equals(typeof propDesc.get, "function", "property descriptor is a getter");
+ assert_equals(propDesc.set, undefined, "property descriptor is not a setter");
+ assert_true(propDesc.enumerable, "property descriptor enumerable");
+ assert_true(propDesc.configurable, "property descriptor configurable");
+}, "message property descriptor");
+
+test(() => {
+ const getter = Object.getOwnPropertyDescriptor(DOMException.prototype, "message").get;
+
+ assert_throws_js(TypeError, () => getter.apply({}));
+}, "message getter performs brand checks (i.e. is not [LegacyLenientThis])");
+
+test(() => {
+ const e = new DOMException("message", "name");
+ assert_false(e.hasOwnProperty("name"), "property is not own");
+
+ const propDesc = Object.getOwnPropertyDescriptor(DOMException.prototype, "name");
+ assert_equals(typeof propDesc.get, "function", "property descriptor is a getter");
+ assert_equals(propDesc.set, undefined, "property descriptor is not a setter");
+ assert_true(propDesc.enumerable, "property descriptor enumerable");
+ assert_true(propDesc.configurable, "property descriptor configurable");
+}, "name property descriptor");
+
+test(() => {
+ const getter = Object.getOwnPropertyDescriptor(DOMException.prototype, "name").get;
+
+ assert_throws_js(TypeError, () => getter.apply({}));
+}, "name getter performs brand checks (i.e. is not [LegacyLenientThis])");
+
+test(() => {
+ const e = new DOMException("message", "name");
+ assert_false(e.hasOwnProperty("code"), "property is not own");
+
+ const propDesc = Object.getOwnPropertyDescriptor(DOMException.prototype, "code");
+ assert_equals(typeof propDesc.get, "function", "property descriptor is a getter");
+ assert_equals(propDesc.set, undefined, "property descriptor is not a setter");
+ assert_true(propDesc.enumerable, "property descriptor enumerable");
+ assert_true(propDesc.configurable, "property descriptor configurable");
+}, "code property descriptor");
+
+test(() => {
+ const getter = Object.getOwnPropertyDescriptor(DOMException.prototype, "code").get;
+
+ assert_throws_js(TypeError, () => getter.apply({}));
+}, "code getter performs brand checks (i.e. is not [LegacyLenientThis])");
+
+test(() => {
+ const e = new DOMException("message", "InvalidCharacterError");
+ assert_equals(e.code, 5, "Initially the code is set to 5");
+
+ Object.defineProperty(e, "name", {
+ value: "WrongDocumentError"
+ });
+
+ assert_equals(e.code, 5, "The code is still set to 5");
+}, "code property is not affected by shadowing the name property");
+
+test(() => {
+ const e = new DOMException("message", "name");
+ assert_equals(Object.prototype.toString.call(e), "[object DOMException]");
+}, "Object.prototype.toString behavior is like other interfaces");
+
+test(() => {
+ const e = new DOMException("message", "name");
+ assert_false(e.hasOwnProperty("toString"), "toString must not exist on the instance");
+ assert_false(DOMException.prototype.hasOwnProperty("toString"), "toString must not exist on DOMException.prototype");
+ assert_equals(typeof e.toString, "function", "toString must still exist (via Error.prototype)");
+}, "Inherits its toString() from Error.prototype");
+
+test(() => {
+ const e = new DOMException("message", "name");
+ assert_equals(e.toString(), "name: message",
+ "The default Error.prototype.toString() behavior must work on supplied name and message");
+
+ Object.defineProperty(e, "name", { value: "new name" });
+ Object.defineProperty(e, "message", { value: "new message" });
+ assert_equals(e.toString(), "new name: new message",
+ "The default Error.prototype.toString() behavior must work on shadowed names and messages");
+}, "toString() behavior from Error.prototype applies as expected");
+
+test(() => {
+ assert_throws_js(TypeError, () => DOMException.prototype.toString());
+}, "DOMException.prototype.toString() applied to DOMException.prototype throws because of name/message brand checks");
+
+test(() => {
+ let stackOnNormalErrors;
+ try {
+ throw new Error("normal error");
+ } catch (e) {
+ stackOnNormalErrors = e.stack;
+ }
+
+ let stackOnDOMException;
+ try {
+ throw new DOMException("message", "name");
+ } catch (e) {
+ stackOnDOMException = e.stack;
+ }
+
+ assert_equals(typeof stackOnDOMException, typeof stackOnNormalErrors, "The typeof values must match");
+}, "If the implementation has a stack property on normal errors, it also does on DOMExceptions");
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/exceptions.html b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/exceptions.html
new file mode 100644
index 0000000000..d26c662669
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/es-exceptions/exceptions.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>DOMException-throwing tests</title>
+<link rel=author title="Aryeh Gregor" href=ayg@aryeh.name>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+/**
+ * This file just picks one case where browsers are supposed to throw an
+ * exception, and tests the heck out of whether it meets the spec. In the
+ * future, all these checks should be in assert_throws_dom(), but we don't want
+ * every browser failing every assert_throws_dom() check until they fix every
+ * single bug in their exception-throwing.
+ *
+ * We don't go out of our way to test everything that's already tested by
+ * interfaces.html, like whether all constants are present on the object, but
+ * some things are duplicated.
+ */
+setup({explicit_done: true});
+
+function testException(exception, global, desc) {
+ test(function() {
+ assert_equals(global.Object.getPrototypeOf(exception),
+ global.DOMException.prototype);
+ }, desc + "Object.getPrototypeOf(exception) === DOMException.prototype");
+
+
+ test(function() {
+ assert_false(exception.hasOwnProperty("name"));
+ }, desc + "exception.hasOwnProperty(\"name\")");
+ test(function() {
+ assert_false(exception.hasOwnProperty("message"));
+ }, desc + "exception.hasOwnProperty(\"message\")");
+
+ test(function() {
+ assert_equals(exception.name, "HierarchyRequestError");
+ }, desc + "exception.name === \"HierarchyRequestError\"");
+
+ test(function() {
+ assert_equals(exception.code, global.DOMException.HIERARCHY_REQUEST_ERR);
+ }, desc + "exception.code === DOMException.HIERARCHY_REQUEST_ERR");
+
+ test(function() {
+ assert_equals(global.Object.prototype.toString.call(exception),
+ "[object DOMException]");
+ }, desc + "Object.prototype.toString.call(exception) === \"[object DOMException]\"");
+}
+
+
+// Test in current window
+var exception = null;
+try {
+ // This should throw a HierarchyRequestError in every browser since the
+ // Stone Age, so we're really only testing exception-throwing details.
+ document.documentElement.appendChild(document);
+} catch(e) {
+ exception = e;
+}
+testException(exception, window, "");
+
+// Test in iframe
+var iframe = document.createElement("iframe");
+iframe.src = "about:blank";
+iframe.onload = function() {
+ var exception = null;
+ try {
+ iframe.contentDocument.documentElement.appendChild(iframe.contentDocument);
+ } catch(e) {
+ exception = e;
+ }
+ testException(exception, iframe.contentWindow, "In iframe: ");
+
+ document.body.removeChild(iframe);
+ done();
+};
+document.body.appendChild(iframe);
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/global-immutable-prototype.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/global-immutable-prototype.any.js
new file mode 100644
index 0000000000..6291c3ae93
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/global-immutable-prototype.any.js
@@ -0,0 +1,25 @@
+// META: global=window,worker
+// META: title=Immutability of the global prototype chain
+
+const objects = [];
+setup(() => {
+ for (let object = self; object; object = Object.getPrototypeOf(object)) {
+ objects.push(object);
+ }
+});
+
+test(() => {
+ for (const object of objects) {
+ assert_throws_js(TypeError, () => {
+ Object.setPrototypeOf(object, {});
+ });
+ }
+}, "Setting to a different prototype");
+
+test(() => {
+ for (const object of objects) {
+ const expected = Object.getPrototypeOf(object);
+ Object.setPrototypeOf(object, expected);
+ assert_equals(Object.getPrototypeOf(object), expected);
+ }
+}, "Setting to the same prototype");
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/global-mutable-prototype.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/global-mutable-prototype.any.js
new file mode 100644
index 0000000000..eba96e9adf
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/global-mutable-prototype.any.js
@@ -0,0 +1,27 @@
+// META: global=shadowrealm
+// META: title=Mutability of the global prototype chain
+
+const objects = [];
+setup(() => {
+ for (let object = self; object; object = Object.getPrototypeOf(object)) {
+ objects.push(object);
+ }
+});
+
+test(() => {
+ for (const object of objects) {
+ const proto = Object.getPrototypeOf(object);
+ const plainObject = {};
+ Object.setPrototypeOf(object, plainObject);
+ assert_equals(Object.getPrototypeOf(object), plainObject);
+ Object.setPrototypeOf(object, proto);
+ }
+}, "Setting to a different prototype");
+
+test(() => {
+ for (const object of objects) {
+ const expected = Object.getPrototypeOf(object);
+ Object.setPrototypeOf(object, expected);
+ assert_equals(Object.getPrototypeOf(object), expected);
+ }
+}, "Setting to the same prototype");
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/global-object-implicit-this-value-cross-realm.html b/testing/web-platform/tests/webidl/ecmascript-binding/global-object-implicit-this-value-cross-realm.html
new file mode 100644
index 0000000000..b9939b801c
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/global-object-implicit-this-value-cross-realm.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Cross-realm getter / setter / operation doesn't use lexical global object if |this| value is incompatible object / null / undefined</title>
+<link rel="help" href="https://webidl.spec.whatwg.org/#dfn-attribute-getter">
+<link rel="help" href="https://webidl.spec.whatwg.org/#dfn-attribute-setter">
+<link rel="help" href="https://webidl.spec.whatwg.org/#dfn-create-operation-function">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/create-realm.js"></script>
+
+<body>
+<script>
+promise_test(async t => {
+ const other = await createRealm(t);
+ const notWindow = Object.create(Object.getPrototypeOf(other));
+
+ assert_throws_js(other.TypeError, () => { Object.create(other).window; });
+ assert_throws_js(other.TypeError, () => { Object.getOwnPropertyDescriptor(other, "history").get.call(notWindow); });
+ assert_throws_js(other.TypeError, () => { Reflect.get(other, "screen", notWindow); });
+ assert_throws_js(other.TypeError, () => { new Proxy(other, {}).onclick; });
+}, "Cross-realm global object's getter throws when called on incompatible object");
+
+promise_test(async t => {
+ const other = await createRealm(t);
+ const notWindow = Object.create(Object.getPrototypeOf(other));
+
+ assert_throws_js(other.TypeError, () => { Object.create(other).name = "dummy"; });
+ assert_throws_js(other.TypeError, () => { Object.getOwnPropertyDescriptor(other, "status").set.call(notWindow, other.status); });
+ // parent is [Replaceable]
+ assert_throws_js(other.TypeError, () => { Reflect.set(other, "parent", window, notWindow); });
+ assert_throws_js(other.TypeError, () => { new Proxy(other, {}).location = location; });
+}, "Cross-realm global object's setter throws when called on incompatible object");
+
+promise_test(async t => {
+ const other = await createRealm(t);
+ const notWindow = Object.create(Object.getPrototypeOf(other));
+
+ assert_throws_js(other.TypeError, () => { Object.create(other).focus(); });
+ assert_throws_js(other.TypeError, () => { other.clearInterval.call(notWindow, 0); });
+ assert_throws_js(other.TypeError, () => { Reflect.apply(other.blur, notWindow, []); });
+ assert_throws_js(other.TypeError, () => { new Proxy(other, {}).removeEventListener("foo", () => {}); });
+}, "Cross-realm global object's operation throws when called on incompatible object");
+
+promise_test(async t => {
+ const other = await createRealm(t);
+ const otherNameGetter = Object.getOwnPropertyDescriptor(other, "name").get;
+
+ assert_equals(Reflect.get(other, "self", null), other);
+ assert_equals(Reflect.get(other, "document", undefined), other.document);
+ assert_equals(otherNameGetter.call(null), "dummy");
+ // An engine might have different code path for calling a function from outer scope to implement step 1.b.iii of https://tc39.es/ecma262/#sec-evaluatecall
+ assert_equals((() => otherNameGetter())(), "dummy");
+}, "Cross-realm global object's getter called on null / undefined");
+
+promise_test(async t => {
+ const other = await createRealm(t);
+ const otherLocationSetter = Object.getOwnPropertyDescriptor(other, "location").set;
+ const otherHref = other.location.href;
+ const newSelf = {};
+
+ // self is [Replaceable]
+ assert_true(Reflect.set(other, "self", newSelf, null));
+ assert_true(Reflect.set(other, "name", "newName", undefined));
+
+ otherLocationSetter.call(null, `${otherHref}#foo`);
+ assert_equals(other.location.hash, "#foo");
+ // An engine might have different code path for calling a function from outer scope to implement step 1.b.iii of https://tc39.es/ecma262/#sec-evaluatecall
+ (() => { otherLocationSetter(`${otherHref}#bar`); })();
+ assert_equals(other.location.hash, "#bar");
+
+ // Check these after calling "location" setter make sure no navigation has occurred
+ assert_equals(other.self, newSelf);
+ assert_equals(other.name, "newName");
+}, "Cross-realm global object's setter called on null / undefined");
+
+promise_test(async t => {
+ const other = await createRealm(t);
+ const otherFocus = other.focus;
+ const otherDispatchEvent = other.dispatchEvent;
+
+ assert_equals(document.activeElement, document.body);
+ // An engine might have different code path for calling a function from outer scope to implement step 1.b.iii of https://tc39.es/ecma262/#sec-evaluatecall
+ (() => { otherFocus(); })();
+ assert_equals(document.activeElement.contentWindow, other);
+
+ let caughtEvent;
+ other.addEventListener.call(null, "foo", event => { caughtEvent = event; });
+ const dispatchedEvent = new other.Event("foo");
+ assert_true(otherDispatchEvent(dispatchedEvent));
+ assert_equals(caughtEvent, dispatchedEvent);
+
+ const messagePromise = new EventWatcher(t, other, "message").wait_for("message");
+ other.postMessage.call(null, "foo");
+ assert_equals((await messagePromise).data, "foo");
+}, "Cross-realm global object's operation called on null / undefined");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/global-object-implicit-this-value.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/global-object-implicit-this-value.any.js
new file mode 100644
index 0000000000..4c159c6751
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/global-object-implicit-this-value.any.js
@@ -0,0 +1,85 @@
+// META: global=window,worker
+
+// https://webidl.spec.whatwg.org/#dfn-attribute-getter (step 1.1.2.1)
+// https://webidl.spec.whatwg.org/#dfn-attribute-setter (step 4.5.1)
+// https://webidl.spec.whatwg.org/#dfn-create-operation-function (step 2.1.2.1)
+
+const notGlobalObject = Object.create(Object.getPrototypeOf(globalThis));
+
+test(() => {
+ assert_throws_js(TypeError, () => { Object.create(globalThis).self; });
+ assert_throws_js(TypeError, () => { getGlobalPropertyDescriptor("location").get.call(notGlobalObject); });
+ assert_throws_js(TypeError, () => { Reflect.get(globalThis, "navigator", notGlobalObject); });
+ assert_throws_js(TypeError, () => { new Proxy(globalThis, {}).onerror; });
+}, "Global object's getter throws when called on incompatible object");
+
+test(() => {
+ assert_throws_js(TypeError, () => { Object.create(globalThis).origin = origin; });
+ assert_throws_js(TypeError, () => { getGlobalPropertyDescriptor("onerror").set.call(notGlobalObject, onerror); });
+ assert_throws_js(TypeError, () => { Reflect.set(globalThis, "onoffline", onoffline, notGlobalObject); });
+ assert_throws_js(TypeError, () => { new Proxy(globalThis, {}).ononline = ononline; });
+}, "Global object's setter throws when called on incompatible object");
+
+test(() => {
+ assert_throws_js(TypeError, () => { Object.create(globalThis).setInterval(() => {}, 100); });
+ assert_throws_js(TypeError, () => { clearTimeout.call(notGlobalObject, () => {}); });
+ assert_throws_js(TypeError, () => { Reflect.apply(btoa, notGlobalObject, [""]); });
+ assert_throws_js(TypeError, () => { new Proxy(globalThis, {}).removeEventListener("foo", () => {}); });
+}, "Global object's operation throws when called on incompatible object");
+
+if (typeof document !== "undefined") {
+ test(() => {
+ assert_throws_js(TypeError, () => { Object.getOwnPropertyDescriptor(window, "document").get.call(document.all); });
+ }, "Global object's getter throws when called on incompatible object (document.all)");
+
+ test(() => {
+ assert_throws_js(TypeError, () => { Object.getOwnPropertyDescriptor(window, "name").set.call(document.all); });
+ }, "Global object's setter throws when called on incompatible object (document.all)");
+
+ test(() => {
+ assert_throws_js(TypeError, () => { focus.call(document.all); });
+ }, "Global object's operation throws when called on incompatible object (document.all)");
+}
+
+// An engine might have different code path for calling a function from outer scope to implement step 1.b.iii of https://tc39.es/ecma262/#sec-evaluatecall
+const locationGetter = getGlobalPropertyDescriptor("location").get;
+test(() => {
+ assert_equals(getGlobalPropertyDescriptor("self").get.call(null), self);
+ assert_equals((() => locationGetter())(), location);
+ assert_equals(Reflect.get(globalThis, "origin", null), origin);
+ assert_equals(Reflect.get(globalThis, "onoffline", undefined), onoffline);
+}, "Global object's getter works when called on null / undefined");
+
+test(() => {
+ const fn = () => {};
+
+ // origin is [Replaceable]
+ getGlobalPropertyDescriptor("origin").set.call(null, "foo");
+ assert_equals(origin, "foo");
+ getGlobalPropertyDescriptor("onerror").set.call(undefined, fn);
+ assert_equals(onerror, fn);
+ assert_true(Reflect.set(globalThis, "onoffline", fn, null));
+ assert_equals(onoffline, fn);
+
+ const ononlineSetter = getGlobalPropertyDescriptor("ononline").set;
+ (() => { ononlineSetter(fn); })();
+ assert_equals(ononline, fn);
+}, "Global object's setter works when called on null / undefined");
+
+// An engine might have different code path for calling a function from outer scope to implement step 1.b.iii of https://tc39.es/ecma262/#sec-evaluatecall
+const __addEventListener = addEventListener;
+test(() => {
+ assert_equals(atob.call(null, ""), "");
+ assert_equals(typeof (0, setInterval)(() => {}, 100), "number");
+
+ (() => { __addEventListener("foo", event => { event.preventDefault(); }); })();
+ const __dispatchEvent = dispatchEvent;
+ (() => { assert_false(__dispatchEvent(new Event("foo", { cancelable: true }))); })();
+}, "Global object's operation works when called on null / undefined");
+
+function getGlobalPropertyDescriptor(key) {
+ for (let obj = globalThis; obj; obj = Object.getPrototypeOf(obj)) {
+ const desc = Object.getOwnPropertyDescriptor(obj, key);
+ if (desc) return desc;
+ }
+}
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/has-instance.html b/testing/web-platform/tests/webidl/ecmascript-binding/has-instance.html
new file mode 100644
index 0000000000..caf0be4729
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/has-instance.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>instanceof behavior</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe></iframe>
+
+<script>
+test(function() {
+ var obj = Object.create(Element.prototype);
+ assert_true(obj instanceof Element);
+ assert_true(obj instanceof Node);
+ assert_false(obj instanceof Attr);
+}, "Manually-constructed prototype chains are correctly handled by instanceof");
+
+test(() => {
+ // This tests that the historical override of [[HasInstance]] was removed:
+ // https://github.com/heycam/webidl/pull/356
+ assert_false(document.body instanceof frames[0].Element);
+}, "instanceof must return false across different globals, for platform objects");
+
+test(() => {
+ assert_false(EventTarget.hasOwnProperty(Symbol.hasInstance));
+}, "platform objects do not have Symbol.hasInstance installed");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/interface-object-set-receiver.html b/testing/web-platform/tests/webidl/ecmascript-binding/interface-object-set-receiver.html
new file mode 100644
index 0000000000..ca75a96bba
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/interface-object-set-receiver.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.Interface is defined on [[Set]] receiver</title>
+<link rel="help" href="https://webidl.spec.whatwg.org/#define-the-global-property-references">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+const testValue = Object.freeze(function() {});
+
+test(() => {
+ window.Window = testValue;
+ assert_false(window.propertyIsEnumerable("Window"));
+
+ window.History = testValue;
+ assert_false(window.propertyIsEnumerable("History"));
+
+ window.HTMLDivElement = testValue;
+ assert_false(window.propertyIsEnumerable("HTMLDivElement"))
+}, "Direct [[Set]] preserves [[Enumerable]]: false property attribute");
+
+test(() => {
+ const heir = Object.create(window);
+
+ heir.Location = testValue;
+ assert_equals(heir.Location, testValue);
+ assert_not_equals(window.Location, testValue);
+
+ heir.HTMLDocument = testValue;
+ assert_equals(heir.HTMLDocument, testValue);
+ assert_not_equals(window.HTMLDocument, testValue);
+
+ heir.HTMLPreElement = testValue;
+ assert_equals(heir.HTMLPreElement, testValue);
+ assert_not_equals(window.HTMLPreElement, testValue);
+}, "Prototype chain [[Set]] creates property on receiver");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/interface-object.html b/testing/web-platform/tests/webidl/ecmascript-binding/interface-object.html
new file mode 100644
index 0000000000..132c61ddae
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/interface-object.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Interface objects</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function () {
+ assert_equals(typeof window.Blob, "function")
+ delete window.Blob;
+ assert_equals(window.Blob, undefined);
+}, "An interface object deleted after it has been accessed is undefined");
+
+test(function () {
+ delete window.File;
+ assert_equals(window.File, undefined);
+}, "An interface object deleted before it has been defined is undefined");
+
+test(function () {
+ delete window.ImageData;
+ assert_equals(Object.getOwnPropertyDescriptor(window, "ImageData"), undefined);
+ delete window.ImageData;
+ assert_equals(Object.getOwnPropertyDescriptor(window, "ImageData"), undefined);
+}, "Interface objects deleted multiple times stay deleted");
+
+test(function () {
+ assert_equals(window["abc\udc88"], undefined);
+}, "Fancy property names don't break the resolve hook on Window");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/interface-prototype-constructor-set-receiver.html b/testing/web-platform/tests/webidl/ecmascript-binding/interface-prototype-constructor-set-receiver.html
new file mode 100644
index 0000000000..64a2da8eb2
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/interface-prototype-constructor-set-receiver.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Interface.prototype.constructor is defined on [[Set]] receiver</title>
+<link rel="help" href="https://webidl.spec.whatwg.org/#interface-prototype-object">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+const testValue = Object.freeze(function() {});
+
+test(() => {
+ Location.prototype.constructor = testValue;
+ assert_false(Location.prototype.propertyIsEnumerable("constructor"));
+
+ HTMLDocument.prototype.constructor = testValue;
+ assert_false(HTMLDocument.prototype.propertyIsEnumerable("constructor"));
+
+ HTMLDivElement.prototype.constructor = testValue;
+ assert_false(HTMLDivElement.prototype.propertyIsEnumerable("constructor"));
+}, "Direct [[Set]] preserves [[Enumerable]]: false property attribute");
+
+test(() => {
+ window.constructor = testValue;
+ assert_equals(window.constructor, testValue);
+ assert_equals(Window.prototype.constructor, Window);
+
+ navigator.constructor = testValue;
+ assert_equals(navigator.constructor, testValue);
+ assert_equals(Navigator.prototype.constructor, Navigator);
+
+ const span = document.createElement("span");
+ span.constructor = testValue;
+ assert_equals(span.constructor, testValue);
+ assert_equals(HTMLSpanElement.prototype.constructor, HTMLSpanElement);
+}, "Prototype chain [[Set]] creates property on receiver");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/interface-prototype-object.html b/testing/web-platform/tests/webidl/ecmascript-binding/interface-prototype-object.html
new file mode 100644
index 0000000000..299bcf926d
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/interface-prototype-object.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Interface prototype objects</title>
+<link rel="help" href="https://webidl.spec.whatwg.org/#interface-prototype-object">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+ // https://webidl.spec.whatwg.org/#create-an-interface-prototype-object
+ assert_own_property(Element.prototype, Symbol.unscopables, "Element.prototype has @@unscopables.");
+ let unscopables = Element.prototype[Symbol.unscopables];
+ assert_equals(typeof unscopables, "object", "@@unscopables is an Object.");
+ assert_equals(Object.getPrototypeOf(unscopables), null, "@@unscopables's prototype is null.");
+}, "[Unscopable] extended attribute makes @@unscopables object on the prototype object, whose prototype is null.");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/invalid-this-value-cross-realm.html b/testing/web-platform/tests/webidl/ecmascript-binding/invalid-this-value-cross-realm.html
new file mode 100644
index 0000000000..0535115ac6
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/invalid-this-value-cross-realm.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Cross-realm getter / setter / operation doesn't use lexical global object to throw an error for incompatible |this| value</title>
+<link rel="help" href="https://webidl.spec.whatwg.org/#dfn-attribute-getter">
+<link rel="help" href="https://webidl.spec.whatwg.org/#dfn-attribute-setter">
+<link rel="help" href="https://webidl.spec.whatwg.org/#dfn-create-operation-function">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/create-realm.js"></script>
+
+<body>
+<script>
+promise_test(async t => {
+ const other = await createRealm(t);
+ const notElement = Object.create(other.HTMLElement.prototype);
+
+ assert_throws_js(other.TypeError, () => { Object.create(other.document).head; });
+ assert_throws_js(other.TypeError, () => { Object.getOwnPropertyDescriptor(other.HTMLElement.prototype, "title").get.call(notElement); });
+ assert_throws_js(other.TypeError, () => { Reflect.get(other.document.createElement("div"), "hidden", notElement); });
+ assert_throws_js(other.TypeError, () => { new Proxy(other.text, {}).nodeType; });
+}, "Cross-realm getter throws when called on incompatible object");
+
+promise_test(async t => {
+ const other = await createRealm(t);
+ const notElement = Object.create(other.HTMLElement.prototype);
+ const notText = Object.create(other.Text.prototype);
+
+ assert_throws_js(other.TypeError, () => { Object.create(other.element).innerHTML = ""; });
+ assert_throws_js(other.TypeError, () => { Object.getOwnPropertyDescriptor(other.HTMLElement.prototype, "onclick").set.call(notElement, null); });
+ assert_throws_js(other.TypeError, () => { Reflect.set(new other.Text("foo"), "data", "foo", notText); });
+ assert_throws_js(other.TypeError, () => { new Proxy(other.document, {}).title = ""; });
+}, "Cross-realm setter throws when called on incompatible object");
+
+promise_test(async t => {
+ const other = await createRealm(t);
+ const notDocument = Object.create(other.HTMLDocument.prototype);
+ const notText = Object.create(other.Text.prototype);
+
+ assert_throws_js(other.TypeError, () => { Object.create(other.element).click(); });
+ assert_throws_js(other.TypeError, () => { other.document.querySelector.call(notDocument, "*"); });
+ assert_throws_js(other.TypeError, () => { Reflect.apply(other.text.remove, notText, []); });
+ assert_throws_js(other.TypeError, () => { new Proxy(other.document.createElement("a"), {}).addEventListener("foo", () => {}); });
+}, "Cross-realm operation throws when called on incompatible object");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/iterator-invalidation-foreach.html b/testing/web-platform/tests/webidl/ecmascript-binding/iterator-invalidation-foreach.html
new file mode 100644
index 0000000000..9d2e3b9cb2
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/iterator-invalidation-foreach.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Behavior of iterators when modified within foreach</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://webidl.spec.whatwg.org/#es-forEach">
+<link rel="author" title="Manish Goregaokar" href="mailto:manishsmail@gmail.com">
+<script>
+test(function() {
+ let params = new URLSearchParams("a=1&b=2&c=3");
+ let arr = [];
+ params.forEach((p) => {
+ arr.push(p);
+ params.delete("b");
+ })
+ assert_array_equals(arr, ["1", "3"]);
+}, "forEach will not iterate over elements removed during iteration");
+test(function() {
+ let params = new URLSearchParams("a=1&b=2&c=3&d=4");
+ let arr = [];
+ params.forEach((p) => {
+ arr.push(p);
+ if (p == "2") {
+ params.delete("a");
+ }
+ })
+ assert_array_equals(arr, ["1", "2", "4"]);
+}, "Removing elements already iterated over during forEach will cause iterator to skip an element");
+test(function() {
+ let params = new URLSearchParams("a=1&b=2&c=3&d=4");
+ let arr = [];
+ params.forEach((p) => {
+ arr.push(p);
+ if (p == "2") {
+ params.append("e", "5");
+ }
+ })
+ assert_array_equals(arr, ["1", "2", "3", "4", "5"]);
+}, "Elements added during iteration with forEach will be reached");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/iterator-prototype-object.html b/testing/web-platform/tests/webidl/ecmascript-binding/iterator-prototype-object.html
new file mode 100644
index 0000000000..7859c1e46a
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/iterator-prototype-object.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Iterator prototype objects</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+ const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
+ const iteratorProto = Object.getPrototypeOf(new URLSearchParams().entries());
+ assert_equals(Object.getPrototypeOf(iteratorProto), esIteratorPrototype);
+}, "Has %IteratorPrototype% as prototype");
+
+test(() => {
+ const iteratorProto = Object.getPrototypeOf(new URLSearchParams().entries());
+ const desc = Object.getOwnPropertyDescriptor(iteratorProto, "next");
+ assert_equals(typeof desc.value, "function");
+ assert_equals(desc.writable, true);
+ assert_equals(desc.enumerable, true);
+ assert_equals(desc.configurable, true);
+}, "next() exists and is writable, enumerable, and configurable");
+
+test(() => {
+ const usp = new URLSearchParams();
+ const iteratorProto = Object.getPrototypeOf(usp.entries());
+
+ assert_throws_js(TypeError, () => {
+ iteratorProto.next();
+ });
+ assert_throws_js(TypeError, () => {
+ iteratorProto.next.call(undefined);
+ });
+ assert_throws_js(TypeError, () => {
+ iteratorProto.next.call(42);
+ });
+ assert_throws_js(TypeError, () => {
+ iteratorProto.next.call(new Headers().entries());
+ });
+}, "next() throws TypeError when called on ineligible receiver");
+
+// class string behavior tested in a dedicated file
+
+test(() => {
+ const iteratorProto1 = Object.getPrototypeOf(new URLSearchParams().entries());
+ const iteratorProto2 = Object.getPrototypeOf(new Headers().entries());
+ assert_not_equals(iteratorProto1, iteratorProto2);
+}, "Is specific to an interface");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/legacy-callback-interface-object.html b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-callback-interface-object.html
new file mode 100644
index 0000000000..627d29507f
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-callback-interface-object.html
@@ -0,0 +1,69 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Legacy callback interface objects</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://webidl.spec.whatwg.org/#legacy-callback-interface-object">
+
+<script>
+test(() => {
+ assert_equals(typeof NodeFilter, "function");
+}, "Must be a function according to typeof");
+
+test(() => {
+ assert_true(Object.getPrototypeOf(NodeFilter) === Function.prototype);
+}, "Must have the correct [[Prototype]]");
+
+test(() => {
+ const propDesc = Object.getOwnPropertyDescriptor(window, "NodeFilter");
+ assert_true(propDesc.writable, "writable");
+ assert_false(propDesc.enumerable, "enumerable");
+ assert_true(propDesc.configurable, "configurable");
+}, "Must have the correct property descriptor");
+
+test(() => {
+ assert_throws_js(TypeError, () => NodeFilter(), "call");
+ assert_throws_js(TypeError, () => new NodeFilter(), "construct");
+}, "Must throw a TypeError when called or constructed")
+
+test(() => {
+ assert_false(NodeFilter.hasOwnProperty("prototype"));
+}, "Must not have a .prototype property");
+
+test(() => {
+ assert_true(NodeFilter.hasOwnProperty("name"));
+ assert_equals(NodeFilter.name, "NodeFilter");
+
+ const propDesc = Object.getOwnPropertyDescriptor(NodeFilter, "name");
+ assert_false(propDesc.writable, "writable");
+ assert_false(propDesc.enumerable, "enumerable");
+ assert_true(propDesc.configurable, "configurable");
+}, "Must have an own name property equal to the interface name and with the correct descriptors");
+
+test(() => {
+ assert_true(NodeFilter.hasOwnProperty("length"));
+ assert_equals(NodeFilter.length, 0);
+
+ const propDesc = Object.getOwnPropertyDescriptor(NodeFilter, "length");
+ assert_false(propDesc.writable, "writable");
+ assert_false(propDesc.enumerable, "enumerable");
+ assert_true(propDesc.configurable, "configurable");
+}, "Must have an own length property with value zero and the correct descriptors");
+
+test(() => {
+ // The JS spec (OrdinaryHasInstance) bails out early for non-objects
+ // Historically we overrode [[HasInstance]] to throw anyway, but this was removed:
+ // https://github.com/heycam/webidl/pull/356
+ assert_false(5 instanceof NodeFilter, "5");
+
+ // OrdinaryHasInstance throws a TypeError if the right-hand-side doesn't have a .prototype object,
+ // which is the case for callback interfaces.
+ assert_throws_js(TypeError, () => {
+ (function () { }) instanceof NodeFilter;
+ });
+ assert_throws_js(TypeError, () => {
+ ({ }) instanceof NodeFilter;
+ });
+}, "instanceof must throw but only when we don't bail out early");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/legacy-factor-function-subclass.window.js b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-factor-function-subclass.window.js
new file mode 100644
index 0000000000..1fd64f41bb
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-factor-function-subclass.window.js
@@ -0,0 +1,13 @@
+"use strict";
+
+test(() => {
+ class CustomImage extends Image {}
+ var instance = new CustomImage();
+
+ assert_equals(
+ Object.getPrototypeOf(instance), CustomImage.prototype,
+ "Object.getPrototypeOf(instance) === CustomImage.prototype");
+
+ assert_true(instance instanceof CustomImage, "instance instanceof CustomImage");
+ assert_true(instance instanceof HTMLImageElement, "instance instanceof HTMLImageElement");
+}, "[LegacyFactoryFunction] can be subclassed and correctly handles NewTarget");
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/DefineOwnProperty.html b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/DefineOwnProperty.html
new file mode 100644
index 0000000000..bd7ba19c1a
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/DefineOwnProperty.html
@@ -0,0 +1,165 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Legacy platform objects [[DefineOwnProperty]] method</title>
+<link rel="help" href="https://webidl.spec.whatwg.org/#legacy-platform-object-defineownproperty">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./helper.js"></script>
+<script>
+
+test(function() {
+ let span = document.createElement("span");
+ span.className = "foo";
+ // DOMTokenList supports an indexed property getter but not a setter.
+ let domTokenList = span.classList;
+ // Confirm the test settings.
+ assert_equals(domTokenList.length, 1);
+ assert_prop_desc_equals(domTokenList, "0",
+ {value: "foo", writable: false, enumerable: true,
+ configurable: true});
+ assert_prop_desc_equals(domTokenList, "1", undefined);
+ // Actual test
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(domTokenList, "0", {value: true, writable: true}));
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(domTokenList, "1", {value: true, writable: true}));
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(domTokenList, "0", {get: () => {}}));
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(domTokenList, "0", {set: () => {}}));
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(domTokenList, "1", {get: () => {}}));
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(domTokenList, "1", {set: () => {}}));
+ assert_equals(domTokenList[0], "foo");
+ assert_equals(domTokenList[1], undefined);
+ domTokenList[0] = "bar";
+ domTokenList[1] = "bar";
+ assert_equals(domTokenList[0], "foo");
+ assert_equals(domTokenList[1], undefined);
+ assert_throws_js(TypeError, () => {
+ "use strict";
+ domTokenList[0] = "bar";
+ });
+ assert_throws_js(TypeError, () => {
+ "use strict";
+ domTokenList[1] = "bar";
+ });
+ // Nothing must change after all.
+ assert_equals(domTokenList.length, 1);
+ assert_prop_desc_equals(domTokenList, "0",
+ {value: "foo", writable: false, enumerable: true,
+ configurable: true});
+ assert_prop_desc_equals(domTokenList, "1", undefined);
+}, "Test [[DefineOwnProperty]] with no indexed property setter support.");
+
+test(function() {
+ // HTMLSelectElement supports an indexed property setter.
+ let select = document.createElement("select");
+ let option0 = document.createElement("option");
+ let option1 = document.createElement("option");
+ // Confirm the test settings.
+ assert_equals(select.length, 0);
+ assert_prop_desc_equals(select, "0", undefined);
+ // Try to define an accessor property with non supported property index.
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(select, "0", {get: () => {}}));
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(select, "0", {set: () => {}}));
+ assert_prop_desc_equals(select, "0", undefined);
+ // writable, enumerable, configurable will be ignored.
+ Object.defineProperty(select, "0", {value: option0, writable: false,
+ enumerable: false, configurable: false});
+ assert_prop_desc_equals(select, "0",
+ {value: option0, writable: true, enumerable: true,
+ configurable: true});
+ select[1] = option1;
+ assert_prop_desc_equals(select, "1",
+ {value: option1, writable: true, enumerable: true,
+ configurable: true});
+ // Try to define an accessor property with a supported property index.
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(select, "0", {get: () => {}}));
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(select, "0", {set: () => {}}));
+ assert_prop_desc_equals(select, "0",
+ {value: option0, writable: true, enumerable: true,
+ configurable: true});
+}, "Test [[DefineOwnProperty]] with indexed property setter support.");
+
+test(function() {
+ let dataList = document.createElement("datalist");
+ let option0 = document.createElement("option");
+ option0.id = "foo";
+ dataList.append(option0);
+ // HTMLCollection supports a named property getter but not a setter.
+ let htmlCollection = dataList.options;
+ // Confirm the test settings.
+ assert_equals(htmlCollection.length, 1);
+ // HTMLCollection is marked LegacyUnenumerableNamedProperties so enumerable
+ // is false.
+ assert_prop_desc_equals(htmlCollection, "foo",
+ {value: option0, writable: false, enumerable: false,
+ configurable: true});
+ assert_prop_desc_equals(htmlCollection, "bar", undefined);
+ // Actual test
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(htmlCollection, "foo",
+ {value: true, writable: true}));
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(htmlCollection, "foo", {get: () => {}}));
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(htmlCollection, "foo", {set: () => {}}));
+ let option1 = document.createElement("option");
+ option1.id = "bar";
+ Object.defineProperty(htmlCollection, "bar",
+ {value: option1, writable: true});
+ assert_equals(htmlCollection["foo"], option0);
+ assert_equals(htmlCollection["bar"], option1);
+ htmlCollection["foo"] = document.documentElement;
+ htmlCollection["baz"] = document.documentElement;
+ assert_equals(htmlCollection["foo"], option0);
+ assert_equals(htmlCollection["baz"], document.documentElement);
+ assert_throws_js(TypeError, () => {
+ "use strict";
+ htmlCollection["foo"] = document.documentElement;
+ });
+ // Nothing must change after all for supported property names.
+ assert_prop_desc_equals(htmlCollection, "foo",
+ {value: option0, writable: false, enumerable: false,
+ configurable: true});
+}, "Test [[DefineOwnProperty]] with no named property setter support.");
+
+test(function() {
+ let element = document.createElement("div");
+ // DOMStringMap supports a named property setter.
+ let domStringMap = element.dataset;
+ // Confirm the test settings.
+ assert_prop_desc_equals(domStringMap, "foo", undefined);
+ // Try to define an accessor property with non supported property name.
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(domStringMap, "foo", {get: () => {}}));
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(domStringMap, "foo", {set: () => {}}));
+ assert_prop_desc_equals(domStringMap, "foo", undefined);
+ // writable, enumerable, configurable will be ignored.
+ Object.defineProperty(domStringMap, "foo",
+ {value: "foo content", writable: false,
+ enumerable: false, configurable: false});
+ assert_prop_desc_equals(domStringMap, "foo",
+ {value: "foo content", writable: true,
+ enumerable: true, configurable: true});
+ domStringMap["bar"] = "bar content";
+ assert_prop_desc_equals(domStringMap, "bar",
+ {value: "bar content", writable: true,
+ enumerable: true, configurable: true});
+ // Try to define an accessor property with a supported property name.
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(domStringMap, "foo", {get: () => {}}));
+ assert_throws_js(TypeError, () =>
+ Object.defineProperty(domStringMap, "foo", {set: () => {}}));
+ assert_prop_desc_equals(domStringMap, "foo",
+ {value: "foo content", writable: true,
+ enumerable: true, configurable: true});
+}, "Test [[DefineOwnProperty]] with named property setter support.");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/GetOwnProperty.html b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/GetOwnProperty.html
new file mode 100644
index 0000000000..be3bcc61f0
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/GetOwnProperty.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Legacy platform objects [[GetOwnProperty]] method</title>
+<link rel="help" href="https://webidl.spec.whatwg.org/#legacy-platform-object-getownproperty">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./helper.js"></script>
+<script>
+
+test(function() {
+ // DOMTokenList has an indexed property getter, no indexed property setter
+ // and no named property handlers.
+ let div = document.createElement("div");
+ div.classList.add("baz", "quux");
+ const domTokenList = div.classList;
+ assert_prop_desc_equals(
+ domTokenList, "1",
+ {value: "quux", writable: false, enumerable: true, configurable: true},
+ "[[GetOwnProperty]] for indexed properties returns the right descriptor");
+ assert_prop_desc_equals(
+ domTokenList, "42", undefined,
+ "[[GetOwnProperty]] with invalid index returns undefined");
+ assert_array_equals(Object.keys(domTokenList), ["0", "1"]);
+ assert_array_equals(Object.values(domTokenList), ["baz", "quux"]);
+
+ // getElementsByTagName() returns an HTMLCollection.
+ // HTMLCollection has indexed and named property getters, no setters. Its IDL
+ // interface declaration has [LegacyUnenumerableNamedProperties] so its named
+ // properties are not enumerable.
+ let span1 = document.createElement("span");
+ span1.id = "foo";
+ let span2 = document.createElement("span");
+ span2.id = "bar";
+ document.head.appendChild(span1);
+ document.head.appendChild(span2);
+ const elementList = document.getElementsByTagName("span");
+ assert_prop_desc_equals(
+ elementList, "foo",
+ {value: span1, writable: false, enumerable: false, configurable: true},
+ "[[GetOwnProperty]] for named properties returns the right descriptor");
+ assert_prop_desc_equals(
+ elementList, "1",
+ {value: span2, writable: false, enumerable: true, configurable: true},
+ "[[GetOwnProperty]] for indexed properties returns the right descriptor");
+ assert_prop_desc_equals(
+ elementList, "unknown", undefined,
+ "[[GetOwnProperty]] with invalid property name returns undefined");
+ assert_array_equals(Object.keys(elementList), ["0", "1"]);
+ assert_array_equals(Object.values(elementList), [span1, span2]);
+}, "[[GetOwnProperty]] with getters and no setters");
+
+test(function() {
+ // DOMStringMap supports named property getters and setters, but not indexed
+ // properties.
+ let span = document.createElement("span");
+ span.dataset.foo = "bar";
+ assert_prop_desc_equals(
+ span.dataset, "foo",
+ {value: "bar", writable: true, enumerable: true, configurable: true},
+ "[[GetOwnProperty]] for named properties returns the right descriptor");
+ assert_prop_desc_equals(
+ span.dataset, "unknown", undefined,
+ "[[GetOwnProperty]] with invalid property name returns undefined");
+ assert_array_equals(Object.keys(span.dataset), ["foo"]);
+ assert_array_equals(Object.values(span.dataset), ["bar"]);
+}, "[[GetOwnProperty]] with named property getters and setters");
+
+test(function() {
+ // HTMLSelectElement has indexed property getters and setters, but no support
+ // for named properties.
+ let selectElement = document.createElement("select");
+ assert_prop_desc_equals(
+ selectElement, "0", undefined,
+ "[[GetOwnProperty]] with invalid property index returns undefined");
+ let optionElement = document.createElement("option");
+ selectElement.appendChild(optionElement);
+ assert_prop_desc_equals(
+ selectElement, "0",
+ {value: optionElement, writable: true, enumerable: true, configurable: true},
+ "[[GetOwnProperty]] for indexed properties returns the right descriptor");
+ assert_array_equals(Object.keys(selectElement), ["0"]);
+ assert_array_equals(Object.values(selectElement), [optionElement]);
+}, "[[GetOwnProperty]] with indexed property getters and setters");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/OwnPropertyKeys.html b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/OwnPropertyKeys.html
new file mode 100644
index 0000000000..d33980517b
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/OwnPropertyKeys.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Legacy platform objects [[OwnPropertyKeys]] method</title>
+<link rel="help" href="https://webidl.spec.whatwg.org/#legacy-platform-object-ownpropertykeys">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ function custom_assert_array_equals(actual, expected, msg) {
+ function replacer(k, v) {
+ if (typeof v === "symbol") {
+ return v.toString();
+ }
+ return v;
+ }
+ assert_array_equals(actual, expected, " actual " + JSON.stringify(actual, replacer) + " expected " + JSON.stringify(expected, replacer));
+ }
+
+ test(function() {
+ var element = document.createElement("div");
+ element.appendChild(document.createElement("div"));
+ element.appendChild(document.createElement("div"));
+ element.appendChild(document.createElement("div"));
+ custom_assert_array_equals(Reflect.ownKeys(element.childNodes), ["0", "1", "2"]);
+ }, "must enumerate property indices in ascending numerical order");
+
+ test(function() {
+ var element = document.createElement("div");
+ element.setAttribute("data-foo", "foo content");
+ element.setAttribute("data-bar", "bar content");
+ element.setAttribute("data-baz", "baz content");
+ custom_assert_array_equals(Reflect.ownKeys(element.dataset), ["foo", "bar", "baz"]);
+ }, "must enumerate property names in list order");
+
+
+ test(function() {
+ var element = document.createElement("div");
+ element.setAttribute("id", "foo");
+ element.setAttribute("class", "bar");
+ custom_assert_array_equals(Reflect.ownKeys(element.attributes), ["0", "1", "id", "class"]);
+ }, "must first enumerate property indices in ascending numerical order, then named properties in list order");
+
+
+ test(function() {
+ var element = document.createElement("div");
+ element.attributes.foo = "some value";
+ element.attributes.bar = "and another";
+ element.setAttribute("id", "foo");
+ element.setAttribute("class", "bar");
+ custom_assert_array_equals(Reflect.ownKeys(element.attributes), ["0", "1", "id", "class", "foo", "bar"]);
+ }, "must enumerate own properties after indexed and named properties even when they're added first");
+
+ test(function() {
+ var symb1 = Symbol();
+ var symb2 = Symbol();
+ var element = document.createElement("div");
+ element.attributes.foo = "some value";
+ element.attributes[symb1] = "symb1";
+ element.attributes[symb2] = "symb2";
+ element.attributes.bar = "and another";
+ element.setAttribute("id", "foo");
+ element.setAttribute("class", "bar");
+ custom_assert_array_equals(Reflect.ownKeys(element.attributes),
+ ["0", "1", "id", "class", "foo", "bar", symb1, symb2]);
+ }, "must enumerate symbols after strings, regardless of which ones got added first");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/Set.html b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/Set.html
new file mode 100644
index 0000000000..1390b51cd0
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/Set.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Legacy platform objects [[Set]] method</title>
+<link rel="help" href="https://webidl.spec.whatwg.org/#legacy-platform-object-set">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ test(function() {
+ var element = document.createElement("div");
+ element.appendChild(document.createElement("div"));
+ element.childNodes["5"] = "foo";
+ assert_equals(element.childNodes["5"], undefined);
+ }, "must not set the property value when assigning to a numeric property on an object which implements an indexed property getter but not a setter when not in strict mode");
+
+ test(function() {
+ "use strict";
+ var element = document.createElement("div");
+ element.appendChild(document.createElement("div"));
+ assert_throws_js(TypeError, function() { element.childNodes["5"] = "foo"; });
+ }, "must throw when assigning to a numeric property on an object which implements a indexed property getter but not a setter in strict mode");
+
+ test(function() {
+ var element = document.createElement("div");
+ element.attributes.foo = "foo";
+ assert_equals(element.attributes.foo, "foo");
+ }, "must allow assigning to a named property on an object which implements a named property getter but not a setter when not in strict mode");
+
+ test(function() {
+ "use strict";
+ var element = document.createElement("div");
+ element.attributes.foo = "foo";
+ assert_equals(element.attributes.foo, "foo");
+ }, "must allow assigning to a named property on an object which implements a named property getter but not a setter in strict mode");
+
+ test(function() {
+ "use strict";
+ var form = document.createElement("form");
+ assert_equals(form.method, "get");
+ var input = document.createElement("input");
+ input.name = "method";
+ input.id = "method";
+ form.appendChild(input);
+ assert_equals(form.method, input);
+ form.method = "post";
+ assert_equals(form.method, input);
+ input.remove();
+ assert_equals(form.method, "post");
+ }, "must allow setting built-in property on a [LegacyOverrideBuiltIns] object even if a named property shadows it");
+
+ var symbol = Symbol();
+
+ test(function() {
+ var element = document.createElement("div");
+ element.appendChild(document.createElement("div"));
+ element.childNodes[symbol] = "foo";
+ assert_equals(element.childNodes[symbol], "foo");
+ }, "must allow assigning to a symbol property on an object which implements an indexed property getter but not a setter when not in strict mode");
+
+ test(function() {
+ "use strict";
+ var element = document.createElement("div");
+ element.appendChild(document.createElement("div"));
+ element.childNodes[symbol] = "foo";
+ assert_equals(element.childNodes[symbol], "foo");
+ }, "must allow assigning to a symbol property on an object which implements an indexed property getter but not a setter in strict mode");
+
+ test(function() {
+ var element = document.createElement("div");
+ element.attributes[symbol] = "foo";
+ assert_equals(element.attributes[symbol], "foo");
+ }, "must allow assigning to a symbol property on an object which implements indexed and named property getters but no setters when not in strict mode");
+
+ test(function() {
+ "use strict";
+ var element = document.createElement("div");
+ element.attributes[symbol] = "foo";
+ assert_equals(element.attributes[symbol], "foo");
+ }, "must allow assigning to a symbol property on an object which implements indexed and named property getters but no setters in strict mode");
+
+ test(function() {
+ sessionStorage.clear();
+ this.add_cleanup(function() { sessionStorage.clear(); });
+ sessionStorage[symbol] = "foo";
+ assert_equals(sessionStorage[symbol], "foo");
+ }, "must allow assigning to a symbol property on an object which implements indexed and named property getters and setters when not in strict mode");
+
+ test(function() {
+ "use strict";
+ sessionStorage.clear();
+ this.add_cleanup(function() { sessionStorage.clear(); });
+ sessionStorage[symbol] = "foo";
+ assert_equals(sessionStorage[symbol], "foo");
+ }, "must allow assigning to a symbol property on an object which implements indexed and named property getters and setters in strict mode");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/helper.js b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/helper.js
new file mode 100644
index 0000000000..01c1d00694
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/legacy-platform-object/helper.js
@@ -0,0 +1,22 @@
+function assert_prop_desc_equals(object, property_key, expected) {
+ let actual = Object.getOwnPropertyDescriptor(object, property_key);
+ if (expected === undefined) {
+ assert_equals(
+ actual, undefined,
+ "(assert_prop_desc_equals: no property descriptor expected)");
+ return;
+ }
+ for (p in actual) {
+ assert_true(
+ expected.hasOwnProperty(p),
+ "(assert_prop_desc_equals: property '" + p + "' is not expected)");
+ assert_equals(
+ actual[p], expected[p],
+ "(assert_prop_desc_equals: property '" + p + "')");
+ }
+ for (p in expected) {
+ assert_true(
+ actual.hasOwnProperty(p),
+ "(assert_prop_desc_equals: expected property '" + p + "' missing)");
+ }
+}
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/no-regexp-special-casing.any.js b/testing/web-platform/tests/webidl/ecmascript-binding/no-regexp-special-casing.any.js
new file mode 100644
index 0000000000..4446dbf69c
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/no-regexp-special-casing.any.js
@@ -0,0 +1,47 @@
+"use strict";
+// RegExps used to be special-cased in Web IDL, but that was removed in
+// https://github.com/heycam/webidl/commit/bbb2bde. These tests check that implementations no longer
+// do any such special-casing.
+
+test(() => {
+ const regExp = new RegExp();
+ regExp.message = "some message";
+
+ const errorEvent = new ErrorEvent("type", regExp);
+
+ assert_equals(errorEvent.message, "some message");
+}, "Conversion to a dictionary works");
+
+test(() => {
+ const messageChannel = new MessageChannel();
+ const regExp = new RegExp();
+ regExp[Symbol.iterator] = function* () {
+ yield messageChannel.port1;
+ };
+
+ const messageEvent = new MessageEvent("type", { ports: regExp });
+
+ assert_array_equals(messageEvent.ports, [messageChannel.port1]);
+}, "Conversion to a sequence works");
+
+promise_test(async () => {
+ const regExp = new RegExp();
+
+ const response = new Response(regExp);
+
+ assert_equals(await response.text(), "/(?:)/");
+}, "Can convert a RegExp to a USVString");
+
+test(() => {
+ let functionCalled = false;
+
+ const regExp = new RegExp();
+ regExp.handleEvent = () => {
+ functionCalled = true;
+ };
+
+ self.addEventListener("testevent", regExp);
+ self.dispatchEvent(new Event("testevent"));
+
+ assert_true(functionCalled);
+}, "Can be used as an object implementing a callback interface");
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/observable-array-no-leak-of-internals.window.js b/testing/web-platform/tests/webidl/ecmascript-binding/observable-array-no-leak-of-internals.window.js
new file mode 100644
index 0000000000..f93464005d
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/observable-array-no-leak-of-internals.window.js
@@ -0,0 +1,18 @@
+"use strict";
+
+test(() => {
+ const observableArray = document.adoptedStyleSheets;
+
+ let leaked_target = null;
+ let leaked_handler = null;
+
+ let target_leaker = (target) => { leaked_target = target; return null; };
+ Object.defineProperty(Object.prototype, "getPrototypeOf", {get: function() {
+ leaked_handler = this;
+ return target_leaker;
+ }})
+ Object.getPrototypeOf(observableArray);
+
+ assert_equals(leaked_target, null, "The proxy target leaked.");
+ assert_equals(leaked_handler, null, "The proxy handler leaked.");
+}, "ObservableArray's internals won't leak");
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/observable-array-ownkeys.window.js b/testing/web-platform/tests/webidl/ecmascript-binding/observable-array-ownkeys.window.js
new file mode 100644
index 0000000000..29b537c475
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/observable-array-ownkeys.window.js
@@ -0,0 +1,34 @@
+"use strict";
+
+test(() => {
+ const observableArray = document.adoptedStyleSheets;
+ assert_array_equals(
+ Object.getOwnPropertyNames(observableArray),
+ ["length"],
+ "Initially only \"length\".");
+
+ observableArray["zzz"] = true;
+ observableArray["aaa"] = true;
+ assert_array_equals(
+ Object.getOwnPropertyNames(observableArray),
+ ["length", "zzz", "aaa"],
+ "Own properties whose key is a string have been added.");
+
+ observableArray[0] = new CSSStyleSheet();
+ observableArray[1] = new CSSStyleSheet();
+ assert_array_equals(
+ Object.getOwnPropertyNames(observableArray),
+ ["0", "1", "length", "zzz", "aaa"],
+ "Own properties whose key is an array index have been added.");
+
+ observableArray[Symbol.toStringTag] = "string_tag";
+ observableArray[Symbol.toPrimitive] = "primitive";
+ assert_array_equals(
+ Object.getOwnPropertyNames(observableArray),
+ ["0", "1", "length", "zzz", "aaa"],
+ "Own properties whose key is a symbol have been added (non-symbol).");
+ assert_array_equals(
+ Object.getOwnPropertySymbols(observableArray),
+ [Symbol.toStringTag, Symbol.toPrimitive],
+ "Own properties whose key is a symbol have been added (symbol).");
+}, "ObservableArray's ownKeys trap");
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/put-forwards.html b/testing/web-platform/tests/webidl/ecmascript-binding/put-forwards.html
new file mode 100644
index 0000000000..7d99d65aa2
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/put-forwards.html
@@ -0,0 +1,148 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>[PutForwards] behavior</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Jens Widell" href="mailto:jl@opera.com">
+<link rel="help" href="https://webidl.spec.whatwg.org/#PutForwards">
+
+<script>
+test(() => {
+ var getter_called = false;
+ var element = document.createElement("div");
+ var element_style = element.style;
+ var descriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "style");
+
+ Object.defineProperty(element, "style", {
+ get: function () {
+ getter_called = true;
+ return element_style;
+ },
+ set: descriptor.set
+ });
+
+ element.style = "color: green";
+
+ assert_true(getter_called, "Overridden getter should be called");
+ assert_equals(element_style.color, "green", "Put forwarding still works");
+}, "Overriding getter of [PutForwards] attribute");
+
+test(() => {
+ var setter_called = false;
+ var element = document.createElement("div");
+ var element_style = element.style;
+ var descriptor = Object.getOwnPropertyDescriptor(CSSStyleDeclaration.prototype, "cssText");
+
+ Object.defineProperty(element_style, "cssText", {
+ get: descriptor.get,
+ set: function (v) {
+ setter_called = true;
+ descriptor.set.call(this, v);
+ }
+ });
+
+ element.style = "color: green";
+
+ assert_true(setter_called, "Overridden setter should be called");
+ assert_equals(element_style.color, "green", "Put forwarding still works");
+}, "Overriding setter of [PutForwards] target attribute");
+
+test(() => {
+ var element = document.createElement("div");
+ var element_style = element.style;
+ var fake_style = { cssText: "original" };
+ var descriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "style");
+
+ Object.defineProperty(element, "style", {
+ get: function () {
+ return fake_style;
+ },
+ set: descriptor.set
+ });
+
+ element.style = "color: green";
+
+ assert_equals(element_style.cssText, "", "Original value intact");
+ assert_equals(fake_style.cssText, "color: green", "Fake style object updated");
+}, "Overriding target of [PutForwards] attribute");
+
+test(() => {
+ var element = document.createElement("div");
+ var descriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "style");
+
+ Object.defineProperty(element, "style", {
+ get: function () {
+ throw new SyntaxError();
+ },
+ set: descriptor.set
+ });
+
+ assert_throws_js(SyntaxError, () => {
+ element.style = "color: green";
+ });
+}, "Exception propagation from getter of [PutForwards] attribute");
+
+test(() => {
+ var element = document.createElement("div");
+ var element_style = element.style;
+ var descriptor = Object.getOwnPropertyDescriptor(CSSStyleDeclaration.prototype, "cssText");
+
+ Object.defineProperty(element_style, "cssText", {
+ get: descriptor.get,
+ set: function (v) {
+ throw new SyntaxError();
+ }
+ });
+
+ assert_throws_js(SyntaxError, () => {
+ element.style = "color: green";
+ });
+}, "Exception propagation from setter of [PutForwards] target attribute");
+
+test(() => {
+ var element = document.createElement("div");
+ var descriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "style");
+
+ Object.defineProperty(element, "style", {
+ get: function () {
+ return null;
+ },
+ set: descriptor.set
+ });
+
+ assert_throws_js(TypeError, () => {
+ element.style = "color: green";
+ });
+}, "TypeError when getter of [PutForwards] attribute returns non-object");
+
+
+test(() => {
+ var element = document.createElement("div");
+
+ var element_style = element.style;
+ Object.defineProperty(element.style, "cssText", {
+ value: null,
+ writable: false,
+ });
+
+ element.style = "color: green";
+ assert_equals(element.style, element_style);
+ assert_equals(element.style.cssText, null);
+}, "Does not throw when setter of [PutForwards] attribute returns false");
+
+function test_token_list(elementName, attribute, target, value) {
+ test(() => {
+ var element=document.createElement(elementName);
+ assert_true(element[attribute] instanceof DOMTokenList,"The attribute is a DOMTokenList");
+ element[attribute]=value;
+ assert_equals(element.getAttribute(target),value,"Setting the attribute is reflected in the target");
+ element[attribute]="";
+ assert_equals(element.getAttribute(target),"","Clearing the attribute is reflected in the target");
+ },"Setting "+elementName+"."+attribute+" to "+value+" is reflected in "+target)
+}
+
+test_token_list("a","relList","rel","noreferrer");
+test_token_list("area","relList","rel","noreferrer");
+test_token_list("form","relList","rel","noreferrer");
+test_token_list("link","relList","rel","stylesheet");
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/replaceable-setter-throws-if-defineownproperty-fails.html b/testing/web-platform/tests/webidl/ecmascript-binding/replaceable-setter-throws-if-defineownproperty-fails.html
new file mode 100644
index 0000000000..872bbff960
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/replaceable-setter-throws-if-defineownproperty-fails.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>[Replaceable] setter throws TypeError if [[DefineOwnProperty]] fails</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Alexey Shvayka" href="shvaikalesh@gmail.com">
+<link rel="help" href="https://webidl.spec.whatwg.org/#Replaceable">
+
+<body>
+<script>
+for (const key of ["self", "parent", "origin", "innerWidth"]) {
+ test(() => {
+ Object.defineProperty(window, key, { configurable: false });
+ assert_throws_js(TypeError, () => { window[key] = 1; });
+
+ const desc = Object.getOwnPropertyDescriptor(window, key);
+ assert_false("value" in desc);
+ assert_equals(typeof desc.get, "function");
+ assert_equals(typeof desc.set, "function");
+ assert_true(desc.enumerable);
+ assert_false(desc.configurable);
+ }, `window.${key} setter throws TypeError if called on non-configurable accessor property`);
+}
+
+for (const key of ["screen", "length", "event", "outerHeight"]) {
+ test(() => {
+ const { set } = Object.getOwnPropertyDescriptor(window, key);
+ Object.defineProperty(window, key, { value: 1, writable: false, configurable: false });
+ assert_throws_js(TypeError, () => { set.call(window, 2); });
+
+ const desc = Object.getOwnPropertyDescriptor(window, key);
+ assert_equals(desc.value, 1);
+ assert_false(desc.writable);
+ assert_true(desc.enumerable);
+ assert_false(desc.configurable);
+ }, `window.${key} setter throws TypeError if called on non-configurable non-writable data property`);
+}
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/sequence-conversion.html b/testing/web-platform/tests/webidl/ecmascript-binding/sequence-conversion.html
new file mode 100644
index 0000000000..40764e9f57
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/sequence-conversion.html
@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Sequence conversion</title>
+<link rel="help" href="https://webidl.spec.whatwg.org/#es-sequence">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<canvas></canvas>
+
+<script>
+"use strict";
+
+const canvas = document.querySelector("canvas");
+const ctx = canvas.getContext("2d");
+
+test(() => {
+ ctx.setLineDash([1, 2]);
+ assert_array_equals(ctx.getLineDash(), [1, 2]);
+}, "An array");
+
+test(() => {
+ function* generatorFunc() {
+ yield 4;
+ yield 5;
+ }
+ const generator = generatorFunc();
+
+ ctx.setLineDash(generator);
+ assert_array_equals(ctx.getLineDash(), [4, 5]);
+}, "A generator");
+
+test(() => {
+ function* generatorFunc() {
+ yield 6;
+ yield 7;
+ }
+
+ let callCount = 0;
+ const array = [1, 2];
+ Object.defineProperty(array, Symbol.iterator, {
+ get() {
+ ++callCount;
+ return generatorFunc;
+ }
+ });
+
+ ctx.setLineDash(array);
+ assert_array_equals(ctx.getLineDash(), [6, 7]);
+ assert_equals(callCount, 1, "@@iterator must only have been gotten once");
+}, "An array with an overridden Symbol.iterator");
+
+test(t => {
+ function* generatorFunc() {
+ yield ["foo", "bar"];
+ yield ["baz", "quux"];
+ }
+
+ let callCount = 0;
+ const obj = {};
+ Object.defineProperty(obj, Symbol.iterator, {
+ get() {
+ ++callCount;
+ return generatorFunc;
+ }
+ });
+
+ const searchParams = new URLSearchParams(obj);
+ assert_equals(searchParams.get("foo"), "bar");
+ assert_equals(searchParams.get("baz"), "quux");
+ assert_equals(callCount, 1, "@@iterator must only have been gotten once");
+}, "An object with an overriden Symbol.iterator");
+
+test(t => {
+ const originalIterator = Object.getOwnPropertyDescriptor(Array.prototype, Symbol.iterator);
+ t.add_cleanup(() => {
+ Object.defineProperty(Array.prototype, Symbol.iterator, originalIterator);
+ });
+
+ function* generatorFunc() {
+ yield 11;
+ yield 12;
+ }
+
+ let callCount = 0;
+ const array = [1, 2];
+ Object.defineProperty(Array.prototype, Symbol.iterator, {
+ get() {
+ ++callCount;
+ return generatorFunc;
+ }
+ });
+
+ ctx.setLineDash(array);
+ assert_array_equals(ctx.getLineDash(), [11, 12]);
+ assert_equals(callCount, 1, "@@iterator must only have been gotten once");
+}, "An array with an overridden Symbol.iterator on the prototype");
+
+test(t => {
+ const arrayIteratorPrototype = Object.getPrototypeOf(Array.prototype[Symbol.iterator]());
+ const nextBefore = arrayIteratorPrototype.next;
+ t.add_cleanup(() => {
+ arrayIteratorPrototype.next = nextBefore;
+ });
+
+ let callCount = 0;
+ arrayIteratorPrototype.next = () => {
+ switch (callCount) {
+ case 0: {
+ ++callCount;
+ return { done: false, value: 8 };
+ }
+ case 1: {
+ ++callCount;
+ return { done: false, value: 9 };
+ }
+ case 2: {
+ ++callCount;
+ return { done: true, value: 10 }; // value should be ignored this time
+ }
+ default: {
+ assert_unreached("next() should be called three times exactly");
+ }
+ }
+ };
+
+ const array = [1, 2];
+ ctx.setLineDash(array);
+ assert_array_equals(ctx.getLineDash(), [8, 9]);
+ assert_equals(callCount, 3, "next() must have been called thrice");
+}, "An array with an overridden %ArrayIterator%.prototype.next");
+
+test(t => {
+ t.add_cleanup(() => {
+ delete Array.prototype[1];
+ });
+
+ Object.defineProperty(Array.prototype, "1", {
+ configurable: true,
+ enumerable: true,
+ get() {
+ return 14;
+ }
+ });
+
+ const array = [13, , 15, 16];
+ ctx.setLineDash(array);
+ assert_array_equals(ctx.getLineDash(), [13, 14, 15, 16]);
+}, "A holey array with fallback to an accessor on the prototype");
+
+test(t => {
+ // Should fail rather than falling back to record
+ assert_throws_js(TypeError, function() { new URLSearchParams(["key", "value"]); });
+}, "A string array in sequence<sequence> or record");
+
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/support/constructors-support.html b/testing/web-platform/tests/webidl/ecmascript-binding/support/constructors-support.html
new file mode 100644
index 0000000000..3b2616170b
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/support/constructors-support.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script>
+window.badNewTarget = function() {};
+badNewTarget.prototype = 8;
+
+window.DOMParserSubclass = class extends DOMParser {}
+window.ForeignDOMParserSubclass = class extends parent.DOMParser {}
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/support/create-realm.js b/testing/web-platform/tests/webidl/ecmascript-binding/support/create-realm.js
new file mode 100644
index 0000000000..45ded884fc
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/support/create-realm.js
@@ -0,0 +1,12 @@
+"use strict";
+
+function createRealm(t) {
+ return new Promise(resolve => {
+ const iframe = document.createElement("iframe");
+ t.add_cleanup(() => { iframe.remove(); });
+ iframe.onload = () => { resolve(iframe.contentWindow); };
+ iframe.name = "dummy";
+ iframe.src = "support/dummy-iframe.html";
+ document.body.append(iframe);
+ });
+}
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/support/dummy-iframe.html b/testing/web-platform/tests/webidl/ecmascript-binding/support/dummy-iframe.html
new file mode 100644
index 0000000000..3f773ae6f8
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/support/dummy-iframe.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<body>foo
+<button id="element"></button>
+<script>
+ window.text = document.body.firstChild;
+</script>
diff --git a/testing/web-platform/tests/webidl/ecmascript-binding/window-named-properties-object.html b/testing/web-platform/tests/webidl/ecmascript-binding/window-named-properties-object.html
new file mode 100644
index 0000000000..cc49768906
--- /dev/null
+++ b/testing/web-platform/tests/webidl/ecmascript-binding/window-named-properties-object.html
@@ -0,0 +1,284 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Internal methods of Window's named properties object</title>
+<link rel="help" href="https://webidl.spec.whatwg.org/#named-properties-object">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+function sloppyModeSet(base, key, value) { base[key] = value; }
+</script>
+<script>
+"use strict";
+
+const supportedNonIndex = "supported non-index property name";
+const supportedIndex = "supported indexed property name";
+const unsupportedNonIndex = "unsupported non-index property name";
+const unsupportedIndex = "unsupported indexed property name";
+const existingSymbol = "existing symbol property name";
+const nonExistingSymbol = "non-existing symbol property name";
+
+test(t => {
+ const { w, wp } = createWindowProperties(t);
+
+ Object.setPrototypeOf(wp, w.EventTarget.prototype); // Setting current [[Prototype]] value shouldn't throw
+
+ assert_throws_js(TypeError, () => { Object.setPrototypeOf(wp, {}); });
+ assert_throws_js(w.TypeError, () => { wp.__proto__ = null; });
+ assert_false(Reflect.setPrototypeOf(wp, w.Object.prototype));
+
+ assert_equals(Object.getPrototypeOf(wp), w.EventTarget.prototype);
+}, "[[SetPrototypeOf]] and [[GetPrototypeOf]]");
+
+test(t => {
+ const { wp } = createWindowProperties(t);
+
+ assert_throws_js(TypeError, () => { Object.preventExtensions(wp); });
+ assert_false(Reflect.preventExtensions(wp));
+
+ assert_true(Object.isExtensible(wp));
+}, "[[PreventExtensions]] and [[IsExtensible]]");
+
+test(t => {
+ const { w, wp } = createWindowProperties(t);
+
+ const elA = appendElementWithId(w, "a");
+ const el0 = appendIframeWithName(w, 0);
+
+ assert_prop_desc(Object.getOwnPropertyDescriptor(wp, "a"), elA, supportedNonIndex);
+ assert_prop_desc(Reflect.getOwnPropertyDescriptor(wp, 0), el0, supportedIndex);
+ assert_equals(Reflect.getOwnPropertyDescriptor(wp, "b"), undefined, unsupportedNonIndex);
+ assert_equals(Object.getOwnPropertyDescriptor(wp, 1), undefined, unsupportedIndex);
+}, "[[GetOwnProperty]]");
+
+test(t => {
+ const { w, wp } = createWindowProperties(t);
+
+ appendIframeWithName(w, "hasOwnProperty");
+ appendFormWithName(w, "addEventListener");
+ appendElementWithId(w, "a");
+ appendIframeWithName(w, 0);
+
+ w.Object.prototype.a = {};
+ w.EventTarget.prototype[0] = {};
+
+ // These are shadowed by properties higher in [[Prototype]] chain. See https://webidl.spec.whatwg.org/#dfn-named-property-visibility
+ assert_equals(Object.getOwnPropertyDescriptor(wp, "hasOwnProperty"), undefined, supportedNonIndex);
+ assert_equals(Reflect.getOwnPropertyDescriptor(wp, "addEventListener"), undefined, supportedNonIndex);
+ assert_equals(Object.getOwnPropertyDescriptor(wp, "a"), undefined, supportedNonIndex);
+ assert_equals(Reflect.getOwnPropertyDescriptor(wp, 0), undefined, supportedIndex);
+}, "[[GetOwnProperty]] (named property visibility algorithm)");
+
+test(t => {
+ const { w, wp } = createWindowProperties(t);
+
+ appendElementWithId(w, "a");
+ appendFormWithName(w, 0);
+
+ assert_define_own_property_fails(wp, "a", {}, supportedNonIndex);
+ assert_define_own_property_fails(wp, 0, {}, supportedIndex);
+ assert_define_own_property_fails(wp, "b", {}, unsupportedNonIndex);
+ assert_define_own_property_fails(wp, 1, {}, unsupportedIndex);
+ assert_define_own_property_fails(wp, Symbol.toStringTag, {}, existingSymbol);
+ assert_define_own_property_fails(wp, Symbol(), {}, nonExistingSymbol);
+}, "[[DefineOwnProperty]]");
+
+test(t => {
+ const { w, wp } = createWindowProperties(t);
+
+ appendFormWithName(w, "a");
+ appendElementWithId(w, 0);
+
+ assert_true("a" in wp, supportedNonIndex);
+ assert_true(Reflect.has(wp, "a"), supportedNonIndex);
+ assert_true(0 in wp, supportedIndex);
+ assert_true(Reflect.has(wp, 0), supportedIndex);
+
+ assert_false("b" in wp, unsupportedNonIndex);
+ assert_false(Reflect.has(wp, 1), unsupportedIndex);
+}, "[[HasProperty]]");
+
+test(t => {
+ const { w, wp } = createWindowProperties(t);
+ const elA = appendFormWithName(w, "a");
+ const el0 = appendIframeWithName(w, 0);
+
+ assert_equals(wp.a, elA, supportedNonIndex);
+ assert_equals(wp[0], el0, supportedIndex);
+ assert_equals(wp[Symbol.toStringTag], "WindowProperties", existingSymbol);
+
+ assert_equals(wp.b, undefined, unsupportedNonIndex);
+ assert_equals(wp[1], undefined, unsupportedIndex);
+ assert_equals(wp[Symbol.iterator], undefined, nonExistingSymbol);
+}, "[[Get]]");
+
+test(t => {
+ const { w, wp } = createWindowProperties(t);
+
+ appendIframeWithName(w, "isPrototypeOf");
+ appendFormWithName(w, "dispatchEvent");
+ appendElementWithId(w, "a");
+ appendElementWithId(w, 0);
+
+ w.EventTarget.prototype.a = 10;
+ w.Object.prototype[0] = 20;
+
+ // These are shadowed by properties higher in [[Prototype]] chain. See https://webidl.spec.whatwg.org/#dfn-named-property-visibility
+ assert_equals(wp.isPrototypeOf, w.Object.prototype.isPrototypeOf, supportedNonIndex);
+ assert_equals(wp.dispatchEvent, w.EventTarget.prototype.dispatchEvent, supportedNonIndex);
+ assert_equals(wp.a, 10, supportedNonIndex);
+ assert_equals(wp[0], 20, supportedIndex);
+}, "[[Get]] (named property visibility algorithm)");
+
+test(t => {
+ const { w, wp } = createWindowProperties(t);
+ const elA = appendIframeWithName(w, "a");
+ const el0 = appendFormWithName(w, 0);
+
+ assert_set_fails(wp, "a", supportedNonIndex);
+ assert_set_fails(wp, "b", unsupportedNonIndex);
+ assert_set_fails(wp, 0, supportedIndex);
+ assert_set_fails(wp, 1, unsupportedIndex);
+ assert_set_fails(wp, Symbol.toStringTag, existingSymbol);
+ assert_set_fails(wp, Symbol(), nonExistingSymbol);
+
+ assert_equals(wp.a, elA, supportedNonIndex);
+ assert_equals(wp[0], el0, supportedIndex);
+ assert_equals(wp.b, undefined, unsupportedNonIndex);
+ assert_equals(wp[1], undefined, unsupportedIndex);
+}, "[[Set]] (direct)");
+
+test(t => {
+ const { w, wp } = createWindowProperties(t);
+ const receiver = Object.create(wp);
+
+ appendIframeWithName(w, "a");
+ appendElementWithId(w, 0);
+
+ let setterThisValue;
+ Object.defineProperty(w.Object.prototype, 1, { set() { setterThisValue = this; } });
+ Object.defineProperty(w.EventTarget.prototype, "b", { writable: false });
+
+ receiver.a = 10;
+ assert_throws_js(TypeError, () => { receiver.b = {}; }, unsupportedNonIndex);
+ receiver[0] = 20;
+ receiver[1] = {};
+
+ assert_equals(receiver.a, 10, supportedNonIndex);
+ assert_equals(receiver[0], 20, supportedIndex);
+ assert_false(receiver.hasOwnProperty("b"), unsupportedNonIndex);
+ assert_false(receiver.hasOwnProperty(1), unsupportedIndex);
+ assert_equals(setterThisValue, receiver, "setter |this| value is receiver");
+}, "[[Set]] (prototype chain)");
+
+test(t => {
+ const { w, wp } = createWindowProperties(t);
+ const receiver = {};
+
+ appendFormWithName(w, "a");
+ appendIframeWithName(w, 0);
+
+ let setterThisValue;
+ Object.defineProperty(w.Object.prototype, "b", { set() { setterThisValue = this; } });
+ Object.defineProperty(w.EventTarget.prototype, 1, { writable: false });
+
+ assert_true(Reflect.set(wp, "a", 10, receiver), supportedNonIndex);
+ assert_true(Reflect.set(wp, 0, 20, receiver), supportedIndex);
+ assert_true(Reflect.set(wp, "b", {}, receiver), unsupportedNonIndex);
+ assert_false(Reflect.set(wp, 1, {}, receiver), unsupportedIndex);
+
+ assert_equals(receiver.a, 10, supportedNonIndex);
+ assert_equals(receiver[0], 20, supportedIndex);
+ assert_false(receiver.hasOwnProperty("b"), unsupportedNonIndex);
+ assert_equals(setterThisValue, receiver, "setter |this| value is receiver");
+ assert_false(receiver.hasOwnProperty(1), unsupportedIndex);
+}, "[[Set]] (Reflect.set)");
+
+test(t => {
+ const { w, wp } = createWindowProperties(t);
+ const elA = appendFormWithName(w, "a");
+ const el0 = appendElementWithId(w, 0);
+
+ assert_delete_fails(wp, "a", supportedNonIndex);
+ assert_delete_fails(wp, 0, supportedIndex);
+ assert_delete_fails(wp, "b", unsupportedNonIndex);
+ assert_delete_fails(wp, 1, unsupportedIndex);
+ assert_delete_fails(wp, Symbol.toStringTag, existingSymbol);
+ assert_delete_fails(wp, Symbol("foo"), nonExistingSymbol);
+
+ assert_equals(wp.a, elA, supportedNonIndex);
+ assert_equals(wp[0], el0, supportedIndex);
+ assert_equals(wp[Symbol.toStringTag], "WindowProperties", existingSymbol);
+}, "[[Delete]]");
+
+test(t => {
+ const { w, wp } = createWindowProperties(t);
+
+ appendIframeWithName(w, "a");
+ appendElementWithId(w, 0);
+ appendFormWithName(w, "b");
+
+ const forInKeys = [];
+ for (const key in wp)
+ forInKeys.push(key);
+
+ assert_array_equals(forInKeys, Object.keys(w.EventTarget.prototype));
+ assert_array_equals(Object.getOwnPropertyNames(wp), []);
+ assert_array_equals(Reflect.ownKeys(wp), [Symbol.toStringTag]);
+}, "[[OwnPropertyKeys]]");
+
+function createWindowProperties(t) {
+ const iframe = document.createElement("iframe");
+ document.body.append(iframe);
+ t.add_cleanup(() => { iframe.remove(); });
+
+ const w = iframe.contentWindow;
+ const wp = Object.getPrototypeOf(w.Window.prototype);
+ return { w, wp };
+}
+
+function appendIframeWithName(w, name) {
+ const el = w.document.createElement("iframe");
+ el.name = name;
+ w.document.body.append(el);
+ return el.contentWindow;
+}
+
+function appendFormWithName(w, name) {
+ const el = w.document.createElement("form");
+ el.name = name;
+ w.document.body.append(el);
+ return el;
+}
+
+function appendElementWithId(w, id) {
+ const el = w.document.createElement("div");
+ el.id = id;
+ w.document.body.append(el);
+ return el;
+}
+
+function assert_prop_desc(desc, value, testInfo) {
+ assert_equals(typeof desc, "object", `${testInfo} typeof desc`);
+ assert_equals(desc.value, value, `${testInfo} [[Value]]`);
+ assert_true(desc.writable, `${testInfo} [[Writable]]`);
+ assert_false(desc.enumerable, `${testInfo} [[Enumerable]]`);
+ assert_true(desc.configurable, `${testInfo} [[Configurable]]`);
+}
+
+function assert_define_own_property_fails(object, key, desc, testInfo) {
+ assert_throws_js(TypeError, () => { Object.defineProperty(object, key, desc); }, testInfo);
+ assert_false(Reflect.defineProperty(object, key, desc), testInfo);
+}
+
+function assert_set_fails(object, key, value, testInfo) {
+ sloppyModeSet(object, key, value);
+ assert_throws_js(TypeError, () => { object[key] = value; }, testInfo);
+ assert_false(Reflect.set(object, key, value), testInfo);
+}
+
+function assert_delete_fails(object, key, testInfo) {
+ assert_throws_js(TypeError, () => { delete object[key]; }, testInfo);
+ assert_false(Reflect.deleteProperty(object, key), testInfo);
+}
+</script>