Skip to content

Conversation

@itaybre
Copy link
Contributor

@itaybre itaybre commented Nov 2, 2025

📜 Description

Improve check for view opaqueness

💡 Motivation and Context

User's (or libraries) can override UIView's backgroundColor and report a report different value (example).
Since UIView are wrappers around CALayer. we should consider using the actual layers properties too.

This is draft because I have to verify this actually works

💚 How did you test it?

Run the app with Session Replay enabled and view the output.
Still needs more tests with popups and custom views

📝 Checklist

You have to check all boxes before merging:

  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

Closes #6634

philprime and others added 15 commits October 28, 2025 16:13
Added expectations to verify the animation state during redaction, ensuring the position of the masked region is within the expected range during animation. This improves the robustness of the edge case tests for UI redaction.
…se tests

Updated the animation duration and expectations in the edge case tests for UI redaction. The assertions now verify that the position of the masked region is approximately at the midpoint of the animation, enhancing the accuracy of the tests.
@codecov
Copy link

codecov bot commented Nov 2, 2025

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
3931 1 3930 60
View the top 2 failed test(s) by shortest run time
SentryCrashBinaryImageCacheTests::testRemoveImageAddAgain
Stack Traces | 0s run time
.../SentryTests/SentryCrash/SentryCrashBinaryImageCacheTests.m:317 - *** -[__NSArrayM objectAtIndexedSubscript:]: index 4 beyond bounds [0 .. 3] (NSRangeException)
iOS_SwiftUI_UITests.LaunchUITests::testNoNewTransactionForSecondCallToBody
Stack Traces | 0s run time
.../iOS-SwiftUI/iOS-SwiftUI-UITests/LaunchUITests.swift:39 - Failed to get matching snapshot: Timed out while evaluating UI query.

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 2, 2025

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1228.73 ms 1264.84 ms 36.10 ms
Size 23.75 KiB 1023.84 KiB 1000.09 KiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
9cbff30 1236.33 ms 1254.35 ms 18.03 ms
52603c5 1229.35 ms 1251.82 ms 22.47 ms
b57ee62 1218.21 ms 1248.94 ms 30.73 ms
ea5a59b 1222.87 ms 1253.47 ms 30.60 ms
449d185 1216.31 ms 1251.94 ms 35.62 ms
a9fac2e 1212.45 ms 1219.67 ms 7.22 ms
f5d202b 1237.90 ms 1259.49 ms 21.59 ms
884b224 1233.41 ms 1259.50 ms 26.09 ms
7cc23cf 1203.15 ms 1232.11 ms 28.96 ms
4e3915a 1230.02 ms 1258.90 ms 28.88 ms

App size

Revision Plain With Sentry Diff
9cbff30 23.75 KiB 928.15 KiB 904.40 KiB
52603c5 23.75 KiB 969.66 KiB 945.92 KiB
b57ee62 23.75 KiB 912.47 KiB 888.72 KiB
ea5a59b 23.75 KiB 874.46 KiB 850.71 KiB
449d185 23.75 KiB 980.81 KiB 957.06 KiB
a9fac2e 23.75 KiB 879.53 KiB 855.78 KiB
f5d202b 23.75 KiB 904.53 KiB 880.78 KiB
884b224 23.75 KiB 879.60 KiB 855.86 KiB
7cc23cf 23.75 KiB 913.62 KiB 889.87 KiB
4e3915a 23.75 KiB 858.69 KiB 834.94 KiB

Previous results on branch: itay/improve_opaque_logic

Startup times

Revision Plain With Sentry Diff
8858a16 1225.98 ms 1255.96 ms 29.98 ms
6b1198e 1212.35 ms 1245.35 ms 32.99 ms
27fdaa1 1232.08 ms 1265.20 ms 33.11 ms
65af6ea 1219.83 ms 1250.20 ms 30.37 ms

App size

Revision Plain With Sentry Diff
8858a16 23.75 KiB 1.01 MiB 1016.01 KiB
6b1198e 23.75 KiB 1023.93 KiB 1000.19 KiB
27fdaa1 23.75 KiB 1.02 MiB 1019.46 KiB
65af6ea 23.75 KiB 1.02 MiB 1016.57 KiB

