chromium/third_party/blink/web_tests/fast/scroll-behavior/overscroll-behavior.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/percent-based-util.js"></script>
<script src="../../resources/testdriver.js"></script>
<script src="../../resources/testdriver-actions.js"></script>
<script src="../../resources/testdriver-vendor.js"></script>
<style>
body {
  margin: 0;
}
.outer {
  height: 250px;
  width: 500px;
}
.content {
  height: calc(100vh + 100px);
  width: calc(100vw + 100px);
}
#container {
  overflow: scroll;
}
#non_scrollable {
  background-color: #eee;
  overflow: hidden;
}
</style>

<div id='non_scrollable' class='outer'>
  <div class='content'></div>
</div>
<div id='container' class='outer'>
  <div class='content'></div>
</div>
<div class="content"></div>
<script>
const container = document.getElementById('container');
const non_scrollable = document.getElementById('non_scrollable');
const scrollAmount = 120;
const scrollLeft = { dx: -scrollAmount, dy: 0 };
const scrollUp = { dx: 0, dy: -scrollAmount };
const scrollRight = { dx: scrollAmount, dy: 0 };
const scrollDown = { dx: 0, dy: scrollAmount };
const scrollUpAndLeft = { dx: -scrollAmount, dy: -scrollAmount };
const startPositionCenter = { x: 0.5, y: 0.5 };
const startPositionBottom = { x: 0.5, y: 0.9 };

async function setUpForWindow() {
  await waitForCompositorCommit();
  await waitForScrollReset(document.scrollingElement, 100, 100);
  await waitForScrollReset(container, 0, 0);
  assert_equals(window.scrollY, 100);
  assert_equals(window.scrollX, 100);
  assert_equals(container.scrollTop, 0);
  assert_equals(container.scrollLeft, 0);
}

async function setUpForContainer() {
  await waitForCompositorCommit();
  await waitForScrollReset(document.scrollingElement, 0, 0);
  await waitForScrollReset(container, 100, 100);
  assert_equals(window.scrollY, 0);
  assert_equals(window.scrollX, 0);
  assert_equals(container.scrollTop, 100);
  assert_equals(container.scrollLeft, 100);
}

// Performs a wheel scroll or touch drag gesture that may result in scrolling.
// If scrollEventTarget is not null, it indicates the target for the scrollend
// event.
function performGestureScroll(element, startPosition, deltaX, deltaY, gesture,
                              scrollEventTarget) {
  // Pick a starting point within the element with room to accommodate the
  // scroll gesture.
  const position = elementPosition(element, startPosition.x, startPosition.y);
  assert_point_within_viewport(position.x, position.y);
  const promises = [];
  if (scrollEventTarget) {
    promises.push(waitForScrollendEvent(scrollEventTarget));
  }
  promises.push(gesture(position.x, position.y,  deltaX, deltaY));
  return Promise.all(promises);
};

function performWheelScroll(element, startPosition, scrollAmount,
                            scrollEventTarget = null) {
  const scroll = (x, y, dx, dy) => {
    return wheelScroll(x, y, dx, dy, scrollEventTarget);
  };
  return performGestureScroll(element, startPosition, scrollAmount.dx,
                              scrollAmount.dy, scroll, scrollEventTarget);
}

function performTouchDrag(element, startPosition, scrollAmount,
                          scrollEventTarget = null) {
  // Touch scrolling is in the opposite direction to the drag.
  return performGestureScroll(element, startPosition, -scrollAmount.dx,
                              -scrollAmount.dy, touchDrag, scrollEventTarget);
}

async function test_boundary_prevents_y(scrollGesture) {
  container.style.overscrollBehaviorX = 'auto';
  container.style.overscrollBehaviorY = 'none';
  await setUpForWindow();

  // Scroll-chaining blocked by overscroll-behavior-y.
  await scrollGesture(container, startPositionCenter, scrollUp);
  assert_equals(window.scrollY, 100);
  assert_equals(container.scrollTop, 0);

  await raf();

  // Scroll-chaining permitted by overscroll-behavior-x.
  await scrollGesture(container, startPositionCenter, scrollLeft, document);
  assert_equals(window.scrollX, 0);
  assert_equals(container.scrollLeft, 0);
}

async function test_boundary_prevents_x(scrollGesture) {
  container.style.overscrollBehaviorX = 'none';
  container.style.overscrollBehaviorY = 'auto';
  await setUpForWindow();

  // Scroll-chaining blocked by overscroll-behavior-x.
  await scrollGesture(container, startPositionCenter, scrollLeft);
  assert_equals(window.scrollX, 100);
  assert_equals(container.scrollLeft, 0);

  await raf();

  // Scroll-chaining permitted by overscroll-behavior-y.
  await scrollGesture(container, startPositionCenter, scrollUp, document);
  assert_equals(window.scrollY, 0);
  assert_equals(container.scrollTop, 0);
}

async function test_boundary_allows_inner(scrollGesture) {
  container.style.overscrollBehaviorX = 'none';
  container.style.overscrollBehaviorY = 'none';
  await setUpForContainer();

  await scrollGesture(container, startPositionCenter, scrollUpAndLeft,
                      container);
  assert_equals(container.scrollTop, 0);
  assert_equals(container.scrollLeft, 0);
}

async function test_boundary_on_nonscrollable_allows_propagation(scrollGesture)
{
  non_scrollable.style.overscrollBehaviorX = 'none';
  non_scrollable.style.overscrollBehaviorY = 'none';
  await waitForCompositorCommit;
  await waitForScrollReset(document.scrollingElement);

  await scrollGesture(non_scrollable, startPositionBottom, scrollRight,
                      document);
  assert_greater_than(window.scrollX, 100);

  await raf();

  await scrollGesture(non_scrollable, startPositionBottom, scrollDown,
                      document);
  assert_greater_than(window.scrollY, 100);
}

promise_test(t => {
  return test_boundary_prevents_y(performWheelScroll);
}, 'overscroll-behavior-y: none should only prevent scroll propagation on y ' +
   'axis with: wheel.');
promise_test(t => {
  return test_boundary_prevents_x(performWheelScroll);
}, 'overscroll-behavior-x: none should only prevent scroll propagation on x ' +
   'axis with: wheel.');
promise_test(t => {
  return test_boundary_allows_inner(performWheelScroll);
}, 'overscroll-behavior should not affect scrolling inside the applied ' +
   'container with: wheel.');
promise_test(t => {
  return test_boundary_on_nonscrollable_allows_propagation(performWheelScroll);
}, 'overscroll-behavior on non-scrollable area should not affect scroll ' +
   'propagation with: wheel.');

const IS_MAC = navigator.platform.indexOf('Mac') == 0;
if (!IS_MAC) {
  promise_test(t => {
    return test_boundary_prevents_y(performTouchDrag);
  }, 'overscroll-behavior-y: none should only prevent scroll propagation on ' +
     'y axis with: touch.');
  promise_test(t => {
    return test_boundary_prevents_x(performTouchDrag);
  }, 'overscroll-behavior-x: none should only prevent scroll propagation on ' +
     'x axis with: touch.');
  promise_test(t => {
      return test_boundary_on_nonscrollable_allows_propagation(
                 performTouchDrag);
    }, 'overscroll-behavior on non-scrollable area should not affect scroll ' +
       'propagation with: touch.');
}
</script>