| <!doctype html> |
| <meta charset=utf-8> |
| <title>Document.getAnimations() for CSS animations</title> |
| <link rel="help" href="https://drafts.csswg.org/css-animations-2/#animation-composite-order"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="support/testcommon.js"></script> |
| <style> |
| @keyframes animLeft { |
| to { left: 100px } |
| } |
| @keyframes animTop { |
| to { top: 100px } |
| } |
| @keyframes animBottom { |
| to { bottom: 100px } |
| } |
| @keyframes animRight { |
| to { right: 100px } |
| } |
| </style> |
| <div id="log"></div> |
| <script> |
| 'use strict'; |
| |
| test(t => { |
| assert_equals(document.getAnimations().length, 0, |
| 'getAnimations returns an empty sequence for a document' |
| + ' with no animations'); |
| }, 'getAnimations for non-animated content'); |
| |
| test(t => { |
| const div = addDiv(t); |
| |
| // Add an animation |
| div.style.animation = 'animLeft 100s'; |
| assert_equals(document.getAnimations().length, 1, |
| 'getAnimations returns a running CSS Animation'); |
| |
| // Add another animation |
| div.style.animation = 'animLeft 100s, animTop 100s'; |
| assert_equals(document.getAnimations().length, 2, |
| 'getAnimations returns two running CSS Animations'); |
| |
| // Remove both |
| div.style.animation = ''; |
| assert_equals(document.getAnimations().length, 0, |
| 'getAnimations returns no running CSS Animations'); |
| }, 'getAnimations for CSS Animations'); |
| |
| test(t => { |
| const div = addDiv(t); |
| div.style.animation = 'animLeft 100s, animTop 100s, animRight 100s, ' + |
| 'animBottom 100s'; |
| |
| const animations = document.getAnimations(); |
| assert_equals(animations.length, 4, |
| 'getAnimations returns all running CSS Animations'); |
| assert_equals(animations[0].animationName, 'animLeft', |
| 'Order of first animation returned'); |
| assert_equals(animations[1].animationName, 'animTop', |
| 'Order of second animation returned'); |
| assert_equals(animations[2].animationName, 'animRight', |
| 'Order of third animation returned'); |
| assert_equals(animations[3].animationName, 'animBottom', |
| 'Order of fourth animation returned'); |
| }, 'Order of CSS Animations - within an element'); |
| |
| test(t => { |
| const div1 = addDiv(t, { style: 'animation: animLeft 100s' }); |
| const div2 = addDiv(t, { style: 'animation: animLeft 100s' }); |
| const div3 = addDiv(t, { style: 'animation: animLeft 100s' }); |
| const div4 = addDiv(t, { style: 'animation: animLeft 100s' }); |
| |
| let animations = document.getAnimations(); |
| assert_equals(animations.length, 4, |
| 'getAnimations returns all running CSS Animations'); |
| assert_equals(animations[0].effect.target, div1, |
| 'Order of first animation returned'); |
| assert_equals(animations[1].effect.target, div2, |
| 'Order of second animation returned'); |
| assert_equals(animations[2].effect.target, div3, |
| 'Order of third animation returned'); |
| assert_equals(animations[3].effect.target, div4, |
| 'Order of fourth animation returned'); |
| |
| // Order should be depth-first pre-order so add some depth as follows: |
| // |
| // <parent> |
| // / | |
| // 2 3 |
| // / \ |
| // 1 4 |
| // |
| // Which should give: 2, 1, 4, 3 |
| div2.appendChild(div1); |
| div2.appendChild(div4); |
| animations = document.getAnimations(); |
| assert_equals(animations[0].effect.target, div2, |
| 'Order of first animation returned after tree surgery'); |
| assert_equals(animations[1].effect.target, div1, |
| 'Order of second animation returned after tree surgery'); |
| assert_equals(animations[2].effect.target, div4, |
| 'Order of third animation returned after tree surgery'); |
| assert_equals(animations[3].effect.target, div3, |
| 'Order of fourth animation returned after tree surgery'); |
| |
| }, 'Order of CSS Animations - across elements'); |
| |
| test(t => { |
| const div1 = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' }); |
| const div2 = addDiv(t, { style: 'animation: animBottom 100s' }); |
| |
| let expectedResults = [ [ div1, 'animLeft' ], |
| [ div1, 'animTop' ], |
| [ div2, 'animBottom' ] ]; |
| let animations = document.getAnimations(); |
| assert_equals(animations.length, expectedResults.length, |
| 'getAnimations returns all running CSS Animations'); |
| animations.forEach((anim, i) => { |
| assert_equals(anim.effect.target, expectedResults[i][0], |
| 'Target of animation in position ' + i); |
| assert_equals(anim.animationName, expectedResults[i][1], |
| 'Name of animation in position ' + i); |
| }); |
| |
| // Modify tree structure and animation list |
| div2.appendChild(div1); |
| div1.style.animation = 'animLeft 100s, animRight 100s, animTop 100s'; |
| |
| expectedResults = [ [ div2, 'animBottom' ], |
| [ div1, 'animLeft' ], |
| [ div1, 'animRight' ], |
| [ div1, 'animTop' ] ]; |
| animations = document.getAnimations(); |
| assert_equals(animations.length, expectedResults.length, |
| 'getAnimations returns all running CSS Animations after ' + |
| 'making changes'); |
| animations.forEach((anim, i) => { |
| assert_equals(anim.effect.target, expectedResults[i][0], |
| 'Target of animation in position ' + i + ' after changes'); |
| assert_equals(anim.animationName, expectedResults[i][1], |
| 'Name of animation in position ' + i + ' after changes'); |
| }); |
| }, 'Order of CSS Animations - across and within elements'); |
| |
| test(t => { |
| const div = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' }); |
| const animLeft = document.getAnimations()[0]; |
| assert_equals(animLeft.animationName, 'animLeft', |
| 'Originally, animLeft animation comes first'); |
| |
| // Disassociate animLeft from markup and restart |
| div.style.animation = 'animTop 100s'; |
| animLeft.play(); |
| |
| const animations = document.getAnimations(); |
| assert_equals(animations.length, 2, |
| 'getAnimations returns markup-bound and free animations'); |
| assert_equals(animations[0].animationName, 'animTop', |
| 'Markup-bound animations come first'); |
| assert_equals(animations[1], animLeft, 'Free animations come last'); |
| }, 'Order of CSS Animations - markup-bound vs free animations'); |
| |
| test(t => { |
| const div = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' }); |
| const animLeft = document.getAnimations()[0]; |
| const animTop = document.getAnimations()[1]; |
| |
| // Disassociate both animations from markup and restart in opposite order |
| div.style.animation = ''; |
| animTop.play(); |
| animLeft.play(); |
| |
| const animations = document.getAnimations(); |
| assert_equals(animations.length, 2, |
| 'getAnimations returns free animations'); |
| assert_equals(animations[0], animTop, |
| 'Free animations are returned in the order they are started'); |
| assert_equals(animations[1], animLeft, |
| 'Animations started later are returned later'); |
| |
| // Restarting an animation should have no effect |
| animTop.cancel(); |
| animTop.play(); |
| assert_equals(document.getAnimations()[0], animTop, |
| 'After restarting, the ordering of free animations' + |
| ' does not change'); |
| }, 'Order of CSS Animations - free animations'); |
| |
| test(t => { |
| // Add an animation first |
| const div = addDiv(t, { style: 'animation: animLeft 100s' }); |
| div.style.top = '0px'; |
| div.style.transition = 'all 100s'; |
| flushComputedStyle(div); |
| |
| // *Then* add a transition |
| div.style.top = '100px'; |
| flushComputedStyle(div); |
| |
| // Although the transition was added later, it should come first in the list |
| const animations = document.getAnimations(); |
| assert_equals(animations.length, 2, |
| 'Both CSS animations and transitions are returned'); |
| assert_class_string(animations[0], 'CSSTransition', 'Transition comes first'); |
| assert_class_string(animations[1], 'CSSAnimation', 'Animation comes second'); |
| }, 'Order of CSS Animations and CSS Transitions'); |
| |
| test(t => { |
| const div = addDiv(t, { style: 'animation: animLeft 100s forwards' }); |
| div.getAnimations()[0].finish(); |
| assert_equals(document.getAnimations().length, 1, |
| 'Forwards-filling CSS animations are returned'); |
| }, 'Finished but filling CSS Animations are returned'); |
| |
| test(t => { |
| const div = addDiv(t, { style: 'animation: animLeft 100s' }); |
| div.getAnimations()[0].finish(); |
| assert_equals(document.getAnimations().length, 0, |
| 'Non-filling finished CSS animations are not returned'); |
| }, 'Finished but not filling CSS Animations are not returned'); |
| |
| test(t => { |
| const div = addDiv(t, { style: 'animation: animLeft 100s 100s' }); |
| assert_equals(document.getAnimations().length, 1, |
| 'Yet-to-start CSS animations are returned'); |
| }, 'Yet-to-start CSS Animations are returned'); |
| |
| test(t => { |
| const div = addDiv(t, { style: 'animation: animLeft 100s' }); |
| div.getAnimations()[0].cancel(); |
| assert_equals(document.getAnimations().length, 0, |
| 'CSS animations canceled by the API are not returned'); |
| }, 'CSS Animations canceled via the API are not returned'); |
| |
| test(t => { |
| const div = addDiv(t, { style: 'animation: animLeft 100s' }); |
| const anim = div.getAnimations()[0]; |
| anim.cancel(); |
| anim.play(); |
| assert_equals(document.getAnimations().length, 1, |
| 'CSS animations canceled and restarted by the API are ' + |
| 'returned'); |
| }, 'CSS Animations canceled and restarted via the API are returned'); |
| |
| test(t => { |
| // Create two divs with the following arrangement: |
| // |
| // parent |
| // (::marker,) |
| // ::before, |
| // ::after |
| // | |
| // child |
| |
| addStyle(t, { |
| '#parent::after': "content: ''; animation: animLeft 100s;", |
| '#parent::before': "content: ''; animation: animRight 100s;", |
| }); |
| |
| const supportsMarkerPseudos = CSS.supports('selector(::marker)'); |
| if (supportsMarkerPseudos) { |
| addStyle(t, { |
| '#parent': 'display: list-item;', |
| '#parent::marker': "content: ''; animation: animLeft 100s;", |
| }); |
| } |
| |
| const parent = addDiv(t, { id: 'parent' }); |
| const child = addDiv(t); |
| parent.appendChild(child); |
| for (const div of [parent, child]) { |
| div.setAttribute('style', 'animation: animBottom 100s'); |
| } |
| |
| const expectedAnimations = [ |
| [parent, undefined], |
| [parent, '::marker'], |
| [parent, '::before'], |
| [parent, '::after'], |
| [child, undefined], |
| ]; |
| if (!supportsMarkerPseudos) { |
| expectedAnimations.splice(1, 1); |
| } |
| |
| const animations = document.getAnimations(); |
| assert_equals( |
| animations.length, |
| expectedAnimations.length, |
| 'CSS animations on both pseudo-elements and elements are returned' |
| ); |
| |
| for (const [index, expected] of expectedAnimations.entries()) { |
| const [element, pseudo] = expected; |
| const actual = animations[index]; |
| |
| if (pseudo) { |
| assert_equals( |
| actual.effect.target, |
| element, |
| `Animation #${index + 1} has expected target` |
| ); |
| assert_equals( |
| actual.effect.pseudoElement, |
| pseudo, |
| `Animation #${index + 1} has expected pseudo type` |
| ); |
| } else { |
| assert_equals( |
| actual.effect.target, |
| element, |
| `Animation #${index + 1} has expected target` |
| ); |
| assert_equals( |
| actual.effect.pseudoElement, |
| null, |
| `Animation #${index + 1} has null pseudo type` |
| ); |
| } |
| } |
| }, 'CSS Animations targetting (pseudo-)elements should have correct order ' |
| + 'after sorting'); |
| |
| </script> |