diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/web-animations/animation-model/keyframe-effects | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/web-animations/animation-model/keyframe-effects')
17 files changed, 1893 insertions, 0 deletions
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-opacity-replaced-effect-in-shadow-root-ref.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-opacity-replaced-effect-in-shadow-root-ref.html new file mode 100644 index 0000000000..792ca214cf --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-opacity-replaced-effect-in-shadow-root-ref.html @@ -0,0 +1,9 @@ +<!doctype html> +<style> +div { + width: 100px; + height: 100px; + background: green; +} +</style> +<div></div> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-opacity-replaced-effect-in-shadow-root.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-opacity-replaced-effect-in-shadow-root.html new file mode 100644 index 0000000000..43e229856b --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-opacity-replaced-effect-in-shadow-root.html @@ -0,0 +1,26 @@ +<!doctype html> +<html class="reftest-wait"> +<head> +<meta charset=utf-8> +<title>The effect value of consecutive animations targeting 'opacity' in a shadow root</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/"> +<link rel="match" href="effect-value-opacity-replaced-effect-in-shadow-root-ref.html"> +</head> +<body> +<custom-element></custom-element> +<script> +'use strict'; + +(async function () { + const customElement = document.querySelector('custom-element'); + const shadowRoot = customElement.attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = `<div style="width: 100px; height: 100px; background-color: green"></div>`; + const target = shadowRoot.firstElementChild; + + await target.animate({ opacity: [1, 0] }, { duration: 10, fill: 'both' }).finished; + await target.animate({ opacity: [0, 1] }, { duration: 10, fill: 'both' }).finished; + document.documentElement.classList.remove("reftest-wait"); +})(); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-opacity-replaced-effect-ref.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-opacity-replaced-effect-ref.html new file mode 100644 index 0000000000..792ca214cf --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-opacity-replaced-effect-ref.html @@ -0,0 +1,9 @@ +<!doctype html> +<style> +div { + width: 100px; + height: 100px; + background: green; +} +</style> +<div></div> diff --git a/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-opacity-replaced-effect.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-opacity-replaced-effect.html new file mode 100644 index 0000000000..bef3bd3e8d --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/effect-value-opacity-replaced-effect.html @@ -0,0 +1,32 @@ +<!doctype html> +<html class="reftest-wait"> +<head> +<meta charset=utf-8> +<title>The effect value of consecutive animations targeting 'opacity'</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/"> +<link rel="match" href="effect-value-opacity-replaced-effect-ref.html"> +<style> +div { + width: 100px; + height: 100px; + background: green; + will-change: opacity; +} +</style> +</head> +<body> +<div></div> +<script> +'use strict'; + +(async function () { + const div = document.querySelector('div'); + + await div.animate({ opacity: [1, 0] }, { duration: 10, fill: 'both' }).finished; + await div.animate({ opacity: [0, 1] }, { duration: 10, fill: 'both' }).finished; + + document.documentElement.classList.remove("reftest-wait"); +})(); +</script> +</body> +</html> 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/keyframe-exceptions.html b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/keyframe-exceptions.html new file mode 100644 index 0000000000..4cb4be76a8 --- /dev/null +++ b/testing/web-platform/tests/web-animations/animation-model/keyframe-effects/keyframe-exceptions.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<head> +<meta charset="utf-8"> +<link rel="help" src="https://www.w3.org/TR/web-animations-1/#processing-a-keyframes-argument"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/web-animations/testcommon.js"></script> +<script src="support/testcommon.js"></script> +<title>Keyframes with invalid offsets</title> +</head> +<script> + test(() => { + assert_throws_js(TypeError, + function() { + document.documentElement.animate([ + {offset: 0.6}, + {offset: 0.4} + ]); + }); + }, 'Offsets must be loosely sorted'); + + test(() => { + assert_throws_js(TypeError, + function() { + document.documentElement.animate([ + {offset: 'whatever'} + ]); + }); + }, 'Invalid offset'); + + test(() => { + assert_throws_js(TypeError, + function() { + document.documentElement.animate([ + {offset: -1} + ]); + }); + }, 'Offsets must be null or in the range [0,1]'); +</script> 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> |