Skip to content

Commit b58b91d

Browse files
authored
Merge branch 'main' into feat/capture-touch-breadcrumbs
2 parents 7563246 + 77db8d4 commit b58b91d

File tree

8 files changed

+111
-146
lines changed

8 files changed

+111
-146
lines changed

CHANGELOG.md

+2-1
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/event_processor/url_filter/html_url_filter_event_processor.dart

+4-21
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,23 @@ class WebUrlFilterEventProcessor implements UrlFilterEventProcessor {
1515
this._options,
1616
);
1717

18+
final html.Window _window = html.window;
1819
final SentryFlutterOptions _options;
1920

2021
@override
2122
SentryEvent? apply(SentryEvent event, Hint hint) {
22-
final frames = _getStacktraceFrames(event);
23-
final lastPath = frames?.first?.absPath;
24-
25-
if (lastPath == null) {
26-
return event;
27-
}
23+
final url = _window.location.toString();
2824

2925
if (_options.allowUrls.isNotEmpty &&
30-
!isMatchingRegexPattern(lastPath, _options.allowUrls)) {
26+
!isMatchingRegexPattern(url, _options.allowUrls)) {
3127
return null;
3228
}
3329

3430
if (_options.denyUrls.isNotEmpty &&
35-
isMatchingRegexPattern(lastPath, _options.denyUrls)) {
31+
isMatchingRegexPattern(url, _options.denyUrls)) {
3632
return null;
3733
}
3834

3935
return event;
4036
}
41-
42-
Iterable<SentryStackFrame?>? _getStacktraceFrames(SentryEvent event) {
43-
if (event.exceptions?.isNotEmpty == true) {
44-
return event.exceptions?.first.stackTrace?.frames;
45-
}
46-
if (event.threads?.isNotEmpty == true) {
47-
final stacktraces = event.threads?.map((e) => e.stacktrace);
48-
return stacktraces
49-
?.where((element) => element != null)
50-
.expand((element) => element!.frames);
51-
}
52-
return null;
53-
}
5437
}

flutter/lib/src/event_processor/url_filter/web_url_filter_event_processor.dart

+4-21
Original file line numberDiff line numberDiff line change
@@ -17,40 +17,23 @@ class WebUrlFilterEventProcessor implements UrlFilterEventProcessor {
1717
this._options,
1818
);
1919

20+
final web.Window _window = web.window;
2021
final SentryFlutterOptions _options;
2122

2223
@override
2324
SentryEvent? apply(SentryEvent event, Hint hint) {
24-
final frames = _getStacktraceFrames(event);
25-
final lastPath = frames?.first?.absPath;
26-
27-
if (lastPath == null) {
28-
return event;
29-
}
25+
final url = _window.location.toString();
3026

3127
if (_options.allowUrls.isNotEmpty &&
32-
!isMatchingRegexPattern(lastPath, _options.allowUrls)) {
28+
!isMatchingRegexPattern(url, _options.allowUrls)) {
3329
return null;
3430
}
3531

3632
if (_options.denyUrls.isNotEmpty &&
37-
isMatchingRegexPattern(lastPath, _options.denyUrls)) {
33+
isMatchingRegexPattern(url, _options.denyUrls)) {
3834
return null;
3935
}
4036

4137
return event;
4238
}
43-
44-
Iterable<SentryStackFrame?>? _getStacktraceFrames(SentryEvent event) {
45-
if (event.exceptions?.isNotEmpty == true) {
46-
return event.exceptions?.first.stackTrace?.frames;
47-
}
48-
if (event.threads?.isNotEmpty == true) {
49-
final stacktraces = event.threads?.map((e) => e.stacktrace);
50-
return stacktraces
51-
?.where((element) => element != null)
52-
.expand((element) => element!.frames);
53-
}
54-
return null;
55-
}
5639
}

flutter/lib/src/replay/widget_filter.dart

+26-14
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/event_processor/url_filter/web_url_filter_event_processor_test.dart

