| // 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 'dart:async'; |
| import 'dart:convert' show json; |
| import 'dart:developer' as developer; |
| import 'dart:io' show exit; |
| import 'dart:ui' as ui show saveCompilationTrace, Window, window; |
| // Before adding any more dart:ui imports, please read the README. |
| |
| import 'package:meta/meta.dart'; |
| |
| import 'assertions.dart'; |
| import 'basic_types.dart'; |
| import 'constants.dart'; |
| import 'debug.dart'; |
| import 'object.dart'; |
| import 'platform.dart'; |
| import 'print.dart'; |
| |
| /// Signature for service extensions. |
| /// |
| /// The returned map must not contain the keys "type" or "method", as |
| /// they will be replaced before the value is sent to the client. The |
| /// "type" key will be set to the string `_extensionType` to indicate |
| /// that this is a return value from a service extension, and the |
| /// "method" key will be set to the full name of the method. |
| typedef ServiceExtensionCallback = Future<Map<String, dynamic>> Function(Map<String, String> parameters); |
| |
| /// Base class for mixins that provide singleton services (also known as |
| /// "bindings"). |
| /// |
| /// To use this class in an `on` clause of a mixin, inherit from it and implement |
| /// [initInstances()]. The mixin is guaranteed to only be constructed once in |
| /// the lifetime of the app (more precisely, it will assert if constructed twice |
| /// in checked mode). |
| /// |
| /// The top-most layer used to write the application will have a concrete class |
| /// that inherits from [BindingBase] and uses all the various [BindingBase] |
| /// mixins (such as [ServicesBinding]). For example, the Widgets library in |
| /// Flutter introduces a binding called [WidgetsFlutterBinding]. The relevant |
| /// library defines how to create the binding. It could be implied (for example, |
| /// [WidgetsFlutterBinding] is automatically started from [runApp]), or the |
| /// application might be required to explicitly call the constructor. |
| abstract class BindingBase { |
| /// Default abstract constructor for bindings. |
| /// |
| /// First calls [initInstances] to have bindings initialize their |
| /// instance pointers and other state, then calls |
| /// [initServiceExtensions] to have bindings initialize their |
| /// observatory service extensions, if any. |
| BindingBase() { |
| developer.Timeline.startSync('Framework initialization'); |
| |
| assert(!_debugInitialized); |
| initInstances(); |
| assert(_debugInitialized); |
| |
| assert(!_debugServiceExtensionsRegistered); |
| initServiceExtensions(); |
| assert(_debugServiceExtensionsRegistered); |
| |
| developer.postEvent('Flutter.FrameworkInitialization', <String, String>{}); |
| |
| developer.Timeline.finishSync(); |
| } |
| |
| static bool _debugInitialized = false; |
| static bool _debugServiceExtensionsRegistered = false; |
| |
| /// The window to which this binding is bound. |
| /// |
| /// A number of additional bindings are defined as extensions of [BindingBase], |
| /// e.g., [ServicesBinding], [RendererBinding], and [WidgetsBinding]. Each of |
| /// these bindings define behaviors that interact with a [ui.Window], e.g., |
| /// [ServicesBinding] registers a [ui.Window.onPlatformMessage] handler, and |
| /// [RendererBinding] registers [ui.Window.onMetricsChanged], |
| /// [ui.Window.onTextScaleFactorChanged], [ui.Window.onSemanticsEnabledChanged], |
| /// and [ui.Window.onSemanticsAction] handlers. |
| /// |
| /// Each of these other bindings could individually access a [Window] statically, |
| /// but that would preclude the ability to test these behaviors with a fake |
| /// window for verification purposes. Therefore, [BindingBase] exposes this |
| /// [Window] for use by other bindings. A subclass of [BindingBase], such as |
| /// [TestWidgetsFlutterBinding], can override this accessor to return a |
| /// different [Window] implementation, such as a [TestWindow]. |
| ui.Window get window => ui.window; |
| |
| /// The initialization method. Subclasses override this method to hook into |
| /// the platform and otherwise configure their services. Subclasses must call |
| /// "super.initInstances()". |
| /// |
| /// By convention, if the service is to be provided as a singleton, it should |
| /// be exposed as `MixinClassName.instance`, a static getter that returns |
| /// `MixinClassName._instance`, a static field that is set by |
| /// `initInstances()`. |
| @protected |
| @mustCallSuper |
| void initInstances() { |
| assert(!_debugInitialized); |
| assert(() { |
| _debugInitialized = true; |
| return true; |
| }()); |
| } |
| |
| /// Called when the binding is initialized, to register service |
| /// extensions. |
| /// |
| /// Bindings that want to expose service extensions should overload |
| /// this method to register them using calls to |
| /// [registerSignalServiceExtension], |
| /// [registerBoolServiceExtension], |
| /// [registerNumericServiceExtension], and |
| /// [registerServiceExtension] (in increasing order of complexity). |
| /// |
| /// Implementations of this method must call their superclass |
| /// implementation. |
| /// |
| /// {@macro flutter.foundation.bindingBase.registerServiceExtension} |
| /// |
| /// See also: |
| /// |
| /// * <https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#rpcs-requests-and-responses> |
| @protected |
| @mustCallSuper |
| void initServiceExtensions() { |
| assert(!_debugServiceExtensionsRegistered); |
| |
| assert(() { |
| registerSignalServiceExtension( |
| name: 'reassemble', |
| callback: reassembleApplication, |
| ); |
| return true; |
| }()); |
| |
| if (!kReleaseMode && !kIsWeb) { |
| registerSignalServiceExtension( |
| name: 'exit', |
| callback: _exitApplication, |
| ); |
| registerServiceExtension( |
| name: 'saveCompilationTrace', |
| callback: (Map<String, String> parameters) async { |
| return <String, dynamic>{ |
| 'value': ui.saveCompilationTrace(), |
| }; |
| }, |
| ); |
| } |
| |
| assert(() { |
| const String platformOverrideExtensionName = 'platformOverride'; |
| registerServiceExtension( |
| name: platformOverrideExtensionName, |
| callback: (Map<String, String> parameters) async { |
| if (parameters.containsKey('value')) { |
| switch (parameters['value']) { |
| case 'android': |
| debugDefaultTargetPlatformOverride = TargetPlatform.android; |
| break; |
| case 'fuchsia': |
| debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; |
| break; |
| case 'iOS': |
| debugDefaultTargetPlatformOverride = TargetPlatform.iOS; |
| break; |
| case 'linux': |
| debugDefaultTargetPlatformOverride = TargetPlatform.linux; |
| break; |
| case 'macOS': |
| debugDefaultTargetPlatformOverride = TargetPlatform.macOS; |
| break; |
| case 'windows': |
| debugDefaultTargetPlatformOverride = TargetPlatform.windows; |
| break; |
| case 'default': |
| default: |
| debugDefaultTargetPlatformOverride = null; |
| } |
| _postExtensionStateChangedEvent( |
| platformOverrideExtensionName, |
| defaultTargetPlatform.toString().substring('$TargetPlatform.'.length), |
| ); |
| await reassembleApplication(); |
| } |
| return <String, dynamic>{ |
| 'value': defaultTargetPlatform |
| .toString() |
| .substring('$TargetPlatform.'.length), |
| }; |
| }, |
| ); |
| return true; |
| }()); |
| assert(() { |
| _debugServiceExtensionsRegistered = true; |
| return true; |
| }()); |
| } |
| |
| /// Whether [lockEvents] is currently locking events. |
| /// |
| /// Binding subclasses that fire events should check this first, and if it is |
| /// set, queue events instead of firing them. |
| /// |
| /// Events should be flushed when [unlocked] is called. |
| @protected |
| bool get locked => _lockCount > 0; |
| int _lockCount = 0; |
| |
| /// Locks the dispatching of asynchronous events and callbacks until the |
| /// callback's future completes. |
| /// |
| /// This causes input lag and should therefore be avoided when possible. It is |
| /// primarily intended for use during non-user-interactive time such as to |
| /// allow [reassembleApplication] to block input while it walks the tree |
| /// (which it partially does asynchronously). |
| /// |
| /// The [Future] returned by the `callback` argument is returned by [lockEvents]. |
| @protected |
| Future<void> lockEvents(Future<void> callback()) { |
| developer.Timeline.startSync('Lock events'); |
| |
| assert(callback != null); |
| _lockCount += 1; |
| final Future<void> future = callback(); |
| assert(future != null, 'The lockEvents() callback returned null; it should return a Future<void> that completes when the lock is to expire.'); |
| future.whenComplete(() { |
| _lockCount -= 1; |
| if (!locked) { |
| developer.Timeline.finishSync(); |
| unlocked(); |
| } |
| }); |
| return future; |
| } |
| |
| /// Called by [lockEvents] when events get unlocked. |
| /// |
| /// This should flush any events that were queued while [locked] was true. |
| @protected |
| @mustCallSuper |
| void unlocked() { |
| assert(!locked); |
| } |
| |
| /// Cause the entire application to redraw, e.g. after a hot reload. |
| /// |
| /// This is used by development tools when the application code has changed, |
| /// to cause the application to pick up any changed code. It can be triggered |
| /// manually by sending the `ext.flutter.reassemble` service extension signal. |
| /// |
| /// This method is very computationally expensive and should not be used in |
| /// production code. There is never a valid reason to cause the entire |
| /// application to repaint in production. All aspects of the Flutter framework |
| /// know how to redraw when necessary. It is only necessary in development |
| /// when the code is literally changed on the fly (e.g. in hot reload) or when |
| /// debug flags are being toggled. |
| /// |
| /// While this method runs, events are locked (e.g. pointer events are not |
| /// dispatched). |
| /// |
| /// Subclasses (binding classes) should override [performReassemble] to react |
| /// to this method being called. This method itself should not be overridden. |
| Future<void> reassembleApplication() { |
| return lockEvents(performReassemble); |
| } |
| |
| /// This method is called by [reassembleApplication] to actually cause the |
| /// application to reassemble, e.g. after a hot reload. |
| /// |
| /// Bindings are expected to use this method to re-register anything that uses |
| /// closures, so that they do not keep pointing to old code, and to flush any |
| /// caches of previously computed values, in case the new code would compute |
| /// them differently. For example, the rendering layer triggers the entire |
| /// application to repaint when this is called. |
| /// |
| /// Do not call this method directly. Instead, use [reassembleApplication]. |
| @mustCallSuper |
| @protected |
| Future<void> performReassemble() { |
| FlutterError.resetErrorCount(); |
| return Future<void>.value(); |
| } |
| |
| /// Registers a service extension method with the given name (full |
| /// name "ext.flutter.name"), which takes no arguments and returns |
| /// no value. |
| /// |
| /// Calls the `callback` callback when the service extension is called. |
| /// |
| /// {@macro flutter.foundation.bindingBase.registerServiceExtension} |
| @protected |
| void registerSignalServiceExtension({ |
| @required String name, |
| @required AsyncCallback callback, |
| }) { |
| assert(name != null); |
| assert(callback != null); |
| registerServiceExtension( |
| name: name, |
| callback: (Map<String, String> parameters) async { |
| await callback(); |
| return <String, dynamic>{}; |
| }, |
| ); |
| } |
| |
| /// Registers a service extension method with the given name (full |
| /// name "ext.flutter.name"), which takes a single argument |
| /// "enabled" which can have the value "true" or the value "false" |
| /// or can be omitted to read the current value. (Any value other |
| /// than "true" is considered equivalent to "false". Other arguments |
| /// are ignored.) |
| /// |
| /// Calls the `getter` callback to obtain the value when |
| /// responding to the service extension method being called. |
| /// |
| /// Calls the `setter` callback with the new value when the |
| /// service extension method is called with a new value. |
| /// |
| /// {@macro flutter.foundation.bindingBase.registerServiceExtension} |
| @protected |
| void registerBoolServiceExtension({ |
| @required String name, |
| @required AsyncValueGetter<bool> getter, |
| @required AsyncValueSetter<bool> setter, |
| }) { |
| assert(name != null); |
| assert(getter != null); |
| assert(setter != null); |
| registerServiceExtension( |
| name: name, |
| callback: (Map<String, String> parameters) async { |
| if (parameters.containsKey('enabled')) { |
| await setter(parameters['enabled'] == 'true'); |
| _postExtensionStateChangedEvent(name, await getter() ? 'true' : 'false'); |
| } |
| return <String, dynamic>{'enabled': await getter() ? 'true' : 'false'}; |
| }, |
| ); |
| } |
| |
| /// Registers a service extension method with the given name (full |
| /// name "ext.flutter.name"), which takes a single argument with the |
| /// same name as the method which, if present, must have a value |
| /// that can be parsed by [double.parse], and can be omitted to read |
| /// the current value. (Other arguments are ignored.) |
| /// |
| /// Calls the `getter` callback to obtain the value when |
| /// responding to the service extension method being called. |
| /// |
| /// Calls the `setter` callback with the new value when the |
| /// service extension method is called with a new value. |
| /// |
| /// {@macro flutter.foundation.bindingBase.registerServiceExtension} |
| @protected |
| void registerNumericServiceExtension({ |
| @required String name, |
| @required AsyncValueGetter<double> getter, |
| @required AsyncValueSetter<double> setter, |
| }) { |
| assert(name != null); |
| assert(getter != null); |
| assert(setter != null); |
| registerServiceExtension( |
| name: name, |
| callback: (Map<String, String> parameters) async { |
| if (parameters.containsKey(name)) { |
| await setter(double.parse(parameters[name])); |
| _postExtensionStateChangedEvent(name, (await getter()).toString()); |
| } |
| return <String, dynamic>{name: (await getter()).toString()}; |
| }, |
| ); |
| } |
| |
| /// Sends an event when a service extension's state is changed. |
| /// |
| /// Clients should listen for this event to stay aware of the current service |
| /// extension state. Any service extension that manages a state should call |
| /// this method on state change. |
| /// |
| /// `value` reflects the newly updated service extension value. |
| /// |
| /// This will be called automatically for service extensions registered via |
| /// [registerBoolServiceExtension], [registerNumericServiceExtension], or |
| /// [registerStringServiceExtension]. |
| void _postExtensionStateChangedEvent(String name, dynamic value) { |
| postEvent( |
| 'Flutter.ServiceExtensionStateChanged', |
| <String, dynamic>{ |
| 'extension': 'ext.flutter.$name', |
| 'value': value, |
| }, |
| ); |
| } |
| |
| /// All events dispatched by a [BindingBase] use this method instead of |
| /// calling [developer.postEvent] directly so that tests for [BindingBase] |
| /// can track which events were dispatched by overriding this method. |
| @protected |
| void postEvent(String eventKind, Map<String, dynamic> eventData) { |
| developer.postEvent(eventKind, eventData); |
| } |
| |
| /// Registers a service extension method with the given name (full name |
| /// "ext.flutter.name"), which optionally takes a single argument with the |
| /// name "value". If the argument is omitted, the value is to be read, |
| /// otherwise it is to be set. Returns the current value. |
| /// |
| /// Calls the `getter` callback to obtain the value when |
| /// responding to the service extension method being called. |
| /// |
| /// Calls the `setter` callback with the new value when the |
| /// service extension method is called with a new value. |
| /// |
| /// {@macro flutter.foundation.bindingBase.registerServiceExtension} |
| @protected |
| void registerStringServiceExtension({ |
| @required String name, |
| @required AsyncValueGetter<String> getter, |
| @required AsyncValueSetter<String> setter, |
| }) { |
| assert(name != null); |
| assert(getter != null); |
| assert(setter != null); |
| registerServiceExtension( |
| name: name, |
| callback: (Map<String, String> parameters) async { |
| if (parameters.containsKey('value')) { |
| await setter(parameters['value']); |
| _postExtensionStateChangedEvent(name, await getter()); |
| } |
| return <String, dynamic>{'value': await getter()}; |
| }, |
| ); |
| } |
| |
| /// Registers a service extension method with the given name (full name |
| /// "ext.flutter.name"). |
| /// |
| /// The given callback is called when the extension method is called. The |
| /// callback must return a [Future] that either eventually completes to a |
| /// return value in the form of a name/value map where the values can all be |
| /// converted to JSON using `json.encode()` (see [JsonEncoder]), or fails. In |
| /// case of failure, the failure is reported to the remote caller and is |
| /// dumped to the logs. |
| /// |
| /// The returned map will be mutated. |
| /// |
| /// {@template flutter.foundation.bindingBase.registerServiceExtension} |
| /// A registered service extension can only be activated if the vm-service |
| /// is included in the build, which only happens in debug and profile mode. |
| /// Although a service extension cannot be used in release mode its code may |
| /// still be included in the Dart snapshot and blow up binary size if it is |
| /// not wrapped in a guard that allows the tree shaker to remove it (see |
| /// sample code below). |
| /// |
| /// {@tool snippet} |
| /// The following code registers a service extension that is only included in |
| /// debug builds. |
| /// |
| /// ```dart |
| /// void myRegistrationFunction() { |
| /// assert(() { |
| /// // Register your service extension here. |
| /// return true; |
| /// }()); |
| /// } |
| /// ``` |
| /// {@end-tool} |
| /// |
| /// {@tool snippet} |
| /// A service extension registered with the following code snippet is |
| /// available in debug and profile mode. |
| /// |
| /// ```dart |
| /// void myRegistrationFunction() { |
| /// // kReleaseMode is defined in the 'flutter/foundation.dart' package. |
| /// if (!kReleaseMode) { |
| /// // Register your service extension here. |
| /// } |
| /// } |
| /// ``` |
| /// {@end-tool} |
| /// |
| /// Both guards ensure that Dart's tree shaker can remove the code for the |
| /// service extension in release builds. |
| /// {@endtemplate} |
| @protected |
| void registerServiceExtension({ |
| @required String name, |
| @required ServiceExtensionCallback callback, |
| }) { |
| assert(name != null); |
| assert(callback != null); |
| final String methodName = 'ext.flutter.$name'; |
| developer.registerExtension(methodName, (String method, Map<String, String> parameters) async { |
| assert(method == methodName); |
| assert(() { |
| if (debugInstrumentationEnabled) |
| debugPrint('service extension method received: $method($parameters)'); |
| return true; |
| }()); |
| |
| // VM service extensions are handled as "out of band" messages by the VM, |
| // which means they are handled at various times, generally ASAP. |
| // Notably, this includes being handled in the middle of microtask loops. |
| // While this makes sense for some service extensions (e.g. "dump current |
| // stack trace", which explicitly doesn't want to wait for a loop to |
| // complete), Flutter extensions need not be handled with such high |
| // priority. Further, handling them with such high priority exposes us to |
| // the possibility that they're handled in the middle of a frame, which |
| // breaks many assertions. As such, we ensure they we run the callbacks |
| // on the outer event loop here. |
| await debugInstrumentAction<void>('Wait for outer event loop', () { |
| return Future<void>.delayed(Duration.zero); |
| }); |
| |
| dynamic caughtException; |
| StackTrace caughtStack; |
| Map<String, dynamic> result; |
| try { |
| result = await callback(parameters); |
| } catch (exception, stack) { |
| caughtException = exception; |
| caughtStack = stack; |
| } |
| if (caughtException == null) { |
| result['type'] = '_extensionType'; |
| result['method'] = method; |
| return developer.ServiceExtensionResponse.result(json.encode(result)); |
| } else { |
| FlutterError.reportError(FlutterErrorDetails( |
| exception: caughtException, |
| stack: caughtStack, |
| context: ErrorDescription('during a service extension callback for "$method"'), |
| )); |
| return developer.ServiceExtensionResponse.error( |
| developer.ServiceExtensionResponse.extensionError, |
| json.encode(<String, String>{ |
| 'exception': caughtException.toString(), |
| 'stack': caughtStack.toString(), |
| 'method': method, |
| }), |
| ); |
| } |
| }); |
| } |
| |
| @override |
| String toString() => '<${objectRuntimeType(this, 'BindingBase')}>'; |
| } |
| |
| /// Terminate the Flutter application. |
| Future<void> _exitApplication() async { |
| exit(0); |
| } |