Skip to content
Draft
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Features

- Add isiOSAppOnVisionOS, isiOSAppOnMac, isMacCatalystApp to device context #6939

## 9.0.0

This changelog lists every breaking change. For a high-level overview and upgrade guidance, see the [migration guide](https://docs.sentry.io/platforms/apple/migration/).
Expand Down
4 changes: 4 additions & 0 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,7 @@
D4D0E1E82E9D040A00358814 /* SentrySessionReplayEnvironmentCheckerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D0E1E22E9D040800358814 /* SentrySessionReplayEnvironmentCheckerTests.swift */; };
D4D12E7A2DFC608800DC45C4 /* SentryScreenshotOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D12E792DFC607F00DC45C4 /* SentryScreenshotOptionsTests.swift */; };
D4DEE6592E439B2E00FCA5A9 /* SentryProfileTimeseriesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D4DEE6582E439B2E00FCA5A9 /* SentryProfileTimeseriesTests.m */; };
D4E1CE6A2EDDCBD900D2EAC1 /* SentryProcessInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E1CE642EDDCBD300D2EAC1 /* SentryProcessInfoTests.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 */; };
D4E9420A2E9D1CFB00DB7521 /* TestSessionReplayEnvironmentChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E942042E9D1CF300DB7521 /* TestSessionReplayEnvironmentChecker.swift */; };
Expand Down Expand Up @@ -2183,6 +2184,7 @@
D4D0E1E22E9D040800358814 /* SentrySessionReplayEnvironmentCheckerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySessionReplayEnvironmentCheckerTests.swift; sourceTree = "<group>"; };
D4D12E792DFC607F00DC45C4 /* SentryScreenshotOptionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryScreenshotOptionsTests.swift; sourceTree = "<group>"; };
D4DEE6582E439B2E00FCA5A9 /* SentryProfileTimeseriesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryProfileTimeseriesTests.m; sourceTree = "<group>"; };
D4E1CE642EDDCBD300D2EAC1 /* SentryProcessInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryProcessInfoTests.swift; sourceTree = "<group>"; };
D4E942042E9D1CF300DB7521 /* TestSessionReplayEnvironmentChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSessionReplayEnvironmentChecker.swift; sourceTree = "<group>"; };
D4E9420B2E9D1D7600DB7521 /* TestSessionReplayEnvironmentCheckerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSessionReplayEnvironmentCheckerTests.swift; sourceTree = "<group>"; };
D4ECA3FF2E3CBEDE00C757EA /* SentryDummyPrivateEmptyClass.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryDummyPrivateEmptyClass.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3596,6 +3598,7 @@
7BD7299B24654CD500EA3610 /* Helper */ = {
isa = PBXGroup;
children = (
D4E1CE642EDDCBD300D2EAC1 /* SentryProcessInfoTests.swift */,
D4A0C22A2E9E3CE100791353 /* InfoPlist */,
F4A930242E661856006DA6EF /* SentryMobileProvisionParserTests.swift */,
D4F7BD7C2E4373BB004A2D77 /* SentryLevelMapperTests.swift */,
Expand Down Expand Up @@ -6284,6 +6287,7 @@
D8019910286B089000C277F0 /* SentryCrashReportSinkTests.swift in Sources */,
D885266427739D01001269FC /* SentryFileIOTrackingIntegrationTests.swift in Sources */,
7BBD18992449DE9D00427C76 /* TestRateLimits.swift in Sources */,
D4E1CE6A2EDDCBD900D2EAC1 /* SentryProcessInfoTests.swift in Sources */,
7B04A9AB24EA5F8D00E710B1 /* SentryUserTests.swift in Sources */,
7BA61CCF247EB59500C130A8 /* SentryCrashUUIDConversionTests.swift in Sources */,
7BBD188D2448453600427C76 /* SentryHttpDateParserTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
#import "SentryDefines.h"

#if TARGET_OS_IOS || TARGET_OS_TV
# define SENTRY_UIKIT_AVAILABLE 1
#else
# define SENTRY_UIKIT_AVAILABLE 0
#endif

#if SENTRY_HAS_UIKIT
# import "SentryAppStartTracker.h"
# import "SentryDefaultUIViewControllerPerformanceTracker.h"
Expand Down
8 changes: 6 additions & 2 deletions SentryTestUtils/Sources/TestDisplayLinkWrapper.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation
@_spi(Private) @testable import Sentry

#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
#if os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst)

public enum GPUFrame {
case normal
Expand All @@ -18,7 +18,7 @@ public enum FrameRate: UInt64 {
}
}

@_spi(Private) public class TestDisplayLinkWrapper: SentryDisplayLinkWrapper, SentryReplayDisplayLinkWrapper {
@_spi(Private) public class TestDisplayLinkWrapper: SentryDisplayLinkWrapper {
public var target: AnyObject!
public var selector: Selector!
public var currentFrameRate: FrameRate = .low
Expand Down Expand Up @@ -146,3 +146,7 @@ public enum FrameRate: UInt64 {
}

#endif

#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
extension TestDisplayLinkWrapper: SentryReplayDisplayLinkWrapper {}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
public var environment: [String: String]?
public var isiOSAppOnMac: Bool?
public var isMacCatalystApp: Bool?
public var isiOSAppOnVisionOS: Bool?
}

public var overrides = Override()
Expand Down Expand Up @@ -45,4 +46,8 @@
public var isMacCatalystApp: Bool {
return overrides.isMacCatalystApp ?? ProcessInfo.processInfo.isMacCatalystApp
}

public var isiOSAppOnVisionOS: Bool {
return overrides.isiOSAppOnVisionOS ?? ProcessInfo.processInfo.isiOSAppOnVisionOS
}
}
2 changes: 1 addition & 1 deletion Sources/Sentry/SentryBuildAppStartSpans.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#import "SentryTracer.h"
#import <SentryBuildAppStartSpans.h>

#if SENTRY_HAS_UIKIT
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION

SentrySpan *
sentryBuildAppStartSpan(
Expand Down
6 changes: 3 additions & 3 deletions Sources/Sentry/SentrySpan.m
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ @implementation SentrySpan {
}

- (instancetype)initWithContext:(SentrySpanContext *)context
#if SENTRY_HAS_UIKIT
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION
framesTracker:(nullable SentryFramesTracker *)framesTracker;
#endif // SENTRY_HAS_UIKIT
{
Expand All @@ -65,7 +65,7 @@ - (instancetype)initWithContext:(SentrySpanContext *)context
getThreadName:currentThread];
}

#if SENTRY_HAS_UIKIT
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION
_framesTracker = framesTracker;
if (_framesTracker.isRunning) {
SentryScreenFrames *currentFrames = _framesTracker.currentFrames;
Expand Down Expand Up @@ -137,7 +137,7 @@ - (void)stopObservingContinuousProfiling

- (instancetype)initWithTracer:(SentryTracer *)tracer
context:(SentrySpanContext *)context
#if SENTRY_HAS_UIKIT
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION
framesTracker:(nullable SentryFramesTracker *)framesTracker
{
if (self = [self initWithContext:context framesTracker:framesTracker]) {
Expand Down
20 changes: 10 additions & 10 deletions Sources/Sentry/SentryTracer.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
# import "SentryProfiledTracerConcurrency.h"
#endif // SENTRY_TARGET_PROFILING_SUPPORTED

#if SENTRY_HAS_UIKIT
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION
# import "SentryAppStartMeasurement.h"
# import "SentryBuildAppStartSpans.h"
#endif // SENTRY_HAS_UIKIT
Expand All @@ -39,7 +39,7 @@

static const void *spanTimestampObserver = &spanTimestampObserver;

#if SENTRY_HAS_UIKIT
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION
/**
* The maximum amount of seconds the app start measurement end time and the start time of the
* transaction are allowed to be apart.
Expand Down Expand Up @@ -114,7 +114,7 @@ - (instancetype)initWithTransactionContext:(SentryTransactionContext *)transacti
configuration:(SentryTracerConfiguration *)configuration;
{
if (!(self = [super initWithContext:transactionContext
#if SENTRY_HAS_UIKIT
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION
framesTracker:SentryDependencyContainer.sharedInstance.framesTracker
#endif // SENTRY_HAS_UIKIT
])) {
Expand Down Expand Up @@ -390,7 +390,7 @@ - (BOOL)isAutoGeneratedTransaction
SentrySpan *child =
[[SentrySpan alloc] initWithTracer:self
context:context
#if SENTRY_HAS_UIKIT
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION
framesTracker:SentryDependencyContainer.sharedInstance.framesTracker
#endif // SENTRY_HAS_UIKIT
];
Expand Down Expand Up @@ -615,13 +615,13 @@ - (BOOL)finishTracer:(SentrySpanStatus)unfinishedSpansFinishStatus shouldCleanUp
}
[super finishWithStatus:_finishStatus];
}
#if SENTRY_HAS_UIKIT
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION
appStartMeasurement = [self getAppStartMeasurement];

if (appStartMeasurement != nil) {
[self updateStartTime:appStartMeasurement.appStartTimestamp];
}
#endif // SENTRY_HAS_UIKIT
#endif // SENTRY_HAS_UIKIT && !TARGET_OS_VISION

if (shouldCleanUp) {
[self.delegate tracerDidFinish:self];
Expand Down Expand Up @@ -721,7 +721,7 @@ - (SentryTransaction *)toTransaction
{

NSUInteger capacity;
#if SENTRY_HAS_UIKIT
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION
[self addFrameStatistics];

NSArray<id<SentrySpan>> *appStartSpans = sentryBuildAppStartSpans(self, appStartMeasurement);
Expand All @@ -736,7 +736,7 @@ - (SentryTransaction *)toTransaction
[spans addObjectsFromArray:_children];
}

#if SENTRY_HAS_UIKIT
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION
[spans addObjectsFromArray:appStartSpans];
#endif // SENTRY_HAS_UIKIT

Expand All @@ -763,7 +763,7 @@ - (SentryTransaction *)toTransaction
[debugImageProvider getDebugImagesFromCacheForFrames:framesOfAllSpans];
}

#if SENTRY_HAS_UIKIT
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION
[self addAppStartMeasurements:transaction];

if ([viewNames count] > 0) {
Expand All @@ -774,7 +774,7 @@ - (SentryTransaction *)toTransaction
return transaction;
}

#if SENTRY_HAS_UIKIT
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION

- (nullable SentryAppStartMeasurement *)getAppStartMeasurement SENTRY_DISABLE_THREAD_SANITIZER(
"double-checked lock produce false alarms")
Expand Down
4 changes: 2 additions & 2 deletions Sources/Sentry/include/SentrySpan.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ SENTRY_NO_INIT
*/
- (instancetype)initWithTracer:(SentryTracer *)transaction
context:(SentrySpanContext *)context
#if SENTRY_HAS_UIKIT
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION
framesTracker:(nullable SentryFramesTracker *)framesTracker;
#endif // SENTRY_HAS_UIKIT
;
Expand All @@ -106,7 +106,7 @@ SENTRY_NO_INIT
* @param context This span context information.
*/
- (instancetype)initWithContext:(SentrySpanContext *)context
#if SENTRY_HAS_UIKIT
#if SENTRY_HAS_UIKIT && !TARGET_OS_VISION
framesTracker:(nullable SentryFramesTracker *)framesTracker;
#endif // SENTRY_HAS_UIKIT
;
Expand Down
20 changes: 20 additions & 0 deletions Sources/Swift/Helper/SentryProcessInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

@available(macOS 12.0, *)
var isMacCatalystApp: Bool { get }

var isiOSAppOnVisionOS: Bool { get }
}

// This is needed because a file that only contains an @objc extension will get automatically stripped out
Expand All @@ -26,4 +28,22 @@
public var processPath: String? {
Bundle.main.executablePath
}

public var isiOSAppOnVisionOS: Bool {
if #available(iOS 26.1, visionOS 26.1, *) {
// Use official API when available
// https://developer.apple.com/documentation/foundation/processinfo/isiosapponvision
//
// For unknown reasons when running an iOS app "Designed for iPad" on visionOS 1.1, the simulator system
// version is simulator 17.4, but it still enters this block.
//
// Due to that it crashes with an uncaught exception 'NSInvalidArgumentException', reason: '-[NSProcessInfo isiOSAppOnVision]: unrecognized selector sent to instance 0x600001549230'
if self.responds(to: NSSelectorFromString("isiOSAppOnVision")) {
return self.isiOSAppOnVision
}
}
// Fallback for older versions: `UIWindowSceneGeometryPreferencesVision` is only available on visionOS
// https://developer.apple.com/documentation/uikit/uiwindowscene/geometrypreferences/vision?language=objc
return NSClassFromString("UIWindowSceneGeometryPreferencesVision") != nil
}
}
9 changes: 8 additions & 1 deletion Sources/Swift/SentryCrash/SentryCrashWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,13 @@ public final class SentryCrashWrapper: NSObject {

deviceData["locale"] = Locale.autoupdatingCurrent.identifier

if #available(macOS 12, *) {
deviceData["ios_app_on_macos"] = self.processInfoWrapper.isiOSAppOnMac
deviceData["mac_catalyst_app"] = self.processInfoWrapper.isMacCatalystApp
}

