vmservice.dart 48.7 KB
Newer Older
1 2 3 4 5
// 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';
6
import 'dart:convert' show base64;
7
import 'dart:math' as math;
8

9
import 'package:file/file.dart';
10
import 'package:json_rpc_2/error_code.dart' as rpc_error_code;
11
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
12
import 'package:meta/meta.dart' show required;
13
import 'package:stream_channel/stream_channel.dart';
14
import 'package:web_socket_channel/io.dart';
15
import 'package:web_socket_channel/web_socket_channel.dart';
16

17
import 'base/common.dart';
18
import 'base/file_system.dart';
19
import 'base/io.dart' as io;
20
import 'globals.dart';
21
import 'vmservice_record_replay.dart';
22

23
/// A function that opens a two-way communication channel to the specified [uri].
24
typedef Future<StreamChannel<String>> _OpenChannel(Uri uri);
25

26
_OpenChannel _openChannel = _defaultOpenChannel;
27

28 29 30 31 32 33 34 35 36 37 38
/// 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
39
typedef Future<Null> ReloadSources(
40 41 42 43 44
  String isolateId, {
  bool force,
  bool pause,
});

45
typedef Future<String> CompileExpression(
46 47 48 49 50 51 52 53 54
  String isolateId,
  String expression,
  List<String> definitions,
  List<String> typeDefinitions,
  String libraryUri,
  String klass,
  bool isStatic,
);

55 56
const String _kRecordingType = 'vmservice';

57 58 59 60 61
Future<StreamChannel<String>> _defaultOpenChannel(Uri uri) async {
  const int _kMaxAttempts = 5;
  Duration delay = const Duration(milliseconds: 100);
  int attempts = 0;
  io.WebSocket socket;
62 63 64 65 66 67 68 69 70 71 72 73

  Future<void> onError(dynamic e) async {
    printTrace('Exception attempting to connect to observatory: $e');
    printTrace('This was attempt #$attempts. Will retry in $delay.');

    // Delay next attempt.
    await new Future<Null>.delayed(delay);

    // Back off exponentially.
    delay *= 2;
  }

74 75 76 77
  while (attempts < _kMaxAttempts && socket == null) {
    attempts += 1;
    try {
      socket = await io.WebSocket.connect(uri.toString());
78 79 80 81
    } on io.WebSocketException catch (e) {
      await onError(e);
    } on io.SocketException catch (e) {
      await onError(e);
82 83 84 85 86 87 88 89 90 91 92 93
    }
  }

  if (socket == null) {
    throw new ToolExit(
      'Attempted to connect to Dart observatory $_kMaxAttempts times, and '
      'all attempts failed. Giving up. The URL was $uri'
    );
  }

  return new IOWebSocketChannel(socket).cast<String>();
}
94

95
/// The default VM service request timeout.
96
const Duration kDefaultRequestTimeout = const Duration(seconds: 30);
97 98 99 100

/// Used for RPC requests that may take a long time.
const Duration kLongRequestTimeout = const Duration(minutes: 1);

101 102 103
/// Used for RPC requests that should never take a long time.
const Duration kShortRequestTimeout = const Duration(seconds: 5);

104
/// A connection to the Dart VM Service.
105
class VMService {
106 107 108 109 110 111
  VMService._(
    this._peer,
    this.httpAddress,
    this.wsAddress,
    this._requestTimeout,
    ReloadSources reloadSources,
112
    CompileExpression compileExpression,
113
  ) {
114
    _vm = new VM._empty(this);
115
    _peer.listen().catchError(_connectionError.completeError);
116

117
    _peer.registerMethod('streamNotify', (rpc.Parameters event) {
118 119
      _handleStreamNotify(event.asMap);
    });
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151

    if (reloadSources != null) {
      _peer.registerMethod('reloadSources', (rpc.Parameters params) async {
        final String isolateId = params['isolateId'].value;
        final bool force = params.asMap['force'] ?? false;
        final bool pause = params.asMap['pause'] ?? false;

        if (isolateId is! String || isolateId.isEmpty)
          throw new rpc.RpcException.invalidParams('Invalid \'isolateId\': $isolateId');
        if (force is! bool)
          throw new rpc.RpcException.invalidParams('Invalid \'force\': $force');
        if (pause is! bool)
          throw new rpc.RpcException.invalidParams('Invalid \'pause\': $pause');

        try {
          await reloadSources(isolateId, force: force, pause: pause);
          return <String, String>{'type': 'Success'};
        } on rpc.RpcException {
          rethrow;
        } catch (e, st) {
          throw new rpc.RpcException(rpc_error_code.SERVER_ERROR,
              'Error during Sources Reload: $e\n$st');
        }
      });

      // If the Flutter Engine doesn't support service registration this will
      // have no effect
      _peer.sendNotification('_registerService', <String, String>{
        'service': 'reloadSources',
        'alias': 'Flutter Tools'
      });
    }
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187

    if (compileExpression != null) {
      _peer.registerMethod('compileExpression', (rpc.Parameters params) async {
        final String isolateId = params['isolateId'].asString;
        if (isolateId is! String || isolateId.isEmpty)
          throw new rpc.RpcException.invalidParams(
              'Invalid \'isolateId\': $isolateId');
        final String expression = params['expression'].asString;
        if (expression is! String || expression.isEmpty)
          throw new rpc.RpcException.invalidParams(
              'Invalid \'expression\': $expression');
        final List<String> definitions = params['definitions'].asList;
        final List<String> typeDefinitions = params['typeDefinitions'].asList;
        final String libraryUri = params['libraryUri'].asString;
        final String klass = params['klass'] != null ? 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 new rpc.RpcException(rpc_error_code.SERVER_ERROR,
              'Error during expression compilation: $e\n$st');
        }
      });

      _peer.sendNotification('_registerService', <String, String>{
        'service': 'compileExpression',
        'alias': 'Flutter Tools'
      });
    }
188 189
  }

