style: Only store applicable ::before / ::after pseudo styles during the traversal.

This commit is contained in:
Emilio Cobos Álvarez 2017-08-24 11:09:33 +02:00
parent 5a57a3ef4b
commit ff700aba75
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
7 changed files with 136 additions and 28 deletions

View file

@ -702,7 +702,7 @@ pub fn process_resolved_style_request<'a, N>(context: &LayoutContext,
thread_local: &mut tlc, thread_local: &mut tlc,
}; };
let styles = resolve_style(&mut context, element, RuleInclusion::All, false); let styles = resolve_style(&mut context, element, RuleInclusion::All, false, pseudo.as_ref());
let style = styles.primary(); let style = styles.primary();
let longhand_id = match *property { let longhand_id = match *property {
PropertyId::Longhand(id) => id, PropertyId::Longhand(id) => id,

View file

@ -123,7 +123,7 @@ trait PrivateMatchMethods: TElement {
primary_style: &Arc<ComputedValues> primary_style: &Arc<ComputedValues>
) -> Option<Arc<ComputedValues>> { ) -> Option<Arc<ComputedValues>> {
use context::CascadeInputs; use context::CascadeInputs;
use style_resolver::StyleResolverForElement; use style_resolver::{PseudoElementResolution, StyleResolverForElement};
use stylist::RuleInclusion; use stylist::RuleInclusion;
let rule_node = primary_style.rules(); let rule_node = primary_style.rules();
@ -143,8 +143,9 @@ trait PrivateMatchMethods: TElement {
visited_rules: primary_style.get_visited_style().and_then(|s| s.rules.clone()), visited_rules: primary_style.get_visited_style().and_then(|s| s.rules.clone()),
}; };
// Actually `PseudoElementResolution` doesn't really matter.
let style = let style =
StyleResolverForElement::new(*self, context, RuleInclusion::All) 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)

View file

@ -44,5 +44,17 @@ bitflags! {
/// A flag used to mark styles which are in a display: none subtree, or /// A flag used to mark styles which are in a display: none subtree, or
/// under one. /// under one.
const IS_IN_DISPLAY_NONE_SUBTREE = 1 << 5, const IS_IN_DISPLAY_NONE_SUBTREE = 1 << 5,
/// Whether this style inherits the `display` property.
///
/// This is important because it may affect our optimizations to avoid
/// computing the style of pseudo-elements, given whether the
/// pseudo-element is generated depends on the `display` value.
const INHERITS_DISPLAY = 1 << 6,
/// Whether this style inherits the `content` property.
///
/// Important because of the same reason.
const INHERITS_CONTENT = 1 << 7,
} }
} }

View file

