mirror of
https://github.com/servo/servo.git
synced 2025-06-17 21:04:28 +00:00
404 lines
15 KiB
Rust
404 lines
15 KiB
Rust
/* 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::element_state::{DocumentState, ElementState};
|
|
use crate::selector_map::{MaybeCaseInsensitiveHashMap, SelectorMap, SelectorMapEntry};
|
|
use crate::selector_parser::SelectorImpl;
|
|
use crate::{Atom, LocalName, Namespace};
|
|
use fallible::FallibleVec;
|
|
use hashglobe::FailedAllocationError;
|
|
use selectors::attr::NamespaceConstraint;
|
|
use selectors::parser::{Combinator, Component};
|
|
use selectors::parser::{Selector, SelectorIter, Visit};
|
|
use selectors::visitor::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<SelectorImpl>,
|
|
|
|
/// The offset into the selector that we should match on.
|
|
pub selector_offset: usize,
|
|
}
|
|
|
|
/// 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,
|
|
}
|
|
|
|
impl Dependency {
|
|
/// 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<Combinator> {
|
|
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,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SelectorMapEntry for Dependency {
|
|
fn selector(&self) -> SelectorIter<SelectorImpl> {
|
|
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<SelectorImpl> {
|
|
self.dep.selector()
|
|
}
|
|
}
|
|
|
|
/// The same, but for document state selectors.
|
|
#[derive(Clone, Debug, MallocSizeOf)]
|
|
pub struct DocumentStateDependency {
|
|
/// The selector that is affected. 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 selector: Selector<SelectorImpl>,
|
|
/// 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(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<Atom, SmallVec<[Dependency; 1]>>,
|
|
/// 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<Atom, SmallVec<[Dependency; 1]>>,
|
|
/// A map of all the state dependencies.
|
|
pub state_affecting_selectors: SelectorMap<StateDependency>,
|
|
/// 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: SelectorMap<Dependency>,
|
|
/// Whether there are attribute rules of the form `[class~="foo"]` that may
|
|
/// match. In that case, we need to look at
|
|
/// `other_attribute_affecting_selectors` too even if only the `class` has
|
|
/// changed.
|
|
pub has_class_attribute_selectors: bool,
|
|
/// Whether there are attribute rules of the form `[id|="foo"]` that may
|
|
/// match. In that case, we need to look at
|
|
/// `other_attribute_affecting_selectors` too even if only the `id` has
|
|
/// changed.
|
|
pub has_id_attribute_selectors: bool,
|
|
}
|
|
|
|
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: SelectorMap::new(),
|
|
has_class_attribute_selectors: false,
|
|
has_id_attribute_selectors: false,
|
|
}
|
|
}
|
|
|
|
/// 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.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();
|
|
self.has_id_attribute_selectors = false;
|
|
self.has_class_attribute_selectors = false;
|
|
}
|
|
|
|
/// Adds a selector to this `InvalidationMap`. Returns Err(..) to
|
|
/// signify OOM.
|
|
pub fn note_selector(
|
|
&mut self,
|
|
selector: &Selector<SelectorImpl>,
|
|
quirks_mode: QuirksMode,
|
|
) -> Result<(), FailedAllocationError> {
|
|
debug!("InvalidationMap::note_selector({:?})", selector);
|
|
|
|
let mut iter = selector.iter();
|
|
let mut combinator;
|
|
let mut index = 0;
|
|
|
|
let mut document_state = DocumentState::empty();
|
|
|
|
loop {
|
|
let sequence_start = index;
|
|
|
|
let mut compound_visitor = CompoundSelectorDependencyCollector {
|
|
classes: SmallVec::new(),
|
|
ids: SmallVec::new(),
|
|
state: ElementState::empty(),
|
|
document_state: &mut document_state,
|
|
other_attributes: false,
|
|
has_id_attribute_selectors: false,
|
|
has_class_attribute_selectors: false,
|
|
};
|
|
|
|
// Visit all the simple selectors in this sequence.
|
|
//
|
|
// Note that this works because we can't have combinators nested
|
|
// inside simple selectors (i.e. in :not() or :-moz-any()).
|
|
//
|
|
// If we ever support that we'll need to visit nested complex
|
|
// selectors as well, in order to mark them as affecting descendants
|
|
// at least.
|
|
for ss in &mut iter {
|
|
ss.visit(&mut compound_visitor);
|
|
index += 1; // Account for the simple selector.
|
|
}
|
|
|
|
self.has_id_attribute_selectors |= compound_visitor.has_id_attribute_selectors;
|
|
self.has_class_attribute_selectors |= compound_visitor.has_class_attribute_selectors;
|
|
|
|
for class in compound_visitor.classes {
|
|
self.class_to_selector
|
|
.try_entry(class, quirks_mode)?
|
|
.or_insert_with(SmallVec::new)
|
|
.try_push(Dependency {
|
|
selector: selector.clone(),
|
|
selector_offset: sequence_start,
|
|
})?;
|
|
}
|
|
|
|
for id in compound_visitor.ids {
|
|
self.id_to_selector
|
|
.try_entry(id, quirks_mode)?
|
|
.or_insert_with(SmallVec::new)
|
|
.try_push(Dependency {
|
|
selector: selector.clone(),
|
|
selector_offset: sequence_start,
|
|
})?;
|
|
}
|
|
|
|
if !compound_visitor.state.is_empty() {
|
|
self.state_affecting_selectors.insert(
|
|
StateDependency {
|
|
dep: Dependency {
|
|
selector: selector.clone(),
|
|
selector_offset: sequence_start,
|
|
},
|
|
state: compound_visitor.state,
|
|
},
|
|
quirks_mode,
|
|
)?;
|
|
}
|
|
|
|
if compound_visitor.other_attributes {
|
|
self.other_attribute_affecting_selectors.insert(
|
|
Dependency {
|
|
selector: selector.clone(),
|
|
selector_offset: sequence_start,
|
|
},
|
|
quirks_mode,
|
|
)?;
|
|
}
|
|
|
|
combinator = iter.next_sequence();
|
|
if combinator.is_none() {
|
|
break;
|
|
}
|
|
|
|
index += 1; // Account for the combinator.
|
|
}
|
|
|
|
if !document_state.is_empty() {
|
|
self.document_state_selectors
|
|
.try_push(DocumentStateDependency {
|
|
state: document_state,
|
|
selector: selector.clone(),
|
|
})?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// A struct that collects invalidations for a given compound selector.
|
|
struct CompoundSelectorDependencyCollector<'a> {
|
|
/// The state this compound selector is affected by.
|
|
state: ElementState,
|
|
|
|
/// 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 classes this compound selector is affected by.
|
|
///
|
|
/// NB: This will be often a single class, but could be multiple in
|
|
/// presence of :not, :-moz-any, .foo.bar.baz, etc.
|
|
classes: SmallVec<[Atom; 5]>,
|
|
|
|
/// The IDs this compound selector is affected by.
|
|
///
|
|
/// NB: This will be almost always a single id, but could be multiple in
|
|
/// presence of :not, :-moz-any, #foo#bar, etc.
|
|
ids: SmallVec<[Atom; 5]>,
|
|
|
|
/// Whether it affects other attribute-dependent selectors that aren't ID or
|
|
/// class selectors (NB: We still set this to true in presence of [class] or
|
|
/// [id] attribute selectors).
|
|
other_attributes: bool,
|
|
|
|
/// Whether there were attribute selectors with the id attribute.
|
|
has_id_attribute_selectors: bool,
|
|
|
|
/// Whether there were attribute selectors with the class attribute.
|
|
has_class_attribute_selectors: bool,
|
|
}
|
|
|
|
impl<'a> SelectorVisitor for CompoundSelectorDependencyCollector<'a> {
|
|
type Impl = SelectorImpl;
|
|
|
|
fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool {
|
|
#[cfg(feature = "gecko")]
|
|
use crate::selector_parser::NonTSPseudoClass;
|
|
|
|
match *s {
|
|
Component::ID(ref id) => {
|
|
self.ids.push(id.clone());
|
|
},
|
|
Component::Class(ref class) => {
|
|
self.classes.push(class.clone());
|
|
},
|
|
Component::NonTSPseudoClass(ref pc) => {
|
|
self.other_attributes |= pc.is_attr_based();
|
|
self.state |= match *pc {
|
|
#[cfg(feature = "gecko")]
|
|
NonTSPseudoClass::Dir(ref dir) => dir.element_state(),
|
|
_ => pc.state_flag(),
|
|
};
|
|
*self.document_state |= pc.document_state_flag();
|
|
},
|
|
_ => {},
|
|
}
|
|
|
|
true
|
|
}
|
|
|
|
fn visit_attribute_selector(
|
|
&mut self,
|
|
constraint: &NamespaceConstraint<&Namespace>,
|
|
_local_name: &LocalName,
|
|
local_name_lower: &LocalName,
|
|
) -> bool {
|
|
self.other_attributes = true;
|
|
let may_match_in_no_namespace = match *constraint {
|
|
NamespaceConstraint::Any => true,
|
|
NamespaceConstraint::Specific(ref ns) => ns.is_empty(),
|
|
};
|
|
|
|
if may_match_in_no_namespace {
|
|
self.has_id_attribute_selectors |= *local_name_lower == local_name!("id");
|
|
self.has_class_attribute_selectors |= *local_name_lower == local_name!("class");
|
|
}
|
|
|
|
true
|
|
}
|
|
}
|