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
// found in the LICENSE file.
import 'dart:async';
import 'dart:collection';
// COMMON SIGNATURES
@ -20,16 +21,46 @@ typedef void ValueChanged<T>(T value);
/// For example, service extensions use this callback because they
/// call the callback whenever the extension is called with a
/// 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);
/// 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>();
/// Signature for callbacks that filter an iterable.
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

View file

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

View file

@ -54,6 +54,7 @@ const int _kDebugPrintCapacity = 16 * 1024;
const Duration _kDebugPrintPauseTime = const Duration(seconds: 1);
final Queue<String> _debugPrintBuffer = new Queue<String>();
final Stopwatch _debugPrintStopwatch = new Stopwatch();
Completer<Null> _debugPrintCompleter;
bool _debugPrintScheduled = false;
void _debugPrintTask() {
_debugPrintScheduled = false;
@ -71,11 +72,19 @@ void _debugPrintTask() {
_debugPrintScheduled = true;
_debugPrintedCharacters = 0;
new Timer(_kDebugPrintPauseTime, _debugPrintTask);
_debugPrintCompleter ??= new Completer<Null>();
} else {
_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]+[.):] )?');
enum _WordWrapParseMode { inSpace, inWord, atBreak }
/// 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
// found in the LICENSE file.
import 'dart:async';
import 'dart:developer';
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
registerBoolServiceExtension(
name: 'debugPaint',
getter: () => debugPaintSizeEnabled,
getter: () async => debugPaintSizeEnabled,
setter: (bool value) {
if (debugPaintSizeEnabled == value)
return;
return new Future<Null>.value();
debugPaintSizeEnabled = value;
_forceRepaint();
return endOfFrame;
}
);
return true;
@ -64,19 +66,20 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
registerSignalServiceExtension(
name: 'debugDumpRenderTree',
callback: debugDumpRenderTree
callback: () { debugDumpRenderTree(); return debugPrintDone; }
);
assert(() {
// this service extension only works in checked mode
registerBoolServiceExtension(
name: 'repaintRainbow',
getter: () => debugRepaintRainbowEnabled,
getter: () async => debugRepaintRainbowEnabled,
setter: (bool value) {
bool repaint = debugRepaintRainbowEnabled && !value;
debugRepaintRainbowEnabled = value;
if (repaint)
_forceRepaint();
return endOfFrame;
}
);
return true;
@ -226,8 +229,8 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
}
@override
void reassembleApplication() {
super.reassembleApplication();
Future<Null> reassembleApplication() async {
await super.reassembleApplication();
Timeline.startSync('Dirty Render Tree');
try {
renderView.reassemble();
@ -235,6 +238,7 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
Timeline.finishSync();
}
handleBeginFrame(null);
await endOfFrame;
}
@override

View file

@ -157,8 +157,8 @@ abstract class SchedulerBinding extends BindingBase {
super.initServiceExtensions();
registerNumericServiceExtension(
name: 'timeDilation',
getter: () => timeDilation,
setter: (double value) {
getter: () async => timeDilation,
setter: (double value) async {
timeDilation = value;
}
);
@ -441,6 +441,30 @@ abstract class SchedulerBinding extends BindingBase {
_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.
bool get hasScheduledFrame => _hasScheduledFrame;
bool _hasScheduledFrame = false;

View file

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

View file

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

View file

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

View file

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

View file

@ -181,8 +181,10 @@ class ServiceProtocolDevFSOperations implements DevFSOperations {
@override
Future<dynamic> destroy(String fsName) async {
await vmService.vm.invokeRpcRaw('_deleteDevFS',
<String, dynamic> { 'fsName': fsName });
await vmService.vm.invokeRpcRaw(
'_deleteDevFS',
params: <String, dynamic> { 'fsName': fsName },
);
}
@override
@ -195,14 +197,16 @@ class ServiceProtocolDevFSOperations implements DevFSOperations {
}
String fileContents = BASE64.encode(bytes);
try {
return await vmService.vm.invokeRpcRaw('_writeDevFSFile',
<String, dynamic> {
return await vmService.vm.invokeRpcRaw(
'_writeDevFSFile',
params: <String, dynamic> {
'fsName': fsName,
'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;
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.
Future<List<DiscoveredApp>> discoverApps() =>

View file

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

View file

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

View file

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

View file

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

View file

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