binding.dart 39.4 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:async';
6
import 'dart:convert' show json;
7
import 'dart:developer' as developer;
8
import 'dart:io' show exit;
9
import 'dart:ui' as ui show Brightness, PlatformDispatcher, SingletonFlutterWindow, window; // ignore: deprecated_member_use
10

11
// Before adding any more dart:ui imports, please read the README.
12

13
import 'package:meta/meta.dart';
14 15 16

import 'assertions.dart';
import 'basic_types.dart';
17
import 'constants.dart';
18
import 'debug.dart';
19
import 'object.dart';
20
import 'platform.dart';
21
import 'print.dart';
22
import 'service_extensions.dart';
23
import 'timeline.dart';
24

25
export 'dart:ui' show PlatformDispatcher, SingletonFlutterWindow, clampDouble; // ignore: deprecated_member_use
26

27 28
export 'basic_types.dart' show AsyncCallback, AsyncValueGetter, AsyncValueSetter;

29 30 31
// Examples can assume:
// mixin BarBinding on BindingBase { }

32 33 34 35 36 37 38
/// 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.
39
typedef ServiceExtensionCallback = Future<Map<String, dynamic>> Function(Map<String, String> parameters);
40

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
/// 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`.
85
///
86 87 88 89 90 91 92 93
/// ```dart
/// mixin FooBinding on BindingBase, BarBinding {
///   @override
///   void initInstances() {
///     super.initInstances();
///     _instance = this;
///     // ...binding initialization...
///   }
94
///
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
///   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
115
/// to be overridden by developers who have more specific needs, while
116 117 118 119 120 121 122 123 124 125
/// 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
126
/// // continuing from previous example...
127 128 129 130 131 132 133 134 135 136
/// class FooLibraryBinding extends BindingBase with BarBinding, FooBinding {
///   static FooBinding ensureInitialized() {
///     if (FooBinding._instance == null) {
///       FooLibraryBinding();
///     }
///     return FooBinding.instance;
///   }
/// }
/// ```
/// {@end-tool}
137
abstract class BindingBase {
138 139 140 141 142
  /// 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
143
  /// VM service extensions, if any.
144
  BindingBase() {
145 146 147
    if (!kReleaseMode) {
      FlutterTimeline.startSync('Framework initialization');
    }
148 149 150 151
    assert(() {
      _debugConstructed = true;
      return true;
    }());
152

153
    assert(_debugInitializedType == null, 'Binding is already initialized to $_debugInitializedType');
154
    initInstances();
155
    assert(_debugInitializedType != null);
156 157 158 159

    assert(!_debugServiceExtensionsRegistered);
    initServiceExtensions();
    assert(_debugServiceExtensionsRegistered);
160

161
    if (!kReleaseMode) {
162
      developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
163 164
      FlutterTimeline.finishSync();
    }
165 166
  }

167
  bool _debugConstructed = false;
168
  static Type? _debugInitializedType;
169
  static bool _debugServiceExtensionsRegistered = false;
170

171
  /// Deprecated. Will be removed in a future version of Flutter.
172
  ///
173 174
  /// This property has been deprecated to prepare for Flutter's upcoming
  /// support for multiple views and multiple windows.
175
  ///
176 177 178 179 180
  /// 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.
181
  ///
182 183
  /// The following options exists to migrate code that relies on accessing
  /// this deprecated property:
184
  ///
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
  /// 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:
201
  ///
202 203 204 205 206 207 208 209 210 211 212
  /// * [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.'
  )
213 214 215 216 217 218 219
  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
220
  /// with a [ui.PlatformDispatcher], e.g., [ServicesBinding] registers
221
  /// listeners with the [ChannelBuffers], [RendererBinding]
222
  /// registers [ui.PlatformDispatcher.onMetricsChanged],
223 224 225 226
  /// [ui.PlatformDispatcher.onTextScaleFactorChanged], and [SemanticsBinding]
  /// registers [ui.PlatformDispatcher.onSemanticsEnabledChanged],
  /// [ui.PlatformDispatcher.onSemanticsActionEvent], and
  /// [ui.PlatformDispatcher.onAccessibilityFeaturesChanged] handlers.
227 228 229 230 231 232
  ///
  /// 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
233
  /// [TestWidgetsFlutterBinding], can override this accessor to return a
234 235
  /// different [ui.PlatformDispatcher] implementation.
  ui.PlatformDispatcher get platformDispatcher => ui.PlatformDispatcher.instance;