@philprime philprime changed the title fix: Include layer background color when checkig if a view is opaque fix(session-replay): Include layer background color when checkig if a view is opaque Nov 3, 2025
@philprime
Copy link
Member

philprime commented Nov 3, 2025

I followed up the investigation and confirm the issue and the fix:

  1. To cause the original issue, add the Orderella/PopupDialog as a dependency
  2. Then create a new popup like this in any view controller, e.g in the viewDidAppear(_:):
let popup = PopupDialog(
    title: "THIS IS THE DIALOG TITLE",
    message: "This is the message section of the popup dialog default view",
)

let overlayAppearance = PopupDialogOverlayView.appearance()
overlayAppearance.color           = .red
overlayAppearance.blurEnabled     = false
overlayAppearance.liveBlurEnabled = false
overlayAppearance.opacity         = 0.2

self.present(popup, animated: true) {
    print(self.view.window!.value(forKey: "recursiveDescription")!)
}
  1. It will overlay the view controller with a faint red tint:
783854750 454676
(lldb) po self.view.window!.value(forKey: "recursiveDescription")!
<UIWindow: 0x10371f540; frame = (0 0; 402 874); gestureRecognizers = <NSArray: 0x600000c74210>; backgroundColor = <UIDynamicSystemColor: 0x600001761040; name = _windowBackgroundColor>; layer = <UIWindowLayer: 0x600001765300>>
   | <UITransitionView: 0x10374b010; frame = (0 0; 402 874); autoresize = W+H; layer = <CALayer: 0x600000c8e4c0>>
   |    | <UIDropShadowView: 0x10374b500; frame = (0 0; 402 874); autoresize = W+H; layer = <CALayer: 0x600000c8db30>>
   |    |    | <UILayoutContainerView: 0x10320c560; frame = (0 0; 402 874); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x600000022f00>; layer = <CALayer: 0x600000c86940>>
   |    |    |    | <UINavigationTransitionView: 0x1037490b0; frame = (0 0; 402 874); autoresize = W+H; layer = <CALayer: 0x600000c8cf30>>
   |    |    |    |    | <UIViewControllerWrapperView: 0x103741ec0; frame = (0 0; 402 874); autoresize = W+H; layer = <CALayer: 0x600000c6edf0>>
   |    |    |    |    |    | <UILayoutContainerView: 0x103724320; frame = (0 0; 402 874); autoresize = W+H; backgroundColor = <UIDynamicSystemColor: 0x60000176c700; name = systemBackgroundColor>; layer = <CALayer: 0x600000c6ed60>>
   |    |    |    |    |    |    | <UITransitionView: 0x1037076d0; frame = (0 0; 402 874); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x600000c6e9a0>>
   |    |    |    |    |    |    |    | <UIViewControllerWrapperView: 0x1032a62b0; frame = (0 0; 402 874); autoresize = W+H; layer = <CALayer: 0x600000c96430>>
   |    |    |    |    |    |    |    |    | <UIView: 0x1032946e0; frame = (0 0; 402 874); autoresize = W+H; backgroundColor = <UIDynamicSystemColor: 0x60000176c700; name = systemBackgroundColor>; layer = <CALayer: 0x600000c96760>>
   |    |    |    |    |    |    |    |    |    | <UIStackView: 0x103298000; frame = (0 100.333; 402 274.333); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x600000c964f0>> axis=vert distribution=fill alignment=fill
   |    |    |    |    |    |    |    |    |    |    | <UIStackView: 0x10329c690; frame = (0 0; 402 146.333); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x600000c96730>> axis=horiz distribution=fillEqually alignment=fill
   |    |    |    |    |    |    |    |    |    |    |    | <UIStackView: 0x1032a2a80; frame = (0 0; 201 146.333); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x600000c96220>> axis=vert distribution=equalSpacing alignment=fill
   |    |    |    |    |    |    |    |    |    |    |    |    | <UIButton: 0x1032a2c40; frame = (0 0; 201 28); opaque = NO; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x6000002cbf40>; layer = <CALayer: 0x600000c95ce0>>
   |    |    |    |    |    |    |    |    |    |    |    |    |    | <UIButtonLabel: 0x1032a9730; frame = (59.6667 6; 82 16); text = 'Capture Error'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600002c5b680>>
   |    |    |    |    |    |    |    |    |    |    |    |    | <UIButton: 0x1032a3190; frame = (0 29.6667; 201 28); opaque = NO; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x6000002cd420>; layer = <CALayer: 0x600000c969d0>>
   |    |    |    |    |    |    |    |    |    |    |    |    |    | <UIButtonLabel: 0x1032a93f0; frame = (35.6667 6; 130 16); text = 'Capture NSException'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600002c5b580>>
   |    |    |    |    |    |    |    |    |    |    |    |    | <UIButton: 0x10371b640; frame = (0 59.3333; 201 28); opaque = NO; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x6000002b9740>; layer = <CALayer: 0x600000c6e550>>
   |    |    |    |    |    |    |    |    |    |    |    |    |    | <UIButtonLabel: 0x1032a90b0; frame = (50 6; 101 16); text = 'Throw FatalError'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600002c5ac80>>
   |    |    |    |    |    |    |    |    |    |    |    |    | <UIButton: 0x1032a3480; frame = (0 88.6667; 201 28); opaque = NO; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x6000002a4f80>; layer = <CALayer: 0x600000c96c10>>
   |    |    |    |    |    |    |    |    |    |    |    |    |    | <UIButtonLabel: 0x1032a8c40; frame = (25.6667 6; 150 16); text = 'Fatal Duplicate Key Error'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600002c5b400>>
   |    |    |    |    |    |    |    |    |    |    |    |    | <UIButton: 0x103613d20; frame = (0 118.333; 201 28); opaque = NO; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x6000002cece0>; layer = <CALayer: 0x600000c82100>>
   |    |    |    |    |    |    |    |    |    |    |    |    |    | <UIButtonLabel: 0x1032a6e50; frame = (66 6; 69 16); text = 'OOM crash'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600002c5ab80>>
   |    |    |    |    |    |    |    |    |    |    |    | <UIStackView: 0x10371adc0; frame = (201 0; 201 146.333); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x600000c6cbd0>> axis=vert distribution=equalSpacing alignment=fill
   |    |    |    |    |    |    |    |    |    |    |    |    | <UIButton: 0x103739ef0; frame = (0 0; 201 28); opaque = NO; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x6000002b9920>; layer = <CALayer: 0x600000c6cc60>>
   |    |    |    |    |    |    |    |    |    |    |    |    |    | <UIButtonLabel: 0x1032a7d10; frame = (33 6; 135 16); text = 'Force unwrap optional'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600002c5b000>>
   |    |    |    |    |    |    |    |    |    |    |    |    | <UIButton: 0x1032a3770; frame = (0 28; 201 28); opaque = NO; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x6000002a5b00>; layer = <CALayer: 0x600000c96d30>>
   |    |    |    |    |    |    |    |    |    |    |    |    |    | <UIButtonLabel: 0x1032a79d0; frame = (41.6667 6; 118 16); text = 'DiskWriteException'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600002c5af00>>
   |    |    |    |    |    |    |    |    |    |    |    |    | <UIButton: 0x10361b970; frame = (0 56; 201 28); opaque = NO; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x6000002cfa60>; layer = <CALayer: 0x600000c82310>>
   |    |    |    |    |    |    |    |    |    |    |    |    |    | <UIButtonLabel: 0x1032a7690; frame = (57.6667 6; 86 16); text = 'Crash the app'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600002c5ae00>>
   |    |    |    |    |    |    |    |    |    |    |    |    | <UIButton: 0x1037079e0; frame = (0 84; 201 28); opaque = NO; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x6000002bba80>; layer = <CALayer: 0x600000c6d380>>
   |    |    |    |    |    |    |    |    |    |    |    |    |    | <UIButtonLabel: 0x1032a7190; frame = (21.6667 6; 158 16); text = 'Unhandled C++ Exception'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600002c5ad00>>
   |    |    |    |    |    |    |    |    |    |    |    |    | <UIButton: 0x1032a3a60; frame = (0 112; 201 34.3333); opaque = NO; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x6000002cbde0>; layer = <CALayer: 0x600000c96f70>> configuration=<UIButtonConfiguration: 0x1032a5480> baseStyle=plain macStyle=automatic buttonSize=medium cornerStyle=dynamic title=<0x6000002b4020>:'Use-after-free' contentInsets=default imagePlacement=leading imagePadding=0 titlePadding=1 titleAlignment=automatic automaticallyUpdateForSelection background=<UIBackgroundConfiguration: 0x600003b45340; Base Style = Custom; cornerRadius = 5.95>
   |    |    |    |    |    |    |    |    |    |    |    |    |    | <_UISystemBackgroundView: 0x1032a4e90; frame = (0 0; 201 34.3333); userInteractionEnabled = NO; layer = <CALayer: 0x600000c97180>; configuration = <UIBackgroundConfiguration: 0x600003b45420; Base Style = Custom; cornerRadius = 6>>
   |    |    |    |    |    |    |    |    |    |    |    |    |    | <UILabel: 0x1032a4760; frame = (45.3333 7; 110 20.3333); text = 'Use-after-free'; userInteractionEnabled = NO; backgroundColor = UIExtendedGrayColorSpace 0 0; layer = <_UILabelLayer: 0x600002c59f80>>
   |    |    |    |    |    |    |    |    |    |    | <UIImageView: 0x10320e350; frame = (0 146.333; 402 128); clipsToBounds = YES; autoresize = RM+BM; userInteractionEnabled = NO; image = <(null):0x0 (null) anonymous; (0 0)@0>; layer = <CALayer: 0x600000c95cb0>>
   |    |    |    |    |    |    | <UITabBar: 0x1038067e0; frame = (0 791; 402 83); autoresize = W+TM; gestureRecognizers = <NSArray: 0x60000000a520>; backgroundColor = UIExtendedGrayColorSpace 0 0; layer = <CALayer: 0x600000c19a40>>
   |    |    |    |    |    |    |    | <_UIBarBackground: 0x1037387c0; frame = (0 0; 402 83); userInteractionEnabled = NO; layer = <CALayer: 0x600000c77600>>
   |    |    |    |    |    |    |    |    | <UIVisualEffectView: 0x10362d330; frame = (0 0; 402 83); alpha = 0; layer = <CALayer: 0x600000c832a0>> effect=none
   |    |    |    |    |    |    |    |    |    | <_UIVisualEffectBackdropView: 0x10362db60; frame = (0 0; 402 83); autoresize = W+H; userInteractionEnabled = NO; layer = <UICABackdropLayer: 0x600001786400>>
   |    |    |    |    |    |    |    |    | <_UIBarBackgroundShadowView: 0x10362d550; frame = (0 -0.333333; 402 0.333333); layer = <CALayer: 0x600000c83390>> clientRequestedContentView effect=none
   |    |    |    |    |    |    |    |    |    | <_UIBarBackgroundShadowContentImageView: 0x10362df30; frame = (0 0; 402 0.333333); alpha = 0; autoresize = W+H; userInteractionEnabled = NO; backgroundColor = <UIDynamicSystemColor: 0x6000017a0100; name = _systemChromeShadowColor>; image = <(null):0x0 (null) anonymous; (0 0)@0>; layer = <CALayer: 0x600000c83930>>
   |    |    |    |    |    |    |    | <UITabBarButton: 0x103605010; frame = (2 1; 76 48); opaque = NO; layer = <CALayer: 0x600000c0e940>>
   |    |    |    |    |    |    |    |    | <UIVisualEffectView: 0x10332a070; frame = (0 0; 76 48); userInteractionEnabled = NO; layer = <CALayer: 0x600000c79680>> clientRequestedContentView effect=<UIVibrancyEffect: 0x600000011030> style=UIBlurEffectStyleSystemChromeMaterial vibrancyStyle=UIVibrancyEffectStyleFill
   |    |    |    |    |    |    |    |    |    | <_UIVisualEffectContentView: 0x10332a3c0; frame = (0 0; 76 48); autoresize = W+H; tintColor = UIExtendedGrayColorSpace 1 1; layer = <CALayer: 0x600000c79830>> disablesGroupFiltering
   |    |    |    |    |    |    |    |    | <UITabBarSwappableImageView: 0x103617540; frame = (23.3333 4.33333; 28.6667 26); opaque = NO; userInteractionEnabled = NO; image = <UIImage:0x60000300d050 symbol "exclamationmark.triangle.fill"; (29 26)@3{2}>; layer = <CALayer: 0x600000c0ee80>>
   |    |    |    |    |    |    |    |    | <UITabBarButtonLabel: 0x103612050; frame = (22.6667 34.3333; 30.3333 12); text = 'Errors'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600002c0f880>>
   |    |    |    |    |    |    |    | <UITabBarButton: 0x10371eee0; frame = (82 1; 77 48); opaque = NO; layer = <CALayer: 0x600000c4c240>>
   |    |    |    |    |    |    |    |    | <UIVisualEffectView: 0x1033289e0; frame = (0 0; 77 48); userInteractionEnabled = NO; layer = <CALayer: 0x600000c79110>> clientRequestedContentView effect=<UIVibrancyEffect: 0x600000011030> style=UIBlurEffectStyleSystemChromeMaterial vibrancyStyle=UIVibrancyEffectStyleFill
   |    |    |    |    |    |    |    |    |    | <_UIVisualEffectContentView: 0x103328d30; frame = (0 0; 77 48); autoresize = W+H; tintColor = UIExtendedGrayColorSpace 1 1; layer = <CALayer: 0x600000c792c0>> disablesGroupFiltering
   |    |    |    |    |    |    |    |    |    |    | <UITabBarSwappableImageView: 0x10371f8d0; frame = (24.6667 4; 27.3333 27.3333); opaque = NO; userInteractionEnabled = NO; image = <UIImage:0x60000300eb50 symbol "clock.fill"; (27 27)@3{2}>; layer = <CALayer: 0x600000c4c450>>
   |    |    |    |    |    |    |    |    |    |    | <UITabBarButtonLabel: 0x10371f1d0; frame = (6.66667 34.3333; 63.6667 12); text = 'Transactions'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600002c38880>>
   |    |    |    |    |    |    |    | <UITabBarButton: 0x103724960; frame = (163 1; 76 48); opaque = NO; layer = <CALayer: 0x600000c03a80>>
   |    |    |    |    |    |    |    |    | <UIVisualEffectView: 0x103327630; frame = (0 0; 76 48); userInteractionEnabled = NO; layer = <CALayer: 0x600000c78ba0>> clientRequestedContentView effect=<UIVibrancyEffect: 0x600000011030> style=UIBlurEffectStyleSystemChromeMaterial vibrancyStyle=UIVibrancyEffectStyleFill
   |    |    |    |    |    |    |    |    |    | <_UIVisualEffectContentView: 0x103327980; frame = (0 0; 76 48); autoresize = W+H; tintColor = UIExtendedGrayColorSpace 1 1; layer = <CALayer: 0x600000c78d50>> disablesGroupFiltering
   |    |    |    |    |    |    |    |    |    |    | <UITabBarSwappableImageView: 0x103725220; frame = (24 4; 27.3333 27.3333); opaque = NO; userInteractionEnabled = NO; image = <UIImage:0x60000300f8d0 symbol "ellipsis.circle.fill"; (27 27)@3{2}>; layer = <CALayer: 0x600000c03810>>
   |    |    |    |    |    |    |    |    |    |    | <UITabBarButtonLabel: 0x103724c50; frame = (25 34.3333; 25.6667 12); text = 'Extra'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600002c39800>>
   |    |    |    |    |    |    |    | <UITabBarButton: 0x103726e20; frame = (243 1; 77 48); opaque = NO; layer = <CALayer: 0x600000c72850>>
   |    |    |    |    |    |    |    |    | <UIVisualEffectView: 0x103326280; frame = (0 0; 77 48); userInteractionEnabled = NO; layer = <CALayer: 0x600000c78630>> clientRequestedContentView effect=<UIVibrancyEffect: 0x600000011030> style=UIBlurEffectStyleSystemChromeMaterial vibrancyStyle=UIVibrancyEffectStyleFill
   |    |    |    |    |    |    |    |    |    | <_UIVisualEffectContentView: 0x1033265d0; frame = (0 0; 77 48); autoresize = W+H; tintColor = UIExtendedGrayColorSpace 1 1; layer = <CALayer: 0x600000c787e0>> disablesGroupFiltering
   |    |    |    |    |    |    |    |    |    |    | <UITabBarSwappableImageView: 0x10372eb90; frame = (26.6667 2.33333; 23.3333 28.6667); opaque = NO; userInteractionEnabled = NO; image = <UIImage:0x60000302a760 symbol "flame.fill"; (23 29)@3{2}>; layer = <CALayer: 0x600000c72880>>
   |    |    |    |    |    |    |    |    |    |    | <UITabBarButtonLabel: 0x10372e440; frame = (17.6667 34.3333; 41.6667 12); text = 'Profiling'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600002c39200>>
   |    |    |    |    |    |    |    | <UITabBarButton: 0x103730060; frame = (324 1; 76 48); opaque = NO; layer = <CALayer: 0x600000c74660>>
   |    |    |    |    |    |    |    |    | <UIVisualEffectView: 0x103630330; frame = (0 0; 76 48); userInteractionEnabled = NO; layer = <CALayer: 0x600000c83cc0>> clientRequestedContentView effect=<UIVibrancyEffect: 0x600000011030> style=UIBlurEffectStyleSystemChromeMaterial vibrancyStyle=UIVibrancyEffectStyleFill
   |    |    |    |    |    |    |    |    |    | <_UIVisualEffectContentView: 0x103318fe0; frame = (0 0; 76 48); autoresize = W+H; tintColor = UIExtendedGrayColorSpace 1 1; layer = <CALayer: 0x600000c78270>> disablesGroupFiltering
   |    |    |    |    |    |    |    |    |    |    | <UITabBarSwappableImageView: 0x103731d90; frame = (24 3.66667; 27.3333 27.6667); opaque = NO; userInteractionEnabled = NO; image = <UIImage:0x60000302b4e0 symbol "deskclock"; (27 28)@3{2}>; layer = <CALayer: 0x600000c74690>>
   |    |    |    |    |    |    |    |    |    |    | <UITabBarButtonLabel: 0x103731430; frame = (2.33333 34.3333; 71 12); text = 'Benchmarking'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600002c3bc80>>
   |    |    |    | <UINavigationBar: 0x10330b030; frame = (0 56.3333; 402 44); opaque = NO; autoresize = W; layer = <CALayer: 0x600000c52e20>> delegate=0x104834800
   |    |    |    |    | <_UIBarBackground: 0x10330b5f0; frame = (0 -62; 402 106); userInteractionEnabled = NO; layer = <CALayer: 0x600000c52a30>>
   |    |    |    |    |    | <UIVisualEffectView: 0x10352bd50; frame = (0 0; 402 106); alpha = 0; layer = <CALayer: 0x600000c90300>> effect=none
   |    |    |    |    |    |    | <_UIVisualEffectBackdropView: 0x10352b160; frame = (0 0; 402 106); autoresize = W+H; userInteractionEnabled = NO; layer = <UICABackdropLayer: 0x6000017a1140>>
   |    |    |    |    |    | <_UIBarBackgroundShadowView: 0x10352bf70; frame = (0 106; 402 0.333333); layer = <CALayer: 0x600000c903c0>> clientRequestedContentView effect=none
   |    |    |    |    |    |    | <_UIBarBackgroundShadowContentImageView: 0x10352d580; frame = (0 0; 402 0.333333); alpha = 0; autoresize = W+H; userInteractionEnabled = NO; backgroundColor = <UIDynamicSystemColor: 0x6000017a0100; name = _systemChromeShadowColor>; image = <(null):0x0 (null) anonymous; (0 0)@0>; layer = <CALayer: 0x600000c90c90>>
   |    |    |    |    | <_UINavigationBarContentView: 0x10330ba00; frame = (0 0; 402 44); layer = <CALayer: 0x600000c529d0>> layout=0x10330bf30
   |    |    |    |    |    | <_UIButtonBarStackView: 0x103529ca0; frame = (386 0; 8 44); layer = <CALayer: 0x600000c6fd20>> axis=horiz distribution=fill alignment=center layoutMarginsRelative NO ARRANGED SUBVIEWS buttonBar=0x600003e41e00
   |    |    |    |    | <_UIPointerInteractionAssistantEffectContainerView: 0x103528e20; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x600000c6fae0>>
   | <UITransitionView: 0x103530530; frame = (0 0; 402 874); autoresize = W+H; layer = <CALayer: 0x600000cb8750>>
   |    | <PopupDialog.PopupDialogOverlayView: 0x10362e540; frame = (0 0; 402 874); autoresize = W+H; backgroundColor = UIExtendedGrayColorSpace 0 0; layer = <CALayer: 0x600000cb4840>>
   |    |    | <DynamicBlurView.DynamicBlurView: 0x10362f7b0; frame = (0 0; 402 874); hidden = YES; autoresize = W+H; userInteractionEnabled = NO; tintColor = UIExtendedGrayColorSpace 0 0; layer = <DynamicBlurView.BlurLayer: 0x600001772e80>>
   |    |    | <UIView: 0x103607260; frame = (0 0; 402 874); alpha = 0.2; autoresize = W+H; backgroundColor = UIExtendedSRGBColorSpace 1 0 0 1; layer = <CALayer: 0x600000cb4960>>
   |    | <PopupDialog.PopupDialogContainerView: 0x10362e370; frame = (0 0; 402 874); gestureRecognizers = <NSArray: 0x60000000e650>; backgroundColor = UIExtendedGrayColorSpace 1 1; layer = <CALayer: 0x600000cb41e0>>
   |    |    | <UIView: 0x10362e740; frame = (31 377.667; 340 118.667); backgroundColor = UIExtendedGrayColorSpace 0 0; layer = <CALayer: 0x600000cb4210>>
   |    |    |    | <UIView: 0x10362ea10; frame = (0 0; 340 118.667); clipsToBounds = YES; backgroundColor = UIExtendedGrayColorSpace 1 1; layer = <CALayer: 0x600000cb4270>>
   |    |    |    |    | <UIStackView: 0x10362eea0; frame = (0 0; 340 118.667); gestureRecognizers = <NSArray: 0x60000000e6b0>; layer = <CALayer: 0x600000cb4420>> axis=vert distribution=fill alignment=fill
   |    |    |    |    |    | <UIStackView: 0x10362ece0; frame = (0 0; 0 0); layer = <CALayer: 0x600000cb4300>> axis=vert distribution=fillEqually alignment=fill NO ARRANGED SUBVIEWS
   |    |    |    |    |    | <PopupDialog.PopupDialogDefaultView: 0x1046053a0; frame = (0 0; 340 118.667); layer = <CALayer: 0x600000c228b0>>
   |    |    |    |    |    |    | <UIImageView: 0x1046058c0; frame = (0 0; 340 0); clipsToBounds = YES; userInteractionEnabled = NO; image = <(null):0x0 (null) anonymous; (0 0)@0>; layer = <CALayer: 0x600000c229d0>>
   |    |    |    |    |    |    | <UILabel: 0x104605cc0; frame = (20 30; 300 17); text = 'THIS IS THE DIALOG TITLE'; userInteractionEnabled = NO; backgroundColor = UIExtendedGrayColorSpace 0 0; layer = <_UILabelLayer: 0x600002c1cd00>>
   |    |    |    |    |    |    | <UILabel: 0x104606fd0; frame = (20 55; 300 33.6667); text = 'This is the message secti...'; userInteractionEnabled = NO; backgroundColor = UIExtendedGrayColorSpace 0 0; layer = <_UILabelLayer: 0x600002c1ce80>>

