dom/animation/test/chrome/test_running_on_compositor.html
author Andreea Pavel <apavel@mozilla.com>
Tue, 27 Nov 2018 15:33:29 +0200
changeset 507467 20898bcaaa0069be066b088fde0262169fc71261
parent 507423 359e81b35cfb72928457964fcf1c826db1404818
child 507676 d99d8f275d8bfa954671ea2663bbe37a561d7583
permissions -rw-r--r--
Backed out 2 changesets (bug 1504065) for failing Win reftest at child-in-animating-element-display-none.html on a CLOSED TREE Backed out changeset 129188370231 (bug 1504065) Backed out changeset 359e81b35cfb (bug 1504065)

<!doctype html>
<head>
<meta charset=utf-8>
<title>Bug 1045994 - Add a chrome-only property to inspect if an animation is
       running on the compositor or not</title>
<script type="application/javascript" src="../testharness.js"></script>
<script type="application/javascript" src="../testharnessreport.js"></script>
<script type="application/javascript" src="../testcommon.js"></script>
<style>
@keyframes anim {
  to { transform: translate(100px) }
}
@keyframes transform-starts-with-none {
    0% { transform: none }
   99% { transform: none }
  100% { transform: translate(100px) }
}
@keyframes opacity {
  to { opacity: 0 }
}
@keyframes zIndex_and_translate {
  to { z-index: 999; transform: translate(100px); }
}
@keyframes z-index {
  to { z-index: 999; }
}
@keyframes rotate {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
@keyframes rotate-and-opacity {
  from { transform: rotate(0deg); opacity: 1;}
  to { transform: rotate(360deg); opacity: 0;}
}
div {
  /* Element needs geometry to be eligible for layerization */
  width: 100px;
  height: 100px;
  background-color: white;
}
</style>
</head>
<body>
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1045994"
  target="_blank">Mozilla Bug 1045994</a>
<div id="log"></div>
<script>
'use strict';

/** Test for bug 1045994 - Add a chrome-only property to inspect if an
    animation is running on the compositor or not **/

var omtaEnabled = isOMTAEnabled();

function assert_animation_is_running_on_compositor(animation, desc) {
  assert_equals(animation.isRunningOnCompositor, omtaEnabled,
                desc + ' at ' + animation.currentTime + 'ms');
}

function assert_animation_is_not_running_on_compositor(animation, desc) {
  assert_equals(animation.isRunningOnCompositor, false,
                desc + ' at ' + animation.currentTime + 'ms');
}

promise_test(async t => {
  // FIXME: When we implement Element.animate, use that here instead of CSS
  // so that we remove any dependency on the CSS mapping.
  var div = addDiv(t, { style: 'animation: anim 100s' });
  var animation = div.getAnimations()[0];

  // If the animation starts at the current timeline time, we need to wait for
  // one more frame to avoid receiving the fake timer-based MozAfterPaint event.
  // FIXME: Bug 1419226: Drop this 'animation.ready' and 'waitForFrame'.  Once
  // MozAfterPaint is fired reliably, we just need to wait for a MozAfterPaint
  // here.
  await animation.ready;

  if (animationStartsRightNow(animation)) {
    await waitForNextFrame();
  }

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
     'Animation reports that it is running on the compositor'
     + ' during playback');

  div.style.animationPlayState = 'paused';

  await animation.ready;

  assert_animation_is_not_running_on_compositor(animation,
     'Animation reports that it is NOT running on the compositor'
     + ' when paused');
}, '');

promise_test(async t => {
  var div = addDiv(t, { style: 'animation: z-index 100s' });
  var animation = div.getAnimations()[0];

  await waitForPaints();

  assert_animation_is_not_running_on_compositor(animation,
     'Animation reports that it is NOT running on the compositor'
     + ' for animation of "z-index"');
}, 'isRunningOnCompositor is false for animation of "z-index"');