236

237 238 239 240
  /// The initialization method. Subclasses override this method to hook into
  /// the platform and otherwise configure their services. Subclasses must call
  /// "super.initInstances()".
  ///
241 242 243 244 245 246 247 248 249 250 251
  /// 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
252
  /// `MixinClassName._instance`, a static field that is set by
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
  /// `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}
274 275
  @protected
  @mustCallSuper
276
  void initInstances() {
277
    assert(_debugInitializedType == null);
278
    assert(() {
279
      _debugInitializedType = runtimeType;
280
      _debugBindingZone = Zone.current;
281 282
      return true;
    }());
283 284
  }

285 286 287 288 289 290 291 292 293 294 295 296 297 298
  /// 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(() {
299
      if (_debugInitializedType == null && instance == null) {
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
        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) {
320
        assert(_debugInitializedType == null);
321 322 323 324 325 326 327 328 329 330 331 332 333 334
        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 '
335
            'documentation of the BindingBase class for more details.',
336
          ),
337 338 339
          ErrorHint(
            'The binding that was initialized was of the type "$_debugInitializedType". '
          ),
340 341 342
        ]);
      }
      try {
343
        if (instance._debugConstructed && _debugInitializedType == null) {
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
          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) {
359
          // The state of _debugInitializedType doesn't matter in this failure mode.
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
          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!;
  }

385 386 387 388 389 390
  /// 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.
  ///
apeltop's avatar
apeltop committed
391
  /// For example, if an application uses [Zone]s to report uncaught exceptions, it may
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
  /// 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;
  }

415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
  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;
  }

504 505 506 507 508 509 510 511 512 513 514 515 516
  /// 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.
  ///
517
  /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
518 519 520
  ///
  /// See also:
  ///
521
  ///  * <https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#rpcs-requests-and-responses>
522 523
  @protected
  @mustCallSuper
524 525
  void initServiceExtensions() {
    assert(!_debugServiceExtensionsRegistered);
526 527 528

    assert(() {
      registerSignalServiceExtension(
529
        name: FoundationServiceExtensions.reassemble.name,
530 531 532 533 534
        callback: reassembleApplication,
      );
      return true;
    }());

535 536 537
    if (!kReleaseMode) {
      if (!kIsWeb) {
        registerSignalServiceExtension(
538
          name: FoundationServiceExtensions.exit.name,
539 540 541 542 543
          callback: _exitApplication,
        );
      }
      // These service extensions are used in profile mode applications.
      registerStringServiceExtension(
544
        name: FoundationServiceExtensions.connectedVmServiceUri.name,
545 546 547 548 549 550
        getter: () async => connectedVmServiceUri ?? '',
        setter: (String uri) async {
          connectedVmServiceUri = uri;
        },
      );
      registerStringServiceExtension(
551
        name: FoundationServiceExtensions.activeDevToolsServerAddress.name,
552 553 554 555
        getter: () async => activeDevToolsServerAddress ?? '',
        setter: (String serverAddress) async {
          activeDevToolsServerAddress = serverAddress;
        },
556 557 558
      );
    }

559 560
    assert(() {
      registerServiceExtension(
561
        name: FoundationServiceExtensions.platformOverride.name,
562 563
        callback: (Map<String, String> parameters) async {
          if (parameters.containsKey('value')) {
564 565 566 567 568 569 570
            final String value = parameters['value']!;
            debugDefaultTargetPlatformOverride = null;
            for (final TargetPlatform candidate in TargetPlatform.values) {
              if (candidate.name == value) {
                debugDefaultTargetPlatformOverride = candidate;
                break;
              }
571
            }
572
            _postExtensionStateChangedEvent(
573
              FoundationServiceExtensions.platformOverride.name,
574
              defaultTargetPlatform.name,
575
            );
576 577
            await reassembleApplication();
          }
578
          return <String, dynamic>{
579
            'value': defaultTargetPlatform.name,
580
          };
581
        },
582
      );
583 584

      registerServiceExtension(
585
        name: FoundationServiceExtensions.brightnessOverride.name,
586 587 588 589 590 591 592 593 594 595 596
        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(
597
              FoundationServiceExtensions.brightnessOverride.name,
598
              (debugBrightnessOverride ?? platformDispatcher.platformBrightness).toString(),
599 600 601 602
            );
            await reassembleApplication();
          }
          return <String, dynamic>{
603
            'value': (debugBrightnessOverride ?? platformDispatcher.platformBrightness).toString(),
604 605 606
          };
        },
      );
607
      return true;
608
    }());
