Auto merge of #18496 - bholley:2ndpass, r=emilio

do a second pass on the style sharing cache after computing the rule node

https://bugzilla.mozilla.org/show_bug.cgi?id=1397976
This commit is contained in:
bors-servo 2017-09-14 00:07:52 -05:00 committed by GitHub
commit 298b1363ff
9 changed files with 346 additions and 126 deletions

View file

@ -313,6 +313,9 @@ pub struct TraversalStatistics {
pub elements_matched: u32, pub elements_matched: u32,
/// The number of cache hits from the StyleSharingCache. /// The number of cache hits from the StyleSharingCache.
pub styles_shared: u32, pub styles_shared: u32,
/// The number of styles reused via rule node comparison from the
/// StyleSharingCache.
pub styles_reused: u32,
/// The number of selectors in the stylist. /// The number of selectors in the stylist.
pub selectors: u32, pub selectors: u32,
/// The number of revalidation selectors. /// The number of revalidation selectors.
@ -347,6 +350,7 @@ impl<'a> ops::Add for &'a TraversalStatistics {
elements_styled: self.elements_styled + other.elements_styled, elements_styled: self.elements_styled + other.elements_styled,
elements_matched: self.elements_matched + other.elements_matched, elements_matched: self.elements_matched + other.elements_matched,
styles_shared: self.styles_shared + other.styles_shared, styles_shared: self.styles_shared + other.styles_shared,
styles_reused: self.styles_reused + other.styles_reused,
selectors: 0, selectors: 0,
revalidation_selectors: 0, revalidation_selectors: 0,
dependency_selectors: 0, dependency_selectors: 0,
@ -374,6 +378,7 @@ impl fmt::Display for TraversalStatistics {
writeln!(f, "[PERF],elements_styled,{}", self.elements_styled)?; writeln!(f, "[PERF],elements_styled,{}", self.elements_styled)?;
writeln!(f, "[PERF],elements_matched,{}", self.elements_matched)?; writeln!(f, "[PERF],elements_matched,{}", self.elements_matched)?;
writeln!(f, "[PERF],styles_shared,{}", self.styles_shared)?; writeln!(f, "[PERF],styles_shared,{}", self.styles_shared)?;
writeln!(f, "[PERF],styles_reused,{}", self.styles_reused)?;
writeln!(f, "[PERF],selectors,{}", self.selectors)?; writeln!(f, "[PERF],selectors,{}", self.selectors)?;
writeln!(f, "[PERF],revalidation_selectors,{}", self.revalidation_selectors)?; writeln!(f, "[PERF],revalidation_selectors,{}", self.revalidation_selectors)?;
writeln!(f, "[PERF],dependency_selectors,{}", self.dependency_selectors)?; writeln!(f, "[PERF],dependency_selectors,{}", self.dependency_selectors)?;

View file

@ -17,11 +17,14 @@ use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage};
use servo_arc::Arc; use servo_arc::Arc;
use shared_lock::StylesheetGuards; use shared_lock::StylesheetGuards;
use std::fmt; use std::fmt;
use std::mem;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use style_resolver::{PrimaryStyle, ResolvedElementStyles, ResolvedStyle};
bitflags! { bitflags! {
/// Various flags stored on ElementData.
#[derive(Default)] #[derive(Default)]
flags ElementDataFlags: u8 { pub flags ElementDataFlags: u8 {
/// Whether the styles changed for this restyle. /// Whether the styles changed for this restyle.
const WAS_RESTYLED = 1 << 0, const WAS_RESTYLED = 1 << 0,
/// Whether the last traversal of this element did not do /// Whether the last traversal of this element did not do
@ -35,6 +38,13 @@ bitflags! {
const TRAVERSED_WITHOUT_STYLING = 1 << 1, const TRAVERSED_WITHOUT_STYLING = 1 << 1,
/// Whether we reframed/reconstructed any ancestor or self. /// Whether we reframed/reconstructed any ancestor or self.
const ANCESTOR_WAS_RECONSTRUCTED = 1 << 2, const ANCESTOR_WAS_RECONSTRUCTED = 1 << 2,
/// Whether the primary style of this element data was reused from another
/// element via a rule node comparison. This allows us to differentiate
/// between elements that shared styles because they met all the criteria
/// of the style sharing cache, compared to elements that reused style
/// structs via rule node identity. The former gives us stronger transitive
/// guarantees that allows us to apply the style sharing cache to cousins.
const PRIMARY_STYLE_REUSED_VIA_RULE_NODE = 1 << 3,
} }
} }
@ -200,7 +210,7 @@ pub struct ElementData {
pub hint: RestyleHint, pub hint: RestyleHint,
/// Flags. /// Flags.
flags: ElementDataFlags, pub flags: ElementDataFlags,
} }
/// The kind of restyle that a single element should do. /// The kind of restyle that a single element should do.
@ -264,6 +274,30 @@ impl ElementData {
self.styles.primary.is_some() self.styles.primary.is_some()
} }
/// Returns this element's styles as resolved styles to use for sharing.
pub fn share_styles(&self) -> ResolvedElementStyles {
ResolvedElementStyles {
primary: self.share_primary_style(),
pseudos: self.styles.pseudos.clone(),
}
}
/// Returns this element's primary style as a resolved style to use for sharing.
pub fn share_primary_style(&self) -> PrimaryStyle {
let primary_is_reused = self.flags.contains(PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
PrimaryStyle(ResolvedStyle::new(self.styles.primary().clone(), primary_is_reused))
}
/// Sets a new set of styles, returning the old ones.
pub fn set_styles(&mut self, new_styles: ResolvedElementStyles) -> ElementStyles {
if new_styles.primary.0.reused_via_rule_node {
self.flags.insert(PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
} else {
self.flags.remove(PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
}
mem::replace(&mut self.styles, new_styles.into())
}
/// Returns the kind of restyling that we're going to need to do on this /// Returns the kind of restyling that we're going to need to do on this
/// element, based of the stored restyle hint. /// element, based of the stored restyle hint.
pub fn restyle_kind( pub fn restyle_kind(
@ -422,6 +456,28 @@ impl ElementData {
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
pub fn skip_applying_damage(&self) -> bool { false } pub fn skip_applying_damage(&self) -> bool { false }
/// Returns whether it is safe to perform cousin sharing based on the ComputedValues
/// identity of the primary style in this ElementData. There are a few subtle things
/// to check.
///
/// First, if a parent element was already styled and we traversed past it without
/// restyling it, that may be because our clever invalidation logic was able to prove
/// that the styles of that element would remain unchanged despite changes to the id
/// or class attributes. However, style sharing relies on the strong guarantee that all
/// the classes and ids up the respective parent chains are identical. As such, if we
/// skipped styling for one (or both) of the parents on this traversal, we can't share
/// styles across cousins. Note that this is a somewhat conservative check. We could
/// tighten it by having the invalidation logic explicitly flag elements for which it
/// ellided styling.
///
/// Second, we want to only consider elements whose ComputedValues match due to a hit
/// in the style sharing cache, rather than due to the rule-node-based reuse that
/// happens later in the styling pipeline. The former gives us the stronger guarantees
/// we need for style sharing, the latter does not.
pub fn safe_for_cousin_sharing(&self) -> bool {
!self.flags.intersects(TRAVERSED_WITHOUT_STYLING | PRIMARY_STYLE_REUSED_VIA_RULE_NODE)
}
/// Measures memory usage. /// Measures memory usage.
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
pub fn size_of_excluding_cvs(&self, ops: &mut MallocSizeOfOps) -> usize { pub fn size_of_excluding_cvs(&self, ops: &mut MallocSizeOfOps) -> usize {

View file

@ -8,7 +8,7 @@
#![deny(missing_docs)] #![deny(missing_docs)]
use context::{ElementCascadeInputs, SelectorFlagsMap, SharedStyleContext, StyleContext}; use context::{ElementCascadeInputs, SelectorFlagsMap, SharedStyleContext, StyleContext};
use data::{ElementData, ElementStyles}; use data::ElementData;
use dom::TElement; use dom::TElement;
use invalidation::element::restyle_hints::{RESTYLE_CSS_ANIMATIONS, RESTYLE_CSS_TRANSITIONS}; use invalidation::element::restyle_hints::{RESTYLE_CSS_ANIMATIONS, RESTYLE_CSS_TRANSITIONS};
use invalidation::element::restyle_hints::{RESTYLE_SMIL, RESTYLE_STYLE_ATTRIBUTE}; use invalidation::element::restyle_hints::{RESTYLE_SMIL, RESTYLE_STYLE_ATTRIBUTE};
@ -18,6 +18,7 @@ use rule_tree::{CascadeLevel, StrongRuleNode};
use selector_parser::{PseudoElement, RestyleDamage}; use selector_parser::{PseudoElement, RestyleDamage};
use selectors::matching::ElementSelectorFlags; use selectors::matching::ElementSelectorFlags;
use servo_arc::{Arc, ArcBorrow}; use servo_arc::{Arc, ArcBorrow};
use style_resolver::ResolvedElementStyles;
use traversal_flags; use traversal_flags;
/// Represents the result of comparing an element's old and new style. /// Represents the result of comparing an element's old and new style.
@ -156,7 +157,7 @@ trait PrivateMatchMethods: TElement {
StyleResolverForElement::new(*self, context, RuleInclusion::All, PseudoElementResolution::IfApplicable) StyleResolverForElement::new(*self, context, RuleInclusion::All, PseudoElementResolution::IfApplicable)
.cascade_style_and_visited_with_default_parents(inputs); .cascade_style_and_visited_with_default_parents(inputs);
Some(style) Some(style.into())
} }
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
@ -530,26 +531,23 @@ pub trait MatchMethods : TElement {
&self, &self,
context: &mut StyleContext<Self>, context: &mut StyleContext<Self>,
data: &mut ElementData, data: &mut ElementData,
mut new_styles: ElementStyles, mut new_styles: ResolvedElementStyles,
important_rules_changed: bool, important_rules_changed: bool,
) -> ChildCascadeRequirement { ) -> ChildCascadeRequirement {
use app_units::Au; use app_units::Au;
use dom::TNode; use dom::TNode;
use std::cmp; use std::cmp;
use std::mem;
debug_assert!(new_styles.primary.is_some(), "How did that happen?");
self.process_animations( self.process_animations(
context, context,
&mut data.styles.primary, &mut data.styles.primary,
&mut new_styles.primary.as_mut().unwrap(), &mut new_styles.primary.0.style,
data.hint, data.hint,
important_rules_changed, important_rules_changed,
); );
// First of all, update the styles. // First of all, update the styles.
let old_styles = mem::replace(&mut data.styles, new_styles); let old_styles = data.set_styles(new_styles);
// Propagate the "can be fragmented" bit. It would be nice to // Propagate the "can be fragmented" bit. It would be nice to
// encapsulate this better. // encapsulate this better.

View file

@ -1997,6 +1997,13 @@ pub struct ComputedValues {
inner: ComputedValuesInner, inner: ComputedValuesInner,
} }
impl ComputedValues {
/// Returns the visited rules, if applicable.
pub fn visited_rules(&self) -> Option<<&StrongRuleNode> {
self.visited_style.as_ref().and_then(|s| s.rules.as_ref())
}
}
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
impl ComputedValues { impl ComputedValues {
/// Create a new refcounted `ComputedValues` /// Create a new refcounted `ComputedValues`

View file

@ -10,26 +10,30 @@ use Atom;
use bloom::StyleBloom; use bloom::StyleBloom;
use context::{SelectorFlagsMap, SharedStyleContext}; use context::{SelectorFlagsMap, SharedStyleContext};
use dom::TElement; use dom::TElement;
use servo_arc::Arc;
use sharing::{StyleSharingCandidate, StyleSharingTarget}; use sharing::{StyleSharingCandidate, StyleSharingTarget};
/// Whether styles may be shared across the children of the given parent elements. /// Determines whether a target and a candidate have compatible parents for sharing.
/// This is used to share style across cousins. pub fn parents_allow_sharing<E>(
/// target: &mut StyleSharingTarget<E>,
/// Both elements need to be styled already. candidate: &mut StyleSharingCandidate<E>
pub fn can_share_style_across_parents<E>(first: Option<E>, second: Option<E>) -> bool ) -> bool
where E: TElement, where E: TElement,
{ {
let (first, second) = match (first, second) { // If the identity of the parent style isn't equal, we can't share. We check
(Some(f), Some(s)) => (f, s), // this first, because the result is cached.
_ => return false, if target.parent_style_identity() != candidate.parent_style_identity() {
}; return false;
}
debug_assert_ne!(first, second); // Siblings can always share.
let parent = target.inheritance_parent().unwrap();
let first_data = first.borrow_data().unwrap(); let candidate_parent = candidate.element.inheritance_parent().unwrap();
let second_data = second.borrow_data().unwrap(); if parent == candidate_parent {
return true;
}
// Cousins are a bit more complicated.
//
// If a parent element was already styled and we traversed past it without // If a parent element was already styled and we traversed past it without
// restyling it, that may be because our clever invalidation logic was able // restyling it, that may be because our clever invalidation logic was able
// to prove that the styles of that element would remain unchanged despite // to prove that the styles of that element would remain unchanged despite
@ -40,14 +44,13 @@ pub fn can_share_style_across_parents<E>(first: Option<E>, second: Option<E>) ->
// //
// This is a somewhat conservative check. We could tighten it by having the // This is a somewhat conservative check. We could tighten it by having the
// invalidation logic explicitly flag elements for which it ellided styling. // invalidation logic explicitly flag elements for which it ellided styling.
if first_data.traversed_without_styling() || second_data.traversed_without_styling() { let parent_data = parent.borrow_data().unwrap();
let candidate_parent_data = candidate_parent.borrow_data().unwrap();
if !parent_data.safe_for_cousin_sharing() || !candidate_parent_data.safe_for_cousin_sharing() {
return false; return false;
} }
let same_computed_values = true
Arc::ptr_eq(first_data.styles.primary(), second_data.styles.primary());
same_computed_values
} }
/// Whether two elements have the same same style attribute (by pointer identity). /// Whether two elements have the same same style attribute (by pointer identity).

View file

@ -70,13 +70,13 @@ use atomic_refcell::{AtomicRefCell, AtomicRefMut};
use bloom::StyleBloom; use bloom::StyleBloom;
use cache::LRUCache; use cache::LRUCache;
use context::{SelectorFlagsMap, SharedStyleContext, StyleContext}; use context::{SelectorFlagsMap, SharedStyleContext, StyleContext};
use data::ElementStyles;
use dom::{TElement, SendElement}; use dom::{TElement, SendElement};
use matching::MatchMethods; use matching::MatchMethods;
use owning_ref::OwningHandle; use owning_ref::OwningHandle;
use properties::ComputedValues; use properties::ComputedValues;
use rule_tree::StrongRuleNode;
use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode}; use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode};
use servo_arc::Arc; use servo_arc::{Arc, NonZeroPtrMut};
use smallbitvec::SmallBitVec; use smallbitvec::SmallBitVec;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::marker::PhantomData; use std::marker::PhantomData;
@ -108,6 +108,18 @@ pub enum StyleSharingBehavior {
Disallow, Disallow,
} }
/// Opaque pointer type to compare ComputedValues identities.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OpaqueComputedValues(NonZeroPtrMut<()>);
impl OpaqueComputedValues {
fn from(cv: &ComputedValues) -> Self {
let p = NonZeroPtrMut::new(cv as *const ComputedValues as *const () as *mut ());
OpaqueComputedValues(p)
}
fn eq(&self, cv: &ComputedValues) -> bool { Self::from(cv) == *self }
}
/// Some data we want to avoid recomputing all the time while trying to share /// Some data we want to avoid recomputing all the time while trying to share
/// style. /// style.
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -121,6 +133,9 @@ pub struct ValidationData {
/// The list of presentational attributes of the element. /// The list of presentational attributes of the element.
pres_hints: Option<SmallVec<[ApplicableDeclarationBlock; 5]>>, pres_hints: Option<SmallVec<[ApplicableDeclarationBlock; 5]>>,
/// The pointer identity of the parent ComputedValues.
parent_style_identity: Option<OpaqueComputedValues>,
/// The cached result of matching this entry against the revalidation /// The cached result of matching this entry against the revalidation
/// selectors. /// selectors.
revalidation_match_results: Option<SmallBitVec>, revalidation_match_results: Option<SmallBitVec>,
@ -167,6 +182,18 @@ impl ValidationData {
&*self.class_list.as_ref().unwrap() &*self.class_list.as_ref().unwrap()
} }
/// Get or compute the parent style identity.
pub fn parent_style_identity<E>(&mut self, el: E) -> OpaqueComputedValues
where E: TElement,
{
if self.parent_style_identity.is_none() {
let parent = el.inheritance_parent().unwrap();
self.parent_style_identity =
Some(OpaqueComputedValues::from(parent.borrow_data().unwrap().styles.primary()));
}
self.parent_style_identity.as_ref().unwrap().clone()
}
/// Computes the revalidation results if needed, and returns it. /// Computes the revalidation results if needed, and returns it.
/// Inline so we know at compile time what bloom_known_valid is. /// Inline so we know at compile time what bloom_known_valid is.
#[inline] #[inline]
@ -250,6 +277,11 @@ impl<E: TElement> StyleSharingCandidate<E> {
self.validation_data.pres_hints(self.element) self.validation_data.pres_hints(self.element)
} }
/// Get the parent style identity.
fn parent_style_identity(&mut self) -> OpaqueComputedValues {
self.validation_data.parent_style_identity(self.element)
}
/// Compute the bit vector of revalidation selector match results /// Compute the bit vector of revalidation selector match results
/// for this candidate. /// for this candidate.
fn revalidation_match_results( fn revalidation_match_results(
@ -304,6 +336,11 @@ impl<E: TElement> StyleSharingTarget<E> {
self.validation_data.pres_hints(self.element) self.validation_data.pres_hints(self.element)
} }
/// Get the parent style identity.
fn parent_style_identity(&mut self) -> OpaqueComputedValues {
self.validation_data.parent_style_identity(self.element)
}
fn revalidation_match_results( fn revalidation_match_results(
&mut self, &mut self,
stylist: &Stylist, stylist: &Stylist,
@ -342,7 +379,7 @@ impl<E: TElement> StyleSharingTarget<E> {
pub fn share_style_if_possible( pub fn share_style_if_possible(
&mut self, &mut self,
context: &mut StyleContext<E>, context: &mut StyleContext<E>,
) -> Option<ElementStyles> { ) -> Option<E> {
let cache = &mut context.thread_local.sharing_cache; let cache = &mut context.thread_local.sharing_cache;
let shared_context = &context.shared; let shared_context = &context.shared;
let selector_flags_map = &mut context.thread_local.selector_flags; let selector_flags_map = &mut context.thread_local.selector_flags;
@ -393,14 +430,19 @@ impl<Candidate> SharingCacheBase<Candidate> {
} }
impl<E: TElement> SharingCache<E> { impl<E: TElement> SharingCache<E> {
fn insert(&mut self, el: E, validation_data_holder: &mut StyleSharingTarget<E>) { fn insert(
self.entries.insert(StyleSharingCandidate { &mut self,
element: el, element: E,
validation_data: validation_data_holder.take_validation_data(), validation_data_holder: Option<&mut StyleSharingTarget<E>>,
}); ) {
let validation_data = match validation_data_holder {
Some(v) => v.take_validation_data(),
None => ValidationData::default(),
};
self.entries.insert(StyleSharingCandidate { element, validation_data });
} }
fn lookup<F>(&mut self, mut is_match: F) -> Option<ElementStyles> fn lookup<F>(&mut self, mut is_match: F) -> Option<E>
where where
F: FnMut(&mut StyleSharingCandidate<E>) -> bool F: FnMut(&mut StyleSharingCandidate<E>) -> bool
{ {
@ -418,7 +460,7 @@ impl<E: TElement> SharingCache<E> {
self.entries.touch(i); self.entries.touch(i);
let front = self.entries.front_mut().unwrap(); let front = self.entries.front_mut().unwrap();
debug_assert!(is_match(front)); debug_assert!(is_match(front));
Some(front.element.borrow_data().unwrap().styles.clone()) Some(front.element)
} }
} }
} }
@ -507,7 +549,7 @@ impl<E: TElement> StyleSharingCache<E> {
pub fn insert_if_possible(&mut self, pub fn insert_if_possible(&mut self,
element: &E, element: &E,
style: &ComputedValues, style: &ComputedValues,
validation_data_holder: &mut StyleSharingTarget<E>, validation_data_holder: Option<&mut StyleSharingTarget<E>>,
dom_depth: usize) { dom_depth: usize) {
let parent = match element.traversal_parent() { let parent = match element.traversal_parent() {
Some(element) => element, Some(element) => element,
@ -575,14 +617,14 @@ impl<E: TElement> StyleSharingCache<E> {
selector_flags_map: &mut SelectorFlagsMap<E>, selector_flags_map: &mut SelectorFlagsMap<E>,
bloom_filter: &StyleBloom<E>, bloom_filter: &StyleBloom<E>,
target: &mut StyleSharingTarget<E>, target: &mut StyleSharingTarget<E>,
) -> Option<ElementStyles> { ) -> Option<E> {
if shared_context.options.disable_style_sharing_cache { if shared_context.options.disable_style_sharing_cache {
debug!("{:?} Cannot share style: style sharing cache disabled", debug!("{:?} Cannot share style: style sharing cache disabled",
target.element); target.element);
return None; return None;
} }
if target.traversal_parent().is_none() { if target.inheritance_parent().is_none() {
debug!("{:?} Cannot share style: element has no parent", debug!("{:?} Cannot share style: element has no parent",
target.element); target.element);
return None; return None;
@ -615,10 +657,7 @@ impl<E: TElement> StyleSharingCache<E> {
// share styles and permit sharing across their children. The latter // share styles and permit sharing across their children. The latter
// check allows us to share style between cousins if the parents // check allows us to share style between cousins if the parents
// shared style. // shared style.
let parent = target.traversal_parent(); if !checks::parents_allow_sharing(target, candidate) {
let candidate_parent = candidate.element.traversal_parent();
if parent != candidate_parent &&
!checks::can_share_style_across_parents(parent, candidate_parent) {
trace!("Miss: Parent"); trace!("Miss: Parent");
return false; return false;
} }
@ -699,4 +738,44 @@ impl<E: TElement> StyleSharingCache<E> {
true true
} }
/// Attempts to find an element in the cache with the given primary rule node and parent.
pub fn lookup_by_rules(
&mut self,
inherited: &ComputedValues,
rules: &StrongRuleNode,
visited_rules: Option<&StrongRuleNode>,
target: E,
) -> Option<E> {
self.cache_mut().lookup(|candidate| {
if !candidate.parent_style_identity().eq(inherited) {
return false;
}
let data = candidate.element.borrow_data().unwrap();
let style = data.styles.primary();
if style.rules.as_ref() != Some(&rules) {
return false;
}
if style.visited_rules() != visited_rules {
return false;
}
// Rule nodes and styles are computed independent of the element's
// actual visitedness, but at the end of the cascade (in
// `adjust_for_visited`) we do store the visitedness as a flag in
// style. (This is a subtle change from initial visited work that
// landed when computed values were fused, see
// https://bugzilla.mozilla.org/show_bug.cgi?id=1381635.)
// So at the moment, we need to additionally compare visitedness,
// since that is not accounted for by rule nodes alone.
// FIXME(jryans): This seems like it breaks the constant time
// requirements of visited, assuming we get a cache hit on only one
// of unvisited vs. visited.
if target.is_visited_link() != candidate.element.is_visited_link() {
return false;
}
true
})
}
} }

View file

@ -49,10 +49,52 @@ struct MatchingResults {
relevant_link_found: bool, relevant_link_found: bool,
} }
/// The primary style of an element or an element-backed pseudo-element. /// A style returned from the resolver machinery.
pub struct PrimaryStyle { pub struct ResolvedStyle {
/// The style per se. /// The style itself.
pub style: Arc<ComputedValues>, pub style: Arc<ComputedValues>,
/// Whether the style was reused from another element via the rule node.
pub reused_via_rule_node: bool,
}
/// The primary style of an element or an element-backed pseudo-element.
pub struct PrimaryStyle(pub ResolvedStyle);
/// A set of style returned from the resolver machinery.
pub struct ResolvedElementStyles {
/// Primary style.
pub primary: PrimaryStyle,
/// Pseudo styles.
pub pseudos: EagerPseudoStyles,
}
impl ResolvedStyle {
/// Creates a new ResolvedStyle.
pub fn new(style: Arc<ComputedValues>, reused_via_rule_node: bool) -> Self {
ResolvedStyle { style, reused_via_rule_node }
}
}
impl PrimaryStyle {
/// Convenience accessor for the style.
pub fn style(&self) -> &ComputedValues {
&*self.0.style
}
}
impl From<ResolvedStyle> for Arc<ComputedValues> {
fn from(r: ResolvedStyle) -> Arc<ComputedValues> {
r.style
}
}
impl From<ResolvedElementStyles> for ElementStyles {
fn from(r: ResolvedElementStyles) -> ElementStyles {
ElementStyles {
primary: Some(r.primary.0.into()),
pseudos: r.pseudos,
}
}
} }
fn with_default_parent_styles<E, F, R>(element: E, f: F) -> R fn with_default_parent_styles<E, F, R>(element: E, f: F) -> R
@ -140,41 +182,25 @@ where
None None
}; };
let mut visited_style = None; PrimaryStyle(
let should_compute_visited_style = self.cascade_style_and_visited(
relevant_link_found || CascadeInputs {
parent_style.and_then(|s| s.get_visited_style()).is_some(); rules: Some(primary_results.rule_node),
visited_rules,
let pseudo = self.element.implemented_pseudo_element(); },
if should_compute_visited_style {
visited_style = Some(self.cascade_style(
visited_rules.as_ref().or(Some(&primary_results.rule_node)),
/* style_if_visited = */ None,
parent_style, parent_style,
layout_parent_style, layout_parent_style,
CascadeVisitedMode::Visited, /* pseudo = */ None,
/* pseudo = */ pseudo.as_ref(), )
)); )
}
let style = self.cascade_style(
Some(&primary_results.rule_node),
visited_style,
parent_style,
layout_parent_style,
CascadeVisitedMode::Unvisited,
/* pseudo = */ pseudo.as_ref(),
);
PrimaryStyle { style, }
} }
/// Resolve the style of a given element, and all its eager pseudo-elements. /// Resolve the style of a given element, and all its eager pseudo-elements.
pub fn resolve_style( pub fn resolve_style(
&mut self, &mut self,
parent_style: Option<&ComputedValues>, parent_style: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>,
) -> ElementStyles { ) -> ResolvedElementStyles {
let primary_style = let primary_style =
self.resolve_primary_style(parent_style, layout_parent_style); self.resolve_primary_style(parent_style, layout_parent_style);
@ -182,10 +208,10 @@ where
if self.element.implemented_pseudo_element().is_none() { if self.element.implemented_pseudo_element().is_none() {
let layout_parent_style_for_pseudo = let layout_parent_style_for_pseudo =
if primary_style.style.is_display_contents() { if primary_style.style().is_display_contents() {
layout_parent_style layout_parent_style
} else { } else {
Some(&*primary_style.style) Some(primary_style.style())
}; };
SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
let pseudo_style = self.resolve_pseudo_style( let pseudo_style = self.resolve_pseudo_style(
@ -204,16 +230,15 @@ where
}) })
} }
ElementStyles { ResolvedElementStyles {
// FIXME(emilio): Remove the Option<>. primary: primary_style,
primary: Some(primary_style.style),
pseudos: pseudo_styles, pseudos: pseudo_styles,
} }
} }
/// Resolve an element's styles with the default inheritance parent/layout /// Resolve an element's styles with the default inheritance parent/layout
/// parents. /// parents.
pub fn resolve_style_with_default_parents(&mut self) -> ElementStyles { pub fn resolve_style_with_default_parents(&mut self) -> ResolvedElementStyles {
with_default_parent_styles(self.element, |parent_style, layout_parent_style| { with_default_parent_styles(self.element, |parent_style, layout_parent_style| {
self.resolve_style(parent_style, layout_parent_style) self.resolve_style(parent_style, layout_parent_style)
}) })
@ -223,7 +248,7 @@ where
pub fn cascade_style_and_visited_with_default_parents( pub fn cascade_style_and_visited_with_default_parents(
&mut self, &mut self,
inputs: CascadeInputs, inputs: CascadeInputs,
) -> Arc<ComputedValues> { ) -> ResolvedStyle {
with_default_parent_styles(self.element, |parent_style, layout_parent_style| { with_default_parent_styles(self.element, |parent_style, layout_parent_style| {
self.cascade_style_and_visited( self.cascade_style_and_visited(
inputs, inputs,
@ -240,7 +265,28 @@ where
parent_style: Option<&ComputedValues>, parent_style: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>,
pseudo: Option<&PseudoElement>, pseudo: Option<&PseudoElement>,
) -> Arc<ComputedValues> { ) -> ResolvedStyle {
// Before doing the cascade, check the sharing cache and see if we can reuse
// the style via rule node identity.
let may_reuse = pseudo.is_none() && !self.element.is_native_anonymous() &&
parent_style.is_some() && inputs.rules.is_some();
if may_reuse {
let cached = self.context.thread_local.sharing_cache.lookup_by_rules(
parent_style.unwrap(),
inputs.rules.as_ref().unwrap(),
inputs.visited_rules.as_ref(),
self.element,
);
if let Some(el) = cached {
self.context.thread_local.statistics.styles_reused += 1;
let mut style = el.borrow_data().unwrap().share_primary_style().0;
style.reused_via_rule_node = true;
return style;
}
}
// No style to reuse. Cascade the style, starting with visited style
// if necessary.
let mut style_if_visited = None; let mut style_if_visited = None;
if parent_style.map_or(false, |s| s.get_visited_style().is_some()) || if parent_style.map_or(false, |s| s.get_visited_style().is_some()) ||
inputs.visited_rules.is_some() { inputs.visited_rules.is_some() {
@ -253,38 +299,42 @@ where
pseudo, pseudo,
)); ));
} }
self.cascade_style(
inputs.rules.as_ref(), ResolvedStyle {
style_if_visited, style: self.cascade_style(
parent_style, inputs.rules.as_ref(),
layout_parent_style, style_if_visited,
CascadeVisitedMode::Unvisited, parent_style,
pseudo, layout_parent_style,
) CascadeVisitedMode::Unvisited,
pseudo,
),
reused_via_rule_node: false,
}
} }
/// Cascade the element and pseudo-element styles with the default parents. /// Cascade the element and pseudo-element styles with the default parents.
pub fn cascade_styles_with_default_parents( pub fn cascade_styles_with_default_parents(
&mut self, &mut self,
inputs: ElementCascadeInputs, inputs: ElementCascadeInputs,
) -> ElementStyles { ) -> ResolvedElementStyles {
with_default_parent_styles(self.element, move |parent_style, layout_parent_style| { with_default_parent_styles(self.element, move |parent_style, layout_parent_style| {
let primary_style = PrimaryStyle { let primary_style = PrimaryStyle(
style: self.cascade_style_and_visited( self.cascade_style_and_visited(
inputs.primary, inputs.primary,
parent_style, parent_style,
layout_parent_style, layout_parent_style,
/* pseudo = */ None, /* pseudo = */ None,
), )
}; );
let mut pseudo_styles = EagerPseudoStyles::default(); let mut pseudo_styles = EagerPseudoStyles::default();
if let Some(mut pseudo_array) = inputs.pseudos.into_array() { if let Some(mut pseudo_array) = inputs.pseudos.into_array() {
let layout_parent_style_for_pseudo = let layout_parent_style_for_pseudo =
if primary_style.style.is_display_contents() { if primary_style.style().is_display_contents() {
layout_parent_style layout_parent_style
} else { } else {
Some(&*primary_style.style) Some(primary_style.style())
}; };
for (i, inputs) in pseudo_array.iter_mut().enumerate() { for (i, inputs) in pseudo_array.iter_mut().enumerate() {
@ -294,23 +344,23 @@ where
let style = let style =
self.cascade_style_and_visited( self.cascade_style_and_visited(
inputs, inputs,
Some(&*primary_style.style), Some(&*primary_style.style()),
layout_parent_style_for_pseudo, layout_parent_style_for_pseudo,
Some(&pseudo), Some(&pseudo),
); );
if !matches!(self.pseudo_resolution, PseudoElementResolution::Force) && if !matches!(self.pseudo_resolution, PseudoElementResolution::Force) &&
eager_pseudo_is_definitely_not_generated(&pseudo, &style) { eager_pseudo_is_definitely_not_generated(&pseudo, &style.style) {
continue; continue;
} }
pseudo_styles.set(&pseudo, style); pseudo_styles.set(&pseudo, style.style);
} }
} }
} }
ElementStyles { ResolvedElementStyles {
primary: Some(primary_style.style), primary: primary_style,
pseudos: pseudo_styles, pseudos: pseudo_styles,
} }
}) })
@ -323,7 +373,7 @@ where
layout_parent_style: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>,
) -> Option<Arc<ComputedValues>> { ) -> Option<Arc<ComputedValues>> {
let rules = self.match_pseudo( let rules = self.match_pseudo(
&originating_element_style.style, originating_element_style.style(),
pseudo, pseudo,
VisitedHandlingMode::AllLinksUnvisited VisitedHandlingMode::AllLinksUnvisited
); );
@ -333,9 +383,9 @@ where
}; };
let mut visited_rules = None; let mut visited_rules = None;
if originating_element_style.style.get_visited_style().is_some() { if originating_element_style.style().get_visited_style().is_some() {
visited_rules = self.match_pseudo( visited_rules = self.match_pseudo(
&originating_element_style.style, originating_element_style.style(),
pseudo, pseudo,
VisitedHandlingMode::RelevantLinkVisited, VisitedHandlingMode::RelevantLinkVisited,
); );
@ -346,10 +396,10 @@ where
rules: Some(rules), rules: Some(rules),
visited_rules visited_rules
}, },
Some(&originating_element_style.style), Some(originating_element_style.style()),
layout_parent_style, layout_parent_style,
Some(pseudo), Some(pseudo),
)) ).style)
} }
fn match_primary( fn match_primary(
@ -485,6 +535,12 @@ where
cascade_visited: CascadeVisitedMode, cascade_visited: CascadeVisitedMode,
pseudo: Option<&PseudoElement>, pseudo: Option<&PseudoElement>,
) -> Arc<ComputedValues> { ) -> Arc<ComputedValues> {
debug_assert!(
self.element.implemented_pseudo_element().is_none() || pseudo.is_none(),
"Pseudo-elements can't have other pseudos!"
);
debug_assert!(pseudo.map_or(true, |p| p.is_eager()));
let mut cascade_flags = CascadeFlags::empty(); let mut cascade_flags = CascadeFlags::empty();
if self.element.skip_root_and_item_based_display_fixup() || if self.element.skip_root_and_item_based_display_fixup() ||

View file

@ -419,9 +419,9 @@ where
layout_parent_style.as_ref().map(|s| &**s) layout_parent_style.as_ref().map(|s| &**s)
); );
let is_display_contents = primary_style.style.is_display_contents(); let is_display_contents = primary_style.style().is_display_contents();
style = Some(primary_style.style); style = Some(primary_style.0.into());
if !is_display_contents { if !is_display_contents {
layout_parent_style = style.clone(); layout_parent_style = style.clone();
} }
@ -434,7 +434,7 @@ where
.resolve_style( .resolve_style(
style.as_ref().map(|s| &**s), style.as_ref().map(|s| &**s),
layout_parent_style.as_ref().map(|s| &**s) layout_parent_style.as_ref().map(|s| &**s)
) ).into()
} }
/// Calculates the style for a single node. /// Calculates the style for a single node.
@ -646,6 +646,10 @@ where
traversal_data.current_dom_depth); traversal_data.current_dom_depth);
context.thread_local.bloom_filter.assert_complete(element); context.thread_local.bloom_filter.assert_complete(element);
debug_assert_eq!(
context.thread_local.bloom_filter.matching_depth(),
traversal_data.current_dom_depth
);
// This is only relevant for animations as of right now. // This is only relevant for animations as of right now.
important_rules_changed = true; important_rules_changed = true;
@ -655,9 +659,9 @@ where
// Now that our bloom filter is set up, try the style sharing // Now that our bloom filter is set up, try the style sharing
// cache. // cache.
match target.share_style_if_possible(context) { match target.share_style_if_possible(context) {
Some(styles) => { Some(shareable_element) => {
context.thread_local.statistics.styles_shared += 1; context.thread_local.statistics.styles_shared += 1;
styles shareable_element.borrow_data().unwrap().share_styles()
} }
None => { None => {
context.thread_local.statistics.elements_matched += 1; context.thread_local.statistics.elements_matched += 1;
@ -678,9 +682,9 @@ where
.sharing_cache .sharing_cache
.insert_if_possible( .insert_if_possible(
&element, &element,
new_styles.primary(), new_styles.primary.style(),
&mut target, Some(&mut target),
context.thread_local.bloom_filter.matching_depth(), traversal_data.current_dom_depth,
); );
new_styles new_styles
@ -709,15 +713,25 @@ where
let cascade_inputs = let cascade_inputs =
ElementCascadeInputs::new_from_element_data(data); ElementCascadeInputs::new_from_element_data(data);
let mut resolver = let new_styles = {
StyleResolverForElement::new( let mut resolver =
element, StyleResolverForElement::new(
context, element,
RuleInclusion::All, context,
PseudoElementResolution::IfApplicable RuleInclusion::All,
); PseudoElementResolution::IfApplicable
);
resolver.cascade_styles_with_default_parents(cascade_inputs) resolver.cascade_styles_with_default_parents(cascade_inputs)
};
context.thread_local.sharing_cache.insert_if_possible(
&element,
new_styles.primary.style(),
None,
traversal_data.current_dom_depth,
);
new_styles
} }
}; };

View file

@ -727,9 +727,11 @@ pub extern "C" fn Servo_StyleSet_GetBaseComputedValuesForElement(raw_data: RawSe
}; };
// Actually `PseudoElementResolution` doesn't matter. // Actually `PseudoElementResolution` doesn't matter.
StyleResolverForElement::new(element, &mut context, RuleInclusion::All, PseudoElementResolution::IfApplicable) let style: Arc<ComputedValues> =
StyleResolverForElement::new(element, &mut context, RuleInclusion::All, PseudoElementResolution::IfApplicable)
.cascade_style_and_visited_with_default_parents(inputs) .cascade_style_and_visited_with_default_parents(inputs)
.into() .into();
style.into()
} }
#[no_mangle] #[no_mangle]