promise_test(async t => {
  var div = addDiv(t, { style: 'animation: zIndex_and_translate 100s' });
  var animation = div.getAnimations()[0];

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
     'Animation reports that it is running on the compositor'
      + ' when the animation has two properties, where one can run'
      + ' on the compositor, the other cannot');
}, 'isRunningOnCompositor is true if the animation has at least one ' +
   'property can run on compositor');

promise_test(async t => {
  var div = addDiv(t, { style: 'animation: anim 100s' });
  var animation = div.getAnimations()[0];

  await waitForPaints();

  animation.pause();
  await animation.ready;

  assert_animation_is_not_running_on_compositor(animation,
     'Animation reports that it is NOT running on the compositor'
     + ' when animation.pause() is called');
}, 'isRunningOnCompositor is false when the animation.pause() is called');

promise_test(async t => {
  var div = addDiv(t, { style: 'animation: anim 100s' });
  var animation = div.getAnimations()[0];

  await waitForPaints();

  animation.finish();
  assert_animation_is_not_running_on_compositor(animation,
     'Animation reports that it is NOT running on the compositor'
     + ' immediately after animation.finish() is called');
  // Check that we don't set the flag back again on the next tick.
  await waitForFrame();

  assert_animation_is_not_running_on_compositor(animation,
     'Animation reports that it is NOT running on the compositor'
     + ' on the next tick after animation.finish() is called');
}, 'isRunningOnCompositor is false when the animation.finish() is called');

promise_test(async t => {
  var div = addDiv(t, { style: 'animation: anim 100s' });
  var animation = div.getAnimations()[0];

  await waitForPaints();

  animation.currentTime = 100 * MS_PER_SEC;
  assert_animation_is_not_running_on_compositor(animation,
     'Animation reports that it is NOT running on the compositor'
     + ' immediately after manually seeking the animation to the end');
  // Check that we don't set the flag back again on the next tick.
  await waitForFrame();

  assert_animation_is_not_running_on_compositor(animation,
     'Animation reports that it is NOT running on the compositor'
     + ' on the next tick after manually seeking the animation to the end');
}, 'isRunningOnCompositor is false when manually seeking the animation to ' +
   'the end');

promise_test(async t => {
  var div = addDiv(t, { style: 'animation: anim 100s' });
  var animation = div.getAnimations()[0];

  await waitForPaints();

  animation.cancel();
  assert_animation_is_not_running_on_compositor(animation,
     'Animation reports that it is NOT running on the compositor'
     + ' immediately after animation.cancel() is called');
  // Check that we don't set the flag back again on the next tick.
  await waitForFrame();

  assert_animation_is_not_running_on_compositor(animation,
     'Animation reports that it is NOT running on the compositor'
     + ' on the next tick after animation.cancel() is called');
}, 'isRunningOnCompositor is false when animation.cancel() is called');

// This is to test that we don't simply clobber the flag when ticking
// animations and then set it again during painting.
promise_test(async t => {
  var div = addDiv(t, { style: 'animation: anim 100s' });
  var animation = div.getAnimations()[0];

  await waitForPaints();

  await new Promise(resolve => {
    window.requestAnimationFrame(() => {
      t.step(() => {
        assert_animation_is_running_on_compositor(animation,
          'Animation reports that it is running on the compositor'
           + ' in requestAnimationFrame callback');
      });

      resolve();
    });
  });
}, 'isRunningOnCompositor is true in requestAnimationFrame callback');

promise_test(async t => {
  var div = addDiv(t, { style: 'animation: anim 100s' });
  var animation = div.getAnimations()[0];

  await waitForPaints();

  await new Promise(resolve => {
    var observer = new MutationObserver(records => {
      var changedAnimation;

      records.forEach(record => {
        changedAnimation =
          record.changedAnimations.find(changedAnim => {
            return changedAnim == animation;
          });
      });

      t.step(() => {
        assert_true(!!changedAnimation, 'The animation should be recorded '
          + 'as one of the changedAnimations');

        assert_animation_is_running_on_compositor(animation,
          'Animation reports that it is running on the compositor'
           + ' in MutationObserver callback');
      });

      resolve();
    });
    observer.observe(div, { animations: true, subtree: false });
    t.add_cleanup(() => {
      observer.disconnect();
    });
    div.style.animationDuration = "200s";
  });
}, 'isRunningOnCompositor is true in MutationObserver callback');