deviceData["ios_app_on_visionos"] = self.processInfoWrapper.isiOSAppOnVisionOS

// Set screen dimensions if available
setScreenDimensions(&deviceData)

Expand Down Expand Up @@ -210,7 +217,7 @@ public final class SentryCrashWrapper: NSObject {

if self.processInfoWrapper.isMacCatalystApp {
runtimeContext["name"] = "Mac Catalyst App"
runtimeContext["raw_description"] = "raw_description"
runtimeContext["raw_description"] = "mac-catalyst-app"
}
}

Expand Down
18 changes: 18 additions & 0 deletions Tests/SentryTests/Helper/SentryDeviceTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ - (void)testCPUArchitecture
// TODO: create a watch UI test target to test this branch as it cannot run on the watch
// simulator
SENTRY_ASSERT_CONTAINS(arch, @"arm"); // Real ο£ΏWatches
#elif TARGET_OS_VISION
# if TARGET_OS_SIMULATOR
# if TARGET_CPU_ARM64
SENTRY_ASSERT_CONTAINS(arch, @"arm"); // Vision Pro simulator on M1 macs
# elif TARGET_CPU_X86_64
SENTRY_ASSERT_CONTAINS(arch, @"x86"); // Vision Pro simulator on Intel macs
# else
XCTFail(@"Unexpected CPU type on test host.");
# endif // TARGET_CPU_ARM64
# else
SENTRY_ASSERT_CONTAINS(arch, @"arm"); // Real Vision Pro devices
# endif
#else
XCTFail(@"Unexpected device OS");
#endif
Expand Down Expand Up @@ -127,6 +139,8 @@ - (void)testOSName
#elif TARGET_OS_WATCH
// TODO: create a watch UI test target to test this branch
SENTRY_ASSERT_EQUAL(osName, @"watchOS");
#elif TARGET_OS_VISION
SENTRY_ASSERT_EQUAL(osName, @"visionOS");
#else
XCTFail(@"Unexpected device OS");
#endif
Expand Down Expand Up @@ -161,6 +175,8 @@ - (void)testDeviceModel
// TODO: create a watch UI test target to test this branch as it cannot run on the watch
// simulator
SENTRY_ASSERT_CONTAINS(modelName, @"Watch");
#elif TARGET_OS_VISION
SENTRY_ASSERT_CONTAINS(modelName, @"RealityDevice");
#else
XCTFail(@"Unexpected target OS");
#endif
Expand Down Expand Up @@ -205,6 +221,8 @@ - (void)testSimulatedDeviceModel
// TODO: create a watch UI test target to test this branch as it cannot run on the watch
// simulator
SENTRY_ASSERT_CONTAINS(modelName, @"Watch");
# elif TARGET_OS_VISION
SENTRY_ASSERT_CONTAINS(modelName, @"RealityDevice");
# else
XCTFail(@"Unexpected device OS");
# endif
Expand Down
20 changes: 20 additions & 0 deletions Tests/SentryTests/Helper/SentryProcessInfoTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@_spi(Private) @testable import Sentry
import XCTest

final class SentryProcessInfoTests: XCTestCase {

func testIsiOSAppOnVisionOS() throws {
// -- Arrange --
let processInfo = ProcessInfo.processInfo

// -- Act --
let result = processInfo.isiOSAppOnVisionOS

// -- Assert --
// This test only asserts that the property exists, as we can not adapt the process info in tests
// and a test running on visionOS is also not an iOS app.
//
// We asserted this manually by running iOS-Swift on visionOS, then exploring the data in `lldb`
XCTAssertFalse(result, "isiOSAppOnVisionOS should be false when not running on visionOS")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import Foundation
@_spi(Private) import SentryTestUtils
import XCTest

#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)

class TestDelayedWrapper: SentryDelayedFramesTracker {}

#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
class SentryTimeToDisplayTrackerTest: XCTestCase {

private class Fixture {
Expand Down
Loading