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 | import 'dart:async'; |
6 | import 'dart:convert' show json; |
7 | import 'dart:developer' as developer; |
8 | import 'dart:io' show exit; |
9 | import 'dart:ui' as ui show Brightness, PlatformDispatcher, SingletonFlutterWindow, window; // ignore: deprecated_member_use |
10 | |
11 | // Before adding any more dart:ui imports, please read the README. |
12 | |
13 | import 'package:meta/meta.dart' ; |
14 | |
15 | import 'assertions.dart'; |
16 | import 'basic_types.dart'; |
17 | import 'constants.dart'; |
18 | import 'debug.dart'; |
19 | import 'object.dart'; |
20 | import 'platform.dart'; |
21 | import 'print.dart'; |
22 | import 'service_extensions.dart'; |
23 | import 'timeline.dart'; |
24 | |
25 | export 'dart:ui' show PlatformDispatcher, SingletonFlutterWindow, clampDouble; // ignore: deprecated_member_use |
26 | |
27 | export '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. |
39 | typedef 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} |
137 | abstract 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. |
971 | Future<void> _exitApplication() async { |
972 | exit(0); |
973 | } |
974 | |