Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Session replay redact view with transformation #4308

Merged
merged 24 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Fixes

- Resumes replay when the app becomes active (#4303)
- Session replay redact view with transformation (#4308)

## 8.36.0

Expand Down
4 changes: 4 additions & 0 deletions Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
D8D7BB4A2750067900044146 /* UIAssert.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D7BB492750067900044146 /* UIAssert.swift */; };
D8D7BB4C2750095800044146 /* UIViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D7BB4B2750095800044146 /* UIViewExtension.swift */; };
D8D7BB4E27501B9400044146 /* SpanObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D7BB4D27501B9400044146 /* SpanObserver.swift */; };
D8DA29042C7F2199008BC825 /* UITestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DA29032C7F2199008BC825 /* UITestViewController.swift */; };
D8DBDA76274D591F00007380 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DBDA75274D591F00007380 /* TableViewController.swift */; };
D8DBDA78274D5FC400007380 /* SplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DBDA77274D5FC400007380 /* SplitViewController.swift */; };
D8F01DEA2A1376B5008F4996 /* InfoForBreadcrumbController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F01DE92A1376B5008F4996 /* InfoForBreadcrumbController.swift */; };
Expand Down Expand Up @@ -354,6 +355,7 @@
D8D7BB492750067900044146 /* UIAssert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAssert.swift; sourceTree = "<group>"; };
D8D7BB4B2750095800044146 /* UIViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewExtension.swift; sourceTree = "<group>"; };
D8D7BB4D27501B9400044146 /* SpanObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanObserver.swift; sourceTree = "<group>"; };
D8DA29032C7F2199008BC825 /* UITestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestViewController.swift; sourceTree = "<group>"; };
D8DBDA75274D591F00007380 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = "<group>"; };
D8DBDA77274D5FC400007380 /* SplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewController.swift; sourceTree = "<group>"; };
D8F01DE92A1376B5008F4996 /* InfoForBreadcrumbController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoForBreadcrumbController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -588,6 +590,7 @@
D8832B1D2AF52D0500C522B0 /* PageViewController.swift */,
B70038842BB33E7700065A38 /* ReplaceContentViewController.swift */,
D8AE48C82C57DC2F0092A2A6 /* WebViewController.swift */,
D8DA29032C7F2199008BC825 /* UITestViewController.swift */,
);
path = ViewControllers;
sourceTree = "<group>";
Expand Down Expand Up @@ -932,6 +935,7 @@
D8444E4C275E38090042F4DE /* UIViewControllerExtension.swift in Sources */,
637AFDAE243B02760034958B /* TransactionsViewController.swift in Sources */,
D8832B132AF4F7FE00C522B0 /* TopViewControllerInspector.swift in Sources */,
D8DA29042C7F2199008BC825 /* UITestViewController.swift in Sources */,
0AABE2EA28855FF80057ED69 /* PermissionsViewController.swift in Sources */,
7B5525B32938B5B5006A2932 /* DiskWriteException.swift in Sources */,
D8F3D062274EBD4800B56F8C /* SpanExtension.swift in Sources */,
Expand Down
92 changes: 82 additions & 10 deletions Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Foundation

class TargetView: UIView {

}

