web engine - experimental web browser
1//! CSS selector matching engine.
2//!
3//! Matches selectors against DOM elements and computes specificity.
4
5use we_css::parser::{
6 AttributeOp, AttributeSelector, Combinator, CompoundSelector, Declaration, Rule, Selector,
7 SelectorComponent, SelectorList, SimpleSelector, StyleRule, Stylesheet,
8};
9use we_dom::{Document, NodeData, NodeId};
10
11/// Selector specificity as (a, b, c):
12/// a = number of ID selectors
13/// b = number of class selectors, attribute selectors, pseudo-classes
14/// c = number of type selectors, pseudo-elements
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct Specificity {
17 pub a: u32,
18 pub b: u32,
19 pub c: u32,
20}
21
22impl Specificity {
23 pub fn new(a: u32, b: u32, c: u32) -> Self {
24 Self { a, b, c }
25 }
26}
27
28impl PartialOrd for Specificity {
29 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
30 Some(self.cmp(other))
31 }
32}
33
34impl Ord for Specificity {
35 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
36 self.a
37 .cmp(&other.a)
38 .then(self.b.cmp(&other.b))
39 .then(self.c.cmp(&other.c))
40 }
41}
42
43/// A matched rule: a style rule paired with the specificity of the selector
44/// that matched, plus source order for tie-breaking.
45#[derive(Debug, Clone)]
46pub struct MatchedRule<'a> {
47 pub rule: &'a StyleRule,
48 pub specificity: Specificity,
49 pub source_order: usize,
50}
51
52/// Check if a selector list matches an element. Returns true if any selector
53/// in the comma-separated list matches.
54pub fn matches_selector_list(doc: &Document, node: NodeId, selector_list: &SelectorList) -> bool {
55 selector_list
56 .selectors
57 .iter()
58 .any(|sel| matches_selector(doc, node, sel))
59}
60
61/// Check if a complex selector matches an element.
62///
63/// A complex selector is a chain of compound selectors joined by combinators,
64/// stored left-to-right: `[Compound, Combinator, Compound, ...]`.
65/// We match right-to-left: the rightmost compound (key selector) must match
66/// the target element, then we walk the tree based on combinators.
67pub fn matches_selector(doc: &Document, node: NodeId, selector: &Selector) -> bool {
68 // Extract compounds and combinators from the components list.
69 // Components alternate: Compound, Combinator, Compound, Combinator, ...
70 let compounds: Vec<&CompoundSelector> = selector
71 .components
72 .iter()
73 .filter_map(|c| match c {
74 SelectorComponent::Compound(cs) => Some(cs),
75 _ => None,
76 })
77 .collect();
78
79 let combinators: Vec<Combinator> = selector
80 .components
81 .iter()
82 .filter_map(|c| match c {
83 SelectorComponent::Combinator(comb) => Some(*comb),
84 _ => None,
85 })
86 .collect();
87
88 if compounds.is_empty() {
89 return false;
90 }
91
92 // The key selector is the last compound.
93 if !matches_compound(doc, node, compounds[compounds.len() - 1]) {
94 return false;
95 }
96
97 // Walk right-to-left through the remaining compounds.
98 // compounds[i] is connected to compounds[i+1] by combinators[i].
99 let mut current = node;
100 for i in (0..compounds.len() - 1).rev() {
101 let compound = compounds[i];
102 let combinator = combinators[i];
103
104 match combinator {
105 Combinator::Descendant => {
106 // Walk up ancestor chain until we find a match or run out.
107 let mut found = false;
108 let mut ancestor = doc.parent(current);
109 while let Some(anc) = ancestor {
110 if matches_compound(doc, anc, compound) {
111 current = anc;
112 found = true;
113 break;
114 }
115 ancestor = doc.parent(anc);
116 }
117 if !found {
118 return false;
119 }
120 }
121 Combinator::Child => {
122 // Direct parent must match.
123 if let Some(parent) = doc.parent(current) {
124 if matches_compound(doc, parent, compound) {
125 current = parent;
126 } else {
127 return false;
128 }
129 } else {
130 return false;
131 }
132 }
133 Combinator::AdjacentSibling => {
134 // Previous element sibling must match.
135 if let Some(prev) = prev_element_sibling(doc, current) {
136 if matches_compound(doc, prev, compound) {
137 current = prev;
138 } else {
139 return false;
140 }
141 } else {
142 return false;
143 }
144 }
145 Combinator::GeneralSibling => {
146 // Any previous element sibling must match.
147 let mut found = false;
148 let mut prev = prev_element_sibling(doc, current);
149 while let Some(sib) = prev {
150 if matches_compound(doc, sib, compound) {
151 current = sib;
152 found = true;
153 break;
154 }
155 prev = prev_element_sibling(doc, sib);
156 }
157 if !found {
158 return false;
159 }
160 }
161 }
162 }
163
164 true
165}
166
167/// Check if a compound selector matches an element. All simple selectors
168/// in the compound must match.
169pub fn matches_compound(doc: &Document, node: NodeId, compound: &CompoundSelector) -> bool {
170 // Must be an element node.
171 if !matches!(doc.node_data(node), NodeData::Element { .. }) {
172 return false;
173 }
174 compound.simple.iter().all(|s| matches_simple(doc, node, s))
175}
176
177/// Check if a simple selector matches an element.
178pub fn matches_simple(doc: &Document, node: NodeId, selector: &SimpleSelector) -> bool {
179 match selector {
180 SimpleSelector::Universal => {
181 matches!(doc.node_data(node), NodeData::Element { .. })
182 }
183 SimpleSelector::Type(name) => doc
184 .tag_name(node)
185 .map(|tag| tag.eq_ignore_ascii_case(name))
186 .unwrap_or(false),
187 SimpleSelector::Class(class_name) => {
188 if let Some(class_attr) = doc.get_attribute(node, "class") {
189 class_attr.split_ascii_whitespace().any(|c| c == class_name)
190 } else {
191 false
192 }
193 }
194 SimpleSelector::Id(id) => doc
195 .get_attribute(node, "id")
196 .map(|val| val == id)
197 .unwrap_or(false),
198 SimpleSelector::Attribute(attr_sel) => matches_attribute(doc, node, attr_sel),
199 SimpleSelector::PseudoClass(_name) => {
200 // Pseudo-classes are not yet implemented; always return false.
201 false
202 }
203 }
204}
205
206/// Check if an attribute selector matches an element.
207fn matches_attribute(doc: &Document, node: NodeId, sel: &AttributeSelector) -> bool {
208 let attr_value = doc.get_attribute(node, &sel.name);
209
210 match (&sel.op, &sel.value) {
211 // [attr] — attribute presence
212 (None, _) => attr_value.is_some(),
213
214 (Some(op), Some(expected)) => {
215 let actual = match attr_value {
216 Some(v) => v,
217 None => return false,
218 };
219
220 match op {
221 // [attr=val]
222 AttributeOp::Exact => actual == expected,
223 // [attr~=val] — whitespace-separated list contains val
224 AttributeOp::Includes => actual.split_ascii_whitespace().any(|w| w == expected),
225 // [attr|=val] — equals val or starts with val-
226 AttributeOp::DashMatch => {
227 actual == expected
228 || (actual.starts_with(expected.as_str())
229 && actual.as_bytes().get(expected.len()) == Some(&b'-'))
230 }
231 // [attr^=val]
232 AttributeOp::Prefix => actual.starts_with(expected.as_str()),
233 // [attr$=val]
234 AttributeOp::Suffix => actual.ends_with(expected.as_str()),
235 // [attr*=val]
236 AttributeOp::Substring => actual.contains(expected.as_str()),
237 }
238 }
239
240 // op without value — shouldn't happen from parser, but treat as no match
241 (Some(_), None) => false,
242 }
243}
244
245/// Calculate specificity of a selector.
246pub fn specificity(selector: &Selector) -> Specificity {
247 let mut a = 0u32;
248 let mut b = 0u32;
249 let mut c = 0u32;
250
251 for component in &selector.components {
252 if let SelectorComponent::Compound(compound) = component {
253 for simple in &compound.simple {
254 match simple {
255 SimpleSelector::Id(_) => a += 1,
256 SimpleSelector::Class(_)
257 | SimpleSelector::Attribute(_)
258 | SimpleSelector::PseudoClass(_) => b += 1,
259 SimpleSelector::Type(_) => c += 1,
260 SimpleSelector::Universal => {}
261 }
262 }
263 }
264 }
265
266 Specificity::new(a, b, c)
267}
268
269/// Collect all matching rules from a stylesheet for a given element,
270/// sorted by specificity and source order (ascending — highest priority last).
271pub fn collect_matching_rules<'a>(
272 doc: &Document,
273 node: NodeId,
274 stylesheet: &'a Stylesheet,
275) -> Vec<MatchedRule<'a>> {
276 let mut matched = Vec::new();
277 collect_from_rules(doc, node, &stylesheet.rules, &mut matched, &mut 0);
278 matched.sort_by(|a, b| {
279 a.specificity
280 .cmp(&b.specificity)
281 .then(a.source_order.cmp(&b.source_order))
282 });
283 matched
284}
285
286fn collect_from_rules<'a>(
287 doc: &Document,
288 node: NodeId,
289 rules: &'a [Rule],
290 matched: &mut Vec<MatchedRule<'a>>,
291 order: &mut usize,
292) {
293 for rule in rules {
294 match rule {
295 Rule::Style(style_rule) => {
296 let current_order = *order;
297 *order += 1;
298
299 // Check each selector in the selector list.
300 for selector in &style_rule.selectors.selectors {
301 if matches_selector(doc, node, selector) {
302 matched.push(MatchedRule {
303 rule: style_rule,
304 specificity: specificity(selector),
305 source_order: current_order,
306 });
307 // Only add the rule once even if multiple selectors match.
308 break;
309 }
310 }
311 }
312 Rule::Media(media_rule) => {
313 // For now, assume all media rules match (media query evaluation
314 // is out of scope for this issue).
315 collect_from_rules(doc, node, &media_rule.rules, matched, order);
316 }
317 Rule::Import(_) => {
318 // Imports are resolved at a higher level; skip here.
319 }
320 }
321 }
322}
323
324/// Find the previous element sibling (skipping text/comment nodes).
325fn prev_element_sibling(doc: &Document, node: NodeId) -> Option<NodeId> {
326 let mut prev = doc.prev_sibling(node);
327 while let Some(p) = prev {
328 if matches!(doc.node_data(p), NodeData::Element { .. }) {
329 return Some(p);
330 }
331 prev = doc.prev_sibling(p);
332 }
333 None
334}
335
336/// Get all declarations that apply to a node, in cascade order.
337/// Returns declarations from matching rules, sorted by specificity and source order.
338/// Important declarations are placed after normal declarations.
339pub fn get_declarations_for_node<'a>(
340 doc: &Document,
341 node: NodeId,
342 stylesheet: &'a Stylesheet,
343) -> Vec<&'a Declaration> {
344 let matched_rules = collect_matching_rules(doc, node, stylesheet);
345
346 let mut normal: Vec<(Specificity, usize, &'a Declaration)> = Vec::new();
347 let mut important: Vec<(Specificity, usize, &'a Declaration)> = Vec::new();
348
349 for matched in &matched_rules {
350 for decl in &matched.rule.declarations {
351 if decl.important {
352 important.push((matched.specificity, matched.source_order, decl));
353 } else {
354 normal.push((matched.specificity, matched.source_order, decl));
355 }
356 }
357 }
358
359 // Normal declarations are already sorted by specificity/order from collect_matching_rules.
360 // Important declarations override normal ones regardless of specificity,
361 // but among themselves they follow specificity order too.
362 let mut result: Vec<&'a Declaration> = Vec::new();
363 for (_, _, decl) in &normal {
364 result.push(decl);
365 }
366 for (_, _, decl) in &important {
367 result.push(decl);
368 }
369 result
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use we_css::parser::Parser;
376
377 #[allow(dead_code)]
378 struct TestDom {
379 doc: Document,
380 html: NodeId,
381 body: NodeId,
382 div_main: NodeId,
383 p_intro: NodeId,
384 text_hello: NodeId,
385 p2: NodeId,
386 span: NodeId,
387 a_link: NodeId,
388 }
389
390 fn make_test_dom() -> TestDom {
391 let mut doc = Document::new();
392 let root = doc.root();
393
394 let html = doc.create_element("html");
395 doc.append_child(root, html);
396
397 let body = doc.create_element("body");
398 doc.append_child(html, body);
399
400 let div = doc.create_element("div");
401 doc.set_attribute(div, "id", "main");
402 doc.set_attribute(div, "class", "container wide");
403 doc.append_child(body, div);
404
405 let p1 = doc.create_element("p");
406 doc.set_attribute(p1, "class", "intro");
407 doc.append_child(div, p1);
408
409 let text1 = doc.create_text("Hello");
410 doc.append_child(p1, text1);
411
412 let p2 = doc.create_element("p");
413 doc.append_child(div, p2);
414
415 let span = doc.create_element("span");
416 doc.set_attribute(span, "data-x", "foo");
417 doc.append_child(p2, span);
418
419 let a = doc.create_element("a");
420 doc.set_attribute(a, "href", "https://example.com");
421 doc.set_attribute(a, "class", "link");
422 doc.append_child(div, a);
423
424 TestDom {
425 doc,
426 html,
427 body,
428 div_main: div,
429 p_intro: p1,
430 text_hello: text1,
431 p2,
432 span,
433 a_link: a,
434 }
435 }
436
437 // -----------------------------------------------------------------------
438 // Type selector
439 // -----------------------------------------------------------------------
440
441 #[test]
442 fn type_selector_matches() {
443 let t = make_test_dom();
444 let sel = parse_first_selector("p {}");
445 assert!(matches_selector(&t.doc, t.p_intro, &sel));
446 assert!(matches_selector(&t.doc, t.p2, &sel));
447 assert!(!matches_selector(&t.doc, t.div_main, &sel));
448 }
449
450 #[test]
451 fn type_selector_case_insensitive() {
452 let t = make_test_dom();
453 let sel = parse_first_selector("P {}");
454 assert!(matches_selector(&t.doc, t.p_intro, &sel));
455 }
456
457 // -----------------------------------------------------------------------
458 // Universal selector
459 // -----------------------------------------------------------------------
460
461 #[test]
462 fn universal_selector_matches_any_element() {
463 let t = make_test_dom();
464 let sel = parse_first_selector("* {}");
465 assert!(matches_selector(&t.doc, t.html, &sel));
466 assert!(matches_selector(&t.doc, t.div_main, &sel));
467 assert!(matches_selector(&t.doc, t.p_intro, &sel));
468 assert!(!matches_selector(&t.doc, t.text_hello, &sel));
469 }
470
471 // -----------------------------------------------------------------------
472 // Class selector
473 // -----------------------------------------------------------------------
474
475 #[test]
476 fn class_selector_matches() {
477 let t = make_test_dom();
478 let sel = parse_first_selector(".intro {}");
479 assert!(matches_selector(&t.doc, t.p_intro, &sel));
480 assert!(!matches_selector(&t.doc, t.p2, &sel));
481 }
482
483 #[test]
484 fn class_selector_matches_multi_class() {
485 let t = make_test_dom();
486 let sel = parse_first_selector(".wide {}");
487 assert!(matches_selector(&t.doc, t.div_main, &sel));
488 }
489
490 // -----------------------------------------------------------------------
491 // ID selector
492 // -----------------------------------------------------------------------
493
494 #[test]
495 fn id_selector_matches() {
496 let t = make_test_dom();
497 let sel = parse_first_selector("#main {}");
498 assert!(matches_selector(&t.doc, t.div_main, &sel));
499 assert!(!matches_selector(&t.doc, t.p_intro, &sel));
500 }
501
502 // -----------------------------------------------------------------------
503 // Attribute selectors
504 // -----------------------------------------------------------------------
505
506 #[test]
507 fn attribute_presence() {
508 let t = make_test_dom();
509 let sel = parse_first_selector("[data-x] {}");
510 assert!(matches_selector(&t.doc, t.span, &sel));
511 assert!(!matches_selector(&t.doc, t.p_intro, &sel));
512 }
513
514 #[test]
515 fn attribute_exact_match() {
516 let t = make_test_dom();
517 let sel = parse_first_selector("[data-x=\"foo\"] {}");
518 assert!(matches_selector(&t.doc, t.span, &sel));
519
520 let sel2 = parse_first_selector("[data-x=\"bar\"] {}");
521 assert!(!matches_selector(&t.doc, t.span, &sel2));
522 }
523
524 #[test]
525 fn attribute_includes() {
526 let t = make_test_dom();
527 let sel = parse_first_selector("[class~=\"container\"] {}");
528 assert!(matches_selector(&t.doc, t.div_main, &sel));
529
530 let sel2 = parse_first_selector("[class~=\"wide\"] {}");
531 assert!(matches_selector(&t.doc, t.div_main, &sel2));
532
533 let sel3 = parse_first_selector("[class~=\"contain\"] {}");
534 assert!(!matches_selector(&t.doc, t.div_main, &sel3));
535 }
536
537 #[test]
538 fn attribute_prefix() {
539 let t = make_test_dom();
540 let sel = parse_first_selector("[href^=\"https\"] {}");
541 assert!(matches_selector(&t.doc, t.a_link, &sel));
542 }
543
544 #[test]
545 fn attribute_suffix() {
546 let t = make_test_dom();
547 let sel = parse_first_selector("[href$=\".com\"] {}");
548 assert!(matches_selector(&t.doc, t.a_link, &sel));
549 }
550
551 #[test]
552 fn attribute_substring() {
553 let t = make_test_dom();
554 let sel = parse_first_selector("[href*=\"example\"] {}");
555 assert!(matches_selector(&t.doc, t.a_link, &sel));
556 }
557
558 #[test]
559 fn attribute_dash_match() {
560 let mut doc = Document::new();
561 let root = doc.root();
562 let el = doc.create_element("div");
563 doc.set_attribute(el, "lang", "en-US");
564 doc.append_child(root, el);
565
566 let sel = parse_first_selector("[lang|=\"en\"] {}");
567 assert!(matches_selector(&doc, el, &sel));
568
569 let sel2 = parse_first_selector("[lang|=\"en-US\"] {}");
570 assert!(matches_selector(&doc, el, &sel2));
571
572 let sel3 = parse_first_selector("[lang|=\"fr\"] {}");
573 assert!(!matches_selector(&doc, el, &sel3));
574 }
575
576 // -----------------------------------------------------------------------
577 // Compound selectors
578 // -----------------------------------------------------------------------
579
580 #[test]
581 fn compound_selector() {
582 let t = make_test_dom();
583 let sel = parse_first_selector("p.intro {}");
584 assert!(matches_selector(&t.doc, t.p_intro, &sel));
585 assert!(!matches_selector(&t.doc, t.p2, &sel));
586 }
587
588 #[test]
589 fn compound_type_id_class() {
590 let t = make_test_dom();
591 let sel = parse_first_selector("div#main.container {}");
592 assert!(matches_selector(&t.doc, t.div_main, &sel));
593 }
594
595 // -----------------------------------------------------------------------
596 // Descendant combinator
597 // -----------------------------------------------------------------------
598
599 #[test]
600 fn descendant_combinator() {
601 let t = make_test_dom();
602 let sel = parse_first_selector("body p {}");
603 assert!(matches_selector(&t.doc, t.p_intro, &sel));
604 assert!(matches_selector(&t.doc, t.p2, &sel));
605 assert!(!matches_selector(&t.doc, t.div_main, &sel));
606 }
607
608 #[test]
609 fn descendant_combinator_deep() {
610 let t = make_test_dom();
611 let sel = parse_first_selector("html span {}");
612 assert!(matches_selector(&t.doc, t.span, &sel));
613 }
614
615 // -----------------------------------------------------------------------
616 // Child combinator
617 // -----------------------------------------------------------------------
618
619 #[test]
620 fn child_combinator() {
621 let t = make_test_dom();
622 let sel = parse_first_selector("div > p {}");
623 assert!(matches_selector(&t.doc, t.p_intro, &sel));
624 assert!(matches_selector(&t.doc, t.p2, &sel));
625
626 let sel2 = parse_first_selector("div > span {}");
627 assert!(!matches_selector(&t.doc, t.span, &sel2));
628 }
629
630 // -----------------------------------------------------------------------
631 // Adjacent sibling combinator
632 // -----------------------------------------------------------------------
633
634 #[test]
635 fn adjacent_sibling_combinator() {
636 let t = make_test_dom();
637 let sel = parse_first_selector("p.intro + p {}");
638 assert!(matches_selector(&t.doc, t.p2, &sel));
639
640 let sel2 = parse_first_selector("p + a {}");
641 assert!(matches_selector(&t.doc, t.a_link, &sel2));
642
643 // p.intro + a — not adjacent (p2 is between)
644 let sel3 = parse_first_selector("p.intro + a {}");
645 assert!(!matches_selector(&t.doc, t.a_link, &sel3));
646 }
647
648 // -----------------------------------------------------------------------
649 // General sibling combinator
650 // -----------------------------------------------------------------------
651
652 #[test]
653 fn general_sibling_combinator() {
654 let t = make_test_dom();
655 let sel = parse_first_selector("p ~ a {}");
656 assert!(matches_selector(&t.doc, t.a_link, &sel));
657
658 let sel2 = parse_first_selector("p.intro ~ a {}");
659 assert!(matches_selector(&t.doc, t.a_link, &sel2));
660 }
661
662 // -----------------------------------------------------------------------
663 // Selector list
664 // -----------------------------------------------------------------------
665
666 #[test]
667 fn selector_list_matches() {
668 let t = make_test_dom();
669 let ss = Parser::parse("span, a {}");
670 let rule = match &ss.rules[0] {
671 Rule::Style(r) => r,
672 _ => panic!("expected style rule"),
673 };
674 assert!(matches_selector_list(&t.doc, t.span, &rule.selectors));
675 assert!(matches_selector_list(&t.doc, t.a_link, &rule.selectors));
676 assert!(!matches_selector_list(&t.doc, t.div_main, &rule.selectors));
677 }
678
679 // -----------------------------------------------------------------------
680 // Specificity
681 // -----------------------------------------------------------------------
682
683 #[test]
684 fn specificity_type_selector() {
685 let sel = parse_first_selector("p {}");
686 assert_eq!(specificity(&sel), Specificity::new(0, 0, 1));
687 }
688
689 #[test]
690 fn specificity_class_selector() {
691 let sel = parse_first_selector(".intro {}");
692 assert_eq!(specificity(&sel), Specificity::new(0, 1, 0));
693 }
694
695 #[test]
696 fn specificity_id_selector() {
697 let sel = parse_first_selector("#main {}");
698 assert_eq!(specificity(&sel), Specificity::new(1, 0, 0));
699 }
700
701 #[test]
702 fn specificity_compound() {
703 let sel = parse_first_selector("div#main.container {}");
704 assert_eq!(specificity(&sel), Specificity::new(1, 1, 1));
705 }
706
707 #[test]
708 fn specificity_complex() {
709 let sel = parse_first_selector("body div > p.intro {}");
710 assert_eq!(specificity(&sel), Specificity::new(0, 1, 3));
711 }
712
713 #[test]
714 fn specificity_universal() {
715 let sel = parse_first_selector("* {}");
716 assert_eq!(specificity(&sel), Specificity::new(0, 0, 0));
717 }
718
719 #[test]
720 fn specificity_attribute() {
721 let sel = parse_first_selector("[data-x] {}");
722 assert_eq!(specificity(&sel), Specificity::new(0, 1, 0));
723 }
724
725 #[test]
726 fn specificity_ordering() {
727 let s1 = Specificity::new(0, 0, 1);
728 let s2 = Specificity::new(0, 1, 0);
729 let s3 = Specificity::new(1, 0, 0);
730 assert!(s1 < s2);
731 assert!(s2 < s3);
732 assert!(s1 < s3);
733 }
734
735 // -----------------------------------------------------------------------
736 // Collect matching rules
737 // -----------------------------------------------------------------------
738
739 #[test]
740 fn collect_matching_rules_basic() {
741 let t = make_test_dom();
742 let ss = Parser::parse(
743 r#"
744 p { color: red; }
745 .intro { font-size: 16px; }
746 div { margin: 0; }
747 "#,
748 );
749
750 let matched = collect_matching_rules(&t.doc, t.p_intro, &ss);
751 assert_eq!(matched.len(), 2);
752 assert_eq!(matched[0].specificity, Specificity::new(0, 0, 1));
753 assert_eq!(matched[1].specificity, Specificity::new(0, 1, 0));
754 }
755
756 #[test]
757 fn collect_matching_rules_source_order() {
758 let t = make_test_dom();
759 let ss = Parser::parse(
760 r#"
761 p { color: red; }
762 p { color: blue; }
763 "#,
764 );
765
766 let matched = collect_matching_rules(&t.doc, t.p_intro, &ss);
767 assert_eq!(matched.len(), 2);
768 assert!(matched[0].source_order < matched[1].source_order);
769 }
770
771 // -----------------------------------------------------------------------
772 // Important declarations
773 // -----------------------------------------------------------------------
774
775 #[test]
776 fn important_declarations_come_after_normal() {
777 let t = make_test_dom();
778 let ss = Parser::parse(
779 r#"
780 p { color: red !important; }
781 #main p { color: blue; }
782 "#,
783 );
784
785 let decls = get_declarations_for_node(&t.doc, t.p_intro, &ss);
786 assert_eq!(decls.len(), 2);
787 assert!(!decls[0].important);
788 assert!(decls[1].important);
789 }
790
791 // -----------------------------------------------------------------------
792 // Text node doesn't match
793 // -----------------------------------------------------------------------
794
795 #[test]
796 fn text_node_never_matches() {
797 let t = make_test_dom();
798 let sel = parse_first_selector("* {}");
799 assert!(!matches_selector(&t.doc, t.text_hello, &sel));
800 }
801
802 // -----------------------------------------------------------------------
803 // Complex multi-level selector
804 // -----------------------------------------------------------------------
805
806 #[test]
807 fn complex_multi_level() {
808 let t = make_test_dom();
809 let sel = parse_first_selector("html body div > p.intro {}");
810 assert!(matches_selector(&t.doc, t.p_intro, &sel));
811 assert!(!matches_selector(&t.doc, t.p2, &sel));
812 }
813
814 // -----------------------------------------------------------------------
815 // Helper
816 // -----------------------------------------------------------------------
817
818 fn parse_first_selector(css: &str) -> Selector {
819 let ss = Parser::parse(css);
820 match &ss.rules[0] {
821 Rule::Style(r) => r.selectors.selectors[0].clone(),
822 _ => panic!("expected style rule"),
823 }
824 }
825}