vmservice.dart 31.3 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

8
import 'package:json_rpc_2/error_code.dart' as rpc_error_code;
9
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
10
import 'package:path/path.dart' as path;
11
import 'package:stream_channel/stream_channel.dart';
12 13
import 'package:web_socket_channel/io.dart';

14
import 'base/io.dart';
15 16
import 'globals.dart';

17 18 19 20 21 22
/// The default VM service request timeout.
const Duration kDefaultRequestTimeout = const Duration(seconds: 10);

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

23
/// A connection to the Dart VM Service.
24
class VMService {
25
  VMService._(this.peer, this.httpAddress, this.wsAddress, this._requestTimeout) {
26 27
    _vm = new VM._empty(this);

28 29 30 31 32
    peer.registerMethod('streamNotify', (rpc.Parameters event) {
      _handleStreamNotify(event.asMap);
    });
  }

33 34 35 36
  /// Connect to a Dart VM Service at [httpUri].
  ///
  /// Requests made via the returns [VMService] time out after [requestTimeout]
  /// amount of time, which is [kDefaultRequestTimeout] by default.
37
  static Future<VMService> connect(Uri httpUri, { Duration requestTimeout: kDefaultRequestTimeout }) async {
38
    Uri wsUri = httpUri.replace(scheme: 'ws', path: path.join(httpUri.path, 'ws'));
39 40
    WebSocket ws;
    try {
41
      ws = await WebSocket.connect(wsUri.toString());
42
    } catch (e) {
43
      return new Future<VMService>.error('Failed to connect to $wsUri\n  $e');
44
    }
45
    rpc.Peer peer = new rpc.Peer.withoutJson(jsonDocument.bind(new IOWebSocketChannel(ws).cast()));
46
    peer.listen();
47
    return new VMService._(peer, httpUri, wsUri, requestTimeout);
48
  }
49

50
  final Uri httpAddress;
51
  final Uri wsAddress;
52
  final rpc.Peer peer;
53
  final Duration _requestTimeout;
54

55 56 57
  VM _vm;
  /// The singleton [VM] object. Owns [Isolate] and [FlutterView] objects.
  VM get vm => _vm;
58

59 60
  final Map<String, StreamController<ServiceEvent>> _eventControllers =
      <String, StreamController<ServiceEvent>>{};
61

62 63
  Set<String> _listeningFor = new Set<String>();

64 65 66 67
  bool get isClosed => peer.isClosed;
  Future<Null> get done => peer.done;

  // Events
68 69
  Stream<ServiceEvent> get onDebugEvent => onEvent('Debug');
  Stream<ServiceEvent> get onExtensionEvent => onEvent('Extension');
70
  // IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, ServiceExtensionAdded
71 72 73
  Stream<ServiceEvent> get onIsolateEvent => onEvent('Isolate');
  Stream<ServiceEvent> get onTimelineEvent => onEvent('Timeline');
  // TODO(johnmccutchan): Add FlutterView events.
74 75

  // Listen for a specific event name.
76 77
  Stream<ServiceEvent> onEvent(String streamId) {
    _streamListen(streamId);
78 79
    return _getEventController(streamId).stream;
  }
80

81 82
  StreamController<ServiceEvent> _getEventController(String eventName) {
    StreamController<ServiceEvent> controller = _eventControllers[eventName];
83
    if (controller == null) {
84
      controller = new StreamController<ServiceEvent>.broadcast();
85 86 87 88 89 90
      _eventControllers[eventName] = controller;
    }
    return controller;
  }

  void _handleStreamNotify(Map<String, dynamic> data) {
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
    final String streamId = data['streamId'];
    final Map<String, dynamic> eventData = data['event'];
    final Map<String, dynamic> eventIsolate = eventData['isolate'];
    ServiceEvent event;
    if (eventIsolate != null) {
      // getFromMap creates the Isolate if necessary.
      Isolate isolate = vm.getFromMap(eventIsolate);
      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);
112 113
  }

114 115 116 117 118 119
  Future<Null> _streamListen(String streamId) async {
    if (!_listeningFor.contains(streamId)) {
      _listeningFor.add(streamId);
      await peer.sendRequest('streamListen',
                             <String, dynamic>{ 'streamId': streamId });
    }
120 121
  }

122 123 124 125 126
  /// Reloads the VM.
  Future<VM> getVM() {
    return _vm.reload();
  }
}
127

128 129 130 131 132 133
/// 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;
}
134

135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
bool _isServiceMap(Map<String, dynamic> m) {
  return (m != null) && (m['type'] != null);
}
bool _hasRef(String type) => (type != null) && type.startsWith('@');
String _stripRef(String type) => (_hasRef(type) ? type.substring(1) : type);

/// Given a raw response from the service protocol and a [ServiceObjectOwner],
/// recursively walk the response and replace values that are service maps with
/// actual [ServiceObject]s. During the upgrade the owner is given a chance
/// to return a cached / canonicalized object.
void _upgradeCollection(dynamic collection,
                        ServiceObjectOwner owner) {
  if (collection is ServiceMap) {
    return;
  }
150
  if (collection is Map<String, dynamic>) {
151 152 153 154 155 156 157 158
    _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) {
159
    if ((v is Map<String, dynamic>) && _isServiceMap(v)) {
160 161 162
      map[k] = owner.getFromMap(v);
    } else if (v is List) {
      _upgradeList(v, owner);
163
    } else if (v is Map<String, dynamic>) {
164 165 166 167
      _upgradeMap(v, owner);
    }
  });
}
168

169 170 171
void _upgradeList(List<dynamic> list, ServiceObjectOwner owner) {
  for (int i = 0; i < list.length; i++) {
    dynamic v = list[i];
172
    if ((v is Map<String, dynamic>) && _isServiceMap(v)) {
173 174 175
      list[i] = owner.getFromMap(v);
    } else if (v is List) {
      _upgradeList(v, owner);
176
    } else if (v is Map<String, dynamic>) {
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
      _upgradeMap(v, owner);
    }
  }
}

/// Base class of all objects received over the service protocol.
abstract class ServiceObject {
  ServiceObject._empty(this._owner);

  /// Factory constructor given a [ServiceObjectOwner] and a service map,
  /// upgrade the map into a proper [ServiceObject]. This function always
  /// returns a new instance and does not interact with caches.
  factory ServiceObject._fromMap(ServiceObjectOwner owner,
                                 Map<String, dynamic> map) {
    if (map == null)
      return null;

    if (!_isServiceMap(map))
      throw new VMServiceObjectLoadError("Expected a service map", map);

    String type = _stripRef(map['type']);

    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;
    }
    if (serviceObject == null) {
      // 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);
    }
    // We have now constructed an emtpy service object, call update to
    // populate it.
    serviceObject.update(map);
    return serviceObject;
220 221
  }

222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
  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;
247

248 249 250 251 252 253 254 255 256 257 258 259
  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();
260 261
  }

262 263 264 265 266
  /// Fetch this object from vmService and return the response directly.
  Future<Map<String, dynamic>> _fetchDirect() {
    Map<String, dynamic> params = <String, dynamic>{
      'objectId': id,
    };
267
    return _owner.isolate.invokeRpcRaw('getObject', params: params);
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
  }

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

    if (_inProgressReload == null) {
      Completer<ServiceObject> completer = new Completer<ServiceObject>();
      _inProgressReload = completer.future;

      try {
        Map<String, dynamic> response = await _fetchDirect();
        if (_stripRef(response['type']) == 'Sentinel') {
          // An object may have been collected.
          completer.complete(new ServiceObject._fromMap(owner, response));
        } else {
          update(response);
          completer.complete(this);
        }
      } catch (e, st) {
        completer.completeError(e, st);
      }
      _inProgressReload = null;
300
    }
301 302

    return _inProgressReload;
303 304
  }

305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
  /// Update [this] using [map] as a source. [map] can be a service reference.
  void update(Map<String, dynamic> map) {
    // Don't allow the type to change on an object update.
    final bool mapIsRef = _hasRef(map['type']);
    final String mapType = _stripRef(map['type']);

    if ((_type != null) && (_type != mapType)) {
      throw new VMServiceObjectLoadError("ServiceObject types must not change",
                                         map);
    }
    _type = mapType;
    _vmType = map.containsKey('_vmType') ? _stripRef(map['_vmType']) : _type;

    _canCache = map['fixedId'] == true;
    if ((_id != null) && (_id != map['id']) && _canCache) {
      throw new VMServiceObjectLoadError("ServiceObject id changed", map);
    }
    _id = map['id'];

    // Copy name properties.
    _name = map['name'];
    _vmName = map.containsKey('_vmName') ? map['_vmName'] : _name;

    // We have now updated all common properties, let the subclasses update
    // their specific properties.
    _update(map, mapIsRef);
331 332
  }

333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
  /// 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';
352
  static const String kPausePostRequest       = 'PausePostRequest';
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
  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'];
390
    }
391
    _timelineEvents = map['timelineEvents'];
392 393
  }

394 395 396 397 398 399
  bool get isPauseEvent {
    return (kind == kPauseStart ||
            kind == kPauseExit ||
            kind == kPauseBreakpoint ||
            kind == kPauseInterrupted ||
            kind == kPauseException ||
400
            kind == kPausePostRequest ||
401
            kind == kNone);
402
  }
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
}

/// A ServiceObjectOwner is either a [VM] or an [Isolate]. Owners can cache
/// and/or canonicalize service objets received over the wire.
abstract class ServiceObjectOwner extends ServiceObject {
  ServiceObjectOwner._empty(ServiceObjectOwner owner) : super._empty(owner);

  /// Returns the owning VM.
  VM get vm => null;

  /// Returns the owning isolate (if any).
  Isolate get isolate => null;

  /// Returns the vmService connection.
  VMService get vmService => null;
418

419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
  /// Builds a [ServiceObject] corresponding to the [id] from [map].
  /// The result may come from the cache.  The result will not necessarily
  /// be [loaded].
  ServiceObject getFromMap(Map<String, dynamic> map);
}

/// There is only one instance of the VM class. The VM class owns [Isolate]
/// and [FlutterView] objects.
class VM extends ServiceObjectOwner {
  VM._empty(this._vmService) : super._empty(null);

  /// Connection to the VMService.
  final VMService _vmService;
  @override
  VMService get vmService => _vmService;

  @override
  VM get vm => this;

  @override
  Future<Map<String, dynamic>> _fetchDirect() async {
440
    return invokeRpcRaw('getVM');
441 442
  }

443 444 445 446 447 448 449 450 451 452 453 454 455 456
  @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.

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

459 460
  final Map<String, ServiceObject> _cache = new Map<String,ServiceObject>();
  final Map<String,Isolate> _isolateCache = new Map<String,Isolate>();
461

462 463
  /// The list of live isolates, ordered by isolate start time.
  final List<Isolate> isolates = new List<Isolate>();
464

465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
  /// The set of live views.
  final Map<String, FlutterView> _viewCache = new Map<String, FlutterView>();

  int _compareIsolates(Isolate a, Isolate b) {
    DateTime aStart = a.startTime;
    DateTime bStart = b.startTime;
    if (aStart == null) {
      if (bStart == null) {
        return 0;
      } else {
        return 1;
      }
    }
    if (bStart == null) {
      return -1;
    }
    return aStart.compareTo(bStart);
  }

  void _buildIsolateList() {
    List<Isolate> isolateList = _isolateCache.values.toList();
    isolateList.sort(_compareIsolates);
    isolates.clear();
    isolates.addAll(isolateList);
  }

