Skip to content

Commit

Permalink
Add animation and transition support for pseudo-elements
Browse files Browse the repository at this point in the history
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
  • Loading branch information
mrobinson committed Jun 16, 2020
1 parent ba5568a commit f3e373b
Show file tree
Hide file tree
Showing 19 changed files with 359 additions and 138 deletions.
10 changes: 7 additions & 3 deletions components/layout_2020/flow/root.rs
Expand Up @@ -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;

Expand Down Expand Up @@ -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::<()>
});
}
Expand Down
24 changes: 17 additions & 7 deletions components/layout_thread/dom_wrapper.rs
Expand Up @@ -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(),
)
Expand All @@ -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(),
)
Expand Down Expand Up @@ -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)
}

Expand Down
16 changes: 14 additions & 2 deletions components/layout_thread/lib.rs
Expand Up @@ -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::{
Expand Down Expand Up @@ -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)
Expand Down
24 changes: 17 additions & 7 deletions components/layout_thread_2020/dom_wrapper.rs
Expand Up @@ -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(),
)
Expand All @@ -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(),
)
Expand Down Expand Up @@ -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)
}

Expand Down
26 changes: 18 additions & 8 deletions components/script/animations.rs
Expand Up @@ -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;
Expand All @@ -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)]
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -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)
});
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -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);

Expand All @@ -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>()
Expand All @@ -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>()
Expand Down Expand Up @@ -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,
Expand Down
93 changes: 88 additions & 5 deletions components/style/animation.rs
Expand Up @@ -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};
Expand Down Expand Up @@ -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.
Expand All @@ -1154,17 +1187,15 @@ 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.
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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit f3e373b

Please sign in to comment.