Bug 1646811 - servo: Add animation and transition support for pseudo-elements.
authorMartin Robinson <mrobinson@igalia.com>
Thu, 18 Jun 2020 18:14:05 +0000
changeset 536372 0da5430c2b2eb7ad0592dc831be1945750813132
parent 536371 04f8394b23bb2fcc2f19d6baf1a31a66be6bc297
child 536373 e6d0a75ce3952ea7575d77c565e5dcd536b1ea03
push id119420
push userealvarez@mozilla.com
push dateThu, 18 Jun 2020 18:18:12 +0000
treeherderautoland@206011a5cc20 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1646811, 10316
milestone79.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
Bug 1646811 - servo: Add animation and transition support for pseudo-elements. This change extends the DocumentAnimationSet to hold animations for pseudo-elements. Since pseudo-elements in Servo are not in the DOM like in Gecko, they need to be handled a bit carefully in stylo. When a pseudo-element has an animation, recascade the style. Finally, this change passes the pseudo-element string properly to animation events. Fixes: #10316 Depends on D80242 Differential Revision: https://phabricator.services.mozilla.com/D80243
servo/components/style/animation.rs
servo/components/style/dom.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/matching.rs
servo/components/style/servo/selector_parser.rs
servo/components/style/style_resolver.rs
--- 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()));