web engine - experimental web browser
at main 825 lines 28 kB view raw
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}