<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>DOMWindowUtils test with animation</title>
  <script src="/tests/SimpleTest/paint_listener.js"></script>
  <script src="/tests/dom/animation/test/testcommon.js"></script>
</head>
<body>
<script type="application/javascript">

const SimpleTest = window.opener.SimpleTest;
const utils = SpecialPowers.getDOMWindowUtils(window);
const next = window.opener.next;
const is = window.opener.is;
const ok = window.opener.ok;

function addStyle(rules) {
  const extraStyle = document.createElement("style");
  document.head.appendChild(extraStyle);
  rules.forEach(rule => {
    extraStyle.sheet.insertRule(rule, extraStyle.sheet.cssRules.length);
  });
}

function deleteStyle() {
  document.head.querySelector("style").remove();
}


function test_getUnanimatedComputedStyle() {
  [
    {
      property: "opacity",
      keyframes: [1, 0],
      expectedInitialStyle: "1",
      expectedDuringTransitionStyle: "0",
      isDiscrete: false,
    },
    {
      property: "clear",
      keyframes: ["left", "inline-end"],
      expectedInitialStyle: "none",
      expectedDuringTransitionStyle: "inline-end",
      isDiscrete: true,
    },
  ].forEach(testcase => {
    const { property, keyframes, expectedInitialStyle,
            expectedDuringTransitionStyle, isDiscrete } = testcase;

    [null, "unset", "initial", "inherit"].forEach(initialStyle => {
      const scriptAnimation = target => {
        return target.animate({ [property]: keyframes }, 1000);
      }
      checkUnanimatedComputedStyle(property, initialStyle, null,
                                   expectedInitialStyle, expectedInitialStyle,
                                   scriptAnimation, "script animation");

      const cssAnimationStyle = `@keyframes cssanimation {`
                                + ` from { ${property}: ${ keyframes[0] }; }`
                                + ` to { ${property}: ${ keyframes[1] }; } }`;
      addStyle([cssAnimationStyle]);
      const cssAnimation = target => {
        target.style.animation = "cssanimation 1s";
        return target.getAnimations()[0];
      }
      checkUnanimatedComputedStyle(property, initialStyle, null,
                                   expectedInitialStyle, expectedInitialStyle,
                                   cssAnimation, "CSS Animations");
      deleteStyle();

      // We don't support discrete animations for CSS Transitions yet.
      // (bug 1320854)
      if (!isDiscrete) {
        const cssTransition = target => {
          target.style[property] = keyframes[0];
          target.style.transition =
            `${ property } 1s`;
          window.getComputedStyle(target)[property];
          target.style[property] = keyframes[1];
          return target.getAnimations()[0];
        }
        checkUnanimatedComputedStyle(property, initialStyle, null,
                                     expectedInitialStyle,
                                     expectedDuringTransitionStyle,
                                     cssTransition, "CSS Transitions");
      }

      addStyle([cssAnimationStyle,
                ".pseudo::before { content: '' }",
                ".animation::before { animation: cssanimation 1s }"]);
      const pseudoAnimation = target => {
        target.classList.add("animation");
        return target.getAnimations({ subtree: true })[0];
      }
      checkUnanimatedComputedStyle(property, initialStyle, "::before",
                                   expectedInitialStyle, expectedInitialStyle,
                                   pseudoAnimation, "Animation at pseudo");
      deleteStyle();
    });
  });

  const div = document.createElement("div");
  document.body.appendChild(div);

  SimpleTest.doesThrow(
    () => utils.getUnanimatedComputedStyle(div, null, "background", utils.FLUSH_NONE),
    "NS_ERROR_INVALID_ARG",
    "Shorthand property should throw");

  SimpleTest.doesThrow(
    () => utils.getUnanimatedComputedStyle(div, null, "invalid", utils.FLUSH_NONE),
    "NS_ERROR_INVALID_ARG",
    "Invalid property should throw");

  SimpleTest.doesThrow(
    () => utils.getUnanimatedComputedStyle(null, null, "opacity", utils.FLUSH_NONE),
    "NS_ERROR_INVALID_ARG",
    "Null element should throw");

  SimpleTest.doesThrow(
    () => utils.getUnanimatedComputedStyle(div, null, "opacity", utils.FLUSH_LAYOUT),
    "NS_ERROR_INVALID_ARG",
    "FLUSH_LAYOUT option should throw");

  SimpleTest.doesThrow(
    () => utils.getUnanimatedComputedStyle(div, null, "opacity", utils.FLUSH_DISPLAY),
    "NS_ERROR_INVALID_ARG",
    "FLUSH_DISPLAY option should throw");

  SimpleTest.doesThrow(
    () => utils.getUnanimatedComputedStyle(div, "::before", "opacity", utils.FLUSH_NONE),
    "NS_ERROR_FAILURE",
    "Non-existent pseudo should throw");

  // Flush styles since getUnanimatedComputedStyle flushes pending styles even
  // with FLUSH_NONE option if the element hasn't yet styled.
  getComputedStyle(div).opacity;

  div.style.opacity = "0";
  is(utils.getUnanimatedComputedStyle(div, null, "opacity", utils.FLUSH_NONE),
     "1",
     "getUnanimatedComputedStyle with FLUSH_NONE should not flush pending styles");

  is(utils.getUnanimatedComputedStyle(div, null, "opacity", utils.FLUSH_STYLE),
     "0",
     "getUnanimatedComputedStyle with FLUSH_STYLE should flush pending styles");

  div.remove();

  test_needsFlushWithThrottledAnimations();
}

