diff --git a/crates/obscura-render/src/dom.rs b/crates/obscura-render/src/dom.rs index 20adb76..a91e038 100644 --- a/crates/obscura-render/src/dom.rs +++ b/crates/obscura-render/src/dom.rs @@ -1912,6 +1912,33 @@ fn build( let mut taffy_style = to_taffy_style(style); + // CSS has separate outer and inner display types, while taffy exposes one + // display value. An inline-block normally needs our wrapping inline + // approximation so it remains atomic in an inline formatting context. + // As a direct flex/grid item, however, its outer display is blockified and + // its flow-root inner display must stack block children. Apply the block + // inner mode only in that formatting context; doing it globally turns + // inline-block lists into one full-width item per line. + if style.is_inline_block { + let mut parent = node.parent; + let flex_or_grid_item = loop { + let Some(parent_id) = parent else { break false }; + let Some(parent_style) = styles.get(&parent_id) else { break false }; + if parent_style.display_contents { + parent = tree.get_node(parent_id).and_then(|node| node.parent); + continue; + } + break matches!( + parent_style.display, + crate::Display::Flex | crate::Display::Grid + ) && !parent_style.internal_flex_container; + }; + if flex_or_grid_item { + taffy_style.display = taffy::style::Display::Block; + taffy_style.flex_wrap = taffy::FlexWrap::NoWrap; + } + } + // A replaced image is a measured leaf, even when CSS gives it a percentage // width. Its intrinsic dimensions participate in an auto-sized ancestor's // max-content measurement; once the percentage axis becomes definite, the diff --git a/crates/obscura-render/src/lib.rs b/crates/obscura-render/src/lib.rs index 123c79d..f2d1a83 100644 --- a/crates/obscura-render/src/lib.rs +++ b/crates/obscura-render/src/lib.rs @@ -165,6 +165,10 @@ impl Dimension { #[derive(Debug, Clone, Default)] pub struct LayoutStyle { pub display: Display, + /// True when `display:flex` is only an internal stand-in for native HTML + /// layout such as table cells or `
`, rather than the computed CSS + /// display. Descendants are not CSS flex items in these containers. + pub internal_flex_container: bool, pub width: Dimension, pub height: Dimension, /// Which box edge `width`/`height` and min/max sizes describe. CSS starts diff --git a/crates/obscura-render/src/style.rs b/crates/obscura-render/src/style.rs index 457c689..af34ec4 100644 --- a/crates/obscura-render/src/style.rs +++ b/crates/obscura-render/src/style.rs @@ -45,6 +45,7 @@ pub fn ua_style(tag: &str) -> LayoutStyle { }; if tag == "center" { style.display = Display::Flex; + style.internal_flex_container = true; style.flex_direction = Some(taffy::FlexDirection::Column); style.align_items = Some(taffy::AlignItems::CENTER); } else if tag == "head" || tag == "script" || tag == "style" || tag == "title" || tag == "meta" || tag == "link" || tag == "noscript" || tag == "template" @@ -106,6 +107,7 @@ pub fn ua_style(tag: &str) -> LayoutStyle { style.underline = Some(true); // UA default: links are underlined } else if tag == "table" || tag == "tbody" { style.display = Display::Flex; + style.internal_flex_container = true; style.flex_direction = Some(taffy::FlexDirection::Column); style.align_items = Some(taffy::AlignItems::STRETCH); // stretch rows to fill table width // Rows fill the table width and may shrink below their content's @@ -118,6 +120,7 @@ pub fn ua_style(tag: &str) -> LayoutStyle { style.width = crate::Dimension::Percent(1.0); } } else if tag == "tr" { + style.internal_flex_container = true; // Rows fill the table width and can shrink below content min-content; // this is exactly why Wikipedia's own responsive CSS uses // `tr{min-width:100%}`. `align-items:stretch` alone did not pin them @@ -126,6 +129,7 @@ pub fn ua_style(tag: &str) -> LayoutStyle { style.width = crate::Dimension::Percent(1.0); } else if tag == "td" || tag == "th" { style.display = Display::Flex; + style.internal_flex_container = true; style.flex_direction = Some(taffy::FlexDirection::Column); style.align_items = Some(taffy::AlignItems::FLEX_START); style.padding = Edges { top: 0.0, right: 5.0, bottom: 0.0, left: 0.0 }; @@ -241,6 +245,20 @@ fn apply_value(style: &mut LayoutStyle, name: &str, value: &str) { if value != "contents" { style.display_contents = false; } + if matches!( + value, + "none" + | "flex" + | "inline-flex" + | "inline" + | "inline-block" + | "grid" + | "inline-grid" + | "block" + | "contents" + ) { + style.internal_flex_container = false; + } match value { "none" => style.display = crate::Display::None, "flex" => style.display = crate::Display::Flex, @@ -2577,6 +2595,20 @@ mod tests { assert_eq!(s.height, crate::Dimension::Px(50.0)); } + #[test] + fn authored_display_replaces_internal_flex_provenance() { + let native_cell = compute_style("td", None); + assert_eq!(native_cell.display, Display::Flex); + assert!(native_cell.internal_flex_container); + + let authored_cell = compute_style("td", Some("display:flex")); + assert_eq!(authored_cell.display, Display::Flex); + assert!(!authored_cell.internal_flex_container); + + let invalid = compute_style("td", Some("display:bogus")); + assert!(invalid.internal_flex_container); + } + #[test] fn font_shorthand_expands_layout_fields_and_resets_omissions() { let s = compute_style(