Skip to content

Commit ccc277c

Browse files
Fix LayoutExplorer cycle (#115526)
* Fix LayoutExplorer cycle * fix tests * Update widget_inspector.dart * Update widget_inspector.dart * review * expandPropertyValues
1 parent b22ab51 commit ccc277c

File tree

2 files changed

+110
-79
lines changed

2 files changed

+110
-79
lines changed

packages/flutter/lib/src/widgets/widget_inspector.dart

Lines changed: 75 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2038,90 +2038,90 @@ mixin WidgetInspectorService {
20382038
subtreeDepth: subtreeDepth,
20392039
service: this,
20402040
addAdditionalPropertiesCallback: (DiagnosticsNode node, InspectorSerializationDelegate delegate) {
2041-
final Map<String, Object> additionalJson = <String, Object>{};
20422041
final Object? value = node.value;
2043-
if (value is Element) {
2044-
final RenderObject? renderObject = value.renderObject;
2045-
if (renderObject != null) {
2046-
additionalJson['renderObject'] =
2047-
renderObject.toDiagnosticsNode().toJsonMap(
2042+
final RenderObject? renderObject = value is Element ? value.renderObject : null;
2043+
if (renderObject == null) {
2044+
return const <String, Object>{};
2045+
}
2046+
2047+
final DiagnosticsSerializationDelegate renderObjectSerializationDelegate = delegate.copyWith(
2048+
subtreeDepth: 0,
2049+
includeProperties: true,
2050+
expandPropertyValues: false,
2051+
);
2052+
final Map<String, Object> additionalJson = <String, Object>{
2053+
'renderObject': renderObject.toDiagnosticsNode().toJsonMap(renderObjectSerializationDelegate),
2054+
};
2055+
2056+
final AbstractNode? renderParent = renderObject.parent;
2057+
if (renderParent is RenderObject && subtreeDepth > 0) {
2058+
final Object? parentCreator = renderParent.debugCreator;
2059+
if (parentCreator is DebugCreator) {
2060+
additionalJson['parentRenderElement'] =
2061+
parentCreator.element.toDiagnosticsNode().toJsonMap(
20482062
delegate.copyWith(
20492063
subtreeDepth: 0,
20502064
includeProperties: true,
20512065
),
20522066
);
2067+
// TODO(jacobr): also describe the path back up the tree to
2068+
// the RenderParentElement from the current element. It
2069+
// could be a surprising distance up the tree if a lot of
2070+
// elements don't have their own RenderObjects.
2071+
}
2072+
}
20532073

2054-
final AbstractNode? renderParent = renderObject.parent;
2055-
if (renderParent is RenderObject && subtreeDepth > 0) {
2056-
final Object? parentCreator = renderParent.debugCreator;
2057-
if (parentCreator is DebugCreator) {
2058-
additionalJson['parentRenderElement'] =
2059-
parentCreator.element.toDiagnosticsNode().toJsonMap(
2060-
delegate.copyWith(
2061-
subtreeDepth: 0,
2062-
includeProperties: true,
2063-
),
2064-
);
2065-
// TODO(jacobr): also describe the path back up the tree to
2066-
// the RenderParentElement from the current element. It
2067-
// could be a surprising distance up the tree if a lot of
2068-
// elements don't have their own RenderObjects.
2069-
}
2070-
}
2071-
2072-
try {
2073-
if (!renderObject.debugNeedsLayout) {
2074-
// ignore: invalid_use_of_protected_member
2075-
final Constraints constraints = renderObject.constraints;
2076-
final Map<String, Object>constraintsProperty = <String, Object>{
2077-
'type': constraints.runtimeType.toString(),
2078-
'description': constraints.toString(),
2079-
};
2080-
if (constraints is BoxConstraints) {
2081-
constraintsProperty.addAll(<String, Object>{
2082-
'minWidth': constraints.minWidth.toString(),
2083-
'minHeight': constraints.minHeight.toString(),
2084-
'maxWidth': constraints.maxWidth.toString(),
2085-
'maxHeight': constraints.maxHeight.toString(),
2086-
});
2087-
}
2088-
additionalJson['constraints'] = constraintsProperty;
2089-
}
2090-
} catch (e) {
2091-
// Constraints are sometimes unavailable even though
2092-
// debugNeedsLayout is false.
2074+
try {
2075+
if (!renderObject.debugNeedsLayout) {
2076+
// ignore: invalid_use_of_protected_member
2077+
final Constraints constraints = renderObject.constraints;
2078+
final Map<String, Object>constraintsProperty = <String, Object>{
2079+
'type': constraints.runtimeType.toString(),
2080+
'description': constraints.toString(),
2081+
};
2082+
if (constraints is BoxConstraints) {
2083+
constraintsProperty.addAll(<String, Object>{
2084+
'minWidth': constraints.minWidth.toString(),
2085+
'minHeight': constraints.minHeight.toString(),
2086+
'maxWidth': constraints.maxWidth.toString(),
2087+
'maxHeight': constraints.maxHeight.toString(),
2088+
});
20932089
}
2090+
additionalJson['constraints'] = constraintsProperty;
2091+
}
2092+
} catch (e) {
2093+
// Constraints are sometimes unavailable even though
2094+
// debugNeedsLayout is false.
2095+
}
20942096

2095-
try {
2096-
if (renderObject is RenderBox) {
2097-
additionalJson['isBox'] = true;
2098-
additionalJson['size'] = <String, Object>{
2099-
'width': renderObject.size.width.toString(),
2100-
'height': renderObject.size.height.toString(),
2101-
};
2102-
2103-
final ParentData? parentData = renderObject.parentData;
2104-
if (parentData is FlexParentData) {
2105-
additionalJson['flexFactor'] = parentData.flex!;
2106-
additionalJson['flexFit'] =
2107-
describeEnum(parentData.fit ?? FlexFit.tight);
2108-
} else if (parentData is BoxParentData) {
2109-
final Offset offset = parentData.offset;
2110-
additionalJson['parentData'] = <String, Object>{
2111-
'offsetX': offset.dx.toString(),
2112-
'offsetY': offset.dy.toString(),
2113-
};
2114-
}
2115-
} else if (renderObject is RenderView) {
2116-
additionalJson['size'] = <String, Object>{
2117-
'width': renderObject.size.width.toString(),
2118-
'height': renderObject.size.height.toString(),
2119-
};
2120-
}
2121-
} catch (e) {
2122-
// Not laid out yet.
2097+
try {
2098+
if (renderObject is RenderBox) {
2099+
additionalJson['isBox'] = true;
2100+
additionalJson['size'] = <String, Object>{
2101+
'width': renderObject.size.width.toString(),
2102+
'height': renderObject.size.height.toString(),
2103+
};
2104+
2105+
final ParentData? parentData = renderObject.parentData;
2106+
if (parentData is FlexParentData) {
2107+
additionalJson['flexFactor'] = parentData.flex!;
2108+
additionalJson['flexFit'] =
2109+
describeEnum(parentData.fit ?? FlexFit.tight);
2110+
} else if (parentData is BoxParentData) {
2111+
final Offset offset = parentData.offset;
2112+
additionalJson['parentData'] = <String, Object>{
2113+
'offsetX': offset.dx.toString(),
2114+
'offsetY': offset.dy.toString(),
2115+
};
21232116
}
2117+
} else if (renderObject is RenderView) {
2118+
additionalJson['size'] = <String, Object>{
2119+
'width': renderObject.size.width.toString(),
2120+
'height': renderObject.size.height.toString(),
2121+
};
21242122
}
2123+
} catch (e) {
2124+
// Not laid out yet.
21252125
}
21262126
return additionalJson;
21272127
},
@@ -3633,12 +3633,12 @@ class InspectorSerializationDelegate implements DiagnosticsSerializationDelegate
36333633
}
36343634

36353635
@override
3636-
DiagnosticsSerializationDelegate copyWith({int? subtreeDepth, bool? includeProperties}) {
3636+
DiagnosticsSerializationDelegate copyWith({int? subtreeDepth, bool? includeProperties, bool? expandPropertyValues}) {
36373637
return InspectorSerializationDelegate(
36383638
groupName: groupName,
36393639
summaryTree: summaryTree,
36403640
maxDescendentsTruncatableNode: maxDescendentsTruncatableNode,
3641-
expandPropertyValues: expandPropertyValues,
3641+
expandPropertyValues: expandPropertyValues ?? this.expandPropertyValues,
36423642
subtreeDepth: subtreeDepth ?? this.subtreeDepth,
36433643
includeProperties: includeProperties ?? this.includeProperties,
36443644
service: service,

packages/flutter/test/widgets/widget_inspector_test.dart

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'dart:convert';
1818
import 'dart:math';
1919
import 'dart:ui' as ui;
2020

21+
import 'package:flutter/cupertino.dart';
2122
import 'package:flutter/foundation.dart';
2223
import 'package:flutter/gestures.dart' show DragStartBehavior;
2324
import 'package:flutter/material.dart';
@@ -3549,7 +3550,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
35493550
_CreationLocation location = knownLocations[id]!;
35503551
expect(location.file, equals(file));
35513552
// ClockText widget.
3552-
expect(location.line, equals(59));
3553+
expect(location.line, equals(60));
35533554
expect(location.column, equals(9));
35543555
expect(location.name, equals('ClockText'));
35553556
expect(count, equals(1));
@@ -3559,7 +3560,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
35593560
location = knownLocations[id]!;
35603561
expect(location.file, equals(file));
35613562
// Text widget in _ClockTextState build method.
3562-
expect(location.line, equals(97));
3563+
expect(location.line, equals(98));
35633564
expect(location.column, equals(12));
35643565
expect(location.name, equals('Text'));
35653566
expect(count, equals(1));
@@ -3586,7 +3587,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
35863587
location = knownLocations[id]!;
35873588
expect(location.file, equals(file));
35883589
// ClockText widget.
3589-
expect(location.line, equals(59));
3590+
expect(location.line, equals(60));
35903591
expect(location.column, equals(9));
35913592
expect(location.name, equals('ClockText'));
35923593
expect(count, equals(3)); // 3 clock widget instances rebuilt.
@@ -3596,7 +3597,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
35963597
location = knownLocations[id]!;
35973598
expect(location.file, equals(file));
35983599
// Text widget in _ClockTextState build method.
3599-
expect(location.line, equals(97));
3600+
expect(location.line, equals(98));
36003601
expect(location.column, equals(12));
36013602
expect(location.name, equals('Text'));
36023603
expect(count, equals(3)); // 3 clock widget instances rebuilt.
@@ -4437,6 +4438,36 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
44374438
expect(mainAxisAlignment, equals('center'));
44384439
expect(crossAxisAlignment, equals('start'));
44394440
});
4441+
4442+
testWidgets('ext.flutter.inspector.getLayoutExplorerNode does not throw StackOverflowError',(WidgetTester tester) async {
4443+
// Regression test for https://github.com/flutter/flutter/issues/115228
4444+
const Key leafKey = ValueKey<String>('ColoredBox');
4445+
await tester.pumpWidget(
4446+
CupertinoApp(
4447+
home: CupertinoPageScaffold(
4448+
child: Builder(
4449+
builder: (BuildContext context) => ColoredBox(key: leafKey, color: CupertinoTheme.of(context).primaryColor),
4450+
),
4451+
),
4452+
),
4453+
);
4454+
4455+
final Element leaf = tester.element(find.byKey(leafKey));
4456+
service.setSelection(leaf, group);
4457+
final DiagnosticsNode diagnostic = leaf.toDiagnosticsNode();
4458+
final String id = service.toId(diagnostic, group)!;
4459+
4460+
Object? error;
4461+
try {
4462+
await service.testExtension(
4463+
WidgetInspectorServiceExtensions.getLayoutExplorerNode.name,
4464+
<String, String>{'id': id, 'groupName': group, 'subtreeDepth': '1'},
4465+
);
4466+
} catch (e) {
4467+
error = e;
4468+
}
4469+
expect(error, isNull);
4470+
});
44404471
});
44414472

44424473
test('ext.flutter.inspector.structuredErrors', () async {

0 commit comments

Comments
 (0)