Fix LayoutExplorer cycle (#115526)
* Fix LayoutExplorer cycle * fix tests * Update widget_inspector.dart * Update widget_inspector.dart * review * expandPropertyValues
This commit is contained in:
parent
b22ab5117f
commit
ccc277c38b
@ -2038,91 +2038,91 @@ mixin WidgetInspectorService {
|
|||||||
subtreeDepth: subtreeDepth,
|
subtreeDepth: subtreeDepth,
|
||||||
service: this,
|
service: this,
|
||||||
addAdditionalPropertiesCallback: (DiagnosticsNode node, InspectorSerializationDelegate delegate) {
|
addAdditionalPropertiesCallback: (DiagnosticsNode node, InspectorSerializationDelegate delegate) {
|
||||||
final Map<String, Object> additionalJson = <String, Object>{};
|
|
||||||
final Object? value = node.value;
|
final Object? value = node.value;
|
||||||
if (value is Element) {
|
final RenderObject? renderObject = value is Element ? value.renderObject : null;
|
||||||
final RenderObject? renderObject = value.renderObject;
|
if (renderObject == null) {
|
||||||
if (renderObject != null) {
|
return const <String, Object>{};
|
||||||
additionalJson['renderObject'] =
|
}
|
||||||
renderObject.toDiagnosticsNode().toJsonMap(
|
|
||||||
|
final DiagnosticsSerializationDelegate renderObjectSerializationDelegate = delegate.copyWith(
|
||||||
|
subtreeDepth: 0,
|
||||||
|
includeProperties: true,
|
||||||
|
expandPropertyValues: false,
|
||||||
|
);
|
||||||
|
final Map<String, Object> additionalJson = <String, Object>{
|
||||||
|
'renderObject': renderObject.toDiagnosticsNode().toJsonMap(renderObjectSerializationDelegate),
|
||||||
|
};
|
||||||
|
|
||||||
|
final AbstractNode? renderParent = renderObject.parent;
|
||||||
|
if (renderParent is RenderObject && subtreeDepth > 0) {
|
||||||
|
final Object? parentCreator = renderParent.debugCreator;
|
||||||
|
if (parentCreator is DebugCreator) {
|
||||||
|
additionalJson['parentRenderElement'] =
|
||||||
|
parentCreator.element.toDiagnosticsNode().toJsonMap(
|
||||||
delegate.copyWith(
|
delegate.copyWith(
|
||||||
subtreeDepth: 0,
|
subtreeDepth: 0,
|
||||||
includeProperties: true,
|
includeProperties: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
// TODO(jacobr): also describe the path back up the tree to
|
||||||
final AbstractNode? renderParent = renderObject.parent;
|
// the RenderParentElement from the current element. It
|
||||||
if (renderParent is RenderObject && subtreeDepth > 0) {
|
// could be a surprising distance up the tree if a lot of
|
||||||
final Object? parentCreator = renderParent.debugCreator;
|
// elements don't have their own RenderObjects.
|
||||||
if (parentCreator is DebugCreator) {
|
|
||||||
additionalJson['parentRenderElement'] =
|
|
||||||
parentCreator.element.toDiagnosticsNode().toJsonMap(
|
|
||||||
delegate.copyWith(
|
|
||||||
subtreeDepth: 0,
|
|
||||||
includeProperties: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
// TODO(jacobr): also describe the path back up the tree to
|
|
||||||
// the RenderParentElement from the current element. It
|
|
||||||
// could be a surprising distance up the tree if a lot of
|
|
||||||
// elements don't have their own RenderObjects.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!renderObject.debugNeedsLayout) {
|
|
||||||
// ignore: invalid_use_of_protected_member
|
|
||||||
final Constraints constraints = renderObject.constraints;
|
|
||||||
final Map<String, Object>constraintsProperty = <String, Object>{
|
|
||||||
'type': constraints.runtimeType.toString(),
|
|
||||||
'description': constraints.toString(),
|
|
||||||
};
|
|
||||||
if (constraints is BoxConstraints) {
|
|
||||||
constraintsProperty.addAll(<String, Object>{
|
|
||||||
'minWidth': constraints.minWidth.toString(),
|
|
||||||
'minHeight': constraints.minHeight.toString(),
|
|
||||||
'maxWidth': constraints.maxWidth.toString(),
|
|
||||||
'maxHeight': constraints.maxHeight.toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
additionalJson['constraints'] = constraintsProperty;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Constraints are sometimes unavailable even though
|
|
||||||
// debugNeedsLayout is false.
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (renderObject is RenderBox) {
|
|
||||||
additionalJson['isBox'] = true;
|
|
||||||
additionalJson['size'] = <String, Object>{
|
|
||||||
'width': renderObject.size.width.toString(),
|
|
||||||
'height': renderObject.size.height.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
final ParentData? parentData = renderObject.parentData;
|
|
||||||
if (parentData is FlexParentData) {
|
|
||||||
additionalJson['flexFactor'] = parentData.flex!;
|
|
||||||
additionalJson['flexFit'] =
|
|
||||||
describeEnum(parentData.fit ?? FlexFit.tight);
|
|
||||||
} else if (parentData is BoxParentData) {
|
|
||||||
final Offset offset = parentData.offset;
|
|
||||||
additionalJson['parentData'] = <String, Object>{
|
|
||||||
'offsetX': offset.dx.toString(),
|
|
||||||
'offsetY': offset.dy.toString(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if (renderObject is RenderView) {
|
|
||||||
additionalJson['size'] = <String, Object>{
|
|
||||||
'width': renderObject.size.width.toString(),
|
|
||||||
'height': renderObject.size.height.toString(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Not laid out yet.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!renderObject.debugNeedsLayout) {
|
||||||
|
// ignore: invalid_use_of_protected_member
|
||||||
|
final Constraints constraints = renderObject.constraints;
|
||||||
|
final Map<String, Object>constraintsProperty = <String, Object>{
|
||||||
|
'type': constraints.runtimeType.toString(),
|
||||||
|
'description': constraints.toString(),
|
||||||
|
};
|
||||||
|
if (constraints is BoxConstraints) {
|
||||||
|
constraintsProperty.addAll(<String, Object>{
|
||||||
|
'minWidth': constraints.minWidth.toString(),
|
||||||
|
'minHeight': constraints.minHeight.toString(),
|
||||||
|
'maxWidth': constraints.maxWidth.toString(),
|
||||||
|
'maxHeight': constraints.maxHeight.toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
additionalJson['constraints'] = constraintsProperty;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Constraints are sometimes unavailable even though
|
||||||
|
// debugNeedsLayout is false.
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (renderObject is RenderBox) {
|
||||||
|
additionalJson['isBox'] = true;
|
||||||
|
additionalJson['size'] = <String, Object>{
|
||||||
|
'width': renderObject.size.width.toString(),
|
||||||
|
'height': renderObject.size.height.toString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
final ParentData? parentData = renderObject.parentData;
|
||||||
|
if (parentData is FlexParentData) {
|
||||||
|
additionalJson['flexFactor'] = parentData.flex!;
|
||||||
|
additionalJson['flexFit'] =
|
||||||
|
describeEnum(parentData.fit ?? FlexFit.tight);
|
||||||
|
} else if (parentData is BoxParentData) {
|
||||||
|
final Offset offset = parentData.offset;
|
||||||
|
additionalJson['parentData'] = <String, Object>{
|
||||||
|
'offsetX': offset.dx.toString(),
|
||||||
|
'offsetY': offset.dy.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (renderObject is RenderView) {
|
||||||
|
additionalJson['size'] = <String, Object>{
|
||||||
|
'width': renderObject.size.width.toString(),
|
||||||
|
'height': renderObject.size.height.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Not laid out yet.
|
||||||
|
}
|
||||||
return additionalJson;
|
return additionalJson;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -3633,12 +3633,12 @@ class InspectorSerializationDelegate implements DiagnosticsSerializationDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DiagnosticsSerializationDelegate copyWith({int? subtreeDepth, bool? includeProperties}) {
|
DiagnosticsSerializationDelegate copyWith({int? subtreeDepth, bool? includeProperties, bool? expandPropertyValues}) {
|
||||||
return InspectorSerializationDelegate(
|
return InspectorSerializationDelegate(
|
||||||
groupName: groupName,
|
groupName: groupName,
|
||||||
summaryTree: summaryTree,
|
summaryTree: summaryTree,
|
||||||
maxDescendentsTruncatableNode: maxDescendentsTruncatableNode,
|
maxDescendentsTruncatableNode: maxDescendentsTruncatableNode,
|
||||||
expandPropertyValues: expandPropertyValues,
|
expandPropertyValues: expandPropertyValues ?? this.expandPropertyValues,
|
||||||
subtreeDepth: subtreeDepth ?? this.subtreeDepth,
|
subtreeDepth: subtreeDepth ?? this.subtreeDepth,
|
||||||
includeProperties: includeProperties ?? this.includeProperties,
|
includeProperties: includeProperties ?? this.includeProperties,
|
||||||
service: service,
|
service: service,
|
||||||
|
@ -18,6 +18,7 @@ import 'dart:convert';
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -3549,7 +3550,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
_CreationLocation location = knownLocations[id]!;
|
_CreationLocation location = knownLocations[id]!;
|
||||||
expect(location.file, equals(file));
|
expect(location.file, equals(file));
|
||||||
// ClockText widget.
|
// ClockText widget.
|
||||||
expect(location.line, equals(59));
|
expect(location.line, equals(60));
|
||||||
expect(location.column, equals(9));
|
expect(location.column, equals(9));
|
||||||
expect(location.name, equals('ClockText'));
|
expect(location.name, equals('ClockText'));
|
||||||
expect(count, equals(1));
|
expect(count, equals(1));
|
||||||
@ -3559,7 +3560,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
location = knownLocations[id]!;
|
location = knownLocations[id]!;
|
||||||
expect(location.file, equals(file));
|
expect(location.file, equals(file));
|
||||||
// Text widget in _ClockTextState build method.
|
// Text widget in _ClockTextState build method.
|
||||||
expect(location.line, equals(97));
|
expect(location.line, equals(98));
|
||||||
expect(location.column, equals(12));
|
expect(location.column, equals(12));
|
||||||
expect(location.name, equals('Text'));
|
expect(location.name, equals('Text'));
|
||||||
expect(count, equals(1));
|
expect(count, equals(1));
|
||||||
@ -3586,7 +3587,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
location = knownLocations[id]!;
|
location = knownLocations[id]!;
|
||||||
expect(location.file, equals(file));
|
expect(location.file, equals(file));
|
||||||
// ClockText widget.
|
// ClockText widget.
|
||||||
expect(location.line, equals(59));
|
expect(location.line, equals(60));
|
||||||
expect(location.column, equals(9));
|
expect(location.column, equals(9));
|
||||||
expect(location.name, equals('ClockText'));
|
expect(location.name, equals('ClockText'));
|
||||||
expect(count, equals(3)); // 3 clock widget instances rebuilt.
|
expect(count, equals(3)); // 3 clock widget instances rebuilt.
|
||||||
@ -3596,7 +3597,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
location = knownLocations[id]!;
|
location = knownLocations[id]!;
|
||||||
expect(location.file, equals(file));
|
expect(location.file, equals(file));
|
||||||
// Text widget in _ClockTextState build method.
|
// Text widget in _ClockTextState build method.
|
||||||
expect(location.line, equals(97));
|
expect(location.line, equals(98));
|
||||||
expect(location.column, equals(12));
|
expect(location.column, equals(12));
|
||||||
expect(location.name, equals('Text'));
|
expect(location.name, equals('Text'));
|
||||||
expect(count, equals(3)); // 3 clock widget instances rebuilt.
|
expect(count, equals(3)); // 3 clock widget instances rebuilt.
|
||||||
@ -4437,6 +4438,36 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
expect(mainAxisAlignment, equals('center'));
|
expect(mainAxisAlignment, equals('center'));
|
||||||
expect(crossAxisAlignment, equals('start'));
|
expect(crossAxisAlignment, equals('start'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('ext.flutter.inspector.getLayoutExplorerNode does not throw StackOverflowError',(WidgetTester tester) async {
|
||||||
|
// Regression test for https://github.com/flutter/flutter/issues/115228
|
||||||
|
const Key leafKey = ValueKey<String>('ColoredBox');
|
||||||
|
await tester.pumpWidget(
|
||||||
|
CupertinoApp(
|
||||||
|
home: CupertinoPageScaffold(
|
||||||
|
child: Builder(
|
||||||
|
builder: (BuildContext context) => ColoredBox(key: leafKey, color: CupertinoTheme.of(context).primaryColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Element leaf = tester.element(find.byKey(leafKey));
|
||||||
|
service.setSelection(leaf, group);
|
||||||
|
final DiagnosticsNode diagnostic = leaf.toDiagnosticsNode();
|
||||||
|
final String id = service.toId(diagnostic, group)!;
|
||||||
|
|
||||||
|
Object? error;
|
||||||
|
try {
|
||||||
|
await service.testExtension(
|
||||||
|
WidgetInspectorServiceExtensions.getLayoutExplorerNode.name,
|
||||||
|
<String, String>{'id': id, 'groupName': group, 'subtreeDepth': '1'},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
expect(error, isNull);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ext.flutter.inspector.structuredErrors', () async {
|
test('ext.flutter.inspector.structuredErrors', () async {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user