commit 04285ccad74dc0766778495a5c6a486b2a1887ca
parent 457417d3eb61669e6495f6fc09a00d76fdb5a1ed
Author: Hiroyuki Ikezoe <hikezoe.birchill@mozilla.com>
Date: Tue, 4 Nov 2025 04:43:42 +0000
Bug 1989868 - Cancel smooth scroll animation triggered by JS on touch move event. r=botond
And then restart a new touch event series to keep touch scrolling works.
Differential Revision: https://phabricator.services.mozilla.com/D269904
Diffstat:
4 files changed, 71 insertions(+), 0 deletions(-)
diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1446,6 +1446,14 @@ nsEventStatus AsyncPanZoomController::OnTouchMove(
const MultiTouchInput& aEvent) {
APZC_LOG_DETAIL("got a touch-move in state %s\n", this,
ToString(mState).c_str());
+
+ if (InScrollAnimationTriggeredByScript()) {
+ // Cancel smooth animation triggered by script.
+ CancelAnimation();
+ // Restart the touch event series.
+ return OnTouchStart(aEvent);
+ }
+
switch (mState) {
case FLING:
case SMOOTH_SCROLL:
@@ -6726,6 +6734,16 @@ bool AsyncPanZoomController::InScrollAnimation(
return smoothScroll && smoothScroll->Kind() == aKind;
}
+bool AsyncPanZoomController::InScrollAnimationTriggeredByScript() const {
+ RecursiveMutexAutoLock lock(mRecursiveMutex);
+ if (!mAnimation) {
+ return false;
+ }
+ RefPtr<SmoothScrollAnimation> smoothScroll =
+ mAnimation->AsSmoothScrollAnimation();
+ return smoothScroll && smoothScroll->WasTriggeredByScript();
+}
+
void AsyncPanZoomController::UpdateZoomConstraints(
const ZoomConstraints& aConstraints) {
if ((MOZ_LOG_TEST(sApzCtlLog, LogLevel::Debug) &&
diff --git a/gfx/layers/apz/src/AsyncPanZoomController.h b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -1500,6 +1500,12 @@ class AsyncPanZoomController {
bool InScrollAnimation(ScrollAnimationKind aKind) const;
/**
+ * Check whether there is an ongoing smooth scroll animation triggered by
+ * script.
+ */
+ bool InScrollAnimationTriggeredByScript() const;
+
+ /**
* Returns whether the specified PanZoomState does not need to be reset when
* a scroll offset update is processed.
*/
diff --git a/gfx/layers/apz/test/mochitest/helper_bug1989868.html b/gfx/layers/apz/test/mochitest/helper_bug1989868.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<meta name="viewport" content="width=device-width; initial-scale=1.0">
+<title>Test that touch scrolling keeps working with a smooth scroll operation by JS</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<script src="apz_test_utils.js"></script>
+<script src="apz_test_native_event_utils.js"></script>
+<style>
+body {
+ margin: 0px;
+ padding: 0px;
+}
+</style>
+<div style="width: 100vw; height: 300vh;"></div>
+<script>
+async function test() {
+ let scrollYInEventHandler;
+ window.addEventListener("scroll", () => {
+ scrollYInEventHandler = window.scrollY;
+ // Trigger a smooth scroll.
+ window.scrollTo({top: 0, behavior: "smooth"});
+ }, { once: true });
+
+ // Send touch events to scroll down the content.
+ // Note that each touch event needs to be sent in each frame to
+ // give a chance to receive a scroll event while sending the evets.
+ for (let y = 200; y > 0; y -= 10) {
+ await synthesizeNativeTouch(window, 100, y, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT);
+ await promiseFrame();
+ }
+
+ ok(scrollYInEventHandler * 2 <= window.scrollY,
+ `The last scroll position ${window.scrollY} should be greater than the
+ double of the position in scroll event handler ${scrollYInEventHandler}`);
+
+ await synthesizeNativeTouch(window, 100, 0, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE);
+}
+
+waitUntilApzStable()
+.then(test)
+.then(subtestDone, subtestFailed);
+</script>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_group_touchevents-6.html b/gfx/layers/apz/test/mochitest/test_group_touchevents-6.html
@@ -53,6 +53,8 @@ var subtests = [
["ui.click_hold_context_menus.delay", 1000000],
["test.events.async.enabled", true]
]},
+ {"file": "helper_bug1989868.html",
+ "prefs": getPrefs("TOUCH_EVENTS:PAN")},
// Add new subtests here. If this starts timing out because it's taking too
// long, create a test_group_touchevents-7.html file. Refer to 1423011#c57