Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/utils.js"></script>
<div id=outer>
<div id=div></div>
</div>
<script>
test_with_at_property({
syntax: '"<length>"',
inherits: false,
initialValue: '0px'
}, (name) => {
with_style_node(`
@keyframes test {
from { ${name}: 100px; }
to { ${name}: 200px; }
}
#div { animation: test 100s -50s linear; }
`, () => {
assert_equals(getComputedStyle(div).getPropertyValue(name), '150px');
});
}, '@keyframes works with @property');
test_with_at_property({
syntax: '"<length>"',
inherits: false,
initialValue: '0px'
}, (name) => {
with_style_node(`
@property ${name} {
syntax: "<color>";
inherits: false;
initial-value: black;
}
@keyframes test {
from { ${name}: rgb(100, 100, 100); }
to { ${name}: rgb(200, 200, 200); }
}
#div { animation: test 100s -50s linear; }
`, () => {
assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(150, 150, 150)');
});
}, '@keyframes picks up the latest @property in the document');
test_with_at_property({
syntax: '"<length>"',
inherits: false,
initialValue: '0px'
}, (name) => {
// These keyframes are initially invalid for the declared custom property.
let animation = div.animate([
{ [name]: 'rgb(100, 100, 100)'},
{ [name]: 'rgb(200, 200, 200)'},
], { duration: 10000, delay: -5000, easing: 'linear' });
let cs = getComputedStyle(div);
assert_equals(cs.getPropertyValue(name), '0px');
// Redeclare the property as a <color>, effectively making the existing
// keyframes valid.
with_at_property({
name: name,
syntax: '"<color>"',
inherits: false,
initialValue: 'black'
}, (name) => {
assert_equals(cs.getPropertyValue(name), 'rgb(150, 150, 150)');
});
animation.finish();
}, 'Ongoing animation picks up redeclared custom property');
test_with_at_property({
syntax: '"<length>"',
inherits: false,
initialValue: '0px'
}, (name) => {
// These keyframes are initially invalid for the declared custom property.
let animation = div.animate([
{ [name]: 'rgb(100, 100, 100)'},
{ [name]: 'rgb(200, 200, 200)'},
], { duration: 10000, delay: -5000, easing: 'linear' });
let cs = getComputedStyle(div);
assert_equals(cs.getPropertyValue(name), '0px');
// Setting the keyframes to something that matches <length> makes the
// interpolation valid.
animation.effect.setKeyframes([
{[name]: '100px'},
{[name]: '200px'}
]);
assert_equals(cs.getPropertyValue(name), '150px');
animation.finish();
}, 'Ongoing animation matches new keyframes against the current registration');
test_with_at_property({
syntax: '"<length>"',
inherits: false,
initialValue: '0px'
}, (name) => {
let animation = div.animate([
{ [name]: 'initial'},
{ [name]: '400px'},
], { duration: 10000, delay: -5000, easing: 'linear' });
let cs = getComputedStyle(div);
assert_equals(cs.getPropertyValue(name), '200px');
// Change initial value.
with_at_property({
name: name,
syntax: '"<length>"',
inherits: false,
initialValue: '100px'
}, (name) => {
assert_equals(cs.getPropertyValue(name), '250px');
});
animation.finish();
}, 'Ongoing animation picks up redeclared intial value');
test_with_at_property({
syntax: '"<length>"',
inherits: false,
initialValue: '0px'
}, (name) => {
try {
document.body.style = `${name}: 100px`;
// Note that 'inherit' here refers to #outer, which has the initial
// value. (#outer did not inherit from body, since the property is not
// yet declared as inherited).
let animation = div.animate([
{ [name]: 'inherit'},
{ [name]: '400px'},
], { duration: 10000, delay: -5000, easing: 'linear' });
let cs = getComputedStyle(div);
assert_equals(cs.getPropertyValue(name), '200px');
// Change inherits to 'true'. The value should now propagate from body
// to #outer.
with_at_property({
name: name,
syntax: '"<length>"',
inherits: true,
initialValue: '0px'
}, (name) => {
assert_equals(cs.getPropertyValue(name), '250px');
});
animation.finish();
} finally {
document.body.style = '';
}
}, 'Ongoing animation picks up redeclared inherits flag');
test_with_at_property({
syntax: '"<length>"',
inherits: false,
initialValue: '0px'
}, (name) => {
try {
outer.style = `${name}: 100px`;
// 'unset' should take the initial value (not the value from #outer), since
// the property is not declared as inherited.
let animation = div.animate([
{ [name]: 'unset'},
{ [name]: '400px'},
], { duration: 10000, delay: -5000, easing: 'linear' });
let cs = getComputedStyle(div);
assert_equals(cs.getPropertyValue(name), '200px');
// Change inherits to 'true'. 'unset' now refers to #outer's value.
with_at_property({
name: name,
syntax: '"<length>"',
inherits: true,
initialValue: '0px'
}, (name) => {
assert_equals(cs.getPropertyValue(name), '250px');
});
animation.finish();
} finally {
outer.style = '';
}
}, 'Ongoing animation picks up redeclared meaning of \'unset\'');
test_with_at_property({
syntax: '"<color>"',
inherits: false,
initialValue: 'red'
}, (name) => {
try {
assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(255, 0, 0)');
div.style = `transition: ${name} steps(2, start) 100s; ${name}: blue`;
assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(128, 0, 128)');
} finally {
div.style = '';
}
}, 'Transitioning from initial value');
test_with_at_property({
syntax: '"<color>"',
inherits: false,
initialValue: 'red'
}, (name) => {
try {
div.style = `${name}: blue;`;
assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(0, 0, 255)');
div.style = `transition: ${name} steps(2, start) 100s; ${name}: green`;
assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(0, 64, 128)');
} finally {
div.style = '';
}
}, 'Transitioning from specified value');
test_with_at_property({
syntax: '"<length>"',
inherits: false,
initialValue: '100px'
}, (name) => {
with_style_node(`div { transition: ${name} steps(2, start) 100s; }`, () => {
assert_equals(getComputedStyle(div).getPropertyValue(name), '100px');
// Re-declaring the property with a different initial value effectively
// means the computed value has changed. This means we should transition
// from the old initial value to the new initial value.
with_at_property({
name: name,
syntax: '"<length>"',
inherits: false,
initialValue: '200px'
}, () => {
assert_equals(getComputedStyle(div).getPropertyValue(name), '150px');
});
});
}, 'Transition triggered by initial value change');
test_with_at_property({
syntax: '"<length>"',
inherits: false,
initialValue: '100px'
}, (name) => {
with_style_node(`div { transition: ${name} steps(2, start) 100s; }`, () => {
assert_equals(getComputedStyle(div).getPropertyValue(name), '100px');
with_at_property({
name: name,
syntax: '"<color>"',
inherits: false,
initialValue: 'green'
}, () => {
assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(0, 128, 0)');
});
});
}, 'No transition when changing types');
test(() => {
let name = generate_name();
with_style_node(`div { ${name}: 100px; transition: ${name} steps(2, start) 100s; }`, () => {
assert_equals(getComputedStyle(div).getPropertyValue(name), '100px');
let style1 = document.createElement('style');
style1.textContent = `
@property ${name} {
syntax: "<length>";
inherits: false;
initial-value: 200px;
}
`;
let style2 = document.createElement('style');
style2.textContent = `div { ${name}: 400px; }`;
try {
// Register the property:
document.body.append(style1);
// The token sequence ' 100px' is now interpreted as a length '100px'.
assert_equals(getComputedStyle(div).getPropertyValue(name), '100px');
// Change the computed value:
document.body.append(style2);
// This should cause an interpolation between 100px and 400px:
assert_equals(getComputedStyle(div).getPropertyValue(name), '250px');
// In the middle of the transition above, remove the @property rule
// (making the computed value a token sequence again). We should snap
// to the new token sequence.
style1.remove();
assert_equals(getComputedStyle(div).getPropertyValue(name), '400px');
} finally {
style1.remove();
style2.remove();
}
});
}, 'No transition when removing @property rule');
test(() => {
let name = generate_name();
with_style_node(`div { transition: ${name} steps(2, start) 100s; }`, () => {
let style1 = document.createElement('style');
style1.textContent = `
@property ${name} {
syntax: "<angle>";
inherits: false;
initial-value: -90deg;
}
`;
let style2 = document.createElement('style');
style2.textContent = `div { ${name}: 90deg; }`;
try {
// Register the property:
document.body.append(style1);
// No transition in the beginning.
assert_equals(getComputedStyle(div).getPropertyValue(name), '-90deg');
// Change the computed value:
document.body.append(style2);
// This should cause an interpolation between -90deg and 90deg (for more
// specifically, it is 50% from -90deg to 90deg, with steps(2, start)).
assert_equals(getComputedStyle(div).getPropertyValue(name), '0deg');
// In the middle of the transition above, remove style2, which creates
// a transition which reverses the existing one, from 0deg to -90deg.
// Also, it is 50% from 0deg to -90deg, with steps(2, start).
style2.remove();
assert_equals(getComputedStyle(div).getPropertyValue(name), '-45deg');
} finally {
style1.remove();
style2.remove();
}
});
}, 'Ongoing transition reverts to its initial state');
test_with_at_property({
syntax: '"<length>"',
inherits: false,
initialValue: '0px'
}, (name) => {
with_style_node(`
@keyframes test {
from { ${name}: 100px; }
to { ${name}: 200px; }
}
#div {
animation: test 100s -50s linear;
--unregistered: var(${name});
}
`, () => {
assert_equals(getComputedStyle(div).getPropertyValue('--unregistered'), '150px');
});
}, 'Unregistered properties referencing animated properties update correctly.');
test_with_at_property({
syntax: '"<length>"',
inherits: false,
initialValue: '0px'
}, (name) => {
with_style_node(`
@keyframes test {
from { ${name}: 100px; }
to { ${name}: 200px; }
}
@property --registered {
syntax: "<length>";
inherits: false;
initialValue: 0px;
}
#div {
animation: test 100s -50s linear;
--registered: var(${name});
}
`, () => {
assert_equals(getComputedStyle(div).getPropertyValue('--registered'), '150px');
});
}, 'Registered properties referencing animated properties update correctly.');
test_with_at_property({
syntax: '"<length>"',
inherits: false,
initialValue: '0px'
}, (name) => {
with_style_node(`
@keyframes test {
from { ${name}: inherit; }
to { ${name}: 300px; }
}
#outer {
${name}: 100px;
}
#div {
animation: test 100s -50s linear paused;
}
`, () => {
assert_equals(getComputedStyle(div).getPropertyValue(name), '200px');
outer.style.setProperty(name, '200px');
assert_equals(getComputedStyle(div).getPropertyValue(name), '250px');
outer.style.setProperty(name, '0px');
assert_equals(getComputedStyle(div).getPropertyValue(name), '150px');
outer.style.removeProperty(name);
});
}, 'CSS animation setting "inherit" for a custom property on a keyframe is responsive to changing that custom property on the parent.');
test_with_at_property({
syntax: '"<length>"',
inherits: false,
initialValue: '0px'
}, (name) => {
with_style_node(`
#outer {
${name}: 100px;
}
`, () => {
const animation = div.animate({ [name]: ["inherit", "300px"]}, 1000);
animation.currentTime = 500;
animation.pause();
assert_equals(getComputedStyle(div).getPropertyValue(name), '200px');
outer.style.setProperty(name, '200px');
assert_equals(getComputedStyle(div).getPropertyValue(name), '250px');
outer.style.setProperty(name, '0px');
assert_equals(getComputedStyle(div).getPropertyValue(name), '150px');
outer.style.removeProperty(name);
});
}, 'JS-originated animation setting "inherit" for a custom property on a keyframe is responsive to changing that custom property on the parent.');
test_with_at_property({
syntax: '"<color>"',
inherits: false,
initialValue: 'black'
}, (name) => {
with_style_node(`
@keyframes test {
from { ${name}: currentcolor; }
to { ${name}: rgb(200, 200, 200); }
}
#outer {
color: rgb(100, 100, 100);
}
#div {
animation: test 100s -50s linear paused;
}
`, (style) => {
// possibilities.
assert_in_array(getComputedStyle(div).getPropertyValue(name), [
'color-mix(in srgb, currentcolor, rgb(200, 200, 200))',
'rgb(150, 150, 150)',
]);
outer.style.color = 'rgb(50, 50, 50)';
assert_in_array(getComputedStyle(div).getPropertyValue(name), [
'color-mix(in srgb, currentcolor, rgb(200, 200, 200))',
'rgb(125, 125, 125)',
]);
outer.style.color = 'rgb(150, 150, 150)';
assert_in_array(getComputedStyle(div).getPropertyValue(name), [
'color-mix(in srgb, currentcolor, rgb(200, 200, 200))',
'rgb(175, 175, 175)',
]);
outer.style.removeProperty("color");
});
}, 'CSS animation setting "currentColor" for a custom property on a keyframe is responsive to changing "color" on the parent.');
test_with_at_property({
syntax: '"<color>"',
inherits: false,
initialValue: 'black'
}, (name) => {
with_style_node(`
#outer {
color: rgb(100, 100, 100);
}
`, () => {
const animation = div.animate({ [name]: ["currentcolor", "rgb(200, 200, 200)"]}, 1000);
animation.currentTime = 500;
animation.pause();
assert_in_array(getComputedStyle(div).getPropertyValue(name), [
'color-mix(in srgb, currentcolor, rgb(200, 200, 200))',
'rgb(150, 150, 150)',
]);
outer.style.color = 'rgb(50, 50, 50)';
assert_in_array(getComputedStyle(div).getPropertyValue(name), [
'color-mix(in srgb, currentcolor, rgb(200, 200, 200))',
'rgb(125, 125, 125)',
]);
outer.style.color = 'rgb(150, 150, 150)';
assert_in_array(getComputedStyle(div).getPropertyValue(name), [
'color-mix(in srgb, currentcolor, rgb(200, 200, 200))',
'rgb(175, 175, 175)',
]);
outer.style.removeProperty("color");
});
}, 'JS-originated animation setting "currentColor" for a custom property on a keyframe is responsive to changing "color" on the parent.');
</script>