diff --git a/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart b/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart index 44dd48ca10b1..3daffd0838e0 100644 --- a/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart +++ b/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart @@ -51,7 +51,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { bool get supportsRestartRequest => true; /// A list of reverse-requests from `flutter run --machine` that should be forwarded to the client. - final Set _requestsToForwardToClient = { + static const Set _requestsToForwardToClient = { // The 'app.exposeUrl' request is sent by Flutter to request the client // exposes a URL to the user and return the public version of that URL. // @@ -65,6 +65,14 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { 'app.exposeUrl', }; + /// A list of events from `flutter run --machine` that should be forwarded to the client. + static const Set _eventsToForwardToClient = { + // The 'app.webLaunchUrl' event is sent to the client to tell it about a URL + // that should be launched (including a flag for whether it has been + // launched by the tool or needs launching by the editor). + 'app.webLaunchUrl', + }; + /// Completers for reverse requests from Flutter that may need to be handled by the client. final Map> _reverseRequestCompleters = >{}; @@ -454,6 +462,17 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { _handleAppStarted(); break; } + + if (_eventsToForwardToClient.contains(event)) { + // Forward the event to the client. + sendEvent( + RawEventBody({ + 'event': event, + 'params': params, + }), + eventType: 'flutter.forwardedEvent', + ); + } } /// Handles incoming reverse requests from `flutter run --machine`. diff --git a/packages/flutter_tools/test/general.shard/dap/flutter_adapter_test.dart b/packages/flutter_tools/test/general.shard/dap/flutter_adapter_test.dart index e2b2a9c90161..6d3e9e98a655 100644 --- a/packages/flutter_tools/test/general.shard/dap/flutter_adapter_test.dart +++ b/packages/flutter_tools/test/general.shard/dap/flutter_adapter_test.dart @@ -121,7 +121,7 @@ void main() { await adapter.terminateRequest(MockRequest(), TerminateArguments(restart: false), terminateCompleter.complete); await terminateCompleter.future; - expect(adapter.flutterRequests, contains('app.stop')); + expect(adapter.dapToFlutterRequests, contains('app.stop')); }); test('does not call "app.stop" on terminateRequest if app was not started', () async { @@ -145,7 +145,7 @@ void main() { await adapter.terminateRequest(MockRequest(), TerminateArguments(restart: false), terminateCompleter.complete); await terminateCompleter.future; - expect(adapter.flutterRequests, isNot(contains('app.stop'))); + expect(adapter.dapToFlutterRequests, isNot(contains('app.stop'))); }); }); @@ -210,7 +210,39 @@ void main() { await adapter.terminateRequest(MockRequest(), TerminateArguments(restart: false), terminateCompleter.complete); await terminateCompleter.future; - expect(adapter.flutterRequests, contains('app.detach')); + expect(adapter.dapToFlutterRequests, contains('app.detach')); + }); + }); + + group('forwards events', () { + test('app.webLaunchUrl', () async { + final MockFlutterDebugAdapter adapter = MockFlutterDebugAdapter( + fileSystem: MemoryFileSystem.test(style: fsStyle), + platform: platform, + ); + + // Simulate Flutter asking for a URL to be launched. + adapter.simulateStdoutMessage({ + 'event': 'app.webLaunchUrl', + 'params': { + 'url': 'http://localhost:123/', + 'launched': false, + } + }); + + // Allow the handler to be processed. + await pumpEventQueue(times: 5000); + + // Find the forwarded event. + final Map message = adapter.dapToClientMessages.singleWhere((Map data) => data['event'] == 'flutter.forwardedEvent'); + // Ensure the body of the event matches the original event sent by Flutter. + expect(message['body'], { + 'event': 'app.webLaunchUrl', + 'params': { + 'url': 'http://localhost:123/', + 'launched': false, + } + }); }); }); @@ -238,7 +270,7 @@ void main() { // Allow the handler to be processed. await pumpEventQueue(times: 5000); - final Map message = adapter.flutterMessages.singleWhere((Map data) => data['id'] == requestId); + final Map message = adapter.dapToFlutterMessages.singleWhere((Map data) => data['id'] == requestId); expect(message['result'], 'http://mapped-host:123/'); }); }); diff --git a/packages/flutter_tools/test/general.shard/dap/mocks.dart b/packages/flutter_tools/test/general.shard/dap/mocks.dart index c304b2cffd79..b8001783794c 100644 --- a/packages/flutter_tools/test/general.shard/dap/mocks.dart +++ b/packages/flutter_tools/test/general.shard/dap/mocks.dart @@ -53,11 +53,15 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter { late List processArgs; late Map? env; - /// A list of all messages sent to the `flutter run` processes `stdin`. - final List> flutterMessages = >[]; + /// A list of all messages sent from the adapter back to the client. + final List> dapToClientMessages = >[]; - /// The `method`s of all requests send to the `flutter run` processes `stdin`. - List get flutterRequests => flutterMessages + /// A list of all messages sent from the adapter to the `flutter run` processes `stdin`. + final List> dapToFlutterMessages = >[]; + + /// The `method`s of all mesages sent to the `flutter run` processes `stdin` + /// by the debug adapter. + List get dapToFlutterRequests => dapToFlutterMessages .map((Map message) => message['method'] as String?) .whereNotNull() .toList(); @@ -92,6 +96,8 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter { /// Handles messages sent from the debug adapter back to the client. void _handleDapToClientMessage(ProtocolMessage message) { + dapToClientMessages.add(message.toJson()); + // Pretend to be the client, delegating any reverse-requests to the relevant // handler that is provided by the test. if (message is Event && message.event == 'flutter.forwardedRequest') { @@ -133,7 +139,7 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter { @override void sendFlutterMessage(Map message) { - flutterMessages.add(message); + dapToFlutterMessages.add(message); // Don't call super because it will try to write to the process that we // didn't actually spawn. }