From b8bd70cd9b9a02434f2f901b38a8581c79642c63 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 14 Aug 2025 18:26:29 +1200 Subject: [PATCH 01/15] Only detect custom session replay masks when necessary Resolves https://github.com/getsentry/sentry-dotnet/issues/4439 --- .../Internal/MauiVisualElementEventsBinder.cs | 19 +++++++++++++-- src/Sentry/Platforms/Android/NativeOptions.cs | 24 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/Sentry.Maui/Internal/MauiVisualElementEventsBinder.cs b/src/Sentry.Maui/Internal/MauiVisualElementEventsBinder.cs index c62a19fdff..13a159d91c 100644 --- a/src/Sentry.Maui/Internal/MauiVisualElementEventsBinder.cs +++ b/src/Sentry.Maui/Internal/MauiVisualElementEventsBinder.cs @@ -12,22 +12,37 @@ namespace Sentry.Maui.Internal; internal class MauiVisualElementEventsBinder : IMauiElementEventBinder { private readonly SentryMauiOptions _options; + private readonly bool _isEnabled; public MauiVisualElementEventsBinder(IOptions options) { _options = options.Value; +#if __ANDROID__ + var replayOptions = _options.Native.ExperimentalOptions.SessionReplay; + _isEnabled = (replayOptions.SessionSampleRate > 0.0 || replayOptions.OnErrorSampleRate > 0.0) + && replayOptions is not { MaskAllImages: true, MaskAllText: true } + && !replayOptions.DisableCustomSessionReplayMasks; +#else + _isEnabled = false; // Session replay is only supported on Android for now +#endif } /// public void Bind(VisualElement element, Action _) { - element.Loaded += OnElementLoaded; + if (_isEnabled) + { + element.Loaded += OnElementLoaded; + } } /// public void UnBind(VisualElement element) { - element.Loaded -= OnElementLoaded; + if (_isEnabled) + { + element.Loaded -= OnElementLoaded; + } } internal void OnElementLoaded(object? sender, EventArgs _) diff --git a/src/Sentry/Platforms/Android/NativeOptions.cs b/src/Sentry/Platforms/Android/NativeOptions.cs index a803140bae..afcfa03435 100644 --- a/src/Sentry/Platforms/Android/NativeOptions.cs +++ b/src/Sentry/Platforms/Android/NativeOptions.cs @@ -272,6 +272,30 @@ public class NativeSentryReplayOptions public double? SessionSampleRate { get; set; } public bool MaskAllImages { get; set; } = true; public bool MaskAllText { get; set; } = true; + + /// + /// + /// Apps with complex user interfaces, consisting of hundreds of visual controls on a single page, may experience + /// performance issues due to the overhead of detecting custom masking of visual elements for Session Replays. + /// + /// + /// In such cases you have a few different options: + /// + /// + /// Disable Session Replays entirely + /// + /// + /// Mask all text and all images + /// + /// + /// Disable custom session replay masks (so nothing gets masked) + /// + /// + /// Set this option to true to disable custom session replay masks. + /// + /// + public bool DisableCustomSessionReplayMasks { get; set; } = false; + internal HashSet MaskedControls { get; } = []; internal HashSet UnmaskedControls { get; } = []; From 8202c1d524a9f0273c539a63c1b4fffe5857cdc5 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 14 Aug 2025 18:29:43 +1200 Subject: [PATCH 02/15] Renamed MauiVisualElementEventsBinder to MauiCustomSessionReplayMaskBinder --- ...EventsBinder.cs => MauiCustomSessionReplayMaskBinder.cs} | 4 ++-- src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs | 2 +- ...erTests.cs => MauiCustomSessionReplayMaskBinderTests.cs} | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename src/Sentry.Maui/Internal/{MauiVisualElementEventsBinder.cs => MauiCustomSessionReplayMaskBinder.cs} (94%) rename test/Sentry.Maui.Tests/{MauiVisualElementEventsBinderTests.cs => MauiCustomSessionReplayMaskBinderTests.cs} (88%) diff --git a/src/Sentry.Maui/Internal/MauiVisualElementEventsBinder.cs b/src/Sentry.Maui/Internal/MauiCustomSessionReplayMaskBinder.cs similarity index 94% rename from src/Sentry.Maui/Internal/MauiVisualElementEventsBinder.cs rename to src/Sentry.Maui/Internal/MauiCustomSessionReplayMaskBinder.cs index 13a159d91c..f972c8dbc7 100644 --- a/src/Sentry.Maui/Internal/MauiVisualElementEventsBinder.cs +++ b/src/Sentry.Maui/Internal/MauiCustomSessionReplayMaskBinder.cs @@ -9,12 +9,12 @@ namespace Sentry.Maui.Internal; /// /// Masks or unmasks visual elements for session replay recordings /// -internal class MauiVisualElementEventsBinder : IMauiElementEventBinder +internal class MauiCustomSessionReplayMaskBinder : IMauiElementEventBinder { private readonly SentryMauiOptions _options; private readonly bool _isEnabled; - public MauiVisualElementEventsBinder(IOptions options) + public MauiCustomSessionReplayMaskBinder(IOptions options) { _options = options.Value; #if __ANDROID__ diff --git a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs index e9cbd6d39d..c61473c388 100644 --- a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs +++ b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs @@ -67,7 +67,7 @@ public static MauiAppBuilder UseSentry(this MauiAppBuilder builder, services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); // Resolve the configured options and register any event binders that have been injected by integrations var options = new SentryMauiOptions(); diff --git a/test/Sentry.Maui.Tests/MauiVisualElementEventsBinderTests.cs b/test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs similarity index 88% rename from test/Sentry.Maui.Tests/MauiVisualElementEventsBinderTests.cs rename to test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs index fd50cb40b1..205de3626e 100644 --- a/test/Sentry.Maui.Tests/MauiVisualElementEventsBinderTests.cs +++ b/test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs @@ -6,11 +6,11 @@ namespace Sentry.Maui.Tests; -public class MauiVisualElementEventsBinderTests +public class MauiCustomSessionReplayMaskBinderTests { private class Fixture { - public MauiVisualElementEventsBinder Binder { get; } + public MauiCustomSessionReplayMaskBinder Binder { get; } public SentryMauiOptions Options { get; } = new(); @@ -21,7 +21,7 @@ public Fixture() logger.IsEnabled(Arg.Any()).Returns(true); Options.DiagnosticLogger = logger; var options = Microsoft.Extensions.Options.Options.Create(Options); - Binder = new MauiVisualElementEventsBinder(options); + Binder = new MauiCustomSessionReplayMaskBinder(options); } } From 34474d3dc9dfe33821a4f30cbc05801d782c6501 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Fri, 15 Aug 2025 10:19:13 +1200 Subject: [PATCH 03/15] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c685603f23..8414adc4de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Experimental _Structured Logs_: - Remove `IDisposable` from `SentryStructuredLogger`. Disposal is intended through the owning `IHub` instance. ([#4424](https://github.com/getsentry/sentry-dotnet/pull/4424)) - Ensure all buffered logs are sent to Sentry when the application terminates unexpectedly. ([#4425](https://github.com/getsentry/sentry-dotnet/pull/4425)) +- Only detects custom session replay masks when necessary to avoid performance issues in MAUI apps with complex UIs ([#4445](https://github.com/getsentry/sentry-dotnet/pull/4445)) ## 5.14.1 From cc9a3c7d4f35fd51d691ab44e8f8756eb11d2afa Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 19 Aug 2025 22:17:11 +1200 Subject: [PATCH 04/15] Update MauiCustomSessionReplayMaskBinder.cs --- .../Internal/MauiCustomSessionReplayMaskBinder.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Sentry.Maui/Internal/MauiCustomSessionReplayMaskBinder.cs b/src/Sentry.Maui/Internal/MauiCustomSessionReplayMaskBinder.cs index f972c8dbc7..9cd98984ee 100644 --- a/src/Sentry.Maui/Internal/MauiCustomSessionReplayMaskBinder.cs +++ b/src/Sentry.Maui/Internal/MauiCustomSessionReplayMaskBinder.cs @@ -19,9 +19,8 @@ public MauiCustomSessionReplayMaskBinder(IOptions options) _options = options.Value; #if __ANDROID__ var replayOptions = _options.Native.ExperimentalOptions.SessionReplay; - _isEnabled = (replayOptions.SessionSampleRate > 0.0 || replayOptions.OnErrorSampleRate > 0.0) - && replayOptions is not { MaskAllImages: true, MaskAllText: true } - && !replayOptions.DisableCustomSessionReplayMasks; + var sessionReplayEnabled = replayOptions.OnErrorSampleRate > 0.0 || replayOptions.SessionSampleRate > 0.0; + _isEnabled = sessionReplayEnabled && !replayOptions.DisableCustomSessionReplayMasks; #else _isEnabled = false; // Session replay is only supported on Android for now #endif From 54cda92958131c91c83a13324ce29e2b1bf4a2f3 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 21 Aug 2025 12:20:07 +1200 Subject: [PATCH 05/15] Make custom masking opt in --- .../MauiCustomSessionReplayMaskBinder.cs | 18 +------ .../SentryMauiAppBuilderExtensions.cs | 11 ++++- src/Sentry/Platforms/Android/NativeOptions.cs | 48 ++++++++++--------- 3 files changed, 36 insertions(+), 41 deletions(-) diff --git a/src/Sentry.Maui/Internal/MauiCustomSessionReplayMaskBinder.cs b/src/Sentry.Maui/Internal/MauiCustomSessionReplayMaskBinder.cs index 9cd98984ee..da1e422701 100644 --- a/src/Sentry.Maui/Internal/MauiCustomSessionReplayMaskBinder.cs +++ b/src/Sentry.Maui/Internal/MauiCustomSessionReplayMaskBinder.cs @@ -12,36 +12,22 @@ namespace Sentry.Maui.Internal; internal class MauiCustomSessionReplayMaskBinder : IMauiElementEventBinder { private readonly SentryMauiOptions _options; - private readonly bool _isEnabled; public MauiCustomSessionReplayMaskBinder(IOptions options) { _options = options.Value; -#if __ANDROID__ - var replayOptions = _options.Native.ExperimentalOptions.SessionReplay; - var sessionReplayEnabled = replayOptions.OnErrorSampleRate > 0.0 || replayOptions.SessionSampleRate > 0.0; - _isEnabled = sessionReplayEnabled && !replayOptions.DisableCustomSessionReplayMasks; -#else - _isEnabled = false; // Session replay is only supported on Android for now -#endif } /// public void Bind(VisualElement element, Action _) { - if (_isEnabled) - { - element.Loaded += OnElementLoaded; - } + element.Loaded += OnElementLoaded; } /// public void UnBind(VisualElement element) { - if (_isEnabled) - { - element.Loaded -= OnElementLoaded; - } + element.Loaded -= OnElementLoaded; } internal void OnElementLoaded(object? sender, EventArgs _) diff --git a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs index c61473c388..27ff6110ab 100644 --- a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs +++ b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs @@ -67,11 +67,18 @@ public static MauiAppBuilder UseSentry(this MauiAppBuilder builder, services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - // Resolve the configured options and register any event binders that have been injected by integrations + // Resolve the configured options and register any event binders that have been enabled via configuration or + // injected by integrations var options = new SentryMauiOptions(); configureOptions?.Invoke(options); +#if __ANDROID__ + var replayOptions = options.Native.ExperimentalOptions.SessionReplay; + if (replayOptions is { IsCustomMaskingEnabled: true, IsSessionReplayEnabled: true }) + { + services.AddSingleton(); + } +#endif foreach (var eventBinder in options.IntegrationEventBinders) { eventBinder.Register(services); diff --git a/src/Sentry/Platforms/Android/NativeOptions.cs b/src/Sentry/Platforms/Android/NativeOptions.cs index afcfa03435..f25e4f1508 100644 --- a/src/Sentry/Platforms/Android/NativeOptions.cs +++ b/src/Sentry/Platforms/Android/NativeOptions.cs @@ -273,32 +273,13 @@ public class NativeSentryReplayOptions public bool MaskAllImages { get; set; } = true; public bool MaskAllText { get; set; } = true; - /// - /// - /// Apps with complex user interfaces, consisting of hundreds of visual controls on a single page, may experience - /// performance issues due to the overhead of detecting custom masking of visual elements for Session Replays. - /// - /// - /// In such cases you have a few different options: - /// - /// - /// Disable Session Replays entirely - /// - /// - /// Mask all text and all images - /// - /// - /// Disable custom session replay masks (so nothing gets masked) - /// - /// - /// Set this option to true to disable custom session replay masks. - /// - /// - public bool DisableCustomSessionReplayMasks { get; set; } = false; - internal HashSet MaskedControls { get; } = []; internal HashSet UnmaskedControls { get; } = []; + internal bool IsCustomMaskingEnabled { get; private set; } + + internal bool IsSessionReplayEnabled => OnErrorSampleRate > 0.0 || SessionSampleRate > 0.0; + public void MaskControlsOfType() { MaskedControls.Add(typeof(T)); @@ -308,6 +289,27 @@ public void UnmaskControlsOfType() { UnmaskedControls.Add(typeof(T)); } + + /// + /// + /// The and and + /// options allow you to set the default masking behaviour for all visual elements of certain types. + /// + /// + /// This option enables the use of `sentry:SessionReplay.Mask` attributes to override the masking behaviour + /// of specific visual elemennts (for example masking a specific image even though images more generally are + /// not masked). + /// + /// + /// WARNING: In apps with complex user interfaces, consisting of hundreds of visual controls on a single + /// page, enabling this option may cause performance issues. + /// + /// + public NativeSentryReplayOptions EnableCustomSessionReplayMasks() + { + IsCustomMaskingEnabled = true; + return this; + } } /// From f19a466fa5ce9c20c0ec54b20bc9eb70976bedcd Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 21 Aug 2025 12:55:12 +1200 Subject: [PATCH 06/15] Renamed binder to MauiSessionReplayMaskControlsOfTypeBinder --- samples/Sentry.Samples.Maui/MauiProgram.cs | 7 +++- ...iSessionReplayMaskControlsOfTypeBinder.cs} | 0 .../SentryMauiAppBuilderExtensions.cs | 2 +- src/Sentry/Platforms/Android/NativeOptions.cs | 41 +++++++++---------- 4 files changed, 26 insertions(+), 24 deletions(-) rename src/Sentry.Maui/Internal/{MauiCustomSessionReplayMaskBinder.cs => MauiSessionReplayMaskControlsOfTypeBinder.cs} (100%) diff --git a/samples/Sentry.Samples.Maui/MauiProgram.cs b/samples/Sentry.Samples.Maui/MauiProgram.cs index 909851b7d4..1a8a6a30a7 100644 --- a/samples/Sentry.Samples.Maui/MauiProgram.cs +++ b/samples/Sentry.Samples.Maui/MauiProgram.cs @@ -45,15 +45,18 @@ public static MauiApp CreateMauiApp() options.AddCommunityToolkitIntegration(); #if __ANDROID__ - // Currently experimental support is only available on Android + // Currently, experimental support is only available on Android options.Native.ExperimentalOptions.SessionReplay.OnErrorSampleRate = 1.0; options.Native.ExperimentalOptions.SessionReplay.SessionSampleRate = 1.0; // Mask all images and text by default. This can be overridden for individual view elements via the // sentry:SessionReplay.Mask XML attribute (see MainPage.xaml for an example) options.Native.ExperimentalOptions.SessionReplay.MaskAllImages = true; options.Native.ExperimentalOptions.SessionReplay.MaskAllText = true; - // Alternatively the masking behaviour for entire classes of VisualElements can be configured here as + // Alternatively, the masking behaviour for entire classes of VisualElements can be configured here as // an exception to the default behaviour. + // WARNING: In apps with complex user interfaces, consisting of hundreds of visual controls on a single + // page, this option may cause performance issues. In such cases, consider applying the + // sentry:SessionReplay.Mask="Unmask" attribute to individual controls instead. options.Native.ExperimentalOptions.SessionReplay.UnmaskControlsOfType - public NativeSentryReplayOptions EnableCustomSessionReplayMasks() + public void UnmaskControlsOfType() { - IsCustomMaskingEnabled = true; - return this; + UnmaskedControls.Add(typeof(T)); } + + internal bool IsTypeMaskingUsed => MaskedControls.Count > 0 || UnmaskedControls.Count > 0; } /// From 4bd60e6514e7c286c0d47fe940604a5cbda90415 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Fri, 22 Aug 2025 15:41:47 +1200 Subject: [PATCH 07/15] Renamed MauiCustomSessionReplayMaskBinder to MauiSessionReplayMaskControlsOfTypeBinder --- .../MauiSessionReplayMaskControlsOfTypeBinder.cs | 4 ++-- src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs | 2 +- .../MauiCustomSessionReplayMaskBinderTests.cs | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs b/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs index da1e422701..0161b892b5 100644 --- a/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs +++ b/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs @@ -9,11 +9,11 @@ namespace Sentry.Maui.Internal; /// /// Masks or unmasks visual elements for session replay recordings /// -internal class MauiCustomSessionReplayMaskBinder : IMauiElementEventBinder +internal class MauiSessionReplayMaskControlsOfTypeBinder : IMauiElementEventBinder { private readonly SentryMauiOptions _options; - public MauiCustomSessionReplayMaskBinder(IOptions options) + public MauiSessionReplayMaskControlsOfTypeBinder(IOptions options) { _options = options.Value; } diff --git a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs index f347ae00f5..04e553865f 100644 --- a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs +++ b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs @@ -76,7 +76,7 @@ public static MauiAppBuilder UseSentry(this MauiAppBuilder builder, var replayOptions = options.Native.ExperimentalOptions.SessionReplay; if (replayOptions is { IsSessionReplayEnabled: true, IsTypeMaskingUsed: true }) { - services.AddSingleton(); + services.AddSingleton(); } #endif foreach (var eventBinder in options.IntegrationEventBinders) diff --git a/test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs b/test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs index 205de3626e..5db8f5fa6b 100644 --- a/test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs +++ b/test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs @@ -6,11 +6,11 @@ namespace Sentry.Maui.Tests; -public class MauiCustomSessionReplayMaskBinderTests +public class MauiSessionReplayMaskControlsOfTypeBinderTests { private class Fixture { - public MauiCustomSessionReplayMaskBinder Binder { get; } + public MauiSessionReplayMaskControlsOfTypeBinder ControlsOfTypeBinder { get; } public SentryMauiOptions Options { get; } = new(); @@ -21,7 +21,7 @@ public Fixture() logger.IsEnabled(Arg.Any()).Returns(true); Options.DiagnosticLogger = logger; var options = Microsoft.Extensions.Options.Options.Create(Options); - Binder = new MauiCustomSessionReplayMaskBinder(options); + ControlsOfTypeBinder = new MauiSessionReplayMaskControlsOfTypeBinder(options); } } @@ -34,7 +34,7 @@ public void OnElementLoaded_SenderIsNotVisualElement_LogsDebugAndReturns() var element = new MockElement("element"); // Act - _fixture.Binder.OnElementLoaded(element, EventArgs.Empty); + _fixture.ControlsOfTypeBinder.OnElementLoaded(element, EventArgs.Empty); // Assert _fixture.Options.DiagnosticLogger.Received(1).LogDebug("OnElementLoaded: sender is not a VisualElement"); @@ -50,7 +50,7 @@ public void OnElementLoaded_HandlerIsNull_LogsDebugAndReturns() }; // Act - _fixture.Binder.OnElementLoaded(element, EventArgs.Empty); + _fixture.ControlsOfTypeBinder.OnElementLoaded(element, EventArgs.Empty); // Assert _fixture.Options.DiagnosticLogger.Received(1).LogDebug("OnElementLoaded: handler is null"); From dd4590b0ec844daa6b1e4caea4808dcc686307ea Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Fri, 22 Aug 2025 15:42:04 +1200 Subject: [PATCH 08/15] Update SentryMauiAppBuilderExtensionsTests.cs --- .../SentryMauiAppBuilderExtensionsTests.cs | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs index c3fac5e100..d4e874c71d 100644 --- a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs +++ b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs @@ -329,4 +329,81 @@ public void UseSentry_DebugTrue_CustomDiagnosticsLogger() // Assert options.DiagnosticLogger.Should().BeOfType(); } + +#if ANDROID + [Theory] + [InlineData(0.0, 1.0)] + [InlineData(1.0, 0.0)] + [InlineData(1.0, 1.0)] + public void UseSentry_SessionReplayEnabled_RegistersMauiSessionReplayMaskControlsOfTypeBinder( + double sessionSampleRate, double onErrorSampleRate) + { + // Arrange + var builder = _fixture.Builder; + + // Act + builder.UseSentry(options => + { + options.Dsn = ValidDsn; + // force custom masking to be enabled + options.Native.ExperimentalOptions.SessionReplay.MaskControlsOfType(); + // One of the below has to be non-zero for session replay to be enabled + options.Native.ExperimentalOptions.SessionReplay.SessionSampleRate = sessionSampleRate; + options.Native.ExperimentalOptions.SessionReplay.OnErrorSampleRate = onErrorSampleRate; + }); + + using var app = builder.Build(); + var binders = app.Services.GetServices(); + + // Assert + binders.Should().ContainSingle(b => + b.GetType() == typeof(MauiSessionReplayMaskControlsOfTypeBinder)); + } + + [Fact] + public void UseSentry_SessionReplayDisabled_DoesNotRegisterMauiSessionReplayMaskControlsOfTypeBinder() + { + // Arrange + var builder = _fixture.Builder; + + // Act + builder.UseSentry(options => + { + options.Dsn = ValidDsn; + // force custom masking to be enabled + options.Native.ExperimentalOptions.SessionReplay.MaskControlsOfType(); + // No sessionSampleRate or onErrorSampleRate set... so should be disabled + }); + + using var app = builder.Build(); + var binders = app.Services.GetServices(); + + // Assert + binders.Should().NotContain(b => + b.GetType() == typeof(MauiSessionReplayMaskControlsOfTypeBinder)); + } + + [Fact] + public void UseSentry_NoMaskedControls_DoesNotRegisterMauiVisualElementEventsBinder() + { + // Arrange + var builder = _fixture.Builder; + + // Act + builder.UseSentry(options => + { + options.Dsn = ValidDsn; + options.Native.ExperimentalOptions.SessionReplay.OnErrorSampleRate = 1.0; + options.Native.ExperimentalOptions.SessionReplay.SessionSampleRate = 1.0; + // Not really necessary, but just to be explicit + options.Native.ExperimentalOptions.SessionReplay.MaskedControls.Clear(); + }); + + using var app = builder.Build(); + var binders = app.Services.GetServices(); + + // Assert + binders.Should().NotContain(b => b.GetType() == typeof(MauiSessionReplayMaskControlsOfTypeBinder)); + } +#endif } From e62e8ec75434d9e7c479b1956099f6f4c38f5231 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 25 Aug 2025 10:54:25 +1200 Subject: [PATCH 09/15] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bf1af4183..35727cc48e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ - Remove `IDisposable` from `SentryStructuredLogger`. Disposal is intended through the owning `IHub` instance ([#4424](https://github.com/getsentry/sentry-dotnet/pull/4424)) - Ensure all buffered logs are sent to Sentry when the application terminates unexpectedly ([#4425](https://github.com/getsentry/sentry-dotnet/pull/4425)) - `InvalidOperationException` potentially thrown during a race condition, especially in concurrent high-volume logging scenarios ([#4428](https://github.com/getsentry/sentry-dotnet/pull/4428)) -- Only detects custom session replay masks when necessary to avoid performance issues in MAUI apps with complex UIs ([#4445](https://github.com/getsentry/sentry-dotnet/pull/4445)) +- Only applies Session Replay masks to specific controls types when necessary, to avoid performance issues in MAUI apps with complex UIs ([#4445](https://github.com/getsentry/sentry-dotnet/pull/4445)) ### Dependencies From 439947d8977b83b48f06026fa4428b57ee46902e Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 28 Aug 2025 16:23:39 +1200 Subject: [PATCH 10/15] Added MauiSessionReplayMaskControlsOfTypeBinder.IsEnabled --- src/Sentry.Maui/Internal/MauiEventsBinder.cs | 6 +++--- .../MauiSessionReplayMaskControlsOfTypeBinder.cs | 8 ++++++++ src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs | 12 ++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Sentry.Maui/Internal/MauiEventsBinder.cs b/src/Sentry.Maui/Internal/MauiEventsBinder.cs index 737902041a..82f7039392 100644 --- a/src/Sentry.Maui/Internal/MauiEventsBinder.cs +++ b/src/Sentry.Maui/Internal/MauiEventsBinder.cs @@ -1,6 +1,4 @@ using Microsoft.Extensions.Options; -using Microsoft.Maui.Platform; -using Sentry.Extensibility; namespace Sentry.Maui.Internal; @@ -29,7 +27,9 @@ public MauiEventsBinder(IHub hub, IOptions options, IEnumerab { _hub = hub; _options = options.Value; - _elementEventBinders = elementEventBinders; + _elementEventBinders = elementEventBinders.Where(b + => b is not MauiSessionReplayMaskControlsOfTypeBinder maskControlTypeBinder + || maskControlTypeBinder.IsEnabled); } public void HandleApplicationEvents(Application application, bool bind = true) diff --git a/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs b/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs index 0161b892b5..890239513b 100644 --- a/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs +++ b/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs @@ -13,9 +13,17 @@ internal class MauiSessionReplayMaskControlsOfTypeBinder : IMauiElementEventBind { private readonly SentryMauiOptions _options; + internal bool IsEnabled { get; private set; } + public MauiSessionReplayMaskControlsOfTypeBinder(IOptions options) { _options = options.Value; +#if __ANDROID__ + var replayOptions = options.Value.Native.ExperimentalOptions.SessionReplay; + IsEnabled = replayOptions is { IsSessionReplayEnabled: true, IsTypeMaskingUsed: true }; +#else + IsEnabled = false; +#endif } /// diff --git a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs index 04e553865f..9274cf5812 100644 --- a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs +++ b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs @@ -2,7 +2,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Maui.LifecycleEvents; -using Sentry; using Sentry.Extensibility; using Sentry.Extensions.Logging.Extensions.DependencyInjection; using Sentry.Maui; @@ -67,18 +66,11 @@ public static MauiAppBuilder UseSentry(this MauiAppBuilder builder, services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); - // Resolve the configured options and register any event binders that have been enabled via configuration or - // injected by integrations + // Resolve options configured via the options callback and register any binders injected by integrations var options = new SentryMauiOptions(); configureOptions?.Invoke(options); -#if __ANDROID__ - var replayOptions = options.Native.ExperimentalOptions.SessionReplay; - if (replayOptions is { IsSessionReplayEnabled: true, IsTypeMaskingUsed: true }) - { - services.AddSingleton(); - } -#endif foreach (var eventBinder in options.IntegrationEventBinders) { eventBinder.Register(services); From 51351b6547fbff69760977f6b15d83d97e11277e Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 28 Aug 2025 17:07:04 +1200 Subject: [PATCH 11/15] Updated tests --- src/Sentry.Maui/Internal/MauiEventsBinder.cs | 2 +- ...uiSessionReplayMaskControlsOfTypeBinder.cs | 8 +- .../MauiCustomSessionReplayMaskBinderTests.cs | 60 ++++++++++++++- .../MauiEventsBinderTests.cs | 31 ++++++++ .../SentryMauiAppBuilderExtensionsTests.cs | 77 ------------------- 5 files changed, 94 insertions(+), 84 deletions(-) diff --git a/src/Sentry.Maui/Internal/MauiEventsBinder.cs b/src/Sentry.Maui/Internal/MauiEventsBinder.cs index 82f7039392..4af503036b 100644 --- a/src/Sentry.Maui/Internal/MauiEventsBinder.cs +++ b/src/Sentry.Maui/Internal/MauiEventsBinder.cs @@ -11,7 +11,7 @@ internal class MauiEventsBinder : IMauiEventsBinder { private readonly IHub _hub; private readonly SentryMauiOptions _options; - private readonly IEnumerable _elementEventBinders; + internal readonly IEnumerable _elementEventBinders; // https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types // https://github.com/getsentry/sentry/blob/master/static/app/types/breadcrumbs.tsx diff --git a/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs b/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs index 890239513b..c22bee4b7b 100644 --- a/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs +++ b/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs @@ -13,13 +13,13 @@ internal class MauiSessionReplayMaskControlsOfTypeBinder : IMauiElementEventBind { private readonly SentryMauiOptions _options; - internal bool IsEnabled { get; private set; } + internal bool IsEnabled { get; } - public MauiSessionReplayMaskControlsOfTypeBinder(IOptions options) + public MauiSessionReplayMaskControlsOfTypeBinder(SentryMauiOptions options) { - _options = options.Value; + _options = options; #if __ANDROID__ - var replayOptions = options.Value.Native.ExperimentalOptions.SessionReplay; + var replayOptions = options.Native.ExperimentalOptions.SessionReplay; IsEnabled = replayOptions is { IsSessionReplayEnabled: true, IsTypeMaskingUsed: true }; #else IsEnabled = false; diff --git a/test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs b/test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs index 5db8f5fa6b..e240890ace 100644 --- a/test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs +++ b/test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs @@ -20,8 +20,7 @@ public Fixture() var logger = Substitute.For(); logger.IsEnabled(Arg.Any()).Returns(true); Options.DiagnosticLogger = logger; - var options = Microsoft.Extensions.Options.Options.Create(Options); - ControlsOfTypeBinder = new MauiSessionReplayMaskControlsOfTypeBinder(options); + ControlsOfTypeBinder = new MauiSessionReplayMaskControlsOfTypeBinder(Options); } } @@ -56,4 +55,61 @@ public void OnElementLoaded_HandlerIsNull_LogsDebugAndReturns() _fixture.Options.DiagnosticLogger.Received(1).LogDebug("OnElementLoaded: handler is null"); } +#if __ANDROID__ + [Theory] + [InlineData(0.0, 1.0)] + [InlineData(1.0, 0.0)] + [InlineData(1.0, 1.0)] + public void SessionReplayEnabled_IsEnabled( + double sessionSampleRate, double onErrorSampleRate) + { + // Arrange + var options = new SentryMauiOptions { Dsn = ValidDsn }; + // force custom masking to be enabled + options.Native.ExperimentalOptions.SessionReplay.MaskControlsOfType(); + // One of the below has to be non-zero for session replay to be enabled + options.Native.ExperimentalOptions.SessionReplay.SessionSampleRate = sessionSampleRate; + options.Native.ExperimentalOptions.SessionReplay.OnErrorSampleRate = onErrorSampleRate; + + // Act + var binder = new MauiSessionReplayMaskControlsOfTypeBinder(options); + + // Assert + binder.IsEnabled.Should().Be(true); + } + + [Fact] + public void SessionReplayDisabled_IsNotEnabled() + { + // Arrange + var options = new SentryMauiOptions { Dsn = ValidDsn }; + // force custom masking to be enabled + options.Native.ExperimentalOptions.SessionReplay.MaskControlsOfType(); + // No sessionSampleRate or onErrorSampleRate set... so should be disabled + + // Act + var binder = new MauiSessionReplayMaskControlsOfTypeBinder(options); + + // Assert + binder.IsEnabled.Should().Be(false); + } + + [Fact] + public void UseSentry_NoMaskedControls_DoesNotRegisterMauiVisualElementEventsBinder() + { + // Arrange + var options = new SentryMauiOptions { Dsn = ValidDsn }; + options.Native.ExperimentalOptions.SessionReplay.OnErrorSampleRate = 1.0; + options.Native.ExperimentalOptions.SessionReplay.SessionSampleRate = 1.0; + // Not really necessary, but just to be explicit + options.Native.ExperimentalOptions.SessionReplay.MaskedControls.Clear(); + + // Act + var binder = new MauiSessionReplayMaskControlsOfTypeBinder(options); + + // Assert + binder.IsEnabled.Should().Be(false); + } +#endif + } diff --git a/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs b/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs index 0de536dcfd..b918fe1b1c 100644 --- a/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs +++ b/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs @@ -35,4 +35,35 @@ public void OnBreadcrumbCreateCallback_CreatesBreadcrumb() } } } + + [Fact] + public void ElementEventBinders_EnabledOnly() + { + // Arrange + var options1 = new SentryMauiOptions { Dsn = ValidDsn }; +#if __ANDROID__ + options1.Native.ExperimentalOptions.SessionReplay.MaskControlsOfType(); // force masking to be enabled + options1.Native.ExperimentalOptions.SessionReplay.SessionSampleRate = 1.0; + options1.Native.ExperimentalOptions.SessionReplay.OnErrorSampleRate = 1.0; +#endif + var enabledBinder = new MauiSessionReplayMaskControlsOfTypeBinder(options1); + + var options2 = new SentryMauiOptions { Dsn = ValidDsn }; +#if __ANDROID__ + options2.Native.ExperimentalOptions.SessionReplay.SessionSampleRate = 0.0; + options2.Native.ExperimentalOptions.SessionReplay.OnErrorSampleRate = 0.0; +#endif + var disabledBinder = new MauiSessionReplayMaskControlsOfTypeBinder(options2); + + // Act + var fixture = new MauiEventsBinderFixture(enabledBinder, disabledBinder); + +#if __ANDROID__ + // Assert + _fixture.Binder._elementEventBinders.Should().BeEquivalentTo([enabledBinder]); +#else + // Currently only Android supports Session Replay, so we don't register these binders on other platforms + _fixture.Binder._elementEventBinders.Should().BeEmpty(); +#endif + } } diff --git a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs index d4e874c71d..c3fac5e100 100644 --- a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs +++ b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs @@ -329,81 +329,4 @@ public void UseSentry_DebugTrue_CustomDiagnosticsLogger() // Assert options.DiagnosticLogger.Should().BeOfType(); } - -#if ANDROID - [Theory] - [InlineData(0.0, 1.0)] - [InlineData(1.0, 0.0)] - [InlineData(1.0, 1.0)] - public void UseSentry_SessionReplayEnabled_RegistersMauiSessionReplayMaskControlsOfTypeBinder( - double sessionSampleRate, double onErrorSampleRate) - { - // Arrange - var builder = _fixture.Builder; - - // Act - builder.UseSentry(options => - { - options.Dsn = ValidDsn; - // force custom masking to be enabled - options.Native.ExperimentalOptions.SessionReplay.MaskControlsOfType(); - // One of the below has to be non-zero for session replay to be enabled - options.Native.ExperimentalOptions.SessionReplay.SessionSampleRate = sessionSampleRate; - options.Native.ExperimentalOptions.SessionReplay.OnErrorSampleRate = onErrorSampleRate; - }); - - using var app = builder.Build(); - var binders = app.Services.GetServices(); - - // Assert - binders.Should().ContainSingle(b => - b.GetType() == typeof(MauiSessionReplayMaskControlsOfTypeBinder)); - } - - [Fact] - public void UseSentry_SessionReplayDisabled_DoesNotRegisterMauiSessionReplayMaskControlsOfTypeBinder() - { - // Arrange - var builder = _fixture.Builder; - - // Act - builder.UseSentry(options => - { - options.Dsn = ValidDsn; - // force custom masking to be enabled - options.Native.ExperimentalOptions.SessionReplay.MaskControlsOfType(); - // No sessionSampleRate or onErrorSampleRate set... so should be disabled - }); - - using var app = builder.Build(); - var binders = app.Services.GetServices(); - - // Assert - binders.Should().NotContain(b => - b.GetType() == typeof(MauiSessionReplayMaskControlsOfTypeBinder)); - } - - [Fact] - public void UseSentry_NoMaskedControls_DoesNotRegisterMauiVisualElementEventsBinder() - { - // Arrange - var builder = _fixture.Builder; - - // Act - builder.UseSentry(options => - { - options.Dsn = ValidDsn; - options.Native.ExperimentalOptions.SessionReplay.OnErrorSampleRate = 1.0; - options.Native.ExperimentalOptions.SessionReplay.SessionSampleRate = 1.0; - // Not really necessary, but just to be explicit - options.Native.ExperimentalOptions.SessionReplay.MaskedControls.Clear(); - }); - - using var app = builder.Build(); - var binders = app.Services.GetServices(); - - // Assert - binders.Should().NotContain(b => b.GetType() == typeof(MauiSessionReplayMaskControlsOfTypeBinder)); - } -#endif } From 7c0ecea2331abb23071da662165591a019c562ca Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 28 Aug 2025 17:32:20 +1200 Subject: [PATCH 12/15] Update MauiEventsBinderTests.cs --- test/Sentry.Maui.Tests/MauiEventsBinderTests.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs b/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs index b918fe1b1c..802ce8c71e 100644 --- a/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs +++ b/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs @@ -55,15 +55,18 @@ public void ElementEventBinders_EnabledOnly() #endif var disabledBinder = new MauiSessionReplayMaskControlsOfTypeBinder(options2); + var buttonEventBinder = new MauiButtonEventsBinder(); + // Act - var fixture = new MauiEventsBinderFixture(enabledBinder, disabledBinder); + var fixture = new MauiEventsBinderFixture(buttonEventBinder, enabledBinder, disabledBinder); -#if __ANDROID__ // Assert - _fixture.Binder._elementEventBinders.Should().BeEquivalentTo([enabledBinder]); +#if __ANDROID__ + var expectedBinders = new List { buttonEventBinder, enabledBinder }; #else - // Currently only Android supports Session Replay, so we don't register these binders on other platforms - _fixture.Binder._elementEventBinders.Should().BeEmpty(); + // We only register MauiSessionReplayMaskControlsOfTypeBinder on platforms that support Session Replay + var expectedBinders = new List { buttonEventBinder}; #endif + fixture.Binder._elementEventBinders.Should().BeEquivalentTo(expectedBinders); } } From 0599c9c0600ffbb911de78deeeeee8bb6d4457b4 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Thu, 28 Aug 2025 05:46:03 +0000 Subject: [PATCH 13/15] Format code --- test/Sentry.Maui.Tests/MauiEventsBinderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs b/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs index 802ce8c71e..d926695941 100644 --- a/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs +++ b/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs @@ -65,7 +65,7 @@ public void ElementEventBinders_EnabledOnly() var expectedBinders = new List { buttonEventBinder, enabledBinder }; #else // We only register MauiSessionReplayMaskControlsOfTypeBinder on platforms that support Session Replay - var expectedBinders = new List { buttonEventBinder}; + var expectedBinders = new List { buttonEventBinder }; #endif fixture.Binder._elementEventBinders.Should().BeEquivalentTo(expectedBinders); } From 4953592bc076bb8aedd95d69cc78022aafec24e5 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 2 Sep 2025 17:07:51 +1200 Subject: [PATCH 14/15] Inject IOptions --- .../MauiSessionReplayMaskControlsOfTypeBinder.cs | 6 +++--- .../MauiCustomSessionReplayMaskBinderTests.cs | 12 ++++++++---- test/Sentry.Maui.Tests/MauiEventsBinderTests.cs | 6 ++++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs b/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs index c22bee4b7b..71f098ce18 100644 --- a/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs +++ b/src/Sentry.Maui/Internal/MauiSessionReplayMaskControlsOfTypeBinder.cs @@ -15,11 +15,11 @@ internal class MauiSessionReplayMaskControlsOfTypeBinder : IMauiElementEventBind internal bool IsEnabled { get; } - public MauiSessionReplayMaskControlsOfTypeBinder(SentryMauiOptions options) + public MauiSessionReplayMaskControlsOfTypeBinder(IOptions options) { - _options = options; + _options = options.Value; #if __ANDROID__ - var replayOptions = options.Native.ExperimentalOptions.SessionReplay; + var replayOptions = _options.Native.ExperimentalOptions.SessionReplay; IsEnabled = replayOptions is { IsSessionReplayEnabled: true, IsTypeMaskingUsed: true }; #else IsEnabled = false; diff --git a/test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs b/test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs index e240890ace..876204f265 100644 --- a/test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs +++ b/test/Sentry.Maui.Tests/MauiCustomSessionReplayMaskBinderTests.cs @@ -20,7 +20,8 @@ public Fixture() var logger = Substitute.For(); logger.IsEnabled(Arg.Any()).Returns(true); Options.DiagnosticLogger = logger; - ControlsOfTypeBinder = new MauiSessionReplayMaskControlsOfTypeBinder(Options); + var options = Microsoft.Extensions.Options.Options.Create(Options); + ControlsOfTypeBinder = new MauiSessionReplayMaskControlsOfTypeBinder(options); } } @@ -72,7 +73,8 @@ public void SessionReplayEnabled_IsEnabled( options.Native.ExperimentalOptions.SessionReplay.OnErrorSampleRate = onErrorSampleRate; // Act - var binder = new MauiSessionReplayMaskControlsOfTypeBinder(options); + var iOptions = Microsoft.Extensions.Options.Options.Create(options); + var binder = new MauiSessionReplayMaskControlsOfTypeBinder(iOptions); // Assert binder.IsEnabled.Should().Be(true); @@ -88,7 +90,8 @@ public void SessionReplayDisabled_IsNotEnabled() // No sessionSampleRate or onErrorSampleRate set... so should be disabled // Act - var binder = new MauiSessionReplayMaskControlsOfTypeBinder(options); + var iOptions = Microsoft.Extensions.Options.Options.Create(options); + var binder = new MauiSessionReplayMaskControlsOfTypeBinder(iOptions); // Assert binder.IsEnabled.Should().Be(false); @@ -105,7 +108,8 @@ public void UseSentry_NoMaskedControls_DoesNotRegisterMauiVisualElementEventsBin options.Native.ExperimentalOptions.SessionReplay.MaskedControls.Clear(); // Act - var binder = new MauiSessionReplayMaskControlsOfTypeBinder(options); + var iOptions = Microsoft.Extensions.Options.Options.Create(options); + var binder = new MauiSessionReplayMaskControlsOfTypeBinder(iOptions); // Assert binder.IsEnabled.Should().Be(false); diff --git a/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs b/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs index d926695941..657f1a8487 100644 --- a/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs +++ b/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs @@ -46,14 +46,16 @@ public void ElementEventBinders_EnabledOnly() options1.Native.ExperimentalOptions.SessionReplay.SessionSampleRate = 1.0; options1.Native.ExperimentalOptions.SessionReplay.OnErrorSampleRate = 1.0; #endif - var enabledBinder = new MauiSessionReplayMaskControlsOfTypeBinder(options1); + var iOptions1 = Microsoft.Extensions.Options.Options.Create(options1); + var enabledBinder = new MauiSessionReplayMaskControlsOfTypeBinder(iOptions1); var options2 = new SentryMauiOptions { Dsn = ValidDsn }; #if __ANDROID__ options2.Native.ExperimentalOptions.SessionReplay.SessionSampleRate = 0.0; options2.Native.ExperimentalOptions.SessionReplay.OnErrorSampleRate = 0.0; #endif - var disabledBinder = new MauiSessionReplayMaskControlsOfTypeBinder(options2); + var iOptions2 = Microsoft.Extensions.Options.Options.Create(options2); + var disabledBinder = new MauiSessionReplayMaskControlsOfTypeBinder(iOptions2); var buttonEventBinder = new MauiButtonEventsBinder(); From 02cf1758275cab62fbc71829f8a8f2dbf30e255c Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 4 Sep 2025 10:04:35 +1200 Subject: [PATCH 15/15] Update CHANGELOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stefan Pölz <38893694+Flash0ver@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f95ead296d..45457b0063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ - Ensure all buffered logs are sent to Sentry when the application terminates unexpectedly ([#4425](https://github.com/getsentry/sentry-dotnet/pull/4425)) - `InvalidOperationException` potentially thrown during a race condition, especially in concurrent high-volume logging scenarios ([#4428](https://github.com/getsentry/sentry-dotnet/pull/4428)) - Blocking calls are no longer treated as unhandled crashes ([#4458](https://github.com/getsentry/sentry-dotnet/pull/4458)) -- Only applies Session Replay masks to specific controls types when necessary, to avoid performance issues in MAUI apps with complex UIs ([#4445](https://github.com/getsentry/sentry-dotnet/pull/4445)) +- Only apply Session Replay masks to specific control types when necessary to avoid performance issues in MAUI apps with complex UIs ([#4445](https://github.com/getsentry/sentry-dotnet/pull/4445)) ### Dependencies