Skip to content

Commit

Permalink
[css-scroll-snap-2] Prioritize targeted snap areas
Browse files Browse the repository at this point in the history
Per step 4 of [1], chromium should give preference to targeted[2] elements when there are multiple aligned options.

Some improvements are made to the test files such as:
(in common.js)
- scrollToAlignedElementsInAxis now detects already being at the scroll
  position aligned with the snap targets.
- wrapping verifySelectedSnapTarget in a t.step so that a failure during
  the verification doesn't lead to timeouts.
(in scroll_support.js)
- waitForScrollReset now takes x and y params which default to 0,0, so
- tests can now use the function to default to positions other than 0,0.

Bug: 324916797

[1]w3c/csswg-drafts#9622 (comment)
[2]https://drafts.csswg.org/selectors/#the-target-pseudo

Change-Id: Ifbade7f2b2fd5e0b4579e8c5430b7ae8616be797
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5292274
Commit-Queue: David Awogbemila <awogbemila@chromium.org>
Reviewed-by: Steve Kobes <skobes@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1264011}
  • Loading branch information
David Awogbemila authored and chromium-wpt-export-bot committed Feb 22, 2024
1 parent 52e1352 commit dce6c69
Show file tree
Hide file tree
Showing 6 changed files with 315 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html>
<body>
<style>
.target {
width: 100px;
height: 100px;
background-color: green;
scroll-snap-align: start;
}
.placeholder {
background-color: purple;
}
.snapcontainer {
border:solid 1px black;
overflow: scroll;
scroll-snap-type: y mandatory;
}
.big {
height: 315px;
width: 600px;
position: relative;
}
.small {
height: 115px;
width: 120px;
}
.positioned {
position: absolute;
}
#target1, #target2, #target3, #target4, #target5 {
top: 400px;
}
#target1 {
left: 0px;
}
#target2 {
left: 110px;
}
#target3 {
left: 220px;
}
#target4 {
left: 330px;
}
#target5 {
left: 440px;
}
:target {
background-color: yellow;
}
.large-space {
position: absolute;
height: 300vh;
width: 300vw;
}
</style>
<div id="outer" class="big snapcontainer">
<div id="outerplaceholder1" class="placeholder target"></div>
<div id="outerplaceholder2" class="placeholder target"></div>
<div id="inner" class="small snapcontainer">
<div id="innerplaceholder1" class="placeholder target"></div>
<div id="innerplaceholder2" class="placeholder target"></div>
<div id="target1" class="positioned target"><h1>Box 1</h1></div>
<div id="target2" class="positioned target"><h1>Box 2</h1></div>
<div id="target3" class="positioned target"><h1>Box 3</h1></div>
<div id="target4" class="positioned target"><h1>Box 4</h1></div>
<div id="target5" class="positioned target"><h1>Box 5</h1></div>
</div>
<div class="large-space"></div>
</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html>
<body>
<style>
.scroller {
overflow: scroll;
width: 350px;
height: 350px;
border: solid 1px black;
scroll-snap-type: y mandatory;
position: relative;
resize: both;
}
.large-space {
height: 300vh;
width: 300vw;
position: absolute;
}
.snap {
scroll-snap-align: start;
}
.box {
width: 100px;
height: 100px;
background-color: green;
display: inline-block;
position: relative;
}
.grid {
position: absolute;
width: 350px;
height: 350px;
}
.snap:target {
background-color: blue;
}
</style>
<div class="scroller" id="scroller">
<div class="large-space"></div>
<div class="grid" id="grid">
<div id="box1" class="snap box">Box 1</div>
<div id="box2" class="snap box">Box 2</div>
<div id="box3" class="snap box">Box 3</div>
<div id="box4" class="snap box">Box 4</div>
<div id="box5" class="snap box">Box 5</div>
<div id="box6" class="snap box">Box 6</div>
<div id="box7" class="snap box">Box 7</div>
<div id="box8" class="snap box">Box 8</div>
<div id="box9" class="snap box">Box 9</div>
</div>
</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!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>
.iframe {
height: 1000px;
width: 1000px;
}
</style>
<script>
window.onload = async () => {
await waitForCompositorCommit();
async function test(target_number) {
return promise_test(async (t) => {
let finish = null;
const finished = new Promise((res) => { finish = res; });
var iframe = document.createElement("iframe");
iframe.classList.add("iframe");
iframe.onload = async () => {
let boxes =
iframe.contentDocument.getElementsByClassName("positioned");
const box = (i) => {
return boxes[i - 1];
}
let scroller = iframe.contentDocument.getElementById("outer");
// There are 5 aligned boxes in positioned-target-iframe.html.
assert_equals(boxes.length, 5);
await runScrollSnapSelectionVerificationTest(t, scroller,
[box(1), box(2), box(3), box(4), box(5)],
box(target_number), "y");

// Let scroller no longer be a scroll container.
scroller.style.overflow = "visible";
assert_equals(scroller.scrollTop, 0);

// Let scroller be a scroll container once again.
scroller.style.overflow = "scroll";

// Run the test again.
await runScrollSnapSelectionVerificationTest(t, scroller,
[box(1), box(2), box(3), box(4), box(5)],
box(target_number), "y");
finish();
};
iframe.src = `positioned-target-iframe.html#target${target_number}`;
document.body.appendChild(iframe);
await finished;
document.body.removeChild(iframe);
}, "");
}

await test(1);
await test(2);
await test(3);
await test(4);
await test(5);
}
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<!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>
.iframe {
width: 1000px;
height: 1000px;
}
</style>
<script>
window.onload = async () => {
await waitForCompositorCommit();
// This test sets up a 3x3 grid within scroller:
// -------------------------
// | Box 1 | Box 2 | Box 3 |
// ------------------------
// | Box 4 | Box 5 | Box 6 |
// -------------------------
// | Box 7 | Box 8 | Box 9 |
// -------------------------
// within an iframe.
// This function just gets the numbers beside |box_number| on each row.
// E.g. 4: 4%3 = 1; so the nmubers we want are 5 (4+1) and 6 (4+2).
function getAlignedNumbers(n) {
const mod_3 = n % 3;
if (mod_3 == 1) {
return [n + 1, n + 2];
} else if (mod_3 == 2) {
return [n - 1, n + 1];
}
return [n - 1, n - 2];
}

async function test(box_number) {
return promise_test(async (t) => {
let [other_box_1, other_box_2] = getAlignedNumbers(box_number);
let finish = null;
const finished = new Promise((res) => {
finish = res;
});
var iframe = document.createElement("iframe");
iframe.classList.add("iframe");
iframe.onload = async () => {
let boxes = iframe.contentDocument.getElementsByClassName("box");
const box = (i) => {
return boxes[i - 1];
}
let scroller = iframe.contentDocument.getElementById("scroller");
assert_equals(boxes.length, 9);
await runScrollSnapSelectionVerificationTest(t, scroller,
[box(box_number), box(other_box_1), box(other_box_2)],
box(box_number), "y");

// Let scroller no longer be a scroll container.
scroller.style.overflow = "visible";
assert_equals(scroller.scrollTop, 0);

// Let scroller be a scroll container once again.
scroller.style.overflow = "scroll";

// Run the test again.
await runScrollSnapSelectionVerificationTest(t, scroller,
[box(box_number), box(other_box_1), box(other_box_2)],
box(box_number), "y");

finish();
};
iframe.src = `prefer-targeted-element-iframe.html#box${box_number}`;
document.body.appendChild(iframe);
await finished;
document.body.removeChild(iframe);
}, `scroller selects targeted area box${box_number} among multiple` +
` aligned areas.`);
}

await test(1);
await test(2);
await test(3);
await test(4);
await test(5);
await test(6);
await test(7);
await test(8);
await test(9);
}
</script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@ async function scrollToAlignedElementsInAxis(scroller, elements, axis) {
}
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 ((target_offset_x != null && scroller.scrollLeft != target_offset_x) ||
(target_offset_y != null && scroller.scrollTop != target_offset_y)) {
const scrollend_promise = waitForScrollendEventNoTimeout(scroller);
await new test_driver.Actions().scroll(0, 0,
(target_offset_x || scroller.scrollLeft) - scroller.scrollLeft,
(target_offset_y || scroller.scrollTop) - scroller.scrollTop,
{ origin: scroller })
.send();
await scrollend_promise;
}
if (axis == "y") {
assert_equals(scroller.scrollTop, target_offset_y, "vertical scroll done");
} else {
Expand Down Expand Up @@ -89,13 +92,12 @@ async function runScrollSnapSelectionVerificationTest(t, scroller, aligned_eleme
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);
// Wrapping this in t.step prevents timeouts in the event of a failure.
t.step(async () => {
verifySelectedSnapTarget(scroller, expected_target, axis);
});
scroller.scrollTo(initial_scroll_left, initial_scroll_top);
await scrollend_promise;
// Restore initial scroll offsets.
await waitForScrollReset(t, scroller, initial_scroll_left, initial_scroll_top);
}

// This is a utility function for tests verifying that a layout shift does not
Expand Down
11 changes: 5 additions & 6 deletions dom/events/scrolling/scroll_support.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,16 @@ async function waitForPointercancelEvent(test, target, timeoutMs = 500) {

// Resets the scroll position to (0,0). If a scroll is required, then the
// promise is not resolved until the scrollend event is received.
async function waitForScrollReset(test, scroller, timeoutMs = 500) {
async function waitForScrollReset(test, scroller, x = 0, y = 0) {
return new Promise(resolve => {
if (scroller.scrollTop == 0 &&
scroller.scrollLeft == 0) {
if (scroller.scrollTop == x && scroller.scrollLeft == y) {
resolve();
} else {
const eventTarget =
scroller == document.scrollingElement ? document : scroller;
scroller.scrollTop = 0;
scroller.scrollLeft = 0;
waitForScrollendEvent(test, eventTarget, timeoutMs).then(resolve);
scroller.scrollTop = y;
scroller.scrollLeft = x;
waitForScrollendEventNoTimeout(eventTarget).then(resolve);
}
});
}
Expand Down

0 comments on commit dce6c69

Please sign in to comment.