diff --git a/src/browser/tests/element/position.html b/src/browser/tests/element/position.html index 6147cb0b4..291bf60f3 100644 --- a/src/browser/tests/element/position.html +++ b/src/browser/tests/element/position.html @@ -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); } diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index 70b046bca..50a1ad790 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -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 {