webapi: Improve DOMRect

We had a basic DORect implementation (which probably covered more cases). This
commit extends the implementation to be more complete:

- Add DOMRectReadOnly (which DOMRect inherits from)
- Add StructuredClone support to DOMRect and DOMRectReadOnly
- Make DOMRect and DOMRectReadOnly usable from Worker

DOMRect can be on the hot path with IntersectionObserver, so we maintain a
value-based data object (DOMRect.Data) for internal use (things returned to js
always have to be heap allocated (and always were, we're just more explicit
about this part now)).
This commit is contained in:
Karl Seguin
2026-07-13 12:03:52 +08:00
parent 0ff08420c8
commit 7b8ea39164
11 changed files with 462 additions and 100 deletions

View File

@@ -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(

View File

@@ -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

View File

@@ -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"),

View File

@@ -0,0 +1,155 @@
<!DOCTYPE html>
<head>
<title>DOMRect Test</title>
<script src="testing.js"></script>
</head>
<body>
</body>
<script id=default_construct>
{
const r = new DOMRect();
testing.expectEqual(0, r.x);
testing.expectEqual(0, r.y);
testing.expectEqual(0, r.width);
testing.expectEqual(0, r.height);
}
</script>
<script id=construct_with_args>
{
const r = new DOMRect(1, 2, 3, 4);
testing.expectEqual(1, r.x);
testing.expectEqual(2, r.y);
testing.expectEqual(3, r.width);
testing.expectEqual(4, r.height);
testing.expectEqual(2, r.top);
testing.expectEqual(4, r.right);
testing.expectEqual(6, r.bottom);
testing.expectEqual(1, r.left);
}
</script>
<script id=negative_dimensions>
{
// top/left take the min edge, right/bottom the max edge.
const r = new DOMRect(10, 20, -4, -6);
testing.expectEqual(14, r.top);
testing.expectEqual(10, r.right);
testing.expectEqual(20, r.bottom);
testing.expectEqual(6, r.left);
}
</script>
<script id=mutable_setters>
{
const r = new DOMRect(1, 1, 1, 1);
r.x = 5;
r.y = 6;
r.width = 7;
r.height = 8;
testing.expectEqual(5, r.x);
testing.expectEqual(6, r.y);
testing.expectEqual(7, r.width);
testing.expectEqual(8, r.height);
// Inherited computed getters reflect the mutated values.
testing.expectEqual(6, r.top);
testing.expectEqual(12, r.right);
}
</script>
<script id=readonly_is_immutable>
{
const ro = new DOMRectReadOnly(1, 2, 3, 4);
testing.expectEqual(1, ro.x);
// Assigning a readonly attribute is a silent no-op in non-strict mode.
ro.x = 99;
testing.expectEqual(1, ro.x);
testing.expectEqual(3, ro.width);
}
</script>
<script id=prototype_chain>
{
const r = new DOMRect(1, 2, 3, 4);
testing.expectTrue(r instanceof DOMRect);
testing.expectTrue(r instanceof DOMRectReadOnly);
const ro = new DOMRectReadOnly(1, 2, 3, 4);
testing.expectTrue(ro instanceof DOMRectReadOnly);
testing.expectFalse(ro instanceof DOMRect);
}
</script>
<script id=from_rect>
{
const r = DOMRect.fromRect({ x: 5, y: 6, width: 7 });
testing.expectTrue(r instanceof DOMRect);
testing.expectEqual(5, r.x);
testing.expectEqual(6, r.y);
testing.expectEqual(7, r.width);
testing.expectEqual(0, r.height);
const ro = DOMRectReadOnly.fromRect({ width: 10, height: 20 });
testing.expectTrue(ro instanceof DOMRectReadOnly);
testing.expectFalse(ro instanceof DOMRect);
testing.expectEqual(10, ro.width);
testing.expectEqual(20, ro.height);
}
</script>
<script id=from_rect_empty>
{
const r = DOMRect.fromRect();
testing.expectEqual(0, r.x);
testing.expectEqual(0, r.y);
testing.expectEqual(0, r.width);
testing.expectEqual(0, r.height);
}
</script>
<script id=to_json>
{
const r = new DOMRect(1, 2, 3, 4);
const j = r.toJSON();
testing.expectEqual(1, j.x);
testing.expectEqual(2, j.y);
testing.expectEqual(3, j.width);
testing.expectEqual(4, j.height);
testing.expectEqual(2, j.top);
testing.expectEqual(4, j.right);
testing.expectEqual(6, j.bottom);
testing.expectEqual(1, j.left);
}
</script>
<script id=structured_clone>
{
const r = structuredClone(new DOMRect(1, 2, 3, 4));
testing.expectTrue(r instanceof DOMRect);
testing.expectTrue(r instanceof DOMRectReadOnly);
testing.expectEqual(1, r.x);
testing.expectEqual(2, r.y);
testing.expectEqual(3, r.width);
testing.expectEqual(4, r.height);
const ro = structuredClone(new DOMRectReadOnly(5, 6, 7, 8));
testing.expectTrue(ro instanceof DOMRectReadOnly);
testing.expectFalse(ro instanceof DOMRect);
testing.expectEqual(5, ro.x);
testing.expectEqual(6, ro.y);
testing.expectEqual(7, ro.width);
testing.expectEqual(8, ro.height);
}
</script>
<script id=bounding_client_rect>
{
const el = document.createElement('div');
document.body.appendChild(el);
const r = el.getBoundingClientRect();
testing.expectTrue(r instanceof DOMRect);
testing.expectTrue(r instanceof DOMRectReadOnly);
}
</script>

View File

@@ -16,55 +16,73 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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, .{});
};

View File

@@ -0,0 +1,172 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// 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 <https://www.gnu.org/licenses/>.
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", .{});
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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 &.{};
}

View File

@@ -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, .{});

View File

@@ -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),
} }, .{});
}