240 lines
8.4 KiB
HTML
240 lines
8.4 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="variant" content="?buttonType=LEFT&button=0&buttons=1">
|
|
<meta name="variant" content="?buttonType=MIDDLE&button=1&buttons=4">
|
|
<title>Testing button state of synthesized mouse(out|over|leave|enter) events</title>
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="/resources/testdriver.js"></script>
|
|
<script src="/resources/testdriver-vendor.js"></script>
|
|
<script src="/resources/testdriver-actions.js"></script>
|
|
<style>
|
|
#parent {
|
|
background-color: lightseagreen;
|
|
padding: 0;
|
|
height: 40px;
|
|
width: 40px;
|
|
}
|
|
#child {
|
|
background-color: red;
|
|
margin: 0;
|
|
height: 30px;
|
|
width: 30px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="parent"><div id="child">abc</div></div>
|
|
<script>
|
|
const searchParams = new URLSearchParams(document.location.search);
|
|
const buttonType = searchParams.get("buttonType");
|
|
const button = parseInt(searchParams.get("button"));
|
|
const buttons = parseInt(searchParams.get("buttons"));
|
|
|
|
let events = [];
|
|
function eventToString(data) {
|
|
if (!data) {
|
|
return "{}";
|
|
}
|
|
return `{ '${data.type}' on '${data.target}': button=${data.button}, buttons=${data.buttons} }`;
|
|
}
|
|
|
|
function eventsToString(events) {
|
|
if (!events.length) {
|
|
return "[]";
|
|
}
|
|
let ret = "[";
|
|
for (const data of events) {
|
|
if (ret != "[") {
|
|
ret += ", ";
|
|
}
|
|
ret += eventToString(data);
|
|
}
|
|
return ret + "]";
|
|
}
|
|
|
|
function removeEventsBefore(eventType) {
|
|
while (events[0]?.type != eventType) {
|
|
events.shift();
|
|
}
|
|
}
|
|
|
|
const parentElement = document.getElementById("parent");
|
|
const childElement = document.getElementById("child");
|
|
|
|
function promiseLayout() {
|
|
return new Promise(resolve => {
|
|
(childElement.isConnected ? childElement : parentElement).getBoundingClientRect();
|
|
requestAnimationFrame(() => requestAnimationFrame(resolve));
|
|
});
|
|
}
|
|
|
|
promise_test(async () => {
|
|
await new Promise(resolve => {
|
|
addEventListener("load", resolve, { once: true });
|
|
});
|
|
|
|
["mouseout", "mouseover", "mouseleave", "mouseenter", "mousemove", "mousedown"].forEach(eventType => {
|
|
parentElement.addEventListener(eventType, event => {
|
|
if (event.target != parentElement) {
|
|
return;
|
|
}
|
|
events.push({
|
|
type: event.type,
|
|
target: "parent",
|
|
button: event.button,
|
|
buttons: event.buttons,
|
|
});
|
|
});
|
|
childElement.addEventListener(eventType, event => {
|
|
if (event.target != childElement) {
|
|
return;
|
|
}
|
|
events.push({
|
|
type: event.type,
|
|
target: "child",
|
|
button: event.button,
|
|
buttons: event.buttons,
|
|
});
|
|
});
|
|
});
|
|
}, "Setup event listeners and wait for load");
|
|
|
|
promise_test(async t => {
|
|
events = [];
|
|
await promiseLayout();
|
|
childElement.addEventListener("mousedown", () => childElement.remove(), {once: true});
|
|
const {x, y} = (function () {
|
|
const rect = childElement.getBoundingClientRect();
|
|
return { x: rect.left, y: rect.top };
|
|
})();
|
|
const actions = new test_driver.Actions();
|
|
await actions.pointerMove(10, 10, {origin: childElement})
|
|
.pointerDown({button: actions.ButtonType[buttonType]})
|
|
.pause(100) // Allow browsers to synthesize mouseout, etc
|
|
.pointerUp({button: actions.ButtonType[buttonType]})
|
|
.send();
|
|
await promiseLayout();
|
|
removeEventsBefore("mousedown");
|
|
test(() => {
|
|
const maybeMouseDownEvent =
|
|
events.length && events[0].type == "mousedown" ? events.shift() : undefined;
|
|
assert_equals(
|
|
eventToString(maybeMouseDownEvent),
|
|
eventToString({ type: "mousedown", target: "child", button, buttons })
|
|
);
|
|
}, `${t.name}: mousedown should've been fired`);
|
|
assert_true(events.length > 0, `${t.name}: Some events should've been fired after mousedown`);
|
|
test(() => {
|
|
// Before `mousedown` is fired, both parent and child must have received
|
|
// `mouseenter`, only the child must have received `mouseover`. Then, the
|
|
// child is now moved away by the `mousedown` listener. Therefore,
|
|
// `mouseout` and `mouseleave` should be fired on the child as the spec of
|
|
// UI Events defines. Then, they are not a button press events. Therefore,
|
|
// the `button` should be 0, but buttons should be set to 4 because of
|
|
// pressing the middle button.
|
|
let mouseOutOrLeave = [];
|
|
while (events[0]?.type == "mouseout" || events[0]?.type == "mouseleave") {
|
|
mouseOutOrLeave.push(events.shift());
|
|
}
|
|
assert_equals(
|
|
eventsToString(mouseOutOrLeave),
|
|
eventsToString([
|
|
{ type: "mouseout", target: "child", button: 0, buttons },
|
|
{ type: "mouseleave", target: "child", button: 0, buttons },
|
|
])
|
|
);
|
|
}, `${t.name}: mouseout and mouseleave should've been fired on the removed child`);
|
|
test(() => {
|
|
// And `mouseover` should be fired on the parent as the spec of UI Events
|
|
// defines.
|
|
let mouseOver = [];
|
|
while (events[0]?.type == "mouseover") {
|
|
mouseOver.push(events.shift());
|
|
}
|
|
assert_equals(
|
|
eventsToString(mouseOver),
|
|
eventsToString([{ type: "mouseover", target: "parent", button: 0, buttons }])
|
|
);
|
|
}, `${t.name}: mouseover should've been fired on the parent`);
|
|
test(() => {
|
|
// On the other hand, it's unclear about `mouseenter`. The mouse cursor has
|
|
// never been moved out from the parent. Therefore, it shouldn't be fired
|
|
// on the parent ideally, but all browsers do not pass this test and there
|
|
// is no clear definition about this case.
|
|
let mouseEnter = [];
|
|
while (events.length && events[0].type == "mouseenter") {
|
|
mouseEnter.push(events.shift());
|
|
}
|
|
assert_equals(eventsToString(mouseEnter), eventsToString([]));
|
|
}, `${t.name}: mouseenter should not have been fired on the parent`);
|
|
assert_equals(eventsToString(events), eventsToString([]), "All events should've been checked");
|
|
parentElement.appendChild(childElement);
|
|
}, "Removing an element at mousedown");
|
|
|
|
promise_test(async t => {
|
|
events = [];
|
|
await promiseLayout();
|
|
childElement.addEventListener("mouseup", () => childElement.remove(), {once: true});
|
|
const {x, y} = (function () {
|
|
const rect = childElement.getBoundingClientRect();
|
|
return { x: rect.left, y: rect.top };
|
|
})();
|
|
const actions = new test_driver.Actions();
|
|
await actions.pointerMove(10, 10, {origin: childElement})
|
|
.pointerDown({button: actions.ButtonType[buttonType]})
|
|
.pointerUp({button: actions.ButtonType[buttonType]})
|
|
.send();
|
|
await promiseLayout();
|
|
removeEventsBefore("mousedown");
|
|
test(() => {
|
|
const maybeMouseDownEvent =
|
|
events.length && events[0].type == "mousedown" ? events.shift() : undefined;
|
|
assert_equals(
|
|
eventToString(maybeMouseDownEvent),
|
|
eventToString({ type: "mousedown", target: "child", button, buttons })
|
|
);
|
|
}, `${t.name}: mousedown should've been fired`);
|
|
assert_true(events.length > 0, `${t.name}: Some events should've been fired after mousedown`);
|
|
// Same as the `mousedown` case except `buttons` value because `mouseout`,
|
|
// `mouseleave`, `mouseover` and `mouseenter` should (or may) be fired
|
|
// after the `mouseup`. Therefore, `.buttons` should not have the button
|
|
// flag.
|
|
test(() => {
|
|
let mouseOutOrLeave = [];
|
|
while (events[0]?.type == "mouseout" || events[0]?.type == "mouseleave") {
|
|
mouseOutOrLeave.push(events.shift());
|
|
}
|
|
assert_equals(
|
|
eventsToString(mouseOutOrLeave),
|
|
eventsToString([
|
|
{ type: "mouseout", target: "child", button: 0, buttons: 0 },
|
|
{ type: "mouseleave", target: "child", button: 0, buttons: 0 },
|
|
])
|
|
);
|
|
}, `${t.name}: mouseout and mouseleave should've been fired on the removed child`);
|
|
test(() => {
|
|
let mouseOver = [];
|
|
while (events[0]?.type == "mouseover") {
|
|
mouseOver.push(events.shift());
|
|
}
|
|
assert_equals(
|
|
eventsToString(mouseOver),
|
|
eventsToString([{ type: "mouseover", target: "parent", button: 0, buttons: 0 }])
|
|
);
|
|
}, `${t.name}: mouseover should've been fired on the parent`);
|
|
test(() => {
|
|
let mouseEnter = [];
|
|
while (events[0]?.type == "mouseenter") {
|
|
mouseEnter.push(events.shift());
|
|
}
|
|
assert_equals(eventsToString(mouseEnter), eventsToString([]));
|
|
}, `${t.name}: mouseenter should not have been fired on the parent`);
|
|
assert_equals(eventsToString(events), eventsToString([]), "All events should've been checked");
|
|
parentElement.appendChild(childElement);
|
|
}, "Removing an element at mouseup");
|
|
</script>
|
|
</body>
|
|
</html>
|