190 191 192 193 194 195 196 197
  /// 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) {
198
    final Directory dir = getRecordingSink(location, _kRecordingType);
199 200
    _openChannel = (Uri uri) async {
      final StreamChannel<String> delegate = await _defaultOpenChannel(uri);
201 202 203 204 205 206 207 208 209 210
      return new 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) {
211
    final Directory dir = getReplaySource(location, _kRecordingType);
212
    _openChannel = (Uri uri) async => new ReplayVMServiceChannel(dir);
213 214
  }

215 216
  /// Connect to a Dart VM Service at [httpUri].
  ///
217
  /// Requests made via the returned [VMService] time out after [requestTimeout]
218
  /// amount of time, which is [kDefaultRequestTimeout] by default.
219 220 221 222 223 224 225
  ///
  /// 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
226
  static Future<VMService> connect(
227
    Uri httpUri, {
228
    Duration requestTimeout = kDefaultRequestTimeout,
229
    ReloadSources reloadSources,
230
    CompileExpression compileExpression,
231
  }) async {
232
    final Uri wsUri = httpUri.replace(scheme: 'ws', path: fs.path.join(httpUri.path, 'ws'));
233
    final StreamChannel<String> channel = await _openChannel(wsUri);
234
    final rpc.Peer peer = new rpc.Peer.withoutJson(jsonDocument.bind(channel));
235
    final VMService service = new VMService._(peer, httpUri, wsUri, requestTimeout, reloadSources, compileExpression);
236 237 238 239
    // 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;
240
  }
241

242
  final Uri httpAddress;
243
  final Uri wsAddress;
244
  final rpc.Peer _peer;
245
  final Duration _requestTimeout;
246
  final Completer<Map<String, dynamic>> _connectionError = new Completer<Map<String, dynamic>>();
247

248 249 250
  VM _vm;
  /// The singleton [VM] object. Owns [Isolate] and [FlutterView] objects.
  VM get vm => _vm;
251

252 253
  final Map<String, StreamController<ServiceEvent>> _eventControllers =
      <String, StreamController<ServiceEvent>>{};
254

255
  final Set<String> _listeningFor = new Set<String>();
256

257 258 259
  /// Whether our connection to the VM service has been closed;
  bool get isClosed => _peer.isClosed;
  Future<Null> get done => _peer.done;
260 261

  // Events
262 263
  Future<Stream<ServiceEvent>> get onDebugEvent => onEvent('Debug');
  Future<Stream<ServiceEvent>> get onExtensionEvent => onEvent('Extension');
264
  // IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, ServiceExtensionAdded
265 266
  Future<Stream<ServiceEvent>> get onIsolateEvent => onEvent('Isolate');
  Future<Stream<ServiceEvent>> get onTimelineEvent => onEvent('Timeline');
267
  // TODO(johnmccutchan): Add FlutterView events.
268

269 270 271 272 273 274 275 276
  /// 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.
277 278
  Future<Stream<ServiceEvent>> onEvent(String streamId) async {
    await _streamListen(streamId);
279 280
    return _getEventController(streamId).stream;
  }
281

282 283 284 285 286 287 288 289 290 291
  Future<Map<String, dynamic>> _sendRequest(
    String method,
    Map<String, dynamic> params,
  ) {
    return Future.any(<Future<Map<String, dynamic>>>[
      _peer.sendRequest(method, params),
      _connectionError.future,
    ]);
  }

292 293
  StreamController<ServiceEvent> _getEventController(String eventName) {
    StreamController<ServiceEvent> controller = _eventControllers[eventName];
294
    if (controller == null) {
295
      controller = new StreamController<ServiceEvent>.broadcast();
296 297 298 299 300 301
      _eventControllers[eventName] = controller;
    }
    return controller;
  }

  void _handleStreamNotify(Map<String, dynamic> data) {
302 303 304
    final String streamId = data['streamId'];
    final Map<String, dynamic> eventData = data['event'];
    final Map<String, dynamic> eventIsolate = eventData['isolate'];
305 306 307 308

    // Log event information.
    printTrace(data.toString());

309 310 311
    ServiceEvent event;
    if (eventIsolate != null) {
      // getFromMap creates the Isolate if necessary.
312
      final Isolate isolate = vm.getFromMap(eventIsolate);
313 314 315 316 317 318 319 320 321 322 323 324 325 326
      event = new 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 = new ServiceObject._fromMap(vm, eventData);
    }
    _getEventController(streamId).add(event);
327 328
  }

329 330 331
  Future<Null> _streamListen(String streamId) async {
    if (!_listeningFor.contains(streamId)) {
      _listeningFor.add(streamId);
332
      await _sendRequest('streamListen', <String, dynamic>{ 'streamId': streamId });
333
    }
334 335
  }

336 337 338 339
  /// Reloads the VM.
  Future<VM> getVM() {
    return _vm.reload();
  }
