…ap the duration, one span per stall
B14-2083 (snabbit app-webview). Web long tasks come from
PerformanceObserver entries, and a WebView renderer suspended by its
host mid-task reports one task spanning the whole suspension when it
resumes, so a user switching apps produced 6-25 s frozen_frame spans.
The tracker now records hidden / frozen / suspended intervals and drops
any entry overlapping one, ignores buffered entries that ended before
install, and caps frozen_frame.duration at the new frozenFrameMaxMs
(default 10 s) with frozen_frame.capped=true. Where the browser has
Long Animation Frames it no longer also observes longtask, which
described the same stall a second time.
The React Native rAF loop had the same gap in a purer form: rAF stops
in the background and the first frame after resume arrived as one gap
the length of the whole stay. It now follows AppState, drops the gap
across a background stay, and applies the same cap. AppState is
injectable so the loop is unit-tested.
Summary
Fixes B14-2083 (snabbit
app-webview-rum:frozen_frame.durationup to 25 s, p90 ≈ 6 s on one screen). Also answers the client's question: the timer did not pause in the background.long-task.ts). Long tasks arePerformanceObserverentries. Ordinary hidden-tab throttling does not fabricate them, but an Android WebView renderer suspended by its host mid-task reports one task covering the whole suspension when it resumes. The tracker now records hidden /freeze/resumeintervals (theanr.tspattern) and drops any entry overlapping one, ignoresbufferedentries that ended before install, and capsfrozen_frame.durationat the newfrozenFrameMaxMs(default 10 s, min 700 ms) withfrozen_frame.capped: true.long_task.durationkeeps the measured value.PerformanceObserver.supportedEntryTypesincludeslong-animation-frame(Chrome 123+) the tracker no longer also observeslongtask, which described the same stall twice (twolong_task, twofrozen_frame, twoview.frozen_frame.countincrements).frame-metrics.ts). The rAF-gap loop had noAppStateguard, so the first frame after resume was a "frozen frame" as long as the background stay. It now followsAppState, resets its baseline across a background stay, and applies the same cap.AppStateis injectable, so the loop finally has unit tests.Tests
long-task.test.ts(new): visible task →long_task+frozen_frame; task spanning a hidden interval dropped; task while hidden dropped; task before hidden kept;freeze/resume; pre-install buffered entry ignored; cap + flag; LoAF-only when supported,longtaskfallback otherwise.frame-metrics.test.ts(new, first native test): frozen frame while active; background gap not reported and baseline reset; frames while backgrounded ignored; cap + flag; noAppStateavailable.make cigreen locally.Docs
CHANGELOG
[Unreleased],docs/configuration.md(frozenFrameMaxMsrow,enableLongTaskDetectiondescription), README frozen-frame row.