// This is to test that we don't temporarily clear the flag when forcing
// an unthrottled sample.
promise_test(async t => {
  // Needs scrollbars to cause overflow.
  await SpecialPowers.pushPrefEnv({ set: [["ui.showHideScrollbars", 1]] })

  var div = addDiv(t, { style: 'animation: rotate 100s' });
  var animation = div.getAnimations()[0];

  await waitForPaints();

  await new Promise(resolve => {
    var timeAtStart = window.performance.now();
    function handleFrame() {
      t.step(() => {
        assert_animation_is_running_on_compositor(animation,
          'Animation reports that it is running on the compositor'
           + ' in requestAnimationFrame callback');
      });

      // we have to wait at least 200ms because this animation is
      // unthrottled on every 200ms.
      // See https://hg.mozilla.org/mozilla-central/file/cafb1c90f794/layout/style/AnimationCommon.cpp#l863
      if (window.performance.now() - timeAtStart > 200) {
        resolve();
        return;
      }
      window.requestAnimationFrame(handleFrame);
    }
    window.requestAnimationFrame(handleFrame);
  });
}, 'isRunningOnCompositor remains true in requestAnimationFrameCallback for ' +
   'overflow animation');

promise_test(async t => {
  var div = addDiv(t, { style: 'transition: opacity 100s; opacity: 1' });

  getComputedStyle(div).opacity;

  div.style.opacity = 0;
  var animation = div.getAnimations()[0];

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
     'Transition reports that it is running on the compositor'
     + ' during playback for opacity transition');
}, 'isRunningOnCompositor for transitions');

promise_test(async t => {
  var div = addDiv(t, { style: 'animation: rotate-and-opacity 100s; ' +
                               'backface-visibility: hidden; ' +
                               'transform: none !important;' });
  var animation = div.getAnimations()[0];

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
     'If an animation has a property that can run on the compositor and a '
     + 'property that cannot (due to Gecko limitations) but where the latter'
     + 'property is overridden in the CSS cascade, the animation should '
     + 'still report that it is running on the compositor');
}, 'isRunningOnCompositor is true when a property that would otherwise block ' +
   'running on the compositor is overridden in the CSS cascade');

