Skip to content

fix(session-replay): add masking improvements #5073

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

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ lint-staged:
$(call run-lint-tools,$(STAGED_SWIFT_FILES))
.PHONY: lint-staged

format: format-clang format-swift format-markdown format-json format-yaml
format: format-clang format-swift-all format-markdown format-json format-yaml

# Format ObjC, ObjC++, C, and C++
format-clang:
Expand Down
24 changes: 20 additions & 4 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,7 @@
A8F17B2E2901765900990B25 /* SentryRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F17B2D2901765900990B25 /* SentryRequest.m */; };
A8F17B342902870300990B25 /* SentryHttpStatusCodeRange.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F17B332902870300990B25 /* SentryHttpStatusCodeRange.m */; };
D4009EB22D771BC20007AF30 /* SentryFileIOTrackerSwiftHelpersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4009EB12D771BB90007AF30 /* SentryFileIOTrackerSwiftHelpersTests.swift */; };
D41415A72DEEE532003B14D5 /* SentryRedactViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41415A62DEEE532003B14D5 /* SentryRedactViewHelper.swift */; };
D4291A692DD61A3F00772088 /* SentryDispatchQueueProviderProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D4291A672DD61A3F00772088 /* SentryDispatchQueueProviderProtocol.h */; };
D4291A6D2DD62ACE00772088 /* SentryDispatchFactoryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D4291A6C2DD62AC800772088 /* SentryDispatchFactoryTests.m */; };
D42E48572D48DF1600D251BC /* SentryBuildAppStartSpansTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42E48562D48DF1600D251BC /* SentryBuildAppStartSpansTests.swift */; };
Expand Down Expand Up @@ -857,6 +858,9 @@
D4C5F59A2D4249E6002A9BF6 /* DataSentryTracingIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C5F5992D4249E0002A9BF6 /* DataSentryTracingIntegrationTests.swift */; };
D4CBA2472DE06D0200581618 /* libSentryTestUtils.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8431F00A29B284F200D8DC56 /* libSentryTestUtils.a */; };
D4CBA2532DE06D1600581618 /* TestConstantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4CBA2512DE06D1600581618 /* TestConstantTests.swift */; };
D4CD2A7F2DE9F91900DA9F59 /* SentryViewHierarchyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4CD2A7E2DE9F91900DA9F59 /* SentryViewHierarchyNode.swift */; };
D4CD2A802DE9F91900DA9F59 /* SentryRedactRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4CD2A7C2DE9F91900DA9F59 /* SentryRedactRegion.swift */; };
D4CD2A812DE9F91900DA9F59 /* SentryRedactRegionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4CD2A7D2DE9F91900DA9F59 /* SentryRedactRegionType.swift */; };
D4E3F35D2D4A864600F79E2B /* SentryNSDictionarySanitizeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42E48582D48FC8F00D251BC /* SentryNSDictionarySanitizeTests.swift */; };
D4E3F35E2D4A877300F79E2B /* SentryNSDictionarySanitize+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = D41909942D490006002B83D0 /* SentryNSDictionarySanitize+Tests.m */; };
D4EDF9842D0B2A210071E7B3 /* Data+SentryTracing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4EDF9832D0B2A1D0071E7B3 /* Data+SentryTracing.swift */; };
Expand Down Expand Up @@ -1035,7 +1039,7 @@
FA67DD0A2DDBD4EA00896B02 /* SentryBaggageSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA67DCC92DDBD4EA00896B02 /* SentryBaggageSerialization.swift */; };
FA67DD0B2DDBD4EA00896B02 /* UIViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA67DCC52DDBD4EA00896B02 /* UIViewExtensions.swift */; };
FA67DD0C2DDBD4EA00896B02 /* SentryMaskRendererV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA67DCE32DDBD4EA00896B02 /* SentryMaskRendererV2.swift */; };
FA67DD0D2DDBD4EA00896B02 /* UIRedactBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA67DCE82DDBD4EA00896B02 /* UIRedactBuilder.swift */; };
FA67DD0D2DDBD4EA00896B02 /* SentryUIRedactBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA67DCE82DDBD4EA00896B02 /* SentryUIRedactBuilder.swift */; };
FA67DD0E2DDBD4EA00896B02 /* SentryFileContents.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA67DCCB2DDBD4EA00896B02 /* SentryFileContents.swift */; };
FA67DD0F2DDBD4EA00896B02 /* SentryViewControllerBreadcrumbTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA67DCDD2DDBD4EA00896B02 /* SentryViewControllerBreadcrumbTracking.swift */; };
FA67DD102DDBD4EA00896B02 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA67DCC42DDBD4EA00896B02 /* StringExtensions.swift */; };
Expand Down Expand Up @@ -2010,6 +2014,7 @@
A8F17B2D2901765900990B25 /* SentryRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryRequest.m; sourceTree = "<group>"; };
A8F17B332902870300990B25 /* SentryHttpStatusCodeRange.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryHttpStatusCodeRange.m; sourceTree = "<group>"; };
D4009EB12D771BB90007AF30 /* SentryFileIOTrackerSwiftHelpersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryFileIOTrackerSwiftHelpersTests.swift; sourceTree = "<group>"; };
D41415A62DEEE532003B14D5 /* SentryRedactViewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRedactViewHelper.swift; sourceTree = "<group>"; };
D41909922D48FFF6002B83D0 /* SentryNSDictionarySanitize+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryNSDictionarySanitize+Tests.h"; sourceTree = "<group>"; };
D41909942D490006002B83D0 /* SentryNSDictionarySanitize+Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SentryNSDictionarySanitize+Tests.m"; sourceTree = "<group>"; };
D4291A672DD61A3F00772088 /* SentryDispatchQueueProviderProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryDispatchQueueProviderProtocol.h; path = include/SentryDispatchQueueProviderProtocol.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2067,6 +2072,9 @@
D4C5F5992D4249E0002A9BF6 /* DataSentryTracingIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSentryTracingIntegrationTests.swift; sourceTree = "<group>"; };
D4CBA2432DE06D0200581618 /* SentryTestUtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SentryTestUtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
D4CBA2512DE06D1600581618 /* TestConstantTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstantTests.swift; sourceTree = "<group>"; };
D4CD2A7C2DE9F91900DA9F59 /* SentryRedactRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRedactRegion.swift; sourceTree = "<group>"; };
D4CD2A7D2DE9F91900DA9F59 /* SentryRedactRegionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRedactRegionType.swift; sourceTree = "<group>"; };
D4CD2A7E2DE9F91900DA9F59 /* SentryViewHierarchyNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryViewHierarchyNode.swift; sourceTree = "<group>"; };
D4EDF9832D0B2A1D0071E7B3 /* Data+SentryTracing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+SentryTracing.swift"; sourceTree = "<group>"; };
D4F2B5342D0C69D100649E42 /* SentryCrashCTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCrashCTests.swift; sourceTree = "<group>"; };
D4FC68162DD632E7001B74FF /* SentryDispatchSourceProviderProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryDispatchSourceProviderProtocol.h; path = include/SentryDispatchSourceProviderProtocol.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2265,7 +2273,7 @@
FA67DCE52DDBD4EA00896B02 /* SentryViewRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryViewRenderer.swift; sourceTree = "<group>"; };
FA67DCE62DDBD4EA00896B02 /* SentryViewRendererV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryViewRendererV2.swift; sourceTree = "<group>"; };
FA67DCE72DDBD4EA00896B02 /* SentryViewScreenshotProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryViewScreenshotProvider.swift; sourceTree = "<group>"; };
FA67DCE82DDBD4EA00896B02 /* UIRedactBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIRedactBuilder.swift; sourceTree = "<group>"; };
FA67DCE82DDBD4EA00896B02 /* SentryUIRedactBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryUIRedactBuilder.swift; sourceTree = "<group>"; };
FA67DCEA2DDBD4EA00896B02 /* HTTPHeaderSanitizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPHeaderSanitizer.swift; sourceTree = "<group>"; };
FA67DCEB2DDBD4EA00896B02 /* SentryLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLog.swift; sourceTree = "<group>"; };
FA67DCEC2DDBD4EA00896B02 /* SentryLogOutput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogOutput.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4499,6 +4507,8 @@
FA67DCE92DDBD4EA00896B02 /* ViewCapture */ = {
isa = PBXGroup;
children = (
D4CD2A7C2DE9F91900DA9F59 /* SentryRedactRegion.swift */,
D4CD2A7D2DE9F91900DA9F59 /* SentryRedactRegionType.swift */,
FA67DCDF2DDBD4EA00896B02 /* SentryDefaultMaskRenderer.swift */,
FA67DCE02DDBD4EA00896B02 /* SentryDefaultViewRenderer.swift */,
FA67DCE12DDBD4EA00896B02 /* SentryGraphicsImageRenderer.swift */,
Expand All @@ -4508,7 +4518,9 @@
FA67DCE52DDBD4EA00896B02 /* SentryViewRenderer.swift */,
FA67DCE62DDBD4EA00896B02 /* SentryViewRendererV2.swift */,
FA67DCE72DDBD4EA00896B02 /* SentryViewScreenshotProvider.swift */,
FA67DCE82DDBD4EA00896B02 /* UIRedactBuilder.swift */,
FA67DCE82DDBD4EA00896B02 /* SentryUIRedactBuilder.swift */,
D4CD2A7E2DE9F91900DA9F59 /* SentryViewHierarchyNode.swift */,
D41415A62DEEE532003B14D5 /* SentryRedactViewHelper.swift */,
);
path = ViewCapture;
sourceTree = "<group>";
Expand Down Expand Up @@ -5360,6 +5372,7 @@
FA67DCFD2DDBD4EA00896B02 /* SentryANRTracker.swift in Sources */,
FA67DCFE2DDBD4EA00896B02 /* SentryANRTrackerV2Delegate.swift in Sources */,
FA67DCFF2DDBD4EA00896B02 /* SentryMXManager.swift in Sources */,
D41415A72DEEE532003B14D5 /* SentryRedactViewHelper.swift in Sources */,
FA67DD002DDBD4EA00896B02 /* SentryMaskRenderer.swift in Sources */,
FA67DD012DDBD4EA00896B02 /* SentryMXCallStackTree.swift in Sources */,
FA67DD022DDBD4EA00896B02 /* SentryViewScreenshotProvider.swift in Sources */,
Expand All @@ -5373,7 +5386,7 @@
FA67DD0A2DDBD4EA00896B02 /* SentryBaggageSerialization.swift in Sources */,
FA67DD0B2DDBD4EA00896B02 /* UIViewExtensions.swift in Sources */,
FA67DD0C2DDBD4EA00896B02 /* SentryMaskRendererV2.swift in Sources */,
FA67DD0D2DDBD4EA00896B02 /* UIRedactBuilder.swift in Sources */,
FA67DD0D2DDBD4EA00896B02 /* SentryUIRedactBuilder.swift in Sources */,
FA67DD0E2DDBD4EA00896B02 /* SentryFileContents.swift in Sources */,
FA67DD0F2DDBD4EA00896B02 /* SentryViewControllerBreadcrumbTracking.swift in Sources */,
FA67DD102DDBD4EA00896B02 /* StringExtensions.swift in Sources */,
Expand Down Expand Up @@ -5433,6 +5446,9 @@
03F84D3227DD4191008FE43F /* SentryProfiler.mm in Sources */,
635B3F391EBC6E2500A6176D /* SentryAsynchronousOperation.m in Sources */,
63FE717520DA4C1100CDBAE8 /* SentryCrash.m in Sources */,
D4CD2A7F2DE9F91900DA9F59 /* SentryViewHierarchyNode.swift in Sources */,
D4CD2A802DE9F91900DA9F59 /* SentryRedactRegion.swift in Sources */,
D4CD2A812DE9F91900DA9F59 /* SentryRedactRegionType.swift in Sources */,
6344DDB11EC308E400D9160D /* SentryCrashInstallationReporter.m in Sources */,
84BA62272CAE2EEF0049F636 /* SentryUserFeedbackWidgetButtonView.swift in Sources */,
D85596F3280580F10041FF8B /* SentryScreenshotIntegration.m in Sources */,
Expand Down
13 changes: 10 additions & 3 deletions Sources/Sentry/SentrySessionReplayIntegration.m
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ - (void)resumePreviousSessionReplay:(SentryEvent *)event
dateProvider:_dateProvider];
resumeReplayMaker.bitRate = _replayOptions.replayBitRate;
resumeReplayMaker.videoScale = _replayOptions.sizeScale;
resumeReplayMaker.frameRate = _replayOptions.frameRate;

