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

Configure Feed

Select the types of activity you want to include in your feed.

Implement CSS Custom Properties (--var, var())

Add support for CSS Custom Properties per CSS Custom Properties for
Cascading Variables Module Level 1:

- Custom property declarations (--name: value) stored on ComputedStyle
- var() function substitution with fallback values
- Custom properties inherit through the DOM tree by default
- Cycle detection for circular var() references
- Support for initial/inherit/unset keywords on custom properties
- Nested var() in fallback values: var(--a, var(--b, blue))
- var() substitution in shorthand properties
- Case-sensitive custom property names

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+482 -18
+482 -18
crates/style/src/computed.rs
··· 4 4 //! by collecting matching rules, applying the cascade (specificity + source order), 5 5 //! handling property inheritance, and resolving relative values. 6 6 7 - use we_css::parser::{Declaration, Stylesheet}; 7 + use std::collections::HashMap; 8 + 9 + use we_css::parser::{ComponentValue, Declaration, Stylesheet}; 8 10 use we_css::values::{expand_shorthand, parse_value, Color, CssValue, LengthUnit}; 9 11 use we_dom::{Document, NodeData, NodeId}; 10 12 ··· 364 366 pub flex_basis: LengthOrAuto, 365 367 pub align_self: AlignSelf, 366 368 pub order: i32, 369 + 370 + // CSS Custom Properties (inherited by default) 371 + pub custom_properties: HashMap<String, Vec<ComponentValue>>, 367 372 } 368 373 369 374 impl Default for ComputedStyle { ··· 440 445 flex_basis: LengthOrAuto::Auto, 441 446 align_self: AlignSelf::Auto, 442 447 order: 0, 448 + 449 + custom_properties: HashMap::new(), 443 450 } 444 451 } 445 452 } ··· 1278 1285 } 1279 1286 1280 1287 // --------------------------------------------------------------------------- 1288 + // CSS Custom Properties and var() substitution 1289 + // --------------------------------------------------------------------------- 1290 + 1291 + /// Apply a custom property declaration to the custom properties map. 1292 + fn apply_custom_property( 1293 + custom_properties: &mut HashMap<String, Vec<ComponentValue>>, 1294 + name: &str, 1295 + value: &[ComponentValue], 1296 + parent_custom_properties: &HashMap<String, Vec<ComponentValue>>, 1297 + ) { 1298 + let non_ws: Vec<&ComponentValue> = value 1299 + .iter() 1300 + .filter(|v| !matches!(v, ComponentValue::Whitespace)) 1301 + .collect(); 1302 + 1303 + if non_ws.len() == 1 { 1304 + if let ComponentValue::Ident(kw) = non_ws[0] { 1305 + match kw.to_ascii_lowercase().as_str() { 1306 + "initial" => { 1307 + custom_properties.remove(name); 1308 + return; 1309 + } 1310 + "inherit" | "unset" => { 1311 + // Custom properties are inherited, so unset = inherit 1312 + if let Some(parent_val) = parent_custom_properties.get(name) { 1313 + custom_properties.insert(name.to_string(), parent_val.clone()); 1314 + } else { 1315 + custom_properties.remove(name); 1316 + } 1317 + return; 1318 + } 1319 + _ => {} 1320 + } 1321 + } 1322 + } 1323 + 1324 + custom_properties.insert(name.to_string(), value.to_vec()); 1325 + } 1326 + 1327 + /// Check if a list of component values contains any var() references. 1328 + fn has_var_references(values: &[ComponentValue]) -> bool { 1329 + values.iter().any(|cv| match cv { 1330 + ComponentValue::Function(name, args) => { 1331 + name.eq_ignore_ascii_case("var") || has_var_references(args) 1332 + } 1333 + _ => false, 1334 + }) 1335 + } 1336 + 1337 + /// Substitute all var() references in a list of component values. 1338 + /// Returns `Some(substituted)` on success, `None` if any var() can't be resolved. 1339 + fn substitute_vars( 1340 + values: &[ComponentValue], 1341 + custom_properties: &HashMap<String, Vec<ComponentValue>>, 1342 + ) -> Option<Vec<ComponentValue>> { 1343 + let mut result = Vec::new(); 1344 + let mut resolving = Vec::new(); 1345 + substitute_vars_inner(values, custom_properties, &mut resolving, &mut result)?; 1346 + Some(result) 1347 + } 1348 + 1349 + fn substitute_vars_inner( 1350 + values: &[ComponentValue], 1351 + custom_properties: &HashMap<String, Vec<ComponentValue>>, 1352 + resolving: &mut Vec<String>, 1353 + result: &mut Vec<ComponentValue>, 1354 + ) -> Option<()> { 1355 + for cv in values { 1356 + match cv { 1357 + ComponentValue::Function(name, args) if name.eq_ignore_ascii_case("var") => { 1358 + let resolved = resolve_var_reference(args, custom_properties, resolving)?; 1359 + result.extend(resolved); 1360 + } 1361 + ComponentValue::Function(name, args) => { 1362 + // Recursively substitute vars in function arguments 1363 + let mut resolved_args = Vec::new(); 1364 + substitute_vars_inner(args, custom_properties, resolving, &mut resolved_args)?; 1365 + result.push(ComponentValue::Function(name.clone(), resolved_args)); 1366 + } 1367 + _ => result.push(cv.clone()), 1368 + } 1369 + } 1370 + Some(()) 1371 + } 1372 + 1373 + /// Resolve a single var() reference given its arguments. 1374 + fn resolve_var_reference( 1375 + args: &[ComponentValue], 1376 + custom_properties: &HashMap<String, Vec<ComponentValue>>, 1377 + resolving: &mut Vec<String>, 1378 + ) -> Option<Vec<ComponentValue>> { 1379 + // Parse var() arguments: var(--name) or var(--name, fallback...) 1380 + let non_ws: Vec<&ComponentValue> = args 1381 + .iter() 1382 + .filter(|v| !matches!(v, ComponentValue::Whitespace)) 1383 + .collect(); 1384 + 1385 + if non_ws.is_empty() { 1386 + return None; 1387 + } 1388 + 1389 + // First argument must be a custom property name 1390 + let name = match non_ws[0] { 1391 + ComponentValue::Ident(s) if s.starts_with("--") => s.clone(), 1392 + _ => return None, 1393 + }; 1394 + 1395 + // Find fallback: everything after the first comma in the original args 1396 + let comma_pos = args.iter().position(|v| matches!(v, ComponentValue::Comma)); 1397 + let fallback = comma_pos.map(|pos| &args[pos + 1..]); 1398 + 1399 + // Check for cycles 1400 + if resolving.contains(&name) { 1401 + return resolve_fallback(fallback, custom_properties, resolving); 1402 + } 1403 + 1404 + // Look up the custom property 1405 + if let Some(values) = custom_properties.get(&name) { 1406 + resolving.push(name.clone()); 1407 + let mut result = Vec::new(); 1408 + let ok = substitute_vars_inner(values, custom_properties, resolving, &mut result); 1409 + resolving.pop(); 1410 + 1411 + if ok.is_some() { 1412 + return Some(result); 1413 + } 1414 + } 1415 + 1416 + // Property not found or nested resolution failed: try fallback 1417 + resolve_fallback(fallback, custom_properties, resolving) 1418 + } 1419 + 1420 + /// Resolve a var() fallback value. Returns `None` if no fallback is available. 1421 + fn resolve_fallback( 1422 + fallback: Option<&[ComponentValue]>, 1423 + custom_properties: &HashMap<String, Vec<ComponentValue>>, 1424 + resolving: &mut Vec<String>, 1425 + ) -> Option<Vec<ComponentValue>> { 1426 + let fb_values = fallback?; 1427 + let mut result = Vec::new(); 1428 + substitute_vars_inner(fb_values, custom_properties, resolving, &mut result)?; 1429 + Some(result) 1430 + } 1431 + 1432 + // --------------------------------------------------------------------------- 1281 1433 // Styled tree 1282 1434 // --------------------------------------------------------------------------- 1283 1435 ··· 1427 1579 text_decoration: parent_style.text_decoration, 1428 1580 line_height: parent_style.line_height, 1429 1581 visibility: parent_style.visibility, 1582 + custom_properties: parent_style.custom_properties.clone(), 1430 1583 ..ComputedStyle::default() 1431 1584 }; 1432 1585 1433 1586 // Step 2: Collect matching rules, sorted by specificity + source order 1434 1587 let matched_rules = collect_matching_rules(doc, node, stylesheet); 1435 1588 1436 - // Step 3: Separate normal and !important declarations 1589 + // Step 3: Parse inline style declarations (from style attribute). 1590 + let inline_decls = parse_inline_style(doc, node); 1591 + 1592 + // ---- Pass 1: Resolve custom properties in cascade order ---- 1593 + // Normal declarations from stylesheets 1594 + for matched in &matched_rules { 1595 + for decl in &matched.rule.declarations { 1596 + if decl.property.starts_with("--") && !decl.important { 1597 + apply_custom_property( 1598 + &mut style.custom_properties, 1599 + &decl.property, 1600 + &decl.value, 1601 + &parent_style.custom_properties, 1602 + ); 1603 + } 1604 + } 1605 + } 1606 + // Normal inline declarations 1607 + for decl in &inline_decls { 1608 + if decl.property.starts_with("--") && !decl.important { 1609 + apply_custom_property( 1610 + &mut style.custom_properties, 1611 + &decl.property, 1612 + &decl.value, 1613 + &parent_style.custom_properties, 1614 + ); 1615 + } 1616 + } 1617 + // !important declarations from stylesheets 1618 + for matched in &matched_rules { 1619 + for decl in &matched.rule.declarations { 1620 + if decl.property.starts_with("--") && decl.important { 1621 + apply_custom_property( 1622 + &mut style.custom_properties, 1623 + &decl.property, 1624 + &decl.value, 1625 + &parent_style.custom_properties, 1626 + ); 1627 + } 1628 + } 1629 + } 1630 + // !important inline declarations 1631 + for decl in &inline_decls { 1632 + if decl.property.starts_with("--") && decl.important { 1633 + apply_custom_property( 1634 + &mut style.custom_properties, 1635 + &decl.property, 1636 + &decl.value, 1637 + &parent_style.custom_properties, 1638 + ); 1639 + } 1640 + } 1641 + 1642 + // ---- Pass 2: Resolve regular properties with var() substitution ---- 1437 1643 let mut normal_decls: Vec<(String, CssValue)> = Vec::new(); 1438 1644 let mut important_decls: Vec<(String, CssValue)> = Vec::new(); 1439 1645 1440 1646 for matched in &matched_rules { 1441 1647 for decl in &matched.rule.declarations { 1442 - let property = &decl.property; 1648 + if decl.property.starts_with("--") { 1649 + continue; 1650 + } 1443 1651 1444 - // Try shorthand expansion first 1445 - if let Some(longhands) = expand_shorthand(property, &decl.value, decl.important) { 1652 + // Substitute var() references before parsing 1653 + let resolved_values; 1654 + let values = if has_var_references(&decl.value) { 1655 + resolved_values = match substitute_vars(&decl.value, &style.custom_properties) { 1656 + Some(v) => v, 1657 + None => continue, 1658 + }; 1659 + &resolved_values 1660 + } else { 1661 + &decl.value 1662 + }; 1663 + 1664 + let property = &decl.property; 1665 + if let Some(longhands) = expand_shorthand(property, values, decl.important) { 1446 1666 for lh in longhands { 1447 1667 if lh.important { 1448 1668 important_decls.push((lh.property, lh.value)); ··· 1451 1671 } 1452 1672 } 1453 1673 } else { 1454 - // Regular longhand property 1455 - let value = parse_value(&decl.value); 1674 + let value = parse_value(values); 1456 1675 if decl.important { 1457 1676 important_decls.push((property.clone(), value)); 1458 1677 } else { ··· 1461 1680 } 1462 1681 } 1463 1682 } 1464 - 1465 - // Step 4: Apply inline style declarations (from style attribute). 1466 - // Inline styles have specificity (1,0,0,0) — higher than any selector. 1467 - // We apply them after stylesheet rules so they override. 1468 - let inline_decls = parse_inline_style(doc, node); 1469 1683 1470 1684 // Step 5: Apply normal declarations (already in specificity order) 1471 1685 for (prop, value) in &normal_decls { ··· 1474 1688 1475 1689 // Step 6: Apply inline style normal declarations (override stylesheet normals) 1476 1690 for decl in &inline_decls { 1477 - if !decl.important { 1691 + if !decl.important && !decl.property.starts_with("--") { 1692 + let resolved_values; 1693 + let values = if has_var_references(&decl.value) { 1694 + resolved_values = match substitute_vars(&decl.value, &style.custom_properties) { 1695 + Some(v) => v, 1696 + None => continue, 1697 + }; 1698 + &resolved_values 1699 + } else { 1700 + &decl.value 1701 + }; 1702 + 1478 1703 let property = decl.property.as_str(); 1479 - if let Some(longhands) = expand_shorthand(property, &decl.value, false) { 1704 + if let Some(longhands) = expand_shorthand(property, values, false) { 1480 1705 for lh in &longhands { 1481 1706 apply_property(&mut style, &lh.property, &lh.value, parent_style, viewport); 1482 1707 } 1483 1708 } else { 1484 - let value = parse_value(&decl.value); 1709 + let value = parse_value(values); 1485 1710 apply_property(&mut style, property, &value, parent_style, viewport); 1486 1711 } 1487 1712 } ··· 1494 1719 1495 1720 // Step 8: Apply inline style !important declarations (highest priority) 1496 1721 for decl in &inline_decls { 1497 - if decl.important { 1722 + if decl.important && !decl.property.starts_with("--") { 1723 + let resolved_values; 1724 + let values = if has_var_references(&decl.value) { 1725 + resolved_values = match substitute_vars(&decl.value, &style.custom_properties) { 1726 + Some(v) => v, 1727 + None => continue, 1728 + }; 1729 + &resolved_values 1730 + } else { 1731 + &decl.value 1732 + }; 1733 + 1498 1734 let property = decl.property.as_str(); 1499 - if let Some(longhands) = expand_shorthand(property, &decl.value, true) { 1735 + if let Some(longhands) = expand_shorthand(property, values, true) { 1500 1736 for lh in &longhands { 1501 1737 apply_property(&mut style, &lh.property, &lh.value, parent_style, viewport); 1502 1738 } 1503 1739 } else { 1504 - let value = parse_value(&decl.value); 1740 + let value = parse_value(values); 1505 1741 apply_property(&mut style, property, &value, parent_style, viewport); 1506 1742 } 1507 1743 } ··· 2526 2762 let style = ComputedStyle::default(); 2527 2763 assert!(!style.will_change.transform); 2528 2764 assert!(!style.will_change.opacity); 2765 + } 2766 + 2767 + // ----------------------------------------------------------------------- 2768 + // CSS Custom Properties 2769 + // ----------------------------------------------------------------------- 2770 + 2771 + #[test] 2772 + fn custom_property_declaration_stored() { 2773 + let (mut doc, _, _, body) = make_doc_with_body(); 2774 + let div = doc.create_element("div"); 2775 + let text = doc.create_text("x"); 2776 + doc.append_child(body, div); 2777 + doc.append_child(div, text); 2778 + 2779 + let ss = Parser::parse("div { --my-color: red; }"); 2780 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 2781 + let body_node = &styled.children[0]; 2782 + let div_node = &body_node.children[0]; 2783 + 2784 + assert!(div_node.style.custom_properties.contains_key("--my-color")); 2785 + } 2786 + 2787 + #[test] 2788 + fn var_resolves_to_declared_value() { 2789 + let (mut doc, _, _, body) = make_doc_with_body(); 2790 + let div = doc.create_element("div"); 2791 + let text = doc.create_text("x"); 2792 + doc.append_child(body, div); 2793 + doc.append_child(div, text); 2794 + 2795 + let ss = Parser::parse("div { --my-color: red; color: var(--my-color); }"); 2796 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 2797 + let body_node = &styled.children[0]; 2798 + let div_node = &body_node.children[0]; 2799 + 2800 + assert_eq!(div_node.style.color, Color::rgb(255, 0, 0)); 2801 + } 2802 + 2803 + #[test] 2804 + fn var_fallback_when_missing() { 2805 + let (mut doc, _, _, body) = make_doc_with_body(); 2806 + let div = doc.create_element("div"); 2807 + let text = doc.create_text("x"); 2808 + doc.append_child(body, div); 2809 + doc.append_child(div, text); 2810 + 2811 + let ss = Parser::parse("div { color: var(--undefined, blue); }"); 2812 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 2813 + let body_node = &styled.children[0]; 2814 + let div_node = &body_node.children[0]; 2815 + 2816 + assert_eq!(div_node.style.color, Color::rgb(0, 0, 255)); 2817 + } 2818 + 2819 + #[test] 2820 + fn custom_properties_inherit_through_dom() { 2821 + let (mut doc, _, _, body) = make_doc_with_body(); 2822 + let div = doc.create_element("div"); 2823 + let p = doc.create_element("p"); 2824 + let text = doc.create_text("x"); 2825 + doc.append_child(body, div); 2826 + doc.append_child(div, p); 2827 + doc.append_child(p, text); 2828 + 2829 + let ss = Parser::parse("div { --text-color: green; } p { color: var(--text-color); }"); 2830 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 2831 + let body_node = &styled.children[0]; 2832 + let div_node = &body_node.children[0]; 2833 + let p_node = &div_node.children[0]; 2834 + 2835 + // --text-color defined on div, inherited by p 2836 + assert!(div_node 2837 + .style 2838 + .custom_properties 2839 + .contains_key("--text-color")); 2840 + assert!(p_node.style.custom_properties.contains_key("--text-color")); 2841 + assert_eq!(p_node.style.color, Color::rgb(0, 128, 0)); 2842 + } 2843 + 2844 + #[test] 2845 + fn custom_property_overridden_in_child() { 2846 + let (mut doc, _, _, body) = make_doc_with_body(); 2847 + let div = doc.create_element("div"); 2848 + let p = doc.create_element("p"); 2849 + let text = doc.create_text("x"); 2850 + doc.append_child(body, div); 2851 + doc.append_child(div, p); 2852 + doc.append_child(p, text); 2853 + 2854 + let ss = Parser::parse("div { --c: red; } p { --c: blue; color: var(--c); }"); 2855 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 2856 + let body_node = &styled.children[0]; 2857 + let div_node = &body_node.children[0]; 2858 + let p_node = &div_node.children[0]; 2859 + 2860 + // div has --c: red, p overrides to blue 2861 + assert_eq!(div_node.style.color, Color::rgb(0, 0, 0)); // default black 2862 + assert_eq!(p_node.style.color, Color::rgb(0, 0, 255)); // blue 2863 + } 2864 + 2865 + #[test] 2866 + fn cyclic_var_references_use_fallback() { 2867 + let (mut doc, _, _, body) = make_doc_with_body(); 2868 + let div = doc.create_element("div"); 2869 + let text = doc.create_text("x"); 2870 + doc.append_child(body, div); 2871 + doc.append_child(div, text); 2872 + 2873 + // --a references --b, --b references --a → cycle 2874 + let ss = Parser::parse("div { --a: var(--b); --b: var(--a); color: var(--a, red); }"); 2875 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 2876 + let body_node = &styled.children[0]; 2877 + let div_node = &body_node.children[0]; 2878 + 2879 + // Cycle detected, fallback to red 2880 + assert_eq!(div_node.style.color, Color::rgb(255, 0, 0)); 2881 + } 2882 + 2883 + #[test] 2884 + fn cyclic_var_no_fallback_skips_property() { 2885 + let (mut doc, _, _, body) = make_doc_with_body(); 2886 + let div = doc.create_element("div"); 2887 + let text = doc.create_text("x"); 2888 + doc.append_child(body, div); 2889 + doc.append_child(div, text); 2890 + 2891 + // Cycle with no fallback: color should remain default (inherited black) 2892 + let ss = Parser::parse("div { --a: var(--b); --b: var(--a); color: var(--a); }"); 2893 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 2894 + let body_node = &styled.children[0]; 2895 + let div_node = &body_node.children[0]; 2896 + 2897 + assert_eq!(div_node.style.color, Color::rgb(0, 0, 0)); // default 2898 + } 2899 + 2900 + #[test] 2901 + fn nested_var_in_fallback() { 2902 + let (mut doc, _, _, body) = make_doc_with_body(); 2903 + let div = doc.create_element("div"); 2904 + let text = doc.create_text("x"); 2905 + doc.append_child(body, div); 2906 + doc.append_child(div, text); 2907 + 2908 + // var(--missing, var(--fallback-color)) 2909 + let ss = Parser::parse( 2910 + "div { --fallback-color: green; color: var(--missing, var(--fallback-color)); }", 2911 + ); 2912 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 2913 + let body_node = &styled.children[0]; 2914 + let div_node = &body_node.children[0]; 2915 + 2916 + assert_eq!(div_node.style.color, Color::rgb(0, 128, 0)); 2917 + } 2918 + 2919 + #[test] 2920 + fn var_in_shorthand() { 2921 + let (mut doc, _, _, body) = make_doc_with_body(); 2922 + let div = doc.create_element("div"); 2923 + let text = doc.create_text("x"); 2924 + doc.append_child(body, div); 2925 + doc.append_child(div, text); 2926 + 2927 + let ss = Parser::parse("div { --spacing: 10px; margin: var(--spacing); }"); 2928 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 2929 + let body_node = &styled.children[0]; 2930 + let div_node = &body_node.children[0]; 2931 + 2932 + assert_eq!(div_node.style.margin_top, LengthOrAuto::Length(10.0)); 2933 + assert_eq!(div_node.style.margin_right, LengthOrAuto::Length(10.0)); 2934 + assert_eq!(div_node.style.margin_bottom, LengthOrAuto::Length(10.0)); 2935 + assert_eq!(div_node.style.margin_left, LengthOrAuto::Length(10.0)); 2936 + } 2937 + 2938 + #[test] 2939 + fn custom_property_initial_removes() { 2940 + let (mut doc, _, _, body) = make_doc_with_body(); 2941 + let div = doc.create_element("div"); 2942 + let p = doc.create_element("p"); 2943 + let text = doc.create_text("x"); 2944 + doc.append_child(body, div); 2945 + doc.append_child(div, p); 2946 + doc.append_child(p, text); 2947 + 2948 + // div defines --c, p resets it with initial 2949 + let ss = Parser::parse("div { --c: red; } p { --c: initial; color: var(--c, blue); }"); 2950 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 2951 + let body_node = &styled.children[0]; 2952 + let div_node = &body_node.children[0]; 2953 + let p_node = &div_node.children[0]; 2954 + 2955 + assert!(div_node.style.custom_properties.contains_key("--c")); 2956 + assert!(!p_node.style.custom_properties.contains_key("--c")); 2957 + // Fallback used since --c is removed 2958 + assert_eq!(p_node.style.color, Color::rgb(0, 0, 255)); 2959 + } 2960 + 2961 + #[test] 2962 + fn custom_property_case_sensitive() { 2963 + let (mut doc, _, _, body) = make_doc_with_body(); 2964 + let div = doc.create_element("div"); 2965 + let text = doc.create_text("x"); 2966 + doc.append_child(body, div); 2967 + doc.append_child(div, text); 2968 + 2969 + // --Color and --color are different properties 2970 + let ss = Parser::parse("div { --Color: red; --color: blue; color: var(--color); }"); 2971 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 2972 + let body_node = &styled.children[0]; 2973 + let div_node = &body_node.children[0]; 2974 + 2975 + assert_eq!(div_node.style.color, Color::rgb(0, 0, 255)); 2976 + } 2977 + 2978 + #[test] 2979 + fn chained_var_references() { 2980 + let (mut doc, _, _, body) = make_doc_with_body(); 2981 + let div = doc.create_element("div"); 2982 + let text = doc.create_text("x"); 2983 + doc.append_child(body, div); 2984 + doc.append_child(div, text); 2985 + 2986 + // --a → --b → actual value 2987 + let ss = Parser::parse("div { --base: green; --alias: var(--base); color: var(--alias); }"); 2988 + let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); 2989 + let body_node = &styled.children[0]; 2990 + let div_node = &body_node.children[0]; 2991 + 2992 + assert_eq!(div_node.style.color, Color::rgb(0, 128, 0)); 2529 2993 } 2530 2994 }