Source code

Revision control

Copy as Markdown

Other Tools

<!DOCTYPE HTML>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1.0">
<title>
Tests that the overscroll-behavior restrictions of the scroll containers a
fling is handed off through are accumulated per axis, with three nested
scroll containers (root, middle, innermost) whose overscroll-behavior is
(auto, contain(y), contain(x)) and a diagonal fling
</title>
<script src="apz_test_utils.js"></script>
<script src="apz_test_native_event_utils.js"></script>
<script src="/tests/SimpleTest/paint_listener.js"></script>
<style>
html {
overflow: scroll;
}
#middle {
width: 500px;
height: 450px;
overflow: scroll;
overscroll-behavior-x: auto;
overscroll-behavior-y: contain;
}
#innermost {
width: 400px;
height: 400px;
overflow: scroll;
overscroll-behavior-x: contain;
overscroll-behavior-y: auto;
}
#innermost-content {
width: 1000px;
height: 1000px;
}
#middle-spacer {
width: 1000px;
height: 1000px;
}
#root-spacer {
width: 300vw;
height: 300vh;
}
</style>
<div id="middle">
<div id="middle-spacer"></div>
<div id="innermost">
<div id="innermost-content"></div>
</div>
</div>
<div id="root-spacer"></div>
<script>
SimpleTest.requestFlakyTimeout(
"synthesizeNativeTouchForFastFling has to wait for a period of the time where touch events trigger a fast fling"
);
// The information collected above is a snapshot of the current state,
// and an overscroll effect only exists while the fling is running, so
// collect the information repeatedly. SimpleTest.promiseWaitForCondition()
// can't do the sampling: it waits on the window SimpleTest.js was loaded in,
// which is the opener, and the opener is in the background while a subtest
// runs, so its timers are clamped to dom.min_background_timeout_value, which
// the test profile pins at 1s.
async function promiseOverscrolledScrollIds(aPredicate) {
const overscrolledIds = new Set();
const deadline = performance.now() + 3000;
do {
const apzData =
SpecialPowers.DOMWindowUtils.getCompositorAPZTestData().additionalData;
for (const data of apzData) {
const wrapped = SpecialPowers.wrap(data);
if (wrapped.value.split(",").includes("overscrolled")) {
overscrolledIds.add(String(wrapped.key));
}
}
if (aPredicate(overscrolledIds)) {
break;
}
await new Promise(resolve => setTimeout(resolve, 10));
} while (performance.now() < deadline);
return overscrolledIds;
}
function scrollIdOf(aElement) {
return String(SpecialPowers.DOMWindowUtils.getViewId(aElement));
}
// The innermost container is parked at its bottom right corner and flung
// towards the top left, so that both axes of the fling have to be handed
// off. The horizontal velocity has to stop at the innermost container
// (overscroll-behavior-x: contain), and the vertical velocity has to stop
// at the middle container (overscroll-behavior-y: contain). The middle
// container starts with horizontal room left so that a horizontal handoff
// which escapes the innermost container would be visible. Also the top
// container starts with vertical room left so that a vertical handoff
// which escapes the middle container would be visible.
async function test() {
ok(innermost.scrollLeftMax > 0 && innermost.scrollTopMax > 0,
"The innermost scroll container is scrollable in both directions");
ok(middle.scrollLeftMax > 0 && middle.scrollTopMax > 0,
"The middle scroll container is scrollable in both directions");
ok(document.scrollingElement.scrollLeftMax > 0 &&
document.scrollingElement.scrollTopMax > 0,
"The root scroll container is scrollable in both directions");
innermost.scrollLeft = innermost.scrollLeftMax;
innermost.scrollTop = innermost.scrollTopMax;
middle.scrollTop = middle.scrollTopMax;
await promiseApzFlushedRepaints();
is(innermost.scrollLeft, innermost.scrollLeftMax,
"The innermost scroll container starts at its right edge");
is(innermost.scrollTop, innermost.scrollTopMax,
"The innermost scroll container starts at its bottom edge");
is(middle.scrollLeft, 0,
"The middle scroll container starts at its left edge");
is(middle.scrollTop, middle.scrollTopMax,
"The middle scroll container starts at its bottom edge");
// Also makes sure the gesture actually reached APZ.
const transformEnd = promiseTransformEnd();
// The fling has to be fast enough that the overscroll it leaves behind is
// worth more than one device pixel: Axis::SampleOverscrollAnimation() ends
// the animation and clears the overscroll as soon as it drops below that,
// which on a 1x display happens right away for a slower fling. The touch
// points also have to stay clear of the scrollbars, which take up room on
// the platforms without overlay scrollbars.
await synthesizeNativeTouchForFastFling(
innermost, 300, 300, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT);
await synthesizeNativeTouchForFastFling(
innermost, 200, 200, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT);
await synthesizeNativeTouchForFastFling(
innermost, 100, 100, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE);
// On Android the overscroll effect is handled by the widget,
// which never puts the APZC itself into an overscrolled state,
// so these checks only work on desktop.
if (getPlatform() != "android") {
const overscrolledIds = await promiseOverscrolledScrollIds(
ids => ids.has(scrollIdOf(innermost)) && ids.has(scrollIdOf(middle)));
ok(overscrolledIds.has(scrollIdOf(innermost)),
"The innermost scroll container took the horizontal velocity as an overscroll effect");
ok(overscrolledIds.has(scrollIdOf(middle)),
"The middle scroll container took the vertical velocity as an overscroll effect");
ok(!overscrolledIds.has(scrollIdOf(document.scrollingElement)),
"The root scroll container was not overscrolled");
}
await transformEnd;
await promiseApzFlushedRepaints();
// Check that either the middle or the root doesn't scroll.
is(middle.scrollLeft, 0,
"The middle scroll container did not scroll horizontally");
is(document.scrollingElement.scrollLeft, 0,
"The root scroll container did not scroll horizontally");
is(document.scrollingElement.scrollTop, 0,
"The root scroll container did not scroll vertically");
}
waitUntilApzStable()
.then(test)
.then(subtestDone, subtestFailed);
</script>