diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart index a3fb394f8a9..bfeef099697 100644 --- a/packages/flutter/lib/src/rendering/binding.dart +++ b/packages/flutter/lib/src/rendering/binding.dart @@ -68,6 +68,16 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding, callback: () { debugDumpRenderTree(); return debugPrintDone; } ); + registerSignalServiceExtension( + name: 'debugDumpLayerTree', + callback: () { debugDumpLayerTree(); return debugPrintDone; } + ); + + registerSignalServiceExtension( + name: 'debugDumpSemanticsTree', + callback: () { debugDumpSemanticsTree(); return debugPrintDone; } + ); + assert(() { // this service extension only works in checked mode registerBoolServiceExtension( diff --git a/packages/flutter/test/foundation/service_extensions_test.dart b/packages/flutter/test/foundation/service_extensions_test.dart index bc40947e49e..d94ccd20ebb 100644 --- a/packages/flutter/test/foundation/service_extensions_test.dart +++ b/packages/flutter/test/foundation/service_extensions_test.dart @@ -142,16 +142,52 @@ void main() { expect(result, {}); expect(console, [ matches( + r'^' r'RenderView#[0-9]+\n' r' debug mode enabled - [a-zA-Z]+\n' r' window size: Size\(800\.0, 600\.0\) \(in physical pixels\)\n' r' device pixel ratio: 1\.0 \(physical pixels per logical pixel\)\n' r' configuration: Size\(800\.0, 600\.0\) at 1\.0x \(in logical pixels\)\n' + r'$' ), ]); console.clear(); }); + test('Service extensions - debugDumpLayerTree', () async { + Map result; + + await binding.doFrame(); + result = await binding.testExtension('debugDumpLayerTree', {}); + expect(result, {}); + expect(console, [ + matches( + r'^' + r'TransformLayer#[0-9]+\n' + r' owner: RenderView#[0-9]+\n' + r' creator: RenderView\n' + r' offset: Offset\(0\.0, 0\.0\)\n' + r' transform:\n' + r' \[0] 1\.0,0\.0,0\.0,0\.0\n' + r' \[1] 0\.0,1\.0,0\.0,0\.0\n' + r' \[2] 0\.0,0\.0,1\.0,0\.0\n' + r' \[3] 0\.0,0\.0,0\.0,1\.0\n' + r'$' + ), + ]); + console.clear(); + }); + + test('Service extensions - debugDumpSemanticsTree', () async { + Map result; + + await binding.doFrame(); + result = await binding.testExtension('debugDumpSemanticsTree', {}); + expect(result, {}); + expect(console, ['Semantics not collected.']); + console.clear(); + }); + test('Service extensions - debugPaint', () async { Map result; Future> pendingResult; @@ -381,7 +417,7 @@ void main() { test('Service extensions - posttest', () async { // If you add a service extension... TEST IT! :-) // ...then increment this number. - expect(binding.extensions.length, 12); + expect(binding.extensions.length, 14); expect(console, isEmpty); debugPrint = debugPrintThrottled; diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index e4f07d15d7a..a568d62cd21 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -147,12 +147,22 @@ class FlutterDevice { await view.uiIsolate.flutterDebugDumpRenderTree(); } + Future debugDumpLayerTree() async { + for (FlutterView view in views) + await view.uiIsolate.flutterDebugDumpLayerTree(); + } + + Future debugDumpSemanticsTree() async { + for (FlutterView view in views) + await view.uiIsolate.flutterDebugDumpSemanticsTree(); + } + Future toggleDebugPaintSizeEnabled() async { for (FlutterView view in views) await view.uiIsolate.flutterToggleDebugPaintSizeEnabled(); } - Future togglePlatform({String from}) async { + Future togglePlatform({ String from }) async { String to; switch (from) { case 'iOS': @@ -163,9 +173,8 @@ class FlutterDevice { to = 'iOS'; break; } - for (FlutterView view in views) { + for (FlutterView view in views) await view.uiIsolate.flutterPlatformOverride(to); - } return to; } @@ -415,6 +424,18 @@ abstract class ResidentRunner { await device.debugDumpRenderTree(); } + Future _debugDumpLayerTree() async { + await refreshViews(); + for (FlutterDevice device in flutterDevices) + await device.debugDumpLayerTree(); + } + + Future _debugDumpSemanticsTree() async { + await refreshViews(); + for (FlutterDevice device in flutterDevices) + await device.debugDumpSemanticsTree(); + } + Future _debugToggleDebugPaintSizeEnabled() async { await refreshViews(); for (FlutterDevice device in flutterDevices) @@ -505,9 +526,8 @@ abstract class ResidentRunner { } Future connectToServiceProtocol({String viewFilter}) async { - if (!debuggingOptions.debuggingEnabled) { + if (!debuggingOptions.debuggingEnabled) return new Future.error('Error the service protocol is not enabled.'); - } bool viewFound = false; for (FlutterDevice device in flutterDevices) { @@ -564,12 +584,22 @@ abstract class ResidentRunner { await _debugDumpRenderTree(); return true; } + } else if (character == 'L') { + if (supportsServiceProtocol) { + await _debugDumpLayerTree(); + return true; + } + } else if (character == 'S') { + if (supportsServiceProtocol) { + await _debugDumpSemanticsTree(); + return true; + } } else if (lower == 'p') { if (supportsServiceProtocol && isRunningDebug) { await _debugToggleDebugPaintSizeEnabled(); return true; } - } else if (lower == 's') { + } else if (character == 's') { for (FlutterDevice device in flutterDevices) { if (device.device.supportsScreenshot) await _screenshot(device); @@ -656,16 +686,13 @@ abstract class ResidentRunner { final DependencyChecker dependencyChecker = new DependencyChecker(dartDependencySetBuilder, assetBundle); final String path = device.package.packagePath; - if (path == null) { + if (path == null) return true; - } final FileStat stat = fs.file(path).statSync(); - if (stat.type != FileSystemEntityType.FILE) { + if (stat.type != FileSystemEntityType.FILE) return true; - } - if (!fs.file(path).existsSync()) { + if (!fs.file(path).existsSync()) return true; - } final DateTime lastBuildTime = stat.modified; return dependencyChecker.check(lastBuildTime); } @@ -683,8 +710,9 @@ abstract class ResidentRunner { void printHelpDetails() { if (supportsServiceProtocol) { - printStatus('To dump the widget hierarchy of the app (debugDumpApp), press "w".'); + printStatus('You can dump the widget hierarchy of the app (debugDumpApp) by pressing "w".'); printStatus('To dump the rendering tree of the app (debugDumpRenderTree), press "t".'); + printStatus('For layers (debugDumpLayerTree), use "L"; accessibility (debugDumpSemantics), "S".'); if (isRunningDebug) { printStatus('To toggle the display of construction lines (debugPaintSizeEnabled), press "p".'); printStatus('To simulate different operating systems, (defaultTargetPlatform), press "o".'); diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index 9cf767504bc..fa3899bf8ac 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -998,6 +998,14 @@ class Isolate extends ServiceObjectOwner { return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpRenderTree', timeout: kLongRequestTimeout); } + Future> flutterDebugDumpLayerTree() { + return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpLayerTree', timeout: kLongRequestTimeout); + } + + Future> flutterDebugDumpSemanticsTree() { + return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTree', timeout: kLongRequestTimeout); + } + Future> flutterToggleDebugPaintSizeEnabled() async { Map state = await invokeFlutterExtensionRpcRaw('ext.flutter.debugPaint'); if (state != null && state.containsKey('enabled') && state['enabled'] is String) {