From 6ed15ea9c9462cf3048552c8a509979136d8de15 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 11 Dec 2024 15:23:22 +0100 Subject: [PATCH 1/8] wip --- Sources/Sentry/SentryScreenshot.m | 17 +++++++++++++++-- .../Swift/Protocol/SentryRedactOptions.swift | 8 ++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Sources/Sentry/SentryScreenshot.m b/Sources/Sentry/SentryScreenshot.m index 50017f073df..eddbb4c0468 100644 --- a/Sources/Sentry/SentryScreenshot.m +++ b/Sources/Sentry/SentryScreenshot.m @@ -7,8 +7,18 @@ # import "SentryDispatchQueueWrapper.h" # import "SentryUIApplication.h" # import +# import "SentrySwift.h" -@implementation SentryScreenshot +@implementation SentryScreenshot { + SentryViewPhotographer * photographer; +} + +- (instancetype)init { + if (self = [super init]) { + photographer = [[SentryViewPhotographer alloc] initWithRedactOptions:[[SentryRedactDefaultOptions alloc] init]]; + } + return self; +} - (NSArray *)appScreenshotsFromMainThread { @@ -40,8 +50,9 @@ - (void)saveScreenShots:(NSString *)imagesDirectoryPath - (NSArray *)appScreenshots { +// [SentryViewPhotographer alloc] ini + NSArray *windows = [SentryDependencyContainer.sharedInstance.application windows]; - NSMutableArray *result = [NSMutableArray arrayWithCapacity:windows.count]; for (UIWindow *window in windows) { @@ -53,6 +64,8 @@ - (void)saveScreenShots:(NSString *)imagesDirectoryPath continue; } + //photographer imageWithView:window options:<#(id _Nonnull)#> onComplete:<#^(UIImage * _Nonnull)onComplete#> + UIGraphicsBeginImageContext(size); if ([window drawViewHierarchyInRect:window.bounds afterScreenUpdates:false]) { diff --git a/Sources/Swift/Protocol/SentryRedactOptions.swift b/Sources/Swift/Protocol/SentryRedactOptions.swift index 24560dddea7..26cc222a3d5 100644 --- a/Sources/Swift/Protocol/SentryRedactOptions.swift +++ b/Sources/Swift/Protocol/SentryRedactOptions.swift @@ -7,3 +7,11 @@ protocol SentryRedactOptions { var maskedViewClasses: [AnyClass] { get } var unmaskedViewClasses: [AnyClass] { get } } + +@objcMembers +final class SentryRedactDefaultOptions: NSObject, SentryRedactOptions { + var maskAllText: Bool = true + var maskAllImages: Bool = true + var maskedViewClasses: [AnyClass] = [] + var unmaskedViewClasses: [AnyClass] = [] +} From 2b2f0e0f06efa09568a7ffda2c0d5c8e15030500 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 12 Dec 2024 10:54:29 +0100 Subject: [PATCH 2/8] Mask screenshots --- Sources/Sentry/SentryScreenshot.m | 23 ++-- .../Swift/Tools/SentryViewPhotographer.swift | 100 ++++++++++-------- 2 files changed, 63 insertions(+), 60 deletions(-) diff --git a/Sources/Sentry/SentryScreenshot.m b/Sources/Sentry/SentryScreenshot.m index eddbb4c0468..0791a7e846e 100644 --- a/Sources/Sentry/SentryScreenshot.m +++ b/Sources/Sentry/SentryScreenshot.m @@ -50,8 +50,6 @@ - (void)saveScreenShots:(NSString *)imagesDirectoryPath - (NSArray *)appScreenshots { -// [SentryViewPhotographer alloc] ini - NSArray *windows = [SentryDependencyContainer.sharedInstance.application windows]; NSMutableArray *result = [NSMutableArray arrayWithCapacity:windows.count]; @@ -64,23 +62,16 @@ - (void)saveScreenShots:(NSString *)imagesDirectoryPath continue; } - //photographer imageWithView:window options:<#(id _Nonnull)#> onComplete:<#^(UIImage * _Nonnull)onComplete#> + UIImage * img = [photographer imageWithView:window]; - UIGraphicsBeginImageContext(size); - - if ([window drawViewHierarchyInRect:window.bounds afterScreenUpdates:false]) { - UIImage *img = UIGraphicsGetImageFromCurrentImageContext(); - // this shouldn't happen now that we discard windows with either 0 height or 0 width, - // but still, we shouldn't send any images with either one. - if (LIKELY(img.size.width > 0 && img.size.height > 0)) { - NSData *bytes = UIImagePNGRepresentation(img); - if (bytes && bytes.length > 0) { - [result addObject:bytes]; - } + // this shouldn't happen now that we discard windows with either 0 height or 0 width, + // but still, we shouldn't send any images with either one. + if (LIKELY(img.size.width > 0 && img.size.height > 0)) { + NSData *bytes = UIImagePNGRepresentation(img); + if (bytes && bytes.length > 0) { + [result addObject:bytes]; } } - - UIGraphicsEndImageContext(); } return result; } diff --git a/Sources/Swift/Tools/SentryViewPhotographer.swift b/Sources/Swift/Tools/SentryViewPhotographer.swift index 33d22c15e8a..f742f987250 100644 --- a/Sources/Swift/Tools/SentryViewPhotographer.swift +++ b/Sources/Swift/Tools/SentryViewPhotographer.swift @@ -37,60 +37,72 @@ class SentryViewPhotographer: NSObject, SentryViewScreenshotProvider { self.redactBuilder = UIRedactBuilder(options: redactOptions) } - func image(view: UIView, onComplete: @escaping ScreenshotCallback ) { + func image(view: UIView, onComplete: @escaping ScreenshotCallback) { let redact = redactBuilder.redactRegionsFor(view: view) let image = renderer.render(view: view) - let imageSize = view.bounds.size dispatchQueue.dispatchAsync { - let screenshot = UIGraphicsImageRenderer(size: imageSize, format: .init(for: .init(displayScale: 1))).image { context in - - let clipOutPath = CGMutablePath(rect: CGRect(origin: .zero, size: imageSize), transform: nil) - var clipPaths = [CGPath]() + let screenshot = self.maskScreenshot(screenshot: image, from: view, masking: redact) + onComplete(screenshot) + } + } + + func image(view: UIView) -> UIImage { + let redact = redactBuilder.redactRegionsFor(view: view) + let image = renderer.render(view: view) + + return self.maskScreenshot(screenshot: image, from: view, masking: redact) + } + + private func maskScreenshot(screenshot image: UIImage, from view: UIView, masking: [RedactRegion]) -> UIImage { + let imageSize = view.bounds.size + let screenshot = UIGraphicsImageRenderer(size: imageSize, format: .init(for: .init(displayScale: 1))).image { context in + + let clipOutPath = CGMutablePath(rect: CGRect(origin: .zero, size: imageSize), transform: nil) + var clipPaths = [CGPath]() + + let imageRect = CGRect(origin: .zero, size: imageSize) + context.cgContext.addRect(CGRect(origin: CGPoint.zero, size: imageSize)) + context.cgContext.clip(using: .evenOdd) + UIColor.blue.setStroke() + + context.cgContext.interpolationQuality = .none + image.draw(at: .zero) + + var latestRegion: RedactRegion? + for region in masking { + let rect = CGRect(origin: CGPoint.zero, size: region.size) + var transform = region.transform + let path = CGPath(rect: rect, transform: &transform) - let imageRect = CGRect(origin: .zero, size: imageSize) - context.cgContext.addRect(CGRect(origin: CGPoint.zero, size: imageSize)) - context.cgContext.clip(using: .evenOdd) - UIColor.blue.setStroke() + defer { latestRegion = region } - context.cgContext.interpolationQuality = .none - image.draw(at: .zero) + guard latestRegion?.canReplace(as: region) != true && imageRect.intersects(path.boundingBoxOfPath) else { continue } - var latestRegion: RedactRegion? - for region in redact { - let rect = CGRect(origin: CGPoint.zero, size: region.size) - var transform = region.transform - let path = CGPath(rect: rect, transform: &transform) - - defer { latestRegion = region } - - guard latestRegion?.canReplace(as: region) != true && imageRect.intersects(path.boundingBoxOfPath) else { continue } - - switch region.type { - case .redact, .redactSwiftUI: - (region.color ?? UIImageHelper.averageColor(of: context.currentImage, at: rect.applying(region.transform))).setFill() - context.cgContext.addPath(path) - context.cgContext.fillPath() - case .clipOut: - clipOutPath.addPath(path) - self.updateClipping(for: context.cgContext, - clipPaths: clipPaths, - clipOutPath: clipOutPath) - case .clipBegin: - clipPaths.append(path) - self.updateClipping(for: context.cgContext, - clipPaths: clipPaths, - clipOutPath: clipOutPath) - case .clipEnd: - clipPaths.removeLast() - self.updateClipping(for: context.cgContext, - clipPaths: clipPaths, - clipOutPath: clipOutPath) - } + switch region.type { + case .redact, .redactSwiftUI: + (region.color ?? UIImageHelper.averageColor(of: context.currentImage, at: rect.applying(region.transform))).setFill() + context.cgContext.addPath(path) + context.cgContext.fillPath() + case .clipOut: + clipOutPath.addPath(path) + self.updateClipping(for: context.cgContext, + clipPaths: clipPaths, + clipOutPath: clipOutPath) + case .clipBegin: + clipPaths.append(path) + self.updateClipping(for: context.cgContext, + clipPaths: clipPaths, + clipOutPath: clipOutPath) + case .clipEnd: + clipPaths.removeLast() + self.updateClipping(for: context.cgContext, + clipPaths: clipPaths, + clipOutPath: clipOutPath) } } - onComplete(screenshot) } + return screenshot } private func updateClipping(for context: CGContext, clipPaths: [CGPath], clipOutPath: CGPath) { From 6bed6124e3ec19f8a2466bc5ed2c066d4fc295ca Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 12 Dec 2024 10:55:08 +0100 Subject: [PATCH 3/8] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0dd335680e..4655c966221 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Improvements - Improve compiler error message for missing Swift declarations due to APPLICATION_EXTENSION_API_ONLY (#4603) +- Mask screenshots for errors () ## 8.42.0-beta.2 From 43ec98bec29436742575abede433eaffa5857ba6 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Thu, 12 Dec 2024 09:58:25 +0000 Subject: [PATCH 4/8] Format code --- Sources/Sentry/SentryScreenshot.m | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Sources/Sentry/SentryScreenshot.m b/Sources/Sentry/SentryScreenshot.m index 0791a7e846e..f7d0b0ad793 100644 --- a/Sources/Sentry/SentryScreenshot.m +++ b/Sources/Sentry/SentryScreenshot.m @@ -5,17 +5,19 @@ # import "SentryCompiler.h" # import "SentryDependencyContainer.h" # import "SentryDispatchQueueWrapper.h" +# import "SentrySwift.h" # import "SentryUIApplication.h" # import -# import "SentrySwift.h" @implementation SentryScreenshot { - SentryViewPhotographer * photographer; + SentryViewPhotographer *photographer; } -- (instancetype)init { +- (instancetype)init +{ if (self = [super init]) { - photographer = [[SentryViewPhotographer alloc] initWithRedactOptions:[[SentryRedactDefaultOptions alloc] init]]; + photographer = [[SentryViewPhotographer alloc] + initWithRedactOptions:[[SentryRedactDefaultOptions alloc] init]]; } return self; } @@ -62,8 +64,8 @@ - (void)saveScreenShots:(NSString *)imagesDirectoryPath continue; } - UIImage * img = [photographer imageWithView:window]; - + UIImage *img = [photographer imageWithView:window]; + // this shouldn't happen now that we discard windows with either 0 height or 0 width, // but still, we shouldn't send any images with either one. if (LIKELY(img.size.width > 0 && img.size.height > 0)) { From 408d2600d1d7d597d51267d0c046622d9d17d94c Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Fri, 13 Dec 2024 14:12:27 +0100 Subject: [PATCH 5/8] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4655c966221..5d656b8c199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### Improvements - Improve compiler error message for missing Swift declarations due to APPLICATION_EXTENSION_API_ONLY (#4603) -- Mask screenshots for errors () +- Mask screenshots for errors (#4623) ## 8.42.0-beta.2 From 7c4591476648be93d98dd285b6cf5beb01b4362b Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Fri, 13 Dec 2024 14:37:09 +0100 Subject: [PATCH 6/8] SENTRY_TARGET_REPLAY_SUPPORTED --- Sources/Sentry/PrivateSentrySDKOnly.mm | 2 +- Sources/Sentry/SentryDependencyContainer.m | 4 +++- Sources/Sentry/SentryOptions.m | 6 +++--- Sources/Sentry/SentryScreenshot.m | 2 +- Sources/Sentry/SentryScreenshotIntegration.m | 2 +- Sources/Sentry/include/SentryScreenshot.h | 2 +- Sources/Sentry/include/SentryScreenshotIntegration.h | 2 +- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Sources/Sentry/PrivateSentrySDKOnly.mm b/Sources/Sentry/PrivateSentrySDKOnly.mm index e1a04912632..635ee078a3b 100644 --- a/Sources/Sentry/PrivateSentrySDKOnly.mm +++ b/Sources/Sentry/PrivateSentrySDKOnly.mm @@ -274,7 +274,7 @@ + (SentryScreenFrames *)currentScreenFrames + (NSArray *)captureScreenshots { -#if SENTRY_HAS_UIKIT +#if SENTRY_TARGET_REPLAY_SUPPORTED return [SentryDependencyContainer.sharedInstance.screenshot appScreenshots]; #else SENTRY_LOG_DEBUG( diff --git a/Sources/Sentry/SentryDependencyContainer.m b/Sources/Sentry/SentryDependencyContainer.m index 710470a6ada..1e6c3cb1313 100644 --- a/Sources/Sentry/SentryDependencyContainer.m +++ b/Sources/Sentry/SentryDependencyContainer.m @@ -255,7 +255,7 @@ - (SentryUIDeviceWrapper *)uiDeviceWrapper SENTRY_DISABLE_THREAD_SANITIZER( #endif // SENTRY_HAS_UIKIT -#if SENTRY_UIKIT_AVAILABLE +#if SENTRY_TARGET_REPLAY_SUPPORTED - (SentryScreenshot *)screenshot SENTRY_DISABLE_THREAD_SANITIZER( "double-checked lock produce false alarms") { @@ -275,7 +275,9 @@ - (SentryScreenshot *)screenshot SENTRY_DISABLE_THREAD_SANITIZER( return nil; # endif // SENTRY_HAS_UIKIT } +#endif +#if SENTRY_UIKIT_AVAILABLE - (SentryViewHierarchy *)viewHierarchy SENTRY_DISABLE_THREAD_SANITIZER( "double-checked lock produce false alarms") { diff --git a/Sources/Sentry/SentryOptions.m b/Sources/Sentry/SentryOptions.m index 7f713ad7544..3159ec8bece 100644 --- a/Sources/Sentry/SentryOptions.m +++ b/Sources/Sentry/SentryOptions.m @@ -59,13 +59,13 @@ @implementation SentryOptions { // SentryCrashIntegration needs to be initialized before SentryAutoSessionTrackingIntegration. // And SentrySessionReplayIntegration before SentryCrashIntegration. NSMutableArray *defaultIntegrations = [NSMutableArray arrayWithObjects: -#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION - [SentrySessionReplayIntegration class], +#if SENTRY_TARGET_REPLAY_SUPPORTED + [SentrySessionReplayIntegration class], [SentryScreenshotIntegration class], #endif [SentryCrashIntegration class], #if SENTRY_HAS_UIKIT [SentryAppStartTrackingIntegration class], [SentryFramesTrackingIntegration class], - [SentryPerformanceTrackingIntegration class], [SentryScreenshotIntegration class], + [SentryPerformanceTrackingIntegration class], [SentryUIEventTrackingIntegration class], [SentryViewHierarchyIntegration class], [SentryWatchdogTerminationTrackingIntegration class], #endif // SENTRY_HAS_UIKIT diff --git a/Sources/Sentry/SentryScreenshot.m b/Sources/Sentry/SentryScreenshot.m index f7d0b0ad793..f1d03090f26 100644 --- a/Sources/Sentry/SentryScreenshot.m +++ b/Sources/Sentry/SentryScreenshot.m @@ -1,6 +1,6 @@ #import "SentryScreenshot.h" -#if SENTRY_HAS_UIKIT +#if SENTRY_TARGET_REPLAY_SUPPORTED # import "SentryCompiler.h" # import "SentryDependencyContainer.h" diff --git a/Sources/Sentry/SentryScreenshotIntegration.m b/Sources/Sentry/SentryScreenshotIntegration.m index 05e798e4f5b..e0e85c85946 100644 --- a/Sources/Sentry/SentryScreenshotIntegration.m +++ b/Sources/Sentry/SentryScreenshotIntegration.m @@ -1,6 +1,6 @@ #import "SentryScreenshotIntegration.h" -#if SENTRY_HAS_UIKIT +#if SENTRY_TARGET_REPLAY_SUPPORTED # import "SentryAttachment.h" # import "SentryCrashC.h" diff --git a/Sources/Sentry/include/SentryScreenshot.h b/Sources/Sentry/include/SentryScreenshot.h index 90c24b827a2..ac0635ff385 100644 --- a/Sources/Sentry/include/SentryScreenshot.h +++ b/Sources/Sentry/include/SentryScreenshot.h @@ -1,6 +1,6 @@ #import "SentryDefines.h" -#if SENTRY_HAS_UIKIT +#if SENTRY_TARGET_REPLAY_SUPPORTED NS_ASSUME_NONNULL_BEGIN diff --git a/Sources/Sentry/include/SentryScreenshotIntegration.h b/Sources/Sentry/include/SentryScreenshotIntegration.h index 161966ea0f3..6c2ba1351fc 100644 --- a/Sources/Sentry/include/SentryScreenshotIntegration.h +++ b/Sources/Sentry/include/SentryScreenshotIntegration.h @@ -1,6 +1,6 @@ #import "SentryDefines.h" -#if SENTRY_HAS_UIKIT +#if SENTRY_TARGET_REPLAY_SUPPORTED # import "SentryBaseIntegration.h" # import "SentryClient+Private.h" From 9773743c698f9335c0dd129ff28f2934d79cd4f3 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Fri, 13 Dec 2024 13:39:11 +0000 Subject: [PATCH 7/8] Format code --- Sources/Sentry/SentryOptions.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Sentry/SentryOptions.m b/Sources/Sentry/SentryOptions.m index 3159ec8bece..148d84446f6 100644 --- a/Sources/Sentry/SentryOptions.m +++ b/Sources/Sentry/SentryOptions.m @@ -65,8 +65,8 @@ @implementation SentryOptions { [SentryCrashIntegration class], #if SENTRY_HAS_UIKIT [SentryAppStartTrackingIntegration class], [SentryFramesTrackingIntegration class], - [SentryPerformanceTrackingIntegration class], - [SentryUIEventTrackingIntegration class], [SentryViewHierarchyIntegration class], + [SentryPerformanceTrackingIntegration class], [SentryUIEventTrackingIntegration class], + [SentryViewHierarchyIntegration class], [SentryWatchdogTerminationTrackingIntegration class], #endif // SENTRY_HAS_UIKIT [SentryANRTrackingIntegration class], [SentryAutoBreadcrumbTrackingIntegration class], From 1918a11ae3781f7375619b8337fc21325af2bffc Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Mon, 16 Dec 2024 10:27:38 +0100 Subject: [PATCH 8/8] Update SentryOptions.m --- Sources/Sentry/SentryOptions.m | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/Sentry/SentryOptions.m b/Sources/Sentry/SentryOptions.m index 3159ec8bece..b508ede5f4d 100644 --- a/Sources/Sentry/SentryOptions.m +++ b/Sources/Sentry/SentryOptions.m @@ -60,8 +60,8 @@ @implementation SentryOptions { // And SentrySessionReplayIntegration before SentryCrashIntegration. NSMutableArray *defaultIntegrations = [NSMutableArray arrayWithObjects: #if SENTRY_TARGET_REPLAY_SUPPORTED - [SentrySessionReplayIntegration class], [SentryScreenshotIntegration class], -#endif + [SentrySessionReplayIntegration class], +#endif // SENTRY_TARGET_REPLAY_SUPPORTED [SentryCrashIntegration class], #if SENTRY_HAS_UIKIT [SentryAppStartTrackingIntegration class], [SentryFramesTrackingIntegration class], @@ -69,6 +69,9 @@ @implementation SentryOptions { [SentryUIEventTrackingIntegration class], [SentryViewHierarchyIntegration class], [SentryWatchdogTerminationTrackingIntegration class], #endif // SENTRY_HAS_UIKIT +#if SENTRY_TARGET_REPLAY_SUPPORTED + [SentryScreenshotIntegration class], +#endif // SENTRY_TARGET_REPLAY_SUPPORTED [SentryANRTrackingIntegration class], [SentryAutoBreadcrumbTrackingIntegration class], [SentryAutoSessionTrackingIntegration class], [SentryCoreDataTrackingIntegration class], [SentryFileIOTrackingIntegration class], [SentryNetworkTrackingIntegration class],