binding.dart 35.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:convert' show json;
6
import 'dart:developer' as developer;
7
import 'dart:io' show exit;
8
import 'dart:ui' as ui show Brightness, PlatformDispatcher, SingletonFlutterWindow, window;
9
// Before adding any more dart:ui imports, please read the README.
10

11
import 'package:meta/meta.dart';
12 13 14

import 'assertions.dart';
import 'basic_types.dart';
15
import 'constants.dart';
16
import 'debug.dart';
17
import 'object.dart';
18
import 'platform.dart';
19
import 'print.dart';
20

21 22
export 'dart:ui' show PlatformDispatcher, SingletonFlutterWindow;

23 24
export 'basic_types.dart' show AsyncCallback, AsyncValueGetter, AsyncValueSetter;

25 26 27
// Examples can assume:
// mixin BarBinding on BindingBase { }

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

37 38 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
/// 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`.
81
///
82 83 84 85 86 87 88 89
/// ```dart
/// mixin FooBinding on BindingBase, BarBinding {
///   @override
///   void initInstances() {
///     super.initInstances();
///     _instance = this;
///     // ...binding initialization...
///   }
90
///
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
///   static FooBinding get instance => BindingBase.checkInstance(_instance);
///   static FooBinding? _instance;
///
///   // ...binding features...
/// }
/// ```
/// {@end-tool}
///
/// ## Implementing a binding class
///
/// The top-most layer used to write the application (e.g. the Flutter
/// [widgets] library) will have a concrete class that inherits from
/// [BindingBase] and uses all the various [BindingBase] mixins (such
/// as [ServicesBinding]). The [widgets] library in Flutter introduces
/// a binding called [WidgetsFlutterBinding].
///
/// A binding _class_ should mix in the relevant bindings from each
/// layer that it wishes to expose, and should have an
/// `ensureInitialized` method that constructs the class if that
/// layer's mixin's `_instance` field is null. This allows the binding
/// to be overriden by developers who have more specific needs, while
/// still allowing other code to call `ensureInitialized` when a binding
/// is needed.
///
/// {@tool snippet}
///
/// A typical binding class is shown below. The `ensureInitialized` method's
/// return type is the library's binding mixin, rather than the concrete
/// class.
///
/// ```dart
/// class FooLibraryBinding extends BindingBase with BarBinding, FooBinding {
///   static FooBinding ensureInitialized() {
///     if (FooBinding._instance == null) {
///       FooLibraryBinding();
///     }
///     return FooBinding.instance;
///   }
/// }
/// ```
/// {@end-tool}
132
abstract class BindingBase {
133 134 135 136 137 138
  /// Default abstract constructor for bindings.
  ///
  /// First calls [initInstances] to have bindings initialize their
  /// instance pointers and other state, then calls
  /// [initServiceExtensions] to have bindings initialize their
  /// observatory service extensions, if any.
139
  BindingBase() {
140
    developer.Timeline.startSync('Framework initialization');
141 142 143 144
    assert(() {
      _debugConstructed = true;
      return true;
    }());
145

146
    assert(_debugInitializedType == null);
147
    initInstances();
148
    assert(_debugInitializedType != null);
149 150 151 152

    assert(!_debugServiceExtensionsRegistered);
    initServiceExtensions();
    assert(_debugServiceExtensionsRegistered);
153

154
    developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
155

156
    developer.Timeline.finishSync();
157 158
  }

159
  bool _debugConstructed = false;
160
  static Type? _debugInitializedType;
161
  static bool _debugServiceExtensionsRegistered = false;
162

163 164 165 166 167 168 169
  /// Additional configuration used by the framework during hot reload.
  ///
  /// See also:
  ///
  ///  * [DebugReassembleConfig], which describes the configuration.
  static DebugReassembleConfig? debugReassembleConfig;

170
  /// The main window to which this binding is bound.
171
  ///
172 173 174 175
  /// A number of additional bindings are defined as extensions of
  /// [BindingBase], e.g., [ServicesBinding], [RendererBinding], and
  /// [WidgetsBinding]. Each of these bindings define behaviors that interact
  /// with a [ui.SingletonFlutterWindow].
176
  ///
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
  /// Each of these other bindings could individually access a
  /// [ui.SingletonFlutterWindow] statically, but that would preclude the
  /// ability to test its behaviors with a fake window for verification
  /// purposes.  Therefore, [BindingBase] exposes this
  /// [ui.SingletonFlutterWindow] for use by other bindings.  A subclass of
  /// [BindingBase], such as [TestWidgetsFlutterBinding], can override this
  /// accessor to return a different [ui.SingletonFlutterWindow] implementation,
  /// such as a [TestWindow].
  ///
  /// The `window` is a singleton meant for use by applications that only have a
  /// single main window. In addition to the properties of [ui.FlutterWindow],
  /// `window` provides access to platform-specific properties and callbacks
  /// available on the [platformDispatcher].
  ///
  /// For applications designed for more than one main window, prefer using the
  /// [platformDispatcher] to access available views via
  /// [ui.PlatformDispatcher.views].
  ///
  /// However, multiple window support is not yet implemented, so currently this
  /// provides access to the one and only window.
  // TODO(gspencergoog): remove the preceding note once multi-window support is
  // active.
  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
206 207
  /// with a [ui.PlatformDispatcher], e.g., [ServicesBinding] registers
  /// listeners with the [ChannelBuffers], and [RendererBinding]
208 209 210 211 212 213 214 215 216 217
  /// 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
218
  /// [TestWidgetsFlutterBinding], can override this accessor to return a
219 220
  /// different [ui.PlatformDispatcher] implementation.
  ui.PlatformDispatcher get platformDispatcher => ui.PlatformDispatcher.instance;
221

222 223 224 225
  /// The initialization method. Subclasses override this method to hook into
  /// the platform and otherwise configure their services. Subclasses must call
  /// "super.initInstances()".
  ///
226 227 228 229 230 231 232 233 234 235 236
  /// 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
237
  /// `MixinClassName._instance`, a static field that is set by
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
  /// `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}
259 260
  @protected
  @mustCallSuper
261
  void initInstances() {
262
    assert(_debugInitializedType == null);
263
    assert(() {
264
      _debugInitializedType = runtimeType;
265 266
      return true;
    }());
267 268
  }

269 270 271 272 273 274 275 276 277 278 279 280 281 282
  /// 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(() {
283
      if (_debugInitializedType == null && instance == null) {
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
        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) {
304
        assert(_debugInitializedType == null);
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('Binding mixin instance is null but bindings are already initialized.'),
          ErrorDescription(
            'The "instance" property of the $T binding mixin was accessed, but that binding was not initialized when '
            'the "initInstances()" method was called.',
          ),
          ErrorHint(
            'This probably indicates that the $T mixin was not mixed into the class that was used to initialize the binding. '
            'If this is a custom binding mixin, there must also be a custom binding class, like WidgetsFlutterBinding, '
            'but that mixes in the selected binding. If this is a test binding, check that the binding being initialized '
            'is the same as the one into which the test binding is mixed.',
          ),
          ErrorHint(
            'It is also possible that $T does not implement "initInstances()" to assign a value to "instance". See the '
            'documentation of the BaseBinding class for more details.',
          ),
321 322 323
          ErrorHint(
            'The binding that was initialized was of the type "$_debugInitializedType". '
          ),
324 325 326 327
        ]);
      }
      try {
        assert(instance != null);
328
        if (instance._debugConstructed && _debugInitializedType == null) {
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
          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) {
344
          // The state of _debugInitializedType doesn't matter in this failure mode.
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
          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!;
  }

370 371 372 373 374 375
  /// 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
376
  /// For example, if an application uses [Zone]s to report uncaught exceptions, it may
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
  /// 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;
  }

