Add finalizers and arenas to events

Events will now be finalized when no longer needed, and allocate using arenas
from the ArenaPool rather than the page.arena.
This commit is contained in:
Karl Seguin
2026-01-27 17:53:30 +08:00
parent 451dd0fd64
commit fdfa0a57ef
19 changed files with 239 additions and 87 deletions

View File

@@ -136,12 +136,16 @@ pub fn dispatch(self: *EventManager, target: *EventTarget, event: *Event) !void
event._dispatch_target = target; // Store original target for composedPath()
var was_handled = false;
defer if (was_handled) {
var ls: js.Local.Scope = undefined;
self.page.js.localScope(&ls);
defer ls.deinit();
ls.local.runMicrotasks();
};
defer {
if (was_handled) {
var ls: js.Local.Scope = undefined;
self.page.js.localScope(&ls);
defer ls.deinit();
ls.local.runMicrotasks();
} else {
event.deinit(false);
}
}
switch (target._type) {
.node => |node| try self.dispatchNode(node, event, &was_handled),
@@ -181,26 +185,30 @@ pub fn dispatchWithFunction(self: *EventManager, target: *EventTarget, event: *E
event._dispatch_target = target; // Store original target for composedPath()
}
var was_dispatched = false;
defer if (was_dispatched) {
var ls: js.Local.Scope = undefined;
self.page.js.localScope(&ls);
defer ls.deinit();
ls.local.runMicrotasks();
};
if (function_) |func| {
event._current_target = target;
if (func.callWithThis(void, target, .{event})) {
was_dispatched = true;
} else |err| {
// a non-JS error
log.warn(.event, opts.context, .{ .err = err });
var was_handled = false;
defer {
if (was_handled) {
var ls: js.Local.Scope = undefined;
self.page.js.localScope(&ls);
defer ls.deinit();
ls.local.runMicrotasks();
} else {
event.deinit(false);
}
}
if (function_) |func| {
const js_val = try func.local.zigValueToJs(event, .{});
was_handled = true;
event._current_target = target;
func.callWithThis(void, target, .{js_val}) catch |err| {
log.warn(.event, opts.context, .{ .err = err });
};
}
const list = self.lookup.get(@intFromPtr(target)) orelse return;
try self.dispatchAll(list, target, event, &was_dispatched);
try self.dispatchAll(list, target, event, &was_handled);
}
fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled: *bool) !void {
@@ -364,7 +372,6 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
self.removeListener(list, listener);
}
was_handled.* = true;
event._current_target = current_target;
// Compute adjusted target for shadow DOM retargeting (only if needed)
@@ -377,8 +384,11 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
page.js.localScope(&ls);
defer ls.deinit();
const js_val = try ls.local.zigValueToJs(event, .{});
was_handled.* = true;
switch (listener.function) {
.value => |value| try ls.toLocal(value).callWithThis(void, current_target, .{event}),
.value => |value| try ls.toLocal(value).callWithThis(void, current_target, .{js_val}),
.string => |string| {
const str = try page.call_arena.dupeZ(u8, string.str());
try ls.local.eval(str, null);
@@ -386,7 +396,7 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
.object => |obj_global| {
const obj = ls.toLocal(obj_global);
if (try obj.getFunction("handleEvent")) |handleEvent| {
try handleEvent.callWithThis(void, obj, .{event});
try handleEvent.callWithThis(void, obj, .{js_val});
}
},
}

View File

@@ -172,60 +172,56 @@ pub fn eventTarget(self: *Factory, child: anytype) !*@TypeOf(child) {
return chain.get(1);
}
fn eventInit(typ: []const u8, value: anytype, page: *Page) !Event {
fn eventInit(arena: Allocator, typ: []const u8, value: anytype, page: *Page) !Event {
// Round to 2ms for privacy (browsers do this)
const raw_timestamp = @import("../datetime.zig").milliTimestamp(.monotonic);
const time_stamp = (raw_timestamp / 2) * 2;
return .{
._page = page,
._arena = arena,
._type = unionInit(Event.Type, value),
._type_string = try String.init(page.arena, typ, .{}),
._type_string = try String.init(arena, typ, .{}),
._time_stamp = time_stamp,
};
}
// this is a root object
pub fn event(self: *Factory, typ: []const u8, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
pub fn event(self: *Factory, arena: Allocator, typ: []const u8, child: anytype) !*@TypeOf(child) {
const chain = try PrototypeChain(
&.{ Event, @TypeOf(child) },
).allocate(allocator);
).allocate(arena);
// Special case: Event has a _type_string field, so we need manual setup
const event_ptr = chain.get(0);
event_ptr.* = try eventInit(typ, chain.get(1), self._page);
event_ptr.* = try eventInit(arena, typ, chain.get(1), self._page);
chain.setLeaf(1, child);
return chain.get(1);
}
pub fn uiEvent(self: *Factory, typ: []const u8, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
pub fn uiEvent(self: *Factory, arena: Allocator, typ: []const u8, child: anytype) !*@TypeOf(child) {
const chain = try PrototypeChain(
&.{ Event, UIEvent, @TypeOf(child) },
).allocate(allocator);
).allocate(arena);
// Special case: Event has a _type_string field, so we need manual setup
const event_ptr = chain.get(0);
event_ptr.* = try eventInit(typ, chain.get(1), self._page);
event_ptr.* = try eventInit(arena, typ, chain.get(1), self._page);
chain.setMiddle(1, UIEvent.Type);
chain.setLeaf(2, child);
return chain.get(2);
}
pub fn mouseEvent(self: *Factory, typ: []const u8, mouse: MouseEvent, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
pub fn mouseEvent(self: *Factory, arena: Allocator, typ: []const u8, mouse: MouseEvent, child: anytype) !*@TypeOf(child) {
const chain = try PrototypeChain(
&.{ Event, UIEvent, MouseEvent, @TypeOf(child) },
).allocate(allocator);
).allocate(arena);
// Special case: Event has a _type_string field, so we need manual setup
const event_ptr = chain.get(0);
event_ptr.* = try eventInit(typ, chain.get(1), self._page);
event_ptr.* = try eventInit(arena, typ, chain.get(1), self._page);
chain.setMiddle(1, UIEvent.Type);
// Set MouseEvent with all its fields

View File

@@ -333,6 +333,10 @@ fn reset(self: *Page, comptime initializing: bool) !void {
self._customized_builtin_disconnected_callback_invoked = .{};
self._undefined_custom_elements = .{};
if (comptime IS_DEBUG) {
self._arena_pool_leak_track = .{};
}
try self.registerBackgroundTasks();
}

View File

@@ -197,22 +197,21 @@ pub fn mapZigInstanceToJs(self: *const Local, js_obj_handle: ?*const v8.Object,
// dont' use js_obj.persist(), because we don't want to track this in
// context.global_objects, we want to track it in context.identity_map.
v8.v8__Global__New(isolate.handle, js_obj.handle, gop.value_ptr);
if (@hasDecl(JsApi.Meta, "finalizer")) {
if (comptime IS_DEBUG) {
// You can normally return a "*Node" and we'll correctly
// handle it as what it really is, e.g. an HTMLScriptElement.
// But for finalizers, we can't do that. I think this
// limitation will be OK - this auto-resolution is largely
// limited to Node -> HtmlElement, none of which has finalizers
std.debug.assert(resolved.class_id == JsApi.Meta.class_id);
}
if (@hasDecl(JsApi.Meta, "finalizer")) {
// It would be great if resolved knew the resolved type, but I
// can't figure out how to make that work, since it depends on
// the [runtime] `value.
// We need the resolved finalizer, which we have in resolved.
// The above if statement would be more clear as:
// if (resolved.finalizer) |finalizer| {
// But that's a runtime check.
// Instead, we check if the base has finalizer. The assumption
// here is that if a resolve type has a finalizer, than the base
// should have a finalizer too.
try ctx.finalizer_callbacks.put(ctx.arena, @intFromPtr(resolved.ptr), .init(value));
if (@hasDecl(JsApi.Meta, "weak")) {
if (comptime IS_DEBUG) {
std.debug.assert(JsApi.Meta.weak == true);
}
v8.v8__Global__SetWeakFinalizer(gop.value_ptr, resolved.ptr, JsApi.Meta.finalizer.from_v8, v8.kParameter);
if (resolved.weak) {
v8.v8__Global__SetWeakFinalizer(gop.value_ptr, resolved.ptr, resolved.finalizer.?, v8.kParameter);
}
}
return js_obj;
@@ -1032,9 +1031,11 @@ fn jsUnsignedIntToZig(comptime T: type, max: comptime_int, maybe: u32) !T {
// This function recursively walks the _type union field (if there is one) to
// get the most specific class_id possible.
const Resolved = struct {
weak: bool,
ptr: *anyopaque,
class_id: u16,
prototype_chain: []const @import("TaggedOpaque.zig").PrototypeChainEntry,
finalizer: ?*const fn (handle: ?*const v8.WeakCallbackInfo) callconv(.c) void = null,
};
pub fn resolveValue(value: anytype) Resolved {
const T = bridge.Struct(@TypeOf(value));
@@ -1062,10 +1063,13 @@ pub fn resolveValue(value: anytype) Resolved {
}
fn resolveT(comptime T: type, value: *anyopaque) Resolved {
const Meta = T.JsApi.Meta;
return .{
.ptr = value,
.class_id = T.JsApi.Meta.class_id,
.prototype_chain = &T.JsApi.Meta.prototype_chain,
.class_id = Meta.class_id,
.prototype_chain = &Meta.prototype_chain,
.weak = if (@hasDecl(Meta, "weak")) Meta.weak else false,
.finalizer = if (@hasDecl(Meta, "finalizer")) Meta.finalizer.from_v8 else null,
};
}

View File

@@ -111,7 +111,8 @@ pub fn Builder(comptime T: type) type {
// to be possible between finalization and context shutdown,
// we need to be defensive).
// There _ARE_ alternatives to this. But this is simple.
const ctx = self._page.js;
const page = findPageField(T, self);
const ctx = page.js;
if (!ctx.identity_map.contains(@intFromPtr(ptr))) {
return;
}
@@ -515,6 +516,17 @@ fn countFlattenedTypes(comptime Types: []const type) usize {
return c;
}
fn findPageField(comptime OriginalT: type, value: anytype) *Page {
const T = Struct(@TypeOf(value));
if (@hasField(T, "_page")) {
return value._page;
}
if (@hasField(T, "_proto") == false) {
@compileError("Expected to find a _page: *Page field on " ++ @typeName(OriginalT) ++ " or it's _proto chain");
}
return findPageField(OriginalT, value._proto);
}
// T => T
// *T => T
pub fn Struct(comptime T: type) type {

View File

@@ -24,11 +24,14 @@ const EventTarget = @import("EventTarget.zig");
const Node = @import("Node.zig");
const String = @import("../../string.zig").String;
const Allocator = std.mem.Allocator;
pub const Event = @This();
pub const _prototype_root = true;
_type: Type,
_page: *Page,
_arena: Allocator,
_bubbles: bool = false,
_cancelable: bool = false,
_composed: bool = false,
@@ -79,23 +82,32 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*Event {
}
fn initWithTrusted(typ: []const u8, opts_: ?Options, trusted: bool, page: *Page) !*Event {
const arena = try page.getArena(.{ .debug = "Event" });
errdefer page.releaseArena(arena);
const opts = opts_ orelse Options{};
// Round to 2ms for privacy (browsers do this)
const raw_timestamp = @import("../../datetime.zig").milliTimestamp(.monotonic);
const time_stamp = (raw_timestamp / 2) * 2;
const event = try page._factory.create(Event{
const self = try arena.create(Event);
self.* = .{
._page = page,
._arena = arena,
._type = .generic,
._bubbles = opts.bubbles,
._time_stamp = time_stamp,
._cancelable = opts.cancelable,
._composed = opts.composed,
._type_string = try String.init(page.arena, typ, .{}),
});
._isTrusted = trusted,
._type_string = try String.init(arena, typ, .{}),
};
return self;
}
event._isTrusted = trusted;
return event;
pub fn deinit(self: *Event, _: bool) void {
self._page.releaseArena(self._arena);
}
pub fn initEvent(
@@ -103,13 +115,12 @@ pub fn initEvent(
event_string: []const u8,
bubbles: ?bool,
cancelable: ?bool,
page: *Page,
) !void {
if (self._event_phase != .none) {
return;
}
self._type_string = try String.init(page.arena, event_string, .{});
self._type_string = try String.init(self._arena, event_string, .{});
self._bubbles = bubbles orelse false;
self._cancelable = cancelable orelse false;
self._stop_propagation = false;
@@ -385,6 +396,8 @@ pub const JsApi = struct {
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(Event.deinit);
};
pub const constructor = bridge.constructor(Event.init, .{});

View File

@@ -33,17 +33,24 @@ const CompositionEventOptions = struct {
const Options = Event.inheritOptions(CompositionEvent, CompositionEventOptions);
pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*CompositionEvent {
const arena = try page.getArena(.{ .debug = "CompositionEvent" });
errdefer page.releaseArena(arena);
const opts = opts_ orelse Options{};
const event = try page._factory.event(typ, CompositionEvent{
const event = try page._factory.event(arena, typ, CompositionEvent{
._proto = undefined,
._data = if (opts.data) |str| try page.dupeString(str) else "",
._data = if (opts.data) |str| try arena.dupe(u8, str) else "",
});
Event.populatePrototypes(event, opts, false);
return event;
}
pub fn deinit(self: *CompositionEvent, shutdown: bool) void {
self._proto.deinit(shutdown);
}
pub fn asEvent(self: *CompositionEvent) *Event {
return self._proto;
}
@@ -59,6 +66,8 @@ pub const JsApi = struct {
pub const name = "CompositionEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(CompositionEvent.deinit);
};
pub const constructor = bridge.constructor(CompositionEvent.init, .{});

View File

@@ -37,10 +37,13 @@ const CustomEventOptions = struct {
const Options = Event.inheritOptions(CustomEvent, CustomEventOptions);
pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*CustomEvent {
const arena = page.arena;
const arena = try page.getArena(.{ .debug = "CustomEvent" });
errdefer page.releaseArena(arena);
const opts = opts_ orelse Options{};
const event = try page._factory.event(
arena,
typ,
CustomEvent{
._arena = arena,
@@ -53,21 +56,23 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*CustomEvent {
return event;
}
pub fn deinit(self: *CustomEvent, shutdown: bool) void {
self._proto.deinit(shutdown);
}
pub fn initCustomEvent(
self: *CustomEvent,
event_string: []const u8,
bubbles: ?bool,
cancelable: ?bool,
detail_: ?js.Value.Global,
page: *Page,
detail: ?js.Value.Global,
) !void {
// This function can only be called after the constructor has called.
// So we assume proto is initialized already by constructor.
self._proto._type_string = try String.init(page.arena, event_string, .{});
self._proto._type_string = try String.init(self._proto._arena, event_string, .{});
self._proto._bubbles = bubbles orelse false;
self._proto._cancelable = cancelable orelse false;
// Detail is stored separately.
self._detail = detail_;
self._detail = detail;
}
pub fn asEvent(self: *CustomEvent) *Event {
@@ -85,6 +90,8 @@ pub const JsApi = struct {
pub const name = "CustomEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(CustomEvent.deinit);
};
pub const constructor = bridge.constructor(CustomEvent.init, .{});

View File

@@ -31,7 +31,6 @@ _filename: []const u8 = "",
_line_number: u32 = 0,
_column_number: u32 = 0,
_error: ?js.Value.Global = null,
_arena: Allocator,
pub const ErrorEventOptions = struct {
message: ?[]const u8 = null,
@@ -52,13 +51,15 @@ pub fn initTrusted(typ: []const u8, opts_: ?Options, page: *Page) !*ErrorEvent {
}
fn initWithTrusted(typ: []const u8, opts_: ?Options, trusted: bool, page: *Page) !*ErrorEvent {
const arena = page.arena;
const arena = try page.getArena(.{ .debug = "ErrorEvent" });
errdefer page.releaseArena(arena);
const opts = opts_ orelse Options{};
const event = try page._factory.event(
arena,
typ,
ErrorEvent{
._arena = arena,
._proto = undefined,
._message = if (opts.message) |str| try arena.dupe(u8, str) else "",
._filename = if (opts.filename) |str| try arena.dupe(u8, str) else "",
@@ -72,6 +73,10 @@ fn initWithTrusted(typ: []const u8, opts_: ?Options, trusted: bool, page: *Page)
return event;
}
pub fn deinit(self: *ErrorEvent, shutdown: bool) void {
self._proto.deinit(shutdown);
}
pub fn asEvent(self: *ErrorEvent) *Event {
return self._proto;
}
@@ -103,6 +108,8 @@ pub const JsApi = struct {
pub const name = "ErrorEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(ErrorEvent.deinit);
};
// Start API

View File

@@ -189,15 +189,19 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*KeyboardEvent {
}
fn initWithTrusted(typ: []const u8, _opts: ?Options, trusted: bool, page: *Page) !*KeyboardEvent {
const arena = try page.getArena(.{ .debug = "KeyboardEvent" });
errdefer page.releaseArena(arena);
const opts = _opts orelse Options{};
const event = try page._factory.uiEvent(
arena,
typ,
KeyboardEvent{
._proto = undefined,
._key = try Key.fromString(page.arena, opts.key),
._key = try Key.fromString(arena, opts.key),
._location = std.meta.intToEnum(Location, opts.location) catch return error.TypeError,
._code = if (opts.code) |c| try page.dupeString(c) else "",
._code = if (opts.code) |c| try arena.dupe(u8, c) else "",
._repeat = opts.repeat,
._is_composing = opts.isComposing,
._ctrl_key = opts.ctrlKey,
@@ -211,6 +215,10 @@ fn initWithTrusted(typ: []const u8, _opts: ?Options, trusted: bool, page: *Page)
return event;
}
pub fn deinit(self: *KeyboardEvent, shutdown: bool) void {
self._proto.deinit(shutdown);
}
pub fn asEvent(self: *KeyboardEvent) *Event {
return self._proto.asEvent();
}
@@ -251,8 +259,8 @@ pub fn getShiftKey(self: *const KeyboardEvent) bool {
return self._shift_key;
}
pub fn getModifierState(self: *const KeyboardEvent, str: []const u8, page: *Page) !bool {
const key = try Key.fromString(page.arena, str);
pub fn getModifierState(self: *KeyboardEvent, str: []const u8) !bool {
const key = try Key.fromString(self.asEvent()._arena, str);
switch (key) {
.Alt, .AltGraph => return self._alt_key,
@@ -274,6 +282,8 @@ pub const JsApi = struct {
pub const name = "KeyboardEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(KeyboardEvent.deinit);
};
pub const constructor = bridge.constructor(KeyboardEvent.init, .{});

View File

@@ -46,14 +46,18 @@ pub fn initTrusted(typ: []const u8, opts_: ?Options, page: *Page) !*MessageEvent
}
fn initWithTrusted(typ: []const u8, opts_: ?Options, trusted: bool, page: *Page) !*MessageEvent {
const arena = try page.getArena(.{ .debug = "MessageEvent" });
errdefer page.releaseArena(arena);
const opts = opts_ orelse Options{};
const event = try page._factory.event(
arena,
typ,
MessageEvent{
._proto = undefined,
._data = opts.data,
._origin = if (opts.origin) |str| try page.arena.dupe(u8, str) else "",
._origin = if (opts.origin) |str| try arena.dupe(u8, str) else "",
._source = opts.source,
},
);
@@ -62,6 +66,10 @@ fn initWithTrusted(typ: []const u8, opts_: ?Options, trusted: bool, page: *Page)
return event;
}
pub fn deinit(self: *MessageEvent, shutdown: bool) void {
self._proto.deinit(shutdown);
}
pub fn asEvent(self: *MessageEvent) *Event {
return self._proto;
}
@@ -85,6 +93,8 @@ pub const JsApi = struct {
pub const name = "MessageEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(MessageEvent.deinit);
};
pub const constructor = bridge.constructor(MessageEvent.init, .{});

View File

@@ -75,9 +75,13 @@ pub const Options = Event.inheritOptions(
);
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*MouseEvent {
const arena = try page.getArena(.{ .debug = "MouseEvent" });
errdefer page.releaseArena(arena);
const opts = _opts orelse Options{};
const event = try page._factory.uiEvent(
arena,
typ,
MouseEvent{
._type = .generic,
@@ -99,6 +103,10 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*MouseEvent {
return event;
}
pub fn deinit(self: *MouseEvent, shutdown: bool) void {
self._proto.deinit(shutdown);
}
pub fn asEvent(self: *MouseEvent) *Event {
return self._proto.asEvent();
}
@@ -180,6 +188,8 @@ pub const JsApi = struct {
pub const name = "MouseEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(MouseEvent.deinit);
};
pub const constructor = bridge.constructor(MouseEvent.init, .{});

View File

@@ -53,12 +53,16 @@ fn initWithTrusted(
trusted: bool,
page: *Page,
) !*NavigationCurrentEntryChangeEvent {
const arena = try page.getArena(.{ .debug = "NavigationCurrentEntryChangeEvent" });
errdefer page.releaseArena(arena);
const navigation_type = if (opts.navigationType) |nav_type_str|
std.meta.stringToEnum(NavigationType, nav_type_str)
else
null;
const event = try page._factory.event(
arena,
typ,
NavigationCurrentEntryChangeEvent{
._proto = undefined,
@@ -71,6 +75,10 @@ fn initWithTrusted(
return event;
}
pub fn deinit(self: *NavigationCurrentEntryChangeEvent, shutdown: bool) void {
self._proto.deinit(shutdown);
}
pub fn asEvent(self: *NavigationCurrentEntryChangeEvent) *Event {
return self._proto;
}
@@ -90,6 +98,8 @@ pub const JsApi = struct {
pub const name = "NavigationCurrentEntryChangeEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(NavigationCurrentEntryChangeEvent.deinit);
};
pub const constructor = bridge.constructor(NavigationCurrentEntryChangeEvent.init, .{});

View File

@@ -41,9 +41,13 @@ pub fn initTrusted(typ: []const u8, _opts: ?Options, page: *Page) !*PageTransiti
}
fn initWithTrusted(typ: []const u8, _opts: ?Options, trusted: bool, page: *Page) !*PageTransitionEvent {
const arena = try page.getArena(.{ .debug = "PageTransitionEvent" });
errdefer page.releaseArena(arena);
const opts = _opts orelse Options{};
const event = try page._factory.event(
arena,
typ,
PageTransitionEvent{
._proto = undefined,
@@ -55,6 +59,10 @@ fn initWithTrusted(typ: []const u8, _opts: ?Options, trusted: bool, page: *Page)
return event;
}
pub fn deinit(self: *PageTransitionEvent, shutdown: bool) void {
self._proto.deinit(shutdown);
}
pub fn asEvent(self: *PageTransitionEvent) *Event {
return self._proto;
}
@@ -70,6 +78,8 @@ pub const JsApi = struct {
pub const name = "PageTransitionEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(PageTransitionEvent.deinit);
};
pub const constructor = bridge.constructor(PageTransitionEvent.init, .{});

View File

@@ -81,9 +81,13 @@ const Options = Event.inheritOptions(
);
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*PointerEvent {
const arena = try page.getArena(.{ .debug = "PointerEvent" });
errdefer page.releaseArena(arena);
const opts = _opts orelse Options{};
const event = try page._factory.mouseEvent(
arena,
typ,
MouseEvent{
._type = .{ .pointer_event = undefined },
@@ -120,6 +124,10 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*PointerEvent {
return event;
}
pub fn deinit(self: *PointerEvent, shutdown: bool) void {
self._proto.deinit(shutdown);
}
pub fn asEvent(self: *PointerEvent) *Event {
return self._proto.asEvent();
}
@@ -179,6 +187,8 @@ pub const JsApi = struct {
pub const name = "PointerEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(PointerEvent.deinit);
};
pub const constructor = bridge.constructor(PointerEvent.init, .{});

View File

@@ -41,9 +41,13 @@ pub fn initTrusted(typ: []const u8, _opts: ?Options, page: *Page) !*PopStateEven
}
fn initWithTrusted(typ: []const u8, _opts: ?Options, trusted: bool, page: *Page) !*PopStateEvent {
const arena = try page.getArena(.{ .debug = "CustomEvPopStateEventent" });
errdefer page.releaseArena(arena);
const opts = _opts orelse Options{};
const event = try page._factory.event(
arena,
typ,
PopStateEvent{
._proto = undefined,
@@ -55,6 +59,10 @@ fn initWithTrusted(typ: []const u8, _opts: ?Options, trusted: bool, page: *Page)
return event;
}
pub fn deinit(self: *PopStateEvent, shutdown: bool) void {
self._proto.deinit(shutdown);
}
pub fn asEvent(self: *PopStateEvent) *Event {
return self._proto;
}
@@ -76,6 +84,8 @@ pub const JsApi = struct {
pub const name = "PopStateEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(PopStateEvent.deinit);
};
pub const constructor = bridge.constructor(PopStateEvent.init, .{});

View File

@@ -42,9 +42,13 @@ pub fn initTrusted(typ: []const u8, _opts: ?Options, page: *Page) !*ProgressEven
}
fn initWithTrusted(typ: []const u8, _opts: ?Options, trusted: bool, page: *Page) !*ProgressEvent {
const arena = try page.getArena(.{ .debug = "ProgressEvent" });
errdefer page.releaseArena(arena);
const opts = _opts orelse Options{};
const event = try page._factory.event(
arena,
typ,
ProgressEvent{
._proto = undefined,
@@ -57,6 +61,10 @@ fn initWithTrusted(typ: []const u8, _opts: ?Options, trusted: bool, page: *Page)
return event;
}
pub fn deinit(self: *ProgressEvent, shutdown: bool) void {
self._proto.deinit(shutdown);
}
pub fn asEvent(self: *ProgressEvent) *Event {
return self._proto;
}
@@ -81,6 +89,8 @@ pub const JsApi = struct {
pub const name = "ProgressEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(ProgressEvent.deinit);
};
pub const constructor = bridge.constructor(ProgressEvent.init, .{});

View File

@@ -45,9 +45,13 @@ pub const Options = Event.inheritOptions(
);
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*UIEvent {
const arena = try page.getArena(.{ .debug = "UIEvent" });
errdefer page.releaseArena(arena);
const opts = _opts orelse Options{};
const event = try page._factory.event(
arena,
typ,
UIEvent{
._type = .generic,
@@ -61,6 +65,10 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*UIEvent {
return event;
}
pub fn deinit(self: *UIEvent, shutdown: bool) void {
self._proto.deinit(shutdown);
}
pub fn as(self: *UIEvent, comptime T: type) *T {
return self.is(T).?;
}
@@ -105,6 +113,8 @@ pub const JsApi = struct {
pub const name = "UIEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(UIEvent.deinit);
};
pub const constructor = bridge.constructor(UIEvent.init, .{});

View File

@@ -356,7 +356,7 @@ test "cdp Node: Registry register" {
}
{
const dom_node = (try doc.querySelector(.wrap ("p"), page)).?.asNode();
const dom_node = (try doc.querySelector(.wrap("p"), page)).?.asNode();
const node = try registry.register(dom_node);
const n1b = registry.lookup_by_id.get(2).?;
const n1c = registry.lookup_by_node.get(node.dom).?;
@@ -400,18 +400,18 @@ test "cdp Node: search list" {
defer page._session.removePage();
var doc = page.window._document;
const s1 = try search_list.create((try doc.querySelectorAll(.wrap ("a"), page))._nodes);
const s1 = try search_list.create((try doc.querySelectorAll(.wrap("a"), page))._nodes);
try testing.expectEqual("1", s1.name);
try testing.expectEqualSlices(u32, &.{ 1, 2 }, s1.node_ids);
try testing.expectEqual(2, registry.lookup_by_id.count());
try testing.expectEqual(2, registry.lookup_by_node.count());
const s2 = try search_list.create((try doc.querySelectorAll(.wrap ("#a1"), page))._nodes);
const s2 = try search_list.create((try doc.querySelectorAll(.wrap("#a1"), page))._nodes);
try testing.expectEqual("2", s2.name);
try testing.expectEqualSlices(u32, &.{1}, s2.node_ids);
const s3 = try search_list.create((try doc.querySelectorAll(.wrap ("#a2"), page))._nodes);
const s3 = try search_list.create((try doc.querySelectorAll(.wrap("#a2"), page))._nodes);
try testing.expectEqual("3", s3.name);
try testing.expectEqualSlices(u32, &.{2}, s3.node_ids);