servo/components/style/animation.rs
Martin Robinson b875f14e86 Better computation of animation keyframes
This begins to address #26625 by properly applying CSS variables during
keyframe computation and no longer using `apply_declarations`. Instead,
walk the declarations, combining them into IntermediateComputedKeyframe,
maintaining declarations that modify CSS custom properties. Then compute
a set of AnimationValues for each keyframe and use those to produce
interpolated animation values.
2020-06-05 13:40:29 +02:00

1232 lines
46 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::{CascadeInputs, SharedStyleContext};
use crate::dom::{OpaqueNode, TDocument, TElement, TNode};
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::{
ComputedValues, Importance, LonghandId, LonghandIdSet, PropertyDeclarationBlock,
PropertyDeclarationId,
};
use crate::rule_tree::CascadeLevel;
use crate::style_resolver::StyleResolverForElement;
use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue};
use crate::values::animated::{Animate, Procedure};
use crate::values::computed::{Time, TimingFunction};
use crate::values::generics::box_::AnimationIterationCount;
use crate::values::generics::easing::{StepPosition, TimingFunction as GenericTimingFunction};
use crate::Atom;
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 temporary data structure used when calculating ComputedKeyframes for an
/// animation. This data structure is used to collapse information for steps
/// which may be spread across multiple keyframe declarations into a single
/// instance per `start_percentage`.
struct IntermediateComputedKeyframe {
declarations: PropertyDeclarationBlock,
timing_function: Option<TimingFunction>,
start_percentage: f32,
}
impl IntermediateComputedKeyframe {
fn new(start_percentage: f32) -> Self {
IntermediateComputedKeyframe {
declarations: PropertyDeclarationBlock::new(),
timing_function: None,
start_percentage,
}
}
/// Walk through all keyframe declarations and combine all declarations with the
/// same `start_percentage` into individual `IntermediateComputedKeyframe`s.
fn generate_for_keyframes(
animation: &KeyframesAnimation,
context: &SharedStyleContext,
base_style: &ComputedValues,
) -> Vec<Self> {
let mut intermediate_steps: Vec<Self> = Vec::with_capacity(animation.steps.len());
let mut current_step = IntermediateComputedKeyframe::new(0.);
for step in animation.steps.iter() {
let start_percentage = step.start_percentage.0;
if start_percentage != current_step.start_percentage {
let new_step = IntermediateComputedKeyframe::new(start_percentage);
intermediate_steps.push(std::mem::replace(&mut current_step, new_step));
}
current_step.update_from_step(step, context, base_style);
}
intermediate_steps.push(current_step);
// We should always have a first and a last step, even if these are just
// generated by KeyframesStepValue::ComputedValues.
debug_assert!(intermediate_steps.first().unwrap().start_percentage == 0.);
debug_assert!(intermediate_steps.last().unwrap().start_percentage == 1.);
intermediate_steps
}
fn update_from_step(
&mut self,
step: &KeyframesStep,
context: &SharedStyleContext,
base_style: &ComputedValues,
) {
// Each keyframe declaration may optionally specify a timing function, falling
// back to the one defined global for the animation.
let guard = &context.guards.author;
if let Some(timing_function) = step.get_animation_timing_function(&guard) {
self.timing_function = Some(timing_function.to_computed_value_without_context());
}
let block = match step.value {
KeyframesStepValue::ComputedValues => return,
KeyframesStepValue::Declarations { ref block } => block,
};
// Filter out !important, non-animatable properties, and the
// 'display' property (which is only animatable from SMIL).
let guard = block.read_with(&guard);
for declaration in guard.normal_declaration_iter() {
if let PropertyDeclarationId::Longhand(id) = declaration.id() {
if id == LonghandId::Display {
continue;
}
if !id.is_animatable() {
continue;
}
}
self.declarations.push(
declaration.to_physical(base_style.writing_mode),
Importance::Normal,
);
}
}
fn resolve_style<E>(
self,
element: E,
context: &SharedStyleContext,
base_style: &Arc<ComputedValues>,
resolver: &mut StyleResolverForElement<E>,
) -> Arc<ComputedValues>
where
E: TElement,
{
if !self.declarations.any_normal() {
return base_style.clone();
}
let document = element.as_node().owner_doc();
let locked_block = Arc::new(document.shared_lock().wrap(self.declarations));
let mut important_rules_changed = false;
let rule_node = base_style.rules().clone();
let new_node = context.stylist.rule_tree().update_rule_at_level(
CascadeLevel::Animations,
Some(locked_block.borrow_arc()),
&rule_node,
&context.guards,
&mut important_rules_changed,
);
if new_node.is_none() {
return base_style.clone();
}
let inputs = CascadeInputs {
rules: new_node,
visited_rules: base_style.visited_rules().cloned(),
};
resolver
.cascade_style_and_visited_with_default_parents(inputs)
.0
}
}
/// A single computed keyframe for a CSS Animation.
#[derive(Clone, MallocSizeOf)]
struct ComputedKeyframe {
/// The timing function to use for transitions between this step
/// and the next one.
timing_function: TimingFunction,
/// The starting percentage (a number between 0 and 1) which represents
/// at what point in an animation iteration this step is.
start_percentage: f32,
/// The animation values to transition to and from when processing this
/// keyframe animation step.
values: Vec<AnimationValue>,
}
impl ComputedKeyframe {
fn generate_for_keyframes<E>(
element: E,
animation: &KeyframesAnimation,
context: &SharedStyleContext,
base_style: &Arc<ComputedValues>,
default_timing_function: TimingFunction,
resolver: &mut StyleResolverForElement<E>,
) -> Vec<Self>
where
E: TElement,
{
let mut animating_properties = LonghandIdSet::new();
for property in animation.properties_changed.iter() {
debug_assert!(property.is_animatable());
animating_properties.insert(property.to_physical(base_style.writing_mode));
}
let animation_values_from_style: Vec<AnimationValue> = animating_properties
.iter()
.map(|property| {
AnimationValue::from_computed_values(property, &**base_style)
.expect("Unexpected non-animatable property.")
})
.collect();
let intermediate_steps =
IntermediateComputedKeyframe::generate_for_keyframes(animation, context, base_style);
let mut computed_steps: Vec<Self> = Vec::with_capacity(intermediate_steps.len());
for (step_index, step) in intermediate_steps.into_iter().enumerate() {
let start_percentage = step.start_percentage;
let timing_function = step.timing_function.unwrap_or(default_timing_function);
let properties_changed_in_step = step.declarations.longhands().clone();
let step_style = step.resolve_style(element, context, base_style, resolver);
let values = {
// If a value is not set in a property declaration we use the value from
// the style for the first and last keyframe. For intermediate ones, we
// use the value from the previous keyframe.
//
// TODO(mrobinson): According to the spec, we should use an interpolated
// value for properties missing from keyframe declarations.
let default_values = if start_percentage == 0. || start_percentage == 1.0 {
&animation_values_from_style
} else {
debug_assert!(step_index != 0);
&computed_steps[step_index - 1].values
};
// For each property that is animating, pull the value from the resolved
// style for this step if it's in one of the declarations. Otherwise, we
// use the default value from the set we calculated above.
animating_properties
.iter()
.zip(default_values.iter())
.map(|(longhand, default_value)| {
if properties_changed_in_step.contains(longhand) {
AnimationValue::from_computed_values(longhand, &step_style)
.unwrap_or_else(|| default_value.clone())
} else {
default_value.clone()
}
})
.collect()
};
computed_steps.push(ComputedKeyframe {
timing_function,
start_percentage,
values,
});
}
computed_steps
}
}
/// 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 properties that change in this animation.
properties_changed: LonghandIdSet,
/// The computed style for each keyframe of this animation.
computed_steps: Vec<ComputedKeyframe>,
/// 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(&self, context: &SharedStyleContext, style: &mut Arc<ComputedValues>) {
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.computed_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;
let num_steps = self.computed_steps.len();
debug_assert!(num_steps > 0);
match self.current_direction {
AnimationDirection::Normal => {
next_keyframe_index = self
.computed_steps
.iter()
.position(|step| total_progress as f32 <= step.start_percentage);
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
.computed_steps
.iter()
.rev()
.position(|step| total_progress as f32 <= 1. - step.start_percentage)
.map(|pos| num_steps - pos - 1);
prev_keyframe_index = next_keyframe_index
.and_then(|pos| {
if pos != num_steps - 1 {
Some(pos + 1)
} else {
None
}
})
.unwrap_or(num_steps - 1)
},
_ => unreachable!(),
}
debug!(
"Animation::update_style: keyframe from {:?} to {:?}",
prev_keyframe_index, next_keyframe_index
);
let prev_keyframe = &self.computed_steps[prev_keyframe_index];
let next_keyframe = match next_keyframe_index {
Some(index) => &self.computed_steps[index],
None => return,
};
let update_with_single_keyframe_style = |style, keyframe: &ComputedKeyframe| {
let mutable_style = Arc::make_mut(style);
for value in keyframe.values.iter() {
value.set_in_style_for_servo(mutable_style);
}
};
if total_progress <= 0.0 {
update_with_single_keyframe_style(style, &prev_keyframe);
return;
}
if total_progress >= 1.0 {
update_with_single_keyframe_style(style, &next_keyframe);
return;
}
let relative_timespan =
(next_keyframe.start_percentage - prev_keyframe.start_percentage).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 as f64)
},
AnimationDirection::Reverse => {
self.started_at + (duration * (1. - prev_keyframe.start_percentage as f64))
},
_ => unreachable!(),
};
let relative_progress = (now - last_keyframe_ended_at) / relative_duration;
let mut new_style = (**style).clone();
for (from, to) in prev_keyframe.values.iter().zip(next_keyframe.values.iter()) {
PropertyAnimation {
from: from.clone(),
to: to.clone(),
timing_function: prev_keyframe.timing_function,
duration: relative_duration as f64,
}
.update(&mut new_style, relative_progress);
}
*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(
&mut self,
context: &SharedStyleContext,
style: &mut Arc<ComputedValues>,
) {
for animation in &self.animations {
animation.update_style(context, style);
}
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()
}
/// If this `ElementAnimationSet` has any any active animations.
pub fn has_active_animation(&self) -> bool {
self.animations
.iter()
.any(|animation| animation.state != AnimationState::Canceled)
}
/// If this `ElementAnimationSet` has any any active transitions.
pub fn has_active_transition(&self) -> bool {
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>,
resolver: &mut StyleResolverForElement<E>,
) 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, resolver);
}
/// Update our transitions given a new style, canceling or starting new animations
/// when appropriate.
pub fn update_transitions_for_new_style(
&mut self,
context: &SharedStyleContext,
opaque_node: OpaqueNode,
old_style: Option<&Arc<ComputedValues>>,
after_change_style: &Arc<ComputedValues>,
) {
// 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() || self.has_active_animation() {
before_change_style = before_change_style.clone();
self.apply_active_animations(context, &mut before_change_style);
}
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
}
/// 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,
resolver: &mut StyleResolverForElement<E>,
) 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 keyframe_animation = 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 keyframe_animation.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 computed_steps = ComputedKeyframe::generate_for_keyframes(
element,
&keyframe_animation,
context,
new_style,
new_style.get_box().animation_timing_function_mod(i),
resolver,
);
let new_animation = Animation {
node: element.as_node().opaque(),
name: name.clone(),
properties_changed: keyframe_animation.properties_changed,
computed_steps,
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);
}
}