Merge pull request #2024 from lightpanda-io/finalizers
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / wba-demo-scripts (push) Has been cancelled
e2e-test / wba-test (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig fmt (push) Has been cancelled
zig-test / zig test using v8 in debug mode (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled

Rework finalizers
This commit is contained in:
Karl Seguin
2026-03-30 17:03:08 +08:00
committed by GitHub
53 changed files with 541 additions and 479 deletions

View File

@@ -205,7 +205,7 @@ pub fn dispatch(self: *EventManager, target: *EventTarget, event: *Event) Dispat
pub fn dispatchOpts(self: *EventManager, target: *EventTarget, event: *Event, comptime opts: DispatchOpts) DispatchError!void { pub fn dispatchOpts(self: *EventManager, target: *EventTarget, event: *Event, comptime opts: DispatchOpts) DispatchError!void {
event.acquireRef(); event.acquireRef();
defer event.deinit(false, self.page._session); defer _ = event.releaseRef(self.page._session);
if (comptime IS_DEBUG) { if (comptime IS_DEBUG) {
log.debug(.event, "eventManager.dispatch", .{ .type = event._type_string.str(), .bubbles = event._bubbles }); log.debug(.event, "eventManager.dispatch", .{ .type = event._type_string.str(), .bubbles = event._bubbles });
@@ -240,7 +240,7 @@ pub fn dispatchDirect(self: *EventManager, target: *EventTarget, event: *Event,
defer window._current_event = prev_event; defer window._current_event = prev_event;
event.acquireRef(); event.acquireRef();
defer event.deinit(false, page._session); defer _ = event.releaseRef(page._session);
if (comptime IS_DEBUG) { if (comptime IS_DEBUG) {
log.debug(.event, "dispatchDirect", .{ .type = event._type_string, .context = opts.context }); log.debug(.event, "dispatchDirect", .{ .type = event._type_string, .context = opts.context });

View File

@@ -239,7 +239,7 @@ fn eventInit(arena: Allocator, typ: String, value: anytype) !Event {
const time_stamp = (raw_timestamp / 2) * 2; const time_stamp = (raw_timestamp / 2) * 2;
return .{ return .{
._rc = 0, ._rc = .{},
._arena = arena, ._arena = arena,
._type = unionInit(Event.Type, value), ._type = unionInit(Event.Type, value),
._type_string = typ, ._type_string = typ,
@@ -255,6 +255,7 @@ pub fn blob(_: *const Factory, arena: Allocator, child: anytype) !*@TypeOf(child
const blob_ptr = chain.get(0); const blob_ptr = chain.get(0);
blob_ptr.* = .{ blob_ptr.* = .{
._rc = .{},
._arena = arena, ._arena = arena,
._type = unionInit(Blob.Type, chain.get(1)), ._type = unionInit(Blob.Type, chain.get(1)),
._slice = "", ._slice = "",
@@ -271,7 +272,7 @@ pub fn abstractRange(_: *const Factory, arena: Allocator, child: anytype, page:
const doc = page.document.asNode(); const doc = page.document.asNode();
const abstract_range = chain.get(0); const abstract_range = chain.get(0);
abstract_range.* = AbstractRange{ abstract_range.* = AbstractRange{
._rc = 0, ._rc = .{},
._arena = arena, ._arena = arena,
._page_id = page.id, ._page_id = page.id,
._type = unionInit(AbstractRange.Type, chain.get(1)), ._type = unionInit(AbstractRange.Type, chain.get(1)),

View File

@@ -3392,7 +3392,7 @@ pub fn handleClick(self: *Page, target: *Node) !void {
pub fn triggerKeyboard(self: *Page, keyboard_event: *KeyboardEvent) !void { pub fn triggerKeyboard(self: *Page, keyboard_event: *KeyboardEvent) !void {
const event = keyboard_event.asEvent(); const event = keyboard_event.asEvent();
const element = self.window._document._active_element orelse { const element = self.window._document._active_element orelse {
keyboard_event.deinit(false, self._session); _ = event.releaseRef(self._session);
return; return;
}; };
@@ -3488,7 +3488,7 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form
// so submit_event is still valid when we check _prevent_default // so submit_event is still valid when we check _prevent_default
submit_event.acquireRef(); submit_event.acquireRef();
defer submit_event.deinit(false, self._session); defer _ = submit_event.releaseRef(self._session);
try self._event_manager.dispatch(form_element.asEventTarget(), submit_event); try self._event_manager.dispatch(form_element.asEventTarget(), submit_event);
// If the submit event was prevented, don't submit the form // If the submit event was prevented, don't submit the form

View File

@@ -71,6 +71,18 @@ origins: std.StringHashMapUnmanaged(*js.Origin) = .empty,
// ensuring object identity works across same-origin frames. // ensuring object identity works across same-origin frames.
identity: js.Identity = .{}, identity: js.Identity = .{},
// Shared finalizer callbacks across all Identities. Keyed by Zig instance ptr.
// This ensures objects are only freed when ALL v8 wrappers are gone.
finalizer_callbacks: std.AutoHashMapUnmanaged(usize, *FinalizerCallback) = .empty,
// Tracked global v8 objects that need to be released on cleanup.
// Lives at Session level so objects can outlive individual Identities.
globals: std.ArrayList(v8.Global) = .empty,
// Temporary v8 globals that can be released early. Key is global.data_ptr.
// Lives at Session level so objects holding Temps can outlive individual Identities.
temps: std.AutoHashMapUnmanaged(usize, v8.Global) = .empty,
// Shared resources for all pages in this session. // Shared resources for all pages in this session.
// These live for the duration of the page tree (root + frames). // These live for the duration of the page tree (root + frames).
arena_pool: *ArenaPool, arena_pool: *ArenaPool,
@@ -224,6 +236,30 @@ pub fn releaseOrigin(self: *Session, origin: *js.Origin) void {
/// Reset page_arena and factory for a clean slate. /// Reset page_arena and factory for a clean slate.
/// Called when root page is removed. /// Called when root page is removed.
fn resetPageResources(self: *Session) void { fn resetPageResources(self: *Session) void {
// Force cleanup all remaining finalized objects
{
var it = self.finalizer_callbacks.valueIterator();
while (it.next()) |fc| {
fc.*.deinit(self);
}
self.finalizer_callbacks = .empty;
}
{
for (self.globals.items) |*global| {
v8.v8__Global__Reset(global);
}
self.globals = .empty;
}
{
var it = self.temps.valueIterator();
while (it.next()) |global| {
v8.v8__Global__Reset(global);
}
self.temps = .empty;
}
self.identity.deinit(); self.identity.deinit();
self.identity = .{}; self.identity = .{};
@@ -457,35 +493,25 @@ pub fn nextPageId(self: *Session) u32 {
return id; return id;
} }
// A type that has a finalizer can have its finalizer called one of two ways. // Every finalizable instance of Zig gets 1 FinalizerCallback registered in the
// The first is from V8 via the WeakCallback we give to weakRef. But that isn't // session. This is to ensure that, if v8 doesn't finalize the value, we can
// guaranteed to fire, so we track this in finalizer_callbacks and call them on // release on page reset.
// page reset.
pub const FinalizerCallback = struct { pub const FinalizerCallback = struct {
arena: Allocator, arena: Allocator,
session: *Session, session: *Session,
ptr: *anyopaque, resolved_ptr_id: usize,
global: v8.Global, finalizer_ptr_id: usize,
_deinit: *const fn (ptr_id: usize, session: *Session) void,
// For every FinalizerCallback we'll have 1+ FinalizerCallback.Identity: one
// for every identity that gets the instance. In most cases, that'l be 1.
pub const Identity = struct {
identity: *js.Identity, identity: *js.Identity,
zig_finalizer: *const fn (ptr: *anyopaque, session: *Session) void, fc: *Session.FinalizerCallback,
};
pub fn deinit(self: *FinalizerCallback) void {
self.zig_finalizer(self.ptr, self.session);
self.session.releaseArena(self.arena);
}
/// Release this item from the identity tracking maps (called after finalizer runs from V8)
pub fn releaseIdentity(self: *FinalizerCallback) void {
const session = self.session;
const id = @intFromPtr(self.ptr);
if (self.identity.identity_map.fetchRemove(id)) |kv| {
var global = kv.value;
v8.v8__Global__Reset(&global);
}
_ = self.identity.finalizer_callbacks.remove(id);
fn deinit(self: *FinalizerCallback, session: *Session) void {
self._deinit(self.finalizer_ptr_id, session);
session.releaseArena(self.arena); session.releaseArena(self.arena);
} }
}; };

View File

@@ -21,6 +21,7 @@ const lp = @import("lightpanda");
const log = @import("../../log.zig"); const log = @import("../../log.zig");
const js = @import("js.zig"); const js = @import("js.zig");
const bridge = @import("bridge.zig");
const Env = @import("Env.zig"); const Env = @import("Env.zig");
const Origin = @import("Origin.zig"); const Origin = @import("Origin.zig");
const Scheduler = @import("Scheduler.zig"); const Scheduler = @import("Scheduler.zig");
@@ -213,48 +214,11 @@ pub fn setOrigin(self: *Context, key: ?[]const u8) !void {
} }
pub fn trackGlobal(self: *Context, global: v8.Global) !void { pub fn trackGlobal(self: *Context, global: v8.Global) !void {
return self.identity.globals.append(self.identity_arena, global); return self.session.globals.append(self.session.page_arena, global);
} }
pub fn trackTemp(self: *Context, global: v8.Global) !void { pub fn trackTemp(self: *Context, global: v8.Global) !void {
return self.identity.temps.put(self.identity_arena, global.data_ptr, global); return self.session.temps.put(self.session.page_arena, global.data_ptr, global);
}
pub fn weakRef(self: *Context, obj: anytype) void {
const resolved = js.Local.resolveValue(obj);
const fc = self.identity.finalizer_callbacks.get(@intFromPtr(resolved.ptr)) orelse {
if (comptime IS_DEBUG) {
// should not be possible
std.debug.assert(false);
}
return;
};
v8.v8__Global__SetWeakFinalizer(&fc.global, fc, resolved.finalizer_from_v8, v8.kParameter);
}
pub fn safeWeakRef(self: *Context, obj: anytype) void {
const resolved = js.Local.resolveValue(obj);
const fc = self.identity.finalizer_callbacks.get(@intFromPtr(resolved.ptr)) orelse {
if (comptime IS_DEBUG) {
// should not be possible
std.debug.assert(false);
}
return;
};
v8.v8__Global__ClearWeak(&fc.global);
v8.v8__Global__SetWeakFinalizer(&fc.global, fc, resolved.finalizer_from_v8, v8.kParameter);
}
pub fn strongRef(self: *Context, obj: anytype) void {
const resolved = js.Local.resolveValue(obj);
const fc = self.identity.finalizer_callbacks.get(@intFromPtr(resolved.ptr)) orelse {
if (comptime IS_DEBUG) {
// should not be possible
std.debug.assert(false);
}
return;
};
v8.v8__Global__ClearWeak(&fc.global);
} }
pub const IdentityResult = struct { pub const IdentityResult = struct {
@@ -270,35 +234,6 @@ pub fn addIdentity(self: *Context, ptr: usize) !IdentityResult {
}; };
} }
pub fn releaseTemp(self: *Context, global: v8.Global) void {
if (self.identity.temps.fetchRemove(global.data_ptr)) |kv| {
var g = kv.value;
v8.v8__Global__Reset(&g);
}
}
pub fn createFinalizerCallback(
self: *Context,
global: v8.Global,
ptr: *anyopaque,
zig_finalizer: *const fn (ptr: *anyopaque, session: *Session) void,
) !*Session.FinalizerCallback {
const session = self.session;
const arena = try session.getArena(.{ .debug = "FinalizerCallback" });
errdefer session.releaseArena(arena);
const fc = try arena.create(Session.FinalizerCallback);
fc.* = .{
.arena = arena,
.session = session,
.ptr = ptr,
.global = global,
.zig_finalizer = zig_finalizer,
// Store identity pointer for cleanup when V8 GCs the object
.identity = self.identity,
};
return fc;
}
// Any operation on the context have to be made from a local. // Any operation on the context have to be made from a local.
pub fn localScope(self: *Context, ls: *js.Local.Scope) void { pub fn localScope(self: *Context, ls: *js.Local.Scope) void {
const isolate = self.isolate; const isolate = self.isolate;

View File

@@ -213,7 +213,7 @@ fn _persist(self: *const Function, comptime is_global: bool) !(if (is_global) Gl
return .{ .handle = global, .temps = {} }; return .{ .handle = global, .temps = {} };
} }
try ctx.trackTemp(global); try ctx.trackTemp(global);
return .{ .handle = global, .temps = &ctx.identity.temps }; return .{ .handle = global, .temps = &ctx.session.temps };
} }
pub fn tempWithThis(self: *const Function, value: anytype) !Temp { pub fn tempWithThis(self: *const Function, value: anytype) !Temp {

View File

@@ -38,38 +38,9 @@ const Identity = @This();
// Maps Zig instance pointers to their v8::Global(Object) wrappers. // Maps Zig instance pointers to their v8::Global(Object) wrappers.
identity_map: std.AutoHashMapUnmanaged(usize, v8.Global) = .empty, identity_map: std.AutoHashMapUnmanaged(usize, v8.Global) = .empty,
// Tracked global v8 objects that need to be released on cleanup.
globals: std.ArrayList(v8.Global) = .empty,
// Temporary v8 globals that can be released early. Key is global.data_ptr.
temps: std.AutoHashMapUnmanaged(usize, v8.Global) = .empty,
// Finalizer callbacks for weak references. Key is @intFromPtr of the Zig instance.
finalizer_callbacks: std.AutoHashMapUnmanaged(usize, *Session.FinalizerCallback) = .empty,
pub fn deinit(self: *Identity) void { pub fn deinit(self: *Identity) void {
{
var it = self.finalizer_callbacks.valueIterator();
while (it.next()) |finalizer| {
finalizer.*.deinit();
}
}
{
var it = self.identity_map.valueIterator(); var it = self.identity_map.valueIterator();
while (it.next()) |global| { while (it.next()) |global| {
v8.v8__Global__Reset(global); v8.v8__Global__Reset(global);
} }
}
for (self.globals.items) |*global| {
v8.v8__Global__Reset(global);
}
{
var it = self.temps.valueIterator();
while (it.next()) |global| {
v8.v8__Global__Reset(global);
}
}
} }

View File

@@ -17,10 +17,11 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const Session = @import("../Session.zig");
const log = @import("../../log.zig"); const log = @import("../../log.zig");
const string = @import("../../string.zig"); const string = @import("../../string.zig");
const Session = @import("../Session.zig");
const js = @import("js.zig"); const js = @import("js.zig");
const bridge = @import("bridge.zig"); const bridge = @import("bridge.zig");
const Caller = @import("Caller.zig"); const Caller = @import("Caller.zig");
@@ -213,7 +214,8 @@ pub fn mapZigInstanceToJs(self: *const Local, js_obj_handle: ?*const v8.Object,
.pointer => |ptr| { .pointer => |ptr| {
const resolved = resolveValue(value); const resolved = resolveValue(value);
const gop = try ctx.addIdentity(@intFromPtr(resolved.ptr)); const resolved_ptr_id = @intFromPtr(resolved.ptr);
const gop = try ctx.addIdentity(resolved_ptr_id);
if (gop.found_existing) { if (gop.found_existing) {
// we've seen this instance before, return the same object // we've seen this instance before, return the same object
return (js.Object.Global{ .handle = gop.value_ptr.* }).local(self); return (js.Object.Global{ .handle = gop.value_ptr.* }).local(self);
@@ -262,31 +264,27 @@ 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 // 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. // context.global_objects, we want to track it in context.identity_map.
v8.v8__Global__New(isolate.handle, js_obj.handle, gop.value_ptr); v8.v8__Global__New(isolate.handle, js_obj.handle, gop.value_ptr);
if (@hasDecl(JsApi.Meta, "finalizer")) { if (resolved.finalizer) |finalizer| {
// It would be great if resolved knew the resolved type, but I const finalizer_ptr_id = finalizer.ptr_id;
// can't figure out how to make that work, since it depends on finalizer.acquireRef(finalizer_ptr_id);
// 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_from_v8) |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, then the base
// should have a finalizer too.
const fc = try ctx.createFinalizerCallback(gop.value_ptr.*, resolved.ptr, resolved.finalizer_from_zig.?);
{
errdefer fc.deinit();
try ctx.identity.finalizer_callbacks.put(ctx.identity_arena, @intFromPtr(resolved.ptr), fc);
}
conditionallyReference(value); const session = ctx.session;
if (@hasDecl(JsApi.Meta, "weak")) { const finalizer_gop = try session.finalizer_callbacks.getOrPut(session.page_arena, finalizer_ptr_id);
if (comptime IS_DEBUG) { if (finalizer_gop.found_existing == false) {
std.debug.assert(JsApi.Meta.weak == true); // This is the first context (and very likely only one) to
} // see this Zig instance. We need to create the FinalizerCallback
v8.v8__Global__SetWeakFinalizer(gop.value_ptr, fc, resolved.finalizer_from_v8, v8.kParameter); // so that we can cleanup on page reset if v8 doesn't finalize.
errdefer _ = session.finalizer_callbacks.remove(finalizer_ptr_id);
finalizer_gop.value_ptr.* = try self.createFinalizerCallback(resolved_ptr_id, finalizer_ptr_id, finalizer.deinit);
} }
const fc = finalizer_gop.value_ptr.*;
const identity_finalizer = try fc.arena.create(Session.FinalizerCallback.Identity);
identity_finalizer.* = .{
.fc = fc,
.identity = ctx.identity,
};
v8.v8__Global__SetWeakFinalizer(gop.value_ptr, identity_finalizer, finalizer.release, v8.kParameter);
} }
return js_obj; return js_obj;
}, },
@@ -1121,12 +1119,19 @@ fn jsUnsignedIntToZig(comptime T: type, max: comptime_int, maybe: u32) !T {
// This function recursively walks the _type union field (if there is one) to // This function recursively walks the _type union field (if there is one) to
// get the most specific class_id possible. // get the most specific class_id possible.
const Resolved = struct { const Resolved = struct {
weak: bool,
ptr: *anyopaque, ptr: *anyopaque,
class_id: u16, class_id: u16,
prototype_chain: []const @import("TaggedOpaque.zig").PrototypeChainEntry, prototype_chain: []const @import("TaggedOpaque.zig").PrototypeChainEntry,
finalizer_from_v8: ?*const fn (handle: ?*const v8.WeakCallbackInfo) callconv(.c) void = null, finalizer: ?Finalizer,
finalizer_from_zig: ?*const fn (ptr: *anyopaque, session: *Session) void = null,
const Finalizer = struct {
// Resolved.ptr is the most specific value in a chain (e.g. IFrame, not EventTarget, Node, ...)
// Finalizer.ptr_id is the most specific value in a chain that defines an acquireRef
ptr_id: usize,
deinit: *const fn (ptr_id: usize, session: *Session) void,
acquireRef: *const fn (ptr_id: usize) void,
release: *const fn (handle: ?*const v8.WeakCallbackInfo) callconv(.c) void,
};
}; };
pub fn resolveValue(value: anytype) Resolved { pub fn resolveValue(value: anytype) Resolved {
const T = bridge.Struct(@TypeOf(value)); const T = bridge.Struct(@TypeOf(value));
@@ -1153,27 +1158,85 @@ pub fn resolveValue(value: anytype) Resolved {
unreachable; unreachable;
} }
fn resolveT(comptime T: type, value: *anyopaque) Resolved { fn resolveT(comptime T: type, value: *T) Resolved {
const Meta = T.JsApi.Meta; const Meta = T.JsApi.Meta;
return .{ return .{
.ptr = value, .ptr = value,
.class_id = Meta.class_id, .class_id = Meta.class_id,
.prototype_chain = &Meta.prototype_chain, .prototype_chain = &Meta.prototype_chain,
.weak = if (@hasDecl(Meta, "weak")) Meta.weak else false, .finalizer = blk: {
.finalizer_from_v8 = if (@hasDecl(Meta, "finalizer")) Meta.finalizer.from_v8 else null, const FT = (comptime findFinalizerType(T)) orelse break :blk null;
.finalizer_from_zig = if (@hasDecl(Meta, "finalizer")) Meta.finalizer.from_zig else null, const getFinalizerPtr = comptime finalizerPtrGetter(T, FT);
const finalizer_ptr = getFinalizerPtr(value);
const Wrap = struct {
fn deinit(ptr_id: usize, session: *Session) void {
FT.deinit(@ptrFromInt(ptr_id), session);
}
fn acquireRef(ptr_id: usize) void {
FT.acquireRef(@ptrFromInt(ptr_id));
}
fn release(handle: ?*const v8.WeakCallbackInfo) callconv(.c) void {
const ptr = v8.v8__WeakCallbackInfo__GetParameter(handle.?).?;
const identity_finalizer: *Session.FinalizerCallback.Identity = @ptrCast(@alignCast(ptr));
const fc = identity_finalizer.fc;
if (identity_finalizer.identity.identity_map.fetchRemove(fc.resolved_ptr_id)) |kv| {
var global = kv.value;
v8.v8__Global__Reset(&global);
}
FT.releaseRef(@ptrFromInt(fc.finalizer_ptr_id), fc.session);
}
};
break :blk .{
.ptr_id = @intFromPtr(finalizer_ptr),
.deinit = Wrap.deinit,
.acquireRef = Wrap.acquireRef,
.release = Wrap.release,
};
},
}; };
} }
fn conditionallyReference(value: anytype) void { // Start at the "resolved" type (the most specific) and work our way up the
const T = bridge.Struct(@TypeOf(value)); // prototype chain looking for the type that defines acquireRef
if (@hasDecl(T, "acquireRef")) { fn findFinalizerType(comptime T: type) ?type {
value.acquireRef(); const S = bridge.Struct(T);
return; if (@hasDecl(S, "acquireRef")) {
return S;
} }
if (@hasField(T, "_proto")) { if (@hasField(S, "_proto")) {
conditionallyReference(value._proto); const ProtoPtr = std.meta.fieldInfo(S, ._proto).type;
const ProtoChild = @typeInfo(ProtoPtr).pointer.child;
return findFinalizerType(ProtoChild);
} }
return null;
}
// Generate a function that follows the _proto pointer chain to get to the finalizer type
fn finalizerPtrGetter(comptime T: type, comptime FT: type) *const fn (*T) *FT {
const S = bridge.Struct(T);
if (S == FT) {
return struct {
fn get(v: *T) *FT {
return v;
}
}.get;
}
if (@hasField(S, "_proto")) {
const ProtoPtr = std.meta.fieldInfo(S, ._proto).type;
const ProtoChild = @typeInfo(ProtoPtr).pointer.child;
const childGetter = comptime finalizerPtrGetter(ProtoChild, FT);
return struct {
fn get(v: *T) *FT {
return childGetter(v._proto);
}
}.get;
}
@compileError("Cannot find path from " ++ @typeName(T) ++ " to " ++ @typeName(FT));
} }
pub fn stackTrace(self: *const Local) !?[]const u8 { pub fn stackTrace(self: *const Local) !?[]const u8 {
@@ -1381,6 +1444,34 @@ pub fn debugContextId(self: *const Local) i32 {
return v8.v8__Context__DebugContextId(self.handle); return v8.v8__Context__DebugContextId(self.handle);
} }
fn createFinalizerCallback(
self: *const Local,
// Key in identity map
// The most specific value (KeyboardEvent, not Event)
resolved_ptr_id: usize,
// The most specific value where finalizers are defined
// What actually gets acquired / released / deinit
finalizer_ptr_id: usize,
deinit: *const fn (ptr_id: usize, session: *Session) void,
) !*Session.FinalizerCallback {
const session = self.ctx.session;
const arena = try session.getArena(.{ .debug = "FinalizerCallback" });
errdefer session.releaseArena(arena);
const fc = try arena.create(Session.FinalizerCallback);
fc.* = .{
.arena = arena,
.session = session,
._deinit = deinit,
.resolved_ptr_id = resolved_ptr_id,
.finalizer_ptr_id = finalizer_ptr_id,
};
return fc;
}
// Encapsulates a Local and a HandleScope. When we're going from V8->Zig // Encapsulates a Local and a HandleScope. When we're going from V8->Zig
// we easily get both a Local and a HandleScope via Caller.init. // we easily get both a Local and a HandleScope via Caller.init.
// But when we're going from Zig -> V8, things are more complicated. // But when we're going from Zig -> V8, things are more complicated.

View File

@@ -67,7 +67,7 @@ fn _persist(self: *const Promise, comptime is_global: bool) !(if (is_global) Glo
return .{ .handle = global, .temps = {} }; return .{ .handle = global, .temps = {} };
} }
try ctx.trackTemp(global); try ctx.trackTemp(global);
return .{ .handle = global, .temps = &ctx.identity.temps }; return .{ .handle = global, .temps = &ctx.session.temps };
} }
pub const Temp = G(.temp); pub const Temp = G(.temp);

View File

@@ -303,7 +303,7 @@ fn _persist(self: *const Value, comptime is_global: bool) !(if (is_global) Globa
return .{ .handle = global, .temps = {} }; return .{ .handle = global, .temps = {} };
} }
try ctx.trackTemp(global); try ctx.trackTemp(global);
return .{ .handle = global, .temps = &ctx.identity.temps }; return .{ .handle = global, .temps = &ctx.session.temps };
} }
pub fn toZig(self: Value, comptime T: type) !T { pub fn toZig(self: Value, comptime T: type) !T {

View File

@@ -101,34 +101,19 @@ pub fn Builder(comptime T: type) type {
} }
return entries; return entries;
} }
pub fn finalizer(comptime func: *const fn (self: *T, shutdown: bool, session: *Session) void) Finalizer {
return .{
.from_zig = struct {
fn wrap(ptr: *anyopaque, session: *Session) void {
func(@ptrCast(@alignCast(ptr)), true, session);
}
}.wrap,
.from_v8 = struct {
fn wrap(handle: ?*const v8.WeakCallbackInfo) callconv(.c) void {
const ptr = v8.v8__WeakCallbackInfo__GetParameter(handle.?).?;
const fc: *Session.FinalizerCallback = @ptrCast(@alignCast(ptr));
const value_ptr = fc.ptr;
if (fc.identity.finalizer_callbacks.contains(@intFromPtr(value_ptr))) {
func(@ptrCast(@alignCast(value_ptr)), false, fc.session);
fc.releaseIdentity();
} else {
// A bit weird, but v8 _requires_ that we release it
// If we don't. We'll 100% crash.
v8.v8__Global__Reset(&fc.global);
}
}
}.wrap,
}; };
}
fn releaseRef(comptime T: type, ptr_id: usize, session: *Session) void {
if (@hasDecl(T, "releaseRef")) {
T.releaseRef(@ptrFromInt(ptr_id), session);
return;
} }
}; if (@hasField(T, "_proto")) {
releaseRef(Struct(std.meta.fieldInfo(T, ._proto).type), ptr_id, session);
return;
}
@compileError(@typeName(T) ++ " marked with finalizer without an acquireRef in its prototype chain");
} }
pub const Constructor = struct { pub const Constructor = struct {
@@ -411,17 +396,6 @@ pub const Property = struct {
} }
}; };
const Finalizer = struct {
// The finalizer wrapper when called from Zig. This is only called on
// Origin.deinit
from_zig: *const fn (ctx: *anyopaque, session: *Session) void,
// The finalizer wrapper when called from V8. This may never be called
// (hence why we fallback to calling in Origin.deinit). If it is called,
// it is only ever called after we SetWeak on the Global.
from_v8: *const fn (?*const v8.WeakCallbackInfo) callconv(.c) void,
};
pub fn unknownWindowPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 { pub fn unknownWindowPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?; const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
var caller: Caller = undefined; var caller: Caller = undefined;

View File

@@ -17,6 +17,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Session = @import("../Session.zig"); const Session = @import("../Session.zig");
@@ -31,7 +33,7 @@ const AbstractRange = @This();
pub const _prototype_root = true; pub const _prototype_root = true;
_rc: u8, _rc: lp.RC(u8) = .{},
_type: Type, _type: Type,
_page_id: u32, _page_id: u32,
_arena: Allocator, _arena: Allocator,
@@ -44,24 +46,18 @@ _start_container: *Node,
_range_link: std.DoublyLinkedList.Node = .{}, _range_link: std.DoublyLinkedList.Node = .{},
pub fn acquireRef(self: *AbstractRange) void { pub fn acquireRef(self: *AbstractRange) void {
self._rc += 1; self._rc.acquire();
} }
pub fn deinit(self: *AbstractRange, shutdown: bool, session: *Session) void { pub fn deinit(self: *AbstractRange, session: *Session) void {
_ = shutdown;
const rc = self._rc;
if (comptime IS_DEBUG) {
std.debug.assert(rc != 0);
}
if (rc == 1) {
if (session.findPageById(self._page_id)) |page| { if (session.findPageById(self._page_id)) |page| {
page._live_ranges.remove(&self._range_link); page._live_ranges.remove(&self._range_link);
} }
session.releaseArena(self._arena); session.releaseArena(self._arena);
return; }
}
self._rc = rc - 1; pub fn releaseRef(self: *AbstractRange, session: *Session) void {
self._rc.release(self, session);
} }
pub const Type = union(enum) { pub const Type = union(enum) {
@@ -338,8 +334,6 @@ pub const JsApi = struct {
pub const name = "AbstractRange"; pub const name = "AbstractRange";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(AbstractRange.deinit);
}; };
pub const startContainer = bridge.accessor(AbstractRange.getStartContainer, null, .{}); pub const startContainer = bridge.accessor(AbstractRange.getStartContainer, null, .{});

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const Writer = std.Io.Writer; const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
@@ -25,6 +25,7 @@ const Session = @import("../Session.zig");
const Mime = @import("../Mime.zig"); const Mime = @import("../Mime.zig");
const Writer = std.Io.Writer;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
/// https://w3c.github.io/FileAPI/#blob-section /// https://w3c.github.io/FileAPI/#blob-section
@@ -34,6 +35,7 @@ const Blob = @This();
pub const _prototype_root = true; pub const _prototype_root = true;
_type: Type, _type: Type,
_rc: lp.RC(u32),
_arena: Allocator, _arena: Allocator,
@@ -120,6 +122,7 @@ pub fn initWithMimeValidation(
const self = try arena.create(Blob); const self = try arena.create(Blob);
self.* = .{ self.* = .{
._rc = .{},
._arena = arena, ._arena = arena,
._type = .generic, ._type = .generic,
._slice = data, ._slice = data,
@@ -128,11 +131,18 @@ pub fn initWithMimeValidation(
return self; return self;
} }
pub fn deinit(self: *Blob, shutdown: bool, session: *Session) void { pub fn deinit(self: *Blob, session: *Session) void {
_ = shutdown;
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *Blob, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *Blob) void {
self._rc.acquire();
}
const largest_vector = @max(std.simd.suggestVectorLength(u8) orelse 1, 8); const largest_vector = @max(std.simd.suggestVectorLength(u8) orelse 1, 8);
/// Array of possible vector sizes for the current arch in decrementing order. /// Array of possible vector sizes for the current arch in decrementing order.
/// We may move this to some file for SIMD helpers in the future. /// We may move this to some file for SIMD helpers in the future.
@@ -325,8 +335,6 @@ pub const JsApi = struct {
pub const name = "Blob"; pub const name = "Blob";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(Blob.deinit);
}; };
pub const constructor = bridge.constructor(Blob.init, .{}); pub const constructor = bridge.constructor(Blob.init, .{});

View File

@@ -61,7 +61,7 @@ _fonts: ?*FontFaceSet = null,
_write_insertion_point: ?*Node = null, _write_insertion_point: ?*Node = null,
_script_created_parser: ?Parser.Streaming = null, _script_created_parser: ?Parser.Streaming = null,
_adopted_style_sheets: ?js.Object.Global = null, _adopted_style_sheets: ?js.Object.Global = null,
_selection: Selection = .init, _selection: Selection = .{ ._rc = .init(1) },
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#throw-on-dynamic-markup-insertion-counter // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#throw-on-dynamic-markup-insertion-counter
// Incremented during custom element reactions when parsing. When > 0, // Incremented during custom element reactions when parsing. When > 0,

View File

@@ -17,6 +17,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
@@ -55,7 +57,7 @@ _is_trusted: bool = false,
// - 0: no reference, always a transient state going to either 1 or about to be deinit'd // - 0: no reference, always a transient state going to either 1 or about to be deinit'd
// - 1: either zig or v8 have a reference // - 1: either zig or v8 have a reference
// - 2: both zig and v8 have a reference // - 2: both zig and v8 have a reference
_rc: u8 = 0, _rc: lp.RC(u8) = .{},
pub const EventPhase = enum(u8) { pub const EventPhase = enum(u8) {
none = 0, none = 0,
@@ -139,25 +141,16 @@ pub fn initEvent(
} }
pub fn acquireRef(self: *Event) void { pub fn acquireRef(self: *Event) void {
self._rc += 1; self._rc.acquire();
} }
pub fn deinit(self: *Event, shutdown: bool, session: *Session) void { /// Force cleanup on Session shutdown.
if (shutdown) { pub fn deinit(self: *Event, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
return; }
}
const rc = self._rc; pub fn releaseRef(self: *Event, session: *Session) void {
if (comptime IS_DEBUG) { self._rc.release(self, session);
std.debug.assert(rc != 0);
}
if (rc == 1) {
session.releaseArena(self._arena);
} else {
self._rc = rc - 1;
}
} }
pub fn as(self: *Event, comptime T: type) *T { pub fn as(self: *Event, comptime T: type) *T {
@@ -440,8 +433,6 @@ pub const JsApi = struct {
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(Event.deinit);
pub const enumerable = false; pub const enumerable = false;
}; };

View File

@@ -60,7 +60,7 @@ pub fn dispatchEvent(self: *EventTarget, event: *Event, page: *Page) !bool {
event._is_trusted = false; event._is_trusted = false;
event.acquireRef(); event.acquireRef();
defer event.deinit(false, page._session); defer _ = event.releaseRef(page._session);
try page._event_manager.dispatch(self, event); try page._event_manager.dispatch(self, event);
return !event._cancelable or !event._prevent_default; return !event._cancelable or !event._prevent_default;
} }

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
@@ -26,7 +27,6 @@ const Blob = @import("Blob.zig");
const File = @This(); const File = @This();
/// `File` inherits `Blob`.
_proto: *Blob, _proto: *Blob,
// TODO: Implement File API. // TODO: Implement File API.
@@ -36,10 +36,6 @@ pub fn init(page: *Page) !*File {
return page._factory.blob(arena, File{ ._proto = undefined }); return page._factory.blob(arena, File{ ._proto = undefined });
} }
pub fn deinit(self: *File, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub const JsApi = struct { pub const JsApi = struct {
pub const bridge = js.Bridge(File); pub const bridge = js.Bridge(File);
@@ -47,8 +43,6 @@ pub const JsApi = struct {
pub const name = "File"; pub const name = "File";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(File.deinit);
}; };
pub const constructor = bridge.constructor(File.init, .{}); pub const constructor = bridge.constructor(File.init, .{});

View File

@@ -17,6 +17,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
@@ -31,6 +33,7 @@ const Allocator = std.mem.Allocator;
/// https://developer.mozilla.org/en-US/docs/Web/API/FileReader /// https://developer.mozilla.org/en-US/docs/Web/API/FileReader
const FileReader = @This(); const FileReader = @This();
_rc: lp.RC(u8) = .{},
_page: *Page, _page: *Page,
_proto: *EventTarget, _proto: *EventTarget,
_arena: Allocator, _arena: Allocator,
@@ -70,7 +73,7 @@ pub fn init(page: *Page) !*FileReader {
}); });
} }
pub fn deinit(self: *FileReader, _: bool, session: *Session) void { pub fn deinit(self: *FileReader, session: *Session) void {
if (self._on_abort) |func| func.release(); if (self._on_abort) |func| func.release();
if (self._on_error) |func| func.release(); if (self._on_error) |func| func.release();
if (self._on_load) |func| func.release(); if (self._on_load) |func| func.release();
@@ -81,6 +84,14 @@ pub fn deinit(self: *FileReader, _: bool, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *FileReader, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *FileReader) void {
self._rc.acquire();
}
fn asEventTarget(self: *FileReader) *EventTarget { fn asEventTarget(self: *FileReader) *EventTarget {
return self._proto; return self._proto;
} }
@@ -309,8 +320,6 @@ pub const JsApi = struct {
pub const name = "FileReader"; pub const name = "FileReader";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(FileReader.deinit);
}; };
pub const constructor = bridge.constructor(FileReader.init, .{}); pub const constructor = bridge.constructor(FileReader.init, .{});

View File

@@ -16,6 +16,8 @@
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const log = @import("../../log.zig"); const log = @import("../../log.zig");
@@ -39,6 +41,7 @@ pub fn registerTypes() []const type {
const IntersectionObserver = @This(); const IntersectionObserver = @This();
_rc: lp.RC(u8) = .{},
_arena: Allocator, _arena: Allocator,
_callback: js.Function.Temp, _callback: js.Function.Temp,
_observing: std.ArrayList(*Element) = .{}, _observing: std.ArrayList(*Element) = .{},
@@ -108,15 +111,22 @@ pub fn init(callback: js.Function.Temp, options: ?ObserverInit, page: *Page) !*I
return self; return self;
} }
pub fn deinit(self: *IntersectionObserver, shutdown: bool, session: *Session) void { pub fn deinit(self: *IntersectionObserver, session: *Session) void {
self._callback.release(); self._callback.release();
if ((comptime IS_DEBUG) and !shutdown) { for (self._pending_entries.items) |entry| {
std.debug.assert(self._observing.items.len == 0); entry.deinitIfUnused(session);
} }
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *IntersectionObserver, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *IntersectionObserver) void {
self._rc.acquire();
}
pub fn observe(self: *IntersectionObserver, target: *Element, page: *Page) !void { pub fn observe(self: *IntersectionObserver, target: *Element, page: *Page) !void {
// Check if already observing this target // Check if already observing this target
for (self._observing.items) |elem| { for (self._observing.items) |elem| {
@@ -127,7 +137,7 @@ pub fn observe(self: *IntersectionObserver, target: *Element, page: *Page) !void
// Register with page if this is our first observation // Register with page if this is our first observation
if (self._observing.items.len == 0) { if (self._observing.items.len == 0) {
page.js.strongRef(self); self._rc._refs += 1;
try page.registerIntersectionObserver(self); try page.registerIntersectionObserver(self);
} }
@@ -144,17 +154,19 @@ pub fn observe(self: *IntersectionObserver, target: *Element, page: *Page) !void
} }
pub fn unobserve(self: *IntersectionObserver, target: *Element, page: *Page) void { pub fn unobserve(self: *IntersectionObserver, target: *Element, page: *Page) void {
const original_length = self._observing.items.len;
for (self._observing.items, 0..) |elem, i| { for (self._observing.items, 0..) |elem, i| {
if (elem == target) { if (elem == target) {
_ = self._observing.swapRemove(i); _ = self._observing.swapRemove(i);
_ = self._previous_states.remove(target); _ = self._previous_states.remove(target);
// Remove any pending entries for this target // Remove any pending entries for this target.
// Entries will be cleaned up by V8 GC via the finalizer.
var j: usize = 0; var j: usize = 0;
while (j < self._pending_entries.items.len) { while (j < self._pending_entries.items.len) {
if (self._pending_entries.items[j]._target == target) { if (self._pending_entries.items[j]._target == target) {
const entry = self._pending_entries.swapRemove(j); const entry = self._pending_entries.swapRemove(j);
entry.deinit(false, page._session); entry.deinitIfUnused(page._session);
} else { } else {
j += 1; j += 1;
} }
@@ -163,21 +175,26 @@ pub fn unobserve(self: *IntersectionObserver, target: *Element, page: *Page) voi
} }
} }
if (self._observing.items.len == 0) { if (original_length > 0 and self._observing.items.len == 0) {
page.js.safeWeakRef(self); self._rc._refs -= 1;
} }
} }
pub fn disconnect(self: *IntersectionObserver, page: *Page) void { pub fn disconnect(self: *IntersectionObserver, page: *Page) void {
page.unregisterIntersectionObserver(self);
self._observing.clearRetainingCapacity();
self._previous_states.clearRetainingCapacity();
for (self._pending_entries.items) |entry| { for (self._pending_entries.items) |entry| {
entry.deinit(false, page._session); entry.deinitIfUnused(page._session);
} }
self._pending_entries.clearRetainingCapacity(); self._pending_entries.clearRetainingCapacity();
page.js.safeWeakRef(self); self._previous_states.clearRetainingCapacity();
const observing_count = self._observing.items.len;
self._observing.clearRetainingCapacity();
page.unregisterIntersectionObserver(self);
if (observing_count > 0) {
_ = self.releaseRef(page._session);
}
} }
pub fn takeRecords(self: *IntersectionObserver, page: *Page) ![]*IntersectionObserverEntry { pub fn takeRecords(self: *IntersectionObserver, page: *Page) ![]*IntersectionObserverEntry {
@@ -268,7 +285,6 @@ fn checkIntersection(self: *IntersectionObserver, target: *Element, page: *Page)
._bounding_client_rect = try page._factory.create(data.bounding_client_rect), ._bounding_client_rect = try page._factory.create(data.bounding_client_rect),
._intersection_ratio = data.intersection_ratio, ._intersection_ratio = data.intersection_ratio,
}; };
try self._pending_entries.append(self._arena, entry); try self._pending_entries.append(self._arena, entry);
} }
@@ -310,6 +326,7 @@ pub fn deliverEntries(self: *IntersectionObserver, page: *Page) !void {
} }
pub const IntersectionObserverEntry = struct { pub const IntersectionObserverEntry = struct {
_rc: lp.RC(u8) = .{},
_arena: Allocator, _arena: Allocator,
_time: f64, _time: f64,
_target: *Element, _target: *Element,
@@ -319,10 +336,25 @@ pub const IntersectionObserverEntry = struct {
_intersection_ratio: f64, _intersection_ratio: f64,
_is_intersecting: bool, _is_intersecting: bool,
pub fn deinit(self: *IntersectionObserverEntry, _: bool, session: *Session) void { pub fn deinit(self: *IntersectionObserverEntry, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
fn deinitIfUnused(self: *IntersectionObserverEntry, session: *Session) void {
if (self._rc._refs == 0) {
// hasn't been handed to JS yet.
self.deinit(session);
}
}
pub fn releaseRef(self: *IntersectionObserverEntry, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *IntersectionObserverEntry) void {
self._rc.acquire();
}
pub fn getTarget(self: *const IntersectionObserverEntry) *Element { pub fn getTarget(self: *const IntersectionObserverEntry) *Element {
return self._target; return self._target;
} }
@@ -358,8 +390,6 @@ pub const IntersectionObserverEntry = struct {
pub const name = "IntersectionObserverEntry"; pub const name = "IntersectionObserverEntry";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(IntersectionObserverEntry.deinit);
}; };
pub const target = bridge.accessor(IntersectionObserverEntry.getTarget, null, .{}); pub const target = bridge.accessor(IntersectionObserverEntry.getTarget, null, .{});
@@ -379,8 +409,6 @@ pub const JsApi = struct {
pub const name = "IntersectionObserver"; pub const name = "IntersectionObserver";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(IntersectionObserver.deinit);
}; };
pub const constructor = bridge.constructor(init, .{}); pub const constructor = bridge.constructor(init, .{});

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const String = @import("../../string.zig").String; const String = @import("../../string.zig").String;
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
@@ -39,6 +40,7 @@ pub fn registerTypes() []const type {
const MutationObserver = @This(); const MutationObserver = @This();
_rc: lp.RC(u8) = .{},
_arena: Allocator, _arena: Allocator,
_callback: js.Function.Temp, _callback: js.Function.Temp,
_observing: std.ArrayList(Observing) = .{}, _observing: std.ArrayList(Observing) = .{},
@@ -85,15 +87,20 @@ pub fn init(callback: js.Function.Temp, page: *Page) !*MutationObserver {
return self; return self;
} }
pub fn deinit(self: *MutationObserver, shutdown: bool, session: *Session) void { /// Force cleanup on Session shutdown.
pub fn deinit(self: *MutationObserver, session: *Session) void {
self._callback.release(); self._callback.release();
if ((comptime IS_DEBUG) and !shutdown) {
std.debug.assert(self._observing.items.len == 0);
}
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *MutationObserver, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *MutationObserver) void {
self._rc.acquire();
}
pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions, page: *Page) !void { pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions, page: *Page) !void {
const arena = self._arena; const arena = self._arena;
@@ -158,7 +165,7 @@ pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions,
// Register with page if this is our first observation // Register with page if this is our first observation
if (self._observing.items.len == 0) { if (self._observing.items.len == 0) {
page.js.strongRef(self); self._rc._refs += 1;
try page.registerMutationObserver(self); try page.registerMutationObserver(self);
} }
@@ -169,13 +176,17 @@ pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions,
} }
pub fn disconnect(self: *MutationObserver, page: *Page) void { pub fn disconnect(self: *MutationObserver, page: *Page) void {
page.unregisterMutationObserver(self);
self._observing.clearRetainingCapacity();
for (self._pending_records.items) |record| { for (self._pending_records.items) |record| {
record.deinit(false, page._session); _ = record.releaseRef(page._session);
} }
self._pending_records.clearRetainingCapacity(); self._pending_records.clearRetainingCapacity();
page.js.safeWeakRef(self); const observing_count = self._observing.items.len;
self._observing.clearRetainingCapacity();
if (observing_count > 0) {
_ = self.releaseRef(page._session);
}
page.unregisterMutationObserver(self);
} }
pub fn takeRecords(self: *MutationObserver, page: *Page) ![]*MutationRecord { pub fn takeRecords(self: *MutationObserver, page: *Page) ![]*MutationRecord {
@@ -348,6 +359,7 @@ pub fn deliverRecords(self: *MutationObserver, page: *Page) !void {
} }
pub const MutationRecord = struct { pub const MutationRecord = struct {
_rc: lp.RC(u8) = .{},
_type: Type, _type: Type,
_target: *Node, _target: *Node,
_arena: Allocator, _arena: Allocator,
@@ -364,10 +376,18 @@ pub const MutationRecord = struct {
characterData, characterData,
}; };
pub fn deinit(self: *MutationRecord, _: bool, session: *Session) void { pub fn deinit(self: *MutationRecord, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *MutationRecord, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *MutationRecord) void {
self._rc.acquire();
}
pub fn getType(self: *const MutationRecord) []const u8 { pub fn getType(self: *const MutationRecord) []const u8 {
return switch (self._type) { return switch (self._type) {
.attributes => "attributes", .attributes => "attributes",
@@ -418,8 +438,6 @@ pub const MutationRecord = struct {
pub const name = "MutationRecord"; pub const name = "MutationRecord";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(MutationRecord.deinit);
}; };
pub const @"type" = bridge.accessor(MutationRecord.getType, null, .{}); pub const @"type" = bridge.accessor(MutationRecord.getType, null, .{});
@@ -441,8 +459,6 @@ pub const JsApi = struct {
pub const name = "MutationObserver"; pub const name = "MutationObserver";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(MutationObserver.deinit);
}; };
pub const constructor = bridge.constructor(MutationObserver.init, .{}); pub const constructor = bridge.constructor(MutationObserver.init, .{});

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const Session = @import("../Session.zig"); const Session = @import("../Session.zig");
@@ -50,14 +51,23 @@ pub fn query(_: *const Permissions, qd: QueryDescriptor, page: *Page) !js.Promis
} }
const PermissionStatus = struct { const PermissionStatus = struct {
_rc: lp.RC(u8) = .{},
_arena: Allocator, _arena: Allocator,
_name: []const u8, _name: []const u8,
_state: []const u8, _state: []const u8,
pub fn deinit(self: *PermissionStatus, _: bool, session: *Session) void { pub fn deinit(self: *PermissionStatus, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *PermissionStatus, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *PermissionStatus) void {
self._rc.acquire();
}
fn getName(self: *const PermissionStatus) []const u8 { fn getName(self: *const PermissionStatus) []const u8 {
return self._name; return self._name;
} }
@@ -72,8 +82,6 @@ const PermissionStatus = struct {
pub const name = "PermissionStatus"; pub const name = "PermissionStatus";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(PermissionStatus.deinit);
}; };
pub const name = bridge.accessor(getName, null, .{}); pub const name = bridge.accessor(getName, null, .{});
pub const state = bridge.accessor(getState, null, .{}); pub const state = bridge.accessor(getState, null, .{});

View File

@@ -38,10 +38,6 @@ pub fn init(page: *Page) !*Range {
return page._factory.abstractRange(arena, Range{ ._proto = undefined }, page); return page._factory.abstractRange(arena, Range{ ._proto = undefined }, page);
} }
pub fn deinit(self: *Range, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asAbstractRange(self: *Range) *AbstractRange { pub fn asAbstractRange(self: *Range) *AbstractRange {
return self._proto; return self._proto;
} }
@@ -697,8 +693,6 @@ pub const JsApi = struct {
pub const name = "Range"; pub const name = "Range";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(Range.deinit);
}; };
// Constants for compareBoundaryPoints // Constants for compareBoundaryPoints

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
@@ -32,18 +33,27 @@ const Selection = @This();
pub const SelectionDirection = enum { backward, forward, none }; pub const SelectionDirection = enum { backward, forward, none };
_rc: lp.RC(u8) = .{},
_range: ?*Range = null, _range: ?*Range = null,
_direction: SelectionDirection = .none, _direction: SelectionDirection = .none,
pub const init: Selection = .{}; pub const init: Selection = .{};
pub fn deinit(self: *Selection, shutdown: bool, session: *Session) void { pub fn deinit(self: *Selection, session: *Session) void {
if (self._range) |r| { if (self._range) |r| {
r.deinit(shutdown, session); r.asAbstractRange().releaseRef(session);
self._range = null; self._range = null;
} }
} }
pub fn releaseRef(self: *Selection, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *Selection) void {
self._rc.acquire();
}
fn dispatchSelectionChangeEvent(page: *Page) !void { fn dispatchSelectionChangeEvent(page: *Page) !void {
const event = try Event.init("selectionchange", .{}, page); const event = try Event.init("selectionchange", .{}, page);
try page._event_manager.dispatch(page.document.asEventTarget(), event); try page._event_manager.dispatch(page.document.asEventTarget(), event);
@@ -693,7 +703,7 @@ pub fn toString(self: *const Selection, page: *Page) ![]const u8 {
fn setRange(self: *Selection, new_range: ?*Range, page: *Page) void { fn setRange(self: *Selection, new_range: ?*Range, page: *Page) void {
if (self._range) |existing| { if (self._range) |existing| {
existing.deinit(false, page._session); _ = existing.asAbstractRange().releaseRef(page._session);
} }
if (new_range) |nr| { if (new_range) |nr| {
nr.asAbstractRange().acquireRef(); nr.asAbstractRange().acquireRef();
@@ -708,7 +718,6 @@ pub const JsApi = struct {
pub const name = "Selection"; pub const name = "Selection";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const finalizer = bridge.finalizer(Selection.deinit);
}; };
pub const anchorNode = bridge.accessor(Selection.getAnchorNode, null, .{}); pub const anchorNode = bridge.accessor(Selection.getAnchorNode, null, .{});

View File

@@ -256,8 +256,7 @@ pub fn createObjectURL(blob: *Blob, page: *Page) ![]const u8 {
.{ page.origin orelse "null", uuid_buf }, .{ page.origin orelse "null", uuid_buf },
); );
try page._blob_urls.put(page.arena, blob_url, blob); try page._blob_urls.put(page.arena, blob_url, blob);
// prevent GC from cleaning up the blob while it's in the registry blob.acquireRef();
page.js.strongRef(blob);
return blob_url; return blob_url;
} }
@@ -267,9 +266,8 @@ pub fn revokeObjectURL(url: []const u8, page: *Page) void {
return; return;
} }
// Remove from registry and release strong ref (no-op if not found)
if (page._blob_urls.fetchRemove(url)) |entry| { if (page._blob_urls.fetchRemove(url)) |entry| {
page.js.weakRef(entry.value); entry.value.releaseRef(page._session);
} }
} }

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const log = @import("../../../log.zig"); const log = @import("../../../log.zig");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
@@ -33,6 +34,7 @@ const PlayState = enum {
finished, finished,
}; };
_rc: lp.RC(u32) = .{},
_page: *Page, _page: *Page,
_arena: Allocator, _arena: Allocator,
@@ -62,10 +64,18 @@ pub fn init(page: *Page) !*Animation {
return self; return self;
} }
pub fn deinit(self: *Animation, _: bool, session: *Session) void { pub fn deinit(self: *Animation, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *Animation, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *Animation) void {
self._rc.acquire();
}
pub fn play(self: *Animation, page: *Page) !void { pub fn play(self: *Animation, page: *Page) !void {
if (self._playState == .running) { if (self._playState == .running) {
return; return;
@@ -75,7 +85,7 @@ pub fn play(self: *Animation, page: *Page) !void {
self._playState = .running; self._playState = .running;
// Schedule the transition from .running => .finished in 10ms. // Schedule the transition from .running => .finished in 10ms.
page.js.strongRef(self); self.acquireRef();
try page.js.scheduler.add( try page.js.scheduler.add(
self, self,
Animation.update, Animation.update,
@@ -201,7 +211,7 @@ fn update(ctx: *anyopaque) !?u32 {
} }
// No future change scheduled, set the object weak for garbage collection. // No future change scheduled, set the object weak for garbage collection.
self._page.js.weakRef(self); self.releaseRef(self._page._session);
return null; return null;
} }
@@ -220,8 +230,6 @@ pub const JsApi = struct {
pub const name = "Animation"; pub const name = "Animation";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(Animation.deinit);
}; };
pub const play = bridge.function(Animation.play, .{}); pub const play = bridge.function(Animation.play, .{});

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const log = @import("../../../log.zig"); const log = @import("../../../log.zig");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
@@ -37,15 +38,9 @@ _data: union(enum) {
radio_node_list: *RadioNodeList, radio_node_list: *RadioNodeList,
name: NodeLive(.name), name: NodeLive(.name),
}, },
_rc: usize = 0, _rc: lp.RC(u32) = .{},
pub fn deinit(self: *NodeList, _: bool, session: *Session) void {
const rc = self._rc;
if (rc > 1) {
self._rc = rc - 1;
return;
}
pub fn deinit(self: *NodeList, session: *Session) void {
switch (self._data) { switch (self._data) {
.selector_list => |list| list.deinit(session), .selector_list => |list| list.deinit(session),
.child_nodes => |cn| cn.deinit(session), .child_nodes => |cn| cn.deinit(session),
@@ -53,8 +48,12 @@ pub fn deinit(self: *NodeList, _: bool, session: *Session) void {
} }
} }
pub fn releaseRef(self: *NodeList, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *NodeList) void { pub fn acquireRef(self: *NodeList) void {
self._rc += 1; self._rc.acquire();
} }
pub fn length(self: *NodeList, page: *Page) !u32 { pub fn length(self: *NodeList, page: *Page) !u32 {
@@ -119,8 +118,12 @@ const Iterator = struct {
const Entry = struct { u32, *Node }; const Entry = struct { u32, *Node };
pub fn deinit(self: *Iterator, shutdown: bool, session: *Session) void { pub fn deinit(self: *Iterator, session: *Session) void {
self.list.deinit(shutdown, session); self.list.deinit(session);
}
pub fn releaseRef(self: *Iterator, session: *Session) void {
self.list.releaseRef(session);
} }
pub fn acquireRef(self: *Iterator) void { pub fn acquireRef(self: *Iterator) void {
@@ -143,8 +146,6 @@ pub const JsApi = struct {
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const enumerable = false; pub const enumerable = false;
pub const weak = true;
pub const finalizer = bridge.finalizer(NodeList.deinit);
}; };
pub const length = bridge.accessor(NodeList.length, null, .{}); pub const length = bridge.accessor(NodeList.length, null, .{});

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig"); const Session = @import("../../Session.zig");
@@ -40,9 +41,15 @@ pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type {
return page._factory.create(Self{ .inner = inner }); return page._factory.create(Self{ .inner = inner });
} }
pub fn deinit(self: *Self, shutdown: bool, session: *Session) void { pub fn deinit(self: *Self, session: *Session) void {
if (@hasDecl(Inner, "deinit")) { _ = self;
self.inner.deinit(shutdown, session); _ = session;
}
pub fn releaseRef(self: *Self, session: *Session) void {
// Release the reference to the inner type that we acquired
if (@hasDecl(Inner, "releaseRef")) {
self.inner.releaseRef(session);
} }
} }
@@ -73,8 +80,6 @@ pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type {
pub const Meta = struct { pub const Meta = struct {
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(Self.deinit);
}; };
pub const next = bridge.function(Self.next, .{ .null_as_undefined = true }); pub const next = bridge.function(Self.next, .{ .null_as_undefined = true });

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig"); const Session = @import("../../Session.zig");
@@ -25,6 +26,7 @@ const Allocator = std.mem.Allocator;
const FontFace = @This(); const FontFace = @This();
_rc: lp.RC(u8) = .{},
_arena: Allocator, _arena: Allocator,
_family: []const u8, _family: []const u8,
@@ -42,10 +44,18 @@ pub fn init(family: []const u8, source: []const u8, page: *Page) !*FontFace {
return self; return self;
} }
pub fn deinit(self: *FontFace, _: bool, session: *Session) void { pub fn deinit(self: *FontFace, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *FontFace, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *FontFace) void {
self._rc.acquire();
}
pub fn getFamily(self: *const FontFace) []const u8 { pub fn getFamily(self: *const FontFace) []const u8 {
return self._family; return self._family;
} }
@@ -67,8 +77,6 @@ pub const JsApi = struct {
pub const name = "FontFace"; pub const name = "FontFace";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(FontFace.deinit);
}; };
pub const constructor = bridge.constructor(FontFace.init, .{}); pub const constructor = bridge.constructor(FontFace.init, .{});

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig"); const Session = @import("../../Session.zig");
@@ -28,6 +29,7 @@ const Allocator = std.mem.Allocator;
const FontFaceSet = @This(); const FontFaceSet = @This();
_rc: lp.RC(u8) = .{},
_proto: *EventTarget, _proto: *EventTarget,
_arena: Allocator, _arena: Allocator,
@@ -41,10 +43,18 @@ pub fn init(page: *Page) !*FontFaceSet {
}); });
} }
pub fn deinit(self: *FontFaceSet, _: bool, session: *Session) void { pub fn deinit(self: *FontFaceSet, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *FontFaceSet, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *FontFaceSet) void {
self._rc.acquire();
}
pub fn asEventTarget(self: *FontFaceSet) *EventTarget { pub fn asEventTarget(self: *FontFaceSet) *EventTarget {
return self._proto; return self._proto;
} }
@@ -95,8 +105,6 @@ pub const JsApi = struct {
pub const name = "FontFaceSet"; pub const name = "FontFaceSet";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(FontFaceSet.deinit);
}; };
pub const size = bridge.property(0, .{ .template = false, .readonly = true }); pub const size = bridge.property(0, .{ .template = false, .readonly = true });

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
@@ -25,6 +26,7 @@ const Allocator = std.mem.Allocator;
const TextDecoder = @This(); const TextDecoder = @This();
_rc: lp.RC(u8) = .{},
_fatal: bool, _fatal: bool,
_arena: Allocator, _arena: Allocator,
_ignore_bom: bool, _ignore_bom: bool,
@@ -60,10 +62,18 @@ pub fn init(label_: ?[]const u8, opts_: ?InitOpts, page: *Page) !*TextDecoder {
return self; return self;
} }
pub fn deinit(self: *TextDecoder, _: bool, session: *Session) void { pub fn deinit(self: *TextDecoder, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *TextDecoder, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *TextDecoder) void {
self._rc.acquire();
}
pub fn getIgnoreBOM(self: *const TextDecoder) bool { pub fn getIgnoreBOM(self: *const TextDecoder) bool {
return self._ignore_bom; return self._ignore_bom;
} }
@@ -109,8 +119,6 @@ pub const JsApi = struct {
pub const name = "TextDecoder"; pub const name = "TextDecoder";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(TextDecoder.deinit);
}; };
pub const constructor = bridge.constructor(TextDecoder.init, .{}); pub const constructor = bridge.constructor(TextDecoder.init, .{});

View File

@@ -53,10 +53,6 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*CompositionEvent {
return event; return event;
} }
pub fn deinit(self: *CompositionEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *CompositionEvent) *Event { pub fn asEvent(self: *CompositionEvent) *Event {
return self._proto; return self._proto;
} }
@@ -72,8 +68,6 @@ pub const JsApi = struct {
pub const name = "CompositionEvent"; pub const name = "CompositionEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; 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, .{}); pub const constructor = bridge.constructor(CompositionEvent.init, .{});

View File

@@ -73,11 +73,19 @@ pub fn initCustomEvent(
self._detail = detail_; self._detail = detail_;
} }
pub fn deinit(self: *CustomEvent, shutdown: bool, session: *Session) void { pub fn deinit(self: *CustomEvent, session: *Session) void {
if (self._detail) |d| { if (self._detail) |d| {
d.release(); d.release();
} }
self._proto.deinit(shutdown, session); self._proto.deinit(session);
}
pub fn acquireRef(self: *CustomEvent) void {
self._proto.acquireRef();
}
pub fn releaseRef(self: *CustomEvent, session: *Session) void {
self._proto._rc.release(self, session);
} }
pub fn asEvent(self: *CustomEvent) *Event { pub fn asEvent(self: *CustomEvent) *Event {
@@ -95,8 +103,6 @@ pub const JsApi = struct {
pub const name = "CustomEvent"; pub const name = "CustomEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(CustomEvent.deinit);
pub const enumerable = false; pub const enumerable = false;
}; };

View File

@@ -80,11 +80,19 @@ fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *ErrorEvent, shutdown: bool, session: *Session) void { pub fn deinit(self: *ErrorEvent, session: *Session) void {
if (self._error) |e| { if (self._error) |e| {
e.release(); e.release();
} }
self._proto.deinit(shutdown, session); self._proto.deinit(session);
}
pub fn acquireRef(self: *ErrorEvent) void {
self._proto.acquireRef();
}
pub fn releaseRef(self: *ErrorEvent, session: *Session) void {
self._proto._rc.release(self, session);
} }
pub fn asEvent(self: *ErrorEvent) *Event { pub fn asEvent(self: *ErrorEvent) *Event {

View File

@@ -70,10 +70,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *FocusEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *FocusEvent) *Event { pub fn asEvent(self: *FocusEvent) *Event {
return self._proto.asEvent(); return self._proto.asEvent();
} }
@@ -89,8 +85,6 @@ pub const JsApi = struct {
pub const name = "FocusEvent"; pub const name = "FocusEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(FocusEvent.deinit);
}; };
pub const constructor = bridge.constructor(FocusEvent.init, .{}); pub const constructor = bridge.constructor(FocusEvent.init, .{});

View File

@@ -66,10 +66,6 @@ fn initWithTrusted(arena: Allocator, typ: String, maybe_options: ?Options, trust
return event; return event;
} }
pub fn deinit(self: *FormDataEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *FormDataEvent) *Event { pub fn asEvent(self: *FormDataEvent) *Event {
return self._proto; return self._proto;
} }
@@ -85,8 +81,6 @@ pub const JsApi = struct {
pub const name = "FormDataEvent"; pub const name = "FormDataEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(FormDataEvent.deinit);
}; };
pub const constructor = bridge.constructor(FormDataEvent.init, .{}); pub const constructor = bridge.constructor(FormDataEvent.init, .{});

View File

@@ -83,10 +83,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *InputEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *InputEvent) *Event { pub fn asEvent(self: *InputEvent) *Event {
return self._proto.asEvent(); return self._proto.asEvent();
} }
@@ -110,8 +106,6 @@ pub const JsApi = struct {
pub const name = "InputEvent"; pub const name = "InputEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(InputEvent.deinit);
}; };
pub const constructor = bridge.constructor(InputEvent.init, .{}); pub const constructor = bridge.constructor(InputEvent.init, .{});

View File

@@ -229,10 +229,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *KeyboardEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *KeyboardEvent) *Event { pub fn asEvent(self: *KeyboardEvent) *Event {
return self._proto.asEvent(); return self._proto.asEvent();
} }
@@ -296,8 +292,6 @@ pub const JsApi = struct {
pub const name = "KeyboardEvent"; pub const name = "KeyboardEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; 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, .{}); pub const constructor = bridge.constructor(KeyboardEvent.init, .{});

View File

@@ -73,11 +73,19 @@ fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *MessageEvent, shutdown: bool, session: *Session) void { pub fn deinit(self: *MessageEvent, session: *Session) void {
if (self._data) |d| { if (self._data) |d| {
d.release(); d.release();
} }
self._proto.deinit(shutdown, session); self._proto.deinit(session);
}
pub fn acquireRef(self: *MessageEvent) void {
self._proto.acquireRef();
}
pub fn releaseRef(self: *MessageEvent, session: *Session) void {
self._proto._rc.release(self, session);
} }
pub fn asEvent(self: *MessageEvent) *Event { pub fn asEvent(self: *MessageEvent) *Event {
@@ -103,8 +111,6 @@ pub const JsApi = struct {
pub const name = "MessageEvent"; pub const name = "MessageEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; 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, .{}); pub const constructor = bridge.constructor(MessageEvent.init, .{});

View File

@@ -121,10 +121,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *MouseEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *MouseEvent) *Event { pub fn asEvent(self: *MouseEvent) *Event {
return self._proto.asEvent(); return self._proto.asEvent();
} }
@@ -203,8 +199,6 @@ pub const JsApi = struct {
pub const name = "MouseEvent"; pub const name = "MouseEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; 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, .{}); pub const constructor = bridge.constructor(MouseEvent.init, .{});

View File

@@ -83,10 +83,6 @@ fn initWithTrusted(
return event; return event;
} }
pub fn deinit(self: *NavigationCurrentEntryChangeEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *NavigationCurrentEntryChangeEvent) *Event { pub fn asEvent(self: *NavigationCurrentEntryChangeEvent) *Event {
return self._proto; return self._proto;
} }
@@ -106,8 +102,6 @@ pub const JsApi = struct {
pub const name = "NavigationCurrentEntryChangeEvent"; pub const name = "NavigationCurrentEntryChangeEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; 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, .{}); pub const constructor = bridge.constructor(NavigationCurrentEntryChangeEvent.init, .{});

View File

@@ -66,10 +66,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *PageTransitionEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *PageTransitionEvent) *Event { pub fn asEvent(self: *PageTransitionEvent) *Event {
return self._proto; return self._proto;
} }
@@ -85,8 +81,6 @@ pub const JsApi = struct {
pub const name = "PageTransitionEvent"; pub const name = "PageTransitionEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; 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, .{}); pub const constructor = bridge.constructor(PageTransitionEvent.init, .{});

View File

@@ -128,10 +128,6 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*PointerEvent {
return event; return event;
} }
pub fn deinit(self: *PointerEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *PointerEvent) *Event { pub fn asEvent(self: *PointerEvent) *Event {
return self._proto.asEvent(); return self._proto.asEvent();
} }
@@ -191,8 +187,6 @@ pub const JsApi = struct {
pub const name = "PointerEvent"; pub const name = "PointerEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; 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, .{}); pub const constructor = bridge.constructor(PointerEvent.init, .{});

View File

@@ -67,10 +67,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *PopStateEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *PopStateEvent) *Event { pub fn asEvent(self: *PopStateEvent) *Event {
return self._proto; return self._proto;
} }
@@ -92,8 +88,6 @@ pub const JsApi = struct {
pub const name = "PopStateEvent"; pub const name = "PopStateEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; 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, .{}); pub const constructor = bridge.constructor(PopStateEvent.init, .{});

View File

@@ -68,10 +68,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *ProgressEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *ProgressEvent) *Event { pub fn asEvent(self: *ProgressEvent) *Event {
return self._proto; return self._proto;
} }
@@ -96,8 +92,6 @@ pub const JsApi = struct {
pub const name = "ProgressEvent"; pub const name = "ProgressEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; 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, .{}); pub const constructor = bridge.constructor(ProgressEvent.init, .{});

View File

@@ -56,14 +56,22 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*PromiseRejectionEve
return event; return event;
} }
pub fn deinit(self: *PromiseRejectionEvent, shutdown: bool, session: *Session) void { pub fn deinit(self: *PromiseRejectionEvent, session: *Session) void {
if (self._reason) |r| { if (self._reason) |r| {
r.release(); r.release();
} }
if (self._promise) |p| { if (self._promise) |p| {
p.release(); p.release();
} }
self._proto.deinit(shutdown, session); self._proto.deinit(session);
}
pub fn acquireRef(self: *PromiseRejectionEvent) void {
self._proto.acquireRef();
}
pub fn releaseRef(self: *PromiseRejectionEvent, session: *Session) void {
self._proto._rc.release(self, session);
} }
pub fn asEvent(self: *PromiseRejectionEvent) *Event { pub fn asEvent(self: *PromiseRejectionEvent) *Event {
@@ -85,8 +93,6 @@ pub const JsApi = struct {
pub const name = "PromiseRejectionEvent"; pub const name = "PromiseRejectionEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(PromiseRejectionEvent.deinit);
}; };
pub const constructor = bridge.constructor(PromiseRejectionEvent.init, .{}); pub const constructor = bridge.constructor(PromiseRejectionEvent.init, .{});

View File

@@ -67,10 +67,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *SubmitEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *SubmitEvent) *Event { pub fn asEvent(self: *SubmitEvent) *Event {
return self._proto; return self._proto;
} }
@@ -86,8 +82,6 @@ pub const JsApi = struct {
pub const name = "SubmitEvent"; pub const name = "SubmitEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(SubmitEvent.deinit);
}; };
pub const constructor = bridge.constructor(SubmitEvent.init, .{}); pub const constructor = bridge.constructor(SubmitEvent.init, .{});

View File

@@ -59,10 +59,6 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*TextEvent {
return event; return event;
} }
pub fn deinit(self: *TextEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *TextEvent) *Event { pub fn asEvent(self: *TextEvent) *Event {
return self._proto.asEvent(); return self._proto.asEvent();
} }
@@ -101,8 +97,6 @@ pub const JsApi = struct {
pub const name = "TextEvent"; pub const name = "TextEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(TextEvent.deinit);
}; };
// No constructor - TextEvent is created via document.createEvent('TextEvent') // No constructor - TextEvent is created via document.createEvent('TextEvent')

View File

@@ -71,10 +71,6 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*UIEvent {
return event; return event;
} }
pub fn deinit(self: *UIEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn as(self: *UIEvent, comptime T: type) *T { pub fn as(self: *UIEvent, comptime T: type) *T {
return self.is(T).?; return self.is(T).?;
} }
@@ -122,8 +118,6 @@ pub const JsApi = struct {
pub const name = "UIEvent"; pub const name = "UIEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; 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, .{}); pub const constructor = bridge.constructor(UIEvent.init, .{});

View File

@@ -87,10 +87,6 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*WheelEvent {
return event; return event;
} }
pub fn deinit(self: *WheelEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *WheelEvent) *Event { pub fn asEvent(self: *WheelEvent) *Event {
return self._proto.asEvent(); return self._proto.asEvent();
} }
@@ -118,8 +114,6 @@ pub const JsApi = struct {
pub const name = "WheelEvent"; pub const name = "WheelEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(WheelEvent.deinit);
}; };
pub const constructor = bridge.constructor(WheelEvent.init, .{}); pub const constructor = bridge.constructor(WheelEvent.init, .{});

View File

@@ -62,7 +62,7 @@ pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise {
} }
const response = try Response.init(null, .{ .status = 0 }, page); const response = try Response.init(null, .{ .status = 0 }, page);
errdefer response.deinit(true, page._session); errdefer response.deinit(page._session);
const fetch = try response._arena.create(Fetch); const fetch = try response._arena.create(Fetch);
fetch.* = .{ fetch.* = .{
@@ -226,7 +226,7 @@ fn httpDoneCallback(ctx: *anyopaque) !void {
return ls.toLocal(self._resolver).resolve("fetch done", js_val); return ls.toLocal(self._resolver).resolve("fetch done", js_val);
} }
fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void { fn httpErrorCallback(ctx: *anyopaque, _: anyerror) void {
const self: *Fetch = @ptrCast(@alignCast(ctx)); const self: *Fetch = @ptrCast(@alignCast(ctx));
var response = self._response; var response = self._response;
@@ -235,7 +235,7 @@ fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
// clear this. (defer since `self is in the response's arena). // clear this. (defer since `self is in the response's arena).
defer if (self._owns_response) { defer if (self._owns_response) {
response.deinit(err == error.Abort, self._page._session); response.deinit(self._page._session);
self._owns_response = false; self._owns_response = false;
}; };
@@ -257,7 +257,7 @@ fn httpShutdownCallback(ctx: *anyopaque) void {
if (self._owns_response) { if (self._owns_response) {
var response = self._response; var response = self._response;
response._transfer = null; response._transfer = null;
response.deinit(true, self._page._session); response.deinit(self._page._session);
// Do not access `self` after this point: the Fetch struct was // Do not access `self` after this point: the Fetch struct was
// allocated from response._arena which has been released. // allocated from response._arena which has been released.
} }

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const HttpClient = @import("../../HttpClient.zig"); const HttpClient = @import("../../HttpClient.zig");
@@ -38,6 +39,7 @@ pub const Type = enum {
opaqueredirect, opaqueredirect,
}; };
_rc: lp.RC(u8) = .{},
_status: u16, _status: u16,
_arena: Allocator, _arena: Allocator,
_headers: *Headers, _headers: *Headers,
@@ -78,18 +80,22 @@ pub fn init(body_: ?[]const u8, opts_: ?InitOpts, page: *Page) !*Response {
return self; return self;
} }
pub fn deinit(self: *Response, shutdown: bool, session: *Session) void { pub fn deinit(self: *Response, session: *Session) void {
if (self._transfer) |transfer| { if (self._transfer) |transfer| {
if (shutdown) {
transfer.terminate();
} else {
transfer.abort(error.Abort); transfer.abort(error.Abort);
}
self._transfer = null; self._transfer = null;
} }
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *Response, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *Response) void {
self._rc.acquire();
}
pub fn getStatus(self: *const Response) u16 { pub fn getStatus(self: *const Response) u16 {
return self._status; return self._status;
} }
@@ -197,8 +203,6 @@ pub const JsApi = struct {
pub const name = "Response"; pub const name = "Response";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(Response.deinit);
}; };
pub const constructor = bridge.constructor(Response.init, .{}); pub const constructor = bridge.constructor(Response.init, .{});

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const log = @import("../../../log.zig"); const log = @import("../../../log.zig");
@@ -38,6 +39,7 @@ const Allocator = std.mem.Allocator;
const IS_DEBUG = @import("builtin").mode == .Debug; const IS_DEBUG = @import("builtin").mode == .Debug;
const XMLHttpRequest = @This(); const XMLHttpRequest = @This();
_rc: lp.RC(u8) = .{},
_page: *Page, _page: *Page,
_proto: *XMLHttpRequestEventTarget, _proto: *XMLHttpRequestEventTarget,
_arena: Allocator, _arena: Allocator,
@@ -87,21 +89,18 @@ const ResponseType = enum {
pub fn init(page: *Page) !*XMLHttpRequest { pub fn init(page: *Page) !*XMLHttpRequest {
const arena = try page.getArena(.{ .debug = "XMLHttpRequest" }); const arena = try page.getArena(.{ .debug = "XMLHttpRequest" });
errdefer page.releaseArena(arena); errdefer page.releaseArena(arena);
return page._factory.xhrEventTarget(arena, XMLHttpRequest{ const xhr = try page._factory.xhrEventTarget(arena, XMLHttpRequest{
._page = page, ._page = page,
._arena = arena, ._arena = arena,
._proto = undefined, ._proto = undefined,
._request_headers = try Headers.init(null, page), ._request_headers = try Headers.init(null, page),
}); });
return xhr;
} }
pub fn deinit(self: *XMLHttpRequest, shutdown: bool, session: *Session) void { pub fn deinit(self: *XMLHttpRequest, session: *Session) void {
if (self._transfer) |transfer| { if (self._transfer) |transfer| {
if (shutdown) {
transfer.terminate();
} else {
transfer.abort(error.Abort); transfer.abort(error.Abort);
}
self._transfer = null; self._transfer = null;
} }
@@ -137,6 +136,14 @@ pub fn deinit(self: *XMLHttpRequest, shutdown: bool, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *XMLHttpRequest, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *XMLHttpRequest) void {
self._rc.acquire();
}
fn asEventTarget(self: *XMLHttpRequest) *EventTarget { fn asEventTarget(self: *XMLHttpRequest) *EventTarget {
return self._proto._proto; return self._proto._proto;
} }
@@ -245,8 +252,6 @@ pub fn send(self: *XMLHttpRequest, body_: ?[]const u8) !void {
.error_callback = httpErrorCallback, .error_callback = httpErrorCallback,
.shutdown_callback = httpShutdownCallback, .shutdown_callback = httpShutdownCallback,
}); });
page.js.strongRef(self);
} }
fn handleBlobUrl(self: *XMLHttpRequest, page: *Page) !void { fn handleBlobUrl(self: *XMLHttpRequest, page: *Page) !void {
@@ -388,6 +393,7 @@ fn httpStartCallback(transfer: *HttpClient.Transfer) !void {
log.debug(.http, "request start", .{ .method = self._method, .url = self._url, .source = "xhr" }); log.debug(.http, "request start", .{ .method = self._method, .url = self._url, .source = "xhr" });
} }
self._transfer = transfer; self._transfer = transfer;
self.acquireRef();
} }
fn httpHeaderCallback(transfer: *HttpClient.Transfer, header: net_http.Header) !void { fn httpHeaderCallback(transfer: *HttpClient.Transfer, header: net_http.Header) !void {
@@ -486,15 +492,17 @@ fn httpDoneCallback(ctx: *anyopaque) !void {
.loaded = loaded, .loaded = loaded,
}, page); }, page);
page.js.weakRef(self); self.releaseRef(page._session);
} }
fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void { fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
const self: *XMLHttpRequest = @ptrCast(@alignCast(ctx)); const self: *XMLHttpRequest = @ptrCast(@alignCast(ctx));
// http client will close it after an error, it isn't safe to keep around // http client will close it after an error, it isn't safe to keep around
self._transfer = null;
self.handleError(err); self.handleError(err);
self._page.js.weakRef(self); if (self._transfer != null) {
self._transfer = null;
self.releaseRef(self._page._session);
}
} }
fn httpShutdownCallback(ctx: *anyopaque) void { fn httpShutdownCallback(ctx: *anyopaque) void {
@@ -505,10 +513,10 @@ fn httpShutdownCallback(ctx: *anyopaque) void {
pub fn abort(self: *XMLHttpRequest) void { pub fn abort(self: *XMLHttpRequest) void {
self.handleError(error.Abort); self.handleError(error.Abort);
if (self._transfer) |transfer| { if (self._transfer) |transfer| {
transfer.abort(error.Abort);
self._transfer = null; self._transfer = null;
transfer.abort(error.Abort);
self.releaseRef(self._page._session);
} }
self._page.js.weakRef(self);
} }
fn handleError(self: *XMLHttpRequest, err: anyerror) void { fn handleError(self: *XMLHttpRequest, err: anyerror) void {
@@ -582,8 +590,6 @@ pub const JsApi = struct {
pub const name = "XMLHttpRequest"; pub const name = "XMLHttpRequest";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(XMLHttpRequest.deinit);
}; };
pub const constructor = bridge.constructor(XMLHttpRequest.init, .{}); pub const constructor = bridge.constructor(XMLHttpRequest.init, .{});

View File

@@ -209,6 +209,37 @@ noinline fn assertionFailure(comptime ctx: []const u8, args: anytype) noreturn {
@import("crash_handler.zig").crash(ctx, args, @returnAddress()); @import("crash_handler.zig").crash(ctx, args, @returnAddress());
} }
// Reference counting helper
pub fn RC(comptime T: type) type {
return struct {
_refs: T = 0,
pub fn init(refs: T) @This() {
return .{ ._refs = refs };
}
pub fn acquire(self: *@This()) void {
self._refs += 1;
}
pub fn release(self: *@This(), value: anytype, session: *Session) void {
if (comptime IS_DEBUG) {
std.debug.assert(self._refs > 0);
}
const refs = self._refs - 1;
self._refs = refs;
if (refs > 0) {
return;
}
value.deinit(session);
if (session.finalizer_callbacks.fetchRemove(@intFromPtr(value))) |kv| {
session.releaseArena(kv.value.arena);
}
}
};
}
test { test {
std.testing.refAllDecls(@This()); std.testing.refAllDecls(@This());
} }