summaryrefslogtreecommitdiffstats
path: root/dom/animation/test/style
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/animation/test/style
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/animation/test/style')
-rw-r--r--dom/animation/test/style/test_animation-seeking-with-current-time.html123
-rw-r--r--dom/animation/test/style/test_animation-seeking-with-start-time.html123
-rw-r--r--dom/animation/test/style/test_animation-setting-effect.html127
-rw-r--r--dom/animation/test/style/test_composite.html142
-rw-r--r--dom/animation/test/style/test_interpolation-from-interpolatematrix-to-none.html43
-rw-r--r--dom/animation/test/style/test_missing-keyframe-on-compositor.html577
-rw-r--r--dom/animation/test/style/test_missing-keyframe.html110
-rw-r--r--dom/animation/test/style/test_transform-non-normalizable-rotate3d.html28
8 files changed, 1273 insertions, 0 deletions
diff --git a/dom/animation/test/style/test_animation-seeking-with-current-time.html b/dom/animation/test/style/test_animation-seeking-with-current-time.html
new file mode 100644
index 0000000000..265de8f0f5
--- /dev/null
+++ b/dom/animation/test/style/test_animation-seeking-with-current-time.html
@@ -0,0 +1,123 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Tests for seeking using Animation.currentTime</title>
+ <style>
+.animated-div {
+ margin-left: -10px;
+ animation-timing-function: linear ! important;
+}
+
+@keyframes anim {
+ from { margin-left: 0px; }
+ to { margin-left: 100px; }
+}
+ </style>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../testcommon.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
+ <script type="text/javascript">
+'use strict';
+
+function assert_marginLeft_equals(target, expect, description) {
+ var marginLeft = parseFloat(getComputedStyle(target).marginLeft);
+ assert_equals(marginLeft, expect, description);
+}
+
+promise_test(function(t) {
+ var div = addDiv(t, {'class': 'animated-div'});
+ div.style.animation = "anim 100s";
+ var animation = div.getAnimations()[0];
+
+ return animation.ready.then(function() {
+ animation.currentTime = 90 * MS_PER_SEC;
+ assert_marginLeft_equals(div, 90,
+ 'Computed style is updated when seeking forwards in active interval');
+
+ animation.currentTime = 10 * MS_PER_SEC;
+ assert_marginLeft_equals(div, 10,
+ 'Computed style is updated when seeking backwards in active interval');
+ });
+}, 'Seeking forwards and backward in active interval');
+
+promise_test(function(t) {
+ var div = addDiv(t, {'class': 'animated-div'});
+ div.style.animation = "anim 100s 100s";
+ var animation = div.getAnimations()[0];
+
+ return animation.ready.then(function() {
+ assert_marginLeft_equals(div, -10,
+ 'Computed style is unaffected in before phase with no backwards fill');
+
+ // before -> active (non-active -> active)
+ animation.currentTime = 150 * MS_PER_SEC;
+ assert_marginLeft_equals(div, 50,
+ 'Computed style is updated when seeking forwards from ' +
+ 'not \'in effect\' to \'in effect\' state');
+ });
+}, 'Seeking to non-\'in effect\' from \'in effect\' (before -> active)');
+
+promise_test(function(t) {
+ var div = addDiv(t, {'class': 'animated-div'});
+ div.style.animation = "anim 100s 100s";
+ var animation = div.getAnimations()[0];
+
+ return animation.ready.then(function() {
+ // move to after phase
+ animation.currentTime = 250 * MS_PER_SEC;
+ assert_marginLeft_equals(div, -10,
+ 'Computed style is unaffected in after phase with no forwards fill');
+
+ // after -> active (non-active -> active)
+ animation.currentTime = 150 * MS_PER_SEC;
+ assert_marginLeft_equals(div, 50,
+ 'Computed style is updated when seeking backwards from ' +
+ 'not \'in effect\' to \'in effect\' state');
+ });
+}, 'Seeking to non-\'in effect\' from \'in effect\' (after -> active)');
+
+promise_test(function(t) {
+ var div = addDiv(t, {'class': 'animated-div'});
+ div.style.animation = "anim 100s 100s";
+ var animation = div.getAnimations()[0];
+
+ return animation.ready.then(function() {
+ // move to active phase
+ animation.currentTime = 150 * MS_PER_SEC;
+ assert_marginLeft_equals(div, 50,
+ 'Computed value is set during active phase');
+
+ // active -> before
+ animation.currentTime = 50 * MS_PER_SEC;
+ assert_marginLeft_equals(div, -10,
+ 'Computed value is not effected after seeking backwards from ' +
+ '\'in effect\' to not \'in effect\' state');
+ });
+}, 'Seeking to \'in effect\' from non-\'in effect\' (active -> before)');
+
+promise_test(function(t) {
+ var div = addDiv(t, {'class': 'animated-div'});
+ div.style.animation = "anim 100s 100s";
+ var animation = div.getAnimations()[0];
+
+ return animation.ready.then(function() {
+ // move to active phase
+ animation.currentTime = 150 * MS_PER_SEC;
+ assert_marginLeft_equals(div, 50,
+ 'Computed value is set during active phase');
+
+ // active -> after
+ animation.currentTime = 250 * MS_PER_SEC;
+ assert_marginLeft_equals(div, -10,
+ 'Computed value is not affected after seeking forwards from ' +
+ '\'in effect\' to not \'in effect\' state');
+ });
+}, 'Seeking to \'in effect\' from non-\'in effect\' (active -> after)');
+
+ </script>
+ </body>
+</html>
diff --git a/dom/animation/test/style/test_animation-seeking-with-start-time.html b/dom/animation/test/style/test_animation-seeking-with-start-time.html
new file mode 100644
index 0000000000..e56db5f23d
--- /dev/null
+++ b/dom/animation/test/style/test_animation-seeking-with-start-time.html
@@ -0,0 +1,123 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Tests for seeking using Animation.startTime</title>
+ <style>
+.animated-div {
+ margin-left: -10px;
+ animation-timing-function: linear ! important;
+}
+
+@keyframes anim {
+ from { margin-left: 0px; }
+ to { margin-left: 100px; }
+}
+ </style>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../testcommon.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
+ <script type="text/javascript">
+'use strict';
+
+function assert_marginLeft_equals(target, expect, description) {
+ var marginLeft = parseFloat(getComputedStyle(target).marginLeft);
+ assert_equals(marginLeft, expect, description);
+}
+
+promise_test(function(t) {
+ var div = addDiv(t, {'class': 'animated-div'});
+ div.style.animation = "anim 100s";
+ var animation = div.getAnimations()[0];
+
+ return animation.ready.then(function() {
+ animation.startTime = animation.timeline.currentTime - 90 * MS_PER_SEC
+ assert_marginLeft_equals(div, 90,
+ 'Computed style is updated when seeking forwards in active interval');
+
+ animation.startTime = animation.timeline.currentTime - 10 * MS_PER_SEC;
+ assert_marginLeft_equals(div, 10,
+ 'Computed style is updated when seeking backwards in active interval');
+ });
+}, 'Seeking forwards and backward in active interval');
+
+promise_test(function(t) {
+ var div = addDiv(t, {'class': 'animated-div'});
+ div.style.animation = "anim 100s 100s";
+ var animation = div.getAnimations()[0];
+
+ return animation.ready.then(function() {
+ assert_marginLeft_equals(div, -10,
+ 'Computed style is unaffected in before phase with no backwards fill');
+
+ // before -> active (non-active -> active)
+ animation.startTime = animation.timeline.currentTime - 150 * MS_PER_SEC;
+ assert_marginLeft_equals(div, 50,
+ 'Computed style is updated when seeking forwards from ' +
+ 'not \'in effect\' to \'in effect\' state');
+ });
+}, 'Seeking to non-\'in effect\' from \'in effect\' (before -> active)');
+
+promise_test(function(t) {
+ var div = addDiv(t, {'class': 'animated-div'});
+ div.style.animation = "anim 100s 100s";
+ var animation = div.getAnimations()[0];
+
+ return animation.ready.then(function() {
+ // move to after phase
+ animation.startTime = animation.timeline.currentTime - 250 * MS_PER_SEC;
+ assert_marginLeft_equals(div, -10,
+ 'Computed style is unaffected in after phase with no forwards fill');
+
+ // after -> active (non-active -> active)
+ animation.startTime = animation.timeline.currentTime - 150 * MS_PER_SEC;
+ assert_marginLeft_equals(div, 50,
+ 'Computed style is updated when seeking backwards from ' +
+ 'not \'in effect\' to \'in effect\' state');
+ });
+}, 'Seeking to non-\'in effect\' from \'in effect\' (after -> active)');
+
+promise_test(function(t) {
+ var div = addDiv(t, {'class': 'animated-div'});
+ div.style.animation = "anim 100s 100s";
+ var animation = div.getAnimations()[0];
+
+ return animation.ready.then(function() {
+ // move to active phase
+ animation.startTime = animation.timeline.currentTime - 150 * MS_PER_SEC;
+ assert_marginLeft_equals(div, 50,
+ 'Computed value is set during active phase');
+
+ // active -> before
+ animation.startTime = animation.timeline.currentTime - 50 * MS_PER_SEC;
+ assert_marginLeft_equals(div, -10,
+ 'Computed value is not affected after seeking backwards from ' +
+ '\'in effect\' to not \'in effect\' state');
+ });
+}, 'Seeking to \'in effect\' from non-\'in effect\' (active -> before)');
+
+promise_test(function(t) {
+ var div = addDiv(t, {'class': 'animated-div'});
+ div.style.animation = "anim 100s 100s";
+ var animation = div.getAnimations()[0];
+
+ return animation.ready.then(function() {
+ // move to active phase
+ animation.startTime = animation.timeline.currentTime - 150 * MS_PER_SEC;
+ assert_marginLeft_equals(div, 50,
+ 'Computed value is set during active phase');
+
+ // active -> after
+ animation.startTime = animation.timeline.currentTime - 250 * MS_PER_SEC;
+ assert_marginLeft_equals(div, -10,
+ 'Computed value is not affected after seeking forwards from ' +
+ '\'in effect\' to not \'in effect\' state');
+ });
+}, 'Seeking to \'in effect\' from non-\'in effect\' (active -> after)');
+
+ </script>
+ </body>
+</html>
diff --git a/dom/animation/test/style/test_animation-setting-effect.html b/dom/animation/test/style/test_animation-setting-effect.html
new file mode 100644
index 0000000000..8712072a51
--- /dev/null
+++ b/dom/animation/test/style/test_animation-setting-effect.html
@@ -0,0 +1,127 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Tests for setting effects by using Animation.effect</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src='../testcommon.js'></script>
+ </head>
+ <body>
+ <div id="log"></div>
+ <script type='text/javascript'>
+
+'use strict';
+
+test(function(t) {
+ var target = addDiv(t);
+ var anim = new Animation();
+ anim.effect = new KeyframeEffect(target,
+ { marginLeft: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+ anim.currentTime = 50 * MS_PER_SEC;
+ assert_equals(getComputedStyle(target).marginLeft, '50px');
+}, 'After setting target effect on an animation with null effect, the ' +
+ 'animation still works');
+
+test(function(t) {
+ var target = addDiv(t);
+ target.style.marginLeft = '10px';
+ var anim = target.animate({ marginLeft: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+ anim.currentTime = 50 * MS_PER_SEC;
+ assert_equals(getComputedStyle(target).marginLeft, '50px');
+
+ anim.effect = null;
+ assert_equals(getComputedStyle(target).marginLeft, '10px');
+}, 'After setting null target effect, the computed style of the target ' +
+ 'element becomes the initial value');
+
+test(function(t) {
+ var target = addDiv(t);
+ var animA = target.animate({ marginLeft: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+ var animB = new Animation();
+ animA.currentTime = 50 * MS_PER_SEC;
+ animB.currentTime = 20 * MS_PER_SEC;
+ assert_equals(getComputedStyle(target).marginLeft, '50px',
+ 'original computed style of the target element');
+
+ animB.effect = animA.effect;
+ assert_equals(getComputedStyle(target).marginLeft, '20px',
+ 'new computed style of the target element');
+}, 'After setting the target effect from an existing animation, the computed ' +
+ 'style of the target effect should reflect the time of the updated ' +
+ 'animation.');
+
+test(function(t) {
+ var target = addDiv(t);
+ target.style.marginTop = '-10px';
+ var animA = target.animate({ marginLeft: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+ var animB = target.animate({ marginTop: [ '0px', '100px' ] },
+ 50 * MS_PER_SEC);
+ animA.currentTime = 50 * MS_PER_SEC;
+ animB.currentTime = 10 * MS_PER_SEC;
+ assert_equals(getComputedStyle(target).marginLeft, '50px',
+ 'original margin-left of the target element');
+ assert_equals(getComputedStyle(target).marginTop, '20px',
+ 'original margin-top of the target element');
+
+ animB.effect = animA.effect;
+ assert_equals(getComputedStyle(target).marginLeft, '10px',
+ 'new margin-left of the target element');
+ assert_equals(getComputedStyle(target).marginTop, '-10px',
+ 'new margin-top of the target element');
+}, 'After setting target effect with an animation to another animation which ' +
+ 'also has an target effect and both animation effects target to the same ' +
+ 'element, the computed style of this element should reflect the time and ' +
+ 'effect of the animation that was set');
+
+test(function(t) {
+ var targetA = addDiv(t);
+ var targetB = addDiv(t);
+ targetB.style.marginLeft = '-10px';
+ var animA = targetA.animate({ marginLeft: [ '0px', '100px' ] },
+ 100 * MS_PER_SEC);
+ var animB = targetB.animate({ marginLeft: [ '0px', '100px' ] },
+ 50 * MS_PER_SEC);
+ animA.currentTime = 50 * MS_PER_SEC;
+ animB.currentTime = 10 * MS_PER_SEC;
+ assert_equals(getComputedStyle(targetA).marginLeft, '50px',
+ 'original margin-left of the first element');
+ assert_equals(getComputedStyle(targetB).marginLeft, '20px',
+ 'original margin-left of the second element');
+
+ animB.effect = animA.effect;
+ assert_equals(getComputedStyle(targetA).marginLeft, '10px',
+ 'new margin-left of the first element');
+ assert_equals(getComputedStyle(targetB).marginLeft, '-10px',
+ 'new margin-left of the second element');
+}, 'After setting target effect with an animation to another animation which ' +
+ 'also has an target effect and these animation effects target to ' +
+ 'different elements, the computed styles of the two elements should ' +
+ 'reflect the time and effect of the animation that was set');
+
+test(function(t) {
+ var target = addDiv(t);
+ var animA = target.animate({ marginLeft: [ '0px', '100px' ] },
+ 50 * MS_PER_SEC);
+ var animB = target.animate({ marginTop: [ '0px', '50px' ] },
+ 100 * MS_PER_SEC);
+ animA.currentTime = 20 * MS_PER_SEC;
+ animB.currentTime = 30 * MS_PER_SEC;
+ assert_equals(getComputedStyle(target).marginLeft, '40px');
+ assert_equals(getComputedStyle(target).marginTop, '15px');
+
+ var effectA = animA.effect;
+ animA.effect = animB.effect;
+ animB.effect = effectA;
+ assert_equals(getComputedStyle(target).marginLeft, '60px');
+ assert_equals(getComputedStyle(target).marginTop, '10px');
+}, 'After swapping effects of two playing animations, both animations are ' +
+ 'still running with the same current time');
+
+ </script>
+ </body>
+</html>
diff --git a/dom/animation/test/style/test_composite.html b/dom/animation/test/style/test_composite.html
new file mode 100644
index 0000000000..1383b1b1e6
--- /dev/null
+++ b/dom/animation/test/style/test_composite.html
@@ -0,0 +1,142 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../testcommon.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<style>
+div {
+ /* Element needs geometry to be eligible for layerization */
+ width: 20px;
+ height: 20px;
+ background-color: white;
+}
+</style>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+if (!SpecialPowers.DOMWindowUtils.layerManagerRemote ||
+ !SpecialPowers.getBoolPref(
+ 'layers.offmainthreadcomposition.async-animations')) {
+ // If OMTA is disabled, nothing to run.
+ done();
+}
+
+function waitForPaintsFlushed() {
+ return new Promise(function(resolve, reject) {
+ waitForAllPaintsFlushed(resolve);
+ });
+}
+
+promise_test(t => {
+ // Without this, the first test case fails on Android.
+ return waitForDocumentLoad();
+}, 'Ensure document has been loaded');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'transform: translateX(100px)' });
+ div.animate({ transform: ['translateX(0px)', 'translateX(200px)'],
+ composite: 'accumulate' },
+ 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 200, 0)',
+ 'Transform value at 50%');
+ });
+}, 'Accumulate onto the base value');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t);
+ div.animate({ transform: ['translateX(100px)', 'translateX(200px)'],
+ composite: 'replace' },
+ 100 * MS_PER_SEC);
+ div.animate({ transform: ['translateX(0px)', 'translateX(100px)'],
+ composite: 'accumulate' },
+ 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 200, 0)',
+ 'Transform value at 50%');
+ });
+}, 'Accumulate onto an underlying animation value');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'transform: translateX(100px)' });
+ div.animate([{ transform: 'translateX(100px)', composite: 'accumulate' },
+ { transform: 'translateX(300px)', composite: 'replace' }],
+ 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 250, 0)',
+ 'Transform value at 50s');
+ });
+}, 'Composite when mixing accumulate and replace');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'transform: translateX(100px)' });
+ div.animate([{ transform: 'translateX(100px)', composite: 'replace' },
+ { transform: 'translateX(300px)' }],
+ { duration: 100 * MS_PER_SEC, composite: 'accumulate' });
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 250, 0)',
+ 'Transform value at 50%');
+ });
+}, 'Composite specified on a keyframe overrides the composite mode of the ' +
+ 'effect');
+
+promise_test(t => {
+ var div;
+ var anim;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t);
+ div.animate({ transform: [ 'scale(2)', 'scale(2)' ] }, 100 * MS_PER_SEC);
+ anim = div.animate({ transform: [ 'scale(4)', 'scale(4)' ] },
+ { duration: 100 * MS_PER_SEC, composite: 'add' });
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(8, 0, 0, 8, 0, 0)',
+ 'The additive scale value should be scale(8)'); // scale(2) scale(4)
+
+ anim.effect.composite = 'accumulate';
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(1);
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(5, 0, 0, 5, 0, 0)',
+ // (scale(2 - 1) + scale(4 - 1) + scale(1))
+ 'The accumulate scale value should be scale(5)');
+ });
+}, 'Composite operation change');
+
+</script>
+</body>
diff --git a/dom/animation/test/style/test_interpolation-from-interpolatematrix-to-none.html b/dom/animation/test/style/test_interpolation-from-interpolatematrix-to-none.html
new file mode 100644
index 0000000000..1da95392eb
--- /dev/null
+++ b/dom/animation/test/style/test_interpolation-from-interpolatematrix-to-none.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src='../testcommon.js'></script>
+<div id='log'></div>
+<script type='text/javascript'>
+'use strict';
+
+test(function(t) {
+ var target = addDiv(t);
+ target.style.transform = 'translateX(100px)';
+ target.style.transition = 'all 10s linear -5s';
+ getComputedStyle(target).transform;
+
+ target.style.transform = 'rotate(90deg)';
+ var interpolated_matrix = 'matrix(' + Math.cos(Math.PI / 4) + ',' +
+ Math.sin(Math.PI / 4) + ',' +
+ -Math.sin(Math.PI / 4) + ',' +
+ Math.cos(Math.PI / 4) + ',' +
+ '50, 0)';
+ assert_matrix_equals(getComputedStyle(target).transform,
+ interpolated_matrix,
+ 'the equivalent matrix of ' + 'interpolatematrix(' +
+ 'translateX(100px), rotate(90deg), 0.5)');
+
+ // Trigger a new transition from
+ // interpolatematrix(translateX(100px), rotate(90deg), 0.5) to none
+ // with 'all 10s linear -5s'.
+ target.style.transform = 'none';
+ interpolated_matrix = 'matrix(' + Math.cos(Math.PI / 8) + ',' +
+ Math.sin(Math.PI / 8) + ',' +
+ -Math.sin(Math.PI / 8) + ',' +
+ Math.cos(Math.PI / 8) + ',' +
+ '25, 0)';
+ assert_matrix_equals(getComputedStyle(target).transform,
+ interpolated_matrix,
+ 'the expected matrix from interpolatematrix(' +
+ 'translateX(100px), rotate(90deg), 0.5) to none at 50%');
+}, 'Test interpolation from interpolatematrix to none at 50%');
+
+</script>
+</html>
diff --git a/dom/animation/test/style/test_missing-keyframe-on-compositor.html b/dom/animation/test/style/test_missing-keyframe-on-compositor.html
new file mode 100644
index 0000000000..8b92a89168
--- /dev/null
+++ b/dom/animation/test/style/test_missing-keyframe-on-compositor.html
@@ -0,0 +1,577 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../testcommon.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<style>
+div {
+ /* Element needs geometry to be eligible for layerization */
+ width: 100px;
+ height: 100px;
+ background-color: white;
+}
+</style>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+if (!SpecialPowers.DOMWindowUtils.layerManagerRemote ||
+ !SpecialPowers.getBoolPref(
+ 'layers.offmainthreadcomposition.async-animations')) {
+ // If OMTA is disabled, nothing to run.
+ done();
+}
+
+function waitForPaintsFlushed() {
+ return new Promise(function(resolve, reject) {
+ waitForAllPaintsFlushed(resolve);
+ });
+}
+
+// Note that promise tests run in sequence so this ensures the document is
+// loaded before any of the other tests run.
+promise_test(t => {
+ // Without this, the first test case fails on Android.
+ return waitForDocumentLoad();
+}, 'Ensure document has been loaded');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'opacity: 0.1' });
+ div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
+ return waitForPaintsFlushed();
+ }).then(() => {
+ var opacity =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
+ assert_equals(opacity, '0.1',
+ 'The initial opacity value should be the base value');
+ });
+}, 'Initial opacity value for animation with no no keyframe at offset 0');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'opacity: 0.1' });
+ div.animate({ opacity: [ 0.5, 1 ] }, 100 * MS_PER_SEC);
+ div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ var opacity =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
+ assert_equals(opacity, '0.5',
+ 'The initial opacity value should be the value of ' +
+ 'lower-priority animation value');
+ });
+}, 'Initial opacity value for animation with no keyframe at offset 0 when ' +
+ 'there is a lower-priority animation');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'opacity: 0.1; transition: opacity 100s linear' });
+ getComputedStyle(div).opacity;
+
+ div.style.opacity = '0.5';
+ getComputedStyle(div).opacity;
+
+ div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ var opacity =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
+ assert_equals(opacity, '0.1',
+ 'The initial opacity value should be the initial value of ' +
+ 'the transition');
+ });
+}, 'Initial opacity value for animation with no keyframe at offset 0 when ' +
+ 'there is a transition on the same property');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'opacity: 0' });
+ div.animate([{ offset: 0, opacity: 1 }], 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+
+ var opacity =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
+ assert_equals(opacity, '0.5',
+ 'Opacity value at 50% should be composed onto the base ' +
+ 'value');
+ });
+}, 'Opacity value for animation with no keyframe at offset 1 at 50% ');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'opacity: 0' });
+ div.animate({ opacity: [ 0.5, 0.5 ] }, 100 * MS_PER_SEC);
+ div.animate([{ offset: 0, opacity: 1 }], 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+
+ var opacity =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
+ assert_equals(opacity, '0.75', // (0.5 + 1) * 0.5
+ 'Opacity value at 50% should be composed onto the value ' +
+ 'of middle of lower-priority animation');
+ });
+}, 'Opacity value for animation with no keyframe at offset 1 at 50% when ' +
+ 'there is a lower-priority animation');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'opacity: 0; transition: opacity 100s linear' });
+ getComputedStyle(div).opacity;
+
+ div.style.opacity = '0.5';
+ getComputedStyle(div).opacity;
+
+ div.animate([{ offset: 0, opacity: 1 }], 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+
+ var opacity =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
+ assert_equals(opacity, '0.625', // ((0 + 0.5) * 0.5 + 1) * 0.5
+ 'Opacity value at 50% should be composed onto the value ' +
+ 'of middle of transition');
+ });
+}, 'Opacity value for animation with no keyframe at offset 1 at 50% when ' +
+ 'there is a transition on the same property');
+
+promise_test(t => {
+ var div;
+ var lowerAnimation;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t);
+ lowerAnimation = div.animate({ opacity: [ 0.5, 1 ] }, 100 * MS_PER_SEC);
+ var higherAnimation = div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ lowerAnimation.pause();
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+
+ var opacity =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
+ // The underlying value is the value that is staying at 0ms of the
+ // lowerAnimation, that is 0.5.
+ // (0.5 + 1.0) * (50 * MS_PER_SEC / 100 * MS_PER_SEC) = 0.75.
+ assert_equals(opacity, '0.75',
+ 'Composed opacity value should be composed onto the value ' +
+ 'of lower-priority paused animation');
+ });
+}, 'Opacity value for animation with no keyframe at offset 0 at 50% when ' +
+ 'composed onto a paused underlying animation');
+
+promise_test(t => {
+ var div;
+ var lowerAnimation;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t);
+ lowerAnimation = div.animate({ opacity: [ 0.5, 1 ] }, 100 * MS_PER_SEC);
+ var higherAnimation = div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ lowerAnimation.playbackRate = 0;
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+
+ var opacity =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
+ // The underlying value is the value that is staying at 0ms of the
+ // lowerAnimation, that is 0.5.
+ // (0.5 + 1.0) * (50 * MS_PER_SEC / 100 * MS_PER_SEC) = 0.75.
+ assert_equals(opacity, '0.75',
+ 'Composed opacity value should be composed onto the value ' +
+ 'of lower-priority zero playback rate animation');
+ });
+}, 'Opacity value for animation with no keyframe at offset 0 at 50% when ' +
+ 'composed onto a zero playback rate underlying animation');
+
+promise_test(t => {
+ var div;
+ var lowerAnimation;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t);
+ lowerAnimation = div.animate({ opacity: [ 1, 0.5 ] }, 100 * MS_PER_SEC);
+ var higherAnimation = div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ lowerAnimation.effect.updateTiming({
+ duration: 0,
+ fill: 'forwards',
+ });
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+
+ var opacity =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
+ // The underlying value is the value that is filling forwards state of the
+ // lowerAnimation, that is 0.5.
+ // (0.5 + 1.0) * (50 * MS_PER_SEC / 100 * MS_PER_SEC) = 0.75.
+ assert_equals(opacity, '0.75',
+ 'Composed opacity value should be composed onto the value ' +
+ 'of lower-priority zero active duration animation');
+ });
+}, 'Opacity value for animation with no keyframe at offset 0 at 50% when ' +
+ 'composed onto a zero active duration underlying animation');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'transform: translateX(100px)' });
+ div.animate({ transform: 'translateX(200px)' }, 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 100, 0)',
+ 'The initial transform value should be the base value');
+ });
+}, 'Initial transform value for animation with no keyframe at offset 0');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'transform: translateX(100px)' });
+ div.animate({ transform: [ 'translateX(200px)', 'translateX(300px)' ] },
+ 100 * MS_PER_SEC);
+ div.animate({ transform: 'translateX(400px)' }, 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 200, 0)',
+ 'The initial transform value should be lower-priority animation value');
+ });
+}, 'Initial transform value for animation with no keyframe at offset 0 when ' +
+ 'there is a lower-priority animation');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'transform: translateX(100px);' +
+ 'transition: transform 100s linear' });
+ getComputedStyle(div).transform;
+
+ div.style.transform = 'translateX(200px)';
+ getComputedStyle(div).transform;
+
+ div.animate({ transform: 'translateX(400px)' }, 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 100, 0)',
+ 'The initial transform value should be the initial value of the ' +
+ 'transition');
+ });
+}, 'Initial transform value for animation with no keyframe at offset 0 when ' +
+ 'there is a transition');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'transform: translateX(100px)' });
+ div.animate([{ offset: 0, transform: 'translateX(200pX)' }],
+ 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 150, 0)',
+ 'Transform value at 50% should be the base value');
+ });
+}, 'Transform value for animation with no keyframe at offset 1 at 50%');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'transform: translateX(100px)' });
+ div.animate({ transform: [ 'translateX(200px)', 'translateX(200px)' ] },
+ 100 * MS_PER_SEC);
+ div.animate([{ offset: 0, transform: 'translateX(300px)' }],
+ 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 250, 0)',
+ 'The final transform value should be the base value');
+ });
+}, 'Transform value for animation with no keyframe at offset 1 at 50% when ' +
+ 'there is a lower-priority animation');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'transform: translateX(100px);' +
+ 'transition: transform 100s linear' });
+ getComputedStyle(div).transform;
+
+ div.style.transform = 'translateX(200px)';
+ getComputedStyle(div).transform;
+
+ div.animate([{ offset: 0, transform: 'translateX(300px)' }],
+ 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ // (150px + 300px) * 0.5
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 225, 0)',
+ 'The final transform value should be the final value of the transition');
+ });
+}, 'Transform value for animation with no keyframe at offset 1 at 50% when ' +
+ 'there is a transition');
+
+promise_test(t => {
+ var div;
+ var lowerAnimation;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t);
+ lowerAnimation =
+ div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
+ 100 * MS_PER_SEC);
+ var higherAnimation = div.animate({ transform: 'translateX(300px)' },
+ 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ lowerAnimation.pause();
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ // The underlying value is the value that is staying at 0ms of the
+ // lowerAnimation, that is 100px.
+ // (100px + 300px) * (50 * MS_PER_SEC / 100 * MS_PER_SEC) = 200px.
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 200, 0)',
+ 'Composed transform value should be composed onto the value of ' +
+ 'lower-priority paused animation');
+ });
+}, 'Transform value for animation with no keyframe at offset 0 at 50% when ' +
+ 'composed onto a paused underlying animation');
+
+promise_test(t => {
+ var div;
+ var lowerAnimation;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t);
+ lowerAnimation =
+ div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
+ 100 * MS_PER_SEC);
+ var higherAnimation = div.animate({ transform: 'translateX(300px)' },
+ 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ lowerAnimation.playbackRate = 0;
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ // The underlying value is the value that is staying at 0ms of the
+ // lowerAnimation, that is 100px.
+ // (100px + 300px) * (50 * MS_PER_SEC / 100 * MS_PER_SEC) = 200px.
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 200, 0)',
+ 'Composed transform value should be composed onto the value of ' +
+ 'lower-priority zero playback rate animation');
+ });
+}, 'Transform value for animation with no keyframe at offset 0 at 50% when ' +
+ 'composed onto a zero playback rate underlying animation');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t);
+ var lowerAnimation =
+ div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
+ { duration: 10 * MS_PER_SEC,
+ fill: 'forwards' });
+ var higherAnimation = div.animate({ transform: 'translateX(300px)' },
+ 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+
+ // We need to wait for a paint so that we can send the state of the lower
+ // animation that is actually finished at this point.
+ return waitForPaintsFlushed();
+ }).then(() => {
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ // (200px + 300px) * (50 * MS_PER_SEC / 100 * MS_PER_SEC) = 250px.
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 250, 0)',
+ 'Composed transform value should be composed onto the value of ' +
+ 'lower-priority animation with fill:forwards');
+ });
+}, 'Transform value for animation with no keyframe at offset 0 at 50% when ' +
+ 'composed onto a underlying animation with fill:forwards');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t);
+ var lowerAnimation =
+ div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
+ { duration: 10 * MS_PER_SEC,
+ endDelay: -5 * MS_PER_SEC,
+ fill: 'forwards' });
+ var higherAnimation = div.animate({ transform: 'translateX(300px)' },
+ 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+
+ // We need to wait for a paint just like the above test.
+ return waitForPaintsFlushed();
+ }).then(() => {
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ // (150px + 300px) * (50 * MS_PER_SEC / 100 * MS_PER_SEC) = 225px.
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 225, 0)',
+ 'Composed transform value should be composed onto the value of ' +
+ 'lower-priority animation with fill:forwards and negative endDelay');
+ });
+}, 'Transform value for animation with no keyframe at offset 0 at 50% when ' +
+ 'composed onto a underlying animation with fill:forwards and negative ' +
+ 'endDelay');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t);
+ var lowerAnimation =
+ div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
+ { duration: 10 * MS_PER_SEC,
+ endDelay: 100 * MS_PER_SEC,
+ fill: 'forwards' });
+ var higherAnimation = div.animate({ transform: 'translateX(300px)' },
+ 100 * MS_PER_SEC);
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ // (200px + 300px) * 0.5
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 250, 0)',
+ 'Composed transform value should be composed onto the value of ' +
+ 'lower-priority animation with fill:forwards during positive endDelay');
+ });
+}, 'Transform value for animation with no keyframe at offset 0 at 50% when ' +
+ 'composed onto a underlying animation with fill:forwards during positive ' +
+ 'endDelay');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'transform: translateX(100px)' });
+ div.animate({ transform: 'translateX(200px)' },
+ { duration: 100 * MS_PER_SEC, delay: 50 * MS_PER_SEC });
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(100 * MS_PER_SEC);
+
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 150, 0)',
+ 'Transform value for animation with positive delay should be composed ' +
+ 'onto the base style');
+ });
+}, 'Transform value for animation with no keyframe at offset 0 and with ' +
+ 'positive delay');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t, { style: 'transform: translateX(100px)' });
+
+ div.animate([{ offset: 0, transform: 'translateX(200px)'}],
+ { duration: 100 * MS_PER_SEC,
+ iterationStart: 1,
+ iterationComposite: 'accumulate' });
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 300, 0)',
+ 'Transform value for animation with no keyframe at offset 1 and its ' +
+ 'iterationComposite is accumulate');
+ });
+}, 'Transform value for animation with no keyframe at offset 1 and its ' +
+ 'iterationComposite is accumulate');
+
+promise_test(t => {
+ var div;
+ return useTestRefreshMode(t).then(() => {
+ div = addDiv(t);
+ var lowerAnimation =
+ div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
+ 100 * MS_PER_SEC);
+ var higherAnimation = div.animate({ transform: 'translateX(300px)' },
+ 100 * MS_PER_SEC);
+
+ lowerAnimation.timeline = null;
+ // Set current time at 50% duration.
+ lowerAnimation.currentTime = 50 * MS_PER_SEC;
+
+ return waitForPaintsFlushed();
+ }).then(() => {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+
+ var transform =
+ SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+ // (150px + 300px) * (50 * MS_PER_SEC / 100 * MS_PER_SEC) = 225px.
+ assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 225, 0)',
+ 'Composed transform value should be composed onto the value of ' +
+ 'lower-priority animation without timeline');
+ });
+}, 'Transform value for animation with no keyframe at offset 0 at 50% when ' +
+ 'composed onto an animation without timeline');
+
+</script>
+</body>
diff --git a/dom/animation/test/style/test_missing-keyframe.html b/dom/animation/test/style/test_missing-keyframe.html
new file mode 100644
index 0000000000..4047e62408
--- /dev/null
+++ b/dom/animation/test/style/test_missing-keyframe.html
@@ -0,0 +1,110 @@
+<!doctype html>
+<meta charset=utf-8>
+<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 => {
+ var div = addDiv(t, { style: 'margin-left: 100px' });
+ div.animate([{ marginLeft: '200px' }], 100 * MS_PER_SEC);
+
+ assert_equals(getComputedStyle(div).marginLeft, '100px',
+ 'The initial margin-left value should be the base value');
+}, 'Initial margin-left value for an animation with no keyframe at offset 0');
+
+test(t => {
+ var div = addDiv(t, { style: 'margin-left: 100px' });
+ div.animate([{ offset: 0, marginLeft: '200px' },
+ { offset: 1, marginLeft: '300px' }],
+ 100 * MS_PER_SEC);
+ div.animate([{ marginLeft: '200px' }], 100 * MS_PER_SEC);
+
+ assert_equals(getComputedStyle(div).marginLeft, '200px',
+ 'The initial margin-left value should be the initial value ' +
+ 'of lower-priority animation');
+}, 'Initial margin-left value for an animation with no keyframe at offset 0 ' +
+ 'is that of lower-priority animations');
+
+test(t => {
+ var div = addDiv(t, { style: 'margin-left: 100px;' +
+ 'transition: margin-left 100s -50s linear'});
+ flushComputedStyle(div);
+
+ div.style.marginLeft = '200px';
+ flushComputedStyle(div);
+
+ div.animate([{ marginLeft: '300px' }], 100 * MS_PER_SEC);
+
+ assert_equals(getComputedStyle(div).marginLeft, '150px',
+ 'The initial margin-left value should be the initial value ' +
+ 'of the transition');
+}, 'Initial margin-left value for an animation with no keyframe at offset 0 ' +
+ 'is that of transition');
+
+test(t => {
+ var div = addDiv(t, { style: 'margin-left: 100px' });
+ var animation = div.animate([{ offset: 0, marginLeft: '200px' }],
+ 100 * MS_PER_SEC);
+
+ animation.currentTime = 50 * MS_PER_SEC;
+ assert_equals(getComputedStyle(div).marginLeft, '150px',
+ 'The margin-left value at 50% should be the base value');
+}, 'margin-left value at 50% for an animation with no keyframe at offset 1');
+
+test(t => {
+ var div = addDiv(t, { style: 'margin-left: 100px' });
+ var lowerAnimation = div.animate([{ offset: 0, marginLeft: '200px' },
+ { offset: 1, marginLeft: '300px' }],
+ 100 * MS_PER_SEC);
+ var higherAnimation = div.animate([{ offset: 0, marginLeft: '400px' }],
+ 100 * MS_PER_SEC);
+
+ lowerAnimation.currentTime = 50 * MS_PER_SEC;
+ higherAnimation.currentTime = 50 * MS_PER_SEC;
+ // (250px + 400px) * 0.5
+ assert_equals(getComputedStyle(div).marginLeft, '325px',
+ 'The margin-left value at 50% should be additive value of ' +
+ 'lower-priority animation and higher-priority animation');
+}, 'margin-left value at 50% for an animation with no keyframe at offset 1 ' +
+ 'is that of lower-priority animations');
+
+test(t => {
+ var div = addDiv(t, { style: 'margin-left: 100px;' +
+ 'transition: margin-left 100s linear' });
+ flushComputedStyle(div);
+
+ div.style.marginLeft = '300px';
+ flushComputedStyle(div);
+
+ div.animate([{ offset: 0, marginLeft: '200px' }], 100 * MS_PER_SEC);
+
+ div.getAnimations().forEach(animation => {
+ animation.currentTime = 50 * MS_PER_SEC;
+ });
+ // (200px + 200px) * 0.5
+ assert_equals(getComputedStyle(div).marginLeft, '200px',
+ 'The margin-left value at 50% should be additive value of ' +
+ 'the transition and animation');
+}, 'margin-left value at 50% for an animation with no keyframe at offset 1 ' +
+ 'is that of transition');
+
+test(t => {
+ var div = addDiv(t, { style: 'margin-left: 100px' });
+
+ var animation = div.animate([{ offset: 0, marginLeft: '200px' }],
+ { duration: 100 * MS_PER_SEC,
+ iterationStart: 1,
+ iterationComposite: 'accumulate' });
+
+ assert_equals(getComputedStyle(div).marginLeft, '300px',
+ 'The margin-left value should be additive value of the ' +
+ 'accumulation of the initial value onto the base value ');
+}, 'margin-left value for an animation with no keyframe at offset 1 and its ' +
+ 'iterationComposite is accumulate');
+
+</script>
+</body>
diff --git a/dom/animation/test/style/test_transform-non-normalizable-rotate3d.html b/dom/animation/test/style/test_transform-non-normalizable-rotate3d.html
new file mode 100644
index 0000000000..ad2584ac40
--- /dev/null
+++ b/dom/animation/test/style/test_transform-non-normalizable-rotate3d.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src='../testcommon.js'></script>
+<div id='log'></div>
+<script type='text/javascript'>
+'use strict';
+
+test(function(t) {
+ var target = addDiv(t);
+ target.style.transform = 'rotate3d(0, 0, 1, 90deg)';
+ target.style.transition = 'all 10s linear -5s';
+ getComputedStyle(target).transform;
+
+ target.style.transform = 'rotate3d(0, 0, 0, 270deg)';
+ var interpolated_matrix = 'matrix(' + Math.cos(Math.PI / 4) + ',' +
+ Math.sin(Math.PI / 4) + ',' +
+ -Math.sin(Math.PI / 4) + ',' +
+ Math.cos(Math.PI / 4) + ',' +
+ '0, 0)';
+ assert_matrix_equals(getComputedStyle(target).transform, interpolated_matrix,
+ 'transition from a normal rotate3d to a ' +
+ 'non-normalizable rotate3d');
+}, 'Test interpolation on non-normalizable rotate3d function');
+
+</script>
+</html>