Skip to main content

hpx_browser/css_cascade/
cascade.rs

1use crate::{
2    css_cascade::layers::LayerId,
3    css_selectors::Specificity,
4    css_values::property::{CssValue, PropertyDeclaration, PropertyId},
5};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
8pub enum Origin {
9    UserAgent = 0,
10    User = 1,
11    Author = 2,
12}
13
14#[derive(Debug, Clone)]
15pub struct CascadeEntry {
16    pub declaration: PropertyDeclaration,
17    pub origin: Origin,
18    pub layer: Option<LayerId>,
19    pub specificity: Specificity,
20    pub source_order: u32,
21}
22
23pub fn cascade_sort(
24    entries: &mut [CascadeEntry],
25) -> std::collections::HashMap<PropertyId, CssValue> {
26    entries.sort_by(cascade_compare);
27
28    let mut result = std::collections::HashMap::new();
29    for entry in entries.iter() {
30        result.insert(
31            entry.declaration.property.clone(),
32            entry.declaration.value.clone(),
33        );
34    }
35    result
36}
37
38fn cascade_compare(a: &CascadeEntry, b: &CascadeEntry) -> std::cmp::Ordering {
39    let a_important = a.declaration.important;
40    let b_important = b.declaration.important;
41
42    let a_priority = origin_priority(a.origin, a_important);
43    let b_priority = origin_priority(b.origin, b_important);
44    let ord = a_priority.cmp(&b_priority);
45    if ord != std::cmp::Ordering::Equal {
46        return ord;
47    }
48
49    match (&a.layer, &b.layer) {
50        (None, Some(_)) if !a_important => return std::cmp::Ordering::Greater,
51        (Some(_), None) if !a_important => return std::cmp::Ordering::Less,
52        (None, Some(_)) if a_important => return std::cmp::Ordering::Less,
53        (Some(_), None) if a_important => return std::cmp::Ordering::Greater,
54        (Some(la), Some(lb)) => {
55            let layer_ord = la.cmp(lb);
56            if layer_ord != std::cmp::Ordering::Equal {
57                return if a_important {
58                    layer_ord.reverse()
59                } else {
60                    layer_ord
61                };
62            }
63        }
64        _ => {}
65    }
66
67    let spec_ord = a.specificity.cmp(&b.specificity);
68    if spec_ord != std::cmp::Ordering::Equal {
69        return spec_ord;
70    }
71
72    a.source_order.cmp(&b.source_order)
73}
74
75fn origin_priority(origin: Origin, important: bool) -> u8 {
76    if important {
77        match origin {
78            Origin::Author => 4,
79            Origin::User => 5,
80            Origin::UserAgent => 6,
81        }
82    } else {
83        match origin {
84            Origin::UserAgent => 1,
85            Origin::User => 2,
86            Origin::Author => 3,
87        }
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use crate::css_values::types::color::Color;
95
96    fn entry(
97        origin: Origin,
98        specificity: (u32, u32, u32),
99        order: u32,
100        important: bool,
101        value: CssValue,
102    ) -> CascadeEntry {
103        CascadeEntry {
104            declaration: PropertyDeclaration {
105                property: PropertyId::Color,
106                value,
107                important,
108            },
109            origin,
110            layer: None,
111            specificity: Specificity::new(specificity.0, specificity.1, specificity.2),
112            source_order: order,
113        }
114    }
115
116    #[test]
117    fn higher_specificity_wins() {
118        let mut entries = vec![
119            entry(
120                Origin::Author,
121                (0, 1, 0),
122                0,
123                false,
124                CssValue::Color(Color::Rgba {
125                    r: 255,
126                    g: 0,
127                    b: 0,
128                    a: 1.0,
129                }),
130            ),
131            entry(
132                Origin::Author,
133                (1, 0, 0),
134                1,
135                false,
136                CssValue::Color(Color::Rgba {
137                    r: 0,
138                    g: 0,
139                    b: 255,
140                    a: 1.0,
141                }),
142            ),
143        ];
144        let result = cascade_sort(&mut entries);
145        assert!(matches!(
146            result.get(&PropertyId::Color),
147            Some(CssValue::Color(Color::Rgba {
148                r: 0,
149                g: 0,
150                b: 255,
151                ..
152            }))
153        ));
154    }
155
156    #[test]
157    fn important_beats_normal() {
158        let mut entries = vec![
159            entry(
160                Origin::Author,
161                (1, 0, 0),
162                1,
163                false,
164                CssValue::Color(Color::Rgba {
165                    r: 255,
166                    g: 0,
167                    b: 0,
168                    a: 1.0,
169                }),
170            ),
171            entry(
172                Origin::Author,
173                (0, 0, 1),
174                0,
175                true,
176                CssValue::Color(Color::Rgba {
177                    r: 0,
178                    g: 0,
179                    b: 255,
180                    a: 1.0,
181                }),
182            ),
183        ];
184        let result = cascade_sort(&mut entries);
185        assert!(matches!(
186            result.get(&PropertyId::Color),
187            Some(CssValue::Color(Color::Rgba {
188                r: 0,
189                g: 0,
190                b: 255,
191                ..
192            }))
193        ));
194    }
195}