selector_map.rs (29720B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! A data structure to efficiently index structs containing selectors by local 6 //! name, ids and hash. 7 8 use crate::applicable_declarations::{ApplicableDeclarationList, ScopeProximity}; 9 use crate::context::QuirksMode; 10 use crate::derives::*; 11 use crate::dom::TElement; 12 use crate::rule_tree::CascadeLevel; 13 use crate::selector_parser::SelectorImpl; 14 use crate::stylist::{CascadeData, ContainerConditionId, Rule, ScopeConditionId, Stylist}; 15 use crate::AllocErr; 16 use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; 17 use dom::ElementState; 18 use precomputed_hash::PrecomputedHash; 19 use selectors::matching::{matches_selector, MatchingContext}; 20 use selectors::parser::{Combinator, Component, SelectorIter}; 21 use smallvec::SmallVec; 22 use std::collections::hash_map; 23 use std::collections::{HashMap, HashSet}; 24 use std::hash::{BuildHasherDefault, Hash, Hasher}; 25 26 /// A hasher implementation that doesn't hash anything, because it expects its 27 /// input to be a suitable u32 hash. 28 pub struct PrecomputedHasher { 29 hash: Option<u32>, 30 } 31 32 impl Default for PrecomputedHasher { 33 fn default() -> Self { 34 Self { hash: None } 35 } 36 } 37 38 /// A vector of relevant attributes, that can be useful for revalidation. 39 pub type RelevantAttributes = thin_vec::ThinVec<LocalName>; 40 41 /// This is a set of pseudo-classes that are both relatively-rare (they don't 42 /// affect most elements by default) and likely or known to have global rules 43 /// (in e.g., the UA sheets). 44 /// 45 /// We can avoid selector-matching those global rules for all elements without 46 /// these pseudo-class states. 47 const RARE_PSEUDO_CLASS_STATES: ElementState = ElementState::from_bits_retain( 48 ElementState::FULLSCREEN.bits() 49 | ElementState::VISITED_OR_UNVISITED.bits() 50 | ElementState::URLTARGET.bits() 51 | ElementState::INERT.bits() 52 | ElementState::FOCUS.bits() 53 | ElementState::FOCUSRING.bits() 54 | ElementState::TOPMOST_MODAL.bits() 55 | ElementState::SUPPRESS_FOR_PRINT_SELECTION.bits() 56 | ElementState::ACTIVE_VIEW_TRANSITION.bits() 57 | ElementState::HEADING_LEVEL_BITS.bits(), 58 ); 59 60 /// A simple alias for a hashmap using PrecomputedHasher. 61 pub type PrecomputedHashMap<K, V> = HashMap<K, V, BuildHasherDefault<PrecomputedHasher>>; 62 63 /// A simple alias for a hashset using PrecomputedHasher. 64 pub type PrecomputedHashSet<K> = HashSet<K, BuildHasherDefault<PrecomputedHasher>>; 65 66 impl Hasher for PrecomputedHasher { 67 #[inline] 68 fn write(&mut self, _: &[u8]) { 69 unreachable!( 70 "Called into PrecomputedHasher with something that isn't \ 71 a u32" 72 ) 73 } 74 75 #[inline] 76 fn write_u32(&mut self, i: u32) { 77 debug_assert!(self.hash.is_none()); 78 self.hash = Some(i); 79 } 80 81 #[inline] 82 fn finish(&self) -> u64 { 83 self.hash.expect("PrecomputedHasher wasn't fed?") as u64 84 } 85 } 86 87 /// A trait to abstract over a given selector map entry. 88 pub trait SelectorMapEntry: Sized + Clone { 89 /// Gets the selector we should use to index in the selector map. 90 fn selector(&self) -> SelectorIter<'_, SelectorImpl>; 91 } 92 93 /// Map element data to selector-providing objects for which the last simple 94 /// selector starts with them. 95 /// 96 /// e.g., 97 /// "p > img" would go into the set of selectors corresponding to the 98 /// element "img" 99 /// "a .foo .bar.baz" would go into the set of selectors corresponding to 100 /// the class "bar" 101 /// 102 /// Because we match selectors right-to-left (i.e., moving up the tree 103 /// from an element), we need to compare the last simple selector in the 104 /// selector with the element. 105 /// 106 /// So, if an element has ID "id1" and classes "foo" and "bar", then all 107 /// the rules it matches will have their last simple selector starting 108 /// either with "#id1" or with ".foo" or with ".bar". 109 /// 110 /// Hence, the union of the rules keyed on each of element's classes, ID, 111 /// element name, etc. will contain the Selectors that actually match that 112 /// element. 113 /// 114 /// We use a 1-entry SmallVec to avoid a separate heap allocation in the case 115 /// where we only have one entry, which is quite common. See measurements in: 116 /// * https://bugzilla.mozilla.org/show_bug.cgi?id=1363789#c5 117 /// * https://bugzilla.mozilla.org/show_bug.cgi?id=681755 118 /// 119 /// TODO: Tune the initial capacity of the HashMap 120 #[derive(Clone, Debug, MallocSizeOf)] 121 pub struct SelectorMap<T: 'static> { 122 /// Rules that have `:root` selectors. 123 pub root: SmallVec<[T; 1]>, 124 /// A hash from an ID to rules which contain that ID selector. 125 pub id_hash: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[T; 1]>>, 126 /// A hash from a class name to rules which contain that class selector. 127 pub class_hash: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[T; 1]>>, 128 /// A hash from local name to rules which contain that local name selector. 129 pub local_name_hash: PrecomputedHashMap<LocalName, SmallVec<[T; 1]>>, 130 /// A hash from attributes to rules which contain that attribute selector. 131 pub attribute_hash: PrecomputedHashMap<LocalName, SmallVec<[T; 1]>>, 132 /// A hash from namespace to rules which contain that namespace selector. 133 pub namespace_hash: PrecomputedHashMap<Namespace, SmallVec<[T; 1]>>, 134 /// Rules for pseudo-states that are rare but have global selectors. 135 pub rare_pseudo_classes: SmallVec<[T; 1]>, 136 /// All other rules. 137 pub other: SmallVec<[T; 1]>, 138 /// The number of entries in this map. 139 pub count: usize, 140 } 141 142 impl<T: 'static> Default for SelectorMap<T> { 143 #[inline] 144 fn default() -> Self { 145 Self::new() 146 } 147 } 148 149 impl<T> SelectorMap<T> { 150 /// Trivially constructs an empty `SelectorMap`. 151 pub fn new() -> Self { 152 SelectorMap { 153 root: SmallVec::new(), 154 id_hash: MaybeCaseInsensitiveHashMap::new(), 155 class_hash: MaybeCaseInsensitiveHashMap::new(), 156 attribute_hash: HashMap::default(), 157 local_name_hash: HashMap::default(), 158 namespace_hash: HashMap::default(), 159 rare_pseudo_classes: SmallVec::new(), 160 other: SmallVec::new(), 161 count: 0, 162 } 163 } 164 165 /// Shrink the capacity of the map if needed. 166 pub fn shrink_if_needed(&mut self) { 167 self.id_hash.shrink_if_needed(); 168 self.class_hash.shrink_if_needed(); 169 self.attribute_hash.shrink_if_needed(); 170 self.local_name_hash.shrink_if_needed(); 171 self.namespace_hash.shrink_if_needed(); 172 } 173 174 /// Clears the hashmap retaining storage. 175 pub fn clear(&mut self) { 176 self.root.clear(); 177 self.id_hash.clear(); 178 self.class_hash.clear(); 179 self.attribute_hash.clear(); 180 self.local_name_hash.clear(); 181 self.namespace_hash.clear(); 182 self.rare_pseudo_classes.clear(); 183 self.other.clear(); 184 self.count = 0; 185 } 186 187 /// Returns whether there are any entries in the map. 188 pub fn is_empty(&self) -> bool { 189 self.count == 0 190 } 191 192 /// Returns the number of entries. 193 pub fn len(&self) -> usize { 194 self.count 195 } 196 } 197 198 impl SelectorMap<Rule> { 199 /// Append to `rule_list` all Rules in `self` that match element. 200 /// 201 /// Extract matching rules as per element's ID, classes, tag name, etc.. 202 /// Sort the Rules at the end to maintain cascading order. 203 pub fn get_all_matching_rules<E>( 204 &self, 205 element: E, 206 rule_hash_target: E, 207 matching_rules_list: &mut ApplicableDeclarationList, 208 matching_context: &mut MatchingContext<E::Impl>, 209 cascade_level: CascadeLevel, 210 cascade_data: &CascadeData, 211 stylist: &Stylist, 212 ) where 213 E: TElement, 214 { 215 if self.is_empty() { 216 return; 217 } 218 219 let quirks_mode = matching_context.quirks_mode(); 220 221 if rule_hash_target.is_root() { 222 SelectorMap::get_matching_rules( 223 element, 224 &self.root, 225 matching_rules_list, 226 matching_context, 227 cascade_level, 228 cascade_data, 229 stylist, 230 ); 231 } 232 233 if let Some(id) = rule_hash_target.id() { 234 if let Some(rules) = self.id_hash.get(id, quirks_mode) { 235 SelectorMap::get_matching_rules( 236 element, 237 rules, 238 matching_rules_list, 239 matching_context, 240 cascade_level, 241 cascade_data, 242 stylist, 243 ) 244 } 245 } 246 247 rule_hash_target.each_class(|class| { 248 if let Some(rules) = self.class_hash.get(&class, quirks_mode) { 249 SelectorMap::get_matching_rules( 250 element, 251 rules, 252 matching_rules_list, 253 matching_context, 254 cascade_level, 255 cascade_data, 256 stylist, 257 ) 258 } 259 }); 260 261 rule_hash_target.each_attr_name(|name| { 262 if let Some(rules) = self.attribute_hash.get(name) { 263 SelectorMap::get_matching_rules( 264 element, 265 rules, 266 matching_rules_list, 267 matching_context, 268 cascade_level, 269 cascade_data, 270 stylist, 271 ) 272 } 273 }); 274 275 if let Some(rules) = self.local_name_hash.get(rule_hash_target.local_name()) { 276 SelectorMap::get_matching_rules( 277 element, 278 rules, 279 matching_rules_list, 280 matching_context, 281 cascade_level, 282 cascade_data, 283 stylist, 284 ) 285 } 286 287 if rule_hash_target 288 .state() 289 .intersects(RARE_PSEUDO_CLASS_STATES) 290 { 291 SelectorMap::get_matching_rules( 292 element, 293 &self.rare_pseudo_classes, 294 matching_rules_list, 295 matching_context, 296 cascade_level, 297 cascade_data, 298 stylist, 299 ); 300 } 301 302 if let Some(rules) = self.namespace_hash.get(rule_hash_target.namespace()) { 303 SelectorMap::get_matching_rules( 304 element, 305 rules, 306 matching_rules_list, 307 matching_context, 308 cascade_level, 309 cascade_data, 310 stylist, 311 ) 312 } 313 314 SelectorMap::get_matching_rules( 315 element, 316 &self.other, 317 matching_rules_list, 318 matching_context, 319 cascade_level, 320 cascade_data, 321 stylist, 322 ); 323 } 324 325 /// Adds rules in `rules` that match `element` to the `matching_rules` list. 326 pub(crate) fn get_matching_rules<E>( 327 element: E, 328 rules: &[Rule], 329 matching_rules: &mut ApplicableDeclarationList, 330 matching_context: &mut MatchingContext<E::Impl>, 331 cascade_level: CascadeLevel, 332 cascade_data: &CascadeData, 333 stylist: &Stylist, 334 ) where 335 E: TElement, 336 { 337 use selectors::matching::IncludeStartingStyle; 338 339 let include_starting_style = matches!( 340 matching_context.include_starting_style, 341 IncludeStartingStyle::Yes 342 ); 343 for rule in rules { 344 let scope_proximity = if rule.scope_condition_id == ScopeConditionId::none() { 345 if !matches_selector( 346 &rule.selector, 347 0, 348 Some(&rule.hashes), 349 &element, 350 matching_context, 351 ) { 352 continue; 353 } 354 ScopeProximity::infinity() 355 } else { 356 let result = 357 cascade_data.find_scope_proximity_if_matching(rule, element, matching_context); 358 if result == ScopeProximity::infinity() { 359 continue; 360 } 361 result 362 }; 363 364 if rule.container_condition_id != ContainerConditionId::none() { 365 if !cascade_data.container_condition_matches( 366 rule.container_condition_id, 367 stylist, 368 element, 369 matching_context, 370 ) { 371 continue; 372 } 373 } 374 375 if rule.is_starting_style { 376 // Set this flag if there are any rules inside @starting-style. This flag is for 377 // optimization to avoid any redundant resolution of starting style if the author 378 // doesn't specify for this element. 379 matching_context.has_starting_style = true; 380 381 if !include_starting_style { 382 continue; 383 } 384 } 385 386 matching_rules.push(rule.to_applicable_declaration_block( 387 cascade_level, 388 cascade_data, 389 scope_proximity, 390 )); 391 } 392 } 393 } 394 395 impl<T: SelectorMapEntry> SelectorMap<T> { 396 /// Inserts an entry into the correct bucket(s). 397 pub fn insert(&mut self, entry: T, quirks_mode: QuirksMode) -> Result<(), AllocErr> { 398 self.count += 1; 399 400 // NOTE(emilio): It'd be nice for this to be a separate function, but 401 // then the compiler can't reason about the lifetime dependency between 402 // `entry` and `bucket`, and would force us to clone the rule in the 403 // common path. 404 macro_rules! insert_into_bucket { 405 ($entry:ident, $bucket:expr) => {{ 406 let vec = match $bucket { 407 Bucket::Root => &mut self.root, 408 Bucket::ID(id) => self 409 .id_hash 410 .try_entry(id.clone(), quirks_mode)? 411 .or_default(), 412 Bucket::Class(class) => self 413 .class_hash 414 .try_entry(class.clone(), quirks_mode)? 415 .or_default(), 416 Bucket::Attribute { name, lower_name } 417 | Bucket::LocalName { name, lower_name } => { 418 // If the local name in the selector isn't lowercase, 419 // insert it into the rule hash twice. This means that, 420 // during lookup, we can always find the rules based on 421 // the local name of the element, regardless of whether 422 // it's an html element in an html document (in which 423 // case we match against lower_name) or not (in which 424 // case we match against name). 425 // 426 // In the case of a non-html-element-in-html-document 427 // with a lowercase localname and a non-lowercase 428 // selector, the rulehash lookup may produce superfluous 429 // selectors, but the subsequent selector matching work 430 // will filter them out. 431 let is_attribute = matches!($bucket, Bucket::Attribute { .. }); 432 let hash = if is_attribute { 433 &mut self.attribute_hash 434 } else { 435 &mut self.local_name_hash 436 }; 437 if name != lower_name { 438 hash.try_reserve(1)?; 439 let vec = hash.entry(lower_name.clone()).or_default(); 440 vec.try_reserve(1)?; 441 vec.push($entry.clone()); 442 } 443 hash.try_reserve(1)?; 444 hash.entry(name.clone()).or_default() 445 }, 446 Bucket::Namespace(url) => { 447 self.namespace_hash.try_reserve(1)?; 448 self.namespace_hash.entry(url.clone()).or_default() 449 }, 450 Bucket::RarePseudoClasses => &mut self.rare_pseudo_classes, 451 Bucket::Universal => &mut self.other, 452 }; 453 vec.try_reserve(1)?; 454 vec.push($entry); 455 }}; 456 } 457 458 let bucket = { 459 let mut disjoint_buckets = SmallVec::new(); 460 let bucket = find_bucket(entry.selector(), &mut disjoint_buckets); 461 462 // See if inserting this selector in multiple entries in the 463 // selector map would be worth it. Consider a case like: 464 // 465 // .foo:where(div, #bar) 466 // 467 // There, `bucket` would be `Class(foo)`, and disjoint_buckets would 468 // be `[LocalName { div }, ID(bar)]`. 469 // 470 // Here we choose to insert the selector in the `.foo` bucket in 471 // such a case, as it's likely more worth it than inserting it in 472 // both `div` and `#bar`. 473 // 474 // This is specially true if there's any universal selector in the 475 // `disjoint_selectors` set, at which point we'd just be doing 476 // wasted work. 477 if !disjoint_buckets.is_empty() 478 && disjoint_buckets 479 .iter() 480 .all(|b| b.more_specific_than(&bucket)) 481 { 482 for bucket in &disjoint_buckets { 483 let entry = entry.clone(); 484 insert_into_bucket!(entry, *bucket); 485 } 486 return Ok(()); 487 } 488 bucket 489 }; 490 491 insert_into_bucket!(entry, bucket); 492 Ok(()) 493 } 494 495 /// Looks up entries by id, class, local name, namespace, and other (in 496 /// order). 497 /// 498 /// Each entry is passed to the callback, which returns true to continue 499 /// iterating entries, or false to terminate the lookup. 500 /// 501 /// Returns false if the callback ever returns false. 502 /// 503 /// FIXME(bholley) This overlaps with SelectorMap<Rule>::get_all_matching_rules, 504 /// but that function is extremely hot and I'd rather not rearrange it. 505 pub fn lookup<'a, E, F>( 506 &'a self, 507 element: E, 508 quirks_mode: QuirksMode, 509 relevant_attributes: Option<&mut RelevantAttributes>, 510 f: F, 511 ) -> bool 512 where 513 E: TElement, 514 F: FnMut(&'a T) -> bool, 515 { 516 self.lookup_with_state( 517 element, 518 element.state(), 519 quirks_mode, 520 relevant_attributes, 521 f, 522 ) 523 } 524 525 #[inline] 526 fn lookup_with_state<'a, E, F>( 527 &'a self, 528 element: E, 529 element_state: ElementState, 530 quirks_mode: QuirksMode, 531 mut relevant_attributes: Option<&mut RelevantAttributes>, 532 mut f: F, 533 ) -> bool 534 where 535 E: TElement, 536 F: FnMut(&'a T) -> bool, 537 { 538 if element.is_root() { 539 for entry in self.root.iter() { 540 if !f(&entry) { 541 return false; 542 } 543 } 544 } 545 546 if let Some(id) = element.id() { 547 if let Some(v) = self.id_hash.get(id, quirks_mode) { 548 for entry in v.iter() { 549 if !f(&entry) { 550 return false; 551 } 552 } 553 } 554 } 555 556 let mut done = false; 557 element.each_class(|class| { 558 if done { 559 return; 560 } 561 if let Some(v) = self.class_hash.get(class, quirks_mode) { 562 for entry in v.iter() { 563 if !f(&entry) { 564 done = true; 565 return; 566 } 567 } 568 } 569 }); 570 571 if done { 572 return false; 573 } 574 575 element.each_attr_name(|name| { 576 if done { 577 return; 578 } 579 if let Some(v) = self.attribute_hash.get(name) { 580 if let Some(ref mut relevant_attributes) = relevant_attributes { 581 relevant_attributes.push(name.clone()); 582 } 583 for entry in v.iter() { 584 if !f(&entry) { 585 done = true; 586 return; 587 } 588 } 589 } 590 }); 591 592 if done { 593 return false; 594 } 595 596 if let Some(v) = self.local_name_hash.get(element.local_name()) { 597 for entry in v.iter() { 598 if !f(&entry) { 599 return false; 600 } 601 } 602 } 603 604 if let Some(v) = self.namespace_hash.get(element.namespace()) { 605 for entry in v.iter() { 606 if !f(&entry) { 607 return false; 608 } 609 } 610 } 611 612 if element_state.intersects(RARE_PSEUDO_CLASS_STATES) { 613 for entry in self.rare_pseudo_classes.iter() { 614 if !f(&entry) { 615 return false; 616 } 617 } 618 } 619 620 for entry in self.other.iter() { 621 if !f(&entry) { 622 return false; 623 } 624 } 625 626 true 627 } 628 629 /// Performs a normal lookup, and also looks up entries for the passed-in 630 /// id and classes. 631 /// 632 /// Each entry is passed to the callback, which returns true to continue 633 /// iterating entries, or false to terminate the lookup. 634 /// 635 /// Returns false if the callback ever returns false. 636 #[inline] 637 pub fn lookup_with_additional<'a, E, F>( 638 &'a self, 639 element: E, 640 quirks_mode: QuirksMode, 641 additional_id: Option<&WeakAtom>, 642 additional_classes: &[Atom], 643 additional_states: ElementState, 644 mut f: F, 645 ) -> bool 646 where 647 E: TElement, 648 F: FnMut(&'a T) -> bool, 649 { 650 // Do the normal lookup. 651 if !self.lookup_with_state( 652 element, 653 element.state() | additional_states, 654 quirks_mode, 655 /* relevant_attributes = */ None, 656 |entry| f(entry), 657 ) { 658 return false; 659 } 660 661 // Check the additional id. 662 if let Some(id) = additional_id { 663 if let Some(v) = self.id_hash.get(id, quirks_mode) { 664 for entry in v.iter() { 665 if !f(&entry) { 666 return false; 667 } 668 } 669 } 670 } 671 672 // Check the additional classes. 673 for class in additional_classes { 674 if let Some(v) = self.class_hash.get(class, quirks_mode) { 675 for entry in v.iter() { 676 if !f(&entry) { 677 return false; 678 } 679 } 680 } 681 } 682 683 true 684 } 685 } 686 687 enum Bucket<'a> { 688 Universal, 689 Namespace(&'a Namespace), 690 RarePseudoClasses, 691 LocalName { 692 name: &'a LocalName, 693 lower_name: &'a LocalName, 694 }, 695 Attribute { 696 name: &'a LocalName, 697 lower_name: &'a LocalName, 698 }, 699 Class(&'a Atom), 700 ID(&'a Atom), 701 Root, 702 } 703 704 impl<'a> Bucket<'a> { 705 /// root > id > class > local name > namespace > pseudo-classes > universal. 706 #[inline] 707 fn specificity(&self) -> usize { 708 match *self { 709 Bucket::Universal => 0, 710 Bucket::Namespace(..) => 1, 711 Bucket::RarePseudoClasses => 2, 712 Bucket::LocalName { .. } => 3, 713 Bucket::Attribute { .. } => 4, 714 Bucket::Class(..) => 5, 715 Bucket::ID(..) => 6, 716 Bucket::Root => 7, 717 } 718 } 719 720 #[inline] 721 fn more_or_equally_specific_than(&self, other: &Self) -> bool { 722 self.specificity() >= other.specificity() 723 } 724 725 #[inline] 726 fn more_specific_than(&self, other: &Self) -> bool { 727 self.specificity() > other.specificity() 728 } 729 } 730 731 type DisjointBuckets<'a> = SmallVec<[Bucket<'a>; 5]>; 732 733 fn specific_bucket_for<'a>( 734 component: &'a Component<SelectorImpl>, 735 disjoint_buckets: &mut DisjointBuckets<'a>, 736 ) -> Bucket<'a> { 737 match *component { 738 Component::Root => Bucket::Root, 739 Component::ID(ref id) => Bucket::ID(id), 740 Component::Class(ref class) => Bucket::Class(class), 741 Component::AttributeInNoNamespace { ref local_name, .. } => Bucket::Attribute { 742 name: local_name, 743 lower_name: local_name, 744 }, 745 Component::AttributeInNoNamespaceExists { 746 ref local_name, 747 ref local_name_lower, 748 } => Bucket::Attribute { 749 name: local_name, 750 lower_name: local_name_lower, 751 }, 752 Component::AttributeOther(ref selector) => Bucket::Attribute { 753 name: &selector.local_name, 754 lower_name: &selector.local_name_lower, 755 }, 756 Component::LocalName(ref selector) => Bucket::LocalName { 757 name: &selector.name, 758 lower_name: &selector.lower_name, 759 }, 760 Component::Namespace(_, ref url) | Component::DefaultNamespace(ref url) => { 761 Bucket::Namespace(url) 762 }, 763 // ::slotted(..) isn't a normal pseudo-element, so we can insert it on 764 // the rule hash normally without much problem. For example, in a 765 // selector like: 766 // 767 // div::slotted(span)::before 768 // 769 // It looks like: 770 // 771 // [ 772 // LocalName(div), 773 // Combinator(SlotAssignment), 774 // Slotted(span), 775 // Combinator::PseudoElement, 776 // PseudoElement(::before), 777 // ] 778 // 779 // So inserting `span` in the rule hash makes sense since we want to 780 // match the slotted <span>. 781 Component::Slotted(ref selector) => find_bucket(selector.iter(), disjoint_buckets), 782 Component::Host(Some(ref selector)) => find_bucket(selector.iter(), disjoint_buckets), 783 Component::Is(ref list) | Component::Where(ref list) => { 784 if list.len() == 1 { 785 find_bucket(list.slice()[0].iter(), disjoint_buckets) 786 } else { 787 for selector in list.slice() { 788 let bucket = find_bucket(selector.iter(), disjoint_buckets); 789 disjoint_buckets.push(bucket); 790 } 791 Bucket::Universal 792 } 793 }, 794 Component::NonTSPseudoClass(ref pseudo_class) 795 if pseudo_class 796 .state_flag() 797 .intersects(RARE_PSEUDO_CLASS_STATES) => 798 { 799 Bucket::RarePseudoClasses 800 }, 801 _ => Bucket::Universal, 802 } 803 } 804 805 /// Searches a compound selector from left to right, and returns the appropriate 806 /// bucket for it. 807 /// 808 /// It also populates disjoint_buckets with dependencies from nested selectors 809 /// with any semantics like :is() and :where(). 810 #[inline(always)] 811 fn find_bucket<'a>( 812 mut iter: SelectorIter<'a, SelectorImpl>, 813 disjoint_buckets: &mut DisjointBuckets<'a>, 814 ) -> Bucket<'a> { 815 let mut current_bucket = Bucket::Universal; 816 817 loop { 818 for ss in &mut iter { 819 let new_bucket = specific_bucket_for(ss, disjoint_buckets); 820 // NOTE: When presented with the choice of multiple specific selectors, use the 821 // rightmost, on the assumption that that's less common, see bug 1829540. 822 if new_bucket.more_or_equally_specific_than(¤t_bucket) { 823 current_bucket = new_bucket; 824 } 825 } 826 827 // Effectively, pseudo-elements are ignored, given only state 828 // pseudo-classes may appear before them. 829 if iter.next_sequence() != Some(Combinator::PseudoElement) { 830 break; 831 } 832 } 833 834 current_bucket 835 } 836 837 /// Wrapper for PrecomputedHashMap that does ASCII-case-insensitive lookup in quirks mode. 838 #[derive(Clone, Debug, MallocSizeOf)] 839 pub struct MaybeCaseInsensitiveHashMap<K: PrecomputedHash + Hash + Eq, V>(PrecomputedHashMap<K, V>); 840 841 impl<V> Default for MaybeCaseInsensitiveHashMap<Atom, V> { 842 #[inline] 843 fn default() -> Self { 844 MaybeCaseInsensitiveHashMap(PrecomputedHashMap::default()) 845 } 846 } 847 848 impl<V> MaybeCaseInsensitiveHashMap<Atom, V> { 849 /// Empty map 850 pub fn new() -> Self { 851 Self::default() 852 } 853 854 /// Shrink the capacity of the map if needed. 855 pub fn shrink_if_needed(&mut self) { 856 self.0.shrink_if_needed() 857 } 858 859 /// HashMap::try_entry 860 pub fn try_entry( 861 &mut self, 862 mut key: Atom, 863 quirks_mode: QuirksMode, 864 ) -> Result<hash_map::Entry<'_, Atom, V>, AllocErr> { 865 if quirks_mode == QuirksMode::Quirks { 866 key = key.to_ascii_lowercase() 867 } 868 self.0.try_reserve(1)?; 869 Ok(self.0.entry(key)) 870 } 871 872 /// HashMap::is_empty 873 #[inline] 874 pub fn is_empty(&self) -> bool { 875 self.0.is_empty() 876 } 877 878 /// HashMap::iter 879 pub fn iter(&self) -> hash_map::Iter<'_, Atom, V> { 880 self.0.iter() 881 } 882 883 /// HashMap::clear 884 pub fn clear(&mut self) { 885 self.0.clear() 886 } 887 888 /// HashMap::get 889 pub fn get(&self, key: &WeakAtom, quirks_mode: QuirksMode) -> Option<&V> { 890 if quirks_mode == QuirksMode::Quirks { 891 self.0.get(&key.to_ascii_lowercase()) 892 } else { 893 self.0.get(key) 894 } 895 } 896 }