Source code
Revision control
Copy as Markdown
Other Tools
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Test for scrolled out of view animation optimization in an OOPIF</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <script src="/tests/SimpleTest/paint_listener.js"></script>
  <script src="helper_fission_utils.js"></script>
  <script src="apz_test_native_event_utils.js"></script>
  <script src="apz_test_utils.js"></script>
</head>
<div style="width: 300px; height: 300px; overflow: hidden;" id="scroller">
  <div style="width: 100%; height: 1000px;"></div>
  <!-- I am not sure it's worth setting scrolling="no" and pointer-events: none. -->
  <!-- I just want to make sure that HitTestingTreeNode is generated even with these properties. -->
  <iframe scrolling="no" style="pointer-events: none;" id="testframe"></iframe>
</div>
<script>
async function setup_in_oopif() {
  let iframeURL = SimpleTest.getTestFileURL("helper_fission_plain.html");
  // We can't use `setupCrossOriginIFrame` directly here since the iframe here
  // is clipped out by an overflow: hidden ancestor, thus any APZ stuff
  // corresponding to the iframe document hasn't been established.
  const iframe = document.querySelector("iframe");
  const iframeLoadPromise = promiseOneEvent(iframe, "load", null);
  iframe.src = iframeURL;
  await iframeLoadPromise;
  await SpecialPowers.spawn(iframe, [], async () => {
    // Load utility functions for animation stuff.
    const script = content.document.createElement("script");
    script.setAttribute("src", "/tests/dom/animation/test/testcommon.js");
    content.document.head.appendChild(script);
    const extraStyle = content.document.createElement("style");
    content.document.head.appendChild(extraStyle);
    // an animation doesn't cause any geometric changes and doesn't run on the
    // compositor either
    extraStyle.sheet.insertRule("@keyframes anim { from { color: red; } to { color: blue; } }", 0);
    const div = content.document.createElement("div");
    // Position an element for animation at top: 20px.
    div.style = "position: absolute; top: 40px; animation: anim 1s infinite; box-shadow: 0px -20px red;";
    div.setAttribute("id", "target");
    div.innerHTML = "hello";
    content.document.body.appendChild(div);
    await new Promise(resolve => {
      script.onload = async () => {
        // Force to flush the first style to avoid the first style is observed.
        content.document.querySelector("#target").getAnimations()[0];
        await content.wrappedJSObject.promiseFrame();
        resolve();
      };
    });
  });
}
async function observe_styling_in_oopif(aFrameCount) {
  const iframe = document.querySelector("iframe");
  return await SpecialPowers.spawn(iframe, [aFrameCount], async (frameCount) => {
    // Start in a rAF callback.
    await content.wrappedJSObject.waitForAnimationFrames(1);
    return await content.wrappedJSObject.
        observeStylingInTargetWindow(content.window, frameCount);
  });
}
async function promiseScrollInfoArrivalInOOPIF() {
  const scrollPromise = new Promise(resolve => {
    scroller.addEventListener("scroll", resolve, { once: true });
  });
  const transformReceivedPromise = SpecialPowers.spawn(testframe, [], async () => {
    await SpecialPowers.contentTransformsReceived(content);
  });
  await Promise.all([scrollPromise, transformReceivedPromise]);
}
function isWindows11() {
  return getPlatform() == "windows" &&
    SpecialPowers.Services.sysinfo.getProperty("version", null) == "10.0" &&
    SpecialPowers.Services.sysinfo.getProperty("build", null) == "22621";
}
// The actual test
function assertThrottledRestyles(restyleCount, msg) {
  // Allow 1 restyle count on Windows 11 since on our CIs there's something
  // causing force flushing.
  if (isWindows11()) {
    ok(restyleCount <= 1, msg);
  } else {
    is(restyleCount, 0, msg);
  }
}
async function test() {
  // Generate an infinite animation which is initially clipped out by
  // overflow: hidden style in the out-of-process iframe.
  await setup_in_oopif();
  let restyleCount = await observe_styling_in_oopif(5);
  assertThrottledRestyles(
    restyleCount,
    "Animation in an out-of-process iframe which is initially clipped out " +
    "due to 'overflow: hidden' should be throttled");
  // Scroll synchronously to a position where the iframe gets visible.
  scroller.scrollTo(0, 1000);
  await promiseScrollInfoArrivalInOOPIF();
  // Wait for a frame to make sure the notification of the last scroll position
  // from APZC reaches the iframe process
  await observe_styling_in_oopif(1);
  restyleCount = await observe_styling_in_oopif(5);
  is(restyleCount, 5,
     "Animation in an out-of-process iframe which is no longer clipped out " +
       "should NOT be throttled");
  // Scroll synchronously to a position where the iframe is invisible again.
  scroller.scrollTo(0, 0);
  await promiseScrollInfoArrivalInOOPIF();
  // Wait for a frame to make sure the notification of the last scroll position
  // from APZC reaches the iframe process
  await observe_styling_in_oopif(1);
  restyleCount = await observe_styling_in_oopif(5);
  assertThrottledRestyles(
    restyleCount,
     "Animation in an out-of-process iframe which is clipped out again " +
       "should be throttled again");
  // ===== Asyncronous scrolling tests =====
  scroller.style.overflow = "scroll";
  // Scroll asynchronously to a position where the animating element gets
  // visible.
  scroller.scrollTo({ left: 0, top: 750, behavior: "smooth"});
  // Wait for the asyncronous scroll finish. `60` frames is the same number in
  // helper_fission_scroll_oopif.html
  await observe_styling_in_oopif(60);
  restyleCount = await observe_styling_in_oopif(5);
  is(restyleCount, 5,
     "Animation in an out-of-process iframe which is now visible by " +
       "asynchronous scrolling should NOT be throttled");
  // Scroll asynchronously to a position where the iframe is still visible but
  // the animating element gets invisible.
  scroller.scrollTo({ left: 0, top: 720, behavior: "smooth"});
  // Wait for the asyncronous scroll finish.
  await observe_styling_in_oopif(60);
  restyleCount = await observe_styling_in_oopif(5);
  assertThrottledRestyles(
    restyleCount,
     "Animation in an out-of-process iframe which is scrolled out of view by " +
       "asynchronous scrolling should be throttled");
  // Scroll asynchronously to a position where the animating element gets
  // visible again.
  scroller.scrollTo({ left: 0, top: 750, behavior: "smooth"});
  // Wait for the asyncronous scroll finish.
  await observe_styling_in_oopif(60);
  restyleCount = await observe_styling_in_oopif(5);
  is(restyleCount, 5,
     "Animation in an out-of-process iframe appeared by the asynchronous " +
       "scrolling should be NOT throttled");
}
waitUntilApzStable()
  .then(test)
  .then(subtestDone, subtestFailed);
</script>
</html>