Move origin lookup to Session

With the last commit, this becomes the more logical place to hold this as it
ties into the Session's awareness of the root page's lifetime.
This commit is contained in:
Karl Seguin
2026-03-09 14:16:46 +08:00
parent 2a103fc94a
commit 7d90c3f582
3 changed files with 59 additions and 68 deletions

View File

@@ -62,6 +62,9 @@ factory: Factory,
page_arena: Allocator, page_arena: Allocator,
// Origin map for same-origin context sharing. Scoped to the root page lifetime.
origins: std.StringHashMapUnmanaged(*js.Origin) = .empty,
// Shared resources for all pages in this session. // Shared resources for all pages in this session.
// These live for the duration of the page tree (root + frames). // These live for the duration of the page tree (root + frames).
arena_pool: *ArenaPool, arena_pool: *ArenaPool,
@@ -194,6 +197,41 @@ pub fn releaseArena(self: *Session, allocator: Allocator) void {
return self.arena_pool.release(allocator); return self.arena_pool.release(allocator);
} }
pub fn getOrCreateOrigin(self: *Session, key_: ?[]const u8) !*js.Origin {
const key = key_ orelse {
var opaque_origin: [36]u8 = undefined;
@import("../id.zig").uuidv4(&opaque_origin);
// Origin.init will dupe opaque_origin. It's fine that this doesn't
// get added to self.origins. In fact, it further isolates it. When the
// context is freed, it'll call session.releaseOrigin which will free it.
return js.Origin.init(self.browser.app, self.browser.env.isolate, &opaque_origin);
};
const gop = try self.origins.getOrPut(self.arena, key);
if (gop.found_existing) {
const origin = gop.value_ptr.*;
origin.rc += 1;
return origin;
}
errdefer _ = self.origins.remove(key);
const origin = try js.Origin.init(self.browser.app, self.browser.env.isolate, key);
gop.key_ptr.* = origin.key;
gop.value_ptr.* = origin;
return origin;
}
pub fn releaseOrigin(self: *Session, origin: *js.Origin) void {
const rc = origin.rc;
if (rc == 1) {
_ = self.origins.remove(origin.key);
origin.deinit(self.browser.app);
} else {
origin.rc = rc - 1;
}
}
/// Reset page_arena and factory for a clean slate. /// Reset page_arena and factory for a clean slate.
/// Called when root page is removed. /// Called when root page is removed.
fn resetPageResources(self: *Session) void { fn resetPageResources(self: *Session) void {
@@ -208,6 +246,20 @@ fn resetPageResources(self: *Session) void {
self._arena_pool_leak_track.clearRetainingCapacity(); self._arena_pool_leak_track.clearRetainingCapacity();
} }
// All origins should have been released when contexts were destroyed
if (comptime IS_DEBUG) {
std.debug.assert(self.origins.count() == 0);
}
// Defensive cleanup in case origins leaked
{
const app = self.browser.app;
var it = self.origins.valueIterator();
while (it.next()) |value| {
value.*.deinit(app);
}
self.origins.clearRetainingCapacity();
}
// Release old page_arena and acquire fresh one // Release old page_arena and acquire fresh one
self.frame_id_gen = 0; self.frame_id_gen = 0;
self.arena_pool.reset(self.page_arena, 64 * 1024); self.arena_pool.reset(self.page_arena, 64 * 1024);

View File

@@ -153,7 +153,7 @@ pub fn deinit(self: *Context) void {
v8.v8__Global__Reset(global); v8.v8__Global__Reset(global);
} }
env.releaseOrigin(self.origin); self.session.releaseOrigin(self.origin);
v8.v8__Global__Reset(&self.handle); v8.v8__Global__Reset(&self.handle);
env.isolate.notifyContextDisposed(); env.isolate.notifyContextDisposed();
@@ -167,8 +167,8 @@ pub fn setOrigin(self: *Context, key: ?[]const u8) !void {
const env = self.env; const env = self.env;
const isolate = env.isolate; const isolate = env.isolate;
const origin = try env.getOrCreateOrigin(key); const origin = try self.session.getOrCreateOrigin(key);
errdefer env.releaseOrigin(origin); errdefer self.session.releaseOrigin(origin);
try self.origin.transferTo(origin); try self.origin.transferTo(origin);
self.origin.deinit(env.app); self.origin.deinit(env.app);

View File

@@ -77,14 +77,6 @@ context_id: usize,
// same-origin Contexts. There's a mismatch here between our JS model and our // same-origin Contexts. There's a mismatch here between our JS model and our
// Browser model. Origins only live as long as the root page of a session exists. // Browser model. Origins only live as long as the root page of a session exists.
// It would be wrong/dangerous to re-use an Origin across root page navigations. // It would be wrong/dangerous to re-use an Origin across root page navigations.
// But we have no mechanism to capture that lifetime in js. We used to have a
// js.BrowserContext which mapped to a Session (oops, I took it out), but even
// that wouldn't match correctly, because 1 session can have have muliple non-
// concurrent pages. We deal with this in destroyContext by checking if we're
// destroying the root context and, if so, making sure origins is empty. But, if
// we ever add multiple Sessions to a Browser or mulitple Pages to a Session,
// this map will have to live in a new, better scoped, container.
origins: std.StringHashMapUnmanaged(*Origin) = .empty,
// Global handles that need to be freed on deinit // Global handles that need to be freed on deinit
eternal_function_templates: []v8.Eternal, eternal_function_templates: []v8.Eternal,
@@ -248,14 +240,6 @@ pub fn deinit(self: *Env) void {
const app = self.app; const app = self.app;
const allocator = app.allocator; const allocator = app.allocator;
{
var it = self.origins.valueIterator();
while (it.next()) |value| {
value.*.deinit(app);
}
self.origins.deinit(allocator);
}
if (self.inspector) |i| { if (self.inspector) |i| {
i.deinit(allocator); i.deinit(allocator);
} }
@@ -323,8 +307,8 @@ pub fn createContext(self: *Env, page: *Page) !*Context {
const context_id = self.context_id; const context_id = self.context_id;
self.context_id = context_id + 1; self.context_id = context_id + 1;
const origin = try self.getOrCreateOrigin(null); const origin = try page._session.getOrCreateOrigin(null);
errdefer self.releaseOrigin(origin); errdefer page._session.releaseOrigin(origin);
const context = try context_arena.create(Context); const context = try context_arena.create(Context);
context.* = .{ context.* = .{
@@ -383,56 +367,11 @@ pub fn destroyContext(self: *Env, context: *Context, is_root: bool) void {
context.deinit(); context.deinit();
if (is_root) { if (is_root) {
// When the root is destroyed, the all of our contexts should be gone // When the root is destroyed, all of our contexts should be gone.
// and with them, all of our origins. Keep origins around longer than // Origin cleanup happens in Session.resetPageResources.
// intended would cause issues, so we're going to be defensive here and
// clean things up.
if (comptime IS_DEBUG) { if (comptime IS_DEBUG) {
std.debug.assert(self.context_count == 0); std.debug.assert(self.context_count == 0);
std.debug.assert(self.origins.count() == 0);
} }
const app = self.app;
var it = self.origins.valueIterator();
while (it.next()) |value| {
value.*.deinit(app);
}
self.origins.clearRetainingCapacity();
}
}
pub fn getOrCreateOrigin(self: *Env, key_: ?[]const u8) !*Origin {
const key = key_ orelse {
var opaque_origin: [36]u8 = undefined;
@import("../../id.zig").uuidv4(&opaque_origin);
// Origin.init will dupe opaque_origin. It's fine that this doesn't
// get added to self.origins. In fact, it further isolates it. When the
// context is freed, it'll call env.releaseOrigin which will free it.
return Origin.init(self.app, self.isolate, &opaque_origin);
};
const gop = try self.origins.getOrPut(self.allocator, key);
if (gop.found_existing) {
const origin = gop.value_ptr.*;
origin.rc += 1;
return origin;
}
errdefer _ = self.origins.remove(key);
const origin = try Origin.init(self.app, self.isolate, key);
gop.key_ptr.* = origin.key;
gop.value_ptr.* = origin;
return origin;
}
pub fn releaseOrigin(self: *Env, origin: *Origin) void {
const rc = origin.rc;
if (rc == 1) {
_ = self.origins.remove(origin.key);
origin.deinit(self.app);
} else {
origin.rc = rc - 1;
} }
} }