diff --git a/src/browser/Factory.zig b/src/browser/Factory.zig
index 9e1ee0685..00626ed12 100644
--- a/src/browser/Factory.zig
+++ b/src/browser/Factory.zig
@@ -35,6 +35,8 @@ const EventTarget = @import("webapi/EventTarget.zig");
const XMLHttpRequestEventTarget = @import("webapi/net/XMLHttpRequestEventTarget.zig");
const Blob = @import("webapi/Blob.zig");
const AbstractRange = @import("webapi/AbstractRange.zig");
+const DOMRect = @import("webapi/DOMRect.zig");
+const DOMRectReadOnly = @import("webapi/DOMRectReadOnly.zig");
const log = lp.log;
const String = lp.String;
@@ -292,6 +294,22 @@ pub fn abstractRange(_: *const Factory, arena: Allocator, child: anytype, frame:
return chain.get(1);
}
+pub fn domRect(self: *Factory, rect: DOMRectReadOnly.Data) !*DOMRect {
+ const chain = try PrototypeChain(&.{ DOMRectReadOnly, DOMRect }).allocate(self._slab.allocator());
+
+ const base = chain.get(0);
+ base.* = .{
+ ._type = .{ .mutable = chain.get(1) },
+ ._x = rect.x,
+ ._y = rect.y,
+ ._width = rect.width,
+ ._height = rect.height,
+ };
+ chain.setLeaf(1, DOMRect{ ._proto = base });
+
+ return chain.get(1);
+}
+
pub fn node(self: *Factory, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
return try AutoPrototypeChain(
diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig
index 08bb1b8b4..95dc641dc 100644
--- a/src/browser/js/Value.zig
+++ b/src/browser/js/Value.zig
@@ -425,6 +425,8 @@ const cloneable_types = .{
@import("../webapi/ImageData.zig"),
@import("../webapi/DOMPointReadOnly.zig"),
@import("../webapi/DOMPoint.zig"),
+ @import("../webapi/DOMRectReadOnly.zig"),
+ @import("../webapi/DOMRect.zig"),
};
// Passed to a type's structuredSerialize hook to write its payload into the
diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig
index b4c0f4168..03bb0bbaf 100644
--- a/src/browser/js/bridge.zig
+++ b/src/browser/js/bridge.zig
@@ -907,6 +907,7 @@ pub const PageJsApis = flattenTypes(&.{
@import("../webapi/DOMImplementation.zig"),
@import("../webapi/DOMTreeWalker.zig"),
@import("../webapi/DOMNodeIterator.zig"),
+ @import("../webapi/DOMRectReadOnly.zig"),
@import("../webapi/DOMRect.zig"),
@import("../webapi/DOMMatrixReadOnly.zig"),
@import("../webapi/DOMMatrix.zig"),
@@ -1128,6 +1129,8 @@ pub const WorkerJsApis = flattenTypes(&.{
@import("../webapi/event/PromiseRejectionEvent.zig"),
@import("../webapi/event/CloseEvent.zig"),
@import("../webapi/DOMException.zig"),
+ @import("../webapi/DOMRectReadOnly.zig"),
+ @import("../webapi/DOMRect.zig"),
@import("../webapi/DOMMatrixReadOnly.zig"),
@import("../webapi/DOMMatrix.zig"),
@import("../webapi/DOMPointReadOnly.zig"),
diff --git a/src/browser/tests/domrect.html b/src/browser/tests/domrect.html
new file mode 100644
index 000000000..e7af22990
--- /dev/null
+++ b/src/browser/tests/domrect.html
@@ -0,0 +1,155 @@
+
+
+ DOMRect Test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/browser/webapi/DOMRect.zig b/src/browser/webapi/DOMRect.zig
index e3b2502c6..00eee6b1c 100644
--- a/src/browser/webapi/DOMRect.zig
+++ b/src/browser/webapi/DOMRect.zig
@@ -16,55 +16,73 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
+const js = @import("../js/js.zig");
+const Page = @import("../Page.zig");
+const Factory = @import("../Factory.zig");
+
+const RO = @import("DOMRectReadOnly.zig");
+
+pub const Data = RO.Data;
+
const DOMRect = @This();
-const js = @import("../js/js.zig");
-const Frame = @import("../Frame.zig");
+_proto: *RO,
-_x: f64,
-_y: f64,
-_width: f64,
-_height: f64,
-
-pub fn init(x: f64, y: f64, width: f64, height: f64, frame: *Frame) !*DOMRect {
- return frame._factory.create(DOMRect{
- ._x = x,
- ._y = y,
- ._width = width,
- ._height = height,
- });
+pub fn init(x_: ?f64, y_: ?f64, width_: ?f64, height_: ?f64, exec: *const js.Execution) !*DOMRect {
+ return create(.{
+ .x = x_ orelse 0,
+ .y = y_ orelse 0,
+ .width = width_ orelse 0,
+ .height = height_ orelse 0,
+ }, exec._factory);
}
+pub fn create(rect: Data, factory: *Factory) !*DOMRect {
+ return factory.domRect(rect);
+}
+
+pub fn fromRect(other_: ?Data, exec: *const js.Execution) !*DOMRect {
+ return create(other_ orelse .{}, exec._factory);
+}
+
+pub fn structuredSerialize(self: *const DOMRect, writer: *js.StructuredWriter) !void {
+ try self._proto.structuredSerialize(writer);
+}
+
+pub fn structuredDeserialize(reader: *js.StructuredReader, page: *Page) !*DOMRect {
+ return page.factory.domRect(try RO.readData(reader));
+}
+
+// DOMRect redeclares x/y/width/height as writable, so DOMRect.prototype needs its
+// own read-write accessors distinct from the read-only ones on
+// DOMRectReadOnly.prototype. The setters are the point; each accessor bundles a
+// getter too, so we pair them with DOMRect-typed getters that read through
+// `_proto`. top/right/bottom/left stay read-only and are inherited from the base.
+
pub fn getX(self: *const DOMRect) f64 {
- return self._x;
+ return self._proto._x;
}
-
pub fn getY(self: *const DOMRect) f64 {
- return self._y;
+ return self._proto._y;
}
-
pub fn getWidth(self: *const DOMRect) f64 {
- return self._width;
+ return self._proto._width;
}
-
pub fn getHeight(self: *const DOMRect) f64 {
- return self._height;
+ return self._proto._height;
}
-pub fn getTop(self: *const DOMRect) f64 {
- return @min(self._y, self._y + self._height);
+pub fn setX(self: *DOMRect, v: f64) void {
+ self._proto._x = v;
}
-
-pub fn getRight(self: *const DOMRect) f64 {
- return @max(self._x, self._x + self._width);
+pub fn setY(self: *DOMRect, v: f64) void {
+ self._proto._y = v;
}
-
-pub fn getBottom(self: *const DOMRect) f64 {
- return @max(self._y, self._y + self._height);
+pub fn setWidth(self: *DOMRect, v: f64) void {
+ self._proto._width = v;
}
-
-pub fn getLeft(self: *const DOMRect) f64 {
- return @min(self._x, self._x + self._width);
+pub fn setHeight(self: *DOMRect, v: f64) void {
+ self._proto._height = v;
}
pub const JsApi = struct {
@@ -77,12 +95,12 @@ pub const JsApi = struct {
};
pub const constructor = bridge.constructor(DOMRect.init, .{});
- 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, .{});
+ pub const fromRect = bridge.function(DOMRect.fromRect, .{ .static = true });
+
+ // Writable components (the read-only top/right/bottom/left are inherited
+ // from DOMRectReadOnly.prototype).
+ pub const x = bridge.accessor(DOMRect.getX, DOMRect.setX, .{});
+ pub const y = bridge.accessor(DOMRect.getY, DOMRect.setY, .{});
+ pub const width = bridge.accessor(DOMRect.getWidth, DOMRect.setWidth, .{});
+ pub const height = bridge.accessor(DOMRect.getHeight, DOMRect.setHeight, .{});
};
diff --git a/src/browser/webapi/DOMRectReadOnly.zig b/src/browser/webapi/DOMRectReadOnly.zig
new file mode 100644
index 000000000..49ee7e54e
--- /dev/null
+++ b/src/browser/webapi/DOMRectReadOnly.zig
@@ -0,0 +1,172 @@
+// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
+//
+// Francis Bouvier
+// Pierre Tachoire
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+const js = @import("../js/js.zig");
+const Page = @import("../Page.zig");
+const Factory = @import("../Factory.zig");
+
+const DOMRect = @import("DOMRect.zig");
+
+const DOMRectReadOnly = @This();
+
+pub const _prototype_root = true;
+
+_type: Type,
+
+_x: f64,
+_y: f64,
+_width: f64,
+_height: f64,
+
+pub const Type = union(enum) {
+ generic,
+ mutable: *DOMRect,
+};
+
+pub const Data = struct {
+ x: f64 = 0,
+ y: f64 = 0,
+ width: f64 = 0,
+ height: f64 = 0,
+};
+
+pub fn init(x_: ?f64, y_: ?f64, width_: ?f64, height_: ?f64, exec: *const js.Execution) !*DOMRectReadOnly {
+ return createBare(.{
+ .x = x_ orelse 0,
+ .y = y_ orelse 0,
+ .width = width_ orelse 0,
+ .height = height_ orelse 0,
+ }, exec._factory);
+}
+
+pub fn createBare(rect: Data, factory: *Factory) !*DOMRectReadOnly {
+ return factory.create(DOMRectReadOnly{
+ ._type = .generic,
+ ._x = rect.x,
+ ._y = rect.y,
+ ._width = rect.width,
+ ._height = rect.height,
+ });
+}
+
+pub fn fromRect(other_: ?Data, exec: *const js.Execution) !*DOMRectReadOnly {
+ return createBare(other_ orelse .{}, exec._factory);
+}
+
+pub fn structuredSerialize(self: *const DOMRectReadOnly, writer: *js.StructuredWriter) !void {
+ writer.writeUint64(@bitCast(self._x));
+ writer.writeUint64(@bitCast(self._y));
+ writer.writeUint64(@bitCast(self._width));
+ writer.writeUint64(@bitCast(self._height));
+}
+
+pub fn readData(reader: *js.StructuredReader) !Data {
+ return .{
+ .x = @bitCast(try reader.readUint64()),
+ .y = @bitCast(try reader.readUint64()),
+ .width = @bitCast(try reader.readUint64()),
+ .height = @bitCast(try reader.readUint64()),
+ };
+}
+
+pub fn structuredDeserialize(reader: *js.StructuredReader, page: *Page) !*DOMRectReadOnly {
+ return createBare(try readData(reader), &page.factory);
+}
+
+pub fn getX(self: *const DOMRectReadOnly) f64 {
+ return self._x;
+}
+
+pub fn getY(self: *const DOMRectReadOnly) f64 {
+ return self._y;
+}
+
+pub fn getWidth(self: *const DOMRectReadOnly) f64 {
+ return self._width;
+}
+
+pub fn getHeight(self: *const DOMRectReadOnly) f64 {
+ return self._height;
+}
+
+pub fn getTop(self: *const DOMRectReadOnly) f64 {
+ return @min(self._y, self._y + self._height);
+}
+
+pub fn getRight(self: *const DOMRectReadOnly) f64 {
+ return @max(self._x, self._x + self._width);
+}
+
+pub fn getBottom(self: *const DOMRectReadOnly) f64 {
+ return @max(self._y, self._y + self._height);
+}
+
+pub fn getLeft(self: *const DOMRectReadOnly) f64 {
+ return @min(self._x, self._x + self._width);
+}
+
+pub fn toJSON(self: *const DOMRectReadOnly) struct {
+ x: f64,
+ y: f64,
+ width: f64,
+ height: f64,
+ top: f64,
+ right: f64,
+ bottom: f64,
+ left: f64,
+} {
+ return .{
+ .x = self._x,
+ .y = self._y,
+ .width = self._width,
+ .height = self._height,
+ .top = self.getTop(),
+ .right = self.getRight(),
+ .bottom = self.getBottom(),
+ .left = self.getLeft(),
+ };
+}
+
+pub const JsApi = struct {
+ pub const bridge = js.Bridge(DOMRectReadOnly);
+
+ pub const Meta = struct {
+ pub const name = "DOMRectReadOnly";
+ pub const prototype_chain = bridge.prototypeChain();
+ pub var class_id: bridge.ClassId = undefined;
+ };
+
+ pub const constructor = bridge.constructor(DOMRectReadOnly.init, .{});
+ pub const fromRect = bridge.function(DOMRectReadOnly.fromRect, .{ .static = true });
+
+ pub const x = bridge.accessor(DOMRectReadOnly.getX, null, .{});
+ pub const y = bridge.accessor(DOMRectReadOnly.getY, null, .{});
+ pub const width = bridge.accessor(DOMRectReadOnly.getWidth, null, .{});
+ pub const height = bridge.accessor(DOMRectReadOnly.getHeight, null, .{});
+ pub const top = bridge.accessor(DOMRectReadOnly.getTop, null, .{});
+ pub const right = bridge.accessor(DOMRectReadOnly.getRight, null, .{});
+ pub const bottom = bridge.accessor(DOMRectReadOnly.getBottom, null, .{});
+ pub const left = bridge.accessor(DOMRectReadOnly.getLeft, null, .{});
+
+ pub const toJSON = bridge.function(DOMRectReadOnly.toJSON, .{});
+};
+
+const testing = @import("../../testing.zig");
+test "WebApi: DOMRect" {
+ try testing.htmlRunner("domrect.html", .{});
+}
diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig
index bfb758281..7aa341e7c 100644
--- a/src/browser/webapi/Element.zig
+++ b/src/browser/webapi/Element.zig
@@ -1223,22 +1223,22 @@ pub fn getClientHeight(self: *Element, frame: *Frame) f64 {
return dims.height;
}
-pub fn getBoundingClientRect(self: *Element, frame: *Frame) DOMRect {
- if (!self.checkVisibilityCached(null, frame)) {
- return .{
- ._x = 0.0,
- ._y = 0.0,
- ._width = 0.0,
- ._height = 0.0,
- };
- }
-
- return self.getBoundingClientRectForVisible(frame);
+pub fn getBoundingClientRect(self: *Element, frame: *Frame) !*DOMRect {
+ return DOMRect.create(self.boundingClientRectValues(frame), frame._factory);
}
-// Some cases need a the BoundingClientRect but have already done the
-// visibility check.
-pub fn getBoundingClientRectForVisible(self: *Element, frame: *Frame) DOMRect {
+// Plain rect values, no JS-backed allocation: the internal fast path shared by
+// getBoundingClientRect, getClientRects, and IntersectionObserver. A DOMRect is
+// only materialized at the JS boundary.
+pub fn boundingClientRectValues(self: *Element, frame: *Frame) DOMRect.Data {
+ if (!self.checkVisibilityCached(null, frame)) {
+ return .{};
+ }
+ return self.boundingClientRectValuesForVisible(frame);
+}
+
+// Some cases need the bounding rect but have already done the visibility check.
+pub fn boundingClientRectValuesForVisible(self: *Element, frame: *Frame) DOMRect.Data {
const y = calculateDocumentPosition(self.asNode());
const dims = self.getElementDimensions(frame);
@@ -1246,19 +1246,19 @@ pub fn getBoundingClientRectForVisible(self: *Element, frame: *Frame) DOMRect {
const x = calculateSiblingPosition(self.asNode());
return .{
- ._x = x,
- ._y = y,
- ._width = dims.width,
- ._height = dims.height,
+ .x = x,
+ .y = y,
+ .width = dims.width,
+ .height = dims.height,
};
}
-pub fn getClientRects(self: *Element, frame: *Frame) ![]DOMRect {
+pub fn getClientRects(self: *Element, frame: *Frame) ![]*DOMRect {
if (!self.checkVisibilityCached(null, frame)) {
return &.{};
}
- const rects = try frame.local_arena.alloc(DOMRect, 1);
- rects[0] = self.getBoundingClientRectForVisible(frame);
+ const rects = try frame.local_arena.alloc(*DOMRect, 1);
+ rects[0] = try DOMRect.create(self.boundingClientRectValuesForVisible(frame), frame._factory);
return rects;
}
diff --git a/src/browser/webapi/IntersectionObserver.zig b/src/browser/webapi/IntersectionObserver.zig
index 8a70a7436..c6899c739 100644
--- a/src/browser/webapi/IntersectionObserver.zig
+++ b/src/browser/webapi/IntersectionObserver.zig
@@ -50,13 +50,9 @@ _pending_entries: std.ArrayList(*IntersectionObserverEntry) = .{},
// tracked targets that aren't reported yet
_tracked: std.AutoHashMapUnmanaged(*Element, void) = .{},
-// Shared zero DOMRect to avoid repeated allocations for non-intersecting elements
-var zero_rect: DOMRect = .{
- ._x = 0.0,
- ._y = 0.0,
- ._width = 0.0,
- ._height = 0.0,
-};
+// Shared zero rect (plain values) for non-intersecting elements. Materialized
+// into a DOMRect only if it ends up on a delivered entry.
+const zero_rect: DOMRect.Data = .{};
pub const ObserverInit = struct {
root: ?*Node = null,
@@ -202,18 +198,16 @@ fn calculateIntersection(
has_parent: bool,
frame: *Frame,
) !IntersectionData {
- const target_rect = target.getBoundingClientRect(frame);
+ const target_rect = target.boundingClientRectValues(frame);
// Use root element's rect or the faux-layout viewport.
const root_rect = if (self._root) |root|
- root.getBoundingClientRect(frame)
+ root.boundingClientRectValues(frame)
else blk: {
const viewport = frame._page.getViewport();
- break :blk DOMRect{
- ._x = 0.0,
- ._y = 0.0,
- ._width = @floatFromInt(viewport.width),
- ._height = @floatFromInt(viewport.height),
+ break :blk DOMRect.Data{
+ .width = @floatFromInt(viewport.width),
+ .height = @floatFromInt(viewport.height),
};
};
@@ -238,9 +232,9 @@ fn calculateIntersection(
const IntersectionData = struct {
is_intersecting: bool,
intersection_ratio: f64,
- intersection_rect: DOMRect,
- bounding_client_rect: DOMRect,
- root_bounds: DOMRect,
+ intersection_rect: DOMRect.Data,
+ bounding_client_rect: DOMRect.Data,
+ root_bounds: DOMRect.Data,
};
fn meetsThreshold(self: *IntersectionObserver, ratio: f64) bool {
@@ -280,9 +274,9 @@ fn checkIntersection(self: *IntersectionObserver, target: *Element, frame: *Fram
._target = target,
._time = frame.window._performance.now(),
._is_intersecting = is_now_intersecting,
- ._root_bounds = try frame._factory.create(data.root_bounds),
- ._intersection_rect = try frame._factory.create(data.intersection_rect),
- ._bounding_client_rect = try frame._factory.create(data.bounding_client_rect),
+ ._root_bounds = try DOMRect.create(data.root_bounds, frame._factory),
+ ._intersection_rect = try DOMRect.create(data.intersection_rect, frame._factory),
+ ._bounding_client_rect = try DOMRect.create(data.bounding_client_rect, frame._factory),
._intersection_ratio = data.intersection_ratio,
};
try self._pending_entries.append(self._arena, entry);
diff --git a/src/browser/webapi/Range.zig b/src/browser/webapi/Range.zig
index 29a6d31f8..d0f23c649 100644
--- a/src/browser/webapi/Range.zig
+++ b/src/browser/webapi/Range.zig
@@ -664,17 +664,17 @@ fn nextAfterSubtree(node: *Node, root: *Node) ?*Node {
return null;
}
-pub fn getBoundingClientRect(self: *const Range, frame: *Frame) DOMRect {
+pub fn getBoundingClientRect(self: *const Range, frame: *Frame) !*DOMRect {
if (self._proto.getCollapsed()) {
- return .{ ._x = 0, ._y = 0, ._width = 0, ._height = 0 };
+ return DOMRect.create(.{}, frame._factory);
}
const element = self.getContainerElement() orelse {
- return .{ ._x = 0, ._y = 0, ._width = 0, ._height = 0 };
+ return DOMRect.create(.{}, frame._factory);
};
return element.getBoundingClientRect(frame);
}
-pub fn getClientRects(self: *const Range, frame: *Frame) ![]DOMRect {
+pub fn getClientRects(self: *const Range, frame: *Frame) ![]*DOMRect {
if (self._proto.getCollapsed()) {
return &.{};
}
diff --git a/src/browser/webapi/element/svg/Svg.zig b/src/browser/webapi/element/svg/Svg.zig
index 920b25c7f..11b809b43 100644
--- a/src/browser/webapi/element/svg/Svg.zig
+++ b/src/browser/webapi/element/svg/Svg.zig
@@ -85,7 +85,7 @@ pub const JsApi = struct {
pub const createSVGRect = bridge.function(_createSVGRect, .{});
fn _createSVGRect(_: *Svg, frame: *Frame) !*DOMRect {
- return DOMRect.init(0, 0, 0, 0, frame);
+ return DOMRect.create(.{}, frame._factory);
}
pub const createSVGNumber = bridge.function(_createSVGNumber, .{});
diff --git a/src/cdp/domains/dom.zig b/src/cdp/domains/dom.zig
index 37235ba20..6db73357f 100644
--- a/src/cdp/domains/dom.zig
+++ b/src/cdp/domains/dom.zig
@@ -429,16 +429,16 @@ const BoxModel = struct {
// shapeOutside: ?ShapeOutsideInfo,
};
-fn rectToQuad(rect: DOMNode.Element.DOMRect) Quad {
+fn rectToQuad(rect: DOMNode.Element.DOMRect.Data) Quad {
return Quad{
- rect._x,
- rect._y,
- rect._x + rect._width,
- rect._y,
- rect._x + rect._width,
- rect._y + rect._height,
- rect._x,
- rect._y + rect._height,
+ rect.x,
+ rect.y,
+ rect.x + rect.width,
+ rect.y,
+ rect.x + rect.width,
+ rect.y + rect.height,
+ rect.x,
+ rect.y + rect.height,
};
}
@@ -447,7 +447,7 @@ fn scrollIntoViewIfNeeded(cmd: *CDP.Command) !void {
nodeId: ?Node.Id = null,
backendNodeId: ?u32 = null,
objectId: ?[]const u8 = null,
- rect: ?DOMNode.Element.DOMRect = null,
+ rect: ?DOMNode.Element.DOMRect.Data = null,
})) orelse return error.InvalidParams;
// Only 1 of nodeId, backendNodeId, objectId may be set, but chrome just takes the first non-null
@@ -507,7 +507,7 @@ fn getContentQuads(cmd: *CDP.Command) !void {
// Text may be tricky, multiple quads in case of multiple lines? empty quads of text = ""?
// Elements like SVGElement may have multiple quads.
- const quad = rectToQuad(element.getBoundingClientRect(frame));
+ const quad = rectToQuad(element.boundingClientRectValues(frame));
return cmd.sendResult(.{ .quads = &.{quad} }, .{});
}
@@ -526,7 +526,7 @@ fn getBoxModel(cmd: *CDP.Command) !void {
// TODO implement for document or text
const element = node.dom.is(DOMNode.Element) orelse return error.NodeIsNotAnElement;
- const rect = element.getBoundingClientRect(frame);
+ const rect = element.boundingClientRectValues(frame);
const quad = rectToQuad(rect);
const zero = [_]f64{0.0} ** 8;
@@ -535,8 +535,8 @@ fn getBoxModel(cmd: *CDP.Command) !void {
.padding = zero,
.border = zero,
.margin = zero,
- .width = @intFromFloat(rect._width),
- .height = @intFromFloat(rect._height),
+ .width = @intFromFloat(rect.width),
+ .height = @intFromFloat(rect.height),
} }, .{});
}