diff --git a/packages/devtools_app/lib/devtools_app.dart b/packages/devtools_app/lib/devtools_app.dart index fd801656ad5..ddfa7466ab2 100644 --- a/packages/devtools_app/lib/devtools_app.dart +++ b/packages/devtools_app/lib/devtools_app.dart @@ -57,6 +57,7 @@ export 'src/screens/vm_developer/object_inspector/class_hierarchy_explorer.dart' export 'src/screens/vm_developer/object_inspector/class_hierarchy_explorer_controller.dart'; export 'src/screens/vm_developer/object_inspector/object_inspector_view_controller.dart'; export 'src/screens/vm_developer/object_inspector/vm_object_model.dart'; +export 'src/screens/vm_developer/queued_microtasks/queued_microtasks_controller.dart'; export 'src/screens/vm_developer/vm_developer_tools_controller.dart'; export 'src/screens/vm_developer/vm_developer_tools_screen.dart'; export 'src/screens/vm_developer/vm_service_private_extensions.dart'; diff --git a/packages/devtools_app/lib/src/screens/vm_developer/queued_microtasks/queued_microtasks_controller.dart b/packages/devtools_app/lib/src/screens/vm_developer/queued_microtasks/queued_microtasks_controller.dart new file mode 100644 index 00000000000..e6779886612 --- /dev/null +++ b/packages/devtools_app/lib/src/screens/vm_developer/queued_microtasks/queued_microtasks_controller.dart @@ -0,0 +1,72 @@ +// Copyright 2025 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. + +import 'dart:async'; + +import 'package:devtools_app_shared/utils.dart'; +import 'package:flutter/foundation.dart'; +import 'package:vm_service/vm_service.dart'; + +import '../../../shared/globals.dart'; +import '../../../shared/utils/future_work_tracker.dart'; + +enum QueuedMicrotasksControllerStatus { empty, refreshing, ready } + +class QueuedMicrotasksController extends DisposableController + with AutoDisposeControllerMixin { + QueuedMicrotasksController() { + addAutoDisposeListener(_refreshWorkTracker.active, () { + final active = _refreshWorkTracker.active.value; + if (active) { + _status.value = QueuedMicrotasksControllerStatus.refreshing; + } else { + _status.value = QueuedMicrotasksControllerStatus.ready; + } + }); + } + + ValueListenable get status => _status; + final _status = ValueNotifier( + QueuedMicrotasksControllerStatus.empty, + ); + + ValueListenable get queuedMicrotasks => _queuedMicrotasks; + final _queuedMicrotasks = ValueNotifier(null); + + ValueListenable get selectedMicrotask => _selectedMicrotask; + final _selectedMicrotask = ValueNotifier(null); + + final _refreshWorkTracker = FutureWorkTracker(); + + Future refresh() => _refreshWorkTracker.track(() async { + _selectedMicrotask.value = null; + + final isolateId = serviceConnection + .serviceManager + .isolateManager + .selectedIsolate + .value! + .id!; + final queuedMicrotasks = await serviceConnection.serviceManager.service! + .getQueuedMicrotasks(isolateId); + _queuedMicrotasks.value = queuedMicrotasks; + + return; + }); + + void setSelectedMicrotask(Microtask? microtask) { + _selectedMicrotask.value = microtask; + } + + @override + void dispose() { + _status.dispose(); + _queuedMicrotasks.dispose(); + _selectedMicrotask.dispose(); + _refreshWorkTracker + ..clear() + ..dispose(); + super.dispose(); + } +} diff --git a/packages/devtools_app/lib/src/screens/vm_developer/queued_microtasks/queued_microtasks_view.dart b/packages/devtools_app/lib/src/screens/vm_developer/queued_microtasks/queued_microtasks_view.dart new file mode 100644 index 00000000000..f11e60d552b --- /dev/null +++ b/packages/devtools_app/lib/src/screens/vm_developer/queued_microtasks/queued_microtasks_view.dart @@ -0,0 +1,275 @@ +// Copyright 2025 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. + +import 'package:collection/collection.dart' show ListExtensions; +import 'package:devtools_app_shared/ui.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:vm_service/vm_service.dart'; + +import '../../../shared/analytics/constants.dart' as gac; +import '../../../shared/primitives/utils.dart' show SortDirection; +import '../../../shared/table/table.dart' show FlatTable; +import '../../../shared/table/table_data.dart'; +import '../../../shared/ui/common_widgets.dart'; +import '../vm_developer_tools_screen.dart'; +import 'queued_microtasks_controller.dart'; + +class RefreshQueuedMicrotasksButton extends StatelessWidget { + const RefreshQueuedMicrotasksButton({super.key, required this.controller}); + + final QueuedMicrotasksController controller; + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: controller.status, + builder: (_, status, _) { + return RefreshButton( + onPressed: status == QueuedMicrotasksControllerStatus.refreshing + ? null + : controller.refresh, + tooltip: + "Take a new snapshot of the selected isolate's microtask queue.", + gaScreen: gac.vmTools, + gaSelection: gac.refreshQueuedMicrotasks, + ); + }, + ); + } +} + +class RefreshQueuedMicrotasksInstructions extends StatelessWidget { + const RefreshQueuedMicrotasksInstructions({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: RichText( + text: TextSpan( + style: Theme.of(context).regularTextStyle, + children: [ + const TextSpan(text: 'Click the refresh button '), + WidgetSpan(child: Icon(Icons.refresh, size: defaultIconSize)), + const TextSpan( + text: + " to take a new snapshot of the selected isolate's " + 'microtask queue.', + ), + ], + ), + ), + ); + } +} + +/// Record containing details about a particular microtask that was in a +/// microtask queue snapshot, and an index representing how close to the front +/// of the queue that microtask was when the snapshot was taken. +/// +/// In the response returned by the VM Service, microtasks are sorted in +/// ascending order of when they will be dequeued, i.e. the microtask that will +/// run earliest is at index 0 of the returned list. We use those indices of the +/// returned list to sort the entries of the microtask selector, so that they +/// they also appear in ascending order of when they will be dequeued. +typedef IndexedMicrotask = ({int index, Microtask microtask}); + +class _MicrotaskIdColumn extends ColumnData { + _MicrotaskIdColumn() + : super.wide('Microtask ID', alignment: ColumnAlignment.center); + + @override + int getValue(IndexedMicrotask indexedMicrotask) => indexedMicrotask.index; + + @override + String getDisplayValue(IndexedMicrotask indexedMicrotask) => + indexedMicrotask.microtask.id!.toString(); +} + +class QueuedMicrotaskSelector extends StatelessWidget { + const QueuedMicrotaskSelector({ + super.key, + required List indexedMicrotasks, + required void Function(Microtask?) onMicrotaskSelected, + }) : _indexedMicrotasks = indexedMicrotasks, + _setSelectedMicrotask = onMicrotaskSelected; + + static final _idColumn = _MicrotaskIdColumn(); + final List _indexedMicrotasks; + final void Function(Microtask?) _setSelectedMicrotask; + + @override + Widget build(BuildContext context) => FlatTable( + keyFactory: (IndexedMicrotask microtask) => ValueKey(microtask.index), + data: _indexedMicrotasks, + dataKey: 'queued-microtask-selector', + columns: [_idColumn], + defaultSortColumn: _idColumn, + defaultSortDirection: SortDirection.ascending, + onItemSelected: (indexedMicrotask) => + _setSelectedMicrotask(indexedMicrotask?.microtask), + ); +} + +class MicrotaskStackTraceView extends StatelessWidget { + const MicrotaskStackTraceView({super.key, required selectedMicrotask}) + : _selectedMicrotask = selectedMicrotask; + + final Microtask? _selectedMicrotask; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Column( + children: [ + Container( + height: defaultHeaderHeight, + padding: const EdgeInsets.only(left: defaultSpacing), + alignment: Alignment.centerLeft, + child: OutlineDecoration.onlyBottom( + child: const Row( + children: [ + Text('Stack trace captured when microtask was enqueued'), + ], + ), + ), + ), + Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: denseRowSpacing, + horizontal: defaultSpacing, + ), + child: SelectableText( + style: theme.fixedFontStyle, + _selectedMicrotask!.stackTrace.toString(), + ), + ), + ), + ], + ), + ], + ); + } +} + +class QueuedMicrotasksView extends VMDeveloperView { + const QueuedMicrotasksView() + : super(title: 'Queued Microtasks', icon: Icons.pending_actions); + + @override + bool get showIsolateSelector => true; + + @override + Widget build(BuildContext context) => QueuedMicrotasksViewBody(); +} + +class QueuedMicrotasksViewBody extends StatelessWidget { + QueuedMicrotasksViewBody({super.key}); + + @visibleForTesting + static final dateTimeFormat = DateFormat('HH:mm:ss.SSS (MM/dd/yy)'); + final controller = QueuedMicrotasksController(); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RefreshQueuedMicrotasksButton(controller: controller), + const SizedBox(height: denseRowSpacing), + Expanded( + child: OutlineDecoration( + child: ValueListenableBuilder( + valueListenable: controller.status, + builder: (context, status, _) { + if (status == QueuedMicrotasksControllerStatus.empty) { + return const RefreshQueuedMicrotasksInstructions(); + } else if (status == + QueuedMicrotasksControllerStatus.refreshing) { + return const CenteredMessage(message: 'Refreshing...'); + } else { + return ValueListenableBuilder( + valueListenable: controller.queuedMicrotasks, + builder: (_, queuedMicrotasks, _) { + assert(queuedMicrotasks != null); + if (queuedMicrotasks == null) { + return const CenteredMessage( + message: 'Unexpected null value', + ); + } + + final indexedMicrotasks = queuedMicrotasks.microtasks! + .mapIndexed( + (index, microtask) => + (index: index, microtask: microtask), + ) + .toList(); + final formattedTimestamp = dateTimeFormat.format( + DateTime.fromMicrosecondsSinceEpoch( + queuedMicrotasks.timestamp!, + ), + ); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: denseRowSpacing, + horizontal: defaultSpacing, + ), + child: Text( + 'Viewing snapshot that was taken at ' + '$formattedTimestamp.', + ), + ), + Expanded( + child: SplitPane( + axis: Axis.horizontal, + initialFractions: const [0.15, 0.85], + children: [ + OutlineDecoration( + child: QueuedMicrotaskSelector( + indexedMicrotasks: indexedMicrotasks, + onMicrotaskSelected: + controller.setSelectedMicrotask, + ), + ), + ValueListenableBuilder( + valueListenable: controller.selectedMicrotask, + builder: (_, selectedMicrotask, _) => + OutlineDecoration( + child: selectedMicrotask == null + ? const CenteredMessage( + message: + 'Select a microtask ID on ' + 'the left to see ' + 'information about the ' + 'corresponding microtask.', + ) + : MicrotaskStackTraceView( + selectedMicrotask: + selectedMicrotask, + ), + ), + ), + ], + ), + ), + ], + ); + }, + ); + } + }, + ), + ), + ), + ], + ); + } +} diff --git a/packages/devtools_app/lib/src/screens/vm_developer/vm_developer_tools_screen.dart b/packages/devtools_app/lib/src/screens/vm_developer/vm_developer_tools_screen.dart index f7564d5b638..e30f8cdae62 100644 --- a/packages/devtools_app/lib/src/screens/vm_developer/vm_developer_tools_screen.dart +++ b/packages/devtools_app/lib/src/screens/vm_developer/vm_developer_tools_screen.dart @@ -6,11 +6,13 @@ import 'package:devtools_app_shared/ui.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import '../../service/vm_flags.dart' as vm_flags; import '../../shared/framework/screen.dart'; import '../../shared/globals.dart'; import 'isolate_statistics/isolate_statistics_view.dart'; import 'object_inspector/object_inspector_view.dart'; import 'process_memory/process_memory_view.dart'; +import 'queued_microtasks/queued_microtasks_view.dart'; import 'vm_developer_tools_controller.dart'; import 'vm_statistics/vm_statistics_view.dart'; @@ -49,11 +51,21 @@ class VMDeveloperToolsScreen extends Screen { class VMDeveloperToolsScreenBody extends StatelessWidget { const VMDeveloperToolsScreenBody({super.key}); + // The value of the `--profile-microtasks` VM flag cannot be modified once + // the VM has started running. + static final showQueuedMicrotasks = + serviceConnection.vmFlagManager + .flag(vm_flags.profileMicrotasks) + ?.value + .valueAsString == + 'true'; + static final views = [ const VMStatisticsView(), const IsolateStatisticsView(), ObjectInspectorView(), const VMProcessMemoryView(), + if (showQueuedMicrotasks) const QueuedMicrotasksView(), ]; @override diff --git a/packages/devtools_app/lib/src/service/vm_flags.dart b/packages/devtools_app/lib/src/service/vm_flags.dart index 3e803ca3603..71d9955aed0 100644 --- a/packages/devtools_app/lib/src/service/vm_flags.dart +++ b/packages/devtools_app/lib/src/service/vm_flags.dart @@ -16,6 +16,9 @@ const profiler = 'profiler'; // Defined in SDK: https://github.com/dart-lang/sdk/blob/master/runtime/vm/profiler.cc#L36 const profilePeriod = 'profile_period'; +// Defined in SDK: https://github.com/dart-lang/sdk/blob/main/runtime/vm/microtask_mirror_queues.cc. +const profileMicrotasks = 'profile_microtasks'; + class VmFlagManager with DisposerMixin { VmServiceWrapper get service => _service; late VmServiceWrapper _service; diff --git a/packages/devtools_app/lib/src/shared/analytics/constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants.dart index ad42fcfec24..2e9ec0da4df 100644 --- a/packages/devtools_app/lib/src/shared/analytics/constants.dart +++ b/packages/devtools_app/lib/src/shared/analytics/constants.dart @@ -115,6 +115,7 @@ const refreshIsolateStatistics = 'refreshIsolateStatistics'; const refreshVmStatistics = 'refreshVmStatistics'; const refreshProcessMemoryStatistics = 'refreshProcessMemoryStatistics'; const requestSize = 'requestSize'; +const refreshQueuedMicrotasks = 'refreshQueuedMicrotasks'; // Settings actions: const settingsDialog = 'settings'; diff --git a/packages/devtools_app/pubspec.yaml b/packages/devtools_app/pubspec.yaml index 973f709784d..5af4ae32bd4 100644 --- a/packages/devtools_app/pubspec.yaml +++ b/packages/devtools_app/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: collection: ^1.15.0 dap: ^1.1.0 dart_service_protocol_shared: ^0.0.3 - dds_service_extensions: ^2.0.0 + dds_service_extensions: ^2.0.2 devtools_app_shared: devtools_extensions: devtools_shared: @@ -53,7 +53,7 @@ dependencies: stack_trace: ^1.12.0 string_scanner: ^1.4.0 unified_analytics: ^7.0.0 - vm_service: '>=15.0.0 <16.0.0' + vm_service: ^15.0.2 vm_service_protos: ^1.0.0 vm_snapshot_analysis: ^0.7.6 web: ^1.0.0 diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index 02466fc7044..7122bb2d6e4 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -61,6 +61,14 @@ TODO: Remove this section if there are not any general updates. TODO: Remove this section if there are not any general updates. +## Advanced developer mode updates + +- Added a Queued Microtasks tab to the VM Tools screen, which allows a user to + see details about the microtasks scheduled in an isolate's microtask queue. + This tab currently only appears when DevTools is connected to a Flutter or + Dart app started with `--profile-microtasks`. - + [#9239](https://github.com/flutter/devtools/pull/9239). + ## Full commit history To find a complete list of changes in this release, check out the diff --git a/packages/devtools_app/release_notes/helpers/release_notes_template.md b/packages/devtools_app/release_notes/helpers/release_notes_template.md index ac14a563bc4..afc1b1521ef 100644 --- a/packages/devtools_app/release_notes/helpers/release_notes_template.md +++ b/packages/devtools_app/release_notes/helpers/release_notes_template.md @@ -61,6 +61,10 @@ TODO: Remove this section if there are not any general updates. TODO: Remove this section if there are not any general updates. +## Advanced developer mode updates + +TODO: Remove this section if there are not any general updates. + ## Full commit history To find a complete list of changes in this release, check out the diff --git a/packages/devtools_app/test/screens/vm_developer/queued_microtasks/queued_microtasks_controller_test.dart b/packages/devtools_app/test/screens/vm_developer/queued_microtasks/queued_microtasks_controller_test.dart new file mode 100644 index 00000000000..9e7e76cdc17 --- /dev/null +++ b/packages/devtools_app/test/screens/vm_developer/queued_microtasks/queued_microtasks_controller_test.dart @@ -0,0 +1,38 @@ +// Copyright 2025 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. + +import 'package:devtools_app/devtools_app.dart'; +import 'package:devtools_app_shared/utils.dart'; +import 'package:devtools_test/devtools_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../vm_developer_test_utils.dart'; + +void main() { + late FakeServiceConnectionManager fakeServiceConnection; + late QueuedMicrotasksController controller; + + group('QueuedMicrotasksController', () { + setUp(() { + fakeServiceConnection = FakeServiceConnectionManager( + service: FakeServiceManager.createFakeService( + queuedMicrotasks: testQueuedMicrotasks, + ), + ); + setGlobal(ServiceConnectionManager, fakeServiceConnection); + + controller = QueuedMicrotasksController(); + }); + + test('refresh', () async { + expect(controller.status.value, QueuedMicrotasksControllerStatus.empty); + expect(controller.queuedMicrotasks.value, null); + + await controller.refresh(); + + expect(controller.status.value, QueuedMicrotasksControllerStatus.ready); + expect(controller.queuedMicrotasks.value, testQueuedMicrotasks); + }); + }); +} diff --git a/packages/devtools_app/test/screens/vm_developer/queued_microtasks/queued_microtasks_view_test.dart b/packages/devtools_app/test/screens/vm_developer/queued_microtasks/queued_microtasks_view_test.dart new file mode 100644 index 00000000000..f024b87f538 --- /dev/null +++ b/packages/devtools_app/test/screens/vm_developer/queued_microtasks/queued_microtasks_view_test.dart @@ -0,0 +1,74 @@ +// Copyright 2025 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. + +import 'package:devtools_app/devtools_app.dart'; +import 'package:devtools_app/src/screens/vm_developer/queued_microtasks/queued_microtasks_view.dart'; +import 'package:devtools_app_shared/ui.dart'; +import 'package:devtools_app_shared/utils.dart'; +import 'package:devtools_test/devtools_test.dart'; +import 'package:devtools_test/helpers.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../vm_developer_test_utils.dart'; + +void main() { + group('QueuedMicrotasksViewBody', () { + setUp(() { + final fakeServiceConnection = FakeServiceConnectionManager( + service: FakeServiceManager.createFakeService( + queuedMicrotasks: testQueuedMicrotasks, + ), + ); + + setGlobal(ServiceConnectionManager, fakeServiceConnection); + setGlobal(IdeTheme, IdeTheme()); + }); + + const windowSize = Size(2225.0, 1000.0); + + testWidgetsWithWindowSize('interactions work as intended', windowSize, ( + WidgetTester tester, + ) async { + // First, we verify that instructions explaining how to take a snapshot + // are shown in the Queued Microtasks View in its initial state. + + await tester.pumpWidget(wrapSimple(QueuedMicrotasksViewBody())); + + expect(find.byType(RefreshQueuedMicrotasksButton), findsOneWidget); + expect(find.byType(RefreshQueuedMicrotasksInstructions), findsOneWidget); + + // Then, we verify that after taking a snapshot, the user is instructed to + // select a microtask ID to see information about the corresponding + // microtask. + + await tester.tap(find.byType(RefreshQueuedMicrotasksButton)); + await tester.pumpAndSettle(); + + final formattedTimestamp = QueuedMicrotasksViewBody.dateTimeFormat.format( + DateTime.fromMicrosecondsSinceEpoch(testQueuedMicrotasks!.timestamp!), + ); + expect( + find.text('Viewing snapshot that was taken at $formattedTimestamp.'), + findsOneWidget, + ); + expect(find.byType(QueuedMicrotaskSelector), findsOneWidget); + expect( + find.text( + 'Select a microtask ID on the left to see information about the ' + 'corresponding microtask.', + ), + findsOneWidget, + ); + + // Finally, we verify that after selecting a microtask ID, the user is + // shown information about the corresponding microtask. + + await tester.tap(find.text(testMicrotask!.id.toString())); + await tester.pumpAndSettle(); + + expect(find.byType(MicrotaskStackTraceView), findsOneWidget); + }); + }); +} diff --git a/packages/devtools_app/test/screens/vm_developer/vm_developer_test_utils.dart b/packages/devtools_app/test/screens/vm_developer/vm_developer_test_utils.dart index 1bd1b373c74..7506a057dda 100644 --- a/packages/devtools_app/test/screens/vm_developer/vm_developer_test_utils.dart +++ b/packages/devtools_app/test/screens/vm_developer/vm_developer_test_utils.dart @@ -240,3 +240,15 @@ void mockVmObject(VmObject object) { when(object.vmName).thenReturn(null); } } + +final testMicrotask = Microtask.parse({ + 'type': 'Microtask', + 'id': 123, + 'stackTrace': 'stack trace', +}); + +final testQueuedMicrotasks = QueuedMicrotasks.parse({ + 'type': 'QueuedMicrotasks', + 'timestamp': DateTime(2001).microsecondsSinceEpoch, + 'microtasks': [testMicrotask], +}); diff --git a/packages/devtools_test/lib/src/mocks/fake_service_manager.dart b/packages/devtools_test/lib/src/mocks/fake_service_manager.dart index 278229aebb7..206eb16f740 100644 --- a/packages/devtools_test/lib/src/mocks/fake_service_manager.dart +++ b/packages/devtools_test/lib/src/mocks/fake_service_manager.dart @@ -116,6 +116,7 @@ class FakeServiceManager extends Fake static FakeVmServiceWrapper createFakeService({ PerfettoTimeline? timelineData, + QueuedMicrotasks? queuedMicrotasks, SocketProfile? socketProfile, HttpProfile? httpProfile, SamplesMemoryJson? memoryData, @@ -128,6 +129,7 @@ class FakeServiceManager extends Fake }) => FakeVmServiceWrapper( _flagManager, timelineData, + queuedMicrotasks, socketProfile, httpProfile, memoryData, diff --git a/packages/devtools_test/lib/src/mocks/fake_vm_service_wrapper.dart b/packages/devtools_test/lib/src/mocks/fake_vm_service_wrapper.dart index 01e2fdd3838..60d2f987857 100644 --- a/packages/devtools_test/lib/src/mocks/fake_vm_service_wrapper.dart +++ b/packages/devtools_test/lib/src/mocks/fake_vm_service_wrapper.dart @@ -17,6 +17,7 @@ class FakeVmServiceWrapper extends Fake implements VmServiceWrapper { FakeVmServiceWrapper( this._vmFlagManager, this._timelineData, + this._queuedMicrotasks, this._socketProfile, this._httpProfile, this._memoryData, @@ -74,6 +75,7 @@ class FakeVmServiceWrapper extends Fake implements VmServiceWrapper { final VmFlagManager _vmFlagManager; final PerfettoTimeline? _timelineData; + final QueuedMicrotasks? _queuedMicrotasks; SocketProfile? _socketProfile; final List _startingSockets; HttpProfile? _httpProfile; @@ -372,6 +374,17 @@ class FakeVmServiceWrapper extends Fake implements VmServiceWrapper { @override Future clearVMTimeline() => Future.value(Success()); + @override + Future getQueuedMicrotasks(String isolateId) { + if (_queuedMicrotasks == null) { + throw StateError( + 'An argument to the queuedMicrotasks parameter was not provided to ' + 'createFakeService', + ); + } + return Future.value(_queuedMicrotasks); + } + @override Future getClassList(String isolateId) { return Future.value(_classList ?? ClassList(classes: []));