chromium/content/test/data/iframe_clipped.html

<!DOCTYPE html>
<html>
<head>
  <style>
    body, html {
      width: 100%;
      height: 100%;
      margin: 0;
    }

    #iframe {
      width: 100vw;
      height: 100vh;
      border: 0;
      padding: 0;
      overflow: scroll;
    }

    #clip {
      width: 50%;
      height: 20%;
      overflow: hidden;
    }
  </style>
</head>
<body>
  <div id="clip">
    <iframe id="iframe">
    </iframe>
  </div>
  <script>
  window.loaded = false;
  window.notifyBrowser = false;
  const kMarginOfError = 1;
  const tinyTimeout = 50;

  window.addEventListener("load", () => {
    window.loaded = true;
    if (window.notifyBrowser)
      notifyWhenLoaded();
  });

  function notifyWhenLoaded() {
    if (loaded)
      window.domAutomationController.send("LOADED");
    else
      window.notifyBrowser = true;
  }

  // Waits until the |visualViewport| reaches the given size (approximately).
  // Notifies the browser when this happens.
  function notifyVisualViewportChanged(width, height) {
    if ((Math.abs(visualViewport.width - width) < kMarginOfError) &&
        (Math.abs(visualViewport.height - height) < kMarginOfError)) {
      return window.domAutomationController.send("SIZED");
    }

    window.setTimeout(() => {
      notifyVisualViewportChanged(width, height);
    }, tinyTimeout);
  }

  function isEmptyRect(rect) {
    return (rect.width * rect.height) === 0;
  }

  function intersect(rect1, rect2) {
    let minX1 = rect1.x, maxX1 = minX1 + rect1.width
        minY1 = rect1.y, maxY1 = minY1 + rect1.height,
        minX2 = rect2.x, maxX2 = minX2 + rect2.width,
        minY2 = rect2.y, maxY2 = minY2 + rect2.height;

    return !(isEmptyRect(rect1) ||
             isEmptyRect(rect2) ||
             (minX1 > maxX2) ||
             (minX2 > maxX1) ||
             (minY1 > maxY2) ||
             (minY2 > maxY1));
  }

  function visualViewportAsRect() {
    return {
      x: visualViewport.offsetLeft,
      y: visualViewport.offsetTop,
      width: Math.round(visualViewport.width),
      height: Math.round(visualViewport.height)
    };
  }

  function rectAsString(rect) {
    return `${rect.x},${rect.y},${rect.width},${rect.height}`;
  }

  // Waits until the given element is inside the |visualViewport|.
  function notifyWhenVisible(el) {
    if (intersect(el.getBoundingClientRect(), visualViewportAsRect())) {
      window.domAutomationController.send("VISIBLE");
    } else {
      window.setTimeout(() => {
        notifyWhenVisible(el);
      }, tinyTimeout);
    }
  }

  let lastLayoutViewportX;
  let lastLayoutViewportY;
  let lastVisualViewportX;
  let lastVisualViewportY;
  let lastScale;
  function notifyWhenViewportStable(numFrames) {
    const kUnchangedFramesConsideredStable = 10;

    if (lastLayoutViewportX !== window.scrollX ||
        lastLayoutViewportY !== window.scrollY ||
        lastVisualViewportX !== window.visualViewport.offsetLeft ||
        lastVisualViewportY !== window.visualViewport.offsetTop ||
        lastScale !== window.visualViewport.scale) {
      lastLayoutViewportX = window.scrollX;
      lastLayoutViewportY = window.scrollY;
      lastVisualViewportX = window.visualViewport.offsetLeft;
      lastVisualViewportY = window.visualViewport.offsetTop;
      lastScale = window.visualViewport.scale;
      numFrames = 0;
    }

    numFrames++;

    if (numFrames == kUnchangedFramesConsideredStable) {
      window.domAutomationController.send("VIEWPORT_STABLE");
    } else {
      requestAnimationFrame(notifyWhenViewportStable.bind(null, numFrames));
    }
  }
  </script>
</body>
</html>