From 62d6c5ba31fd7881ba8a5cafb192def77043999f Mon Sep 17 00:00:00 2001 From: Pierre Le Fevre Date: Sat, 21 Mar 2026 20:40:13 +0100 Subject: [PATCH] Implement viewport units (vw/vh/vmin/vmax) and percentage resolution Thread viewport dimensions through style resolution so viewport units resolve correctly: 1vw = 1% of viewport width, 1vh = 1% of viewport height, vmin/vmax use the smaller/larger dimension. For layout properties (width, height, margin, padding), percentages are now preserved as LengthOrAuto::Percentage through style computation and resolved during layout against the containing block width. Height percentages resolve against viewport height. Per CSS spec, even vertical margins and padding resolve against containing block width. Changes: - Add Percentage(f32) variant to LengthOrAuto - Change ComputedStyle padding fields from f32 to LengthOrAuto - Add viewport parameter to resolve_styles, threaded through resolution - resolve_length_unit now computes vw/vh/vmin/vmax from viewport dims - Layout resolves percentage width/height/margin/padding against containing block; nested percentages compound correctly - Store css_margin/css_padding/css_offsets on LayoutBox for deferred resolution during layout - 9 new tests covering all acceptance criteria Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/browser/src/main.rs | 6 +- crates/layout/src/lib.rs | 509 +++++++++++++++++++++---- crates/render/src/lib.rs | 10 +- crates/style/src/computed.rs | 236 ++++++------ crates/style/tests/style_extraction.rs | 12 +- 5 files changed, 566 insertions(+), 207 deletions(-) diff --git a/crates/browser/src/main.rs b/crates/browser/src/main.rs index 2fe4ad6..a3ae9bd 100644 --- a/crates/browser/src/main.rs +++ b/crates/browser/src/main.rs @@ -78,7 +78,11 @@ fn render_page(page: &PageState, font: &Font, bitmap: &mut BitmapContext) { } // Resolve computed styles from DOM + stylesheet. - let styled = match resolve_styles(&page.doc, std::slice::from_ref(&page.stylesheet)) { + let styled = match resolve_styles( + &page.doc, + std::slice::from_ref(&page.stylesheet), + (width as f32, height as f32), + ) { Some(s) => s, None => return, }; diff --git a/crates/layout/src/lib.rs b/crates/layout/src/lib.rs index 2b72121..50144a3 100644 --- a/crates/layout/src/lib.rs +++ b/crates/layout/src/lib.rs @@ -97,10 +97,16 @@ pub struct LayoutBox { pub overflow: Overflow, /// CSS `box-sizing` property. pub box_sizing: BoxSizing, - /// CSS `width` property (explicit or auto). + /// CSS `width` property (explicit or auto, may contain percentage). pub css_width: LengthOrAuto, - /// CSS `height` property (explicit or auto). + /// CSS `height` property (explicit or auto, may contain percentage). pub css_height: LengthOrAuto, + /// CSS margin values (may contain percentages for layout resolution). + pub css_margin: [LengthOrAuto; 4], + /// CSS padding values (may contain percentages for layout resolution). + pub css_padding: [LengthOrAuto; 4], + /// CSS position offset values (top, right, bottom, left) for relative positioning. + pub css_offsets: [LengthOrAuto; 4], /// CSS `visibility` property. pub visibility: Visibility, } @@ -140,6 +146,19 @@ impl LayoutBox { box_sizing: style.box_sizing, css_width: style.width, css_height: style.height, + css_margin: [ + style.margin_top, + style.margin_right, + style.margin_bottom, + style.margin_left, + ], + css_padding: [ + style.padding_top, + style.padding_right, + style.padding_bottom, + style.padding_left, + ], + css_offsets: [style.top, style.right, style.bottom, style.left], visibility: style.visibility, } } @@ -197,20 +216,25 @@ impl LayoutTree { // Resolve LengthOrAuto to f32 // --------------------------------------------------------------------------- -fn resolve_length(value: LengthOrAuto) -> f32 { +/// Resolve a `LengthOrAuto` to px. Percentages are resolved against +/// `reference` (typically the containing block width). Auto resolves to 0. +fn resolve_length_against(value: LengthOrAuto, reference: f32) -> f32 { match value { LengthOrAuto::Length(px) => px, + LengthOrAuto::Percentage(p) => p / 100.0 * reference, LengthOrAuto::Auto => 0.0, } } /// Resolve horizontal offset for `position: relative`. /// If both `left` and `right` are specified, `left` wins (CSS2 §9.4.3, ltr). -fn resolve_relative_horizontal(left: LengthOrAuto, right: LengthOrAuto) -> f32 { +fn resolve_relative_horizontal(left: LengthOrAuto, right: LengthOrAuto, cb_width: f32) -> f32 { match left { LengthOrAuto::Length(px) => px, + LengthOrAuto::Percentage(p) => p / 100.0 * cb_width, LengthOrAuto::Auto => match right { LengthOrAuto::Length(px) => -px, + LengthOrAuto::Percentage(p) => -(p / 100.0 * cb_width), LengthOrAuto::Auto => 0.0, }, } @@ -218,11 +242,13 @@ fn resolve_relative_horizontal(left: LengthOrAuto, right: LengthOrAuto) -> f32 { /// Resolve vertical offset for `position: relative`. /// If both `top` and `bottom` are specified, `top` wins (CSS2 §9.4.3). -fn resolve_relative_vertical(top: LengthOrAuto, bottom: LengthOrAuto) -> f32 { +fn resolve_relative_vertical(top: LengthOrAuto, bottom: LengthOrAuto, cb_height: f32) -> f32 { match top { LengthOrAuto::Length(px) => px, + LengthOrAuto::Percentage(p) => p / 100.0 * cb_height, LengthOrAuto::Auto => match bottom { LengthOrAuto::Length(px) => -px, + LengthOrAuto::Percentage(p) => -(p / 100.0 * cb_height), LengthOrAuto::Auto => 0.0, }, } @@ -263,17 +289,21 @@ fn build_box( return None; } + // Margin and padding: resolve absolute lengths now; percentages + // will be re-resolved in compute_layout against containing block. + // Use 0.0 as a placeholder reference for percentages — they'll be + // resolved properly in compute_layout. let margin = EdgeSizes { - top: resolve_length(style.margin_top), - right: resolve_length(style.margin_right), - bottom: resolve_length(style.margin_bottom), - left: resolve_length(style.margin_left), + top: resolve_length_against(style.margin_top, 0.0), + right: resolve_length_against(style.margin_right, 0.0), + bottom: resolve_length_against(style.margin_bottom, 0.0), + left: resolve_length_against(style.margin_left, 0.0), }; let padding = EdgeSizes { - top: style.padding_top, - right: style.padding_right, - bottom: style.padding_bottom, - left: style.padding_left, + top: resolve_length_against(style.padding_top, 0.0), + right: resolve_length_against(style.padding_right, 0.0), + bottom: resolve_length_against(style.padding_bottom, 0.0), + left: resolve_length_against(style.padding_left, 0.0), }; let border = EdgeSizes { top: if style.border_top_style != BorderStyle::None { @@ -326,12 +356,8 @@ fn build_box( b.replaced_size = Some((w, h)); } - // Compute relative position offset. - if style.position == Position::Relative { - let dx = resolve_relative_horizontal(style.left, style.right); - let dy = resolve_relative_vertical(style.top, style.bottom); - b.relative_offset = (dx, dy); - } + // Relative position offsets are resolved in compute_layout + // where the containing block dimensions are known. Some(b) } @@ -421,14 +447,49 @@ fn is_block_level(b: &LayoutBox) -> bool { // --------------------------------------------------------------------------- /// Position and size a layout box within `available_width` at position (`x`, `y`). +/// +/// `available_width` is the containing block width — used as the reference for +/// percentage widths, margins, and paddings (per CSS spec, even vertical margins/ +/// padding resolve against the containing block width). fn compute_layout( b: &mut LayoutBox, x: f32, y: f32, available_width: f32, + viewport_height: f32, font: &Font, doc: &Document, ) { + // Resolve percentage margins against containing block width. + // Only re-resolve percentages — absolute margins may have been modified + // by margin collapsing and must not be overwritten. + if matches!(b.css_margin[0], LengthOrAuto::Percentage(_)) { + b.margin.top = resolve_length_against(b.css_margin[0], available_width); + } + if matches!(b.css_margin[1], LengthOrAuto::Percentage(_)) { + b.margin.right = resolve_length_against(b.css_margin[1], available_width); + } + if matches!(b.css_margin[2], LengthOrAuto::Percentage(_)) { + b.margin.bottom = resolve_length_against(b.css_margin[2], available_width); + } + if matches!(b.css_margin[3], LengthOrAuto::Percentage(_)) { + b.margin.left = resolve_length_against(b.css_margin[3], available_width); + } + + // Resolve percentage padding against containing block width. + if matches!(b.css_padding[0], LengthOrAuto::Percentage(_)) { + b.padding.top = resolve_length_against(b.css_padding[0], available_width); + } + if matches!(b.css_padding[1], LengthOrAuto::Percentage(_)) { + b.padding.right = resolve_length_against(b.css_padding[1], available_width); + } + if matches!(b.css_padding[2], LengthOrAuto::Percentage(_)) { + b.padding.bottom = resolve_length_against(b.css_padding[2], available_width); + } + if matches!(b.css_padding[3], LengthOrAuto::Percentage(_)) { + b.padding.left = resolve_length_against(b.css_padding[3], available_width); + } + let content_x = x + b.margin.left + b.border.left + b.padding.left; let content_y = y + b.margin.top + b.border.top + b.padding.top; @@ -440,6 +501,13 @@ fn compute_layout( BoxSizing::ContentBox => w.max(0.0), BoxSizing::BorderBox => (w - horizontal_extra).max(0.0), }, + LengthOrAuto::Percentage(p) => { + let resolved = p / 100.0 * available_width; + match b.box_sizing { + BoxSizing::ContentBox => resolved.max(0.0), + BoxSizing::BorderBox => (resolved - horizontal_extra).max(0.0), + } + } LengthOrAuto::Auto => { (available_width - b.margin.left - b.margin.right - horizontal_extra).max(0.0) } @@ -451,18 +519,16 @@ fn compute_layout( // Replaced elements (e.g., ) have intrinsic dimensions. if let Some((rw, rh)) = b.replaced_size { - // Use CSS width/height if specified, otherwise use replaced dimensions. - // Content width is the minimum of replaced width and available width. b.rect.width = rw.min(content_width); b.rect.height = rh; - apply_relative_offset(b); + apply_relative_offset(b, available_width, viewport_height); return; } match &b.box_type { BoxType::Block(_) | BoxType::Anonymous => { if has_block_children(b) { - layout_block_children(b, font, doc); + layout_block_children(b, viewport_height, font, doc); } else { layout_inline_children(b, font, doc); } @@ -473,23 +539,43 @@ fn compute_layout( } // Apply explicit CSS height (adjusted for box-sizing), overriding auto height. - if let LengthOrAuto::Length(h) = b.css_height { - let vertical_extra = b.border.top + b.border.bottom + b.padding.top + b.padding.bottom; - b.rect.height = match b.box_sizing { - BoxSizing::ContentBox => h.max(0.0), - BoxSizing::BorderBox => (h - vertical_extra).max(0.0), - }; + match b.css_height { + LengthOrAuto::Length(h) => { + let vertical_extra = b.border.top + b.border.bottom + b.padding.top + b.padding.bottom; + b.rect.height = match b.box_sizing { + BoxSizing::ContentBox => h.max(0.0), + BoxSizing::BorderBox => (h - vertical_extra).max(0.0), + }; + } + LengthOrAuto::Percentage(p) => { + // Height percentage resolves against containing block height. + // For the root element, use viewport height. + let cb_height = viewport_height; + let resolved = p / 100.0 * cb_height; + let vertical_extra = b.border.top + b.border.bottom + b.padding.top + b.padding.bottom; + b.rect.height = match b.box_sizing { + BoxSizing::ContentBox => resolved.max(0.0), + BoxSizing::BorderBox => (resolved - vertical_extra).max(0.0), + }; + } + LengthOrAuto::Auto => {} } - apply_relative_offset(b); + apply_relative_offset(b, available_width, viewport_height); } /// Apply `position: relative` offset to a box and all its descendants. /// -/// This shifts the visual position without affecting the normal-flow layout -/// of surrounding elements (the original space is preserved). -fn apply_relative_offset(b: &mut LayoutBox) { - let (dx, dy) = b.relative_offset; +/// Resolves the CSS position offsets (which may contain percentages) and +/// shifts the visual position without affecting the normal-flow layout. +fn apply_relative_offset(b: &mut LayoutBox, cb_width: f32, cb_height: f32) { + if b.position != Position::Relative { + return; + } + let [top, right, bottom, left] = b.css_offsets; + let dx = resolve_relative_horizontal(left, right, cb_width); + let dy = resolve_relative_vertical(top, bottom, cb_height); + b.relative_offset = (dx, dy); if dx == 0.0 && dy == 0.0 { return; } @@ -627,7 +713,12 @@ fn last_block_bottom_margin(children: &[LayoutBox]) -> Option { /// Handles adjacent-sibling collapsing, empty-block collapsing, and /// parent-child internal spacing (the parent's external margins were already /// updated by `pre_collapse_margins`). -fn layout_block_children(parent: &mut LayoutBox, font: &Font, doc: &Document) { +fn layout_block_children( + parent: &mut LayoutBox, + viewport_height: f32, + font: &Font, + doc: &Document, +) { let content_x = parent.rect.x; let content_width = parent.rect.width; let mut cursor_y = parent.rect.y; @@ -685,6 +776,7 @@ fn layout_block_children(parent: &mut LayoutBox, font: &Font, doc: &Document) { content_x, y_for_child, content_width, + viewport_height, font, doc, ); @@ -1015,7 +1107,7 @@ pub fn layout( styled_root: &StyledNode, doc: &Document, viewport_width: f32, - _viewport_height: f32, + viewport_height: f32, font: &Font, image_sizes: &HashMap, ) -> LayoutTree { @@ -1033,7 +1125,15 @@ pub fn layout( // Pre-collapse parent-child margins before positioning. pre_collapse_margins(&mut root); - compute_layout(&mut root, 0.0, 0.0, viewport_width, font, doc); + compute_layout( + &mut root, + 0.0, + 0.0, + viewport_width, + viewport_height, + font, + doc, + ); let height = root.margin_box_height(); LayoutTree { @@ -1066,7 +1166,7 @@ mod tests { fn layout_doc(doc: &Document) -> LayoutTree { let font = test_font(); let sheets = extract_stylesheets(doc); - let styled = resolve_styles(doc, &sheets).unwrap(); + let styled = resolve_styles(doc, &sheets, (800.0, 600.0)).unwrap(); layout(&styled, doc, 800.0, 600.0, &font, &HashMap::new()) } @@ -1075,7 +1175,7 @@ mod tests { let doc = Document::new(); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)); if let Some(styled) = styled { let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); assert_eq!(tree.width, 800.0); @@ -1237,7 +1337,7 @@ mod tests { let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 100.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; let p_box = &body_box.children[0]; @@ -1403,7 +1503,7 @@ mod tests { let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1478,7 +1578,7 @@ p { margin-top: 50px; margin-bottom: 50px; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1509,7 +1609,7 @@ p { margin-top: 50px; margin-bottom: 50px; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1529,7 +1629,7 @@ p { margin-top: 50px; margin-bottom: 50px; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1551,7 +1651,7 @@ p { margin-top: 50px; margin-bottom: 50px; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1577,7 +1677,7 @@ p { margin-top: 50px; margin-bottom: 50px; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1604,7 +1704,7 @@ p { margin-top: 50px; margin-bottom: 50px; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1631,7 +1731,7 @@ p { margin-top: 50px; margin-bottom: 50px; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1658,7 +1758,7 @@ p { margin-top: 50px; margin-bottom: 50px; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1691,7 +1791,7 @@ p { margin-top: 50px; margin-bottom: 50px; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1714,7 +1814,7 @@ p { margin-top: 50px; margin-bottom: 50px; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); // Narrow viewport to force wrapping. let tree = layout(&styled, &doc, 100.0, 600.0, &font, &HashMap::new()); @@ -1747,7 +1847,7 @@ p { margin-top: 50px; margin-bottom: 50px; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1787,7 +1887,7 @@ p { margin: 0; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1825,7 +1925,7 @@ p { margin: 0; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1848,7 +1948,7 @@ p { margin: 0; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1871,7 +1971,7 @@ p { margin: 0; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1892,7 +1992,7 @@ p { margin: 0; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1926,7 +2026,7 @@ p { margin: 0; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1955,7 +2055,7 @@ p { margin-top: 16px; margin-bottom: 16px; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -1991,7 +2091,7 @@ body { margin: 0; border-top: 1px solid black; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2024,7 +2124,7 @@ body { margin: 0; border-top: 1px solid black; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2052,7 +2152,7 @@ body { margin: 0; border-top: 1px solid black; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2087,7 +2187,7 @@ body { margin: 0; border-top: 1px solid black; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2122,7 +2222,7 @@ body { margin: 0; border-top: 1px solid black; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2158,7 +2258,7 @@ body { margin: 0; border-top: 1px solid black; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2187,7 +2287,7 @@ p { margin-top: 5px; margin-bottom: 5px; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2238,7 +2338,7 @@ div { width: 200px; padding: 10px; border: 5px solid black; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2268,7 +2368,7 @@ div { box-sizing: border-box; width: 200px; padding: 10px; border: 5px solid bla let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2298,7 +2398,7 @@ div { box-sizing: border-box; width: 20px; padding: 15px; border: 5px solid blac let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2325,7 +2425,7 @@ body { margin: 0; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2354,7 +2454,7 @@ div { box-sizing: border-box; height: 100px; padding: 10px; border: 5px solid bl let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2380,7 +2480,7 @@ div { height: 100px; padding: 10px; border: 5px solid black; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2410,7 +2510,7 @@ p { margin: 0; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2444,7 +2544,7 @@ p { margin: 0; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2478,7 +2578,7 @@ body { margin: 0; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2504,7 +2604,7 @@ body { margin: 0; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2529,7 +2629,7 @@ div { visibility: collapse; height: 50px; } let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); let body_box = &tree.root.children[0]; @@ -2538,4 +2638,259 @@ div { visibility: collapse; height: 50px; } // Still occupies space (non-table collapse = hidden behavior). assert_eq!(div_box.rect.height, 50.0); } + + // --- Viewport units and percentage resolution tests --- + + #[test] + fn width_50_percent_resolves_to_half_containing_block() { + let html_str = r#" + + +
Half width
+"#; + let doc = we_html::parse_html(html_str); + let font = test_font(); + let sheets = extract_stylesheets(&doc); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); + + let body_box = &tree.root.children[0]; + let div_box = &body_box.children[0]; + assert!( + (div_box.rect.width - 400.0).abs() < 0.01, + "width: 50% should be 400px on 800px viewport, got {}", + div_box.rect.width + ); + } + + #[test] + fn margin_10_percent_resolves_against_containing_block_width() { + let html_str = r#" + + +
Box
+"#; + let doc = we_html::parse_html(html_str); + let font = test_font(); + let sheets = extract_stylesheets(&doc); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); + + let body_box = &tree.root.children[0]; + let div_box = &body_box.children[0]; + // All margins (including top/bottom) resolve against containing block WIDTH. + assert!( + (div_box.margin.left - 80.0).abs() < 0.01, + "margin-left: 10% should be 80px on 800px viewport, got {}", + div_box.margin.left + ); + assert!( + (div_box.margin.top - 80.0).abs() < 0.01, + "margin-top: 10% should be 80px (against width, not height), got {}", + div_box.margin.top + ); + } + + #[test] + fn width_50vw_resolves_to_half_viewport() { + let html_str = r#" + + +
Half viewport
+"#; + let doc = we_html::parse_html(html_str); + let font = test_font(); + let sheets = extract_stylesheets(&doc); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); + + let body_box = &tree.root.children[0]; + let div_box = &body_box.children[0]; + assert!( + (div_box.rect.width - 400.0).abs() < 0.01, + "width: 50vw should be 400px on 800px viewport, got {}", + div_box.rect.width + ); + } + + #[test] + fn height_100vh_resolves_to_full_viewport() { + let html_str = r#" + + +
Full height
+"#; + let doc = we_html::parse_html(html_str); + let font = test_font(); + let sheets = extract_stylesheets(&doc); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); + + let body_box = &tree.root.children[0]; + let div_box = &body_box.children[0]; + assert!( + (div_box.rect.height - 600.0).abs() < 0.01, + "height: 100vh should be 600px on 600px viewport, got {}", + div_box.rect.height + ); + } + + #[test] + fn font_size_5vmin_resolves_to_smaller_dimension() { + let html_str = r#" + + +

