Repository navigation
Conversation
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>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
ResizeObservercallback ondocument.documentElement, which readsscrollHeight,offsetHeightandclientHeightonbodyanddocumentElementscrolllistener, which readswindow.scrollYandwindow.innerHeightEach 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
ResizeObservercallback and 61ms in thescrolllistener, using the Long Animation Frames API. The numbers are in #6764.With this change both only schedule an update:
scheduleScrollMetricsUpdateasks for oneupdateScrollMetricscall throughrequestIdleCallback, orsetTimeout(…, 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.triggerEngagementmakes a pending update first, so the scroll depth in an engagement is as current as before.prePageviewTrackalready measures both values itself, so it only clears the pending flag.The
compatscript keeps its polling for the document height. Itsscrolllistener 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
scrollevent. 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_versionas #6174 did. I can't add thetracker-release: patchlabel myself.Tests
A new test in
scroll-depth.spec.jscounts reads ofwindow.scrollYwhilescrollevents are dispatched and expects none, then checks that the engagement still carries the right scroll depth. It fails onmasterwith 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
ResizeObserverpath, since I found no way to trigger its callback synchronously from a test.Changelog
Documentation
Dark mode
🤖 Generated with Claude Code