<!DOCTYPE html>
<title>Testing scroll positions when scrolling an element with smooth behavior</title>
<meta name="timeout" content="long"/>
<link rel="author" title="Frédéric Wang" href="mailto:fwang@igalia.com">
<link rel="help" href="https://drafts.csswg.org/cssom-view/#propdef-scroll-behavior">
<link rel="help" href="https://drafts.csswg.org/cssom-view/#scrolling-box">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/scroll-behavior.js"></script>
<style>
  .scrollable {
    overflow: auto;
    width: 400px;
    height: 200px;
    scroll-behavior: smooth;
  }
</style>
<div id="log">
</div>
<div id="overflowNode" class="scrollable">
  <div style="width: 2000px; height: 1000px; background: linear-gradient(135deg, red, green);">
    <span style="display: inline-block; width: 500px; height: 250px;"></span><span id="elementToReveal" style="display: inline-block; vertical-align: -15px; width: 10px; height: 15px; background: black;"></span>
  </div>
</div>
<script>
  // For smooth behavior, evolution of scroll positions over time is not specified by CSSOM View.
  // This test relies on the minimal assumption that scroll position functions are monotonic.
  ["scroll", "scrollTo", "scrollBy", "scrollIntoView"].forEach(function(scrollFunction) {
    [{left:0, top:0}, {left:1000, top:0}, {left:0, top:500}, {left:1000, top:500}].forEach((initial) => {
      var finalLeft = 500;
      var finalTop = 250;
      promise_test(() => {
        return new Promise(function(resolve, reject) {
          scrollNode(overflowNode, "scroll", "instant", initial.left, initial.top);
          var oldLeft = overflowNode.scrollLeft;
          var oldTop = overflowNode.scrollTop;
          assert_equals(oldLeft, initial.left, "ScrollLeft should be at initial position");
          assert_equals(oldTop, initial.top, "ScrollTop should be at initial position");
          if (scrollFunction === "scrollBy")
            scrollNode(overflowNode, scrollFunction, "smooth", finalLeft - initial.left, finalTop - initial.top);
          else
            scrollNode(overflowNode, scrollFunction, "smooth", finalLeft, finalTop);
          observeScrolling(overflowNode, function(done) {
            try {
              var newLeft = overflowNode.scrollLeft;
              var newTop = overflowNode.scrollTop;
              assert_less_than_equal(Math.hypot(finalLeft - newLeft, finalTop - newTop), Math.hypot(finalLeft - oldLeft, finalTop - oldTop), "Scroll position should move towards the final position");
              if (done) {
                assert_equals(newLeft, finalLeft, "ScrollLeft should reach final position");
                assert_equals(newTop, finalTop, "ScrollTop should reach final position");
              }
              oldLeft = newLeft;
              oldTop = newTop;
            } catch(e) {
              reject(e);
            }
            if (done)
              resolve();
          });
        });
      }, `Scroll positions when performing smooth scrolling from (${initial.left}, ${initial.top}) to (${finalLeft}, ${finalTop}) using ${scrollFunction}() `);
    });
  });

  [{scrollAttribute: "scrollLeft", scrollValue: 500}, {scrollAttribute: "scrollTop", scrollValue: 250}].forEach(function(scrollTest) {
    var initialPosition = Number(scrollTest.scrollValue) * 2;
    [0, initialPosition].forEach((initial) => {
      promise_test(() => {
        return new Promise(function(resolve, reject) {
          scrollNode(overflowNode, "scroll", "instant", initial, initial);
          var oldValue = overflowNode[scrollTest.scrollAttribute];
          assert_equals(oldValue, initial, `${scrollTest.scrollAttribute} should be at initial position`);
          var expectedValue = Number(scrollTest.scrollValue);
          overflowNode[scrollTest.scrollAttribute] = expectedValue;
          observeScrolling(overflowNode, function(done) {
            try {
              var newValue = overflowNode[scrollTest.scrollAttribute];
              assert_less_than_equal(Math.abs(expectedValue - newValue), Math.abs(expectedValue - oldValue), "Scroll position should move towards the final position");
              if (done)
                assert_equals(newValue, expectedValue, `${scrollTest.scrollAttribute} should reach final position`);
              oldValue = newValue;
            } catch(e) {
              reject(e);
            }
            if (done)
              resolve();
          });
        });
      }, `Scroll positions when performing smooth scrolling from ${initial} to ${scrollTest.scrollValue} by setting ${scrollTest.scrollAttribute} `);
    });
  });

  promise_test(() => {
    return new Promise(function(resolve, reject) {
      resetScroll(overflowNode);
      var initialScrollAborted = false;
      var scrollDirectionChanged = false;
      var oldLeft = overflowNode.scrollLeft;
      var oldTop = overflowNode.scrollTop;
      assert_equals(oldLeft, 0);
      assert_equals(oldTop, 0);
      scrollNode(overflowNode, "scroll", "smooth", 1500, 750);
      observeScrolling(overflowNode, function(done) {
        try {
          var newLeft = overflowNode.scrollLeft;
          var newTop = overflowNode.scrollTop;
          if (initialScrollAborted) {
            if (scrollDirectionChanged) {
              assert_greater_than_equal(oldLeft, newLeft, "ScrollLeft keeps decreasing");
              assert_greater_than_equal(oldTop, newTop, "ScrollTop keeps decreasing");
            } else
              scrollDirectionChanged = newLeft <= oldLeft && newTop <= oldTop;
          } else {
              assert_less_than_equal(oldLeft, newLeft, "ScrollLeft keeps increasing");
              assert_less_than_equal(oldTop, newTop, "ScrollTop keeps increasing");
              if (newLeft > 1000 && newTop > 500) {
                // Abort the initial scroll.
                initialScrollAborted = true;
                scrollNode(overflowNode, "scroll", "smooth", 500, 250);
                newLeft = overflowNode.scrollLeft;
                newTop = overflowNode.scrollTop;
              }
          }
          if (done) {
            assert_equals(newLeft, 500, "ScrollLeft should reach final position");
            assert_equals(newTop, 250, "ScrollTop should reach final position");
          }
          oldLeft = newLeft;
          oldTop = newTop;
        } catch(e) {
          reject(e);
        }
        if (done)
          resolve();
      });
    });
  }, "Scroll positions when aborting a smooth scrolling with another smooth scrolling");

  promise_test(() => {
    return new Promise(function(resolve, reject) {
      resetScroll(overflowNode);
      var initialScrollAborted = false;
      var oldLeft = overflowNode.scrollLeft;
      var oldTop = overflowNode.scrollTop;
      assert_equals(oldLeft, 0);
      assert_equals(oldTop, 0);
      scrollNode(overflowNode, "scroll", "smooth", 1500, 750);
      observeScrolling(overflowNode, function(done) {
        try {
          var newLeft = overflowNode.scrollLeft;
          var newTop = overflowNode.scrollTop;
          if (!initialScrollAborted) {
              assert_less_than_equal(oldLeft, newLeft, "ScrollLeft keeps increasing");
              assert_less_than_equal(oldTop, newTop, "ScrollTop keeps increasing");
              if (newLeft > 1000 && newTop > 500) {
                // Abort the initial scroll.
                initialScrollAborted = true;
                scrollNode(overflowNode, "scroll", "instant", 500, 250);
                newLeft = overflowNode.scrollLeft;
                newTop = overflowNode.scrollTop;
                assert_equals(newLeft, 500, "ScrollLeft should reach final position");
                assert_equals(newTop, 250, "ScrollTop should reach final position");
              }
          }
          if (done) {
            assert_equals(newLeft, 500, "ScrollLeft should stay at final position");
            assert_equals(newTop, 250, "ScrollTop should stay at final position");
          }
          oldLeft = newLeft;
          oldTop = newTop;
        } catch(e) {
          reject(e);
        }
        if (done)
          resolve();
      });
    });
  }, "Scroll positions when aborting a smooth scrolling with an instant scrolling");
</script>
