Remove Origins

js.Origin was added to allow frames on the same origin to share our zig<->js
maps / identity. It assumes that scripts on different origins will never be
allowed (by v8) to access the same zig instances.

If two different origins DID access the same zig instance, we'd have a few
different problems. First, while the mapping would exist in Origin1's
identity_map, when the zig instance was returned to a script in Origin2, it
would not be found in Origin2's identity_map, and thus create a new v8::Object.
Thus we'd end up with 2 v8::Objects for the same Zig instance. This is
potentially not the end of the world, but not great either as any zig-native
data _would_ be shared (it's the same instance after all), but js-native data
wouldn't.

The real problem this introduces though is with Finalizers. A weak reference
that falls out of scope in Origin1 will get cleaned up, even though it's still
referenced from Origin2.

Now, under normal circumstances, this isn't an issue; v8 _does_ ensure that
cross-origin access isn't allowed (because we set a SecurityToken on the
v8::Context). But it seems like the v8::Inspector isn't bound by these
restrictions and can happily access and share objects across origin.

The simplest solution I can come up with is to move the mapping from the Origin
to the Session. This does mean that objects might live longer than they have to.
When all references to an origin go out of scope, we can do some cleanup. Not
so when the Session owns this data. But really, how often are iframes on
different origins being created and deleted within the lifetime of a page?

When Origins were first introduces, the Session got burdened with having to
manage multiple lifecycles:
1 - The page-surviving data (e.g. history)
2 - The root page lifecycle (e.g. page_arena, queuedNavigation)
3 - The origin lookup

This commit doesn't change that, but it makes the session responsible for
_a lot_ more of the root page lifecycle (#2 above).

I lied. js.Origin still exists, but it's a shell of its former self. It only
exists to store the SecurityToken name that is re-used for every context with
the same origin.

The v8 namespace leaks into Session.

MutationObserver and IntersectionObserver are now back to using weak/strong refs
which was one of the failing cases before this change.
This commit is contained in:
Karl Seguin
2026-03-18 18:59:09 +08:00
parent edd0c5c83f
commit d9c5f56500
12 changed files with 215 additions and 259 deletions

View File

@@ -21,6 +21,7 @@ const js = @import("js.zig");
const v8 = js.v8;
const log = @import("../../log.zig");
const Session = @import("../Session.zig");
const Function = @This();
@@ -210,10 +211,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);
return .{ .handle = global, .origin = {} };
return .{ .handle = global, .session = {} };
}
try ctx.trackTemp(global);
return .{ .handle = global, .origin = ctx.origin };
return .{ .handle = global, .session = ctx.session };
}
pub fn tempWithThis(self: *const Function, value: anytype) !Temp {
@@ -237,7 +238,7 @@ const GlobalType = enum(u8) {
fn G(comptime global_type: GlobalType) type {
return struct {
handle: v8.Global,
origin: if (global_type == .temp) *js.Origin else void,
session: if (global_type == .temp) *Session else void,
const Self = @This();
@@ -257,7 +258,7 @@ fn G(comptime global_type: GlobalType) type {
}
pub fn release(self: *const Self) void {
self.origin.releaseTemp(self.handle);
self.session.releaseTemp(self.handle);
}
};
}