609 610 611 612
    assert(() {
      _debugServiceExtensionsRegistered = true;
      return true;
    }());
613 614
  }

615 616 617 618 619 620 621 622 623 624 625 626 627 628
  /// 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
629 630 631
  /// 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).
632 633
  ///
  /// The [Future] returned by the `callback` argument is returned by [lockEvents].
634 635 636 637 638
  ///
  /// 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].
639
  @protected
640
  Future<void> lockEvents(Future<void> Function() callback) {
641 642 643 644
    developer.TimelineTask? debugTimelineTask;
    if (!kReleaseMode) {
      debugTimelineTask = developer.TimelineTask()..start('Lock events');
    }
645

646
    _lockCount += 1;
647
    final Future<void> future = callback();
648 649
    future.whenComplete(() {
      _lockCount -= 1;
650
      if (!locked) {
651 652 653
        if (!kReleaseMode) {
          debugTimelineTask!.finish();
        }
654 655 656 657 658 659 660 661 662 663
        try {
          unlocked();
        } catch (error, stack) {
          FlutterError.reportError(FlutterErrorDetails(
            exception: error,
            stack: stack,
            library: 'foundation',
            context: ErrorDescription('while handling pending events'),
          ));
        }
664
      }
665 666 667 668 669 670 671 672
    });
    return future;
  }

  /// Called by [lockEvents] when events get unlocked.
  ///
  /// This should flush any events that were queued while [locked] was true.
  @protected
673
  @mustCallSuper
674 675 676 677
  void unlocked() {
    assert(!locked);
  }

678
  /// Cause the entire application to redraw, e.g. after a hot reload.
679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695
  ///
  /// 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.
696
  Future<void> reassembleApplication() {
697 698 699 700
    return lockEvents(performReassemble);
  }

  /// This method is called by [reassembleApplication] to actually cause the
701
  /// application to reassemble, e.g. after a hot reload.
702
  ///
703
  /// Bindings are expected to use this method to re-register anything that uses
704 705 706 707 708 709 710 711
  /// 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
712
  Future<void> performReassemble() {
713
    FlutterError.resetErrorCount();
714
    return Future<void>.value();
715
  }
716 717 718 719 720

  /// Registers a service extension method with the given name (full
  /// name "ext.flutter.name"), which takes no arguments and returns
  /// no value.
  ///
721
  /// Calls the `callback` callback when the service extension is called.
722
  ///
723
  /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
724
  @protected
725
  void registerSignalServiceExtension({
726 727
    required String name,
    required AsyncCallback callback,
728 729 730 731
  }) {
    registerServiceExtension(
      name: name,
      callback: (Map<String, String> parameters) async {
732
        await callback();
733
        return <String, dynamic>{};
734
      },
735 736 737 738 739 740 741 742 743 744
    );
  }

  /// 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.)
  ///
745 746
  /// Calls the `getter` callback to obtain the value when
  /// responding to the service extension method being called.
747
  ///
748 749
  /// Calls the `setter` callback with the new value when the
  /// service extension method is called with a new value.
750
  ///
751
  /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
752
  @protected
753
  void registerBoolServiceExtension({
754 755 756
    required String name,
    required AsyncValueGetter<bool> getter,
    required AsyncValueSetter<bool> setter,
757 758
  }) {
    registerServiceExtension(
759
      name: name,
760
      callback: (Map<String, String> parameters) async {
761
        if (parameters.containsKey('enabled')) {
762
          await setter(parameters['enabled'] == 'true');
763 764
          _postExtensionStateChangedEvent(name, await getter() ? 'true' : 'false');
        }
765
        return <String, dynamic>{'enabled': await getter() ? 'true' : 'false'};
766
      },
767 768 769 770 771 772 773 774 775
    );
  }

  /// 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.)
  ///
776 777
  /// Calls the `getter` callback to obtain the value when
  /// responding to the service extension method being called.
778
  ///
779 780
  /// Calls the `setter` callback with the new value when the
  /// service extension method is called with a new value.
781
  ///
782
  /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
783
  @protected
