From 307d0fd1de6934cced9420989eff645969f9febf Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 2 Jul 2026 15:20:51 +0200 Subject: [PATCH] reclaim FinalizerCallback identities at page teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Page teardown resets the identity-map Globals, which cancels their pending weak callbacks — the only place fc_identity_pool nodes were freed. Any object still referenced at page close leaked one Identity per navigation until the connection closed. Destroy them in FinalizerCallback.deinit instead; releaseRef unlinks itself when it fires, so everything still linked there has a cancelled callback. --- src/browser/js/js.zig | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index 3095d20b3..22a777f95 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -414,13 +414,24 @@ pub const FinalizerCallback = struct { // Called during Page teardown to force cleanup regardless of identities. pub fn deinit(self: *FinalizerCallback, page: *Page) void { - // Mark all identities as done so stale V8 weak callbacks - // won't find the wrong FC if resolved_ptr_id is reused. + // Any Identity still linked here has a weak callback that never + // fired (releaseRef unlinks itself when it runs). By this point + // every Global referencing these Identities was Reset by its + // identity map's teardown — main world in Page.deinit, isolated + // worlds on frame_remove / BrowserContext.deinit — and resetting a + // weak Global cancels its pending first-pass callback, so + // releaseRef can no longer fire for them. Return the nodes to the + // pool: waiting on the (now-cancelled) callback to free them leaks + // one Identity per surfaced object per navigation until the + // Browser is torn down. var id = self.identities; while (id) |identity| { - identity.done = true; - id = identity.next; + const next = identity.next; + identity.browser.fc_identity_pool.destroy(identity); + id = next; } + self.identities = null; + self.identity_count = 0; self.release_ref(self.finalizer_ptr_id, page); page.releaseArena(self.arena); }