-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make
PostEventHandler::CheckPointerCaptureState
synthesize `ePointe…
…rMove` and `eMouseMove` if nobody captures the pointer anymore When an element starts capturing a pointer, pointer/mouse boundary events are dispatched by `EventStateManager::PreHandleEvent` [1]. However, when the capturing element loses the capture, they are not dispatched. When the pointer capture is implicitly released, the pointer may be over another document. Therefore, this patch synthesizes an internal `ePointerMove` and `eMouseMove` on the widget to make `PresShell::HandleEvent` redirects the event to proper document under the pointer. Unfortunately, I add 2 manual tests into WPT. The reason is, a drag operation across document boundary with test driver does not work even if I specify the pointer position within the parent document coordinates. This is same both on Firefox and Chrome. Additionally, writing the new tests as a mochitest won't work too. If I use synthesized mouse events, I see similar failure. Additionally, when I use native events, it works, but unstable to run on CI. 1. https://searchfox.org/mozilla-central/rev/669fac9888b173c02baa4c036e980c0c204dfe02/dom/events/EventStateManager.cpp#1139-1140 Differential Revision: https://phabricator.services.mozilla.com/D218896 bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1793267 gecko-commit: f3d7c6dd48d53773bf29d8fe448cda233b955b68 gecko-reviewers: smaug
- Loading branch information
1 parent
2eb5c22
commit f9d5548
Showing
2 changed files
with
255 additions
and
0 deletions.
There are no files selected for viewing
127 changes: 127 additions & 0 deletions
127
...nts/pointerevent_capture_implicit_release_on_parent_doc_while_subdoc_captures-manual.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"> | ||
<title>Pointer Events when mouse button up on the parent document while an element in a child document captures the pointer</title> | ||
<script src="/resources/testharness.js"></script> | ||
<script src="/resources/testharnessreport.js"></script> | ||
<script> | ||
"use strict"; | ||
|
||
setup({explicit_timeout: true}); | ||
|
||
addEventListener("load", () => { | ||
promise_test(async t => { | ||
const iframe = document.querySelector("iframe"); | ||
const button = iframe.contentDocument.querySelector("button"); | ||
const div = iframe.contentDocument.querySelector("div"); | ||
const divInParent = document.querySelector("iframe + div"); | ||
|
||
let pointerEvents = []; | ||
let mouseEvents = []; | ||
function recordPointerEvent(event) { | ||
pointerEvents.push(event); | ||
} | ||
function recordMouseEvent(event) { | ||
mouseEvents.push(event); | ||
} | ||
|
||
function stringifyEvent(event) { | ||
function stringifyTarget(target) { | ||
switch (target) { | ||
case button: | ||
return "<button> in child"; | ||
case div: | ||
return "<div> in child which captured the pointer"; | ||
case divInParent: | ||
return "<div> in parent"; | ||
default: | ||
return target?.nodeName; | ||
} | ||
} | ||
return `"${event.type}" on ${stringifyTarget(event.target)}`; | ||
} | ||
|
||
const pointerEventTypes = ["pointerup", "lostpointercapture", "pointerover", "pointerout", "pointerenter", "pointerleave", "pointermove"]; | ||
const mouseEventTypes = ["mouseup", "mouseover", "mouseout", "mouseenter", "mouseleave", "mousemove"]; | ||
const promisePointerUp = new Promise(resolve => { | ||
button.addEventListener("pointerdown", event => { | ||
div.setPointerCapture(event.pointerId); | ||
iframe.contentWindow.addEventListener("pointerup", event => { | ||
recordPointerEvent(event); | ||
[button, div, divInParent].forEach(target => { | ||
pointerEventTypes.forEach(type => { | ||
target.addEventListener(type, recordPointerEvent); | ||
}); | ||
mouseEventTypes.forEach(type => { | ||
target.addEventListener(type, recordMouseEvent); | ||
}); | ||
}); | ||
resolve(); | ||
}, {once: true}); | ||
}, {once: true}); | ||
}); | ||
|
||
await promisePointerUp; | ||
await new Promise( | ||
resolve => requestAnimationFrame( | ||
() => requestAnimationFrame(resolve) | ||
) | ||
); | ||
|
||
const stringifiedPointerEvents = []; | ||
const stringifiedMouseEvents = []; | ||
for (const event of pointerEvents) { | ||
stringifiedPointerEvents.push(stringifyEvent(event)); | ||
} | ||
for (const event of mouseEvents) { | ||
stringifiedMouseEvents.push(stringifyEvent(event)); | ||
} | ||
const stringifiedExpectedPointerEvents = [ | ||
stringifyEvent({ type: "pointerup", target: div }), | ||
stringifyEvent({ type: "lostpointercapture", target: div }), | ||
stringifyEvent({ type: "pointerout", target: div }), | ||
stringifyEvent({ type: "pointerleave", target: div }), | ||
stringifyEvent({ type: "pointerover", target: divInParent }), | ||
stringifyEvent({ type: "pointerenter", target: divInParent }), | ||
]; | ||
if (pointerEvents[pointerEvents.length - 1]?.type == "pointermove") { | ||
stringifiedExpectedPointerEvents.push( | ||
stringifyEvent({ type: "pointermove", target: divInParent }) | ||
); | ||
} | ||
const stringifiedExpectedMouseEvents = [ | ||
stringifyEvent({ type: "mouseup", target: div }), | ||
stringifyEvent({ type: "mouseout", target: div }), | ||
stringifyEvent({ type: "mouseleave", target: div }), | ||
stringifyEvent({ type: "mouseover", target: divInParent }), | ||
stringifyEvent({ type: "mouseenter", target: divInParent }), | ||
]; | ||
if (mouseEvents[mouseEvents.length - 1]?.type == "mousemove") { | ||
stringifiedExpectedMouseEvents.push( | ||
stringifyEvent({ type: "mousemove", target: divInParent }) | ||
); | ||
} | ||
|
||
t.step(() => { | ||
assert_array_equals(stringifiedPointerEvents, stringifiedExpectedPointerEvents) | ||
assert_array_equals(stringifiedMouseEvents, stringifiedExpectedMouseEvents) | ||
}); | ||
t.done(); | ||
}, "boundary events should be fired for notifying web apps of over the element in parent document"); | ||
}, {once: true}); | ||
</script> | ||
</head> | ||
<body> | ||
<div> | ||
<p>Test steps:</p> | ||
<ol> | ||
<li>Press the button with primary button of your mouse and start dragging</li> | ||
<li>Move the mouse cursor over the red border box and release the mouse button</li> | ||
</ol> | ||
</div> | ||
<iframe srcdoc="<button>Start dragging from this button</button><div><br></div>"></iframe> | ||
<div style='border: 1px solid red'>And release mouse button over this box</div> | ||
</body> | ||
</html> |
128 changes: 128 additions & 0 deletions
128
...nts/pointerevent_capture_implicit_release_on_subdoc_while_parent_doc_captures-manual.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"> | ||
<title>Pointer Events when mouse button up on a sub-document while an element in parent document captures the pointer</title> | ||
<script src="/resources/testharness.js"></script> | ||
<script src="/resources/testharnessreport.js"></script> | ||
<script> | ||
"use strict"; | ||
|
||
setup({explicit_timeout: true}); | ||
|
||
addEventListener("load", () => { | ||
promise_test(async t => { | ||
const button = document.querySelector("button"); | ||
const div = document.querySelector("button + div"); | ||
const iframe = document.querySelector("iframe"); | ||
const divInChild = iframe.contentDocument.querySelector("div"); | ||
|
||
let pointerEvents = []; | ||
let mouseEvents = []; | ||
function recordPointerEvent(event) { | ||
pointerEvents.push(event); | ||
} | ||
function recordMouseEvent(event) { | ||
mouseEvents.push(event); | ||
} | ||
|
||
function stringifyEvent(event) { | ||
function stringifyTarget(target) { | ||
switch (target) { | ||
case button: | ||
return "<button>"; | ||
case div: | ||
return "<div> in parent which captured the pointer"; | ||
case divInChild: | ||
return "<div> in child"; | ||
default: | ||
return target?.nodeName; | ||
} | ||
} | ||
return `"${event.type}" on ${stringifyTarget(event.target)}`; | ||
} | ||
|
||
const pointerEventTypes = ["pointerup", "lostpointercapture", "pointerover", "pointerout", "pointerenter", "pointerleave", "pointermove"]; | ||
const mouseEventTypes = ["mouseup", "mouseover", "mouseout", "mouseenter", "mouseleave", "mousemove"]; | ||
const promisePointerUp = new Promise(resolve => { | ||
button.addEventListener("pointerdown", event => { | ||
div.setPointerCapture(event.pointerId); | ||
addEventListener("pointerup", event => { | ||
recordPointerEvent(event); | ||
[button, div, divInChild].forEach(target => { | ||
pointerEventTypes.forEach(type => { | ||
target.addEventListener(type, recordPointerEvent); | ||
}); | ||
mouseEventTypes.forEach(type => { | ||
target.addEventListener(type, recordMouseEvent); | ||
}); | ||
}); | ||
resolve(); | ||
}, {once: true}); | ||
}, {once: true}); | ||
}); | ||
|
||
await promisePointerUp; | ||
await new Promise( | ||
resolve => requestAnimationFrame( | ||
() => requestAnimationFrame(resolve) | ||
) | ||
); | ||
|
||
const stringifiedPointerEvents = []; | ||
const stringifiedMouseEvents = []; | ||
for (const event of pointerEvents) { | ||
stringifiedPointerEvents.push(stringifyEvent(event)); | ||
} | ||
for (const event of mouseEvents) { | ||
stringifiedMouseEvents.push(stringifyEvent(event)); | ||
} | ||
const stringifiedExpectedPointerEvents = [ | ||
stringifyEvent({ type: "pointerup", target: div }), | ||
stringifyEvent({ type: "lostpointercapture", target: div }), | ||
stringifyEvent({ type: "pointerout", target: div }), | ||
stringifyEvent({ type: "pointerleave", target: div }), | ||
stringifyEvent({ type: "pointerover", target: divInChild }), | ||
stringifyEvent({ type: "pointerenter", target: divInChild }), | ||
]; | ||
if (pointerEvents[pointerEvents.length - 1]?.type == "pointermove") { | ||
stringifiedExpectedPointerEvents.push( | ||
stringifyEvent({ type: "pointermove", target: divInChild }) | ||
); | ||
} | ||
const stringifiedExpectedMouseEvents = [ | ||
stringifyEvent({ type: "mouseup", target: div }), | ||
stringifyEvent({ type: "mouseout", target: div }), | ||
stringifyEvent({ type: "mouseleave", target: div }), | ||
stringifyEvent({ type: "mouseover", target: divInChild }), | ||
stringifyEvent({ type: "mouseenter", target: divInChild }), | ||
]; | ||
if (mouseEvents[mouseEvents.length - 1]?.type == "mousemove") { | ||
stringifiedExpectedMouseEvents.push( | ||
stringifyEvent({ type: "mousemove", target: divInChild }) | ||
); | ||
} | ||
|
||
t.step(() => { | ||
assert_array_equals(stringifiedPointerEvents, stringifiedExpectedPointerEvents) | ||
assert_array_equals(stringifiedMouseEvents, stringifiedExpectedMouseEvents) | ||
}); | ||
t.done(); | ||
}, "boundary events should be fired for notifying web apps of over the element in child document"); | ||
}, {once: true}); | ||
</script> | ||
</head> | ||
<body> | ||
<div> | ||
<p>Test steps:</p> | ||
<ol> | ||
<li>Press the button with primary button of your mouse and start dragging</li> | ||
<li>Move the mouse cursor over the red border box and release the mouse button</li> | ||
</ol> | ||
</div> | ||
<button>Start dragging from this button</button> | ||
<div><br></div> | ||
<iframe srcdoc="<div style='border: 1px solid red'>And release mouse button over this box</div>"></iframe> | ||
</body> | ||
</html> |