Hit testing ignores clips_children, so descendants clipped out by an ancestor are still hit
維護者通常 1 天內回覆
還沒有人認領這個 Issue。
評估
- 難度
- 2/5
- 預估耗時
- 1-3 小時
- 新手友好度
- 78/100
- Issue 類型
- 缺陷
- 描述清晰度
- 描述清楚
- 活躍度
- 活躍
- 技術堆疊
- rust
研究方向
從 accesskit_consumer/src/node.rs 中的 NodeRef::hit_test 和其中現有的 node_at_point 測試開始。為 issue 中描述的被裁剪和部分可見的子節點情況新增測試涵蓋,並涵蓋父節點不裁剪子節點的情況。完成標準是這些被裁剪的後代不會被命中,而不裁剪子節點的父節點下的溢出內容仍會被命中。
由索引模型根據 Issue 內容生成。
描述
What happens
NodeRef::hit_test (accesskit_consumer/src/node.rs, used by node_at_point) tests each node's raw bounds and visits every child. It never consults clips_children. A descendant that its parent clips out of view is still returned for points where it would be if nothing were clipped. Often that's on top of UI that is actually visible there.
clips_children is used today only by common_filter (filters.rs, from #574). That filter excludes a child of a clipping parent when the child lies entirely outside the parent, unless a neighbouring sibling is inside, so that off-screen items stay reachable for scrolling. Three cases still hit-test wrongly:
- Off-screen neighbours: a child kept by that rule (the one just before or after the visible ones) is hit at its off-screen position.
- Partly visible children: the clipped-off part of a partly visible child is hit.
- Overflowing descendants: a partly visible child's descendants that extend past the clipping ancestor are hit. The filter only looks at the immediate parent.
What should happen
A point outside a node that clips its children shouldn't hit any of that node's descendants.
Platforms
This affects every platform. The macOS, Windows, Unix (AT-SPI), iOS and Android adapters all resolve hit tests through node_at_point.
How to reproduce
The tree: a header button above a clipping scroll view, which holds one child scrolled up under the header and one visible child. Sketched in the style of the filters.rs tests:
let update = TreeUpdate {
nodes: vec![
(NodeId(0), {
let mut node = Node::new(Role::Window);
node.set_bounds(Rect::new(0.0, 0.0, 200.0, 300.0));
node.set_children(vec![NodeId(1), NodeId(2)]);
node
}),
// Header, drawn over the top of the scroll view's content area.
(NodeId(1), {
let mut node = Node::new(Role::Button);
node.set_bounds(Rect::new(0.0, 0.0, 200.0, 50.0));
node
}),
(NodeId(2), {
let mut node = Node::new(Role::ScrollView);
node.set_clips_children();
node.set_bounds(Rect::new(0.0, 50.0, 200.0, 300.0));
node.set_children(vec![NodeId(3), NodeId(4)]);
node
}),
// Scrolled above the viewport, under the header. Kept by
// common_filter because its next sibling is visible.
(NodeId(3), {
let mut node = Node::new(Role::Button);
node.set_bounds(Rect::new(0.0, 10.0, 200.0, 40.0));
node
}),
(NodeId(4), {
let mut node = Node::new(Role::Button);
node.set_bounds(Rect::new(0.0, 60.0, 200.0, 90.0));
node
}),
],
tree: Some(TreeInfo::new(NodeId(0))),
tree_id: TreeId::ROOT,
focus: NodeId(0),
};
let tree = crate::Tree::new(update, false);
let hit = tree
.state()
.root()
.node_at_point(Point::new(100.0, 25.0), &common_filter)
.map(|node| node.id());
// Expected: the header, NodeId(1). Today: NodeId(3), which is clipped out.
A second case: give node 3 bounds of 0..200 × 30..80 (partly visible), and hit (100, 40). That point is above the viewport, so the header should win. Today node 3 does.
We hit this in a GPUI app. With a panel scrolled, VoiceOver and other accessibility clients pressing a header button activated a hidden list item beneath it. We worked around it in the toolkit by clipping each node's bounds before handing them to AccessKit. But toolkits that already set clips_children shouldn't have to do that.
Proposed plan
- The change: in
hit_test'sVisitframe, when a node hasclips_children()and has bounds, push its children only if the point (already in that node's coordinate space at that stage) lies inside its bounds. The node's ownTestframe is unchanged. - Why this is enough: applied recursively, this means descendants are only hit within the intersection of their clipping ancestors. That covers all three cases above, without changing the filter or which nodes are reachable for scrolling into view.
- Chromium comparison:
BrowserAccessibility::ApproximateHitTest(ui/accessibility/platform/browser_accessibility.cc) only descends into a child whoseGetClippedScreenBoundsRect()contains the point, so it already hit-tests against ancestor-clipped bounds. The change would bring AccessKit in line. - Platform parity: all adapters call
node_at_point, so a consumer-only change applies everywhere. No adapter changes are needed. - Tests: the two cases above as unit tests next to the existing
node_at_pointtest innode.rs. Plus a regression check that a node whose parent doesn't setclips_childrenstill hits outside the parent's bounds, as overflow does today.
I'm happy to implement this if the approach works for you.
- 主要語言
- Rust
- 星號
- 1.6k
- 分支
- 135
- 平均合併
- 10 小時 53 分鐘
- 30 天內合併 PR
- 11
環境準備
- 沒有 Dockerfile 或 Docker Compose 檔案
- 沒有 Pull Request 範本
- 閱讀貢獻指南
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
AccessKit/accesskit 的其他 Issue
-
難度 2/5 1-3 小時 新手友好度 82/100
維護者通常 1 天內回覆
-
難度 2/5 1-3 小時 新手友好度 78/100
維護者通常 1 天內回覆
-
難度 2/5 1-3 小時 新手友好度 75/100
維護者通常 1 天內回覆
-
難度 2/5 1-3 小時 新手友好度 72/100
維護者通常 1 天內回覆
-
難度 5/5 一週以上 新手友好度 15/100
維護者通常 1 天內回覆
查看 AccessKit/accesskit 的全部 Issue
相似的 Issue
-
good first issue help wanted
難度 2/5 1-3 小時 新手友好度 80/100
維護者通常 1 天內回覆
-
bug
難度 2/5 1-3 小時 新手友好度 68/100
swc-project/swc-ai-issues#907 ·
-
難度 1/5 1 小時以內 新手友好度 62/100
-
area:helper kind:bug priority:p3 ready
難度 2/5 1-3 小時 新手友好度 82/100
維護者通常 1 天內回覆
-
bug UI
難度 2/5 1-3 小時 新手友好度 70/100
wardian-app/Wardian#1746 ·
維護者通常 1 天內回覆