| <!doctype html> |
| <meta charset=utf-8> |
| <script src="../../../resources/testharness.js"></script> |
| <script src="../../../resources/testharnessreport.js"></script> |
| <script src="../resources/testcommon.js"></script> |
| <style> |
| @keyframes anim1 { |
| to { left: 100px } |
| } |
| @keyframes anim2 { |
| to { top: 100px } |
| } |
| @keyframes multiPropAnim { |
| to { background: green, opacity: 0.5, left: 100px, top: 100px } |
| } |
| ::before { |
| content: '' |
| } |
| ::after { |
| content: '' |
| } |
| @keyframes empty { } |
| </style> |
| <body> |
| <div id="log"></div> |
| <script> |
| 'use strict'; |
| |
| test(function(t) { |
| var div = addDiv(t); |
| assert_equals(div.getAnimations().length, 0, |
| 'getAnimations returns an empty sequence for an element' |
| + ' with no animations'); |
| }, 'getAnimations for non-animated content'); |
| |
| promise_test(function(t) { |
| var div = addDiv(t); |
| |
| // FIXME: This test does too many things. It should be split up. |
| |
| // Add an animation |
| div.style.animation = 'anim1 100s'; |
| var animations = div.getAnimations(); |
| assert_equals(animations.length, 1, |
| 'getAnimations returns an Animation running CSS Animations'); |
| return animations[0].ready.then(function() { |
| var startTime = animations[0].startTime; |
| assert_true(startTime > 0 && startTime <= document.timeline.currentTime, |
| 'CSS animation has a sensible start time'); |
| |
| // Wait a moment then add a second animation. |
| // |
| // We wait for the next frame so that we can test that the start times of |
| // the animations differ. |
| return waitForFrame(); |
| }).then(function() { |
| div.style.animation = 'anim1 100s, anim2 100s'; |
| animations = div.getAnimations(); |
| assert_equals(animations.length, 2, |
| 'getAnimations returns one Animation for each value of' |
| + ' animation-name'); |
| // Wait until both Animations are ready |
| // (We don't make any assumptions about the order of the Animations since |
| // that is the purpose of the following test.) |
| return waitForAllAnimations(animations); |
| }).then(function() { |
| assert_true(animations[0].startTime < animations[1].startTime, |
| 'Additional Animations for CSS animations start after the original' |
| + ' animation and appear later in the list'); |
| }); |
| }, 'getAnimations for CSS Animations'); |
| |
| test(function(t) { |
| var div = addDiv(t, { style: 'animation: anim1 100s' }); |
| assert_class_string(div.getAnimations()[0], 'CSSAnimation', |
| 'Interface of returned animation is CSSAnimation'); |
| }, 'getAnimations returns CSSAnimation objects for CSS Animations'); |
| |
| test(function(t) { |
| var div = addDiv(t); |
| |
| // Add an animation that targets multiple properties |
| div.style.animation = 'multiPropAnim 100s'; |
| assert_equals(div.getAnimations().length, 1, |
| 'getAnimations returns only one Animation for a CSS Animation' |
| + ' that targets multiple properties'); |
| }, 'getAnimations for multi-property animations'); |
| |
| promise_test(function(t) { |
| var div = addDiv(t); |
| |
| // Add an animation |
| div.style.backgroundColor = 'red'; |
| div.style.animation = 'anim1 100s'; |
| getComputedStyle(div).backgroundColor; |
| |
| // Wait until a frame after the animation starts, then add a transition |
| var animations = div.getAnimations(); |
| return animations[0].ready.then(waitForFrame).then(function() { |
| div.style.transition = 'all 100s'; |
| div.style.backgroundColor = 'green'; |
| |
| animations = div.getAnimations(); |
| assert_equals(animations.length, 2, |
| 'getAnimations returns Animations for both animations and' |
| + ' transitions that run simultaneously'); |
| assert_class_string(animations[0], 'CSSTransition', |
| 'First-returned animation is the CSS Transition'); |
| assert_class_string(animations[1], 'CSSAnimation', |
| 'Second-returned animation is the CSS Animation'); |
| }); |
| }, 'getAnimations for both CSS Animations and CSS Transitions at once'); |
| |
| async_test(function(t) { |
| var div = addDiv(t); |
| |
| // Set up event listener |
| div.addEventListener('animationend', t.step_func(function() { |
| assert_equals(div.getAnimations().length, 0, |
| 'getAnimations does not return Animations for finished ' |
| + ' (and non-forwards-filling) CSS Animations'); |
| t.done(); |
| })); |
| |
| // Add a very short animation |
| div.style.animation = 'anim1 0.01s'; |
| }, 'getAnimations for CSS Animations that have finished'); |
| |
| async_test(function(t) { |
| var div = addDiv(t); |
| |
| // Set up event listener |
| div.addEventListener('animationend', t.step_func(function() { |
| assert_equals(div.getAnimations().length, 1, |
| 'getAnimations returns Animations for CSS Animations that have' |
| + ' finished but are filling forwards'); |
| t.done(); |
| })); |
| |
| // Add a very short animation |
| div.style.animation = 'anim1 0.01s forwards'; |
| }, 'getAnimations for CSS Animations that have finished but are' |
| + ' forwards filling'); |
| |
| test(function(t) { |
| var div = addDiv(t); |
| div.style.animation = 'none 100s'; |
| |
| var animations = div.getAnimations(); |
| assert_equals(animations.length, 0, |
| 'getAnimations returns an empty sequence for an element' |
| + ' with animation-name: none'); |
| |
| div.style.animation = 'none 100s, anim1 100s'; |
| animations = div.getAnimations(); |
| assert_equals(animations.length, 1, |
| 'getAnimations returns Animations only for those CSS Animations whose' |
| + ' animation-name is not none'); |
| }, 'getAnimations for CSS Animations with animation-name: none'); |
| |
| test(function(t) { |
| var div = addDiv(t); |
| div.style.animation = 'missing 100s'; |
| var animations = div.getAnimations(); |
| assert_equals(animations.length, 0, |
| 'getAnimations returns an empty sequence for an element' |
| + ' with animation-name: missing'); |
| |
| div.style.animation = 'anim1 100s, missing 100s'; |
| animations = div.getAnimations(); |
| assert_equals(animations.length, 1, |
| 'getAnimations returns Animations only for those CSS Animations whose' |
| + ' animation-name is found'); |
| }, 'getAnimations for CSS Animations with animation-name: missing'); |
| |
| promise_test(function(t) { |
| var div = addDiv(t); |
| div.style.animation = 'anim1 100s, notyet 100s'; |
| var animations = div.getAnimations(); |
| assert_equals(animations.length, 1, |
| 'getAnimations initally only returns Animations for CSS Animations whose' |
| + ' animation-name is found'); |
| |
| return animations[0].ready.then(waitForFrame).then(function() { |
| var keyframes = '@keyframes notyet { to { left: 100px; } }'; |
| document.styleSheets[0].insertRule(keyframes, 0); |
| animations = div.getAnimations(); |
| assert_equals(animations.length, 2, |
| 'getAnimations includes Animation when @keyframes rule is added' |
| + ' later'); |
| return waitForAllAnimations(animations); |
| }).then(function() { |
| assert_true(animations[0].startTime < animations[1].startTime, |
| 'Newly added animation has a later start time'); |
| document.styleSheets[0].deleteRule(0); |
| }); |
| }, 'getAnimations for CSS Animations where the @keyframes rule is added' |
| + ' later'); |
| |
| test(function(t) { |
| var div = addDiv(t); |
| div.style.animation = 'anim1 100s, anim1 100s'; |
| assert_equals(div.getAnimations().length, 2, |
| 'getAnimations returns one Animation for each CSS animation-name' |
| + ' even if the names are duplicated'); |
| }, 'getAnimations for CSS Animations with duplicated animation-name'); |
| |
| test(function(t) { |
| var div = addDiv(t); |
| div.style.animation = 'empty 100s'; |
| assert_equals(div.getAnimations().length, 1, |
| 'getAnimations returns Animations for CSS animations with an' |
| + ' empty keyframes rule'); |
| }, 'getAnimations for CSS Animations with empty keyframes rule'); |
| |
| promise_test(function(t) { |
| var div = addDiv(t); |
| div.style.animation = 'anim1 100s 100s'; |
| var animations = div.getAnimations(); |
| assert_equals(animations.length, 1, |
| 'getAnimations returns animations for CSS animations whose' |
| + ' delay makes them start later'); |
| return animations[0].ready.then(waitForFrame).then(function() { |
| assert_true(animations[0].startTime <= document.timeline.currentTime, |
| 'For CSS Animations in delay phase, the start time of the Animation is' |
| + ' not in the future'); |
| }); |
| }, 'getAnimations for CSS animations in delay phase'); |
| |
| test(function(t) { |
| var div = addDiv(t); |
| div.style.animation = 'anim1 0s 100s'; |
| assert_equals(div.getAnimations().length, 1, |
| 'getAnimations returns animations for CSS animations whose' |
| + ' duration is zero'); |
| div.remove(); |
| }, 'getAnimations for zero-duration CSS Animations'); |
| |
| test(function(t) { |
| var div = addDiv(t); |
| div.style.animation = 'anim1 100s'; |
| var originalAnimation = div.getAnimations()[0]; |
| |
| // Update pause state (an Animation change) |
| div.style.animationPlayState = 'paused'; |
| var pendingAnimation = div.getAnimations()[0]; |
| assert_equals(pendingAnimation.playState, 'paused', |
| 'animation\'s play state is updated'); |
| assert_equals(originalAnimation, pendingAnimation, |
| 'getAnimations returns the same objects even when their' |
| + ' play state changes'); |
| |
| // Update duration (an Animation change) |
| div.style.animationDuration = '200s'; |
| var extendedAnimation = div.getAnimations()[0]; |
| // FIXME: Check extendedAnimation.effect.timing.duration has changed once the |
| // API is available |
| assert_equals(originalAnimation, extendedAnimation, |
| 'getAnimations returns the same objects even when their' |
| + ' duration changes'); |
| }, 'getAnimations returns objects with the same identity'); |
| |
| test(function(t) { |
| var div = addDiv(t); |
| div.style.animation = 'anim1 100s'; |
| |
| assert_equals(div.getAnimations().length, 1, |
| 'getAnimations returns an animation before cancelling'); |
| |
| var animation = div.getAnimations()[0]; |
| |
| animation.cancel(); |
| assert_equals(div.getAnimations().length, 0, |
| 'getAnimations does not return cancelled animations'); |
| |
| animation.play(); |
| assert_equals(div.getAnimations().length, 1, |
| 'getAnimations returns cancelled animations that have been re-started'); |
| |
| }, 'getAnimations for CSS Animations that are cancelled'); |
| |
| promise_test(function(t) { |
| var div = addDiv(t); |
| div.style.animation = 'anim2 100s'; |
| |
| return div.getAnimations()[0].ready.then(function() { |
| // Prepend to the list and test that even though anim1 was triggered |
| // *after* anim2, it should come first because it appears first |
| // in the animation-name property. |
| div.style.animation = 'anim1 100s, anim2 100s'; |
| var anims = div.getAnimations(); |
| assert_equals(anims[0].animationName, 'anim1', |
| 'animation order after prepending to list'); |
| assert_equals(anims[1].animationName, 'anim2', |
| 'animation order after prepending to list'); |
| |
| // Normally calling cancel and play would this push anim1 to the top of |
| // the stack but it shouldn't for CSS animations that map an the |
| // animation-name property. |
| var anim1 = anims[0]; |
| anim1.cancel(); |
| anim1.play(); |
| anims = div.getAnimations(); |
| assert_equals(anims[0].animationName, 'anim1', |
| 'animation order after cancelling and restarting'); |
| assert_equals(anims[1].animationName, 'anim2', |
| 'animation order after cancelling and restarting'); |
| }); |
| }, 'getAnimations for CSS Animations follows animation-name order'); |
| |
| test(function(t) { |
| addStyle(t, { '#target::after': 'animation: anim1 10s;', |
| '#target::before': 'animation: anim1 10s;' }); |
| var target = addDiv(t, { 'id': 'target' }); |
| target.style.animation = 'anim1 100s'; |
| |
| var animations = target.getAnimations({ subtree: false }); |
| assert_equals(animations.length, 1, |
| 'Should find only the element'); |
| assert_equals(animations[0].effect.target, target, |
| 'Effect target should be the element'); |
| }, 'Test AnimationFilter{ subtree: false } with single element'); |
| |
| test(function(t) { |
| addStyle(t, { '#target::after': 'animation: anim1 10s;', |
| '#target::before': 'animation: anim1 10s;' }); |
| var target = addDiv(t, { 'id': 'target' }); |
| target.style.animation = 'anim1 100s'; |
| |
| var animations = target.getAnimations({ subtree: true }); |
| assert_equals(animations.length, 3, |
| 'getAnimations({ subtree: true }) ' + |
| 'should return animations on pseudo-elements'); |
| assert_equals(animations[0].effect.target, target, |
| 'The animation targeting the parent element ' + |
| 'should be returned first'); |
| assert_equals(animations[1].effect.target.type, '::before', |
| 'The animation targeting the ::before pseudo-element ' + |
| 'should be returned second'); |
| assert_equals(animations[2].effect.target.type, '::after', |
| 'The animation targeting the ::after pesudo-element ' + |
| 'should be returned last'); |
| }, 'Test AnimationFilter{ subtree: true } with single element'); |
| |
| test(function(t) { |
| addStyle(t, { '#parent::after': 'animation: anim1 10s;', |
| '#parent::before': 'animation: anim1 10s;', |
| '#child::after': 'animation: anim1 10s;', |
| '#child::before': 'animation: anim1 10s;' }); |
| var parent = addDiv(t, { 'id': 'parent' }); |
| parent.style.animation = 'anim1 100s'; |
| var child = addDiv(t, { 'id': 'child' }); |
| child.style.animation = 'anim1 100s'; |
| parent.appendChild(child); |
| |
| var animations = parent.getAnimations({ subtree: false }); |
| assert_equals(animations.length, 1, |
| 'Should find only the element even if it has a child'); |
| assert_equals(animations[0].effect.target, parent, |
| 'Effect target shuld be the element'); |
| }, 'Test AnimationFilter{ subtree: false } with element that has a child'); |
| |
| test(function(t) { |
| addStyle(t, { '#parent::after': 'animation: anim1 10s;', |
| '#parent::before': 'animation: anim1 10s;', |
| '#child::after': 'animation: anim1 10s;', |
| '#child::before': 'animation: anim1 10s;' }); |
| var parent = addDiv(t, { 'id': 'parent' }); |
| var child = addDiv(t, { 'id': 'child' }); |
| parent.style.animation = 'anim1 100s'; |
| child.style.animation = 'anim1 100s'; |
| parent.appendChild(child); |
| |
| var animations = parent.getAnimations({ subtree: true }); |
| assert_equals(animations.length, 6, |
| 'Should find all elements, pesudo-elements that parent has'); |
| |
| assert_equals(animations[0].effect.target, parent, |
| 'The animation targeting the parent element ' + |
| 'should be returned first'); |
| assert_equals(animations[1].effect.target.type, '::before', |
| 'The animation targeting the ::before pseudo-element ' + |
| 'should be returned second'); |
| assert_equals(animations[1].effect.target.parentElement, parent, |
| 'This ::before element should be child of parent element'); |
| assert_equals(animations[2].effect.target.type, '::after', |
| 'The animation targeting the ::after pesudo-element ' + |
| 'should be returned third'); |
| assert_equals(animations[2].effect.target.parentElement, parent, |
| 'This ::after element should be child of parent element'); |
| |
| assert_equals(animations[3].effect.target, child, |
| 'The animation targeting the child element ' + |
| 'should be returned fourth'); |
| assert_equals(animations[4].effect.target.type, '::before', |
| 'The animation targeting the ::before pseudo-element ' + |
| 'should be returned fifth'); |
| assert_equals(animations[4].effect.target.parentElement, child, |
| 'This ::before element should be child of child element'); |
| assert_equals(animations[5].effect.target.type, '::after', |
| 'The animation targeting the ::after pesudo-element ' + |
| 'should be returned last'); |
| assert_equals(animations[5].effect.target.parentElement, child, |
| 'This ::after element should be child of child element'); |
| }, 'Test AnimationFilter{ subtree: true } with element that has a child'); |
| |
| test(function(t) { |
| var parent = addDiv(t, { 'id': 'parent' }); |
| var child1 = addDiv(t, { 'id': 'child1' }); |
| var grandchild1 = addDiv(t, { 'id': 'grandchild1' }); |
| var grandchild2 = addDiv(t, { 'id': 'grandchild2' }); |
| var child2 = addDiv(t, { 'id': 'child2' }); |
| |
| parent.style.animation = 'anim1 100s'; |
| child1.style.animation = 'anim1 100s'; |
| grandchild1.style.animation = 'anim1 100s'; |
| grandchild2.style.animation = 'anim1 100s'; |
| child2.style.animation = 'anim1 100s'; |
| |
| parent.appendChild(child1); |
| child1.appendChild(grandchild1); |
| child1.appendChild(grandchild2); |
| parent.appendChild(child2); |
| |
| var animations = parent.getAnimations({ subtree: true }); |
| assert_equals( |
| parent.getAnimations({ subtree: true }).length, 5, |
| 'Should find all descendants of the element'); |
| |
| assert_equals(animations[0].effect.target, parent, |
| 'The animation targeting the parent element ' + |
| 'should be returned first'); |
| |
| assert_equals(animations[1].effect.target, child1, |
| 'The animation targeting the child1 element ' + |
| 'should be returned second'); |
| |
| assert_equals(animations[2].effect.target, grandchild1, |
| 'The animation targeting the grandchild1 element ' + |
| 'should be returned third'); |
| |
| assert_equals(animations[3].effect.target, grandchild2, |
| 'The animation targeting the grandchild2 element ' + |
| 'should be returned fourth'); |
| |
| assert_equals(animations[4].effect.target, child2, |
| 'The animation targeting the child2 element ' + |
| 'should be returned last'); |
| |
| }, 'Test AnimationFilter{ subtree: true } with element that has many descendant'); |
| |
| </script> |
| </body> |