use std::cell::Cell;
use base::id::PipelineId;
use cssparser::ToCss;
use fxhash::{FxHashMap, FxHashSet};
use libc::c_void;
use script_traits::{AnimationState as AnimationsPresentState, ScriptMsg, UntrustedNodeAddress};
use serde::{Deserialize, Serialize};
use style::animation::{
Animation, AnimationSetKey, AnimationState, DocumentAnimationSet, ElementAnimationSet,
KeyframesIterationState, Transition,
};
use style::dom::OpaqueNode;
use style::selector_parser::PseudoElement;
use crate::dom::animationevent::AnimationEvent;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::AnimationEventBinding::AnimationEventInit;
use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit;
use crate::dom::bindings::codegen::Bindings::TransitionEventBinding::TransitionEventInit;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::trace::NoTrace;
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 crate::script_runtime::CanGc;
#[derive(Default, JSTraceable, MallocSizeOf)]
#[crown::unrooted_must_root_lint::must_root]
pub(crate) struct Animations {
#[no_trace]
pub sets: DocumentAnimationSet,
has_running_animations: Cell<bool>,
rooted_nodes: DomRefCell<FxHashMap<NoTrace<OpaqueNode>, Dom<Node>>>,
pending_events: DomRefCell<Vec<TransitionOrAnimationEvent>>,
timeline_value_at_last_dirty: Cell<f64>,
}
impl Animations {
pub(crate) fn new() -> Self {
Animations {
sets: Default::default(),
has_running_animations: Cell::new(false),
rooted_nodes: Default::default(),
pending_events: Default::default(),
timeline_value_at_last_dirty: Cell::new(0.0),
}
}
pub(crate) fn clear(&self) {
self.sets.sets.write().clear();
self.rooted_nodes.borrow_mut().clear();
self.pending_events.borrow_mut().clear();
}
pub(crate) fn mark_animating_nodes_as_dirty(&self, current_timeline_value: f64) -> bool {
if current_timeline_value <= self.timeline_value_at_last_dirty.get() {
return false;
}
self.timeline_value_at_last_dirty
.set(current_timeline_value);
let sets = self.sets.sets.read();
let rooted_nodes = self.rooted_nodes.borrow();
for node in sets
.keys()
.filter_map(|key| rooted_nodes.get(&NoTrace(key.node)))
{
node.dirty(NodeDamage::NodeStyleDamaged);
}
true
}
pub(crate) fn update_for_new_timeline_value(&self, window: &Window, now: f64) {
let pipeline_id = window.pipeline_id();
let mut sets = self.sets.sets.write();
for (key, set) in sets.iter_mut() {
self.start_pending_animations(key, set, now, pipeline_id);
for animation in set.animations.iter_mut() {
if animation.iterate_if_necessary(now) {
self.add_animation_event(
key,
animation,
TransitionOrAnimationEventType::AnimationIteration,
now,
pipeline_id,
);
}
}
self.finish_running_animations(key, set, now, pipeline_id);
}
self.unroot_unused_nodes(&sets);
}
pub(crate) fn cancel_animations_for_node(&self, node: &Node) {
let mut animations = self.sets.sets.write();
let mut cancel_animations_for = |key| {
if let Some(set) = animations.get_mut(&key) {
set.cancel_all_animations();
}
};
let opaque_node = node.to_opaque();
cancel_animations_for(AnimationSetKey::new_for_non_pseudo(opaque_node));
cancel_animations_for(AnimationSetKey::new_for_pseudo(
opaque_node,
PseudoElement::Before,
));
cancel_animations_for(AnimationSetKey::new_for_pseudo(
opaque_node,
PseudoElement::After,
));
}
pub(crate) fn do_post_reflow_update(&self, window: &Window, now: f64) {
let pipeline_id = window.pipeline_id();
let mut sets = self.sets.sets.write();
self.root_newly_animating_dom_nodes(&sets);
for (key, set) in sets.iter_mut() {
self.handle_canceled_animations(key, set, now, pipeline_id);
self.handle_new_animations(key, set, now, pipeline_id);
}
sets.retain(|_, state| !state.is_empty());
let have_running_animations = sets.values().any(|state| state.needs_animation_ticks());
self.update_running_animations_presence(window, have_running_animations);
}
fn update_running_animations_presence(&self, window: &Window, new_value: bool) {
let had_running_animations = self.has_running_animations.get();
if new_value == had_running_animations {
return;
}
self.has_running_animations.set(new_value);
self.handle_animation_presence_or_pending_events_change(window);
}
fn handle_animation_presence_or_pending_events_change(&self, window: &Window) {
let has_running_animations = self.has_running_animations.get();
let has_pending_events = !self.pending_events.borrow().is_empty();
let state = match has_running_animations || has_pending_events {
true => AnimationsPresentState::AnimationsPresent,
false => AnimationsPresentState::NoAnimationsPresent,
};
window.send_to_constellation(ScriptMsg::ChangeRunningAnimationsState(state));
}
pub(crate) fn running_animation_count(&self) -> usize {
self.sets
.sets
.read()
.values()
.map(|state| state.running_animation_and_transition_count())
.sum()
}
fn start_pending_animations(
&self,
key: &AnimationSetKey,
set: &mut ElementAnimationSet,
now: f64,
pipeline_id: PipelineId,
) {
for animation in set.animations.iter_mut() {
if animation.state == AnimationState::Pending && animation.started_at <= now {
animation.state = AnimationState::Running;
self.add_animation_event(
key,
animation,
TransitionOrAnimationEventType::AnimationStart,
now,
pipeline_id,
);
}
}
for transition in set.transitions.iter_mut() {
if transition.state == AnimationState::Pending && transition.start_time <= now {
transition.state = AnimationState::Running;
self.add_transition_event(
key,
transition,
TransitionOrAnimationEventType::TransitionStart,
now,
pipeline_id,
);
}
}
}
fn finish_running_animations(
&self,
key: &AnimationSetKey,
set: &mut ElementAnimationSet,
now: f64,
pipeline_id: PipelineId,
) {
for animation in set.animations.iter_mut() {
if animation.state == AnimationState::Running && animation.has_ended(now) {
animation.state = AnimationState::Finished;
self.add_animation_event(
key,
animation,
TransitionOrAnimationEventType::AnimationEnd,
now,
pipeline_id,
);
}
}
for transition in set.transitions.iter_mut() {
if transition.state == AnimationState::Running && transition.has_ended(now) {
transition.state = AnimationState::Finished;
self.add_transition_event(
key,
transition,
TransitionOrAnimationEventType::TransitionEnd,
now,
pipeline_id,
);
}
}
}
fn handle_canceled_animations(
&self,
key: &AnimationSetKey,
set: &mut ElementAnimationSet,
now: f64,
pipeline_id: PipelineId,
) {
for transition in &set.transitions {
if transition.state == AnimationState::Canceled {
self.add_transition_event(
key,
transition,
TransitionOrAnimationEventType::TransitionCancel,
now,
pipeline_id,
);
}
}
for animation in &set.animations {
if animation.state == AnimationState::Canceled {
self.add_animation_event(
key,
animation,
TransitionOrAnimationEventType::AnimationCancel,
now,
pipeline_id,
);
}
}
set.clear_canceled_animations();
}
fn handle_new_animations(
&self,
key: &AnimationSetKey,
set: &mut ElementAnimationSet,
now: f64,
pipeline_id: PipelineId,
) {
for animation in set.animations.iter_mut() {
animation.is_new = false;
}
for transition in set.transitions.iter_mut() {
if transition.is_new {
self.add_transition_event(
key,
transition,
TransitionOrAnimationEventType::TransitionRun,
now,
pipeline_id,
);
transition.is_new = false;
}
}
}
#[allow(unsafe_code)]
fn root_newly_animating_dom_nodes(
&self,
sets: &FxHashMap<AnimationSetKey, ElementAnimationSet>,
) {
let mut rooted_nodes = self.rooted_nodes.borrow_mut();
for (key, set) in sets.iter() {
let opaque_node = key.node;
if rooted_nodes.contains_key(&NoTrace(opaque_node)) {
continue;
}
if set.animations.iter().any(|animation| animation.is_new) ||
set.transitions.iter().any(|transition| transition.is_new)
{
let address = UntrustedNodeAddress(opaque_node.0 as *const c_void);
unsafe {
rooted_nodes.insert(
NoTrace(opaque_node),
Dom::from_ref(&*from_untrusted_node_address(address)),
)
};
}
}
}
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.node).collect();
self.rooted_nodes.borrow_mut().retain(|node, _| {
nodes.contains(&node.0) || pending_events.iter().any(|event| event.node == node.0)
});
}
fn add_transition_event(
&self,
key: &AnimationSetKey,
transition: &Transition,
event_type: TransitionOrAnimationEventType,
now: f64,
pipeline_id: PipelineId,
) {
let elapsed_time = match event_type {
TransitionOrAnimationEventType::TransitionRun |
TransitionOrAnimationEventType::TransitionStart => transition
.property_animation
.duration
.min((-transition.delay).max(0.)),
TransitionOrAnimationEventType::TransitionEnd => transition.property_animation.duration,
TransitionOrAnimationEventType::TransitionCancel => {
(now - transition.start_time).max(0.)
},
_ => unreachable!(),
}
.abs();
self.pending_events
.borrow_mut()
.push(TransitionOrAnimationEvent {
pipeline_id,
event_type,
node: key.node,
pseudo_element: key.pseudo_element,
property_or_animation_name: transition
.property_animation
.property_id()
.name()
.into(),
elapsed_time,
});
}
fn add_animation_event(
&self,
key: &AnimationSetKey,
animation: &Animation,
event_type: TransitionOrAnimationEventType,
now: f64,
pipeline_id: PipelineId,
) {
let iteration_index = match animation.iteration_state {
KeyframesIterationState::Finite(current, _) |
KeyframesIterationState::Infinite(current) => current,
};
let active_duration = match animation.iteration_state {
KeyframesIterationState::Finite(_, max) => max * animation.duration,
KeyframesIterationState::Infinite(_) => f64::MAX,
};
let elapsed_time = match event_type {
TransitionOrAnimationEventType::AnimationStart => {
(-animation.delay).max(0.).min(active_duration)
},
TransitionOrAnimationEventType::AnimationIteration => {
iteration_index * animation.duration
},
TransitionOrAnimationEventType::AnimationEnd => {
(iteration_index * animation.duration) + animation.current_iteration_duration()
},
TransitionOrAnimationEventType::AnimationCancel => {
(iteration_index * animation.duration) + (now - animation.started_at).max(0.)
},
_ => unreachable!(),
}
.abs();
self.pending_events
.borrow_mut()
.push(TransitionOrAnimationEvent {
pipeline_id,
event_type,
node: key.node,
pseudo_element: key.pseudo_element,
property_or_animation_name: animation.name.to_string(),
elapsed_time,
});
}
pub(crate) fn send_pending_events(&self, window: &Window, can_gc: CanGc) {
let events = std::mem::take(&mut *self.pending_events.borrow_mut());
if events.is_empty() {
return;
}
for event in events.into_iter() {
let node = match self.rooted_nodes.borrow().get(&NoTrace(event.node)) {
Some(node) => DomRoot::from_ref(&**node),
None => {
warn!("Tried to send an event for an unrooted node");
continue;
},
};
let event_atom = match event.event_type {
TransitionOrAnimationEventType::AnimationEnd => atom!("animationend"),
TransitionOrAnimationEventType::AnimationStart => atom!("animationstart"),
TransitionOrAnimationEventType::AnimationCancel => atom!("animationcancel"),
TransitionOrAnimationEventType::AnimationIteration => atom!("animationiteration"),
TransitionOrAnimationEventType::TransitionCancel => atom!("transitioncancel"),
TransitionOrAnimationEventType::TransitionEnd => atom!("transitionend"),
TransitionOrAnimationEventType::TransitionRun => atom!("transitionrun"),
TransitionOrAnimationEventType::TransitionStart => atom!("transitionstart"),
};
let parent = EventInit {
bubbles: true,
cancelable: false,
};
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);
if event.event_type.is_transition_event() {
let event_init = TransitionEventInit {
parent,
propertyName: property_or_animation_name,
elapsedTime: elapsed_time,
pseudoElement: pseudo_element,
};
TransitionEvent::new(&window, event_atom, &event_init, can_gc)
.upcast::<Event>()
.fire(node.upcast(), can_gc);
} else {
let event_init = AnimationEventInit {
parent,
animationName: property_or_animation_name,
elapsedTime: elapsed_time,
pseudoElement: pseudo_element,
};
AnimationEvent::new(&window, event_atom, &event_init, can_gc)
.upcast::<Event>()
.fire(node.upcast(), can_gc);
}
}
if self.pending_events.borrow().is_empty() {
self.handle_animation_presence_or_pending_events_change(window);
}
}
}
#[derive(Clone, Debug, Deserialize, JSTraceable, MallocSizeOf, Serialize)]
pub enum TransitionOrAnimationEventType {
TransitionRun,
TransitionStart,
TransitionEnd,
TransitionCancel,
AnimationStart,
AnimationIteration,
AnimationEnd,
AnimationCancel,
}
impl TransitionOrAnimationEventType {
pub fn is_transition_event(&self) -> bool {
match *self {
Self::TransitionRun |
Self::TransitionEnd |
Self::TransitionCancel |
Self::TransitionStart => true,
Self::AnimationEnd |
Self::AnimationIteration |
Self::AnimationStart |
Self::AnimationCancel => false,
}
}
}
#[derive(Deserialize, JSTraceable, MallocSizeOf, Serialize)]
pub struct TransitionOrAnimationEvent {
#[no_trace]
pub pipeline_id: PipelineId,
pub event_type: TransitionOrAnimationEventType,
#[no_trace]
pub node: OpaqueNode,
#[no_trace]
pub pseudo_element: Option<PseudoElement>,
pub property_or_animation_name: String,
pub elapsed_time: f64,
}