Bug 1966551 - Make PointerEventHandler::DispatchPointerFromMouseOrTouch() dispatch synthesized ePointerMove for synthesized eMouseMove if it's caused by hoverable pointer r=smaug

Currently, we don't dispatch synthesized `ePointerMove` unless it's
required for dispatch the boundary events after dispatching
`ePointerLostCapture` event [1] since Pointer Events defined that the
boundary events should be fired only when before dispatching a pointer
event.  However, it's changed, Point Events currently defines that the
boundary events should be fired if the element under the pointer is
changed without a `pointermove` [2] if and only if the pointer supports
hover.

Therefore, this patch makes `PresShell` store the last input source
whose event set the mouse location at last and
`PresShell::ProcessSynthMouseMoveEvent()` sets the input source to make
`PointerEventHandler::DispatchPointerFromMouseOrTouch()` can consider
whether it needs to dispatch pointer boundary events or not for the
pointer.

Additionally, the mochitests for the manual WPTs under
`dom/events/test/pointerevents` checks `pointerId`.  Therefore, this
patch makes `PresShell` also store the last `pointerId` and set it to
the synthesized `eMouseMove` too.

I think that this approach is **not** correct approach to fix this bug
because there could be multiple hoverable pointers, but we synthesize
pointer boundary events only for the last input device.  I think it's
enough for now because we've not supported pen well (we've not supported
the test API yet!), so, we only support only mouse input well as
hoverable inputs.  I think we should extend `PointerInfo` and make a
synthesizer of `ePointerMove` later.

Note that this patch changes 2 WPTs which both are in the scope of
Interop.

The expectation of
`pointerevent_pointer_boundary_events_after_removing_last_over_element.html`
needs to be changed for conforming to the latest spec.  I wrote this
test before the spec change and it wasn't updated when the spec is
changed. I filed this issue to interop [3].

The changes for `pointerevent_pointerout_no_pointer_movement.html` is
required for avoiding the timeout.  Gecko does not allow recursive
synthesized `eMouseMove` to prevent infinite reflow loops without moving
the mouse cursor.  However, the test expects that and that causes
requiring the hack for Chrome too.  Therefore, I split the test to
make each step run in different event loop and I removed the hack for
Chrome.

Note that this patch also removes 2 sets of mochitests for WPT manual
tests because they are now tested with the test driver [4][5] and they
fail without maintained.

1. https://searchfox.org/mozilla-central/rev/f571db8014431de31d245017e2f5457046aec4ea/dom/events/PointerEventHandler.cpp#494-503
2. https://w3c.github.io/pointerevents/#boundary-events-caused-by-layout-changes
3. https://github.com/web-platform-tests/interop/issues/961
4. https://wpt.fyi/results/pointerevents/pointerevent_boundary_events_in_capturing.html%3Fmouse?label=master&label=experimental&aligned&view=interop
5. https://wpt.fyi/results/pointerevents/pointerevent_releasepointercapture_events_to_original_target.html%3Fmouse?label=master&label=experimental&aligned&view=interop

Differential Revision: https://phabricator.services.mozilla.com/D250421
This commit is contained in:
Masayuki Nakano
2025-05-22 03:22:05 +00:00
committed by masayuki@d-toybox.com
parent 9b669b5846
commit 66ee19892a
15 changed files with 86 additions and 373 deletions

View File

