style: Add a low-priority selector list for pseudo-classes that have global rules

This avoids trying to match those global rules for most elements that
can't match them anyways.

Differential Revision: https://phabricator.services.mozilla.com/D147640
This commit is contained in:
Emilio Cobos Álvarez 2022-06-09 12:16:26 +00:00 committed by Martin Robinson
parent 1162204bad
commit c283b32991
2 changed files with 86 additions and 15 deletions

View file

@ -407,24 +407,24 @@ where
} }
}); });
let state_changes = self.state_changes; self.collect_state_dependencies(&map.state_affecting_selectors)
if !state_changes.is_empty() {
self.collect_state_dependencies(&map.state_affecting_selectors, state_changes)
}
} }
fn collect_state_dependencies( fn collect_state_dependencies(
&mut self, &mut self,
map: &'selectors SelectorMap<StateDependency>, map: &'selectors SelectorMap<StateDependency>,
state_changes: ElementState,
) { ) {
if self.state_changes.is_empty() {
return;
}
map.lookup_with_additional( map.lookup_with_additional(
self.lookup_element, self.lookup_element,
self.matching_context.quirks_mode(), self.matching_context.quirks_mode(),
self.removed_id, self.removed_id,
self.classes_removed, self.classes_removed,
self.state_changes,
|dependency| { |dependency| {
if !dependency.state.intersects(state_changes) { if !dependency.state.intersects(self.state_changes) {
return true; return true;
} }
self.scan_dependency(&dependency.dep); self.scan_dependency(&dependency.dep);

View file

@ -10,9 +10,10 @@ use crate::context::QuirksMode;
use crate::dom::TElement; use crate::dom::TElement;
use crate::rule_tree::CascadeLevel; use crate::rule_tree::CascadeLevel;
use crate::selector_parser::SelectorImpl; use crate::selector_parser::SelectorImpl;
use crate::stylist::{Stylist, CascadeData, Rule, ContainerConditionId}; use crate::stylist::{CascadeData, ContainerConditionId, Rule, Stylist};
use crate::AllocErr; use crate::AllocErr;
use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom};
use dom::ElementState;
use precomputed_hash::PrecomputedHash; use precomputed_hash::PrecomputedHash;
use selectors::matching::{matches_selector, MatchingContext}; use selectors::matching::{matches_selector, MatchingContext};
use selectors::parser::{Combinator, Component, SelectorIter}; use selectors::parser::{Combinator, Component, SelectorIter};
@ -33,6 +34,22 @@ impl Default for PrecomputedHasher {
} }
} }
/// This is a set of pseudo-classes that are both relatively-rare (they don't
/// affect most elements by default) and likely or known to have global rules
/// (in e.g., the UA sheets).
///
/// We can avoid selector-matching those global rules for all elements without
/// these pseudo-class states.
const RARE_PSEUDO_CLASS_STATES: ElementState = ElementState::from_bits_truncate(
ElementState::FULLSCREEN.bits() |
ElementState::VISITED_OR_UNVISITED.bits() |
ElementState::URLTARGET.bits() |
ElementState::INERT.bits() |
ElementState::FOCUS.bits() |
ElementState::FOCUSRING.bits() |
ElementState::TOPMOST_MODAL.bits()
);
/// A simple alias for a hashmap using PrecomputedHasher. /// A simple alias for a hashmap using PrecomputedHasher.
pub type PrecomputedHashMap<K, V> = HashMap<K, V, BuildHasherDefault<PrecomputedHasher>>; pub type PrecomputedHashMap<K, V> = HashMap<K, V, BuildHasherDefault<PrecomputedHasher>>;
@ -107,6 +124,8 @@ pub struct SelectorMap<T: 'static> {
pub attribute_hash: PrecomputedHashMap<LocalName, SmallVec<[T; 1]>>, pub attribute_hash: PrecomputedHashMap<LocalName, SmallVec<[T; 1]>>,
/// A hash from namespace to rules which contain that namespace selector. /// A hash from namespace to rules which contain that namespace selector.
pub namespace_hash: PrecomputedHashMap<Namespace, SmallVec<[T; 1]>>, pub namespace_hash: PrecomputedHashMap<Namespace, SmallVec<[T; 1]>>,
/// Rules for pseudo-states that are rare but have global selectors.
pub rare_pseudo_classes: SmallVec<[T; 1]>,
/// All other rules. /// All other rules.
pub other: SmallVec<[T; 1]>, pub other: SmallVec<[T; 1]>,
/// Whether we should bucket by attribute names. /// Whether we should bucket by attribute names.
@ -132,6 +151,7 @@ impl<T> SelectorMap<T> {
attribute_hash: HashMap::default(), attribute_hash: HashMap::default(),
local_name_hash: HashMap::default(), local_name_hash: HashMap::default(),
namespace_hash: HashMap::default(), namespace_hash: HashMap::default(),
rare_pseudo_classes: SmallVec::new(),
other: SmallVec::new(), other: SmallVec::new(),
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
bucket_attributes: static_prefs::pref!("layout.css.bucket-attribute-names.enabled"), bucket_attributes: static_prefs::pref!("layout.css.bucket-attribute-names.enabled"),
@ -166,6 +186,7 @@ impl<T> SelectorMap<T> {
self.attribute_hash.clear(); self.attribute_hash.clear();
self.local_name_hash.clear(); self.local_name_hash.clear();
self.namespace_hash.clear(); self.namespace_hash.clear();
self.rare_pseudo_classes.clear();
self.other.clear(); self.other.clear();
self.count = 0; self.count = 0;
} }
@ -272,6 +293,18 @@ impl SelectorMap<Rule> {
) )
} }
if rule_hash_target.state().intersects(RARE_PSEUDO_CLASS_STATES) {
SelectorMap::get_matching_rules(
element,
&self.rare_pseudo_classes,
matching_rules_list,
matching_context,
cascade_level,
cascade_data,
stylist,
);
}
if let Some(rules) = self.namespace_hash.get(rule_hash_target.namespace()) { if let Some(rules) = self.namespace_hash.get(rule_hash_target.namespace()) {
SelectorMap::get_matching_rules( SelectorMap::get_matching_rules(
element, element,
@ -385,6 +418,7 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
self.namespace_hash.try_reserve(1)?; self.namespace_hash.try_reserve(1)?;
self.namespace_hash.entry(url.clone()).or_default() self.namespace_hash.entry(url.clone()).or_default()
}, },
Bucket::RarePseudoClasses => &mut self.rare_pseudo_classes,
Bucket::Universal => &mut self.other, Bucket::Universal => &mut self.other,
}; };
vec.try_reserve(1)?; vec.try_reserve(1)?;
@ -443,8 +477,22 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
/// ///
/// FIXME(bholley) This overlaps with SelectorMap<Rule>::get_all_matching_rules, /// FIXME(bholley) This overlaps with SelectorMap<Rule>::get_all_matching_rules,
/// but that function is extremely hot and I'd rather not rearrange it. /// but that function is extremely hot and I'd rather not rearrange it.
pub fn lookup<'a, E, F>(&'a self, element: E, quirks_mode: QuirksMode, f: F) -> bool
where
E: TElement,
F: FnMut(&'a T) -> bool,
{
self.lookup_with_state(element, element.state(), quirks_mode, f)
}
#[inline] #[inline]
pub fn lookup<'a, E, F>(&'a self, element: E, quirks_mode: QuirksMode, mut f: F) -> bool fn lookup_with_state<'a, E, F>(
&'a self,
element: E,
element_state: ElementState,
quirks_mode: QuirksMode,
mut f: F,
) -> bool
where where
E: TElement, E: TElement,
F: FnMut(&'a T) -> bool, F: FnMut(&'a T) -> bool,
@ -522,6 +570,14 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
} }
} }
if element_state.intersects(RARE_PSEUDO_CLASS_STATES) {
for entry in self.rare_pseudo_classes.iter() {
if !f(&entry) {
return false;
}
}
}
for entry in self.other.iter() { for entry in self.other.iter() {
if !f(&entry) { if !f(&entry) {
return false; return false;
@ -545,6 +601,7 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
quirks_mode: QuirksMode, quirks_mode: QuirksMode,
additional_id: Option<&WeakAtom>, additional_id: Option<&WeakAtom>,
additional_classes: &[Atom], additional_classes: &[Atom],
additional_states: ElementState,
mut f: F, mut f: F,
) -> bool ) -> bool
where where
@ -552,7 +609,12 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
F: FnMut(&'a T) -> bool, F: FnMut(&'a T) -> bool,
{ {
// Do the normal lookup. // Do the normal lookup.
if !self.lookup(element, quirks_mode, |entry| f(entry)) { if !self.lookup_with_state(
element,
element.state() | additional_states,
quirks_mode,
|entry| f(entry),
) {
return false; return false;
} }
@ -585,6 +647,7 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
enum Bucket<'a> { enum Bucket<'a> {
Universal, Universal,
Namespace(&'a Namespace), Namespace(&'a Namespace),
RarePseudoClasses,
LocalName { LocalName {
name: &'a LocalName, name: &'a LocalName,
lower_name: &'a LocalName, lower_name: &'a LocalName,
@ -599,17 +662,18 @@ enum Bucket<'a> {
} }
impl<'a> Bucket<'a> { impl<'a> Bucket<'a> {
/// root > id > class > local name > namespace > universal. /// root > id > class > local name > namespace > pseudo-classes > universal.
#[inline] #[inline]
fn specificity(&self) -> usize { fn specificity(&self) -> usize {
match *self { match *self {
Bucket::Universal => 0, Bucket::Universal => 0,
Bucket::Namespace(..) => 1, Bucket::Namespace(..) => 1,
Bucket::LocalName { .. } => 2, Bucket::RarePseudoClasses => 2,
Bucket::Attribute { .. } => 3, Bucket::LocalName { .. } => 3,
Bucket::Class(..) => 4, Bucket::Attribute { .. } => 4,
Bucket::ID(..) => 5, Bucket::Class(..) => 5,
Bucket::Root => 6, Bucket::ID(..) => 6,
Bucket::Root => 7,
} }
} }
@ -689,6 +753,13 @@ fn specific_bucket_for<'a>(
Bucket::Universal Bucket::Universal
} }
}, },
Component::NonTSPseudoClass(ref pseudo_class)
if pseudo_class
.state_flag()
.intersects(RARE_PSEUDO_CLASS_STATES) =>
{
Bucket::RarePseudoClasses
},
_ => Bucket::Universal, _ => Bucket::Universal,
} }
} }