Introduce the concept of asynchronicity to the service extensions. (#7823)

This allows us, for example, to wait for the slow mode banner to have
been removed from the screen before triggering a screen shot.
This commit is contained in:
Ian Hickson 2017-02-02 15:48:35 -08:00 committed by GitHub
parent a742a5ddf6
commit dc634e195e
16 changed files with 261 additions and 146 deletions

View file

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'dart:collection'; import 'dart:collection';
// COMMON SIGNATURES // COMMON SIGNATURES
@ -20,16 +21,46 @@ typedef void ValueChanged<T>(T value);
/// For example, service extensions use this callback because they /// For example, service extensions use this callback because they
/// call the callback whenever the extension is called with a /// call the callback whenever the extension is called with a
/// value, regardless of whether the given value is new or not. /// value, regardless of whether the given value is new or not.
///
/// See also:
/// * [ValueGetter], the getter equivalent of this signature.
/// * [AsyncValueSetter], an asynchronous version of this signature.
typedef void ValueSetter<T>(T value); typedef void ValueSetter<T>(T value);
/// Signature for callbacks that are to report a value on demand. /// Signature for callbacks that are to report a value on demand.
/// ///
/// See also [ValueSetter]. /// See also:
/// * [ValueSetter], the setter equivalent of this signature.
/// * [AsyncValueGetter], an asynchronous version of this signature.
typedef T ValueGetter<T>(); typedef T ValueGetter<T>();
/// Signature for callbacks that filter an iterable. /// Signature for callbacks that filter an iterable.
typedef Iterable<T> IterableFilter<T>(Iterable<T> input); typedef Iterable<T> IterableFilter<T>(Iterable<T> input);
/// Signature of callbacks that have no arguments and return no data, but that
/// return a [Future] to indicate when their work is complete.
///
/// See also:
/// * [VoidCallback], a synchronous version of this signature.
/// * [AsyncValueGetter], a signature for asynchronous getters.
/// * [AsyncValueSetter], a signature for asynchronous setters.
typedef Future<Null> AsyncCallback();
/// Signature for callbacks that report that a value has been set and return a
/// [Future] that completes when the value has been saved.
///
/// See also:
/// * [ValueSetter], a synchronous version of this signature.
/// * [AsyncValueGetter], the getter equivalent of this signature.
typedef Future<Null> AsyncValueSetter<T>(T value);
/// Signature for callbacks that are to asynchronously report a value on demand.
///
/// See also:
/// * [ValueGetter], a synchronous version of this signature.
/// * [AsyncValueSetter], the setter equivalent of this signature.
typedef Future<T> AsyncValueGetter<T>();
// BITFIELD // BITFIELD

View file

@ -113,7 +113,7 @@ abstract class BindingBase {
); );
registerSignalServiceExtension( registerSignalServiceExtension(
name: 'frameworkPresent', name: 'frameworkPresent',
callback: () => null callback: () => new Future<Null>.value()
); );
assert(() { _debugServiceExtensionsRegistered = true; return true; }); assert(() { _debugServiceExtensionsRegistered = true; return true; });
} }
@ -128,8 +128,9 @@ abstract class BindingBase {
/// code, and to flush any caches of previously computed values, in /// code, and to flush any caches of previously computed values, in
/// case the new code would compute them differently. /// case the new code would compute them differently.
@mustCallSuper @mustCallSuper
void reassembleApplication() { Future<Null> reassembleApplication() {
FlutterError.resetErrorCount(); FlutterError.resetErrorCount();
return new Future<Null>.value();
} }
@ -141,14 +142,14 @@ abstract class BindingBase {
@protected @protected
void registerSignalServiceExtension({ void registerSignalServiceExtension({
@required String name, @required String name,
@required VoidCallback callback @required AsyncCallback callback
}) { }) {
assert(name != null); assert(name != null);
assert(callback != null); assert(callback != null);
registerServiceExtension( registerServiceExtension(
name: name, name: name,
callback: (Map<String, String> parameters) async { callback: (Map<String, String> parameters) async {
callback(); await callback();
return <String, dynamic>{}; return <String, dynamic>{};
} }
); );
@ -169,8 +170,8 @@ abstract class BindingBase {
@protected @protected
void registerBoolServiceExtension({ void registerBoolServiceExtension({
String name, String name,
@required ValueGetter<bool> getter, @required AsyncValueGetter<bool> getter,
@required ValueSetter<bool> setter @required AsyncValueSetter<bool> setter
}) { }) {
assert(name != null); assert(name != null);
assert(getter != null); assert(getter != null);
@ -179,8 +180,8 @@ abstract class BindingBase {
name: name, name: name,
callback: (Map<String, String> parameters) async { callback: (Map<String, String> parameters) async {
if (parameters.containsKey('enabled')) if (parameters.containsKey('enabled'))
setter(parameters['enabled'] == 'true'); await setter(parameters['enabled'] == 'true');
return <String, dynamic>{ 'enabled': getter() }; return <String, dynamic>{ 'enabled': await getter() };
} }
); );
} }
@ -199,8 +200,8 @@ abstract class BindingBase {
@protected @protected
void registerNumericServiceExtension({ void registerNumericServiceExtension({
@required String name, @required String name,
@required ValueGetter<double> getter, @required AsyncValueGetter<double> getter,
@required ValueSetter<double> setter @required AsyncValueSetter<double> setter
}) { }) {
assert(name != null); assert(name != null);
assert(getter != null); assert(getter != null);
@ -209,8 +210,8 @@ abstract class BindingBase {
name: name, name: name,
callback: (Map<String, String> parameters) async { callback: (Map<String, String> parameters) async {
if (parameters.containsKey(name)) if (parameters.containsKey(name))
setter(double.parse(parameters[name])); await setter(double.parse(parameters[name]));
return <String, dynamic>{ name: getter() }; return <String, dynamic>{ name: await getter() };
} }
); );
} }
@ -228,8 +229,8 @@ abstract class BindingBase {
@protected @protected
void registerStringServiceExtension({ void registerStringServiceExtension({
@required String name, @required String name,
@required ValueGetter<String> getter, @required AsyncValueGetter<String> getter,
@required ValueSetter<String> setter @required AsyncValueSetter<String> setter
}) { }) {
assert(name != null); assert(name != null);
assert(getter != null); assert(getter != null);
@ -238,8 +239,8 @@ abstract class BindingBase {
name: name, name: name,
callback: (Map<String, String> parameters) async { callback: (Map<String, String> parameters) async {
if (parameters.containsKey('value')) if (parameters.containsKey('value'))
setter(parameters['value']); await setter(parameters['value']);
return <String, dynamic>{ 'value': getter() }; return <String, dynamic>{ 'value': await getter() };
} }
); );
} }
@ -300,6 +301,6 @@ abstract class BindingBase {
} }
/// Terminate the Flutter application. /// Terminate the Flutter application.
void _exitApplication() { Future<Null> _exitApplication() async {
exit(0); exit(0);
} }

View file

@ -54,6 +54,7 @@ const int _kDebugPrintCapacity = 16 * 1024;
const Duration _kDebugPrintPauseTime = const Duration(seconds: 1); const Duration _kDebugPrintPauseTime = const Duration(seconds: 1);
final Queue<String> _debugPrintBuffer = new Queue<String>(); final Queue<String> _debugPrintBuffer = new Queue<String>();
final Stopwatch _debugPrintStopwatch = new Stopwatch(); final Stopwatch _debugPrintStopwatch = new Stopwatch();
Completer<Null> _debugPrintCompleter;
bool _debugPrintScheduled = false; bool _debugPrintScheduled = false;
void _debugPrintTask() { void _debugPrintTask() {
_debugPrintScheduled = false; _debugPrintScheduled = false;
@ -71,11 +72,19 @@ void _debugPrintTask() {
_debugPrintScheduled = true; _debugPrintScheduled = true;
_debugPrintedCharacters = 0; _debugPrintedCharacters = 0;
new Timer(_kDebugPrintPauseTime, _debugPrintTask); new Timer(_kDebugPrintPauseTime, _debugPrintTask);
_debugPrintCompleter ??= new Completer<Null>();
} else { } else {
_debugPrintStopwatch.start(); _debugPrintStopwatch.start();
_debugPrintCompleter?.complete();
_debugPrintCompleter = null;
} }
} }
/// A Future that resolves when there is no longer any buffered content being
/// printed by [debugPrintThrottled] (which is the default implementation for
/// [debugPrint], which is used to report errors to the console).
Future<Null> get debugPrintDone => _debugPrintCompleter?.future ?? new Future<Null>.value();
final RegExp _indentPattern = new RegExp('^ *(?:[-+*] |[0-9]+[.):] )?'); final RegExp _indentPattern = new RegExp('^ *(?:[-+*] |[0-9]+[.):] )?');
enum _WordWrapParseMode { inSpace, inWord, atBreak } enum _WordWrapParseMode { inSpace, inWord, atBreak }
/// Wraps the given string at the given width. /// Wraps the given string at the given width.

View file

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'dart:developer'; import 'dart:developer';
import 'dart:ui' as ui show window; import 'dart:ui' as ui show window;
@ -51,12 +52,13 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
// this service extension only works in checked mode // this service extension only works in checked mode
registerBoolServiceExtension( registerBoolServiceExtension(
name: 'debugPaint', name: 'debugPaint',
getter: () => debugPaintSizeEnabled, getter: () async => debugPaintSizeEnabled,
setter: (bool value) { setter: (bool value) {
if (debugPaintSizeEnabled == value) if (debugPaintSizeEnabled == value)
return; return new Future<Null>.value();
debugPaintSizeEnabled = value; debugPaintSizeEnabled = value;
_forceRepaint(); _forceRepaint();
return endOfFrame;
} }
); );
return true; return true;
@ -64,19 +66,20 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
registerSignalServiceExtension( registerSignalServiceExtension(
name: 'debugDumpRenderTree', name: 'debugDumpRenderTree',
callback: debugDumpRenderTree callback: () { debugDumpRenderTree(); return debugPrintDone; }
); );
assert(() { assert(() {
// this service extension only works in checked mode // this service extension only works in checked mode
registerBoolServiceExtension( registerBoolServiceExtension(
name: 'repaintRainbow', name: 'repaintRainbow',
getter: () => debugRepaintRainbowEnabled, getter: () async => debugRepaintRainbowEnabled,
setter: (bool value) { setter: (bool value) {
bool repaint = debugRepaintRainbowEnabled && !value; bool repaint = debugRepaintRainbowEnabled && !value;
debugRepaintRainbowEnabled = value; debugRepaintRainbowEnabled = value;
if (repaint) if (repaint)
_forceRepaint(); _forceRepaint();
return endOfFrame;
} }
); );
return true; return true;
@ -226,8 +229,8 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
} }
@override @override
void reassembleApplication() { Future<Null> reassembleApplication() async {
super.reassembleApplication(); await super.reassembleApplication();
Timeline.startSync('Dirty Render Tree'); Timeline.startSync('Dirty Render Tree');
try { try {
renderView.reassemble(); renderView.reassemble();
@ -235,6 +238,7 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
Timeline.finishSync(); Timeline.finishSync();
} }
handleBeginFrame(null); handleBeginFrame(null);
await endOfFrame;
} }
@override @override

View file

@ -157,8 +157,8 @@ abstract class SchedulerBinding extends BindingBase {
super.initServiceExtensions(); super.initServiceExtensions();
registerNumericServiceExtension( registerNumericServiceExtension(
name: 'timeDilation', name: 'timeDilation',
getter: () => timeDilation, getter: () async => timeDilation,
setter: (double value) { setter: (double value) async {
timeDilation = value; timeDilation = value;
} }
); );
@ -441,6 +441,30 @@ abstract class SchedulerBinding extends BindingBase {
_postFrameCallbacks.add(callback); _postFrameCallbacks.add(callback);
} }
Completer<Null> _nextFrameCompleter;
/// Returns a Future that completes after the frame completes.
///
/// If this is called between frames, a frame is immediately scheduled if
/// necessary. If this is called during a frame, the Future completes after
/// the current frame.
///
/// If the device's screen is currently turned off, this may wait a very long
/// time, since frames are not scheduled while the device's screen is turned
/// off.
Future<Null> get endOfFrame {
if (_nextFrameCompleter == null) {
if (schedulerPhase == SchedulerPhase.idle)
scheduleFrame();
_nextFrameCompleter = new Completer<Null>();
addPostFrameCallback((Duration timeStamp) {
_nextFrameCompleter.complete();
_nextFrameCompleter = null;
});
}
return _nextFrameCompleter.future;
}
/// Whether this scheduler has requested that handleBeginFrame be called soon. /// Whether this scheduler has requested that handleBeginFrame be called soon.
bool get hasScheduledFrame => _hasScheduledFrame; bool get hasScheduledFrame => _hasScheduledFrame;
bool _hasScheduledFrame = false; bool _hasScheduledFrame = false;

View file

@ -52,8 +52,8 @@ abstract class ServicesBinding extends BindingBase {
// out the cache of resources that have changed. // out the cache of resources that have changed.
// TODO(ianh): find a way to only evict affected images, not all images // TODO(ianh): find a way to only evict affected images, not all images
name: 'evict', name: 'evict',
getter: () => '', getter: () async => '',
setter: (String value) { setter: (String value) async {
rootBundle.evict(value); rootBundle.evict(value);
imageCache.clear(); imageCache.clear();
} }

View file

@ -76,28 +76,30 @@ abstract class WidgetsBinding extends BindingBase implements GestureBinding, Ren
registerSignalServiceExtension( registerSignalServiceExtension(
name: 'debugDumpApp', name: 'debugDumpApp',
callback: debugDumpApp callback: () { debugDumpApp(); return debugPrintDone; }
); );
registerBoolServiceExtension( registerBoolServiceExtension(
name: 'showPerformanceOverlay', name: 'showPerformanceOverlay',
getter: () => WidgetsApp.showPerformanceOverlayOverride, getter: () => new Future<bool>.value(WidgetsApp.showPerformanceOverlayOverride),
setter: (bool value) { setter: (bool value) {
if (WidgetsApp.showPerformanceOverlayOverride == value) if (WidgetsApp.showPerformanceOverlayOverride == value)
return; return new Future<Null>.value();
WidgetsApp.showPerformanceOverlayOverride = value; WidgetsApp.showPerformanceOverlayOverride = value;
buildOwner.reassemble(renderViewElement); buildOwner.reassemble(renderViewElement);
return endOfFrame;
} }
); );
registerBoolServiceExtension( registerBoolServiceExtension(
name: 'debugAllowBanner', name: 'debugAllowBanner',
getter: () => WidgetsApp.debugAllowBannerOverride, getter: () => new Future<bool>.value(WidgetsApp.debugAllowBannerOverride),
setter: (bool value) { setter: (bool value) {
if (WidgetsApp.debugAllowBannerOverride == value) if (WidgetsApp.debugAllowBannerOverride == value)
return; return new Future<Null>.value();
WidgetsApp.debugAllowBannerOverride = value; WidgetsApp.debugAllowBannerOverride = value;
buildOwner.reassemble(renderViewElement); buildOwner.reassemble(renderViewElement);
return endOfFrame;
} }
); );
} }
@ -365,12 +367,12 @@ abstract class WidgetsBinding extends BindingBase implements GestureBinding, Ren
} }
@override @override
void reassembleApplication() { Future<Null> reassembleApplication() {
_needToReportFirstFrame = true; _needToReportFirstFrame = true;
preventThisFrameFromBeingReportedAsFirstFrame(); preventThisFrameFromBeingReportedAsFirstFrame();
if (renderViewElement != null) if (renderViewElement != null)
buildOwner.reassemble(renderViewElement); buildOwner.reassemble(renderViewElement);
super.reassembleApplication(); return super.reassembleApplication();
} }
} }

View file

@ -432,14 +432,12 @@ class AndroidDevice extends Device {
bool get supportsScreenshot => true; bool get supportsScreenshot => true;
@override @override
Future<bool> takeScreenshot(File outputFile) { Future<Null> takeScreenshot(File outputFile) {
const String remotePath = '/data/local/tmp/flutter_screenshot.png'; const String remotePath = '/data/local/tmp/flutter_screenshot.png';
runCheckedSync(adbCommandForDevice(<String>['shell', 'screencap', '-p', remotePath])); runCheckedSync(adbCommandForDevice(<String>['shell', 'screencap', '-p', remotePath]));
runCheckedSync(adbCommandForDevice(<String>['pull', remotePath, outputFile.path])); runCheckedSync(adbCommandForDevice(<String>['pull', remotePath, outputFile.path]));
runCheckedSync(adbCommandForDevice(<String>['shell', 'rm', remotePath])); runCheckedSync(adbCommandForDevice(<String>['shell', 'rm', remotePath]));
return new Future<Null>.value();
return new Future<bool>.value(true);
} }
@override @override

View file

@ -85,8 +85,7 @@ class ScreenshotCommand extends FlutterCommand {
Future<Null> runScreenshot(File outputFile) async { Future<Null> runScreenshot(File outputFile) async {
outputFile ??= getUniqueFile(fs.currentDirectory, 'flutter', 'png'); outputFile ??= getUniqueFile(fs.currentDirectory, 'flutter', 'png');
try { try {
if (!await device.takeScreenshot(outputFile)) await device.takeScreenshot(outputFile);
throwToolExit('Screenshot failed');
} catch (error) { } catch (error) {
throwToolExit('Error taking screenshot: $error'); throwToolExit('Error taking screenshot: $error');
} }

View file

@ -181,8 +181,10 @@ class ServiceProtocolDevFSOperations implements DevFSOperations {
@override @override
Future<dynamic> destroy(String fsName) async { Future<dynamic> destroy(String fsName) async {
await vmService.vm.invokeRpcRaw('_deleteDevFS', await vmService.vm.invokeRpcRaw(
<String, dynamic> { 'fsName': fsName }); '_deleteDevFS',
params: <String, dynamic> { 'fsName': fsName },
);
} }
@override @override
@ -195,14 +197,16 @@ class ServiceProtocolDevFSOperations implements DevFSOperations {
} }
String fileContents = BASE64.encode(bytes); String fileContents = BASE64.encode(bytes);
try { try {
return await vmService.vm.invokeRpcRaw('_writeDevFSFile', return await vmService.vm.invokeRpcRaw(
<String, dynamic> { '_writeDevFSFile',
'fsName': fsName, params: <String, dynamic> {
'path': devicePath, 'fsName': fsName,
'fileContents': fileContents 'path': devicePath,
}); 'fileContents': fileContents
} catch (e) { },
printTrace('DevFS: Failed to write $devicePath: $e'); );
} catch (error) {
printTrace('DevFS: Failed to write $devicePath: $error');
} }
} }

View file

@ -219,7 +219,7 @@ abstract class Device {
bool get supportsScreenshot => false; bool get supportsScreenshot => false;
Future<bool> takeScreenshot(File outputFile) => new Future<bool>.error('unimplemented'); Future<Null> takeScreenshot(File outputFile) => new Future<Null>.error('unimplemented');
/// Find the apps that are currently running on this device. /// Find the apps that are currently running on this device.
Future<List<DiscoveredApp>> discoverApps() => Future<List<DiscoveredApp>> discoverApps() =>

View file

@ -365,12 +365,11 @@ class IOSDevice extends Device {
bool get supportsScreenshot => false; bool get supportsScreenshot => false;
@override @override
Future<bool> takeScreenshot(File outputFile) { Future<Null> takeScreenshot(File outputFile) {
// We could use idevicescreenshot here (installed along with the brew // We could use idevicescreenshot here (installed along with the brew
// ideviceinstaller tools). It however requires a developer disk image on // ideviceinstaller tools). It however requires a developer disk image on
// the device. // the device.
return new Future<Null>.error('Taking screenshots is not supported on iOS devices. Consider using a simulator instead.');
return new Future<bool>.value(false);
} }
} }

View file

@ -587,7 +587,7 @@ class IOSSimulator extends Device {
bool get supportsScreenshot => true; bool get supportsScreenshot => true;
@override @override
Future<bool> takeScreenshot(File outputFile) async { Future<Null> takeScreenshot(File outputFile) async {
Directory desktopDir = fs.directory(path.join(homeDirPath, 'Desktop')); Directory desktopDir = fs.directory(path.join(homeDirPath, 'Desktop'));
// 'Simulator Screen Shot Mar 25, 2016, 2.59.43 PM.png' // 'Simulator Screen Shot Mar 25, 2016, 2.59.43 PM.png'
@ -621,8 +621,6 @@ class IOSSimulator extends Device {
File shot = shots.first; File shot = shots.first;
outputFile.writeAsBytesSync(shot.readAsBytesSync()); outputFile.writeAsBytesSync(shot.readAsBytesSync());
shot.delete(); shot.delete();
return true;
} }
} }

View file

@ -108,19 +108,34 @@ abstract class ResidentRunner {
} }
Future<Null> _screenshot() async { Future<Null> _screenshot() async {
Status status = logger.startProgress('Taking screenshot...');
File outputFile = getUniqueFile(fs.currentDirectory, 'flutter', 'png'); File outputFile = getUniqueFile(fs.currentDirectory, 'flutter', 'png');
try { try {
if (vmService != null) if (vmService != null)
await vmService.vm.refreshViews(); await vmService.vm.refreshViews();
if (isRunningDebug) try {
await currentView.uiIsolate.flutterDebugAllowBanner(false); if (isRunningDebug)
if (!await device.takeScreenshot(outputFile)) await currentView.uiIsolate.flutterDebugAllowBanner(false);
printError('Error taking screenshot.'); } catch (error) {
if (isRunningDebug) status.stop();
await currentView.uiIsolate.flutterDebugAllowBanner(true); printError(error);
}
try {
await device.takeScreenshot(outputFile);
} finally {
try {
if (isRunningDebug)
await currentView.uiIsolate.flutterDebugAllowBanner(true);
} catch (error) {
status.stop();
printError(error);
}
}
int sizeKB = (await outputFile.length()) ~/ 1024; int sizeKB = (await outputFile.length()) ~/ 1024;
status.stop();
printStatus('Screenshot written to ${path.relative(outputFile.path)} (${sizeKB}kB).'); printStatus('Screenshot written to ${path.relative(outputFile.path)} (${sizeKB}kB).');
} catch (error) { } catch (error) {
status.stop();
printError('Error taking screenshot: $error'); printError('Error taking screenshot: $error');
} }
} }

View file

@ -34,7 +34,7 @@ class VMService {
/// ///
/// Requests made via the returns [VMService] time out after [requestTimeout] /// Requests made via the returns [VMService] time out after [requestTimeout]
/// amount of time, which is [kDefaultRequestTimeout] by default. /// amount of time, which is [kDefaultRequestTimeout] by default.
static Future<VMService> connect(Uri httpUri, {Duration requestTimeout: kDefaultRequestTimeout}) async { static Future<VMService> connect(Uri httpUri, { Duration requestTimeout: kDefaultRequestTimeout }) async {
Uri wsUri = httpUri.replace(scheme: 'ws', path: path.join(httpUri.path, 'ws')); Uri wsUri = httpUri.replace(scheme: 'ws', path: path.join(httpUri.path, 'ws'));
WebSocket ws; WebSocket ws;
try { try {
@ -264,7 +264,7 @@ abstract class ServiceObject {
Map<String, dynamic> params = <String, dynamic>{ Map<String, dynamic> params = <String, dynamic>{
'objectId': id, 'objectId': id,
}; };
return _owner.isolate.invokeRpcRaw('getObject', params); return _owner.isolate.invokeRpcRaw('getObject', params: params);
} }
Future<ServiceObject> _inProgressReload; Future<ServiceObject> _inProgressReload;
@ -437,7 +437,7 @@ class VM extends ServiceObjectOwner {
@override @override
Future<Map<String, dynamic>> _fetchDirect() async { Future<Map<String, dynamic>> _fetchDirect() async {
return invokeRpcRaw('getVM', <String, dynamic> {}); return invokeRpcRaw('getVM');
} }
@override @override
@ -567,27 +567,39 @@ class VM extends ServiceObjectOwner {
} }
/// Invoke the RPC and return the raw response. /// Invoke the RPC and return the raw response.
Future<Map<String, dynamic>> invokeRpcRaw( ///
String method, [Map<String, dynamic> params, Duration timeout]) async { /// If `timeoutFatal` is false, then a timeout will result in a null return
if (params == null) { /// value. Otherwise, it results in an exception.
params = <String, dynamic>{}; Future<Map<String, dynamic>> invokeRpcRaw(String method, {
} Map<String, dynamic> params: const <String, dynamic>{},
Duration timeout,
bool timeoutFatal: true,
}) async {
assert(params != null);
timeout ??= _vmService._requestTimeout;
try { try {
Map<String, dynamic> result = await _vmService.peer Map<String, dynamic> result = await _vmService.peer
.sendRequest(method, params) .sendRequest(method, params)
.timeout(timeout ?? _vmService._requestTimeout); .timeout(timeout);
return result; return result;
} on TimeoutException catch(_) { } on TimeoutException {
printError('Request to Dart VM Service timed out: $method($params)'); printTrace('Request to Dart VM Service timed out: $method($params)');
rethrow; if (timeoutFatal)
throw new TimeoutException('Request to Dart VM Service timed out: $method($params)');
return null;
} }
} }
/// Invoke the RPC and return a ServiceObject response. /// Invoke the RPC and return a [ServiceObject] response.
Future<ServiceObject> invokeRpc( Future<ServiceObject> invokeRpc(String method, {
String method, { Map<String, dynamic> params, Duration timeout }) async { Map<String, dynamic> params: const <String, dynamic>{},
Map<String, dynamic> response = await invokeRpcRaw(method, params, timeout); Duration timeout,
}) async {
Map<String, dynamic> response = await invokeRpcRaw(
method,
params: params,
timeout: timeout,
);
ServiceObject serviceObject = new ServiceObject._fromMap(this, response); ServiceObject serviceObject = new ServiceObject._fromMap(this, response);
if ((serviceObject != null) && (serviceObject._canCache)) { if ((serviceObject != null) && (serviceObject._canCache)) {
String serviceObjectId = serviceObject.id; String serviceObjectId = serviceObject.id;
@ -597,19 +609,13 @@ class VM extends ServiceObjectOwner {
} }
/// Create a new development file system on the device. /// Create a new development file system on the device.
Future<Map<String, dynamic>> createDevFS(String fsName) async { Future<Map<String, dynamic>> createDevFS(String fsName) {
Map<String, dynamic> response = return invokeRpcRaw('_createDevFS', params: <String, dynamic> { 'fsName': fsName });
await invokeRpcRaw('_createDevFS', <String, dynamic> {
'fsName': fsName
});
return response;
} }
/// List the development file system son the device. /// List the development file system son the device.
Future<List<String>> listDevFS() async { Future<List<String>> listDevFS() async {
Map<String, dynamic> response = return (await invokeRpcRaw('_listDevFS'))['fsNames'];
await invokeRpcRaw('_listDevFS', <String, dynamic>{});
return response['fsNames'];
} }
// Write one file into a file system. // Write one file into a file system.
@ -619,36 +625,36 @@ class VM extends ServiceObjectOwner {
}) { }) {
assert(path != null); assert(path != null);
assert(fileContents != null); assert(fileContents != null);
return invokeRpcRaw(
return invokeRpcRaw('_writeDevFSFile', <String, dynamic> { '_writeDevFSFile',
'fsName': fsName, params: <String, dynamic>{
'path': path, 'fsName': fsName,
'fileContents': BASE64.encode(fileContents) 'path': path,
}); 'fileContents': BASE64.encode(fileContents),
},
);
} }
// Read one file from a file system. // Read one file from a file system.
Future<List<int>> readDevFSFile(String fsName, String path) { Future<List<int>> readDevFSFile(String fsName, String path) async {
return invokeRpcRaw('_readDevFSFile', <String, dynamic> { Map<String, dynamic> response = await invokeRpcRaw(
'fsName': fsName, '_readDevFSFile',
'path': path params: <String, dynamic>{
}).then((Map<String, dynamic> response) { 'fsName': fsName,
return BASE64.decode(response['fileContents']); 'path': path,
}); },
);
return BASE64.decode(response['fileContents']);
} }
/// The complete list of a file system. /// The complete list of a file system.
Future<List<String>> listDevFSFiles(String fsName) { Future<List<String>> listDevFSFiles(String fsName) async {
return invokeRpcRaw('_listDevFSFiles', <String, dynamic> { return (await invokeRpcRaw('_listDevFSFiles', params: <String, dynamic>{ 'fsName': fsName }))['files'];
'fsName': fsName
}).then((Map<String, dynamic> response) {
return response['files'];
});
} }
/// Delete an existing file system. /// Delete an existing file system.
Future<Map<String, dynamic>> deleteDevFS(String fsName) { Future<Map<String, dynamic>> deleteDevFS(String fsName) {
return invokeRpcRaw('_deleteDevFS', <String, dynamic> { 'fsName': fsName }); return invokeRpcRaw('_deleteDevFS', params: <String, dynamic>{ 'fsName': fsName });
} }
Future<ServiceMap> runInView(String viewId, Future<ServiceMap> runInView(String viewId,
@ -665,20 +671,21 @@ class VM extends ServiceObjectOwner {
} }
Future<Map<String, dynamic>> clearVMTimeline() { Future<Map<String, dynamic>> clearVMTimeline() {
return invokeRpcRaw('_clearVMTimeline', <String, dynamic>{}); return invokeRpcRaw('_clearVMTimeline');
} }
Future<Map<String, dynamic>> setVMTimelineFlags( Future<Map<String, dynamic>> setVMTimelineFlags(List<String> recordedStreams) {
List<String> recordedStreams) {
assert(recordedStreams != null); assert(recordedStreams != null);
return invokeRpcRaw(
return invokeRpcRaw('_setVMTimelineFlags', <String, dynamic> { '_setVMTimelineFlags',
'recordedStreams': recordedStreams params: <String, dynamic>{
}); 'recordedStreams': recordedStreams,
},
);
} }
Future<Map<String, dynamic>> getVMTimeline() { Future<Map<String, dynamic>> getVMTimeline() {
return invokeRpcRaw('_getVMTimeline', <String, dynamic> {}, kLongRequestTimeout); return invokeRpcRaw('_getVMTimeline', timeout: kLongRequestTimeout);
} }
Future<Null> refreshViews() async { Future<Null> refreshViews() async {
@ -736,12 +743,15 @@ class Isolate extends ServiceObjectOwner {
@override @override
Future<Map<String, dynamic>> _fetchDirect() { Future<Map<String, dynamic>> _fetchDirect() {
return invokeRpcRaw('getIsolate', <String, dynamic>{}); return invokeRpcRaw('getIsolate');
} }
/// Invoke the RPC and return the raw response. /// Invoke the RPC and return the raw response.
Future<Map<String, dynamic>> invokeRpcRaw( Future<Map<String, dynamic>> invokeRpcRaw(String method, {
String method, [Map<String, dynamic> params, Duration timeout]) { Map<String, dynamic> params,
Duration timeout,
bool timeoutFatal: true,
}) {
// Inject the 'isolateId' parameter. // Inject the 'isolateId' parameter.
if (params == null) { if (params == null) {
params = <String, dynamic>{ params = <String, dynamic>{
@ -750,21 +760,18 @@ class Isolate extends ServiceObjectOwner {
} else { } else {
params['isolateId'] = id; params['isolateId'] = id;
} }
return vm.invokeRpcRaw(method, params, timeout); return vm.invokeRpcRaw(method, params: params, timeout: timeout, timeoutFatal: timeoutFatal);
} }
/// Invoke the RPC and return a ServiceObject response. /// Invoke the RPC and return a ServiceObject response.
Future<ServiceObject> invokeRpc( Future<ServiceObject> invokeRpc(String method, Map<String, dynamic> params) async {
String method, Map<String, dynamic> params) async { return getFromMap(await invokeRpcRaw(method, params: params));
Map<String, dynamic> response = await invokeRpcRaw(method, params);
return getFromMap(response);
} }
@override @override
void _update(Map<String, dynamic> map, bool mapIsRef) { void _update(Map<String, dynamic> map, bool mapIsRef) {
if (mapIsRef) { if (mapIsRef)
return; return;
}
_loaded = true; _loaded = true;
int startTimeMillis = map['startTime']; int startTimeMillis = map['startTime'];
@ -791,7 +798,7 @@ class Isolate extends ServiceObjectOwner {
if (packagesPath != null) { if (packagesPath != null) {
arguments['packagesUri'] = packagesPath; arguments['packagesUri'] = packagesPath;
} }
Map<String, dynamic> response = await invokeRpcRaw('_reloadSources', arguments); Map<String, dynamic> response = await invokeRpcRaw('_reloadSources', params: arguments);
return response; return response;
} on rpc.RpcException catch(e) { } on rpc.RpcException catch(e) {
return new Future<Map<String, dynamic>>.error(<String, dynamic>{ return new Future<Map<String, dynamic>>.error(<String, dynamic>{
@ -807,9 +814,14 @@ class Isolate extends ServiceObjectOwner {
// Invoke a flutter extension method, if the flutter extension is not // Invoke a flutter extension method, if the flutter extension is not
// available, returns null. // available, returns null.
Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw( Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw(
String method, { Map<String, dynamic> params, Duration timeout }) async { String method, {
Map<String, dynamic> params,
Duration timeout,
bool timeoutFatal: true,
}
) async {
try { try {
return await invokeRpcRaw(method, params, timeout); return await invokeRpcRaw(method, params: params, timeout: timeout, timeoutFatal: timeoutFatal);
} on rpc.RpcException catch (e) { } on rpc.RpcException catch (e) {
// If an application is not using the framework // If an application is not using the framework
if (e.code == rpc_error_code.METHOD_NOT_FOUND) if (e.code == rpc_error_code.METHOD_NOT_FOUND)
@ -821,29 +833,42 @@ class Isolate extends ServiceObjectOwner {
// Debug dump extension methods. // Debug dump extension methods.
Future<Map<String, dynamic>> flutterDebugDumpApp() { Future<Map<String, dynamic>> flutterDebugDumpApp() {
return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpApp'); return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpApp', timeout: kLongRequestTimeout);
} }
Future<Map<String, dynamic>> flutterDebugDumpRenderTree() { Future<Map<String, dynamic>> flutterDebugDumpRenderTree() {
return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpRenderTree'); return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpRenderTree', timeout: kLongRequestTimeout);
} }
Future<Map<String, dynamic>> flutterToggleDebugPaintSizeEnabled() async { Future<Map<String, dynamic>> flutterToggleDebugPaintSizeEnabled() async {
Map<String, dynamic> state = await invokeFlutterExtensionRpcRaw('ext.flutter.debugPaint'); Map<String, dynamic> state = await invokeFlutterExtensionRpcRaw('ext.flutter.debugPaint');
if (state != null && state.containsKey('enabled') && state['enabled'] is bool) if (state != null && state.containsKey('enabled') && state['enabled'] is bool) {
state = await invokeFlutterExtensionRpcRaw('ext.flutter.debugPaint', state = await invokeFlutterExtensionRpcRaw(
params: <String, dynamic>{ 'enabled': !state['enabled'] }); 'ext.flutter.debugPaint',
params: <String, dynamic>{ 'enabled': !state['enabled'] },
timeout: const Duration(milliseconds: 150),
timeoutFatal: false,
);
}
return state; return state;
} }
Future<Null> flutterDebugAllowBanner(bool show) async { Future<Null> flutterDebugAllowBanner(bool show) async {
await invokeFlutterExtensionRpcRaw('ext.flutter.debugAllowBanner', params: <String, dynamic>{ 'enabled': show }); await invokeFlutterExtensionRpcRaw(
'ext.flutter.debugAllowBanner',
params: <String, dynamic>{ 'enabled': show },
timeout: const Duration(milliseconds: 150),
timeoutFatal: false,
);
} }
// Reload related extension methods. // Reload related extension methods.
Future<Map<String, dynamic>> flutterReassemble() async { Future<Map<String, dynamic>> flutterReassemble() async {
return await invokeFlutterExtensionRpcRaw('ext.flutter.reassemble', return await invokeFlutterExtensionRpcRaw(
timeout: kLongRequestTimeout); 'ext.flutter.reassemble',
timeout: kLongRequestTimeout,
timeoutFatal: false,
);
} }
Future<bool> flutterFrameworkPresent() async { Future<bool> flutterFrameworkPresent() async {
@ -856,16 +881,19 @@ class Isolate extends ServiceObjectOwner {
Future<Map<String, dynamic>> flutterEvictAsset(String assetPath) async { Future<Map<String, dynamic>> flutterEvictAsset(String assetPath) async {
return await invokeFlutterExtensionRpcRaw('ext.flutter.evict', return await invokeFlutterExtensionRpcRaw('ext.flutter.evict',
params: <String, dynamic>{ params: <String, dynamic>{
'value': assetPath 'value': assetPath,
} }
); );
} }
// Application control extension methods. // Application control extension methods.
Future<Map<String, dynamic>> flutterExit() async { Future<Map<String, dynamic>> flutterExit() async {
return await invokeFlutterExtensionRpcRaw('ext.flutter.exit').timeout( return await invokeFlutterExtensionRpcRaw(
const Duration(seconds: 2), onTimeout: () => null); 'ext.flutter.exit',
timeout: const Duration(seconds: 2),
timeoutFatal: false,
);
} }
} }

View file

@ -297,8 +297,11 @@ class MockVM implements VM {
} }
@override @override
Future<Map<String, dynamic>> invokeRpcRaw( Future<Map<String, dynamic>> invokeRpcRaw(String method, {
String method, [Map<String, dynamic> params, Duration timeout]) async { Map<String, dynamic> params: const <String, dynamic>{},
Duration timeout,
bool timeoutFatal: true,
}) async {
_service.messages.add('$method $params'); _service.messages.add('$method $params');
return <String, dynamic>{'success': true}; return <String, dynamic>{'success': true};
} }