Skip to content

blitz-dom: `get_client_bounding_rect` moves a scroll container by its own scroll offset

Generated from repros/blitz-dom-bounding-rect-scroller-own-offset/README.md when the site was built at 7aa23e2.

crates/niso-core/src/api.rs:254-260 (Dom::rect): unless the element scrolls the viewport, add the element’s own scroll_offset() back to the rect blitz returns.

physical_unrounded_geometry (packages/blitz-dom/src/document.rs:2433-2448) sums location - scroll_offset over the containing-block chain starting at the node itself, so a scroller’s own scroll offset is subtracted from its own border box. Scrolling moves the content of a scroll container, not the container. Its descendants come out right (the content below is at -150 as expected).

overflow: auto box at y = 50, 100px tall, with 500px of content, scrolled to 200:

#scroller scrollTop = 200
#scroller: expected y = 50, actual y = -150
#content: expected y = -150, actual y = -150
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.

Reproduces. Blitz main was at be60597f67b3cece0456d26e8d7ff5a88600bbc5 when tested (2026-11-08, git ls-remote https://github.com/DioxusLabs/blitz HEAD), the same commit niso pins, so the pinned result is also the main result.

None found (same searches as the fixed-position repro: getBoundingClientRect, get_client_bounding_rect, scroll offset, scroll). #874 (merged) introduced physical_unrounded_geometry but is not about this.

Title: get_client_bounding_rect subtracts a scroll container’s own scroll offset from its own box

Body:

physical_unrounded_geometry starts its location - scroll_offset walk at the node itself, so a scrolled overflow: auto element reports its border box moved by its own scrollTop/scrollLeft. Only its descendants should move.

use blitz_dom::{DocumentConfig, ScrollBehavior};
use blitz_html::HtmlDocument;
use blitz_traits::shell::{ColorScheme, Viewport};
const HTML: &str = r#"<!DOCTYPE html>
<html><body style="margin:0">
<div id="scroller" style="margin-top:50px;width:100px;height:100px;overflow:auto"><div id="content" style="height:500px"></div></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 scroller = doc.get_element_by_id("scroller").unwrap();
doc.scroll_to(scroller, 0.0, 200.0, ScrollBehavior::Instant);
doc.resolve(0.0);
println!("{}", doc.get_client_bounding_rect(scroller).unwrap().y); // expected 50, actual -150
}

Tested at be60597f67b3cece0456d26e8d7ff5a88600bbc5. A fix: add the node’s own location without subtracting its own scroll offset, and subtract scroll_offset only for ancestors on the containing-block chain.