diff --git a/packages/flutter/lib/src/foundation/binding.dart b/packages/flutter/lib/src/foundation/binding.dart index abdf6b0b55..c6f1f083c9 100644 --- a/packages/flutter/lib/src/foundation/binding.dart +++ b/packages/flutter/lib/src/foundation/binding.dart @@ -93,9 +93,7 @@ abstract class BindingBase { /// 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. + /// {@macro flutter.foundation.bindingBase.registerServiceExtension} /// /// See also: /// @@ -104,18 +102,23 @@ abstract class BindingBase { @mustCallSuper void initServiceExtensions() { assert(!_debugServiceExtensionsRegistered); - registerSignalServiceExtension( - name: 'reassemble', - callback: reassembleApplication, - ); - registerSignalServiceExtension( - name: 'exit', - callback: _exitApplication, - ); - registerSignalServiceExtension( - name: 'frameworkPresent', - callback: () => Future.value(), - ); + + assert(() { + registerSignalServiceExtension( + name: 'reassemble', + callback: reassembleApplication, + ); + return true; + }()); + + const bool isReleaseMode = bool.fromEnvironment('dart.vm.product'); + if (!isReleaseMode) { + registerSignalServiceExtension( + name: 'exit', + callback: _exitApplication, + ); + } + assert(() { registerServiceExtension( name: 'platformOverride', @@ -239,6 +242,8 @@ abstract class BindingBase { /// no value. /// /// Calls the `callback` callback when the service extension is called. + /// + /// {@macro flutter.foundation.bindingBase.registerServiceExtension} @protected void registerSignalServiceExtension({ @required String name, @@ -267,6 +272,8 @@ abstract class BindingBase { /// /// 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, @@ -297,6 +304,8 @@ abstract class BindingBase { /// /// 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, @@ -326,6 +335,8 @@ abstract class BindingBase { /// /// 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, @@ -345,16 +356,51 @@ abstract class BindingBase { ); } - /// 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. + /// 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). + /// + /// ## Sample Code + /// + /// The following code registers a service extension that is only included in + /// debug builds: + /// + /// ```dart + /// assert(() { + /// // Register your service extension here. + /// return true; + /// }()); + /// + /// ``` + /// + /// A service extension registered with the following code snippet is + /// available in debug and profile mode: + /// + /// ```dart + /// if (!const bool.fromEnvironment('dart.vm.product')) { + // // Register your service extension here. + // } + /// ``` + /// + /// 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, diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart index f3ab2701d8..41e823deaf 100644 --- a/packages/flutter/lib/src/rendering/binding.dart +++ b/packages/flutter/lib/src/rendering/binding.dart @@ -51,7 +51,7 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Semanti super.initServiceExtensions(); assert(() { - // these service extensions only work in checked mode + // these service extensions only work in debug mode registerBoolServiceExtension( name: 'debugPaint', getter: () async => debugPaintSizeEnabled, @@ -60,17 +60,17 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Semanti return Future.value(); debugPaintSizeEnabled = value; return _forceRepaint(); - } + }, ); registerBoolServiceExtension( - name: 'debugPaintBaselinesEnabled', - getter: () async => debugPaintBaselinesEnabled, - setter: (bool value) { + name: 'debugPaintBaselinesEnabled', + getter: () async => debugPaintBaselinesEnabled, + setter: (bool value) { if (debugPaintBaselinesEnabled == value) return Future.value(); debugPaintBaselinesEnabled = value; return _forceRepaint(); - } + }, ); registerBoolServiceExtension( name: 'repaintRainbow', @@ -83,28 +83,43 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Semanti return Future.value(); }, ); + registerSignalServiceExtension( + name: 'debugDumpLayerTree', + callback: () { + debugDumpLayerTree(); + return debugPrintDone; + }, + ); return true; }()); - registerSignalServiceExtension( - name: 'debugDumpRenderTree', - callback: () { debugDumpRenderTree(); return debugPrintDone; } - ); + const bool isReleaseMode = bool.fromEnvironment('dart.vm.product'); + if (!isReleaseMode) { + // these service extensions work in debug or profile mode + registerSignalServiceExtension( + name: 'debugDumpRenderTree', + callback: () { + debugDumpRenderTree(); + return debugPrintDone; + }, + ); - registerSignalServiceExtension( - name: 'debugDumpLayerTree', - callback: () { debugDumpLayerTree(); return debugPrintDone; } - ); + registerSignalServiceExtension( + name: 'debugDumpSemanticsTreeInTraversalOrder', + callback: () { + debugDumpSemanticsTree(DebugSemanticsDumpOrder.traversalOrder); + return debugPrintDone; + }, + ); - registerSignalServiceExtension( - name: 'debugDumpSemanticsTreeInTraversalOrder', - callback: () { debugDumpSemanticsTree(DebugSemanticsDumpOrder.traversalOrder); return debugPrintDone; } - ); - - registerSignalServiceExtension( - name: 'debugDumpSemanticsTreeInInverseHitTestOrder', - callback: () { debugDumpSemanticsTree(DebugSemanticsDumpOrder.inverseHitTest); return debugPrintDone; } - ); + registerSignalServiceExtension( + name: 'debugDumpSemanticsTreeInInverseHitTestOrder', + callback: () { + debugDumpSemanticsTree(DebugSemanticsDumpOrder.inverseHitTest); + return debugPrintDone; + }, + ); + } } /// Creates a [RenderView] object to be the root of the diff --git a/packages/flutter/lib/src/scheduler/binding.dart b/packages/flutter/lib/src/scheduler/binding.dart index ab2c3848f0..df8ffc3e19 100644 --- a/packages/flutter/lib/src/scheduler/binding.dart +++ b/packages/flutter/lib/src/scheduler/binding.dart @@ -203,13 +203,17 @@ mixin SchedulerBinding on BindingBase, ServicesBinding { @override void initServiceExtensions() { super.initServiceExtensions(); - registerNumericServiceExtension( - name: 'timeDilation', - getter: () async => timeDilation, - setter: (double value) async { - timeDilation = value; - } - ); + + const bool isReleaseMode = bool.fromEnvironment('dart.vm.product'); + if (!isReleaseMode) { + registerNumericServiceExtension( + name: 'timeDilation', + getter: () async => timeDilation, + setter: (double value) async { + timeDilation = value; + }, + ); + } } /// Whether the application is visible, and if so, whether it is currently diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart index 1112969b0b..ccaa815661 100644 --- a/packages/flutter/lib/src/services/binding.dart +++ b/packages/flutter/lib/src/services/binding.dart @@ -86,17 +86,21 @@ mixin ServicesBinding on BindingBase { @override void initServiceExtensions() { super.initServiceExtensions(); - registerStringServiceExtension( - // ext.flutter.evict value=foo.png will cause foo.png to be evicted from - // the rootBundle cache and cause the entire image cache to be cleared. - // This is used by hot reload mode to clear out the cache of resources - // that have changed. - name: 'evict', - getter: () async => '', - setter: (String value) async { - evict(value); - } - ); + + assert(() { + registerStringServiceExtension( + // ext.flutter.evict value=foo.png will cause foo.png to be evicted from + // the rootBundle cache and cause the entire image cache to be cleared. + // This is used by hot reload mode to clear out the cache of resources + // that have changed. + name: 'evict', + getter: () async => '', + setter: (String value) async { + evict(value); + }, + ); + return true; + }()); } /// Called in response to the `ext.flutter.evict` service extension. diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart index cd1f7528fe..e73934f2e8 100644 --- a/packages/flutter/lib/src/widgets/binding.dart +++ b/packages/flutter/lib/src/widgets/binding.dart @@ -265,37 +265,41 @@ mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererB void initServiceExtensions() { super.initServiceExtensions(); - registerSignalServiceExtension( - name: 'debugDumpApp', - callback: () { - debugDumpApp(); - return debugPrintDone; - } - ); + const bool isReleaseMode = bool.fromEnvironment('dart.vm.product'); + if (!isReleaseMode) { + registerSignalServiceExtension( + name: 'debugDumpApp', + callback: () { + debugDumpApp(); + return debugPrintDone; + }, + ); - registerBoolServiceExtension( - name: 'showPerformanceOverlay', - getter: () => Future.value(WidgetsApp.showPerformanceOverlayOverride), - setter: (bool value) { - if (WidgetsApp.showPerformanceOverlayOverride == value) - return Future.value(); - WidgetsApp.showPerformanceOverlayOverride = value; - return _forceRebuild(); - } - ); - - registerBoolServiceExtension( - name: 'debugAllowBanner', - getter: () => Future.value(WidgetsApp.debugAllowBannerOverride), - setter: (bool value) { - if (WidgetsApp.debugAllowBannerOverride == value) - return Future.value(); - WidgetsApp.debugAllowBannerOverride = value; - return _forceRebuild(); - } - ); + registerBoolServiceExtension( + name: 'showPerformanceOverlay', + getter: () => + Future.value(WidgetsApp.showPerformanceOverlayOverride), + setter: (bool value) { + if (WidgetsApp.showPerformanceOverlayOverride == value) + return Future.value(); + WidgetsApp.showPerformanceOverlayOverride = value; + return _forceRebuild(); + }, + ); + } assert(() { + registerBoolServiceExtension( + name: 'debugAllowBanner', + getter: () => Future.value(WidgetsApp.debugAllowBannerOverride), + setter: (bool value) { + if (WidgetsApp.debugAllowBannerOverride == value) + return Future.value(); + WidgetsApp.debugAllowBannerOverride = value; + return _forceRebuild(); + }, + ); + // Expose the ability to send Widget rebuilds as [Timeline] events. registerBoolServiceExtension( name: 'profileWidgetBuilds', @@ -303,25 +307,26 @@ mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererB setter: (bool value) async { if (debugProfileBuildsEnabled != value) debugProfileBuildsEnabled = value; - } + }, ); + + // This service extension is deprecated and will be removed by 12/1/2018. + // Use ext.flutter.inspector.show instead. + registerBoolServiceExtension( + name: 'debugWidgetInspector', + getter: () async => WidgetsApp.debugShowWidgetInspectorOverride, + setter: (bool value) { + if (WidgetsApp.debugShowWidgetInspectorOverride == value) + return Future.value(); + WidgetsApp.debugShowWidgetInspectorOverride = value; + return _forceRebuild(); + } + ); + + WidgetInspectorService.instance.initServiceExtensions(registerServiceExtension); + return true; }()); - - // This service extension is deprecated and will be removed by 7/1/2018. - // Use ext.flutter.inspector.show instead. - registerBoolServiceExtension( - name: 'debugWidgetInspector', - getter: () async => WidgetsApp.debugShowWidgetInspectorOverride, - setter: (bool value) { - if (WidgetsApp.debugShowWidgetInspectorOverride == value) - return Future.value(); - WidgetsApp.debugShowWidgetInspectorOverride = value; - return _forceRebuild(); - } - ); - - WidgetInspectorService.instance.initServiceExtensions(registerServiceExtension); } Future _forceRebuild() { diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index e65a08edc8..19dec90a25 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart @@ -917,13 +917,11 @@ mixin WidgetInspectorService { /// Called to register service extensions. /// - /// 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: /// /// * + /// * [BindingBase.initServiceExtensions], which explains when service + /// extensions can be used. void initServiceExtensions( _RegisterServiceExtensionCallback registerServiceExtensionCallback) { _registerServiceExtensionCallback = registerServiceExtensionCallback; @@ -1023,36 +1021,33 @@ mixin WidgetInspectorService { name: 'isWidgetCreationTracked', callback: isWidgetCreationTracked, ); - assert(() { - registerServiceExtension( - name: 'screenshot', - callback: (Map parameters) async { - assert(parameters.containsKey('id')); - assert(parameters.containsKey('width')); - assert(parameters.containsKey('height')); + registerServiceExtension( + name: 'screenshot', + callback: (Map parameters) async { + assert(parameters.containsKey('id')); + assert(parameters.containsKey('width')); + assert(parameters.containsKey('height')); - final ui.Image image = await screenshot( - toObject(parameters['id']), - width: double.parse(parameters['width']), - height: double.parse(parameters['height']), - margin: parameters.containsKey('margin') ? - double.parse(parameters['margin']) : 0.0, - maxPixelRatio: parameters.containsKey('maxPixelRatio') ? - double.parse(parameters['maxPixelRatio']) : 1.0, - debugPaint: parameters['debugPaint'] == 'true', - ); - if (image == null) { - return {'result': null}; - } - final ByteData byteData = await image.toByteData(format:ui.ImageByteFormat.png); + final ui.Image image = await screenshot( + toObject(parameters['id']), + width: double.parse(parameters['width']), + height: double.parse(parameters['height']), + margin: parameters.containsKey('margin') ? + double.parse(parameters['margin']) : 0.0, + maxPixelRatio: parameters.containsKey('maxPixelRatio') ? + double.parse(parameters['maxPixelRatio']) : 1.0, + debugPaint: parameters['debugPaint'] == 'true', + ); + if (image == null) { + return {'result': null}; + } + final ByteData byteData = await image.toByteData(format:ui.ImageByteFormat.png); - return { - 'result': base64.encoder.convert(Uint8List.view(byteData.buffer)), - }; - }, - ); - return true; - }()); + return { + 'result': base64.encoder.convert(Uint8List.view(byteData.buffer)), + }; + }, + ); } /// Clear all InspectorService object references. diff --git a/packages/flutter/test/foundation/service_extensions_test.dart b/packages/flutter/test/foundation/service_extensions_test.dart index a5be6123e9..775163e9db 100644 --- a/packages/flutter/test/foundation/service_extensions_test.dart +++ b/packages/flutter/test/foundation/service_extensions_test.dart @@ -359,13 +359,6 @@ void main() { expect(binding.extensions.containsKey('exit'), isTrue); }); - test('Service extensions - frameworkPresent', () async { - Map result; - - result = await binding.testExtension('frameworkPresent', {}); - expect(result, {}); - }); - test('Service extensions - platformOverride', () async { Map result; @@ -491,7 +484,6 @@ void main() { test('Service extensions - debugWidgetInspector', () async { Map result; - expect(binding.frameScheduled, isFalse); expect(WidgetsApp.debugShowWidgetInspectorOverride, false); result = await binding.testExtension('debugWidgetInspector', {}); @@ -541,7 +533,7 @@ void main() { // If you add a service extension... TEST IT! :-) // ...then increment this number. - expect(binding.extensions.length, 39); + expect(binding.extensions.length, 38); expect(console, isEmpty); debugPrint = debugPrintThrottled; diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index a66e4f870b..bc1586f724 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -1296,10 +1296,6 @@ class Isolate extends ServiceObjectOwner { ); } - Future flutterFrameworkPresent() async { - return await invokeFlutterExtensionRpcRaw('ext.flutter.frameworkPresent') != null; - } - Future> uiWindowScheduleFrame() async { return await invokeFlutterExtensionRpcRaw('ext.ui.window.scheduleFrame'); }