1use alloc::vec::Vec;
4
5use azul_css::{
6 CssContentGroup, CssNthChildSelector::*, CssPath, CssPathPseudoSelector, CssPathSelector,
7};
8
9use crate::{
10 dom::NodeData,
11 id_tree::{NodeDataContainer, NodeDataContainerRef, NodeHierarchyRef, NodeId},
12 styled_dom::NodeHierarchyItem,
13};
14
15#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[repr(C)]
18pub struct CascadeInfo {
19 pub index_in_parent: u32,
20 pub is_last_child: bool,
21}
22
23impl_vec!(CascadeInfo, CascadeInfoVec, CascadeInfoVecDestructor);
24impl_vec_mut!(CascadeInfo, CascadeInfoVec);
25impl_vec_debug!(CascadeInfo, CascadeInfoVec);
26impl_vec_partialord!(CascadeInfo, CascadeInfoVec);
27impl_vec_clone!(CascadeInfo, CascadeInfoVec, CascadeInfoVecDestructor);
28impl_vec_partialeq!(CascadeInfo, CascadeInfoVec);
29
30impl CascadeInfoVec {
31 pub fn as_container<'a>(&'a self) -> NodeDataContainerRef<'a, CascadeInfo> {
32 NodeDataContainerRef {
33 internal: self.as_ref(),
34 }
35 }
36}
37
38pub(crate) fn matches_html_element(
41 css_path: &CssPath,
42 node_id: NodeId,
43 node_hierarchy: &NodeDataContainerRef<NodeHierarchyItem>,
44 node_data: &NodeDataContainerRef<NodeData>,
45 html_node_tree: &NodeDataContainerRef<CascadeInfo>,
46 expected_path_ending: Option<CssPathPseudoSelector>,
47) -> bool {
48 use self::CssGroupSplitReason::*;
49
50 if css_path.selectors.is_empty() {
51 return false;
52 }
53
54 let mut current_node = Some(node_id);
55 let mut direct_parent_has_to_match = false;
56 let mut last_selector_matched = true;
57
58 let mut iterator = CssGroupIterator::new(css_path.selectors.as_ref());
59 while let Some((content_group, reason)) = iterator.next() {
60 let is_last_content_group = iterator.is_last_content_group();
61 let cur_node_id = match current_node {
62 Some(c) => c,
63 None => {
64 return *content_group == [&CssPathSelector::Global];
68 }
69 };
70
71 let current_selector_matches = selector_group_matches(
72 &content_group,
73 &html_node_tree[cur_node_id],
74 &node_data[cur_node_id],
75 expected_path_ending,
76 is_last_content_group,
77 );
78
79 if direct_parent_has_to_match && !current_selector_matches {
80 return false; }
84
85 if current_selector_matches && !last_selector_matched {
88 return false;
89 }
90
91 last_selector_matched = current_selector_matches;
93 direct_parent_has_to_match = reason == DirectChildren;
95 current_node = node_hierarchy[cur_node_id].parent_id();
96 }
97
98 last_selector_matched
99}
100
101pub(crate) struct CssGroupIterator<'a> {
114 pub css_path: &'a [CssPathSelector],
115 pub current_idx: usize,
116 pub last_reason: CssGroupSplitReason,
117}
118
119#[derive(Debug, Copy, Clone, PartialEq, Eq)]
120pub(crate) enum CssGroupSplitReason {
121 Children,
123 DirectChildren,
125}
126
127impl<'a> CssGroupIterator<'a> {
128 pub fn new(css_path: &'a [CssPathSelector]) -> Self {
129 let initial_len = css_path.len();
130 Self {
131 css_path,
132 current_idx: initial_len,
133 last_reason: CssGroupSplitReason::Children,
134 }
135 }
136 pub fn is_last_content_group(&self) -> bool {
137 self.current_idx.saturating_add(1) == self.css_path.len().saturating_sub(1)
138 }
139}
140
141impl<'a> Iterator for CssGroupIterator<'a> {
142 type Item = (CssContentGroup<'a>, CssGroupSplitReason);
143
144 fn next(&mut self) -> Option<(CssContentGroup<'a>, CssGroupSplitReason)> {
145 use self::CssPathSelector::*;
146
147 let mut new_idx = self.current_idx;
148
149 if new_idx == 0 {
150 return None;
151 }
152
153 let mut current_path = Vec::new();
154
155 while new_idx != 0 {
156 match self.css_path.get(new_idx - 1)? {
157 Children => {
158 self.last_reason = CssGroupSplitReason::Children;
159 break;
160 }
161 DirectChildren => {
162 self.last_reason = CssGroupSplitReason::DirectChildren;
163 break;
164 }
165 other => current_path.push(other),
166 }
167 new_idx -= 1;
168 }
169
170 #[cfg(test)]
173 current_path.reverse();
174
175 if new_idx == 0 {
176 if current_path.is_empty() {
177 None
178 } else {
179 self.current_idx = 0;
181 Some((current_path, self.last_reason))
182 }
183 } else {
184 self.current_idx = new_idx - 1;
186 Some((current_path, self.last_reason))
187 }
188 }
189}
190
191pub(crate) fn construct_html_cascade_tree(
192 node_hierarchy: &NodeHierarchyRef,
193 node_depths_sorted: &[(usize, NodeId)],
194) -> NodeDataContainer<CascadeInfo> {
195 let mut nodes = (0..node_hierarchy.len())
196 .map(|_| CascadeInfo {
197 index_in_parent: 0,
198 is_last_child: false,
199 })
200 .collect::<Vec<_>>();
201
202 for (_depth, parent_id) in node_depths_sorted {
203 let index_in_parent = parent_id.preceding_siblings(node_hierarchy).count();
205
206 let parent_html_matcher = CascadeInfo {
207 index_in_parent: (index_in_parent - 1) as u32,
208 is_last_child: node_hierarchy[*parent_id].next_sibling.is_none(), };
210
211 nodes[parent_id.index()] = parent_html_matcher;
212
213 for (child_idx, child_id) in parent_id.children(node_hierarchy).enumerate() {
214 let child_html_matcher = CascadeInfo {
215 index_in_parent: child_idx as u32,
216 is_last_child: node_hierarchy[child_id].next_sibling.is_none(),
217 };
218
219 nodes[child_id.index()] = child_html_matcher;
220 }
221 }
222
223 NodeDataContainer { internal: nodes }
224}
225
226#[inline]
228pub fn rule_ends_with(path: &CssPath, target: Option<CssPathPseudoSelector>) -> bool {
229 match target {
230 None => match path.selectors.as_ref().last() {
231 None => false,
232 Some(q) => match q {
233 CssPathSelector::PseudoSelector(_) => false,
234 _ => true,
235 },
236 },
237 Some(s) => match path.selectors.as_ref().last() {
238 None => false,
239 Some(q) => match q {
240 CssPathSelector::PseudoSelector(q) => *q == s,
241 _ => false,
242 },
243 },
244 }
245}
246
247pub(crate) fn selector_group_matches(
252 selectors: &[&CssPathSelector],
253 html_node: &CascadeInfo,
254 node_data: &NodeData,
255 expected_path_ending: Option<CssPathPseudoSelector>,
256 is_last_content_group: bool,
257) -> bool {
258 use self::CssPathSelector::*;
259
260 for selector in selectors {
261 match selector {
262 Global => {}
263 Type(t) => {
264 if node_data.get_node_type().get_path() != *t {
265 return false;
266 }
267 }
268 Class(c) => {
269 if !node_data
270 .get_ids_and_classes()
271 .iter()
272 .filter_map(|i| i.as_class())
273 .any(|class| class == c.as_str())
274 {
275 return false;
276 }
277 }
278 Id(id) => {
279 if !node_data
280 .get_ids_and_classes()
281 .iter()
282 .filter_map(|i| i.as_id())
283 .any(|html_id| html_id == id.as_str())
284 {
285 return false;
286 }
287 }
288 PseudoSelector(p) => {
289 match p {
290 CssPathPseudoSelector::First => {
291 if html_node.index_in_parent != 0 {
293 return false;
294 }
295 }
296 CssPathPseudoSelector::Last => {
297 if !html_node.is_last_child {
299 return false;
300 }
301 }
302 CssPathPseudoSelector::NthChild(x) => {
303 use azul_css::CssNthChildPattern;
304 let index_in_parent = html_node.index_in_parent + 1; match *x {
306 Number(value) => {
307 if index_in_parent != value {
308 return false;
309 }
310 }
311 Even => {
312 if index_in_parent % 2 == 0 {
313 return false;
314 }
315 }
316 Odd => {
317 if index_in_parent % 2 == 1 {
318 return false;
319 }
320 }
321 Pattern(CssNthChildPattern { repeat, offset }) => {
322 if index_in_parent >= offset
323 && ((index_in_parent - offset) % repeat != 0)
324 {
325 return false;
326 }
327 }
328 }
329 }
330
331 CssPathPseudoSelector::Hover => {
335 if !is_last_content_group {
336 return false;
337 }
338 if expected_path_ending != Some(CssPathPseudoSelector::Hover) {
339 return false;
340 }
341 }
342 CssPathPseudoSelector::Active => {
343 if !is_last_content_group {
344 return false;
345 }
346 if expected_path_ending != Some(CssPathPseudoSelector::Active) {
347 return false;
348 }
349 }
350 CssPathPseudoSelector::Focus => {
351 if !is_last_content_group {
352 return false;
353 }
354 if expected_path_ending != Some(CssPathPseudoSelector::Focus) {
355 return false;
356 }
357 }
358 }
359 }
360 DirectChildren | Children => {
361 return false;
363 }
364 }
365 }
366
367 true
368}