[Focus] Add run key command to dump the focus tree (#123473)
[Focus] Add run key command to dump the focus tree
This commit is contained in:
parent
89da04682c
commit
8f62e34267
@ -81,6 +81,7 @@ Future<void> smokeDemo(WidgetTester tester, GalleryDemo demo) async {
|
||||
verifyToStringOutput('debugDumpApp', routeName, WidgetsBinding.instance.rootElement!.toStringDeep());
|
||||
verifyToStringOutput('debugDumpRenderTree', routeName, RendererBinding.instance.renderView.toStringDeep());
|
||||
verifyToStringOutput('debugDumpLayerTree', routeName, RendererBinding.instance.renderView.debugLayer?.toStringDeep() ?? '');
|
||||
verifyToStringOutput('debugDumpFocusTree', routeName, WidgetsBinding.instance.focusManager.toStringDeep());
|
||||
|
||||
// Scroll the demo around a bit more.
|
||||
await tester.flingFrom(const Offset(400.0, 300.0), const Offset(0.0, 400.0), 1000.0);
|
||||
|
@ -68,7 +68,6 @@ enum RenderingServiceExtensions {
|
||||
/// registered.
|
||||
debugDumpLayerTree,
|
||||
|
||||
|
||||
/// Name of service extension that, when called, will toggle whether all
|
||||
/// clipping effects from the layer tree will be ignored.
|
||||
///
|
||||
|
@ -386,6 +386,16 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
||||
},
|
||||
);
|
||||
|
||||
registerServiceExtension(
|
||||
name: WidgetsServiceExtensions.debugDumpFocusTree.name,
|
||||
callback: (Map<String, String> parameters) async {
|
||||
final String data = focusManager.toStringDeep();
|
||||
return <String, Object>{
|
||||
'data': data,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
if (!kIsWeb) {
|
||||
registerBoolServiceExtension(
|
||||
name: WidgetsServiceExtensions.showPerformanceOverlay.name,
|
||||
|
@ -20,6 +20,15 @@ enum WidgetsServiceExtensions {
|
||||
/// registered.
|
||||
debugDumpApp,
|
||||
|
||||
/// Name of service extension that, when called, will output a string
|
||||
/// representation of the focus tree to the console.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [WidgetsBinding.initServiceExtensions], where the service extension is
|
||||
/// registered.
|
||||
debugDumpFocusTree,
|
||||
|
||||
/// Name of service extension that, when called, will overlay a performance
|
||||
/// graph on top of this app.
|
||||
///
|
||||
|
@ -177,7 +177,7 @@ void main() {
|
||||
// framework, excluding any that are for the widget inspector
|
||||
// (see widget_inspector_test.dart for tests of the ext.flutter.inspector
|
||||
// service extensions).
|
||||
const int serviceExtensionCount = 37;
|
||||
const int serviceExtensionCount = 38;
|
||||
|
||||
expect(binding.extensions.length, serviceExtensionCount + widgetInspectorExtensionCount - disabledExtensions);
|
||||
|
||||
@ -218,6 +218,19 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
test('Service extensions - debugDumpFocusTree', () async {
|
||||
final Map<String, dynamic> result = await binding.testExtension(WidgetsServiceExtensions.debugDumpFocusTree.name, <String, String>{});
|
||||
|
||||
expect(result, <String, dynamic>{
|
||||
'data': matches(
|
||||
r'^'
|
||||
r'FocusManager#[0-9a-f]{5}\n'
|
||||
r' └─rootScope: FocusScopeNode#[0-9a-f]{5}\(Root Focus Scope\)\n'
|
||||
r'$',
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
test('Service extensions - debugDumpRenderTree', () async {
|
||||
await binding.doFrame();
|
||||
final Map<String, dynamic> result = await binding.testExtension(RenderingServiceExtensions.debugDumpRenderTree.name, <String, String>{});
|
||||
|
@ -97,6 +97,12 @@ class CommandHelp {
|
||||
'Detach (terminate "flutter run" but leave application running).',
|
||||
);
|
||||
|
||||
late final CommandHelpOption f = _makeOption(
|
||||
'f',
|
||||
'Dump focus tree to the console.',
|
||||
'debugDumpFocusTree',
|
||||
);
|
||||
|
||||
late final CommandHelpOption g = _makeOption(
|
||||
'g',
|
||||
'Run source code generators.'
|
||||
|
@ -752,6 +752,22 @@ abstract class ResidentHandlers {
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> debugDumpFocusTree() async {
|
||||
if (!supportsServiceProtocol || !isRunningDebug) {
|
||||
return false;
|
||||
}
|
||||
for (final FlutterDevice? device in flutterDevices) {
|
||||
final List<FlutterView> views = await device!.vmService!.getFlutterViews();
|
||||
for (final FlutterView view in views) {
|
||||
final String data = await device.vmService!.flutterDebugDumpFocusTree(
|
||||
isolateId: view.uiIsolate!.id!,
|
||||
);
|
||||
logger.printStatus(data);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Dump the application's current semantics tree to the terminal.
|
||||
///
|
||||
/// If semantics are not enabled, nothing is returned.
|
||||
@ -1521,6 +1537,7 @@ abstract class ResidentRunner extends ResidentHandlers {
|
||||
commandHelp.t.print();
|
||||
if (isRunningDebug) {
|
||||
commandHelp.L.print();
|
||||
commandHelp.f.print();
|
||||
commandHelp.S.print();
|
||||
commandHelp.U.print();
|
||||
commandHelp.i.print();
|
||||
@ -1706,6 +1723,8 @@ class TerminalHandler {
|
||||
case 'D':
|
||||
await residentRunner.detach();
|
||||
return true;
|
||||
case 'f':
|
||||
return residentRunner.debugDumpFocusTree();
|
||||
case 'g':
|
||||
await residentRunner.runSourceGenerators();
|
||||
return true;
|
||||
|
@ -643,6 +643,16 @@ class FlutterVmService {
|
||||
return response?['data']?.toString() ?? '';
|
||||
}
|
||||
|
||||
Future<String> flutterDebugDumpFocusTree({
|
||||
required String isolateId,
|
||||
}) async {
|
||||
final Map<String, Object?>? response = await invokeFlutterExtensionRpcRaw(
|
||||
'ext.flutter.debugDumpFocusTree',
|
||||
isolateId: isolateId,
|
||||
);
|
||||
return response?['data']?.toString() ?? '';
|
||||
}
|
||||
|
||||
Future<String> flutterDebugDumpSemanticsTreeInTraversalOrder({
|
||||
required String isolateId,
|
||||
}) async {
|
||||
|
@ -60,6 +60,7 @@ void _testMessageLength({
|
||||
expect(commandHelp.b.toString().length, lessThanOrEqualTo(expectedWidth));
|
||||
expect(commandHelp.c.toString().length, lessThanOrEqualTo(expectedWidth));
|
||||
expect(commandHelp.d.toString().length, lessThanOrEqualTo(expectedWidth));
|
||||
expect(commandHelp.f.toString().length, lessThanOrEqualTo(expectedWidth));
|
||||
expect(commandHelp.g.toString().length, lessThanOrEqualTo(expectedWidth));
|
||||
expect(commandHelp.hWithDetails.toString().length, lessThanOrEqualTo(expectedWidth));
|
||||
expect(commandHelp.hWithoutDetails.toString().length, lessThanOrEqualTo(expectedWidth));
|
||||
@ -137,6 +138,7 @@ void main() {
|
||||
expect(commandHelp.U.toString(), endsWith('\x1B[90m(debugDumpSemantics)\x1B[39m\x1B[22m'));
|
||||
expect(commandHelp.a.toString(), endsWith('\x1B[90m(debugProfileWidgetBuilds)\x1B[39m\x1B[22m'));
|
||||
expect(commandHelp.b.toString(), endsWith('\x1B[90m(debugBrightnessOverride)\x1B[39m\x1B[22m'));
|
||||
expect(commandHelp.f.toString(), endsWith('\x1B[90m(debugDumpFocusTree)\x1B[39m\x1B[22m'));
|
||||
expect(commandHelp.i.toString(), endsWith('\x1B[90m(WidgetsApp.showWidgetInspectorOverride)\x1B[39m\x1B[22m'));
|
||||
expect(commandHelp.o.toString(), endsWith('\x1B[90m(defaultTargetPlatform)\x1B[39m\x1B[22m'));
|
||||
expect(commandHelp.p.toString(), endsWith('\x1B[90m(debugPaintSizeEnabled)\x1B[39m\x1B[22m'));
|
||||
@ -193,6 +195,7 @@ void main() {
|
||||
expect(commandHelp.b.toString(), equals('\x1B[1mb\x1B[22m Toggle platform brightness (dark and light mode). \x1B[90m(debugBrightnessOverride)\x1B[39m\x1B[22m'));
|
||||
expect(commandHelp.c.toString(), equals('\x1B[1mc\x1B[22m Clear the screen'));
|
||||
expect(commandHelp.d.toString(), equals('\x1B[1md\x1B[22m Detach (terminate "flutter run" but leave application running).'));
|
||||
expect(commandHelp.f.toString(), equals('\x1B[1mf\x1B[22m Dump focus tree to the console. \x1B[90m(debugDumpFocusTree)\x1B[39m\x1B[22m'));
|
||||
expect(commandHelp.g.toString(), equals('\x1B[1mg\x1B[22m Run source code generators.'));
|
||||
expect(commandHelp.hWithDetails.toString(), equals('\x1B[1mh\x1B[22m Repeat this help message.'));
|
||||
expect(commandHelp.hWithoutDetails.toString(), equals('\x1B[1mh\x1B[22m List all available interactive commands.'));
|
||||
|
@ -1455,6 +1455,7 @@ flutter:
|
||||
commandHelp.w,
|
||||
commandHelp.t,
|
||||
commandHelp.L,
|
||||
commandHelp.f,
|
||||
commandHelp.S,
|
||||
commandHelp.U,
|
||||
commandHelp.i,
|
||||
|
@ -400,6 +400,52 @@ void main() {
|
||||
await terminalHandler.processTerminalInput('L');
|
||||
});
|
||||
|
||||
testWithoutContext('f - debugDumpFocusTree', () async {
|
||||
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
|
||||
listViews,
|
||||
const FakeVmServiceRequest(
|
||||
method: 'ext.flutter.debugDumpFocusTree',
|
||||
args: <String, Object>{
|
||||
'isolateId': '1',
|
||||
},
|
||||
jsonResponse: <String, Object>{
|
||||
'data': 'FOCUS TREE',
|
||||
}
|
||||
),
|
||||
]);
|
||||
await terminalHandler.processTerminalInput('f');
|
||||
|
||||
expect(terminalHandler.logger.statusText, contains('FOCUS TREE'));
|
||||
});
|
||||
|
||||
testWithoutContext('f - debugDumpLayerTree with web target', () async {
|
||||
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
|
||||
listViews,
|
||||
const FakeVmServiceRequest(
|
||||
method: 'ext.flutter.debugDumpFocusTree',
|
||||
args: <String, Object>{
|
||||
'isolateId': '1',
|
||||
},
|
||||
jsonResponse: <String, Object>{
|
||||
'data': 'FOCUS TREE',
|
||||
}
|
||||
),
|
||||
], web: true);
|
||||
await terminalHandler.processTerminalInput('f');
|
||||
|
||||
expect(terminalHandler.logger.statusText, contains('FOCUS TREE'));
|
||||
});
|
||||
|
||||
testWithoutContext('f - debugDumpFocusTree with service protocol and profile mode is skipped', () async {
|
||||
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], buildMode: BuildMode.profile);
|
||||
await terminalHandler.processTerminalInput('f');
|
||||
});
|
||||
|
||||
testWithoutContext('f - debugDumpFocusTree without service protocol is skipped', () async {
|
||||
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
|
||||
await terminalHandler.processTerminalInput('f');
|
||||
});
|
||||
|
||||
testWithoutContext('o,O - debugTogglePlatform', () async {
|
||||
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
|
||||
// Request 1.
|
||||
|
@ -447,6 +447,46 @@ void main() {
|
||||
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
||||
});
|
||||
|
||||
testWithoutContext('flutterDebugDumpFocusTree handles missing method', () async {
|
||||
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
||||
requests: <VmServiceExpectation>[
|
||||
const FakeVmServiceRequest(
|
||||
method: 'ext.flutter.debugDumpFocusTree',
|
||||
args: <String, Object>{
|
||||
'isolateId': '1',
|
||||
},
|
||||
errorCode: RPCErrorCodes.kMethodNotFound,
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
expect(await fakeVmServiceHost.vmService.flutterDebugDumpFocusTree(
|
||||
isolateId: '1',
|
||||
), '');
|
||||
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
||||
});
|
||||
|
||||
testWithoutContext('flutterDebugDumpFocusTree returns data', () async {
|
||||
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
||||
requests: <VmServiceExpectation>[
|
||||
const FakeVmServiceRequest(
|
||||
method: 'ext.flutter.debugDumpFocusTree',
|
||||
args: <String, Object>{
|
||||
'isolateId': '1',
|
||||
},
|
||||
jsonResponse: <String, Object> {
|
||||
'data': 'Hello world',
|
||||
},
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
expect(await fakeVmServiceHost.vmService.flutterDebugDumpFocusTree(
|
||||
isolateId: '1',
|
||||
), 'Hello world');
|
||||
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
||||
});
|
||||
|
||||
testWithoutContext('Framework service extension invocations return null if service disappears ', () async {
|
||||
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
||||
requests: <VmServiceExpectation>[
|
||||
|
@ -604,6 +604,7 @@ void main() {
|
||||
'w Dump widget hierarchy to the console. (debugDumpApp)',
|
||||
't Dump rendering tree to the console. (debugDumpRenderTree)',
|
||||
'L Dump layer tree to the console. (debugDumpLayerTree)',
|
||||
'f Dump focus tree to the console. (debugDumpFocusTree)',
|
||||
'S Dump accessibility tree in traversal order. (debugDumpSemantics)',
|
||||
'U Dump accessibility tree in inverse hit test order. (debugDumpSemantics)',
|
||||
'i Toggle widget inspector. (WidgetsApp.showWidgetInspectorOverride)',
|
||||
|
Loading…
x
Reference in New Issue
Block a user