diff options
-rw-r--r-- | components/style/invalidation/mod.rs | 293 | ||||
-rw-r--r-- | components/style/invalidation/stylesheets.rs | 296 | ||||
-rw-r--r-- | components/style/stylesheet_set.rs | 2 |
3 files changed, 299 insertions, 292 deletions
diff --git a/components/style/invalidation/mod.rs b/components/style/invalidation/mod.rs index 93f0d8783ad..6107894b00d 100644 --- a/components/style/invalidation/mod.rs +++ b/components/style/invalidation/mod.rs @@ -2,295 +2,6 @@ * 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/. */ -//! A collection of invalidations due to changes in which stylesheets affect a -//! document. +//! Different bits of code related to invalidating style. -#![deny(unsafe_code)] - -use Atom; -use data::StoredRestyleHint; -use dom::{TElement, TNode}; -use fnv::FnvHashSet; -use selector_parser::SelectorImpl; -use selectors::parser::{Component, Selector}; -use shared_lock::SharedRwLockReadGuard; -use stylesheets::{CssRule, Stylesheet}; -use stylist::Stylist; - -/// An invalidation scope represents a kind of subtree that may need to be -/// restyled. -#[derive(Debug, Hash, Eq, PartialEq)] -enum InvalidationScope { - /// All the descendants of an element with a given id. - ID(Atom), - /// All the descendants of an element with a given class name. - Class(Atom), -} - -impl InvalidationScope { - fn is_id(&self) -> bool { - matches!(*self, InvalidationScope::ID(..)) - } - - fn matches<E>(&self, element: E) -> bool - where E: TElement, - { - match *self { - InvalidationScope::Class(ref class) => { - element.has_class(class) - } - InvalidationScope::ID(ref id) => { - match element.get_id() { - Some(element_id) => element_id == *id, - None => false, - } - } - } - } -} - -/// A set of invalidations due to stylesheet additions. -/// -/// TODO(emilio): We might be able to do the same analysis for removals and -/// media query changes too? -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. - fully_invalid: bool, -} - -impl StylesheetInvalidationSet { - /// Create an empty `StylesheetInvalidationSet`. - pub fn new() -> Self { - Self { - invalid_scopes: FnvHashSet::default(), - fully_invalid: false, - } - } - - /// Mark the DOM tree styles' as fully invalid. - pub fn invalidate_fully(&mut self) { - debug!("StylesheetInvalidationSet::invalidate_fully"); - self.invalid_scopes.clear(); - self.fully_invalid = true; - } - - /// Analyze the given stylesheet, and collect invalidations from their - /// rules, in order to avoid doing a full restyle when we style the document - /// next time. - pub fn collect_invalidations_for( - &mut self, - stylist: &Stylist, - stylesheet: &Stylesheet, - guard: &SharedRwLockReadGuard) - { - debug!("StylesheetInvalidationSet::collect_invalidations_for"); - if self.fully_invalid { - debug!(" > Fully invalid already"); - return; - } - - if stylesheet.disabled() || - !stylesheet.is_effective_for_device(stylist.device(), guard) { - debug!(" > Stylesheet was not effective"); - return; // Nothing to do here. - } - - for rule in stylesheet.effective_rules(stylist.device(), guard) { - self.collect_invalidations_for_rule(rule, guard); - if self.fully_invalid { - self.invalid_scopes.clear(); - break; - } - } - - debug!(" > resulting invalidations: {:?}", self.invalid_scopes); - debug!(" > fully_invalid: {}", self.fully_invalid); - } - - /// Clears the invalidation set, invalidating elements as needed if - /// `document_element` is provided. - pub fn flush<E>(&mut self, document_element: Option<E>) - where E: TElement, - { - if let Some(e) = document_element { - self.process_invalidations_in_subtree(e); - } - self.invalid_scopes.clear(); - self.fully_invalid = false; - } - - /// 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. - /// - /// Returns whether it invalidated at least one element's style. - #[allow(unsafe_code)] - fn process_invalidations_in_subtree<E>(&self, element: E) -> bool - where E: TElement, - { - let mut data = match element.mutate_data() { - Some(data) => data, - None => return false, - }; - - if !data.has_styles() { - return false; - } - - if let Some(ref r) = data.get_restyle() { - if r.hint.contains_subtree() { - debug!("process_invalidations_in_subtree: {:?} was already invalid", - element); - return false; - } - } - - if self.fully_invalid { - debug!("process_invalidations_in_subtree: fully_invalid({:?})", - element); - data.ensure_restyle().hint.insert(StoredRestyleHint::subtree()); - return true; - } - - for scope in &self.invalid_scopes { - if scope.matches(element) { - debug!("process_invalidations_in_subtree: {:?} matched {:?}", - element, scope); - data.ensure_restyle().hint.insert(StoredRestyleHint::subtree()); - return true; - } - } - - - let mut any_children_invalid = false; - - for child in element.as_node().children() { - let child = match child.as_element() { - Some(e) => e, - None => continue, - }; - - any_children_invalid |= self.process_invalidations_in_subtree(child); - } - - if any_children_invalid { - debug!("Children of {:?} changed, setting dirty descendants", - element); - unsafe { element.set_dirty_descendants() } - } - - return any_children_invalid - } - - fn scan_component( - component: &Component<SelectorImpl>, - scope: &mut Option<InvalidationScope>) - { - match *component { - Component::Class(ref class) => { - if scope.as_ref().map_or(true, |s| !s.is_id()) { - *scope = Some(InvalidationScope::Class(class.clone())); - } - } - Component::ID(ref id) => { - if scope.is_none() { - *scope = Some(InvalidationScope::ID(id.clone())); - } - } - _ => { - // Ignore everything else, at least for now. - } - } - } - - /// Collect a style scopes for a given selector. - /// - /// We look at the outermost class or id selector to the left of an ancestor - /// combinator, in order to restyle only a given subtree. - /// - /// 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); - - let mut scope: Option<InvalidationScope> = None; - - let mut scan = true; - let mut iter = selector.inner.complex.iter(); - - loop { - for component in &mut iter { - if scan { - Self::scan_component(component, &mut scope); - } - } - match iter.next_sequence() { - None => break, - Some(combinator) => { - scan = combinator.is_ancestor(); - } - } - } - - 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; - } - } - } - - /// Collects invalidations for a given CSS rule. - fn collect_invalidations_for_rule( - &mut self, - rule: &CssRule, - guard: &SharedRwLockReadGuard) - { - use stylesheets::CssRule::*; - debug!("StylesheetInvalidationSet::collect_invalidations_for_rule"); - debug_assert!(!self.fully_invalid, "Not worth to be here!"); - - match *rule { - Style(ref lock) => { - let style_rule = lock.read_with(guard); - for selector in &style_rule.selectors.0 { - self.collect_scopes(selector); - if self.fully_invalid { - return; - } - } - } - Document(..) | - Namespace(..) | - Import(..) | - Media(..) | - Supports(..) => { - // Do nothing, relevant nested rules are visited as part of the - // iteration. - } - FontFace(..) | - CounterStyle(..) | - Keyframes(..) | - Page(..) | - Viewport(..) => { - debug!(" > Found unsupported rule, marking the whole subtree \ - invalid."); - - // TODO(emilio): Can we do better here? - // - // At least in `@page`, we could check the relevant media, I - // guess. - self.fully_invalid = true; - } - } - } -} +pub mod stylesheets; diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs new file mode 100644 index 00000000000..93f0d8783ad --- /dev/null +++ b/components/style/invalidation/stylesheets.rs @@ -0,0 +1,296 @@ +/* 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/. */ + +//! A collection of invalidations due to changes in which stylesheets affect a +//! document. + +#![deny(unsafe_code)] + +use Atom; +use data::StoredRestyleHint; +use dom::{TElement, TNode}; +use fnv::FnvHashSet; +use selector_parser::SelectorImpl; +use selectors::parser::{Component, Selector}; +use shared_lock::SharedRwLockReadGuard; +use stylesheets::{CssRule, Stylesheet}; +use stylist::Stylist; + +/// An invalidation scope represents a kind of subtree that may need to be +/// restyled. +#[derive(Debug, Hash, Eq, PartialEq)] +enum InvalidationScope { + /// All the descendants of an element with a given id. + ID(Atom), + /// All the descendants of an element with a given class name. + Class(Atom), +} + +impl InvalidationScope { + fn is_id(&self) -> bool { + matches!(*self, InvalidationScope::ID(..)) + } + + fn matches<E>(&self, element: E) -> bool + where E: TElement, + { + match *self { + InvalidationScope::Class(ref class) => { + element.has_class(class) + } + InvalidationScope::ID(ref id) => { + match element.get_id() { + Some(element_id) => element_id == *id, + None => false, + } + } + } + } +} + +/// A set of invalidations due to stylesheet additions. +/// +/// TODO(emilio): We might be able to do the same analysis for removals and +/// media query changes too? +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. + fully_invalid: bool, +} + +impl StylesheetInvalidationSet { + /// Create an empty `StylesheetInvalidationSet`. + pub fn new() -> Self { + Self { + invalid_scopes: FnvHashSet::default(), + fully_invalid: false, + } + } + + /// Mark the DOM tree styles' as fully invalid. + pub fn invalidate_fully(&mut self) { + debug!("StylesheetInvalidationSet::invalidate_fully"); + self.invalid_scopes.clear(); + self.fully_invalid = true; + } + + /// Analyze the given stylesheet, and collect invalidations from their + /// rules, in order to avoid doing a full restyle when we style the document + /// next time. + pub fn collect_invalidations_for( + &mut self, + stylist: &Stylist, + stylesheet: &Stylesheet, + guard: &SharedRwLockReadGuard) + { + debug!("StylesheetInvalidationSet::collect_invalidations_for"); + if self.fully_invalid { + debug!(" > Fully invalid already"); + return; + } + + if stylesheet.disabled() || + !stylesheet.is_effective_for_device(stylist.device(), guard) { + debug!(" > Stylesheet was not effective"); + return; // Nothing to do here. + } + + for rule in stylesheet.effective_rules(stylist.device(), guard) { + self.collect_invalidations_for_rule(rule, guard); + if self.fully_invalid { + self.invalid_scopes.clear(); + break; + } + } + + debug!(" > resulting invalidations: {:?}", self.invalid_scopes); + debug!(" > fully_invalid: {}", self.fully_invalid); + } + + /// Clears the invalidation set, invalidating elements as needed if + /// `document_element` is provided. + pub fn flush<E>(&mut self, document_element: Option<E>) + where E: TElement, + { + if let Some(e) = document_element { + self.process_invalidations_in_subtree(e); + } + self.invalid_scopes.clear(); + self.fully_invalid = false; + } + + /// 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. + /// + /// Returns whether it invalidated at least one element's style. + #[allow(unsafe_code)] + fn process_invalidations_in_subtree<E>(&self, element: E) -> bool + where E: TElement, + { + let mut data = match element.mutate_data() { + Some(data) => data, + None => return false, + }; + + if !data.has_styles() { + return false; + } + + if let Some(ref r) = data.get_restyle() { + if r.hint.contains_subtree() { + debug!("process_invalidations_in_subtree: {:?} was already invalid", + element); + return false; + } + } + + if self.fully_invalid { + debug!("process_invalidations_in_subtree: fully_invalid({:?})", + element); + data.ensure_restyle().hint.insert(StoredRestyleHint::subtree()); + return true; + } + + for scope in &self.invalid_scopes { + if scope.matches(element) { + debug!("process_invalidations_in_subtree: {:?} matched {:?}", + element, scope); + data.ensure_restyle().hint.insert(StoredRestyleHint::subtree()); + return true; + } + } + + + let mut any_children_invalid = false; + + for child in element.as_node().children() { + let child = match child.as_element() { + Some(e) => e, + None => continue, + }; + + any_children_invalid |= self.process_invalidations_in_subtree(child); + } + + if any_children_invalid { + debug!("Children of {:?} changed, setting dirty descendants", + element); + unsafe { element.set_dirty_descendants() } + } + + return any_children_invalid + } + + fn scan_component( + component: &Component<SelectorImpl>, + scope: &mut Option<InvalidationScope>) + { + match *component { + Component::Class(ref class) => { + if scope.as_ref().map_or(true, |s| !s.is_id()) { + *scope = Some(InvalidationScope::Class(class.clone())); + } + } + Component::ID(ref id) => { + if scope.is_none() { + *scope = Some(InvalidationScope::ID(id.clone())); + } + } + _ => { + // Ignore everything else, at least for now. + } + } + } + + /// Collect a style scopes for a given selector. + /// + /// We look at the outermost class or id selector to the left of an ancestor + /// combinator, in order to restyle only a given subtree. + /// + /// 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); + + let mut scope: Option<InvalidationScope> = None; + + let mut scan = true; + let mut iter = selector.inner.complex.iter(); + + loop { + for component in &mut iter { + if scan { + Self::scan_component(component, &mut scope); + } + } + match iter.next_sequence() { + None => break, + Some(combinator) => { + scan = combinator.is_ancestor(); + } + } + } + + 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; + } + } + } + + /// Collects invalidations for a given CSS rule. + fn collect_invalidations_for_rule( + &mut self, + rule: &CssRule, + guard: &SharedRwLockReadGuard) + { + use stylesheets::CssRule::*; + debug!("StylesheetInvalidationSet::collect_invalidations_for_rule"); + debug_assert!(!self.fully_invalid, "Not worth to be here!"); + + match *rule { + Style(ref lock) => { + let style_rule = lock.read_with(guard); + for selector in &style_rule.selectors.0 { + self.collect_scopes(selector); + if self.fully_invalid { + return; + } + } + } + Document(..) | + Namespace(..) | + Import(..) | + Media(..) | + Supports(..) => { + // Do nothing, relevant nested rules are visited as part of the + // iteration. + } + FontFace(..) | + CounterStyle(..) | + Keyframes(..) | + Page(..) | + Viewport(..) => { + debug!(" > Found unsupported rule, marking the whole subtree \ + invalid."); + + // TODO(emilio): Can we do better here? + // + // At least in `@page`, we could check the relevant media, I + // guess. + self.fully_invalid = true; + } + } + } +} diff --git a/components/style/stylesheet_set.rs b/components/style/stylesheet_set.rs index 2908d81bef4..5746446d2cf 100644 --- a/components/style/stylesheet_set.rs +++ b/components/style/stylesheet_set.rs @@ -5,7 +5,7 @@ //! A centralized set of stylesheets for a document. use dom::TElement; -use invalidation::StylesheetInvalidationSet; +use invalidation::stylesheets::StylesheetInvalidationSet; use shared_lock::SharedRwLockReadGuard; use std::slice; use stylearc::Arc; |