Skip to content

[Tracker] Read scroll metrics when the browser is idle - #6765

Open
trotzig wants to merge 1 commit into
plausible:masterfrom
trotzig:tracker-defer-scroll-metrics
Open

trotzig wants to merge 1 commit into
plausible:masterfrom
trotzig:tracker-defer-scroll-metrics

Conversation

@trotzig

@trotzig trotzig commented Oct 9, 2026

Copy link
Copy Markdown

Changes

Fixes #6764, which is what remained of #5727 after #6174.

The tracker measures scroll depth in two places that run in the middle of a frame:

  • the ResizeObserver callback on document.documentElement, which reads scrollHeight, offsetHeight and clientHeight on body and documentElement
  • the scroll listener, which reads window.scrollY and window.innerHeight

Each read makes the browser finish any pending layout before it answers. When something else has changed the page earlier in the same frame, the tracker pays for that layout. On a page with a large DOM (about 180,000 elements in our case) we measured 645ms of forced layout in the ResizeObserver callback and 61ms in the scroll listener, using the Long Animation Frames API. The numbers are in #6764.

With this change both only schedule an update:

  • scheduleScrollMetricsUpdate asks for one updateScrollMetrics call through requestIdleCallback, or setTimeout(…, 0) where that is missing (Safari). Either way the read happens after the frame, when layout is already done. Any number of resizes and scrolls before the update runs lead to a single one.
  • triggerEngagement makes a pending update first, so the scroll depth in an engagement is as current as before.
  • prePageviewTrack already measures both values itself, so it only clears the pending flag.

The compat script keeps its polling for the document height. Its scroll listener is deferred in the same way.

One difference in behaviour: the maximum scroll depth is now sampled when the browser is idle rather than on every scroll event. A reader who scrolls down and back up again within one busy stretch, before any idle time, is recorded at the position they ended on. An engagement still measures the current position before it is sent.

I bumped tracker_script_version as #6174 did. I can't add the tracker-release: patch label myself.

Tests

  • Automated tests have been added

A new test in scroll-depth.spec.js counts reads of window.scrollY while scroll events are dispatched and expects none, then checks that the engagement still carries the right scroll depth. It fails on master with 10 reads and passes with this change, in Chromium, Firefox and WebKit.

I ran the whole tracker suite locally. One Firefox run of "document height gets reevaluated on scroll" failed during the full parallel run; it passed in 12 repeated runs on its own and in a second full Firefox run, so I believe it is load-related, but I did not check how often it fails on master.

I did not add a test for the ResizeObserver path, since I found no way to trigger its callback synchronously from a test.

Changelog

  • Entry has been added to changelog

Documentation

  • This change does not need a documentation update

Dark mode

  • This PR does not change the UI

🤖 Generated with Claude Code

The ResizeObserver callback read the document height, and the scroll
listener read window.scrollY. Each read makes the browser finish any
pending layout before it answers, in the middle of a frame. On a page
with a large DOM that blocks the main thread for hundreds of
milliseconds.

Both now schedule one update for when the browser is idle. An
engagement makes a pending update first, so the scroll depth it sends
is up to date.

Fixes plausible#6764

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Oct 9, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Tracker] Scroll-depth tracking still forces layout on large pages after #6174

2 participants