layout: Round clientTop, etc queries to pixels properly (#31187)

* layout: Round getClientRect queries to pixels properly

Instead of just flooring all pixels in getClientRect queries, we should
round the rectangle.

* Fix scrollWidth/scrollHeight too, and tests

* Tests passing

* Test expectation for legacy layout

---------

Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Martin Robinson 2024-01-27 17:34:21 +01:00 committed by GitHub
parent bbba839278
commit bbe505e52b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 194 additions and 25 deletions

View file

@ -156,18 +156,20 @@ impl FragmentTree {
return Some(Rect::zero());
}
let padding_rect = padding_rect.to_physical(style.writing_mode, containing_block);
let border = style.get_border();
Some(Rect::new(
Point2D::new(
border.border_left_width.to_px(),
border.border_top_width.to_px(),
),
Size2D::new(
padding_rect.size.width.px() as i32,
padding_rect.size.height.px() as i32,
),
))
let padding_rect = padding_rect.to_physical(style.writing_mode, containing_block);
Some(
Rect::new(
Point2D::new(
border.border_left_width.to_f32_px(),
border.border_top_width.to_f32_px(),
),
Size2D::new(padding_rect.size.width.px(), padding_rect.size.height.px()),
)
.round()
.to_i32()
.to_untyped(),
)
})
.unwrap_or_else(Rect::zero)
}

View file

@ -211,9 +211,12 @@ pub fn process_node_scroll_area_request(
};
Rect::new(
Point2D::new(rect.origin.x.px() as i32, rect.origin.y.px() as i32),
Size2D::new(rect.size.width.px() as i32, rect.size.height.px() as i32),
Point2D::new(rect.origin.x.px(), rect.origin.y.px()),
Size2D::new(rect.size.width.px(), rect.size.height.px()),
)
.round()
.to_i32()
.to_untyped()
}
/// Return the resolved value of property for a given (pseudo)element.

View file

