diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig
index a5f8c475..49169f1d 100644
--- a/src/browser/EventManager.zig
+++ b/src/browser/EventManager.zig
@@ -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 {
event.acquireRef();
- defer event.deinit(false, self.page._session);
+ defer _ = event.releaseRef(self.page._session);
if (comptime IS_DEBUG) {
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;
event.acquireRef();
- defer event.deinit(false, page._session);
+ defer _ = event.releaseRef(page._session);
if (comptime IS_DEBUG) {
log.debug(.event, "dispatchDirect", .{ .type = event._type_string, .context = opts.context });
diff --git a/src/browser/Factory.zig b/src/browser/Factory.zig
index 0d8b87cd..14770cea 100644
--- a/src/browser/Factory.zig
+++ b/src/browser/Factory.zig
@@ -239,7 +239,7 @@ fn eventInit(arena: Allocator, typ: String, value: anytype) !Event {
const time_stamp = (raw_timestamp / 2) * 2;
return .{
- ._rc = 0,
+ ._rc = .{},
._arena = arena,
._type = unionInit(Event.Type, value),
._type_string = typ,
@@ -255,6 +255,7 @@ pub fn blob(_: *const Factory, arena: Allocator, child: anytype) !*@TypeOf(child
const blob_ptr = chain.get(0);
blob_ptr.* = .{
+ ._rc = .{},
._arena = arena,
._type = unionInit(Blob.Type, chain.get(1)),
._slice = "",
@@ -271,7 +272,7 @@ pub fn abstractRange(_: *const Factory, arena: Allocator, child: anytype, page:
const doc = page.document.asNode();
const abstract_range = chain.get(0);
abstract_range.* = AbstractRange{
- ._rc = 0,
+ ._rc = .{},
._arena = arena,
._page_id = page.id,
._type = unionInit(AbstractRange.Type, chain.get(1)),
diff --git a/src/browser/Page.zig b/src/browser/Page.zig
index 36be0708..f32218cd 100644
--- a/src/browser/Page.zig
+++ b/src/browser/Page.zig
@@ -3395,7 +3395,7 @@ pub fn handleClick(self: *Page, target: *Node) !void {
pub fn triggerKeyboard(self: *Page, keyboard_event: *KeyboardEvent) !void {
const event = keyboard_event.asEvent();
const element = self.window._document._active_element orelse {
- keyboard_event.deinit(false, self._session);
+ _ = event.releaseRef(self._session);
return;
};
@@ -3491,7 +3491,7 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form
// so submit_event is still valid when we check _prevent_default
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);
// If the submit event was prevented, don't submit the form
diff --git a/src/browser/Session.zig b/src/browser/Session.zig
index 5801038b..c4e6d1ca 100644
--- a/src/browser/Session.zig
+++ b/src/browser/Session.zig
@@ -71,6 +71,18 @@ origins: std.StringHashMapUnmanaged(*js.Origin) = .empty,
// ensuring object identity works across same-origin frames.
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.
// These live for the duration of the page tree (root + frames).
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.
/// Called when root page is removed.
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 = .{};
@@ -457,35 +493,25 @@ pub fn nextPageId(self: *Session) u32 {
return id;
}
-// A type that has a finalizer can have its finalizer called one of two ways.
-// The first is from V8 via the WeakCallback we give to weakRef. But that isn't
-// guaranteed to fire, so we track this in finalizer_callbacks and call them on
-// page reset.
+// Every finalizable instance of Zig gets 1 FinalizerCallback registered in the
+// session. This is to ensure that, if v8 doesn't finalize the value, we can
+// release on page reset.
pub const FinalizerCallback = struct {
arena: Allocator,
session: *Session,
- ptr: *anyopaque,
- global: v8.Global,
- identity: *js.Identity,
- zig_finalizer: *const fn (ptr: *anyopaque, session: *Session) void,
+ resolved_ptr_id: usize,
+ finalizer_ptr_id: usize,
+ _deinit: *const fn (ptr_id: usize, session: *Session) void,
- 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);
+ // 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,
+ fc: *Session.FinalizerCallback,
+ };
+ fn deinit(self: *FinalizerCallback, session: *Session) void {
+ self._deinit(self.finalizer_ptr_id, session);
session.releaseArena(self.arena);
}
};
diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig
index 4b4fcead..beec0625 100644
--- a/src/browser/js/Context.zig
+++ b/src/browser/js/Context.zig
@@ -21,6 +21,7 @@ const lp = @import("lightpanda");
const log = @import("../../log.zig");
const js = @import("js.zig");
+const bridge = @import("bridge.zig");
const Env = @import("Env.zig");
const Origin = @import("Origin.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 {
- 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 {
- return self.identity.temps.put(self.identity_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);
+ return self.session.temps.put(self.session.page_arena, global.data_ptr, global);
}
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.
pub fn localScope(self: *Context, ls: *js.Local.Scope) void {
const isolate = self.isolate;
diff --git a/src/browser/js/Function.zig b/src/browser/js/Function.zig
index 14cc8d14..16664cbe 100644
--- a/src/browser/js/Function.zig
+++ b/src/browser/js/Function.zig
@@ -213,7 +213,7 @@ fn _persist(self: *const Function, comptime is_global: bool) !(if (is_global) Gl
return .{ .handle = global, .temps = {} };
}
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 {
diff --git a/src/browser/js/Identity.zig b/src/browser/js/Identity.zig
index 2e2b35f5..e101003e 100644
--- a/src/browser/js/Identity.zig
+++ b/src/browser/js/Identity.zig
@@ -38,38 +38,9 @@ const Identity = @This();
// Maps Zig instance pointers to their v8::Global(Object) wrappers.
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 {
- {
- var it = self.finalizer_callbacks.valueIterator();
- while (it.next()) |finalizer| {
- finalizer.*.deinit();
- }
- }
-
- {
- var it = self.identity_map.valueIterator();
- while (it.next()) |global| {
- v8.v8__Global__Reset(global);
- }
- }
-
- for (self.globals.items) |*global| {
+ var it = self.identity_map.valueIterator();
+ while (it.next()) |global| {
v8.v8__Global__Reset(global);
}
-
- {
- var it = self.temps.valueIterator();
- while (it.next()) |global| {
- v8.v8__Global__Reset(global);
- }
- }
}
diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig
index 6dda5f38..9543d078 100644
--- a/src/browser/js/Local.zig
+++ b/src/browser/js/Local.zig
@@ -17,10 +17,11 @@
// along with this program. If not, see .
const std = @import("std");
-const Session = @import("../Session.zig");
const log = @import("../../log.zig");
const string = @import("../../string.zig");
+const Session = @import("../Session.zig");
+
const js = @import("js.zig");
const bridge = @import("bridge.zig");
const Caller = @import("Caller.zig");
@@ -213,7 +214,8 @@ pub fn mapZigInstanceToJs(self: *const Local, js_obj_handle: ?*const v8.Object,
.pointer => |ptr| {
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) {
// we've seen this instance before, return the same object
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
// context.global_objects, we want to track it in context.identity_map.
v8.v8__Global__New(isolate.handle, js_obj.handle, gop.value_ptr);
- if (@hasDecl(JsApi.Meta, "finalizer")) {
- // It would be great if resolved knew the resolved type, but I
- // can't figure out how to make that work, since it depends on
- // the [runtime] `value`.
- // We need the resolved finalizer, which we have in resolved.
- //
- // The above if statement would be more clear as:
- // if (resolved.finalizer_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);
- }
+ if (resolved.finalizer) |finalizer| {
+ const finalizer_ptr_id = finalizer.ptr_id;
+ finalizer.acquireRef(finalizer_ptr_id);
- conditionallyReference(value);
- if (@hasDecl(JsApi.Meta, "weak")) {
- if (comptime IS_DEBUG) {
- std.debug.assert(JsApi.Meta.weak == true);
- }
- v8.v8__Global__SetWeakFinalizer(gop.value_ptr, fc, resolved.finalizer_from_v8, v8.kParameter);
+ const session = ctx.session;
+ const finalizer_gop = try session.finalizer_callbacks.getOrPut(session.page_arena, finalizer_ptr_id);
+ if (finalizer_gop.found_existing == false) {
+ // This is the first context (and very likely only one) to
+ // see this Zig instance. We need to create the FinalizerCallback
+ // 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;
},
@@ -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
// get the most specific class_id possible.
const Resolved = struct {
- weak: bool,
ptr: *anyopaque,
class_id: u16,
prototype_chain: []const @import("TaggedOpaque.zig").PrototypeChainEntry,
- finalizer_from_v8: ?*const fn (handle: ?*const v8.WeakCallbackInfo) callconv(.c) void = null,
- finalizer_from_zig: ?*const fn (ptr: *anyopaque, session: *Session) void = null,
+ finalizer: ?Finalizer,
+
+ 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 {
const T = bridge.Struct(@TypeOf(value));
@@ -1153,27 +1158,85 @@ pub fn resolveValue(value: anytype) Resolved {
unreachable;
}
-fn resolveT(comptime T: type, value: *anyopaque) Resolved {
+fn resolveT(comptime T: type, value: *T) Resolved {
const Meta = T.JsApi.Meta;
return .{
.ptr = value,
.class_id = Meta.class_id,
.prototype_chain = &Meta.prototype_chain,
- .weak = if (@hasDecl(Meta, "weak")) Meta.weak else false,
- .finalizer_from_v8 = if (@hasDecl(Meta, "finalizer")) Meta.finalizer.from_v8 else null,
- .finalizer_from_zig = if (@hasDecl(Meta, "finalizer")) Meta.finalizer.from_zig else null,
+ .finalizer = blk: {
+ const FT = (comptime findFinalizerType(T)) orelse break :blk 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 {
- const T = bridge.Struct(@TypeOf(value));
- if (@hasDecl(T, "acquireRef")) {
- value.acquireRef();
- return;
+// Start at the "resolved" type (the most specific) and work our way up the
+// prototype chain looking for the type that defines acquireRef
+fn findFinalizerType(comptime T: type) ?type {
+ const S = bridge.Struct(T);
+ if (@hasDecl(S, "acquireRef")) {
+ return S;
}
- if (@hasField(T, "_proto")) {
- conditionallyReference(value._proto);
+ if (@hasField(S, "_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 {
@@ -1381,6 +1444,34 @@ pub fn debugContextId(self: *const Local) i32 {
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
// we easily get both a Local and a HandleScope via Caller.init.
// But when we're going from Zig -> V8, things are more complicated.
diff --git a/src/browser/js/Promise.zig b/src/browser/js/Promise.zig
index 4d8d1f11..8418c408 100644
--- a/src/browser/js/Promise.zig
+++ b/src/browser/js/Promise.zig
@@ -67,7 +67,7 @@ fn _persist(self: *const Promise, comptime is_global: bool) !(if (is_global) Glo
return .{ .handle = global, .temps = {} };
}
try ctx.trackTemp(global);
- return .{ .handle = global, .temps = &ctx.identity.temps };
+ return .{ .handle = global, .temps = &ctx.session.temps };
}
pub const Temp = G(.temp);
diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig
index 02d2a404..af5dd031 100644
--- a/src/browser/js/Value.zig
+++ b/src/browser/js/Value.zig
@@ -303,7 +303,7 @@ fn _persist(self: *const Value, comptime is_global: bool) !(if (is_global) Globa
return .{ .handle = global, .temps = {} };
}
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 {
diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig
index a6fe16cb..c4217201 100644
--- a/src/browser/js/bridge.zig
+++ b/src/browser/js/bridge.zig
@@ -101,36 +101,21 @@ pub fn Builder(comptime T: type) type {
}
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 {
func: *const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void,
@@ -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 {
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
var caller: Caller = undefined;
diff --git a/src/browser/webapi/AbstractRange.zig b/src/browser/webapi/AbstractRange.zig
index d80fc115..a775ee22 100644
--- a/src/browser/webapi/AbstractRange.zig
+++ b/src/browser/webapi/AbstractRange.zig
@@ -17,6 +17,8 @@
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
+
const js = @import("../js/js.zig");
const Session = @import("../Session.zig");
@@ -31,7 +33,7 @@ const AbstractRange = @This();
pub const _prototype_root = true;
-_rc: u8,
+_rc: lp.RC(u8) = .{},
_type: Type,
_page_id: u32,
_arena: Allocator,
@@ -44,24 +46,18 @@ _start_container: *Node,
_range_link: std.DoublyLinkedList.Node = .{},
pub fn acquireRef(self: *AbstractRange) void {
- self._rc += 1;
+ self._rc.acquire();
}
-pub fn deinit(self: *AbstractRange, shutdown: bool, session: *Session) void {
- _ = shutdown;
- const rc = self._rc;
- if (comptime IS_DEBUG) {
- std.debug.assert(rc != 0);
+pub fn deinit(self: *AbstractRange, session: *Session) void {
+ if (session.findPageById(self._page_id)) |page| {
+ page._live_ranges.remove(&self._range_link);
}
+ session.releaseArena(self._arena);
+}
- if (rc == 1) {
- if (session.findPageById(self._page_id)) |page| {
- page._live_ranges.remove(&self._range_link);
- }
- 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) {
@@ -338,8 +334,6 @@ pub const JsApi = struct {
pub const name = "AbstractRange";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/browser/webapi/Blob.zig b/src/browser/webapi/Blob.zig
index 5b8bf81c..bf0c1118 100644
--- a/src/browser/webapi/Blob.zig
+++ b/src/browser/webapi/Blob.zig
@@ -17,7 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
-const Writer = std.Io.Writer;
+const lp = @import("lightpanda");
const js = @import("../js/js.zig");
const Page = @import("../Page.zig");
@@ -25,6 +25,7 @@ const Session = @import("../Session.zig");
const Mime = @import("../Mime.zig");
+const Writer = std.Io.Writer;
const Allocator = std.mem.Allocator;
/// https://w3c.github.io/FileAPI/#blob-section
@@ -34,6 +35,7 @@ const Blob = @This();
pub const _prototype_root = true;
_type: Type,
+_rc: lp.RC(u32),
_arena: Allocator,
@@ -120,6 +122,7 @@ pub fn initWithMimeValidation(
const self = try arena.create(Blob);
self.* = .{
+ ._rc = .{},
._arena = arena,
._type = .generic,
._slice = data,
@@ -128,11 +131,18 @@ pub fn initWithMimeValidation(
return self;
}
-pub fn deinit(self: *Blob, shutdown: bool, session: *Session) void {
- _ = shutdown;
+pub fn deinit(self: *Blob, session: *Session) void {
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);
/// 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.
@@ -325,8 +335,6 @@ pub const JsApi = struct {
pub const name = "Blob";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig
index d61be42c..1891ae96 100644
--- a/src/browser/webapi/Document.zig
+++ b/src/browser/webapi/Document.zig
@@ -61,7 +61,7 @@ _fonts: ?*FontFaceSet = null,
_write_insertion_point: ?*Node = null,
_script_created_parser: ?Parser.Streaming = 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
// Incremented during custom element reactions when parsing. When > 0,
diff --git a/src/browser/webapi/Event.zig b/src/browser/webapi/Event.zig
index 54a53d7e..b48bc059 100644
--- a/src/browser/webapi/Event.zig
+++ b/src/browser/webapi/Event.zig
@@ -17,6 +17,8 @@
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
+
const js = @import("../js/js.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
// - 1: either zig or v8 have a reference
// - 2: both zig and v8 have a reference
-_rc: u8 = 0,
+_rc: lp.RC(u8) = .{},
pub const EventPhase = enum(u8) {
none = 0,
@@ -139,25 +141,16 @@ pub fn initEvent(
}
pub fn acquireRef(self: *Event) void {
- self._rc += 1;
+ self._rc.acquire();
}
-pub fn deinit(self: *Event, shutdown: bool, session: *Session) void {
- if (shutdown) {
- session.releaseArena(self._arena);
- return;
- }
+/// Force cleanup on Session shutdown.
+pub fn deinit(self: *Event, session: *Session) void {
+ session.releaseArena(self._arena);
+}
- const rc = self._rc;
- if (comptime IS_DEBUG) {
- std.debug.assert(rc != 0);
- }
-
- if (rc == 1) {
- session.releaseArena(self._arena);
- } else {
- self._rc = rc - 1;
- }
+pub fn releaseRef(self: *Event, session: *Session) void {
+ self._rc.release(self, session);
}
pub fn as(self: *Event, comptime T: type) *T {
@@ -440,8 +433,6 @@ pub const JsApi = struct {
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
- pub const weak = true;
- pub const finalizer = bridge.finalizer(Event.deinit);
pub const enumerable = false;
};
diff --git a/src/browser/webapi/EventTarget.zig b/src/browser/webapi/EventTarget.zig
index cfdf0872..704efeb3 100644
--- a/src/browser/webapi/EventTarget.zig
+++ b/src/browser/webapi/EventTarget.zig
@@ -60,7 +60,7 @@ pub fn dispatchEvent(self: *EventTarget, event: *Event, page: *Page) !bool {
event._is_trusted = false;
event.acquireRef();
- defer event.deinit(false, page._session);
+ defer _ = event.releaseRef(page._session);
try page._event_manager.dispatch(self, event);
return !event._cancelable or !event._prevent_default;
}
diff --git a/src/browser/webapi/File.zig b/src/browser/webapi/File.zig
index f41e44bb..fb27359a 100644
--- a/src/browser/webapi/File.zig
+++ b/src/browser/webapi/File.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
const js = @import("../js/js.zig");
const Page = @import("../Page.zig");
@@ -26,7 +27,6 @@ const Blob = @import("Blob.zig");
const File = @This();
-/// `File` inherits `Blob`.
_proto: *Blob,
// TODO: Implement File API.
@@ -36,10 +36,6 @@ pub fn init(page: *Page) !*File {
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 bridge = js.Bridge(File);
@@ -47,8 +43,6 @@ pub const JsApi = struct {
pub const name = "File";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/browser/webapi/FileReader.zig b/src/browser/webapi/FileReader.zig
index da7ec71c..109fdc7b 100644
--- a/src/browser/webapi/FileReader.zig
+++ b/src/browser/webapi/FileReader.zig
@@ -17,6 +17,8 @@
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
+
const js = @import("../js/js.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
const FileReader = @This();
+_rc: lp.RC(u8) = .{},
_page: *Page,
_proto: *EventTarget,
_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_error) |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);
}
+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 {
return self._proto;
}
@@ -309,8 +320,6 @@ pub const JsApi = struct {
pub const name = "FileReader";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/browser/webapi/IntersectionObserver.zig b/src/browser/webapi/IntersectionObserver.zig
index 68cf086b..5d6c6e4d 100644
--- a/src/browser/webapi/IntersectionObserver.zig
+++ b/src/browser/webapi/IntersectionObserver.zig
@@ -16,6 +16,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
+
const js = @import("../js/js.zig");
const log = @import("../../log.zig");
@@ -39,6 +41,7 @@ pub fn registerTypes() []const type {
const IntersectionObserver = @This();
+_rc: lp.RC(u8) = .{},
_arena: Allocator,
_callback: js.Function.Temp,
_observing: std.ArrayList(*Element) = .{},
@@ -108,15 +111,22 @@ pub fn init(callback: js.Function.Temp, options: ?ObserverInit, page: *Page) !*I
return self;
}
-pub fn deinit(self: *IntersectionObserver, shutdown: bool, session: *Session) void {
+pub fn deinit(self: *IntersectionObserver, session: *Session) void {
self._callback.release();
- if ((comptime IS_DEBUG) and !shutdown) {
- std.debug.assert(self._observing.items.len == 0);
+ for (self._pending_entries.items) |entry| {
+ entry.deinitIfUnused(session);
}
-
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 {
// Check if already observing this target
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
if (self._observing.items.len == 0) {
- page.js.strongRef(self);
+ self._rc._refs += 1;
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 {
+ const original_length = self._observing.items.len;
for (self._observing.items, 0..) |elem, i| {
if (elem == target) {
_ = self._observing.swapRemove(i);
_ = 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;
while (j < self._pending_entries.items.len) {
if (self._pending_entries.items[j]._target == target) {
const entry = self._pending_entries.swapRemove(j);
- entry.deinit(false, page._session);
+ entry.deinitIfUnused(page._session);
} else {
j += 1;
}
@@ -163,21 +175,26 @@ pub fn unobserve(self: *IntersectionObserver, target: *Element, page: *Page) voi
}
}
- if (self._observing.items.len == 0) {
- page.js.safeWeakRef(self);
+ if (original_length > 0 and self._observing.items.len == 0) {
+ self._rc._refs -= 1;
}
}
pub fn disconnect(self: *IntersectionObserver, page: *Page) void {
- page.unregisterIntersectionObserver(self);
- self._observing.clearRetainingCapacity();
- self._previous_states.clearRetainingCapacity();
-
for (self._pending_entries.items) |entry| {
- entry.deinit(false, page._session);
+ entry.deinitIfUnused(page._session);
}
self._pending_entries.clearRetainingCapacity();
- page.js.safeWeakRef(self);
+ self._previous_states.clearRetainingCapacity();
+
+ const observing_count = self._observing.items.len;
+ self._observing.clearRetainingCapacity();
+
+ if (observing_count > 0) {
+ _ = self.releaseRef(page._session);
+ }
+
+ page.unregisterIntersectionObserver(self);
}
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),
._intersection_ratio = data.intersection_ratio,
};
-
try self._pending_entries.append(self._arena, entry);
}
@@ -310,6 +326,7 @@ pub fn deliverEntries(self: *IntersectionObserver, page: *Page) !void {
}
pub const IntersectionObserverEntry = struct {
+ _rc: lp.RC(u8) = .{},
_arena: Allocator,
_time: f64,
_target: *Element,
@@ -319,10 +336,25 @@ pub const IntersectionObserverEntry = struct {
_intersection_ratio: f64,
_is_intersecting: bool,
- pub fn deinit(self: *IntersectionObserverEntry, _: bool, session: *Session) void {
+ pub fn deinit(self: *IntersectionObserverEntry, session: *Session) void {
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 {
return self._target;
}
@@ -358,8 +390,6 @@ pub const IntersectionObserverEntry = struct {
pub const name = "IntersectionObserverEntry";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
@@ -379,8 +409,6 @@ pub const JsApi = struct {
pub const name = "IntersectionObserver";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
- pub const weak = true;
- pub const finalizer = bridge.finalizer(IntersectionObserver.deinit);
};
pub const constructor = bridge.constructor(init, .{});
diff --git a/src/browser/webapi/MutationObserver.zig b/src/browser/webapi/MutationObserver.zig
index b8608381..fa2d7f29 100644
--- a/src/browser/webapi/MutationObserver.zig
+++ b/src/browser/webapi/MutationObserver.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
const String = @import("../../string.zig").String;
const js = @import("../js/js.zig");
@@ -39,6 +40,7 @@ pub fn registerTypes() []const type {
const MutationObserver = @This();
+_rc: lp.RC(u8) = .{},
_arena: Allocator,
_callback: js.Function.Temp,
_observing: std.ArrayList(Observing) = .{},
@@ -85,15 +87,20 @@ pub fn init(callback: js.Function.Temp, page: *Page) !*MutationObserver {
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();
- if ((comptime IS_DEBUG) and !shutdown) {
- std.debug.assert(self._observing.items.len == 0);
- }
-
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 {
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
if (self._observing.items.len == 0) {
- page.js.strongRef(self);
+ self._rc._refs += 1;
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 {
- page.unregisterMutationObserver(self);
- self._observing.clearRetainingCapacity();
for (self._pending_records.items) |record| {
- record.deinit(false, page._session);
+ _ = record.releaseRef(page._session);
}
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 {
@@ -348,6 +359,7 @@ pub fn deliverRecords(self: *MutationObserver, page: *Page) !void {
}
pub const MutationRecord = struct {
+ _rc: lp.RC(u8) = .{},
_type: Type,
_target: *Node,
_arena: Allocator,
@@ -364,10 +376,18 @@ pub const MutationRecord = struct {
characterData,
};
- pub fn deinit(self: *MutationRecord, _: bool, session: *Session) void {
+ pub fn deinit(self: *MutationRecord, session: *Session) void {
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 {
return switch (self._type) {
.attributes => "attributes",
@@ -418,8 +438,6 @@ pub const MutationRecord = struct {
pub const name = "MutationRecord";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
@@ -441,8 +459,6 @@ pub const JsApi = struct {
pub const name = "MutationObserver";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/browser/webapi/Permissions.zig b/src/browser/webapi/Permissions.zig
index ee197d3f..8a06b4f4 100644
--- a/src/browser/webapi/Permissions.zig
+++ b/src/browser/webapi/Permissions.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
const js = @import("../js/js.zig");
const Page = @import("../Page.zig");
const Session = @import("../Session.zig");
@@ -50,14 +51,23 @@ pub fn query(_: *const Permissions, qd: QueryDescriptor, page: *Page) !js.Promis
}
const PermissionStatus = struct {
+ _rc: lp.RC(u8) = .{},
_arena: Allocator,
_name: []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);
}
+ 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 {
return self._name;
}
@@ -72,8 +82,6 @@ const PermissionStatus = struct {
pub const name = "PermissionStatus";
pub const prototype_chain = bridge.prototypeChain();
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 state = bridge.accessor(getState, null, .{});
diff --git a/src/browser/webapi/Range.zig b/src/browser/webapi/Range.zig
index 8d4bcd5c..720fc5ff 100644
--- a/src/browser/webapi/Range.zig
+++ b/src/browser/webapi/Range.zig
@@ -38,10 +38,6 @@ pub fn init(page: *Page) !*Range {
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 {
return self._proto;
}
@@ -697,8 +693,6 @@ pub const JsApi = struct {
pub const name = "Range";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
- pub const weak = true;
- pub const finalizer = bridge.finalizer(Range.deinit);
};
// Constants for compareBoundaryPoints
diff --git a/src/browser/webapi/Selection.zig b/src/browser/webapi/Selection.zig
index c6b84e89..9aaecc49 100644
--- a/src/browser/webapi/Selection.zig
+++ b/src/browser/webapi/Selection.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
const js = @import("../js/js.zig");
const Page = @import("../Page.zig");
@@ -32,18 +33,27 @@ const Selection = @This();
pub const SelectionDirection = enum { backward, forward, none };
+_rc: lp.RC(u8) = .{},
_range: ?*Range = null,
_direction: SelectionDirection = .none,
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| {
- r.deinit(shutdown, session);
+ r.asAbstractRange().deinit(session);
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 {
const event = try Event.init("selectionchange", .{}, page);
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 {
if (self._range) |existing| {
- existing.deinit(false, page._session);
+ _ = existing.asAbstractRange().releaseRef(page._session);
}
if (new_range) |nr| {
nr.asAbstractRange().acquireRef();
@@ -708,7 +718,6 @@ pub const JsApi = struct {
pub const name = "Selection";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
- pub const finalizer = bridge.finalizer(Selection.deinit);
};
pub const anchorNode = bridge.accessor(Selection.getAnchorNode, null, .{});
diff --git a/src/browser/webapi/URL.zig b/src/browser/webapi/URL.zig
index cb8e5d11..8856c83d 100644
--- a/src/browser/webapi/URL.zig
+++ b/src/browser/webapi/URL.zig
@@ -256,8 +256,7 @@ pub fn createObjectURL(blob: *Blob, page: *Page) ![]const u8 {
.{ page.origin orelse "null", uuid_buf },
);
try page._blob_urls.put(page.arena, blob_url, blob);
- // prevent GC from cleaning up the blob while it's in the registry
- page.js.strongRef(blob);
+ blob.acquireRef();
return blob_url;
}
@@ -267,9 +266,8 @@ pub fn revokeObjectURL(url: []const u8, page: *Page) void {
return;
}
- // Remove from registry and release strong ref (no-op if not found)
if (page._blob_urls.fetchRemove(url)) |entry| {
- page.js.weakRef(entry.value);
+ entry.value.releaseRef(page._session);
}
}
diff --git a/src/browser/webapi/animation/Animation.zig b/src/browser/webapi/animation/Animation.zig
index 8d445733..818cef58 100644
--- a/src/browser/webapi/animation/Animation.zig
+++ b/src/browser/webapi/animation/Animation.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
const log = @import("../../../log.zig");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
@@ -33,6 +34,7 @@ const PlayState = enum {
finished,
};
+_rc: lp.RC(u32) = .{},
_page: *Page,
_arena: Allocator,
@@ -62,10 +64,18 @@ pub fn init(page: *Page) !*Animation {
return self;
}
-pub fn deinit(self: *Animation, _: bool, session: *Session) void {
+pub fn deinit(self: *Animation, session: *Session) void {
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 {
if (self._playState == .running) {
return;
@@ -75,7 +85,7 @@ pub fn play(self: *Animation, page: *Page) !void {
self._playState = .running;
// Schedule the transition from .running => .finished in 10ms.
- page.js.strongRef(self);
+ self.acquireRef();
try page.js.scheduler.add(
self,
Animation.update,
@@ -201,7 +211,7 @@ fn update(ctx: *anyopaque) !?u32 {
}
// No future change scheduled, set the object weak for garbage collection.
- self._page.js.weakRef(self);
+ self.releaseRef(self._page._session);
return null;
}
@@ -220,8 +230,6 @@ pub const JsApi = struct {
pub const name = "Animation";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/browser/webapi/collections/NodeList.zig b/src/browser/webapi/collections/NodeList.zig
index a61cc598..97ec0e39 100644
--- a/src/browser/webapi/collections/NodeList.zig
+++ b/src/browser/webapi/collections/NodeList.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
const log = @import("../../../log.zig");
const js = @import("../../js/js.zig");
@@ -37,15 +38,9 @@ _data: union(enum) {
radio_node_list: *RadioNodeList,
name: NodeLive(.name),
},
-_rc: usize = 0,
-
-pub fn deinit(self: *NodeList, _: bool, session: *Session) void {
- const rc = self._rc;
- if (rc > 1) {
- self._rc = rc - 1;
- return;
- }
+_rc: lp.RC(u32) = .{},
+pub fn deinit(self: *NodeList, session: *Session) void {
switch (self._data) {
.selector_list => |list| list.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 {
- self._rc += 1;
+ self._rc.acquire();
}
pub fn length(self: *NodeList, page: *Page) !u32 {
@@ -119,8 +118,12 @@ const Iterator = struct {
const Entry = struct { u32, *Node };
- pub fn deinit(self: *Iterator, shutdown: bool, session: *Session) void {
- self.list.deinit(shutdown, session);
+ pub fn deinit(self: *Iterator, session: *Session) void {
+ self.list.deinit(session);
+ }
+
+ pub fn releaseRef(self: *Iterator, session: *Session) void {
+ self.list.releaseRef(session);
}
pub fn acquireRef(self: *Iterator) void {
@@ -143,8 +146,6 @@ pub const JsApi = struct {
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const enumerable = false;
- pub const weak = true;
- pub const finalizer = bridge.finalizer(NodeList.deinit);
};
pub const length = bridge.accessor(NodeList.length, null, .{});
diff --git a/src/browser/webapi/collections/iterator.zig b/src/browser/webapi/collections/iterator.zig
index 9fe3354d..6443c6ac 100644
--- a/src/browser/webapi/collections/iterator.zig
+++ b/src/browser/webapi/collections/iterator.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.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 });
}
- pub fn deinit(self: *Self, shutdown: bool, session: *Session) void {
- if (@hasDecl(Inner, "deinit")) {
- self.inner.deinit(shutdown, session);
+ pub fn deinit(self: *Self, session: *Session) void {
+ _ = self;
+ _ = 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 prototype_chain = bridge.prototypeChain();
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 });
diff --git a/src/browser/webapi/css/FontFace.zig b/src/browser/webapi/css/FontFace.zig
index f3c4059d..075d9135 100644
--- a/src/browser/webapi/css/FontFace.zig
+++ b/src/browser/webapi/css/FontFace.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
@@ -25,6 +26,7 @@ const Allocator = std.mem.Allocator;
const FontFace = @This();
+_rc: lp.RC(u8) = .{},
_arena: Allocator,
_family: []const u8,
@@ -42,10 +44,18 @@ pub fn init(family: []const u8, source: []const u8, page: *Page) !*FontFace {
return self;
}
-pub fn deinit(self: *FontFace, _: bool, session: *Session) void {
+pub fn deinit(self: *FontFace, session: *Session) void {
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 {
return self._family;
}
@@ -67,8 +77,6 @@ pub const JsApi = struct {
pub const name = "FontFace";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/browser/webapi/css/FontFaceSet.zig b/src/browser/webapi/css/FontFaceSet.zig
index 36c89570..b20017ca 100644
--- a/src/browser/webapi/css/FontFaceSet.zig
+++ b/src/browser/webapi/css/FontFaceSet.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
@@ -28,6 +29,7 @@ const Allocator = std.mem.Allocator;
const FontFaceSet = @This();
+_rc: lp.RC(u8) = .{},
_proto: *EventTarget,
_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);
}
+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 {
return self._proto;
}
@@ -95,8 +105,6 @@ pub const JsApi = struct {
pub const name = "FontFaceSet";
pub const prototype_chain = bridge.prototypeChain();
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 });
diff --git a/src/browser/webapi/encoding/TextDecoder.zig b/src/browser/webapi/encoding/TextDecoder.zig
index 736a4008..c117df09 100644
--- a/src/browser/webapi/encoding/TextDecoder.zig
+++ b/src/browser/webapi/encoding/TextDecoder.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
@@ -25,6 +26,7 @@ const Allocator = std.mem.Allocator;
const TextDecoder = @This();
+_rc: lp.RC(u8) = .{},
_fatal: bool,
_arena: Allocator,
_ignore_bom: bool,
@@ -60,10 +62,18 @@ pub fn init(label_: ?[]const u8, opts_: ?InitOpts, page: *Page) !*TextDecoder {
return self;
}
-pub fn deinit(self: *TextDecoder, _: bool, session: *Session) void {
+pub fn deinit(self: *TextDecoder, session: *Session) void {
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 {
return self._ignore_bom;
}
@@ -109,8 +119,6 @@ pub const JsApi = struct {
pub const name = "TextDecoder";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/browser/webapi/event/CompositionEvent.zig b/src/browser/webapi/event/CompositionEvent.zig
index 6de4e702..7f3fd1d2 100644
--- a/src/browser/webapi/event/CompositionEvent.zig
+++ b/src/browser/webapi/event/CompositionEvent.zig
@@ -53,10 +53,6 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*CompositionEvent {
return event;
}
-pub fn deinit(self: *CompositionEvent, shutdown: bool, session: *Session) void {
- self._proto.deinit(shutdown, session);
-}
-
pub fn asEvent(self: *CompositionEvent) *Event {
return self._proto;
}
@@ -72,8 +68,6 @@ pub const JsApi = struct {
pub const name = "CompositionEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
- pub const weak = true;
- pub const finalizer = bridge.finalizer(CompositionEvent.deinit);
};
pub const constructor = bridge.constructor(CompositionEvent.init, .{});
diff --git a/src/browser/webapi/event/CustomEvent.zig b/src/browser/webapi/event/CustomEvent.zig
index eaba2e7d..9013bb4a 100644
--- a/src/browser/webapi/event/CustomEvent.zig
+++ b/src/browser/webapi/event/CustomEvent.zig
@@ -73,11 +73,19 @@ pub fn initCustomEvent(
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| {
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 {
@@ -95,8 +103,6 @@ pub const JsApi = struct {
pub const name = "CustomEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
- pub const weak = true;
- pub const finalizer = bridge.finalizer(CustomEvent.deinit);
pub const enumerable = false;
};
diff --git a/src/browser/webapi/event/ErrorEvent.zig b/src/browser/webapi/event/ErrorEvent.zig
index 2d3b857f..aef63a0e 100644
--- a/src/browser/webapi/event/ErrorEvent.zig
+++ b/src/browser/webapi/event/ErrorEvent.zig
@@ -80,11 +80,19 @@ fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool
return event;
}
-pub fn deinit(self: *ErrorEvent, shutdown: bool, session: *Session) void {
+pub fn deinit(self: *ErrorEvent, session: *Session) void {
if (self._error) |e| {
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 {
diff --git a/src/browser/webapi/event/FocusEvent.zig b/src/browser/webapi/event/FocusEvent.zig
index f6823c23..776605db 100644
--- a/src/browser/webapi/event/FocusEvent.zig
+++ b/src/browser/webapi/event/FocusEvent.zig
@@ -70,10 +70,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event;
}
-pub fn deinit(self: *FocusEvent, shutdown: bool, session: *Session) void {
- self._proto.deinit(shutdown, session);
-}
-
pub fn asEvent(self: *FocusEvent) *Event {
return self._proto.asEvent();
}
@@ -89,8 +85,6 @@ pub const JsApi = struct {
pub const name = "FocusEvent";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/browser/webapi/event/FormDataEvent.zig b/src/browser/webapi/event/FormDataEvent.zig
index d50a0fc1..93eadfa3 100644
--- a/src/browser/webapi/event/FormDataEvent.zig
+++ b/src/browser/webapi/event/FormDataEvent.zig
@@ -66,10 +66,6 @@ fn initWithTrusted(arena: Allocator, typ: String, maybe_options: ?Options, trust
return event;
}
-pub fn deinit(self: *FormDataEvent, shutdown: bool, session: *Session) void {
- self._proto.deinit(shutdown, session);
-}
-
pub fn asEvent(self: *FormDataEvent) *Event {
return self._proto;
}
@@ -85,8 +81,6 @@ pub const JsApi = struct {
pub const name = "FormDataEvent";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/browser/webapi/event/InputEvent.zig b/src/browser/webapi/event/InputEvent.zig
index d81d51d6..3b01b900 100644
--- a/src/browser/webapi/event/InputEvent.zig
+++ b/src/browser/webapi/event/InputEvent.zig
@@ -83,10 +83,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event;
}
-pub fn deinit(self: *InputEvent, shutdown: bool, session: *Session) void {
- self._proto.deinit(shutdown, session);
-}
-
pub fn asEvent(self: *InputEvent) *Event {
return self._proto.asEvent();
}
@@ -110,8 +106,6 @@ pub const JsApi = struct {
pub const name = "InputEvent";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/browser/webapi/event/KeyboardEvent.zig b/src/browser/webapi/event/KeyboardEvent.zig
index d79d1e78..ddc7548d 100644
--- a/src/browser/webapi/event/KeyboardEvent.zig
+++ b/src/browser/webapi/event/KeyboardEvent.zig
@@ -229,10 +229,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event;
}
-pub fn deinit(self: *KeyboardEvent, shutdown: bool, session: *Session) void {
- self._proto.deinit(shutdown, session);
-}
-
pub fn asEvent(self: *KeyboardEvent) *Event {
return self._proto.asEvent();
}
@@ -296,8 +292,6 @@ pub const JsApi = struct {
pub const name = "KeyboardEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
- pub const weak = true;
- pub const finalizer = bridge.finalizer(KeyboardEvent.deinit);
};
pub const constructor = bridge.constructor(KeyboardEvent.init, .{});
diff --git a/src/browser/webapi/event/MessageEvent.zig b/src/browser/webapi/event/MessageEvent.zig
index 32ced1d8..03530400 100644
--- a/src/browser/webapi/event/MessageEvent.zig
+++ b/src/browser/webapi/event/MessageEvent.zig
@@ -73,11 +73,19 @@ fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool
return event;
}
-pub fn deinit(self: *MessageEvent, shutdown: bool, session: *Session) void {
+pub fn deinit(self: *MessageEvent, session: *Session) void {
if (self._data) |d| {
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 {
@@ -103,8 +111,6 @@ pub const JsApi = struct {
pub const name = "MessageEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
- pub const weak = true;
- pub const finalizer = bridge.finalizer(MessageEvent.deinit);
};
pub const constructor = bridge.constructor(MessageEvent.init, .{});
diff --git a/src/browser/webapi/event/MouseEvent.zig b/src/browser/webapi/event/MouseEvent.zig
index e13dc1b3..6d57142d 100644
--- a/src/browser/webapi/event/MouseEvent.zig
+++ b/src/browser/webapi/event/MouseEvent.zig
@@ -121,10 +121,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event;
}
-pub fn deinit(self: *MouseEvent, shutdown: bool, session: *Session) void {
- self._proto.deinit(shutdown, session);
-}
-
pub fn asEvent(self: *MouseEvent) *Event {
return self._proto.asEvent();
}
@@ -203,8 +199,6 @@ pub const JsApi = struct {
pub const name = "MouseEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
- pub const weak = true;
- pub const finalizer = bridge.finalizer(MouseEvent.deinit);
};
pub const constructor = bridge.constructor(MouseEvent.init, .{});
diff --git a/src/browser/webapi/event/NavigationCurrentEntryChangeEvent.zig b/src/browser/webapi/event/NavigationCurrentEntryChangeEvent.zig
index 98cba330..816fa1c8 100644
--- a/src/browser/webapi/event/NavigationCurrentEntryChangeEvent.zig
+++ b/src/browser/webapi/event/NavigationCurrentEntryChangeEvent.zig
@@ -83,10 +83,6 @@ fn initWithTrusted(
return event;
}
-pub fn deinit(self: *NavigationCurrentEntryChangeEvent, shutdown: bool, session: *Session) void {
- self._proto.deinit(shutdown, session);
-}
-
pub fn asEvent(self: *NavigationCurrentEntryChangeEvent) *Event {
return self._proto;
}
@@ -106,8 +102,6 @@ pub const JsApi = struct {
pub const name = "NavigationCurrentEntryChangeEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
- pub const weak = true;
- pub const finalizer = bridge.finalizer(NavigationCurrentEntryChangeEvent.deinit);
};
pub const constructor = bridge.constructor(NavigationCurrentEntryChangeEvent.init, .{});
diff --git a/src/browser/webapi/event/PageTransitionEvent.zig b/src/browser/webapi/event/PageTransitionEvent.zig
index bf747c9a..e11be386 100644
--- a/src/browser/webapi/event/PageTransitionEvent.zig
+++ b/src/browser/webapi/event/PageTransitionEvent.zig
@@ -66,10 +66,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event;
}
-pub fn deinit(self: *PageTransitionEvent, shutdown: bool, session: *Session) void {
- self._proto.deinit(shutdown, session);
-}
-
pub fn asEvent(self: *PageTransitionEvent) *Event {
return self._proto;
}
@@ -85,8 +81,6 @@ pub const JsApi = struct {
pub const name = "PageTransitionEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
- pub const weak = true;
- pub const finalizer = bridge.finalizer(PageTransitionEvent.deinit);
};
pub const constructor = bridge.constructor(PageTransitionEvent.init, .{});
diff --git a/src/browser/webapi/event/PointerEvent.zig b/src/browser/webapi/event/PointerEvent.zig
index c5178faf..c5440d45 100644
--- a/src/browser/webapi/event/PointerEvent.zig
+++ b/src/browser/webapi/event/PointerEvent.zig
@@ -128,10 +128,6 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*PointerEvent {
return event;
}
-pub fn deinit(self: *PointerEvent, shutdown: bool, session: *Session) void {
- self._proto.deinit(shutdown, session);
-}
-
pub fn asEvent(self: *PointerEvent) *Event {
return self._proto.asEvent();
}
@@ -191,8 +187,6 @@ pub const JsApi = struct {
pub const name = "PointerEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
- pub const weak = true;
- pub const finalizer = bridge.finalizer(PointerEvent.deinit);
};
pub const constructor = bridge.constructor(PointerEvent.init, .{});
diff --git a/src/browser/webapi/event/PopStateEvent.zig b/src/browser/webapi/event/PopStateEvent.zig
index f26c17b6..cd430cf8 100644
--- a/src/browser/webapi/event/PopStateEvent.zig
+++ b/src/browser/webapi/event/PopStateEvent.zig
@@ -67,10 +67,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event;
}
-pub fn deinit(self: *PopStateEvent, shutdown: bool, session: *Session) void {
- self._proto.deinit(shutdown, session);
-}
-
pub fn asEvent(self: *PopStateEvent) *Event {
return self._proto;
}
@@ -92,8 +88,6 @@ pub const JsApi = struct {
pub const name = "PopStateEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
- pub const weak = true;
- pub const finalizer = bridge.finalizer(PopStateEvent.deinit);
};
pub const constructor = bridge.constructor(PopStateEvent.init, .{});
diff --git a/src/browser/webapi/event/ProgressEvent.zig b/src/browser/webapi/event/ProgressEvent.zig
index b257f12b..6498da48 100644
--- a/src/browser/webapi/event/ProgressEvent.zig
+++ b/src/browser/webapi/event/ProgressEvent.zig
@@ -68,10 +68,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event;
}
-pub fn deinit(self: *ProgressEvent, shutdown: bool, session: *Session) void {
- self._proto.deinit(shutdown, session);
-}
-
pub fn asEvent(self: *ProgressEvent) *Event {
return self._proto;
}
@@ -96,8 +92,6 @@ pub const JsApi = struct {
pub const name = "ProgressEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
- pub const weak = true;
- pub const finalizer = bridge.finalizer(ProgressEvent.deinit);
};
pub const constructor = bridge.constructor(ProgressEvent.init, .{});
diff --git a/src/browser/webapi/event/PromiseRejectionEvent.zig b/src/browser/webapi/event/PromiseRejectionEvent.zig
index 5be6d792..cc014b39 100644
--- a/src/browser/webapi/event/PromiseRejectionEvent.zig
+++ b/src/browser/webapi/event/PromiseRejectionEvent.zig
@@ -56,14 +56,22 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*PromiseRejectionEve
return event;
}
-pub fn deinit(self: *PromiseRejectionEvent, shutdown: bool, session: *Session) void {
+pub fn deinit(self: *PromiseRejectionEvent, session: *Session) void {
if (self._reason) |r| {
r.release();
}
if (self._promise) |p| {
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 {
@@ -85,8 +93,6 @@ pub const JsApi = struct {
pub const name = "PromiseRejectionEvent";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/browser/webapi/event/SubmitEvent.zig b/src/browser/webapi/event/SubmitEvent.zig
index fbb1af06..f48365dc 100644
--- a/src/browser/webapi/event/SubmitEvent.zig
+++ b/src/browser/webapi/event/SubmitEvent.zig
@@ -67,10 +67,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event;
}
-pub fn deinit(self: *SubmitEvent, shutdown: bool, session: *Session) void {
- self._proto.deinit(shutdown, session);
-}
-
pub fn asEvent(self: *SubmitEvent) *Event {
return self._proto;
}
@@ -86,8 +82,6 @@ pub const JsApi = struct {
pub const name = "SubmitEvent";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/browser/webapi/event/TextEvent.zig b/src/browser/webapi/event/TextEvent.zig
index fd5e32fb..3ddb2636 100644
--- a/src/browser/webapi/event/TextEvent.zig
+++ b/src/browser/webapi/event/TextEvent.zig
@@ -59,10 +59,6 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*TextEvent {
return event;
}
-pub fn deinit(self: *TextEvent, shutdown: bool, session: *Session) void {
- self._proto.deinit(shutdown, session);
-}
-
pub fn asEvent(self: *TextEvent) *Event {
return self._proto.asEvent();
}
@@ -101,8 +97,6 @@ pub const JsApi = struct {
pub const name = "TextEvent";
pub const prototype_chain = bridge.prototypeChain();
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')
diff --git a/src/browser/webapi/event/UIEvent.zig b/src/browser/webapi/event/UIEvent.zig
index bbd9a60f..6874d6d5 100644
--- a/src/browser/webapi/event/UIEvent.zig
+++ b/src/browser/webapi/event/UIEvent.zig
@@ -71,10 +71,6 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*UIEvent {
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 {
return self.is(T).?;
}
@@ -122,8 +118,6 @@ pub const JsApi = struct {
pub const name = "UIEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
- pub const weak = true;
- pub const finalizer = bridge.finalizer(UIEvent.deinit);
};
pub const constructor = bridge.constructor(UIEvent.init, .{});
diff --git a/src/browser/webapi/event/WheelEvent.zig b/src/browser/webapi/event/WheelEvent.zig
index 831c4e02..4711ac25 100644
--- a/src/browser/webapi/event/WheelEvent.zig
+++ b/src/browser/webapi/event/WheelEvent.zig
@@ -87,10 +87,6 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*WheelEvent {
return event;
}
-pub fn deinit(self: *WheelEvent, shutdown: bool, session: *Session) void {
- self._proto.deinit(shutdown, session);
-}
-
pub fn asEvent(self: *WheelEvent) *Event {
return self._proto.asEvent();
}
@@ -118,8 +114,6 @@ pub const JsApi = struct {
pub const name = "WheelEvent";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/browser/webapi/net/Fetch.zig b/src/browser/webapi/net/Fetch.zig
index de1af4ec..af60f3b7 100644
--- a/src/browser/webapi/net/Fetch.zig
+++ b/src/browser/webapi/net/Fetch.zig
@@ -62,7 +62,7 @@ pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise {
}
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);
fetch.* = .{
@@ -225,7 +225,7 @@ fn httpDoneCallback(ctx: *anyopaque) !void {
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));
var response = self._response;
@@ -234,7 +234,7 @@ fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
// clear this. (defer since `self is in the response's arena).
defer if (self._owns_response) {
- response.deinit(err == error.Abort, self._page._session);
+ response.deinit(self._page._session);
self._owns_response = false;
};
@@ -256,7 +256,7 @@ fn httpShutdownCallback(ctx: *anyopaque) void {
if (self._owns_response) {
var response = self._response;
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
// allocated from response._arena which has been released.
}
diff --git a/src/browser/webapi/net/Response.zig b/src/browser/webapi/net/Response.zig
index 7020ac05..b9df006e 100644
--- a/src/browser/webapi/net/Response.zig
+++ b/src/browser/webapi/net/Response.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
const js = @import("../../js/js.zig");
const HttpClient = @import("../../HttpClient.zig");
@@ -38,6 +39,7 @@ pub const Type = enum {
opaqueredirect,
};
+_rc: lp.RC(u8) = .{},
_status: u16,
_arena: Allocator,
_headers: *Headers,
@@ -78,18 +80,22 @@ pub fn init(body_: ?[]const u8, opts_: ?InitOpts, page: *Page) !*Response {
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 (shutdown) {
- transfer.terminate();
- } else {
- transfer.abort(error.Abort);
- }
+ transfer.abort(error.Abort);
self._transfer = null;
}
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 {
return self._status;
}
@@ -197,8 +203,6 @@ pub const JsApi = struct {
pub const name = "Response";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/browser/webapi/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig
index e8780394..a601ce13 100644
--- a/src/browser/webapi/net/XMLHttpRequest.zig
+++ b/src/browser/webapi/net/XMLHttpRequest.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const lp = @import("lightpanda");
const js = @import("../../js/js.zig");
const log = @import("../../../log.zig");
@@ -38,6 +39,7 @@ const Allocator = std.mem.Allocator;
const IS_DEBUG = @import("builtin").mode == .Debug;
const XMLHttpRequest = @This();
+_rc: lp.RC(u8) = .{},
_page: *Page,
_proto: *XMLHttpRequestEventTarget,
_arena: Allocator,
@@ -87,21 +89,18 @@ const ResponseType = enum {
pub fn init(page: *Page) !*XMLHttpRequest {
const arena = try page.getArena(.{ .debug = "XMLHttpRequest" });
errdefer page.releaseArena(arena);
- return page._factory.xhrEventTarget(arena, XMLHttpRequest{
+ const xhr = try page._factory.xhrEventTarget(arena, XMLHttpRequest{
._page = page,
._arena = arena,
._proto = undefined,
._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 (shutdown) {
- transfer.terminate();
- } else {
- transfer.abort(error.Abort);
- }
+ transfer.abort(error.Abort);
self._transfer = null;
}
@@ -137,6 +136,14 @@ pub fn deinit(self: *XMLHttpRequest, shutdown: bool, session: *Session) void {
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 {
return self._proto._proto;
}
@@ -244,8 +251,6 @@ pub fn send(self: *XMLHttpRequest, body_: ?[]const u8) !void {
.error_callback = httpErrorCallback,
.shutdown_callback = httpShutdownCallback,
});
-
- page.js.strongRef(self);
}
fn handleBlobUrl(self: *XMLHttpRequest, page: *Page) !void {
@@ -387,6 +392,7 @@ fn httpStartCallback(transfer: *HttpClient.Transfer) !void {
log.debug(.http, "request start", .{ .method = self._method, .url = self._url, .source = "xhr" });
}
self._transfer = transfer;
+ self.acquireRef();
}
fn httpHeaderCallback(transfer: *HttpClient.Transfer, header: net_http.Header) !void {
@@ -485,15 +491,17 @@ fn httpDoneCallback(ctx: *anyopaque) !void {
.loaded = loaded,
}, page);
- page.js.weakRef(self);
+ self.releaseRef(page._session);
}
fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
const self: *XMLHttpRequest = @ptrCast(@alignCast(ctx));
// http client will close it after an error, it isn't safe to keep around
- self._transfer = null;
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 {
@@ -504,10 +512,10 @@ fn httpShutdownCallback(ctx: *anyopaque) void {
pub fn abort(self: *XMLHttpRequest) void {
self.handleError(error.Abort);
if (self._transfer) |transfer| {
- transfer.abort(error.Abort);
self._transfer = null;
+ transfer.abort(error.Abort);
+ self.releaseRef(self._page._session);
}
- self._page.js.weakRef(self);
}
fn handleError(self: *XMLHttpRequest, err: anyerror) void {
@@ -581,8 +589,6 @@ pub const JsApi = struct {
pub const name = "XMLHttpRequest";
pub const prototype_chain = bridge.prototypeChain();
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, .{});
diff --git a/src/lightpanda.zig b/src/lightpanda.zig
index de9c0835..9d163de4 100644
--- a/src/lightpanda.zig
+++ b/src/lightpanda.zig
@@ -209,6 +209,37 @@ noinline fn assertionFailure(comptime ctx: []const u8, args: anytype) noreturn {
@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 {
std.testing.refAllDecls(@This());
}