Commit dc634e19 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

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.
parent a742a5dd
......@@ -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
......
......@@ -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);
}
......@@ -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.
......
......@@ -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
......
......@@ -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;
......
......@@ -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();
}
......
......@@ -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();
}
}
......
......@@ -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
......
......@@ -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');
}
......
......@@ -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> {
'fsName': fsName,
'path': devicePath,
'fileContents': fileContents
});
} catch (e) {
printTrace('DevFS: Failed to write $devicePath: $e');
return await vmService.vm.invokeRpcRaw(
'_writeDevFSFile',
params: <String, dynamic> {
'fsName': fsName,
'path': devicePath,
'fileContents': fileContents
},
);
} catch (error) {
printTrace('DevFS: Failed to write $devicePath: $error');
}
}
......
......@@ -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() =>
......
......@@ -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.');
}
}
......
......@@ -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;
}
}
......
......@@ -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();
if (isRunningDebug)
await currentView.uiIsolate.flutterDebugAllowBanner(false);
if (!await device.takeScreenshot(outputFile))
printError('Error taking screenshot.');
if (isRunningDebug)
await currentView.uiIsolate.flutterDebugAllowBanner(true);
try {
if (isRunningDebug)
await currentView.uiIsolate.flutterDebugAllowBanner(false);
} 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');
}
}
......
......@@ -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};
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment