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