/* 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::SelectorImpl; use crate::AllocErr; use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded}; use dom::{DocumentState, ElementState}; use selectors::attr::NamespaceConstraint; use selectors::parser::{Combinator, Component}; use selectors::parser::{Selector, SelectorIter}; use selectors::visitor::{SelectorListKind, SelectorVisitor}; 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. #[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 selector: Selector, /// 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) /// pub parent: Option>, } /// The kind of elements down the tree this dependency may affect. #[derive(Debug, Eq, PartialEq)] pub enum DependencyInvalidationKind { /// 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, } 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) -> Self { Self { selector_offset: selector.len() + 1, selector, parent: None, } } /// Returns the combinator to the right of the partial selector this /// dependency represents. /// /// TODO(emilio): Consider storing inline if it helps cache locality? pub fn combinator(&self) -> Option { if self.selector_offset == 0 { return None; } Some( self.selector .combinator_at_match_order(self.selector_offset - 1), ) } /// The kind of invalidation that this would generate. pub fn invalidation_kind(&self) -> DependencyInvalidationKind { match self.combinator() { None => DependencyInvalidationKind::Element, Some(Combinator::Child) | Some(Combinator::Descendant) => { DependencyInvalidationKind::Descendants }, Some(Combinator::LaterSibling) | Some(Combinator::NextSibling) => { DependencyInvalidationKind::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) => DependencyInvalidationKind::ElementAndDescendants, Some(Combinator::SlotAssignment) => DependencyInvalidationKind::SlottedElements, Some(Combinator::Part) => DependencyInvalidationKind::Parts, } } } impl SelectorMapEntry for Dependency { fn selector(&self) -> SelectorIter { self.selector.iter_from(self.selector_offset) } } /// 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 { 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, } /// 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: MaybeCaseInsensitiveHashMap>, /// 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: MaybeCaseInsensitiveHashMap>, /// A map of all the state dependencies. pub state_affecting_selectors: SelectorMap, /// 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: PrecomputedHashMap>, } impl InvalidationMap { /// Creates an empty `InvalidationMap`. pub fn new() -> Self { Self { class_to_selector: MaybeCaseInsensitiveHashMap::new(), id_to_selector: MaybeCaseInsensitiveHashMap::new(), state_affecting_selectors: SelectorMap::new(), document_state_selectors: Vec::new(), other_attribute_affecting_selectors: PrecomputedHashMap::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 this `InvalidationMap`. Returns Err(..) to /// signify OOM. pub fn note_selector( &mut self, selector: &Selector, quirks_mode: QuirksMode, ) -> Result<(), AllocErr> { debug!("InvalidationMap::note_selector({:?})", selector); let mut document_state = DocumentState::empty(); { let mut parent_stack = SmallVec::new(); let mut alloc_error = None; let mut collector = SelectorDependencyCollector { map: self, 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()), }; self.document_state_selectors.try_reserve(1)?; self.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(), } } } /// A struct that collects invalidations for a given compound selector. struct SelectorDependencyCollector<'a> { map: &'a mut InvalidationMap, /// 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 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. parent_selectors: &'a mut SmallVec<[(Selector, usize); 5]>, /// 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, } 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, 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 !self.compound_state.element_state.is_empty() { let dependency = self.dependency(); let result = self.map.state_affecting_selectors.insert( StateDependency { dep: dependency, state: self.compound_state.element_state, }, self.quirks_mode, ); if let Err(alloc_error) = result { *self.alloc_error = Some(alloc_error.into()); return false; } } let combinator = iter.next_sequence(); if combinator.is_none() { return true; } index += 1; // account for the combinator } } fn add_attr_dependency(&mut self, name: LocalName) -> bool { let dependency = self.dependency(); let map = &mut self.map.other_attribute_affecting_selectors; if let Err(err) = map.try_reserve(1) { *self.alloc_error = Some(err.into()); return false; } let vec = map.entry(name).or_default(); if let Err(err) = vec.try_reserve(1) { *self.alloc_error = Some(err.into()); return false; } vec.push(dependency); true } fn dependency(&self) -> Dependency { let mut parent = None; // TODO(emilio): Maybe we should refcount the parent dependencies, or // cache them or something. for &(ref selector, ref selector_offset) in self.parent_selectors.iter() { debug_assert_ne!( self.compound_state.offset, 0, "Shouldn't bother creating nested dependencies for the rightmost compound", ); let new_parent = Dependency { selector: selector.clone(), selector_offset: *selector_offset, parent, }; parent = Some(Box::new(new_parent)); } Dependency { selector: self.selector.clone(), selector_offset: self.compound_state.offset, parent, } } } impl<'a> SelectorVisitor for SelectorDependencyCollector<'a> { type Impl = SelectorImpl; fn visit_selector_list( &mut self, _list_kind: SelectorListKind, list: &[Selector], ) -> 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((self.selector.clone(), self.compound_state.offset)); let mut nested = SelectorDependencyCollector { map: &mut *self.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_simple_selector(&mut self, s: &Component) -> bool { use crate::selector_parser::NonTSPseudoClass; match *s { Component::ID(ref atom) | Component::Class(ref atom) => { let dependency = self.dependency(); let map = match *s { Component::ID(..) => &mut self.map.id_to_selector, Component::Class(..) => &mut self.map.class_to_selector, _ => unreachable!(), }; let entry = match map.try_entry(atom.0.clone(), self.quirks_mode) { Ok(entry) => entry, Err(err) => { *self.alloc_error = Some(err.into()); return false; }, }; let vec = entry.or_insert_with(SmallVec::new); if let Err(err) = vec.try_reserve(1) { *self.alloc_error = Some(err.into()); return false; } vec.push(dependency); true }, Component::NonTSPseudoClass(ref pc) => { self.compound_state.element_state |= pc.state_flag(); *self.document_state |= pc.document_state_flag(); let attr_name = match *pc { #[cfg(feature = "gecko")] NonTSPseudoClass::MozTableBorderNonzero => local_name!("border"), #[cfg(feature = "gecko")] NonTSPseudoClass::MozBrowserFrame => local_name!("mozbrowser"), #[cfg(feature = "gecko")] NonTSPseudoClass::MozSelectListBox => { // This depends on two attributes. return self.add_attr_dependency(local_name!("multiple")) && self.add_attr_dependency(local_name!("size")); }, NonTSPseudoClass::Lang(..) => local_name!("lang"), _ => return true, }; self.add_attr_dependency(attr_name) }, _ => true, } } fn visit_attribute_selector( &mut self, _: &NamespaceConstraint<&Namespace>, local_name: &LocalName, local_name_lower: &LocalName, ) -> bool { if !self.add_attr_dependency(local_name.clone()) { return false; } if local_name != local_name_lower && !self.add_attr_dependency(local_name_lower.clone()) { return false; } true } }