@philprime philprime changed the title fix(session-replay): Include layer background color when checkig if a view is opaque fix(session-replay): Include layer background color when checking if a view is opaque Nov 3, 2025
@philprime philprime self-assigned this Nov 3, 2025
@philprime philprime marked this pull request as ready for review November 3, 2025 12:58
@philprime
Copy link
Member

The changes of this PR will also need to be released as a hotfix in v8

@philprime philprime marked this pull request as draft November 3, 2025 13:29
@philprime
Copy link
Member

Converting back to draft because the sample did not properly mask.

@philprime
Copy link
Member

philprime commented Nov 3, 2025

I made masking stricter to stop semi‑transparent overlays from unmasking things underneath. Some overlays (like PopupDialog) add a see‑through tint as a child view on top of a clear container. Our old check could think the container was “opaque” and clear redactions behind it.

Now, a view only clears what’s behind it if it’s truly opaque:

  • layer opacity is 1
  • both the view and the layer have an opaque background (alpha == 1)
  • both report opaque

This protects privacy and worst case we do a bit less “clip‑out” optimization, leaving more masking in than necessary. I also updated tests to explicitly set opaque props when they expect clip‑out.

Next Step:

  • Evaluate masking in sample apps
  • Evaluate masking in real app (e.g. Flinky)
  • Merge into main, then merge into v8-x
  • Add a callout to the release notes so people know that masking might be stricter than before