@ -2691,6 +2691,14 @@ impl<'a> StyleBuilder<'a> {
self.inherited_style_ignoring_first_line.get_${property.style_struct.name_lower}(); self.inherited_style_ignoring_first_line.get_${property.style_struct.name_lower}();
% endif % endif
% if property.ident == "content":
self.flags.insert(::properties::computed_value_flags::INHERITS_CONTENT);
% endif
% if property.ident == "display":
self.flags.insert(::properties::computed_value_flags::INHERITS_DISPLAY);
% endif
self.${property.style_struct.ident}.mutate() self.${property.style_struct.ident}.mutate()
.copy_${property.ident}_from( .copy_${property.ident}_from(
inherited_struct, inherited_struct,

View file

@ -15,12 +15,22 @@ use properties::{AnimationRules, CascadeFlags, ComputedValues};
use properties::{IS_LINK, IS_ROOT_ELEMENT, IS_VISITED_LINK}; use properties::{IS_LINK, IS_ROOT_ELEMENT, IS_VISITED_LINK};
use properties::{PROHIBIT_DISPLAY_CONTENTS, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP}; use properties::{PROHIBIT_DISPLAY_CONTENTS, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP};
use properties::{VISITED_DEPENDENT_ONLY, cascade}; use properties::{VISITED_DEPENDENT_ONLY, cascade};
use properties::longhands::display::computed_value::T as display;
use rule_tree::StrongRuleNode; use rule_tree::StrongRuleNode;
use selector_parser::{PseudoElement, SelectorImpl}; use selector_parser::{PseudoElement, SelectorImpl};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, VisitedHandlingMode}; use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, VisitedHandlingMode};
use servo_arc::Arc; use servo_arc::Arc;
use stylist::RuleInclusion; use stylist::RuleInclusion;
/// Whether pseudo-elements should be resolved or not.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PseudoElementResolution {
/// Only resolve pseudo-styles if possibly applicable.
IfApplicable,
/// Force pseudo-element resolution.
Force,
}
/// A struct that takes care of resolving the style of a given element. /// A struct that takes care of resolving the style of a given element.
pub struct StyleResolverForElement<'a, 'ctx, 'le, E> pub struct StyleResolverForElement<'a, 'ctx, 'le, E>
where where
@ -31,6 +41,7 @@ where
element: E, element: E,
context: &'a mut StyleContext<'ctx, E>, context: &'a mut StyleContext<'ctx, E>,
rule_inclusion: RuleInclusion, rule_inclusion: RuleInclusion,
pseudo_resolution: PseudoElementResolution,
_marker: ::std::marker::PhantomData<&'le E>, _marker: ::std::marker::PhantomData<&'le E>,
} }
@ -66,6 +77,29 @@ where
f(parent_style.map(|x| &**x), layout_parent_style.map(|s| &**s)) f(parent_style.map(|x| &**x), layout_parent_style.map(|s| &**s))
} }
fn eager_pseudo_is_definitely_not_generated(
pseudo: &PseudoElement,
style: &ComputedValues,
) -> bool {
use properties::computed_value_flags::{INHERITS_CONTENT, INHERITS_DISPLAY};
if !pseudo.is_before_or_after() {
return false;
}
if !style.flags.intersects(INHERITS_DISPLAY) &&
style.get_box().clone_display() == display::none {
return true;
}
if !style.flags.intersects(INHERITS_CONTENT) &&
style.ineffective_content_property() {
return true;
}
false
}
impl<'a, 'ctx, 'le, E> StyleResolverForElement<'a, 'ctx, 'le, E> impl<'a, 'ctx, 'le, E> StyleResolverForElement<'a, 'ctx, 'le, E>
where where
'ctx: 'a, 'ctx: 'a,
@ -77,11 +111,13 @@ where
element: E, element: E,
context: &'a mut StyleContext<'ctx, E>, context: &'a mut StyleContext<'ctx, E>,
rule_inclusion: RuleInclusion, rule_inclusion: RuleInclusion,
pseudo_resolution: PseudoElementResolution,
) -> Self { ) -> Self {
Self { Self {
element, element,
context, context,
rule_inclusion, rule_inclusion,
pseudo_resolution,
_marker: ::std::marker::PhantomData, _marker: ::std::marker::PhantomData,
} }
} }
@ -250,15 +286,21 @@ where
for (i, inputs) in pseudo_array.iter_mut().enumerate() { for (i, inputs) in pseudo_array.iter_mut().enumerate() {
if let Some(inputs) = inputs.take() { if let Some(inputs) = inputs.take() {
let pseudo = PseudoElement::from_eager_index(i); let pseudo = PseudoElement::from_eager_index(i);
pseudo_styles.set(
&pseudo, 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) &&
eager_pseudo_is_definitely_not_generated(&pseudo, &style) {
continue;
}
pseudo_styles.set(&pseudo, style);
} }
} }
} }

View file

@ -9,9 +9,10 @@ use data::{ElementData, ElementStyles};
use dom::{NodeInfo, OpaqueNode, TElement, TNode}; use dom::{NodeInfo, OpaqueNode, TElement, TNode};
use invalidation::element::restyle_hints::{RECASCADE_SELF, RECASCADE_DESCENDANTS, RestyleHint}; use invalidation::element::restyle_hints::{RECASCADE_SELF, RECASCADE_DESCENDANTS, RestyleHint};
use matching::{ChildCascadeRequirement, MatchMethods}; use matching::{ChildCascadeRequirement, MatchMethods};
use selector_parser::PseudoElement;
use sharing::StyleSharingTarget; use sharing::StyleSharingTarget;
use smallvec::SmallVec; use smallvec::SmallVec;
use style_resolver::StyleResolverForElement; use style_resolver::{PseudoElementResolution, StyleResolverForElement};
#[cfg(feature = "servo")] use style_traits::ToCss; #[cfg(feature = "servo")] use style_traits::ToCss;
use stylist::RuleInclusion; use stylist::RuleInclusion;
use traversal_flags::{TraversalFlags, self}; use traversal_flags::{TraversalFlags, self};
@ -387,6 +388,7 @@ pub fn resolve_style<E>(
element: E, element: E,
rule_inclusion: RuleInclusion, rule_inclusion: RuleInclusion,
ignore_existing_style: bool, ignore_existing_style: bool,
pseudo: Option<&PseudoElement>,
) -> ElementStyles ) -> ElementStyles
where where
E: TElement, E: TElement,
@ -395,6 +397,7 @@ where
debug_assert!(rule_inclusion == RuleInclusion::DefaultOnly || debug_assert!(rule_inclusion == RuleInclusion::DefaultOnly ||
ignore_existing_style || ignore_existing_style ||
pseudo.map_or(false, |p| p.is_before_or_after()) ||
element.borrow_data().map_or(true, |d| !d.has_styles()), element.borrow_data().map_or(true, |d| !d.has_styles()),
"Why are we here?"); "Why are we here?");
let mut ancestors_requiring_style_resolution = SmallVec::<[E; 16]>::new(); let mut ancestors_requiring_style_resolution = SmallVec::<[E; 16]>::new();
@ -438,8 +441,10 @@ where
for ancestor in ancestors_requiring_style_resolution.iter().rev() { for ancestor in ancestors_requiring_style_resolution.iter().rev() {
context.thread_local.bloom_filter.assert_complete(*ancestor); context.thread_local.bloom_filter.assert_complete(*ancestor);
// Actually `PseudoElementResolution` doesn't really matter here.
// (but it does matter below!).
let primary_style = let primary_style =
StyleResolverForElement::new(*ancestor, context, rule_inclusion) StyleResolverForElement::new(*ancestor, context, rule_inclusion, PseudoElementResolution::IfApplicable)
.resolve_primary_style( .resolve_primary_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)
@ -456,7 +461,7 @@ where
} }
context.thread_local.bloom_filter.assert_complete(element); context.thread_local.bloom_filter.assert_complete(element);
StyleResolverForElement::new(element, context, rule_inclusion) StyleResolverForElement::new(element, context, rule_inclusion, PseudoElementResolution::Force)
.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)
@ -692,9 +697,17 @@ where
CannotShare => { CannotShare => {
context.thread_local.statistics.elements_matched += 1; context.thread_local.statistics.elements_matched += 1;
// Perform the matching and cascading. // Perform the matching and cascading.
let new_styles = let new_styles = {
StyleResolverForElement::new(element, context, RuleInclusion::All) let mut resolver =
.resolve_style_with_default_parents(); StyleResolverForElement::new(
element,
context,
RuleInclusion::All,
PseudoElementResolution::IfApplicable
);
resolver.resolve_style_with_default_parents()
};
context.thread_local context.thread_local
.style_sharing_candidate_cache .style_sharing_candidate_cache
@ -715,15 +728,31 @@ where
ElementCascadeInputs::new_from_element_data(data); ElementCascadeInputs::new_from_element_data(data);
important_rules_changed = important_rules_changed =
element.replace_rules(flags, context, &mut cascade_inputs); element.replace_rules(flags, context, &mut cascade_inputs);
StyleResolverForElement::new(element, context, RuleInclusion::All)
.cascade_styles_with_default_parents(cascade_inputs) let mut resolver =
StyleResolverForElement::new(
element,
context,
RuleInclusion::All,
PseudoElementResolution::IfApplicable
);
resolver.cascade_styles_with_default_parents(cascade_inputs)
} }
CascadeOnly => { CascadeOnly => {
// Skipping full matching, load cascade inputs from previous values. // Skipping full matching, load cascade inputs from previous values.
let cascade_inputs = let cascade_inputs =
ElementCascadeInputs::new_from_element_data(data); ElementCascadeInputs::new_from_element_data(data);
StyleResolverForElement::new(element, context, RuleInclusion::All)
.cascade_styles_with_default_parents(cascade_inputs) let mut resolver =
StyleResolverForElement::new(
element,
context,
RuleInclusion::All,
PseudoElementResolution::IfApplicable
);
resolver.cascade_styles_with_default_parents(cascade_inputs)
} }
}; };

