binding.dart 14.7 KB
Newer Older
1 2 3 4
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:async';
6
import 'dart:convert' show json;
7
import 'dart:developer' as developer;
8
import 'dart:io' show exit;
9

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

import 'assertions.dart';
import 'basic_types.dart';
14
import 'platform.dart';
15 16 17 18 19 20 21 22

/// 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.
23
typedef Future<Map<String, dynamic>> ServiceExtensionCallback(Map<String, String> parameters);
24

25 26 27 28
/// Base class for mixins that provide singleton services (also known as
/// "bindings").
///
/// To use this class in a mixin, inherit from it and implement
29 30 31
/// [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).
32
///
33 34 35 36 37 38 39
/// 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.
40
abstract class BindingBase {
41 42 43 44 45 46
  /// 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.
47
  BindingBase() {
48 49
    developer.Timeline.startSync('Framework initialization');

50 51 52
    assert(!_debugInitialized);
    initInstances();
    assert(_debugInitialized);
53 54 55 56

    assert(!_debugServiceExtensionsRegistered);
    initServiceExtensions();
    assert(_debugServiceExtensionsRegistered);
57

58
    developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
59

60
    developer.Timeline.finishSync();
61 62 63
  }

  static bool _debugInitialized = false;
64
  static bool _debugServiceExtensionsRegistered = false;
65 66 67 68 69 70 71 72 73

  /// 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()`.
74 75
  @protected
  @mustCallSuper
76
  void initInstances() {
77
    assert(!_debugInitialized);
78
    assert(() { _debugInitialized = true; return true; }());
79 80
  }

81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
  /// 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.
  ///
  /// Service extensions are only exposed when the observatory is
  /// included in the build, which should only happen in checked mode
  /// and in profile mode.
  ///
  /// See also:
  ///
  ///  * <https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#rpcs-requests-and-responses>
101 102
  @protected
  @mustCallSuper
103 104 105 106
  void initServiceExtensions() {
    assert(!_debugServiceExtensionsRegistered);
    registerSignalServiceExtension(
      name: 'reassemble',
107
      callback: reassembleApplication,
108
    );
109 110
    registerSignalServiceExtension(
      name: 'exit',
111
      callback: _exitApplication,
112
    );
113 114
    registerSignalServiceExtension(
      name: 'frameworkPresent',
115
      callback: () => new Future<Null>.value(),
116
    );
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
    assert(() {
      registerServiceExtension(
        name: 'platformOverride',
        callback: (Map<String, String> parameters) async {
          if (parameters.containsKey('value')) {
            switch (parameters['value']) {
              case 'android':
                debugDefaultTargetPlatformOverride = TargetPlatform.android;
                break;
              case 'iOS':
                debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
                break;
              case 'fuchsia':
                debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
                break;
              case 'default':
              default:
                debugDefaultTargetPlatformOverride = null;
            }
            await reassembleApplication();
          }
138
          return <String, dynamic>{
139 140 141 142 143 144 145
            'value': defaultTargetPlatform
                     .toString()
                     .substring('$TargetPlatform.'.length),
          };
        }
      );
      return true;
146 147
    }());
    assert(() { _debugServiceExtensionsRegistered = true; return true; }());
148 149
  }

150 151 152 153 154 155 156 157 158 159 160 161 162 163
  /// 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
164 165 166
  /// 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).
167 168 169 170
  ///
  /// The [Future] returned by the `callback` argument is returned by [lockEvents].
  @protected
  Future<Null> lockEvents(Future<Null> callback()) {
171 172
    developer.Timeline.startSync('Lock events');

173 174 175 176 177 178
    assert(callback != null);
    _lockCount += 1;
    final Future<Null> future = callback();
    assert(future != null, 'The lockEvents() callback returned null; it should return a Future<Null> that completes when the lock is to expire.');
    future.whenComplete(() {
      _lockCount -= 1;
179 180
      if (!locked) {
        developer.Timeline.finishSync();
181
        unlocked();
182
      }
183 184 185 186 187 188 189 190
    });
    return future;
  }

  /// Called by [lockEvents] when events get unlocked.
  ///
  /// This should flush any events that were queued while [locked] was true.
  @protected
191
  @mustCallSuper
192 193 194 195
  void unlocked() {
    assert(!locked);
  }

196
  /// Cause the entire application to redraw, e.g. after a hot reload.
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
  ///
  /// 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.
214
  Future<Null> reassembleApplication() {
215 216 217 218
    return lockEvents(performReassemble);
  }

  /// This method is called by [reassembleApplication] to actually cause the
219
  /// application to reassemble, e.g. after a hot reload.
220 221 222 223 224 225 226 227 228 229 230
  ///
  /// Bindings are expected to use this method to reregister 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<Null> performReassemble() {
231
    FlutterError.resetErrorCount();
232
    return new Future<Null>.value();
233
  }
