diff options
-rw-r--r-- | components/layout/layout_task.rs | 6 | ||||
-rw-r--r-- | components/layout/wrapper.rs | 19 | ||||
-rw-r--r-- | components/script/dom/attr.rs | 5 | ||||
-rw-r--r-- | components/script/dom/bindings/trace.rs | 2 | ||||
-rw-r--r-- | components/script/dom/document.rs | 27 | ||||
-rw-r--r-- | components/script/dom/element.rs | 42 | ||||
-rw-r--r-- | components/script/dom/node.rs | 17 | ||||
-rw-r--r-- | components/style/restyle_hints.rs | 196 | ||||
-rw-r--r-- | components/style/selector_matching.rs | 24 |
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]) { |