diff --git a/src/browser/Browser.zig b/src/browser/Browser.zig index c29c796e..18a674da 100644 --- a/src/browser/Browser.zig +++ b/src/browser/Browser.zig @@ -86,7 +86,6 @@ pub fn closeSession(self: *Browser) void { if (self.session) |*session| { session.deinit(); self.session = null; - self.env.memoryPressureNotification(.critical); } } diff --git a/src/browser/Session.zig b/src/browser/Session.zig index 5e8141ca..e8f80e38 100644 --- a/src/browser/Session.zig +++ b/src/browser/Session.zig @@ -105,6 +105,12 @@ pub fn deinit(self: *Session) void { self.removePage(); } self.cookie_jar.deinit(); + + // Force V8 to flush any remaining weak callbacks while + // fc_identity_pool is still alive. Identity structs allocated from + // this pool back V8 weak-callback parameters; freeing the pool first + // would leave dangling pointers that segfault on the next GC. + self.browser.env.memoryPressureNotification(.critical); self.fc_identity_pool.deinit(); self.storage_shed.deinit(self.browser.app.allocator); diff --git a/src/cdp/CDP.zig b/src/cdp/CDP.zig index a56c5f30..a235fda2 100644 --- a/src/cdp/CDP.zig +++ b/src/cdp/CDP.zig @@ -365,6 +365,16 @@ pub fn disposeBrowserContext(self: *CDP, browser_context_id: []const u8) bool { if (std.mem.eql(u8, bc.id, browser_context_id) == false) { return false; } + // Reentrant teardown from a CDP message drained inside HttpClient.syncRequest. + // Tearing down the browser context here would free Session/Page state + // that the unwinding script-eval frame above us is about to dereference + // (see Session.removePage's matching guard). Defer cleanup to + // CDP.deinit at connection close, by which time eval has unwound. + if (bc.session.currentPage()) |page| { + if (page.frame._script_manager.base.is_evaluating) { + return true; + } + } bc.deinit(); self.browser.closeSession(); self.browser_context = null;