@ -0,0 +1,24 @@
[subpixel-sizes-and-offsets.tentative.html]
[clientWidth, offsetWidth and scrollWidth round 5.5 to 6]
expected: FAIL
[clientWidth, offsetWidth and scrollWidth round 5.9 to 6]
expected: FAIL
[clientHeight, offsetHeight and scrollHeight round 5.1 to 5]
expected: FAIL
[clientHeight, offsetHeight and scrollHeight round 5.5 to 6]
expected: FAIL
[clientHeight, offsetHeight and scrollHeight round 5.9 to 6]
expected: FAIL
[clientLeft and clientTop don't round 44.9]
expected: FAIL
[clientLeft and clientTop don't round 44.5]
expected: FAIL
[clientLeft and clientTop don't round 44.1]
expected: FAIL

View file

@ -562330,6 +562330,13 @@
{}
]
],
"subpixel-sizes-and-offsets.tentative.html": [
"d198b9dde60c8cac16241c412d3e55f772166010",
[
null,
{}
]
],
"table-border-collapse-client-width-height.html": [
"a7a1a435b2279ccb07136ed295dcc25b38c2c87c",
[

View file

@ -1,6 +0,0 @@
[fractional-percent-width.html]
[.cell 2]
expected: FAIL
[.cell 3]
expected: FAIL

View file

@ -0,0 +1,24 @@
[subpixel-sizes-and-offsets.tentative.html]
[clientWidth, offsetWidth and scrollWidth round 5.5 to 6]
expected: FAIL
[clientWidth, offsetWidth and scrollWidth round 5.9 to 6]
expected: FAIL
[clientHeight, offsetHeight and scrollHeight round 5.1 to 5]
expected: FAIL
[clientHeight, offsetHeight and scrollHeight round 5.5 to 6]
expected: FAIL
[clientHeight, offsetHeight and scrollHeight round 5.9 to 6]
expected: FAIL
[clientLeft and clientTop don't round 44.9]
expected: FAIL
[clientLeft and clientTop don't round 44.5]
expected: FAIL
[clientLeft and clientTop don't round 44.1]
expected: FAIL

View file

@ -1,3 +0,0 @@
[table-border-collapse-client-width-height.html]
[Table's clientWidth/Height and OffsetWidth/Height should be the same]
expected: FAIL

View file

@ -1,3 +0,0 @@
[table-border-separate-client-width-height.html]
[Table's clientWidth/Height and OffsetWidth/Height should be the same]
expected: FAIL

View file

@ -0,0 +1,121 @@
<!DOCTYPE html>
<title>CSSOM View Module test: subpixel sizes and offsets</title>
<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
<link rel="help" href="https://drafts.csswg.org/cssom-view/#extension-to-the-element-interface">
<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/9866">
<link rel="help" href="https://lists.w3.org/Archives/Public/www-style/2015Feb/0195.html">
<div style="overflow: hidden; position: relative">
<div id="target">
<div id="child"></div>
</div>
</div>
<div id="log"></div>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
// clientWidth, offsetWidth and scrollWidth round to the nearest integer.
test(function() {
target.style.cssText = "width: 5.1px";
assert_equals(target.clientWidth, 5, "clientWidth");
assert_equals(target.offsetWidth, 5, "offsetWidth");
assert_equals(target.scrollWidth, 5, "scrollWidth");
}, "clientWidth, offsetWidth and scrollWidth round 5.1 to 5");
test(function() {
target.style.cssText = "width: 5.5px";
assert_equals(target.clientWidth, 6, "clientWidth");
assert_equals(target.offsetWidth, 6, "offsetWidth");
assert_equals(target.scrollWidth, 6, "scrollWidth");
}, "clientWidth, offsetWidth and scrollWidth round 5.5 to 6");
test(function() {
target.style.cssText = "width: 5.9px";
assert_equals(target.clientWidth, 6, "clientWidth");
assert_equals(target.offsetWidth, 6, "offsetWidth");
assert_equals(target.scrollWidth, 6, "scrollWidth");
}, "clientWidth, offsetWidth and scrollWidth round 5.9 to 6");
// clientHeight, offsetHeight and scrollHeight round to the nearest integer.
test(function() {
target.style.cssText = "height: 5.1px";
assert_equals(target.clientHeight, 5, "clientHeight");
assert_equals(target.offsetHeight, 5, "offsetHeight");
assert_equals(target.scrollHeight, 5, "scrollHeight");
}, "clientHeight, offsetHeight and scrollHeight round 5.1 to 5");
test(function() {
target.style.cssText = "height: 5.5px";
assert_equals(target.clientHeight, 6, "clientHeight");
assert_equals(target.offsetHeight, 6, "offsetHeight");
assert_equals(target.scrollHeight, 6, "scrollHeight");
}, "clientHeight, offsetHeight and scrollHeight round 5.5 to 6");
test(function() {
target.style.cssText = "height: 5.9px";
assert_equals(target.clientHeight, 6, "clientHeight");
assert_equals(target.offsetHeight, 6, "offsetHeight");
assert_equals(target.scrollHeight, 6, "scrollHeight");
}, "clientHeight, offsetHeight and scrollHeight round 5.9 to 6");
// offsetLeft and offsetTop round to the nearest integer.
test(function() {
target.style.cssText = "margin: 5.1px";
assert_equals(target.offsetLeft, 5, "offsetLeft");
assert_equals(target.offsetTop, 5, "offsetTop");
}, "offsetLeft and offsetTop round 5.1 to 5");
test(function() {
target.style.cssText = "margin: 5.5px";
assert_equals(target.offsetLeft, 6, "offsetLeft");
assert_equals(target.offsetTop, 6, "offsetTop");
}, "offsetLeft and offsetTop round 5.5 to 6");
test(function() {
target.style.cssText = "margin: 5.9px";
assert_equals(target.offsetLeft, 6, "offsetLeft");
assert_equals(target.offsetTop, 6, "offsetTop");
}, "offsetLeft and offsetTop round 5.9 to 6");
// clientLeft and clientTop round the border width to the nearest integer.
// Note that the computed value of a border width is snapped to device pixels,
// so the results can vary depending on the device pixel ratio.
function borderLeftWidth() {
return child.getBoundingClientRect().left - target.getBoundingClientRect().left;
}
function borderTopWidth() {
return child.getBoundingClientRect().top - target.getBoundingClientRect().top;
}
test(function() {
target.style.cssText = "border: 5.1px solid";
assert_equals(target.clientLeft, Math.round(borderLeftWidth()), "clientLeft");
assert_equals(target.clientLeft, Math.round(borderTopWidth()), "clientTop");
}, "clientLeft and clientTop round 5.1");
test(function() {
target.style.cssText = "border: 5.5px solid";
assert_equals(target.clientLeft, Math.round(borderLeftWidth()), "clientLeft");
assert_equals(target.clientLeft, Math.round(borderTopWidth()), "clientTop");
}, "clientLeft and clientTop round 5.5");
test(function() {
target.style.cssText = "border: 5.9px solid";
assert_equals(target.clientLeft, Math.round(borderLeftWidth()), "clientLeft");
assert_equals(target.clientLeft, Math.round(borderTopWidth()), "clientTop");
}, "clientLeft and clientTop round 5.9");
// Unlike the attributes above, scrollLeft and scrollTop are `double`s,
// so the results shouldn't be rounded.
child.style.cssText = "width: 50px; height: 50px";
test(function() {
target.style.cssText = "overflow: hidden; width: 5.1px;";
target.scrollTo(target.scrollWidth, target.scrollHeight);
assert_equals(target.scrollLeft, 44.9, "scrollLeft");
assert_equals(target.scrollTop, 44.9, "scrollTop");
}, "clientLeft and clientTop don't round 44.9");
test(function() {
target.style.cssText = "overflow: hidden; width: 5.5px;";
target.scrollTo(target.scrollWidth, target.scrollHeight);
assert_equals(target.scrollLeft, 44.5, "scrollLeft");
assert_equals(target.scrollTop, 44.5, "scrollTop");
}, "clientLeft and clientTop don't round 44.5");
test(function() {
target.style.cssText = "overflow: hidden; width: 5.9px;";
target.scrollTo(target.scrollWidth, target.scrollHeight);
assert_equals(target.scrollLeft, 44.1, "scrollLeft");
assert_equals(target.scrollTop, 44.1, "scrollTop");
}, "clientLeft and clientTop don't round 44.1");
</script>