diff options
Diffstat (limited to 'layout/style/test/test_animations_omta.html')
-rw-r--r-- | layout/style/test/test_animations_omta.html | 2969 |
1 files changed, 2969 insertions, 0 deletions
diff --git a/layout/style/test/test_animations_omta.html b/layout/style/test/test_animations_omta.html new file mode 100644 index 0000000000..06a409b490 --- /dev/null +++ b/layout/style/test/test_animations_omta.html @@ -0,0 +1,2969 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=964646 +--> +<!-- + + ========= PLEASE KEEP THIS IN SYNC WITH test_animations.html ========= + + This test mimicks the content of test_animations.html but performs tests + specific to animations that run on the compositor thread since they require + special (asynchronous) handling. Furthermore, these tests check that + animations that are expected to run on the compositor thread, are actually + doing so. + + If you are making changes to this file or to test_animations.html, please + try to keep them consistent where appropriate. + +--> +<head> + <meta charset="utf-8"> + <title>Test for css3-animations running on the compositor thread (Bug + 964646)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + @keyframes transform-anim { + to { + transform: translate(100px); + } + } + @keyframes anim1 { + 0% { transform: translate(0px) } + 50% { transform: translate(80px) } + 100% { transform: translate(100px) } + } + @keyframes anim2 { + from { opacity: 0 } to { opacity: 1 } + } + @keyframes anim3 { + from { opacity: 0 } to { opacity: 1 } + } + @keyframes anim4 { + from { transform: translate(0px, 0px) } + to { transform: translate(0px, 100px) } + } + + @keyframes kf1 { + 50% { transform: translate(50px) } + to { transform: translate(150px) } + } + @keyframes kf2 { + from { transform: translate(150px) } + 50% { transform: translate(50px) } + } + @keyframes kf3 { + 25% { transform: translate(100px) } + } + @keyframes kf4 { + to, from { display: none; transform: translate(37px) } + } + @keyframes kf_cascade1 { + from { transform: translate(50px) } + 50%, from { transform: translate(30px) } /* wins: 0% */ + 75%, 85%, 50% { transform: translate(20px) } /* wins: 75%, 50% */ + 100%, 85% { transform: translate(70px) } /* wins: 100% */ + 85.1% { transform: translate(60px) } /* wins: 85.1% */ + 85% { transform: translate(30px) } /* wins: 85% */ + } + @keyframes kf_cascade2 { from, to { opacity: 0.3 } } + @keyframes kf_cascade2 { from, to { transform: translate(50px) } } + @keyframes kf_cascade2 { from, to { transform: translate(100px) } } + @keyframes kf_tf1 { + 0% { transform: translate(20px); animation-timing-function: ease } + 25% { transform: translate(60px); } + 50% { transform: translate(160px); animation-timing-function: steps(5) } + 75% { transform: translate(120px); animation-timing-function: linear } + 100% { transform: translate(20px); animation-timing-function: ease-out } + } + @keyframes kf_scale { + to { scale: 2.25 2.25; } + } + + @keyframes always_fifty { + from, to { transform: translate(50px) } + } + + #withbefore::before, #withafter::after { + content: "test"; + animation: anim4 1s linear alternate 3; + display:block; + } + + @keyframes multiprop { + 0% { + transform: translate(10px); opacity: 0.3; + animation-timing-function: ease; + } + 25% { + opacity: 0.5; + animation-timing-function: ease-out; + } + 50% { + transform: translate(40px); + } + 75% { + transform: translate(80px); opacity: 0.6; + animation-timing-function: ease-in; + } + } + + @keyframes cascade { + 0%, 25%, 100% { transform: translate(0px) } + 50%, 75% { transform: translate(100px) } + 0%, 75%, 100% { opacity: 0 } + 25%, 50% { opacity: 1 } + } + @keyframes cascade2 { + 0% { transform: translate(0px) } + 25% { transform: translate(30px); + animation-timing-function: ease-in } /* beaten by rule below */ + 50% { transform: translate(0px) } + 25% { transform: translate(50px) } + 100% { transform: translate(100px) } + } + + @keyframes primitives1 { + from { transform: rotate(0deg) translateX(0px) scaleX(1) + translate(0px) scale3d(1, 1, 1); } + to { transform: rotate(270deg) translate3d(0px, 0px, 0px) scale(1) + translateY(0px) scaleY(1); } + } + + @keyframes important1 { + from { opacity: 0.5; } + 50% { opacity: 1 !important; } /* ignored */ + to { opacity: 0.8; } + } + @keyframes important2 { + from { opacity: 0.5; + transform: translate(100px); } + to { opacity: 0.2 !important; /* ignored */ + transform: translate(50px); } + } + + @keyframes empty { } + @keyframes nearlyempty { + to { + transform: translate(100px); + } + } + + .target { + /* The animation target needs geometry in order to qualify for OMTA */ + width: 100px; + height: 100px; + background-color: white; + } + + .visitedLink:link { background-color: yellow } + .visitedLink:visited { background-color: blue } + + @keyframes opacitymid { + 0% { opacity: 0.2 } + 100% { opacity: 0.8 } + } + + @keyframes transformnone { + 0%, 100% { transform: translateX(50px) } + 25%, 75% { transform: none } + } + </style> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=964646">Mozilla Bug + 964646</a> +<div id="display"></div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +const { AppConstants } = SpecialPowers.ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +/** Test for css3-animations running on the compositor thread (Bug 964646) **/ + +// Global state +var gDisplay = document.getElementById("display") + , gDiv = null; + +// Shortcut omta_is and friends by filling in the initial 'elem' argument +// with gDiv. +[ 'omta_is', 'omta_todo_is', 'omta_is_approx' ].forEach(function(fn) { + var origFn = window[fn]; + window[fn] = function() { + var args = Array.from(arguments); + if (!(args[0] instanceof Element)) { + args.unshift(gDiv); + } + return origFn.apply(window, args); + }; +}); + +// Shortcut new_div and done_div to update gDiv +var originalNewDiv = window.new_div; +window.new_div = function(style) { + [ gDiv ] = originalNewDiv(style); +}; +var originalDoneDiv = window.done_div; +window.done_div = function() { + originalDoneDiv(); + gDiv = null; +}; + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestLongerTimeout(2); +runOMTATest(function() { + var onAbort = function() { + if (gDiv) { + done_div(); + } + }; + runAllAsyncAnimTests(onAbort).then(function() { + SimpleTest.finish(); + }); +}, SimpleTest.finish); + +//---------------------------------------------------------------------- +// +// Test cases +// +//---------------------------------------------------------------------- + +// This test is not in test_animations.html but is here to test that +// transform animations are actually run on the compositor thread as expected. +addAsyncAnimTest(async function() { + new_div("animation: transform-anim linear 300s"); + + await waitForPaintsFlushed(); + + advance_clock(200000); + omta_is("transform", { tx: 100 * 2 / 3 }, RunningOn.Compositor, + "OMTA animation is animating as expected"); + done_div(); +}); + +async function testFillMode(fillMode, fillsBackwards, fillsForwards) +{ + var style = "transform: translate(30px); animation: 10s 3s anim1 linear"; + var desc; + if (fillMode.length > 0) { + style += " " + fillMode; + desc = "fill mode " + fillMode + ": "; + } else { + desc = "default fill mode: "; + } + new_div(style); + listen(); + + await waitForPaintsFlushed(); + + if (fillsBackwards) + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + desc + "does affect value during delay (0s)"); + else + omta_is("transform", { tx: 30 }, RunningOn.MainThread, + desc + "doesn't affect value during delay (0s)"); + + advance_clock(2000); + if (fillsBackwards) + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + desc + "does affect value during delay (0s)"); + else + omta_is("transform", { tx: 30 }, RunningOn.MainThread, + desc + "does affect value during delay (0s)"); + + check_events([], "before start in testFillMode"); + advance_clock(1000); + check_events([{ type: "animationstart", target: gDiv, + bubbles: true, cancelable: false, + animationName: "anim1", elapsedTime: 0.0, + pseudoElement: "" }], + "right after start in testFillMode"); + + // If we have a backwards fill then at the start of the animation we will end + // up applying the same value as the fill value. Various optimizations in + // RestyleManager may filter out this meaning that the animation doesn't get + // added to the compositor thread until the first time the value changes. + // + // As a result we look for this first sample on either the compositor or the + // computed style + await waitForPaints(); + omta_is("transform", { tx: 0 }, RunningOn.Either, + desc + "affects value at start of animation"); + advance_clock(125); + // We might not add the animation to compositor until the second sample (due + // to the optimizations mentioned above) so we should wait for paints before + // proceeding + await waitForPaints(); + omta_is("transform", { tx: 2 }, RunningOn.Compositor, + desc + "affects value during animation"); + advance_clock(2375); + omta_is("transform", { tx: 40 }, RunningOn.Compositor, + desc + "affects value during animation"); + advance_clock(2500); + omta_is("transform", { tx: 80 }, RunningOn.Compositor, + desc + "affects value during animation"); + advance_clock(2500); + omta_is("transform", { tx: 90 }, RunningOn.Compositor, + desc + "affects value during animation"); + advance_clock(2375); + omta_is("transform", { tx: 99.5 }, RunningOn.Compositor, + desc + "affects value during animation"); + check_events([], "before end in testFillMode"); + advance_clock(125); + check_events([{ type: "animationend", target: gDiv, + bubbles: true, cancelable: false, + animationName: "anim1", elapsedTime: 10.0, + pseudoElement: "" }], + "right after end in testFillMode"); + + // Currently the compositor will apply a forwards fill until it gets told by + // the main thread to clear the animation. As a result we should wait for + // paints to be flushed before checking that the animated value does *not* + // appear on the compositor thread. + await waitForPaints(); + if (fillsForwards) + omta_is("transform", { tx: 100 }, RunningOn.MainThread, + desc + "affects value at end of animation"); + advance_clock(10); + if (fillsForwards) + omta_is("transform", { tx: 100 }, RunningOn.MainThread, + desc + "affects value after animation"); + else + omta_is("transform", { tx: 30 }, RunningOn.MainThread, + desc + "does not affect value after animation"); + + done_div(); +} + +addAsyncAnimTest(function() { return testFillMode("", false, false); }); +addAsyncAnimTest(function() { return testFillMode("none", false, false); }); +addAsyncAnimTest(function() { return testFillMode("forwards", false, true); }); +addAsyncAnimTest(function() { return testFillMode("backwards", true, false); }); +addAsyncAnimTest(function() { return testFillMode("both", true, true); }); + +// Test that animations continue running when the animation name +// list is changed. +// +// test_animations.html combines all these tests into one block but this is +// difficult for OMTA because currently there are only two properties to which +// we apply OMTA. Instead we break the test down into a few independent pieces +// in order to exercise the same functionality. + +// Append to list +addAsyncAnimTest(async function() { + new_div("animation: anim1 linear 10s"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Either, + "just anim1, translate at start"); + advance_clock(1000); + omta_is("transform", { tx: 16 }, RunningOn.Compositor, + "just anim1, translate at 1s"); + // append anim2 + gDiv.style.animation = "anim1 linear 10s, anim2 linear 10s"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 16 }, RunningOn.Compositor, + "anim1 + anim2, translate at 1s"); + omta_is("opacity", 0, RunningOn.Compositor, + "anim1 + anim2, opacity at 1s"); + advance_clock(1000); + omta_is("transform", { tx: 32 }, RunningOn.Compositor, + "anim1 + anim2, translate at 2s"); + omta_is("opacity", 0.1, RunningOn.Compositor, + "anim1 + anim2, opacity at 2s"); + done_div(); +}); + +// Prepend to list; delete from list +addAsyncAnimTest(async function() { + new_div("animation: anim1 linear 10s"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Either, + "just anim1, translate at start"); + advance_clock(1000); + omta_is("transform", { tx: 16 }, RunningOn.Compositor, + "just anim1, translate at 1s"); + // prepend anim2 + gDiv.style.animation = "anim2 linear 10s, anim1 linear 10s"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 16 }, RunningOn.Compositor, + "anim2 + anim1, translate at 1s"); + omta_is("opacity", 0, RunningOn.Compositor, + "anim2 + anim1, opacity at 1s"); + advance_clock(1000); + omta_is("transform", { tx: 32 }, RunningOn.Compositor, + "anim2 + anim1, translate at 2s"); + omta_is("opacity", 0.1, RunningOn.Compositor, + "anim2 + anim1, opacity at 2s"); + // remove anim2 from list + gDiv.style.animation = "anim1 linear 10s"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 32 }, RunningOn.Compositor, + "just anim1, translate at 2s"); + omta_is("opacity", 1, RunningOn.MainThread, "just anim1, opacity at 2s"); + advance_clock(1000); + omta_is("transform", { tx: 48 }, RunningOn.Compositor, + "just anim1, translate at 3s"); + omta_is("opacity", 1, RunningOn.MainThread, "just anim1, opacity at 3s"); + done_div(); +}); + +// Swap elements +addAsyncAnimTest(async function() { + new_div("animation: anim1 linear 10s, anim2 linear 10s"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Either, + "anim1 + anim2, translate at start"); + omta_is("opacity", 0, RunningOn.Compositor, + "anim1 + anim2, opacity at start"); + advance_clock(1000); + omta_is("transform", { tx: 16 }, RunningOn.Compositor, + "anim1 + anim2, translate at 1s"); + omta_is("opacity", 0.1, RunningOn.Compositor, + "anim1 + anim2, opacity at 1s"); + // swap anim1 and anim2, change duration of anim2 + gDiv.style.animation = "anim2 linear 5s, anim1 linear 10s"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 16 }, RunningOn.Compositor, + "anim2 + anim1, translate at 1s"); + omta_is("opacity", 0.2, RunningOn.Compositor, + "anim2 + anim1, opacity at 1s"); + advance_clock(1000); + omta_is("transform", { tx: 32 }, RunningOn.Compositor, + "anim2 + anim1, translate at 2s"); + omta_is("opacity", 0.4, RunningOn.Compositor, + "anim2 + anim1, opacity at 2s"); + // list anim2 twice, last duration wins, original start time still applies + gDiv.style.animation = "anim2 linear 5s, anim1 linear 10s, anim2 linear 20s"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 32 }, RunningOn.Compositor, + "anim2 + anim1 + anim2, translate at 2s"); + is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.1", + "anim2 + anim1 + anim2, opacity at 2s"); + // drop one of the anim2, and list anim3 as well, which animates + // the same property as anim2 + gDiv.style.animation = "anim1 linear 10s, anim2 linear 20s, anim3 linear 10s"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 32 }, RunningOn.Compositor, + "anim1 + anim2 + anim3, translate at 2s"); + is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0", + "anim1 + anim2 + anim3, opacity at 2s"); + advance_clock(1000); + omta_is("transform", { tx: 48 }, RunningOn.Compositor, + "anim1 + anim2 + anim3, translate at 3s"); + is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.1", + "anim1 + anim2 + anim3, opacity at 3s"); + // now swap the anim3 and anim2 order + gDiv.style.animation = "anim1 linear 10s, anim3 linear 10s, anim2 linear 20s"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 48 }, RunningOn.Compositor, + "anim1 + anim3 + anim2, translate at 3s"); + is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.15", + "anim1 + anim3 + anim2, opacity at 3s"); + advance_clock(2000); // (unlike test_animations.html, we seek 2s forwards here + // since at 4s anim2 and anim3 produce the same result so + // we can't tell which won.) + omta_is("transform", { tx: 80 }, RunningOn.Compositor, + "anim1 + anim3 + anim2, translate at 5s"); + is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.25", + "anim1 + anim3 + anim2, opacity at 5s"); + // swap anim3 and anim2 back + gDiv.style.animation = "anim1 linear 10s, anim2 linear 20s, anim3 linear 10s"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 80 }, RunningOn.Compositor, + "anim1 + anim2 + anim3, translate at 5s"); + is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.3", + "anim1 + anim2 + anim3, opacity at 5s"); + // seek past end of anim1 + advance_clock(5100); + await waitForPaints(); + omta_is("transform", { tx: 0 }, RunningOn.MainThread, + "anim1 + anim2 + anim3, translate at 10.1s"); + // Change the animation fill mode on the completed animation. + gDiv.style.animation = + "anim1 linear 10s forwards, anim2 linear 20s, anim3 linear 10s"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 100 }, RunningOn.MainThread, + "anim1 + anim2 + anim3, translate at 10.1s with fill mode"); + advance_clock(900); + omta_is("transform", { tx: 100 }, RunningOn.MainThread, + "anim1 + anim2 + anim3, translate at 11s with fill mode"); + // Change the animation duration on the completed animation, so it is + // no longer completed. + // XXX Not sure about this---there seems to be a bug in test_animations.html + // in that it drops the fill mode but the test comment says it has a fill mode + gDiv.style.animation = "anim1 linear 20s, anim2 linear 20s, anim3 linear 10s"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 82 }, RunningOn.Compositor, + "anim1 + anim2 + anim3, translate at 11s with fill mode"); + is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.9", + "anim1 + anim2 + anim3, opacity at 11s"); + done_div(); +}); + +/* + * css3-animations: 3. Keyframes + * http://dev.w3.org/csswg/css3-animations/#keyframes + */ + +// Test the rules on keyframes that lack a 0% or 100% rule: +// (simultaneously, test that reverse animations have their keyframes +// run backwards) + +addAsyncAnimTest(async function() { + // 100px at 0%, 50px at 50%, 150px at 100% + new_div("transform: translate(100px); " + + "animation: kf1 ease 1s alternate infinite"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-0% at 0.0s"); + advance_clock(100); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.2) }, 0.01, + RunningOn.Compositor, "no-0% at 0.1s"); + advance_clock(200); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.6) }, 0.01, + RunningOn.Compositor, "no-0% at 0.3s"); + advance_clock(200); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, "no-0% at 0.5s"); + advance_clock(200); + omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.4) }, 0.01, + RunningOn.Compositor, "no-0% at 0.7s"); + advance_clock(200); + omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.8) }, 0.01, + RunningOn.Compositor, "no-0% at 0.9s"); + advance_clock(100); + omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-0% at 1.0s"); + advance_clock(100); + omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.8) }, 0.01, + RunningOn.Compositor, "no-0% at 1.1s"); + advance_clock(300); + omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.2) }, 0.01, + RunningOn.Compositor, "no-0% at 1.4s"); + advance_clock(300); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.6) }, 0.01, + RunningOn.Compositor, "no-0% at 1.7s"); + advance_clock(200); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.2) }, 0.01, + RunningOn.Compositor, "no-0% at 1.9s"); + advance_clock(100); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-0% at 2.0s"); + done_div(); + + // 150px at 0%, 50px at 50%, 100px at 100% + new_div("transform: translate(100px); " + + "animation: kf2 ease-in 1s alternate infinite"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-100% at 0.0s"); + advance_clock(100); + omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.2) }, 0.01, + RunningOn.Compositor, "no-100% at 0.1s"); + advance_clock(200); + omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.6) }, 0.01, + RunningOn.Compositor, "no-100% at 0.3s"); + advance_clock(200); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, "no-100% at 0.5s"); + advance_clock(200); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.4) }, 0.01, + RunningOn.Compositor, "no-100% at 0.7s"); + advance_clock(200); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.8) }, 0.01, + RunningOn.Compositor, "no-100% at 0.9s"); + advance_clock(100); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-100% at 1.0s"); + advance_clock(100); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.8) }, 0.01, + RunningOn.Compositor, "no-100% at 1.1s"); + advance_clock(300); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.2) }, 0.01, + RunningOn.Compositor, "no-100% at 1.4s"); + advance_clock(300); + omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.6) }, 0.01, + RunningOn.Compositor, "no-100% at 1.7s"); + advance_clock(200); + omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.2) }, 0.01, + RunningOn.Compositor, "no-100% at 1.9s"); + advance_clock(100); + omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-100% at 2.0s"); + done_div(); + + // 50px at 0%, 100px at 25%, 50px at 100% + new_div("transform: translate(50px); " + + "animation: kf3 ease-out 1s alternate infinite"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "no-0%-no-100% at 0.0s"); + advance_clock(50); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.2) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 0.05s"); + advance_clock(100); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.6) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 0.15s"); + advance_clock(100); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "no-0%-no-100% at 0.25s"); + advance_clock(300); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.4) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 0.55s"); + advance_clock(300); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.8) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 0.85s"); + advance_clock(150); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "no-0%-no-100% at 1.0s"); + advance_clock(150); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.8) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 1.15s"); + advance_clock(450); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.2) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 1.6s"); + advance_clock(250); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.6) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 1.85s"); + advance_clock(100); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.2) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 1.95s"); + advance_clock(50); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "no-0%-no-100% at 2.0s"); + done_div(); + + // Test that non-animatable properties are ignored. + // Simultaneously, test that the block is still honored, and that + // we still override the value when two consecutive keyframes have + // the same value. + new_div("animation: kf4 ease 10s"); + await waitForPaintsFlushed(); + var cs = window.getComputedStyle(gDiv); + is(cs.display, "block", + "non-animatable properties should be ignored (linear, 0s)"); + omta_is("transform", { tx: 37 }, RunningOn.Compositor, + "animatable properties should still apply (linear, 0s)"); + advance_clock(1000); + is(cs.display, "block", + "non-animatable properties should be ignored (linear, 1s)"); + omta_is("transform", { tx: 37 }, RunningOn.Compositor, + "animatable properties should still apply (linear, 1s)"); + done_div(); + new_div("animation: kf4 step-start 10s"); + await waitForPaintsFlushed(); + cs = window.getComputedStyle(gDiv); + is(cs.display, "block", + "non-animatable properties should be ignored (step-start, 0s)"); + omta_is("transform", { tx: 37 }, RunningOn.Compositor, + "animatable properties should still apply (step-start, 0s)"); + advance_clock(1000); + is(cs.display, "block", + "non-animatable properties should be ignored (step-start, 1s)"); + omta_is("transform", { tx: 37 }, RunningOn.Compositor, + "animatable properties should still apply (step-start, 1s)"); + done_div(); + + // Test cascading of the keyframes within an @keyframes rule. + new_div("animation: kf_cascade1 linear 10s"); + await waitForPaintsFlushed(); + // 0%: 30px + // 50%: 20px + // 75%: 20px + // 85%: 30px + // 85.1%: 60px + // 100%: 70px + omta_is("transform", { tx: 30 }, RunningOn.Compositor, "kf_cascade1 at 0s"); + advance_clock(2500); + omta_is("transform", { tx: 25 }, RunningOn.Compositor, "kf_cascade1 at 2.5s"); + advance_clock(2500); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 5s"); + advance_clock(2000); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 7s"); + advance_clock(500); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 7.5s"); + advance_clock(500); + omta_is("transform", { tx: 25 }, RunningOn.Compositor, "kf_cascade1 at 8s"); + advance_clock(500); + omta_is("transform", { tx: 30 }, RunningOn.Compositor, "kf_cascade1 at 8.5s"); + advance_clock(10); + // For some reason we get an error of 0.0003 for this test only + omta_is_approx("transform", { tx: 60 }, 0.001, RunningOn.Compositor, + "kf_cascade1 at 8.51s"); + advance_clock(745); + omta_is("transform", { tx: 65 }, RunningOn.Compositor, + "kf_cascade1 at 9.2505s"); + done_div(); + + // Test cascading of the @keyframes rules themselves. + new_div("animation: kf_cascade2 linear 10s"); + await waitForPaintsFlushed(); + omta_is("opacity", 1, RunningOn.MainThread, + "last @keyframes rule with transform should win"); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "last @keyframes rule with transform should win"); + done_div(); +}); + +/* + * css3-animations: 3.1. Timing functions for keyframes + * http://dev.w3.org/csswg/css3-animations/#timing-functions-for-keyframes- + */ + +addAsyncAnimTest(async function() { + new_div("animation: kf_tf1 ease-in 10s alternate infinite"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, + "keyframe timing functions test at 0s (test needed for flush)"); + advance_clock(1000); + omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.4) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 1s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.8) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 2s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.2) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 3s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.6) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 4s"); + advance_clock(1000); + omta_is("transform", { tx: 160 }, RunningOn.Compositor, + "keyframe timing functions test at 5s"); + advance_clock(1010); // avoid floating-point error + omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.4) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 6s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.8) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 7s"); + advance_clock(990); + omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.2) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 8s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.6) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 9s"); + advance_clock(1000); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, + "keyframe timing functions test at 10s"); + advance_clock(20000); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, + "keyframe timing functions test at 30s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.6) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 31s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.2) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 32s"); + advance_clock(990); // avoid floating-point error + omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.8) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 33s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.4) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 34s"); + advance_clock(1010); + omta_is("transform", { tx: 160 }, RunningOn.Compositor, + "keyframe timing functions test at 35s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.6) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 36s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.2) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 37s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.8) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 38s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.4) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 39s"); + advance_clock(1000); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, + "keyframe timing functions test at 40s"); + done_div(); + + // spot-check the same thing without alternate + new_div("animation: kf_tf1 ease-in 10s infinite"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, + "keyframe timing functions test at 0s (test needed for flush)"); + advance_clock(11000); + omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.4) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 11s"); + advance_clock(3000); + omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.6) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 14s"); + advance_clock(2010); // avoid floating-point error + omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.4) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 16s"); + advance_clock(1990); + omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.2) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 18s"); + done_div(); +}); + +/* + * css3-animations: 3.2. The 'animation-name' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-name-property- + */ + +// animation-name is reasonably well-tested up in the tests for Section +// 2, particularly the tests that "Test that animations continue running +// when the animation name list is changed." + +// Test that 'animation-name: none' stops the animation, and setting +// it again starts a new one. + +addAsyncAnimTest(async function() { + new_div("animation: anim2 ease-in-out 10s"); + await waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "after setting animation-name to anim2"); + advance_clock(1000); + omta_is_approx("opacity", gTF.ease_in_out(0.1), 0.01, RunningOn.Compositor, + "before changing animation-name to none"); + gDiv.style.animationName = "none"; + await waitForPaintsFlushed(); + omta_is("opacity", 1, RunningOn.MainThread, + "after changing animation-name to none"); + advance_clock(1000); + omta_is("opacity", 1, RunningOn.MainThread, + "after changing animation-name to none plus 1s"); + gDiv.style.animationName = "anim2"; + await waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "after changing animation-name to anim2"); + advance_clock(1000); + omta_is_approx("opacity", gTF.ease_in_out(0.1), 0.01, RunningOn.Compositor, + "at 1s in animation when animation-name no longer none again"); + gDiv.style.animationName = "none"; + await waitForPaintsFlushed(); + omta_is("opacity", 1, RunningOn.MainThread, + "after changing animation-name to none"); + advance_clock(1000); + omta_is("opacity", 1, RunningOn.MainThread, + "after changing animation-name to none plus 1s"); + done_div(); +}); + +/* + * css3-animations: 3.3. The 'animation-duration' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-duration-property- + */ + +// FIXME: test animation-duration of 0 (quite a bit, including interaction +// with fill-mode, count, and reversing), once I know what the right +// behavior is. + +/* + * css3-animations: 3.4. The 'animation-timing-function' Property + * http://dev.w3.org/csswg/css3-animations/#animation-timing-function_tag + */ + +// tested in tests for section 3.1 + +/* + * css3-animations: 3.5. The 'animation-iteration-count' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-iteration-count-property- + */ +addAsyncAnimTest(async function() { + new_div("animation: anim2 ease-in 10s 0.3 forwards"); + await waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "animation-iteration-count test 1 at 0s"); + advance_clock(2000); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-iteration-count test 1 at 2s"); + advance_clock(900); + omta_is_approx("opacity", gTF.ease_in(0.29), 0.01, RunningOn.Compositor, + "animation-iteration-count test 1 at 2.9s"); + advance_clock(100); + // Animation has reached the end so allow it to be cleared from the compositor + await waitForPaints(); + // For transform animations we can tell whether a transform on the compositor + // thread was set by animation or not since there is a special flag for it. + // + // For opacity animations, however, there is no such flag so we'll get an + // "OMTA" opacity even when it wasn't set by animation. When we pause an + // opacity animation we don't worry about where it is reported to be running + // (main thread or compositor) so long as the result is correct, hence we + // check for "either" below. + omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Either, + "animation-iteration-count test 1 at 3s"); + advance_clock(100); + omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Either, + "animation-iteration-count test 1 at 3.1s"); + advance_clock(5000); + omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Either, + "animation-iteration-count test 1 at 8.1s"); + done_div(); + + // The corresponding test in test_animations.html runs three animations in + // parallel but since we only have two properties that are OMTA-enabled at + // this time and no additive animation we split this test into two parts. + new_div("animation: anim2 ease-in 10s 0.3, " + + "anim4 ease-out 20s 1.2 alternate forwards"); + await waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "animation-iteration-count test 2 at 0s"); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "animation-iteration-count test 3 at 0s"); + advance_clock(2000); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-iteration-count test 2 at 2s"); + omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.1) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 3 at 2s"); + advance_clock(900); + omta_is_approx("opacity", gTF.ease_in(0.29), 0.01, RunningOn.Compositor, + "animation-iteration-count test 2 at 2.9s"); + advance_clock(200); + await waitForPaints(); + omta_is("opacity", 1, RunningOn.Either, + "animation-iteration-count test 2 at 3.1s"); + advance_clock(2000); + omta_is("opacity", 1, RunningOn.Either, + "animation-iteration-count test 2 at 5.1s"); + advance_clock(14700); + omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.99) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 3 at 19.8s"); + advance_clock(200); + omta_is("transform", { ty: 100 }, RunningOn.Compositor, + "animation-iteration-count test 3 at 20s"); + advance_clock(200); + omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.99) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 3 at 20.2s"); + advance_clock(3600); + omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.81) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 3 at 23.8s"); + advance_clock(200); + omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.8) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 3 at 24s"); + advance_clock(200); + await waitForPaints(); + omta_is("opacity", 1, RunningOn.Either, + "animation-iteration-count test 2 at 25s"); + omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.8) }, 0.01, + RunningOn.MainThread, + "animation-iteration-count test 3 at 25s"); + done_div(); + + new_div("animation: anim4 ease-in-out 5s 1.6 forwards"); + await waitForPaintsFlushed(); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "animation-iteration-count test 4 at 0s"); + advance_clock(2000); + omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.4) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 4 at 2s"); + advance_clock(2900); + omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.98) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 4 at 4.9s"); + advance_clock(200); + omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.02) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 4 at 5.1s"); + advance_clock(2800); + omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.58) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 4 at 7.9s"); + advance_clock(100); + omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.6) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 4 at 8s"); + advance_clock(100); + await waitForPaints(); + omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.6) }, 0.01, + RunningOn.Either, + "animation-iteration-count test 4 at 8.1s"); + advance_clock(16100); + omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.6) }, 0.01, + RunningOn.Either, + "animation-iteration-count test 4 at 25s"); + done_div(); +}); + +/* + * css3-animations: 3.6. The 'animation-direction' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-direction-property- + */ + +// Tested in tests for sections 3.1 and 3.5. + +addAsyncAnimTest(async function() { + new_div("animation: anim2 ease-in 10s infinite"); + gDiv.style.animationDirection = "normal"; + await waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "animation-direction test 1 (normal) at 0s"); + gDiv.style.animationDirection = "reverse"; + await waitForPaintsFlushed(); + omta_is("opacity", 1, RunningOn.Compositor, + "animation-direction test 1 (reverse) at 0s"); + gDiv.style.animationDirection = "alternate"; + await waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "animation-direction test 1 (alternate) at 0s"); + gDiv.style.animationDirection = "alternate-reverse"; + await waitForPaintsFlushed(); + omta_is("opacity", 1, RunningOn.Compositor, + "animation-direction test 1 (alternate-reverse) at 0s"); + advance_clock(2000); + gDiv.style.animationDirection = "normal"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (normal) at 2s"); + gDiv.style.animationDirection = "reverse"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (reverse) at 2s"); + gDiv.style.animationDirection = "alternate"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate) at 2s"); + gDiv.style.animationDirection = "alternate-reverse"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate-reverse) at 2s"); + advance_clock(5000); + gDiv.style.animationDirection = "normal"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.7), 0.01, RunningOn.Compositor, + "animation-direction test 1 (normal) at 7s"); + gDiv.style.animationDirection = "reverse"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Compositor, + "animation-direction test 1 (reverse) at 7s"); + gDiv.style.animationDirection = "alternate"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.7), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate) at 7s"); + gDiv.style.animationDirection = "alternate-reverse"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate-reverse) at 7s"); + advance_clock(5000); + gDiv.style.animationDirection = "normal"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (normal) at 12s"); + gDiv.style.animationDirection = "reverse"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (reverse) at 12s"); + gDiv.style.animationDirection = "alternate"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate) at 12s"); + gDiv.style.animationDirection = "alternate-reverse"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate-reverse) at 12s"); + advance_clock(10000); + gDiv.style.animationDirection = "normal"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (normal) at 22s"); + gDiv.style.animationDirection = "reverse"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (reverse) at 22s"); + gDiv.style.animationDirection = "alternate"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate) at 22s"); + gDiv.style.animationDirection = "alternate-reverse"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate-reverse) at 22s"); + advance_clock(30000); + gDiv.style.animationDirection = "normal"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (normal) at 52s"); + gDiv.style.animationDirection = "reverse"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (reverse) at 52s"); + gDiv.style.animationDirection = "alternate"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate) at 52s"); + gDiv.style.animationDirection = "alternate-reverse"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate-reverse) at 52s"); + done_div(); +}); + +/* + * css3-animations: 3.7. The 'animation-play-state' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-play-state-property- + */ + +addAsyncAnimTest(async function() { + // simple test with just one animation + new_div(""); + gDiv.style.animationTimingFunction = "ease"; + gDiv.style.animationName = "anim1"; + gDiv.style.animationDuration = "1s"; + gDiv.style.animationDirection = "alternate"; + gDiv.style.animationIterationCount = "2"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "animation-play-state test 1, at 0s"); + advance_clock(250); + omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 1 at 250ms"); + gDiv.style.animationPlayState = "paused"; + await waitForPaintsFlushed(); + omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 1 at 250ms"); + advance_clock(250); + omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 1 still at 500ms"); + gDiv.style.animationPlayState = "running"; + await waitForPaintsFlushed(); + omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 1 still at 500ms"); + advance_clock(500); + omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 1 at 1000ms"); + advance_clock(250); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "animation-play-state test 1 at 1250ms"); + advance_clock(250); + omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 1 at 1500ms"); + gDiv.style.animationPlayState = "paused"; + await waitForPaintsFlushed(); + omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 1 at 1500ms"); + advance_clock(2000); + omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 1 at 3500ms"); + advance_clock(500); + omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 1 at 4000ms"); + gDiv.style.animationPlayState = ""; + await waitForPaintsFlushed(); + omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 1 at 4000ms"); + advance_clock(500); + omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 1 at 4500ms"); + advance_clock(250); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.MainThread, + "animation-play-state test 1, at 4750ms"); + advance_clock(250); + omta_is("transform", { tx: 0 }, RunningOn.MainThread, + "animation-play-state test 1, at 5000ms"); + done_div(); + + // The corresponding test in test_animations.html tests various cases of + // pausing individual animations in a list of three different animations + // but since there are only two OMTA properties we can animate + // independently this test is substantially simpler. + new_div(""); + gDiv.style.animationTimingFunction = "ease-out, ease-in"; + gDiv.style.animationName = "anim2, anim4"; + gDiv.style.animationDuration = "1s, 2s"; + gDiv.style.animationDirection = "alternate, normal"; + gDiv.style.animationIterationCount = "4, 2"; + await waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "animation-play-state test 2, at 0s"); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "animation-play-state test 3, at 0s"); + advance_clock(250); + gDiv.style.animationPlayState = "paused, running"; // pause 1 + await waitForPaintsFlushed(); + // As noted with the tests for animation-iteration-count, for opacity + // animations we don't strictly check the finished animation is being animated + // on the main thread, but simply that it is producing the correct result. + omta_is_approx("opacity", gTF.ease_out(0.25), 0.01, RunningOn.MainThread, + "animation-play-state test 2 at 250ms"); // paused + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.125) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 3 at 250ms"); + advance_clock(250); + omta_is_approx("opacity", gTF.ease_out(0.25), 0.01, RunningOn.MainThread, + "animation-play-state test 2 at 500ms"); // paused + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.25) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 3 at 500ms"); + advance_clock(250); + gDiv.style.animationPlayState = "running, paused"; // unpause 1, pause 2 + await waitForPaintsFlushed(); + advance_clock(250); + omta_is_approx("opacity", gTF.ease_out(0.5), 0.01, RunningOn.Compositor, + "animation-play-state test 2 at 1000ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 3 at 1000ms"); // paused + gDiv.style.animationPlayState = "paused"; // pause all + await waitForPaintsFlushed(); + advance_clock(3000); + omta_is_approx("opacity", gTF.ease_out(0.5), 0.01, RunningOn.MainThread, + "animation-play-state test 2 at 4000ms"); // paused + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 3 at 4000ms"); // paused + gDiv.style.animationPlayState = "running, paused"; // pause 2 + await waitForPaintsFlushed(); + advance_clock(850); + omta_is_approx("opacity", gTF.ease_out(0.65), 0.01, RunningOn.Compositor, + "animation-play-state test 2 at 4850ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 3 at 4850ms"); + advance_clock(300); + omta_is_approx("opacity", gTF.ease_out(0.35), 0.01, RunningOn.Compositor, + "animation-play-state test 2 at 5150ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 3 at 5150ms"); + advance_clock(2300); + omta_is_approx("opacity", gTF.ease_out(0.05), 0.01, RunningOn.Compositor, + "animation-play-state test 2 at 7450ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 3 at 7450ms"); + advance_clock(100); + // test 2 has finished so wait for it to be removed from the + // compositor (otherwise it will fill forwards) + await waitForPaints(); + omta_is("opacity", 1, RunningOn.Either, + "animation-play-state test 2 at 7550ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 3 at 7550ms"); + gDiv.style.animationPlayState = "running"; // unpause 2 + await waitForPaintsFlushed(); + advance_clock(1000); + omta_is("opacity", 1, RunningOn.Either, + "animation-play-state test 2 at 7550ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.875) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 3 at 7550ms"); + advance_clock(500); + omta_is("opacity", 1, RunningOn.Either, + "animation-play-state test 2 at 8050ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.125) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 3 at 8050ms"); + advance_clock(1000); + omta_is("opacity", 1, RunningOn.Either, + "animation-play-state test 2 at 9050ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.625) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 3 at 9050ms"); + advance_clock(500); + omta_is("opacity", 1, RunningOn.Either, + "animation-play-state test 2 at 9550ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.875) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 3 at 9550ms"); + advance_clock(500); + await waitForPaints(); + omta_is("opacity", 1, RunningOn.Either, + "animation-play-state test 2 at 10050ms"); + omta_is("transform", { ty: 0 }, RunningOn.MainThread, + "animation-play-state test 3 at 10050ms"); + done_div(); +}); + +/* + * css3-animations: 3.8. The 'animation-delay' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-delay-property- + */ + +addAsyncAnimTest(async function() { + // test positive delay + new_div("animation: anim2 1s 0.5s ease-out"); + await waitForPaintsFlushed(); + // NOTE: getOMTAStyle() can't detect the animation is running on the + // compositor or not during the delay phase, since no opacity style is + // applied during the delay phase. + omta_is("opacity", 1, RunningOn.Either, "positive delay test at 0ms"); + advance_clock(400); + omta_is("opacity", 1, RunningOn.Either, "positive delay test at 400ms"); + advance_clock(100); + await waitForPaints(); + omta_is("opacity", 0, RunningOn.Compositor, "positive delay test at 500ms"); + advance_clock(100); + omta_is_approx("opacity", gTF.ease_out(0.1), 0.01, RunningOn.Compositor, + "positive delay test at 500ms"); + done_div(); + + // test dynamic changes to delay (i.e., that we preserve the start time + // that's before the delay) + new_div("animation: anim2 1s 0.5s ease-out both"); + await waitForPaintsFlushed(); + // NOTE: As noted above, getOMTAStyle() can't detect the animation is running + // on the compositor during the delay phase. + omta_is("opacity", 0, RunningOn.Either, "dynamic delay delay test at 0ms"); + advance_clock(400); + omta_is("opacity", 0, RunningOn.Either, + "dynamic delay delay test at 400ms (1)"); + gDiv.style.animationDelay = "0.2s"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_out(0.2), 0.01, RunningOn.Compositor, + "dynamic delay delay test at 400ms (2)"); + gDiv.style.animationDelay = "0.6s"; + await waitForPaintsFlushed(); + advance_clock(200); + omta_is("opacity", 0, RunningOn.Either, "dynamic delay delay test at 600ms"); + advance_clock(200); + await waitForPaints(); + omta_is_approx("opacity", gTF.ease_out(0.2), 0.01, RunningOn.Compositor, + "dynamic delay delay test at 800ms"); + advance_clock(1000); + await waitForPaints(); + omta_is("opacity", 1, RunningOn.Either, + "dynamic delay delay test at 1800ms (1)"); + gDiv.style.animationDelay = "1.5s"; + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_out(0.3), 0.01, RunningOn.Compositor, + "dynamic delay delay test at 1800ms (2)"); + gDiv.style.animationDelay = "2s"; + await waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Either, + "dynamic delay delay test at 1800ms (3)"); + done_div(); + + // test delay and play-state interaction + new_div("animation: anim2 1s 0.5s ease-out"); + await waitForPaintsFlushed(); + // NOTE: As noted above, getOMTAStyle() can't detect the animation is running + // on the compositor during the delay phase. + omta_is("opacity", 1, RunningOn.Either, + "delay and play-state delay test at 0ms"); + advance_clock(400); + omta_is("opacity", 1, RunningOn.Either, + "delay and play-state delay test at 400ms"); + gDiv.style.animationPlayState = "paused"; + await waitForPaintsFlushed(); + advance_clock(100); + omta_is("opacity", 1, RunningOn.MainThread, // paused + "delay and play-state delay test at 500ms"); + advance_clock(500); + omta_is("opacity", 1, RunningOn.MainThread, // paused + "delay and play-state delay test at 1000ms"); + gDiv.style.animationPlayState = "running"; + await waitForPaintsFlushed(); + advance_clock(100); + await waitForPaints(); + omta_is("opacity", 0, RunningOn.Compositor, + "delay and play-state delay test at 1100ms"); + advance_clock(100); + omta_is_approx("opacity", gTF.ease_out(0.1), 0.01, RunningOn.Compositor, + "delay and play-state delay test at 1200ms"); + gDiv.style.animationPlayState = "paused"; + await waitForPaintsFlushed(); + advance_clock(100); + omta_is_approx("opacity", gTF.ease_out(0.1), 0.01, RunningOn.Either, + "delay and play-state delay test at 1300ms"); + done_div(); + + // test negative delay and implicit starting values + new_div("transform: translate(1000px)"); + await waitForPaintsFlushed(); + advance_clock(300); + gDiv.style.transform = "translate(100px)"; + gDiv.style.animation = "kf1 1s -0.1s ease-in"; + await waitForPaintsFlushed(); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_in(0.2) }, + 0.01, RunningOn.Compositor, + "delay and implicit starting values test"); + done_div(); + + // test large negative delay that causes the animation to start + // in the fourth iteration + new_div("animation: anim2 1s -3.6s ease-in 5 alternate forwards"); + listen(); + await waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.4), 0.01, RunningOn.Compositor, + "large negative delay test at 0ms"); + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'anim2', elapsedTime: 3.6, + pseudoElement: "" }], + "right after start in large negative delay test"); + advance_clock(380); + omta_is_approx("opacity", gTF.ease_in(0.02), 0.01, RunningOn.Compositor, + "large negative delay test at 380ms"); + check_events([]); + advance_clock(20); + omta_is("opacity", 0, RunningOn.Compositor, + "large negative delay test at 400ms"); + check_events([{ type: 'animationiteration', target: gDiv, + animationName: 'anim2', elapsedTime: 4.0, + pseudoElement: "" }], + "right after start in large negative delay test"); + advance_clock(800); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "large negative delay test at 1200ms"); + check_events([]); + advance_clock(200); + omta_is("opacity", 1, RunningOn.Either, + "large negative delay test at 1400ms"); + check_events([{ type: 'animationend', target: gDiv, + animationName: 'anim2', elapsedTime: 5.0, + pseudoElement: "" }], + "right after start in large negative delay test"); + done_div(); +}); + +/* + * css3-animations: 3.9. The 'animation-fill-mode' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-fill-mode-property- + */ + +// animation-fill-mode is tested in the tests for section (2). + +/* + * css3-animations: 3.10. The 'animation' Shorthand Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-shorthand-property- + */ + +/** + * Basic tests of animations on pseudo-elements + */ +addAsyncAnimTest(async function() { + new_div(""); + listen(); + gDiv.id = "withbefore"; + await waitForPaintsFlushed(); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + ":before test at 0ms", "::before"); + advance_clock(400); + omta_is("transform", { ty: 40 }, RunningOn.Compositor, + ":before test at 400ms", "::before"); + advance_clock(800); + omta_is("transform", { ty: 80 }, RunningOn.Compositor, + ":before test at 1200ms", "::before"); + omta_is("transform", { ty: 0 }, RunningOn.MainThread, + ":before animation should not affect element"); + advance_clock(800); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + ":before test at 2000ms", "::before"); + advance_clock(300); + omta_is("transform", { ty: 30 }, RunningOn.Compositor, + ":before test at 2300ms", "::before"); + advance_clock(700); + check_events([ { type: "animationstart", animationName: "anim4", + elapsedTime: 0, pseudoElement: "::before" }, + { type: "animationiteration", animationName: "anim4", + elapsedTime: 1, pseudoElement: "::before" }, + { type: "animationiteration", animationName: "anim4", + elapsedTime: 2, pseudoElement: "::before" }, + { type: "animationend", animationName: "anim4", + elapsedTime: 3, pseudoElement: "::before" }]); + done_div(); + + new_div(""); + listen(); + gDiv.id = "withafter"; + await waitForPaintsFlushed(); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + ":after test at 0ms", "::after"); + advance_clock(400); + omta_is("transform", { ty: 40 }, RunningOn.Compositor, + ":after test at 400ms", "::after"); + advance_clock(800); + omta_is("transform", { ty: 80 }, RunningOn.Compositor, + ":after test at 1200ms", "::after"); + omta_is("transform", { ty: 0 }, RunningOn.MainThread, + ":before animation should not affect element"); + advance_clock(800); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + ":after test at 2000ms", "::after"); + advance_clock(300); + omta_is("transform", { ty: 30 }, RunningOn.Compositor, + ":after test at 2300ms", "::after"); + advance_clock(700); + check_events([ { type: "animationstart", animationName: "anim4", + elapsedTime: 0, pseudoElement: "::after" }, + { type: "animationiteration", animationName: "anim4", + elapsedTime: 1, pseudoElement: "::after" }, + { type: "animationiteration", animationName: "anim4", + elapsedTime: 2, pseudoElement: "::after" }, + { type: "animationend", animationName: "anim4", + elapsedTime: 3, pseudoElement: "::after" }]); + done_div(); +}); + +/** + * Test handling of properties that are present in only some of the + * keyframes. + */ +addAsyncAnimTest(async function() { + new_div("animation: multiprop 1s ease-in-out alternate infinite"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 10 }, RunningOn.Compositor, + "multiprop transform at 0ms"); + omta_is("opacity", 0.3, RunningOn.Compositor, "multiprop opacity at 0ms"); + advance_clock(100); + omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.2) }, 0.01, + RunningOn.Compositor, "multiprop transform at 100ms"); + omta_is_approx("opacity", 0.3 + 0.2 * gTF.ease(0.4), 0.01, + RunningOn.Compositor, "multiprop opacity at 100ms"); + advance_clock(200); + omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.6) }, 0.01, + RunningOn.Compositor, "multiprop transform at 300ms"); + omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.1), 0.01, + RunningOn.Compositor, "multiprop opacity at 300ms"); + advance_clock(300); + omta_is_approx("transform", { tx: 40 + 40 * gTF.ease_in_out(0.4) }, 0.01, + RunningOn.Compositor, "multiprop transform at 600ms"); + omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.7), 0.01, + RunningOn.Compositor, "multiprop opacity at 600ms"); + advance_clock(200); + omta_is_approx("transform", { tx: 80 - 80 * gTF.ease_in(0.2) }, 0.01, + RunningOn.Compositor, "multiprop transform at 800ms"); + omta_is_approx("opacity", 0.6 + 0.4 * gTF.ease_in(0.2), 0.01, + RunningOn.Compositor, "multiprop opacity at 800ms"); + advance_clock(400); + omta_is_approx("transform", { tx: 80 - 80 * gTF.ease_in(0.2) }, 0.01, + RunningOn.Compositor, "multiprop transform at 1200ms"); + omta_is_approx("opacity", 0.6 + 0.4 * gTF.ease_in(0.2), 0.01, + RunningOn.Compositor, "multiprop opacity at 1200ms"); + advance_clock(200); + omta_is_approx("transform", { tx: 40 + 40 * gTF.ease_in_out(0.4) }, 0.01, + RunningOn.Compositor, "multiprop transform at 1400ms"); + omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.7), 0.01, + RunningOn.Compositor, "multiprop opacity at 1400ms"); + advance_clock(300); + omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.6) }, 0.01, + RunningOn.Compositor, "multiprop transform at 1700ms"); + omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.1), 0.01, + RunningOn.Compositor, "multiprop opacity at 1700ms"); + advance_clock(200); + omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.2) }, 0.01, + RunningOn.Compositor, "multiprop transform at 1900ms"); + omta_is_approx("opacity", 0.3 + 0.2 * gTF.ease(0.4), 0.01, + RunningOn.Compositor, "multiprop opacity at 1900ms"); + done_div(); +}); + +// Test for https://bugzilla.mozilla.org/show_bug.cgi?id=651456 -- make +// sure that refreshing of animations doesn't break when we get two +// refreshes with the same timestamp. +addAsyncAnimTest(async function() { + new_div("animation: anim2 1s linear"); + await waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, "bug 651456 at 0ms"); + advance_clock(100); + omta_is("opacity", 0.1, RunningOn.Compositor, "bug 651456 at 100ms (1)"); + advance_clock(0); // still forces a refresh + omta_is("opacity", 0.1, RunningOn.Compositor, "bug 651456 at 100ms (2)"); + advance_clock(100); + omta_is("opacity", 0.2, RunningOn.Compositor, "bug 651456 at 200ms"); + done_div(); +}); + +// test_animations.html includes a test that UA !important rules override +// animations. Unfortunately, there do not appear to be any UA !important rules +// for opacity or transform except for one targetting a pseudo-element and +// pseudo elements are not animated on the compositor. As a result we cannot +// currently test this behavior. + +// Test that author !important rules override animations, but +// that animations override regular author rules. +addAsyncAnimTest(async function() { + new_div("animation: always_fifty 1s linear infinite; " + + "transform: translate(200px)"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "animations override regular author rules"); + done_div(); + new_div("animation: always_fifty 1s linear infinite; " + + "transform: translate(200px) ! important;"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 200 }, RunningOn.MainThread, + "important author rules override animations"); + done_div(); +}); + +// Test interaction of animations and restyling (Bug 686656). +// This test depends on kf3 getting its 0% and 100% values from the +// rules below it in the cascade; we're checking that the animation +// isn't rebuilt when the restyles happen. +addAsyncAnimTest(async function() { + new_div("animation: kf3 1s linear forwards"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "bug 686656 test 1 at 0ms"); + advance_clock(250); + gDisplay.style.color = "blue"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "bug 686656 test 1 at 250ms"); + advance_clock(375); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "bug 686656 test 1 at 625ms"); + advance_clock(375); + await waitForPaints(); + omta_is("transform", { tx: 0 }, RunningOn.MainThread, + "bug 686656 test 1 at 1000ms"); + done_div(); + gDisplay.style.color = ""; +}); + +// Test interaction of animations and restyling (Bug 686656), +// with reframing. +// This test depends on kf3 getting its 0% and 100% values from the +// rules below it in the cascade; we're checking that the animation +// isn't rebuilt when the restyles happen. +addAsyncAnimTest(async function() { + new_div("animation: kf3 1s linear forwards"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "bug 686656 test 2 at 0ms"); + advance_clock(250); + gDisplay.style.overflow = "scroll"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "bug 686656 test 2 at 250ms"); + advance_clock(375); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "bug 686656 test 2 at 625ms"); + advance_clock(375); + await waitForPaints(); + omta_is("transform", { tx: 0 }, RunningOn.MainThread, + "bug 686656 test 2 at 1000ms"); + done_div(); + gDisplay.style.overflow = ""; +}); + +// Test that cascading between keyframes rules is per-property rather +// than per-rule (bug ), and that the timing function isn't taken from a +// rule that's skipped. (Bug 738003) +addAsyncAnimTest(async function() { + new_div("animation: cascade 1s linear forwards; position: relative"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "cascade test (transform) at 0ms"); + omta_is("opacity", 0, RunningOn.Compositor, + "cascade test (opacity) at 0ms"); + advance_clock(125); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "cascade test (transform) at 125ms"); + omta_is("opacity", 0.5, RunningOn.Compositor, + "cascade test (opacity) at 125ms"); + advance_clock(125); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "cascade test (transform) at 250ms"); + omta_is("opacity", 1, RunningOn.Compositor, + "cascade test (opacity) at 250ms"); + advance_clock(125); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "cascade test (transform) at 375ms"); + omta_is("opacity", 1, RunningOn.Compositor, + "cascade test (opacity) at 375ms"); + advance_clock(125); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "cascade test (transform) at 500ms"); + omta_is("opacity", 1, RunningOn.Compositor, + "cascade test (opacity) at 500ms"); + advance_clock(125); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "cascade test (transform) at 625ms"); + omta_is("opacity", 0.5, RunningOn.Compositor, + "cascade test (opacity) at 625ms"); + advance_clock(125); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "cascade test (transform) at 750ms"); + omta_is("opacity", 0, RunningOn.Compositor, + "cascade test (opacity) at 750ms"); + advance_clock(125); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "cascade test (transform) at 875ms"); + omta_is("opacity", 0, RunningOn.Compositor, + "cascade test (opacity) at 875ms"); + advance_clock(125); + await waitForPaints(); + omta_is("transform", { tx: 0 }, RunningOn.MainThread, + "cascade test (transform) at 1000ms"); + omta_is("opacity", 0, RunningOn.Either, + "cascade test (opacity) at 1000ms"); + done_div(); +}); + +addAsyncAnimTest(async function() { + new_div("animation: cascade2 8s linear forwards"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, "cascade2 test at 0s"); + advance_clock(1000); + omta_is("transform", { tx: 25 }, RunningOn.Compositor, "cascade2 test at 1s"); + advance_clock(1000); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, "cascade2 test at 2s"); + advance_clock(1000); + omta_is("transform", { tx: 25 }, RunningOn.Compositor, "cascade2 test at 3s"); + advance_clock(1000); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, "cascade2 test at 4s"); + advance_clock(3000); + omta_is("transform", { tx: 75 }, RunningOn.Compositor, "cascade2 test at 7s"); + advance_clock(1000); + await waitForPaints(); + omta_is("transform", { tx: 100 }, RunningOn.MainThread, + "cascade2 test at 8s"); + done_div(); +}); + +addAsyncAnimTest(async function() { + new_div("animation: primitives1 2s linear forwards"); + await waitForPaintsFlushed(); + omta_is("transform", { }, RunningOn.Compositor, "primitives1 at 0s"); + advance_clock(1000); + omta_is("transform", [ -0.707107, 0.707107, -0.707107, -0.707107, 0, 0 ], + RunningOn.Compositor, "primitives1 at 1s"); + advance_clock(1000); + await waitForPaints(); + omta_is("transform", [ 0, -1, 1, 0, 0, 0 ], RunningOn.MainThread, + "primitives1 at 0s"); + done_div(); +}); + +addAsyncAnimTest(async function() { + new_div("animation: important1 1s linear forwards"); + await waitForPaintsFlushed(); + omta_is("opacity", 0.5, RunningOn.Compositor, "important1 test at 0s"); + advance_clock(500); + omta_is("opacity", 0.65, RunningOn.Compositor, "important1 test at 0.5s"); + advance_clock(500); + await waitForPaints(); + omta_is("opacity", 0.8, RunningOn.Either, "important1 test at 1s"); + done_div(); +}); + +addAsyncAnimTest(async function() { + new_div("animation: important2 1s linear forwards"); + await waitForPaintsFlushed(); + omta_is("opacity", 0.5, RunningOn.Compositor, + "important2 (opacity) test at 0s"); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "important2 (transform) test at 0s"); + advance_clock(1000); + await waitForPaints(); + omta_is("opacity", 1, RunningOn.Either, + "important2 (opacity) test at 1s"); + omta_is("transform", { tx: 50 }, RunningOn.MainThread, + "important2 (transform) test at 1s"); + done_div(); +}); + +addAsyncAnimTest(async function() { + // Test that it's the length of the 'animation-name' list that's used to + // start animations. + // note: anim2 animates opacity from 0 to 1 + // note: anim4 animates transform's y translation component from 0 to 100px + new_div("animation-name: anim2, anim4; " + + "animation-duration: 1s; " + + "animation-timing-function: linear; " + + "animation-delay: -250ms, -250ms, -750ms, -500ms;"); + await waitForPaintsFlushed(); + omta_is("opacity", 0.25, RunningOn.Compositor, + "animation-name list length is the length that matters"); + omta_is("transform", { ty: 25 }, RunningOn.Compositor, + "animation-name list length is the length that matters"); + done_div(); + new_div("animation-name: anim2, anim4, anim2; " + + "animation-duration: 1s; " + + "animation-timing-function: linear; " + + "animation-delay: -250ms, -250ms, -750ms, -500ms;"); + await waitForPaintsFlushed(); + omta_is("opacity", 0.75, RunningOn.Compositor, + "animation-name list length is the length that matters, " + + "and the last occurrence of a name wins"); + omta_is("transform", { ty: 25 }, RunningOn.Compositor, + "animation-name list length is the length that matters"); + done_div(); +}); + +addAsyncAnimTest(async function() { + var dyn_sheet_elt = document.createElement("style"); + document.head.appendChild(dyn_sheet_elt); + var dyn_sheet = dyn_sheet_elt.sheet; + dyn_sheet.insertRule( + "@keyframes dyn1 { from { transform: translate(0px) } " + + "50% { transform: translate(50px) } " + + "to { transform: translate(100px) } }", 0); + dyn_sheet.insertRule( + "@keyframes dyn2 { from { transform: translate(100px) } " + + "to { transform: translate(200px) } }", 1); + var dyn1 = dyn_sheet.cssRules[0]; + var dyn2 = dyn_sheet.cssRules[1]; + new_div("animation: dyn1 1s linear"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "dynamic rule change test, initial state"); + advance_clock(250); + omta_is("transform", { tx: 25 }, RunningOn.Compositor, + "dynamic rule change test, 250ms"); + dyn2.name = "dyn1"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 125 }, RunningOn.Compositor, + "dynamic rule change test, change in @keyframes name applies"); + dyn2.appendRule("50% { transform: translate(0px) }"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "dynamic rule change test, @keyframes appendRule"); + // currently 0% { transform: translate(100px) } + var dyn2_kf1 = dyn2.cssRules[0]; + dyn2_kf1.style.transform = "translate(-100px)"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: -50 }, RunningOn.Compositor, + "dynamic rule change test, keyframe style set"); + dyn2.name = "dyn2"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 25 }, RunningOn.Compositor, + "dynamic rule change test, " + + "change in @keyframes name applies (second time)"); + // currently 50% { transform: translate(50px) } + var dyn1_kf2 = dyn1.cssRules[1]; + dyn1_kf2.keyText = "25%"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "dynamic rule change test, change in keyframe keyText"); + dyn1.deleteRule("25%"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 25 }, RunningOn.Compositor, + "dynamic rule change test, @keyframes deleteRule"); + done_div(); + dyn_sheet_elt.remove(); + dyn_sheet_elt = null; + dyn_sheet = null; +}); + +/* + * Bug 1004361 - CSS animations with short duration sometimes don't dispatch + * a start event + */ +addAsyncAnimTest(async function() { + new_div("animation: anim2 1s 0.1s"); + listen(); + await waitForPaintsFlushed(); + advance_clock(1200); // Skip past end of animation's entire active duration + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: gDiv, + animationName: 'anim2', elapsedTime: 1, + pseudoElement: "" }], + "events after skipping over animation interval"); + done_div(); +}); + +/* + * Bug 1007513 - AnimationEvent.elapsedTime should be animation time + * + * There is no OMTA-version of this test since it is specific to the + * contents of animation events which are dispatched on the main thread. + * + * We *do* provide an OMTA-version of some tests regarding the *dispatch* of + * events to catch possible regressions if in future event dispatch is tied + * to animation throttling. + */ + +/* + * Bug 1004365 - zero-duration animations + */ + +addAsyncAnimTest(async function() { + new_div("transform: translate(0, 200px); animation: anim4 0s 1s both"); + listen(); + await waitForPaintsFlushed(); + advance_clock(0); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "transform during backwards fill of zero-duration animation"); + advance_clock(2000); // Skip over animation + await waitForPaints(); + omta_is("transform", { ty: 100 }, RunningOn.MainThread, + "transform during backwards fill of zero-duration animation"); + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }], + "events after skipping over zero-duration animation"); + done_div(); +}); + +addAsyncAnimTest(async function() { + new_div("transform: translate(0, 200px); animation: anim4 0s 1s both"); + listen(); + await waitForPaintsFlushed(); + advance_clock(0); + // Seek to exactly the point where the animation starts and stops + advance_clock(1000); + await waitForPaints(); + omta_is("transform", { ty: 100 }, RunningOn.MainThread, + "transform during backwards fill of zero-duration animation"); + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }], + "events after seeking to end of zero-duration animation"); + // Check no further events are dispatched + advance_clock(0); + advance_clock(100); + check_events([]); + done_div(); +}); + +// We don't need to include all the animation-direction related tests +// found in test_animations.html. We have already asserted above that +// these zero-length animations do in fact run on the main thread and +// we have checked that they dispatch events correctly. +// The actual calculation of values on the main thread is covered by +// test_animations.html + +// We do however still want to test with an infinite repeat count and zero +// duration to ensure this does not confuse the screening of OMTA animations. +addAsyncAnimTest(async function() { + new_div("transform: translate(0, 200px); " + + "animation: anim4 0s 1s both infinite"); + listen(); + await waitForPaintsFlushed(); + advance_clock(0); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "transform during backwards fill of infinitely repeating " + + "zero-duration animation"); + advance_clock(2000); + await waitForPaints(); + omta_is("transform", { ty: 100 }, RunningOn.MainThread, + "transform during forwards fill of infinitely repeating " + + "zero-duration animation"); + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }], + "events after seeking to end of infinitely repeating " + + "zero-duration animation"); + done_div(); +}); + +// Test with negative delay +addAsyncAnimTest(async function() { + new_div("transform: translate(0, 200px); " + + "animation: anim4 0s -1s both reverse 12.7 linear"); + listen(); + await waitForPaintsFlushed(); + advance_clock(0); + omta_is("transform", { ty: 30 }, RunningOn.MainThread, + "transform during forwards fill of reversed and repeated " + + "zero-duration animation with negative delay"); + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }], + "events after skipping over zero-duration animation " + + "with negative delay"); + done_div(); +}); + +/* + * Bug 1004377 - Animations with empty keyframes rule + */ + +addAsyncAnimTest(async function() { + new_div("margin-right: 200px; animation: empty 2s 1s both"); + listen(); + advance_clock(0); + await waitForPaintsFlushed(); + check_events([], "events during delay"); + advance_clock(2000); // Skip to middle of animation + gDiv.clientTop; // Trigger events + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'empty', elapsedTime: 0, + pseudoElement: "" }], + "events during middle of animation with empty keyframes rule"); + advance_clock(1000); // Skip to end of animation + gDiv.clientTop; // Trigger events + check_events([{ type: 'animationend', target: gDiv, + animationName: 'empty', elapsedTime: 2, + pseudoElement: "" }], + "events at end of animation with empty keyframes rule"); + done_div(); +}); + +// Test with a zero-duration animation and empty @keyframes rule +addAsyncAnimTest(async function() { + new_div("margin-right: 200px; animation: empty 0s 1s both"); + listen(); + await waitForPaintsFlushed(); + advance_clock(1000); + gDiv.clientTop; // Trigger events + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'empty', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: gDiv, + animationName: 'empty', elapsedTime: 0, + pseudoElement: "" }], + "events at end of zero-duration animation with " + + "empty keyframes rule"); + done_div(); +}); + +// Test with a keyframes rule that becomes empty +addAsyncAnimTest(async function() { + new_div("animation: nearlyempty 1s both linear"); + await waitForPaintsFlushed(); + advance_clock(500); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "Animation is animating on compositor"); + + // Update keyframes rule and check the result gets removed + listen(); + findKeyframesRule("nearlyempty").deleteRule("to"); + await waitForPaintsFlushed(); + omta_is("transform", { }, RunningOn.MainThread, + "Animation with (now) empty keyframes rule is cleared " + + "from compositor"); + + // Check we still dispatch the end event however + advance_clock(500); + gDiv.clientTop; // Trigger events + check_events([{ type: 'animationend', target: gDiv, + animationName: 'nearlyempty', elapsedTime: 1, + pseudoElement: "" }], + "events at end of animation with newly " + + "empty keyframes rule"); + + done_div(); +}); + +// Test when we update to point to an empty animation +addAsyncAnimTest(async function() { + new_div("animation: always_fifty 1s both linear"); + await waitForPaintsFlushed(); + advance_clock(500); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "Animation is animating on compositor"); + + // Update animation name + listen(); + gDiv.style.animationName = "empty"; + await waitForPaintsFlushed(); + omta_is("transform", { }, RunningOn.MainThread, + "Animation updated to use empty keyframes rule is cleared " + + "from compositor"); + + // Check events + advance_clock(500); + gDiv.clientTop; // Trigger events + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'empty', elapsedTime: 0, + pseudoElement: "" }], + "events at start of animation updated to use " + + "empty keyframes rule"); + + done_div(); +}); + +// Bug 996796 patch 12 - test for correct visited styles during +// animation-only style flush. +addAsyncAnimTest(async function() { + if (AppConstants.platform === "android") { + todo(false, "no global history on GeckoView; can't run test"); + return; + } + + var div1 = document.createElement("div"); + div1.classList.add("target"); + div1.style.height = "10px"; + div1.style.animation = "anim2 linear 1s"; + + const topLocation = + await SpecialPowers.spawnChrome([], () => browsingContext.top.currentURI.spec); + + var visitedLink = document.createElement("a"); + visitedLink.setAttribute("href", topLocation); + visitedLink.classList.add("visitedLink"); + visitedLink.classList.add("target"); + visitedLink.style.display = "block"; + visitedLink.style.height = "10px"; + visitedLink.style.animation = "anim2 linear 1s"; + + var refVisitedLink = document.createElement("a"); + refVisitedLink.setAttribute("href", topLocation); + refVisitedLink.classList.add("visitedLink"); + + gDisplay.appendChild(div1); + gDisplay.appendChild(visitedLink); + gDisplay.appendChild(refVisitedLink); + + // Wait for visited link coloring. + await waitForVisitedLinkColoring(refVisitedLink, + "background-color", "rgb(0, 0, 255)"); + + // Wait for animations to start. + await waitForPaintsFlushed(); + + var bgColor = SpecialPowers.DOMWindowUtils + .getVisitedDependentComputedStyle(visitedLink, "", "background-color"); + is(bgColor, "rgb(0, 0, 255)", "initial visited link background color"); + + advance_clock(250); + + // Trigger a style change on div1 that will force us to do a miniflush, + // but which will not trigger a style change on visitedLink. + div1.style.color = "blue"; + advance_clock(250); + + bgColor = SpecialPowers.DOMWindowUtils + .getVisitedDependentComputedStyle(visitedLink, "", "background-color"); + + is(bgColor, "rgb(0, 0, 255)", + "visited link background color after animation-only flush"); + + div1.remove(); + visitedLink.remove(); + refVisitedLink.remove(); +}); + +/* + * Bug 962594 - Turn off CSS animations when the element is display:none, or + * is in a display:none subtree. + */ + +// Check that it works if the animated element itself becomes display:none +addAsyncAnimTest(async function() { + new_div("animation: anim4 linear 10s"); + await waitForPaintsFlushed(); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "transform animation is running on compositor"); + advance_clock(1000); + omta_is("transform", { ty: 10 }, RunningOn.Compositor, + "transform animation is at 1s on compositor"); + gDiv.style.display = "none"; + await waitForPaintsFlushed(); + omta_is("transform", "none", RunningOn.MainThread, + "transform animation stopped on compositor"); + advance_clock(1000); + omta_is("transform", "none", RunningOn.MainThread, + "transform animation 1s after display:none"); + gDiv.style.display = ""; + await waitForPaintsFlushed(); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "transform animation after display:block"); + advance_clock(1000); + omta_is("transform", { ty: 10 }, RunningOn.Compositor, + "transform animation 1s after display:block"); + done_div(); +}); + +// Check that it works if an ancestor of the animated element becomes display:none +addAsyncAnimTest(async function() { + new_div("animation: anim4 linear 10s"); + var ancestor = document.createElement("div"); + gDiv.parentNode.insertBefore(ancestor, gDiv); + ancestor.appendChild(gDiv); + await waitForPaintsFlushed(); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "transform animation is running on compositor"); + advance_clock(1000); + omta_is("transform", { ty: 10 }, RunningOn.Compositor, + "transform animation is at 1s on compositor"); + gDiv.style.display = "none"; + await waitForPaintsFlushed(); + omta_is("transform", "none", RunningOn.MainThread, + "transform animation stopped on compositor"); + advance_clock(1000); + omta_is("transform", "none", RunningOn.MainThread, + "transform animation 1s after display:none"); + gDiv.style.display = ""; + await waitForPaintsFlushed(); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "transform animation after display:block"); + advance_clock(1000); + omta_is("transform", { ty: 10 }, RunningOn.Compositor, + "transform animation 1s after display:block"); + ancestor.parentNode.insertBefore(gDiv, ancestor); + ancestor.remove(); + done_div(); +}); + +// Bug 1125455 - Transitions should not run when animations are running. +addAsyncAnimTest(async function() { + new_div("transition: opacity 2s linear; opacity: 0.8"); + await waitForPaintsFlushed(); + omta_is("opacity", 0.8, RunningOn.MainThread, + "initial opacity"); + gDiv.style.opacity = "0.2"; + await waitForPaintsFlushed(); + omta_is("opacity", 0.8, RunningOn.Compositor, + "opacity transition at 0s"); + advance_clock(500); + omta_is("opacity", 0.65, RunningOn.Compositor, + "opacity transition at 0.5s"); + gDiv.style.animation = "opacitymid 2s linear"; + await waitForPaintsFlushed(); + omta_is("opacity", 0.2, RunningOn.Compositor, + "opacity animation overriding transition at 0s"); + advance_clock(500); + omta_is("opacity", 0.35, RunningOn.Compositor, + "opacity animation overriding transition at 0.5s"); + done_div(); +}); + +// Bug 1320474 - keyframes-name may be a string, allows names that would +// otherwise be excluded. +// These tests don't need to be duplicated here as they relate purely to +// the animation setup which is common to both main-thread and compositor +// animations. + +// Bug 847287 - Test that changes of when an animation is dynamically +// overridden work correctly. +addAsyncAnimTest(async function() { + // anim2 and anim3 are both animations from opacity 0 to 1 + + new_div("animation: anim2 1s linear forwards; opacity: 0.5 ! important"); + await waitForPaintsFlushed(); + omta_is("opacity", 0.5, RunningOn.MainThread, + "opacity overriding animation at start (0s)"); + advance_clock(750); + omta_is("opacity", 0.5, RunningOn.MainThread, + "opacity overriding animation while running (750ms)"); + advance_clock(1000); + omta_is("opacity", 0.5, RunningOn.MainThread, + "opacity overriding animation while filling (1750ms)"); + done_div(); + + new_div("animation: anim2 1s linear; opacity: 0.5 ! important"); + await waitForPaintsFlushed(); + omta_is("opacity", 0.5, RunningOn.MainThread, + "opacity overriding animation at start (0s)"); + advance_clock(750); + omta_is("opacity", 0.5, RunningOn.MainThread, + "opacity overriding animation while running (750ms)"); + advance_clock(1000); + omta_is("opacity", 0.5, RunningOn.MainThread, + "opacity overriding animation after complete (1750ms)"); + done_div(); + + // One animation overriding another, and then not. + new_div("animation: anim2 1s linear, anim3 500ms linear reverse"); + await waitForPaintsFlushed(); + omta_is("opacity", 1, RunningOn.Compositor, + "anim3 overriding anim2 at start (0s)"); + advance_clock(400); + omta_is("opacity", 0.2, RunningOn.Compositor, + "anim3 overriding anim2 at 400ms"); + advance_clock(200); + // Wait for paints because we're resending animations to the + // compositor via an UpdateOpacityLayer hint, which does the resending + // via painting. + await waitForPaints(); + omta_is("opacity", 0.6, RunningOn.Compositor, + "anim2 at 600ms"); + done_div(); + + // One animation overriding another, and then not, but without a + // restyle when the overriding one ends. + new_div("animation: anim2 1s steps(8, end)"); + await waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "anim2 at start (0s)"); + advance_clock(300); + omta_is("opacity", 0.25, RunningOn.Compositor, + "anim2 at 300ms"); + gDiv.style.animation = "anim2 1s steps(8, end), anim3 500ms steps(4, end)"; + await waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "anim3 overriding anim2 at 300ms"); + advance_clock(475); + omta_is("opacity", 0.75, RunningOn.Compositor, + "anim3 the same as anim2 at 775ms"); + advance_clock(50); + // Wait for paints because we're resending animations to the + // compositor via an UpdateOpacityLayer hint, which does the resending + // via painting. + await waitForPaints(); + omta_is("opacity", 0.75, RunningOn.Compositor, + "anim2 at 825ms"); + advance_clock(75); + omta_is("opacity", 0.875, RunningOn.Compositor, + "anim2 at 900ms"); + done_div(); + + // Exactly the same as the previous test, except with an extra + // waitForPaintsFlushed(), since that extra one exposes other bugs. + new_div("animation: anim2 1s steps(8, end)"); + await waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "anim2 at start (0s)"); + advance_clock(300); + omta_is("opacity", 0.25, RunningOn.Compositor, + "anim2 at 300ms"); + gDiv.style.animation = "anim2 1s steps(8, end), anim3 500ms steps(4, end)"; + await waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "anim3 overriding anim2 at 300ms"); + advance_clock(475); + omta_is("opacity", 0.75, RunningOn.Compositor, + "anim3 the same as anim2 at 775ms"); + // Extra waitForPaintsFlushed to expose bugs. + await waitForPaintsFlushed(); + advance_clock(50); + // Wait for paints because we're resending animations to the + // compositor via an UpdateOpacityLayer hint, which does the resending + // via painting. + await waitForPaints(); + omta_is("opacity", 0.75, RunningOn.Compositor, + "anim2 at 825ms"); + advance_clock(75); + omta_is("opacity", 0.875, RunningOn.Compositor, + "anim2 at 900ms"); + done_div(); + + // Test that an interpolation that produces transform: none doesn't + // crash. + new_div("animation: transformnone 1s linear"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "transformnone animation at 0ms"); + advance_clock(500); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "transformnone animation at 500ms, interpolating none values"); + done_div(); +}); + +addAsyncAnimTest(async function() { + new_div("transform: translate(100px); transition: transform 10s 5s linear"); + await waitForPaintsFlushed(); + gDiv.style.transform = "translate(200px)"; + await waitForPaintsFlushed(); + // NOTE: As noted above, getOMTAStyle() can't detect the animation is running + // on the compositor during the delay phase. + omta_is("transform", { tx: 100 }, RunningOn.Either, + "transition runs on compositor thread during delay"); + // At the *very* start of the transition the start value of the transition + // will match the underlying transform value. Various optimizations in + // RestyleManager may recognize this a "no change" and filter out the + // transition meaning that the animation doesn't get added to the compositor + // thread until the first time the value changes. As a result, we fast-forward + // a little past the beginning and then wait for the animation to be sent + // to the compositor. + advance_clock(5100); + await waitForPaints(); + omta_is("transform", { tx: 101 }, RunningOn.Compositor, + "transition runs on compositor at start of active interval"); + advance_clock(4900); + omta_is("transform", { tx: 150 }, RunningOn.Compositor, + "transition runs on compositor at during active interval"); + advance_clock(5000); + // Currently the compositor will apply a forwards fill until it gets told by + // the main thread to clear the animation. As a result we should wait for + // paints before checking that the animated value does *not* appear on the + // compositor thread. + await waitForPaints(); + omta_is("transform", { tx: 200 }, RunningOn.MainThread, + "transition runs on main thread at end of active interval"); + + done_div(); +}); + +// Normal background-color animation. +addAsyncAnimTest(async function() { + new_div("background-color: rgb(255, 0, 0); " + + "transition: background-color 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.backgroundColor = "rgb(0, 255, 0)"; + await waitForPaintsFlushed(); + + omta_is("background-color", "rgb(255, 0, 0)", RunningOn.Compositor, + "background-color transition runs on compositor thread"); + + advance_clock(5000); + omta_is("background-color", "rgb(128, 128, 0)", RunningOn.Compositor, + "background-color on compositor at 5s"); + + done_div(); +}); + +// background-color animation with currentColor. +addAsyncAnimTest(async function() { + new_div("color: rgb(255, 0, 0); " + + "background-color: currentColor; " + + "transition: background-color 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.backgroundColor = "rgb(0, 255, 0)"; + await waitForPaintsFlushed(); + + omta_todo_is("background-color", "rgb(255, 0, 0)", RunningOn.TodoCompositor, + "background-color transition starting with current-color runs on " + + "compositor thread"); + + advance_clock(5000); + omta_todo_is("background-color", "rgb(128, 128, 0)", RunningOn.TodoCompositor, + "background-color on compositor at 5s"); + + done_div(); +}); + +// Tests that a background-color animation from inherited currentColor to +// a normal color on the compositor is updated when the parent color is +// changed. +addAsyncAnimTest(async function() { + new_div(""); + const parent = document.createElement("div"); + gDiv.parentNode.insertBefore(parent, gDiv); + parent.style.color = "rgb(255, 0, 0)"; + parent.appendChild(gDiv); + + gDiv.animate({ backgroundColor: [ "currentColor", "rgb(0, 255, 0)" ] }, 1000); + + await waitForPaintsFlushed(); + + omta_todo_is("background-color", "rgb(255, 0, 0)", RunningOn.TodoCompositor, + "background-color animation starting with current-color runs on " + + "compositor thread"); + + advance_clock(500); + + omta_todo_is("background-color", "rgb(128, 128, 0)", RunningOn.TodoCompositor, + "background-color on compositor at 5s"); + + // Change the parent's color in the middle of the animation. + parent.style.color = "rgb(0, 0, 255)"; + await waitForPaintsFlushed(); + + omta_todo_is("background-color", "rgb(0, 128, 128)", RunningOn.TodoCompositor, + "background-color on compositor is reflected by the parent's " + + "color change"); + + done_div(); + parent.remove(); +}); + +// Tests that a background-color animation from currentColor to a normal color +// on <a> element is updated when the link is visited. +addAsyncAnimTest(async function() { + if (AppConstants.platform === "android") { + todo(false, "no global history on GeckoView; can't run test"); + return; + } + + [ gDiv ] = new_element("a", "display: block"); + gDiv.setAttribute("href", "not-exist.html"); + gDiv.classList.add("visited"); + + const extraStyle = document.createElement('style'); + document.head.appendChild(extraStyle); + extraStyle.sheet.insertRule(".visited:visited { color: rgb(0, 0, 255); }", 0); + extraStyle.sheet.insertRule(".visited:link { color: rgb(255, 0, 0); }", 1); + + gDiv.animate({ backgroundColor: [ "currentColor", "rgb(0, 255, 0)" ] }, 1000); + await waitForPaintsFlushed(); + + omta_todo_is("background-color", "rgb(255, 0, 0)", RunningOn.TodoCompositor, + "background-color animation starting with current-color runs on " + + "compositor thread"); + + advance_clock(500); + + omta_todo_is("background-color", "rgb(128, 128, 0)", RunningOn.TodoCompositor, + "background-color on compositor at 5s"); + + const topLocation = + await SpecialPowers.spawnChrome([], () => browsingContext.top.currentURI.spec); + gDiv.setAttribute("href", topLocation); + await waitForVisitedLinkColoring(gDiv, "color", "rgb(0, 0, 255)"); + await waitForPaintsFlushed(); + + // `omta_is` checks that the result on the compositor equals to the value by + // getComputedValue() but getComputedValue lies for visited link values so + // we use getOMTAStyle directly instead. + todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "background-color"), + "rgb(0, 128, 128)", + "background-color on <a> element after the link is visited"); + + extraStyle.remove(); + done_element(); + gDiv = null; +}); + +// Normal translate animation. +addAsyncAnimTest(async function() { + new_div("translate: 100px; " + + "transition: translate 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.translate = "200px"; + await waitForPaintsFlushed(); + + omta_is("translate", { compositorValue: { tx: 100 }, computed: "100px" }, + RunningOn.Compositor, + "translate transition runs on compositor thread"); + + advance_clock(5000); + omta_is("translate", { compositorValue: { tx: 150 }, computed: "150px" }, + RunningOn.Compositor, "translate on compositor at 5s"); + + done_div(); +}); + +// Normal rotate animation. +addAsyncAnimTest(async function() { + new_div("rotate: 0deg; " + + "transition: rotate 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.rotate = "90deg"; + await waitForPaintsFlushed(); + + omta_is("rotate", { compositorValue: [ 1, 0, 0, 1, 0, 0 ], computed: "0deg"}, + RunningOn.Compositor, "rotate transition runs on compositor thread"); + + advance_clock(5000); + omta_is("rotate", + { compositorValue: [ Math.cos(Math.PI / 4), Math.sin(Math.PI / 4), + -Math.sin(Math.PI / 4), Math.cos(Math.PI / 4), + 0, 0 ], + computed: "45deg" }, RunningOn.Compositor, + "rotate on compositor at 5s"); + + done_div(); +}); + +// Normal scale animation. +addAsyncAnimTest(async function() { + new_div("scale: 1 1; " + + "transition: scale 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.scale = "2 2"; + await waitForPaintsFlushed(); + + omta_is("scale", { compositorValue: [ 1, 0, 0, 1, 0, 0 ], computed: "1" }, + RunningOn.Compositor, "scale transition runs on compositor thread"); + + advance_clock(5000); + omta_is("scale", + { compositorValue: [ 1.5, 0, 0, 1.5, 0, 0 ], computed: "1.5" }, + RunningOn.Compositor, "scale on compositor at 5s"); + + done_div(); +}); + +// Normal multiple transform-like properties animation. +addAsyncAnimTest(async function() { + new_div("translate: 100px; " + + "scale: 1 1; " + + "transform: translate(200px); " + + "transition: all 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.translate = "200px"; + gDiv.style.scale = "2 2"; + gDiv.style.transform = "translate(100px)"; + await waitForPaintsFlushed(); + + omta_is("transform", { compositorValue: { tx: 300 }, + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("transform", + // The order is: translate, scale, transform. + // So the translate() in transform should be multiplied by 1.5. + { compositorValue: [ 1.5, 0, 0, 1.5, (150 + 150*1.5), 0 ], + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties on compositor at 5s"); + + done_div(); +}); + +// Multiple transform-like properties animation. The non-animating properties +// shouldn't be overridden by animating ones. +addAsyncAnimTest(async function() { + new_div("translate: 100px; " + + "scale: 1 1; " + + "transform: translate(200px); " + + "transition: all 10s linear"); + await waitForPaintsFlushed(); + + // No transition on transform property. + gDiv.style.translate = "200px"; + gDiv.style.scale = "2 2"; + await waitForPaintsFlushed(); + + omta_is("transform", { compositorValue: { tx: 300 }, + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("transform", + // The order is: translate, scale, transform. + // So the translate() in transform should be multiplied by 1.5. + { compositorValue: [ 1.5, 0, 0, 1.5, (150 + 200*1.5), 0 ], + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties on compositor at 5s"); + + done_div(); +}); + +// Multiple transform-like properties animation with delay. The delayed +// animating properties shouldn't be overridden. +// +// Note: +// In delay phase, the SampleResult should be None, even though there is +// an non-animating property which is also sent to the compositor. +// If the SampleResult is Sampled in this case, we may get an incorrect result +// ("scale" would be "1" because the final matrix is calculated by a default +// scale value which overrides the scale css style). +// That's why we shouldn't take non-animating properties into account on +// SampleResult. +addAsyncAnimTest(async function() { + new_div("translate: 100px; " + + "scale: 1.25 1.25; " + + "animation: kf_scale 10s 2.5s linear"); + await waitForPaintsFlushed(); + + // All transform-like properties use DisplayItemType::TYPE_TRANSFORM, + // so using "transform" is enough. + var compositorStr = + SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform"); + ok(compositorStr === "", + "transform-like properties should not run on the compositor at 0s " + + "(in delay phase)"); + var computedStr = window.getComputedStyle(gDiv).translate; + ok(computedStr === "100px", + "The computed value of translate property should be equal to 100px " + + "in delay phase, got " + computedStr); + computedStr = window.getComputedStyle(gDiv).scale; + ok(computedStr === "1.25", + "The computed value of scale property should be equal to 1.25 " + + "in delay phase, got " + computedStr); + + advance_clock(2500); + + omta_is("transform", { compositorValue: [ 1.25, 0, 0, 1.25, 100, 0 ], + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties on compositor at 2.5s"); + + advance_clock(5000); + + omta_is("transform", + { compositorValue: [ 1.75, 0, 0, 1.75, 100, 0 ], + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties on compositor at 7.5s"); + + done_div(); +}); + +// Normal offset-path animation with path(). +addAsyncAnimTest(async function() { + new_div("offset-path: path('M50 50L100 100'); " + + "offset-distance: 50%; " + + "offset-rotate: 0deg; " + + "transition: offset-path 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetPath = "path('M50 50L200 200')"; + await waitForPaintsFlushed(); + + omta_is("offset-path", + { compositorValue: { tx: 25, ty: 25 }, + computed: 'path("M 50 50 L 100 100")' }, + RunningOn.Compositor, + "offset-path transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("offset-path", + { compositorValue: { tx: 50, ty: 50 }, + computed: 'path("M 50 50 L 150 150")' }, + RunningOn.Compositor, + "offset-path on compositor at 5s"); + + done_div(); +}); + +// Normal offset-path animation with ray(). +addAsyncAnimTest(async function() { + new_div("offset-path: ray(90deg); " + + "offset-distance: 0%; " + + "offset-position: auto; " + + "transition: offset-path 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetPath = "ray(180deg)"; + await waitForPaintsFlushed(); + + // At 0%, it's ray(90deg), so there is no rotation and only movement because + // the default offset-anchor is 50%. + omta_is("offset-path", + { compositorValue: { tx: -50, ty: -50 }, + computed: 'ray(90deg)' }, + RunningOn.Compositor, + "offset-path transition runs on compositor thread"); + + advance_clock(5000); + + // At 50%, ray() is 135deg. so the matrix is kind of rotate -45 deg: + // [cos(-1/4 * pi), -sin(-1/4 * pi), sin(-1/4 * pi), cos(-1/4 * pi), -50, -50] + // Note: the movement of (-50 -50) is from the default offset-anchor. + omta_is("offset-path", + { + compositorValue: [ + Math.cos(-Math.PI * 1/4), -Math.sin(-Math.PI * 1/4), + Math.sin(-Math.PI * 1/4), Math.cos(-Math.PI * 1/4), + -50, -50 + ], + computed: 'ray(135deg)' + }, + RunningOn.Compositor, + "offset-path on compositor at 5s"); + + done_div(); +}); + +// Normal offset-path animation with polygon(). +addAsyncAnimTest(async function() { + new_div("offset-path: polygon(0px 0px, 100px 0px, 50px 100px); " + + "offset-distance: 0%; " + + "offset-rotate: 0deg; " + + "offset-anchor: left top; " + + "transition: offset-path 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetPath = "polygon(50px 0px, 100px 0px, 50px 100px)"; + await waitForPaintsFlushed(); + + omta_is("offset-path", + { compositorValue: { tx: 0 }, + computed: 'polygon(0px 0px, 100px 0px, 50px 100px)' }, + RunningOn.Compositor, + "offset-path transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("offset-path", + { compositorValue: { tx: 25 }, + computed: 'polygon(25px 0px, 100px 0px, 50px 100px)' }, + RunningOn.Compositor, + "offset-path on compositor at 5s"); + + done_div(); +}); + +// Normal offset-distance animation with path(). +addAsyncAnimTest(async function() { + new_div("offset-path: path('M50 50v100'); " + + "offset-distance: 0%; " + + "offset-rotate: 0deg; " + + "transition: offset-distance 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetDistance = "100%"; + await waitForPaintsFlushed(); + + omta_is("offset-distance", + { compositorValue: { ty: 0 }, computed: '0%' }, + RunningOn.Compositor, + "offset-distance transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("offset-distance", + { compositorValue: { ty: 50 }, computed: '50%' }, + RunningOn.Compositor, + "offset-distance on compositor at 5s"); + + done_div(); +}); + +// Normal offset-distance animation with polygon(). +addAsyncAnimTest(async function() { + new_div("offset-path: polygon(0px 0px, 100px 0px, 100px 50px, 0px 50px); " + + "offset-distance: 0%; " + + "offset-rotate: 0deg; " + + "offset-anchor: left top; " + + "transition: offset-distance 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetDistance = "100%"; + await waitForPaintsFlushed(); + + omta_is("offset-distance", + { compositorValue: { tx: 0 }, computed: '0%' }, + RunningOn.Compositor, + "offset-distance transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("offset-distance", + { compositorValue: { tx: 100, ty: 50 }, computed: '50%' }, + RunningOn.Compositor, + "offset-distance on compositor at 5s"); + + done_div(); +}); + +// Normal offset-rotate animation. +addAsyncAnimTest(async function() { + new_div("offset-path: path('M50 50v100'); " + + "offset-rotate: auto; " + + "transition: offset-rotate 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetRotate = "auto 90deg"; + await waitForPaintsFlushed(); + + // The direction vector is 90deg (because the path go from top to bottom), and + // offset-rotate is auto 0deg, so the entire rotation is 90deg. + // The matrix is [cos(pi/2), sin(pi/2), -sin(pi/2), cos(pi/2), 0, 0]. + omta_is("offset-rotate", + { compositorValue: [ 0, 1, -1, 0, 0, 0 ], computed: 'auto' }, + RunningOn.Compositor, + "offset-rotate transition runs on compositor thread"); + + advance_clock(5000); + + // At 50%, offset-rotate is auto 45deg, so the entire rotation is 135deg + // (= 90deg + 45deg = pi * 3/4), so the matrix is + // [cos(pi * 3/4), sin(pi * 3/4), -sin(pi * 3/4), cos(pi * 3/4), 0, 0] + omta_is("offset-rotate", + { + compositorValue: [ + Math.cos(Math.PI * 3/4), Math.sin(Math.PI * 3/4), + -Math.sin(Math.PI * 3/4), Math.cos(Math.PI * 3/4), + 0, 0 + ], + computed: 'auto 45deg', + }, + RunningOn.Compositor, + "offset-rotate on compositor at 5s"); + + done_div(); +}); + +// Normal offset-anchor animation. +addAsyncAnimTest(async function() { + new_div("offset-path: path('M50 50v100'); " + + "offset-rotate: 0deg; " + + "offset-anchor: 0% 0%; " + + "transition: offset-anchor 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetAnchor = "100% 100%"; + await waitForPaintsFlushed(); + + omta_is("offset-anchor", + { compositorValue: { tx: 50, ty: 50 }, computed: '0% 0%' }, + RunningOn.Compositor, + "offset-anchor transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("offset-anchor", + { compositorValue: { tx: 0, ty: 0 }, computed: '50% 50%' }, + RunningOn.Compositor, + "offset-anchor on compositor at 5s"); + + done_div(); +}); + +// Normal offset-position animation. +addAsyncAnimTest(async function() { + new_div("offset-path: ray(0deg sides); " + + "offset-rotate: 0deg; " + + "offset-anchor: 0% 0%; " + + "offset-position: 0px 0px; " + + "transition: offset-position 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetPosition = "100px 100px"; + await waitForPaintsFlushed(); + + omta_is("offset-position", + { compositorValue: { tx: 0, ty: 0 }, computed: '0px 0px' }, + RunningOn.Compositor, + "offset-position transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("offset-position", + { compositorValue: { tx: 50, ty: 50 }, computed: '50px 50px' }, + RunningOn.Compositor, + "offset-position on compositor at 5s"); + + done_div(); +}); + +// Normal multiple transform-like properties animation (including motion-path). +addAsyncAnimTest(async function() { + new_div("translate: 0px; " + + "offset-path: path('M50 50v100');" + + "offset-distance: 0%;" + + "transform: translateX(0px); " + + "transition: all 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.translate = "0px 100px"; + gDiv.style.transform = "translateX(-100px)"; + gDiv.style.offsetDistance = "100%"; + await waitForPaintsFlushed(); + + omta_is("transform", { compositorValue: [ 0, 1, -1, 0, 0, 0 ], + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("transform", { compositorValue: [ 0, 1, -1, 0, 0, 50 ], + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties transition runs on compositor thread"); + + done_div(); +}); + +</script> +</html> |