NSDate *beginning = hasCrashInfo
? [NSDate dateWithTimeIntervalSinceReferenceDate:crashInfo.lastSegmentEnd]
Expand Down Expand Up @@ -384,9 +385,15 @@ - (void)startWithOptions:(SentryReplayOptions *)replayOptions
dateProvider:_dateProvider];
replayMaker.bitRate = replayOptions.replayBitRate;
replayMaker.videoScale = replayOptions.sizeScale;
replayMaker.cacheMaxSize
= (NSInteger)(shouldReplayFullSession ? replayOptions.sessionSegmentDuration + 1
: replayOptions.errorReplayDuration + 1);
replayMaker.frameRate = replayOptions.frameRate;
replayMaker.onNewFrame = replayOptions.onNewFrame;

// The cache should be at least the amount of frames fitting into the session segment duration
// plus one frame to ensure that the last frame is not dropped.
NSInteger sessionSegmentDuration
= (NSInteger)(shouldReplayFullSession ? replayOptions.sessionSegmentDuration
: replayOptions.errorReplayDuration);
replayMaker.cacheMaxSize = (sessionSegmentDuration * replayOptions.frameRate) + 1;

SentryDisplayLinkWrapper *displayLinkWrapper = [[SentryDisplayLinkWrapper alloc] init];
self.sessionReplay = [[SentrySessionReplay alloc] initWithReplayOptions:replayOptions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import UIKit

class SentryDefaultMaskRenderer: NSObject, SentryMaskRenderer {
func maskScreenshot(screenshot image: UIImage, size: CGSize, masking: [RedactRegion]) -> UIImage {
func maskScreenshot(screenshot image: UIImage, size: CGSize, masking: [SentryRedactRegion]) -> UIImage {
let image = UIGraphicsImageRenderer(size: size, format: .init(for: .init(displayScale: 1))).image { context in
applyMasking(to: context, image: image, size: size, masking: masking)
}
Expand All @@ -15,7 +15,7 @@ class SentryDefaultMaskRenderer: NSObject, SentryMaskRenderer {
to context: SentryMaskRendererContext,
image: UIImage,
size: CGSize,
masking: [RedactRegion]
masking: [SentryRedactRegion]
) {
let clipOutPath = CGMutablePath(rect: CGRect(origin: .zero, size: size), transform: nil)
var clipPaths = [CGPath]()
Expand All @@ -27,7 +27,7 @@ class SentryDefaultMaskRenderer: NSObject, SentryMaskRenderer {
context.cgContext.interpolationQuality = .none
image.draw(at: .zero)

var latestRegion: RedactRegion?
var latestRegion: SentryRedactRegion?
for region in masking {
let rect = CGRect(origin: CGPoint.zero, size: region.size)
var transform = region.transform
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import UIKit

protocol SentryMaskRenderer {
func maskScreenshot(screenshot image: UIImage, size: CGSize, masking: [RedactRegion]) -> UIImage
func maskScreenshot(screenshot image: UIImage, size: CGSize, masking: [SentryRedactRegion]) -> UIImage
}

protocol SentryMaskRendererContext {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import UIKit

class SentryMaskRendererV2: SentryDefaultMaskRenderer {
override func maskScreenshot(screenshot image: UIImage, size: CGSize, masking: [RedactRegion]) -> UIImage {
override func maskScreenshot(screenshot image: UIImage, size: CGSize, masking: [SentryRedactRegion]) -> UIImage {
// The `SentryDefaultMaskRenderer` is also using an display scale of 1, therefore we also use 1 here.
// This could be evaluated in future iterations to view performance impact vs quality.
let image = SentryGraphicsImageRenderer(size: size, scale: 1).image { context in
Expand Down
109 changes: 109 additions & 0 deletions Sources/Swift/Core/Tools/ViewCapture/SentryRedactRegion.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#if canImport(UIKit) && !SENTRY_NO_UIKIT
#if os(iOS) || os(tvOS)
import Foundation
import ObjectiveC.NSObjCRuntime
import UIKit

@objc public class SentryRedactRegion: NSObject, Encodable {
enum CodingKeys: CodingKey {
case size
case transform
case type
case color
case name
}

public let size: CGSize
public let transform: CGAffineTransform
public let type: SentryRedactRegionType
public let color: UIColor?
public let name: String

init(size: CGSize, transform: CGAffineTransform, type: SentryRedactRegionType, color: UIColor? = nil, name: String) {
self.size = size
self.transform = transform
self.type = type
self.color = color
self.name = name
super.init()
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(size, forKey: .size)
try container.encode(transform, forKey: .transform)
try container.encode(type, forKey: .type)
try container.encode(SentryUIColorBox(color), forKey: .color)
try container.encode(name, forKey: .name)
}

func canReplace(as other: SentryRedactRegion) -> Bool {
size == other.size && transform == other.transform && type == other.type
}
}

private struct SentryUIColorBox: Codable {
let color: UIColor?

init(_ color: UIColor?) {
self.color = color
}

init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
let cgColorBox = try container.decode(SentryCGColorBox.self)
if let cgColor = cgColorBox.cgColor {
color = UIColor(cgColor: cgColor)
} else {
color = nil
}
}

func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(SentryCGColorBox(color?.cgColor))
}
}

private struct SentryCGColorBox: Codable {
let cgColor: CGColor?

init(_ cgColor: CGColor?) {
self.cgColor = cgColor
}

init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
let colorData = try container.decode(SentryCGColorData.self)

guard let colorSpace = CGColorSpace(name: colorData.colorSpaceName as CFString),
let cgColor = CGColor(colorSpace: colorSpace, components: colorData.components) else {
self.cgColor = nil
return
}

self.cgColor = cgColor
}

func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()

guard let cgColor = cgColor,
let colorSpaceName = cgColor.colorSpace?.name as String?,
let components = cgColor.components else {
try container.encodeNil()
return
}

let colorData = SentryCGColorData(components: components, colorSpaceName: colorSpaceName)
try container.encode(colorData)
}
}

private struct SentryCGColorData: Codable {
let components: [CGFloat]
let colorSpaceName: String
}

#endif
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
public enum SentryRedactRegionType: String, Codable {
/// Redacts the region.
case redact = "redact"

/// Marks a region to not draw anything.
/// This is used for opaque views.
case clipOut = "clip_out"

/// Push a clip region to the drawing context.
/// This is used for views that clip to its bounds.
case clipBegin = "clip_begin"

/// Pop the last Pushed region from the drawing context.
/// Used after prossing every child of a view that clip to its bounds.
case clipEnd = "clip_end"

/// These regions are redacted first, there is no way to avoid it.
case redactSwiftUI = "redact_swiftui"
}
Loading
Loading