promise_test(async t => {
  var animation = addDivAndAnimate(t,
                                   {},
                                   { opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
    'Animation reports that it is running on the compositor');

  animation.currentTime = 150 * MS_PER_SEC;
  animation.effect.updateTiming({ duration: 100 * MS_PER_SEC });

  assert_animation_is_not_running_on_compositor(animation,
     'Animation reports that it is NOT running on the compositor'
     + ' when the animation is set a shorter duration than current time');
}, 'animation is immediately removed from compositor' +
   'when the duration is made shorter than the current time');

promise_test(async t => {
  var animation = addDivAndAnimate(t,
                                   {},
                                   { opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
    'Animation reports that it is running on the compositor');

  animation.currentTime = 500 * MS_PER_SEC;

  assert_animation_is_not_running_on_compositor(animation,
    'Animation reports that it is NOT running on the compositor'
    + ' when finished');

  animation.effect.updateTiming({ duration: 1000 * MS_PER_SEC });
  await waitForFrame();

  assert_animation_is_running_on_compositor(animation,
    'Animation reports that it is running on the compositor'
    + ' when restarted');
}, 'animation is added to compositor' +
   ' when the duration is made longer than the current time');

promise_test(async t => {
  var animation = addDivAndAnimate(t,
                                   {},
                                   { opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
    'Animation reports that it is running on the compositor');

  animation.effect.updateTiming({ endDelay: 100 * MS_PER_SEC });

  assert_animation_is_running_on_compositor(animation,
    'Animation reports that it is running on the compositor'
    + ' when endDelay is changed');

  animation.currentTime = 110 * MS_PER_SEC;
  await waitForFrame();

  assert_animation_is_not_running_on_compositor(animation,
    'Animation reports that it is NOT running on the compositor'
    + ' when currentTime is during endDelay');
}, 'animation is removed from compositor' +
   ' when current time is made longer than the duration even during endDelay');

promise_test(async t => {
  var animation = addDivAndAnimate(t,
                                   {},
                                   { opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
    'Animation reports that it is running on the compositor');

  animation.effect.updateTiming({ endDelay: -200 * MS_PER_SEC });
  await waitForFrame();

  assert_animation_is_not_running_on_compositor(animation,
    'Animation reports that it is NOT running on the compositor'
    + ' when endTime is negative value');
}, 'animation is removed from compositor' +
   ' when endTime is negative value');

promise_test(async t => {
  var animation = addDivAndAnimate(t,
                                   {},
                                   { opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
    'Animation reports that it is running on the compositor');

  animation.effect.updateTiming({ endDelay: -100 * MS_PER_SEC });
  await waitForFrame();

  assert_animation_is_running_on_compositor(animation,
    'Animation reports that it is running on the compositor'
    + ' when endTime is positive and endDelay is negative');
  animation.currentTime = 110 * MS_PER_SEC;
  await waitForFrame();

  assert_animation_is_not_running_on_compositor(animation,
    'Animation reports that it is NOT running on the compositor'
    + ' when currentTime is after endTime');
}, 'animation is NOT running on compositor' +
   ' when endTime is positive and endDelay is negative');

promise_test(async t => {
  var effect = new KeyframeEffect(null,
                                  { opacity: [ 0, 1 ] },
                                  100 * MS_PER_SEC);
  var animation = new Animation(effect, document.timeline);
  animation.play();

  var div = addDiv(t);

  await waitForPaints();

  assert_animation_is_not_running_on_compositor(animation,
                'Animation with null target reports that it is not running ' +
                'on the compositor');

  animation.effect.target = div;
  await waitForFrame();

  assert_animation_is_running_on_compositor(animation,
                'Animation reports that it is running on the compositor ' +
                'after setting a valid target');
}, 'animation is added to the compositor when setting a valid target');

promise_test(async t => {
  var div = addDiv(t);
  var animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
                'Animation reports that it is running on the compositor');

  animation.effect.target = null;
  assert_animation_is_not_running_on_compositor(animation,
                'Animation reports that it is NOT running on the ' +
                'compositor after setting null target');
}, 'animation is removed from the compositor when setting null target');

promise_test(async t => {
  var div = addDiv(t);
  var animation = div.animate({ opacity: [ 0, 1 ] },
                              { duration: 100 * MS_PER_SEC,
                                delay: 100 * MS_PER_SEC,
                                fill: 'backwards' });

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
                'Animation with fill:backwards in delay phase reports ' +
                'that it is running on the compositor');

  animation.currentTime = 100 * MS_PER_SEC;
  await waitForFrame();

  assert_animation_is_running_on_compositor(animation,
      'Animation with fill:backwards in delay phase reports ' +
      'that it is running on the compositor after delay phase');
}, 'animation with fill:backwards in delay phase is running on the ' +
   ' main-thread while it is in delay phase');

promise_test(async t => {
  var div = addDiv(t);
  var animation = div.animate([{ opacity: 1, offset: 0 },
                               { opacity: 1, offset: 0.99 },
                               { opacity: 0, offset: 1 }], 100 * MS_PER_SEC);

  var another = addDiv(t);

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
                'Opacity animation on a 100% opacity keyframe reports ' +
                'that it is running on the compositor from the begining');

  animation.effect.target = another;
  await waitForFrame();

  assert_animation_is_running_on_compositor(animation,
                'Opacity animation on a 100% opacity keyframe keeps ' +
                'running on the compositor after changing the target ' +
                'element');
}, '100% opacity animations with keeps running on the ' +
   'compositor after changing the target element');

promise_test(async t => {
  var div = addDiv(t);
  var animation = div.animate({ color: ['red', 'black'] }, 100 * MS_PER_SEC);

  await waitForPaints();

  assert_animation_is_not_running_on_compositor(animation,
                'Color animation reports that it is not running on the ' +
                'compositor');

  animation.effect.setKeyframes([{ opacity: 1, offset: 0 },
                                 { opacity: 1, offset: 0.99 },
                                 { opacity: 0, offset: 1 }]);
  await waitForFrame();

  assert_animation_is_running_on_compositor(animation,
                '100% opacity animation set by using setKeyframes reports ' +
                'that it is running on the compositor');
}, '100% opacity animation set up by converting an existing animation with ' +
   'cannot be run on the compositor, is running on the compositor');

promise_test(async t => {
  var div = addDiv(t);
  var animation = div.animate({ color: ['red', 'black'] }, 100 * MS_PER_SEC);
  var effect = new KeyframeEffect(div,
                                  [{ opacity: 1, offset: 0 },
                                   { opacity: 1, offset: 0.99 },
                                   { opacity: 0, offset: 1 }],
                                  100 * MS_PER_SEC);

  await waitForPaints();

  assert_animation_is_not_running_on_compositor(animation,
                'Color animation reports that it is not running on the ' +
                'compositor');

  animation.effect = effect;
  await waitForFrame();

  assert_animation_is_running_on_compositor(animation,
                '100% opacity animation set up by changing effects reports ' +
                'that it is running on the compositor');
}, '100% opacity animation set up by changing the effects on an existing ' +
   'animation which cannot be run on the compositor, is running on the ' +
   'compositor');

promise_test(async t => {
  var div = addDiv(t, { style: "opacity: 1 ! important" });

  var animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);

  await waitForPaints();

  assert_animation_is_not_running_on_compositor(animation,
    'Opacity animation on an element which has 100% opacity style with ' +
    '!important flag reports that it is not running on the compositor');
  // Clear important flag from the opacity style on the target element.
  div.style.setProperty("opacity", "1", "");
  await waitForFrame();

  assert_animation_is_running_on_compositor(animation,
    'Opacity animation reports that it is running on the compositor after '
    + 'clearing the !important flag');
}, 'Clearing *important* opacity style on the target element sends the ' +
   'animation to the compositor');

promise_test(async t => {
  var div = addDiv(t);
  var lowerAnimation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC);
  var higherAnimation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);

  await waitForPaints();

  assert_animation_is_running_on_compositor(higherAnimation,
                'A higher-priority opacity animation on an element ' +
                'reports that it is running on the compositor');
  assert_animation_is_running_on_compositor(lowerAnimation,
                'A lower-priority opacity animation on the same ' +
                'element also reports that it is running on the compositor');
}, 'Opacity animations on the same element run on the compositor');

