Add support for remaining animation and transition events

Fixes #21564.
This commit is contained in:
Martin Robinson 2020-05-26 20:34:58 +02:00
parent 47642e0eee
commit 77aa3721c5
28 changed files with 493 additions and 68 deletions

View file

@ -2,6 +2,7 @@ DOMContentLoaded
abort abort
activate activate
addtrack addtrack
animationcancel
animationend animationend
animationiteration animationiteration
animationstart animationstart
@ -132,6 +133,7 @@ track
transitioncancel transitioncancel
transitionend transitionend
transitionrun transitionrun
transitionstart
unhandledrejection unhandledrejection
unload unload
url url

View file

@ -78,12 +78,15 @@ impl Animations {
let mut sets = self.sets.write(); let mut sets = self.sets.write();
for set in sets.values_mut() { for set in sets.values_mut() {
self.start_pending_animations(set, now, pipeline_id);
// When necessary, iterate our running animations to the next iteration. // When necessary, iterate our running animations to the next iteration.
for animation in set.animations.iter_mut() { for animation in set.animations.iter_mut() {
if animation.iterate_if_necessary(now) { if animation.iterate_if_necessary(now) {
self.add_animation_event( self.add_animation_event(
animation, animation,
TransitionOrAnimationEventType::AnimationIteration, TransitionOrAnimationEventType::AnimationIteration,
now,
pipeline_id, pipeline_id,
); );
} }
@ -97,7 +100,6 @@ impl Animations {
/// Processes any new animations that were discovered after reflow. Collect messages /// Processes any new animations that were discovered after reflow. Collect messages
/// that trigger events for any animations that changed state. /// that trigger events for any animations that changed state.
/// TODO(mrobinson): The specification dictates that this should happen before reflow.
pub(crate) fn do_post_reflow_update(&self, window: &Window, now: f64) { pub(crate) fn do_post_reflow_update(&self, window: &Window, now: f64) {
let pipeline_id = window.pipeline_id(); let pipeline_id = window.pipeline_id();
let mut sets = self.sets.write(); let mut sets = self.sets.write();
@ -140,6 +142,39 @@ impl Animations {
.sum() .sum()
} }
/// Walk through the list of pending animations and start all of the ones that
/// have left the delay phase.
fn start_pending_animations(
&self,
set: &mut ElementAnimationSet,
now: f64,
pipeline_id: PipelineId,
) {
for animation in set.animations.iter_mut() {
if animation.state == AnimationState::Pending && animation.started_at <= now {
animation.state = AnimationState::Running;
self.add_animation_event(
animation,
TransitionOrAnimationEventType::AnimationStart,
now,
pipeline_id,
);
}
}
for transition in set.transitions.iter_mut() {
if transition.state == AnimationState::Pending && transition.start_time <= now {
transition.state = AnimationState::Running;
self.add_transition_event(
transition,
TransitionOrAnimationEventType::TransitionStart,
now,
pipeline_id,
);
}
}
}
/// Walk through the list of running animations and remove all of the ones that /// Walk through the list of running animations and remove all of the ones that
/// have ended. /// have ended.
fn finish_running_animations( fn finish_running_animations(
@ -154,6 +189,7 @@ impl Animations {
self.add_animation_event( self.add_animation_event(
animation, animation,
TransitionOrAnimationEventType::AnimationEnd, TransitionOrAnimationEventType::AnimationEnd,
now,
pipeline_id, pipeline_id,
); );
} }
@ -192,7 +228,17 @@ impl Animations {
} }
} }
// TODO(mrobinson): We need to send animationcancel events. for animation in &set.animations {
if animation.state == AnimationState::Canceled {
self.add_animation_event(
animation,
TransitionOrAnimationEventType::AnimationCancel,
now,
pipeline_id,
);
}
}
set.clear_canceled_animations(); set.clear_canceled_animations();
} }
@ -263,14 +309,21 @@ impl Animations {
now: f64, now: f64,
pipeline_id: PipelineId, pipeline_id: PipelineId,
) { ) {
// Calculate the `elapsed-time` property of the event and take the absolute
// value to prevent -0 values.
let elapsed_time = match event_type { let elapsed_time = match event_type {
TransitionOrAnimationEventType::TransitionRun | TransitionOrAnimationEventType::TransitionRun |
TransitionOrAnimationEventType::TransitionStart => transition
.property_animation
.duration
.min((-transition.delay).max(0.)),
TransitionOrAnimationEventType::TransitionEnd => transition.property_animation.duration, TransitionOrAnimationEventType::TransitionEnd => transition.property_animation.duration,
TransitionOrAnimationEventType::TransitionCancel => { TransitionOrAnimationEventType::TransitionCancel => {
(now - transition.start_time).max(0.) (now - transition.start_time).max(0.)
}, },
_ => unreachable!(), _ => unreachable!(),
}; }
.abs();
self.pending_events self.pending_events
.borrow_mut() .borrow_mut()
@ -291,6 +344,7 @@ impl Animations {
&self, &self,
animation: &Animation, animation: &Animation,
event_type: TransitionOrAnimationEventType, event_type: TransitionOrAnimationEventType,
now: f64,
pipeline_id: PipelineId, pipeline_id: PipelineId,
) { ) {
let num_iterations = match animation.iteration_state { let num_iterations = match animation.iteration_state {
@ -298,11 +352,25 @@ impl Animations {
KeyframesIterationState::Infinite(current) => current, KeyframesIterationState::Infinite(current) => current,
}; };
let active_duration = match animation.iteration_state {
KeyframesIterationState::Finite(_, max) => max * animation.duration,
KeyframesIterationState::Infinite(_) => std::f64::MAX,
};
// Calculate the `elapsed-time` property of the event and take the absolute
// value to prevent -0 values.
let elapsed_time = match event_type { let elapsed_time = match event_type {
TransitionOrAnimationEventType::AnimationStart => {
(-animation.delay).max(0.).min(active_duration)
},
TransitionOrAnimationEventType::AnimationIteration | TransitionOrAnimationEventType::AnimationIteration |
TransitionOrAnimationEventType::AnimationEnd => num_iterations * animation.duration, TransitionOrAnimationEventType::AnimationEnd => num_iterations * animation.duration,
TransitionOrAnimationEventType::AnimationCancel => {
(num_iterations * animation.duration) + (now - animation.started_at).max(0.)
},
_ => unreachable!(), _ => unreachable!(),
}; }
.abs();
self.pending_events self.pending_events
.borrow_mut() .borrow_mut()
@ -333,10 +401,13 @@ impl Animations {
let event_atom = match event.event_type { let event_atom = match event.event_type {
TransitionOrAnimationEventType::AnimationEnd => atom!("animationend"), TransitionOrAnimationEventType::AnimationEnd => atom!("animationend"),
TransitionOrAnimationEventType::AnimationStart => atom!("animationstart"),
TransitionOrAnimationEventType::AnimationCancel => atom!("animationcancel"),
TransitionOrAnimationEventType::AnimationIteration => atom!("animationiteration"), TransitionOrAnimationEventType::AnimationIteration => atom!("animationiteration"),
TransitionOrAnimationEventType::TransitionCancel => atom!("transitioncancel"), TransitionOrAnimationEventType::TransitionCancel => atom!("transitioncancel"),
TransitionOrAnimationEventType::TransitionEnd => atom!("transitionend"), TransitionOrAnimationEventType::TransitionEnd => atom!("transitionend"),
TransitionOrAnimationEventType::TransitionRun => atom!("transitionrun"), TransitionOrAnimationEventType::TransitionRun => atom!("transitionrun"),
TransitionOrAnimationEventType::TransitionStart => atom!("transitionstart"),
}; };
let parent = EventInit { let parent = EventInit {
bubbles: true, bubbles: true,
@ -381,25 +452,39 @@ pub enum TransitionOrAnimationEventType {
/// "The transitionrun event occurs when a transition is created (i.e., when it /// "The transitionrun event occurs when a transition is created (i.e., when it
/// is added to the set of running transitions)." /// is added to the set of running transitions)."
TransitionRun, TransitionRun,
/// "The transitionstart event occurs when a transitions delay phase ends."
TransitionStart,
/// "The transitionend event occurs at the completion of the transition. In the /// "The transitionend event occurs at the completion of the transition. In the
/// case where a transition is removed before completion, such as if the /// case where a transition is removed before completion, such as if the
/// transition-property is removed, then the event will not fire." /// transition-property is removed, then the event will not fire."
TransitionEnd, TransitionEnd,
/// "The transitioncancel event occurs when a transition is canceled." /// "The transitioncancel event occurs when a transition is canceled."
TransitionCancel, TransitionCancel,
/// "The animationstart event occurs at the start of the animation. If there is
/// an animation-delay then this event will fire once the delay period has expired."
AnimationStart,
/// "The animationiteration event occurs at the end of each iteration of an /// "The animationiteration event occurs at the end of each iteration of an
/// animation, except when an animationend event would fire at the same time." /// animation, except when an animationend event would fire at the same time."
AnimationIteration, AnimationIteration,
/// "The animationend event occurs when the animation finishes" /// "The animationend event occurs when the animation finishes"
AnimationEnd, AnimationEnd,
/// "The animationcancel event occurs when the animation stops running in a way
/// that does not fire an animationend event..."
AnimationCancel,
} }
impl TransitionOrAnimationEventType { impl TransitionOrAnimationEventType {
/// Whether or not this event is a transition-related event. /// Whether or not this event is a transition-related event.
pub fn is_transition_event(&self) -> bool { pub fn is_transition_event(&self) -> bool {
match *self { match *self {
Self::TransitionRun | Self::TransitionEnd | Self::TransitionCancel => true, Self::TransitionRun |
Self::AnimationEnd | Self::AnimationIteration => false, Self::TransitionEnd |
Self::TransitionCancel |
Self::TransitionStart => true,
Self::AnimationEnd |
Self::AnimationIteration |
Self::AnimationStart |
Self::AnimationCancel => false,
} }
} }
} }

View file

@ -139,17 +139,27 @@ impl PropertyAnimation {
/// This structure represents the state of an animation. /// This structure represents the state of an animation.
#[derive(Clone, Debug, MallocSizeOf, PartialEq)] #[derive(Clone, Debug, MallocSizeOf, PartialEq)]
pub enum AnimationState { 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 /// This animation is paused. The inner field is the percentage of progress
/// when it was paused, from 0 to 1. /// when it was paused, from 0 to 1.
Paused(f64), Paused(f64),
/// This animation is currently running.
Running,
/// This animation has finished. /// This animation has finished.
Finished, Finished,
/// This animation has been canceled. /// This animation has been canceled.
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. /// This structure represents a keyframes animation current iteration state.
/// ///
/// If the iteration count is infinite, there's no other state, otherwise we /// If the iteration count is infinite, there's no other state, otherwise we
@ -174,7 +184,8 @@ pub struct Animation {
/// The internal animation from the style system. /// The internal animation from the style system.
pub keyframes_animation: KeyframesAnimation, pub keyframes_animation: KeyframesAnimation,
/// The time this animation started at. /// 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, pub started_at: f64,
/// The duration of this animation. /// The duration of this animation.
@ -273,9 +284,11 @@ impl Animation {
/// canceled due to changes in the style. /// canceled due to changes in the style.
pub fn has_ended(&self, time: f64) -> bool { pub fn has_ended(&self, time: f64) -> bool {
match self.state { match self.state {
AnimationState::Canceled | AnimationState::Paused(_) => return false,
AnimationState::Finished => return true,
AnimationState::Running => {}, AnimationState::Running => {},
AnimationState::Finished => return true,
AnimationState::Pending | AnimationState::Canceled | AnimationState::Paused(_) => {
return false
},
} }
if !self.iteration_over(time) { if !self.iteration_over(time) {
@ -312,24 +325,11 @@ impl Animation {
let old_direction = self.current_direction; let old_direction = self.current_direction;
let old_state = self.state.clone(); let old_state = self.state.clone();
let old_iteration_state = self.iteration_state.clone(); let old_iteration_state = self.iteration_state.clone();
*self = other.clone(); *self = other.clone();
let mut new_started_at = old_started_at; self.started_at = old_started_at;
self.current_direction = old_direction;
// 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 Running, Paused(progress)) => new_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
},
_ => {},
}
// Don't update the iteration count, just the iteration limit. // Don't update the iteration count, just the iteration limit.
// TODO: see how changing the limit affects rendering in other browsers. // TODO: see how changing the limit affects rendering in other browsers.
@ -342,8 +342,37 @@ impl Animation {
_ => {}, _ => {},
} }
self.current_direction = old_direction; // Don't pause or restart animations that should remain finished.
self.started_at = new_started_at; // 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` /// Update the given style to reflect the values specified by this `Animation`
@ -360,7 +389,7 @@ impl Animation {
let started_at = self.started_at; let started_at = self.started_at;
let now = match self.state { let now = match self.state {
AnimationState::Running | AnimationState::Finished => { AnimationState::Running | AnimationState::Pending | AnimationState::Finished => {
context.current_time_for_animations context.current_time_for_animations
}, },
AnimationState::Paused(progress) => started_at + duration * progress, AnimationState::Paused(progress) => started_at + duration * progress,
@ -551,9 +580,12 @@ pub struct Transition {
pub node: OpaqueNode, pub node: OpaqueNode,
/// The start time of this transition, which is the current value of the animation /// The start time of this transition, which is the current value of the animation
/// timeline when this transition created. /// timeline when this transition was created plus any animation delay.
pub start_time: f64, pub start_time: f64,
/// The delay used for this transition.
pub delay: f64,
/// The internal style `PropertyAnimation` for this transition. /// The internal style `PropertyAnimation` for this transition.
pub property_animation: PropertyAnimation, pub property_animation: PropertyAnimation,
@ -724,26 +756,25 @@ impl ElementAnimationSet {
} }
/// Whether or not this state needs animation ticks for its transitions /// Whether or not this state needs animation ticks for its transitions
/// or animations. New animations don't need ticks until they are no /// or animations.
/// longer marked as new.
pub fn needs_animation_ticks(&self) -> bool { pub fn needs_animation_ticks(&self) -> bool {
self.animations self.animations
.iter() .iter()
.any(|animation| animation.state == AnimationState::Running && !animation.is_new) || .any(|animation| animation.state.needs_to_be_ticked()) ||
self.transitions.iter().any(|transition| { self.transitions
transition.state == AnimationState::Running && !transition.is_new .iter()
}) .any(|transition| transition.state.needs_to_be_ticked())
} }
/// The number of running animations and transitions for this `ElementAnimationSet`. /// The number of running animations and transitions for this `ElementAnimationSet`.
pub fn running_animation_and_transition_count(&self) -> usize { pub fn running_animation_and_transition_count(&self) -> usize {
self.animations self.animations
.iter() .iter()
.filter(|animation| animation.state == AnimationState::Running) .filter(|animation| animation.state.needs_to_be_ticked())
.count() + .count() +
self.transitions self.transitions
.iter() .iter()
.filter(|transition| transition.state == AnimationState::Running) .filter(|transition| transition.state.needs_to_be_ticked())
.count() .count()
} }
@ -870,8 +901,9 @@ impl ElementAnimationSet {
let mut new_transition = Transition { let mut new_transition = Transition {
node: opaque_node, node: opaque_node,
start_time: now + delay, start_time: now + delay,
delay,
property_animation, property_animation,
state: AnimationState::Running, state: AnimationState::Pending,
is_new: true, is_new: true,
reversing_adjusted_start_value, reversing_adjusted_start_value,
reversing_shortening_factor: 1.0, reversing_shortening_factor: 1.0,
@ -1040,7 +1072,7 @@ pub fn maybe_start_animations<E>(
let state = match box_style.animation_play_state_mod(i) { let state = match box_style.animation_play_state_mod(i) {
AnimationPlayState::Paused => AnimationState::Paused(0.), AnimationPlayState::Paused => AnimationState::Paused(0.),
AnimationPlayState::Running => AnimationState::Running, AnimationPlayState::Running => AnimationState::Pending,
}; };
let new_animation = Animation { let new_animation = Animation {

View file

@ -1,5 +0,0 @@
[animationevent-types.html]
expected: TIMEOUT
[animationstart event is instanceof AnimationEvent]
expected: TIMEOUT

View file

@ -1,5 +1,5 @@
[Element-getAnimations.tentative.html] [Element-getAnimations.tentative.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/26626
[getAnimations for CSS Animations with animation-name: none] [getAnimations for CSS Animations with animation-name: none]
expected: FAIL expected: FAIL

View file

@ -1,5 +1,5 @@
[animationevent-pseudoelement.html] [animationevent-pseudoelement.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/10316
expected: TIMEOUT expected: TIMEOUT
[AnimationEvent should have the correct pseudoElement memeber] [AnimationEvent should have the correct pseudoElement memeber]
expected: TIMEOUT expected: TIMEOUT

View file

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

View file

@ -1,5 +1,5 @@
[events-006.html] [events-006.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/10316
expected: TIMEOUT expected: TIMEOUT
[transition padding-left on ::after] [transition padding-left on ::after]
expected: NOTRUN expected: NOTRUN

View file

@ -1,5 +1,5 @@
[variable-animation-from-to.html] [variable-animation-from-to.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/26625
expected: TIMEOUT expected: TIMEOUT
[Verify CSS variable value before animation] [Verify CSS variable value before animation]
expected: FAIL expected: FAIL

View file

@ -1,5 +1,5 @@
[variable-animation-over-transition.html] [variable-animation-over-transition.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/26625
expected: TIMEOUT expected: TIMEOUT
[Verify CSS variable value before animation] [Verify CSS variable value before animation]
expected: FAIL expected: FAIL

View file

@ -1,2 +1,2 @@
[variable-animation-substitute-into-keyframe-shorthand.html] [variable-animation-substitute-into-keyframe-shorthand.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/26625

View file

@ -1,5 +1,5 @@
[variable-animation-substitute-into-keyframe-transform.html] [variable-animation-substitute-into-keyframe-transform.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/26625
[Verify transform before animation] [Verify transform before animation]
expected: FAIL expected: FAIL

View file

@ -1,2 +1,2 @@
[variable-animation-substitute-into-keyframe.html] [variable-animation-substitute-into-keyframe.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/26625

View file

@ -1,5 +1,5 @@
[variable-animation-substitute-within-keyframe-fallback.html] [variable-animation-substitute-within-keyframe-fallback.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/26625
[Verify color after animation] [Verify color after animation]
expected: FAIL expected: FAIL

View file

@ -1,5 +1,5 @@
[variable-animation-substitute-within-keyframe-multiple.html] [variable-animation-substitute-within-keyframe-multiple.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/26625
[Verify color after animation] [Verify color after animation]
expected: FAIL expected: FAIL

View file

@ -1,5 +1,5 @@
[variable-animation-substitute-within-keyframe.html] [variable-animation-substitute-within-keyframe.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/26625
[Verify color after animation] [Verify color after animation]
expected: FAIL expected: FAIL

View file

@ -1,5 +1,5 @@
[variable-animation-to-only.html] [variable-animation-to-only.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/26625
expected: TIMEOUT expected: TIMEOUT
[Verify CSS variable value after animation] [Verify CSS variable value after animation]
expected: TIMEOUT expected: TIMEOUT

View file

@ -1,5 +1,5 @@
[variable-transitions-from-no-value.html] [variable-transitions-from-no-value.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/26625
expected: TIMEOUT expected: TIMEOUT
[Verify CSS variable value after transition] [Verify CSS variable value after transition]
expected: NOTRUN expected: NOTRUN

View file

@ -1,5 +1,5 @@
[variable-transitions-to-no-value.html] [variable-transitions-to-no-value.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/26625
expected: TIMEOUT expected: TIMEOUT
[Verify CSS variable value after transition] [Verify CSS variable value after transition]
expected: NOTRUN expected: NOTRUN

View file

@ -1,5 +1,5 @@
[variable-transitions-transition-property-variable-before-value.html] [variable-transitions-transition-property-variable-before-value.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/26625
expected: TIMEOUT expected: TIMEOUT
[Verify CSS variable value after transition] [Verify CSS variable value after transition]
expected: NOTRUN expected: NOTRUN

View file

@ -1,5 +1,5 @@
[variable-transitions-value-before-transition-property-variable.html] [variable-transitions-value-before-transition-property-variable.html]
bug: https://github.com/servo/servo/issues/21564 bug: https://github.com/servo/servo/issues/26625
expected: TIMEOUT expected: TIMEOUT
[Verify CSS variable value after transition] [Verify CSS variable value after transition]
expected: NOTRUN expected: NOTRUN

View file

@ -0,0 +1,2 @@
prefs: ["layout.animations.test.enabled:false",
"dom.testbinding.enabled:false"]

View file

@ -0,0 +1,3 @@
prefs: ["layout.animations.test.enabled:false",
"dom.testbinding.enabled:false"]

View file

@ -12849,6 +12849,15 @@
}, },
"css": { "css": {
"animations": { "animations": {
"animation-events.html": [
"0975aa64ec47ca4b4c8fc1e0a40414a51719ad67",
[
null,
{
"timeout": "long"
}
]
],
"animation-fill-mode.html": [ "animation-fill-mode.html": [
"4cfaab9fbce0adccd83f592935e63fa8ff58a1cf", "4cfaab9fbce0adccd83f592935e63fa8ff58a1cf",
[ [
@ -12884,6 +12893,13 @@
{} {}
] ]
], ],
"transition-events.html": [
"b561fc8353276e6bdd13a9d1b965f57733ecd19b",
[
null,
{}
]
],
"transition-raf.html": [ "transition-raf.html": [
"c38404503408e04b3c75b42df18ec3a7ec0819f5", "c38404503408e04b3c75b42df18ec3a7ec0819f5",
[ [

View file

@ -0,0 +1,2 @@
prefs: ["layout.animations.test.enabled:false",
"dom.testbinding.enabled:false"]

View file

@ -0,0 +1,3 @@
prefs: ["layout.animations.test.enabled:false",
"dom.testbinding.enabled:false"]

View file

@ -0,0 +1,160 @@
<!doctype html>
<meta charset=utf-8>
<title>CSS animation event dispatch</title>
<meta name="timeout" content="long">
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#event-dispatch"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
@keyframes anim {
from { margin-left: 0px; }
to { margin-left: 100px; }
}
</style>
<div id="log"></div>
<script>
'use strict';
// This series of tests is a forked version of the Web Platform Test
// /css/css-animations/event-dispatch.tentative that do not make use
// of the Web Animations API, since Servo doesn't yet support Web Animations.
function waitForFrame() {
return new Promise(resolve => {
window.requestAnimationFrame(resolve);
});
}
// All animation events should be received on the next animation frame.
const animationEventsTimeout = () => {
return new Promise(function(resolve) {
setTimeout(resolve, 5000);
});
}
const setupAnimation = (t, animationStyle) => {
var div = document.createElement('div');
div.setAttribute('style', 'animation: ' + animationStyle);
document.body.appendChild(div);
if (t && typeof t.add_cleanup === 'function') {
t.add_cleanup(function() {
if (div.parentNode) {
div.remove();
}
});
}
const watcher = new EventWatcher(t, div, [ 'animationstart',
'animationiteration',
'animationend',
'animationcancel' ],
animationEventsTimeout);
return { watcher, div };
};
promise_test(async t => {
const { watcher } = setupAnimation(t, 'anim 100s');
const events = await watcher.wait_for(
['animationstart' ],
{
record: 'all',
}
);
assert_equals(events[0].elapsedTime, 0.0);
}, 'animationstart');
promise_test(async t => {
const { watcher } = setupAnimation(t, 'anim 0.1s');
const events = await watcher.wait_for(
['animationstart', 'animationend'],
{
record: 'all',
}
);
assert_equals(events[0].elapsedTime, 0);
assert_equals(events[0].animationName, "anim");
assert_approx_equals(events[1].elapsedTime, 0.1, 0.001);
assert_equals(events[1].animationName, "anim");
}, 'animationstart and animationend');
promise_test(async t => {
const { watcher } = setupAnimation(t, 'anim 1s 0.5s');
const events = await watcher.wait_for(
['animationstart', 'animationend'], { record: 'all', }
);
assert_approx_equals(events[0].elapsedTime, 0, 0.01);
assert_equals(events[0].animationName, "anim");
assert_approx_equals(events[1].elapsedTime, 1, 0.01);
assert_equals(events[1].animationName, "anim");
}, 'animationstart and animationend with positive delay');
promise_test(async t => {
const { watcher } = setupAnimation(t, 'anim 100s -99.99s');
const events = await watcher.wait_for(
['animationstart', 'animationend'], { record: 'all', }
);
assert_approx_equals(events[0].elapsedTime, 99.99, 0.1);
assert_equals(events[0].animationName, "anim");
assert_approx_equals(events[1].elapsedTime, 99.99, 0.1);
assert_equals(events[1].animationName, "anim");
}, 'animationstart and animationend with negative delay');
promise_test(async t => {
const { watcher } = setupAnimation(t, 'anim 100s -200s');
const events = await watcher.wait_for(
['animationstart', 'animationend'], { record: 'all', }
);
assert_approx_equals(events[0].elapsedTime, 99.99, 0.1);
assert_equals(events[0].animationName, "anim");
assert_approx_equals(events[1].elapsedTime, 99.99, 0.1);
assert_equals(events[1].animationName, "anim");
}, 'animationstart and animationend with negative delay larger than active duration');
promise_test(async t => {
const { watcher, div } = setupAnimation(t, 'anim 100s');
await watcher.wait_for('animationstart');
div.style.animation = "";
await watcher.wait_for('animationcancel');
}, 'animationcancel');
promise_test(async t => {
const { watcher, div } = setupAnimation(t, 'anim 100s 50s');
// Wait for two animation frames. One is not enough in some browser engines.
await waitForFrame();
await waitForFrame();
div.style.animation = "";
const events = await watcher.wait_for(
['animationcancel'], { record: 'all', }
);
assert_equals(events[0].elapsedTime, 0);
}, 'animationcancel with positive delay');
promise_test(async t => {
const { watcher, div } = setupAnimation(t, 'anim 100s -50s');
await watcher.wait_for('animationstart');
div.style.animation = "";
const events = await watcher.wait_for(
['animationcancel'], { record: 'all', }
);
assert_approx_equals(events[0].elapsedTime, 50, 0.1);
}, 'animationcancel with negative delay');
</script>

View file

@ -0,0 +1,131 @@
<!doctype html>
<meta charset=utf-8>
<title>CSS transition event dispatch</title>
<link rel="help" href="https://drafts.csswg.org/css-transitions-2/#event-dispatch">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<script>
'use strict';
// This series of tests is a forked version of the Web Platform Test
// /css/css-transitions/event-dispatch.tentative that do not make use
// of the Web Animations API, since Servo doesn't yet support Web Animations.
// All transition events should be received on the next animation frame.
function transitionEventsTimeout() {
return new Promise(function(resolve) {
return new Promise(resolve => {
window.requestAnimationFrame(resolve);
});
});
};
const setupTransition = (t, transitionStyle) => {
var div = document.createElement('div');
div.setAttribute('style', 'transition: ' + transitionStyle);
document.body.appendChild(div);
if (t && typeof t.add_cleanup === 'function') {
t.add_cleanup(function() {
if (div.parentNode) {
div.remove();
}
});
}
const watcher = new EventWatcher(t, div, [ 'transitionrun',
'transitionstart',
'transitionend',
'transitioncancel' ],
transitionEventsTimeout);
getComputedStyle(div).marginLeft;
div.style.marginLeft = '100px';
return { watcher, div };
};
promise_test(async t => {
const { watcher } = setupTransition(t, 'margin-left 100s 100s');
const events = await watcher.wait_for(
['transitionrun' ],
{
record: 'all',
}
);
assert_equals(events[0].elapsedTime, 0.0);
}, 'transitionrun');
promise_test(async t => {
const { watcher } = setupTransition(t, 'margin-left 100s');
const events = await watcher.wait_for(
['transitionrun', 'transitionstart', ],
{
record: 'all',
}
);
assert_equals(events[0].elapsedTime, 0.0);
assert_equals(events[1].elapsedTime, 0.0);
}, 'transitionrun, transitionstart');
promise_test(async t => {
const { watcher, div } = setupTransition(t, 'margin-left 0.1s');
const events = await watcher.wait_for(
['transitionrun', 'transitionstart', 'transitionend'],
{
record: 'all',
}
);
assert_equals(events[0].elapsedTime, 0);
assert_equals(events[0].propertyName, "margin-left");
assert_equals(events[1].elapsedTime, 0);
assert_equals(events[1].propertyName, "margin-left");
assert_approx_equals(events[2].elapsedTime, 0.1, 0.01);
assert_equals(events[2].propertyName, "margin-left");
}, 'transitionrun, transitionstart, transitionend');
promise_test(async t => {
const { watcher, div } = setupTransition(t, 'margin-left 1s 0.5s');
const events = await watcher.wait_for(
['transitionrun', 'transitionstart', 'transitionend'],
{
record: 'all',
}
);
assert_equals(events[0].elapsedTime, 0);
assert_equals(events[0].propertyName, "margin-left");
assert_equals(events[1].elapsedTime, 0);
assert_equals(events[1].propertyName, "margin-left");
assert_equals(events[2].elapsedTime, 1);
assert_equals(events[2].propertyName, "margin-left");
}, 'transitionrun, transitionstart, transitionend with positive delay');
promise_test(async t => {
const { watcher, div } = setupTransition(t, 'margin-left 100s -99.99s');
const events = await watcher.wait_for(
['transitionrun', 'transitionstart', 'transitionend'],
{
record: 'all',
}
);
assert_approx_equals(events[0].elapsedTime, 99.99, 0.1);
assert_equals(events[0].propertyName, "margin-left");
assert_approx_equals(events[1].elapsedTime, 99.99, 0.1);
assert_equals(events[1].propertyName, "margin-left");
assert_approx_equals(events[2].elapsedTime, 99.99, 0.1);
assert_equals(events[2].propertyName, "margin-left");
}, 'transitionrun, transitionstart, transitionend with negative delay');
</script>