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() + + + + +
+ + + + + + + + + + + + + + + + + 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 + + + + +
+ + + + + + + + + + + + + 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 + + + + +
+ + + + + + + + + + + + + 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 + + + +
+ + + + + + + + + + + + + + + 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 + + + + +
+ + + + + + + + + + + + + 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() + + + + +
+ + + + + + + + + + + + + + + + + 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 + + +
+ + + + + + + + + + + + + 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 + + + +
+ + + + + + + + + + + + + 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 + + + +
+ + + + + + + + + + + + + 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 + + + + + + +
+ + + + + + + + + + + + + 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 + + + + + + + + diff --git a/test/fixtures/wpt/dom/events/resources/empty-document.html b/test/fixtures/wpt/dom/events/resources/empty-document.html new file mode 100644 index 00000000000000..b9cd130a07f77e --- /dev/null +++ b/test/fixtures/wpt/dom/events/resources/empty-document.html @@ -0,0 +1,3 @@ + + + diff --git a/test/fixtures/wpt/dom/events/resources/event-global-extra-frame.html b/test/fixtures/wpt/dom/events/resources/event-global-extra-frame.html new file mode 100644 index 00000000000000..241dda8b66f8c8 --- /dev/null +++ b/test/fixtures/wpt/dom/events/resources/event-global-extra-frame.html @@ -0,0 +1,9 @@ + diff --git a/test/fixtures/wpt/dom/events/resources/event-global-is-still-set-when-coercing-beforeunload-result-frame.html b/test/fixtures/wpt/dom/events/resources/event-global-is-still-set-when-coercing-beforeunload-result-frame.html new file mode 100644 index 00000000000000..5df4fa27936379 --- /dev/null +++ b/test/fixtures/wpt/dom/events/resources/event-global-is-still-set-when-coercing-beforeunload-result-frame.html @@ -0,0 +1,6 @@ + + + + diff --git a/test/fixtures/wpt/dom/events/resources/prefixed-animation-event-tests.js b/test/fixtures/wpt/dom/events/resources/prefixed-animation-event-tests.js new file mode 100644 index 00000000000000..021b6bb9dfdc42 --- /dev/null +++ b/test/fixtures/wpt/dom/events/resources/prefixed-animation-event-tests.js @@ -0,0 +1,366 @@ +'use strict' + +// Runs a set of tests for a given prefixed/unprefixed animation event (e.g. +// animationstart/webkitAnimationStart). +// +// The eventDetails object must have the following form: +// { +// isTransition: false, <-- can be omitted, default false +// unprefixedType: 'animationstart', +// prefixedType: 'webkitAnimationStart', +// animationCssStyle: '1ms', <-- must NOT include animation name or +// transition property +// } +function runAnimationEventTests(eventDetails) { + const { + isTransition, + unprefixedType, + prefixedType, + animationCssStyle + } = eventDetails; + + // Derive the DOM event handler names, e.g. onanimationstart. + const unprefixedHandler = `on${unprefixedType}`; + const prefixedHandler = `on${prefixedType.toLowerCase()}`; + + const style = document.createElement('style'); + document.head.appendChild(style); + if (isTransition) { + style.sheet.insertRule( + `.baseStyle { width: 100px; transition: width ${animationCssStyle}; }`); + style.sheet.insertRule('.transition { width: 200px !important; }'); + } else { + style.sheet.insertRule('@keyframes anim {}'); + } + + function triggerAnimation(div) { + if (isTransition) { + div.classList.add('transition'); + } else { + div.style.animation = `anim ${animationCssStyle}`; + } + } + + test(t => { + const div = createDiv(t); + + assert_equals(div[unprefixedHandler], null, + `${unprefixedHandler} should initially be null`); + assert_equals(div[prefixedHandler], null, + `${prefixedHandler} should initially be null`); + + // Setting one should not affect the other. + div[unprefixedHandler] = () => { }; + + assert_not_equals(div[unprefixedHandler], null, + `setting ${unprefixedHandler} should make it non-null`); + assert_equals(div[prefixedHandler], null, + `setting ${unprefixedHandler} should not affect ${prefixedHandler}`); + + div[prefixedHandler] = () => { }; + + assert_not_equals(div[prefixedHandler], null, + `setting ${prefixedHandler} should make it non-null`); + assert_not_equals(div[unprefixedHandler], div[prefixedHandler], + 'the setters should be different'); + }, `${unprefixedHandler} and ${prefixedHandler} are not aliases`); + + // The below tests primarily test the interactions of prefixed animation + // events in the algorithm for invoking events: + // https://dom.spec.whatwg.org/#concept-event-listener-invoke + + promise_test(async t => { + const div = createDiv(t); + + let receivedEventCount = 0; + addTestScopedEventHandler(t, div, prefixedHandler, () => { + receivedEventCount++; + }); + addTestScopedEventListener(t, div, prefixedType, () => { + receivedEventCount++; + }); + + // The HTML spec[0] specifies that the prefixed event handlers have an + // 'Event handler event type' of the appropriate prefixed event type. E.g. + // onwebkitanimationend creates a listener for the event type + // 'webkitAnimationEnd'. + // + // [0]: https://html.spec.whatwg.org/multipage/webappapis.html#event-handlers-on-elements,-document-objects,-and-window-objects + div.dispatchEvent(new AnimationEvent(prefixedType)); + assert_equals(receivedEventCount, 2, + 'prefixed listener and handler received event'); + }, `dispatchEvent of a ${prefixedType} event does trigger a ` + + `prefixed event handler or listener`); + + promise_test(async t => { + const div = createDiv(t); + + let receivedEvent = false; + addTestScopedEventHandler(t, div, unprefixedHandler, () => { + receivedEvent = true; + }); + addTestScopedEventListener(t, div, unprefixedType, () => { + receivedEvent = true; + }); + + div.dispatchEvent(new AnimationEvent(prefixedType)); + assert_false(receivedEvent, + 'prefixed listener or handler received event'); + }, `dispatchEvent of a ${prefixedType} event does not trigger an ` + + `unprefixed event handler or listener`); + + + promise_test(async t => { + const div = createDiv(t); + + let receivedEvent = false; + addTestScopedEventHandler(t, div, prefixedHandler, () => { + receivedEvent = true; + }); + addTestScopedEventListener(t, div, prefixedType, () => { + receivedEvent = true; + }); + + // The rewrite rules from + // https://dom.spec.whatwg.org/#concept-event-listener-invoke step 8 do not + // apply because isTrusted will be false. + div.dispatchEvent(new AnimationEvent(unprefixedType)); + assert_false(receivedEvent, 'prefixed listener or handler received event'); + }, `dispatchEvent of an ${unprefixedType} event does not trigger a ` + + `prefixed event handler or listener`); + + promise_test(async t => { + const div = createDiv(t); + + let receivedEvent = false; + addTestScopedEventHandler(t, div, prefixedHandler, () => { + receivedEvent = true; + }); + + triggerAnimation(div); + await waitForEventThenAnimationFrame(t, unprefixedType); + assert_true(receivedEvent, `received ${prefixedHandler} event`); + }, `${prefixedHandler} event handler should trigger for an animation`); + + promise_test(async t => { + const div = createDiv(t); + + let receivedPrefixedEvent = false; + addTestScopedEventHandler(t, div, prefixedHandler, () => { + receivedPrefixedEvent = true; + }); + let receivedUnprefixedEvent = false; + addTestScopedEventHandler(t, div, unprefixedHandler, () => { + receivedUnprefixedEvent = true; + }); + + triggerAnimation(div); + await waitForEventThenAnimationFrame(t, unprefixedType); + assert_true(receivedUnprefixedEvent, `received ${unprefixedHandler} event`); + assert_false(receivedPrefixedEvent, `received ${prefixedHandler} event`); + }, `${prefixedHandler} event handler should not trigger if an unprefixed ` + + `event handler also exists`); + + promise_test(async t => { + const div = createDiv(t); + + let receivedPrefixedEvent = false; + addTestScopedEventHandler(t, div, prefixedHandler, () => { + receivedPrefixedEvent = true; + }); + let receivedUnprefixedEvent = false; + addTestScopedEventListener(t, div, unprefixedType, () => { + receivedUnprefixedEvent = true; + }); + + triggerAnimation(div); + await waitForEventThenAnimationFrame(t, unprefixedHandler); + assert_true(receivedUnprefixedEvent, `received ${unprefixedHandler} event`); + assert_false(receivedPrefixedEvent, `received ${prefixedHandler} event`); + }, `${prefixedHandler} event handler should not trigger if an unprefixed ` + + `listener also exists`); + + promise_test(async t => { + // We use a parent/child relationship to be able to register both prefixed + // and unprefixed event handlers without the deduplication logic kicking in. + const parent = createDiv(t); + const child = createDiv(t); + parent.appendChild(child); + // After moving the child, we have to clean style again. + getComputedStyle(child).transition; + getComputedStyle(child).width; + + let observedUnprefixedType; + addTestScopedEventHandler(t, parent, unprefixedHandler, e => { + observedUnprefixedType = e.type; + }); + let observedPrefixedType; + addTestScopedEventHandler(t, child, prefixedHandler, e => { + observedPrefixedType = e.type; + }); + + triggerAnimation(child); + await waitForEventThenAnimationFrame(t, unprefixedType); + + assert_equals(observedUnprefixedType, unprefixedType); + assert_equals(observedPrefixedType, prefixedType); + }, `event types for prefixed and unprefixed ${unprefixedType} event ` + + `handlers should be named appropriately`); + + promise_test(async t => { + const div = createDiv(t); + + let receivedEvent = false; + addTestScopedEventListener(t, div, prefixedType, () => { + receivedEvent = true; + }); + + triggerAnimation(div); + await waitForEventThenAnimationFrame(t, unprefixedHandler); + assert_true(receivedEvent, `received ${prefixedType} event`); + }, `${prefixedType} event listener should trigger for an animation`); + + promise_test(async t => { + const div = createDiv(t); + + let receivedPrefixedEvent = false; + addTestScopedEventListener(t, div, prefixedType, () => { + receivedPrefixedEvent = true; + }); + let receivedUnprefixedEvent = false; + addTestScopedEventListener(t, div, unprefixedType, () => { + receivedUnprefixedEvent = true; + }); + + triggerAnimation(div); + await waitForEventThenAnimationFrame(t, unprefixedHandler); + assert_true(receivedUnprefixedEvent, `received ${unprefixedType} event`); + assert_false(receivedPrefixedEvent, `received ${prefixedType} event`); + }, `${prefixedType} event listener should not trigger if an unprefixed ` + + `listener also exists`); + + promise_test(async t => { + const div = createDiv(t); + + let receivedPrefixedEvent = false; + addTestScopedEventListener(t, div, prefixedType, () => { + receivedPrefixedEvent = true; + }); + let receivedUnprefixedEvent = false; + addTestScopedEventHandler(t, div, unprefixedHandler, () => { + receivedUnprefixedEvent = true; + }); + + triggerAnimation(div); + await waitForEventThenAnimationFrame(t, unprefixedHandler); + assert_true(receivedUnprefixedEvent, `received ${unprefixedType} event`); + assert_false(receivedPrefixedEvent, `received ${prefixedType} event`); + }, `${prefixedType} event listener should not trigger if an unprefixed ` + + `event handler also exists`); + + promise_test(async t => { + // We use a parent/child relationship to be able to register both prefixed + // and unprefixed event listeners without the deduplication logic kicking in. + const parent = createDiv(t); + const child = createDiv(t); + parent.appendChild(child); + // After moving the child, we have to clean style again. + getComputedStyle(child).transition; + getComputedStyle(child).width; + + let observedUnprefixedType; + addTestScopedEventListener(t, parent, unprefixedType, e => { + observedUnprefixedType = e.type; + }); + let observedPrefixedType; + addTestScopedEventListener(t, child, prefixedType, e => { + observedPrefixedType = e.type; + }); + + triggerAnimation(child); + await waitForEventThenAnimationFrame(t, unprefixedHandler); + + assert_equals(observedUnprefixedType, unprefixedType); + assert_equals(observedPrefixedType, prefixedType); + }, `event types for prefixed and unprefixed ${unprefixedType} event ` + + `listeners should be named appropriately`); + + promise_test(async t => { + const div = createDiv(t); + + let receivedEvent = false; + addTestScopedEventListener(t, div, prefixedType.toLowerCase(), () => { + receivedEvent = true; + }); + addTestScopedEventListener(t, div, prefixedType.toUpperCase(), () => { + receivedEvent = true; + }); + + triggerAnimation(div); + await waitForEventThenAnimationFrame(t, unprefixedHandler); + assert_false(receivedEvent, `received ${prefixedType} event`); + }, `${prefixedType} event listener is case sensitive`); +} + +// Below are utility functions. + +// Creates a div element, appends it to the document body and removes the +// created element during test cleanup. +function createDiv(test) { + const element = document.createElement('div'); + element.classList.add('baseStyle'); + document.body.appendChild(element); + test.add_cleanup(() => { + element.remove(); + }); + + // Flush style before returning. Some browsers only do partial style re-calc, + // so ask for all important properties to make sure they are applied. + getComputedStyle(element).transition; + getComputedStyle(element).width; + + return element; +} + +// Adds an event handler for |handlerName| (calling |callback|) to the given +// |target|, that will automatically be cleaned up at the end of the test. +function addTestScopedEventHandler(test, target, handlerName, callback) { + assert_regexp_match( + handlerName, /^on/, 'Event handler names must start with "on"'); + assert_equals(target[handlerName], null, + `${handlerName} must be supported and not previously set`); + target[handlerName] = callback; + // We need this cleaned up even if the event handler doesn't run. + test.add_cleanup(() => { + if (target[handlerName]) + target[handlerName] = null; + }); +} + +// Adds an event listener for |type| (calling |callback|) to the given +// |target|, that will automatically be cleaned up at the end of the test. +function addTestScopedEventListener(test, target, type, callback) { + target.addEventListener(type, callback); + // We need this cleaned up even if the event handler doesn't run. + test.add_cleanup(() => { + target.removeEventListener(type, callback); + }); +} + +// Returns a promise that will resolve once the passed event (|eventName|) has +// triggered and one more animation frame has happened. Automatically chooses +// between an event handler or event listener based on whether |eventName| +// begins with 'on'. +// +// We always listen on window as we don't want to interfere with the test via +// triggering the prefixed event deduplication logic. +function waitForEventThenAnimationFrame(test, eventName) { + return new Promise((resolve, _) => { + const eventFunc = eventName.startsWith('on') + ? addTestScopedEventHandler : addTestScopedEventListener; + eventFunc(test, window, eventName, () => { + // rAF once to give the event under test time to come through. + requestAnimationFrame(resolve); + }); + }); +} diff --git a/test/fixtures/wpt/dom/events/scrolling/iframe-chains.html b/test/fixtures/wpt/dom/events/scrolling/iframe-chains.html new file mode 100644 index 00000000000000..2d7e1827adad6f --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/iframe-chains.html @@ -0,0 +1,47 @@ + + + + + + + + + + + +
+ +
+
+
+ + + diff --git a/test/fixtures/wpt/dom/events/scrolling/input-text-scroll-event-when-using-arrow-keys.html b/test/fixtures/wpt/dom/events/scrolling/input-text-scroll-event-when-using-arrow-keys.html new file mode 100644 index 00000000000000..f84e4465275b7a --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/input-text-scroll-event-when-using-arrow-keys.html @@ -0,0 +1,71 @@ + + + + + + + + + + + +

Moving the cursor using the arrow keys into an + input element fires scroll events when text has to scroll into view. + Uses arrow keys to move forward and backwards in the input + element.

+ + + + + + diff --git a/test/fixtures/wpt/dom/events/scrolling/overscroll-deltas.html b/test/fixtures/wpt/dom/events/scrolling/overscroll-deltas.html new file mode 100644 index 00000000000000..6f0b77f22eda2a --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/overscroll-deltas.html @@ -0,0 +1,85 @@ + + + + + + + + + + +
+
+ + + diff --git a/test/fixtures/wpt/dom/events/scrolling/overscroll-event-fired-to-document.html b/test/fixtures/wpt/dom/events/scrolling/overscroll-event-fired-to-document.html new file mode 100644 index 00000000000000..c054ffca9c471f --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/overscroll-event-fired-to-document.html @@ -0,0 +1,62 @@ + + + + + + + + + + +
+
+
+
+ + + diff --git a/test/fixtures/wpt/dom/events/scrolling/overscroll-event-fired-to-element-with-overscroll-behavior.html b/test/fixtures/wpt/dom/events/scrolling/overscroll-event-fired-to-element-with-overscroll-behavior.html new file mode 100644 index 00000000000000..750080e6568e86 --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/overscroll-event-fired-to-element-with-overscroll-behavior.html @@ -0,0 +1,92 @@ + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/test/fixtures/wpt/dom/events/scrolling/overscroll-event-fired-to-scrolled-element.html b/test/fixtures/wpt/dom/events/scrolling/overscroll-event-fired-to-scrolled-element.html new file mode 100644 index 00000000000000..cfc782a809a7e7 --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/overscroll-event-fired-to-scrolled-element.html @@ -0,0 +1,65 @@ + + + + + + + + + + +
+
+
+
+ + + diff --git a/test/fixtures/wpt/dom/events/scrolling/overscroll-event-fired-to-window.html b/test/fixtures/wpt/dom/events/scrolling/overscroll-event-fired-to-window.html new file mode 100644 index 00000000000000..ef5ae3daef8158 --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/overscroll-event-fired-to-window.html @@ -0,0 +1,52 @@ + + + + + + + + + + +
+
+
+
+ + + diff --git a/test/fixtures/wpt/dom/events/scrolling/scroll_support.js b/test/fixtures/wpt/dom/events/scrolling/scroll_support.js new file mode 100644 index 00000000000000..0a73f34fefc8ab --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/scroll_support.js @@ -0,0 +1,97 @@ +const MAX_FRAME = 700; +const MAX_UNCHANGED_FRAMES = 20; + +// Returns a promise that resolves when the given condition is met or rejects +// after MAX_FRAME animation frames. +function waitFor(condition, error_message = 'Reaches the maximum frames.') { + return new Promise((resolve, reject) => { + function tick(frames) { + // We requestAnimationFrame either for MAX_FRAM frames or until condition + // is met. + if (frames >= MAX_FRAME) + reject(error_message); + else if (condition()) + resolve(); + else + requestAnimationFrame(tick.bind(this, frames + 1)); + } + tick(0); + }); +} + +function waitForCompositorCommit() { + return new Promise((resolve) => { + // rAF twice. + window.requestAnimationFrame(() => { + window.requestAnimationFrame(resolve); + }); + }); +} + +function waitForAnimationEnd(getValue) { + var last_changed_frame = 0; + var last_position = getValue(); + return new Promise((resolve, reject) => { + function tick(frames) { + // We requestAnimationFrame either for MAX_FRAME or until + // MAX_UNCHANGED_FRAMES with no change have been observed. + if (frames >= MAX_FRAME || frames - last_changed_frame > MAX_UNCHANGED_FRAMES) { + resolve(); + } else { + current_value = getValue(); + if (last_position != current_value) { + last_changed_frame = frames; + last_position = current_value; + } + requestAnimationFrame(tick.bind(this, frames + 1)); + } + } + tick(0); + }) +} + +function touchScrollInTarget(pixels_to_scroll, target, direction, pause_time_in_ms = 100) { + var x_delta = 0; + var y_delta = 0; + const num_movs = 5; + if (direction == "down") { + y_delta = -1 * pixels_to_scroll / num_movs; + } else if (direction == "up") { + y_delta = pixels_to_scroll / num_movs; + } else if (direction == "right") { + x_delta = -1 * pixels_to_scroll / num_movs; + } else if (direction == "left") { + x_delta = pixels_to_scroll / num_movs;; + } else { + throw("scroll direction '" + direction + "' is not expected, direction should be 'down', 'up', 'left' or 'right'"); + } + return new test_driver.Actions() + .addPointer("pointer1", "touch") + .pointerMove(0, 0, {origin: target}) + .pointerDown() + .pointerMove(x_delta, y_delta, {origin: target}) + .pointerMove(2 * x_delta, 2 * y_delta, {origin: target}) + .pointerMove(3 * x_delta, 3 * y_delta, {origin: target}) + .pointerMove(4 * x_delta, 4 * y_delta, {origin: target}) + .pointerMove(5 * x_delta, 5 * y_delta, {origin: target}) + .pause(pause_time_in_ms) + .pointerUp() + .send(); +} + +// Trigger fling by doing pointerUp right after pointerMoves. +function touchFlingInTarget(pixels_to_scroll, target, direction) { + touchScrollInTarget(pixels_to_scroll, target, direction, 0 /* pause_time */); +} + +function mouseActionsInTarget(target, origin, delta, pause_time_in_ms = 100) { + return new test_driver.Actions() + .addPointer("pointer1", "mouse") + .pointerMove(origin.x, origin.y, { origin: target }) + .pointerDown() + .pointerMove(origin.x + delta.x, origin.y + delta.y, { origin: target }) + .pointerMove(origin.x + delta.x * 2, origin.y + delta.y * 2, { origin: target }) + .pause(pause_time_in_ms) + .pointerUp() + .send(); +} diff --git a/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-after-snap.html b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-after-snap.html new file mode 100644 index 00000000000000..ef1b495791cad1 --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-after-snap.html @@ -0,0 +1,86 @@ + + + + + + + + + + +
+
+
+
+
+
+ + + diff --git a/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-for-programmatic-scroll.html b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-for-programmatic-scroll.html new file mode 100644 index 00000000000000..79b5f5f0186871 --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-for-programmatic-scroll.html @@ -0,0 +1,134 @@ + + + + + + + + + + + +
+
+
+
+ + diff --git a/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-for-scrollIntoView.html b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-for-scrollIntoView.html new file mode 100644 index 00000000000000..63e1c3e22eaafc --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-for-scrollIntoView.html @@ -0,0 +1,123 @@ + + + + + + + + + + + +
+
+
+
+ + diff --git a/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-to-document.html b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-to-document.html new file mode 100644 index 00000000000000..99c1c6930fab0b --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-to-document.html @@ -0,0 +1,69 @@ + + + + + + + + + + +
+
+
+
+ + + diff --git a/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-to-element-with-overscroll-behavior.html b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-to-element-with-overscroll-behavior.html new file mode 100644 index 00000000000000..0269c66fdde192 --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-to-element-with-overscroll-behavior.html @@ -0,0 +1,93 @@ + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-to-scrolled-element.html b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-to-scrolled-element.html new file mode 100644 index 00000000000000..87cad79df7c2af --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-to-scrolled-element.html @@ -0,0 +1,67 @@ + + + + + + + + + + +
+
+
+
+ + + diff --git a/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-to-window.html b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-to-window.html new file mode 100644 index 00000000000000..f9510e6e231615 --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-to-window.html @@ -0,0 +1,54 @@ + + + + + + + + + + +
+
+
+
+ + + diff --git a/test/fixtures/wpt/dom/events/scrolling/scrollend-event-for-user-scroll.html b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-for-user-scroll.html new file mode 100644 index 00000000000000..30f16571cd8320 --- /dev/null +++ b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-for-user-scroll.html @@ -0,0 +1,138 @@ + + + + + + + + + + + + +
+
+
+
+ + + + diff --git a/test/fixtures/wpt/dom/events/shadow-relatedTarget.html b/test/fixtures/wpt/dom/events/shadow-relatedTarget.html new file mode 100644 index 00000000000000..713555b7d8ab61 --- /dev/null +++ b/test/fixtures/wpt/dom/events/shadow-relatedTarget.html @@ -0,0 +1,30 @@ + + + + +
+ + diff --git a/test/fixtures/wpt/dom/events/webkit-animation-end-event.html b/test/fixtures/wpt/dom/events/webkit-animation-end-event.html new file mode 100644 index 00000000000000..4186f6b7a9d241 --- /dev/null +++ b/test/fixtures/wpt/dom/events/webkit-animation-end-event.html @@ -0,0 +1,20 @@ + + +Prefixed CSS Animation end events + + + + + + + + + diff --git a/test/fixtures/wpt/dom/events/webkit-animation-iteration-event.html b/test/fixtures/wpt/dom/events/webkit-animation-iteration-event.html new file mode 100644 index 00000000000000..fb251972a32e16 --- /dev/null +++ b/test/fixtures/wpt/dom/events/webkit-animation-iteration-event.html @@ -0,0 +1,23 @@ + + +Prefixed CSS Animation iteration events + + + + + + + + + diff --git a/test/fixtures/wpt/dom/events/webkit-animation-start-event.html b/test/fixtures/wpt/dom/events/webkit-animation-start-event.html new file mode 100644 index 00000000000000..ad1036644a96e1 --- /dev/null +++ b/test/fixtures/wpt/dom/events/webkit-animation-start-event.html @@ -0,0 +1,20 @@ + + +Prefixed CSS Animation start events + + + + + + + + + diff --git a/test/fixtures/wpt/dom/events/webkit-transition-end-event.html b/test/fixtures/wpt/dom/events/webkit-transition-end-event.html new file mode 100644 index 00000000000000..2741824e3054a2 --- /dev/null +++ b/test/fixtures/wpt/dom/events/webkit-transition-end-event.html @@ -0,0 +1,21 @@ + + +Prefixed CSS Transition End event + + + + + + + + + diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index bde6cf862f6358..e9539ebf6410ae 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -11,6 +11,10 @@ "commit": "c49cafb491d99d6318f8f24a26936cc66501f412", "path": "dom/abort" }, + "dom/events": { + "commit": "f8821adb281696322f4bd96d412a98ae510f9125", + "path": "dom/events" + }, "encoding": { "commit": "35f70910d3753c8b650fdfd4c716caedfefe88c9", "path": "encoding" @@ -79,4 +83,4 @@ "commit": "a370aad338d6ed743abb4d2c6ae84a7f1058558c", "path": "webidl/ecmascript-binding/es-exceptions" } -} \ No newline at end of file +} diff --git a/test/wpt/status/dom/events.json b/test/wpt/status/dom/events.json new file mode 100644 index 00000000000000..9266118feca49f --- /dev/null +++ b/test/wpt/status/dom/events.json @@ -0,0 +1,52 @@ +{ + "Event-dispatch-listener-order.window.js": { "fail": "document is not defined" }, + "AddEventListenerOptions-passive.any.js": { + "fail": { + "unexpected": [ + "assert_equals: Incorrect defaultPrevented for options: {\"passive\":true} expected false but got true", + "assert_equals: Incorrect defaultPrevented for options: undefined expected true but got false" + ] + } + }, + "AddEventListenerOptions-signal.any.js": { + "fail": { + "unexpected": [ + "assert_throws_js: function \"() => { et.addEventListener(\"foo\", () => {}, { signal: null }); }\" did not throw", + "assert_throws_js: function \"() => { et.addEventListener(\"foo\", null, { signal: null }); }\" did not throw" + ] + } + }, + "Event-constructors.any.js": { + "fail": { + "unexpected": [ + "assert_true: expected true got false", + "assert_array_equals: lengths differ, expected array [\"bubbles\", \"cancelable\"] length 2, got [\"cancelable\", \"bubbles\", \"sweet\"] length 3", + "CustomEvent is not defined" + ] + } + }, + "EventListener-addEventListener.sub.window.js": { + "fail": "document is not defined" + }, + "EventTarget-constructible.any.js": { + "fail": "CustomEvent is not defined" + }, + "relatedTarget.window.js": { + "fail": "document is not defined" + }, + "event-global.worker.js": { + "fail": "importScripts is not defined" + }, + "event-global-extra.window.js": { + "fail": "document is not defined" + }, + "legacy-pre-activation-behavior.window.js": { + "fail": "document is not defined" + }, + "event-global-set-before-handleEvent-lookup.window.js": { + "fail": "window is not defined" + }, + "EventTarget-removeEventListener.any.js": { + "fail": "globalThis.removeEventListener is not a function" + } +} diff --git a/test/wpt/test-events.js b/test/wpt/test-events.js new file mode 100644 index 00000000000000..5040d56d6a2c58 --- /dev/null +++ b/test/wpt/test-events.js @@ -0,0 +1,7 @@ +'use strict'; +require('../common'); +const { WPTRunner } = require('../common/wpt'); + +const runner = new WPTRunner('dom/events'); + +runner.runJsTests();