1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert' show json;
import 'dart:developer' as developer;
import 'dart:io' show exit;
import 'dart:ui' as ui show Window, window, Brightness;
// Before adding any more dart:ui imports, please read the README.
import 'package:meta/meta.dart';
import 'assertions.dart';
import 'basic_types.dart';
import 'constants.dart';
import 'debug.dart';
import 'object.dart';
import 'platform.dart';
import 'print.dart';
/// Signature for service extensions.
///
/// The returned map must not contain the keys "type" or "method", as
/// they will be replaced before the value is sent to the client. The
/// "type" key will be set to the string `_extensionType` to indicate
/// that this is a return value from a service extension, and the
/// "method" key will be set to the full name of the method.
typedef ServiceExtensionCallback = Future<Map<String, dynamic>> Function(Map<String, String> parameters);
/// Base class for mixins that provide singleton services (also known as
/// "bindings").
///
/// To use this class in an `on` clause of a mixin, inherit from it and implement
/// [initInstances()]. The mixin is guaranteed to only be constructed once in
/// the lifetime of the app (more precisely, it will assert if constructed twice
/// in checked mode).
///
/// The top-most layer used to write the application will have a concrete class
/// that inherits from [BindingBase] and uses all the various [BindingBase]
/// mixins (such as [ServicesBinding]). For example, the Widgets library in
/// Flutter introduces a binding called [WidgetsFlutterBinding]. The relevant
/// library defines how to create the binding. It could be implied (for example,
/// [WidgetsFlutterBinding] is automatically started from [runApp]), or the
/// application might be required to explicitly call the constructor.
abstract class BindingBase {
/// Default abstract constructor for bindings.
///
/// First calls [initInstances] to have bindings initialize their
/// instance pointers and other state, then calls
/// [initServiceExtensions] to have bindings initialize their
/// observatory service extensions, if any.
BindingBase() {
developer.Timeline.startSync('Framework initialization');
assert(!_debugInitialized);
initInstances();
assert(_debugInitialized);
assert(!_debugServiceExtensionsRegistered);
initServiceExtensions();
assert(_debugServiceExtensionsRegistered);
developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
developer.Timeline.finishSync();
}
static bool _debugInitialized = false;
static bool _debugServiceExtensionsRegistered = false;
/// The window to which this binding is bound.
///
/// A number of additional bindings are defined as extensions of [BindingBase],
/// e.g., [ServicesBinding], [RendererBinding], and [WidgetsBinding]. Each of
/// these bindings define behaviors that interact with a [ui.Window], e.g.,
/// [ServicesBinding] registers a [ui.Window.onPlatformMessage] handler, and
/// [RendererBinding] registers [ui.Window.onMetricsChanged],
/// [ui.Window.onTextScaleFactorChanged], [ui.Window.onSemanticsEnabledChanged],
/// and [ui.Window.onSemanticsAction] handlers.
///
/// Each of these other bindings could individually access a [Window] statically,
/// but that would preclude the ability to test these behaviors with a fake
/// window for verification purposes. Therefore, [BindingBase] exposes this
/// [Window] for use by other bindings. A subclass of [BindingBase], such as
/// [TestWidgetsFlutterBinding], can override this accessor to return a
/// different [Window] implementation, such as a [TestWindow].
ui.Window get window => ui.window;
/// The initialization method. Subclasses override this method to hook into
/// the platform and otherwise configure their services. Subclasses must call
/// "super.initInstances()".
///
/// By convention, if the service is to be provided as a singleton, it should
/// be exposed as `MixinClassName.instance`, a static getter that returns
/// `MixinClassName._instance`, a static field that is set by
/// `initInstances()`.
@protected
@mustCallSuper
void initInstances() {
assert(!_debugInitialized);
assert(() {
_debugInitialized = true;
return true;
}());
}
/// Called when the binding is initialized, to register service
/// extensions.
///
/// Bindings that want to expose service extensions should overload
/// this method to register them using calls to
/// [registerSignalServiceExtension],
/// [registerBoolServiceExtension],
/// [registerNumericServiceExtension], and
/// [registerServiceExtension] (in increasing order of complexity).
///
/// Implementations of this method must call their superclass
/// implementation.
///
/// {@macro flutter.foundation.bindingBase.registerServiceExtension}
///
/// See also:
///
/// * <https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#rpcs-requests-and-responses>
@protected
@mustCallSuper
void initServiceExtensions() {
assert(!_debugServiceExtensionsRegistered);
assert(() {
registerSignalServiceExtension(
name: 'reassemble',
callback: reassembleApplication,
);
return true;
}());
if (!kReleaseMode && !kIsWeb) {
registerSignalServiceExtension(
name: 'exit',
callback: _exitApplication,
);
}
assert(() {
const String platformOverrideExtensionName = 'platformOverride';
registerServiceExtension(
name: platformOverrideExtensionName,
callback: (Map<String, String> parameters) async {
if (parameters.containsKey('value')) {
switch (parameters['value']) {
case 'android':
debugDefaultTargetPlatformOverride = TargetPlatform.android;
break;
case 'fuchsia':
debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
break;
case 'iOS':
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
break;
case 'linux':
debugDefaultTargetPlatformOverride = TargetPlatform.linux;
break;
case 'macOS':
debugDefaultTargetPlatformOverride = TargetPlatform.macOS;
break;
case 'windows':
debugDefaultTargetPlatformOverride = TargetPlatform.windows;
break;
case 'default':
default:
debugDefaultTargetPlatformOverride = null;
}
_postExtensionStateChangedEvent(
platformOverrideExtensionName,
defaultTargetPlatform.toString().substring('$TargetPlatform.'.length),
);
await reassembleApplication();
}
return <String, dynamic>{
'value': defaultTargetPlatform
.toString()
.substring('$TargetPlatform.'.length),
};
},
);
const String brightnessOverrideExtensionName = 'brightnessOverride';
registerServiceExtension(
name: brightnessOverrideExtensionName,
callback: (Map<String, String> parameters) async {
if (parameters.containsKey('value')) {
switch (parameters['value']) {
case 'Brightness.light':
debugBrightnessOverride = ui.Brightness.light;
break;
case 'Brightness.dark':
debugBrightnessOverride = ui.Brightness.dark;
break;
default:
debugBrightnessOverride = null;
}
_postExtensionStateChangedEvent(
brightnessOverrideExtensionName,
(debugBrightnessOverride ?? window.platformBrightness).toString(),
);
await reassembleApplication();
}
return <String, dynamic>{
'value': (debugBrightnessOverride ?? window.platformBrightness).toString(),
};
},
);
return true;
}());
assert(() {
_debugServiceExtensionsRegistered = true;
return true;
}());
}
/// Whether [lockEvents] is currently locking events.
///
/// Binding subclasses that fire events should check this first, and if it is
/// set, queue events instead of firing them.
///
/// Events should be flushed when [unlocked] is called.
@protected
bool get locked => _lockCount > 0;
int _lockCount = 0;
/// Locks the dispatching of asynchronous events and callbacks until the
/// callback's future completes.
///
/// This causes input lag and should therefore be avoided when possible. It is
/// primarily intended for use during non-user-interactive time such as to
/// allow [reassembleApplication] to block input while it walks the tree
/// (which it partially does asynchronously).
///
/// The [Future] returned by the `callback` argument is returned by [lockEvents].
@protected
Future<void> lockEvents(Future<void> callback()) {
developer.Timeline.startSync('Lock events');
assert(callback != null);
_lockCount += 1;
final Future<void> future = callback();
assert(future != null, 'The lockEvents() callback returned null; it should return a Future<void> that completes when the lock is to expire.');
future.whenComplete(() {
_lockCount -= 1;
if (!locked) {
developer.Timeline.finishSync();
unlocked();
}
});
return future;
}
/// Called by [lockEvents] when events get unlocked.
///
/// This should flush any events that were queued while [locked] was true.
@protected
@mustCallSuper
void unlocked() {
assert(!locked);
}
/// Cause the entire application to redraw, e.g. after a hot reload.
///
/// This is used by development tools when the application code has changed,
/// to cause the application to pick up any changed code. It can be triggered
/// manually by sending the `ext.flutter.reassemble` service extension signal.
///
/// This method is very computationally expensive and should not be used in
/// production code. There is never a valid reason to cause the entire
/// application to repaint in production. All aspects of the Flutter framework
/// know how to redraw when necessary. It is only necessary in development
/// when the code is literally changed on the fly (e.g. in hot reload) or when
/// debug flags are being toggled.
///
/// While this method runs, events are locked (e.g. pointer events are not
/// dispatched).
///
/// Subclasses (binding classes) should override [performReassemble] to react
/// to this method being called. This method itself should not be overridden.
Future<void> reassembleApplication() {
return lockEvents(performReassemble);
}
/// This method is called by [reassembleApplication] to actually cause the
/// application to reassemble, e.g. after a hot reload.
///
/// Bindings are expected to use this method to re-register anything that uses
/// closures, so that they do not keep pointing to old code, and to flush any
/// caches of previously computed values, in case the new code would compute
/// them differently. For example, the rendering layer triggers the entire
/// application to repaint when this is called.
///
/// Do not call this method directly. Instead, use [reassembleApplication].
@mustCallSuper
@protected
Future<void> performReassemble() {
FlutterError.resetErrorCount();
return Future<void>.value();
}
/// Registers a service extension method with the given name (full
/// name "ext.flutter.name"), which takes no arguments and returns
/// no value.
///
/// Calls the `callback` callback when the service extension is called.
///
/// {@macro flutter.foundation.bindingBase.registerServiceExtension}
@protected
void registerSignalServiceExtension({
required String name,
required AsyncCallback callback,
}) {
assert(name != null);
assert(callback != null);
registerServiceExtension(
name: name,
callback: (Map<String, String> parameters) async {
await callback();
return <String, dynamic>{};
},
);
}
/// Registers a service extension method with the given name (full
/// name "ext.flutter.name"), which takes a single argument
/// "enabled" which can have the value "true" or the value "false"
/// or can be omitted to read the current value. (Any value other
/// than "true" is considered equivalent to "false". Other arguments
/// are ignored.)
///
/// Calls the `getter` callback to obtain the value when
/// responding to the service extension method being called.
///
/// Calls the `setter` callback with the new value when the
/// service extension method is called with a new value.
///
/// {@macro flutter.foundation.bindingBase.registerServiceExtension}
@protected
void registerBoolServiceExtension({
required String name,
required AsyncValueGetter<bool> getter,
required AsyncValueSetter<bool> setter,
}) {
assert(name != null);
assert(getter != null);
assert(setter != null);
registerServiceExtension(
name: name,
callback: (Map<String, String> parameters) async {
if (parameters.containsKey('enabled')) {
await setter(parameters['enabled'] == 'true');
_postExtensionStateChangedEvent(name, await getter() ? 'true' : 'false');
}
return <String, dynamic>{'enabled': await getter() ? 'true' : 'false'};
},
);
}
/// Registers a service extension method with the given name (full
/// name "ext.flutter.name"), which takes a single argument with the
/// same name as the method which, if present, must have a value
/// that can be parsed by [double.parse], and can be omitted to read
/// the current value. (Other arguments are ignored.)
///
/// Calls the `getter` callback to obtain the value when
/// responding to the service extension method being called.
///
/// Calls the `setter` callback with the new value when the
/// service extension method is called with a new value.
///
/// {@macro flutter.foundation.bindingBase.registerServiceExtension}
@protected
void registerNumericServiceExtension({
required String name,
required AsyncValueGetter<double> getter,
required AsyncValueSetter<double> setter,
}) {
assert(name != null);
assert(getter != null);
assert(setter != null);
registerServiceExtension(
name: name,
callback: (Map<String, String> parameters) async {
if (parameters.containsKey(name)) {
await setter(double.parse(parameters[name]!));
_postExtensionStateChangedEvent(name, (await getter()).toString());
}
return <String, dynamic>{name: (await getter()).toString()};
},
);
}
/// Sends an event when a service extension's state is changed.
///
/// Clients should listen for this event to stay aware of the current service
/// extension state. Any service extension that manages a state should call
/// this method on state change.
///
/// `value` reflects the newly updated service extension value.
///
/// This will be called automatically for service extensions registered via
/// [registerBoolServiceExtension], [registerNumericServiceExtension], or
/// [registerStringServiceExtension].
void _postExtensionStateChangedEvent(String name, dynamic value) {
postEvent(
'Flutter.ServiceExtensionStateChanged',
<String, dynamic>{
'extension': 'ext.flutter.$name',
'value': value,
},
);
}
/// All events dispatched by a [BindingBase] use this method instead of
/// calling [developer.postEvent] directly so that tests for [BindingBase]
/// can track which events were dispatched by overriding this method.
@protected
void postEvent(String eventKind, Map<String, dynamic> eventData) {
developer.postEvent(eventKind, eventData);
}
/// Registers a service extension method with the given name (full name
/// "ext.flutter.name"), which optionally takes a single argument with the
/// name "value". If the argument is omitted, the value is to be read,
/// otherwise it is to be set. Returns the current value.
///
/// Calls the `getter` callback to obtain the value when
/// responding to the service extension method being called.
///
/// Calls the `setter` callback with the new value when the
/// service extension method is called with a new value.
///
/// {@macro flutter.foundation.bindingBase.registerServiceExtension}
@protected
void registerStringServiceExtension({
required String name,
required AsyncValueGetter<String> getter,
required AsyncValueSetter<String> setter,
}) {
assert(name != null);
assert(getter != null);
assert(setter != null);
registerServiceExtension(
name: name,
callback: (Map<String, String> parameters) async {
if (parameters.containsKey('value')) {
await setter(parameters['value']!);
_postExtensionStateChangedEvent(name, await getter());
}
return <String, dynamic>{'value': await getter()};
},
);
}
/// Registers a service extension method with the given name (full name
/// "ext.flutter.name").
///
/// The given callback is called when the extension method is called. The
/// callback must return a [Future] that either eventually completes to a
/// return value in the form of a name/value map where the values can all be
/// converted to JSON using `json.encode()` (see [JsonEncoder]), or fails. In
/// case of failure, the failure is reported to the remote caller and is
/// dumped to the logs.
///
/// The returned map will be mutated.
///
/// {@template flutter.foundation.bindingBase.registerServiceExtension}
/// A registered service extension can only be activated if the vm-service
/// is included in the build, which only happens in debug and profile mode.
/// Although a service extension cannot be used in release mode its code may
/// still be included in the Dart snapshot and blow up binary size if it is
/// not wrapped in a guard that allows the tree shaker to remove it (see
/// sample code below).
///
/// {@tool snippet}
/// The following code registers a service extension that is only included in
/// debug builds.
///
/// ```dart
/// void myRegistrationFunction() {
/// assert(() {
/// // Register your service extension here.
/// return true;
/// }());
/// }
/// ```
/// {@end-tool}
///
/// {@tool snippet}
/// A service extension registered with the following code snippet is
/// available in debug and profile mode.
///
/// ```dart
/// void myRegistrationFunction() {
/// // kReleaseMode is defined in the 'flutter/foundation.dart' package.
/// if (!kReleaseMode) {
/// // Register your service extension here.
/// }
/// }
/// ```
/// {@end-tool}
///
/// Both guards ensure that Dart's tree shaker can remove the code for the
/// service extension in release builds.
/// {@endtemplate}
@protected
void registerServiceExtension({
required String name,
required ServiceExtensionCallback callback,
}) {
assert(name != null);
assert(callback != null);
final String methodName = 'ext.flutter.$name';
developer.registerExtension(methodName, (String method, Map<String, String> parameters) async {
assert(method == methodName);
assert(() {
if (debugInstrumentationEnabled)
debugPrint('service extension method received: $method($parameters)');
return true;
}());
// VM service extensions are handled as "out of band" messages by the VM,
// which means they are handled at various times, generally ASAP.
// Notably, this includes being handled in the middle of microtask loops.
// While this makes sense for some service extensions (e.g. "dump current
// stack trace", which explicitly doesn't want to wait for a loop to
// complete), Flutter extensions need not be handled with such high
// priority. Further, handling them with such high priority exposes us to
// the possibility that they're handled in the middle of a frame, which
// breaks many assertions. As such, we ensure they we run the callbacks
// on the outer event loop here.
await debugInstrumentAction<void>('Wait for outer event loop', () {
return Future<void>.delayed(Duration.zero);
});
Object? caughtException;
StackTrace? caughtStack;
late Map<String, dynamic> result;
try {
result = await callback(parameters);
} catch (exception, stack) {
caughtException = exception;
caughtStack = stack;
}
if (caughtException == null) {
result['type'] = '_extensionType';
result['method'] = method;
return developer.ServiceExtensionResponse.result(json.encode(result));
} else {
FlutterError.reportError(FlutterErrorDetails(
exception: caughtException,
stack: caughtStack,
context: ErrorDescription('during a service extension callback for "$method"'),
));
return developer.ServiceExtensionResponse.error(
developer.ServiceExtensionResponse.extensionError,
json.encode(<String, String>{
'exception': caughtException.toString(),
'stack': caughtStack.toString(),
'method': method,
}),
);
}
});
}
@override
String toString() => '<${objectRuntimeType(this, 'BindingBase')}>';
}
/// Terminate the Flutter application.
Future<void> _exitApplication() async {
exit(0);
}