234 235 236 237 238

  /// Registers a service extension method with the given name (full
  /// name "ext.flutter.name"), which takes no arguments and returns
  /// no value.
  ///
239
  /// Calls the `callback` callback when the service extension is called.
240
  @protected
241
  void registerSignalServiceExtension({
242
    @required String name,
243
    @required AsyncCallback callback
244 245 246 247 248 249
  }) {
    assert(name != null);
    assert(callback != null);
    registerServiceExtension(
      name: name,
      callback: (Map<String, String> parameters) async {
250
        await callback();
251
        return <String, dynamic>{};
252 253 254 255 256 257 258 259 260 261 262
      }
    );
  }

  /// 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.)
  ///
263 264
  /// Calls the `getter` callback to obtain the value when
  /// responding to the service extension method being called.
265
  ///
266 267
  /// Calls the `setter` callback with the new value when the
  /// service extension method is called with a new value.
268
  @protected
269
  void registerBoolServiceExtension({
270
    @required String name,
271 272
    @required AsyncValueGetter<bool> getter,
    @required AsyncValueSetter<bool> setter
273 274 275 276 277
  }) {
    assert(name != null);
    assert(getter != null);
    assert(setter != null);
    registerServiceExtension(
278
      name: name,
279 280
      callback: (Map<String, String> parameters) async {
        if (parameters.containsKey('enabled'))
281
          await setter(parameters['enabled'] == 'true');
282
        return <String, dynamic>{ 'enabled': await getter() ? 'true' : 'false' };
283 284 285 286 287 288 289 290 291 292
      }
    );
  }

  /// 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.)
  ///
293 294
  /// Calls the `getter` callback to obtain the value when
  /// responding to the service extension method being called.
295
  ///
296 297
  /// Calls the `setter` callback with the new value when the
  /// service extension method is called with a new value.
298
  @protected
299
  void registerNumericServiceExtension({
300
    @required String name,
301 302
    @required AsyncValueGetter<double> getter,
    @required AsyncValueSetter<double> setter
303 304 305 306 307 308 309 310
  }) {
    assert(name != null);
    assert(getter != null);
    assert(setter != null);
    registerServiceExtension(
      name: name,
      callback: (Map<String, String> parameters) async {
        if (parameters.containsKey(name))
311
          await setter(double.parse(parameters[name]));
312
        return <String, dynamic>{ name: (await getter()).toString() };
313 314 315 316
      }
    );
  }

317 318 319 320 321 322 323 324 325 326
  /// 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.
327
  @protected
328 329
  void registerStringServiceExtension({
    @required String name,
330 331
    @required AsyncValueGetter<String> getter,
    @required AsyncValueSetter<String> setter
332 333 334 335 336 337 338 339
  }) {
    assert(name != null);
    assert(getter != null);
    assert(setter != null);
    registerServiceExtension(
      name: name,
      callback: (Map<String, String> parameters) async {
        if (parameters.containsKey('value'))
340
          await setter(parameters['value']);
341
        return <String, dynamic>{ 'value': await getter() };
342 343 344 345
      }
    );
  }

346
  /// Registers a service extension method with the given name (full
347
  /// name "ext.flutter.name"). The given callback is called when the
348 349 350
  /// 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
351
  /// JSON using `json.encode()` (see [JsonEncoder]), or fails. In case of failure, the
352 353 354 355
  /// failure is reported to the remote caller and is dumped to the
  /// logs.
  ///
  /// The returned map will be mutated.
356
  @protected
357
  void registerServiceExtension({
358 359 360 361 362 363 364 365 366 367
    @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);
      dynamic caughtException;
      StackTrace caughtStack;
368
      Map<String, dynamic> result;
369 370 371 372 373 374 375 376 377
      try {
        result = await callback(parameters);
      } catch (exception, stack) {
        caughtException = exception;
        caughtStack = stack;
      }
      if (caughtException == null) {
        result['type'] = '_extensionType';
        result['method'] = method;
378
        return new developer.ServiceExtensionResponse.result(json.encode(result));
379 380 381 382 383 384 385 386
      } else {
        FlutterError.reportError(new FlutterErrorDetails(
          exception: caughtException,
          stack: caughtStack,
          context: 'during a service extension callback for "$method"'
        ));
        return new developer.ServiceExtensionResponse.error(
          developer.ServiceExtensionResponse.extensionError,
387
          json.encode(<String, String>{
388 389
            'exception': caughtException.toString(),
            'stack': caughtStack.toString(),
390
            'method': method,
391 392 393 394 395 396
          })
        );
      }
    });
  }

397 398 399
  @override
  String toString() => '<$runtimeType>';
}
400 401

/// Terminate the Flutter application.
402
Future<Null> _exitApplication() async {
403 404
  exit(0);
}