这是indexloc提供的服务,不要输入任何密码
Skip to content

[Focus] defer autofocus resolution to _applyFocusChange #85562

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 4, 2021

Conversation

LongCatIsLooong
Copy link
Contributor

Fixes: #84845.
also fixes a potential crash in FocusState.didUpdateWdiget.

when FocusScopeNode.autofocus is called, the FocusScopeNode may have a
focusedChild that's about to be detached.

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 feature I am adding, or Hixie said the 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.

Fixes: flutter#84845.
also fixes a potential crash in FocusState.didUpdateWdiget.

when `FocusScopeNode.autofocus` is called, the FocusScopeNode may have a
focusedChild that's about to be detached.
@flutter-dashboard flutter-dashboard bot added the framework flutter/packages/flutter repository. See also f: labels. label Jun 29, 2021
@google-cla google-cla bot added the cla: yes label Jun 29, 2021
Copy link
Contributor Author

@LongCatIsLooong LongCatIsLooong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a different note, have we tried using LinkedHashList for storing FocusNode._ancestors and/or FocusScopeNode._focusedChildren?

assert(_manager != null);
assert(_focusDebug('Autofocus scheduled for $node: scope $this'));
_manager?._pendingAutofocuses.add(_Autofocus(scope: this, autofocusNode: node));
_manager?._markNeedsUpdate();
}

@override
void _doRequestFocus({required bool findFirstFocus}) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method still does the same thing. Just trying to avoid runtime type checks like primaryFocus is FocusScopeNode.

@@ -1756,6 +1759,13 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
void _applyFocusChange() {
_haveScheduledUpdate = false;
final FocusNode? previousFocus = _primaryFocus;
for (final _Autofocus autofocus in _pendingAutofocuses) {
if (identical(autofocus.scope._manager, this)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't going to work well if a subtree involved changes manager after calling FocusScopeNode.autofocus since we keep the information in the manager. FocusManager has a public constructor but I guess most of the time we only use the singleton from bindings.

@@ -657,7 +657,8 @@ class _FocusState extends State<Focus> {
focusNode.descendantsAreFocusable = widget.descendantsAreFocusable;
} else {
_focusAttachment!.detach();
focusNode.removeListener(_handleFocusChanged);
Copy link
Contributor Author

@LongCatIsLooong LongCatIsLooong Jun 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This getter crashes if we supply an external FocusNode in the old widget and do not specify one in the new widget.


assert(_manager != null);
assert(_focusDebug('Autofocus scheduled for $node: scope $this'));
_manager?._pendingAutofocuses.add(_Autofocus(scope: this, autofocusNode: node));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should remove the node if it was disposed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The node and the scope? I think they're supposed to be owned by widgets so FocusManager shouldn't call dispose on FocusNodes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's correct. The owner of the FocusNode has the responsibility to dispose of it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the node was disposed before applying the autofocus.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see. But I'm not sure if there's a good way to check if a FocusNode is already disposed. It seems fine to keep a disposed FocusNode around? In _applyFocusChange if either of these 2 nodes are detached from the tree then we throw the entry away anyways.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a node is disposed then it's guaranteed to be detached (reattaching disposed nodes back to the tree shouldn't be allowed). In _applyFocusChange we do check if any of the nodes are already detached and discard them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That reminds me we didn't have tests to verify the first come first served behavior. I'll add that in a bit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we scroll the list ,the primary focus node will be changed. A little strange I think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's only true if you have autofocus set to true on more than one widget, which, as I said above, isn't well defined (mainly because of situations like this where things are built later).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.

@@ -1756,6 +1759,13 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
void _applyFocusChange() {
_haveScheduledUpdate = false;
final FocusNode? previousFocus = _primaryFocus;
for (final _Autofocus autofocus in _pendingAutofocuses) {
if (identical(autofocus.scope._manager, this)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LongCatIsLooong did you mean this line? For the test I posted this is true .

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh right. I thought detaching removes the manager.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can do this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After looking at FocusNode.detach, I think the idea is the widget tree (or whatever that the focus tree is going to attach to & and manage focus for) is responsible for calling "reparent" and "detach" so each focus node has the right parent and manager. So I think these checks should be sufficient.

@xu-baolin you mean this is true for something in the first row? I'd expect those elements to have already been disposed of (along with their focus nodes)?

Copy link
Member

@xu-baolin xu-baolin Jul 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The node is disposed but the scope node not.

if (identical(autofocus.scope._manager, this) && autofocus.autofocusNode._parent!= null)

@Piinks Piinks added the f: focus Focus traversal, gaining or losing focus label Jun 30, 2021
@LongCatIsLooong
Copy link
Contributor Author

@xu-baolin I added some additional checks to make sure the scope is attached to the focus tree.

),
);

// _applyFocusChanges will be called before persistentCallbacks,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. '_applyFocusChanges' should be '_applyFocusChange'.
  2. I remember the microTask should be called after the persistentCallbacks in a frame, right? Or do you mean before the next frame?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The microtask queue is also drained before the persistent callbacks.

Copy link
Member

@xu-baolin xu-baolin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@gspencergoog
Copy link
Contributor

@LongCatIsLooong Are you still planning to commit this?

@LongCatIsLooong
Copy link
Contributor Author

@gspencergoog yeah but there appears to be an analyzer problem that I believe has nothing to do with the PR. Does the approach look good to you?

@xu-baolin
Copy link
Member

@LongCatIsLooong Can we reland #82671 now?#84845 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
f: focus Focus traversal, gaining or losing focus framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

FocusNode.autofocus should not skip when the previously focused child in the focus scope is about to unmount.
5 participants