| <!doctype html> |
| <meta charset=utf-8> |
| <title>Timelines</title> |
| <link rel="help" href="https://drafts.csswg.org/web-animations/#timelines"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="../../testcommon.js"></script> |
| <style> |
| @keyframes opacity-animation { |
| from { opacity: 1; } |
| to { opacity: 0; } |
| } |
| </style> |
| <div id="log"></div> |
| <script> |
| 'use strict'; |
| |
| promise_test(t => { |
| const valueAtStart = document.timeline.currentTime; |
| const timeAtStart = window.performance.now(); |
| while (window.performance.now() - timeAtStart < 50) { |
| // Wait 50ms |
| } |
| assert_equals(document.timeline.currentTime, valueAtStart, |
| 'Timeline time does not change within an animation frame'); |
| return waitForAnimationFrames(1).then(() => { |
| assert_greater_than(document.timeline.currentTime, valueAtStart, |
| 'Timeline time increases between animation frames'); |
| }); |
| }, 'Timeline time increases once per animation frame'); |
| |
| async_test(t => { |
| const iframe = document.createElement('iframe'); |
| iframe.width = 10; |
| iframe.height = 10; |
| |
| iframe.addEventListener('load', t.step_func(() => { |
| const iframeTimeline = iframe.contentDocument.timeline; |
| const valueAtStart = iframeTimeline.currentTime; |
| const timeAtStart = window.performance.now(); |
| while (iframe.contentWindow.performance.now() - timeAtStart < 50) { |
| // Wait 50ms |
| } |
| assert_equals(iframeTimeline.currentTime, valueAtStart, |
| 'Timeline time within an iframe does not change within an ' |
| + ' animation frame'); |
| |
| iframe.contentWindow.requestAnimationFrame(t.step_func_done(() => { |
| assert_greater_than(iframeTimeline.currentTime, valueAtStart, |
| 'Timeline time within an iframe increases between animation frames'); |
| iframe.remove(); |
| })); |
| })); |
| |
| document.body.appendChild(iframe); |
| }, 'Timeline time increases once per animation frame in an iframe'); |
| |
| async_test(t => { |
| const startTime = document.timeline.currentTime; |
| let firstRafTime; |
| |
| requestAnimationFrame(() => { |
| t.step(() => { |
| assert_greater_than_equal(document.timeline.currentTime, startTime, |
| 'Timeline time should have progressed'); |
| firstRafTime = document.timeline.currentTime; |
| }); |
| }); |
| |
| requestAnimationFrame(() => { |
| t.step(() => { |
| assert_equals(document.timeline.currentTime, firstRafTime, |
| 'Timeline time should be the same'); |
| }); |
| t.done(); |
| }); |
| }, 'Timeline time should be the same for all RAF callbacks in an animation' |
| + ' frame'); |
| |
| promise_test(async t => { |
| // A microtask checkpoint is run as part of the process of updating |
| // timelines to ensure that any microtasks queued during promise |
| // resolution are handled before dispatching animation events. |
| const div = createDiv(t); |
| const events = []; |
| let microtaskFrameTime = undefined; |
| let finishFrameTime = undefined; |
| const waitForMicrotask = (animation) => { |
| return new Promise(resolve => { |
| queueMicrotask(() => { |
| events.push('microtask'); |
| microtaskFrameTime = document.timeline.currentTime; |
| resolve(); |
| }); |
| }); |
| } |
| const waitForFinishEvent = (animation) => { |
| return new Promise(resolve => { |
| animation.onfinish = (event) => { |
| events.push('finish'); |
| finishFrameTime = event.timelineTime; |
| resolve(); |
| }; |
| }); |
| } |
| |
| await waitForNextFrame(); |
| |
| const animation = div.animate({ opacity: [0, 1] }, 1000 * MS_PER_SEC); |
| const finishPromise = waitForFinishEvent(animation); |
| await animation.ready; |
| |
| // Advance the timing to effect end, to asynchronously queue up a finish task. |
| // Queue up microtask, which must be processed ahead of the finish event. |
| // See "Perform a microtask checkpoint" step in |
| // https://www.w3.org/TR/web-animations-1/#timelines. |
| animation.currentTime = animation.effect.getComputedTiming().duration; |
| const microtaskPromise = waitForMicrotask(animation); |
| await Promise.all([finishPromise, microtaskPromise]); |
| assert_array_equals(events, ['microtask', 'finish']); |
| assert_times_equal(microtaskFrameTime, finishFrameTime); |
| |
| }, 'Performs a microtask checkpoint after updating timelines'); |
| |
| async_test(t => { |
| const div = createDiv(t); |
| let readyPromiseRan = false; |
| let finishedPromiseRan = false; |
| div.style.animation = 'opacity-animation 1ms'; |
| let anim = div.getAnimations()[0]; |
| anim.ready.then(t.step_func(() => { |
| readyPromiseRan = true; |
| })); |
| div.addEventListener('animationstart', t.step_func(() => { |
| assert_true(readyPromiseRan, 'It should run ready promise before animationstart event'); |
| })); |
| anim.finished.then(t.step_func(() => { |
| finishedPromiseRan = true; |
| })); |
| div.addEventListener('animationend', t.step_func_done(() => { |
| assert_true(finishedPromiseRan, 'It should run finished promise before animationend event'); |
| })); |
| }, 'Runs finished promise before animation events'); |
| </script> |