class UITestViewController: UIViewController {
brustolin marked this conversation as resolved.
Show resolved Hide resolved

@IBOutlet var transparentView: UIView!
brustolin marked this conversation as resolved.
Show resolved Hide resolved

@IBOutlet var label: UILabel!

override func viewDidLoad() {
super.viewDidLoad()

transparentView.backgroundColor = .green
transparentView.transform = CGAffineTransform(rotationAngle: 45 * .pi / 180.0)

SentrySDK.replayIgnore(transparentView)
}

@IBAction func showAlert(_ sender: UIButton) {

brustolin marked this conversation as resolved.
Show resolved Hide resolved
}

}
8 changes: 4 additions & 4 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,7 @@
D820CDB82BB1895F00BA339D /* SentrySessionReplayIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = D820CDB52BB1895F00BA339D /* SentrySessionReplayIntegration.h */; };
D82859432C3E753C009A28AA /* SentrySessionReplaySyncC.c in Sources */ = {isa = PBXBuildFile; fileRef = D82859422C3E753C009A28AA /* SentrySessionReplaySyncC.c */; };
D82859442C3E753C009A28AA /* SentrySessionReplaySyncC.h in Headers */ = {isa = PBXBuildFile; fileRef = D82859412C3E753C009A28AA /* SentrySessionReplaySyncC.h */; };
D82915632C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D82915622C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift */; };
D8292D7D2A39A027009872F7 /* UrlSanitizedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8292D7C2A39A027009872F7 /* UrlSanitizedTests.swift */; };
D82DD1CD2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D82DD1CC2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift */; };
D8370B6A273DF1E900F66E2D /* SentryNSURLSessionTaskSearch.m in Sources */ = {isa = PBXBuildFile; fileRef = D8370B68273DF1E900F66E2D /* SentryNSURLSessionTaskSearch.m */; };
Expand Down Expand Up @@ -883,7 +884,6 @@
D8AFC01A2BD7A20B00118BE1 /* SentryViewScreenshotProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AFC0192BD7A20B00118BE1 /* SentryViewScreenshotProvider.swift */; };
D8AFC03D2BDA79BF00118BE1 /* SentryReplayVideoMaker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AFC03C2BDA79BF00118BE1 /* SentryReplayVideoMaker.swift */; };
D8AFC0572BDA895400118BE1 /* UIRedactBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AFC0562BDA895400118BE1 /* UIRedactBuilder.swift */; };
D8AFC05A2BDA89C100118BE1 /* RedactRegionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AFC0582BDA899A00118BE1 /* RedactRegionTests.swift */; };
D8B0542E2A7D2C720056BAF6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D8B0542D2A7D2C720056BAF6 /* PrivacyInfo.xcprivacy */; };
D8B088B629C9E3FF00213258 /* SentryTracerConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = D8B088B429C9E3FF00213258 /* SentryTracerConfiguration.h */; };
D8B088B729C9E3FF00213258 /* SentryTracerConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = D8B088B529C9E3FF00213258 /* SentryTracerConfiguration.m */; };
Expand Down Expand Up @@ -1871,6 +1871,7 @@
D820CDB62BB1895F00BA339D /* SentrySessionReplayIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySessionReplayIntegration.m; sourceTree = "<group>"; };
D82859412C3E753C009A28AA /* SentrySessionReplaySyncC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySessionReplaySyncC.h; path = include/SentrySessionReplaySyncC.h; sourceTree = "<group>"; };
D82859422C3E753C009A28AA /* SentrySessionReplaySyncC.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SentrySessionReplaySyncC.c; sourceTree = "<group>"; };
D82915622C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryViewPhotographerTests.swift; sourceTree = "<group>"; };
D8292D7A2A38AF04009872F7 /* HTTPHeaderSanitizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPHeaderSanitizer.swift; sourceTree = "<group>"; };
D8292D7C2A39A027009872F7 /* UrlSanitizedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlSanitizedTests.swift; sourceTree = "<group>"; };
D82DD1CC2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySRDefaultBreadcrumbConverterTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1953,7 +1954,6 @@
D8AFC0192BD7A20B00118BE1 /* SentryViewScreenshotProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryViewScreenshotProvider.swift; sourceTree = "<group>"; };
D8AFC03C2BDA79BF00118BE1 /* SentryReplayVideoMaker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryReplayVideoMaker.swift; sourceTree = "<group>"; };
D8AFC0562BDA895400118BE1 /* UIRedactBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIRedactBuilder.swift; sourceTree = "<group>"; };
D8AFC0582BDA899A00118BE1 /* RedactRegionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedactRegionTests.swift; sourceTree = "<group>"; };
D8AFC0612BDBEDF100118BE1 /* SentrySessionReplayIntegration+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentrySessionReplayIntegration+Private.h"; path = "include/SentrySessionReplayIntegration+Private.h"; sourceTree = "<group>"; };
D8B0542D2A7D2C720056BAF6 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
D8B088B429C9E3FF00213258 /* SentryTracerConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryTracerConfiguration.h; path = include/SentryTracerConfiguration.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3696,10 +3696,10 @@
D84541192A2DC55100E2B11C /* SentryBinaryImageCache+Private.h */,
D8292D7C2A39A027009872F7 /* UrlSanitizedTests.swift */,
D8F8F5562B835BC600AC5465 /* SentryMsgPackSerializerTests.m */,
D8AFC0582BDA899A00118BE1 /* RedactRegionTests.swift */,
D8F67AEF2BE0D31A00C9197B /* UIImageHelperTests.swift */,
D8F67AF22BE10F7600C9197B /* UIRedactBuilderTests.swift */,
51B15F7F2BE88D510026A2F2 /* URLSessionTaskHelperTests.swift */,
D82915622C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift */,
);
name = Tools;
sourceTree = "<group>";
Expand Down Expand Up @@ -4822,6 +4822,7 @@
63FE722420DA66EC00CDBAE8 /* SentryCrashMonitor_NSException_Tests.m in Sources */,
7B5AB65D27E48E5200F1D1BA /* TestThreadInspector.swift in Sources */,
7BF9EF742722A85B00B5BBEF /* SentryClassRegistrator.m in Sources */,
D82915632C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift in Sources */,
D8DBE0CA2C0E093000FAB1FD /* SentryTouchTrackerTests.swift in Sources */,
D8F67AF42BE10F9600C9197B /* UIRedactBuilderTests.swift in Sources */,
63B819141EC352A7002FDF4C /* SentryInterfacesTests.m in Sources */,
Expand Down Expand Up @@ -4950,7 +4951,6 @@
62BAD74E2BA1C58D00EBAAFC /* EncodeMetricTests.swift in Sources */,
7BE0DC29272A9E1C004FA8B7 /* SentryBreadcrumbTrackerTests.swift in Sources */,
63FE722520DA66EC00CDBAE8 /* SentryCrashFileUtils_Tests.m in Sources */,
D8AFC05A2BDA89C100118BE1 /* RedactRegionTests.swift in Sources */,
D86130122BB563FD004C0F5E /* SentrySessionReplayIntegrationTests.swift in Sources */,
7BFC16BA2524D4AF00FF6266 /* SentryMessage+Equality.m in Sources */,
7B4260342630315C00B36EDD /* SampleError.swift in Sources */,
Expand Down
53 changes: 45 additions & 8 deletions Sources/Swift/Tools/SentryViewPhotographer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,65 @@ import CoreGraphics
import Foundation
import UIKit