400 401 402 403 404 405 406 407 408 409 410 411 412
  /// 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.
  ///
413
  /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
414 415 416
  ///
  /// See also:
  ///
417
  ///  * <https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#rpcs-requests-and-responses>
418 419
  @protected
  @mustCallSuper
420 421
  void initServiceExtensions() {
    assert(!_debugServiceExtensionsRegistered);
422 423 424 425 426 427 428 429 430

    assert(() {
      registerSignalServiceExtension(
        name: 'reassemble',
        callback: reassembleApplication,
      );
      return true;
    }());

431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
    if (!kReleaseMode) {
      if (!kIsWeb) {
        registerSignalServiceExtension(
          name: 'exit',
          callback: _exitApplication,
        );
      }
      // These service extensions are used in profile mode applications.
      registerStringServiceExtension(
        name: 'connectedVmServiceUri',
        getter: () async => connectedVmServiceUri ?? '',
        setter: (String uri) async {
          connectedVmServiceUri = uri;
        },
      );
      registerStringServiceExtension(
        name: 'activeDevToolsServerAddress',
        getter: () async => activeDevToolsServerAddress ?? '',
        setter: (String serverAddress) async {
          activeDevToolsServerAddress = serverAddress;
        },
452 453 454
      );
    }

455
    assert(() {
456
      const String platformOverrideExtensionName = 'platformOverride';
457
      registerServiceExtension(
458
        name: platformOverrideExtensionName,
459 460 461 462 463 464
        callback: (Map<String, String> parameters) async {
          if (parameters.containsKey('value')) {
            switch (parameters['value']) {
              case 'android':
                debugDefaultTargetPlatformOverride = TargetPlatform.android;
                break;
465 466 467
              case 'fuchsia':
                debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
                break;
468 469 470
              case 'iOS':
                debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
                break;
471 472 473
              case 'linux':
                debugDefaultTargetPlatformOverride = TargetPlatform.linux;
                break;
474 475 476
              case 'macOS':
                debugDefaultTargetPlatformOverride = TargetPlatform.macOS;
                break;
477 478
              case 'windows':
                debugDefaultTargetPlatformOverride = TargetPlatform.windows;
479 480 481 482 483
                break;
              case 'default':
              default:
                debugDefaultTargetPlatformOverride = null;
            }
484 485 486 487
            _postExtensionStateChangedEvent(
              platformOverrideExtensionName,
              defaultTargetPlatform.toString().substring('$TargetPlatform.'.length),
            );
488 489
            await reassembleApplication();
          }
490
          return <String, dynamic>{
491 492 493 494
            'value': defaultTargetPlatform
                     .toString()
                     .substring('$TargetPlatform.'.length),
          };
495
        },
496
      );
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514

      const String brightnessOverrideExtensionName = 'brightnessOverride';
      registerServiceExtension(
        name: brightnessOverrideExtensionName,
        callback: (Map<String, String> parameters) async {
          if (parameters.containsKey('value')) {
            switch (parameters['value']) {
              case 'Brightness.light':
                debugBrightnessOverride = ui.Brightness.light;
                break;
              case 'Brightness.dark':
                debugBrightnessOverride = ui.Brightness.dark;
                break;
              default:
                debugBrightnessOverride = null;
            }
            _postExtensionStateChangedEvent(
              brightnessOverrideExtensionName,
515
              (debugBrightnessOverride ?? platformDispatcher.platformBrightness).toString(),
516 517 518 519
            );
            await reassembleApplication();
          }
          return <String, dynamic>{
520
            'value': (debugBrightnessOverride ?? platformDispatcher.platformBrightness).toString(),
521 522 523
          };
        },
      );
524
      return true;
525
    }());
526 527 528 529
    assert(() {
      _debugServiceExtensionsRegistered = true;
      return true;
    }());
530 531
  }

