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

❌ 5 Tests Failed:

Tests completed Failed Passed Skipped
4065 5 4060 28
View the top 3 failed test(s) by shortest run time
iOS_Swift_UITests.LaunchUITests::testCheckTotalFrames
Stack Traces | 0s run time
.../iOS-Swift/iOS-Swift-UITests/UITestHelpers.swift:5 - Failed to get matching snapshots: Timed out while evaluating UI query.
iOS_Swift_UITests.LaunchUITests::testSplitView
Stack Traces | 0s run time
.../iOS-Swift/iOS-Swift-UITests/LaunchUITests.swift:55 - Failed to tap "showSplitViewButton" Button: Timed out while evaluating UI query.
iOS_Swift_UITests.UserFeedbackUITests::testSubmitOnlyWithOptionalFieldsFilled
Stack Traces | 0s run time
.../iOS-Swift/iOS-Swift-UITests/BaseUITest.swift:63 - Failed to get background assertion for target app with pid 29556: Timed out while acquiring background assertion.
iOS_Swift_UITests.ViewLifecycleUITests::testViewLifecycle_callingDismissWithViewDidAppear_shouldNotCrashSDK
Stack Traces | 0s run time
.../iOS-Swift/iOS-Swift-UITests/ViewLifecycleUITests.swift:75 - Failed to tap "view-lifecycle-test" Button: Timed out while evaluating UI query.
View the full list of 1 ❄️ flaky test(s)
SentryTests.SentrySDKInternalTests::testResumeAndPauseAppHangTracking

Flake rate in main: 14.00% (Passed 43 times, Failed 7 times)

Stack Traces | 0s run time
.../Tests/SentryTests/SentrySDKInternalTests.swift:633 - XCTAssertEqual failed: ("1") is not equal to ("0") - The SDK should capture an AppHang after resuming the tracking, but it didn't.

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@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

github-actions bot commented Dec 24, 2025

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1218.30 ms 1242.27 ms 23.97 ms
Size 24.14 KiB 1005.14 KiB 981.00 KiB

Baseline results on branch: v8.x

Startup times

Revision Plain With Sentry Diff
e537c90 1226.22 ms 1256.64 ms 30.41 ms
b79b552 1210.26 ms 1232.54 ms 22.28 ms
3af1ae9 1225.60 ms 1252.65 ms 27.05 ms
b66be9b 1218.22 ms 1244.19 ms 25.96 ms
c21a31f 1237.04 ms 1256.65 ms 19.61 ms
653de7c 1205.02 ms 1222.20 ms 17.18 ms
237dfb1 1214.90 ms 1258.63 ms 43.73 ms
5e3fb04 1239.84 ms 1267.39 ms 27.55 ms
c21a31f 1216.02 ms 1236.34 ms 20.32 ms
b79b552 1216.04 ms 1241.27 ms 25.22 ms

App size

Revision Plain With Sentry Diff
e537c90 23.75 KiB 992.03 KiB 968.28 KiB
b79b552 23.75 KiB 992.25 KiB 968.50 KiB
3af1ae9 23.74 KiB 981.29 KiB 957.55 KiB
b66be9b 23.75 KiB 996.03 KiB 972.28 KiB
c21a31f 23.75 KiB 1000.77 KiB 977.02 KiB
653de7c 23.75 KiB 992.25 KiB 968.50 KiB
237dfb1 23.75 KiB 1000.79 KiB 977.04 KiB
5e3fb04 23.74 KiB 981.30 KiB 957.56 KiB
c21a31f 23.75 KiB 1000.80 KiB 977.05 KiB
b79b552 23.75 KiB 992.16 KiB 968.41 KiB

Previous results on branch: sentry-cocoa-subtree

Startup times

Revision Plain With Sentry Diff
38f375c 1200.67 ms 1220.84 ms 20.18 ms
a221e87 1219.06 ms 1260.35 ms 41.28 ms

App size

Revision Plain With Sentry Diff
38f375c 24.14 KiB 1004.33 KiB 980.19 KiB
a221e87 24.14 KiB 1004.33 KiB 980.19 KiB

@benjaminpeters
Copy link

benjaminpeters commented Dec 30, 2025

Tested on react-native using this branch (local sentry-cocoa with your branch checked out) and everything looks to be working for me

@philprime
Copy link
Member Author

Thank you @benjaminpeters for your feedback! I am now back from my Christmas break and will continue working on getting this done in the next few days.

@itaybre I noticed you picked up the PR as discussed, the checks are green. Anything missing to get this approved?

@philprime philprime requested review from alwx and antonis and removed request for benjaminpeters January 5, 2026 13:41
Copy link
Contributor

@antonis antonis left a comment

Choose a reason for hiding this comment

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

LGTM 🎸
Thank you for your work on this Phil 🙇

Copy link
Contributor

@itaybre itaybre left a comment

Choose a reason for hiding this comment

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

LGTM

@philprime philprime enabled auto-merge (squash) January 8, 2026 12:35
@philprime philprime merged commit 27e260c into v8.x Jan 8, 2026
215 of 220 checks passed
@philprime philprime deleted the sentry-cocoa-subtree branch January 8, 2026 15:57
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 Waiting for: Review ⏳

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants