diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 5dadd8a2e55..a4270f51cb5 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -f5a4a9378740c3d5996583a9ed1f7e28ff08ee85 +36a9c343b7c5ec361b60c37d154071146daeeea7 diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index 5852abec022..079740d1829 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -92,6 +92,7 @@ class SemanticsData extends Diagnosticable { @required this.decreasedValue, @required this.hint, @required this.textDirection, + @required this.nextNodeId, @required this.previousNodeId, @required this.rect, @required this.textSelection, @@ -151,6 +152,10 @@ class SemanticsData extends Diagnosticable { /// [increasedValue], and [decreasedValue]. final TextDirection textDirection; + /// The index indicating the ID of the next node in the traversal order after + /// this node for the platform's accessibility services. + final int nextNodeId; + /// The index indicating the ID of the previous node in the traversal order before /// this node for the platform's accessibility services. final int previousNodeId; @@ -237,6 +242,7 @@ class SemanticsData extends Diagnosticable { properties.add(new StringProperty('decreasedValue', decreasedValue, defaultValue: '')); properties.add(new StringProperty('hint', hint, defaultValue: '')); properties.add(new EnumProperty('textDirection', textDirection, defaultValue: null)); + properties.add(new IntProperty('nextNodeId', nextNodeId, defaultValue: null)); properties.add(new IntProperty('previousNodeId', previousNodeId, defaultValue: null)); if (textSelection?.isValid == true) properties.add(new MessageProperty('textSelection', '[${textSelection.start}, ${textSelection.end}]')); @@ -258,6 +264,7 @@ class SemanticsData extends Diagnosticable { && typedOther.decreasedValue == decreasedValue && typedOther.hint == hint && typedOther.textDirection == textDirection + && typedOther.nextNodeId == nextNodeId && typedOther.previousNodeId == previousNodeId && typedOther.rect == rect && setEquals(typedOther.tags, tags) @@ -269,7 +276,7 @@ class SemanticsData extends Diagnosticable { } @override - int get hashCode => ui.hashValues(flags, actions, label, value, increasedValue, decreasedValue, hint, textDirection, previousNodeId, rect, tags, textSelection, scrollPosition, scrollExtentMax, scrollExtentMin, transform); + int get hashCode => ui.hashValues(flags, actions, label, value, increasedValue, decreasedValue, hint, textDirection, nextNodeId, previousNodeId, rect, tags, textSelection, scrollPosition, scrollExtentMax, scrollExtentMin, transform); } class _SemanticsDiagnosticableNode extends DiagnosticableNode { @@ -1067,10 +1074,30 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { /// The sort order for ordering the traversal of [SemanticsNode]s by the /// platform's accessibility services (e.g. VoiceOver on iOS and TalkBack on - /// Android). This is used to determine the [previousNodeId] during a semantics update. + /// Android). This is used to determine the [nextNodeId] and [previousNodeId] + /// during a semantics update. SemanticsSortOrder _sortOrder; SemanticsSortOrder get sortOrder => _sortOrder; + /// The ID of the next node in the traversal order after this node. + /// + /// Only valid after at least one semantics update has been built. + /// + /// This is the value passed to the engine to tell it what the order + /// should be for traversing semantics nodes. + /// + /// If this is set to -1, it will indicate that there is no next node to + /// the engine (i.e. this is the last node in the sort order). When it is + /// null, it means that no semantics update has been built yet. + int _nextNodeId; + void _updateNextNodeId(int value) { + if (value == _nextNodeId) + return; + _nextNodeId = value; + _markDirty(); + } + int get nextNodeId => _nextNodeId; + /// The ID of the previous node in the traversal order before this node. /// /// Only valid after at least one semantics update has been built. @@ -1194,6 +1221,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { String increasedValue = _increasedValue; String decreasedValue = _decreasedValue; TextDirection textDirection = _textDirection; + int nextNodeId = _nextNodeId; int previousNodeId = _previousNodeId; Set mergedTags = tags == null ? null : new Set.from(tags); TextSelection textSelection = _textSelection; @@ -1207,6 +1235,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { flags |= node._flags; actions |= node._actionsAsBits; textDirection ??= node._textDirection; + nextNodeId ??= node._nextNodeId; previousNodeId ??= node._previousNodeId; textSelection ??= node._textSelection; scrollPosition ??= node._scrollPosition; @@ -1247,6 +1276,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { decreasedValue: decreasedValue, hint: hint, textDirection: textDirection, + nextNodeId: nextNodeId, previousNodeId: previousNodeId, rect: rect, transform: transform, @@ -1289,6 +1319,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { increasedValue: data.increasedValue, hint: data.hint, textDirection: data.textDirection, + nextNodeId: data.nextNodeId, previousNodeId: data.previousNodeId, textSelectionBase: data.textSelection != null ? data.textSelection.baseOffset : -1, textSelectionExtent: data.textSelection != null ? data.textSelection.extentOffset : -1, @@ -1363,6 +1394,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { properties.add(new StringProperty('decreasedValue', _decreasedValue, defaultValue: '')); properties.add(new StringProperty('hint', _hint, defaultValue: '')); properties.add(new EnumProperty('textDirection', _textDirection, defaultValue: null)); + properties.add(new IntProperty('nextNodeId', _nextNodeId, defaultValue: null)); properties.add(new IntProperty('previousNodeId', _previousNodeId, defaultValue: null)); properties.add(new DiagnosticsProperty('sortOrder', sortOrder, defaultValue: null)); if (_textSelection?.isValid == true) @@ -1539,10 +1571,10 @@ class SemanticsOwner extends ChangeNotifier { super.dispose(); } - // Updates the previousNodeId IDs on the semantics nodes. These IDs are used - // on the platform side to order the nodes for traversal by the accessibility - // services. If the previousNodeId for a node changes, the node will be marked as - // dirty. + // Updates the nextNodeId and previousNodeId IDs on the semantics nodes. These + // IDs are used on the platform side to order the nodes for traversal by the + // accessibility services. If the nextNodeId or previousNodeId for a node + // changes, the node will be marked as dirty. void _updateTraversalOrder() { final List<_TraversalSortNode> nodesInSemanticsTraversalOrder = <_TraversalSortNode>[]; SemanticsSortOrder currentSortOrder = new SemanticsSortOrder(keys: []); @@ -1577,12 +1609,20 @@ class SemanticsOwner extends ChangeNotifier { return true; } rootSemanticsNode.visitChildren(visitor); + + if (nodesInSemanticsTraversalOrder.isEmpty) + return; + nodesInSemanticsTraversalOrder.sort(); - int previousNodeId = -1; - for (_TraversalSortNode node in nodesInSemanticsTraversalOrder) { - node.node._updatePreviousNodeId(previousNodeId); - previousNodeId = node.node.id; + _TraversalSortNode node = nodesInSemanticsTraversalOrder.removeLast(); + node.node._updateNextNodeId(-1); + while (nodesInSemanticsTraversalOrder.isNotEmpty) { + final _TraversalSortNode previousNode = nodesInSemanticsTraversalOrder.removeLast(); + node.node._updatePreviousNodeId(previousNode.node.id); + previousNode.node._updateNextNodeId(node.node.id); + node = previousNode; } + node.node._updatePreviousNodeId(-1); } /// Update the semantics using [Window.updateSemantics]. diff --git a/packages/flutter/test/semantics/semantics_test.dart b/packages/flutter/test/semantics/semantics_test.dart index 7647d7210b2..df8abd681af 100644 --- a/packages/flutter/test/semantics/semantics_test.dart +++ b/packages/flutter/test/semantics/semantics_test.dart @@ -348,7 +348,7 @@ void main() { expect( minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden), - 'SemanticsNode#1(owner: null, isMergedIntoParent: false, mergeAllDescendantsIntoThisNode: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isInMutuallyExcusiveGroup: false, isSelected: false, isFocused: false, isButton: false, isTextField: false, invisible, label: "", value: "", increasedValue: "", decreasedValue: "", hint: "", textDirection: null, previousNodeId: null, sortOrder: null, scrollExtentMin: null, scrollPosition: null, scrollExtentMax: null)\n' + 'SemanticsNode#1(owner: null, isMergedIntoParent: false, mergeAllDescendantsIntoThisNode: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isInMutuallyExcusiveGroup: false, isSelected: false, isFocused: false, isButton: false, isTextField: false, invisible, label: "", value: "", increasedValue: "", decreasedValue: "", hint: "", textDirection: null, nextNodeId: null, previousNodeId: null, sortOrder: null, scrollExtentMin: null, scrollPosition: null, scrollExtentMax: null)\n' ); final SemanticsConfiguration config = new SemanticsConfiguration() diff --git a/packages/flutter/test/widgets/semantics_test.dart b/packages/flutter/test/widgets/semantics_test.dart index 9c11b5a4620..ae925ceda12 100644 --- a/packages/flutter/test/widgets/semantics_test.dart +++ b/packages/flutter/test/widgets/semantics_test.dart @@ -417,6 +417,7 @@ void main() { rect: TestSemantics.fullScreen, actions: allActions.fold(0, (int previous, SemanticsAction action) => previous | action.index), previousNodeId: -1, + nextNodeId: -1, ), ], ); @@ -613,36 +614,51 @@ void main() { expect(semanticsUpdateCount, 1); expect(semantics, hasSemantics( new TestSemantics( + id: 0, children: [ new TestSemantics( + id: 2, + nextNodeId: 5, previousNodeId: -1, children: [ new TestSemantics( + id: 3, label: r'Label 1', textDirection: TextDirection.ltr, + nextNodeId: -1, previousNodeId: 4, ), new TestSemantics( + id: 4, label: r'Label 2', textDirection: TextDirection.ltr, + nextNodeId: 3, previousNodeId: 6, ), new TestSemantics( + id: 5, + nextNodeId: 8, previousNodeId: 2, children: [ new TestSemantics( + id: 6, label: r'Label 3', textDirection: TextDirection.ltr, + nextNodeId: 4, previousNodeId: 7, ), new TestSemantics( + id: 7, label: r'Label 4', textDirection: TextDirection.ltr, + nextNodeId: 6, previousNodeId: 8, ), new TestSemantics( + id: 8, label: r'Label 5', textDirection: TextDirection.ltr, + nextNodeId: 7, previousNodeId: 5, ), ], @@ -650,7 +666,7 @@ void main() { ], ), ], - ), ignoreTransform: true, ignoreRect: true, ignoreId: true), + ), ignoreTransform: true, ignoreRect: true), ); semantics.dispose(); }); @@ -713,26 +729,31 @@ void main() { new TestSemantics( label: r'Label 1', textDirection: TextDirection.ltr, + nextNodeId: -1, previousNodeId: 3, ), new TestSemantics( label: r'Label 2', textDirection: TextDirection.ltr, + nextNodeId: 2, previousNodeId: 4, ), new TestSemantics( label: r'Label 3', textDirection: TextDirection.ltr, + nextNodeId: 3, previousNodeId: 5, ), new TestSemantics( label: r'Label 4', textDirection: TextDirection.ltr, + nextNodeId: 4, previousNodeId: 6, ), new TestSemantics( label: r'Label 5', textDirection: TextDirection.ltr, + nextNodeId: 5, previousNodeId: -1, ), ], @@ -802,31 +823,37 @@ void main() { new TestSemantics( children: [ new TestSemantics( + nextNodeId: 5, previousNodeId: -1, children: [ new TestSemantics( label: r'Label 1', textDirection: TextDirection.ltr, + nextNodeId: 7, previousNodeId: 5, ), new TestSemantics( label: r'Label 2', textDirection: TextDirection.ltr, + nextNodeId: -1, previousNodeId: 6, ), new TestSemantics( label: r'Label 3', textDirection: TextDirection.ltr, + nextNodeId: 3, previousNodeId: 2, ), new TestSemantics( label: r'Label 4', textDirection: TextDirection.ltr, + nextNodeId: 4, previousNodeId: 7, ), new TestSemantics( label: r'Label 5', textDirection: TextDirection.ltr, + nextNodeId: 6, previousNodeId: 3, ), ], diff --git a/packages/flutter/test/widgets/semantics_tester.dart b/packages/flutter/test/widgets/semantics_tester.dart index b6a5233ec66..440d9d4f8c1 100644 --- a/packages/flutter/test/widgets/semantics_tester.dart +++ b/packages/flutter/test/widgets/semantics_tester.dart @@ -43,6 +43,7 @@ class TestSemantics { this.decreasedValue: '', this.hint: '', this.textDirection, + this.nextNodeId, this.previousNodeId, this.rect, this.transform, @@ -71,6 +72,7 @@ class TestSemantics { this.hint: '', this.textDirection, this.previousNodeId, + this.nextNodeId, this.transform, this.textSelection, this.children: const [], @@ -106,6 +108,7 @@ class TestSemantics { this.increasedValue: '', this.decreasedValue: '', this.textDirection, + this.nextNodeId, this.previousNodeId, this.rect, Matrix4 transform, @@ -173,6 +176,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 ID of the node that is previous in the semantics traversal order before /// this node. final int previousNodeId; @@ -258,6 +265,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 (previousNodeId != null && previousNodeId != nodeData.previousNodeId) return fail('expected node id $id to have previousNodeId "$previousNodeId" but found "${nodeData.previousNodeId}".'); if ((nodeData.label != '' || nodeData.value != '' || nodeData.hint != '' || node.increasedValue != '' || node.decreasedValue != '') && nodeData.textDirection == null) @@ -311,6 +320,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 (previousNodeId != null) buf.writeln('$indent previousNodeId: $previousNodeId,'); if (textSelection?.isValid == true) @@ -522,6 +533,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.previousNodeId != null) buf.writeln(' previousNodeId: ${node.previousNodeId},'); diff --git a/packages/flutter/test/widgets/semantics_tester_generateTestSemanticsExpressionForCurrentSemanticsTree_test.dart b/packages/flutter/test/widgets/semantics_tester_generateTestSemanticsExpressionForCurrentSemanticsTree_test.dart index 4afd069f457..59a189545d2 100644 --- a/packages/flutter/test/widgets/semantics_tester_generateTestSemanticsExpressionForCurrentSemanticsTree_test.dart +++ b/packages/flutter/test/widgets/semantics_tester_generateTestSemanticsExpressionForCurrentSemanticsTree_test.dart @@ -105,14 +105,17 @@ void _tests() { new TestSemantics( children: [ new TestSemantics( + nextNodeId: 4, previousNodeId: -1, children: [ new TestSemantics( + nextNodeId: 2, previousNodeId: 1, children: [ new TestSemantics( label: r'Plain text', textDirection: TextDirection.ltr, + nextNodeId: 3, previousNodeId: 4, ), new TestSemantics( @@ -124,6 +127,7 @@ void _tests() { decreasedValue: r'test-decreasedValue', hint: r'test-hint', textDirection: TextDirection.rtl, + nextNodeId: -1, previousNodeId: 2, ), ],