Unverified Commit 8e2ca93f authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Remove all service extensions from release mode (#23038)

Service extensions can only be activated in debug or profile mode, their code should never be included in release mode. This PR adds guards around all service extension registration calls that enable Dart's tree shaker to remove the extension's code in release mode, which reduces our binary size:

Android Snapshot (uncompressed): minus 127,384 Bytes (-124.40KB)
APK (compressed): minus 38,136 Bytes (-37.24KB)
iOS Snapshot (App.framework, uncompressed): 264,304 Bytes(-258.10KB)

For details: https://docs.google.com/document/d/13JlgvliCn5sWwT2K2SfDwD1NhEfxpJH9DCf22gZZru8/edit

**Benchmark Regressions:** This PR may cause benchmarks to regress because it may change the timing of GC. If you notice a benchmark regression **please note down the exact set of benchmarks that regressed on this PR** and then feel free to revert. I will follow-up with a PR that forces a GC before the effected benchmarks run to get a clean baseline before re-applying this PR.
parent 04a26778
......@@ -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);
assert(() {
registerSignalServiceExtension(
name: 'reassemble',
callback: reassembleApplication,
);
return true;
}());
const bool isReleaseMode = bool.fromEnvironment('dart.vm.product');
if (!isReleaseMode) {
registerSignalServiceExtension(
name: 'exit',
callback: _exitApplication,
);
registerSignalServiceExtension(
name: 'frameworkPresent',
callback: () => Future<Null>.value(),
);
}
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,
......
......@@ -55,7 +55,7 @@ abstract class RendererBinding extends BindingBase with ServicesBinding, Schedul
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,
......@@ -64,7 +64,7 @@ abstract class RendererBinding extends BindingBase with ServicesBinding, Schedul
return Future<Null>.value();
debugPaintSizeEnabled = value;
return _forceRepaint();
}
},
);
registerBoolServiceExtension(
name: 'debugPaintBaselinesEnabled',
......@@ -74,7 +74,7 @@ abstract class RendererBinding extends BindingBase with ServicesBinding, Schedul
return Future<Null>.value();
debugPaintBaselinesEnabled = value;
return _forceRepaint();
}
},
);
registerBoolServiceExtension(
name: 'repaintRainbow',
......@@ -85,31 +85,46 @@ abstract class RendererBinding extends BindingBase with ServicesBinding, Schedul
if (repaint)
return _forceRepaint();
return Future<Null>.value();
}
},
);
registerSignalServiceExtension(
name: 'debugDumpLayerTree',
callback: () {
debugDumpLayerTree();
return debugPrintDone;
},
);
return true;
}());
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; }
callback: () {
debugDumpRenderTree();
return debugPrintDone;
},
);
registerSignalServiceExtension(
name: 'debugDumpSemanticsTreeInTraversalOrder',
callback: () { debugDumpSemanticsTree(DebugSemanticsDumpOrder.traversalOrder); return debugPrintDone; }
callback: () {
debugDumpSemanticsTree(DebugSemanticsDumpOrder.traversalOrder);
return debugPrintDone;
},
);
registerSignalServiceExtension(
name: 'debugDumpSemanticsTreeInInverseHitTestOrder',
callback: () { debugDumpSemanticsTree(DebugSemanticsDumpOrder.inverseHitTest); return debugPrintDone; }
callback: () {
debugDumpSemanticsTree(DebugSemanticsDumpOrder.inverseHitTest);
return debugPrintDone;
},
);
}
}
/// Creates a [RenderView] object to be the root of the
/// [RenderObject] rendering tree, and initializes it so that it
......
......@@ -207,14 +207,18 @@ abstract class SchedulerBinding extends BindingBase with ServicesBinding {
@override
void initServiceExtensions() {
super.initServiceExtensions();
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
/// interactive.
......
......@@ -90,6 +90,8 @@ abstract class ServicesBinding extends BindingBase {
@override
void initServiceExtensions() {
super.initServiceExtensions();
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.
......@@ -99,8 +101,10 @@ abstract class ServicesBinding extends BindingBase {
getter: () async => '',
setter: (String value) async {
evict(value);
}
},
);
return true;
}());
}
/// Called in response to the `ext.flutter.evict` service extension.
......
......@@ -267,25 +267,30 @@ abstract class WidgetsBinding extends BindingBase with SchedulerBinding, Gesture
void initServiceExtensions() {
super.initServiceExtensions();
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),
getter: () =>
Future<bool>.value(WidgetsApp.showPerformanceOverlayOverride),
setter: (bool value) {
if (WidgetsApp.showPerformanceOverlayOverride == value)
return Future<Null>.value();
WidgetsApp.showPerformanceOverlayOverride = value;
return _forceRebuild();
}
},
);
}
assert(() {
registerBoolServiceExtension(
name: 'debugAllowBanner',
getter: () => Future<bool>.value(WidgetsApp.debugAllowBannerOverride),
......@@ -294,10 +299,9 @@ abstract class WidgetsBinding extends BindingBase with SchedulerBinding, Gesture
return Future<Null>.value();
WidgetsApp.debugAllowBannerOverride = value;
return _forceRebuild();
}
},
);
assert(() {
// Expose the ability to send Widget rebuilds as [Timeline] events.
registerBoolServiceExtension(
name: 'profileWidgetBuilds',
......@@ -305,25 +309,13 @@ abstract class WidgetsBinding extends BindingBase with SchedulerBinding, Gesture
setter: (bool value) async {
if (debugProfileBuildsEnabled != value)
debugProfileBuildsEnabled = value;
}
);
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<Null>.value();
WidgetsApp.debugShowWidgetInspectorOverride = value;
return _forceRebuild();
}
},
);
WidgetInspectorService.instance.initServiceExtensions(registerServiceExtension);
return true;
}());
}
Future<Null> _forceRebuild() {
......
......@@ -921,13 +921,11 @@ class 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:
///
/// * <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;
......@@ -1027,7 +1025,6 @@ class WidgetInspectorService {
name: 'isWidgetCreationTracked',
callback: isWidgetCreationTracked,
);
assert(() {
registerServiceExtension(
name: 'screenshot',
callback: (Map<String, String> parameters) async {
......@@ -1055,8 +1052,6 @@ class WidgetInspectorService {
};
},
);
return true;
}());
}
/// Clear all InspectorService object references.
......
......@@ -359,13 +359,6 @@ void main() {
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;
......@@ -489,29 +482,6 @@ void main() {
expect(binding.frameScheduled, isFalse);
});
test('Service extensions - debugWidgetInspector', () async {
Map<String, dynamic> result;
expect(binding.frameScheduled, isFalse);
expect(WidgetsApp.debugShowWidgetInspectorOverride, false);
result = await binding.testExtension('debugWidgetInspector', <String, String>{});
expect(result, <String, String>{ 'enabled': 'false' });
expect(WidgetsApp.debugShowWidgetInspectorOverride, false);
result = await binding.testExtension('debugWidgetInspector', <String, String>{ 'enabled': 'true' });
expect(result, <String, String>{ 'enabled': 'true' });
expect(WidgetsApp.debugShowWidgetInspectorOverride, true);
result = await binding.testExtension('debugWidgetInspector', <String, String>{});
expect(result, <String, String>{ 'enabled': 'true' });
expect(WidgetsApp.debugShowWidgetInspectorOverride, true);
result = await binding.testExtension('debugWidgetInspector', <String, String>{ 'enabled': 'false' });
expect(result, <String, String>{ 'enabled': 'false' });
expect(WidgetsApp.debugShowWidgetInspectorOverride, false);
result = await binding.testExtension('debugWidgetInspector', <String, String>{});
expect(result, <String, String>{ 'enabled': 'false' });
expect(WidgetsApp.debugShowWidgetInspectorOverride, false);
expect(binding.frameScheduled, isFalse);
});
test('Service extensions - timeDilation', () async {
Map<String, dynamic> result;
......@@ -541,7 +511,7 @@ void main() {
// If you add a service extension... TEST IT! :-)
// ...then increment this number.
expect(binding.extensions.length, 39);
expect(binding.extensions.length, 37);
expect(console, isEmpty);
debugPrint = debugPrintThrottled;
......
......@@ -1295,10 +1295,6 @@ class Isolate extends ServiceObjectOwner {
);
}
Future<bool> flutterFrameworkPresent() async {
return await invokeFlutterExtensionRpcRaw('ext.flutter.frameworkPresent') != null;
}
Future<Map<String, dynamic>> uiWindowScheduleFrame() async {
return await invokeFlutterExtensionRpcRaw('ext.ui.window.scheduleFrame');
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment