mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-07-30 17:25:58 -04:00
webapi: Improve ResizeObserver
This gives ResizeObserver a touch up that brings it on par with how the more flushed out IntersectionObserver behaves. While it's impossible to fully implement this in a headless world, we can correctly emit ResizeObserverEntry in a number of important cases, e.g. visibility change, and style width/height changes. Most importantly, we not execute the callback on the initial observe, which can unlock some cases.
This commit is contained in:
@@ -200,7 +200,8 @@ _broadcast_channels: std.DoublyLinkedList = .{},
|
||||
// List of MessagePorts living in this frame's context.
|
||||
_message_ports: std.DoublyLinkedList = .{},
|
||||
|
||||
// MutationObserver / IntersectionObserver bookkeeping. See frame/observers.zig.
|
||||
// See frame/observers.zig.
|
||||
_resize: observers.Resize = .{},
|
||||
_mutation: observers.Mutation = .{},
|
||||
_intersection: observers.Intersection = .{},
|
||||
|
||||
@@ -1944,14 +1945,10 @@ pub fn openPopup(self: *Frame, opts: OpenPopupOpts) !*Frame {
|
||||
pub fn domChanged(self: *Frame) void {
|
||||
self._page.dom_version += 1;
|
||||
|
||||
if (self._intersection.check_scheduled) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._intersection.check_scheduled = true;
|
||||
self.js.queueIntersectionChecks() catch |err| {
|
||||
log.err(.frame, "frame.schedIntersectChecks", .{ .err = err, .type = self._type, .url = self.url });
|
||||
};
|
||||
// A DOM change is our "rendering opportunity": re-evaluate the layout
|
||||
// observers. Both are no-ops unless something they track actually changed.
|
||||
observers.scheduleIntersectionChecks(self);
|
||||
observers.scheduleResizeDelivery(self);
|
||||
}
|
||||
|
||||
const ElementIdMaps = struct { lookup: *std.StringHashMapUnmanaged(*Element), removed_ids: *std.StringHashMapUnmanaged(void) };
|
||||
|
||||
@@ -30,6 +30,7 @@ const Page = @import("../Page.zig");
|
||||
const Node = @import("../webapi/Node.zig");
|
||||
const Event = @import("../webapi/Event.zig");
|
||||
const Element = @import("../webapi/Element.zig");
|
||||
const ResizeObserver = @import("../webapi/ResizeObserver.zig");
|
||||
const MutationObserver = @import("../webapi/MutationObserver.zig");
|
||||
const IntersectionObserver = @import("../webapi/IntersectionObserver.zig");
|
||||
|
||||
@@ -52,6 +53,14 @@ pub const Intersection = struct {
|
||||
delivery_scheduled: bool = false,
|
||||
};
|
||||
|
||||
// ResizeObserver bookkeeping for a frame.
|
||||
pub const Resize = struct {
|
||||
// List of active ResizeObservers (i.e. those with >= 1 observation)
|
||||
observers: std.ArrayList(*ResizeObserver) = .{},
|
||||
delivery_scheduled: bool = false,
|
||||
delivery_depth: u32 = 0,
|
||||
};
|
||||
|
||||
// Releases the frame's references to its registered observers. Called from
|
||||
// Frame.deinit.
|
||||
pub fn deinit(frame: *Frame, page: *Page) void {
|
||||
@@ -65,6 +74,10 @@ pub fn deinit(frame: *Frame, page: *Page) void {
|
||||
for (frame._intersection.observers.items) |observer| {
|
||||
observer.releaseRef(page);
|
||||
}
|
||||
|
||||
for (frame._resize.observers.items) |observer| {
|
||||
observer.releaseRef(page);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn registerMutationObserver(frame: *Frame, observer: *MutationObserver) !void {
|
||||
@@ -92,6 +105,21 @@ pub fn unregisterIntersectionObserver(frame: *Frame, observer: *IntersectionObse
|
||||
}
|
||||
}
|
||||
|
||||
pub fn registerResizeObserver(frame: *Frame, observer: *ResizeObserver) !void {
|
||||
observer.acquireRef();
|
||||
try frame._resize.observers.append(frame.arena, observer);
|
||||
}
|
||||
|
||||
pub fn unregisterResizeObserver(frame: *Frame, observer: *ResizeObserver) void {
|
||||
for (frame._resize.observers.items, 0..) |obs, i| {
|
||||
if (obs == observer) {
|
||||
observer.releaseRef(frame._page);
|
||||
_ = frame._resize.observers.swapRemove(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hasMutationObservers(frame: *const Frame) bool {
|
||||
return frame._mutation.observers.first != null;
|
||||
}
|
||||
@@ -118,6 +146,63 @@ pub fn scheduleIntersectionDelivery(frame: *Frame) !void {
|
||||
try frame.js.queueIntersectionDelivery();
|
||||
}
|
||||
|
||||
pub fn scheduleIntersectionChecks(frame: *Frame) void {
|
||||
if (frame._intersection.check_scheduled) {
|
||||
return;
|
||||
}
|
||||
frame._intersection.check_scheduled = true;
|
||||
frame.js.queueIntersectionChecks() catch |err| {
|
||||
frame._intersection.check_scheduled = false;
|
||||
log.err(.frame, "frame.scheduleIntersectionChecks", .{ .err = err, .type = frame._type, .url = frame.url });
|
||||
};
|
||||
}
|
||||
|
||||
pub fn scheduleResizeDelivery(frame: *Frame) void {
|
||||
if (frame._resize.observers.items.len == 0) {
|
||||
return;
|
||||
}
|
||||
if (frame._resize.delivery_scheduled) {
|
||||
return;
|
||||
}
|
||||
frame._resize.delivery_scheduled = true;
|
||||
frame.js.queueResizeDelivery() catch |err| {
|
||||
frame._resize.delivery_scheduled = false;
|
||||
log.err(.frame, "frame.scheduleResizeDelivery", .{ .err = err, .type = frame._type, .url = frame.url });
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deliverResizes(frame: *Frame) void {
|
||||
if (!frame._resize.delivery_scheduled) {
|
||||
return;
|
||||
}
|
||||
frame._resize.delivery_scheduled = false;
|
||||
|
||||
// guard against a callback that keeps mutating the layout, and thus causes
|
||||
// an endless stram of deliveries.
|
||||
frame._resize.delivery_depth += 1;
|
||||
defer if (!frame._resize.delivery_scheduled) {
|
||||
frame._resize.delivery_depth = 0;
|
||||
};
|
||||
if (frame._resize.delivery_depth > 50) {
|
||||
log.warn(.frame, "frame.ResizeLimit", .{ .type = frame._type, .url = frame.url });
|
||||
frame._resize.delivery_depth = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate backwards so an observer disconnecting during its callback is safe.
|
||||
var i = frame._resize.observers.items.len;
|
||||
while (i > 0) {
|
||||
i -= 1;
|
||||
if (i >= frame._resize.observers.items.len) {
|
||||
continue;
|
||||
}
|
||||
const observer = frame._resize.observers.items[i];
|
||||
observer.deliverEntries(frame) catch |err| {
|
||||
log.err(.frame, "frame.deliverResizes", .{ .err = err, .type = frame._type, .url = frame.url });
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn performScheduledIntersectionChecks(frame: *Frame) void {
|
||||
if (!frame._intersection.check_scheduled) {
|
||||
return;
|
||||
@@ -157,7 +242,7 @@ pub fn deliverMutations(frame: *Frame) void {
|
||||
frame._mutation.delivery_depth = 0;
|
||||
};
|
||||
|
||||
if (frame._mutation.delivery_depth > 100) {
|
||||
if (frame._mutation.delivery_depth > 50) {
|
||||
log.err(.frame, "frame.MutationLimit", .{ .type = frame._type, .url = frame.url });
|
||||
frame._mutation.delivery_depth = 0;
|
||||
return;
|
||||
|
||||
@@ -1093,6 +1093,17 @@ pub fn queueIntersectionDelivery(self: *Context) !void {
|
||||
}.run);
|
||||
}
|
||||
|
||||
pub fn queueResizeDelivery(self: *Context) !void {
|
||||
self.enqueueMicrotask(struct {
|
||||
fn run(ctx: *Context) void {
|
||||
switch (ctx.global) {
|
||||
.frame => |frame| Frame.observers.deliverResizes(frame),
|
||||
.worker => unreachable,
|
||||
}
|
||||
}
|
||||
}.run);
|
||||
}
|
||||
|
||||
pub fn queueCustomElementBackupDrain(self: *Context) !void {
|
||||
self.enqueueMicrotask(struct {
|
||||
fn run(ctx: *Context) void {
|
||||
|
||||
54
src/browser/tests/resize_observer/basic.html
Normal file
54
src/browser/tests/resize_observer/basic.html
Normal file
@@ -0,0 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
<div id="target" style="width: 100px; height: 60px;">Target Element</div>
|
||||
<div id="hidden" style="width: 50px; display: none;">Hidden</div>
|
||||
|
||||
<script id="initial">
|
||||
{
|
||||
const target = document.getElementById('target');
|
||||
let count = 0;
|
||||
let entries = null;
|
||||
|
||||
const observer = new ResizeObserver((observerEntries, obs) => {
|
||||
count += 1;
|
||||
entries = observerEntries;
|
||||
});
|
||||
observer.observe(target);
|
||||
|
||||
testing.onload(() => {
|
||||
// observe() delivers a single initial entry
|
||||
testing.expectEqual(1, count);
|
||||
testing.expectEqual(1, entries.length);
|
||||
|
||||
const entry = entries[0];
|
||||
testing.expectEqual(target, entry.target);
|
||||
testing.expectEqual('object', typeof entry.contentRect);
|
||||
testing.expectEqual(100, entry.contentRect.width);
|
||||
testing.expectEqual(60, entry.contentRect.height);
|
||||
|
||||
// content-box / border-box / device-pixel are the same for us
|
||||
testing.expectEqual(1, entry.contentBoxSize.length);
|
||||
testing.expectEqual(100, entry.contentBoxSize[0].inlineSize);
|
||||
testing.expectEqual(60, entry.contentBoxSize[0].blockSize);
|
||||
testing.expectEqual(100, entry.borderBoxSize[0].inlineSize);
|
||||
testing.expectEqual(100, entry.devicePixelContentBoxSize[0].inlineSize);
|
||||
|
||||
observer.disconnect();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="no_initial_for_zero_size">
|
||||
{
|
||||
// A display:none element has no box, so observe() delivers nothing.
|
||||
const hidden = document.getElementById('hidden');
|
||||
let hiddenCount = 0;
|
||||
const hiddenObserver = new ResizeObserver(() => { hiddenCount += 1; });
|
||||
hiddenObserver.observe(hidden);
|
||||
|
||||
testing.onload(() => {
|
||||
testing.expectEqual(0, hiddenCount);
|
||||
hiddenObserver.disconnect();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
55
src/browser/tests/resize_observer/resize.html
Normal file
55
src/browser/tests/resize_observer/resize.html
Normal file
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
<div id="anchor">anchor</div>
|
||||
|
||||
<script id="style_change_delivers" type=module>
|
||||
// Changing an element's size delivers a fresh notification.
|
||||
const state = await testing.async();
|
||||
const t = document.createElement('div');
|
||||
t.style.width = '100px';
|
||||
t.style.height = '50px';
|
||||
document.body.appendChild(t);
|
||||
|
||||
let step = 0;
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
step += 1;
|
||||
if (step === 1) {
|
||||
testing.expectEqual(100, entries[0].contentRect.width);
|
||||
t.style.width = '25px';
|
||||
} else if (step === 2) {
|
||||
testing.expectEqual(25, entries[0].contentRect.width);
|
||||
observer.disconnect();
|
||||
state.resolve();
|
||||
}
|
||||
});
|
||||
observer.observe(t);
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(2, step);
|
||||
});
|
||||
</script>
|
||||
|
||||
<script id="disconnect_from_dom" type=module>
|
||||
// Removing the target from the DOM collapses its size to 0 and notifies.
|
||||
const state = await testing.async();
|
||||
const t = document.createElement('div');
|
||||
t.style.width = '80px';
|
||||
document.body.appendChild(t);
|
||||
|
||||
const widths = [];
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
widths.push(entries[0].contentRect.width);
|
||||
if (widths.length === 1) {
|
||||
t.remove();
|
||||
} else if (widths.length === 2) {
|
||||
observer.disconnect();
|
||||
state.resolve();
|
||||
}
|
||||
});
|
||||
observer.observe(t);
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(80, widths[0]);
|
||||
testing.expectEqual(0, widths[1]);
|
||||
});
|
||||
</script>
|
||||
64
src/browser/tests/resize_observer/unobserve.html
Normal file
64
src/browser/tests/resize_observer/unobserve.html
Normal file
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
<div id="anchor">anchor</div>
|
||||
|
||||
<script id="unobserve_before_delivery">
|
||||
{
|
||||
const t = document.createElement('div');
|
||||
t.style.width = '100px';
|
||||
document.body.appendChild(t);
|
||||
|
||||
let count = 0;
|
||||
const observer = new ResizeObserver(() => { count += 1; });
|
||||
observer.observe(t);
|
||||
observer.unobserve(t);
|
||||
// unobserving a target we never observed is a no-op
|
||||
observer.unobserve(document.body);
|
||||
|
||||
testing.onload(() => {
|
||||
testing.expectEqual(0, count);
|
||||
observer.disconnect();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="disconnect_stops_notifications">
|
||||
{
|
||||
const t1 = document.createElement('div');
|
||||
const t2 = document.createElement('div');
|
||||
t1.style.width = '100px';
|
||||
t2.style.width = '100px';
|
||||
document.body.appendChild(t1);
|
||||
document.body.appendChild(t2);
|
||||
|
||||
let count = 0;
|
||||
const observer = new ResizeObserver(() => { count += 1; });
|
||||
observer.observe(t1);
|
||||
observer.observe(t2);
|
||||
observer.disconnect();
|
||||
t1.style.width = '30px';
|
||||
|
||||
testing.onload(() => {
|
||||
testing.expectEqual(0, count);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="observe_twice_is_one">
|
||||
{
|
||||
const t = document.createElement('div');
|
||||
t.style.width = '100px';
|
||||
document.body.appendChild(t);
|
||||
|
||||
let entryCount = 0;
|
||||
const observer = new ResizeObserver((entries) => { entryCount += entries.length; });
|
||||
observer.observe(t);
|
||||
observer.observe(t);
|
||||
|
||||
testing.onload(() => {
|
||||
// observing the same target twice still yields a single entry
|
||||
testing.expectEqual(1, entryCount);
|
||||
observer.disconnect();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
@@ -16,39 +16,238 @@
|
||||
// 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/>.
|
||||
|
||||
// We're a headless browser, so this is never goig to be perfect, but we CAN
|
||||
// correctly deliver some effects, e.g. the initial entry when observe() is
|
||||
// called and some changes to display or style's width/height.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
|
||||
const Element = @import("Element.zig");
|
||||
const DOMRect = @import("DOMRect.zig");
|
||||
const Factory = @import("../Factory.zig");
|
||||
|
||||
pub const ResizeObserver = @This();
|
||||
const log = lp.log;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
// Padding to avoid zero-size struct, which causes identity_map pointer collisions.
|
||||
_pad: bool = false,
|
||||
|
||||
fn init(cbk: js.Function) ResizeObserver {
|
||||
_ = cbk;
|
||||
return .{};
|
||||
pub fn registerTypes() []const type {
|
||||
return &.{
|
||||
ResizeObserver,
|
||||
ResizeObserverEntry,
|
||||
ResizeObserverSize,
|
||||
};
|
||||
}
|
||||
|
||||
const ResizeObserver = @This();
|
||||
|
||||
_rc: lp.RC(u8) = .{},
|
||||
_arena: Allocator,
|
||||
_callback: js.Function.Global,
|
||||
_observations: std.ArrayList(Observation) = .{},
|
||||
|
||||
const Observation = struct {
|
||||
target: *Element,
|
||||
last_width: f64 = 0,
|
||||
last_height: f64 = 0,
|
||||
};
|
||||
|
||||
const Options = struct {
|
||||
box: []const u8 = "content-box",
|
||||
};
|
||||
pub fn observe(self: *const ResizeObserver, element: *Element, options_: ?Options) void {
|
||||
_ = self;
|
||||
_ = element;
|
||||
_ = options_;
|
||||
return;
|
||||
|
||||
pub fn init(callback: js.Function.Global, frame: *Frame) !*ResizeObserver {
|
||||
const arena = try frame.getArena(.small, "ResizeObserver");
|
||||
errdefer frame.releaseArena(arena);
|
||||
|
||||
const self = try arena.create(ResizeObserver);
|
||||
self.* = .{
|
||||
._arena = arena,
|
||||
._callback = callback,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn unobserve(self: *const ResizeObserver, element: *Element) void {
|
||||
_ = self;
|
||||
_ = element;
|
||||
return;
|
||||
pub fn deinit(self: *ResizeObserver, page: *Page) void {
|
||||
self._callback.release();
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn disconnect(self: *const ResizeObserver) void {
|
||||
_ = self;
|
||||
pub fn acquireRef(self: *ResizeObserver) void {
|
||||
self._rc.acquire();
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *ResizeObserver, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn observe(self: *ResizeObserver, target: *Element, options_: ?Options, frame: *Frame) !void {
|
||||
_ = options_; // Can't make use of this
|
||||
|
||||
for (self._observations.items) |obs| {
|
||||
if (obs.target == target) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try self._observations.append(self._arena, .{ .target = target });
|
||||
if (self._observations.items.len == 1) {
|
||||
try Frame.observers.registerResizeObserver(frame, self);
|
||||
}
|
||||
|
||||
Frame.observers.scheduleResizeDelivery(frame);
|
||||
}
|
||||
|
||||
pub fn unobserve(self: *ResizeObserver, target: *Element, frame: *Frame) void {
|
||||
for (self._observations.items, 0..) |obs, i| {
|
||||
if (obs.target == target) {
|
||||
_ = self._observations.swapRemove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (self._observations.items.len == 0) {
|
||||
Frame.observers.unregisterResizeObserver(frame, self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disconnect(self: *ResizeObserver, frame: *Frame) void {
|
||||
if (self._observations.items.len == 0) {
|
||||
return;
|
||||
}
|
||||
self._observations.clearRetainingCapacity();
|
||||
Frame.observers.unregisterResizeObserver(frame, self);
|
||||
}
|
||||
|
||||
// Gather the observations whose size changed since the last delivery and, if
|
||||
// any, invoke the callback.
|
||||
pub fn deliverEntries(self: *ResizeObserver, frame: *Frame) !void {
|
||||
var entries: std.ArrayList(*ResizeObserverEntry) = .empty;
|
||||
for (self._observations.items) |*obs| {
|
||||
const target = obs.target;
|
||||
|
||||
const width, const height = blk: {
|
||||
if (obs.target.asNode().isConnected() == false) {
|
||||
break :blk .{ 0, 0 };
|
||||
}
|
||||
break :blk .{ target.getClientWidth(frame), target.getClientHeight(frame) };
|
||||
};
|
||||
|
||||
if (width == obs.last_width and height == obs.last_height) {
|
||||
continue;
|
||||
}
|
||||
obs.last_width = width;
|
||||
obs.last_height = height;
|
||||
|
||||
const entry = try ResizeObserverEntry.create(obs.target, width, height, frame._factory);
|
||||
try entries.append(frame.call_arena, entry);
|
||||
}
|
||||
|
||||
if (entries.items.len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var caught: js.TryCatch.Caught = undefined;
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
ls.toLocal(self._callback).tryCall(void, .{ entries.items, self }, &caught) catch |err| {
|
||||
log.err(.frame, "ResizeObserver.deliverEntries", .{ .err = err, .caught = caught });
|
||||
return err;
|
||||
};
|
||||
}
|
||||
|
||||
pub const ResizeObserverEntry = struct {
|
||||
_target: *Element,
|
||||
_content_rect: *DOMRect,
|
||||
_box_size: [1]*ResizeObserverSize,
|
||||
|
||||
pub fn create(target: *Element, width: f64, height: f64, factory: *Factory) !*ResizeObserverEntry {
|
||||
const content_rect = try DOMRect.create(.{ .width = width, .height = height }, factory);
|
||||
const size = try ResizeObserverSize.create(width, height, factory);
|
||||
return factory.create(ResizeObserverEntry{
|
||||
._target = target,
|
||||
._content_rect = content_rect,
|
||||
._box_size = .{size},
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getTarget(self: *const ResizeObserverEntry) *Element {
|
||||
return self._target;
|
||||
}
|
||||
|
||||
pub fn getContentRect(self: *const ResizeObserverEntry) *DOMRect {
|
||||
return self._content_rect;
|
||||
}
|
||||
|
||||
pub fn getBorderBoxSize(self: *const ResizeObserverEntry) []const *ResizeObserverSize {
|
||||
return &self._box_size;
|
||||
}
|
||||
|
||||
pub fn getContentBoxSize(self: *const ResizeObserverEntry) []const *ResizeObserverSize {
|
||||
return &self._box_size;
|
||||
}
|
||||
|
||||
pub fn getDevicePixelContentBoxSize(self: *const ResizeObserverEntry) []const *ResizeObserverSize {
|
||||
return &self._box_size;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(ResizeObserverEntry);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "ResizeObserverEntry";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const target = bridge.accessor(ResizeObserverEntry.getTarget, null, .{});
|
||||
pub const contentRect = bridge.accessor(ResizeObserverEntry.getContentRect, null, .{});
|
||||
pub const borderBoxSize = bridge.accessor(ResizeObserverEntry.getBorderBoxSize, null, .{});
|
||||
pub const contentBoxSize = bridge.accessor(ResizeObserverEntry.getContentBoxSize, null, .{});
|
||||
pub const devicePixelContentBoxSize = bridge.accessor(ResizeObserverEntry.getDevicePixelContentBoxSize, null, .{});
|
||||
};
|
||||
};
|
||||
|
||||
pub const ResizeObserverSize = struct {
|
||||
_inline_size: f64,
|
||||
_block_size: f64,
|
||||
|
||||
pub fn create(inline_size: f64, block_size: f64, factory: *Factory) !*ResizeObserverSize {
|
||||
return factory.create(ResizeObserverSize{
|
||||
._inline_size = inline_size,
|
||||
._block_size = block_size,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getInlineSize(self: *const ResizeObserverSize) f64 {
|
||||
return self._inline_size;
|
||||
}
|
||||
|
||||
pub fn getBlockSize(self: *const ResizeObserverSize) f64 {
|
||||
return self._block_size;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(ResizeObserverSize);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "ResizeObserverSize";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const inlineSize = bridge.accessor(ResizeObserverSize.getInlineSize, null, .{});
|
||||
pub const blockSize = bridge.accessor(ResizeObserverSize.getBlockSize, null, .{});
|
||||
};
|
||||
};
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(ResizeObserver);
|
||||
|
||||
@@ -56,7 +255,6 @@ pub const JsApi = struct {
|
||||
pub const name = "ResizeObserver";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
pub const empty_with_no_proto = true;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(ResizeObserver.init, .{});
|
||||
@@ -64,3 +262,8 @@ pub const JsApi = struct {
|
||||
pub const unobserve = bridge.function(ResizeObserver.unobserve, .{});
|
||||
pub const disconnect = bridge.function(ResizeObserver.disconnect, .{});
|
||||
};
|
||||
|
||||
const testing = @import("../../testing.zig");
|
||||
test "WebApi: ResizeObserver" {
|
||||
try testing.htmlRunner("resize_observer", .{});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user