Skip to content

Commit e5a239a

Browse files
authored
fix: Session replay redact view with transformation (#4308)
1 parent 24e0744 commit e5a239a

File tree

11 files changed

+532
-241
lines changed

11 files changed

+532
-241
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
### Fixes
1515

1616
- Resumes replay when the app becomes active (#4303)
17+
- Session replay redact view with transformation (#4308)
18+
- Correct redact UIView with higher zPosition (#4309)
19+
- Don't redact clipped views (#4325)
1720
- Session replay for crash not created because of a race condition (#4314)
1821
- Double-quoted include, expected angle-bracketed instead (#4298)
1922

Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
D8D7BB4A2750067900044146 /* UIAssert.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D7BB492750067900044146 /* UIAssert.swift */; };
107107
D8D7BB4C2750095800044146 /* UIViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D7BB4B2750095800044146 /* UIViewExtension.swift */; };
108108
D8D7BB4E27501B9400044146 /* SpanObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D7BB4D27501B9400044146 /* SpanObserver.swift */; };
109+
D8DA29042C7F2199008BC825 /* SRRedactSampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DA29032C7F2199008BC825 /* SRRedactSampleViewController.swift */; };
109110
D8DBDA76274D591F00007380 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DBDA75274D591F00007380 /* TableViewController.swift */; };
110111
D8DBDA78274D5FC400007380 /* SplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DBDA77274D5FC400007380 /* SplitViewController.swift */; };
111112
D8F01DEA2A1376B5008F4996 /* InfoForBreadcrumbController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F01DE92A1376B5008F4996 /* InfoForBreadcrumbController.swift */; };
@@ -367,6 +368,7 @@
367368
D8D7BB492750067900044146 /* UIAssert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAssert.swift; sourceTree = "<group>"; };
368369
D8D7BB4B2750095800044146 /* UIViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewExtension.swift; sourceTree = "<group>"; };
369370
D8D7BB4D27501B9400044146 /* SpanObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanObserver.swift; sourceTree = "<group>"; };
371+
D8DA29032C7F2199008BC825 /* SRRedactSampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRRedactSampleViewController.swift; sourceTree = "<group>"; };
370372
D8DBDA75274D591F00007380 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = "<group>"; };
371373
D8DBDA77274D5FC400007380 /* SplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewController.swift; sourceTree = "<group>"; };
372374
D8F01DE92A1376B5008F4996 /* InfoForBreadcrumbController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoForBreadcrumbController.swift; sourceTree = "<group>"; };
@@ -612,6 +614,7 @@
612614
D8832B1D2AF52D0500C522B0 /* PageViewController.swift */,
613615
B70038842BB33E7700065A38 /* ReplaceContentViewController.swift */,
614616
D8AE48C82C57DC2F0092A2A6 /* WebViewController.swift */,
617+
D8DA29032C7F2199008BC825 /* SRRedactSampleViewController.swift */,
615618
);
616619
path = ViewControllers;
617620
sourceTree = "<group>";
@@ -1080,6 +1083,7 @@
10801083
D8444E4C275E38090042F4DE /* UIViewControllerExtension.swift in Sources */,
10811084
637AFDAE243B02760034958B /* TransactionsViewController.swift in Sources */,
10821085
D8832B132AF4F7FE00C522B0 /* TopViewControllerInspector.swift in Sources */,
1086+
D8DA29042C7F2199008BC825 /* SRRedactSampleViewController.swift in Sources */,
10831087
0AABE2EA28855FF80057ED69 /* PermissionsViewController.swift in Sources */,
10841088
84BA71E42C8BBBEC0045B828 /* DSNDisplayViewController.swift in Sources */,
10851089
7B5525B32938B5B5006A2932 /* DiskWriteException.swift in Sources */,

Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard

Lines changed: 92 additions & 12 deletions
Large diffs are not rendered by default.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Foundation
2+
3+
class SRRedactSampleViewController: UIViewController {
4+
5+
@IBOutlet var notRedactedView: UIView!
6+
7+
@IBOutlet var label: UILabel!
8+
9+
override func viewDidLoad() {
10+
super.viewDidLoad()
11+
12+
notRedactedView.backgroundColor = .green
13+
notRedactedView.transform = CGAffineTransform(rotationAngle: 45 * .pi / 180.0)
14+
15+
SentrySDK.replay.ignoreView(notRedactedView)
16+
}
17+
}

