diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig index 573aa4f9..5588b704 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); + 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 }); diff --git a/src/browser/Factory.zig b/src/browser/Factory.zig index cbc2170d..c5ede22b 100644 --- a/src/browser/Factory.zig +++ b/src/browser/Factory.zig @@ -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 diff --git a/src/browser/Page.zig b/src/browser/Page.zig index dbb93792..60a3f968 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -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")); diff --git a/src/browser/Session.zig b/src/browser/Session.zig index d8a85fa2..42fe8ef5 100644 --- a/src/browser/Session.zig +++ b/src/browser/Session.zig @@ -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); diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 065b5e28..750cd809 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -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) { diff --git a/src/browser/js/Env.zig b/src/browser/js/Env.zig index d64ef649..4318394d 100644 --- a/src/browser/js/Env.zig +++ b/src/browser/js/Env.zig @@ -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); diff --git a/src/browser/js/Function.zig b/src/browser/js/Function.zig index 203fd9ab..bfb5e53d 100644 --- a/src/browser/js/Function.zig +++ b/src/browser/js/Function.zig @@ -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); + } }; } diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig index 305391f5..84e83b49 100644 --- a/src/browser/js/Local.zig +++ b/src/browser/js/Local.zig @@ -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)); diff --git a/src/browser/js/Origin.zig b/src/browser/js/Origin.zig index 6cb0b356..356b75c8 100644 --- a/src/browser/js/Origin.zig +++ b/src/browser/js/Origin.zig @@ -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; diff --git a/src/browser/js/Promise.zig b/src/browser/js/Promise.zig index 98520d4b..372d2578 100644 --- a/src/browser/js/Promise.zig +++ b/src/browser/js/Promise.zig @@ -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); + } }; } diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig index fbc961ed..309bdb6b 100644 --- a/src/browser/js/Value.zig +++ b/src/browser/js/Value.zig @@ -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); + } }; } diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index fcbbea21..f95329dc 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -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, }; diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index 22651c39..0c196e5b 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -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"); diff --git a/src/browser/webapi/Event.zig b/src/browser/webapi/Event.zig index 2821e9f1..d70042f8 100644 --- a/src/browser/webapi/Event.zig +++ b/src/browser/webapi/Event.zig @@ -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; } diff --git a/src/browser/webapi/EventTarget.zig b/src/browser/webapi/EventTarget.zig index 31f472c3..715c209a 100644 --- a/src/browser/webapi/EventTarget.zig +++ b/src/browser/webapi/EventTarget.zig @@ -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; } diff --git a/src/browser/webapi/FileReader.zig b/src/browser/webapi/FileReader.zig index 90e26aa0..da7ec71c 100644 --- a/src/browser/webapi/FileReader.zig +++ b/src/browser/webapi/FileReader.zig @@ -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 { diff --git a/src/browser/webapi/IntersectionObserver.zig b/src/browser/webapi/IntersectionObserver.zig index 9d64de3c..74a5d79e 100644 --- a/src/browser/webapi/IntersectionObserver.zig +++ b/src/browser/webapi/IntersectionObserver.zig @@ -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 { diff --git a/src/browser/webapi/MutationObserver.zig b/src/browser/webapi/MutationObserver.zig index a5dd15dd..b8608381 100644 --- a/src/browser/webapi/MutationObserver.zig +++ b/src/browser/webapi/MutationObserver.zig @@ -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 { diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index 40265bb5..2bc9fbb0 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -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); } diff --git a/src/browser/webapi/animation/Animation.zig b/src/browser/webapi/animation/Animation.zig index 96143952..8d445733 100644 --- a/src/browser/webapi/animation/Animation.zig +++ b/src/browser/webapi/animation/Animation.zig @@ -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 { diff --git a/src/browser/webapi/collections/ChildNodes.zig b/src/browser/webapi/collections/ChildNodes.zig index 5ba6256a..9c2bde91 100644 --- a/src/browser/webapi/collections/ChildNodes.zig +++ b/src/browser/webapi/collections/ChildNodes.zig @@ -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 { diff --git a/src/browser/webapi/collections/NodeList.zig b/src/browser/webapi/collections/NodeList.zig index 0237a76c..a61cc598 100644 --- a/src/browser/webapi/collections/NodeList.zig +++ b/src/browser/webapi/collections/NodeList.zig @@ -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 { diff --git a/src/browser/webapi/collections/iterator.zig b/src/browser/webapi/collections/iterator.zig index 3c43f3f8..9fe3354d 100644 --- a/src/browser/webapi/collections/iterator.zig +++ b/src/browser/webapi/collections/iterator.zig @@ -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); } } diff --git a/src/browser/webapi/encoding/TextDecoder.zig b/src/browser/webapi/encoding/TextDecoder.zig index 4f23cfcc..736a4008 100644 --- a/src/browser/webapi/encoding/TextDecoder.zig +++ b/src/browser/webapi/encoding/TextDecoder.zig @@ -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 { diff --git a/src/browser/webapi/event/CompositionEvent.zig b/src/browser/webapi/event/CompositionEvent.zig index b98fdd6f..a761e3ba 100644 --- a/src/browser/webapi/event/CompositionEvent.zig +++ b/src/browser/webapi/event/CompositionEvent.zig @@ -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 { diff --git a/src/browser/webapi/event/CustomEvent.zig b/src/browser/webapi/event/CustomEvent.zig index e303d901..eaba2e7d 100644 --- a/src/browser/webapi/event/CustomEvent.zig +++ b/src/browser/webapi/event/CustomEvent.zig @@ -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 { diff --git a/src/browser/webapi/event/ErrorEvent.zig b/src/browser/webapi/event/ErrorEvent.zig index 5dd12a26..2d3b857f 100644 --- a/src/browser/webapi/event/ErrorEvent.zig +++ b/src/browser/webapi/event/ErrorEvent.zig @@ -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 { diff --git a/src/browser/webapi/event/FocusEvent.zig b/src/browser/webapi/event/FocusEvent.zig index 37065936..f6823c23 100644 --- a/src/browser/webapi/event/FocusEvent.zig +++ b/src/browser/webapi/event/FocusEvent.zig @@ -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 { diff --git a/src/browser/webapi/event/KeyboardEvent.zig b/src/browser/webapi/event/KeyboardEvent.zig index 6e391812..1d4494b3 100644 --- a/src/browser/webapi/event/KeyboardEvent.zig +++ b/src/browser/webapi/event/KeyboardEvent.zig @@ -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 { diff --git a/src/browser/webapi/event/MessageEvent.zig b/src/browser/webapi/event/MessageEvent.zig index 34e04518..32ced1d8 100644 --- a/src/browser/webapi/event/MessageEvent.zig +++ b/src/browser/webapi/event/MessageEvent.zig @@ -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 { diff --git a/src/browser/webapi/event/MouseEvent.zig b/src/browser/webapi/event/MouseEvent.zig index c94a37d1..6b032433 100644 --- a/src/browser/webapi/event/MouseEvent.zig +++ b/src/browser/webapi/event/MouseEvent.zig @@ -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 { diff --git a/src/browser/webapi/event/NavigationCurrentEntryChangeEvent.zig b/src/browser/webapi/event/NavigationCurrentEntryChangeEvent.zig index 59e32b06..98cba330 100644 --- a/src/browser/webapi/event/NavigationCurrentEntryChangeEvent.zig +++ b/src/browser/webapi/event/NavigationCurrentEntryChangeEvent.zig @@ -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 { diff --git a/src/browser/webapi/event/PageTransitionEvent.zig b/src/browser/webapi/event/PageTransitionEvent.zig index eceab4f2..bf747c9a 100644 --- a/src/browser/webapi/event/PageTransitionEvent.zig +++ b/src/browser/webapi/event/PageTransitionEvent.zig @@ -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 { diff --git a/src/browser/webapi/event/PointerEvent.zig b/src/browser/webapi/event/PointerEvent.zig index 82f4874f..c5178faf 100644 --- a/src/browser/webapi/event/PointerEvent.zig +++ b/src/browser/webapi/event/PointerEvent.zig @@ -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 { diff --git a/src/browser/webapi/event/PopStateEvent.zig b/src/browser/webapi/event/PopStateEvent.zig index 45774998..f26c17b6 100644 --- a/src/browser/webapi/event/PopStateEvent.zig +++ b/src/browser/webapi/event/PopStateEvent.zig @@ -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 { diff --git a/src/browser/webapi/event/ProgressEvent.zig b/src/browser/webapi/event/ProgressEvent.zig index a78982a1..b257f12b 100644 --- a/src/browser/webapi/event/ProgressEvent.zig +++ b/src/browser/webapi/event/ProgressEvent.zig @@ -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 { diff --git a/src/browser/webapi/event/PromiseRejectionEvent.zig b/src/browser/webapi/event/PromiseRejectionEvent.zig index 957228df..f0a195b9 100644 --- a/src/browser/webapi/event/PromiseRejectionEvent.zig +++ b/src/browser/webapi/event/PromiseRejectionEvent.zig @@ -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 { diff --git a/src/browser/webapi/event/TextEvent.zig b/src/browser/webapi/event/TextEvent.zig index 54789c13..fd5e32fb 100644 --- a/src/browser/webapi/event/TextEvent.zig +++ b/src/browser/webapi/event/TextEvent.zig @@ -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 { diff --git a/src/browser/webapi/event/UIEvent.zig b/src/browser/webapi/event/UIEvent.zig index 6d329221..0aa2943b 100644 --- a/src/browser/webapi/event/UIEvent.zig +++ b/src/browser/webapi/event/UIEvent.zig @@ -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 { diff --git a/src/browser/webapi/event/WheelEvent.zig b/src/browser/webapi/event/WheelEvent.zig index ee725941..831c4e02 100644 --- a/src/browser/webapi/event/WheelEvent.zig +++ b/src/browser/webapi/event/WheelEvent.zig @@ -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 { diff --git a/src/browser/webapi/net/Fetch.zig b/src/browser/webapi/net/Fetch.zig index 699cc9c4..9b0f2f98 100644 --- a/src/browser/webapi/net/Fetch.zig +++ b/src/browser/webapi/net/Fetch.zig @@ -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. } diff --git a/src/browser/webapi/net/Response.zig b/src/browser/webapi/net/Response.zig index 13048f33..6a926369 100644 --- a/src/browser/webapi/net/Response.zig +++ b/src/browser/webapi/net/Response.zig @@ -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 { diff --git a/src/browser/webapi/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig index b9f053a8..d8d5e369 100644 --- a/src/browser/webapi/net/XMLHttpRequest.zig +++ b/src/browser/webapi/net/XMLHttpRequest.zig @@ -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 { diff --git a/src/browser/webapi/selector/List.zig b/src/browser/webapi/selector/List.zig index 04055910..1061320d 100644 --- a/src/browser/webapi/selector/List.zig +++ b/src/browser/webapi/selector/List.zig @@ -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( diff --git a/src/cdp/Node.zig b/src/cdp/Node.zig index 34c58e3a..1260217b 100644 --- a/src/cdp/Node.zig +++ b/src/cdp/Node.zig @@ -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); diff --git a/src/cdp/domains/dom.zig b/src/cdp/domains/dom.zig index 2f2befa5..df1d37c2 100644 --- a/src/cdp/domains/dom.zig +++ b/src/cdp/domains/dom.zig @@ -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; diff --git a/src/mcp/tools.zig b/src/mcp/tools.zig index 1ca9d120..53d29757 100644 --- a/src/mcp/tools.zig +++ b/src/mcp/tools.zig @@ -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| {