@philprime philprime marked this pull request as ready for review November 4, 2025 09:43
Copy link
Contributor

@noahsmartin noahsmartin left a comment

Choose a reason for hiding this comment

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

I think this is an improvement, but I can still think of edge cases. For example what if you use UIColor(patternImage: ) I don't know what the opacity of this will be, but you could use an image that has some transparent regions. I wonder why we even need this, what if we just went ahead and masked on top of things even if they were covered by another view. I don't think that would hurt performance or anything a noticeable amount. IMO our long term strategy should be to do some simplifying things like that, rather than adding more edge cases

@philprime
Copy link
Member

I think this is an improvement, but I can still think of edge cases. For example what if you use UIColor(patternImage: ) I don't know what the opacity of this will be, but you could use an image that has some transparent regions.

We might need another unit test for that, but I would actually limit the scope on this PR for now so we can get it merged.

I wonder why we even need this, what if we just went ahead and masked on top of things even if they were covered by another view. I don't think that would hurt performance or anything a noticeable amount.

AFAIK the clipping is also necessary for modal views. If we do not use clipping to remove regions covered by an opaque view, presenting a modal would use the regions underneath it, hiding more information than necessary, therefore hiding actual UI.

IMO our long term strategy should be to do some simplifying things like that, rather than adding more edge cases

