diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md
index 4d35030d0ef766..3c70ab626d6a0b 100644
--- a/test/fixtures/wpt/README.md
+++ b/test/fixtures/wpt/README.md
@@ -35,6 +35,7 @@ Last update:
- wasm/webapi: https://github.com/web-platform-tests/wpt/tree/fd1b23eeaa/wasm/webapi
- web-locks: https://github.com/web-platform-tests/wpt/tree/10a122a6bc/web-locks
- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/1e4933113d/WebCryptoAPI
+- webidl: https://github.com/web-platform-tests/wpt/tree/63ca529a02/webidl
- webidl/ecmascript-binding/es-exceptions: https://github.com/web-platform-tests/wpt/tree/2f96fa1996/webidl/ecmascript-binding/es-exceptions
- webmessaging/broadcastchannel: https://github.com/web-platform-tests/wpt/tree/6495c91853/webmessaging/broadcastchannel
- webstorage: https://github.com/web-platform-tests/wpt/tree/1d2c5fb36a/webstorage
diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json
index ea80904af6527b..b764ca61fa9b4c 100644
--- a/test/fixtures/wpt/versions.json
+++ b/test/fixtures/wpt/versions.json
@@ -99,6 +99,10 @@
"commit": "1e4933113d2028e092d07a9e865db8f606b21026",
"path": "WebCryptoAPI"
},
+ "webidl": {
+ "commit": "63ca529a021fec15b2996db82fd7dd04c11abe85",
+ "path": "webidl"
+ },
"webidl/ecmascript-binding/es-exceptions": {
"commit": "2f96fa19966d6bc19e979a09479ac8a7aa337c54",
"path": "webidl/ecmascript-binding/es-exceptions"
diff --git a/test/fixtures/wpt/webidl/META.yml b/test/fixtures/wpt/webidl/META.yml
new file mode 100644
index 00000000000000..9a1192f6704842
--- /dev/null
+++ b/test/fixtures/wpt/webidl/META.yml
@@ -0,0 +1,3 @@
+spec: https://webidl.spec.whatwg.org/
+suggested_reviewers:
+ - yuki3
diff --git a/test/fixtures/wpt/webidl/README.md b/test/fixtures/wpt/webidl/README.md
new file mode 100644
index 00000000000000..136a7649981ea4
--- /dev/null
+++ b/test/fixtures/wpt/webidl/README.md
@@ -0,0 +1 @@
+Tests for the [Web IDL Standard](https://webidl.spec.whatwg.org/).
diff --git a/test/fixtures/wpt/webidl/current-realm.html b/test/fixtures/wpt/webidl/current-realm.html
new file mode 100644
index 00000000000000..29e07d1f79edc0
--- /dev/null
+++ b/test/fixtures/wpt/webidl/current-realm.html
@@ -0,0 +1,168 @@
+
+
+
+
Current Realm
+
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/allow-resizable.html b/test/fixtures/wpt/webidl/ecmascript-binding/allow-resizable.html
new file mode 100644
index 00000000000000..54daa57bce676a
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/allow-resizable.html
@@ -0,0 +1,31 @@
+
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/attributes-accessors-unique-function-objects.html b/test/fixtures/wpt/webidl/ecmascript-binding/attributes-accessors-unique-function-objects.html
new file mode 100644
index 00000000000000..167f55bcef7ead
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/attributes-accessors-unique-function-objects.html
@@ -0,0 +1,35 @@
+
+
+All attributes accessors are unique function objects
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/builtin-function-properties.any.js b/test/fixtures/wpt/webidl/ecmascript-binding/builtin-function-properties.any.js
new file mode 100644
index 00000000000000..885bb441ead442
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/webidl/ecmascript-binding/class-string-interface.any.js b/test/fixtures/wpt/webidl/ecmascript-binding/class-string-interface.any.js
new file mode 100644
index 00000000000000..ee792d5368389b
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/webidl/ecmascript-binding/class-string-iterator-prototype-object.any.js b/test/fixtures/wpt/webidl/ecmascript-binding/class-string-iterator-prototype-object.any.js
new file mode 100644
index 00000000000000..5ca549d69cff81
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/webidl/ecmascript-binding/class-string-named-properties-object.window.js b/test/fixtures/wpt/webidl/ecmascript-binding/class-string-named-properties-object.window.js
new file mode 100644
index 00000000000000..a427a2f8142e53
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/webidl/ecmascript-binding/constructors.html b/test/fixtures/wpt/webidl/ecmascript-binding/constructors.html
new file mode 100644
index 00000000000000..61993a6200ed56
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/constructors.html
@@ -0,0 +1,132 @@
+
+
+Realm for constructed objects
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/default-iterator-object.html b/test/fixtures/wpt/webidl/ecmascript-binding/default-iterator-object.html
new file mode 100644
index 00000000000000..c7e9188521a2ae
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/default-iterator-object.html
@@ -0,0 +1,27 @@
+
+
+Default iterator objects
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/default-toJSON-cross-realm.html b/test/fixtures/wpt/webidl/ecmascript-binding/default-toJSON-cross-realm.html
new file mode 100644
index 00000000000000..79c3097f339b58
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/default-toJSON-cross-realm.html
@@ -0,0 +1,26 @@
+
+
+Cross-realm [Default] toJSON() creates result object in its realm
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/global-immutable-prototype.any.js b/test/fixtures/wpt/webidl/ecmascript-binding/global-immutable-prototype.any.js
new file mode 100644
index 00000000000000..6291c3ae935608
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/webidl/ecmascript-binding/global-mutable-prototype.any.js b/test/fixtures/wpt/webidl/ecmascript-binding/global-mutable-prototype.any.js
new file mode 100644
index 00000000000000..eba96e9adf4647
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/webidl/ecmascript-binding/global-object-implicit-this-value-cross-realm.html b/test/fixtures/wpt/webidl/ecmascript-binding/global-object-implicit-this-value-cross-realm.html
new file mode 100644
index 00000000000000..b9939b801cbd80
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/global-object-implicit-this-value-cross-realm.html
@@ -0,0 +1,97 @@
+
+
+Cross-realm getter / setter / operation doesn't use lexical global object if |this| value is incompatible object / null / undefined
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/global-object-implicit-this-value.any.js b/test/fixtures/wpt/webidl/ecmascript-binding/global-object-implicit-this-value.any.js
new file mode 100644
index 00000000000000..4c159c67519c0b
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/webidl/ecmascript-binding/has-instance.html b/test/fixtures/wpt/webidl/ecmascript-binding/has-instance.html
new file mode 100644
index 00000000000000..caf0be47290607
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/has-instance.html
@@ -0,0 +1,26 @@
+
+
+instanceof behavior
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/interface-object-set-receiver.html b/test/fixtures/wpt/webidl/ecmascript-binding/interface-object-set-receiver.html
new file mode 100644
index 00000000000000..ca75a96bbad52e
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/interface-object-set-receiver.html
@@ -0,0 +1,37 @@
+
+
+window.Interface is defined on [[Set]] receiver
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/interface-object.html b/test/fixtures/wpt/webidl/ecmascript-binding/interface-object.html
new file mode 100644
index 00000000000000..132c61ddaed4a3
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/interface-object.html
@@ -0,0 +1,28 @@
+
+
+Interface objects
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/interface-prototype-constructor-set-receiver.html b/test/fixtures/wpt/webidl/ecmascript-binding/interface-prototype-constructor-set-receiver.html
new file mode 100644
index 00000000000000..64a2da8eb2da2d
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/interface-prototype-constructor-set-receiver.html
@@ -0,0 +1,36 @@
+
+
+Interface.prototype.constructor is defined on [[Set]] receiver
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/interface-prototype-object.html b/test/fixtures/wpt/webidl/ecmascript-binding/interface-prototype-object.html
new file mode 100644
index 00000000000000..299bcf926dc23d
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/interface-prototype-object.html
@@ -0,0 +1,15 @@
+
+
+Interface prototype objects
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/invalid-this-value-cross-realm.html b/test/fixtures/wpt/webidl/ecmascript-binding/invalid-this-value-cross-realm.html
new file mode 100644
index 00000000000000..0535115ac61f43
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/invalid-this-value-cross-realm.html
@@ -0,0 +1,45 @@
+
+
+Cross-realm getter / setter / operation doesn't use lexical global object to throw an error for incompatible |this| value
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/iterator-invalidation-foreach.html b/test/fixtures/wpt/webidl/ecmascript-binding/iterator-invalidation-foreach.html
new file mode 100644
index 00000000000000..9d2e3b9cb25ce6
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/iterator-invalidation-foreach.html
@@ -0,0 +1,40 @@
+
+
+Behavior of iterators when modified within foreach
+
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/iterator-prototype-object.html b/test/fixtures/wpt/webidl/ecmascript-binding/iterator-prototype-object.html
new file mode 100644
index 00000000000000..7859c1e46ac464
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/iterator-prototype-object.html
@@ -0,0 +1,47 @@
+
+
+Iterator prototype objects
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/legacy-callback-interface-object.html b/test/fixtures/wpt/webidl/ecmascript-binding/legacy-callback-interface-object.html
new file mode 100644
index 00000000000000..627d29507f7dad
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/legacy-callback-interface-object.html
@@ -0,0 +1,69 @@
+
+
+Legacy callback interface objects
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/legacy-factor-function-subclass.window.js b/test/fixtures/wpt/webidl/ecmascript-binding/legacy-factor-function-subclass.window.js
new file mode 100644
index 00000000000000..1fd64f41bb2e8e
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/webidl/ecmascript-binding/legacy-factory-function-builtin-properties.window.js b/test/fixtures/wpt/webidl/ecmascript-binding/legacy-factory-function-builtin-properties.window.js
new file mode 100644
index 00000000000000..fc5c48aca380c0
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/legacy-factory-function-builtin-properties.window.js
@@ -0,0 +1,6 @@
+"use strict";
+
+test(() => {
+ const ownPropKeys = Reflect.ownKeys(Image).slice(0, 3);
+ assert_array_equals(ownPropKeys, ["length", "name", "prototype"]);
+}, 'Legacy factory function property enumeration order of "length", "name", and "prototype"');
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/legacy-platform-object/DefineOwnProperty.html b/test/fixtures/wpt/webidl/ecmascript-binding/legacy-platform-object/DefineOwnProperty.html
new file mode 100644
index 00000000000000..bd7ba19c1a90b6
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/legacy-platform-object/DefineOwnProperty.html
@@ -0,0 +1,165 @@
+
+
+Legacy platform objects [[DefineOwnProperty]] method
+
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/legacy-platform-object/GetOwnProperty.html b/test/fixtures/wpt/webidl/ecmascript-binding/legacy-platform-object/GetOwnProperty.html
new file mode 100644
index 00000000000000..be3bcc61f0a3aa
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/legacy-platform-object/GetOwnProperty.html
@@ -0,0 +1,84 @@
+
+
+Legacy platform objects [[GetOwnProperty]] method
+
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/legacy-platform-object/OwnPropertyKeys.html b/test/fixtures/wpt/webidl/ecmascript-binding/legacy-platform-object/OwnPropertyKeys.html
new file mode 100644
index 00000000000000..d33980517b1a94
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/legacy-platform-object/OwnPropertyKeys.html
@@ -0,0 +1,65 @@
+
+
+Legacy platform objects [[OwnPropertyKeys]] method
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/legacy-platform-object/Set.html b/test/fixtures/wpt/webidl/ecmascript-binding/legacy-platform-object/Set.html
new file mode 100644
index 00000000000000..1390b51cd03b51
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/legacy-platform-object/Set.html
@@ -0,0 +1,94 @@
+
+
+Legacy platform objects [[Set]] method
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/legacy-platform-object/helper.js b/test/fixtures/wpt/webidl/ecmascript-binding/legacy-platform-object/helper.js
new file mode 100644
index 00000000000000..01c1d00694eb9b
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/webidl/ecmascript-binding/no-regexp-special-casing.any.js b/test/fixtures/wpt/webidl/ecmascript-binding/no-regexp-special-casing.any.js
new file mode 100644
index 00000000000000..4446dbf69c02ab
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/webidl/ecmascript-binding/observable-array-no-leak-of-internals.window.js b/test/fixtures/wpt/webidl/ecmascript-binding/observable-array-no-leak-of-internals.window.js
new file mode 100644
index 00000000000000..f93464005d017f
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/webidl/ecmascript-binding/observable-array-ownkeys.window.js b/test/fixtures/wpt/webidl/ecmascript-binding/observable-array-ownkeys.window.js
new file mode 100644
index 00000000000000..29b537c4750a38
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/webidl/ecmascript-binding/put-forwards.html b/test/fixtures/wpt/webidl/ecmascript-binding/put-forwards.html
new file mode 100644
index 00000000000000..7d99d65aa21385
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/put-forwards.html
@@ -0,0 +1,148 @@
+
+
+[PutForwards] behavior
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/replaceable-setter-throws-if-defineownproperty-fails.html b/test/fixtures/wpt/webidl/ecmascript-binding/replaceable-setter-throws-if-defineownproperty-fails.html
new file mode 100644
index 00000000000000..872bbff9604265
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/replaceable-setter-throws-if-defineownproperty-fails.html
@@ -0,0 +1,38 @@
+
+
+[Replaceable] setter throws TypeError if [[DefineOwnProperty]] fails
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/sequence-conversion.html b/test/fixtures/wpt/webidl/ecmascript-binding/sequence-conversion.html
new file mode 100644
index 00000000000000..40764e9f577603
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/sequence-conversion.html
@@ -0,0 +1,157 @@
+
+
+Sequence conversion
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/setter-argument.html b/test/fixtures/wpt/webidl/ecmascript-binding/setter-argument.html
new file mode 100644
index 00000000000000..bfa4291b236533
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/setter-argument.html
@@ -0,0 +1,176 @@
+
+
+Setter should treat no arguments as undefined
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/support/constructors-support.html b/test/fixtures/wpt/webidl/ecmascript-binding/support/constructors-support.html
new file mode 100644
index 00000000000000..3b2616170b1d4f
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/support/constructors-support.html
@@ -0,0 +1,8 @@
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/support/create-realm.js b/test/fixtures/wpt/webidl/ecmascript-binding/support/create-realm.js
new file mode 100644
index 00000000000000..45ded884fc1f22
--- /dev/null
+++ b/test/fixtures/wpt/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/test/fixtures/wpt/webidl/ecmascript-binding/support/dummy-iframe.html b/test/fixtures/wpt/webidl/ecmascript-binding/support/dummy-iframe.html
new file mode 100644
index 00000000000000..3f773ae6f811ac
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/support/dummy-iframe.html
@@ -0,0 +1,7 @@
+
+
+foo
+
+
diff --git a/test/fixtures/wpt/webidl/ecmascript-binding/window-named-properties-object.html b/test/fixtures/wpt/webidl/ecmascript-binding/window-named-properties-object.html
new file mode 100644
index 00000000000000..cc4976890683f4
--- /dev/null
+++ b/test/fixtures/wpt/webidl/ecmascript-binding/window-named-properties-object.html
@@ -0,0 +1,284 @@
+
+
+Internal methods of Window's named properties object
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/webidl/idlharness.any.js b/test/fixtures/wpt/webidl/idlharness.any.js
new file mode 100644
index 00000000000000..f8b285c485c43a
--- /dev/null
+++ b/test/fixtures/wpt/webidl/idlharness.any.js
@@ -0,0 +1,17 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: global=window,dedicatedworker,shadowrealm-in-window
+
+"use strict";
+
+idl_test(
+ ['webidl'],
+ [],
+ idl_array => {
+ idl_array.add_objects({
+ DOMException: ['new DOMException()',
+ 'new DOMException("my message")',
+ 'new DOMException("my message", "myName")']
+ });
+ }
+);
diff --git a/test/wpt/status/webidl.json b/test/wpt/status/webidl.json
new file mode 100644
index 00000000000000..b29a000a9d8b92
--- /dev/null
+++ b/test/wpt/status/webidl.json
@@ -0,0 +1,70 @@
+{
+ "ecmascript-binding/class-string-named-properties-object.window.js": {
+ "fail": {
+ "expected": [
+ "evaluation in WPTRunner.runJsTests()"
+ ]
+ }
+ },
+ "ecmascript-binding/global-immutable-prototype.any.js": {
+ "fail": {
+ "expected": [
+ "Setting to a different prototype"
+ ]
+ }
+ },
+ "ecmascript-binding/global-mutable-prototype.any.js": {
+ "fail": {
+ "expected": [
+ "Setting to a different prototype"
+ ]
+ }
+ },
+ "ecmascript-binding/global-object-implicit-this-value.any.js": {
+ "fail": {
+ "expected": [
+ "Global object's getter throws when called on incompatible object",
+ "Global object's setter throws when called on incompatible object",
+ "Global object's operation throws when called on incompatible object",
+ "Global object's getter works when called on null / undefined",
+ "Global object's setter works when called on null / undefined",
+ "evaluation in WPTRunner.runJsTests()"
+ ]
+ }
+ },
+ "ecmascript-binding/legacy-factor-function-subclass.window.js": {
+ "fail": {
+ "expected": [
+ "[LegacyFactoryFunction] can be subclassed and correctly handles NewTarget"
+ ]
+ }
+ },
+ "ecmascript-binding/legacy-factory-function-builtin-properties.window.js": {
+ "fail": {
+ "expected": [
+ "Legacy factory function property enumeration order of \"length\", \"name\", and \"prototype\""
+ ]
+ }
+ },
+ "ecmascript-binding/no-regexp-special-casing.any.js": {
+ "fail": {
+ "expected": [
+ "Can be used as an object implementing a callback interface"
+ ]
+ }
+ },
+ "ecmascript-binding/observable-array-no-leak-of-internals.window.js": {
+ "fail": {
+ "expected": [
+ "ObservableArray's internals won't leak"
+ ]
+ }
+ },
+ "ecmascript-binding/observable-array-ownkeys.window.js": {
+ "fail": {
+ "expected": [
+ "ObservableArray's ownKeys trap"
+ ]
+ }
+ }
+}
diff --git a/test/wpt/test-webidl.js b/test/wpt/test-webidl.js
new file mode 100644
index 00000000000000..682616608ea169
--- /dev/null
+++ b/test/wpt/test-webidl.js
@@ -0,0 +1,8 @@
+'use strict';
+
+require('../common');
+const { WPTRunner } = require('../common/wpt');
+
+const runner = new WPTRunner('webidl');
+
+runner.runJsTests();