340 341

  Future<Null> waitForViews({int attempts = 5, int attemptSeconds = 1}) async {
342 343
    if (!vm.isFlutterEngine)
      return;
344 345 346 347 348 349 350 351
    await vm.refreshViews();
    for (int i = 0; (vm.firstView == null) && (i < attempts); i++) {
      // If the VM doesn't yet have a view, wait for one to show up.
      printTrace('Waiting for Flutter view');
      await new Future<Null>.delayed(new Duration(seconds: attemptSeconds));
      await vm.refreshViews();
    }
  }
352
}
353

354 355 356 357 358 359
/// 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;
}
360

361 362 363 364
bool _isServiceMap(Map<String, dynamic> m) {
  return (m != null) && (m['type'] != null);
}
bool _hasRef(String type) => (type != null) && type.startsWith('@');
365
String _stripRef(String type) => _hasRef(type) ? type.substring(1) : type;
366 367 368 369 370 371 372 373 374 375

/// 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;
  }
376
  if (collection is Map<String, dynamic>) {
377 378 379 380 381 382 383 384
    _upgradeMap(collection, owner);
  } else if (collection is List) {
    _upgradeList(collection, owner);
  }
}

void _upgradeMap(Map<String, dynamic> map, ServiceObjectOwner owner) {
  map.forEach((String k, dynamic v) {
385
    if ((v is Map<String, dynamic>) && _isServiceMap(v)) {
386 387 388
      map[k] = owner.getFromMap(v);
    } else if (v is List) {
      _upgradeList(v, owner);
389
    } else if (v is Map<String, dynamic>) {
390 391 392 393
      _upgradeMap(v, owner);
    }
  });
}
394

395 396
void _upgradeList(List<dynamic> list, ServiceObjectOwner owner) {
  for (int i = 0; i < list.length; i++) {
397
    final dynamic v = list[i];
398
    if ((v is Map<String, dynamic>) && _isServiceMap(v)) {
399 400 401
      list[i] = owner.getFromMap(v);
    } else if (v is List) {
      _upgradeList(v, owner);
402
    } else if (v is Map<String, dynamic>) {
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
      _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))
421
      throw new VMServiceObjectLoadError('Expected a service map', map);
422

423
    final String type = _stripRef(map['type']);
424 425 426 427 428 429 430 431 432 433 434 435 436

    ServiceObject serviceObject;
    switch (type) {
      case 'Event':
        serviceObject = new ServiceEvent._empty(owner);
      break;
      case 'FlutterView':
        serviceObject = new FlutterView._empty(owner.vm);
      break;
      case 'Isolate':
        serviceObject = new Isolate._empty(owner.vm);
      break;
    }
437 438 439
    // If we don't have a model object for this service object type, as a
    // fallback return a ServiceMap object.
    serviceObject ??= new ServiceMap._empty(owner);
440
    // We have now constructed an empty service object, call update to populate it.
441
    serviceObject.updateFromMap(map);
442
    return serviceObject;
443 444
  }

445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
  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;
470

471 472 473 474 475 476 477 478 479 480 481 482
  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();
483 484
  }

485 486
  /// Fetch this object from vmService and return the response directly.
  Future<Map<String, dynamic>> _fetchDirect() {
487
    final Map<String, dynamic> params = <String, dynamic>{
488 489
      'objectId': id,
    };
490
    return _owner.isolate.invokeRpcRaw('getObject', params: params);
491 492 493 494 495
  }

  Future<ServiceObject> _inProgressReload;
  /// Reload the service object (if possible).
  Future<ServiceObject> reload() async {
496 497
    final bool hasId = (id != null) && (id != '');
    final bool isVM = this is VM;
498 499 500
    // We should always reload the VM.
    // We can't reload objects without an id.
    // We shouldn't reload an immutable and already loaded object.
501
    final bool skipLoad = !isVM && (!hasId || (immutable && loaded));
502 503 504 505 506
    if (skipLoad) {
      return this;
    }

    if (_inProgressReload == null) {
507
      final Completer<ServiceObject> completer = new Completer<ServiceObject>();
508 509 510
      _inProgressReload = completer.future;

      try {
511
        final Map<String, dynamic> response = await _fetchDirect();
512 513 514 515
        if (_stripRef(response['type']) == 'Sentinel') {
          // An object may have been collected.
          completer.complete(new ServiceObject._fromMap(owner, response));
        } else {
516
          updateFromMap(response);
517 518 519 520 521 522
          completer.complete(this);
        }
      } catch (e, st) {
        completer.completeError(e, st);
      }
      _inProgressReload = null;
523
      return await completer.future;
524
    }
525

526
    return await _inProgressReload;
527 528
  }

529
  /// Update [this] using [map] as a source. [map] can be a service reference.
530
  void updateFromMap(Map<String, dynamic> map) {
531 532 533 534 535
    // 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)) {
536
      throw new VMServiceObjectLoadError('ServiceObject types must not change',
537 538 539 540 541 542 543
                                         map);
    }
    _type = mapType;
    _vmType = map.containsKey('_vmType') ? _stripRef(map['_vmType']) : _type;

    _canCache = map['fixedId'] == true;
    if ((_id != null) && (_id != map['id']) && _canCache) {
544
      throw new VMServiceObjectLoadError('ServiceObject id changed', map);
545 546 547 548 549 550 551 552 553 554
    }
    _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);
555 556
  }

557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575
  /// Implemented by subclasses to populate their model.
  void _update(Map<String, dynamic> map, bool mapIsRef);
}

class ServiceEvent extends ServiceObject {
  /// 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';
576
  static const String kPausePostRequest       = 'PausePostRequest';
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
  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';

