Skip to content

Conversation

@philprime
Copy link
Member

@philprime philprime commented Dec 17, 2025

This PR should be merged after #7089

📜 Description

This PR adds configurable subtree traversal ignoring functionality to prevent crashes when traversing problematic view hierarchies during session replay and screenshot capture.

Key Changes:

  • Added viewTypesIgnoredFromSubtreeTraversal: Set<String> property to SentryRedactOptions protocol
  • Implemented the property in all conforming classes:
    • SentryReplayOptions - with helper methods excludeViewTypeFromSubtreeTraversal(_:) and includeViewTypeInSubtreeTraversal(_:)
    • SentryRedactDefaultOptions - computed property with iOS 26+ default
    • SentryViewScreenshotOptions - mutable property with empty default
    • PreviewRedactOptions - immutable property with default from SentryReplayOptions
  • Modified SentryUIRedactBuilder to check ignored view types before accessing layer.sublayers, preventing crashes
  • Added dictionary initialization support for viewTypesIgnoredFromSubtreeTraversal in SentryReplayOptions and SentryViewScreenshotOptions

Default Behavior:

  • On iOS 26+, CameraUI.ChromeSwiftUIView is automatically included in the ignore set by default for SentryReplayOptions and SentryRedactDefaultOptions to prevent crashes when accessing CameraUI.ModeLoupeLayer
  • Other platforms/versions have an empty ignore set by default
  • SentryViewScreenshotOptions has an empty ignore set by default

Implementation Details:

When a view type is in the ignore set, SentryUIRedactBuilder will:

  • Still redact the view itself (unless explicitly marked to be ignored, like UISwitch)
  • Skip traversing the view's subtree by returning early
  • Prevent crashes from accessing problematic sublayers

💡 Motivation and Context

Closes #7053

Some view hierarchies contain layers that crash when accessed during traversal. Specifically, CameraUI.ChromeSwiftUIView on iOS 26+ contains CameraUI.ModeLoupeLayer instances that cause fatal errors when their sublayers property is accessed, resulting in crashes during session replay or screenshot capture.

This change provides a configurable way to exclude problematic view types from subtree traversal while still redacting the views themselves.

💚 How did you test it?

  • Added comprehensive unit tests for all new functionality:
    • Default value tests verifying CameraUI.ChromeSwiftUIView is included on iOS 26+
    • Dictionary initialization tests covering valid/invalid/mixed values
    • Helper method tests for excludeViewTypeFromSubtreeTraversal and includeViewTypeInSubtreeTraversal
    • Subtree traversal behavior tests using ProblematicLayer/ProblematicView test classes that track sublayers access
    • Tests verify that ignored view types skip subtree traversal (no sublayers access) while non-ignored types traverse normally
  • Updated existing tests to use the new property name and API
  • All tests pass on macOS and iOS simulators

📝 Checklist

You have to check all boxes before merging:

- Introduced a new property `subtreeTraversalIgnoredViewTypes` to the `SentryRedactOptions` protocol and its implementations, allowing specific view types to be excluded from subtree traversal during redaction.
- Default implementation includes `CameraUI.ChromeSwiftUIView` on iOS 26+ to prevent crashes related to accessing problematic layers.
- Updated related classes and tests to support the new functionality, ensuring robust handling of view hierarchies during redaction.
@philprime philprime self-assigned this Dec 17, 2025
@philprime philprime added the ready-to-merge Use this label to trigger all PR workflows label Dec 17, 2025
@codecov
Copy link

codecov bot commented Dec 17, 2025

Codecov Report

❌ Patch coverage is 93.65079% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.027%. Comparing base (48dc176) to head (e8db378).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...egrations/Screenshot/SentryScreenshotOptions.swift 69.230% 4 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##              v8.x     #7063       +/-   ##
=============================================
+ Coverage   85.959%   86.027%   +0.068%     
=============================================
  Files          440       440               
  Lines        27506     27555       +49     
  Branches     11891     11899        +8     
=============================================
+ Hits         23644     23705       +61     
+ Misses        3817      3807       -10     
+ Partials        45        43        -2     
Files with missing lines Coverage Δ
...s/SentrySwiftUI/Preview/PreviewRedactOptions.swift 100.000% <100.000%> (ø)
...rces/Swift/Core/Protocol/SentryRedactOptions.swift 100.000% <100.000%> (ø)
...Core/Tools/ViewCapture/SentryUIRedactBuilder.swift 96.045% <100.000%> (+4.251%) ⬆️
...tegrations/SessionReplay/SentryReplayOptions.swift 100.000% <100.000%> (ø)
...egrations/Screenshot/SentryScreenshotOptions.swift 92.156% <69.230%> (-7.844%) ⬇️

... and 4 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 48dc176...e8db378. Read the comment docs.

