Skip to content

Commit

Permalink
Implement animationiteration event
Browse files Browse the repository at this point in the history
This event is triggered when an animation iterates. This change also
moves iteration out of style calculation to an "update animations" which
is the next part of having animation event handling match the HTML spec.
  • Loading branch information
mrobinson committed May 21, 2020
1 parent f02aba1 commit 873cdd1
Show file tree
Hide file tree
Showing 12 changed files with 93 additions and 80 deletions.
31 changes: 29 additions & 2 deletions components/script/animations.rs
Expand Up @@ -35,6 +35,30 @@ impl Animations {
}
}

pub(crate) fn update_for_new_timeline_value(
&mut self,
window: &Window,
now: f64,
) -> AnimationsUpdate {
let mut update = AnimationsUpdate::new(window.pipeline_id());
let mut sets = self.sets.write();

for set in sets.values_mut() {
// When necessary, iterate our running animations to the next iteration.
for animation in set.animations.iter_mut() {
if animation.iterate_if_necessary(now) {
update.add_event(
animation.node,
animation.name.to_string(),
TransitionOrAnimationEventType::AnimationIteration,
animation.active_duration(),
);
}
}
}
update
}

/// Processes any new animations that were discovered after reflow. Collect messages
/// that trigger events for any animations that changed state.
/// TODO(mrobinson): The specification dictates that this should happen before reflow.
Expand Down Expand Up @@ -255,6 +279,9 @@ pub enum TransitionOrAnimationEventType {
TransitionCancel,
/// "The animationend event occurs when the animation finishes"
AnimationEnd,
/// "The animationiteration event occurs at the end of each iteration of an
/// animation, except when an animationend event would fire at the same time."
AnimationIteration,
}

impl TransitionOrAnimationEventType {
Expand All @@ -263,15 +290,15 @@ impl TransitionOrAnimationEventType {
pub fn finalizes_transition_or_animation(&self) -> bool {
match *self {
Self::TransitionEnd | Self::TransitionCancel | Self::AnimationEnd => true,
Self::TransitionRun => false,
Self::TransitionRun | Self::AnimationIteration => false,
}
}

/// Whether or not this event is a transition-related event.
pub fn is_transition_event(&self) -> bool {
match *self {
Self::TransitionRun | Self::TransitionEnd | Self::TransitionCancel => true,
Self::AnimationEnd => false,
Self::AnimationEnd | Self::AnimationIteration => false,
}
}
}
Expand Down
22 changes: 18 additions & 4 deletions components/script/dom/document.rs
Expand Up @@ -3750,12 +3750,26 @@ impl Document {
.collect()
}

pub(crate) fn advance_animation_timeline_for_testing(&self, delta: f64) {
pub(crate) fn advance_animation_timeline_for_testing(&self, delta: f64) -> AnimationsUpdate {
self.animation_timeline.borrow_mut().advance_specific(delta);
let current_timeline_value = self.current_animation_timeline_value();
self.animations
.borrow_mut()
.update_for_new_timeline_value(&self.window, current_timeline_value)
}

pub(crate) fn update_animation_timeline(&self) {
self.animation_timeline.borrow_mut().update();
pub(crate) fn update_animation_timeline(&self) -> AnimationsUpdate {
// Only update the time if it isn't being managed by a test.
if !pref!(layout.animations.test.enabled) {
self.animation_timeline.borrow_mut().update();
}

// We still want to update the animations, because our timeline
// value might have been advanced previously via the TestBinding.
let current_timeline_value = self.current_animation_timeline_value();
self.animations
.borrow_mut()
.update_for_new_timeline_value(&self.window, current_timeline_value)
}

pub(crate) fn current_animation_timeline_value(&self) -> f64 {
Expand All @@ -3766,7 +3780,7 @@ impl Document {
self.animations.borrow()
}

pub(crate) fn update_animations(&self) -> AnimationsUpdate {
pub(crate) fn update_animations_post_reflow(&self) -> AnimationsUpdate {
self.animations
.borrow_mut()
.do_post_reflow_update(&self.window, self.current_animation_timeline_value())
Expand Down
1 change: 1 addition & 0 deletions components/script/dom/macros.rs
Expand Up @@ -443,6 +443,7 @@ macro_rules! global_event_handlers(
(NoOnload) => (
event_handler!(abort, GetOnabort, SetOnabort);
event_handler!(animationend, GetOnanimationend, SetOnanimationend);
event_handler!(animationiteration, GetOnanimationiteration, SetOnanimationiteration);
event_handler!(cancel, GetOncancel, SetOncancel);
event_handler!(canplay, GetOncanplay, SetOncanplay);
event_handler!(canplaythrough, GetOncanplaythrough, SetOncanplaythrough);
Expand Down
1 change: 1 addition & 0 deletions components/script/dom/webidls/EventHandler.webidl
Expand Up @@ -93,6 +93,7 @@ interface mixin GlobalEventHandlers {
// https://drafts.csswg.org/css-animations/#interface-globaleventhandlers-idl
partial interface mixin GlobalEventHandlers {
attribute EventHandler onanimationend;
attribute EventHandler onanimationiteration;
};

// https://drafts.csswg.org/css-transitions/#interface-globaleventhandlers-idl
Expand Down
13 changes: 9 additions & 4 deletions components/script/dom/window.rs
Expand Up @@ -1578,10 +1578,15 @@ impl Window {

/// Prepares to tick animations and then does a reflow which also advances the
/// layout animation clock.
pub fn advance_animation_clock(&self, delta: i32) {
#[allow(unsafe_code)]
pub fn advance_animation_clock(&self, delta_ms: i32) {
let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
self.Document()
.advance_animation_timeline_for_testing(delta as f64 / 1000.);
let update = self
.Document()
.advance_animation_timeline_for_testing(delta_ms as f64 / 1000.);
unsafe {
ScriptThread::process_animations_update(update);
}
ScriptThread::handle_tick_all_animations_for_testing(pipeline_id);
}

Expand Down Expand Up @@ -1747,7 +1752,7 @@ impl Window {
}
}

let update = document.update_animations();
let update = document.update_animations_post_reflow();
unsafe {
ScriptThread::process_animations_update(update);
}
Expand Down
14 changes: 6 additions & 8 deletions components/script/script_thread.rs
Expand Up @@ -151,7 +151,6 @@ use script_traits::{
};
use servo_atoms::Atom;
use servo_config::opts;
use servo_config::pref;
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
use std::borrow::Cow;
use std::cell::Cell;
Expand Down Expand Up @@ -1650,19 +1649,17 @@ impl ScriptThread {
// TODO(mrobinson): This should also update the current animations to conform to
// the HTML specification.
fn update_animations_and_send_events(&self) {
for (_, document) in self.documents.borrow().iter() {
// Only update the time if it isn't being managed by a test.
if !pref!(layout.animations.test.enabled) {
document.update_animation_timeline();
}
}

// We remove the events because handling these events might trigger
// a reflow which might want to add more events to the queue.
let events = self.animation_events.replace(Vec::new());
for event in events.into_iter() {
self.handle_transition_or_animation_event(&event);
}

for (_, document) in self.documents.borrow().iter() {
let update = document.update_animation_timeline();
unsafe { ScriptThread::process_animations_update(update) };
}
}

fn categorize_msg(&self, msg: &MixedMessage) -> ScriptThreadEventCategory {
Expand Down Expand Up @@ -3021,6 +3018,7 @@ impl ScriptThread {

let event_atom = match event.event_type {
TransitionOrAnimationEventType::AnimationEnd => atom!("animationend"),
TransitionOrAnimationEventType::AnimationIteration => atom!("animationiteration"),
TransitionOrAnimationEventType::TransitionCancel => atom!("transitioncancel"),
TransitionOrAnimationEventType::TransitionEnd => atom!("transitionend"),
TransitionOrAnimationEventType::TransitionRun => atom!("transitionrun"),
Expand Down
53 changes: 23 additions & 30 deletions components/style/animation.rs
Expand Up @@ -156,12 +156,10 @@ pub enum AnimationState {
/// have to keep track the current iteration and the max iteration count.
#[derive(Clone, Debug, MallocSizeOf)]
pub enum KeyframesIterationState {
/// Infinite iterations, so no need to track a state.
Infinite,
/// Infinite iterations with the current iteration count.
Infinite(f64),
/// Current and max iterations.
/// TODO: Everything else in this file uses f64, so perhaps this should
/// be as well.
Finite(f32, f32),
Finite(f64, f64),
}

/// A CSS Animation
Expand Down Expand Up @@ -227,26 +225,26 @@ impl Animation {

/// Given the current time, advances this animation to the next iteration,
/// updates times, and then toggles the direction if appropriate. Otherwise
/// does nothing.
pub fn iterate_if_necessary(&mut self, time: f64) {
/// 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;
return false;
}

// Only iterate animations that are currently running.
if self.state != AnimationState::Running {
return;
return false;
}

if let KeyframesIterationState::Finite(ref mut current, max) = self.iteration_state {
// If we are already on the final iteration, just exit now.
// NB: This prevent us from updating the direction, which might be
// needed for the correct handling of animation-fill-mode.
if (max - *current) <= 1.0 {
return;
// 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;
}

*current += 1.0;
}

// Update the next iteration direction if applicable.
Expand All @@ -262,6 +260,8 @@ impl Animation {
},
_ => {},
}

true
}

fn iteration_over(&self, time: f64) -> bool {
Expand All @@ -285,8 +285,8 @@ impl Animation {
// 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) if (max - current) <= 1.0 => true,
KeyframesIterationState::Finite(..) | KeyframesIterationState::Infinite => false,
KeyframesIterationState::Finite(current, max) => max == current,
KeyframesIterationState::Infinite(..) => false,
};
}

Expand Down Expand Up @@ -347,13 +347,11 @@ impl Animation {
}

/// Calculate the active-duration of this animation according to
/// https://drafts.csswg.org/css-animations/#active-duration. active-duration
/// is not really meaningful for infinite animations so we just return 0
/// here in that case.
/// https://drafts.csswg.org/css-animations/#active-duration.
pub fn active_duration(&self) -> f64 {
match self.iteration_state {
KeyframesIterationState::Finite(_, max) => self.duration * (max as f64),
KeyframesIterationState::Infinite => 0.,
KeyframesIterationState::Finite(current, _) |
KeyframesIterationState::Infinite(current) => self.duration * current,
}
}

Expand Down Expand Up @@ -784,11 +782,6 @@ impl ElementAnimationSet {
}

maybe_start_animations(element, &context, &new_style, self);

// When necessary, iterate our running animations to the next iteration.
for animation in self.animations.iter_mut() {
animation.iterate_if_necessary(context.current_time_for_animations);
}
}

/// Update our transitions given a new style, canceling or starting new animations
Expand Down Expand Up @@ -1039,8 +1032,8 @@ pub fn maybe_start_animations<E>(
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,
AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n),
AnimationIterationCount::Infinite => KeyframesIterationState::Infinite(0.0),
AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n.into()),
};

let animation_direction = box_style.animation_direction_mod(i);
Expand Down
@@ -1,8 +1,5 @@
[animationevent-types.html]
expected: TIMEOUT
[animationiteration event is instanceof AnimationEvent]
expected: TIMEOUT

[animationstart event is instanceof AnimationEvent]
expected: TIMEOUT

@@ -1,7 +1,4 @@
[idlharness.html]
[HTMLElement interface: attribute onanimationiteration]
expected: FAIL

[HTMLElement interface: attribute onanimationstart]
expected: FAIL

Expand All @@ -20,18 +17,12 @@
[Window interface: attribute onanimationstart]
expected: FAIL

[Window interface: attribute onanimationiteration]
expected: FAIL

[Document interface: attribute onanimationcancel]
expected: FAIL

[CSSKeyframeRule interface: attribute style]
expected: FAIL

[Document interface: attribute onanimationiteration]
expected: FAIL

[HTMLElement interface: attribute onanimationcancel]
expected: FAIL

@@ -1,9 +1,6 @@
[animationevent-types.html]
bug: https://github.com/servo/servo/issues/21564
expected: TIMEOUT
[animationiteration event is instanceof AnimationEvent]
expected: TIMEOUT

[animationstart event is instanceof AnimationEvent]
expected: TIMEOUT

9 changes: 0 additions & 9 deletions tests/wpt/metadata/css/css-animations/idlharness.html.ini
@@ -1,16 +1,10 @@
[idlharness.html]
[Document interface: attribute onanimationiteration]
expected: FAIL

[CSSKeyframeRule interface: attribute style]
expected: FAIL

[Document interface: attribute onanimationstart]
expected: FAIL

[HTMLElement interface: attribute onanimationiteration]
expected: FAIL

[Window interface: attribute onanimationcancel]
expected: FAIL

Expand All @@ -29,9 +23,6 @@
[HTMLElement interface: attribute onanimationcancel]
expected: FAIL

[Window interface: attribute onanimationiteration]
expected: FAIL

[CSSKeyframeRule interface: keyframes.cssRules[0\] must inherit property "keyText" with the proper type]
expected: FAIL

0 comments on commit 873cdd1

Please sign in to comment.