532 533 534 535 536 537 538 539 540 541 542 543 544 545
  /// 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
546 547 548
  /// 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).
549 550 551
  ///
  /// The [Future] returned by the `callback` argument is returned by [lockEvents].
  @protected
552
  Future<void> lockEvents(Future<void> Function() callback) {
553
    final developer.TimelineTask timelineTask = developer.TimelineTask()..start('Lock events');
554

555 556
    assert(callback != null);
    _lockCount += 1;
557
    final Future<void> future = callback();
558
    assert(future != null, 'The lockEvents() callback returned null; it should return a Future<void> that completes when the lock is to expire.');
559 560
    future.whenComplete(() {
      _lockCount -= 1;
561
      if (!locked) {
562
        timelineTask.finish();
563
        unlocked();
564
      }
565 566 567 568 569 570 571 572
    });
    return future;
  }

  /// Called by [lockEvents] when events get unlocked.
  ///
  /// This should flush any events that were queued while [locked] was true.
  @protected
573
  @mustCallSuper
574 575 576 577
  void unlocked() {
    assert(!locked);
  }

578
  /// Cause the entire application to redraw, e.g. after a hot reload.
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
  ///
  /// 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.
596
  Future<void> reassembleApplication() {
597 598 599 600
    return lockEvents(performReassemble);
  }

  /// This method is called by [reassembleApplication] to actually cause the
