diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md
index 927a8a6f80fb58..e4bd0ab9737bf4 100644
--- a/test/fixtures/wpt/README.md
+++ b/test/fixtures/wpt/README.md
@@ -13,6 +13,7 @@ Last update:
- common: https://github.com/web-platform-tests/wpt/tree/03c5072aff/common
- console: https://github.com/web-platform-tests/wpt/tree/3b1f72e99a/console
- dom/abort: https://github.com/web-platform-tests/wpt/tree/c49cafb491/dom/abort
+- dom/events: https://github.com/web-platform-tests/wpt/tree/f8821adb28/dom/events
- encoding: https://github.com/web-platform-tests/wpt/tree/35f70910d3/encoding
- FileAPI: https://github.com/web-platform-tests/wpt/tree/3b279420d4/FileAPI
- hr-time: https://github.com/web-platform-tests/wpt/tree/9910784394/hr-time
diff --git a/test/fixtures/wpt/dom/events/AddEventListenerOptions-once.any.js b/test/fixtures/wpt/dom/events/AddEventListenerOptions-once.any.js
new file mode 100644
index 00000000000000..b4edd4345c5e34
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/AddEventListenerOptions-once.any.js
@@ -0,0 +1,96 @@
+// META: title=AddEventListenerOptions.once
+
+"use strict";
+
+test(function() {
+ var invoked_once = false;
+ var invoked_normal = false;
+ function handler_once() {
+ invoked_once = true;
+ }
+ function handler_normal() {
+ invoked_normal = true;
+ }
+
+ const et = new EventTarget();
+ et.addEventListener('test', handler_once, {once: true});
+ et.addEventListener('test', handler_normal);
+ et.dispatchEvent(new Event('test'));
+ assert_equals(invoked_once, true, "Once handler should be invoked");
+ assert_equals(invoked_normal, true, "Normal handler should be invoked");
+
+ invoked_once = false;
+ invoked_normal = false;
+ et.dispatchEvent(new Event('test'));
+ assert_equals(invoked_once, false, "Once handler shouldn't be invoked again");
+ assert_equals(invoked_normal, true, "Normal handler should be invoked again");
+ et.removeEventListener('test', handler_normal);
+}, "Once listener should be invoked only once");
+
+test(function() {
+ const et = new EventTarget();
+ var invoked_count = 0;
+ function handler() {
+ invoked_count++;
+ if (invoked_count == 1)
+ et.dispatchEvent(new Event('test'));
+ }
+ et.addEventListener('test', handler, {once: true});
+ et.dispatchEvent(new Event('test'));
+ assert_equals(invoked_count, 1, "Once handler should only be invoked once");
+
+ invoked_count = 0;
+ function handler2() {
+ invoked_count++;
+ if (invoked_count == 1)
+ et.addEventListener('test', handler2, {once: true});
+ if (invoked_count <= 2)
+ et.dispatchEvent(new Event('test'));
+ }
+ et.addEventListener('test', handler2, {once: true});
+ et.dispatchEvent(new Event('test'));
+ assert_equals(invoked_count, 2, "Once handler should only be invoked once after each adding");
+}, "Once listener should be invoked only once even if the event is nested");
+
+test(function() {
+ var invoked_count = 0;
+ function handler() {
+ invoked_count++;
+ }
+
+ const et = new EventTarget();
+
+ et.addEventListener('test', handler, {once: true});
+ et.addEventListener('test', handler);
+ et.dispatchEvent(new Event('test'));
+ assert_equals(invoked_count, 1, "The handler should only be added once");
+
+ invoked_count = 0;
+ et.dispatchEvent(new Event('test'));
+ assert_equals(invoked_count, 0, "The handler was added as a once listener");
+
+ invoked_count = 0;
+ et.addEventListener('test', handler, {once: true});
+ et.removeEventListener('test', handler);
+ et.dispatchEvent(new Event('test'));
+ assert_equals(invoked_count, 0, "The handler should have been removed");
+}, "Once listener should be added / removed like normal listeners");
+
+test(function() {
+ const et = new EventTarget();
+
+ var invoked_count = 0;
+
+ for (let n = 4; n > 0; n--) {
+ et.addEventListener('test', (e) => {
+ invoked_count++;
+ e.stopImmediatePropagation();
+ }, {once: true});
+ }
+
+ for (let n = 4; n > 0; n--) {
+ et.dispatchEvent(new Event('test'));
+ }
+
+ assert_equals(invoked_count, 4, "The listeners should be invoked");
+}, "Multiple once listeners should be invoked even if the stopImmediatePropagation is set");
diff --git a/test/fixtures/wpt/dom/events/AddEventListenerOptions-passive.any.js b/test/fixtures/wpt/dom/events/AddEventListenerOptions-passive.any.js
new file mode 100644
index 00000000000000..8e59cf5b379f2e
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/AddEventListenerOptions-passive.any.js
@@ -0,0 +1,134 @@
+// META: title=AddEventListenerOptions.passive
+
+test(function() {
+ var supportsPassive = false;
+ var query_options = {
+ get passive() {
+ supportsPassive = true;
+ return false;
+ },
+ get dummy() {
+ assert_unreached("dummy value getter invoked");
+ return false;
+ }
+ };
+
+ const et = new EventTarget();
+ et.addEventListener('test_event', null, query_options);
+ assert_true(supportsPassive, "addEventListener doesn't support the passive option");
+
+ supportsPassive = false;
+ et.removeEventListener('test_event', null, query_options);
+ assert_false(supportsPassive, "removeEventListener supports the passive option when it should not");
+}, "Supports passive option on addEventListener only");
+
+function testPassiveValue(optionsValue, expectedDefaultPrevented, existingEventTarget) {
+ var defaultPrevented = undefined;
+ var handler = function handler(e) {
+ assert_false(e.defaultPrevented, "Event prematurely marked defaultPrevented");
+ e.preventDefault();
+ defaultPrevented = e.defaultPrevented;
+ }
+ const et = existingEventTarget || new EventTarget();
+ et.addEventListener('test', handler, optionsValue);
+ var uncanceled = et.dispatchEvent(new Event('test', {bubbles: true, cancelable: true}));
+
+ assert_equals(defaultPrevented, expectedDefaultPrevented, "Incorrect defaultPrevented for options: " + JSON.stringify(optionsValue));
+ assert_equals(uncanceled, !expectedDefaultPrevented, "Incorrect return value from dispatchEvent");
+
+ et.removeEventListener('test', handler, optionsValue);
+}
+
+test(function() {
+ testPassiveValue(undefined, true);
+ testPassiveValue({}, true);
+ testPassiveValue({passive: false}, true);
+ testPassiveValue({passive: true}, false);
+ testPassiveValue({passive: 0}, true);
+ testPassiveValue({passive: 1}, false);
+}, "preventDefault should be ignored if-and-only-if the passive option is true");
+
+function testPassiveValueOnReturnValue(test, optionsValue, expectedDefaultPrevented) {
+ var defaultPrevented = undefined;
+ var handler = test.step_func(e => {
+ assert_false(e.defaultPrevented, "Event prematurely marked defaultPrevented");
+ e.returnValue = false;
+ defaultPrevented = e.defaultPrevented;
+ });
+ const et = new EventTarget();
+ et.addEventListener('test', handler, optionsValue);
+ var uncanceled = et.dispatchEvent(new Event('test', {bubbles: true, cancelable: true}));
+
+ assert_equals(defaultPrevented, expectedDefaultPrevented, "Incorrect defaultPrevented for options: " + JSON.stringify(optionsValue));
+ assert_equals(uncanceled, !expectedDefaultPrevented, "Incorrect return value from dispatchEvent");
+
+ et.removeEventListener('test', handler, optionsValue);
+}
+
+async_test(t => {
+ testPassiveValueOnReturnValue(t, undefined, true);
+ testPassiveValueOnReturnValue(t, {}, true);
+ testPassiveValueOnReturnValue(t, {passive: false}, true);
+ testPassiveValueOnReturnValue(t, {passive: true}, false);
+ testPassiveValueOnReturnValue(t, {passive: 0}, true);
+ testPassiveValueOnReturnValue(t, {passive: 1}, false);
+ t.done();
+}, "returnValue should be ignored if-and-only-if the passive option is true");
+
+function testPassiveWithOtherHandlers(optionsValue, expectedDefaultPrevented) {
+ var handlerInvoked1 = false;
+ var dummyHandler1 = function() {
+ handlerInvoked1 = true;
+ };
+ var handlerInvoked2 = false;
+ var dummyHandler2 = function() {
+ handlerInvoked2 = true;
+ };
+
+ const et = new EventTarget();
+ et.addEventListener('test', dummyHandler1, {passive:true});
+ et.addEventListener('test', dummyHandler2);
+
+ testPassiveValue(optionsValue, expectedDefaultPrevented, et);
+
+ assert_true(handlerInvoked1, "Extra passive handler not invoked");
+ assert_true(handlerInvoked2, "Extra non-passive handler not invoked");
+
+ et.removeEventListener('test', dummyHandler1);
+ et.removeEventListener('test', dummyHandler2);
+}
+
+test(function() {
+ testPassiveWithOtherHandlers({}, true);
+ testPassiveWithOtherHandlers({passive: false}, true);
+ testPassiveWithOtherHandlers({passive: true}, false);
+}, "passive behavior of one listener should be unaffected by the presence of other listeners");
+
+function testOptionEquivalence(optionValue1, optionValue2, expectedEquality) {
+ var invocationCount = 0;
+ var handler = function handler(e) {
+ invocationCount++;
+ }
+ const et = new EventTarget();
+ et.addEventListener('test', handler, optionValue1);
+ et.addEventListener('test', handler, optionValue2);
+ et.dispatchEvent(new Event('test', {bubbles: true}));
+ assert_equals(invocationCount, expectedEquality ? 1 : 2, "equivalence of options " +
+ JSON.stringify(optionValue1) + " and " + JSON.stringify(optionValue2));
+ et.removeEventListener('test', handler, optionValue1);
+ et.removeEventListener('test', handler, optionValue2);
+}
+
+test(function() {
+ // Sanity check options that should be treated as distinct handlers
+ testOptionEquivalence({capture:true}, {capture:false, passive:false}, false);
+ testOptionEquivalence({capture:true}, {passive:true}, false);
+
+ // Option values that should be treated as equivalent
+ testOptionEquivalence({}, {passive:false}, true);
+ testOptionEquivalence({passive:true}, {passive:false}, true);
+ testOptionEquivalence(undefined, {passive:true}, true);
+ testOptionEquivalence({capture: true, passive: false}, {capture: true, passive: true}, true);
+
+}, "Equivalence of option values");
+
diff --git a/test/fixtures/wpt/dom/events/AddEventListenerOptions-signal.any.js b/test/fixtures/wpt/dom/events/AddEventListenerOptions-signal.any.js
new file mode 100644
index 00000000000000..e6a34261594bca
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/AddEventListenerOptions-signal.any.js
@@ -0,0 +1,143 @@
+'use strict';
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('test', handler, { signal: controller.signal });
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 1, "Adding a signal still adds a listener");
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 2, "The listener was not added with the once flag");
+ controller.abort();
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 2, "Aborting on the controller removes the listener");
+ et.addEventListener('test', handler, { signal: controller.signal });
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 2, "Passing an aborted signal never adds the handler");
+}, "Passing an AbortSignal to addEventListener options should allow removing a listener");
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('test', handler, { signal: controller.signal });
+ et.removeEventListener('test', handler);
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 0, "The listener was still removed");
+}, "Passing an AbortSignal to addEventListener does not prevent removeEventListener");
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('test', handler, { signal: controller.signal, once: true });
+ controller.abort();
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 0, "The listener was still removed");
+}, "Passing an AbortSignal to addEventListener works with the once flag");
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('test', handler, { signal: controller.signal, once: true });
+ et.removeEventListener('test', handler);
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 0, "The listener was still removed");
+}, "Removing a once listener works with a passed signal");
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('first', handler, { signal: controller.signal, once: true });
+ et.addEventListener('second', handler, { signal: controller.signal, once: true });
+ controller.abort();
+ et.dispatchEvent(new Event('first'));
+ et.dispatchEvent(new Event('second'));
+ assert_equals(count, 0, "The listener was still removed");
+}, "Passing an AbortSignal to multiple listeners");
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('test', handler, { signal: controller.signal, capture: true });
+ controller.abort();
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 0, "The listener was still removed");
+}, "Passing an AbortSignal to addEventListener works with the capture flag");
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('test', () => {
+ controller.abort();
+ }, { signal: controller.signal });
+ et.addEventListener('test', handler, { signal: controller.signal });
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 0, "The listener was still removed");
+}, "Aborting from a listener does not call future listeners");
+
+test(function() {
+ let count = 0;
+ function handler() {
+ count++;
+ }
+ const et = new EventTarget();
+ const controller = new AbortController();
+ et.addEventListener('test', () => {
+ et.addEventListener('test', handler, { signal: controller.signal });
+ controller.abort();
+ }, { signal: controller.signal });
+ et.dispatchEvent(new Event('test'));
+ assert_equals(count, 0, "The listener was still removed");
+}, "Adding then aborting a listener in another listener does not call it");
+
+test(function() {
+ const et = new EventTarget();
+ const ac = new AbortController();
+ let count = 0;
+ et.addEventListener('foo', () => {
+ et.addEventListener('foo', () => {
+ count++;
+ if (count > 5) ac.abort();
+ et.dispatchEvent(new Event('foo'));
+ }, { signal: ac.signal });
+ et.dispatchEvent(new Event('foo'));
+ }, { once: true });
+ et.dispatchEvent(new Event('foo'));
+}, "Aborting from a nested listener should remove it");
+
+test(function() {
+ const et = new EventTarget();
+ assert_throws_js(TypeError, () => { et.addEventListener("foo", () => {}, { signal: null }); });
+}, "Passing null as the signal should throw");
+
+test(function() {
+ const et = new EventTarget();
+ assert_throws_js(TypeError, () => { et.addEventListener("foo", null, { signal: null }); });
+}, "Passing null as the signal should throw (listener is also null)");
diff --git a/test/fixtures/wpt/dom/events/CustomEvent.html b/test/fixtures/wpt/dom/events/CustomEvent.html
new file mode 100644
index 00000000000000..87050943f9bffa
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/CustomEvent.html
@@ -0,0 +1,35 @@
+
+
CustomEvent
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-cancelBubble.html b/test/fixtures/wpt/dom/events/Event-cancelBubble.html
new file mode 100644
index 00000000000000..d8d2d7239d025f
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-cancelBubble.html
@@ -0,0 +1,132 @@
+
+
+
+
+ Event.cancelBubble
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-constants.html b/test/fixtures/wpt/dom/events/Event-constants.html
new file mode 100644
index 00000000000000..635e9894d9a3a4
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-constants.html
@@ -0,0 +1,23 @@
+
+Event constants
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-constructors.any.js b/test/fixtures/wpt/dom/events/Event-constructors.any.js
new file mode 100644
index 00000000000000..aced2f3c2cda61
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-constructors.any.js
@@ -0,0 +1,114 @@
+// META: title=Event constructors
+
+test(function() {
+ assert_throws_js(TypeError, function() {
+ new Event()
+ })
+})
+test(function() {
+ var test_error = { name: "test" }
+ assert_throws_exactly(test_error, function() {
+ new Event({ toString: function() { throw test_error; } })
+ })
+})
+test(function() {
+ var ev = new Event("")
+ assert_equals(ev.type, "")
+ assert_equals(ev.target, null)
+ assert_equals(ev.srcElement, null)
+ assert_equals(ev.currentTarget, null)
+ assert_equals(ev.eventPhase, Event.NONE)
+ assert_equals(ev.bubbles, false)
+ assert_equals(ev.cancelable, false)
+ assert_equals(ev.defaultPrevented, false)
+ assert_equals(ev.returnValue, true)
+ assert_equals(ev.isTrusted, false)
+ assert_true(ev.timeStamp > 0)
+ assert_true("initEvent" in ev)
+})
+test(function() {
+ var ev = new Event("test")
+ assert_equals(ev.type, "test")
+ assert_equals(ev.target, null)
+ assert_equals(ev.srcElement, null)
+ assert_equals(ev.currentTarget, null)
+ assert_equals(ev.eventPhase, Event.NONE)
+ assert_equals(ev.bubbles, false)
+ assert_equals(ev.cancelable, false)
+ assert_equals(ev.defaultPrevented, false)
+ assert_equals(ev.returnValue, true)
+ assert_equals(ev.isTrusted, false)
+ assert_true(ev.timeStamp > 0)
+ assert_true("initEvent" in ev)
+})
+test(function() {
+ assert_throws_js(TypeError, function() { Event("test") },
+ 'Calling Event constructor without "new" must throw');
+})
+test(function() {
+ var ev = new Event("I am an event", { bubbles: true, cancelable: false})
+ assert_equals(ev.type, "I am an event")
+ assert_equals(ev.bubbles, true)
+ assert_equals(ev.cancelable, false)
+})
+test(function() {
+ var ev = new Event("@", { bubblesIGNORED: true, cancelable: true})
+ assert_equals(ev.type, "@")
+ assert_equals(ev.bubbles, false)
+ assert_equals(ev.cancelable, true)
+})
+test(function() {
+ var ev = new Event("@", { "bubbles\0IGNORED": true, cancelable: true})
+ assert_equals(ev.type, "@")
+ assert_equals(ev.bubbles, false)
+ assert_equals(ev.cancelable, true)
+})
+test(function() {
+ var ev = new Event("Xx", { cancelable: true})
+ assert_equals(ev.type, "Xx")
+ assert_equals(ev.bubbles, false)
+ assert_equals(ev.cancelable, true)
+})
+test(function() {
+ var ev = new Event("Xx", {})
+ assert_equals(ev.type, "Xx")
+ assert_equals(ev.bubbles, false)
+ assert_equals(ev.cancelable, false)
+})
+test(function() {
+ var ev = new Event("Xx", {bubbles: true, cancelable: false, sweet: "x"})
+ assert_equals(ev.type, "Xx")
+ assert_equals(ev.bubbles, true)
+ assert_equals(ev.cancelable, false)
+ assert_equals(ev.sweet, undefined)
+})
+test(function() {
+ var called = []
+ var ev = new Event("Xx", {
+ get cancelable() {
+ called.push("cancelable")
+ return false
+ },
+ get bubbles() {
+ called.push("bubbles")
+ return true;
+ },
+ get sweet() {
+ called.push("sweet")
+ return "x"
+ }
+ })
+ assert_array_equals(called, ["bubbles", "cancelable"])
+ assert_equals(ev.type, "Xx")
+ assert_equals(ev.bubbles, true)
+ assert_equals(ev.cancelable, false)
+ assert_equals(ev.sweet, undefined)
+})
+test(function() {
+ var ev = new CustomEvent("$", {detail: 54, sweet: "x", sweet2: "x", cancelable:true})
+ assert_equals(ev.type, "$")
+ assert_equals(ev.bubbles, false)
+ assert_equals(ev.cancelable, true)
+ assert_equals(ev.sweet, undefined)
+ assert_equals(ev.detail, 54)
+})
diff --git a/test/fixtures/wpt/dom/events/Event-defaultPrevented-after-dispatch.html b/test/fixtures/wpt/dom/events/Event-defaultPrevented-after-dispatch.html
new file mode 100644
index 00000000000000..8fef005eb57772
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-defaultPrevented-after-dispatch.html
@@ -0,0 +1,44 @@
+
+
+Event.defaultPrevented is not reset after dispatchEvent()
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-defaultPrevented.html b/test/fixtures/wpt/dom/events/Event-defaultPrevented.html
new file mode 100644
index 00000000000000..2548fa3e064bb1
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-defaultPrevented.html
@@ -0,0 +1,55 @@
+
+Event.defaultPrevented
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-bubble-canceled.html b/test/fixtures/wpt/dom/events/Event-dispatch-bubble-canceled.html
new file mode 100644
index 00000000000000..20f398f66f9bcf
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-bubble-canceled.html
@@ -0,0 +1,59 @@
+
+
+
+Setting cancelBubble=true prior to dispatchEvent()
+
+
+
+
+
+
+
+
+
+ Shady Grove |
+ Aeolian |
+
+
+ Over the river, Charlie |
+ Dorian |
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-bubbles-false.html b/test/fixtures/wpt/dom/events/Event-dispatch-bubbles-false.html
new file mode 100644
index 00000000000000..0f43cb0275662d
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-bubbles-false.html
@@ -0,0 +1,98 @@
+
+
+ Event.bubbles attribute is set to false
+
+
+
+
+
+
+
+
+ Shady Grove |
+ Aeolian |
+
+
+ Over the river, Charlie |
+ Dorian |
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-bubbles-true.html b/test/fixtures/wpt/dom/events/Event-dispatch-bubbles-true.html
new file mode 100644
index 00000000000000..b23605a1ebc318
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-bubbles-true.html
@@ -0,0 +1,108 @@
+
+
+ Event.bubbles attribute is set to false
+
+
+
+
+
+
+
+
+ Shady Grove |
+ Aeolian |
+
+
+ Over the river, Charlie |
+ Dorian |
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-click.html b/test/fixtures/wpt/dom/events/Event-dispatch-click.html
new file mode 100644
index 00000000000000..010305775df7e9
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-click.html
@@ -0,0 +1,369 @@
+
+Synthetic click event "magic"
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-click.tentative.html b/test/fixtures/wpt/dom/events/Event-dispatch-click.tentative.html
new file mode 100644
index 00000000000000..cfdae55ef2ab48
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-click.tentative.html
@@ -0,0 +1,78 @@
+
+Clicks on input element
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-detached-click.html b/test/fixtures/wpt/dom/events/Event-dispatch-detached-click.html
new file mode 100644
index 00000000000000..76ea3d78ba74e0
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-detached-click.html
@@ -0,0 +1,20 @@
+
+Click event on an element not in the document
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-detached-input-and-change.html b/test/fixtures/wpt/dom/events/Event-dispatch-detached-input-and-change.html
new file mode 100644
index 00000000000000..a53ae71ac2a08d
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-detached-input-and-change.html
@@ -0,0 +1,190 @@
+
+
+
+input and change events for detached checkbox and radio elements
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-handlers-changed.html b/test/fixtures/wpt/dom/events/Event-dispatch-handlers-changed.html
new file mode 100644
index 00000000000000..24e6fd70cb4ec4
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-handlers-changed.html
@@ -0,0 +1,91 @@
+
+
+ Dispatch additional events inside an event listener
+
+
+
+
+
+
+
+
+ Shady Grove |
+ Aeolian |
+
+
+ Over the river, Charlie |
+ Dorian |
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-listener-order.window.js b/test/fixtures/wpt/dom/events/Event-dispatch-listener-order.window.js
new file mode 100644
index 00000000000000..a01a472872b927
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-listener-order.window.js
@@ -0,0 +1,20 @@
+test(t => {
+ const hostParent = document.createElement("section"),
+ host = hostParent.appendChild(document.createElement("div")),
+ shadowRoot = host.attachShadow({ mode: "closed" }),
+ targetParent = shadowRoot.appendChild(document.createElement("p")),
+ target = targetParent.appendChild(document.createElement("span")),
+ path = [hostParent, host, shadowRoot, targetParent, target],
+ expected = [],
+ result = [];
+ path.forEach((node, index) => {
+ expected.splice(index, 0, "capturing " + node.nodeName);
+ expected.splice(index + 1, 0, "bubbling " + node.nodeName);
+ });
+ path.forEach(node => {
+ node.addEventListener("test", () => { result.push("bubbling " + node.nodeName) });
+ node.addEventListener("test", () => { result.push("capturing " + node.nodeName) }, true);
+ });
+ target.dispatchEvent(new CustomEvent('test', { detail: {}, bubbles: true, composed: true }));
+ assert_array_equals(result, expected);
+});
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-multiple-cancelBubble.html b/test/fixtures/wpt/dom/events/Event-dispatch-multiple-cancelBubble.html
new file mode 100644
index 00000000000000..2873fd7794b782
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-multiple-cancelBubble.html
@@ -0,0 +1,51 @@
+
+
+
+Multiple dispatchEvent() and cancelBubble
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-multiple-stopPropagation.html b/test/fixtures/wpt/dom/events/Event-dispatch-multiple-stopPropagation.html
new file mode 100644
index 00000000000000..72644bd861ea1d
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-multiple-stopPropagation.html
@@ -0,0 +1,51 @@
+
+
+
+ Multiple dispatchEvent() and stopPropagation()
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-omitted-capture.html b/test/fixtures/wpt/dom/events/Event-dispatch-omitted-capture.html
new file mode 100644
index 00000000000000..77074d9a3ec2b3
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-omitted-capture.html
@@ -0,0 +1,70 @@
+
+
+EventTarget.addEventListener: capture argument omitted
+
+
+
+
+
+
+
+
+ Shady Grove |
+ Aeolian |
+
+
+ Over the river, Charlie |
+ Dorian |
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-on-disabled-elements.html b/test/fixtures/wpt/dom/events/Event-dispatch-on-disabled-elements.html
new file mode 100644
index 00000000000000..361006a7240496
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-on-disabled-elements.html
@@ -0,0 +1,251 @@
+
+
+
+Events must dispatch on disabled elements
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-order-at-target.html b/test/fixtures/wpt/dom/events/Event-dispatch-order-at-target.html
new file mode 100644
index 00000000000000..79673c32564cbe
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-order-at-target.html
@@ -0,0 +1,31 @@
+
+
+Listeners are invoked in correct order (AT_TARGET phase)
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-order.html b/test/fixtures/wpt/dom/events/Event-dispatch-order.html
new file mode 100644
index 00000000000000..ca94434595c6ee
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-order.html
@@ -0,0 +1,26 @@
+
+Event phases order
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-other-document.html b/test/fixtures/wpt/dom/events/Event-dispatch-other-document.html
new file mode 100644
index 00000000000000..689b48087a0327
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-other-document.html
@@ -0,0 +1,23 @@
+
+Custom event on an element in another document
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-propagation-stopped.html b/test/fixtures/wpt/dom/events/Event-dispatch-propagation-stopped.html
new file mode 100644
index 00000000000000..889f8cfe11489d
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-propagation-stopped.html
@@ -0,0 +1,59 @@
+
+
+
+ Calling stopPropagation() prior to dispatchEvent()
+
+
+
+
+
+
+
+
+
+ Shady Grove |
+ Aeolian |
+
+
+ Over the river, Charlie |
+ Dorian |
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-redispatch.html b/test/fixtures/wpt/dom/events/Event-dispatch-redispatch.html
new file mode 100644
index 00000000000000..cf861ca17744c2
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-redispatch.html
@@ -0,0 +1,124 @@
+
+
+EventTarget#dispatchEvent(): redispatching a native event
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-reenter.html b/test/fixtures/wpt/dom/events/Event-dispatch-reenter.html
new file mode 100644
index 00000000000000..71f8517bdd9dbe
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-reenter.html
@@ -0,0 +1,66 @@
+
+
+ Dispatch additional events inside an event listener
+
+
+
+
+
+
+ Shady Grove |
+ Aeolian |
+
+
+ Over the river, Charlie |
+ Dorian |
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-target-moved.html b/test/fixtures/wpt/dom/events/Event-dispatch-target-moved.html
new file mode 100644
index 00000000000000..facb2c7b9568f6
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-target-moved.html
@@ -0,0 +1,73 @@
+
+
+ Determined event propagation path - target moved
+
+
+
+
+
+
+
+ Shady Grove |
+ Aeolian |
+
+
+ Over the river, Charlie |
+ Dorian |
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-target-removed.html b/test/fixtures/wpt/dom/events/Event-dispatch-target-removed.html
new file mode 100644
index 00000000000000..531799c3ade6b6
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-target-removed.html
@@ -0,0 +1,72 @@
+
+
+Determined event propagation path - target removed
+
+
+
+
+
+
+
+ Shady Grove |
+ Aeolian |
+
+
+ Over the river, Charlie |
+ Dorian |
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-throwing.html b/test/fixtures/wpt/dom/events/Event-dispatch-throwing.html
new file mode 100644
index 00000000000000..7d1c0d94a0845b
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-dispatch-throwing.html
@@ -0,0 +1,51 @@
+
+
+Throwing in event listeners
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-init-while-dispatching.html b/test/fixtures/wpt/dom/events/Event-init-while-dispatching.html
new file mode 100644
index 00000000000000..2aa1f6701c4c4b
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-init-while-dispatching.html
@@ -0,0 +1,83 @@
+
+
+Re-initializing events while dispatching them
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-initEvent.html b/test/fixtures/wpt/dom/events/Event-initEvent.html
new file mode 100644
index 00000000000000..ad1018d4daf7ba
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-initEvent.html
@@ -0,0 +1,136 @@
+
+Event.initEvent
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-isTrusted.any.js b/test/fixtures/wpt/dom/events/Event-isTrusted.any.js
new file mode 100644
index 00000000000000..00bcecd0ed67a0
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-isTrusted.any.js
@@ -0,0 +1,11 @@
+test(function() {
+ var desc1 = Object.getOwnPropertyDescriptor(new Event("x"), "isTrusted");
+ assert_not_equals(desc1, undefined);
+ assert_equals(typeof desc1.get, "function");
+
+ var desc2 = Object.getOwnPropertyDescriptor(new Event("x"), "isTrusted");
+ assert_not_equals(desc2, undefined);
+ assert_equals(typeof desc2.get, "function");
+
+ assert_equals(desc1.get, desc2.get);
+});
diff --git a/test/fixtures/wpt/dom/events/Event-propagation.html b/test/fixtures/wpt/dom/events/Event-propagation.html
new file mode 100644
index 00000000000000..33989eb4bf9526
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-propagation.html
@@ -0,0 +1,48 @@
+
+Event propagation tests
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-returnValue.html b/test/fixtures/wpt/dom/events/Event-returnValue.html
new file mode 100644
index 00000000000000..08df2d41416ffc
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-returnValue.html
@@ -0,0 +1,64 @@
+
+
+
+
+ Event.returnValue
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-stopImmediatePropagation.html b/test/fixtures/wpt/dom/events/Event-stopImmediatePropagation.html
new file mode 100644
index 00000000000000..b75732257a89dd
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-stopImmediatePropagation.html
@@ -0,0 +1,34 @@
+
+
+Event's stopImmediatePropagation
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-stopPropagation-cancel-bubbling.html b/test/fixtures/wpt/dom/events/Event-stopPropagation-cancel-bubbling.html
new file mode 100644
index 00000000000000..5c2c49f33826a9
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-stopPropagation-cancel-bubbling.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-subclasses-constructors.html b/test/fixtures/wpt/dom/events/Event-subclasses-constructors.html
new file mode 100644
index 00000000000000..08a5ded011635c
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-subclasses-constructors.html
@@ -0,0 +1,179 @@
+
+
+Event constructors
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-timestamp-cross-realm-getter.html b/test/fixtures/wpt/dom/events/Event-timestamp-cross-realm-getter.html
new file mode 100644
index 00000000000000..45823de26b27f9
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-timestamp-cross-realm-getter.html
@@ -0,0 +1,27 @@
+
+
+event.timeStamp is initialized using event's relevant global object
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-timestamp-high-resolution.html b/test/fixtures/wpt/dom/events/Event-timestamp-high-resolution.html
new file mode 100644
index 00000000000000..a049fef64b4c7d
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-timestamp-high-resolution.html
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-timestamp-high-resolution.https.html b/test/fixtures/wpt/dom/events/Event-timestamp-high-resolution.https.html
new file mode 100644
index 00000000000000..70f974294796b5
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-timestamp-high-resolution.https.html
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-timestamp-safe-resolution.html b/test/fixtures/wpt/dom/events/Event-timestamp-safe-resolution.html
new file mode 100644
index 00000000000000..24f2dec93c5ae8
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-timestamp-safe-resolution.html
@@ -0,0 +1,49 @@
+
+
+
+
\ No newline at end of file
diff --git a/test/fixtures/wpt/dom/events/Event-type-empty.html b/test/fixtures/wpt/dom/events/Event-type-empty.html
new file mode 100644
index 00000000000000..225b85a613a655
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-type-empty.html
@@ -0,0 +1,35 @@
+
+Event.type set to the empty string
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/Event-type.html b/test/fixtures/wpt/dom/events/Event-type.html
new file mode 100644
index 00000000000000..22792f5c6c7883
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/Event-type.html
@@ -0,0 +1,22 @@
+
+Event.type
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/EventListener-addEventListener.sub.window.js b/test/fixtures/wpt/dom/events/EventListener-addEventListener.sub.window.js
new file mode 100644
index 00000000000000..b44bc332859da5
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventListener-addEventListener.sub.window.js
@@ -0,0 +1,9 @@
+async_test(function(t) {
+ let crossOriginFrame = document.createElement('iframe');
+ crossOriginFrame.src = 'https://{{hosts[alt][]}}:{{ports[https][0]}}/common/blank.html';
+ document.body.appendChild(crossOriginFrame);
+ crossOriginFrame.addEventListener('load', t.step_func_done(function() {
+ let crossOriginWindow = crossOriginFrame.contentWindow;
+ window.addEventListener('click', crossOriginWindow);
+ }));
+}, "EventListener.addEventListener doesn't throw when a cross origin object is passed in.");
diff --git a/test/fixtures/wpt/dom/events/EventListener-handleEvent-cross-realm.html b/test/fixtures/wpt/dom/events/EventListener-handleEvent-cross-realm.html
new file mode 100644
index 00000000000000..663d04213f82bb
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventListener-handleEvent-cross-realm.html
@@ -0,0 +1,75 @@
+
+
+Cross-realm EventListener throws TypeError of its associated Realm
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/EventListener-handleEvent.html b/test/fixtures/wpt/dom/events/EventListener-handleEvent.html
new file mode 100644
index 00000000000000..06bc1f6e2ab267
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventListener-handleEvent.html
@@ -0,0 +1,102 @@
+
+
+EventListener::handleEvent()
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/EventListener-incumbent-global-1.sub.html b/test/fixtures/wpt/dom/events/EventListener-incumbent-global-1.sub.html
new file mode 100644
index 00000000000000..9d941385cbc483
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventListener-incumbent-global-1.sub.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/EventListener-incumbent-global-2.sub.html b/test/fixtures/wpt/dom/events/EventListener-incumbent-global-2.sub.html
new file mode 100644
index 00000000000000..4433c098d75c58
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventListener-incumbent-global-2.sub.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/EventListener-incumbent-global-subframe-1.sub.html b/test/fixtures/wpt/dom/events/EventListener-incumbent-global-subframe-1.sub.html
new file mode 100644
index 00000000000000..25487cc5e0bcb1
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventListener-incumbent-global-subframe-1.sub.html
@@ -0,0 +1,13 @@
+
+
+
diff --git a/test/fixtures/wpt/dom/events/EventListener-incumbent-global-subframe-2.sub.html b/test/fixtures/wpt/dom/events/EventListener-incumbent-global-subframe-2.sub.html
new file mode 100644
index 00000000000000..9c7235e2ad4358
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventListener-incumbent-global-subframe-2.sub.html
@@ -0,0 +1,13 @@
+
+
+
diff --git a/test/fixtures/wpt/dom/events/EventListener-incumbent-global-subsubframe.sub.html b/test/fixtures/wpt/dom/events/EventListener-incumbent-global-subsubframe.sub.html
new file mode 100644
index 00000000000000..dd683f6f65f89f
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventListener-incumbent-global-subsubframe.sub.html
@@ -0,0 +1,20 @@
+
+
diff --git a/test/fixtures/wpt/dom/events/EventListener-invoke-legacy.html b/test/fixtures/wpt/dom/events/EventListener-invoke-legacy.html
new file mode 100644
index 00000000000000..a01afcd8d1985c
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventListener-invoke-legacy.html
@@ -0,0 +1,66 @@
+
+
+Invoke legacy event listener
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/EventListenerOptions-capture.html b/test/fixtures/wpt/dom/events/EventListenerOptions-capture.html
new file mode 100644
index 00000000000000..f72cf3ca5463f1
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventListenerOptions-capture.html
@@ -0,0 +1,98 @@
+
+
+EventListenerOptions.capture
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/EventTarget-add-listener-platform-object.html b/test/fixtures/wpt/dom/events/EventTarget-add-listener-platform-object.html
new file mode 100644
index 00000000000000..d5565c22b3d5af
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventTarget-add-listener-platform-object.html
@@ -0,0 +1,32 @@
+
+
+addEventListener with a platform object
+
+
+
+Click me!
+
diff --git a/test/fixtures/wpt/dom/events/EventTarget-add-remove-listener.any.js b/test/fixtures/wpt/dom/events/EventTarget-add-remove-listener.any.js
new file mode 100644
index 00000000000000..b1d7ffb3e07fbe
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventTarget-add-remove-listener.any.js
@@ -0,0 +1,21 @@
+// META: title=EventTarget's addEventListener + removeEventListener
+
+"use strict";
+
+function listener(evt) {
+ evt.preventDefault();
+ return false;
+}
+
+test(() => {
+ const et = new EventTarget();
+ et.addEventListener("x", listener, false);
+ let event = new Event("x", { cancelable: true });
+ let ret = et.dispatchEvent(event);
+ assert_false(ret);
+
+ et.removeEventListener("x", listener);
+ event = new Event("x", { cancelable: true });
+ ret = et.dispatchEvent(event);
+ assert_true(ret);
+}, "Removing an event listener without explicit capture arg should succeed");
diff --git a/test/fixtures/wpt/dom/events/EventTarget-addEventListener.any.js b/test/fixtures/wpt/dom/events/EventTarget-addEventListener.any.js
new file mode 100644
index 00000000000000..e22da4aff850a2
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventTarget-addEventListener.any.js
@@ -0,0 +1,9 @@
+// META: title=EventTarget.addEventListener
+
+// Step 1.
+test(function() {
+ const et = new EventTarget();
+ assert_equals(et.addEventListener("x", null, false), undefined);
+ assert_equals(et.addEventListener("x", null, true), undefined);
+ assert_equals(et.addEventListener("x", null), undefined);
+}, "Adding a null event listener should succeed");
diff --git a/test/fixtures/wpt/dom/events/EventTarget-constructible.any.js b/test/fixtures/wpt/dom/events/EventTarget-constructible.any.js
new file mode 100644
index 00000000000000..b0e7614e625b3d
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventTarget-constructible.any.js
@@ -0,0 +1,62 @@
+"use strict";
+
+test(() => {
+ const target = new EventTarget();
+ const event = new Event("foo", { bubbles: true, cancelable: false });
+ let callCount = 0;
+
+ function listener(e) {
+ assert_equals(e, event);
+ ++callCount;
+ }
+
+ target.addEventListener("foo", listener);
+
+ target.dispatchEvent(event);
+ assert_equals(callCount, 1);
+
+ target.dispatchEvent(event);
+ assert_equals(callCount, 2);
+
+ target.removeEventListener("foo", listener);
+ target.dispatchEvent(event);
+ assert_equals(callCount, 2);
+}, "A constructed EventTarget can be used as expected");
+
+test(() => {
+ class NicerEventTarget extends EventTarget {
+ on(...args) {
+ this.addEventListener(...args);
+ }
+
+ off(...args) {
+ this.removeEventListener(...args);
+ }
+
+ dispatch(type, detail) {
+ this.dispatchEvent(new CustomEvent(type, { detail }));
+ }
+ }
+
+ const target = new NicerEventTarget();
+ const event = new Event("foo", { bubbles: true, cancelable: false });
+ const detail = "some data";
+ let callCount = 0;
+
+ function listener(e) {
+ assert_equals(e.detail, detail);
+ ++callCount;
+ }
+
+ target.on("foo", listener);
+
+ target.dispatch("foo", detail);
+ assert_equals(callCount, 1);
+
+ target.dispatch("foo", detail);
+ assert_equals(callCount, 2);
+
+ target.off("foo", listener);
+ target.dispatch("foo", detail);
+ assert_equals(callCount, 2);
+}, "EventTarget can be subclassed");
diff --git a/test/fixtures/wpt/dom/events/EventTarget-dispatchEvent-returnvalue.html b/test/fixtures/wpt/dom/events/EventTarget-dispatchEvent-returnvalue.html
new file mode 100644
index 00000000000000..c4466e0d6cdada
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventTarget-dispatchEvent-returnvalue.html
@@ -0,0 +1,71 @@
+
+
+EventTarget.dispatchEvent: return value
+
+
+
+
+
+
+
+
+
+
+ Shady Grove |
+ Aeolian |
+
+
+ Over the river, Charlie |
+ Dorian |
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/EventTarget-dispatchEvent.html b/test/fixtures/wpt/dom/events/EventTarget-dispatchEvent.html
new file mode 100644
index 00000000000000..783561f5fb5943
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventTarget-dispatchEvent.html
@@ -0,0 +1,104 @@
+
+
+EventTarget.dispatchEvent
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/EventTarget-removeEventListener.any.js b/test/fixtures/wpt/dom/events/EventTarget-removeEventListener.any.js
new file mode 100644
index 00000000000000..289dfcfbabde0f
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventTarget-removeEventListener.any.js
@@ -0,0 +1,8 @@
+// META: title=EventTarget.removeEventListener
+
+// Step 1.
+test(function() {
+ assert_equals(globalThis.removeEventListener("x", null, false), undefined);
+ assert_equals(globalThis.removeEventListener("x", null, true), undefined);
+ assert_equals(globalThis.removeEventListener("x", null), undefined);
+}, "removing a null event listener should succeed");
diff --git a/test/fixtures/wpt/dom/events/EventTarget-this-of-listener.html b/test/fixtures/wpt/dom/events/EventTarget-this-of-listener.html
new file mode 100644
index 00000000000000..506564c4133ce6
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/EventTarget-this-of-listener.html
@@ -0,0 +1,182 @@
+
+
+EventTarget listeners this value
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/KeyEvent-initKeyEvent.html b/test/fixtures/wpt/dom/events/KeyEvent-initKeyEvent.html
new file mode 100644
index 00000000000000..3fffaba0143c78
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/KeyEvent-initKeyEvent.html
@@ -0,0 +1,23 @@
+
+
+KeyEvent.initKeyEvent
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/document-level-touchmove-event-listener-passive-by-default.tentative.html b/test/fixtures/wpt/dom/events/document-level-touchmove-event-listener-passive-by-default.tentative.html
new file mode 100644
index 00000000000000..f3f0d58209fd8e
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/document-level-touchmove-event-listener-passive-by-default.tentative.html
@@ -0,0 +1,73 @@
+
+Default passive document level touchmove event listener test
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/document-level-wheel-event-listener-passive-by-default.tentative.html b/test/fixtures/wpt/dom/events/document-level-wheel-event-listener-passive-by-default.tentative.html
new file mode 100644
index 00000000000000..b7224835fa398b
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/document-level-wheel-event-listener-passive-by-default.tentative.html
@@ -0,0 +1,51 @@
+
+Default passive document level wheel event listener manual test
+
+
+
+
+
+
+
+This is a manual test since there is no way to synthesize wheel events.
+Scroll by wheel in the middle of the page to run the test.
+
+
diff --git a/test/fixtures/wpt/dom/events/event-disabled-dynamic.html b/test/fixtures/wpt/dom/events/event-disabled-dynamic.html
new file mode 100644
index 00000000000000..3f995b02f1b421
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/event-disabled-dynamic.html
@@ -0,0 +1,21 @@
+
+
+Test that disabled is honored immediately in presence of dynamic changes
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/event-global-extra.window.js b/test/fixtures/wpt/dom/events/event-global-extra.window.js
new file mode 100644
index 00000000000000..0f14961c40a650
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/event-global-extra.window.js
@@ -0,0 +1,90 @@
+const otherWindow = document.body.appendChild(document.createElement("iframe")).contentWindow;
+
+["EventTarget", "XMLHttpRequest"].forEach(constructorName => {
+ async_test(t => {
+ const eventTarget = new otherWindow[constructorName]();
+ eventTarget.addEventListener("hi", t.step_func_done(e => {
+ assert_equals(otherWindow.event, undefined);
+ assert_equals(e, window.event);
+ }));
+ eventTarget.dispatchEvent(new Event("hi"));
+ }, "window.event for constructors from another global: " + constructorName);
+});
+
+// XXX: It would be good to test a subclass of EventTarget once we sort out
+// https://github.com/heycam/webidl/issues/540
+
+async_test(t => {
+ const element = document.body.appendChild(otherWindow.document.createElement("meh"));
+ element.addEventListener("yo", t.step_func_done(e => {
+ assert_equals(e, window.event);
+ }));
+ element.dispatchEvent(new Event("yo"));
+}, "window.event and element from another document");
+
+async_test(t => {
+ const doc = otherWindow.document,
+ element = doc.body.appendChild(doc.createElement("meh")),
+ child = element.appendChild(doc.createElement("bleh"));
+ element.addEventListener("yoyo", t.step_func(e => {
+ document.body.appendChild(element);
+ assert_equals(element.ownerDocument, document);
+ assert_equals(window.event, e);
+ assert_equals(otherWindow.event, undefined);
+ }), true);
+ element.addEventListener("yoyo", t.step_func(e => {
+ assert_equals(element.ownerDocument, document);
+ assert_equals(window.event, e);
+ assert_equals(otherWindow.event, undefined);
+ }), true);
+ child.addEventListener("yoyo", t.step_func_done(e => {
+ assert_equals(child.ownerDocument, document);
+ assert_equals(window.event, e);
+ assert_equals(otherWindow.event, undefined);
+ }));
+ child.dispatchEvent(new Event("yoyo"));
+}, "window.event and moving an element post-dispatch");
+
+test(t => {
+ const host = document.createElement("div"),
+ shadow = host.attachShadow({ mode: "open" }),
+ child = shadow.appendChild(document.createElement("trala")),
+ furtherChild = child.appendChild(document.createElement("waddup"));
+ let counter = 0;
+ host.addEventListener("hi", t.step_func(e => {
+ assert_equals(window.event, e);
+ assert_equals(counter++, 3);
+ }));
+ child.addEventListener("hi", t.step_func(e => {
+ assert_equals(window.event, undefined);
+ assert_equals(counter++, 2);
+ }));
+ furtherChild.addEventListener("hi", t.step_func(e => {
+ host.appendChild(child);
+ assert_equals(window.event, undefined);
+ assert_equals(counter++, 0);
+ }));
+ furtherChild.addEventListener("hi", t.step_func(e => {
+ assert_equals(window.event, undefined);
+ assert_equals(counter++, 1);
+ }));
+ furtherChild.dispatchEvent(new Event("hi", { composed: true, bubbles: true }));
+ assert_equals(counter, 4);
+}, "window.event should not be affected by nodes moving post-dispatch");
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ frame.src = "resources/event-global-extra-frame.html";
+ frame.onload = t.step_func_done((load_event) => {
+ const event = new Event("hi");
+ document.addEventListener("hi", frame.contentWindow.listener); // listener intentionally not wrapped in t.step_func
+ document.addEventListener("hi", t.step_func(e => {
+ assert_equals(event, e);
+ assert_equals(window.event, e);
+ }));
+ document.dispatchEvent(event);
+ assert_equals(frameState.event, event);
+ assert_equals(frameState.windowEvent, event);
+ assert_equals(frameState.parentEvent, load_event);
+ });
+}, "Listener from a different global");
diff --git a/test/fixtures/wpt/dom/events/event-global-is-still-set-when-coercing-beforeunload-result.html b/test/fixtures/wpt/dom/events/event-global-is-still-set-when-coercing-beforeunload-result.html
new file mode 100644
index 00000000000000..a64c8b6b8b6d5f
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/event-global-is-still-set-when-coercing-beforeunload-result.html
@@ -0,0 +1,23 @@
+
+
+window.event is still set when 'beforeunload' result is coerced to string
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/event-global-set-before-handleEvent-lookup.window.js b/test/fixtures/wpt/dom/events/event-global-set-before-handleEvent-lookup.window.js
new file mode 100644
index 00000000000000..8f934bcea97fe1
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/event-global-set-before-handleEvent-lookup.window.js
@@ -0,0 +1,19 @@
+// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke (steps 8.2 - 12)
+// https://webidl.spec.whatwg.org/#call-a-user-objects-operation (step 10.1)
+
+test(() => {
+ const eventTarget = new EventTarget;
+
+ let currentEvent;
+ eventTarget.addEventListener("foo", {
+ get handleEvent() {
+ currentEvent = window.event;
+ return () => {};
+ }
+ });
+
+ const event = new Event("foo");
+ eventTarget.dispatchEvent(event);
+
+ assert_equals(currentEvent, event);
+}, "window.event is set before 'handleEvent' lookup");
diff --git a/test/fixtures/wpt/dom/events/event-global.html b/test/fixtures/wpt/dom/events/event-global.html
new file mode 100644
index 00000000000000..3e8d25ecb5dd9d
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/event-global.html
@@ -0,0 +1,117 @@
+
+window.event tests
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/event-global.worker.js b/test/fixtures/wpt/dom/events/event-global.worker.js
new file mode 100644
index 00000000000000..116cf32932bd8d
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/event-global.worker.js
@@ -0,0 +1,14 @@
+importScripts("/resources/testharness.js");
+test(t => {
+ let seen = false;
+ const event = new Event("hi");
+ assert_equals(self.event, undefined);
+ self.addEventListener("hi", t.step_func(e => {
+ seen = true;
+ assert_equals(self.event, undefined);
+ assert_equals(e, event);
+ }));
+ self.dispatchEvent(event);
+ assert_true(seen);
+}, "There's no self.event (that's why we call it window.event) in workers");
+done();
diff --git a/test/fixtures/wpt/dom/events/focus-event-document-move.html b/test/fixtures/wpt/dom/events/focus-event-document-move.html
new file mode 100644
index 00000000000000..2943761ce132e3
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/focus-event-document-move.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+Click me
+
+
diff --git a/test/fixtures/wpt/dom/events/keypress-dispatch-crash.html b/test/fixtures/wpt/dom/events/keypress-dispatch-crash.html
new file mode 100644
index 00000000000000..3207adbd8c8919
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/keypress-dispatch-crash.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/test/fixtures/wpt/dom/events/legacy-pre-activation-behavior.window.js b/test/fixtures/wpt/dom/events/legacy-pre-activation-behavior.window.js
new file mode 100644
index 00000000000000..e9e84bfad1e6f7
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/legacy-pre-activation-behavior.window.js
@@ -0,0 +1,10 @@
+test(t => {
+ const input = document.body.appendChild(document.createElement('input'));
+ input.type = "radio";
+ t.add_cleanup(() => input.remove());
+ const clickEvent = new MouseEvent('click', { button: 0, which: 1 });
+ input.addEventListener('change', t.step_func(() => {
+ assert_equals(clickEvent.eventPhase, Event.NONE);
+ }));
+ input.dispatchEvent(clickEvent);
+}, "Use NONE phase during legacy-pre-activation behavior");
diff --git a/test/fixtures/wpt/dom/events/relatedTarget.window.js b/test/fixtures/wpt/dom/events/relatedTarget.window.js
new file mode 100644
index 00000000000000..ebc83ceb209a7c
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/relatedTarget.window.js
@@ -0,0 +1,81 @@
+// https://dom.spec.whatwg.org/#concept-event-dispatch
+
+const host = document.createElement("div"),
+ child = host.appendChild(document.createElement("p")),
+ shadow = host.attachShadow({ mode: "closed" }),
+ slot = shadow.appendChild(document.createElement("slot"));
+
+test(() => {
+ for (target of [shadow, slot]) {
+ for (relatedTarget of [new XMLHttpRequest(), self, host]) {
+ const event = new FocusEvent("demo", { relatedTarget: relatedTarget });
+ target.dispatchEvent(event);
+ assert_equals(event.target, null);
+ assert_equals(event.relatedTarget, null);
+ }
+ }
+}, "Reset if target pointed to a shadow tree");
+
+test(() => {
+ for (relatedTarget of [shadow, slot]) {
+ for (target of [new XMLHttpRequest(), self, host]) {
+ const event = new FocusEvent("demo", { relatedTarget: relatedTarget });
+ target.dispatchEvent(event);
+ assert_equals(event.target, target);
+ assert_equals(event.relatedTarget, host);
+ }
+ }
+}, "Retarget a shadow-tree relatedTarget");
+
+test(t => {
+ const shadowChild = shadow.appendChild(document.createElement("div"));
+ shadowChild.addEventListener("demo", t.step_func(() => document.body.appendChild(shadowChild)));
+ const event = new FocusEvent("demo", { relatedTarget: new XMLHttpRequest() });
+ shadowChild.dispatchEvent(event);
+ assert_equals(shadowChild.parentNode, document.body);
+ assert_equals(event.target, null);
+ assert_equals(event.relatedTarget, null);
+ shadowChild.remove();
+}, "Reset if target pointed to a shadow tree pre-dispatch");
+
+test(t => {
+ const shadowChild = shadow.appendChild(document.createElement("div"));
+ document.body.addEventListener("demo", t.step_func(() => document.body.appendChild(shadowChild)));
+ const event = new FocusEvent("demo", { relatedTarget: shadowChild });
+ document.body.dispatchEvent(event);
+ assert_equals(shadowChild.parentNode, document.body);
+ assert_equals(event.target, document.body);
+ assert_equals(event.relatedTarget, host);
+ shadowChild.remove();
+}, "Retarget a shadow-tree relatedTarget, part 2");
+
+test(t => {
+ const event = new FocusEvent("heya", { relatedTarget: shadow, cancelable: true }),
+ callback = t.unreached_func();
+ host.addEventListener("heya", callback);
+ t.add_cleanup(() => host.removeEventListener("heya", callback));
+ event.preventDefault();
+ assert_true(event.defaultPrevented);
+ assert_false(host.dispatchEvent(event));
+ assert_equals(event.target, null);
+ assert_equals(event.relatedTarget, null);
+ // Check that the dispatch flag is cleared
+ event.initEvent("x");
+ assert_equals(event.type, "x");
+}, "Reset targets on early return");
+
+test(t => {
+ const input = document.body.appendChild(document.createElement("input")),
+ event = new MouseEvent("click", { relatedTarget: shadow });
+ let seen = false;
+ t.add_cleanup(() => input.remove());
+ input.type = "checkbox";
+ input.oninput = t.step_func(() => {
+ assert_equals(event.target, null);
+ assert_equals(event.relatedTarget, null);
+ assert_equals(event.composedPath().length, 0);
+ seen = true;
+ });
+ assert_true(input.dispatchEvent(event));
+ assert_true(seen);
+}, "Reset targets before activation behavior");
diff --git a/test/fixtures/wpt/dom/events/replace-event-listener-null-browsing-context-crash.html b/test/fixtures/wpt/dom/events/replace-event-listener-null-browsing-context-crash.html
new file mode 100644
index 00000000000000..f41955eedd39fb
--- /dev/null
+++ b/test/fixtures/wpt/dom/events/replace-event-listener-null-browsing-context-crash.html
@@ -0,0 +1,16 @@
+
+Event listeners: replace listener after moving between documents
+
+
+
+
+