diff --git a/components/style/data.rs b/components/style/data.rs index 43c2a045d79..c2807a2036b 100644 --- a/components/style/data.rs +++ b/components/style/data.rs @@ -9,7 +9,7 @@ use dom::TElement; use properties::ComputedValues; use properties::longhands::display::computed_value as display; -use restyle_hints::{RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RESTYLE_STYLE_ATTRIBUTE, RestyleHint}; +use restyle_hints::{RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint}; use rule_tree::StrongRuleNode; use selector_parser::{PseudoElement, RestyleDamage, Snapshot}; use std::collections::HashMap; @@ -168,7 +168,7 @@ pub struct StoredRestyleHint { /// /// This hint is stripped down, and only contains hints that are a subset of /// RestyleHint::for_single_element(). - pub for_self: RestyleHint, + pub self_: RestyleHint, /// Whether the descendants of this element need to be restyled. pub descendants: DescendantRestyleHint, } @@ -177,7 +177,7 @@ impl StoredRestyleHint { /// Propagates this restyle hint to a child element. pub fn propagate(&self) -> Self { StoredRestyleHint { - for_self: if self.descendants == DescendantRestyleHint::Empty { + self_: if self.descendants == DescendantRestyleHint::Empty { RestyleHint::empty() } else { RESTYLE_SELF @@ -189,7 +189,7 @@ impl StoredRestyleHint { /// Creates an empty `StoredRestyleHint`. pub fn empty() -> Self { StoredRestyleHint { - for_self: RestyleHint::empty(), + self_: RestyleHint::empty(), descendants: DescendantRestyleHint::Empty, } } @@ -198,24 +198,25 @@ impl StoredRestyleHint { /// including the element. pub fn subtree() -> Self { StoredRestyleHint { - for_self: RESTYLE_SELF, + self_: RESTYLE_SELF, descendants: DescendantRestyleHint::Descendants, } } - /// Whether the restyle hint is empty (nothing requires to be restyled). - pub fn is_empty(&self) -> bool { - !self.needs_restyle_self() && self.descendants == DescendantRestyleHint::Empty + /// Returns true if the hint indicates that our style may be invalidated. + pub fn has_self_invalidations(&self) -> bool { + !self.self_.is_empty() } - /// Whether the element holding the hint needs to be restyled on some way. - pub fn needs_restyle_self(&self) -> bool { - !self.for_self.is_empty() + /// Whether the restyle hint is empty (nothing requires to be restyled). + pub fn is_empty(&self) -> bool { + !self.has_self_invalidations() && + self.descendants == DescendantRestyleHint::Empty } /// Insert another restyle hint, effectively resulting in the union of both. pub fn insert(&mut self, other: &Self) { - self.for_self |= other.for_self; + self.self_ |= other.self_; self.descendants = self.descendants.union(other.descendants); } } @@ -232,7 +233,7 @@ impl From for StoredRestyleHint { use self::DescendantRestyleHint::*; debug_assert!(!hint.contains(RESTYLE_LATER_SIBLINGS), "Caller should apply sibling hints"); StoredRestyleHint { - for_self: hint & RestyleHint::for_single_element(), + self_: hint & RestyleHint::for_self(), descendants: if hint.contains(RESTYLE_DESCENDANTS) { Descendants } else { Empty }, } } @@ -348,7 +349,7 @@ impl RestyleData { /// Returns true if this RestyleData might invalidate the current style. pub fn has_invalidations(&self) -> bool { - self.hint.needs_restyle_self() || + self.hint.has_self_invalidations() || self.recascade || self.snapshot.is_some() } @@ -369,6 +370,19 @@ pub struct ElementData { restyle: Option>, } +/// The kind of restyle that a single element should do. +pub enum RestyleKind { + /// We need to run selector matching plus re-cascade, that is, a full + /// restyle. + MatchAndCascade, + /// We need to recascade with some replacement rule, such as the style + /// attribute, or animation rules. + CascadeWithReplacements(RestyleHint), + /// We only need to recascade, for example, because only inherited + /// properties in the parent changed. + CascadeOnly, +} + impl ElementData { /// Trivially construct an ElementData. pub fn new(existing: Option) -> Self { @@ -387,16 +401,31 @@ impl ElementData { /// invalidation. pub fn has_current_styles(&self) -> bool { self.has_styles() && - self.restyle.as_ref().map_or(true, |r| !r.has_invalidations()) + self.restyle.as_ref().map_or(true, |r| !r.has_invalidations()) } - /// Returns true if this element and its pseudo elements do not need - /// selector matching, and can just go on with a style attribute update in - /// the rule tree plus re-cascade. - pub fn needs_only_style_attribute_update(&self) -> bool { + /// Returns the kind of restyling that we're going to need to do on this + /// element, based of the stored restyle hint. + pub fn restyle_kind(&self) -> RestyleKind { debug_assert!(!self.has_current_styles(), "Should've stopped earlier"); - self.has_styles() && - self.restyle.as_ref().map_or(false, |r| r.hint.for_self == RESTYLE_STYLE_ATTRIBUTE) + if !self.has_styles() { + return RestyleKind::MatchAndCascade; + } + + debug_assert!(self.restyle.is_some()); + let restyle_data = self.restyle.as_ref().unwrap(); + let hint = restyle_data.hint.self_; + if hint.contains(RESTYLE_SELF) { + return RestyleKind::MatchAndCascade; + } + + if !hint.is_empty() { + return RestyleKind::CascadeWithReplacements(hint); + } + + debug_assert!(restyle_data.recascade, + "We definitely need to do something!"); + return RestyleKind::CascadeOnly; } /// Gets the element styles, if any. diff --git a/components/style/matching.rs b/components/style/matching.rs index 41df10f190e..bacb0905389 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -17,6 +17,7 @@ use data::{ComputedStyle, ElementData, ElementStyles, PseudoRuleNodes, PseudoSty use dom::{SendElement, TElement, TNode}; use properties::{CascadeFlags, ComputedValues, SHAREABLE, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade}; use properties::longhands::display::computed_value as display; +use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RestyleHint}; use rule_tree::{CascadeLevel, StrongRuleNode}; use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl}; use selectors::MatchAttr; @@ -627,31 +628,50 @@ pub trait MatchMethods : TElement { } } - /// Updates the style attribute rule nodes without re-running selector - /// matching, using just the rule tree. - fn update_style_attribute(&self, - context: &StyleContext, - data: &mut AtomicRefMut) - -> MatchResults { - let style_attribute = self.style_attribute(); + /// Get the appropriate MatchResults from the current styles, to perform a + /// recascade. + /// + /// TODO(emilio): Stop using `MachResults`, use an enum, or something. + fn match_results_from_current_style(&self, data: &ElementData) -> MatchResults { + let rule_node = data.styles().primary.rules.clone(); + MatchResults { + primary: rule_node, + // FIXME(emilio): Same concern as below. + relations: StyleRelations::empty(), + // The per-pseudo rule-nodes haven't changed, but still need to be + // recascaded. + per_pseudo: data.styles().pseudos.get_rules(), + } - let mut new_rule_node = data.styles().primary.rules.clone(); + } - new_rule_node = context.shared.stylist.rule_tree - .replace_rule_at_level_if_applicable(CascadeLevel::StyleAttributeNormal, - style_attribute, - new_rule_node); + /// Updates the rule nodes without re-running selector matching, using just + /// the rule tree. + fn cascade_with_replacements(&self, + hint: RestyleHint, + context: &StyleContext, + data: &mut AtomicRefMut) + -> MatchResults { + let mut rule_node = data.styles().primary.rules.clone(); - new_rule_node = context.shared.stylist.rule_tree - .replace_rule_at_level_if_applicable(CascadeLevel::StyleAttributeImportant, - style_attribute, - new_rule_node); + if hint.contains(RESTYLE_STYLE_ATTRIBUTE) { + let style_attribute = self.style_attribute(); + + rule_node = context.shared.stylist.rule_tree + .update_rule_at_level(CascadeLevel::StyleAttributeNormal, + style_attribute, + rule_node); + + rule_node = context.shared.stylist.rule_tree + .update_rule_at_level(CascadeLevel::StyleAttributeImportant, + style_attribute, + rule_node); + } MatchResults { - primary: new_rule_node, - // FIXME(emilio): This is ok, for now at least, because it disables - // style sharing. That being said, it's not ideal, probably should - // be optional? + primary: rule_node, + // FIXME(emilio): This is ok, for now, we shouldn't need to fake + // this. relations: AFFECTED_BY_STYLE_ATTRIBUTE, // The per-pseudo rule-nodes haven't changed, but still need to be // recomputed. @@ -675,6 +695,10 @@ pub trait MatchMethods : TElement { return StyleSharingResult::CannotShare } + if self.parent_element().is_none() { + return StyleSharingResult::CannotShare + } + if self.style_attribute().is_some() { return StyleSharingResult::CannotShare } diff --git a/components/style/restyle_hints.rs b/components/style/restyle_hints.rs index d1458b41646..cb0d889e7e9 100644 --- a/components/style/restyle_hints.rs +++ b/components/style/restyle_hints.rs @@ -53,7 +53,7 @@ bitflags! { impl RestyleHint { /// The subset hints that affect the styling of a single element during the /// traversal. - pub fn for_single_element() -> Self { + pub fn for_self() -> Self { RESTYLE_SELF | RESTYLE_STYLE_ATTRIBUTE } } diff --git a/components/style/rule_tree/mod.rs b/components/style/rule_tree/mod.rs index 7e044dab118..c93ff94a841 100644 --- a/components/style/rule_tree/mod.rs +++ b/components/style/rule_tree/mod.rs @@ -184,13 +184,11 @@ impl RuleTree { /// Replaces a rule in a given level (if present) for another rule. /// /// Returns the resulting node that represents the new path. - /// - /// TODO(emilio): Maybe this should be in `StrongRuleNode`? - pub fn replace_rule_at_level_if_applicable(&self, - level: CascadeLevel, - pdb: Option<&Arc>>, - path: StrongRuleNode) - -> StrongRuleNode { + pub fn update_rule_at_level(&self, + level: CascadeLevel, + pdb: Option<&Arc>>, + path: StrongRuleNode) + -> StrongRuleNode { debug_assert!(level.is_unique_per_element()); // TODO(emilio): Being smarter with lifetimes we could avoid a bit of // the refcount churn. diff --git a/components/style/traversal.rs b/components/style/traversal.rs index 959caf812b8..a438211495a 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -8,7 +8,7 @@ use atomic_refcell::{AtomicRefCell, AtomicRefMut}; use context::{SharedStyleContext, StyleContext, ThreadLocalStyleContext}; -use data::{ElementData, ElementStyles, StoredRestyleHint}; +use data::{ElementData, ElementStyles, RestyleKind, StoredRestyleHint}; use dom::{NodeInfo, TElement, TNode}; use matching::{MatchMethods, StyleSharingResult}; use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_SELF}; @@ -453,9 +453,6 @@ pub fn recalc_style_at(traversal: &D, // Computes style, returning true if the inherited styles changed for this // element. -// -// FIXME(bholley): This should differentiate between matching and cascading, -// since we have separate bits for each now. fn compute_style(_traversal: &D, traversal_data: &mut PerLevelTraversalData, context: &mut StyleContext, @@ -467,73 +464,74 @@ fn compute_style(_traversal: &D, context.thread_local.statistics.elements_styled += 1; let shared_context = context.shared; - // Ensure the bloom filter is up to date. - // - // TODO(emilio): In theory we could avoid a bit of this work in the style - // attribute case. - let dom_depth = context.thread_local.bloom_filter - .insert_parents_recovering(element, traversal_data.current_dom_depth); + // TODO(emilio): Make cascade_input less expensive to compute in the cases + // we don't need to run selector matching. + let cascade_input = match data.restyle_kind() { + RestyleKind::MatchAndCascade => { + // Check to see whether we can share a style with someone. + let sharing_result = unsafe { + element.share_style_if_possible(&mut context.thread_local.style_sharing_candidate_cache, + shared_context, + &mut data) + }; - // Update the dom depth with the up-to-date dom depth. - // - // Note that this is always the same than the pre-existing depth, but it can - // change from unknown to known at this step. - traversal_data.current_dom_depth = Some(dom_depth); - - context.thread_local.bloom_filter.assert_complete(element); - - // Check to see whether we can share a style with someone. - let sharing_result = if element.parent_element().is_none() { - StyleSharingResult::CannotShare - } else { - unsafe { element.share_style_if_possible(&mut context.thread_local.style_sharing_candidate_cache, - shared_context, &mut data) } - }; - - // Otherwise, match and cascade selectors. - match sharing_result { - StyleSharingResult::CannotShare => { - let match_results; - let shareable_element = { - // Perform the CSS selector matching. - context.thread_local.statistics.elements_matched += 1; - - // TODO(emilio): Here we'll need to support animation-only hints - // and similar. - match_results = if data.needs_only_style_attribute_update() { - element.update_style_attribute(context, data) - } else { - let filter = context.thread_local.bloom_filter.filter(); - element.match_element(context, Some(filter)) - }; - if match_results.primary_is_shareable() { - Some(element) - } else { + match sharing_result { + StyleSharingResult::StyleWasShared(index) => { + context.thread_local.statistics.styles_shared += 1; + context.thread_local.style_sharing_candidate_cache.touch(index); None } - }; - let relations = match_results.relations; + StyleSharingResult::CannotShare => { + // Ensure the bloom filter is up to date. + let dom_depth = + context.thread_local.bloom_filter + .insert_parents_recovering(element, + traversal_data.current_dom_depth); - // Perform the CSS cascade. - unsafe { - let shareable = match_results.primary_is_shareable(); - element.cascade_node(context, &mut data, - element.parent_element(), - match_results.primary, - match_results.per_pseudo, - shareable); - } + // Update the dom depth with the up-to-date dom depth. + // + // Note that this is always the same than the pre-existing depth, + // but it can change from unknown to known at this step. + traversal_data.current_dom_depth = Some(dom_depth); - // Add ourselves to the LRU cache. - if let Some(element) = shareable_element { - context.thread_local - .style_sharing_candidate_cache - .insert_if_possible(&element, &data.styles().primary.values, relations); + context.thread_local.bloom_filter.assert_complete(element); + + // Perform the CSS selector matching. + context.thread_local.statistics.elements_matched += 1; + + let filter = context.thread_local.bloom_filter.filter(); + Some(element.match_element(context, Some(filter))) + } } } - StyleSharingResult::StyleWasShared(index) => { - context.thread_local.statistics.styles_shared += 1; - context.thread_local.style_sharing_candidate_cache.touch(index); + RestyleKind::CascadeWithReplacements(hint) => { + Some(element.cascade_with_replacements(hint, context, &mut data)) + } + RestyleKind::CascadeOnly => { + // TODO(emilio): Stop doing this work, and teach cascade_node about + // the current style instead. + Some(element.match_results_from_current_style(&*data)) + } + }; + + if let Some(match_results) = cascade_input { + // Perform the CSS cascade. + let shareable = match_results.primary_is_shareable(); + unsafe { + element.cascade_node(context, &mut data, + element.parent_element(), + match_results.primary, + match_results.per_pseudo, + shareable); + } + + if shareable { + // Add ourselves to the LRU cache. + context.thread_local + .style_sharing_candidate_cache + .insert_if_possible(&element, + &data.styles().primary.values, + match_results.relations); } }