protocol ViewRenderer {
func render(view: UIView) -> UIImage
}

class DefaultViewRenderer: ViewRenderer {
func render(view: UIView) -> UIImage {
let image = UIGraphicsImageRenderer(size: view.bounds.size).image { _ in
view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
}
return image
}
}

@objcMembers
class SentryViewPhotographer: NSObject, SentryViewScreenshotProvider {

static let shared = SentryViewPhotographer()

private let redactBuilder = UIRedactBuilder()

var renderer: ViewRenderer

init(renderer: ViewRenderer) {
self.renderer = renderer
super.init()
}

private convenience override init() {
self.init(renderer: DefaultViewRenderer())
}

func image(view: UIView, options: SentryRedactOptions, onComplete: @escaping ScreenshotCallback ) {
let image = UIGraphicsImageRenderer(size: view.bounds.size).image { _ in
view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
}
let image = renderer.render(view: view)

let redact = redactBuilder.redactRegionsFor(view: view, options: options)
let redact = redactBuilder.redactRegionsFor(view: view, options: options).reversed()
brustolin marked this conversation as resolved.
Show resolved Hide resolved
let imageSize = view.bounds.size
DispatchQueue.global().async {
brustolin marked this conversation as resolved.
Show resolved Hide resolved
let screenshot = UIGraphicsImageRenderer(size: imageSize, format: .init(for: .init(displayScale: 1))).image { context in

context.cgContext.addRect(CGRect(origin: CGPoint.zero, size: imageSize))
context.cgContext.clip(using: .evenOdd)
brustolin marked this conversation as resolved.
Show resolved Hide resolved

context.cgContext.interpolationQuality = .none
image.draw(at: .zero)

for region in redact {
(region.color ?? UIImageHelper.averageColor(of: context.currentImage, at: region.rect)).setFill()
context.fill(region.rect)
context.cgContext.saveGState()
context.cgContext.concatenate(region.transform)

let rect = CGRect(origin: CGPoint.zero, size: region.size)
switch region.type {
case .redact:
(region.color ?? UIImageHelper.averageColor(of: context.currentImage, at: rect)).setFill()
context.fill(rect)
context.cgContext.restoreGState()
case .clip:
context.cgContext.addRect(context.cgContext.boundingBoxOfClipPath)
context.cgContext.addRect(rect)
context.cgContext.restoreGState()
context.cgContext.clip(using: .evenOdd)
brustolin marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
onComplete(screenshot)
Expand Down
Loading
Loading