  ServiceEvent._empty(ServiceObjectOwner owner) : super._empty(owner);

  String _kind;
  String get kind => _kind;
  DateTime _timestamp;
  DateTime get timestmap => _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;

  @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 =
        new DateTime.fromMillisecondsSinceEpoch(map['timestamp']);
    if (map['extensionKind'] != null) {
      _extensionKind = map['extensionKind'];
      _extensionData = map['extensionData'];
614
    }
615
    _timelineEvents = map['timelineEvents'];
616 617
  }

618
  bool get isPauseEvent {
619 620 621 622 623 624 625
    return kind == kPauseStart ||
           kind == kPauseExit ||
           kind == kPauseBreakpoint ||
           kind == kPauseInterrupted ||
           kind == kPauseException ||
           kind == kPausePostRequest ||
           kind == kNone;
626
  }
627 628 629
}

/// A ServiceObjectOwner is either a [VM] or an [Isolate]. Owners can cache
630
/// and/or canonicalize service objects received over the wire.
631 632 633 634 635 636 637 638 639 640 641
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;
642

643
  /// Builds a [ServiceObject] corresponding to the [id] from [map].
644
  /// The result may come from the cache. The result will not necessarily
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663
  /// 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() async {
664
    return invokeRpcRaw('getVM');
665 666
  }

667 668 669 670 671 672 673 674 675 676 677
  @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;

    // TODO(johnmccutchan): Extract any properties we care about here.
678
    _pid = map['pid'];
679 680 681 682
    if (map['_heapAllocatedMemoryUsage'] != null) {
      _heapAllocatedMemoryUsage = map['_heapAllocatedMemoryUsage'];
    }
    _maxRSS = map['_maxRSS'];
683
    _embedder = map['_embedder'];
684 685 686

    // Remove any isolates which are now dead from the isolate cache.
    _removeDeadIsolates(map['isolates']);
687 688
  }

689 690
  final Map<String, ServiceObject> _cache = <String,ServiceObject>{};
  final Map<String,Isolate> _isolateCache = <String,Isolate>{};
691

692
  /// The list of live isolates, ordered by isolate start time.
693
  final List<Isolate> isolates = <Isolate>[];
694

695
  /// The set of live views.
696
  final Map<String, FlutterView> _viewCache = <String, FlutterView>{};
697

698 699 700 701
  /// The pid of the VM's process.
  int _pid;
  int get pid => _pid;

702 703 704 705 706 707 708 709 710 711
  /// The number of bytes allocated (e.g. by malloc) in the native heap.
  int _heapAllocatedMemoryUsage;
  int get heapAllocatedMemoryUsage {
    return _heapAllocatedMemoryUsage == null ? 0 : _heapAllocatedMemoryUsage;
  }

  /// The peak resident set size for the process.
  int _maxRSS;
  int get maxRSS => _maxRSS == null ? 0 : _maxRSS;

712 713 714 715 716 717
  // 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';

718
  int _compareIsolates(Isolate a, Isolate b) {
719 720
    final DateTime aStart = a.startTime;
    final DateTime bStart = b.startTime;
721 722 723 724 725 726 727 728 729 730 731 732 733 734
    if (aStart == null) {
      if (bStart == null) {
        return 0;
      } else {
        return 1;
      }
    }
    if (bStart == null) {
      return -1;
    }
    return aStart.compareTo(bStart);
  }

  void _buildIsolateList() {
735
    final List<Isolate> isolateList = _isolateCache.values.toList();
736 737 738 739 740 741 742
    isolateList.sort(_compareIsolates);
    isolates.clear();
    isolates.addAll(isolateList);
  }

  void _removeDeadIsolates(List<Isolate> newIsolates) {
    // Build a set of new isolates.
743
    final Set<String> newIsolateSet = new Set<String>();
744 745
    for (Isolate iso in newIsolates)
      newIsolateSet.add(iso.id);
746 747

    // Remove any old isolates which no longer exist.
748
    final List<String> toRemove = <String>[];
749 750 751 752
    _isolateCache.forEach((String id, _) {
      if (!newIsolateSet.contains(id)) {
        toRemove.add(id);
      }
753
    });
754
    toRemove.forEach(_isolateCache.remove);
755 756 757 758 759 760 761 762
    _buildIsolateList();
  }

  @override
  ServiceObject getFromMap(Map<String, dynamic> map) {
    if (map == null) {
      return null;
    }
763
    final String type = _stripRef(map['type']);
764 765
    if (type == 'VM') {
      // Update this VM object.
766
      updateFromMap(map);
767 768 769
      return this;
    }

770
    final String mapId = map['id'];
771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787

    switch (type) {
      case 'Isolate': {
        // Check cache.
        Isolate isolate = _isolateCache[mapId];
        if (isolate == null) {
          // Add new isolate to the cache.
          isolate = new 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.
788
          isolate.updateFromMap(map);
789 790 791 792 793 794 795 796 797 798 799
        }
        return isolate;
      }
      break;
      case 'FlutterView': {
        FlutterView view = _viewCache[mapId];
        if (view == null) {
          // Add new view to the cache.
          view = new ServiceObject._fromMap(this, map);
          _viewCache[mapId] = view;
        } else {
800
          view.updateFromMap(map);
801 802 803 804 805 806 807 808
        }
        return view;
      }
      break;
      default:
        throw new VMServiceObjectLoadError(
            'VM.getFromMap called for something other than an isolate', map);
    }
809 810
  }

