summaryrefslogtreecommitdiffstats
path: root/servo/components/style/invalidation/element/invalidation_map.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/invalidation/element/invalidation_map.rs')
-rw-r--r--servo/components/style/invalidation/element/invalidation_map.rs1425
1 files changed, 1425 insertions, 0 deletions
diff --git a/servo/components/style/invalidation/element/invalidation_map.rs b/servo/components/style/invalidation/element/invalidation_map.rs
new file mode 100644
index 0000000000..cb03862740
--- /dev/null
+++ b/servo/components/style/invalidation/element/invalidation_map.rs
@@ -0,0 +1,1425 @@
+/* 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::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::Arc;
+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<SelectorImpl>,
+
+ /// The offset into the selector that we should match on.
+ pub selector_offset: usize,
+
+ /// The parent dependency for an ancestor selector. For example, consider
+ /// the following:
+ ///
+ /// .foo .bar:where(.baz span) .qux
+ /// ^ ^ ^
+ /// A B C
+ ///
+ /// We'd generate:
+ ///
+ /// * One dependency for .qux (offset: 0, parent: None)
+ /// * One dependency for .baz pointing to B with parent being a
+ /// dependency pointing to C.
+ /// * One dependency from .bar pointing to C (parent: None)
+ /// * One dependency from .foo pointing to A (parent: None)
+ ///
+ #[ignore_malloc_size_of = "Arc"]
+ pub parent: Option<Arc<Dependency>>,
+
+ /// What kind of relative selector invalidation this generates.
+ /// None if this dependency is not within a relative selector.
+ relative_kind: Option<RelativeDependencyInvalidationKind>,
+}
+
+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,
+}
+
+/// Invalidation kind merging normal and relative dependencies.
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, MallocSizeOf)]
+pub enum DependencyInvalidationKind {
+ /// This dependency is a normal dependency.
+ Normal(NormalDependencyInvalidationKind),
+ /// This dependency is a relative dependency.
+ Relative(RelativeDependencyInvalidationKind),
+}
+
+impl Dependency {
+ /// 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<SelectorImpl>) -> Self {
+ Self {
+ selector_offset: selector.len() + 1,
+ selector,
+ parent: None,
+ relative_kind: None,
+ }
+ }
+
+ /// Returns the combinator to the right of the partial selector this
+ /// dependency represents.
+ ///
+ /// TODO(emilio): Consider storing inline if it helps cache locality?
+ fn combinator(&self) -> Option<Combinator> {
+ if self.selector_offset == 0 {
+ return None;
+ }
+
+ Some(
+ self.selector
+ .combinator_at_match_order(self.selector_offset - 1),
+ )
+ }
+
+ /// 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 {
+ debug_assert!(
+ self.relative_kind.is_none(),
+ "Querying normal invalidation kind on relative dependency."
+ );
+ match self.combinator() {
+ None => NormalDependencyInvalidationKind::Element,
+ Some(Combinator::Child) | Some(Combinator::Descendant) => {
+ NormalDependencyInvalidationKind::Descendants
+ },
+ Some(Combinator::LaterSibling) | Some(Combinator::NextSibling) => {
+ NormalDependencyInvalidationKind::Siblings
+ },
+ // TODO(emilio): We could look at the selector itself to see if it's
+ // an eager pseudo, and return only Descendants here if not.
+ Some(Combinator::PseudoElement) => {
+ NormalDependencyInvalidationKind::ElementAndDescendants
+ },
+ Some(Combinator::SlotAssignment) => NormalDependencyInvalidationKind::SlottedElements,
+ Some(Combinator::Part) => NormalDependencyInvalidationKind::Parts,
+ }
+ }
+
+ /// The kind of invalidation that this would generate.
+ pub fn invalidation_kind(&self) -> DependencyInvalidationKind {
+ if let Some(kind) = self.relative_kind {
+ return DependencyInvalidationKind::Relative(kind);
+ }
+ DependencyInvalidationKind::Normal(self.normal_invalidation_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::Normal(_) => false,
+ DependencyInvalidationKind::Relative(kind) => {
+ kind == RelativeDependencyInvalidationKind::PrevSibling
+ },
+ }
+ }
+}
+
+/// 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<Atom, SmallVec<[Dependency; 1]>>;
+/// Dependency mapping for non-tree-strctural pseudo-class states.
+pub type StateDependencyMap = SelectorMap<StateDependency>;
+/// Dependency mapping for local names.
+pub type LocalNameDependencyMap = PrecomputedHashMap<LocalName, SmallVec<[Dependency; 1]>>;
+
+/// 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<DocumentStateDependency>,
+ /// A map of other attribute affecting selectors.
+ pub other_attribute_affecting_selectors: LocalNameDependencyMap,
+}
+
+/// 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.
+#[derive(Clone, Copy, Debug, MallocSizeOf)]
+pub struct TSStateForInvalidation(u8);
+
+bitflags! {
+ impl TSStateForInvalidation : u8 {
+ /// :empty
+ const EMPTY = 1 << 0;
+ /// :nth etc, without of.
+ const NTH = 1 << 1;
+ /// "Simple" edge child selectors, like :first-child, :last-child, etc.
+ /// Excludes :*-of-type.
+ const NTH_EDGE = 1 << 2;
+ }
+}
+
+/// 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<TSStateDependency>;
+/// Dependency mapping for * selectors.
+pub type AnyDependencyMap = SmallVec<[Dependency; 1]>;
+
+/// A map to store all relative selector invalidations.
+/// 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 RelativeSelectorInvalidationMap {
+ /// Portion common to the normal invalidation map, except that this is for relative selectors and their inner selectors.
+ pub map: InvalidationMap,
+ /// 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 RelativeSelectorInvalidationMap {
+ /// Creates an empty `InvalidationMap`.
+ pub fn new() -> Self {
+ Self {
+ map: InvalidationMap::new(),
+ ts_state_to_selector: TSStateDependencyMap::default(),
+ type_to_selector: LocalNameDependencyMap::default(),
+ any_to_selector: SmallVec::default(),
+ used: false,
+ needs_ancestors_traversal: false,
+ }
+ }
+
+ /// Returns the number of dependencies stored in the invalidation map.
+ pub fn len(&self) -> usize {
+ self.map.len()
+ }
+
+ /// Clears this map, leaving it empty.
+ pub fn clear(&mut self) {
+ self.map.clear();
+ 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.map.shrink_if_needed();
+ 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(),
+ }
+ }
+
+ /// 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())
+ }
+
+ /// 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();
+ }
+
+ /// 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();
+ }
+}
+
+/// Adds a selector to the given `InvalidationMap`. Returns Err(..) to signify OOM.
+pub fn note_selector_for_invalidation(
+ selector: &Selector<SelectorImpl>,
+ quirks_mode: QuirksMode,
+ map: &mut InvalidationMap,
+ relative_selector_invalidation_map: &mut RelativeSelectorInvalidationMap,
+) -> Result<(), AllocErr> {
+ debug!("note_selector_for_invalidation({:?})", selector);
+
+ let mut document_state = DocumentState::empty();
+ {
+ let mut parent_stack = ParentSelectors::new();
+ let mut alloc_error = None;
+ let mut collector = SelectorDependencyCollector {
+ map,
+ relative_selector_invalidation_map,
+ document_state: &mut document_state,
+ selector,
+ parent_selectors: &mut parent_stack,
+ quirks_mode,
+ compound_state: PerCompoundState::new(0),
+ 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: Dependency::for_full_selector_invalidation(selector.clone()),
+ };
+ map.document_state_selectors.try_reserve(1)?;
+ map.document_state_selectors.push(dep);
+ }
+ Ok(())
+}
+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 ParentDependencyEntry {
+ selector: Selector<SelectorImpl>,
+ offset: usize,
+ cached_dependency: Option<Arc<Dependency>>,
+}
+
+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 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<C: Collector>(
+ 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<C: Collector>(
+ s: &Component<SelectorImpl>,
+ 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 add_attr_dependency<C: Collector>(name: LocalName, collector: &mut C) -> Result<(), AllocErr> {
+ let dependency = collector.dependency();
+ let map = collector.attribute_map();
+ add_local_name(name, dependency, map)
+}
+
+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<C: Collector>(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"),
+ _ => return Ok(()),
+ };
+
+ add_attr_dependency(attr_name, collector)
+}
+
+fn add_pseudo_class_dependency<C: Collector>(
+ 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,
+ )
+}
+
+type ParentSelectors = SmallVec<[ParentDependencyEntry; 5]>;
+
+/// A struct that collects invalidations for a given compound selector.
+struct SelectorDependencyCollector<'a> {
+ map: &'a mut InvalidationMap,
+ relative_selector_invalidation_map: &'a mut RelativeSelectorInvalidationMap,
+
+ /// 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<SelectorImpl>,
+
+ /// The stack of parent 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.
+ parent_selectors: &'a mut ParentSelectors,
+
+ /// The quirks mode of the document where we're inserting dependencies.
+ quirks_mode: QuirksMode,
+
+ /// State relevant to a given compound selector.
+ compound_state: PerCompoundState,
+
+ /// The allocation error, if we OOM.
+ alloc_error: &'a mut Option<AllocErr>,
+}
+
+fn parent_dependency(
+ parent_selectors: &mut ParentSelectors,
+ outer_parent: Option<&Arc<Dependency>>,
+) -> Option<Arc<Dependency>> {
+ if parent_selectors.is_empty() {
+ return outer_parent.cloned();
+ }
+
+ fn dependencies_from(
+ entries: &mut [ParentDependencyEntry],
+ outer_parent: &Option<&Arc<Dependency>>,
+ ) -> Option<Arc<Dependency>> {
+ if entries.is_empty() {
+ return None;
+ }
+
+ 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;
+ Some(
+ last.cached_dependency
+ .get_or_insert_with(|| {
+ Arc::new(Dependency {
+ selector: selector.clone(),
+ selector_offset,
+ parent: dependencies_from(previous, outer_parent),
+ relative_kind: None,
+ })
+ })
+ .clone(),
+ )
+ }
+
+ dependencies_from(parent_selectors, &outer_parent)
+}
+
+impl<'a> Collector for SelectorDependencyCollector<'a> {
+ fn dependency(&mut self) -> Dependency {
+ let parent = parent_dependency(self.parent_selectors, None);
+ Dependency {
+ selector: self.selector.clone(),
+ selector_offset: self.compound_state.offset,
+ parent,
+ relative_kind: None,
+ }
+ }
+
+ fn id_map(&mut self) -> &mut IdOrClassDependencyMap {
+ &mut self.map.id_to_selector
+ }
+
+ fn class_map(&mut self) -> &mut IdOrClassDependencyMap {
+ &mut self.map.class_to_selector
+ }
+
+ fn state_map(&mut self) -> &mut StateDependencyMap {
+ &mut self.map.state_affecting_selectors
+ }
+
+ fn attribute_map(&mut self) -> &mut LocalNameDependencyMap {
+ &mut self.map.other_attribute_affecting_selectors
+ }
+
+ fn update_states(&mut self, element_state: ElementState, document_state: DocumentState) {
+ self.compound_state.element_state |= element_state;
+ *self.document_state |= document_state;
+ }
+}
+
+impl<'a> SelectorDependencyCollector<'a> {
+ 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<SelectorImpl>,
+ mut index: usize,
+ ) -> bool {
+ loop {
+ // Reset the compound state.
+ self.compound_state = PerCompoundState::new(index);
+
+ // Visit all the simple selectors in this sequence.
+ for ss in &mut iter {
+ if !ss.visit(self) {
+ return false;
+ }
+ index += 1; // Account for the simple selector.
+ }
+
+ if let Err(err) = add_pseudo_class_dependency(
+ self.compound_state.element_state,
+ self.quirks_mode,
+ 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> SelectorVisitor for SelectorDependencyCollector<'a> {
+ type Impl = SelectorImpl;
+
+ fn visit_selector_list(
+ &mut self,
+ _list_kind: SelectorListKind,
+ list: &[Selector<SelectorImpl>],
+ ) -> bool {
+ 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 mut index = 0;
+
+ for ss in &mut iter {
+ if !ss.visit(self) {
+ return false;
+ }
+ index += 1;
+ }
+
+ let combinator = iter.next_sequence();
+ if combinator.is_none() {
+ continue;
+ }
+
+ index += 1; // account for the combinator.
+
+ self.parent_selectors.push(ParentDependencyEntry {
+ selector: self.selector.clone(),
+ offset: self.compound_state.offset,
+ cached_dependency: None,
+ });
+ let mut nested = SelectorDependencyCollector {
+ map: &mut *self.map,
+ relative_selector_invalidation_map: &mut *self.relative_selector_invalidation_map,
+ document_state: &mut *self.document_state,
+ selector,
+ parent_selectors: &mut *self.parent_selectors,
+ quirks_mode: self.quirks_mode,
+ compound_state: PerCompoundState::new(index),
+ alloc_error: &mut *self.alloc_error,
+ };
+ if !nested.visit_whole_selector_from(iter, index) {
+ return false;
+ }
+ self.parent_selectors.pop();
+ }
+ true
+ }
+
+ fn visit_relative_selector_list(
+ &mut self,
+ list: &[selectors::parser::RelativeSelector<Self::Impl>],
+ ) -> bool {
+ self.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.parent_selectors.push(ParentDependencyEntry {
+ selector: self.selector.clone(),
+ offset: self.compound_state.offset,
+ cached_dependency: None,
+ });
+ let mut nested = RelativeSelectorDependencyCollector {
+ map: &mut *self.relative_selector_invalidation_map,
+ document_state: &mut *self.document_state,
+ selector: &relative_selector,
+ combinator_count: RelativeSelectorCombinatorCount::new(relative_selector),
+ parent_selectors: &mut *self.parent_selectors,
+ quirks_mode: self.quirks_mode,
+ compound_state: RelativeSelectorPerCompoundState::new(0),
+ alloc_error: &mut *self.alloc_error,
+ };
+ if !nested.visit_whole_selector() {
+ return false;
+ }
+ self.parent_selectors.pop();
+ }
+ true
+ }
+
+ fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool {
+ match *s {
+ Component::ID(..) | Component::Class(..) => {
+ if let Err(err) = on_id_or_class(s, self.quirks_mode, self) {
+ *self.alloc_error = Some(err.into());
+ return false;
+ }
+ true
+ },
+ Component::NonTSPseudoClass(ref pc) => {
+ if let Err(err) = on_pseudo_class(pc, self) {
+ *self.alloc_error = Some(err.into());
+ return false;
+ }
+ true
+ },
+ _ => true,
+ }
+ }
+
+ fn visit_attribute_selector(
+ &mut self,
+ _: &NamespaceConstraint<&Namespace>,
+ local_name: &LocalName,
+ local_name_lower: &LocalName,
+ ) -> bool {
+ if let Err(err) = on_attribute(local_name, local_name_lower, self) {
+ *self.alloc_error = Some(err);
+ return false;
+ }
+ true
+ }
+}
+
+struct RelativeSelectorPerCompoundState {
+ state: PerCompoundState,
+ ts_state: TSStateForInvalidation,
+ added_entry: bool,
+}
+
+impl RelativeSelectorPerCompoundState {
+ fn new(offset: usize) -> Self {
+ Self {
+ state: PerCompoundState::new(offset),
+ ts_state: TSStateForInvalidation::empty(),
+ added_entry: false,
+ }
+ }
+}
+
+/// A struct that collects invalidations for a given compound selector.
+struct RelativeSelectorDependencyCollector<'a> {
+ map: &'a mut RelativeSelectorInvalidationMap,
+
+ /// 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<SelectorImpl>,
+ /// Running combinator for this inner relative selector.
+ combinator_count: RelativeSelectorCombinatorCount,
+
+ /// The stack of parent 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.
+ parent_selectors: &'a mut ParentSelectors,
+
+ /// The quirks mode of the document where we're inserting dependencies.
+ quirks_mode: QuirksMode,
+
+ /// State relevant to a given compound selector.
+ compound_state: RelativeSelectorPerCompoundState,
+
+ /// The allocation error, if we OOM.
+ alloc_error: &'a mut Option<AllocErr>,
+}
+
+fn add_non_unique_info<C: Collector>(
+ selector: &Selector<SelectorImpl>,
+ 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<C: Collector>(
+ 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> RelativeSelectorDependencyCollector<'a> {
+ fn visit_whole_selector(&mut self) -> bool {
+ let mut iter = self.selector.selector.iter_skip_relative_selector_anchor();
+ let mut index = 0;
+
+ self.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 = RelativeSelectorPerCompoundState::new(index);
+
+ // Visit all the simple selectors in this sequence.
+ for ss in &mut iter {
+ if !ss.visit(self) {
+ return false;
+ }
+ index += 1; // Account for the simple selector.
+ }
+
+ if let Err(err) = add_pseudo_class_dependency(
+ self.compound_state.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.ts_state, self.quirks_mode, self)
+ {
+ *self.alloc_error = Some(err);
+ return false;
+ }
+
+ if !self.compound_state.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.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> Collector for RelativeSelectorDependencyCollector<'a> {
+ fn dependency(&mut self) -> Dependency {
+ let parent = parent_dependency(self.parent_selectors, None);
+ Dependency {
+ selector: self.selector.selector.clone(),
+ selector_offset: self.compound_state.state.offset,
+ relative_kind: Some(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
+ },
+ }),
+ parent,
+ }
+ }
+
+ fn id_map(&mut self) -> &mut IdOrClassDependencyMap {
+ &mut self.map.map.id_to_selector
+ }
+
+ fn class_map(&mut self) -> &mut IdOrClassDependencyMap {
+ &mut self.map.map.class_to_selector
+ }
+
+ fn state_map(&mut self) -> &mut StateDependencyMap {
+ &mut self.map.map.state_affecting_selectors
+ }
+
+ fn attribute_map(&mut self) -> &mut LocalNameDependencyMap {
+ &mut self.map.map.other_attribute_affecting_selectors
+ }
+
+ fn update_states(&mut self, element_state: ElementState, document_state: DocumentState) {
+ self.compound_state.state.element_state |= element_state;
+ *self.document_state |= document_state;
+ }
+
+ fn type_map(&mut self) -> &mut LocalNameDependencyMap {
+ &mut self.map.type_to_selector
+ }
+
+ fn ts_state_map(&mut self) -> &mut TSStateDependencyMap {
+ &mut self.map.ts_state_to_selector
+ }
+
+ fn any_vec(&mut self) -> &mut AnyDependencyMap {
+ &mut self.map.any_to_selector
+ }
+}
+
+impl<'a> SelectorVisitor for RelativeSelectorDependencyCollector<'a> {
+ type Impl = SelectorImpl;
+
+ fn visit_selector_list(
+ &mut self,
+ _list_kind: SelectorListKind,
+ list: &[Selector<SelectorImpl>],
+ ) -> bool {
+ let mut parent_stack = ParentSelectors::new();
+ let parent_dependency = Arc::new(self.dependency());
+ for selector in list {
+ // Subjects inside relative selectors aren't really subjects.
+ // This simplifies compound state tracking as well (Additional
+ // states we track for relative selector's inner selectors should
+ // not leak out of the relevant selector).
+ let mut nested = RelativeSelectorInnerDependencyCollector {
+ map: &mut *self.map,
+ parent_dependency: &parent_dependency,
+ document_state: &mut *self.document_state,
+ selector,
+ parent_selectors: &mut parent_stack,
+ quirks_mode: self.quirks_mode,
+ compound_state: RelativeSelectorPerCompoundState::new(0),
+ alloc_error: &mut *self.alloc_error,
+ };
+ if !nested.visit_whole_selector() {
+ return false;
+ }
+ }
+ true
+ }
+
+ fn visit_relative_selector_list(
+ &mut self,
+ _list: &[selectors::parser::RelativeSelector<Self::Impl>],
+ ) -> bool {
+ // Ignore nested relative selectors. These can happen as a result of nesting.
+ true
+ }
+
+ fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool {
+ match *s {
+ Component::ID(..) | Component::Class(..) => {
+ self.compound_state.added_entry = true;
+ if let Err(err) = on_id_or_class(s, self.quirks_mode, self) {
+ *self.alloc_error = Some(err.into());
+ return false;
+ }
+ true
+ },
+ Component::NonTSPseudoClass(ref pc) => {
+ if !pc
+ .state_flag()
+ .intersects(ElementState::VISITED_OR_UNVISITED)
+ {
+ // Visited/Unvisited styling doesn't take the usual state invalidation path.
+ self.compound_state.added_entry = true;
+ }
+ if let Err(err) = on_pseudo_class(pc, self) {
+ *self.alloc_error = Some(err.into());
+ return false;
+ }
+ true
+ },
+ Component::Empty => {
+ self.compound_state
+ .ts_state
+ .insert(TSStateForInvalidation::EMPTY);
+ true
+ },
+ Component::Nth(data) => {
+ let kind = if data.is_simple_edge() {
+ TSStateForInvalidation::NTH_EDGE
+ } else {
+ TSStateForInvalidation::NTH
+ };
+ self.compound_state
+ .ts_state
+ .insert(kind);
+ true
+ },
+ Component::RelativeSelectorAnchor => unreachable!("Should not visit this far"),
+ _ => true,
+ }
+ }
+
+ fn visit_attribute_selector(
+ &mut self,
+ _: &NamespaceConstraint<&Namespace>,
+ local_name: &LocalName,
+ local_name_lower: &LocalName,
+ ) -> bool {
+ self.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
+ }
+}
+
+/// A struct that collects invalidations from a complex selector inside a relative selector.
+/// TODO(dshin): All of this duplication is not great Perhaps should be merged to the normal
+/// one, if possible? See bug 1855690.
+struct RelativeSelectorInnerDependencyCollector<'a, 'b> {
+ map: &'a mut RelativeSelectorInvalidationMap,
+
+ /// 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,
+
+ /// Parent relative selector dependency.
+ parent_dependency: &'b Arc<Dependency>,
+
+ /// The current inner relative selector and offset we're iterating.
+ selector: &'a Selector<SelectorImpl>,
+
+ /// The stack of parent 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.
+ parent_selectors: &'a mut ParentSelectors,
+
+ /// The quirks mode of the document where we're inserting dependencies.
+ quirks_mode: QuirksMode,
+
+ /// State relevant to a given compound selector.
+ compound_state: RelativeSelectorPerCompoundState,
+
+ /// The allocation error, if we OOM.
+ alloc_error: &'a mut Option<AllocErr>,
+}
+
+impl<'a, 'b> Collector for RelativeSelectorInnerDependencyCollector<'a, 'b> {
+ fn dependency(&mut self) -> Dependency {
+ let parent = parent_dependency(self.parent_selectors, Some(self.parent_dependency));
+ Dependency {
+ selector: self.selector.clone(),
+ selector_offset: self.compound_state.state.offset,
+ parent,
+ relative_kind: None,
+ }
+ }
+
+ fn id_map(&mut self) -> &mut IdOrClassDependencyMap {
+ &mut self.map.map.id_to_selector
+ }
+
+ fn class_map(&mut self) -> &mut IdOrClassDependencyMap {
+ &mut self.map.map.class_to_selector
+ }
+
+ fn state_map(&mut self) -> &mut StateDependencyMap {
+ &mut self.map.map.state_affecting_selectors
+ }
+
+ fn attribute_map(&mut self) -> &mut LocalNameDependencyMap {
+ &mut self.map.map.other_attribute_affecting_selectors
+ }
+
+ fn update_states(&mut self, element_state: ElementState, document_state: DocumentState) {
+ self.compound_state.state.element_state |= element_state;
+ *self.document_state |= document_state;
+ }
+
+ fn type_map(&mut self) -> &mut LocalNameDependencyMap {
+ &mut self.map.type_to_selector
+ }
+
+ fn ts_state_map(&mut self) -> &mut TSStateDependencyMap {
+ &mut self.map.ts_state_to_selector
+ }
+
+ fn any_vec(&mut self) -> &mut AnyDependencyMap {
+ &mut self.map.any_to_selector
+ }
+}
+
+impl<'a, 'b> RelativeSelectorInnerDependencyCollector<'a, 'b> {
+ fn visit_whole_selector(&mut self) -> bool {
+ let mut iter = self.selector.iter();
+ let mut index = 0;
+ loop {
+ // Reset the compound state.
+ self.compound_state = RelativeSelectorPerCompoundState::new(index);
+
+ // Visit all the simple selectors in this sequence.
+ for ss in &mut iter {
+ if !ss.visit(self) {
+ return false;
+ }
+ index += 1; // Account for the simple selector.
+ }
+
+ if let Err(err) = add_pseudo_class_dependency(
+ self.compound_state.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.ts_state, self.quirks_mode, self)
+ {
+ *self.alloc_error = Some(err);
+ return false;
+ }
+
+ if !self.compound_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.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> SelectorVisitor for RelativeSelectorInnerDependencyCollector<'a, 'b> {
+ type Impl = SelectorImpl;
+
+ fn visit_selector_list(
+ &mut self,
+ _list_kind: SelectorListKind,
+ list: &[Selector<SelectorImpl>],
+ ) -> bool {
+ let parent_dependency = Arc::new(self.dependency());
+ for selector in list {
+ // Subjects inside relative selectors aren't really subjects.
+ // This simplifies compound state tracking as well (Additional
+ // states we track for relative selector's inner selectors should
+ // not leak out of the relevant selector).
+ let mut nested = RelativeSelectorInnerDependencyCollector {
+ map: &mut *self.map,
+ parent_dependency: &parent_dependency,
+ document_state: &mut *self.document_state,
+ selector,
+ parent_selectors: &mut *self.parent_selectors,
+ quirks_mode: self.quirks_mode,
+ compound_state: RelativeSelectorPerCompoundState::new(0),
+ alloc_error: &mut *self.alloc_error,
+ };
+ if !nested.visit_whole_selector() {
+ return false;
+ }
+ }
+ true
+ }
+
+ fn visit_relative_selector_list(
+ &mut self,
+ _list: &[selectors::parser::RelativeSelector<Self::Impl>],
+ ) -> bool {
+ // Ignore nested relative selectors. These can happen as a result of nesting.
+ true
+ }
+
+ fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool {
+ match *s {
+ Component::ID(..) | Component::Class(..) => {
+ self.compound_state.added_entry = true;
+ if let Err(err) = on_id_or_class(s, self.quirks_mode, self) {
+ *self.alloc_error = Some(err.into());
+ return false;
+ }
+ true
+ },
+ Component::NonTSPseudoClass(ref pc) => {
+ if !pc
+ .state_flag()
+ .intersects(ElementState::VISITED_OR_UNVISITED)
+ {
+ // Visited/Unvisited styling doesn't take the usual state invalidation path.
+ self.compound_state.added_entry = true;
+ }
+ if let Err(err) = on_pseudo_class(pc, self) {
+ *self.alloc_error = Some(err.into());
+ return false;
+ }
+ true
+ },
+ Component::Empty => {
+ self.compound_state
+ .ts_state
+ .insert(TSStateForInvalidation::EMPTY);
+ true
+ },
+ Component::Nth(data) => {
+ let kind = if data.is_simple_edge() {
+ TSStateForInvalidation::NTH_EDGE
+ } else {
+ TSStateForInvalidation::NTH
+ };
+ self.compound_state
+ .ts_state
+ .insert(kind);
+ true
+ },
+ Component::RelativeSelectorAnchor => unreachable!("Should not visit this far"),
+ _ => true,
+ }
+ }
+
+ fn visit_attribute_selector(
+ &mut self,
+ _: &NamespaceConstraint<&Namespace>,
+ local_name: &LocalName,
+ local_name_lower: &LocalName,
+ ) -> bool {
+ self.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
+ }
+}