| <!doctype html> | |
| <meta charset=utf-8> | |
| <title>CSSAnimation.effect</title> | |
| <meta name="timeout" content="long"> | |
| <!-- TODO: Add a more specific link for this once it is specified. --> | |
| <link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation"> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <script src="support/testcommon.js"></script> | |
| <style> | |
| @keyframes anim { | |
| from { | |
| margin-left: 0px; | |
| } | |
| to { | |
| margin-left: 100px; | |
| } | |
| } | |
| </style> | |
| <div id="log"></div> | |
| <script> | |
| 'use strict'; | |
| promise_test(async t => { | |
| const div = addDiv(t); | |
| div.style.animation = 'anim 100s'; | |
| const watcher = new EventWatcher(t, div, [ 'animationend', | |
| 'animationcancel' ], | |
| fastEventsTimeout); | |
| const animation = div.getAnimations()[0]; | |
| await animation.ready; | |
| animation.currentTime = 50 * MS_PER_SEC; | |
| animation.effect = null; | |
| assert_equals(animation.playState, 'finished'); | |
| assert_equals(getComputedStyle(div).marginLeft, '0px'); | |
| await watcher.wait_for('animationend'); | |
| }, 'Setting a null effect on a running animation fires an animationend event'); | |
| promise_test(async t => { | |
| const div = addDiv(t); | |
| div.style.animation = 'anim 100s'; | |
| const animation = div.getAnimations()[0]; | |
| await animation.ready; | |
| animation.currentTime = 50 * MS_PER_SEC; | |
| animation.effect = new KeyframeEffect(div, | |
| { left: [ '0px' , '100px'] }, | |
| 100 * MS_PER_SEC); | |
| assert_equals(getComputedStyle(div).marginLeft, '0px'); | |
| assert_equals(getComputedStyle(div).left, '50px'); | |
| }, 'Replacing an animation\'s effect with an effect that targets a different ' + | |
| 'property should update both properties'); | |
| promise_test(async t => { | |
| const div = addDiv(t); | |
| div.style.animation = 'anim 100s'; | |
| const animation = div.getAnimations()[0]; | |
| await animation.ready; | |
| animation.currentTime = 50 * MS_PER_SEC; | |
| animation.effect = new KeyframeEffect(div, | |
| { left: [ '0px' , '100px'] }, | |
| 20 * MS_PER_SEC); | |
| assert_equals(animation.playState, 'finished'); | |
| }, 'Replacing an animation\'s effect with a shorter one that should have ' + | |
| 'already finished, the animation finishes immediately'); | |
| promise_test(async t => { | |
| const div = addDiv(t); | |
| div.style.animation = 'anim 100s'; | |
| const animation = div.getAnimations()[0]; | |
| assert_true(animation.pending); | |
| animation.effect = new KeyframeEffect(div, | |
| { left: [ '0px' , '100px'] }, | |
| 100 * MS_PER_SEC); | |
| assert_true(animation.pending); | |
| await animation.ready; | |
| assert_false(animation.pending); | |
| }, 'A play-pending animation\'s effect whose effect is replaced still exits ' + | |
| 'the pending state'); | |
| promise_test(async t => { | |
| const div1 = addDiv(t); | |
| const div2 = addDiv(t); | |
| const watcher1 = new EventWatcher(t, div1, 'animationstart', | |
| fastEventsTimeout); | |
| // Watch |div2| as well to ensure it does *not* get events. | |
| const watcher2 = new EventWatcher(t, div2, 'animationstart'); | |
| div1.style.animation = 'anim 100s'; | |
| const animation = div1.getAnimations()[0]; | |
| animation.effect = new KeyframeEffect(div2, | |
| { left: [ '0px', '100px' ] }, | |
| 100 * MS_PER_SEC); | |
| await watcher1.wait_for('animationstart'); | |
| assert_equals(animation.effect.target, div2); | |
| // Then wait a couple of frames and check that no event was dispatched. | |
| await waitForAnimationFrames(2); | |
| }, 'CSS animation events are dispatched at the original element even after' | |
| + ' setting an effect with a different target element'); | |
| promise_test(async t => { | |
| const div = addDiv(t); | |
| const watcher = new EventWatcher(t, div, [ 'animationstart', | |
| 'animationend', | |
| 'animationcancel' ], | |
| fastEventsTimeout); | |
| div.style.animation = 'anim 100s'; | |
| const animation = div.getAnimations()[0]; | |
| animation.finish(); | |
| await watcher.wait_for([ 'animationstart', 'animationend' ]); | |
| // Set a longer effect | |
| animation.effect = new KeyframeEffect(div, | |
| { left: [ '0px', '100px' ] }, | |
| 200 * MS_PER_SEC); | |
| await watcher.wait_for('animationstart'); | |
| }, 'After replacing a finished animation\'s effect with a longer one ' + | |
| 'it fires an animationstart event'); | |
| test(t => { | |
| const div = addDiv(t); | |
| div.style.animation = 'anim 100s'; | |
| div.style.animationComposition = 'add'; | |
| const animation = div.getAnimations()[0]; | |
| assert_equals(animation.effect.composite, 'add'); | |
| }, 'Setting animation-composition sets the composite property on the effect'); | |
| test(t => { | |
| const div = addDiv(t); | |
| // Create custom keyframes so we can tweak them | |
| const stylesheet = document.styleSheets[0]; | |
| const keyframes = '@keyframes anim-custom { to { left: 100px } }'; | |
| const ruleIndex = stylesheet.insertRule(keyframes, 0); | |
| const keyframesRule = stylesheet.cssRules[ruleIndex]; | |
| t.add_cleanup(function() { | |
| stylesheet.deleteRule(ruleIndex); | |
| }); | |
| div.style.animation = 'anim-custom 100s'; | |
| // Replace the effect | |
| const animation = div.getAnimations()[0]; | |
| animation.effect = new KeyframeEffect( | |
| div, | |
| { left: '200px' }, | |
| 200 * MS_PER_SEC | |
| ); | |
| // Update the timing properties | |
| div.style.animationDuration = '4s'; | |
| div.style.animationIterationCount = '6'; | |
| div.style.animationDirection = 'reverse'; | |
| div.style.animationDelay = '8s'; | |
| div.style.animationFillMode = 'both'; | |
| div.style.animationPlayState = 'paused'; | |
| div.style.animationComposition = 'add'; | |
| div.style.animationTimeline = 'none'; | |
| // Update the keyframes | |
| keyframesRule.deleteRule(0); | |
| keyframesRule.appendRule('to { left: 300px }'); | |
| // Check nothing (except the play state) changed | |
| assert_equals( | |
| animation.effect.getTiming().duration, | |
| 200 * MS_PER_SEC, | |
| 'duration should be the value set by the API' | |
| ); | |
| assert_equals( | |
| animation.effect.getTiming().iterations, | |
| 1, | |
| 'iterations should be the value set by the API' | |
| ); | |
| assert_equals( | |
| animation.effect.getTiming().direction, | |
| 'normal', | |
| 'direction should be the value set by the API' | |
| ); | |
| assert_equals( | |
| animation.effect.getTiming().delay, | |
| 0, | |
| 'delay should be the value set by the API' | |
| ); | |
| assert_equals( | |
| animation.effect.getTiming().fill, | |
| 'auto', | |
| 'fill should be the value set by the API' | |
| ); | |
| assert_equals( | |
| animation.effect.getKeyframes()[0].left, | |
| '200px', | |
| 'keyframes should be the value set by the API' | |
| ); | |
| assert_equals( | |
| animation.effect.composite, | |
| 'replace', | |
| 'composite should be the value set by the API' | |
| ); | |
| // Unlike the other properties animation-play-state and animation-timeline map | |
| // to the Animation, not the KeyframeEffect, so they should be overridden. | |
| assert_equals( | |
| animation.playState, | |
| 'paused', | |
| 'play state should be the value set by style' | |
| ); | |
| assert_equals( | |
| animation.timeline, | |
| null, | |
| 'timeline should be the value set by style' | |
| ); | |
| }, 'Replacing the effect of a CSSAnimation causes subsequent changes to' | |
| + ' corresponding animation-* properties to be ignored'); | |
| </script> |