/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Code for invalidations due to state or attribute changes. use crate::context::QuirksMode; use crate::selector_map::{ MaybeCaseInsensitiveHashMap, PrecomputedHashMap, SelectorMap, SelectorMapEntry, }; use crate::selector_parser::{NonTSPseudoClass, SelectorImpl}; use crate::values::AtomIdent; use crate::AllocErr; use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded}; use dom::{DocumentState, ElementState}; use selectors::attr::NamespaceConstraint; use selectors::parser::{ Combinator, Component, RelativeSelector, RelativeSelectorCombinatorCount, RelativeSelectorMatchHint, }; use selectors::parser::{Selector, SelectorIter}; use selectors::visitor::{SelectorListKind, SelectorVisitor}; use servo_arc::ThinArc; use smallvec::SmallVec; /// Mapping between (partial) CompoundSelectors (and the combinator to their /// right) and the states and attributes they depend on. /// /// In general, for all selectors in all applicable stylesheets of the form: /// /// |a _ b _ c _ d _ e| /// /// Where: /// * |b| and |d| are simple selectors that depend on state (like :hover) or /// attributes (like [attr...], .foo, or #foo). /// * |a|, |c|, and |e| are arbitrary simple selectors that do not depend on /// state or attributes. /// /// We generate a Dependency for both |a _ b:X _| and |a _ b:X _ c _ d:Y _|, /// even though those selectors may not appear on their own in any stylesheet. /// This allows us to quickly scan through the dependency sites of all style /// rules and determine the maximum effect that a given state or attribute /// change may have on the style of elements in the document. #[derive(Clone, Debug, MallocSizeOf)] pub struct Dependency { /// The dependency selector. #[ignore_malloc_size_of = "CssRules have primary refs, we measure there"] pub selector: Selector, /// The offset into the selector that we should match on. pub selector_offset: usize, /// The next dependency for a selector chain. For example, consider /// the following: /// /// .foo .bar:where(.baz span) .qux /// ^ ^ ^ /// A B C /// /// We'd generate: /// /// * One dependency for .qux (offset: 0, next: None) /// * One dependency for .baz pointing to B with next being a /// dependency pointing to C. /// * One dependency from .bar pointing to C (next: None) /// * One dependency from .foo pointing to A (next: None) /// /// Scope blocks can add multiple next entries: e.g. With /// @scope (.a) { .b {/*...*/ } .c { /*...*/ }} /// .a's Dependency would have two entries, for .b and .c. #[ignore_malloc_size_of = "Arc"] pub next: Option>, /// What kind of selector invalidation this generates. kind: DependencyInvalidationKind, } impl SelectorMapEntry for Dependency { fn selector(&self) -> SelectorIter<'_, SelectorImpl> { self.selector.iter_from(self.selector_offset) } } /// The kind of elements down the tree this dependency may affect. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, MallocSizeOf)] pub enum NormalDependencyInvalidationKind { /// This dependency may affect the element that changed itself. Element, /// This dependency affects the style of the element itself, and also the /// style of its descendants. /// /// TODO(emilio): Each time this feels more of a hack for eager pseudos... ElementAndDescendants, /// This dependency may affect descendants down the tree. Descendants, /// This dependency may affect siblings to the right of the element that /// changed. Siblings, /// This dependency may affect slotted elements of the element that changed. SlottedElements, /// This dependency may affect parts of the element that changed. Parts, } /// The kind of elements up the tree this relative selector dependency may /// affect. Because this travels upwards, it's not viable for parallel subtree /// traversal, and is handled separately. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, MallocSizeOf)] pub enum RelativeDependencyInvalidationKind { /// This dependency may affect relative selector anchors for ancestors. Ancestors, /// This dependency may affect a relative selector anchor for the parent. Parent, /// This dependency may affect a relative selector anchor for the previous sibling. PrevSibling, /// This dependency may affect relative selector anchors for ancestors' previous siblings. AncestorPrevSibling, /// This dependency may affect relative selector anchors for earlier siblings. EarlierSibling, /// This dependency may affect relative selector anchors for ancestors' earlier siblings. AncestorEarlierSibling, } /// The kind of invalidation the subject of this dependency triggers. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, MallocSizeOf)] pub enum ScopeDependencyInvalidationKind { /// This dependency's subject is an explicit scope root ExplicitScope, /// This dependency's subject is an implicit scope root ImplicitScope, /// This dependency's subject is an end scope condition ScopeEnd, } /// Invalidation kind merging normal and relative dependencies. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, MallocSizeOf)] pub enum DependencyInvalidationKind { /// This dependency is for full selector invalidation. /// It is assuumed that there will be no next dependency to look for. FullSelector, /// This dependency is a normal dependency. Normal(NormalDependencyInvalidationKind), /// This dependency is a relative dependency. Relative(RelativeDependencyInvalidationKind), /// This dependency is a scope dependency. Scope(ScopeDependencyInvalidationKind), } /// The type of invalidation a non-relative selector can generate. #[derive(Clone, Copy, Debug, MallocSizeOf)] pub enum GeneratedInvalidation<'a> { /// Generates a normal invalidation. Normal, /// Generates a scope invalidation. Scope(Option<&'a ThinArc<(), Dependency>>), } /// Return the type of normal invalidation given a selector & an offset. #[inline(always)] fn get_non_relative_invalidation_kind( selector: &Selector, selector_offset: usize, scope_kind: Option, ) -> DependencyInvalidationKind { if let Some(kind) = scope_kind { return DependencyInvalidationKind::Scope(kind); } if selector_offset == 0 { return DependencyInvalidationKind::Normal(NormalDependencyInvalidationKind::Element); } let combinator = selector.combinator_at_match_order(selector_offset - 1); DependencyInvalidationKind::Normal(match combinator { Combinator::Child | Combinator::Descendant => NormalDependencyInvalidationKind::Descendants, Combinator::LaterSibling | Combinator::NextSibling => { NormalDependencyInvalidationKind::Siblings }, Combinator::PseudoElement => NormalDependencyInvalidationKind::ElementAndDescendants, Combinator::SlotAssignment => NormalDependencyInvalidationKind::SlottedElements, Combinator::Part => NormalDependencyInvalidationKind::Parts, }) } impl Dependency { /// Generate a new dependency pub fn new( selector: Selector, selector_offset: usize, next: Option>, kind: DependencyInvalidationKind, ) -> Self { Self { selector, selector_offset, next, kind, } } /// Creates a dummy dependency to invalidate the whole selector. /// /// This is necessary because document state invalidation wants to /// invalidate all elements in the document. /// /// The offset is such as that Invalidation::new(self) returns a zero /// offset. That is, it points to a virtual "combinator" outside of the /// selector, so calling combinator() on such a dependency will panic. pub fn for_full_selector_invalidation(selector: Selector) -> Self { Self { selector_offset: selector.len() + 1, selector, next: None, kind: DependencyInvalidationKind::FullSelector, } } /// The kind of normal invalidation that this would generate. The dependency /// in question must be a normal dependency. pub fn normal_invalidation_kind(&self) -> NormalDependencyInvalidationKind { if let DependencyInvalidationKind::Normal(kind) = self.kind { return kind; } unreachable!("Querying normal invalidation kind on non-normal dependency."); } /// The kind of relative invalidation that this would generate. The dependency /// in question must be a relative dependency. #[inline(always)] pub fn relative_invalidation_kind(&self) -> RelativeDependencyInvalidationKind { if let DependencyInvalidationKind::Relative(kind) = self.kind { return kind; } unreachable!("Querying relative invalidation kind on non-relative dependency."); } /// The kind of invalidation that this would generate. pub fn invalidation_kind(&self) -> DependencyInvalidationKind { self.kind } /// Is the combinator to the right of this dependency's compound selector /// the next sibling combinator? This matters for insertion/removal in between /// two elements connected through next sibling, e.g. `.foo:has(> .a + .b)` /// where an element gets inserted between `.a` and `.b`. pub fn right_combinator_is_next_sibling(&self) -> bool { if self.selector_offset == 0 { return false; } matches!( self.selector .combinator_at_match_order(self.selector_offset - 1), Combinator::NextSibling ) } /// Is this dependency's compound selector a single compound in `:has` /// with the next sibling relative combinator i.e. `:has(> .foo)`? /// This matters for insertion between an anchor and an element /// connected through next sibling, e.g. `.a:has(> .b)`. pub fn dependency_is_relative_with_single_next_sibling(&self) -> bool { match self.invalidation_kind() { DependencyInvalidationKind::Relative(kind) => { kind == RelativeDependencyInvalidationKind::PrevSibling }, _ => false, } } } /// The same, but for state selectors, which can track more exactly what state /// do they track. #[derive(Clone, Debug, MallocSizeOf)] pub struct StateDependency { /// The other dependency fields. pub dep: Dependency, /// The state this dependency is affected by. pub state: ElementState, } impl SelectorMapEntry for StateDependency { fn selector(&self) -> SelectorIter<'_, SelectorImpl> { self.dep.selector() } } /// The same, but for document state selectors. #[derive(Clone, Debug, MallocSizeOf)] pub struct DocumentStateDependency { /// We track `Dependency` even though we don't need to track an offset, /// since when it changes it changes for the whole document anyway. #[cfg_attr( feature = "gecko", ignore_malloc_size_of = "CssRules have primary refs, we measure there" )] #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] pub dependency: Dependency, /// The state this dependency is affected by. pub state: DocumentState, } /// Dependency mapping for classes or IDs. pub type IdOrClassDependencyMap = MaybeCaseInsensitiveHashMap>; /// Dependency mapping for non-tree-strctural pseudo-class states. pub type StateDependencyMap = SelectorMap; /// Dependency mapping for local names. pub type LocalNameDependencyMap = PrecomputedHashMap>; /// Dependency mapping for customstates pub type CustomStateDependencyMap = PrecomputedHashMap>; /// A map where we store invalidations. /// /// This is slightly different to a SelectorMap, in the sense of that the same /// selector may appear multiple times. /// /// In particular, we want to lookup as few things as possible to get the fewer /// selectors the better, so this looks up by id, class, or looks at the list of /// state/other attribute affecting selectors. #[derive(Clone, Debug, MallocSizeOf)] pub struct InvalidationMap { /// A map from a given class name to all the selectors with that class /// selector. pub class_to_selector: IdOrClassDependencyMap, /// A map from a given id to all the selectors with that ID in the /// stylesheets currently applying to the document. pub id_to_selector: IdOrClassDependencyMap, /// A map of all the state dependencies. pub state_affecting_selectors: StateDependencyMap, /// A list of document state dependencies in the rules we represent. pub document_state_selectors: Vec, /// A map of other attribute affecting selectors. pub other_attribute_affecting_selectors: LocalNameDependencyMap, /// A map of CSS custom states pub custom_state_affecting_selectors: CustomStateDependencyMap, } /// Tree-structural pseudoclasses that we care about for (Relative selector) invalidation. /// Specifically, we need to store information on ones that don't generate the inner selector. /// Given the nature of these selectors: /// * These are only relevant during DOM mutation invalidations /// * Some invalidations may be optimized away. #[derive(Clone, Copy, Debug, MallocSizeOf)] pub struct TSStateForInvalidation(u8); bitflags! { impl TSStateForInvalidation : u8 { /// :empty. This only needs to be considered for DOM mutation, and for /// elements that do not have any children. const EMPTY = 1 << 0; /// :nth and related selectors, without of. const NTH = 1 << 1; /// :first-child. This only needs to be considered for DOM mutation, and /// for elements that have no previous sibling. const NTH_EDGE_FIRST = 1 << 2; /// :last-child. This only needs to be considered for DOM mutation, /// and for elements have no next sibling. const NTH_EDGE_LAST = 1 << 3; } } impl TSStateForInvalidation { /// Return true if this state invalidation could be skipped (As per comment /// in the definition of this bitflags) pub fn may_be_optimized(&self) -> bool { (Self::EMPTY | Self::NTH_EDGE_FIRST | Self::NTH_EDGE_LAST).contains(*self) } } /// Dependency for tree-structural pseudo-classes. #[derive(Clone, Debug, MallocSizeOf)] pub struct TSStateDependency { /// The other dependency fields. pub dep: Dependency, /// The state this dependency is affected by. pub state: TSStateForInvalidation, } impl SelectorMapEntry for TSStateDependency { fn selector(&self) -> SelectorIter<'_, SelectorImpl> { self.dep.selector() } } /// Dependency mapping for tree-structural pseudo-class states. pub type TSStateDependencyMap = SelectorMap; /// Dependency mapping for * selectors. pub type AnyDependencyMap = SmallVec<[Dependency; 1]>; /// A map to store invalidation dependencies specific to relative selectors. /// This keeps a lot more data than the usual map, because any change can generate /// upward traversals that need to be handled separately. #[derive(Clone, Debug, MallocSizeOf)] pub struct AdditionalRelativeSelectorInvalidationMap { /// A map for a given tree-structural pseudo-class to all the relative selector dependencies with that type. pub ts_state_to_selector: TSStateDependencyMap, /// A map from a given type name to all the relative selector dependencies with that type. pub type_to_selector: LocalNameDependencyMap, /// All relative selector dependencies that specify `*`. pub any_to_selector: AnyDependencyMap, /// Flag indicating if any relative selector is used. pub used: bool, /// Flag indicating if invalidating a relative selector requires ancestor traversal. pub needs_ancestors_traversal: bool, } impl AdditionalRelativeSelectorInvalidationMap { /// Creates an empty `InvalidationMap`. pub fn new() -> Self { Self { ts_state_to_selector: TSStateDependencyMap::default(), type_to_selector: LocalNameDependencyMap::default(), any_to_selector: SmallVec::default(), used: false, needs_ancestors_traversal: false, } } /// Clears this map, leaving it empty. pub fn clear(&mut self) { self.ts_state_to_selector.clear(); self.type_to_selector.clear(); self.any_to_selector.clear(); } /// Shrink the capacity of hash maps if needed. pub fn shrink_if_needed(&mut self) { self.ts_state_to_selector.shrink_if_needed(); self.type_to_selector.shrink_if_needed(); } } impl InvalidationMap { /// Creates an empty `InvalidationMap`. pub fn new() -> Self { Self { class_to_selector: IdOrClassDependencyMap::new(), id_to_selector: IdOrClassDependencyMap::new(), state_affecting_selectors: StateDependencyMap::new(), document_state_selectors: Vec::new(), other_attribute_affecting_selectors: LocalNameDependencyMap::default(), custom_state_affecting_selectors: CustomStateDependencyMap::default(), } } /// Returns the number of dependencies stored in the invalidation map. pub fn len(&self) -> usize { self.state_affecting_selectors.len() + self.document_state_selectors.len() + self .other_attribute_affecting_selectors .iter() .fold(0, |accum, (_, ref v)| accum + v.len()) + self .id_to_selector .iter() .fold(0, |accum, (_, ref v)| accum + v.len()) + self .class_to_selector .iter() .fold(0, |accum, (_, ref v)| accum + v.len()) + self .custom_state_affecting_selectors .iter() .fold(0, |accum, (_, ref v)| accum + v.len()) } /// Clears this map, leaving it empty. pub fn clear(&mut self) { self.class_to_selector.clear(); self.id_to_selector.clear(); self.state_affecting_selectors.clear(); self.document_state_selectors.clear(); self.other_attribute_affecting_selectors.clear(); self.custom_state_affecting_selectors.clear(); } /// Shrink the capacity of hash maps if needed. pub fn shrink_if_needed(&mut self) { self.class_to_selector.shrink_if_needed(); self.id_to_selector.shrink_if_needed(); self.state_affecting_selectors.shrink_if_needed(); self.other_attribute_affecting_selectors.shrink_if_needed(); self.custom_state_affecting_selectors.shrink_if_needed(); } } /// Adds a selector to the given `InvalidationMap`. Returns Err(..) to signify OOM. pub fn note_selector_for_invalidation( selector: &Selector, quirks_mode: QuirksMode, map: &mut InvalidationMap, relative_selector_invalidation_map: &mut InvalidationMap, additional_relative_selector_invalidation_map: &mut AdditionalRelativeSelectorInvalidationMap, inner_scope_dependencies: Option<&ThinArc<(), Dependency>>, scope_kind: Option, ) -> Result>, AllocErr> { let next_dependency = Dependency::for_full_selector_invalidation(selector.clone()); let mut document_state = DocumentState::empty(); let mut scope_dependencies = ScopeSelectorCollectorState { inner_dependencies: &inner_scope_dependencies.cloned(), this_dependencies: None, scope_kind, }; { let mut next_stack = NextSelectors::new(); let mut alloc_error = None; let mut collector = SelectorDependencyCollector { map, relative_selector_invalidation_map, additional_relative_selector_invalidation_map, document_state: &mut document_state, selector, next_selectors: &mut next_stack, quirks_mode, compound_state: PerCompoundState::new(0), relative_inner_collector: None, scope_dependencies: &mut scope_dependencies, alloc_error: &mut alloc_error, }; let visit_result = collector.visit_whole_selector(); debug_assert_eq!(!visit_result, alloc_error.is_some()); if let Some(alloc_error) = alloc_error { return Err(alloc_error); } } if !document_state.is_empty() { let dep = DocumentStateDependency { state: document_state, dependency: next_dependency, }; map.document_state_selectors.try_reserve(1)?; map.document_state_selectors.push(dep); } Ok(scope_dependencies.this_dependencies) } struct PerCompoundState { /// The offset at which our compound starts. offset: usize, /// The state this compound selector is affected by. element_state: ElementState, } impl PerCompoundState { fn new(offset: usize) -> Self { Self { offset, element_state: ElementState::empty(), } } } struct NextDependencyEntry { selector: Selector, offset: usize, cached_dependency: Option>, } struct RelativeSelectorInnerCollectorState<'a> { next_dependency: &'a ThinArc<(), Dependency>, relative_compound_state: RelativeSelectorCompoundStateAttributes, } struct ScopeSelectorCollectorState<'a> { // Inner scope dependencies this scope selector need to point to inner_dependencies: &'a Option>, // Scope dependencies added by this scope selector this_dependencies: Option>, // Whether this dependency is scope start, end, or other. scope_kind: Option, } trait Collector { fn dependency(&mut self) -> Dependency; fn id_map(&mut self) -> &mut IdOrClassDependencyMap; fn class_map(&mut self) -> &mut IdOrClassDependencyMap; fn state_map(&mut self) -> &mut StateDependencyMap; fn attribute_map(&mut self) -> &mut LocalNameDependencyMap; fn custom_state_map(&mut self) -> &mut CustomStateDependencyMap; fn inner_scope_dependencies(&self) -> Option>; fn this_scope_dependencies(&mut self) -> &mut Option>; fn update_states(&mut self, element_state: ElementState, document_state: DocumentState); // In normal invalidations, type-based dependencies don't need to be explicitly tracked; // elements don't change their types, and mutations cause invalidations to go descendant // (Where they are about to be styled anyway), and/or later-sibling direction (Where they // siblings after inserted/removed elements get restyled anyway). // However, for relative selectors, a DOM mutation can affect and arbitrary ancestor and/or // earlier siblings, so we need to keep track of them. fn type_map(&mut self) -> &mut LocalNameDependencyMap { unreachable!(); } // Tree-structural pseudo-selectors generally invalidates in a well-defined way, which are // handled by RestyleManager. However, for relative selectors, as with type invalidations, // the direction of invalidation becomes arbitrary, so we need to keep track of them. fn ts_state_map(&mut self) -> &mut TSStateDependencyMap { unreachable!(); } // Same story as type invalidation maps. fn any_vec(&mut self) -> &mut AnyDependencyMap { unreachable!(); } } fn on_attribute( local_name: &LocalName, local_name_lower: &LocalName, collector: &mut C, ) -> Result<(), AllocErr> { add_attr_dependency(local_name.clone(), collector)?; if local_name != local_name_lower { add_attr_dependency(local_name_lower.clone(), collector)?; } Ok(()) } fn on_id_or_class( s: &Component, quirks_mode: QuirksMode, collector: &mut C, ) -> Result<(), AllocErr> { let dependency = collector.dependency(); let (atom, map) = match *s { Component::ID(ref atom) => (atom, collector.id_map()), Component::Class(ref atom) => (atom, collector.class_map()), _ => unreachable!(), }; let entry = map.try_entry(atom.0.clone(), quirks_mode)?; let vec = entry.or_insert_with(SmallVec::new); vec.try_reserve(1)?; vec.push(dependency); Ok(()) } fn on_scope(collector: &mut C) -> Result<(), AllocErr> { let new_dependency = collector.dependency(); let this_scope_dependencies = collector.this_scope_dependencies(); this_scope_dependencies .get_or_insert(Vec::new()) .push(new_dependency); Ok(()) } fn add_attr_dependency(name: LocalName, collector: &mut C) -> Result<(), AllocErr> { let dependency = collector.dependency(); let map = collector.attribute_map(); add_local_name(name, dependency, map) } fn add_custom_state_dependency( name: AtomIdent, collector: &mut C, ) -> Result<(), AllocErr> { let dependency = collector.dependency(); let map = collector.custom_state_map(); map.try_reserve(1)?; let vec = map.entry(name).or_default(); vec.try_reserve(1)?; vec.push(dependency); Ok(()) } fn add_local_name( name: LocalName, dependency: Dependency, map: &mut LocalNameDependencyMap, ) -> Result<(), AllocErr> { map.try_reserve(1)?; let vec = map.entry(name).or_default(); vec.try_reserve(1)?; vec.push(dependency); Ok(()) } fn on_pseudo_class(pc: &NonTSPseudoClass, collector: &mut C) -> Result<(), AllocErr> { collector.update_states(pc.state_flag(), pc.document_state_flag()); let attr_name = match *pc { #[cfg(feature = "gecko")] NonTSPseudoClass::MozTableBorderNonzero => local_name!("border"), #[cfg(feature = "gecko")] NonTSPseudoClass::MozSelectListBox => { // This depends on two attributes. add_attr_dependency(local_name!("multiple"), collector)?; return add_attr_dependency(local_name!("size"), collector); }, NonTSPseudoClass::Lang(..) => local_name!("lang"), NonTSPseudoClass::CustomState(ref name) => { return add_custom_state_dependency(name.0.clone(), collector); }, _ => return Ok(()), }; add_attr_dependency(attr_name, collector) } fn add_pseudo_class_dependency( element_state: ElementState, quirks_mode: QuirksMode, collector: &mut C, ) -> Result<(), AllocErr> { if element_state.is_empty() { return Ok(()); } let dependency = collector.dependency(); collector.state_map().insert( StateDependency { dep: dependency, state: element_state, }, quirks_mode, ) } /// Visit all the simple selectors in the iter compound /// and return the number of simple selectors visited. /// We need to return a tuple because we need to keep /// track of two things: /// 1) Should the traversal continue and /// 2) What the offset of the compound state is. fn visit_all_in_iter_compound>( visitor: &mut T, iter: &mut SelectorIter<'_, SelectorImpl>, ) -> (bool, usize) { let mut index = 0; for ss in iter { if !ss.visit(visitor) { return (false, index); } index += 1; } (true, index) } type NextSelectors = SmallVec<[NextDependencyEntry; 5]>; /// A struct that collects invalidations for a given compound selector. struct SelectorDependencyCollector<'a, 'b, 'c> { map: &'a mut InvalidationMap, relative_selector_invalidation_map: &'a mut InvalidationMap, additional_relative_selector_invalidation_map: &'a mut AdditionalRelativeSelectorInvalidationMap, /// The document this _complex_ selector is affected by. /// /// We don't need to track state per compound selector, since it's global /// state and it changes for everything. document_state: &'a mut DocumentState, /// The current selector and offset we're iterating. selector: &'a Selector, /// The stack of next selectors that we have, and at which offset of the /// sequence. /// /// This starts empty. It grows when we find nested :is and :where selector /// lists. The dependency field is cached and reference counted. next_selectors: &'a mut NextSelectors, /// The quirks mode of the document where we're inserting dependencies. quirks_mode: QuirksMode, /// State relevant to a given compound selector. compound_state: PerCompoundState, /// Additional state to keep track of for collecting nested inner selectors of relative selectors /// Holds the next relative selector dependency and the state given to a relative selector. relative_inner_collector: Option>, scope_dependencies: &'a mut ScopeSelectorCollectorState<'c>, /// The allocation error, if we OOM. alloc_error: &'a mut Option, } fn next_dependency( next_selector: &mut NextSelectors, next_outer_dependency: Option<&ThinArc<(), Dependency>>, next_scope_dependencies: Option<&ThinArc<(), Dependency>>, scope_kind: Option, ) -> Option> { if next_selector.is_empty() { return match next_outer_dependency { Some(..) => next_outer_dependency.cloned(), None => next_scope_dependencies.cloned(), }; } fn dependencies_from( entries: &mut [NextDependencyEntry], next_outer_dependency: &Option<&ThinArc<(), Dependency>>, next_scope_dependencies: &Option<&ThinArc<(), Dependency>>, scope_kind: Option, ) -> Option> { if entries.is_empty() { return next_scope_dependencies.cloned(); } let last_index = entries.len() - 1; let (previous, last) = entries.split_at_mut(last_index); let last = &mut last[0]; let selector = &last.selector; let selector_offset = last.offset; let dependency = Dependency { selector: selector.clone(), selector_offset, next: dependencies_from( previous, next_outer_dependency, next_scope_dependencies, scope_kind, ), kind: get_non_relative_invalidation_kind( selector, selector_offset, next_scope_dependencies .is_some() .then(|| scope_kind) .flatten(), ), }; Some( last.cached_dependency .get_or_insert_with(|| ThinArc::from_header_and_iter((), [dependency].into_iter())) .clone(), ) } dependencies_from( next_selector, &next_outer_dependency, &next_scope_dependencies, scope_kind, ) } impl<'a, 'b, 'c> Collector for SelectorDependencyCollector<'a, 'b, 'c> { fn dependency(&mut self) -> Dependency { let optional_dependency = self .relative_inner_collector .as_ref() .map(|collector| collector.next_dependency); let offset = self.compound_state.offset; let scope_dependencies = self.inner_scope_dependencies(); let next = next_dependency( self.next_selectors, optional_dependency, scope_dependencies.as_ref(), self.scope_dependencies.scope_kind, ); Dependency { selector: self.selector.clone(), selector_offset: offset, next: next, kind: get_non_relative_invalidation_kind( self.selector, offset, scope_dependencies .is_some() .then(|| self.scope_dependencies.scope_kind) .flatten(), ), } } fn id_map(&mut self) -> &mut IdOrClassDependencyMap { if self.relative_inner_collector.is_none() { &mut self.map.id_to_selector } else { &mut self.relative_selector_invalidation_map.id_to_selector } } fn class_map(&mut self) -> &mut IdOrClassDependencyMap { if self.relative_inner_collector.is_none() { &mut self.map.class_to_selector } else { &mut self.relative_selector_invalidation_map.class_to_selector } } fn state_map(&mut self) -> &mut StateDependencyMap { if self.relative_inner_collector.is_none() { &mut self.map.state_affecting_selectors } else { &mut self .relative_selector_invalidation_map .state_affecting_selectors } } fn attribute_map(&mut self) -> &mut LocalNameDependencyMap { if self.relative_inner_collector.is_none() { &mut self.map.other_attribute_affecting_selectors } else { &mut self .relative_selector_invalidation_map .other_attribute_affecting_selectors } } fn inner_scope_dependencies(&self) -> Option> { self.scope_dependencies.inner_dependencies.clone() } fn this_scope_dependencies(&mut self) -> &mut Option> { &mut self.scope_dependencies.this_dependencies } fn update_states(&mut self, element_state: ElementState, document_state: DocumentState) { self.compound_state.element_state |= element_state; *self.document_state |= document_state; } fn custom_state_map(&mut self) -> &mut CustomStateDependencyMap { if self.relative_inner_collector.is_none() { &mut self.map.custom_state_affecting_selectors } else { &mut self .relative_selector_invalidation_map .custom_state_affecting_selectors } } fn type_map(&mut self) -> &mut LocalNameDependencyMap { debug_assert!( self.relative_inner_collector.is_some(), "Asking for relative selector invalidation outside of relative selector" ); &mut self .additional_relative_selector_invalidation_map .type_to_selector } fn ts_state_map(&mut self) -> &mut TSStateDependencyMap { debug_assert!( self.relative_inner_collector.is_some(), "Asking for relative selector invalidation outside of relative selector" ); &mut self .additional_relative_selector_invalidation_map .ts_state_to_selector } fn any_vec(&mut self) -> &mut AnyDependencyMap { debug_assert!( self.relative_inner_collector.is_some(), "Asking for relative selector invalidation outside of relative selector" ); &mut self .additional_relative_selector_invalidation_map .any_to_selector } } impl<'a, 'b, 'c> SelectorDependencyCollector<'a, 'b, 'c> { fn visit_whole_selector(&mut self) -> bool { let iter = self.selector.iter(); self.visit_whole_selector_from(iter, 0) } fn visit_whole_selector_from( &mut self, mut iter: SelectorIter, mut index: usize, ) -> bool { loop { // Reset the compound state. self.compound_state = PerCompoundState::new(index); if let Some(state) = self.relative_inner_collector.as_mut() { state.relative_compound_state = RelativeSelectorCompoundStateAttributes::new(); } // Visit all the simple selectors in this sequence. let (keep_traversing, index_offset) = visit_all_in_iter_compound(self, &mut iter); if !keep_traversing { return false; } index += index_offset; if let Err(err) = add_pseudo_class_dependency( self.compound_state.element_state, self.quirks_mode, self, ) { *self.alloc_error = Some(err); return false; } if let Some(state) = self .relative_inner_collector .as_ref() .map(|state| state.relative_compound_state) { if let Err(err) = add_ts_pseudo_class_dependency(state.ts_state, self.quirks_mode, self) { *self.alloc_error = Some(err); return false; } if !state.added_entry { // Not great - we didn't add any uniquely identifiable information. if let Err(err) = add_non_unique_info(&self.selector, self.compound_state.offset, self) { *self.alloc_error = Some(err); return false; } } } let combinator = iter.next_sequence(); if combinator.is_none() { return true; } index += 1; // account for the combinator } } } impl<'a, 'b, 'c> SelectorVisitor for SelectorDependencyCollector<'a, 'b, 'c> { type Impl = SelectorImpl; fn visit_selector_list( &mut self, _list_kind: SelectorListKind, list: &[Selector], ) -> bool { let next_relative_dependency = self .relative_inner_collector .is_some() .then(|| ThinArc::from_header_and_iter((), std::iter::once(self.dependency()))); for selector in list { // Here we cheat a bit: We can visit the rightmost compound with // the "outer" visitor, and it'd be fine. This reduces the amount of // state and attribute invalidations, and we need to check the outer // selector to the left anyway to avoid over-invalidation, so it // avoids matching it twice uselessly. let mut iter = selector.iter(); let saved_added_entry = self .relative_inner_collector .as_ref() .map(|state| state.relative_compound_state.added_entry); let (keep_traversing, mut index) = visit_all_in_iter_compound(self, &mut iter); if !keep_traversing { return false; } if let Some(state) = self.relative_inner_collector.as_mut() { state.relative_compound_state.added_entry = saved_added_entry.unwrap_or_default(); } let combinator = iter.next_sequence(); if combinator.is_none() { continue; } index += 1; // account for the combinator. let offset = self.compound_state.offset; if self.relative_inner_collector.is_none() { self.next_selectors.push(NextDependencyEntry { selector: self.selector.clone(), offset: offset, cached_dependency: None, }); } debug_assert!( next_relative_dependency.is_some() == self.relative_inner_collector.is_some(), "Next relative dependency and relative inner collector must be Some/None at the same time!" ); let mut nested = SelectorDependencyCollector { map: &mut *self.map, relative_selector_invalidation_map: &mut *self.relative_selector_invalidation_map, additional_relative_selector_invalidation_map: &mut *self .additional_relative_selector_invalidation_map, document_state: &mut *self.document_state, selector, next_selectors: &mut *self.next_selectors, quirks_mode: self.quirks_mode, compound_state: PerCompoundState::new(index), relative_inner_collector: next_relative_dependency.as_ref().map( |next_dependency| RelativeSelectorInnerCollectorState { next_dependency, relative_compound_state: RelativeSelectorCompoundStateAttributes::new(), }, ), scope_dependencies: &mut self.scope_dependencies, alloc_error: &mut *self.alloc_error, }; if !nested.visit_whole_selector_from(iter, index) { return false; } self.next_selectors.pop(); } true } fn visit_relative_selector_list( &mut self, list: &[selectors::parser::RelativeSelector], ) -> bool { // Ignore nested relative selectors. This can happen as a result of nesting. if self.relative_inner_collector.is_some() { return true; } self.additional_relative_selector_invalidation_map.used = true; for relative_selector in list { // We can't cheat here like we do with other selector lists - the rightmost // compound of a relative selector is not the subject of the invalidation. self.next_selectors.push(NextDependencyEntry { selector: self.selector.clone(), offset: self.compound_state.offset, cached_dependency: None, }); let mut nested = RelativeSelectorDependencyCollector { map: &mut *self.map, relative_selector_invalidation_map: &mut *self.relative_selector_invalidation_map, additional_relative_selector_invalidation_map: &mut *self .additional_relative_selector_invalidation_map, document_state: &mut *self.document_state, selector: &relative_selector, combinator_count: RelativeSelectorCombinatorCount::new(relative_selector), next_selectors: &mut *self.next_selectors, quirks_mode: self.quirks_mode, compound_state: PerCompoundState::new(0), compound_state_attributes: RelativeSelectorCompoundStateAttributes::new(), scope_dependencies: &mut self.scope_dependencies, alloc_error: &mut *self.alloc_error, }; if !nested.visit_whole_selector() { return false; } self.next_selectors.pop(); } true } fn visit_simple_selector(&mut self, s: &Component) -> bool { match on_simple_selector(s, self.quirks_mode, self) { Ok(result) => { if let ComponentVisitResult::Handled(state) = result { if let Some(inner_collector_state) = self.relative_inner_collector.as_mut() { inner_collector_state.relative_compound_state.added_entry = true; inner_collector_state .relative_compound_state .ts_state .insert(state); } } true }, Err(err) => { *self.alloc_error = Some(err.into()); false }, } } fn visit_attribute_selector( &mut self, _: &NamespaceConstraint<&Namespace>, local_name: &LocalName, local_name_lower: &LocalName, ) -> bool { if let Some(state) = self.relative_inner_collector.as_mut() { state.relative_compound_state.added_entry = true; } if let Err(err) = on_attribute(local_name, local_name_lower, self) { *self.alloc_error = Some(err); return false; } true } } #[derive(Clone, Copy)] struct RelativeSelectorCompoundStateAttributes { ts_state: TSStateForInvalidation, added_entry: bool, } impl RelativeSelectorCompoundStateAttributes { fn new() -> Self { Self { ts_state: TSStateForInvalidation::empty(), added_entry: false, } } } /// A struct that collects invalidations for a given compound selector. struct RelativeSelectorDependencyCollector<'a, 'b> { map: &'a mut InvalidationMap, relative_selector_invalidation_map: &'a mut InvalidationMap, additional_relative_selector_invalidation_map: &'a mut AdditionalRelativeSelectorInvalidationMap, /// The document this _complex_ selector is affected by. /// /// We don't need to track state per compound selector, since it's global /// state and it changes for everything. document_state: &'a mut DocumentState, /// The current inner relative selector and offset we're iterating. selector: &'a RelativeSelector, /// Running combinator for this inner relative selector. combinator_count: RelativeSelectorCombinatorCount, /// The stack of next selectors that we have, and at which offset of the /// sequence. /// /// This starts empty. It grows when we find nested :is and :where selector /// lists. The dependency field is cached and reference counted. next_selectors: &'a mut NextSelectors, /// The quirks mode of the document where we're inserting dependencies. quirks_mode: QuirksMode, /// State relevant to a given compound selector. compound_state: PerCompoundState, /// Attributes relevant to the relative compound selector state. compound_state_attributes: RelativeSelectorCompoundStateAttributes, scope_dependencies: &'a mut ScopeSelectorCollectorState<'b>, /// The allocation error, if we OOM. alloc_error: &'a mut Option, } fn add_non_unique_info( selector: &Selector, offset: usize, collector: &mut C, ) -> Result<(), AllocErr> { // Go through this compound again. for ss in selector.iter_from(offset) { match ss { Component::LocalName(ref name) => { let dependency = collector.dependency(); add_local_name(name.name.clone(), dependency, &mut collector.type_map())?; if name.name != name.lower_name { let dependency = collector.dependency(); add_local_name( name.lower_name.clone(), dependency, &mut collector.type_map(), )?; } return Ok(()); }, _ => (), }; } // Ouch. Add one for *. collector.any_vec().try_reserve(1)?; let dependency = collector.dependency(); collector.any_vec().push(dependency); Ok(()) } fn add_ts_pseudo_class_dependency( state: TSStateForInvalidation, quirks_mode: QuirksMode, collector: &mut C, ) -> Result<(), AllocErr> { if state.is_empty() { return Ok(()); } let dependency = collector.dependency(); collector.ts_state_map().insert( TSStateDependency { dep: dependency, state, }, quirks_mode, ) } impl<'a, 'b> RelativeSelectorDependencyCollector<'a, 'b> { fn visit_whole_selector(&mut self) -> bool { let mut iter = self.selector.selector.iter_skip_relative_selector_anchor(); let mut index = 0; self.additional_relative_selector_invalidation_map .needs_ancestors_traversal |= match self.selector.match_hint { RelativeSelectorMatchHint::InNextSiblingSubtree | RelativeSelectorMatchHint::InSiblingSubtree | RelativeSelectorMatchHint::InSubtree => true, _ => false, }; loop { // Reset the compound state. self.compound_state = PerCompoundState::new(index); let (keep_traversing, index_offset) = visit_all_in_iter_compound(self, &mut iter); if !keep_traversing { return false; } index += index_offset; if let Err(err) = add_pseudo_class_dependency( self.compound_state.element_state, self.quirks_mode, self, ) { *self.alloc_error = Some(err); return false; } if let Err(err) = add_ts_pseudo_class_dependency( self.compound_state_attributes.ts_state, self.quirks_mode, self, ) { *self.alloc_error = Some(err); return false; } if !self.compound_state_attributes.added_entry { // Not great - we didn't add any uniquely identifiable information. if let Err(err) = add_non_unique_info(&self.selector.selector, self.compound_state.offset, self) { *self.alloc_error = Some(err); return false; } } let combinator = iter.next_sequence(); if let Some(c) = combinator { match c { Combinator::Child | Combinator::Descendant => { self.combinator_count.child_or_descendants -= 1 }, Combinator::NextSibling | Combinator::LaterSibling => { self.combinator_count.adjacent_or_next_siblings -= 1 }, Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => (), } } else { return true; } index += 1; // account for the combinator } } } impl<'a, 'b> Collector for RelativeSelectorDependencyCollector<'a, 'b> { fn dependency(&mut self) -> Dependency { let scope_dependencies = self.inner_scope_dependencies(); let scope_kind = self.scope_dependencies.scope_kind; let next = next_dependency( self.next_selectors, None, scope_dependencies.as_ref(), scope_kind, ); debug_assert!( next.as_ref().is_some_and(|d| !matches!( d.slice()[0].kind, DependencyInvalidationKind::Relative(_) )), "Duplicate relative dependency?" ); debug_assert!( next.as_ref().is_some_and(|d| !d.slice().is_empty()), "Empty dependency?" ); Dependency { selector: self.selector.selector.clone(), selector_offset: self.compound_state.offset, kind: DependencyInvalidationKind::Relative( match self.combinator_count.get_match_hint() { RelativeSelectorMatchHint::InChild => { RelativeDependencyInvalidationKind::Parent }, RelativeSelectorMatchHint::InSubtree => { RelativeDependencyInvalidationKind::Ancestors }, RelativeSelectorMatchHint::InNextSibling => { RelativeDependencyInvalidationKind::PrevSibling }, RelativeSelectorMatchHint::InSibling => { RelativeDependencyInvalidationKind::EarlierSibling }, RelativeSelectorMatchHint::InNextSiblingSubtree => { RelativeDependencyInvalidationKind::AncestorPrevSibling }, RelativeSelectorMatchHint::InSiblingSubtree => { RelativeDependencyInvalidationKind::AncestorEarlierSibling }, }, ), next: next, } } fn id_map(&mut self) -> &mut IdOrClassDependencyMap { &mut self.relative_selector_invalidation_map.id_to_selector } fn class_map(&mut self) -> &mut IdOrClassDependencyMap { &mut self.relative_selector_invalidation_map.class_to_selector } fn state_map(&mut self) -> &mut StateDependencyMap { &mut self .relative_selector_invalidation_map .state_affecting_selectors } fn attribute_map(&mut self) -> &mut LocalNameDependencyMap { &mut self .relative_selector_invalidation_map .other_attribute_affecting_selectors } fn custom_state_map(&mut self) -> &mut CustomStateDependencyMap { &mut self .relative_selector_invalidation_map .custom_state_affecting_selectors } fn inner_scope_dependencies(&self) -> Option> { self.scope_dependencies.inner_dependencies.clone() } fn this_scope_dependencies(&mut self) -> &mut Option> { &mut self.scope_dependencies.this_dependencies } fn update_states(&mut self, element_state: ElementState, document_state: DocumentState) { self.compound_state.element_state |= element_state; *self.document_state |= document_state; } fn type_map(&mut self) -> &mut LocalNameDependencyMap { &mut self .additional_relative_selector_invalidation_map .type_to_selector } fn ts_state_map(&mut self) -> &mut TSStateDependencyMap { &mut self .additional_relative_selector_invalidation_map .ts_state_to_selector } fn any_vec(&mut self) -> &mut AnyDependencyMap { &mut self .additional_relative_selector_invalidation_map .any_to_selector } } enum ComponentVisitResult { /// This component is not relevant for building up the invalidation map. IsIrrelevant, /// This component has been added to the invalidation map. Any additional /// tree-structural pseudo-class dependency is also included, if required. Handled(TSStateForInvalidation), } #[inline(always)] fn on_simple_selector( s: &Component, quirks_mode: QuirksMode, collector: &mut C, ) -> Result { match *s { Component::ID(..) | Component::Class(..) => { on_id_or_class(s, quirks_mode, collector)?; Ok(ComponentVisitResult::Handled( TSStateForInvalidation::empty(), )) }, Component::ImplicitScope | Component::Scope => { on_scope(collector)?; Ok(ComponentVisitResult::Handled( TSStateForInvalidation::empty(), )) }, Component::NonTSPseudoClass(ref pc) => { on_pseudo_class(pc, collector)?; Ok(ComponentVisitResult::Handled( TSStateForInvalidation::empty(), )) }, Component::Empty => Ok(ComponentVisitResult::Handled(TSStateForInvalidation::EMPTY)), Component::Nth(data) => { let kind = if data.is_simple_edge() { if data.ty.is_from_end() { TSStateForInvalidation::NTH_EDGE_LAST } else { TSStateForInvalidation::NTH_EDGE_FIRST } } else { TSStateForInvalidation::NTH }; Ok(ComponentVisitResult::Handled(kind)) }, Component::RelativeSelectorAnchor => unreachable!("Should not visit this far"), _ => Ok(ComponentVisitResult::IsIrrelevant), } } impl<'a, 'b> SelectorVisitor for RelativeSelectorDependencyCollector<'a, 'b> { type Impl = SelectorImpl; fn visit_selector_list( &mut self, _list_kind: SelectorListKind, list: &[Selector], ) -> bool { let mut next_stack = NextSelectors::new(); let next_dependency = ThinArc::from_header_and_iter((), [self.dependency()].into_iter()); for selector in list { let mut iter = selector.iter(); let saved_added_entry = self.compound_state_attributes.added_entry; let (keep_traversing, mut index) = visit_all_in_iter_compound(self, &mut iter); if !keep_traversing { return false; } let combinator = iter.next_sequence(); // We want to preserve added_entry, to handle all DOM manipulations // correctly. For example, given `.anchor:has(:not(.foo))`, and a // DOM tree `.anchor > .foo`, insertion of _any_ element without // `.foo` as `.anchor`'s child must trigger an invalidation. self.compound_state_attributes.added_entry = saved_added_entry; if combinator.is_none() { continue; } index += 1; // account for the combinator. let mut nested = SelectorDependencyCollector { map: &mut *self.map, relative_selector_invalidation_map: &mut *self.relative_selector_invalidation_map, additional_relative_selector_invalidation_map: self .additional_relative_selector_invalidation_map, document_state: &mut *self.document_state, selector, next_selectors: &mut next_stack, quirks_mode: self.quirks_mode, compound_state: PerCompoundState::new(index), relative_inner_collector: Some(RelativeSelectorInnerCollectorState { next_dependency: &next_dependency, relative_compound_state: RelativeSelectorCompoundStateAttributes::new(), }), scope_dependencies: &mut self.scope_dependencies, alloc_error: &mut *self.alloc_error, }; if !nested.visit_whole_selector_from(iter, index) { return false; } } true } fn visit_relative_selector_list( &mut self, _list: &[selectors::parser::RelativeSelector], ) -> bool { // Ignore nested relative selectors. These can happen as a result of nesting. true } fn visit_simple_selector(&mut self, s: &Component) -> bool { match on_simple_selector(s, self.quirks_mode, self) { Ok(result) => { if let ComponentVisitResult::Handled(state) = result { self.compound_state_attributes.added_entry = true; self.compound_state_attributes.ts_state.insert(state); } true }, Err(err) => { *self.alloc_error = Some(err.into()); false }, } } fn visit_attribute_selector( &mut self, _: &NamespaceConstraint<&Namespace>, local_name: &LocalName, local_name_lower: &LocalName, ) -> bool { self.compound_state_attributes.added_entry = true; if let Err(err) = on_attribute(local_name, local_name_lower, self) { *self.alloc_error = Some(err); return false; } true } }