811
  // This function does not reload the isolate if it's found in the cache.
812 813 814
  Future<Isolate> getIsolate(String isolateId) {
    if (!loaded) {
      // Trigger a VM load, then get the isolate. Ignore any errors.
815
      return load().then<Isolate>((ServiceObject serviceObject) => getIsolate(isolateId)).catchError((dynamic error) => null);
816 817 818
    }
    return new Future<Isolate>.value(_isolateCache[isolateId]);
  }
819

820
  /// Invoke the RPC and return the raw response.
821 822 823 824
  ///
  /// If `timeoutFatal` is false, then a timeout will result in a null return
  /// value. Otherwise, it results in an exception.
  Future<Map<String, dynamic>> invokeRpcRaw(String method, {
825
    Map<String, dynamic> params = const <String, dynamic>{},
826
    Duration timeout,
827
    bool timeoutFatal = true,
828
  }) async {
829 830
    printTrace('$method: $params');

831 832
    assert(params != null);
    timeout ??= _vmService._requestTimeout;
833
    try {
834
      final Map<String, dynamic> result = await _vmService
835
          ._sendRequest(method, params)
836
          .timeout(timeout);
837
      return result;
838 839 840 841 842
    } on TimeoutException {
      printTrace('Request to Dart VM Service timed out: $method($params)');
      if (timeoutFatal)
        throw new TimeoutException('Request to Dart VM Service timed out: $method($params)');
      return null;
843 844 845
    } on WebSocketChannelException catch (error) {
      throwToolExit('Error connecting to observatory: $error');
      return null;
846 847 848
    } on rpc.RpcException catch (error) {
      printError('Error ${error.code} received from application: ${error.message}');
      printTrace('${error.data}');
849
      rethrow;
850
    }
851
  }
852

853 854
  /// Invoke the RPC and return a [ServiceObject] response.
  Future<ServiceObject> invokeRpc(String method, {
855
    Map<String, dynamic> params = const <String, dynamic>{},
856 857
    Duration timeout,
  }) async {
858
    final Map<String, dynamic> response = await invokeRpcRaw(
859 860 861 862
      method,
      params: params,
      timeout: timeout,
    );
863
    final ServiceObject serviceObject = new ServiceObject._fromMap(this, response);
864
    if ((serviceObject != null) && (serviceObject._canCache)) {
865
      final String serviceObjectId = serviceObject.id;
866 867 868
      _cache.putIfAbsent(serviceObjectId, () => serviceObject);
    }
    return serviceObject;
869 870
  }

871
  /// Create a new development file system on the device.
872 873
  Future<Map<String, dynamic>> createDevFS(String fsName) {
    return invokeRpcRaw('_createDevFS', params: <String, dynamic> { 'fsName': fsName });
874 875 876 877
  }

  /// List the development file system son the device.
  Future<List<String>> listDevFS() async {
878
    return (await invokeRpcRaw('_listDevFS'))['fsNames'];
879 880 881
  }

  // Write one file into a file system.
882
  Future<Map<String, dynamic>> writeDevFSFile(String fsName, {
883 884
    @required String path,
    @required List<int> fileContents
885 886 887
  }) {
    assert(path != null);
    assert(fileContents != null);
888 889 890 891 892
    return invokeRpcRaw(
      '_writeDevFSFile',
      params: <String, dynamic>{
        'fsName': fsName,
        'path': path,
893
        'fileContents': base64.encode(fileContents),
894 895
      },
    );
896 897
  }

898
  // Read one file from a file system.
899
  Future<List<int>> readDevFSFile(String fsName, String path) async {
900
    final Map<String, dynamic> response = await invokeRpcRaw(
901 902 903 904 905 906
      '_readDevFSFile',
      params: <String, dynamic>{
        'fsName': fsName,
        'path': path,
      },
    );
907
    return base64.decode(response['fileContents']);
908 909 910
  }

  /// The complete list of a file system.
911 912
  Future<List<String>> listDevFSFiles(String fsName) async {
    return (await invokeRpcRaw('_listDevFSFiles', params: <String, dynamic>{ 'fsName': fsName }))['files'];
913 914 915
  }

  /// Delete an existing file system.
916
  Future<Map<String, dynamic>> deleteDevFS(String fsName) {
917
    return invokeRpcRaw('_deleteDevFS', params: <String, dynamic>{ 'fsName': fsName });
918 919
  }

920
  Future<ServiceMap> runInView(String viewId,
921 922 923 924
                               Uri main,
                               Uri packages,
                               Uri assetsDirectory) {
    // TODO(goderbauer): Transfer Uri (instead of file path) when remote end supports it.
925
    return invokeRpc('_flutter.runInView',
926
                    params: <String, dynamic> {
927
                      'viewId': viewId,
928 929 930
                      'mainScript': main.toFilePath(windows: false),
                      'packagesFile': packages.toFilePath(windows: false),
                      'assetDirectory': assetsDirectory.toFilePath(windows: false)
931 932
                    });
  }
933

934
  Future<Map<String, dynamic>> clearVMTimeline() {
935
    return invokeRpcRaw('_clearVMTimeline');
936 937
  }

938
  Future<Map<String, dynamic>> setVMTimelineFlags(List<String> recordedStreams) {
939
    assert(recordedStreams != null);
940 941 942 943 944 945
    return invokeRpcRaw(
      '_setVMTimelineFlags',
      params: <String, dynamic>{
        'recordedStreams': recordedStreams,
      },
    );
946 947
  }

