diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/web-animations/animation-model | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/web-animations/animation-model')
26 files changed, 6755 insertions, 0 deletions
diff --git a/testing/web-platform/tests/web-animations/animation-model/animation-types/accumulation-per-property-001.html b/testing/web-platform/tests/web-animations/animation-model/animation-types/accumulation-per-property-001.html new file mode 100644 index 0000000000..a3fd115563 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/accumulation-per-property-001.html @@ -0,0 +1,24 @@ +<!doctype html> +<meta charset=utf-8> +<title>Accumulation for each property</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#animation-types"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<script src="property-list.js"></script> +<script src="property-types.js"></script> +<script src="property-utils.js"></script> +<style> +html { + font-size: 10px; +} +</style> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function() { + runAnimationTypeTest(gCSSProperties1, 'testAccumulation'); +}, 'Setup'); +</script> diff --git a/testing/web-platform/tests/web-animations/animation-model/animation-types/accumulation-per-property-002.html b/testing/web-platform/tests/web-animations/animation-model/animation-types/accumulation-per-property-002.html new file mode 100644 index 0000000000..5cf411edf6 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/accumulation-per-property-002.html @@ -0,0 +1,24 @@ +<!doctype html> +<meta charset=utf-8> +<title>Accumulation for each property</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#animation-types"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<script src="property-list.js"></script> +<script src="property-types.js"></script> +<script src="property-utils.js"></script> +<style> +html { + font-size: 10px; +} +</style> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function() { + runAnimationTypeTest(gCSSProperties2, 'testAccumulation'); +}, 'Setup'); +</script> diff --git a/testing/web-platform/tests/web-animations/animation-model/animation-types/addition-per-property-001.html b/testing/web-platform/tests/web-animations/animation-model/animation-types/addition-per-property-001.html new file mode 100644 index 0000000000..2fbec2c4cd --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/addition-per-property-001.html @@ -0,0 +1,24 @@ +<!doctype html> +<meta charset=utf-8> +<title>Addition for each property</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#animation-types"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<script src="property-list.js"></script> +<script src="property-types.js"></script> +<script src="property-utils.js"></script> +<style> +html { + font-size: 10px; +} +</style> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function() { + runAnimationTypeTest(gCSSProperties1, 'testAddition'); +}, "Setup"); +</script> diff --git a/testing/web-platform/tests/web-animations/animation-model/animation-types/addition-per-property-002.html b/testing/web-platform/tests/web-animations/animation-model/animation-types/addition-per-property-002.html new file mode 100644 index 0000000000..3b1c40e3c7 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/addition-per-property-002.html @@ -0,0 +1,24 @@ +<!doctype html> +<meta charset=utf-8> +<title>Addition for each property</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#animation-types"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<script src="property-list.js"></script> +<script src="property-types.js"></script> +<script src="property-utils.js"></script> +<style> +html { + font-size: 10px; +} +</style> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function() { + runAnimationTypeTest(gCSSProperties2, 'testAddition'); +}, "Setup"); +</script> diff --git a/testing/web-platform/tests/web-animations/animation-model/animation-types/discrete.html b/testing/web-platform/tests/web-animations/animation-model/animation-types/discrete.html new file mode 100644 index 0000000000..76f42bc7a4 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/discrete.html @@ -0,0 +1,135 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Discrete animation type</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#discrete-animation-type"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(t => { + const div = createDiv(t); + + const anim = div.animate({ fontStyle: [ 'normal', 'italic' ] }, + { duration: 1000, fill: 'forwards' }); + + assert_equals(getComputedStyle(div).fontStyle, 'normal', + 'Animation produces \'from\' value at start of interval'); + anim.currentTime = anim.effect.getComputedTiming().duration / 2 - 1; + assert_equals(getComputedStyle(div).fontStyle, 'normal', + 'Animation produces \'from\' value just before the middle of' + + ' the interval'); + anim.currentTime++; + assert_equals(getComputedStyle(div).fontStyle, 'italic', + 'Animation produces \'to\' value at exact middle of' + + ' the interval'); + anim.finish(); + assert_equals(getComputedStyle(div).fontStyle, 'italic', + 'Animation produces \'to\' value during forwards fill'); +}, 'Test animating discrete values'); + +test(t => { + const div = createDiv(t); + const originalHeight = getComputedStyle(div).height; + + const anim = div.animate({ height: [ 'auto', '200px' ] }, + { duration: 1000, fill: 'forwards' }); + + assert_equals(getComputedStyle(div).height, originalHeight, + 'Animation produces \'from\' value at start of interval'); + anim.currentTime = anim.effect.getComputedTiming().duration / 2 - 1; + assert_equals(getComputedStyle(div).height, originalHeight, + 'Animation produces \'from\' value just before the middle of' + + ' the interval'); + anim.currentTime++; + assert_equals(getComputedStyle(div).height, '200px', + 'Animation produces \'to\' value at exact middle of' + + ' the interval'); + anim.finish(); + assert_equals(getComputedStyle(div).height, '200px', + 'Animation produces \'to\' value during forwards fill'); +}, 'Test discrete animation is used when interpolation fails'); + +test(t => { + const div = createDiv(t); + const originalHeight = getComputedStyle(div).height; + + const anim = div.animate({ height: [ 'auto', + '200px', + '300px', + 'auto', + '400px' ] }, + { duration: 1000, fill: 'forwards' }); + + // There are five values, so there are four pairs to try to interpolate. + // We test at the middle of each pair. + assert_equals(getComputedStyle(div).height, originalHeight, + 'Animation produces \'from\' value at start of interval'); + anim.currentTime = 125; + assert_equals(getComputedStyle(div).height, '200px', + 'First non-interpolable pair uses discrete interpolation'); + anim.currentTime += 250; + assert_equals(getComputedStyle(div).height, '250px', + 'Second interpolable pair uses linear interpolation'); + anim.currentTime += 250; + assert_equals(getComputedStyle(div).height, originalHeight, + 'Third non-interpolable pair uses discrete interpolation'); + anim.currentTime += 250; + assert_equals(getComputedStyle(div).height, '400px', + 'Fourth non-interpolable pair uses discrete interpolation'); +}, 'Test discrete animation is used only for pairs of values that cannot' + + ' be interpolated'); + +test(t => { + const div = createDiv(t); + const originalHeight = getComputedStyle(div).height; + + // Easing: http://cubic-bezier.com/#.68,0,1,.01 + // With this curve, we don't reach the 50% point until about 95% of + // the time has expired. + const anim = div.animate({ fontStyle: [ 'normal', 'italic' ] }, + { duration: 1000, fill: 'forwards', + easing: 'cubic-bezier(0.68,0,1,0.01)' }); + + assert_equals(getComputedStyle(div).fontStyle, 'normal', + 'Animation produces \'from\' value at start of interval'); + anim.currentTime = 940; + assert_equals(getComputedStyle(div).fontStyle, 'normal', + 'Animation produces \'from\' value at 94% of the iteration' + + ' time'); + anim.currentTime = 960; + assert_equals(getComputedStyle(div).fontStyle, 'italic', + 'Animation produces \'to\' value at 96% of the iteration' + + ' time'); +}, 'Test the 50% switch point for discrete animation is based on the' + + ' effect easing'); + +test(t => { + const div = createDiv(t); + const originalHeight = getComputedStyle(div).height; + + // Easing: http://cubic-bezier.com/#.68,0,1,.01 + // With this curve, we don't reach the 50% point until about 95% of + // the time has expired. + const anim = div.animate([ { fontStyle: 'normal', + easing: 'cubic-bezier(0.68,0,1,0.01)' }, + { fontStyle: 'italic' } ], + { duration: 1000, fill: 'forwards' }); + + assert_equals(getComputedStyle(div).fontStyle, 'normal', + 'Animation produces \'from\' value at start of interval'); + anim.currentTime = 940; + assert_equals(getComputedStyle(div).fontStyle, 'normal', + 'Animation produces \'from\' value at 94% of the iteration' + + ' time'); + anim.currentTime = 960; + assert_equals(getComputedStyle(div).fontStyle, 'italic', + 'Animation produces \'to\' value at 96% of the iteration' + + ' time'); +}, 'Test the 50% switch point for discrete animation is based on the' + + ' keyframe easing'); + +</script> diff --git a/testing/web-platform/tests/web-animations/animation-model/animation-types/interpolation-per-property-001.html b/testing/web-platform/tests/web-animations/animation-model/animation-types/interpolation-per-property-001.html new file mode 100644 index 0000000000..97f2822473 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/interpolation-per-property-001.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Interpolation for each property</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#animation-types"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<script src="property-list.js"></script> +<script src="property-types.js"></script> +<script src="property-utils.js"></script> +<style> +html { + font-size: 10px; +} +</style> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function() { + runAnimationTypeTest(gCSSProperties1, 'testInterpolation'); +}, 'Setup'); +</script> diff --git a/testing/web-platform/tests/web-animations/animation-model/animation-types/interpolation-per-property-002.html b/testing/web-platform/tests/web-animations/animation-model/animation-types/interpolation-per-property-002.html new file mode 100644 index 0000000000..9ccc613cfc --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/interpolation-per-property-002.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Interpolation for each property</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#animation-types"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<script src="property-list.js"></script> +<script src="property-types.js"></script> +<script src="property-utils.js"></script> +<style> +html { + font-size: 10px; +} +</style> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(function() { + runAnimationTypeTest(gCSSProperties2, 'testInterpolation'); +}, 'Setup'); +</script> diff --git a/testing/web-platform/tests/web-animations/animation-model/animation-types/property-list.js b/testing/web-platform/tests/web-animations/animation-model/animation-types/property-list.js new file mode 100644 index 0000000000..59866a5897 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/property-list.js @@ -0,0 +1,1564 @@ +'use strict'; + +const gCSSProperties1 = { + 'align-content': { + // https://drafts.csswg.org/css-align/#propdef-align-content + types: [ + { type: 'discrete' , options: [ [ 'flex-start', 'flex-end' ] ] } + ] + }, + 'align-items': { + // https://drafts.csswg.org/css-align/#propdef-align-items + types: [ + { type: 'discrete', options: [ [ 'flex-start', 'flex-end' ] ] } + ] + }, + 'align-self': { + // https://drafts.csswg.org/css-align/#propdef-align-self + types: [ + { type: 'discrete', options: [ [ 'flex-start', 'flex-end' ] ] } + ] + }, + 'backface-visibility': { + // https://drafts.csswg.org/css-transforms/#propdef-backface-visibility + types: [ + { type: 'discrete', options: [ [ 'visible', 'hidden' ] ] } + ] + }, + 'background-attachment': { + // https://drafts.csswg.org/css-backgrounds-3/#background-attachment + types: [ + { type: 'discrete', options: [ [ 'fixed', 'local' ] ] } + ] + }, + 'background-color': { + // https://drafts.csswg.org/css-backgrounds-3/#background-color + types: [ 'color' ] + }, + 'background-blend-mode': { + // https://drafts.fxtf.org/compositing-1/#propdef-background-blend-mode + types: [ + { type: 'discrete', options: [ [ 'multiply', 'screen' ] ] } + ] + }, + 'background-clip': { + // https://drafts.csswg.org/css-backgrounds-3/#background-clip + types: [ + { type: 'discrete', options: [ [ 'padding-box', 'content-box' ] ] } + ] + }, + 'background-image': { + // https://drafts.csswg.org/css-backgrounds-3/#background-image + types: [ + { type: 'discrete', + options: [ [ 'url("http://localhost/test-1")', + 'url("http://localhost/test-2")' ] ] } + ] + }, + 'background-origin': { + // https://drafts.csswg.org/css-backgrounds-3/#background-origin + types: [ + { type: 'discrete', options: [ [ 'padding-box', 'content-box' ] ] } + ] + }, + 'background-position': { + // https://drafts.csswg.org/css-backgrounds-3/#background-position + types: [ + ] + }, + 'background-position-x': { + // https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-x + types: [ + ] + }, + 'background-position-y': { + // https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-y + types: [ + ] + }, + 'background-repeat': { + // https://drafts.csswg.org/css-backgrounds-3/#background-repeat + types: [ + { type: 'discrete', options: [ [ 'space', 'round' ] ] } + ] + }, + 'background-size': { + // https://drafts.csswg.org/css-backgrounds-3/#background-size + types: [ + ] + }, + 'block-size': { + // https://drafts.csswg.org/css-logical-props/#propdef-block-size + types: [ + ] + }, + 'border-block-end-color': { + // https://drafts.csswg.org/css-logical-props/#propdef-border-block-end-color + types: [ + ] + }, + 'border-block-end-style': { + // https://drafts.csswg.org/css-logical-props/#propdef-border-block-end-style + types: [ + ] + }, + 'border-block-end-width': { + // https://drafts.csswg.org/css-logical-props/#propdef-border-block-end-width + types: [ + ] + }, + 'border-block-start-color': { + // https://drafts.csswg.org/css-logical-props/#propdef-border-block-start-color + types: [ + ] + }, + 'border-block-start-style': { + // https://drafts.csswg.org/css-logical-props/#propdef-border-block-start-style + types: [ + ] + }, + 'border-block-start-width': { + // https://drafts.csswg.org/css-logical-props/#propdef-border-block-start-width + types: [ + ] + }, + 'border-bottom-color': { + // https://drafts.csswg.org/css-backgrounds-3/#border-bottom-color + types: [ 'color' ] + }, + 'border-bottom-left-radius': { + // https://drafts.csswg.org/css-backgrounds-3/#border-bottom-left-radius + types: [ + ] + }, + 'border-bottom-right-radius': { + // https://drafts.csswg.org/css-backgrounds-3/#border-bottom-right-radius + types: [ + ] + }, + 'border-bottom-style': { + // https://drafts.csswg.org/css-backgrounds-3/#border-bottom-style + types: [ + { type: 'discrete', options: [ [ 'dotted', 'solid' ] ] } + ] + }, + 'border-bottom-width': { + // https://drafts.csswg.org/css-backgrounds-3/#border-bottom-width + types: [ 'length' ], + setup: t => { + const element = createElement(t); + element.style.borderBottomStyle = 'solid'; + return element; + } + }, + 'border-collapse': { + // https://drafts.csswg.org/css-tables/#propdef-border-collapse + types: [ + { type: 'discrete', options: [ [ 'collapse', 'separate' ] ] } + ] + }, + 'border-inline-end-color': { + // https://drafts.csswg.org/css-logical-props/#propdef-border-inline-end-color + types: [ + ] + }, + 'border-inline-end-style': { + // https://drafts.csswg.org/css-logical-props/#propdef-border-inline-end-style + types: [ + ] + }, + 'border-inline-end-width': { + // https://drafts.csswg.org/css-logical-props/#propdef-border-inline-end-width + types: [ + ] + }, + 'border-inline-start-color': { + // https://drafts.csswg.org/css-logical-props/#propdef-border-inline-start-color + types: [ + ] + }, + 'border-inline-start-style': { + // https://drafts.csswg.org/css-logical-props/#propdef-border-block-start-style + types: [ + ] + }, + 'border-inline-start-width': { + // https://drafts.csswg.org/css-logical-props/#propdef-border-inline-start-width + types: [ + ] + }, + 'border-image-outset': { + // https://drafts.csswg.org/css-backgrounds-3/#border-image-outset + types: [ + ] + }, + 'border-image-repeat': { + // https://drafts.csswg.org/css-backgrounds-3/#border-image-repeat + types: [ + { type: 'discrete', options: [ [ 'stretch repeat', 'round space' ] ] } + ] + }, + 'border-image-slice': { + // https://drafts.csswg.org/css-backgrounds-3/#border-image-slice + types: [ + ] + }, + 'border-image-source': { + // https://drafts.csswg.org/css-backgrounds-3/#border-image-source + types: [ + { type: 'discrete', + options: [ [ 'url("http://localhost/test-1")', + 'url("http://localhost/test-2")' ] ] } + ] + }, + 'border-image-width': { + // https://drafts.csswg.org/css-backgrounds-3/#border-image-width + types: [ + ] + }, + 'border-left-color': { + // https://drafts.csswg.org/css-backgrounds-3/#border-left-color + types: [ 'color' ] + }, + 'border-left-style': { + // https://drafts.csswg.org/css-backgrounds-3/#border-left-style + types: [ + { type: 'discrete', options: [ [ 'dotted', 'solid' ] ] } + ] + }, + 'border-left-width': { + // https://drafts.csswg.org/css-backgrounds-3/#border-left-width + types: [ 'length' ], + setup: t => { + const element = createElement(t); + element.style.borderLeftStyle = 'solid'; + return element; + } + }, + 'border-right-color': { + // https://drafts.csswg.org/css-backgrounds-3/#border-right-color + types: [ 'color' ] + }, + 'border-right-style': { + // https://drafts.csswg.org/css-backgrounds-3/#border-right-style + types: [ + { type: 'discrete', options: [ [ 'dotted', 'solid' ] ] } + ] + }, + 'border-right-width': { + // https://drafts.csswg.org/css-backgrounds-3/#border-right-width + types: [ 'length' ], + setup: t => { + const element = createElement(t); + element.style.borderRightStyle = 'solid'; + return element; + } + }, + 'border-spacing': { + // https://drafts.csswg.org/css-tables/#propdef-border-spacing + types: [ 'lengthPair' ] + }, + 'border-top-color': { + // https://drafts.csswg.org/css-backgrounds-3/#border-top-color + types: [ 'color' ] + }, + 'border-top-left-radius': { + // https://drafts.csswg.org/css-backgrounds-3/#border-top-left-radius + types: [ + ] + }, + 'border-top-right-radius': { + // https://drafts.csswg.org/css-backgrounds-3/#border-top-right-radius + types: [ + ] + }, + 'border-top-style': { + // https://drafts.csswg.org/css-backgrounds-3/#border-top-style + types: [ + { type: 'discrete', options: [ [ 'dotted', 'solid' ] ] } + ] + }, + 'border-top-width': { + // https://drafts.csswg.org/css-backgrounds-3/#border-top-width + types: [ 'length' ], + setup: t => { + const element = createElement(t); + element.style.borderTopStyle = 'solid'; + return element; + } + }, + 'bottom': { + // https://drafts.csswg.org/css-position/#propdef-bottom + types: [ + ] + }, + 'box-decoration-break': { + // https://drafts.csswg.org/css-break/#propdef-box-decoration-break + types: [ + { type: 'discrete', options: [ [ 'slice', 'clone' ] ] } + ] + }, + 'box-shadow': { + // https://drafts.csswg.org/css-backgrounds/#box-shadow + types: [ 'boxShadowList' ], + }, + 'box-sizing': { + // https://drafts.csswg.org/css-ui-4/#box-sizing + types: [ + { type: 'discrete', options: [ [ 'content-box', 'border-box' ] ] } + ] + }, + 'caption-side': { + // https://drafts.csswg.org/css-tables/#propdef-caption-side + types: [ + { type: 'discrete', options: [ [ 'top', 'bottom' ] ] } + ] + }, + 'caret-color': { + // https://drafts.csswg.org/css-ui/#propdef-caret-color + types: [ 'color' ] + }, + 'clear': { + // https://drafts.csswg.org/css-page-floats/#propdef-clear + types: [ + { type: 'discrete', options: [ [ 'left', 'right' ] ] } + ] + }, + 'clip': { + // https://drafts.fxtf.org/css-masking-1/#propdef-clip + types: [ + 'rect', + { type: 'discrete', options: [ [ 'rect(10px, 10px, 10px, 10px)', + 'auto' ], + [ 'rect(10px, 10px, 10px, 10px)', + 'rect(10px, 10px, 10px, auto)'] ] } + ] + }, + 'clip-path': { + // https://drafts.fxtf.org/css-masking-1/#propdef-clip-path + types: [ + ] + }, + 'clip-rule': { + // https://drafts.fxtf.org/css-masking-1/#propdef-clip-rule + types: [ + { type: 'discrete', options: [ [ 'evenodd', 'nonzero' ] ] } + ] + }, + 'color': { + // https://drafts.csswg.org/css-color/#propdef-color + types: [ 'color' ] + }, + 'color-adjust': { + // https://drafts.csswg.org/css-color-4/#color-adjust + types: [ + { type: 'discrete', options: [ [ 'economy', 'exact' ] ] } + ] + }, + 'color-interpolation': { + // https://svgwg.org/svg2-draft/painting.html#ColorInterpolationProperty + types: [ + { type: 'discrete', options: [ [ 'linearrgb', 'auto' ] ] } + ] + }, + 'color-interpolation-filters': { + // https://drafts.fxtf.org/filters-1/#propdef-color-interpolation-filters + types: [ + { type: 'discrete', options: [ [ 'srgb', 'linearrgb' ] ] } + ] + }, + 'column-count': { + // https://drafts.csswg.org/css-multicol/#propdef-column-count + types: [ 'positiveInteger', + { type: 'discrete', options: [ [ 'auto', '10' ] ] } + ] + }, + 'column-gap': { + // https://drafts.csswg.org/css-multicol/#propdef-column-gap + types: [ 'length', + { type: 'discrete', options: [ [ 'normal', '200px' ] ] } + ] + }, + 'column-rule-color': { + // https://drafts.csswg.org/css-multicol/#propdef-column-rule-color + types: [ 'color' ] + }, + 'column-fill': { + // https://drafts.csswg.org/css-multicol/#propdef-column-fill + types: [ + { type: 'discrete', options: [ [ 'auto', 'balance' ] ] } + ] + }, + 'column-rule-style': { + // https://drafts.csswg.org/css-multicol/#propdef-column-rule-style + types: [ + { type: 'discrete', options: [ [ 'none', 'dotted' ] ] } + ] + }, + 'column-rule-width': { + // https://drafts.csswg.org/css-multicol/#propdef-column-rule-width + types: [ 'length' ], + setup: t => { + const element = createElement(t); + element.style.columnRuleStyle = 'solid'; + return element; + } + }, + 'column-width': { + // https://drafts.csswg.org/css-multicol/#propdef-column-width + types: [ 'length', + { type: 'discrete', options: [ [ 'auto', '1px' ] ] } + ] + }, + 'counter-increment': { + // https://drafts.csswg.org/css-lists-3/#propdef-counter-increment + types: [ + { type: 'discrete', options: [ [ 'ident-1 1', 'ident-2 2' ] ] } + ] + }, + 'counter-reset': { + // https://drafts.csswg.org/css-lists-3/#propdef-counter-reset + types: [ + { type: 'discrete', options: [ [ 'ident-1 1', 'ident-2 2' ] ] } + ] + }, + 'cursor': { + // https://drafts.csswg.org/css2/ui.html#propdef-cursor + types: [ + { type: 'discrete', options: [ [ 'pointer', 'wait' ] ] } + ] + }, + 'dominant-baseline': { + // https://drafts.csswg.org/css-inline/#propdef-dominant-baseline + types: [ + { type: 'discrete', options: [ [ 'ideographic', 'alphabetic' ] ] } + ] + }, + 'empty-cells': { + // https://drafts.csswg.org/css-tables/#propdef-empty-cells + types: [ + { type: 'discrete', options: [ [ 'show', 'hide' ] ] } + ] + }, + 'fill': { + // https://svgwg.org/svg2-draft/painting.html#FillProperty + types: [ + ] + }, + 'fill-opacity': { + // https://svgwg.org/svg2-draft/painting.html#FillOpacityProperty + types: [ 'opacity' ] + }, + 'fill-rule': { + // https://svgwg.org/svg2-draft/painting.html#FillRuleProperty + types: [ + { type: 'discrete', options: [ [ 'evenodd', 'nonzero' ] ] } + ] + }, + 'filter': { + // https://drafts.fxtf.org/filters/#propdef-filter + types: [ 'filterList' ] + }, + 'flex-basis': { + // https://drafts.csswg.org/css-flexbox/#propdef-flex-basis + types: [ + 'lengthPercentageOrCalc', + { type: 'discrete', options: [ [ 'auto', '10px' ] ] } + ] + }, + 'flex-direction': { + // https://drafts.csswg.org/css-flexbox/#propdef-flex-direction + types: [ + { type: 'discrete', options: [ [ 'row', 'row-reverse' ] ] } + ] + }, + 'flex-grow': { + // https://drafts.csswg.org/css-flexbox/#flex-grow-property + types: [ 'positiveNumber' ] + }, + 'flex-shrink': { + // https://drafts.csswg.org/css-flexbox/#propdef-flex-shrink + types: [ 'positiveNumber' ] + }, + 'flex-wrap': { + // https://drafts.csswg.org/css-flexbox/#propdef-flex-wrap + types: [ + { type: 'discrete', options: [ [ 'nowrap', 'wrap' ] ] } + ] + }, + 'flood-color': { + // https://drafts.fxtf.org/filters/#FloodColorProperty + types: [ 'color' ] + }, + 'flood-opacity': { + // https://drafts.fxtf.org/filters/#propdef-flood-opacity + types: [ 'opacity' ] + }, + 'font-size': { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-size + types: [ + ] + }, + 'font-size-adjust': { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-size-adjust + types: [ + ] + }, + 'font-stretch': { + // https://drafts.csswg.org/css-fonts-4/#propdef-font-stretch + types: [ 'percentage' ] + }, + 'font-style': { + // https://drafts.csswg.org/css-fonts/#propdef-font-style + types: [ + { type: 'discrete', options: [ [ 'italic', 'oblique' ] ] } + ] + }, + 'float': { + // https://drafts.csswg.org/css-page-floats/#propdef-float + types: [ + { type: 'discrete', options: [ [ 'left', 'right' ] ] } + ] + }, + 'font-family': { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-family + types: [ + { type: 'discrete', options: [ [ 'helvetica', 'verdana' ] ] } + ] + }, + 'font-feature-settings': { + // https://drafts.csswg.org/css-fonts/#descdef-font-feature-settings + types: [ + { type: 'discrete', options: [ [ '"liga" 5', 'normal' ] ] } + ] + }, + 'font-kerning': { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-kerning + types: [ + { type: 'discrete', options: [ [ 'auto', 'normal' ] ] } + ] + }, + 'font-language-override': { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-language-override + types: [ + { type: 'discrete', options: [ [ '"eng"', 'normal' ] ] } + ] + }, + 'font-style': { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-style + types: [ + { type: 'discrete', options: [ [ 'italic', 'oblique' ] ] } + ] + }, + 'font-synthesis': { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-synthesis + types: [ + { type: 'discrete', options: [ [ 'none', 'weight style' ] ] } + ] + }, + 'font-variant-alternates': { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-alternates + types: [ + { type: 'discrete', + options: [ [ 'swash(unknown)', 'stylistic(unknown)' ] ] } + ] + }, + 'font-variant-caps': { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-caps + types: [ + { type: 'discrete', options: [ [ 'small-caps', 'unicase' ] ] } + ] + }, + 'font-variant-east-asian': { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-east-asian + types: [ + { type: 'discrete', options: [ [ 'full-width', 'proportional-width' ] ] } + ] + }, + 'font-variant-ligatures': { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-ligatures + types: [ + { type: 'discrete', + options: [ [ 'common-ligatures', 'no-common-ligatures' ] ] } + ] + }, + 'font-variant-numeric': { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-numeric + types: [ + { type: 'discrete', options: [ [ 'lining-nums', 'oldstyle-nums' ] ] } + ] + }, + 'font-variant-position': { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-position + types: [ + { type: 'discrete', options: [ [ 'sub', 'super' ] ] } + ] + }, + 'font-variation-settings': { + // https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-variation-settings + types: [ + 'fontVariationSettings', + { type: 'discrete', + options: [ ['"wdth" 1, "wght" 1.1', '"wdth" 5'], + ['"wdth" 5', 'normal'] + ] }, + ] + }, + 'font-weight': { + // https://drafts.csswg.org/css-fonts-3/#propdef-font-weight + types: [ + ] + }, + 'grid-auto-columns': { + // https://drafts.csswg.org/css-grid/#propdef-grid-auto-columns + types: [ + { type: 'discrete', options: [ [ '1px', '5px' ] ] } + ] + }, + 'grid-auto-flow': { + // https://drafts.csswg.org/css-grid/#propdef-grid-auto-flow + types: [ + { type: 'discrete', options: [ [ 'row', 'column' ] ] } + ] + }, + 'grid-auto-rows': { + // https://drafts.csswg.org/css-grid/#propdef-grid-auto-rows + types: [ + { type: 'discrete', options: [ [ '1px', '5px' ] ] } + ] + }, + 'grid-column-end': { + // https://drafts.csswg.org/css-grid/#propdef-grid-column-end + types: [ + { type: 'discrete', options: [ [ '1', '5' ] ] } + ] + }, + 'grid-column-gap': { + // https://drafts.csswg.org/css-grid/#propdef-grid-column-gap + types: [ + ] + }, + 'grid-column-start': { + // https://drafts.csswg.org/css-grid/#propdef-grid-column-start + types: [ + { type: 'discrete', options: [ [ '1', '5' ] ] } + ] + }, + 'grid-row-end': { + // https://drafts.csswg.org/css-grid/#propdef-grid-row-end + types: [ + { type: 'discrete', options: [ [ '1', '5' ] ] } + ] + }, + 'grid-row-gap': { + // https://drafts.csswg.org/css-grid/#propdef-grid-row-gap + types: [ + ] + }, + 'grid-row-start': { + // https://drafts.csswg.org/css-grid/#propdef-grid-row-start + types: [ + { type: 'discrete', options: [ [ '1', '5' ] ] } + ] + }, + 'grid-template-areas': { + // https://drafts.csswg.org/css-template/#grid-template-areas + types: [ + { type: 'discrete', options: [ [ '". . a b" ". .a b"', 'none' ] ] } + ] + }, + 'height': { + // https://drafts.csswg.org/css21/visudet.html#propdef-height + types: [ + ] + }, + 'hyphens': { + // https://drafts.csswg.org/css-text-3/#propdef-hyphens + types: [ + { type: 'discrete', options: [ [ 'manual', 'none' ] ] } + ] + }, + 'image-orientation': { + // https://drafts.csswg.org/css-images-3/#propdef-image-orientation + types: [ + { type: 'discrete', options: [ [ 'none', 'from-image' ] ] } + ] + }, + 'image-rendering': { + // https://drafts.csswg.org/css-images-3/#propdef-image-rendering + types: [ + ] + }, + 'ime-mode': { + // https://drafts.csswg.org/css-ui/#input-method-editor + types: [ + { type: 'discrete', options: [ [ 'disabled', 'auto' ] ] } + ] + }, + 'initial-letter': { + // https://drafts.csswg.org/css-inline/#propdef-initial-letter + types: [ + { type: 'discrete', options: [ [ '1 2', '3 4' ] ] } + ] + }, +}; + +const gCSSProperties2 = { + 'inline-size': { + // https://drafts.csswg.org/css-logical-props/#propdef-inline-size + types: [ + ] + }, + 'isolation': { + // https://drafts.fxtf.org/compositing-1/#propdef-isolation + types: [ + { type: 'discrete', options: [ [ 'auto', 'isolate' ] ] } + ] + }, + 'justify-content': { + // https://drafts.csswg.org/css-align/#propdef-justify-content + types: [ + { type: 'discrete', options: [ [ 'start', 'end' ] ] } + ] + }, + 'justify-items': { + // https://drafts.csswg.org/css-align/#propdef-justify-items + types: [ + { type: 'discrete', options: [ [ 'start', 'end' ] ] } + ] + }, + 'justify-self': { + // https://drafts.csswg.org/css-align/#propdef-justify-self + types: [ + { type: 'discrete', options: [ [ 'start', 'end' ] ] } + ] + }, + 'left': { + // https://drafts.csswg.org/css-position/#propdef-left + types: [ + ] + }, + 'letter-spacing': { + // https://drafts.csswg.org/css-text-3/#propdef-letter-spacing + types: [ 'length' ] + }, + 'lighting-color': { + // https://drafts.fxtf.org/filters/#LightingColorProperty + types: [ 'color' ] + }, + 'line-height': { + // https://drafts.csswg.org/css21/visudet.html#propdef-line-height + types: [ + ] + }, + 'list-style-image': { + // https://drafts.csswg.org/css-lists-3/#propdef-list-style-image + types: [ + { type: 'discrete', + options: [ [ 'url("http://localhost/test-1")', + 'url("http://localhost/test-2")' ] ] } + ] + }, + 'list-style-position': { + // https://drafts.csswg.org/css-lists-3/#propdef-list-style-position + types: [ + { type: 'discrete', options: [ [ 'inside', 'outside' ] ] } + ] + }, + 'list-style-type': { + // https://drafts.csswg.org/css-lists-3/#propdef-list-style-type + types: [ + { type: 'discrete', options: [ [ 'circle', 'square' ] ] } + ] + }, + 'margin-block-end': { + // https://drafts.csswg.org/css-logical-props/#propdef-margin-block-end + types: [ + ] + }, + 'margin-block-start': { + // https://drafts.csswg.org/css-logical-props/#propdef-margin-block-start + types: [ + ] + }, + 'margin-bottom': { + // https://drafts.csswg.org/css-box/#propdef-margin-bottom + types: [ + ] + }, + 'margin-inline-end': { + // https://drafts.csswg.org/css-logical-props/#propdef-margin-inline-end + types: [ + ] + }, + 'margin-inline-start': { + // https://drafts.csswg.org/css-logical-props/#propdef-margin-inline-start + types: [ + ] + }, + 'margin-left': { + // https://drafts.csswg.org/css-box/#propdef-margin-left + types: [ + ] + }, + 'margin-right': { + // https://drafts.csswg.org/css-box/#propdef-margin-right + types: [ + ] + }, + 'margin-top': { + // https://drafts.csswg.org/css-box/#propdef-margin-top + types: [ + ] + }, + 'marker-end': { + // https://svgwg.org/specs/markers/#MarkerEndProperty + types: [ + { type: 'discrete', + options: [ [ 'url("http://localhost/test-1")', + 'url("http://localhost/test-2")' ] ] } + ] + }, + 'marker-mid': { + // https://svgwg.org/specs/markers/#MarkerMidProperty + types: [ + { type: 'discrete', + options: [ [ 'url("http://localhost/test-1")', + 'url("http://localhost/test-2")' ] ] } + ] + }, + 'marker-start': { + // https://svgwg.org/specs/markers/#MarkerStartProperty + types: [ + { type: 'discrete', + options: [ [ 'url("http://localhost/test-1")', + 'url("http://localhost/test-2")' ] ] } + ] + }, + 'mask': { + // https://drafts.fxtf.org/css-masking-1/#the-mask + types: [ + { type: 'discrete', + options: [ [ 'url("http://localhost/test-1")', + 'url("http://localhost/test-2")' ] ] } + ] + }, + 'mask-clip': { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-clip + types: [ + { type: 'discrete', options: [ [ 'content-box', 'border-box' ] ] } + ] + }, + 'mask-composite': { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-composite + types: [ + { type: 'discrete', options: [ [ 'add', 'subtract' ] ] } + ] + }, + 'mask-image': { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-image + types: [ + { type: 'discrete', + options: [ [ 'url("http://localhost/test-1")', + 'url("http://localhost/test-2")' ] ] } + ] + }, + 'mask-mode': { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-mode + types: [ + { type: 'discrete', options: [ [ 'alpha', 'luminance' ] ] } + ] + }, + 'mask-origin': { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-origin + types: [ + { type: 'discrete', options: [ [ 'content-box', 'border-box' ] ] } + ] + }, + 'mask-position': { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-position + types: [ + ] + }, + 'mask-position-x': { + // https://lists.w3.org/Archives/Public/www-style/2014Jun/0166.html + types: [ + ] + }, + 'mask-position-y': { + // https://lists.w3.org/Archives/Public/www-style/2014Jun/0166.html + types: [ + ] + }, + 'mask-repeat': { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-repeat + types: [ + { type: 'discrete', options: [ [ 'space', 'round' ] ] } + ] + }, + 'mask-size': { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-size + types: [ + ] + }, + 'mask-type': { + // https://drafts.fxtf.org/css-masking-1/#propdef-mask-type + types: [ + { type: 'discrete', options: [ [ 'alpha', 'luminance' ] ] } + ] + }, + 'max-block-size': { + // https://drafts.csswg.org/css-logical-props/#propdef-max-block-size + types: [ + ] + }, + 'max-height': { + // https://drafts.csswg.org/css21/visudet.html#propdef-max-height + types: [ + ] + }, + 'max-inline-size': { + // https://drafts.csswg.org/css-logical-props/#propdef-max-inline-size + types: [ + ] + }, + 'max-width': { + // https://drafts.csswg.org/css21/visudet.html#propdef-max-width + types: [ + ] + }, + 'min-block-size': { + // https://drafts.csswg.org/css-logical-props/#propdef-min-block-size + types: [ + ] + }, + 'min-height': { + // https://drafts.csswg.org/css21/visudet.html#propdef-min-height + types: [ + ] + }, + 'min-inline-size': { + // https://drafts.csswg.org/css-logical-props/#propdef-min-inline-size + types: [ + ] + }, + 'min-width': { + // https://drafts.csswg.org/css21/visudet.html#propdef-min-width + types: [ + ] + }, + 'mix-blend-mode': { + // https://drafts.fxtf.org/compositing-1/#propdef-mix-blend-mode + types: [ + { type: 'discrete', options: [ [ 'multiply', 'screen' ] ] } + ] + }, + 'object-fit': { + // https://drafts.csswg.org/css-images-3/#propdef-object-fit + types: [ + { type: 'discrete', options: [ [ 'fill', 'contain' ] ] } + ] + }, + 'object-position': { + // https://drafts.csswg.org/css-images-3/#propdef-object-position + types: [ + ] + }, + 'inset-block-end': { + // https://drafts.csswg.org/css-logical-props/#propdef-inset-block-end + types: [ + ] + }, + 'inset-block-start': { + // https://drafts.csswg.org/css-logical-props/#propdef-inset-block-start + types: [ + ] + }, + 'inset-inline-end': { + // https://drafts.csswg.org/css-logical-props/#propdef-inset-inline-end + types: [ + ] + }, + 'inset-inline-start': { + // https://drafts.csswg.org/css-logical-props/#propdef-inset-inline-start + types: [ + ] + }, + 'offset-distance': { + // https://drafts.fxtf.org/motion-1/#offset-distance-property + types: [ 'lengthPercentageOrCalc' ] + }, + 'offset-path': { + // https://drafts.fxtf.org/motion-1/#offset-path-property + types: [ + ] + }, + 'opacity': { + // https://drafts.csswg.org/css-color/#propdef-opacity + types: [ + ] + }, + 'order': { + // https://drafts.csswg.org/css-flexbox/#propdef-order + types: [ 'integer' ] + }, + 'outline-color': { + // https://drafts.csswg.org/css-ui-3/#propdef-outline-color + types: [ 'color' ] + }, + 'outline-offset': { + // https://drafts.csswg.org/css-ui-3/#propdef-outline-offset + types: [ 'length' ] + }, + 'outline-style': { + // https://drafts.csswg.org/css-ui/#propdef-outline-style + types: [ + { type: 'discrete', options: [ [ 'none', 'dotted' ] ] } + ] + }, + 'outline-width': { + // https://drafts.csswg.org/css-ui-3/#propdef-outline-width + types: [ 'length' ], + setup: t => { + const element = createElement(t); + element.style.outlineStyle = 'solid'; + return element; + } + }, + 'overflow': { + // https://drafts.csswg.org/css-overflow/#propdef-overflow + types: [ + ] + }, + 'overflow-wrap': { + // https://drafts.csswg.org/css-text-3/#propdef-overflow-wrap + types: [ + { type: 'discrete', options: [ [ 'normal', 'break-word' ] ] } + ] + }, + 'overflow-x': { + // https://drafts.csswg.org/css-overflow-3/#propdef-overflow-x + types: [ + { type: 'discrete', options: [ [ 'visible', 'hidden' ] ] } + ] + }, + 'overflow-y': { + // https://drafts.csswg.org/css-overflow-3/#propdef-overflow-y + types: [ + { type: 'discrete', options: [ [ 'visible', 'hidden' ] ] } + ] + }, + 'padding-block-end': { + // https://drafts.csswg.org/css-logical-props/#propdef-padding-block-end + types: [ + ] + }, + 'padding-block-start': { + // https://drafts.csswg.org/css-logical-props/#propdef-padding-block-start + types: [ + ] + }, + 'padding-bottom': { + // https://drafts.csswg.org/css-box/#propdef-padding-bottom + types: [ + ] + }, + 'padding-inline-end': { + // https://drafts.csswg.org/css-logical-props/#propdef-padding-inline-end + types: [ + ] + }, + 'padding-inline-start': { + // https://drafts.csswg.org/css-logical-props/#propdef-padding-inline-start + types: [ + ] + }, + 'padding-left': { + // https://drafts.csswg.org/css-box/#propdef-padding-left + types: [ + ] + }, + 'padding-right': { + // https://drafts.csswg.org/css-box/#propdef-padding-right + types: [ + ] + }, + 'padding-top': { + // https://drafts.csswg.org/css-box/#propdef-padding-top + types: [ + ] + }, + 'page-break-after': { + // https://drafts.csswg.org/css-break-3/#propdef-break-after + types: [ + { type: 'discrete', options: [ [ 'always', 'auto' ] ] } + ] + }, + 'page-break-before': { + // https://drafts.csswg.org/css-break-3/#propdef-break-before + types: [ + { type: 'discrete', options: [ [ 'always', 'auto' ] ] } + ] + }, + 'page-break-inside': { + // https://drafts.csswg.org/css-break-3/#propdef-break-inside + types: [ + { type: 'discrete', options: [ [ 'auto', 'avoid' ] ] } + ] + }, + 'paint-order': { + // https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty + types: [ + { type: 'discrete', options: [ [ 'fill', 'stroke' ] ] } + ] + }, + 'perspective': { + // https://drafts.csswg.org/css-transforms-1/#propdef-perspective + types: [ 'length' ] + }, + 'perspective-origin': { + // https://drafts.csswg.org/css-transforms-1/#propdef-perspective-origin + types: [ 'position' ] + }, + 'pointer-events': { + // https://svgwg.org/svg2-draft/interact.html#PointerEventsProperty + types: [ + { type: 'discrete', options: [ [ 'fill', 'none' ] ] } + ] + }, + 'position': { + // https://drafts.csswg.org/css-position/#propdef-position + types: [ + { type: 'discrete', options: [ [ 'absolute', 'fixed' ] ] } + ] + }, + 'quotes': { + // https://drafts.csswg.org/css-content-3/#propdef-quotes + types: [ + { type: 'discrete', options: [ [ '"“" "”" "‘" "’"', '"‘" "’" "“" "”"' ] ] } + ] + }, + 'resize': { + // https://drafts.csswg.org/css-ui/#propdef-resize + types: [ + { type: 'discrete', options: [ [ 'both', 'horizontal' ] ] } + ] + }, + 'right': { + // https://drafts.csswg.org/css-position/#propdef-right + types: [ + ] + }, + 'ruby-align': { + // https://drafts.csswg.org/css-ruby-1/#propdef-ruby-align + types: [ + { type: 'discrete', options: [ [ 'start', 'center' ] ] } + ] + }, + 'ruby-position': { + // https://drafts.csswg.org/css-ruby-1/#propdef-ruby-position + types: [ + { type: 'discrete', options: [ [ 'under', 'over' ] ] } + ], + setup: t => { + return createElement(t, 'ruby'); + } + }, + 'scroll-behavior': { + // https://drafts.csswg.org/cssom-view/#propdef-scroll-behavior + types: [ + { type: 'discrete', options: [ [ 'auto', 'smooth' ] ] } + ] + }, + 'shape-outside': { + // http://dev.w3.org/csswg/css-shapes/#propdef-shape-outside + types: [ + { type: 'discrete', + options: [ [ 'url("http://localhost/test-1")', + 'url("http://localhost/test-2")' ] ] } + ] + }, + 'shape-rendering': { + // https://svgwg.org/svg2-draft/painting.html#ShapeRenderingProperty + types: [ + { type: 'discrete', options: [ [ 'optimizeSpeed', 'crispEdges' ] ] } + ] + }, + 'stop-color': { + // https://svgwg.org/svg2-draft/pservers.html#StopColorProperty + types: [ 'color' ] + }, + 'stop-opacity': { + // https://svgwg.org/svg2-draft/pservers.html#StopOpacityProperty + types: [ 'opacity' ] + }, + 'stroke': { + // https://svgwg.org/svg2-draft/painting.html#StrokeProperty + types: [ + ] + }, + 'stroke-dasharray': { + // https://svgwg.org/svg2-draft/painting.html#StrokeDasharrayProperty + types: [ + 'dasharray', + { type: 'discrete', options: [ [ 'none', '10px, 20px' ] ] } + ] + }, + 'stroke-dashoffset': { + // https://svgwg.org/svg2-draft/painting.html#StrokeDashoffsetProperty + types: [ + ] + }, + 'stroke-linecap': { + // https://svgwg.org/svg2-draft/painting.html#StrokeLinecapProperty + types: [ + { type: 'discrete', options: [ [ 'round', 'square' ] ] } + ] + }, + 'stroke-linejoin': { + // https://svgwg.org/svg2-draft/painting.html#StrokeLinejoinProperty + types: [ + { type: 'discrete', options: [ [ 'round', 'miter' ] ] } + ], + setup: t => { + return createElement(t, 'rect'); + } + }, + 'stroke-miterlimit': { + // https://svgwg.org/svg2-draft/painting.html#StrokeMiterlimitProperty + types: [ 'positiveNumber' ] + }, + 'stroke-opacity': { + // https://svgwg.org/svg2-draft/painting.html#StrokeOpacityProperty + types: [ 'opacity' ] + }, + 'stroke-width': { + // https://svgwg.org/svg2-draft/painting.html#StrokeWidthProperty + types: [ + ] + }, + 'table-layout': { + // https://drafts.csswg.org/css-tables/#propdef-table-layout + types: [ + { type: 'discrete', options: [ [ 'auto', 'fixed' ] ] } + ] + }, + 'text-align': { + // https://drafts.csswg.org/css-text-3/#propdef-text-align + types: [ + { type: 'discrete', options: [ [ 'start', 'end' ] ] } + ] + }, + 'text-align-last': { + // https://drafts.csswg.org/css-text-3/#propdef-text-align-last + types: [ + { type: 'discrete', options: [ [ 'start', 'end' ] ] } + ] + }, + 'text-anchor': { + // https://svgwg.org/svg2-draft/text.html#TextAnchorProperty + types: [ + { type: 'discrete', options: [ [ 'middle', 'end' ] ] } + ] + }, + 'text-decoration-color': { + // https://drafts.csswg.org/css-text-decor-3/#propdef-text-decoration-color + types: [ 'color' ] + }, + 'text-decoration-line': { + // https://drafts.csswg.org/css-text-decor-3/#propdef-text-decoration-line + types: [ + { type: 'discrete', options: [ [ 'underline', 'overline' ] ] } + ] + }, + 'text-decoration-style': { + // http://dev.w3.org/csswg/css-text-decor-3/#propdef-text-decoration-style + types: [ + { type: 'discrete', options: [ [ 'solid', 'dotted' ] ] } + ] + }, + 'text-emphasis-color': { + // https://drafts.csswg.org/css-text-decor-3/#propdef-text-emphasis-color + types: [ 'color' ] + }, + 'text-emphasis-position': { + // http://dev.w3.org/csswg/css-text-decor-3/#propdef-text-emphasis-position + types: [ + { type: 'discrete', options: [ [ 'over', 'under left' ] ] } + ] + }, + 'text-emphasis-style': { + // http://dev.w3.org/csswg/css-text-decor-3/#propdef-text-emphasis-style + types: [ + { type: 'discrete', options: [ [ 'circle', 'open dot' ] ] } + ] + }, + 'text-group-align': { + // https://drafts.csswg.org/css-text-4/#propdef-text-group-align + types: [ + { type: 'discrete', options: [ [ 'none', 'center' ] ] } + ] + }, + 'text-indent': { + // https://drafts.csswg.org/css-text-3/#propdef-text-indent + types: [ + ] + }, + 'text-overflow': { + // https://drafts.csswg.org/css-ui/#propdef-text-overflow + types: [ + { type: 'discrete', options: [ [ 'clip', 'ellipsis' ] ] } + ] + }, + 'text-rendering': { + // https://svgwg.org/svg2-draft/painting.html#TextRenderingProperty + types: [ + { type: 'discrete', options: [ [ 'optimizeSpeed', 'optimizeLegibility' ] ] } + ] + }, + 'text-shadow': { + // https://drafts.csswg.org/css-text-decor-3/#propdef-text-shadow + types: [ 'textShadowList' ], + setup: t => { + const element = createElement(t); + element.style.color = 'green'; + return element; + } + }, + 'text-transform': { + // https://drafts.csswg.org/css-text-3/#propdef-text-transform + types: [ + { type: 'discrete', options: [ [ 'capitalize', 'uppercase' ] ] } + ] + }, + 'text-wrap': { + // https://drafts.csswg.org/css-text-4/#propdef-text-wrap + types: [ + { type: 'discrete', options: [ [ 'wrap', 'nowrap' ] ] } + ] + }, + 'touch-action': { + // https://w3c.github.io/pointerevents/#the-touch-action-css-property + types: [ + { type: 'discrete', options: [ [ 'auto', 'none' ] ] } + ] + }, + 'top': { + // https://drafts.csswg.org/css-position/#propdef-top + types: [ + ] + }, + 'transform': { + // https://drafts.csswg.org/css-transforms/#propdef-transform + types: [ 'transformList' ] + }, + 'transform-box': { + // https://drafts.csswg.org/css-transforms/#propdef-transform-box + types: [ + { type: 'discrete', options: [ [ 'fill-box', 'border-box' ] ] } + ] + }, + 'transform-origin': { + // https://drafts.csswg.org/css-transforms/#propdef-transform-origin + types: [ + ] + }, + 'transform-style': { + // https://drafts.csswg.org/css-transforms/#propdef-transform-style + types: [ + { type: 'discrete', options: [ [ 'flat', 'preserve-3d' ] ] } + ] + }, + 'rotate': { + // https://drafts.csswg.org/css-transforms-2/#individual-transforms + types: [ 'rotateList' ] + }, + 'translate': { + // https://drafts.csswg.org/css-transforms-2/#individual-transforms + types: [ 'translateList' ], + setup: t => { + // We need to set a width/height for resolving percentages against. + const element = createElement(t); + element.style.width = '100px'; + element.style.height = '100px'; + return element; + } + }, + 'scale': { + // https://drafts.csswg.org/css-transforms-2/#individual-transforms + types: [ 'scaleList' ] + }, + 'vector-effect': { + // https://svgwg.org/svg2-draft/coords.html#VectorEffectProperty + types: [ + { type: 'discrete', options: [ [ 'none', 'non-scaling-stroke' ] ] }, + ] + }, + 'vertical-align': { + // https://drafts.csswg.org/css21/visudet.html#propdef-vertical-align + types: [ + ] + }, + 'visibility': { + // https://drafts.csswg.org/css2/visufx.html#propdef-visibility + types: [ 'visibility' ] + }, + 'white-space': { + // https://drafts.csswg.org/css-text-4/#propdef-white-space + types: [ + { type: 'discrete', options: [ [ 'pre', 'nowrap' ] ] } + ] + }, + 'width': { + // https://drafts.csswg.org/css21/visudet.html#propdef-width + types: [ + ] + }, + 'word-break': { + // https://drafts.csswg.org/css-text-3/#propdef-word-break + types: [ + { type: 'discrete', options: [ [ 'keep-all', 'break-all' ] ] } + ] + }, + 'word-spacing': { + // https://drafts.csswg.org/css-text-3/#propdef-word-spacing + types: [ 'lengthPercentageOrCalc' ] + }, + 'z-index': { + // https://drafts.csswg.org/css-position/#propdef-z-index + types: [ + ] + }, +}; + +function testAnimationSamples(animation, idlName, testSamples) { + const pseudoType = animation.effect.pseudoElement; + const target = animation.effect.target; + for (const testSample of testSamples) { + animation.currentTime = testSample.time; + assert_equals(getComputedStyle(target, pseudoType)[idlName].toLowerCase(), + testSample.expected, + `The value should be ${testSample.expected}` + + ` at ${testSample.time}ms`); + } +} + +function toOrderedArray(string) { + return string.split(/\s*,\s/).sort(); +} + +// This test is for some list-based CSS properties such as font-variant-settings +// don't specify an order for serializing computed values. +// This test is for such the property. +function testAnimationSamplesWithAnyOrder(animation, idlName, testSamples) { + const type = animation.effect.pseudoElement; + const target = animation.effect.target; + for (const testSample of testSamples) { + animation.currentTime = testSample.time; + + // Convert to array and sort the expected and actual value lists first + // before comparing them. + const computedValues = + toOrderedArray(getComputedStyle(target, type)[idlName]); + const expectedValues = toOrderedArray(testSample.expected); + + assert_array_equals(computedValues, expectedValues, + `The computed values should be ${expectedValues}` + + ` at ${testSample.time}ms`); + } +} + +function RoundMatrix(style) { + var matrixMatch = style.match(/^(matrix(3d)?)\(.+\)$/); + if (!!matrixMatch) { + var matrixType = matrixMatch[1]; + var matrixArgs = style.substr(matrixType.length); + var extractmatrix = function(matrixStr) { + var list = []; + var regex = /[+\-]?[0-9]+[.]?[0-9]*(e[+/-][0-9]+)?/g; + var match = undefined; + do { + match = regex.exec(matrixStr); + if (match) { + list.push(parseFloat(parseFloat(match[0]).toFixed(6))); + } + } while (match); + return list; + } + return matrixType + '(' + extractmatrix(matrixArgs).join(', ') + ')'; + } + return style; +} + +function testAnimationSampleMatrices(animation, idlName, testSamples) { + const target = animation.effect.target; + for (const testSample of testSamples) { + animation.currentTime = testSample.time; + const actual = RoundMatrix(getComputedStyle(target)[idlName]); + const expected = RoundMatrix(createMatrixFromArray(testSample.expected)); + assert_matrix_equals(actual, expected, + `The value should be ${expected} at` + + ` ${testSample.time}ms but got ${actual}`); + } +} + +function testAnimationSampleRotate3d(animation, idlName, testSamples) { + const target = animation.effect.target; + for (const testSample of testSamples) { + animation.currentTime = testSample.time; + const actual = getComputedStyle(target)[idlName]; + const expected = testSample.expected; + assert_rotate3d_equals(actual, expected, + `The value should be ${expected} at` + + ` ${testSample.time}ms but got ${actual}`); + } +} + +function createTestElement(t, setup) { + return setup ? setup(t) : createElement(t); +} + +function isSupported(property) { + const testKeyframe = new TestKeyframe(propertyToIDL(property)); + assert_not_equals(window.KeyframeEffect, undefined, 'window.KeyframeEffect'); + try { + // Since TestKeyframe returns 'undefined' for |property|, + // the KeyframeEffect constructor will throw + // if the string 'undefined' is not a valid value for the property. + new KeyframeEffect(null, testKeyframe); + } catch(e) {} + return testKeyframe.propAccessCount !== 0; +} + +function TestKeyframe(testProp) { + let _propAccessCount = 0; + + Object.defineProperty(this, testProp, { + get: function() { _propAccessCount++; }, + enumerable: true + }); + + Object.defineProperty(this, 'propAccessCount', { + get: function() { return _propAccessCount; } + }); +} + +function propertyToIDL(property) { + // https://drafts.csswg.org/web-animations/#animation-property-name-to-idl-attribute-name + if (property === 'float') { + return 'cssFloat'; + } + return property.replace(/-[a-z]/gi, + function (str) { + return str.substr(1).toUpperCase(); }); +} +function calcFromPercentage(idlName, percentageValue) { + const examElem = document.createElement('div'); + document.body.appendChild(examElem); + examElem.style[idlName] = percentageValue; + + const calcValue = getComputedStyle(examElem)[idlName]; + document.body.removeChild(examElem); + + return calcValue; +} diff --git a/testing/web-platform/tests/web-animations/animation-model/animation-types/property-types.js b/testing/web-platform/tests/web-animations/animation-model/animation-types/property-types.js new file mode 100644 index 0000000000..6f39020b5c --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/property-types.js @@ -0,0 +1,2769 @@ +'use strict'; + +const discreteType = { + testInterpolation: (property, setup, options) => { + for (const keyframes of options) { + const [ from, to ] = keyframes; + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: [from, to] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: from.toLowerCase() }, + { time: 499, expected: from.toLowerCase() }, + { time: 500, expected: to.toLowerCase() }, + { time: 1000, expected: to.toLowerCase() }]); + }, `${property} uses discrete animation when animating between` + + ` "${from}" and "${to}" with linear easing`); + + test(t => { + // Easing: http://cubic-bezier.com/#.68,0,1,.01 + // With this curve, we don't reach the 50% point until about 95% of + // the time has expired. + const idlName = propertyToIDL(property); + const keyframes = {}; + const target = createTestElement(t, setup); + const animation = target.animate( + { [idlName]: [from, to] }, + { + duration: 1000, + fill: 'both', + easing: 'cubic-bezier(0.68,0,1,0.01)', + } + ); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: from.toLowerCase() }, + { time: 940, expected: from.toLowerCase() }, + { time: 960, expected: to.toLowerCase() }]); + }, `${property} uses discrete animation when animating between` + + ` "${from}" and "${to}" with effect easing`); + + test(t => { + // Easing: http://cubic-bezier.com/#.68,0,1,.01 + // With this curve, we don't reach the 50% point until about 95% of + // the time has expired. + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { + [idlName]: [from, to], + easing: 'cubic-bezier(0.68,0,1,0.01)', + }, + { duration: 1000, fill: 'both' } + ); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: from.toLowerCase() }, + { time: 940, expected: from.toLowerCase() }, + { time: 960, expected: to.toLowerCase() }]); + }, `${property} uses discrete animation when animating between` + + ` "${from}" and "${to}" with keyframe easing`); + } + }, + + testAdditionOrAccumulation: (property, setup, options, composite) => { + for (const keyframes of options) { + const [ from, to ] = keyframes; + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.animate({ [idlName]: [from, from] }, 1000); + const animation = target.animate( + { [idlName]: [to, to] }, + { duration: 1000, composite } + ); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: to.toLowerCase() }]); + }, `${property}: "${to}" onto "${from}"`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.animate({ [idlName]: [to, to] }, 1000); + const animation = target.animate( + { [idlName]: [from, from] }, + { duration: 1000, composite } + ); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: from.toLowerCase() }]); + }, `${property}: "${from}" onto "${to}"`); + } + }, + + testAddition: function(property, setup, options) { + this.testAdditionOrAccumulation(property, setup, options, 'add'); + }, + + testAccumulation: function(property, setup, options) { + this.testAdditionOrAccumulation(property, setup, options, 'accumulate'); + }, +}; + +const lengthType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['10px', '50px'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '30px' }]); + }, `${property} supports animating as a length`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['1rem', '5rem'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '30px' }]); + }, `${property} supports animating as a length of rem`); + }, + + testAdditionOrAccumulation: (property, setup, composite) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '10px'; + const animation = target.animate( + { [idlName]: ['10px', '50px'] }, + { duration: 1000, composite } + ); + testAnimationSamples(animation, idlName, [{ time: 0, expected: '20px' }]); + }, `${property}: length`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '1rem'; + const animation = target.animate( + { [idlName]: ['1rem', '5rem'] }, + { duration: 1000, composite } + ); + testAnimationSamples(animation, idlName, [{ time: 0, expected: '20px' }]); + }, `${property}: length of rem`); + }, + + testAddition: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'add'); + }, + + testAccumulation: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'accumulate'); + }, +}; + +const lengthPairType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { [idlName]: ['10px 10px', '50px 50px'] }, + { duration: 1000, fill: 'both' } + ); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '30px 30px' }]); + }, `${property} supports animating as a length pair`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { [idlName]: ['1rem 1rem', '5rem 5rem'] }, + { duration: 1000, fill: 'both' } + ); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '30px 30px' }]); + }, `${property} supports animating as a length pair of rem`); + }, + + testAdditionOrAccumulation: (property, setup, composite) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '10px 10px'; + const animation = target.animate( + { [idlName]: ['10px 10px', '50px 50px'] }, + { duration: 1000, composite } + ); + testAnimationSamples( + animation, + idlName, + [{ time: 0, expected: '20px 20px' }] + ); + }, `${property}: length pair`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '1rem 1rem'; + const animation = target.animate( + { [idlName]: ['1rem 1rem', '5rem 5rem'] }, + { duration: 1000, composite } + ); + testAnimationSamples( + animation, + idlName, + [{ time: 0, expected: '20px 20px' }] + ); + }, `${property}: length pair of rem`); + }, + + testAddition: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'add'); + }, + + testAccumulation: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'accumulate'); + }, +}; + +const percentageType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['10%', '50%'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '30%' }]); + }, `${property} supports animating as a percentage`); + }, + + testAdditionOrAccumulation: (property, setup, composite) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '60%'; + const animation = target.animate( + { [idlName]: ['70%', '100%'] }, + { duration: 1000, composite } + ); + testAnimationSamples(animation, idlName, [{ time: 0, expected: '130%' }]); + }, `${property}: percentage`); + }, + + testAddition: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'add'); + }, + + testAccumulation: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'accumulate'); + }, +}; + +const integerType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: [-2, 2] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '0' }]); + }, `${property} supports animating as an integer`); + }, + + testAdditionOrAccumulation: (property, setup, composite) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = -1; + const animation = target.animate( + { [idlName]: [-2, 2] }, + { duration: 1000, composite } + ); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: '-3' }]); + }, `${property}: integer`); + }, + + testAddition: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'add'); + }, + + testAccumulation: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'accumulate'); + }, +}; + +const positiveIntegerType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: [1, 3] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [ { time: 500, expected: '2' } ]); + }, `${property} supports animating as a positive integer`); + }, + + testAdditionOrAccumulation: (property, setup, composite) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 1; + const animation = target.animate( + { [idlName]: [2, 5] }, + { duration: 1000, composite } + ); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: '3' }]); + }, `${property}: positive integer`); + }, + + testAddition: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'add'); + }, + + testAccumulation: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'accumulate'); + }, +}; + +const lengthPercentageOrCalcType = { + testInterpolation: (property, setup) => { + lengthType.testInterpolation(property, setup); + percentageType.testInterpolation(property, setup); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['10px', '20%'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'calc(10% + 5px)' }]); + }, `${property} supports animating as combination units "px" and "%"`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['10%', '2em'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'calc(5% + 10px)' }]); + }, `${property} supports animating as combination units "%" and "em"`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['1em', '2rem'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '15px' }]); + }, `${property} supports animating as combination units "em" and "rem"`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { [idlName]: ['10px', 'calc(1em + 20%)'] }, + { duration: 1000, fill: 'both' } + ); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'calc(10% + 10px)' }]); + }, `${property} supports animating as combination units "px" and "calc"`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { [idlName]: ['calc(10px + 10%)', 'calc(1em + 1rem + 20%)'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, + expected: 'calc(15% + 15px)' }]); + }, `${property} supports animating as a calc`); + }, + + testAdditionOrAccumulation: (property, setup, composite) => { + lengthType.testAddition(property, setup); + percentageType.testAddition(property, setup); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '10px'; + const animation = target.animate({ [idlName]: ['10%', '50%'] }, + { duration: 1000, composite }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'calc(10% + 10px)' }]); + }, `${property}: units "%" onto "px"`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '10%'; + const animation = target.animate({ [idlName]: ['10px', '50px'] }, + { duration: 1000, composite }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'calc(10% + 10px)' }]); + }, `${property}: units "px" onto "%"`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '10%'; + const animation = target.animate({ [idlName]: ['2rem', '5rem'] }, + { duration: 1000, composite }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'calc(10% + 20px)' }]); + }, `${property}: units "rem" onto "%"`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '2rem'; + const animation = target.animate({ [idlName]: ['10%', '50%'] }, + { duration: 1000, composite }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'calc(10% + 20px)' }]); + }, `${property}: units "%" onto "rem"`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '2em'; + const animation = target.animate({ [idlName]: ['2rem', '5rem'] }, + { duration: 1000, composite }); + testAnimationSamples(animation, idlName, [{ time: 0, expected: '40px' }]); + }, `${property}: units "rem" onto "em"`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '2rem'; + const animation = target.animate({ [idlName]: ['2em', '5em'] }, + { duration: 1000, composite }); + testAnimationSamples(animation, idlName, [{ time: 0, expected: '40px' }]); + }, `${property}: units "em" onto "rem"`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '10px'; + const animation = target.animate({ [idlName]: ['calc(2em + 20%)', + 'calc(5rem + 50%)'] }, + { duration: 1000, composite }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'calc(20% + 30px)' }]); + }, `${property}: units "calc" onto "px"`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'calc(10px + 10%)'; + const animation = target.animate({ [idlName]: ['calc(20px + 20%)', + 'calc(2em + 3rem + 40%)'] }, + { duration: 1000, composite }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'calc(30% + 30px)' }]); + }, `${property}: calc`); + }, + + testAddition: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'add'); + }, + + testAccumulation: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'accumulate'); + }, +}; + +const positiveNumberType = { + testInterpolation: (property, setup, expectedUnit='') => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: [1.1, 1.5] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '1.3' + expectedUnit }]); + }, `${property} supports animating as a positive number`); + }, + + testAdditionOrAccumulation: (property, setup, composite) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 1.1; + const animation = target.animate({ [idlName]: [1.1, 1.5] }, + { duration: 1000, composite }); + testAnimationSamples(animation, idlName, [{ time: 0, expected: '2.2' }]); + }, `${property}: positive number`); + }, + + testAddition: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'add'); + }, + + testAccumulation: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'accumulate'); + }, +}; + +// Test using float values in the range [0, 1] +const opacityType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: [0.3, 0.8] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '0.55' }]); + }, `${property} supports animating as a [0, 1] number`); + }, + + testAdditionOrAccumulation: (property, setup, composite) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 0.3; + const animation = target.animate({ [idlName]: [0.3, 0.8] }, + { duration: 1000, composite }); + testAnimationSamples(animation, idlName, [{ time: 0, expected: '0.6' }]); + }, `${property}: [0, 1] number`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 0.8; + const animation = target.animate({ [idlName]: [0.3, 0.8] }, + { duration: 1000, composite }); + testAnimationSamples(animation, idlName, [{ time: 0, expected: '1' }]); + }, `${property}: [0, 1] number (clamped)`); + }, + + testAddition: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'add'); + }, + + testAccumulation: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'accumulate'); + }, +}; + +const visibilityType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['visible', 'hidden'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'visible' }, + { time: 999, expected: 'visible' }, + { time: 1000, expected: 'hidden' }]); + }, `${property} uses visibility animation when animating` + + ' from "visible" to "hidden"'); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['hidden', 'visible'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'hidden' }, + { time: 1, expected: 'visible' }, + { time: 1000, expected: 'visible' }]); + }, `${property} uses visibility animation when animating` + + ' from "hidden" to "visible"'); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['hidden', 'collapse'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'hidden' }, + { time: 499, expected: 'hidden' }, + { time: 500, expected: 'collapse' }, + { time: 1000, expected: 'collapse' }]); + }, `${property} uses visibility animation when animating` + + ' from "hidden" to "collapse"'); + + test(t => { + // Easing: http://cubic-bezier.com/#.68,-.55,.26,1.55 + // With this curve, the value is less than 0 till about 34% + // also more than 1 since about 63% + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: ['visible', 'hidden'] }, + { duration: 1000, fill: 'both', + easing: 'cubic-bezier(0.68, -0.55, 0.26, 1.55)' }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'visible' }, + { time: 1, expected: 'visible' }, + { time: 330, expected: 'visible' }, + { time: 340, expected: 'visible' }, + { time: 620, expected: 'visible' }, + { time: 630, expected: 'hidden' }, + { time: 1000, expected: 'hidden' }]); + }, `${property} uses visibility animation when animating` + + ' from "visible" to "hidden" with easeInOutBack easing'); + }, + + testAdditionOrAccumulation: (property, setup, composite) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'visible'; + const animation = target.animate({ [idlName]: ['visible', 'hidden'] }, + { duration: 1000, fill: 'both', + composite }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'visible' }, + { time: 1000, expected: 'hidden' }]); + }, `${property}: onto "visible"`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'hidden'; + const animation = target.animate({ [idlName]: ['hidden', 'visible'] }, + { duration: 1000, fill: 'both', + composite }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'hidden' }, + { time: 1000, expected: 'visible' }]); + }, `${property}: onto "hidden"`); + }, + + testAddition: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'add'); + }, + + testAccumulation: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'accumulate'); + }, +}; + +const colorType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['rgb(255, 0, 0)', + 'rgb(0, 0, 255)'] }, + 1000); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'rgb(128, 0, 128)' }]); + }, `${property} supports animating as color of rgb()`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['#ff0000', '#0000ff'] }, + 1000); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'rgb(128, 0, 128)' }]); + }, `${property} supports animating as color of #RGB`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['hsl(0, 100%, 50%)', + 'hsl(240, 100%, 50%)'] }, + 1000); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'rgb(128, 0, 128)' }]); + }, `${property} supports animating as color of hsl()`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { [idlName]: ['#ff000066', '#0000ffcc'] }, + 1000 + ); + // R: 255 * (0.4 * 0.5) / 0.6 = 85 + // B: 255 * (0.8 * 0.5) / 0.6 = 170 + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'rgba(85, 0, 170, 0.6)' }]); + }, `${property} supports animating as color of #RGBa`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { + [idlName]: ['rgba(255, 0, 0, 0.4)', 'rgba(0, 0, 255, 0.8)'], + }, + 1000 + ); + testAnimationSamples(animation, idlName, // Same as above. + [{ time: 500, expected: 'rgba(85, 0, 170, 0.6)' }]); + }, `${property} supports animating as color of rgba()`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { + [idlName]: ['hsla(0, 100%, 50%, 0.4)', 'hsla(240, 100%, 50%, 0.8)'], + }, + 1000 + ); + testAnimationSamples(animation, idlName, // Same as above. + [{ time: 500, expected: 'rgba(85, 0, 170, 0.6)' }]); + }, `${property} supports animating as color of hsla()`); + }, + + testAdditionOrAccumulation: (property, setup, composite) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rgb(128, 128, 128)'; + const animation = target.animate( + { + [idlName]: ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'], + }, + { duration: 1000, composite } + ); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'rgb(255, 128, 128)' }, + // The value at 50% is interpolated + // from 'rgb(128+255, 128, 128)' + // to 'rgb(128, 128, 128+255)'. + { time: 500, expected: 'rgb(255, 128, 255)' }]); + }, `${property} supports animating as color of rgb() with overflowed ` + + ' from and to values'); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rgb(128, 128, 128)'; + const animation = target.animate({ [idlName]: ['#ff0000', '#0000ff'] }, + { duration: 1000, composite }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'rgb(255, 128, 128)' }]); + }, `${property} supports animating as color of #RGB`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rgb(128, 128, 128)'; + const animation = target.animate({ [idlName]: ['hsl(0, 100%, 50%)', + 'hsl(240, 100%, 50%)'] }, + { duration: 1000, composite }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'rgb(255, 128, 128)' }]); + }, `${property} supports animating as color of hsl()`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rgb(128, 128, 128)'; + const animation = target.animate( + { [idlName]: ['#ff000066', '#0000ffcc'] }, + { duration: 1000, composite } + ); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'rgb(230, 128, 128)' }]); + }, `${property} supports animating as color of #RGBa`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rgb(128, 128, 128)'; + const animation = target.animate({ [idlName]: ['rgba(255, 0, 0, 0.4)', + 'rgba(0, 0, 255, 0.8)'] }, + { duration: 1000, composite }); + testAnimationSamples(animation, idlName, // Same as above. + [{ time: 0, expected: 'rgb(230, 128, 128)' }]); + }, `${property} supports animating as color of rgba()`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rgb(128, 128, 128)'; + const animation = target.animate( + { + [idlName]: ['hsla(0, 100%, 50%, 0.4)', 'hsla(240, 100%, 50%, 0.8)'], + }, + { duration: 1000, composite } + ); + testAnimationSamples(animation, idlName, // Same as above. + [{ time: 0, expected: 'rgb(230, 128, 128)' }]); + }, `${property} supports animating as color of hsla()`); + }, + + testAddition: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'add'); + }, + + testAccumulation: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'accumulate'); + }, +}; + +const transformListType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { + [idlName]: ['translate(200px, -200px)', 'translate(400px, 400px)'], + }, + 1000 + ); + testAnimationSampleMatrices(animation, idlName, + [{ time: 500, expected: [ 1, 0, 0, 1, 300, 100 ] }]); + }, `${property}: translate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { + [idlName]: ['rotate(45deg)', 'rotate(135deg)'], + }, + 1000 + ); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 500, expected: [ Math.cos(Math.PI / 2), + Math.sin(Math.PI / 2), + -Math.sin(Math.PI / 2), + Math.cos(Math.PI / 2), + 0, 0] }]); + }, `${property}: rotate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['scale(3)', 'scale(5)'] }, + 1000); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 500, expected: [ 4, 0, 0, 4, 0, 0 ] }]); + }, `${property}: scale`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['skew(30deg, 60deg)', + 'skew(60deg, 30deg)'] }, + 1000); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 500, expected: [ 1, Math.tan(Math.PI / 4), + Math.tan(Math.PI / 4), 1, + 0, 0] }]); + }, `${property}: skew`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: ['translateX(100px) rotate(45deg)', + 'translateX(200px) rotate(135deg)'] }, + 1000); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 500, expected: [ Math.cos(Math.PI / 2), + Math.sin(Math.PI / 2), + -Math.sin(Math.PI / 2), + Math.cos(Math.PI / 2), + 150, 0 ] }]); + }, `${property}: rotate and translate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: ['rotate(45deg) translateX(100px)', + 'rotate(135deg) translateX(200px)'] }, + 1000); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 500, expected: [ Math.cos(Math.PI / 2), + Math.sin(Math.PI / 2), + -Math.sin(Math.PI / 2), + Math.cos(Math.PI / 2), + 150 * Math.cos(Math.PI / 2), + 150 * Math.sin(Math.PI / 2) ] }]); + }, `${property}: translate and rotate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: ['rotate(0deg)', + 'rotate(1080deg) translateX(100px)'] }, + 1000); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 500, expected: [ -1, 0, 0, -1, -50, 0 ] }]); + }, `${property}: extend shorter list (from)`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: ['rotate(0deg) translateX(100px)', + 'rotate(1080deg)'] }, + 1000); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 500, expected: [ -1, 0, 0, -1, -50, 0 ] }]); + }, `${property}: extend shorter list (to)`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = // matrix(0, 1, -1, 0, 0, 100) + target.animate({ [idlName]: ['rotate(90deg) translateX(100px)', + // matrix(-1, 0, 0, -1, 200, 0) + 'translateX(200px) rotate(180deg)'] }, + 1000); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 500, expected: [ Math.cos(Math.PI * 3 / 4), + Math.sin(Math.PI * 3 / 4), + -Math.sin(Math.PI * 3 / 4), + Math.cos(Math.PI * 3 / 4), + 100, 50 ] }]); + }, `${property}: mismatch order of translate and rotate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = // Same matrices as above. + target.animate({ [idlName]: [ 'matrix(0, 1, -1, 0, 0, 100)', + 'matrix(-1, 0, 0, -1, 200, 0)' ] }, + 1000); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 500, expected: [ Math.cos(Math.PI * 3 / 4), + Math.sin(Math.PI * 3 / 4), + -Math.sin(Math.PI * 3 / 4), + Math.cos(Math.PI * 3 / 4), + 100, 50 ] }]); + }, `${property}: matrix`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ 'rotate3d(1, 1, 0, 0deg)', + 'rotate3d(1, 1, 0, 90deg)'] }, + 1000); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 500, expected: rotate3dToMatrix(1, 1, 0, Math.PI / 4) }]); + }, `${property}: rotate3d`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + // To calculate expected matrices easily, generate input matrices from + // rotate3d. + const from = rotate3dToMatrix3d(1, 1, 0, Math.PI / 4); + const to = rotate3dToMatrix3d(1, 1, 0, Math.PI * 3 / 4); + const animation = target.animate({ [idlName]: [ from, to ] }, 1000); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 500, expected: rotate3dToMatrix(1, 1, 0, Math.PI * 2 / 4) }]); + }, `${property}: matrix3d`); + + // This test aims for forcing the two mismatched transforms to be + // decomposed into matrix3d before interpolation. Therefore, we not only + // test the interpolation, but also test the 3D matrix decomposition. + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { + [idlName]: [ + 'scale(0.3)', + // scale(0.5) translateZ(1px) + 'matrix3d(0.5, 0, 0, 0, 0, 0.5, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1)', + ], + }, + 1000 + ); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 500, expected: [ 0.4, 0, 0, 0, + 0, 0.4, 0, 0, + 0, 0, 1, 0, + 0, 0, 0.5, 1] }]); + }, `${property}: mismatched 3D transforms`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: ['rotateY(60deg)', 'none' ] }, 1000); + + testAnimationSampleMatrices(animation, idlName, + // rotateY(30deg) == rotate3D(0, 1, 0, 30deg) + [{ time: 500, expected: rotate3dToMatrix(0, 1, 0, Math.PI / 6) }]); + }, `${property}: rotateY`); + + // Following tests aim for test the fallback discrete interpolation behavior + // for non-invertible matrices. The non-invertible matrix that we use is the + // singular matrix, matrix(1, 1, 0, 0, 0, 100). + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: ['matrix(-1, 0, 0, -1, 200, 0)', + 'matrix( 1, 1, 0, 0, 0, 100)'] }, + { duration: 1000, fill: 'both' }); + + testAnimationSampleMatrices(animation, idlName, + [ { time: 0, expected: [ -1, 0, 0, -1, 200, 0 ] }, + { time: 499, expected: [ -1, 0, 0, -1, 200, 0 ] }, + { time: 500, expected: [ 1, 1, 0, 0, 0, 100 ] }, + { time: 1000, expected: [ 1, 1, 0, 0, 0, 100 ] }]); + }, `${property}: non-invertible matrices`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { + [idlName]: [ + // matrix(0, -1, 1, 0, 250, 0) + 'translate(50px) matrix(-1, 0, 0, -1, 200, 0) rotate(90deg)', + // matrix(-1, -1, 0, 0, 100, 100) + 'translate(100px) matrix( 1, 1, 0, 0, 0, 100) rotate(180deg)', + ], + }, + { duration: 1000, fill: 'both' } + ); + + testAnimationSampleMatrices(animation, idlName, + [ { time: 0, expected: [ 0, -1, 1, 0, 250, 0 ] }, + { time: 499, expected: [ 0, -1, 1, 0, 250, 0 ] }, + { time: 500, expected: [ -1, -1, 0, 0, 100, 100 ] }, + { time: 1000, expected: [ -1, -1, 0, 0, 100, 100 ] }]); + }, `${property}: non-invertible matrices in matched transform lists`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { + [idlName]: [ + // matrix(-2, 0, 0, -2, 250, 0) + 'translate(50px) matrix(-1, 0, 0, -1, 200, 0) scale(2)', + // matrix(1, 1, 1, 1, 100, 100) + 'translate(100px) matrix( 1, 1, 0, 0, 0, 100) skew(45deg)', + ], + }, + { duration: 1000, fill: 'both' } + ); + + testAnimationSampleMatrices(animation, idlName, + [ { time: 0, expected: [ -2, 0, 0, -2, 250, 0 ] }, + { time: 499, expected: [ -2, 0, 0, -2, 250, 0 ] }, + { time: 500, expected: [ 1, 1, 1, 1, 100, 100 ] }, + { time: 1000, expected: [ 1, 1, 1, 1, 100, 100 ] }]); + }, `${property}: non-invertible matrices in mismatched transform lists`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { + // perspective(0) is treated as perspective(1px) + [idlName]: ['perspective(0)', 'perspective(10px)'], + }, + 1000 + ); + testAnimationSampleMatrices(animation, idlName, + [{ time: 500, expected: [ 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, -0.55, + 0, 0, 0, 1 ] }]); + }, `${property}: perspective`); + + }, + + testAddition: function(property, setup) { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'translateX(100px)'; + const animation = target.animate({ [idlName]: ['translateX(-200px)', + 'translateX(500px)'] }, + { duration: 1000, fill: 'both', + composite: 'add' }); + testAnimationSampleMatrices(animation, idlName, + [ { time: 0, expected: [ 1, 0, 0, 1, -100, 0 ] }, + { time: 1000, expected: [ 1, 0, 0, 1, 600, 0 ] }]); + }, `${property}: translate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rotate(45deg)'; + const animation = target.animate({ [idlName]: ['rotate(-90deg)', + 'rotate(90deg)'] }, + { duration: 1000, fill: 'both', + composite: 'add' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: [ Math.cos(-Math.PI / 4), + Math.sin(-Math.PI / 4), + -Math.sin(-Math.PI / 4), + Math.cos(-Math.PI / 4), + 0, 0] }, + { time: 1000, expected: [ Math.cos(Math.PI * 3 / 4), + Math.sin(Math.PI * 3 / 4), + -Math.sin(Math.PI * 3 / 4), + Math.cos(Math.PI * 3 / 4), + 0, 0] }]); + }, `${property}: rotate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'scale(2)'; + const animation = target.animate({ [idlName]: ['scale(-3)', 'scale(5)'] }, + { duration: 1000, fill: 'both', + composite: 'add' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: [ -6, 0, 0, -6, 0, 0 ] }, // scale(-3) scale(2) + { time: 1000, expected: [ 10, 0, 0, 10, 0, 0 ] }]); // scale(5) scale(2) + }, `${property}: scale`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + // matrix(1, tan(10deg), tan(10deg), 1) + target.style[idlName] = 'skew(10deg, 10deg)'; + const animation = // matrix(1, tan(20deg), tan(-30deg), 1) + target.animate({ [idlName]: ['skew(-30deg, 20deg)', + // matrix(1, tan(-30deg), tan(20deg), 1) + 'skew(20deg, -30deg)'] }, + { duration: 1000, fill: 'both', composite: 'add' }); + + // matrix at 0%. + // [ 1 tan(10deg) ] [ 1 tan(-30deg) ] + // [ tan(10deg) 1 ] [ tan(20deg) 1 ] = + // + // [ 1 + tan(10deg) * tan(20deg) tan(-30deg) + tan(10deg) ] + // [ tan(10deg) + tan(20deg) tan(10deg) * tan(-30deg) + 1 ] + + // matrix at 100%. + // [ 1 tan(10deg) ] [ 1 tan(20deg) ] + // [ tan(10deg) 1 ] [ tan(-30deg) 1 ] = + // + // [ 1 + tan(10deg) * tan(-30deg) tan(20deg) + tan(10deg) ] + // [ tan(10deg) + tan(-30deg) tan(10deg) * tan(20deg) + 1 ] + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: [ 1 + Math.tan(Math.PI/18) * Math.tan(Math.PI/9), + Math.tan(Math.PI/18) + Math.tan(Math.PI/9), + Math.tan(-Math.PI/6) + Math.tan(Math.PI/18), + 1 + Math.tan(Math.PI/18) * Math.tan(-Math.PI/6), + 0, 0] }, + { time: 1000, expected: [ 1 + Math.tan(Math.PI/18) * Math.tan(-Math.PI/6), + Math.tan(Math.PI/18) + Math.tan(-Math.PI/6), + Math.tan(Math.PI/9) + Math.tan(Math.PI/18), + 1 + Math.tan(Math.PI/18) * Math.tan(Math.PI/9), + 0, 0] }]); + }, `${property}: skew`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + // matrix(1, 0, 0, 1, 100, 0) + target.style[idlName] = 'translateX(100px)'; + const animation = // matrix(0, 1, -1, 0, 0, 0) + target.animate({ [idlName]: ['rotate(90deg)', + // matrix(-1, 0, 0, -1, 0, 0) + 'rotate(180deg)'] }, + { duration: 1000, fill: 'both', composite: 'add' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: [ 0, 1, -1, 0, 100, 0 ] }, + { time: 1000, expected: [ -1, 0, 0, -1, 100, 0 ] }]); + }, `${property}: rotate on translate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + // matrix(0, 1, -1, 0, 0, 0) + target.style[idlName] = 'rotate(90deg)'; + const animation = // matrix(1, 0, 0, 1, 100, 0) + target.animate({ [idlName]: ['translateX(100px)', + // matrix(1, 0, 0, 1, 200, 0) + 'translateX(200px)'] }, + { duration: 1000, fill: 'both', composite: 'add' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: [ 0, 1, -1, 0, 0, 100 ] }, + { time: 1000, expected: [ 0, 1, -1, 0, 0, 200 ] }]); + }, `${property}: translate on rotate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rotate(45deg) translateX(100px)'; + const animation = target.animate({ [idlName]: ['rotate(-90deg)', + 'rotate(90deg)'] }, + { duration: 1000, fill: 'both', + composite: 'add' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: [ Math.cos(-Math.PI / 4), + Math.sin(-Math.PI / 4), + -Math.sin(-Math.PI / 4), + Math.cos(-Math.PI / 4), + 100 * Math.cos(Math.PI / 4), + 100 * Math.sin(Math.PI / 4) ] }, + { time: 1000, expected: [ Math.cos(Math.PI * 3 / 4), + Math.sin(Math.PI * 3 / 4), + -Math.sin(Math.PI * 3 / 4), + Math.cos(Math.PI * 3 / 4), + 100 * Math.cos(Math.PI / 4), + 100 * Math.sin(Math.PI / 4) ] }]); + }, `${property}: rotate on rotate and translate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'matrix(0, 1, -1, 0, 0, 0)'; + const animation = // Same matrices as above. + target.animate({ [idlName]: [ 'matrix(1, 0, 0, 1, 100, 0)', + 'matrix(1, 0, 0, 1, 200, 0)' ] }, + { duration: 1000, fill: 'both', composite: 'add' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: [ 0, 1, -1, 0, 0, 100 ] }, + { time: 1000, expected: [ 0, 1, -1, 0, 0, 200 ] }]); + }, `${property}: matrix`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rotate3d(1, 1, 0, 45deg)'; + const animation = + target.animate({ [idlName]: [ 'rotate3d(1, 1, 0, -90deg)', + 'rotate3d(1, 1, 0, 90deg)'] }, + { duration: 1000, fill: 'both', composite: 'add' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: rotate3dToMatrix(1, 1, 0, -Math.PI / 4) }, + { time: 1000, expected: rotate3dToMatrix(1, 1, 0, 3 * Math.PI / 4) }]); + }, `${property}: rotate3d`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + // To calculate expected matrices easily, generate input matrices from + // rotate3d. + target.style[idlName] = rotate3dToMatrix3d(1, 1, 0, Math.PI / 4); + const from = rotate3dToMatrix3d(1, 1, 0, -Math.PI / 2); + const to = rotate3dToMatrix3d(1, 1, 0, Math.PI / 2); + const animation = + target.animate({ [idlName]: [ from, to ] }, + { duration: 1000, fill: 'both', composite: 'add' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: rotate3dToMatrix(1, 1, 0, -Math.PI / 4) }, + { time: 1000, expected: rotate3dToMatrix(1, 1, 0, 3 * Math.PI / 4) }]); + }, `${property}: matrix3d`); + + // Following tests aim for test the addition behavior for non-invertible + // matrices. Note that the addition for non-invertible matrices should be + // the same, just like addition for invertible matrices. With these tests, + // we can assure that addition never behaves as discrete. The non-invertible + // matrix that we use is the singular matrix, matrix(1, 1, 0, 0, 0, 100). + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'translateX(50px)'; + const animation = + target.animate({ [idlName]: ['matrix(-1, 0, 0, -1, 200, 0)', + 'matrix( 1, 1, 0, 0, 0, 100)'] }, + { duration: 1000, fill: 'both', composite: 'add' }); + + testAnimationSampleMatrices(animation, idlName, + [ { time: 0, expected: [ -1, 0, 0, -1, 250, 0 ] }, + { time: 1000, expected: [ 1, 1, 0, 0, 50, 100 ] }]); + }, `${property}: non-invertible matrices`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'translateX(50px)'; + const animation = // matrix(0, -1, 1, 0, 200, 0) + target.animate({ [idlName]: ['matrix(-1, 0, 0, -1, 200, 0) rotate(90deg)', + // matrix(-1, -1, 0, 0, 0, 100) + 'matrix( 1, 1, 0, 0, 0, 100) rotate(180deg)'] }, + { duration: 1000, fill: 'both', composite: 'add' }); + + testAnimationSampleMatrices(animation, idlName, + [ { time: 0, expected: [ 0, -1, 1, 0, 250, 0 ] }, + { time: 1000, expected: [ -1, -1, 0, 0, 50, 100 ] }]); + }, `${property}: non-invertible matrices in matched transform lists`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'translateX(50px)'; + const animation = // matrix(-2, 0, 0, -2, 200, 0) + target.animate({ [idlName]: ['matrix(-1, 0, 0, -1, 200, 0) scale(2)', + // matrix(1, 1, 1, 1, 0, 100) + 'matrix( 1, 1, 0, 0, 0, 100) skew(45deg)'] }, + { duration: 1000, fill: 'both', composite: 'add' }); + + testAnimationSampleMatrices(animation, idlName, + [ { time: 0, expected: [ -2, 0, 0, -2, 250, 0 ] }, + { time: 1000, expected: [ 1, 1, 1, 1, 50, 100 ] }]); + }, `${property}: non-invertible matrices in mismatched transform lists`); + }, + + testAccumulation: function(property, setup) { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'translateX(100px)'; + const animation = target.animate({ [idlName]: ['translateX(-200px)', + 'translateX(500px)'] }, + { duration: 1000, fill: 'both', + composite: 'accumulate' }); + testAnimationSampleMatrices(animation, idlName, + [ { time: 0, expected: [ 1, 0, 0, 1, -100, 0 ] }, + { time: 1000, expected: [ 1, 0, 0, 1, 600, 0 ] }]); + }, `${property}: translate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rotate(45deg)'; + const animation = target.animate({ [idlName]: ['rotate(-90deg)', + 'rotate(90deg)'] }, + { duration: 1000, fill: 'both', + composite: 'accumulate' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: [ Math.cos(-Math.PI / 4), + Math.sin(-Math.PI / 4), + -Math.sin(-Math.PI / 4), + Math.cos(-Math.PI / 4), + 0, 0] }, + { time: 1000, expected: [ Math.cos(Math.PI * 3 / 4), + Math.sin(Math.PI * 3 / 4), + -Math.sin(Math.PI * 3 / 4), + Math.cos(Math.PI * 3 / 4), + 0, 0] }]); + }, `${property}: rotate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'scale(2)'; + const animation = target.animate({ [idlName]: ['scale(-3)', 'scale(5)'] }, + { duration: 1000, fill: 'both', + composite: 'accumulate' }); + + testAnimationSampleMatrices(animation, idlName, + // scale((2 - 1) + (-3 - 1) + 1) + [{ time: 0, expected: [ -2, 0, 0, -2, 0, 0 ] }, + // scale((2 - 1) + (5 - 1) + 1) + { time: 1000, expected: [ 6, 0, 0, 6, 0, 0 ] }]); + }, `${property}: scale`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + // matrix(1, tan(10deg), tan(10deg), 1) + target.style[idlName] = 'skew(10deg, 10deg)'; + const animation = // matrix(1, tan(20deg), tan(-30deg), 1) + target.animate({ [idlName]: ['skew(-30deg, 20deg)', + // matrix(1, tan(-30deg), tan(20deg), 1) + 'skew(20deg, -30deg)'] }, + { duration: 1000, fill: 'both', composite: 'accumulate' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: [ 1, Math.tan(Math.PI/6), + Math.tan(-Math.PI/9), 1, + 0, 0] }, + { time: 1000, expected: [ 1, Math.tan(-Math.PI/9), + Math.tan(Math.PI/6), 1, + 0, 0] }]); + }, `${property}: skew`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + // matrix(1, 0, 0, 1, 100, 0) + target.style[idlName] = 'translateX(100px)'; + const animation = // matrix(0, 1, -1, 0, 0, 0) + target.animate({ [idlName]: ['rotate(90deg)', + // matrix(-1, 0, 0, -1, 0, 0) + 'rotate(180deg)'] }, + { duration: 1000, fill: 'both', composite: 'accumulate' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: [ 0, 1, -1, 0, 100, 0 ] }, + { time: 1000, expected: [ -1, 0, 0, -1, 100, 0 ] }]); + }, `${property}: rotate on translate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + // matrix(0, 1, -1, 0, 0, 0) + target.style[idlName] = 'rotate(90deg)'; + const animation = // matrix(1, 0, 0, 1, 100, 0) + target.animate({ [idlName]: ['translateX(100px)', + // matrix(1, 0, 0, 1, 200, 0) + 'translateX(200px)'] }, + { duration: 1000, fill: 'both', composite: 'accumulate' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: [ 0, 1, -1, 0, 100, 0 ] }, + { time: 1000, expected: [ 0, 1, -1, 0, 200, 0 ] }]); + }, `${property}: translate on rotate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rotate(45deg)'; + const animation = + target.animate({ [idlName]: ['rotate(45deg) translateX(0px)', + 'rotate(45deg) translateX(100px)'] }, + { duration: 1000, fill: 'both', composite: 'accumulate' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: [ Math.cos(Math.PI / 2), + Math.sin(Math.PI / 2), + -Math.sin(Math.PI / 2), + Math.cos(Math.PI / 2), + 0 * Math.cos(Math.PI / 2), + 0 * Math.sin(Math.PI / 2) ] }, + { time: 1000, expected: [ Math.cos(Math.PI / 2), + Math.sin(Math.PI / 2), + -Math.sin(Math.PI / 2), + Math.cos(Math.PI / 2), + 100 * Math.cos(Math.PI / 2), + 100 * Math.sin(Math.PI / 2) ] }]); + }, `${property}: rotate and translate on rotate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rotate(45deg) translateX(100px)'; + const animation = + target.animate({ [idlName]: ['rotate(45deg)', 'rotate(45deg)'] }, + { duration: 1000, fill: 'both', composite: 'accumulate' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: [ Math.cos(Math.PI / 2), + Math.sin(Math.PI / 2), + -Math.sin(Math.PI / 2), + Math.cos(Math.PI / 2), + 100 * Math.cos(Math.PI / 2), + 100 * Math.sin(Math.PI / 2) ] }, + { time: 1000, expected: [ Math.cos(Math.PI / 2), + Math.sin(Math.PI / 2), + -Math.sin(Math.PI / 2), + Math.cos(Math.PI / 2), + 100 * Math.cos(Math.PI / 2), + 100 * Math.sin(Math.PI / 2) ] }]); + }, `${property}: rotate on rotate and translate`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'matrix(0, 1, -1, 0, 0, 0)'; + const animation = // Same matrices as above. + target.animate({ [idlName]: [ 'matrix(1, 0, 0, 1, 100, 0)', + 'matrix(1, 0, 0, 1, 200, 0)' ] }, + { duration: 1000, fill: 'both', composite: 'accumulate' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: [ 0, 1, -1, 0, 100, 0 ] }, + { time: 1000, expected: [ 0, 1, -1, 0, 200, 0 ] }]); + }, `${property}: matrix`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rotate3d(1, 1, 0, 45deg)'; + const animation = + target.animate({ [idlName]: [ 'rotate3d(1, 1, 0, -90deg)', + 'rotate3d(1, 1, 0, 90deg)'] }, + { duration: 1000, fill: 'both', composite: 'accumulate' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: rotate3dToMatrix(1, 1, 0, -Math.PI / 4) }, + { time: 1000, expected: rotate3dToMatrix(1, 1, 0, 3 * Math.PI / 4) }]); + }, `${property}: rotate3d`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + // To calculate expected matrices easily, generate input matrices from + // rotate3d. + target.style[idlName] = rotate3dToMatrix3d(1, 1, 0, Math.PI / 4); + const from = rotate3dToMatrix3d(1, 1, 0, -Math.PI / 2); + const to = rotate3dToMatrix3d(1, 1, 0, Math.PI / 2); + const animation = + target.animate({ [idlName]: [ from, to ] }, + { duration: 1000, fill: 'both', composite: 'accumulate' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: rotate3dToMatrix(1, 1, 0, -Math.PI / 4) }, + { time: 1000, expected: rotate3dToMatrix(1, 1, 0, 3 * Math.PI / 4) }]); + }, `${property}: matrix3d`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const matrixArray = [ 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 1, 1 ]; + + target.style[idlName] = createMatrixFromArray(matrixArray); + const animation = + target.animate({ [idlName]: [ 'none', 'none' ] }, + { duration: 1000, fill: 'both', composite: 'accumulate' }); + + testAnimationSampleMatrices(animation, idlName, + [{ time: 0, expected: matrixArray }, + { time: 1000, expected: matrixArray }]); + }, `${property}: none`); + + // Following tests aim for test the fallback discrete accumulation behavior + // for non-invertible matrices. The non-invertible matrix that we use is the + // singular matrix, matrix(1, 1, 0, 0, 0, 100). + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.animate({ [idlName]: ['matrix(-1, 0, 0, -1, 200, 0)', + 'matrix(-1, 0, 0, -1, 200, 0)'] }, 1000); + const animation = target.animate( + { + [idlName]: [ + 'matrix( 1, 1, 0, 0, 0, 100)', + 'matrix( 1, 1, 0, 0, 0, 100)', + ], + }, + { duration: 1000, composite: 'accumulate' } + ); + testAnimationSampleMatrices(animation, idlName, [ + { time: 0, expected: [1, 1, 0, 0, 0, 100] }, + ]); + }, `${property}: non-invertible matrices (non-invertible onto invertible)`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.animate({ [idlName]: ['matrix( 1, 1, 0, 0, 0, 100)', + 'matrix( 1, 1, 0, 0, 0, 100)'] }, 1000); + const animation = target.animate( + { + [idlName]: [ + 'matrix(-1, 0, 0, -1, 200, 0)', + 'matrix(-1, 0, 0, -1, 200, 0)', + ], + }, + { duration: 1000, composite: 'accumulate' } + ); + testAnimationSampleMatrices(animation, idlName, [ + { time: 0, expected: [-1, 0, 0, -1, 200, 0] }, + ]); + }, `${property}: non-invertible matrices (invertible onto non-invertible)`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + // matrix(0, -1, 1, 0, 250, 0) + target.animate( + { + [idlName]: [ + 'translate(50px) matrix(-1, 0, 0, -1, 200, 0) rotate(90deg)', + 'translate(50px) matrix(-1, 0, 0, -1, 200, 0) rotate(90deg)', + ], + }, + 1000 + ); + // matrix(-1, -1, 0, 0, 100, 100) + const animation = target.animate( + { + [idlName]: [ + 'translate(100px) matrix( 1, 1, 0, 0, 0, 100) rotate(180deg)', + 'translate(100px) matrix( 1, 1, 0, 0, 0, 100) rotate(180deg)', + ], + }, + { duration: 1000, composite: 'accumulate' } + ); + testAnimationSampleMatrices(animation, idlName, [ + { time: 0, expected: [-1, -1, 0, 0, 100, 100] }, + ]); + }, `${property}: non-invertible matrices in matched transform lists (non-invertible onto invertible)`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + // matrix(-1, -1, 0, 0, 100, 100) + target.animate( + { + [idlName]: [ + 'translate(100px) matrix(1, 1, 0, 0, 0, 100) rotate(180deg)', + 'translate(100px) matrix(1, 1, 0, 0, 0, 100) rotate(180deg)', + ], + }, + 1000 + ); + // matrix(0, -1, 1, 0, 250, 0) + const animation = target.animate( + { + [idlName]: [ + 'translate(50px) matrix(-1, 0, 0, -1, 200, 0) rotate(90deg)', + 'translate(50px) matrix(-1, 0, 0, -1, 200, 0) rotate(90deg)', + ], + }, + { duration: 1000, composite: 'accumulate' } + ); + testAnimationSampleMatrices(animation, idlName, [ + { time: 0, expected: [0, -1, 1, 0, 250, 0] }, + ]); + }, `${property}: non-invertible matrices in matched transform lists (invertible onto non-invertible)`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + // matrix(-2, 0, 0, -2, 250, 0) + target.animate( + { + [idlName]: [ + 'translate(50px) matrix(-1, 0, 0, -1, 200, 0) scale(2)', + 'translate(50px) matrix(-1, 0, 0, -1, 200, 0) scale(2)', + ], + }, + 1000 + ); + // matrix(1, 1, 1, 1, 100, 100) + const animation = target.animate( + { + [idlName]: [ + 'translate(100px) matrix(1, 1, 0, 0, 0, 100) skew(45deg)', + 'translate(100px) matrix(1, 1, 0, 0, 0, 100) skew(45deg)', + ], + }, + { duration: 1000, composite: 'accumulate' } + ); + testAnimationSampleMatrices(animation, idlName, [ + { time: 0, expected: [1, 1, 1, 1, 100, 100] }, + ]); + }, `${property}: non-invertible matrices in mismatched transform lists` + + ' (non-invertible onto invertible)'); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + // matrix(1, 1, 1, 1, 100, 100) + target.animate( + { + [idlName]: [ + 'translate(100px) matrix(1, 1, 0, 0, 0, 100) skew(45deg)', + 'translate(100px) matrix(1, 1, 0, 0, 0, 100) skew(45deg)', + ], + }, + 1000 + ); + // matrix(-2, 0, 0, -2, 250, 0) + const animation = target.animate( + { + [idlName]: [ + 'translate(50px) matrix(-1, 0, 0, -1, 200, 0) scale(2)', + 'translate(50px) matrix(-1, 0, 0, -1, 200, 0) scale(2)', + ], + }, + { duration: 1000, composite: 'accumulate' } + ); + testAnimationSampleMatrices(animation, idlName, [ + { time: 0, expected: [-2, 0, 0, -2, 250, 0] }, + ]); + }, `${property}: non-invertible matrices in mismatched transform lists` + + ' (invertible onto non-invertible)'); + }, +}; + +const rotateListType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { + [idlName]: ['45deg', '135deg'], + }, + 1000 + ); + + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '90deg' }]); + }, `${property} without rotation axes`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ '0 1 0 0deg', + '0 1 0 90deg'] }, + 1000); + + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'y 45deg' }]); + }, `${property} with rotation axes`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + + const animation = + target.animate({ [idlName]: [ '0 1 0 0deg', + '0 1 0 720deg'] }, + 1000); + + testAnimationSamples(animation, idlName, + [{ time: 250, expected: 'y 180deg' }]); + }, `${property} with rotation axes and range over 360 degrees`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ '0 1 0 0deg', + '0 1 1 90deg'] }, + 1000); + + testAnimationSampleRotate3d(animation, idlName, + [{ time: 500, expected: '0 0.707107 0.707107 45deg' }]); + }, `${property} with different rotation axes`); + }, + testAddition: function(property, setup) { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '45deg'; + const animation = target.animate({ [idlName]: ['-90deg', '90deg'] }, + { duration: 1000, fill: 'both', + composite: 'add' }); + + testAnimationSamples(animation, idlName, + [{ time: 0, expected: '-45deg' }, + { time: 1000, expected: '135deg' }]); + }, `${property} without rotation axes`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + // Rotation specified in transform property should not affect the computed + // value of |property|. + target.style.transform = 'rotate(20deg)'; + target.style[idlName] = 'y -45deg'; + const animation = + target.animate({ [idlName]: ['0 1 0 90deg', + '0 1 0 180deg'] }, + { duration: 1000, fill: 'both', composite: 'add' }); + + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'y 45deg' }, + { time: 1000, expected: 'y 135deg' }]); + }, `${property} with underlying transform`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ '0 1 0 0deg', + '1 1 1 270deg'] }, + { duration: 1000, fill: 'both', composite: 'add' }); + + testAnimationSampleRotate3d(animation, idlName, + [{ time: 500, expected: '0.57735 0.57735 0.57735 135deg' }]); + }, `${property} with different rotation axes`); + }, + testAccumulation: function(property, setup) { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '45deg'; + const animation = target.animate({ [idlName]: ['-90deg', '90deg'] }, + { duration: 1000, fill: 'both', + composite: 'accumulate' }); + + testAnimationSamples(animation, idlName, + [{ time: 0, expected: '-45deg' }, + { time: 1000, expected: '135deg' }]); + }, `${property} without rotation axes`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style.transform = 'translateX(100px)'; + target.style[idlName] = '1 0 0 -45deg'; + const animation = + target.animate({ [idlName]: ['1 0 0 90deg', + '1 0 0 180deg'] }, + { duration: 1000, fill: 'both', composite: 'accumulate' }); + + testAnimationSamples(animation, idlName, + [{ time: 0, expected: 'x 45deg' }, + { time: 1000, expected: 'x 135deg' }]); + }, `${property} with underlying transform`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ '0 1 0 0deg', + '1 0 1 180deg'] }, + { duration: 1000, fill: 'both', composite: 'accumulate' }); + + testAnimationSampleRotate3d(animation, idlName, + [{ time: 500, expected: '0.707107 0 0.707107 90deg' }]); + }, `${property} with different rotation axes`); + }, +}; + +const translateListType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { + [idlName]: ['200px', '400px'], + }, + 1000 + ); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '300px' }]); + }, `${property} with two unspecified values`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { + [idlName]: ['200px -200px', '400px 400px'], + }, + 1000 + ); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '300px 100px' }]); + }, `${property} with one unspecified value`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { + [idlName]: ['200px -200px 600px', '400px 400px -200px'], + }, + 1000 + ); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '300px 100px 200px' }]); + }, `${property} with all three values specified`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { + [idlName]: ['0% -101px 600px', '400px 50% -200px'], + }, + 1000 + ); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'calc(0% + 200px) calc(25% - 50.5px) 200px' }]); + }, `${property} with combination of percentages and lengths`); + }, + testAddition: function(property, setup) { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '100px'; + const animation = target.animate({ [idlName]: ['-200px', '500px'] }, + { duration: 1000, fill: 'both', + composite: 'add' }); + testAnimationSamples(animation, idlName, + [ { time: 0, expected: '-100px' }, + { time: 1000, expected: '600px' }]); + + }, `${property}`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.transform = 'translateY(100px)'; + target.style[idlName] = '100px'; + const animation = target.animate({ [idlName]: ['-200px', '500px'] }, + { duration: 1000, fill: 'both', + composite: 'add' }); + testAnimationSamples(animation, idlName, + [ { time: 0, expected: '-100px' }, + { time: 1000, expected: '600px' }]); + + }, `${property} with underlying transform`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '50%'; + const animation = target.animate({ [idlName]: ['-200px', '500px'] }, + { duration: 1000, fill: 'both', + composite: 'add' }); + testAnimationSamples(animation, idlName, + [ { time: 0, expected: 'calc(50% - 200px)' }, + { time: 1000, expected: 'calc(50% + 500px)' }]); + + }, `${property} with underlying percentage value`); + }, + testAccumulation: function(property, setup) { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '100px'; + const animation = target.animate({ [idlName]: ['-200px', '500px'] }, + { duration: 1000, fill: 'both', + composite: 'accumulate' }); + testAnimationSamples(animation, idlName, + [ { time: 0, expected: '-100px' }, + { time: 1000, expected: '600px' }]); + }, `${property}`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.transform = 'translateY(100px)'; + target.style[idlName] = '100px'; + const animation = target.animate({ [idlName]: ['-200px', '500px'] }, + { duration: 1000, fill: 'both', + composite: 'accumulate' }); + testAnimationSamples(animation, idlName, + [ { time: 0, expected: '-100px' }, + { time: 1000, expected: '600px' }]); + }, `${property} with transform`); + }, +}; + +const scaleListType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['3', '5'] }, + 1000); + + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '4' }]); + }, `${property} with two unspecified values`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['3 3', '5 5'] }, + 1000); + + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '4' }]); + }, `${property} with one unspecified value`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['3 3 3', '5 5 5'] }, + 1000); + + testAnimationSamples(animation, idlName, + [{ time: 500, expected: '4 4 4' }]); + }, `${property}`); + }, + testAddition: function(property, setup) { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '2'; + const animation = target.animate({ [idlName]: ['-3', '5'] }, + { duration: 1000, fill: 'both', + composite: 'add' }); + + testAnimationSamples(animation, idlName, + [{ time: 0, expected: '-6' }, + { time: 1000, expected: '10' }]); + }, `${property} with two unspecified values`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '2 2'; + const animation = target.animate({ [idlName]: ['-3 -3', '5 5'] }, + { duration: 1000, fill: 'both', + composite: 'add' }); + + testAnimationSamples(animation, idlName, + [{ time: 0, expected: '-6' }, + { time: 1000, expected: '10' }]); + }, `${property} with one unspecified value`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '2 2 2'; + const animation = target.animate({ [idlName]: ['-1 -2 -3', '4 5 6'] }, + { duration: 1000, fill: 'both', + composite: 'add' }); + + testAnimationSamples(animation, idlName, + [{ time: 0, expected: '-2 -4 -6' }, + { time: 1000, expected: '8 10 12' }]); + }, `${property}`); + }, + testAccumulation: function(property, setup) { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '2'; + const animation = target.animate({ [idlName]: ['-3', '5'] }, + { duration: 1000, fill: 'both', + composite: 'accumulate' }); + + testAnimationSamples(animation, idlName, + [{ time: 0, expected: '-2' }, + { time: 1000, expected: '6' }]); + }, `${property} with two unspecified values`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '2 2'; + const animation = target.animate({ [idlName]: ['-3 -3', '5 5'] }, + { duration: 1000, fill: 'both', + composite: 'accumulate' }); + + testAnimationSamples(animation, idlName, + [{ time: 0, expected: '-2' }, + { time: 1000, expected: '6' }]); + }, `${property} with one unspecified value`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '2 2 2'; + const animation = target.animate({ [idlName]: ['-1 -2 -3', '4 5 6'] }, + { duration: 1000, fill: 'both', + composite: 'accumulate' }); + + testAnimationSamples(animation, idlName, + [{ time: 0, expected: '0 -1 -2' }, + { time: 1000, expected: '5 6 7' }]); + }, `${property}`); + }, +}; + +const filterListType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: + ['blur(10px)', 'blur(50px)'] }, + 1000); + + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'blur(30px)' }]); + }, `${property}: blur function`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['hue-rotate(0deg)', + 'hue-rotate(100deg)'] }, + 1000); + + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'hue-rotate(50deg)' }]); + }, `${property}: hue-rotate function with same unit(deg)`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['hue-rotate(10deg)', + 'hue-rotate(100rad)'] }, + 1000); + + // 10deg = 0.1745rad. + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'hue-rotate(2869.79deg)' }]); + }, `${property}: hue-rotate function with different unit(deg -> rad)`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { [idlName]: + ['drop-shadow(10px 10px 10px rgba(255, 0, 0, 0.4))', + 'drop-shadow(50px 50px 50px rgba(0, 0, 255, 0.8))'] }, + 1000); + + testAnimationSamples( + animation, idlName, + [{ time: 500, + expected: 'drop-shadow(rgba(85, 0, 170, 0.6) 30px 30px 30px)' }]); + }, `${property}: drop-shadow function`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { [idlName]: + ['brightness(0.1) contrast(0.1) grayscale(0.1) invert(0.1) ' + + 'opacity(0.1) saturate(0.1) sepia(0.1)', + 'brightness(0.5) contrast(0.5) grayscale(0.5) invert(0.5) ' + + 'opacity(0.5) saturate(0.5) sepia(0.5)'] }, + 1000); + + testAnimationSamples(animation, idlName, + [{ time: 500, + expected: 'brightness(0.3) contrast(0.3) grayscale(0.3) ' + + 'invert(0.3) opacity(0.3) saturate(0.3) sepia(0.3)' }]); + }, `${property}: percentage or numeric-specifiable functions` + + ' (number value)'); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { [idlName]: + ['brightness(10%) contrast(10%) grayscale(10%) invert(10%) ' + + 'opacity(10%) saturate(10%) sepia(10%)', + 'brightness(50%) contrast(50%) grayscale(50%) invert(50%) ' + + 'opacity(50%) saturate(50%) sepia(50%)'] }, + 1000); + + testAnimationSamples(animation, idlName, + [{ time: 500, + expected: 'brightness(0.3) contrast(0.3) grayscale(0.3) ' + + 'invert(0.3) opacity(0.3) saturate(0.3) sepia(0.3)' }]); + }, `${property}: percentage or numeric-specifiable functions` + + ' (percentage value)'); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { [idlName]: + // To make missing filter-function-lists, specified the grayscale. + ['grayscale(0)', + 'grayscale(1) brightness(0) contrast(0) opacity(0) saturate(0)' ]}, + 1000); + + testAnimationSamples(animation, idlName, + [{ time: 500, + expected: 'grayscale(0.5) brightness(0.5) contrast(0.5) ' + + 'opacity(0.5) saturate(0.5)' }]); + }, `${property}: interpolate different length of filter-function-list` + + ' with function which lacuna value is 1'); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { [idlName]: + // To make missing filter-function-lists, specified the opacity. + ['opacity(1)', + 'opacity(0) grayscale(1) invert(1) sepia(1) blur(10px)'] }, + 1000); + + testAnimationSamples(animation, idlName, + [{ time: 500, + expected: + 'opacity(0.5) grayscale(0.5) invert(0.5) sepia(0.5) blur(5px)' }]); + }, `${property}: interpolate different length of filter-function-list` + + ' with function which lacuna value is 0'); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { [idlName]: + ['blur(0px)', + 'blur(10px) drop-shadow(10px 10px 10px rgba(0, 0, 255, 0.8))'] }, + 1000); + + testAnimationSamples(animation, idlName, + [{ time: 500, + // Per the spec: The initial value for interpolation is all length values + // set to 0 and the used color set to transparent. + expected: 'blur(5px) drop-shadow(rgba(0, 0, 255, 0.4) 5px 5px 5px)' }]); + }, `${property}: interpolate different length of filter-function-list` + + ' with drop-shadow function'); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['none', 'blur(10px)'] }, + 1000); + + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'blur(5px)' }]); + }, `${property}: interpolate from none`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate( + { [idlName]: + ['blur(0px) url(\"#f1\")', + 'blur(10px) url(\"#f2\")']}, + 1000); + testAnimationSamples(animation, idlName, + [{ time: 499, expected: 'blur(0px) url(\"#f1\")' }, + { time: 500, expected: 'blur(10px) url(\"#f2\")' }]); + }, `${property}: url function (interpoalte as discrete)`); + }, + + testAddition: function(property, setup) { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'blur(10px)'; + const animation = target.animate({ [idlName]: ['blur(20px)', + 'blur(50px)'] }, + { duration: 1000, composite: 'add' }); + testAnimationSamples(animation, idlName, + [ { time: 0, expected: 'blur(10px) blur(20px)' }]); + }, `${property}: blur on blur`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'blur(10px)'; + const animation = target.animate({ [idlName]: ['brightness(80%)', + 'brightness(40%)'] }, + { duration: 1000, composite: 'add' }); + testAnimationSamples(animation, idlName, + [ { time: 0, expected: 'blur(10px) brightness(0.8)' }]); + }, `${property}: different filter functions`); + }, + + testAccumulation: function(property, setup) { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'blur(10px) brightness(0.3)'; + const animation = target.animate( + { + [idlName]: [ + 'blur(20px) brightness(0.1)', + 'blur(20px) brightness(0.1)', + ], + }, + { duration: 1000, composite: 'accumulate' } + ); + // brightness(0.1) onto brightness(0.3) means + // brightness((0.1 - 1.0) + (0.3 - 1.0) + 1.0). The result of this formula + // is brightness(-0.6) that means brightness(0.0). + testAnimationSamples(animation, idlName, + [ { time: 0, expected: 'blur(30px) brightness(0)' }]); + }, `${property}: same ordered filter functions`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'blur(10px) brightness(1.3)'; + const animation = target.animate( + { + [idlName]: [ + 'brightness(1.2) blur(20px)', + 'brightness(1.2) blur(20px)', + ], + }, + { duration: 1000, composite: 'accumulate' } + ); + // Mismatched ordered functions can't be accumulated. + testAnimationSamples(animation, idlName, + [ { time: 0, expected: 'brightness(1.2) blur(20px)' }]); + }, `${property}: mismatched ordered filter functions`); + }, +}; + +const textShadowListType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ 'none', + 'rgb(100, 100, 100) 10px 10px 10px'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + // Premultiplied + [{ time: 500, expected: 'rgba(100, 100, 100, 0.5) 5px 5px 5px' }]); + }, `${property}: from none to other`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ 'rgb(100, 100, 100) 10px 10px 10px', + 'none' ] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + // Premultiplied + [{ time: 500, expected: 'rgba(100, 100, 100, 0.5) 5px 5px 5px' }]); + }, `${property}: from other to none`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ 'rgb(0, 0, 0) 0px 0px 0px', + 'rgb(100, 100, 100) 10px 10px 10px'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'rgb(50, 50, 50) 5px 5px 5px' }]); + }, `${property}: single shadow`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ 'rgb(0, 0, 0) 0px 0px 0px, ' + + 'rgb(200, 200, 200) 20px 20px 20px', + 'rgb(100, 100, 100) 10px 10px 10px, ' + + 'rgb(100, 100, 100) 10px 10px 10px'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'rgb(50, 50, 50) 5px 5px 5px, ' + + 'rgb(150, 150, 150) 15px 15px 15px' }]); + }, `${property}: shadow list`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ 'rgb(200, 200, 200) 20px 20px 20px', + 'rgb(100, 100, 100) 10px 10px 10px, ' + + 'rgb(100, 100, 100) 10px 10px 10px'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'rgb(150, 150, 150) 15px 15px 15px, ' + + 'rgba(100, 100, 100, 0.5) 5px 5px 5px' }]); + }, `${property}: mismatched list length (from longer to shorter)`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ 'rgb(100, 100, 100) 10px 10px 10px, ' + + 'rgb(100, 100, 100) 10px 10px 10px', + 'rgb(200, 200, 200) 20px 20px 20px'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'rgb(150, 150, 150) 15px 15px 15px, ' + + 'rgba(100, 100, 100, 0.5) 5px 5px 5px' }]); + }, `${property}: mismatched list length (from shorter to longer)`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style.color = 'rgb(0, 255, 0)'; + const animation = + target.animate({ [idlName]: [ 'currentcolor 0px 0px 0px', + 'currentcolor 10px 10px 10px'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'rgb(0, 255, 0) 5px 5px 5px' }]); + }, `${property}: with currentcolor`); + }, + + testAddition: function(property, setup) { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rgb(0, 0, 0) 0px 0px 0px'; + const animation = + target.animate({ [idlName]: [ 'rgb(120, 120, 120) 10px 10px 10px', + 'rgb(120, 120, 120) 10px 10px 10px'] }, + { duration: 1000, composite: 'add' }); + testAnimationSamples(animation, idlName, + [ { time: 0, expected: 'rgb(0, 0, 0) 0px 0px 0px, ' + + 'rgb(120, 120, 120) 10px 10px 10px' }]); + }, `${property}: shadow`); + }, + + testAccumulation: function(property, setup) { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rgb(120, 120, 120) 10px 10px 10px'; + const animation = + target.animate({ [idlName]: [ 'rgb(120, 120, 120) 10px 10px 10px', + 'rgb(120, 120, 120) 10px 10px 10px'] }, + { duration: 1000, composite: 'accumulate' }); + testAnimationSamples(animation, idlName, + [ { time: 0, expected: 'rgb(240, 240, 240) 20px 20px 20px' }]); + }, `${property}: shadow`); + }, +}; + + +const boxShadowListType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ 'none', + 'rgb(100, 100, 100) 10px 10px 10px 0px'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + // Premultiplied + [{ time: 500, expected: 'rgba(100, 100, 100, 0.5) 5px 5px 5px 0px' }]); + }, `${property}: from none to other`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ 'rgb(100, 100, 100) 10px 10px 10px 0px', + 'none' ] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + // Premultiplied + [{ time: 500, expected: 'rgba(100, 100, 100, 0.5) 5px 5px 5px 0px' }]); + }, `${property}: from other to none`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ 'rgb(0, 0, 0) 0px 0px 0px 0px', + 'rgb(100, 100, 100) 10px 10px 10px 0px'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'rgb(50, 50, 50) 5px 5px 5px 0px' }]); + }, `${property}: single shadow`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ 'rgb(0, 0, 0) 0px 0px 0px 0px, ' + + 'rgb(200, 200, 200) 20px 20px 20px 20px', + 'rgb(100, 100, 100) 10px 10px 10px 0px, ' + + 'rgb(100, 100, 100) 10px 10px 10px 0px'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'rgb(50, 50, 50) 5px 5px 5px 0px, ' + + 'rgb(150, 150, 150) 15px 15px 15px 10px' }]); + }, `${property}: shadow list`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ 'rgb(200, 200, 200) 20px 20px 20px 20px', + 'rgb(100, 100, 100) 10px 10px 10px 0px, ' + + 'rgb(100, 100, 100) 10px 10px 10px 0px'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'rgb(150, 150, 150) 15px 15px 15px 10px, ' + + 'rgba(100, 100, 100, 0.5) 5px 5px 5px 0px' }]); + }, `${property}: mismatched list length (from shorter to longer)`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: [ 'rgb(100, 100, 100) 10px 10px 10px 0px, ' + + 'rgb(100, 100, 100) 10px 10px 10px 0px', + 'rgb(200, 200, 200) 20px 20px 20px 20px']}, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'rgb(150, 150, 150) 15px 15px 15px 10px, ' + + 'rgba(100, 100, 100, 0.5) 5px 5px 5px 0px' }]); + }, `${property}: mismatched list length (from longer to shorter)`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style.color = 'rgb(0, 255, 0)'; + const animation = + target.animate({ [idlName]: [ 'currentcolor 0px 0px 0px 0px', + 'currentcolor 10px 10px 10px 10px'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 500, expected: 'rgb(0, 255, 0) 5px 5px 5px 5px' }]); + }, `${property}: with currentcolor`); + }, + + testAddition: function(property, setup) { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rgb(0, 0, 0) 0px 0px 0px 0px'; + const animation = + target.animate({ [idlName]: [ 'rgb(120, 120, 120) 10px 10px 10px 0px', + 'rgb(120, 120, 120) 10px 10px 10px 0px'] }, + { duration: 1000, composite: 'add' }); + testAnimationSamples(animation, idlName, + [ { time: 0, expected: 'rgb(0, 0, 0) 0px 0px 0px 0px, ' + + 'rgb(120, 120, 120) 10px 10px 10px 0px' }]); + }, `${property}: shadow`); + }, + + testAccumulation: function(property, setup) { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rgb(120, 120, 120) 10px 10px 10px 10px'; + const animation = + target.animate({ [idlName]: [ 'rgb(120, 120, 120) 10px 10px 10px 10px', + 'rgb(120, 120, 120) 10px 10px 10px 10px'] }, + { duration: 1000, composite: 'accumulate' }); + testAnimationSamples(animation, idlName, + [ { time: 0, expected: 'rgb(240, 240, 240) 20px 20px 20px 20px' }]); + }, `${property}: shadow`); + }, +}; + +const positionType = { + testInterpolation: (property, setup) => { + lengthPairType.testInterpolation(property, setup); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: ['10% 10%', '50% 50%'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples( + animation, idlName, + [{ time: 500, expected: calcFromPercentage(idlName, '30% 30%') }]); + }, `${property} supports animating as a position of percent`); + }, + + testAdditionOrAccumulation: (property, setup, composite) => { + lengthPairType.testAddition(property, setup); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '60% 60%'; + const animation = target.animate({ [idlName]: ['70% 70%', '100% 100%'] }, + { duration: 1000, composite }); + testAnimationSamples( + animation, idlName, + [{ time: 0, expected: calcFromPercentage(idlName, '130% 130%') }]); + }, `${property}: position of percentage`); + }, + + testAddition: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'add'); + }, + + testAccumulation: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'accumulate'); + }, +}; + +const rectType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: + ['rect(10px, 10px, 10px, 10px)', + 'rect(50px, 50px, 50px, 50px)'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples( + animation, idlName, + [{ time: 500, expected: 'rect(30px, 30px, 30px, 30px)' }]); + }, `${property} supports animating as a rect`); + }, + + testAdditionOrAccumulation: (property, setup, composite) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = 'rect(100px, 100px, 100px, 100px)'; + const animation = target.animate({ [idlName]: + ['rect(10px, 10px, 10px, 10px)', + 'rect(10px, 10px, 10px, 10px)'] }, + { duration: 1000, composite }); + testAnimationSamples( + animation, idlName, + [{ time: 0, expected: 'rect(110px, 110px, 110px, 110px)' }]); + }, `${property}: rect`); + }, + + testAddition: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'add'); + }, + + testAccumulation: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'accumulate'); + }, +} + +// stroke-dasharray: none | [ <length> | <percentage> | <number> ]* +const dasharrayType = { + testInterpolation: (property, setup) => { + percentageType.testInterpolation(property, setup); + positiveNumberType.testInterpolation(property, setup, 'px'); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: + ['8, 16, 4', + '4, 8, 12, 16'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples( + animation, idlName, + [{ time: 500, expected: '6px, 12px, 8px, 12px, 10px, 6px, 10px, 16px, 4px, 8px, 14px, 10px' }]); + }, `${property} supports animating as a dasharray (mismatched length)`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = target.animate({ [idlName]: + ['2, 50%, 6, 10', + '6, 30%, 2, 2'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples( + animation, idlName, + [{ time: 500, expected: '4px, 40%, 4px, 6px' }]); + }, `${property} supports animating as a dasharray (mixed lengths and percentages)`); + + }, + + // Note that stroke-dasharray is neither additive nor cumulative, so we should + // write this additive test case that animating value replaces underlying + // values. + // See https://www.w3.org/TR/SVG2/painting.html#StrokeDashing. + testAdditionOrAccumulation: (property, setup, composite) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '6, 30%, 2px'; + const animation = target.animate({ [idlName]: + ['1, 2, 3, 4, 5', + '6, 7, 8, 9, 10'] }, + { duration: 1000, composite }); + testAnimationSamples( + animation, idlName, + [{ time: 0, expected: '1px, 2px, 3px, 4px, 5px' }]); + }, `${property}: dasharray`); + }, + + testAddition: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'add'); + }, + + testAccumulation: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'accumulate'); + }, +} + +const fontVariationSettingsType = { + testInterpolation: (property, setup) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: ['"wght" 1.1', '"wght" 1.5'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamples(animation, idlName, + [{ time: 0, expected: '"wght" 1.1' }, + { time: 250, expected: '"wght" 1.2' }, + { time: 750, expected: '"wght" 1.4' } ]); + }, `${property} supports animation as float`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: ['"wdth" 1, "wght" 1.1', + '"wght" 1.5, "wdth" 5'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamplesWithAnyOrder( + animation, idlName, + [{ time: 0, expected: '"wdth" 1, "wght" 1.1' }, + { time: 250, expected: '"wdth" 2, "wght" 1.2' }, + { time: 750, expected: '"wdth" 4, "wght" 1.4' } ]); + }, `${property} supports animation as float with multiple tags`); + + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + const animation = + target.animate({ [idlName]: ['"wdth" 1, "wght" 1.1', + '"wght" 10, "wdth" 5, "wght" 1.5'] }, + { duration: 1000, fill: 'both' }); + testAnimationSamplesWithAnyOrder( + animation, idlName, + [{ time: 250, expected: '"wdth" 2, "wght" 1.2' }, + { time: 750, expected: '"wdth" 4, "wght" 1.4' } ]); + }, `${property} supports animation as float with multiple duplicate tags`); + }, + + testAdditionOrAccumulation: (property, setup, composite) => { + test(t => { + const idlName = propertyToIDL(property); + const target = createTestElement(t, setup); + target.style[idlName] = '"wght" 1'; + const animation = + target.animate({ [idlName]: ['"wght" 1.1', '"wght" 1.5'] }, + { duration: 1000, composite }); + testAnimationSamples(animation, idlName, + [{ time: 250, expected: '"wght" 2.2' }, + { time: 750, expected: '"wght" 2.4' } ]); + }, `${property} with composite type ${composite}`); + }, + + testAddition: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'add'); + }, + + testAccumulation: function(property, setup) { + this.testAdditionOrAccumulation(property, setup, 'accumulate'); + }, +} + +const types = { + color: colorType, + discrete: discreteType, + filterList: filterListType, + integer: integerType, + positiveInteger: positiveIntegerType, + length: lengthType, + percentage: percentageType, + lengthPercentageOrCalc: lengthPercentageOrCalcType, + lengthPair: lengthPairType, + positiveNumber: positiveNumberType, + opacity: opacityType, + transformList: transformListType, + rotateList: rotateListType, + translateList: translateListType, + scaleList: scaleListType, + visibility: visibilityType, + boxShadowList: boxShadowListType, + textShadowList: textShadowListType, + rect: rectType, + position: positionType, + dasharray: dasharrayType, + fontVariationSettings: fontVariationSettingsType, +}; diff --git a/testing/web-platform/tests/web-animations/animation-model/animation-types/property-utils.js b/testing/web-platform/tests/web-animations/animation-model/animation-types/property-utils.js new file mode 100644 index 0000000000..d3a7b12a61 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/property-utils.js @@ -0,0 +1,38 @@ +'use strict'; + +function runAnimationTypeTest(gCSSProperties, testType) { + for (const property in gCSSProperties) { + if (!isSupported(property)) { + continue; + } + + const setupFunction = gCSSProperties[property].setup; + for (const animationType of gCSSProperties[property].types) { + let typeObject; + let animationTypeString; + if (typeof animationType === 'string') { + typeObject = types[animationType]; + animationTypeString = animationType; + } else if (typeof animationType === 'object' && + animationType.type && typeof animationType.type === 'string') { + typeObject = types[animationType.type]; + animationTypeString = animationType.type; + } + + // First, test that the animation type object has 'testAccumulation', or + // 'testAddition', or 'testInterpolation'. + // We use test() function here so that we can continue the remainder tests + // even if this test fails. + test(t => { + assert_own_property(typeObject, testType, animationTypeString + + ` should have ${testType} property`); + assert_equals(typeof typeObject[testType], 'function', + `${testType} method should be a function`); + }, `${property} (type: ${animationTypeString}) has ${testType} function`); + + if (typeObject[testType] && typeof typeObject[testType] === 'function') { + typeObject[testType](property, setupFunction, animationType.options); + } + } + } +} diff --git a/testing/web-platform/tests/web-animations/animation-model/animation-types/visibility.html b/testing/web-platform/tests/web-animations/animation-model/animation-types/visibility.html new file mode 100644 index 0000000000..f5a60b4e2c --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/visibility.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation type for the 'visibility' property</title> +<!-- FIXME: The following spec link should be updated once this definition has + been moved to CSS Values & Units. --> +<link rel="help" href="https://drafts.csswg.org/css-transitions/#animtype-visibility"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<div id="target"></div> +<script> +'use strict'; + +test(t => { + const div = createDiv(t); + const anim = div.animate({ visibility: ['hidden','visible'] }, + { duration: 100 * MS_PER_SEC, fill: 'both' }); + + anim.currentTime = 0; + assert_equals(getComputedStyle(div).visibility, 'hidden', + 'Visibility when progress = 0'); + + anim.currentTime = 10 * MS_PER_SEC + 1; + assert_equals(getComputedStyle(div).visibility, 'visible', + 'Visibility when progress > 0 due to linear easing'); + + anim.finish(); + assert_equals(getComputedStyle(div).visibility, 'visible', + 'Visibility when progress = 1'); + +}, 'Visibility clamping behavior'); + +test(t => { + const div = createDiv(t); + const anim = div.animate({ visibility: ['hidden', 'visible'] }, + { duration: 100 * MS_PER_SEC, fill: 'both', + easing: 'cubic-bezier(0.25, -0.6, 0, 0.5)' }); + + anim.currentTime = 0; + assert_equals(getComputedStyle(div).visibility, 'hidden', + 'Visibility when progress = 0'); + + // Timing function is below zero. So we expected visibility is hidden. + anim.currentTime = 10 * MS_PER_SEC + 1; + assert_equals(getComputedStyle(div).visibility, 'hidden', + 'Visibility when progress < 0 due to cubic-bezier easing'); + + anim.currentTime = 60 * MS_PER_SEC; + assert_equals(getComputedStyle(div).visibility, 'visible', + 'Visibility when progress > 0 due to cubic-bezier easing'); + +}, 'Visibility clamping behavior with an easing that has a negative component'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/animation-model/combining-effects/applying-interpolated-transform.html b/testing/web-platform/tests/web-animations/animation-model/combining-effects/applying-interpolated-transform.html new file mode 100644 index 0000000000..c101b5f0cf --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/combining-effects/applying-interpolated-transform.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title> apply interpolated transform on multiple keyframes</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#keyframes-section"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<div id="target"></div> +<script> +'use strict'; +// This test tests the correctness if animation behavior under +// box-size-denpendent and non-box-size-dependent transformation. +test(t => { + var div_1 = createDiv(t); + div_1.style.width = "100px"; + // Non-pairwise compatible transforms that are effectively no-ops with a + // matrix fallback. Both rotate(360deg) and scale(1) are identity matrix, + // making it easy to compute. + const keyframe1 = [ + {"transform":"translateX( 200px ) rotate( 360deg )"}, + {"transform":"translateX( 100% ) scale( 1 )"}, + ]; + const keyframe2 = [ + {"transform":"translateX( 200px ) rotate( 360deg )"}, + {"transform":"translateX( 100% ) scale( 1 )"}, + {"transform":"none"}, + {} + ]; + + const animation1 = div_1.animate(keyframe1, { + "duration":3000, + "easing":"linear", + }); + const animation2 = div_1.animate(keyframe2, { + "duration":3000, + "easing":"linear", + }); + // new animation on the second div, using px value instead of % as a reference + + var div_2 = createDiv(t); + div_2.style.width = "100px"; + const keyframe3 = [ + {"transform":"translateX( 200px ) rotate( 360deg )"}, + {"transform":"translateX( 100px ) scale( 1 )"}, + ]; + const keyframe4 = [ + {"transform":"translateX( 200px ) rotate( 360deg )"}, + {"transform":"translateX( 100px ) scale( 1 )"}, + {"transform":"none"}, + {} + ]; + + const animation3 = div_2.animate(keyframe1, { + "duration":3000, + "easing":"linear", + }); + const animation4 = div_2.animate(keyframe2, { + "duration":3000, + "easing":"linear", + "composite": 'replace', + }); + animation1.pause(); + animation2.pause(); + animation3.pause(); + animation4.pause(); + var i; + for (i = 0; i <= 30; i++) { + animation2.currentTime = 100 * i; + animation4.currentTime = 100 * i; + var box_size_dependent_transform = getComputedStyle(div_1)['transform']; + var reference_transform = getComputedStyle(div_2)['transform'] + var progress = i / 30; + // The second animation replaces the first animation. As the rotate and + // scale perations are effectively no-ops when the matrix fallback is + // applied. The expected behavior is to go from x-postion 200px to 0 in the + // first 2000ms and go back to x-position 200px in the last 1000ms. + var expected_transform = 'matrix(1, 0, 0, 1, $1, 0)' + .replace('$1', Math.max(200 - 300 * progress, 0) + + Math.max(0, -400 + 600 * progress)); + assert_matrix_equals(box_size_dependent_transform, reference_transform); + assert_matrix_equals(reference_transform, expected_transform); + } +}) +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/animation-model/combining-effects/applying-the-composited-result.html b/testing/web-platform/tests/web-animations/animation-model/combining-effects/applying-the-composited-result.html new file mode 100644 index 0000000000..336115e577 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/combining-effects/applying-the-composited-result.html @@ -0,0 +1,29 @@ +<!doctype html> +<meta charset=utf-8> +<title>Applying the composited result</title> +<link rel="help" + href="https://drafts.csswg.org/web-animations-1/#applying-the-composited-result"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src="../../testcommon.js"></script> +<div id="log"></div> +<script> +'use strict'; + +promise_test(async t => { + const div = createDiv(t); + div.style.marginLeft = '10px'; + const animation = div.animate( + { marginLeft: ['100px', '200px'] }, + 100 * MS_PER_SEC + ); + await animation.ready; + + animation.finish(); + + const marginLeft = parseFloat(getComputedStyle(div).marginLeft); + assert_equals(marginLeft, 10, 'The computed style should be reset'); +}, 'Finishing an animation that does not fill forwards causes its animation' + + ' style to be cleared'); + +</script> diff --git a/testing/web-platform/tests/web-animations/animation-model/combining-effects/effect-composition.html b/testing/web-platform/tests/web-animations/animation-model/combining-effects/effect-composition.html new file mode 100644 index 0000000000..70a8cdd92c --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/combining-effects/effect-composition.html @@ -0,0 +1,154 @@ +<!doctype html> +<meta charset=utf-8> +<title>Effect composition</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#effect-composition"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src="../../testcommon.js"></script> +<div id="log"></div> +<script> +'use strict'; + +for (const composite of ['accumulate', 'add']) { + test(t => { + const div = createDiv(t); + div.style.marginLeft = '10px'; + const anim = + div.animate({ marginLeft: ['0px', '10px'], composite }, 100); + + anim.currentTime = 50; + assert_equals(getComputedStyle(div).marginLeft, '15px', + 'Animated margin-left style at 50%'); + }, `${composite} onto the base value`); + + test(t => { + const div = createDiv(t); + div.style.marginLeft = '10px'; + const anim = + div.animate([{ marginLeft: '20px', offset: 0.25 }, { marginLeft: '30px', offset: 0.75 }], + { duration: 100, composite }); + + anim.currentTime = 50; + assert_equals(getComputedStyle(div).marginLeft, '35px', + 'Animated margin-left style at 50%'); + }, `${composite} onto the base value when the interval does not include the 0 or 1 keyframe`); + + test(t => { + const div = createDiv(t); + const anims = []; + anims.push(div.animate({ marginLeft: ['10px', '20px'], + composite: 'replace' }, + 100)); + anims.push(div.animate({ marginLeft: ['0px', '10px'], + composite }, + 100)); + + for (const anim of anims) { + anim.currentTime = 50; + } + + assert_equals(getComputedStyle(div).marginLeft, '20px', + 'Animated style at 50%'); + }, `${composite} onto an underlying animation value`); + + test(t => { + const div = createDiv(t); + const anims = []; + anims.push(div.animate({ transform: 'translateX(100px)' }, { duration: 100, composite: 'replace' })); + anims.push(div.animate({ transform: 'translateY(100px)' }, { duration: 100, composite })); + + for (const anim of anims) { + anim.currentTime = 50; + } + + assert_equals(getComputedStyle(div).transform, 'matrix(1, 0, 0, 1, 50, 50)', + 'Animated style at 50%'); + }, `${composite} onto an underlying animation value with implicit from values`); + + test(t => { + const div = createDiv(t); + const anims = []; + anims.push(div.animate([{ offset: 1, transform: 'translateX(100px)' }], { duration: 100, composite: 'replace' })); + anims.push(div.animate([{ offset: 1, transform: 'translateY(100px)' }], { duration: 100, composite })); + + for (const anim of anims) { + anim.currentTime = 50; + } + + assert_equals(getComputedStyle(div).transform, 'matrix(1, 0, 0, 1, 50, 50)', + 'Animated style at 50%'); + }, `${composite} onto an underlying animation value with implicit to values`); + + test(t => { + const div = createDiv(t); + div.style.marginLeft = '10px'; + const anim = + div.animate([{ marginLeft: '10px', composite }, + { marginLeft: '30px', composite: 'replace' }], + 100); + + anim.currentTime = 50; + assert_equals(getComputedStyle(div).marginLeft, '25px', + 'Animated style at 50%'); + }, `Composite when mixing ${composite} and replace`); + + test(t => { + const div = createDiv(t); + div.style.marginLeft = '10px'; + const anim = + div.animate([{ marginLeft: '10px', composite: 'replace' }, + { marginLeft: '20px' }], + { duration: 100 , composite }); + + anim.currentTime = 50; + assert_equals(getComputedStyle(div).marginLeft, '20px', + 'Animated style at 50%'); + }, `${composite} specified on a keyframe overrides the composite mode of` + + ' the effect'); + + test(t => { + const div = createDiv(t); + div.style.marginLeft = '10px'; + const anim = + div.animate([{ marginLeft: '10px', composite: 'replace' }, + { marginLeft: '20px' }], + 100); + + anim.effect.composite = composite; + anim.currentTime = 50; // (10 + (10 + 20)) * 0.5 + assert_equals(getComputedStyle(div).marginLeft, '20px', + 'Animated style at 50%'); + }, 'unspecified composite mode on a keyframe is overriden by setting' + + ` ${composite} of the effect`); +} + +test(t => { + const div = createDiv(t); + const anims = []; + anims.push(div.animate({ marginLeft: ['10px', '20px'], + composite: 'replace' }, + 100)); + anims.push(div.animate({ marginLeft: ['0px', '10px'], + composite: 'add' }, + 100)); + // This should fully replace the previous effects. + anims.push(div.animate({ marginLeft: ['20px', '30px'], + composite: 'replace' }, + 100)); + anims.push(div.animate({ marginLeft: ['30px', '40px'], + composite: 'add' }, + 100)); + + for (const anim of anims) { + anim.currentTime = 50; + } + + // The result of applying the above effect stack is: + // underlying = 0.5 * 20 + 0.5 * 30 = 25 + // result = 0.5 * (underlying + 30px) + 0.5 * (underlying + 40px) + // = 60 + assert_equals(getComputedStyle(div).marginLeft, '60px', + 'Animated style at 50%'); +}, 'Composite replace fully replaces the underlying animation value'); + +</script> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/computed-keyframes-shorthands.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/computed-keyframes-shorthands.html new file mode 100644 index 0000000000..ff62a23bce --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/computed-keyframes-shorthands.html @@ -0,0 +1,30 @@ +<!doctype html> +<meta charset=utf-8> +<title>Calculating computed keyframes: Shorthand properties</title> +<link rel="help" href="https://drafts.csswg.org/web-animations-1/#calculating-computed-keyframes"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<div id="target"></div> +<script> +'use strict'; + +test(t => { + const div = createDiv(t); + div.style.opacity = '0'; + + const animation = div.animate({ all: 'initial' }, 100 * MS_PER_SEC); + animation.currentTime = 50 * MS_PER_SEC; + + assert_approx_equals( + parseFloat(getComputedStyle(div).opacity), + 0.5, + 0.0001, + 'Should be half way through an opacity animation' + ); +}, 'It should be possible to animate the all shorthand'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-in-removed-iframe-crash.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-in-removed-iframe-crash.html new file mode 100644 index 0000000000..209ede786f --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-in-removed-iframe-crash.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>Querying keyframes for an effect in a removed iframe should not crash + </title> +<link rel="help" href="https://www.w3.org/TR/web-animations-1/#dom-keyframeeffect-getkeyframes"> +</head> +<body> + <div id="target"></div> + <iframe id="iframe"></iframe> +</body> +<script type="text/javascript"> + const target = document.getElementById('target'); + const iframe = document.getElementById('iframe'); + const keyframes = [{ background: 'green' }, { background: 'blue' }, ]; + const effect = new iframe.contentWindow.KeyframeEffect(target, keyframes); + iframe.parentNode.removeChild(iframe); + const result = effect.getKeyframes(); +</script> +</html> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-on-marquee-parent-crash.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-on-marquee-parent-crash.html new file mode 100644 index 0000000000..2948d6b66e --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-on-marquee-parent-crash.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<title>Animating marquee's parent inside display:none</title> +<link rel="help" href="https://drafts.csswg.org/web-animations-1/"> +<link rel="help" href="https://crbug.com/1290016"> +<div id=container></div> +<script> + let outer = document.createElement('div'); + outer.style.display = 'none'; + let inner = document.createElement('div'); + let marquee = document.createElement('marquee'); + + let effect = new KeyframeEffect(inner, [{'width': '1px'}], 1); + let anim = new Animation(effect, null); + anim.pause(); + + document.body.append(outer); + outer.append(inner); + inner.append(marquee); + + // Don't crash. +</script> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-context-filling.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-context-filling.html new file mode 100644 index 0000000000..fcb7f13126 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-context-filling.html @@ -0,0 +1,377 @@ +<!doctype html> +<meta charset=utf-8> +<title>The effect value of a keyframe effect: Forwards-filling animations whose + values depend on their context (target element)</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#calculating-computed-keyframes"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(t => { + const div = createDiv(t); + div.style.fontSize = '10px'; + const animation = div.animate( + [{ marginLeft: '10em' }, { marginLeft: '20em' }], + { duration: 1000, fill: 'forwards' } + ); + animation.finish(); + assert_equals( + getComputedStyle(div).marginLeft, + '200px', + 'Effect value before updating font-size' + ); + + div.style.fontSize = '20px'; + + assert_equals( + getComputedStyle(div).marginLeft, + '400px', + 'Effect value after updating font-size' + ); +}, 'Filling effect values reflect changes to font-size on element'); + +test(t => { + const parentDiv = createDiv(t); + const div = createDiv(t); + parentDiv.appendChild(div); + parentDiv.style.fontSize = '10px'; + + const animation = div.animate( + [{ marginLeft: '10em' }, { marginLeft: '20em' }], + { duration: 1000, fill: 'forwards' } + ); + animation.finish(); + assert_equals( + getComputedStyle(div).marginLeft, + '200px', + 'Effect value before updating font-size on parent element' + ); + + parentDiv.style.fontSize = '20px'; + + assert_equals( + getComputedStyle(div).marginLeft, + '400px', + 'Effect value after updating font-size on parent element' + ); +}, 'Filling effect values reflect changes to font-size on parent element'); + +test(t => { + const div = createDiv(t); + div.style.setProperty('--target', '100px'); + const animation = div.animate( + [{ marginLeft: '0px' }, { marginLeft: 'var(--target)' }], + { duration: 1000, fill: 'forwards' } + ); + animation.finish(); + assert_equals( + getComputedStyle(div).marginLeft, + '100px', + 'Effect value before updating variable' + ); + + div.style.setProperty('--target', '200px'); + + assert_equals( + getComputedStyle(div).marginLeft, + '200px', + 'Effect value after updating variable' + ); +}, 'Filling effect values reflect changes to variables on element'); + +test(t => { + const parentDiv = createDiv(t); + const div = createDiv(t); + parentDiv.appendChild(div); + + parentDiv.style.setProperty('--target', '10em'); + parentDiv.style.fontSize = '10px'; + + const animation = div.animate( + [{ marginLeft: '0px' }, { marginLeft: 'calc(var(--target) * 2)' }], + { duration: 1000, fill: 'forwards' } + ); + animation.finish(); + assert_equals( + getComputedStyle(div).marginLeft, + '200px', + 'Effect value before updating variable' + ); + + parentDiv.style.setProperty('--target', '20em'); + + assert_equals( + getComputedStyle(div).marginLeft, + '400px', + 'Effect value after updating variable' + ); +}, 'Filling effect values reflect changes to variables on parent element'); + +test(t => { + const div = createDiv(t); + const animation = div.animate( + [{ marginLeft: '100px' }, { marginLeft: '200px' }], + { duration: 1000, fill: 'forwards' } + ); + animation.finish(); + assert_equals( + getComputedStyle(div).marginLeft, + '200px', + 'Effect value before updating the animation' + ); + + animation.effect.setKeyframes([ + { marginLeft: '100px' }, + { marginLeft: '300px' }, + ]); + + assert_equals( + getComputedStyle(div).marginLeft, + '300px', + 'Effect value after updating the animation' + ); +}, 'Filling effect values reflect changes to the the animation\'s keyframes'); + +test(t => { + const div = createDiv(t); + div.style.marginLeft = '100px'; + const animation = div.animate( + [{ marginLeft: '100px' }, { marginLeft: '200px' }], + { duration: 1000, fill: 'forwards' } + ); + animation.finish(); + assert_equals( + getComputedStyle(div).marginLeft, + '200px', + 'Effect value before updating the animation' + ); + + animation.effect.composite = 'add'; + + assert_equals( + getComputedStyle(div).marginLeft, + '300px', + 'Effect value after updating the composite mode' + ); +}, 'Filling effect values reflect changes to the the animation\'s composite mode'); + +test(t => { + const div = createDiv(t); + const animation = div.animate( + [{ marginLeft: '0px' }, { marginLeft: '100px' }], + { duration: 1000, iterations: 2, fill: 'forwards' } + ); + animation.finish(); + assert_equals( + getComputedStyle(div).marginLeft, + '100px', + 'Effect value before updating the animation' + ); + + animation.effect.iterationComposite = 'accumulate'; + + assert_equals( + getComputedStyle(div).marginLeft, + '200px', + 'Effect value after updating the iteration composite mode' + ); +}, 'Filling effect values reflect changes to the the animation\'s iteration composite mode'); + +test(t => { + const div = createDiv(t); + div.style.marginLeft = '100px'; + const animation = div.animate( + [{ marginLeft: '100px' }, { marginLeft: '200px' }], + { duration: 1000, fill: 'forwards', composite: 'add' } + ); + animation.finish(); + assert_equals( + getComputedStyle(div).marginLeft, + '300px', + 'Effect value before updating underlying value' + ); + + div.style.marginLeft = '200px'; + + assert_equals( + getComputedStyle(div).marginLeft, + '400px', + 'Effect value after updating underlying value' + ); +}, 'Filling effect values reflect changes to the base value when using' + + ' additive animation'); + +test(t => { + const div = createDiv(t); + div.style.marginLeft = '100px'; + const animation = div.animate( + [{ marginLeft: '100px' }, { marginLeft: '200px', composite: 'add' }], + { duration: 1000, fill: 'forwards' } + ); + animation.finish(); + assert_equals( + getComputedStyle(div).marginLeft, + '300px', + 'Effect value before updating underlying value' + ); + + div.style.marginLeft = '200px'; + + assert_equals( + getComputedStyle(div).marginLeft, + '400px', + 'Effect value after updating underlying value' + ); +}, 'Filling effect values reflect changes to the base value when using' + + ' additive animation on a single keyframe'); + +test(t => { + const div = createDiv(t); + div.style.marginLeft = '0px'; + const animation = div.animate([{ marginLeft: '100px', offset: 0 }], { + duration: 1000, + fill: 'forwards', + }); + animation.finish(); + assert_equals( + getComputedStyle(div).marginLeft, + '0px', + 'Effect value before updating underlying value' + ); + + div.style.marginLeft = '200px'; + + assert_equals( + getComputedStyle(div).marginLeft, + '200px', + 'Effect value after updating underlying value' + ); +}, 'Filling effect values reflect changes to the base value when using' + + ' the fill value is an implicit keyframe'); + +test(t => { + const parentDiv = createDiv(t); + const div = createDiv(t); + parentDiv.appendChild(div); + parentDiv.style.fontSize = '10px'; + div.style.marginLeft = '10em'; + // Computed underlying margin-left is 100px + + const animation = div.animate( + [{ marginLeft: '100px' }, { marginLeft: '200px' }], + { duration: 1000, fill: 'forwards', composite: 'add' } + ); + animation.finish(); + assert_equals( + getComputedStyle(div).marginLeft, + '300px', + 'Effect value before updating font-size on parent' + ); + + parentDiv.style.fontSize = '20px'; + // Computed underlying margin-left is now 200px + + assert_equals( + getComputedStyle(div).marginLeft, + '400px', + 'Effect value after updating font-size on parent' + ); +}, 'Filling effect values reflect changes to the base value via a' + + ' parent element'); + +test(t => { + const div = createDiv(t); + const animationA = div.animate( + [{ marginLeft: '0px' }, { marginLeft: '100px' }], + { duration: 2000, fill: 'forwards', easing: 'step-end' } + ); + const animationB = div.animate( + [{ marginLeft: '100px' }, { marginLeft: '200px' }], + { duration: 1000, fill: 'forwards', composite: 'add' } + ); + animationB.finish(); + assert_equals( + getComputedStyle(div).marginLeft, + '200px', + 'Effect value before updating underyling animation' + ); + + // Go to end of the underlying animation so that it jumps to 100px + animationA.finish(); + + assert_equals( + getComputedStyle(div).marginLeft, + '300px', + 'Effect value after updating underlying animation' + ); +}, 'Filling effect values reflect changes to underlying animations'); + +test(t => { + const parentDiv = createDiv(t); + const div = createDiv(t); + parentDiv.appendChild(div); + + parentDiv.style.fontSize = '10px'; + + const animationA = div.animate( + [{ marginLeft: '0px' }, { marginLeft: '10em' }], + { duration: 2000, fill: 'forwards', easing: 'step-start' } + ); + const animationB = div.animate( + [{ marginLeft: '100px' }, { marginLeft: '200px' }], + { duration: 1000, fill: 'forwards', composite: 'add' } + ); + animationB.finish(); + assert_equals( + getComputedStyle(div).marginLeft, + '300px', + 'Effect value before updating parent font-size' + ); + + parentDiv.style.fontSize = '20px'; + // At this point the underlying animation's output should jump to 200px. + + assert_equals( + getComputedStyle(div).marginLeft, + '400px', + 'Effect value after updating parent font-size' + ); +}, 'Filling effect values reflect changes to underlying animations via a' + + ' a parent element'); + +test(t => { + const div = createDiv(t); + const animationA = div.animate( + [{ marginLeft: '0px' }, { marginLeft: '0px' }], + { duration: 2000, fill: 'forwards' } + ); + const animationB = div.animate( + [{ marginLeft: '100px' }, { marginLeft: '200px' }], + { duration: 1000, fill: 'forwards', composite: 'add' } + ); + animationB.finish(); + assert_equals( + getComputedStyle(div).marginLeft, + '200px', + 'Effect value before updating underyling animation' + ); + + animationA.effect.setKeyframes([ + { marginLeft: '100px' }, + { marginLeft: '100px' }, + ]); + + assert_equals( + getComputedStyle(div).marginLeft, + '300px', + 'Effect value after updating underlying animation' + ); +}, 'Filling effect values reflect changes to underlying animations made by' + + ' directly changing the keyframes'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-context.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-context.html new file mode 100644 index 0000000000..3730a02098 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-context.html @@ -0,0 +1,105 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>The effect value of a keyframe effect: Property values that depend on + their context (target element)</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#calculating-computed-keyframes"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(t => { + const div = createDiv(t); + div.style.fontSize = '10px'; + const animation = div.animate([ { marginLeft: '10em' }, + { marginLeft: '20em' } ], 1000); + animation.currentTime = 500; + assert_equals(getComputedStyle(div).marginLeft, '150px', + 'Effect value before updating font-size'); + div.style.fontSize = '20px'; + assert_equals(getComputedStyle(div).marginLeft, '300px', + 'Effect value after updating font-size'); +}, 'Effect values reflect changes to font-size on element'); + +test(t => { + const parentDiv = createDiv(t); + const div = createDiv(t); + parentDiv.appendChild(div); + parentDiv.style.fontSize = '10px'; + + const animation = div.animate([ { marginLeft: '10em' }, + { marginLeft: '20em' } ], 1000); + animation.currentTime = 500; + assert_equals(getComputedStyle(div).marginLeft, '150px', + 'Effect value before updating font-size on parent element'); + parentDiv.style.fontSize = '20px'; + assert_equals(getComputedStyle(div).marginLeft, '300px', + 'Effect value after updating font-size on parent element'); +}, 'Effect values reflect changes to font-size on parent element'); + +promise_test(t => { + const parentDiv = createDiv(t); + const div = createDiv(t); + parentDiv.appendChild(div); + parentDiv.style.fontSize = '10px'; + const animation = div.animate([ { marginLeft: '10em' }, + { marginLeft: '20em' } ], 1000); + + animation.pause(); + animation.currentTime = 500; + parentDiv.style.fontSize = '20px'; + + return animation.ready.then(() => { + assert_equals(getComputedStyle(div).marginLeft, '300px', + 'Effect value after updating font-size on parent element'); + }); +}, 'Effect values reflect changes to font-size when computed style is not' + + ' immediately flushed'); + +promise_test(t => { + const divWith10pxFontSize = createDiv(t); + divWith10pxFontSize.style.fontSize = '10px'; + const divWith20pxFontSize = createDiv(t); + divWith20pxFontSize.style.fontSize = '20px'; + + const div = createDiv(t); + div.remove(); // Detach + const animation = div.animate([ { marginLeft: '10em' }, + { marginLeft: '20em' } ], 1000); + animation.pause(); + + return animation.ready.then(() => { + animation.currentTime = 500; + + divWith10pxFontSize.appendChild(div); + assert_equals(getComputedStyle(div).marginLeft, '150px', + 'Effect value after attaching to font-size:10px parent'); + divWith20pxFontSize.appendChild(div); + assert_equals(getComputedStyle(div).marginLeft, '300px', + 'Effect value after attaching to font-size:20px parent'); + }); +}, 'Effect values reflect changes to font-size from reparenting'); + +test(t => { + const divA = createDiv(t); + divA.style.fontSize = '10px'; + + const divB = createDiv(t); + divB.style.fontSize = '20px'; + + const animation = divA.animate([ { marginLeft: '10em' }, + { marginLeft: '20em' } ], 1000); + animation.currentTime = 500; + assert_equals(getComputedStyle(divA).marginLeft, '150px', + 'Effect value before updating target element'); + + animation.effect.target = divB; + assert_equals(getComputedStyle(divB).marginLeft, '300px', + 'Effect value after updating target element'); +}, 'Effect values reflect changes to target element'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-interval-distance.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-interval-distance.html new file mode 100644 index 0000000000..6bf5d8cd46 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-interval-distance.html @@ -0,0 +1,36 @@ +<!doctype html> +<meta charset=utf-8> +<title>The effect value of a keyframe effect: Calculating the interval + distance between keyframes</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#the-effect-value-of-a-keyframe-animation-effect"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +test(t => { + // In Firefox there was a floating precision bug in the calculation of the + // progress at the end of the 0.2<->1.0 interval. This test exercises that + // calculation in case other UAs suffer from the same problem. + const target = createDiv(t); + const anim = target.animate( + [ + { opacity: 0 }, + { offset: 0.2, opacity: 1, easing: 'step-end' }, + { opacity: 0 }, + ], + { + duration: 1000, + fill: 'forwards', + } + ); + + anim.currentTime = 1000; + assert_equals(getComputedStyle(target).opacity, '0'); +}, 'Interval distance is calculated correctly (precision test)'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html new file mode 100644 index 0000000000..abfda86f4d --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html @@ -0,0 +1,824 @@ +<!doctype html> +<meta charset=utf-8> +<title>The effect value of a keyframe effect: Applying the iteration composite + operation</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#the-effect-value-of-a-keyframe-animation-effect"> +<link rel="help" href="https://drafts.csswg.org/web-animations/#effect-accumulation-section"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src="../../testcommon.js"></script> +<div id="log"></div> +<script> +'use strict'; + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ alignContent: ['flex-start', 'flex-end'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).alignContent, 'flex-end', + 'Animated align-content style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).alignContent, 'flex-start', + 'Animated align-content style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).alignContent, 'flex-end', + 'Animated align-content style at 50s of the third iteration'); +}, 'iteration composition of discrete type animation (align-content)'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ marginLeft: ['0px', '10px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '5px', + 'Animated margin-left style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).marginLeft, '20px', + 'Animated margin-left style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '25px', + 'Animated margin-left style at 50s of the third iteration'); +}, 'iteration composition of <length> type animation'); + +test(t => { + const parent = createDiv(t); + parent.style.width = '100px'; + const div = createDiv(t); + parent.appendChild(div); + + const anim = + div.animate({ width: ['0%', '50%'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).width, '25px', + 'Animated width style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).width, '100px', + 'Animated width style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).width, '125px', + 'Animated width style at 50s of the third iteration'); +}, 'iteration composition of <percentage> type animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ color: ['rgb(0, 0, 0)', 'rgb(120, 120, 120)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).color, 'rgb(60, 60, 60)', + 'Animated color style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).color, 'rgb(240, 240, 240)', + 'Animated color style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).color, 'rgb(255, 255, 255)', + 'Animated color style at 50s of the third iteration'); +}, 'iteration composition of <color> type animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ color: ['rgb(0, 120, 0)', 'rgb(60, 60, 60)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).color, 'rgb(30, 90, 30)', + 'Animated color style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).color, 'rgb(120, 240, 120)', + 'Animated color style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + // The green color is (240 + 180) / 2 = 210 + assert_equals(getComputedStyle(div).color, 'rgb(150, 210, 150)', + 'Animated color style at 50s of the third iteration'); +}, 'iteration composition of <color> type animation that green component is ' + + 'decreasing'); + + test(t => { + const div = createDiv(t); + const anim = + div.animate({ flexGrow: [0, 10] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).flexGrow, '5', + 'Animated flex-grow style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).flexGrow, '20', + 'Animated flex-grow style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).flexGrow, '25', + 'Animated flex-grow style at 50s of the third iteration'); +}, 'iteration composition of <number> type animation'); + +test(t => { + const div = createDiv(t); + div.style.position = 'absolute'; + const anim = + div.animate({ clip: ['rect(0px, 0px, 0px, 0px)', + 'rect(10px, 10px, 10px, 10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).clip, 'rect(5px, 5px, 5px, 5px)', + 'Animated clip style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).clip, 'rect(20px, 20px, 20px, 20px)', + 'Animated clip style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).clip, 'rect(25px, 25px, 25px, 25px)', + 'Animated clip style at 50s of the third iteration'); +}, 'iteration composition of <shape> type animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ width: ['calc(0vw + 0px)', 'calc(0vw + 10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).width, '5px', + 'Animated calc width style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).width, '20px', + 'Animated calc width style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).width, '25px', + 'Animated calc width style at 50s of the third iteration'); +}, 'iteration composition of <calc()> value animation'); + +test(t => { + const parent = createDiv(t); + parent.style.width = '100px'; + const div = createDiv(t); + parent.appendChild(div); + + const anim = + div.animate({ width: ['calc(0% + 0px)', 'calc(10% + 10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).width, '10px', + // 100px * 5% + 5px + 'Animated calc width style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).width, + '40px', // 100px * (10% + 10%) + (10px + 10px) + 'Animated calc width style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).width, + '50px', // (40px + 60px) / 2 + 'Animated calc width style at 50s of the third iteration'); +}, 'iteration composition of <calc()> value animation that the values can\'t' + + 'be reduced'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ opacity: [0, 0.4] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).opacity, '0.2', + 'Animated opacity style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).opacity, '0.8', + 'Animated opacity style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).opacity, '1', // (0.8 + 1.2) * 0.5 + 'Animated opacity style at 50s of the third iteration'); +}, 'iteration composition of opacity animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ boxShadow: ['rgb(0, 0, 0) 0px 0px 0px 0px', + 'rgb(120, 120, 120) 10px 10px 10px 0px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).boxShadow, + 'rgb(60, 60, 60) 5px 5px 5px 0px', + 'Animated box-shadow style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).boxShadow, + 'rgb(240, 240, 240) 20px 20px 20px 0px', + 'Animated box-shadow style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).boxShadow, + 'rgb(255, 255, 255) 25px 25px 25px 0px', + 'Animated box-shadow style at 50s of the third iteration'); +}, 'iteration composition of box-shadow animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ filter: ['blur(0px)', 'blur(10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).filter, 'blur(5px)', + 'Animated filter blur style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).filter, 'blur(20px)', + 'Animated filter blur style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).filter, 'blur(25px)', + 'Animated filter blur style at 50s of the third iteration'); +}, 'iteration composition of filter blur animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ filter: ['brightness(1)', + 'brightness(180%)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(1.4)', + 'Animated filter brightness style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(2.6)', // brightness(1) + brightness(0.8) + brightness(0.8) + 'Animated filter brightness style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(3)', // (brightness(2.6) + brightness(3.4)) * 0.5 + 'Animated filter brightness style at 50s of the third iteration'); +}, 'iteration composition of filter brightness for different unit animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ filter: ['brightness(0)', + 'brightness(1)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(0.5)', + 'Animated filter brightness style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(0)', // brightness(1) is an identity element, not accumulated. + 'Animated filter brightness style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(0.5)', // brightness(1) is an identity element, not accumulated. + 'Animated filter brightness style at 50s of the third iteration'); +}, 'iteration composition of filter brightness animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ filter: ['drop-shadow(rgb(0, 0, 0) 0px 0px 0px)', + 'drop-shadow(rgb(120, 120, 120) 10px 10px 10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).filter, + 'drop-shadow(rgb(60, 60, 60) 5px 5px 5px)', + 'Animated filter drop-shadow style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).filter, + 'drop-shadow(rgb(240, 240, 240) 20px 20px 20px)', + 'Animated filter drop-shadow style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).filter, + 'drop-shadow(rgb(255, 255, 255) 25px 25px 25px)', + 'Animated filter drop-shadow style at 50s of the third iteration'); +}, 'iteration composition of filter drop-shadow animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ filter: ['brightness(1) contrast(1)', + 'brightness(2) contrast(2)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(1.5) contrast(1.5)', + 'Animated filter list at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(3) contrast(3)', + 'Animated filter list at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(3.5) contrast(3.5)', + 'Animated filter list at 50s of the third iteration'); +}, 'iteration composition of same filter list animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ filter: ['brightness(1) contrast(1)', + 'contrast(2) brightness(2)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).filter, + 'contrast(2) brightness(2)', // discrete + 'Animated filter list at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).filter, + // We can't accumulate 'contrast(2) brightness(2)' onto + // the first list 'brightness(1) contrast(1)' because of + // mismatch of the order. + 'brightness(1) contrast(1)', + 'Animated filter list at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).filter, + // We *can* accumulate 'contrast(2) brightness(2)' onto + // the same list 'contrast(2) brightness(2)' here. + 'contrast(4) brightness(4)', // discrete + 'Animated filter list at 50s of the third iteration'); +}, 'iteration composition of discrete filter list because of mismatch ' + + 'of the order'); + + test(t => { + const div = createDiv(t); + const anim = + div.animate({ filter: ['sepia(0)', + 'sepia(1) contrast(2)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).filter, + 'sepia(0.5) contrast(1.5)', + 'Animated filter list at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).filter, + 'sepia(1) contrast(3)', + 'Animated filter list at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).filter, + 'sepia(1) contrast(3.5)', + 'Animated filter list at 50s of the third iteration'); +}, 'iteration composition of different length filter list animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ transform: ['rotate(0deg)', 'rotate(180deg)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(0, 1, -1, 0, 0, 0)', // rotate(90deg) + 'Animated transform(rotate) style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(1, 0, 0, 1, 0, 0)', // rotate(360deg) + 'Animated transform(rotate) style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(0, 1, -1, 0, 0, 0)', // rotate(450deg) + 'Animated transform(rotate) style at 50s of the third iteration'); +}, 'iteration composition of transform(rotate) animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ transform: ['scale(0)', 'scale(1)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(0.5, 0, 0, 0.5, 0, 0)', // scale(0.5) + 'Animated transform(scale) style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(0, 0, 0, 0, 0, 0)', // scale(0); scale(1) is an identity element, + // not accumulated. + 'Animated transform(scale) style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(0.5, 0, 0, 0.5, 0, 0)', // scale(0.5); scale(1) an identity + // element, not accumulated. + 'Animated transform(scale) style at 50s of the third iteration'); +}, 'iteration composition of transform: [ scale(0), scale(1) ] animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ transform: ['scale(1)', 'scale(2)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(1.5, 0, 0, 1.5, 0, 0)', // scale(1.5) + 'Animated transform(scale) style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(3, 0, 0, 3, 0, 0)', // scale(1 + (2 -1) + (2 -1)) + 'Animated transform(scale) style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(3.5, 0, 0, 3.5, 0, 0)', // (scale(3) + scale(4)) * 0.5 + 'Animated transform(scale) style at 50s of the third iteration'); +}, 'iteration composition of transform: [ scale(1), scale(2) ] animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ transform: ['scale(0)', 'scale(2)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(1, 0, 0, 1, 0, 0)', // scale(1) + 'Animated transform(scale) style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(2, 0, 0, 2, 0, 0)', // (scale(0) + scale(2-1)*2) + 'Animated transform(scale) style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(3, 0, 0, 3, 0, 0)', // (scale(2) + scale(4)) * 0.5 + 'Animated transform(scale) style at 50s of the third iteration'); +}, 'iteration composition of transform: scale(2) animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ transform: ['rotate(0deg) translateX(0px)', + 'rotate(180deg) translateX(10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(0, 1, -1, 0, 0, 5)', // rotate(90deg) translateX(5px) + 'Animated transform list at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(1, 0, 0, 1, 20, 0)', // rotate(360deg) translateX(20px) + 'Animated transform list at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(0, 1, -1, 0, 0, 25)', // rotate(450deg) translateX(25px) + 'Animated transform list at 50s of the third iteration'); +}, 'iteration composition of transform list animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ transform: ['matrix(2, 0, 0, 2, 0, 0)', + 'matrix(3, 0, 0, 3, 30, 0)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(2.5, 0, 0, 2.5, 15, 0)', + 'Animated transform of matrix function at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_matrix_equals(getComputedStyle(div).transform, + // scale(2) + (scale(3-1)*2) + translateX(30px)*2 + 'matrix(6, 0, 0, 6, 60, 0)', + 'Animated transform of matrix function at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + // from: matrix(6, 0, 0, 6, 60, 0) + // to: matrix(7, 0, 0, 7, 90, 0) + // = scale(3) + (scale(3-1)*2) + translateX(30px)*3 + 'matrix(6.5, 0, 0, 6.5, 75, 0)', + 'Animated transform of matrix function at 50s of the third iteration'); +}, 'iteration composition of transform of matrix function'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ transform: ['translateX(0px) scale(2)', + 'scale(3) translateX(10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + // Interpolate between matrix(2, 0, 0, 2, 0, 0) = translateX(0px) scale(2) + // and matrix(3, 0, 0, 3, 30, 0) = scale(3) translateX(10px) + 'matrix(2.5, 0, 0, 2.5, 15, 0)', + 'Animated transform list at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_matrix_equals(getComputedStyle(div).transform, + // 'from' and 'to' value are mismatched, so accumulate + // matrix(2, 0, 0, 2, 0, 0) onto matrix(3, 0, 0, 3, 30, 0) * 2 + // = scale(2) + (scale(3-1)*2) + translateX(30px)*2 + 'matrix(6, 0, 0, 6, 60, 0)', + 'Animated transform list at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + // Interpolate between matrix(6, 0, 0, 6, 60, 0) + // and matrix(7, 0, 0, 7, 210, 0) = scale(7) translate(30px) + 'matrix(6.5, 0, 0, 6.5, 135, 0)', + 'Animated transform list at 50s of the third iteration'); +}, 'iteration composition of transform list animation whose order is' + + ' mismatched'); + +test(t => { + const div = createDiv(t); + // Even if each transform list does not have functions which exist in + // other pair of the list, we don't fill any missing functions at all. + const anim = + div.animate({ transform: ['translateX(0px)', + 'scale(2) translateX(10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + // Interpolate between matrix(1, 0, 0, 1, 0, 0) = translateX(0px) + // and matrix(2, 0, 0, 2, 20, 0) = scale(2) translateX(10px) + 'matrix(1.5, 0, 0, 1.5, 10, 0)', + 'Animated transform list at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_matrix_equals(getComputedStyle(div).transform, + // 'from' and 'to' value are mismatched, so accumulate + // matrix(1, 0, 0, 1, 0, 0) onto matrix(2, 0, 0, 2, 20, 0) * 2 + // = scale(1) + (scale(2-1)*2) + translateX(20px)*2 + 'matrix(3, 0, 0, 3, 40, 0)', + 'Animated transform list at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + // Interpolate between matrix(3, 0, 0, 3, 40, 0) + // and matrix(4, 0, 0, 4, 120, 0) = + // scale(2 + (2-1)*2) translate(10px * 3) + 'matrix(3.5, 0, 0, 3.5, 80, 0)', + 'Animated transform list at 50s of the third iteration'); +}, 'iteration composition of transform list animation whose order is' + + ' mismatched because of missing functions'); + + test(t => { + const div = createDiv(t); + const anim = + div.animate({ transform: ['none', + 'translateX(10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + // translateX(none) -> translateX(10px) @ 50% + 'matrix(1, 0, 0, 1, 5, 0)', + 'Animated transform list at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_matrix_equals(getComputedStyle(div).transform, + // translateX(10px * 2 + none) -> translateX(10px * 2 + 10px) @ 0% + 'matrix(1, 0, 0, 1, 20, 0)', + 'Animated transform list at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + // translateX(10px * 2 + none) -> translateX(10px * 2 + 10px) @ 50% + 'matrix(1, 0, 0, 1, 25, 0)', + 'Animated transform list at 50s of the third iteration'); +}, 'iteration composition of transform from none to translate'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ transform: ['matrix3d(1, 0, 0, 0, ' + + '0, 1, 0, 0, ' + + '0, 0, 1, 0, ' + + '0, 0, 30, 1)', + 'matrix3d(1, 0, 0, 0, ' + + '0, 1, 0, 0, ' + + '0, 0, 1, 0, ' + + '0, 0, 50, 1)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 40, 1)', + 'Animated transform of matrix3d function at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_matrix_equals(getComputedStyle(div).transform, + // translateZ(30px) + (translateZ(50px)*2) + 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 130, 1)', + 'Animated transform of matrix3d function at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + // from: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 130, 1) + // to: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 150, 1) + 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 140, 1)', + 'Animated transform of matrix3d function at 50s of the third iteration'); +}, 'iteration composition of transform of matrix3d function'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ transform: ['rotate3d(1, 1, 0, 0deg)', + 'rotate3d(1, 1, 0, 90deg)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = 0; + assert_matrix_equals(getComputedStyle(div).transform, + 'matrix(1, 0, 0, 1, 0, 0)', // Actually not rotated at all. + 'Animated transform of rotate3d function at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_matrix_equals(getComputedStyle(div).transform, + rotate3dToMatrix3d(1, 1, 0, Math.PI), // 180deg + 'Animated transform of rotate3d function at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_matrix_equals(getComputedStyle(div).transform, + rotate3dToMatrix3d(1, 1, 0, 225 * Math.PI / 180), //((270 + 180) * 0.5)deg + 'Animated transform of rotate3d function at 50s of the third iteration'); +}, 'iteration composition of transform of rotate3d function'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ marginLeft: ['10px', '20px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '15px', + 'Animated margin-left style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).marginLeft, '50px', // 10px + 20px + 20px + 'Animated margin-left style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '55px', // (50px + 60px) * 0.5 + 'Animated margin-left style at 50s of the third iteration'); +}, 'iteration composition starts with non-zero value animation'); + +test(t => { + const div = createDiv(t); + const anim = + div.animate({ marginLeft: ['10px', '-10px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).marginLeft, + '0px', + 'Animated margin-left style at 50s of the first iteration'); + anim.currentTime = anim.effect.getComputedTiming().duration * 2; + assert_equals(getComputedStyle(div).marginLeft, + '-10px', // 10px + -10px + -10px + 'Animated margin-left style at 0s of the third iteration'); + anim.currentTime += anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).marginLeft, + '-20px', // (-10px + -30px) * 0.5 + 'Animated margin-left style at 50s of the third iteration'); +}, 'iteration composition with negative final value animation'); + +test(t => { + const div = createDiv(t); + const anim = div.animate({ marginLeft: ['0px', '10px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = + anim.effect.getComputedTiming().duration * 2 + + anim.effect.getComputedTiming().duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '25px', + 'Animated style at 50s of the third iteration'); + + // double its duration. + anim.effect.updateTiming({ + duration: anim.effect.getComputedTiming().duration * 2 + }); + assert_equals(getComputedStyle(div).marginLeft, '12.5px', + 'Animated style at 25s of the first iteration'); + + // half of original. + anim.effect.updateTiming({ + duration: anim.effect.getComputedTiming().duration / 4 + }); + assert_equals(getComputedStyle(div).marginLeft, '50px', + 'Animated style at 50s of the fourth iteration'); +}, 'duration changes with an iteration composition operation of accumulate'); + +</script> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-overlapping-keyframes.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-overlapping-keyframes.html new file mode 100644 index 0000000000..a2a0683921 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-overlapping-keyframes.html @@ -0,0 +1,77 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>The effect value of a keyframe effect: Overlapping keyframes</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#the-effect-value-of-a-keyframe-animation-effect"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<div id="target"></div> +<script> +'use strict'; + +function assert_opacity_value(opacity, expected, description) { + return assert_approx_equals(parseFloat(opacity), expected, 0.0001, description); +} + +test(t => { + const div = createDiv(t); + const anim = div.animate([ { offset: 0, opacity: 0 }, + { offset: 0, opacity: 0.1 }, + { offset: 0, opacity: 0.2 }, + { offset: 1, opacity: 0.8 }, + { offset: 1, opacity: 0.9 }, + { offset: 1, opacity: 1 } ], + { duration: 1000, + easing: 'cubic-bezier(0.5, -0.5, 0.5, 1.5)' }); + assert_opacity_value(getComputedStyle(div).opacity, 0.2, + 'When progress is zero the last keyframe with offset 0 should' + + ' be used'); + // http://cubic-bezier.com/#.5,-0.5,.5,1.5 + // At t=0.15, the progress should be negative + anim.currentTime = 150; + assert_equals(getComputedStyle(div).opacity, '0', + 'When progress is negative, the first keyframe with a 0 offset' + + ' should be used'); + // At t=0.71, the progress should be just less than 1 + anim.currentTime = 710; + assert_approx_equals(parseFloat(getComputedStyle(div).opacity), 0.8, 0.01, + 'When progress is just less than 1, the first keyframe with an' + + ' offset of 1 should be used as the interval endpoint'); + // At t=0.85, the progress should be > 1 + anim.currentTime = 850; + assert_equals(getComputedStyle(div).opacity, '1', + 'When progress is greater than 1.0, the last keyframe with a 1' + + ' offset should be used'); + anim.finish(); + assert_equals(getComputedStyle(div).opacity, '1', + 'When progress is equal to 1.0, the last keyframe with a 1' + + ' offset should be used'); +}, 'Overlapping keyframes at 0 and 1 use the appropriate value when the' + + ' progress is outside the range [0, 1]'); + +test(t => { + const div = createDiv(t); + const anim = div.animate([ { offset: 0, opacity: 0 }, + { offset: 0.5, opacity: 0.3 }, + { offset: 0.5, opacity: 0.5 }, + { offset: 0.5, opacity: 0.7 }, + { offset: 1, opacity: 1 } ], 1000); + anim.currentTime = 250; + assert_opacity_value(getComputedStyle(div).opacity, 0.15, + 'Before the overlap point, the first keyframe from the' + + ' overlap point should be used as interval endpoint'); + anim.currentTime = 500; + assert_opacity_value(getComputedStyle(div).opacity, 0.7, + 'At the overlap point, the last keyframe from the' + + ' overlap point should be used as interval startpoint'); + anim.currentTime = 750; + assert_opacity_value(getComputedStyle(div).opacity, 0.85, + 'After the overlap point, the last keyframe from the' + + ' overlap point should be used as interval startpoint'); +}, 'Overlapping keyframes between 0 and 1 use the appropriate value on each' + + ' side of the overlap point'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-replaced-animations.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-replaced-animations.html new file mode 100644 index 0000000000..d40a01fdd2 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-replaced-animations.html @@ -0,0 +1,161 @@ +<!doctype html> +<meta charset=utf-8> +<title>The effect value of a keyframe effect: Overlapping keyframes</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#the-effect-value-of-a-keyframe-animation-effect"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +function assert_opacity_value(opacity, expected, description) { + return assert_approx_equals( + parseFloat(opacity), + expected, + 0.0001, + description + ); +} + +promise_test(async t => { + const div = createDiv(t); + div.style.opacity = '0.1'; + + const animA = div.animate( + { opacity: 0.2 }, + { duration: 1, fill: 'forwards' } + ); + const animB = div.animate( + { opacity: 0.3 }, + { duration: 1, fill: 'forwards' } + ); + await animA.finished; + + // Sanity check + assert_equals(animA.replaceState, 'removed'); + assert_equals(animB.replaceState, 'active'); + + // animA is now removed so if we cancel animB, we should go back to the + // underlying value + animB.cancel(); + assert_opacity_value( + getComputedStyle(div).opacity, + 0.1, + 'Opacity should be the un-animated value' + ); +}, 'Removed animations do not contribute to animated style'); + +promise_test(async t => { + const div = createDiv(t); + div.style.opacity = '0.1'; + + const animA = div.animate( + { opacity: 0.2 }, + { duration: 1, fill: 'forwards' } + ); + const animB = div.animate( + { opacity: 0.3, composite: 'add' }, + { duration: 1, fill: 'forwards' } + ); + await animA.finished; + + // Sanity check + assert_equals(animA.replaceState, 'removed'); + assert_equals(animB.replaceState, 'active'); + + // animA has been removed so the final result should be 0.1 + 0.3 = 0.4. + // (If animA were not removed it would be 0.2 + 0.3 = 0.5.) + assert_opacity_value( + getComputedStyle(div).opacity, + 0.4, + 'Opacity value should not include the removed animation' + ); +}, 'Removed animations do not contribute to the effect stack'); + +promise_test(async t => { + const div = createDiv(t); + div.style.opacity = '0.1'; + + const animA = div.animate( + { opacity: 0.2 }, + { duration: 1, fill: 'forwards' } + ); + const animB = div.animate( + { opacity: 0.3 }, + { duration: 1, fill: 'forwards' } + ); + + await animA.finished; + + animA.persist(); + + animB.cancel(); + assert_opacity_value( + getComputedStyle(div).opacity, + 0.2, + "Opacity should be the persisted animation's value" + ); +}, 'Persisted animations contribute to animated style'); + +promise_test(async t => { + const div = createDiv(t); + div.style.opacity = '0.1'; + + const animA = div.animate( + { opacity: 0.2 }, + { duration: 1, fill: 'forwards' } + ); + const animB = div.animate( + { opacity: 0.3, composite: 'add' }, + { duration: 1, fill: 'forwards' } + ); + + await animA.finished; + + assert_opacity_value( + getComputedStyle(div).opacity, + 0.4, + 'Opacity value should NOT include the contribution of the removed animation' + ); + + animA.persist(); + + assert_opacity_value( + getComputedStyle(div).opacity, + 0.5, + 'Opacity value should include the contribution of the persisted animation' + ); +}, 'Persisted animations contribute to the effect stack'); + +promise_test(async t => { + const div = createDiv(t); + div.style.opacity = '0.1'; + + const animA = div.animate( + { opacity: 0.2 }, + { duration: 1, fill: 'forwards' } + ); + + // Persist the animation before it finishes (and before it would otherwise be + // removed). + animA.persist(); + + const animB = div.animate( + { opacity: 0.3, composite: 'add' }, + { duration: 1, fill: 'forwards' } + ); + + await animA.finished; + + assert_opacity_value( + getComputedStyle(div).opacity, + 0.5, + 'Opacity value should include the contribution of the persisted animation' + ); +}, 'Animations persisted before they would be removed contribute to the' + + ' effect stack'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-transformed-distance.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-transformed-distance.html new file mode 100644 index 0000000000..a33d6d4f24 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-transformed-distance.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>The effect value of a keyframe effect: Calculating the transformed + distance between keyframes</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#the-effect-value-of-a-keyframe-animation-effect"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<script src="../../resources/easing-tests.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +// Test that applying easing to keyframes is applied as expected + +for (const params of gEasingTests) { + test(t => { + const target = createDiv(t); + const anim = target.animate([ { width: '0px' }, + // We put the easing on the second keyframe + // so we can test that it is only applied + // to the specified keyframe. + { width: '100px', easing: params.easing }, + { width: '200px' } ], + { duration: 2000, + fill: 'forwards' }); + + for (const sampleTime of [0, 999, 1000, 1100, 1500, 2000]) { + anim.currentTime = sampleTime; + + const portion = (sampleTime - 1000) / 1000; + const expectedWidth = sampleTime < 1000 + ? sampleTime / 10 // first segment is linear + : 100 + params.easingFunction(portion) * 100; + assert_approx_equals(parseFloat(getComputedStyle(target).width), + expectedWidth, + 0.02, + 'The width should be approximately ' + + `${expectedWidth} at ${sampleTime}ms`); + } + }, `A ${params.desc} on a keyframe affects the resulting style`); +} + +// Test that a linear-equivalent cubic-bezier easing applied to a keyframe does +// not alter (including clamping) the result. + +for (const params of gEasingTests) { + const linearEquivalentEasings = [ 'cubic-bezier(0, 0, 0, 0)', + 'cubic-bezier(1, 1, 1, 1)' ]; + test(t => { + for (const linearEquivalentEasing of linearEquivalentEasings) { + const timing = { duration: 1000, + fill: 'forwards', + easing: params.easing }; + + const linearTarget = createDiv(t); + const linearAnim = linearTarget.animate([ { width: '0px' }, + { width: '100px' } ], + timing); + + const equivalentTarget = createDiv(t); + const equivalentAnim = + equivalentTarget.animate([ { width: '0px', + easing: linearEquivalentEasing }, + { width: '100px' } ], + timing); + + for (const sampleTime of [0, 250, 500, 750, 1000]) { + linearAnim.currentTime = sampleTime; + equivalentAnim.currentTime = sampleTime; + + assert_equals(getComputedStyle(linearTarget).width, + getComputedStyle(equivalentTarget).width, + `The 'width' of the animated elements should be equal ` + + `at ${sampleTime}ms`); + } + } + }, 'Linear-equivalent cubic-bezier keyframe easing applied to an effect ' + + `with a ${params.desc} does not alter the result`); +} + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/transform-and-opacity-on-inline-001-ref.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/transform-and-opacity-on-inline-001-ref.html new file mode 100644 index 0000000000..1e7f250c48 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/transform-and-opacity-on-inline-001-ref.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +<title>CSS Test (Animations): Element.animate() animating both transform and opacity on an inline</title> +<link rel="author" title="L. David Baron" href="https://dbaron.org/"> +<link rel="author" title="Google" href="http://www.google.com/"> + +<style> +#target { + opacity: 0.4; + will-change: opacity; +} +</style> + +<body><span id="target">x</span></body> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/transform-and-opacity-on-inline-001.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/transform-and-opacity-on-inline-001.html new file mode 100644 index 0000000000..f76b53cd4c --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/transform-and-opacity-on-inline-001.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> +<title>CSS Test (Animations): Element.animate() animating both transform and opacity on an inline</title> +<link rel="author" title="L. David Baron" href="https://dbaron.org/"> +<link rel="author" title="Google" href="http://www.google.com/"> +<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1316688"> +<link rel="match" href="transform-and-opacity-on-inline-001-ref.html"> +<meta name="assert" content="This should not crash, and should render as opacity 0.5."> + +<script> +// The transform animation should be ignored; the opacity animation should work. +window.onload = function() { + document.getElementById("target").animate( + [ + { + "transform": "translateX(0px)", + "opacity": "0.8", + }, + { + "transform": "translateX(300px)", + "opacity": "0.0", + } + ], + { duration:1000000, delay: -500000, easing: "steps(3, jump-both)" }); +} +</script> +<body><span id="target">x</span></body> |