commit a2466e87b00d1617782a9112c7088607589b3f55
parent 300f5338b9b3dd094f1411f40268f02015f55b45
Author: Mustaq Ahmed <mustaq@google.com>
Date: Fri, 7 Nov 2025 08:47:09 +0000
Bug 1998030 [wpt PR 55837] - Fix PointerEventManager's element tracker on content removal, a=testonly
Automatic update from web-platform-tests
Fix PointerEventManager's element tracker on content removal
This CL hooks up PointerEventManager's element-under-pointer tracker
to Document::NodeChildrenWillBeRemoved, as already done in
MouseEventManager. Before this change, when JS "overwrites" an
element's innerHTML, PEM used to wrongly retain references to deleted
nodes and even send events to those nodes.
Bug: 41487938
Change-Id: I7090f29061594a06e8b8a52c0654707a7657efb9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7081517
Commit-Queue: Mustaq Ahmed <mustaq@chromium.org>
Reviewed-by: Robert Flack <flackr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1540035}
--
wpt-commits: bb1eecc43d30bd3f67b79d90101aa60e7436c9bf
wpt-pr: 55837
Diffstat:
1 file changed, 102 insertions(+), 0 deletions(-)
diff --git a/testing/web-platform/tests/pointerevents/pointer_boundary_events_after_removing_last_over_element_through_innerhtml.html b/testing/web-platform/tests/pointerevents/pointer_boundary_events_after_removing_last_over_element_through_innerhtml.html
@@ -0,0 +1,102 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Boundary events fired after an innerHTML change removes the element under
+ pointer</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-actions.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<script src=pointerevent_support.js></script>
+
+<style>
+ div {
+ width: 80px;
+ height: 80px;
+ }
+</style>
+
+<div id="div1">
+ <div id="div2">
+ <div id="div3">
+ <div id="div4"></div>
+ </div>
+ </div>
+</div>
+<div id="done"></div>
+
+<script>
+"use strict";
+
+let event_log = [];
+let logged_event_prefix = "";
+
+function eventLogger(event) {
+ if (event.type.startsWith(logged_event_prefix) &&
+ event.eventPhase == event.AT_TARGET) {
+ event_log.push(`${event.type}@${event.target.id}`);
+ }
+}
+
+addEventListener("load", () => {
+ const div1 = document.getElementById("div1");
+ const div2 = document.getElementById("div2");
+ const div3 = document.getElementById("div3");
+ const div4 = document.getElementById("div4");
+ const done = document.getElementById("done");
+
+ for (const div of [div1, div2, div3, div4]) {
+ for (const event_suffix of ["enter", "leave", "over", "out", "move"]) {
+ div.addEventListener("pointer" + event_suffix, eventLogger);
+ div.addEventListener("mouse" + event_suffix, eventLogger);
+ }
+ }
+
+ div4.addEventListener("click", event => {
+ div2.innerHTML = "";
+ event_log.push("(empty-div2)");
+ });
+
+ function makePromiseTest(prefix) {
+ promise_test(async test => {
+ event_log = [];
+ logged_event_prefix = prefix;
+
+ test.add_cleanup(() => {
+ div2.appendChild(div3);
+ });
+
+ let done_click_promise = getEvent("click", done, test);
+
+ await new test_driver.Actions()
+ .pointerMove(0, 0, {origin: div4})
+ .pointerDown()
+ .pointerUp() // This sends a click that makes div2 empty.
+ .pointerMove(0, 0, {origin: done})
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ await done_click_promise;
+
+ const expected_events = [
+ "Eover@div4",
+ "Eenter@div1",
+ "Eenter@div2",
+ "Eenter@div3",
+ "Eenter@div4",
+ "Emove@div4",
+ "(empty-div2)",
+ "Eover@div2",
+ "Eout@div2",
+ "Eleave@div2",
+ "Eleave@div1"
+ ].map(s => s.replace("E", prefix));
+ assert_equals(event_log.toString(), expected_events.toString());
+ }, prefix + " boundary events fired");
+ }
+
+ makePromiseTest("pointer");
+ makePromiseTest("mouse");
+}, {once: true});
+</script>