From e1c386dd3caafa0ce7201169e724a4241e191fa0 Mon Sep 17 00:00:00 2001 From: Khafra Date: Thu, 29 Aug 2024 13:37:39 -0400 Subject: [PATCH] events: reset event state after dispatch Fixes https://github.com/nodejs/node/issues/54617 --- lib/internal/event_target.js | 15 +- test/fixtures/wpt/README.md | 2 +- .../wpt/dom/events/Event-dispatch-click.html | 56 +++++ .../Event-dispatch-on-disabled-elements.html | 2 +- .../events/EventTarget-constructible.any.js | 17 ++ .../fixtures/wpt/dom/events/event-global.html | 10 + .../events/scrolling/overscroll-deltas.html | 208 +++++++++++----- ...croll-event-fired-to-scrolled-element.html | 11 +- .../dom/events/scrolling/scroll_support.js | 125 +++++++++- ...d-after-sequence-of-scrolls.tentative.html | 76 ++++-- ...d-event-fired-for-programmatic-scroll.html | 166 +++++++------ ...ollend-event-fired-for-scrollIntoView.html | 8 +- .../scrollend-event-fired-to-document.html | 142 +++++++---- ...d-to-element-with-overscroll-behavior.html | 233 ++++++++++++------ ...llend-event-fired-to-scrolled-element.html | 68 ----- .../scrollend-event-fired-to-window.html | 90 ++++--- .../scrollend-event-for-user-scroll.html | 149 +---------- test/fixtures/wpt/versions.json | 2 +- 18 files changed, 811 insertions(+), 569 deletions(-) delete mode 100644 test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-to-scrolled-element.html diff --git a/lib/internal/event_target.js b/lib/internal/event_target.js index ecdc1bbba054a3..50a70214b37e9a 100644 --- a/lib/internal/event_target.js +++ b/lib/internal/event_target.js @@ -60,6 +60,7 @@ const kEvents = Symbol('kEvents'); const kIsBeingDispatched = Symbol('kIsBeingDispatched'); const kStop = Symbol('kStop'); const kTarget = Symbol('kTarget'); +const kCurrentTarget = Symbol('kCurrentTarget'); const kHandlers = Symbol('kHandlers'); const kWeakHandler = Symbol('kWeak'); const kResistStopPropagation = Symbol('kResistStopPropagation'); @@ -126,6 +127,7 @@ class Event { } this[kTarget] = null; + this[kCurrentTarget] = null; this[kIsBeingDispatched] = false; } @@ -196,7 +198,7 @@ class Event { get currentTarget() { if (!isEvent(this)) throw new ERR_INVALID_THIS('Event'); - return this[kTarget]; + return this[kCurrentTarget]; } /** @@ -757,20 +759,23 @@ class EventTarget { const createEvent = () => { if (event === undefined) { event = this[kCreateEvent](nodeValue, type); - event[kTarget] = this; + event[kCurrentTarget] = this; event[kIsBeingDispatched] = true; } return event; }; if (event !== undefined) { event[kTarget] = this; + event[kCurrentTarget] = this; event[kIsBeingDispatched] = true; } const root = this[kEvents].get(type); if (root === undefined || root.next === undefined) { - if (event !== undefined) + if (event !== undefined) { + event[kCurrentTarget] = null; event[kIsBeingDispatched] = false; + } return true; } @@ -827,8 +832,10 @@ class EventTarget { handler = next; } - if (event !== undefined) + if (event !== undefined) { + event[kCurrentTarget] = null; event[kIsBeingDispatched] = false; + } } [kCreateEvent](nodeValue, type) { diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index e5565462a12029..67693cfd15a014 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -14,7 +14,7 @@ Last update: - compression: https://github.com/web-platform-tests/wpt/tree/5aa50dd415/compression - console: https://github.com/web-platform-tests/wpt/tree/767ae35464/console - dom/abort: https://github.com/web-platform-tests/wpt/tree/d1f1ecbd52/dom/abort -- dom/events: https://github.com/web-platform-tests/wpt/tree/ab8999891c/dom/events +- dom/events: https://github.com/web-platform-tests/wpt/tree/0a811c5161/dom/events - encoding: https://github.com/web-platform-tests/wpt/tree/5aa50dd415/encoding - fetch/data-urls/resources: https://github.com/web-platform-tests/wpt/tree/7c79d998ff/fetch/data-urls/resources - FileAPI: https://github.com/web-platform-tests/wpt/tree/cceaf3628d/FileAPI diff --git a/test/fixtures/wpt/dom/events/Event-dispatch-click.html b/test/fixtures/wpt/dom/events/Event-dispatch-click.html index 010305775df7e9..ab4a24a5ad5096 100644 --- a/test/fixtures/wpt/dom/events/Event-dispatch-click.html +++ b/test/fixtures/wpt/dom/events/Event-dispatch-click.html @@ -87,6 +87,22 @@ child.dispatchEvent(new MouseEvent("click", {bubbles:true})) }, "pick the first with activation behavior ") +async_test(function(t) { + var input = document.createElement("input") + input.type = "radio" + dump.appendChild(input) + input.onclick = t.step_func(function() { + assert_false(input.checked, "input pre-click must not be triggered") + }) + var child = input.appendChild(document.createElement("input")) + child.type = "radio" + child.onclick = t.step_func(function() { + assert_true(child.checked, "child pre-click must be triggered") + }) + child.dispatchEvent(new MouseEvent("click", {bubbles:true})) + t.done() +}, "pick the first with activation behavior ") + async_test(function(t) { var input = document.createElement("input") input.type = "checkbox" @@ -173,6 +189,46 @@ t.done() }, "disabled checkbox still has activation behavior, part 2") +async_test(function(t) { + var state = "start" + + var form = document.createElement("form") + form.onsubmit = t.step_func(() => { + if(state == "start" || state == "radio") { + state = "failure" + } else if(state == "form") { + state = "done" + } + return false + }) + dump.appendChild(form) + var button = form.appendChild(document.createElement("button")) + button.type = "submit" + var radio = button.appendChild(document.createElement("input")) + radio.type = "radio" + radio.onclick = t.step_func(() => { + if(state == "start") { + assert_unreached() + } else if(state == "radio") { + assert_true(radio.checked) + } + }) + radio.disabled = true + radio.click() + assert_equals(state, "start") + + state = "radio" + radio.disabled = false + radio.click() + assert_equals(state, "radio") + + state = "form" + button.click() + assert_equals(state, "done") + + t.done() +}, "disabled radio still has activation behavior") + async_test(function(t) { var input = document.createElement("input") input.type = "checkbox" 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 index 361006a7240496..e7d6b455bbcaab 100644 --- a/test/fixtures/wpt/dom/events/Event-dispatch-on-disabled-elements.html +++ b/test/fixtures/wpt/dom/events/Event-dispatch-on-disabled-elements.html @@ -19,7 +19,7 @@ diff --git a/test/fixtures/wpt/dom/events/scrolling/overscroll-deltas.html b/test/fixtures/wpt/dom/events/scrolling/overscroll-deltas.html index 6f0b77f22eda2a..e13e9f1cce5949 100644 --- a/test/fixtures/wpt/dom/events/scrolling/overscroll-deltas.html +++ b/test/fixtures/wpt/dom/events/scrolling/overscroll-deltas.html @@ -6,80 +6,152 @@ - -
-
+ +
+
+
+
+
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 index cfc782a809a7e7..be4176df59d6a1 100644 --- 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 @@ -10,11 +10,14 @@ width: 200px; height: 200px; overflow: scroll; + position: absolute; + left: 150px; + top: 150px; } #innerDiv { - width: 400px; - height: 400px; + width: 250px; + height: 250px; } @@ -45,7 +48,7 @@ await waitForCompositorCommit(); // Do a horizontal scroll and wait for overscroll event. - await touchScrollInTarget(300, scrolling_div , 'right'); + await touchScrollInTarget(100, scrolling_div , 'right'); await waitFor(() => { return overscrolled_x_delta > 0; }, 'Scroller did not receive overscroll event after horizontal scroll.'); assert_equals(scrolling_div.scrollWidth - scrolling_div.scrollLeft, @@ -55,7 +58,7 @@ overscrolled_y_delta = 0; // Do a vertical scroll and wait for overscroll event. - await touchScrollInTarget(300, scrolling_div, 'down'); + await touchScrollInTarget(100, scrolling_div, 'down'); await waitFor(() => { return overscrolled_y_delta > 0; }, 'Scroller did not receive overscroll event after vertical scroll.'); assert_equals(scrolling_div.scrollHeight - scrolling_div.scrollTop, diff --git a/test/fixtures/wpt/dom/events/scrolling/scroll_support.js b/test/fixtures/wpt/dom/events/scrolling/scroll_support.js index 169393e4c3e419..a708364df07cad 100644 --- a/test/fixtures/wpt/dom/events/scrolling/scroll_support.js +++ b/test/fixtures/wpt/dom/events/scrolling/scroll_support.js @@ -1,15 +1,83 @@ -async function waitForScrollendEvent(test, target, timeoutMs = 500) { +async function waitForEvent(eventName, test, target, timeoutMs = 500) { return new Promise((resolve, reject) => { const timeoutCallback = test.step_timeout(() => { - reject(`No Scrollend event received for target ${target}`); + reject(`No ${eventName} event received for target ${target}`); }, timeoutMs); - target.addEventListener('scrollend', (evt) => { + target.addEventListener(eventName, (evt) => { clearTimeout(timeoutCallback); resolve(evt); }, { once: true }); }); } +async function waitForScrollendEvent(test, target, timeoutMs = 500) { + return waitForEvent("scrollend", test, target, timeoutMs); +} + +async function waitForScrollendEventNoTimeout(target) { + return new Promise((resolve) => { + target.addEventListener("scrollend", resolve); + }); +} + +async function waitForPointercancelEvent(test, target, timeoutMs = 500) { + return waitForEvent("pointercancel", test, target, timeoutMs); +} + +// Resets the scroll position to (0,0). If a scroll is required, then the +// promise is not resolved until the scrollend event is received. +async function waitForScrollReset(test, scroller, x = 0, y = 0) { + return new Promise(resolve => { + if (scroller.scrollTop == x && scroller.scrollLeft == y) { + resolve(); + } else { + const eventTarget = + scroller == document.scrollingElement ? document : scroller; + scroller.scrollTo(x, y); + waitForScrollendEventNoTimeout(eventTarget).then(resolve); + } + }); +} + +async function createScrollendPromiseForTarget(test, + target_div, + timeoutMs = 500) { + return waitForScrollendEvent(test, target_div, timeoutMs).then(evt => { + assert_false(evt.cancelable, 'Event is not cancelable'); + assert_false(evt.bubbles, 'Event targeting element does not bubble'); + }); +} + +function verifyNoScrollendOnDocument(test) { + const callback = + test.unreached_func("window got unexpected scrollend event."); + window.addEventListener('scrollend', callback); + test.add_cleanup(() => { + window.removeEventListener('scrollend', callback); + }); +} + +async function verifyScrollStopped(test, target_div) { + const unscaled_pause_time_in_ms = 100; + const x = target_div.scrollLeft; + const y = target_div.scrollTop; + return new Promise(resolve => { + test.step_timeout(() => { + assert_equals(target_div.scrollLeft, x); + assert_equals(target_div.scrollTop, y); + resolve(); + }, unscaled_pause_time_in_ms); + }); +} + +async function resetTargetScrollState(test, target_div) { + if (target_div.scrollTop != 0 || target_div.scrollLeft != 0) { + target_div.scrollTop = 0; + target_div.scrollLeft = 0; + return waitForScrollendEvent(test, target_div); + } +} + const MAX_FRAME = 700; const MAX_UNCHANGED_FRAMES = 20; @@ -45,6 +113,29 @@ function waitForCompositorCommit() { }); } +// Please don't remove this. This is necessary for chromium-based browsers. It +// can be a no-op on user-agents that do not have a separate compositor thread. +// TODO(crbug.com/1509054): This shouldn't be necessary if the test harness +// deferred running the tests until after paint holding. +async function waitForCompositorReady() { + const animation = + document.body.animate({ opacity: [ 0, 1 ] }, {duration: 1 }); + return animation.finished; +} + +function waitForNextFrame() { + const startTime = performance.now(); + return new Promise(resolve => { + window.requestAnimationFrame((frameTime) => { + if (frameTime < startTime) { + window.requestAnimationFrame(resolve); + } else { + resolve(); + } + }); + }); +} + // TODO(crbug.com/1400399): Deprecate as frame rates may vary greatly in // different test environments. function waitForAnimationEnd(getValue) { @@ -70,6 +161,10 @@ function waitForAnimationEnd(getValue) { } // Scrolls in target according to move_path with pauses in between +// The move_path should contains coordinates that are within target boundaries. +// Keep in mind that 0,0 is the center of the target element and is also +// the pointerDown position. +// pointerUp() is fired after sequence of moves. function touchScrollInTargetSequentiallyWithPause(target, move_path, pause_time_in_ms = 100) { const test_driver_actions = new test_driver.Actions() .addPointer("pointer1", "touch") @@ -88,7 +183,7 @@ function touchScrollInTargetSequentiallyWithPause(target, move_path, pause_time_ y += step_y; test_driver_actions.pointerMove(x, y, {origin: target}); } - test_driver_actions.pause(pause_time_in_ms); + test_driver_actions.pause(pause_time_in_ms); // To prevent inertial scroll } return test_driver_actions.pointerUp().send(); @@ -125,7 +220,7 @@ function touchScrollInTarget(pixels_to_scroll, target, direction, pause_time_in_ // Trigger fling by doing pointerUp right after pointerMoves. function touchFlingInTarget(pixels_to_scroll, target, direction) { - touchScrollInTarget(pixels_to_scroll, target, direction, 0 /* pause_time */); + return touchScrollInTarget(pixels_to_scroll, target, direction, 0 /* pause_time */); } function mouseActionsInTarget(target, origin, delta, pause_time_in_ms = 100) { @@ -161,3 +256,23 @@ function conditionHolds(condition, error_message = 'Condition is not true anymor tick(0); }); } + +function scrollElementDown(element, scroll_amount) { + let x = 0; + let y = 0; + let delta_x = 0; + let delta_y = scroll_amount; + let actions = new test_driver.Actions() + .scroll(x, y, delta_x, delta_y, {origin: element}); + return actions.send(); +} + +function scrollElementLeft(element, scroll_amount) { + let x = 0; + let y = 0; + let delta_x = scroll_amount; + let delta_y = 0; + let actions = new test_driver.Actions() + .scroll(x, y, delta_x, delta_y, {origin: element}); + return actions.send(); +} diff --git a/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-after-sequence-of-scrolls.tentative.html b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-after-sequence-of-scrolls.tentative.html index 77bf029ced58c5..dab6dcc9bd8d67 100644 --- a/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-after-sequence-of-scrolls.tentative.html +++ b/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-after-sequence-of-scrolls.tentative.html @@ -14,7 +14,7 @@ } #innerDiv { - width: 500px; + width: 4000px; height: 4000px; } @@ -28,36 +28,64 @@ 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 index c6569e0bebbd9f..449aea05351244 100644 --- 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 @@ -1,11 +1,19 @@ - + + + + + + + + + -
-
+
+
-
+
+
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 index acad168e56c995..edda88e7cb2064 100644 --- 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 @@ -7,96 +7,167 @@ -
-
-
-
-
-
-
-
+
+
+
+
-
+
+
+ +
+
+
+
+
+
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 deleted file mode 100644 index 734339694220cc..00000000000000 --- a/test/fixtures/wpt/dom/events/scrolling/scrollend-event-fired-to-scrolled-element.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - -
-
-
-
- - - 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 index ef72f56d2ba9d6..d2fd6f4d3158a5 100644 --- 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 @@ -7,49 +7,63 @@ -
-
+
+
-
+
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 index 5146c5f719a1e4..a06843a35e7ec9 100644 --- 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 @@ -8,6 +8,7 @@ +