diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index 2e2c27840b0..a76da79e457 100644 --- a/components/style/selector_map.rs +++ b/components/style/selector_map.rs @@ -15,7 +15,6 @@ use rule_tree::CascadeLevel; use selector_parser::SelectorImpl; use selectors::matching::{matches_selector, MatchingContext, ElementSelectorFlags}; use selectors::parser::{Component, Combinator, SelectorIter}; -use selectors::parser::LocalName as LocalNameSelector; use smallvec::{SmallVec, VecLike}; use std::collections::{HashMap, HashSet}; use std::collections::hash_map; @@ -102,7 +101,7 @@ pub struct SelectorMap { /// A hash from local name to rules which contain that local name selector. pub local_name_hash: PrecomputedHashMap>, /// Rules that don't have ID, class, or element selectors. - pub other: Vec, + pub other: SmallVec<[T; 1]>, /// The number of entries in this map. pub count: usize, } @@ -119,7 +118,7 @@ impl SelectorMap { id_hash: MaybeCaseInsensitiveHashMap::new(), class_hash: MaybeCaseInsensitiveHashMap::new(), local_name_hash: HashMap::default(), - other: Vec::new(), + other: SmallVec::new(), count: 0, } } @@ -231,37 +230,42 @@ impl SelectorMap { pub fn insert(&mut self, entry: T, quirks_mode: QuirksMode) { self.count += 1; - if let Some(id_name) = get_id_name(entry.selector()) { - self.id_hash.entry(id_name, quirks_mode).or_insert_with(SmallVec::new).push(entry); - return; - } - - if let Some(class_name) = get_class_name(entry.selector()) { - self.class_hash.entry(class_name, quirks_mode).or_insert_with(SmallVec::new).push(entry); - return; - } - - if let Some(LocalNameSelector { name, lower_name }) = get_local_name(entry.selector()) { - // If the local name in the selector isn't lowercase, insert it into - // the rule hash twice. This means that, during lookup, we can always - // find the rules based on the local name of the element, regardless - // of whether it's an html element in an html document (in which case - // we match against lower_name) or not (in which case we match against - // name). - // - // In the case of a non-html-element-in-html-document with a - // lowercase localname and a non-lowercase selector, the rulehash - // lookup may produce superfluous selectors, but the subsequent - // selector matching work will filter them out. - if name != lower_name { - find_push(&mut self.local_name_hash, lower_name, entry.clone()); + let vector = match find_bucket(entry.selector()) { + Bucket::ID(id) => { + self.id_hash + .entry(id.clone(), quirks_mode) + .or_insert_with(SmallVec::new) } - find_push(&mut self.local_name_hash, name, entry); + Bucket::Class(class) => { + self.class_hash + .entry(class.clone(), quirks_mode) + .or_insert_with(SmallVec::new) + } + Bucket::LocalName { name, lower_name } => { + // If the local name in the selector isn't lowercase, insert it + // into the rule hash twice. This means that, during lookup, we + // can always find the rules based on the local name of the + // element, regardless of whether it's an html element in an + // html document (in which case we match against lower_name) or + // not (in which case we match against name). + // + // In the case of a non-html-element-in-html-document with a + // lowercase localname and a non-lowercase selector, the + // rulehash lookup may produce superfluous selectors, but the + // subsequent selector matching work will filter them out. + if name != lower_name { + find_push(&mut self.local_name_hash, lower_name.clone(), entry.clone()); + } + self.local_name_hash + .entry(name.clone()) + .or_insert_with(SmallVec::new) + } + Bucket::Universal => { + &mut self.other + } + }; - return; - } - - self.other.push(entry); + vector.push(entry); } /// Looks up entries by id, class, local name, and other (in order). @@ -375,74 +379,65 @@ impl SelectorMap { } } -/// Searches a compound selector from left to right. If the compound selector -/// is a pseudo-element, it's ignored. -/// -/// The first non-None value returned from |f| is returned. -#[inline(always)] -fn find_from_left( - mut iter: SelectorIter, - mut f: F -) -> Option -where - F: FnMut(&Component) -> Option, -{ - for ss in &mut iter { - if let Some(r) = f(ss) { - return Some(r) - } - } +enum Bucket<'a> { + ID(&'a Atom), + Class(&'a Atom), + LocalName { name: &'a LocalName, lower_name: &'a LocalName, }, + Universal, +} - // Effectively, pseudo-elements are ignored, given only state pseudo-classes - // may appear before them. - if iter.next_sequence() == Some(Combinator::PseudoElement) { - for ss in &mut iter { - if let Some(r) = f(ss) { - return Some(r) +fn specific_bucket_for<'a>( + component: &'a Component +) -> Bucket<'a> { + match *component { + Component::ID(ref id) => Bucket::ID(id), + Component::Class(ref class) => Bucket::Class(class), + Component::LocalName(ref selector) => { + Bucket::LocalName { + name: &selector.name, + lower_name: &selector.lower_name, } } + _ => Bucket::Universal + } +} + +/// Searches a compound selector from left to right, and returns the appropriate +/// bucket for it. +#[inline(always)] +fn find_bucket<'a>(mut iter: SelectorIter<'a, SelectorImpl>) -> Bucket<'a> { + let mut current_bucket = Bucket::Universal; + + loop { + // We basically want to find the most specific bucket, + // where: + // + // id > class > local name > universal. + // + for ss in &mut iter { + let new_bucket = specific_bucket_for(ss); + match new_bucket { + Bucket::ID(..) => return new_bucket, + Bucket::Class(..) => { + current_bucket = new_bucket; + } + Bucket::LocalName { .. } => { + if matches!(current_bucket, Bucket::Universal) { + current_bucket = new_bucket; + } + } + Bucket::Universal => {}, + } + } + + // Effectively, pseudo-elements are ignored, given only state + // pseudo-classes may appear before them. + if iter.next_sequence() != Some(Combinator::PseudoElement) { + break; + } } - None -} - -/// Retrieve the first ID name in the selector, or None otherwise. -#[inline(always)] -pub fn get_id_name(iter: SelectorIter) - -> Option { - find_from_left(iter, |ss| { - if let Component::ID(ref id) = *ss { - return Some(id.clone()); - } - None - }) -} - -/// Retrieve the FIRST class name in the selector, or None otherwise. -#[inline(always)] -pub fn get_class_name(iter: SelectorIter) - -> Option { - find_from_left(iter, |ss| { - if let Component::Class(ref class) = *ss { - return Some(class.clone()); - } - None - }) -} - -/// Retrieve the name if it is a type selector, or None otherwise. -#[inline(always)] -pub fn get_local_name(iter: SelectorIter) - -> Option> { - find_from_left(iter, |ss| { - if let Component::LocalName(ref n) = *ss { - return Some(LocalNameSelector { - name: n.name.clone(), - lower_name: n.lower_name.clone(), - }) - } - None - }) + return current_bucket } #[inline] diff --git a/tests/unit/style/stylist.rs b/tests/unit/style/stylist.rs index 82c3dc26d2b..3ae3715e9c7 100644 --- a/tests/unit/style/stylist.rs +++ b/tests/unit/style/stylist.rs @@ -167,36 +167,6 @@ fn test_rule_ordering_same_specificity() { "The rule that comes later should win."); } - -#[test] -fn test_get_id_name() { - let (rules_list, _) = get_mock_rules(&[".intro", "#top"]); - assert_eq!(selector_map::get_id_name(rules_list[0][0].selector.iter()), None); - assert_eq!(selector_map::get_id_name(rules_list[1][0].selector.iter()), Some(Atom::from("top"))); -} - -#[test] -fn test_get_class_name() { - let (rules_list, _) = get_mock_rules(&[".intro.foo", "#top"]); - assert_eq!(selector_map::get_class_name(rules_list[0][0].selector.iter()), Some(Atom::from("intro"))); - assert_eq!(selector_map::get_class_name(rules_list[1][0].selector.iter()), None); -} - -#[test] -fn test_get_local_name() { - let (rules_list, _) = get_mock_rules(&["img.foo", "#top", "IMG", "ImG"]); - let check = |i: usize, names: Option<(&str, &str)>| { - assert!(selector_map::get_local_name(rules_list[i][0].selector.iter()) - == names.map(|(name, lower_name)| LocalNameSelector { - name: LocalName::from(name), - lower_name: LocalName::from(lower_name) })) - }; - check(0, Some(("img", "img"))); - check(1, None); - check(2, Some(("IMG", "img"))); - check(3, Some(("ImG", "img"))); -} - #[test] fn test_insert() { let (rules_list, _) = get_mock_rules(&[".intro.foo", "#top"]); @@ -204,8 +174,8 @@ fn test_insert() { selector_map.insert(rules_list[1][0].clone(), QuirksMode::NoQuirks); assert_eq!(1, selector_map.id_hash.get(&Atom::from("top"), QuirksMode::NoQuirks).unwrap()[0].source_order); selector_map.insert(rules_list[0][0].clone(), QuirksMode::NoQuirks); - assert_eq!(0, selector_map.class_hash.get(&Atom::from("intro"), QuirksMode::NoQuirks).unwrap()[0].source_order); - assert!(selector_map.class_hash.get(&Atom::from("foo"), QuirksMode::NoQuirks).is_none()); + assert_eq!(0, selector_map.class_hash.get(&Atom::from("foo"), QuirksMode::NoQuirks).unwrap()[0].source_order); + assert!(selector_map.class_hash.get(&Atom::from("intro"), QuirksMode::NoQuirks).is_none()); } fn mock_stylist() -> Stylist {