948
  Future<Map<String, dynamic>> getVMTimeline() {
949
    return invokeRpcRaw('_getVMTimeline', timeout: kLongRequestTimeout);
950
  }
951

952
  Future<Null> refreshViews() async {
953 954
    if (!isFlutterEngine)
      return;
955
    _viewCache.clear();
956
    for (Isolate isolate in isolates.toList()) {
957 958 959 960
      await vmService.vm.invokeRpc('_flutter.listViews',
          timeout: kLongRequestTimeout,
          params: <String, dynamic> {'isolateId': isolate.id});
    }
961 962
  }

963 964 965
  Iterable<FlutterView> get views => _viewCache.values;

  FlutterView get firstView {
966
    return _viewCache.values.isEmpty ? null : _viewCache.values.first;
967
  }
968

969 970
  List<FlutterView> allViewsWithName(String isolateFilter) {
    if (_viewCache.values.isEmpty)
971
      return null;
972 973 974
    return _viewCache.values.where(
      (FlutterView v) => v.uiIsolate.name.contains(isolateFilter)
    ).toList();
975
  }
976
}
977

978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993
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 *
994
      Duration.microsecondsPerSecond /
995 996 997 998 999 1000
      math.max(_collections, 1);
    return new Duration(microseconds: mcs.ceil());
  }

  Duration get avgCollectionPeriod {
    final double mcs = _averageCollectionPeriodInMillis *
1001
                       Duration.microsecondsPerMillisecond;
1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015
    return new 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'];
  }
}

1016
/// A function, field or class along with its source location.
1017
class ProgramElement {
1018
  ProgramElement(this.qualifiedName, this.uri, [this.line, this.column]);
1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033

  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)';
  }
}

1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
/// 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;
1049 1050 1051

  /// The last pause event delivered to the isolate. If the isolate is running,
  /// this will be a resume event.
1052
  ServiceEvent pauseEvent;
1053

1054
  final Map<String, ServiceObject> _cache = <String, ServiceObject>{};
1055

1056 1057 1058 1059 1060 1061
  HeapSpace _newSpace;
  HeapSpace _oldSpace;

  HeapSpace get newSpace => _newSpace;
  HeapSpace get oldSpace => _oldSpace;

1062 1063
  @override
  ServiceObject getFromMap(Map<String, dynamic> map) {
1064
    if (map == null)
1065
      return null;
1066
    final String mapType = _stripRef(map['type']);
1067 1068 1069 1070 1071
    if (mapType == 'Isolate') {
      // There are sometimes isolate refs in ServiceEvents.
      return vm.getFromMap(map);
    }

1072
    final String mapId = map['id'];
1073 1074
    ServiceObject serviceObject = (mapId != null) ? _cache[mapId] : null;
    if (serviceObject != null) {
1075
      serviceObject.updateFromMap(map);
1076 1077 1078 1079
      return serviceObject;
    }
    // Build the object from the map directly.
    serviceObject = new ServiceObject._fromMap(this, map);
1080
    if ((serviceObject != null) && serviceObject.canCache)
1081 1082
      _cache[mapId] = serviceObject;
    return serviceObject;
1083 1084
  }

1085 1086
  @override
  Future<Map<String, dynamic>> _fetchDirect() {
1087
    return invokeRpcRaw('getIsolate');
1088 1089
  }

1090
  /// Invoke the RPC and return the raw response.
1091 1092 1093
  Future<Map<String, dynamic>> invokeRpcRaw(String method, {
    Map<String, dynamic> params,
    Duration timeout,
1094
    bool timeoutFatal = true,
1095
  }) {
1096 1097 1098 1099 1100 1101 1102 1103
    // Inject the 'isolateId' parameter.
    if (params == null) {
      params = <String, dynamic>{
        'isolateId': id
      };
    } else {
      params['isolateId'] = id;
    }
1104
    return vm.invokeRpcRaw(method, params: params, timeout: timeout, timeoutFatal: timeoutFatal);
1105 1106
  }

1107
  /// Invoke the RPC and return a ServiceObject response.
1108 1109
  Future<ServiceObject> invokeRpc(String method, Map<String, dynamic> params) async {
    return getFromMap(await invokeRpcRaw(method, params: params));
1110
  }
1111

1112
  void _updateHeaps(Map<String, dynamic> map, bool mapIsRef) {
1113
    _newSpace ??= new HeapSpace._empty(this);
1114
    _newSpace._update(map['new'], mapIsRef);
1115
    _oldSpace ??= new HeapSpace._empty(this);
1116 1117 1118
    _oldSpace._update(map['old'], mapIsRef);
  }

1119 1120
  @override
  void _update(Map<String, dynamic> map, bool mapIsRef) {
1121
    if (mapIsRef)
1122 1123 1124
      return;
    _loaded = true;

1125
    final int startTimeMillis = map['startTime'];
1126
    startTime = new DateTime.fromMillisecondsSinceEpoch(startTimeMillis);
1127

1128
    _upgradeCollection(map, this);
1129 1130

    pauseEvent = map['pauseEvent'];
1131 1132

    _updateHeaps(map['_heaps'], mapIsRef);
1133 1134
  }

1135
  static const int kIsolateReloadBarred = 1005;
1136

