Files
sousa-gecko/testing/web-platform/tests/css/css-overflow/parsing/getComputedStyle-scroll-button.html
T
Daniil Sakhapov c79406ad8f Bug 2041032 [wpt PR 60009] - Fix getComputedStyle() for scroll-button pseudo-elements, a=testonly
Automatic update from web-platform-tests
Fix getComputedStyle() for scroll-button pseudo-elements

During pseudo-element parsing for getComputedStyle() in
CSSSelectorParser::ParsePseudoElement, the argument for ::scroll-button
was discarded.

Because of it, getComputedStyle() mapped all directional
scroll buttons to the same pseudo ID and empty argument, so they
collided during style resolution.

The selector checker, matching on a regular ultimate originating element
instead of the pseudo-element itself, ignored the argument, making all
::scroll-button() rules match and overwrite each other

Because of it, Element::GetStyledPseudoElement returned nullptr for the
general kPseudoIdScrollButton, getComputedStyle() fell back to the
originating element and resolved style directly on it, completely
bypassing the pseudo-element's actual layout object.

The fix to preserve the scroll-button's parsed argument and resolving
the general pseudo ID to the specific directional scroll-button ID in
GetStyledPseudoElement, allowing the correct pseudo-element child and
its layout object to be successfully retrieved.

Change-Id: I72648a3c6405395dec254f9a567ed6e50bf71754
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7852571
Reviewed-by: Rune Lillesveen <futhark@chromium.org>
Commit-Queue: Daniil Sakhapov <sakhapov@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1633502}

--

wpt-commits: 5cd9c22f05a0403ff0f8a51fd1f611030aad80ae
wpt-pr: 60009
2026-05-22 14:17:11 +00:00

144 lines
6.1 KiB
HTML

<!doctype html>
<meta charset="utf-8">
<title>CSS Overflow Test: getComputedStyle() resolves different properties for scroll-button pseudo-elements</title>
<link rel="help" href="https://drafts.csswg.org/css-overflow-5/#scroll-buttons">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
.scroller {
width: 200px;
height: 200px;
overflow: auto;
}
#scroller-vertical {
writing-mode: vertical-rl;
direction: ltr;
}
/* Test 1: Horizontal LTR synonym matching */
#scroller-horizontal::scroll-button(inline-start) {
content: "A";
width: 30px;
height: 20px;
}
#scroller-horizontal::scroll-button(left) {
content: "B"; /* Should cascade and overwrite inline-start */
width: 35px;
height: 20px;
}
/* Test 2: Vertical-RL writing mode mapping */
#scroller-vertical::scroll-button(inline-start) {
content: "V-Start";
width: 45px;
height: 20px;
}
#scroller-vertical::scroll-button(up) {
content: "V-Up"; /* Should cascade and overwrite inline-start (which is up) */
width: 50px;
height: 20px;
}
#scroller-vertical::scroll-button(block-start) {
content: "V-BStart";
width: 55px;
height: 20px;
}
#scroller-vertical::scroll-button(right) {
content: "V-Right"; /* Should cascade and overwrite block-start (which is right) */
width: 60px;
height: 20px;
}
/* Test 3: Scroll button styled without 'content' (not generated) */
#scroller-no-content::scroll-button(inline-start) {
width: 80px;
height: 20px;
}
/* Test 4: Non-scroller originating element */
#not-scroller::scroll-button(inline-start) {
content: "<";
width: 90px;
height: 20px;
}
</style>
<div id="scroller-horizontal" class="scroller">
<div style="width: 500px; height: 100px;"></div>
</div>
<div id="scroller-vertical" class="scroller">
<div style="width: 500px; height: 500px;"></div>
</div>
<div id="scroller-no-content" class="scroller">
<div style="width: 500px; height: 100px;"></div>
</div>
<div id="not-scroller" style="width: 100px; height: 100px;"></div>
<script>
test(() => {
const scroller = document.getElementById("scroller-horizontal");
const styleStart = getComputedStyle(scroller, "::scroll-button(inline-start)");
const styleLeft = getComputedStyle(scroller, "::scroll-button(left)");
// In horizontal LTR, inline-start and left map to the same pseudo-element.
// Therefore, the 'left' rule (defined later) should cascade and overwrite
// the 'inline-start' rule, and both queries must return identical values.
assert_equals(styleStart.content, '"B"', "inline-start should return cascaded 'B'");
assert_equals(styleStart.width, "35px", "inline-start should return cascaded 35px");
assert_equals(styleLeft.content, '"B"', "left should return cascaded 'B'");
assert_equals(styleLeft.width, "35px", "left should return cascaded 35px");
}, "inline-start and left map to the same pseudo-element in horizontal LTR");
test(() => {
const scroller = document.getElementById("scroller-vertical");
// In vertical-rl LTR, inline-start is up (top).
// Therefore, 'up' (defined later) should overwrite 'inline-start'.
const styleStart = getComputedStyle(scroller, "::scroll-button(inline-start)");
const styleUp = getComputedStyle(scroller, "::scroll-button(up)");
assert_equals(styleStart.content, '"V-Up"', "inline-start should map to up and be overwritten");
assert_equals(styleStart.width, "50px", "inline-start width should be overwritten");
assert_equals(styleUp.content, '"V-Up"', "up should return 'V-Up'");
assert_equals(styleUp.width, "50px", "up width should be 50px");
// In vertical-rl LTR, block-start is right.
// Therefore, 'right' (defined later) should overwrite 'block-start'.
const styleBStart = getComputedStyle(scroller, "::scroll-button(block-start)");
const styleRight = getComputedStyle(scroller, "::scroll-button(right)");
assert_equals(styleBStart.content, '"V-Right"', "block-start should map to right and be overwritten");
assert_equals(styleBStart.width, "60px", "block-start width should be overwritten");
assert_equals(styleRight.content, '"V-Right"', "right should return 'V-Right'");
assert_equals(styleRight.width, "60px", "right width should be 60px");
}, "Writing-mode vertical-rl LTR correctly maps logical directions to physical directions");
test(() => {
const scroller = document.getElementById("scroller-no-content");
const styleStart = getComputedStyle(scroller, "::scroll-button(inline-start)");
// The pseudo-element is not created because it has no content.
// However, getComputedStyle() should still resolve and return the styled width.
assert_equals(styleStart.content, "normal", "Should return default 'normal' content");
assert_equals(styleStart.width, "80px", "Should correctly resolve styled width");
}, "getComputedStyle() resolves properties when pseudo-element is not created due to no content");
test(() => {
const notScroller = document.getElementById("not-scroller");
const styleStart = getComputedStyle(notScroller, "::scroll-button(inline-start)");
// The originating element is not a scroll container, so the pseudo-element
// is generated in a disabled state (since it has styled content).
// getComputedStyle() should resolve and return its styled properties correctly.
assert_equals(styleStart.content, '"<"', "Should correctly resolve styled content");
assert_equals(styleStart.width, "90px", "Should correctly resolve styled width");
}, "getComputedStyle() resolves properties when the originating element is not a scroll container");
test(() => {
const scroller = document.getElementById("scroller-horizontal");
const styleInvalid = getComputedStyle(scroller, "::scroll-button(invalid-argument)");
// Invalid argument should not crash the browser, and instead fall back cleanly.
assert_equals(styleInvalid.width, "", "Should fall back cleanly and return empty width");
}, "getComputedStyle() with invalid argument falls back cleanly and does not crash");
</script>