  void _removeDeadIsolates(List<Isolate> newIsolates) {
    // Build a set of new isolates.
    Set<String> newIsolateSet = new Set<String>();
    newIsolates.forEach((Isolate iso) => newIsolateSet.add(iso.id));

    // Remove any old isolates which no longer exist.
    List<String> toRemove = <String>[];
    _isolateCache.forEach((String id, _) {
      if (!newIsolateSet.contains(id)) {
        toRemove.add(id);
      }
502
    });
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
    toRemove.forEach((String id) => _isolateCache.remove(id));
    _buildIsolateList();
  }

  @override
  ServiceObject getFromMap(Map<String, dynamic> map) {
    if (map == null) {
      return null;
    }
    String type = _stripRef(map['type']);
    if (type == 'VM') {
      // Update this VM object.
      update(map);
      return this;
    }

    String mapId = map['id'];

    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.
          isolate.update(map);
        }
        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 {
          view.update(map);
        }
        return view;
      }
      break;
      default:
        throw new VMServiceObjectLoadError(
            'VM.getFromMap called for something other than an isolate', map);
    }
558 559
  }

560
  // This function does not reload the isolate if it's found in the cache.
561 562 563
  Future<Isolate> getIsolate(String isolateId) {
    if (!loaded) {
      // Trigger a VM load, then get the isolate. Ignore any errors.
564
      return load().then<Isolate>((ServiceObject serviceObject) => getIsolate(isolateId)).catchError((dynamic error) => null);
565 566 567
    }
    return new Future<Isolate>.value(_isolateCache[isolateId]);
  }
568

569
  /// Invoke the RPC and return the raw response.
570 571 572 573 574 575 576 577 578 579
  ///
  /// 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, {
    Map<String, dynamic> params: const <String, dynamic>{},
    Duration timeout,
    bool timeoutFatal: true,
  }) async {
    assert(params != null);
    timeout ??= _vmService._requestTimeout;
580 581 582
    try {
      Map<String, dynamic> result = await _vmService.peer
          .sendRequest(method, params)
583
          .timeout(timeout);
584
      return result;
585 586 587 588 589
    } 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;
590
    }
591
  }
592

593 594 595 596 597 598 599 600 601 602
  /// Invoke the RPC and return a [ServiceObject] response.
  Future<ServiceObject> invokeRpc(String method, {
    Map<String, dynamic> params: const <String, dynamic>{},
    Duration timeout,
  }) async {
    Map<String, dynamic> response = await invokeRpcRaw(
      method,
      params: params,
      timeout: timeout,
    );
603 604 605 606 607 608
    ServiceObject serviceObject = new ServiceObject._fromMap(this, response);
    if ((serviceObject != null) && (serviceObject._canCache)) {
      String serviceObjectId = serviceObject.id;
      _cache.putIfAbsent(serviceObjectId, () => serviceObject);
    }
    return serviceObject;
609 610
  }

611
  /// Create a new development file system on the device.
612 613
  Future<Map<String, dynamic>> createDevFS(String fsName) {
    return invokeRpcRaw('_createDevFS', params: <String, dynamic> { 'fsName': fsName });
614 615 616 617
  }

  /// List the development file system son the device.
  Future<List<String>> listDevFS() async {
618
    return (await invokeRpcRaw('_listDevFS'))['fsNames'];
619 620 621
  }

  // Write one file into a file system.
622
  Future<Map<String, dynamic>> writeDevFSFile(String fsName, {
623 624 625 626 627
    String path,
    List<int> fileContents
  }) {
    assert(path != null);
    assert(fileContents != null);
628 629 630 631 632 633 634 635
    return invokeRpcRaw(
      '_writeDevFSFile',
      params: <String, dynamic>{
        'fsName': fsName,
        'path': path,
        'fileContents': BASE64.encode(fileContents),
      },
    );
636 637
  }

