aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/style/invalidation/stylesheets.rs168
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;
}