flutter/packages/flutter_tools/lib/src/vmservice.dart
Jonah Williams 183fe75d58
[flutter_tools] reland: drive service (#68887)
Overhaul of flutter drive in order to deliver a better experience, namely:

* flutter run and flutter drive now share more flags, so code paths that were previously only testable on run are now testable on drive.
* Removes web-initialize-platform as this is no longer used
* flutter drive correctly sets up a logger that shows native exceptions, by connecting to the vm service.
* VM service connection now provides access to memory info without launching devtools (only for debug/profile mode)

Web changes

* Passes on the one test in the repo, otherwise the webdriver code has been isolated as much as possible

Additional NNBD related bug fixes:

No longer passes --enable-experiment to the test script. (FYI @blasten ). earlier we might have assumed that the flutter gallery benchmarks would be migrated along side the app and flutter driver, but only the app under test needs to be migrated. The test scripts should never be run with the experiment.
2020-10-26 10:11:30 -07:00

879 lines
28 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/file.dart';
import 'package:meta/meta.dart' show required, visibleForTesting;
import 'package:vm_service/vm_service.dart' as vm_service;
import 'base/context.dart';
import 'base/io.dart' as io;
import 'base/logger.dart';
import 'build_info.dart';
import 'convert.dart';
import 'device.dart';
import 'globals.dart' as globals;
import 'version.dart';
const String kGetSkSLsMethod = '_flutter.getSkSLs';
const String kSetAssetBundlePathMethod = '_flutter.setAssetBundlePath';
const String kFlushUIThreadTasksMethod = '_flutter.flushUIThreadTasks';
const String kRunInViewMethod = '_flutter.runInView';
const String kListViewsMethod = '_flutter.listViews';
const String kScreenshotSkpMethod = '_flutter.screenshotSkp';
const String kScreenshotMethod = '_flutter.screenshot';
/// The error response code from an unrecoverable compilation failure.
const int kIsolateReloadBarred = 1005;
/// Override `WebSocketConnector` in [context] to use a different constructor
/// for [WebSocket]s (used by tests).
typedef WebSocketConnector = Future<io.WebSocket> Function(String url, {io.CompressionOptions compression});
typedef PrintStructuredErrorLogMethod = void Function(vm_service.Event);
WebSocketConnector _openChannel = _defaultOpenChannel;
/// The error codes for the JSON-RPC standard.
///
/// See also: https://www.jsonrpc.org/specification#error_object
abstract class RPCErrorCodes {
/// The method does not exist or is not available.
static const int kMethodNotFound = -32601;
/// Invalid method parameter(s), such as a mismatched type.
static const int kInvalidParams = -32602;
/// Internal JSON-RPC error.
static const int kInternalError = -32603;
/// Application specific error codes.
static const int kServerError = -32000;
}
/// A function that reacts to the invocation of the 'reloadSources' service.
///
/// The VM Service Protocol allows clients to register custom services that
/// can be invoked by other clients through the service protocol itself.
///
/// Clients like Observatory use external 'reloadSources' services,
/// when available, instead of the VM internal one. This allows these clients to
/// invoke Flutter HotReload when connected to a Flutter Application started in
/// hot mode.
///
/// See: https://github.com/dart-lang/sdk/issues/30023
typedef ReloadSources = Future<void> Function(
String isolateId, {
bool force,
bool pause,
});
typedef Restart = Future<void> Function({ bool pause });
typedef CompileExpression = Future<String> Function(
String isolateId,
String expression,
List<String> definitions,
List<String> typeDefinitions,
String libraryUri,
String klass,
bool isStatic,
);
/// A method that pulls an SkSL shader from the device and writes it to a file.
///
/// The name of the file returned as a result.
typedef GetSkSLMethod = Future<String> Function();
Future<io.WebSocket> _defaultOpenChannel(String url, {
io.CompressionOptions compression = io.CompressionOptions.compressionDefault
}) async {
Duration delay = const Duration(milliseconds: 100);
int attempts = 0;
io.WebSocket socket;
Future<void> handleError(dynamic e) async {
void Function(String) printVisibleTrace = globals.printTrace;
if (attempts == 10) {
globals.printStatus('Connecting to the VM Service is taking longer than expected...');
} else if (attempts == 20) {
globals.printStatus('Still attempting to connect to the VM Service...');
globals.printStatus(
'If you do NOT see the Flutter application running, it might have '
'crashed. The device logs (e.g. from adb or XCode) might have more '
'details.');
globals.printStatus(
'If you do see the Flutter application running on the device, try '
're-running with --host-vmservice-port to use a specific port known to '
'be available.');
} else if (attempts % 50 == 0) {
printVisibleTrace = globals.printStatus;
}
printVisibleTrace('Exception attempting to connect to the VM Service: $e');
printVisibleTrace('This was attempt #$attempts. Will retry in $delay.');
// Delay next attempt.
await Future<void>.delayed(delay);
// Back off exponentially, up to 1600ms per attempt.
if (delay < const Duration(seconds: 1)) {
delay *= 2;
}
}
final WebSocketConnector constructor = context.get<WebSocketConnector>() ?? io.WebSocket.connect;
while (socket == null) {
attempts += 1;
try {
socket = await constructor(url, compression: compression);
} on io.WebSocketException catch (e) {
await handleError(e);
} on io.SocketException catch (e) {
await handleError(e);
}
}
return socket;
}
/// Override `VMServiceConnector` in [context] to return a different VMService
/// from [VMService.connect] (used by tests).
typedef VMServiceConnector = Future<vm_service.VmService> Function(Uri httpUri, {
ReloadSources reloadSources,
Restart restart,
CompileExpression compileExpression,
GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
io.CompressionOptions compression,
Device device,
});
final Expando<Uri> _httpAddressExpando = Expando<Uri>();
final Expando<Uri> _wsAddressExpando = Expando<Uri>();
@visibleForTesting
void setHttpAddress(Uri uri, vm_service.VmService vmService) {
_httpAddressExpando[vmService] = uri;
}
@visibleForTesting
void setWsAddress(Uri uri, vm_service.VmService vmService) {
_wsAddressExpando[vmService] = uri;
}
/// A connection to the Dart VM Service.
vm_service.VmService setUpVmService(
ReloadSources reloadSources,
Restart restart,
CompileExpression compileExpression,
Device device,
GetSkSLMethod skSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
vm_service.VmService vmService
) {
if (reloadSources != null) {
vmService.registerServiceCallback('reloadSources', (Map<String, dynamic> params) async {
final String isolateId = _validateRpcStringParam('reloadSources', params, 'isolateId');
final bool force = _validateRpcBoolParam('reloadSources', params, 'force');
final bool pause = _validateRpcBoolParam('reloadSources', params, 'pause');
await reloadSources(isolateId, force: force, pause: pause);
return <String, dynamic>{
'result': <String, Object>{
'type': 'Success',
}
};
});
vmService.registerService('reloadSources', 'Flutter Tools');
}
if (restart != null) {
vmService.registerServiceCallback('hotRestart', (Map<String, dynamic> params) async {
final bool pause = _validateRpcBoolParam('compileExpression', params, 'pause');
await restart(pause: pause);
return <String, dynamic>{
'result': <String, Object>{
'type': 'Success',
}
};
});
vmService.registerService('hotRestart', 'Flutter Tools');
}
vmService.registerServiceCallback('flutterVersion', (Map<String, dynamic> params) async {
final FlutterVersion version = context.get<FlutterVersion>() ?? FlutterVersion();
final Map<String, Object> versionJson = version.toJson();
versionJson['frameworkRevisionShort'] = version.frameworkRevisionShort;
versionJson['engineRevisionShort'] = version.engineRevisionShort;
return <String, dynamic>{
'result': <String, Object>{
'type': 'Success',
...versionJson,
}
};
});
vmService.registerService('flutterVersion', 'Flutter Tools');
if (compileExpression != null) {
vmService.registerServiceCallback('compileExpression', (Map<String, dynamic> params) async {
final String isolateId = _validateRpcStringParam('compileExpression', params, 'isolateId');
final String expression = _validateRpcStringParam('compileExpression', params, 'expression');
final List<String> definitions = List<String>.from(params['definitions'] as List<dynamic>);
final List<String> typeDefinitions = List<String>.from(params['typeDefinitions'] as List<dynamic>);
final String libraryUri = params['libraryUri'] as String;
final String klass = params['klass'] as String;
final bool isStatic = _validateRpcBoolParam('compileExpression', params, 'isStatic');
final String kernelBytesBase64 = await compileExpression(isolateId,
expression, definitions, typeDefinitions, libraryUri, klass,
isStatic);
return <String, dynamic>{
'type': 'Success',
'result': <String, dynamic>{'kernelBytes': kernelBytesBase64},
};
});
vmService.registerService('compileExpression', 'Flutter Tools');
}
if (device != null) {
vmService.registerServiceCallback('flutterMemoryInfo', (Map<String, dynamic> params) async {
final MemoryInfo result = await device.queryMemoryInfo();
return <String, dynamic>{
'result': <String, Object>{
'type': 'Success',
...result.toJson(),
}
};
});
vmService.registerService('flutterMemoryInfo', 'Flutter Tools');
}
if (skSLMethod != null) {
vmService.registerServiceCallback('flutterGetSkSL', (Map<String, dynamic> params) async {
final String filename = await skSLMethod();
return <String, dynamic>{
'result': <String, Object>{
'type': 'Success',
'filename': filename,
}
};
});
vmService.registerService('flutterGetSkSL', 'Flutter Tools');
}
if (printStructuredErrorLogMethod != null) {
try {
vmService.streamListen(vm_service.EventStreams.kExtension);
} on vm_service.RPCError {
// It is safe to ignore this error because we expect an error to be
// thrown if we're already subscribed.
}
vmService.onExtensionEvent.listen(printStructuredErrorLogMethod);
}
return vmService;
}
/// Connect to a Dart VM Service at [httpUri].
///
/// If the [reloadSources] parameter is not null, the 'reloadSources' service
/// will be registered. The VM Service Protocol allows clients to register
/// custom services that can be invoked by other clients through the service
/// protocol itself.
///
/// See: https://github.com/dart-lang/sdk/commit/df8bf384eb815cf38450cb50a0f4b62230fba217
Future<vm_service.VmService> connectToVmService(
Uri httpUri, {
ReloadSources reloadSources,
Restart restart,
CompileExpression compileExpression,
GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
io.CompressionOptions compression = io.CompressionOptions.compressionDefault,
Device device,
}) async {
final VMServiceConnector connector = context.get<VMServiceConnector>() ?? _connect;
return connector(httpUri,
reloadSources: reloadSources,
restart: restart,
compileExpression: compileExpression,
compression: compression,
device: device,
getSkSLMethod: getSkSLMethod,
printStructuredErrorLogMethod: printStructuredErrorLogMethod,
);
}
Future<vm_service.VmService> _connect(
Uri httpUri, {
ReloadSources reloadSources,
Restart restart,
CompileExpression compileExpression,
GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
io.CompressionOptions compression = io.CompressionOptions.compressionDefault,
Device device,
}) async {
final Uri wsUri = httpUri.replace(scheme: 'ws', path: globals.fs.path.join(httpUri.path, 'ws'));
final io.WebSocket channel = await _openChannel(wsUri.toString(), compression: compression);
final vm_service.VmService delegateService = vm_service.VmService(
channel,
channel.add,
log: null,
disposeHandler: () async {
await channel.close();
},
);
final vm_service.VmService service = setUpVmService(
reloadSources,
restart,
compileExpression,
device,
getSkSLMethod,
printStructuredErrorLogMethod,
delegateService,
);
_httpAddressExpando[service] = httpUri;
_wsAddressExpando[service] = wsUri;
// This call is to ensure we are able to establish a connection instead of
// keeping on trucking and failing farther down the process.
await delegateService.getVersion();
return service;
}
String _validateRpcStringParam(String methodName, Map<String, dynamic> params, String paramName) {
final dynamic value = params[paramName];
if (value is! String || (value as String).isEmpty) {
throw vm_service.RPCError(
methodName,
RPCErrorCodes.kInvalidParams,
"Invalid '$paramName': $value",
);
}
return value as String;
}
bool _validateRpcBoolParam(String methodName, Map<String, dynamic> params, String paramName) {
final dynamic value = params[paramName];
if (value != null && value is! bool) {
throw vm_service.RPCError(
methodName,
RPCErrorCodes.kInvalidParams,
"Invalid '$paramName': $value",
);
}
return (value as bool) ?? false;
}
/// Peered to an Android/iOS FlutterView widget on a device.
class FlutterView {
FlutterView({
@required this.id,
@required this.uiIsolate,
});
factory FlutterView.parse(Map<String, Object> json) {
final Map<String, Object> rawIsolate = json['isolate'] as Map<String, Object>;
vm_service.IsolateRef isolate;
if (rawIsolate != null) {
rawIsolate['number'] = rawIsolate['number']?.toString();
isolate = vm_service.IsolateRef.parse(rawIsolate);
}
return FlutterView(
id: json['id'] as String,
uiIsolate: isolate,
);
}
final vm_service.IsolateRef uiIsolate;
final String id;
bool get hasIsolate => uiIsolate != null;
@override
String toString() => id;
Map<String, Object> toJson() {
return <String, Object>{
'id': id,
'isolate': uiIsolate?.toJson(),
};
}
}
/// Flutter specific VM Service functionality.
extension FlutterVmService on vm_service.VmService {
Uri get wsAddress => this != null ? _wsAddressExpando[this] : null;
Uri get httpAddress => this != null ? _httpAddressExpando[this] : null;
/// Set the asset directory for the an attached Flutter view.
Future<void> setAssetDirectory({
@required Uri assetsDirectory,
@required String viewId,
@required String uiIsolateId,
}) async {
assert(assetsDirectory != null);
await callMethod(kSetAssetBundlePathMethod,
isolateId: uiIsolateId,
args: <String, dynamic>{
'viewId': viewId,
'assetDirectory': assetsDirectory.toFilePath(windows: false),
});
}
/// Retrieve the cached SkSL shaders from an attached Flutter view.
///
/// This method will only return data if `--cache-sksl` was provided as a
/// flutter run argument, and only then on physical devices.
Future<Map<String, Object>> getSkSLs({
@required String viewId,
}) async {
final vm_service.Response response = await callMethod(
kGetSkSLsMethod,
args: <String, String>{
'viewId': viewId,
},
);
return response.json['SkSLs'] as Map<String, Object>;
}
/// Flush all tasks on the UI thread for an attached Flutter view.
///
/// This method is currently used only for benchmarking.
Future<void> flushUIThreadTasks({
@required String uiIsolateId,
}) async {
await callMethod(
kFlushUIThreadTasksMethod,
args: <String, String>{
'isolateId': uiIsolateId,
},
);
}
/// Launch the Dart isolate with entrypoint [main] in the Flutter engine [viewId]
/// with [assetsDirectory] as the devFS.
///
/// This method is used by the tool to hot restart an already running Flutter
/// engine.
Future<void> runInView({
@required String viewId,
@required Uri main,
@required Uri assetsDirectory,
}) async {
try {
await streamListen('Isolate');
} on vm_service.RPCError {
// Do nothing, since the tool is already subscribed.
}
final Future<void> onRunnable = onIsolateEvent.firstWhere((vm_service.Event event) {
return event.kind == vm_service.EventKind.kIsolateRunnable;
});
await callMethod(
kRunInViewMethod,
args: <String, Object>{
'viewId': viewId,
'mainScript': main.toString(),
'assetDirectory': assetsDirectory.toString(),
},
);
await onRunnable;
}
Future<Map<String, dynamic>> flutterDebugDumpApp({
@required String isolateId,
}) {
return invokeFlutterExtensionRpcRaw(
'ext.flutter.debugDumpApp',
isolateId: isolateId,
);
}
Future<Map<String, dynamic>> flutterDebugDumpRenderTree({
@required String isolateId,
}) {
return invokeFlutterExtensionRpcRaw(
'ext.flutter.debugDumpRenderTree',
isolateId: isolateId,
);
}
Future<Map<String, dynamic>> flutterDebugDumpLayerTree({
@required String isolateId,
}) {
return invokeFlutterExtensionRpcRaw(
'ext.flutter.debugDumpLayerTree',
isolateId: isolateId,
);
}
Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInTraversalOrder({
@required String isolateId,
}) {
return invokeFlutterExtensionRpcRaw(
'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
isolateId: isolateId,
);
}
Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInInverseHitTestOrder({
@required String isolateId,
}) {
return invokeFlutterExtensionRpcRaw(
'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder',
isolateId: isolateId,
);
}
Future<Map<String, dynamic>> _flutterToggle(String name, {
@required String isolateId,
}) async {
Map<String, dynamic> state = await invokeFlutterExtensionRpcRaw(
'ext.flutter.$name',
isolateId: isolateId,
);
if (state != null && state.containsKey('enabled') && state['enabled'] is String) {
state = await invokeFlutterExtensionRpcRaw(
'ext.flutter.$name',
isolateId: isolateId,
args: <String, dynamic>{
'enabled': state['enabled'] == 'true' ? 'false' : 'true',
},
);
}
return state;
}
Future<Map<String, dynamic>> flutterToggleDebugPaintSizeEnabled({
@required String isolateId,
}) => _flutterToggle('debugPaint', isolateId: isolateId);
Future<Map<String, dynamic>> flutterToggleDebugCheckElevationsEnabled({
@required String isolateId,
}) => _flutterToggle('debugCheckElevationsEnabled', isolateId: isolateId);
Future<Map<String, dynamic>> flutterTogglePerformanceOverlayOverride({
@required String isolateId,
}) => _flutterToggle('showPerformanceOverlay', isolateId: isolateId);
Future<Map<String, dynamic>> flutterToggleWidgetInspector({
@required String isolateId,
}) => _flutterToggle('inspector.show', isolateId: isolateId);
Future<Map<String,dynamic>> flutterToggleInvertOversizedImages({
@required String isolateId,
}) => _flutterToggle('invertOversizedImages', isolateId: isolateId);
Future<Map<String, dynamic>> flutterToggleProfileWidgetBuilds({
@required String isolateId,
}) => _flutterToggle('profileWidgetBuilds', isolateId: isolateId);
Future<Map<String, dynamic>> flutterDebugAllowBanner(bool show, {
@required String isolateId,
}) {
return invokeFlutterExtensionRpcRaw(
'ext.flutter.debugAllowBanner',
isolateId: isolateId,
args: <String, dynamic>{'enabled': show ? 'true' : 'false'},
);
}
Future<Map<String, dynamic>> flutterReassemble({
@required String isolateId,
}) {
return invokeFlutterExtensionRpcRaw(
'ext.flutter.reassemble',
isolateId: isolateId,
);
}
Future<Map<String, dynamic>> flutterFastReassemble({
@required String isolateId,
@required String className,
}) {
return invokeFlutterExtensionRpcRaw(
'ext.flutter.fastReassemble',
isolateId: isolateId,
args: <String, Object>{
'className': className,
},
);
}
Future<bool> flutterAlreadyPaintedFirstUsefulFrame({
@required String isolateId,
}) async {
final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw(
'ext.flutter.didSendFirstFrameRasterizedEvent',
isolateId: isolateId,
);
// result might be null when the service extension is not initialized
return result != null && result['enabled'] == 'true';
}
Future<Map<String, dynamic>> uiWindowScheduleFrame({
@required String isolateId,
}) {
return invokeFlutterExtensionRpcRaw(
'ext.ui.window.scheduleFrame',
isolateId: isolateId,
);
}
Future<Map<String, dynamic>> flutterEvictAsset(String assetPath, {
@required String isolateId,
}) {
return invokeFlutterExtensionRpcRaw(
'ext.flutter.evict',
isolateId: isolateId,
args: <String, dynamic>{
'value': assetPath,
},
);
}
/// Exit the application by calling [exit] from `dart:io`.
///
/// This method is only supported by certain embedders. This is
/// described by [Device.supportsFlutterExit].
Future<void> flutterExit({
@required String isolateId,
}) {
return invokeFlutterExtensionRpcRaw(
'ext.flutter.exit',
isolateId: isolateId,
).catchError((dynamic error, StackTrace stackTrace) {
globals.logger.printTrace('Failure in ext.flutter.exit: $error\n$stackTrace');
// Do nothing on sentinel or exception, the isolate already exited.
}, test: (dynamic error) => error is vm_service.SentinelException || error is vm_service.RPCError);
}
/// Return the current platform override for the flutter view running with
/// the main isolate [isolateId].
///
/// If a non-null value is provided for [platform], the platform override
/// is updated with this value.
Future<String> flutterPlatformOverride({
String platform,
@required String isolateId,
}) async {
final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw(
'ext.flutter.platformOverride',
isolateId: isolateId,
args: platform != null
? <String, dynamic>{'value': platform}
: <String, String>{},
);
if (result != null && result['value'] is String) {
return result['value'] as String;
}
return 'unknown';
}
/// Return the current brightness value for the flutter view running with
/// the main isolate [isolateId].
///
/// If a non-null value is provided for [brightness], the brightness override
/// is updated with this value.
Future<Brightness> flutterBrightnessOverride({
Brightness brightness,
@required String isolateId,
}) async {
final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw(
'ext.flutter.brightnessOverride',
isolateId: isolateId,
args: brightness != null
? <String, dynamic>{'value': brightness.toString()}
: <String, String>{},
);
if (result != null && result['value'] is String) {
return (result['value'] as String) == 'Brightness.light'
? Brightness.light
: Brightness.dark;
}
return null;
}
/// Invoke a flutter extension method, if the flutter extension is not
/// available, returns null.
Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw(
String method, {
@required String isolateId,
Map<String, dynamic> args,
}) async {
try {
final vm_service.Response response = await callServiceExtension(
method,
args: <String, Object>{
'isolateId': isolateId,
...?args,
},
);
return response.json;
} on vm_service.RPCError catch (err) {
// If an application is not using the framework
if (err.code == RPCErrorCodes.kMethodNotFound) {
return null;
}
rethrow;
}
}
/// List all [FlutterView]s attached to the current VM.
///
/// If this returns an empty list, it will poll forever unless [returnEarly]
/// is set to true.
///
/// By default, the poll duration is 50 milliseconds.
Future<List<FlutterView>> getFlutterViews({
bool returnEarly = false,
Duration delay = const Duration(milliseconds: 50),
}) async {
while (true) {
final vm_service.Response response = await callMethod(
kListViewsMethod,
);
final List<Object> rawViews = response.json['views'] as List<Object>;
final List<FlutterView> views = <FlutterView>[
for (final Object rawView in rawViews)
FlutterView.parse(rawView as Map<String, Object>)
];
if (views.isNotEmpty || returnEarly) {
return views;
}
await Future<void>.delayed(delay);
}
}
/// Attempt to retrieve the isolate with id [isolateId], or `null` if it has
/// been collected.
Future<vm_service.Isolate> getIsolateOrNull(String isolateId) {
return getIsolate(isolateId)
.catchError((dynamic error, StackTrace stackTrace) {
return null;
}, test: (dynamic error) => error is vm_service.SentinelException);
}
/// Create a new development file system on the device.
Future<vm_service.Response> createDevFS(String fsName) {
return callServiceExtension('_createDevFS', args: <String, dynamic>{'fsName': fsName});
}
/// Delete an existing file system.
Future<vm_service.Response> deleteDevFS(String fsName) {
return callServiceExtension('_deleteDevFS', args: <String, dynamic>{'fsName': fsName});
}
Future<vm_service.Response> screenshot() {
return callServiceExtension(kScreenshotMethod);
}
Future<vm_service.Response> screenshotSkp() {
return callServiceExtension(kScreenshotSkpMethod);
}
/// Set the VM timeline flags.
Future<vm_service.Response> setVMTimelineFlags(List<String> recordedStreams) {
assert(recordedStreams != null);
return callServiceExtension(
'setVMTimelineFlags',
args: <String, dynamic>{
'recordedStreams': recordedStreams,
},
);
}
Future<vm_service.Response> getVMTimeline() {
return callServiceExtension('getVMTimeline');
}
}
/// Whether the event attached to an [Isolate.pauseEvent] should be considered
/// a "pause" event.
bool isPauseEvent(String kind) {
return kind == vm_service.EventKind.kPauseStart ||
kind == vm_service.EventKind.kPauseExit ||
kind == vm_service.EventKind.kPauseBreakpoint ||
kind == vm_service.EventKind.kPauseInterrupted ||
kind == vm_service.EventKind.kPauseException ||
kind == vm_service.EventKind.kPausePostRequest ||
kind == vm_service.EventKind.kNone;
}
// TODO(jonahwilliams): either refactor drive to use the resident runner
// or delete it.
Future<String> sharedSkSlWriter(Device device, Map<String, Object> data, {
File outputFile,
Logger logger,
}) async {
logger ??= globals.logger;
if (data.isEmpty) {
logger.printStatus(
'No data was received. To ensure SkSL data can be generated use a '
'physical device then:\n'
' 1. Pass "--cache-sksl" as an argument to flutter run.\n'
' 2. Interact with the application to force shaders to be compiled.\n'
);
return null;
}
if (outputFile == null) {
outputFile = globals.fsUtils.getUniqueFile(
globals.fs.currentDirectory,
'flutter',
'sksl.json',
);
} else if (!outputFile.parent.existsSync()) {
outputFile.parent.createSync(recursive: true);
}
// Convert android sub-platforms to single target platform.
TargetPlatform targetPlatform = await device.targetPlatform;
switch (targetPlatform) {
case TargetPlatform.android_arm:
case TargetPlatform.android_arm64:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
targetPlatform = TargetPlatform.android;
break;
default:
break;
}
final Map<String, Object> manifest = <String, Object>{
'platform': getNameForTargetPlatform(targetPlatform),
'name': device.name,
'engineRevision': globals.flutterVersion.engineRevision,
'data': data,
};
outputFile.writeAsStringSync(json.encode(manifest));
logger.printStatus('Wrote SkSL data to ${outputFile.path}.');
return outputFile.path;
}
/// A brightness enum that matches the values https://github.com/flutter/engine/blob/3a96741247528133c0201ab88500c0c3c036e64e/lib/ui/window.dart#L1328
/// Describes the contrast of a theme or color palette.
enum Brightness {
/// The color is dark and will require a light text color to achieve readable
/// contrast.
///
/// For example, the color might be dark grey, requiring white text.
dark,
/// The color is light and will require a dark text color to achieve readable
/// contrast.
///
/// For example, the color might be bright white, requiring black text.
light,
}
/// Process a VM service log event into a string message.
String processVmServiceMessage(vm_service.Event event) {
final String message = utf8.decode(base64.decode(event.bytes));
// Remove extra trailing newlines appended by the vm service.
if (message.endsWith('\n')) {
return message.substring(0, message.length - 1);
}
return message;
}