servo/components/style/animation.rs
2020-05-26 20:34:58 +02:00

1109 lines
42 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! CSS transitions and animations.
// NOTE(emilio): This code isn't really executed in Gecko, but we don't want to
// compile it out so that people remember it exists.
use crate::bezier::Bezier;
use crate::context::SharedStyleContext;
use crate::dom::{OpaqueNode, TElement, TNode};
use crate::font_metrics::FontMetricsProvider;
use crate::properties::animated_properties::AnimationValue;
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::LonghandIdSet;
use crate::properties::{self, CascadeMode, ComputedValues, LonghandId};
use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue};
use crate::stylesheets::Origin;
use crate::values::animated::{Animate, Procedure};
use crate::values::computed::Time;
use crate::values::computed::TimingFunction;
use crate::values::generics::box_::AnimationIterationCount;
use crate::values::generics::easing::{StepPosition, TimingFunction as GenericTimingFunction};
use crate::Atom;
use servo_arc::Arc;
use std::fmt;
/// Represents an animation for a given property.
#[derive(Clone, Debug, MallocSizeOf)]
pub struct PropertyAnimation {
/// The value we are animating from.
from: AnimationValue,
/// The value we are animating to.
to: AnimationValue,
/// The timing function of this `PropertyAnimation`.
timing_function: TimingFunction,
/// The duration of this `PropertyAnimation` in seconds.
pub duration: f64,
}
impl PropertyAnimation {
/// Returns the given property longhand id.
pub fn property_id(&self) -> LonghandId {
debug_assert_eq!(self.from.id(), self.to.id());
self.from.id()
}
fn from_longhand(
longhand: LonghandId,
timing_function: TimingFunction,
duration: Time,
old_style: &ComputedValues,
new_style: &ComputedValues,
) -> Option<PropertyAnimation> {
// FIXME(emilio): Handle the case where old_style and new_style's writing mode differ.
let longhand = longhand.to_physical(new_style.writing_mode);
let from = AnimationValue::from_computed_values(longhand, old_style)?;
let to = AnimationValue::from_computed_values(longhand, new_style)?;
let duration = duration.seconds() as f64;
if from == to || duration == 0.0 {
return None;
}
Some(PropertyAnimation {
from,
to,
timing_function,
duration,
})
}
/// The output of the timing function given the progress ration of this animation.
fn timing_function_output(&self, progress: f64) -> f64 {
let epsilon = 1. / (200. * self.duration);
match self.timing_function {
GenericTimingFunction::CubicBezier { x1, y1, x2, y2 } => {
Bezier::new(x1, y1, x2, y2).solve(progress, epsilon)
},
GenericTimingFunction::Steps(steps, pos) => {
let mut current_step = (progress * (steps as f64)).floor() as i32;
if pos == StepPosition::Start ||
pos == StepPosition::JumpStart ||
pos == StepPosition::JumpBoth
{
current_step = current_step + 1;
}
// FIXME: We should update current_step according to the "before flag".
// In order to get the before flag, we have to know the current animation phase
// and whether the iteration is reversed. For now, we skip this calculation.
// (i.e. Treat before_flag is unset,)
// https://drafts.csswg.org/css-easing/#step-timing-function-algo
if progress >= 0.0 && current_step < 0 {
current_step = 0;
}
let jumps = match pos {
StepPosition::JumpBoth => steps + 1,
StepPosition::JumpNone => steps - 1,
StepPosition::JumpStart |
StepPosition::JumpEnd |
StepPosition::Start |
StepPosition::End => steps,
};
if progress <= 1.0 && current_step > jumps {
current_step = jumps;
}
(current_step as f64) / (jumps as f64)
},
GenericTimingFunction::Keyword(keyword) => {
let (x1, x2, y1, y2) = keyword.to_bezier();
Bezier::new(x1, x2, y1, y2).solve(progress, epsilon)
},
}
}
/// Update the given animation at a given point of progress.
fn update(&self, style: &mut ComputedValues, progress: f64) {
let procedure = Procedure::Interpolate {
progress: self.timing_function_output(progress),
};
if let Ok(new_value) = self.from.animate(&self.to, procedure) {
new_value.set_in_style_for_servo(style);
}
}
}
/// This structure represents the state of an animation.
#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
pub enum AnimationState {
/// The animation has been created, but is not running yet. This state
/// is also used when an animation is still in the first delay phase.
Pending,
/// This animation is currently running.
Running,
/// This animation is paused. The inner field is the percentage of progress
/// when it was paused, from 0 to 1.
Paused(f64),
/// This animation has finished.
Finished,
/// This animation has been canceled.
Canceled,
}
impl AnimationState {
/// Whether or not this state requires its owning animation to be ticked.
fn needs_to_be_ticked(&self) -> bool {
*self == AnimationState::Running || *self == AnimationState::Pending
}
}
/// This structure represents a keyframes animation current iteration state.
///
/// If the iteration count is infinite, there's no other state, otherwise we
/// have to keep track the current iteration and the max iteration count.
#[derive(Clone, Debug, MallocSizeOf)]
pub enum KeyframesIterationState {
/// Infinite iterations with the current iteration count.
Infinite(f64),
/// Current and max iterations.
Finite(f64, f64),
}
/// A CSS Animation
#[derive(Clone, MallocSizeOf)]
pub struct Animation {
/// The node associated with this animation.
pub node: OpaqueNode,
/// The name of this animation as defined by the style.
pub name: Atom,
/// The internal animation from the style system.
pub keyframes_animation: KeyframesAnimation,
/// The time this animation started at, which is the current value of the animation
/// timeline when this animation was created plus any animation delay.
pub started_at: f64,
/// The duration of this animation.
pub duration: f64,
/// The delay of the animation.
pub delay: f64,
/// The `animation-fill-mode` property of this animation.
pub fill_mode: AnimationFillMode,
/// The current iteration state for the animation.
pub iteration_state: KeyframesIterationState,
/// Whether this animation is paused.
pub state: AnimationState,
/// The declared animation direction of this animation.
pub direction: AnimationDirection,
/// The current animation direction. This can only be `normal` or `reverse`.
pub current_direction: AnimationDirection,
/// The original cascade style, needed to compute the generated keyframes of
/// the animation.
#[ignore_malloc_size_of = "ComputedValues"]
pub cascade_style: Arc<ComputedValues>,
/// Whether or not this animation is new and or has already been tracked
/// by the script thread.
pub is_new: bool,
}
impl Animation {
/// Whether or not this animation is cancelled by changes from a new style.
fn is_cancelled_in_new_style(&self, new_style: &Arc<ComputedValues>) -> bool {
let index = new_style
.get_box()
.animation_name_iter()
.position(|animation_name| Some(&self.name) == animation_name.as_atom());
let index = match index {
Some(index) => index,
None => return true,
};
new_style.get_box().animation_duration_mod(index).seconds() == 0.
}
/// Given the current time, advances this animation to the next iteration,
/// updates times, and then toggles the direction if appropriate. Otherwise
/// does nothing. Returns true if this animation has iterated.
pub fn iterate_if_necessary(&mut self, time: f64) -> bool {
if !self.iteration_over(time) {
return false;
}
// Only iterate animations that are currently running.
if self.state != AnimationState::Running {
return false;
}
if let KeyframesIterationState::Finite(ref mut current, max) = self.iteration_state {
// If we are already on the final iteration, just exit now. This prevents
// us from updating the direction, which might be needed for the correct
// handling of animation-fill-mode and also firing animationiteration events
// at the end of animations.
*current = (*current + 1.).min(max);
if *current == max {
return false;
}
}
// Update the next iteration direction if applicable.
// TODO(mrobinson): The duration might now be wrong for floating point iteration counts.
self.started_at += self.duration + self.delay;
match self.direction {
AnimationDirection::Alternate | AnimationDirection::AlternateReverse => {
self.current_direction = match self.current_direction {
AnimationDirection::Normal => AnimationDirection::Reverse,
AnimationDirection::Reverse => AnimationDirection::Normal,
_ => unreachable!(),
};
},
_ => {},
}
true
}
fn iteration_over(&self, time: f64) -> bool {
time > (self.started_at + self.duration)
}
/// Whether or not this animation has finished at the provided time. This does
/// not take into account canceling i.e. when an animation or transition is
/// canceled due to changes in the style.
pub fn has_ended(&self, time: f64) -> bool {
match self.state {
AnimationState::Running => {},
AnimationState::Finished => return true,
AnimationState::Pending | AnimationState::Canceled | AnimationState::Paused(_) => {
return false
},
}
if !self.iteration_over(time) {
return false;
}
// If we have a limited number of iterations and we cannot advance to another
// iteration, then we have ended.
return match self.iteration_state {
KeyframesIterationState::Finite(current, max) => max == current,
KeyframesIterationState::Infinite(..) => false,
};
}
/// Updates the appropiate state from other animation.
///
/// This happens when an animation is re-submitted to layout, presumably
/// because of an state change.
///
/// There are some bits of state we can't just replace, over all taking in
/// account times, so here's that logic.
pub fn update_from_other(&mut self, other: &Self, now: f64) {
use self::AnimationState::*;
debug!(
"KeyframesAnimationState::update_from_other({:?}, {:?})",
self, other
);
// NB: We shall not touch the started_at field, since we don't want to
// restart the animation.
let old_started_at = self.started_at;
let old_duration = self.duration;
let old_direction = self.current_direction;
let old_state = self.state.clone();
let old_iteration_state = self.iteration_state.clone();
*self = other.clone();
self.started_at = old_started_at;
self.current_direction = old_direction;
// Don't update the iteration count, just the iteration limit.
// TODO: see how changing the limit affects rendering in other browsers.
// We might need to keep the iteration count even when it's infinite.
match (&mut self.iteration_state, old_iteration_state) {
(
&mut KeyframesIterationState::Finite(ref mut iters, _),
KeyframesIterationState::Finite(old_iters, _),
) => *iters = old_iters,
_ => {},
}
// Don't pause or restart animations that should remain finished.
// We call mem::replace because `has_ended(...)` looks at `Animation::state`.
let new_state = std::mem::replace(&mut self.state, Running);
if old_state == Finished && self.has_ended(now) {
self.state = Finished;
} else {
self.state = new_state;
}
// If we're unpausing the animation, fake the start time so we seem to
// restore it.
//
// If the animation keeps paused, keep the old value.
//
// If we're pausing the animation, compute the progress value.
match (&mut self.state, &old_state) {
(&mut Pending, &Paused(progress)) => {
self.started_at = now - (self.duration * progress);
},
(&mut Paused(ref mut new), &Paused(old)) => *new = old,
(&mut Paused(ref mut progress), &Running) => {
*progress = (now - old_started_at) / old_duration
},
_ => {},
}
// Try to detect when we should skip straight to the running phase to
// avoid sending multiple animationstart events.
if self.state == Pending && self.started_at <= now && old_state != Pending {
self.state = Running;
}
}
/// Update the given style to reflect the values specified by this `Animation`
/// at the time provided by the given `SharedStyleContext`.
fn update_style<E>(
&self,
context: &SharedStyleContext,
style: &mut Arc<ComputedValues>,
font_metrics_provider: &dyn FontMetricsProvider,
) where
E: TElement,
{
let duration = self.duration;
let started_at = self.started_at;
let now = match self.state {
AnimationState::Running | AnimationState::Pending | AnimationState::Finished => {
context.current_time_for_animations
},
AnimationState::Paused(progress) => started_at + duration * progress,
AnimationState::Canceled => return,
};
debug_assert!(!self.keyframes_animation.steps.is_empty());
let mut total_progress = (now - started_at) / duration;
if total_progress < 0. &&
self.fill_mode != AnimationFillMode::Backwards &&
self.fill_mode != AnimationFillMode::Both
{
return;
}
if total_progress > 1. &&
self.fill_mode != AnimationFillMode::Forwards &&
self.fill_mode != AnimationFillMode::Both
{
return;
}
total_progress = total_progress.min(1.0).max(0.0);
// Get the indices of the previous (from) keyframe and the next (to) keyframe.
let next_keyframe_index;
let prev_keyframe_index;
match self.current_direction {
AnimationDirection::Normal => {
next_keyframe_index = self
.keyframes_animation
.steps
.iter()
.position(|step| total_progress as f32 <= step.start_percentage.0);
prev_keyframe_index = next_keyframe_index
.and_then(|pos| if pos != 0 { Some(pos - 1) } else { None })
.unwrap_or(0);
},
AnimationDirection::Reverse => {
next_keyframe_index = self
.keyframes_animation
.steps
.iter()
.rev()
.position(|step| total_progress as f32 <= 1. - step.start_percentage.0)
.map(|pos| self.keyframes_animation.steps.len() - pos - 1);
prev_keyframe_index = next_keyframe_index
.and_then(|pos| {
if pos != self.keyframes_animation.steps.len() - 1 {
Some(pos + 1)
} else {
None
}
})
.unwrap_or(self.keyframes_animation.steps.len() - 1)
},
_ => unreachable!(),
}
debug!(
"Animation::update_style: keyframe from {:?} to {:?}",
prev_keyframe_index, next_keyframe_index
);
let prev_keyframe = &self.keyframes_animation.steps[prev_keyframe_index];
let next_keyframe = match next_keyframe_index {
Some(target) => &self.keyframes_animation.steps[target],
None => return,
};
let update_with_single_keyframe_style = |style, computed_style: &Arc<ComputedValues>| {
let mutable_style = Arc::make_mut(style);
for property in self
.keyframes_animation
.properties_changed
.iter()
.filter_map(|longhand| {
AnimationValue::from_computed_values(longhand, &**computed_style)
})
{
property.set_in_style_for_servo(mutable_style);
}
};
// TODO: How could we optimise it? Is it such a big deal?
let prev_keyframe_style = compute_style_for_animation_step::<E>(
context,
prev_keyframe,
style,
&self.cascade_style,
font_metrics_provider,
);
if total_progress <= 0.0 {
update_with_single_keyframe_style(style, &prev_keyframe_style);
return;
}
let next_keyframe_style = compute_style_for_animation_step::<E>(
context,
next_keyframe,
&prev_keyframe_style,
&self.cascade_style,
font_metrics_provider,
);
if total_progress >= 1.0 {
update_with_single_keyframe_style(style, &next_keyframe_style);
return;
}
let relative_timespan =
(next_keyframe.start_percentage.0 - prev_keyframe.start_percentage.0).abs();
let relative_duration = relative_timespan as f64 * duration;
let last_keyframe_ended_at = match self.current_direction {
AnimationDirection::Normal => {
self.started_at + (duration * prev_keyframe.start_percentage.0 as f64)
},
AnimationDirection::Reverse => {
self.started_at + (duration * (1. - prev_keyframe.start_percentage.0 as f64))
},
_ => unreachable!(),
};
let relative_progress = (now - last_keyframe_ended_at) / relative_duration;
// NB: The spec says that the timing function can be overwritten
// from the keyframe style.
let timing_function = if prev_keyframe.declared_timing_function {
// NB: animation_timing_function can never be empty, always has
// at least the default value (`ease`).
prev_keyframe_style
.get_box()
.animation_timing_function_at(0)
} else {
// TODO(mrobinson): It isn't optimal to have to walk this list every
// time. Perhaps this should be stored in the animation.
let index = match style
.get_box()
.animation_name_iter()
.position(|animation_name| Some(&self.name) == animation_name.as_atom())
{
Some(index) => index,
None => return warn!("Tried to update a style with a cancelled animation."),
};
style.get_box().animation_timing_function_mod(index)
};
let mut new_style = (**style).clone();
let mut update_style_for_longhand = |longhand| {
let from = AnimationValue::from_computed_values(longhand, &prev_keyframe_style)?;
let to = AnimationValue::from_computed_values(longhand, &next_keyframe_style)?;
PropertyAnimation {
from,
to,
timing_function,
duration: relative_duration as f64,
}
.update(&mut new_style, relative_progress);
None::<()>
};
for property in self.keyframes_animation.properties_changed.iter() {
update_style_for_longhand(property);
}
*Arc::make_mut(style) = new_style;
}
}
impl fmt::Debug for Animation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Animation")
.field("name", &self.name)
.field("started_at", &self.started_at)
.field("duration", &self.duration)
.field("delay", &self.delay)
.field("iteration_state", &self.iteration_state)
.field("state", &self.state)
.field("direction", &self.direction)
.field("current_direction", &self.current_direction)
.field("cascade_style", &())
.finish()
}
}
/// A CSS Transition
#[derive(Clone, Debug, MallocSizeOf)]
pub struct Transition {
/// The node associated with this animation.
pub node: OpaqueNode,
/// The start time of this transition, which is the current value of the animation
/// timeline when this transition was created plus any animation delay.
pub start_time: f64,
/// The delay used for this transition.
pub delay: f64,
/// The internal style `PropertyAnimation` for this transition.
pub property_animation: PropertyAnimation,
/// The state of this transition.
pub state: AnimationState,
/// Whether or not this transition is new and or has already been tracked
/// by the script thread.
pub is_new: bool,
/// If this `Transition` has been replaced by a new one this field is
/// used to help produce better reversed transitions.
pub reversing_adjusted_start_value: AnimationValue,
/// If this `Transition` has been replaced by a new one this field is
/// used to help produce better reversed transitions.
pub reversing_shortening_factor: f64,
}
impl Transition {
fn update_for_possibly_reversed_transition(
&mut self,
replaced_transition: &Transition,
delay: f64,
now: f64,
) {
// If we reach here, we need to calculate a reversed transition according to
// https://drafts.csswg.org/css-transitions/#starting
//
// "...if the reversing-adjusted start value of the running transition
// is the same as the value of the property in the after-change style (see
// the section on reversing of transitions for why these case exists),
// implementations must cancel the running transition and start
// a new transition..."
if replaced_transition.reversing_adjusted_start_value != self.property_animation.to {
return;
}
// "* reversing-adjusted start value is the end value of the running transition"
let replaced_animation = &replaced_transition.property_animation;
self.reversing_adjusted_start_value = replaced_animation.to.clone();
// "* reversing shortening factor is the absolute value, clamped to the
// range [0, 1], of the sum of:
// 1. the output of the timing function of the old transition at the
// time of the style change event, times the reversing shortening
// factor of the old transition
// 2. 1 minus the reversing shortening factor of the old transition."
let transition_progress = replaced_transition.progress(now);
let timing_function_output = replaced_animation.timing_function_output(transition_progress);
let old_reversing_shortening_factor = replaced_transition.reversing_shortening_factor;
self.reversing_shortening_factor = ((timing_function_output *
old_reversing_shortening_factor) +
(1.0 - old_reversing_shortening_factor))
.abs()
.min(1.0)
.max(0.0);
// "* start time is the time of the style change event plus:
// 1. if the matching transition delay is nonnegative, the matching
// transition delay, or.
// 2. if the matching transition delay is negative, the product of the new
// transitions reversing shortening factor and the matching transition delay,"
self.start_time = if delay >= 0. {
now + delay
} else {
now + (self.reversing_shortening_factor * delay)
};
// "* end time is the start time plus the product of the matching transition
// duration and the new transitions reversing shortening factor,"
self.property_animation.duration *= self.reversing_shortening_factor;
// "* start value is the current value of the property in the running transition,
// * end value is the value of the property in the after-change style,"
let procedure = Procedure::Interpolate {
progress: timing_function_output,
};
match replaced_animation
.from
.animate(&replaced_animation.to, procedure)
{
Ok(new_start) => self.property_animation.from = new_start,
Err(..) => {},
}
}
/// Whether or not this animation has ended at the provided time. This does
/// not take into account canceling i.e. when an animation or transition is
/// canceled due to changes in the style.
pub fn has_ended(&self, time: f64) -> bool {
time >= self.start_time + (self.property_animation.duration)
}
/// Whether this animation has the same end value as another one.
#[inline]
fn progress(&self, now: f64) -> f64 {
let progress = (now - self.start_time) / (self.property_animation.duration);
progress.min(1.0)
}
/// Update a style to the value specified by this `Transition` given a `SharedStyleContext`.
fn update_style(&self, context: &SharedStyleContext, style: &mut Arc<ComputedValues>) {
// Never apply canceled transitions to a style.
if self.state == AnimationState::Canceled {
return;
}
let progress = self.progress(context.current_time_for_animations);
if progress >= 0.0 {
self.property_animation
.update(Arc::make_mut(style), progress);
}
}
}
/// Holds the animation state for a particular element.
#[derive(Debug, Default, MallocSizeOf)]
pub struct ElementAnimationSet {
/// The animations for this element.
pub animations: Vec<Animation>,
/// The transitions for this element.
pub transitions: Vec<Transition>,
}
impl ElementAnimationSet {
/// Cancel all animations in this `ElementAnimationSet`. This is typically called
/// when the element has been removed from the DOM.
pub fn cancel_all_animations(&mut self) {
for animation in self.animations.iter_mut() {
animation.state = AnimationState::Canceled;
}
for transition in self.transitions.iter_mut() {
transition.state = AnimationState::Canceled;
}
}
pub(crate) fn apply_active_animations<E>(
&mut self,
context: &SharedStyleContext,
style: &mut Arc<ComputedValues>,
font_metrics: &dyn crate::font_metrics::FontMetricsProvider,
) where
E: TElement,
{
for animation in &self.animations {
animation.update_style::<E>(context, style, font_metrics);
}
for transition in &self.transitions {
transition.update_style(context, style);
}
}
/// Clear all canceled animations and transitions from this `ElementAnimationSet`.
pub fn clear_canceled_animations(&mut self) {
self.animations
.retain(|animation| animation.state != AnimationState::Canceled);
self.transitions
.retain(|animation| animation.state != AnimationState::Canceled);
}
/// Whether this `ElementAnimationSet` is empty, which means it doesn't
/// hold any animations in any state.
pub fn is_empty(&self) -> bool {
self.animations.is_empty() && self.transitions.is_empty()
}
/// Whether or not this state needs animation ticks for its transitions
/// or animations.
pub fn needs_animation_ticks(&self) -> bool {
self.animations
.iter()
.any(|animation| animation.state.needs_to_be_ticked()) ||
self.transitions
.iter()
.any(|transition| transition.state.needs_to_be_ticked())
}
/// The number of running animations and transitions for this `ElementAnimationSet`.
pub fn running_animation_and_transition_count(&self) -> usize {
self.animations
.iter()
.filter(|animation| animation.state.needs_to_be_ticked())
.count() +
self.transitions
.iter()
.filter(|transition| transition.state.needs_to_be_ticked())
.count()
}
fn has_active_transition_or_animation(&self) -> bool {
self.animations
.iter()
.any(|animation| animation.state != AnimationState::Canceled) ||
self.transitions
.iter()
.any(|transition| transition.state != AnimationState::Canceled)
}
/// Update our animations given a new style, canceling or starting new animations
/// when appropriate.
pub fn update_animations_for_new_style<E>(
&mut self,
element: E,
context: &SharedStyleContext,
new_style: &Arc<ComputedValues>,
) where
E: TElement,
{
for animation in self.animations.iter_mut() {
if animation.is_cancelled_in_new_style(new_style) {
animation.state = AnimationState::Canceled;
}
}
maybe_start_animations(element, &context, &new_style, self);
}
/// Update our transitions given a new style, canceling or starting new animations
/// when appropriate.
pub fn update_transitions_for_new_style<E>(
&mut self,
context: &SharedStyleContext,
opaque_node: OpaqueNode,
old_style: Option<&Arc<ComputedValues>>,
after_change_style: &Arc<ComputedValues>,
font_metrics: &dyn crate::font_metrics::FontMetricsProvider,
) where
E: TElement,
{
// If this is the first style, we don't trigger any transitions and we assume
// there were no previously triggered transitions.
let mut before_change_style = match old_style {
Some(old_style) => Arc::clone(old_style),
None => return,
};
// We convert old values into `before-change-style` here.
// See https://drafts.csswg.org/css-transitions/#starting. We need to clone the
// style because this might still be a reference to the original `old_style` and
// we want to preserve that so that we can later properly calculate restyle damage.
if self.has_active_transition_or_animation() {
before_change_style = before_change_style.clone();
self.apply_active_animations::<E>(context, &mut before_change_style, font_metrics);
}
let transitioning_properties = start_transitions_if_applicable(
context,
opaque_node,
&before_change_style,
after_change_style,
self,
);
// Cancel any non-finished transitions that have properties which no longer transition.
for transition in self.transitions.iter_mut() {
if transition.state == AnimationState::Finished {
continue;
}
if transitioning_properties.contains(transition.property_animation.property_id()) {
continue;
}
transition.state = AnimationState::Canceled;
}
}
fn start_transition_if_applicable(
&mut self,
context: &SharedStyleContext,
opaque_node: OpaqueNode,
longhand_id: LonghandId,
index: usize,
old_style: &ComputedValues,
new_style: &Arc<ComputedValues>,
) {
let box_style = new_style.get_box();
let timing_function = box_style.transition_timing_function_mod(index);
let duration = box_style.transition_duration_mod(index);
let delay = box_style.transition_delay_mod(index).seconds() as f64;
let now = context.current_time_for_animations;
// Only start a new transition if the style actually changes between
// the old style and the new style.
let property_animation = match PropertyAnimation::from_longhand(
longhand_id,
timing_function,
duration,
old_style,
new_style,
) {
Some(property_animation) => property_animation,
None => return,
};
// Per [1], don't trigger a new transition if the end state for that
// transition is the same as that of a transition that's running or
// completed. We don't take into account any canceled animations.
// [1]: https://drafts.csswg.org/css-transitions/#starting
if self
.transitions
.iter()
.filter(|transition| transition.state != AnimationState::Canceled)
.any(|transition| transition.property_animation.to == property_animation.to)
{
return;
}
// We are going to start a new transition, but we might have to update
// it if we are replacing a reversed transition.
let reversing_adjusted_start_value = property_animation.from.clone();
let mut new_transition = Transition {
node: opaque_node,
start_time: now + delay,
delay,
property_animation,
state: AnimationState::Pending,
is_new: true,
reversing_adjusted_start_value,
reversing_shortening_factor: 1.0,
};
if let Some(old_transition) = self
.transitions
.iter_mut()
.filter(|transition| transition.state == AnimationState::Running)
.find(|transition| transition.property_animation.property_id() == longhand_id)
{
// We always cancel any running transitions for the same property.
old_transition.state = AnimationState::Canceled;
new_transition.update_for_possibly_reversed_transition(old_transition, delay, now);
}
self.transitions.push(new_transition);
}
}
/// 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,
opaque_node: OpaqueNode,
old_style: &ComputedValues,
new_style: &Arc<ComputedValues>,
animation_state: &mut ElementAnimationSet,
) -> LonghandIdSet {
// If the style of this element is display:none, then we don't start any transitions
// and we cancel any currently running transitions by returning an empty LonghandIdSet.
let box_style = new_style.get_box();
if box_style.clone_display().is_none() {
return LonghandIdSet::new();
}
let mut properties_that_transition = LonghandIdSet::new();
for transition in new_style.transition_properties() {
let physical_property = transition.longhand_id.to_physical(new_style.writing_mode);
if properties_that_transition.contains(physical_property) {
continue;
}
properties_that_transition.insert(physical_property);
animation_state.start_transition_if_applicable(
context,
opaque_node,
physical_property,
transition.index,
old_style,
new_style,
);
}
properties_that_transition
}
fn compute_style_for_animation_step<E>(
context: &SharedStyleContext,
step: &KeyframesStep,
previous_style: &ComputedValues,
style_from_cascade: &Arc<ComputedValues>,
font_metrics_provider: &dyn FontMetricsProvider,
) -> Arc<ComputedValues>
where
E: TElement,
{
match step.value {
KeyframesStepValue::ComputedValues => style_from_cascade.clone(),
KeyframesStepValue::Declarations {
block: ref declarations,
} => {
let guard = declarations.read_with(context.guards.author);
let iter = || {
// It's possible to have !important properties in keyframes
// so we have to filter them out.
// See the spec issue https://github.com/w3c/csswg-drafts/issues/1824
// Also we filter our non-animatable properties.
guard
.normal_declaration_iter()
.filter(|declaration| declaration.is_animatable())
.map(|decl| (decl, Origin::Author))
};
// This currently ignores visited styles, which seems acceptable,
// as existing browsers don't appear to animate visited styles.
let computed = properties::apply_declarations::<E, _, _>(
context.stylist.device(),
/* pseudo = */ None,
previous_style.rules(),
&context.guards,
iter,
Some(previous_style),
Some(previous_style),
Some(previous_style),
font_metrics_provider,
CascadeMode::Unvisited {
visited_rules: None,
},
context.quirks_mode(),
/* rule_cache = */ None,
&mut Default::default(),
/* element = */ None,
);
computed
},
}
}
/// Triggers animations for a given node looking at the animation property
/// values.
pub fn maybe_start_animations<E>(
element: E,
context: &SharedStyleContext,
new_style: &Arc<ComputedValues>,
animation_state: &mut ElementAnimationSet,
) where
E: TElement,
{
let box_style = new_style.get_box();
for (i, name) in box_style.animation_name_iter().enumerate() {
let name = match name.as_atom() {
Some(atom) => atom,
None => continue,
};
debug!("maybe_start_animations: name={}", name);
let duration = box_style.animation_duration_mod(i).seconds();
if duration == 0. {
continue;
}
let anim = match context.stylist.get_animation(name, element) {
Some(animation) => animation,
None => continue,
};
debug!("maybe_start_animations: animation {} found", name);
// If this animation doesn't have any keyframe, we can just continue
// without submitting it to the compositor, since both the first and
// the second keyframes would be synthetised from the computed
// values.
if anim.steps.is_empty() {
continue;
}
let delay = box_style.animation_delay_mod(i).seconds();
let animation_start = context.current_time_for_animations + delay as f64;
let iteration_state = match box_style.animation_iteration_count_mod(i) {
AnimationIterationCount::Infinite => KeyframesIterationState::Infinite(0.0),
AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n.into()),
};
let animation_direction = box_style.animation_direction_mod(i);
let initial_direction = match animation_direction {
AnimationDirection::Normal | AnimationDirection::Alternate => {
AnimationDirection::Normal
},
AnimationDirection::Reverse | AnimationDirection::AlternateReverse => {
AnimationDirection::Reverse
},
};
let state = match box_style.animation_play_state_mod(i) {
AnimationPlayState::Paused => AnimationState::Paused(0.),
AnimationPlayState::Running => AnimationState::Pending,
};
let new_animation = Animation {
node: element.as_node().opaque(),
name: name.clone(),
keyframes_animation: anim.clone(),
started_at: animation_start,
duration: duration as f64,
fill_mode: box_style.animation_fill_mode_mod(i),
delay: delay as f64,
iteration_state,
state,
direction: animation_direction,
current_direction: initial_direction,
cascade_style: new_style.clone(),
is_new: true,
};
// If the animation was already present in the list for the node, just update its state.
for existing_animation in animation_state.animations.iter_mut() {
if existing_animation.state == AnimationState::Canceled {
continue;
}
if new_animation.name == existing_animation.name {
existing_animation
.update_from_other(&new_animation, context.current_time_for_animations);
return;
}
}
animation_state.animations.push(new_animation);
}
}