Skip to content

Commit

Permalink
style: Avoid selector-matching when only the style attribute is changed.
Browse files Browse the repository at this point in the history
  • Loading branch information
emilio committed Feb 2, 2017
1 parent da89099 commit 8859aa0
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 41 deletions.
72 changes: 56 additions & 16 deletions components/style/data.rs
Expand Up @@ -9,7 +9,7 @@
use dom::TElement;
use properties::ComputedValues;
use properties::longhands::display::computed_value as display;
use restyle_hints::{RESTYLE_LATER_SIBLINGS, RestyleHint};
use restyle_hints::{RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RESTYLE_STYLE_ATTRIBUTE, RestyleHint};
use rule_tree::StrongRuleNode;
use selector_parser::{PseudoElement, RestyleDamage, Snapshot};
use std::collections::HashMap;
Expand Down Expand Up @@ -44,8 +44,8 @@ impl ComputedStyle {
}
}

// We manually implement Debug for ComputedStyle so tht we can avoid the verbose
// stringification of ComputedValues for normal logging.
// We manually implement Debug for ComputedStyle so that we can avoid the
// verbose stringification of ComputedValues for normal logging.
impl fmt::Debug for ComputedStyle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ComputedStyle {{ rules: {:?}, values: {{..}} }}", self.rules)
Expand All @@ -55,6 +55,13 @@ impl fmt::Debug for ComputedStyle {
type PseudoStylesInner = HashMap<PseudoElement, ComputedStyle,
BuildHasherDefault<::fnv::FnvHasher>>;

/// The rule nodes for each of the pseudo-elements of an element.
///
/// TODO(emilio): Probably shouldn't be a `HashMap` by default, but a smaller
/// array.
pub type PseudoRuleNodes = HashMap<PseudoElement, StrongRuleNode,
BuildHasherDefault<::fnv::FnvHasher>>;

/// A set of styles for a given element's pseudo-elements.
///
/// This is a map from pseudo-element to `ComputedStyle`.
Expand All @@ -69,6 +76,19 @@ impl PseudoStyles {
pub fn empty() -> Self {
PseudoStyles(HashMap::with_hasher(Default::default()))
}

/// Gets the rules that the different pseudo-elements matched.
///
/// FIXME(emilio): We could in theory avoid creating these when we have
/// support for just re-cascading an element. Then the input to
/// `cascade_node` could be `MatchResults` or just `UseExistingStyle`.
pub fn get_rules(&self) -> PseudoRuleNodes {
let mut rules = HashMap::with_hasher(Default::default());
for (pseudo, style) in &self.0 {
rules.insert(pseudo.clone(), style.rules.clone());
}
rules
}
}

impl Deref for PseudoStyles {
Expand Down Expand Up @@ -144,8 +164,11 @@ impl DescendantRestyleHint {
/// to provide more type safety while propagating restyle hints down the tree.
#[derive(Clone, Debug)]
pub struct StoredRestyleHint {
/// Whether this element should be restyled during the traversal.
pub restyle_self: bool,
/// Whether this element should be restyled during the traversal, and how.
///
/// This hint is stripped down, and only contains hints that are a subset of
/// RestyleHint::for_single_element().
pub for_self: RestyleHint,
/// Whether the descendants of this element need to be restyled.
pub descendants: DescendantRestyleHint,
}
Expand All @@ -154,15 +177,19 @@ impl StoredRestyleHint {
/// Propagates this restyle hint to a child element.
pub fn propagate(&self) -> Self {
StoredRestyleHint {
restyle_self: self.descendants != DescendantRestyleHint::Empty,
for_self: if self.descendants == DescendantRestyleHint::Empty {
RestyleHint::empty()
} else {
RESTYLE_SELF
},
descendants: self.descendants.propagate(),
}
}

/// Creates an empty `StoredRestyleHint`.
pub fn empty() -> Self {
StoredRestyleHint {
restyle_self: false,
for_self: RestyleHint::empty(),
descendants: DescendantRestyleHint::Empty,
}
}
Expand All @@ -171,29 +198,31 @@ impl StoredRestyleHint {
/// including the element.
pub fn subtree() -> Self {
StoredRestyleHint {
restyle_self: true,
for_self: RESTYLE_SELF,
descendants: DescendantRestyleHint::Descendants,
}
}

/// Whether the restyle hint is empty (nothing requires to be restyled).
pub fn is_empty(&self) -> bool {
!self.restyle_self && self.descendants == DescendantRestyleHint::Empty
!self.needs_restyle_self() && self.descendants == DescendantRestyleHint::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()
}

/// Insert another restyle hint, effectively resulting in the union of both.
pub fn insert(&mut self, other: &Self) {
self.restyle_self = self.restyle_self || other.restyle_self;
self.for_self |= other.for_self;
self.descendants = self.descendants.union(other.descendants);
}
}

impl Default for StoredRestyleHint {
fn default() -> Self {
StoredRestyleHint {
restyle_self: false,
descendants: DescendantRestyleHint::Empty,
}
StoredRestyleHint::empty()
}
}

Expand All @@ -203,7 +232,7 @@ impl From<RestyleHint> for StoredRestyleHint {
use self::DescendantRestyleHint::*;
debug_assert!(!hint.contains(RESTYLE_LATER_SIBLINGS), "Caller should apply sibling hints");
StoredRestyleHint {
restyle_self: hint.contains(RESTYLE_SELF) || hint.contains(RESTYLE_STYLE_ATTRIBUTE),
for_self: hint & RestyleHint::for_single_element(),
descendants: if hint.contains(RESTYLE_DESCENDANTS) { Descendants } else { Empty },
}
}
Expand Down Expand Up @@ -319,7 +348,9 @@ impl RestyleData {

/// Returns true if this RestyleData might invalidate the current style.
pub fn has_invalidations(&self) -> bool {
self.hint.restyle_self || self.recascade || self.snapshot.is_some()
self.hint.needs_restyle_self() ||
self.recascade ||
self.snapshot.is_some()
}
}

Expand Down Expand Up @@ -359,6 +390,15 @@ impl ElementData {
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 {
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)
}

/// Gets the element styles, if any.
pub fn get_styles(&self) -> Option<&ElementStyles> {
self.styles.as_ref()
Expand Down
48 changes: 37 additions & 11 deletions components/style/matching.rs
Expand Up @@ -13,19 +13,18 @@ use atomic_refcell::AtomicRefMut;
use cache::LRUCache;
use cascade_info::CascadeInfo;
use context::{SharedStyleContext, StyleContext};
use data::{ComputedStyle, ElementData, ElementStyles, PseudoStyles};
use data::{ComputedStyle, ElementData, ElementStyles, PseudoRuleNodes, PseudoStyles};
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 rule_tree::StrongRuleNode;
use rule_tree::{CascadeLevel, StrongRuleNode};
use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
use selectors::MatchAttr;
use selectors::bloom::BloomFilter;
use selectors::matching::{AFFECTED_BY_PSEUDO_ELEMENTS, MatchingReason, StyleRelations};
use selectors::matching::{AFFECTED_BY_PSEUDO_ELEMENTS, AFFECTED_BY_STYLE_ATTRIBUTE, MatchingReason, StyleRelations};
use servo_config::opts;
use sink::ForgetfulSink;
use std::collections::HashMap;
use std::hash::BuildHasherDefault;
use std::slice::IterMut;
use std::sync::Arc;
use stylist::ApplicableDeclarationBlock;
Expand Down Expand Up @@ -60,13 +59,6 @@ fn create_common_style_affecting_attributes_from_element<E: TElement>(element: &
flags
}

/// The rule nodes for each of the pseudo-elements of an element.
///
/// TODO(emilio): Probably shouldn't be a `HashMap` by default, but a smaller
/// array.
type PseudoRuleNodes = HashMap<PseudoElement, StrongRuleNode,
BuildHasherDefault<::fnv::FnvHasher>>;

/// The results of selector matching on an element.
pub struct MatchResults {
/// The rule node reference that represents the rules matched by the
Expand Down Expand Up @@ -635,6 +627,40 @@ 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<Self>,
data: &mut AtomicRefMut<ElementData>)
-> MatchResults {
let style_attribute = self.style_attribute();

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);

new_rule_node = context.shared.stylist.rule_tree
.replace_rule_at_level_if_applicable(CascadeLevel::StyleAttributeImportant,
style_attribute,
new_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?
relations: AFFECTED_BY_STYLE_ATTRIBUTE,
// The per-pseudo rule-nodes haven't changed, but still need to be
// recomputed.
//
// TODO(emilio): We could probably optimize this quite a bit.
per_pseudo: data.styles().pseudos.get_rules(),
}
}

/// Attempts to share a style with another node. This method is unsafe
/// because it depends on the `style_sharing_candidate_cache` having only
/// live nodes in it, and we have no way to guarantee that at the type
Expand Down
8 changes: 8 additions & 0 deletions components/style/restyle_hints.rs
Expand Up @@ -50,6 +50,14 @@ bitflags! {
}
}

impl RestyleHint {
/// The subset hints that affect the styling of a single element during the
/// traversal.
pub fn for_single_element() -> Self {
RESTYLE_SELF | RESTYLE_STYLE_ATTRIBUTE
}
}

#[cfg(feature = "gecko")]
impl From<nsRestyleHint> for RestyleHint {
fn from(raw: nsRestyleHint) -> Self {
Expand Down

0 comments on commit 8859aa0

Please sign in to comment.