Skip to content

Commit f76a31d

Browse files
committed
Update
1 parent efa9362 commit f76a31d

File tree

4 files changed

+76
-48
lines changed

4 files changed

+76
-48
lines changed

flutter/lib/src/screenshot/recorder.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'dart:ui';
55
import 'package:flutter/cupertino.dart' as cupertino;
66
import 'package:flutter/material.dart' as material;
77
import 'package:flutter/rendering.dart';
8+
import 'package:flutter/services.dart';
89
import 'package:flutter/widgets.dart' as widgets;
910
import 'package:meta/meta.dart';
1011

@@ -34,8 +35,8 @@ class ScreenshotRecorder {
3435
}) {
3536
privacyOptions ??= options.privacy;
3637

37-
final maskingConfig =
38-
privacyOptions.buildMaskingConfig(_log, options.runtimeChecker);
38+
final maskingConfig = privacyOptions.buildMaskingConfig(
39+
_log, options.runtimeChecker, FlutterVersion.version);
3940
_maskingConfig = maskingConfig.length > 0 ? maskingConfig : null;
4041
}
4142

flutter/lib/src/sentry_privacy_options.dart

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ class SentryPrivacyOptions {
2727
Iterable<SentryMaskingRule> get userMaskingRules => _userMaskingRules;
2828

2929
@internal
30-
SentryMaskingConfig buildMaskingConfig(
31-
SdkLogCallback logger, RuntimeChecker runtimeChecker) {
30+
SentryMaskingConfig buildMaskingConfig(SdkLogCallback logger,
31+
RuntimeChecker runtimeChecker, String? flutterVersion) {
3232
// First, we collect rules defined by the user (so they're applied first).
3333
final rules = _userMaskingRules.toList();
3434

@@ -75,7 +75,7 @@ class SentryPrivacyOptions {
7575
));
7676
}
7777

78-
_maybeAddSensitiveContentRule(rules);
78+
maybeAddSensitiveContentRule(rules, flutterVersion);
7979

8080
// In Debug mode, check if users explicitly mask (or unmask) widgets that
8181
// look like they should be masked, e.g. Videos, WebViews, etc.
@@ -161,63 +161,63 @@ class SentryPrivacyOptions {
161161
}
162162
}
163163

