<!DOCTYPE HTML>
<link rel="help" href="https://w3c.github.io/pointerevents/#firing-events-using-the-pointerevent-interface">
<title>Enter/leave events fired to parent after child is removed from slot</title>
<meta name="variant" content="?mouse">
<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>
<template id="template">
<style>
div {
width: 100px;
height: 100px;
}
</style>
<div id="parent">
<slot id="slot">slot</slot>
</div>
</template>
<style>
div, my-elem {
width: 100px;
height: 100px;
display: block;
}
</style>
<my-elem id="host">
<div id="child">child</div>
</my-elem>
<div id="done">done</div>
<script>
"use strict";
customElements.define(
"my-elem",
class extends HTMLElement {
constructor() {
super();
let content = document.getElementById("template").content;
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.appendChild(content.cloneNode(true));
}
},
);
const pointer_type = location.search.substring(1);
const shadow_host = document.getElementById("host");
const parent = shadow_host.shadowRoot.getElementById("parent");
const slot = parent.firstElementChild;
const slotted_child = document.getElementById("child");
const done = document.getElementById("done");
let event_log = [];
let elem_to_remove;
function logEvent(e) {
if (e.eventPhase == e.AT_TARGET) {
event_log.push(e.type + "@" + e.target.id);
}
}
function removeChildFromSlot() {
elem_to_remove.remove();
event_log.push("(child-removed)");
}
function restoreChildInSlot() {
if (!slotted_child.parentElement) {
shadow_host.appendChild(slotted_child);
}
if (!slot.parentElement) {
parent.appendChild(slot);
}
}
function setup() {
const events = ["pointerover", "pointerout",
"pointerenter", "pointerleave", "pointerdown", "pointerup"];
let targets = [shadow_host, parent, slot, slotted_child];
for (let i = 0; i < targets.length; i++) {
events.forEach(event => targets[i].addEventListener(event, logEvent));
}
}
function addPromiseTest(remover_event, tested_elem_to_remove,
expected_events) {
assert_true(["slot", "slotted-child"].includes(tested_elem_to_remove),
"Unexpcted tested_elem_to_remove param");
const test_name = `Pointer events from ${pointer_type} `+
`received before/after ${tested_elem_to_remove} removal `+
`at ${remover_event}`;
promise_test(async test => {
event_log = [];
elem_to_remove = (tested_elem_to_remove == "slot" ? slot : slotted_child);
restoreChildInSlot();
child.addEventListener(remover_event, removeChildFromSlot,
{ once: true });
// TODO([email protected]): It would be more robust if we could remove
// the event listener above through `test.add_cleanup()` but strangely the
// cleanup call fails after the test that removes the slotted-child! This
// happens even if we make the shadow DOM construction dynamic inside this
// `promise_test`!!!
let done_click_promise = getEvent("click", done);
let actions = new test_driver.Actions()
.addPointer("TestPointer", pointer_type)
.pointerMove(-30, -30, {origin: shadow_host})
.pointerDown()
.pointerUp()
.pointerMove(30, 30, {origin: shadow_host})
.pointerDown()
.pointerUp()
.pointerMove(0, 0, {origin: done})
.pointerDown()
.pointerUp();
await actions.send();
await done_click_promise;
let removal_in_event_log = event_log.indexOf("(child-removed)");
let removal_in_expected_list = expected_events.indexOf("(child-removed)");
assert_true(removal_in_event_log != -1 && removal_in_expected_list != -1,
"(child-removed) expected in both lists");
assert_equals(event_log.slice(0, removal_in_event_log).toString(),
expected_events.slice(0, removal_in_expected_list).toString(),
"events received before removal");
assert_equals(event_log.slice(removal_in_event_log+1).toString(),
expected_events.slice(removal_in_expected_list+1).toString(),
"events received after removal");
}, test_name);
}
setup();
addPromiseTest(
"pointerdown",
"slot",
[
"pointerover@child",
"pointerenter@host", "pointerenter@parent", "pointerenter@slot", "pointerenter@child",
"pointerdown@child", "(child-removed)",
"pointerover@parent", "pointerover@host", "pointerup@parent", "pointerup@host",
"pointerdown@parent", "pointerdown@host", "pointerup@parent", "pointerup@host",
"pointerout@parent", "pointerout@host",
"pointerleave@parent", "pointerleave@host"
]
);
addPromiseTest(
"pointerdown",
"slotted-child",
[
"pointerover@child",
"pointerenter@host", "pointerenter@parent", "pointerenter@slot", "pointerenter@child",
"pointerdown@child", "(child-removed)",
"pointerleave@slot",
"pointerover@parent", "pointerover@host", "pointerup@parent", "pointerup@host",
"pointerdown@parent", "pointerdown@host", "pointerup@parent", "pointerup@host",
"pointerout@parent", "pointerout@host",
"pointerleave@parent", "pointerleave@host"
]
);
addPromiseTest(
"pointerup",
"slot",
[
"pointerover@child",
"pointerenter@host", "pointerenter@parent", "pointerenter@slot", "pointerenter@child",
"pointerdown@child", "pointerup@child", "(child-removed)",
"pointerover@parent", "pointerover@host",
"pointerdown@parent", "pointerdown@host", "pointerup@parent", "pointerup@host",
"pointerout@parent", "pointerout@host",
"pointerleave@parent", "pointerleave@host"
]
);
addPromiseTest(
"pointerup",
"slotted-child",
[
"pointerover@child",
"pointerenter@host", "pointerenter@parent", "pointerenter@slot", "pointerenter@child",
"pointerdown@child", "pointerup@child", "(child-removed)",
"pointerleave@slot",
"pointerover@parent", "pointerover@host",
"pointerdown@parent", "pointerdown@host", "pointerup@parent", "pointerup@host",
"pointerout@parent", "pointerout@host",
"pointerleave@parent", "pointerleave@host"
]
);
</script>