Skip to content

Commit 27e260c

Browse files
authored
fix(session-replay): Add options to ignore views from subtree traversal (#7063)
1 parent 784b102 commit 27e260c

File tree

12 files changed

+1929
-137
lines changed

12 files changed

+1929
-137
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Fixes
66

77
- Encode SwiftUI internal class names in session replay redaction to avoid false-positive App Store review rejections (#7123)
8+
- Add options `options.sessionReplay.includedViewClasses` and `options.sessionReplay.excludedViewClasses` to ignore views from subtree traversal (#7063)
89

910
## 8.57.3
1011

Sentry.xcodeproj/project.pbxproj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -698,8 +698,6 @@
698698
84EB21942BF01C6C00EDDA28 /* TestNSNotificationCenterWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B18DE4328D9F8F6004845C6 /* TestNSNotificationCenterWrapper.swift */; };
699699
84EB21962BF01CEA00EDDA28 /* SentryCrashInstallationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EB21952BF01CEA00EDDA28 /* SentryCrashInstallationTests.swift */; };
700700
84F2A1CE2E06001300A94524 /* (null) in Sources */ = {isa = PBXBuildFile; };
701-
84F994E62A6894B500EC0190 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84F994E52A6894B500EC0190 /* CoreData.framework */; };
702-
84F994E82A6894BD00EC0190 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84F994E72A6894BD00EC0190 /* SystemConfiguration.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); };
703701
861265F92404EC1500C4AFDE /* SentryArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 861265F72404EC1500C4AFDE /* SentryArray.h */; };
704702
861265FA2404EC1500C4AFDE /* SentryArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 861265F82404EC1500C4AFDE /* SentryArray.m */; };
705703
8E0551E026A7A63C00400526 /* TestProtocolClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E0551DF26A7A63C00400526 /* TestProtocolClient.swift */; };
@@ -1015,6 +1013,8 @@
10151013
F41362152E1C568400B84443 /* SentryScopePersistentStore+Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41362142E1C568400B84443 /* SentryScopePersistentStore+Context.swift */; };
10161014
F4227F092EFB0D8C004A27DB /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 6387B82F1ED851970045A84C /* libz.tbd */; };
10171015
F426748D2EB11E7900E09150 /* SentryReplayApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F42674872EB11E7000E09150 /* SentryReplayApiTests.swift */; };
1016+
F43F8CBC2EFC1E970004C45D /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F43F8CB62EFC1E970004C45D /* SystemConfiguration.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); };
1017+
F43F8CBE2EFC1EA00004C45D /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F43F8CBD2EFC1EA00004C45D /* CoreData.framework */; };
10181018
F443DB272E09BE8C009A9045 /* LoadValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F443DB262E09BE8C009A9045 /* LoadValidatorTests.swift */; };
10191019
F44858132E03579D0013E63B /* SentryCrashDynamicLinker+Test.h in Headers */ = {isa = PBXBuildFile; fileRef = F44858122E0357940013E63B /* SentryCrashDynamicLinker+Test.h */; };
10201020
F451FAA62E0B304E0050ACF2 /* LoadValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F451FAA52E0B304E0050ACF2 /* LoadValidator.swift */; };
@@ -2036,8 +2036,6 @@
20362036
84EACEBC2C33CA7A009B8753 /* SentryWithoutUIKit.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; name = SentryWithoutUIKit.modulemap; path = Sources/Resources/SentryWithoutUIKit.modulemap; sourceTree = SOURCE_ROOT; };
20372037
84EACEDF2C3DCAE2009B8753 /* DeploymentTargets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DeploymentTargets.xcconfig; sourceTree = "<group>"; };
20382038
84EB21952BF01CEA00EDDA28 /* SentryCrashInstallationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCrashInstallationTests.swift; sourceTree = "<group>"; };
2039-
84F994E52A6894B500EC0190 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = ./System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
2040-
84F994E72A6894BD00EC0190 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = ./System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
20412039
861265F72404EC1500C4AFDE /* SentryArray.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryArray.h; path = include/SentryArray.h; sourceTree = "<group>"; };
20422040
861265F82404EC1500C4AFDE /* SentryArray.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryArray.m; sourceTree = "<group>"; };
20432041
8E0551DF26A7A63C00400526 /* TestProtocolClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestProtocolClient.swift; sourceTree = "<group>"; };
@@ -2380,6 +2378,8 @@
23802378
F41362122E1C566100B84443 /* SentryScopePersistentStore+User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+User.swift"; sourceTree = "<group>"; };
23812379
F41362142E1C568400B84443 /* SentryScopePersistentStore+Context.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+Context.swift"; sourceTree = "<group>"; };
23822380
F42674872EB11E7000E09150 /* SentryReplayApiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryReplayApiTests.swift; sourceTree = "<group>"; };
2381+
F43F8CB62EFC1E970004C45D /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
2382+
F43F8CBD2EFC1EA00004C45D /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
23832383
F443DB262E09BE8C009A9045 /* LoadValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadValidatorTests.swift; sourceTree = "<group>"; };
23842384
F44858122E0357940013E63B /* SentryCrashDynamicLinker+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryCrashDynamicLinker+Test.h"; sourceTree = "<group>"; };
23852385
F451FAA52E0B304E0050ACF2 /* LoadValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadValidator.swift; sourceTree = "<group>"; };
@@ -2520,9 +2520,9 @@
25202520
isa = PBXFrameworksBuildPhase;
25212521
buildActionMask = 2147483647;
25222522
files = (
2523-
84F994E82A6894BD00EC0190 /* SystemConfiguration.framework in Frameworks */,
2523+
F43F8CBC2EFC1E970004C45D /* SystemConfiguration.framework in Frameworks */,
2524+
F43F8CBE2EFC1EA00004C45D /* CoreData.framework in Frameworks */,
25242525
F4227F092EFB0D8C004A27DB /* libz.tbd in Frameworks */,
2525-
84F994E62A6894B500EC0190 /* CoreData.framework in Frameworks */,
25262526
);
25272527
runOnlyForDeploymentPostprocessing = 0;
25282528
};
@@ -2815,9 +2815,9 @@
28152815
6304360C1EC05CEF00C4D3FA /* Frameworks */ = {
28162816
isa = PBXGroup;
28172817
children = (
2818+
F43F8CBD2EFC1EA00004C45D /* CoreData.framework */,
2819+
F43F8CB62EFC1E970004C45D /* SystemConfiguration.framework */,
28182820
D483AFA32E9D555300B43C27 /* XCTest.framework */,
2819-
84F994E72A6894BD00EC0190 /* SystemConfiguration.framework */,
2820-
84F994E52A6894B500EC0190 /* CoreData.framework */,
28212821
6387B82F1ED851970045A84C /* libz.tbd */,
28222822
);
28232823
name = Frameworks;

Sources/SentrySwiftUI/Preview/PreviewRedactOptions.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,44 @@ public class PreviewRedactOptions: SentryRedactOptions {
3535
*/
3636
public let unmaskedViewClasses: [AnyClass]
3737

38+
/**
39+
* A set of view type identifier strings that should be excluded from subtree traversal.
40+
*
41+
* Views matching these types will have their subtrees skipped during redaction to avoid crashes
42+
* caused by traversing problematic view hierarchies (e.g., views that activate internal CoreAnimation
43+
* animations when their layers are accessed).
44+
*
45+
* Matching uses partial string containment: if a view's class name (from `type(of: view).description()`)
46+
* contains any of these strings, the subtree will be ignored. For example, "MyView" will match
47+
* "MyApp.MyView", "MyViewSubclass", "Some.MyView.Container", etc.
48+
*
49+
* - Note: See ``SentryReplayOptions.DefaultValues.excludedViewClasses`` for the default value.
50+
* - Note: The final set of excluded view types is computed by `SentryUIRedactBuilder` using the formula:
51+
* **Default View Classes + Excluded View Classes - Included View Classes**
52+
* Default view classes are defined in `SentryUIRedactBuilder` (e.g., `CameraUI.ChromeSwiftUIView` on iOS 26+).
53+
*/
54+
public let excludedViewClasses: Set<String>
55+
56+
/**
57+
* A set of view type identifier strings that should be included in subtree traversal.
58+
*
59+
* View types exactly matching these strings will be removed from the excluded set, allowing their subtrees
60+
* to be traversed even if they would otherwise be excluded by default or via `excludedViewClasses`.
61+
*
62+
* Matching uses exact string matching: the view's class name (from `type(of: view).description()`)
63+
* must exactly equal one of these strings. For example, "MyApp.MyView" will only match exactly "MyApp.MyView",
64+
* not "MyApp.MyViewSubclass".
65+
*
66+
* - Note: See ``SentryReplayOptions.DefaultValues.includedViewClasses`` for the default value.
67+
* - Note: The final set of excluded view types is computed by `SentryUIRedactBuilder` using the formula:
68+
* **Default View Classes + Excluded View Classes - Included View Classes**
69+
* Default view classes are defined in `SentryUIRedactBuilder` (e.g., `CameraUI.ChromeSwiftUIView` on iOS 26+).
70+
* - Note: Included patterns use exact matching (not partial) to prevent accidental matches. For example,
71+
* if "ChromeCameraUI" is excluded and "Camera" is included, "ChromeCameraUI" will still be excluded
72+
* because "Camera" doesn't exactly match "ChromeCameraUI".
73+
*/
74+
public let includedViewClasses: Set<String>
75+
3876
/**
3977
* Enables the up to 5x faster view renderer.
4078
*
@@ -50,6 +88,8 @@ public class PreviewRedactOptions: SentryRedactOptions {
5088
* - maskAllImages: Flag to redact all images in the app by drawing a rectangle over it.
5189
* - maskedViewClasses: The classes of views to mask.
5290
* - unmaskedViewClasses: The classes of views to exclude from masking.
91+
* - excludedViewClasses: A set of view type identifiers that should be excluded from subtree traversal.
92+
* - includedViewClasses: A set of view type identifiers that should be included in subtree traversal.
5393
* - enableViewRendererV2: Enables the up to 5x faster view renderer.
5494
*
5595
* - Note: See ``SentryReplayOptions.DefaultValues`` for the default values of each parameter.
@@ -59,12 +99,16 @@ public class PreviewRedactOptions: SentryRedactOptions {
5999
maskAllImages: Bool = SentryReplayOptions.DefaultValues.maskAllImages,
60100
maskedViewClasses: [AnyClass] = SentryReplayOptions.DefaultValues.maskedViewClasses,
61101
unmaskedViewClasses: [AnyClass] = SentryReplayOptions.DefaultValues.unmaskedViewClasses,
102+
excludedViewClasses: Set<String> = SentryReplayOptions.DefaultValues.excludedViewClasses,
103+
includedViewClasses: Set<String> = SentryReplayOptions.DefaultValues.includedViewClasses,
62104
enableViewRendererV2: Bool = SentryReplayOptions.DefaultValues.enableViewRendererV2
63105
) {
64106
self.maskAllText = maskAllText
65107
self.maskAllImages = maskAllImages
66108
self.maskedViewClasses = maskedViewClasses
67109
self.unmaskedViewClasses = unmaskedViewClasses
110+
self.excludedViewClasses = excludedViewClasses
111+
self.includedViewClasses = includedViewClasses
68112
self.enableViewRendererV2 = enableViewRendererV2
69113
}
70114
}

Sources/Swift/Core/Protocol/SentryRedactOptions.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ public protocol SentryRedactOptions {
66
var maskAllImages: Bool { get }
77
var maskedViewClasses: [AnyClass] { get }
88
var unmaskedViewClasses: [AnyClass] { get }
9+
var excludedViewClasses: Set<String> { get }
10+
var includedViewClasses: Set<String> { get }
911
}
1012

1113
@objcMembers
@@ -14,4 +16,41 @@ public protocol SentryRedactOptions {
1416
public var maskAllImages: Bool = true
1517
public var maskedViewClasses: [AnyClass] = []
1618
public var unmaskedViewClasses: [AnyClass] = []
19+
20+
/**
21+
* A set of view type identifier strings that should be excluded from subtree traversal.
22+
*
23+
* Views matching these types will have their subtrees skipped during redaction to avoid crashes
24+
* caused by traversing problematic view hierarchies (e.g., views that activate internal CoreAnimation
25+
* animations when their layers are accessed).
26+
*
27+
* Matching uses partial string containment: if a view's class name (from `type(of: view).description()`)
28+
* contains any of these strings, the subtree will be ignored. For example, "MyView" will match
29+
* "MyApp.MyView", "MyViewSubclass", "Some.MyView.Container", etc.
30+
*
31+
* - Note: The final set of excluded view types is computed by `SentryUIRedactBuilder` using the formula:
32+
* **Default View Classes + Excluded View Classes - Included View Classes**
33+
* Default view classes are defined in `SentryUIRedactBuilder` (e.g., `CameraUI.ChromeSwiftUIView` on iOS 26+).
34+
*/
35+
public var excludedViewClasses: Set<String> = []
36+
37+
/**
38+
* A set of view type identifier strings that should be included in subtree traversal.
39+
*
40+
* View types exactly matching these strings will be removed from the excluded set, allowing their subtrees
41+
* to be traversed even if they would otherwise be excluded by default or via `excludedViewClasses`.
42+
*
43+
* Matching uses exact string matching: the view's class name (from `type(of: view).description()`)
44+
* must exactly equal one of these strings. For example, "MyApp.MyView" will only match exactly "MyApp.MyView",
45+
* not "MyApp.MyViewSubclass".
46+
*
47+
* - Note: The final set of excluded view types is computed by `SentryUIRedactBuilder` using the formula:
48+
* **Default View Classes + Excluded View Classes - Included View Classes**
49+
* Default view classes are defined in `SentryUIRedactBuilder` (e.g., `CameraUI.ChromeSwiftUIView` on iOS 26+).
50+
* For example, you can use this to re-enable traversal for `CameraUI.ChromeSwiftUIView` on iOS 26+.
51+
* - Note: Included patterns use exact matching (not partial) to prevent accidental matches. For example,
52+
* if "ChromeCameraUI" is excluded and "Camera" is included, "ChromeCameraUI" will still be excluded
53+
* because "Camera" doesn't exactly match "ChromeCameraUI".
54+
*/
55+
public var includedViewClasses: Set<String> = []
1756
}

0 commit comments

Comments
 (0)