164-
/// Adds a masking rule for the [SensitiveContent] widget.
165-
///
166-
/// The rule masks any widget that exposes a `sensitivity` property which is an
167-
/// [Enum]. This is how the [SensitiveContent] widget can be detected
168-
/// without depending on its type directly (which would fail to compile on
169-
/// older Flutter versions).
170-
void _maybeAddSensitiveContentRule(List<SentryMaskingRule> rules) {
171-
final flutterVersion = FlutterVersion.version;
172-
173-
// Only add the rule if we can statically determine that the running
174-
// Flutter SDK is at least 3.33 – that is the first version that contains
175-
// the SensitiveContent widget. For older SDKs we skip the rule entirely.
176-
if (flutterVersion == null) {
177-
return;
178-
}
164+
/// Returns `true` if a SensitiveContent masking rule _should_ be added for a
165+
/// given [flutterVersion] string. The SensitiveContent widget was introduced
166+
/// in Flutter 3.33, therefore we only add the masking rule when the detected
167+
/// version is >= 3.33.
168+
bool _shouldAddSensitiveContentRule(String? flutterVersion) {
169+
if (flutterVersion == null) return false;
179170

180171
final parts = flutterVersion.split('.');
181172
if (parts.length < 2) {
182173
// Malformed version string – be safe and skip.
183-
return;
174+
return false;
184175
}
185176

186177
const requiredMajor = 3;
187178
const requiredMinor = 33;
188-
final major = int.tryParse(parts[0]) ?? 0;
189-
final minor = int.tryParse(parts[1]) ?? 0;
179+
final major = int.tryParse(parts[0]);
180+
final minor = int.tryParse(parts[1]);
181+
if (major == null || minor == null) {
182+
// Not numeric – treat as unknown.
183+
return false;
184+
}
190185

191-
final isNewEnough = major > requiredMajor ||
186+
return major > requiredMajor ||
192187
(major == requiredMajor && minor >= requiredMinor);
188+
}
193189

194-
if (!isNewEnough) {
195-
// Older than 3.33 – skip.
196-
return;
190+
/// Adds a masking rule for the [SensitiveContent] widget.
191+
///
192+
/// The rule masks any widget that exposes a `sensitivity` property which is an
193+
/// [Enum]. This is how the [SensitiveContent] widget can be detected
194+
/// without depending on its type directly (which would fail to compile on
195+
/// older Flutter versions).
196+
@visibleForTesting
197+
void maybeAddSensitiveContentRule(
198+
List<SentryMaskingRule> rules, String? flutterVersion) {
199+
if (!_shouldAddSensitiveContentRule(flutterVersion)) return;
200+
201+
SentryMaskingDecision maskSensitiveContent(Element element, Widget widget) {
202+
try {
203+
final dynamic dynWidget = widget;
204+
final sensitivity = dynWidget.sensitivity;
205+
// If the property exists, we assume this is the SensitiveContent widget.
206+
assert(sensitivity is Enum);
207+
return SentryMaskingDecision.mask;
208+
} catch (_) {
209+
// Property not found – continue processing other rules.
210+
return SentryMaskingDecision.continueProcessing;
211+
}
197212
}
198213

199214
rules.add(SentryMaskingCustomRule<Widget>(
200-
callback: _maskSensitiveContent,
215+
callback: maskSensitiveContent,
201216
name: 'SensitiveContent',
202217
description: 'Mask SensitiveContent widget.',
203218
));
204219
}
205220

206-
/// Callback that detects the future `SensitiveContent` widget by checking for
207-
/// the presence of a `sensitivity` property at runtime.
208-
SentryMaskingDecision _maskSensitiveContent(Element element, Widget widget) {
209-
try {
210-
final dynamic dynWidget = widget;
211-
final sensitivity = dynWidget.sensitivity;
212-
// If the property exists, we assume this is the SensitiveContent widget.
213-
assert(sensitivity is Enum);
214-
return SentryMaskingDecision.mask;
215-
} catch (_) {
216-
// Property not found – continue processing other rules.
217-
return SentryMaskingDecision.continueProcessing;
218-
}
219-
}
220-
221221
SentryMaskingDecision _maskImagesExceptAssets(Element element, Image widget) {
222222
final image = widget.image;
223223
if (image is AssetBundleImageProvider) {

flutter/test/screenshot/masking_config_test.dart

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'package:collection/collection.dart';
2+
import 'package:flutter/services.dart';
13
import 'package:flutter/widgets.dart';
24
import 'package:flutter_test/flutter_test.dart';
35
import 'package:sentry_flutter/sentry_flutter.dart';
@@ -146,9 +148,10 @@ void main() async {
146148
});
147149

148150
group('$SentryReplayOptions.buildMaskingConfig()', () {
149-
List<String> rulesAsStrings(SentryPrivacyOptions options) {
150-
final config =
151-
options.buildMaskingConfig(MockLogger().call, RuntimeChecker());
151+
List<String> rulesAsStrings(SentryPrivacyOptions options,
152+
{String? flutterVersion}) {
153+
final config = options.buildMaskingConfig(
154+
MockLogger().call, RuntimeChecker(), flutterVersion);
152155
return config.rules
153156
.map((rule) => rule.toString())
154157
// These normalize the string on VM & js & wasm:
@@ -222,6 +225,30 @@ void main() async {
222225
]);
223226
});
224227

228+
test(
229+
'SensitiveContent rule is automatically added when current Flutter version is equal or newer than 3.33',
230+
() {
231+
final testCases = <String?, bool>{
232+
null: false,
233+
'1.0.0': false,
234+
'3.32.5': false,
235+
'3.33.0': true,
236+
'3.40.0': true,
237+
'4.0.0': true,
238+
'3.a.b': false,
239+
'invalid': false,
240+
};
241+
242+
testCases.forEach((version, shouldAdd) {
243+
final sut = SentryPrivacyOptions();
244+
expect(
245+
rulesAsStrings(sut, flutterVersion: version).contains(
246+
'SentryMaskingCustomRule<SensitiveContent>(Mask SensitiveContent widget.)'),
247+
shouldAdd,
248+
reason: 'Test failed with version: $version');
249+
});
250+
});
251+
225252
group('user rules', () {
226253
final defaultRules = [
227254
...alwaysEnabledRules,

flutter/test/screenshot/widget_filter_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ void main() async {
2929
..maskAllImages = redactImages
3030
..maskAllText = redactText;
3131
logger.clear();
32-
final maskingConfig = privacyOptions.buildMaskingConfig(
33-
logger.call, runtimeChecker ?? RuntimeChecker());
32+
final maskingConfig = privacyOptions.buildMaskingConfig(logger.call,
33+
runtimeChecker ?? RuntimeChecker(), FlutterVersion.version);
3434
return WidgetFilter(maskingConfig, logger.call);
3535
};
3636

0 commit comments

Comments
 (0)