"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter_tools/lib/src/vmservice.dart" (13 Nov 2020, 30466 Bytes) of package /linux/misc/flutter-1.22.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 // Copyright 2014 The Flutter 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 
    5 import 'dart:async';
    6 
    7 import 'package:file/file.dart';
    8 import 'package:meta/meta.dart' show required, visibleForTesting;
    9 import 'package:vm_service/vm_service.dart' as vm_service;
   10 
   11 import 'base/context.dart';
   12 import 'base/io.dart' as io;
   13 import 'build_info.dart';
   14 import 'convert.dart';
   15 import 'device.dart';
   16 import 'globals.dart' as globals;
   17 import 'version.dart';
   18 
   19 const String kGetSkSLsMethod = '_flutter.getSkSLs';
   20 const String kSetAssetBundlePathMethod = '_flutter.setAssetBundlePath';
   21 const String kFlushUIThreadTasksMethod = '_flutter.flushUIThreadTasks';
   22 const String kRunInViewMethod = '_flutter.runInView';
   23 const String kListViewsMethod = '_flutter.listViews';
   24 const String kScreenshotSkpMethod = '_flutter.screenshotSkp';
   25 const String kScreenshotMethod = '_flutter.screenshot';
   26 
   27 /// The error response code from an unrecoverable compilation failure.
   28 const int kIsolateReloadBarred = 1005;
   29 
   30 /// Override `WebSocketConnector` in [context] to use a different constructor
   31 /// for [WebSocket]s (used by tests).
   32 typedef WebSocketConnector = Future<io.WebSocket> Function(String url, {io.CompressionOptions compression});
   33 
   34 typedef PrintStructuredErrorLogMethod = void Function(vm_service.Event);
   35 
   36 WebSocketConnector _openChannel = _defaultOpenChannel;
   37 
   38 /// The error codes for the JSON-RPC standard.
   39 ///
   40 /// See also: https://www.jsonrpc.org/specification#error_object
   41 abstract class RPCErrorCodes {
   42   /// The method does not exist or is not available.
   43   static const int kMethodNotFound = -32601;
   44 
   45   /// Invalid method parameter(s), such as a mismatched type.
   46   static const int kInvalidParams = -32602;
   47 
   48   /// Internal JSON-RPC error.
   49   static const int kInternalError = -32603;
   50 
   51   /// Application specific error codes.
   52   static const int kServerError = -32000;
   53 }
   54 
   55 /// A function that reacts to the invocation of the 'reloadSources' service.
   56 ///
   57 /// The VM Service Protocol allows clients to register custom services that
   58 /// can be invoked by other clients through the service protocol itself.
   59 ///
   60 /// Clients like Observatory use external 'reloadSources' services,
   61 /// when available, instead of the VM internal one. This allows these clients to
   62 /// invoke Flutter HotReload when connected to a Flutter Application started in
   63 /// hot mode.
   64 ///
   65 /// See: https://github.com/dart-lang/sdk/issues/30023
   66 typedef ReloadSources = Future<void> Function(
   67   String isolateId, {
   68   bool force,
   69   bool pause,
   70 });
   71 
   72 typedef Restart = Future<void> Function({ bool pause });
   73 
   74 typedef CompileExpression = Future<String> Function(
   75   String isolateId,
   76   String expression,
   77   List<String> definitions,
   78   List<String> typeDefinitions,
   79   String libraryUri,
   80   String klass,
   81   bool isStatic,
   82 );
   83 
   84 typedef ReloadMethod = Future<void> Function({
   85   String classId,
   86   String libraryId,
   87 });
   88 
   89 
   90 /// A method that pulls an SkSL shader from the device and writes it to a file.
   91 ///
   92 /// The name of the file returned as a result.
   93 typedef GetSkSLMethod = Future<String> Function();
   94 
   95 Future<io.WebSocket> _defaultOpenChannel(String url, {
   96   io.CompressionOptions compression = io.CompressionOptions.compressionDefault
   97 }) async {
   98   Duration delay = const Duration(milliseconds: 100);
   99   int attempts = 0;
  100   io.WebSocket socket;
  101 
  102   Future<void> handleError(dynamic e) async {
  103     void Function(String) printVisibleTrace = globals.printTrace;
  104     if (attempts == 10) {
  105       globals.printStatus('Connecting to the VM Service is taking longer than expected...');
  106     } else if (attempts == 20) {
  107       globals.printStatus('Still attempting to connect to the VM Service...');
  108       globals.printStatus(
  109         'If you do NOT see the Flutter application running, it might have '
  110         'crashed. The device logs (e.g. from adb or XCode) might have more '
  111         'details.');
  112       globals.printStatus(
  113         'If you do see the Flutter application running on the device, try '
  114         're-running with --host-vmservice-port to use a specific port known to '
  115         'be available.');
  116     } else if (attempts % 50 == 0) {
  117       printVisibleTrace = globals.printStatus;
  118     }
  119 
  120     printVisibleTrace('Exception attempting to connect to the VM Service: $e');
  121     printVisibleTrace('This was attempt #$attempts. Will retry in $delay.');
  122 
  123     // Delay next attempt.
  124     await Future<void>.delayed(delay);
  125 
  126     // Back off exponentially, up to 1600ms per attempt.
  127     if (delay < const Duration(seconds: 1)) {
  128       delay *= 2;
  129     }
  130   }
  131 
  132   final WebSocketConnector constructor = context.get<WebSocketConnector>() ?? io.WebSocket.connect;
  133   while (socket == null) {
  134     attempts += 1;
  135     try {
  136       socket = await constructor(url, compression: compression);
  137     } on io.WebSocketException catch (e) {
  138       await handleError(e);
  139     } on io.SocketException catch (e) {
  140       await handleError(e);
  141     }
  142   }
  143   return socket;
  144 }
  145 
  146 /// Override `VMServiceConnector` in [context] to return a different VMService
  147 /// from [VMService.connect] (used by tests).
  148 typedef VMServiceConnector = Future<vm_service.VmService> Function(Uri httpUri, {
  149   ReloadSources reloadSources,
  150   Restart restart,
  151   CompileExpression compileExpression,
  152   ReloadMethod reloadMethod,
  153   GetSkSLMethod getSkSLMethod,
  154   PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
  155   io.CompressionOptions compression,
  156   Device device,
  157 });
  158 
  159 final Expando<Uri> _httpAddressExpando = Expando<Uri>();
  160 
  161 final Expando<Uri> _wsAddressExpando = Expando<Uri>();
  162 
  163 @visibleForTesting
  164 void setHttpAddress(Uri uri, vm_service.VmService vmService) {
  165   _httpAddressExpando[vmService] = uri;
  166 }
  167 
  168 @visibleForTesting
  169 void setWsAddress(Uri uri, vm_service.VmService vmService) {
  170   _wsAddressExpando[vmService] = uri;
  171 }
  172 
  173 /// A connection to the Dart VM Service.
  174 vm_service.VmService setUpVmService(
  175   ReloadSources reloadSources,
  176   Restart restart,
  177   CompileExpression compileExpression,
  178   Device device,
  179   ReloadMethod reloadMethod,
  180   GetSkSLMethod skSLMethod,
  181   PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
  182   vm_service.VmService vmService
  183 ) {
  184   if (reloadSources != null) {
  185     vmService.registerServiceCallback('reloadSources', (Map<String, dynamic> params) async {
  186       final String isolateId = _validateRpcStringParam('reloadSources', params, 'isolateId');
  187       final bool force = _validateRpcBoolParam('reloadSources', params, 'force');
  188       final bool pause = _validateRpcBoolParam('reloadSources', params, 'pause');
  189 
  190       await reloadSources(isolateId, force: force, pause: pause);
  191 
  192       return <String, dynamic>{
  193         'result': <String, Object>{
  194           'type': 'Success',
  195         }
  196       };
  197     });
  198     vmService.registerService('reloadSources', 'Flutter Tools');
  199   }
  200 
  201   if (reloadMethod != null) {
  202     // Register a special method for hot UI. while this is implemented
  203     // currently in the same way as hot reload, it leaves the tool free
  204     // to change to a more efficient implementation in the future.
  205     //
  206     // `library` should be the file URI of the updated code.
  207     // `class` should be the name of the Widget subclass to be marked dirty. For example,
  208     // if the build method of a StatelessWidget is updated, this is the name of class.
  209     // If the build method of a StatefulWidget is updated, then this is the name
  210     // of the Widget class that created the State object.
  211     vmService.registerServiceCallback('reloadMethod', (Map<String, dynamic> params) async {
  212       final String libraryId = _validateRpcStringParam('reloadMethod', params, 'library');
  213       final String classId = _validateRpcStringParam('reloadMethod', params, 'class');
  214 
  215       globals.printTrace('reloadMethod not yet supported, falling back to hot reload');
  216 
  217       await reloadMethod(libraryId: libraryId, classId: classId);
  218       return <String, dynamic>{
  219         'result': <String, Object>{
  220           'type': 'Success',
  221         }
  222       };
  223     });
  224     vmService.registerService('reloadMethod', 'Flutter Tools');
  225   }
  226 
  227   if (restart != null) {
  228     vmService.registerServiceCallback('hotRestart', (Map<String, dynamic> params) async {
  229       final bool pause = _validateRpcBoolParam('compileExpression', params, 'pause');
  230       await restart(pause: pause);
  231       return <String, dynamic>{
  232         'result': <String, Object>{
  233           'type': 'Success',
  234         }
  235       };
  236     });
  237     vmService.registerService('hotRestart', 'Flutter Tools');
  238   }
  239 
  240   vmService.registerServiceCallback('flutterVersion', (Map<String, dynamic> params) async {
  241     final FlutterVersion version = context.get<FlutterVersion>() ?? FlutterVersion();
  242     final Map<String, Object> versionJson = version.toJson();
  243     versionJson['frameworkRevisionShort'] = version.frameworkRevisionShort;
  244     versionJson['engineRevisionShort'] = version.engineRevisionShort;
  245     return <String, dynamic>{
  246       'result': <String, Object>{
  247         'type': 'Success',
  248         ...versionJson,
  249       }
  250     };
  251   });
  252   vmService.registerService('flutterVersion', 'Flutter Tools');
  253 
  254   if (compileExpression != null) {
  255     vmService.registerServiceCallback('compileExpression', (Map<String, dynamic> params) async {
  256       final String isolateId = _validateRpcStringParam('compileExpression', params, 'isolateId');
  257       final String expression = _validateRpcStringParam('compileExpression', params, 'expression');
  258       final List<String> definitions = List<String>.from(params['definitions'] as List<dynamic>);
  259       final List<String> typeDefinitions = List<String>.from(params['typeDefinitions'] as List<dynamic>);
  260       final String libraryUri = params['libraryUri'] as String;
  261       final String klass = params['klass'] as String;
  262       final bool isStatic = _validateRpcBoolParam('compileExpression', params, 'isStatic');
  263 
  264       final String kernelBytesBase64 = await compileExpression(isolateId,
  265           expression, definitions, typeDefinitions, libraryUri, klass,
  266           isStatic);
  267       return <String, dynamic>{
  268         'type': 'Success',
  269         'result': <String, dynamic>{'kernelBytes': kernelBytesBase64},
  270       };
  271     });
  272     vmService.registerService('compileExpression', 'Flutter Tools');
  273   }
  274   if (device != null) {
  275     vmService.registerServiceCallback('flutterMemoryInfo', (Map<String, dynamic> params) async {
  276       final MemoryInfo result = await device.queryMemoryInfo();
  277       return <String, dynamic>{
  278         'result': <String, Object>{
  279           'type': 'Success',
  280           ...result.toJson(),
  281         }
  282       };
  283     });
  284     vmService.registerService('flutterMemoryInfo', 'Flutter Tools');
  285   }
  286   if (skSLMethod != null) {
  287     vmService.registerServiceCallback('flutterGetSkSL', (Map<String, dynamic> params) async {
  288       final String filename = await skSLMethod();
  289       return <String, dynamic>{
  290         'result': <String, Object>{
  291           'type': 'Success',
  292           'filename': filename,
  293         }
  294       };
  295     });
  296     vmService.registerService('flutterGetSkSL', 'Flutter Tools');
  297   }
  298   if (printStructuredErrorLogMethod != null) {
  299     try {
  300       vmService.streamListen(vm_service.EventStreams.kExtension);
  301     } on vm_service.RPCError {
  302       // It is safe to ignore this error because we expect an error to be
  303       // thrown if we're already subscribed.
  304     }
  305     vmService.onExtensionEvent.listen(printStructuredErrorLogMethod);
  306   }
  307   return vmService;
  308 }
  309 
  310 /// Connect to a Dart VM Service at [httpUri].
  311 ///
  312 /// If the [reloadSources] parameter is not null, the 'reloadSources' service
  313 /// will be registered. The VM Service Protocol allows clients to register
  314 /// custom services that can be invoked by other clients through the service
  315 /// protocol itself.
  316 ///
  317 /// See: https://github.com/dart-lang/sdk/commit/df8bf384eb815cf38450cb50a0f4b62230fba217
  318 Future<vm_service.VmService> connectToVmService(
  319   Uri httpUri, {
  320     ReloadSources reloadSources,
  321     Restart restart,
  322     CompileExpression compileExpression,
  323     ReloadMethod reloadMethod,
  324     GetSkSLMethod getSkSLMethod,
  325     PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
  326     io.CompressionOptions compression = io.CompressionOptions.compressionDefault,
  327     Device device,
  328   }) async {
  329   final VMServiceConnector connector = context.get<VMServiceConnector>() ?? _connect;
  330   return connector(httpUri,
  331     reloadSources: reloadSources,
  332     restart: restart,
  333     compileExpression: compileExpression,
  334     compression: compression,
  335     device: device,
  336     reloadMethod: reloadMethod,
  337     getSkSLMethod: getSkSLMethod,
  338     printStructuredErrorLogMethod: printStructuredErrorLogMethod,
  339   );
  340 }
  341 
  342 Future<vm_service.VmService> _connect(
  343   Uri httpUri, {
  344   ReloadSources reloadSources,
  345   Restart restart,
  346   CompileExpression compileExpression,
  347   ReloadMethod reloadMethod,
  348   GetSkSLMethod getSkSLMethod,
  349   PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
  350   io.CompressionOptions compression = io.CompressionOptions.compressionDefault,
  351   Device device,
  352 }) async {
  353   final Uri wsUri = httpUri.replace(scheme: 'ws', path: globals.fs.path.join(httpUri.path, 'ws'));
  354   final io.WebSocket channel = await _openChannel(wsUri.toString(), compression: compression);
  355   final vm_service.VmService delegateService = vm_service.VmService(
  356     channel,
  357     channel.add,
  358     log: null,
  359     disposeHandler: () async {
  360       await channel.close();
  361     },
  362   );
  363 
  364   final vm_service.VmService service = setUpVmService(
  365     reloadSources,
  366     restart,
  367     compileExpression,
  368     device,
  369     reloadMethod,
  370     getSkSLMethod,
  371     printStructuredErrorLogMethod,
  372     delegateService,
  373   );
  374   _httpAddressExpando[service] = httpUri;
  375   _wsAddressExpando[service] = wsUri;
  376 
  377   // This call is to ensure we are able to establish a connection instead of
  378   // keeping on trucking and failing farther down the process.
  379   await delegateService.getVersion();
  380   return service;
  381 }
  382 
  383 String _validateRpcStringParam(String methodName, Map<String, dynamic> params, String paramName) {
  384   final dynamic value = params[paramName];
  385   if (value is! String || (value as String).isEmpty) {
  386     throw vm_service.RPCError(
  387       methodName,
  388       RPCErrorCodes.kInvalidParams,
  389       "Invalid '$paramName': $value",
  390     );
  391   }
  392   return value as String;
  393 }
  394 
  395 bool _validateRpcBoolParam(String methodName, Map<String, dynamic> params, String paramName) {
  396   final dynamic value = params[paramName];
  397   if (value != null && value is! bool) {
  398     throw vm_service.RPCError(
  399       methodName,
  400       RPCErrorCodes.kInvalidParams,
  401       "Invalid '$paramName': $value",
  402     );
  403   }
  404   return (value as bool) ?? false;
  405 }
  406 
  407 /// Peered to an Android/iOS FlutterView widget on a device.
  408 class FlutterView {
  409   FlutterView({
  410     @required this.id,
  411     @required this.uiIsolate,
  412   });
  413 
  414   factory FlutterView.parse(Map<String, Object> json) {
  415     final Map<String, Object> rawIsolate = json['isolate'] as Map<String, Object>;
  416     vm_service.IsolateRef isolate;
  417     if (rawIsolate != null) {
  418       rawIsolate['number'] = rawIsolate['number']?.toString();
  419       isolate = vm_service.IsolateRef.parse(rawIsolate);
  420     }
  421     return FlutterView(
  422       id: json['id'] as String,
  423       uiIsolate: isolate,
  424     );
  425   }
  426 
  427   final vm_service.IsolateRef uiIsolate;
  428   final String id;
  429 
  430   bool get hasIsolate => uiIsolate != null;
  431 
  432   @override
  433   String toString() => id;
  434 
  435   Map<String, Object> toJson() {
  436     return <String, Object>{
  437       'id': id,
  438       'isolate': uiIsolate?.toJson(),
  439     };
  440   }
  441 }
  442 
  443 /// Flutter specific VM Service functionality.
  444 extension FlutterVmService on vm_service.VmService {
  445   Uri get wsAddress => this != null ? _wsAddressExpando[this] : null;
  446 
  447   Uri get httpAddress => this != null ? _httpAddressExpando[this] : null;
  448 
  449   /// Set the asset directory for the an attached Flutter view.
  450   Future<void> setAssetDirectory({
  451     @required Uri assetsDirectory,
  452     @required String viewId,
  453     @required String uiIsolateId,
  454   }) async {
  455     assert(assetsDirectory != null);
  456     await callMethod(kSetAssetBundlePathMethod,
  457       isolateId: uiIsolateId,
  458       args: <String, dynamic>{
  459         'viewId': viewId,
  460         'assetDirectory': assetsDirectory.toFilePath(windows: false),
  461       });
  462   }
  463 
  464   /// Retreive the cached SkSL shaders from an attached Flutter view.
  465   ///
  466   /// This method will only return data if `--cache-sksl` was provided as a
  467   /// flutter run agument, and only then on physical devices.
  468   Future<Map<String, Object>> getSkSLs({
  469     @required String viewId,
  470   }) async {
  471     final vm_service.Response response = await callMethod(
  472       kGetSkSLsMethod,
  473       args: <String, String>{
  474         'viewId': viewId,
  475       },
  476     );
  477     return response.json['SkSLs'] as Map<String, Object>;
  478   }
  479 
  480   /// Flush all tasks on the UI thead for an attached Flutter view.
  481   ///
  482   /// This method is currently used only for benchmarking.
  483   Future<void> flushUIThreadTasks({
  484     @required String uiIsolateId,
  485   }) async {
  486     await callMethod(
  487       kFlushUIThreadTasksMethod,
  488       args: <String, String>{
  489         'isolateId': uiIsolateId,
  490       },
  491     );
  492   }
  493 
  494   /// Launch the Dart isolate with entrypoint [main] in the Flutter engine [viewId]
  495   /// with [assetsDirectory] as the devFS.
  496   ///
  497   /// This method is used by the tool to hot restart an already running Flutter
  498   /// engine.
  499   Future<void> runInView({
  500     @required String viewId,
  501     @required Uri main,
  502     @required Uri assetsDirectory,
  503   }) async {
  504     try {
  505       await streamListen('Isolate');
  506     } on vm_service.RPCError {
  507       // Do nothing, since the tool is already subscribed.
  508     }
  509     final Future<void> onRunnable = onIsolateEvent.firstWhere((vm_service.Event event) {
  510       return event.kind == vm_service.EventKind.kIsolateRunnable;
  511     });
  512     await callMethod(
  513       kRunInViewMethod,
  514       args: <String, Object>{
  515         'viewId': viewId,
  516         'mainScript': main.toString(),
  517         'assetDirectory': assetsDirectory.toString(),
  518       },
  519     );
  520     await onRunnable;
  521   }
  522 
  523   Future<Map<String, dynamic>> flutterDebugDumpApp({
  524     @required String isolateId,
  525   }) {
  526     return invokeFlutterExtensionRpcRaw(
  527       'ext.flutter.debugDumpApp',
  528       isolateId: isolateId,
  529     );
  530   }
  531 
  532   Future<Map<String, dynamic>> flutterDebugDumpRenderTree({
  533     @required String isolateId,
  534   }) {
  535     return invokeFlutterExtensionRpcRaw(
  536       'ext.flutter.debugDumpRenderTree',
  537       isolateId: isolateId,
  538     );
  539   }
  540 
  541   Future<Map<String, dynamic>> flutterDebugDumpLayerTree({
  542     @required String isolateId,
  543   }) {
  544     return invokeFlutterExtensionRpcRaw(
  545       'ext.flutter.debugDumpLayerTree',
  546       isolateId: isolateId,
  547     );
  548   }
  549 
  550   Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInTraversalOrder({
  551     @required String isolateId,
  552   }) {
  553     return invokeFlutterExtensionRpcRaw(
  554       'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
  555       isolateId: isolateId,
  556     );
  557   }
  558 
  559   Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInInverseHitTestOrder({
  560     @required String isolateId,
  561   }) {
  562     return invokeFlutterExtensionRpcRaw(
  563       'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder',
  564       isolateId: isolateId,
  565     );
  566   }
  567 
  568   Future<Map<String, dynamic>> _flutterToggle(String name, {
  569     @required String isolateId,
  570   }) async {
  571     Map<String, dynamic> state = await invokeFlutterExtensionRpcRaw(
  572       'ext.flutter.$name',
  573       isolateId: isolateId,
  574     );
  575     if (state != null && state.containsKey('enabled') && state['enabled'] is String) {
  576       state = await invokeFlutterExtensionRpcRaw(
  577         'ext.flutter.$name',
  578         isolateId: isolateId,
  579         args: <String, dynamic>{
  580           'enabled': state['enabled'] == 'true' ? 'false' : 'true',
  581         },
  582       );
  583     }
  584 
  585     return state;
  586   }
  587 
  588   Future<Map<String, dynamic>> flutterToggleDebugPaintSizeEnabled({
  589     @required String isolateId,
  590   }) => _flutterToggle('debugPaint', isolateId: isolateId);
  591 
  592   Future<Map<String, dynamic>> flutterToggleDebugCheckElevationsEnabled({
  593     @required String isolateId,
  594   }) => _flutterToggle('debugCheckElevationsEnabled', isolateId: isolateId);
  595 
  596   Future<Map<String, dynamic>> flutterTogglePerformanceOverlayOverride({
  597     @required String isolateId,
  598   }) => _flutterToggle('showPerformanceOverlay', isolateId: isolateId);
  599 
  600   Future<Map<String, dynamic>> flutterToggleWidgetInspector({
  601     @required String isolateId,
  602   }) => _flutterToggle('inspector.show', isolateId: isolateId);
  603 
  604   Future<Map<String,dynamic>> flutterToggleInvertOversizedImages({
  605     @required String isolateId,
  606   }) => _flutterToggle('invertOversizedImages', isolateId: isolateId);
  607 
  608   Future<Map<String, dynamic>> flutterToggleProfileWidgetBuilds({
  609     @required String isolateId,
  610   }) => _flutterToggle('profileWidgetBuilds', isolateId: isolateId);
  611 
  612   Future<Map<String, dynamic>> flutterDebugAllowBanner(bool show, {
  613     @required String isolateId,
  614   }) {
  615     return invokeFlutterExtensionRpcRaw(
  616       'ext.flutter.debugAllowBanner',
  617       isolateId: isolateId,
  618       args: <String, dynamic>{'enabled': show ? 'true' : 'false'},
  619     );
  620   }
  621 
  622   Future<Map<String, dynamic>> flutterReassemble({
  623     @required String isolateId,
  624   }) {
  625     return invokeFlutterExtensionRpcRaw(
  626       'ext.flutter.reassemble',
  627       isolateId: isolateId,
  628     );
  629   }
  630 
  631   Future<Map<String, dynamic>> flutterFastReassemble({
  632    @required String isolateId,
  633   }) {
  634     return invokeFlutterExtensionRpcRaw(
  635       'ext.flutter.fastReassemble',
  636       isolateId: isolateId,
  637       args: <String, Object>{},
  638     );
  639   }
  640 
  641   Future<bool> flutterAlreadyPaintedFirstUsefulFrame({
  642     @required String isolateId,
  643   }) async {
  644     final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw(
  645       'ext.flutter.didSendFirstFrameRasterizedEvent',
  646       isolateId: isolateId,
  647     );
  648     // result might be null when the service extension is not initialized
  649     return result != null && result['enabled'] == 'true';
  650   }
  651 
  652   Future<Map<String, dynamic>> uiWindowScheduleFrame({
  653     @required String isolateId,
  654   }) {
  655     return invokeFlutterExtensionRpcRaw(
  656       'ext.ui.window.scheduleFrame',
  657       isolateId: isolateId,
  658     );
  659   }
  660 
  661   Future<Map<String, dynamic>> flutterEvictAsset(String assetPath, {
  662    @required String isolateId,
  663   }) {
  664     return invokeFlutterExtensionRpcRaw(
  665       'ext.flutter.evict',
  666       isolateId: isolateId,
  667       args: <String, dynamic>{
  668         'value': assetPath,
  669       },
  670     );
  671   }
  672 
  673   /// Exit the application by calling [exit] from `dart:io`.
  674   ///
  675   /// This method is only supported by certain embedders. This is
  676   /// described by [Device.supportsFlutterExit].
  677   Future<void> flutterExit({
  678     @required String isolateId,
  679   }) {
  680     return invokeFlutterExtensionRpcRaw(
  681       'ext.flutter.exit',
  682       isolateId: isolateId,
  683     ).catchError((dynamic error, StackTrace stackTrace) {
  684       globals.logger.printTrace('Failure in ext.flutter.exit: $error\n$stackTrace');
  685       // Do nothing on sentinel or exception, the isolate already exited.
  686     }, test: (dynamic error) => error is vm_service.SentinelException || error is vm_service.RPCError);
  687   }
  688 
  689   /// Return the current platform override for the flutter view running with
  690   /// the main isolate [isolateId].
  691   ///
  692   /// If a non-null value is provided for [platform], the platform override
  693   /// is updated with this value.
  694   Future<String> flutterPlatformOverride({
  695     String platform,
  696     @required String isolateId,
  697   }) async {
  698     final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw(
  699       'ext.flutter.platformOverride',
  700       isolateId: isolateId,
  701       args: platform != null
  702         ? <String, dynamic>{'value': platform}
  703         : <String, String>{},
  704     );
  705     if (result != null && result['value'] is String) {
  706       return result['value'] as String;
  707     }
  708     return 'unknown';
  709   }
  710 
  711   /// Return the current brightness value for the flutter view running with
  712   /// the main isolate [isolateId].
  713   ///
  714   /// If a non-null value is provided for [brightness], the brightness override
  715   /// is updated with this value.
  716   Future<Brightness> flutterBrightnessOverride({
  717     Brightness brightness,
  718     @required String isolateId,
  719   }) async {
  720     final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw(
  721       'ext.flutter.brightnessOverride',
  722       isolateId: isolateId,
  723       args: brightness != null
  724         ? <String, dynamic>{'value': brightness.toString()}
  725         : <String, String>{},
  726     );
  727     if (result != null && result['value'] is String) {
  728       return (result['value'] as String) == 'Brightness.light'
  729         ? Brightness.light
  730         : Brightness.dark;
  731     }
  732     return null;
  733   }
  734 
  735   /// Invoke a flutter extension method, if the flutter extension is not
  736   /// available, returns null.
  737   Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw(
  738     String method, {
  739     @required String isolateId,
  740     Map<String, dynamic> args,
  741   }) async {
  742     try {
  743 
  744       final vm_service.Response response = await callServiceExtension(
  745         method,
  746         args: <String, Object>{
  747           'isolateId': isolateId,
  748           ...?args,
  749         },
  750       );
  751       return response.json;
  752     } on vm_service.RPCError catch (err) {
  753       // If an application is not using the framework
  754       if (err.code == RPCErrorCodes.kMethodNotFound) {
  755         return null;
  756       }
  757       rethrow;
  758     }
  759   }
  760 
  761   /// List all [FlutterView]s attached to the current VM.
  762   ///
  763   /// If this returns an empty list, it will poll forever unless [returnEarly]
  764   /// is set to true.
  765   ///
  766   /// By default, the poll duration is 50 milliseconds.
  767   Future<List<FlutterView>> getFlutterViews({
  768     bool returnEarly = false,
  769     Duration delay = const Duration(milliseconds: 50),
  770   }) async {
  771     while (true) {
  772       final vm_service.Response response = await callMethod(
  773         kListViewsMethod,
  774       );
  775       final List<Object> rawViews = response.json['views'] as List<Object>;
  776       final List<FlutterView> views = <FlutterView>[
  777         for (final Object rawView in rawViews)
  778           FlutterView.parse(rawView as Map<String, Object>)
  779       ];
  780       if (views.isNotEmpty || returnEarly) {
  781         return views;
  782       }
  783       await Future<void>.delayed(delay);
  784     }
  785   }
  786 
  787   /// Attempt to retrieve the isolate with id [isolateId], or `null` if it has
  788   /// been collected.
  789   Future<vm_service.Isolate> getIsolateOrNull(String isolateId) {
  790     return getIsolate(isolateId)
  791       .catchError((dynamic error, StackTrace stackTrace) {
  792         return null;
  793       }, test: (dynamic error) => error is vm_service.SentinelException);
  794   }
  795 
  796   /// Create a new development file system on the device.
  797   Future<vm_service.Response> createDevFS(String fsName) {
  798     return callServiceExtension('_createDevFS', args: <String, dynamic>{'fsName': fsName});
  799   }
  800 
  801   /// Delete an existing file system.
  802   Future<vm_service.Response> deleteDevFS(String fsName) {
  803     return callServiceExtension('_deleteDevFS', args: <String, dynamic>{'fsName': fsName});
  804   }
  805 
  806   Future<vm_service.Response> screenshot() {
  807     return callServiceExtension(kScreenshotMethod);
  808   }
  809 
  810   Future<vm_service.Response> screenshotSkp() {
  811     return callServiceExtension(kScreenshotSkpMethod);
  812   }
  813 
  814   /// Set the VM timeline flags.
  815   Future<vm_service.Response> setVMTimelineFlags(List<String> recordedStreams) {
  816     assert(recordedStreams != null);
  817     return callServiceExtension(
  818       'setVMTimelineFlags',
  819       args: <String, dynamic>{
  820         'recordedStreams': recordedStreams,
  821       },
  822     );
  823   }
  824 
  825   Future<vm_service.Response> getVMTimeline() {
  826     return callServiceExtension('getVMTimeline');
  827   }
  828 }
  829 
  830 /// Whether the event attached to an [Isolate.pauseEvent] should be considered
  831 /// a "pause" event.
  832 bool isPauseEvent(String kind) {
  833   return kind == vm_service.EventKind.kPauseStart ||
  834          kind == vm_service.EventKind.kPauseExit ||
  835          kind == vm_service.EventKind.kPauseBreakpoint ||
  836          kind == vm_service.EventKind.kPauseInterrupted ||
  837          kind == vm_service.EventKind.kPauseException ||
  838          kind == vm_service.EventKind.kPausePostRequest ||
  839          kind == vm_service.EventKind.kNone;
  840 }
  841 
  842 // TODO(jonahwilliams): either refactor drive to use the resident runner
  843 // or delete it.
  844 Future<String> sharedSkSlWriter(Device device, Map<String, Object> data, {
  845   File outputFile,
  846 }) async {
  847   if (data.isEmpty) {
  848     globals.logger.printStatus(
  849       'No data was receieved. To ensure SkSL data can be generated use a '
  850       'physical device then:\n'
  851       '  1. Pass "--cache-sksl" as an argument to flutter run.\n'
  852       '  2. Interact with the application to force shaders to be compiled.\n'
  853     );
  854     return null;
  855   }
  856   if (outputFile == null) {
  857     outputFile = globals.fsUtils.getUniqueFile(
  858       globals.fs.currentDirectory,
  859       'flutter',
  860       'sksl.json',
  861     );
  862   } else if (!outputFile.parent.existsSync()) {
  863     outputFile.parent.createSync(recursive: true);
  864   }
  865   // Convert android sub-platforms to single target platform.
  866   TargetPlatform targetPlatform = await device.targetPlatform;
  867   switch (targetPlatform) {
  868     case TargetPlatform.android_arm:
  869     case TargetPlatform.android_arm64:
  870     case TargetPlatform.android_x64:
  871     case TargetPlatform.android_x86:
  872       targetPlatform = TargetPlatform.android;
  873       break;
  874     default:
  875       break;
  876   }
  877   final Map<String, Object> manifest = <String, Object>{
  878     'platform': getNameForTargetPlatform(targetPlatform),
  879     'name': device.name,
  880     'engineRevision': globals.flutterVersion.engineRevision,
  881     'data': data,
  882   };
  883   outputFile.writeAsStringSync(json.encode(manifest));
  884   globals.logger.printStatus('Wrote SkSL data to ${outputFile.path}.');
  885   return outputFile.path;
  886 }
  887 
  888 /// A brightness enum that matches the values https://github.com/flutter/engine/blob/3a96741247528133c0201ab88500c0c3c036e64e/lib/ui/window.dart#L1328
  889 /// Describes the contrast of a theme or color palette.
  890 enum Brightness {
  891   /// The color is dark and will require a light text color to achieve readable
  892   /// contrast.
  893   ///
  894   /// For example, the color might be dark grey, requiring white text.
  895   dark,
  896 
  897   /// The color is light and will require a dark text color to achieve readable
  898   /// contrast.
  899   ///
  900   /// For example, the color might be bright white, requiring black text.
  901   light,
  902 }
  903 
  904 /// Process a VM service log event into a string message.
  905 String processVmServiceMessage(vm_service.Event event) {
  906   final String message = utf8.decode(base64.decode(event.bytes));
  907   // Remove extra trailing newlines appended by the vm service.
  908   if (message.endsWith('\n')) {
  909     return message.substring(0, message.length - 1);
  910   }
  911   return message;
  912 }