From b487b8c19ac9e88a26d820f49c26c03bb34cfd9e Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Tue, 7 May 2024 16:06:59 -0700 Subject: [PATCH] [flutter web] Listen for service extension registration events to determine hot-restart method name (#147897) ### Some background: * Flutter registers the hot-restart service extension for all devices **except** web devices. For web devices, DWDS registers the service extensions. * When a user is debugging their code via VS Code, the VS Code Dart extension [sends a hot-restart](https://github.com/Dart-Code/Dart-Code/blob/94cb81c5526adc7c8a1203cba201e8c1d99adf82/src/debug/run_daemon_base.ts#L100) request via stdin/out to flutter_tools * flutter_tools then [calls the "hotRestart" service extension](https://github.com/flutter/flutter/blob/f3978c7a46f083c8e91690b8b8be3e03b1e38155/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart#L447) (which, again, has been registered by DWDS) ### Why is this change necessary? In DWDS, we are changing how we register the "hotRestart" service extension (here is the PR for that: https://github.com/dart-lang/webdev/pull/2388). Previously, we registered the "hotRestart" service extension on a client that was directly connected to the VmService. With https://github.com/dart-lang/webdev/pull/2388, we will be registering the "hotRestart" service extension on a client that is connected to DDS. When a service extension is registered against DDS, DDS adds a prefix to the service extension method name (e.g. "hotRestart" becomes "s0.hotRestart"). It informs clients of the service extension name via `kServiceRegistered` events sent to the `Service` stream. Therefore, this change simply listens to those service extension registered events, and uses them to determine the "hotRestart" service extension's method name. --- .../lib/src/isolated/resident_web_runner.dart | 35 +++++++++++++++++-- .../resident_web_runner_test.dart | 3 ++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index a0de64f755a..54307a49dbb 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -130,6 +130,9 @@ class ResidentWebRunner extends ResidentRunner { FlutterDevice? get device => flutterDevices.first; final FlutterProject flutterProject; + // Mapping from service name to service method. + final Map _registeredMethodsForService = {}; + // Used with the new compiler to generate a bootstrap file containing plugins // and platform initialization. Directory? _generatedEntrypointDirectory; @@ -156,6 +159,7 @@ class ResidentWebRunner extends ResidentRunner { ConnectionResult? _connectionResult; StreamSubscription? _stdOutSub; StreamSubscription? _stdErrSub; + StreamSubscription? _serviceSub; StreamSubscription? _extensionEventSub; bool _exited = false; WipConnection? _wipConnection; @@ -190,8 +194,10 @@ class ResidentWebRunner extends ResidentRunner { await residentDevtoolsHandler!.shutdown(); await _stdOutSub?.cancel(); await _stdErrSub?.cancel(); + await _serviceSub?.cancel(); await _extensionEventSub?.cancel(); await device!.device!.stopApp(null); + _registeredMethodsForService.clear(); try { _generatedEntrypointDirectory?.deleteSync(recursive: true); } on FileSystemException { @@ -444,7 +450,11 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). if (!deviceIsDebuggable) { _logger.printStatus('Recompile complete. Page requires refresh.'); } else if (isRunningDebug) { - await _vmService.service.callMethod('hotRestart'); + // If the hot-restart service extension method is registered, then use + // it. Otherwise, default to calling "hotRestart" without a namespace. + final String hotRestartMethod = + _registeredMethodsForService['hotRestart'] ?? 'hotRestart'; + await _vmService.service.callMethod(hotRestartMethod); } else { // On non-debug builds, a hard refresh is required to ensure the // up to date sources are loaded. @@ -615,17 +625,24 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). _stdOutSub = _vmService.service.onStdoutEvent.listen(onLogEvent); _stdErrSub = _vmService.service.onStderrEvent.listen(onLogEvent); + _serviceSub = _vmService.service.onServiceEvent.listen(_onServiceEvent); try { await _vmService.service.streamListen(vmservice.EventStreams.kStdout); } on vmservice.RPCError { // It is safe to ignore this error because we expect an error to be - // thrown if we're not already subscribed. + // thrown if we're already subscribed. } try { await _vmService.service.streamListen(vmservice.EventStreams.kStderr); } on vmservice.RPCError { // It is safe to ignore this error because we expect an error to be - // thrown if we're not already subscribed. + // thrown if we're already subscribed. + } + try { + await _vmService.service.streamListen(vmservice.EventStreams.kService); + } on vmservice.RPCError { + // It is safe to ignore this error because we expect an error to be + // thrown if we're already subscribed. } try { await _vmService.service.streamListen(vmservice.EventStreams.kIsolate); @@ -703,6 +720,18 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). await device!.exitApps(); appFinished(); } + + void _onServiceEvent(vmservice.Event e) { + if (e.kind == vmservice.EventKind.kServiceRegistered) { + final String serviceName = e.service!; + _registeredMethodsForService[serviceName] = e.method!; + } + + if (e.kind == vmservice.EventKind.kServiceUnregistered) { + final String serviceName = e.service!; + _registeredMethodsForService.remove(serviceName); + } + } } Uri _httpUriFromWebsocketUri(Uri websocketUri) { diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart index 761c4168c4e..775e926a1e2 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart @@ -62,6 +62,9 @@ const List kAttachLogExpectations = const List kAttachIsolateExpectations = [ + FakeVmServiceRequest(method: 'streamListen', args: { + 'streamId': 'Service', + }), FakeVmServiceRequest(method: 'streamListen', args: { 'streamId': 'Isolate', }),