At some point we could consider not doing selective masking and just (safely) blur the entire screen. Replay users would see a rough idea of the screen and no details, but also no leaks

@philprime philprime enabled auto-merge (squash) November 5, 2025 07:43
@philprime
Copy link
Member

philprime commented Nov 5, 2025

I tested masking the sample app and it seems to mostly work:

https://sentry-sdks.sentry.io/explore/replays/ec53419bc43643e6b04a35741f5153fb/?project=4509348709924865&project=5428557&query=&referrer=%2Fexplore%2Freplays%2F%3AreplaySlug%2F&statsPeriod=1h&yAxis=count%28%29

I found one more masking issue at 00:24 which needs to be investigated.

EDIT:
The screen in question is the ReplaceContainerViewController, but I was not able to reliably reproduce it.

https://sentry-sdks.sentry.io/explore/replays/d5ccd9645b0c4fa499f87b8b266e0926/?project=5428557&query=&referrer=%2Fexplore%2Freplays%2F&statsPeriod=1h&yAxis=count%28%29

@philprime philprime merged commit 1655116 into main Nov 5, 2025
193 of 199 checks passed
@philprime philprime deleted the itay/improve_opaque_logic branch November 5, 2025 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(session-replay): Include layer background color when checking if a view is opaque

4 participants