Use Session as a container for cross-frame resources

The introduction of frames means that data is no longer tied to a specific Page
or Context. 255b9a91cc introduced Origins for
v8 values shared across frames of the same origin. The commit highlighted the
lifetime mismatched that we now have with data that can outlive 1 frame. A
specific issue with that commit was the finalizers were still Context-owned.
But like any other piece of data, that isn't right; aside from modules, nothing
should be context-owned.

This commit continues where the last left off and moves finalizers from Context
to Origin. This is done in a separate commit because it introduces significant
changes. Currently, finalizers take a *Page, but that's no longer correct. A
value created in one Page, can outlive the Page. We need another container. I
original thought to use Origin, but that isn't know to CDP/MCP. Instead, I
decide to enhance the Session.

Session is now the owner of the page.arena, the page.factory and the
page.arena_pool. Finalizers are given a *Session which they can use to release
their arena.
This commit is contained in:
Karl Seguin
2026-03-09 13:14:57 +08:00
parent 753391b7e2
commit 2a103fc94a
47 changed files with 427 additions and 334 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 {
event.acquireRef();
defer event.deinit(false, self.page);
defer event.deinit(false, self.page._session);
if (comptime IS_DEBUG) {
log.debug(.event, "eventManager.dispatch", .{ .type = event._type_string.str(), .bubbles = event._bubbles });
@@ -234,7 +234,7 @@ pub fn dispatchDirect(self: *EventManager, target: *EventTarget, event: *Event,
const page = self.page;
event.acquireRef();
defer event.deinit(false, page);
defer event.deinit(false, page._session);
if (comptime IS_DEBUG) {
log.debug(.event, "dispatchDirect", .{ .type = event._type_string, .context = opts.context });

View File

@@ -48,13 +48,11 @@ const Factory = @This();
_arena: Allocator,
_slab: SlabAllocator,
pub fn init(arena: Allocator) !*Factory {
const self = try arena.create(Factory);
self.* = .{
pub fn init(arena: Allocator) Factory {
return .{
._arena = arena,
._slab = SlabAllocator.init(arena, 128),
};
return self;
}
// this is a root object

View File

@@ -214,14 +214,6 @@ arena: Allocator,
// from JS. Best arena to use, when possible.
call_arena: Allocator,
arena_pool: *ArenaPool,
// In Debug, we use this to see if anything fails to release an arena back to
// the pool.
_arena_pool_leak_track: (if (IS_DEBUG) std.AutoHashMapUnmanaged(usize, struct {
owner: []const u8,
count: usize,
}) else void) = if (IS_DEBUG) .empty else {},
parent: ?*Page,
window: *Window,
document: *Document,
@@ -248,17 +240,11 @@ pub fn init(self: *Page, frame_id: u32, session: *Session, parent: ?*Page) !void
if (comptime IS_DEBUG) {
log.debug(.page, "page.init", .{});
}
const browser = session.browser;
const arena_pool = browser.arena_pool;
const page_arena = if (parent) |p| p.arena else try arena_pool.acquire();
errdefer if (parent == null) arena_pool.release(page_arena);
var factory = if (parent) |p| p._factory else try Factory.init(page_arena);
const call_arena = try arena_pool.acquire();
errdefer arena_pool.release(call_arena);
const call_arena = try session.getArena(.{ .debug = "call_arena" });
errdefer session.releaseArena(call_arena);
const factory = &session.factory;
const document = (try factory.document(Node.Document.HTMLDocument{
._proto = undefined,
})).asDocument();
@@ -266,10 +252,9 @@ pub fn init(self: *Page, frame_id: u32, session: *Session, parent: ?*Page) !void
self.* = .{
.js = undefined,
.parent = parent,
.arena = page_arena,
.arena = session.page_arena,
.document = document,
.window = undefined,
.arena_pool = arena_pool,
.call_arena = call_arena,
._frame_id = frame_id,
._session = session,
@@ -277,7 +262,7 @@ pub fn init(self: *Page, frame_id: u32, session: *Session, parent: ?*Page) !void
._pending_loads = 1, // always 1 for the ScriptManager
._type = if (parent == null) .root else .frame,
._script_manager = undefined,
._event_manager = EventManager.init(page_arena, self),
._event_manager = EventManager.init(session.page_arena, self),
};
var screen: *Screen = undefined;
@@ -305,6 +290,7 @@ pub fn init(self: *Page, frame_id: u32, session: *Session, parent: ?*Page) !void
._visual_viewport = visual_viewport,
});
const browser = session.browser;
self._script_manager = ScriptManager.init(browser.allocator, browser.http_client, self);
errdefer self._script_manager.deinit();
@@ -340,11 +326,12 @@ pub fn deinit(self: *Page, abort_http: bool) void {
// stats.print(&stream) catch unreachable;
}
const session = self._session;
if (self._queued_navigation) |qn| {
self.arena_pool.release(qn.arena);
session.releaseArena(qn.arena);
}
const session = self._session;
const is_root = self.parent == null;
session.browser.env.destroyContext(self.js, is_root);
@@ -361,23 +348,7 @@ pub fn deinit(self: *Page, abort_http: bool) void {
self._script_manager.deinit();
if (comptime IS_DEBUG) {
var it = self._arena_pool_leak_track.valueIterator();
while (it.next()) |value_ptr| {
if (value_ptr.count > 0) {
log.err(.bug, "ArenaPool Leak", .{ .owner = value_ptr.owner, .type = self._type, .url = self.url });
if (comptime builtin.is_test) {
@panic("ArenaPool Leak");
}
}
}
}
self.arena_pool.release(self.call_arena);
if (self.parent == null) {
self.arena_pool.release(self.arena);
}
session.releaseArena(self.call_arena);
}
pub fn base(self: *const Page) [:0]const u8 {
@@ -417,34 +388,12 @@ pub fn headersForRequest(self: *Page, temp: Allocator, url: [:0]const u8, header
}
}
const GetArenaOpts = struct {
debug: []const u8,
};
pub fn getArena(self: *Page, comptime opts: GetArenaOpts) !Allocator {
const allocator = try self.arena_pool.acquire();
if (comptime IS_DEBUG) {
const gop = try self._arena_pool_leak_track.getOrPut(self.arena, @intFromPtr(allocator.ptr));
if (gop.found_existing) {
std.debug.assert(gop.value_ptr.count == 0);
}
gop.value_ptr.* = .{ .owner = opts.debug, .count = 1 };
}
return allocator;
pub fn getArena(self: *Page, comptime opts: Session.GetArenaOpts) !Allocator {
return self._session.getArena(opts);
}
pub fn releaseArena(self: *Page, allocator: Allocator) void {
if (comptime IS_DEBUG) {
const found = self._arena_pool_leak_track.getPtr(@intFromPtr(allocator.ptr)).?;
if (found.count != 1) {
log.err(.bug, "ArenaPool Double Free", .{ .owner = found.owner, .count = found.count, .type = self._type, .url = self.url });
if (comptime builtin.is_test) {
@panic("ArenaPool Double Free");
}
return;
}
found.count = 0;
}
return self.arena_pool.release(allocator);
return self._session.releaseArena(allocator);
}
pub fn isSameOrigin(self: *const Page, url: [:0]const u8) !bool {
@@ -586,8 +535,8 @@ pub fn scheduleNavigation(self: *Page, request_url: []const u8, opts: NavigateOp
if (self.canScheduleNavigation(std.meta.activeTag(nt)) == false) {
return;
}
const arena = try self.arena_pool.acquire();
errdefer self.arena_pool.release(arena);
const arena = try self._session.getArena(.{ .debug = "scheduleNavigation" });
errdefer self._session.releaseArena(arena);
return self.scheduleNavigationWithArena(arena, request_url, opts, nt);
}
@@ -626,9 +575,8 @@ fn scheduleNavigationWithArena(originator: *Page, arena: Allocator, request_url:
if (target.parent == null) {
try session.navigation.updateEntries(target.url, opts.kind, target, true);
}
// doin't defer this, the caller, the caller is responsible for freeing
// it on error
target.arena_pool.release(arena);
// don't defer this, the caller is responsible for freeing it on error
session.releaseArena(arena);
return;
}
@@ -3177,7 +3125,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);
keyboard_event.deinit(false, self._session);
return;
};
@@ -3253,7 +3201,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);
defer submit_event.deinit(false, self._session);
try self._event_manager.dispatch(form_element.asEventTarget(), submit_event);
// If the submit event was prevented, don't submit the form
@@ -3267,8 +3215,8 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form
// I don't think this is technically correct, but FormData handles it ok
const form_data = try FormData.init(form, submitter_, self);
const arena = try self.arena_pool.acquire();
errdefer self.arena_pool.release(arena);
const arena = try self._session.getArena(.{ .debug = "submitForm" });
errdefer self._session.releaseArena(arena);
const encoding = form_element.getAttributeSafe(comptime .wrap("enctype"));

View File

@@ -21,6 +21,7 @@ const lp = @import("lightpanda");
const builtin = @import("builtin");
const log = @import("../log.zig");
const App = @import("../App.zig");
const js = @import("js/js.zig");
const storage = @import("webapi/storage/storage.zig");
@@ -29,20 +30,50 @@ const History = @import("webapi/History.zig");
const Page = @import("Page.zig");
const Browser = @import("Browser.zig");
const Factory = @import("Factory.zig");
const Notification = @import("../Notification.zig");
const QueuedNavigation = Page.QueuedNavigation;
const Allocator = std.mem.Allocator;
const ArenaPool = App.ArenaPool;
const IS_DEBUG = builtin.mode == .Debug;
// Session is like a browser's tab.
// It owns the js env and the loader for all the pages of the session.
// You can create successively multiple pages for a session, but you must
// deinit a page before running another one.
// deinit a page before running another one. It manages two distinct lifetimes.
//
// The first is the lifetime of the Session itself, where pages are created and
// removed, but share the same cookie jar and navigation history (etc...)
//
// The second is as a container the data needed by the full page hierarchy, i.e. \
// the root page and all of its frames (and all of their frames.)
const Session = @This();
// These are the fields that remain intact for the duration of the Session
browser: *Browser,
arena: Allocator,
history: History,
navigation: Navigation,
storage_shed: storage.Shed,
notification: *Notification,
cookie_jar: storage.Cookie.Jar,
// These are the fields that get reset whenever the Session's page (the root) is reset.
factory: Factory,
page_arena: Allocator,
// Shared resources for all pages in this session.
// These live for the duration of the page tree (root + frames).
arena_pool: *ArenaPool,
// In Debug, we use this to see if anything fails to release an arena back to
// the pool.
_arena_pool_leak_track: if (IS_DEBUG) std.AutoHashMapUnmanaged(usize, struct {
owner: []const u8,
count: usize,
}) else void = if (IS_DEBUG) .empty else {},
page: ?Page,
queued_navigation: std.ArrayList(*Page),
// Temporary buffer for about:blank navigations during processing.
@@ -50,27 +81,24 @@ queued_navigation: std.ArrayList(*Page),
// about:blank navigations (which may add to queued_navigation).
queued_queued_navigation: std.ArrayList(*Page),
// Used to create our Inspector and in the BrowserContext.
arena: Allocator,
cookie_jar: storage.Cookie.Jar,
storage_shed: storage.Shed,
history: History,
navigation: Navigation,
page: ?Page,
frame_id_gen: u32,
pub fn init(self: *Session, browser: *Browser, notification: *Notification) !void {
const allocator = browser.app.allocator;
const arena = try browser.arena_pool.acquire();
errdefer browser.arena_pool.release(arena);
const arena_pool = browser.arena_pool;
const arena = try arena_pool.acquire();
errdefer arena_pool.release(arena);
const page_arena = try arena_pool.acquire();
errdefer arena_pool.release(page_arena);
self.* = .{
.page = null,
.arena = arena,
.arena_pool = arena_pool,
.page_arena = page_arena,
.factory = Factory.init(page_arena),
.history = .{},
.frame_id_gen = 0,
// The prototype (EventTarget) for Navigation is created when a Page is created.
@@ -90,9 +118,9 @@ pub fn deinit(self: *Session) void {
}
self.cookie_jar.deinit();
const browser = self.browser;
self.storage_shed.deinit(browser.app.allocator);
browser.arena_pool.release(self.arena);
self.storage_shed.deinit(self.browser.app.allocator);
self.arena_pool.release(self.page_arena);
self.arena_pool.release(self.arena);
}
// NOTE: the caller is not the owner of the returned value,
@@ -126,29 +154,84 @@ pub fn removePage(self: *Session) void {
self.page = null;
self.navigation.onRemovePage();
self.resetPageResources();
if (comptime IS_DEBUG) {
log.debug(.browser, "remove page", .{});
}
}
pub const GetArenaOpts = struct {
debug: []const u8,
};
pub fn getArena(self: *Session, opts: GetArenaOpts) !Allocator {
const allocator = try self.arena_pool.acquire();
if (comptime IS_DEBUG) {
// Use session's arena (not page_arena) since page_arena gets reset between pages
const gop = try self._arena_pool_leak_track.getOrPut(self.arena, @intFromPtr(allocator.ptr));
if (gop.found_existing and gop.value_ptr.count != 0) {
log.err(.bug, "ArenaPool Double Use", .{ .owner = gop.value_ptr.*.owner });
@panic("ArenaPool Double Use");
}
gop.value_ptr.* = .{ .owner = opts.debug, .count = 1 };
}
return allocator;
}
pub fn releaseArena(self: *Session, allocator: Allocator) void {
if (comptime IS_DEBUG) {
const found = self._arena_pool_leak_track.getPtr(@intFromPtr(allocator.ptr)).?;
if (found.count != 1) {
log.err(.bug, "ArenaPool Double Free", .{ .owner = found.owner, .count = found.count });
if (comptime builtin.is_test) {
@panic("ArenaPool Double Free");
}
return;
}
found.count = 0;
}
return self.arena_pool.release(allocator);
}
/// Reset page_arena and factory for a clean slate.
/// Called when root page is removed.
fn resetPageResources(self: *Session) void {
// Check for arena leaks before releasing
if (comptime IS_DEBUG) {
var it = self._arena_pool_leak_track.valueIterator();
while (it.next()) |value_ptr| {
if (value_ptr.count > 0) {
log.err(.bug, "ArenaPool Leak", .{ .owner = value_ptr.owner });
}
}
self._arena_pool_leak_track.clearRetainingCapacity();
}
// Release old page_arena and acquire fresh one
self.frame_id_gen = 0;
self.arena_pool.reset(self.page_arena, 64 * 1024);
self.factory = Factory.init(self.page_arena);
}
pub fn replacePage(self: *Session) !*Page {
if (comptime IS_DEBUG) {
log.debug(.browser, "replace page", .{});
}
lp.assert(self.page != null, "Session.replacePage null page", .{});
lp.assert(self.page.?.parent == null, "Session.replacePage with parent", .{});
var current = self.page.?;
const frame_id = current._frame_id;
const parent = current.parent;
current.deinit(false);
self.resetPageResources();
self.browser.env.memoryPressureNotification(.moderate);
self.page = @as(Page, undefined);
const page = &self.page.?;
try Page.init(page, frame_id, self, parent);
try Page.init(page, frame_id, self, null);
return page;
}
@@ -428,12 +511,11 @@ fn processQueuedNavigation(self: *Session) !void {
fn processFrameNavigation(self: *Session, page: *Page, qn: *QueuedNavigation) !void {
lp.assert(page.parent != null, "root queued navigation", .{});
const browser = self.browser;
const iframe = page.iframe.?;
const parent = page.parent.?;
page._queued_navigation = null;
defer browser.arena_pool.release(qn.arena);
defer self.releaseArena(qn.arena);
errdefer iframe._window = null;
@@ -465,9 +547,21 @@ fn processRootQueuedNavigation(self: *Session) !void {
// create a copy before the page is cleared
const qn = current_page._queued_navigation.?;
current_page._queued_navigation = null;
defer self.browser.arena_pool.release(qn.arena);
defer self.arena_pool.release(qn.arena);
// HACK
// Mark as released in tracking BEFORE removePage clears the map.
// We can't call releaseArena() because that would also return the arena
// to the pool, making the memory invalid before we use qn.url/qn.opts.
if (comptime IS_DEBUG) {
if (self._arena_pool_leak_track.getPtr(@intFromPtr(qn.arena.ptr))) |found| {
found.count = 0;
}
}
self.removePage();
self.page = @as(Page, undefined);
const new_page = &self.page.?;
try Page.init(new_page, frame_id, self, null);

View File

@@ -27,6 +27,7 @@ const Origin = @import("Origin.zig");
const Scheduler = @import("Scheduler.zig");
const Page = @import("../Page.zig");
const Session = @import("../Session.zig");
const ScriptManager = @import("../ScriptManager.zig");
const v8 = js.v8;
@@ -42,6 +43,7 @@ const Context = @This();
id: usize,
env: *Env,
page: *Page,
session: *Session,
isolate: js.Isolate,
// Per-context microtask queue for isolation between contexts
@@ -77,12 +79,6 @@ local: ?*const js.Local = null,
origin: *Origin,
// Any type that is stored in the identity_map which has a finalizer declared
// will have its finalizer stored here. This is only used when shutting down
// if v8 hasn't called the finalizer directly itself.
finalizer_callbacks: std.AutoHashMapUnmanaged(usize, *FinalizerCallback) = .empty,
finalizer_callback_pool: std.heap.MemoryPool(FinalizerCallback),
// Unlike other v8 types, like functions or objects, modules are not shared
// across origins.
global_modules: std.ArrayList(v8.Global) = .empty,
@@ -153,14 +149,6 @@ pub fn deinit(self: *Context) void {
// this can release objects
self.scheduler.deinit();
{
var it = self.finalizer_callbacks.valueIterator();
while (it.next()) |finalizer| {
finalizer.*.deinit();
}
self.finalizer_callback_pool.deinit();
}
for (self.global_modules.items) |*global| {
v8.v8__Global__Reset(global);
}
@@ -208,7 +196,7 @@ pub fn trackTemp(self: *Context, global: v8.Global) !void {
}
pub fn weakRef(self: *Context, obj: anytype) void {
const fc = self.finalizer_callbacks.get(@intFromPtr(obj)) orelse {
const fc = self.origin.finalizer_callbacks.get(@intFromPtr(obj)) orelse {
if (comptime IS_DEBUG) {
// should not be possible
std.debug.assert(false);
@@ -219,7 +207,7 @@ pub fn weakRef(self: *Context, obj: anytype) void {
}
pub fn safeWeakRef(self: *Context, obj: anytype) void {
const fc = self.finalizer_callbacks.get(@intFromPtr(obj)) orelse {
const fc = self.origin.finalizer_callbacks.get(@intFromPtr(obj)) orelse {
if (comptime IS_DEBUG) {
// should not be possible
std.debug.assert(false);
@@ -231,7 +219,7 @@ pub fn safeWeakRef(self: *Context, obj: anytype) void {
}
pub fn strongRef(self: *Context, obj: anytype) void {
const fc = self.finalizer_callbacks.get(@intFromPtr(obj)) orelse {
const fc = self.origin.finalizer_callbacks.get(@intFromPtr(obj)) orelse {
if (comptime IS_DEBUG) {
// should not be possible
std.debug.assert(false);
@@ -241,45 +229,6 @@ pub fn strongRef(self: *Context, obj: anytype) void {
v8.v8__Global__ClearWeak(&fc.global);
}
pub fn release(self: *Context, item: anytype) void {
if (@TypeOf(item) == *anyopaque) {
// Existing *anyopaque path for identity_map. Called internally from
// finalizers
var global = self.origin.identity_map.fetchRemove(@intFromPtr(item)) orelse {
if (comptime IS_DEBUG) {
// should not be possible
std.debug.assert(false);
}
return;
};
v8.v8__Global__Reset(&global.value);
// The item has been fianalized, remove it for the finalizer callback so that
// we don't try to call it again on shutdown.
const fc = self.finalizer_callbacks.fetchRemove(@intFromPtr(item)) orelse {
if (comptime IS_DEBUG) {
// should not be possible
std.debug.assert(false);
}
return;
};
self.finalizer_callback_pool.destroy(fc.value);
return;
}
if (comptime IS_DEBUG) {
switch (@TypeOf(item)) {
js.Value.Temp, js.Promise.Temp, js.Function.Temp => {},
else => |T| @compileError("Context.release cannot be called with a " ++ @typeName(T)),
}
}
if (self.origin.temps.fetchRemove(item.handle.data_ptr)) |kv| {
var global = kv.value;
v8.v8__Global__Reset(&global);
}
}
// 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;
@@ -1005,34 +954,6 @@ pub fn queueMicrotaskFunc(self: *Context, cb: js.Function) void {
v8.v8__MicrotaskQueue__EnqueueMicrotaskFunc(self.microtask_queue, self.isolate.handle, cb.handle);
}
pub fn createFinalizerCallback(self: *Context, global: v8.Global, ptr: *anyopaque, finalizerFn: *const fn (ptr: *anyopaque, page: *Page) void) !*FinalizerCallback {
const fc = try self.finalizer_callback_pool.create();
fc.* = .{
.ctx = self,
.ptr = ptr,
.global = global,
.finalizerFn = finalizerFn,
};
return fc;
}
// == Misc ==
// 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 ctx._finalizers and call them on
// context shutdown.
pub const FinalizerCallback = struct {
ctx: *Context,
ptr: *anyopaque,
global: v8.Global,
finalizerFn: *const fn (ptr: *anyopaque, page: *Page) void,
pub fn deinit(self: *FinalizerCallback) void {
self.finalizerFn(self.ptr, self.ctx.page);
self.ctx.finalizer_callback_pool.destroy(self);
}
};
// == Profiler ==
pub fn startCpuProfiler(self: *Context) void {
if (comptime !IS_DEBUG) {

View File

@@ -330,6 +330,7 @@ pub fn createContext(self: *Env, page: *Page) !*Context {
context.* = .{
.env = self,
.page = page,
.session = page._session,
.origin = origin,
.id = context_id,
.isolate = isolate,
@@ -340,7 +341,6 @@ pub fn createContext(self: *Env, page: *Page) !*Context {
.microtask_queue = microtask_queue,
.script_manager = &page._script_manager,
.scheduler = .init(context_arena),
.finalizer_callback_pool = std.heap.MemoryPool(Context.FinalizerCallback).init(self.app.allocator),
};
try context.origin.identity_map.putNoClobber(context_arena, @intFromPtr(page.window), global_global);

View File

@@ -210,10 +210,10 @@ fn _persist(self: *const Function, comptime is_global: bool) !(if (is_global) Gl
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
if (comptime is_global) {
try ctx.trackGlobal(global);
} else {
try ctx.trackTemp(global);
return .{ .handle = global, .origin = {} };
}
return .{ .handle = global };
try ctx.trackTemp(global);
return .{ .handle = global, .origin = ctx.origin };
}
pub fn tempWithThis(self: *const Function, value: anytype) !Temp {
@@ -226,15 +226,18 @@ pub fn persistWithThis(self: *const Function, value: anytype) !Global {
return with_this.persist();
}
pub const Temp = G(0);
pub const Global = G(1);
pub const Temp = G(.temp);
pub const Global = G(.global);
fn G(comptime discriminator: u8) type {
const GlobalType = enum(u8) {
temp,
global,
};
fn G(comptime global_type: GlobalType) type {
return struct {
handle: v8.Global,
// makes the types different (G(0) != G(1)), without taking up space
comptime _: u8 = discriminator,
origin: if (global_type == .temp) *js.Origin else void,
const Self = @This();
@@ -252,5 +255,9 @@ fn G(comptime discriminator: u8) type {
pub fn isEqual(self: *const Self, other: Function) bool {
return v8.v8__Global__IsEqual(&self.handle, other.handle);
}
pub fn release(self: *const Self) void {
self.origin.releaseTemp(self.handle);
}
};
}

View File

@@ -18,6 +18,7 @@
const std = @import("std");
const Page = @import("../Page.zig");
const Session = @import("../Session.zig");
const log = @import("../../log.zig");
const string = @import("../../string.zig");
@@ -231,10 +232,10 @@ pub fn mapZigInstanceToJs(self: *const Local, js_obj_handle: ?*const v8.Object,
// 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.?);
const fc = try ctx.origin.createFinalizerCallback(ctx.session, gop.value_ptr.*, resolved.ptr, resolved.finalizer_from_zig.?);
{
errdefer fc.deinit();
try ctx.finalizer_callbacks.put(ctx.arena, @intFromPtr(resolved.ptr), fc);
try ctx.origin.finalizer_callbacks.put(ctx.origin.arena, @intFromPtr(resolved.ptr), fc);
}
conditionallyReference(value);
@@ -1083,7 +1084,7 @@ const Resolved = struct {
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, page: *Page) void = null,
finalizer_from_zig: ?*const fn (ptr: *anyopaque, session: *Session) void = null,
};
pub fn resolveValue(value: anytype) Resolved {
const T = bridge.Struct(@TypeOf(value));

View File

@@ -24,10 +24,11 @@ const std = @import("std");
const js = @import("js.zig");
const App = @import("../../App.zig");
const Session = @import("../Session.zig");
const v8 = js.v8;
const Allocator = std.mem.Allocator;
const IS_DEBUG = @import("build").mode == .Debug;
const IS_DEBUG = @import("builtin").mode == .Debug;
const Origin = @This();
@@ -62,6 +63,29 @@ globals: std.ArrayList(v8.Global) = .empty,
// Key is global.data_ptr.
temps: std.AutoHashMapUnmanaged(usize, v8.Global) = .empty,
// Any type that is stored in the identity_map which has a finalizer declared
// will have its finalizer stored here. This is only used when shutting down
// if v8 hasn't called the finalizer directly itself.
finalizer_callbacks: std.AutoHashMapUnmanaged(usize, *FinalizerCallback) = .empty,
finalizer_callback_pool: std.heap.MemoryPool(FinalizerCallback),
// 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
// origin shutdown.
pub const FinalizerCallback = struct {
origin: *Origin,
session: *Session,
ptr: *anyopaque,
global: v8.Global,
finalizerFn: *const fn (ptr: *anyopaque, session: *Session) void,
pub fn deinit(self: *FinalizerCallback) void {
self.finalizerFn(self.ptr, self.session);
self.origin.finalizer_callback_pool.destroy(self);
}
};
pub fn init(app: *App, isolate: js.Isolate, key: []const u8) !*Origin {
const arena = try app.arena_pool.acquire();
errdefer app.arena_pool.release(arena);
@@ -83,11 +107,21 @@ pub fn init(app: *App, isolate: js.Isolate, key: []const u8) !*Origin {
.globals = .empty,
.temps = .empty,
.security_token = token_global,
.finalizer_callback_pool = .init(arena),
};
return self;
}
pub fn deinit(self: *Origin, app: *App) void {
// Call finalizers before releasing anything
{
var it = self.finalizer_callbacks.valueIterator();
while (it.next()) |finalizer| {
finalizer.*.deinit();
}
self.finalizer_callback_pool.deinit();
}
v8.v8__Global__Reset(&self.security_token);
{
@@ -119,6 +153,52 @@ pub fn trackTemp(self: *Origin, global: v8.Global) !void {
return self.temps.put(self.arena, global.data_ptr, global);
}
pub fn releaseTemp(self: *Origin, global: v8.Global) void {
if (self.temps.fetchRemove(global.data_ptr)) |kv| {
var g = kv.value;
v8.v8__Global__Reset(&g);
}
}
/// Release an item from the identity_map (called after finalizer runs from V8)
pub fn release(self: *Origin, item: *anyopaque) void {
var global = self.identity_map.fetchRemove(@intFromPtr(item)) orelse {
if (comptime IS_DEBUG) {
std.debug.assert(false);
}
return;
};
v8.v8__Global__Reset(&global.value);
// The item has been finalized, remove it from the finalizer callback so that
// we don't try to call it again on shutdown.
const fc = self.finalizer_callbacks.fetchRemove(@intFromPtr(item)) orelse {
if (comptime IS_DEBUG) {
std.debug.assert(false);
}
return;
};
self.finalizer_callback_pool.destroy(fc.value);
}
pub fn createFinalizerCallback(
self: *Origin,
session: *Session,
global: v8.Global,
ptr: *anyopaque,
finalizerFn: *const fn (ptr: *anyopaque, session: *Session) void,
) !*FinalizerCallback {
const fc = try self.finalizer_callback_pool.create();
fc.* = .{
.origin = self,
.session = session,
.ptr = ptr,
.global = global,
.finalizerFn = finalizerFn,
};
return fc;
}
pub fn transferTo(self: *Origin, dest: *Origin) !void {
const arena = dest.arena;

View File

@@ -63,21 +63,24 @@ fn _persist(self: *const Promise, comptime is_global: bool) !(if (is_global) Glo
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
if (comptime is_global) {
try ctx.trackGlobal(global);
} else {
try ctx.trackTemp(global);
return .{ .handle = global, .origin = {} };
}
return .{ .handle = global };
try ctx.trackTemp(global);
return .{ .handle = global, .origin = ctx.origin };
}
pub const Temp = G(0);
pub const Global = G(1);
pub const Temp = G(.temp);
pub const Global = G(.global);
fn G(comptime discriminator: u8) type {
const GlobalType = enum(u8) {
temp,
global,
};
fn G(comptime global_type: GlobalType) type {
return struct {
handle: v8.Global,
// makes the types different (G(0) != G(1)), without taking up space
comptime _: u8 = discriminator,
origin: if (global_type == .temp) *js.Origin else void,
const Self = @This();
@@ -91,5 +94,9 @@ fn G(comptime discriminator: u8) type {
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
};
}
pub fn release(self: *const Self) void {
self.origin.releaseTemp(self.handle);
}
};
}

View File

@@ -260,10 +260,10 @@ fn _persist(self: *const Value, comptime is_global: bool) !(if (is_global) Globa
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
if (comptime is_global) {
try ctx.trackGlobal(global);
} else {
try ctx.trackTemp(global);
return .{ .handle = global, .origin = {} };
}
return .{ .handle = global };
try ctx.trackTemp(global);
return .{ .handle = global, .origin = ctx.origin };
}
pub fn toZig(self: Value, comptime T: type) !T {
@@ -310,15 +310,18 @@ pub fn format(self: Value, writer: *std.Io.Writer) !void {
return js_str.format(writer);
}
pub const Temp = G(0);
pub const Global = G(1);
pub const Temp = G(.temp);
pub const Global = G(.global);
fn G(comptime discriminator: u8) type {
const GlobalType = enum(u8) {
temp,
global,
};
fn G(comptime global_type: GlobalType) type {
return struct {
handle: v8.Global,
// makes the types different (G(0) != G(1)), without taking up space
comptime _: u8 = discriminator,
origin: if (global_type == .temp) *js.Origin else void,
const Self = @This();
@@ -336,5 +339,9 @@ fn G(comptime discriminator: u8) type {
pub fn isEqual(self: *const Self, other: Value) bool {
return v8.v8__Global__IsEqual(&self.handle, other.handle);
}
pub fn release(self: *const Self) void {
self.origin.releaseTemp(self.handle);
}
};
}

View File

@@ -21,11 +21,13 @@ const js = @import("js.zig");
const lp = @import("lightpanda");
const log = @import("../../log.zig");
const Page = @import("../Page.zig");
const Session = @import("../Session.zig");
const v8 = js.v8;
const Caller = @import("Caller.zig");
const Context = @import("Context.zig");
const Origin = @import("Origin.zig");
const IS_DEBUG = @import("builtin").mode == .Debug;
@@ -104,24 +106,24 @@ pub fn Builder(comptime T: type) type {
return entries;
}
pub fn finalizer(comptime func: *const fn (self: *T, shutdown: bool, page: *Page) void) Finalizer {
pub fn finalizer(comptime func: *const fn (self: *T, shutdown: bool, session: *Session) void) Finalizer {
return .{
.from_zig = struct {
fn wrap(ptr: *anyopaque, page: *Page) void {
func(@ptrCast(@alignCast(ptr)), true, page);
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: *Context.FinalizerCallback = @ptrCast(@alignCast(ptr));
const fc: *Origin.FinalizerCallback = @ptrCast(@alignCast(ptr));
const ctx = fc.ctx;
const origin = fc.origin;
const value_ptr = fc.ptr;
if (ctx.finalizer_callbacks.contains(@intFromPtr(value_ptr))) {
func(@ptrCast(@alignCast(value_ptr)), false, ctx.page);
ctx.release(value_ptr);
if (origin.finalizer_callbacks.contains(@intFromPtr(value_ptr))) {
func(@ptrCast(@alignCast(value_ptr)), false, fc.session);
origin.release(value_ptr);
} else {
// A bit weird, but v8 _requires_ that we release it
// If we don't. We'll 100% crash.
@@ -413,12 +415,12 @@ pub const Property = struct {
};
const Finalizer = struct {
// The finalizer wrapper when called fro Zig. This is only called on
// Context.deinit
from_zig: *const fn (ctx: *anyopaque, page: *Page) void,
// 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 Context.denit). If it is 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,
};

View File

@@ -24,6 +24,7 @@ const string = @import("../../string.zig");
pub const Env = @import("Env.zig");
pub const bridge = @import("bridge.zig");
pub const Caller = @import("Caller.zig");
pub const Origin = @import("Origin.zig");
pub const Context = @import("Context.zig");
pub const Local = @import("Local.zig");
pub const Inspector = @import("Inspector.zig");

View File

@@ -20,6 +20,7 @@ const std = @import("std");
const js = @import("../js/js.zig");
const Page = @import("../Page.zig");
const Session = @import("../Session.zig");
const EventTarget = @import("EventTarget.zig");
const Node = @import("Node.zig");
const String = @import("../../string.zig").String;
@@ -139,9 +140,9 @@ pub fn acquireRef(self: *Event) void {
self._rc += 1;
}
pub fn deinit(self: *Event, shutdown: bool, page: *Page) void {
pub fn deinit(self: *Event, shutdown: bool, session: *Session) void {
if (shutdown) {
page.releaseArena(self._arena);
session.releaseArena(self._arena);
return;
}
@@ -151,7 +152,7 @@ pub fn deinit(self: *Event, shutdown: bool, page: *Page) void {
}
if (rc == 1) {
page.releaseArena(self._arena);
session.releaseArena(self._arena);
} else {
self._rc = rc - 1;
}

View File

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

View File

@@ -20,6 +20,7 @@ const std = @import("std");
const js = @import("../js/js.zig");
const Page = @import("../Page.zig");
const Session = @import("../Session.zig");
const EventTarget = @import("EventTarget.zig");
const ProgressEvent = @import("event/ProgressEvent.zig");
const Blob = @import("Blob.zig");
@@ -69,17 +70,15 @@ pub fn init(page: *Page) !*FileReader {
});
}
pub fn deinit(self: *FileReader, _: bool, page: *Page) void {
const js_ctx = page.js;
pub fn deinit(self: *FileReader, _: bool, session: *Session) void {
if (self._on_abort) |func| func.release();
if (self._on_error) |func| func.release();
if (self._on_load) |func| func.release();
if (self._on_load_end) |func| func.release();
if (self._on_load_start) |func| func.release();
if (self._on_progress) |func| func.release();
if (self._on_abort) |func| js_ctx.release(func);
if (self._on_error) |func| js_ctx.release(func);
if (self._on_load) |func| js_ctx.release(func);
if (self._on_load_end) |func| js_ctx.release(func);
if (self._on_load_start) |func| js_ctx.release(func);
if (self._on_progress) |func| js_ctx.release(func);
page.releaseArena(self._arena);
session.releaseArena(self._arena);
}
fn asEventTarget(self: *FileReader) *EventTarget {

View File

@@ -24,6 +24,7 @@ const IS_DEBUG = @import("builtin").mode == .Debug;
const Allocator = std.mem.Allocator;
const Page = @import("../Page.zig");
const Session = @import("../Session.zig");
const Element = @import("Element.zig");
const DOMRect = @import("DOMRect.zig");
@@ -91,13 +92,13 @@ pub fn init(callback: js.Function.Temp, options: ?ObserverInit, page: *Page) !*I
return self;
}
pub fn deinit(self: *IntersectionObserver, shutdown: bool, page: *Page) void {
page.js.release(self._callback);
pub fn deinit(self: *IntersectionObserver, shutdown: bool, session: *Session) void {
self._callback.release();
if ((comptime IS_DEBUG) and !shutdown) {
std.debug.assert(self._observing.items.len == 0);
}
page.releaseArena(self._arena);
session.releaseArena(self._arena);
}
pub fn observe(self: *IntersectionObserver, target: *Element, page: *Page) !void {
@@ -137,7 +138,7 @@ pub fn unobserve(self: *IntersectionObserver, target: *Element, page: *Page) voi
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);
entry.deinit(false, page._session);
} else {
j += 1;
}
@@ -157,7 +158,7 @@ pub fn disconnect(self: *IntersectionObserver, page: *Page) void {
self._previous_states.clearRetainingCapacity();
for (self._pending_entries.items) |entry| {
entry.deinit(false, page);
entry.deinit(false, page._session);
}
self._pending_entries.clearRetainingCapacity();
page.js.safeWeakRef(self);
@@ -302,8 +303,8 @@ pub const IntersectionObserverEntry = struct {
_intersection_ratio: f64,
_is_intersecting: bool,
pub fn deinit(self: *const IntersectionObserverEntry, _: bool, page: *Page) void {
page.releaseArena(self._arena);
pub fn deinit(self: *IntersectionObserverEntry, _: bool, session: *Session) void {
session.releaseArena(self._arena);
}
pub fn getTarget(self: *const IntersectionObserverEntry) *Element {

View File

@@ -21,6 +21,7 @@ const String = @import("../../string.zig").String;
const js = @import("../js/js.zig");
const Page = @import("../Page.zig");
const Session = @import("../Session.zig");
const Node = @import("Node.zig");
const Element = @import("Element.zig");
const log = @import("../../log.zig");
@@ -84,13 +85,13 @@ pub fn init(callback: js.Function.Temp, page: *Page) !*MutationObserver {
return self;
}
pub fn deinit(self: *MutationObserver, shutdown: bool, page: *Page) void {
page.js.release(self._callback);
pub fn deinit(self: *MutationObserver, shutdown: bool, session: *Session) void {
self._callback.release();
if ((comptime IS_DEBUG) and !shutdown) {
std.debug.assert(self._observing.items.len == 0);
}
page.releaseArena(self._arena);
session.releaseArena(self._arena);
}
pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions, page: *Page) !void {
@@ -171,7 +172,7 @@ pub fn disconnect(self: *MutationObserver, page: *Page) void {
page.unregisterMutationObserver(self);
self._observing.clearRetainingCapacity();
for (self._pending_records.items) |record| {
record.deinit(false, page);
record.deinit(false, page._session);
}
self._pending_records.clearRetainingCapacity();
page.js.safeWeakRef(self);
@@ -363,8 +364,8 @@ pub const MutationRecord = struct {
characterData,
};
pub fn deinit(self: *const MutationRecord, _: bool, page: *Page) void {
page.releaseArena(self._arena);
pub fn deinit(self: *MutationRecord, _: bool, session: *Session) void {
session.releaseArena(self._arena);
}
pub fn getType(self: *const MutationRecord) []const u8 {

View File

@@ -646,9 +646,9 @@ const ScheduleCallback = struct {
}
fn deinit(self: *ScheduleCallback) void {
self.page.js.release(self.cb);
self.cb.release();
for (self.params) |param| {
self.page.js.release(param);
param.release();
}
self.page.releaseArena(self.arena);
}

View File

@@ -20,6 +20,7 @@ const std = @import("std");
const log = @import("../../../log.zig");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Allocator = std.mem.Allocator;
@@ -61,8 +62,8 @@ pub fn init(page: *Page) !*Animation {
return self;
}
pub fn deinit(self: *Animation, _: bool, page: *Page) void {
page.releaseArena(self._arena);
pub fn deinit(self: *Animation, _: bool, session: *Session) void {
session.releaseArena(self._arena);
}
pub fn play(self: *Animation, page: *Page) !void {

View File

@@ -20,6 +20,7 @@ const std = @import("std");
const Node = @import("../Node.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const GenericIterator = @import("iterator.zig").Entry;
// Optimized for node.childNodes, which has to be a live list.
@@ -53,8 +54,8 @@ pub fn init(node: *Node, page: *Page) !*ChildNodes {
return self;
}
pub fn deinit(self: *const ChildNodes, page: *Page) void {
page.releaseArena(self._arena);
pub fn deinit(self: *const ChildNodes, session: *Session) void {
session.releaseArena(self._arena);
}
pub fn length(self: *ChildNodes, page: *Page) !u32 {

View File

@@ -21,6 +21,7 @@ const std = @import("std");
const log = @import("../../../log.zig");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Node = @import("../Node.zig");
const ChildNodes = @import("ChildNodes.zig");
@@ -38,7 +39,7 @@ _data: union(enum) {
},
_rc: usize = 0,
pub fn deinit(self: *NodeList, _: bool, page: *Page) void {
pub fn deinit(self: *NodeList, _: bool, session: *Session) void {
const rc = self._rc;
if (rc > 1) {
self._rc = rc - 1;
@@ -46,8 +47,8 @@ pub fn deinit(self: *NodeList, _: bool, page: *Page) void {
}
switch (self._data) {
.selector_list => |list| list.deinit(page),
.child_nodes => |cn| cn.deinit(page),
.selector_list => |list| list.deinit(session),
.child_nodes => |cn| cn.deinit(session),
else => {},
}
}
@@ -118,8 +119,8 @@ const Iterator = struct {
const Entry = struct { u32, *Node };
pub fn deinit(self: *Iterator, shutdown: bool, page: *Page) void {
self.list.deinit(shutdown, page);
pub fn deinit(self: *Iterator, shutdown: bool, session: *Session) void {
self.list.deinit(shutdown, session);
}
pub fn acquireRef(self: *Iterator) void {

View File

@@ -19,6 +19,7 @@
const std = @import("std");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type {
const R = reflect(Inner, field);
@@ -39,9 +40,9 @@ 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, page: *Page) void {
pub fn deinit(self: *Self, shutdown: bool, session: *Session) void {
if (@hasDecl(Inner, "deinit")) {
self.inner.deinit(shutdown, page);
self.inner.deinit(shutdown, session);
}
}

View File

@@ -20,6 +20,7 @@ const std = @import("std");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Allocator = std.mem.Allocator;
const TextDecoder = @This();
@@ -59,8 +60,8 @@ pub fn init(label_: ?[]const u8, opts_: ?InitOpts, page: *Page) !*TextDecoder {
return self;
}
pub fn deinit(self: *TextDecoder, _: bool, page: *Page) void {
page.releaseArena(self._arena);
pub fn deinit(self: *TextDecoder, _: bool, session: *Session) void {
session.releaseArena(self._arena);
}
pub fn getIgnoreBOM(self: *const TextDecoder) bool {

View File

@@ -20,6 +20,7 @@ const String = @import("../../../string.zig").String;
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Event = @import("../Event.zig");
const Allocator = std.mem.Allocator;
@@ -53,8 +54,8 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*CompositionEvent {
return event;
}
pub fn deinit(self: *CompositionEvent, shutdown: bool, page: *Page) void {
self._proto.deinit(shutdown, page);
pub fn deinit(self: *CompositionEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *CompositionEvent) *Event {

View File

@@ -21,6 +21,7 @@ const String = @import("../../../string.zig").String;
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Event = @import("../Event.zig");
const Allocator = std.mem.Allocator;
@@ -72,11 +73,11 @@ pub fn initCustomEvent(
self._detail = detail_;
}
pub fn deinit(self: *CustomEvent, shutdown: bool, page: *Page) void {
pub fn deinit(self: *CustomEvent, shutdown: bool, session: *Session) void {
if (self._detail) |d| {
page.js.release(d);
d.release();
}
self._proto.deinit(shutdown, page);
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *CustomEvent) *Event {

View File

@@ -21,6 +21,7 @@ const String = @import("../../../string.zig").String;
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Event = @import("../Event.zig");
const Allocator = std.mem.Allocator;
@@ -79,11 +80,11 @@ fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool
return event;
}
pub fn deinit(self: *ErrorEvent, shutdown: bool, page: *Page) void {
pub fn deinit(self: *ErrorEvent, shutdown: bool, session: *Session) void {
if (self._error) |e| {
page.js.release(e);
e.release();
}
self._proto.deinit(shutdown, page);
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *ErrorEvent) *Event {

View File

@@ -20,6 +20,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const String = @import("../../../string.zig").String;
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const js = @import("../../js/js.zig");
const Event = @import("../Event.zig");
@@ -69,8 +70,8 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event;
}
pub fn deinit(self: *FocusEvent, shutdown: bool, page: *Page) void {
self._proto.deinit(shutdown, page);
pub fn deinit(self: *FocusEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *FocusEvent) *Event {

View File

@@ -21,6 +21,7 @@ const String = @import("../../../string.zig").String;
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Event = @import("../Event.zig");
const UIEvent = @import("UIEvent.zig");
@@ -221,8 +222,8 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event;
}
pub fn deinit(self: *KeyboardEvent, shutdown: bool, page: *Page) void {
self._proto.deinit(shutdown, page);
pub fn deinit(self: *KeyboardEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *KeyboardEvent) *Event {

View File

@@ -22,6 +22,7 @@ const String = @import("../../../string.zig").String;
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Event = @import("../Event.zig");
const Window = @import("../Window.zig");
const Allocator = std.mem.Allocator;
@@ -72,11 +73,11 @@ fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool
return event;
}
pub fn deinit(self: *MessageEvent, shutdown: bool, page: *Page) void {
pub fn deinit(self: *MessageEvent, shutdown: bool, session: *Session) void {
if (self._data) |d| {
page.js.release(d);
d.release();
}
self._proto.deinit(shutdown, page);
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *MessageEvent) *Event {

View File

@@ -19,6 +19,7 @@
const std = @import("std");
const String = @import("../../../string.zig").String;
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const js = @import("../../js/js.zig");
const Event = @import("../Event.zig");
@@ -109,8 +110,8 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*MouseEvent {
return event;
}
pub fn deinit(self: *MouseEvent, shutdown: bool, page: *Page) void {
self._proto.deinit(shutdown, page);
pub fn deinit(self: *MouseEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *MouseEvent) *Event {

View File

@@ -21,6 +21,7 @@ const String = @import("../../../string.zig").String;
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Event = @import("../Event.zig");
const NavigationHistoryEntry = @import("../navigation/NavigationHistoryEntry.zig");
@@ -82,8 +83,8 @@ fn initWithTrusted(
return event;
}
pub fn deinit(self: *NavigationCurrentEntryChangeEvent, shutdown: bool, page: *Page) void {
self._proto.deinit(shutdown, page);
pub fn deinit(self: *NavigationCurrentEntryChangeEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *NavigationCurrentEntryChangeEvent) *Event {

View File

@@ -21,6 +21,7 @@ const String = @import("../../../string.zig").String;
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Event = @import("../Event.zig");
const Allocator = std.mem.Allocator;
@@ -65,8 +66,8 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event;
}
pub fn deinit(self: *PageTransitionEvent, shutdown: bool, page: *Page) void {
self._proto.deinit(shutdown, page);
pub fn deinit(self: *PageTransitionEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *PageTransitionEvent) *Event {

View File

@@ -21,6 +21,7 @@ const String = @import("../../../string.zig").String;
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Event = @import("../Event.zig");
const MouseEvent = @import("MouseEvent.zig");
@@ -127,8 +128,8 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*PointerEvent {
return event;
}
pub fn deinit(self: *PointerEvent, shutdown: bool, page: *Page) void {
self._proto.deinit(shutdown, page);
pub fn deinit(self: *PointerEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *PointerEvent) *Event {

View File

@@ -21,6 +21,7 @@ const String = @import("../../../string.zig").String;
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Event = @import("../Event.zig");
const Allocator = std.mem.Allocator;
@@ -66,8 +67,8 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event;
}
pub fn deinit(self: *PopStateEvent, shutdown: bool, page: *Page) void {
self._proto.deinit(shutdown, page);
pub fn deinit(self: *PopStateEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *PopStateEvent) *Event {

View File

@@ -20,6 +20,7 @@ const std = @import("std");
const String = @import("../../../string.zig").String;
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Event = @import("../Event.zig");
const Allocator = std.mem.Allocator;
@@ -67,8 +68,8 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event;
}
pub fn deinit(self: *ProgressEvent, shutdown: bool, page: *Page) void {
self._proto.deinit(shutdown, page);
pub fn deinit(self: *ProgressEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *ProgressEvent) *Event {

View File

@@ -20,6 +20,7 @@ const String = @import("../../../string.zig").String;
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Event = @import("../Event.zig");
const Allocator = std.mem.Allocator;
@@ -56,14 +57,14 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*PromiseRejectionEve
return event;
}
pub fn deinit(self: *PromiseRejectionEvent, shutdown: bool, page: *Page) void {
pub fn deinit(self: *PromiseRejectionEvent, shutdown: bool, session: *Session) void {
if (self._reason) |r| {
page.js.release(r);
r.release();
}
if (self._promise) |p| {
page.js.release(p);
p.release();
}
self._proto.deinit(shutdown, page);
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *PromiseRejectionEvent) *Event {

View File

@@ -19,6 +19,7 @@
const std = @import("std");
const String = @import("../../../string.zig").String;
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const js = @import("../../js/js.zig");
const Event = @import("../Event.zig");
@@ -58,8 +59,8 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*TextEvent {
return event;
}
pub fn deinit(self: *TextEvent, shutdown: bool, page: *Page) void {
self._proto.deinit(shutdown, page);
pub fn deinit(self: *TextEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *TextEvent) *Event {

View File

@@ -18,6 +18,7 @@
const String = @import("../../../string.zig").String;
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const js = @import("../../js/js.zig");
const Event = @import("../Event.zig");
@@ -69,8 +70,8 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*UIEvent {
return event;
}
pub fn deinit(self: *UIEvent, shutdown: bool, page: *Page) void {
self._proto.deinit(shutdown, page);
pub fn deinit(self: *UIEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn as(self: *UIEvent, comptime T: type) *T {

View File

@@ -19,6 +19,7 @@
const std = @import("std");
const String = @import("../../../string.zig").String;
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const js = @import("../../js/js.zig");
const Event = @import("../Event.zig");
@@ -86,8 +87,8 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*WheelEvent {
return event;
}
pub fn deinit(self: *WheelEvent, shutdown: bool, page: *Page) void {
self._proto.deinit(shutdown, page);
pub fn deinit(self: *WheelEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *WheelEvent) *Event {

View File

@@ -45,7 +45,7 @@ pub const InitOpts = Request.InitOpts;
pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise {
const request = try Request.init(input, options, page);
const response = try Response.init(null, .{ .status = 0 }, page);
errdefer response.deinit(true, page);
errdefer response.deinit(true, page._session);
const resolver = page.js.local.?.createPromiseResolver();
@@ -184,7 +184,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);
response.deinit(err == error.Abort, self._page._session);
self._owns_response = false;
};
@@ -205,7 +205,7 @@ fn httpShutdownCallback(ctx: *anyopaque) void {
if (self._owns_response) {
var response = self._response;
response._transfer = null;
response.deinit(true, self._page);
response.deinit(true, self._page._session);
// Do not access `self` after this point: the Fetch struct was
// allocated from response._arena which has been released.
}

View File

@@ -21,6 +21,7 @@ const js = @import("../../js/js.zig");
const HttpClient = @import("../../HttpClient.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Headers = @import("Headers.zig");
const ReadableStream = @import("../streams/ReadableStream.zig");
const Blob = @import("../Blob.zig");
@@ -77,7 +78,7 @@ pub fn init(body_: ?[]const u8, opts_: ?InitOpts, page: *Page) !*Response {
return self;
}
pub fn deinit(self: *Response, shutdown: bool, page: *Page) void {
pub fn deinit(self: *Response, shutdown: bool, session: *Session) void {
if (self._transfer) |transfer| {
if (shutdown) {
transfer.terminate();
@@ -86,7 +87,7 @@ pub fn deinit(self: *Response, shutdown: bool, page: *Page) void {
}
self._transfer = null;
}
page.releaseArena(self._arena);
session.releaseArena(self._arena);
}
pub fn getStatus(self: *const Response) u16 {

View File

@@ -26,6 +26,8 @@ const net_http = @import("../../../network/http.zig");
const URL = @import("../../URL.zig");
const Mime = @import("../../Mime.zig");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Node = @import("../Node.zig");
const Event = @import("../Event.zig");
const Headers = @import("Headers.zig");
@@ -93,7 +95,7 @@ pub fn init(page: *Page) !*XMLHttpRequest {
});
}
pub fn deinit(self: *XMLHttpRequest, shutdown: bool, page: *Page) void {
pub fn deinit(self: *XMLHttpRequest, shutdown: bool, session: *Session) void {
if (self._transfer) |transfer| {
if (shutdown) {
transfer.terminate();
@@ -103,37 +105,36 @@ pub fn deinit(self: *XMLHttpRequest, shutdown: bool, page: *Page) void {
self._transfer = null;
}
const js_ctx = page.js;
if (self._on_ready_state_change) |func| {
js_ctx.release(func);
func.release();
}
{
const proto = self._proto;
if (proto._on_abort) |func| {
js_ctx.release(func);
func.release();
}
if (proto._on_error) |func| {
js_ctx.release(func);
func.release();
}
if (proto._on_load) |func| {
js_ctx.release(func);
func.release();
}
if (proto._on_load_end) |func| {
js_ctx.release(func);
func.release();
}
if (proto._on_load_start) |func| {
js_ctx.release(func);
func.release();
}
if (proto._on_progress) |func| {
js_ctx.release(func);
func.release();
}
if (proto._on_timeout) |func| {
js_ctx.release(func);
func.release();
}
}
page.releaseArena(self._arena);
session.releaseArena(self._arena);
}
fn asEventTarget(self: *XMLHttpRequest) *EventTarget {

View File

@@ -19,6 +19,7 @@
const std = @import("std");
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const Node = @import("../Node.zig");
const Part = @import("Selector.zig").Part;
@@ -40,8 +41,8 @@ pub const EntryIterator = GenericIterator(Iterator, null);
pub const KeyIterator = GenericIterator(Iterator, "0");
pub const ValueIterator = GenericIterator(Iterator, "1");
pub fn deinit(self: *const List, page: *Page) void {
page.releaseArena(self._arena);
pub fn deinit(self: *const List, session: *Session) void {
session.releaseArena(self._arena);
}
pub fn collect(

View File

@@ -406,7 +406,7 @@ test "cdp Node: search list" {
{
const l1 = try doc.querySelectorAll(.wrap("a"), page);
defer l1.deinit(page);
defer l1.deinit(page._session);
const s1 = try search_list.create(l1._nodes);
try testing.expectEqual("1", s1.name);
try testing.expectEqualSlices(u32, &.{ 1, 2 }, s1.node_ids);
@@ -417,7 +417,7 @@ test "cdp Node: search list" {
{
const l2 = try doc.querySelectorAll(.wrap("#a1"), page);
defer l2.deinit(page);
defer l2.deinit(page._session);
const s2 = try search_list.create(l2._nodes);
try testing.expectEqual("2", s2.name);
try testing.expectEqualSlices(u32, &.{1}, s2.node_ids);
@@ -425,7 +425,7 @@ test "cdp Node: search list" {
{
const l3 = try doc.querySelectorAll(.wrap("#a2"), page);
defer l3.deinit(page);
defer l3.deinit(page._session);
const s3 = try search_list.create(l3._nodes);
try testing.expectEqual("3", s3.name);
try testing.expectEqualSlices(u32, &.{2}, s3.node_ids);

View File

@@ -98,7 +98,7 @@ fn performSearch(cmd: anytype) !void {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
const list = try Selector.querySelectorAll(page.window._document.asNode(), params.query, page);
defer list.deinit(page);
defer list.deinit(page._session);
const search = try bc.node_search_list.create(list._nodes);
@@ -249,7 +249,7 @@ fn querySelectorAll(cmd: anytype) !void {
};
const selected_nodes = try Selector.querySelectorAll(node.dom, params.selector, page);
defer selected_nodes.deinit(page);
defer selected_nodes.deinit(page._session);
const nodes = selected_nodes._nodes;

View File

@@ -92,7 +92,8 @@ const ToolStreamingText = struct {
},
.links => {
if (Selector.querySelectorAll(self.page.document.asNode(), "a[href]", self.page)) |list| {
defer list.deinit(self.page);
defer list.deinit(self.page._session);
var first = true;
for (list._nodes) |node| {
if (node.is(Element.Html.Anchor)) |anchor| {