promise_test(async t => {
  var div = addDiv(t, { style: 'transition: opacity 100s; opacity: 1' });

  getComputedStyle(div).opacity;

  div.style.opacity = 0;
  getComputedStyle(div).opacity;

  var transition = div.getAnimations()[0];
  var animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
                'An opacity animation on an element reports that' +
                'that it is running on the compositor');
  assert_animation_is_running_on_compositor(transition,
                'An opacity transition on the same element reports that ' +
                'it is running on the compositor');
}, 'Both of transition and script animation on the same element run on the ' +
   'compositor');

promise_test(async t => {
  var div = addDiv(t);
  var importantOpacityElement = addDiv(t, { style: "opacity: 1 ! important" });

  var animation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC);

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
                'Opacity animation on an element reports ' +
                'that it is running on the compositor');

  animation.effect.target = null;
  await waitForFrame();

  assert_animation_is_not_running_on_compositor(animation,
                'Animation is no longer running on the compositor after ' +
                'removing from the element');
  animation.effect.target = importantOpacityElement;
  await waitForFrame();

  assert_animation_is_not_running_on_compositor(animation,
                'Animation is NOT running on the compositor even after ' +
                'being applied to a different element which has an ' +
                '!important opacity declaration');
}, 'Animation continues not running on the compositor after being ' +
   'applied to an element which has an important declaration and ' +
   'having previously been temporarily associated with no target element');

