1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5 //! CSS transitions and animations.
6
7 // NOTE(emilio): This code isn't really executed in Gecko, but we don't want to
8 // compile it out so that people remember it exists, thus the cfg'd Sender
9 // import.
10
11 use crate::bezier::Bezier;
12 use crate::context::SharedStyleContext;
13 use crate::dom::{OpaqueNode, TElement};
14 use crate::font_metrics::FontMetricsProvider;
15 use crate::properties::animated_properties::AnimatedProperty;
16 use crate::properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection;
17 use crate::properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState;
18 use crate::properties::{self, CascadeMode, ComputedValues, LonghandId};
19 #[cfg(feature = "servo")]
20 use crate::properties::LonghandIdSet;
21 use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue};
22 use crate::stylesheets::Origin;
23 use crate::timer::Timer;
24 use crate::values::computed::Time;
25 use crate::values::computed::TimingFunction;
26 use crate::values::generics::box_::AnimationIterationCount;
27 use crate::values::generics::easing::{StepPosition, TimingFunction as GenericTimingFunction};
28 use crate::Atom;
29 #[cfg(feature = "servo")]
30 use crossbeam_channel::Sender;
31 use servo_arc::Arc;
32 use std::fmt;
33 #[cfg(feature = "gecko")]
34 use std::sync::mpsc::Sender;
35
36 /// This structure represents a keyframes animation current iteration state.
37 ///
38 /// If the iteration count is infinite, there's no other state, otherwise we
39 /// have to keep track the current iteration and the max iteration count.
40 #[derive(Clone, Debug)]
41 pub enum KeyframesIterationState {
42 /// Infinite iterations, so no need to track a state.
43 Infinite,
44 /// Current and max iterations.
45 Finite(f32, f32),
46 }
47
48 /// This structure represents wether an animation is actually running.
49 ///
50 /// An animation can be running, or paused at a given time.
51 #[derive(Clone, Debug)]
52 pub enum KeyframesRunningState {
53 /// This animation is paused. The inner field is the percentage of progress
54 /// when it was paused, from 0 to 1.
55 Paused(f64),
56 /// This animation is actually running.
57 Running,
58 }
59
60 /// This structure represents the current keyframe animation state, i.e., the
61 /// duration, the current and maximum iteration count, and the state (either
62 /// playing or paused).
63 // TODO: unify the use of f32/f64 in this file.
64 #[derive(Clone)]
65 pub struct KeyframesAnimationState {
66 /// The time this animation started at.
67 pub started_at: f64,
68 /// The duration of this animation.
69 pub duration: f64,
70 /// The delay of the animation.
71 pub delay: f64,
72 /// The current iteration state for the animation.
73 pub iteration_state: KeyframesIterationState,
74 /// Werther this animation is paused.
75 pub running_state: KeyframesRunningState,
76 /// The declared animation direction of this animation.
77 pub direction: AnimationDirection,
78 /// The current animation direction. This can only be `normal` or `reverse`.
79 pub current_direction: AnimationDirection,
80 /// Werther this keyframe animation is outdated due to a restyle.
81 pub expired: bool,
82 /// The original cascade style, needed to compute the generated keyframes of
83 /// the animation.
84 pub cascade_style: Arc<ComputedValues>,
85 }
86
87 impl KeyframesAnimationState {
88 /// Performs a tick in the animation state, i.e., increments the counter of
89 /// the current iteration count, updates times and then toggles the
90 /// direction if appropriate.
91 ///
92 /// Returns true if the animation should keep running.
tick(&mut self) -> bool93 pub fn tick(&mut self) -> bool {
94 debug!("KeyframesAnimationState::tick");
95 debug_assert!(!self.expired);
96
97 self.started_at += self.duration + self.delay;
98 match self.running_state {
99 // If it's paused, don't update direction or iteration count.
100 KeyframesRunningState::Paused(_) => return true,
101 KeyframesRunningState::Running => {},
102 }
103
104 if let KeyframesIterationState::Finite(ref mut current, ref max) = self.iteration_state {
105 *current += 1.0;
106 // NB: This prevent us from updating the direction, which might be
107 // needed for the correct handling of animation-fill-mode.
108 if *current >= *max {
109 return false;
110 }
111 }
112
113 // Update the next iteration direction if applicable.
114 match self.direction {
115 AnimationDirection::Alternate | AnimationDirection::AlternateReverse => {
116 self.current_direction = match self.current_direction {
117 AnimationDirection::Normal => AnimationDirection::Reverse,
118 AnimationDirection::Reverse => AnimationDirection::Normal,
119 _ => unreachable!(),
120 };
121 },
122 _ => {},
123 }
124
125 true
126 }
127
128 /// Updates the appropiate state from other animation.
129 ///
130 /// This happens when an animation is re-submitted to layout, presumably
131 /// because of an state change.
132 ///
133 /// There are some bits of state we can't just replace, over all taking in
134 /// account times, so here's that logic.
update_from_other(&mut self, other: &Self, timer: &Timer)135 pub fn update_from_other(&mut self, other: &Self, timer: &Timer) {
136 use self::KeyframesRunningState::*;
137
138 debug!(
139 "KeyframesAnimationState::update_from_other({:?}, {:?})",
140 self, other
141 );
142
143 // NB: We shall not touch the started_at field, since we don't want to
144 // restart the animation.
145 let old_started_at = self.started_at;
146 let old_duration = self.duration;
147 let old_direction = self.current_direction;
148 let old_running_state = self.running_state.clone();
149 let old_iteration_state = self.iteration_state.clone();
150 *self = other.clone();
151
152 let mut new_started_at = old_started_at;
153
154 // If we're unpausing the animation, fake the start time so we seem to
155 // restore it.
156 //
157 // If the animation keeps paused, keep the old value.
158 //
159 // If we're pausing the animation, compute the progress value.
160 match (&mut self.running_state, old_running_state) {
161 (&mut Running, Paused(progress)) => {
162 new_started_at = timer.seconds() - (self.duration * progress)
163 },
164 (&mut Paused(ref mut new), Paused(old)) => *new = old,
165 (&mut Paused(ref mut progress), Running) => {
166 *progress = (timer.seconds() - old_started_at) / old_duration
167 },
168 _ => {},
169 }
170
171 // Don't update the iteration count, just the iteration limit.
172 // TODO: see how changing the limit affects rendering in other browsers.
173 // We might need to keep the iteration count even when it's infinite.
174 match (&mut self.iteration_state, old_iteration_state) {
175 (
176 &mut KeyframesIterationState::Finite(ref mut iters, _),
177 KeyframesIterationState::Finite(old_iters, _),
178 ) => *iters = old_iters,
179 _ => {},
180 }
181
182 self.current_direction = old_direction;
183 self.started_at = new_started_at;
184 }
185 }
186
187 impl fmt::Debug for KeyframesAnimationState {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result188 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
189 f.debug_struct("KeyframesAnimationState")
190 .field("started_at", &self.started_at)
191 .field("duration", &self.duration)
192 .field("delay", &self.delay)
193 .field("iteration_state", &self.iteration_state)
194 .field("running_state", &self.running_state)
195 .field("direction", &self.direction)
196 .field("current_direction", &self.current_direction)
197 .field("expired", &self.expired)
198 .field("cascade_style", &())
199 .finish()
200 }
201 }
202
203 /// State relating to an animation.
204 #[derive(Clone, Debug)]
205 pub enum Animation {
206 /// A transition is just a single frame triggered at a time, with a reflow.
207 ///
208 /// the f64 field is the start time as returned by `time::precise_time_s()`.
209 Transition(OpaqueNode, f64, PropertyAnimation),
210
211 /// A keyframes animation is identified by a name, and can have a
212 /// node-dependent state (i.e. iteration count, etc.).
213 ///
214 /// TODO(emilio): The animation object could be refcounted.
215 Keyframes(
216 OpaqueNode,
217 KeyframesAnimation,
218 Atom,
219 KeyframesAnimationState,
220 ),
221 }
222
223 impl Animation {
224 /// Whether this animation is expired.
225 #[inline]
is_expired(&self) -> bool226 pub fn is_expired(&self) -> bool {
227 match *self {
228 Animation::Transition(..) => false,
229 Animation::Keyframes(_, _, _, ref state) => state.expired,
230 }
231 }
232
233 /// The opaque node that owns the animation.
234 #[inline]
node(&self) -> &OpaqueNode235 pub fn node(&self) -> &OpaqueNode {
236 match *self {
237 Animation::Transition(ref node, _, _) => node,
238 Animation::Keyframes(ref node, _, _, _) => node,
239 }
240 }
241
242 /// Whether this animation is a transition.
243 #[inline]
is_transition(&self) -> bool244 pub fn is_transition(&self) -> bool {
245 match *self {
246 Animation::Transition(..) => true,
247 Animation::Keyframes(..) => false,
248 }
249 }
250
251 /// Whether this animation has the same end value as another one.
252 #[inline]
is_transition_with_same_end_value(&self, other_animation: &PropertyAnimation) -> bool253 pub fn is_transition_with_same_end_value(&self, other_animation: &PropertyAnimation) -> bool {
254 match *self {
255 Animation::Transition(_, _, ref animation) => {
256 animation.has_the_same_end_value_as(other_animation)
257 },
258 Animation::Keyframes(..) => false,
259 }
260 }
261 }
262
263 /// Represents an animation for a given property.
264 #[derive(Clone, Debug)]
265 pub struct PropertyAnimation {
266 /// An `AnimatedProperty` that this `PropertyAnimation` corresponds to.
267 property: AnimatedProperty,
268
269 /// The timing function of this `PropertyAnimation`.
270 timing_function: TimingFunction,
271
272 /// The duration of this `PropertyAnimation` in seconds.
273 pub duration: f64,
274 }
275
276 impl PropertyAnimation {
277 /// Returns the given property longhand id.
property_id(&self) -> LonghandId278 pub fn property_id(&self) -> LonghandId {
279 self.property.id()
280 }
281
282 /// Returns the given property name.
property_name(&self) -> &'static str283 pub fn property_name(&self) -> &'static str {
284 self.property.name()
285 }
286
from_longhand( longhand: LonghandId, timing_function: TimingFunction, duration: Time, old_style: &ComputedValues, new_style: &ComputedValues, ) -> Option<PropertyAnimation>287 fn from_longhand(
288 longhand: LonghandId,
289 timing_function: TimingFunction,
290 duration: Time,
291 old_style: &ComputedValues,
292 new_style: &ComputedValues,
293 ) -> Option<PropertyAnimation> {
294 let animated_property = AnimatedProperty::from_longhand(longhand, old_style, new_style)?;
295
296 let property_animation = PropertyAnimation {
297 property: animated_property,
298 timing_function,
299 duration: duration.seconds() as f64,
300 };
301
302 if property_animation.does_animate() {
303 Some(property_animation)
304 } else {
305 None
306 }
307 }
308
309 /// Update the given animation at a given point of progress.
update(&self, style: &mut ComputedValues, time: f64)310 pub fn update(&self, style: &mut ComputedValues, time: f64) {
311 let epsilon = 1. / (200. * self.duration);
312 let progress = match self.timing_function {
313 GenericTimingFunction::CubicBezier { x1, y1, x2, y2 } => {
314 Bezier::new(x1, y1, x2, y2).solve(time, epsilon)
315 },
316 GenericTimingFunction::Steps(steps, pos) => {
317 let mut current_step = (time * (steps as f64)).floor() as i32;
318
319 if pos == StepPosition::Start ||
320 pos == StepPosition::JumpStart ||
321 pos == StepPosition::JumpBoth
322 {
323 current_step = current_step + 1;
324 }
325
326 // FIXME: We should update current_step according to the "before flag".
327 // In order to get the before flag, we have to know the current animation phase
328 // and whether the iteration is reversed. For now, we skip this calculation.
329 // (i.e. Treat before_flag is unset,)
330 // https://drafts.csswg.org/css-easing/#step-timing-function-algo
331
332 if time >= 0.0 && current_step < 0 {
333 current_step = 0;
334 }
335
336 let jumps = match pos {
337 StepPosition::JumpBoth => steps + 1,
338 StepPosition::JumpNone => steps - 1,
339 StepPosition::JumpStart |
340 StepPosition::JumpEnd |
341 StepPosition::Start |
342 StepPosition::End => steps,
343 };
344
345 if time <= 1.0 && current_step > jumps {
346 current_step = jumps;
347 }
348
349 (current_step as f64) / (jumps as f64)
350 },
351 GenericTimingFunction::Keyword(keyword) => {
352 let (x1, x2, y1, y2) = keyword.to_bezier();
353 Bezier::new(x1, x2, y1, y2).solve(time, epsilon)
354 },
355 };
356
357 self.property.update(style, progress);
358 }
359
360 #[inline]
does_animate(&self) -> bool361 fn does_animate(&self) -> bool {
362 self.property.does_animate() && self.duration != 0.0
363 }
364
365 /// Whether this animation has the same end value as another one.
366 #[inline]
has_the_same_end_value_as(&self, other: &Self) -> bool367 pub fn has_the_same_end_value_as(&self, other: &Self) -> bool {
368 self.property.has_the_same_end_value_as(&other.property)
369 }
370 }
371
372 /// Start any new transitions for this node and ensure that any existing transitions
373 /// that are cancelled are marked as cancelled in the SharedStyleContext. This is
374 /// at the end of calculating style for a single node.
375 #[cfg(feature = "servo")]
update_transitions( context: &SharedStyleContext, new_animations_sender: &Sender<Animation>, opaque_node: OpaqueNode, old_style: &ComputedValues, new_style: &mut Arc<ComputedValues>, expired_transitions: &[PropertyAnimation], )376 pub fn update_transitions(
377 context: &SharedStyleContext,
378 new_animations_sender: &Sender<Animation>,
379 opaque_node: OpaqueNode,
380 old_style: &ComputedValues,
381 new_style: &mut Arc<ComputedValues>,
382 expired_transitions: &[PropertyAnimation],
383 ) {
384 let mut all_running_animations = context.running_animations.write();
385 let previously_running_animations = all_running_animations
386 .remove(&opaque_node)
387 .unwrap_or_else(Vec::new);
388
389 let properties_that_transition = start_transitions_if_applicable(
390 context,
391 new_animations_sender,
392 opaque_node,
393 old_style,
394 new_style,
395 expired_transitions,
396 &previously_running_animations,
397 );
398
399 let mut all_cancelled_animations = context.cancelled_animations.write();
400 let mut cancelled_animations = all_cancelled_animations
401 .remove(&opaque_node)
402 .unwrap_or_else(Vec::new);
403 let mut running_animations = vec![];
404
405 // For every animation that was running before this style change, we cancel it
406 // if the property no longer transitions.
407 for running_animation in previously_running_animations.into_iter() {
408 if let Animation::Transition(_, _, ref property_animation) = running_animation {
409 if !properties_that_transition.contains(property_animation.property_id()) {
410 cancelled_animations.push(running_animation);
411 continue;
412 }
413 }
414 running_animations.push(running_animation);
415 }
416
417 if !cancelled_animations.is_empty() {
418 all_cancelled_animations.insert(opaque_node, cancelled_animations);
419 }
420 if !running_animations.is_empty() {
421 all_running_animations.insert(opaque_node, running_animations);
422 }
423 }
424
425 /// Kick off any new transitions for this node and return all of the properties that are
426 /// transitioning. This is at the end of calculating style for a single node.
427 #[cfg(feature = "servo")]
start_transitions_if_applicable( context: &SharedStyleContext, new_animations_sender: &Sender<Animation>, opaque_node: OpaqueNode, old_style: &ComputedValues, new_style: &mut Arc<ComputedValues>, expired_transitions: &[PropertyAnimation], running_animations: &[Animation], ) -> LonghandIdSet428 pub fn start_transitions_if_applicable(
429 context: &SharedStyleContext,
430 new_animations_sender: &Sender<Animation>,
431 opaque_node: OpaqueNode,
432 old_style: &ComputedValues,
433 new_style: &mut Arc<ComputedValues>,
434 expired_transitions: &[PropertyAnimation],
435 running_animations: &[Animation],
436 ) -> LonghandIdSet {
437 use crate::properties::animated_properties::TransitionPropertyIteration;
438
439 // If the style of this element is display:none, then we don't start any transitions
440 // and we cancel any currently running transitions by returning an empty LonghandIdSet.
441 if new_style.get_box().clone_display().is_none() {
442 return LonghandIdSet::new();
443 }
444
445 let mut properties_that_transition = LonghandIdSet::new();
446 let transitions: Vec<TransitionPropertyIteration> = new_style.transition_properties().collect();
447 for transition in &transitions {
448 if properties_that_transition.contains(transition.longhand_id) {
449 continue;
450 } else {
451 properties_that_transition.insert(transition.longhand_id);
452 }
453
454 let property_animation = match PropertyAnimation::from_longhand(
455 transition.longhand_id,
456 new_style
457 .get_box()
458 .transition_timing_function_mod(transition.index),
459 new_style
460 .get_box()
461 .transition_duration_mod(transition.index),
462 old_style,
463 Arc::make_mut(new_style),
464 ) {
465 Some(property_animation) => property_animation,
466 None => continue,
467 };
468
469 // Set the property to the initial value.
470 //
471 // NB: get_mut is guaranteed to succeed since we called make_mut()
472 // above.
473 property_animation.update(Arc::get_mut(new_style).unwrap(), 0.0);
474
475 // Per [1], don't trigger a new transition if the end state for that
476 // transition is the same as that of a transition that's expired.
477 // [1]: https://drafts.csswg.org/css-transitions/#starting
478 debug!("checking {:?} for matching end value", expired_transitions);
479 if expired_transitions
480 .iter()
481 .any(|animation| animation.has_the_same_end_value_as(&property_animation))
482 {
483 debug!(
484 "Not initiating transition for {}, expired transition \
485 found with the same end value",
486 property_animation.property_name()
487 );
488 continue;
489 }
490
491 if running_animations
492 .iter()
493 .any(|animation| animation.is_transition_with_same_end_value(&property_animation))
494 {
495 debug!(
496 "Not initiating transition for {}, running transition \
497 found with the same end value",
498 property_animation.property_name()
499 );
500 continue;
501 }
502
503 // Kick off the animation.
504 debug!("Kicking off transition of {:?}", property_animation);
505 let box_style = new_style.get_box();
506 let now = context.timer.seconds();
507 let start_time = now + (box_style.transition_delay_mod(transition.index).seconds() as f64);
508 new_animations_sender
509 .send(Animation::Transition(
510 opaque_node,
511 start_time,
512 property_animation,
513 ))
514 .unwrap();
515 }
516
517 properties_that_transition
518 }
519
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,520 fn compute_style_for_animation_step<E>(
521 context: &SharedStyleContext,
522 step: &KeyframesStep,
523 previous_style: &ComputedValues,
524 style_from_cascade: &Arc<ComputedValues>,
525 font_metrics_provider: &dyn FontMetricsProvider,
526 ) -> Arc<ComputedValues>
527 where
528 E: TElement,
529 {
530 match step.value {
531 KeyframesStepValue::ComputedValues => style_from_cascade.clone(),
532 KeyframesStepValue::Declarations {
533 block: ref declarations,
534 } => {
535 let guard = declarations.read_with(context.guards.author);
536
537 // This currently ignores visited styles, which seems acceptable,
538 // as existing browsers don't appear to animate visited styles.
539 let computed = properties::apply_declarations::<E, _>(
540 context.stylist.device(),
541 /* pseudo = */ None,
542 previous_style.rules(),
543 &context.guards,
544 // It's possible to have !important properties in keyframes
545 // so we have to filter them out.
546 // See the spec issue https://github.com/w3c/csswg-drafts/issues/1824
547 // Also we filter our non-animatable properties.
548 guard
549 .normal_declaration_iter()
550 .filter(|declaration| declaration.is_animatable())
551 .map(|decl| (decl, Origin::Author)),
552 Some(previous_style),
553 Some(previous_style),
554 Some(previous_style),
555 font_metrics_provider,
556 CascadeMode::Unvisited {
557 visited_rules: None,
558 },
559 context.quirks_mode(),
560 /* rule_cache = */ None,
561 &mut Default::default(),
562 /* element = */ None,
563 );
564 computed
565 },
566 }
567 }
568
569 /// Triggers animations for a given node looking at the animation property
570 /// values.
maybe_start_animations<E>( element: E, context: &SharedStyleContext, new_animations_sender: &Sender<Animation>, node: OpaqueNode, new_style: &Arc<ComputedValues>, ) -> bool where E: TElement,571 pub fn maybe_start_animations<E>(
572 element: E,
573 context: &SharedStyleContext,
574 new_animations_sender: &Sender<Animation>,
575 node: OpaqueNode,
576 new_style: &Arc<ComputedValues>,
577 ) -> bool
578 where
579 E: TElement,
580 {
581 let mut had_animations = false;
582
583 let box_style = new_style.get_box();
584 for (i, name) in box_style.animation_name_iter().enumerate() {
585 let name = match name.as_atom() {
586 Some(atom) => atom,
587 None => continue,
588 };
589
590 debug!("maybe_start_animations: name={}", name);
591 let total_duration = box_style.animation_duration_mod(i).seconds();
592 if total_duration == 0. {
593 continue;
594 }
595
596 let anim = match context.stylist.get_animation(name, element) {
597 Some(animation) => animation,
598 None => continue,
599 };
600
601 debug!("maybe_start_animations: animation {} found", name);
602
603 // If this animation doesn't have any keyframe, we can just continue
604 // without submitting it to the compositor, since both the first and
605 // the second keyframes would be synthetised from the computed
606 // values.
607 if anim.steps.is_empty() {
608 continue;
609 }
610
611 let delay = box_style.animation_delay_mod(i).seconds();
612 let now = context.timer.seconds();
613 let animation_start = now + delay as f64;
614 let duration = box_style.animation_duration_mod(i).seconds();
615 let iteration_state = match box_style.animation_iteration_count_mod(i) {
616 AnimationIterationCount::Infinite => KeyframesIterationState::Infinite,
617 AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n),
618 };
619
620 let animation_direction = box_style.animation_direction_mod(i);
621
622 let initial_direction = match animation_direction {
623 AnimationDirection::Normal | AnimationDirection::Alternate => {
624 AnimationDirection::Normal
625 },
626 AnimationDirection::Reverse | AnimationDirection::AlternateReverse => {
627 AnimationDirection::Reverse
628 },
629 };
630
631 let running_state = match box_style.animation_play_state_mod(i) {
632 AnimationPlayState::Paused => KeyframesRunningState::Paused(0.),
633 AnimationPlayState::Running => KeyframesRunningState::Running,
634 };
635
636 new_animations_sender
637 .send(Animation::Keyframes(
638 node,
639 anim.clone(),
640 name.clone(),
641 KeyframesAnimationState {
642 started_at: animation_start,
643 duration: duration as f64,
644 delay: delay as f64,
645 iteration_state,
646 running_state,
647 direction: animation_direction,
648 current_direction: initial_direction,
649 expired: false,
650 cascade_style: new_style.clone(),
651 },
652 ))
653 .unwrap();
654 had_animations = true;
655 }
656
657 had_animations
658 }
659
660 /// Returns the kind of animation update that happened.
661 pub enum AnimationUpdate {
662 /// The style was successfully updated, the animation is still running.
663 Regular,
664 /// A style change canceled this animation.
665 AnimationCanceled,
666 }
667
668 /// Updates a single animation and associated style based on the current time.
669 ///
670 /// FIXME(emilio): This doesn't handle any kind of dynamic change to the
671 /// animation or transition properties in any reasonable way.
672 ///
673 /// This should probably be split in two, one from updating animations and
674 /// transitions in response to a style change (that is,
675 /// consider_starting_transitions + maybe_start_animations, but handling
676 /// canceled animations, duration changes, etc, there instead of here), and this
677 /// function should be only about the style update in response of a transition.
update_style_for_animation<E>( context: &SharedStyleContext, animation: &Animation, style: &mut Arc<ComputedValues>, font_metrics_provider: &dyn FontMetricsProvider, ) -> AnimationUpdate where E: TElement,678 pub fn update_style_for_animation<E>(
679 context: &SharedStyleContext,
680 animation: &Animation,
681 style: &mut Arc<ComputedValues>,
682 font_metrics_provider: &dyn FontMetricsProvider,
683 ) -> AnimationUpdate
684 where
685 E: TElement,
686 {
687 debug!("update_style_for_animation: {:?}", animation);
688 debug_assert!(!animation.is_expired());
689
690 match *animation {
691 Animation::Transition(_, start_time, ref property_animation) => {
692 let now = context.timer.seconds();
693 let progress = (now - start_time) / (property_animation.duration);
694 let progress = progress.min(1.0);
695 if progress >= 0.0 {
696 property_animation.update(Arc::make_mut(style), progress);
697 }
698 AnimationUpdate::Regular
699 },
700 Animation::Keyframes(_, ref animation, ref name, ref state) => {
701 let duration = state.duration;
702 let started_at = state.started_at;
703
704 let now = match state.running_state {
705 KeyframesRunningState::Running => context.timer.seconds(),
706 KeyframesRunningState::Paused(progress) => started_at + duration * progress,
707 };
708
709 debug_assert!(!animation.steps.is_empty());
710
711 let maybe_index = style
712 .get_box()
713 .animation_name_iter()
714 .position(|animation_name| Some(name) == animation_name.as_atom());
715
716 let index = match maybe_index {
717 Some(index) => index,
718 None => return AnimationUpdate::AnimationCanceled,
719 };
720
721 let total_duration = style.get_box().animation_duration_mod(index).seconds() as f64;
722 if total_duration == 0. {
723 return AnimationUpdate::AnimationCanceled;
724 }
725
726 let mut total_progress = (now - started_at) / total_duration;
727 if total_progress < 0. {
728 warn!("Negative progress found for animation {:?}", name);
729 return AnimationUpdate::Regular;
730 }
731 if total_progress > 1. {
732 total_progress = 1.;
733 }
734
735 // Get the target and the last keyframe position.
736 let last_keyframe_position;
737 let target_keyframe_position;
738 match state.current_direction {
739 AnimationDirection::Normal => {
740 target_keyframe_position = animation
741 .steps
742 .iter()
743 .position(|step| total_progress as f32 <= step.start_percentage.0);
744
745 last_keyframe_position = target_keyframe_position
746 .and_then(|pos| if pos != 0 { Some(pos - 1) } else { None })
747 .unwrap_or(0);
748 },
749 AnimationDirection::Reverse => {
750 target_keyframe_position = animation
751 .steps
752 .iter()
753 .rev()
754 .position(|step| total_progress as f32 <= 1. - step.start_percentage.0)
755 .map(|pos| animation.steps.len() - pos - 1);
756
757 last_keyframe_position = target_keyframe_position
758 .and_then(|pos| {
759 if pos != animation.steps.len() - 1 {
760 Some(pos + 1)
761 } else {
762 None
763 }
764 })
765 .unwrap_or(animation.steps.len() - 1);
766 },
767 _ => unreachable!(),
768 }
769
770 debug!(
771 "update_style_for_animation: keyframe from {:?} to {:?}",
772 last_keyframe_position, target_keyframe_position
773 );
774
775 let target_keyframe = match target_keyframe_position {
776 Some(target) => &animation.steps[target],
777 None => return AnimationUpdate::Regular,
778 };
779
780 let last_keyframe = &animation.steps[last_keyframe_position];
781
782 let relative_timespan =
783 (target_keyframe.start_percentage.0 - last_keyframe.start_percentage.0).abs();
784 let relative_duration = relative_timespan as f64 * duration;
785 let last_keyframe_ended_at = match state.current_direction {
786 AnimationDirection::Normal => {
787 state.started_at + (total_duration * last_keyframe.start_percentage.0 as f64)
788 },
789 AnimationDirection::Reverse => {
790 state.started_at +
791 (total_duration * (1. - last_keyframe.start_percentage.0 as f64))
792 },
793 _ => unreachable!(),
794 };
795 let relative_progress = (now - last_keyframe_ended_at) / relative_duration;
796
797 // TODO: How could we optimise it? Is it such a big deal?
798 let from_style = compute_style_for_animation_step::<E>(
799 context,
800 last_keyframe,
801 &**style,
802 &state.cascade_style,
803 font_metrics_provider,
804 );
805
806 // NB: The spec says that the timing function can be overwritten
807 // from the keyframe style.
808 let mut timing_function = style.get_box().animation_timing_function_mod(index);
809 if last_keyframe.declared_timing_function {
810 // NB: animation_timing_function can never be empty, always has
811 // at least the default value (`ease`).
812 timing_function = from_style.get_box().animation_timing_function_at(0);
813 }
814
815 let target_style = compute_style_for_animation_step::<E>(
816 context,
817 target_keyframe,
818 &from_style,
819 &state.cascade_style,
820 font_metrics_provider,
821 );
822
823 let mut new_style = (*style).clone();
824
825 for property in animation.properties_changed.iter() {
826 debug!(
827 "update_style_for_animation: scanning prop {:?} for animation \"{}\"",
828 property, name
829 );
830 let animation = PropertyAnimation::from_longhand(
831 property,
832 timing_function,
833 Time::from_seconds(relative_duration as f32),
834 &from_style,
835 &target_style,
836 );
837
838 match animation {
839 Some(property_animation) => {
840 debug!(
841 "update_style_for_animation: got property animation for prop {:?}",
842 property
843 );
844 debug!("update_style_for_animation: {:?}", property_animation);
845 property_animation.update(Arc::make_mut(&mut new_style), relative_progress);
846 },
847 None => {
848 debug!(
849 "update_style_for_animation: property animation {:?} not animating",
850 property
851 );
852 },
853 }
854 }
855
856 debug!(
857 "update_style_for_animation: got style change in animation \"{}\"",
858 name
859 );
860 *style = new_style;
861 AnimationUpdate::Regular
862 },
863 }
864 }
865