summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/web-animations/animation-model
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/web-animations/animation-model
parentInitial commit. (diff)
downloadfirefox-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')
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/animation-types/accumulation-per-property-001.html24
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/animation-types/accumulation-per-property-002.html24
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/animation-types/addition-per-property-001.html24
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/animation-types/addition-per-property-002.html24
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/animation-types/discrete.html135
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/animation-types/interpolation-per-property-001.html24
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/animation-types/interpolation-per-property-002.html24
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/animation-types/property-list.js1564
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/animation-types/property-types.js2769
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/animation-types/property-utils.js38
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/animation-types/visibility.html57
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/combining-effects/applying-interpolated-transform.html87
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/combining-effects/applying-the-composited-result.html29
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/combining-effects/effect-composition.html154
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/keyframe-effects/computed-keyframes-shorthands.html30
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-in-removed-iframe-crash.html22
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-on-marquee-parent-crash.html21
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-context-filling.html377
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-context.html105
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-interval-distance.html36
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-iteration-composite-operation.html824
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-overlapping-keyframes.html77
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-replaced-animations.html161
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-transformed-distance.html84
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/keyframe-effects/transform-and-opacity-on-inline-001-ref.html14
-rw-r--r--testing/web-platform/tests/web-animations/animation-model/keyframe-effects/transform-and-opacity-on-inline-001.html27
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>