1use alloc::vec::Vec;
4
5use azul_css::css::{
6 CssContentGroup, CssNthChildSelector, CssNthChildSelector::*, CssPath, CssPathPseudoSelector,
7 CssPathSelector,
8};
9
10use crate::{
11 dom::NodeData,
12 id::{NodeDataContainer, NodeDataContainerRef, NodeHierarchyRef, NodeId},
13 styled_dom::NodeHierarchyItem,
14};
15
16#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
18#[repr(C)]
19pub struct CascadeInfo {
20 pub index_in_parent: u32,
21 pub is_last_child: bool,
22}
23
24impl_option!(
25 CascadeInfo,
26 OptionCascadeInfo,
27 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
28);
29
30impl_vec!(CascadeInfo, CascadeInfoVec, CascadeInfoVecDestructor, CascadeInfoVecDestructorType, CascadeInfoVecSlice, OptionCascadeInfo);
31impl_vec_mut!(CascadeInfo, CascadeInfoVec);
32impl_vec_debug!(CascadeInfo, CascadeInfoVec);
33impl_vec_partialord!(CascadeInfo, CascadeInfoVec);
34impl_vec_clone!(CascadeInfo, CascadeInfoVec, CascadeInfoVecDestructor);
35impl_vec_partialeq!(CascadeInfo, CascadeInfoVec);
36
37impl CascadeInfoVec {
38 pub fn as_container<'a>(&'a self) -> NodeDataContainerRef<'a, CascadeInfo> {
39 NodeDataContainerRef {
40 internal: self.as_ref(),
41 }
42 }
43}
44
45pub fn matches_html_element(
48 css_path: &CssPath,
49 node_id: NodeId,
50 node_hierarchy: &NodeDataContainerRef<NodeHierarchyItem>,
51 node_data: &NodeDataContainerRef<NodeData>,
52 html_node_tree: &NodeDataContainerRef<CascadeInfo>,
53 expected_path_ending: Option<CssPathPseudoSelector>,
54) -> bool {
55 use self::CssGroupSplitReason::*;
56
57 if css_path.selectors.is_empty() {
58 return false;
59 }
60
61 if node_data[node_id].is_anonymous() {
64 return false;
65 }
66
67 let mut current_node = Some(node_id);
68 let mut next_match_requirement = Children; let mut last_selector_matched = true;
70
71 let mut iterator = CssGroupIterator::new(css_path.selectors.as_ref());
72 while let Some((content_group, reason)) = iterator.next() {
73 let is_last_content_group = iterator.is_last_content_group();
74 let cur_node_id = match current_node {
75 Some(c) => c,
76 None => {
77 return *content_group == [&CssPathSelector::Global];
81 }
82 };
83
84 let current_selector_matches = selector_group_matches(
85 &content_group,
86 &html_node_tree[cur_node_id],
87 &node_data[cur_node_id],
88 expected_path_ending.clone(),
89 is_last_content_group,
90 );
91
92 match next_match_requirement {
93 DirectChildren => {
94 if !current_selector_matches {
96 return false;
97 }
98 }
99 AdjacentSibling => {
100 if !current_selector_matches {
102 return false;
103 }
104 }
105 GeneralSibling => {
106 if !current_selector_matches {
109 let mut found_match = false;
111 let mut sibling = node_hierarchy[cur_node_id].previous_sibling_id();
112 while let Some(sib_id) = sibling {
113 if selector_group_matches(
114 &content_group,
115 &html_node_tree[sib_id],
116 &node_data[sib_id],
117 expected_path_ending.clone(),
118 is_last_content_group,
119 ) {
120 found_match = true;
121 current_node = Some(sib_id);
122 break;
123 }
124 sibling = node_hierarchy[sib_id].previous_sibling_id();
125 }
126 if !found_match {
127 return false;
128 }
129 next_match_requirement = reason;
131 continue;
132 }
133 }
134 Children => {
135 if current_selector_matches && !last_selector_matched {
138 return false;
140 }
141 }
142 }
143
144 last_selector_matched = current_selector_matches;
146 next_match_requirement = reason;
148
149 match reason {
151 Children | DirectChildren => {
152 let mut next = node_hierarchy[cur_node_id].parent_id();
154 while let Some(n) = next {
155 if !node_data[n].is_anonymous() {
156 break;
157 }
158 next = node_hierarchy[n].parent_id();
159 }
160 current_node = next;
161 }
162 AdjacentSibling | GeneralSibling => {
163 let mut next = node_hierarchy[cur_node_id].previous_sibling_id();
165 while let Some(n) = next {
166 if !node_data[n].is_anonymous() {
167 break;
168 }
169 next = node_hierarchy[n].previous_sibling_id();
170 }
171 current_node = next;
172 }
173 }
174 }
175
176 last_selector_matched
177}
178
179pub struct CssGroupIterator<'a> {
192 pub css_path: &'a [CssPathSelector],
193 pub current_idx: usize,
194 pub last_reason: CssGroupSplitReason,
195}
196
197#[derive(Debug, Copy, Clone, PartialEq, Eq)]
198pub enum CssGroupSplitReason {
199 Children,
201 DirectChildren,
203 AdjacentSibling,
205 GeneralSibling,
207}
208
209impl<'a> CssGroupIterator<'a> {
210 pub fn new(css_path: &'a [CssPathSelector]) -> Self {
211 let initial_len = css_path.len();
212 Self {
213 css_path,
214 current_idx: initial_len,
215 last_reason: CssGroupSplitReason::Children,
216 }
217 }
218 pub fn is_last_content_group(&self) -> bool {
219 self.current_idx.saturating_add(1) == self.css_path.len().saturating_sub(1)
220 }
221}
222
223impl<'a> Iterator for CssGroupIterator<'a> {
224 type Item = (CssContentGroup<'a>, CssGroupSplitReason);
225
226 fn next(&mut self) -> Option<(CssContentGroup<'a>, CssGroupSplitReason)> {
227 use self::CssPathSelector::*;
228
229 let mut new_idx = self.current_idx;
230
231 if new_idx == 0 {
232 return None;
233 }
234
235 let mut current_path = Vec::new();
236
237 while new_idx != 0 {
238 match self.css_path.get(new_idx - 1)? {
239 Children => {
240 self.last_reason = CssGroupSplitReason::Children;
241 break;
242 }
243 DirectChildren => {
244 self.last_reason = CssGroupSplitReason::DirectChildren;
245 break;
246 }
247 AdjacentSibling => {
248 self.last_reason = CssGroupSplitReason::AdjacentSibling;
249 break;
250 }
251 GeneralSibling => {
252 self.last_reason = CssGroupSplitReason::GeneralSibling;
253 break;
254 }
255 other => current_path.push(other),
256 }
257 new_idx -= 1;
258 }
259
260 #[cfg(test)]
263 current_path.reverse();
264
265 if new_idx == 0 {
266 if current_path.is_empty() {
267 None
268 } else {
269 self.current_idx = 0;
271 Some((current_path, self.last_reason))
272 }
273 } else {
274 self.current_idx = new_idx - 1;
276 Some((current_path, self.last_reason))
277 }
278 }
279}
280
281pub fn construct_html_cascade_tree(
282 node_hierarchy: &NodeHierarchyRef,
283 node_depths_sorted: &[(usize, NodeId)],
284 node_data: &NodeDataContainerRef<NodeData>,
285) -> NodeDataContainer<CascadeInfo> {
286 let mut nodes = (0..node_hierarchy.len())
287 .map(|_| CascadeInfo {
288 index_in_parent: 0,
289 is_last_child: false,
290 })
291 .collect::<Vec<_>>();
292
293 for (_depth, parent_id) in node_depths_sorted {
294 let element_index_in_parent = parent_id
300 .preceding_siblings(node_hierarchy)
301 .filter(|sib_id| !node_data[*sib_id].is_text_node())
302 .count();
303
304 let parent_html_matcher = CascadeInfo {
305 index_in_parent: (element_index_in_parent - 1) as u32,
306 is_last_child: {
308 let mut is_last_element = true;
309 let mut next = node_hierarchy[*parent_id].next_sibling;
310 while let Some(sib_id) = next {
311 if !node_data[sib_id].is_text_node() {
312 is_last_element = false;
313 break;
314 }
315 next = node_hierarchy[sib_id].next_sibling;
316 }
317 is_last_element
318 },
319 };
320
321 nodes[parent_id.index()] = parent_html_matcher;
322
323 let mut element_idx: u32 = 0;
325 for child_id in parent_id.children(node_hierarchy) {
326 let is_text = node_data[child_id].is_text_node();
327
328 let is_last_element_child = if is_text {
330 false
331 } else {
332 let mut is_last = true;
333 let mut next = node_hierarchy[child_id].next_sibling;
334 while let Some(sib_id) = next {
335 if !node_data[sib_id].is_text_node() {
336 is_last = false;
337 break;
338 }
339 next = node_hierarchy[sib_id].next_sibling;
340 }
341 is_last
342 };
343
344 let child_html_matcher = CascadeInfo {
345 index_in_parent: element_idx,
346 is_last_child: is_last_element_child,
347 };
348
349 nodes[child_id.index()] = child_html_matcher;
350
351 if !is_text {
352 element_idx += 1;
353 }
354 }
355 }
356
357 NodeDataContainer { internal: nodes }
358}
359
360#[inline]
362pub fn rule_ends_with(path: &CssPath, target: Option<CssPathPseudoSelector>) -> bool {
363 fn is_interactive_pseudo(p: &CssPathPseudoSelector) -> bool {
366 matches!(
367 p,
368 CssPathPseudoSelector::Hover
369 | CssPathPseudoSelector::Active
370 | CssPathPseudoSelector::Focus
371 | CssPathPseudoSelector::Backdrop
372 | CssPathPseudoSelector::Dragging
373 | CssPathPseudoSelector::DragOver
374 )
375 }
376
377 match target {
378 None => match path.selectors.as_ref().last() {
379 None => false,
380 Some(q) => match q {
381 CssPathSelector::PseudoSelector(p) => !is_interactive_pseudo(p),
384 _ => true,
385 },
386 },
387 Some(s) => match path.selectors.as_ref().last() {
388 None => false,
389 Some(q) => match q {
390 CssPathSelector::PseudoSelector(q) => *q == s,
391 _ => false,
392 },
393 },
394 }
395}
396
397pub fn selector_group_matches(
402 selectors: &[&CssPathSelector],
403 html_node: &CascadeInfo,
404 node_data: &NodeData,
405 expected_path_ending: Option<CssPathPseudoSelector>,
406 is_last_content_group: bool,
407) -> bool {
408 selectors.iter().all(|selector| {
409 match_single_selector(
410 selector,
411 html_node,
412 node_data,
413 expected_path_ending.clone(),
414 is_last_content_group,
415 )
416 })
417}
418
419fn match_single_selector(
421 selector: &CssPathSelector,
422 html_node: &CascadeInfo,
423 node_data: &NodeData,
424 expected_path_ending: Option<CssPathPseudoSelector>,
425 is_last_content_group: bool,
426) -> bool {
427 use self::CssPathSelector::*;
428
429 match selector {
430 Global => true,
431 Type(t) => node_data.get_node_type().get_path() == *t,
432 Class(c) => match_class(node_data, c.as_str()),
433 Id(id) => match_id(node_data, id.as_str()),
434 PseudoSelector(p) => {
435 match_pseudo_selector(p, html_node, expected_path_ending, is_last_content_group)
436 }
437 DirectChildren | Children | AdjacentSibling | GeneralSibling => false,
438 }
439}
440
441fn match_class(node_data: &NodeData, class_name: &str) -> bool {
443 node_data
444 .get_ids_and_classes()
445 .iter()
446 .filter_map(|i| i.as_class())
447 .any(|class| class == class_name)
448}
449
450fn match_id(node_data: &NodeData, id_name: &str) -> bool {
452 node_data
453 .get_ids_and_classes()
454 .iter()
455 .filter_map(|i| i.as_id())
456 .any(|html_id| html_id == id_name)
457}
458
459fn match_pseudo_selector(
461 pseudo: &CssPathPseudoSelector,
462 html_node: &CascadeInfo,
463 expected_path_ending: Option<CssPathPseudoSelector>,
464 is_last_content_group: bool,
465) -> bool {
466 match pseudo {
467 CssPathPseudoSelector::First => match_first_child(html_node),
468 CssPathPseudoSelector::Last => match_last_child(html_node),
469 CssPathPseudoSelector::NthChild(pattern) => match_nth_child(html_node, pattern),
470 CssPathPseudoSelector::Hover => match_interactive_pseudo(
471 CssPathPseudoSelector::Hover,
472 expected_path_ending,
473 is_last_content_group,
474 ),
475 CssPathPseudoSelector::Active => match_interactive_pseudo(
476 CssPathPseudoSelector::Active,
477 expected_path_ending,
478 is_last_content_group,
479 ),
480 CssPathPseudoSelector::Focus => match_interactive_pseudo(
481 CssPathPseudoSelector::Focus,
482 expected_path_ending,
483 is_last_content_group,
484 ),
485 CssPathPseudoSelector::Backdrop => match_interactive_pseudo(
486 CssPathPseudoSelector::Backdrop,
487 expected_path_ending,
488 is_last_content_group,
489 ),
490 CssPathPseudoSelector::Dragging => match_interactive_pseudo(
491 CssPathPseudoSelector::Dragging,
492 expected_path_ending,
493 is_last_content_group,
494 ),
495 CssPathPseudoSelector::DragOver => match_interactive_pseudo(
496 CssPathPseudoSelector::DragOver,
497 expected_path_ending,
498 is_last_content_group,
499 ),
500 CssPathPseudoSelector::Lang(lang) => {
501 if let Some(ref expected) = expected_path_ending {
504 if let CssPathPseudoSelector::Lang(expected_lang) = expected {
505 return lang == expected_lang;
506 }
507 }
508 false
510 }
511 }
512}
513
514fn match_first_child(html_node: &CascadeInfo) -> bool {
516 html_node.index_in_parent == 0
517}
518
519fn match_last_child(html_node: &CascadeInfo) -> bool {
521 html_node.is_last_child
522}
523
524fn match_nth_child(html_node: &CascadeInfo, pattern: &CssNthChildSelector) -> bool {
526 use azul_css::css::CssNthChildPattern;
527
528 let index = html_node.index_in_parent + 1;
530
531 match pattern {
532 Number(n) => index == *n,
533 Even => index % 2 == 0,
534 Odd => index % 2 == 1,
535 Pattern(CssNthChildPattern {
536 pattern_repeat,
537 offset,
538 }) => {
539 if *pattern_repeat == 0 {
540 index == *offset
541 } else {
542 index >= *offset && ((index - offset) % pattern_repeat == 0)
543 }
544 }
545 }
546}
547
548fn match_interactive_pseudo(
551 pseudo: CssPathPseudoSelector,
552 expected_path_ending: Option<CssPathPseudoSelector>,
553 is_last_content_group: bool,
554) -> bool {
555 is_last_content_group && expected_path_ending == Some(pseudo)
556}