diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index cc551e2be..a6ca0fcb0 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -91,6 +91,7 @@ Future setupSentry( options.experimental.replay.sessionSampleRate = 1.0; options.experimental.replay.onErrorSampleRate = 1.0; + options.experimental.replay.maskAllText = false; _isIntegrationTest = isIntegrationTest; if (_isIntegrationTest) { @@ -198,6 +199,17 @@ class MainScaffold extends StatelessWidget { body: SingleChildScrollView( child: Column( children: [ + const CustomPaint( + child: Center( + child: Text( + 'Custom paint', + style: TextStyle( + fontSize: 40.0, + fontWeight: FontWeight.w900, + color: Color.fromARGB(255, 166, 7, 7), + ), + ), + )), if (_isIntegrationTest) const IntegrationTestWidget(), const Center(child: Text('Trigger an action.\n')), const Padding( diff --git a/flutter/lib/src/replay/widget_filter.dart b/flutter/lib/src/replay/widget_filter.dart index 32231a0d1..ec1d6b46f 100644 --- a/flutter/lib/src/replay/widget_filter.dart +++ b/flutter/lib/src/replay/widget_filter.dart @@ -197,7 +197,8 @@ class WidgetFilterItem { const WidgetFilterItem(this.color, this.bounds); } -extension on Element { +@internal +extension ElementParentFinder on Element { Element? get parent { Element? result; visitAncestorElements((el) { diff --git a/flutter/lib/src/sentry_replay_options.dart b/flutter/lib/src/sentry_replay_options.dart index a6e83fec4..abe1e3b72 100644 --- a/flutter/lib/src/sentry_replay_options.dart +++ b/flutter/lib/src/sentry_replay_options.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; @@ -75,6 +76,23 @@ class SentryReplayOptions { rules .add(const SentryMaskingCustomRule(_maskImagesExceptAssets)); } + + // Mask CustomPaint, except for DEBUG banner which overlays the whole screen in debug builds. + if (kReleaseMode) { + rules.add(const SentryMaskingConstantRule( + SentryMaskingDecision.mask)); + } else { + rules.add(SentryMaskingCustomRule( + (Element element, CustomPaint widget) { + final parent = element.parent; + if (parent is StatefulElement && + (parent.widget is Banner) && + (parent.widget as Banner).message == 'DEBUG') { + return SentryMaskingDecision.continueProcessing; + } + return SentryMaskingDecision.mask; + })); + } } else { assert(!maskAssetImages, "maskAssetImages can't be true if maskAllImages is false");