Platform views (WebView, Maps, etc.) are masked by default in session
replay — they appear as a black box rather than being captured or
leaking native content.
- Adds `maskAllPlatformViews` config flag (default `true`)
- Adds `PostHogPlatformView` marker widget + `PostHogPlatformViewPrivacy`
enum (`.mask` / `.capture`) for per-view override
- iOS: `WKWebView.takeSnapshot` for capture; no `drawHierarchy` fallback
(would leak masked CALayer-backed views into replay)
- Android: PixelCopy + async bitmap compression; de-duplicates frames
via `compositedBytesHash`
- 5-second timeout on `captureNativeScreenshot` to prevent replay freeze
- Example app: 8 test flows covering masked/captured combinations
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
💡 Motivation and Context
Platform views (WebView, Google Maps, etc.) rendered by Flutter are composited outside the Flutter layer tree. Without explicit handling, session replay either silently skips them (leaving blank gaps) or could inadvertently capture sensitive content. This PR adds first-class support: views are masked by default (black box), with opt-in capture on a per-view or global basis.
Closes #393.
Addresses only part 1 of #151
Note I'll address fully native views in a separate PR, that will require iOS and android SDK work as well.
💚 How did you test it?
See videos:
Android: https://us.posthog.com/shared/AYnlFG5M-DGczYUYyn27g1EC55HvDg?t=1
https://us.posthog.com/shared/kWlqsDn5LUynHgynC0Mp1t_wnAfK5w?t=2
iOS: https://us.posthog.com/shared/2G_GNlw70uTOwJQOdVcZFY_uiDasTA?t=5
📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Authored with Claude Code (claude-sonnet-4-6). Anna Garcia directed the work throughout.
Key decisions made during the session: