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

RUM-7176 [SR] Record SwiftUI raster images #2123

Merged
merged 3 commits into from
Dec 3, 2024
Merged
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
20 changes: 20 additions & 0 deletions Datadog/Datadog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,11 @@
962C41A72CA431370050B747 /* SessionReplayPrivacyOverrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966253B52C98807400B90B63 /* SessionReplayPrivacyOverrides.swift */; };
962C41A82CA431AA0050B747 /* DDSessionReplayOverridesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E863752C9C7E800023BF78 /* DDSessionReplayOverridesTests.swift */; };
962C41A92CB00FD60050B747 /* DDSessionReplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A434AD2A8E426C0028E329 /* DDSessionReplayTests.swift */; };
962D72BC2CF6436700F86EF0 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 962D72BA2CF6436600F86EF0 /* Image.swift */; };
962D72BD2CF6436700F86EF0 /* Image+Reflection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 962D72BB2CF6436600F86EF0 /* Image+Reflection.swift */; };
962D72BF2CF7538800F86EF0 /* CGImage+SessionReplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 962D72BE2CF7538800F86EF0 /* CGImage+SessionReplay.swift */; };
962D72C52CF7806300F86EF0 /* ImageReflectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 962D72C42CF7806300F86EF0 /* ImageReflectionTests.swift */; };
962D72C72CF7815300F86EF0 /* ReflectionMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 962D72C62CF7815300F86EF0 /* ReflectionMocks.swift */; };
969B3B212C33F80500D62400 /* UIActivityIndicatorRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 969B3B202C33F80500D62400 /* UIActivityIndicatorRecorder.swift */; };
969B3B232C33F81E00D62400 /* UIActivityIndicatorRecorderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 969B3B222C33F81E00D62400 /* UIActivityIndicatorRecorderTests.swift */; };
96E414142C2AF56F005A6119 /* UIProgressViewRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E414132C2AF56F005A6119 /* UIProgressViewRecorder.swift */; };
Expand Down Expand Up @@ -2755,6 +2760,11 @@
61FF282F24BC5E2D000B3D9B /* RUMEventFileOutputTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMEventFileOutputTests.swift; sourceTree = "<group>"; };
61FF416125EE5FF400CE35EC /* CrashLogReceiverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashLogReceiverTests.swift; sourceTree = "<group>"; };
61FF9A4425AC5DEA001058CC /* ViewIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewIdentifier.swift; sourceTree = "<group>"; };
962D72BA2CF6436600F86EF0 /* Image.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = "<group>"; };
962D72BB2CF6436600F86EF0 /* Image+Reflection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Image+Reflection.swift"; sourceTree = "<group>"; };
962D72BE2CF7538800F86EF0 /* CGImage+SessionReplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGImage+SessionReplay.swift"; sourceTree = "<group>"; };
962D72C42CF7806300F86EF0 /* ImageReflectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageReflectionTests.swift; sourceTree = "<group>"; };
962D72C62CF7815300F86EF0 /* ReflectionMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReflectionMocks.swift; sourceTree = "<group>"; };
966253B52C98807400B90B63 /* SessionReplayPrivacyOverrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionReplayPrivacyOverrides.swift; sourceTree = "<group>"; };
969B3B202C33F80500D62400 /* UIActivityIndicatorRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIActivityIndicatorRecorder.swift; sourceTree = "<group>"; };
969B3B222C33F81E00D62400 /* UIActivityIndicatorRecorderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIActivityIndicatorRecorderTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3635,6 +3645,7 @@
61054F592A6EE1BA00AAA894 /* Recorder */,
61054F3E2A6EE1B900AAA894 /* Utilities */,
61054F492A6EE1BA00AAA894 /* Writer */,
962D72C42CF7806300F86EF0 /* ImageReflectionTests.swift */,
);
name = DatadogSessionReplayTests;
path = ../DatadogSessionReplay/Tests;
Expand Down Expand Up @@ -3677,6 +3688,7 @@
isa = PBXGroup;
children = (
61054E142A6EE10A00AAA894 /* UIImage+SessionReplay.swift */,
962D72BE2CF7538800F86EF0 /* CGImage+SessionReplay.swift */,
D22442C42CA301DA002E71E4 /* UIColor+SessionReplay.swift */,
61054E152A6EE10A00AAA894 /* UIView+SessionReplay.swift */,
61054E162A6EE10A00AAA894 /* CFType+Safety.swift */,
Expand Down Expand Up @@ -4055,6 +4067,7 @@
A74A72862B10CE4100771FEB /* ResourceMocks.swift */,
A74A72882B10D95D00771FEB /* MultipartBuilderSpy.swift */,
A7D952892B28BD94004C79B1 /* ResourceProcessorSpy.swift */,
962D72C62CF7815300F86EF0 /* ReflectionMocks.swift */,
);
path = Mocks;
sourceTree = "<group>";
Expand Down Expand Up @@ -6465,6 +6478,8 @@
D2AD1CBE2CE4AE6600106C74 /* SwiftUIWireframesBuilder.swift */,
D2AD1CBF2CE4AE6600106C74 /* Text.swift */,
D2AD1CC02CE4AE6600106C74 /* Text+Reflection.swift */,
962D72BA2CF6436600F86EF0 /* Image.swift */,
962D72BB2CF6436600F86EF0 /* Image+Reflection.swift */,
);
path = SwiftUI;
sourceTree = "<group>";
Expand Down Expand Up @@ -8460,6 +8475,7 @@
61054E772A6EE10A00AAA894 /* ViewTreeRecorder.swift in Sources */,
61054E9E2A6EE10B00AAA894 /* Queue.swift in Sources */,
61054E6A2A6EE10A00AAA894 /* UIView+SessionReplay.swift in Sources */,
962D72BF2CF7538800F86EF0 /* CGImage+SessionReplay.swift in Sources */,
96F25A832CC7EA4400459567 /* UIView+SessionReplayPrivacyOverrides+objc.swift in Sources */,
61054E7D2A6EE10A00AAA894 /* UITextFieldRecorder.swift in Sources */,
61054E832A6EE10A00AAA894 /* UISwitchRecorder.swift in Sources */,
Expand All @@ -8472,8 +8488,10 @@
61054E952A6EE10A00AAA894 /* SnapshotProcessor.swift in Sources */,
61054E722A6EE10A00AAA894 /* TouchIdentifierGenerator.swift in Sources */,
A7B932F52B1F694000AE6477 /* ResourcesProcessor.swift in Sources */,
962D72BD2CF6436700F86EF0 /* Image+Reflection.swift in Sources */,
61054E742A6EE10A00AAA894 /* ViewTreeSnapshotProducer.swift in Sources */,
61054E7E2A6EE10A00AAA894 /* NodeRecorder.swift in Sources */,
962D72BC2CF6436700F86EF0 /* Image.swift in Sources */,
61054E6F2A6EE10A00AAA894 /* UIApplicationSwizzler.swift in Sources */,
61054E6D2A6EE10A00AAA894 /* CGRect+SessionReplay.swift in Sources */,
D274FD1C2CBFEF6D005270B5 /* CGSize+SessionReplay.swift in Sources */,
Expand Down Expand Up @@ -8555,6 +8573,7 @@
61054FB62A6EE1BA00AAA894 /* UnsupportedViewRecorderTests.swift in Sources */,
61054F9F2A6EE1BA00AAA894 /* RecordsWriterTests.swift in Sources */,
61054FB82A6EE1BA00AAA894 /* UIDatePickerRecorderTests.swift in Sources */,
962D72C52CF7806300F86EF0 /* ImageReflectionTests.swift in Sources */,
61054FA32A6EE1BA00AAA894 /* Diff+SRWireframesTests.swift in Sources */,
61054FAF2A6EE1BA00AAA894 /* ViewTreeRecordingContextTests.swift in Sources */,
61054FC52A6EE1BA00AAA894 /* UIKitMocks.swift in Sources */,
Expand All @@ -8574,6 +8593,7 @@
61054FD32A6EE1BA00AAA894 /* MultipartFormDataTests.swift in Sources */,
61054FB12A6EE1BA00AAA894 /* NodeIDGeneratorTests.swift in Sources */,
61054F9C2A6EE1BA00AAA894 /* SwiftExtensionsTests.swift in Sources */,
962D72C72CF7815300F86EF0 /* ReflectionMocks.swift in Sources */,
61054FA72A6EE1BA00AAA894 /* NodesFlattenerTests.swift in Sources */,
61054F9D2A6EE1BA00AAA894 /* MainThreadSchedulerTests.swift in Sources */,
61054FAA2A6EE1BA00AAA894 /* UIView+SessionReplayTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-Present Datadog, Inc.
*/