promise_test(async t => {
  var div = addDiv(t);
  var another = addDiv(t);

  var lowerAnimation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC);
  var higherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);

  await waitForPaints();

  assert_animation_is_running_on_compositor(lowerAnimation,
                'An opacity animation on an element reports that ' +
                'it is running on the compositor');
  assert_animation_is_running_on_compositor(higherAnimation,
                'Opacity animation on a different element reports ' +
                'that it is running on the compositor');

  lowerAnimation.effect.target = null;
  await waitForFrame();

  assert_animation_is_not_running_on_compositor(lowerAnimation,
                'Animation is no longer running on the compositor after ' +
                'being removed from the element');
  lowerAnimation.effect.target = another;
  await waitForFrame();

  assert_animation_is_running_on_compositor(lowerAnimation,
                'A lower-priority animation begins running ' +
                'on the compositor after being applied to an element ' +
                'which has a higher-priority animation');
  assert_animation_is_running_on_compositor(higherAnimation,
                'A higher-priority animation continues to run on the ' +
                'compositor even after a lower-priority animation is ' +
                'applied to the same element');
}, 'Animation begins running on the compositor after being applied ' +
   'to an element which has a higher-priority animation and after ' +
   'being temporarily associated with no target element');

promise_test(async t => {
  var div = addDiv(t);
  var another = addDiv(t);

  var lowerAnimation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC);
  var higherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);

  await waitForPaints();

  assert_animation_is_running_on_compositor(lowerAnimation,
                'An opacity animation on an element reports that ' +
                'it is running on the compositor');
  assert_animation_is_running_on_compositor(higherAnimation,
                'Opacity animation on a different element reports ' +
                'that it is running on the compositor');

  higherAnimation.effect.target = null;
  await waitForFrame();

  assert_animation_is_not_running_on_compositor(higherAnimation,
                'Animation is no longer running on the compositor after ' +
                'being removed from the element');
  higherAnimation.effect.target = div;
  await waitForFrame();

  assert_animation_is_running_on_compositor(lowerAnimation,
                'Animation continues running on the compositor after ' +
                'a higher-priority animation applied to the same element');
  assert_animation_is_running_on_compositor(higherAnimation,
                'A higher-priority animation begins to running on the ' +
                'compositor after being applied to an element which has ' +
                'a lower-priority-animation');
}, 'Animation begins running on the compositor after being applied ' +
   'to an element which has a lower-priority animation once after ' +
   'disassociating with an element');

var delayPhaseTests = [
  {
    desc: 'script animation of opacity',
    setupAnimation: t => {
      return addDiv(t).animate(
        { opacity: [0, 1] },
        { delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC });
    },
  },
  {
    desc: 'script animation of transform',
    setupAnimation: t => {
      return addDiv(t).animate(
        { transform: ['translateX(0px)', 'translateX(100px)'] },
        { delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC });
    },
  },
  {
    desc: 'CSS animation of opacity',
    setupAnimation: t => {
      return addDiv(t, { style: 'animation: opacity 100s 100s' })
        .getAnimations()[0];
    },
  },
  {
    desc: 'CSS animation of transform',
    setupAnimation: t => {
      return addDiv(t, { style: 'animation: anim 100s 100s' })
        .getAnimations()[0];
    },
  },
  {
    desc: 'CSS transition of opacity',
    setupAnimation: t => {
      var div = addDiv(t, { style: 'transition: opacity 100s 100s' });
      getComputedStyle(div).opacity;

      div.style.opacity = 0;
      return div.getAnimations()[0];
    },
  },
  {
    desc: 'CSS transition of transform',
    setupAnimation: t => {
      var div = addDiv(t, { style: 'transition: transform 100s 100s' });
      getComputedStyle(div).transform;

      div.style.transform = 'translateX(100px)';
      return div.getAnimations()[0];
    },
  },
];

