chromium/third_party/blink/web_tests/virtual/threaded-prefer-compositing/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html

<!DOCTYPE html>
<title>Tests mouse autoscroll interactions on a non-custom composited div scrollbar.</title>
<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>
.appearance {
  width: 100px;
  height: 100px;
  overflow: scroll;
  border: 1px solid black;
}
.location {
  position: absolute;
  top: 100px;
  left: 100px;
}
.space {
  height: 2000px;
  width: 2000px;
}

</style>

<!-- Composited non-custom scroller -->
<div id="scroller" class="appearance location">
  <div id="divContent" class="space"></div>
</div>

<script>
  // Turn off animated scrolling. The "conditionHolds" API expects the scrollTop to
  // *not* change for 10 frames. This will fail since the last GSU would still be
  // animating if animated scrolling were on.
  if (window.internals)
    internals.settings.setScrollAnimatorEnabled(false);

  const scroller = document.getElementById("scroller");
  const scrollerRect = scroller.getBoundingClientRect();
  const onMacPlatform =  navigator.userAgent.search(/\bMac OS X\b/) != -1;

  const TRACK_WIDTH = calculateScrollbarThickness();
  const BUTTON_WIDTH = TRACK_WIDTH;
  const SCROLL_CORNER = TRACK_WIDTH;
  const SCROLL_DELTA = (internals.runtimeFlags.percentBasedScrollingEnabled) ? 
      Math.floor(scroller.clientHeight * SCROLLBAR_SCROLL_PERCENTAGE) * 10 :
      SCROLLBAR_SCROLL_PIXELS * 10;
  const MAX_SCROLLER_OFFSET = 1900 + TRACK_WIDTH;

  promise_test (async () => {
    if(onMacPlatform)
      return;

    await waitForCompositorCommit();
    scroller.scrollTop = 0;

    const down_arrow_x = scrollerRect.right - BUTTON_WIDTH / 2;
    const down_arrow_y = scrollerRect.bottom - SCROLL_CORNER - BUTTON_WIDTH / 2;

    await mouseMoveTo(down_arrow_x, down_arrow_y);
    await mouseDownAt(down_arrow_x, down_arrow_y);
    await waitUntil(() => { return scroller.scrollTop > SCROLL_DELTA; },
    `scroller.scrollTop = ${scroller.scrollTop} never went beyond ${SCROLL_DELTA}`);
    await mouseUpAt(down_arrow_x, down_arrow_y);
    await waitForStableScrollOffset(scroller);

    // Since autoscroll for arrows happens at 800 px per second, verify that the
    // scrollTop has not reached the end.
    assert_less_than(scroller.scrollTop, MAX_SCROLLER_OFFSET, "Reached scroller end.");
  },"Test arrow autoscroll down and autoscroll stop.");

  promise_test (async () => {
    await waitForCompositorCommit();
    scroller.scrollTop = 0;

    const trackscroll_x = scrollerRect.right - BUTTON_WIDTH / 2;
    const trackscroll_y = scrollerRect.bottom - SCROLL_CORNER - BUTTON_WIDTH;

    await mouseMoveTo(trackscroll_x, trackscroll_y);
    await mouseDownAt(trackscroll_x, trackscroll_y);
    await waitUntil(() => { return scroller.scrollTop > SCROLL_DELTA; },
    `scroller.scrollTop = ${scroller.scrollTop} never went beyond ${SCROLL_DELTA}`);
    await mouseUpAt(trackscroll_x, trackscroll_y);
    await waitForStableScrollOffset(scroller);

    // Verify that the track autoscroll actually stops as expected. Autoscroll velocity
    // in this particular case is 1480 px/sec (i.e 74 * 20). There is currently a bug in
    // the main thread autoscrolling code that causes autoscrolling to happen at a
    // much lower velocity (crbug.com/1053398)
    assert_less_than(scroller.scrollTop, 800, "Track autosroll did not end.");
  },"Test track autoscroll down and autoscroll stop.");

  promise_test (async () => {
    // Scrollbars on Mac don't have arrows. This test is irrelevant. Infinite auto
    // scroll can't be tested for track scrolls since autoscrolling aborts the as
    // soon as the thumb reaches the pointer. Regular autoscrolling still has coverage
    // on all platforms.
    if(onMacPlatform)
      return;

    await waitForCompositorCommit();
    scroller.scrollTop = MAX_SCROLLER_OFFSET;

    const content = document.getElementById("divContent");
    const originalDivHeight = content.clientHeight;
    const extendedDivHeight = originalDivHeight + 500;
    setTimeout(function() {
      content.setAttribute("style","height:" + extendedDivHeight + "px");
    }, 500);

    const down_arrow_x = scrollerRect.right - BUTTON_WIDTH / 2;
    const down_arrow_y = scrollerRect.bottom - SCROLL_CORNER - BUTTON_WIDTH / 2;

    // Keep the mouse pressed. The previously scheduled scroller height increment kicks in
    // and at this point, the autoscrolling is expected to take place. This should prove
    // that scrolling occured *after* the scroller length was extended.
    await mouseMoveTo(down_arrow_x, down_arrow_y);
    await mouseDownAt(down_arrow_x, down_arrow_y);

    // Verify that autoscroll took place beyond the old bounds. If there is a regression here,
    // the scroller.scrollTop would've stayed at MAX_SCROLLER_OFFSET.
    await waitUntil(() => { return scroller.scrollTop > MAX_SCROLLER_OFFSET; },
    `Infinite autoscroll down failed (scroller.scrollTop = ${scroller.scrollTop})`);

    await mouseUpAt(down_arrow_x, down_arrow_y);

    // Reset the scroller dimensions.
    content.setAttribute("style","height:" + originalDivHeight + "px");
  },"Test infinite scrolling when content is extended dynamically.");

  promise_test (async () => {
    // Scrollbars on Mac don't have arrows. This test is irrelevant.
    if(onMacPlatform)
      return;

    scroller.scrollTop = 0;
    const SCROLL_TOP = 100;

    const down_arrow_x = scrollerRect.right - BUTTON_WIDTH / 2;
    const down_arrow_y = scrollerRect.bottom - SCROLL_CORNER - BUTTON_WIDTH / 2;

    // Keep the mouse pressed on the down arrow.
    await mouseMoveTo(down_arrow_x, down_arrow_y);
    await mouseDownAt(down_arrow_x, down_arrow_y);

    // Wait for a bit for the autoscroll to start. In the call below, the first 250ms
    // is spent waiting for the autoscroll to start. After 250ms, autoscroll initiates
    // with a velocity of 800px/sec. So, in the remaining time (of 250ms), the duration
    // should be enough to take the scroller beyond the expected threshold (SCROLL_TOP).
    // Note that the expected SCROLL_TOP here is 100px. If scrolling crosses this value,
    // it should suffice as proof that autoscrolling works as expected.
    await waitForMs(500);
    await waitFor(() => { return scroller.scrollTop >= SCROLL_TOP; },
    `scroller.scrollTop = ${scroller.scrollTop} never reached ${SCROLL_TOP}`);

    // Without releasing the mouse, move away from the arrow.
    await mouseMoveTo(down_arrow_x, down_arrow_y - 20);
    // If there's an animation in flight, it will not be stopped abruptly by the
    // mouse move, so wait for it to end.
    await waitForStableScrollOffset(scroller);
    assert_less_than(scroller.scrollTop, MAX_SCROLLER_OFFSET, "Reached scroller end.");

    // Now move back on the arrow and verify that auto-scrolling starts immediately. There
    // should not be the 250ms pause before starting autoscroll since the mouse was never released.
    await mouseMoveTo(down_arrow_x, down_arrow_y);

    // Allow some time for queued GSU's to fire.
    const current_scrolltop = scroller.scrollTop;
    await waitFor(() => { return scroller.scrollTop >= current_scrolltop; },
    `Animation did not restart [scroller.scrollTop = ${scroller.scrollTop}]`);

    await mouseUpAt(down_arrow_x, down_arrow_y);
  },"Test autoscroll play/pause when pointer moves in and out of arrow bounds.");
</script>