mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
style: Avoid multiple selector walk-ups during SelectorMap insertion.
MozReview-Commit-ID: 5b2X0GL2753 Signed-off-by: Emilio Cobos Álvarez <emilio@crisal.io>
This commit is contained in:
parent
dc654c9912
commit
b040d79333
2 changed files with 92 additions and 127 deletions
|
@ -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<T> {
|
|||
/// A hash from local name to rules which contain that local name selector.
|
||||
pub local_name_hash: PrecomputedHashMap<LocalName, SmallVec<[T; 1]>>,
|
||||
/// Rules that don't have ID, class, or element selectors.
|
||||
pub other: Vec<T>,
|
||||
pub other: SmallVec<[T; 1]>,
|
||||
/// The number of entries in this map.
|
||||
pub count: usize,
|
||||
}
|
||||
|
@ -119,7 +118,7 @@ impl<T> SelectorMap<T> {
|
|||
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<T: SelectorMapEntry> SelectorMap<T> {
|
|||
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<T: SelectorMapEntry> SelectorMap<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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<F, R>(
|
||||
mut iter: SelectorIter<SelectorImpl>,
|
||||
mut f: F
|
||||
) -> Option<R>
|
||||
where
|
||||
F: FnMut(&Component<SelectorImpl>) -> Option<R>,
|
||||
{
|
||||
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<SelectorImpl>
|
||||
) -> 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<SelectorImpl>)
|
||||
-> Option<Atom> {
|
||||
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<SelectorImpl>)
|
||||
-> Option<Atom> {
|
||||
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<SelectorImpl>)
|
||||
-> Option<LocalNameSelector<SelectorImpl>> {
|
||||
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]
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue