@@ -22,13 +22,13 @@ class WidgetFilter {
2222 _bounds = bounds;
2323 items.clear ();
2424 if (context is Element ) {
25- _obscure (context);
25+ _process (context);
2626 } else {
27- context.visitChildElements (_obscure );
27+ context.visitChildElements (_process );
2828 }
2929 }
3030
31- void _obscure (Element element) {
31+ void _process (Element element) {
3232 final widget = element.widget;
3333
3434 if (! _isVisible (widget)) {
@@ -39,46 +39,73 @@ class WidgetFilter {
3939 return ;
4040 }
4141
42- final obscured = _obscureIfNeeded (element, widget);
43- if (! obscured) {
44- element.visitChildElements (_obscure);
42+ if (! _shouldObscure (element, widget)) {
43+ // If this element should not be obscured, visit and check its children.
44+ element.visitChildElements (_process);
45+ } else {
46+ final item = _obscureElementOrParent (element, widget);
47+ if (item != null ) {
48+ items.add (item);
49+ }
4550 }
4651 }
4752
4853 @pragma ('vm:prefer-inline' )
49- bool _obscureIfNeeded (Element element, Widget widget) {
54+ bool _shouldObscure (Element element, Widget widget) {
55+ // Check if we should mask this widget based on the configuration.
5056 final maskingConfig = config[widget.runtimeType];
5157 if (maskingConfig == null ) {
5258 return false ;
5359 } else if (! maskingConfig.shouldMask (element, widget)) {
54- logger (SentryLevel .debug, "WidgetFilter skipping: $widget " );
55- return false ;
56- }
57-
58- Color ? color;
59- if (widget is Text ) {
60- color = widget.style? .color;
61- } else if (widget is EditableText ) {
62- color = widget.style.color;
63- } else if (widget is Image ) {
64- color = widget.color;
65- } else {
66- // No other type is currently obscured.
60+ assert (() {
61+ logger (SentryLevel .debug, "WidgetFilter skipping: $widget " );
62+ return true ;
63+ }());
6764 return false ;
6865 }
66+ return true ;
67+ }
6968
70- final renderObject = element.renderObject;
71- if (renderObject is ! RenderBox ) {
72- _cantObscure (widget, "its renderObject is not a RenderBox" );
73- return false ;
69+ /// Determine the color and bounding box of the widget.
70+ /// If the widget is offscreen, returns null.
71+ /// If the widget cannot be obscured, obscures the parent.
72+ @pragma ('vm:prefer-inline' )
73+ WidgetFilterItem ? _obscureElementOrParent (Element element, Widget widget) {
74+ while (true ) {
75+ try {
76+ return _obscure (element, widget);
77+ } catch (e, stackTrace) {
78+ final parent = element.parent;
79+ if (! _warnedWidgets.contains (widget.hashCode)) {
80+ _warnedWidgets.add (widget.hashCode);
81+ logger (
82+ SentryLevel .warning,
83+ 'WidgetFilter cannot obscure widget $widget : $e .'
84+ 'Obscuring the parent instead: ${parent ?.widget }.' ,
85+ stackTrace: stackTrace);
86+ }
87+ if (parent == null ) {
88+ return WidgetFilterItem (_defaultColor, _bounds);
89+ }
90+ element = parent;
91+ widget = element.widget;
92+ }
7493 }
94+ }
7595
76- var rect = _boundingBox (renderObject);
96+ /// Determine the color and bounding box of the widget.
97+ /// If the widget is offscreen, returns null.
98+ /// This function may throw in which case the caller is responsible for
99+ /// calling it again on the parent element.
100+ @pragma ('vm:prefer-inline' )
101+ WidgetFilterItem ? _obscure (Element element, Widget widget) {
102+ final RenderBox renderBox = element.renderObject as RenderBox ;
103+ var rect = _boundingBox (renderBox);
77104
78105 // If it's a clipped render object, use parent's offset and size.
79106 // This helps with text fields which often have oversized render objects.
80- if (renderObject .parent is RenderStack ) {
81- final renderStack = (renderObject .parent as RenderStack );
107+ if (renderBox .parent is RenderStack ) {
108+ final renderStack = (renderBox .parent as RenderStack );
82109 final clipBehavior = renderStack.clipBehavior;
83110 if (clipBehavior == Clip .hardEdge ||
84111 clipBehavior == Clip .antiAlias ||
@@ -93,19 +120,28 @@ class WidgetFilter {
93120 logger (SentryLevel .debug, "WidgetFilter skipping offscreen: $widget " );
94121 return true ;
95122 }());
96- return false ;
123+ return null ;
97124 }
98125
99- items.add (WidgetFilterItem (color ?? _defaultColor, rect));
100126 assert (() {
101127 logger (SentryLevel .debug, "WidgetFilter obscuring: $widget " );
102128 return true ;
103129 }());
104130
105- return true ;
131+ Color ? color;
132+ if (widget is Text ) {
133+ color = (widget).style? .color;
134+ } else if (widget is EditableText ) {
135+ color = (widget).style.color;
136+ } else if (widget is Image ) {
137+ color = (widget).color;
138+ }
139+
140+ return WidgetFilterItem (color ?? _defaultColor, rect);
106141 }
107142
108143 // We cut off some widgets early because they're not visible at all.
144+ @pragma ('vm:prefer-inline' )
109145 bool _isVisible (Widget widget) {
110146 if (widget is Visibility ) {
111147 return widget.visible;
@@ -136,15 +172,6 @@ class WidgetFilter {
136172 (bundle is SentryAssetBundle && bundle.bundle == rootAssetBundle));
137173 }
138174
139- @pragma ('vm:prefer-inline' )
140- void _cantObscure (Widget widget, String message) {
141- if (! _warnedWidgets.contains (widget.hashCode)) {
142- _warnedWidgets.add (widget.hashCode);
143- logger (SentryLevel .warning,
144- "WidgetFilter cannot obscure widget $widget : $message " );
145- }
146- }
147-
148175 @pragma ('vm:prefer-inline' )
149176 Rect _boundingBox (RenderBox box) {
150177 final offset = box.localToGlobal (Offset .zero);
@@ -194,3 +221,14 @@ class WidgetFilterMaskingConfig {
194221 }
195222 }
196223}
224+
225+ extension on Element {
226+ Element ? get parent {
227+ Element ? result;
228+ visitAncestorElements ((el) {
229+ result = el;
230+ return false ;
231+ });
232+ return result;
233+ }
234+ }
0 commit comments