1137
  Future<Map<String, dynamic>> reloadSources(
1138
      { bool pause = false,
1139 1140
        Uri rootLibUri,
        Uri packagesUri}) async {
1141
    try {
1142
      final Map<String, dynamic> arguments = <String, dynamic>{
1143 1144
        'pause': pause
      };
1145 1146 1147 1148
      // TODO(goderbauer): Transfer Uri (instead of file path) when remote end supports it.
      //     Note: Despite the name, `rootLibUri` and `packagesUri` expect file paths.
      if (rootLibUri != null) {
        arguments['rootLibUri'] = rootLibUri.toFilePath(windows: false);
1149
      }
1150 1151
      if (packagesUri != null) {
        arguments['packagesUri'] = packagesUri.toFilePath(windows: false);
1152
      }
1153
      final Map<String, dynamic> response = await invokeRpcRaw('_reloadSources', params: arguments);
1154
      return response;
1155
    } on rpc.RpcException catch (e) {
1156 1157 1158 1159 1160
      return new Future<Map<String, dynamic>>.error(<String, dynamic>{
        'code': e.code,
        'message': e.message,
        'data': e.data,
      });
1161 1162
    }
  }
1163

1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176
  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'];
1177
      name = '$ownerName.$name';
1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200
      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 new ProgramElement(name, uri, line, column);
        }
      }
    }
1201
    return new ProgramElement(name, uri);
1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215
  }

  // 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(unusedElements);
  }

1216 1217 1218 1219 1220
  /// Resumes the isolate.
  Future<Map<String, dynamic>> resume() {
    return invokeRpcRaw('resume');
  }

1221
  // Flutter extension methods.
1222

1223 1224 1225
  // Invoke a flutter extension method, if the flutter extension is not
  // available, returns null.
  Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw(
1226 1227 1228
    String method, {
      Map<String, dynamic> params,
      Duration timeout,
1229
      bool timeoutFatal = true,
1230 1231
    }
  ) async {
1232
    try {
1233
      return await invokeRpcRaw(method, params: params, timeout: timeout, timeoutFatal: timeoutFatal);
1234
    } on rpc.RpcException catch (e) {
1235
      // If an application is not using the framework
1236
      if (e.code == rpc_error_code.METHOD_NOT_FOUND)
1237 1238 1239 1240 1241 1242 1243
        return null;
      rethrow;
    }
  }

  // Debug dump extension methods.

1244
  Future<Map<String, dynamic>> flutterDebugDumpApp() {
1245
    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpApp', timeout: kLongRequestTimeout);
1246
  }
1247

1248
  Future<Map<String, dynamic>> flutterDebugDumpRenderTree() {
1249
    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpRenderTree', timeout: kLongRequestTimeout);
1250
  }
1251

1252 1253 1254 1255
  Future<Map<String, dynamic>> flutterDebugDumpLayerTree() {
    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpLayerTree', timeout: kLongRequestTimeout);
  }

1256 1257
  Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInTraversalOrder() {
    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInTraversalOrder', timeout: kLongRequestTimeout);
1258 1259 1260 1261
  }

  Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInInverseHitTestOrder() {
    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder', timeout: kLongRequestTimeout);
1262 1263
  }

1264 1265
  Future<Map<String, dynamic>> _flutterToggle(String name) async {
    Map<String, dynamic> state = await invokeFlutterExtensionRpcRaw('ext.flutter.$name');
1266
    if (state != null && state.containsKey('enabled') && state['enabled'] is String) {
1267
      state = await invokeFlutterExtensionRpcRaw(
1268
        'ext.flutter.$name',
1269
        params: <String, dynamic>{ 'enabled': state['enabled'] == 'true' ? 'false' : 'true' },
1270 1271 1272 1273
        timeout: const Duration(milliseconds: 150),
        timeoutFatal: false,
      );
    }
1274 1275 1276
    return state;
  }

1277 1278 1279 1280
  Future<Map<String, dynamic>> flutterToggleDebugPaintSizeEnabled() => _flutterToggle('debugPaint');

  Future<Map<String, dynamic>> flutterTogglePerformanceOverlayOverride() => _flutterToggle('showPerformanceOverlay');

1281
  Future<Map<String, dynamic>> flutterToggleWidgetInspector() => _flutterToggle('inspector.show');
1282

1283
  Future<Null> flutterDebugAllowBanner(bool show) async {
1284 1285
    await invokeFlutterExtensionRpcRaw(
      'ext.flutter.debugAllowBanner',
1286
      params: <String, dynamic>{ 'enabled': show ? 'true' : 'false' },
1287 1288 1289
      timeout: const Duration(milliseconds: 150),
      timeoutFatal: false,
    );
1290 1291
  }

1292 1293
  // Reload related extension methods.
  Future<Map<String, dynamic>> flutterReassemble() async {
1294 1295
    return await invokeFlutterExtensionRpcRaw(
      'ext.flutter.reassemble',
1296 1297
      timeout: kShortRequestTimeout,
      timeoutFatal: true,
1298
    );
1299 1300 1301
  }

  Future<bool> flutterFrameworkPresent() async {
1302
    return await invokeFlutterExtensionRpcRaw('ext.flutter.frameworkPresent') != null;
1303 1304 1305 1306 1307 1308 1309 1310
  }

  Future<Map<String, dynamic>> uiWindowScheduleFrame() async {
    return await invokeFlutterExtensionRpcRaw('ext.ui.window.scheduleFrame');
  }

  Future<Map<String, dynamic>> flutterEvictAsset(String assetPath) async {
    return await invokeFlutterExtensionRpcRaw('ext.flutter.evict',
1311 1312 1313
      params: <String, dynamic>{
        'value': assetPath,
      }
1314
    );
1315
  }
