Skip to content

Commit

Permalink
Post a ToolEvent when selecting widget for inspection (#118098)
Browse files Browse the repository at this point in the history
Changes our inspection behaviour so that it also sends a postEvent on the ToolEvent stream.
  • Loading branch information
CoderDake authored Jan 17, 2023
1 parent f22280a commit cc7845e
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 30 deletions.
75 changes: 61 additions & 14 deletions packages/flutter/lib/src/widgets/widget_inspector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1500,13 +1500,13 @@ mixin WidgetInspectorService {
return false;
}
selection.currentElement = object;
developer.inspect(selection.currentElement);
_sendInspectEvent(selection.currentElement);
} else {
if (object == selection.current) {
return false;
}
selection.current = object! as RenderObject;
developer.inspect(selection.current);
_sendInspectEvent(selection.current);
}
if (selectionChangedCallback != null) {
if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle) {
Expand All @@ -1525,6 +1525,25 @@ mixin WidgetInspectorService {
return false;
}

/// Notify attached tools to navigate to an object's source location.
void _sendInspectEvent(Object? object){
inspect(object);

final _Location? location = _getSelectedSummaryWidgetLocation(null);
if (location != null) {
postEvent(
'navigate',
<String, Object>{
'fileUri': location.file, // URI file path of the location.
'line': location.line, // 1-based line number.
'column': location.column, // 1-based column number.
'source': 'flutter.inspector',
},
stream: 'ToolEvent',
);
}
}

/// Returns a DevTools uri linking to a specific element on the inspector page.
String? _devToolsInspectorUriForElement(Element element) {
if (activeDevToolsServerAddress != null && connectedVmServiceUri != null) {
Expand Down Expand Up @@ -2214,9 +2233,16 @@ mixin WidgetInspectorService {
}

Map<String, Object?>? _getSelectedWidget(String? previousSelectionId, String groupName) {
return _nodeToJson(
_getSelectedWidgetDiagnosticsNode(previousSelectionId),
InspectorSerializationDelegate(groupName: groupName, service: this),
);
}

DiagnosticsNode? _getSelectedWidgetDiagnosticsNode(String? previousSelectionId) {
final DiagnosticsNode? previousSelection = toObject(previousSelectionId) as DiagnosticsNode?;
final Element? current = selection.currentElement;
return _nodeToJson(current == previousSelection?.value ? previousSelection : current?.toDiagnosticsNode(), InspectorSerializationDelegate(groupName: groupName, service: this));
return current == previousSelection?.value ? previousSelection : current?.toDiagnosticsNode();
}

/// Returns a [DiagnosticsNode] representing the currently selected [Element]
Expand All @@ -2231,9 +2257,13 @@ mixin WidgetInspectorService {
return _safeJsonEncode(_getSelectedSummaryWidget(previousSelectionId, groupName));
}

Map<String, Object?>? _getSelectedSummaryWidget(String? previousSelectionId, String groupName) {
_Location? _getSelectedSummaryWidgetLocation(String? previousSelectionId) {
return _getCreationLocation(_getSelectedSummaryDiagnosticsNode(previousSelectionId)?.value);
}

DiagnosticsNode? _getSelectedSummaryDiagnosticsNode(String? previousSelectionId) {
if (!isWidgetCreationTracked()) {
return _getSelectedWidget(previousSelectionId, groupName);
return _getSelectedWidgetDiagnosticsNode(previousSelectionId);
}
final DiagnosticsNode? previousSelection = toObject(previousSelectionId) as DiagnosticsNode?;
Element? current = selection.currentElement;
Expand All @@ -2247,7 +2277,11 @@ mixin WidgetInspectorService {
}
current = firstLocal;
}
return _nodeToJson(current == previousSelection?.value ? previousSelection : current?.toDiagnosticsNode(), InspectorSerializationDelegate(groupName: groupName, service: this));
return current == previousSelection?.value ? previousSelection : current?.toDiagnosticsNode();
}

Map<String, Object?>? _getSelectedSummaryWidget(String? previousSelectionId, String groupName) {
return _nodeToJson(_getSelectedSummaryDiagnosticsNode(previousSelectionId), InspectorSerializationDelegate(groupName: groupName, service: this));
}

/// Returns whether [Widget] creation locations are available.
Expand Down Expand Up @@ -2281,12 +2315,27 @@ mixin WidgetInspectorService {
}

/// All events dispatched by a [WidgetInspectorService] use this method
/// instead of calling [developer.postEvent] directly so that tests for
/// [WidgetInspectorService] can track which events were dispatched by
/// overriding this method.
/// instead of calling [developer.postEvent] directly.
///
/// This allows tests for [WidgetInspectorService] to track which events were
/// dispatched by overriding this method.
@protected
void postEvent(
String eventKind,
Map<Object, Object?> eventData, {
String stream = 'Extension',
}) {
developer.postEvent(eventKind, eventData, stream: stream);
}

/// All events dispatched by a [WidgetInspectorService] use this method
/// instead of calling [developer.inspect].
///
/// This allows tests for [WidgetInspectorService] to track which events were
/// dispatched by overriding this method.
@protected
void postEvent(String eventKind, Map<Object, Object?> eventData) {
developer.postEvent(eventKind, eventData);
void inspect(Object? object) {
developer.inspect(object);
}

final _ElementLocationStatsTracker _rebuildStats = _ElementLocationStatsTracker();
Expand Down Expand Up @@ -2743,9 +2792,7 @@ class _WidgetInspectorState extends State<WidgetInspector>
}
if (_lastPointerLocation != null) {
_inspectAt(_lastPointerLocation!);

// Notify debuggers to open an inspector on the object.
developer.inspect(selection.current);
WidgetInspectorService.instance._sendInspectEvent(selection.current);
}
setState(() {
// Only exit select mode if there is a button to return to select mode.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class StructureErrorTestWidgetInspectorService extends TestWidgetInspectorServic
final FlutterExceptionHandler? oldHandler = FlutterError.onError;

try {
expect(service.getEventsDispatched('Flutter.Error'), isEmpty);
expect(service.dispatchedEvents('Flutter.Error'), isEmpty);

// Set callback that doesn't call presentError.
bool onErrorCalled = false;
Expand All @@ -49,7 +49,7 @@ class StructureErrorTestWidgetInspectorService extends TestWidgetInspectorServic

// Verify structured errors are not shown.
expect(onErrorCalled, true);
expect(service.getEventsDispatched('Flutter.Error'), isEmpty);
expect(service.dispatchedEvents('Flutter.Error'), isEmpty);

// Set callback that calls presentError.
onErrorCalled = false;
Expand All @@ -64,9 +64,9 @@ class StructureErrorTestWidgetInspectorService extends TestWidgetInspectorServic
expect(onErrorCalled, true);
// Structured errors are not supported on web.
if (!kIsWeb) {
expect(service.getEventsDispatched('Flutter.Error'), hasLength(1));
expect(service.dispatchedEvents('Flutter.Error'), hasLength(1));
} else {
expect(service.getEventsDispatched('Flutter.Error'), isEmpty);
expect(service.dispatchedEvents('Flutter.Error'), isEmpty);
}

// Verify disabling structured errors sets the default FlutterError.presentError
Expand Down
153 changes: 147 additions & 6 deletions packages/flutter/test/widgets/widget_inspector_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,147 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
expect(columnA, equals(columnB));
}, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // [intended] Test requires --track-widget-creation flag.

testWidgets('WidgetInspectorService setSelection notifiers for an Element',
(WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Stack(
children: const <Widget>[
Text('a'),
Text('b', textDirection: TextDirection.ltr),
Text('c', textDirection: TextDirection.ltr),
],
),
),
);
final Element elementA = find.text('a').evaluate().first;

service.disposeAllGroups();

setupDefaultPubRootDirectory(service);

// Select the widget
service.setSelection(elementA, 'my-group');

// ensure that developer.inspect was called on the widget
final List<Object?> objectsInspected = service.inspectedObjects();
expect(objectsInspected, equals(<Element>[elementA]));

// ensure that a navigate event was sent for the element
final List<Map<Object, Object?>> navigateEventsPosted
= service.dispatchedEvents('navigate', stream: 'ToolEvent',);
expect(navigateEventsPosted.length, equals(1));
final Map<Object,Object?> event = navigateEventsPosted[0];
final String file = event['fileUri']! as String;
final int line = event['line']! as int;
final int column = event['column']! as int;
expect(file, endsWith('widget_inspector_test.dart'));
// We don't hardcode the actual lines the widgets are created on as that
// would make this test fragile.
expect(line, isNotNull);
// Column numbers are more stable than line numbers.
expect(column, equals(15));
},
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(), // [intended] Test requires --track-widget-creation flag.
);

testWidgets(
'WidgetInspectorService setSelection notifiers for a RenderObject',
(WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Stack(
children: const <Widget>[
Text('a'),
Text('b', textDirection: TextDirection.ltr),
Text('c', textDirection: TextDirection.ltr),
],
),
),
);
final Element elementA = find.text('a').evaluate().first;

service.disposeAllGroups();

setupDefaultPubRootDirectory(service);

// Select the render object for the widget.
service.setSelection(elementA.renderObject, 'my-group');

// ensure that developer.inspect was called on the widget
final List<Object?> objectsInspected = service.inspectedObjects();
expect(objectsInspected, equals(<RenderObject?>[elementA.renderObject]));

// ensure that a navigate event was sent for the renderObject
final List<Map<Object, Object?>> navigateEventsPosted
= service.dispatchedEvents('navigate', stream: 'ToolEvent',);
expect(navigateEventsPosted.length, equals(1));
final Map<Object,Object?> event = navigateEventsPosted[0];
final String file = event['fileUri']! as String;
final int line = event['line']! as int;
final int column = event['column']! as int;
expect(file, endsWith('widget_inspector_test.dart'));
// We don't hardcode the actual lines the widgets are created on as that
// would make this test fragile.
expect(line, isNotNull);
// Column numbers are more stable than line numbers.
expect(column, equals(17));
},
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(), // [intended] Test requires --track-widget-creation flag.
);

testWidgets(
'WidgetInspector selectButton inspection for tap',
(WidgetTester tester) async {
final GlobalKey selectButtonKey = GlobalKey();
final GlobalKey inspectorKey = GlobalKey();
setupDefaultPubRootDirectory(service);

Widget selectButtonBuilder(BuildContext context, VoidCallback onPressed) {
return Material(child: ElevatedButton(onPressed: onPressed, key: selectButtonKey, child: null));
}

await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: WidgetInspector(
key: inspectorKey,
selectButtonBuilder: selectButtonBuilder,
child: const Text('Child 1'),
),
),
);
final Finder child = find.text('Child 1');
final Element childElement = child.evaluate().first;

await tester.tap(child, warnIfMissed: false);

await tester.pump();

// ensure that developer.inspect was called on the widget
final List<Object?> objectsInspected = service.inspectedObjects();
expect(objectsInspected, equals(<RenderObject?>[childElement.renderObject]));

// ensure that a navigate event was sent for the renderObject
final List<Map<Object, Object?>> navigateEventsPosted
= service.dispatchedEvents('navigate', stream: 'ToolEvent',);
expect(navigateEventsPosted.length, equals(1));
final Map<Object,Object?> event = navigateEventsPosted[0];
final String file = event['fileUri']! as String;
final int line = event['line']! as int;
final int column = event['column']! as int;
expect(file, endsWith('widget_inspector_test.dart'));
// We don't hardcode the actual lines the widgets are created on as that
// would make this test fragile.
expect(line, isNotNull);
// Column numbers are more stable than line numbers.
expect(column, equals(28));
},
skip: !WidgetInspectorService.instance.isWidgetCreationTracked() // [intended] Test requires --track-widget-creation flag.
);

