we (web engine): Experimental web browser project to understand the limits of Claude

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) <noreply@anthropic.com>

+566 -207
+5 -1
crates/browser/src/main.rs
··· 78 78 } 79 79 80 80 // Resolve computed styles from DOM + stylesheet. 81 - let styled = match resolve_styles(&page.doc, std::slice::from_ref(&page.stylesheet)) { 81 + let styled = match resolve_styles( 82 + &page.doc, 83 + std::slice::from_ref(&page.stylesheet), 84 + (width as f32, height as f32), 85 + ) { 82 86 Some(s) => s, 83 87 None => return, 84 88 };
+432 -77
crates/layout/src/lib.rs
··· 97 97 pub overflow: Overflow, 98 98 /// CSS `box-sizing` property. 99 99 pub box_sizing: BoxSizing, 100 - /// CSS `width` property (explicit or auto). 100 + /// CSS `width` property (explicit or auto, may contain percentage). 101 101 pub css_width: LengthOrAuto, 102 - /// CSS `height` property (explicit or auto). 102 + /// CSS `height` property (explicit or auto, may contain percentage). 103 103 pub css_height: LengthOrAuto, 104 + /// CSS margin values (may contain percentages for layout resolution). 105 + pub css_margin: [LengthOrAuto; 4], 106 + /// CSS padding values (may contain percentages for layout resolution). 107 + pub css_padding: [LengthOrAuto; 4], 108 + /// CSS position offset values (top, right, bottom, left) for relative positioning. 109 + pub css_offsets: [LengthOrAuto; 4], 104 110 /// CSS `visibility` property. 105 111 pub visibility: Visibility, 106 112 } ··· 140 146 box_sizing: style.box_sizing, 141 147 css_width: style.width, 142 148 css_height: style.height, 149 + css_margin: [ 150 + style.margin_top, 151 + style.margin_right, 152 + style.margin_bottom, 153 + style.margin_left, 154 + ], 155 + css_padding: [ 156 + style.padding_top, 157 + style.padding_right, 158 + style.padding_bottom, 159 + style.padding_left, 160 + ], 161 + css_offsets: [style.top, style.right, style.bottom, style.left], 143 162 visibility: style.visibility, 144 163 } 145 164 } ··· 197 216 // Resolve LengthOrAuto to f32 198 217 // --------------------------------------------------------------------------- 199 218 200 - fn resolve_length(value: LengthOrAuto) -> f32 { 219 + /// Resolve a `LengthOrAuto` to px. Percentages are resolved against 220 + /// `reference` (typically the containing block width). Auto resolves to 0. 221 + fn resolve_length_against(value: LengthOrAuto, reference: f32) -> f32 { 201 222 match value { 202 223 LengthOrAuto::Length(px) => px, 224 + LengthOrAuto::Percentage(p) => p / 100.0 * reference, 203 225 LengthOrAuto::Auto => 0.0, 204 226 } 205 227 } 206 228 207 229 /// Resolve horizontal offset for `position: relative`. 208 230 /// If both `left` and `right` are specified, `left` wins (CSS2 §9.4.3, ltr). 209 - fn resolve_relative_horizontal(left: LengthOrAuto, right: LengthOrAuto) -> f32 { 231 + fn resolve_relative_horizontal(left: LengthOrAuto, right: LengthOrAuto, cb_width: f32) -> f32 { 210 232 match left { 211 233 LengthOrAuto::Length(px) => px, 234 + LengthOrAuto::Percentage(p) => p / 100.0 * cb_width, 212 235 LengthOrAuto::Auto => match right { 213 236 LengthOrAuto::Length(px) => -px, 237 + LengthOrAuto::Percentage(p) => -(p / 100.0 * cb_width), 214 238 LengthOrAuto::Auto => 0.0, 215 239 }, 216 240 } ··· 218 242 219 243 /// Resolve vertical offset for `position: relative`. 220 244 /// If both `top` and `bottom` are specified, `top` wins (CSS2 §9.4.3). 221 - fn resolve_relative_vertical(top: LengthOrAuto, bottom: LengthOrAuto) -> f32 { 245 + fn resolve_relative_vertical(top: LengthOrAuto, bottom: LengthOrAuto, cb_height: f32) -> f32 { 222 246 match top { 223 247 LengthOrAuto::Length(px) => px, 248 + LengthOrAuto::Percentage(p) => p / 100.0 * cb_height, 224 249 LengthOrAuto::Auto => match bottom { 225 250 LengthOrAuto::Length(px) => -px, 251 + LengthOrAuto::Percentage(p) => -(p / 100.0 * cb_height), 226 252 LengthOrAuto::Auto => 0.0, 227 253 }, 228 254 } ··· 263 289 return None; 264 290 } 265 291 292 + // Margin and padding: resolve absolute lengths now; percentages 293 + // will be re-resolved in compute_layout against containing block. 294 + // Use 0.0 as a placeholder reference for percentages — they'll be 295 + // resolved properly in compute_layout. 266 296 let margin = EdgeSizes { 267 - top: resolve_length(style.margin_top), 268 - right: resolve_length(style.margin_right), 269 - bottom: resolve_length(style.margin_bottom), 270 - left: resolve_length(style.margin_left), 297 + top: resolve_length_against(style.margin_top, 0.0), 298 + right: resolve_length_against(style.margin_right, 0.0), 299 + bottom: resolve_length_against(style.margin_bottom, 0.0), 300 + left: resolve_length_against(style.margin_left, 0.0), 271 301 }; 272 302 let padding = EdgeSizes { 273 - top: style.padding_top, 274 - right: style.padding_right, 275 - bottom: style.padding_bottom, 276 - left: style.padding_left, 303 + top: resolve_length_against(style.padding_top, 0.0), 304 + right: resolve_length_against(style.padding_right, 0.0), 305 + bottom: resolve_length_against(style.padding_bottom, 0.0), 306 + left: resolve_length_against(style.padding_left, 0.0), 277 307 }; 278 308 let border = EdgeSizes { 279 309 top: if style.border_top_style != BorderStyle::None { ··· 326 356 b.replaced_size = Some((w, h)); 327 357 } 328 358 329 - // Compute relative position offset. 330 - if style.position == Position::Relative { 331 - let dx = resolve_relative_horizontal(style.left, style.right); 332 - let dy = resolve_relative_vertical(style.top, style.bottom); 333 - b.relative_offset = (dx, dy); 334 - } 359 + // Relative position offsets are resolved in compute_layout 360 + // where the containing block dimensions are known. 335 361 336 362 Some(b) 337 363 } ··· 421 447 // --------------------------------------------------------------------------- 422 448 423 449 /// Position and size a layout box within `available_width` at position (`x`, `y`). 450 + /// 451 + /// `available_width` is the containing block width — used as the reference for 452 + /// percentage widths, margins, and paddings (per CSS spec, even vertical margins/ 453 + /// padding resolve against the containing block width). 424 454 fn compute_layout( 425 455 b: &mut LayoutBox, 426 456 x: f32, 427 457 y: f32, 428 458 available_width: f32, 459 + viewport_height: f32, 429 460 font: &Font, 430 461 doc: &Document, 431 462 ) { 463 + // Resolve percentage margins against containing block width. 464 + // Only re-resolve percentages — absolute margins may have been modified 465 + // by margin collapsing and must not be overwritten. 466 + if matches!(b.css_margin[0], LengthOrAuto::Percentage(_)) { 467 + b.margin.top = resolve_length_against(b.css_margin[0], available_width); 468 + } 469 + if matches!(b.css_margin[1], LengthOrAuto::Percentage(_)) { 470 + b.margin.right = resolve_length_against(b.css_margin[1], available_width); 471 + } 472 + if matches!(b.css_margin[2], LengthOrAuto::Percentage(_)) { 473 + b.margin.bottom = resolve_length_against(b.css_margin[2], available_width); 474 + } 475 + if matches!(b.css_margin[3], LengthOrAuto::Percentage(_)) { 476 + b.margin.left = resolve_length_against(b.css_margin[3], available_width); 477 + } 478 + 479 + // Resolve percentage padding against containing block width. 480 + if matches!(b.css_padding[0], LengthOrAuto::Percentage(_)) { 481 + b.padding.top = resolve_length_against(b.css_padding[0], available_width); 482 + } 483 + if matches!(b.css_padding[1], LengthOrAuto::Percentage(_)) { 484 + b.padding.right = resolve_length_against(b.css_padding[1], available_width); 485 + } 486 + if matches!(b.css_padding[2], LengthOrAuto::Percentage(_)) { 487 + b.padding.bottom = resolve_length_against(b.css_padding[2], available_width); 488 + } 489 + if matches!(b.css_padding[3], LengthOrAuto::Percentage(_)) { 490 + b.padding.left = resolve_length_against(b.css_padding[3], available_width); 491 + } 492 + 432 493 let content_x = x + b.margin.left + b.border.left + b.padding.left; 433 494 let content_y = y + b.margin.top + b.border.top + b.padding.top; 434 495 ··· 440 501 BoxSizing::ContentBox => w.max(0.0), 441 502 BoxSizing::BorderBox => (w - horizontal_extra).max(0.0), 442 503 }, 504 + LengthOrAuto::Percentage(p) => { 505 + let resolved = p / 100.0 * available_width; 506 + match b.box_sizing { 507 + BoxSizing::ContentBox => resolved.max(0.0), 508 + BoxSizing::BorderBox => (resolved - horizontal_extra).max(0.0), 509 + } 510 + } 443 511 LengthOrAuto::Auto => { 444 512 (available_width - b.margin.left - b.margin.right - horizontal_extra).max(0.0) 445 513 } ··· 451 519 452 520 // Replaced elements (e.g., <img>) have intrinsic dimensions. 453 521 if let Some((rw, rh)) = b.replaced_size { 454 - // Use CSS width/height if specified, otherwise use replaced dimensions. 455 - // Content width is the minimum of replaced width and available width. 456 522 b.rect.width = rw.min(content_width); 457 523 b.rect.height = rh; 458 - apply_relative_offset(b); 524 + apply_relative_offset(b, available_width, viewport_height); 459 525 return; 460 526 } 461 527 462 528 match &b.box_type { 463 529 BoxType::Block(_) | BoxType::Anonymous => { 464 530 if has_block_children(b) { 465 - layout_block_children(b, font, doc); 531 + layout_block_children(b, viewport_height, font, doc); 466 532 } else { 467 533 layout_inline_children(b, font, doc); 468 534 } ··· 473 539 } 474 540 475 541 // Apply explicit CSS height (adjusted for box-sizing), overriding auto height. 476 - if let LengthOrAuto::Length(h) = b.css_height { 477 - let vertical_extra = b.border.top + b.border.bottom + b.padding.top + b.padding.bottom; 478 - b.rect.height = match b.box_sizing { 479 - BoxSizing::ContentBox => h.max(0.0), 480 - BoxSizing::BorderBox => (h - vertical_extra).max(0.0), 481 - }; 542 + match b.css_height { 543 + LengthOrAuto::Length(h) => { 544 + let vertical_extra = b.border.top + b.border.bottom + b.padding.top + b.padding.bottom; 545 + b.rect.height = match b.box_sizing { 546 + BoxSizing::ContentBox => h.max(0.0), 547 + BoxSizing::BorderBox => (h - vertical_extra).max(0.0), 548 + }; 549 + } 550 + LengthOrAuto::Percentage(p) => { 551 + // Height percentage resolves against containing block height. 552 + // For the root element, use viewport height. 553 + let cb_height = viewport_height; 554 + let resolved = p / 100.0 * cb_height; 555 + let vertical_extra = b.border.top + b.border.bottom + b.padding.top + b.padding.bottom; 556 + b.rect.height = match b.box_sizing { 557 + BoxSizing::ContentBox => resolved.max(0.0), 558 + BoxSizing::BorderBox => (resolved - vertical_extra).max(0.0), 559 + }; 560 + } 561 + LengthOrAuto::Auto => {} 482 562 } 483 563 484 - apply_relative_offset(b); 564 + apply_relative_offset(b, available_width, viewport_height); 485 565 } 486 566 487 567 /// Apply `position: relative` offset to a box and all its descendants. 488 568 /// 489 - /// This shifts the visual position without affecting the normal-flow layout 490 - /// of surrounding elements (the original space is preserved). 491 - fn apply_relative_offset(b: &mut LayoutBox) { 492 - let (dx, dy) = b.relative_offset; 569 + /// Resolves the CSS position offsets (which may contain percentages) and 570 + /// shifts the visual position without affecting the normal-flow layout. 571 + fn apply_relative_offset(b: &mut LayoutBox, cb_width: f32, cb_height: f32) { 572 + if b.position != Position::Relative { 573 + return; 574 + } 575 + let [top, right, bottom, left] = b.css_offsets; 576 + let dx = resolve_relative_horizontal(left, right, cb_width); 577 + let dy = resolve_relative_vertical(top, bottom, cb_height); 578 + b.relative_offset = (dx, dy); 493 579 if dx == 0.0 && dy == 0.0 { 494 580 return; 495 581 } ··· 627 713 /// Handles adjacent-sibling collapsing, empty-block collapsing, and 628 714 /// parent-child internal spacing (the parent's external margins were already 629 715 /// updated by `pre_collapse_margins`). 630 - fn layout_block_children(parent: &mut LayoutBox, font: &Font, doc: &Document) { 716 + fn layout_block_children( 717 + parent: &mut LayoutBox, 718 + viewport_height: f32, 719 + font: &Font, 720 + doc: &Document, 721 + ) { 631 722 let content_x = parent.rect.x; 632 723 let content_width = parent.rect.width; 633 724 let mut cursor_y = parent.rect.y; ··· 685 776 content_x, 686 777 y_for_child, 687 778 content_width, 779 + viewport_height, 688 780 font, 689 781 doc, 690 782 ); ··· 1015 1107 styled_root: &StyledNode, 1016 1108 doc: &Document, 1017 1109 viewport_width: f32, 1018 - _viewport_height: f32, 1110 + viewport_height: f32, 1019 1111 font: &Font, 1020 1112 image_sizes: &HashMap<NodeId, (f32, f32)>, 1021 1113 ) -> LayoutTree { ··· 1033 1125 // Pre-collapse parent-child margins before positioning. 1034 1126 pre_collapse_margins(&mut root); 1035 1127 1036 - compute_layout(&mut root, 0.0, 0.0, viewport_width, font, doc); 1128 + compute_layout( 1129 + &mut root, 1130 + 0.0, 1131 + 0.0, 1132 + viewport_width, 1133 + viewport_height, 1134 + font, 1135 + doc, 1136 + ); 1037 1137 1038 1138 let height = root.margin_box_height(); 1039 1139 LayoutTree { ··· 1066 1166 fn layout_doc(doc: &Document) -> LayoutTree { 1067 1167 let font = test_font(); 1068 1168 let sheets = extract_stylesheets(doc); 1069 - let styled = resolve_styles(doc, &sheets).unwrap(); 1169 + let styled = resolve_styles(doc, &sheets, (800.0, 600.0)).unwrap(); 1070 1170 layout(&styled, doc, 800.0, 600.0, &font, &HashMap::new()) 1071 1171 } 1072 1172 ··· 1075 1175 let doc = Document::new(); 1076 1176 let font = test_font(); 1077 1177 let sheets = extract_stylesheets(&doc); 1078 - let styled = resolve_styles(&doc, &sheets); 1178 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)); 1079 1179 if let Some(styled) = styled { 1080 1180 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1081 1181 assert_eq!(tree.width, 800.0); ··· 1237 1337 1238 1338 let font = test_font(); 1239 1339 let sheets = extract_stylesheets(&doc); 1240 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1340 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1241 1341 let tree = layout(&styled, &doc, 100.0, 600.0, &font, &HashMap::new()); 1242 1342 let body_box = &tree.root.children[0]; 1243 1343 let p_box = &body_box.children[0]; ··· 1403 1503 1404 1504 let font = test_font(); 1405 1505 let sheets = extract_stylesheets(&doc); 1406 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1506 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1407 1507 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1408 1508 let body_box = &tree.root.children[0]; 1409 1509 ··· 1478 1578 let doc = we_html::parse_html(html_str); 1479 1579 let font = test_font(); 1480 1580 let sheets = extract_stylesheets(&doc); 1481 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1581 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1482 1582 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1483 1583 1484 1584 let body_box = &tree.root.children[0]; ··· 1509 1609 let doc = we_html::parse_html(html_str); 1510 1610 let font = test_font(); 1511 1611 let sheets = extract_stylesheets(&doc); 1512 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1612 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1513 1613 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1514 1614 1515 1615 let body_box = &tree.root.children[0]; ··· 1529 1629 let doc = we_html::parse_html(html_str); 1530 1630 let font = test_font(); 1531 1631 let sheets = extract_stylesheets(&doc); 1532 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1632 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1533 1633 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1534 1634 1535 1635 let body_box = &tree.root.children[0]; ··· 1551 1651 let doc = we_html::parse_html(html_str); 1552 1652 let font = test_font(); 1553 1653 let sheets = extract_stylesheets(&doc); 1554 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1654 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1555 1655 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1556 1656 1557 1657 let body_box = &tree.root.children[0]; ··· 1577 1677 let doc = we_html::parse_html(html_str); 1578 1678 let font = test_font(); 1579 1679 let sheets = extract_stylesheets(&doc); 1580 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1680 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1581 1681 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1582 1682 1583 1683 let body_box = &tree.root.children[0]; ··· 1604 1704 let doc = we_html::parse_html(html_str); 1605 1705 let font = test_font(); 1606 1706 let sheets = extract_stylesheets(&doc); 1607 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1707 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1608 1708 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1609 1709 1610 1710 let body_box = &tree.root.children[0]; ··· 1631 1731 let doc = we_html::parse_html(html_str); 1632 1732 let font = test_font(); 1633 1733 let sheets = extract_stylesheets(&doc); 1634 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1734 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1635 1735 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1636 1736 1637 1737 let body_box = &tree.root.children[0]; ··· 1658 1758 let doc = we_html::parse_html(html_str); 1659 1759 let font = test_font(); 1660 1760 let sheets = extract_stylesheets(&doc); 1661 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1761 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1662 1762 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1663 1763 1664 1764 let body_box = &tree.root.children[0]; ··· 1691 1791 let doc = we_html::parse_html(html_str); 1692 1792 let font = test_font(); 1693 1793 let sheets = extract_stylesheets(&doc); 1694 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1794 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1695 1795 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1696 1796 1697 1797 let body_box = &tree.root.children[0]; ··· 1714 1814 let doc = we_html::parse_html(html_str); 1715 1815 let font = test_font(); 1716 1816 let sheets = extract_stylesheets(&doc); 1717 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1817 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1718 1818 // Narrow viewport to force wrapping. 1719 1819 let tree = layout(&styled, &doc, 100.0, 600.0, &font, &HashMap::new()); 1720 1820 ··· 1747 1847 let doc = we_html::parse_html(html_str); 1748 1848 let font = test_font(); 1749 1849 let sheets = extract_stylesheets(&doc); 1750 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1850 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1751 1851 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1752 1852 1753 1853 let body_box = &tree.root.children[0]; ··· 1787 1887 let doc = we_html::parse_html(html_str); 1788 1888 let font = test_font(); 1789 1889 let sheets = extract_stylesheets(&doc); 1790 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1890 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1791 1891 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1792 1892 1793 1893 let body_box = &tree.root.children[0]; ··· 1825 1925 let doc = we_html::parse_html(html_str); 1826 1926 let font = test_font(); 1827 1927 let sheets = extract_stylesheets(&doc); 1828 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1928 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1829 1929 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1830 1930 1831 1931 let body_box = &tree.root.children[0]; ··· 1848 1948 let doc = we_html::parse_html(html_str); 1849 1949 let font = test_font(); 1850 1950 let sheets = extract_stylesheets(&doc); 1851 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1951 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1852 1952 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1853 1953 1854 1954 let body_box = &tree.root.children[0]; ··· 1871 1971 let doc = we_html::parse_html(html_str); 1872 1972 let font = test_font(); 1873 1973 let sheets = extract_stylesheets(&doc); 1874 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1974 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1875 1975 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1876 1976 1877 1977 let body_box = &tree.root.children[0]; ··· 1892 1992 let doc = we_html::parse_html(html_str); 1893 1993 let font = test_font(); 1894 1994 let sheets = extract_stylesheets(&doc); 1895 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1995 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1896 1996 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1897 1997 1898 1998 let body_box = &tree.root.children[0]; ··· 1926 2026 let doc = we_html::parse_html(html_str); 1927 2027 let font = test_font(); 1928 2028 let sheets = extract_stylesheets(&doc); 1929 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2029 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1930 2030 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1931 2031 1932 2032 let body_box = &tree.root.children[0]; ··· 1955 2055 let doc = we_html::parse_html(html_str); 1956 2056 let font = test_font(); 1957 2057 let sheets = extract_stylesheets(&doc); 1958 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2058 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1959 2059 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1960 2060 1961 2061 let body_box = &tree.root.children[0]; ··· 1991 2091 let doc = we_html::parse_html(html_str); 1992 2092 let font = test_font(); 1993 2093 let sheets = extract_stylesheets(&doc); 1994 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2094 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1995 2095 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 1996 2096 1997 2097 let body_box = &tree.root.children[0]; ··· 2024 2124 let doc = we_html::parse_html(html_str); 2025 2125 let font = test_font(); 2026 2126 let sheets = extract_stylesheets(&doc); 2027 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2127 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2028 2128 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2029 2129 2030 2130 let body_box = &tree.root.children[0]; ··· 2052 2152 let doc = we_html::parse_html(html_str); 2053 2153 let font = test_font(); 2054 2154 let sheets = extract_stylesheets(&doc); 2055 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2155 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2056 2156 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2057 2157 2058 2158 let body_box = &tree.root.children[0]; ··· 2087 2187 let doc = we_html::parse_html(html_str); 2088 2188 let font = test_font(); 2089 2189 let sheets = extract_stylesheets(&doc); 2090 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2190 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2091 2191 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2092 2192 2093 2193 let body_box = &tree.root.children[0]; ··· 2122 2222 let doc = we_html::parse_html(html_str); 2123 2223 let font = test_font(); 2124 2224 let sheets = extract_stylesheets(&doc); 2125 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2225 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2126 2226 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2127 2227 2128 2228 let body_box = &tree.root.children[0]; ··· 2158 2258 let doc = we_html::parse_html(html_str); 2159 2259 let font = test_font(); 2160 2260 let sheets = extract_stylesheets(&doc); 2161 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2261 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2162 2262 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2163 2263 2164 2264 let body_box = &tree.root.children[0]; ··· 2187 2287 let doc = we_html::parse_html(html_str); 2188 2288 let font = test_font(); 2189 2289 let sheets = extract_stylesheets(&doc); 2190 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2290 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2191 2291 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2192 2292 2193 2293 let body_box = &tree.root.children[0]; ··· 2238 2338 let doc = we_html::parse_html(html_str); 2239 2339 let font = test_font(); 2240 2340 let sheets = extract_stylesheets(&doc); 2241 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2341 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2242 2342 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2243 2343 2244 2344 let body_box = &tree.root.children[0]; ··· 2268 2368 let doc = we_html::parse_html(html_str); 2269 2369 let font = test_font(); 2270 2370 let sheets = extract_stylesheets(&doc); 2271 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2371 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2272 2372 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2273 2373 2274 2374 let body_box = &tree.root.children[0]; ··· 2298 2398 let doc = we_html::parse_html(html_str); 2299 2399 let font = test_font(); 2300 2400 let sheets = extract_stylesheets(&doc); 2301 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2401 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2302 2402 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2303 2403 2304 2404 let body_box = &tree.root.children[0]; ··· 2325 2425 let doc = we_html::parse_html(html_str); 2326 2426 let font = test_font(); 2327 2427 let sheets = extract_stylesheets(&doc); 2328 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2428 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2329 2429 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2330 2430 2331 2431 let body_box = &tree.root.children[0]; ··· 2354 2454 let doc = we_html::parse_html(html_str); 2355 2455 let font = test_font(); 2356 2456 let sheets = extract_stylesheets(&doc); 2357 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2457 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2358 2458 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2359 2459 2360 2460 let body_box = &tree.root.children[0]; ··· 2380 2480 let doc = we_html::parse_html(html_str); 2381 2481 let font = test_font(); 2382 2482 let sheets = extract_stylesheets(&doc); 2383 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2483 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2384 2484 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2385 2485 2386 2486 let body_box = &tree.root.children[0]; ··· 2410 2510 let doc = we_html::parse_html(html_str); 2411 2511 let font = test_font(); 2412 2512 let sheets = extract_stylesheets(&doc); 2413 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2513 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2414 2514 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2415 2515 2416 2516 let body_box = &tree.root.children[0]; ··· 2444 2544 let doc = we_html::parse_html(html_str); 2445 2545 let font = test_font(); 2446 2546 let sheets = extract_stylesheets(&doc); 2447 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2547 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2448 2548 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2449 2549 2450 2550 let body_box = &tree.root.children[0]; ··· 2478 2578 let doc = we_html::parse_html(html_str); 2479 2579 let font = test_font(); 2480 2580 let sheets = extract_stylesheets(&doc); 2481 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2581 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2482 2582 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2483 2583 2484 2584 let body_box = &tree.root.children[0]; ··· 2504 2604 let doc = we_html::parse_html(html_str); 2505 2605 let font = test_font(); 2506 2606 let sheets = extract_stylesheets(&doc); 2507 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2607 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2508 2608 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2509 2609 2510 2610 let body_box = &tree.root.children[0]; ··· 2529 2629 let doc = we_html::parse_html(html_str); 2530 2630 let font = test_font(); 2531 2631 let sheets = extract_stylesheets(&doc); 2532 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2632 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2533 2633 let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2534 2634 2535 2635 let body_box = &tree.root.children[0]; ··· 2537 2637 assert_eq!(div_box.visibility, Visibility::Collapse); 2538 2638 // Still occupies space (non-table collapse = hidden behavior). 2539 2639 assert_eq!(div_box.rect.height, 50.0); 2640 + } 2641 + 2642 + // --- Viewport units and percentage resolution tests --- 2643 + 2644 + #[test] 2645 + fn width_50_percent_resolves_to_half_containing_block() { 2646 + let html_str = r#"<!DOCTYPE html> 2647 + <html> 2648 + <head><style> 2649 + body { margin: 0; } 2650 + div { width: 50%; } 2651 + </style></head> 2652 + <body><div>Half width</div></body> 2653 + </html>"#; 2654 + let doc = we_html::parse_html(html_str); 2655 + let font = test_font(); 2656 + let sheets = extract_stylesheets(&doc); 2657 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2658 + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2659 + 2660 + let body_box = &tree.root.children[0]; 2661 + let div_box = &body_box.children[0]; 2662 + assert!( 2663 + (div_box.rect.width - 400.0).abs() < 0.01, 2664 + "width: 50% should be 400px on 800px viewport, got {}", 2665 + div_box.rect.width 2666 + ); 2667 + } 2668 + 2669 + #[test] 2670 + fn margin_10_percent_resolves_against_containing_block_width() { 2671 + let html_str = r#"<!DOCTYPE html> 2672 + <html> 2673 + <head><style> 2674 + body { margin: 0; } 2675 + div { margin: 10%; width: 100px; height: 50px; } 2676 + </style></head> 2677 + <body><div>Box</div></body> 2678 + </html>"#; 2679 + let doc = we_html::parse_html(html_str); 2680 + let font = test_font(); 2681 + let sheets = extract_stylesheets(&doc); 2682 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2683 + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2684 + 2685 + let body_box = &tree.root.children[0]; 2686 + let div_box = &body_box.children[0]; 2687 + // All margins (including top/bottom) resolve against containing block WIDTH. 2688 + assert!( 2689 + (div_box.margin.left - 80.0).abs() < 0.01, 2690 + "margin-left: 10% should be 80px on 800px viewport, got {}", 2691 + div_box.margin.left 2692 + ); 2693 + assert!( 2694 + (div_box.margin.top - 80.0).abs() < 0.01, 2695 + "margin-top: 10% should be 80px (against width, not height), got {}", 2696 + div_box.margin.top 2697 + ); 2698 + } 2699 + 2700 + #[test] 2701 + fn width_50vw_resolves_to_half_viewport() { 2702 + let html_str = r#"<!DOCTYPE html> 2703 + <html> 2704 + <head><style> 2705 + body { margin: 0; } 2706 + div { width: 50vw; } 2707 + </style></head> 2708 + <body><div>Half viewport</div></body> 2709 + </html>"#; 2710 + let doc = we_html::parse_html(html_str); 2711 + let font = test_font(); 2712 + let sheets = extract_stylesheets(&doc); 2713 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2714 + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2715 + 2716 + let body_box = &tree.root.children[0]; 2717 + let div_box = &body_box.children[0]; 2718 + assert!( 2719 + (div_box.rect.width - 400.0).abs() < 0.01, 2720 + "width: 50vw should be 400px on 800px viewport, got {}", 2721 + div_box.rect.width 2722 + ); 2723 + } 2724 + 2725 + #[test] 2726 + fn height_100vh_resolves_to_full_viewport() { 2727 + let html_str = r#"<!DOCTYPE html> 2728 + <html> 2729 + <head><style> 2730 + body { margin: 0; } 2731 + div { height: 100vh; } 2732 + </style></head> 2733 + <body><div>Full height</div></body> 2734 + </html>"#; 2735 + let doc = we_html::parse_html(html_str); 2736 + let font = test_font(); 2737 + let sheets = extract_stylesheets(&doc); 2738 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2739 + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2740 + 2741 + let body_box = &tree.root.children[0]; 2742 + let div_box = &body_box.children[0]; 2743 + assert!( 2744 + (div_box.rect.height - 600.0).abs() < 0.01, 2745 + "height: 100vh should be 600px on 600px viewport, got {}", 2746 + div_box.rect.height 2747 + ); 2748 + } 2749 + 2750 + #[test] 2751 + fn font_size_5vmin_resolves_to_smaller_dimension() { 2752 + let html_str = r#"<!DOCTYPE html> 2753 + <html> 2754 + <head><style> 2755 + body { margin: 0; } 2756 + p { font-size: 5vmin; } 2757 + </style></head> 2758 + <body><p>Text</p></body> 2759 + </html>"#; 2760 + let doc = we_html::parse_html(html_str); 2761 + let font = test_font(); 2762 + let sheets = extract_stylesheets(&doc); 2763 + // viewport 800x600 → vmin = 600 2764 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2765 + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2766 + 2767 + let body_box = &tree.root.children[0]; 2768 + let p_box = &body_box.children[0]; 2769 + // 5vmin = 5% of min(800, 600) = 5% of 600 = 30px 2770 + assert!( 2771 + (p_box.font_size - 30.0).abs() < 0.01, 2772 + "font-size: 5vmin should be 30px, got {}", 2773 + p_box.font_size 2774 + ); 2775 + } 2776 + 2777 + #[test] 2778 + fn nested_percentage_widths_compound() { 2779 + let html_str = r#"<!DOCTYPE html> 2780 + <html> 2781 + <head><style> 2782 + body { margin: 0; } 2783 + .outer { width: 50%; } 2784 + .inner { width: 50%; } 2785 + </style></head> 2786 + <body> 2787 + <div class="outer"><div class="inner">Nested</div></div> 2788 + </body> 2789 + </html>"#; 2790 + let doc = we_html::parse_html(html_str); 2791 + let font = test_font(); 2792 + let sheets = extract_stylesheets(&doc); 2793 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2794 + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2795 + 2796 + let body_box = &tree.root.children[0]; 2797 + let outer_box = &body_box.children[0]; 2798 + let inner_box = &outer_box.children[0]; 2799 + // outer = 50% of 800 = 400 2800 + assert!( 2801 + (outer_box.rect.width - 400.0).abs() < 0.01, 2802 + "outer width should be 400px, got {}", 2803 + outer_box.rect.width 2804 + ); 2805 + // inner = 50% of 400 = 200 2806 + assert!( 2807 + (inner_box.rect.width - 200.0).abs() < 0.01, 2808 + "inner width should be 200px (50% of 400), got {}", 2809 + inner_box.rect.width 2810 + ); 2811 + } 2812 + 2813 + #[test] 2814 + fn padding_percent_resolves_against_width() { 2815 + let html_str = r#"<!DOCTYPE html> 2816 + <html> 2817 + <head><style> 2818 + body { margin: 0; } 2819 + div { padding: 5%; width: 400px; } 2820 + </style></head> 2821 + <body><div>Content</div></body> 2822 + </html>"#; 2823 + let doc = we_html::parse_html(html_str); 2824 + let font = test_font(); 2825 + let sheets = extract_stylesheets(&doc); 2826 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2827 + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2828 + 2829 + let body_box = &tree.root.children[0]; 2830 + let div_box = &body_box.children[0]; 2831 + // padding: 5% resolves against containing block width (800px) 2832 + assert!( 2833 + (div_box.padding.top - 40.0).abs() < 0.01, 2834 + "padding-top: 5% should be 40px (5% of 800), got {}", 2835 + div_box.padding.top 2836 + ); 2837 + assert!( 2838 + (div_box.padding.left - 40.0).abs() < 0.01, 2839 + "padding-left: 5% should be 40px (5% of 800), got {}", 2840 + div_box.padding.left 2841 + ); 2842 + } 2843 + 2844 + #[test] 2845 + fn vmax_uses_larger_dimension() { 2846 + let html_str = r#"<!DOCTYPE html> 2847 + <html> 2848 + <head><style> 2849 + body { margin: 0; } 2850 + div { width: 10vmax; } 2851 + </style></head> 2852 + <body><div>Content</div></body> 2853 + </html>"#; 2854 + let doc = we_html::parse_html(html_str); 2855 + let font = test_font(); 2856 + let sheets = extract_stylesheets(&doc); 2857 + // viewport 800x600 → vmax = 800 2858 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2859 + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2860 + 2861 + let body_box = &tree.root.children[0]; 2862 + let div_box = &body_box.children[0]; 2863 + // 10vmax = 10% of max(800, 600) = 10% of 800 = 80px 2864 + assert!( 2865 + (div_box.rect.width - 80.0).abs() < 0.01, 2866 + "width: 10vmax should be 80px, got {}", 2867 + div_box.rect.width 2868 + ); 2869 + } 2870 + 2871 + #[test] 2872 + fn height_50_percent_resolves_against_viewport() { 2873 + let html_str = r#"<!DOCTYPE html> 2874 + <html> 2875 + <head><style> 2876 + body { margin: 0; } 2877 + div { height: 50%; } 2878 + </style></head> 2879 + <body><div>Half height</div></body> 2880 + </html>"#; 2881 + let doc = we_html::parse_html(html_str); 2882 + let font = test_font(); 2883 + let sheets = extract_stylesheets(&doc); 2884 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2885 + let tree = layout(&styled, &doc, 800.0, 600.0, &font, &HashMap::new()); 2886 + 2887 + let body_box = &tree.root.children[0]; 2888 + let div_box = &body_box.children[0]; 2889 + // height: 50% resolves against viewport height (600) 2890 + assert!( 2891 + (div_box.rect.height - 300.0).abs() < 0.01, 2892 + "height: 50% should be 300px on 600px viewport, got {}", 2893 + div_box.rect.height 2894 + ); 2540 2895 } 2541 2896 }
+5 -5
crates/render/src/lib.rs
··· 490 490 fn layout_doc(doc: &Document) -> we_layout::LayoutTree { 491 491 let font = test_font(); 492 492 let sheets = extract_stylesheets(doc); 493 - let styled = resolve_styles(doc, &sheets).unwrap(); 493 + let styled = resolve_styles(doc, &sheets, (800.0, 600.0)).unwrap(); 494 494 we_layout::layout( 495 495 &styled, 496 496 doc, ··· 551 551 let doc = Document::new(); 552 552 let font = test_font(); 553 553 let sheets = extract_stylesheets(&doc); 554 - let styled = resolve_styles(&doc, &sheets); 554 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)); 555 555 if let Some(styled) = styled { 556 556 let tree = we_layout::layout( 557 557 &styled, ··· 726 726 let doc = we_html::parse_html(html_str); 727 727 let font = test_font(); 728 728 let sheets = extract_stylesheets(&doc); 729 - let styled = resolve_styles(&doc, &sheets).unwrap(); 729 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 730 730 let tree = we_layout::layout( 731 731 &styled, 732 732 &doc, ··· 760 760 let doc = we_html::parse_html(html_str); 761 761 let font = test_font(); 762 762 let sheets = extract_stylesheets(&doc); 763 - let styled = resolve_styles(&doc, &sheets).unwrap(); 763 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 764 764 let tree = we_layout::layout( 765 765 &styled, 766 766 &doc, ··· 793 793 let doc = we_html::parse_html(html_str); 794 794 let font = test_font(); 795 795 let sheets = extract_stylesheets(&doc); 796 - let styled = resolve_styles(&doc, &sheets).unwrap(); 796 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 797 797 let tree = we_layout::layout( 798 798 &styled, 799 799 &doc,
+118 -118
crates/style/src/computed.rs
··· 149 149 // LengthOrAuto 150 150 // --------------------------------------------------------------------------- 151 151 152 - /// A computed length (resolved to px) or `auto`. 152 + /// A computed length (resolved to px), a percentage (unresolved), or `auto`. 153 153 #[derive(Debug, Clone, Copy, PartialEq, Default)] 154 154 pub enum LengthOrAuto { 155 155 Length(f32), 156 + /// Percentage value (0.0–100.0), resolved during layout against the containing block. 157 + Percentage(f32), 156 158 #[default] 157 159 Auto, 158 160 } ··· 173 175 pub margin_bottom: LengthOrAuto, 174 176 pub margin_left: LengthOrAuto, 175 177 176 - // Box model: padding (no auto for padding) 177 - pub padding_top: f32, 178 - pub padding_right: f32, 179 - pub padding_bottom: f32, 180 - pub padding_left: f32, 178 + // Box model: padding (percentages resolve against containing block width) 179 + pub padding_top: LengthOrAuto, 180 + pub padding_right: LengthOrAuto, 181 + pub padding_bottom: LengthOrAuto, 182 + pub padding_left: LengthOrAuto, 181 183 182 184 // Box model: border width 183 185 pub border_top_width: f32, ··· 241 243 margin_bottom: LengthOrAuto::Length(0.0), 242 244 margin_left: LengthOrAuto::Length(0.0), 243 245 244 - padding_top: 0.0, 245 - padding_right: 0.0, 246 - padding_bottom: 0.0, 247 - padding_left: 0.0, 246 + padding_top: LengthOrAuto::Length(0.0), 247 + padding_right: LengthOrAuto::Length(0.0), 248 + padding_bottom: LengthOrAuto::Length(0.0), 249 + padding_left: LengthOrAuto::Length(0.0), 248 250 249 251 border_top_width: 0.0, 250 252 border_right_width: 0.0, ··· 406 408 // Resolve a CssValue to f32 px given context 407 409 // --------------------------------------------------------------------------- 408 410 409 - fn resolve_length(value: &CssValue, _parent_font_size: f32, current_font_size: f32) -> Option<f32> { 410 - match value { 411 - // Em units resolve relative to the element's own computed font-size 412 - // (for properties other than font-size, which has its own handling). 413 - CssValue::Length(n, unit) => Some(resolve_length_unit(*n, *unit, current_font_size)), 414 - CssValue::Percentage(p) => Some((*p / 100.0) as f32 * current_font_size), 415 - CssValue::Zero => Some(0.0), 416 - CssValue::Number(n) if *n == 0.0 => Some(0.0), 417 - _ => None, 418 - } 419 - } 420 - 421 - fn resolve_length_unit(value: f64, unit: LengthUnit, em_base: f32) -> f32 { 411 + fn resolve_length_unit(value: f64, unit: LengthUnit, em_base: f32, viewport: (f32, f32)) -> f32 { 422 412 let v = value as f32; 413 + let (vw, vh) = viewport; 423 414 match unit { 424 415 LengthUnit::Px => v, 425 416 LengthUnit::Em => v * em_base, ··· 429 420 LengthUnit::Mm => v * (96.0 / 25.4), 430 421 LengthUnit::In => v * 96.0, 431 422 LengthUnit::Pc => v * 16.0, 432 - // Viewport units: use a reasonable default (can be parameterized later) 433 - LengthUnit::Vw | LengthUnit::Vh | LengthUnit::Vmin | LengthUnit::Vmax => v, 423 + LengthUnit::Vw => v * vw / 100.0, 424 + LengthUnit::Vh => v * vh / 100.0, 425 + LengthUnit::Vmin => v * vw.min(vh) / 100.0, 426 + LengthUnit::Vmax => v * vw.max(vh) / 100.0, 434 427 } 435 428 } 436 429 437 - fn resolve_length_or_auto( 430 + /// Resolve a CSS value to `LengthOrAuto` for layout properties (width, height, 431 + /// margin, padding, top/right/bottom/left). Percentages are preserved as 432 + /// `LengthOrAuto::Percentage` for later resolution during layout against the 433 + /// containing block. 434 + fn resolve_layout_length_or_auto( 438 435 value: &CssValue, 439 - parent_font_size: f32, 440 436 current_font_size: f32, 437 + viewport: (f32, f32), 441 438 ) -> LengthOrAuto { 442 439 match value { 443 440 CssValue::Auto => LengthOrAuto::Auto, 444 - _ => resolve_length(value, parent_font_size, current_font_size) 445 - .map(LengthOrAuto::Length) 446 - .unwrap_or(LengthOrAuto::Auto), 441 + CssValue::Percentage(p) => LengthOrAuto::Percentage(*p as f32), 442 + CssValue::Length(n, unit) => { 443 + LengthOrAuto::Length(resolve_length_unit(*n, *unit, current_font_size, viewport)) 444 + } 445 + CssValue::Zero => LengthOrAuto::Length(0.0), 446 + CssValue::Number(n) if *n == 0.0 => LengthOrAuto::Length(0.0), 447 + _ => LengthOrAuto::Auto, 447 448 } 448 449 } 449 450 ··· 465 466 property: &str, 466 467 value: &CssValue, 467 468 parent: &ComputedStyle, 469 + viewport: (f32, f32), 468 470 ) { 469 471 // Handle inherit/initial/unset 470 472 match value { ··· 503 505 }; 504 506 } 505 507 506 - // Margin 508 + // Margin (percentages preserved for layout resolution) 507 509 "margin-top" => { 508 - style.margin_top = resolve_length_or_auto(value, parent_fs, current_fs); 510 + style.margin_top = resolve_layout_length_or_auto(value, current_fs, viewport); 509 511 } 510 512 "margin-right" => { 511 - style.margin_right = resolve_length_or_auto(value, parent_fs, current_fs); 513 + style.margin_right = resolve_layout_length_or_auto(value, current_fs, viewport); 512 514 } 513 515 "margin-bottom" => { 514 - style.margin_bottom = resolve_length_or_auto(value, parent_fs, current_fs); 516 + style.margin_bottom = resolve_layout_length_or_auto(value, current_fs, viewport); 515 517 } 516 518 "margin-left" => { 517 - style.margin_left = resolve_length_or_auto(value, parent_fs, current_fs); 519 + style.margin_left = resolve_layout_length_or_auto(value, current_fs, viewport); 518 520 } 519 521 520 - // Padding 522 + // Padding (percentages preserved for layout resolution) 521 523 "padding-top" => { 522 - if let Some(v) = resolve_length(value, parent_fs, current_fs) { 523 - style.padding_top = v; 524 - } 524 + style.padding_top = resolve_layout_length_or_auto(value, current_fs, viewport); 525 525 } 526 526 "padding-right" => { 527 - if let Some(v) = resolve_length(value, parent_fs, current_fs) { 528 - style.padding_right = v; 529 - } 527 + style.padding_right = resolve_layout_length_or_auto(value, current_fs, viewport); 530 528 } 531 529 "padding-bottom" => { 532 - if let Some(v) = resolve_length(value, parent_fs, current_fs) { 533 - style.padding_bottom = v; 534 - } 530 + style.padding_bottom = resolve_layout_length_or_auto(value, current_fs, viewport); 535 531 } 536 532 "padding-left" => { 537 - if let Some(v) = resolve_length(value, parent_fs, current_fs) { 538 - style.padding_left = v; 539 - } 533 + style.padding_left = resolve_layout_length_or_auto(value, current_fs, viewport); 540 534 } 541 535 542 536 // Border width 543 537 "border-top-width" | "border-right-width" | "border-bottom-width" | "border-left-width" => { 544 - let w = resolve_border_width(value, parent_fs); 538 + let w = resolve_border_width(value, parent_fs, viewport); 545 539 match property { 546 540 "border-top-width" => style.border_top_width = w, 547 541 "border-right-width" => style.border_right_width = w, ··· 553 547 554 548 // Border width shorthand (single value applied to all sides) 555 549 "border-width" => { 556 - let w = resolve_border_width(value, parent_fs); 550 + let w = resolve_border_width(value, parent_fs, viewport); 557 551 style.border_top_width = w; 558 552 style.border_right_width = w; 559 553 style.border_bottom_width = w; ··· 602 596 } 603 597 } 604 598 605 - // Dimensions 599 + // Dimensions (percentages preserved for layout resolution) 606 600 "width" => { 607 - style.width = resolve_length_or_auto(value, parent_fs, current_fs); 601 + style.width = resolve_layout_length_or_auto(value, current_fs, viewport); 608 602 } 609 603 "height" => { 610 - style.height = resolve_length_or_auto(value, parent_fs, current_fs); 604 + style.height = resolve_layout_length_or_auto(value, current_fs, viewport); 611 605 } 612 606 613 607 // Box sizing ··· 634 628 "font-size" => { 635 629 match value { 636 630 CssValue::Length(n, unit) => { 637 - style.font_size = resolve_length_unit(*n, *unit, parent_fs); 631 + style.font_size = resolve_length_unit(*n, *unit, parent_fs, viewport); 638 632 } 639 633 CssValue::Percentage(p) => { 640 634 style.font_size = (*p / 100.0) as f32 * parent_fs; ··· 734 728 style.line_height = *n as f32 * style.font_size; 735 729 } 736 730 CssValue::Length(n, unit) => { 737 - style.line_height = resolve_length_unit(*n, *unit, style.font_size); 731 + style.line_height = resolve_length_unit(*n, *unit, style.font_size, viewport); 738 732 } 739 733 CssValue::Percentage(p) => { 740 734 style.line_height = (*p / 100.0) as f32 * style.font_size; ··· 763 757 }; 764 758 } 765 759 766 - // Position offsets 767 - "top" => style.top = resolve_length_or_auto(value, parent_fs, current_fs), 768 - "right" => style.right = resolve_length_or_auto(value, parent_fs, current_fs), 769 - "bottom" => style.bottom = resolve_length_or_auto(value, parent_fs, current_fs), 770 - "left" => style.left = resolve_length_or_auto(value, parent_fs, current_fs), 760 + // Position offsets (percentages preserved for layout resolution) 761 + "top" => style.top = resolve_layout_length_or_auto(value, current_fs, viewport), 762 + "right" => style.right = resolve_layout_length_or_auto(value, current_fs, viewport), 763 + "bottom" => style.bottom = resolve_layout_length_or_auto(value, current_fs, viewport), 764 + "left" => style.left = resolve_layout_length_or_auto(value, current_fs, viewport), 771 765 772 766 // Overflow 773 767 "overflow" => { ··· 800 794 } 801 795 } 802 796 803 - fn resolve_border_width(value: &CssValue, em_base: f32) -> f32 { 797 + fn resolve_border_width(value: &CssValue, em_base: f32, viewport: (f32, f32)) -> f32 { 804 798 match value { 805 - CssValue::Length(n, unit) => resolve_length_unit(*n, *unit, em_base), 799 + CssValue::Length(n, unit) => resolve_length_unit(*n, *unit, em_base, viewport), 806 800 CssValue::Zero => 0.0, 807 801 CssValue::Number(n) if *n == 0.0 => 0.0, 808 802 CssValue::Keyword(k) => match k.as_str() { ··· 959 953 /// 960 954 /// `stylesheets` is a list of author stylesheets (the UA stylesheet is 961 955 /// automatically prepended). 962 - pub fn resolve_styles(doc: &Document, author_stylesheets: &[Stylesheet]) -> Option<StyledNode> { 956 + pub fn resolve_styles( 957 + doc: &Document, 958 + author_stylesheets: &[Stylesheet], 959 + viewport: (f32, f32), 960 + ) -> Option<StyledNode> { 963 961 let ua = ua_stylesheet(); 964 962 965 963 // Combine UA + author stylesheets into a single list for rule collection. ··· 972 970 } 973 971 974 972 let root = doc.root(); 975 - resolve_node(doc, root, &combined, &ComputedStyle::default()) 973 + resolve_node(doc, root, &combined, &ComputedStyle::default(), viewport) 976 974 } 977 975 978 976 fn resolve_node( ··· 980 978 node: NodeId, 981 979 stylesheet: &Stylesheet, 982 980 parent_style: &ComputedStyle, 981 + viewport: (f32, f32), 983 982 ) -> Option<StyledNode> { 984 983 match doc.node_data(node) { 985 984 NodeData::Document => { 986 985 // Document node: resolve children, return first element child or wrapper. 987 986 let mut children = Vec::new(); 988 987 for child in doc.children(node) { 989 - if let Some(styled) = resolve_node(doc, child, stylesheet, parent_style) { 988 + if let Some(styled) = resolve_node(doc, child, stylesheet, parent_style, viewport) { 990 989 children.push(styled); 991 990 } 992 991 } ··· 1003 1002 } 1004 1003 } 1005 1004 NodeData::Element { .. } => { 1006 - let style = compute_style_for_element(doc, node, stylesheet, parent_style); 1005 + let style = compute_style_for_element(doc, node, stylesheet, parent_style, viewport); 1007 1006 1008 1007 if style.display == Display::None { 1009 1008 return None; ··· 1011 1010 1012 1011 let mut children = Vec::new(); 1013 1012 for child in doc.children(node) { 1014 - if let Some(styled) = resolve_node(doc, child, stylesheet, &style) { 1013 + if let Some(styled) = resolve_node(doc, child, stylesheet, &style, viewport) { 1015 1014 children.push(styled); 1016 1015 } 1017 1016 } ··· 1043 1042 node: NodeId, 1044 1043 stylesheet: &Stylesheet, 1045 1044 parent_style: &ComputedStyle, 1045 + viewport: (f32, f32), 1046 1046 ) -> ComputedStyle { 1047 1047 // Start from initial values, inheriting inherited properties from parent 1048 1048 let mut style = ComputedStyle { ··· 1097 1097 1098 1098 // Step 5: Apply normal declarations (already in specificity order) 1099 1099 for (prop, value) in &normal_decls { 1100 - apply_property(&mut style, prop, value, parent_style); 1100 + apply_property(&mut style, prop, value, parent_style, viewport); 1101 1101 } 1102 1102 1103 1103 // Step 6: Apply inline style normal declarations (override stylesheet normals) ··· 1106 1106 let property = decl.property.as_str(); 1107 1107 if let Some(longhands) = expand_shorthand(property, &decl.value, false) { 1108 1108 for lh in &longhands { 1109 - apply_property(&mut style, &lh.property, &lh.value, parent_style); 1109 + apply_property(&mut style, &lh.property, &lh.value, parent_style, viewport); 1110 1110 } 1111 1111 } else { 1112 1112 let value = parse_value(&decl.value); 1113 - apply_property(&mut style, property, &value, parent_style); 1113 + apply_property(&mut style, property, &value, parent_style, viewport); 1114 1114 } 1115 1115 } 1116 1116 } 1117 1117 1118 1118 // Step 7: Apply !important declarations (override everything normal) 1119 1119 for (prop, value) in &important_decls { 1120 - apply_property(&mut style, prop, value, parent_style); 1120 + apply_property(&mut style, prop, value, parent_style, viewport); 1121 1121 } 1122 1122 1123 1123 // Step 8: Apply inline style !important declarations (highest priority) ··· 1126 1126 let property = decl.property.as_str(); 1127 1127 if let Some(longhands) = expand_shorthand(property, &decl.value, true) { 1128 1128 for lh in &longhands { 1129 - apply_property(&mut style, &lh.property, &lh.value, parent_style); 1129 + apply_property(&mut style, &lh.property, &lh.value, parent_style, viewport); 1130 1130 } 1131 1131 } else { 1132 1132 let value = parse_value(&decl.value); 1133 - apply_property(&mut style, property, &value, parent_style); 1133 + apply_property(&mut style, property, &value, parent_style, viewport); 1134 1134 } 1135 1135 } 1136 1136 } ··· 1180 1180 #[test] 1181 1181 fn ua_body_has_8px_margin() { 1182 1182 let (doc, _, _, _) = make_doc_with_body(); 1183 - let styled = resolve_styles(&doc, &[]).unwrap(); 1183 + let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); 1184 1184 // styled is <html>, first child is <body> 1185 1185 let body = &styled.children[0]; 1186 1186 assert_eq!(body.style.margin_top, LengthOrAuto::Length(8.0)); ··· 1197 1197 doc.append_child(body, h1); 1198 1198 doc.append_child(h1, text); 1199 1199 1200 - let styled = resolve_styles(&doc, &[]).unwrap(); 1200 + let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); 1201 1201 let body_node = &styled.children[0]; 1202 1202 let h1_node = &body_node.children[0]; 1203 1203 ··· 1212 1212 let h2 = doc.create_element("h2"); 1213 1213 doc.append_child(body, h2); 1214 1214 1215 - let styled = resolve_styles(&doc, &[]).unwrap(); 1215 + let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); 1216 1216 let body_node = &styled.children[0]; 1217 1217 let h2_node = &body_node.children[0]; 1218 1218 ··· 1228 1228 doc.append_child(body, p); 1229 1229 doc.append_child(p, text); 1230 1230 1231 - let styled = resolve_styles(&doc, &[]).unwrap(); 1231 + let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); 1232 1232 let body_node = &styled.children[0]; 1233 1233 let p_node = &body_node.children[0]; 1234 1234 ··· 1243 1243 let div = doc.create_element("div"); 1244 1244 doc.append_child(body, div); 1245 1245 1246 - let styled = resolve_styles(&doc, &[]).unwrap(); 1246 + let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); 1247 1247 let body_node = &styled.children[0]; 1248 1248 let div_node = &body_node.children[0]; 1249 1249 assert_eq!(div_node.style.display, Display::Block); ··· 1259 1259 doc.append_child(p, span); 1260 1260 doc.append_child(span, text); 1261 1261 1262 - let styled = resolve_styles(&doc, &[]).unwrap(); 1262 + let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); 1263 1263 let body_node = &styled.children[0]; 1264 1264 let p_node = &body_node.children[0]; 1265 1265 let span_node = &p_node.children[0]; ··· 1274 1274 doc.append_child(html, head); 1275 1275 doc.append_child(head, title); 1276 1276 1277 - let styled = resolve_styles(&doc, &[]).unwrap(); 1277 + let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); 1278 1278 // head should not appear in styled tree (display: none) 1279 1279 for child in &styled.children { 1280 1280 if let NodeData::Element { tag_name, .. } = doc.node_data(child.node) { ··· 1296 1296 doc.append_child(p, text); 1297 1297 1298 1298 let ss = Parser::parse("p { color: red; }"); 1299 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1299 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1300 1300 let body_node = &styled.children[0]; 1301 1301 let p_node = &body_node.children[0]; 1302 1302 assert_eq!(p_node.style.color, Color::rgb(255, 0, 0)); ··· 1309 1309 doc.append_child(body, div); 1310 1310 1311 1311 let ss = Parser::parse("div { background-color: blue; }"); 1312 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1312 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1313 1313 let body_node = &styled.children[0]; 1314 1314 let div_node = &body_node.children[0]; 1315 1315 assert_eq!(div_node.style.background_color, Color::rgb(0, 0, 255)); ··· 1322 1322 doc.append_child(body, p); 1323 1323 1324 1324 let ss = Parser::parse("p { font-size: 24px; }"); 1325 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1325 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1326 1326 let body_node = &styled.children[0]; 1327 1327 let p_node = &body_node.children[0]; 1328 1328 assert_eq!(p_node.style.font_size, 24.0); ··· 1335 1335 doc.append_child(body, div); 1336 1336 1337 1337 let ss = Parser::parse("div { margin-top: 20px; margin-bottom: 10px; }"); 1338 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1338 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1339 1339 let body_node = &styled.children[0]; 1340 1340 let div_node = &body_node.children[0]; 1341 1341 assert_eq!(div_node.style.margin_top, LengthOrAuto::Length(20.0)); ··· 1357 1357 1358 1358 // .highlight (0,1,0) > p (0,0,1) 1359 1359 let ss = Parser::parse("p { color: red; } .highlight { color: green; }"); 1360 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1360 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1361 1361 let body_node = &styled.children[0]; 1362 1362 let p_node = &body_node.children[0]; 1363 1363 assert_eq!(p_node.style.color, Color::rgb(0, 128, 0)); ··· 1373 1373 1374 1374 // Same specificity: later wins 1375 1375 let ss = Parser::parse("p { color: red; } p { color: blue; }"); 1376 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1376 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1377 1377 let body_node = &styled.children[0]; 1378 1378 let p_node = &body_node.children[0]; 1379 1379 assert_eq!(p_node.style.color, Color::rgb(0, 0, 255)); ··· 1390 1390 1391 1391 // #main (1,0,0) has higher specificity, but p has !important 1392 1392 let ss = Parser::parse("#main { color: blue; } p { color: red !important; }"); 1393 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1393 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1394 1394 let body_node = &styled.children[0]; 1395 1395 let p_node = &body_node.children[0]; 1396 1396 assert_eq!(p_node.style.color, Color::rgb(255, 0, 0)); ··· 1411 1411 doc.append_child(p, text); 1412 1412 1413 1413 let ss = Parser::parse("div { color: green; }"); 1414 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1414 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1415 1415 let body_node = &styled.children[0]; 1416 1416 let div_node = &body_node.children[0]; 1417 1417 let p_node = &div_node.children[0]; ··· 1432 1432 doc.append_child(p, text); 1433 1433 1434 1434 let ss = Parser::parse("div { font-size: 20px; }"); 1435 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1435 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1436 1436 let body_node = &styled.children[0]; 1437 1437 let div_node = &body_node.children[0]; 1438 1438 let p_node = &div_node.children[0]; ··· 1452 1452 doc.append_child(span, text); 1453 1453 1454 1454 let ss = Parser::parse("div { margin-top: 50px; }"); 1455 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1455 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1456 1456 let body_node = &styled.children[0]; 1457 1457 let div_node = &body_node.children[0]; 1458 1458 let span_node = &div_node.children[0]; ··· 1477 1477 doc.append_child(p, text); 1478 1478 1479 1479 let ss = Parser::parse("div { background-color: red; } p { background-color: inherit; }"); 1480 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1480 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1481 1481 let body_node = &styled.children[0]; 1482 1482 let div_node = &body_node.children[0]; 1483 1483 let p_node = &div_node.children[0]; ··· 1498 1498 1499 1499 // div sets color to red, p resets to initial (black) 1500 1500 let ss = Parser::parse("div { color: red; } p { color: initial; }"); 1501 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1501 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1502 1502 let body_node = &styled.children[0]; 1503 1503 let div_node = &body_node.children[0]; 1504 1504 let p_node = &div_node.children[0]; ··· 1519 1519 1520 1520 // color is inherited, so unset => inherit 1521 1521 let ss = Parser::parse("div { color: green; } p { color: unset; }"); 1522 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1522 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1523 1523 let body_node = &styled.children[0]; 1524 1524 let div_node = &body_node.children[0]; 1525 1525 let p_node = &div_node.children[0]; ··· 1539 1539 1540 1540 // margin is non-inherited, so unset => initial (0) 1541 1541 let ss = Parser::parse("p { margin-top: unset; }"); 1542 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1542 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1543 1543 let body_node = &styled.children[0]; 1544 1544 let p_node = &body_node.children[0]; 1545 1545 ··· 1560 1560 doc.append_child(div, text); 1561 1561 1562 1562 let ss = Parser::parse("div { font-size: 20px; margin-top: 2em; }"); 1563 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1563 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1564 1564 let body_node = &styled.children[0]; 1565 1565 let div_node = &body_node.children[0]; 1566 1566 ··· 1580 1580 doc.append_child(p, text); 1581 1581 1582 1582 let ss = Parser::parse("div { font-size: 20px; } p { font-size: 1.5em; }"); 1583 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1583 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1584 1584 let body_node = &styled.children[0]; 1585 1585 let div_node = &body_node.children[0]; 1586 1586 let p_node = &div_node.children[0]; ··· 1604 1604 doc.append_child(p, text); 1605 1605 1606 1606 let ss = Parser::parse("p { color: red; }"); 1607 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1607 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1608 1608 let body_node = &styled.children[0]; 1609 1609 let p_node = &body_node.children[0]; 1610 1610 ··· 1622 1622 doc.append_child(p, text); 1623 1623 1624 1624 let ss = Parser::parse("p { color: red !important; }"); 1625 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1625 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1626 1626 let body_node = &styled.children[0]; 1627 1627 let p_node = &body_node.children[0]; 1628 1628 ··· 1643 1643 doc.append_child(div, text); 1644 1644 1645 1645 let ss = Parser::parse("div { margin: 10px 20px; }"); 1646 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1646 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1647 1647 let body_node = &styled.children[0]; 1648 1648 let div_node = &body_node.children[0]; 1649 1649 ··· 1662 1662 doc.append_child(div, text); 1663 1663 1664 1664 let ss = Parser::parse("div { padding: 5px; }"); 1665 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1665 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1666 1666 let body_node = &styled.children[0]; 1667 1667 let div_node = &body_node.children[0]; 1668 1668 1669 - assert_eq!(div_node.style.padding_top, 5.0); 1670 - assert_eq!(div_node.style.padding_right, 5.0); 1671 - assert_eq!(div_node.style.padding_bottom, 5.0); 1672 - assert_eq!(div_node.style.padding_left, 5.0); 1669 + assert_eq!(div_node.style.padding_top, LengthOrAuto::Length(5.0)); 1670 + assert_eq!(div_node.style.padding_right, LengthOrAuto::Length(5.0)); 1671 + assert_eq!(div_node.style.padding_bottom, LengthOrAuto::Length(5.0)); 1672 + assert_eq!(div_node.style.padding_left, LengthOrAuto::Length(5.0)); 1673 1673 } 1674 1674 1675 1675 // ----------------------------------------------------------------------- ··· 1686 1686 1687 1687 // UA gives p margin-top=1em=16px. Author overrides to 0. 1688 1688 let ss = Parser::parse("p { margin-top: 0; margin-bottom: 0; }"); 1689 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1689 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1690 1690 let body_node = &styled.children[0]; 1691 1691 let p_node = &body_node.children[0]; 1692 1692 ··· 1707 1707 doc.append_child(p, text); 1708 1708 1709 1709 let ss = Parser::parse("p { color: red; font-size: 20px; }"); 1710 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1710 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1711 1711 let body_node = &styled.children[0]; 1712 1712 let p_node = &body_node.children[0]; 1713 1713 let text_node = &p_node.children[0]; ··· 1730 1730 1731 1731 let ss1 = Parser::parse("p { color: red; }"); 1732 1732 let ss2 = Parser::parse("p { color: blue; }"); 1733 - let styled = resolve_styles(&doc, &[ss1, ss2]).unwrap(); 1733 + let styled = resolve_styles(&doc, &[ss1, ss2], (800.0, 600.0)).unwrap(); 1734 1734 let body_node = &styled.children[0]; 1735 1735 let p_node = &body_node.children[0]; 1736 1736 ··· 1751 1751 doc.append_child(div, text); 1752 1752 1753 1753 let ss = Parser::parse("div { border: 2px solid red; }"); 1754 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1754 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1755 1755 let body_node = &styled.children[0]; 1756 1756 let div_node = &body_node.children[0]; 1757 1757 ··· 1773 1773 doc.append_child(div, text); 1774 1774 1775 1775 let ss = Parser::parse("div { position: relative; top: 10px; }"); 1776 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1776 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1777 1777 let body_node = &styled.children[0]; 1778 1778 let div_node = &body_node.children[0]; 1779 1779 ··· 1800 1800 doc.append_child(div2, t2); 1801 1801 1802 1802 let ss = Parser::parse(".hidden { display: none; }"); 1803 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1803 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1804 1804 let body_node = &styled.children[0]; 1805 1805 1806 1806 // Only div1 should appear ··· 1822 1822 doc.append_child(p, text); 1823 1823 1824 1824 let ss = Parser::parse("div { visibility: hidden; }"); 1825 - let styled = resolve_styles(&doc, &[ss]).unwrap(); 1825 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 1826 1826 let body_node = &styled.children[0]; 1827 1827 let div_node = &body_node.children[0]; 1828 1828 let p_node = &div_node.children[0]; ··· 1920 1920 doc.append_child(p, text); 1921 1921 1922 1922 let sheets = extract_stylesheets(&doc); 1923 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1923 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1924 1924 let body_node = &styled.children[0]; 1925 1925 let p_node = &body_node.children[0]; 1926 1926 ··· 1941 1941 let sheets = extract_stylesheets(&doc); 1942 1942 assert!(sheets.is_empty()); 1943 1943 1944 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1944 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1945 1945 let body_node = &styled.children[0]; 1946 1946 let div_node = &body_node.children[0]; 1947 1947 ··· 1967 1967 doc.append_child(p, text); 1968 1968 1969 1969 let sheets = extract_stylesheets(&doc); 1970 - let styled = resolve_styles(&doc, &sheets).unwrap(); 1970 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 1971 1971 let body_node = &styled.children[0]; 1972 1972 let p_node = &body_node.children[0]; 1973 1973 ··· 2003 2003 let sheets = extract_stylesheets(&doc); 2004 2004 assert_eq!(sheets.len(), 2); 2005 2005 2006 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2006 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2007 2007 let body_node = &styled.children[0]; 2008 2008 let p_node = &body_node.children[0]; 2009 2009 ··· 2017 2017 <html><head></head><body><div>Test</div></body></html>"#; 2018 2018 let doc = we_html::parse_html(html_str); 2019 2019 let sheets = extract_stylesheets(&doc); 2020 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2020 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2021 2021 let body = &styled.children[0]; 2022 2022 let div = &body.children[0]; 2023 2023 assert_eq!(div.style.box_sizing, BoxSizing::ContentBox); ··· 2030 2030 <body><div>Test</div></body></html>"#; 2031 2031 let doc = we_html::parse_html(html_str); 2032 2032 let sheets = extract_stylesheets(&doc); 2033 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2033 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2034 2034 let body = &styled.children[0]; 2035 2035 let div = &body.children[0]; 2036 2036 assert_eq!(div.style.box_sizing, BoxSizing::BorderBox); ··· 2043 2043 <body><div class="parent"><p>Child</p></div></body></html>"#; 2044 2044 let doc = we_html::parse_html(html_str); 2045 2045 let sheets = extract_stylesheets(&doc); 2046 - let styled = resolve_styles(&doc, &sheets).unwrap(); 2046 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 2047 2047 let body = &styled.children[0]; 2048 2048 let parent = &body.children[0]; 2049 2049 let child = &parent.children[0];
+6 -6
crates/style/tests/style_extraction.rs
··· 23 23 let sheets = extract_stylesheets(&doc); 24 24 assert_eq!(sheets.len(), 1); 25 25 26 - let styled = resolve_styles(&doc, &sheets).unwrap(); 26 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 27 27 // styled root is <html> 28 28 // Find body (first visible child) 29 29 let body = &styled.children[0]; ··· 52 52 let sheets = extract_stylesheets(&doc); 53 53 assert_eq!(sheets.len(), 2); 54 54 55 - let styled = resolve_styles(&doc, &sheets).unwrap(); 55 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 56 56 let body = &styled.children[0]; 57 57 let p = &body.children[0]; 58 58 ··· 75 75 let doc = we_html::parse_html(html); 76 76 let sheets = extract_stylesheets(&doc); 77 77 78 - let styled = resolve_styles(&doc, &sheets).unwrap(); 78 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 79 79 let body = &styled.children[0]; 80 80 let p = &body.children[0]; 81 81 ··· 97 97 let sheets = extract_stylesheets(&doc); 98 98 assert!(sheets.is_empty()); 99 99 100 - let styled = resolve_styles(&doc, &sheets).unwrap(); 100 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 101 101 let body = &styled.children[0]; 102 102 103 103 // Body should have 8px margin from UA stylesheet. ··· 122 122 123 123 let doc = we_html::parse_html(html); 124 124 let sheets = extract_stylesheets(&doc); 125 - let styled = resolve_styles(&doc, &sheets).unwrap(); 125 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 126 126 127 127 // The <head> and <style> elements should be removed (display: none). 128 128 // Only body should remain as a child of html. ··· 148 148 149 149 let doc = we_html::parse_html(html); 150 150 let sheets = extract_stylesheets(&doc); 151 - let styled = resolve_styles(&doc, &sheets).unwrap(); 151 + let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); 152 152 let body = &styled.children[0]; 153 153 154 154 let highlight_p = &body.children[0];