| 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | /// @docImport 'dart:convert'; |
| 6 | /// @docImport 'dart:ui'; |
| 7 | /// |
| 8 | /// @docImport 'package:flutter/rendering.dart'; |
| 9 | /// @docImport 'package:flutter/scheduler.dart'; |
| 10 | /// @docImport 'package:flutter/services.dart'; |
| 11 | /// @docImport 'package:flutter/widgets.dart'; |
| 12 | /// @docImport 'package:flutter_test/flutter_test.dart'; |
| 13 | library; |
| 14 | |
| 15 | import 'dart:async'; |
| 16 | import 'dart:convert' show json; |
| 17 | import 'dart:developer' as developer; |
| 18 | import 'dart:io' show exit; |
| 19 | import 'dart:ui' as ui show Brightness, PlatformDispatcher, SingletonFlutterWindow, window; |
| 20 | |
| 21 | // Before adding any more dart:ui imports, please read the README. |
| 22 | |
| 23 | import 'package:meta/meta.dart' ; |
| 24 | |
| 25 | import 'assertions.dart'; |
| 26 | import 'basic_types.dart'; |
| 27 | import 'constants.dart'; |
| 28 | import 'debug.dart'; |
| 29 | import 'object.dart'; |
| 30 | import 'platform.dart'; |
| 31 | import 'print.dart'; |
| 32 | import 'service_extensions.dart'; |
| 33 | import 'timeline.dart'; |
| 34 | |
| 35 | export 'dart:ui' show PlatformDispatcher, SingletonFlutterWindow, clampDouble; |
| 36 | |
| 37 | export 'basic_types.dart' show AsyncCallback, AsyncValueGetter, AsyncValueSetter; |
| 38 | |
| 39 | // Examples can assume: |
| 40 | // mixin BarBinding on BindingBase { } |
| 41 | |
| 42 | /// Signature for service extensions. |
| 43 | /// |
| 44 | /// The returned map must not contain the keys "type" or "method", as |
| 45 | /// they will be replaced before the value is sent to the client. The |
| 46 | /// "type" key will be set to the string `_extensionType` to indicate |
| 47 | /// that this is a return value from a service extension, and the |
| 48 | /// "method" key will be set to the full name of the method. |
| 49 | typedef ServiceExtensionCallback = |
| 50 | Future<Map<String, dynamic>> Function(Map<String, String> parameters); |
| 51 | |
| 52 | /// Base class for mixins that provide singleton services. |
| 53 | /// |
| 54 | /// The Flutter engine ([dart:ui]) exposes some low-level services, |
| 55 | /// but these are typically not suitable for direct use, for example |
| 56 | /// because they only provide a single callback which an application |
| 57 | /// may wish to multiplex to allow multiple listeners. |
| 58 | /// |
| 59 | /// Bindings provide the glue between these low-level APIs and the |
| 60 | /// higher-level framework APIs. They _bind_ the two together, whence |
| 61 | /// the name. |
| 62 | /// |
| 63 | /// ## Implementing a binding mixin |
| 64 | /// |
| 65 | /// A library would typically create a new binding mixin to expose a |
| 66 | /// feature in [dart:ui]. This is rare in general, but it is something |
| 67 | /// that an alternative framework would do, e.g. if a framework were |
| 68 | /// to replace the [widgets] library with an alternative API but still |
| 69 | /// wished to leverage the [services] and [foundation] libraries. |
| 70 | /// |
| 71 | /// To create a binding mixin, declare a mixin `on` the [BindingBase] class |
| 72 | /// and whatever other bindings the concrete binding must implement for |
| 73 | /// this binding mixin to be useful. |
| 74 | /// |
| 75 | /// The mixin is guaranteed to only be constructed once in the |
| 76 | /// lifetime of the app; this is handled by [initInstances]. |
| 77 | /// |
| 78 | /// A binding mixin must at a minimum implement the following features: |
| 79 | /// |
| 80 | /// * The [initInstances] method, which must call `super.initInstances` and |
| 81 | /// set an `_instance` static field to `this`. |
| 82 | /// * An `instance` static getter, which must return that field using [checkInstance]. |
| 83 | /// |
| 84 | /// In addition, it should implement whatever singleton features the library needs. |
| 85 | /// |
| 86 | /// As a general rule, the less can be placed in the binding, the |
| 87 | /// better. Prefer having APIs that takes objects rather than having |
| 88 | /// them refer to global singletons. Bindings are best limited to |
| 89 | /// exposing features that literally only exist once, for example, the |
| 90 | /// APIs in [dart:ui]. |
| 91 | /// |
| 92 | /// {@tool snippet} |
| 93 | /// |
| 94 | /// Here is a basic example of a binding that implements these features. It relies on |
| 95 | /// another fictional binding called `BarBinding`. |
| 96 | /// |
| 97 | /// ```dart |
| 98 | /// mixin FooBinding on BindingBase, BarBinding { |
| 99 | /// @override |
| 100 | /// void initInstances() { |
| 101 | /// super.initInstances(); |
| 102 | /// _instance = this; |
| 103 | /// // ...binding initialization... |
| 104 | /// } |
| 105 | /// |
| 106 | /// static FooBinding get instance => BindingBase.checkInstance(_instance); |
| 107 | /// static FooBinding? _instance; |
| 108 | /// |
| 109 | /// // ...binding features... |
| 110 | /// } |
| 111 | /// ``` |
| 112 | /// {@end-tool} |
| 113 | /// |
| 114 | /// ## Implementing a binding class |
| 115 | /// |
| 116 | /// The top-most layer used to write the application (e.g. the Flutter |
| 117 | /// [widgets] library) will have a concrete class that inherits from |
| 118 | /// [BindingBase] and uses all the various [BindingBase] mixins (such |
| 119 | /// as [ServicesBinding]). The [widgets] library in Flutter introduces |
| 120 | /// a binding called [WidgetsFlutterBinding]. |
| 121 | /// |
| 122 | /// A binding _class_ should mix in the relevant bindings from each |
| 123 | /// layer that it wishes to expose, and should have an |
| 124 | /// `ensureInitialized` method that constructs the class if that |
| 125 | /// layer's mixin's `_instance` field is null. This allows the binding |
| 126 | /// to be overridden by developers who have more specific needs, while |
| 127 | /// still allowing other code to call `ensureInitialized` when a binding |
| 128 | /// is needed. |
| 129 | /// |
| 130 | /// {@tool snippet} |
| 131 | /// |
| 132 | /// A typical binding class is shown below. The `ensureInitialized` method's |
| 133 | /// return type is the library's binding mixin, rather than the concrete |
| 134 | /// class. |
| 135 | /// |
| 136 | /// ```dart |
| 137 | /// // continuing from previous example... |
| 138 | /// class FooLibraryBinding extends BindingBase with BarBinding, FooBinding { |
| 139 | /// static FooBinding ensureInitialized() { |
| 140 | /// if (FooBinding._instance == null) { |
| 141 | /// FooLibraryBinding(); |
| 142 | /// } |
| 143 | /// return FooBinding.instance; |
| 144 | /// } |
| 145 | /// } |
| 146 | /// ``` |
| 147 | /// {@end-tool} |
| 148 | abstract class BindingBase { |
| 149 | /// Default abstract constructor for bindings. |
| 150 | /// |
| 151 | /// First calls [initInstances] to have bindings initialize their |
| 152 | /// instance pointers and other state, then calls |
| 153 | /// [initServiceExtensions] to have bindings initialize their |
| 154 | /// VM service extensions, if any. |
| 155 | BindingBase() { |
| 156 | if (!kReleaseMode) { |
| 157 | FlutterTimeline.startSync('Framework initialization' ); |
| 158 | } |
| 159 | assert(() { |
| 160 | _debugConstructed = true; |
| 161 | return true; |
| 162 | }()); |
| 163 | |
| 164 | assert( |
| 165 | _debugInitializedType == null, |
| 166 | 'Binding is already initialized to $_debugInitializedType' , |
| 167 | ); |
| 168 | initInstances(); |
| 169 | assert(_debugInitializedType != null); |
| 170 | |
| 171 | assert(!_debugServiceExtensionsRegistered); |
| 172 | initServiceExtensions(); |
| 173 | assert(_debugServiceExtensionsRegistered); |
| 174 | |
| 175 | if (!kReleaseMode) { |
| 176 | developer.postEvent('Flutter.FrameworkInitialization' , <String, String>{}); |
| 177 | FlutterTimeline.finishSync(); |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | bool _debugConstructed = false; |
| 182 | static Type? _debugInitializedType; |
| 183 | static bool _debugServiceExtensionsRegistered = false; |
| 184 | |
| 185 | /// Deprecated. Will be removed in a future version of Flutter. |
| 186 | /// |
| 187 | /// This property has been deprecated to prepare for Flutter's upcoming |
| 188 | /// support for multiple views and multiple windows. |
| 189 | /// |
| 190 | /// It represents the main view for applications where there is only one |
| 191 | /// view, such as applications designed for single-display mobile devices. |
| 192 | /// If the embedder supports multiple views, it points to the first view |
| 193 | /// created which is assumed to be the main view. It throws if no view has |
| 194 | /// been created yet or if the first view has been removed again. |
| 195 | /// |
| 196 | /// The following options exists to migrate code that relies on accessing |
| 197 | /// this deprecated property: |
| 198 | /// |
| 199 | /// If a [BuildContext] is available, consider looking up the current |
| 200 | /// [FlutterView] associated with that context via [View.of]. It gives access |
| 201 | /// to the same functionality as this deprecated property. However, the |
| 202 | /// platform-specific functionality has moved to the [PlatformDispatcher], |
| 203 | /// which may be accessed from the view returned by [View.of] via |
| 204 | /// [FlutterView.platformDispatcher]. Using [View.of] with a [BuildContext] is |
| 205 | /// the preferred option to migrate away from this deprecated [window] |
| 206 | /// property. |
| 207 | /// |
| 208 | /// If no context is available to look up a [FlutterView], the |
| 209 | /// [platformDispatcher] exposed by this binding can be used directly for |
| 210 | /// platform-specific functionality. It also maintains a list of all available |
| 211 | /// [FlutterView]s in [PlatformDispatcher.views] to access view-specific |
| 212 | /// functionality without a context. |
| 213 | /// |
| 214 | /// See also: |
| 215 | /// |
| 216 | /// * [View.of] to access view-specific functionality on the [FlutterView] |
| 217 | /// associated with the provided [BuildContext]. |
| 218 | /// * [FlutterView.platformDispatcher] to access platform-specific |
| 219 | /// functionality from a given [FlutterView]. |
| 220 | /// * [platformDispatcher] on this binding to access the [PlatformDispatcher], |
| 221 | /// which provides platform-specific functionality. |
| 222 | @Deprecated( |
| 223 | 'Look up the current FlutterView from the context via View.of(context) or consult the PlatformDispatcher directly instead. ' |
| 224 | 'Deprecated to prepare for the upcoming multi-window support. ' |
| 225 | 'This feature was deprecated after v3.7.0-32.0.pre.' , |
| 226 | ) |
| 227 | ui.SingletonFlutterWindow get window => ui.window; |
| 228 | |
| 229 | /// The [ui.PlatformDispatcher] to which this binding is bound. |
| 230 | /// |
| 231 | /// A number of additional bindings are defined as extensions of |
| 232 | /// [BindingBase], e.g., [ServicesBinding], [RendererBinding], and |
| 233 | /// [WidgetsBinding]. Each of these bindings define behaviors that interact |
| 234 | /// with a [ui.PlatformDispatcher], e.g., [ServicesBinding] registers |
| 235 | /// listeners with the [ChannelBuffers], [RendererBinding] |
| 236 | /// registers [ui.PlatformDispatcher.onMetricsChanged], |
| 237 | /// [ui.PlatformDispatcher.onTextScaleFactorChanged], and [SemanticsBinding] |
| 238 | /// registers [ui.PlatformDispatcher.onSemanticsEnabledChanged], |
| 239 | /// [ui.PlatformDispatcher.onSemanticsActionEvent], and |
| 240 | /// [ui.PlatformDispatcher.onAccessibilityFeaturesChanged] handlers. |
| 241 | /// |
| 242 | /// Each of these other bindings could individually access a |
| 243 | /// [ui.PlatformDispatcher] statically, but that would preclude the ability to |
| 244 | /// test these behaviors with a fake platform dispatcher for verification |
| 245 | /// purposes. Therefore, [BindingBase] exposes this [ui.PlatformDispatcher] |
| 246 | /// for use by other bindings. A subclass of [BindingBase], such as |
| 247 | /// [TestWidgetsFlutterBinding], can override this accessor to return a |
| 248 | /// different [ui.PlatformDispatcher] implementation. |
| 249 | ui.PlatformDispatcher get platformDispatcher => ui.PlatformDispatcher.instance; |
| 250 | |
| 251 | /// The initialization method. Subclasses override this method to hook into |
| 252 | /// the platform and otherwise configure their services. Subclasses must call |
| 253 | /// "super.initInstances()". |
| 254 | /// |
| 255 | /// The binding is not fully initialized when this method runs (for |
| 256 | /// example, other binding mixins may not yet have run their |
| 257 | /// [initInstances] method). For this reason, code in this method |
| 258 | /// should avoid invoking callbacks or synchronously triggering any |
| 259 | /// code that would normally assume that the bindings are ready. |
| 260 | /// |
| 261 | /// {@tool snippet} |
| 262 | /// |
| 263 | /// By convention, if the service is to be provided as a singleton, |
| 264 | /// it should be exposed as `MixinClassName.instance`, a static |
| 265 | /// getter with a non-nullable return type that returns |
| 266 | /// `MixinClassName._instance`, a static field that is set by |
| 267 | /// `initInstances()`. To improve the developer experience, the |
| 268 | /// return value should actually be |
| 269 | /// `BindingBase.checkInstance(_instance)` (see [checkInstance]), as |
| 270 | /// in the example below. |
| 271 | /// |
| 272 | /// ```dart |
| 273 | /// mixin BazBinding on BindingBase { |
| 274 | /// @override |
| 275 | /// void initInstances() { |
| 276 | /// super.initInstances(); |
| 277 | /// _instance = this; |
| 278 | /// // ...binding initialization... |
| 279 | /// } |
| 280 | /// |
| 281 | /// static BazBinding get instance => BindingBase.checkInstance(_instance); |
| 282 | /// static BazBinding? _instance; |
| 283 | /// |
| 284 | /// // ...binding features... |
| 285 | /// } |
| 286 | /// ``` |
| 287 | /// {@end-tool} |
| 288 | @protected |
| 289 | @mustCallSuper |
| 290 | void initInstances() { |
| 291 | assert(_debugInitializedType == null); |
| 292 | assert(() { |
| 293 | _debugInitializedType = runtimeType; |
| 294 | _debugBindingZone = Zone.current; |
| 295 | return true; |
| 296 | }()); |
| 297 | } |
| 298 | |
| 299 | /// A method that shows a useful error message if the given binding |
| 300 | /// instance is not initialized. |
| 301 | /// |
| 302 | /// See [initInstances] for advice on using this method. |
| 303 | /// |
| 304 | /// This method either returns the argument or throws an exception. |
| 305 | /// In release mode it always returns the argument. |
| 306 | /// |
| 307 | /// The type argument `T` should be the kind of binding mixin (e.g. |
| 308 | /// `SchedulerBinding`) that is calling the method. It is used in |
| 309 | /// error messages. |
| 310 | @protected |
| 311 | static T checkInstance<T extends BindingBase>(T? instance) { |
| 312 | assert(() { |
| 313 | if (_debugInitializedType == null && instance == null) { |
| 314 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
| 315 | ErrorSummary('Binding has not yet been initialized.' ), |
| 316 | ErrorDescription( |
| 317 | 'The "instance" getter on the $T binding mixin is only available once that binding has been initialized.' , |
| 318 | ), |
| 319 | ErrorHint( |
| 320 | 'Typically, this is done by calling "WidgetsFlutterBinding.ensureInitialized()" or "runApp()" (the ' |
| 321 | 'latter calls the former). Typically this call is done in the "void main()" method. The "ensureInitialized" method ' |
| 322 | 'is idempotent; calling it multiple times is not harmful. After calling that method, the "instance" getter will ' |
| 323 | 'return the binding.' , |
| 324 | ), |
| 325 | ErrorHint( |
| 326 | 'In a test, one can call "TestWidgetsFlutterBinding.ensureInitialized()" as the first line in the test\'s "main()" method ' |
| 327 | 'to initialize the binding.' , |
| 328 | ), |
| 329 | ErrorHint( |
| 330 | 'If $T is a custom binding mixin, there must also be a custom binding class, like WidgetsFlutterBinding, ' |
| 331 | 'but that mixes in the selected binding, and that is the class that must be constructed before using the "instance" getter.' , |
| 332 | ), |
| 333 | ]); |
| 334 | } |
| 335 | if (instance == null) { |
| 336 | assert(_debugInitializedType == null); |
| 337 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
| 338 | ErrorSummary('Binding mixin instance is null but bindings are already initialized.' ), |
| 339 | ErrorDescription( |
| 340 | 'The "instance" property of the $T binding mixin was accessed, but that binding was not initialized when ' |
| 341 | 'the "initInstances()" method was called.' , |
| 342 | ), |
| 343 | ErrorHint( |
| 344 | 'This probably indicates that the $T mixin was not mixed into the class that was used to initialize the binding. ' |
| 345 | 'If this is a custom binding mixin, there must also be a custom binding class, like WidgetsFlutterBinding, ' |
| 346 | 'but that mixes in the selected binding. If this is a test binding, check that the binding being initialized ' |
| 347 | 'is the same as the one into which the test binding is mixed.' , |
| 348 | ), |
| 349 | ErrorHint( |
| 350 | 'It is also possible that $T does not implement "initInstances()" to assign a value to "instance". See the ' |
| 351 | 'documentation of the BindingBase class for more details.' , |
| 352 | ), |
| 353 | ErrorHint('The binding that was initialized was of the type " $_debugInitializedType". ' ), |
| 354 | ]); |
| 355 | } |
| 356 | try { |
| 357 | if (instance._debugConstructed && _debugInitializedType == null) { |
| 358 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
| 359 | ErrorSummary('Binding initialized without calling initInstances.' ), |
| 360 | ErrorDescription( |
| 361 | 'An instance of $T is non-null, but BindingBase.initInstances() has not yet been called.' , |
| 362 | ), |
| 363 | ErrorHint( |
| 364 | 'This could happen because a binding mixin was somehow used outside of the normal binding mechanisms, or because ' |
| 365 | 'the binding\'s initInstances() method did not call "super.initInstances()".' , |
| 366 | ), |
| 367 | ErrorHint( |
| 368 | 'This could also happen if some code was invoked that used the binding while the binding was initializing, ' |
| 369 | 'for example if the "initInstances" method invokes a callback. Bindings should not invoke callbacks before ' |
| 370 | '"initInstances" has completed.' , |
| 371 | ), |
| 372 | ]); |
| 373 | } |
| 374 | if (!instance._debugConstructed) { |
| 375 | // The state of _debugInitializedType doesn't matter in this failure mode. |
| 376 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
| 377 | ErrorSummary('Binding did not complete initialization.' ), |
| 378 | ErrorDescription( |
| 379 | 'An instance of $T is non-null, but the BindingBase() constructor has not yet been called.' , |
| 380 | ), |
| 381 | ErrorHint( |
| 382 | 'This could also happen if some code was invoked that used the binding while the binding was initializing, ' |
| 383 | "for example if the binding's constructor itself invokes a callback. Bindings should not invoke callbacks " |
| 384 | 'before "initInstances" has completed.' , |
| 385 | ), |
| 386 | ]); |
| 387 | } |
| 388 | } on NoSuchMethodError { |
| 389 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
| 390 | ErrorSummary('Binding does not extend BindingBase' ), |
| 391 | ErrorDescription( |
| 392 | 'An instance of $T was created but the BindingBase constructor was not called.' , |
| 393 | ), |
| 394 | ErrorHint( |
| 395 | 'This could happen because the binding was implemented using "implements" rather than "extends" or "with". ' |
| 396 | 'Concrete binding classes must extend or mix in BindingBase.' , |
| 397 | ), |
| 398 | ]); |
| 399 | } |
| 400 | return true; |
| 401 | }()); |
| 402 | return instance!; |
| 403 | } |
| 404 | |
| 405 | /// In debug builds, the type of the current binding, if any, or else null. |
| 406 | /// |
| 407 | /// This may be useful in asserts to verify that the binding has not been initialized |
| 408 | /// before the point in the application code that wants to initialize the binding, or |
| 409 | /// to verify that the binding is the one that is expected. |
| 410 | /// |
| 411 | /// For example, if an application uses [Zone]s to report uncaught exceptions, it may |
| 412 | /// need to ensure that `ensureInitialized()` has not yet been invoked on any binding |
| 413 | /// at the point where it configures the zone and initializes the binding. |
| 414 | /// |
| 415 | /// If this returns null, the binding has not been initialized. |
| 416 | /// |
| 417 | /// If this returns a non-null value, it returns the type of the binding instance. |
| 418 | /// |
| 419 | /// To obtain the binding itself, consider the `instance` getter on the [BindingBase] |
| 420 | /// subclass or mixin. |
| 421 | /// |
| 422 | /// This method only returns a useful value in debug builds. In release builds, the |
| 423 | /// return value is always null; to improve startup performance, the type of the |
| 424 | /// binding is not tracked in release builds. |
| 425 | /// |
| 426 | /// See also: |
| 427 | /// |
| 428 | /// * [BindingBase], whose class documentation describes the conventions for dealing |
| 429 | /// with bindings. |
| 430 | /// * [initInstances], whose documentation details how to create a binding mixin. |
| 431 | static Type? debugBindingType() { |
| 432 | return _debugInitializedType; |
| 433 | } |
| 434 | |
| 435 | Zone? _debugBindingZone; |
| 436 | |
| 437 | /// Whether [debugCheckZone] should throw (true) or just report the error (false). |
| 438 | /// |
| 439 | /// Setting this to true makes it easier to catch cases where the zones are |
| 440 | /// misconfigured, by allowing debuggers to stop at the point of error. |
| 441 | /// |
| 442 | /// Currently this defaults to false, to avoid suddenly breaking applications |
| 443 | /// that are affected by this check but appear to be working today. Applications |
| 444 | /// are encouraged to resolve any issues that cause the [debugCheckZone] message |
| 445 | /// to appear, as even if they appear to be working today, they are likely to be |
| 446 | /// hiding hard-to-find bugs, and are more brittle (likely to collect bugs in |
| 447 | /// the future). |
| 448 | /// |
| 449 | /// To silence the message displayed by [debugCheckZone], ensure that the same |
| 450 | /// zone is used when calling `ensureInitialized()` as when calling the framework |
| 451 | /// in any other context (e.g. via [runApp]). |
| 452 | static bool debugZoneErrorsAreFatal = false; |
| 453 | |
| 454 | /// Checks that the current [Zone] is the same as that which was used |
| 455 | /// to initialize the binding. |
| 456 | /// |
| 457 | /// If the current zone ([Zone.current]) is not the zone that was active when |
| 458 | /// the binding was initialized, then this method generates a [FlutterError] |
| 459 | /// exception with detailed information. The exception is either thrown |
| 460 | /// directly, or reported via [FlutterError.reportError], depending on the |
| 461 | /// value of [BindingBase.debugZoneErrorsAreFatal]. |
| 462 | /// |
| 463 | /// To silence the message displayed by [debugCheckZone], ensure that the same |
| 464 | /// zone is used when calling `ensureInitialized()` as when calling the |
| 465 | /// framework in any other context (e.g. via [runApp]). For example, consider |
| 466 | /// keeping a reference to the zone used to initialize the binding, and using |
| 467 | /// [Zone.run] to use it again when calling into the framework. |
| 468 | /// |
| 469 | /// ## Usage |
| 470 | /// |
| 471 | /// The binding is considered initialized once [BindingBase.initInstances] has |
| 472 | /// run; if this is called before then, it will throw an [AssertionError]. |
| 473 | /// |
| 474 | /// The `entryPoint` parameter is the name of the API that is checking the |
| 475 | /// zones are consistent, for example, `'runApp'`. |
| 476 | /// |
| 477 | /// This function always returns true (if it does not throw). It is expected |
| 478 | /// to be invoked via the binding instance, e.g.: |
| 479 | /// |
| 480 | /// ```dart |
| 481 | /// void startup() { |
| 482 | /// WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); |
| 483 | /// assert(binding.debugCheckZone('startup')); |
| 484 | /// // ... |
| 485 | /// } |
| 486 | /// ``` |
| 487 | /// |
| 488 | /// If the binding expects to be used with multiple zones, it should override |
| 489 | /// this method to return true always without throwing. (For example, the |
| 490 | /// bindings used with [flutter_test] do this as they make heavy use of zones |
| 491 | /// to drive the framework with an artificial clock and to catch errors and |
| 492 | /// report them as test failures.) |
| 493 | bool debugCheckZone(String entryPoint) { |
| 494 | assert(() { |
| 495 | assert( |
| 496 | _debugBindingZone != null, |
| 497 | 'debugCheckZone can only be used after the binding is fully initialized.' , |
| 498 | ); |
| 499 | if (Zone.current != _debugBindingZone) { |
| 500 | final Error message = FlutterError( |
| 501 | 'Zone mismatch.\n' |
| 502 | 'The Flutter bindings were initialized in a different zone than is now being used. ' |
| 503 | 'This will likely cause confusion and bugs as any zone-specific configuration will ' |
| 504 | 'inconsistently use the configuration of the original binding initialization zone ' |
| 505 | 'or this zone based on hard-to-predict factors such as which zone was active when ' |
| 506 | 'a particular callback was set.\n' |
| 507 | 'It is important to use the same zone when calling `ensureInitialized` on the binding ' |
| 508 | 'as when calling ` $entryPoint` later.\n' |
| 509 | 'To make this ${debugZoneErrorsAreFatal ? 'error non-fatal' : 'warning fatal' }, ' |
| 510 | 'set BindingBase.debugZoneErrorsAreFatal to ${!debugZoneErrorsAreFatal} before the ' |
| 511 | 'bindings are initialized (i.e. as the first statement in `void main() { }`).' , |
| 512 | ); |
| 513 | if (debugZoneErrorsAreFatal) { |
| 514 | throw message; |
| 515 | } |
| 516 | FlutterError.reportError( |
| 517 | FlutterErrorDetails( |
| 518 | exception: message, |
| 519 | stack: StackTrace.current, |
| 520 | context: ErrorDescription('during $entryPoint' ), |
| 521 | ), |
| 522 | ); |
| 523 | } |
| 524 | return true; |
| 525 | }()); |
| 526 | return true; |
| 527 | } |
| 528 | |
| 529 | /// Called when the binding is initialized, to register service |
| 530 | /// extensions. |
| 531 | /// |
| 532 | /// Bindings that want to expose service extensions should overload |
| 533 | /// this method to register them using calls to |
| 534 | /// [registerSignalServiceExtension], |
| 535 | /// [registerBoolServiceExtension], |
| 536 | /// [registerNumericServiceExtension], and |
| 537 | /// [registerServiceExtension] (in increasing order of complexity). |
| 538 | /// |
| 539 | /// Implementations of this method must call their superclass |
| 540 | /// implementation. |
| 541 | /// |
| 542 | /// {@macro flutter.foundation.BindingBase.registerServiceExtension} |
| 543 | /// |
| 544 | /// See also: |
| 545 | /// |
| 546 | /// * <https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#rpcs-requests-and-responses> |
| 547 | @protected |
| 548 | @mustCallSuper |
| 549 | void initServiceExtensions() { |
| 550 | assert(!_debugServiceExtensionsRegistered); |
| 551 | |
| 552 | assert(() { |
| 553 | registerSignalServiceExtension( |
| 554 | name: FoundationServiceExtensions.reassemble.name, |
| 555 | callback: reassembleApplication, |
| 556 | ); |
| 557 | return true; |
| 558 | }()); |
| 559 | |
| 560 | if (!kReleaseMode) { |
| 561 | if (!kIsWeb) { |
| 562 | registerSignalServiceExtension( |
| 563 | name: FoundationServiceExtensions.exit.name, |
| 564 | callback: _exitApplication, |
| 565 | ); |
| 566 | } |
| 567 | // These service extensions are used in profile mode applications. |
| 568 | registerStringServiceExtension( |
| 569 | name: FoundationServiceExtensions.connectedVmServiceUri.name, |
| 570 | getter: () async => connectedVmServiceUri ?? '' , |
| 571 | setter: (String uri) async { |
| 572 | connectedVmServiceUri = uri; |
| 573 | }, |
| 574 | ); |
| 575 | registerStringServiceExtension( |
| 576 | name: FoundationServiceExtensions.activeDevToolsServerAddress.name, |
| 577 | getter: () async => activeDevToolsServerAddress ?? '' , |
| 578 | setter: (String serverAddress) async { |
| 579 | activeDevToolsServerAddress = serverAddress; |
| 580 | }, |
| 581 | ); |
| 582 | } |
| 583 | |
| 584 | assert(() { |
| 585 | registerServiceExtension( |
| 586 | name: FoundationServiceExtensions.platformOverride.name, |
| 587 | callback: (Map<String, String> parameters) async { |
| 588 | if (parameters.containsKey('value' )) { |
| 589 | final String value = parameters['value' ]!; |
| 590 | debugDefaultTargetPlatformOverride = null; |
| 591 | for (final TargetPlatform candidate in TargetPlatform.values) { |
| 592 | if (candidate.name == value) { |
| 593 | debugDefaultTargetPlatformOverride = candidate; |
| 594 | break; |
| 595 | } |
| 596 | } |
| 597 | _postExtensionStateChangedEvent( |
| 598 | FoundationServiceExtensions.platformOverride.name, |
| 599 | defaultTargetPlatform.name, |
| 600 | ); |
| 601 | await reassembleApplication(); |
| 602 | } |
| 603 | return <String, dynamic>{'value' : defaultTargetPlatform.name}; |
| 604 | }, |
| 605 | ); |
| 606 | |
| 607 | registerServiceExtension( |
| 608 | name: FoundationServiceExtensions.brightnessOverride.name, |
| 609 | callback: (Map<String, String> parameters) async { |
| 610 | if (parameters.containsKey('value' )) { |
| 611 | debugBrightnessOverride = switch (parameters['value' ]) { |
| 612 | 'Brightness.light' => ui.Brightness.light, |
| 613 | 'Brightness.dark' => ui.Brightness.dark, |
| 614 | _ => null, |
| 615 | }; |
| 616 | _postExtensionStateChangedEvent( |
| 617 | FoundationServiceExtensions.brightnessOverride.name, |
| 618 | (debugBrightnessOverride ?? platformDispatcher.platformBrightness).toString(), |
| 619 | ); |
| 620 | await reassembleApplication(); |
| 621 | } |
| 622 | return <String, dynamic>{ |
| 623 | 'value' : (debugBrightnessOverride ?? platformDispatcher.platformBrightness).toString(), |
| 624 | }; |
| 625 | }, |
| 626 | ); |
| 627 | return true; |
| 628 | }()); |
| 629 | assert(() { |
| 630 | _debugServiceExtensionsRegistered = true; |
| 631 | return true; |
| 632 | }()); |
| 633 | } |
| 634 | |
| 635 | /// Whether [lockEvents] is currently locking events. |
| 636 | /// |
| 637 | /// Binding subclasses that fire events should check this first, and if it is |
| 638 | /// set, queue events instead of firing them. |
| 639 | /// |
| 640 | /// Events should be flushed when [unlocked] is called. |
| 641 | @protected |
| 642 | bool get locked => _lockCount > 0; |
| 643 | int _lockCount = 0; |
| 644 | |
| 645 | /// Locks the dispatching of asynchronous events and callbacks until the |
| 646 | /// callback's future completes. |
| 647 | /// |
| 648 | /// This causes input lag and should therefore be avoided when possible. It is |
| 649 | /// primarily intended for use during non-user-interactive time such as to |
| 650 | /// allow [reassembleApplication] to block input while it walks the tree |
| 651 | /// (which it partially does asynchronously). |
| 652 | /// |
| 653 | /// The [Future] returned by the `callback` argument is returned by [lockEvents]. |
| 654 | /// |
| 655 | /// The [gestures] binding wraps [PlatformDispatcher.onPointerDataPacket] in |
| 656 | /// logic that honors this event locking mechanism. Similarly, tasks queued |
| 657 | /// using [SchedulerBinding.scheduleTask] will only start when events are not |
| 658 | /// [locked]. |
| 659 | @protected |
| 660 | Future<void> lockEvents(Future<void> Function() callback) { |
| 661 | developer.TimelineTask? debugTimelineTask; |
| 662 | if (!kReleaseMode) { |
| 663 | debugTimelineTask = developer.TimelineTask()..start('Lock events' ); |
| 664 | } |
| 665 | |
| 666 | _lockCount += 1; |
| 667 | final Future<void> future = callback(); |
| 668 | future.whenComplete(() { |
| 669 | _lockCount -= 1; |
| 670 | if (!locked) { |
| 671 | if (!kReleaseMode) { |
| 672 | debugTimelineTask!.finish(); |
| 673 | } |
| 674 | try { |
| 675 | unlocked(); |
| 676 | } catch (error, stack) { |
| 677 | FlutterError.reportError( |
| 678 | FlutterErrorDetails( |
| 679 | exception: error, |
| 680 | stack: stack, |
| 681 | library: 'foundation' , |
| 682 | context: ErrorDescription('while handling pending events' ), |
| 683 | ), |
| 684 | ); |
| 685 | } |
| 686 | } |
| 687 | }); |
| 688 | return future; |
| 689 | } |
| 690 | |
| 691 | /// Called by [lockEvents] when events get unlocked. |
| 692 | /// |
| 693 | /// This should flush any events that were queued while [locked] was true. |
| 694 | @protected |
| 695 | @mustCallSuper |
| 696 | void unlocked() { |
| 697 | assert(!locked); |
| 698 | } |
| 699 | |
| 700 | /// Cause the entire application to redraw, e.g. after a hot reload. |
| 701 | /// |
| 702 | /// This is used by development tools when the application code has changed, |
| 703 | /// to cause the application to pick up any changed code. It can be triggered |
| 704 | /// manually by sending the `ext.flutter.reassemble` service extension signal. |
| 705 | /// |
| 706 | /// This method is very computationally expensive and should not be used in |
| 707 | /// production code. There is never a valid reason to cause the entire |
| 708 | /// application to repaint in production. All aspects of the Flutter framework |
| 709 | /// know how to redraw when necessary. It is only necessary in development |
| 710 | /// when the code is literally changed on the fly (e.g. in hot reload) or when |
| 711 | /// debug flags are being toggled. |
| 712 | /// |
| 713 | /// While this method runs, events are locked (e.g. pointer events are not |
| 714 | /// dispatched). |
| 715 | /// |
| 716 | /// Subclasses (binding classes) should override [performReassemble] to react |
| 717 | /// to this method being called. This method itself should not be overridden. |
| 718 | Future<void> reassembleApplication() { |
| 719 | return lockEvents(performReassemble); |
| 720 | } |
| 721 | |
| 722 | /// This method is called by [reassembleApplication] to actually cause the |
| 723 | /// application to reassemble, e.g. after a hot reload. |
| 724 | /// |
| 725 | /// Bindings are expected to use this method to re-register anything that uses |
| 726 | /// closures, so that they do not keep pointing to old code, and to flush any |
| 727 | /// caches of previously computed values, in case the new code would compute |
| 728 | /// them differently. For example, the rendering layer triggers the entire |
| 729 | /// application to repaint when this is called. |
| 730 | /// |
| 731 | /// Do not call this method directly. Instead, use [reassembleApplication]. |
| 732 | @mustCallSuper |
| 733 | @protected |
| 734 | Future<void> performReassemble() { |
| 735 | FlutterError.resetErrorCount(); |
| 736 | return Future<void>.value(); |
| 737 | } |
| 738 | |
| 739 | /// Registers a service extension method with the given name (full |
| 740 | /// name "ext.flutter.name"), which takes no arguments and returns |
| 741 | /// no value. |
| 742 | /// |
| 743 | /// Calls the `callback` callback when the service extension is called. |
| 744 | /// |
| 745 | /// {@macro flutter.foundation.BindingBase.registerServiceExtension} |
| 746 | @protected |
| 747 | void registerSignalServiceExtension({required String name, required AsyncCallback callback}) { |
| 748 | registerServiceExtension( |
| 749 | name: name, |
| 750 | callback: (Map<String, String> parameters) async { |
| 751 | await callback(); |
| 752 | return <String, dynamic>{}; |
| 753 | }, |
| 754 | ); |
| 755 | } |
| 756 | |
| 757 | /// Registers a service extension method with the given name (full |
| 758 | /// name "ext.flutter.name"), which takes a single argument |
| 759 | /// "enabled" which can have the value "true" or the value "false" |
| 760 | /// or can be omitted to read the current value. (Any value other |
| 761 | /// than "true" is considered equivalent to "false". Other arguments |
| 762 | /// are ignored.) |
| 763 | /// |
| 764 | /// Calls the `getter` callback to obtain the value when |
| 765 | /// responding to the service extension method being called. |
| 766 | /// |
| 767 | /// Calls the `setter` callback with the new value when the |
| 768 | /// service extension method is called with a new value. |
| 769 | /// |
| 770 | /// {@macro flutter.foundation.BindingBase.registerServiceExtension} |
| 771 | @protected |
| 772 | void registerBoolServiceExtension({ |
| 773 | required String name, |
| 774 | required AsyncValueGetter<bool> getter, |
| 775 | required AsyncValueSetter<bool> setter, |
| 776 | }) { |
| 777 | registerServiceExtension( |
| 778 | name: name, |
| 779 | callback: (Map<String, String> parameters) async { |
| 780 | if (parameters.containsKey('enabled' )) { |
| 781 | await setter(parameters['enabled' ] == 'true' ); |
| 782 | _postExtensionStateChangedEvent(name, await getter() ? 'true' : 'false' ); |
| 783 | } |
| 784 | return <String, dynamic>{'enabled' : await getter() ? 'true' : 'false' }; |
| 785 | }, |
| 786 | ); |
| 787 | } |
| 788 | |
| 789 | /// Registers a service extension method with the given name (full |
| 790 | /// name "ext.flutter.name"), which takes a single argument with the |
| 791 | /// same name as the method which, if present, must have a value |
| 792 | /// that can be parsed by [double.parse], and can be omitted to read |
| 793 | /// the current value. (Other arguments are ignored.) |
| 794 | /// |
| 795 | /// Calls the `getter` callback to obtain the value when |
| 796 | /// responding to the service extension method being called. |
| 797 | /// |
| 798 | /// Calls the `setter` callback with the new value when the |
| 799 | /// service extension method is called with a new value. |
| 800 | /// |
| 801 | /// {@macro flutter.foundation.BindingBase.registerServiceExtension} |
| 802 | @protected |
| 803 | void registerNumericServiceExtension({ |
| 804 | required String name, |
| 805 | required AsyncValueGetter<double> getter, |
| 806 | required AsyncValueSetter<double> setter, |
| 807 | }) { |
| 808 | registerServiceExtension( |
| 809 | name: name, |
| 810 | callback: (Map<String, String> parameters) async { |
| 811 | if (parameters.containsKey(name)) { |
| 812 | await setter(double.parse(parameters[name]!)); |
| 813 | _postExtensionStateChangedEvent(name, (await getter()).toString()); |
| 814 | } |
| 815 | return <String, dynamic>{name: (await getter()).toString()}; |
| 816 | }, |
| 817 | ); |
| 818 | } |
| 819 | |
| 820 | /// Sends an event when a service extension's state is changed. |
| 821 | /// |
| 822 | /// Clients should listen for this event to stay aware of the current service |
| 823 | /// extension state. Any service extension that manages a state should call |
| 824 | /// this method on state change. |
| 825 | /// |
| 826 | /// `value` reflects the newly updated service extension value. |
| 827 | /// |
| 828 | /// This will be called automatically for service extensions registered via |
| 829 | /// [registerBoolServiceExtension], [registerNumericServiceExtension], or |
| 830 | /// [registerStringServiceExtension]. |
| 831 | void _postExtensionStateChangedEvent(String name, dynamic value) { |
| 832 | postEvent('Flutter.ServiceExtensionStateChanged' , <String, dynamic>{ |
| 833 | 'extension' : 'ext.flutter. $name' , |
| 834 | 'value' : value, |
| 835 | }); |
| 836 | } |
| 837 | |
| 838 | /// All events dispatched by a [BindingBase] use this method instead of |
| 839 | /// calling [developer.postEvent] directly so that tests for [BindingBase] |
| 840 | /// can track which events were dispatched by overriding this method. |
| 841 | /// |
| 842 | /// This is unrelated to the events managed by [lockEvents]. |
| 843 | @protected |
| 844 | void postEvent(String eventKind, Map<String, dynamic> eventData) { |
| 845 | developer.postEvent(eventKind, eventData); |
| 846 | } |
| 847 | |
| 848 | /// Registers a service extension method with the given name (full name |
| 849 | /// "ext.flutter.name"), which optionally takes a single argument with the |
| 850 | /// name "value". If the argument is omitted, the value is to be read, |
| 851 | /// otherwise it is to be set. Returns the current value. |
| 852 | /// |
| 853 | /// Calls the `getter` callback to obtain the value when |
| 854 | /// responding to the service extension method being called. |
| 855 | /// |
| 856 | /// Calls the `setter` callback with the new value when the |
| 857 | /// service extension method is called with a new value. |
| 858 | /// |
| 859 | /// {@macro flutter.foundation.BindingBase.registerServiceExtension} |
| 860 | @protected |
| 861 | void registerStringServiceExtension({ |
| 862 | required String name, |
| 863 | required AsyncValueGetter<String> getter, |
| 864 | required AsyncValueSetter<String> setter, |
| 865 | }) { |
| 866 | registerServiceExtension( |
| 867 | name: name, |
| 868 | callback: (Map<String, String> parameters) async { |
| 869 | if (parameters.containsKey('value' )) { |
| 870 | await setter(parameters['value' ]!); |
| 871 | _postExtensionStateChangedEvent(name, await getter()); |
| 872 | } |
| 873 | return <String, dynamic>{'value' : await getter()}; |
| 874 | }, |
| 875 | ); |
| 876 | } |
| 877 | |
| 878 | /// Registers a service extension method with the given name (full name |
| 879 | /// "ext.flutter.name"). |
| 880 | /// |
| 881 | /// The given callback is called when the extension method is called. The |
| 882 | /// callback must return a [Future] that either eventually completes to a |
| 883 | /// return value in the form of a name/value map where the values can all be |
| 884 | /// converted to JSON using `json.encode()` (see [JsonEncoder]), or fails. In |
| 885 | /// case of failure, the failure is reported to the remote caller and is |
| 886 | /// dumped to the logs. |
| 887 | /// |
| 888 | /// The returned map will be mutated. |
| 889 | /// |
| 890 | /// {@template flutter.foundation.BindingBase.registerServiceExtension} |
| 891 | /// A registered service extension can only be activated if the vm-service |
| 892 | /// is included in the build, which only happens in debug and profile mode. |
| 893 | /// Although a service extension cannot be used in release mode its code may |
| 894 | /// still be included in the Dart snapshot and blow up binary size if it is |
| 895 | /// not wrapped in a guard that allows the tree shaker to remove it (see |
| 896 | /// sample code below). |
| 897 | /// |
| 898 | /// {@tool snippet} |
| 899 | /// The following code registers a service extension that is only included in |
| 900 | /// debug builds. |
| 901 | /// |
| 902 | /// ```dart |
| 903 | /// void myRegistrationFunction() { |
| 904 | /// assert(() { |
| 905 | /// // Register your service extension here. |
| 906 | /// return true; |
| 907 | /// }()); |
| 908 | /// } |
| 909 | /// ``` |
| 910 | /// {@end-tool} |
| 911 | /// |
| 912 | /// {@tool snippet} |
| 913 | /// A service extension registered with the following code snippet is |
| 914 | /// available in debug and profile mode. |
| 915 | /// |
| 916 | /// ```dart |
| 917 | /// void myOtherRegistrationFunction() { |
| 918 | /// // kReleaseMode is defined in the 'flutter/foundation.dart' package. |
| 919 | /// if (!kReleaseMode) { |
| 920 | /// // Register your service extension here. |
| 921 | /// } |
| 922 | /// } |
| 923 | /// ``` |
| 924 | /// {@end-tool} |
| 925 | /// |
| 926 | /// Both guards ensure that Dart's tree shaker can remove the code for the |
| 927 | /// service extension in release builds. |
| 928 | /// {@endtemplate} |
| 929 | @protected |
| 930 | void registerServiceExtension({ |
| 931 | required String name, |
| 932 | required ServiceExtensionCallback callback, |
| 933 | }) { |
| 934 | final String methodName = 'ext.flutter. $name' ; |
| 935 | developer.registerExtension(methodName, (String method, Map<String, String> parameters) async { |
| 936 | assert(method == methodName); |
| 937 | assert(() { |
| 938 | if (debugInstrumentationEnabled) { |
| 939 | debugPrint('service extension method received: $method( $parameters)' ); |
| 940 | } |
| 941 | return true; |
| 942 | }()); |
| 943 | |
| 944 | // VM service extensions are handled as "out of band" messages by the VM, |
| 945 | // which means they are handled at various times, generally ASAP. |
| 946 | // Notably, this includes being handled in the middle of microtask loops. |
| 947 | // While this makes sense for some service extensions (e.g. "dump current |
| 948 | // stack trace", which explicitly doesn't want to wait for a loop to |
| 949 | // complete), Flutter extensions need not be handled with such high |
| 950 | // priority. Further, handling them with such high priority exposes us to |
| 951 | // the possibility that they're handled in the middle of a frame, which |
| 952 | // breaks many assertions. As such, we ensure they we run the callbacks |
| 953 | // on the outer event loop here. |
| 954 | await debugInstrumentAction<void>('Wait for outer event loop' , () { |
| 955 | return Future<void>.delayed(Duration.zero); |
| 956 | }); |
| 957 | |
| 958 | late Map<String, dynamic> result; |
| 959 | try { |
| 960 | result = await callback(parameters); |
| 961 | } catch (exception, stack) { |
| 962 | FlutterError.reportError( |
| 963 | FlutterErrorDetails( |
| 964 | exception: exception, |
| 965 | stack: stack, |
| 966 | context: ErrorDescription('during a service extension callback for " $method"' ), |
| 967 | ), |
| 968 | ); |
| 969 | return developer.ServiceExtensionResponse.error( |
| 970 | developer.ServiceExtensionResponse.extensionError, |
| 971 | json.encode(<String, String>{ |
| 972 | 'exception' : exception.toString(), |
| 973 | 'stack' : stack.toString(), |
| 974 | 'method' : method, |
| 975 | }), |
| 976 | ); |
| 977 | } |
| 978 | result['type' ] = '_extensionType' ; |
| 979 | result['method' ] = method; |
| 980 | return developer.ServiceExtensionResponse.result(json.encode(result)); |
| 981 | }); |
| 982 | } |
| 983 | |
| 984 | @override |
| 985 | String toString() => '< ${objectRuntimeType(this, 'BindingBase' )}>' ; |
| 986 | } |
| 987 | |
| 988 | /// Terminate the Flutter application. |
| 989 | Future<void> _exitApplication() async { |
| 990 | exit(0); |
| 991 | } |
| 992 | |