diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index b4c4ba49d37..91de539be17 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -44,12 +44,20 @@ class FlutterDevice { _viewsCache = null; } - void connect() { + /// If the [reloadSources] parameter is not null the 'reloadSources' service + /// will be registered. + /// The 'reloadSources' service can be used by other Service Protocol clients + /// connected to the VM (e.g. Observatory) to request a reload of the source + /// code of the running application (a.k.a. HotReload). + /// This ensures that the reload process follows the normal orchestration of + /// the Flutter Tools and not just the VM internal service. + void connect({ReloadSources reloadSources}) { if (vmServices != null) return; vmServices = new List(observatoryUris.length); for (int i = 0; i < observatoryUris.length; i++) { - vmServices[i] = VMService.connect(observatoryUris[i]); + vmServices[i] = VMService.connect(observatoryUris[i], + reloadSources: reloadSources); printTrace('Connected to service protocol: ${observatoryUris[i]}'); } } @@ -526,14 +534,17 @@ abstract class ResidentRunner { device.stopEchoingDeviceLog(); } - Future connectToServiceProtocol({String viewFilter}) async { + /// If the [reloadSources] parameter is not null the 'reloadSources' service + /// will be registered + Future connectToServiceProtocol({String viewFilter, + ReloadSources reloadSources}) async { if (!debuggingOptions.debuggingEnabled) return new Future.error('Error the service protocol is not enabled.'); bool viewFound = false; for (FlutterDevice device in flutterDevices) { device.viewFilter = viewFilter; - device.connect(); + device.connect(reloadSources: reloadSources); await device.getVMs(); await device.waitForViews(); if (device.views == null) diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index e7eeb335ecd..51300a601f6 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -5,6 +5,8 @@ import 'dart:async'; import 'package:meta/meta.dart'; +import 'package:json_rpc_2/error_code.dart' as rpc_error_code; +import 'package:json_rpc_2/json_rpc_2.dart' as rpc; import 'base/context.dart'; import 'base/file_system.dart'; @@ -83,13 +85,26 @@ class HotRunner extends ResidentRunner { return true; } + Future _reloadSourcesService(String isolateId, + { bool force: false, bool pause: false }) async { + // TODO(cbernaschina): check that isolateId is the id of the UI isolate. + final OperationResult result = await restart(pauseAfterRestart: pause); + if (result != OperationResult.ok) { + throw new rpc.RpcException( + rpc_error_code.INTERNAL_ERROR, + 'Unable to reload sources', + ); + } + } + Future attach({ Completer connectionInfoCompleter, Completer appStartedCompleter, String viewFilter, }) async { try { - await connectToServiceProtocol(viewFilter: viewFilter); + await connectToServiceProtocol(viewFilter: viewFilter, + reloadSources: _reloadSourcesService); } catch (error) { printError('Error connecting to the service protocol: $error'); return 2; diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index fa3899bf8ac..2b5f41bb6e1 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -24,6 +24,23 @@ typedef StreamChannel _OpenChannel(Uri uri); _OpenChannel _openChannel = _defaultOpenChannel; +/// A function that reacts to the invocation of the 'reloadSources' service. +/// +/// The VM Service Protocol allows clients to register custom services that +/// can be invoked by other clients through the service protocol itself. +/// +/// Clients like Observatory use external 'reloadSources' services, +/// when available, instead of the VM internal one. This allows these clients to +/// invoke Flutter HotReload when connected to a Flutter Application started in +/// hot mode. +/// +/// See: https://github.com/dart-lang/sdk/issues/30023 +typedef Future ReloadSources( + String isolateId, { + bool force, + bool pause, +}); + const String _kRecordingType = 'vmservice'; StreamChannel _defaultOpenChannel(Uri uri) => @@ -40,13 +57,51 @@ const Duration kShortRequestTimeout = const Duration(seconds: 5); /// A connection to the Dart VM Service. class VMService { - VMService._(this._peer, this.httpAddress, this.wsAddress, this._requestTimeout) { + VMService._( + this._peer, + this.httpAddress, + this.wsAddress, + this._requestTimeout, + ReloadSources reloadSources, + ) { _vm = new VM._empty(this); _peer.listen().catchError(_connectionError.completeError); _peer.registerMethod('streamNotify', (rpc.Parameters event) { _handleStreamNotify(event.asMap); }); + + if (reloadSources != null) { + _peer.registerMethod('reloadSources', (rpc.Parameters params) async { + final String isolateId = params['isolateId'].value; + final bool force = params.asMap['force'] ?? false; + final bool pause = params.asMap['pause'] ?? false; + + if (isolateId is! String || isolateId.isEmpty) + throw new rpc.RpcException.invalidParams('Invalid \'isolateId\': $isolateId'); + if (force is! bool) + throw new rpc.RpcException.invalidParams('Invalid \'force\': $force'); + if (pause is! bool) + throw new rpc.RpcException.invalidParams('Invalid \'pause\': $pause'); + + try { + await reloadSources(isolateId, force: force, pause: pause); + return {'type': 'Success'}; + } on rpc.RpcException { + rethrow; + } catch (e, st) { + throw new rpc.RpcException(rpc_error_code.SERVER_ERROR, + 'Error during Sources Reload: $e\n$st'); + } + }); + + // If the Flutter Engine doesn't support service registration this will + // have no effect + _peer.sendNotification('_registerService', { + 'service': 'reloadSources', + 'alias': 'Flutter Tools' + }); + } } /// Enables recording of VMService JSON-rpc activity to the specified base @@ -76,16 +131,24 @@ class VMService { /// Connect to a Dart VM Service at [httpUri]. /// - /// Requests made via the returns [VMService] time out after [requestTimeout] + /// Requests made via the returned [VMService] time out after [requestTimeout] /// amount of time, which is [kDefaultRequestTimeout] by default. + /// + /// If the [reloadSources] parameter is not null, the 'reloadSources' service + /// will be registered. The VM Service Protocol allows clients to register + /// custom services that can be invoked by other clients through the service + /// protocol itself. + /// + /// See: https://github.com/dart-lang/sdk/commit/df8bf384eb815cf38450cb50a0f4b62230fba217 static VMService connect( Uri httpUri, { Duration requestTimeout: kDefaultRequestTimeout, + ReloadSources reloadSources, }) { final Uri wsUri = httpUri.replace(scheme: 'ws', path: fs.path.join(httpUri.path, 'ws')); final StreamChannel channel = _openChannel(wsUri); final rpc.Peer peer = new rpc.Peer.withoutJson(jsonDocument.bind(channel)); - return new VMService._(peer, httpUri, wsUri, requestTimeout); + return new VMService._(peer, httpUri, wsUri, requestTimeout, reloadSources); } final Uri httpAddress;