binding.dart 40.5 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

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

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

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

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

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
/// 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`.
84
///
85 86 87 88 89 90 91 92
/// ```dart
/// mixin FooBinding on BindingBase, BarBinding {
///   @override
///   void initInstances() {
///     super.initInstances();
///     _instance = this;
///     // ...binding initialization...
///   }
93
///
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
///   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
114
/// to be overridden by developers who have more specific needs, while
115 116 117 118 119 120 121 122 123 124
/// 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
125
/// // continuing from previous example...
126 127 128 129 130 131 132 133 134 135
/// class FooLibraryBinding extends BindingBase with BarBinding, FooBinding {
///   static FooBinding ensureInitialized() {
///     if (FooBinding._instance == null) {
///       FooLibraryBinding();
///     }
///     return FooBinding.instance;
///   }
/// }
/// ```
/// {@end-tool}
136
abstract class BindingBase {
137 138 139 140 141
  /// 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
142
  /// VM service extensions, if any.
143
  BindingBase() {
144
    developer.Timeline.startSync('Framework initialization');
145 146 147 148
    assert(() {
      _debugConstructed = true;
      return true;
    }());
149

150
    assert(_debugInitializedType == null, 'Binding is already initialized to $_debugInitializedType');
151
    initInstances();
152
    assert(_debugInitializedType != null);
153 154 155 156

    assert(!_debugServiceExtensionsRegistered);
    initServiceExtensions();
    assert(_debugServiceExtensionsRegistered);
157

158
    developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
159

160
    developer.Timeline.finishSync();
161 162
  }

163
  bool _debugConstructed = false;
164
  static Type? _debugInitializedType;
165
  static bool _debugServiceExtensionsRegistered = false;
166

167 168 169 170 171 172 173
  /// Additional configuration used by the framework during hot reload.
  ///
  /// See also:
  ///
  ///  * [DebugReassembleConfig], which describes the configuration.
  static DebugReassembleConfig? debugReassembleConfig;

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

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

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

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

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 504 505
  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;
  }

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

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

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

561 562
    assert(() {
      registerServiceExtension(
563
        name: FoundationServiceExtensions.platformOverride.name,
564 565 566 567 568
        callback: (Map<String, String> parameters) async {
          if (parameters.containsKey('value')) {
            switch (parameters['value']) {
              case 'android':
                debugDefaultTargetPlatformOverride = TargetPlatform.android;
569 570
              case 'fuchsia':
                debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
571 572
              case 'iOS':
                debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
573 574
              case 'linux':
                debugDefaultTargetPlatformOverride = TargetPlatform.linux;
575 576
              case 'macOS':
                debugDefaultTargetPlatformOverride = TargetPlatform.macOS;
577 578
              case 'windows':
                debugDefaultTargetPlatformOverride = TargetPlatform.windows;
579 580 581 582
              case 'default':
              default:
                debugDefaultTargetPlatformOverride = null;
            }
583
            _postExtensionStateChangedEvent(
584
              FoundationServiceExtensions.platformOverride.name,
585 586
              defaultTargetPlatform.toString().substring('$TargetPlatform.'.length),
            );
587 588
            await reassembleApplication();
          }
589
          return <String, dynamic>{
590 591 592 593
            'value': defaultTargetPlatform
                     .toString()
                     .substring('$TargetPlatform.'.length),
          };
594
        },
595
      );
596 597

      registerServiceExtension(
598
        name: FoundationServiceExtensions.brightnessOverride.name,
599 600 601 602 603 604 605 606 607 608 609
        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(
610
              FoundationServiceExtensions.brightnessOverride.name,
611
              (debugBrightnessOverride ?? platformDispatcher.platformBrightness).toString(),
612 613 614 615
            );
            await reassembleApplication();
          }
          return <String, dynamic>{
616
            'value': (debugBrightnessOverride ?? platformDispatcher.platformBrightness).toString(),
617 618 619
          };
        },
      );
620
      return true;
621
    }());
622 623 624 625
    assert(() {
      _debugServiceExtensionsRegistered = true;
      return true;
    }());
626 627
  }

628 629 630 631 632 633 634 635 636 637 638 639 640 641
  /// 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
642 643 644
  /// 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).
645 646
  ///
  /// The [Future] returned by the `callback` argument is returned by [lockEvents].
647 648 649 650 651
  ///
  /// 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].
652
  @protected
653
  Future<void> lockEvents(Future<void> Function() callback) {
654
    final developer.TimelineTask timelineTask = developer.TimelineTask()..start('Lock events');
655

656
    _lockCount += 1;
657
    final Future<void> future = callback();
658 659
    future.whenComplete(() {
      _lockCount -= 1;
660
      if (!locked) {
661
        timelineTask.finish();
662 663 664 665 666 667 668 669 670 671
        try {
          unlocked();
        } catch (error, stack) {
          FlutterError.reportError(FlutterErrorDetails(
            exception: error,
            stack: stack,
            library: 'foundation',
            context: ErrorDescription('while handling pending events'),
          ));
        }
672
      }
673 674 675 676 677 678 679 680
    });
    return future;
  }

  /// Called by [lockEvents] when events get unlocked.
  ///
  /// This should flush any events that were queued while [locked] was true.
  @protected
681
  @mustCallSuper
682 683 684 685
  void unlocked() {
    assert(!locked);
  }

686
  /// Cause the entire application to redraw, e.g. after a hot reload.
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
  ///
  /// 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.
704
  Future<void> reassembleApplication() {
705 706 707 708
    return lockEvents(performReassemble);
  }

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

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

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

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

809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832
  /// 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.
833 834
  ///
  /// This is unrelated to the events managed by [lockEvents].
835 836 837 838 839
  @protected
  void postEvent(String eventKind, Map<String, dynamic> eventData) {
    developer.postEvent(eventKind, eventData);
  }

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

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

      // 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.
946
      await debugInstrumentAction<void>('Wait for outer event loop', () {
947
        return Future<void>.delayed(Duration.zero);
948
      });
949

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

974
  @override
975
  String toString() => '<${objectRuntimeType(this, 'BindingBase')}>';
976
}
977 978

/// Terminate the Flutter application.
979
Future<void> _exitApplication() async {
980 981
  exit(0);
}
982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001

/// 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;
}