638
  // Read one file from a file system.
639 640 641 642 643 644 645 646 647
  Future<List<int>> readDevFSFile(String fsName, String path) async {
    Map<String, dynamic> response = await invokeRpcRaw(
      '_readDevFSFile',
      params: <String, dynamic>{
        'fsName': fsName,
        'path': path,
      },
    );
    return BASE64.decode(response['fileContents']);
648 649 650
  }

  /// The complete list of a file system.
651 652
  Future<List<String>> listDevFSFiles(String fsName) async {
    return (await invokeRpcRaw('_listDevFSFiles', params: <String, dynamic>{ 'fsName': fsName }))['files'];
653 654 655
  }

  /// Delete an existing file system.
656
  Future<Map<String, dynamic>> deleteDevFS(String fsName) {
657
    return invokeRpcRaw('_deleteDevFS', params: <String, dynamic>{ 'fsName': fsName });
658 659
  }

660 661 662 663 664
  Future<ServiceMap> runInView(String viewId,
                               String main,
                               String packages,
                               String assetsDirectory) {
    return invokeRpc('_flutter.runInView',
665
                    params: <String, dynamic> {
666 667 668 669 670 671
                      'viewId': viewId,
                      'mainScript': main,
                      'packagesFile': packages,
                      'assetDirectory': assetsDirectory
                    });
  }
672

673
  Future<Map<String, dynamic>> clearVMTimeline() {
674
    return invokeRpcRaw('_clearVMTimeline');
675 676
  }

677
  Future<Map<String, dynamic>> setVMTimelineFlags(List<String> recordedStreams) {
678
    assert(recordedStreams != null);
679 680 681 682 683 684
    return invokeRpcRaw(
      '_setVMTimelineFlags',
      params: <String, dynamic>{
        'recordedStreams': recordedStreams,
      },
    );
685 686
  }

687
  Future<Map<String, dynamic>> getVMTimeline() {
688
    return invokeRpcRaw('_getVMTimeline', timeout: kLongRequestTimeout);
689
  }
690

691
  Future<Null> refreshViews() async {
692
    await vmService.vm.invokeRpc('_flutter.listViews', timeout: kLongRequestTimeout);
693 694
  }

695
  FlutterView get mainView {
696
    return _viewCache.values.isEmpty ? null : _viewCache.values.first;
697
  }
698
}
699

700 701 702 703 704 705 706 707 708 709 710 711 712 713 714
/// 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;
715
  ServiceEvent pauseEvent;
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741

  final Map<String, ServiceObject> _cache = new Map<String, ServiceObject>();

  @override
  ServiceObject getFromMap(Map<String, dynamic> map) {
    if (map == null) {
      return null;
    }
    String mapType = _stripRef(map['type']);
    if (mapType == 'Isolate') {
      // There are sometimes isolate refs in ServiceEvents.
      return vm.getFromMap(map);
    }

    String mapId = map['id'];
    ServiceObject serviceObject = (mapId != null) ? _cache[mapId] : null;
    if (serviceObject != null) {
      serviceObject.update(map);
      return serviceObject;
    }
    // Build the object from the map directly.
    serviceObject = new ServiceObject._fromMap(this, map);
    if ((serviceObject != null) && serviceObject.canCache) {
      _cache[mapId] = serviceObject;
    }
    return serviceObject;
742 743
  }

744 745
  @override
  Future<Map<String, dynamic>> _fetchDirect() {
746
    return invokeRpcRaw('getIsolate');
747 748
  }

749
  /// Invoke the RPC and return the raw response.
750 751 752 753 754
  Future<Map<String, dynamic>> invokeRpcRaw(String method, {
    Map<String, dynamic> params,
    Duration timeout,
    bool timeoutFatal: true,
  }) {
755 756 757 758 759 760 761 762
    // Inject the 'isolateId' parameter.
    if (params == null) {
      params = <String, dynamic>{
        'isolateId': id
      };
    } else {
      params['isolateId'] = id;
    }
763
    return vm.invokeRpcRaw(method, params: params, timeout: timeout, timeoutFatal: timeoutFatal);
764 765
  }

