From f2050e8a10cf46a18c47ad11a8494c5af929e153 Mon Sep 17 00:00:00 2001 From: chunhtai <47866232+chunhtai@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:17:52 -0700 Subject: [PATCH] Fixes focus traversal crash if the current node can't request focus (#134954) fixes https://github.com/flutter/flutter/issues/134854 ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [Features we expect every widget to implement]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat --- .../lib/src/widgets/focus_traversal.dart | 4 ++- .../test/widgets/focus_traversal_test.dart | 35 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/widgets/focus_traversal.dart b/packages/flutter/lib/src/widgets/focus_traversal.dart index 203e13252958..1d01055b914a 100644 --- a/packages/flutter/lib/src/widgets/focus_traversal.dart +++ b/packages/flutter/lib/src/widgets/focus_traversal.dart @@ -433,7 +433,9 @@ abstract class FocusTraversalPolicy with Diagnosticable { // finds. assert((){ final Set difference = sortedDescendants.toSet().difference(scope.traversalDescendants.toSet()); - if (currentNode.skipTraversal) { + if (currentNode.skipTraversal || !currentNode.canRequestFocus) { + // The scope.traversalDescendants will not contain currentNode if it + // skips traversal or not focusable. assert( difference.length == 1 && difference.contains(currentNode), 'Sorted descendants contains different nodes than FocusScopeNode.traversalDescendants would. ' diff --git a/packages/flutter/test/widgets/focus_traversal_test.dart b/packages/flutter/test/widgets/focus_traversal_test.dart index d8ccbbc8389a..5b2abf4cf25e 100644 --- a/packages/flutter/test/widgets/focus_traversal_test.dart +++ b/packages/flutter/test/widgets/focus_traversal_test.dart @@ -596,6 +596,41 @@ void main() { expect(scope.hasFocus, isTrue); }); + testWidgetsWithLeakTracking('Requesting nextFocus on node focuses its descendant', (WidgetTester tester) async { + for (final bool canRequestFocus in {true, false}) { + final FocusNode node1 = FocusNode(); + final FocusNode node2 = FocusNode(); + addTearDown(() { + node1.dispose(); + node2.dispose(); + }); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: FocusTraversalGroup( + policy: ReadingOrderTraversalPolicy(), + child: FocusScope( + child: Focus( + focusNode: node1, + canRequestFocus: canRequestFocus, + child: Focus( + focusNode: node2, + child: Container(), + ), + ), + ), + ), + ), + ); + + final bool didFindNode = node1.nextFocus(); + await tester.pump(); + expect(didFindNode, isTrue); + expect(node1.hasPrimaryFocus, isFalse); + expect(node2.hasPrimaryFocus, isTrue); + } + }); + testWidgetsWithLeakTracking('Move reading focus to previous node.', (WidgetTester tester) async { final GlobalKey key1 = GlobalKey(debugLabel: '1'); final GlobalKey key2 = GlobalKey(debugLabel: '2');