+25-85
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import 'package:sentry_flutter/src/event_processor/url_filter/url_filter_event_p
77

88
// can be tested on command line with
99
// `flutter test --platform=chrome test/event_processor/url_filter/web_url_filter_event_processor_test.dart`
10+
// The URL looks something like this: http://localhost:58551/event_processor/url_filter/web_url_filter_event_processor_test.html
11+
1012
void main() {
1113
group(UrlFilterEventProcessor, () {
1214
late Fixture fixture;
@@ -16,57 +18,53 @@ void main() {
1618
});
1719

1820
test('returns event if no allowUrl and no denyUrl is set', () async {
19-
SentryEvent? event = SentryEvent(
20-
request: SentryRequest(
21-
url: 'foo.bar',
22-
),
23-
);
21+
final event = SentryEvent();
22+
final eventProcessor = fixture.getSut();
2423

25-
var eventProcessor = fixture.getSut();
26-
event = await eventProcessor.apply(event, Hint());
24+
final processedEvent = await eventProcessor.apply(event, Hint());
2725

28-
expect(event, isNotNull);
26+
expect(processedEvent, isNotNull);
2927
});
3028

3129
test('returns null if allowUrl is set and does not match with url',
3230
() async {
33-
final event = _createEventWithException("foo.bar");
31+
final event = SentryEvent();
3432
fixture.options.allowUrls = ["another.url"];
33+
final eventProcessor = fixture.getSut();
3534

36-
var eventProcessor = fixture.getSut();
3735
final processedEvent = await eventProcessor.apply(event, Hint());
3836

3937
expect(processedEvent, isNull);
4038
});
4139

4240
test('returns event if allowUrl is set and does partially match with url',
4341
() async {
44-
final event = _createEventWithException("foo.bar");
45-
fixture.options.allowUrls = ["bar"];
42+
final event = SentryEvent();
43+
fixture.options.allowUrls = ["event_processor_test"];
44+
final eventProcessor = fixture.getSut();
4645

47-
var eventProcessor = fixture.getSut();
4846
final processedEvent = await eventProcessor.apply(event, Hint());
4947

5048
expect(processedEvent, isNotNull);
5149
});
5250

5351
test('returns event if denyUrl is set and does not match with url',
5452
() async {
55-
final event = _createEventWithException("foo.bar");
53+
final event = SentryEvent();
5654
fixture.options.denyUrls = ["another.url"];
55+
final eventProcessor = fixture.getSut();
5756

58-
var eventProcessor = fixture.getSut();
5957
final processedEvent = await eventProcessor.apply(event, Hint());
6058

6159
expect(processedEvent, isNotNull);
6260
});
6361

6462
test('returns null if denyUrl is set and partially matches with url',
6563
() async {
66-
final event = _createEventWithException("foo.bar");
67-
fixture.options.denyUrls = ["bar"];
64+
final event = SentryEvent();
65+
fixture.options.denyUrls = ["event_processor_test"];
66+
final eventProcessor = fixture.getSut();
6867

69-
var eventProcessor = fixture.getSut();
7068
final processedEvent = await eventProcessor.apply(event, Hint());
7169

7270
expect(processedEvent, isNull);
@@ -75,13 +73,11 @@ void main() {
7573
test(
7674
'returns null if it is part of the allowed domain, but blocked for subdomain',
7775
() async {
78-
final event = _createEventWithException(
79-
"this.is/a/special/url/for-testing/this-feature");
80-
81-
fixture.options.allowUrls = ["^this.is/.*\$"];
82-
fixture.options.denyUrls = ["special"];
76+
final event = SentryEvent();
77+
fixture.options.allowUrls = [".*localhost.*\$"];
78+
fixture.options.denyUrls = ["event"];
79+
final eventProcessor = fixture.getSut();
8380

84-
var eventProcessor = fixture.getSut();
8581
final processedEvent = await eventProcessor.apply(event, Hint());
8682

8783
expect(processedEvent, isNull);
@@ -90,12 +86,11 @@ void main() {
9086
test(
9187
'returns event if it is part of the allowed domain, and not of the blocked for subdomain',
9288
() async {
93-
final event = _createEventWithException(
94-
"this.is/a/test/url/for-testing/this-feature");
95-
fixture.options.allowUrls = ["^this.is/.*\$"];
89+
final event = SentryEvent();
90+
fixture.options.allowUrls = [".*localhost.*\$"];
9691
fixture.options.denyUrls = ["special"];
92+
final eventProcessor = fixture.getSut();
9793

98-
var eventProcessor = fixture.getSut();
9994
final processedEvent = await eventProcessor.apply(event, Hint());
10095

10196
expect(processedEvent, isNotNull);
@@ -104,60 +99,15 @@ void main() {
10499
test(
105100
'returns null if it is not part of the allowed domain, and not of the blocked for subdomain',
106101
() async {
107-
final event = _createEventWithException(
108-
"another.url/for/a/test/testing/this-feature");
102+
final event = SentryEvent();
109103
fixture.options.allowUrls = ["^this.is/.*\$"];
110104
fixture.options.denyUrls = ["special"];
105+
final eventProcessor = fixture.getSut();
111106

112-
var eventProcessor = fixture.getSut();
113107
final processedEvent = await eventProcessor.apply(event, Hint());
114108

115109
expect(processedEvent, isNull);
116110
});
117-
118-
test(
119-
'returns event if denyUrl is set and not matching with url of first exception',
120-
() async {
121-
final frame1 = SentryStackFrame(absPath: "test.url");
122-
final st1 = SentryStackTrace(frames: [frame1]);
123-
final exception1 = SentryException(
124-
type: "test-type", value: "test-value", stackTrace: st1);
125-
126-
final frame2 = SentryStackFrame(absPath: "foo.bar");
127-
final st2 = SentryStackTrace(frames: [frame2]);
128-
final exception2 = SentryException(
129-
type: "test-type", value: "test-value", stackTrace: st2);
130-
131-
SentryEvent event = SentryEvent(exceptions: [exception1, exception2]);
132-
133-
fixture.options.denyUrls = ["bar"];
134-
135-
var eventProcessor = fixture.getSut();
136-
final processedEvent = await eventProcessor.apply(event, Hint());
137-
138-
expect(processedEvent, isNotNull);
139-
});
140-
141-
test(
142-
'returns event if denyUrl is set and not matching with url of first stacktraceframe',
143-
() async {
144-
final frame1 = SentryStackFrame(absPath: "test.url");
145-
final st1 = SentryStackTrace(frames: [frame1]);
146-
final thread1 = SentryThread(stacktrace: st1);
147-
148-
final frame2 = SentryStackFrame(absPath: "foo.bar");
149-
final st2 = SentryStackTrace(frames: [frame2]);
150-
final thread2 = SentryThread(stacktrace: st2);
151-
152-
SentryEvent event = SentryEvent(threads: [thread1, thread2]);
153-
154-
fixture.options.denyUrls = ["bar"];
155-
156-
var eventProcessor = fixture.getSut();
157-
final processedEvent = await eventProcessor.apply(event, Hint());
158-
159-
expect(processedEvent, isNotNull);
160-
});
161111
});
162112
}
163113

@@ -167,13 +117,3 @@ class Fixture {
167117
return UrlFilterEventProcessor(options);
168118
}
169119
}
170-
171-
SentryEvent _createEventWithException(String url) {
172-
final frame = SentryStackFrame(absPath: url);
173-
final st = SentryStackTrace(frames: [frame]);
174-
final exception =
175-
SentryException(type: "test-type", value: "test-value", stackTrace: st);
176-
SentryEvent event = SentryEvent(exceptions: [exception]);
177-
178-
return event;
179-
}

flutter/test/replay/test_widget.dart

+23-1
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.';

0 commit comments

Comments
 (0)