Skip to content

Commit 3adbea9

Browse files
authored
feat: improve obscure rectangle fit/size (#2236)
1 parent a40bb7c commit 3adbea9

File tree

4 files changed

+77
-18
lines changed

4 files changed

+77
-18
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
### Features
66

7-
- Session replay Alpha for Android and iOS ([#2208](https://github.com/getsentry/sentry-dart/pull/2208), [#2269](https://github.com/getsentry/sentry-dart/pull/2269)).
7+
- Session replay Alpha for Android and iOS ([#2208](https://github.com/getsentry/sentry-dart/pull/2208), [#2269](https://github.com/getsentry/sentry-dart/pull/2269), [#2236](https://github.com/getsentry/sentry-dart/pull/2236)).
88

99
To try out replay, you can set following options (access is limited to early access orgs on Sentry. If you're interested, [sign up for the waitlist](https://sentry.io/lp/mobile-replay-beta/)):
1010

@@ -27,6 +27,7 @@
2727
...
2828
options.allowUrls = ["^https://sentry.com.*\$", "my-custom-domain"];
2929
options.denyUrls = ["^.*ends-with-this\$", "denied-url"];
30+
options.denyUrls = ["^.*ends-with-this\$", "denied-url"];
3031
},
3132
appRunner: () => runApp(MyApp()),
3233
);

flutter/lib/src/replay/widget_filter.dart

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:flutter/rendering.dart';
12
import 'package:flutter/services.dart';
23
import 'package:flutter/widgets.dart';
34
import 'package:meta/meta.dart';
@@ -77,25 +78,25 @@ class WidgetFilter {
7778

7879
final renderObject = element.renderObject;
7980
if (renderObject is! RenderBox) {
80-
_cantObscure(widget, "it's renderObject is not a RenderBox");
81+
_cantObscure(widget, "its renderObject is not a RenderBox");
8182
return false;
8283
}
8384

84-
final size = element.size;
85-
if (size == null) {
86-
_cantObscure(widget, "it's renderObject has a null size");
87-
return false;
85+
var rect = _boundingBox(renderObject);
86+
87+
// If it's a clipped render object, use parent's offset and size.
88+
// This helps with text fields which often have oversized render objects.
89+
if (renderObject.parent is RenderStack) {
90+
final renderStack = (renderObject.parent as RenderStack);
91+
final clipBehavior = renderStack.clipBehavior;
92+
if (clipBehavior == Clip.hardEdge ||
93+
clipBehavior == Clip.antiAlias ||
94+
clipBehavior == Clip.antiAliasWithSaveLayer) {
95+
final clipRect = _boundingBox(renderStack);
96+
rect = rect.intersect(clipRect);
97+
}
8898
}
8999

90-
final offset = renderObject.localToGlobal(Offset.zero);
91-
92-
final rect = Rect.fromLTWH(
93-
offset.dx * _pixelRatio,
94-
offset.dy * _pixelRatio,
95-
size.width * _pixelRatio,
96-
size.height * _pixelRatio,
97-
);
98-
99100
if (!rect.overlaps(_bounds)) {
100101
assert(() {
101102
logger(SentryLevel.debug, "WidgetFilter skipping offscreen: $widget");
@@ -151,6 +152,17 @@ class WidgetFilter {
151152
"WidgetFilter cannot obscure widget $widget: $message");
152153
}
153154
}
155+
156+
@pragma('vm:prefer-inline')
157+
Rect _boundingBox(RenderBox box) {
158+
final offset = box.localToGlobal(Offset.zero);
159+
return Rect.fromLTWH(
160+
offset.dx * _pixelRatio,
161+
offset.dy * _pixelRatio,
162+
box.size.width * _pixelRatio,
163+
box.size.height * _pixelRatio,
164+
);
165+
}
154166
}
155167

156168
class WidgetFilterItem {

flutter/test/replay/test_widget.dart

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,22 @@ Future<Element> pumpTestElement(WidgetTester tester,
3333
Opacity(opacity: 0, child: newImage()),
3434
Offstage(offstage: true, child: Text('Offstage text')),
3535
Offstage(offstage: true, child: newImage()),
36+
Text(dummyText),
37+
SizedBox(
38+
width: 100,
39+
height: 20,
40+
child: Stack(children: [
41+
Positioned(
42+
top: 0,
43+
left: 0,
44+
width: 50,
45+
child: Text(dummyText)),
46+
Positioned(
47+
top: 0,
48+
left: 0,
49+
width: 50,
50+
child: newImage(width: 500, height: 500)),
51+
]))
3652
],
3753
),
3854
),
@@ -55,4 +71,10 @@ final testImageData = Uint8List.fromList([
5571
// This comment prevents dartfmt reformatting this to single-item lines.
5672
]);
5773

58-
Image newImage() => Image.memory(testImageData, width: 1, height: 1);
74+
Image newImage({double width = 1, double height = 1}) => Image.memory(
75+
testImageData,
76+
width: width,
77+
height: height,
78+
);
79+
80+
const dummyText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';

flutter/test/replay/widget_filter_test.dart

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@ void main() async {
2121
rootAssetBundle: rootBundle,
2222
);
2323

24+
boundsRect(WidgetFilterItem item) =>
25+
'${item.bounds.width.floor()}x${item.bounds.height.floor()}';
26+
2427
group('redact text', () {
2528
testWidgets('redacts the correct number of elements', (tester) async {
2629
final sut = createSut(redactText: true);
2730
final element = await pumpTestElement(tester);
2831
sut.obscure(element, 1.0, defaultBounds);
29-
expect(sut.items.length, 2);
32+
expect(sut.items.length, 4);
3033
});
3134

3235
testWidgets('does not redact text when disabled', (tester) async {
@@ -43,14 +46,25 @@ void main() async {
4346
sut.obscure(element, 1.0, Rect.fromLTRB(0, 0, 100, 100));
4447
expect(sut.items.length, 1);
4548
});
49+
50+
testWidgets('correctly determines sizes', (tester) async {
51+
final sut = createSut(redactText: true);
52+
final element = await pumpTestElement(tester);
53+
sut.obscure(element, 1.0, defaultBounds);
54+
expect(sut.items.length, 4);
55+
expect(boundsRect(sut.items[0]), '624x48');
56+
expect(boundsRect(sut.items[1]), '169x20');
57+
expect(boundsRect(sut.items[2]), '800x192');
58+
expect(boundsRect(sut.items[3]), '50x20');
59+
});
4660
});
4761

4862
group('redact images', () {
4963
testWidgets('redacts the correct number of elements', (tester) async {
5064
final sut = createSut(redactImages: true);
5165
final element = await pumpTestElement(tester);
5266
sut.obscure(element, 1.0, defaultBounds);
53-
expect(sut.items.length, 2);
67+
expect(sut.items.length, 3);
5468
});
5569

5670
// Note: we cannot currently test actual asset images without either:
@@ -93,6 +107,16 @@ void main() async {
93107
sut.obscure(element, 1.0, Rect.fromLTRB(0, 0, 500, 100));
94108
expect(sut.items.length, 1);
95109
});
110+
111+
testWidgets('correctly determines sizes', (tester) async {
112+
final sut = createSut(redactImages: true);
113+
final element = await pumpTestElement(tester);
114+
sut.obscure(element, 1.0, defaultBounds);
115+
expect(sut.items.length, 3);
116+
expect(boundsRect(sut.items[0]), '1x1');
117+
expect(boundsRect(sut.items[1]), '1x1');
118+
expect(boundsRect(sut.items[2]), '50x20');
119+
});
96120
});
97121
}
98122

0 commit comments

Comments
 (0)