601
  /// application to reassemble, e.g. after a hot reload.
602
  ///
603
  /// Bindings are expected to use this method to re-register anything that uses
604 605 606 607 608 609 610 611
  /// 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
612
  Future<void> performReassemble() {
613
    FlutterError.resetErrorCount();
614
    return Future<void>.value();
615
  }
616 617 618 619 620

  /// Registers a service extension method with the given name (full
  /// name "ext.flutter.name"), which takes no arguments and returns
  /// no value.
  ///
621
  /// Calls the `callback` callback when the service extension is called.
622
  ///
623
  /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
624
  @protected
625
  void registerSignalServiceExtension({
626 627
    required String name,
    required AsyncCallback callback,
628 629 630 631 632 633
  }) {
    assert(name != null);
    assert(callback != null);
    registerServiceExtension(
      name: name,
      callback: (Map<String, String> parameters) async {
634
        await callback();
635
        return <String, dynamic>{};
636
      },
637 638 639 640 641 642 643 644 645 646
    );
  }

  /// 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.)
  ///
647 648
  /// Calls the `getter` callback to obtain the value when
  /// responding to the service extension method being called.
649
  ///
650 651
  /// Calls the `setter` callback with the new value when the
  /// service extension method is called with a new value.
652
  ///
653
  /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
654
  @protected
655
  void registerBoolServiceExtension({
656 657 658
    required String name,
    required AsyncValueGetter<bool> getter,
    required AsyncValueSetter<bool> setter,
659 660 661 662 663
  }) {
    assert(name != null);
    assert(getter != null);
    assert(setter != null);
    registerServiceExtension(
664
      name: name,
665
      callback: (Map<String, String> parameters) async {
666
        if (parameters.containsKey('enabled')) {
667
          await setter(parameters['enabled'] == 'true');
668 669
          _postExtensionStateChangedEvent(name, await getter() ? 'true' : 'false');
        }
670
        return <String, dynamic>{'enabled': await getter() ? 'true' : 'false'};
671
      },
672 673 674 675 676 677 678 679 680
    );
  }

  /// 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.)
  ///
681 682
  /// Calls the `getter` callback to obtain the value when
  /// responding to the service extension method being called.
683
  ///
684 685
  /// Calls the `setter` callback with the new value when the
  /// service extension method is called with a new value.
686
  ///
687
  /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
688
  @protected
689
  void registerNumericServiceExtension({
690 691 692
    required String name,
    required AsyncValueGetter<double> getter,
    required AsyncValueSetter<double> setter,
693 694 695 696 697 698 699
  }) {
    assert(name != null);
    assert(getter != null);
    assert(setter != null);
    registerServiceExtension(
      name: name,
      callback: (Map<String, String> parameters) async {
700
        if (parameters.containsKey(name)) {
701
          await setter(double.parse(parameters[name]!));
702 703
          _postExtensionStateChangedEvent(name, (await getter()).toString());
        }
704
        return <String, dynamic>{name: (await getter()).toString()};
705
      },
706 707 708
    );
  }

709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
  /// Sends an event when a service extension's state is changed.
  ///
  /// Clients should listen for this event to stay aware of the current service
  /// extension state. Any service extension that manages a state should call
  /// this method on state change.
  ///
  /// `value` reflects the newly updated service extension value.
  ///
  /// This will be called automatically for service extensions registered via
  /// [registerBoolServiceExtension], [registerNumericServiceExtension], or
  /// [registerStringServiceExtension].
  void _postExtensionStateChangedEvent(String name, dynamic value) {
    postEvent(
      'Flutter.ServiceExtensionStateChanged',
      <String, dynamic>{
        'extension': 'ext.flutter.$name',
        'value': value,
      },
    );
  }

  /// All events dispatched by a [BindingBase] use this method instead of
  /// calling [developer.postEvent] directly so that tests for [BindingBase]
  /// can track which events were dispatched by overriding this method.
  @protected
  void postEvent(String eventKind, Map<String, dynamic> eventData) {
    developer.postEvent(eventKind, eventData);
  }

