diff --git a/components/layout/query.rs b/components/layout/query.rs index 5803a247e19..9d1d4f18254 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -702,7 +702,7 @@ pub fn process_resolved_style_request<'a, N>(context: &LayoutContext, 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 longhand_id = match *property { PropertyId::Longhand(id) => id, diff --git a/components/style/matching.rs b/components/style/matching.rs index 701c20304f5..6bc55eee0d6 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -123,7 +123,7 @@ trait PrivateMatchMethods: TElement { primary_style: &Arc ) -> Option> { use context::CascadeInputs; - use style_resolver::StyleResolverForElement; + use style_resolver::{PseudoElementResolution, StyleResolverForElement}; use stylist::RuleInclusion; 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()), }; + // Actually `PseudoElementResolution` doesn't really matter. let style = - StyleResolverForElement::new(*self, context, RuleInclusion::All) + StyleResolverForElement::new(*self, context, RuleInclusion::All, PseudoElementResolution::IfApplicable) .cascade_style_and_visited_with_default_parents(inputs); Some(style) diff --git a/components/style/properties/computed_value_flags.rs b/components/style/properties/computed_value_flags.rs index ef903bf775f..83a12618872 100644 --- a/components/style/properties/computed_value_flags.rs +++ b/components/style/properties/computed_value_flags.rs @@ -44,5 +44,17 @@ bitflags! { /// A flag used to mark styles which are in a display: none subtree, or /// under one. 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, } } diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index c2a5e3970c9..60022596fb4 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -2691,6 +2691,14 @@ impl<'a> StyleBuilder<'a> { self.inherited_style_ignoring_first_line.get_${property.style_struct.name_lower}(); % 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() .copy_${property.ident}_from( inherited_struct, diff --git a/components/style/style_resolver.rs b/components/style/style_resolver.rs index 18b29162bcb..cfdef9e296b 100644 --- a/components/style/style_resolver.rs +++ b/components/style/style_resolver.rs @@ -15,12 +15,22 @@ use properties::{AnimationRules, CascadeFlags, ComputedValues}; use properties::{IS_LINK, IS_ROOT_ELEMENT, IS_VISITED_LINK}; use properties::{PROHIBIT_DISPLAY_CONTENTS, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP}; use properties::{VISITED_DEPENDENT_ONLY, cascade}; +use properties::longhands::display::computed_value::T as display; use rule_tree::StrongRuleNode; use selector_parser::{PseudoElement, SelectorImpl}; use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, VisitedHandlingMode}; use servo_arc::Arc; 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. pub struct StyleResolverForElement<'a, 'ctx, 'le, E> where @@ -31,6 +41,7 @@ where element: E, context: &'a mut StyleContext<'ctx, E>, rule_inclusion: RuleInclusion, + pseudo_resolution: PseudoElementResolution, _marker: ::std::marker::PhantomData<&'le E>, } @@ -66,6 +77,29 @@ where 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> where 'ctx: 'a, @@ -77,11 +111,13 @@ where element: E, context: &'a mut StyleContext<'ctx, E>, rule_inclusion: RuleInclusion, + pseudo_resolution: PseudoElementResolution, ) -> Self { Self { element, context, rule_inclusion, + pseudo_resolution, _marker: ::std::marker::PhantomData, } } @@ -250,15 +286,21 @@ where for (i, inputs) in pseudo_array.iter_mut().enumerate() { if let Some(inputs) = inputs.take() { let pseudo = PseudoElement::from_eager_index(i); - pseudo_styles.set( - &pseudo, + + let style = self.cascade_style_and_visited( inputs, Some(&*primary_style.style), layout_parent_style_for_pseudo, Some(&pseudo), - ) - ) + ); + + if !matches!(self.pseudo_resolution, PseudoElementResolution::Force) && + eager_pseudo_is_definitely_not_generated(&pseudo, &style) { + continue; + } + + pseudo_styles.set(&pseudo, style); } } } diff --git a/components/style/traversal.rs b/components/style/traversal.rs index b4d409ad79c..e4ac1da29d3 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -9,9 +9,10 @@ use data::{ElementData, ElementStyles}; use dom::{NodeInfo, OpaqueNode, TElement, TNode}; use invalidation::element::restyle_hints::{RECASCADE_SELF, RECASCADE_DESCENDANTS, RestyleHint}; use matching::{ChildCascadeRequirement, MatchMethods}; +use selector_parser::PseudoElement; use sharing::StyleSharingTarget; use smallvec::SmallVec; -use style_resolver::StyleResolverForElement; +use style_resolver::{PseudoElementResolution, StyleResolverForElement}; #[cfg(feature = "servo")] use style_traits::ToCss; use stylist::RuleInclusion; use traversal_flags::{TraversalFlags, self}; @@ -387,6 +388,7 @@ pub fn resolve_style( element: E, rule_inclusion: RuleInclusion, ignore_existing_style: bool, + pseudo: Option<&PseudoElement>, ) -> ElementStyles where E: TElement, @@ -395,6 +397,7 @@ where debug_assert!(rule_inclusion == RuleInclusion::DefaultOnly || ignore_existing_style || + pseudo.map_or(false, |p| p.is_before_or_after()) || element.borrow_data().map_or(true, |d| !d.has_styles()), "Why are we here?"); let mut ancestors_requiring_style_resolution = SmallVec::<[E; 16]>::new(); @@ -438,8 +441,10 @@ where for ancestor in ancestors_requiring_style_resolution.iter().rev() { context.thread_local.bloom_filter.assert_complete(*ancestor); + // Actually `PseudoElementResolution` doesn't really matter here. + // (but it does matter below!). let primary_style = - StyleResolverForElement::new(*ancestor, context, rule_inclusion) + StyleResolverForElement::new(*ancestor, context, rule_inclusion, PseudoElementResolution::IfApplicable) .resolve_primary_style( 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); - StyleResolverForElement::new(element, context, rule_inclusion) + StyleResolverForElement::new(element, context, rule_inclusion, PseudoElementResolution::Force) .resolve_style( style.as_ref().map(|s| &**s), layout_parent_style.as_ref().map(|s| &**s) @@ -692,9 +697,17 @@ where CannotShare => { context.thread_local.statistics.elements_matched += 1; // Perform the matching and cascading. - let new_styles = - StyleResolverForElement::new(element, context, RuleInclusion::All) - .resolve_style_with_default_parents(); + let new_styles = { + let mut resolver = + StyleResolverForElement::new( + element, + context, + RuleInclusion::All, + PseudoElementResolution::IfApplicable + ); + + resolver.resolve_style_with_default_parents() + }; context.thread_local .style_sharing_candidate_cache @@ -715,15 +728,31 @@ where ElementCascadeInputs::new_from_element_data(data); important_rules_changed = 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 => { // Skipping full matching, load cascade inputs from previous values. let cascade_inputs = 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) } }; diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index ec4dd237121..fbf4ab9f061 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -669,7 +669,7 @@ pub extern "C" fn Servo_StyleSet_GetBaseComputedValuesForElement(raw_data: RawSe pseudo_type: CSSPseudoElementType) -> ServoStyleContextStrong { - use style::style_resolver::StyleResolverForElement; + use style::style_resolver::{PseudoElementResolution, StyleResolverForElement}; debug_assert!(!snapshots.is_null()); 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, }; - 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) .into() } @@ -2932,8 +2933,9 @@ pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed, let doc_data = PerDocumentStyleData::from_ffi(raw_data); let data = doc_data.borrow(); let rule_inclusion = RuleInclusion::from(rule_inclusion); - let finish = |styles: &ElementStyles| -> Arc { - match PseudoElement::from_pseudo_type(pseudo_type) { + let pseudo = PseudoElement::from_pseudo_type(pseudo_type); + let finish = |styles: &ElementStyles, is_probe: bool| -> Option> { + match pseudo { Some(ref pseudo) => { get_pseudo_style( &guard, @@ -2943,22 +2945,27 @@ pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed, styles, /* inherited_styles = */ None, &*data, - /* is_probe = */ false, - ).expect("We're not probing, so we should always get a style \ - back") + is_probe, + ) } - 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 // up all the computation machinery. (Don't use it when we're getting // default styles or in a bfcached document (as indicated by // 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 { let styles = element.mutate_data().and_then(|d| { if d.has_styles() { - Some(finish(&d.styles)) + finish(&d.styles, is_before_or_after) } else { None } @@ -2980,8 +2987,17 @@ pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed, thread_local: &mut tlc, }; - let styles = resolve_style(&mut context, element, rule_inclusion, ignore_existing_styles); - finish(&styles).into() + let styles = resolve_style( + &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]