766
  /// Invoke the RPC and return a ServiceObject response.
767 768
  Future<ServiceObject> invokeRpc(String method, Map<String, dynamic> params) async {
    return getFromMap(await invokeRpcRaw(method, params: params));
769
  }
770

771 772
  @override
  void _update(Map<String, dynamic> map, bool mapIsRef) {
773
    if (mapIsRef)
774 775 776 777 778
      return;
    _loaded = true;

    int startTimeMillis = map['startTime'];
    startTime = new DateTime.fromMillisecondsSinceEpoch(startTimeMillis);
779

780
    _upgradeCollection(map, this);
781 782

    pauseEvent = map['pauseEvent'];
783 784
  }

785 786
  static final int kIsolateReloadBarred = 1005;

787 788 789 790
  Future<Map<String, dynamic>> reloadSources(
      { bool pause: false,
        String rootLibPath,
        String packagesPath}) async {
791
    try {
792 793 794 795 796 797 798 799 800
      Map<String, dynamic> arguments = <String, dynamic>{
        'pause': pause
      };
      if (rootLibPath != null) {
        arguments['rootLibUri'] = rootLibPath;
      }
      if (packagesPath != null) {
        arguments['packagesUri'] = packagesPath;
      }
801
      Map<String, dynamic> response = await invokeRpcRaw('_reloadSources', params: arguments);
802
      return response;
803 804 805 806 807 808
    } on rpc.RpcException catch(e) {
      return new Future<Map<String, dynamic>>.error(<String, dynamic>{
        'code': e.code,
        'message': e.message,
        'data': e.data,
      });
809 810
    }
  }
811

812
  // Flutter extension methods.
813

814 815 816
  // Invoke a flutter extension method, if the flutter extension is not
  // available, returns null.
  Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw(
817 818 819 820 821 822
    String method, {
      Map<String, dynamic> params,
      Duration timeout,
      bool timeoutFatal: true,
    }
  ) async {
823
    try {
824
      return await invokeRpcRaw(method, params: params, timeout: timeout, timeoutFatal: timeoutFatal);
825
    } on rpc.RpcException catch (e) {
826
      // If an application is not using the framework
827
      if (e.code == rpc_error_code.METHOD_NOT_FOUND)
828 829 830 831 832 833 834
        return null;
      rethrow;
    }
  }

  // Debug dump extension methods.

835
  Future<Map<String, dynamic>> flutterDebugDumpApp() {
836
    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpApp', timeout: kLongRequestTimeout);
837
  }
838

839
  Future<Map<String, dynamic>> flutterDebugDumpRenderTree() {
840
    return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpRenderTree', timeout: kLongRequestTimeout);
841
  }
842

843 844
  Future<Map<String, dynamic>> flutterToggleDebugPaintSizeEnabled() async {
    Map<String, dynamic> state = await invokeFlutterExtensionRpcRaw('ext.flutter.debugPaint');
845
    if (state != null && state.containsKey('enabled') && state['enabled'] is String) {
846 847
      state = await invokeFlutterExtensionRpcRaw(
        'ext.flutter.debugPaint',
848
        params: <String, dynamic>{ 'enabled': state['enabled'] == 'true' ? 'false' : 'true' },
849 850 851 852
        timeout: const Duration(milliseconds: 150),
        timeoutFatal: false,
      );
    }
853 854 855
    return state;
  }

856
  Future<Null> flutterDebugAllowBanner(bool show) async {
857 858
    await invokeFlutterExtensionRpcRaw(
      'ext.flutter.debugAllowBanner',
859
      params: <String, dynamic>{ 'enabled': show ? 'true' : 'false' },
860 861 862
      timeout: const Duration(milliseconds: 150),
      timeoutFatal: false,
    );
863 864
  }

