Unverified Commit 77fca358 authored by Danny Tuppeny's avatar Danny Tuppeny Committed by GitHub

[DAP] Add a custom event to pass flutter.serviceExtensionStateChanged… (#93870)

parent fae84f67
...@@ -66,4 +66,19 @@ Some custom requests are available for clients to call. Below are the Flutter-sp ...@@ -66,4 +66,19 @@ Some custom requests are available for clients to call. Below are the Flutter-sp
## Custom Events ## Custom Events
The debug adapter may emit several custom events that are useful to clients. There are not currently any custom Flutter events, but the standard Dart DAP custom requests are [documented here](https://github.com/dart-lang/sdk/blob/main/pkg/dds/tool/dap/README.md#custom-events). The debug adapter may emit several custom events that are useful to clients. Below are the Flutter-specific custom events, and the standard Dart DAP custom events are [documented here](https://github.com/dart-lang/sdk/blob/main/pkg/dds/tool/dap/README.md#custom-events).
### `flutter.serviceExtensionStateChanged`
When the value of a Flutter service extension changes, this event is emitted and includes the new value. Values are always encoded as strings, even if numeric/boolean.
```
{
"type": "event",
"event": "flutter.serviceExtensionStateChanged",
"body": {
"extension": "ext.flutter.debugPaint",
"value": "true",
}
}
```
...@@ -145,6 +145,21 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -145,6 +145,21 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
terminatePids(ProcessSignal.sigkill); terminatePids(ProcessSignal.sigkill);
} }
@override
Future<void> handleExtensionEvent(vm.Event event) async {
await super.handleExtensionEvent(event);
switch (event.kind) {
case vm.EventKind.kExtension:
switch (event.extensionKind) {
case 'Flutter.ServiceExtensionStateChanged':
_sendServiceExtensionStateChanged(event.extensionData);
break;
}
break;
}
}
/// Called by [launchRequest] to request that we actually start the app to be run/debugged. /// Called by [launchRequest] to request that we actually start the app to be run/debugged.
/// ///
/// For debugging, this should start paused, connect to the VM Service, set /// For debugging, this should start paused, connect to the VM Service, set
...@@ -445,4 +460,14 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -445,4 +460,14 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
sendOutput('console', 'Failed to $action: $error'); sendOutput('console', 'Failed to $action: $error');
} }
} }
void _sendServiceExtensionStateChanged(vm.ExtensionData? extensionData) {
final Map<String, dynamic>? data = extensionData?.data;
if (data != null) {
sendEvent(
RawEventBody(data),
eventType: 'flutter.serviceExtensionStateChanged',
);
}
}
} }
...@@ -220,6 +220,54 @@ void main() { ...@@ -220,6 +220,54 @@ void main() {
await dap.client.terminate(); await dap.client.terminate();
}); });
testWithoutContext('sends events for extension state updates', () async {
final BasicProject _project = BasicProject();
await _project.setUpIn(tempDir);
const String debugPaintRpc = 'ext.flutter.debugPaint';
// Create a future to capture the isolate ID when the debug paint service
// extension loads, as we'll need that to call it later.
final Future<String> isolateIdForDebugPaint = dap.client
.serviceExtensionAdded(debugPaintRpc)
.then((Map<String, Object/*?*/> body) => body['isolateId'] as String);
// Launch the app and wait for it to print "topLevelFunction" so we know
// it's up and running.
await Future.wait(<Future<Object>>[
dap.client.outputEvents.firstWhere((OutputEventBody output) =>
output.output.startsWith('topLevelFunction')),
dap.client.start(
launch: () => dap.client.launch(
cwd: _project.dir.path,
toolArgs: <String>['-d', 'flutter-tester'],
),
),
], eagerError: true);
// Capture the next relevant state-change event (which should occur as a
// result of the call below).
final Future<Map<String, Object/*?*/>> stateChangeEventFuture =
dap.client.serviceExtensionStateChanged(debugPaintRpc);
// Enable debug paint to trigger the state change.
await dap.client.custom(
'callService',
<String, Object/*?*/>{
'method': debugPaintRpc,
'params': <String, Object/*?*/>{
'enabled': true,
'isolateId': await isolateIdForDebugPaint,
},
},
);
// Ensure the event occurred, and its value was as expected.
final Map<String, Object/*?*/> stateChangeEvent = await stateChangeEventFuture;
expect(stateChangeEvent['value'], 'true'); // extension state change values are always strings
await dap.client.terminate();
});
} }
/// Extracts the output from a set of [OutputEventBody], removing any /// Extracts the output from a set of [OutputEventBody], removing any
......
...@@ -78,6 +78,16 @@ class DapTestClient { ...@@ -78,6 +78,16 @@ class DapTestClient {
return _eventController.stream.where((Event e) => e.event == event); return _eventController.stream.where((Event e) => e.event == event);
} }
/// Returns a stream of custom 'dart.serviceExtensionAdded' events.
Stream<Map<String, Object?>> get serviceExtensionAddedEvents =>
events('dart.serviceExtensionAdded')
.map((Event e) => e.body! as Map<String, Object?>);
/// Returns a stream of custom 'flutter.serviceExtensionStateChanged' events.
Stream<Map<String, Object?>> get serviceExtensionStateChangedEvents =>
events('flutter.serviceExtensionStateChanged')
.map((Event e) => e.body! as Map<String, Object?>);
/// Returns a stream of 'dart.testNotification' custom events from the /// Returns a stream of 'dart.testNotification' custom events from the
/// package:test JSON reporter. /// package:test JSON reporter.
Stream<Map<String, Object?>> get testNotificationEvents => Stream<Map<String, Object?>> get testNotificationEvents =>
...@@ -171,6 +181,18 @@ class DapTestClient { ...@@ -171,6 +181,18 @@ class DapTestClient {
return completer.future; return completer.future;
} }
/// Returns a Future that completes with the next serviceExtensionAdded
/// event for [extension].
Future<Map<String, Object?>> serviceExtensionAdded(String extension) => serviceExtensionAddedEvents.firstWhere(
(Map<String, Object?> body) => body['extensionRPC'] == extension,
orElse: () => throw 'Did not recieve $extension extension added event before stream closed');
/// Returns a Future that completes with the next serviceExtensionStateChanged
/// event for [extension].
Future<Map<String, Object?>> serviceExtensionStateChanged(String extension) => serviceExtensionStateChangedEvents.firstWhere(
(Map<String, Object?> body) => body['extension'] == extension,
orElse: () => throw 'Did not recieve $extension extension state changed event before stream closed');
/// Initializes the debug adapter and launches [program]/[cwd] or calls the /// Initializes the debug adapter and launches [program]/[cwd] or calls the
/// custom [launch] method. /// custom [launch] method.
Future<void> start({ Future<void> start({
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment