|  | // Copyright 2016 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | import 'dart:async'; | 
|  | import 'dart:math' as math; | 
|  |  | 
|  | import 'package:file/file.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 'package:meta/meta.dart' show required; | 
|  | import 'package:stream_channel/stream_channel.dart'; | 
|  | import 'package:web_socket_channel/io.dart'; | 
|  | import 'package:web_socket_channel/web_socket_channel.dart'; | 
|  |  | 
|  | import 'base/common.dart'; | 
|  | import 'base/context.dart'; | 
|  | import 'base/file_system.dart'; | 
|  | import 'base/io.dart' as io; | 
|  | import 'base/utils.dart'; | 
|  | import 'convert.dart' show base64; | 
|  | import 'globals.dart'; | 
|  | import 'vmservice_record_replay.dart'; | 
|  |  | 
|  | /// Override `WebSocketConnector` in [context] to use a different constructor | 
|  | /// for [WebSocket]s (used by tests). | 
|  | typedef WebSocketConnector = Future<io.WebSocket> Function(String url, {io.CompressionOptions compression}); | 
|  |  | 
|  | /// A function that opens a two-way communication channel to the specified [uri]. | 
|  | typedef _OpenChannel = Future<StreamChannel<String>> Function(Uri uri, {io.CompressionOptions compression}); | 
|  |  | 
|  | _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 ReloadSources = Future<void> Function( | 
|  | String isolateId, { | 
|  | bool force, | 
|  | bool pause, | 
|  | }); | 
|  |  | 
|  | typedef Restart = Future<void> Function({ bool pause }); | 
|  |  | 
|  | typedef CompileExpression = Future<String> Function( | 
|  | String isolateId, | 
|  | String expression, | 
|  | List<String> definitions, | 
|  | List<String> typeDefinitions, | 
|  | String libraryUri, | 
|  | String klass, | 
|  | bool isStatic, | 
|  | ); | 
|  |  | 
|  | const String _kRecordingType = 'vmservice'; | 
|  |  | 
|  | Future<StreamChannel<String>> _defaultOpenChannel(Uri uri, {io.CompressionOptions compression = io.CompressionOptions.compressionDefault}) async { | 
|  | Duration delay = const Duration(milliseconds: 100); | 
|  | int attempts = 0; | 
|  | io.WebSocket socket; | 
|  |  | 
|  | Future<void> handleError(dynamic e) async { | 
|  | printTrace('Exception attempting to connect to Observatory: $e'); | 
|  | printTrace('This was attempt #$attempts. Will retry in $delay.'); | 
|  |  | 
|  | if (attempts == 10) | 
|  | printStatus('This is taking longer than expected...'); | 
|  |  | 
|  | // Delay next attempt. | 
|  | await Future<void>.delayed(delay); | 
|  |  | 
|  | // Back off exponentially, up to 1600ms per attempt. | 
|  | if (delay < const Duration(seconds: 1)) | 
|  | delay *= 2; | 
|  | } | 
|  |  | 
|  | final WebSocketConnector constructor = context.get<WebSocketConnector>() ?? io.WebSocket.connect; | 
|  | while (socket == null) { | 
|  | attempts += 1; | 
|  | try { | 
|  | socket = await constructor(uri.toString(), compression: compression); | 
|  | } on io.WebSocketException catch (e) { | 
|  | await handleError(e); | 
|  | } on io.SocketException catch (e) { | 
|  | await handleError(e); | 
|  | } | 
|  | } | 
|  | return IOWebSocketChannel(socket).cast<String>(); | 
|  | } | 
|  |  | 
|  | /// A connection to the Dart VM Service. | 
|  | // TODO(mklim): Test this, https://github.com/flutter/flutter/issues/23031 | 
|  | class VMService { | 
|  | VMService( | 
|  | this._peer, | 
|  | this.httpAddress, | 
|  | this.wsAddress, | 
|  | ReloadSources reloadSources, | 
|  | Restart restart, | 
|  | CompileExpression compileExpression, | 
|  | ) { | 
|  | _vm = 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 rpc.RpcException.invalidParams('Invalid \'isolateId\': $isolateId'); | 
|  | if (force is! bool) | 
|  | throw rpc.RpcException.invalidParams('Invalid \'force\': $force'); | 
|  | if (pause is! bool) | 
|  | throw rpc.RpcException.invalidParams('Invalid \'pause\': $pause'); | 
|  |  | 
|  | try { | 
|  | await reloadSources(isolateId, force: force, pause: pause); | 
|  | return <String, String>{'type': 'Success'}; | 
|  | } on rpc.RpcException { | 
|  | rethrow; | 
|  | } catch (e, st) { | 
|  | throw 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', <String, String>{ | 
|  | 'service': 'reloadSources', | 
|  | 'alias': 'Flutter Tools', | 
|  | }); | 
|  | } | 
|  |  | 
|  | if (restart != null) { | 
|  | _peer.registerMethod('hotRestart', (rpc.Parameters params) async { | 
|  | final bool pause = params.asMap['pause'] ?? false; | 
|  |  | 
|  | if (pause is! bool) | 
|  | throw rpc.RpcException.invalidParams('Invalid \'pause\': $pause'); | 
|  |  | 
|  | try { | 
|  | await restart(pause: pause); | 
|  | return <String, String>{'type': 'Success'}; | 
|  | } on rpc.RpcException { | 
|  | rethrow; | 
|  | } catch (e, st) { | 
|  | throw rpc.RpcException(rpc_error_code.SERVER_ERROR, | 
|  | 'Error during Hot Restart: $e\n$st'); | 
|  | } | 
|  | }); | 
|  |  | 
|  | // If the Flutter Engine doesn't support service registration this will | 
|  | // have no effect | 
|  | _peer.sendNotification('_registerService', <String, String>{ | 
|  | 'service': 'hotRestart', | 
|  | 'alias': 'Flutter Tools', | 
|  | }); | 
|  | } | 
|  |  | 
|  | if (compileExpression != null) { | 
|  | _peer.registerMethod('compileExpression', (rpc.Parameters params) async { | 
|  | final String isolateId = params['isolateId'].asString; | 
|  | if (isolateId is! String || isolateId.isEmpty) | 
|  | throw rpc.RpcException.invalidParams( | 
|  | 'Invalid \'isolateId\': $isolateId'); | 
|  | final String expression = params['expression'].asString; | 
|  | if (expression is! String || expression.isEmpty) | 
|  | throw rpc.RpcException.invalidParams( | 
|  | 'Invalid \'expression\': $expression'); | 
|  | final List<String> definitions = | 
|  | List<String>.from(params['definitions'].asList); | 
|  | final List<String> typeDefinitions = | 
|  | List<String>.from(params['typeDefinitions'].asList); | 
|  | final String libraryUri = params['libraryUri'].asString; | 
|  | final String klass = params['klass'].exists ? params['klass'].asString : null; | 
|  | final bool isStatic = params['isStatic'].asBoolOr(false); | 
|  |  | 
|  | try { | 
|  | final String kernelBytesBase64 = await compileExpression(isolateId, | 
|  | expression, definitions, typeDefinitions, libraryUri, klass, | 
|  | isStatic); | 
|  | return <String, dynamic>{'type': 'Success', | 
|  | 'result': <String, dynamic> {'kernelBytes': kernelBytesBase64}}; | 
|  | } on rpc.RpcException { | 
|  | rethrow; | 
|  | } catch (e, st) { | 
|  | throw rpc.RpcException(rpc_error_code.SERVER_ERROR, | 
|  | 'Error during expression compilation: $e\n$st'); | 
|  | } | 
|  | }); | 
|  |  | 
|  | _peer.sendNotification('_registerService', <String, String>{ | 
|  | 'service': 'compileExpression', | 
|  | 'alias': 'Flutter Tools', | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Enables recording of VMService JSON-rpc activity to the specified base | 
|  | /// recording [location]. | 
|  | /// | 
|  | /// Activity will be recorded in a subdirectory of [location] named | 
|  | /// `"vmservice"`. It is permissible for [location] to represent an existing | 
|  | /// non-empty directory as long as there is no collision with the | 
|  | /// `"vmservice"` subdirectory. | 
|  | static void enableRecordingConnection(String location) { | 
|  | final Directory dir = getRecordingSink(location, _kRecordingType); | 
|  | _openChannel = (Uri uri, {io.CompressionOptions compression}) async { | 
|  | final StreamChannel<String> delegate = await _defaultOpenChannel(uri); | 
|  | return RecordingVMServiceChannel(delegate, dir); | 
|  | }; | 
|  | } | 
|  |  | 
|  | /// Enables VMService JSON-rpc replay mode. | 
|  | /// | 
|  | /// [location] must represent a directory to which VMService JSON-rpc | 
|  | /// activity has been recorded (i.e. the result of having been previously | 
|  | /// passed to [enableRecordingConnection]), or a [ToolExit] will be thrown. | 
|  | static void enableReplayConnection(String location) { | 
|  | final Directory dir = getReplaySource(location, _kRecordingType); | 
|  | _openChannel = (Uri uri, {io.CompressionOptions compression}) async => ReplayVMServiceChannel(dir); | 
|  | } | 
|  |  | 
|  | static void _unhandledError(dynamic error, dynamic stack) { | 
|  | logger.printTrace('Error in internal implementation of JSON RPC.\n$error\n$stack'); | 
|  | assert(false); | 
|  | } | 
|  |  | 
|  | /// Connect to a Dart VM Service at [httpUri]. | 
|  | /// | 
|  | /// 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 Future<VMService> connect( | 
|  | Uri httpUri, { | 
|  | ReloadSources reloadSources, | 
|  | Restart restart, | 
|  | CompileExpression compileExpression, | 
|  | io.CompressionOptions compression = io.CompressionOptions.compressionDefault, | 
|  | }) async { | 
|  | final Uri wsUri = httpUri.replace(scheme: 'ws', path: fs.path.join(httpUri.path, 'ws')); | 
|  | final StreamChannel<String> channel = await _openChannel(wsUri, compression: compression); | 
|  | final rpc.Peer peer = rpc.Peer.withoutJson(jsonDocument.bind(channel), onUnhandledError: _unhandledError); | 
|  | final VMService service = VMService(peer, httpUri, wsUri, reloadSources, restart, compileExpression); | 
|  | // This call is to ensure we are able to establish a connection instead of | 
|  | // keeping on trucking and failing farther down the process. | 
|  | await service._sendRequest('getVersion', const <String, dynamic>{}); | 
|  | return service; | 
|  | } | 
|  |  | 
|  | final Uri httpAddress; | 
|  | final Uri wsAddress; | 
|  | final rpc.Peer _peer; | 
|  | final Completer<Map<String, dynamic>> _connectionError = Completer<Map<String, dynamic>>(); | 
|  |  | 
|  | VM _vm; | 
|  | /// The singleton [VM] object. Owns [Isolate] and [FlutterView] objects. | 
|  | VM get vm => _vm; | 
|  |  | 
|  | final Map<String, StreamController<ServiceEvent>> _eventControllers = | 
|  | <String, StreamController<ServiceEvent>>{}; | 
|  |  | 
|  | final Set<String> _listeningFor = <String>{}; | 
|  |  | 
|  | /// Whether our connection to the VM service has been closed; | 
|  | bool get isClosed => _peer.isClosed; | 
|  |  | 
|  | Future<void> get done async { | 
|  | await _peer.done; | 
|  | } | 
|  |  | 
|  | // Events | 
|  | Future<Stream<ServiceEvent>> get onDebugEvent => onEvent('Debug'); | 
|  | Future<Stream<ServiceEvent>> get onExtensionEvent => onEvent('Extension'); | 
|  | // IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, ServiceExtensionAdded | 
|  | Future<Stream<ServiceEvent>> get onIsolateEvent => onEvent('Isolate'); | 
|  | Future<Stream<ServiceEvent>> get onTimelineEvent => onEvent('Timeline'); | 
|  | // TODO(johnmccutchan): Add FlutterView events. | 
|  |  | 
|  | /// Returns a stream of VM service events. | 
|  | /// | 
|  | /// This purposely returns a `Future<Stream<T>>` rather than a `Stream<T>` | 
|  | /// because it first registers with the VM to receive events on the stream, | 
|  | /// and only once the VM has acknowledged that the stream has started will | 
|  | /// we return the associated stream. Any attempt to streamline this API into | 
|  | /// returning `Stream<T>` should take that into account to avoid race | 
|  | /// conditions. | 
|  | Future<Stream<ServiceEvent>> onEvent(String streamId) async { | 
|  | await _streamListen(streamId); | 
|  | return _getEventController(streamId).stream; | 
|  | } | 
|  |  | 
|  | Future<Map<String, dynamic>> _sendRequest( | 
|  | String method, | 
|  | Map<String, dynamic> params, | 
|  | ) { | 
|  | return Future.any<Map<String, dynamic>>(<Future<Map<String, dynamic>>>[ | 
|  | _peer.sendRequest(method, params).then<Map<String, dynamic>>(castStringKeyedMap), | 
|  | _connectionError.future, | 
|  | ]); | 
|  | } | 
|  |  | 
|  | StreamController<ServiceEvent> _getEventController(String eventName) { | 
|  | StreamController<ServiceEvent> controller = _eventControllers[eventName]; | 
|  | if (controller == null) { | 
|  | controller = StreamController<ServiceEvent>.broadcast(); | 
|  | _eventControllers[eventName] = controller; | 
|  | } | 
|  | return controller; | 
|  | } | 
|  |  | 
|  | void _handleStreamNotify(Map<String, dynamic> data) { | 
|  | final String streamId = data['streamId']; | 
|  | final Map<String, dynamic> eventData = data['event']; | 
|  | final Map<String, dynamic> eventIsolate = eventData['isolate']; | 
|  |  | 
|  | // Log event information. | 
|  | printTrace('Notification from VM: $data'); | 
|  |  | 
|  | ServiceEvent event; | 
|  | if (eventIsolate != null) { | 
|  | // getFromMap creates the Isolate if necessary. | 
|  | final Isolate isolate = vm.getFromMap(eventIsolate); | 
|  | event = ServiceObject._fromMap(isolate, eventData); | 
|  | if (event.kind == ServiceEvent.kIsolateExit) { | 
|  | vm._isolateCache.remove(isolate.id); | 
|  | vm._buildIsolateList(); | 
|  | } else if (event.kind == ServiceEvent.kIsolateRunnable) { | 
|  | // Force reload once the isolate becomes runnable so that we | 
|  | // update the root library. | 
|  | isolate.reload(); | 
|  | } | 
|  | } else { | 
|  | // The event doesn't have an isolate, so it is owned by the VM. | 
|  | event = ServiceObject._fromMap(vm, eventData); | 
|  | } | 
|  | _getEventController(streamId).add(event); | 
|  | } | 
|  |  | 
|  | Future<void> _streamListen(String streamId) async { | 
|  | if (!_listeningFor.contains(streamId)) { | 
|  | _listeningFor.add(streamId); | 
|  | await _sendRequest('streamListen', <String, dynamic>{'streamId': streamId}); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Reloads the VM. | 
|  | Future<void> getVM() async => await vm.reload(); | 
|  |  | 
|  | Future<void> refreshViews({ bool waitForViews = false }) => vm.refreshViews(waitForViews: waitForViews); | 
|  | } | 
|  |  | 
|  | /// An error that is thrown when constructing/updating a service object. | 
|  | class VMServiceObjectLoadError { | 
|  | VMServiceObjectLoadError(this.message, this.map); | 
|  | final String message; | 
|  | final Map<String, dynamic> map; | 
|  | } | 
|  |  | 
|  | bool _isServiceMap(Map<String, dynamic> m) { | 
|  | return (m != null) && (m['type'] != null); | 
|  | } | 
|  | bool _hasRef(String type) => (type != null) && type.startsWith('@'); | 
|  | String _stripRef(String type) => _hasRef(type) ? type.substring(1) : type; | 
|  |  | 
|  | /// Given a raw response from the service protocol and a [ServiceObjectOwner], | 
|  | /// recursively walk the response and replace values that are service maps with | 
|  | /// actual [ServiceObject]s. During the upgrade the owner is given a chance | 
|  | /// to return a cached / canonicalized object. | 
|  | void _upgradeCollection( | 
|  | dynamic collection, | 
|  | ServiceObjectOwner owner, | 
|  | ) { | 
|  | if (collection is ServiceMap) | 
|  | return; | 
|  | if (collection is Map<String, dynamic>) { | 
|  | _upgradeMap(collection, owner); | 
|  | } else if (collection is List) { | 
|  | _upgradeList(collection, owner); | 
|  | } | 
|  | } | 
|  |  | 
|  | void _upgradeMap(Map<String, dynamic> map, ServiceObjectOwner owner) { | 
|  | map.forEach((String k, Object v) { | 
|  | if ((v is Map<String, dynamic>) && _isServiceMap(v)) { | 
|  | map[k] = owner.getFromMap(v); | 
|  | } else if (v is List) { | 
|  | _upgradeList(v, owner); | 
|  | } else if (v is Map<String, dynamic>) { | 
|  | _upgradeMap(v, owner); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | void _upgradeList(List<dynamic> list, ServiceObjectOwner owner) { | 
|  | for (int i = 0; i < list.length; i += 1) { | 
|  | final Object v = list[i]; | 
|  | if ((v is Map<String, dynamic>) && _isServiceMap(v)) { | 
|  | list[i] = owner.getFromMap(v); | 
|  | } else if (v is List) { | 
|  | _upgradeList(v, owner); | 
|  | } else if (v is Map<String, dynamic>) { | 
|  | _upgradeMap(v, owner); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Base class of all objects received over the service protocol. | 
|  | abstract class ServiceObject { | 
|  | ServiceObject._empty(this._owner); | 
|  |  | 
|  | /// Factory constructor given a [ServiceObjectOwner] and a service map, | 
|  | /// upgrade the map into a proper [ServiceObject]. This function always | 
|  | /// returns a new instance and does not interact with caches. | 
|  | factory ServiceObject._fromMap( | 
|  | ServiceObjectOwner owner, | 
|  | Map<String, dynamic> map, | 
|  | ) { | 
|  | if (map == null) | 
|  | return null; | 
|  |  | 
|  | if (!_isServiceMap(map)) | 
|  | throw VMServiceObjectLoadError('Expected a service map', map); | 
|  |  | 
|  | final String type = _stripRef(map['type']); | 
|  |  | 
|  | ServiceObject serviceObject; | 
|  | switch (type) { | 
|  | case 'Event': | 
|  | serviceObject = ServiceEvent._empty(owner); | 
|  | break; | 
|  | case 'FlutterView': | 
|  | serviceObject = FlutterView._empty(owner.vm); | 
|  | break; | 
|  | case 'Isolate': | 
|  | serviceObject = Isolate._empty(owner.vm); | 
|  | break; | 
|  | } | 
|  | // If we don't have a model object for this service object type, as a | 
|  | // fallback return a ServiceMap object. | 
|  | serviceObject ??= ServiceMap._empty(owner); | 
|  | // We have now constructed an empty service object, call update to populate it. | 
|  | serviceObject.updateFromMap(map); | 
|  | return serviceObject; | 
|  | } | 
|  |  | 
|  | final ServiceObjectOwner _owner; | 
|  | ServiceObjectOwner get owner => _owner; | 
|  |  | 
|  | /// The id of this object. | 
|  | String get id => _id; | 
|  | String _id; | 
|  |  | 
|  | /// The user-level type of this object. | 
|  | String get type => _type; | 
|  | String _type; | 
|  |  | 
|  | /// The vm-level type of this object. Usually the same as [type]. | 
|  | String get vmType => _vmType; | 
|  | String _vmType; | 
|  |  | 
|  | /// Is it safe to cache this object? | 
|  | bool _canCache = false; | 
|  | bool get canCache => _canCache; | 
|  |  | 
|  | /// Has this object been fully loaded? | 
|  | bool get loaded => _loaded; | 
|  | bool _loaded = false; | 
|  |  | 
|  | /// Is this object immutable after it is [loaded]? | 
|  | bool get immutable => false; | 
|  |  | 
|  | String get name => _name; | 
|  | String _name; | 
|  |  | 
|  | String get vmName => _vmName; | 
|  | String _vmName; | 
|  |  | 
|  | /// If this is not already loaded, load it. Otherwise reload. | 
|  | Future<ServiceObject> load() async { | 
|  | if (loaded) | 
|  | return this; | 
|  | return reload(); | 
|  | } | 
|  |  | 
|  | /// Fetch this object from vmService and return the response directly. | 
|  | Future<Map<String, dynamic>> _fetchDirect() { | 
|  | final Map<String, dynamic> params = <String, dynamic>{ | 
|  | 'objectId': id, | 
|  | }; | 
|  | return _owner.isolate.invokeRpcRaw('getObject', params: params); | 
|  | } | 
|  |  | 
|  | Future<ServiceObject> _inProgressReload; | 
|  | /// Reload the service object (if possible). | 
|  | Future<ServiceObject> reload() async { | 
|  | final bool hasId = (id != null) && (id != ''); | 
|  | final bool isVM = this is VM; | 
|  | // We should always reload the VM. | 
|  | // We can't reload objects without an id. | 
|  | // We shouldn't reload an immutable and already loaded object. | 
|  | if (!isVM && (!hasId || (immutable && loaded))) | 
|  | return this; | 
|  |  | 
|  | if (_inProgressReload == null) { | 
|  | final Completer<ServiceObject> completer = Completer<ServiceObject>(); | 
|  | _inProgressReload = completer.future; | 
|  | try { | 
|  | final Map<String, dynamic> response = await _fetchDirect(); | 
|  | if (_stripRef(response['type']) == 'Sentinel') { | 
|  | // An object may have been collected. | 
|  | completer.complete(ServiceObject._fromMap(owner, response)); | 
|  | } else { | 
|  | updateFromMap(response); | 
|  | completer.complete(this); | 
|  | } | 
|  | } catch (e, st) { | 
|  | completer.completeError(e, st); | 
|  | } | 
|  | _inProgressReload = null; | 
|  | return await completer.future; | 
|  | } | 
|  |  | 
|  | return await _inProgressReload; | 
|  | } | 
|  |  | 
|  | /// Update [this] using [map] as a source. [map] can be a service reference. | 
|  | void updateFromMap(Map<String, dynamic> map) { | 
|  | // Don't allow the type to change on an object update. | 
|  | final bool mapIsRef = _hasRef(map['type']); | 
|  | final String mapType = _stripRef(map['type']); | 
|  |  | 
|  | if ((_type != null) && (_type != mapType)) { | 
|  | throw VMServiceObjectLoadError('ServiceObject types must not change', | 
|  | map); | 
|  | } | 
|  | _type = mapType; | 
|  | _vmType = map.containsKey('_vmType') ? _stripRef(map['_vmType']) : _type; | 
|  |  | 
|  | _canCache = map['fixedId'] == true; | 
|  | if ((_id != null) && (_id != map['id']) && _canCache) { | 
|  | throw VMServiceObjectLoadError('ServiceObject id changed', map); | 
|  | } | 
|  | _id = map['id']; | 
|  |  | 
|  | // Copy name properties. | 
|  | _name = map['name']; | 
|  | _vmName = map.containsKey('_vmName') ? map['_vmName'] : _name; | 
|  |  | 
|  | // We have now updated all common properties, let the subclasses update | 
|  | // their specific properties. | 
|  | _update(map, mapIsRef); | 
|  | } | 
|  |  | 
|  | /// Implemented by subclasses to populate their model. | 
|  | void _update(Map<String, dynamic> map, bool mapIsRef); | 
|  | } | 
|  |  | 
|  | class ServiceEvent extends ServiceObject { | 
|  | ServiceEvent._empty(ServiceObjectOwner owner) : super._empty(owner); | 
|  |  | 
|  | String _kind; | 
|  | String get kind => _kind; | 
|  | DateTime _timestamp; | 
|  | DateTime get timestamp => _timestamp; | 
|  | String _extensionKind; | 
|  | String get extensionKind => _extensionKind; | 
|  | Map<String, dynamic> _extensionData; | 
|  | Map<String, dynamic> get extensionData => _extensionData; | 
|  | List<Map<String, dynamic>> _timelineEvents; | 
|  | List<Map<String, dynamic>> get timelineEvents => _timelineEvents; | 
|  |  | 
|  | // The possible 'kind' values. | 
|  | static const String kVMUpdate               = 'VMUpdate'; | 
|  | static const String kIsolateStart           = 'IsolateStart'; | 
|  | static const String kIsolateRunnable        = 'IsolateRunnable'; | 
|  | static const String kIsolateExit            = 'IsolateExit'; | 
|  | static const String kIsolateUpdate          = 'IsolateUpdate'; | 
|  | static const String kIsolateReload          = 'IsolateReload'; | 
|  | static const String kIsolateSpawn           = 'IsolateSpawn'; | 
|  | static const String kServiceExtensionAdded  = 'ServiceExtensionAdded'; | 
|  | static const String kPauseStart             = 'PauseStart'; | 
|  | static const String kPauseExit              = 'PauseExit'; | 
|  | static const String kPauseBreakpoint        = 'PauseBreakpoint'; | 
|  | static const String kPauseInterrupted       = 'PauseInterrupted'; | 
|  | static const String kPauseException         = 'PauseException'; | 
|  | static const String kPausePostRequest       = 'PausePostRequest'; | 
|  | static const String kNone                   = 'None'; | 
|  | static const String kResume                 = 'Resume'; | 
|  | static const String kBreakpointAdded        = 'BreakpointAdded'; | 
|  | static const String kBreakpointResolved     = 'BreakpointResolved'; | 
|  | static const String kBreakpointRemoved      = 'BreakpointRemoved'; | 
|  | static const String kGraph                  = '_Graph'; | 
|  | static const String kGC                     = 'GC'; | 
|  | static const String kInspect                = 'Inspect'; | 
|  | static const String kDebuggerSettingsUpdate = '_DebuggerSettingsUpdate'; | 
|  | static const String kConnectionClosed       = 'ConnectionClosed'; | 
|  | static const String kLogging                = '_Logging'; | 
|  | static const String kExtension              = 'Extension'; | 
|  |  | 
|  | @override | 
|  | void _update(Map<String, dynamic> map, bool mapIsRef) { | 
|  | _loaded = true; | 
|  | _upgradeCollection(map, owner); | 
|  | _kind = map['kind']; | 
|  | assert(map['isolate'] == null || owner == map['isolate']); | 
|  | _timestamp = | 
|  | DateTime.fromMillisecondsSinceEpoch(map['timestamp']); | 
|  | if (map['extensionKind'] != null) { | 
|  | _extensionKind = map['extensionKind']; | 
|  | _extensionData = map['extensionData']; | 
|  | } | 
|  | // map['timelineEvents'] is List<dynamic> which can't be assigned to | 
|  | // List<Map<String, dynamic>> directly. Unfortunately, we previously didn't | 
|  | // catch this exception because json_rpc_2 is hiding all these exceptions | 
|  | // on a Stream. | 
|  | final List<dynamic> dynamicList = map['timelineEvents']; | 
|  | _timelineEvents = dynamicList?.cast<Map<String, dynamic>>(); | 
|  | } | 
|  |  | 
|  | bool get isPauseEvent { | 
|  | return kind == kPauseStart || | 
|  | kind == kPauseExit || | 
|  | kind == kPauseBreakpoint || | 
|  | kind == kPauseInterrupted || | 
|  | kind == kPauseException || | 
|  | kind == kPausePostRequest || | 
|  | kind == kNone; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// A ServiceObjectOwner is either a [VM] or an [Isolate]. Owners can cache | 
|  | /// and/or canonicalize service objects received over the wire. | 
|  | abstract class ServiceObjectOwner extends ServiceObject { | 
|  | ServiceObjectOwner._empty(ServiceObjectOwner owner) : super._empty(owner); | 
|  |  | 
|  | /// Returns the owning VM. | 
|  | VM get vm => null; | 
|  |  | 
|  | /// Returns the owning isolate (if any). | 
|  | Isolate get isolate => null; | 
|  |  | 
|  | /// Returns the vmService connection. | 
|  | VMService get vmService => null; | 
|  |  | 
|  | /// Builds a [ServiceObject] corresponding to the [id] from [map]. | 
|  | /// The result may come from the cache. The result will not necessarily | 
|  | /// be [loaded]. | 
|  | ServiceObject getFromMap(Map<String, dynamic> map); | 
|  | } | 
|  |  | 
|  | /// There is only one instance of the VM class. The VM class owns [Isolate] | 
|  | /// and [FlutterView] objects. | 
|  | class VM extends ServiceObjectOwner { | 
|  | VM._empty(this._vmService) : super._empty(null); | 
|  |  | 
|  | /// Connection to the VMService. | 
|  | final VMService _vmService; | 
|  | @override | 
|  | VMService get vmService => _vmService; | 
|  |  | 
|  | @override | 
|  | VM get vm => this; | 
|  |  | 
|  | @override | 
|  | Future<Map<String, dynamic>> _fetchDirect() => invokeRpcRaw('getVM'); | 
|  |  | 
|  | @override | 
|  | void _update(Map<String, dynamic> map, bool mapIsRef) { | 
|  | if (mapIsRef) | 
|  | return; | 
|  |  | 
|  | // Upgrade the collection. A side effect of this call is that any new | 
|  | // isolates in the map are created and added to the isolate cache. | 
|  | _upgradeCollection(map, this); | 
|  | _loaded = true; | 
|  |  | 
|  | _pid = map['pid']; | 
|  | if (map['_heapAllocatedMemoryUsage'] != null) | 
|  | _heapAllocatedMemoryUsage = map['_heapAllocatedMemoryUsage']; | 
|  | _maxRSS = map['_maxRSS']; | 
|  | _embedder = map['_embedder']; | 
|  |  | 
|  | // Remove any isolates which are now dead from the isolate cache. | 
|  | _removeDeadIsolates(map['isolates'].cast<Isolate>()); | 
|  | } | 
|  |  | 
|  | final Map<String, ServiceObject> _cache = <String,ServiceObject>{}; | 
|  | final Map<String,Isolate> _isolateCache = <String,Isolate>{}; | 
|  |  | 
|  | /// The list of live isolates, ordered by isolate start time. | 
|  | final List<Isolate> isolates = <Isolate>[]; | 
|  |  | 
|  | /// The set of live views. | 
|  | final Map<String, FlutterView> _viewCache = <String, FlutterView>{}; | 
|  |  | 
|  | /// The pid of the VM's process. | 
|  | int _pid; | 
|  | int get pid => _pid; | 
|  |  | 
|  | /// The number of bytes allocated (e.g. by malloc) in the native heap. | 
|  | int _heapAllocatedMemoryUsage; | 
|  | int get heapAllocatedMemoryUsage => _heapAllocatedMemoryUsage ?? 0; | 
|  |  | 
|  | /// The peak resident set size for the process. | 
|  | int _maxRSS; | 
|  | int get maxRSS => _maxRSS ?? 0; | 
|  |  | 
|  | // The embedder's name, Flutter or dart_runner. | 
|  | String _embedder; | 
|  | String get embedder => _embedder; | 
|  | bool get isFlutterEngine => embedder == 'Flutter'; | 
|  | bool get isDartRunner => embedder == 'dart_runner'; | 
|  |  | 
|  | int _compareIsolates(Isolate a, Isolate b) { | 
|  | final DateTime aStart = a.startTime; | 
|  | final DateTime bStart = b.startTime; | 
|  | if (aStart == null) { | 
|  | if (bStart == null) { | 
|  | return 0; | 
|  | } else { | 
|  | return 1; | 
|  | } | 
|  | } | 
|  | if (bStart == null) { | 
|  | return -1; | 
|  | } | 
|  | return aStart.compareTo(bStart); | 
|  | } | 
|  |  | 
|  | void _buildIsolateList() { | 
|  | final List<Isolate> isolateList = _isolateCache.values.toList(); | 
|  | isolateList.sort(_compareIsolates); | 
|  | isolates.clear(); | 
|  | isolates.addAll(isolateList); | 
|  | } | 
|  |  | 
|  | void _removeDeadIsolates(List<Isolate> newIsolates) { | 
|  | // Build a set of new isolates. | 
|  | final Set<String> newIsolateSet = <String>{}; | 
|  | for (Isolate iso in newIsolates) | 
|  | newIsolateSet.add(iso.id); | 
|  |  | 
|  | // Remove any old isolates which no longer exist. | 
|  | final List<String> toRemove = <String>[]; | 
|  | _isolateCache.forEach((String id, _) { | 
|  | if (!newIsolateSet.contains(id)) { | 
|  | toRemove.add(id); | 
|  | } | 
|  | }); | 
|  | toRemove.forEach(_isolateCache.remove); | 
|  | _buildIsolateList(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | ServiceObject getFromMap(Map<String, dynamic> map) { | 
|  | if (map == null) { | 
|  | return null; | 
|  | } | 
|  | final String type = _stripRef(map['type']); | 
|  | if (type == 'VM') { | 
|  | // Update this VM object. | 
|  | updateFromMap(map); | 
|  | return this; | 
|  | } | 
|  |  | 
|  | final String mapId = map['id']; | 
|  |  | 
|  | switch (type) { | 
|  | case 'Isolate': { | 
|  | // Check cache. | 
|  | Isolate isolate = _isolateCache[mapId]; | 
|  | if (isolate == null) { | 
|  | // Add new isolate to the cache. | 
|  | isolate = ServiceObject._fromMap(this, map); | 
|  | _isolateCache[mapId] = isolate; | 
|  | _buildIsolateList(); | 
|  |  | 
|  | // Eagerly load the isolate. | 
|  | isolate.load().catchError((dynamic e, StackTrace stack) { | 
|  | printTrace('Eagerly loading an isolate failed: $e\n$stack'); | 
|  | }); | 
|  | } else { | 
|  | // Existing isolate, update data. | 
|  | isolate.updateFromMap(map); | 
|  | } | 
|  | return isolate; | 
|  | } | 
|  | break; | 
|  | case 'FlutterView': { | 
|  | FlutterView view = _viewCache[mapId]; | 
|  | if (view == null) { | 
|  | // Add new view to the cache. | 
|  | view = ServiceObject._fromMap(this, map); | 
|  | _viewCache[mapId] = view; | 
|  | } else { | 
|  | view.updateFromMap(map); | 
|  | } | 
|  | return view; | 
|  | } | 
|  | break; | 
|  | default: | 
|  | throw VMServiceObjectLoadError( | 
|  | 'VM.getFromMap called for something other than an isolate', map); | 
|  | } | 
|  | } | 
|  |  | 
|  | // This function does not reload the isolate if it's found in the cache. | 
|  | Future<Isolate> getIsolate(String isolateId) { | 
|  | if (!loaded) { | 
|  | // Trigger a VM load, then get the isolate. Ignore any errors. | 
|  | return load().then<Isolate>((ServiceObject serviceObject) => getIsolate(isolateId)).catchError((dynamic error) => null); | 
|  | } | 
|  | return Future<Isolate>.value(_isolateCache[isolateId]); | 
|  | } | 
|  |  | 
|  | static String _truncate(String message, int width, String ellipsis) { | 
|  | assert(ellipsis.length < width); | 
|  | if (message.length <= width) | 
|  | return message; | 
|  | return message.substring(0, width - ellipsis.length) + ellipsis; | 
|  | } | 
|  |  | 
|  | /// Invoke the RPC and return the raw response. | 
|  | Future<Map<String, dynamic>> invokeRpcRaw( | 
|  | String method, { | 
|  | Map<String, dynamic> params = const <String, dynamic>{}, | 
|  | bool truncateLogs = true, | 
|  | }) async { | 
|  | printTrace('Sending to VM service: $method($params)'); | 
|  | assert(params != null); | 
|  | try { | 
|  | final Map<String, dynamic> result = await _vmService._sendRequest(method, params); | 
|  | final String resultString = | 
|  | truncateLogs ? _truncate(result.toString(), 250, '...') : result.toString(); | 
|  | printTrace('Result: $resultString'); | 
|  | return result; | 
|  | } on WebSocketChannelException catch (error) { | 
|  | throwToolExit('Error connecting to observatory: $error'); | 
|  | return null; | 
|  | } on rpc.RpcException catch (error) { | 
|  | printError('Error ${error.code} received from application: ${error.message}'); | 
|  | printTrace('${error.data}'); | 
|  | rethrow; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Invoke the RPC and return a [ServiceObject] response. | 
|  | Future<T> invokeRpc<T extends ServiceObject>( | 
|  | String method, { | 
|  | Map<String, dynamic> params = const <String, dynamic>{}, | 
|  | bool truncateLogs = true, | 
|  | }) async { | 
|  | final Map<String, dynamic> response = await invokeRpcRaw( | 
|  | method, | 
|  | params: params, | 
|  | truncateLogs: truncateLogs, | 
|  | ); | 
|  | final ServiceObject serviceObject = ServiceObject._fromMap(this, response); | 
|  | if ((serviceObject != null) && (serviceObject._canCache)) { | 
|  | final String serviceObjectId = serviceObject.id; | 
|  | _cache.putIfAbsent(serviceObjectId, () => serviceObject); | 
|  | } | 
|  | return serviceObject; | 
|  | } | 
|  |  | 
|  | /// Create a new development file system on the device. | 
|  | Future<Map<String, dynamic>> createDevFS(String fsName) { | 
|  | return invokeRpcRaw('_createDevFS', params: <String, dynamic>{'fsName': fsName}); | 
|  | } | 
|  |  | 
|  | /// List the development file system son the device. | 
|  | Future<List<String>> listDevFS() async { | 
|  | return (await invokeRpcRaw('_listDevFS'))['fsNames']; | 
|  | } | 
|  |  | 
|  | // Write one file into a file system. | 
|  | Future<Map<String, dynamic>> writeDevFSFile( | 
|  | String fsName, { | 
|  | @required String path, | 
|  | @required List<int> fileContents, | 
|  | }) { | 
|  | assert(path != null); | 
|  | assert(fileContents != null); | 
|  | return invokeRpcRaw( | 
|  | '_writeDevFSFile', | 
|  | params: <String, dynamic>{ | 
|  | 'fsName': fsName, | 
|  | 'path': path, | 
|  | 'fileContents': base64.encode(fileContents), | 
|  | }, | 
|  | ); | 
|  | } | 
|  |  | 
|  | // Read one file from a file system. | 
|  | Future<List<int>> readDevFSFile(String fsName, String path) async { | 
|  | final Map<String, dynamic> response = await invokeRpcRaw( | 
|  | '_readDevFSFile', | 
|  | params: <String, dynamic>{ | 
|  | 'fsName': fsName, | 
|  | 'path': path, | 
|  | }, | 
|  | ); | 
|  | return base64.decode(response['fileContents']); | 
|  | } | 
|  |  | 
|  | /// The complete list of a file system. | 
|  | Future<List<String>> listDevFSFiles(String fsName) async { | 
|  | return (await invokeRpcRaw('_listDevFSFiles', params: <String, dynamic>{'fsName': fsName}))['files']; | 
|  | } | 
|  |  | 
|  | /// Delete an existing file system. | 
|  | Future<Map<String, dynamic>> deleteDevFS(String fsName) { | 
|  | return invokeRpcRaw('_deleteDevFS', params: <String, dynamic>{'fsName': fsName}); | 
|  | } | 
|  |  | 
|  | Future<ServiceMap> runInView( | 
|  | String viewId, | 
|  | Uri main, | 
|  | Uri packages, | 
|  | Uri assetsDirectory, | 
|  | ) { | 
|  | return invokeRpc<ServiceMap>('_flutter.runInView', | 
|  | params: <String, dynamic>{ | 
|  | 'viewId': viewId, | 
|  | 'mainScript': main.toString(), | 
|  | 'packagesFile': packages.toString(), | 
|  | 'assetDirectory': assetsDirectory.toString(), | 
|  | }); | 
|  | } | 
|  |  | 
|  | Future<Map<String, dynamic>> clearVMTimeline() { | 
|  | return invokeRpcRaw('_clearVMTimeline'); | 
|  | } | 
|  |  | 
|  | Future<Map<String, dynamic>> setVMTimelineFlags(List<String> recordedStreams) { | 
|  | assert(recordedStreams != null); | 
|  | return invokeRpcRaw( | 
|  | '_setVMTimelineFlags', | 
|  | params: <String, dynamic>{ | 
|  | 'recordedStreams': recordedStreams, | 
|  | }, | 
|  | ); | 
|  | } | 
|  |  | 
|  | Future<Map<String, dynamic>> getVMTimeline() { | 
|  | return invokeRpcRaw('_getVMTimeline'); | 
|  | } | 
|  |  | 
|  | Future<void> refreshViews({ bool waitForViews = false }) async { | 
|  | assert(waitForViews != null); | 
|  | assert(loaded); | 
|  | if (!isFlutterEngine) | 
|  | return; | 
|  | int failCount = 0; | 
|  | while (true) { | 
|  | _viewCache.clear(); | 
|  | // When the future returned by invokeRpc() below returns, | 
|  | // the _viewCache will have been updated. | 
|  | // This message updates all the views of every isolate. | 
|  | await vmService.vm.invokeRpc<ServiceObject>( | 
|  | '_flutter.listViews', truncateLogs: false); | 
|  | if (_viewCache.values.isNotEmpty || !waitForViews) | 
|  | return; | 
|  | failCount += 1; | 
|  | if (failCount == 5) // waited 200ms | 
|  | printStatus('Flutter is taking longer than expected to report its views. Still trying...'); | 
|  | await Future<void>.delayed(const Duration(milliseconds: 50)); | 
|  | await reload(); | 
|  | } | 
|  | } | 
|  |  | 
|  | Iterable<FlutterView> get views => _viewCache.values; | 
|  |  | 
|  | FlutterView get firstView { | 
|  | return _viewCache.values.isEmpty ? null : _viewCache.values.first; | 
|  | } | 
|  |  | 
|  | List<FlutterView> allViewsWithName(String isolateFilter) { | 
|  | if (_viewCache.values.isEmpty) | 
|  | return null; | 
|  | return _viewCache.values.where( | 
|  | (FlutterView v) => v.uiIsolate.name.contains(isolateFilter) | 
|  | ).toList(); | 
|  | } | 
|  | } | 
|  |  | 
|  | class HeapSpace extends ServiceObject { | 
|  | HeapSpace._empty(ServiceObjectOwner owner) : super._empty(owner); | 
|  |  | 
|  | int _used = 0; | 
|  | int _capacity = 0; | 
|  | int _external = 0; | 
|  | int _collections = 0; | 
|  | double _totalCollectionTimeInSeconds = 0.0; | 
|  | double _averageCollectionPeriodInMillis = 0.0; | 
|  |  | 
|  | int get used => _used; | 
|  | int get capacity => _capacity; | 
|  | int get external => _external; | 
|  |  | 
|  | Duration get avgCollectionTime { | 
|  | final double mcs = _totalCollectionTimeInSeconds * | 
|  | Duration.microsecondsPerSecond / | 
|  | math.max(_collections, 1); | 
|  | return Duration(microseconds: mcs.ceil()); | 
|  | } | 
|  |  | 
|  | Duration get avgCollectionPeriod { | 
|  | final double mcs = _averageCollectionPeriodInMillis * | 
|  | Duration.microsecondsPerMillisecond; | 
|  | return Duration(microseconds: mcs.ceil()); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void _update(Map<String, dynamic> map, bool mapIsRef) { | 
|  | _used = map['used']; | 
|  | _capacity = map['capacity']; | 
|  | _external = map['external']; | 
|  | _collections = map['collections']; | 
|  | _totalCollectionTimeInSeconds = map['time']; | 
|  | _averageCollectionPeriodInMillis = map['avgCollectionPeriodMillis']; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// A function, field or class along with its source location. | 
|  | class ProgramElement { | 
|  | ProgramElement(this.qualifiedName, this.uri, [this.line, this.column]); | 
|  |  | 
|  | final String qualifiedName; | 
|  | final Uri uri; | 
|  | final int line; | 
|  | final int column; | 
|  |  | 
|  | @override | 
|  | String toString() { | 
|  | if (line == null) | 
|  | return '$qualifiedName ($uri)'; | 
|  | else | 
|  | return '$qualifiedName ($uri:$line)'; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// An isolate running inside the VM. Instances of the Isolate class are always | 
|  | /// canonicalized. | 
|  | class Isolate extends ServiceObjectOwner { | 
|  | Isolate._empty(ServiceObjectOwner owner) : super._empty(owner); | 
|  |  | 
|  | @override | 
|  | VM get vm => owner; | 
|  |  | 
|  | @override | 
|  | VMService get vmService => vm.vmService; | 
|  |  | 
|  | @override | 
|  | Isolate get isolate => this; | 
|  |  | 
|  | DateTime startTime; | 
|  |  | 
|  | /// The last pause event delivered to the isolate. If the isolate is running, | 
|  | /// this will be a resume event. | 
|  | ServiceEvent pauseEvent; | 
|  |  | 
|  | final Map<String, ServiceObject> _cache = <String, ServiceObject>{}; | 
|  |  | 
|  | HeapSpace _newSpace; | 
|  | HeapSpace _oldSpace; | 
|  |  | 
|  | HeapSpace get newSpace => _newSpace; | 
|  | HeapSpace get oldSpace => _oldSpace; | 
|  |  | 
|  | @override | 
|  | ServiceObject getFromMap(Map<String, dynamic> map) { | 
|  | if (map == null) | 
|  | return null; | 
|  | final String mapType = _stripRef(map['type']); | 
|  | if (mapType == 'Isolate') { | 
|  | // There are sometimes isolate refs in ServiceEvents. | 
|  | return vm.getFromMap(map); | 
|  | } | 
|  |  | 
|  | final String mapId = map['id']; | 
|  | ServiceObject serviceObject = (mapId != null) ? _cache[mapId] : null; | 
|  | if (serviceObject != null) { | 
|  | serviceObject.updateFromMap(map); | 
|  | return serviceObject; | 
|  | } | 
|  | // Build the object from the map directly. | 
|  | serviceObject = ServiceObject._fromMap(this, map); | 
|  | if ((serviceObject != null) && serviceObject.canCache) | 
|  | _cache[mapId] = serviceObject; | 
|  | return serviceObject; | 
|  | } | 
|  |  | 
|  | @override | 
|  | Future<Map<String, dynamic>> _fetchDirect() => invokeRpcRaw('getIsolate'); | 
|  |  | 
|  | /// Invoke the RPC and return the raw response. | 
|  | Future<Map<String, dynamic>> invokeRpcRaw( | 
|  | String method, { | 
|  | Map<String, dynamic> params, | 
|  | }) { | 
|  | // Inject the 'isolateId' parameter. | 
|  | if (params == null) { | 
|  | params = <String, dynamic>{ | 
|  | 'isolateId': id, | 
|  | }; | 
|  | } else { | 
|  | params['isolateId'] = id; | 
|  | } | 
|  | return vm.invokeRpcRaw(method, params: params); | 
|  | } | 
|  |  | 
|  | /// Invoke the RPC and return a ServiceObject response. | 
|  | Future<ServiceObject> invokeRpc(String method, Map<String, dynamic> params) async { | 
|  | return getFromMap(await invokeRpcRaw(method, params: params)); | 
|  | } | 
|  |  | 
|  | void _updateHeaps(Map<String, dynamic> map, bool mapIsRef) { | 
|  | _newSpace ??= HeapSpace._empty(this); | 
|  | _newSpace._update(map['new'], mapIsRef); | 
|  | _oldSpace ??= HeapSpace._empty(this); | 
|  | _oldSpace._update(map['old'], mapIsRef); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void _update(Map<String, dynamic> map, bool mapIsRef) { | 
|  | if (mapIsRef) | 
|  | return; | 
|  | _loaded = true; | 
|  |  | 
|  | final int startTimeMillis = map['startTime']; | 
|  | startTime = DateTime.fromMillisecondsSinceEpoch(startTimeMillis); | 
|  |  | 
|  | _upgradeCollection(map, this); | 
|  |  | 
|  | pauseEvent = map['pauseEvent']; | 
|  |  | 
|  | _updateHeaps(map['_heaps'], mapIsRef); | 
|  | } | 
|  |  | 
|  | static const int kIsolateReloadBarred = 1005; | 
|  |  | 
|  | Future<Map<String, dynamic>> reloadSources({ | 
|  | bool pause = false, | 
|  | Uri rootLibUri, | 
|  | Uri packagesUri, | 
|  | }) async { | 
|  | try { | 
|  | final Map<String, dynamic> arguments = <String, dynamic>{ | 
|  | 'pause': pause, | 
|  | }; | 
|  | if (rootLibUri != null) { | 
|  | arguments['rootLibUri'] = rootLibUri.toString(); | 
|  | } | 
|  | if (packagesUri != null) { | 
|  | arguments['packagesUri'] = packagesUri.toString(); | 
|  | } | 
|  | final Map<String, dynamic> response = await invokeRpcRaw('_reloadSources', params: arguments); | 
|  | return response; | 
|  | } on rpc.RpcException catch (e) { | 
|  | return Future<Map<String, dynamic>>.error(<String, dynamic>{ | 
|  | 'code': e.code, | 
|  | 'message': e.message, | 
|  | 'data': e.data, | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | Future<Map<String, dynamic>> getObject(Map<String, dynamic> objectRef) { | 
|  | return invokeRpcRaw('getObject', | 
|  | params: <String, dynamic>{'objectId': objectRef['id']}); | 
|  | } | 
|  |  | 
|  | Future<ProgramElement> _describeElement(Map<String, dynamic> elementRef) async { | 
|  | String name = elementRef['name']; | 
|  | Map<String, dynamic> owner = elementRef['owner']; | 
|  | while (owner != null) { | 
|  | final String ownerType = owner['type']; | 
|  | if (ownerType == 'Library' || ownerType == '@Library') | 
|  | break; | 
|  | final String ownerName = owner['name']; | 
|  | name = '$ownerName.$name'; | 
|  | owner = owner['owner']; | 
|  | } | 
|  |  | 
|  | final Map<String, dynamic> fullElement = await getObject(elementRef); | 
|  | final Map<String, dynamic> location = fullElement['location']; | 
|  | final int tokenPos = location['tokenPos']; | 
|  | final Map<String, dynamic> script = await getObject(location['script']); | 
|  |  | 
|  | // The engine's tag handler doesn't seem to create proper URIs. | 
|  | Uri uri = Uri.parse(script['uri']); | 
|  | if (uri.scheme == '') | 
|  | uri = uri.replace(scheme: 'file'); | 
|  |  | 
|  | // See https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md | 
|  | for (List<int> lineTuple in script['tokenPosTable']) { | 
|  | final int line = lineTuple[0]; | 
|  | for (int i = 1; i < lineTuple.length; i += 2) { | 
|  | if (lineTuple[i] == tokenPos) { | 
|  | final int column = lineTuple[i + 1]; | 
|  | return ProgramElement(name, uri, line, column); | 
|  | } | 
|  | } | 
|  | } | 
|  | return ProgramElement(name, uri); | 
|  | } | 
|  |  | 
|  | // Lists program elements changed in the most recent reload that have not | 
|  | // since executed. | 
|  | Future<List<ProgramElement>> getUnusedChangesInLastReload() async { | 
|  | final Map<String, dynamic> response = | 
|  | await invokeRpcRaw('_getUnusedChangesInLastReload'); | 
|  | final List<Future<ProgramElement>> unusedElements = | 
|  | <Future<ProgramElement>>[]; | 
|  | for (Map<String, dynamic> element in response['unused']) | 
|  | unusedElements.add(_describeElement(element)); | 
|  | return Future.wait<ProgramElement>(unusedElements); | 
|  | } | 
|  |  | 
|  | /// Resumes the isolate. | 
|  | Future<Map<String, dynamic>> resume() { | 
|  | return invokeRpcRaw('resume'); | 
|  | } | 
|  |  | 
|  | // Flutter extension methods. | 
|  |  | 
|  | // Invoke a flutter extension method, if the flutter extension is not | 
|  | // available, returns null. | 
|  | Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw( | 
|  | String method, { | 
|  | Map<String, dynamic> params, | 
|  | }) async { | 
|  | try { | 
|  | return await invokeRpcRaw(method, params: params); | 
|  | } on rpc.RpcException catch (e) { | 
|  | // If an application is not using the framework | 
|  | if (e.code == rpc_error_code.METHOD_NOT_FOUND) | 
|  | return null; | 
|  | rethrow; | 
|  | } | 
|  | } | 
|  |  | 
|  | Future<Map<String, dynamic>> flutterDebugDumpApp() { | 
|  | return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpApp'); | 
|  | } | 
|  |  | 
|  | Future<Map<String, dynamic>> flutterDebugDumpRenderTree() { | 
|  | return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpRenderTree'); | 
|  | } | 
|  |  | 
|  | Future<Map<String, dynamic>> flutterDebugDumpLayerTree() { | 
|  | return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpLayerTree'); | 
|  | } | 
|  |  | 
|  | Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInTraversalOrder() { | 
|  | return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInTraversalOrder'); | 
|  | } | 
|  |  | 
|  | Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInInverseHitTestOrder() { | 
|  | return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder'); | 
|  | } | 
|  |  | 
|  | Future<Map<String, dynamic>> _flutterToggle(String name) async { | 
|  | Map<String, dynamic> state = await invokeFlutterExtensionRpcRaw('ext.flutter.$name'); | 
|  | if (state != null && state.containsKey('enabled') && state['enabled'] is String) { | 
|  | state = await invokeFlutterExtensionRpcRaw( | 
|  | 'ext.flutter.$name', | 
|  | params: <String, dynamic>{'enabled': state['enabled'] == 'true' ? 'false' : 'true'}, | 
|  | ); | 
|  | } | 
|  | return state; | 
|  | } | 
|  |  | 
|  | Future<Map<String, dynamic>> flutterToggleDebugPaintSizeEnabled() => _flutterToggle('debugPaint'); | 
|  |  | 
|  | Future<Map<String, dynamic>> flutterToggleDebugCheckElevationsEnabled() => _flutterToggle('debugCheckElevationsEnabled'); | 
|  |  | 
|  | Future<Map<String, dynamic>> flutterTogglePerformanceOverlayOverride() => _flutterToggle('showPerformanceOverlay'); | 
|  |  | 
|  | Future<Map<String, dynamic>> flutterToggleWidgetInspector() => _flutterToggle('inspector.show'); | 
|  |  | 
|  | Future<Map<String, dynamic>> flutterToggleProfileWidgetBuilds() => _flutterToggle('profileWidgetBuilds'); | 
|  |  | 
|  | Future<Map<String, dynamic>> flutterDebugAllowBanner(bool show) { | 
|  | return invokeFlutterExtensionRpcRaw( | 
|  | 'ext.flutter.debugAllowBanner', | 
|  | params: <String, dynamic>{'enabled': show ? 'true' : 'false'}, | 
|  | ); | 
|  | } | 
|  |  | 
|  | Future<Map<String, dynamic>> flutterReassemble() { | 
|  | return invokeFlutterExtensionRpcRaw('ext.flutter.reassemble'); | 
|  | } | 
|  |  | 
|  | Future<bool> flutterAlreadyPaintedFirstUsefulFrame() async { | 
|  | final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw('ext.flutter.didSendFirstFrameEvent'); | 
|  | // result might be null when the service extension is not initialized | 
|  | return result != null && result['enabled'] == 'true'; | 
|  | } | 
|  |  | 
|  | Future<Map<String, dynamic>> uiWindowScheduleFrame() { | 
|  | return invokeFlutterExtensionRpcRaw('ext.ui.window.scheduleFrame'); | 
|  | } | 
|  |  | 
|  | Future<Map<String, dynamic>> flutterEvictAsset(String assetPath) { | 
|  | return invokeFlutterExtensionRpcRaw( | 
|  | 'ext.flutter.evict', | 
|  | params: <String, dynamic>{ | 
|  | 'value': assetPath, | 
|  | }, | 
|  | ); | 
|  | } | 
|  |  | 
|  | Future<List<int>> flutterDebugSaveCompilationTrace() async { | 
|  | final Map<String, dynamic> result = | 
|  | await invokeFlutterExtensionRpcRaw('ext.flutter.saveCompilationTrace'); | 
|  | if (result != null && result['value'] is List<dynamic>) | 
|  | return result['value'].cast<int>(); | 
|  | return null; | 
|  | } | 
|  |  | 
|  | // Application control extension methods. | 
|  | Future<Map<String, dynamic>> flutterExit() { | 
|  | return invokeFlutterExtensionRpcRaw('ext.flutter.exit'); | 
|  | } | 
|  |  | 
|  | Future<String> flutterPlatformOverride([ String platform ]) async { | 
|  | final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw( | 
|  | 'ext.flutter.platformOverride', | 
|  | params: platform != null ? <String, dynamic>{'value': platform} : <String, String>{}, | 
|  | ); | 
|  | if (result != null && result['value'] is String) | 
|  | return result['value']; | 
|  | return 'unknown'; | 
|  | } | 
|  |  | 
|  | @override | 
|  | String toString() => 'Isolate $id'; | 
|  | } | 
|  |  | 
|  | class ServiceMap extends ServiceObject implements Map<String, dynamic> { | 
|  | ServiceMap._empty(ServiceObjectOwner owner) : super._empty(owner); | 
|  |  | 
|  | final Map<String, dynamic> _map = <String, dynamic>{}; | 
|  |  | 
|  | @override | 
|  | void _update(Map<String, dynamic> map, bool mapIsRef) { | 
|  | _loaded = !mapIsRef; | 
|  | _upgradeCollection(map, owner); | 
|  | _map.clear(); | 
|  | _map.addAll(map); | 
|  | } | 
|  |  | 
|  | // Forward Map interface calls. | 
|  | @override | 
|  | void addAll(Map<String, dynamic> other) => _map.addAll(other); | 
|  | @override | 
|  | void clear() => _map.clear(); | 
|  | @override | 
|  | bool containsValue(dynamic v) => _map.containsValue(v); | 
|  | @override | 
|  | bool containsKey(Object k) => _map.containsKey(k); | 
|  | @override | 
|  | void forEach(void f(String key, dynamic value)) => _map.forEach(f); | 
|  | @override | 
|  | dynamic putIfAbsent(String key, dynamic ifAbsent()) => _map.putIfAbsent(key, ifAbsent); | 
|  | @override | 
|  | void remove(Object key) => _map.remove(key); | 
|  | @override | 
|  | dynamic operator [](Object k) => _map[k]; | 
|  | @override | 
|  | void operator []=(String k, dynamic v) => _map[k] = v; | 
|  | @override | 
|  | bool get isEmpty => _map.isEmpty; | 
|  | @override | 
|  | bool get isNotEmpty => _map.isNotEmpty; | 
|  | @override | 
|  | Iterable<String> get keys => _map.keys; | 
|  | @override | 
|  | Iterable<dynamic> get values => _map.values; | 
|  | @override | 
|  | int get length => _map.length; | 
|  | @override | 
|  | String toString() => _map.toString(); | 
|  | @override | 
|  | void addEntries(Iterable<MapEntry<String, dynamic>> entries) => _map.addEntries(entries); | 
|  | @override | 
|  | Map<RK, RV> cast<RK, RV>() => _map.cast<RK, RV>(); | 
|  | @override | 
|  | void removeWhere(bool test(String key, dynamic value)) => _map.removeWhere(test); | 
|  | @override | 
|  | Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> transform(String key, dynamic value)) => _map.map<K2, V2>(transform); | 
|  | @override | 
|  | Iterable<MapEntry<String, dynamic>> get entries => _map.entries; | 
|  | @override | 
|  | void updateAll(dynamic update(String key, dynamic value)) => _map.updateAll(update); | 
|  | Map<RK, RV> retype<RK, RV>() => _map.cast<RK, RV>(); | 
|  | @override | 
|  | dynamic update(String key, dynamic update(dynamic value), { dynamic ifAbsent() }) => _map.update(key, update, ifAbsent: ifAbsent); | 
|  | } | 
|  |  | 
|  | /// Peered to an Android/iOS FlutterView widget on a device. | 
|  | class FlutterView extends ServiceObject { | 
|  | FlutterView._empty(ServiceObjectOwner owner) : super._empty(owner); | 
|  |  | 
|  | Isolate _uiIsolate; | 
|  | Isolate get uiIsolate => _uiIsolate; | 
|  |  | 
|  | @override | 
|  | void _update(Map<String, dynamic> map, bool mapIsRef) { | 
|  | _loaded = !mapIsRef; | 
|  | _upgradeCollection(map, owner); | 
|  | _uiIsolate = map['isolate']; | 
|  | } | 
|  |  | 
|  | // TODO(johnmccutchan): Report errors when running failed. | 
|  | Future<void> runFromSource( | 
|  | Uri entryUri, | 
|  | Uri packagesUri, | 
|  | Uri assetsDirectoryUri, | 
|  | ) async { | 
|  | final String viewId = id; | 
|  | // When this completer completes the isolate is running. | 
|  | final Completer<void> completer = Completer<void>(); | 
|  | final StreamSubscription<ServiceEvent> subscription = | 
|  | (await owner.vm.vmService.onIsolateEvent).listen((ServiceEvent event) { | 
|  | // TODO(johnmccutchan): Listen to the debug stream and catch initial | 
|  | // launch errors. | 
|  | if (event.kind == ServiceEvent.kIsolateRunnable) { | 
|  | printTrace('Isolate is runnable.'); | 
|  | if (!completer.isCompleted) | 
|  | completer.complete(); | 
|  | } | 
|  | }); | 
|  | await owner.vm.runInView(viewId, | 
|  | entryUri, | 
|  | packagesUri, | 
|  | assetsDirectoryUri); | 
|  | await completer.future; | 
|  | await owner.vm.refreshViews(waitForViews: true); | 
|  | await subscription.cancel(); | 
|  | } | 
|  |  | 
|  | Future<void> setAssetDirectory(Uri assetsDirectory) async { | 
|  | assert(assetsDirectory != null); | 
|  | await owner.vmService.vm.invokeRpc<ServiceObject>('_flutter.setAssetBundlePath', | 
|  | params: <String, dynamic>{ | 
|  | 'isolateId': _uiIsolate.id, | 
|  | 'viewId': id, | 
|  | 'assetDirectory': assetsDirectory.toFilePath(windows: false), | 
|  | }); | 
|  | } | 
|  |  | 
|  | bool get hasIsolate => _uiIsolate != null; | 
|  |  | 
|  | Future<void> flushUIThreadTasks() async { | 
|  | await owner.vm.invokeRpcRaw('_flutter.flushUIThreadTasks', | 
|  | params: <String, dynamic>{'isolateId': _uiIsolate.id}); | 
|  | } | 
|  |  | 
|  | @override | 
|  | String toString() => id; | 
|  | } |