aboutsummaryrefslogtreecommitdiffstats
path: root/components/style/restyle_hints.rs
diff options
context:
space:
mode:
authorBobby Holley <bobbyholley@gmail.com>2015-11-05 09:58:06 -0800
committerBobby Holley <bobbyholley@gmail.com>2015-11-10 11:57:24 -0800
commit7fa79366576751fa6d032091ea54698263783d5d (patch)
tree1eacd1b76fe400d33613afa5ff55e1d8cd0ae3ae /components/style/restyle_hints.rs
parent47744d95add56352ab5d0631d3dd5e054aad3849 (diff)
downloadservo-7fa79366576751fa6d032091ea54698263783d5d.tar.gz
servo-7fa79366576751fa6d032091ea54698263783d5d.zip
Implement attribute restyle hints.
Fixes #6942.
Diffstat (limited to 'components/style/restyle_hints.rs')
-rw-r--r--components/style/restyle_hints.rs196
1 files changed, 146 insertions, 50 deletions
diff --git a/components/style/restyle_hints.rs b/components/style/restyle_hints.rs
index c180c13dcde..cc96f3f89dc 100644
--- a/components/style/restyle_hints.rs
+++ b/components/style/restyle_hints.rs
@@ -2,9 +2,10 @@
* 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/. */
+use attr::{AttrIdentifier, AttrValue};
use selectors::Element;
use selectors::matching::matches_compound_selector;
-use selectors::parser::{AttrSelector, Combinator, CompoundSelector, SimpleSelector};
+use selectors::parser::{AttrSelector, Combinator, CompoundSelector, NamespaceConstraint, SimpleSelector};
use selectors::states::*;
use std::clone::Clone;
use std::sync::Arc;
@@ -12,12 +13,10 @@ use string_cache::{Atom, Namespace};
/// When the ElementState of an element (like IN_HOVER_STATE) changes, certain
/// pseudo-classes (like :hover) may require us to restyle that element, its
-/// siblings, and/or its descendants. Doing this conservatively is expensive,
-/// and so we RestyleHints to short-circuit work we know is unnecessary.
-///
-/// NB: We should extent restyle hints to check for attribute-dependent style
-/// in addition to state-dependent style (Gecko does this).
-
+/// siblings, and/or its descendants. Similarly, when various attributes of an
+/// element change, we may also need to restyle things with id, class, and attribute
+/// selectors. Doing this conservatively is expensive, and so we use RestyleHints to
+/// short-circuit work we know is unnecessary.
bitflags! {
flags RestyleHint: u8 {
@@ -49,34 +48,68 @@ bitflags! {
/// take the ElementWrapper approach for attribute-dependent style. So we do it the same both ways for
/// now to reduce complexity, but it's worth measuring the performance impact (if any) of the
/// mStateMask approach.
-struct ElementWrapper<E> where E: Element {
+
+#[derive(HeapSizeOf, Clone)]
+pub struct ElementSnapshot {
+ pub state: Option<ElementState>,
+ pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
+}
+
+impl ElementSnapshot {
+ pub fn new() -> ElementSnapshot {
+ EMPTY_SNAPSHOT.clone()
+ }
+
+ // Gets an attribute matching |namespace| and |name|, if any. Panics if |attrs| is None.
+ pub fn get_attr(&self, namespace: &Namespace, name: &Atom) -> Option<&AttrValue> {
+ self.attrs.as_ref().unwrap().iter()
+ .find(|&&(ref ident, _)| ident.local_name == *name && ident.namespace == *namespace)
+ .map(|&(_, ref v)| v)
+ }
+
+ // Gets an attribute matching |name| if any, ignoring namespace. Panics if |attrs| is None.
+ pub fn get_attr_ignore_ns(&self, name: &Atom) -> Option<&AttrValue> {
+ self.attrs.as_ref().unwrap().iter()
+ .find(|&&(ref ident, _)| ident.local_name == *name)
+ .map(|&(_, ref v)| v)
+ }
+}
+
+static EMPTY_SNAPSHOT: ElementSnapshot = ElementSnapshot { state: None, attrs: None };
+
+struct ElementWrapper<'a, E> where E: Element {
element: E,
- state_override: ElementState,
+ snapshot: &'a ElementSnapshot,
}
-impl<'a, E> ElementWrapper<E> where E: Element {
- pub fn new(el: E) -> ElementWrapper<E> {
- ElementWrapper { element: el, state_override: ElementState::empty() }
+impl<'a, E> ElementWrapper<'a, E> where E: Element {
+ pub fn new(el: E) -> ElementWrapper<'a, E> {
+ ElementWrapper { element: el, snapshot: &EMPTY_SNAPSHOT }
}
- pub fn new_with_override(el: E, state: ElementState) -> ElementWrapper<E> {
- ElementWrapper { element: el, state_override: state }
+ pub fn new_with_snapshot(el: E, snapshot: &'a ElementSnapshot) -> ElementWrapper<'a, E> {
+ ElementWrapper { element: el, snapshot: snapshot }
}
}
-macro_rules! overridden_state_accessors {
+macro_rules! snapshot_state_accessors {
($(
$(#[$Flag_attr: meta])*
state $css: expr => $variant: ident / $method: ident /
$flag: ident = $value: expr,
- )+) => { $( fn $method(&self) -> bool { self.state_override.contains($flag) } )+
+ )+) => { $( fn $method(&self) -> bool {
+ match self.snapshot.state {
+ Some(s) => s.contains($flag),
+ None => self.element.$method()
+ }
+ } )+
}
}
-impl<E> Element for ElementWrapper<E> where E: Element {
+impl<'a, E> Element for ElementWrapper<'a, E> where E: Element {
- // Implement the state accessors on Element to use our overridden state.
- state_pseudo_classes!(overridden_state_accessors);
+ // Implement the state accessors on Element to use the snapshot state if it exists.
+ state_pseudo_classes!(snapshot_state_accessors);
fn parent_element(&self) -> Option<Self> {
self.element.parent_element().map(|el| ElementWrapper::new(el))
@@ -103,14 +136,31 @@ impl<E> Element for ElementWrapper<E> where E: Element {
self.element.get_namespace()
}
fn get_id(&self) -> Option<Atom> {
- self.element.get_id()
+ match self.snapshot.attrs {
+ Some(_) => self.snapshot.get_attr(&ns!(""), &atom!("id")).map(|value| value.as_atom().clone()),
+ None => self.element.get_id(),
+ }
}
fn has_class(&self, name: &Atom) -> bool {
- self.element.has_class(name)
+ match self.snapshot.attrs {
+ Some(_) => self.snapshot.get_attr(&ns!(""), &atom!("class"))
+ .map_or(false, |v| { v.as_tokens().iter().any(|atom| atom == name) }),
+ None => self.element.has_class(name),
+ }
}
fn match_attr<F>(&self, attr: &AttrSelector, test: F) -> bool
where F: Fn(&str) -> bool {
- self.element.match_attr(attr, test)
+ match self.snapshot.attrs {
+ Some(_) => {
+ let html = self.is_html_element_in_html_document();
+ let local_name = if html { &attr.lower_name } else { &attr.name };
+ match attr.namespace {
+ NamespaceConstraint::Specific(ref ns) => self.snapshot.get_attr(ns, local_name),
+ NamespaceConstraint::Any => self.snapshot.get_attr_ignore_ns(local_name),
+ }.map_or(false, |v| test(v))
+ },
+ None => self.element.match_attr(attr, test)
+ }
}
fn is_empty(&self) -> bool {
self.element.is_empty()
@@ -127,8 +177,15 @@ impl<E> Element for ElementWrapper<E> where E: Element {
fn is_unvisited_link(&self) -> bool {
self.element.is_unvisited_link()
}
- fn each_class<F>(&self, callback: F) where F: FnMut(&Atom) {
- self.element.each_class(callback)
+ fn each_class<F>(&self, mut callback: F) where F: FnMut(&Atom) {
+ match self.snapshot.attrs {
+ Some(_) => {
+ if let Some(v) = self.snapshot.get_attr(&ns!(""), &atom!("class")) {
+ for c in v.as_tokens() { callback(c) }
+ }
+ }
+ None => self.element.each_class(callback),
+ }
}
}
@@ -147,6 +204,23 @@ macro_rules! gen_selector_to_state {
}
}
+state_pseudo_classes!(gen_selector_to_state);
+
+fn is_attr_selector(sel: &SimpleSelector) -> bool {
+ match *sel {
+ SimpleSelector::ID(_) |
+ SimpleSelector::Class(_) |
+ SimpleSelector::AttrExists(_) |
+ SimpleSelector::AttrEqual(_, _, _) |
+ SimpleSelector::AttrIncludes(_, _) |
+ SimpleSelector::AttrDashMatch(_, _, _) |
+ SimpleSelector::AttrPrefixMatch(_, _) |
+ SimpleSelector::AttrSubstringMatch(_, _) |
+ SimpleSelector::AttrSuffixMatch(_, _) => true,
+ _ => false,
+ }
+}
+
fn combinator_to_restyle_hint(combinator: Option<Combinator>) -> RestyleHint {
match combinator {
None => RESTYLE_SELF,
@@ -159,49 +233,68 @@ fn combinator_to_restyle_hint(combinator: Option<Combinator>) -> RestyleHint {
}
}
-state_pseudo_classes!(gen_selector_to_state);
+#[derive(Debug)]
+struct Sensitivities {
+ pub states: ElementState,
+ pub attrs: bool,
+}
+
+impl Sensitivities {
+ fn is_empty(&self) -> bool {
+ self.states.is_empty() && !self.attrs
+ }
+
+ fn new() -> Sensitivities {
+ Sensitivities {
+ states: ElementState::empty(),
+ attrs: false,
+ }
+ }
+}
// Mapping between (partial) CompoundSelectors (and the combinator to their right)
-// and the states they depend on.
+// and the states and attributes they depend on.
//
// In general, for all selectors in all applicable stylesheets of the form:
//
-// |s _ s:X _ s _ s:Y _ s|
+// |a _ b _ c _ d _ e|
//
// Where:
-// * Each |s| is an arbitrary simple selector.
-// * Each |s| is an arbitrary combinator (or nothing).
-// * X and Y are state-dependent pseudo-classes like :hover.
+// * |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 StateDependency for both |s _ s:X _| and |s _ s:X _ s _ s:Y _|, even
+// 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 operation points of pseudo-classes and determine the
-// maximum effect their associated state changes may have on the style of elements in
-// the document.
+// 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(Debug)]
-struct StateDependency {
+struct Dependency {
selector: Arc<CompoundSelector>,
combinator: Option<Combinator>,
- state: ElementState,
+ sensitivities: Sensitivities,
}
#[derive(Debug)]
-pub struct StateDependencySet {
- deps: Vec<StateDependency>,
+pub struct DependencySet {
+ deps: Vec<Dependency>,
}
-impl StateDependencySet {
- pub fn new() -> StateDependencySet {
- StateDependencySet { deps: Vec::new() }
+impl DependencySet {
+ pub fn new() -> DependencySet {
+ DependencySet { deps: Vec::new() }
}
- pub fn compute_hint<E>(&self, el: &E, current_state: ElementState, old_state: ElementState)
+ pub fn compute_hint<E>(&self, el: &E, snapshot: &ElementSnapshot, current_state: ElementState)
-> RestyleHint where E: Element, E: Clone {
+ let state_changes = snapshot.state.map_or(ElementState::empty(), |old_state| current_state ^ old_state);
+ let attrs_changed = snapshot.attrs.is_some();
let mut hint = RestyleHint::empty();
- let state_changes = current_state ^ old_state;
for dep in &self.deps {
- if state_changes.intersects(dep.state) {
- let old_el: ElementWrapper<E> = ElementWrapper::new_with_override(el.clone(), old_state);
+ if state_changes.intersects(dep.sensitivities.states) || (attrs_changed && dep.sensitivities.attrs) {
+ let old_el: ElementWrapper<E> = ElementWrapper::new_with_snapshot(el.clone(), snapshot);
let matched_then = matches_compound_selector(&*dep.selector, &old_el, None, &mut false);
let matches_now = matches_compound_selector(&*dep.selector, el, None, &mut false);
if matched_then != matches_now {
@@ -219,15 +312,18 @@ impl StateDependencySet {
let mut cur = selector;
let mut combinator: Option<Combinator> = None;
loop {
- let mut deps = ElementState::empty();
+ let mut sensitivities = Sensitivities::new();
for s in &cur.simple_selectors {
- deps.insert(selector_to_state(s));
+ sensitivities.states.insert(selector_to_state(s));
+ if !sensitivities.attrs {
+ sensitivities.attrs = is_attr_selector(s);
+ }
}
- if !deps.is_empty() {
- self.deps.push(StateDependency {
+ if !sensitivities.is_empty() {
+ self.deps.push(Dependency {
selector: cur.clone(),
combinator: combinator,
- state: deps,
+ sensitivities: sensitivities,
});
}