delayPhaseTests.forEach(test => {
  promise_test(async t => {
    var animation = test.setupAnimation(t);

    await waitForPaints();

    assert_animation_is_running_on_compositor(animation,
       test.desc + ' reports that it is running on the '
       + 'compositor even though it is in the delay phase');
  }, 'isRunningOnCompositor for ' + test.desc + ' is true even though ' +
     'it is in the delay phase');
});

// The purpose of thie test cases is to check that
// NS_FRAME_MAY_BE_TRANSFORMED flag on the associated nsIFrame persists
// after transform style on the frame is removed.
var delayPhaseWithTransformStyleTests = [
  {
    desc: 'script animation of transform with transform style',
    setupAnimation: t => {
      return addDiv(t, { style: 'transform: translateX(10px)' }).animate(
        { transform: ['translateX(0px)', 'translateX(100px)'] },
        { delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC });
    },
  },
  {
    desc: 'CSS animation of transform with transform style',
    setupAnimation: t => {
      return addDiv(t, { style: 'animation: anim 100s 100s;' +
                                'transform: translateX(10px)' })
        .getAnimations()[0];
    },
  },
  {
    desc: 'CSS transition of transform with transform style',
    setupAnimation: t => {
      var div = addDiv(t, { style: 'transition: transform 100s 100s;' +
                                   'transform: translateX(10px)'});
      getComputedStyle(div).transform;

      div.style.transform = 'translateX(100px)';
      return div.getAnimations()[0];
    },
  },
];

delayPhaseWithTransformStyleTests.forEach(test => {
  promise_test(async t => {
    var animation = test.setupAnimation(t);

    await waitForPaints();

    assert_animation_is_running_on_compositor(animation,
       test.desc + ' reports that it is running on the '
       + 'compositor even though it is in the delay phase');

    // Remove the initial transform style during delay phase.
    animation.effect.target.style.transform = 'none';
    await animation.ready;

    assert_animation_is_running_on_compositor(animation,
       test.desc + ' reports that it keeps running on the '
       + 'compositor after removing the initial transform style');
  }, 'isRunningOnCompositor for ' + test.desc + ' is true after removing ' +
     'the initial transform style during the delay phase');
});

var startsWithNoneTests = [
  {
    desc: 'script animation of transform starts with transform:none segment',
    setupAnimation: t => {
      return addDiv(t).animate(
        { transform: ['none', 'none', 'translateX(100px)'] }, 100 * MS_PER_SEC);
    },
  },
  {
    desc: 'CSS animation of transform starts with transform:none segment',
    setupAnimation: t => {
      return addDiv(t,
        { style: 'animation: transform-starts-with-none 100s 100s' })
          .getAnimations()[0];
    },
  },
];

startsWithNoneTests.forEach(test => {
  promise_test(async t => {
    var animation = test.setupAnimation(t);

    await waitForPaints();

    assert_animation_is_running_on_compositor(animation,
       test.desc + ' reports that it is running on the '
       + 'compositor even though it is in transform:none segment');
  }, 'isRunningOnCompositor for ' + test.desc + ' is true even though ' +
     'it is in transform:none segment');
});

