author | Martin Robinson <mrobinson@igalia.com> |
Thu, 18 Jun 2020 18:14:05 +0000 | |
changeset 536372 | 0da5430c2b2eb7ad0592dc831be1945750813132 |
parent 536371 | 04f8394b23bb2fcc2f19d6baf1a31a66be6bc297 |
child 536373 | e6d0a75ce3952ea7575d77c565e5dcd536b1ea03 |
push id | 119420 |
push user | ealvarez@mozilla.com |
push date | Thu, 18 Jun 2020 18:18:12 +0000 |
treeherder | autoland@206011a5cc20 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
bugs | 1646811, 10316 |
milestone | 79.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/servo/components/style/animation.rs +++ b/servo/components/style/animation.rs @@ -14,16 +14,17 @@ use crate::properties::animated_properti use crate::properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection; use crate::properties::longhands::animation_fill_mode::computed_value::single_value::T as AnimationFillMode; use crate::properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState; use crate::properties::{ ComputedValues, Importance, LonghandId, LonghandIdSet, PropertyDeclarationBlock, PropertyDeclarationId, }; use crate::rule_tree::CascadeLevel; +use crate::selector_parser::PseudoElement; use crate::shared_lock::{Locked, SharedRwLock}; use crate::style_resolver::StyleResolverForElement; use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue}; use crate::values::animated::{Animate, Procedure}; use crate::values::computed::{Time, TimingFunction}; use crate::values::generics::box_::AnimationIterationCount; use crate::values::generics::easing::{StepPosition, TimingFunction as GenericTimingFunction}; use crate::Atom; @@ -1133,43 +1134,73 @@ impl ElementAnimationSet { } Some(map) } } #[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)] /// A key that is used to identify nodes in the `DocumentAnimationSet`. -pub struct AnimationSetKey(pub OpaqueNode); +pub struct AnimationSetKey { + /// The node for this `AnimationSetKey`. + pub node: OpaqueNode, + /// The pseudo element for this `AnimationSetKey`. If `None` this key will + /// refer to the main content for its node. + pub pseudo_element: Option<PseudoElement>, +} + +impl AnimationSetKey { + /// Create a new key given a node and optional pseudo element. + pub fn new(node: OpaqueNode, pseudo_element: Option<PseudoElement>) -> Self { + AnimationSetKey { + node, + pseudo_element, + } + } + + /// Create a new key for the main content of this node. + pub fn new_for_non_pseudo(node: OpaqueNode) -> Self { + AnimationSetKey { + node, + pseudo_element: None, + } + } + + /// Create a new key for given node and pseudo element. + pub fn new_for_pseudo(node: OpaqueNode, pseudo_element: PseudoElement) -> Self { + AnimationSetKey { + node, + pseudo_element: Some(pseudo_element), + } + } +} #[derive(Clone, Debug, Default, MallocSizeOf)] /// A set of animations for a document. pub struct DocumentAnimationSet { /// The `ElementAnimationSet`s that this set contains. #[ignore_malloc_size_of = "Arc is hard"] pub sets: Arc<RwLock<FxHashMap<AnimationSetKey, ElementAnimationSet>>>, } impl DocumentAnimationSet { /// Return whether or not the provided node has active CSS animations. pub fn has_active_animations(&self, key: &AnimationSetKey) -> bool { self.sets .read() .get(key) - .map(|set| set.has_active_animation()) - .unwrap_or(false) + .map_or(false, |set| set.has_active_animation()) } /// Return whether or not the provided node has active CSS transitions. pub fn has_active_transitions(&self, key: &AnimationSetKey) -> bool { self.sets .read() .get(key) - .map(|set| set.has_active_transition()) - .unwrap_or(false) + .map_or(false, |set| set.has_active_transition()) } /// Return a locked PropertyDeclarationBlock with animation values for the given /// key and time. pub fn get_animation_declarations( &self, key: &AnimationSetKey, time: f64, @@ -1197,16 +1228,68 @@ impl DocumentAnimationSet { .read() .get(key) .and_then(|set| set.get_value_map_for_active_transitions(time)) .map(|map| { let block = PropertyDeclarationBlock::from_animation_value_map(&map); Arc::new(shared_lock.wrap(block)) }) } + + /// Get all the animation declarations for the given key, returning an empty + /// `AnimationAndTransitionDeclarations` if there are no animations. + pub(crate) fn get_all_declarations( + &self, + key: &AnimationSetKey, + time: f64, + shared_lock: &SharedRwLock, + ) -> AnimationAndTransitionDeclarations { + let sets = self.sets.read(); + let set = match sets.get(key) { + Some(set) => set, + None => return Default::default(), + }; + + let animations = set.get_value_map_for_active_animations(time).map(|map| { + let block = PropertyDeclarationBlock::from_animation_value_map(&map); + Arc::new(shared_lock.wrap(block)) + }); + let transitions = set.get_value_map_for_active_transitions(time).map(|map| { + let block = PropertyDeclarationBlock::from_animation_value_map(&map); + Arc::new(shared_lock.wrap(block)) + }); + AnimationAndTransitionDeclarations { + animations, + transitions, + } + } + + /// Cancel all animations for set at the given key. + pub(crate) fn cancel_all_animations_for_key(&self, key: &AnimationSetKey) { + if let Some(set) = self.sets.write().get_mut(key) { + set.cancel_all_animations(); + } + } +} + +/// A set of property declarations for a node, including animations and +/// transitions. +#[derive(Default)] +pub struct AnimationAndTransitionDeclarations { + /// Declarations for animations. + pub animations: Option<Arc<Locked<PropertyDeclarationBlock>>>, + /// Declarations for transitions. + pub transitions: Option<Arc<Locked<PropertyDeclarationBlock>>>, +} + +impl AnimationAndTransitionDeclarations { + /// Whether or not this `AnimationAndTransitionDeclarations` is empty. + pub(crate) fn is_empty(&self) -> bool { + self.animations.is_none() && self.transitions.is_none() + } } /// Kick off any new transitions for this node and return all of the properties that are /// transitioning. This is at the end of calculating style for a single node. pub fn start_transitions_if_applicable( context: &SharedStyleContext, old_style: &ComputedValues, new_style: &Arc<ComputedValues>,
--- a/servo/components/style/dom.rs +++ b/servo/components/style/dom.rs @@ -745,22 +745,33 @@ pub trait TElement: #[cfg(feature = "gecko")] fn process_post_animation(&self, tasks: PostAnimationTasks); /// Returns true if the element has relevant animations. Relevant /// animations are those animations that are affecting the element's style /// or are scheduled to do so in the future. fn has_animations(&self, context: &SharedStyleContext) -> bool; - /// Returns true if the element has a CSS animation. - fn has_css_animations(&self, context: &SharedStyleContext) -> bool; + /// Returns true if the element has a CSS animation. The `context` and `pseudo_element` + /// arguments are only used by Servo, since it stores animations globally and pseudo-elements + /// are not in the DOM. + fn has_css_animations( + &self, + context: &SharedStyleContext, + pseudo_element: Option<PseudoElement>, + ) -> bool; /// Returns true if the element has a CSS transition (including running transitions and - /// completed transitions). - fn has_css_transitions(&self, context: &SharedStyleContext) -> bool; + /// completed transitions). The `context` and `pseudo_element` arguments are only used + /// by Servo, since it stores animations globally and pseudo-elements are not in the DOM. + fn has_css_transitions( + &self, + context: &SharedStyleContext, + pseudo_element: Option<PseudoElement>, + ) -> bool; /// Returns true if the element has animation restyle hints. fn has_animation_restyle_hints(&self) -> bool { let data = match self.borrow_data() { Some(d) => d, None => return false, }; return data.hint.has_animation_hint();
--- a/servo/components/style/gecko/wrapper.rs +++ b/servo/components/style/gecko/wrapper.rs @@ -1515,21 +1515,21 @@ impl<'le> TElement for GeckoElement<'le> ); } } fn has_animations(&self) -> bool { self.may_have_animations() && unsafe { Gecko_ElementHasAnimations(self.0) } } - fn has_css_animations(&self, _: &SharedStyleContext) -> bool { + fn has_css_animations(&self, _: &SharedStyleContext, _: Option<PseudoElement>) -> bool { self.may_have_animations() && unsafe { Gecko_ElementHasCSSAnimations(self.0) } } - fn has_css_transitions(&self, _: &SharedStyleContext) -> bool { + fn has_css_transitions(&self, _: &SharedStyleContext, _: Option<PseudoElement>) -> bool { self.may_have_animations() && unsafe { Gecko_ElementHasCSSTransitions(self.0) } } // Detect if there are any changes that require us to update transitions. // This is used as a more thoroughgoing check than the, cheaper // might_need_transitions_update check. // // The following logic shadows the logic used on the Gecko side
--- a/servo/components/style/matching.rs +++ b/servo/components/style/matching.rs @@ -5,17 +5,17 @@ //! High-level interface to CSS selector matching. #![allow(unsafe_code)] #![deny(missing_docs)] use crate::computed_value_flags::ComputedValueFlags; use crate::context::{CascadeInputs, ElementCascadeInputs, QuirksMode, SelectorFlagsMap}; use crate::context::{SharedStyleContext, StyleContext}; -use crate::data::ElementData; +use crate::data::{ElementData, ElementStyles}; use crate::dom::TElement; #[cfg(feature = "servo")] use crate::dom::TNode; use crate::invalidation::element::restyle_hints::RestyleHint; use crate::properties::longhands::display::computed_value::T as Display; use crate::properties::ComputedValues; use crate::properties::PropertyDeclarationBlock; use crate::rule_tree::{CascadeLevel, StrongRuleNode}; @@ -85,23 +85,23 @@ enum CascadeVisitedMode { /// Cascade the styles used when an element's relevant link is visited. A /// "relevant link" is the element being matched if it is a link or the /// nearest ancestor link. Visited, } trait PrivateMatchMethods: TElement { fn replace_single_rule_node( - context: &mut StyleContext<Self>, + context: &SharedStyleContext, level: CascadeLevel, pdb: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>, path: &mut StrongRuleNode, ) -> bool { - let stylist = &context.shared.stylist; - let guards = &context.shared.guards; + let stylist = &context.stylist; + let guards = &context.guards; let mut important_rules_changed = false; let new_node = stylist.rule_tree().update_rule_at_level( level, pdb, path, guards, &mut important_rules_changed, @@ -138,23 +138,23 @@ trait PrivateMatchMethods: TElement { None => return false, }; if !context.shared.traversal_flags.for_animation_only() { let mut result = false; if replacements.contains(RestyleHint::RESTYLE_STYLE_ATTRIBUTE) { let style_attribute = self.style_attribute(); result |= Self::replace_single_rule_node( - context, + context.shared, CascadeLevel::same_tree_author_normal(), style_attribute, primary_rules, ); result |= Self::replace_single_rule_node( - context, + context.shared, CascadeLevel::same_tree_author_important(), style_attribute, primary_rules, ); // FIXME(emilio): Still a hack! self.unset_dirty_style_attribute(); } return result; @@ -165,37 +165,37 @@ trait PrivateMatchMethods: TElement { // // Non-animation restyle hints will be processed in a subsequent // normal traversal. if replacements.intersects(RestyleHint::for_animations()) { debug_assert!(context.shared.traversal_flags.for_animation_only()); if replacements.contains(RestyleHint::RESTYLE_SMIL) { Self::replace_single_rule_node( - context, + context.shared, CascadeLevel::SMILOverride, self.smil_override(), primary_rules, ); } if replacements.contains(RestyleHint::RESTYLE_CSS_TRANSITIONS) { Self::replace_single_rule_node( - context, + context.shared, CascadeLevel::Transitions, self.transition_rule(&context.shared) .as_ref() .map(|a| a.borrow_arc()), primary_rules, ); } if replacements.contains(RestyleHint::RESTYLE_CSS_ANIMATIONS) { Self::replace_single_rule_node( - context, + context.shared, CascadeLevel::Animations, self.animation_rule(&context.shared) .as_ref() .map(|a| a.borrow_arc()), primary_rules, ); } } @@ -240,21 +240,22 @@ trait PrivateMatchMethods: TElement { Some(style.0) } fn needs_animations_update( &self, context: &mut StyleContext<Self>, old_style: Option<&ComputedValues>, new_style: &ComputedValues, + pseudo_element: Option<PseudoElement>, ) -> bool { let new_box_style = new_style.get_box(); let new_style_specifies_animations = new_box_style.specifies_animations(); - let has_animations = self.has_css_animations(&context.shared); + let has_animations = self.has_css_animations(&context.shared, pseudo_element); if !new_style_specifies_animations && !has_animations { return false; } let old_style = match old_style { Some(old) => old, // If we have no old style but have animations, we may be a // pseudo-element which was re-created without style changes. @@ -321,24 +322,27 @@ trait PrivateMatchMethods: TElement { false } fn might_need_transitions_update( &self, context: &StyleContext<Self>, old_style: Option<&ComputedValues>, new_style: &ComputedValues, + pseudo_element: Option<PseudoElement>, ) -> bool { let old_style = match old_style { Some(v) => v, None => return false, }; let new_box_style = new_style.get_box(); - if !self.has_css_transitions(context.shared) && !new_box_style.specifies_transitions() { + if !self.has_css_transitions(context.shared, pseudo_element) && + !new_box_style.specifies_transitions() + { return false; } if new_box_style.clone_display().is_none() || old_style.clone_display().is_none() { return false; } return true; @@ -374,17 +378,17 @@ trait PrivateMatchMethods: TElement { context.thread_local.tasks.push(task); } } #[cfg(feature = "gecko")] fn process_animations( &self, context: &mut StyleContext<Self>, - old_values: &mut Option<Arc<ComputedValues>>, + old_styles: &mut ElementStyles, new_styles: &mut ResolvedElementStyles, restyle_hint: RestyleHint, important_rules_changed: bool, ) { use crate::context::UpdateAnimationsTasks; let new_values = new_styles.primary_style_mut(); if context.shared.traversal_flags.for_animation_only() { @@ -396,24 +400,30 @@ trait PrivateMatchMethods: TElement { ); return; } // Bug 868975: These steps should examine and update the visited styles // in addition to the unvisited styles. let mut tasks = UpdateAnimationsTasks::empty(); - if self.needs_animations_update(context, old_values.as_ref().map(|s| &**s), new_values) { + if self.needs_animations_update( + context, + old_values.as_ref().map(|s| &**s), + new_values, + /* pseudo_element = */ None, + ) { tasks.insert(UpdateAnimationsTasks::CSS_ANIMATIONS); } let before_change_style = if self.might_need_transitions_update( context, old_values.as_ref().map(|s| &**s), new_values, + /* pseudo_element = */ None, ) { let after_change_style = if self.has_css_transitions(context.shared) { self.after_change_style(context, new_values) } else { None }; // In order to avoid creating a SequentialTask for transitions which @@ -462,94 +472,193 @@ trait PrivateMatchMethods: TElement { context.thread_local.tasks.push(task); } } #[cfg(feature = "servo")] fn process_animations( &self, context: &mut StyleContext<Self>, - old_values: &mut Option<Arc<ComputedValues>>, + old_styles: &mut ElementStyles, new_resolved_styles: &mut ResolvedElementStyles, _restyle_hint: RestyleHint, _important_rules_changed: bool, ) { - if !self.process_animations_for_style( + use crate::animation::AnimationSetKey; + use crate::dom::TDocument; + + let style_changed = self.process_animations_for_style( + context, + &mut old_styles.primary, + new_resolved_styles.primary_style_mut(), + /* pseudo_element = */ None, + ); + + // If we have modified animation or transitions, we recascade style for this node. + if style_changed { + let mut rule_node = new_resolved_styles.primary_style().rules().clone(); + let declarations = context.shared.animations.get_all_declarations( + &AnimationSetKey::new_for_non_pseudo(self.as_node().opaque()), + context.shared.current_time_for_animations, + self.as_node().owner_doc().shared_lock(), + ); + Self::replace_single_rule_node( + &context.shared, + CascadeLevel::Transitions, + declarations.transitions.as_ref().map(|a| a.borrow_arc()), + &mut rule_node, + ); + Self::replace_single_rule_node( + &context.shared, + CascadeLevel::Animations, + declarations.animations.as_ref().map(|a| a.borrow_arc()), + &mut rule_node, + ); + + if rule_node != *new_resolved_styles.primary_style().rules() { + let inputs = CascadeInputs { + rules: Some(rule_node), + visited_rules: new_resolved_styles.primary_style().visited_rules().cloned(), + }; + + new_resolved_styles.primary.style = StyleResolverForElement::new( + *self, + context, + RuleInclusion::All, + PseudoElementResolution::IfApplicable, + ) + .cascade_style_and_visited_with_default_parents(inputs); + } + } + + self.process_animations_for_pseudo( context, - old_values, - new_resolved_styles.primary_style_mut(), - ) { + old_styles, + new_resolved_styles, + PseudoElement::Before, + ); + self.process_animations_for_pseudo( + context, + old_styles, + new_resolved_styles, + PseudoElement::After, + ); + } + + #[cfg(feature = "servo")] + fn process_animations_for_pseudo( + &self, + context: &mut StyleContext<Self>, + old_styles: &mut ElementStyles, + new_resolved_styles: &mut ResolvedElementStyles, + pseudo_element: PseudoElement, + ) { + use crate::animation::AnimationSetKey; + use crate::dom::TDocument; + + let key = AnimationSetKey::new_for_pseudo(self.as_node().opaque(), pseudo_element.clone()); + let mut style = match new_resolved_styles.pseudos.get(&pseudo_element) { + Some(style) => Arc::clone(style), + None => { + context + .shared + .animations + .cancel_all_animations_for_key(&key); + return; + }, + }; + + let mut old_style = old_styles.pseudos.get(&pseudo_element).cloned(); + self.process_animations_for_style( + context, + &mut old_style, + &mut style, + Some(pseudo_element.clone()), + ); + + let declarations = context.shared.animations.get_all_declarations( + &key, + context.shared.current_time_for_animations, + self.as_node().owner_doc().shared_lock(), + ); + if declarations.is_empty() { return; } - // If we have modified animation or transitions, we recascade style for this node. - let mut rule_node = new_resolved_styles.primary_style().rules().clone(); + let mut rule_node = style.rules().clone(); Self::replace_single_rule_node( - context, + &context.shared, CascadeLevel::Transitions, - self.transition_rule(&context.shared) - .as_ref() - .map(|a| a.borrow_arc()), + declarations.transitions.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); Self::replace_single_rule_node( - context, + &context.shared, CascadeLevel::Animations, - self.animation_rule(&context.shared) - .as_ref() - .map(|a| a.borrow_arc()), + declarations.animations.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); - - // If these animations haven't modified the rule now, we can just exit early. - if rule_node == *new_resolved_styles.primary_style().rules() { + if rule_node == *style.rules() { return; } let inputs = CascadeInputs { rules: Some(rule_node), - visited_rules: new_resolved_styles.primary_style().visited_rules().cloned(), + visited_rules: style.visited_rules().cloned(), }; - let style = StyleResolverForElement::new( + let new_style = StyleResolverForElement::new( *self, context, RuleInclusion::All, PseudoElementResolution::IfApplicable, ) - .cascade_style_and_visited_with_default_parents(inputs); + .cascade_style_and_visited_for_pseudo_with_default_parents( + inputs, + &pseudo_element, + &new_resolved_styles.primary, + ); - new_resolved_styles.primary.style = style; + new_resolved_styles + .pseudos + .set(&pseudo_element, new_style.0); } #[cfg(feature = "servo")] fn process_animations_for_style( &self, context: &mut StyleContext<Self>, old_values: &mut Option<Arc<ComputedValues>>, new_values: &mut Arc<ComputedValues>, + pseudo_element: Option<PseudoElement>, ) -> bool { use crate::animation::{AnimationSetKey, AnimationState}; // We need to call this before accessing the `ElementAnimationSet` from the // map because this call will do a RwLock::read(). - let needs_animations_update = - self.needs_animations_update(context, old_values.as_ref().map(|s| &**s), new_values); + let needs_animations_update = self.needs_animations_update( + context, + old_values.as_ref().map(|s| &**s), + new_values, + pseudo_element, + ); + let might_need_transitions_update = self.might_need_transitions_update( context, old_values.as_ref().map(|s| &**s), new_values, + pseudo_element, ); let mut after_change_style = None; if might_need_transitions_update { after_change_style = self.after_change_style(context, new_values); } - let key = AnimationSetKey(self.as_node().opaque()); + let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element); let shared_context = context.shared; let mut animation_set = shared_context .animations .sets .write() .remove(&key) .unwrap_or_default(); @@ -759,17 +868,17 @@ pub trait MatchMethods: TElement { data: &mut ElementData, mut new_styles: ResolvedElementStyles, important_rules_changed: bool, ) -> ChildCascadeRequirement { use std::cmp; self.process_animations( context, - &mut data.styles.primary, + &mut data.styles, &mut new_styles, data.hint, important_rules_changed, ); // First of all, update the styles. let old_styles = data.set_styles(new_styles);
--- a/servo/components/style/servo/selector_parser.rs +++ b/servo/components/style/servo/selector_parser.rs @@ -24,17 +24,17 @@ use selectors::visitor::SelectorVisitor; use std::fmt; use std::mem; use std::ops::{Deref, DerefMut}; use style_traits::{ParseError, StyleParseErrorKind}; /// A pseudo-element, both public and private. /// /// NB: If you add to this list, be sure to update `each_simple_pseudo_element` too. -#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, ToShmem)] #[allow(missing_docs)] #[repr(usize)] pub enum PseudoElement { // Eager pseudos. Keep these first so that eager_index() works. After = 0, Before, Selection, // If/when :first-letter is added, update is_first_letter accordingly.
--- a/servo/components/style/style_resolver.rs +++ b/servo/components/style/style_resolver.rs @@ -113,16 +113,27 @@ where } f( parent_style.map(|x| &**x), layout_parent_style.map(|s| &**s), ) } +fn layout_parent_style_for_pseudo<'a>( + primary_style: &'a PrimaryStyle, + layout_parent_style: Option<&'a ComputedValues>, +) -> Option<&'a ComputedValues> { + if primary_style.style().is_display_contents() { + layout_parent_style + } else { + Some(primary_style.style()) + } +} + fn eager_pseudo_is_definitely_not_generated( pseudo: &PseudoElement, style: &ComputedValues, ) -> bool { use crate::computed_value_flags::ComputedValueFlags; if !pseudo.is_before_or_after() { return false; @@ -241,21 +252,18 @@ where parent_style: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, ) -> ResolvedElementStyles { let primary_style = self.resolve_primary_style(parent_style, layout_parent_style); let mut pseudo_styles = EagerPseudoStyles::default(); if !self.element.is_pseudo_element() { - let layout_parent_style_for_pseudo = if primary_style.style().is_display_contents() { - layout_parent_style - } else { - Some(primary_style.style()) - }; + let layout_parent_style_for_pseudo = + layout_parent_style_for_pseudo(&primary_style, layout_parent_style); SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { let pseudo_style = self.resolve_pseudo_style( &pseudo, &primary_style, layout_parent_style_for_pseudo, ); if let Some(style) = pseudo_style { @@ -293,16 +301,36 @@ where inputs, parent_style, layout_parent_style, /* pseudo = */ None, ) }) } + /// Cascade a set of rules for pseudo element, using the default parent for inheritance. + pub(crate) fn cascade_style_and_visited_for_pseudo_with_default_parents( + &mut self, + inputs: CascadeInputs, + pseudo: &PseudoElement, + primary_style: &PrimaryStyle, + ) -> ResolvedStyle { + with_default_parent_styles(self.element, |_, layout_parent_style| { + let layout_parent_style_for_pseudo = + layout_parent_style_for_pseudo(primary_style, layout_parent_style); + + self.cascade_style_and_visited( + inputs, + Some(primary_style.style()), + layout_parent_style_for_pseudo, + Some(pseudo), + ) + }) + } + fn cascade_style_and_visited( &mut self, inputs: CascadeInputs, parent_style: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, pseudo: Option<&PseudoElement>, ) -> ResolvedStyle { debug_assert!(pseudo.map_or(true, |p| p.is_eager()));