@philprime philprime changed the title feat(session-replay): Add options to ignore views from subtree traversal fix(session-replay): Add options to ignore views from subtree traversal Dec 17, 2025
- Added options `options.sessionReplay.includeSubtreeTraversalForViewType` and `options.sessionReplay.excludeSubtreeTraversalForViewType` to allow ignoring specific views during subtree traversal.
- Updated default handling for `CameraUI.ChromeSwiftUIView` to prevent crashes on iOS 26+.
@philprime philprime marked this pull request as draft December 17, 2025 15:50
@philprime philprime marked this pull request as ready for review December 17, 2025 16:01
Copy link
Member

@philipphofmann philipphofmann left a comment

Choose a reason for hiding this comment

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

Are all the changes in sdk_api_V9.json and sdk_api.json intended? If yes, does it make make sense to open extra PRs for this?

The changes LGTM, but I have a few high-level questions.

@philprime
Copy link
Member Author

@philipphofmann and I have discussed the pull request review, and the confusion I need to resolve is mainly around terminology and mental models exposed to the user.

The core issue is that two conceptually different mechanisms are currently perceived as related because of naming:

  1. Masking-related configuration

    • maskedViewClasses
    • unmaskedViewClasses

    These purely control what gets redacted in a screenshot.
    unmaskedViewClasses can be used to “cut holes” into an otherwise masked area, i.e. explicitly allow certain subviews to remain visible even if their parent would normally be masked.

  2. Traversal-related configuration

    • viewTypesIgnoredFromSubtreeTraversal

    This does not control masking as it's primary purpose.
    Its purpose is to stop view hierarchy traversal for specific view types in order to avoid crashes (e.g. when accessing view.sublayers on problematic views).
    When traversal is stopped, the children of that view effectively do not exist in the algorithm. As an implementation detail, the view itself is currently masked to ensure no data leaks, but that is secondary to its primary purpose.

The confusion arises because:

  • The name viewTypesIgnoredFromSubtreeTraversal suggests a masking-related behavior (“ignored”), while it actually affects traversal control flow.
  • From a user’s perspective, it is unclear whether views in this list are masked, skipped, or both.
  • Users who only think in terms of “masking” should ideally not need to understand subtree traversal internals at all.

The action item on my side is therefore to:

  • Improve naming so that traversal-stopping semantics are explicit and not conflated with masking.
  • Improve documentation to clearly separate:
    • what is masked vs.
    • where traversal stops and why.

…actOptions

- Replaced `viewTypesIgnoredFromSubtreeTraversal` with `excludedViewClasses` and added `includedViewClasses` to allow more granular control over subtree traversal during redaction.
- Updated documentation to clarify the behavior of these new properties, including partial string matching for view class names.
- Adjusted related classes and tests to reflect these changes, ensuring consistent handling of view hierarchies.
@philipphofmann
Copy link
Member

philipphofmann commented Dec 23, 2025

That #7063 (comment) summarizes it pretty well @philprime what we discussed 👏 ⬆️

@github-actions
Copy link
Contributor

🚨 Detected changes in high risk code 🚨

High-risk code can easily blow up and is hard to test. We had severe bugs in the past. Be extra careful when changing these files, and have an extra careful look at these:

  • .github/file-filters.yml

@github-actions
Copy link
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1200.67 ms 1220.84 ms 20.18 ms
Size 24.14 KiB 1004.33 KiB 980.19 KiB

Baseline results on branch: v8.x

Startup times

Revision Plain With Sentry Diff
5e3fb04 1239.84 ms 1267.39 ms 27.55 ms
b66be9b 1218.22 ms 1244.19 ms 25.96 ms
f73c5c8 1211.10 ms 1234.62 ms 23.51 ms
237dfb1 1214.90 ms 1258.63 ms 43.73 ms
c21a31f 1216.02 ms 1236.34 ms 20.32 ms
3af1ae9 1225.60 ms 1252.65 ms 27.05 ms
c21a31f 1237.04 ms 1256.65 ms 19.61 ms
653de7c 1205.02 ms 1222.20 ms 17.18 ms
e537c90 1226.22 ms 1256.64 ms 30.41 ms
48dc176 1204.96 ms 1237.73 ms 32.77 ms

App size

Revision Plain With Sentry Diff
5e3fb04 23.74 KiB 981.30 KiB 957.56 KiB
b66be9b 23.75 KiB 996.03 KiB 972.28 KiB
f73c5c8 24.14 KiB 1001.32 KiB 977.18 KiB
237dfb1 23.75 KiB 1000.79 KiB 977.04 KiB
c21a31f 23.75 KiB 1000.80 KiB 977.05 KiB
3af1ae9 23.74 KiB 981.29 KiB 957.55 KiB
c21a31f 23.75 KiB 1000.77 KiB 977.02 KiB
653de7c 23.75 KiB 992.25 KiB 968.50 KiB
e537c90 23.75 KiB 992.03 KiB 968.28 KiB
48dc176 24.14 KiB 1001.34 KiB 977.20 KiB

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Use this label to trigger all PR workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants