Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
2942089
fix(session-replay): Ignore list background decoration view in redaction
philprime Sep 26, 2025
648f62d
Merge remote-tracking branch 'origin/main' into philprime/fix-masking
philprime Sep 29, 2025
26dccd1
add comments
philprime Sep 29, 2025
ea7ecaa
WIP
philprime Sep 30, 2025
edfe055
WIP
philprime Oct 2, 2025
3b84342
WIP
philprime Oct 3, 2025
b7e28ef
WIP
philprime Oct 3, 2025
0fc7b14
Merge remote-tracking branch 'origin/main' into philprime/fix-masking
philprime Oct 6, 2025
2adac27
WIP
philprime Oct 6, 2025
5777824
WIP
philprime Oct 6, 2025
fa8a6ed
add snapshot tests
philprime Oct 6, 2025
53cff29
Merge remote-tracking branch 'origin/main' into philprime/fix-masking
philprime Oct 7, 2025
e180ae3
update tests in common
philprime Oct 8, 2025
1595fb0
fix uiswitch masking
philprime Oct 8, 2025
d627f40
cleanup
philprime Oct 8, 2025
8bb64c2
add more assertions to SwiftUI redaction tests
philprime Oct 8, 2025
98c4cb8
Merge remote-tracking branch 'origin/main' into philprime/fix-masking
philprime Oct 8, 2025
f0d3b4d
remove legacy screenshot snapshots
philprime Oct 8, 2025
5d5eddb
fix linting issues
philprime Oct 8, 2025
1176dad
revert mask renderer scale 2
philprime Oct 8, 2025
c2cc920
regenerate snapshots at 1x
philprime Oct 8, 2025
7c1cd59
add snapshots for SwiftUI masking tests
philprime Oct 9, 2025
3ae9bd4
Merge remote-tracking branch 'origin/main' into philprime/fix-masking
philprime Oct 14, 2025
1ccb38a
WIP
philprime Oct 14, 2025
d0aa648
wip
philprime Oct 14, 2025
4e67405
WIP
philprime Oct 14, 2025
8e6ea50
fixes
philprime Oct 15, 2025
00a82e8
remove unstable SwiftUI tests
philprime Oct 15, 2025
081e33b
Revert "remove unstable SwiftUI tests"
philprime Oct 15, 2025
13423d1
revert skipping tests
philprime Oct 15, 2025
8b5086f
wip
philprime Oct 15, 2025
4188bfc
remove reference to removed file
philprime Oct 15, 2025
56aa82a
Merge branch 'philprime/fix-masking' into philprime/swiftui-masking-t…
philprime Oct 15, 2025
6f8c247
revert remove reference
philprime Oct 15, 2025
9a17cae
Merge remote-tracking branch 'origin/main' into philprime/swiftui-mas…
philprime Nov 13, 2025
0f4a584
remove snapshots
philprime Nov 13, 2025
c4f29cd
revert changelog
philprime Nov 13, 2025
1693456
updated
philprime Nov 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,7 @@
A8F17B342902870300990B25 /* SentryHttpStatusCodeRange.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F17B332902870300990B25 /* SentryHttpStatusCodeRange.m */; };
D4009EB22D771BC20007AF30 /* SentryFileIOTrackerSwiftHelpersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4009EB12D771BB90007AF30 /* SentryFileIOTrackerSwiftHelpersTests.swift */; };
D41415A72DEEE532003B14D5 /* SentryRedactViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41415A62DEEE532003B14D5 /* SentryRedactViewHelper.swift */; };
D4145CF62EC60B910066BBC6 /* SentryUIRedactBuilderTests+SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4145CF52EC60B910066BBC6 /* SentryUIRedactBuilderTests+SwiftUI.swift */; };
D4237B3D2EB39D9700FE027C /* SentryDsn+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = D4237B3C2EB39D9700FE027C /* SentryDsn+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
D4291A6D2DD62ACE00772088 /* SentryDispatchFactoryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D4291A6C2DD62AC800772088 /* SentryDispatchFactoryTests.m */; };
D42ADEEF2E9CF43200753166 /* SentrySessionReplayEnvironmentChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42ADEE92E9CF42800753166 /* SentrySessionReplayEnvironmentChecker.swift */; };
Expand Down Expand Up @@ -2105,6 +2106,7 @@
A8F17B332902870300990B25 /* SentryHttpStatusCodeRange.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryHttpStatusCodeRange.m; sourceTree = "<group>"; };
D4009EB12D771BB90007AF30 /* SentryFileIOTrackerSwiftHelpersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryFileIOTrackerSwiftHelpersTests.swift; sourceTree = "<group>"; };
D41415A62DEEE532003B14D5 /* SentryRedactViewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRedactViewHelper.swift; sourceTree = "<group>"; };
D4145CF52EC60B910066BBC6 /* SentryUIRedactBuilderTests+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryUIRedactBuilderTests+SwiftUI.swift"; sourceTree = "<group>"; };
D41909922D48FFF6002B83D0 /* SentryNSDictionarySanitize+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryNSDictionarySanitize+Tests.h"; sourceTree = "<group>"; };
D41909942D490006002B83D0 /* SentryNSDictionarySanitize+Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SentryNSDictionarySanitize+Tests.m"; sourceTree = "<group>"; };
D4237B3C2EB39D9700FE027C /* SentryDsn+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryDsn+Private.h"; path = "include/SentryDsn+Private.h"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4199,13 +4201,13 @@
D4009EA02D77196F0007AF30 /* ViewCapture */ = {
isa = PBXGroup;
children = (
D4AF802E2E965188004F0F59 /* __Snapshots__ */,
D82915622C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift */,
D8F67AF22BE10F7600C9197B /* SentryUIRedactBuilderTests.swift */,
D4AF7D292E940492004F0F59 /* SentryUIRedactBuilderTests+Common.swift */,
D4AF7D2B2E9404ED004F0F59 /* SentryUIRedactBuilderTests+EdgeCases.swift */,
D4AF7D252E9401EB004F0F59 /* SentryUIRedactBuilderTests+UIKit.swift */,
D4AF7D212E93FFCA004F0F59 /* SentryUIRedactBuilderTests+ReactNative.swift */,
D4145CF52EC60B910066BBC6 /* SentryUIRedactBuilderTests+SwiftUI.swift */,
D4AF7D252E9401EB004F0F59 /* SentryUIRedactBuilderTests+UIKit.swift */,
D82915622C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift */,
D45E2D762E003EBF0072A6B7 /* TestRedactOptions.swift */,
);
path = ViewCapture;
Expand Down Expand Up @@ -4425,13 +4427,6 @@
path = InfoPlist;
sourceTree = "<group>";
};
D4AF802E2E965188004F0F59 /* __Snapshots__ */ = {
isa = PBXGroup;
children = (
);
path = __Snapshots__;
sourceTree = "<group>";
};
D4CBA2522DE06D1600581618 /* SentryTestUtilsTests */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -6421,6 +6416,7 @@
D8AE48C12C57B1550092A2A6 /* SentryLevelTests.swift in Sources */,
63FE721820DA66EC00CDBAE8 /* TestThread.m in Sources */,
7B4D308A26FC616B00C94DE9 /* SentryHttpTransportTests.swift in Sources */,
D4145CF62EC60B910066BBC6 /* SentryUIRedactBuilderTests+SwiftUI.swift in Sources */,
6276E68B2E7A779B002A4A8F /* SentryNetworkTrackerIntegrationTestServerTests.swift in Sources */,
7B4E23B6251A07BD00060D68 /* SentryDispatchQueueWrapperTests.swift in Sources */,
63FE720720DA66EC00CDBAE8 /* SentryCrashReportFilter_Tests.m in Sources */,
Expand Down Expand Up @@ -6700,6 +6696,7 @@
baseConfigurationReference = 63AA75C51EB8B00100D153DE /* Sentry.xcconfig */;
buildSettings = {
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES;
MACOSX_DEPLOYMENT_TARGET = 10.10.0;
OTHER_CFLAGS = (
"-Wnullable-to-nonnull-conversion",
"-Wnullability-completeness",
Expand All @@ -6712,6 +6709,7 @@
baseConfigurationReference = 63AA75C51EB8B00100D153DE /* Sentry.xcconfig */;
buildSettings = {
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES;
MACOSX_DEPLOYMENT_TARGET = 10.10.0;
OTHER_CFLAGS = (
"-Wnullable-to-nonnull-conversion",
"-Wnullability-completeness",
Expand Down Expand Up @@ -6807,6 +6805,7 @@
baseConfigurationReference = 63AA75C51EB8B00100D153DE /* Sentry.xcconfig */;
buildSettings = {
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES;
MACOSX_DEPLOYMENT_TARGET = 10.10.0;
OTHER_CFLAGS = (
"-Wnullable-to-nonnull-conversion",
"-Wnullability-completeness",
Expand Down Expand Up @@ -6868,6 +6867,7 @@
baseConfigurationReference = 63AA75C51EB8B00100D153DE /* Sentry.xcconfig */;
buildSettings = {
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES;
MACOSX_DEPLOYMENT_TARGET = 10.10.0;
OTHER_CFLAGS = (
"-Wnullable-to-nonnull-conversion",
"-Wnullability-completeness",
Expand Down Expand Up @@ -7275,6 +7275,7 @@
baseConfigurationReference = 63AA75C51EB8B00100D153DE /* Sentry.xcconfig */;
buildSettings = {
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES;
MACOSX_DEPLOYMENT_TARGET = 10.10.0;
OTHER_CFLAGS = (
"-Wnullable-to-nonnull-conversion",
"-Wnullability-completeness",
Expand Down Expand Up @@ -7570,6 +7571,7 @@
baseConfigurationReference = 63AA75C51EB8B00100D153DE /* Sentry.xcconfig */;
buildSettings = {
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES;
MACOSX_DEPLOYMENT_TARGET = 10.10.0;
OTHER_CFLAGS = (
"-Wnullable-to-nonnull-conversion",
"-Wnullability-completeness",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ final class SentryUIRedactBuilder {

// Used to render SwiftUI.Text on iOS versions prior to iOS 18
redactClasses.insert(ClassIdentifier(classId: "_TtCOCV7SwiftUI11DisplayList11ViewUpdater8Platform13CGDrawingView"))

// Used by SwiftUI on iOS 26+ to render text as a layer directly (without a view wrapper)
// The layer class name is a mangled Swift name that includes CGDrawingLayer
redactClasses.insert(ClassIdentifier(classId: "_TtC7SwiftUIP33_863CCF9D49B535DAEB1C7D61BEE53B5914CGDrawingLayer"))

}

Expand All @@ -156,6 +160,9 @@ final class SentryUIRedactBuilder {
// The same view class is also used for structural backgrounds. We differentiate by
// requiring the backing layer to be `SwiftUI.ImageLayer` so we only redact the image case.
redactClasses.insert(ClassIdentifier(classId: "SwiftUI._UIGraphicsView", layerId: "SwiftUI.ImageLayer"))

// On iOS 26+, SwiftUI.Image may use ImageLayer directly without a view wrapper
redactClasses.insert(ClassIdentifier(classId: "SwiftUI.ImageLayer"))

// These classes are used by React Native to display images/vectors.
// We are including them here to avoid leaking images from RN apps with manually initialized sentry-cocoa.
Expand Down Expand Up @@ -505,6 +512,66 @@ final class SentryUIRedactBuilder {
))
}
}
} else {
// Handle layers without view delegates (e.g., SwiftUI layers on iOS 26+)
// Check if the layer class itself matches any redact identifiers
let layerType = type(of: layer)
let layerTypeId = layerType.description()

var layerMatchesRedactClass = false

// Check if this layer type matches any of our redact class identifiers
// We check both the exact layer class and look for matches in our identifier set
for identifier in redactClassesIdentifiers {
// If the identifier has a layerId, check if it matches this layer
if let requiredLayerId = identifier.layerId {
if layerTypeId == requiredLayerId {
// This layer matches a constrained identifier (e.g., SwiftUI.ImageLayer)
redacting.append(SentryRedactRegion(
size: layer.bounds.size,
transform: newTransform,
type: .redact,
color: nil,
name: layer.debugDescription
))
enforceRedact = true
layerMatchesRedactClass = true
break
}
} else {
// Check if the layer class name matches the identifier
// This handles cases where we register layer classes directly
if layerTypeId == identifier.classId {
redacting.append(SentryRedactRegion(
size: layer.bounds.size,
transform: newTransform,
type: .redact,
color: nil,
name: layer.debugDescription
))
enforceRedact = true
layerMatchesRedactClass = true
break
}
}
}

// If the layer doesn't match a redact class but is opaque, it should create a clipOut region
// This handles background layers (e.g., SwiftUI background colors on iOS 26+)
if !layerMatchesRedactClass && isLayerOpaque(layer) {
let finalLayerFrame = CGRect(origin: .zero, size: layer.bounds.size).applying(newTransform)
if isAxisAligned(newTransform) && finalLayerFrame == rootFrame {
// Because the current layer is covering everything we found so far we can clear `redacting` list
redacting.removeAll()
} else {
redacting.append(SentryRedactRegion(
size: layer.bounds.size,
transform: newTransform,
type: .clipOut,
name: layer.debugDescription
))
}
}
}

// Traverse the sublayers to redact them if necessary
Expand Down Expand Up @@ -639,6 +706,30 @@ final class SentryUIRedactBuilder {
/// This implementation fixes the issue where semi-transparent overlays (e.g., with `alpha = 0.2`)
/// were incorrectly treated as opaque, causing text behind them to not be redacted.
/// See: https://github.com/getsentry/sentry-cocoa/pull/6629#issuecomment-3479730690
private func isLayerOpaque(_ layer: CALayer) -> Bool {
// Check layer opacity
guard layer.opacity == 1 else {
return false
}

// For layers without view delegates, we check if they have an opaque background color
// Layers may not have isOpaque explicitly set, but if they have a solid background color,
// they effectively block content behind them
if let backgroundColor = layer.backgroundColor, backgroundColor.alpha == 1 {
// If the layer is explicitly marked as opaque, definitely treat it as opaque
if layer.isOpaque {
return true
}
// Even if not explicitly marked, a layer with an opaque background color
// that covers a non-zero area should be treated as opaque
if !layer.bounds.isEmpty {
return true
}
}

return false
}

private func isOpaque(_ view: UIView) -> Bool {
let layer = view.layer.presentation() ?? view.layer

Expand Down
Loading
Loading