mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Remove FocusTraversalGroups from the final sorted list of descendants. (#74758)
This fixes a subtle bug in the focus traversal that would leave unfocusable FocusTraversalGroup nodes in the list of sorted nodes to be traversed, causing an assert to fire if you nested a FocusTraversalGroup in another one and made its children unfocusable.
This commit is contained in:
parent
541188c7cf
commit
c05f623c6b
@ -335,10 +335,23 @@ abstract class FocusTraversalPolicy with Diagnosticable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Visit the children of the scope.
|
||||||
visitGroups(groups[scopeGroupMarker?.focusNode]!);
|
visitGroups(groups[scopeGroupMarker?.focusNode]!);
|
||||||
|
|
||||||
|
// Remove the FocusTraversalGroup nodes themselves, which aren't focusable.
|
||||||
|
// They were left in above because they were needed to find their members
|
||||||
|
// during sorting.
|
||||||
|
sortedDescendants.removeWhere((FocusNode node) {
|
||||||
|
return !node.canRequestFocus || node.skipTraversal;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sanity check to make sure that the algorithm above doesn't diverge from
|
||||||
|
// the one in FocusScopeNode.traversalDescendants in terms of which nodes it
|
||||||
|
// finds.
|
||||||
assert(
|
assert(
|
||||||
sortedDescendants.length <= scope.traversalDescendants.length && sortedDescendants.toSet().difference(scope.traversalDescendants.toSet()).isEmpty,
|
sortedDescendants.length <= scope.traversalDescendants.length && sortedDescendants.toSet().difference(scope.traversalDescendants.toSet()).isEmpty,
|
||||||
'sorted descendants contains more nodes than it should: (${sortedDescendants.toSet().difference(scope.traversalDescendants.toSet())})'
|
'Sorted descendants contains different nodes than FocusScopeNode.traversalDescendants would. '
|
||||||
|
'These are the different nodes: ${sortedDescendants.toSet().difference(scope.traversalDescendants.toSet())}'
|
||||||
);
|
);
|
||||||
return sortedDescendants;
|
return sortedDescendants;
|
||||||
}
|
}
|
||||||
|
@ -2088,6 +2088,82 @@ void main() {
|
|||||||
expect(containerNode.hasFocus, isFalse);
|
expect(containerNode.hasFocus, isFalse);
|
||||||
expect(unfocusableNode.hasFocus, isFalse);
|
expect(unfocusableNode.hasFocus, isFalse);
|
||||||
});
|
});
|
||||||
|
testWidgets("Nested FocusTraversalGroup with unfocusable children doesn't assert.", (WidgetTester tester) async {
|
||||||
|
final GlobalKey key1 = GlobalKey(debugLabel: '1');
|
||||||
|
final GlobalKey key2 = GlobalKey(debugLabel: '2');
|
||||||
|
final FocusNode focusNode = FocusNode();
|
||||||
|
bool? gotFocus;
|
||||||
|
await tester.pumpWidget(
|
||||||
|
FocusTraversalGroup(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Focus(
|
||||||
|
autofocus: true,
|
||||||
|
child: Container(),
|
||||||
|
),
|
||||||
|
FocusTraversalGroup(
|
||||||
|
descendantsAreFocusable: false,
|
||||||
|
child: Focus(
|
||||||
|
onFocusChange: (bool focused) => gotFocus = focused,
|
||||||
|
child: Focus(
|
||||||
|
key: key1,
|
||||||
|
focusNode: focusNode,
|
||||||
|
child: Container(key: key2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Element childWidget = tester.element(find.byKey(key1));
|
||||||
|
final FocusNode unfocusableNode = Focus.of(childWidget);
|
||||||
|
final Element containerWidget = tester.element(find.byKey(key2));
|
||||||
|
final FocusNode containerNode = Focus.of(containerWidget);
|
||||||
|
|
||||||
|
await tester.pump();
|
||||||
|
primaryFocus!.nextFocus();
|
||||||
|
|
||||||
|
expect(gotFocus, isNull);
|
||||||
|
expect(containerNode.hasFocus, isFalse);
|
||||||
|
expect(unfocusableNode.hasFocus, isFalse);
|
||||||
|
|
||||||
|
containerNode.requestFocus();
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(gotFocus, isNull);
|
||||||
|
expect(containerNode.hasFocus, isFalse);
|
||||||
|
expect(unfocusableNode.hasFocus, isFalse);
|
||||||
|
});
|
||||||
|
testWidgets("Empty FocusTraversalGroup doesn't cause an exception.", (WidgetTester tester) async {
|
||||||
|
final GlobalKey key = GlobalKey(debugLabel: 'Test Key');
|
||||||
|
final FocusNode focusNode = FocusNode(debugLabel: 'Test Node');
|
||||||
|
await tester.pumpWidget(
|
||||||
|
FocusTraversalGroup(
|
||||||
|
child: Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
FocusTraversalGroup(
|
||||||
|
child: Container(key: key),
|
||||||
|
),
|
||||||
|
Focus(
|
||||||
|
focusNode: focusNode,
|
||||||
|
autofocus: true,
|
||||||
|
child: Container(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pump();
|
||||||
|
primaryFocus!.nextFocus();
|
||||||
|
await tester.pump();
|
||||||
|
expect(primaryFocus, equals(focusNode));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
group(RawKeyboardListener, () {
|
group(RawKeyboardListener, () {
|
||||||
testWidgets('Raw keyboard listener introduces a Semantics node by default', (WidgetTester tester) async {
|
testWidgets('Raw keyboard listener introduces a Semantics node by default', (WidgetTester tester) async {
|
||||||
|
Loading…
Reference in New Issue
Block a user