784
  void registerNumericServiceExtension({
785 786 787
    required String name,
    required AsyncValueGetter<double> getter,
    required AsyncValueSetter<double> setter,
788 789 790 791
  }) {
    registerServiceExtension(
      name: name,
      callback: (Map<String, String> parameters) async {
792
        if (parameters.containsKey(name)) {
793
          await setter(double.parse(parameters[name]!));
794 795
          _postExtensionStateChangedEvent(name, (await getter()).toString());
        }
796
        return <String, dynamic>{name: (await getter()).toString()};
797
      },
798 799 800
    );
  }

801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824
  /// 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.
825 826
  ///
  /// This is unrelated to the events managed by [lockEvents].
827 828 829 830 831
  @protected
  void postEvent(String eventKind, Map<String, dynamic> eventData) {
    developer.postEvent(eventKind, eventData);
  }

832 833 834 835 836 837 838 839 840 841
  /// 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.
842
  ///
843
  /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
844
  @protected
845
  void registerStringServiceExtension({
846 847 848
    required String name,
    required AsyncValueGetter<String> getter,
    required AsyncValueSetter<String> setter,
849 850 851 852
  }) {
    registerServiceExtension(
      name: name,
      callback: (Map<String, String> parameters) async {
853
        if (parameters.containsKey('value')) {
854
          await setter(parameters['value']!);
855 856
          _postExtensionStateChangedEvent(name, await getter());
        }
857
        return <String, dynamic>{'value': await getter()};
858
      },
859 860 861
    );
  }

862 863 864 865 866 867 868 869 870
  /// 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.
871 872
  ///
  /// The returned map will be mutated.
873
  ///
874
  /// {@template flutter.foundation.BindingBase.registerServiceExtension}
875 876 877 878 879 880 881
  /// 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).
  ///
882
  /// {@tool snippet}
883
  /// The following code registers a service extension that is only included in
884
  /// debug builds.
885 886
  ///
  /// ```dart
887 888 889 890 891 892
  /// void myRegistrationFunction() {
  ///   assert(() {
  ///     // Register your service extension here.
  ///     return true;
  ///   }());
  /// }
893
  /// ```
894
  /// {@end-tool}
895
  ///
896
  /// {@tool snippet}
897
  /// A service extension registered with the following code snippet is
898
  /// available in debug and profile mode.
899 900
  ///
  /// ```dart
901
  /// void myOtherRegistrationFunction() {
902 903 904 905 906
  ///   // kReleaseMode is defined in the 'flutter/foundation.dart' package.
  ///   if (!kReleaseMode) {
  ///     // Register your service extension here.
  ///   }
  /// }
907
  /// ```
908
  /// {@end-tool}
909 910 911
  ///
  /// Both guards ensure that Dart's tree shaker can remove the code for the
  /// service extension in release builds.
912
  /// {@endtemplate}
913
  @protected
914
  void registerServiceExtension({
915 916
    required String name,
    required ServiceExtensionCallback callback,
917 918 919 920
  }) {
    final String methodName = 'ext.flutter.$name';
    developer.registerExtension(methodName, (String method, Map<String, String> parameters) async {
      assert(method == methodName);
921
      assert(() {
922
        if (debugInstrumentationEnabled) {
923
          debugPrint('service extension method received: $method($parameters)');
924
        }
925 926
        return true;
      }());
927 928 929 930 931 932 933 934 935 936 937

      // 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.
938
      await debugInstrumentAction<void>('Wait for outer event loop', () {
939
        return Future<void>.delayed(Duration.zero);
940
      });
941

942
      late Map<String, dynamic> result;
943 944 945
      try {
        result = await callback(parameters);
      } catch (exception, stack) {
946
        FlutterError.reportError(FlutterErrorDetails(
947 948
          exception: exception,
          stack: stack,
949
          context: ErrorDescription('during a service extension callback for "$method"'),
950
        ));
951
        return developer.ServiceExtensionResponse.error(
952
          developer.ServiceExtensionResponse.extensionError,
953
          json.encode(<String, String>{
954 955
            'exception': exception.toString(),
            'stack': stack.toString(),
956
            'method': method,
957
          }),
958 959
        );
      }
960 961 962
      result['type'] = '_extensionType';
      result['method'] = method;
      return developer.ServiceExtensionResponse.result(json.encode(result));
963 964 965
    });
  }

966
  @override
967
  String toString() => '<${objectRuntimeType(this, 'BindingBase')}>';
968
}
969 970

/// Terminate the Flutter application.
971
Future<void> _exitApplication() async {
972 973
  exit(0);
}