#1270)
The snapshot helper is the only capture backend and emits `can-scroll-forward`/
`can-scroll-backward` for exactly the nodes Android reports as scrollable. Their
absence therefore means nothing on screen scrolls, not that scroll state is
unknown — so the `dumpsys activity top` probe only ever ran when there was no
scrollable content for it to describe.
#1288 bounded that probe at 1.5s rather than removing it, leaving every capture
of a screen with a scrollable-typed but non-scrollable node (a list short enough
to fit) paying the cap for hints that cannot exist. `dumpsys activity top`
serializes a view dump of every top activity through each app's main thread, so
one busy app stalls the call until Android's own 10s service-dump timeout — the
mechanism behind the 37ms-to-5.9s spread in the issue's evidence.
Hints for genuinely scrollable nodes are unaffected: they come from the parsed
helper attributes, which is where they already came from on every screen that
reports a scroll action.
Closes #1270.
Summary
#1288 rescoped this issue to budget hygiene and fixed the two things it could see: it capped
dumpsys activity topat 1.5s and skipped hint derivation in the presence-onlywaitpolls. It left the trigger itself in place — as the diagnosis comment predicted, the probe still fires "whenever a scrollable-TYPED node exists while no node is actuallyisScrollable()".That surviving trigger turns out to be pure cost with no possible benefit, so this removes the probe rather than bounding it.
Why the probe can never help
The snapshot helper is the only capture backend (it hard-fails otherwise since #1281/#1217), and it emits
can-scroll-forward/can-scroll-backwardonly whennode.isScrollable()(SnapshotInstrumentation.java:555-568). The parser already maps those tohiddenContentAbove/hiddenContentBelow.So on the only backend we have, absence of the attributes means nothing on screen scrolls — not "scroll state unknown". The probe fires only when the attributes are absent, i.e. only when there is no scrollable content for it to describe.
hasAndroidScrollActionAttributeswas a stock-uiautomator-era distinction (scroll-hints.tspredates the helper, #343/#427); the fallback it guarded became unreachable-in-any-useful-way when the helper became mandatory.Live evidence (emulator API 37, 1344x2992 — same environment as the issue's evidence)
Reproduced the surviving trigger on
com.callstack.agentdevicelab(aScrollViewwhose content fits → nocan-scroll-*), n=5snapshot --interactiveper side:ScrollView below:true)The probe burned the full 1.5s cap on every capture and returned 0 hints. Uncapped it took ~10.2s and still returned 0 hints — exactly as predicted, because nothing was scrollable.
Mechanism behind the variance: the raw dump shows
dumpsys activity topserializing a view dump of every top activity through each app's main thread. One busy app stalls the whole call until Android's own limit:That explains the 37ms → 5.9s spread in the original evidence: the cost depends on an unrelated app's main thread, and was charged to our
wait/getbudget.Settings root (the screen from the original report) already short-circuited on the helper path, consistent with the "fixed-by-side-effect" note — hence the
1/20bottom-wait residual in the wave-3 run rather than8/20.Changes
dumpActivityTop,ACTIVITY_TOP_TIMEOUT_MS, andderiveScrollableContentHintsIfNeededare gone, along with the1.5scap comment that justified the workaround. Deletesscroll-hints.ts(385 lines, its only consumer) and the now-orphanedapplyHiddenContentHintsToNodes.deriveMobileSnapshotHiddenContentHints) fallback is untouched and still applies when the helper reports no scrollable node. The full-tree path's hint block was a no-op once the native derivation went, so it's dropped; parser-set hints are unaffected (includeHiddenContentHints: falsenever removed those).includeHiddenContentHintsplumbing is kept — fix(android): bound the scroll-hint dumpsys probe and skip it in wait polling (#1270) #1288's wait-poll skips still apply (now to in-process geometry only), andalert.tspredates that flag.Tests
snapshotAndroid never probes the activity dump for scroll hints(new) — pins Android capture: slowdumpsys activity topscroll-hint call (intermittently ~5s) charged against the wait/get timeout → false 'not found' on unchanged screens #1270 at the surviving trigger (scrollable-typed node, nocan-scroll-*), full and interactive paths. Replaces the two probe tests (the 1.5s-cap assertion and its short-circuit twin).preserves hidden scroll content hints in interactive snapshotsandpreserves bottomed-out hidden-above hintsfed a mid-scrolledScrollViewcarrying nocan-scroll-*— XML the real helper cannot emit (a scrolledScrollViewalways reportsscrollable="true"plus its actions). Both now use realistic helper XML and keep their original assertions, so the guarantee they describe is unchanged; only the unreachable input is gone.canScrollBackward → hiddenContentAbovemapping fails exactly those two tests, so they pin the parser mapping that is now the sole hint source.Test plan
pnpm typecheck,pnpm exec oxlint --deny-warnings,pnpm format:check,pnpm check:layering,pnpm check:production-exportspnpm check:fallow --base origin/main— no issues in changed filessrc/platforms/android/__tests__/snapshot.test.ts(43/43),selector-read.test.ts(17/17)pnpm check:affected --base origin/main --run— 4218 passed; 7 unrelated failures, all ~5s timeouts inclient-metro/runtime-hints/apple index(none reference the touched files) on a host running an emulator. All 134 pass in isolation — the known contention flake.