From c283b329919c7ad1bb613510adf4326e9ca44dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Thu, 9 Jun 2022 12:16:26 +0000 Subject: [PATCH] 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 --- .../element/state_and_attributes.rs | 12 +-- components/style/selector_map.rs | 89 +++++++++++++++++-- 2 files changed, 86 insertions(+), 15 deletions(-) diff --git a/components/style/invalidation/element/state_and_attributes.rs b/components/style/invalidation/element/state_and_attributes.rs index fc3f0bb064a..a2412171622 100644 --- a/components/style/invalidation/element/state_and_attributes.rs +++ b/components/style/invalidation/element/state_and_attributes.rs @@ -407,24 +407,24 @@ where } }); - let state_changes = self.state_changes; - if !state_changes.is_empty() { - self.collect_state_dependencies(&map.state_affecting_selectors, state_changes) - } + self.collect_state_dependencies(&map.state_affecting_selectors) } fn collect_state_dependencies( &mut self, map: &'selectors SelectorMap, - state_changes: ElementState, ) { + if self.state_changes.is_empty() { + return; + } map.lookup_with_additional( self.lookup_element, self.matching_context.quirks_mode(), self.removed_id, self.classes_removed, + self.state_changes, |dependency| { - if !dependency.state.intersects(state_changes) { + if !dependency.state.intersects(self.state_changes) { return true; } self.scan_dependency(&dependency.dep); diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index b2cd8d918ca..1ddd3928364 100644 --- a/components/style/selector_map.rs +++ b/components/style/selector_map.rs @@ -10,9 +10,10 @@ use crate::context::QuirksMode; use crate::dom::TElement; use crate::rule_tree::CascadeLevel; use crate::selector_parser::SelectorImpl; -use crate::stylist::{Stylist, CascadeData, Rule, ContainerConditionId}; +use crate::stylist::{CascadeData, ContainerConditionId, Rule, Stylist}; use crate::AllocErr; use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; +use dom::ElementState; use precomputed_hash::PrecomputedHash; use selectors::matching::{matches_selector, MatchingContext}; 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. pub type PrecomputedHashMap = HashMap>; @@ -107,6 +124,8 @@ pub struct SelectorMap { pub attribute_hash: PrecomputedHashMap>, /// A hash from namespace to rules which contain that namespace selector. pub namespace_hash: PrecomputedHashMap>, + /// Rules for pseudo-states that are rare but have global selectors. + pub rare_pseudo_classes: SmallVec<[T; 1]>, /// All other rules. pub other: SmallVec<[T; 1]>, /// Whether we should bucket by attribute names. @@ -132,6 +151,7 @@ impl SelectorMap { attribute_hash: HashMap::default(), local_name_hash: HashMap::default(), namespace_hash: HashMap::default(), + rare_pseudo_classes: SmallVec::new(), other: SmallVec::new(), #[cfg(feature = "gecko")] bucket_attributes: static_prefs::pref!("layout.css.bucket-attribute-names.enabled"), @@ -166,6 +186,7 @@ impl SelectorMap { self.attribute_hash.clear(); self.local_name_hash.clear(); self.namespace_hash.clear(); + self.rare_pseudo_classes.clear(); self.other.clear(); self.count = 0; } @@ -272,6 +293,18 @@ impl SelectorMap { ) } + 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()) { SelectorMap::get_matching_rules( element, @@ -385,6 +418,7 @@ impl SelectorMap { self.namespace_hash.try_reserve(1)?; self.namespace_hash.entry(url.clone()).or_default() }, + Bucket::RarePseudoClasses => &mut self.rare_pseudo_classes, Bucket::Universal => &mut self.other, }; vec.try_reserve(1)?; @@ -443,8 +477,22 @@ impl SelectorMap { /// /// FIXME(bholley) This overlaps with SelectorMap::get_all_matching_rules, /// 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] - 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 E: TElement, F: FnMut(&'a T) -> bool, @@ -522,6 +570,14 @@ impl SelectorMap { } } + 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() { if !f(&entry) { return false; @@ -545,6 +601,7 @@ impl SelectorMap { quirks_mode: QuirksMode, additional_id: Option<&WeakAtom>, additional_classes: &[Atom], + additional_states: ElementState, mut f: F, ) -> bool where @@ -552,7 +609,12 @@ impl SelectorMap { F: FnMut(&'a T) -> bool, { // 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; } @@ -585,6 +647,7 @@ impl SelectorMap { enum Bucket<'a> { Universal, Namespace(&'a Namespace), + RarePseudoClasses, LocalName { name: &'a LocalName, lower_name: &'a LocalName, @@ -599,17 +662,18 @@ enum Bucket<'a> { } impl<'a> Bucket<'a> { - /// root > id > class > local name > namespace > universal. + /// root > id > class > local name > namespace > pseudo-classes > universal. #[inline] fn specificity(&self) -> usize { match *self { Bucket::Universal => 0, Bucket::Namespace(..) => 1, - Bucket::LocalName { .. } => 2, - Bucket::Attribute { .. } => 3, - Bucket::Class(..) => 4, - Bucket::ID(..) => 5, - Bucket::Root => 6, + Bucket::RarePseudoClasses => 2, + Bucket::LocalName { .. } => 3, + Bucket::Attribute { .. } => 4, + Bucket::Class(..) => 5, + Bucket::ID(..) => 6, + Bucket::Root => 7, } } @@ -689,6 +753,13 @@ fn specific_bucket_for<'a>( Bucket::Universal } }, + Component::NonTSPseudoClass(ref pseudo_class) + if pseudo_class + .state_flag() + .intersects(RARE_PSEUDO_CLASS_STATES) => + { + Bucket::RarePseudoClasses + }, _ => Bucket::Universal, } }