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