function checkUnanimatedComputedStyle(property, initialStyle, pseudoType,
                                      expectedBeforeAnimation,
                                      expectedDuringAnimation,
                                      animate, animationType) {
  const div = document.createElement("div");
  document.body.appendChild(div);

  if (initialStyle) {
    div.style[property] = initialStyle;
  }
  if (pseudoType) {
    div.classList.add("pseudo");
  }

  is(utils.getUnanimatedComputedStyle(div, pseudoType, property, utils.FLUSH_STYLE),
     expectedBeforeAnimation,
     `'${ property }' property with '${ initialStyle }' style `
     + `should be '${ expectedBeforeAnimation }' `
     + `before animating by ${ animationType }`);

  const animation = animate(div);
  animation.currentTime = 500;
  is(utils.getUnanimatedComputedStyle(div, pseudoType, property, utils.FLUSH_STYLE),
     expectedDuringAnimation,
     `'${ property }' property with '${ initialStyle }' style `
     + `should be '${ expectedDuringAnimation }' `
     + `even while animating by ${ animationType }`);

  div.remove();
}

function test_needsFlushWithThrottledAnimations() {
  const div = document.createElement("div");
  div.style = "width: 100px; height: 100px; background-color: blue;";
  document.body.appendChild(div);

  const animation = div.animate({ opacity: [ 0, 1 ] },
                                { duration: 100000, iterations: Infinity });
  waitForAnimationReadyToRestyle(animation).then(() => {
    ok(SpecialPowers.wrap(animation).isRunningOnCompositor,
        "Opacity animation should run on the compositor");

    // FIXME! Bug 1442861: We should make sure needsFlush() returns true
    // before flusing layout.
    //ok(utils.needsFlush(SpecialPowers.Ci.nsIDOMWindowUtils.FLUSH_STYLE),
    //   "needsFlush should return true if there is an animation on the compositor");

    // Flush layout.
    document.documentElement.getBoundingClientRect();

    ok(!utils.needsFlush(SpecialPowers.Ci.nsIDOMWindowUtils.FLUSH_STYLE),
        "needsFlush should return false after flushing layout");

    div.remove();
    next();
    window.close();
  });
}

window.addEventListener("load", test_getUnanimatedComputedStyle);

</script>
</body>
</html>
