Skip to content

Commit

Permalink
RUM-7176 Record SwiftUI raster images
Browse files Browse the repository at this point in the history
  • Loading branch information
mariedm committed Nov 28, 2024
1 parent b90f78c commit f5e26dc
Show file tree
Hide file tree
Showing 13 changed files with 385 additions and 7 deletions.
24 changes: 24 additions & 0 deletions Datadog/Datadog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,12 @@
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 */; };
962D72C12CF7541A00F86EF0 /* SwfitUIImage+SessionReplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 962D72C02CF7541A00F86EF0 /* SwfitUIImage+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 @@ -2754,6 +2760,12 @@
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>"; };
962D72C02CF7541A00F86EF0 /* SwfitUIImage+SessionReplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwfitUIImage+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 @@ -3633,6 +3645,7 @@
61054F592A6EE1BA00AAA894 /* Recorder */,
61054F3E2A6EE1B900AAA894 /* Utilities */,
61054F492A6EE1BA00AAA894 /* Writer */,
962D72C42CF7806300F86EF0 /* ImageReflectionTests.swift */,
);
name = DatadogSessionReplayTests;
path = ../DatadogSessionReplay/Tests;
Expand Down Expand Up @@ -3675,6 +3688,8 @@
isa = PBXGroup;
children = (
61054E142A6EE10A00AAA894 /* UIImage+SessionReplay.swift */,
962D72BE2CF7538800F86EF0 /* CGImage+SessionReplay.swift */,
962D72C02CF7541A00F86EF0 /* SwfitUIImage+SessionReplay.swift */,
D22442C42CA301DA002E71E4 /* UIColor+SessionReplay.swift */,
61054E152A6EE10A00AAA894 /* UIView+SessionReplay.swift */,
61054E162A6EE10A00AAA894 /* CFType+Safety.swift */,
Expand Down Expand Up @@ -4052,6 +4067,7 @@
A74A72862B10CE4100771FEB /* ResourceMocks.swift */,
A74A72882B10D95D00771FEB /* MultipartBuilderSpy.swift */,
A7D952892B28BD94004C79B1 /* ResourceProcessorSpy.swift */,
962D72C62CF7815300F86EF0 /* ReflectionMocks.swift */,
);
path = Mocks;
sourceTree = "<group>";
Expand Down Expand Up @@ -6462,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 @@ -8450,13 +8468,15 @@
61054E6B2A6EE10A00AAA894 /* CFType+Safety.swift in Sources */,
61054E852A6EE10A00AAA894 /* UISegmentRecorder.swift in Sources */,
61054E982A6EE10A00AAA894 /* RecordsBuilder.swift in Sources */,
962D72C12CF7541A00F86EF0 /* SwfitUIImage+SessionReplay.swift in Sources */,
61054EA12A6EE10B00AAA894 /* MainThreadScheduler.swift in Sources */,
61054E7C2A6EE10A00AAA894 /* UINavigationBarRecorder.swift in Sources */,
96E414142C2AF56F005A6119 /* UIProgressViewRecorder.swift in Sources */,
962C41A72CA431370050B747 /* SessionReplayPrivacyOverrides.swift in Sources */,
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 @@ -8469,8 +8489,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 @@ -8551,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 @@ -8570,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 renderedSize = self.renderedSize(scale: scale)
let maxDimension: CGFloat = 100
return renderedSize.width <= maxDimension && renderedSize.height <= maxDimension
}

fileprivate func renderedSize(scale: CGFloat) -> CGSize {
return CGSize(width: CGFloat(width) / scale, height: CGFloat(height) / scale)
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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 SwiftUI

/// Mapping SwiftUI orientation to UIImage orientation
@available(iOS 13.0, tvOS 13.0, *)
internal extension SwiftUI.Image.Orientation {
var uiImageOrientation: UIImage.Orientation {
switch self {
case .up: return .up
case .down: return .down
case .left: return .left
case .right: return .right
case .upMirrored: return .upMirrored
case .downMirrored: return .downMirrored
case .leftMirrored: return .leftMirrored
case .rightMirrored: return .rightMirrored
}
}
}
#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 @@ -126,8 +126,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 @@ -80,6 +80,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
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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
}
}

@available(iOS 13.0, tvOS 13.0, *)
extension GraphicsImage.Contents: Equatable {
static func == (lhs: GraphicsImage.Contents, rhs: GraphicsImage.Contents) -> Bool {
switch (lhs, rhs) {
case let (.cgImage(lImage), .cgImage(rImage)):
return lImage === rImage
case (.unknown, .unknown):
return true
default:
return false
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,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: resolvedImage.orientation.uiImageOrientation
),
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(
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

0 comments on commit f5e26dc

Please sign in to comment.