зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1877878 [wpt PR 44338] - [css-scroll-snap-2] Prioritize focused snap targets, a=testonly
Automatic update from web-platform-tests [css-scroll-snap-2] Prioritize focused snap targets An agreed upon proposal[1] by the CSS working group specifies that where multiple scroll snap targets are visually equally aligned, scroll containers should give priority to focused elements. Blink did this for programmatic scrolls but not for user scrolls. This patch prioritizes focused elements for both types of scrolls by relying on a bit in SnapAreaData. [1]https://github.com/w3c/csswg-drafts/issues/9622 Bug: 1523819 Change-Id: I81e2aaf0845e9763abb463a960614beb37b05a8a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5255777 Reviewed-by: Steve Kobes <skobes@chromium.org> Commit-Queue: David Awogbemila <awogbemila@chromium.org> Cr-Commit-Position: refs/heads/main@{#1256790} -- wpt-commits: d56b1391cbf0979b0f333a1cae23875acb06be1b wpt-pr: 44338
This commit is contained in:
Родитель
41c20e6c3e
Коммит
13cc952c85
|
@ -0,0 +1,128 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css-scroll-snap" />
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<script src="/dom/events/scrolling/scroll_support.js"></script>
|
||||||
|
<script src="/resources/testdriver.js"></script>
|
||||||
|
<script src="/resources/testdriver-actions.js"></script>
|
||||||
|
<script src="/resources/testdriver-vendor.js"></script>
|
||||||
|
<script src="resources/common.js" ></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<style>
|
||||||
|
.scroller {
|
||||||
|
overflow: scroll;
|
||||||
|
position: relative;
|
||||||
|
height: 400px;
|
||||||
|
width: 400px;
|
||||||
|
border:solid 1px black;
|
||||||
|
scroll-snap-type: y mandatory;
|
||||||
|
}
|
||||||
|
.no-snap { scroll-snap-align: none }
|
||||||
|
.scroller div:focus {
|
||||||
|
border: solid 1px red;
|
||||||
|
}
|
||||||
|
.large-space {
|
||||||
|
height: 300vh;
|
||||||
|
width: 300vw;
|
||||||
|
}
|
||||||
|
.target {
|
||||||
|
scroll-snap-align: start;
|
||||||
|
position: absolute;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border: solid 1px black;
|
||||||
|
}
|
||||||
|
.top {
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
.left {
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
left: 200px;
|
||||||
|
}
|
||||||
|
.bottom {
|
||||||
|
top: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="scroller" class="scroller">
|
||||||
|
<div class="large-space no-snap" tabindex="1" id="space"></div>
|
||||||
|
<div id="topleft" tabindex="1" class="top left target">top left</div>
|
||||||
|
<div id="topright" tabindex="1" class="top right target">top right</div>
|
||||||
|
<div id="bottomleft" tabindex="1" class="bottom left target">bottom left</div>
|
||||||
|
<div id="bottomright" tabindex="1" class="bottom right target">bottom right</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
window.onload = () => {
|
||||||
|
const bottomright = document.getElementById("bottomright");
|
||||||
|
const bottomleft = document.getElementById("bottomleft");
|
||||||
|
const scroller = document.getElementById("scroller");
|
||||||
|
|
||||||
|
async function commonInitialization() {
|
||||||
|
await waitForCompositorCommit();
|
||||||
|
assert_equals(scroller.scrollTop, 0, "snapped to top row");
|
||||||
|
}
|
||||||
|
|
||||||
|
promise_test(async (t) => {
|
||||||
|
await commonInitialization();
|
||||||
|
|
||||||
|
focusAndAssert(bottomright);
|
||||||
|
await runScrollSnapSelectionVerificationTest(t, scroller,
|
||||||
|
[bottomright,
|
||||||
|
bottomleft],
|
||||||
|
/*expected_target=*/bottomright, "y");
|
||||||
|
|
||||||
|
focusAndAssert(bottomleft);
|
||||||
|
await runScrollSnapSelectionVerificationTest(t, scroller,
|
||||||
|
[bottomright,
|
||||||
|
bottomleft],
|
||||||
|
/*expected_target=*/bottomleft, "y");
|
||||||
|
}, "scroller selects focused target from aligned choices on snap");
|
||||||
|
|
||||||
|
promise_test(async (t) => {
|
||||||
|
t.add_cleanup(() => {
|
||||||
|
bottomright.style.left = "200px";
|
||||||
|
})
|
||||||
|
await commonInitialization();
|
||||||
|
|
||||||
|
// Move bottomright out of the snapport.
|
||||||
|
bottomright.style.left = "500px";
|
||||||
|
|
||||||
|
// Set focus on bottomright without scrolling to it.
|
||||||
|
focusAndAssert(bottomright, true);
|
||||||
|
await runScrollSnapSelectionVerificationTest(t, scroller,
|
||||||
|
[bottomright,
|
||||||
|
bottomleft],
|
||||||
|
/*expected_target=*/bottomleft, "y");
|
||||||
|
}, "out-of-viewport focused element is not the selected snap target.");
|
||||||
|
|
||||||
|
promise_test(async(t) => {
|
||||||
|
t.add_cleanup(() => {
|
||||||
|
bottomleft.style.top = "200px";
|
||||||
|
});
|
||||||
|
await commonInitialization();
|
||||||
|
|
||||||
|
// Set focus on bottomright without scrolling to it.
|
||||||
|
focusAndAssert(bottomright, true);
|
||||||
|
|
||||||
|
// Move bottomleft below bottomright.
|
||||||
|
bottomleft.style.top = "400px";
|
||||||
|
|
||||||
|
// Snap to bottomleft.
|
||||||
|
scroller.scrollTop = bottomleft.offsetTop;
|
||||||
|
|
||||||
|
// Test that if bottomright is also shifted so that it is aligned with
|
||||||
|
// bottomleft, bottomleft remains the selected snap target, despite
|
||||||
|
// bottomright's having focus.
|
||||||
|
await runLayoutSnapSeletionVerificationTest(t, scroller, [bottomright],
|
||||||
|
bottomleft, "y");
|
||||||
|
}, "scroller follows selected snap target through layout shift," +
|
||||||
|
"regardless of focus");
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,109 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css-scroll-snap" />
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<script src="/dom/events/scrolling/scroll_support.js"></script>
|
||||||
|
<script src="/resources/testdriver.js"></script>
|
||||||
|
<script src="/resources/testdriver-actions.js"></script>
|
||||||
|
<script src="/resources/testdriver-vendor.js"></script>
|
||||||
|
<script src="resources/common.js" ></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<style>
|
||||||
|
.snap {
|
||||||
|
scroll-snap-align: start;
|
||||||
|
}
|
||||||
|
.placeholder {
|
||||||
|
height: 40%;
|
||||||
|
width: 40%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
border: solid 1px black;
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
border: solid 1px blue;
|
||||||
|
overflow: scroll;
|
||||||
|
scroll-snap-type: y mandatory;
|
||||||
|
}
|
||||||
|
.bigcontainer {
|
||||||
|
height: 1000px;
|
||||||
|
width: 1000px;
|
||||||
|
}
|
||||||
|
.smallcontainer {
|
||||||
|
height: 400px;
|
||||||
|
width: 400px;
|
||||||
|
position: absolute;
|
||||||
|
border: solid 1px blue;
|
||||||
|
top: 500px;
|
||||||
|
overflow: scroll;
|
||||||
|
scroll-snap-type: y mandatory;
|
||||||
|
}
|
||||||
|
.large-space {
|
||||||
|
height: 300vh;
|
||||||
|
width: 300vw;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.target {
|
||||||
|
top: 50%;
|
||||||
|
width: 40%;
|
||||||
|
height: 40%;
|
||||||
|
position: absolute;
|
||||||
|
border: solid 1px red;
|
||||||
|
}
|
||||||
|
.target:focus {
|
||||||
|
border:solid 2px green;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="bigcontainer container" id="outercontainer">
|
||||||
|
<div class="large-space"></div>
|
||||||
|
<div id="leftplaceholder" class="snap placeholder">LPH (outer)</div>
|
||||||
|
<div id="rightplaceholder" class="snap placeholder right">RPH (outer)</div>
|
||||||
|
<div id="leftcontainer" class="snap smallcontainer">
|
||||||
|
<div class="large-space"></div>
|
||||||
|
<div class="snap placeholder"></div>
|
||||||
|
<div class="snap placeholder right"></div>
|
||||||
|
<div id="lefttarget1" tabindex="1" class="snap target"></div>
|
||||||
|
<div id="lefttarget2" tabindex="1" class="snap target right"></div>
|
||||||
|
</div>
|
||||||
|
<div id="rightcontainer" class="snap smallcontainer right">
|
||||||
|
<div class="large-space"></div>
|
||||||
|
<div class="snap placeholder"></div>
|
||||||
|
<div class="snap placeholder right"></div>
|
||||||
|
<div id="righttarget1" tabindex="1" class="snap target"></div>
|
||||||
|
<div id="righttarget2" tabindex="1" class="snap target right"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
// This test verifies that a snap container (outer) which contains another
|
||||||
|
// snap container (inner) snaps with awareness of focus on children of the
|
||||||
|
// inner container, i.e. outer should prefer to select the snap area whose
|
||||||
|
// child has focus even if there is an intermediate snap container between
|
||||||
|
// the child and outer.
|
||||||
|
window.onload = () => {
|
||||||
|
const lefttarget1 = document.getElementById("lefttarget1");
|
||||||
|
const righttarget1 = document.getElementById("righttarget1");
|
||||||
|
const leftcontainer = document.getElementById("leftcontainer");
|
||||||
|
const rightcontainer = document.getElementById("rightcontainer");
|
||||||
|
const outercontainer = document.getElementById("outercontainer");
|
||||||
|
|
||||||
|
promise_test(async (t) => {
|
||||||
|
await waitForCompositorCommit();
|
||||||
|
|
||||||
|
focusAndAssert(lefttarget1, /*preventScroll=*/true);
|
||||||
|
await runScrollSnapSelectionVerificationTest(t, outercontainer,
|
||||||
|
[leftcontainer, rightcontainer], leftcontainer, "y");
|
||||||
|
|
||||||
|
focusAndAssert(righttarget1, /*preventScroll=*/true);
|
||||||
|
await runScrollSnapSelectionVerificationTest(t, outercontainer,
|
||||||
|
[leftcontainer, rightcontainer], rightcontainer, "y");
|
||||||
|
}, "Snap container prefers focused nested snap target.");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,148 @@
|
||||||
|
// Utility functions for scroll snap tests which verify User-Agents' snap point
|
||||||
|
// selection logic when multiple snap targets are aligned.
|
||||||
|
// It depends on methods in /resources/testdriver-actions.js and
|
||||||
|
// /dom/event/scrolling/scroll_support.js so html files using these functions
|
||||||
|
// should include those files as <script>s.
|
||||||
|
|
||||||
|
// This function should be used by scroll snap WPTs wanting to test snap target
|
||||||
|
// selection when scrolling to multiple aligned targets.
|
||||||
|
// It assumes scroll-snap-align: start alignment and tries to align to the list
|
||||||
|
// of snap targets provided, |elements|, which are all expected to be at the
|
||||||
|
// same offset.
|
||||||
|
async function scrollToAlignedElementsInAxis(scroller, elements, axis) {
|
||||||
|
let target_offset_y = null;
|
||||||
|
let target_offset_x = null;
|
||||||
|
if (axis == "y") {
|
||||||
|
for (const e of elements) {
|
||||||
|
if (target_offset_y) {
|
||||||
|
assert_equals(e.offsetTop, target_offset_y,
|
||||||
|
`${e.id} is at y offset ${target_offset_y}`);
|
||||||
|
} else {
|
||||||
|
target_offset_y = e.offsetTop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_equals();
|
||||||
|
} else {
|
||||||
|
for (const e of elements) {
|
||||||
|
if (target_offset_x) {
|
||||||
|
assert_equals(e.offsetLeft, target_offset_x,
|
||||||
|
`${e.id} is at x offset ${target_offset_x}`);
|
||||||
|
} else {
|
||||||
|
target_offset_x = e.offsetLeft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_not_equals(target_offset_x || target_offset_y, null);
|
||||||
|
|
||||||
|
const scrollend_promise = waitForScrollendEventNoTimeout(scroller);
|
||||||
|
await new test_driver.Actions().scroll(0, 0,
|
||||||
|
(target_offset_x || 0) - scroller.scrollLeft,
|
||||||
|
(target_offset_y || 0) - scroller.scrollTop,
|
||||||
|
{ origin: scroller })
|
||||||
|
.send();
|
||||||
|
await scrollend_promise;
|
||||||
|
if (axis == "y") {
|
||||||
|
assert_equals(scroller.scrollTop, target_offset_y, "vertical scroll done");
|
||||||
|
} else {
|
||||||
|
assert_equals(scroller.scrollLeft,target_offset_x, "horizontal scroll done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function verifies the snap target that a scroller picked by triggerring
|
||||||
|
// a layout change and observing which target is followed. Tests using this
|
||||||
|
// method should ensure that there is at least 100px of room to scroll in the
|
||||||
|
// desired axis.
|
||||||
|
// It assumes scroll-snap-align: start alignment.
|
||||||
|
function verifySelectedSnapTarget(scroller, expected_snap_target, axis) {
|
||||||
|
// Save initial style.
|
||||||
|
const initial_left = getComputedStyle(expected_snap_target).left;
|
||||||
|
const initial_top = getComputedStyle(expected_snap_target).top;
|
||||||
|
if (axis == "y") {
|
||||||
|
// Move the expected snap target along the y axis.
|
||||||
|
const initial_scroll_top = scroller.scrollTop;
|
||||||
|
const target_top = expected_snap_target.offsetTop + 100;
|
||||||
|
expected_snap_target.style.top = `${target_top}px`;
|
||||||
|
assert_equals(scroller.scrollTop, expected_snap_target.offsetTop,
|
||||||
|
`scroller followed ${expected_snap_target.id} after layout change`);
|
||||||
|
assert_not_equals(scroller.scrollTop, initial_scroll_top,
|
||||||
|
"scroller actually scrolled in y axis");
|
||||||
|
} else {
|
||||||
|
// Move the expected snap target along the y axis.
|
||||||
|
const initial_scroll_left = scroller.scrollLeft;
|
||||||
|
const target_left = expected_snap_target.offsetLeft + 100;
|
||||||
|
expected_snap_target.style.left = `${target_left}px`;
|
||||||
|
assert_equals(scroller.scrollLeft, expected_snap_target.offsetLeft,
|
||||||
|
`scroller followed ${expected_snap_target.id} after layout change`);
|
||||||
|
assert_not_equals(scroller.scrollLeft, initial_scroll_left,
|
||||||
|
"scroller actually scrolled in x axis");
|
||||||
|
}
|
||||||
|
// Undo style changes.
|
||||||
|
expected_snap_target.style.top = initial_top;
|
||||||
|
expected_snap_target.style.left = initial_left;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a utility function for tests which verify that the correct element
|
||||||
|
// is snapped to when snapping at the end of a scroll.
|
||||||
|
async function runScrollSnapSelectionVerificationTest(t, scroller, aligned_elements,
|
||||||
|
expected_target, axis) {
|
||||||
|
// Save initial scroll offset.
|
||||||
|
const initial_scroll_left = scroller.scrollLeft;
|
||||||
|
const initial_scroll_top = scroller.scrollTop;
|
||||||
|
await scrollToAlignedElementsInAxis(scroller, aligned_elements, axis);
|
||||||
|
verifySelectedSnapTarget(scroller, expected_target, axis);
|
||||||
|
// Restore initial scroll offsets.
|
||||||
|
const scrollend_promise = new Promise((resolve) => {
|
||||||
|
scroller.addEventListener("scrollend", resolve);
|
||||||
|
});
|
||||||
|
scroller.scrollTo(initial_scroll_left, initial_scroll_top);
|
||||||
|
await scrollend_promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a utility function for tests verifying that a layout shift does not
|
||||||
|
// cause a scroller to change its selected snap target.
|
||||||
|
// It assumes the element to be aligned have scroll-snap-align: start.
|
||||||
|
// It tries to align the list of snap targets provided, |elements| with the
|
||||||
|
// current snap target.
|
||||||
|
function shiftLayoutToAlignElements(elements, target, axis) {
|
||||||
|
for (let element of elements) {
|
||||||
|
if (axis == "y") {
|
||||||
|
element.style.top = `${target.offsetTop}px`;
|
||||||
|
} else {
|
||||||
|
element.style.left = `${target.offsetLeft}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a utility function for tests verifying that a layout shift does not
|
||||||
|
// cause a scroller to change its selected snap target.
|
||||||
|
// It assumes scroll-snap-align: start alignment.
|
||||||
|
async function runLayoutSnapSeletionVerificationTest(t, scroller, elements_to_align,
|
||||||
|
expected_target, axis) {
|
||||||
|
// Save initial scroll offsets and position.
|
||||||
|
const initial_scroll_left = scroller.scrollLeft;
|
||||||
|
const initial_scroll_top = scroller.scrollTop;
|
||||||
|
let initial_tops = [];
|
||||||
|
for (const element of elements_to_align) {
|
||||||
|
initial_tops.push(getComputedStyle(element).top);
|
||||||
|
}
|
||||||
|
|
||||||
|
shiftLayoutToAlignElements(elements_to_align, expected_target, axis);
|
||||||
|
verifySelectedSnapTarget(scroller, expected_target, axis);
|
||||||
|
|
||||||
|
// Restore initial scroll offset and position states.
|
||||||
|
let num_elements = initial_tops.length;
|
||||||
|
for (let i = 0; i < num_elements; i++) {
|
||||||
|
elements_to_align[i].style.top = initial_tops[i];
|
||||||
|
}
|
||||||
|
// Restore initial scroll offsets.
|
||||||
|
const scrollend_promise = new Promise((resolve) => {
|
||||||
|
scroller.addEventListener("scrollend", resolve);
|
||||||
|
});
|
||||||
|
scroller.scrollTo(initial_scroll_left, initial_scroll_top);
|
||||||
|
await scrollend_promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusAndAssert(element, preventScroll=false) {
|
||||||
|
element.focus({preventScroll: preventScroll});
|
||||||
|
assert_equals(document.activeElement, element);
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче