Skip to content

Fix mismatch between hit-test tree and traversal tree#186826

Merged
auto-submit[bot] merged 6 commits into
flutter:masterfrom
QuncCccccc:semantics_trees_mismatch
May 29, 2026
Merged

Fix mismatch between hit-test tree and traversal tree#186826
auto-submit[bot] merged 6 commits into
flutter:masterfrom
QuncCccccc:semantics_trees_mismatch

Conversation

@QuncCccccc

@QuncCccccc QuncCccccc commented May 20, 2026

Copy link
Copy Markdown
Contributor

This PR is to update hit-test tree to make sure traversal tree and hit-test tree have the same number of nodes, even though they can have different shapes.

In OverlayPortal, if the child node is a traversal child, but the current node is not a traversal parent, it means the child node should be grafted to be a child of a traversal parent node that has the same identifier as the child. However, if the traversal parent is not in the tree, after grafting, the child will not be in the tree as well. To keep the hit-test tree match the traversal tree, we also skip this node in childrenInHitTestOrder. Otherwise, the number of nodes in two trees will be different and user might accidentally hit test a node that will never be traversed.

Fixes #184898

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools.
  • 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].
  • I followed the [breaking change policy] and added [Data Driven Fixes] where supported.
  • All existing and new tests are passing.

@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label May 20, 2026
@github-actions github-actions Bot added framework flutter/packages/flutter repository. See also f: labels. a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) and removed CICD Run CI/CD labels May 20, 2026
@QuncCccccc QuncCccccc added the CICD Run CI/CD label May 20, 2026
@QuncCccccc QuncCccccc requested a review from chunhtai May 20, 2026 21:48
@QuncCccccc QuncCccccc marked this pull request as ready for review May 20, 2026 21:48

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces logic to filter SemanticsNode children in hit-test order, ensuring that nodes with missing traversal parents are excluded to maintain consistency with the traversal tree. The changes include refactoring _addToUpdate and debug methods to use the new filtering logic, along with a regression test. Feedback highlights a potential null pointer exception on detached nodes and suggests a performance optimization to avoid redundant list allocations when filtering is not necessary.

Comment on lines +4051 to +4083
List<SemanticsNode> _childrenInHitTestOrder() {
if (_children == null) {
return const <SemanticsNode>[];
}
// If the child node is a traversal child, but the current node is
// not a traversal parent, it means the child node should be
// grafted to be a child of a traversal parent node that has the
// same identifier as the child. However, if the traversal parent is not
// in the tree, after grafting, the child will not be in the tree as well.
// To keep the hit-test tree match the traversal tree, we also skip this
// node in childrenInHitTestOrder. Otherwise, the number of nodes in two
// trees will be different and user might accidentally hit test a node
// that will never be traversed.
bool shouldSkipInHitTest(SemanticsNode child) {
if (kIsWeb) {
return false;
}
if (child._isTraversalChild && !_isTraversalParent) {
final SemanticsNode? traversalParent =
owner!._traversalParentNodes[child.getSemanticsData().traversalChildIdentifier];
return traversalParent == null;
}
return false;
}

final filtered = <SemanticsNode>[];
for (final SemanticsNode child in _children!) {
if (!shouldSkipInHitTest(child)) {
filtered.add(child);
}
}
return filtered;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The current implementation of _childrenInHitTestOrder has two main issues:

  1. Potential Crash: The use of owner! (line 4070) will throw a null pointer exception if the SemanticsNode is detached from the semantics owner. This can happen during debugging (e.g., when calling toStringDeep) or when generating diagnostic information for a detached node.
  2. Performance Regression: It always allocates a new List and iterates through all children, even when no filtering is needed (which is the common case, especially when _isTraversalParent is true).

The suggested implementation below addresses both issues by adding a null check for owner, returning _children! directly when no filtering is possible, and using a lazy allocation pattern for the filtered list.

  List<SemanticsNode> _childrenInHitTestOrder() {
    if (_children == null) {
      return const <SemanticsNode>[];
    }
    if (kIsWeb || _isTraversalParent) {
      return _children!;
    }

    bool shouldSkipInHitTest(SemanticsNode child) {
      if (child._isTraversalChild) {
        final SemanticsOwner? owner = this.owner;
        if (owner == null) {
          return false;
        }
        final SemanticsNode? traversalParent =
            owner._traversalParentNodes[child.getSemanticsData().traversalChildIdentifier];
        return traversalParent == null;
      }
      return false;
    }

    List<SemanticsNode>? filtered;
    for (int i = 0; i < _children!.length; i += 1) {
      final SemanticsNode child = _children![i];
      if (shouldSkipInHitTest(child)) {
        filtered ??= _children!.take(i).toList();
      } else {
        filtered?.add(child);
      }
    }
    return filtered ?? _children!;
  }

return false;
}

final filtered = <SemanticsNode>[];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Drive-by nit: use a for literal?

