diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index fe1d4ec1..4fb65b70 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -488,6 +488,7 @@ pub const JsApis = flattenTypes(&.{ @import("../webapi/DOMImplementation.zig"), @import("../webapi/DOMTreeWalker.zig"), @import("../webapi/DOMNodeIterator.zig"), + @import("../webapi/DOMRect.zig"), @import("../webapi/NodeFilter.zig"), @import("../webapi/Element.zig"), @import("../webapi/element/DOMStringMap.zig"), diff --git a/src/browser/webapi/DOMRect.zig b/src/browser/webapi/DOMRect.zig new file mode 100644 index 00000000..6309a20e --- /dev/null +++ b/src/browser/webapi/DOMRect.zig @@ -0,0 +1,64 @@ +const DOMRect = @This(); + +const js = @import("../js/js.zig"); +const Page = @import("../Page.zig"); + +_x: f64, +_y: f64, +_width: f64, +_height: f64, +_top: f64, +_right: f64, +_bottom: f64, +_left: f64, + +pub fn getX(self: *DOMRect) f64 { + return self._x; +} + +pub fn getY(self: *DOMRect) f64 { + return self._y; +} + +pub fn getWidth(self: *DOMRect) f64 { + return self._width; +} + +pub fn getHeight(self: *DOMRect) f64 { + return self._height; +} + +pub fn getTop(self: *DOMRect) f64 { + return self._top; +} + +pub fn getRight(self: *DOMRect) f64 { + return self._right; +} + +pub fn getBottom(self: *DOMRect) f64 { + return self._bottom; +} + +pub fn getLeft(self: *DOMRect) f64 { + return self._left; +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(DOMRect); + + pub const Meta = struct { + pub const name = "DOMRect"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; + + pub const x = bridge.accessor(DOMRect.getX, null, .{}); + pub const y = bridge.accessor(DOMRect.getY, null, .{}); + pub const width = bridge.accessor(DOMRect.getWidth, null, .{}); + pub const height = bridge.accessor(DOMRect.getHeight, null, .{}); + pub const top = bridge.accessor(DOMRect.getTop, null, .{}); + pub const right = bridge.accessor(DOMRect.getRight, null, .{}); + pub const bottom = bridge.accessor(DOMRect.getBottom, null, .{}); + pub const left = bridge.accessor(DOMRect.getLeft, null, .{}); +}; diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index 0ca65757..7849e3e3 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -13,6 +13,8 @@ const Selector = @import("selector/Selector.zig"); pub const Attribute = @import("element/Attribute.zig"); const CSSStyleProperties = @import("css/CSSStyleProperties.zig"); pub const DOMStringMap = @import("element/DOMStringMap.zig"); +const DOMRect = @import("DOMRect.zig"); +const css = @import("css.zig"); pub const Svg = @import("element/Svg.zig"); pub const Html = @import("element/Html.zig"); @@ -467,6 +469,126 @@ pub fn querySelectorAll(self: *Element, input: []const u8, page: *Page) !*Select return Selector.querySelectorAll(self.asNode(), input, page); } +pub fn parentElement(self: *Element) ?*Element { + return self._proto.parentElement(); +} + +pub fn checkVisibility(self: *Element, page: *Page) !bool { + var current: ?*Element = self; + + while (current) |el| { + const style = try el.getStyle(page); + const display = style.asCSSStyleDeclaration().getPropertyValue("display", page); + if (std.mem.eql(u8, display, "none")) { + return false; + } + current = el.parentElement(); + } + + return true; +} + +pub fn getBoundingClientRect(self: *Element, page: *Page) !*DOMRect { + const is_visible = try self.checkVisibility(page); + if (!is_visible) { + return page._factory.create(DOMRect{ + ._x = 0.0, + ._y = 0.0, + ._width = 0.0, + ._height = 0.0, + ._top = 0.0, + ._right = 0.0, + ._bottom = 0.0, + ._left = 0.0, + }); + } + + const y = calculateDocumentPosition(self.asNode()); + + var width: f64 = 1.0; + var height: f64 = 1.0; + + const style = try self.getStyle(page); + const decl = style.asCSSStyleDeclaration(); + width = css.parseDimension(decl.getPropertyValue("width", page)) orelse 1.0; + height = css.parseDimension(decl.getPropertyValue("height", page)) orelse 1.0; + + if (width == 1.0 or height == 1.0) { + const tag = self.getTag(); + if (tag == .img or tag == .iframe) { + if (self.getAttributeSafe("width")) |w| { + width = std.fmt.parseFloat(f64, w) catch width; + } + if (self.getAttributeSafe("height")) |h| { + height = std.fmt.parseFloat(f64, h) catch height; + } + } + } + + const x: f64 = 0.0; + const top = y; + const left = x; + const right = x + width; + const bottom = y + height; + + return page._factory.create(DOMRect{ + ._x = x, + ._y = y, + ._width = width, + ._height = height, + ._top = top, + ._right = right, + ._bottom = bottom, + ._left = left, + }); +} + +// Calculates a pseudo-position in the document using an efficient heuristic. +// +// Instead of walking the entire DOM tree (which would be O(total_nodes)), this +// function walks UP the tree counting previous siblings at each level. Each level +// uses exponential weighting (1000x per depth level) to preserve document order. +// +// This gives O(depth * avg_siblings) complexity while maintaining relative positioning +// that's useful for scraping and understanding element flow in the document. +// +// Example: +//
→ position 0 +//