<!doctype html>
<meta charset=utf-8>
<title>KeyframeEffect.getKeyframes() for CSS animations</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/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: rgb(0, 0, 0); }
to { color: rgb(255, 255, 255); }
}
@keyframes anim-simple-three {
from { color: rgb(0, 0, 0); }
50% { color: rgb(0, 0, 255); }
to { color: rgb(255, 255, 255); }
}
@keyframes anim-simple-timing {
from { color: rgb(0, 0, 0); animation-timing-function: linear; }
50% { color: rgb(0, 0, 255); animation-timing-function: ease-in-out; }
to { color: rgb(255, 255, 255); animation-timing-function: step-end; }
}
@keyframes anim-simple-timing-some {
from { color: rgb(0, 0, 0); animation-timing-function: linear; }
50% { color: rgb(0, 0, 255); }
to { color: rgb(255, 255, 255); }
}
@keyframes anim-simple-composite {
from { color: rgb(0, 0, 0); animation-composition: replace; }
50% { color: rgb(0, 0, 255); animation-composition: add; }
to { color: rgb(255, 255, 255); animation-composition: accumulate; }
}
@keyframes anim-simple-composite-some {
from { color: rgb(0, 0, 0); animation-composition: add; }
50% { color: rgb(0, 0, 255); }
to { color: rgb(255, 255, 255); }
}
@keyframes anim-simple-shorthand {
from { margin: 8px; }
to { margin: 16px; }
}
@keyframes anim-omit-to {
from { color: rgb(0, 0, 255); }
}
@keyframes anim-omit-from {
to { color: rgb(0, 0, 255); }
}
@keyframes anim-omit-from-to {
50% { color: rgb(0, 0, 255); }
}
@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: rgb(0, 0, 0); margin-top: 8px; }
25% { color: rgb(0, 0, 255); }
75% { margin-top: 12px; }
to { color: rgb(255, 255, 255); margin-top: 16px }
}
@keyframes anim-different-props-and-easing {
from { color: rgb(0, 0, 0); margin-top: 8px; animation-timing-function: linear; }
25% { color: rgb(0, 0, 255); animation-timing-function: step-end; }
75% { margin-top: 12px; animation-timing-function: ease-in; }
to { color: rgb(255, 255, 255); margin-top: 16px }
}
@keyframes anim-merge-offset {
from { color: rgb(0, 0, 0); }
to { color: rgb(255, 255, 255); }
from { margin-top: 8px; }
to { margin-top: 16px; }
}
@keyframes anim-merge-offset-and-easing {
from { color: rgb(0, 0, 0); animation-timing-function: step-end; }
to { color: rgb(255, 255, 255); }
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-merge-offset-and-composite {
from { color: rgb(0, 0, 0); animation-composition: add; }
to { color: rgb(255, 255, 255); }
from { margin-top: 8px; animation-composition: accumulate; }
to { margin-top: 16px; }
from { font-size: 16px; animation-composition: add; }
to { font-size: 32px; }
from { padding-left: 2px; animation-composition: accumulate; }
to { padding-left: 4px; }
}
@keyframes anim-merge-offset-easing-and-composite {
from { color: rgb(0, 0, 0); animation-composition: add; }
to { color: rgb(255, 255, 255); }
from { margin-top: 8px; animation-composition: accumulate; }
to { margin-top: 16px; }
from { font-size: 16px; animation-composition: add; animation-timing-function: linear; }
to { font-size: 32px; }
from { padding-left: 2px; animation-composition: accumulate; }
to { padding-left: 4px; }
}
@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-filter-drop-shadow {
from { filter: drop-shadow(10px 10px 10px rgb(0, 255, 0)); }
to { filter: drop-shadow(50px 30px 10px rgb(255, 0, 0)); }
}
@keyframes anim-text-shadow {
to { text-shadow: none; }
}
@keyframes anim-background-size {
to { background-size: 50%, 6px, contain }
}
:root {
--var-100px: 100px;
--end-color: rgb(255, 0, 0);
}
@keyframes anim-variables {
to { transform: translate(var(--var-100px), 0) }
}
@keyframes anim-variables-shorthand {
to { margin: var(--var-100px) }
}
@keyframes anim-custom-property-in-keyframe {
to { --end-color: rgb(0, 255, 0); color: var(--end-color) }
}
@keyframes anim-only-custom-property-in-keyframe {
from { transform: translate(100px, 0) }
to { --not-used: 200px }
}
@keyframes anim-only-timing-function-for-from-and-to {
from, to { animation-timing-function: linear }
50% { left: 10px }
}
</style>
<body>
<div id="log"></div>
<script>
"use strict";
const getKeyframes = elem => elem.getAnimations()[0].effect.getKeyframes();
// 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)"
];
const kCompositeValues = [
"replace",
"add",
"accumulate"
];
test(t => {
const 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");
}, 'KeyframeEffect.getKeyframes() returns no frames for various kinds'
+ ' of empty animations');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-simple 100s';
const frames = getKeyframes(div);
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease",
color: "rgb(0, 0, 0)", composite: "auto" },
{ offset: 1, computedOffset: 1, easing: "ease",
color: "rgb(255, 255, 255)", composite: "auto" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected frames for a simple'
+ ' animation');
test(t => {
for (const easing of kTimingFunctionValues) {
const div = addDiv(t);
div.style.animation = 'anim-simple-three 100s ' + easing;
const frames = getKeyframes(div);
assert_equals(frames.length, 3, "number of frames");
for (let i = 0; i < frames.length; i++) {
assert_equals(frames[i].easing, easing,
"value for 'easing' on ComputedKeyframe #" + i);
}
}
}, 'KeyframeEffect.getKeyframes() returns frames with expected easing'
+ ' values, when the easing comes from animation-timing-function on the'
+ ' element');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-simple-timing 100s';
const 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");
}, 'KeyframeEffect.getKeyframes() returns frames with expected easing'
+ ' values, when the easing is specified on each keyframe');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-simple-timing-some 100s step-start';
const 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");
}, 'KeyframeEffect.getKeyframes() returns frames with expected easing'
+ ' values, when the easing is specified on some keyframes');
test(t => {
for (const composite of kCompositeValues) {
const div = addDiv(t);
div.style.animation = 'anim-simple-three 100s';
div.style.animationComposition = composite;
const frames = getKeyframes(div);
assert_equals(frames.length, 3, "number of frames");
for (let i = 0; i < frames.length; i++) {
assert_equals(frames[i].composite, "auto",
"value for 'composite' on ComputedKeyframe #" + i);
}
}
}, 'KeyframeEffect.getKeyframes() returns frames with expected composite'
+ ' values, when the composite is set on the effect using animation-composition on the'
+ ' element');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-simple-composite 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 3, "number of frames");
assert_equals(frames[0].composite, "replace",
"value of 'composite' on ComputedKeyframe #0");
assert_equals(frames[1].composite, "add",
"value of 'composite' on ComputedKeyframe #1");
assert_equals(frames[2].composite, "accumulate",
"value of 'composite' on ComputedKeyframe #2");
}, 'KeyframeEffect.getKeyframes() returns frames with expected composite'
+ ' values, when the composite is specified on each keyframe');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-simple-composite-some 100s';
div.style.animationComposition = 'accumulate';
const frames = getKeyframes(div);
assert_equals(frames.length, 3, "number of frames");
assert_equals(frames[0].composite, "add",
"value of 'composite' on ComputedKeyframe #0");
assert_equals(frames[1].composite, "auto",
"value of 'composite' on ComputedKeyframe #1");
assert_equals(frames[2].composite, "auto",
"value of 'composite' on ComputedKeyframe #2");
}, 'KeyframeEffect.getKeyframes() returns frames with expected composite'
+ ' values, when the composite is specified on some keyframes');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-simple-shorthand 100s';
const frames = getKeyframes(div);
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
marginBottom: "8px", marginLeft: "8px",
marginRight: "8px", marginTop: "8px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
marginBottom: "16px", marginLeft: "16px",
marginRight: "16px", marginTop: "16px" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected frames for a simple'
+ ' animation that specifies a single shorthand property');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-omit-to 100s';
div.style.color = 'rgb(255, 255, 255)';
const frames = getKeyframes(div);
// Final keyframe should be replace as per sections 7 and 8 of
// https://drafts.csswg.org/css-animations-2/#keyframes
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
color: "rgb(0, 0, 255)" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "replace",
color: "rgb(255, 255, 255)" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with a 0% keyframe and no 100% keyframe');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-omit-from 100s';
div.style.color = 'rgb(255, 255, 255)';
const frames = getKeyframes(div);
// Initial keyframe should be replace as per sections 7 and 8 of
// https://drafts.csswg.org/css-animations-2/#keyframes
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
color: "rgb(255, 255, 255)" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
color: "rgb(0, 0, 255)" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with a 100% keyframe and no 0% keyframe');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-omit-from-to 100s';
div.style.color = 'rgb(255, 255, 255)';
const frames = getKeyframes(div);
// Initial and final keyframes should be replace as per sections 7 and 8 of
// https://drafts.csswg.org/css-animations-2/#keyframes
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
color: "rgb(255, 255, 255)" },
{ offset: 0.5, computedOffset: 0.5, easing: "ease", composite: "auto",
color: "rgb(0, 0, 255)" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "replace",
color: "rgb(255, 255, 255)" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with no 0% or 100% keyframe but with a 50% keyframe');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-partially-omit-to 100s';
div.style.marginTop = '250px';
const frames = getKeyframes(div);
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
marginTop: '50px', marginBottom: '100px' },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
marginTop: '250px', marginBottom: '200px' },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with a partially complete 100% keyframe (because the ' +
'!important rule is ignored)');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-different-props 100s';
const frames = getKeyframes(div);
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
color: "rgb(0, 0, 0)", marginTop: "8px" },
{ offset: 0.25, computedOffset: 0.25, easing: "ease", composite: "auto",
color: "rgb(0, 0, 255)" },
{ offset: 0.75, computedOffset: 0.75, easing: "ease", composite: "auto",
marginTop: "12px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
color: "rgb(255, 255, 255)", marginTop: "16px" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with different properties on different keyframes, all ' +
'with the same easing function');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-different-props-and-easing 100s';
const frames = getKeyframes(div);
const expected = [
{ offset: 0, computedOffset: 0, easing: "linear", composite: "auto",
color: "rgb(0, 0, 0)", marginTop: "8px" },
{ offset: 0.25, computedOffset: 0.25, easing: "steps(1)", composite: "auto",
color: "rgb(0, 0, 255)" },
{ offset: 0.75, computedOffset: 0.75, easing: "ease-in", composite: "auto",
marginTop: "12px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
color: "rgb(255, 255, 255)", marginTop: "16px" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with different properties on different keyframes, with ' +
'a different easing function on each');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-merge-offset 100s';
const frames = getKeyframes(div);
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
color: "rgb(0, 0, 0)", marginTop: "8px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
color: "rgb(255, 255, 255)", marginTop: "16px" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with multiple keyframes for the same time, and all with ' +
'the same easing function');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-merge-offset-and-easing 100s';
const frames = getKeyframes(div);
const expected = [
{ offset: 0, computedOffset: 0, easing: "steps(1)", composite: "auto",
color: "rgb(0, 0, 0)", fontSize: "16px" },
{ offset: 0, computedOffset: 0, easing: "linear", composite: "auto",
marginTop: "8px", paddingLeft: "2px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
color: "rgb(255, 255, 255)", fontSize: "32px", marginTop: "16px",
paddingLeft: "4px" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with multiple keyframes for the same time and with ' +
'different easing functions');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-no-merge-equiv-easing 100s';
const frames = getKeyframes(div);
const expected = [
{ offset: 0, computedOffset: 0, easing: "steps(1)", composite: "auto",
marginTop: "0px", marginRight: "0px", marginBottom: "0px" },
{ offset: 0.5, computedOffset: 0.5, easing: "steps(1)", composite: "auto",
marginTop: "10px", marginRight: "10px", marginBottom: "10px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
marginTop: "20px", marginRight: "20px", marginBottom: "20px" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with multiple keyframes for the same time and with ' +
'different but equivalent easing functions');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-merge-offset-and-composite 100s';
const frames = getKeyframes(div);
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "add",
color: "rgb(0, 0, 0)", fontSize: "16px" },
{ offset: 0, computedOffset: 0, easing: "ease", composite: "accumulate",
marginTop: "8px", paddingLeft: "2px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
color: "rgb(255, 255, 255)", fontSize: "32px", marginTop: "16px",
paddingLeft: "4px" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with multiple keyframes for the same time and with ' +
'different composite operations');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-merge-offset-easing-and-composite 100s';
const frames = getKeyframes(div);
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "add",
color: "rgb(0, 0, 0)" },
{ offset: 0, computedOffset: 0, easing: "ease", composite: "accumulate",
marginTop: "8px", paddingLeft: "2px" },
{ offset: 0, computedOffset: 0, easing: "linear", composite: "add",
fontSize: "16px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
color: "rgb(255, 255, 255)", fontSize: "32px", marginTop: "16px",
paddingLeft: "4px" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with multiple keyframes for the same time and with ' +
'different easing functions and composite operations');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-overriding 100s';
const frames = getKeyframes(div);
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
paddingTop: "30px" },
{ offset: 0.5, computedOffset: 0.5, easing: "ease", composite: "auto",
paddingTop: "20px" },
{ offset: 0.75, computedOffset: 0.75, easing: "ease", composite: "auto",
paddingTop: "20px" },
{ offset: 0.85, computedOffset: 0.85, easing: "ease", composite: "auto",
paddingTop: "30px" },
{ offset: 0.851, computedOffset: 0.851, easing: "ease", composite: "auto",
paddingTop: "60px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
paddingTop: "70px" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.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(t => {
const div = addDiv(t);
div.style.animation = 'anim-filter 100s';
const frames = getKeyframes(div);
// Initial keyframe should be replace as per sections 7 and 8 of
// https://drafts.csswg.org/css-animations-2/#keyframes
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
filter: "none" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
filter: "blur(5px) sepia(60%) saturate(30%)" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
'animations with filter properties and missing keyframes');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-filter-drop-shadow 100s';
const frames = getKeyframes(div);
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
filter: "drop-shadow(rgb(0, 255, 0) 10px 10px 10px)" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
filter: "drop-shadow(rgb(255, 0, 0) 50px 30px 10px)" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
'animation with drop-shadow of filter property');
// 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(t => {
const div = addDiv(t);
div.style.textShadow = '1px 1px 2px rgb(0, 0, 0), ' +
'0 0 16px rgb(0, 0, 255), ' +
'0 0 3.2px rgb(0, 0, 255)';
div.style.animation = 'anim-text-shadow 100s';
const frames = getKeyframes(div);
// Initial keyframe should be replace as per sections 7 and 8 of
// https://drafts.csswg.org/css-animations-2/#keyframes
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
textShadow: "rgb(0, 0, 0) 1px 1px 2px,"
+ " rgb(0, 0, 255) 0px 0px 16px,"
+ " rgb(0, 0, 255) 0px 0px 3.2px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
textShadow: "none" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.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(t => {
const div = addDiv(t);
div.style.animation = 'anim-background-size 100s';
let frames = getKeyframes(div);
assert_equals(frames.length, 2, "number of frames");
// Initial keyframe should be replace as per sections 7 and 8 of
// https://drafts.csswg.org/css-animations-2/#keyframes
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
backgroundSize: "auto" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
backgroundSize: "50% auto, 6px auto, contain" },
];
for (let 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";
frames = getKeyframes(div);
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i
+ " after updating current style");
}
}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
'animations with background-size properties and missing keyframes');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-variables 100s';
const frames = getKeyframes(div);
// Initial keyframe should be replace as per sections 7 and 8 of
// https://drafts.csswg.org/css-animations-2/#keyframes
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
transform: "none" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
transform: "translate(100px)" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
'animations with CSS variables as keyframe values');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-variables-shorthand 100s';
const frames = getKeyframes(div);
// Initial keyframe should be replace as per sections 7 and 8 of
// https://drafts.csswg.org/css-animations-2/#keyframes
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
marginBottom: "0px",
marginLeft: "0px",
marginRight: "0px",
marginTop: "0px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
marginBottom: "100px",
marginLeft: "100px",
marginRight: "100px",
marginTop: "100px" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
'animations with CSS variables as keyframe values in a shorthand property');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-custom-property-in-keyframe 100s steps(2, start)';
const frames = getKeyframes(div);
// Initial keyframe should be replace as per sections 7 and 8 of
// https://drafts.csswg.org/css-animations-2/#keyframes
const expected = [
{ offset: 0, computedOffset: 0, easing: "steps(2, start)", composite: "replace",
color: "rgb(0, 0, 0)" },
{ offset: 1, computedOffset: 1, easing: "steps(2, start)", composite: "auto",
color: "rgb(0, 255, 0)" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
'animations with a CSS variable which is overriden by the value in keyframe');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-only-custom-property-in-keyframe 100s';
const frames = getKeyframes(div);
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: "auto",
transform: "translate(100px)" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "auto",
transform: "none" },
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
'animations with only custom property in a keyframe');
test(t => {
const div = addDiv(t);
// Add custom @keyframes rule
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';
// Sanity check the initial result
let frames = getKeyframes(div);
assert_frames_equal(
frames[frames.length - 1],
{
offset: 1,
computedOffset: 1,
easing: 'ease',
composite: 'auto',
left: '100px',
},
'Keyframes reflect the initial @keyframes rule'
);
// Update the @keyframes rule
keyframesRule.deleteRule(0);
keyframesRule.appendRule('to { left: 200px }');
// Check the result from getKeyframes() is updated
frames = getKeyframes(div);
assert_frames_equal(
frames[frames.length - 1],
{
offset: 1,
computedOffset: 1,
easing: 'ease',
composite: 'auto',
left: '200px',
},
'Keyframes reflects the updated @keyframes rule'
);
}, 'KeyframeEffect.getKeyframes() reflects changes to @keyframes rules');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-only-timing-function-for-from-and-to 100s';
const frames = getKeyframes(div);
// Implicit initial and final keyframes should be replace as per sections
// 7 and 8 of https://drafts.csswg.org/css-animations-2/#keyframes
const expected = [
{ offset: 0, computedOffset: 0, easing: "linear", composite: "auto" },
{ offset: 0, computedOffset: 0, easing: "ease", composite: "replace", left: "auto" },
{ offset: 0.5, computedOffset: 0.5, easing: "ease", composite: "auto", left: "10px" },
{ offset: 1, computedOffset: 1, easing: "linear", composite: "auto" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: "replace", left: "auto" }
];
assert_frame_lists_equal(frames, expected);
}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
'animations with implicit values and a non-default timing' +
'function specified for 0% and 100%');
</script>
</body>