servo/components/style/invalidation/element/invalidation_map.rs
Manish Goregaokar b118ba72d0 Revert "Diagnostic map semantics."
This reverts commit f5c5be00a7.
2017-10-23 13:51:08 -07:00

376 lines
14 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 http://mozilla.org/MPL/2.0/. */
//! Code for invalidations due to state or attribute changes.
use {Atom, LocalName, Namespace};
use context::QuirksMode;
use element_state::ElementState;
use fallible::FallibleVec;
use hashglobe::FailedAllocationError;
use selector_map::{MaybeCaseInsensitiveHashMap, SelectorMap, SelectorMapEntry};
use selector_parser::SelectorImpl;
use selectors::attr::NamespaceConstraint;
use selectors::parser::{Combinator, Component};
use selectors::parser::{Selector, SelectorIter, SelectorMethods};
use selectors::visitor::SelectorVisitor;
use smallvec::SmallVec;
#[cfg(feature = "gecko")]
/// Gets the element state relevant to the given `:dir` pseudo-class selector.
pub fn dir_selector_to_state(s: &[u16]) -> ElementState {
use element_state::{IN_LTR_STATE, IN_RTL_STATE};
// Jump through some hoops to deal with our Box<[u16]> thing.
const LTR: [u16; 4] = [b'l' as u16, b't' as u16, b'r' as u16, 0];
const RTL: [u16; 4] = [b'r' as u16, b't' as u16, b'l' as u16, 0];
if LTR == *s {
IN_LTR_STATE
} else if RTL == *s {
IN_RTL_STATE
} else {
// :dir(something-random) is a valid selector, but shouldn't
// match anything.
ElementState::empty()
}
}
/// 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,
}
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))
}
/// Whether this dependency affects the style of the element.
///
/// NOTE(emilio): pseudo-elements need to be here to account for eager
/// pseudos, since they just grab the style from the originating element.
///
/// TODO(emilio): We could look at the selector itself to see if it's an
/// eager pseudo, and return false here if not.
pub fn affects_self(&self) -> bool {
matches!(self.combinator(), None | Some(Combinator::PseudoElement))
}
/// Whether this dependency may affect style of any of our descendants.
pub fn affects_descendants(&self) -> bool {
matches!(self.combinator(), Some(Combinator::PseudoElement) |
Some(Combinator::Child) |
Some(Combinator::Descendant))
}
/// Whether this dependency may affect style of any of our later siblings.
pub fn affects_later_siblings(&self) -> bool {
matches!(self.combinator(), Some(Combinator::NextSibling) |
Some(Combinator::LaterSibling))
}
}
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()
}
}
/// 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 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(),
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.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()
})
}
/// 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> {
self.collect_invalidations_for(selector, quirks_mode)
}
/// 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.other_attribute_affecting_selectors.clear();
self.has_id_attribute_selectors = false;
self.has_class_attribute_selectors = false;
}
// Returns Err(..) to signify OOM.
fn collect_invalidations_for(
&mut self,
selector: &Selector<SelectorImpl>,
quirks_mode: QuirksMode
) -> Result<(), FailedAllocationError> {
debug!("InvalidationMap::collect_invalidations_for({:?})", selector);
let mut iter = selector.iter();
let mut combinator;
let mut index = 0;
loop {
let sequence_start = index;
let mut compound_visitor = CompoundSelectorDependencyCollector {
classes: SmallVec::new(),
ids: SmallVec::new(),
state: ElementState::empty(),
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
.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
.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.
}
Ok(())
}
}
/// A struct that collects invalidations for a given compound selector.
struct CompoundSelectorDependencyCollector {
/// The state this compound selector is affected by.
state: ElementState,
/// 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 SelectorVisitor for CompoundSelectorDependencyCollector {
type Impl = SelectorImpl;
fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool {
#[cfg(feature = "gecko")]
use 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 s) => {
dir_selector_to_state(s)
}
_ => pc.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
}
}