Skip to content

blitz-dom: an inline element's client rect uses the line box height, not its content area

Generated from repros/blitz-dom-inline-rect-line-height/README.md when the site was built at 7aa23e2.

Found by Acid2 (the .chin div, whose   span is measured). crates/niso-core/src/geometry.rs: Dom::inline_rect measures non-atomic inlines for Element.getBoundingClientRect() itself, each fragment spanning its glyph runs’ font ascent and descent around their baseline. Inline boxes have no native view of their own (they render as part of their inline formatting context’s text), so nothing else uses blitz’s rect.

blitz-dom Node::inline_fragment_boxes (packages/blitz-dom/src/node/node.rs:1789-1886), used by get_client_bounding_rect for non-atomic inlines (document.rs:2451-2456, inline_fragment_rects at document.rs:2545). For each glyph run it takes the line’s block_min_coord/block_max_coord (node.rs:1854-1860), on purpose (“Use the line box’s block extent rather than the run’s font ascent/descent … matches the geometry used for text selection highlights”). CSSOM’s getBoundingClientRect for an inline box is its border box, whose height is the content area of its own font (CSS 2.1 §10.6.1), placed around its baseline, not the line box. So the selection geometry and the CSSOM geometry need different numbers.

<p style="width:100px;font:20px/40px sans-serif">x <span style="font:2px/4px serif">&nbsp;</span> y</p>:

span with font 2px/4px on a 40px line
expected: height = content area of a 2px font (Chrome: y=25 height=3), at most 4
actual: y=0 height=40

Chrome 156: span y=25, height 3. The repro checks only height <= 4 (the span’s own line-height bounds its content area), since the exact content-area height depends on the serif font installed.

Terminal window
CARGO_TARGET_DIR=~/.capy/work/repro-target cargo run

Exit status 1 while the bug reproduces, 0 once fixed. Needs libfontconfig-dev pkg-config on Ubuntu. Built with blitz-dom features system-fonts, accessibility, floats (as niso), viewport 800x600 at scale 1.

Reproduces. Blitz main was still at be60597f67b3cece0456d26e8d7ff5a88600bbc5 (niso’s pin) when tested on 2026-11-08, so the pinned result is the main result. Also still reproduces with PR #1370 applied (0c20c4e).

None found (searched blitz issues/PRs for getBoundingClientRect, inline, line-height, fragment). #874 (merged, unrounded gBCR positions), #1060/#1062 (merged, moved this code to Node::inline_fragment_boxes) introduced or moved the current behaviour.

Title: get_client_bounding_rect of an inline element is the line box’s height, not the inline box’s

Body:

For non-atomic inlines, Node::inline_fragment_boxes uses the line box’s block extent for every glyph run. That suits selection highlights, but getBoundingClientRect/getClientRects of an inline box is its border box: the content area of the element’s own font (ascent + descent, CSS 2.1 §10.6.1) around its baseline, plus padding and border. A small inline on a tall line reports the whole line.

use blitz_dom::DocumentConfig;
use blitz_html::HtmlDocument;
use blitz_traits::shell::{ColorScheme, Viewport};
const HTML: &str = r#"<body style="margin:0">
<p style="width:100px;font:20px/40px sans-serif;margin:0">x <span id="s" style="font:2px/4px serif">&nbsp;</span> y</p></body>"#;
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 r = doc.get_client_bounding_rect(doc.get_element_by_id("s").unwrap()).unwrap();
println!("y={} height={}", r.y, r.height); // Chrome 156: y=25 height=3; blitz: y=0 height=40
}

Tested at be60597. Suggested fix: keep the line extent for selection, and for CSSOM rects use baseline - ascent .. baseline + descent of the run’s font metrics (glyph_run.baseline(), glyph_run.run().metrics()), outset by the element’s block padding and border.