From 4b90c8fd458963e2d5c8159686bc17487f1b827d Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 12 Feb 2026 17:38:00 +0800 Subject: [PATCH] Add various element position properties clientTop, clientLeft, scrollTop, scrollLeft, scrollHeight, scrollWidth, offsetTop, offsetLeft, offsetWidth, offsetHeight. These are all dummy implementation that hook, as much as possible, into what layout information we have. Explicitly set scroll information is stored on the page. --- src/browser/Page.zig | 1 + src/browser/tests/element/position.html | 116 ++++++++++++++++++++++++ src/browser/webapi/Element.zig | 92 +++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 src/browser/tests/element/position.html diff --git a/src/browser/Page.zig b/src/browser/Page.zig index b5f41df9..f34cb84e 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -106,6 +106,7 @@ _element_rel_lists: Element.RelListLookup = .empty, _element_shadow_roots: Element.ShadowRootLookup = .empty, _node_owner_documents: Node.OwnerDocumentLookup = .empty, _element_assigned_slots: Element.AssignedSlotLookup = .empty, +_element_scroll_positions: Element.ScrollPositionLookup = .empty, /// Lazily-created inline event listeners (or listeners provided as attributes). /// Avoids bloating all elements with extra function fields for rare usage. diff --git a/src/browser/tests/element/position.html b/src/browser/tests/element/position.html new file mode 100644 index 00000000..178951dc --- /dev/null +++ b/src/browser/tests/element/position.html @@ -0,0 +1,116 @@ + + + +
Test Element
+
Another Element
+ + + + + + + + + + + + diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index 81cef212..3d8dbbd0 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -49,6 +49,12 @@ pub const RelListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMTok pub const ShadowRootLookup = std.AutoHashMapUnmanaged(*Element, *ShadowRoot); pub const AssignedSlotLookup = std.AutoHashMapUnmanaged(*Element, *Html.Slot); +pub const ScrollPosition = struct { + x: u32 = 0, + y: u32 = 0, +}; +pub const ScrollPositionLookup = std.AutoHashMapUnmanaged(*Element, ScrollPosition); + pub const Namespace = enum(u8) { html, svg, @@ -1027,6 +1033,82 @@ pub fn getClientRects(self: *Element, page: *Page) ![]DOMRect { return ptr[0..1]; } +pub fn getScrollTop(self: *Element, page: *Page) u32 { + const pos = page._element_scroll_positions.get(self) orelse return 0; + return pos.y; +} + +pub fn setScrollTop(self: *Element, value: i32, page: *Page) !void { + const gop = try page._element_scroll_positions.getOrPut(page.arena, self); + if (!gop.found_existing) { + gop.value_ptr.* = .{}; + } + gop.value_ptr.y = @intCast(@max(0, value)); +} + +pub fn getScrollLeft(self: *Element, page: *Page) u32 { + const pos = page._element_scroll_positions.get(self) orelse return 0; + return pos.x; +} + +pub fn setScrollLeft(self: *Element, value: i32, page: *Page) !void { + const gop = try page._element_scroll_positions.getOrPut(page.arena, self); + if (!gop.found_existing) { + gop.value_ptr.* = .{}; + } + gop.value_ptr.x = @intCast(@max(0, value)); +} + +pub fn getScrollHeight(self: *Element, page: *Page) !f64 { + // In our dummy layout engine, content doesn't overflow + return self.getClientHeight(page); +} + +pub fn getScrollWidth(self: *Element, page: *Page) !f64 { + // In our dummy layout engine, content doesn't overflow + return self.getClientWidth(page); +} + +pub fn getOffsetHeight(self: *Element, page: *Page) !f64 { + if (!try self.checkVisibility(page)) { + return 0.0; + } + const dims = try self.getElementDimensions(page); + return dims.height; +} + +pub fn getOffsetWidth(self: *Element, page: *Page) !f64 { + if (!try self.checkVisibility(page)) { + return 0.0; + } + const dims = try self.getElementDimensions(page); + return dims.width; +} + +pub fn getOffsetTop(self: *Element, page: *Page) !f64 { + if (!try self.checkVisibility(page)) { + return 0.0; + } + return calculateDocumentPosition(self.asNode()); +} + +pub fn getOffsetLeft(self: *Element, page: *Page) !f64 { + if (!try self.checkVisibility(page)) { + return 0.0; + } + return calculateSiblingPosition(self.asNode()); +} + +pub fn getClientTop(_: *Element) f64 { + // Border width - in our dummy layout, we don't apply borders to layout + return 0.0; +} + +pub fn getClientLeft(_: *Element) f64 { + // Border width - in our dummy layout, we don't apply borders to layout + return 0.0; +} + // Calculates document position by counting all nodes that appear before this one // in tree order, but only traversing the "left side" of the tree. // @@ -1502,6 +1584,16 @@ pub const JsApi = struct { pub const checkVisibility = bridge.function(Element.checkVisibility, .{}); pub const clientWidth = bridge.accessor(Element.getClientWidth, null, .{}); pub const clientHeight = bridge.accessor(Element.getClientHeight, null, .{}); + pub const clientTop = bridge.accessor(Element.getClientTop, null, .{}); + pub const clientLeft = bridge.accessor(Element.getClientLeft, null, .{}); + pub const scrollTop = bridge.accessor(Element.getScrollTop, Element.setScrollTop, .{}); + pub const scrollLeft = bridge.accessor(Element.getScrollLeft, Element.setScrollLeft, .{}); + pub const scrollHeight = bridge.accessor(Element.getScrollHeight, null, .{}); + pub const scrollWidth = bridge.accessor(Element.getScrollWidth, null, .{}); + pub const offsetTop = bridge.accessor(Element.getOffsetTop, null, .{}); + pub const offsetLeft = bridge.accessor(Element.getOffsetLeft, null, .{}); + pub const offsetWidth = bridge.accessor(Element.getOffsetWidth, null, .{}); + pub const offsetHeight = bridge.accessor(Element.getOffsetHeight, null, .{}); pub const getClientRects = bridge.function(Element.getClientRects, .{}); pub const getBoundingClientRect = bridge.function(Element.getBoundingClientRect, .{}); pub const getElementsByTagName = bridge.function(Element.getElementsByTagName, .{});