Sentry.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,7 @@
808808
D820CDB82BB1895F00BA339D /* SentrySessionReplayIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = D820CDB52BB1895F00BA339D /* SentrySessionReplayIntegration.h */; };
809809
D82859432C3E753C009A28AA /* SentrySessionReplaySyncC.c in Sources */ = {isa = PBXBuildFile; fileRef = D82859422C3E753C009A28AA /* SentrySessionReplaySyncC.c */; };
810810
D82859442C3E753C009A28AA /* SentrySessionReplaySyncC.h in Headers */ = {isa = PBXBuildFile; fileRef = D82859412C3E753C009A28AA /* SentrySessionReplaySyncC.h */; };
811+
D82915632C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D82915622C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift */; };
811812
D8292D7D2A39A027009872F7 /* UrlSanitizedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8292D7C2A39A027009872F7 /* UrlSanitizedTests.swift */; };
812813
D82DD1CD2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D82DD1CC2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift */; };
813814
D8370B6A273DF1E900F66E2D /* SentryNSURLSessionTaskSearch.m in Sources */ = {isa = PBXBuildFile; fileRef = D8370B68273DF1E900F66E2D /* SentryNSURLSessionTaskSearch.m */; };
@@ -885,7 +886,6 @@
885886
D8AFC01A2BD7A20B00118BE1 /* SentryViewScreenshotProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AFC0192BD7A20B00118BE1 /* SentryViewScreenshotProvider.swift */; };
886887
D8AFC03D2BDA79BF00118BE1 /* SentryReplayVideoMaker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AFC03C2BDA79BF00118BE1 /* SentryReplayVideoMaker.swift */; };
887888
D8AFC0572BDA895400118BE1 /* UIRedactBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AFC0562BDA895400118BE1 /* UIRedactBuilder.swift */; };
888-
D8AFC05A2BDA89C100118BE1 /* RedactRegionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AFC0582BDA899A00118BE1 /* RedactRegionTests.swift */; };
889889
D8B0542E2A7D2C720056BAF6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D8B0542D2A7D2C720056BAF6 /* PrivacyInfo.xcprivacy */; };
890890
D8B088B629C9E3FF00213258 /* SentryTracerConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = D8B088B429C9E3FF00213258 /* SentryTracerConfiguration.h */; };
891891
D8B088B729C9E3FF00213258 /* SentryTracerConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = D8B088B529C9E3FF00213258 /* SentryTracerConfiguration.m */; };
@@ -1873,6 +1873,7 @@
18731873
D820CDB62BB1895F00BA339D /* SentrySessionReplayIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySessionReplayIntegration.m; sourceTree = "<group>"; };
18741874
D82859412C3E753C009A28AA /* SentrySessionReplaySyncC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySessionReplaySyncC.h; path = include/SentrySessionReplaySyncC.h; sourceTree = "<group>"; };
18751875
D82859422C3E753C009A28AA /* SentrySessionReplaySyncC.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SentrySessionReplaySyncC.c; sourceTree = "<group>"; };
1876+
D82915622C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryViewPhotographerTests.swift; sourceTree = "<group>"; };
18761877
D8292D7A2A38AF04009872F7 /* HTTPHeaderSanitizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPHeaderSanitizer.swift; sourceTree = "<group>"; };
18771878
D8292D7C2A39A027009872F7 /* UrlSanitizedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlSanitizedTests.swift; sourceTree = "<group>"; };
18781879
D82DD1CC2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySRDefaultBreadcrumbConverterTests.swift; sourceTree = "<group>"; };
@@ -1957,7 +1958,6 @@
19571958
D8AFC0192BD7A20B00118BE1 /* SentryViewScreenshotProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryViewScreenshotProvider.swift; sourceTree = "<group>"; };
19581959
D8AFC03C2BDA79BF00118BE1 /* SentryReplayVideoMaker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryReplayVideoMaker.swift; sourceTree = "<group>"; };
19591960
D8AFC0562BDA895400118BE1 /* UIRedactBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIRedactBuilder.swift; sourceTree = "<group>"; };
1960-
D8AFC0582BDA899A00118BE1 /* RedactRegionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedactRegionTests.swift; sourceTree = "<group>"; };
19611961
D8AFC0612BDBEDF100118BE1 /* SentrySessionReplayIntegration+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentrySessionReplayIntegration+Private.h"; path = "include/SentrySessionReplayIntegration+Private.h"; sourceTree = "<group>"; };
19621962
D8B0542D2A7D2C720056BAF6 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
19631963
D8B088B429C9E3FF00213258 /* SentryTracerConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryTracerConfiguration.h; path = include/SentryTracerConfiguration.h; sourceTree = "<group>"; };
@@ -3702,10 +3702,10 @@
37023702
D84541192A2DC55100E2B11C /* SentryBinaryImageCache+Private.h */,
37033703
D8292D7C2A39A027009872F7 /* UrlSanitizedTests.swift */,
37043704
D8F8F5562B835BC600AC5465 /* SentryMsgPackSerializerTests.m */,
3705-
D8AFC0582BDA899A00118BE1 /* RedactRegionTests.swift */,
37063705
D8F67AEF2BE0D31A00C9197B /* UIImageHelperTests.swift */,
37073706
D8F67AF22BE10F7600C9197B /* UIRedactBuilderTests.swift */,
37083707
51B15F7F2BE88D510026A2F2 /* URLSessionTaskHelperTests.swift */,
3708+
D82915622C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift */,
37093709
);
37103710
name = Tools;
37113711
sourceTree = "<group>";
@@ -4830,6 +4830,7 @@
48304830
63FE722420DA66EC00CDBAE8 /* SentryCrashMonitor_NSException_Tests.m in Sources */,
48314831
7B5AB65D27E48E5200F1D1BA /* TestThreadInspector.swift in Sources */,
48324832
7BF9EF742722A85B00B5BBEF /* SentryClassRegistrator.m in Sources */,
4833+
D82915632C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift in Sources */,
48334834
D8DBE0CA2C0E093000FAB1FD /* SentryTouchTrackerTests.swift in Sources */,
48344835
D8F67AF42BE10F9600C9197B /* UIRedactBuilderTests.swift in Sources */,
48354836
63B819141EC352A7002FDF4C /* SentryInterfacesTests.m in Sources */,
@@ -4958,7 +4959,6 @@
49584959
62BAD74E2BA1C58D00EBAAFC /* EncodeMetricTests.swift in Sources */,
49594960
7BE0DC29272A9E1C004FA8B7 /* SentryBreadcrumbTrackerTests.swift in Sources */,
49604961
63FE722520DA66EC00CDBAE8 /* SentryCrashFileUtils_Tests.m in Sources */,
4961-
D8AFC05A2BDA89C100118BE1 /* RedactRegionTests.swift in Sources */,
49624962
D86130122BB563FD004C0F5E /* SentrySessionReplayIntegrationTests.swift in Sources */,
49634963
7BFC16BA2524D4AF00FF6266 /* SentryMessage+Equality.m in Sources */,
49644964
7B4260342630315C00B36EDD /* SampleError.swift in Sources */,

Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions {
7878
* A list of custom UIView subclasses to be ignored
7979
* during masking step of the session replay.
8080
* The view itself and any child will be ignored and not masked.
81+
* This property has precedence over `redactViewTypes`.
8182
*/
8283
public var ignoreRedactViewTypes = [AnyClass]()
8384

Sources/Swift/Tools/SentryViewPhotographer.swift

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,78 @@
11
#if canImport(UIKit) && !SENTRY_NO_UIKIT
22
#if os(iOS) || os(tvOS)
33

4+
@_implementationOnly import _SentryPrivate
45
import CoreGraphics
56
import Foundation
67
import UIKit
78

9+
protocol ViewRenderer {
10+
func render(view: UIView) -> UIImage
11+
}
12+
13+
class DefaultViewRenderer: ViewRenderer {
14+
func render(view: UIView) -> UIImage {
15+
let image = UIGraphicsImageRenderer(size: view.bounds.size).image { _ in
16+
view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
17+
}
18+
return image
19+
}
20+
}
21+
822
@objcMembers
923
class SentryViewPhotographer: NSObject, SentryViewScreenshotProvider {
10-
1124
static let shared = SentryViewPhotographer()
12-
1325
private let redactBuilder = UIRedactBuilder()
26+
private let dispatchQueue = SentryDispatchQueueWrapper()
27+
28+
var renderer: ViewRenderer
29+
30+
init(renderer: ViewRenderer) {
31+
self.renderer = renderer
32+
super.init()
33+
}
34+
35+
private convenience override init() {
36+
self.init(renderer: DefaultViewRenderer())
37+
}
1438

1539
func image(view: UIView, options: SentryRedactOptions, onComplete: @escaping ScreenshotCallback ) {
16-
let image = UIGraphicsImageRenderer(size: view.bounds.size).image { _ in
17-
view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
18-
}
40+
let image = renderer.render(view: view)
1941

2042
let redact = redactBuilder.redactRegionsFor(view: view, options: options)
2143
let imageSize = view.bounds.size
22-
DispatchQueue.global().async {
44+
dispatchQueue.dispatchAsync {
2345
let screenshot = UIGraphicsImageRenderer(size: imageSize, format: .init(for: .init(displayScale: 1))).image { context in
46+
47+
context.cgContext.addRect(CGRect(origin: CGPoint.zero, size: imageSize))
48+
context.cgContext.clip(using: .evenOdd)
49+
UIColor.blue.setStroke()
50+
2451
context.cgContext.interpolationQuality = .none
2552
image.draw(at: .zero)
2653

2754
for region in redact {
28-
(region.color ?? UIImageHelper.averageColor(of: context.currentImage, at: region.rect)).setFill()
29-
context.fill(region.rect)
55+
let rect = CGRect(origin: CGPoint.zero, size: region.size)
56+
var transform = region.transform
57+
let path = CGPath(rect: rect, transform: &transform)
58+
59+
switch region.type {
60+
case .redact:
61+
(region.color ?? UIImageHelper.averageColor(of: context.currentImage, at: rect.applying(region.transform))).setFill()
62+
context.cgContext.addPath(path)
63+
context.cgContext.fillPath()
64+
case .clipOut:
65+
context.cgContext.addRect(context.cgContext.boundingBoxOfClipPath)
66+
context.cgContext.addPath(path)
67+
context.cgContext.clip(using: .evenOdd)
68+
case .clipBegin:
69+
context.cgContext.saveGState()
70+
context.cgContext.resetClip()
71+
context.cgContext.addPath(path)
72+
context.cgContext.clip()
73+
case .clipEnd:
74+
context.cgContext.restoreGState()
75+
}
3076
}
3177
}
3278
onComplete(screenshot)

0 commit comments

Comments
 (0)