mirror of
https://github.com/flutter/flutter
synced 2024-06-30 23:54:38 +00:00
Add frame number and widget location map service extension (#148702)
This helps us add widget rebuild counts to the DevTools performance page: https://github.com/flutter/devtools/issues/4564
This commit is contained in:
parent
d57ea48ca1
commit
edf312d506
|
@ -144,7 +144,22 @@ enum WidgetInspectorServiceExtensions {
|
|||
/// extension is registered.
|
||||
trackRebuildDirtyWidgets,
|
||||
|
||||
/// Name of service extension that, when called, returns the mapping of
|
||||
/// widget locations to ids.
|
||||
///
|
||||
/// This service extension is only supported if
|
||||
/// [WidgetInspectorService._widgetCreationTracked] is true.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [trackRebuildDirtyWidgets], which toggles dispatching events that use
|
||||
/// these ids to efficiently indicate the locations of widgets.
|
||||
/// * [WidgetInspectorService.initServiceExtensions], where the service
|
||||
/// extension is registered.
|
||||
widgetLocationIdMap,
|
||||
|
||||
/// Name of service extension that, when called, determines whether
|
||||
/// [WidgetInspectorService._trackRepaintWidgets], which determines whether
|
||||
/// a callback is invoked for every [RenderObject] painted each frame.
|
||||
///
|
||||
/// See also:
|
||||
|
|
|
@ -1114,6 +1114,14 @@ mixin WidgetInspectorService {
|
|||
registerExtension: registerExtension,
|
||||
);
|
||||
|
||||
_registerSignalServiceExtension(
|
||||
name: WidgetInspectorServiceExtensions.widgetLocationIdMap.name,
|
||||
callback: () {
|
||||
return _locationIdMapToJson();
|
||||
},
|
||||
registerExtension: registerExtension,
|
||||
);
|
||||
|
||||
_registerBoolServiceExtension(
|
||||
name: WidgetInspectorServiceExtensions.trackRepaintWidgets.name,
|
||||
getter: () async => _trackRepaintWidgets,
|
||||
|
@ -2365,9 +2373,11 @@ mixin WidgetInspectorService {
|
|||
bool? _widgetCreationTracked;
|
||||
|
||||
late Duration _frameStart;
|
||||
late int _frameNumber;
|
||||
|
||||
void _onFrameStart(Duration timeStamp) {
|
||||
_frameStart = timeStamp;
|
||||
_frameNumber = PlatformDispatcher.instance.frameData.frameNumber;
|
||||
SchedulerBinding.instance.addPostFrameCallback(_onFrameEnd, debugLabel: 'WidgetInspector.onFrameStart');
|
||||
}
|
||||
|
||||
|
@ -2381,7 +2391,13 @@ mixin WidgetInspectorService {
|
|||
}
|
||||
|
||||
void _postStatsEvent(String eventName, _ElementLocationStatsTracker stats) {
|
||||
postEvent(eventName, stats.exportToJson(_frameStart));
|
||||
postEvent(
|
||||
eventName,
|
||||
stats.exportToJson(
|
||||
_frameStart,
|
||||
frameNumber: _frameNumber,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// All events dispatched by a [WidgetInspectorService] use this method
|
||||
|
@ -2590,7 +2606,7 @@ class _ElementLocationStatsTracker {
|
|||
|
||||
/// Exports the current counts and then resets the stats to prepare to track
|
||||
/// the next frame of data.
|
||||
Map<String, dynamic> exportToJson(Duration startTime) {
|
||||
Map<String, dynamic> exportToJson(Duration startTime, {required int frameNumber}) {
|
||||
final List<int> events = List<int>.filled(active.length * 2, 0);
|
||||
int j = 0;
|
||||
for (final _LocationCount stat in active) {
|
||||
|
@ -2600,6 +2616,7 @@ class _ElementLocationStatsTracker {
|
|||
|
||||
final Map<String, dynamic> json = <String, dynamic>{
|
||||
'startTime': startTime.inMicroseconds,
|
||||
'frameNumber': frameNumber,
|
||||
'events': events,
|
||||
};
|
||||
|
||||
|
@ -3246,12 +3263,21 @@ class _InspectorOverlayLayer extends Layer {
|
|||
final Rect targetRect = MatrixUtils.transformRect(
|
||||
state.selected.transform, state.selected.rect,
|
||||
);
|
||||
final Offset target = Offset(targetRect.left, targetRect.center.dy);
|
||||
const double offsetFromWidget = 9.0;
|
||||
final double verticalOffset = (targetRect.height) / 2 + offsetFromWidget;
|
||||
|
||||
_paintDescription(canvas, state.tooltip, state.textDirection, target, verticalOffset, size, targetRect);
|
||||
if (!targetRect.hasNaN) {
|
||||
final Offset target = Offset(targetRect.left, targetRect.center.dy);
|
||||
const double offsetFromWidget = 9.0;
|
||||
final double verticalOffset = (targetRect.height) / 2 + offsetFromWidget;
|
||||
|
||||
_paintDescription(
|
||||
canvas,
|
||||
state.tooltip,
|
||||
state.textDirection,
|
||||
target,
|
||||
verticalOffset,
|
||||
size,
|
||||
targetRect,
|
||||
);
|
||||
}
|
||||
// TODO(jacobr): provide an option to perform a debug paint of just the
|
||||
// selected widget.
|
||||
return recorder.endRecording();
|
||||
|
@ -3630,6 +3656,34 @@ int _toLocationId(_Location location) {
|
|||
return id;
|
||||
}
|
||||
|
||||
Map<String, dynamic> _locationIdMapToJson() {
|
||||
const String idsKey = 'ids';
|
||||
const String linesKey = 'lines';
|
||||
const String columnsKey = 'columns';
|
||||
const String namesKey = 'names';
|
||||
|
||||
final Map<String, Map<String, List<Object?>>> fileLocationsMap =
|
||||
<String, Map<String, List<Object?>>>{};
|
||||
for (final MapEntry<_Location, int> entry in _locationToId.entries) {
|
||||
final _Location location = entry.key;
|
||||
final Map<String, List<Object?>> locations = fileLocationsMap.putIfAbsent(
|
||||
location.file,
|
||||
() => <String, List<Object?>>{
|
||||
idsKey: <int>[],
|
||||
linesKey: <int>[],
|
||||
columnsKey: <int>[],
|
||||
namesKey: <String?>[],
|
||||
},
|
||||
);
|
||||
|
||||
locations[idsKey]!.add(entry.value);
|
||||
locations[linesKey]!.add(location.line);
|
||||
locations[columnsKey]!.add(location.column);
|
||||
locations[namesKey]!.add(location.name);
|
||||
}
|
||||
return fileLocationsMap;
|
||||
}
|
||||
|
||||
/// A delegate that configures how a hierarchy of [DiagnosticsNode]s are
|
||||
/// serialized by the Flutter Inspector.
|
||||
@visibleForTesting
|
||||
|
|
|
@ -170,7 +170,7 @@ void main() {
|
|||
if (WidgetInspectorService.instance.isWidgetCreationTracked()) {
|
||||
// Some inspector extensions are only exposed if widget creation locations
|
||||
// are tracked.
|
||||
widgetInspectorExtensionCount += 2;
|
||||
widgetInspectorExtensionCount += 3;
|
||||
}
|
||||
expect(binding.extensions.keys.where((String name) => name.startsWith('inspector.')), hasLength(widgetInspectorExtensionCount));
|
||||
|
||||
|
|
|
@ -3765,6 +3765,49 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(), // [intended] Test requires --track-widget-creation flag.
|
||||
);
|
||||
|
||||
testWidgets('ext.flutter.inspector.widgetLocationIdMap',
|
||||
(WidgetTester tester) async {
|
||||
service.rebuildCount = 0;
|
||||
|
||||
await tester.pumpWidget(const ClockDemo());
|
||||
|
||||
final Element clockDemoElement = find.byType(ClockDemo).evaluate().first;
|
||||
|
||||
service.setSelection(clockDemoElement, 'my-group');
|
||||
final Map<String, Object?> jsonObject = (await service.testExtension(
|
||||
WidgetInspectorServiceExtensions.getSelectedWidget.name,
|
||||
<String, String>{'objectGroup': 'my-group'},
|
||||
))! as Map<String, Object?>;
|
||||
final Map<String, Object?> creationLocation =
|
||||
jsonObject['creationLocation']! as Map<String, Object?>;
|
||||
final String file = creationLocation['file']! as String;
|
||||
expect(file, endsWith('widget_inspector_test.dart'));
|
||||
|
||||
final Map<String, Object?> locationMapJson = (await service.testExtension(
|
||||
WidgetInspectorServiceExtensions.widgetLocationIdMap.name,
|
||||
<String, String>{},
|
||||
))! as Map<String, Object?>;
|
||||
|
||||
final Map<String, Object?> widgetTestLocations =
|
||||
locationMapJson[file]! as Map<String, Object?>;
|
||||
expect(widgetTestLocations, isNotNull);
|
||||
|
||||
final List<dynamic> ids = widgetTestLocations['ids']! as List<dynamic>;
|
||||
expect(ids.length, greaterThan(0));
|
||||
final List<dynamic> lines =
|
||||
widgetTestLocations['lines']! as List<dynamic>;
|
||||
expect(lines.length, equals(ids.length));
|
||||
final List<dynamic> columns =
|
||||
widgetTestLocations['columns']! as List<dynamic>;
|
||||
expect(columns.length, equals(ids.length));
|
||||
final List<dynamic> names =
|
||||
widgetTestLocations['names']! as List<dynamic>;
|
||||
expect(names.length, equals(ids.length));
|
||||
expect(names, contains('ClockDemo'));
|
||||
expect(names, contains('Directionality'));
|
||||
expect(names, contains('ClockText'));
|
||||
}, skip: !WidgetInspectorService.instance.isWidgetCreationTracked()); // [intended] Test requires --track-widget-creation flag.
|
||||
|
||||
testWidgets('ext.flutter.inspector.trackRebuildDirtyWidgets', (WidgetTester tester) async {
|
||||
service.rebuildCount = 0;
|
||||
|
||||
|
@ -3951,6 +3994,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||
expect(rebuildEvents.length, equals(1));
|
||||
event = removeLastEvent(rebuildEvents);
|
||||
expect(event['startTime'], isA<int>());
|
||||
expect(event['frameNumber'], isA<int>());
|
||||
data = event['events']! as List<int>;
|
||||
newLocations = event['newLocations']! as Map<String, List<int>>;
|
||||
fileLocationsMap = event['locations']! as Map<String, Map<String, List<Object?>>>;
|
||||
|
@ -4080,6 +4124,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||
expect(repaintEvents.length, equals(1));
|
||||
event = removeLastEvent(repaintEvents);
|
||||
expect(event['startTime'], isA<int>());
|
||||
expect(event['frameNumber'], isA<int>());
|
||||
data = event['events']! as List<int>;
|
||||
// No new locations were rebuilt.
|
||||
expect(event, isNot(contains('newLocations')));
|
||||
|
|
Loading…
Reference in New Issue
Block a user