promise_test(async t => {
  var div = addDiv(t, { style: 'opacity: 1 ! important' });

  var animation = div.animate(
    { opacity: [0, 1] },
    { delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC });

  await waitForPaints();

  assert_animation_is_not_running_on_compositor(animation,
    'Opacity animation on an element which has opacity:1 important style'
    + 'reports that it is not running on the compositor');
  // Clear the opacity style on the target element.
  div.style.setProperty("opacity", "1", "");
  await waitForFrame();

  assert_animation_is_running_on_compositor(animation,
    'Opacity animations reports that it is running on the compositor after '
    + 'clearing the opacity style on the element');
}, 'Clearing *important* opacity style on the target element sends the ' +
   'animation to the compositor even if the animation is in the delay phase');

promise_test(async t => {
  var opaqueDiv = addDiv(t, { style: 'opacity: 1 ! important' });
  var anotherDiv = addDiv(t);

  var animation = opaqueDiv.animate(
    { opacity: [0, 1] },
    { delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC });

  await waitForPaints();

  assert_animation_is_not_running_on_compositor(animation,
    'Opacity animation on an element which has opacity:1 important style'
    + 'reports that it is not running on the compositor');
  // Changing target element to another element which has no opacity style.
  animation.effect.target = anotherDiv;
  await waitForFrame();

  assert_animation_is_running_on_compositor(animation,
    'Opacity animations reports that it is running on the compositor after '
    + 'changing the target element to another elemenent having no '
    + 'opacity style');
}, 'Changing target element of opacity animation sends the animation to the ' +
   'the compositor even if the animation is in the delay phase');

promise_test(async t => {
  var animation =
    addDivAndAnimate(t,
                     {},
                     { width: ['100px', '200px'] },
                     { duration: 100 * MS_PER_SEC, delay: 100 * MS_PER_SEC });

  await waitForPaints();

  assert_animation_is_not_running_on_compositor(animation,
    'Width animation reports that it is not running on the compositor '
    + 'in the delay phase');
  // Changing to property runnable on the compositor.
  animation.effect.setKeyframes({ opacity: [0, 1] });
  await waitForFrame();

  assert_animation_is_running_on_compositor(animation,
    'Opacity animation reports that it is running on the compositor '
    + 'after changing the property from width property in the delay phase');
}, 'Dynamic change to a property runnable on the compositor ' +
   'in the delay phase');

promise_test(async t => {
  var div = addDiv(t, { style: 'transition: opacity 100s; ' +
                               'opacity: 0 !important' });
  getComputedStyle(div).opacity;

  div.style.setProperty('opacity', '1', 'important');
  getComputedStyle(div).opacity;

  var animation = div.getAnimations()[0];

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
     'Transition reports that it is running on the compositor even if the ' +
     'property is overridden by an !important rule');
}, 'Transitions override important rules');

promise_test(async t => {
  var div = addDiv(t, { style: 'transition: opacity 100s; ' +
                               'opacity: 0 !important' });
  getComputedStyle(div).opacity;

  div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);

  div.style.setProperty('opacity', '1', 'important');
  getComputedStyle(div).opacity;

  var [transition, animation] = div.getAnimations();

  await waitForPaints();

  assert_animation_is_not_running_on_compositor(transition,
     'Transition suppressed by an animation which is overridden by an ' +
     '!important rule reports that it is NOT running on the compositor');
  assert_animation_is_not_running_on_compositor(animation,
     'Animation overridden by an !important rule reports that it is ' +
     'NOT running on the compositor');
}, 'Neither transition nor animation does run on the compositor if the ' +
   'property is overridden by an !important rule');

promise_test(async t => {
  var div = addDiv(null, { style: 'display: table;' });
  var animation =
    div.animate({ transform: ['rotate(0deg)', 'rotate(360deg)'] },
                100 * MS_PER_SEC);

  await animation.ready;

  if (animationStartsRightNow(animation)) {
    await waitForNextFrame();
  }

  await waitForPaints();

  assert_animation_is_running_on_compositor(animation,
    'Transform animation on table element should be running on the compositor');
}, 'Transform animation on table element runs on the compositor');

</script>
</body>