Skip to content

Commit

Permalink
Merge 21e01cf into 419f1d4
Browse files Browse the repository at this point in the history
  • Loading branch information
brustolin authored Dec 16, 2024
2 parents 419f1d4 + 21e01cf commit 79a596d
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 68 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Improvements

- Improve compiler error message for missing Swift declarations due to APPLICATION_EXTENSION_API_ONLY (#4603)
- Mask screenshots for errors (#4623)

### Fixes

Expand Down
2 changes: 1 addition & 1 deletion Sources/Sentry/PrivateSentrySDKOnly.mm
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ + (SentryScreenFrames *)currentScreenFrames

+ (NSArray<NSData *> *)captureScreenshots
{
#if SENTRY_HAS_UIKIT
#if SENTRY_TARGET_REPLAY_SUPPORTED
return [SentryDependencyContainer.sharedInstance.screenshot appScreenshots];
#else
SENTRY_LOG_DEBUG(
Expand Down
4 changes: 3 additions & 1 deletion Sources/Sentry/SentryDependencyContainer.m
Original file line number Diff line number Diff line change
Expand Up @@ -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")
{
Expand All @@ -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")
{
Expand Down
11 changes: 7 additions & 4 deletions Sources/Sentry/SentryOptions.m
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,19 @@ @implementation SentryOptions {
// SentryCrashIntegration needs to be initialized before SentryAutoSessionTrackingIntegration.
// And SentrySessionReplayIntegration before SentryCrashIntegration.
NSMutableArray<Class> *defaultIntegrations = [NSMutableArray<Class> arrayWithObjects:
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION
#if SENTRY_TARGET_REPLAY_SUPPORTED
[SentrySessionReplayIntegration class],
#endif
#endif // SENTRY_TARGET_REPLAY_SUPPORTED
[SentryCrashIntegration class],
#if SENTRY_HAS_UIKIT
[SentryAppStartTrackingIntegration class], [SentryFramesTrackingIntegration class],
[SentryPerformanceTrackingIntegration class], [SentryScreenshotIntegration class],
[SentryUIEventTrackingIntegration class], [SentryViewHierarchyIntegration class],
[SentryPerformanceTrackingIntegration class], [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],
Expand Down
36 changes: 21 additions & 15 deletions Sources/Sentry/SentryScreenshot.m
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
#import "SentryScreenshot.h"

#if SENTRY_HAS_UIKIT
#if SENTRY_TARGET_REPLAY_SUPPORTED

# import "SentryCompiler.h"
# import "SentryDependencyContainer.h"
# import "SentryDispatchQueueWrapper.h"
# import "SentrySwift.h"
# import "SentryUIApplication.h"
# import <UIKit/UIKit.h>

@implementation SentryScreenshot
@implementation SentryScreenshot {
SentryViewPhotographer *photographer;
}

- (instancetype)init
{
if (self = [super init]) {
photographer = [[SentryViewPhotographer alloc]
initWithRedactOptions:[[SentryRedactDefaultOptions alloc] init]];
}
return self;
}

- (NSArray<NSData *> *)appScreenshotsFromMainThread
{
Expand Down Expand Up @@ -41,7 +53,6 @@ - (void)saveScreenShots:(NSString *)imagesDirectoryPath
- (NSArray<NSData *> *)appScreenshots
{
NSArray<UIWindow *> *windows = [SentryDependencyContainer.sharedInstance.application windows];

NSMutableArray *result = [NSMutableArray arrayWithCapacity:windows.count];

for (UIWindow *window in windows) {
Expand All @@ -53,21 +64,16 @@ - (void)saveScreenShots:(NSString *)imagesDirectoryPath
continue;
}

UIGraphicsBeginImageContext(size);
UIImage *img = [photographer imageWithView:window];

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;
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Sentry/SentryScreenshotIntegration.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#import "SentryScreenshotIntegration.h"

#if SENTRY_HAS_UIKIT
#if SENTRY_TARGET_REPLAY_SUPPORTED

# import "SentryAttachment.h"
# import "SentryCrashC.h"
Expand Down
2 changes: 1 addition & 1 deletion Sources/Sentry/include/SentryScreenshot.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#import "SentryDefines.h"

#if SENTRY_HAS_UIKIT
#if SENTRY_TARGET_REPLAY_SUPPORTED

NS_ASSUME_NONNULL_BEGIN

Expand Down
2 changes: 1 addition & 1 deletion Sources/Sentry/include/SentryScreenshotIntegration.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#import "SentryDefines.h"

#if SENTRY_HAS_UIKIT
#if SENTRY_TARGET_REPLAY_SUPPORTED

# import "SentryBaseIntegration.h"
# import "SentryClient+Private.h"
Expand Down
8 changes: 8 additions & 0 deletions Sources/Swift/Protocol/SentryRedactOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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] = []
}
100 changes: 56 additions & 44 deletions Sources/Swift/Tools/SentryViewPhotographer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 79a596d

Please sign in to comment.