Closed Bug 2054442 Opened 1 month ago Closed 12 days ago

`moveOverTime` timer delays can cause intermediate events to be skipped

Categories

(Remote Protocol :: Agent, defect, P3)

defect
Points:
3

Tracking

(firefox156 fixed)

RESOLVED FIXED
156 Branch
Tracking Status
firefox156 --- fixed

People

(Reporter: whimboo, Assigned: whimboo)

References

(Blocks 1 open bug)

Details

(Whiteboard: [webdriver:m21][wptsync upstream])

Attachments

(3 files)

The moveOverTime function in Actions.sys.mjs uses an nsITimer (TYPE_ONE_SHOT) with a 17ms delay to slow-down incremental actions transitions. Before the first intermediate transition, it waits for this timer to fire. However, the timer callback is delivered as a Normal priority (4) runnable and as such gets starved by higher-priority work on the parent process main thread (Control priority IPC messages, Vsync priority RefreshDriver ticks), causing delays of ~140ms instead of the expected 17ms.

This was observed in CI mainly on macOS aarch64 workers where a wheel scroll action with duration=100 sometimes only dispatches a single wheel event instead of multiple, because the initial timer delay exceeds already the specified duration. For more details see bug 1852529 comment 7.

From a Gecko Profile (https://share.firefox.dev/4hacYcF):

  • Timer initialized at ~t=22ms with a 17ms delay
  • Parent process main thread is not CPU-bound between t=22ms and t=160ms but it's processing vsync ticks and IPC
  • Timer callback fires at ~t=160ms (139ms late)
  • The actual wheel event synthesis + dispatch takes only ~10ms after the timer fires

As per the WebDriver specification (here the case for wheel and scroll) the endpoint should do the following before any dispatching of events:

If duration is greater than 0 and inside any implementation-defined bounds, asynchronously wait for an implementation defined amount of time to pass.

This wait allows the implementation to model the overall wheel scroll as a series of small scroll occurring at an implementation defined rate (e.g. one scroll per vsync).

Later on in Perform a scroll there is the same logic for delaying individual scroll events repeated.

Why do we have the initial wait before dispatching the events? Is that required at all? If not lets remove it and maybe we can live with the lower priority of timers for the rest of the events? We may then still not dispatch the right amount of events, but at least we have 2 of them and not just a single one.

As discussed the initial delay was most likely expected because at time zero no event should be fired. So we should leave it in.

Instead lets check with requestAnimationFrame if that makes a difference and how its priority is actually set.

Severity: -- → S3
Priority: -- → P3
Whiteboard: [webdriver:backlog]

Together with the new test from bug 1852529 I pushed a new try build by using requestAnimationFrame instead of the timer:
https://treeherder.mozilla.org/jobs?repo=try&landoInstance=lando-prod-2025&landoCommitID=68585

(In reply to Henrik Skupin [:whimboo][⌚️UTC+2] (away July 24th - August 16th) from comment #2)

Together with the new test from bug 1852529 I pushed a new try build by using requestAnimationFrame instead of the timer:
https://treeherder.mozilla.org/jobs?repo=try&landoInstance=lando-prod-2025&landoCommitID=68585

The results look great and we as well dispatch the events at a very constant interval of 17ms:
https://treeherder.mozilla.org/logviewer?job_id=579192426&repo=try&task=EVkNuhl8T9q-hmSUNndU7A.0&lineNumber=13191

Now I only wonder what would happen when the renderer is blocked (see bug 2045643). Could we actually rely on requestAnimationFrame given that normal timers are that unreliably due to downgraded priority, or would it cause issues when the renderer is blocked? Maybe we could fallback to the timer in such a case? Is there a way to detect if the renderer is blocked?

When we could go ahead with requestAnimationFrame I would add the test from bug 1852529 and as well need to extend it to check the final delta values as well. Further we should add the same test for pointer actions.

Flags: needinfo?(emilio)

If rendering is blocked, wheel and scroll events also don't fire. So it's unclear to me what you'd like to do? Obviously the frame callbacks won't run.

Flags: needinfo?(emilio)

(In reply to Emilio Cobos Álvarez [:emilio] from comment #4)

If rendering is blocked, wheel and scroll events also don't fire. So it's unclear to me what you'd like to do? Obviously the frame callbacks won't run.

With a duration greater than 0, we split the entire scroll or pointer move action into a sequence of sub-actions, each dispatching an incremental scroll or move event at the best possible interval. Currently, this is implemented using a timer, but the timing is highly unpredictable because of the timer's lower priority (as mentioned in the initial comment). By switching to requestAnimationFrame, we seem to achieve the behavior we actually want, and I'm not aware of another mechanism that provides the same level of consistency.

If the renderer is blocked and a WebDriver client issues a Perform Actions command with a duration greater than 0, we should avoid blocking the WebDriver command itself. If no events would be dispatched anyway, would it make sense to skip dispatching them altogether? If so, is there a reliable way to determine whether the renderer is currently blocked?

Flags: needinfo?(emilio)

Maybe? Whenever it gets unblocked the callbacks will start firing, which might be more like what you want?

Flags: needinfo?(emilio)

Ok, so there are a couple of failures. One obvious issue is that by using requestAnimationFrame, we're now dependent on the device's refresh rate, which can vary. Our implementation currently assumes a default tick duration of 16 ms, and the test runner makes the same assumption. On a device with a 120 Hz display, we'd end up dispatching roughly twice as many events as on a 60 Hz display.

I don't think that's the behavior we want. Instead, we should throttle event dispatching to maintain the default 60 Hz cadence, regardless of the display's refresh rate.

Here a new try build:
https://treeherder.mozilla.org/jobs?repo=try&landoInstance=lando-prod-2025&landoCommitID=80885

Assignee: nobody → hskupin
Status: NEW → ASSIGNED

A pointer move, touch move, or wheel scroll action with a duration greater
than zero has to be split into multiple incremental events, dispatched roughly
once per animation frame, instead of collapsing into a single event.

Add tests for all three input types, for both WebDriver classic and BiDi, that
record the dispatched events and assert that more than one is received.

Pushed by hskupin@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/32a4a894961d https://hg.mozilla.org/integration/autoland/rev/5a770d835c2d [remote] Use requestAnimationFrame for incremental actions transitions. r=jdescottes https://github.com/mozilla-firefox/firefox/commit/3f4c1fdbe36a https://hg.mozilla.org/integration/autoland/rev/9bae90b8c643 [wdspec] Add tests to ensure actions with a duration dispatch multiple events. r=jdescottes
Status: ASSIGNED → RESOLVED
Closed: 12 days ago
Resolution: --- → FIXED
Target Milestone: --- → 156 Branch

Created web-platform-tests PR https://github.com/web-platform-tests/wpt/pull/62203 for changes under testing/web-platform/tests

Whiteboard: [webdriver:backlog] → [webdriver:backlog], [wptsync upstream]

Upstream PR merged by moz-wptsync-bot

Points: --- → 3
Whiteboard: [webdriver:backlog], [wptsync upstream] → [webdriver:m21][wptsync upstream]

A patch has been attached on this bug, which was already closed. Filing a separate bug will ensure better tracking. If this was not by mistake and further action is needed, please alert the appropriate party. (Or: if the patch doesn't change behavior -- e.g. landing a test case, or fixing a typo -- then feel free to disregard this message)

Created web-platform-tests PR https://github.com/web-platform-tests/wpt/pull/62219 for changes under testing/web-platform/tests

Upstream PR merged by moz-wptsync-bot

You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: