Files
sousa-gecko/layout/base/tests/file_synthmousemove_after_navigation.html
T
Stephen A Pohl 5c406a7341 Bug 2038491: Claim orphaned last-mouse state in PresShell::SynthesizeMouseMove so cursor/:hover restyles take effect after a top-level navigation without a fresh mouse move. r=masayuki
After a top-level navigation, the new PresShell has no per-PresShell mouse state until a real mouse event arrives. PresShell::SynthesizeMouseMove bails on that empty state, so a JS-driven cursor or :hover change (e.g. `body.style.cursor = 'none'` triggered by a keypress) does not take effect until the user moves the mouse.

PointerEventHandler still holds the static last-mouse state from before the navigation. When the previous owning PresShell has been torn down, claim that state for the new PresShell. The existing path in ProcessSynthMouseMoveEvent then dispatches a synthetic eMouseMove at the cached position.

Gate the claim with three checks. Skip if another live PresShell still owns the state (e.g. a popup). Skip unless the widget that originally recorded the state matches aRootPresShell's widget, since PresShell::IsRoot() means root of the in-process presContext tree and the previous owner could have been on a different top-level window in the same content process. And use a new sLastMousePointerId static for the recovered pointerId, since sLastPointerId tracks any pointer (including touch) and would mismatch the eMouseMove dispatched downstream.

Differential Revision: https://phabricator.services.mozilla.com/D301688
2026-05-28 05:18:31 +00:00

67 lines
2.3 KiB
HTML

<!doctype html>
<style>
html, body { margin: 0; padding: 0; height: 100%; }
body { background: white; }
body:hover { background: red; }
</style>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script>
async function runTest() {
const isReloaded = sessionStorage.getItem("bug2038491_reloaded") === "true";
if (!isReloaded) {
// First load: synthesize a real mouse move at a known position so the
// root PresShell records the mouse state, then reload to drop that
// PresShell.
synthesizeMouse(document.body, 100, 100, { type: "mousemove" });
// Wait one paint tick so the synth move propagates and :hover is applied.
await new Promise(r => requestAnimationFrame(r));
await new Promise(r => requestAnimationFrame(r));
const bg = getComputedStyle(document.body).backgroundColor;
if (bg !== "rgb(255, 0, 0)") {
opener.reportResult(
false,
`Setup: body should be red (hovered) after initial mouse move, got ${bg}`
);
window.close();
return;
}
sessionStorage.setItem("bug2038491_reloaded", "true");
location.reload();
return;
}
// Second load: the new root PresShell has no per-PresShell mouse state.
// PointerEventHandler::sLastMouseInfo still holds the cached position from
// before the reload, with a dead weak ref to the old PresShell. The fix
// for bug 2038491 lets PresShell::SynthesizeMouseMove claim that orphaned
// state. PresShell::UnsuppressAndInvalidate calls SynthesizeMouseMove
// when painting unsuppresses; with the fix in place a synthetic eMouseMove
// dispatches and re-evaluates :hover on the new document.
// Give the refresh driver several ticks to dispatch the synthetic mouse
// move.
for (let i = 0; i < 10; i++) {
await new Promise(r => requestAnimationFrame(r));
}
const bg = getComputedStyle(document.body).backgroundColor;
const passed = bg === "rgb(255, 0, 0)";
sessionStorage.removeItem("bug2038491_reloaded");
opener.reportResult(
passed,
passed
? "body is red (hovered) after navigation without a fresh mouse move"
: `body should be red (hovered) after navigation, got ${bg}`
);
window.close();
}
SimpleTest.waitForFocus(runTest);
</script>
<body></body>