Skip to content

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.

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.

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 500
viewport scrolled to: expected 500, actual 0
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.

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).

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’s overflow is visible on both axes. Node::used_overflow gives visible for that body, else the computed values.
  • A StyleFlags::OVERFLOW_PROPAGATED flag that Node::layout_style sets, with which TaffyStyloStyle::overflow reports visible, so Taffy lays the body out unclipped and the content overflows into the viewport’s scrollable area.
  • is_paint_layer_root, the scroll range in scrolling.rs, touch panning in events/pointer.rs and the scrollbar check in node/scrollbar.rs read used_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.

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.

None found in a search of DioxusLabs/blitz issues and PRs for overflow propagation, body overflow, viewport overflow.

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.

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.