[Interop] pointerenter/leave events after child element is removed

A removed element shouldn't cause either a `mouseenter` or a
`pointerenter` event to be fired to the parent element.  This test
asserts this behavior as implied by the UIEvent spec.  Unfortunately
the major browsers don't agree here!

Bug: 1147998
Change-Id: Ia8ede1c121d6f62bda41b7675683e077d20af00f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4064304
Reviewed-by: Kevin Ellis <kevers@chromium.org>
Commit-Queue: Mustaq Ahmed <mustaq@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1078126}
diff --git a/pointerevents/pointerevent_after_target_removed.html b/pointerevents/pointerevent_after_target_removed.html
new file mode 100644
index 0000000..43d5ab3
--- /dev/null
+++ b/pointerevents/pointerevent_after_target_removed.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<title>Enter/leave events fired to parent after child is removed</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.target {
+      width: 100px;
+      height: 50px;
+  }
+</style>
+<div class="target" id="parent">
+  <div class="target" id="child">child</div>
+</div>
+<div id="done">done</div>
+
+<script>
+  'use strict';
+
+  const parent = document.getElementById("parent");
+  const child = document.getElementById("child");
+  const done = document.getElementById("done");
+
+  let event_log = [];
+  let logged_event_prefix = "";
+
+  function logEvent(e) {
+    if (e.type.startsWith(logged_event_prefix) && e.eventPhase == e.AT_TARGET)
+      event_log.push(e.type);
+  }
+
+  function setup() {
+    const logged_events = [
+      "pointerover", "pointerout", "pointerenter", "pointerleave",
+      "mouseover", "mouseout", "mouseenter", "mouseleave"
+    ];
+    logged_events.forEach(ename => parent.addEventListener(ename, logEvent));
+  }
+
+  function addPromiseTest(remover_event, tested_event_prefix) {
+    const test_name = tested_event_prefix
+        + " events at parent after child is removed at " + remover_event;
+    const expected_events = ["enter", "over", "out", "leave"]
+        .map(suffix => tested_event_prefix + suffix);
+
+    promise_test(async () => {
+      event_log = [];
+      logged_event_prefix = tested_event_prefix;
+
+      // Bring the child back if it was removed in the previous promise_test.
+      if (!child.parentElement)
+        parent.appendChild(child);
+
+      child.addEventListener(remover_event,
+          () => parent.removeChild(child),
+          { once:true });
+
+      let done_click_promise = getEvent("click", done);
+
+      let actions = new test_driver.Actions().addPointer("TestPointer", "mouse")
+          .pointerMove(0, 0, {origin: parent})
+          .pointerDown()
+          .pointerUp()
+          .pointerMove(0, 0, {origin: done})
+          .pointerDown()
+          .pointerUp()
+
+      await actions.send();
+      await done_click_promise;
+
+      assert_equals(event_log.toString(), expected_events.toString(),
+          "received events");
+    }, test_name);
+  }
+
+  setup();
+  addPromiseTest("pointerdown", "pointer");
+  addPromiseTest("pointerdown", "mouse");
+  addPromiseTest("pointerup", "pointer");
+  addPromiseTest("pointerup", "mouse");
+</script>