aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/layout/layout_task.rs6
-rw-r--r--components/layout/wrapper.rs19
-rw-r--r--components/script/dom/attr.rs5
-rw-r--r--components/script/dom/bindings/trace.rs2
-rw-r--r--components/script/dom/document.rs27
-rw-r--r--components/script/dom/element.rs42
-rw-r--r--components/script/dom/node.rs17
-rw-r--r--components/style/restyle_hints.rs196
-rw-r--r--components/style/selector_matching.rs24
9 files changed, 237 insertions, 101 deletions
diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs
index 514e3e71e66..d650a927ad3 100644
--- a/components/layout/layout_task.rs
+++ b/components/layout/layout_task.rs
@@ -1145,10 +1145,8 @@ impl LayoutTask {
let modified_elements = document.drain_modified_elements();
if !needs_dirtying {
- for &(el, old_state) in modified_elements.iter() {
- let hint = rw_data.stylist.restyle_hint_for_state_change(&el,
- el.get_state(),
- old_state);
+ for (el, snapshot) in modified_elements {
+ let hint = rw_data.stylist.compute_restyle_hint(&el, &snapshot, el.get_state());
el.note_restyle_hint(hint);
}
}
diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs
index ad745d2d091..75244159727 100644
--- a/components/layout/wrapper.rs
+++ b/components/layout/wrapper.rs
@@ -58,7 +58,6 @@ use selectors::states::*;
use smallvec::VecLike;
use std::borrow::ToOwned;
use std::cell::{Ref, RefMut};
-use std::iter::FromIterator;
use std::marker::PhantomData;
use std::mem;
use std::sync::Arc;
@@ -69,7 +68,7 @@ use style::legacy::UnsignedIntegerAttribute;
use style::node::TElementAttributes;
use style::properties::ComputedValues;
use style::properties::{PropertyDeclaration, PropertyDeclarationBlock};
-use style::restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
+use style::restyle_hints::{ElementSnapshot, RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
use url::Url;
use util::str::{is_whitespace, search_index};
@@ -375,15 +374,13 @@ impl<'le> LayoutDocument<'le> {
self.as_node().children().find(LayoutNode::is_element)
}
- pub fn drain_modified_elements(&self) -> Vec<(LayoutElement, ElementState)> {
- unsafe {
- let elements = self.document.drain_modified_elements();
- Vec::from_iter(elements.iter().map(|&(el, state)|
- (LayoutElement {
- element: el,
- chain: PhantomData,
- }, state)))
- }
+ pub fn drain_modified_elements(&self) -> Vec<(LayoutElement, ElementSnapshot)> {
+ let elements = unsafe { self.document.drain_modified_elements() };
+ elements.into_iter().map(|(el, snapshot)|
+ (LayoutElement {
+ element: el,
+ chain: PhantomData,
+ }, snapshot)).collect()
}
}
diff --git a/components/script/dom/attr.rs b/components/script/dom/attr.rs
index c5cd7c3d4ec..02ef5e4451e 100644
--- a/components/script/dom/attr.rs
+++ b/components/script/dom/attr.rs
@@ -148,6 +148,7 @@ impl AttrMethods for Attr {
impl Attr {
pub fn set_value(&self, mut value: AttrValue, owner: &Element) {
assert!(Some(owner) == self.owner().r());
+ owner.will_mutate_attr();
mem::swap(&mut *self.value.borrow_mut(), &mut value);
if self.identifier.namespace == ns!("") {
vtable_for(owner.upcast()).attribute_mutated(
@@ -155,6 +156,10 @@ impl Attr {
}
}
+ pub fn identifier(&self) -> &AttrIdentifier {
+ &self.identifier
+ }
+
pub fn value(&self) -> Ref<AttrValue> {
self.value.borrow()
}
diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs
index d3259f56453..cd8fab7e56f 100644
--- a/components/script/dom/bindings/trace.rs
+++ b/components/script/dom/bindings/trace.rs
@@ -84,6 +84,7 @@ use std::sync::mpsc::{Receiver, Sender};
use string_cache::{Atom, Namespace, QualName};
use style::attr::{AttrIdentifier, AttrValue};
use style::properties::PropertyDeclarationBlock;
+use style::restyle_hints::ElementSnapshot;
use style::values::specified::Length;
use url::Url;
use util::str::{DOMString, LengthOrPercentageOrAuto};
@@ -292,6 +293,7 @@ no_jsmanaged_fields!(DOMString);
no_jsmanaged_fields!(Mime);
no_jsmanaged_fields!(AttrIdentifier);
no_jsmanaged_fields!(AttrValue);
+no_jsmanaged_fields!(ElementSnapshot);
impl JSTraceable for Box<ScriptChan + Send> {
#[inline]
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index c077a4f5769..262e5a8b4d5 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -88,7 +88,6 @@ use net_traits::{AsyncResponseTarget, PendingAsyncLoad};
use num::ToPrimitive;
use script_task::{MainThreadScriptMsg, Runnable};
use script_traits::{MouseButton, TouchEventType, TouchId, UntrustedNodeAddress};
-use selectors::states::*;
use std::ascii::AsciiExt;
use std::borrow::ToOwned;
use std::boxed::FnBox;
@@ -102,6 +101,7 @@ use std::rc::Rc;
use std::sync::Arc;
use std::sync::mpsc::channel;
use string_cache::{Atom, QualName};
+use style::restyle_hints::ElementSnapshot;
use style::stylesheets::Stylesheet;
use time;
use url::Url;
@@ -188,8 +188,9 @@ pub struct Document {
/// This field is set to the document itself for inert documents.
/// https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document
appropriate_template_contents_owner_document: MutNullableHeap<JS<Document>>,
- /// For each element that has had a state change since the last restyle, track the original state.
- modified_elements: DOMRefCell<HashMap<JS<Element>, ElementState>>,
+ /// For each element that has had a state or attribute change since the last restyle,
+ /// track the original condition of the element.
+ modified_elements: DOMRefCell<HashMap<JS<Element>, ElementSnapshot>>,
/// http://w3c.github.io/touch-events/#dfn-active-touch-point
active_touch_points: DOMRefCell<Vec<JS<Touch>>>,
}
@@ -1275,7 +1276,7 @@ pub enum DocumentSource {
#[allow(unsafe_code)]
pub trait LayoutDocumentHelpers {
unsafe fn is_html_document_for_layout(&self) -> bool;
- unsafe fn drain_modified_elements(&self) -> Vec<(LayoutJS<Element>, ElementState)>;
+ unsafe fn drain_modified_elements(&self) -> Vec<(LayoutJS<Element>, ElementSnapshot)>;
}
#[allow(unsafe_code)]
@@ -1287,7 +1288,7 @@ impl LayoutDocumentHelpers for LayoutJS<Document> {
#[inline]
#[allow(unrooted_must_root)]
- unsafe fn drain_modified_elements(&self) -> Vec<(LayoutJS<Element>, ElementState)> {
+ unsafe fn drain_modified_elements(&self) -> Vec<(LayoutJS<Element>, ElementSnapshot)> {
let mut elements = (*self.unsafe_get()).modified_elements.borrow_mut_for_layout();
let drain = elements.drain();
let layout_drain = drain.map(|(k, v)| (k.to_layout(), v));
@@ -1457,7 +1458,21 @@ impl Document {
pub fn element_state_will_change(&self, el: &Element) {
let mut map = self.modified_elements.borrow_mut();
- map.entry(JS::from_ref(el)).or_insert(el.get_state());
+ let snapshot = map.entry(JS::from_ref(el)).or_insert(ElementSnapshot::new());
+ if snapshot.state.is_none() {
+ snapshot.state = Some(el.get_state());
+ }
+ }
+
+ pub fn element_attr_will_change(&self, el: &Element) {
+ let mut map = self.modified_elements.borrow_mut();
+ let mut snapshot = map.entry(JS::from_ref(el)).or_insert(ElementSnapshot::new());
+ if snapshot.attrs.is_none() {
+ let attrs = el.attrs().iter()
+ .map(|attr| (attr.identifier().clone(), attr.value().clone()))
+ .collect();
+ snapshot.attrs = Some(attrs);
+ }
}
}
diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs
index 829383a4a62..4d0cf6fea54 100644
--- a/components/script/dom/element.rs
+++ b/components/script/dom/element.rs
@@ -65,6 +65,7 @@ use html5ever::serialize::TraversalScope;
use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};
use html5ever::tree_builder::{LimitedQuirks, NoQuirks, Quirks};
use selectors::matching::{DeclarationBlock, matches};
+use selectors::matching::{common_style_affecting_attributes, rare_style_affecting_attributes};
use selectors::parser::{AttrSelector, NamespaceConstraint, parse_author_origin_selector_list_from_str};
use selectors::states::*;
use smallvec::VecLike;
@@ -851,6 +852,7 @@ impl Element {
name: Atom,
namespace: Namespace,
prefix: Option<Atom>) {
+ self.will_mutate_attr();
let window = window_from_node(self);
let in_empty_ns = namespace == ns!("");
let attr = Attr::new(&window, local_name, value, name, namespace, prefix, Some(self));
@@ -963,6 +965,7 @@ impl Element {
let idx = self.attrs.borrow().iter().position(|attr| find(&attr));
idx.map(|idx| {
+ self.will_mutate_attr();
let attr = Root::from_ref(&*(*self.attrs.borrow())[idx]);
self.attrs.borrow_mut().remove(idx);
attr.set_owner(None);
@@ -1075,6 +1078,11 @@ impl Element {
assert!(&**local_name == local_name.to_ascii_lowercase());
self.set_attribute(local_name, AttrValue::UInt(DOMString(value.to_string()), value));
}
+
+ pub fn will_mutate_attr(&self) {
+ let node = self.upcast::<Node>();
+ node.owner_doc().element_attr_will_change(self);
+ }
}
impl ElementMethods for Element {
@@ -1459,6 +1467,10 @@ impl ElementMethods for Element {
}
}
+pub fn fragment_affecting_attributes() -> [Atom; 3] {
+ [atom!("width"), atom!("height"), atom!("src")]
+}
+
impl VirtualMethods for Element {
fn super_type(&self) -> Option<&VirtualMethods> {
Some(self.upcast::<Node>() as &VirtualMethods)
@@ -1468,18 +1480,16 @@ impl VirtualMethods for Element {
self.super_type().unwrap().attribute_mutated(attr, mutation);
let node = self.upcast::<Node>();
let doc = node.owner_doc();
- let damage = match attr.local_name() {
+ match attr.local_name() {
&atom!(style) => {
// Modifying the `style` attribute might change style.
*self.style_attribute.borrow_mut() =
mutation.new_value(attr).map(|value| {
parse_style_attribute(&value, &doc.base_url())
});
- NodeDamage::NodeStyleDamaged
- },
- &atom!(class) => {
- // Modifying a class can change style.
- NodeDamage::NodeStyleDamaged
+ if node.is_in_doc() {
+ doc.content_changed(node, NodeDamage::NodeStyleDamaged);
+ }
},
&atom!(id) => {
*self.id_attribute.borrow_mut() =
@@ -1510,16 +1520,22 @@ impl VirtualMethods for Element {
}
}
}
- NodeDamage::NodeStyleDamaged
},
- _ => {
- // Modifying any other attribute might change arbitrary things.
- NodeDamage::OtherNodeDamage
+ _ if attr.namespace() == &ns!("") => {
+ if fragment_affecting_attributes().iter().any(|a| a == attr.local_name()) ||
+ common_style_affecting_attributes().iter().any(|a| &a.atom == attr.local_name()) ||
+ rare_style_affecting_attributes().iter().any(|a| a == attr.local_name())
+ {
+ doc.content_changed(node, NodeDamage::OtherNodeDamage);
+ }
},
+ _ => {},
};
- if node.is_in_doc() {
- doc.content_changed(node, damage);
- }
+
+ // Make sure we rev the version even if we didn't dirty the node. If we
+ // don't do this, various attribute-dependent htmlcollections (like those
+ // generated by getElementsByClassName) might become stale.
+ node.rev_version();
}
fn parse_plain_attribute(&self, name: &Atom, value: DOMString) -> AttrValue {
diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs
index 49bb441e21e..3a8baa399d2 100644
--- a/components/script/dom/node.rs
+++ b/components/script/dom/node.rs
@@ -488,13 +488,7 @@ impl Node {
self.dirty_impl(damage, true)
}
- pub fn dirty(&self, damage: NodeDamage) {
- self.dirty_impl(damage, false)
- }
-
- pub fn dirty_impl(&self, damage: NodeDamage, force_ancestors: bool) {
-
- // 0. Set version counter
+ pub fn rev_version(&self) {
// The new version counter is 1 plus the max of the node's current version counter,
// its descendants version, and the document's version. Normally, this will just be
// the document's version, but we do have to deal with the case where the node has moved
@@ -505,6 +499,15 @@ impl Node {
ancestor.inclusive_descendants_version.set(version);
}
doc.inclusive_descendants_version.set(version);
+ }
+
+ pub fn dirty(&self, damage: NodeDamage) {
+ self.dirty_impl(damage, false)
+ }
+
+ pub fn dirty_impl(&self, damage: NodeDamage, force_ancestors: bool) {
+ // 0. Set version counter
+ self.rev_version();
// 1. Dirty self.
match damage {
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,
});
}
diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs
index 85dadac97d1..5a4b1734917 100644
--- a/components/style/selector_matching.rs
+++ b/components/style/selector_matching.rs
@@ -6,7 +6,7 @@ use legacy::PresentationalHintSynthesis;
use media_queries::{Device, MediaType};
use node::TElementAttributes;
use properties::{PropertyDeclaration, PropertyDeclarationBlock};
-use restyle_hints::{RestyleHint, StateDependencySet};
+use restyle_hints::{ElementSnapshot, RestyleHint, DependencySet};
use selectors::Element;
use selectors::bloom::BloomFilter;
use selectors::matching::DeclarationBlock as GenericDeclarationBlock;
@@ -95,8 +95,8 @@ pub struct Stylist {
after_map: PerPseudoElementSelectorMap,
rules_source_order: usize,
- // Selector state dependencies used to compute restyle hints.
- state_deps: StateDependencySet,
+ // Selector dependencies used to compute restyle hints.
+ state_deps: DependencySet,
}
impl Stylist {
@@ -112,7 +112,7 @@ impl Stylist {
before_map: PerPseudoElementSelectorMap::new(),
after_map: PerPseudoElementSelectorMap::new(),
rules_source_order: 0,
- state_deps: StateDependencySet::new(),
+ state_deps: DependencySet::new(),
};
// FIXME: Add iso-8859-9.css when the document’s encoding is ISO-8859-8.
stylist
@@ -204,12 +204,16 @@ impl Stylist {
self.rules_source_order = rules_source_order;
}
- pub fn restyle_hint_for_state_change<E>(&self, element: &E,
- current_state: ElementState,
- old_state: ElementState)
- -> RestyleHint
- where E: Element + Clone {
- self.state_deps.compute_hint(element, current_state, old_state)
+ pub fn compute_restyle_hint<E>(&self, element: &E,
+ snapshot: &ElementSnapshot,
+ // NB: We need to pass current_state as an argument because
+ // selectors::Element doesn't provide access to ElementState
+ // directly, and computing it from the ElementState would be
+ // more expensive than getting it directly from the caller.
+ current_state: ElementState)
+ -> RestyleHint
+ where E: Element + Clone {
+ self.state_deps.compute_hint(element, snapshot, current_state)
}
pub fn set_device(&mut self, mut device: Device, stylesheets: &[&Stylesheet]) {