<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Scroll customization methods are called appropriately.</title>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<style>

* {
  margin:0;
  padding:0;
}

*::-webkit-scrollbar {
  width: 0 !important;
  height: 0 !important;
}

#a {
  height:400px;
  width:400px;
  overflow:scroll;
}

#b {
  height:500px;
  width:500px;
  background-color:purple;
}

#c {
  height:300px;
  width:300px;
  overflow:scroll;
}

#d {
  height:400px;
  width:400px;
  background-color:green;
}

body {
  height:3000px;
}

</style>
</head>
<body>

<div id="a">
<div id="b">
<div id="c">
<div id="d">
</div>
</div>
</div>
</div>

<script>
test(function() {
  assert_true('ScrollState' in window, "'ScrollState' in window");
}, "These tests only work with scroll customization enabled.");

internals.settings.setScrollAnimatorEnabled(false);

var originalApplyScrolls = [];
var originalDistributeScrolls = [];
var deltas = [85, 75, 65, 55, 45];

var elements = [
  document.getElementById("d"),
  document.getElementById("c"),
  document.getElementById("b"),
  document.getElementById("a"),
  document.scrollingElement];

var scrollableElements = [elements[1], elements[3], elements[4]];

document.scrollingElement.id = "scrollingElement";

function reset() {
  for (var i = 0; i < elements.length; ++i) {
    elements[i].scrollTop = 0;
    elements[i].unappliedDeltaY = [];
    elements[i].distributedDeltaY = [];
    elements[i].numberOfScrollBegins = 0;
    elements[i].numberOfScrollEnds = 0;

    elements[i].setApplyScroll((function(scrollState){
      if (!scrollState.isEnding && !scrollState.isBeginning)
        this.unappliedDeltaY.push(scrollState.deltaY);
    }).bind(elements[i]), "perform-after-native-scroll");

    elements[i].setDistributeScroll((function(scrollState) {
      if (scrollState.isBeginning)
        this.numberOfScrollBegins++;
      else if (scrollState.isEnding)
        this.numberOfScrollEnds++;
      else
        this.distributedDeltaY.push(scrollState.deltaY);
    }).bind(elements[i]), "perform-before-native-scroll");

    // Add a gc, to ensure that these callbacks don't get collected.
    gc();
  }
}

function applyDelta(d) {
  eventSender.gestureScrollBegin(10, 10);
  eventSender.gestureScrollUpdate(0, -d);
  eventSender.gestureScrollEnd(0, 0);
}

if ('ScrollState' in window) {
  test(function() {
    reset();

    // Scroll five times, with three scrollable elements.
    var cScrollTop = [85, 100, 100, 100, 100];
    var aScrollTop = [0, 0, 65, 100, 100];
    var scrollingElementScrollTop = [0, 0, 0, 0, 45];

    for (var i = 0; i < deltas.length; ++i) {
      applyDelta(deltas[i]);
      assert_equals(a.scrollTop, aScrollTop[i], "For id 'a' on step " + i);
      assert_equals(c.scrollTop, cScrollTop[i], "For id 'c' on step " + i);
      assert_equals(document.scrollingElement.scrollTop, scrollingElementScrollTop[i], "For scrollingElement on step " + i);
    }
  }, "Scroll offsets are modified correctly.");

  test(function() {
    reset();

    // Scroll five times, with five elements.
    var unapplied = [
      // d, the innermost element, never applies any scroll.
      [85, 75, 65, 55, 45],
      // c applies the first two scrolls, and then hits its scroll extents.
      [0, 0, 65, 55, 45],
      // b doesn't scroll, and so leaves the same deltas unapplied as c.
      [65, 55, 45],
      // a hits its scroll extent on the second last step.
      [0, 0, 45],
      // The scrollingElement performs the frame scroll.
      [0]];

    for (var i = 0; i < deltas.length; ++i)
      applyDelta(deltas[i]);

    for (var i = 0; i < elements.length; ++i) {
      var el = elements[i];
      // Every element sees the same deltas being distributed.
      assert_array_equals(el.distributedDeltaY, deltas, "distributed delta for " + el.id + ":");
      assert_array_equals(el.unappliedDeltaY, unapplied[i], "unapplied delta for " + el.id + ":");
    }

    // Ensure that the document leaves scroll unapplied when appropriate.
    var documentUnapplied = document.scrollingElement.unappliedDeltaY;
    applyDelta(4000);
    assert_equals(documentUnapplied[documentUnapplied.length - 1], 0);
    applyDelta(4000);
    assert_equals(documentUnapplied[documentUnapplied.length - 1], 4000);
  }, "Correct amount of delta is consumed.");

  test(function() {
    reset();

    // Scroll five times, with three scrollable elements.
    var cScrollTop = [85, 100, 100, 100, 100];
    var aScrollTop = [0, 0, 65, 100, 100];
    var scrollingElementScrollTop = [0, 0, 0, 0, 45];
    for (var i = 0; i < deltas.length; ++i) {
      applyDelta(deltas[i]);
      assert_equals(c.scrollTop, cScrollTop[i], "For id 'c' on step " + i);
      assert_equals(a.scrollTop, aScrollTop[i], "For id 'a' on step " + i);
      assert_equals(document.scrollingElement.scrollTop, scrollingElementScrollTop[i], "For scrollingElement on step " + i);
    }
  }, "Scroll propagation behaves correctly.");

  test(function() {
    reset();

    for (var i = 0; i < deltas.length; ++i)
      applyDelta(deltas[i]);

    for (var i = 0; i < elements.length; ++i) {
      assert_equals(elements[i].numberOfScrollBegins, deltas.length, "Incorrect number of begin events for " + elements[i].id);
      assert_equals(elements[i].numberOfScrollEnds, deltas.length, "Incorrect number of end events for " + elements[i].id);
    }
  }, "Correct number of scroll begin and end events observed.");

  {
    // NOTE - this async test needs to be run last, as it shares state with the
    // other tests. If other tests are run after it, they'll modify the state
    // while this test is still running.
    var flingTest = async_test("Touchscreen fling doesn't propagate.");
    reset();

    function assertScrollTops(cTop, aTop, scrollingElementTop, step) {
      assert_equals(c.scrollTop, cTop, "For id 'c' on step " + step);
      assert_equals(a.scrollTop, aTop, "For id 'a' on step " + step);
      assert_equals(document.scrollingElement.scrollTop, scrollingElementTop, "For scrollingElement on step " + step);
    };

    var frame_actions = [
      function() {
        eventSender.gestureFlingStart(10, 10, -1000000, -1000000, "touchscreen");
      },
      flingTest.step_func(function() {
        assertScrollTops(0, 0, 0, 1);
      }),
      flingTest.step_func(function() {
        assertScrollTops(100, 0, 0, 2);
      }),
      flingTest.step_func(function() {
        assertScrollTops(100, 0, 0, 3);
      }),
      flingTest.step_func(function() {
        assertScrollTops(100, 0, 0, 4);
        flingTest.done();
      })
    ]

    function executeFrameActions(frame_actions) {
      var frame = 0;
      function raf() {
        frame_actions[frame]();
        frame++;
        if (frame >= frame_actions.length)
          return;
        window.requestAnimationFrame(raf);
      }
      window.requestAnimationFrame(raf);
    }

    executeFrameActions(frame_actions);
  }
}

</script>
</body>
</html>