return [
  for (final SemanticsNode child in _children ?? const <SemanticsNode>[])
    if (!shouldSkipInHitTest(child))
      child,
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we want to clean this up, I would inverse the
shouldSkipInHitTest to shouldNotSkipInHitTest
and then
_children!.where(shouldNotSkipInHitTest).toList();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

may as well make the return type a iterable so you don't need a toList at the end

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I still kept it as List to keep consistent with _childrenInTraversalOrder. But let me know if Iterable is prefered.

Comment thread packages/flutter/test/semantics/semantics_update_test.dart Outdated
Comment thread packages/flutter/lib/src/semantics/semantics.dart Outdated
return false;
}

final filtered = <SemanticsNode>[];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we want to clean this up, I would inverse the
shouldSkipInHitTest to shouldNotSkipInHitTest
and then
_children!.where(shouldNotSkipInHitTest).toList();

Comment thread packages/flutter/lib/src/semantics/semantics.dart Outdated
@github-actions github-actions Bot removed the CICD Run CI/CD label May 26, 2026
@QuncCccccc QuncCccccc force-pushed the semantics_trees_mismatch branch from 0e23b89 to 4117678 Compare May 26, 2026 21:55
@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label May 26, 2026
@QuncCccccc QuncCccccc force-pushed the semantics_trees_mismatch branch from 4117678 to 5a5d194 Compare May 27, 2026 00:23
@github-actions github-actions Bot removed the CICD Run CI/CD label May 27, 2026
@QuncCccccc QuncCccccc added the CICD Run CI/CD label May 27, 2026
navaronbracke
navaronbracke previously approved these changes May 27, 2026
@github-actions github-actions Bot removed the CICD Run CI/CD label May 27, 2026
@QuncCccccc QuncCccccc added the CICD Run CI/CD label May 27, 2026
chunhtai
chunhtai previously approved these changes May 28, 2026

@chunhtai chunhtai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, except one comment

Comment thread packages/flutter/lib/src/semantics/semantics.dart Outdated
@QuncCccccc

Copy link
Copy Markdown
Contributor Author

Thanks for your reviews and suggestions! Addressing comments made the approvals be dismissed. Just requested your stamps again:)!

@QuncCccccc QuncCccccc added the CICD Run CI/CD label May 28, 2026
@flutter-dashboard

Copy link
Copy Markdown

Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change).

If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #186826 at sha 74d78df

@flutter-dashboard flutter-dashboard Bot added the will affect goldens Changes to golden files label May 28, 2026
@QuncCccccc QuncCccccc force-pushed the semantics_trees_mismatch branch from 74d78df to 153111a Compare May 28, 2026 23:27
@github-actions github-actions Bot removed the CICD Run CI/CD label May 28, 2026
@QuncCccccc QuncCccccc added the CICD Run CI/CD label May 29, 2026
@QuncCccccc QuncCccccc added the autosubmit Merge PR when tree becomes green via auto submit App label May 29, 2026
@auto-submit auto-submit Bot added this pull request to the merge queue May 29, 2026
Merged via the queue into flutter:master with commit c7f49fe May 29, 2026
94 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label May 29, 2026
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Jun 1, 2026
flutter/flutter@b05a9d7...54e199a