#if os(iOS)
import CoreGraphics

/// Same heuristic as Android to determine if an image is likely bundled:
/// Icons and small assets usually have dimensions <= 100 points.
internal extension CGImage {
func isLikelyBundled(scale: CGFloat) -> Bool {
let pointSize = self.pointSize(scale: scale)
let maxDimension: CGFloat = 100
return pointSize.width <= maxDimension && pointSize.height <= maxDimension
}

private func pointSize(scale: CGFloat) -> CGSize {
return CGSize(width: CGFloat(width) / scale, height: CGFloat(height) / scale)
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ extension DatadogExtension where ExtendedType: UIImage {

/// Compress the image to PNG.
///
/// Scale down the image an apply tint color if necessary.
/// Scale down the image and apply tint color if necessary.
///
/// - Parameters:
/// - maxSize: The maximum size of the image.
Expand All @@ -97,7 +97,7 @@ extension DatadogExtension where ExtendedType: UIImage {

/// Compress an image to PNG.
///
/// Scale down the image an apply tint color if necessary.
/// Scale down the image and apply tint color if necessary.
///
/// - Parameters:
/// - image: The image to compress.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Copyright 2019-Present Datadog, Inc.
*/

#if os(iOS)
#if os(iOS)

import Foundation
import SwiftUI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,9 @@ extension DisplayList.Content.Value: Reflection {
case (.enum("platformView"), _):
self = .platformView

case (.enum("image"), _):
self = .unknown
case (.enum("image"), let graphicsImage):
let resolvedImage = try GraphicsImage(reflecting: graphicsImage)
self = .image(resolvedImage)

case (.enum("drawing"), _):
self = .unknown
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Copyright 2019-Present Datadog, Inc.
*/

#if os(iOS)
#if os(iOS)

import Foundation
import SwiftUI
Expand Down Expand Up @@ -83,6 +83,7 @@ internal struct DisplayList {
case text(StyledTextContentView, CGSize)
case platformView
case color(Color._Resolved)
case image(GraphicsImage)
case unknown
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-Present Datadog, Inc.
*/

#if os(iOS)

import CoreGraphics
import SwiftUI

@available(iOS 13.0, tvOS 13.0, *)
extension GraphicsImage: Reflection {
init(_ mirror: ReflectionMirror) throws {
scale = try mirror.descendant("scale")
orientation = try mirror.descendant("orientation")
contents = try mirror.descendant("contents")
}
}

@available(iOS 13.0, tvOS 13.0, *)
extension GraphicsImage.Contents: Reflection {
init(_ mirror: ReflectionMirror) throws {
switch (mirror.displayStyle, mirror.descendant(0)) {
case let (.enum("cgImage"), cgImage as CGImage):
self = .cgImage(cgImage)
default:
self = .unknown
mariedm marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-Present Datadog, Inc.
*/

#if os(iOS)
import CoreGraphics
import SwiftUI

/// Represents a SwiftUI.GraphicsImage
@available(iOS 13.0, tvOS 13.0, *)
internal struct GraphicsImage {
let contents: Contents
let scale: CGFloat
let orientation: SwiftUI.Image.Orientation

enum Contents {
case cgImage(CGImage)
case unknown
}
}

/// Mapping SwiftUI orientation to UIImage orientation
@available(iOS 13.0, tvOS 13.0, *)
internal extension UIImage.Orientation {
init(_ orientation: SwiftUI.Image.Orientation) {
switch orientation {
case .up: self = UIImage.Orientation.up
case .down: self = .down
case .left: self = .left
case .right: self = .right
case .upMirrored: self = .upMirrored
case .downMirrored: self = .downMirrored
case .leftMirrored: self = .leftMirrored
case .rightMirrored: self = .rightMirrored
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,43 @@ internal struct SwiftUIWireframesBuilder: NodeWireframesBuilder {
cornerRadius: viewInfo?.cornerRadius,
opacity: viewInfo?.alpha
)
case let .image(resolvedImage):
switch resolvedImage.contents {
case .cgImage(let cgImage):
// TODO: RUM-7462 - Apply global privacy setting
// TODO: RUM-7370 - Apply FGM overrides
if cgImage.isLikelyBundled(scale: resolvedImage.scale) {
let imageResource = UIImageResource(
image: UIImage(
cgImage: cgImage,
scale: resolvedImage.scale,
orientation: .init(resolvedImage.orientation)
),
tintColor: nil
)
return context.builder.createImageWireframe(
id: Int64(content.seed.value),
resource: imageResource,
frame: context.convert(frame: item.frame),
clip: context.clip
)
} else {
return context.builder.createPlaceholderWireframe(
id: Int64(content.seed.value),
frame: item.frame,
clip: context.clip,
label: "Content Image"
)
}
case .unknown:
return context.builder.createPlaceholderWireframe(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could send some telemetry to gather which image types we don't support.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have a ticket for adding telemetry 👍

id: Int64(content.seed.value),
frame: item.frame,
clip: context.clip,
label: "Unsupported image type"
)
}

case .platformView:
return nil // Should be recorder by UIKit recorder
case .unknown:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Copyright 2019-Present Datadog, Inc.
*/

#if os(iOS)
#if os(iOS)

import Foundation
import SwiftUI
Expand Down
Loading