Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
Prev Previous commit
Next Next commit
Make custom masking opt in
  • Loading branch information
jamescrosswell committed Aug 21, 2025
commit 54cda92958131c91c83a13324ce29e2b1bf4a2f3
18 changes: 2 additions & 16 deletions src/Sentry.Maui/Internal/MauiCustomSessionReplayMaskBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,22 @@ namespace Sentry.Maui.Internal;
internal class MauiCustomSessionReplayMaskBinder : IMauiElementEventBinder
{
private readonly SentryMauiOptions _options;
private readonly bool _isEnabled;

public MauiCustomSessionReplayMaskBinder(IOptions<SentryMauiOptions> 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
}

/// <inheritdoc />
public void Bind(VisualElement element, Action<BreadcrumbEvent> _)
{
if (_isEnabled)
{
element.Loaded += OnElementLoaded;
}
element.Loaded += OnElementLoaded;
}

/// <inheritdoc />
public void UnBind(VisualElement element)
{
if (_isEnabled)
{
element.Loaded -= OnElementLoaded;
}
element.Loaded -= OnElementLoaded;
}

internal void OnElementLoaded(object? sender, EventArgs _)
Expand Down
11 changes: 9 additions & 2 deletions src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,18 @@ public static MauiAppBuilder UseSentry(this MauiAppBuilder builder,
services.AddSingleton<IMauiElementEventBinder, MauiButtonEventsBinder>();
services.AddSingleton<IMauiElementEventBinder, MauiImageButtonEventsBinder>();
services.AddSingleton<IMauiElementEventBinder, MauiGestureRecognizerEventsBinder>();
services.AddSingleton<IMauiElementEventBinder, MauiCustomSessionReplayMaskBinder>();

// 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<IMauiElementEventBinder, MauiCustomSessionReplayMaskBinder>();
}
#endif
Copy link

Choose a reason for hiding this comment

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

Bug: Session Replay Masking Fails Without Programmatic Configuration

The MauiCustomSessionReplayMaskBinder isn't registered unless programmatic type masking is configured. This causes XML attribute-based session replay masking (e.g., sentry:SessionReplay.Mask) to be ignored, contradicting the sample's guidance on using XML attributes as an alternative.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not true.

Copy link
Member

Choose a reason for hiding this comment

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

Is that untrue because

  • the BindableProperty does set the tags for native (""sentry-mask"" and "sentry-unmask") already
  • where the IMauiElementEventBinder must now do the same for all configured MaskedControls/UnmaskedControls

?
(just clarifying if I'm understanding it)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The XML attribute-based session replay masking works just fine without this MAUI binder. It's taken care of by our custom bindable properties (which are always enabled):

/// <summary>
/// Mask can be used to either unmask or mask a view.
/// </summary>
public static readonly BindableProperty MaskProperty =
BindableProperty.CreateAttached(
"Mask",
typeof(SessionReplayMaskMode),
typeof(SessionReplay),
defaultValue: SessionReplayMaskMode.Mask,
propertyChanged: OnMaskChanged);

Under the hood we use these to set the native android tags that are used by the Java SDK in the OnUnmaskedElementHandlerChanged and OnMaskedElementHandlerChanged handlers.

foreach (var eventBinder in options.IntegrationEventBinders)
{
eventBinder.Register(services);
Expand Down
48 changes: 25 additions & 23 deletions src/Sentry/Platforms/Android/NativeOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -273,32 +273,13 @@ public class NativeSentryReplayOptions
public bool MaskAllImages { get; set; } = true;
public bool MaskAllText { get; set; } = true;

/// <summary>
/// <para>
/// 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.
/// </para>
/// <para>
/// In such cases you have a few different options:
/// <list type="bullet">
/// <item>
/// <description>Disable Session Replays entirely</description>
/// </item>
/// <item>
/// <description>Mask all text and all images</description>
/// </item>
/// <item>
/// <description>Disable custom session replay masks (so nothing gets masked)</description>
/// </item>
/// </list>
/// Set this option to <c>true</c> to disable custom session replay masks.
/// </para>
/// </summary>
public bool DisableCustomSessionReplayMasks { get; set; } = false;

internal HashSet<Type> MaskedControls { get; } = [];
internal HashSet<Type> UnmaskedControls { get; } = [];

internal bool IsCustomMaskingEnabled { get; private set; }

internal bool IsSessionReplayEnabled => OnErrorSampleRate > 0.0 || SessionSampleRate > 0.0;

public void MaskControlsOfType<T>()
{
MaskedControls.Add(typeof(T));
Expand All @@ -308,6 +289,27 @@ public void UnmaskControlsOfType<T>()
{
UnmaskedControls.Add(typeof(T));
}

/// <summary>
/// <para>
/// The <see cref="MaskAllImages"/> and <see cref="MaskAllText"/> and <see cref="MaskControlsOfType"/>
/// options allow you to set the default masking behaviour for all visual elements of certain types.
/// </para>
/// <para>
/// 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).
/// </para>
/// <remarks>
/// WARNING: In apps with complex user interfaces, consisting of hundreds of visual controls on a single
/// page, enabling this option may cause performance issues.
/// </remarks>
/// </summary>
public NativeSentryReplayOptions EnableCustomSessionReplayMasks()
{
IsCustomMaskingEnabled = true;
return this;
}
}

/// <summary>
Expand Down