Text

+"#; + let doc = we_html::parse_html(html_str); + let font = test_font(); + let sheets = extract_stylesheets(&doc); + // viewport 800x600 → vmin = 600 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); + + let body_box = &tree.root.children[0]; + let p_box = &body_box.children[0]; + // 5vmin = 5% of min(800, 600) = 5% of 600 = 30px + assert!( + (p_box.font_size - 30.0).abs() < 0.01, + "font-size: 5vmin should be 30px, got {}", + p_box.font_size + ); + } + + #[test] + fn nested_percentage_widths_compound() { + let html_str = r#" + + + +
Nested
+ +"#; + let doc = we_html::parse_html(html_str); + let font = test_font(); + let sheets = extract_stylesheets(&doc); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); + + let body_box = &tree.root.children[0]; + let outer_box = &body_box.children[0]; + let inner_box = &outer_box.children[0]; + // outer = 50% of 800 = 400 + assert!( + (outer_box.rect.width - 400.0).abs() < 0.01, + "outer width should be 400px, got {}", + outer_box.rect.width + ); + // inner = 50% of 400 = 200 + assert!( + (inner_box.rect.width - 200.0).abs() < 0.01, + "inner width should be 200px (50% of 400), got {}", + inner_box.rect.width + ); + } + + #[test] + fn padding_percent_resolves_against_width() { + let html_str = r#" + + +
Content
+"#; + let doc = we_html::parse_html(html_str); + let font = test_font(); + let sheets = extract_stylesheets(&doc); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); + + let body_box = &tree.root.children[0]; + let div_box = &body_box.children[0]; + // padding: 5% resolves against containing block width (800px) + assert!( + (div_box.padding.top - 40.0).abs() < 0.01, + "padding-top: 5% should be 40px (5% of 800), got {}", + div_box.padding.top + ); + assert!( + (div_box.padding.left - 40.0).abs() < 0.01, + "padding-left: 5% should be 40px (5% of 800), got {}", + div_box.padding.left + ); + } + + #[test] + fn vmax_uses_larger_dimension() { + let html_str = r#" + + +
Content
+"#; + let doc = we_html::parse_html(html_str); + let font = test_font(); + let sheets = extract_stylesheets(&doc); + // viewport 800x600 → vmax = 800 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); + + let body_box = &tree.root.children[0]; + let div_box = &body_box.children[0]; + // 10vmax = 10% of max(800, 600) = 10% of 800 = 80px + assert!( + (div_box.rect.width - 80.0).abs() < 0.01, + "width: 10vmax should be 80px, got {}", + div_box.rect.width + ); + } + + #[test] + fn height_50_percent_resolves_against_viewport() { + let html_str = r#" + + +
Half height
+"#; + let doc = we_html::parse_html(html_str); + let font = test_font(); + let sheets = extract_stylesheets(&doc); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); + + let body_box = &tree.root.children[0]; + let div_box = &body_box.children[0]; + // height: 50% resolves against viewport height (600) + assert!( + (div_box.rect.height - 300.0).abs() < 0.01, + "height: 50% should be 300px on 600px viewport, got {}", + div_box.rect.height + ); + } } diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 8641992..9101f51 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -490,7 +490,7 @@ mod tests { fn layout_doc(doc: &Document) -> we_layout::LayoutTree { let font = test_font(); let sheets = extract_stylesheets(doc); - let styled = resolve_styles(doc, &sheets).unwrap(); + let styled = resolve_styles(doc, &sheets, (800.0, 600.0)).unwrap(); we_layout::layout( &styled, doc, @@ -551,7 +551,7 @@ mod tests { let doc = Document::new(); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)); if let Some(styled) = styled { let tree = we_layout::layout( &styled, @@ -726,7 +726,7 @@ mod tests { let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = we_layout::layout( &styled, &doc, @@ -760,7 +760,7 @@ mod tests { let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = we_layout::layout( &styled, &doc, @@ -793,7 +793,7 @@ mod tests { let doc = we_html::parse_html(html_str); let font = test_font(); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let tree = we_layout::layout( &styled, &doc, diff --git a/crates/style/src/computed.rs b/crates/style/src/computed.rs index c34ffe2..1997b9a 100644 --- a/crates/style/src/computed.rs +++ b/crates/style/src/computed.rs @@ -149,10 +149,12 @@ pub enum BorderStyle { // LengthOrAuto // --------------------------------------------------------------------------- -/// A computed length (resolved to px) or `auto`. +/// A computed length (resolved to px), a percentage (unresolved), or `auto`. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub enum LengthOrAuto { Length(f32), + /// Percentage value (0.0–100.0), resolved during layout against the containing block. + Percentage(f32), #[default] Auto, } @@ -173,11 +175,11 @@ pub struct ComputedStyle { pub margin_bottom: LengthOrAuto, pub margin_left: LengthOrAuto, - // Box model: padding (no auto for padding) - pub padding_top: f32, - pub padding_right: f32, - pub padding_bottom: f32, - pub padding_left: f32, + // Box model: padding (percentages resolve against containing block width) + pub padding_top: LengthOrAuto, + pub padding_right: LengthOrAuto, + pub padding_bottom: LengthOrAuto, + pub padding_left: LengthOrAuto, // Box model: border width pub border_top_width: f32, @@ -241,10 +243,10 @@ impl Default for ComputedStyle { margin_bottom: LengthOrAuto::Length(0.0), margin_left: LengthOrAuto::Length(0.0), - padding_top: 0.0, - padding_right: 0.0, - padding_bottom: 0.0, - padding_left: 0.0, + padding_top: LengthOrAuto::Length(0.0), + padding_right: LengthOrAuto::Length(0.0), + padding_bottom: LengthOrAuto::Length(0.0), + padding_left: LengthOrAuto::Length(0.0), border_top_width: 0.0, border_right_width: 0.0, @@ -406,20 +408,9 @@ u { // Resolve a CssValue to f32 px given context // --------------------------------------------------------------------------- -fn resolve_length(value: &CssValue, _parent_font_size: f32, current_font_size: f32) -> Option { - match value { - // Em units resolve relative to the element's own computed font-size - // (for properties other than font-size, which has its own handling). - CssValue::Length(n, unit) => Some(resolve_length_unit(*n, *unit, current_font_size)), - CssValue::Percentage(p) => Some((*p / 100.0) as f32 * current_font_size), - CssValue::Zero => Some(0.0), - CssValue::Number(n) if *n == 0.0 => Some(0.0), - _ => None, - } -} - -fn resolve_length_unit(value: f64, unit: LengthUnit, em_base: f32) -> f32 { +fn resolve_length_unit(value: f64, unit: LengthUnit, em_base: f32, viewport: (f32, f32)) -> f32 { let v = value as f32; + let (vw, vh) = viewport; match unit { LengthUnit::Px => v, LengthUnit::Em => v * em_base, @@ -429,21 +420,31 @@ fn resolve_length_unit(value: f64, unit: LengthUnit, em_base: f32) -> f32 { LengthUnit::Mm => v * (96.0 / 25.4), LengthUnit::In => v * 96.0, LengthUnit::Pc => v * 16.0, - // Viewport units: use a reasonable default (can be parameterized later) - LengthUnit::Vw | LengthUnit::Vh | LengthUnit::Vmin | LengthUnit::Vmax => v, + LengthUnit::Vw => v * vw / 100.0, + LengthUnit::Vh => v * vh / 100.0, + LengthUnit::Vmin => v * vw.min(vh) / 100.0, + LengthUnit::Vmax => v * vw.max(vh) / 100.0, } } -fn resolve_length_or_auto( +/// Resolve a CSS value to `LengthOrAuto` for layout properties (width, height, +/// margin, padding, top/right/bottom/left). Percentages are preserved as +/// `LengthOrAuto::Percentage` for later resolution during layout against the +/// containing block. +fn resolve_layout_length_or_auto( value: &CssValue, - parent_font_size: f32, current_font_size: f32, + viewport: (f32, f32), ) -> LengthOrAuto { match value { CssValue::Auto => LengthOrAuto::Auto, - _ => resolve_length(value, parent_font_size, current_font_size) - .map(LengthOrAuto::Length) - .unwrap_or(LengthOrAuto::Auto), + CssValue::Percentage(p) => LengthOrAuto::Percentage(*p as f32), + CssValue::Length(n, unit) => { + LengthOrAuto::Length(resolve_length_unit(*n, *unit, current_font_size, viewport)) + } + CssValue::Zero => LengthOrAuto::Length(0.0), + CssValue::Number(n) if *n == 0.0 => LengthOrAuto::Length(0.0), + _ => LengthOrAuto::Auto, } } @@ -465,6 +466,7 @@ fn apply_property( property: &str, value: &CssValue, parent: &ComputedStyle, + viewport: (f32, f32), ) { // Handle inherit/initial/unset match value { @@ -503,45 +505,37 @@ fn apply_property( }; } - // Margin + // Margin (percentages preserved for layout resolution) "margin-top" => { - style.margin_top = resolve_length_or_auto(value, parent_fs, current_fs); + style.margin_top = resolve_layout_length_or_auto(value, current_fs, viewport); } "margin-right" => { - style.margin_right = resolve_length_or_auto(value, parent_fs, current_fs); + style.margin_right = resolve_layout_length_or_auto(value, current_fs, viewport); } "margin-bottom" => { - style.margin_bottom = resolve_length_or_auto(value, parent_fs, current_fs); + style.margin_bottom = resolve_layout_length_or_auto(value, current_fs, viewport); } "margin-left" => { - style.margin_left = resolve_length_or_auto(value, parent_fs, current_fs); + style.margin_left = resolve_layout_length_or_auto(value, current_fs, viewport); } - // Padding + // Padding (percentages preserved for layout resolution) "padding-top" => { - if let Some(v) = resolve_length(value, parent_fs, current_fs) { - style.padding_top = v; - } + style.padding_top = resolve_layout_length_or_auto(value, current_fs, viewport); } "padding-right" => { - if let Some(v) = resolve_length(value, parent_fs, current_fs) { - style.padding_right = v; - } + style.padding_right = resolve_layout_length_or_auto(value, current_fs, viewport); } "padding-bottom" => { - if let Some(v) = resolve_length(value, parent_fs, current_fs) { - style.padding_bottom = v; - } + style.padding_bottom = resolve_layout_length_or_auto(value, current_fs, viewport); } "padding-left" => { - if let Some(v) = resolve_length(value, parent_fs, current_fs) { - style.padding_left = v; - } + style.padding_left = resolve_layout_length_or_auto(value, current_fs, viewport); } // Border width "border-top-width" | "border-right-width" | "border-bottom-width" | "border-left-width" => { - let w = resolve_border_width(value, parent_fs); + let w = resolve_border_width(value, parent_fs, viewport); match property { "border-top-width" => style.border_top_width = w, "border-right-width" => style.border_right_width = w, @@ -553,7 +547,7 @@ fn apply_property( // Border width shorthand (single value applied to all sides) "border-width" => { - let w = resolve_border_width(value, parent_fs); + let w = resolve_border_width(value, parent_fs, viewport); style.border_top_width = w; style.border_right_width = w; style.border_bottom_width = w; @@ -602,12 +596,12 @@ fn apply_property( } } - // Dimensions + // Dimensions (percentages preserved for layout resolution) "width" => { - style.width = resolve_length_or_auto(value, parent_fs, current_fs); + style.width = resolve_layout_length_or_auto(value, current_fs, viewport); } "height" => { - style.height = resolve_length_or_auto(value, parent_fs, current_fs); + style.height = resolve_layout_length_or_auto(value, current_fs, viewport); } // Box sizing @@ -634,7 +628,7 @@ fn apply_property( "font-size" => { match value { CssValue::Length(n, unit) => { - style.font_size = resolve_length_unit(*n, *unit, parent_fs); + style.font_size = resolve_length_unit(*n, *unit, parent_fs, viewport); } CssValue::Percentage(p) => { style.font_size = (*p / 100.0) as f32 * parent_fs; @@ -734,7 +728,7 @@ fn apply_property( style.line_height = *n as f32 * style.font_size; } CssValue::Length(n, unit) => { - style.line_height = resolve_length_unit(*n, *unit, style.font_size); + style.line_height = resolve_length_unit(*n, *unit, style.font_size, viewport); } CssValue::Percentage(p) => { style.line_height = (*p / 100.0) as f32 * style.font_size; @@ -763,11 +757,11 @@ fn apply_property( }; } - // Position offsets - "top" => style.top = resolve_length_or_auto(value, parent_fs, current_fs), - "right" => style.right = resolve_length_or_auto(value, parent_fs, current_fs), - "bottom" => style.bottom = resolve_length_or_auto(value, parent_fs, current_fs), - "left" => style.left = resolve_length_or_auto(value, parent_fs, current_fs), + // Position offsets (percentages preserved for layout resolution) + "top" => style.top = resolve_layout_length_or_auto(value, current_fs, viewport), + "right" => style.right = resolve_layout_length_or_auto(value, current_fs, viewport), + "bottom" => style.bottom = resolve_layout_length_or_auto(value, current_fs, viewport), + "left" => style.left = resolve_layout_length_or_auto(value, current_fs, viewport), // Overflow "overflow" => { @@ -800,9 +794,9 @@ fn apply_property( } } -fn resolve_border_width(value: &CssValue, em_base: f32) -> f32 { +fn resolve_border_width(value: &CssValue, em_base: f32, viewport: (f32, f32)) -> f32 { match value { - CssValue::Length(n, unit) => resolve_length_unit(*n, *unit, em_base), + CssValue::Length(n, unit) => resolve_length_unit(*n, *unit, em_base, viewport), CssValue::Zero => 0.0, CssValue::Number(n) if *n == 0.0 => 0.0, CssValue::Keyword(k) => match k.as_str() { @@ -959,7 +953,11 @@ fn collect_style_elements(doc: &Document, node: NodeId, stylesheets: &mut Vec Option { +pub fn resolve_styles( + doc: &Document, + author_stylesheets: &[Stylesheet], + viewport: (f32, f32), +) -> Option { let ua = ua_stylesheet(); // Combine UA + author stylesheets into a single list for rule collection. @@ -972,7 +970,7 @@ pub fn resolve_styles(doc: &Document, author_stylesheets: &[Stylesheet]) -> Opti } let root = doc.root(); - resolve_node(doc, root, &combined, &ComputedStyle::default()) + resolve_node(doc, root, &combined, &ComputedStyle::default(), viewport) } fn resolve_node( @@ -980,13 +978,14 @@ fn resolve_node( node: NodeId, stylesheet: &Stylesheet, parent_style: &ComputedStyle, + viewport: (f32, f32), ) -> Option { match doc.node_data(node) { NodeData::Document => { // Document node: resolve children, return first element child or wrapper. let mut children = Vec::new(); for child in doc.children(node) { - if let Some(styled) = resolve_node(doc, child, stylesheet, parent_style) { + if let Some(styled) = resolve_node(doc, child, stylesheet, parent_style, viewport) { children.push(styled); } } @@ -1003,7 +1002,7 @@ fn resolve_node( } } NodeData::Element { .. } => { - let style = compute_style_for_element(doc, node, stylesheet, parent_style); + let style = compute_style_for_element(doc, node, stylesheet, parent_style, viewport); if style.display == Display::None { return None; @@ -1011,7 +1010,7 @@ fn resolve_node( let mut children = Vec::new(); for child in doc.children(node) { - if let Some(styled) = resolve_node(doc, child, stylesheet, &style) { + if let Some(styled) = resolve_node(doc, child, stylesheet, &style, viewport) { children.push(styled); } } @@ -1043,6 +1042,7 @@ fn compute_style_for_element( node: NodeId, stylesheet: &Stylesheet, parent_style: &ComputedStyle, + viewport: (f32, f32), ) -> ComputedStyle { // Start from initial values, inheriting inherited properties from parent let mut style = ComputedStyle { @@ -1097,7 +1097,7 @@ fn compute_style_for_element( // Step 5: Apply normal declarations (already in specificity order) for (prop, value) in &normal_decls { - apply_property(&mut style, prop, value, parent_style); + apply_property(&mut style, prop, value, parent_style, viewport); } // Step 6: Apply inline style normal declarations (override stylesheet normals) @@ -1106,18 +1106,18 @@ fn compute_style_for_element( let property = decl.property.as_str(); if let Some(longhands) = expand_shorthand(property, &decl.value, false) { for lh in &longhands { - apply_property(&mut style, &lh.property, &lh.value, parent_style); + apply_property(&mut style, &lh.property, &lh.value, parent_style, viewport); } } else { let value = parse_value(&decl.value); - apply_property(&mut style, property, &value, parent_style); + apply_property(&mut style, property, &value, parent_style, viewport); } } } // Step 7: Apply !important declarations (override everything normal) for (prop, value) in &important_decls { - apply_property(&mut style, prop, value, parent_style); + apply_property(&mut style, prop, value, parent_style, viewport); } // Step 8: Apply inline style !important declarations (highest priority) @@ -1126,11 +1126,11 @@ fn compute_style_for_element( let property = decl.property.as_str(); if let Some(longhands) = expand_shorthand(property, &decl.value, true) { for lh in &longhands { - apply_property(&mut style, &lh.property, &lh.value, parent_style); + apply_property(&mut style, &lh.property, &lh.value, parent_style, viewport); } } else { let value = parse_value(&decl.value); - apply_property(&mut style, property, &value, parent_style); + apply_property(&mut style, property, &value, parent_style, viewport); } } } @@ -1180,7 +1180,7 @@ mod tests { #[test] fn ua_body_has_8px_margin() { let (doc, _, _, _) = make_doc_with_body(); - let styled = resolve_styles(&doc, &[]).unwrap(); + let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); // styled is , first child is let body = &styled.children[0]; assert_eq!(body.style.margin_top, LengthOrAuto::Length(8.0)); @@ -1197,7 +1197,7 @@ mod tests { doc.append_child(body, h1); doc.append_child(h1, text); - let styled = resolve_styles(&doc, &[]).unwrap(); + let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let h1_node = &body_node.children[0]; @@ -1212,7 +1212,7 @@ mod tests { let h2 = doc.create_element("h2"); doc.append_child(body, h2); - let styled = resolve_styles(&doc, &[]).unwrap(); + let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let h2_node = &body_node.children[0]; @@ -1228,7 +1228,7 @@ mod tests { doc.append_child(body, p); doc.append_child(p, text); - let styled = resolve_styles(&doc, &[]).unwrap(); + let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; @@ -1243,7 +1243,7 @@ mod tests { let div = doc.create_element("div"); doc.append_child(body, div); - let styled = resolve_styles(&doc, &[]).unwrap(); + let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; assert_eq!(div_node.style.display, Display::Block); @@ -1259,7 +1259,7 @@ mod tests { doc.append_child(p, span); doc.append_child(span, text); - let styled = resolve_styles(&doc, &[]).unwrap(); + let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; let span_node = &p_node.children[0]; @@ -1274,7 +1274,7 @@ mod tests { doc.append_child(html, head); doc.append_child(head, title); - let styled = resolve_styles(&doc, &[]).unwrap(); + let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); // head should not appear in styled tree (display: none) for child in &styled.children { if let NodeData::Element { tag_name, .. } = doc.node_data(child.node) { @@ -1296,7 +1296,7 @@ mod tests { doc.append_child(p, text); let ss = Parser::parse("p { color: red; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; assert_eq!(p_node.style.color, Color::rgb(255, 0, 0)); @@ -1309,7 +1309,7 @@ mod tests { doc.append_child(body, div); let ss = Parser::parse("div { background-color: blue; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; assert_eq!(div_node.style.background_color, Color::rgb(0, 0, 255)); @@ -1322,7 +1322,7 @@ mod tests { doc.append_child(body, p); let ss = Parser::parse("p { font-size: 24px; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; assert_eq!(p_node.style.font_size, 24.0); @@ -1335,7 +1335,7 @@ mod tests { doc.append_child(body, div); let ss = Parser::parse("div { margin-top: 20px; margin-bottom: 10px; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; assert_eq!(div_node.style.margin_top, LengthOrAuto::Length(20.0)); @@ -1357,7 +1357,7 @@ mod tests { // .highlight (0,1,0) > p (0,0,1) let ss = Parser::parse("p { color: red; } .highlight { color: green; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; assert_eq!(p_node.style.color, Color::rgb(0, 128, 0)); @@ -1373,7 +1373,7 @@ mod tests { // Same specificity: later wins let ss = Parser::parse("p { color: red; } p { color: blue; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; assert_eq!(p_node.style.color, Color::rgb(0, 0, 255)); @@ -1390,7 +1390,7 @@ mod tests { // #main (1,0,0) has higher specificity, but p has !important let ss = Parser::parse("#main { color: blue; } p { color: red !important; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; assert_eq!(p_node.style.color, Color::rgb(255, 0, 0)); @@ -1411,7 +1411,7 @@ mod tests { doc.append_child(p, text); let ss = Parser::parse("div { color: green; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let p_node = &div_node.children[0]; @@ -1432,7 +1432,7 @@ mod tests { doc.append_child(p, text); let ss = Parser::parse("div { font-size: 20px; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let p_node = &div_node.children[0]; @@ -1452,7 +1452,7 @@ mod tests { doc.append_child(span, text); let ss = Parser::parse("div { margin-top: 50px; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let span_node = &div_node.children[0]; @@ -1477,7 +1477,7 @@ mod tests { doc.append_child(p, text); let ss = Parser::parse("div { background-color: red; } p { background-color: inherit; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let p_node = &div_node.children[0]; @@ -1498,7 +1498,7 @@ mod tests { // div sets color to red, p resets to initial (black) let ss = Parser::parse("div { color: red; } p { color: initial; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let p_node = &div_node.children[0]; @@ -1519,7 +1519,7 @@ mod tests { // color is inherited, so unset => inherit let ss = Parser::parse("div { color: green; } p { color: unset; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let p_node = &div_node.children[0]; @@ -1539,7 +1539,7 @@ mod tests { // margin is non-inherited, so unset => initial (0) let ss = Parser::parse("p { margin-top: unset; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; @@ -1560,7 +1560,7 @@ mod tests { doc.append_child(div, text); let ss = Parser::parse("div { font-size: 20px; margin-top: 2em; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; @@ -1580,7 +1580,7 @@ mod tests { doc.append_child(p, text); let ss = Parser::parse("div { font-size: 20px; } p { font-size: 1.5em; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let p_node = &div_node.children[0]; @@ -1604,7 +1604,7 @@ mod tests { doc.append_child(p, text); let ss = Parser::parse("p { color: red; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; @@ -1622,7 +1622,7 @@ mod tests { doc.append_child(p, text); let ss = Parser::parse("p { color: red !important; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; @@ -1643,7 +1643,7 @@ mod tests { doc.append_child(div, text); let ss = Parser::parse("div { margin: 10px 20px; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; @@ -1662,14 +1662,14 @@ mod tests { doc.append_child(div, text); let ss = Parser::parse("div { padding: 5px; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; - assert_eq!(div_node.style.padding_top, 5.0); - assert_eq!(div_node.style.padding_right, 5.0); - assert_eq!(div_node.style.padding_bottom, 5.0); - assert_eq!(div_node.style.padding_left, 5.0); + assert_eq!(div_node.style.padding_top, LengthOrAuto::Length(5.0)); + assert_eq!(div_node.style.padding_right, LengthOrAuto::Length(5.0)); + assert_eq!(div_node.style.padding_bottom, LengthOrAuto::Length(5.0)); + assert_eq!(div_node.style.padding_left, LengthOrAuto::Length(5.0)); } // ----------------------------------------------------------------------- @@ -1686,7 +1686,7 @@ mod tests { // UA gives p margin-top=1em=16px. Author overrides to 0. let ss = Parser::parse("p { margin-top: 0; margin-bottom: 0; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; @@ -1707,7 +1707,7 @@ mod tests { doc.append_child(p, text); let ss = Parser::parse("p { color: red; font-size: 20px; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; let text_node = &p_node.children[0]; @@ -1730,7 +1730,7 @@ mod tests { let ss1 = Parser::parse("p { color: red; }"); let ss2 = Parser::parse("p { color: blue; }"); - let styled = resolve_styles(&doc, &[ss1, ss2]).unwrap(); + let styled = resolve_styles(&doc, &[ss1, ss2], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; @@ -1751,7 +1751,7 @@ mod tests { doc.append_child(div, text); let ss = Parser::parse("div { border: 2px solid red; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; @@ -1773,7 +1773,7 @@ mod tests { doc.append_child(div, text); let ss = Parser::parse("div { position: relative; top: 10px; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; @@ -1800,7 +1800,7 @@ mod tests { doc.append_child(div2, t2); let ss = Parser::parse(".hidden { display: none; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; // Only div1 should appear @@ -1822,7 +1822,7 @@ mod tests { doc.append_child(p, text); let ss = Parser::parse("div { visibility: hidden; }"); - let styled = resolve_styles(&doc, &[ss]).unwrap(); + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let p_node = &div_node.children[0]; @@ -1920,7 +1920,7 @@ mod tests { doc.append_child(p, text); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; @@ -1941,7 +1941,7 @@ mod tests { let sheets = extract_stylesheets(&doc); assert!(sheets.is_empty()); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; @@ -1967,7 +1967,7 @@ mod tests { doc.append_child(p, text); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; @@ -2003,7 +2003,7 @@ mod tests { let sheets = extract_stylesheets(&doc); assert_eq!(sheets.len(), 2); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; @@ -2017,7 +2017,7 @@ mod tests {
Test
"#; let doc = we_html::parse_html(html_str); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let div = &body.children[0]; assert_eq!(div.style.box_sizing, BoxSizing::ContentBox); @@ -2030,7 +2030,7 @@ mod tests {
Test
"#; let doc = we_html::parse_html(html_str); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let div = &body.children[0]; assert_eq!(div.style.box_sizing, BoxSizing::BorderBox); @@ -2043,7 +2043,7 @@ mod tests {

Child

"#; let doc = we_html::parse_html(html_str); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let parent = &body.children[0]; let child = &parent.children[0]; diff --git a/crates/style/tests/style_extraction.rs b/crates/style/tests/style_extraction.rs index 41d450e..6e01623 100644 --- a/crates/style/tests/style_extraction.rs +++ b/crates/style/tests/style_extraction.rs @@ -23,7 +23,7 @@ h1 { color: blue; } let sheets = extract_stylesheets(&doc); assert_eq!(sheets.len(), 1); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); // styled root is // Find body (first visible child) let body = &styled.children[0]; @@ -52,7 +52,7 @@ fn html_with_multiple_style_elements() { let sheets = extract_stylesheets(&doc); assert_eq!(sheets.len(), 2); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let p = &body.children[0]; @@ -75,7 +75,7 @@ fn html_with_inline_style_attribute() { let doc = we_html::parse_html(html); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let p = &body.children[0]; @@ -97,7 +97,7 @@ fn html_no_style_uses_ua_defaults() { let sheets = extract_stylesheets(&doc); assert!(sheets.is_empty()); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; // Body should have 8px margin from UA stylesheet. @@ -122,7 +122,7 @@ fn style_element_display_none_in_styled_tree() { let doc = we_html::parse_html(html); let sheets = extract_stylesheets(&doc); - let styled = resolve_styles(&doc, &sheets).unwrap(); + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); // The and