blob: db9f7830c81495f63a269f3a905b8b4c27d542f2 [file] [log] [blame]
Ian Hickson449f4a62019-11-27 15:04:02 -08001// Copyright 2014 The Flutter Authors. All rights reserved.
Devon Carew40c0d6e2016-05-12 18:15:23 -07002// 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';
Zachary Andersona0369802017-04-14 21:18:48 -07006import 'dart:math' as math;
Devon Carew40c0d6e2016-05-12 18:15:23 -07007
John McCutchan24ab8372016-09-15 13:18:32 -07008import 'package:json_rpc_2/error_code.dart' as rpc_error_code;
Devon Carew67377122017-01-12 14:03:04 -08009import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
Alexandre Ardhuin2de61a02017-03-31 18:34:13 +020010import 'package:meta/meta.dart' show required;
Jason Simmons37d78752016-12-09 11:38:38 -080011import 'package:stream_channel/stream_channel.dart';
Devon Carew40c0d6e2016-05-12 18:15:23 -070012import 'package:web_socket_channel/io.dart';
Todd Volkertd6f61b92017-02-15 06:52:28 -080013import 'package:web_socket_channel/web_socket_channel.dart';
Devon Carew40c0d6e2016-05-12 18:15:23 -070014
Todd Volkertd6f61b92017-02-15 06:52:28 -080015import 'base/common.dart';
Ian Hickson31a96262019-01-19 00:31:05 -080016import 'base/context.dart';
Yegor85473d02018-04-19 18:29:49 -070017import 'base/io.dart' as io;
Devon Carew9d9836f2018-07-09 12:22:46 -070018import 'base/utils.dart';
Jenn Magder2e7d9132019-11-01 14:37:17 -070019import 'convert.dart' show base64, utf8;
Jonah Williams831163f2019-11-26 14:49:56 -080020import 'device.dart';
Jonah Williamsee7a37f2020-01-06 11:04:20 -080021import 'globals.dart' as globals;
Kenzie Schmolld50d9c52019-09-05 09:50:36 -070022import 'version.dart';
Ian Hicksond7fb51a2016-08-02 16:52:57 -070023
Ian Hickson31a96262019-01-19 00:31:05 -080024/// Override `WebSocketConnector` in [context] to use a different constructor
25/// for [WebSocket]s (used by tests).
Jonah Williams301eaa82019-04-15 08:59:28 -070026typedef WebSocketConnector = Future<io.WebSocket> Function(String url, {io.CompressionOptions compression});
Ian Hickson31a96262019-01-19 00:31:05 -080027
Todd Volkertd6f61b92017-02-15 06:52:28 -080028/// A function that opens a two-way communication channel to the specified [uri].
Jonah Williams301eaa82019-04-15 08:59:28 -070029typedef _OpenChannel = Future<StreamChannel<String>> Function(Uri uri, {io.CompressionOptions compression});
Todd Volkertd6f61b92017-02-15 06:52:28 -080030
Todd Volkerta68c9792017-02-23 10:05:00 -080031_OpenChannel _openChannel = _defaultOpenChannel;
Todd Volkertd6f61b92017-02-15 06:52:28 -080032
Carlo Bernaschina16037e32017-07-17 15:13:24 -070033/// A function that reacts to the invocation of the 'reloadSources' service.
34///
35/// The VM Service Protocol allows clients to register custom services that
36/// can be invoked by other clients through the service protocol itself.
37///
38/// Clients like Observatory use external 'reloadSources' services,
39/// when available, instead of the VM internal one. This allows these clients to
40/// invoke Flutter HotReload when connected to a Flutter Application started in
41/// hot mode.
42///
43/// See: https://github.com/dart-lang/sdk/issues/30023
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +020044typedef ReloadSources = Future<void> Function(
Carlo Bernaschina16037e32017-07-17 15:13:24 -070045 String isolateId, {
46 bool force,
47 bool pause,
48});
49
Kenzie Schmoll33bfa6a2019-01-17 08:28:54 -080050typedef Restart = Future<void> Function({ bool pause });
51
Alexandre Ardhuina07d3712018-09-14 21:06:19 +020052typedef CompileExpression = Future<String> Function(
Alexander Aprelevf11c8d92018-06-05 13:22:13 -070053 String isolateId,
54 String expression,
55 List<String> definitions,
56 List<String> typeDefinitions,
57 String libraryUri,
58 String klass,
59 bool isStatic,
60);
61
Jonah Williams81aa2712019-12-09 21:31:34 -080062typedef ReloadMethod = Future<void> Function({
63 String classId,
64 String libraryId,
65});
66
Jonah Williams301eaa82019-04-15 08:59:28 -070067Future<StreamChannel<String>> _defaultOpenChannel(Uri uri, {io.CompressionOptions compression = io.CompressionOptions.compressionDefault}) async {
Yegor85473d02018-04-19 18:29:49 -070068 Duration delay = const Duration(milliseconds: 100);
69 int attempts = 0;
70 io.WebSocket socket;
Yegoree735c42018-04-20 14:45:50 -070071
Ian Hickson31a96262019-01-19 00:31:05 -080072 Future<void> handleError(dynamic e) async {
Jonah Williamsee7a37f2020-01-06 11:04:20 -080073 globals.printTrace('Exception attempting to connect to Observatory: $e');
74 globals.printTrace('This was attempt #$attempts. Will retry in $delay.');
Yegoree735c42018-04-20 14:45:50 -070075
Zachary Andersone2340c62019-09-13 14:51:35 -070076 if (attempts == 10) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -080077 globals.printStatus('This is taking longer than expected...');
Zachary Andersone2340c62019-09-13 14:51:35 -070078 }
Ian Hickson31a96262019-01-19 00:31:05 -080079
Yegoree735c42018-04-20 14:45:50 -070080 // Delay next attempt.
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +020081 await Future<void>.delayed(delay);
Yegoree735c42018-04-20 14:45:50 -070082
Ian Hickson31a96262019-01-19 00:31:05 -080083 // Back off exponentially, up to 1600ms per attempt.
Zachary Andersone2340c62019-09-13 14:51:35 -070084 if (delay < const Duration(seconds: 1)) {
Ian Hickson31a96262019-01-19 00:31:05 -080085 delay *= 2;
Zachary Andersone2340c62019-09-13 14:51:35 -070086 }
Yegoree735c42018-04-20 14:45:50 -070087 }
88
Jonah Williams0acd3e62019-04-25 15:51:08 -070089 final WebSocketConnector constructor = context.get<WebSocketConnector>() ?? io.WebSocket.connect;
Ian Hickson31a96262019-01-19 00:31:05 -080090 while (socket == null) {
Yegor85473d02018-04-19 18:29:49 -070091 attempts += 1;
92 try {
Jonah Williams301eaa82019-04-15 08:59:28 -070093 socket = await constructor(uri.toString(), compression: compression);
Yegoree735c42018-04-20 14:45:50 -070094 } on io.WebSocketException catch (e) {
Ian Hickson31a96262019-01-19 00:31:05 -080095 await handleError(e);
Yegoree735c42018-04-20 14:45:50 -070096 } on io.SocketException catch (e) {
Ian Hickson31a96262019-01-19 00:31:05 -080097 await handleError(e);
Yegor85473d02018-04-19 18:29:49 -070098 }
99 }
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200100 return IOWebSocketChannel(socket).cast<String>();
Yegor85473d02018-04-19 18:29:49 -0700101}
Todd Volkertd6f61b92017-02-15 06:52:28 -0800102
Jenn Magder2e7d9132019-11-01 14:37:17 -0700103/// Override `VMServiceConnector` in [context] to return a different VMService
104/// from [VMService.connect] (used by tests).
Jonah Williams831163f2019-11-26 14:49:56 -0800105typedef VMServiceConnector = Future<VMService> Function(Uri httpUri, {
106 ReloadSources reloadSources,
107 Restart restart,
108 CompileExpression compileExpression,
Jonah Williams81aa2712019-12-09 21:31:34 -0800109 ReloadMethod reloadMethod,
Jonah Williams831163f2019-11-26 14:49:56 -0800110 io.CompressionOptions compression,
111 Device device,
112});
Jenn Magder2e7d9132019-11-01 14:37:17 -0700113
John McCutchan3a012b32016-08-17 09:01:04 -0700114/// A connection to the Dart VM Service.
Ian Hickson31a96262019-01-19 00:31:05 -0800115// TODO(mklim): Test this, https://github.com/flutter/flutter/issues/23031
John McCutchancab7c8d2016-08-11 13:14:13 -0700116class VMService {
Ian Hickson6bfd9bb2018-11-08 20:54:06 -0800117 VMService(
Carlo Bernaschina16037e32017-07-17 15:13:24 -0700118 this._peer,
119 this.httpAddress,
120 this.wsAddress,
Carlo Bernaschina16037e32017-07-17 15:13:24 -0700121 ReloadSources reloadSources,
Kenzie Schmoll33bfa6a2019-01-17 08:28:54 -0800122 Restart restart,
Alexander Aprelevf11c8d92018-06-05 13:22:13 -0700123 CompileExpression compileExpression,
Jonah Williams831163f2019-11-26 14:49:56 -0800124 Device device,
Jonah Williams81aa2712019-12-09 21:31:34 -0800125 ReloadMethod reloadMethod,
Carlo Bernaschina16037e32017-07-17 15:13:24 -0700126 ) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200127 _vm = VM._empty(this);
Alexandre Ardhuin2166ea52017-03-15 23:09:58 +0100128 _peer.listen().catchError(_connectionError.completeError);
John McCutchan3a012b32016-08-17 09:01:04 -0700129
Todd Volkertd6f61b92017-02-15 06:52:28 -0800130 _peer.registerMethod('streamNotify', (rpc.Parameters event) {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100131 _handleStreamNotify(event.asMap.cast<String, dynamic>());
Devon Carew40c0d6e2016-05-12 18:15:23 -0700132 });
Carlo Bernaschina16037e32017-07-17 15:13:24 -0700133
134 if (reloadSources != null) {
135 _peer.registerMethod('reloadSources', (rpc.Parameters params) async {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100136 final String isolateId = params['isolateId'].value as String;
137 final bool force = params.asMap['force'] as bool ?? false;
138 final bool pause = params.asMap['pause'] as bool ?? false;
Carlo Bernaschina16037e32017-07-17 15:13:24 -0700139
Jonah Williams331d19f2019-10-29 13:21:06 -0700140 if (isolateId.isEmpty) {
Alexandre Ardhuinf15c8872020-02-11 20:58:27 +0100141 throw rpc.RpcException.invalidParams("Invalid 'isolateId': $isolateId");
Zachary Andersone2340c62019-09-13 14:51:35 -0700142 }
Carlo Bernaschina16037e32017-07-17 15:13:24 -0700143 try {
144 await reloadSources(isolateId, force: force, pause: pause);
145 return <String, String>{'type': 'Success'};
146 } on rpc.RpcException {
147 rethrow;
Zachary Anderson9de77872020-02-27 22:46:23 -0800148 } on Exception catch (e, st) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200149 throw rpc.RpcException(rpc_error_code.SERVER_ERROR,
Carlo Bernaschina16037e32017-07-17 15:13:24 -0700150 'Error during Sources Reload: $e\n$st');
151 }
152 });
153
Ben Konyi8d81c302019-07-08 15:09:17 -0700154 _peer.sendNotification('registerService', <String, String>{
Carlo Bernaschina16037e32017-07-17 15:13:24 -0700155 'service': 'reloadSources',
Kenzie Schmoll33bfa6a2019-01-17 08:28:54 -0800156 'alias': 'Flutter Tools',
157 });
Jonah Williams331d19f2019-10-29 13:21:06 -0700158
Jonah Williams81aa2712019-12-09 21:31:34 -0800159 }
160
161 if (reloadMethod != null) {
Jonah Williams331d19f2019-10-29 13:21:06 -0700162 // Register a special method for hot UI. while this is implemented
163 // currently in the same way as hot reload, it leaves the tool free
164 // to change to a more efficient implementation in the future.
Jonah Williams81aa2712019-12-09 21:31:34 -0800165 //
166 // `library` should be the file URI of the updated code.
167 // `class` should be the name of the Widget subclass to be marked dirty. For example,
168 // if the build method of a StatelessWidget is updated, this is the name of class.
169 // If the build method of a StatefulWidget is updated, then this is the name
170 // of the Widget class that created the State object.
Jonah Williams331d19f2019-10-29 13:21:06 -0700171 _peer.registerMethod('reloadMethod', (rpc.Parameters params) async {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100172 final String libraryId = params['library'].value as String;
173 final String classId = params['class'].value as String;
Jonah Williams331d19f2019-10-29 13:21:06 -0700174
175 if (libraryId.isEmpty) {
Alexandre Ardhuinf15c8872020-02-11 20:58:27 +0100176 throw rpc.RpcException.invalidParams("Invalid 'libraryId': $libraryId");
Jonah Williams331d19f2019-10-29 13:21:06 -0700177 }
178 if (classId.isEmpty) {
Alexandre Ardhuinf15c8872020-02-11 20:58:27 +0100179 throw rpc.RpcException.invalidParams("Invalid 'classId': $classId");
Jonah Williams331d19f2019-10-29 13:21:06 -0700180 }
Jonah Williams331d19f2019-10-29 13:21:06 -0700181
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800182 globals.printTrace('reloadMethod not yet supported, falling back to hot reload');
Jonah Williams331d19f2019-10-29 13:21:06 -0700183
184 try {
Jonah Williams81aa2712019-12-09 21:31:34 -0800185 await reloadMethod(
186 libraryId: libraryId,
187 classId: classId,
188 );
Jonah Williams331d19f2019-10-29 13:21:06 -0700189 return <String, String>{'type': 'Success'};
190 } on rpc.RpcException {
191 rethrow;
Zachary Anderson9de77872020-02-27 22:46:23 -0800192 } on Exception catch (e, st) {
Jonah Williams331d19f2019-10-29 13:21:06 -0700193 throw rpc.RpcException(rpc_error_code.SERVER_ERROR,
194 'Error during Sources Reload: $e\n$st');
195 }
196 });
197 _peer.sendNotification('registerService', <String, String>{
198 'service': 'reloadMethod',
199 'alias': 'Flutter Tools',
200 });
Kenzie Schmoll33bfa6a2019-01-17 08:28:54 -0800201 }
202
203 if (restart != null) {
204 _peer.registerMethod('hotRestart', (rpc.Parameters params) async {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100205 final bool pause = params.asMap['pause'] as bool ?? false;
Kenzie Schmoll33bfa6a2019-01-17 08:28:54 -0800206
Zachary Andersone2340c62019-09-13 14:51:35 -0700207 if (pause is! bool) {
Alexandre Ardhuinf15c8872020-02-11 20:58:27 +0100208 throw rpc.RpcException.invalidParams("Invalid 'pause': $pause");
Zachary Andersone2340c62019-09-13 14:51:35 -0700209 }
Kenzie Schmoll33bfa6a2019-01-17 08:28:54 -0800210
211 try {
212 await restart(pause: pause);
213 return <String, String>{'type': 'Success'};
214 } on rpc.RpcException {
215 rethrow;
Zachary Anderson9de77872020-02-27 22:46:23 -0800216 } on Exception catch (e, st) {
Kenzie Schmoll33bfa6a2019-01-17 08:28:54 -0800217 throw rpc.RpcException(rpc_error_code.SERVER_ERROR,
218 'Error during Hot Restart: $e\n$st');
219 }
220 });
221
Ben Konyi8d81c302019-07-08 15:09:17 -0700222 _peer.sendNotification('registerService', <String, String>{
Kenzie Schmoll33bfa6a2019-01-17 08:28:54 -0800223 'service': 'hotRestart',
224 'alias': 'Flutter Tools',
Carlo Bernaschina16037e32017-07-17 15:13:24 -0700225 });
226 }
Alexander Aprelevf11c8d92018-06-05 13:22:13 -0700227
Kenzie Schmolld50d9c52019-09-05 09:50:36 -0700228 _peer.registerMethod('flutterVersion', (rpc.Parameters params) async {
Mehmet Fidanboylu0c5ae7d2020-02-03 20:43:02 -0800229 final FlutterVersion version = context.get<FlutterVersion>() ?? FlutterVersion();
Kenzie Schmolld50d9c52019-09-05 09:50:36 -0700230 final Map<String, Object> versionJson = version.toJson();
231 versionJson['frameworkRevisionShort'] = version.frameworkRevisionShort;
232 versionJson['engineRevisionShort'] = version.engineRevisionShort;
233 return versionJson;
234 });
235
236 _peer.sendNotification('registerService', <String, String>{
237 'service': 'flutterVersion',
238 'alias': 'Flutter Tools',
239 });
240
Alexander Aprelevf11c8d92018-06-05 13:22:13 -0700241 if (compileExpression != null) {
242 _peer.registerMethod('compileExpression', (rpc.Parameters params) async {
243 final String isolateId = params['isolateId'].asString;
Zachary Andersone2340c62019-09-13 14:51:35 -0700244 if (isolateId is! String || isolateId.isEmpty) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200245 throw rpc.RpcException.invalidParams(
Alexandre Ardhuinf15c8872020-02-11 20:58:27 +0100246 "Invalid 'isolateId': $isolateId");
Zachary Andersone2340c62019-09-13 14:51:35 -0700247 }
Alexander Aprelevf11c8d92018-06-05 13:22:13 -0700248 final String expression = params['expression'].asString;
Zachary Andersone2340c62019-09-13 14:51:35 -0700249 if (expression is! String || expression.isEmpty) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200250 throw rpc.RpcException.invalidParams(
Alexandre Ardhuinf15c8872020-02-11 20:58:27 +0100251 "Invalid 'expression': $expression");
Zachary Andersone2340c62019-09-13 14:51:35 -0700252 }
Alexander Aprelev7ebf2722018-07-12 14:59:22 -0700253 final List<String> definitions =
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200254 List<String>.from(params['definitions'].asList);
Alexander Aprelev7ebf2722018-07-12 14:59:22 -0700255 final List<String> typeDefinitions =
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200256 List<String>.from(params['typeDefinitions'].asList);
Alexander Aprelevf11c8d92018-06-05 13:22:13 -0700257 final String libraryUri = params['libraryUri'].asString;
Alexander Apreleved2376a2018-06-27 15:51:23 -0700258 final String klass = params['klass'].exists ? params['klass'].asString : null;
Alexander Aprelevf11c8d92018-06-05 13:22:13 -0700259 final bool isStatic = params['isStatic'].asBoolOr(false);
260
261 try {
262 final String kernelBytesBase64 = await compileExpression(isolateId,
263 expression, definitions, typeDefinitions, libraryUri, klass,
264 isStatic);
265 return <String, dynamic>{'type': 'Success',
266 'result': <String, dynamic> {'kernelBytes': kernelBytesBase64}};
267 } on rpc.RpcException {
268 rethrow;
Zachary Anderson9de77872020-02-27 22:46:23 -0800269 } on Exception catch (e, st) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200270 throw rpc.RpcException(rpc_error_code.SERVER_ERROR,
Alexander Aprelevf11c8d92018-06-05 13:22:13 -0700271 'Error during expression compilation: $e\n$st');
272 }
273 });
274
Ben Konyi8d81c302019-07-08 15:09:17 -0700275 _peer.sendNotification('registerService', <String, String>{
Alexander Aprelevf11c8d92018-06-05 13:22:13 -0700276 'service': 'compileExpression',
Alexandre Ardhuin387f8852019-03-01 08:17:55 +0100277 'alias': 'Flutter Tools',
Alexander Aprelevf11c8d92018-06-05 13:22:13 -0700278 });
279 }
Jonah Williams831163f2019-11-26 14:49:56 -0800280 if (device != null) {
281 _peer.registerMethod('flutterMemoryInfo', (rpc.Parameters params) async {
282 final MemoryInfo result = await device.queryMemoryInfo();
283 return result.toJson();
284 });
285 _peer.sendNotification('registerService', <String, String>{
286 'service': 'flutterMemoryInfo',
287 'alias': 'Flutter Tools',
288 });
289 }
Devon Carew40c0d6e2016-05-12 18:15:23 -0700290 }
291
Dan Field1db5d662019-04-25 08:27:00 -0700292 static void _unhandledError(dynamic error, dynamic stack) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800293 globals.logger.printTrace('Error in internal implementation of JSON RPC.\n$error\n$stack');
Dan Field1db5d662019-04-25 08:27:00 -0700294 assert(false);
295 }
296
Yegor25e6cc62016-12-12 10:58:21 -0800297 /// Connect to a Dart VM Service at [httpUri].
298 ///
Carlo Bernaschina16037e32017-07-17 15:13:24 -0700299 /// If the [reloadSources] parameter is not null, the 'reloadSources' service
300 /// will be registered. The VM Service Protocol allows clients to register
301 /// custom services that can be invoked by other clients through the service
302 /// protocol itself.
303 ///
304 /// See: https://github.com/dart-lang/sdk/commit/df8bf384eb815cf38450cb50a0f4b62230fba217
Yegord4830fc2017-11-08 10:43:47 -0800305 static Future<VMService> connect(
Todd Volkertd6f61b92017-02-15 06:52:28 -0800306 Uri httpUri, {
Jenn Magder2e7d9132019-11-01 14:37:17 -0700307 ReloadSources reloadSources,
308 Restart restart,
309 CompileExpression compileExpression,
Jonah Williams81aa2712019-12-09 21:31:34 -0800310 ReloadMethod reloadMethod,
Jenn Magder2e7d9132019-11-01 14:37:17 -0700311 io.CompressionOptions compression = io.CompressionOptions.compressionDefault,
Jonah Williams831163f2019-11-26 14:49:56 -0800312 Device device,
Jenn Magder2e7d9132019-11-01 14:37:17 -0700313 }) async {
314 final VMServiceConnector connector = context.get<VMServiceConnector>() ?? VMService._connect;
Jonah Williams831163f2019-11-26 14:49:56 -0800315 return connector(httpUri,
316 reloadSources: reloadSources,
317 restart: restart,
318 compileExpression: compileExpression,
319 compression: compression,
320 device: device,
Jonah Williams81aa2712019-12-09 21:31:34 -0800321 reloadMethod: reloadMethod,
Jonah Williams831163f2019-11-26 14:49:56 -0800322 );
Jenn Magder2e7d9132019-11-01 14:37:17 -0700323 }
324
325 static Future<VMService> _connect(
326 Uri httpUri, {
Carlo Bernaschina16037e32017-07-17 15:13:24 -0700327 ReloadSources reloadSources,
Kenzie Schmoll33bfa6a2019-01-17 08:28:54 -0800328 Restart restart,
Alexander Aprelevf11c8d92018-06-05 13:22:13 -0700329 CompileExpression compileExpression,
Jonah Williams81aa2712019-12-09 21:31:34 -0800330 ReloadMethod reloadMethod,
Jonah Williams301eaa82019-04-15 08:59:28 -0700331 io.CompressionOptions compression = io.CompressionOptions.compressionDefault,
Jonah Williams831163f2019-11-26 14:49:56 -0800332 Device device,
Yegord4830fc2017-11-08 10:43:47 -0800333 }) async {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800334 final Uri wsUri = httpUri.replace(scheme: 'ws', path: globals.fs.path.join(httpUri.path, 'ws'));
Jonah Williams301eaa82019-04-15 08:59:28 -0700335 final StreamChannel<String> channel = await _openChannel(wsUri, compression: compression);
Dan Field1db5d662019-04-25 08:27:00 -0700336 final rpc.Peer peer = rpc.Peer.withoutJson(jsonDocument.bind(channel), onUnhandledError: _unhandledError);
Jonah Williams81aa2712019-12-09 21:31:34 -0800337 final VMService service = VMService(
338 peer,
339 httpUri,
340 wsUri,
341 reloadSources,
342 restart,
343 compileExpression,
344 device,
345 reloadMethod,
346 );
Yegord4830fc2017-11-08 10:43:47 -0800347 // This call is to ensure we are able to establish a connection instead of
348 // keeping on trucking and failing farther down the process.
349 await service._sendRequest('getVersion', const <String, dynamic>{});
350 return service;
Devon Carew40c0d6e2016-05-12 18:15:23 -0700351 }
Yegor25e6cc62016-12-12 10:58:21 -0800352
John McCutchan487f28f2016-08-08 12:42:31 -0700353 final Uri httpAddress;
Dan Rubela9584e12016-11-30 20:29:04 -0500354 final Uri wsAddress;
Todd Volkertd6f61b92017-02-15 06:52:28 -0800355 final rpc.Peer _peer;
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200356 final Completer<Map<String, dynamic>> _connectionError = Completer<Map<String, dynamic>>();
Devon Carew40c0d6e2016-05-12 18:15:23 -0700357
John McCutchan3a012b32016-08-17 09:01:04 -0700358 VM _vm;
359 /// The singleton [VM] object. Owns [Isolate] and [FlutterView] objects.
360 VM get vm => _vm;
Devon Carewec751772016-05-26 15:26:14 -0700361
John McCutchan3a012b32016-08-17 09:01:04 -0700362 final Map<String, StreamController<ServiceEvent>> _eventControllers =
363 <String, StreamController<ServiceEvent>>{};
Devon Carew40c0d6e2016-05-12 18:15:23 -0700364
Phil Quitslund802eca22019-03-06 11:05:16 -0800365 final Set<String> _listeningFor = <String>{};
Devon Carewec751772016-05-26 15:26:14 -0700366
Todd Volkertd6f61b92017-02-15 06:52:28 -0800367 /// Whether our connection to the VM service has been closed;
368 bool get isClosed => _peer.isClosed;
Devon Carew9d9836f2018-07-09 12:22:46 -0700369
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +0200370 Future<void> get done async {
Devon Carew9d9836f2018-07-09 12:22:46 -0700371 await _peer.done;
372 }
Devon Carew40c0d6e2016-05-12 18:15:23 -0700373
374 // Events
Alexander Aprelev09ca3c12018-04-18 01:21:33 -0700375 Future<Stream<ServiceEvent>> get onDebugEvent => onEvent('Debug');
376 Future<Stream<ServiceEvent>> get onExtensionEvent => onEvent('Extension');
Devon Carew40c0d6e2016-05-12 18:15:23 -0700377 // IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, ServiceExtensionAdded
Alexander Aprelev09ca3c12018-04-18 01:21:33 -0700378 Future<Stream<ServiceEvent>> get onIsolateEvent => onEvent('Isolate');
379 Future<Stream<ServiceEvent>> get onTimelineEvent => onEvent('Timeline');
Jenn Magder2e7d9132019-11-01 14:37:17 -0700380 Future<Stream<ServiceEvent>> get onStdoutEvent => onEvent('Stdout'); // WriteEvent
381
John McCutchan3a012b32016-08-17 09:01:04 -0700382 // TODO(johnmccutchan): Add FlutterView events.
Devon Carew40c0d6e2016-05-12 18:15:23 -0700383
Todd Volkert3a0aabc2018-04-18 09:24:52 -0700384 /// Returns a stream of VM service events.
385 ///
386 /// This purposely returns a `Future<Stream<T>>` rather than a `Stream<T>`
387 /// because it first registers with the VM to receive events on the stream,
388 /// and only once the VM has acknowledged that the stream has started will
389 /// we return the associated stream. Any attempt to streamline this API into
390 /// returning `Stream<T>` should take that into account to avoid race
391 /// conditions.
Alexander Aprelev09ca3c12018-04-18 01:21:33 -0700392 Future<Stream<ServiceEvent>> onEvent(String streamId) async {
393 await _streamListen(streamId);
Devon Carewec751772016-05-26 15:26:14 -0700394 return _getEventController(streamId).stream;
395 }
Devon Carew40c0d6e2016-05-12 18:15:23 -0700396
Todd Volkertd6f61b92017-02-15 06:52:28 -0800397 Future<Map<String, dynamic>> _sendRequest(
398 String method,
399 Map<String, dynamic> params,
400 ) {
Alexandre Ardhuinf62afdc2018-10-01 21:29:08 +0200401 return Future.any<Map<String, dynamic>>(<Future<Map<String, dynamic>>>[
Alexandre Ardhuinf11c3412019-09-27 10:46:45 +0200402 _peer.sendRequest(method, params).then<Map<String, dynamic>>(castStringKeyedMap),
403 _connectionError.future,
Todd Volkertd6f61b92017-02-15 06:52:28 -0800404 ]);
405 }
406
John McCutchan3a012b32016-08-17 09:01:04 -0700407 StreamController<ServiceEvent> _getEventController(String eventName) {
408 StreamController<ServiceEvent> controller = _eventControllers[eventName];
Devon Carew40c0d6e2016-05-12 18:15:23 -0700409 if (controller == null) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200410 controller = StreamController<ServiceEvent>.broadcast();
Devon Carew40c0d6e2016-05-12 18:15:23 -0700411 _eventControllers[eventName] = controller;
412 }
413 return controller;
414 }
415
416 void _handleStreamNotify(Map<String, dynamic> data) {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100417 final String streamId = data['streamId'] as String;
418 final Map<String, dynamic> eventData = castStringKeyedMap(data['event']);
419 final Map<String, dynamic> eventIsolate = castStringKeyedMap(eventData['isolate']);
Devon Carew990dae82017-07-27 11:47:33 -0700420
421 // Log event information.
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800422 globals.printTrace('Notification from VM: $data');
Devon Carew990dae82017-07-27 11:47:33 -0700423
John McCutchan3a012b32016-08-17 09:01:04 -0700424 ServiceEvent event;
425 if (eventIsolate != null) {
426 // getFromMap creates the Isolate if necessary.
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100427 final Isolate isolate = vm.getFromMap(eventIsolate) as Isolate;
428 event = ServiceObject._fromMap(isolate, eventData) as ServiceEvent;
John McCutchan3a012b32016-08-17 09:01:04 -0700429 if (event.kind == ServiceEvent.kIsolateExit) {
430 vm._isolateCache.remove(isolate.id);
431 vm._buildIsolateList();
432 } else if (event.kind == ServiceEvent.kIsolateRunnable) {
433 // Force reload once the isolate becomes runnable so that we
434 // update the root library.
435 isolate.reload();
436 }
437 } else {
438 // The event doesn't have an isolate, so it is owned by the VM.
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100439 event = ServiceObject._fromMap(vm, eventData) as ServiceEvent;
John McCutchan3a012b32016-08-17 09:01:04 -0700440 }
441 _getEventController(streamId).add(event);
Devon Carew40c0d6e2016-05-12 18:15:23 -0700442 }
443
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +0200444 Future<void> _streamListen(String streamId) async {
Devon Carewec751772016-05-26 15:26:14 -0700445 if (!_listeningFor.contains(streamId)) {
446 _listeningFor.add(streamId);
Jonah Williams8d6dc622019-08-15 12:13:12 -0700447 await _sendRequest('streamListen', <String, dynamic>{'streamId': streamId});
Devon Carewec751772016-05-26 15:26:14 -0700448 }
Devon Carew40c0d6e2016-05-12 18:15:23 -0700449 }
450
John McCutchan3a012b32016-08-17 09:01:04 -0700451 /// Reloads the VM.
Ian Hickson6bfd9bb2018-11-08 20:54:06 -0800452 Future<void> getVM() async => await vm.reload();
Zachary Anderson9a068c62017-03-16 14:35:53 -0700453
Ian Hickson6bfd9bb2018-11-08 20:54:06 -0800454 Future<void> refreshViews({ bool waitForViews = false }) => vm.refreshViews(waitForViews: waitForViews);
Martin Kustermannd2947572020-02-18 21:39:37 +0100455
456 Future<void> close() async => await _peer.close();
John McCutchan3a012b32016-08-17 09:01:04 -0700457}
458
459/// An error that is thrown when constructing/updating a service object.
460class VMServiceObjectLoadError {
461 VMServiceObjectLoadError(this.message, this.map);
462 final String message;
463 final Map<String, dynamic> map;
464}
465
466bool _isServiceMap(Map<String, dynamic> m) {
467 return (m != null) && (m['type'] != null);
468}
469bool _hasRef(String type) => (type != null) && type.startsWith('@');
Alexandre Ardhuin2e80bf12018-02-06 09:00:11 +0100470String _stripRef(String type) => _hasRef(type) ? type.substring(1) : type;
John McCutchan3a012b32016-08-17 09:01:04 -0700471
472/// Given a raw response from the service protocol and a [ServiceObjectOwner],
473/// recursively walk the response and replace values that are service maps with
474/// actual [ServiceObject]s. During the upgrade the owner is given a chance
475/// to return a cached / canonicalized object.
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +0100476void _upgradeCollection(
477 dynamic collection,
478 ServiceObjectOwner owner,
479) {
Zachary Andersone2340c62019-09-13 14:51:35 -0700480 if (collection is ServiceMap) {
John McCutchan3a012b32016-08-17 09:01:04 -0700481 return;
Zachary Andersone2340c62019-09-13 14:51:35 -0700482 }
Jacob Richman53fc96d2017-02-06 08:55:09 -0800483 if (collection is Map<String, dynamic>) {
John McCutchan3a012b32016-08-17 09:01:04 -0700484 _upgradeMap(collection, owner);
485 } else if (collection is List) {
486 _upgradeList(collection, owner);
487 }
488}
489
490void _upgradeMap(Map<String, dynamic> map, ServiceObjectOwner owner) {
Ian Hickson6bfd9bb2018-11-08 20:54:06 -0800491 map.forEach((String k, Object v) {
Jacob Richman53fc96d2017-02-06 08:55:09 -0800492 if ((v is Map<String, dynamic>) && _isServiceMap(v)) {
John McCutchan3a012b32016-08-17 09:01:04 -0700493 map[k] = owner.getFromMap(v);
494 } else if (v is List) {
495 _upgradeList(v, owner);
Jacob Richman53fc96d2017-02-06 08:55:09 -0800496 } else if (v is Map<String, dynamic>) {
John McCutchan3a012b32016-08-17 09:01:04 -0700497 _upgradeMap(v, owner);
498 }
499 });
500}
501
502void _upgradeList(List<dynamic> list, ServiceObjectOwner owner) {
Ian Hickson6bfd9bb2018-11-08 20:54:06 -0800503 for (int i = 0; i < list.length; i += 1) {
504 final Object v = list[i];
Jacob Richman53fc96d2017-02-06 08:55:09 -0800505 if ((v is Map<String, dynamic>) && _isServiceMap(v)) {
John McCutchan3a012b32016-08-17 09:01:04 -0700506 list[i] = owner.getFromMap(v);
507 } else if (v is List) {
508 _upgradeList(v, owner);
Jacob Richman53fc96d2017-02-06 08:55:09 -0800509 } else if (v is Map<String, dynamic>) {
John McCutchan3a012b32016-08-17 09:01:04 -0700510 _upgradeMap(v, owner);
511 }
512 }
513}
514
515/// Base class of all objects received over the service protocol.
516abstract class ServiceObject {
517 ServiceObject._empty(this._owner);
518
519 /// Factory constructor given a [ServiceObjectOwner] and a service map,
520 /// upgrade the map into a proper [ServiceObject]. This function always
521 /// returns a new instance and does not interact with caches.
Alexandre Ardhuinbfa1d252019-03-23 00:02:21 +0100522 factory ServiceObject._fromMap(
523 ServiceObjectOwner owner,
524 Map<String, dynamic> map,
525 ) {
Zachary Andersone2340c62019-09-13 14:51:35 -0700526 if (map == null) {
John McCutchan3a012b32016-08-17 09:01:04 -0700527 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -0700528 }
John McCutchan3a012b32016-08-17 09:01:04 -0700529
Zachary Andersone2340c62019-09-13 14:51:35 -0700530 if (!_isServiceMap(map)) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200531 throw VMServiceObjectLoadError('Expected a service map', map);
Zachary Andersone2340c62019-09-13 14:51:35 -0700532 }
John McCutchan3a012b32016-08-17 09:01:04 -0700533
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100534 final String type = _stripRef(map['type'] as String);
John McCutchan3a012b32016-08-17 09:01:04 -0700535
536 ServiceObject serviceObject;
537 switch (type) {
538 case 'Event':
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200539 serviceObject = ServiceEvent._empty(owner);
Alexandre Ardhuinb8731622019-09-24 21:03:37 +0200540 break;
John McCutchan3a012b32016-08-17 09:01:04 -0700541 case 'FlutterView':
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200542 serviceObject = FlutterView._empty(owner.vm);
Alexandre Ardhuinb8731622019-09-24 21:03:37 +0200543 break;
John McCutchan3a012b32016-08-17 09:01:04 -0700544 case 'Isolate':
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200545 serviceObject = Isolate._empty(owner.vm);
Alexandre Ardhuinb8731622019-09-24 21:03:37 +0200546 break;
John McCutchan3a012b32016-08-17 09:01:04 -0700547 }
Alexandre Ardhuin8bcf3022017-04-07 21:41:17 +0200548 // If we don't have a model object for this service object type, as a
549 // fallback return a ServiceMap object.
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200550 serviceObject ??= ServiceMap._empty(owner);
Devon Carew990dae82017-07-27 11:47:33 -0700551 // We have now constructed an empty service object, call update to populate it.
Alexander Aprelev0f3aa502018-02-09 10:33:24 -0800552 serviceObject.updateFromMap(map);
John McCutchan3a012b32016-08-17 09:01:04 -0700553 return serviceObject;
Devon Carew40c0d6e2016-05-12 18:15:23 -0700554 }
555
John McCutchan3a012b32016-08-17 09:01:04 -0700556 final ServiceObjectOwner _owner;
557 ServiceObjectOwner get owner => _owner;
558
559 /// The id of this object.
560 String get id => _id;
561 String _id;
562
563 /// The user-level type of this object.
564 String get type => _type;
565 String _type;
566
567 /// The vm-level type of this object. Usually the same as [type].
568 String get vmType => _vmType;
569 String _vmType;
570
571 /// Is it safe to cache this object?
572 bool _canCache = false;
573 bool get canCache => _canCache;
574
575 /// Has this object been fully loaded?
576 bool get loaded => _loaded;
577 bool _loaded = false;
578
579 /// Is this object immutable after it is [loaded]?
580 bool get immutable => false;
581
582 String get name => _name;
583 String _name;
584
585 String get vmName => _vmName;
586 String _vmName;
587
588 /// If this is not already loaded, load it. Otherwise reload.
589 Future<ServiceObject> load() async {
Zachary Andersone2340c62019-09-13 14:51:35 -0700590 if (loaded) {
John McCutchan3a012b32016-08-17 09:01:04 -0700591 return this;
Zachary Andersone2340c62019-09-13 14:51:35 -0700592 }
John McCutchan3a012b32016-08-17 09:01:04 -0700593 return reload();
594 }
595
596 /// Fetch this object from vmService and return the response directly.
597 Future<Map<String, dynamic>> _fetchDirect() {
Chris Bracken7a093162017-03-03 17:50:46 -0800598 final Map<String, dynamic> params = <String, dynamic>{
John McCutchan3a012b32016-08-17 09:01:04 -0700599 'objectId': id,
600 };
Ian Hicksondc634e12017-02-02 15:48:35 -0800601 return _owner.isolate.invokeRpcRaw('getObject', params: params);
John McCutchan3a012b32016-08-17 09:01:04 -0700602 }
603
604 Future<ServiceObject> _inProgressReload;
605 /// Reload the service object (if possible).
606 Future<ServiceObject> reload() async {
Chris Bracken7a093162017-03-03 17:50:46 -0800607 final bool hasId = (id != null) && (id != '');
608 final bool isVM = this is VM;
John McCutchan3a012b32016-08-17 09:01:04 -0700609 // We should always reload the VM.
610 // We can't reload objects without an id.
611 // We shouldn't reload an immutable and already loaded object.
Zachary Andersone2340c62019-09-13 14:51:35 -0700612 if (!isVM && (!hasId || (immutable && loaded))) {
John McCutchan3a012b32016-08-17 09:01:04 -0700613 return this;
Zachary Andersone2340c62019-09-13 14:51:35 -0700614 }
John McCutchan3a012b32016-08-17 09:01:04 -0700615
616 if (_inProgressReload == null) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200617 final Completer<ServiceObject> completer = Completer<ServiceObject>();
John McCutchan3a012b32016-08-17 09:01:04 -0700618 _inProgressReload = completer.future;
John McCutchan3a012b32016-08-17 09:01:04 -0700619 try {
Chris Bracken7a093162017-03-03 17:50:46 -0800620 final Map<String, dynamic> response = await _fetchDirect();
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100621 if (_stripRef(response['type'] as String) == 'Sentinel') {
John McCutchan3a012b32016-08-17 09:01:04 -0700622 // An object may have been collected.
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200623 completer.complete(ServiceObject._fromMap(owner, response));
John McCutchan3a012b32016-08-17 09:01:04 -0700624 } else {
Alexander Aprelev0f3aa502018-02-09 10:33:24 -0800625 updateFromMap(response);
John McCutchan3a012b32016-08-17 09:01:04 -0700626 completer.complete(this);
627 }
Zachary Anderson9de77872020-02-27 22:46:23 -0800628 // Catches all exceptions to propagate to the completer.
629 } catch (e, st) { // ignore: avoid_catches_without_on_clauses
John McCutchan3a012b32016-08-17 09:01:04 -0700630 completer.completeError(e, st);
631 }
632 _inProgressReload = null;
Jason Simmons494270f2018-02-20 09:45:32 -0800633 return await completer.future;
John McCutchan3a012b32016-08-17 09:01:04 -0700634 }
635
Todd Volkertd6f61b92017-02-15 06:52:28 -0800636 return await _inProgressReload;
John McCutchan3a012b32016-08-17 09:01:04 -0700637 }
638
639 /// Update [this] using [map] as a source. [map] can be a service reference.
Alexander Aprelev0f3aa502018-02-09 10:33:24 -0800640 void updateFromMap(Map<String, dynamic> map) {
John McCutchan3a012b32016-08-17 09:01:04 -0700641 // Don't allow the type to change on an object update.
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100642 final bool mapIsRef = _hasRef(map['type'] as String);
643 final String mapType = _stripRef(map['type'] as String);
John McCutchan3a012b32016-08-17 09:01:04 -0700644
645 if ((_type != null) && (_type != mapType)) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200646 throw VMServiceObjectLoadError('ServiceObject types must not change',
John McCutchan3a012b32016-08-17 09:01:04 -0700647 map);
648 }
649 _type = mapType;
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100650 _vmType = map.containsKey('_vmType') ? _stripRef(map['_vmType'] as String) : _type;
John McCutchan3a012b32016-08-17 09:01:04 -0700651
652 _canCache = map['fixedId'] == true;
653 if ((_id != null) && (_id != map['id']) && _canCache) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200654 throw VMServiceObjectLoadError('ServiceObject id changed', map);
John McCutchan3a012b32016-08-17 09:01:04 -0700655 }
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100656 _id = map['id'] as String;
John McCutchan3a012b32016-08-17 09:01:04 -0700657
658 // Copy name properties.
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100659 _name = map['name'] as String;
660 _vmName = map.containsKey('_vmName') ? map['_vmName'] as String : _name;
John McCutchan3a012b32016-08-17 09:01:04 -0700661
662 // We have now updated all common properties, let the subclasses update
663 // their specific properties.
664 _update(map, mapIsRef);
665 }
666
667 /// Implemented by subclasses to populate their model.
668 void _update(Map<String, dynamic> map, bool mapIsRef);
669}
670
671class ServiceEvent extends ServiceObject {
Alexandre Ardhuin2ea1d812018-10-04 07:28:07 +0200672 ServiceEvent._empty(ServiceObjectOwner owner) : super._empty(owner);
673
674 String _kind;
675 String get kind => _kind;
676 DateTime _timestamp;
677 DateTime get timestamp => _timestamp;
678 String _extensionKind;
679 String get extensionKind => _extensionKind;
680 Map<String, dynamic> _extensionData;
681 Map<String, dynamic> get extensionData => _extensionData;
682 List<Map<String, dynamic>> _timelineEvents;
683 List<Map<String, dynamic>> get timelineEvents => _timelineEvents;
Jenn Magder2e7d9132019-11-01 14:37:17 -0700684 String _message;
685 String get message => _message;
Alexandre Ardhuin2ea1d812018-10-04 07:28:07 +0200686
687 // The possible 'kind' values.
John McCutchan3a012b32016-08-17 09:01:04 -0700688 static const String kVMUpdate = 'VMUpdate';
689 static const String kIsolateStart = 'IsolateStart';
690 static const String kIsolateRunnable = 'IsolateRunnable';
691 static const String kIsolateExit = 'IsolateExit';
692 static const String kIsolateUpdate = 'IsolateUpdate';
693 static const String kIsolateReload = 'IsolateReload';
694 static const String kIsolateSpawn = 'IsolateSpawn';
695 static const String kServiceExtensionAdded = 'ServiceExtensionAdded';
696 static const String kPauseStart = 'PauseStart';
697 static const String kPauseExit = 'PauseExit';
698 static const String kPauseBreakpoint = 'PauseBreakpoint';
699 static const String kPauseInterrupted = 'PauseInterrupted';
700 static const String kPauseException = 'PauseException';
John McCutchan91128232016-10-20 07:30:24 -0700701 static const String kPausePostRequest = 'PausePostRequest';
John McCutchan3a012b32016-08-17 09:01:04 -0700702 static const String kNone = 'None';
703 static const String kResume = 'Resume';
704 static const String kBreakpointAdded = 'BreakpointAdded';
705 static const String kBreakpointResolved = 'BreakpointResolved';
706 static const String kBreakpointRemoved = 'BreakpointRemoved';
707 static const String kGraph = '_Graph';
708 static const String kGC = 'GC';
709 static const String kInspect = 'Inspect';
710 static const String kDebuggerSettingsUpdate = '_DebuggerSettingsUpdate';
711 static const String kConnectionClosed = 'ConnectionClosed';
712 static const String kLogging = '_Logging';
713 static const String kExtension = 'Extension';
714
John McCutchan3a012b32016-08-17 09:01:04 -0700715 @override
716 void _update(Map<String, dynamic> map, bool mapIsRef) {
717 _loaded = true;
718 _upgradeCollection(map, owner);
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100719 _kind = map['kind'] as String;
John McCutchan3a012b32016-08-17 09:01:04 -0700720 assert(map['isolate'] == null || owner == map['isolate']);
721 _timestamp =
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100722 DateTime.fromMillisecondsSinceEpoch(map['timestamp'] as int);
John McCutchan3a012b32016-08-17 09:01:04 -0700723 if (map['extensionKind'] != null) {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100724 _extensionKind = map['extensionKind'] as String;
725 _extensionData = castStringKeyedMap(map['extensionData']);
John McCutchan3a012b32016-08-17 09:01:04 -0700726 }
liyuqian8e6f4682019-02-26 16:38:59 -0800727 // map['timelineEvents'] is List<dynamic> which can't be assigned to
728 // List<Map<String, dynamic>> directly. Unfortunately, we previously didn't
729 // catch this exception because json_rpc_2 is hiding all these exceptions
730 // on a Stream.
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100731 final List<dynamic> dynamicList = map['timelineEvents'] as List<dynamic>;
liyuqian8e6f4682019-02-26 16:38:59 -0800732 _timelineEvents = dynamicList?.cast<Map<String, dynamic>>();
Jenn Magder2e7d9132019-11-01 14:37:17 -0700733
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100734 final String base64Bytes = map['bytes'] as String;
Jenn Magder2e7d9132019-11-01 14:37:17 -0700735 if (base64Bytes != null) {
736 _message = utf8.decode(base64.decode(base64Bytes)).trim();
737 }
John McCutchan3a012b32016-08-17 09:01:04 -0700738 }
739
740 bool get isPauseEvent {
Alexandre Ardhuin2e80bf12018-02-06 09:00:11 +0100741 return kind == kPauseStart ||
742 kind == kPauseExit ||
743 kind == kPauseBreakpoint ||
744 kind == kPauseInterrupted ||
745 kind == kPauseException ||
746 kind == kPausePostRequest ||
747 kind == kNone;
John McCutchan3a012b32016-08-17 09:01:04 -0700748 }
749}
750
751/// A ServiceObjectOwner is either a [VM] or an [Isolate]. Owners can cache
Greg Spencer0259be92017-11-17 10:05:21 -0800752/// and/or canonicalize service objects received over the wire.
John McCutchan3a012b32016-08-17 09:01:04 -0700753abstract class ServiceObjectOwner extends ServiceObject {
754 ServiceObjectOwner._empty(ServiceObjectOwner owner) : super._empty(owner);
755
756 /// Returns the owning VM.
757 VM get vm => null;
758
759 /// Returns the owning isolate (if any).
760 Isolate get isolate => null;
761
762 /// Returns the vmService connection.
763 VMService get vmService => null;
764
765 /// Builds a [ServiceObject] corresponding to the [id] from [map].
Alexandre Ardhuinc02b6a82018-02-02 23:27:29 +0100766 /// The result may come from the cache. The result will not necessarily
John McCutchan3a012b32016-08-17 09:01:04 -0700767 /// be [loaded].
768 ServiceObject getFromMap(Map<String, dynamic> map);
769}
770
771/// There is only one instance of the VM class. The VM class owns [Isolate]
772/// and [FlutterView] objects.
773class VM extends ServiceObjectOwner {
774 VM._empty(this._vmService) : super._empty(null);
775
776 /// Connection to the VMService.
777 final VMService _vmService;
778 @override
779 VMService get vmService => _vmService;
780
781 @override
782 VM get vm => this;
783
784 @override
Ian Hickson6bfd9bb2018-11-08 20:54:06 -0800785 Future<Map<String, dynamic>> _fetchDirect() => invokeRpcRaw('getVM');
John McCutchan3a012b32016-08-17 09:01:04 -0700786
787 @override
788 void _update(Map<String, dynamic> map, bool mapIsRef) {
Zachary Andersone2340c62019-09-13 14:51:35 -0700789 if (mapIsRef) {
John McCutchan3a012b32016-08-17 09:01:04 -0700790 return;
Zachary Andersone2340c62019-09-13 14:51:35 -0700791 }
John McCutchan3a012b32016-08-17 09:01:04 -0700792
793 // Upgrade the collection. A side effect of this call is that any new
794 // isolates in the map are created and added to the isolate cache.
795 _upgradeCollection(map, this);
796 _loaded = true;
797
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100798 _pid = map['pid'] as int;
Zachary Andersone2340c62019-09-13 14:51:35 -0700799 if (map['_heapAllocatedMemoryUsage'] != null) {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100800 _heapAllocatedMemoryUsage = map['_heapAllocatedMemoryUsage'] as int;
Zachary Andersone2340c62019-09-13 14:51:35 -0700801 }
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100802 _maxRSS = map['_maxRSS'] as int;
803 _embedder = map['_embedder'] as String;
John McCutchan3a012b32016-08-17 09:01:04 -0700804
805 // Remove any isolates which are now dead from the isolate cache.
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100806 _removeDeadIsolates((map['isolates'] as List<dynamic>).cast<Isolate>());
John McCutchan3a012b32016-08-17 09:01:04 -0700807 }
808
Alexandre Ardhuin329e52c2017-03-08 23:57:31 +0100809 final Map<String, ServiceObject> _cache = <String,ServiceObject>{};
810 final Map<String,Isolate> _isolateCache = <String,Isolate>{};
John McCutchan3a012b32016-08-17 09:01:04 -0700811
812 /// The list of live isolates, ordered by isolate start time.
Alexandre Ardhuin329e52c2017-03-08 23:57:31 +0100813 final List<Isolate> isolates = <Isolate>[];
John McCutchan3a012b32016-08-17 09:01:04 -0700814
815 /// The set of live views.
Alexandre Ardhuin329e52c2017-03-08 23:57:31 +0100816 final Map<String, FlutterView> _viewCache = <String, FlutterView>{};
John McCutchan3a012b32016-08-17 09:01:04 -0700817
Jason Simmons67b38712017-04-07 13:41:29 -0700818 /// The pid of the VM's process.
819 int _pid;
Jason Simmons67b38712017-04-07 13:41:29 -0700820 int get pid => _pid;
821
Zachary Anderson9b250cb2017-04-20 15:06:56 -0700822 /// The number of bytes allocated (e.g. by malloc) in the native heap.
823 int _heapAllocatedMemoryUsage;
Alexandre Ardhuin4fa32df2019-05-16 22:25:51 +0200824 int get heapAllocatedMemoryUsage => _heapAllocatedMemoryUsage ?? 0;
Zachary Anderson9b250cb2017-04-20 15:06:56 -0700825
826 /// The peak resident set size for the process.
827 int _maxRSS;
Alexandre Ardhuin4fa32df2019-05-16 22:25:51 +0200828 int get maxRSS => _maxRSS ?? 0;
Zachary Anderson9b250cb2017-04-20 15:06:56 -0700829
Ryan Macnaka0ffe872018-02-26 15:14:09 -0800830 // The embedder's name, Flutter or dart_runner.
831 String _embedder;
832 String get embedder => _embedder;
833 bool get isFlutterEngine => embedder == 'Flutter';
834 bool get isDartRunner => embedder == 'dart_runner';
835
John McCutchan3a012b32016-08-17 09:01:04 -0700836 int _compareIsolates(Isolate a, Isolate b) {
Chris Bracken7a093162017-03-03 17:50:46 -0800837 final DateTime aStart = a.startTime;
838 final DateTime bStart = b.startTime;
John McCutchan3a012b32016-08-17 09:01:04 -0700839 if (aStart == null) {
840 if (bStart == null) {
841 return 0;
842 } else {
843 return 1;
844 }
845 }
846 if (bStart == null) {
847 return -1;
848 }
849 return aStart.compareTo(bStart);
850 }
851
852 void _buildIsolateList() {
Chris Bracken7a093162017-03-03 17:50:46 -0800853 final List<Isolate> isolateList = _isolateCache.values.toList();
John McCutchan3a012b32016-08-17 09:01:04 -0700854 isolateList.sort(_compareIsolates);
855 isolates.clear();
856 isolates.addAll(isolateList);
857 }
858
859 void _removeDeadIsolates(List<Isolate> newIsolates) {
860 // Build a set of new isolates.
Phil Quitslund802eca22019-03-06 11:05:16 -0800861 final Set<String> newIsolateSet = <String>{};
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100862 for (final Isolate iso in newIsolates) {
Alexandre Ardhuin700dc9f2017-10-19 08:16:16 +0200863 newIsolateSet.add(iso.id);
Zachary Andersone2340c62019-09-13 14:51:35 -0700864 }
John McCutchan3a012b32016-08-17 09:01:04 -0700865
866 // Remove any old isolates which no longer exist.
Chris Bracken7a093162017-03-03 17:50:46 -0800867 final List<String> toRemove = <String>[];
John McCutchan3a012b32016-08-17 09:01:04 -0700868 _isolateCache.forEach((String id, _) {
869 if (!newIsolateSet.contains(id)) {
870 toRemove.add(id);
871 }
872 });
Alexandre Ardhuin2166ea52017-03-15 23:09:58 +0100873 toRemove.forEach(_isolateCache.remove);
John McCutchan3a012b32016-08-17 09:01:04 -0700874 _buildIsolateList();
875 }
876
877 @override
878 ServiceObject getFromMap(Map<String, dynamic> map) {
879 if (map == null) {
880 return null;
881 }
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100882 final String type = _stripRef(map['type'] as String);
John McCutchan3a012b32016-08-17 09:01:04 -0700883 if (type == 'VM') {
884 // Update this VM object.
Alexander Aprelev0f3aa502018-02-09 10:33:24 -0800885 updateFromMap(map);
John McCutchan3a012b32016-08-17 09:01:04 -0700886 return this;
887 }
888
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100889 final String mapId = map['id'] as String;
John McCutchan3a012b32016-08-17 09:01:04 -0700890
891 switch (type) {
Alexandre Ardhuinb8731622019-09-24 21:03:37 +0200892 case 'Isolate':
John McCutchan3a012b32016-08-17 09:01:04 -0700893 // Check cache.
894 Isolate isolate = _isolateCache[mapId];
895 if (isolate == null) {
896 // Add new isolate to the cache.
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100897 isolate = ServiceObject._fromMap(this, map) as Isolate;
John McCutchan3a012b32016-08-17 09:01:04 -0700898 _isolateCache[mapId] = isolate;
899 _buildIsolateList();
900
901 // Eagerly load the isolate.
902 isolate.load().catchError((dynamic e, StackTrace stack) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800903 globals.printTrace('Eagerly loading an isolate failed: $e\n$stack');
John McCutchan3a012b32016-08-17 09:01:04 -0700904 });
905 } else {
906 // Existing isolate, update data.
Alexander Aprelev0f3aa502018-02-09 10:33:24 -0800907 isolate.updateFromMap(map);
John McCutchan3a012b32016-08-17 09:01:04 -0700908 }
909 return isolate;
Alexandre Ardhuinb8731622019-09-24 21:03:37 +0200910 case 'FlutterView':
John McCutchan3a012b32016-08-17 09:01:04 -0700911 FlutterView view = _viewCache[mapId];
912 if (view == null) {
913 // Add new view to the cache.
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100914 view = ServiceObject._fromMap(this, map) as FlutterView;
John McCutchan3a012b32016-08-17 09:01:04 -0700915 _viewCache[mapId] = view;
916 } else {
Alexander Aprelev0f3aa502018-02-09 10:33:24 -0800917 view.updateFromMap(map);
John McCutchan3a012b32016-08-17 09:01:04 -0700918 }
919 return view;
John McCutchan3a012b32016-08-17 09:01:04 -0700920 default:
Ryan Macnak315471b2019-10-17 16:56:57 -0700921 // If we don't have a model object for this service object type, as a
922 // fallback return a ServiceMap object.
923 final ServiceObject serviceObject = ServiceMap._empty(owner);
924 // We have now constructed an empty service object, call update to populate it.
925 serviceObject.updateFromMap(map);
926 return serviceObject;
Devon Carew6cdfd862016-06-24 10:26:43 -0700927 }
Devon Carew40c0d6e2016-05-12 18:15:23 -0700928 }
929
Devon Carew67377122017-01-12 14:03:04 -0800930 // This function does not reload the isolate if it's found in the cache.
John McCutchan3a012b32016-08-17 09:01:04 -0700931 Future<Isolate> getIsolate(String isolateId) {
932 if (!loaded) {
933 // Trigger a VM load, then get the isolate. Ignore any errors.
Ian Hickson63aa1392017-01-23 01:04:31 -0800934 return load().then<Isolate>((ServiceObject serviceObject) => getIsolate(isolateId)).catchError((dynamic error) => null);
John McCutchan3a012b32016-08-17 09:01:04 -0700935 }
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200936 return Future<Isolate>.value(_isolateCache[isolateId]);
John McCutchan728e2a52016-08-11 12:56:47 -0700937 }
938
Ian Hickson6bfd9bb2018-11-08 20:54:06 -0800939 static String _truncate(String message, int width, String ellipsis) {
940 assert(ellipsis.length < width);
Zachary Andersone2340c62019-09-13 14:51:35 -0700941 if (message.length <= width) {
Ian Hickson6bfd9bb2018-11-08 20:54:06 -0800942 return message;
Zachary Andersone2340c62019-09-13 14:51:35 -0700943 }
Ian Hickson6bfd9bb2018-11-08 20:54:06 -0800944 return message.substring(0, width - ellipsis.length) + ellipsis;
945 }
946
John McCutchan3a012b32016-08-17 09:01:04 -0700947 /// Invoke the RPC and return the raw response.
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +0100948 Future<Map<String, dynamic>> invokeRpcRaw(
949 String method, {
Alexandre Ardhuin09276be2018-06-05 08:50:40 +0200950 Map<String, dynamic> params = const <String, dynamic>{},
Zachary Anderson22ca3f92019-06-12 11:18:53 -0700951 bool truncateLogs = true,
Ian Hicksondc634e12017-02-02 15:48:35 -0800952 }) async {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800953 globals.printTrace('Sending to VM service: $method($params)');
Ian Hicksondc634e12017-02-02 15:48:35 -0800954 assert(params != null);
Yegor25e6cc62016-12-12 10:58:21 -0800955 try {
Ian Hickson31a96262019-01-19 00:31:05 -0800956 final Map<String, dynamic> result = await _vmService._sendRequest(method, params);
Zachary Anderson22ca3f92019-06-12 11:18:53 -0700957 final String resultString =
958 truncateLogs ? _truncate(result.toString(), 250, '...') : result.toString();
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800959 globals.printTrace('Result: $resultString');
Yegor25e6cc62016-12-12 10:58:21 -0800960 return result;
Todd Volkertd6f61b92017-02-15 06:52:28 -0800961 } on WebSocketChannelException catch (error) {
962 throwToolExit('Error connecting to observatory: $error');
963 return null;
Ian Hickson8fd20b52017-11-01 15:04:43 -0700964 } on rpc.RpcException catch (error) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800965 globals.printError('Error ${error.code} received from application: ${error.message}');
966 globals.printTrace('${error.data}');
Alexander Aprelevea30c952018-04-13 17:02:14 -0700967 rethrow;
Yegor25e6cc62016-12-12 10:58:21 -0800968 }
John McCutchan81b4e822016-08-05 12:04:33 -0700969 }
970
Ian Hicksondc634e12017-02-02 15:48:35 -0800971 /// Invoke the RPC and return a [ServiceObject] response.
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +0100972 Future<T> invokeRpc<T extends ServiceObject>(
973 String method, {
Alexandre Ardhuin09276be2018-06-05 08:50:40 +0200974 Map<String, dynamic> params = const <String, dynamic>{},
Zachary Anderson22ca3f92019-06-12 11:18:53 -0700975 bool truncateLogs = true,
Ian Hicksondc634e12017-02-02 15:48:35 -0800976 }) async {
Chris Bracken7a093162017-03-03 17:50:46 -0800977 final Map<String, dynamic> response = await invokeRpcRaw(
Ian Hicksondc634e12017-02-02 15:48:35 -0800978 method,
979 params: params,
Zachary Anderson22ca3f92019-06-12 11:18:53 -0700980 truncateLogs: truncateLogs,
Ian Hicksondc634e12017-02-02 15:48:35 -0800981 );
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100982 final T serviceObject = ServiceObject._fromMap(this, response) as T;
John McCutchan3a012b32016-08-17 09:01:04 -0700983 if ((serviceObject != null) && (serviceObject._canCache)) {
Chris Bracken7a093162017-03-03 17:50:46 -0800984 final String serviceObjectId = serviceObject.id;
John McCutchan3a012b32016-08-17 09:01:04 -0700985 _cache.putIfAbsent(serviceObjectId, () => serviceObject);
986 }
987 return serviceObject;
John McCutchan81b4e822016-08-05 12:04:33 -0700988 }
989
John McCutchan3a012b32016-08-17 09:01:04 -0700990 /// Create a new development file system on the device.
Ian Hicksondc634e12017-02-02 15:48:35 -0800991 Future<Map<String, dynamic>> createDevFS(String fsName) {
Alexandre Ardhuinbfa1d252019-03-23 00:02:21 +0100992 return invokeRpcRaw('_createDevFS', params: <String, dynamic>{'fsName': fsName});
Devon Carew40c0d6e2016-05-12 18:15:23 -0700993 }
994
John McCutchan3a012b32016-08-17 09:01:04 -0700995 /// List the development file system son the device.
996 Future<List<String>> listDevFS() async {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100997 return (await invokeRpcRaw('_listDevFS'))['fsNames'] as List<String>;
Devon Carewb2938f402016-06-10 15:08:12 -0700998 }
999
1000 // Write one file into a file system.
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +01001001 Future<Map<String, dynamic>> writeDevFSFile(
1002 String fsName, {
Alexandre Ardhuin2de61a02017-03-31 18:34:13 +02001003 @required String path,
Alexandre Ardhuin387f8852019-03-01 08:17:55 +01001004 @required List<int> fileContents,
Devon Carewb2938f402016-06-10 15:08:12 -07001005 }) {
1006 assert(path != null);
1007 assert(fileContents != null);
Ian Hicksondc634e12017-02-02 15:48:35 -08001008 return invokeRpcRaw(
1009 '_writeDevFSFile',
1010 params: <String, dynamic>{
1011 'fsName': fsName,
1012 'path': path,
Jason Simmons466d1542018-03-12 11:06:32 -07001013 'fileContents': base64.encode(fileContents),
Ian Hicksondc634e12017-02-02 15:48:35 -08001014 },
1015 );
Devon Carewb2938f402016-06-10 15:08:12 -07001016 }
1017
John McCutchan0de69162016-07-20 12:59:30 -07001018 // Read one file from a file system.
Ian Hicksondc634e12017-02-02 15:48:35 -08001019 Future<List<int>> readDevFSFile(String fsName, String path) async {
Chris Bracken7a093162017-03-03 17:50:46 -08001020 final Map<String, dynamic> response = await invokeRpcRaw(
Ian Hicksondc634e12017-02-02 15:48:35 -08001021 '_readDevFSFile',
1022 params: <String, dynamic>{
1023 'fsName': fsName,
1024 'path': path,
1025 },
1026 );
Alexandre Ardhuinadc73512019-11-19 07:57:42 +01001027 return base64.decode(response['fileContents'] as String);
Devon Carewb2938f402016-06-10 15:08:12 -07001028 }
1029
Devon Carewb2938f402016-06-10 15:08:12 -07001030 /// The complete list of a file system.
Ian Hicksondc634e12017-02-02 15:48:35 -08001031 Future<List<String>> listDevFSFiles(String fsName) async {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +01001032 return (await invokeRpcRaw('_listDevFSFiles', params: <String, dynamic>{'fsName': fsName}))['files'] as List<String>;
Devon Carewb2938f402016-06-10 15:08:12 -07001033 }
1034
1035 /// Delete an existing file system.
John McCutchan3a012b32016-08-17 09:01:04 -07001036 Future<Map<String, dynamic>> deleteDevFS(String fsName) {
Alexandre Ardhuina6af4222019-03-20 23:23:31 +01001037 return invokeRpcRaw('_deleteDevFS', params: <String, dynamic>{'fsName': fsName});
John McCutchan3a012b32016-08-17 09:01:04 -07001038 }
1039
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +01001040 Future<ServiceMap> runInView(
1041 String viewId,
1042 Uri main,
1043 Uri packages,
1044 Uri assetsDirectory,
1045 ) {
Devon Carew9d9836f2018-07-09 12:22:46 -07001046 return invokeRpc<ServiceMap>('_flutter.runInView',
Alexandre Ardhuinbfa1d252019-03-23 00:02:21 +01001047 params: <String, dynamic>{
Devon Carew9d9836f2018-07-09 12:22:46 -07001048 'viewId': viewId,
Danny Tuppenyd89604d2018-10-02 18:31:55 +01001049 'mainScript': main.toString(),
1050 'packagesFile': packages.toString(),
Alexandre Ardhuin387f8852019-03-01 08:17:55 +01001051 'assetDirectory': assetsDirectory.toString(),
Devon Carew9d9836f2018-07-09 12:22:46 -07001052 });
John McCutchan3a012b32016-08-17 09:01:04 -07001053 }
1054
1055 Future<Map<String, dynamic>> clearVMTimeline() {
Sivaf4965952019-07-02 16:10:04 -07001056 return invokeRpcRaw('clearVMTimeline');
John McCutchan3a012b32016-08-17 09:01:04 -07001057 }
1058
Ian Hicksondc634e12017-02-02 15:48:35 -08001059 Future<Map<String, dynamic>> setVMTimelineFlags(List<String> recordedStreams) {
John McCutchan3a012b32016-08-17 09:01:04 -07001060 assert(recordedStreams != null);
Ian Hicksondc634e12017-02-02 15:48:35 -08001061 return invokeRpcRaw(
Sivaf4965952019-07-02 16:10:04 -07001062 'setVMTimelineFlags',
Ian Hicksondc634e12017-02-02 15:48:35 -08001063 params: <String, dynamic>{
1064 'recordedStreams': recordedStreams,
1065 },
1066 );
John McCutchan3a012b32016-08-17 09:01:04 -07001067 }
1068
1069 Future<Map<String, dynamic>> getVMTimeline() {
Sivaf4965952019-07-02 16:10:04 -07001070 return invokeRpcRaw('getVMTimeline');
John McCutchan3a012b32016-08-17 09:01:04 -07001071 }
1072
Ian Hickson6bfd9bb2018-11-08 20:54:06 -08001073 Future<void> refreshViews({ bool waitForViews = false }) async {
1074 assert(waitForViews != null);
1075 assert(loaded);
Zachary Andersone2340c62019-09-13 14:51:35 -07001076 if (!isFlutterEngine) {
Ian Hickson6bfd9bb2018-11-08 20:54:06 -08001077 return;
Zachary Andersone2340c62019-09-13 14:51:35 -07001078 }
Ian Hickson6bfd9bb2018-11-08 20:54:06 -08001079 int failCount = 0;
1080 while (true) {
1081 _viewCache.clear();
1082 // When the future returned by invokeRpc() below returns,
1083 // the _viewCache will have been updated.
1084 // This message updates all the views of every isolate.
Zachary Anderson22ca3f92019-06-12 11:18:53 -07001085 await vmService.vm.invokeRpc<ServiceObject>(
1086 '_flutter.listViews', truncateLogs: false);
Zachary Andersone2340c62019-09-13 14:51:35 -07001087 if (_viewCache.values.isNotEmpty || !waitForViews) {
Ian Hickson6bfd9bb2018-11-08 20:54:06 -08001088 return;
Zachary Andersone2340c62019-09-13 14:51:35 -07001089 }
Ian Hickson6bfd9bb2018-11-08 20:54:06 -08001090 failCount += 1;
Zachary Andersone2340c62019-09-13 14:51:35 -07001091 if (failCount == 5) { // waited 200ms
Jonah Williamsee7a37f2020-01-06 11:04:20 -08001092 globals.printStatus('Flutter is taking longer than expected to report its views. Still trying...');
Zachary Andersone2340c62019-09-13 14:51:35 -07001093 }
Ian Hickson6bfd9bb2018-11-08 20:54:06 -08001094 await Future<void>.delayed(const Duration(milliseconds: 50));
1095 await reload();
1096 }
John McCutchan3a012b32016-08-17 09:01:04 -07001097 }
1098
Zachary Anderson9a068c62017-03-16 14:35:53 -07001099 Iterable<FlutterView> get views => _viewCache.values;
1100
1101 FlutterView get firstView {
Jason Simmons59cacd72017-02-01 11:54:27 -08001102 return _viewCache.values.isEmpty ? null : _viewCache.values.first;
John McCutchan3a012b32016-08-17 09:01:04 -07001103 }
Zachary Anderson9a068c62017-03-16 14:35:53 -07001104
Zachary Anderson9fdd4f42017-04-14 11:33:47 -07001105 List<FlutterView> allViewsWithName(String isolateFilter) {
Zachary Andersone2340c62019-09-13 14:51:35 -07001106 if (_viewCache.values.isEmpty) {
Zachary Anderson9a068c62017-03-16 14:35:53 -07001107 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -07001108 }
Zachary Anderson9fdd4f42017-04-14 11:33:47 -07001109 return _viewCache.values.where(
1110 (FlutterView v) => v.uiIsolate.name.contains(isolateFilter)
1111 ).toList();
Zachary Anderson9a068c62017-03-16 14:35:53 -07001112 }
John McCutchan3a012b32016-08-17 09:01:04 -07001113}
1114
Zachary Andersona0369802017-04-14 21:18:48 -07001115class HeapSpace extends ServiceObject {
1116 HeapSpace._empty(ServiceObjectOwner owner) : super._empty(owner);
1117
1118 int _used = 0;
1119 int _capacity = 0;
1120 int _external = 0;
1121 int _collections = 0;
1122 double _totalCollectionTimeInSeconds = 0.0;
1123 double _averageCollectionPeriodInMillis = 0.0;
1124
1125 int get used => _used;
1126 int get capacity => _capacity;
1127 int get external => _external;
1128
1129 Duration get avgCollectionTime {
1130 final double mcs = _totalCollectionTimeInSeconds *
Jason Simmons466d1542018-03-12 11:06:32 -07001131 Duration.microsecondsPerSecond /
Zachary Andersona0369802017-04-14 21:18:48 -07001132 math.max(_collections, 1);
Alexandre Ardhuind927c932018-09-12 08:29:29 +02001133 return Duration(microseconds: mcs.ceil());
Zachary Andersona0369802017-04-14 21:18:48 -07001134 }
1135
1136 Duration get avgCollectionPeriod {
1137 final double mcs = _averageCollectionPeriodInMillis *
Jason Simmons466d1542018-03-12 11:06:32 -07001138 Duration.microsecondsPerMillisecond;
Alexandre Ardhuind927c932018-09-12 08:29:29 +02001139 return Duration(microseconds: mcs.ceil());
Zachary Andersona0369802017-04-14 21:18:48 -07001140 }
1141
1142 @override
1143 void _update(Map<String, dynamic> map, bool mapIsRef) {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +01001144 _used = map['used'] as int;
1145 _capacity = map['capacity'] as int;
1146 _external = map['external'] as int;
1147 _collections = map['collections'] as int;
1148 _totalCollectionTimeInSeconds = map['time'] as double;
1149 _averageCollectionPeriodInMillis = map['avgCollectionPeriodMillis'] as double;
Zachary Andersona0369802017-04-14 21:18:48 -07001150 }
1151}
1152
John McCutchan3a012b32016-08-17 09:01:04 -07001153/// An isolate running inside the VM. Instances of the Isolate class are always
1154/// canonicalized.
1155class Isolate extends ServiceObjectOwner {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +01001156 Isolate._empty(VM owner) : super._empty(owner);
John McCutchan3a012b32016-08-17 09:01:04 -07001157
1158 @override
Alexandre Ardhuinadc73512019-11-19 07:57:42 +01001159 VM get vm => owner as VM;
John McCutchan3a012b32016-08-17 09:01:04 -07001160
1161 @override
1162 VMService get vmService => vm.vmService;
1163
1164 @override
1165 Isolate get isolate => this;
1166
1167 DateTime startTime;
Devon Carew7fb66462017-05-02 16:09:57 -07001168
1169 /// The last pause event delivered to the isolate. If the isolate is running,
1170 /// this will be a resume event.
John McCutchan91128232016-10-20 07:30:24 -07001171 ServiceEvent pauseEvent;
John McCutchan3a012b32016-08-17 09:01:04 -07001172
Alexandre Ardhuin329e52c2017-03-08 23:57:31 +01001173 final Map<String, ServiceObject> _cache = <String, ServiceObject>{};
John McCutchan3a012b32016-08-17 09:01:04 -07001174
Zachary Andersona0369802017-04-14 21:18:48 -07001175 HeapSpace _newSpace;
1176 HeapSpace _oldSpace;
1177
1178 HeapSpace get newSpace => _newSpace;
1179 HeapSpace get oldSpace => _oldSpace;
1180
John McCutchan3a012b32016-08-17 09:01:04 -07001181 @override
1182 ServiceObject getFromMap(Map<String, dynamic> map) {
Zachary Andersone2340c62019-09-13 14:51:35 -07001183 if (map == null) {
John McCutchan3a012b32016-08-17 09:01:04 -07001184 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -07001185 }
Alexandre Ardhuinadc73512019-11-19 07:57:42 +01001186 final String mapType = _stripRef(map['type'] as String);
John McCutchan3a012b32016-08-17 09:01:04 -07001187 if (mapType == 'Isolate') {
1188 // There are sometimes isolate refs in ServiceEvents.
1189 return vm.getFromMap(map);
1190 }
1191
Alexandre Ardhuinadc73512019-11-19 07:57:42 +01001192 final String mapId = map['id'] as String;
John McCutchan3a012b32016-08-17 09:01:04 -07001193 ServiceObject serviceObject = (mapId != null) ? _cache[mapId] : null;
1194 if (serviceObject != null) {
Alexander Aprelev0f3aa502018-02-09 10:33:24 -08001195 serviceObject.updateFromMap(map);
John McCutchan3a012b32016-08-17 09:01:04 -07001196 return serviceObject;
1197 }
1198 // Build the object from the map directly.
Alexandre Ardhuind927c932018-09-12 08:29:29 +02001199 serviceObject = ServiceObject._fromMap(this, map);
Zachary Andersone2340c62019-09-13 14:51:35 -07001200 if ((serviceObject != null) && serviceObject.canCache) {
John McCutchan3a012b32016-08-17 09:01:04 -07001201 _cache[mapId] = serviceObject;
Zachary Andersone2340c62019-09-13 14:51:35 -07001202 }
John McCutchan3a012b32016-08-17 09:01:04 -07001203 return serviceObject;
1204 }
1205
1206 @override
Ian Hickson6bfd9bb2018-11-08 20:54:06 -08001207 Future<Map<String, dynamic>> _fetchDirect() => invokeRpcRaw('getIsolate');
John McCutchan3a012b32016-08-17 09:01:04 -07001208
1209 /// Invoke the RPC and return the raw response.
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +01001210 Future<Map<String, dynamic>> invokeRpcRaw(
1211 String method, {
Ian Hicksondc634e12017-02-02 15:48:35 -08001212 Map<String, dynamic> params,
Ian Hicksondc634e12017-02-02 15:48:35 -08001213 }) {
John McCutchan3a012b32016-08-17 09:01:04 -07001214 // Inject the 'isolateId' parameter.
1215 if (params == null) {
1216 params = <String, dynamic>{
Alexandre Ardhuin387f8852019-03-01 08:17:55 +01001217 'isolateId': id,
John McCutchan3a012b32016-08-17 09:01:04 -07001218 };
1219 } else {
1220 params['isolateId'] = id;
1221 }
Ian Hickson31a96262019-01-19 00:31:05 -08001222 return vm.invokeRpcRaw(method, params: params);
John McCutchan3a012b32016-08-17 09:01:04 -07001223 }
1224
1225 /// Invoke the RPC and return a ServiceObject response.
Ian Hicksondc634e12017-02-02 15:48:35 -08001226 Future<ServiceObject> invokeRpc(String method, Map<String, dynamic> params) async {
1227 return getFromMap(await invokeRpcRaw(method, params: params));
John McCutchan3a012b32016-08-17 09:01:04 -07001228 }
1229
Zachary Andersona0369802017-04-14 21:18:48 -07001230 void _updateHeaps(Map<String, dynamic> map, bool mapIsRef) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +02001231 _newSpace ??= HeapSpace._empty(this);
Alexandre Ardhuinadc73512019-11-19 07:57:42 +01001232 _newSpace._update(castStringKeyedMap(map['new']), mapIsRef);
Alexandre Ardhuind927c932018-09-12 08:29:29 +02001233 _oldSpace ??= HeapSpace._empty(this);
Alexandre Ardhuinadc73512019-11-19 07:57:42 +01001234 _oldSpace._update(castStringKeyedMap(map['old']), mapIsRef);
Zachary Andersona0369802017-04-14 21:18:48 -07001235 }
1236
John McCutchan3a012b32016-08-17 09:01:04 -07001237 @override
1238 void _update(Map<String, dynamic> map, bool mapIsRef) {
Zachary Andersone2340c62019-09-13 14:51:35 -07001239 if (mapIsRef) {
John McCutchan3a012b32016-08-17 09:01:04 -07001240 return;
Zachary Andersone2340c62019-09-13 14:51:35 -07001241 }
John McCutchan3a012b32016-08-17 09:01:04 -07001242 _loaded = true;
1243
Alexandre Ardhuinadc73512019-11-19 07:57:42 +01001244 final int startTimeMillis = map['startTime'] as int;
Alexandre Ardhuind927c932018-09-12 08:29:29 +02001245 startTime = DateTime.fromMillisecondsSinceEpoch(startTimeMillis);
John McCutchan3a012b32016-08-17 09:01:04 -07001246
John McCutchan3a012b32016-08-17 09:01:04 -07001247 _upgradeCollection(map, this);
John McCutchan91128232016-10-20 07:30:24 -07001248
Alexandre Ardhuinadc73512019-11-19 07:57:42 +01001249 pauseEvent = map['pauseEvent'] as ServiceEvent;
Zachary Andersona0369802017-04-14 21:18:48 -07001250
Alexandre Ardhuinadc73512019-11-19 07:57:42 +01001251 _updateHeaps(castStringKeyedMap(map['_heaps']), mapIsRef);
John McCutchan3a012b32016-08-17 09:01:04 -07001252 }
1253
Alexandre Ardhuin10f721c2018-01-25 19:28:22 +01001254 static const int kIsolateReloadBarred = 1005;
John McCutchan852a00a2016-08-25 14:23:13 -07001255
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +01001256 Future<Map<String, dynamic>> reloadSources({
1257 bool pause = false,
1258 Uri rootLibUri,
1259 Uri packagesUri,
1260 }) async {
John McCutchan3a012b32016-08-17 09:01:04 -07001261 try {
Chris Bracken7a093162017-03-03 17:50:46 -08001262 final Map<String, dynamic> arguments = <String, dynamic>{
Alexandre Ardhuin387f8852019-03-01 08:17:55 +01001263 'pause': pause,
John McCutchan26432eb2016-11-22 14:16:20 -08001264 };
Michael Goderbauer17057bb2017-03-01 10:11:56 -08001265 if (rootLibUri != null) {
Alexander Apreleve4b7e872018-08-30 12:08:23 -07001266 arguments['rootLibUri'] = rootLibUri.toString();
John McCutchan26432eb2016-11-22 14:16:20 -08001267 }
Michael Goderbauer17057bb2017-03-01 10:11:56 -08001268 if (packagesUri != null) {
Alexander Apreleve4b7e872018-08-30 12:08:23 -07001269 arguments['packagesUri'] = packagesUri.toString();
John McCutchan26432eb2016-11-22 14:16:20 -08001270 }
Chris Bracken7a093162017-03-03 17:50:46 -08001271 final Map<String, dynamic> response = await invokeRpcRaw('_reloadSources', params: arguments);
John McCutchan3a012b32016-08-17 09:01:04 -07001272 return response;
Alexandre Ardhuin3c379aa2018-02-05 22:20:21 +01001273 } on rpc.RpcException catch (e) {
Jonah Williams8e6205f2019-08-07 17:00:36 -07001274 return Future<Map<String, dynamic>>.value(<String, dynamic>{
John McCutchan852a00a2016-08-25 14:23:13 -07001275 'code': e.code,
1276 'message': e.message,
1277 'data': e.data,
1278 });
John McCutchan3a012b32016-08-17 09:01:04 -07001279 }
Devon Carewb2938f402016-06-10 15:08:12 -07001280 }
1281
Ryan Macnak07a4b4c2017-10-12 15:58:40 -07001282 Future<Map<String, dynamic>> getObject(Map<String, dynamic> objectRef) {
1283 return invokeRpcRaw('getObject',
1284 params: <String, dynamic>{'objectId': objectRef['id']});
1285 }
1286
John McCutchan2cf50ce2017-04-18 13:27:53 -07001287 /// Resumes the isolate.
1288 Future<Map<String, dynamic>> resume() {
1289 return invokeRpcRaw('resume');
1290 }
1291
Devon Carew40c0d6e2016-05-12 18:15:23 -07001292 // Flutter extension methods.
1293
John McCutchan24ab8372016-09-15 13:18:32 -07001294 // Invoke a flutter extension method, if the flutter extension is not
1295 // available, returns null.
1296 Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw(
Ian Hicksondc634e12017-02-02 15:48:35 -08001297 String method, {
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +01001298 Map<String, dynamic> params,
1299 }) async {
Ian Hickson31a96262019-01-19 00:31:05 -08001300 try {
1301 return await invokeRpcRaw(method, params: params);
1302 } on rpc.RpcException catch (e) {
1303 // If an application is not using the framework
Zachary Andersone2340c62019-09-13 14:51:35 -07001304 if (e.code == rpc_error_code.METHOD_NOT_FOUND) {
Ian Hickson31a96262019-01-19 00:31:05 -08001305 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -07001306 }
Ian Hickson31a96262019-01-19 00:31:05 -08001307 rethrow;
1308 }
John McCutchan24ab8372016-09-15 13:18:32 -07001309 }
1310
John McCutchan3a012b32016-08-17 09:01:04 -07001311 Future<Map<String, dynamic>> flutterDebugDumpApp() {
Ian Hickson31a96262019-01-19 00:31:05 -08001312 return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpApp');
Devon Carewec752d82016-07-04 11:21:56 -07001313 }
1314
John McCutchan3a012b32016-08-17 09:01:04 -07001315 Future<Map<String, dynamic>> flutterDebugDumpRenderTree() {
Ian Hickson31a96262019-01-19 00:31:05 -08001316 return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpRenderTree');
Devon Carewec752d82016-07-04 11:21:56 -07001317 }
1318
Ian Hicksondb84df22017-05-12 15:51:58 -07001319 Future<Map<String, dynamic>> flutterDebugDumpLayerTree() {
Ian Hickson31a96262019-01-19 00:31:05 -08001320 return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpLayerTree');
Ian Hicksondb84df22017-05-12 15:51:58 -07001321 }
1322
Yegord3540962018-04-23 14:23:49 -07001323 Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInTraversalOrder() {
Ian Hickson31a96262019-01-19 00:31:05 -08001324 return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInTraversalOrder');
Michael Goderbauer8ecf19d2017-08-28 15:49:16 -07001325 }
1326
1327 Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInInverseHitTestOrder() {
Ian Hickson31a96262019-01-19 00:31:05 -08001328 return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder');
Ian Hicksondb84df22017-05-12 15:51:58 -07001329 }
1330
Ian Hicksone1adc522017-07-19 12:57:22 -07001331 Future<Map<String, dynamic>> _flutterToggle(String name) async {
1332 Map<String, dynamic> state = await invokeFlutterExtensionRpcRaw('ext.flutter.$name');
Ian Hicksonf17e3f42017-02-08 14:24:25 -08001333 if (state != null && state.containsKey('enabled') && state['enabled'] is String) {
Ian Hicksondc634e12017-02-02 15:48:35 -08001334 state = await invokeFlutterExtensionRpcRaw(
Ian Hicksone1adc522017-07-19 12:57:22 -07001335 'ext.flutter.$name',
Alexandre Ardhuina6af4222019-03-20 23:23:31 +01001336 params: <String, dynamic>{'enabled': state['enabled'] == 'true' ? 'false' : 'true'},
Ian Hicksondc634e12017-02-02 15:48:35 -08001337 );
1338 }
Ian Hickson27cceff2016-11-27 23:50:20 -08001339 return state;
1340 }
1341
Ian Hicksone1adc522017-07-19 12:57:22 -07001342 Future<Map<String, dynamic>> flutterToggleDebugPaintSizeEnabled() => _flutterToggle('debugPaint');
1343
Dan Fieldd2790bd2019-04-10 14:57:46 -07001344 Future<Map<String, dynamic>> flutterToggleDebugCheckElevationsEnabled() => _flutterToggle('debugCheckElevationsEnabled');
1345
Ian Hicksone1adc522017-07-19 12:57:22 -07001346 Future<Map<String, dynamic>> flutterTogglePerformanceOverlayOverride() => _flutterToggle('showPerformanceOverlay');
1347
Jacob Richmanab9ba3f2018-04-16 10:04:40 -07001348 Future<Map<String, dynamic>> flutterToggleWidgetInspector() => _flutterToggle('inspector.show');
Jacob Richman5462ddb2017-08-21 16:17:54 -07001349
Jonah Williamscadde242019-04-11 12:44:33 -07001350 Future<Map<String, dynamic>> flutterToggleProfileWidgetBuilds() => _flutterToggle('profileWidgetBuilds');
1351
Ian Hickson31a96262019-01-19 00:31:05 -08001352 Future<Map<String, dynamic>> flutterDebugAllowBanner(bool show) {
1353 return invokeFlutterExtensionRpcRaw(
Ian Hicksondc634e12017-02-02 15:48:35 -08001354 'ext.flutter.debugAllowBanner',
Alexandre Ardhuina6af4222019-03-20 23:23:31 +01001355 params: <String, dynamic>{'enabled': show ? 'true' : 'false'},
Ian Hicksondc634e12017-02-02 15:48:35 -08001356 );
Ian Hickson5f387732017-02-01 22:47:53 -08001357 }
1358
Alexander Aprelev5fc6b872018-10-29 18:51:57 -07001359 Future<Map<String, dynamic>> flutterReassemble() {
Ian Hickson31a96262019-01-19 00:31:05 -08001360 return invokeFlutterExtensionRpcRaw('ext.flutter.reassemble');
1361 }
1362
Jonah Williams81aa2712019-12-09 21:31:34 -08001363 Future<Map<String, dynamic>> flutterFastReassemble(String classId) {
1364 return invokeFlutterExtensionRpcRaw('ext.flutter.fastReassemble', params: <String, Object>{
1365 'class': classId,
1366 });
1367 }
1368
Ian Hickson31a96262019-01-19 00:31:05 -08001369 Future<bool> flutterAlreadyPaintedFirstUsefulFrame() async {
liyuqiane77237d2019-07-31 11:01:52 -07001370 final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw('ext.flutter.didSendFirstFrameRasterizedEvent');
liyuqian8e6f4682019-02-26 16:38:59 -08001371 // result might be null when the service extension is not initialized
1372 return result != null && result['enabled'] == 'true';
John McCutchan95433662016-08-09 11:27:12 -07001373 }
1374
Alexander Aprelev5fc6b872018-10-29 18:51:57 -07001375 Future<Map<String, dynamic>> uiWindowScheduleFrame() {
1376 return invokeFlutterExtensionRpcRaw('ext.ui.window.scheduleFrame');
John McCutchan24ab8372016-09-15 13:18:32 -07001377 }
1378
Alexander Aprelev5fc6b872018-10-29 18:51:57 -07001379 Future<Map<String, dynamic>> flutterEvictAsset(String assetPath) {
Ian Hickson31a96262019-01-19 00:31:05 -08001380 return invokeFlutterExtensionRpcRaw(
1381 'ext.flutter.evict',
Ian Hicksondc634e12017-02-02 15:48:35 -08001382 params: <String, dynamic>{
1383 'value': assetPath,
Alexandre Ardhuin387f8852019-03-01 08:17:55 +01001384 },
John McCutchan24ab8372016-09-15 13:18:32 -07001385 );
1386 }
1387
Stanislav Baranoveb7a59b2018-12-20 16:07:36 -08001388 Future<List<int>> flutterDebugSaveCompilationTrace() async {
Stanislav Baranovc5251cd2018-12-14 16:09:17 -08001389 final Map<String, dynamic> result =
Stanislav Baranoveb7a59b2018-12-20 16:07:36 -08001390 await invokeFlutterExtensionRpcRaw('ext.flutter.saveCompilationTrace');
Zachary Andersone2340c62019-09-13 14:51:35 -07001391 if (result != null && result['value'] is List<dynamic>) {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +01001392 return (result['value'] as List<dynamic>).cast<int>();
Zachary Andersone2340c62019-09-13 14:51:35 -07001393 }
Stanislav Baranovc5251cd2018-12-14 16:09:17 -08001394 return null;
1395 }
1396
John McCutchan24ab8372016-09-15 13:18:32 -07001397 // Application control extension methods.
Alexander Aprelev5fc6b872018-10-29 18:51:57 -07001398 Future<Map<String, dynamic>> flutterExit() {
Ian Hickson31a96262019-01-19 00:31:05 -08001399 return invokeFlutterExtensionRpcRaw('ext.flutter.exit');
John McCutchan3a012b32016-08-17 09:01:04 -07001400 }
Ian Hicksonf9c2d7d2017-02-17 17:47:49 -08001401
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +01001402 Future<String> flutterPlatformOverride([ String platform ]) async {
Devon Carew9d9836f2018-07-09 12:22:46 -07001403 final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw(
Ian Hicksonf9c2d7d2017-02-17 17:47:49 -08001404 'ext.flutter.platformOverride',
Alexandre Ardhuina6af4222019-03-20 23:23:31 +01001405 params: platform != null ? <String, dynamic>{'value': platform} : <String, String>{},
Ian Hicksonf9c2d7d2017-02-17 17:47:49 -08001406 );
Zachary Andersone2340c62019-09-13 14:51:35 -07001407 if (result != null && result['value'] is String) {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +01001408 return result['value'] as String;
Zachary Andersone2340c62019-09-13 14:51:35 -07001409 }
Ian Hicksonf9c2d7d2017-02-17 17:47:49 -08001410 return 'unknown';
1411 }
Devon Carew990dae82017-07-27 11:47:33 -07001412
1413 @override
1414 String toString() => 'Isolate $id';
John McCutchan3a012b32016-08-17 09:01:04 -07001415}
1416
1417class ServiceMap extends ServiceObject implements Map<String, dynamic> {
1418 ServiceMap._empty(ServiceObjectOwner owner) : super._empty(owner);
1419
Alexandre Ardhuin329e52c2017-03-08 23:57:31 +01001420 final Map<String, dynamic> _map = <String, dynamic>{};
John McCutchan3a012b32016-08-17 09:01:04 -07001421
1422 @override
1423 void _update(Map<String, dynamic> map, bool mapIsRef) {
1424 _loaded = !mapIsRef;
1425 _upgradeCollection(map, owner);
1426 _map.clear();
1427 _map.addAll(map);
Devon Carew40c0d6e2016-05-12 18:15:23 -07001428 }
Devon Carewec751772016-05-26 15:26:14 -07001429
John McCutchan3a012b32016-08-17 09:01:04 -07001430 // Forward Map interface calls.
1431 @override
1432 void addAll(Map<String, dynamic> other) => _map.addAll(other);
1433 @override
1434 void clear() => _map.clear();
1435 @override
1436 bool containsValue(dynamic v) => _map.containsValue(v);
1437 @override
Adam Barth2c21d792016-10-07 11:27:54 -07001438 bool containsKey(Object k) => _map.containsKey(k);
John McCutchan3a012b32016-08-17 09:01:04 -07001439 @override
Jacob Richman53fc96d2017-02-06 08:55:09 -08001440 void forEach(void f(String key, dynamic value)) => _map.forEach(f);
John McCutchan3a012b32016-08-17 09:01:04 -07001441 @override
Jacob Richman53fc96d2017-02-06 08:55:09 -08001442 dynamic putIfAbsent(String key, dynamic ifAbsent()) => _map.putIfAbsent(key, ifAbsent);
John McCutchan3a012b32016-08-17 09:01:04 -07001443 @override
Adam Barth2c21d792016-10-07 11:27:54 -07001444 void remove(Object key) => _map.remove(key);
John McCutchan3a012b32016-08-17 09:01:04 -07001445 @override
Adam Barth2c21d792016-10-07 11:27:54 -07001446 dynamic operator [](Object k) => _map[k];
John McCutchan3a012b32016-08-17 09:01:04 -07001447 @override
1448 void operator []=(String k, dynamic v) => _map[k] = v;
1449 @override
1450 bool get isEmpty => _map.isEmpty;
1451 @override
1452 bool get isNotEmpty => _map.isNotEmpty;
1453 @override
1454 Iterable<String> get keys => _map.keys;
1455 @override
1456 Iterable<dynamic> get values => _map.values;
1457 @override
1458 int get length => _map.length;
1459 @override
1460 String toString() => _map.toString();
Alexander Aprelev0f3aa502018-02-09 10:33:24 -08001461 @override
1462 void addEntries(Iterable<MapEntry<String, dynamic>> entries) => _map.addEntries(entries);
1463 @override
1464 Map<RK, RV> cast<RK, RV>() => _map.cast<RK, RV>();
1465 @override
1466 void removeWhere(bool test(String key, dynamic value)) => _map.removeWhere(test);
1467 @override
Alexandre Ardhuinf62afdc2018-10-01 21:29:08 +02001468 Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> transform(String key, dynamic value)) => _map.map<K2, V2>(transform);
Alexander Aprelev0f3aa502018-02-09 10:33:24 -08001469 @override
1470 Iterable<MapEntry<String, dynamic>> get entries => _map.entries;
1471 @override
1472 void updateAll(dynamic update(String key, dynamic value)) => _map.updateAll(update);
Michael Goderbauer6d20ff22019-01-30 08:56:12 -08001473 Map<RK, RV> retype<RK, RV>() => _map.cast<RK, RV>();
Alexander Aprelev0f3aa502018-02-09 10:33:24 -08001474 @override
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +01001475 dynamic update(String key, dynamic update(dynamic value), { dynamic ifAbsent() }) => _map.update(key, update, ifAbsent: ifAbsent);
John McCutchan3a012b32016-08-17 09:01:04 -07001476}
Devon Carewec751772016-05-26 15:26:14 -07001477
Mikkel Nygaard Ravn9b3f50b2018-07-04 11:07:14 +02001478/// Peered to an Android/iOS FlutterView widget on a device.
John McCutchan3a012b32016-08-17 09:01:04 -07001479class FlutterView extends ServiceObject {
1480 FlutterView._empty(ServiceObjectOwner owner) : super._empty(owner);
1481
1482 Isolate _uiIsolate;
1483 Isolate get uiIsolate => _uiIsolate;
1484
1485 @override
1486 void _update(Map<String, dynamic> map, bool mapIsRef) {
1487 _loaded = !mapIsRef;
1488 _upgradeCollection(map, owner);
Alexandre Ardhuinadc73512019-11-19 07:57:42 +01001489 _uiIsolate = map['isolate'] as Isolate;
John McCutchan3a012b32016-08-17 09:01:04 -07001490 }
1491
1492 // TODO(johnmccutchan): Report errors when running failed.
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +01001493 Future<void> runFromSource(
1494 Uri entryUri,
1495 Uri packagesUri,
1496 Uri assetsDirectoryUri,
1497 ) async {
John McCutchan3a012b32016-08-17 09:01:04 -07001498 final String viewId = id;
1499 // When this completer completes the isolate is running.
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +02001500 final Completer<void> completer = Completer<void>();
John McCutchan3a012b32016-08-17 09:01:04 -07001501 final StreamSubscription<ServiceEvent> subscription =
Alexander Aprelev09ca3c12018-04-18 01:21:33 -07001502 (await owner.vm.vmService.onIsolateEvent).listen((ServiceEvent event) {
Alexandre Ardhuin440ce8f2019-03-07 21:09:28 +01001503 // TODO(johnmccutchan): Listen to the debug stream and catch initial
1504 // launch errors.
1505 if (event.kind == ServiceEvent.kIsolateRunnable) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -08001506 globals.printTrace('Isolate is runnable.');
Zachary Andersone2340c62019-09-13 14:51:35 -07001507 if (!completer.isCompleted) {
Alexandre Ardhuin440ce8f2019-03-07 21:09:28 +01001508 completer.complete();
Zachary Andersone2340c62019-09-13 14:51:35 -07001509 }
Alexandre Ardhuin440ce8f2019-03-07 21:09:28 +01001510 }
1511 });
John McCutchan3a012b32016-08-17 09:01:04 -07001512 await owner.vm.runInView(viewId,
Michael Goderbauere4f586d2017-03-02 20:05:47 -08001513 entryUri,
1514 packagesUri,
1515 assetsDirectoryUri);
John McCutchan3a012b32016-08-17 09:01:04 -07001516 await completer.future;
Ian Hickson6bfd9bb2018-11-08 20:54:06 -08001517 await owner.vm.refreshViews(waitForViews: true);
John McCutchan3a012b32016-08-17 09:01:04 -07001518 await subscription.cancel();
Devon Carewec751772016-05-26 15:26:14 -07001519 }
1520
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +02001521 Future<void> setAssetDirectory(Uri assetsDirectory) async {
Alexander Aprelev8da5af52018-01-04 14:27:25 -08001522 assert(assetsDirectory != null);
Alexandre Ardhuinf62afdc2018-10-01 21:29:08 +02001523 await owner.vmService.vm.invokeRpc<ServiceObject>('_flutter.setAssetBundlePath',
Alexander Aprelev8da5af52018-01-04 14:27:25 -08001524 params: <String, dynamic>{
Alexander Aprelev89cb5d22018-05-17 11:37:36 -07001525 'isolateId': _uiIsolate.id,
Alexander Aprelev8da5af52018-01-04 14:27:25 -08001526 'viewId': id,
Alexandre Ardhuin387f8852019-03-01 08:17:55 +01001527 'assetDirectory': assetsDirectory.toFilePath(windows: false),
Alexander Aprelev8da5af52018-01-04 14:27:25 -08001528 });
1529 }
1530
Dan Fielde3acb5c2019-07-08 16:38:49 -07001531 Future<void> setSemanticsEnabled(bool enabled) async {
1532 assert(enabled != null);
1533 await owner.vmService.vm.invokeRpc<ServiceObject>('_flutter.setSemanticsEnabled',
1534 params: <String, dynamic>{
1535 'isolateId': _uiIsolate.id,
1536 'viewId': id,
1537 'enabled': enabled,
1538 });
1539 }
1540
John McCutchan3a012b32016-08-17 09:01:04 -07001541 bool get hasIsolate => _uiIsolate != null;
1542
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +02001543 Future<void> flushUIThreadTasks() async {
Alexander Aprelev89cb5d22018-05-17 11:37:36 -07001544 await owner.vm.invokeRpcRaw('_flutter.flushUIThreadTasks',
1545 params: <String, dynamic>{'isolateId': _uiIsolate.id});
Carlo Bernaschina741598d2017-07-19 16:54:10 -07001546 }
1547
Devon Carewec751772016-05-26 15:26:14 -07001548 @override
John McCutchan3a012b32016-08-17 09:01:04 -07001549 String toString() => id;
Devon Carew40c0d6e2016-05-12 18:15:23 -07001550}