Repository navigation
Wrap routing in FocusScope in WidgetsApp.router - #192601
Conversation
In flutter#109702, Navigator's internal FocusScopeNode was removed and hoisted into WidgetsApp, but only inside the `else if (_usesNavigator)` branch. As a result, apps using `WidgetsApp.router` (including `MaterialApp.router` and `CupertinoApp.router` with `RouterConfig` or `RouterDelegate`) lacked the enclosing `FocusScope(debugLabel: 'Navigator Scope', autofocus: true)`. On Flutter Web with semantics enabled, when widgets such as `Autocomplete` display an `OverlayPortal`, the semantics compiler reparents the route node under a newly synthesized container node. Without an enclosing `FocusScope`, moving the route's semantics node detaches it from its previous DOM parent, triggering a native browser blur event on the focused `<input>`. The text field loses focus immediately after typing a single character, causing the options overlay to dismiss. This change wraps `routing` in `FocusScope` whenever `routing != null` in `WidgetsApp`, ensuring consistent focus scope hierarchy across `_usesNavigator`, `_usesRouterWithConfig`, and `_usesRouterWithDelegates`. Fixes flutter#187561
There was a problem hiding this comment.
Code Review
This pull request modifies WidgetsApp to wrap the routing widget in a FocusScope regardless of whether it uses a Navigator or a Router. Corresponding tests have been added to verify that WidgetsApp.router, WidgetsApp.router with routerConfig, and WidgetsApp with a navigator correctly wrap their routing in a FocusScope. There are no review comments, and I have no feedback to provide.
- Assert `focusScope.child` type across all three routing tests. - Verify `Navigator.focusNode.enclosingScope` attaches to 'Navigator Scope' when built via router or navigator. - Add test verifying `WidgetsApp.router` passes the enclosing `FocusScope` as `child` to `builder`. - Update `WidgetsApp.builder` docstring to clarify that `child` is enclosed by `FocusScope`.
|
Looks good to me but I'd like for someone from the framework team to take a look as well. |
chunhtai
left a comment
There was a problem hiding this comment.
change LGTM, but it is the side effect of wrapping focusscope that fixes the semantics tree structure, we should have a test to make sure the semantics tree is correct
Verify that WidgetsApp.router and WidgetsApp with navigator generate matching semantics tree structures with an enclosing FocusScope node.
- Add semantics tree structure test for WidgetsApp.router with routerConfig. - Verify route semantics node stability upon dynamic overlay insertion. - Clarify FocusScope explicitChildNodes documentation comments.
|
@chunhtai -fixed landed! include more tests |
…mantics Replace full-tree TestSemantics.root assertions with targeted tester.semantics.find spot checks on the FocusScope and route semantics nodes.
chunhtai
left a comment
There was a problem hiding this comment.
LGTM, minus the comment
|
autosubmit label was removed for flutter/flutter/192601, because - The status or check suite ci.yaml validation has failed. Please fix the issues identified (or deflake) before re-applying this label. |
…st (flutter#192995) In flutter#192601, the `'WidgetsApp.router produces expected semantics tree structure'` test added in `packages/flutter/test/widgets/app_test.dart` registered `addTearDown(entry.remove)` for its `OverlayEntry` without calling `entry.dispose()`. Because `OverlayEntry` allocates an internal `ValueNotifier<_OverlayEntryWidgetState?>` that is only disposed in `OverlayEntry.dispose()`, `Windows framework_tests_widgets_leak_tracking` failed in `tearDownAll` with 2 undisposed objects (`OverlayEntry` and `ValueNotifier<_OverlayEntryWidgetState?>`). This change updates the tear-down callback to call both `entry.remove()` and `entry.dispose()`, matching `packages/flutter/test/widgets/overlay_test.dart`. Fixes flutter#192993 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant in-code documentation (doc comments with `///`). - [x] If this PR introduces a new feature or capability, I created and linked a website documentation issue or PR in [flutter/website] (or verified none is needed). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [flutter/website]: https://github.com/flutter/website [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
In #109702, Navigator's internal
FocusScopeNodewas removed and hoisted intoWidgetsApp, but only inside theelse if (_usesNavigator)branch. As a result, apps usingWidgetsApp.router(includingMaterialApp.routerandCupertinoApp.routerwithRouterConfigorRouterDelegate) lacked the enclosingFocusScope(debugLabel: 'Navigator Scope', autofocus: true).On Flutter Web with semantics enabled (
ensureSemantics()), when widgets such asAutocompleteshow anOverlayPortal, the semantics compiler reparents the route node under a newly synthesized container node. Without an enclosingFocusScope, moving the route's semantics node detaches it from its previous DOM parent, triggering a native browser blur event on any focused<input>. The text field loses focus immediately after typing a single character, causing the options overlay to dismiss.This change wraps
routinginFocusScopewheneverrouting != nullinWidgetsApp, ensuring consistent focus scope hierarchy across_usesNavigator,_usesRouterWithConfig, and_usesRouterWithDelegates.Fixes #187561
Pre-launch Checklist
///).