binding.dart 10.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 6 7
import 'dart:async';
import 'dart:convert' show JSON;
import 'dart:developer' as developer;
8
import 'dart:io' show exit;
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

import 'package:meta/meta.dart';

import 'assertions.dart';
import 'basic_types.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 Future<Map<String, dynamic>> ServiceExtensionCallback(Map<String, String> parameters);

24 25 26 27
/// Base class for mixins that provide singleton services (also known as
/// "bindings").
///
/// To use this class in a mixin, inherit from it and implement
28 29 30
/// [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).
31 32 33
///
/// The top-most layer used to write the application will have a
/// concrete class that inherits from BindingBase and uses all the
34
/// various BindingBase mixins (such as [ServicesBinding]). For example, the
35
/// Widgets library in flutter introduces a binding called
36
/// [WidgetsFlutterBinding]. The relevant library defines how to create
37
/// the binding. It could be implied (for example,
38
/// [WidgetsFlutterBinding] is automatically started from [runApp]), or
39 40 41
/// the application might be required to explicitly call the
/// constructor.
abstract class BindingBase {
42 43 44 45 46 47
  /// 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.
48
  BindingBase() {
49 50
    developer.Timeline.startSync('Framework initialization');

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

    assert(!_debugServiceExtensionsRegistered);
    initServiceExtensions();
    assert(_debugServiceExtensionsRegistered);
58

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

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

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

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

82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
  /// 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>
102 103
  @protected
  @mustCallSuper
104 105 106 107 108 109
  void initServiceExtensions() {
    assert(!_debugServiceExtensionsRegistered);
    registerSignalServiceExtension(
      name: 'reassemble',
      callback: reassembleApplication
    );
110 111 112 113
    registerSignalServiceExtension(
      name: 'exit',
      callback: _exitApplication
    );
114 115 116 117
    registerSignalServiceExtension(
      name: 'frameworkPresent',
      callback: () => null
    );
118 119 120 121 122 123 124 125 126 127 128 129
    assert(() { _debugServiceExtensionsRegistered = true; return true; });
  }

  /// Called when the ext.flutter.reassemble signal is sent by
  /// development tools.
  ///
  /// This is used by development tools when the application code has
  /// changed, to cause the application to pick up any changed code.
  /// 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.
130 131 132 133
  @mustCallSuper
  void reassembleApplication() {
    FlutterError.resetErrorCount();
  }
134

135

136 137 138 139
  /// Registers a service extension method with the given name (full
  /// name "ext.flutter.name"), which takes no arguments and returns
  /// no value.
  ///
140
  /// Calls the `callback` callback when the service extension is called.
141
  @protected
142
  void registerSignalServiceExtension({
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
    @required String name,
    @required VoidCallback callback
  }) {
    assert(name != null);
    assert(callback != null);
    registerServiceExtension(
      name: name,
      callback: (Map<String, String> parameters) async {
        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.)
  ///
164 165
  /// Calls the `getter` callback to obtain the value when
  /// responding to the service extension method being called.
166
  ///
167 168
  /// Calls the `setter` callback with the new value when the
  /// service extension method is called with a new value.
169
  @protected
170
  void registerBoolServiceExtension({
171 172 173 174 175 176 177 178
    String name,
    @required ValueGetter<bool> getter,
    @required ValueSetter<bool> setter
  }) {
    assert(name != null);
    assert(getter != null);
    assert(setter != null);
    registerServiceExtension(
179
      name: name,
180 181 182 183 184 185 186 187 188 189 190 191 192 193
      callback: (Map<String, String> parameters) async {
        if (parameters.containsKey('enabled'))
          setter(parameters['enabled'] == 'true');
        return <String, dynamic>{ 'enabled': getter() };
      }
    );
  }

  /// 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.)
  ///
194 195
  /// Calls the `getter` callback to obtain the value when
  /// responding to the service extension method being called.
196
  ///
197 198
  /// Calls the `setter` callback with the new value when the
  /// service extension method is called with a new value.
199
  @protected
200
  void registerNumericServiceExtension({
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
    @required String name,
    @required ValueGetter<double> getter,
    @required ValueSetter<double> setter
  }) {
    assert(name != null);
    assert(getter != null);
    assert(setter != null);
    registerServiceExtension(
      name: name,
      callback: (Map<String, String> parameters) async {
        if (parameters.containsKey(name))
          setter(double.parse(parameters[name]));
        return <String, dynamic>{ name: getter() };
      }
    );
  }

218 219 220 221 222 223 224 225 226 227
  /// 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.
228
  @protected
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
  void registerStringServiceExtension({
    @required String name,
    @required ValueGetter<String> getter,
    @required ValueSetter<String> setter
  }) {
    assert(name != null);
    assert(getter != null);
    assert(setter != null);
    registerServiceExtension(
      name: name,
      callback: (Map<String, String> parameters) async {
        if (parameters.containsKey('value'))
          setter(parameters['value']);
        return <String, dynamic>{ 'value': getter() };
      }
    );
  }

247
  /// Registers a service extension method with the given name (full
248
  /// name "ext.flutter.name"). The given callback is called when the
249 250 251 252 253 254 255 256
  /// 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], 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.
257
  @protected
258
  void registerServiceExtension({
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
    @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;
      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 new developer.ServiceExtensionResponse.result(JSON.encode(result));
      } 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,
288
          JSON.encode(<String, dynamic>{
289 290 291 292 293 294 295 296 297
            'exception': caughtException.toString(),
            'stack': caughtStack.toString(),
            'method': method
          })
        );
      }
    });
  }

298 299 300
  @override
  String toString() => '<$runtimeType>';
}
301 302 303 304 305

/// Terminate the Flutter application.
void _exitApplication() {
  exit(0);
}