Skip to content

blitz-dom: `Node::hit` ignores overflow clips and clip-path

Generated from repros/blitz-dom-hit-ignores-clips/README.md when the site was built at 7aa23e2.

crates/niso-core/src/input.rs: collect_hits walks the boxes the way Node::hit_inner does, but skips a box whose clip-path or CSS clip leaves the point out, and skips what a box holds when the point lies outside that box’s padding box on an axis whose overflow is not visible. Dom::hit (elementFromPoint, pointer dispatch) and Dom::hits (elementsFromPoint) both use that walk instead of BaseDocument::hit.

Node::hit_inner (packages/blitz-dom/src/node/node.rs:1428) tests a box against its border box and its scrollable overflow, then recurses into its children with no clip. Paint clips the children of a box whose overflow is not visible to its padding box (should_clip_node in blitz-paint/src/render.rs), and clips a box with clip-path to its shape (clip_path_shape), so the hit test finds boxes that are not drawn there.

Viewport 800x600. A 100x50 overflow: hidden box holds a 200px tall child, and a 100x100 box with clip-path: circle(20px) sits at y = 150:

(10, 100) below the overflow: hidden box: expected not #inside, actual #inside
(5, 155) in the circle's corner: expected not #circle, actual #circle
Terminal window
CARGO_TARGET_DIR=~/.capy/work/repro-target cargo run

Exit status 1 while the bug reproduces, 0 once fixed. On Ubuntu, blitz needs sudo apt-get install -y libfontconfig-dev pkg-config.

Same code. Blitz main was at 808ded9348289cac0a3b4940e69da21d5b918443 when checked (2026-11-09): hit_inner still tests only the border box, the content rect and the scrollable overflow, with no clip.

None found in a search of DioxusLabs/blitz issues and PRs for “hit test clip”, “overflow hidden click” and “clip-path hit”.

Title: Node::hit finds boxes hidden by overflow clips and clip-path

Body:

BaseDocument::hit returns boxes that paint clips away: content overflowing an overflow: hidden (or clip, auto, scroll) box outside its padding box, and the parts of a box outside its clip-path. Clicks there land on content the user cannot see.

use blitz_dom::DocumentConfig;
use blitz_html::HtmlDocument;
use blitz_traits::shell::{ColorScheme, Viewport};
const HTML: &str = r#"<!DOCTYPE html>
<html><body style="margin:0">
<div id="overflow" style="overflow:hidden;width:100px;height:50px"><div id="inside" style="height:200px"></div></div>
<div id="circle" style="width:100px;height:100px;margin-top:100px;clip-path:circle(20px)"></div>
</body></html>"#;
fn main() {
let config = DocumentConfig { viewport: Some(Viewport::new(800, 600, 1.0, ColorScheme::Light)), ..Default::default() };
let mut doc = HtmlDocument::from_html(HTML, config);
doc.resolve(0.0);
let mut failed = false;
for (what, x, y, wrong) in [("below the overflow: hidden box", 10.0, 100.0, "inside"), ("in the circle's corner", 5.0, 155.0, "circle")] {
let hit = doc.hit(x, y).map(|h| h.node_id);
let id = hit.and_then(|id| doc.get_node(id)).and_then(|n| n.attr(blitz_dom::local_name!("id"))).unwrap_or("").to_string();
println!("({x}, {y}) {what}: expected not #{wrong}, actual #{id}");
failed |= id == wrong;
}
if failed {
std::process::exit(1);
}
}

Tested at be60597f67b3cece0456d26e8d7ff5a88600bbc5. A fix: in hit_inner, return early when the point is outside the box’s clip-path shape (the one clip_path_shape builds for paint) or its CSS clip rect, and skip the children and inline content when the point is outside the padding box on an axis whose overflow is not visible.