1use alloc::vec::Vec;
8
9use azul_css::css::{
10 AttributeMatchOp, CssAttributeSelector, CssContentGroup, CssNthChildSelector,
11 CssNthChildSelector::*, CssPath, CssPathPseudoSelector, CssPathSelector,
12};
13
14use crate::{
15 dom::NodeData,
16 id::{NodeDataContainer, NodeDataContainerRef, NodeHierarchyRef, NodeId},
17 styled_dom::NodeHierarchyItem,
18};
19
20#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
22#[repr(C)]
23pub struct CascadeInfo {
24 pub index_in_parent: u32,
25 pub is_last_child: bool,
26}
27
28impl_option!(
29 CascadeInfo,
30 OptionCascadeInfo,
31 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
32);
33
34impl_vec!(CascadeInfo, CascadeInfoVec, CascadeInfoVecDestructor, CascadeInfoVecDestructorType, CascadeInfoVecSlice, OptionCascadeInfo);
35impl_vec_mut!(CascadeInfo, CascadeInfoVec);
36impl_vec_debug!(CascadeInfo, CascadeInfoVec);
37impl_vec_partialord!(CascadeInfo, CascadeInfoVec);
38impl_vec_clone!(CascadeInfo, CascadeInfoVec, CascadeInfoVecDestructor);
39impl_vec_partialeq!(CascadeInfo, CascadeInfoVec);
40
41impl CascadeInfoVec {
42 pub fn as_container<'a>(&'a self) -> NodeDataContainerRef<'a, CascadeInfo> {
43 NodeDataContainerRef {
44 internal: self.as_ref(),
45 }
46 }
47}
48
49pub fn matches_html_element(
52 css_path: &CssPath,
53 node_id: NodeId,
54 node_hierarchy: &NodeDataContainerRef<NodeHierarchyItem>,
55 node_data: &NodeDataContainerRef<NodeData>,
56 html_node_tree: &NodeDataContainerRef<CascadeInfo>,
57 expected_path_ending: Option<CssPathPseudoSelector>,
58) -> bool {
59 use self::CssGroupSplitReason::*;
60
61 if css_path.selectors.is_empty() {
62 return false;
63 }
64
65 if node_data[node_id].is_anonymous() {
68 return false;
69 }
70
71 let groups: Vec<(CssContentGroup<'_>, CssGroupSplitReason)> =
73 CssGroupIterator::new(css_path.selectors.as_ref()).collect();
74
75 if groups.is_empty() {
76 return false;
77 }
78
79 let (ref first_group, first_reason) = groups[0];
81 let is_last_content_group = groups.len() == 1;
82 if !selector_group_matches(
83 first_group,
84 &html_node_tree[node_id],
85 &node_data[node_id],
86 &expected_path_ending,
87 is_last_content_group,
88 ) {
89 return false;
90 }
91
92 let mut current_node = node_id;
95
96 for (group_idx, (content_group, _reason)) in groups.iter().enumerate().skip(1) {
97 let combinator = groups[group_idx - 1].1;
99 let is_last = group_idx == groups.len() - 1;
100
101 match combinator {
102 DirectChildren => {
103 let parent = find_non_anonymous_parent(current_node, node_hierarchy, node_data);
105 match parent {
106 Some(p) if selector_group_matches(
107 content_group, &html_node_tree[p], &node_data[p],
108 &expected_path_ending, is_last,
109 ) => { current_node = p; }
110 _ => return false,
111 }
112 }
113 Children => {
114 let mut ancestor = find_non_anonymous_parent(current_node, node_hierarchy, node_data);
116 let mut found = false;
117 while let Some(anc) = ancestor {
118 if selector_group_matches(
119 content_group, &html_node_tree[anc], &node_data[anc],
120 &expected_path_ending, is_last,
121 ) {
122 current_node = anc;
123 found = true;
124 break;
125 }
126 ancestor = find_non_anonymous_parent(anc, node_hierarchy, node_data);
127 }
128 if !found {
129 return false;
130 }
131 }
132 AdjacentSibling => {
133 let sibling = find_non_anonymous_prev_sibling(current_node, node_hierarchy, node_data);
135 match sibling {
136 Some(s) if selector_group_matches(
137 content_group, &html_node_tree[s], &node_data[s],
138 &expected_path_ending, is_last,
139 ) => { current_node = s; }
140 _ => return false,
141 }
142 }
143 GeneralSibling => {
144 let mut sibling = find_non_anonymous_prev_sibling(current_node, node_hierarchy, node_data);
146 let mut found = false;
147 while let Some(sib) = sibling {
148 if selector_group_matches(
149 content_group, &html_node_tree[sib], &node_data[sib],
150 &expected_path_ending, is_last,
151 ) {
152 current_node = sib;
153 found = true;
154 break;
155 }
156 sibling = find_non_anonymous_prev_sibling(sib, node_hierarchy, node_data);
157 }
158 if !found {
159 return false;
160 }
161 }
162 }
163 }
164
165 true
166}
167
168fn find_non_anonymous_parent(
170 node_id: NodeId,
171 node_hierarchy: &NodeDataContainerRef<NodeHierarchyItem>,
172 node_data: &NodeDataContainerRef<NodeData>,
173) -> Option<NodeId> {
174 let mut next = node_hierarchy[node_id].parent_id();
175 while let Some(n) = next {
176 if !node_data[n].is_anonymous() {
177 return Some(n);
178 }
179 next = node_hierarchy[n].parent_id();
180 }
181 None
182}
183
184fn find_non_anonymous_prev_sibling(
186 node_id: NodeId,
187 node_hierarchy: &NodeDataContainerRef<NodeHierarchyItem>,
188 node_data: &NodeDataContainerRef<NodeData>,
189) -> Option<NodeId> {
190 let mut next = node_hierarchy[node_id].previous_sibling_id();
191 while let Some(n) = next {
192 if !node_data[n].is_anonymous() {
193 return Some(n);
194 }
195 next = node_hierarchy[n].previous_sibling_id();
196 }
197 None
198}
199
200pub struct CssGroupIterator<'a> {
213 pub css_path: &'a [CssPathSelector],
214 current_idx: usize,
215 last_reason: CssGroupSplitReason,
216}
217
218#[derive(Debug, Copy, Clone, PartialEq, Eq)]
219pub enum CssGroupSplitReason {
220 Children,
222 DirectChildren,
224 AdjacentSibling,
226 GeneralSibling,
228}
229
230impl<'a> CssGroupIterator<'a> {
231 pub fn new(css_path: &'a [CssPathSelector]) -> Self {
232 let initial_len = css_path.len();
233 Self {
234 css_path,
235 current_idx: initial_len,
236 last_reason: CssGroupSplitReason::Children,
237 }
238 }
239}
240
241impl<'a> Iterator for CssGroupIterator<'a> {
242 type Item = (CssContentGroup<'a>, CssGroupSplitReason);
243
244 fn next(&mut self) -> Option<(CssContentGroup<'a>, CssGroupSplitReason)> {
245 use self::CssPathSelector::*;
246
247 let mut new_idx = self.current_idx;
248
249 if new_idx == 0 {
250 return None;
251 }
252
253 let mut current_path = Vec::new();
254
255 while new_idx != 0 {
256 match self.css_path.get(new_idx - 1)? {
257 Children => {
258 self.last_reason = CssGroupSplitReason::Children;
259 break;
260 }
261 DirectChildren => {
262 self.last_reason = CssGroupSplitReason::DirectChildren;
263 break;
264 }
265 AdjacentSibling => {
266 self.last_reason = CssGroupSplitReason::AdjacentSibling;
267 break;
268 }
269 GeneralSibling => {
270 self.last_reason = CssGroupSplitReason::GeneralSibling;
271 break;
272 }
273 other => current_path.push(other),
274 }
275 new_idx -= 1;
276 }
277
278 #[cfg(test)]
281 current_path.reverse();
282
283 if new_idx == 0 {
284 if current_path.is_empty() {
285 None
286 } else {
287 self.current_idx = 0;
289 Some((current_path, self.last_reason))
290 }
291 } else {
292 self.current_idx = new_idx - 1;
294 Some((current_path, self.last_reason))
295 }
296 }
297}
298
299pub fn construct_html_cascade_tree(
300 node_hierarchy: &NodeHierarchyRef,
301 node_depths_sorted: &[(usize, NodeId)],
302 node_data: &NodeDataContainerRef<NodeData>,
303) -> NodeDataContainer<CascadeInfo> {
304 let mut nodes = (0..node_hierarchy.len())
305 .map(|_| CascadeInfo {
306 index_in_parent: 0,
307 is_last_child: false,
308 })
309 .collect::<Vec<_>>();
310
311 for (_depth, parent_id) in node_depths_sorted {
312 let element_index_in_parent = parent_id
318 .preceding_siblings(node_hierarchy)
319 .filter(|sib_id| !node_data[*sib_id].is_text_node())
320 .count();
321
322 let parent_html_matcher = CascadeInfo {
323 index_in_parent: (element_index_in_parent.saturating_sub(1)) as u32,
324 is_last_child: {
326 let mut is_last_element = true;
327 let mut next = node_hierarchy[*parent_id].next_sibling;
328 while let Some(sib_id) = next {
329 if !node_data[sib_id].is_text_node() {
330 is_last_element = false;
331 break;
332 }
333 next = node_hierarchy[sib_id].next_sibling;
334 }
335 is_last_element
336 },
337 };
338
339 nodes[parent_id.index()] = parent_html_matcher;
340
341 let mut element_idx: u32 = 0;
343 for child_id in parent_id.children(node_hierarchy) {
344 let is_text = node_data[child_id].is_text_node();
345
346 let is_last_element_child = if is_text {
348 false
349 } else {
350 let mut is_last = true;
351 let mut next = node_hierarchy[child_id].next_sibling;
352 while let Some(sib_id) = next {
353 if !node_data[sib_id].is_text_node() {
354 is_last = false;
355 break;
356 }
357 next = node_hierarchy[sib_id].next_sibling;
358 }
359 is_last
360 };
361
362 let child_html_matcher = CascadeInfo {
363 index_in_parent: element_idx,
364 is_last_child: is_last_element_child,
365 };
366
367 nodes[child_id.index()] = child_html_matcher;
368
369 if !is_text {
370 element_idx += 1;
371 }
372 }
373 }
374
375 NodeDataContainer { internal: nodes }
376}
377
378#[inline]
384pub fn rule_ends_with(path: &CssPath, target: Option<CssPathPseudoSelector>) -> bool {
385 fn is_interactive_pseudo(p: &CssPathPseudoSelector) -> bool {
388 matches!(
389 p,
390 CssPathPseudoSelector::Hover
391 | CssPathPseudoSelector::Active
392 | CssPathPseudoSelector::Focus
393 | CssPathPseudoSelector::Backdrop
394 | CssPathPseudoSelector::Dragging
395 | CssPathPseudoSelector::DragOver
396 )
397 }
398
399 match target {
400 None => match path.selectors.as_ref().last() {
401 None => false,
402 Some(q) => match q {
403 CssPathSelector::PseudoSelector(p) => !is_interactive_pseudo(p),
406 _ => true,
407 },
408 },
409 Some(s) => match path.selectors.as_ref().last() {
410 None => false,
411 Some(q) => match q {
412 CssPathSelector::PseudoSelector(q) => *q == s,
413 _ => false,
414 },
415 },
416 }
417}
418
419fn selector_group_matches(
424 selectors: &[&CssPathSelector],
425 html_node: &CascadeInfo,
426 node_data: &NodeData,
427 expected_path_ending: &Option<CssPathPseudoSelector>,
428 is_last_content_group: bool,
429) -> bool {
430 selectors.iter().all(|selector| {
431 match_single_selector(
432 selector,
433 html_node,
434 node_data,
435 expected_path_ending,
436 is_last_content_group,
437 )
438 })
439}
440
441fn match_single_selector(
443 selector: &CssPathSelector,
444 html_node: &CascadeInfo,
445 node_data: &NodeData,
446 expected_path_ending: &Option<CssPathPseudoSelector>,
447 is_last_content_group: bool,
448) -> bool {
449 use self::CssPathSelector::*;
450
451 match selector {
452 Global => true,
453 Type(t) => node_data.get_node_type().get_path() == *t,
454 Class(c) => node_data.has_class(c.as_str()),
455 Id(id) => node_data.has_id(id.as_str()),
456 PseudoSelector(p) => {
457 match_pseudo_selector(p, html_node, expected_path_ending, is_last_content_group)
458 }
459 Attribute(a) => match_attribute_selector(a, node_data),
460 DirectChildren | Children | AdjacentSibling | GeneralSibling => false,
461 }
462}
463
464fn match_attribute_selector(sel: &CssAttributeSelector, node_data: &NodeData) -> bool {
471 let name = sel.name.as_str();
472 let target = sel.value.as_ref().map(|v| v.as_str());
473
474 let check = |actual: &str| -> bool {
475 match (&sel.op, target) {
476 (AttributeMatchOp::Exists, _) => true,
477 (AttributeMatchOp::Eq, Some(t)) => actual == t,
478 (AttributeMatchOp::Includes, Some(t)) => {
479 if t.is_empty() || t.contains(char::is_whitespace) {
480 return false;
481 }
482 actual.split_whitespace().any(|word| word == t)
483 }
484 (AttributeMatchOp::DashMatch, Some(t)) => {
485 actual == t || actual.starts_with(&alloc::format!("{}-", t))
486 }
487 (AttributeMatchOp::Prefix, Some(t)) => !t.is_empty() && actual.starts_with(t),
488 (AttributeMatchOp::Suffix, Some(t)) => !t.is_empty() && actual.ends_with(t),
489 (AttributeMatchOp::Substring, Some(t)) => !t.is_empty() && actual.contains(t),
490 (_, None) => false,
492 }
493 };
494
495 for attr in node_data.attributes().iter() {
496 if attr.name() != name {
497 continue;
498 }
499 if check(attr.value().as_str()) {
500 return true;
501 }
502 }
503
504 false
505}
506
507fn match_pseudo_selector(
509 pseudo: &CssPathPseudoSelector,
510 html_node: &CascadeInfo,
511 expected_path_ending: &Option<CssPathPseudoSelector>,
512 is_last_content_group: bool,
513) -> bool {
514 match pseudo {
515 CssPathPseudoSelector::First => match_first_child(html_node),
516 CssPathPseudoSelector::Last => match_last_child(html_node),
517 CssPathPseudoSelector::NthChild(pattern) => match_nth_child(html_node, pattern),
518 CssPathPseudoSelector::Hover => match_interactive_pseudo(
519 &CssPathPseudoSelector::Hover,
520 expected_path_ending,
521 is_last_content_group,
522 ),
523 CssPathPseudoSelector::Active => match_interactive_pseudo(
524 &CssPathPseudoSelector::Active,
525 expected_path_ending,
526 is_last_content_group,
527 ),
528 CssPathPseudoSelector::Focus => match_interactive_pseudo(
529 &CssPathPseudoSelector::Focus,
530 expected_path_ending,
531 is_last_content_group,
532 ),
533 CssPathPseudoSelector::Backdrop => match_interactive_pseudo(
534 &CssPathPseudoSelector::Backdrop,
535 expected_path_ending,
536 is_last_content_group,
537 ),
538 CssPathPseudoSelector::Dragging => match_interactive_pseudo(
539 &CssPathPseudoSelector::Dragging,
540 expected_path_ending,
541 is_last_content_group,
542 ),
543 CssPathPseudoSelector::DragOver => match_interactive_pseudo(
544 &CssPathPseudoSelector::DragOver,
545 expected_path_ending,
546 is_last_content_group,
547 ),
548 CssPathPseudoSelector::Lang(lang) => {
549 if let Some(expected) = expected_path_ending {
552 if let CssPathPseudoSelector::Lang(expected_lang) = expected {
553 return lang == expected_lang;
554 }
555 }
556 false
558 }
559 }
560}
561
562fn match_first_child(html_node: &CascadeInfo) -> bool {
564 html_node.index_in_parent == 0
565}
566
567fn match_last_child(html_node: &CascadeInfo) -> bool {
569 html_node.is_last_child
570}
571
572fn match_nth_child(html_node: &CascadeInfo, pattern: &CssNthChildSelector) -> bool {
574 use azul_css::css::CssNthChildPattern;
575
576 let index = html_node.index_in_parent + 1;
578
579 match pattern {
580 Number(n) => index == *n,
581 Even => index % 2 == 0,
582 Odd => index % 2 == 1,
583 Pattern(CssNthChildPattern {
584 pattern_repeat,
585 offset,
586 }) => {
587 if *pattern_repeat == 0 {
588 index == *offset
589 } else {
590 index >= *offset && ((index - offset) % pattern_repeat == 0)
591 }
592 }
593 }
594}
595
596fn match_interactive_pseudo(
599 pseudo: &CssPathPseudoSelector,
600 expected_path_ending: &Option<CssPathPseudoSelector>,
601 is_last_content_group: bool,
602) -> bool {
603 is_last_content_group && expected_path_ending.as_ref() == Some(pseudo)
604}