738 739 740 741 742 743 744 745 746 747
  /// 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.
748
  ///
749
  /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
750
  @protected
751
  void registerStringServiceExtension({
752 753 754
    required String name,
    required AsyncValueGetter<String> getter,
    required AsyncValueSetter<String> setter,
755 756 757 758 759 760 761
  }) {
    assert(name != null);
    assert(getter != null);
    assert(setter != null);
    registerServiceExtension(
      name: name,
      callback: (Map<String, String> parameters) async {
762
        if (parameters.containsKey('value')) {
763
          await setter(parameters['value']!);
764 765
          _postExtensionStateChangedEvent(name, await getter());
        }
766
        return <String, dynamic>{'value': await getter()};
767
      },
768 769 770
    );
  }

771 772 773 774 775 776 777 778 779
  /// 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.
780 781
  ///
  /// The returned map will be mutated.
782
  ///
783
  /// {@template flutter.foundation.BindingBase.registerServiceExtension}
784 785 786 787 788 789 790
  /// 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).
  ///
791
  /// {@tool snippet}
792
  /// The following code registers a service extension that is only included in
793
  /// debug builds.
794 795
  ///
  /// ```dart
796 797 798 799 800 801
  /// void myRegistrationFunction() {
  ///   assert(() {
  ///     // Register your service extension here.
  ///     return true;
  ///   }());
  /// }
802
  /// ```
803
  /// {@end-tool}
804
  ///
805
  /// {@tool snippet}
806
  /// A service extension registered with the following code snippet is
807
  /// available in debug and profile mode.
808 809
  ///
  /// ```dart
810
  /// void myOtherRegistrationFunction() {
811 812 813 814 815
  ///   // kReleaseMode is defined in the 'flutter/foundation.dart' package.
  ///   if (!kReleaseMode) {
  ///     // Register your service extension here.
  ///   }
  /// }
816
  /// ```
817
  /// {@end-tool}
818 819 820
  ///
  /// Both guards ensure that Dart's tree shaker can remove the code for the
  /// service extension in release builds.
821
  /// {@endtemplate}
822
  @protected
823
  void registerServiceExtension({
824 825
    required String name,
    required ServiceExtensionCallback callback,
826 827 828 829 830 831
  }) {
    assert(name != null);
    assert(callback != null);
    final String methodName = 'ext.flutter.$name';
    developer.registerExtension(methodName, (String method, Map<String, String> parameters) async {
      assert(method == methodName);
832
      assert(() {
833
        if (debugInstrumentationEnabled) {
834
          debugPrint('service extension method received: $method($parameters)');
835
        }
836 837
        return true;
      }());
838 839 840 841 842 843 844 845 846 847 848

      // 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.
849
      await debugInstrumentAction<void>('Wait for outer event loop', () {
850
        return Future<void>.delayed(Duration.zero);
851
      });
852

853
      late Map<String, dynamic> result;
854 855 856
      try {
        result = await callback(parameters);
      } catch (exception, stack) {
857
        FlutterError.reportError(FlutterErrorDetails(
858 859
          exception: exception,
          stack: stack,
860
          context: ErrorDescription('during a service extension callback for "$method"'),
861
        ));
862
        return developer.ServiceExtensionResponse.error(
863
          developer.ServiceExtensionResponse.extensionError,
864
          json.encode(<String, String>{
865 866
            'exception': exception.toString(),
            'stack': stack.toString(),
867
            'method': method,
868
          }),
869 870
        );
      }
871 872 873
      result['type'] = '_extensionType';
      result['method'] = method;
      return developer.ServiceExtensionResponse.result(json.encode(result));
874 875 876
    });
  }

877
  @override
878
  String toString() => '<${objectRuntimeType(this, 'BindingBase')}>';
879
}
880 881

/// Terminate the Flutter application.
882
Future<void> _exitApplication() async {
883 884
  exit(0);
}
885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904

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