2026-06-01 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from jMR_VXQi07kAk8vbR... to q27k7_um1GvVrySZS... (flutter/flutter#187338)
2026-06-01 rmacnak@google.com Remove use of deprecated API related to removal of the VM isolate. (flutter/flutter#187013)
2026-06-01 116356835+AbdeMohlbi@users.noreply.github.com Improve `dependOnInheritedWidgetOfExactType` documentation to explain why it is bad to use it in initState (flutter/flutter#186216)
2026-06-01 chris@bracken.jp Revert "Move dart-lang/ai to a top level third party dependency in en… (flutter/flutter#187370)
2026-05-30 jakemac@google.com Move dart-lang/ai to a top level third party dependency in engine (flutter/flutter#187268)
2026-05-30 evanwall@buffalo.edu add sdf golden variants for OpenGL (flutter/flutter#187246)
2026-05-30 engine-flutter-autoroll@skia.org Roll Skia from dc01525ac468 to 0aee4675e0ad (6 revisions) (flutter/flutter#187334)
2026-05-29 engine-flutter-autoroll@skia.org Roll Skia from c480ba2eb2eb to dc01525ac468 (4 revisions) (flutter/flutter#187317)
2026-05-29 jason-simmons@users.noreply.github.com Remove the Y coordinate flip workaround in the Material stretch effect shader now that it is no longer required by the Impeller GLES back end (flutter/flutter#187247)
2026-05-29 bkonyi@google.com [flutter_tools, devicelab] Fix filesystem safety guard for symlinked temp directories (flutter/flutter#187320)
2026-05-29 30870216+gaaclarke@users.noreply.github.com Brings linux tests out of bringup. (flutter/flutter#187271)
2026-05-29 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#187321)
2026-05-29 engine-flutter-autoroll@skia.org Roll Fuchsia GN SDK from SEfYx3xgueX3aFAY3... to oOAcFhkoE2_-Sy67z... (flutter/flutter#187310)
2026-05-29 36861262+QuncCccccc@users.noreply.github.com Fix mismatch between hit-test tree and traversal tree (flutter/flutter#186826)
2026-05-29 jason-simmons@users.noreply.github.com [Impeller] Ensure that the TextureGLES destructor cleans up all objects that it holds including the sync fence (flutter/flutter#187216)
2026-05-29 engine-flutter-autoroll@skia.org Roll Packages from 10cbdc5 to e930ced (3 revisions) (flutter/flutter#187306)
2026-05-29 engine-flutter-autoroll@skia.org Roll Skia from d9d6b440c4e7 to c480ba2eb2eb (1 revision) (flutter/flutter#187305)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC stuartmorgan@google.com,tarrinneal@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
creatorpiyush pushed a commit to creatorpiyush/packages that referenced this pull request Jun 10, 2026
…r#11816)

flutter/flutter@b05a9d7...54e199a

2026-06-01 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from jMR_VXQi07kAk8vbR... to q27k7_um1GvVrySZS... (flutter/flutter#187338)
2026-06-01 rmacnak@google.com Remove use of deprecated API related to removal of the VM isolate. (flutter/flutter#187013)
2026-06-01 116356835+AbdeMohlbi@users.noreply.github.com Improve `dependOnInheritedWidgetOfExactType` documentation to explain why it is bad to use it in initState (flutter/flutter#186216)
2026-06-01 chris@bracken.jp Revert "Move dart-lang/ai to a top level third party dependency in en… (flutter/flutter#187370)
2026-05-30 jakemac@google.com Move dart-lang/ai to a top level third party dependency in engine (flutter/flutter#187268)
2026-05-30 evanwall@buffalo.edu add sdf golden variants for OpenGL (flutter/flutter#187246)
2026-05-30 engine-flutter-autoroll@skia.org Roll Skia from dc01525ac468 to 0aee4675e0ad (6 revisions) (flutter/flutter#187334)
2026-05-29 engine-flutter-autoroll@skia.org Roll Skia from c480ba2eb2eb to dc01525ac468 (4 revisions) (flutter/flutter#187317)
2026-05-29 jason-simmons@users.noreply.github.com Remove the Y coordinate flip workaround in the Material stretch effect shader now that it is no longer required by the Impeller GLES back end (flutter/flutter#187247)
2026-05-29 bkonyi@google.com [flutter_tools, devicelab] Fix filesystem safety guard for symlinked temp directories (flutter/flutter#187320)
2026-05-29 30870216+gaaclarke@users.noreply.github.com Brings linux tests out of bringup. (flutter/flutter#187271)
2026-05-29 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#187321)
2026-05-29 engine-flutter-autoroll@skia.org Roll Fuchsia GN SDK from SEfYx3xgueX3aFAY3... to oOAcFhkoE2_-Sy67z... (flutter/flutter#187310)
2026-05-29 36861262+QuncCccccc@users.noreply.github.com Fix mismatch between hit-test tree and traversal tree (flutter/flutter#186826)
2026-05-29 jason-simmons@users.noreply.github.com [Impeller] Ensure that the TextureGLES destructor cleans up all objects that it holds including the sync fence (flutter/flutter#187216)
2026-05-29 engine-flutter-autoroll@skia.org Roll Packages from 10cbdc5 to e930ced (3 revisions) (flutter/flutter#187306)
2026-05-29 engine-flutter-autoroll@skia.org Roll Skia from d9d6b440c4e7 to c480ba2eb2eb (1 revision) (flutter/flutter#187305)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC stuartmorgan@google.com,tarrinneal@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
via-guy pushed a commit to via-guy/flutter that referenced this pull request Jun 26, 2026
This PR is to update hit-test tree to make sure traversal tree and
hit-test tree have the same number of nodes, even though they can have
different shapes.

In OverlayPortal, if the child node is a traversal child, but the
current node is not a traversal parent, it means the child node should
be grafted to be a child of a traversal parent node that has the same
identifier as the child. However, if the traversal parent is not in the
tree, after grafting, the child will not be in the tree as well. To keep
the hit-test tree match the traversal tree, we also skip this node in
childrenInHitTestOrder. Otherwise, the number of nodes in two trees will
be different and user might accidentally hit test a node that will never
be traversed.

Fixes flutter#184898

## 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 documentation (doc comments with `///`).
- [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.

---------

Co-authored-by: chunhtai <47866232+chunhtai@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) CICD Run CI/CD framework flutter/packages/flutter repository. See also f: labels. will affect goldens Changes to golden files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App crashes when using TalkBack, misbehaves with VoiceOver

3 participants