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