chromium/third_party/blink/web_tests/fast/scroll-snap/root-scroller-snap-behaviour/smooth-scroll-snaps-visual-viewport.html

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

#root-scroller::-webkit-scrollbar {
  width: 0px;
  height: 0px;
}

#root-scroller {
  width: 100%;
  height: 100%;
  overflow: scroll;
  position: absolute;
  left: 0;
  top: 0;
  background-color: red;
  scroll-snap-type: y mandatory;
}

.spacer {
  width: 100%;
  height: 100%;
}

#initial-snap-area {
  width: 200px;
  height: 50%;
  background-color: blue;
  scroll-snap-align: start;
}

#snap-area {
  width: 200px;
  height: 50%;
  background-color: blue;
  scroll-snap-align: start;
}
</style>

<script src="../../../resources/gesture-util.js"></script>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>

<div id="root-scroller">
  <div class="spacer" style="background-color: PaleGreen"></div>
  <div class="spacer" style="background-color: PaleGreen"></div>
  <div id="initial-snap-area"></div>
  <!--
    Needed because otherwise the other snap-area's snap offset would get
    clipped to the same offset as the initial snap area, which would make either
    snap area a valid choice.
  -->
  <div style="height:10px;"></div>
  <div id="snap-area"></div>
</div>

<script>
if (window.internals) {
  internals.runtimeFlags.implicitRootScrollerEnabled = true;
}

const root_scroller = document.getElementById("root-scroller");
const snap_area = document.getElementById("snap-area");
const initial_area = document.getElementById("initial-snap-area");

// Tests that the visual viewport is affected when scrolling the global root
// scroller by gesture. The snap area is located at the bottom of the layout
// viewport, so the layout viewport cannot align with the snap area.
// However, when it becomes the global root scroller we should be scrolling the
// visual viewport too to align with the snap area.
//
// TODO: There is no test for programmatic scrolling because the expected
// behaviour of the visual viewport while scrolling the root scroller is not
// clear [1]. The current behaviour is that the visual viewport offset does not
// change, and only the layout viewport scrolls to the target.
// [1] https://github.com/w3c/csswg-drafts/issues/4393
promise_test(async function () {
  const scale_factor = 2;
  internals.setPageScaleFactor(scale_factor);
  internals.setVisualViewportOffset(0, 0);

  assert_equals(visualViewport.scale, 2);
  assert_equals(visualViewport.offsetTop, 0);

  await waitForCompositorCommit();

  assert_equals(window.internals.effectiveRootScroller(document),
    root_scroller,
    "#root-scroller must be the effective root scroller");
  // Should be snapped to the closer snap area on the initial layout.
  assert_equals(visualViewport.offsetTop + root_scroller.scrollTop,
      initial_area.offsetTop);


  // Scroll halfway to the snap area. This will trigger the snapping.
  const delta = snap_area.offsetTop / 2;

  // Start scroll from the middle of the viewport.
  const initial_scroll_position = {
    x: visualViewport.width / 2,
    y: visualViewport.height / 2
  }
  await smoothScroll(
    delta,
    initial_scroll_position.x,
    initial_scroll_position.y,
    GestureSourceType.TOUCH_INPUT,
    'down',
    SPEED_INSTANT);

  await waitForAnimationEndTimeBased(() => {
    return root_scroller.scrollTop;
  });

  // The offset of the visual viewport and the layout viewport combined should
  // be at the snap point.
  assert_equals(visualViewport.offsetTop + root_scroller.scrollTop,
    snap_area.offsetTop, "Visual viewport offset combined with the scroller's \
scroll offset should add to the snap area's position.");

}, "Snapping the root scroller after smooth scrolling should affect the\
visual viewport offset.");
</script>