Fix click.pointerId for touch events sent to subframes. (#31597)

Blink's pointerId generation is attached to low-level PointerEvent
handling (like pointerdown/up/move) which stores the data in the
local-frame-root.  However, to find a touch click's pointerId (which is
needed in an asynchronous manner), we have been accessing at the
subframe's state because of a historic crack in Blink EventHandler code
(https://crbug.com/449649).  This CL fixes the id-data lookup to access
local-frame-root's state instead.

Bug: 1264930
Change-Id: I9d2a76dd18c63086afd253575f99bf7f4f26bc7d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3273473
Reviewed-by: Robert Flack <flackr@chromium.org>
Commit-Queue: Mustaq Ahmed <mustaq@chromium.org>
Cr-Commit-Position: refs/heads/main@{#946622}

Co-authored-by: Mustaq Ahmed <mustaq@google.com>
diff --git a/pointerevents/pointerevent_click_is_a_pointerevent.html b/pointerevents/pointerevent_click_is_a_pointerevent.html
index 0ce9f07..46c1ed4 100644
--- a/pointerevents/pointerevent_click_is_a_pointerevent.html
+++ b/pointerevents/pointerevent_click_is_a_pointerevent.html
@@ -9,50 +9,68 @@
 <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>
 
 <input id="target" style="margin: 20px">
 
+<iframe src="resources/minimal.html" height="20" width="20"></iframe>
+
 <script>
 'use strict';
-let target = document.getElementById("target");
-let pointerId = 0;
-let pointerType = "";
-let inputSource = location.search.substring(1);
+const pointer_type = location.search.substring(1);
+let subframe_loaded = getMessageData("subframe-loaded", frames[0]);
 
-target.addEventListener("pointerdown", (e)=>{
-  pointerId = e.pointerId;
-  pointerType = e.pointerType;
-});
-
-function testFunction(test){
-  return test.step_func(e=>{
-    assert_equals(e.constructor, window.PointerEvent, "click should use a PointerEvent constructor");
-    assert_true(e instanceof PointerEvent, "click should be a PointerEvent");
-    assert_equals(e.pointerId, pointerId, "click's pointerId should match the pointerId of the pointer event that triggers it");
-    assert_equals(e.pointerType, pointerType, "click's pointerType should match the pointerType of the pointer event that triggers it");
-  });
+function assert_click_construction(click_event, window_object) {
+  assert_equals(click_event.constructor, window_object.PointerEvent,
+      "click should use a PointerEvent constructor");
+  assert_true(click_event instanceof window_object.PointerEvent,
+      "click should be a PointerEvent instance");
 }
 
-function run_test(pointerType){
-  promise_test((test) => new Promise((resolve, reject) => {
-    const testPointer = pointerType + "TestPointer";
-    let clickFunc = testFunction(test);
-    test.add_cleanup(() => {
-      target.removeEventListener("click", clickFunc);
-      pointerId = 0;
-      pointerType = "";
-    });
-    target.addEventListener("click", clickFunc);
-    let eventWatcher = new EventWatcher(test, target, ["click"]);
-    let actions = new test_driver.Actions();
-    actions = actions
-      .addPointer(testPointer, pointerType)
-      .pointerMove(0,0, {origin:target, sourceName:testPointer})
-      .pointerDown({sourceName:testPointer})
-      .pointerUp({sourceName:testPointer});
-    Promise.all([eventWatcher.wait_for("click"), actions.send()]).then(()=>resolve());
-  }), "click using " + pointerType + " is a PointerEvent");
+function assert_click_attributes(click_event, pointerdown_event, pointerup_event) {
+  assert_equals(click_event.pointerId, pointerdown_event.pointerId,
+      "click.pointerId should match the pointerId of the triggering pointerdown");
+  assert_equals(click_event.pointerType, pointerdown_event.pointerType,
+      "click.pointerType should match the pointerType of the triggering pointerdown");
+
+  assert_equals(click_event.pointerId, pointerup_event.pointerId,
+      "click.pointerId should match the pointerId of the triggering pointerup");
+  assert_equals(click_event.pointerType, pointerup_event.pointerType,
+                "click.pointerType should match the pointerType of the triggering pointerup");
 }
 
-run_test(inputSource);
+promise_test(async () => {
+  const target = document.getElementById("target");
+
+  let pointerdown_promise = getEvent("pointerdown", target);
+  let pointerup_promise = getEvent("pointerup", target);
+  let click_promise = getEvent("click", target);
+
+  await clickInTarget(pointer_type, target);
+
+  let pointerdown_event = await pointerdown_promise;
+  let pointerup_event = await pointerup_promise;
+  let click_event = await click_promise;
+
+  assert_click_construction(click_event, this);
+  assert_click_attributes(click_event, pointerdown_event, pointerup_event);
+}, "click using " + pointer_type + " is a PointerEvent");
+
+promise_test(async () => {
+  await subframe_loaded;
+
+  const target = frames[0];
+  let pointerdown_promise = getEvent("pointerdown", target);
+  let pointerup_promise = getEvent("pointerup", target);
+  let click_promise = getEvent("click", target);
+
+  await clickInTarget(pointer_type, frames[0].document.body);
+
+  let pointerdown_event = await pointerdown_promise;
+  let pointerup_event = await pointerup_promise;
+  let click_event = await click_promise;
+
+  assert_click_construction(click_event, frames[0]);
+  assert_click_attributes(click_event, pointerdown_event, pointerup_event);
+}, "click in a subframe using " + pointer_type + " is a PointerEvent");
 </script>
diff --git a/pointerevents/pointerevent_support.js b/pointerevents/pointerevent_support.js
index 6cbc8d6..727909f 100644
--- a/pointerevents/pointerevent_support.js
+++ b/pointerevents/pointerevent_support.js
@@ -453,3 +453,26 @@
 
   return true;
 }
+
+// Returns a |Promise| that gets resolved with the event object when |target|
+// receives an event of type |event_type|.
+function getEvent(event_type, target) {
+  return new Promise(resolve => {
+    target.addEventListener(event_type, e => resolve(e), {once: true});
+  });
+}
+
+// Returns a |Promise| that gets resolved with |event.data| when |window|
+// receives from |source| a "message" event whose |event.data.type| matches the string
+// |message_data_type|.
+function getMessageData(message_data_type, source) {
+  return new Promise(resolve => {
+    function waitAndRemove(e) {
+      if (e.source != source || !e.data || e.data.type != message_data_type)
+        return;
+      window.removeEventListener("message", waitAndRemove);
+      resolve(e.data);
+    }
+    window.addEventListener("message", waitAndRemove);
+  });
+}
diff --git a/pointerevents/resources/minimal.html b/pointerevents/resources/minimal.html
new file mode 100644
index 0000000..9402b95
--- /dev/null
+++ b/pointerevents/resources/minimal.html
@@ -0,0 +1,4 @@
+<body>Minimal HTML</body>
+<script>
+  parent.postMessage({"type": "subframe-loaded"}, "*");
+</script>