Unverified Commit 14309b93 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Adds the semantic node traversal API. (#14060)

This adds an API for defining the semantic node traversal order.

It adds a sortOrder argument to the Semantics widget, which is a class that can define a list of sort keys to sort on. The keys are sorted globally so that an order that doesn't have to do with the current widget hierarchy may be defined.

It also adds a shortcut sortKey argument to the Semantics widget that simply sets the sortOrder to just contain that key.

The platform side (flutter/engine#4540) gets an additional member in the SemanticsData object that is an integer describing where in the overall order each semantics node belongs. There is an associated engine-side change that takes this integer and uses it to order widgets for the platform's accessibility services.
parent 0f7d4428
......@@ -130,7 +130,7 @@ class StockHomeState extends State<StockHome> {
debugDumpApp();
debugDumpRenderTree();
debugDumpLayerTree();
debugDumpSemanticsTree(DebugSemanticsDumpOrder.traversal);
debugDumpSemanticsTree(DebugSemanticsDumpOrder.geometricOrder);
} catch (e, stack) {
debugPrint('Exception while dumping app:\n$e\n$stack');
}
......
......@@ -101,8 +101,8 @@ abstract class RendererBinding extends BindingBase with ServicesBinding, Schedul
);
registerSignalServiceExtension(
name: 'debugDumpSemanticsTreeInTraversalOrder',
callback: () { debugDumpSemanticsTree(DebugSemanticsDumpOrder.traversal); return debugPrintDone; }
name: 'debugDumpSemanticsTreeInGeometricOrder',
callback: () { debugDumpSemanticsTree(DebugSemanticsDumpOrder.geometricOrder); return debugPrintDone; }
);
registerSignalServiceExtension(
......
......@@ -2228,7 +2228,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
// Dirty the semantics tree starting at `this` until we have reached a
// RenderObject that is a semantics boundary. All semantics past this
// RenderObject are still up-to date. Therefore, we will later only rebuild
// the semantics subtree starting at th identified semantics boundary.
// the semantics subtree starting at the identified semantics boundary.
final bool wasSemanticsBoundary = _semantics != null && _cachedSemanticsConfiguration?.isSemanticBoundary == true;
_cachedSemanticsConfiguration = null;
......@@ -2254,7 +2254,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
// remove it as it is no longer guaranteed that its semantics
// node will continue to be in the tree. If it still is in the tree, the
// ancestor `node` added to [owner._nodesNeedingSemantics] at the end of
// this block will ensure that the semantics of `this` node actually get
// this block will ensure that the semantics of `this` node actually gets
// updated.
// (See semantics_10_test.dart for an example why this is required).
owner._nodesNeedingSemantics.remove(this);
......@@ -3005,8 +3005,8 @@ class _ContainerSemanticsFragment extends _SemanticsFragment {
/// A [_SemanticsFragment] that describes which concrete semantic information
/// a [RenderObject] wants to add to the [SemanticsNode] of its parent.
///
/// Specifically, it describes what children (as returned by [compileChildren])
/// should be added to the parent's [SemanticsNode] and what [config] should be
/// Specifically, it describes which children (as returned by [compileChildren])
/// should be added to the parent's [SemanticsNode] and which [config] should be
/// merged into the parent's [SemanticsNode].
abstract class _InterestingSemanticsFragment extends _SemanticsFragment {
_InterestingSemanticsFragment({
......@@ -3082,7 +3082,7 @@ abstract class _InterestingSemanticsFragment extends _SemanticsFragment {
/// An [_InterestingSemanticsFragment] that produces the root [SemanticsNode] of
/// the semantics tree.
///
/// The root node is available as only element in the Iterable returned by
/// The root node is available as the only element in the Iterable returned by
/// [children].
class _RootSemanticsFragment extends _InterestingSemanticsFragment {
_RootSemanticsFragment({
......@@ -3144,7 +3144,7 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment {
/// fragment it will create a new [SemanticsNode]. The newly created node will
/// be annotated with the [SemanticsConfiguration] that - without the call to
/// [markAsExplicit] - would have been merged into the parent's [SemanticsNode].
/// Similarity, the new node will also take over the children that otherwise
/// Similarly, the new node will also take over the children that otherwise
/// would have been added to the parent's [SemanticsNode].
///
/// After a call to [markAsExplicit] the only element returned by [children]
......
......@@ -3008,6 +3008,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
String decreasedValue,
String hint,
TextDirection textDirection,
SemanticsSortOrder sortOrder,
VoidCallback onTap,
VoidCallback onLongPress,
VoidCallback onScrollLeft,
......@@ -3035,6 +3036,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
_decreasedValue = decreasedValue,
_hint = hint,
_textDirection = textDirection,
_sortOrder = sortOrder,
_onTap = onTap,
_onLongPress = onLongPress,
_onScrollLeft = onScrollLeft,
......@@ -3208,6 +3210,20 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
markNeedsSemanticsUpdate();
}
/// Sets the [SemanticsNode.sortOrder] to the given value.
///
/// This defines how this node will be sorted with the other semantics nodes
/// to determine the order in which they are traversed by the accessibility
/// services on the platform (e.g. VoiceOver on iOS and TalkBack on Android).
SemanticsSortOrder get sortOrder => _sortOrder;
SemanticsSortOrder _sortOrder;
set sortOrder(SemanticsSortOrder value) {
if (sortOrder == value)
return;
_sortOrder = value;
markNeedsSemanticsUpdate();
}
/// The handler for [SemanticsAction.tap].
///
/// This is the semantic equivalent of a user briefly tapping the screen with
......@@ -3504,6 +3520,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
config.hint = hint;
if (textDirection != null)
config.textDirection = textDirection;
if (sortOrder != null)
config.sortOrder = sortOrder;
// Registering _perform* as action handlers instead of the user provided
// ones to ensure that changing a user provided handler from a non-null to
// another non-null value doesn't require a semantics update.
......
......@@ -4828,7 +4828,16 @@ class Semantics extends SingleChildRenderObjectWidget {
/// Creates a semantic annotation.
///
/// The [container] argument must not be null. To create a `const` instance
/// of [Semantics], use the [new Semantics.fromProperties] constructor.
/// of [Semantics], use the [Semantics.fromProperties] constructor.
///
/// Only one of [sortKey] or [sortOrder] may be specified. Specifying [sortKey]
/// is just a shorthand for specifying `new SemanticsSortOrder(key: sortKey)`
/// for the [sortOrder].
///
/// See also:
///
/// * [SemanticsSortOrder] for a class that determines accessibility traversal
/// order.
Semantics({
Key key,
Widget child,
......@@ -4844,6 +4853,8 @@ class Semantics extends SingleChildRenderObjectWidget {
String decreasedValue,
String hint,
TextDirection textDirection,
SemanticsSortOrder sortOrder,
SemanticsSortKey sortKey,
VoidCallback onTap,
VoidCallback onLongPress,
VoidCallback onScrollLeft,
......@@ -4874,6 +4885,7 @@ class Semantics extends SingleChildRenderObjectWidget {
decreasedValue: decreasedValue,
hint: hint,
textDirection: textDirection,
sortOrder: _effectiveSortOrder(sortKey, sortOrder),
onTap: onTap,
onLongPress: onLongPress,
onScrollLeft: onScrollLeft,
......@@ -4887,8 +4899,7 @@ class Semantics extends SingleChildRenderObjectWidget {
onPaste: onPaste,
onMoveCursorForwardByCharacter: onMoveCursorForwardByCharacter,
onMoveCursorBackwardByCharacter: onMoveCursorBackwardByCharacter,
onSetSelection: onSetSelection,
),
onSetSelection: onSetSelection,),
);
/// Creates a semantic annotation using [SemanticsProperties].
......@@ -4904,6 +4915,11 @@ class Semantics extends SingleChildRenderObjectWidget {
assert(properties != null),
super(key: key, child: child);
static SemanticsSortOrder _effectiveSortOrder(SemanticsSortKey sortKey, SemanticsSortOrder sortOrder) {
assert(sortOrder == null || sortKey == null, 'Only one of sortOrder or sortKey may be specified.');
return sortOrder ?? (sortKey != null ? new SemanticsSortOrder(key: sortKey) : null);
}
/// Contains properties used by assistive technologies to make the application
/// more accessible.
final SemanticsProperties properties;
......@@ -4927,8 +4943,8 @@ class Semantics extends SingleChildRenderObjectWidget {
/// information to the semantic tree is to introduce new explicit
/// [SemanticNode]s to the tree.
///
/// This setting is often used in combination with [isSemanticBoundary] to
/// create semantic boundaries that are either writable or not for children.
/// This setting is often used in combination with [SemanticsConfiguration.isSemanticBoundary]
/// to create semantic boundaries that are either writable or not for children.
final bool explicitChildNodes;
@override
......@@ -4946,6 +4962,7 @@ class Semantics extends SingleChildRenderObjectWidget {
decreasedValue: properties.decreasedValue,
hint: properties.hint,
textDirection: _getTextDirection(context),
sortOrder: properties.sortOrder,
onTap: properties.onTap,
onLongPress: properties.onLongPress,
onScrollLeft: properties.onScrollLeft,
......@@ -4989,6 +5006,7 @@ class Semantics extends SingleChildRenderObjectWidget {
..decreasedValue = properties.decreasedValue
..hint = properties.hint
..textDirection = _getTextDirection(context)
..sortOrder = properties.sortOrder
..onTap = properties.onTap
..onLongPress = properties.onLongPress
..onScrollLeft = properties.onScrollLeft
......
......@@ -193,11 +193,11 @@ void main() {
console.clear();
});
test('Service extensions - debugDumpSemanticsTreeInTraversalOrder', () async {
test('Service extensions - debugDumpSemanticsTreeInGeometricOrder', () async {
Map<String, String> result;
await binding.doFrame();
result = await binding.testExtension('debugDumpSemanticsTreeInTraversalOrder', <String, String>{});
result = await binding.testExtension('debugDumpSemanticsTreeInGeometricOrder', <String, String>{});
expect(result, <String, String>{});
expect(console, <String>['Semantics not collected.']);
console.clear();
......
......@@ -410,7 +410,8 @@ void main() {
new TestSemantics.rootChild(
id: expectedId,
rect: TestSemantics.fullScreen,
actions: allActions.fold(0, (int previous, SemanticsAction action) => previous | action.index)
actions: allActions.fold(0, (int previous, SemanticsAction action) => previous | action.index),
nextNodeId: -1,
),
],
);
......@@ -569,4 +570,269 @@ void main() {
semantics.dispose();
});
testWidgets('Semantics widgets built in a widget tree are sorted properly', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
int semanticsUpdateCount = 0;
tester.binding.pipelineOwner.ensureSemantics(
listener: () {
semanticsUpdateCount += 1;
}
);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Semantics(
sortKey: const CustomSortKey(0.0),
explicitChildNodes: true,
child: new Column(
children: <Widget>[
new Semantics(sortKey: const CustomSortKey(3.0), child: const Text('Label 1')),
new Semantics(sortKey: const CustomSortKey(2.0), child: const Text('Label 2')),
new Semantics(
sortKey: const CustomSortKey(1.0),
explicitChildNodes: true,
child: new Row(
children: <Widget>[
new Semantics(sortKey: const OrdinalSortKey(3.0), child: const Text('Label 3')),
new Semantics(sortKey: const OrdinalSortKey(2.0), child: const Text('Label 4')),
new Semantics(sortKey: const OrdinalSortKey(1.0), child: const Text('Label 5')),
],
),
),
],
),
),
),
);
expect(semanticsUpdateCount, 1);
expect(semantics, hasSemantics(
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
nextNodeId: 5,
children: <TestSemantics>[
new TestSemantics(
label: r'Label 1',
textDirection: TextDirection.ltr,
nextNodeId: -1,
),
new TestSemantics(
label: r'Label 2',
textDirection: TextDirection.ltr,
nextNodeId: 3,
),
new TestSemantics(
nextNodeId: 8,
children: <TestSemantics>[
new TestSemantics(
label: r'Label 3',
textDirection: TextDirection.ltr,
nextNodeId: 4,
),
new TestSemantics(
label: r'Label 4',
textDirection: TextDirection.ltr,
nextNodeId: 6,
),
new TestSemantics(
label: r'Label 5',
textDirection: TextDirection.ltr,
nextNodeId: 7,
),
],
),
],
),
],
), ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics widgets built with explicit sort orders are sorted properly', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
int semanticsUpdateCount = 0;
tester.binding.pipelineOwner.ensureSemantics(
listener: () {
semanticsUpdateCount += 1;
}
);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Column(
children: <Widget>[
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(3.0), const OrdinalSortKey(5.0)],
),
child: const Text('Label 1'),
),
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(2.0), const OrdinalSortKey(4.0)],
),
child: const Text('Label 2'),
),
new Row(
children: <Widget>[
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(3.0)],
),
child: const Text('Label 3'),
),
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(2.0)],
),
child: const Text('Label 4'),
),
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(1.0)],
),
child: const Text('Label 5'),
),
],
),
],
),
),
);
expect(semanticsUpdateCount, 1);
expect(semantics, hasSemantics(
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
label: r'Label 1',
textDirection: TextDirection.ltr,
nextNodeId: -1,
),
new TestSemantics(
label: r'Label 2',
textDirection: TextDirection.ltr,
nextNodeId: 2,
),
new TestSemantics(
label: r'Label 3',
textDirection: TextDirection.ltr,
nextNodeId: 3,
),
new TestSemantics(
label: r'Label 4',
textDirection: TextDirection.ltr,
nextNodeId: 4,
),
new TestSemantics(
label: r'Label 5',
textDirection: TextDirection.ltr,
nextNodeId: 5,
),
],
)
, ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
testWidgets('Semantics widgets built with some discarded sort orders are sorted properly', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
int semanticsUpdateCount = 0;
tester.binding.pipelineOwner.ensureSemantics(
listener: () {
semanticsUpdateCount += 1;
}
);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Semantics(
sortKey: const OrdinalSortKey(0.0),
explicitChildNodes: true,
child: new Column(
children: <Widget>[
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(3.0), const OrdinalSortKey(5.0)],
discardParentOrder: true, // Replace this one.
),
child: const Text('Label 1'),
),
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(2.0), const OrdinalSortKey(4.0)],
),
child: const Text('Label 2'),
),
new Row(
children: <Widget>[
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(3.0)],
discardParentOrder: true, // Replace this one.
),
child: const Text('Label 3'),
),
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(2.0)],
),
child: const Text('Label 4'),
),
new Semantics(
sortOrder: new SemanticsSortOrder(
keys: <SemanticsSortKey>[const CustomSortKey(1.0), const OrdinalSortKey(1.0)],
),
child: const Text('Label 5'),
),
],
),
],
),
),
),
);
expect(semanticsUpdateCount, 1);
expect(semantics, hasSemantics(
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
nextNodeId: 5,
children: <TestSemantics>[
new TestSemantics(
label: r'Label 1',
textDirection: TextDirection.ltr,
nextNodeId: 7,
),
new TestSemantics(
label: r'Label 2',
textDirection: TextDirection.ltr,
nextNodeId: -1,
),
new TestSemantics(
label: r'Label 3',
textDirection: TextDirection.ltr,
nextNodeId: 3,
),
new TestSemantics(
label: r'Label 4',
textDirection: TextDirection.ltr,
nextNodeId: 4,
),
new TestSemantics(
label: r'Label 5',
textDirection: TextDirection.ltr,
nextNodeId: 6,
),
],
),
],
), ignoreTransform: true, ignoreRect: true, ignoreId: true));
semantics.dispose();
});
}
class CustomSortKey extends OrdinalSortKey {
const CustomSortKey(double order, {String name}) : super(order, name: name);
}
......@@ -42,6 +42,7 @@ class TestSemantics {
this.decreasedValue: '',
this.hint: '',
this.textDirection,
this.nextNodeId,
this.rect,
this.transform,
this.textSelection,
......@@ -68,6 +69,7 @@ class TestSemantics {
this.decreasedValue: '',
this.hint: '',
this.textDirection,
this.nextNodeId,
this.transform,
this.textSelection,
this.children: const <TestSemantics>[],
......@@ -103,6 +105,7 @@ class TestSemantics {
this.increasedValue: '',
this.decreasedValue: '',
this.textDirection,
this.nextNodeId,
this.rect,
Matrix4 transform,
this.textSelection,
......@@ -169,6 +172,10 @@ class TestSemantics {
/// is also set.
final TextDirection textDirection;
/// The ID of the node that is next in the semantics traversal order after
/// this node.
final int nextNodeId;
/// The bounding box for this node in its coordinate system.
///
/// Convenient values are available:
......@@ -250,6 +257,8 @@ class TestSemantics {
return fail('expected node id $id to have hint "$hint" but found hint "${nodeData.hint}".');
if (textDirection != null && textDirection != nodeData.textDirection)
return fail('expected node id $id to have textDirection "$textDirection" but found "${nodeData.textDirection}".');
if (nextNodeId != null && nextNodeId != nodeData.nextNodeId)
return fail('expected node id $id to have nextNodeId "$nextNodeId" but found "${nodeData.nextNodeId}".');
if ((nodeData.label != '' || nodeData.value != '' || nodeData.hint != '' || node.increasedValue != '' || node.decreasedValue != '') && nodeData.textDirection == null)
return fail('expected node id $id, which has a label, value, or hint, to have a textDirection, but it did not.');
if (!ignoreRect && rect != nodeData.rect)
......@@ -301,6 +310,8 @@ class TestSemantics {
buf.writeln('$indent hint: \'$hint\',');
if (textDirection != null)
buf.writeln('$indent textDirection: $textDirection,');
if (nextNodeId != null)
buf.writeln('$indent nextNodeId: $nextNodeId,');
if (textSelection?.isValid == true)
buf.writeln('$indent textSelection:\n[${textSelection.start}, ${textSelection.end}],');
if (rect != null)
......@@ -483,8 +494,14 @@ class SemanticsTester {
buf.writeln(' flags: ${_flagsToSemanticsFlagExpression(nodeData.flags)},');
if (nodeData.actions != 0)
buf.writeln(' actions: ${_actionsToSemanticsActionExpression(nodeData.actions)},');
if (node.label != null && node.label.isNotEmpty)
buf.writeln(' label: r\'${node.label}\',');
if (node.label != null && node.label.isNotEmpty) {
final String escapedLabel = node.label.replaceAll('\n', r'\n');
if (escapedLabel == node.label) {
buf.writeln(' label: r\'$escapedLabel\',');
} else {
buf.writeln(' label: \'$escapedLabel\',');
}
}
if (node.value != null && node.value.isNotEmpty)
buf.writeln(' value: r\'${node.value}\',');
if (node.increasedValue != null && node.increasedValue.isNotEmpty)
......@@ -495,6 +512,8 @@ class SemanticsTester {
buf.writeln(' hint: r\'${node.hint}\',');
if (node.textDirection != null)
buf.writeln(' textDirection: ${node.textDirection},');
if (node.nextNodeId != null)
buf.writeln(' nextNodeId: ${node.nextNodeId},');
if (node.hasChildren) {
buf.writeln(' children: <TestSemantics>[');
......
......@@ -105,12 +105,15 @@ void _tests() {
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
nextNodeId: 4,
children: <TestSemantics>[
new TestSemantics(
nextNodeId: 2,
children: <TestSemantics>[
new TestSemantics(
label: r'Plain text',
textDirection: TextDirection.ltr,
nextNodeId: 3,
),
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.hasCheckedState, SemanticsFlag.isChecked, SemanticsFlag.isSelected],
......@@ -121,6 +124,7 @@ void _tests() {
decreasedValue: r'test-decreasedValue',
hint: r'test-hint',
textDirection: TextDirection.rtl,
nextNodeId: -1,
),
],
),
......
......@@ -184,9 +184,9 @@ class FlutterDevice {
await view.uiIsolate.flutterDebugDumpLayerTree();
}
Future<Null> debugDumpSemanticsTreeInTraversalOrder() async {
Future<Null> debugDumpSemanticsTreeInGeometricOrder() async {
for (FlutterView view in views)
await view.uiIsolate.flutterDebugDumpSemanticsTreeInTraversalOrder();
await view.uiIsolate.flutterDebugDumpSemanticsTreeInGeometricOrder();
}
Future<Null> debugDumpSemanticsTreeInInverseHitTestOrder() async {
......@@ -499,10 +499,10 @@ abstract class ResidentRunner {
await device.debugDumpLayerTree();
}
Future<Null> _debugDumpSemanticsTreeInTraversalOrder() async {
Future<Null> _debugDumpSemanticsTreeInGeometricOrder() async {
await refreshViews();
for (FlutterDevice device in flutterDevices)
await device.debugDumpSemanticsTreeInTraversalOrder();
await device.debugDumpSemanticsTreeInGeometricOrder();
}
Future<Null> _debugDumpSemanticsTreeInInverseHitTestOrder() async {
......@@ -685,7 +685,7 @@ abstract class ResidentRunner {
}
} else if (character == 'S') {
if (supportsServiceProtocol) {
await _debugDumpSemanticsTreeInTraversalOrder();
await _debugDumpSemanticsTreeInGeometricOrder();
return true;
}
} else if (character == 'U') {
......@@ -826,12 +826,12 @@ abstract class ResidentRunner {
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".');
if (isRunningDebug) {
printStatus('For layers (debugDumpLayerTree), use "L"; accessibility (debugDumpSemantics), "S" (traversal order) or "U" (inverse hit test order).');
printStatus('For layers (debugDumpLayerTree), use "L"; for accessibility (debugDumpSemantics), use "S" (for geometric order) or "U" (for inverse hit test order).');
printStatus('To toggle the widget inspector (WidgetsApp.showWidgetInspectorOverride), press "i".');
printStatus('To toggle the display of construction lines (debugPaintSizeEnabled), press "p".');
printStatus('To simulate different operating systems, (defaultTargetPlatform), press "o".');
} else {
printStatus('To dump the accessibility tree (debugDumpSemantics), press "S" (for traversal order) or "U" (for inverse hit test order).');
printStatus('To dump the accessibility tree (debugDumpSemantics), press "S" (for geometric order) or "U" (for inverse hit test order).');
}
printStatus('To display the performance overlay (WidgetsApp.showPerformanceOverlay), press "P".');
}
......
......@@ -1146,8 +1146,8 @@ class Isolate extends ServiceObjectOwner {
return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpLayerTree', timeout: kLongRequestTimeout);
}
Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInTraversalOrder() {
return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInTraversalOrder', timeout: kLongRequestTimeout);
Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInGeometricOrder() {
return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInGeometricOrder', timeout: kLongRequestTimeout);
}
Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInInverseHitTestOrder() {
......
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