diff options
-rw-r--r-- | components/style/invalidation/stylesheets.rs | 168 |
1 files changed, 114 insertions, 54 deletions
diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index 7d9af116ae1..c65dfc056bc 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -8,45 +8,65 @@ #![deny(unsafe_code)] use Atom; +use LocalName as SelectorLocalName; use dom::{TElement, TNode}; use fnv::FnvHashSet; -use invalidation::element::restyle_hints::RestyleHint; +use invalidation::element::restyle_hints::{RESTYLE_SELF, RestyleHint}; use media_queries::Device; use selector_parser::SelectorImpl; use selectors::attr::CaseSensitivity; -use selectors::parser::{Component, Selector}; +use selectors::parser::{Component, LocalName, Selector}; use shared_lock::SharedRwLockReadGuard; use stylesheets::{CssRule, StylesheetInDocument}; -/// An invalidation scope represents a kind of subtree that may need to be -/// restyled. +/// A style sheet invalidation represents a kind of element or subtree that may +/// need to be restyled. Whether it represents a whole subtree or just a single +/// element is determined by whether the invalidation is stored in the +/// StylesheetInvalidationSet's invalid_scopes or invalid_elements table. #[derive(Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -enum InvalidationScope { - /// All the descendants of an element with a given id. +enum Invalidation { + /// An element with a given id. ID(Atom), - /// All the descendants of an element with a given class name. + /// An element with a given class name. Class(Atom), + /// An element with a given local name. + LocalName { name: SelectorLocalName, lower_name: SelectorLocalName }, } -impl InvalidationScope { +impl Invalidation { fn is_id(&self) -> bool { - matches!(*self, InvalidationScope::ID(..)) + matches!(*self, Invalidation::ID(..)) + } + + fn is_id_or_class(&self) -> bool { + matches!(*self, Invalidation::ID(..) | Invalidation::Class(..)) } fn matches<E>(&self, element: E) -> bool where E: TElement, { match *self { - InvalidationScope::Class(ref class) => { + Invalidation::Class(ref class) => { + // FIXME This should look at the quirks mode of the document to + // determine case sensitivity. element.has_class(class, CaseSensitivity::CaseSensitive) } - InvalidationScope::ID(ref id) => { + Invalidation::ID(ref id) => { match element.get_id() { + // FIXME This should look at the quirks mode of the document + // to determine case sensitivity. Some(element_id) => element_id == *id, None => false, } } + Invalidation::LocalName { ref name, ref lower_name } => { + // This could look at the quirks mode of the document, instead + // of testing against both names, but it's probably not worth + // it. + let local_name = element.get_local_name(); + *local_name == **name || *local_name == **lower_name + } } } } @@ -57,9 +77,11 @@ impl InvalidationScope { /// changes too (or even selector changes?). #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct StylesheetInvalidationSet { - /// The style scopes we know we have to restyle so far. - invalid_scopes: FnvHashSet<InvalidationScope>, - /// Whether the whole document should be invalid. + /// The subtrees we know we have to restyle so far. + invalid_scopes: FnvHashSet<Invalidation>, + /// The elements we know we have to restyle so far. + invalid_elements: FnvHashSet<Invalidation>, + /// Whether the whole document should be restyled. fully_invalid: bool, } @@ -68,6 +90,7 @@ impl StylesheetInvalidationSet { pub fn new() -> Self { Self { invalid_scopes: FnvHashSet::default(), + invalid_elements: FnvHashSet::default(), fully_invalid: false, } } @@ -76,6 +99,7 @@ impl StylesheetInvalidationSet { pub fn invalidate_fully(&mut self) { debug!("StylesheetInvalidationSet::invalidate_fully"); self.invalid_scopes.clear(); + self.invalid_elements.clear(); self.fully_invalid = true; } @@ -107,11 +131,13 @@ impl StylesheetInvalidationSet { self.collect_invalidations_for_rule(rule, guard); if self.fully_invalid { self.invalid_scopes.clear(); + self.invalid_elements.clear(); break; } } - debug!(" > resulting invalidations: {:?}", self.invalid_scopes); + debug!(" > resulting subtree invalidations: {:?}", self.invalid_scopes); + debug!(" > resulting self invalidations: {:?}", self.invalid_elements); debug!(" > fully_invalid: {}", self.fully_invalid); } @@ -133,6 +159,7 @@ impl StylesheetInvalidationSet { /// Clears the invalidation set without processing. pub fn clear(&mut self) { self.invalid_scopes.clear(); + self.invalid_elements.clear(); self.fully_invalid = false; } @@ -153,7 +180,7 @@ impl StylesheetInvalidationSet { } } - if self.invalid_scopes.is_empty() { + if self.invalid_scopes.is_empty() && self.invalid_elements.is_empty() { debug!("process_invalidations: empty invalidation set"); return false; } @@ -161,9 +188,9 @@ impl StylesheetInvalidationSet { self.process_invalidations_in_subtree(element) } - /// Process style invalidations in a given subtree, that is, look for all - /// the relevant scopes in the subtree, and mark as dirty only the relevant - /// ones. + /// Process style invalidations in a given subtree. This traverses the + /// subtree looking for elements that match the invalidations in + /// invalid_scopes and invalid_elements. /// /// Returns whether it invalidated at least one element's style. #[allow(unsafe_code)] @@ -185,15 +212,28 @@ impl StylesheetInvalidationSet { return false; } - for scope in &self.invalid_scopes { - if scope.matches(element) { - debug!("process_invalidations_in_subtree: {:?} matched {:?}", - element, scope); + for invalidation in &self.invalid_scopes { + if invalidation.matches(element) { + debug!("process_invalidations_in_subtree: {:?} matched subtree {:?}", + element, invalidation); data.hint.insert(RestyleHint::restyle_subtree()); return true; } } + let mut self_invalid = false; + + if !data.hint.contains(RESTYLE_SELF) { + for invalidation in &self.invalid_elements { + if invalidation.matches(element) { + debug!("process_invalidations_in_subtree: {:?} matched self {:?}", + element, invalidation); + data.hint.insert(RESTYLE_SELF); + self_invalid = true; + break; + } + } + } let mut any_children_invalid = false; @@ -212,22 +252,30 @@ impl StylesheetInvalidationSet { unsafe { element.set_dirty_descendants() } } - return any_children_invalid + return self_invalid || any_children_invalid } fn scan_component( component: &Component<SelectorImpl>, - scope: &mut Option<InvalidationScope>) + invalidation: &mut Option<Invalidation>) { match *component { + Component::LocalName(LocalName { ref name, ref lower_name }) => { + if invalidation.as_ref().map_or(true, |s| !s.is_id_or_class()) { + *invalidation = Some(Invalidation::LocalName { + name: name.clone(), + lower_name: lower_name.clone(), + }); + } + } Component::Class(ref class) => { - if scope.as_ref().map_or(true, |s| !s.is_id()) { - *scope = Some(InvalidationScope::Class(class.clone())); + if invalidation.as_ref().map_or(true, |s| !s.is_id()) { + *invalidation = Some(Invalidation::Class(class.clone())); } } Component::ID(ref id) => { - if scope.is_none() { - *scope = Some(InvalidationScope::ID(id.clone())); + if invalidation.is_none() { + *invalidation = Some(Invalidation::ID(id.clone())); } } _ => { @@ -236,47 +284,59 @@ impl StylesheetInvalidationSet { } } - /// Collect a style scopes for a given selector. + /// Collect invalidations for a given selector. + /// + /// We look at the outermost local name, class, or ID selector to the left + /// of an ancestor combinator, in order to restyle only a given subtree. /// - /// We look at the outermost class or id selector to the left of an ancestor - /// combinator, in order to restyle only a given subtree. + /// If the selector has no ancestor combinator, then we do the same for + /// the only sequence it has, but record it as an element invalidation + /// instead of a subtree invalidation. /// - /// We prefer id scopes to class scopes, and outermost scopes to innermost - /// scopes (to reduce the amount of traversal we need to do). - fn collect_scopes(&mut self, selector: &Selector<SelectorImpl>) { - debug!("StylesheetInvalidationSet::collect_scopes({:?})", selector); + /// We prefer IDs to classs, and classes to local names, on the basis + /// that the former should be more specific than the latter. We also + /// prefer to generate subtree invalidations for the outermost part + /// of the selector, to reduce the amount of traversal we need to do + /// when flushing invalidations. + fn collect_invalidations(&mut self, selector: &Selector<SelectorImpl>) { + debug!("StylesheetInvalidationSet::collect_invalidations({:?})", selector); - let mut scope: Option<InvalidationScope> = None; + let mut element_invalidation: Option<Invalidation> = None; + let mut subtree_invalidation: Option<Invalidation> = None; + + let mut scan_for_element_invalidation = true; + let mut scan_for_subtree_invalidation = false; - let mut scan = true; let mut iter = selector.iter(); loop { for component in &mut iter { - if scan { - Self::scan_component(component, &mut scope); + if scan_for_element_invalidation { + Self::scan_component(component, &mut element_invalidation); + } else if scan_for_subtree_invalidation { + Self::scan_component(component, &mut subtree_invalidation); } } match iter.next_sequence() { None => break, Some(combinator) => { - scan = combinator.is_ancestor(); + scan_for_subtree_invalidation = combinator.is_ancestor(); } } + scan_for_element_invalidation = false; } - match scope { - Some(s) => { - debug!(" > Found scope: {:?}", s); - self.invalid_scopes.insert(s); - } - None => { - debug!(" > Scope not found"); - - // If we didn't find a scope, any element could match this, so - // let's just bail out. - self.fully_invalid = true; - } + if let Some(s) = subtree_invalidation { + debug!(" > Found subtree invalidation: {:?}", s); + self.invalid_scopes.insert(s); + } else if let Some(s) = element_invalidation { + debug!(" > Found element invalidation: {:?}", s); + self.invalid_elements.insert(s); + } else { + // The selector was of a form that we can't handle. Any element + // could match it, so let's just bail out. + debug!(" > Can't handle selector, marking fully invalid"); + self.fully_invalid = true; } } @@ -294,7 +354,7 @@ impl StylesheetInvalidationSet { Style(ref lock) => { let style_rule = lock.read_with(guard); for selector in &style_rule.selectors.0 { - self.collect_scopes(selector); + self.collect_invalidations(selector); if self.fully_invalid { return; } |