Skip to main content

hpx_browser/css_cascade/
computed.rs

1use std::collections::HashMap;
2
3use crate::{
4    css_cascade::{inheritance::is_inherited, initial::initial_value},
5    css_values::property::{CssValue, PropertyId},
6};
7
8#[derive(Debug, Clone, Default)]
9pub struct ComputedStyle {
10    properties: HashMap<PropertyId, CssValue>,
11}
12
13impl ComputedStyle {
14    pub fn new() -> Self {
15        Self::default()
16    }
17
18    pub fn set(&mut self, property: PropertyId, value: CssValue) {
19        self.properties.insert(property, value);
20    }
21
22    pub fn get(&self, property: &PropertyId) -> Option<&CssValue> {
23        self.properties.get(property)
24    }
25
26    pub fn get_or_initial(&self, property: &PropertyId) -> CssValue {
27        self.properties
28            .get(property)
29            .cloned()
30            .unwrap_or_else(|| initial_value(property))
31    }
32
33    pub fn resolve(
34        cascaded: &HashMap<PropertyId, CssValue>,
35        parent: Option<&ComputedStyle>,
36    ) -> Self {
37        let mut style = ComputedStyle::new();
38
39        let all_properties = all_property_ids();
40
41        for prop in &all_properties {
42            let value = if let Some(cascaded_value) = cascaded.get(prop) {
43                match cascaded_value {
44                    CssValue::Inherit => parent
45                        .and_then(|p| p.get(prop))
46                        .cloned()
47                        .unwrap_or_else(|| initial_value(prop)),
48                    CssValue::Initial => initial_value(prop),
49                    CssValue::Unset => {
50                        if is_inherited(prop) {
51                            parent
52                                .and_then(|p| p.get(prop))
53                                .cloned()
54                                .unwrap_or_else(|| initial_value(prop))
55                        } else {
56                            initial_value(prop)
57                        }
58                    }
59                    CssValue::Revert | CssValue::RevertLayer => {
60                        if is_inherited(prop) {
61                            parent
62                                .and_then(|p| p.get(prop))
63                                .cloned()
64                                .unwrap_or_else(|| initial_value(prop))
65                        } else {
66                            initial_value(prop)
67                        }
68                    }
69                    other => other.clone(),
70                }
71            } else if is_inherited(prop) {
72                parent
73                    .and_then(|p| p.get(prop))
74                    .cloned()
75                    .unwrap_or_else(|| initial_value(prop))
76            } else {
77                initial_value(prop)
78            };
79
80            style.set(prop.clone(), value);
81        }
82
83        style
84    }
85}
86
87fn all_property_ids() -> Vec<PropertyId> {
88    vec![
89        PropertyId::Display,
90        PropertyId::Position,
91        PropertyId::Width,
92        PropertyId::Height,
93        PropertyId::MinWidth,
94        PropertyId::MinHeight,
95        PropertyId::MaxWidth,
96        PropertyId::MaxHeight,
97        PropertyId::MarginTop,
98        PropertyId::MarginRight,
99        PropertyId::MarginBottom,
100        PropertyId::MarginLeft,
101        PropertyId::PaddingTop,
102        PropertyId::PaddingRight,
103        PropertyId::PaddingBottom,
104        PropertyId::PaddingLeft,
105        PropertyId::BorderTopWidth,
106        PropertyId::BorderRightWidth,
107        PropertyId::BorderBottomWidth,
108        PropertyId::BorderLeftWidth,
109        PropertyId::BoxSizing,
110        PropertyId::OverflowX,
111        PropertyId::OverflowY,
112        PropertyId::Float,
113        PropertyId::Clear,
114        PropertyId::FlexDirection,
115        PropertyId::FlexWrap,
116        PropertyId::FlexGrow,
117        PropertyId::FlexShrink,
118        PropertyId::FlexBasis,
119        PropertyId::AlignItems,
120        PropertyId::AlignSelf,
121        PropertyId::AlignContent,
122        PropertyId::JustifyContent,
123        PropertyId::JustifyItems,
124        PropertyId::JustifySelf,
125        PropertyId::Gap,
126        PropertyId::RowGap,
127        PropertyId::ColumnGap,
128        PropertyId::FontSize,
129        PropertyId::FontFamily,
130        PropertyId::FontWeight,
131        PropertyId::FontStyle,
132        PropertyId::LineHeight,
133        PropertyId::TextAlign,
134        PropertyId::WhiteSpace,
135        PropertyId::Color,
136        PropertyId::BackgroundColor,
137        PropertyId::Visibility,
138        PropertyId::Opacity,
139        PropertyId::ZIndex,
140        PropertyId::ContentVisibility,
141        PropertyId::Transform,
142    ]
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148    use crate::css_values::types::{color::Color, display::Display};
149
150    #[test]
151    fn initial_values_default() {
152        let style = ComputedStyle::resolve(&HashMap::new(), None);
153        assert_eq!(
154            style.get_or_initial(&PropertyId::Display),
155            CssValue::Display(Display::Inline)
156        );
157        assert_eq!(
158            style.get_or_initial(&PropertyId::Opacity),
159            CssValue::Number(1.0)
160        );
161    }
162
163    #[test]
164    fn inheritance_works() {
165        let mut parent = ComputedStyle::new();
166        parent.set(
167            PropertyId::Color,
168            CssValue::Color(Color::Rgba {
169                r: 255,
170                g: 0,
171                b: 0,
172                a: 1.0,
173            }),
174        );
175
176        let child = ComputedStyle::resolve(&HashMap::new(), Some(&parent));
177        assert_eq!(
178            child.get(&PropertyId::Color),
179            Some(&CssValue::Color(Color::Rgba {
180                r: 255,
181                g: 0,
182                b: 0,
183                a: 1.0
184            }))
185        );
186    }
187
188    #[test]
189    fn non_inherited_uses_initial() {
190        let mut parent = ComputedStyle::new();
191        parent.set(PropertyId::Display, CssValue::Display(Display::Flex));
192
193        let child = ComputedStyle::resolve(&HashMap::new(), Some(&parent));
194        assert_eq!(
195            child.get(&PropertyId::Display),
196            Some(&CssValue::Display(Display::Inline))
197        );
198    }
199}