Skip to content

Android status-bar systemui chrome leaks into replay divergence screen.refs (non-raw capture drops status_bar resource-id markers) #1251

Description

@thymikee

Summary

On Android, the replay REPLAY_DIVERGENCE screen.refs (and any consumer of collectSettleChromeRefs over a non-raw divergence capture) leak the status-bar systemui chrome — the top-of-screen clock/wifi/battery/carrier nodes are retained instead of dropped, consuming the 20-ref budget and pushing real app controls out of the divergence's screen.refs.

This is the Android analogue of the iOS keyboard case handled in #1233, but for the systemui status bar rather than the IME. IME exclusion works correctly (see #1233 evidence); this is a separate, pre-existing gap in the systemui-chrome classifier's applicability to the divergence's non-raw capture (introduced with the systemui filtering in #1198).

Live repro (Pixel_9_Pro_XL_API_37, test-app Checkout form, Gboard up)

A drifted get text .ad replayed against the running app (keyboard/IME up) → REPLAY_DIVERGENCE:

  • Device raw snapshot: 229 nodes — 147 IME (com.google.android.inputmethod.latin), 33 systemui (com.android.systemui), 49 app.
  • Divergence screen.refs (cap 20): 0 of 147 IME refs leaked (IME exclusion works), but the top-20 is filled by systemui status-bar nodes — clock "7:03", mobile_combo "T-Mobile", wifi_signal, battery, etc. The app's form fields sit at non-IME document position 74+ and fall outside the 20 refs.

Root cause

collectSettleChromeRefscollectAndroidSystemChromeRunIndexes drops a systemui run only when a member carries a com.android.systemui:id/status_bar* / navigation_bar* resource-id marker.

  • On a --raw capture of the identical screen, the filter is perfect: all 33 systemui + 147 IME refs excluded, 0 leak. The status_bar, status_bar_container, status_bar_contents, … markers are present.
  • Stripping identifier from that same raw tree reproduces the divergence leak exactly: IME still excluded (bundleId-based), all 33 systemui leak (marker-based, now unreachable).

So the divergence's non-raw capture carries bundleId but not the systemui status_bar* resource-id markers the filter needs. buildSnapshotState's non-raw path (pruneGroupNodesnormalizeSnapshotTree) preserves the markers when re-fed a raw tree, so the gap is upstream in the divergence's backend dispatch (session-replay-divergence.ts dispatches snapshot with {...action.flags, snapshotInteractiveOnly}, no snapshotRaw). The CLI can't reproduce the divergence node set for inspection (default / --force-full compact to ~10 nodes; only --raw yields the full 229) — pinning backend-resource-id-gating vs. wrapper-pruning needs a one-line instrumentation in the divergence capture.

Scope

  • Affects get / is / wait divergences (requiresRect: false → full capture). press / fill use interactive capture (status bar is non-interactive, so it's not captured).
  • Also blocks the "actionable SystemUI survives" case (volume panel, media/output pickers): without systemui resource-ids the divergence capture can't distinguish a droppable status/nav bar from a keep-worthy actionable overlay, so that distinction is untestable via divergence until this is fixed.

Fix direction (options)

  1. Capture-side (preferred): have the divergence classify chrome against a capture that carries systemui resource-ids — i.e. align the divergence snapshot with whatever the --settle path uses so status_bar* markers reach collectSettleChromeRefs. Smallest change; keeps the classifier untouched.
  2. Filter-side: make systemui status-bar detection robust to missing resource-ids (structural: systemui + top-of-screen band + non-hittable), while keeping actionable systemui (hittable, e.g. volume slider). More invasive; higher risk of misclassifying actionable overlays.

Not a #1233 regression

#1233 (exclude keyboard/IME chrome from divergence screen.refs) is a strict improvement — before it, both IME and status-bar chrome leaked; after it, IME is excluded on both platforms (verified live). This issue is the remaining systemui half, tracked separately per maintainer decision.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions