<!doctype html> <meta charset=utf-8> <title>Tests for 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> <script src="support/testcommon.js"></script> <style> @keyframes anim { from { margin-left: 0px; } to { margin-left: 100px; } } </style> <div id="log"></div> <script> 'use strict'; const setupAnimation = (t, animationStyle) => { const div = addDiv(t, { style: 'animation: ' + animationStyle }); const animation = div.getAnimations()[0]; const timeoutPromise = armTimeoutWhenReady(animation, fastEventsTimeout); const watcher = new EventWatcher(t, div, [ 'animationstart', 'animationiteration', 'animationend', 'animationcancel' ], timeoutPromise); return { animation, watcher, div }; }; promise_test(async t => { // Add 1ms delay to ensure that the delay is not included in the elapsedTime. const { animation, watcher } = setupAnimation(t, 'anim 100s 1ms'); const evt = await watcher.wait_for('animationstart'); assert_equals(evt.elapsedTime, 0.0); }, 'Idle -> Active'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s'); // Seek to After phase. animation.finish(); const events = await watcher.wait_for(['animationstart', 'animationend'], { record: 'all', }); assert_equals(events[0].elapsedTime, 0.0); assert_equals(events[1].elapsedTime, 100); }, 'Idle -> After'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s 100s paused'); await animation.ready; // Seek to Active phase. animation.currentTime = 100 * MS_PER_SEC; const evt = await watcher.wait_for('animationstart'); assert_equals(evt.elapsedTime, 0.0); }, 'Before -> Active'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s 100s paused'); const allEvents = watcher.wait_for(['animationstart', 'animationend'], { record: 'all', }); await animation.ready; // Seek to After phase. animation.finish(); const events = await allEvents; assert_equals(events[0].elapsedTime, 0.0); assert_equals(events[1].elapsedTime, 100.0); }, 'Before -> After'); promise_test(async t => { const { animation, watcher, div } = setupAnimation(t, 'anim 100s paused'); await watcher.wait_for('animationstart'); // Make idle div.style.display = 'none'; const evt = await watcher.wait_for('animationcancel'); assert_equals(evt.elapsedTime, 0.0); }, 'Active -> Idle, display: none'); promise_test(async t => { const { animation, watcher, div } = setupAnimation(t, 'anim 100s paused'); // Seek to After phase. animation.finish(); await watcher.wait_for(['animationstart', 'animationend']); div.style.display = 'none'; // Wait a couple of frames and check that no event was dispatched. await waitForAnimationFrames(2); }, 'After -> Idle, display: none'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s'); await watcher.wait_for('animationstart'); animation.currentTime = 100.0; // Make idle animation.timeline = null; const evt = await watcher.wait_for('animationcancel'); assert_time_equals_literal(evt.elapsedTime, 0.1); }, 'Active -> Idle, setting Animation.timeline = null'); promise_test(async t => { // We should NOT pause animation since calling cancel synchronously. const { animation, watcher } = setupAnimation(t, 'anim 100s'); await watcher.wait_for('animationstart'); animation.currentTime = 50.0; animation.cancel(); const evt = await watcher.wait_for('animationcancel'); assert_time_equals_literal(evt.elapsedTime, 0.05); }, 'Active -> Idle, calling Animation.cancel()'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s 100s paused'); // Seek to Active phase. animation.currentTime = 100 * MS_PER_SEC; await watcher.wait_for('animationstart'); // Seek to Before phase. animation.currentTime = 0; const evt = await watcher.wait_for('animationend'); assert_equals(evt.elapsedTime, 0.0); }, 'Active -> Before'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s paused'); await watcher.wait_for('animationstart'); // Seek to After phase. animation.finish(); const evt = await watcher.wait_for('animationend'); assert_equals(evt.elapsedTime, 100.0); }, 'Active -> After'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s 100s paused'); // Seek to After phase. animation.finish(); await watcher.wait_for([ 'animationstart', 'animationend' ]); // Seek to Before phase. animation.currentTime = 0; const events = await watcher.wait_for(['animationstart', 'animationend'], { record: 'all', }); assert_equals(events[0].elapsedTime, 100.0); assert_equals(events[1].elapsedTime, 0.0); }, 'After -> Before'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s 100s paused'); // Seek to After phase. animation.finish(); await watcher.wait_for([ 'animationstart', 'animationend' ]); // Seek to Active phase. animation.currentTime = 100 * MS_PER_SEC; const evt = await watcher.wait_for('animationstart'); assert_equals(evt.elapsedTime, 100.0); }, 'After -> Active'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s 100s 3 paused'); await animation.ready; // Seek to iteration 0 (no animationiteration event should be dispatched) animation.currentTime = 100 * MS_PER_SEC; await watcher.wait_for('animationstart'); // Seek to iteration 2 animation.currentTime = 300 * MS_PER_SEC; let evt = await watcher.wait_for('animationiteration'); assert_equals(evt.elapsedTime, 200); // Seek to After phase (no animationiteration event should be dispatched) animation.currentTime = 400 * MS_PER_SEC; evt = await watcher.wait_for('animationend'); assert_equals(evt.elapsedTime, 300); }, 'Active -> Active (forwards)'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s 100s 3'); // Seek to After phase. animation.finish(); await watcher.wait_for([ 'animationstart', 'animationend' ]); // Seek to iteration 2 (no animationiteration event should be dispatched) animation.pause(); animation.currentTime = 300 * MS_PER_SEC; await watcher.wait_for('animationstart'); // Seek to mid of iteration 0 phase. animation.currentTime = 200 * MS_PER_SEC; const evt = await watcher.wait_for('animationiteration'); assert_equals(evt.elapsedTime, 200.0); // Seek to before phase (no animationiteration event should be dispatched) animation.currentTime = 0; await watcher.wait_for('animationend'); }, 'Active -> Active (backwards)'); promise_test(async t => { const { animation, watcher, div } = setupAnimation(t, 'anim 100s paused'); await watcher.wait_for('animationstart'); // Seek to Idle phase. div.style.display = 'none'; flushComputedStyle(div); await watcher.wait_for('animationcancel'); // Restart this animation. div.style.display = ''; await watcher.wait_for('animationstart'); }, 'Active -> Idle -> Active: animationstart is fired by restarting animation'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s 100s 2 paused'); // Make After. animation.finish(); await watcher.wait_for([ 'animationstart', 'animationend' ]); animation.playbackRate = -1; let evt = await watcher.wait_for('animationstart'); assert_equals(evt.elapsedTime, 200); // Seek to 1st iteration animation.currentTime = 200 * MS_PER_SEC - 1; evt = await watcher.wait_for('animationiteration'); assert_equals(evt.elapsedTime, 100); // Seek to before animation.currentTime = 100 * MS_PER_SEC - 1; evt = await watcher.wait_for('animationend'); assert_equals(evt.elapsedTime, 0); assert_equals(animation.playState, 'running'); // delay }, 'Negative playbackRate sanity test(Before -> Active -> Before)'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s 100s'); animation.currentTime = 150 * MS_PER_SEC; animation.currentTime = 50 * MS_PER_SEC; // Then wait a couple of frames and check that no event was dispatched. await waitForAnimationFrames(2); }, 'Redundant change, before -> active, then back'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s 100s'); animation.currentTime = 250 * MS_PER_SEC; animation.currentTime = 50 * MS_PER_SEC; // Then wait a couple of frames and check that no event was dispatched. await waitForAnimationFrames(2); }, 'Redundant change, before -> after, then back'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s 100s'); // Get us into the initial state: animation.currentTime = 150 * MS_PER_SEC; await watcher.wait_for('animationstart'); animation.currentTime = 50 * MS_PER_SEC; animation.currentTime = 150 * MS_PER_SEC; // Then wait a couple of frames and check that no event was dispatched. await waitForAnimationFrames(2); }, 'Redundant change, active -> before, then back'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s 100s'); // Get us into the initial state: animation.currentTime = 150 * MS_PER_SEC; await watcher.wait_for('animationstart'); animation.currentTime = 250 * MS_PER_SEC; animation.currentTime = 150 * MS_PER_SEC; // Then wait a couple of frames and check that no event was dispatched. await waitForAnimationFrames(2); }, 'Redundant change, active -> after, then back'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s 100s'); // Get us into the initial state: animation.currentTime = 250 * MS_PER_SEC; await watcher.wait_for(['animationstart', 'animationend']); animation.currentTime = 50 * MS_PER_SEC; animation.currentTime = 250 * MS_PER_SEC; // Then wait a couple of frames and check that no event was dispatched. await waitForAnimationFrames(2); }, 'Redundant change, after -> before, then back'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s 100s'); // Get us into the initial state: animation.currentTime = 250 * MS_PER_SEC; await watcher.wait_for(['animationstart', 'animationend']); animation.currentTime = 150 * MS_PER_SEC; animation.currentTime = 250 * MS_PER_SEC; // Then wait a couple of frames and check that no event was dispatched. await waitForAnimationFrames(2); }, 'Redundant change, after -> active, then back'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s'); await watcher.wait_for('animationstart'); // Make idle animation.cancel(); await watcher.wait_for('animationcancel'); animation.cancel(); // Then wait a couple of frames and check that no event was dispatched. await waitForAnimationFrames(2); }, 'Call Animation.cancel after canceling animation.'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s'); await watcher.wait_for('animationstart'); // Make idle animation.cancel(); animation.play(); await watcher.wait_for([ 'animationcancel', 'animationstart' ]); }, 'Restart animation after canceling animation immediately.'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s'); await watcher.wait_for('animationstart'); // Make idle animation.cancel(); animation.play(); animation.cancel(); await watcher.wait_for('animationcancel'); // Then wait a couple of frames and check that no event was dispatched. await waitForAnimationFrames(2); }, 'Call Animation.cancel after restarting animation immediately.'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s'); await watcher.wait_for('animationstart'); // Make idle animation.timeline = null; await watcher.wait_for('animationcancel'); animation.timeline = document.timeline; animation.play(); await watcher.wait_for('animationstart'); }, 'Set timeline and play transition after clearing the timeline.'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s'); await watcher.wait_for('animationstart'); // Make idle animation.cancel(); await watcher.wait_for('animationcancel'); animation.effect = null; // Then wait a couple of frames and check that no event was dispatched. await waitForAnimationFrames(2); }, 'Set null target effect after canceling the animation.'); promise_test(async t => { const { animation, watcher } = setupAnimation(t, 'anim 100s'); await watcher.wait_for('animationstart'); animation.effect = null; await watcher.wait_for('animationend'); animation.cancel(); // Then wait a couple of frames and check that no event was dispatched. await waitForAnimationFrames(2); }, 'Cancel the animation after clearing the target effect.'); </script>