orinium_browser/engine/layouter/
css_resolver.rs

1//! A CSS resolver that handles selector matching and value resolution.
2
3use crate::engine::css::parser::{ComplexSelector, CssNode, CssNodeType};
4use crate::engine::css::values::CssValue;
5
6use std::collections::HashMap;
7
8type CustomProperties = HashMap<String, CssValue>;
9
10/// A single CSS declaration after selector resolution and value processing.
11///
12/// `ResolvedDeclaration` represents one property-value pair that has been
13/// fully associated with a selector and enriched with all information
14/// required for CSS cascade resolution.
15///
16/// This structure is produced after:
17/// - Parsing selectors
18/// - Resolving `var()` using custom properties
19/// - Computing selector specificity
20///
21/// During the cascade phase, multiple `ResolvedDeclaration`s with the same
22/// property name may compete. The winner is determined by comparing:
23///
24/// 1. `specificity` (higher specificity wins)
25/// 2. `order` (later declarations win)
26#[derive(Debug, Clone)]
27pub struct ResolvedDeclaration {
28    /// The selector this declaration originates from.
29    pub selector: ComplexSelector,
30
31    /// The CSS property name (e.g. `"color"`, `"margin-top"`).
32    pub name: String,
33
34    /// The resolved CSS value for the property.
35    /// This value has already had `var()` functions expanded.
36    pub value: CssValue,
37
38    /// The specificity of the selector, represented as (a, b, c).
39    /// - a: ID selectors
40    /// - b: class, attribute, and pseudo-class selectors
41    /// - c: type and pseudo-element selectors
42    pub specificity: (u32, u32, u32),
43
44    /// The source order of the declaration.
45    /// Higher values indicate declarations that appear later in the stylesheet.
46    pub order: usize,
47
48    /// Whether this declaration is marked as `!important`.
49    pub important: bool,
50}
51
52pub type ResolvedStyles = Vec<ResolvedDeclaration>;
53
54pub struct CssResolver;
55
56impl CssResolver {
57    pub fn resolve(stylesheet: &CssNode) -> ResolvedStyles {
58        let mut styles = Vec::new();
59        let mut order = 0;
60        Self::walk(stylesheet, &mut styles, &mut order);
61        styles
62    }
63
64    fn walk(node: &CssNode, styles: &mut ResolvedStyles, order: &mut usize) {
65        if let CssNodeType::Rule { selectors } = &node.node() {
66            let declarations = Self::collect_declarations(node);
67
68            for selector in selectors {
69                let specificity = selector.specificity();
70
71                for (name, value, important) in &declarations {
72                    styles.push(ResolvedDeclaration {
73                        selector: selector.clone(),
74                        name: name.clone(),
75                        value: value.clone(),
76                        specificity,
77                        order: *order,
78                        important: *important,
79                    });
80                    *order += 1;
81                }
82            }
83        }
84
85        for child in node.children() {
86            Self::walk(child, styles, order);
87        }
88    }
89
90    fn collect_declarations(rule_node: &CssNode) -> Vec<(String, CssValue, bool)> {
91        let mut result = Vec::new();
92        let mut custom_props: CustomProperties = HashMap::new();
93
94        // 1. custom property を先に集める
95        for child in rule_node.children() {
96            if let CssNodeType::Declaration { name, value } = &child.node()
97                && name.starts_with("--")
98            {
99                custom_props.insert(name.clone(), value.clone());
100            }
101        }
102
103        // 2. 通常の declaration を var 解決して追加
104        for child in rule_node.children() {
105            if let CssNodeType::Declaration { name, value } = &child.node() {
106                let (raw_value, important) = Self::extract_important(value);
107
108                if name.starts_with("--") {
109                    result.push((name.clone(), raw_value, important));
110                } else if let Some(resolved) = Self::resolve_var(&raw_value, &custom_props) {
111                    result.push((name.clone(), resolved, important));
112                }
113            }
114        }
115
116        result
117    }
118
119    fn extract_important(value: &CssValue) -> (CssValue, bool) {
120        match value {
121            CssValue::List(list) if list.len() >= 2 => {
122                let len = list.len();
123                let is_important = matches!(
124                    (&list[len - 2], &list[len - 1]),
125                    (
126                        CssValue::Keyword(bang),
127                        CssValue::Keyword(ident)
128                    )
129                    if bang == "!" && ident.eq_ignore_ascii_case("important")
130                );
131
132                if is_important {
133                    let value = if len - 2 == 1 {
134                        list.iter().next().unwrap().clone()
135                    } else {
136                        CssValue::List(list[..len - 2].to_vec())
137                    };
138                    return (value, true);
139                }
140
141                (value.clone(), false)
142            }
143            _ => (value.clone(), false),
144        }
145    }
146
147    fn resolve_var(value: &CssValue, custom_props: &CustomProperties) -> Option<CssValue> {
148        match value {
149            CssValue::Function(name, args) if name == "var" => {
150                // var(--x [, fallback])
151                let var_name = match args.first() {
152                    Some(CssValue::Keyword(name)) => name,
153                    _ => return None,
154                };
155
156                if let Some(v) = custom_props.get(var_name) {
157                    Self::resolve_var(v, custom_props)
158                } else if let Some(fallback) = args.get(1) {
159                    Self::resolve_var(fallback, custom_props)
160                } else {
161                    None
162                }
163            }
164
165            CssValue::Function(name, args) => {
166                let resolved_args = args
167                    .iter()
168                    .map(|v| Self::resolve_var(v, custom_props))
169                    .collect::<Option<Vec<_>>>()?;
170                Some(CssValue::Function(name.clone(), resolved_args))
171            }
172
173            CssValue::List(list) => {
174                let resolved = list
175                    .iter()
176                    .map(|v| Self::resolve_var(v, custom_props))
177                    .collect::<Option<Vec<_>>>()?;
178                Some(CssValue::List(resolved))
179            }
180
181            _ => Some(value.clone()),
182        }
183    }
184}