tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

selector_map.rs (29720B)


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