Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/animation/test/mozilla/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 83 kB image not shown  

Quelle  file_restyles.html   Sprache: HTML

 
 products/Sources/formale Sprachen/C/Firefox/dom/animation/test/mozilla/file_restyles.html


<!doctype html>
<head>
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<meta charset=utf-8>
<title>Tests restyles caused by animations</title>
<script>
const ok = opener.ok.bind(opener);
const is = opener.is.bind(opener);
const todo = opener.todo.bind(opener);
const todo_is = opener.todo_is.bind(opener);
const info = opener.info.bind(opener);
const original_finish = opener.SimpleTest.finish;
const SimpleTest = opener.SimpleTest;
const add_task = opener.add_task;
SimpleTest.finish = function finish() {
  self.close();
  original_finish();
}
</script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script src="/tests/SimpleTest/paint_listener.js"></script>
<script src="../testcommon.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
<style>
@keyframes background-position {
  0% {
    background-position: -25px center;
  }

  40%,
  100% {
    background-position: 36px center;
  }
}
@keyframes opacity {
  from { opacity: 1; }
  to { opacity: 0; }
}
@keyframes opacity-from-zero {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes opacity-without-end-value {
  from { opacity: 0; }
}
@keyframes on-main-thread {
  from { z-index: 0; }
  to { z-index: 999; }
}
@keyframes rotate {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
@keyframes move-in {
  from { transform: translate(120%, 120%); }
  to { transform: translate(0%, 0%); }
}
@keyframes background-color {
  from { background-color: rgb(255, 0, 0,); }
  to { background-color: rgb(0, 255, 0,); }
}
div {
  /* Element needs geometry to be eligible for layerization */
  width: 100px;
  height: 100px;
  background-color: white;
}
progress:not(.stop)::-moz-progress-bar {
  animation: on-main-thread 100s;
}
body {
  /*
   * set overflow:hidden to avoid accidentally unthrottling animations to update
   * the overflow region.
   */
  overflow: hidden;
}
</style>
</head>
<body>
<script>
'use strict';

// Returns observed animation restyle markers when |funcToMakeRestyleHappen|
// is called.
// NOTE: This function is synchronous version of the above observeStyling().
// Unlike the above observeStyling, this function takes a callback function,
// |funcToMakeRestyleHappen|, which may be expected to trigger a synchronous
// restyles, and returns any restyle markers produced by calling that function.
function observeAnimSyncStyling(funcToMakeRestyleHappen) {

  let priorAnimationTriggeredRestyles = SpecialPowers.DOMWindowUtils.animationTriggeredRestyles;

  funcToMakeRestyleHappen();

  const restyleCount = SpecialPowers.DOMWindowUtils.animationTriggeredRestyles - priorAnimationTriggeredRestyles;

  return restyleCount;
}

function ensureElementRemoval(aElement) {
  return new Promise(resolve => {
    aElement.remove();
    waitForAllPaintsFlushed(resolve);
  });
}

function waitForWheelEvent(aTarget) {
  return new Promise(resolve => {
    // Get the scrollable target element position in this window coordinate
    // system to send a wheel event to the element.
    const targetRect = aTarget.getBoundingClientRect();
    const centerX = targetRect.left + targetRect.width / 2;
    const centerY = targetRect.top + targetRect.height / 2;

    sendWheelAndPaintNoFlush(aTarget, centerX, centerY,
                             { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
                               deltaY: targetRect.height },
                             resolve);
  });
}

const omtaEnabled = isOMTAEnabled();

function add_task_if_omta_enabled(test) {
  if (!omtaEnabled) {
    info(test.name + " is skipped because OMTA is disabled");
    return;
  }
  add_task(test);
}

async function estimateVsyncRate() {
  await waitForNextFrame();

  const timeAtStart = document.timeline.currentTime;
  await waitForAnimationFrames(5);
  return (document.timeline.currentTime - timeAtStart) / 5;
}

// We need to wait for all paints before running tests to avoid contaminations
// from styling of this document itself.
waitForAllPaints(async () => {
  const vsyncRate = await estimateVsyncRate();
  // In this test we basically observe restyling counts in 5 frames, if it
  // takes over 200ms during the 5 frames, this test will fail. So
  // "200ms / 5 = 40ms" is a threshold whether the test works as expected or
  // not. We'd take 5ms additional tolerance here.
  // Note that the 200ms is a period we unthrottle throttled animations that
  // at least one of the animating styles produces change hints causing
  // overflow, the value is defined in
  // KeyframeEffect::OverflowRegionRefreshInterval.
  if (vsyncRate > 40 - 5) {
    ok(true, `the vsync rate ${vsyncRate} on this machine is too slow to run this test`);
    SimpleTest.finish();
    return;
  }

  add_task(async function restyling_for_main_thread_animations() {
    const div = addDiv(null, { style'animation: on-main-thread 100s' });
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);

    ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);

    const restyleCount = await observeStyling(5);
    is(restyleCount, 5,
       'CSS animations running on the main-thread should update style ' +
       'on the main thread');
    await ensureElementRemoval(div);
  });

  add_task(async function restyling_for_main_thread_animations_progress_bar_pseudo() {
    const progress = document.createElement("progress");
    document.body.appendChild(progress);

    await waitForNextFrame();
    await waitForNextFrame();

    let restyleCount;
    restyleCount = await observeStyling(5);
    // TODO(bug 1784931): Figure out why we only see four markers sometimes.
    // That's not the point of this test tho.
    let maybe_todo_is = restyleCount == 4 ? todo_is : is;
    maybe_todo_is(restyleCount, 5,
       'CSS animations running on the main-thread should update style ' +
       'on the main thread on ::-moz-progress-bar');
    progress.classList.add("stop");
    await waitForNextFrame();
    await waitForNextFrame();

    restyleCount = await observeStyling(5);
    is(restyleCount, 0, 'Animation is correctly removed');
    await ensureElementRemoval(progress);
  });

  add_task_if_omta_enabled(async function no_restyling_for_compositor_animations() {
    const div = addDiv(null, { style'animation: opacity 100s' });
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);
    ok(SpecialPowers.wrap(animation).isRunningOnCompositor);

    const restyleCount = await observeStyling(5);
    is(restyleCount, 0,
       'CSS animations running on the compositor should not update style ' +
       'on the main thread');
    await ensureElementRemoval(div);
  });

  add_task_if_omta_enabled(async function no_restyling_for_compositor_transitions() {
    const div = addDiv(null, { style'transition: opacity 100s; opacity: 0' });
    getComputedStyle(div).opacity;
    div.style.opacity = 1;

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

    await waitForAnimationReadyToRestyle(animation);
    ok(SpecialPowers.wrap(animation).isRunningOnCompositor);

    const restyleCount = await observeStyling(5);
    is(restyleCount, 0,
       'CSS transitions running on the compositor should not update style ' +
       'on the main thread');
    await ensureElementRemoval(div);
  });

  add_task_if_omta_enabled(async function no_restyling_when_animation_duration_is_changed() {
    const div = addDiv(null, { style'animation: opacity 100s' });
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);
    ok(SpecialPowers.wrap(animation).isRunningOnCompositor);

    div.animationDuration = '200s';

    const restyleCount = await observeStyling(5);
    is(restyleCount, 0,
       'Animations running on the compositor should not update style ' +
       'on the main thread');
    await ensureElementRemoval(div);
  });

  add_task_if_omta_enabled(async function only_one_restyling_after_finish_is_called() {
    const div = addDiv(null, { style'animation: opacity 100s' });
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);
    ok(SpecialPowers.wrap(animation).isRunningOnCompositor);

    animation.finish();

    let restyleCount;
    restyleCount = await observeStyling(1);
    is(restyleCount, 1,
       'Animations running on the compositor should only update style once ' +
       'after finish() is called');

    restyleCount = await observeStyling(1);
    todo_is(restyleCount, 0,
            'Bug 1415457: Animations running on the compositor should only ' +
            'update style once after finish() is called');

    restyleCount = await observeStyling(5);
    is(restyleCount, 0,
       'Finished animations should never update style after one ' +
       'restyle happened for finish()');

    await ensureElementRemoval(div);
  });

  add_task(async function no_restyling_mouse_movement_on_finished_transition() {
    const div = addDiv(null, { style'transition: opacity 1ms; opacity: 0' });
    getComputedStyle(div).opacity;
    div.style.opacity = 1;

    const animation = div.getAnimations()[0];
    const initialRect = div.getBoundingClientRect();

    await animation.finished;
    let restyleCount;
    restyleCount = await observeStyling(1);
    is(restyleCount, 1,
       'Finished transitions should restyle once after Animation.finished ' +
       'was fulfilled');

    let mouseX = initialRect.left + initialRect.width / 2;
    let mouseY = initialRect.top + initialRect.height / 2;
    restyleCount = await observeStyling(5, () => {
      // We can't use synthesizeMouse here since synthesizeMouse causes
      // layout flush.
      synthesizeMouseAtPoint(mouseX++, mouseY++,
                             { type: 'mousemove' }, window);
    });

    is(restyleCount, 0,
       'Finished transitions should never cause restyles when mouse is moved ' +
       'on the transitions');
    await ensureElementRemoval(div);
  });

  add_task(async function no_restyling_mouse_movement_on_finished_animation() {
    const div = addDiv(null, { style'animation: opacity 1ms' });
    const animation = div.getAnimations()[0];

    const initialRect = div.getBoundingClientRect();

    await animation.finished;
    let restyleCount;
    restyleCount = await observeStyling(1);
    is(restyleCount, 1,
       'Finished animations should restyle once after Animation.finished ' +
       'was fulfilled');

    let mouseX = initialRect.left + initialRect.width / 2;
    let mouseY = initialRect.top + initialRect.height / 2;
    restyleCount = await observeStyling(5, () => {
      // We can't use synthesizeMouse here since synthesizeMouse causes
      // layout flush.
      synthesizeMouseAtPoint(mouseX++, mouseY++,
                             { type: 'mousemove' }, window);
    });

    is(restyleCount, 0,
       'Finished animations should never cause restyles when mouse is moved ' +
       'on the animations');
    await ensureElementRemoval(div);
  });

  add_task_if_omta_enabled(async function no_restyling_compositor_animations_out_of_view_element() {
    const div = addDiv(null,
      { style'animation: opacity 100s; transform: translateY(-400px);' });
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);
    ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);

    const restyleCount = await observeStyling(5);

    is(restyleCount, 0,
       'Animations running on the compositor in an out-of-view element ' +
       'should never cause restyles');
    await ensureElementRemoval(div);
  });

  add_task(async function no_restyling_main_thread_animations_out_of_view_element() {
    const div = addDiv(null,
      { style'animation: on-main-thread 100s; transform: translateY(-400px);' });
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);
    const restyleCount = await observeStyling(5);

    is(restyleCount, 0,
       'Animations running on the main-thread in an out-of-view element ' +
       'should never cause restyles');
    await ensureElementRemoval(div);
  });

  add_task_if_omta_enabled(async function no_restyling_compositor_animations_in_scrolled_out_element() {
    const parentElement = addDiv(null,
      { style'overflow-y: scroll; height: 20px;' });
    const div = addDiv(null,
      { style'animation: opacity 100s; position: relative; top: 100px;' });
    parentElement.appendChild(div);
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);

    const restyleCount = await observeStyling(5);

    is(restyleCount, 0,
       'Animations running on the compositor for elements ' +
       'which are scrolled out should never cause restyles');

    await ensureElementRemoval(parentElement);
  });

  add_task(
    async function no_restyling_missing_keyframe_opacity_animations_on_scrolled_out_element() {
      const parentElement = addDiv(null,
        { style'overflow-y: scroll; height: 20px;' });
      const div = addDiv(null,
        { style'animation: opacity-without-end-value 100s; ' +
                 'position: relative; top: 100px;' });
      parentElement.appendChild(div);
      const animation = div.getAnimations()[0];
      await waitForAnimationReadyToRestyle(animation);

      const restyleCount = await observeStyling(5);

      is(restyleCount, 0,
         'Opacity animations on scrolled out elements should never cause ' +
         'restyles even if the animation has missing keyframes');

      await ensureElementRemoval(parentElement);
    }
  );

  add_task(
    async function restyling_transform_animations_in_scrolled_out_element() {
      // Make sure we start from the state right after requestAnimationFrame.
      await waitForFrame();

      const parentElement = addDiv(null,
        { style'overflow-y: scroll; height: 20px;' });
      const div = addDiv(null,
        { style'animation: rotate 100s infinite; position: relative; top: 100px;' });
      parentElement.appendChild(div);
      const animation = div.getAnimations()[0];
      let timeAtStart = document.timeline.currentTime;

      ok(!animation.isRunningOnCompositor,
         'The transform animation is not running on the compositor');

      let restyleCount
      let now;
      let elapsed;
      while (true) {
        now = document.timeline.currentTime;
        elapsed = (now - timeAtStart);
        restyleCount = await observeStyling(1);
        if (restyleCount) {
          break;
        }
      }
      // If the current time has elapsed over 200ms since the animation was
      // created, it means that the animation should have already
      // unthrottled in this tick, let's see what we observe in this tick's
      // restyling process.
      // We use toPrecision here and below so 199.99999999999977 will turn into 200.
      ok(elapsed.toPrecision(10) >= 200,
           'Transform animation running on the element which is scrolled out ' +
           'should be throttled until 200ms is elapsed. now: ' +
            now + ' start time: ' + timeAtStart + ' elapsed:' + elapsed);

      timeAtStart = document.timeline.currentTime;
      restyleCount = await observeStyling(1);
      now = document.timeline.currentTime;
      elapsed = (now - timeAtStart);

      let expectedMarkersLengthValid;
      // On the fence of 200 ms, we probably have 1 marker; but if we hit a bad rounding
      // we might still have 0. But if it's > 200, we should have 1; and less we should have 0.
      if (elapsed.toPrecision(10) == 200)
        expectedMarkersLengthValid = restyleCount < 2;
      else if (elapsed.toPrecision(10) > 200)
        expectedMarkersLengthValid = restyleCount == 1;
      else
        expectedMarkersLengthValid = !restyleCount;
      ok(expectedMarkersLengthValid,
         'Transform animation running on the element which is scrolled out ' +
         'should be unthrottled after around 200ms have elapsed. now: ' +
         now + ' start time: ' + timeAtStart + ' elapsed: ' + elapsed);

      await ensureElementRemoval(parentElement);
    }
  );

  add_task(
    async function restyling_out_of_view_transform_animations_in_another_element() {
      // Make sure we start from the state right after requestAnimationFrame.
      await waitForFrame();

      const parentElement = addDiv(null,
        { style'overflow: hidden;' });
      const div = addDiv(null,
        { style'animation: move-in 100s infinite;' });
      parentElement.appendChild(div);
      const animation = div.getAnimations()[0];
      let timeAtStart = document.timeline.currentTime;

      ok(!animation.isRunningOnCompositor,
         'The transform animation on out of view element ' +
         'is not running on the compositor');

      // Structure copied from restyling_transform_animations_in_scrolled_out_element
      let restyleCount
      let now;
      let elapsed;
      while (true) {
        now = document.timeline.currentTime;
        elapsed = (now - timeAtStart);
        restyleCount = await observeStyling(1);
        if (restyleCount) {
          break;
        }
      }

      ok(elapsed.toPrecision(10) >= 200,
         'Transform animation running on out of view element ' +
         'should be throttled until 200ms is elapsed. now: ' +
            now + ' start time: ' + timeAtStart + ' elapsed:' + elapsed);

      timeAtStart = document.timeline.currentTime;
      restyleCount = await observeStyling(1);
      now = document.timeline.currentTime;
      elapsed = (now - timeAtStart);

      let expectedMarkersLengthValid;
      // On the fence of 200 ms, we probably have 1 marker; but if we hit a bad rounding
      // we might still have 0. But if it's > 200, we should have 1; and less we should have 0.
      if (elapsed.toPrecision(10) == 200)
        expectedMarkersLengthValid = restyleCount < 2;
      else if (elapsed.toPrecision(10) > 200)
        expectedMarkersLengthValid = restyleCount == 1;
      else
        expectedMarkersLengthValid = !restyleCount;
      ok(expectedMarkersLengthValid,
         'Transform animation running on out of view element ' +
         'should be unthrottled after around 200ms have elapsed. now: ' +
         now + ' start time: ' + timeAtStart +  ' elapsed: ' + elapsed);

      await ensureElementRemoval(parentElement);
    }
  );

  add_task(async function finite_transform_animations_in_out_of_view_element() {
    const parentElement = addDiv(null, { style'overflow: hidden;' });
    const div = addDiv(null);
    const animation =
      div.animate({ transform: [ 'translateX(120%)''translateX(100%)' ] },
                                // This animation will move a bit but
                                // will remain out-of-view.
                  100 * MS_PER_SEC);
    parentElement.appendChild(div);

    await waitForAnimationReadyToRestyle(animation);
    ok(!SpecialPowers.wrap(animation).isRunningOnCompositor, "Should not be running in compositor");

    const restyleCount = await observeStyling(20);
    is(restyleCount, 20,
       'Finite transform animation in out-of-view element should never be ' +
       'throttled');

    await ensureElementRemoval(parentElement);
  });

  add_task(async function restyling_main_thread_animations_in_scrolled_out_element() {
    const parentElement = addDiv(null,
      { style'overflow-y: scroll; height: 20px;' });
    const div = addDiv(null,
      { style'animation: on-main-thread 100s; position: relative; top: 20px;' });
    parentElement.appendChild(div);
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);
    let restyleCount;
    restyleCount = await observeStyling(5);

    is(restyleCount, 0,
       'Animations running on the main-thread for elements ' +
       'which are scrolled out should never cause restyles');

    await waitForWheelEvent(parentElement);

    // Make sure we are ready to restyle before counting restyles.
    await waitForFrame();

    restyleCount = await observeStyling(5);
    is(restyleCount, 5,
       'Animations running on the main-thread which were in scrolled out ' +
       'elements should update restyling soon after the element moved in ' +
       'view by scrolling');

    await ensureElementRemoval(parentElement);
  });

  add_task(async function restyling_main_thread_animations_in_nested_scrolled_out_element() {
    const grandParent = addDiv(null,
      { style'overflow-y: scroll; height: 20px;' });
    const parentElement = addDiv(null,
      { style'overflow-y: scroll; height: 100px;' });
    const div = addDiv(null,
      { style'animation: on-main-thread 100s; ' +
               'position: relative; ' +
               'top: 20px;' }); // This element is in-view in the parent, but
                                // out of view in the grandparent.
    grandParent.appendChild(parentElement);
    parentElement.appendChild(div);
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);
    let restyleCount;
    restyleCount = await observeStyling(5);

    is(restyleCount, 0,
       'Animations running on the main-thread which are in nested elements ' +
       'which are scrolled out should never cause restyles');

    await waitForWheelEvent(grandParent);

    await waitForFrame();

    restyleCount = await observeStyling(5);
    is(restyleCount, 5,
       'Animations running on the main-thread which were in nested scrolled ' +
       'out elements should update restyle soon after the element moved ' +
       'in view by scrolling');

    await ensureElementRemoval(grandParent);
  });

  add_task_if_omta_enabled(async function no_restyling_compositor_animations_in_visibility_hidden_element() {
    const div = addDiv(null,
     { style'animation: opacity 100s; visibility: hidden' });
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);
    ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);

    const restyleCount = await observeStyling(5);

    is(restyleCount, 0,
       'Animations running on the compositor in visibility hidden element ' +
       'should never cause restyles');
    await ensureElementRemoval(div);
  });

  add_task(async function restyling_main_thread_animations_move_out_of_view_by_scrolling() {
    const parentElement = addDiv(null,
      { style'overflow-y: scroll; height: 200px;' });
    const div = addDiv(null,
      { style'animation: on-main-thread 100s;' });
    const pad = addDiv(null,
      { style'height: 400px;' });
    parentElement.appendChild(div);
    parentElement.appendChild(pad);
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);

    await waitForWheelEvent(parentElement);

    await waitForFrame();

    const restyleCount = await observeStyling(5);

    // FIXME: We should reduce a redundant restyle here.
    ok(restyleCount >= 0,
       'Animations running on the main-thread which are in scrolled out ' +
       'elements should throttle restyling');

    await ensureElementRemoval(parentElement);
  });

  add_task(async function restyling_main_thread_animations_moved_in_view_by_resizing() {
    const parentElement = addDiv(null,
      { style'overflow-y: scroll; height: 20px;' });
    const div = addDiv(null,
      { style'animation: on-main-thread 100s; position: relative; top: 100px;' });
    parentElement.appendChild(div);
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);

    let restyleCount;
    restyleCount = await observeStyling(5);
    is(restyleCount, 0,
       'Animations running on the main-thread which is in scrolled out ' +
       'elements should not update restyling');

    parentElement.style.height = '100px';
    restyleCount = await observeStyling(1);

    is(restyleCount, 1,
       'Animations running on the main-thread which was in scrolled out ' +
       'elements should update restyling soon after the element moved in ' +
       'view by resizing');

    await ensureElementRemoval(parentElement);
  });

  add_task(
    async function restyling_animations_on_visibility_changed_element_having_child() {
      const div = addDiv(null,
       { style'animation: on-main-thread 100s;' });
      const childElement = addDiv(null);
      div.appendChild(childElement);

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

      await waitForAnimationReadyToRestyle(animation);

      // We don't check the animation causes restyles here since we already
      // check it in the first test case.

      div.style.visibility = 'hidden';
      await waitForNextFrame();

      const restyleCount = await observeStyling(5);
      todo_is(restyleCount, 0,
              'Animations running on visibility hidden element which ' +
              'has a child whose visiblity is inherited from the element and ' +
              'the element was initially visible');

      await ensureElementRemoval(div);
    }
  );

  add_task(
    async function restyling_animations_on_visibility_hidden_element_which_gets_visible() {
      const div = addDiv(null,
       { style'animation: on-main-thread 100s; visibility: hidden' });
      const animation = div.getAnimations()[0];


      await waitForAnimationReadyToRestyle(animation);
      let restyleCount;
      restyleCount = await observeStyling(5);

      is(restyleCount, 0,
         'Animations running on visibility hidden element should never ' +
         'cause restyles');

      div.style.visibility = 'visible';
      await waitForNextFrame();

       restyleCount = await observeStyling(5);
       is(restyleCount, 5,
         'Animations running that was on visibility hidden element which ' +
         'gets visible should not throttle restyling any more');

      await ensureElementRemoval(div);
    }
  );

  add_task(async function restyling_animations_in_visibility_changed_parent() {
    const parentDiv = addDiv(null, { style'visibility: hidden' });
    const div = addDiv(null, { style'animation: on-main-thread 100s;' });
    parentDiv.appendChild(div);

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

    await waitForAnimationReadyToRestyle(animation);
    let restyleCount;
    restyleCount = await observeStyling(5);

    is(restyleCount, 0,
       'Animations running in visibility hidden parent should never cause ' +
       'restyles');

    parentDiv.style.visibility = 'visible';
    await waitForNextFrame();

    restyleCount = await observeStyling(5);
    is(restyleCount, 5,
       'Animations that was in visibility hidden parent should not ' +
       'throttle restyling any more');

    parentDiv.style.visibility = 'hidden';
    await waitForNextFrame();

    restyleCount = await observeStyling(5);
    is(restyleCount, 0,
       'Animations that the parent element became visible should throttle ' +
       'restyling again');

    await ensureElementRemoval(parentDiv);
  });

  add_task(
    async function restyling_animations_on_visibility_hidden_element_with_visibility_changed_children() {
      const div = addDiv(null,
       { style'animation: on-main-thread 100s; visibility: hidden' });
      const animation = div.getAnimations()[0];

      await waitForAnimationReadyToRestyle(animation);
      let restyleCount;
      restyleCount = await observeStyling(5);

      is(restyleCount, 0,
         'Animations on visibility hidden element having no visible children ' +
         'should never cause restyles');

      const childElement = addDiv(null, { style'visibility: visible' });
      div.appendChild(childElement);
      await waitForNextFrame();

      restyleCount = await observeStyling(5);
      is(restyleCount, 5,
         'Animations running on visibility hidden element but the element has ' +
         'a visible child should not throttle restyling');

      childElement.style.visibility = 'hidden';
      await waitForNextFrame();

      restyleCount = await observeStyling(5);
      todo_is(restyleCount, 0,
              'Animations running on visibility hidden element that a child ' +
              'has become invisible should throttle restyling');

      childElement.style.visibility = 'visible';
      await waitForNextFrame();

      restyleCount = await observeStyling(5);
      is(restyleCount, 5,
         'Animations running on visibility hidden element should not throttle ' +
         'restyling after the invisible element changed to visible');

      childElement.remove();
      await waitForNextFrame();

      restyleCount = await observeStyling(5);
      todo_is(restyleCount, 0,
              'Animations running on visibility hidden element should throttle ' +
              'restyling again after all visible descendants were removed');

      await ensureElementRemoval(div);
    }
  );

  add_task(
    async function restyling_animations_on_visiblity_hidden_element_having_oof_child() {
      const div = addDiv(null,
        { style'animation: on-main-thread 100s; position: absolute' });
      const childElement = addDiv(null,
        { style'float: left; visibility: hidden' });
      div.appendChild(childElement);

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

      await waitForAnimationReadyToRestyle(animation);

      // We don't check the animation causes restyles here since we already
      // check it in the first test case.

      div.style.visibility = 'hidden';
      await waitForNextFrame();

      const restyleCount = await observeStyling(5);
      is(restyleCount, 0,
         'Animations running on visibility hidden element which has an ' +
         'out-of-flow child should throttle restyling');

      await ensureElementRemoval(div);
    }
  );

  add_task(
    async function restyling_animations_on_visibility_hidden_element_having_grandchild() {
      // element tree:
      //
      //        root(visibility:hidden)
      //       /                       \
      //    childA                   childB
      //    /     \                 /      \
      //  AA       AB             BA        BB

      const div = addDiv(null,
       { style'animation: on-main-thread 100s; visibility: hidden' });

      const childA = addDiv(null);
      div.appendChild(childA);
      const childB = addDiv(null);
      div.appendChild(childB);

      const grandchildAA = addDiv(null);
      childA.appendChild(grandchildAA);
      const grandchildAB = addDiv(null);
      childA.appendChild(grandchildAB);

      const grandchildBA = addDiv(null);
      childB.appendChild(grandchildBA);
      const grandchildBB = addDiv(null);
      childB.appendChild(grandchildBB);

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

      await waitForAnimationReadyToRestyle(animation);
      let restyleCount;
      restyleCount = await observeStyling(5);
      is(restyleCount, 0,
         'Animations on visibility hidden element having no visible ' +
         'descendants should never cause restyles');

      childA.style.visibility = 'visible';
      grandchildAA.style.visibility = 'visible';
      grandchildAB.style.visibility = 'visible';
      await waitForNextFrame();

      restyleCount = await observeStyling(5);
      is(restyleCount, 5,
         'Animations running on visibility hidden element but the element has ' +
         'visible children should not throttle restyling');

      // Make childA hidden again but both of grandchildAA and grandchildAB are
      // still visible.
      childA.style.visibility = 'hidden';
      await waitForNextFrame();

      restyleCount = await observeStyling(5);
      is(restyleCount, 5,
         'Animations running on visibility hidden element that a child has ' +
         'become invisible again but there are still visible children should ' +
         'not throttle restyling');

      // Make grandchildAA hidden but grandchildAB is still visible.
      grandchildAA.style.visibility = 'hidden';
      await waitForNextFrame();

      restyleCount = await observeStyling(5);
      is(restyleCount, 5,
         'Animations running on visibility hidden element that a grandchild ' +
         'become invisible again but another grandchild is still visible ' +
         'should not throttle restyling');


      // Make childB and grandchildBA visible.
      childB.style.visibility = 'visible';
      grandchildBA.style.visibility = 'visible';
      await waitForNextFrame();

      restyleCount = await observeStyling(5);
      is(restyleCount, 5,
         'Animations running on visibility hidden element but the element has ' +
         'visible descendants should not throttle restyling');

      // Make childB hidden but grandchildAB and grandchildBA are still visible.
      childB.style.visibility = 'hidden';
      await waitForNextFrame();

      restyleCount = await observeStyling(5);
      is(restyleCount, 5,
         'Animations running on visibility hidden element but the element has ' +
         'visible grandchildren should not throttle restyling');

      // Make grandchildAB hidden but grandchildBA is still visible.
      grandchildAB.style.visibility = 'hidden';
      await waitForNextFrame();

      restyleCount = await observeStyling(5);
      is(restyleCount, 5,
         'Animations running on visibility hidden element but the element has ' +
         'a visible grandchild should not throttle restyling');

      // Make grandchildBA hidden. Now all descedants are invisible.
      grandchildBA.style.visibility = 'hidden';
      await waitForNextFrame();

      restyleCount = await observeStyling(5);
      todo_is(restyleCount, 0,
              'Animations on visibility hidden element that all descendants have ' +
              'become invisible again should never cause restyles');

      // Make childB visible.
      childB.style.visibility = 'visible';
      await waitForNextFrame();

      restyleCount = await observeStyling(5);
      is(restyleCount, 5,
         'Animations on visibility hidden element that has a visible child ' +
         'should never cause restyles');

      // Make childB invisible again
      childB.style.visibility = 'hidden';
      await waitForNextFrame();

      restyleCount = await observeStyling(5);
      todo_is(restyleCount, 0,
              'Animations on visibility hidden element that the visible child ' +
              'has become invisible again should never cause restyles');

      await ensureElementRemoval(div);
    }
  );

  add_task_if_omta_enabled(async function no_restyling_compositor_animations_after_pause_is_called() {
    const div = addDiv(null, { style'animation: opacity 100s' });
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);
    ok(SpecialPowers.wrap(animation).isRunningOnCompositor);

    animation.pause();

    await animation.ready;
    let restyleCount = await observeStyling(5);
    is(restyleCount, 0,
       'Paused animations running on the compositor should never cause ' +
       'restyles');
    await ensureElementRemoval(div);
  });

  add_task(async function no_restyling_main_thread_animations_after_pause_is_called() {
    const div = addDiv(null, { style'animation: on-main-thread 100s' });
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);

    animation.pause();

    await animation.ready;
    let restyleCount = await observeStyling(5);
    is(restyleCount, 0,
       'Paused animations running on the main-thread should never cause ' +
       'restyles');
    await ensureElementRemoval(div);
  });

  add_task_if_omta_enabled(async function only_one_restyling_when_current_time_is_set_to_middle_of_duration() {
    const div = addDiv(null, { style'animation: opacity 100s' });
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);

    animation.currentTime = 50 * MS_PER_SEC;

    const restyleCount = await observeStyling(5);
    is(restyleCount, 1,
       'Bug 1235478: Animations running on the compositor should only once ' +
       'update style when currentTime is set to middle of duration time');
    await ensureElementRemoval(div);
  });

  add_task_if_omta_enabled(async function change_duration_and_currenttime() {
    const div = addDiv(null);
    const animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);

    await waitForAnimationReadyToRestyle(animation);
    ok(SpecialPowers.wrap(animation).isRunningOnCompositor);

    // Set currentTime to a time longer than duration.
    animation.currentTime = 500 * MS_PER_SEC;

    // Now the animation immediately get back from compositor.
    ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);

    // Extend the duration.
    animation.effect.updateTiming({ duration: 800 * MS_PER_SEC });
    const restyleCount = await observeStyling(5);
    is(restyleCount, 1,
       'Animations running on the compositor should update style ' +
       'when duration is made longer than the current time');

    await ensureElementRemoval(div);
  });

  add_task(async function script_animation_on_display_none_element() {
    const div = addDiv(null);
    const animation = div.animate({ zIndex: [ '0''999' ] },
                                100 * MS_PER_SEC);

    await waitForAnimationReadyToRestyle(animation);

    div.style.display = 'none';

    // We need to wait a frame to apply display:none style.
    await waitForNextFrame();

    is(animation.playState, 'running',
       'Script animations keep running even when the target element has ' +
       '"display: none" style');

    ok(!SpecialPowers.wrap(animation).isRunningOnCompositor,
       'Script animations on "display:none" element should not run on the ' +
       'compositor');

    let restyleCount;
    restyleCount = await observeStyling(5);
    is(restyleCount, 0,
       'Script animations on "display: none" element should not update styles');

    div.style.display = '';

    restyleCount = await observeStyling(5);
    is(restyleCount, 5,
       'Script animations restored from "display: none" state should update ' +
       'styles');

    await ensureElementRemoval(div);
  });

  add_task_if_omta_enabled(async function compositable_script_animation_on_display_none_element() {
    const div = addDiv(null);
    const animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);

    await waitForAnimationReadyToRestyle(animation);

    div.style.display = 'none';

    // We need to wait a frame to apply display:none style.
    await waitForNextFrame();

    is(animation.playState, 'running',
       'Opacity script animations keep running even when the target element ' +
       'has "display: none" style');

    ok(!SpecialPowers.wrap(animation).isRunningOnCompositor,
       'Opacity script animations on "display:none" element should not ' +
       'run on the compositor');

    let restyleCount;
    restyleCount = await observeStyling(5);
    is(restyleCount, 0,
       'Opacity script animations on "display: none" element should not ' +
       'update styles');

    div.style.display = '';

    restyleCount = await observeStyling(1);
     is(restyleCount, 1,
       'Script animations restored from "display: none" state should update ' +
       'styles soon');

    ok(SpecialPowers.wrap(animation).isRunningOnCompositor,
       'Opacity script animations restored from "display: none" should be ' +
       'run on the compositor in the next frame');

    await ensureElementRemoval(div);
  });

  add_task(async function restyling_for_empty_keyframes() {
    const div = addDiv(null);
    const animation = div.animate({ }, 100 * MS_PER_SEC);

    await waitForAnimationReadyToRestyle(animation);
    let restyleCount;
    restyleCount = await observeStyling(5);

    is(restyleCount, 0,
       'Animations with no keyframes should not cause restyles');

    animation.effect.setKeyframes({ zIndex: ['0''999'] });
    restyleCount = await observeStyling(5);

    is(restyleCount, 5,
       'Setting valid keyframes should cause regular animation restyles to ' +
       'occur');

    animation.effect.setKeyframes({ });
    restyleCount = await observeStyling(5);

    is(restyleCount, 1,
       'Setting an empty set of keyframes should trigger a single restyle ' +
       'to remove the previous animated style');

    await ensureElementRemoval(div);
  });

  add_task_if_omta_enabled(async function no_restyling_when_animation_style_when_re_setting_same_animation_property() {
    const div = addDiv(null, { style'animation: opacity 100s' });
    const animation = div.getAnimations()[0];
    await waitForAnimationReadyToRestyle(animation);
    ok(SpecialPowers.wrap(animation).isRunningOnCompositor);
    // Apply the same animation style
    div.style.animation = 'opacity 100s';
    const restyleCount = await observeStyling(5);
    is(restyleCount, 0,
       'Applying same animation style '  +
       'should never cause restyles');
    await ensureElementRemoval(div);
  });

  add_task(async function necessary_update_should_be_invoked() {
    const div = addDiv(null, { style'animation: on-main-thread 100s' });
    const animation = div.getAnimations()[0];
    await waitForAnimationReadyToRestyle(animation);
    await waitForAnimationFrames(5);
    // Apply another animation style
    div.style.animation = 'on-main-thread 110s';
    const restyleCount = await observeStyling(1);
    // There should be two restyles.
    // 1) Animation-only restyle for before applying the new animation style
    // 2) Animation-only restyle for after applying the new animation style
    is(restyleCount, 2,
       'Applying animation style with different duration '  +
       'should restyle twice');
    await ensureElementRemoval(div);
  });

  add_task_if_omta_enabled(
    async function changing_cascading_result_for_main_thread_animation() {
      const div = addDiv(null, { style'on-main-thread: blue' });
      const animation = div.animate({ opacity: [0, 1],
                                    zIndex: ['0''999'] },
                                  100 * MS_PER_SEC);
      await waitForAnimationReadyToRestyle(animation);
      ok(SpecialPowers.wrap(animation).isRunningOnCompositor,
         'The opacity animation is running on the compositor');
      // Make the z-index style as !important to cause an update
      // to the cascade.
      // Bug 1300982: The z-index animation should be no longer
      // running on the main thread.
      div.style.setProperty('z-index''0''important');
      const restyleCount = await observeStyling(5);
      todo_is(restyleCount, 0,
         'Changing cascading result for the property running on the main ' +
         'thread does not cause synchronization layer of opacity animation ' +
         'running on the compositor');
      await ensureElementRemoval(div);
    }
  );

  add_task_if_omta_enabled(
    async function animation_visibility_and_opacity() {
      const div = addDiv(null);
      const animation1 = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
      const animation2 = div.animate({ visibility: ['hidden''visible'] }, 100 * MS_PER_SEC);
      await waitForAnimationReadyToRestyle(animation1);
      await waitForAnimationReadyToRestyle(animation2);
      const restyleCount = await observeStyling(5);
      is(restyleCount, 5, 'The animation should not be throttled');
      await ensureElementRemoval(div);
    }
  );

  add_task(async function restyling_for_animation_on_orphaned_element() {
    const div = addDiv(null);
    const animation = div.animate({ marginLeft: [ '0px''100px' ] },
                                100 * MS_PER_SEC);

    await waitForAnimationReadyToRestyle(animation);

    div.remove();
    let restyleCount;
    restyleCount = await observeStyling(5);
    is(restyleCount, 0,
       'Animation on orphaned element should not cause restyles');

    document.body.appendChild(div);

    await waitForNextFrame();
    restyleCount = await observeStyling(5);

    is(restyleCount, 5,
       'Animation on re-attached to the document begins to update style, got ' + restyleCount);

    await ensureElementRemoval(div);
  });

  add_task_if_omta_enabled(
    // Tests that if we remove an element from the document whose animation
    // cascade needs recalculating, that it is correctly updated when it is
    // re-attached to the document.
    async function restyling_for_opacity_animation_on_re_attached_element() {
      const div = addDiv(null, { style'opacity: 1 ! important' });
      const animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);

      await waitForAnimationReadyToRestyle(animation);
      ok(!SpecialPowers.wrap(animation).isRunningOnCompositor,
         'The opacity animation overridden by an !important rule is NOT ' +
         'running on the compositor');

      // Drop the !important rule to update the cascade.
      div.style.setProperty('opacity''1''');

      div.remove();

      const restyleCount = await observeStyling(5);
      is(restyleCount, 0,
         'Opacity animation on orphaned element should not cause restyles');

      document.body.appendChild(div);

      // Need a frame to give the animation a chance to be sent to the
      // compositor.
      await waitForNextFrame();

      ok(SpecialPowers.wrap(animation).isRunningOnCompositor,
         'The opacity animation which is no longer overridden by the ' +
         '!important rule begins running on the compositor even if the ' +
         '!important rule had been dropped before the target element was ' +
         'removed');

      await ensureElementRemoval(div);
    }
  );

  add_task(
    async function no_throttling_additive_animations_out_of_view_element() {
      const div = addDiv(null, { style'transform: translateY(-400px);' });
      const animation =
        div.animate([{ visibility: 'visible' }],
                    { duration: 100 * MS_PER_SEC, composite: 'add' });

      await waitForAnimationReadyToRestyle(animation);

      const restyleCount = await observeStyling(5);

      is(restyleCount, 0,
         'Additive animation has no keyframe whose offset is 0 or 1 in an ' +
         'out-of-view element should be throttled');
      await ensureElementRemoval(div);
    }
  );

  // Tests that missing keyframes animations don't throttle at all.
  add_task(async function no_throttling_animations_out_of_view_element() {
    const div = addDiv(null, { style'transform: translateY(-400px);' });
    const animation =
      div.animate([{ visibility: 'visible' }], 100 * MS_PER_SEC);

    await waitForAnimationReadyToRestyle(animation);

    const restyleCount = await observeStyling(5);

    is(restyleCount, 0,
       'Discrete animation has has no keyframe whose offset is 0 or 1 in an ' +
       'out-of-view element should be throttled');
    await ensureElementRemoval(div);
  });

  // Tests that missing keyframes animation on scrolled out element that the
  // animation is not able to be throttled.
  add_task(
    async function no_throttling_missing_keyframe_animations_out_of_view_element() {
      const div =
        addDiv(null, { style'transform: translateY(-400px);' +
                              'visibility: collapse;' });
      const animation =
        div.animate([{ visibility: 'visible' }], 100 * MS_PER_SEC);
      await waitForAnimationReadyToRestyle(animation);

      const restyleCount = await observeStyling(5);
      is(restyleCount, 0,
         'visibility animation has no keyframe whose offset is 0 or 1 in an ' +
         'out-of-view element should be throttled');
      await ensureElementRemoval(div);
    }
  );

  // Counter part of the above test.
  add_task(async function no_restyling_discrete_animations_out_of_view_element() {
    const div = addDiv(null, { style'transform: translateY(-400px);' });
    const animation =
      div.animate({ visibility: ['visible''hidden'] }, 100 * MS_PER_SEC);

    await waitForAnimationReadyToRestyle(animation);

    const restyleCount = await observeStyling(5);

    is(restyleCount, 0,
       'Discrete animation running on the main-thread in an out-of-view ' +
       'element should never cause restyles');
    await ensureElementRemoval(div);
  });

  add_task(async function no_restyling_while_computed_timing_is_not_changed() {
    const div = addDiv(null);
    const animation = div.animate({ zIndex: [ '0''999' ] },
                                  { duration: 100 * MS_PER_SEC,
                                    easing: 'step-end' });

    await waitForAnimationReadyToRestyle(animation);

    const restyleCount = await observeStyling(5);

    // We possibly expect one restyle from the initial animation compose, in
    // order to update animations, but nothing else.
    ok(restyleCount <= 1,
       'Animation running on the main-thread while computed timing is not ' +
       'changed should not cause extra restyles, got ' + restyleCount);
    await ensureElementRemoval(div);
  });

  add_task(async function no_throttling_animations_in_view_svg() {
    const div = addDiv(null, { style'overflow: scroll;' +
                                    'height: 100px; width: 100px;' });
    const svg = addSVGElement(div'svg', { viewBox: '-10 -10 0.1 0.1',
                                          width:   '50px',
                                          height:  '50px' });
    const rect = addSVGElement(svg, 'rect', { x:      '-10',
                                            y:      '-10',
                                            width:  '10',
                                            height: '10',
                                            fill:   'red' });
    const animation = rect.animate({ fill: ['blue''lime'] }, 100 * MS_PER_SEC);
    await waitForAnimationReadyToRestyle(animation);

    const restyleCount = await observeStyling(5);
    is(restyleCount, 5,
       'CSS animations on an in-view svg element with post-transform should ' +
       'not be throttled.');

    await ensureElementRemoval(div);
  });

  add_task_if_omta_enabled(async function svg_non_scaling_stroke_animation() {
    const div = addDiv(null, { style'overflow: scroll;' +
                                    'height: 100px; width: 100px;' });
    const svg = addSVGElement(div'svg', { viewBox: '0 0 250 250',
                                          width:   '40px',
                                          height:  '40px' });
    const rect = addSVGElement(svg, 'rect', { x:    '0',
                                            y:      '0',
                                            width:  '250',
                                            height: '250',
                                            fill:   'red',
                                            style'vector-effect: non-scaling-stroke; animation: rotate 100s infinite;'});
    const animation = rect.getAnimations()[0];
    await waitForAnimationReadyToRestyle(animation);

    ok(!SpecialPowers.wrap(animation).isRunningOnCompositor,
         'The animation of a non-scaling-stroke element is not running on the compositor');

    await ensureElementRemoval(div);
  });

  add_task(async function no_throttling_animations_in_transformed_parent() {
    const div = addDiv(null, { style'overflow: scroll;' +
                                    'transform: translateX(50px);' });
    const svg = addSVGElement(div'svg', { viewBox: '0 0 1250 1250',
                                          width:   '40px',
                                          height:  '40px' });
    const rect = addSVGElement(svg, 'rect', { x:      '0',
                                            y:      '0',
                                            width:  '1250',
                                            height: '1250',
                                            fill:   'red' });
    const animation = rect.animate({ fill: ['blue''lime'] }, 100 * MS_PER_SEC);
    await waitForAnimationReadyToRestyle(animation);

    const restyleCount = await observeStyling(5);
    is(restyleCount, 5,
       'CSS animations on an in-view svg element which is inside transformed ' +
       'parent should not be throttled.');

    await ensureElementRemoval(div);
  });

  add_task(async function throttling_animations_out_of_view_svg() {
    const div = addDiv(null, { style'overflow: scroll;' +
                                    'height: 100px; width: 100px;' });
    const svg = addSVGElement(div'svg', { viewBox: '-10 -10 0.1 0.1',
                                          width: '50px',
                                          height: '50px' });
    const rect = addSVGElement(svg, 'rect', { width: '10',
                                            height: '10',
                                            fill: 'red' });

    const animation = rect.animate({ fill: ['blue''lime'] }, 100 * MS_PER_SEC);
    await waitForAnimationReadyToRestyle(animation);

    const restyleCount = await observeStyling(5);
    is(restyleCount, 0,
       'CSS animations on an out-of-view svg element with post-transform ' +
       'should be throttled.');

    await ensureElementRemoval(div);
  });

  add_task(async function no_throttling_animations_in_view_css_transform() {
    const scrollDiv = addDiv(null, { style'overflow: scroll; ' +
                                          'height: 100px; width: 100px;' });
    const targetDiv = addDiv(null,
                           { style'animation: on-main-thread 100s;' +
                                    'transform: translate(-50px, -50px);' });
    scrollDiv.appendChild(targetDiv);

    const animation = targetDiv.getAnimations()[0];
    await waitForAnimationReadyToRestyle(animation);

    const restyleCount = await observeStyling(5);
    is(restyleCount, 5,
       'CSS animation on an in-view element with pre-transform should not ' +
       'be throttled.');

    await ensureElementRemoval(scrollDiv);
  });

  add_task(async function throttling_animations_out_of_view_css_transform() {
    const scrollDiv = addDiv(null, { style'overflow: scroll;' +
                                          'height: 100px; width: 100px;' });
    const targetDiv = addDiv(null,
                           { style'animation: on-main-thread 100s;' +
                                    'transform: translate(100px, 100px);' });
    scrollDiv.appendChild(targetDiv);

    const animation = targetDiv.getAnimations()[0];
    await waitForAnimationReadyToRestyle(animation);

    const restyleCount = await observeStyling(5);
    is(restyleCount, 0,
       'CSS animation on an out-of-view element with pre-transform should be ' +
       'throttled.');

    await ensureElementRemoval(scrollDiv);
  });

  add_task(
    async function throttling_animations_in_out_of_view_position_absolute_element() {
      const parentDiv = addDiv(null,
                             { style'position: absolute; top: -1000px;' });
      const targetDiv = addDiv(null,
                             { style'animation: on-main-thread 100s;' });
      parentDiv.appendChild(targetDiv);

      const animation = targetDiv.getAnimations()[0];
      await waitForAnimationReadyToRestyle(animation);

      const restyleCount = await observeStyling(5);
      is(restyleCount, 0,
         'CSS animation in an out-of-view position absolute element should ' +
         'be throttled');

      await ensureElementRemoval(parentDiv);
    }
  );

  add_task(
    async function throttling_animations_on_out_of_view_position_absolute_element() {
      const div = addDiv(null,
                       { style'animation: on-main-thread 100s; ' +
                                'position: absolute; top: -1000px;' });

      const animation = div.getAnimations()[0];
      await waitForAnimationReadyToRestyle(animation);

      const restyleCount = await observeStyling(5);
      is(restyleCount, 0,
         'CSS animation on an out-of-view position absolute element should ' +
         'be throttled');

      await ensureElementRemoval(div);
    }
  );

  add_task(
    async function throttling_animations_in_out_of_view_position_fixed_element() {
      const parentDiv = addDiv(null,
                             { style'position: fixed; top: -1000px;' });
      const targetDiv = addDiv(null,
                             { style'animation: on-main-thread 100s;' });
      parentDiv.appendChild(targetDiv);

      const animation = targetDiv.getAnimations()[0];
      await waitForAnimationReadyToRestyle(animation);

      const restyleCount = await observeStyling(5);
      is(restyleCount, 0,
         'CSS animation on an out-of-view position:fixed element should be ' +
         'throttled');

      await ensureElementRemoval(parentDiv);
    }
  );

  add_task(
    async function throttling_animations_on_out_of_view_position_fixed_element() {
      const div = addDiv(null,
                       { style'animation: on-main-thread 100s; ' +
                                'position: fixed; top: -1000px;' });

      const animation = div.getAnimations()[0];
      await waitForAnimationReadyToRestyle(animation);

      const restyleCount = await observeStyling(5);
      is(restyleCount, 0,
         'CSS animation on an out-of-view position:fixed element should be ' +
         'throttled');

      await ensureElementRemoval(div);
    }
  );

  add_task(
    async function no_throttling_animations_in_view_position_fixed_element() {
      const iframe = document.createElement('iframe');
      iframe.setAttribute('srcdoc''
'
);
      document.documentElement.appendChild(iframe);

      await new Promise(resolve => {
        iframe.addEventListener('load', () => {
          resolve();
        });
      });

      const target = iframe.contentDocument.getElementById('target');
      target.style'position: fixed; top: 20px; width: 100px; height: 100px;';

      const animation = target.animate({ zIndex: [ '0''999' ] },
                                       { duration: 100 * MS_PER_SEC,
                                         iterations: Infinity });
      await waitForAnimationReadyToRestyle(animation);

      const restyleCount = await observeStylingInTargetWindow(iframe.contentWindow, 5);
      is(restyleCount, 5,
         'CSS animation on an in-view position:fixed element should NOT be ' +
         'throttled');

      await ensureElementRemoval(iframe);
    }
  );

  add_task(
    async function throttling_position_absolute_animations_in_collapsed_iframe() {
      const iframe = document.createElement('iframe');
      iframe.setAttribute('srcdoc''
'
);
      iframe.style.height = '0px';
      document.documentElement.appendChild(iframe);

      await new Promise(resolve => {
        iframe.addEventListener('load', () => {
          resolve();
        });
      });

      const target = iframe.contentDocument.getElementById("target");
      target.style'position: absolute; top: 50%; width: 100px; height: 100px';

      const animation = target.animate({ opacity: [0, 1] },
                                     { duration: 100 * MS_PER_SEC,
                                       iterations: Infinity });
      await waitForAnimationReadyToRestyle(animation);

      const restyleCount = await observeStylingInTargetWindow(iframe.contentWindow, 5);
      is(restyleCount, 0,
         'Animation on position:absolute element in collapsed iframe should ' +
         'be throttled');

      await ensureElementRemoval(iframe);
    }
  );

  add_task(
    async function position_absolute_animations_in_collapsed_element() {
      const parent = addDiv(null, { style'overflow: scroll; height: 0px;' });
      const target = addDiv(null,
                          { style'animation: on-main-thread 100s infinite;' +
                                   'position: absolute; top: 50%;' +
                                   'width: 100px; height: 100px;' });
      parent.appendChild(target);

      const animation = target.getAnimations()[0];
      await waitForAnimationReadyToRestyle(animation);

      const restyleCount = await observeStyling(5);
      is(restyleCount, 5,
         'Animation on position:absolute element in collapsed element ' +
         'should not be throttled');

      await ensureElementRemoval(parent);
    }
  );

  add_task(
    async function throttling_position_absolute_animations_in_collapsed_element() {
      const parent = addDiv(null, { style'overflow: scroll; height: 0px;' });
      const target = addDiv(null,
                          { style'animation: on-main-thread 100s infinite;' +
                                   'position: absolute; top: 50%;' });
      parent.appendChild(target);

      const animation = target.getAnimations()[0];
      await waitForAnimationReadyToRestyle(animation);

      const restyleCount = await observeStyling(5);
      todo_is(restyleCount, 0,
              'Animation on collapsed position:absolute element in collapsed ' +
              'element should be throttled');

      await ensureElementRemoval(parent);
    }
  );

  add_task_if_omta_enabled(
    async function no_restyling_for_compositor_animation_on_unrelated_style_change() {
      const div = addDiv(null);
      const animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);

      await waitForAnimationReadyToRestyle(animation);
      ok(SpecialPowers.wrap(animation).isRunningOnCompositor,
         'The opacity animation is running on the compositor');

      div.style.setProperty('color''blue''');
      const restyleCount = await observeStyling(5);
      is(restyleCount, 0,
         'The opacity animation keeps running on the compositor when ' +
         'color style is changed');
      await ensureElementRemoval(div);
    }
  );

  add_task(
    async function no_overflow_transform_animations_in_scrollable_element() {
      const parentElement = addDiv(null,
        { style'overflow-y: scroll; height: 100px;' });
      const div = addDiv(null);
      const animation =
        div.animate({ transform: [ 'translateY(10px)''translateY(10px)' ] },
                    100 * MS_PER_SEC);
      parentElement.appendChild(div);

      await waitForAnimationReadyToRestyle(animation);
      ok(SpecialPowers.wrap(animation).isRunningOnCompositor);

      const restyleCount = await observeStyling(20);
      is(restyleCount, 0,
         'No-overflow transform animations running on the compositor should ' +
         'never update style on the main thread');

      await ensureElementRemoval(parentElement);
    }
  );

  add_task(async function no_flush_on_getAnimations() {
    const div = addDiv(null);
    const animation =
      div.animate({ opacity: [ '0''1' ] }, 100 * MS_PER_SEC);
    await waitForAnimationReadyToRestyle(animation);

    ok(SpecialPowers.wrap(animation).isRunningOnCompositor);

    const restyleCount = observeAnimSyncStyling(() => {
      is(div.getAnimations().length, 1, 'There should be one animation');
    });
    is(restyleCount, 0,
       'Element.getAnimations() should not flush throttled animation style');

    await ensureElementRemoval(div);
  });

  add_task(async function restyling_for_throttled_animation_on_getAnimations() {
    const div = addDiv(null, { style'animation: opacity 100s' });
    const animation = div.getAnimations()[0];

    await waitForAnimationReadyToRestyle(animation);
    ok(SpecialPowers.wrap(animation).isRunningOnCompositor);

    const restyleCount = observeAnimSyncStyling(() => {
      div.style.animationDuration = '0s';
      is(div.getAnimations().length, 0, 'There should be no animation');
    });

    is(restyleCount, 1, // For discarding the throttled animation.
       'Element.getAnimations() should flush throttled animation style so ' +
       'that the throttled animation is discarded');

    await ensureElementRemoval(div);
  });

  add_task(
    async function no_restyling_for_throttled_animation_on_querying_play_state() {
      const div = addDiv(null, { style'animation: opacity 100s' });
      const animation = div.getAnimations()[0];
      const sibling = addDiv(null);

      await waitForAnimationReadyToRestyle(animation);
      ok(SpecialPowers.wrap(animation).isRunningOnCompositor);

      const restyleCount = observeAnimSyncStyling(() => {
        sibling.style.opacity = '0.5';
        is(animation.playState, 'running',
           'Animation.playState should be running');
      });
      is(restyleCount, 0,
         'Animation.playState should not flush throttled animation in the ' +
         'case where there are only style changes that don\'t affect the ' +
         'throttled animation');

      await ensureElementRemoval(div);
      await ensureElementRemoval(sibling);
    }
  );

  add_task(
    async function restyling_for_throttled_animation_on_querying_play_state() {
      const div = addDiv(null, { style'animation: opacity 100s' });
      const animation = div.getAnimations()[0];

      await waitForAnimationReadyToRestyle(animation);
      ok(SpecialPowers.wrap(animation).isRunningOnCompositor);

      const restyleCount = observeAnimSyncStyling(() => {
        div.style.animationPlayState = 'paused';
        is(animation.playState, 'paused',
           'Animation.playState should be reflected by pending style');
      });

      is(restyleCount, 1,
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=98 H=97 G=97

¤ Dauer der Verarbeitung: 0.15 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.