Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/mask-custom-painted-content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"posthog_flutter": minor
---

Add `sessionReplayConfig.maskCustomPaint`, defaulting to `false`, to opt into masking the full bounds of custom-painted widgets independently of text and image masking.
11 changes: 11 additions & 0 deletions api/posthog_flutter.api.json
Original file line number Diff line number Diff line change
Expand Up @@ -2663,6 +2663,17 @@
"relativePath": "lib/src/posthog_config.dart",
"typeName": "bool"
},
{
"entryPoints": [],
"isDeprecated": false,
"isExperimental": false,
"isReadable": true,
"isStatic": false,
"isWriteable": true,
"name": "maskCustomPaint",
"relativePath": "lib/src/posthog_config.dart",
"typeName": "bool"
},
{
"entryPoints": [],
"isDeprecated": false,
Expand Down
17 changes: 17 additions & 0 deletions posthog_flutter/lib/src/posthog_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -684,15 +684,32 @@ class PostHogSessionReplayConfig {
/// Enable masking of all text and text input fields.
/// Default: true.
///
/// Does not mask text drawn by CustomPainter. Enable [maskCustomPaint] or
/// wrap sensitive custom-painted widgets in PostHogMaskWidget to mask them.
///
/// With [captureNativeScreens] enabled, setting this false also unmasks text
/// on captured native screens, including native input fields (passwords,
/// card numbers) you may not have built.
var maskAllTexts = true;

/// Enable masking of all images.
/// Default: true.
///
/// Does not mask images drawn by CustomPainter. Enable [maskCustomPaint] or
/// wrap sensitive custom-painted widgets in PostHogMaskWidget to mask them.
var maskAllImages = true;

/// Mask the full bounds of CustomPaint widgets with a painter or
/// foregroundPainter, including their children.
/// Default: false.
///
/// This is independent of [maskAllTexts] and [maskAllImages]. Canvas contents
/// cannot be inspected for individual text or images, so enabling this also
/// masks custom-painted decorations in Flutter widgets. Disable MaterialApp's
/// debugShowCheckedModeBanner when using this option: its full-window
/// CustomPaint otherwise masks the entire screen.
var maskCustomPaint = false;

/// Deprecated setter that forwards assigned values to [throttleDelay].
///
/// Debouncer delay used to reduce the number of snapshots captured and reduce
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flutter/rendering.dart';
import 'package:posthog_flutter/posthog_flutter.dart';
import 'package:posthog_flutter/src/replay/element_parsers/element_data.dart';
import 'package:posthog_flutter/src/replay/element_parsers/element_parser.dart';
import 'package:posthog_flutter/src/replay/element_parsers/element_parsers_const.dart';
import 'package:posthog_flutter/src/replay/mask/posthog_mask_controller.dart';

class ElementObjectParser {
Expand Down Expand Up @@ -64,6 +65,20 @@ class ElementObjectParser {
}
}

final renderObject = element.renderObject;
if (renderObject is RenderCustomPaint &&
(renderObject.painter != null ||
renderObject.foregroundPainter != null)) {
// Canvas commands cannot be inspected for sensitive text or images.
final parser = PostHogMaskController.instance
.parsers[ElementParsersConst.getRuntimeType<RenderCustomPaint>()];
final elementData = parser?.relate(element);
if (elementData != null) {
activeElementData.addChildren(elementData);
return elementData;
}
}

if (element.renderObject is RenderImage) {
final dataType = element.renderObject.runtimeType.toString();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ class ElementParsersConst {
final Map<String, ElementParser> parsersMap = {};

ElementParsersConst(this._factory, PostHogSessionReplayConfig? config) {
if (config?.maskCustomPaint ?? false) {
registerElementParser<RenderCustomPaint>();
}
if (config?.maskAllImages ?? true) {
registerElementParser<RenderImage>();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -642,8 +642,9 @@ class ScreenshotCapturer {
);

final replayConfig = effectiveConfig.sessionReplayConfig;
final maskAllContent =
replayConfig.maskAllTexts || replayConfig.maskAllImages;
final maskAllContent = replayConfig.maskAllTexts ||
replayConfig.maskAllImages ||
replayConfig.maskCustomPaint;

ui.Image? image;
ui.PictureRecorder? recorder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,9 @@ class WebCanvasMaskProvider {
'or declare maskRegionsFn in posthog.init to enable it.',
);
final replayConfig = _config.sessionReplayConfig;
if (!replayConfig.maskAllTexts && !replayConfig.maskAllImages) {
if (!replayConfig.maskAllTexts &&
!replayConfig.maskAllImages &&
!replayConfig.maskCustomPaint) {
return;
}
final captureCanvas =
Expand Down Expand Up @@ -644,8 +646,9 @@ class WebCanvasMaskProvider {

final replayConfig = _config.sessionReplayConfig;
final elements = PostHogMaskController.instance.getMaskElements(
includeAllWidgets:
replayConfig.maskAllTexts || replayConfig.maskAllImages,
includeAllWidgets: replayConfig.maskAllTexts ||
replayConfig.maskAllImages ||
replayConfig.maskCustomPaint,
);
if (elements == null) {
_cachedContainerRects = null;
Expand Down
Loading
Loading