Repository navigation
[web] Do not stop pruning a detached semantics subtree at a reparented node - #195842
auto-submit[bot] merged 4 commits into
Conversation
…d node Fixes flutter#194420 When a node is detached from its parent, `EngineSemanticsOwner._finalizeTree` walks the detached subtree with `_visitDepthFirstInTraversalOrderCanSkipSubtree` to remove every descendant that was not reparented elsewhere. The callback returns false for a reparented node so that its subtree is skipped (it moved along with the node). The visitor, however, recursed into its children with `_visitDepthFirstInTraversalOrder`, whose contract is "stop the whole traversal when the callback returns false". So for any node two or more levels below the detachment root, a reparented child ended the walk of its parent's child list: later siblings that were *not* reparented were never visited, never disposed, and stayed in `_semanticsTree`. In debug mode the post-update consistency check then throws `Bad state: Semantics node map was inconsistent after update` on that update and on every update after it; in release mode the orphaned nodes (with their DOM elements and roles) leak and stay registered in the map under their old ids. This is what happens when a `GlobalKey`'d widget is moved to a new parent while its old ancestors and an old later sibling are removed in the same frame: `0 -> 1 -> 2 -> [3, 4]` becomes `0 -> 5 -> 6 -> 3`, and #4 was left behind. Recurse with the subtree-skipping visitor instead, so that a false return only skips that node's descendants and the walk continues with its siblings. The doc comment now spells out the difference between the two visitors. The regression test replays the issue's tree shape; before the fix it fails with the node map still containing the orphaned #4.
There was a problem hiding this comment.
Code Review
This pull request fixes an issue in semantics tree traversal where skipping a subtree incorrectly stopped the traversal of remaining siblings for nodes deep in a detached subtree. This is resolved by ensuring _visitDepthFirstInTraversalOrderCanSkipSubtree recurses using itself rather than _visitDepthFirstInTraversalOrder. A regression test has been added to verify this behavior. Feedback is provided to correct a private method reference in the documentation to ensure Dartdoc links resolve properly.
…cs.dart Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
flutter-zl
left a comment
There was a problem hiding this comment.
LGTM. Verified the bug and the fix with a before/after demo:
Before: https://flutter-demo-92-before.web.app
After: https://flutter-demo-92-after.web.app
Click Reparent, then Bump. Before throws "Semantics node map was inconsistent after update" on every update; after stays clean.
|
autosubmit label was removed for flutter/flutter/195842, because - The status or check suite Dashboard Checks has failed. Please fix the issues identified (or deflake) before re-applying this label. |
Fixes #194420
When a node is detached from its parent,
EngineSemanticsOwner._finalizeTreewalks the detached subtree with_visitDepthFirstInTraversalOrderCanSkipSubtreeto remove every descendant that was not reparented elsewhere. The callback returns false for a reparented node so that its subtree is skipped (it moved along with the node).The visitor, however, recursed into its children with
_visitDepthFirstInTraversalOrder, whose contract is "stop the whole traversal when the callback returns false". So for any node two or more levels below the detachment root, a reparented child ended the walk of its parent's child list: later siblings that were not reparented were never visited, never disposed, and stayed in_semanticsTree. In debug mode the post-update consistency check then throwsBad state: Semantics node map was inconsistent after updateon that update and on every update after it; in release mode the orphaned nodes (with their DOM elements and roles) leak and stay registered in the map under their old ids.This is what happens when a
GlobalKey'd widget is moved to a new parent while its old ancestors and an old later sibling are removed in the same frame:0 -> 1 -> 2 -> [3, 4]becomes0 -> 5 -> 6 -> 3, and #4 was left behind.Recurse with the subtree-skipping visitor instead, so that a false return only skips that node's descendants and the walk continues with its siblings. The doc comment now spells out the difference between the two visitors.
The regression test replays the issue's tree shape; before the fix it fails with the node map still containing the orphaned #4.
Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.
List which issues are fixed by this PR. You must list at least one issue. An issue is not required if the PR fixes something trivial like a typo.
If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.