testWidgets('test transformDebugCreator will re-order if after stack trace', (WidgetTester tester) async {
final bool widgetTracked = WidgetInspectorService.instance.isWidgetCreationTracked();
await tester.pumpWidget(
Expand Down Expand Up @@ -3472,7 +3613,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
);

final List<Map<Object, Object?>> rebuildEvents =
service.getEventsDispatched('Flutter.RebuiltWidgets');
service.dispatchedEvents('Flutter.RebuiltWidgets');
expect(rebuildEvents, isEmpty);

expect(service.rebuildCount, equals(0));
Expand Down Expand Up @@ -3692,7 +3833,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
);

final List<Map<Object, Object?>> repaintEvents =
service.getEventsDispatched('Flutter.RepaintWidgets');
service.dispatchedEvents('Flutter.RepaintWidgets');
expect(repaintEvents, isEmpty);

expect(service.rebuildCount, equals(0));
Expand Down Expand Up @@ -4467,7 +4608,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
});

test('ext.flutter.inspector.structuredErrors', () async {
List<Map<Object, Object?>> flutterErrorEvents = service.getEventsDispatched('Flutter.Error');
List<Map<Object, Object?>> flutterErrorEvents = service.dispatchedEvents('Flutter.Error');
expect(flutterErrorEvents, isEmpty);

final FlutterExceptionHandler oldHandler = FlutterError.presentError;
Expand All @@ -4490,7 +4631,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
));

// Validate that we received an error.
flutterErrorEvents = service.getEventsDispatched('Flutter.Error');
flutterErrorEvents = service.dispatchedEvents('Flutter.Error');
expect(flutterErrorEvents, hasLength(1));

// Validate the error contents.
Expand All @@ -4513,7 +4654,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
));

// Validate that the error count increased.
flutterErrorEvents = service.getEventsDispatched('Flutter.Error');
flutterErrorEvents = service.dispatchedEvents('Flutter.Error');
expect(flutterErrorEvents, hasLength(2));
error = flutterErrorEvents.last;
expect(error['errorsSinceReload'], 1);
Expand Down Expand Up @@ -4541,7 +4682,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
));

// And, validate that the error count has been reset.
flutterErrorEvents = service.getEventsDispatched('Flutter.Error');
flutterErrorEvents = service.dispatchedEvents('Flutter.Error');
expect(flutterErrorEvents, hasLength(3));
error = flutterErrorEvents.last;
expect(error['errorsSinceReload'], 0);
Expand Down
Loading

0 comments on commit cc7845e

Please sign in to comment.