blitz-dom: the body's `overflow` doesn't propagate to the viewport
Generated from repros/blitz-dom-overflow-propagation/README.md when the site was built at 7aa23e2.
What niso does
Section titled “What niso does”Nothing yet: the gap is documented in docs/wpt.md (Caveats, and row 4 of “What the testdriver tests fail on”).
crates/niso-actions/src/dom.rs (scroll) scrolls a body with overflow: auto itself, to match what niso’s layout
does. niso doesn’t patch blitz, so overflow-propagation.patch here is the fix to propose upstream.
Expected vs actual
Section titled “Expected vs actual”css-overflow-3 §3.3 (“Overflow Viewport Propagation”): when the root element’s overflow is visible, the viewport
takes the overflow of the root’s first <body> child, and the body’s own used overflow is visible. Blitz
already treats the root element’s scrolling as the viewport’s (canonical_scroll_target, on main), but nothing
propagates the body’s: every reader of overflow (Taffy through TaffyStyloStyle::overflow, paint layers, scroll
ranges, scrollbars, touch panning) takes the body’s computed value, so the body becomes a scroll container that clips
its content, and the document can’t scroll.
Viewport 800x600, <body style="margin:0;height:100px;overflow:auto"> with a 2000px child; both the body and the
root element asked to scroll to y = 500:
body scrolled to: expected 0, actual 500viewport scrolled to: expected 500, actual 0CARGO_TARGET_DIR=~/.capy/work/repro-target cargo runExit status 1 while the bug reproduces, 0 once fixed. On Ubuntu, blitz needs sudo apt-get install -y libfontconfig-dev pkg-config.
With the patch applied to a blitz checkout at the pinned revision, adding
[patch."https://github.com/DioxusLabs/blitz"]blitz-dom = { path = "<blitz>/packages/blitz-dom" }blitz-html = { path = "<blitz>/packages/blitz-html" }blitz-traits = { path = "<blitz>/packages/blitz-traits" }stylo_taffy = { path = "<blitz>/packages/stylo_taffy" }to Cargo.toml makes it print the expected values and exit 0 (checked on yachiyo, 2026-11-09).
The patch
Section titled “The patch”overflow-propagation.patch (against be60597, and it applies to main at b6ace6a as well):
Node::overflow_propagates_to_viewport: the node is the root<html>’s first<body>child and the root’soverflowisvisibleon both axes.Node::used_overflowgivesvisiblefor that body, else the computed values.- A
StyleFlags::OVERFLOW_PROPAGATEDflag thatNode::layout_stylesets, with whichTaffyStyloStyle::overflowreportsvisible, so Taffy lays the body out unclipped and the content overflows into the viewport’s scrollable area. is_paint_layer_root, the scroll range inscrolling.rs, touch panning inevents/pointer.rsand the scrollbar check innode/scrollbar.rsreadused_overflow.
Not covered: the viewport doesn’t yet take the body’s value (a body with overflow: hidden should stop the user
scrolling the viewport), and damage.rs doesn’t relayout the body when only the root’s overflow changes.
Upstream main
Section titled “Upstream main”Reproduces by inspection: main (b6ace6a, 2026-11-09) reads the computed overflow in the same places
(events/pointer.rs:190, layout/paint_tree.rs:466, node/scrollbar.rs:91, scrolling.rs:484) and propagates
only the root element’s scrolling to the viewport. Not built and run on main.
Existing issue or PR
Section titled “Existing issue or PR”None found in a search of DioxusLabs/blitz issues and PRs for overflow propagation, body overflow, viewport overflow.
A hook for embedders
Section titled “A hook for embedders”This is the third time niso needed a step between styling and layout to adjust computed styles: text scale under
animation, the radio-group restyle, and now overflow propagation. BaseDocument::resolve runs style and layout back
to back, so an embedder can only work around blitz or patch it. A hook that lets the embedder adjust computed styles
(or the Taffy style flags) after the cascade and before layout would be worth proposing alongside this fix.
Issue text
Section titled “Issue text”Title: The body’s overflow doesn’t propagate to the viewport
Body:
Per css-overflow-3 §3.3, when the root element’s overflow is visible, the viewport uses the overflow of the
root’s first <body> child, and the body’s used overflow becomes visible. Blitz uses the body’s computed
overflow everywhere, so <body style="height:100px;overflow:auto"> with tall content becomes a 100px scroll
container and the document can’t scroll, where browsers scroll the viewport.
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;height:100px;overflow:auto"><div style="height:2000px"></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 root = doc.root_element().id; let body = doc.get_node(root).unwrap().children.iter().copied().find(|&c| doc.get_node(c).unwrap().data.is_element_with_tag_name(&blitz_dom::local_name!("body"))).unwrap(); doc.scroll_to(body, 0.0, 500.0, ScrollBehavior::Instant); doc.scroll_to(root, 0.0, 500.0, ScrollBehavior::Instant); doc.resolve(0.0); let body_y = doc.get_node(body).unwrap().scroll_offset().y; let viewport_y = doc.viewport_scroll().y; println!("body scrolled to: expected 0, actual {body_y}"); println!("viewport scrolled to: expected 500, actual {viewport_y}"); if body_y != 0.0 || viewport_y != 500.0 { std::process::exit(1); }}Expected body scrolled to 0, viewport scrolled to 500; actual 500 and 0. A patch is attached: a
Node::used_overflow that gives visible for the propagated body, a Taffy style flag so layout doesn’t clip it, and
the paint, scroll, scrollbar and touch-panning checks reading the used value.