diff --git a/Sources/Sentry/PrivateSentrySDKOnly.mm b/Sources/Sentry/PrivateSentrySDKOnly.mm index b5ab0ace5e..ed4c72664d 100644 --- a/Sources/Sentry/PrivateSentrySDKOnly.mm +++ b/Sources/Sentry/PrivateSentrySDKOnly.mm @@ -357,6 +357,12 @@ + (void)addReplayRedactClasses:(NSArray *_Nonnull)classes { [[PrivateSentrySDKOnly getReplayIntegration].viewPhotographer addRedactClasses:classes]; } + ++ (void)setIgnoreWrapperClass:(Class _Nonnull)ignoreClass +{ + [[PrivateSentrySDKOnly getReplayIntegration].viewPhotographer + setIgnoreWrapperClass:ignoreClass]; +} #endif @end diff --git a/Sources/Sentry/include/HybridPublic/PrivateSentrySDKOnly.h b/Sources/Sentry/include/HybridPublic/PrivateSentrySDKOnly.h index 13173b4185..facf47beb9 100644 --- a/Sources/Sentry/include/HybridPublic/PrivateSentrySDKOnly.h +++ b/Sources/Sentry/include/HybridPublic/PrivateSentrySDKOnly.h @@ -182,6 +182,7 @@ typedef void (^SentryOnAppStartMeasurementAvailable)( + (NSString *__nullable)getReplayId; + (void)addReplayIgnoreClasses:(NSArray *_Nonnull)classes; + (void)addReplayRedactClasses:(NSArray *_Nonnull)classes; ++ (void)setIgnoreWrapperClass:(Class _Nonnull)ignoreClass; #endif + (nullable NSDictionary *)appStartMeasurementWithSpans; diff --git a/Sources/Swift/Tools/SentryViewPhotographer.swift b/Sources/Swift/Tools/SentryViewPhotographer.swift index d553e4ea17..4848f9c077 100644 --- a/Sources/Swift/Tools/SentryViewPhotographer.swift +++ b/Sources/Swift/Tools/SentryViewPhotographer.swift @@ -95,7 +95,12 @@ class SentryViewPhotographer: NSObject, SentryViewScreenshotProvider { func addRedactClasses(classes: [AnyClass]) { redactBuilder.addRedactClasses(classes) } - + + @objc(setIgnoreWrapperClass:) + func setIgnoreWrapperClass(wrapperClass: AnyClass) { + redactBuilder.setIgnoreWrapperClass(wrapperClass) + } + #if TEST || TESTCI func getRedactBuild() -> UIRedactBuilder { redactBuilder diff --git a/Sources/Swift/Tools/UIRedactBuilder.swift b/Sources/Swift/Tools/UIRedactBuilder.swift index 45635df3a9..c4f20e5312 100644 --- a/Sources/Swift/Tools/UIRedactBuilder.swift +++ b/Sources/Swift/Tools/UIRedactBuilder.swift @@ -46,7 +46,8 @@ struct RedactRegion { } class UIRedactBuilder { - + ///This is a wrapper which marks it's direct children to be redacted + private var ignoreWrapperClassIdentifier: ObjectIdentifier? ///This is a list of UIView subclasses that will be ignored during redact process private var ignoreClassesIdentifiers: Set ///This is a list of UIView subclasses that need to be redacted from screenshot @@ -135,7 +136,17 @@ class UIRedactBuilder { func addRedactClasses(_ redactClasses: [AnyClass]) { redactClasses.forEach(addRedactClass(_:)) } - + + func setIgnoreWrapperClass(_ ignoreClasss: AnyClass) { + ignoreWrapperClassIdentifier = ObjectIdentifier(ignoreClasss) + } + +#if TEST || TESTCI + func isIgnoreWrapperClassTestOnly(_ unmaskWrapperClass: AnyClass) -> Bool { + return isIgnoreWrapperClass(unmaskWrapperClass) + } +#endif + /** This function identifies and returns the regions within a given UIView that need to be redacted, based on the specified redaction options. @@ -178,7 +189,24 @@ class UIRedactBuilder { } private func shouldIgnore(view: UIView) -> Bool { - return SentryRedactViewHelper.shouldUnmask(view) || containsIgnoreClass(type(of: view)) + + return SentryRedactViewHelper.shouldUnmask(view) || containsIgnoreClass(type(of: view)) || isParentIgnoreWrapper(view) + } + + private func isParentIgnoreWrapper(_ view: UIView) -> Bool { + let parent = view.superview + if let parent = parent { + return isIgnoreWrapperClass(type(of: parent)) + } + return false + } + + private func isIgnoreWrapperClass(_ unmaskWrapperClass: AnyClass) -> Bool { + if ignoreWrapperClassIdentifier == nil { + return false + } + + return ObjectIdentifier(unmaskWrapperClass) == ignoreWrapperClassIdentifier } private func shouldRedact(view: UIView) -> Bool { diff --git a/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift b/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift index 3aa3fdf6d8..755d19103c 100644 --- a/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift +++ b/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift @@ -1,3 +1,4 @@ +@testable import Sentry import SentryTestUtils import XCTest @@ -369,6 +370,26 @@ class PrivateSentrySDKOnlyTests: XCTestCase { PrivateSentrySDKOnly.addReplayRedactClasses([UILabel.self]) } + func testAddIgnoreWrapper() throws { + class IgnoreWrapper: UIView {} + + SentrySDK.start { + $0.experimental.sessionReplay = SentryReplayOptions(sessionSampleRate: 1, onErrorSampleRate: 1) + $0.setIntegrations([SentrySessionReplayIntegration.self]) + } + + PrivateSentrySDKOnly.setIgnoreWrapperClass(IgnoreWrapper.self) + + let replayIntegration = try getReplayIntegration() + + let redactBuilder = replayIntegration.viewPhotographer.getRedactBuild() + XCTAssertTrue(redactBuilder.isIgnoreWrapperClassTestOnly(IgnoreWrapper.self)) + } + + private func getReplayIntegration() throws -> SentrySessionReplayIntegration { + return try XCTUnwrap(SentrySDK.currentHub().installedIntegrations().first as? SentrySessionReplayIntegration) + } + let VALID_REPLAY_ID = "0eac7ab503354dd5819b03e263627a29" private class TestSentrySessionReplayIntegration: SentrySessionReplayIntegration { diff --git a/Tests/SentryTests/UIRedactBuilderTests.swift b/Tests/SentryTests/UIRedactBuilderTests.swift index 73cf5b888b..1bb23e5a81 100644 --- a/Tests/SentryTests/UIRedactBuilderTests.swift +++ b/Tests/SentryTests/UIRedactBuilderTests.swift @@ -200,7 +200,41 @@ class UIRedactBuilderTests: XCTestCase { let result = sut.redactRegionsFor(view: rootView) XCTAssertEqual(result.count, 1) } - + + func testIgnoreWrappedChildView() { + class IgnoreWrapper: UIView {} + class AnotherLabel: UILabel {} + + let sut = getSut() + sut.setIgnoreWrapperClass(IgnoreWrapper.self) + + let ignoreWrapper = IgnoreWrapper(frame: CGRect(x: 0, y: 0, width: 60, height: 60)) + let wrappedLabel = AnotherLabel(frame: CGRect(x: 20, y: 20, width: 40, height: 40)) + ignoreWrapper.addSubview(wrappedLabel) + rootView.addSubview(ignoreWrapper) + + let result = sut.redactRegionsFor(view: rootView) + XCTAssertEqual(result.count, 0) + } + + func testIgnoreWrappedDirectChildView() { + class IgnoreWrapper: UIView {} + class AnotherLabel: UILabel {} + + let sut = getSut() + sut.setIgnoreWrapperClass(IgnoreWrapper.self) + + let ignoreWrapper = IgnoreWrapper(frame: CGRect(x: 0, y: 0, width: 60, height: 60)) + let wrappedLabel = AnotherLabel(frame: CGRect(x: 20, y: 20, width: 40, height: 40)) + let redactedLabel = AnotherLabel(frame: CGRect(x: 10, y: 10, width: 10, height: 10)) + wrappedLabel.addSubview(redactedLabel) + ignoreWrapper.addSubview(wrappedLabel) + rootView.addSubview(ignoreWrapper) + + let result = sut.redactRegionsFor(view: rootView) + XCTAssertEqual(result.count, 1) + } + func testIgnoreView() { class AnotherLabel: UILabel { }