| // 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 Brightness, PlatformDispatcher, SingletonFlutterWindow, window; // ignore: deprecated_member_use |
| |
| // 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'; |
| import 'service_extensions.dart'; |
| import 'timeline.dart'; |
| |
| export 'dart:ui' show PlatformDispatcher, SingletonFlutterWindow; // ignore: deprecated_member_use |
| |
| export 'basic_types.dart' show AsyncCallback, AsyncValueGetter, AsyncValueSetter; |
| |
| // Examples can assume: |
| // mixin BarBinding on BindingBase { } |
| |
| /// 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. |
| /// |
| /// The Flutter engine ([dart:ui]) exposes some low-level services, |
| /// but these are typically not suitable for direct use, for example |
| /// because they only provide a single callback which an application |
| /// may wish to multiplex to allow multiple listeners. |
| /// |
| /// Bindings provide the glue between these low-level APIs and the |
| /// higher-level framework APIs. They _bind_ the two together, whence |
| /// the name. |
| /// |
| /// ## Implementing a binding mixin |
| /// |
| /// A library would typically create a new binding mixin to expose a |
| /// feature in [dart:ui]. This is rare in general, but it is something |
| /// that an alternative framework would do, e.g. if a framework were |
| /// to replace the [widgets] library with an alternative API but still |
| /// wished to leverage the [services] and [foundation] libraries. |
| /// |
| /// To create a binding mixin, declare a mixin `on` the [BindingBase] class |
| /// and whatever other bindings the concrete binding must implement for |
| /// this binding mixin to be useful. |
| /// |
| /// The mixin is guaranteed to only be constructed once in the |
| /// lifetime of the app; this is handled by [initInstances]. |
| /// |
| /// A binding mixin must at a minimum implement the following features: |
| /// |
| /// * The [initInstances] method, which must call `super.initInstances` and |
| /// set an `_instance` static field to `this`. |
| /// * An `instance` static getter, which must return that field using [checkInstance]. |
| /// |
| /// In addition, it should implement whatever singleton features the library needs. |
| /// |
| /// As a general rule, the less can be placed in the binding, the |
| /// better. Prefer having APIs that takes objects rather than having |
| /// them refer to global singletons. Bindings are best limited to |
| /// exposing features that literally only exist once, for example, the |
| /// APIs in [dart:ui]. |
| /// |
| /// {@tool snippet} |
| /// |
| /// Here is a basic example of a binding that implements these features. It relies on |
| /// another fictional binding called `BarBinding`. |
| /// |
| /// ```dart |
| /// mixin FooBinding on BindingBase, BarBinding { |
| /// @override |
| /// void initInstances() { |
| /// super.initInstances(); |
| /// _instance = this; |
| /// // ...binding initialization... |
| /// } |
| /// |
| /// static FooBinding get instance => BindingBase.checkInstance(_instance); |
| /// static FooBinding? _instance; |
| /// |
| /// // ...binding features... |
| /// } |
| /// ``` |
| /// {@end-tool} |
| /// |
| /// ## Implementing a binding class |
| /// |
| /// The top-most layer used to write the application (e.g. the Flutter |
| /// [widgets] library) will have a concrete class that inherits from |
| /// [BindingBase] and uses all the various [BindingBase] mixins (such |
| /// as [ServicesBinding]). The [widgets] library in Flutter introduces |
| /// a binding called [WidgetsFlutterBinding]. |
| /// |
| /// A binding _class_ should mix in the relevant bindings from each |
| /// layer that it wishes to expose, and should have an |
| /// `ensureInitialized` method that constructs the class if that |
| /// layer's mixin's `_instance` field is null. This allows the binding |
| /// to be overridden by developers who have more specific needs, while |
| /// still allowing other code to call `ensureInitialized` when a binding |
| /// is needed. |
| /// |
| /// {@tool snippet} |
| /// |
| /// A typical binding class is shown below. The `ensureInitialized` method's |
| /// return type is the library's binding mixin, rather than the concrete |
| /// class. |
| /// |
| /// ```dart |
| /// // continuing from previous example... |
| /// class FooLibraryBinding extends BindingBase with BarBinding, FooBinding { |
| /// static FooBinding ensureInitialized() { |
| /// if (FooBinding._instance == null) { |
| /// FooLibraryBinding(); |
| /// } |
| /// return FooBinding.instance; |
| /// } |
| /// } |
| /// ``` |
| /// {@end-tool} |
| 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 |
| /// VM service extensions, if any. |
| BindingBase() { |
| if (!kReleaseMode) { |
| FlutterTimeline.startSync('Framework initialization'); |
| } |
| assert(() { |
| _debugConstructed = true; |
| return true; |
| }()); |
| |
| assert(_debugInitializedType == null, 'Binding is already initialized to $_debugInitializedType'); |
| initInstances(); |
| assert(_debugInitializedType != null); |
| |
| assert(!_debugServiceExtensionsRegistered); |
| initServiceExtensions(); |
| assert(_debugServiceExtensionsRegistered); |
| |
| developer.postEvent('Flutter.FrameworkInitialization', <String, String>{}); |
| |
| if (!kReleaseMode) { |
| FlutterTimeline.finishSync(); |
| } |
| } |
| |
| bool _debugConstructed = false; |
| static Type? _debugInitializedType; |
| static bool _debugServiceExtensionsRegistered = false; |
| |
| /// Additional configuration used by the framework during hot reload. |
| /// |
| /// See also: |
| /// |
| /// * [DebugReassembleConfig], which describes the configuration. |
| static DebugReassembleConfig? debugReassembleConfig; |
| |
| /// Deprecated. Will be removed in a future version of Flutter. |
| /// |
| /// This property has been deprecated to prepare for Flutter's upcoming |
| /// support for multiple views and multiple windows. |
| /// |
| /// It represents the main view for applications where there is only one |
| /// view, such as applications designed for single-display mobile devices. |
| /// If the embedder supports multiple views, it points to the first view |
| /// created which is assumed to be the main view. It throws if no view has |
| /// been created yet or if the first view has been removed again. |
| /// |
| /// The following options exists to migrate code that relies on accessing |
| /// this deprecated property: |
| /// |
| /// If a [BuildContext] is available, consider looking up the current |
| /// [FlutterView] associated with that context via [View.of]. It gives access |
| /// to the same functionality as this deprecated property. However, the |
| /// platform-specific functionality has moved to the [PlatformDispatcher], |
| /// which may be accessed from the view returned by [View.of] via |
| /// [FlutterView.platformDispatcher]. Using [View.of] with a [BuildContext] is |
| /// the preferred option to migrate away from this deprecated [window] |
| /// property. |
| /// |
| /// If no context is available to look up a [FlutterView], the |
| /// [platformDispatcher] exposed by this binding can be used directly for |
| /// platform-specific functionality. It also maintains a list of all available |
| /// [FlutterView]s in [PlatformDispatcher.views] to access view-specific |
| /// functionality without a context. |
| /// |
| /// See also: |
| /// |
| /// * [View.of] to access view-specific functionality on the [FlutterView] |
| /// associated with the provided [BuildContext]. |
| /// * [FlutterView.platformDispatcher] to access platform-specific |
| /// functionality from a given [FlutterView]. |
| /// * [platformDispatcher] on this binding to access the [PlatformDispatcher], |
| /// which provides platform-specific functionality. |
| @Deprecated( |
| 'Look up the current FlutterView from the context via View.of(context) or consult the PlatformDispatcher directly instead. ' |
| 'Deprecated to prepare for the upcoming multi-window support. ' |
| 'This feature was deprecated after v3.7.0-32.0.pre.' |
| ) |
| ui.SingletonFlutterWindow get window => ui.window; |
| |
| /// The [ui.PlatformDispatcher] 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.PlatformDispatcher], e.g., [ServicesBinding] registers |
| /// listeners with the [ChannelBuffers], [RendererBinding] |
| /// registers [ui.PlatformDispatcher.onMetricsChanged], |
| /// [ui.PlatformDispatcher.onTextScaleFactorChanged], and [SemanticsBinding] |
| /// registers [ui.PlatformDispatcher.onSemanticsEnabledChanged], |
| /// [ui.PlatformDispatcher.onSemanticsActionEvent], and |
| /// [ui.PlatformDispatcher.onAccessibilityFeaturesChanged] handlers. |
| /// |
| /// Each of these other bindings could individually access a |
| /// [ui.PlatformDispatcher] statically, but that would preclude the ability to |
| /// test these behaviors with a fake platform dispatcher for verification |
| /// purposes. Therefore, [BindingBase] exposes this [ui.PlatformDispatcher] |
| /// for use by other bindings. A subclass of [BindingBase], such as |
| /// [TestWidgetsFlutterBinding], can override this accessor to return a |
| /// different [ui.PlatformDispatcher] implementation. |
| ui.PlatformDispatcher get platformDispatcher => ui.PlatformDispatcher.instance; |
| |
| /// The initialization method. Subclasses override this method to hook into |
| /// the platform and otherwise configure their services. Subclasses must call |
| /// "super.initInstances()". |
| /// |
| /// The binding is not fully initialized when this method runs (for |
| /// example, other binding mixins may not yet have run their |
| /// [initInstances] method). For this reason, code in this method |
| /// should avoid invoking callbacks or synchronously triggering any |
| /// code that would normally assume that the bindings are ready. |
| /// |
| /// {@tool snippet} |
| /// |
| /// By convention, if the service is to be provided as a singleton, |
| /// it should be exposed as `MixinClassName.instance`, a static |
| /// getter with a non-nullable return type that returns |
| /// `MixinClassName._instance`, a static field that is set by |
| /// `initInstances()`. To improve the developer experience, the |
| /// return value should actually be |
| /// `BindingBase.checkInstance(_instance)` (see [checkInstance]), as |
| /// in the example below. |
| /// |
| /// ```dart |
| /// mixin BazBinding on BindingBase { |
| /// @override |
| /// void initInstances() { |
| /// super.initInstances(); |
| /// _instance = this; |
| /// // ...binding initialization... |
| /// } |
| /// |
| /// static BazBinding get instance => BindingBase.checkInstance(_instance); |
| /// static BazBinding? _instance; |
| /// |
| /// // ...binding features... |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @protected |
| @mustCallSuper |
| void initInstances() { |
| assert(_debugInitializedType == null); |
| assert(() { |
| _debugInitializedType = runtimeType; |
| _debugBindingZone = Zone.current; |
| return true; |
| }()); |
| } |
| |
| /// A method that shows a useful error message if the given binding |
| /// instance is not initialized. |
| /// |
| /// See [initInstances] for advice on using this method. |
| /// |
| /// This method either returns the argument or throws an exception. |
| /// In release mode it always returns the argument. |
| /// |
| /// The type argument `T` should be the kind of binding mixin (e.g. |
| /// `SchedulerBinding`) that is calling the method. It is used in |
| /// error messages. |
| @protected |
| static T checkInstance<T extends BindingBase>(T? instance) { |
| assert(() { |
| if (_debugInitializedType == null && instance == null) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('Binding has not yet been initialized.'), |
| ErrorDescription('The "instance" getter on the $T binding mixin is only available once that binding has been initialized.'), |
| ErrorHint( |
| 'Typically, this is done by calling "WidgetsFlutterBinding.ensureInitialized()" or "runApp()" (the ' |
| 'latter calls the former). Typically this call is done in the "void main()" method. The "ensureInitialized" method ' |
| 'is idempotent; calling it multiple times is not harmful. After calling that method, the "instance" getter will ' |
| 'return the binding.', |
| ), |
| ErrorHint( |
| 'In a test, one can call "TestWidgetsFlutterBinding.ensureInitialized()" as the first line in the test\'s "main()" method ' |
| 'to initialize the binding.', |
| ), |
| ErrorHint( |
| 'If $T is a custom binding mixin, there must also be a custom binding class, like WidgetsFlutterBinding, ' |
| 'but that mixes in the selected binding, and that is the class that must be constructed before using the "instance" getter.', |
| ), |
| ]); |
| } |
| if (instance == null) { |
| assert(_debugInitializedType == null); |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('Binding mixin instance is null but bindings are already initialized.'), |
| ErrorDescription( |
| 'The "instance" property of the $T binding mixin was accessed, but that binding was not initialized when ' |
| 'the "initInstances()" method was called.', |
| ), |
| ErrorHint( |
| 'This probably indicates that the $T mixin was not mixed into the class that was used to initialize the binding. ' |
| 'If this is a custom binding mixin, there must also be a custom binding class, like WidgetsFlutterBinding, ' |
| 'but that mixes in the selected binding. If this is a test binding, check that the binding being initialized ' |
| 'is the same as the one into which the test binding is mixed.', |
| ), |
| ErrorHint( |
| 'It is also possible that $T does not implement "initInstances()" to assign a value to "instance". See the ' |
| 'documentation of the BindingBase class for more details.', |
| ), |
| ErrorHint( |
| 'The binding that was initialized was of the type "$_debugInitializedType". ' |
| ), |
| ]); |
| } |
| try { |
| if (instance._debugConstructed && _debugInitializedType == null) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('Binding initialized without calling initInstances.'), |
| ErrorDescription('An instance of $T is non-null, but BindingBase.initInstances() has not yet been called.'), |
| ErrorHint( |
| 'This could happen because a binding mixin was somehow used outside of the normal binding mechanisms, or because ' |
| 'the binding\'s initInstances() method did not call "super.initInstances()".', |
| ), |
| ErrorHint( |
| 'This could also happen if some code was invoked that used the binding while the binding was initializing, ' |
| 'for example if the "initInstances" method invokes a callback. Bindings should not invoke callbacks before ' |
| '"initInstances" has completed.', |
| ), |
| ]); |
| } |
| if (!instance._debugConstructed) { |
| // The state of _debugInitializedType doesn't matter in this failure mode. |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('Binding did not complete initialization.'), |
| ErrorDescription('An instance of $T is non-null, but the BindingBase() constructor has not yet been called.'), |
| ErrorHint( |
| 'This could also happen if some code was invoked that used the binding while the binding was initializing, ' |
| "for example if the binding's constructor itself invokes a callback. Bindings should not invoke callbacks " |
| 'before "initInstances" has completed.', |
| ), |
| ]); |
| } |
| } on NoSuchMethodError { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('Binding does not extend BindingBase'), |
| ErrorDescription('An instance of $T was created but the BindingBase constructor was not called.'), |
| ErrorHint( |
| 'This could happen because the binding was implemented using "implements" rather than "extends" or "with". ' |
| 'Concrete binding classes must extend or mix in BindingBase.', |
| ), |
| ]); |
| } |
| return true; |
| }()); |
| return instance!; |
| } |
| |
| /// In debug builds, the type of the current binding, if any, or else null. |
| /// |
| /// This may be useful in asserts to verify that the binding has not been initialized |
| /// before the point in the application code that wants to initialize the binding, or |
| /// to verify that the binding is the one that is expected. |
| /// |
| /// For example, if an application uses [Zone]s to report uncaught exceptions, it may |
| /// need to ensure that `ensureInitialized()` has not yet been invoked on any binding |
| /// at the point where it configures the zone and initializes the binding. |
| /// |
| /// If this returns null, the binding has not been initialized. |
| /// |
| /// If this returns a non-null value, it returns the type of the binding instance. |
| /// |
| /// To obtain the binding itself, consider the `instance` getter on the [BindingBase] |
| /// subclass or mixin. |
| /// |
| /// This method only returns a useful value in debug builds. In release builds, the |
| /// return value is always null; to improve startup performance, the type of the |
| /// binding is not tracked in release builds. |
| /// |
| /// See also: |
| /// |
| /// * [BindingBase], whose class documentation describes the conventions for dealing |
| /// with bindings. |
| /// * [initInstances], whose documentation details how to create a binding mixin. |
| static Type? debugBindingType() { |
| return _debugInitializedType; |
| } |
| |
| Zone? _debugBindingZone; |
| |
| /// Whether [debugCheckZone] should throw (true) or just report the error (false). |
| /// |
| /// Setting this to true makes it easier to catch cases where the zones are |
| /// misconfigured, by allowing debuggers to stop at the point of error. |
| /// |
| /// Currently this defaults to false, to avoid suddenly breaking applications |
| /// that are affected by this check but appear to be working today. Applications |
| /// are encouraged to resolve any issues that cause the [debugCheckZone] message |
| /// to appear, as even if they appear to be working today, they are likely to be |
| /// hiding hard-to-find bugs, and are more brittle (likely to collect bugs in |
| /// the future). |
| /// |
| /// To silence the message displayed by [debugCheckZone], ensure that the same |
| /// zone is used when calling `ensureInitialized()` as when calling the framework |
| /// in any other context (e.g. via [runApp]). |
| static bool debugZoneErrorsAreFatal = false; |
| |
| /// Checks that the current [Zone] is the same as that which was used |
| /// to initialize the binding. |
| /// |
| /// If the current zone ([Zone.current]) is not the zone that was active when |
| /// the binding was initialized, then this method generates a [FlutterError] |
| /// exception with detailed information. The exception is either thrown |
| /// directly, or reported via [FlutterError.reportError], depending on the |
| /// value of [BindingBase.debugZoneErrorsAreFatal]. |
| /// |
| /// To silence the message displayed by [debugCheckZone], ensure that the same |
| /// zone is used when calling `ensureInitialized()` as when calling the |
| /// framework in any other context (e.g. via [runApp]). For example, consider |
| /// keeping a reference to the zone used to initialize the binding, and using |
| /// [Zone.run] to use it again when calling into the framework. |
| /// |
| /// ## Usage |
| /// |
| /// The binding is considered initialized once [BindingBase.initInstances] has |
| /// run; if this is called before then, it will throw an [AssertionError]. |
| /// |
| /// The `entryPoint` parameter is the name of the API that is checking the |
| /// zones are consistent, for example, `'runApp'`. |
| /// |
| /// This function always returns true (if it does not throw). It is expected |
| /// to be invoked via the binding instance, e.g.: |
| /// |
| /// ```dart |
| /// void startup() { |
| /// WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); |
| /// assert(binding.debugCheckZone('startup')); |
| /// // ... |
| /// } |
| /// ``` |
| /// |
| /// If the binding expects to be used with multiple zones, it should override |
| /// this method to return true always without throwing. (For example, the |
| /// bindings used with [flutter_test] do this as they make heavy use of zones |
| /// to drive the framework with an artificial clock and to catch errors and |
| /// report them as test failures.) |
| bool debugCheckZone(String entryPoint) { |
| assert(() { |
| assert(_debugBindingZone != null, 'debugCheckZone can only be used after the binding is fully initialized.'); |
| if (Zone.current != _debugBindingZone) { |
| final Error message = FlutterError( |
| 'Zone mismatch.\n' |
| 'The Flutter bindings were initialized in a different zone than is now being used. ' |
| 'This will likely cause confusion and bugs as any zone-specific configuration will ' |
| 'inconsistently use the configuration of the original binding initialization zone ' |
| 'or this zone based on hard-to-predict factors such as which zone was active when ' |
| 'a particular callback was set.\n' |
| 'It is important to use the same zone when calling `ensureInitialized` on the binding ' |
| 'as when calling `$entryPoint` later.\n' |
| 'To make this ${ debugZoneErrorsAreFatal ? 'error non-fatal' : 'warning fatal' }, ' |
| 'set BindingBase.debugZoneErrorsAreFatal to ${!debugZoneErrorsAreFatal} before the ' |
| 'bindings are initialized (i.e. as the first statement in `void main() { }`).', |
| ); |
| if (debugZoneErrorsAreFatal) { |
| throw message; |
| } |
| FlutterError.reportError(FlutterErrorDetails( |
| exception: message, |
| stack: StackTrace.current, |
| context: ErrorDescription('during $entryPoint'), |
| )); |
| } |
| return 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/main/runtime/vm/service/service.md#rpcs-requests-and-responses> |
| @protected |
| @mustCallSuper |
| void initServiceExtensions() { |
| assert(!_debugServiceExtensionsRegistered); |
| |
| assert(() { |
| registerSignalServiceExtension( |
| name: FoundationServiceExtensions.reassemble.name, |
| callback: reassembleApplication, |
| ); |
| return true; |
| }()); |
| |
| if (!kReleaseMode) { |
| if (!kIsWeb) { |
| registerSignalServiceExtension( |
| name: FoundationServiceExtensions.exit.name, |
| callback: _exitApplication, |
| ); |
| } |
| // These service extensions are used in profile mode applications. |
| registerStringServiceExtension( |
| name: FoundationServiceExtensions.connectedVmServiceUri.name, |
| getter: () async => connectedVmServiceUri ?? '', |
| setter: (String uri) async { |
| connectedVmServiceUri = uri; |
| }, |
| ); |
| registerStringServiceExtension( |
| name: FoundationServiceExtensions.activeDevToolsServerAddress.name, |
| getter: () async => activeDevToolsServerAddress ?? '', |
| setter: (String serverAddress) async { |
| activeDevToolsServerAddress = serverAddress; |
| }, |
| ); |
| } |
| |
| assert(() { |
| registerServiceExtension( |
| name: FoundationServiceExtensions.platformOverride.name, |
| callback: (Map<String, String> parameters) async { |
| if (parameters.containsKey('value')) { |
| switch (parameters['value']) { |
| case 'android': |
| debugDefaultTargetPlatformOverride = TargetPlatform.android; |
| case 'fuchsia': |
| debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; |
| case 'iOS': |
| debugDefaultTargetPlatformOverride = TargetPlatform.iOS; |
| case 'linux': |
| debugDefaultTargetPlatformOverride = TargetPlatform.linux; |
| case 'macOS': |
| debugDefaultTargetPlatformOverride = TargetPlatform.macOS; |
| case 'windows': |
| debugDefaultTargetPlatformOverride = TargetPlatform.windows; |
| case 'default': |
| default: |
| debugDefaultTargetPlatformOverride = null; |
| } |
| _postExtensionStateChangedEvent( |
| FoundationServiceExtensions.platformOverride.name, |
| defaultTargetPlatform.toString().substring('$TargetPlatform.'.length), |
| ); |
| await reassembleApplication(); |
| } |
| return <String, dynamic>{ |
| 'value': defaultTargetPlatform |
| .toString() |
| .substring('$TargetPlatform.'.length), |
| }; |
| }, |
| ); |
| |
| registerServiceExtension( |
| name: FoundationServiceExtensions.brightnessOverride.name, |
| callback: (Map<String, String> parameters) async { |
| if (parameters.containsKey('value')) { |
| switch (parameters['value']) { |
| case 'Brightness.light': |
| debugBrightnessOverride = ui.Brightness.light; |
| case 'Brightness.dark': |
| debugBrightnessOverride = ui.Brightness.dark; |
| default: |
| debugBrightnessOverride = null; |
| } |
| _postExtensionStateChangedEvent( |
| FoundationServiceExtensions.brightnessOverride.name, |
| (debugBrightnessOverride ?? platformDispatcher.platformBrightness).toString(), |
| ); |
| await reassembleApplication(); |
| } |
| return <String, dynamic>{ |
| 'value': (debugBrightnessOverride ?? platformDispatcher.platformBrightness).toString(), |
| }; |
| }, |
| ); |
| 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]. |
| /// |
| /// The [gestures] binding wraps [PlatformDispatcher.onPointerDataPacket] in |
| /// logic that honors this event locking mechanism. Similarly, tasks queued |
| /// using [SchedulerBinding.scheduleTask] will only start when events are not |
| /// [locked]. |
| @protected |
| Future<void> lockEvents(Future<void> Function() callback) { |
| final developer.TimelineTask timelineTask = developer.TimelineTask()..start('Lock events'); |
| |
| _lockCount += 1; |
| final Future<void> future = callback(); |
| future.whenComplete(() { |
| _lockCount -= 1; |
| if (!locked) { |
| timelineTask.finish(); |
| try { |
| unlocked(); |
| } catch (error, stack) { |
| FlutterError.reportError(FlutterErrorDetails( |
| exception: error, |
| stack: stack, |
| library: 'foundation', |
| context: ErrorDescription('while handling pending events'), |
| )); |
| } |
| } |
| }); |
| 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, |
| }) { |
| 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, |
| }) { |
| 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, |
| }) { |
| 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. |
| /// |
| /// This is unrelated to the events managed by [lockEvents]. |
| @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, |
| }) { |
| 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 myOtherRegistrationFunction() { |
| /// // 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, |
| }) { |
| 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); |
| }); |
| |
| late Map<String, dynamic> result; |
| try { |
| result = await callback(parameters); |
| } catch (exception, stack) { |
| FlutterError.reportError(FlutterErrorDetails( |
| exception: exception, |
| stack: stack, |
| context: ErrorDescription('during a service extension callback for "$method"'), |
| )); |
| return developer.ServiceExtensionResponse.error( |
| developer.ServiceExtensionResponse.extensionError, |
| json.encode(<String, String>{ |
| 'exception': exception.toString(), |
| 'stack': stack.toString(), |
| 'method': method, |
| }), |
| ); |
| } |
| result['type'] = '_extensionType'; |
| result['method'] = method; |
| return developer.ServiceExtensionResponse.result(json.encode(result)); |
| }); |
| } |
| |
| @override |
| String toString() => '<${objectRuntimeType(this, 'BindingBase')}>'; |
| } |
| |
| /// Terminate the Flutter application. |
| Future<void> _exitApplication() async { |
| exit(0); |
| } |
| |
| /// Additional configuration used for hot reload reassemble optimizations. |
| /// |
| /// Do not extend, implement, or mixin this class. This may only be instantiated |
| /// in debug mode. |
| class DebugReassembleConfig { |
| /// Create a new [DebugReassembleConfig]. |
| /// |
| /// Throws a [FlutterError] if this is called in profile or release mode. |
| DebugReassembleConfig({ |
| this.widgetName, |
| }) { |
| if (!kDebugMode) { |
| throw FlutterError('Cannot instantiate DebugReassembleConfig in profile or release mode.'); |
| } |
| } |
| |
| /// The name of the widget that was modified, or `null` if the change was elsewhere. |
| final String? widgetName; |
| } |