@@ -894,12 +894,31 @@ void PointerEventHandler::DispatchPointerFromMouseOrTouch(
return;
}
// 1. If it is not mouse then it is likely will come as touch event
// 2. We don't synthesize pointer events for synthesized mouse move
if (!mouseEvent->convertToPointer || mouseEvent->IsSynthesized()) {
// If it is not mouse then it is likely will come as touch event
if (!mouseEvent->convertToPointer) {
return;
}
// If it's a synthesized eMouseMove and the input source supports hover, we
// need to dispatch pointer boundary events if the element underneath the
// pointer has already been changed from the last `pointerover` event
// target.
if (mouseEvent->IsSynthesized()) {
if (!StaticPrefs::
dom_event_pointer_boundary_dispatch_when_layout_change() ||
!mouseEvent->InputSourceSupportsHover()) {
return;
}
// So, if the pointer is captured, we don't need to dispatch pointer
// boundary events since pointer boundary events should be fired before
// gotpointercapture.
PointerCaptureInfo* const captureInfo =
GetPointerCaptureInfo(mouseEvent->pointerId);
if (captureInfo && captureInfo->mOverrideElement) {
return;
}
}
pointerMessage = PointerEventHandler::ToPointerEventMessage(mouseEvent);
if (pointerMessage == eVoidEvent) {
return;

View File

@@ -144,13 +144,6 @@ skip-if = [
"http2",
]
["test_wpt_pointerevent_boundary_events_in_capturing-manual.html"]
support-files = ["wpt/pointerevent_boundary_events_in_capturing-manual.html"]
skip-if = [
"http3",
"http2",
]
["test_wpt_pointerevent_drag_interaction-manual.html"]
support-files = ["wpt/html/pointerevent_drag_interaction-manual.html"]
skip-if = [
@@ -207,13 +200,6 @@ skip-if = [
"http2",
]
["test_wpt_pointerevent_releasepointercapture_events_to_original_target-manual.html"]
support-files = ["wpt/pointerevent_releasepointercapture_events_to_original_target-manual.html"]
skip-if = [
"http3",
"http2",
]
["test_wpt_pointerevent_releasepointercapture_onpointercancel_touch-manual.html"]
support-files = ["wpt/pointerevent_releasepointercapture_onpointercancel_touch-manual.html"]
skip-if = [

View File

@@ -24,16 +24,22 @@ function runTests() {
let target0 = window.document.getElementById("target0");
let pointerEventsList = ["pointerover", "pointerenter", "pointerdown",
"pointerup", "pointerleave", "pointerout"];
let receivedPointerEvents = false;
const pointerEvents = [];
pointerEventsList.forEach((elem, index, arr) => {
target0.addEventListener(elem, (event) => {
ok(false, "receiving event " + event.type);
receivedPointerEvents = true;
});
target0.addEventListener(elem, event => pointerEvents.push(event.type));
});
target1.addEventListener("mouseup", () => {
ok(!receivedPointerEvents, "synthesized mousemove should not trigger any pointer events");
is(
pointerEvents.join(", "),
[
"pointerover", // Should be caused by the synthesized mousemove
"pointerenter",
"pointerout", // Should be caused by clicking target1
"pointerleave",
].join(", "),
"Synthesizing mousemove should cause only pointer boundary events"
);
SimpleTest.finish();
});

View File

@@ -1,46 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>W3C pointerevent_boundary_events_in_capturing-manual.html in Mochitest form</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="mochitest_support_external.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="text/javascript">
SimpleTest.waitForExplicitFinish();
function startTest() {
runTestInNewWindow("wpt/pointerevent_boundary_events_in_capturing-manual.html", true);
}
function executeTest(int_win) {
sendMouseEvent(int_win, "target0", "mousemove");
sendMouseEvent(int_win, "target0", "mousedown");
sendMouseEvent(int_win, "target0", "mousemove", {buttons: 1});
sendMouseEvent(int_win, "target0", "mousemove", {buttons: 1});
sendMouseEvent(int_win, "target0", "mouseup");
window.addEventListener("message", function(aEvent) {
if (aEvent.data == "Test Touch") {
// Synthesize touch events to run this test.
sendTouchEvent(int_win, "target0", "touchstart");
sendTouchEvent(int_win, "target0", "touchmove", {offsetX: 10});
sendTouchEvent(int_win, "target0", "touchmove", {offsetX: 15});
sendTouchEvent(int_win, "target0", "touchmove", {offsetX: 20});
sendTouchEvent(int_win, "target0", "touchend");
window.postMessage("Test Pen", "*");
} else if (aEvent.data == "Test Pen") {
// Synthesize pen events to run this test.
sendMouseEvent(int_win, "target0", "mousemove", {inputSource:MouseEvent.MOZ_SOURCE_PEN});
sendMouseEvent(int_win, "target0", "mousedown", {inputSource:MouseEvent.MOZ_SOURCE_PEN});
sendMouseEvent(int_win, "target0", "mousemove", {inputSource:MouseEvent.MOZ_SOURCE_PEN, buttons: 1});
sendMouseEvent(int_win, "target0", "mousemove", {inputSource:MouseEvent.MOZ_SOURCE_PEN, buttons: 1});
sendMouseEvent(int_win, "target0", "mouseup", {inputSource:MouseEvent.MOZ_SOURCE_PEN});
}
});
window.postMessage("Test Touch", "*");
}
</script>
</head>
<body>
</body>
</html>

View File

@@ -1,49 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1000870
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1000870</title>
<meta name="author" content="Maksim Lebedev" />
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="mochitest_support_external.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="text/javascript">
SimpleTest.waitForExplicitFinish();
function startTest() {
runTestInNewWindow("wpt/pointerevent_releasepointercapture_events_to_original_target-manual.html");
}
function executeTest(int_win) {
// Synthesize mouse events to run this test.
sendMouseEvent(int_win, "target0", "mousemove");
sendMouseEvent(int_win, "target0", "mousedown");
sendMouseEvent(int_win, "target0", "mousemove", {buttons: 1});
sendMouseEvent(int_win, "target0", "mousemove", {buttons: 1});
sendMouseEvent(int_win, "target0", "mouseup");
window.addEventListener("message", function(aEvent) {
if (aEvent.data == "Test Touch") {
// Synthesize touch events to run this test.
sendTouchEvent(int_win, "target0", "touchstart", {offsetX: 10});
sendTouchEvent(int_win, "target0", "touchmove", {offsetX: 11});
sendTouchEvent(int_win, "target0", "touchend", {offsetX: 11});
window.postMessage("Test Pen", "*");
} else if (aEvent.data == "Test Pen") {
// Synthesize pen events to run this test.
sendMouseEvent(int_win, "target0", "mousemove", {inputSource:MouseEvent.MOZ_SOURCE_PEN});
sendMouseEvent(int_win, "target0", "mousedown", {inputSource:MouseEvent.MOZ_SOURCE_PEN});
sendMouseEvent(int_win, "target0", "mousemove", {inputSource:MouseEvent.MOZ_SOURCE_PEN, buttons: 1});
sendMouseEvent(int_win, "target0", "mousemove", {inputSource:MouseEvent.MOZ_SOURCE_PEN, buttons: 1});
sendMouseEvent(int_win, "target0", "mouseup", {inputSource:MouseEvent.MOZ_SOURCE_PEN});
}
});
window.postMessage("Test Touch", "*");
}
</script>
</head>
<body>
</body>
</html>

View File

@@ -1,97 +0,0 @@
<!doctype html>
<html>
<head>
<title>Pointer Events boundary events in capturing tests</title>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<!-- Additional helper script for common checks across event types -->
<script type="text/javascript" src="pointerevent_support.js"></script>
<script>
var detected_pointertypes = {};
var eventList = All_Pointer_Events;
PhaseEnum = {
WaitingForDown: "down",
WaitingForFirstMove: "firstMove",
WaitingForSecondMove: "secondMove",
WaitingForUp: "up"
}
var phase = PhaseEnum.WaitingForDown;
var eventsRecieved = [];
function resetTestState() {
eventsRecieved = [];
phase = PhaseEnum.WaitingForDown;
}
function run() {
var test_pointerEvent = setup_pointerevent_test("pointerevent boundary events in capturing", ALL_POINTERS);
var target = document.getElementById("target0");
var listener = document.getElementById("listener");
eventList.forEach(function(eventName) {
on_event(target, eventName, function (event) {
if (phase == PhaseEnum.WaitingForDown) {
if (eventName == 'pointerdown') {
listener.setPointerCapture(event.pointerId);
phase = PhaseEnum.WaitingForFirstMove;
}
} else if (phase == PhaseEnum.WaitingForUp) {
if (event.type == 'pointerup')
test_pointerEvent.done();
} else {
eventsRecieved.push(event.type + '@target');
if (phase == PhaseEnum.WaitingForSecondMove && event.type == 'pointermove') {
test(function () {
checkPointerEventType(event);
assert_array_equals(eventsRecieved, ['lostpointercapture@listener', 'pointerout@listener', 'pointerleave@listener', 'pointerover@target', 'pointerenter@target', 'pointermove@target'],
'lostpointercapture and pointerout/leave should be dispatched to the capturing target and pointerover/enter should be dispatched to the hit-test element before the first pointermove event after releasing pointer capture');
}, expectedPointerType + " pointer events boundary events when releasing capture");
phase = PhaseEnum.WaitingForUp;
}
}
});
on_event(listener, eventName, function (event) {
if (phase == PhaseEnum.WaitingForDown)
return;
eventsRecieved.push(event.type + '@listener');
if (phase == PhaseEnum.WaitingForFirstMove && eventName == 'pointermove') {
test(function () {
checkPointerEventType(event);
assert_array_equals(eventsRecieved, ['pointerout@target', 'pointerleave@target', 'pointerover@listener', 'pointerenter@listener', 'gotpointercapture@listener', 'pointermove@listener'],
'pointerout/leave should be dispatched to the previous target and pointerover/enter and gotpointercapture should be dispatched to the capturing element before the first captured pointermove event');
}, expectedPointerType + " pointer events boundary events when receiving capture");
listener.releasePointerCapture(event.pointerId);
eventsRecieved = [];
phase = PhaseEnum.WaitingForSecondMove;
}
});
});
}
</script>
</head>
<body onload="run()">
<h1>Pointer Events boundary events in capturing</h1>
<h2 id="pointerTypeDescription"></h2>
<h4>
Test Description: This test checks the boundary events of pointer events while the capturing changes. If you are using hoverable pen don't leave the range of digitizer while doing the instructions.
<ol>
<li>Move your pointer over the black square</li>
<li>Press down the pointer (i.e. press left button with mouse or touch the screen with finger or pen).</li>
<li>Drag the pointer within the black square.</li>
<li>Release the pointer.</li>
</ol>
Test passes if the proper behavior of the events is observed.
</h4>
<div id="target0" class="touchActionNone">
</div>
<div id="listener">Do not hover over or touch this element. </div>
<div id="complete-notice">
<p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
<p>Refresh the page to run the tests again with a different pointer type.</p>
</div>
<div id="log"></div>
</body>
</html>

View File

@@ -1,137 +0,0 @@
<!doctype html>
<html>
<head>
<title>Pointer Event: releasePointerCapture() - subsequent events follow normal hitting testing mechanisms</title>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
<link rel="author" title="Microsoft" href="http://www.microsoft.com/"/>
<meta name="assert" content="After invoking the releasePointerCapture method on an element, subsequent events for the specified pointer must follow normal hit testing mechanisms for determining the event target"/>
<link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<!-- Additional helper script for common checks across event types -->
<script type="text/javascript" src="pointerevent_support.js"></script>
<script type="text/javascript">
var test_pointerEvent;
var detected_pointertypes = {};
var captured_event = null;
var test_done = false;
var overEnterEventsFail = false;
var outLeaveEventsFail = false;
var f_gotPointerCapture = false;
var f_lostPointerCapture = false;
function resetTestState() {
captured_event = null;
test_done = false;
overEnterEventsFail = false;
outLeaveEventsFail = false;
f_gotPointerCapture = false;
f_lostPointerCapture = false;
}
function listenerEventHandler(event) {
if (test_done)
return;
detected_pointertypes[event.pointerType] = true;
if (event.type == "gotpointercapture") {
f_gotPointerCapture = true;
check_PointerEvent(event);
}
else if (event.type == "lostpointercapture") {
f_lostPointerCapture = true;
f_gotPointerCapture = false;
check_PointerEvent(event);
}
else if(event.type == "pointerover" || event.type == "pointerenter") {
if(captured_event && !overEnterEventsFail) {
test(function() {
assert_false(f_gotPointerCapture, "pointerover/enter should be received before the target receives gotpointercapture even when the pointer is not over it.");
}, expectedPointerType + " pointerover/enter should be received before the target receives gotpointercapture even when the pointer is not over it.");
overEnterEventsFail = true;
}
}
else if(event.type == "pointerout" || event.type == "pointerleave") {
if(!outLeaveEventsFail) {
test(function() {
assert_true(f_lostPointerCapture, "pointerout/leave should not be received unless the target just lost the capture.");
}, expectedPointerType + " pointerout/leave should not be received unless the target just lost the capture.");
outLeaveEventsFail = true;
}
}
else if (event.pointerId == captured_event.pointerId) {
if (f_gotPointerCapture && event.type == "pointermove") {
// on first event received for capture, release capture
listener.releasePointerCapture(event.pointerId);
}
else {
// if any other events are received after releaseCapture, then the test fails
test(function () {
assert_unreached(event.target.id + "-" + event.type + " should be handled by target element handler");
}, expectedPointerType + " No other events should be recieved by capturing node after release");
}
}
}
function targetEventHandler(event) {
if (test_done)
return;
if (f_gotPointerCapture) {
if(event.type != "pointerout" && event.type != "pointerleave") {
test(function () {
assert_unreached("The Target element should not have received any events while capture is active. Event recieved:" + event.type + ". ");
}, expectedPointerType + " The target element should not receive any events while capture is active");
}
}
if (event.type == "pointerdown") {
// pointerdown event received will be used to capture events.
listener.setPointerCapture(event.pointerId);
captured_event = event;
}
if (f_lostPointerCapture) {
test_pointerEvent.step(function () {
assert_equals(event.pointerId, captured_event.pointerId, "pointerID is same for event captured and after release");
});
if (event.type == "pointerup") {
test_done = true;
test_pointerEvent.done(); // complete test
}
}
}
function run() {
test_pointerEvent = setup_pointerevent_test("got/lost pointercapture: subsequent events to target", ALL_POINTERS); // set up test harness
var listener = document.getElementById("listener");
var target0 = document.getElementById("target0");
target0.style.touchAction = "none";
// target0 and listener - handle all events
for (var i = 0; i < All_Pointer_Events.length; i++) {
on_event(target0, All_Pointer_Events[i], targetEventHandler);
on_event(listener, All_Pointer_Events[i], listenerEventHandler);
}
}
</script>
</head>
<body onload="run()">
<h2 id="pointerTypeDescription"></h2>
<div id="listener"></div>
<h1>Pointer Event: releasePointerCapture() - subsequent events follow normal hitting testing mechanisms</h1>
<h4>
Test Description:
Use your pointer and press down in the black box. Then move around in the box and release your pointer.
After invoking the releasePointerCapture method on an element, subsequent events for the specified
pointer must follow normal hit testing mechanisms for determining the event target.
</h4>
<br />
<div id="target0">
</div>
<div id="complete-notice">
<p>Test complete: Scroll to Summary to view Pass/Fail Results.</p>
<p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
<p>Refresh the page to run the tests again with a different pointer type.</p>
</div>
<div id="log"></div>
</body>
</html>

View File

@@ -6093,6 +6093,8 @@ void PresShell::ProcessSynthMouseMoveEvent(bool aFromScroll) {
event.mRefPoint =
LayoutDeviceIntPoint::FromAppUnitsToNearest(refpoint, viewAPD);
event.mButtons = PresShell::sMouseButtons;
event.mInputSource = mMouseLocationInputSource;
event.pointerId = mMouseLocationPointerId;
// XXX set event.mModifiers ?
// XXX mnakano I think that we should get the latest information from widget.
@@ -6997,10 +6999,6 @@ void PresShell::RecordPointerLocation(WidgetGUIEvent* aEvent) {
switch (aEvent->mMessage) {
case eMouseMove:
if (!aEvent->AsMouseEvent()->IsReal()) {
break;
}
[[fallthrough]];
case eMouseEnterIntoWidget:
case eMouseDown:
case eMouseUp:
@@ -7008,12 +7006,18 @@ void PresShell::RecordPointerLocation(WidgetGUIEvent* aEvent) {
case eDragStart:
case eDragOver:
case eDrop: {
mMouseLocation = GetEventLocation(*aEvent->AsMouseEvent());
WidgetMouseEvent* const mouseEvent = aEvent->AsMouseEvent();
if (mouseEvent->mMessage == eMouseMove && mouseEvent->IsSynthesized()) {
break;
}
mMouseLocation = GetEventLocation(*mouseEvent);
mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
// FIXME: Don't trust the synthesized for tests flag of drag events.
if (aEvent->mClass != eDragEventClass) {
mMouseLocationInputSource = mouseEvent->mInputSource;
mMouseLocationPointerId = mouseEvent->pointerId;
mMouseLocationWasSetBySynthesizedMouseEventForTests =
aEvent->mFlags.mIsSynthesizedForTests;
mouseEvent->mFlags.mIsSynthesizedForTests;
}
#ifdef DEBUG
if (MOZ_LOG_TEST(gLogMouseLocation, LogLevel::Info)) {
@@ -7059,6 +7063,8 @@ void PresShell::RecordPointerLocation(WidgetGUIEvent* aEvent) {
// into another.
mMouseLocation = nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
mMouseLocationInputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
mMouseLocationPointerId = 0;
mMouseLocationWasSetBySynthesizedMouseEventForTests =
aEvent->mFlags.mIsSynthesizedForTests;
#ifdef DEBUG

View File

@@ -3297,6 +3297,12 @@ class PresShell final : public nsStubDocumentObserver,
// needed, one hopes, but it is for now.
uint16_t mChangeNestCount;
// This is the input source which set mMouseLocation.
uint16_t mMouseLocationInputSource = 0; // MOZ_SOURCE_UNKNOWN by default
// This is the pointerId which set mMouseLocation.
uint32_t mMouseLocationPointerId = 0;
// Flags controlling how our document is rendered. These persist
// between paints and so are tied with retained layer pixels.
// PresShell flushes retained layers when the rendering state

View File

@@ -97,6 +97,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1162990
function executeTest()
{
synthesizeMouse(document.querySelector("a"), 0, 0, {type : "mousemove"});
logger("executeTest");
setEventHandlers();
var rectCd = child.getBoundingClientRect();

View File

@@ -97,6 +97,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1162990
function executeTest()
{
synthesizeMouse(document.querySelector("a"), 0, 0, {type : "mousemove"});
logger("executeTest");
setEventHandlers();
var rectTg = target.getBoundingClientRect();

View File

@@ -2811,6 +2811,13 @@
value: false
mirror: always
# Whether pointer boundary events should be dispatched after the element is
# changed by a layout change or something without pointer move.
- name: dom.event.pointer.boundary.dispatch_when_layout_change
type: bool
value: true
mirror: always
# Whether the result of screenX, screenY, clientX, clientY, offsetX, offsetY,
# x and y of PointerEvent may be fractional values (except `click`, `auxclick`
# and `contextmenu`)

View File

@@ -1,4 +1,2 @@
[pointerevent_pointerout_no_pointer_movement.html]
expected: TIMEOUT
[Layout change under a stationary pointer fires boundary events and no pointermove event]
expected: TIMEOUT
prefs: [layout.reflow.synthMouseMove:true]

View File

@@ -79,10 +79,12 @@ addEventListener("load", () => {
// initialization, but it's out of scope of this bug. Therefore, we
// compare only events after `click`.
const expectedEvents = [ // no events should be fired on the child due to disconnected
{ type: "pointerleave", target: div2}, // no pointerout because of first pointer move after the mutation
{ type: "pointerleave", target: div1},
{ type: "pointerover", target: document.body},
{ type: "pointermove", target: document.body},
{ type: "pointerover", target: div2 }, // no pointerenter because it should've already fired before the `click`
{ type: "pointerout", target: div2 },
{ type: "pointerleave", target: div2 },
{ type: "pointerleave", target: div1 },
{ type: "pointerover", target: document.body },
{ type: "pointermove", target: document.body },
];
assert_equals(
stringifyEvents(eventsAfterClick(events)),
@@ -131,7 +133,9 @@ addEventListener("load", () => {
// initialization, but it's out of scope of this bug. Therefore, we
// compare only events after `click`.
const expectedEvents = [ // no events should be fired on rootElementInShadow due to disconnected
{ type: "pointerleave", target: host}, // no pointerout because of first pointer move after the mutation
{ type: "pointerover", target: host }, // no pointerenter because it should've already fired before the click
{ type: "pointerout", target: host },
{ type: "pointerleave", target: host},
{ type: "pointerleave", target: hostContainer},
{ type: "pointerover", target: document.body},
{ type: "pointermove", target: document.body},

View File

@@ -24,6 +24,10 @@
z-index: 1000;
display: none;
}
#log {
display: none; /* Required to run this test alone */
}
</style>
<div id="target"></div>
<div id="overlay"></div>
@@ -44,19 +48,23 @@
promise_test(async t => {
await test_driver.click(target);
num_pointermoves = 0;
let pointerout_promise = getEvent("pointerout", target);
overlay.style.display = "block";
await pointerout_promise;
assert_equals(num_pointermoves, 0, "no pointermove events are expected");
}, "Layout change under a stationary pointer (a new element appears) fires boundary events and no pointermove event");
promise_test(async t => {
await test_driver.click(overlay);
num_pointermoves = 0;
let pointerover_promise = getEvent("pointerover", target);
overlay.style.display = "none";
await waitForAnimationFrames(2);
// Why does Chrome time out at the next line w/o the redundant wait above?
// Does chrome require something to request a main frame in order to detect
// the new pointer target after the style update to generate the event?
// https://crbug.com/413595088
await pointerover_promise;
assert_equals(num_pointermoves, 0, "no pointermove events are expected");
}, "Layout change under a stationary pointer fires boundary events and no pointermove event");
document.querySelector("style").remove(); // Show the test log if running alone.
}, "Layout change under a stationary pointer (the element disappears) fires boundary events and no pointermove event");
</script>