1316

1317 1318
  // Application control extension methods.
  Future<Map<String, dynamic>> flutterExit() async {
1319 1320 1321 1322 1323
    return await invokeFlutterExtensionRpcRaw(
      'ext.flutter.exit',
      timeout: const Duration(seconds: 2),
      timeoutFatal: false,
    );
1324
  }
1325

1326
  Future<String> flutterPlatformOverride([String platform]) async {
1327
    final Map<String, String> result = await invokeFlutterExtensionRpcRaw(
1328 1329 1330 1331 1332
      'ext.flutter.platformOverride',
      params: platform != null ? <String, dynamic>{ 'value': platform } : <String, String>{},
      timeout: const Duration(seconds: 5),
      timeoutFatal: false,
    );
1333
    if (result != null && result['value'] is String)
1334 1335 1336
      return result['value'];
    return 'unknown';
  }
1337 1338 1339

  @override
  String toString() => 'Isolate $id';
1340 1341
}

1342 1343 1344
class ServiceMap extends ServiceObject implements Map<String, dynamic> {
  ServiceMap._empty(ServiceObjectOwner owner) : super._empty(owner);

1345
  final Map<String, dynamic> _map = <String, dynamic>{};
1346

1347 1348 1349 1350 1351 1352 1353
  @override
  void _update(Map<String, dynamic> map, bool mapIsRef) {
    _loaded = !mapIsRef;
    _upgradeCollection(map, owner);
    _map.clear();
    _map.addAll(map);
  }
1354

1355 1356 1357 1358 1359 1360 1361 1362
  // 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
Adam Barth's avatar
Adam Barth committed
1363
  bool containsKey(Object k) => _map.containsKey(k);
1364
  @override
1365
  void forEach(void f(String key, dynamic value)) => _map.forEach(f);
1366
  @override
1367
  dynamic putIfAbsent(String key, dynamic ifAbsent()) => _map.putIfAbsent(key, ifAbsent);
1368
  @override
Adam Barth's avatar
Adam Barth committed
1369
  void remove(Object key) => _map.remove(key);
1370
  @override
Adam Barth's avatar
Adam Barth committed
1371
  dynamic operator [](Object k) => _map[k];
1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385
  @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();
1386 1387 1388 1389 1390 1391 1392
  @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
1393
  Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> transform(String key, dynamic value)) => _map.map(transform);
1394 1395 1396 1397 1398
  @override
  Iterable<MapEntry<String, dynamic>> get entries => _map.entries;
  @override
  void updateAll(dynamic update(String key, dynamic value)) => _map.updateAll(update);
  @override
1399
  dynamic update(String key, dynamic update(dynamic value), {dynamic ifAbsent()}) => _map.update(key, update, ifAbsent: ifAbsent);
1400
}
1401

1402 1403 1404
/// Peered to a Android/iOS FlutterView widget on a device.
class FlutterView extends ServiceObject {
  FlutterView._empty(ServiceObjectOwner owner) : super._empty(owner);
1405

1406 1407
  Isolate _uiIsolate;
  Isolate get uiIsolate => _uiIsolate;
1408

1409
  @override
1410 1411 1412 1413 1414 1415 1416
  void _update(Map<String, dynamic> map, bool mapIsRef) {
    _loaded = !mapIsRef;
    _upgradeCollection(map, owner);
    _uiIsolate = map['isolate'];
  }

  // TODO(johnmccutchan): Report errors when running failed.
1417 1418 1419
  Future<Null> runFromSource(Uri entryUri,
                             Uri packagesUri,
                             Uri assetsDirectoryUri) async {
1420 1421 1422 1423
    final String viewId = id;
    // When this completer completes the isolate is running.
    final Completer<Null> completer = new Completer<Null>();
    final StreamSubscription<ServiceEvent> subscription =
1424
      (await owner.vm.vmService.onIsolateEvent).listen((ServiceEvent event) {
1425 1426 1427 1428
      // TODO(johnmccutchan): Listen to the debug stream and catch initial
      // launch errors.
      if (event.kind == ServiceEvent.kIsolateRunnable) {
        printTrace('Isolate is runnable.');
1429 1430
        if (!completer.isCompleted)
          completer.complete(null);
1431 1432 1433
      }
    });
    await owner.vm.runInView(viewId,
1434 1435 1436
                             entryUri,
                             packagesUri,
                             assetsDirectoryUri);
1437
    await completer.future;
1438
    await owner.vm.refreshViews();
1439
    await subscription.cancel();
1440
  }
1441

1442 1443 1444 1445
  Future<Null> setAssetDirectory(Uri assetsDirectory) async {
    assert(assetsDirectory != null);
    await owner.vmService.vm.invokeRpc('_flutter.setAssetBundlePath',
        params: <String, dynamic>{
1446
          'isolateId': _uiIsolate.id,
1447 1448 1449 1450 1451
          'viewId': id,
          'assetDirectory': assetsDirectory.toFilePath(windows: false)
        });
  }

1452 1453
  bool get hasIsolate => _uiIsolate != null;

1454
  Future<Null> flushUIThreadTasks() async {
1455 1456
    await owner.vm.invokeRpcRaw('_flutter.flushUIThreadTasks',
      params: <String, dynamic>{'isolateId': _uiIsolate.id});
1457 1458
  }

1459
  @override
1460
  String toString() => id;
1461
}