<!doctype html> <meta charset=utf-8> <script src="../testcommon.js"></script> <style> @keyframes anim-empty { } @keyframes anim-empty-frames { from { } to { } } @keyframes anim-only-timing { from { animation-timing-function: linear; } to { } } @keyframes anim-only-non-animatable { from { animation-duration: 3s; } to { animation-duration: 5s; } } @keyframes anim-simple { from { color: black; } to { color: white; } } @keyframes anim-simple-three { from { color: black; } 50% { color: blue; } to { color: white; } } @keyframes anim-simple-timing { from { color: black; animation-timing-function: linear; } 50% { color: blue; animation-timing-function: ease-in-out; } to { color: white; animation-timing-function: step-end; } } @keyframes anim-simple-timing-some { from { color: black; animation-timing-function: linear; } 50% { color: blue; } to { color: white; } } @keyframes anim-simple-shorthand { from { margin: 8px; } to { margin: 16px; } } @keyframes anim-omit-to { from { color: blue; } } @keyframes anim-omit-from { to { color: blue; } } @keyframes anim-omit-from-to { 50% { color: blue; } } @keyframes anim-partially-omit-to { from { margin-top: 50px; margin-bottom: 100px; } to { margin-top: 150px !important; /* ignored */ margin-bottom: 200px; } } @keyframes anim-different-props { from { color: black; margin-top: 8px; } 25% { color: blue; } 75% { margin-top: 12px; } to { color: white; margin-top: 16px } } @keyframes anim-different-props-and-easing { from { color: black; margin-top: 8px; animation-timing-function: linear; } 25% { color: blue; animation-timing-function: step-end; } 75% { margin-top: 12px; animation-timing-function: ease-in; } to { color: white; margin-top: 16px } } @keyframes anim-merge-offset { from { color: black; } to { color: white; } from { margin-top: 8px; } to { margin-top: 16px; } } @keyframes anim-merge-offset-and-easing { from { color: black; animation-timing-function: step-end; } to { color: white; } from { margin-top: 8px; animation-timing-function: linear; } to { margin-top: 16px; } from { font-size: 16px; animation-timing-function: step-end; } to { font-size: 32px; } from { padding-left: 2px; animation-timing-function: linear; } to { padding-left: 4px; } } @keyframes anim-no-merge-equiv-easing { from { margin-top: 0px; animation-timing-function: steps(1, end); } from { margin-right: 0px; animation-timing-function: step-end; } from { margin-bottom: 0px; animation-timing-function: steps(1); } 50% { margin-top: 10px; animation-timing-function: step-end; } 50% { margin-right: 10px; animation-timing-function: step-end; } 50% { margin-bottom: 10px; animation-timing-function: step-end; } to { margin-top: 20px; margin-right: 20px; margin-bottom: 20px; } } @keyframes anim-overriding { from { padding-top: 50px } 50%, from { padding-top: 30px } /* wins: 0% */ 75%, 85%, 50% { padding-top: 20px } /* wins: 75%, 50% */ 100%, 85% { padding-top: 70px } /* wins: 100% */ 85.1% { padding-top: 60px } /* wins: 85.1% */ 85% { padding-top: 30px } /* wins: 85% */ } @keyframes anim-filter { to { filter: blur(5px) sepia(60%) saturate(30%); } } @keyframes anim-text-shadow { to { text-shadow: none; } } @keyframes anim-background-size { to { background-size: 50%, 6px, contain } } :root { --var-100px: 100px; } @keyframes anim-variables { to { transform: translate(var(--var-100px), 0) } } @keyframes anim-variables-shorthand { to { margin: var(--var-100px) } } </style> <body> <script> "use strict"; function getKeyframes(e) { return e.getAnimations()[0].effect.getKeyframes(); } function assert_frames_equal(a, b, name) { assert_equals(Object.keys(a).sort().toString(), Object.keys(b).sort().toString(), "properties on " + name); for (var p in a) { if (p === 'offset' || p === 'computedOffset') { assert_approx_equals(a[p], b[p], 0.00001, "value for '" + p + "' on " + name); } else { assert_equals(a[p], b[p], "value for '" + p + "' on " + name); } } } // animation-timing-function values to test with, where the value // is exactly the same as its serialization, sorted by the order // getKeyframes() will group frames with the same easing function // together (by nsTimingFunction::Compare). const kTimingFunctionValues = [ "ease", "linear", "ease-in", "ease-out", "ease-in-out", "steps(1, start)", "steps(2, start)", "steps(1)", "steps(2)", "cubic-bezier(0, 0, 1, 1)", "cubic-bezier(0, 0.25, 0.75, 1)" ]; test(function(t) { var div = addDiv(t); div.style.animation = 'anim-empty 100s'; assert_equals(getKeyframes(div).length, 0, "number of frames with empty @keyframes"); div.style.animation = 'anim-empty-frames 100s'; assert_equals(getKeyframes(div).length, 0, "number of frames when @keyframes has empty keyframes"); div.style.animation = 'anim-only-timing 100s'; assert_equals(getKeyframes(div).length, 0, "number of frames when @keyframes only has keyframes with " + "animation-timing-function"); div.style.animation = 'anim-only-non-animatable 100s'; assert_equals(getKeyframes(div).length, 0, "number of frames when @keyframes only has frames with " + "non-animatable properties"); }, 'KeyframeEffectReadOnly.getKeyframes() returns no frames for various kinds' + ' of empty enimations'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-simple 100s'; var frames = getKeyframes(div); assert_equals(frames.length, 2, "number of frames"); var expected = [ { offset: 0, computedOffset: 0, easing: "ease", color: "rgb(0, 0, 0)" }, { offset: 1, computedOffset: 1, easing: "ease", color: "rgb(255, 255, 255)" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for a simple' + 'animation'); test(function(t) { kTimingFunctionValues.forEach(function(easing) { var div = addDiv(t); div.style.animation = 'anim-simple-three 100s ' + easing; var frames = getKeyframes(div); assert_equals(frames.length, 3, "number of frames"); for (var i = 0; i < frames.length; i++) { assert_equals(frames[i].easing, easing, "value for 'easing' on ComputedKeyframe #" + i); } }); }, 'KeyframeEffectReadOnly.getKeyframes() returns frames with expected easing' + ' values, when the easing comes from animation-timing-function on the' + ' element'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-simple-timing 100s'; var frames = getKeyframes(div); assert_equals(frames.length, 3, "number of frames"); assert_equals(frames[0].easing, "linear", "value of 'easing' on ComputedKeyframe #0"); assert_equals(frames[1].easing, "ease-in-out", "value of 'easing' on ComputedKeyframe #1"); assert_equals(frames[2].easing, "steps(1)", "value of 'easing' on ComputedKeyframe #2"); }, 'KeyframeEffectReadOnly.getKeyframes() returns frames with expected easing' + ' values, when the easing is specified on each keyframe'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-simple-timing-some 100s step-start'; var frames = getKeyframes(div); assert_equals(frames.length, 3, "number of frames"); assert_equals(frames[0].easing, "linear", "value of 'easing' on ComputedKeyframe #0"); assert_equals(frames[1].easing, "steps(1, start)", "value of 'easing' on ComputedKeyframe #1"); assert_equals(frames[2].easing, "steps(1, start)", "value of 'easing' on ComputedKeyframe #2"); }, 'KeyframeEffectReadOnly.getKeyframes() returns frames with expected easing' + ' values, when the easing is specified on some keyframes'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-simple-shorthand 100s'; var frames = getKeyframes(div); assert_equals(frames.length, 2, "number of frames"); var expected = [ { offset: 0, computedOffset: 0, easing: "ease", marginBottom: "8px", marginLeft: "8px", marginRight: "8px", marginTop: "8px" }, { offset: 1, computedOffset: 1, easing: "ease", marginBottom: "16px", marginLeft: "16px", marginRight: "16px", marginTop: "16px" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for a simple' + ' animation that specifies a single shorthand property'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-omit-to 100s'; div.style.color = 'white'; var frames = getKeyframes(div); assert_equals(frames.length, 2, "number of frames"); var expected = [ { offset: 0, computedOffset: 0, easing: "ease", color: "rgb(0, 0, 255)" }, { offset: 1, computedOffset: 1, easing: "ease", color: "rgb(255, 255, 255)" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + 'animation with a 0% keyframe and no 100% keyframe'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-omit-from 100s'; div.style.color = 'white'; var frames = getKeyframes(div); assert_equals(frames.length, 2, "number of frames"); var expected = [ { offset: 0, computedOffset: 0, easing: "ease", color: "rgb(255, 255, 255)" }, { offset: 1, computedOffset: 1, easing: "ease", color: "rgb(0, 0, 255)" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + 'animation with a 100% keyframe and no 0% keyframe'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-omit-from-to 100s'; div.style.color = 'white'; var frames = getKeyframes(div); assert_equals(frames.length, 3, "number of frames"); var expected = [ { offset: 0, computedOffset: 0, easing: "ease", color: "rgb(255, 255, 255)" }, { offset: 0.5, computedOffset: 0.5, easing: "ease", color: "rgb(0, 0, 255)" }, { offset: 1, computedOffset: 1, easing: "ease", color: "rgb(255, 255, 255)" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + 'animation with no 0% or 100% keyframe but with a 50% keyframe'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-partially-omit-to 100s'; div.style.marginTop = '250px'; var frames = getKeyframes(div); assert_equals(frames.length, 2, "number of frames"); var expected = [ { offset: 0, computedOffset: 0, easing: "ease", marginTop: '50px', marginBottom: '100px' }, { offset: 1, computedOffset: 1, easing: "ease", marginTop: '250px', marginBottom: '200px' }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + 'animation with a partially complete 100% keyframe (because the ' + '!important rule is ignored)'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-different-props 100s'; var frames = getKeyframes(div); assert_equals(frames.length, 4, "number of frames"); var expected = [ { offset: 0, computedOffset: 0, easing: "ease", color: "rgb(0, 0, 0)", marginTop: "8px" }, { offset: 0.25, computedOffset: 0.25, easing: "ease", color: "rgb(0, 0, 255)" }, { offset: 0.75, computedOffset: 0.75, easing: "ease", marginTop: "12px" }, { offset: 1, computedOffset: 1, easing: "ease", color: "rgb(255, 255, 255)", marginTop: "16px" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + 'animation with different properties on different keyframes, all ' + 'with the same easing function'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-different-props-and-easing 100s'; var frames = getKeyframes(div); assert_equals(frames.length, 4, "number of frames"); var expected = [ { offset: 0, computedOffset: 0, easing: "linear", color: "rgb(0, 0, 0)", marginTop: "8px" }, { offset: 0.25, computedOffset: 0.25, easing: "steps(1)", color: "rgb(0, 0, 255)" }, { offset: 0.75, computedOffset: 0.75, easing: "ease-in", marginTop: "12px" }, { offset: 1, computedOffset: 1, easing: "ease", color: "rgb(255, 255, 255)", marginTop: "16px" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + 'animation with different properties on different keyframes, with ' + 'a different easing function on each'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-merge-offset 100s'; var frames = getKeyframes(div); assert_equals(frames.length, 2, "number of frames"); var expected = [ { offset: 0, computedOffset: 0, easing: "ease", color: "rgb(0, 0, 0)", marginTop: "8px" }, { offset: 1, computedOffset: 1, easing: "ease", color: "rgb(255, 255, 255)", marginTop: "16px" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + 'animation with multiple keyframes for the same time, and all with ' + 'the same easing function'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-merge-offset-and-easing 100s'; var frames = getKeyframes(div); assert_equals(frames.length, 3, "number of frames"); var expected = [ { offset: 0, computedOffset: 0, easing: "steps(1)", color: "rgb(0, 0, 0)", fontSize: "16px" }, { offset: 0, computedOffset: 0, easing: "linear", marginTop: "8px", paddingLeft: "2px" }, { offset: 1, computedOffset: 1, easing: "ease", color: "rgb(255, 255, 255)", fontSize: "32px", marginTop: "16px", paddingLeft: "4px" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + 'animation with multiple keyframes for the same time and with ' + 'different easing functions'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-no-merge-equiv-easing 100s'; var frames = getKeyframes(div); assert_equals(frames.length, 3, "number of frames"); var expected = [ { offset: 0, computedOffset: 0, easing: "steps(1)", marginTop: "0px", marginRight: "0px", marginBottom: "0px" }, { offset: 0.5, computedOffset: 0.5, easing: "steps(1)", marginTop: "10px", marginRight: "10px", marginBottom: "10px" }, { offset: 1, computedOffset: 1, easing: "ease", marginTop: "20px", marginRight: "20px", marginBottom: "20px" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + 'animation with multiple keyframes for the same time and with ' + 'different but equivalent easing functions'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-overriding 100s'; var frames = getKeyframes(div); assert_equals(frames.length, 6, "number of frames"); var expected = [ { offset: 0, computedOffset: 0, easing: "ease", paddingTop: "30px" }, { offset: 0.5, computedOffset: 0.5, easing: "ease", paddingTop: "20px" }, { offset: 0.75, computedOffset: 0.75, easing: "ease", paddingTop: "20px" }, { offset: 0.85, computedOffset: 0.85, easing: "ease", paddingTop: "30px" }, { offset: 0.851, computedOffset: 0.851, easing: "ease", paddingTop: "60px" }, { offset: 1, computedOffset: 1, easing: "ease", paddingTop: "70px" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for ' + 'overlapping keyframes'); // Gecko-specific test case: We are specifically concerned here that the // computed value for filter, "none", is correctly represented. test(function(t) { var div = addDiv(t); div.style.animation = 'anim-filter 100s'; var frames = getKeyframes(div); assert_equals(frames.length, 2, "number of frames"); var expected = [ { offset: 0, computedOffset: 0, easing: "ease", filter: "none" }, { offset: 1, computedOffset: 1, easing: "ease", filter: "blur(5px) sepia(60%) saturate(30%)" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' + 'animations with filter properties and missing keyframes'); // Gecko-specific test case: We are specifically concerned here that the // computed value for text-shadow and a "none" specified on a keyframe // are correctly represented. test(function(t) { var div = addDiv(t); div.style.textShadow = '1px 1px 2px black, 0 0 16px blue, 0 0 3.2px blue'; div.style.animation = 'anim-text-shadow 100s'; var frames = getKeyframes(div); assert_equals(frames.length, 2, "number of frames"); var expected = [ { offset: 0, computedOffset: 0, easing: "ease", textShadow: "1px 1px 2px 0px rgb(0, 0, 0)," + " 0px 0px 16px 0px rgb(0, 0, 255)," + " 0px 0px 3.2px 0px rgb(0, 0, 255)" }, { offset: 1, computedOffset: 1, easing: "ease", textShadow: "none" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' + 'animations with text-shadow properties and missing keyframes'); // Gecko-specific test case: We are specifically concerned here that the // initial value for background-size and the specified list are correctly // represented. test(function(t) { var div = addDiv(t); div.style.animation = 'anim-background-size 100s'; var frames = getKeyframes(div); assert_equals(frames.length, 2, "number of frames"); var expected = [ { offset: 0, computedOffset: 0, easing: "ease", backgroundSize: "auto auto" }, { offset: 1, computedOffset: 1, easing: "ease", backgroundSize: "50% auto, 6px auto, contain" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } // Test inheriting a background-size value expected[0].backgroundSize = div.style.backgroundSize = "30px auto, 40% auto, auto auto"; frames = getKeyframes(div); for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i + " after updating current style"); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' + 'animations with background-size properties and missing keyframes'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-variables 100s'; var frames = getKeyframes(div); var expected = [ { offset: 0, computedOffset: 0, easing: "ease", transform: "none" }, { offset: 1, computedOffset: 1, easing: "ease", transform: "translate(100px, 0px)" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' + 'animations with CSS variables as keyframe values'); test(function(t) { var div = addDiv(t); div.style.animation = 'anim-variables-shorthand 100s'; var frames = getKeyframes(div); var expected = [ { offset: 0, computedOffset: 0, easing: "ease", marginBottom: "0px", marginLeft: "0px", marginRight: "0px", marginTop: "0px" }, { offset: 1, computedOffset: 1, easing: "ease", marginBottom: "100px", marginLeft: "100px", marginRight: "100px", marginTop: "100px" }, ]; for (var i = 0; i < frames.length; i++) { assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); } }, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' + 'animations with CSS variables as keyframe values in a shorthand property'); done(); </script> </body>