From 2a103fc94ab3d50aa777ea9fa113b4d7ec58707c Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Mon, 9 Mar 2026 13:14:57 +0800 Subject: [PATCH] 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. 255b9a91cc409adc685806878b716dc834fb991a 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. --- src/browser/EventManager.zig | 4 +- src/browser/Factory.zig | 6 +- src/browser/Page.zig | 94 +++--------- src/browser/Session.zig | 142 +++++++++++++++--- src/browser/js/Context.zig | 89 +---------- src/browser/js/Env.zig | 2 +- src/browser/js/Function.zig | 25 +-- src/browser/js/Local.zig | 7 +- src/browser/js/Origin.zig | 82 +++++++++- src/browser/js/Promise.zig | 25 +-- src/browser/js/Value.zig | 25 +-- src/browser/js/bridge.zig | 26 ++-- src/browser/js/js.zig | 1 + src/browser/webapi/Event.zig | 7 +- src/browser/webapi/EventTarget.zig | 2 +- src/browser/webapi/FileReader.zig | 19 ++- src/browser/webapi/IntersectionObserver.zig | 15 +- src/browser/webapi/MutationObserver.zig | 13 +- src/browser/webapi/Window.zig | 4 +- src/browser/webapi/animation/Animation.zig | 5 +- src/browser/webapi/collections/ChildNodes.zig | 5 +- src/browser/webapi/collections/NodeList.zig | 11 +- src/browser/webapi/collections/iterator.zig | 5 +- src/browser/webapi/encoding/TextDecoder.zig | 5 +- src/browser/webapi/event/CompositionEvent.zig | 5 +- src/browser/webapi/event/CustomEvent.zig | 7 +- src/browser/webapi/event/ErrorEvent.zig | 7 +- src/browser/webapi/event/FocusEvent.zig | 5 +- src/browser/webapi/event/KeyboardEvent.zig | 5 +- src/browser/webapi/event/MessageEvent.zig | 7 +- src/browser/webapi/event/MouseEvent.zig | 5 +- .../NavigationCurrentEntryChangeEvent.zig | 5 +- .../webapi/event/PageTransitionEvent.zig | 5 +- src/browser/webapi/event/PointerEvent.zig | 5 +- src/browser/webapi/event/PopStateEvent.zig | 5 +- src/browser/webapi/event/ProgressEvent.zig | 5 +- .../webapi/event/PromiseRejectionEvent.zig | 9 +- src/browser/webapi/event/TextEvent.zig | 5 +- src/browser/webapi/event/UIEvent.zig | 5 +- src/browser/webapi/event/WheelEvent.zig | 5 +- src/browser/webapi/net/Fetch.zig | 6 +- src/browser/webapi/net/Response.zig | 5 +- src/browser/webapi/net/XMLHttpRequest.zig | 23 +-- src/browser/webapi/selector/List.zig | 5 +- src/cdp/Node.zig | 6 +- src/cdp/domains/dom.zig | 4 +- src/mcp/tools.zig | 3 +- 47 files changed, 427 insertions(+), 334 deletions(-) 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| {