View file

@ -669,7 +669,7 @@ pub extern "C" fn Servo_StyleSet_GetBaseComputedValuesForElement(raw_data: RawSe
pseudo_type: CSSPseudoElementType) pseudo_type: CSSPseudoElementType)
-> ServoStyleContextStrong -> ServoStyleContextStrong
{ {
use style::style_resolver::StyleResolverForElement; use style::style_resolver::{PseudoElementResolution, StyleResolverForElement};
debug_assert!(!snapshots.is_null()); debug_assert!(!snapshots.is_null());
let computed_values = unsafe { ArcBorrow::from_ref(computed_values) }; let computed_values = unsafe { ArcBorrow::from_ref(computed_values) };
@ -725,7 +725,8 @@ pub extern "C" fn Servo_StyleSet_GetBaseComputedValuesForElement(raw_data: RawSe
visited_rules: None, visited_rules: None,
}; };
StyleResolverForElement::new(element, &mut context, RuleInclusion::All) // Actually `PseudoElementResolution` doesn't matter.
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()
} }
@ -2932,8 +2933,9 @@ pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed,
let doc_data = PerDocumentStyleData::from_ffi(raw_data); let doc_data = PerDocumentStyleData::from_ffi(raw_data);
let data = doc_data.borrow(); let data = doc_data.borrow();
let rule_inclusion = RuleInclusion::from(rule_inclusion); let rule_inclusion = RuleInclusion::from(rule_inclusion);
let finish = |styles: &ElementStyles| -> Arc<ComputedValues> { let pseudo = PseudoElement::from_pseudo_type(pseudo_type);
match PseudoElement::from_pseudo_type(pseudo_type) { let finish = |styles: &ElementStyles, is_probe: bool| -> Option<Arc<ComputedValues>> {
match pseudo {
Some(ref pseudo) => { Some(ref pseudo) => {
get_pseudo_style( get_pseudo_style(
&guard, &guard,
@ -2943,22 +2945,27 @@ pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed,
styles, styles,
/* inherited_styles = */ None, /* inherited_styles = */ None,
&*data, &*data,
/* is_probe = */ false, is_probe,
).expect("We're not probing, so we should always get a style \ )
back")
} }
None => styles.primary().clone(), None => Some(styles.primary().clone()),
} }
}; };
let is_before_or_after = pseudo.as_ref().map_or(false, |p| p.is_before_or_after());
// In the common case we already have the style. Check that before setting // In the common case we already have the style. Check that before setting
// up all the computation machinery. (Don't use it when we're getting // up all the computation machinery. (Don't use it when we're getting
// default styles or in a bfcached document (as indicated by // default styles or in a bfcached document (as indicated by
// ignore_existing_styles), though.) // ignore_existing_styles), though.)
//
// Also, only probe in the ::before or ::after case, since their styles may
// not be in the `ElementData`, given they may exist but not be applicable
// to generate an actual pseudo-element (like, having a `content: none`).
if rule_inclusion == RuleInclusion::All && !ignore_existing_styles { if rule_inclusion == RuleInclusion::All && !ignore_existing_styles {
let styles = element.mutate_data().and_then(|d| { let styles = element.mutate_data().and_then(|d| {
if d.has_styles() { if d.has_styles() {
Some(finish(&d.styles)) finish(&d.styles, is_before_or_after)
} else { } else {
None None
} }
@ -2980,8 +2987,17 @@ pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed,
thread_local: &mut tlc, thread_local: &mut tlc,
}; };
let styles = resolve_style(&mut context, element, rule_inclusion, ignore_existing_styles); let styles = resolve_style(
finish(&styles).into() &mut context,
element,
rule_inclusion,
ignore_existing_styles,
pseudo.as_ref()
);
finish(&styles, /* is_probe = */ false)
.expect("We're not probing, so we should always get a style back")
.into()
} }
#[no_mangle] #[no_mangle]