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}