| <!DOCTYPE html> |
| <meta charset=utf-8> |
| <title>Animatable.getAnimations</title> |
| <link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animatable-getanimations"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="../../testcommon.js"></script> |
| <body> |
| <script> |
| 'use strict'; |
| |
| test(t => { |
| const div = createDiv(t); |
| assert_array_equals(div.getAnimations(), []); |
| }, 'Returns an empty array for an element with no animations'); |
| |
| test(t => { |
| const div = createDiv(t); |
| const animationA = div.animate(null, 100 * MS_PER_SEC); |
| const animationB = div.animate(null, 100 * MS_PER_SEC); |
| assert_array_equals(div.getAnimations(), [animationA, animationB]); |
| }, 'Returns both animations for an element with two animations'); |
| |
| test(t => { |
| const divA = createDiv(t); |
| const divB = createDiv(t); |
| const animationA = divA.animate(null, 100 * MS_PER_SEC); |
| const animationB = divB.animate(null, 100 * MS_PER_SEC); |
| assert_array_equals(divA.getAnimations(), [animationA], 'divA'); |
| assert_array_equals(divB.getAnimations(), [animationB], 'divB'); |
| }, 'Returns only the animations specific to each sibling element'); |
| |
| test(t => { |
| const divParent = createDiv(t); |
| const divChild = createDiv(t); |
| divParent.appendChild(divChild); |
| const animationParent = divParent.animate(null, 100 * MS_PER_SEC); |
| const animationChild = divChild.animate(null, 100 * MS_PER_SEC); |
| assert_array_equals(divParent.getAnimations(), [animationParent], |
| 'divParent'); |
| assert_array_equals(divChild.getAnimations(), [animationChild], 'divChild'); |
| }, 'Returns only the animations specific to each parent/child element'); |
| |
| test(t => { |
| const divParent = createDiv(t); |
| const divChild = createDiv(t); |
| divParent.appendChild(divChild); |
| const divGrandChildA = createDiv(t); |
| const divGrandChildB = createDiv(t); |
| divChild.appendChild(divGrandChildA); |
| divChild.appendChild(divGrandChildB); |
| |
| // Trigger the animations in a somewhat random order |
| const animGrandChildB = divGrandChildB.animate(null, 100 * MS_PER_SEC); |
| const animChild = divChild.animate(null, 100 * MS_PER_SEC); |
| const animGrandChildA = divGrandChildA.animate(null, 100 * MS_PER_SEC); |
| |
| assert_array_equals( |
| divParent.getAnimations({ subtree: true }), |
| [animGrandChildB, animChild, animGrandChildA], |
| 'Returns expected animations from parent' |
| ); |
| assert_array_equals( |
| divChild.getAnimations({ subtree: true }), |
| [animGrandChildB, animChild, animGrandChildA], |
| 'Returns expected animations from child' |
| ); |
| assert_array_equals( |
| divGrandChildA.getAnimations({ subtree: true }), |
| [animGrandChildA], |
| 'Returns expected animations from grandchild A' |
| ); |
| }, 'Returns animations on descendants when subtree: true is specified'); |
| |
| test(t => { |
| createStyle(t, { |
| '@keyframes anim': '', |
| [`.pseudo::before`]: 'animation: anim 100s; ' + "content: '';", |
| }); |
| const div = createDiv(t); |
| div.classList.add('pseudo'); |
| |
| assert_equals( |
| div.getAnimations().length, |
| 0, |
| 'Returns no animations when subtree is false' |
| ); |
| assert_equals( |
| div.getAnimations({ subtree: true }).length, |
| 1, |
| 'Returns one animation when subtree is true' |
| ); |
| }, 'Returns animations on pseudo-elements when subtree: true is specified'); |
| |
| test(t => { |
| const host = createDiv(t); |
| const shadow = host.attachShadow({ mode: 'open' }); |
| |
| const elem = createDiv(t); |
| shadow.appendChild(elem); |
| |
| const elemChild = createDiv(t); |
| elem.appendChild(elemChild); |
| |
| elemChild.animate(null, 100 * MS_PER_SEC); |
| |
| assert_equals( |
| host.getAnimations({ subtree: true }).length, |
| 0, |
| 'Returns no animations with subtree:true when called on the host' |
| ); |
| assert_equals( |
| elem.getAnimations({ subtree: true }).length, |
| 1, |
| 'Returns one animation when called on a parent in the shadow tree' |
| ); |
| }, 'Does NOT cross shadow-tree boundaries when subtree: true is specified'); |
| |
| test(t => { |
| const foreignElement |
| = document.createElementNS('http://example.org/test', 'test'); |
| document.body.appendChild(foreignElement); |
| t.add_cleanup(() => { |
| foreignElement.remove(); |
| }); |
| |
| const animation = foreignElement.animate(null, 100 * MS_PER_SEC); |
| assert_array_equals(foreignElement.getAnimations(), [animation]); |
| }, 'Returns animations for a foreign element'); |
| |
| test(t => { |
| const div = createDiv(t); |
| const animation = div.animate(null, 100 * MS_PER_SEC); |
| animation.finish(); |
| assert_array_equals(div.getAnimations(), []); |
| }, 'Does not return finished animations that do not fill forwards'); |
| |
| test(t => { |
| const div = createDiv(t); |
| const animation = div.animate(null, { |
| duration: 100 * MS_PER_SEC, |
| fill: 'forwards', |
| }); |
| animation.finish(); |
| assert_array_equals(div.getAnimations(), [animation]); |
| }, 'Returns finished animations that fill forwards'); |
| |
| test(t => { |
| const div = createDiv(t); |
| const animation = div.animate(null, { |
| duration: 100 * MS_PER_SEC, |
| delay: 100 * MS_PER_SEC, |
| }); |
| assert_array_equals(div.getAnimations(), [animation]); |
| }, 'Returns animations yet to reach their active phase'); |
| |
| test(t => { |
| const div = createDiv(t); |
| const animation = div.animate(null, 100 * MS_PER_SEC); |
| animation.playbackRate = -1; |
| assert_array_equals(div.getAnimations(), []); |
| }, 'Does not return reversed finished animations that do not fill backwards'); |
| |
| test(t => { |
| const div = createDiv(t); |
| const animation = div.animate(null, { |
| duration: 100 * MS_PER_SEC, |
| fill: 'backwards', |
| }); |
| animation.playbackRate = -1; |
| assert_array_equals(div.getAnimations(), [animation]); |
| }, 'Returns reversed finished animations that fill backwards'); |
| |
| test(t => { |
| const div = createDiv(t); |
| const animation = div.animate(null, 100 * MS_PER_SEC); |
| animation.playbackRate = -1; |
| animation.currentTime = 200 * MS_PER_SEC; |
| assert_array_equals(div.getAnimations(), [animation]); |
| }, 'Returns reversed animations yet to reach their active phase'); |
| |
| test(t => { |
| const div = createDiv(t); |
| const animation = div.animate(null, { |
| duration: 100 * MS_PER_SEC, |
| delay: 100 * MS_PER_SEC, |
| }); |
| animation.playbackRate = 0; |
| assert_array_equals(div.getAnimations(), []); |
| }, 'Does not return animations with zero playback rate in before phase'); |
| |
| test(t => { |
| const div = createDiv(t); |
| const animation = div.animate(null, 100 * MS_PER_SEC); |
| animation.finish(); |
| animation.playbackRate = 0; |
| animation.currentTime = 200 * MS_PER_SEC; |
| assert_array_equals(div.getAnimations(), []); |
| }, 'Does not return animations with zero playback rate in after phase'); |
| |
| test(t => { |
| const div = createDiv(t); |
| const effect = new KeyframeEffect(div, {}, 225); |
| const animation = new Animation(effect, new DocumentTimeline()); |
| animation.reverse(); |
| animation.pause(); |
| animation.playbackRate = -1;; |
| animation.updatePlaybackRate(1); |
| assert_array_equals(div.getAnimations(), []); |
| }, 'Does not return an animation that has recently been made not current by setting the playback rate'); |
| |
| test(t => { |
| const div = createDiv(t); |
| const animation = div.animate(null, 100 * MS_PER_SEC); |
| |
| animation.finish(); |
| assert_array_equals(div.getAnimations(), [], |
| 'Animation should not be returned when it is finished'); |
| |
| animation.effect.updateTiming({ |
| duration: animation.effect.getTiming().duration + 100 * MS_PER_SEC, |
| }); |
| assert_array_equals(div.getAnimations(), [animation], |
| 'Animation should be returned after extending the' |
| + ' duration'); |
| |
| animation.effect.updateTiming({ duration: 0 }); |
| assert_array_equals(div.getAnimations(), [], |
| 'Animation should not be returned after setting the' |
| + ' duration to zero'); |
| }, 'Returns animations based on dynamic changes to individual' |
| + ' animations\' duration'); |
| |
| test(t => { |
| const div = createDiv(t); |
| const animation = div.animate(null, 100 * MS_PER_SEC); |
| |
| animation.effect.updateTiming({ endDelay: -200 * MS_PER_SEC }); |
| assert_array_equals(div.getAnimations(), [], |
| 'Animation should not be returned after setting a' |
| + ' negative end delay such that the end time is less' |
| + ' than the current time'); |
| |
| animation.effect.updateTiming({ endDelay: 100 * MS_PER_SEC }); |
| assert_array_equals(div.getAnimations(), [animation], |
| 'Animation should be returned after setting a positive' |
| + ' end delay such that the end time is more than the' |
| + ' current time'); |
| }, 'Returns animations based on dynamic changes to individual' |
| + ' animations\' end delay'); |
| |
| test(t => { |
| const div = createDiv(t); |
| const animation = div.animate(null, 100 * MS_PER_SEC); |
| |
| animation.finish(); |
| assert_array_equals(div.getAnimations(), [], |
| 'Animation should not be returned when it is finished'); |
| |
| animation.effect.updateTiming({ iterations: 10 }); |
| assert_array_equals(div.getAnimations(), [animation], |
| 'Animation should be returned after inreasing the' |
| + ' number of iterations'); |
| |
| animation.effect.updateTiming({ iterations: 0 }); |
| assert_array_equals(div.getAnimations(), [], |
| 'Animations should not be returned after setting the' |
| + ' iteration count to zero'); |
| |
| animation.effect.updateTiming({ iterations: Infinity }); |
| assert_array_equals(div.getAnimations(), [animation], |
| 'Animation should be returned after inreasing the' |
| + ' number of iterations to infinity'); |
| }, 'Returns animations based on dynamic changes to individual' |
| + ' animations\' iteration count'); |
| |
| test(t => { |
| const div = createDiv(t); |
| const animation = div.animate(null, |
| { duration: 100 * MS_PER_SEC, |
| delay: 50 * MS_PER_SEC, |
| endDelay: -50 * MS_PER_SEC }); |
| |
| assert_array_equals(div.getAnimations(), [animation], |
| 'Animation should be returned at during delay phase'); |
| |
| animation.currentTime = 50 * MS_PER_SEC; |
| assert_array_equals(div.getAnimations(), [animation], |
| 'Animation should be returned after seeking to the start' |
| + ' of the active interval'); |
| |
| animation.currentTime = 100 * MS_PER_SEC; |
| assert_array_equals(div.getAnimations(), [], |
| 'Animation should not be returned after seeking to the' |
| + ' clipped end of the active interval'); |
| }, 'Returns animations based on dynamic changes to individual' |
| + ' animations\' current time'); |
| |
| promise_test(async t => { |
| const div = createDiv(t); |
| |
| const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); |
| const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); |
| await animA.finished; |
| // It is not guaranteed that the mircrotask PerformCheckpoint() happens before |
| // the animation finish promised got resolved, because the microtask |
| // checkpoint could also be triggered from other source such as the event_loop |
| // Thus we wait for one animation frame to make sure the finished animation is |
| // properly removed. |
| await waitForNextFrame(1); |
| assert_array_equals(div.getAnimations(), [animB]); |
| }, 'Does not return an animation that has been removed'); |
| |
| promise_test(async t => { |
| const div = createDiv(t); |
| |
| const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); |
| const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); |
| await animA.finished; |
| |
| animA.persist(); |
| |
| assert_array_equals(div.getAnimations(), [animA, animB]); |
| }, 'Returns an animation that has been persisted'); |
| |
| promise_test(async t => { |
| const div = createDiv(t); |
| const watcher = EventWatcher(t, div, 'transitionrun'); |
| |
| // Create a covering animation to prevent transitions from firing after |
| // calling getAnimations(). |
| const coveringAnimation = new Animation( |
| new KeyframeEffect(div, { opacity: [0, 1] }, 100 * MS_PER_SEC) |
| ); |
| |
| // Setup transition start point. |
| div.style.transition = 'opacity 100s'; |
| getComputedStyle(div).opacity; |
| |
| // Update specified style but don't flush style. |
| div.style.opacity = '0.5'; |
| |
| // Fetch animations |
| div.getAnimations(); |
| |
| // Play the covering animation to ensure that only the call to |
| // getAnimations() has a chance to trigger transitions. |
| coveringAnimation.play(); |
| |
| // If getAnimations() flushed style, we should get a transitionrun event. |
| await watcher.wait_for('transitionrun'); |
| }, 'Triggers a style change event'); |
| |
| </script> |
| </body> |