Merge pull request #3353 from lightpanda-io/body-client-dimensions

render: clientHeight/clientWidth returns the viewport for the body
This commit is contained in:
Karl Seguin authored and GitHub committed 2026-08-31 19:28:56 +08:00
commit efb20f65b4
2 files changed
+44 -13

No files matched your search

+11 -6
View File
@@ -100,10 +100,13 @@
testing.expectTrue(zeroWidth.scrollWidth > 0);
// The root containers keep their synthetic size rather than summing
// children, so horizontal page-overflow checks stay stable.
// children, so horizontal page-overflow checks stay stable. In standards
// mode the root element's clientWidth is the viewport (CSSOM View), not
// its box; offsetWidth and scrollWidth still report the box.
testing.expectEqual(document.body.clientWidth, document.body.scrollWidth);
testing.expectEqual(window.innerWidth, document.documentElement.clientWidth);
testing.expectEqual(
document.documentElement.clientWidth,
document.documentElement.offsetWidth,
document.documentElement.scrollWidth,
);
}
@@ -335,14 +338,16 @@
testing.expectTrue(panel.scrollHeight > 0);
// The root containers keep their synthetic size rather than summing
// children. This is what keeps infinite-scroll triggers reading
// `scrollTop + clientHeight >= scrollHeight` on body/documentElement
// behaving as they do today.
// children. The root element's clientHeight is the viewport (CSSOM View):
// jstracker tiles clientWidth/10 cells over clientHeight and calls
// elementFromPoint per cell, which the 100M px box turned into ~5M walks.
testing.expectEqual(document.body.clientHeight, document.body.scrollHeight);
testing.expectEqual(window.innerHeight, document.documentElement.clientHeight);
testing.expectEqual(
document.documentElement.clientHeight,
document.documentElement.offsetHeight,
document.documentElement.scrollHeight,
);
testing.expectTrue(document.documentElement.scrollHeight > document.documentElement.clientHeight);
}
</script>
+33 -7
View File
@@ -1398,17 +1398,35 @@ pub fn getElementAxis(self: *Element, frame: *Frame, comptime axis: Axis) Axis.S
// width / height treshold is reached. If the size isn't explicit, we fallback
// to the content size.
pub fn getClientWidth(self: *Element, frame: *Frame) f64 {
if (!self.checkVisibilityCached(null, frame, .materialize)) {
return 0.0;
}
return self.boxAxis(frame, .width);
return self.clientAxis(frame, .width);
}
pub fn getClientHeight(self: *Element, frame: *Frame) f64 {
return self.clientAxis(frame, .height);
}
fn clientAxis(self: *Element, frame: *Frame, comptime axis: Axis) f64 {
if (!self.checkVisibilityCached(null, frame, .materialize)) {
return 0.0;
}
return self.boxAxis(frame, .height);
return self.viewportAxis(frame, axis) orelse self.boxAxis(frame, axis);
}
fn viewportAxis(self: *Element, frame: *Frame, comptime axis: Axis) ?f64 {
const tag = self.getTag();
if (tag != .html and tag != .body) {
return null;
}
const doc = self.asNode().ownerDocument(frame) orelse frame.document;
if ((tag == .body) != doc.isQuirksMode()) {
return null;
}
// In quicks mode, the root element (the body) reports the viewport for
// clientWidth and clientHeight rather than its own MASSIVE box. This
// fixes jstracker's uiContourMap which attempts to tile the clientHeight
// of the body. (https://github.com/lightpanda-io/browser/issues/3251)
const viewport = frame._page.getViewport();
return @floatFromInt(if (axis == .width) viewport.width else viewport.height);
}
// Caller must have made sure self is visible.
@@ -1581,12 +1599,20 @@ fn contentAxis(self: *Element, frame: *Frame, comptime axis: Axis) f64 {
return total;
}
// Unlike clientHeight, the root's offsetHeight is its box (the document
// extent), so it stays on the synthetic root default.
pub fn getOffsetHeight(self: *Element, frame: *Frame) f64 {
return self.getClientHeight(frame);
if (!self.checkVisibilityCached(null, frame, .materialize)) {
return 0.0;
}
return self.boxAxis(frame, .height);
}
pub fn getOffsetWidth(self: *Element, frame: *Frame) f64 {
return self.getClientWidth(frame);
if (!self.checkVisibilityCached(null, frame, .materialize)) {
return 0.0;
}
return self.boxAxis(frame, .width);
}
pub fn getOffsetTop(self: *Element, frame: *Frame) f64 {