<!doctype html> <meta charset=utf-8> <script src="../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 } } @keyframes empty { } </style> <body> <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'; window.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, 'pending', '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'); done(); </script> </body>