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