diff options
author | Martin Robinson <mrobinson@igalia.com> | 2020-06-15 11:51:23 +0200 |
---|---|---|
committer | Martin Robinson <mrobinson@igalia.com> | 2020-06-16 16:33:55 +0200 |
commit | f3e373bc623fd355cb08122d31a76f6548e6827a (patch) | |
tree | 94d416bc55b50b1e343ab02fad79a513d4cd418d | |
parent | ba5568a0a60cbd4bbedd3b766b7182824d75b131 (diff) | |
download | servo-f3e373bc623fd355cb08122d31a76f6548e6827a.tar.gz servo-f3e373bc623fd355cb08122d31a76f6548e6827a.zip |
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
19 files changed, 359 insertions, 138 deletions
diff --git a/components/layout_2020/flow/root.rs b/components/layout_2020/flow/root.rs index 55c325a3cd0..0d346a34eeb 100644 --- a/components/layout_2020/flow/root.rs +++ b/components/layout_2020/flow/root.rs @@ -38,6 +38,7 @@ use servo_arc::Arc; use style::animation::AnimationSetKey; use style::dom::OpaqueNode; use style::properties::ComputedValues; +use style::selector_parser::PseudoElement; use style::values::computed::Length; use style_traits::CSSPixel; @@ -449,9 +450,12 @@ impl FragmentTree { pub fn remove_nodes_in_fragment_tree_from_set(&self, set: &mut FxHashSet<AnimationSetKey>) { self.find(|fragment, _| { - if let Some(tag) = fragment.tag().as_ref() { - set.remove(&AnimationSetKey(tag.node())); - } + let (node, pseudo) = match fragment.tag()? { + Tag::Node(node) => (node, None), + Tag::BeforePseudo(node) => (node, Some(PseudoElement::Before)), + Tag::AfterPseudo(node) => (node, Some(PseudoElement::After)), + }; + set.remove(&AnimationSetKey::new(node, pseudo)); None::<()> }); } diff --git a/components/layout_thread/dom_wrapper.rs b/components/layout_thread/dom_wrapper.rs index 3d3f50317a9..f0d25ff8c49 100644 --- a/components/layout_thread/dom_wrapper.rs +++ b/components/layout_thread/dom_wrapper.rs @@ -476,7 +476,7 @@ impl<'le> TElement for ServoLayoutElement<'le> { let node = self.as_node(); let document = node.owner_doc(); context.animations.get_animation_declarations( - &AnimationSetKey(node.opaque()), + &AnimationSetKey::new_for_non_pseudo(node.opaque()), context.current_time_for_animations, document.style_shared_lock(), ) @@ -489,7 +489,7 @@ impl<'le> TElement for ServoLayoutElement<'le> { let node = self.as_node(); let document = node.owner_doc(); context.animations.get_transition_declarations( - &AnimationSetKey(node.opaque()), + &AnimationSetKey::new_for_non_pseudo(node.opaque()), context.current_time_for_animations, document.style_shared_lock(), ) @@ -613,16 +613,26 @@ impl<'le> TElement for ServoLayoutElement<'le> { } fn has_animations(&self, context: &SharedStyleContext) -> bool { - return self.has_css_animations(context) || self.has_css_transitions(context); + // This is not used for pseudo elements currently so we can pass None. + return self.has_css_animations(context, /* pseudo_element = */ None) || + self.has_css_transitions(context, /* pseudo_element = */ None); } - fn has_css_animations(&self, context: &SharedStyleContext) -> bool { - let key = AnimationSetKey(self.as_node().opaque()); + fn has_css_animations( + &self, + context: &SharedStyleContext, + pseudo_element: Option<PseudoElement>, + ) -> bool { + let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element); context.animations.has_active_animations(&key) } - fn has_css_transitions(&self, context: &SharedStyleContext) -> bool { - let key = AnimationSetKey(self.as_node().opaque()); + fn has_css_transitions( + &self, + context: &SharedStyleContext, + pseudo_element: Option<PseudoElement>, + ) -> bool { + let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element); context.animations.has_active_transitions(&key) } diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 5930e85b08c..07cb7c5a4c8 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -109,7 +109,7 @@ use style::invalidation::element::restyle_hints::RestyleHint; use style::logical_geometry::LogicalPoint; use style::media_queries::{Device, MediaList, MediaType}; use style::properties::PropertyId; -use style::selector_parser::SnapshotMap; +use style::selector_parser::{PseudoElement, SnapshotMap}; use style::servo::restyle_damage::ServoRestyleDamage; use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards}; use style::stylesheets::{ @@ -1651,7 +1651,19 @@ impl LayoutThread { fn traverse_flow(flow: &mut dyn Flow, invalid_nodes: &mut FxHashSet<AnimationSetKey>) { flow.mutate_fragments(&mut |fragment| { - invalid_nodes.remove(&AnimationSetKey(fragment.node)); + // Ideally we'd only not cancel ::before and ::after animations if they + // were actually in the tree. At this point layout has lost information + // about whether or not they exist, but have had their fragments accumulated + // together. + invalid_nodes.remove(&AnimationSetKey::new_for_non_pseudo(fragment.node)); + invalid_nodes.remove(&AnimationSetKey::new_for_pseudo( + fragment.node, + PseudoElement::Before, + )); + invalid_nodes.remove(&AnimationSetKey::new_for_pseudo( + fragment.node, + PseudoElement::After, + )); }); for kid in flow.mut_base().children.iter_mut() { traverse_flow(kid, invalid_nodes) diff --git a/components/layout_thread_2020/dom_wrapper.rs b/components/layout_thread_2020/dom_wrapper.rs index c8a7aa4dd05..d3c355c2fe7 100644 --- a/components/layout_thread_2020/dom_wrapper.rs +++ b/components/layout_thread_2020/dom_wrapper.rs @@ -484,7 +484,7 @@ impl<'le> TElement for ServoLayoutElement<'le> { let node = self.as_node(); let document = node.owner_doc(); context.animations.get_animation_declarations( - &AnimationSetKey(node.opaque()), + &AnimationSetKey::new_for_non_pseudo(node.opaque()), context.current_time_for_animations, document.style_shared_lock(), ) @@ -497,7 +497,7 @@ impl<'le> TElement for ServoLayoutElement<'le> { let node = self.as_node(); let document = node.owner_doc(); context.animations.get_transition_declarations( - &AnimationSetKey(node.opaque()), + &AnimationSetKey::new_for_non_pseudo(node.opaque()), context.current_time_for_animations, document.style_shared_lock(), ) @@ -621,16 +621,26 @@ impl<'le> TElement for ServoLayoutElement<'le> { } fn has_animations(&self, context: &SharedStyleContext) -> bool { - return self.has_css_animations(context) || self.has_css_transitions(context); + // This is not used for pseudo elements currently so we can pass None. + return self.has_css_animations(context, /* pseudo_element = */ None) || + self.has_css_transitions(context, /* pseudo_element = */ None); } - fn has_css_animations(&self, context: &SharedStyleContext) -> bool { - let key = AnimationSetKey(self.as_node().opaque()); + fn has_css_animations( + &self, + context: &SharedStyleContext, + pseudo_element: Option<PseudoElement>, + ) -> bool { + let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element); context.animations.has_active_animations(&key) } - fn has_css_transitions(&self, context: &SharedStyleContext) -> bool { - let key = AnimationSetKey(self.as_node().opaque()); + fn has_css_transitions( + &self, + context: &SharedStyleContext, + pseudo_element: Option<PseudoElement>, + ) -> bool { + let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element); context.animations.has_active_transitions(&key) } diff --git a/components/script/animations.rs b/components/script/animations.rs index 0b35ba4ae1d..bddbac14966 100644 --- a/components/script/animations.rs +++ b/components/script/animations.rs @@ -19,6 +19,7 @@ use crate::dom::event::Event; use crate::dom::node::{from_untrusted_node_address, window_from_node, Node, NodeDamage}; use crate::dom::transitionevent::TransitionEvent; use crate::dom::window::Window; +use cssparser::ToCss; use fxhash::{FxHashMap, FxHashSet}; use libc::c_void; use msg::constellation_msg::PipelineId; @@ -29,6 +30,7 @@ use style::animation::{ KeyframesIterationState, Transition, }; use style::dom::OpaqueNode; +use style::selector_parser::PseudoElement; /// The set of animations for a document. #[derive(Default, JSTraceable, MallocSizeOf)] @@ -66,7 +68,7 @@ impl Animations { pub(crate) fn mark_animating_nodes_as_dirty(&self) { let sets = self.sets.sets.read(); let rooted_nodes = self.rooted_nodes.borrow(); - for node in sets.keys().filter_map(|key| rooted_nodes.get(&key.0)) { + for node in sets.keys().filter_map(|key| rooted_nodes.get(&key.node)) { node.dirty(NodeDamage::NodeStyleDamaged); } } @@ -287,7 +289,7 @@ impl Animations { let js_runtime = window.get_js_runtime().as_ref().unwrap().rt(); let mut rooted_nodes = self.rooted_nodes.borrow_mut(); for (key, set) in sets.iter() { - let opaque_node = key.0; + let opaque_node = key.node; if rooted_nodes.contains_key(&opaque_node) { continue; } @@ -309,7 +311,7 @@ impl Animations { // Unroot any nodes that we have rooted but are no longer tracking animations for. fn unroot_unused_nodes(&self, sets: &FxHashMap<AnimationSetKey, ElementAnimationSet>) { let pending_events = self.pending_events.borrow(); - let nodes: FxHashSet<OpaqueNode> = sets.keys().map(|key| key.0).collect(); + let nodes: FxHashSet<OpaqueNode> = sets.keys().map(|key| key.node).collect(); self.rooted_nodes.borrow_mut().retain(|node, _| { nodes.contains(&node) || pending_events.iter().any(|event| event.node == *node) }); @@ -344,7 +346,8 @@ impl Animations { .push(TransitionOrAnimationEvent { pipeline_id, event_type, - node: key.0, + node: key.node, + pseudo_element: key.pseudo_element.clone(), property_or_animation_name: transition .property_animation .property_id() @@ -392,7 +395,8 @@ impl Animations { .push(TransitionOrAnimationEvent { pipeline_id, event_type, - node: key.0, + node: key.node, + pseudo_element: key.pseudo_element.clone(), property_or_animation_name: animation.name.to_string(), elapsed_time, }); @@ -429,9 +433,13 @@ impl Animations { cancelable: false, }; - // TODO: Handle pseudo-elements properly let property_or_animation_name = DOMString::from(event.property_or_animation_name.clone()); + let pseudo_element = event + .pseudo_element + .map_or_else(DOMString::new, |pseudo_element| { + DOMString::from(pseudo_element.to_css_string()) + }); let elapsed_time = Finite::new(event.elapsed_time as f32).unwrap(); let window = window_from_node(&*node); @@ -440,7 +448,7 @@ impl Animations { parent, propertyName: property_or_animation_name, elapsedTime: elapsed_time, - pseudoElement: DOMString::new(), + pseudoElement: pseudo_element, }; TransitionEvent::new(&window, event_atom, &event_init) .upcast::<Event>() @@ -450,7 +458,7 @@ impl Animations { parent, animationName: property_or_animation_name, elapsedTime: elapsed_time, - pseudoElement: DOMString::new(), + pseudoElement: pseudo_element, }; AnimationEvent::new(&window, event_atom, &event_init) .upcast::<Event>() @@ -513,6 +521,8 @@ pub struct TransitionOrAnimationEvent { pub event_type: TransitionOrAnimationEventType, /// The address of the node which owns this transition. pub node: OpaqueNode, + /// The pseudo element for this transition or animation, if applicable. + pub pseudo_element: Option<PseudoElement>, /// The name of the property that is transitioning (in the case of a transition) /// or the name of the animation (in the case of an animation). pub property_or_animation_name: String, diff --git a/components/style/animation.rs b/components/style/animation.rs index ecb921a5e4a..ad5e41553fe 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -19,6 +19,7 @@ use crate::properties::{ 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}; @@ -1138,7 +1139,39 @@ impl ElementAnimationSet { #[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. @@ -1154,8 +1187,7 @@ impl DocumentAnimationSet { 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. @@ -1163,8 +1195,7 @@ impl DocumentAnimationSet { 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 @@ -1202,6 +1233,58 @@ impl DocumentAnimationSet { 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 diff --git a/components/style/dom.rs b/components/style/dom.rs index 9b61cfbd3af..83908cf00cd 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -750,12 +750,23 @@ pub trait TElement: /// 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 { diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 4d680c3ff02..e301bdd1107 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -1520,11 +1520,11 @@ impl<'le> TElement for GeckoElement<'le> { 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) } } diff --git a/components/style/matching.rs b/components/style/matching.rs index a8edf36829e..95742c68f4a 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -10,7 +10,7 @@ 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; @@ -90,13 +90,13 @@ enum CascadeVisitedMode { 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( @@ -143,13 +143,13 @@ trait PrivateMatchMethods: TElement { 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, @@ -170,7 +170,7 @@ trait PrivateMatchMethods: TElement { if replacements.contains(RestyleHint::RESTYLE_SMIL) { Self::replace_single_rule_node( - context, + context.shared, CascadeLevel::SMILOverride, self.smil_override(), primary_rules, @@ -179,7 +179,7 @@ trait PrivateMatchMethods: TElement { if replacements.contains(RestyleHint::RESTYLE_CSS_TRANSITIONS) { Self::replace_single_rule_node( - context, + context.shared, CascadeLevel::Transitions, self.transition_rule(&context.shared) .as_ref() @@ -190,7 +190,7 @@ trait PrivateMatchMethods: TElement { if replacements.contains(RestyleHint::RESTYLE_CSS_ANIMATIONS) { Self::replace_single_rule_node( - context, + context.shared, CascadeLevel::Animations, self.animation_rule(&context.shared) .as_ref() @@ -245,11 +245,12 @@ trait PrivateMatchMethods: TElement { 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; } @@ -326,6 +327,7 @@ trait PrivateMatchMethods: TElement { context: &StyleContext<Self>, old_style: Option<&ComputedValues>, new_style: &ComputedValues, + pseudo_element: Option<PseudoElement>, ) -> bool { let old_style = match old_style { Some(v) => v, @@ -333,7 +335,9 @@ trait PrivateMatchMethods: TElement { }; 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; } @@ -379,7 +383,7 @@ trait PrivateMatchMethods: TElement { 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, @@ -401,7 +405,12 @@ trait PrivateMatchMethods: TElement { // 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); } @@ -409,6 +418,7 @@ trait PrivateMatchMethods: TElement { 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) @@ -464,57 +474,149 @@ trait PrivateMatchMethods: TElement { 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, - old_values, + &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_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")] @@ -523,17 +625,24 @@ trait PrivateMatchMethods: TElement { 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; @@ -541,7 +650,7 @@ trait PrivateMatchMethods: TElement { 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 @@ -761,7 +870,7 @@ pub trait MatchMethods: TElement { self.process_animations( context, - &mut data.styles.primary, + &mut data.styles, &mut new_styles, data.hint, important_rules_changed, diff --git a/components/style/servo/selector_parser.rs b/components/style/servo/selector_parser.rs index af4dd92fcf7..a3a3ca7c2b8 100644 --- a/components/style/servo/selector_parser.rs +++ b/components/style/servo/selector_parser.rs @@ -29,7 +29,7 @@ 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 { diff --git a/components/style/style_resolver.rs b/components/style/style_resolver.rs index 47b0ca53bd7..5e9da39b122 100644 --- a/components/style/style_resolver.rs +++ b/components/style/style_resolver.rs @@ -118,6 +118,17 @@ where ) } +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, @@ -246,11 +257,8 @@ where 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, @@ -298,6 +306,26 @@ where }) } + /// 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, diff --git a/tests/wpt/metadata-layout-2020/css/css-animations/animationevent-pseudoelement.html.ini b/tests/wpt/metadata-layout-2020/css/css-animations/animationevent-pseudoelement.html.ini deleted file mode 100644 index fa19a3fabd9..00000000000 --- a/tests/wpt/metadata-layout-2020/css/css-animations/animationevent-pseudoelement.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[animationevent-pseudoelement.html] - expected: TIMEOUT - [AnimationEvent should have the correct pseudoElement memeber] - expected: TIMEOUT - diff --git a/tests/wpt/metadata-layout-2020/css/css-transitions/events-006.html.ini b/tests/wpt/metadata-layout-2020/css/css-transitions/events-006.html.ini deleted file mode 100644 index 2fc7af21dd4..00000000000 --- a/tests/wpt/metadata-layout-2020/css/css-transitions/events-006.html.ini +++ /dev/null @@ -1,8 +0,0 @@ -[events-006.html] - expected: TIMEOUT - [transition padding-left on ::after] - expected: NOTRUN - - [transition padding-left on ::before] - expected: TIMEOUT - diff --git a/tests/wpt/metadata-layout-2020/css/css-transitions/non-rendered-element-002.html.ini b/tests/wpt/metadata-layout-2020/css/css-transitions/non-rendered-element-002.html.ini deleted file mode 100644 index d0600a9c6b2..00000000000 --- a/tests/wpt/metadata-layout-2020/css/css-transitions/non-rendered-element-002.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[non-rendered-element-002.html] - expected: TIMEOUT - [Transitions on ::before/::after pseudo-elements are canceled when the content property is cleared] - expected: TIMEOUT - diff --git a/tests/wpt/metadata-layout-2020/css/css-transitions/pseudo-elements-001.html.ini b/tests/wpt/metadata-layout-2020/css/css-transitions/pseudo-elements-001.html.ini deleted file mode 100644 index d43cd5b241b..00000000000 --- a/tests/wpt/metadata-layout-2020/css/css-transitions/pseudo-elements-001.html.ini +++ /dev/null @@ -1,13 +0,0 @@ -[pseudo-elements-001.html] - [transition padding-left on :before / values] - expected: FAIL - - [transition padding-left on :after, changing content / values] - expected: FAIL - - [transition padding-left on :before, changing content / values] - expected: FAIL - - [transition padding-left on :after / values] - expected: FAIL - diff --git a/tests/wpt/metadata/css/css-animations/animationevent-pseudoelement.html.ini b/tests/wpt/metadata/css/css-animations/animationevent-pseudoelement.html.ini deleted file mode 100644 index c7f08fec5f7..00000000000 --- a/tests/wpt/metadata/css/css-animations/animationevent-pseudoelement.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[animationevent-pseudoelement.html] - bug: https://github.com/servo/servo/issues/10316 - expected: TIMEOUT - [AnimationEvent should have the correct pseudoElement memeber] - expected: TIMEOUT - diff --git a/tests/wpt/metadata/css/css-transitions/events-006.html.ini b/tests/wpt/metadata/css/css-transitions/events-006.html.ini deleted file mode 100644 index edacd476df2..00000000000 --- a/tests/wpt/metadata/css/css-transitions/events-006.html.ini +++ /dev/null @@ -1,9 +0,0 @@ -[events-006.html] - bug: https://github.com/servo/servo/issues/10316 - expected: TIMEOUT - [transition padding-left on ::after] - expected: NOTRUN - - [transition padding-left on ::before] - expected: TIMEOUT - diff --git a/tests/wpt/metadata/css/css-transitions/non-rendered-element-002.html.ini b/tests/wpt/metadata/css/css-transitions/non-rendered-element-002.html.ini index 684f225e665..1f8c58a6bc8 100644 --- a/tests/wpt/metadata/css/css-transitions/non-rendered-element-002.html.ini +++ b/tests/wpt/metadata/css/css-transitions/non-rendered-element-002.html.ini @@ -1,8 +1,4 @@ [non-rendered-element-002.html] - expected: TIMEOUT - [Transitions on ::before/::after pseudo-elements are canceled when the content property is cleared] - expected: TIMEOUT - [Transitions on ::marker pseudo-elements are canceled when the parent display type is no longer list-item] expected: NOTRUN diff --git a/tests/wpt/metadata/css/css-transitions/pseudo-elements-001.html.ini b/tests/wpt/metadata/css/css-transitions/pseudo-elements-001.html.ini index d43cd5b241b..8bb346ac786 100644 --- a/tests/wpt/metadata/css/css-transitions/pseudo-elements-001.html.ini +++ b/tests/wpt/metadata/css/css-transitions/pseudo-elements-001.html.ini @@ -2,12 +2,6 @@ [transition padding-left on :before / values] expected: FAIL - [transition padding-left on :after, changing content / values] - expected: FAIL - - [transition padding-left on :before, changing content / values] - expected: FAIL - [transition padding-left on :after / values] expected: FAIL |