chromium/third_party/blink/web_tests/fast/scrolling/percent-based-scrolling/max-percent-delta-cross-origin-iframes.html

<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/gesture-util.js"></script>
<script src="../../resources/scrollbar-util.js"></script>

<style>
  .oop-iframe {
    width: 200px;
    height: 200px;
    overflow: scroll;
  }
</style>

<iframe id="target-iframe" class="oop-iframe"
  src="http://localhost:8080/misc/resources/internal-iframe.html">
</iframe>

<script>

// This test verifies that percent-based scroll deltas are corretly clamped in
// cross-origin iframes (or out-of-process iframes - OOPIFS).
// The expected upper-bound is percentage_delta * window.innerHeight.
//
// For this, it creates an out-of-process iframe (target-iframe) in the HTTP
// server and scrolls it clicking on its scrollbar arrows and running a
// mousewheel event hovering it.
//
// It waits for messages from the iframe with the scrolled deltas, since we
// can't access them from the here.
//
// This makes necessary to run the tests by steps, waiting for messages with the
// deltas. For this, we use a state-machine:

// The test uses these 3 major states for waiting, and a final for checking:
// 0. IDLE:
//    waits for target-iframe.onload to click in the down arrow and
//    move to state WAITING_ARROW.
// 1. WAITING_ARROW:
//    waits for target-iframe's message with the scroll delta until it reaches
//    the expected value. Then, move to state CHECKING_SCROLL_STOP for 10 frames.
//    If the scroll delta is updated, fails test. Otherwise, run smoothScroll
//    and move to WAITING_MOUSEWHEEL.
// 2. WAITING_MOUSEWHEEL:
//    waits for target-iframe's message with the scroll delta until it reaches
//    the expected value. Then moves to CHECKING_SCROLL_STOP for 10 frames.
//    If the scroll delta is updated, fails. Otherwise test succeeds.
const states = {
  IDLE: 0,                  // waiting for test to begin.
  WAITING_ARROW: 1,         // waiting for arrow scroll target delta.
  WAITING_MOUSEWHEEL: 2,    // waiting for mousewheel scroll target delta.
  CHECKING_SCROLL_STOP: 3,  // checking if scroll delta holds.
};
// the expected flow of the test is:
// 1. startTest()
// 2. runArrow()
// {waits for message from internal-iframe with the scrolled delta}
// 3. handleMessage()
// {when the expected scroll delta is reached}
// 4. checkScrollStopAndRunMousewheel()
// 5. runMouseWheel()
// {waits for message from internal-iframe with the scrolled delta}
// 6. handleMessage()
// {when the expected scroll delta is reached}
// 7. checkScrollStopAndFinish()
// on mac, we jump form 1 to 5, since it doesn't have scrollbar arrows.

const MOUSEWHEEL_PERCENTAGE = 0.5;
const TRACK_WIDTH = calculateScrollbarThickness();
const BUTTON_WIDTH = TRACK_WIDTH;
const SCROLL_CORNER = TRACK_WIDTH;

const onMacPlatform = navigator.userAgent.includes("Mac OS X");

var scroll_test = async_test("Verify that a subscroller inside a cross-origin"
      + " iframe scrolls a maximum of 12.5% of the iframe");
var state = states.IDLE;
var iframe = document.getElementById("target-iframe");
iframe.onload = startTest;

// Scroll delta of target-iframe's subscroller. It is updated at handleMessage().
var scroll_delta;

function startTest() {
  // Percent-based scrolling specific method.
  if(!internals.runtimeFlags.percentBasedScrollingEnabled) {
      scroll_test.name = "Percent-based scrolling disabled - test skipped "
      scroll_test.done();
      return;
  }
  else if (!internals.isSiteIsolated(iframe)) {
      scroll_test.name = "No site isolation - test skipped"
      scroll_test.done();
      return;
  }
  else {
    window.addEventListener("message", handleMessage);

    // Skip arrow click, as Mac scrollbars doesn't have arrows.
    onMacPlatform ? runMouseWheel() : runArrow();
  }
}

function runArrow() {
  console.log("Clicking arrow");
  iframe.contentWindow.postMessage({ startScroll: true }, "*");

  scrollerRect = iframe.getBoundingClientRect()
  const down_arrow_x = scrollerRect.right - BUTTON_WIDTH / 2;
  const down_arrow_y = scrollerRect.bottom - SCROLL_CORNER - BUTTON_WIDTH / 2;

  state = states.WAITING_ARROW;
  mouseClickOn(down_arrow_x, down_arrow_y);
}

async function checkScrollStopAndRunMouseWheel(targetScrollDelta) {
  // Garantees that the previous scroll stopped.
  state = states.CHECKING_SCROLL_STOP;
  await conditionHolds(() => {
    return scroll_delta == targetScrollDelta});

  runMouseWheel();
}

async function runMouseWheel() {
  console.log("Running mousewheel ", MOUSEWHEEL_PERCENTAGE * 100, "% = ",
    MOUSEWHEEL_PERCENTAGE * iframe.clientHeight);

  iframe.contentWindow.postMessage({ startScroll: true }, "*");
  state = states.WAITING_MOUSEWHEEL;

  scrollerRect = iframe.getBoundingClientRect()
  const down_arrow_x = scrollerRect.right - BUTTON_WIDTH / 2;
  const down_arrow_y = scrollerRect.bottom - SCROLL_CORNER - BUTTON_WIDTH / 2;
  await smoothScroll(MOUSEWHEEL_PERCENTAGE,
    scrollerRect.top + 10,
    scrollerRect.left + 10,
    GestureSourceType.MOUSE_INPUT,
    "down",
    SPEED_INSTANT /* speed_in_pixels_s */,
    false /* precise_scrolling_deltas */,
    false /* scroll_by_page */,
    false /* cursor_visible */,
    true /* scroll_by_percentage */);
}

async function checkScrollStopAndFinish(targetScrollDelta) {
  // Guarantees that the previous scroll stopped.
  state = states.CHECKING_SCROLL_STOP;
  await conditionHolds(() => {
    return scroll_delta == targetScrollDelta});
  scroll_test.done();
}

function handleMessage(event) {
  scroll_test.step(() => {
    scroll_delta = event.data.deltaY;
    switch(state) {
      case states.WAITING_ARROW:
        console.log("Arrow down: ", scroll_delta);
        assert_less_than_equal(
          scroll_delta,
          SCROLLBAR_SCROLL_PERCENTAGE * window.innerHeight);
        if (scroll_delta == SCROLLBAR_SCROLL_PERCENTAGE * window.innerHeight)
          checkScrollStopAndRunMouseWheel(SCROLLBAR_SCROLL_PERCENTAGE * window.innerHeight);
        break;
      case states.WAITING_MOUSEWHEEL:
        console.log("Mousewheel: ", scroll_delta);
        assert_less_than_equal(
          scroll_delta,
          MOUSEWHEEL_PERCENTAGE * window.innerHeight);
        if (event.data.deltaY == MOUSEWHEEL_PERCENTAGE * window.innerHeight)
          checkScrollStopAndFinish(MOUSEWHEEL_PERCENTAGE * window.innerHeight);
        break;
      case states.CHECKING_SCROLL_STOP:
        console.log(scroll_delta);
    }
  });
}
</script>