Skip to content

[PANA-8059] Add navigation and toolbar visual effect fallbacks#3066

Open
gonzalezreal wants to merge 7 commits into
gonzalezreal/PANA-8059/bar-visual-effect-fallbacksfrom
gonzalezreal/PANA-8059/navigation-toolbar-visual-effect-fallbacks
Open

[PANA-8059] Add navigation and toolbar visual effect fallbacks#3066
gonzalezreal wants to merge 7 commits into
gonzalezreal/PANA-8059/bar-visual-effect-fallbacksfrom
gonzalezreal/PANA-8059/navigation-toolbar-visual-effect-fallbacks

Conversation

@gonzalezreal

Copy link
Copy Markdown
Contributor

What and why?

This PR improves how the Core Animation recording pipeline represents navigation bars and toolbars that use iOS 26 visual effects.

These effects cannot be reproduced directly from the captured layer properties, so the pipeline uses stable fallbacks that keep controls visible and preserve the relevant content. This work builds on #3061 and remains behind the internal layer recording feature flag.

testToolbar()-maskSensitiveInputs-privacy

How?

  • Detects automatic capsules, scroll pockets, capture-only backdrops, and portal layers.
  • Replaces automatic capsules with rounded backgrounds and subtle shadows.
  • Represents top and bottom scroll pockets with destination-out gradients.
  • Preserves alpha when mapping vibrant color matrices, keeping projected text and icons visible.
  • Adds a toolbar snapshot fixture and focused unit coverage.
  • Regenerates the Session Replay models with the schema changes from DataDog/rum-events-format#409.

Portal layers initially produced image snapshot requests. This did not work reliably because CAPortalLayer does not render useful pixels when captured directly. Flattening its source into one image also loses the source tree's semantics, filters, and redaction context.

Instead, the pipeline replaces supported portal layers with the captured source hierarchy and moves that hierarchy to the portal location. The regular composition tree and wireframe logic can then preserve content, filters, and privacy handling.

This PR intentionally approximates platform glass effects rather than reproducing blur and refraction. It resolves portals that hide their source and currently supports one portal per source. Horizontal scroll pockets are also out of scope.

Review checklist

  • Feature or bugfix MUST have appropriate tests (unit, integration)
  • Make sure each commit and the PR mention the Issue number or JIRA reference
  • Add CHANGELOG entry for user facing changes
  • Add Objective-C interface for public APIs - see our guidelines (internal)
  • Run make api-surface when adding new APIs

@gonzalezreal
gonzalezreal requested review from a team as code owners July 15, 2026 11:06

@datadog-datadog-prod-us1 datadog-datadog-prod-us1 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Datadog Autotest: PASS

More details

Portal layer resolution successfully implemented with comprehensive edge-case handling and strong test coverage. Portal layers are now properly replaced with their source hierarchies before rendering, eliminating unreliable image snapshot requests. New visual effect types (scroll pockets, automatic capsules) are correctly detected and handled. No behavioral regressions identified.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Datadog Autotest · Commit d672ede · What is Autotest? · Any feedback? Reach out in #autotest

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d672ede10d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +88 to +90
var source = identifiedSnapshots[portal.sourceReplayID]
else {
return resolveAsCompositorSupport(snapshot)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not drop portals whose source was pruned

When a visible CAPortalLayer points at a source layer that was not captured into this root snapshot (for example, the source is offscreen, in another subtree, or otherwise pruned by CALayerSnapshot before resolution), this lookup fails and the portal is converted to compositor support with no sublayers. That makes Session Replay omit content that is actually visible through the portal; the previous path could still render from sourceLayer, so this needs an on-demand source snapshot or a fallback instead of dropping the portal.

Useful? React with 👍 / 👎.

Comment on lines +150 to +153
offsetAbsoluteFrames(
x: destinationFrame.minX - sourceOrigin.x,
y: destinationFrame.minY - sourceOrigin.y
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep moved portal snapshots at the portal position

When matchesPosition is false and a moved source (or one of its descendants) produces a content snapshot whose dependencies expand renderBounds beyond bounds, ContentSnapshotRequest.resolved recomputes the frame from the live CALayer coordinates whenever renderBounds != bounds. The offset applied here only changes the captured absoluteFrame, so those image wireframes are emitted at the hidden source's original location instead of at the portal; carry the portal offset through request resolution or avoid using live coordinates for moved snapshots.

Useful? React with 👍 / 👎.

Comment on lines +122 to +124
var result = snapshot
result.observation = .init(semantics: .layer)
result.sublayers = [source]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve portal privacy when reparenting source layers

When a portal is under a view with stricter Session Replay privacy than the source layer, this attaches the source snapshot with its original textAndInputPrivacyLevel / imagePrivacyLevel. Downstream redaction reads those fields from each emitted source snapshot, so content shown through a portal inside a masked subtree can be sent unmasked; the previous portal image request used the portal snapshot's resolved privacy, so apply the portal privacy to the cloned source subtree before attaching it.

Useful? React with 👍 / 👎.


var result = snapshot
result.observation = .init(semantics: .layer)
result.sublayers = [source]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clip reparented portal content to the portal bounds

When a supported portal has masksToBounds == false and sourceRect is only a slice of a larger source, attaching the entire source hierarchy here lets content outside the portal rectangle render in replay. A portal displays the selected source rect inside its own bounds; after replacing that with sublayers, the resolver needs to force an equivalent clip or the hidden source can bleed around navigation/tool bar controls.

Useful? React with 👍 / 👎.

Comment on lines +59 to 60
guard hasLayerSemantics else {
return false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep redacting portal text snapshots under maskAll

When text visible through a portal is rendered by a layer the regular recorder does not classify as static text (for example custom-drawn text), maskAll no longer runs OCR redaction because portal image snapshots were removed and this guard now only allows layer-semantic snapshots. The deleted portal branch used to redact the flattened portal bitmap, so this can upload text through CAPortalLayer that was previously masked.

Useful? React with 👍 / 👎.

@sbarrio
sbarrio requested a review from tienquocbui July 16, 2026 07:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant