<!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:[email protected]">
<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="/dom/events/scrolling/scroll_support.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>
promise_test(async () => {
await waitForCompositorReady();
}, "Make sure the page is ready for animation.");
// 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>