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