Reland "Remove all service extensions from release mode (#23038)" (#23291)
diff --git a/packages/flutter/lib/src/foundation/binding.dart b/packages/flutter/lib/src/foundation/binding.dart
index abdf6b0..c6f1f08 100644
--- a/packages/flutter/lib/src/foundation/binding.dart
+++ b/packages/flutter/lib/src/foundation/binding.dart
@@ -93,9 +93,7 @@
/// 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 @@
@mustCallSuper
void initServiceExtensions() {
assert(!_debugServiceExtensionsRegistered);
- registerSignalServiceExtension(
- name: 'reassemble',
- callback: reassembleApplication,
- );
- registerSignalServiceExtension(
- name: 'exit',
- callback: _exitApplication,
- );
- registerSignalServiceExtension(
- name: 'frameworkPresent',
- callback: () => Future<void>.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 @@
/// 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 @@
///
/// 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 @@
///
/// 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 @@
///
/// 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 @@
);
}
- /// 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 f3ab270..41e823d 100644
--- a/packages/flutter/lib/src/rendering/binding.dart
+++ b/packages/flutter/lib/src/rendering/binding.dart
@@ -51,7 +51,7 @@
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 @@
return Future<void>.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<void>.value();
debugPaintBaselinesEnabled = value;
return _forceRepaint();
- }
+ },
);
registerBoolServiceExtension(
name: 'repaintRainbow',
@@ -83,28 +83,43 @@
return Future<void>.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 ab2c384..df8ffc3 100644
--- a/packages/flutter/lib/src/scheduler/binding.dart
+++ b/packages/flutter/lib/src/scheduler/binding.dart
@@ -203,13 +203,17 @@
@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 1112969..ccaa815 100644
--- a/packages/flutter/lib/src/services/binding.dart
+++ b/packages/flutter/lib/src/services/binding.dart
@@ -86,17 +86,21 @@
@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 cd1f752..e73934f 100644
--- a/packages/flutter/lib/src/widgets/binding.dart
+++ b/packages/flutter/lib/src/widgets/binding.dart
@@ -265,37 +265,41 @@
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<bool>.value(WidgetsApp.showPerformanceOverlayOverride),
- setter: (bool value) {
- if (WidgetsApp.showPerformanceOverlayOverride == value)
- return Future<void>.value();
- WidgetsApp.showPerformanceOverlayOverride = value;
- return _forceRebuild();
- }
- );
-
- registerBoolServiceExtension(
- name: 'debugAllowBanner',
- getter: () => Future<bool>.value(WidgetsApp.debugAllowBannerOverride),
- setter: (bool value) {
- if (WidgetsApp.debugAllowBannerOverride == value)
- return Future<void>.value();
- WidgetsApp.debugAllowBannerOverride = value;
- return _forceRebuild();
- }
- );
+ registerBoolServiceExtension(
+ name: 'showPerformanceOverlay',
+ getter: () =>
+ Future<bool>.value(WidgetsApp.showPerformanceOverlayOverride),
+ setter: (bool value) {
+ if (WidgetsApp.showPerformanceOverlayOverride == value)
+ return Future<void>.value();
+ WidgetsApp.showPerformanceOverlayOverride = value;
+ return _forceRebuild();
+ },
+ );
+ }
assert(() {
+ registerBoolServiceExtension(
+ name: 'debugAllowBanner',
+ getter: () => Future<bool>.value(WidgetsApp.debugAllowBannerOverride),
+ setter: (bool value) {
+ if (WidgetsApp.debugAllowBannerOverride == value)
+ return Future<void>.value();
+ WidgetsApp.debugAllowBannerOverride = value;
+ return _forceRebuild();
+ },
+ );
+
// Expose the ability to send Widget rebuilds as [Timeline] events.
registerBoolServiceExtension(
name: 'profileWidgetBuilds',
@@ -303,25 +307,26 @@
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<void>.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<void>.value();
- WidgetsApp.debugShowWidgetInspectorOverride = value;
- return _forceRebuild();
- }
- );
-
- WidgetInspectorService.instance.initServiceExtensions(registerServiceExtension);
}
Future<void> _forceRebuild() {
diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart
index e65a08e..19dec90 100644
--- a/packages/flutter/lib/src/widgets/widget_inspector.dart
+++ b/packages/flutter/lib/src/widgets/widget_inspector.dart
@@ -917,13 +917,11 @@
/// 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:
///
/// * <https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#rpcs-requests-and-responses>
+ /// * [BindingBase.initServiceExtensions], which explains when service
+ /// extensions can be used.
void initServiceExtensions(
_RegisterServiceExtensionCallback registerServiceExtensionCallback) {
_registerServiceExtensionCallback = registerServiceExtensionCallback;
@@ -1023,36 +1021,33 @@
name: 'isWidgetCreationTracked',
callback: isWidgetCreationTracked,
);
- assert(() {
- registerServiceExtension(
- name: 'screenshot',
- callback: (Map<String, String> parameters) async {
- assert(parameters.containsKey('id'));
- assert(parameters.containsKey('width'));
- assert(parameters.containsKey('height'));
+ registerServiceExtension(
+ name: 'screenshot',
+ callback: (Map<String, String> 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 <String, Object>{'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 <String, Object>{'result': null};
+ }
+ final ByteData byteData = await image.toByteData(format:ui.ImageByteFormat.png);
- return <String, Object>{
- 'result': base64.encoder.convert(Uint8List.view(byteData.buffer)),
- };
- },
- );
- return true;
- }());
+ return <String, Object>{
+ '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 a5be612..775163e 100644
--- a/packages/flutter/test/foundation/service_extensions_test.dart
+++ b/packages/flutter/test/foundation/service_extensions_test.dart
@@ -359,13 +359,6 @@
expect(binding.extensions.containsKey('exit'), isTrue);
});
- test('Service extensions - frameworkPresent', () async {
- Map<String, dynamic> result;
-
- result = await binding.testExtension('frameworkPresent', <String, String>{});
- expect(result, <String, String>{});
- });
-
test('Service extensions - platformOverride', () async {
Map<String, dynamic> result;
@@ -491,7 +484,6 @@
test('Service extensions - debugWidgetInspector', () async {
Map<String, dynamic> result;
-
expect(binding.frameScheduled, isFalse);
expect(WidgetsApp.debugShowWidgetInspectorOverride, false);
result = await binding.testExtension('debugWidgetInspector', <String, String>{});
@@ -541,7 +533,7 @@
// 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 a66e4f8..bc1586f 100644
--- a/packages/flutter_tools/lib/src/vmservice.dart
+++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -1296,10 +1296,6 @@
);
}
- Future<bool> flutterFrameworkPresent() async {
- return await invokeFlutterExtensionRpcRaw('ext.flutter.frameworkPresent') != null;
- }
-
Future<Map<String, dynamic>> uiWindowScheduleFrame() async {
return await invokeFlutterExtensionRpcRaw('ext.ui.window.scheduleFrame');
}