style/
selector_map.rs

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
8use crate::applicable_declarations::{ApplicableDeclarationList, ScopeProximity};
9use crate::context::QuirksMode;
10use crate::derives::*;
11use crate::dom::TElement;
12use crate::rule_tree::CascadeLevel;
13use crate::selector_parser::SelectorImpl;
14use crate::stylist::{CascadeData, ContainerConditionId, Rule, ScopeConditionId, Stylist};
15use crate::AllocErr;
16use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom};
17use dom::ElementState;
18use precomputed_hash::PrecomputedHash;
19use selectors::matching::{matches_selector, MatchingContext};
20use selectors::parser::{Combinator, Component, SelectorIter};
21use smallvec::SmallVec;
22use std::collections::hash_map;
23use std::collections::{HashMap, HashSet};
24use 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.
28pub struct PrecomputedHasher {
29    hash: Option<u32>,
30}
31
32impl 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.
39pub 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.
47const 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.
61pub type PrecomputedHashMap<K, V> = HashMap<K, V, BuildHasherDefault<PrecomputedHasher>>;
62
63/// A simple alias for a hashset using PrecomputedHasher.
64pub type PrecomputedHashSet<K> = HashSet<K, BuildHasherDefault<PrecomputedHasher>>;
65
66impl 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.
88pub 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)]
121pub 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
142impl<T: 'static> Default for SelectorMap<T> {
143    #[inline]
144    fn default() -> Self {
145        Self::new()
146    }
147}
148
149impl<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
198impl 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
395impl<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
687enum 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
704impl<'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
731type DisjointBuckets<'a> = SmallVec<[Bucket<'a>; 5]>;
732
733fn 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)]
811fn 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(&current_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)]
839pub struct MaybeCaseInsensitiveHashMap<K: PrecomputedHash + Hash + Eq, V>(PrecomputedHashMap<K, V>);
840
841impl<V> Default for MaybeCaseInsensitiveHashMap<Atom, V> {
842    #[inline]
843    fn default() -> Self {
844        MaybeCaseInsensitiveHashMap(PrecomputedHashMap::default())
845    }
846}
847
848impl<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}