Bug 1863194 - Do not terminate wheel transaction on browsing context change. r=hiro,smaug,webdriver-reviewers,Sasha

We should continue to use a wheel transaction for wheel events when the
browsing context changes from the current context. Avoiding the override
of the event target with the current wheel transaction can halt a page
scroll when the mouse moves over content in a different presentation
context.

Differential Revision: https://phabricator.services.mozilla.com/D205495
This commit is contained in:
Dan Robertson 2024-04-25 12:16:40 +00:00
parent eb8068f09a
commit 455ab11833
5 changed files with 190 additions and 7 deletions

View File

@ -0,0 +1,146 @@
<!DOCTYPE html>
<html>
<title>A scroll over an iframe should not terminate the wheel transaction</title>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script src="/tests/SimpleTest/paint_listener.js"></script>
<head>
<style>
body {
height: 250vh;
width: 100%;
margin: 0;
padding: 0;
}
#spacer {
height: 50px;
width: 100vw;
background: yellow;
}
#subframe {
width: 80vw;
height: 60vh;
}
</style>
</head>
<body>
<div id="spacer"></div>
<iframe id="subframe">
</iframe>
</body>
<script>
const searchParams = new URLSearchParams(location.search);
async function scrollWithPan() {
await NativePanHandler.promiseNativePanEvent(
document.scrollingElement,
50,
30,
0,
NativePanHandler.delta,
NativePanHandler.beginPhase,
);
await NativePanHandler.promiseNativePanEvent(
document.scrollingElement,
50,
30,
0,
NativePanHandler.delta,
NativePanHandler.updatePhase,
);
await NativePanHandler.promiseNativePanEvent(
document.scrollingElement,
50,
30,
0,
NativePanHandler.delta,
NativePanHandler.endPhase,
);
}
async function scrollWithWheel() {
await promiseMoveMouseAndScrollWheelOver(document.scrollingElement, 50, 30,
false, 100);
}
async function test() {
let iframeURL =
SimpleTest.getTestFileURL("helper_scroll_over_subframe_child.html");
switch (searchParams.get("oop")) {
case "true":
iframeURL = iframeURL.replace(window.location.origin, "https://example.com/");
break;
default:
break;
}
const iframeLoadPromise = promiseOneEvent(subframe, "load", null);
subframe.src = iframeURL;
await iframeLoadPromise;
await SpecialPowers.spawn(subframe, [], async () => {
await content.wrappedJSObject.waitUntilApzStable();
await SpecialPowers.contentTransformsReceived(content);
});
let childWindowReceivedWheelEvent = false;
window.addEventListener("message", e => {
if (e.data == "child-received-wheel-event") {
childWindowReceivedWheelEvent = true;
}
});
await SpecialPowers.spawn(subframe, [], () => {
let target = content.document.getElementById("target")
target.style.backgroundColor = "green";
content.getComputedStyle(target).backgroundColor;
target.addEventListener("wheel", () => {
target.style.backgroundColor = "red";
content.getComputedStyle(target).backgroundColor;
});
return new Promise(resolve => resolve());
});
await promiseFrame();
let transformEndPromise = promiseTransformEnd();
// Scroll over the iframe
switch (searchParams.get("scroll")) {
case "wheel":
await scrollWithWheel();
break;
case "pan":
await scrollWithPan();
break;
default:
ok(false, "Unsupported scroll value: " + searchParams.get("scroll"));
break;
}
await transformEndPromise;
// Wait an extra frame to ensure any message from the child has
// extra time to be sent to the parent.
await promiseFrame();
let res = await SpecialPowers.spawn(subframe, [], () => {
let target = content.document.getElementById("target")
return target.style.backgroundColor;
});
await promiseFrame();
// We should not have fired a wheel event to the element in the iframe
ok(!childWindowReceivedWheelEvent, "Child window should not receive wheel events");
is(res, "green", "OOP iframe does not halt user scroll of parent");
}
waitUntilApzStable().then(test).then(subtestDone, subtestFailed);
</script>
</html>

View File

@ -0,0 +1,29 @@
<!DOCTYPE HTML>
<html>
<head>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/paint_listener.js"></script>
<script src="apz_test_utils.js"></script>
<style>
body {
margin: 0px;
}
#target {
width: 100%;
height: 100vh;
background: blue;
overflow: scroll;
}
</style>
</head>
<body>
<div id="target">
</div>
</body>
<script>
window.addEventListener("wheel", () => {
window.parent.postMessage("child-received-wheel-event", "*");
});
</script>
</html>

View File

@ -20,6 +20,13 @@ var prefs = [
["mousewheel.transaction.timeout", 0],
];
var wheel_transaction_prefs = [
["dom.event.wheel-event-groups.enabled", true],
["mousewheel.transaction.timeout", 10000],
["apz.test.mac.synth_wheel_input", true],
...getSmoothScrollPrefs("wheel"),
];
// For helper_scroll_over_scrollbar, we need to set a pref to force
// layerization of the scrollbar track to reproduce the bug being fixed.
// Otherwise, the bug only manifests with overlay scrollbars on macOS,
@ -48,6 +55,10 @@ var subtests = [
prefs: [["general.smoothScroll", false],
["apz.test.mac.synth_wheel_input", true]]},
{"file": "helper_scroll_anchoring_on_wheel.html", prefs: smoothness_prefs},
{"file": "helper_scroll_over_subframe.html?scroll=wheel", prefs: wheel_transaction_prefs},
{"file": "helper_scroll_over_subframe.html?oop=true&scroll=wheel", prefs: wheel_transaction_prefs},
{"file": "helper_scroll_over_subframe.html?scroll=pan", prefs: wheel_transaction_prefs},
{"file": "helper_scroll_over_subframe.html?oop=true&scroll=pan", prefs: wheel_transaction_prefs},
];
subtests.push(...buildRelativeScrollSmoothnessVariants("wheel", ["scrollBy", "scrollTo", "scrollTop"]));

View File

@ -12100,13 +12100,6 @@ void PresShell::EventHandler::EventTargetData::UpdateWheelEventTarget(
return;
}
// If the browsing context is no longer the same as the context of the
// current wheel transaction, do not override the event target.
if (!groupFrame->PresContext() || !groupFrame->PresShell() ||
groupFrame->PresContext() != GetPresContext()) {
return;
}
// If dom.event.wheel-event-groups.enabled is set and whe have a stored
// event target from the wheel transaction, override the event target.
SetFrameAndComputePresShellAndContent(groupFrame, aGUIEvent);

View File

@ -34,6 +34,10 @@ class InputModule extends WindowGlobalBiDiModule {
const actionChain = lazy.action.Chain.fromJSON(this.#actionState, actions);
await actionChain.dispatch(this.#actionState, this.messageHandler.window);
// Terminate the current wheel transaction if there is one. Wheel
// transactions should not live longer than a single action chain.
ChromeUtils.endWheelTransaction();
}
async releaseActions() {