Skip to main content

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