865 866
  // Reload related extension methods.
  Future<Map<String, dynamic>> flutterReassemble() async {
867 868 869 870 871
    return await invokeFlutterExtensionRpcRaw(
      'ext.flutter.reassemble',
      timeout: kLongRequestTimeout,
      timeoutFatal: false,
    );
872 873 874 875 876 877 878 879 880 881 882 883
  }

  Future<bool> flutterFrameworkPresent() async {
    return (await invokeFlutterExtensionRpcRaw('ext.flutter.frameworkPresent') != null);
  }

  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',
884 885 886
      params: <String, dynamic>{
        'value': assetPath,
      }
887
    );
888
  }
889

890 891
  // Application control extension methods.
  Future<Map<String, dynamic>> flutterExit() async {
892 893 894 895 896
    return await invokeFlutterExtensionRpcRaw(
      'ext.flutter.exit',
      timeout: const Duration(seconds: 2),
      timeoutFatal: false,
    );
897
  }
898 899
}

900 901 902 903
class ServiceMap extends ServiceObject implements Map<String, dynamic> {
  ServiceMap._empty(ServiceObjectOwner owner) : super._empty(owner);

  final Map<String, dynamic> _map = new Map<String, dynamic>();
904

905 906 907 908 909 910 911
  @override
  void _update(Map<String, dynamic> map, bool mapIsRef) {
    _loaded = !mapIsRef;
    _upgradeCollection(map, owner);
    _map.clear();
    _map.addAll(map);
  }
912

913 914 915 916 917 918 919 920
  // 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
921
  bool containsKey(Object k) => _map.containsKey(k);
922
  @override
923
  void forEach(void f(String key, dynamic value)) => _map.forEach(f);
924
  @override
925
  dynamic putIfAbsent(String key, dynamic ifAbsent()) => _map.putIfAbsent(key, ifAbsent);
926
  @override
Adam Barth's avatar
Adam Barth committed
927
  void remove(Object key) => _map.remove(key);
928
  @override
Adam Barth's avatar
Adam Barth committed
929
  dynamic operator [](Object k) => _map[k];
930 931 932 933 934 935 936 937 938 939 940 941 942 943
  @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();
944
}
945

946 947 948
/// Peered to a Android/iOS FlutterView widget on a device.
class FlutterView extends ServiceObject {
  FlutterView._empty(ServiceObjectOwner owner) : super._empty(owner);
949

950 951
  Isolate _uiIsolate;
  Isolate get uiIsolate => _uiIsolate;
952

953
  @override
954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980
  void _update(Map<String, dynamic> map, bool mapIsRef) {
    _loaded = !mapIsRef;
    _upgradeCollection(map, owner);
    _uiIsolate = map['isolate'];
  }

  // TODO(johnmccutchan): Report errors when running failed.
  Future<Null> runFromSource(String entryPath,
                             String packagesPath,
                             String assetsDirectoryPath) async {
    final String viewId = id;
    // When this completer completes the isolate is running.
    final Completer<Null> completer = new Completer<Null>();
    final StreamSubscription<ServiceEvent> subscription =
      owner.vm.vmService.onIsolateEvent.listen((ServiceEvent event) {
      // TODO(johnmccutchan): Listen to the debug stream and catch initial
      // launch errors.
      if (event.kind == ServiceEvent.kIsolateRunnable) {
        printTrace('Isolate is runnable.');
        completer.complete(null);
      }
    });
    await owner.vm.runInView(viewId,
                             entryPath,
                             packagesPath,
                             assetsDirectoryPath);
    await completer.future;
981
    await owner.vm.refreshViews();
982
    await subscription.cancel();
983
  }
984

985 986
  bool get hasIsolate => _uiIsolate != null;

987
  @override
988
  String toString() => id;
989
}