• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2016 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6import 'dart:math' as math;
7
8import 'package:file/file.dart';
9import 'package:json_rpc_2/error_code.dart' as rpc_error_code;
10import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
11import 'package:meta/meta.dart' show required;
12import 'package:stream_channel/stream_channel.dart';
13import 'package:web_socket_channel/io.dart';
14import 'package:web_socket_channel/web_socket_channel.dart';
15
16import 'base/common.dart';
17import 'base/context.dart';
18import 'base/file_system.dart';
19import 'base/io.dart' as io;
20import 'base/utils.dart';
21import 'convert.dart' show base64;
22import 'globals.dart';
23import 'vmservice_record_replay.dart';
24
25/// Override `WebSocketConnector` in [context] to use a different constructor
26/// for [WebSocket]s (used by tests).
27typedef WebSocketConnector = Future<io.WebSocket> Function(String url, {io.CompressionOptions compression});
28
29/// A function that opens a two-way communication channel to the specified [uri].
30typedef _OpenChannel = Future<StreamChannel<String>> Function(Uri uri, {io.CompressionOptions compression});
31
32_OpenChannel _openChannel = _defaultOpenChannel;
33
34/// A function that reacts to the invocation of the 'reloadSources' service.
35///
36/// The VM Service Protocol allows clients to register custom services that
37/// can be invoked by other clients through the service protocol itself.
38///
39/// Clients like Observatory use external 'reloadSources' services,
40/// when available, instead of the VM internal one. This allows these clients to
41/// invoke Flutter HotReload when connected to a Flutter Application started in
42/// hot mode.
43///
44/// See: https://github.com/dart-lang/sdk/issues/30023
45typedef ReloadSources = Future<void> Function(
46  String isolateId, {
47  bool force,
48  bool pause,
49});
50
51typedef Restart = Future<void> Function({ bool pause });
52
53typedef CompileExpression = Future<String> Function(
54  String isolateId,
55  String expression,
56  List<String> definitions,
57  List<String> typeDefinitions,
58  String libraryUri,
59  String klass,
60  bool isStatic,
61);
62
63const String _kRecordingType = 'vmservice';
64
65Future<StreamChannel<String>> _defaultOpenChannel(Uri uri, {io.CompressionOptions compression = io.CompressionOptions.compressionDefault}) async {
66  Duration delay = const Duration(milliseconds: 100);
67  int attempts = 0;
68  io.WebSocket socket;
69
70  Future<void> handleError(dynamic e) async {
71    printTrace('Exception attempting to connect to Observatory: $e');
72    printTrace('This was attempt #$attempts. Will retry in $delay.');
73
74    if (attempts == 10)
75      printStatus('This is taking longer than expected...');
76
77    // Delay next attempt.
78    await Future<void>.delayed(delay);
79
80    // Back off exponentially, up to 1600ms per attempt.
81    if (delay < const Duration(seconds: 1))
82      delay *= 2;
83  }
84
85  final WebSocketConnector constructor = context.get<WebSocketConnector>() ?? io.WebSocket.connect;
86  while (socket == null) {
87    attempts += 1;
88    try {
89      socket = await constructor(uri.toString(), compression: compression);
90    } on io.WebSocketException catch (e) {
91      await handleError(e);
92    } on io.SocketException catch (e) {
93      await handleError(e);
94    }
95  }
96  return IOWebSocketChannel(socket).cast<String>();
97}
98
99/// A connection to the Dart VM Service.
100// TODO(mklim): Test this, https://github.com/flutter/flutter/issues/23031
101class VMService {
102  VMService(
103    this._peer,
104    this.httpAddress,
105    this.wsAddress,
106    ReloadSources reloadSources,
107    Restart restart,
108    CompileExpression compileExpression,
109  ) {
110    _vm = VM._empty(this);
111    _peer.listen().catchError(_connectionError.completeError);
112
113    _peer.registerMethod('streamNotify', (rpc.Parameters event) {
114      _handleStreamNotify(event.asMap);
115    });
116
117    if (reloadSources != null) {
118      _peer.registerMethod('reloadSources', (rpc.Parameters params) async {
119        final String isolateId = params['isolateId'].value;
120        final bool force = params.asMap['force'] ?? false;
121        final bool pause = params.asMap['pause'] ?? false;
122
123        if (isolateId is! String || isolateId.isEmpty)
124          throw rpc.RpcException.invalidParams('Invalid \'isolateId\': $isolateId');
125        if (force is! bool)
126          throw rpc.RpcException.invalidParams('Invalid \'force\': $force');
127        if (pause is! bool)
128          throw rpc.RpcException.invalidParams('Invalid \'pause\': $pause');
129
130        try {
131          await reloadSources(isolateId, force: force, pause: pause);
132          return <String, String>{'type': 'Success'};
133        } on rpc.RpcException {
134          rethrow;
135        } catch (e, st) {
136          throw rpc.RpcException(rpc_error_code.SERVER_ERROR,
137              'Error during Sources Reload: $e\n$st');
138        }
139      });
140
141      // If the Flutter Engine doesn't support service registration this will
142      // have no effect
143      _peer.sendNotification('registerService', <String, String>{
144        'service': 'reloadSources',
145        'alias': 'Flutter Tools',
146      });
147    }
148
149    if (restart != null) {
150      _peer.registerMethod('hotRestart', (rpc.Parameters params) async {
151        final bool pause = params.asMap['pause'] ?? false;
152
153        if (pause is! bool)
154          throw rpc.RpcException.invalidParams('Invalid \'pause\': $pause');
155
156        try {
157          await restart(pause: pause);
158          return <String, String>{'type': 'Success'};
159        } on rpc.RpcException {
160          rethrow;
161        } catch (e, st) {
162          throw rpc.RpcException(rpc_error_code.SERVER_ERROR,
163              'Error during Hot Restart: $e\n$st');
164        }
165      });
166
167      // If the Flutter Engine doesn't support service registration this will
168      // have no effect
169      _peer.sendNotification('registerService', <String, String>{
170        'service': 'hotRestart',
171        'alias': 'Flutter Tools',
172      });
173    }
174
175    if (compileExpression != null) {
176      _peer.registerMethod('compileExpression', (rpc.Parameters params) async {
177        final String isolateId = params['isolateId'].asString;
178        if (isolateId is! String || isolateId.isEmpty)
179          throw rpc.RpcException.invalidParams(
180              'Invalid \'isolateId\': $isolateId');
181        final String expression = params['expression'].asString;
182        if (expression is! String || expression.isEmpty)
183          throw rpc.RpcException.invalidParams(
184              'Invalid \'expression\': $expression');
185        final List<String> definitions =
186            List<String>.from(params['definitions'].asList);
187        final List<String> typeDefinitions =
188            List<String>.from(params['typeDefinitions'].asList);
189        final String libraryUri = params['libraryUri'].asString;
190        final String klass = params['klass'].exists ? params['klass'].asString : null;
191        final bool isStatic = params['isStatic'].asBoolOr(false);
192
193        try {
194          final String kernelBytesBase64 = await compileExpression(isolateId,
195              expression, definitions, typeDefinitions, libraryUri, klass,
196              isStatic);
197          return <String, dynamic>{'type': 'Success',
198            'result': <String, dynamic> {'kernelBytes': kernelBytesBase64}};
199        } on rpc.RpcException {
200          rethrow;
201        } catch (e, st) {
202          throw rpc.RpcException(rpc_error_code.SERVER_ERROR,
203              'Error during expression compilation: $e\n$st');
204        }
205      });
206
207      _peer.sendNotification('registerService', <String, String>{
208        'service': 'compileExpression',
209        'alias': 'Flutter Tools',
210      });
211    }
212  }
213
214  /// Enables recording of VMService JSON-rpc activity to the specified base
215  /// recording [location].
216  ///
217  /// Activity will be recorded in a subdirectory of [location] named
218  /// `"vmservice"`. It is permissible for [location] to represent an existing
219  /// non-empty directory as long as there is no collision with the
220  /// `"vmservice"` subdirectory.
221  static void enableRecordingConnection(String location) {
222    final Directory dir = getRecordingSink(location, _kRecordingType);
223    _openChannel = (Uri uri, {io.CompressionOptions compression}) async {
224      final StreamChannel<String> delegate = await _defaultOpenChannel(uri);
225      return RecordingVMServiceChannel(delegate, dir);
226    };
227  }
228
229  /// Enables VMService JSON-rpc replay mode.
230  ///
231  /// [location] must represent a directory to which VMService JSON-rpc
232  /// activity has been recorded (i.e. the result of having been previously
233  /// passed to [enableRecordingConnection]), or a [ToolExit] will be thrown.
234  static void enableReplayConnection(String location) {
235    final Directory dir = getReplaySource(location, _kRecordingType);
236    _openChannel = (Uri uri, {io.CompressionOptions compression}) async => ReplayVMServiceChannel(dir);
237  }
238
239  static void _unhandledError(dynamic error, dynamic stack) {
240    logger.printTrace('Error in internal implementation of JSON RPC.\n$error\n$stack');
241    assert(false);
242  }
243
244  /// Connect to a Dart VM Service at [httpUri].
245  ///
246  /// If the [reloadSources] parameter is not null, the 'reloadSources' service
247  /// will be registered. The VM Service Protocol allows clients to register
248  /// custom services that can be invoked by other clients through the service
249  /// protocol itself.
250  ///
251  /// See: https://github.com/dart-lang/sdk/commit/df8bf384eb815cf38450cb50a0f4b62230fba217
252  static Future<VMService> connect(
253    Uri httpUri, {
254    ReloadSources reloadSources,
255    Restart restart,
256    CompileExpression compileExpression,
257    io.CompressionOptions compression = io.CompressionOptions.compressionDefault,
258  }) async {
259    final Uri wsUri = httpUri.replace(scheme: 'ws', path: fs.path.join(httpUri.path, 'ws'));
260    final StreamChannel<String> channel = await _openChannel(wsUri, compression: compression);
261    final rpc.Peer peer = rpc.Peer.withoutJson(jsonDocument.bind(channel), onUnhandledError: _unhandledError);
262    final VMService service = VMService(peer, httpUri, wsUri, reloadSources, restart, compileExpression);
263    // This call is to ensure we are able to establish a connection instead of
264    // keeping on trucking and failing farther down the process.
265    await service._sendRequest('getVersion', const <String, dynamic>{});
266    return service;
267  }
268
269  final Uri httpAddress;
270  final Uri wsAddress;
271  final rpc.Peer _peer;
272  final Completer<Map<String, dynamic>> _connectionError = Completer<Map<String, dynamic>>();
273
274  VM _vm;
275  /// The singleton [VM] object. Owns [Isolate] and [FlutterView] objects.
276  VM get vm => _vm;
277
278  final Map<String, StreamController<ServiceEvent>> _eventControllers =
279      <String, StreamController<ServiceEvent>>{};
280
281  final Set<String> _listeningFor = <String>{};
282
283  /// Whether our connection to the VM service has been closed;
284  bool get isClosed => _peer.isClosed;
285
286  Future<void> get done async {
287    await _peer.done;
288  }
289
290  // Events
291  Future<Stream<ServiceEvent>> get onDebugEvent => onEvent('Debug');
292  Future<Stream<ServiceEvent>> get onExtensionEvent => onEvent('Extension');
293  // IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, ServiceExtensionAdded
294  Future<Stream<ServiceEvent>> get onIsolateEvent => onEvent('Isolate');
295  Future<Stream<ServiceEvent>> get onTimelineEvent => onEvent('Timeline');
296  // TODO(johnmccutchan): Add FlutterView events.
297
298  /// Returns a stream of VM service events.
299  ///
300  /// This purposely returns a `Future<Stream<T>>` rather than a `Stream<T>`
301  /// because it first registers with the VM to receive events on the stream,
302  /// and only once the VM has acknowledged that the stream has started will
303  /// we return the associated stream. Any attempt to streamline this API into
304  /// returning `Stream<T>` should take that into account to avoid race
305  /// conditions.
306  Future<Stream<ServiceEvent>> onEvent(String streamId) async {
307    await _streamListen(streamId);
308    return _getEventController(streamId).stream;
309  }
310
311  Future<Map<String, dynamic>> _sendRequest(
312    String method,
313    Map<String, dynamic> params,
314  ) {
315    return Future.any<Map<String, dynamic>>(<Future<Map<String, dynamic>>>[
316        _peer.sendRequest(method, params).then<Map<String, dynamic>>(castStringKeyedMap),
317        _connectionError.future,
318    ]);
319  }
320
321  StreamController<ServiceEvent> _getEventController(String eventName) {
322    StreamController<ServiceEvent> controller = _eventControllers[eventName];
323    if (controller == null) {
324      controller = StreamController<ServiceEvent>.broadcast();
325      _eventControllers[eventName] = controller;
326    }
327    return controller;
328  }
329
330  void _handleStreamNotify(Map<String, dynamic> data) {
331    final String streamId = data['streamId'];
332    final Map<String, dynamic> eventData = data['event'];
333    final Map<String, dynamic> eventIsolate = eventData['isolate'];
334
335    // Log event information.
336    printTrace('Notification from VM: $data');
337
338    ServiceEvent event;
339    if (eventIsolate != null) {
340      // getFromMap creates the Isolate if necessary.
341      final Isolate isolate = vm.getFromMap(eventIsolate);
342      event = ServiceObject._fromMap(isolate, eventData);
343      if (event.kind == ServiceEvent.kIsolateExit) {
344        vm._isolateCache.remove(isolate.id);
345        vm._buildIsolateList();
346      } else if (event.kind == ServiceEvent.kIsolateRunnable) {
347        // Force reload once the isolate becomes runnable so that we
348        // update the root library.
349        isolate.reload();
350      }
351    } else {
352      // The event doesn't have an isolate, so it is owned by the VM.
353      event = ServiceObject._fromMap(vm, eventData);
354    }
355    _getEventController(streamId).add(event);
356  }
357
358  Future<void> _streamListen(String streamId) async {
359    if (!_listeningFor.contains(streamId)) {
360      _listeningFor.add(streamId);
361      await _sendRequest('streamListen', <String, dynamic>{'streamId': streamId});
362    }
363  }
364
365  /// Reloads the VM.
366  Future<void> getVM() async => await vm.reload();
367
368  Future<void> refreshViews({ bool waitForViews = false }) => vm.refreshViews(waitForViews: waitForViews);
369}
370
371/// An error that is thrown when constructing/updating a service object.
372class VMServiceObjectLoadError {
373  VMServiceObjectLoadError(this.message, this.map);
374  final String message;
375  final Map<String, dynamic> map;
376}
377
378bool _isServiceMap(Map<String, dynamic> m) {
379  return (m != null) && (m['type'] != null);
380}
381bool _hasRef(String type) => (type != null) && type.startsWith('@');
382String _stripRef(String type) => _hasRef(type) ? type.substring(1) : type;
383
384/// Given a raw response from the service protocol and a [ServiceObjectOwner],
385/// recursively walk the response and replace values that are service maps with
386/// actual [ServiceObject]s. During the upgrade the owner is given a chance
387/// to return a cached / canonicalized object.
388void _upgradeCollection(
389  dynamic collection,
390  ServiceObjectOwner owner,
391) {
392  if (collection is ServiceMap)
393    return;
394  if (collection is Map<String, dynamic>) {
395    _upgradeMap(collection, owner);
396  } else if (collection is List) {
397    _upgradeList(collection, owner);
398  }
399}
400
401void _upgradeMap(Map<String, dynamic> map, ServiceObjectOwner owner) {
402  map.forEach((String k, Object v) {
403    if ((v is Map<String, dynamic>) && _isServiceMap(v)) {
404      map[k] = owner.getFromMap(v);
405    } else if (v is List) {
406      _upgradeList(v, owner);
407    } else if (v is Map<String, dynamic>) {
408      _upgradeMap(v, owner);
409    }
410  });
411}
412
413void _upgradeList(List<dynamic> list, ServiceObjectOwner owner) {
414  for (int i = 0; i < list.length; i += 1) {
415    final Object v = list[i];
416    if ((v is Map<String, dynamic>) && _isServiceMap(v)) {
417      list[i] = owner.getFromMap(v);
418    } else if (v is List) {
419      _upgradeList(v, owner);
420    } else if (v is Map<String, dynamic>) {
421      _upgradeMap(v, owner);
422    }
423  }
424}
425
426/// Base class of all objects received over the service protocol.
427abstract class ServiceObject {
428  ServiceObject._empty(this._owner);
429
430  /// Factory constructor given a [ServiceObjectOwner] and a service map,
431  /// upgrade the map into a proper [ServiceObject]. This function always
432  /// returns a new instance and does not interact with caches.
433  factory ServiceObject._fromMap(
434    ServiceObjectOwner owner,
435    Map<String, dynamic> map,
436  ) {
437    if (map == null)
438      return null;
439
440    if (!_isServiceMap(map))
441      throw VMServiceObjectLoadError('Expected a service map', map);
442
443    final String type = _stripRef(map['type']);
444
445    ServiceObject serviceObject;
446    switch (type) {
447      case 'Event':
448        serviceObject = ServiceEvent._empty(owner);
449      break;
450      case 'FlutterView':
451        serviceObject = FlutterView._empty(owner.vm);
452      break;
453      case 'Isolate':
454        serviceObject = Isolate._empty(owner.vm);
455      break;
456    }
457    // If we don't have a model object for this service object type, as a
458    // fallback return a ServiceMap object.
459    serviceObject ??= ServiceMap._empty(owner);
460    // We have now constructed an empty service object, call update to populate it.
461    serviceObject.updateFromMap(map);
462    return serviceObject;
463  }
464
465  final ServiceObjectOwner _owner;
466  ServiceObjectOwner get owner => _owner;
467
468  /// The id of this object.
469  String get id => _id;
470  String _id;
471
472  /// The user-level type of this object.
473  String get type => _type;
474  String _type;
475
476  /// The vm-level type of this object. Usually the same as [type].
477  String get vmType => _vmType;
478  String _vmType;
479
480  /// Is it safe to cache this object?
481  bool _canCache = false;
482  bool get canCache => _canCache;
483
484  /// Has this object been fully loaded?
485  bool get loaded => _loaded;
486  bool _loaded = false;
487
488  /// Is this object immutable after it is [loaded]?
489  bool get immutable => false;
490
491  String get name => _name;
492  String _name;
493
494  String get vmName => _vmName;
495  String _vmName;
496
497  /// If this is not already loaded, load it. Otherwise reload.
498  Future<ServiceObject> load() async {
499    if (loaded)
500      return this;
501    return reload();
502  }
503
504  /// Fetch this object from vmService and return the response directly.
505  Future<Map<String, dynamic>> _fetchDirect() {
506    final Map<String, dynamic> params = <String, dynamic>{
507      'objectId': id,
508    };
509    return _owner.isolate.invokeRpcRaw('getObject', params: params);
510  }
511
512  Future<ServiceObject> _inProgressReload;
513  /// Reload the service object (if possible).
514  Future<ServiceObject> reload() async {
515    final bool hasId = (id != null) && (id != '');
516    final bool isVM = this is VM;
517    // We should always reload the VM.
518    // We can't reload objects without an id.
519    // We shouldn't reload an immutable and already loaded object.
520    if (!isVM && (!hasId || (immutable && loaded)))
521      return this;
522
523    if (_inProgressReload == null) {
524      final Completer<ServiceObject> completer = Completer<ServiceObject>();
525      _inProgressReload = completer.future;
526      try {
527        final Map<String, dynamic> response = await _fetchDirect();
528        if (_stripRef(response['type']) == 'Sentinel') {
529          // An object may have been collected.
530          completer.complete(ServiceObject._fromMap(owner, response));
531        } else {
532          updateFromMap(response);
533          completer.complete(this);
534        }
535      } catch (e, st) {
536        completer.completeError(e, st);
537      }
538      _inProgressReload = null;
539      return await completer.future;
540    }
541
542    return await _inProgressReload;
543  }
544
545  /// Update [this] using [map] as a source. [map] can be a service reference.
546  void updateFromMap(Map<String, dynamic> map) {
547    // Don't allow the type to change on an object update.
548    final bool mapIsRef = _hasRef(map['type']);
549    final String mapType = _stripRef(map['type']);
550
551    if ((_type != null) && (_type != mapType)) {
552      throw VMServiceObjectLoadError('ServiceObject types must not change',
553                                         map);
554    }
555    _type = mapType;
556    _vmType = map.containsKey('_vmType') ? _stripRef(map['_vmType']) : _type;
557
558    _canCache = map['fixedId'] == true;
559    if ((_id != null) && (_id != map['id']) && _canCache) {
560      throw VMServiceObjectLoadError('ServiceObject id changed', map);
561    }
562    _id = map['id'];
563
564    // Copy name properties.
565    _name = map['name'];
566    _vmName = map.containsKey('_vmName') ? map['_vmName'] : _name;
567
568    // We have now updated all common properties, let the subclasses update
569    // their specific properties.
570    _update(map, mapIsRef);
571  }
572
573  /// Implemented by subclasses to populate their model.
574  void _update(Map<String, dynamic> map, bool mapIsRef);
575}
576
577class ServiceEvent extends ServiceObject {
578  ServiceEvent._empty(ServiceObjectOwner owner) : super._empty(owner);
579
580  String _kind;
581  String get kind => _kind;
582  DateTime _timestamp;
583  DateTime get timestamp => _timestamp;
584  String _extensionKind;
585  String get extensionKind => _extensionKind;
586  Map<String, dynamic> _extensionData;
587  Map<String, dynamic> get extensionData => _extensionData;
588  List<Map<String, dynamic>> _timelineEvents;
589  List<Map<String, dynamic>> get timelineEvents => _timelineEvents;
590
591  // The possible 'kind' values.
592  static const String kVMUpdate               = 'VMUpdate';
593  static const String kIsolateStart           = 'IsolateStart';
594  static const String kIsolateRunnable        = 'IsolateRunnable';
595  static const String kIsolateExit            = 'IsolateExit';
596  static const String kIsolateUpdate          = 'IsolateUpdate';
597  static const String kIsolateReload          = 'IsolateReload';
598  static const String kIsolateSpawn           = 'IsolateSpawn';
599  static const String kServiceExtensionAdded  = 'ServiceExtensionAdded';
600  static const String kPauseStart             = 'PauseStart';
601  static const String kPauseExit              = 'PauseExit';
602  static const String kPauseBreakpoint        = 'PauseBreakpoint';
603  static const String kPauseInterrupted       = 'PauseInterrupted';
604  static const String kPauseException         = 'PauseException';
605  static const String kPausePostRequest       = 'PausePostRequest';
606  static const String kNone                   = 'None';
607  static const String kResume                 = 'Resume';
608  static const String kBreakpointAdded        = 'BreakpointAdded';
609  static const String kBreakpointResolved     = 'BreakpointResolved';
610  static const String kBreakpointRemoved      = 'BreakpointRemoved';
611  static const String kGraph                  = '_Graph';
612  static const String kGC                     = 'GC';
613  static const String kInspect                = 'Inspect';
614  static const String kDebuggerSettingsUpdate = '_DebuggerSettingsUpdate';
615  static const String kConnectionClosed       = 'ConnectionClosed';
616  static const String kLogging                = '_Logging';
617  static const String kExtension              = 'Extension';
618
619  @override
620  void _update(Map<String, dynamic> map, bool mapIsRef) {
621    _loaded = true;
622    _upgradeCollection(map, owner);
623    _kind = map['kind'];
624    assert(map['isolate'] == null || owner == map['isolate']);
625    _timestamp =
626        DateTime.fromMillisecondsSinceEpoch(map['timestamp']);
627    if (map['extensionKind'] != null) {
628      _extensionKind = map['extensionKind'];
629      _extensionData = map['extensionData'];
630    }
631    // map['timelineEvents'] is List<dynamic> which can't be assigned to
632    // List<Map<String, dynamic>> directly. Unfortunately, we previously didn't
633    // catch this exception because json_rpc_2 is hiding all these exceptions
634    // on a Stream.
635    final List<dynamic> dynamicList = map['timelineEvents'];
636    _timelineEvents = dynamicList?.cast<Map<String, dynamic>>();
637  }
638
639  bool get isPauseEvent {
640    return kind == kPauseStart ||
641           kind == kPauseExit ||
642           kind == kPauseBreakpoint ||
643           kind == kPauseInterrupted ||
644           kind == kPauseException ||
645           kind == kPausePostRequest ||
646           kind == kNone;
647  }
648}
649
650/// A ServiceObjectOwner is either a [VM] or an [Isolate]. Owners can cache
651/// and/or canonicalize service objects received over the wire.
652abstract class ServiceObjectOwner extends ServiceObject {
653  ServiceObjectOwner._empty(ServiceObjectOwner owner) : super._empty(owner);
654
655  /// Returns the owning VM.
656  VM get vm => null;
657
658  /// Returns the owning isolate (if any).
659  Isolate get isolate => null;
660
661  /// Returns the vmService connection.
662  VMService get vmService => null;
663
664  /// Builds a [ServiceObject] corresponding to the [id] from [map].
665  /// The result may come from the cache. The result will not necessarily
666  /// be [loaded].
667  ServiceObject getFromMap(Map<String, dynamic> map);
668}
669
670/// There is only one instance of the VM class. The VM class owns [Isolate]
671/// and [FlutterView] objects.
672class VM extends ServiceObjectOwner {
673  VM._empty(this._vmService) : super._empty(null);
674
675  /// Connection to the VMService.
676  final VMService _vmService;
677  @override
678  VMService get vmService => _vmService;
679
680  @override
681  VM get vm => this;
682
683  @override
684  Future<Map<String, dynamic>> _fetchDirect() => invokeRpcRaw('getVM');
685
686  @override
687  void _update(Map<String, dynamic> map, bool mapIsRef) {
688    if (mapIsRef)
689      return;
690
691    // Upgrade the collection. A side effect of this call is that any new
692    // isolates in the map are created and added to the isolate cache.
693    _upgradeCollection(map, this);
694    _loaded = true;
695
696    _pid = map['pid'];
697    if (map['_heapAllocatedMemoryUsage'] != null)
698      _heapAllocatedMemoryUsage = map['_heapAllocatedMemoryUsage'];
699    _maxRSS = map['_maxRSS'];
700    _embedder = map['_embedder'];
701
702    // Remove any isolates which are now dead from the isolate cache.
703    _removeDeadIsolates(map['isolates'].cast<Isolate>());
704  }
705
706  final Map<String, ServiceObject> _cache = <String,ServiceObject>{};
707  final Map<String,Isolate> _isolateCache = <String,Isolate>{};
708
709  /// The list of live isolates, ordered by isolate start time.
710  final List<Isolate> isolates = <Isolate>[];
711
712  /// The set of live views.
713  final Map<String, FlutterView> _viewCache = <String, FlutterView>{};
714
715  /// The pid of the VM's process.
716  int _pid;
717  int get pid => _pid;
718
719  /// The number of bytes allocated (e.g. by malloc) in the native heap.
720  int _heapAllocatedMemoryUsage;
721  int get heapAllocatedMemoryUsage => _heapAllocatedMemoryUsage ?? 0;
722
723  /// The peak resident set size for the process.
724  int _maxRSS;
725  int get maxRSS => _maxRSS ?? 0;
726
727  // The embedder's name, Flutter or dart_runner.
728  String _embedder;
729  String get embedder => _embedder;
730  bool get isFlutterEngine => embedder == 'Flutter';
731  bool get isDartRunner => embedder == 'dart_runner';
732
733  int _compareIsolates(Isolate a, Isolate b) {
734    final DateTime aStart = a.startTime;
735    final DateTime bStart = b.startTime;
736    if (aStart == null) {
737      if (bStart == null) {
738        return 0;
739      } else {
740        return 1;
741      }
742    }
743    if (bStart == null) {
744      return -1;
745    }
746    return aStart.compareTo(bStart);
747  }
748
749  void _buildIsolateList() {
750    final List<Isolate> isolateList = _isolateCache.values.toList();
751    isolateList.sort(_compareIsolates);
752    isolates.clear();
753    isolates.addAll(isolateList);
754  }
755
756  void _removeDeadIsolates(List<Isolate> newIsolates) {
757    // Build a set of new isolates.
758    final Set<String> newIsolateSet = <String>{};
759    for (Isolate iso in newIsolates)
760      newIsolateSet.add(iso.id);
761
762    // Remove any old isolates which no longer exist.
763    final List<String> toRemove = <String>[];
764    _isolateCache.forEach((String id, _) {
765      if (!newIsolateSet.contains(id)) {
766        toRemove.add(id);
767      }
768    });
769    toRemove.forEach(_isolateCache.remove);
770    _buildIsolateList();
771  }
772
773  @override
774  ServiceObject getFromMap(Map<String, dynamic> map) {
775    if (map == null) {
776      return null;
777    }
778    final String type = _stripRef(map['type']);
779    if (type == 'VM') {
780      // Update this VM object.
781      updateFromMap(map);
782      return this;
783    }
784
785    final String mapId = map['id'];
786
787    switch (type) {
788      case 'Isolate': {
789        // Check cache.
790        Isolate isolate = _isolateCache[mapId];
791        if (isolate == null) {
792          // Add new isolate to the cache.
793          isolate = ServiceObject._fromMap(this, map);
794          _isolateCache[mapId] = isolate;
795          _buildIsolateList();
796
797          // Eagerly load the isolate.
798          isolate.load().catchError((dynamic e, StackTrace stack) {
799            printTrace('Eagerly loading an isolate failed: $e\n$stack');
800          });
801        } else {
802          // Existing isolate, update data.
803          isolate.updateFromMap(map);
804        }
805        return isolate;
806      }
807      break;
808      case 'FlutterView': {
809        FlutterView view = _viewCache[mapId];
810        if (view == null) {
811          // Add new view to the cache.
812          view = ServiceObject._fromMap(this, map);
813          _viewCache[mapId] = view;
814        } else {
815          view.updateFromMap(map);
816        }
817        return view;
818      }
819      break;
820      default:
821        throw VMServiceObjectLoadError(
822            'VM.getFromMap called for something other than an isolate', map);
823    }
824  }
825
826  // This function does not reload the isolate if it's found in the cache.
827  Future<Isolate> getIsolate(String isolateId) {
828    if (!loaded) {
829      // Trigger a VM load, then get the isolate. Ignore any errors.
830      return load().then<Isolate>((ServiceObject serviceObject) => getIsolate(isolateId)).catchError((dynamic error) => null);
831    }
832    return Future<Isolate>.value(_isolateCache[isolateId]);
833  }
834
835  static String _truncate(String message, int width, String ellipsis) {
836    assert(ellipsis.length < width);
837    if (message.length <= width)
838      return message;
839    return message.substring(0, width - ellipsis.length) + ellipsis;
840  }
841
842  /// Invoke the RPC and return the raw response.
843  Future<Map<String, dynamic>> invokeRpcRaw(
844    String method, {
845    Map<String, dynamic> params = const <String, dynamic>{},
846    bool truncateLogs = true,
847  }) async {
848    printTrace('Sending to VM service: $method($params)');
849    assert(params != null);
850    try {
851      final Map<String, dynamic> result = await _vmService._sendRequest(method, params);
852      final String resultString =
853          truncateLogs ? _truncate(result.toString(), 250, '...') : result.toString();
854      printTrace('Result: $resultString');
855      return result;
856    } on WebSocketChannelException catch (error) {
857      throwToolExit('Error connecting to observatory: $error');
858      return null;
859    } on rpc.RpcException catch (error) {
860      printError('Error ${error.code} received from application: ${error.message}');
861      printTrace('${error.data}');
862      rethrow;
863    }
864  }
865
866  /// Invoke the RPC and return a [ServiceObject] response.
867  Future<T> invokeRpc<T extends ServiceObject>(
868    String method, {
869    Map<String, dynamic> params = const <String, dynamic>{},
870    bool truncateLogs = true,
871  }) async {
872    final Map<String, dynamic> response = await invokeRpcRaw(
873      method,
874      params: params,
875      truncateLogs: truncateLogs,
876    );
877    final ServiceObject serviceObject = ServiceObject._fromMap(this, response);
878    if ((serviceObject != null) && (serviceObject._canCache)) {
879      final String serviceObjectId = serviceObject.id;
880      _cache.putIfAbsent(serviceObjectId, () => serviceObject);
881    }
882    return serviceObject;
883  }
884
885  /// Create a new development file system on the device.
886  Future<Map<String, dynamic>> createDevFS(String fsName) {
887    return invokeRpcRaw('_createDevFS', params: <String, dynamic>{'fsName': fsName});
888  }
889
890  /// List the development file system son the device.
891  Future<List<String>> listDevFS() async {
892    return (await invokeRpcRaw('_listDevFS'))['fsNames'];
893  }
894
895  // Write one file into a file system.
896  Future<Map<String, dynamic>> writeDevFSFile(
897    String fsName, {
898    @required String path,
899    @required List<int> fileContents,
900  }) {
901    assert(path != null);
902    assert(fileContents != null);
903    return invokeRpcRaw(
904      '_writeDevFSFile',
905      params: <String, dynamic>{
906        'fsName': fsName,
907        'path': path,
908        'fileContents': base64.encode(fileContents),
909      },
910    );
911  }
912
913  // Read one file from a file system.
914  Future<List<int>> readDevFSFile(String fsName, String path) async {
915    final Map<String, dynamic> response = await invokeRpcRaw(
916      '_readDevFSFile',
917      params: <String, dynamic>{
918        'fsName': fsName,
919        'path': path,
920      },
921    );
922    return base64.decode(response['fileContents']);
923  }
924
925  /// The complete list of a file system.
926  Future<List<String>> listDevFSFiles(String fsName) async {
927    return (await invokeRpcRaw('_listDevFSFiles', params: <String, dynamic>{'fsName': fsName}))['files'];
928  }
929
930  /// Delete an existing file system.
931  Future<Map<String, dynamic>> deleteDevFS(String fsName) {
932    return invokeRpcRaw('_deleteDevFS', params: <String, dynamic>{'fsName': fsName});
933  }
934
935  Future<ServiceMap> runInView(
936    String viewId,
937    Uri main,
938    Uri packages,
939    Uri assetsDirectory,
940  ) {
941    return invokeRpc<ServiceMap>('_flutter.runInView',
942      params: <String, dynamic>{
943        'viewId': viewId,
944        'mainScript': main.toString(),
945        'packagesFile': packages.toString(),
946        'assetDirectory': assetsDirectory.toString(),
947    });
948  }
949
950  Future<Map<String, dynamic>> clearVMTimeline() {
951    return invokeRpcRaw('clearVMTimeline');
952  }
953
954  Future<Map<String, dynamic>> setVMTimelineFlags(List<String> recordedStreams) {
955    assert(recordedStreams != null);
956    return invokeRpcRaw(
957      'setVMTimelineFlags',
958      params: <String, dynamic>{
959        'recordedStreams': recordedStreams,
960      },
961    );
962  }
963
964  Future<Map<String, dynamic>> getVMTimeline() {
965    return invokeRpcRaw('getVMTimeline');
966  }
967
968  Future<void> refreshViews({ bool waitForViews = false }) async {
969    assert(waitForViews != null);
970    assert(loaded);
971    if (!isFlutterEngine)
972      return;
973    int failCount = 0;
974    while (true) {
975      _viewCache.clear();
976      // When the future returned by invokeRpc() below returns,
977      // the _viewCache will have been updated.
978      // This message updates all the views of every isolate.
979      await vmService.vm.invokeRpc<ServiceObject>(
980          '_flutter.listViews', truncateLogs: false);
981      if (_viewCache.values.isNotEmpty || !waitForViews)
982        return;
983      failCount += 1;
984      if (failCount == 5) // waited 200ms
985        printStatus('Flutter is taking longer than expected to report its views. Still trying...');
986      await Future<void>.delayed(const Duration(milliseconds: 50));
987      await reload();
988    }
989  }
990
991  Iterable<FlutterView> get views => _viewCache.values;
992
993  FlutterView get firstView {
994    return _viewCache.values.isEmpty ? null : _viewCache.values.first;
995  }
996
997  List<FlutterView> allViewsWithName(String isolateFilter) {
998    if (_viewCache.values.isEmpty)
999      return null;
1000    return _viewCache.values.where(
1001      (FlutterView v) => v.uiIsolate.name.contains(isolateFilter)
1002    ).toList();
1003  }
1004}
1005
1006class HeapSpace extends ServiceObject {
1007  HeapSpace._empty(ServiceObjectOwner owner) : super._empty(owner);
1008
1009  int _used = 0;
1010  int _capacity = 0;
1011  int _external = 0;
1012  int _collections = 0;
1013  double _totalCollectionTimeInSeconds = 0.0;
1014  double _averageCollectionPeriodInMillis = 0.0;
1015
1016  int get used => _used;
1017  int get capacity => _capacity;
1018  int get external => _external;
1019
1020  Duration get avgCollectionTime {
1021    final double mcs = _totalCollectionTimeInSeconds *
1022      Duration.microsecondsPerSecond /
1023      math.max(_collections, 1);
1024    return Duration(microseconds: mcs.ceil());
1025  }
1026
1027  Duration get avgCollectionPeriod {
1028    final double mcs = _averageCollectionPeriodInMillis *
1029                       Duration.microsecondsPerMillisecond;
1030    return Duration(microseconds: mcs.ceil());
1031  }
1032
1033  @override
1034  void _update(Map<String, dynamic> map, bool mapIsRef) {
1035    _used = map['used'];
1036    _capacity = map['capacity'];
1037    _external = map['external'];
1038    _collections = map['collections'];
1039    _totalCollectionTimeInSeconds = map['time'];
1040    _averageCollectionPeriodInMillis = map['avgCollectionPeriodMillis'];
1041  }
1042}
1043
1044/// A function, field or class along with its source location.
1045class ProgramElement {
1046  ProgramElement(this.qualifiedName, this.uri, [this.line, this.column]);
1047
1048  final String qualifiedName;
1049  final Uri uri;
1050  final int line;
1051  final int column;
1052
1053  @override
1054  String toString() {
1055    if (line == null)
1056      return '$qualifiedName ($uri)';
1057    else
1058      return '$qualifiedName ($uri:$line)';
1059  }
1060}
1061
1062/// An isolate running inside the VM. Instances of the Isolate class are always
1063/// canonicalized.
1064class Isolate extends ServiceObjectOwner {
1065  Isolate._empty(ServiceObjectOwner owner) : super._empty(owner);
1066
1067  @override
1068  VM get vm => owner;
1069
1070  @override
1071  VMService get vmService => vm.vmService;
1072
1073  @override
1074  Isolate get isolate => this;
1075
1076  DateTime startTime;
1077
1078  /// The last pause event delivered to the isolate. If the isolate is running,
1079  /// this will be a resume event.
1080  ServiceEvent pauseEvent;
1081
1082  final Map<String, ServiceObject> _cache = <String, ServiceObject>{};
1083
1084  HeapSpace _newSpace;
1085  HeapSpace _oldSpace;
1086
1087  HeapSpace get newSpace => _newSpace;
1088  HeapSpace get oldSpace => _oldSpace;
1089
1090  @override
1091  ServiceObject getFromMap(Map<String, dynamic> map) {
1092    if (map == null)
1093      return null;
1094    final String mapType = _stripRef(map['type']);
1095    if (mapType == 'Isolate') {
1096      // There are sometimes isolate refs in ServiceEvents.
1097      return vm.getFromMap(map);
1098    }
1099
1100    final String mapId = map['id'];
1101    ServiceObject serviceObject = (mapId != null) ? _cache[mapId] : null;
1102    if (serviceObject != null) {
1103      serviceObject.updateFromMap(map);
1104      return serviceObject;
1105    }
1106    // Build the object from the map directly.
1107    serviceObject = ServiceObject._fromMap(this, map);
1108    if ((serviceObject != null) && serviceObject.canCache)
1109      _cache[mapId] = serviceObject;
1110    return serviceObject;
1111  }
1112
1113  @override
1114  Future<Map<String, dynamic>> _fetchDirect() => invokeRpcRaw('getIsolate');
1115
1116  /// Invoke the RPC and return the raw response.
1117  Future<Map<String, dynamic>> invokeRpcRaw(
1118    String method, {
1119    Map<String, dynamic> params,
1120  }) {
1121    // Inject the 'isolateId' parameter.
1122    if (params == null) {
1123      params = <String, dynamic>{
1124        'isolateId': id,
1125      };
1126    } else {
1127      params['isolateId'] = id;
1128    }
1129    return vm.invokeRpcRaw(method, params: params);
1130  }
1131
1132  /// Invoke the RPC and return a ServiceObject response.
1133  Future<ServiceObject> invokeRpc(String method, Map<String, dynamic> params) async {
1134    return getFromMap(await invokeRpcRaw(method, params: params));
1135  }
1136
1137  void _updateHeaps(Map<String, dynamic> map, bool mapIsRef) {
1138    _newSpace ??= HeapSpace._empty(this);
1139    _newSpace._update(map['new'], mapIsRef);
1140    _oldSpace ??= HeapSpace._empty(this);
1141    _oldSpace._update(map['old'], mapIsRef);
1142  }
1143
1144  @override
1145  void _update(Map<String, dynamic> map, bool mapIsRef) {
1146    if (mapIsRef)
1147      return;
1148    _loaded = true;
1149
1150    final int startTimeMillis = map['startTime'];
1151    startTime = DateTime.fromMillisecondsSinceEpoch(startTimeMillis);
1152
1153    _upgradeCollection(map, this);
1154
1155    pauseEvent = map['pauseEvent'];
1156
1157    _updateHeaps(map['_heaps'], mapIsRef);
1158  }
1159
1160  static const int kIsolateReloadBarred = 1005;
1161
1162  Future<Map<String, dynamic>> reloadSources({
1163    bool pause = false,
1164    Uri rootLibUri,
1165    Uri packagesUri,
1166  }) async {
1167    try {
1168      final Map<String, dynamic> arguments = <String, dynamic>{
1169        'pause': pause,
1170      };
1171      if (rootLibUri != null) {
1172        arguments['rootLibUri'] = rootLibUri.toString();
1173      }
1174      if (packagesUri != null) {
1175        arguments['packagesUri'] = packagesUri.toString();
1176      }
1177      final Map<String, dynamic> response = await invokeRpcRaw('_reloadSources', params: arguments);
1178      return response;
1179    } on rpc.RpcException catch (e) {
1180      return Future<Map<String, dynamic>>.value(<String, dynamic>{
1181        'code': e.code,
1182        'message': e.message,
1183        'data': e.data,
1184      });
1185    }
1186  }
1187
1188  Future<Map<String, dynamic>> getObject(Map<String, dynamic> objectRef) {
1189    return invokeRpcRaw('getObject',
1190                        params: <String, dynamic>{'objectId': objectRef['id']});
1191  }
1192
1193  Future<ProgramElement> _describeElement(Map<String, dynamic> elementRef) async {
1194    String name = elementRef['name'];
1195    Map<String, dynamic> owner = elementRef['owner'];
1196    while (owner != null) {
1197      final String ownerType = owner['type'];
1198      if (ownerType == 'Library' || ownerType == '@Library')
1199        break;
1200      final String ownerName = owner['name'];
1201      name = '$ownerName.$name';
1202      owner = owner['owner'];
1203    }
1204
1205    final Map<String, dynamic> fullElement = await getObject(elementRef);
1206    final Map<String, dynamic> location = fullElement['location'];
1207    final int tokenPos = location['tokenPos'];
1208    final Map<String, dynamic> script = await getObject(location['script']);
1209
1210    // The engine's tag handler doesn't seem to create proper URIs.
1211    Uri uri = Uri.parse(script['uri']);
1212    if (uri.scheme == '')
1213      uri = uri.replace(scheme: 'file');
1214
1215    // See https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md
1216    for (List<int> lineTuple in script['tokenPosTable']) {
1217      final int line = lineTuple[0];
1218      for (int i = 1; i < lineTuple.length; i += 2) {
1219        if (lineTuple[i] == tokenPos) {
1220          final int column = lineTuple[i + 1];
1221          return ProgramElement(name, uri, line, column);
1222        }
1223      }
1224    }
1225    return ProgramElement(name, uri);
1226  }
1227
1228  // Lists program elements changed in the most recent reload that have not
1229  // since executed.
1230  Future<List<ProgramElement>> getUnusedChangesInLastReload() async {
1231    final Map<String, dynamic> response =
1232      await invokeRpcRaw('_getUnusedChangesInLastReload');
1233    final List<Future<ProgramElement>> unusedElements =
1234      <Future<ProgramElement>>[];
1235    for (Map<String, dynamic> element in response['unused'])
1236      unusedElements.add(_describeElement(element));
1237    return Future.wait<ProgramElement>(unusedElements);
1238  }
1239
1240  /// Resumes the isolate.
1241  Future<Map<String, dynamic>> resume() {
1242    return invokeRpcRaw('resume');
1243  }
1244
1245  // Flutter extension methods.
1246
1247  // Invoke a flutter extension method, if the flutter extension is not
1248  // available, returns null.
1249  Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw(
1250    String method, {
1251    Map<String, dynamic> params,
1252  }) async {
1253    try {
1254      return await invokeRpcRaw(method, params: params);
1255    } on rpc.RpcException catch (e) {
1256      // If an application is not using the framework
1257      if (e.code == rpc_error_code.METHOD_NOT_FOUND)
1258        return null;
1259      rethrow;
1260    }
1261  }
1262
1263  Future<Map<String, dynamic>> flutterDebugDumpApp() {
1264    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpApp');
1265  }
1266
1267  Future<Map<String, dynamic>> flutterDebugDumpRenderTree() {
1268    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpRenderTree');
1269  }
1270
1271  Future<Map<String, dynamic>> flutterDebugDumpLayerTree() {
1272    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpLayerTree');
1273  }
1274
1275  Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInTraversalOrder() {
1276    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInTraversalOrder');
1277  }
1278
1279  Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInInverseHitTestOrder() {
1280    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder');
1281  }
1282
1283  Future<Map<String, dynamic>> _flutterToggle(String name) async {
1284    Map<String, dynamic> state = await invokeFlutterExtensionRpcRaw('ext.flutter.$name');
1285    if (state != null && state.containsKey('enabled') && state['enabled'] is String) {
1286      state = await invokeFlutterExtensionRpcRaw(
1287        'ext.flutter.$name',
1288        params: <String, dynamic>{'enabled': state['enabled'] == 'true' ? 'false' : 'true'},
1289      );
1290    }
1291    return state;
1292  }
1293
1294  Future<Map<String, dynamic>> flutterToggleDebugPaintSizeEnabled() => _flutterToggle('debugPaint');
1295
1296  Future<Map<String, dynamic>> flutterToggleDebugCheckElevationsEnabled() => _flutterToggle('debugCheckElevationsEnabled');
1297
1298  Future<Map<String, dynamic>> flutterTogglePerformanceOverlayOverride() => _flutterToggle('showPerformanceOverlay');
1299
1300  Future<Map<String, dynamic>> flutterToggleWidgetInspector() => _flutterToggle('inspector.show');
1301
1302  Future<Map<String, dynamic>> flutterToggleProfileWidgetBuilds() => _flutterToggle('profileWidgetBuilds');
1303
1304  Future<Map<String, dynamic>> flutterDebugAllowBanner(bool show) {
1305    return invokeFlutterExtensionRpcRaw(
1306      'ext.flutter.debugAllowBanner',
1307      params: <String, dynamic>{'enabled': show ? 'true' : 'false'},
1308    );
1309  }
1310
1311  Future<Map<String, dynamic>> flutterReassemble() {
1312    return invokeFlutterExtensionRpcRaw('ext.flutter.reassemble');
1313  }
1314
1315  Future<bool> flutterAlreadyPaintedFirstUsefulFrame() async {
1316    final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw('ext.flutter.didSendFirstFrameRasterizedEvent');
1317    // result might be null when the service extension is not initialized
1318    return result != null && result['enabled'] == 'true';
1319  }
1320
1321  Future<Map<String, dynamic>> uiWindowScheduleFrame() {
1322    return invokeFlutterExtensionRpcRaw('ext.ui.window.scheduleFrame');
1323  }
1324
1325  Future<Map<String, dynamic>> flutterEvictAsset(String assetPath) {
1326    return invokeFlutterExtensionRpcRaw(
1327      'ext.flutter.evict',
1328      params: <String, dynamic>{
1329        'value': assetPath,
1330      },
1331    );
1332  }
1333
1334  Future<List<int>> flutterDebugSaveCompilationTrace() async {
1335    final Map<String, dynamic> result =
1336      await invokeFlutterExtensionRpcRaw('ext.flutter.saveCompilationTrace');
1337    if (result != null && result['value'] is List<dynamic>)
1338      return result['value'].cast<int>();
1339    return null;
1340  }
1341
1342  // Application control extension methods.
1343  Future<Map<String, dynamic>> flutterExit() {
1344    return invokeFlutterExtensionRpcRaw('ext.flutter.exit');
1345  }
1346
1347  Future<String> flutterPlatformOverride([ String platform ]) async {
1348    final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw(
1349      'ext.flutter.platformOverride',
1350      params: platform != null ? <String, dynamic>{'value': platform} : <String, String>{},
1351    );
1352    if (result != null && result['value'] is String)
1353      return result['value'];
1354    return 'unknown';
1355  }
1356
1357  @override
1358  String toString() => 'Isolate $id';
1359}
1360
1361class ServiceMap extends ServiceObject implements Map<String, dynamic> {
1362  ServiceMap._empty(ServiceObjectOwner owner) : super._empty(owner);
1363
1364  final Map<String, dynamic> _map = <String, dynamic>{};
1365
1366  @override
1367  void _update(Map<String, dynamic> map, bool mapIsRef) {
1368    _loaded = !mapIsRef;
1369    _upgradeCollection(map, owner);
1370    _map.clear();
1371    _map.addAll(map);
1372  }
1373
1374  // Forward Map interface calls.
1375  @override
1376  void addAll(Map<String, dynamic> other) => _map.addAll(other);
1377  @override
1378  void clear() => _map.clear();
1379  @override
1380  bool containsValue(dynamic v) => _map.containsValue(v);
1381  @override
1382  bool containsKey(Object k) => _map.containsKey(k);
1383  @override
1384  void forEach(void f(String key, dynamic value)) => _map.forEach(f);
1385  @override
1386  dynamic putIfAbsent(String key, dynamic ifAbsent()) => _map.putIfAbsent(key, ifAbsent);
1387  @override
1388  void remove(Object key) => _map.remove(key);
1389  @override
1390  dynamic operator [](Object k) => _map[k];
1391  @override
1392  void operator []=(String k, dynamic v) => _map[k] = v;
1393  @override
1394  bool get isEmpty => _map.isEmpty;
1395  @override
1396  bool get isNotEmpty => _map.isNotEmpty;
1397  @override
1398  Iterable<String> get keys => _map.keys;
1399  @override
1400  Iterable<dynamic> get values => _map.values;
1401  @override
1402  int get length => _map.length;
1403  @override
1404  String toString() => _map.toString();
1405  @override
1406  void addEntries(Iterable<MapEntry<String, dynamic>> entries) => _map.addEntries(entries);
1407  @override
1408  Map<RK, RV> cast<RK, RV>() => _map.cast<RK, RV>();
1409  @override
1410  void removeWhere(bool test(String key, dynamic value)) => _map.removeWhere(test);
1411  @override
1412  Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> transform(String key, dynamic value)) => _map.map<K2, V2>(transform);
1413  @override
1414  Iterable<MapEntry<String, dynamic>> get entries => _map.entries;
1415  @override
1416  void updateAll(dynamic update(String key, dynamic value)) => _map.updateAll(update);
1417  Map<RK, RV> retype<RK, RV>() => _map.cast<RK, RV>();
1418  @override
1419  dynamic update(String key, dynamic update(dynamic value), { dynamic ifAbsent() }) => _map.update(key, update, ifAbsent: ifAbsent);
1420}
1421
1422/// Peered to an Android/iOS FlutterView widget on a device.
1423class FlutterView extends ServiceObject {
1424  FlutterView._empty(ServiceObjectOwner owner) : super._empty(owner);
1425
1426  Isolate _uiIsolate;
1427  Isolate get uiIsolate => _uiIsolate;
1428
1429  @override
1430  void _update(Map<String, dynamic> map, bool mapIsRef) {
1431    _loaded = !mapIsRef;
1432    _upgradeCollection(map, owner);
1433    _uiIsolate = map['isolate'];
1434  }
1435
1436  // TODO(johnmccutchan): Report errors when running failed.
1437  Future<void> runFromSource(
1438    Uri entryUri,
1439    Uri packagesUri,
1440    Uri assetsDirectoryUri,
1441  ) async {
1442    final String viewId = id;
1443    // When this completer completes the isolate is running.
1444    final Completer<void> completer = Completer<void>();
1445    final StreamSubscription<ServiceEvent> subscription =
1446      (await owner.vm.vmService.onIsolateEvent).listen((ServiceEvent event) {
1447        // TODO(johnmccutchan): Listen to the debug stream and catch initial
1448        // launch errors.
1449        if (event.kind == ServiceEvent.kIsolateRunnable) {
1450          printTrace('Isolate is runnable.');
1451          if (!completer.isCompleted)
1452            completer.complete();
1453        }
1454      });
1455    await owner.vm.runInView(viewId,
1456                             entryUri,
1457                             packagesUri,
1458                             assetsDirectoryUri);
1459    await completer.future;
1460    await owner.vm.refreshViews(waitForViews: true);
1461    await subscription.cancel();
1462  }
1463
1464  Future<void> setAssetDirectory(Uri assetsDirectory) async {
1465    assert(assetsDirectory != null);
1466    await owner.vmService.vm.invokeRpc<ServiceObject>('_flutter.setAssetBundlePath',
1467        params: <String, dynamic>{
1468          'isolateId': _uiIsolate.id,
1469          'viewId': id,
1470          'assetDirectory': assetsDirectory.toFilePath(windows: false),
1471        });
1472  }
1473
1474  Future<void> setSemanticsEnabled(bool enabled) async {
1475    assert(enabled != null);
1476    await owner.vmService.vm.invokeRpc<ServiceObject>('_flutter.setSemanticsEnabled',
1477        params: <String, dynamic>{
1478          'isolateId': _uiIsolate.id,
1479          'viewId': id,
1480          'enabled': enabled,
1481        });
1482  }
1483
1484  bool get hasIsolate => _uiIsolate != null;
1485
1486  Future<void> flushUIThreadTasks() async {
1487    await owner.vm.invokeRpcRaw('_flutter.flushUIThreadTasks',
1488      params: <String, dynamic>{'isolateId': _uiIsolate.id});
1489  }
1490
1491  @override
1492  String toString() => id;
1493}
1494