diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig index 297d4ac1..9ecd65b2 100644 --- a/src/browser/js/Caller.zig +++ b/src/browser/js/Caller.zig @@ -40,8 +40,8 @@ prev_context: *Context, // Takes the raw v8 isolate and extracts the context from it. pub fn init(self: *Caller, v8_isolate: *v8.Isolate) void { - const v8_context = v8.v8__Isolate__GetCurrentContext(v8_isolate).?; - initWithContext(self, Context.fromC(v8_context), v8_context); + const ctx, const v8_context = Context.fromIsolate(.{ .handle = v8_isolate }); + initWithContext(self, ctx, v8_context); } fn initWithContext(self: *Caller, ctx: *Context, v8_context: *const v8.Context) void { @@ -537,9 +537,7 @@ pub const Function = struct { pub fn call(comptime T: type, info_handle: *const v8.FunctionCallbackInfo, func: anytype, comptime opts: Opts) void { const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(info_handle).?; - const v8_context = v8.v8__Isolate__GetCurrentContext(v8_isolate).?; - - const ctx = Context.fromC(v8_context); + const ctx, const v8_context = Context.fromIsolate(.{ .handle = v8_isolate }); const info = FunctionCallbackInfo{ .handle = info_handle }; var hs: js.HandleScope = undefined; diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index ea1b6f7a..da7362aa 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -119,12 +119,22 @@ const ModuleEntry = struct { resolver_promise: ?js.Promise.Global = null, }; -pub fn fromC(c_context: *const v8.Context) *Context { +pub fn fromC(c_context: *const v8.Context) ?*Context { return @ptrCast(@alignCast(v8.v8__Context__GetAlignedPointerFromEmbedderData(c_context, 1))); } -pub fn fromIsolate(isolate: js.Isolate) *Context { - return fromC(v8.v8__Isolate__GetCurrentContext(isolate.handle).?); +/// Returns the Context and v8::Context for the given isolate. +/// If the current context is from a destroyed Context (e.g., navigated-away iframe), +/// falls back to the incumbent context (the calling context). +pub fn fromIsolate(isolate: js.Isolate) struct { *Context, *const v8.Context } { + const v8_context = v8.v8__Isolate__GetCurrentContext(isolate.handle).?; + if (fromC(v8_context)) |ctx| { + return .{ ctx, v8_context }; + } + // The current context's Context struct has been freed (e.g., iframe navigated away). + // Fall back to the incumbent context (the calling context). + const v8_incumbent = v8.v8__Isolate__GetIncumbentContext(isolate.handle).?; + return .{ fromC(v8_incumbent).?, v8_incumbent }; } pub fn deinit(self: *Context) void { @@ -155,6 +165,11 @@ pub fn deinit(self: *Context) void { self.session.releaseOrigin(self.origin); + // Clear the embedder data so that if V8 keeps this context alive + // (because objects created in it are still referenced), we don't + // have a dangling pointer to our freed Context struct. + v8.v8__Context__SetAlignedPointerInEmbedderData(entered.handle, 1, null); + v8.v8__Global__Reset(&self.handle); env.isolate.notifyContextDisposed(); // There can be other tasks associated with this context that we need to @@ -255,7 +270,7 @@ pub fn toLocal(self: *Context, global: anytype) js.Local.ToLocalReturnType(@Type } pub fn getIncumbent(self: *Context) *Page { - return fromC(v8.v8__Isolate__GetIncumbentContext(self.env.isolate.handle).?).page; + return fromC(v8.v8__Isolate__GetIncumbentContext(self.env.isolate.handle).?).?.page; } pub fn stringToPersistedFunction( @@ -479,7 +494,7 @@ fn resolveModuleCallback( ) callconv(.c) ?*const v8.Module { _ = import_attributes; - const self = fromC(c_context.?); + const self = fromC(c_context.?).?; const local = js.Local{ .ctx = self, .handle = c_context.?, @@ -512,7 +527,7 @@ pub fn dynamicModuleCallback( _ = host_defined_options; _ = import_attrs; - const self = fromC(c_context.?); + const self = fromC(c_context.?).?; const local = js.Local{ .ctx = self, .handle = c_context.?, @@ -559,7 +574,7 @@ pub fn dynamicModuleCallback( pub fn metaObjectCallback(c_context: ?*v8.Context, c_module: ?*v8.Module, c_meta: ?*v8.Value) callconv(.c) void { // @HandleScope implement this without a fat context/local.. - const self = fromC(c_context.?); + const self = fromC(c_context.?).?; var local = js.Local{ .ctx = self, .handle = c_context.?, diff --git a/src/browser/js/Env.zig b/src/browser/js/Env.zig index 1ac9e6b3..09117eb0 100644 --- a/src/browser/js/Env.zig +++ b/src/browser/js/Env.zig @@ -497,13 +497,13 @@ pub fn terminate(self: *const Env) void { fn promiseRejectCallback(message_handle: v8.PromiseRejectMessage) callconv(.c) void { const promise_handle = v8.v8__PromiseRejectMessage__GetPromise(&message_handle).?; const v8_isolate = v8.v8__Object__GetIsolate(@ptrCast(promise_handle)).?; - const js_isolate = js.Isolate{ .handle = v8_isolate }; - const ctx = Context.fromIsolate(js_isolate); + const isolate = js.Isolate{ .handle = v8_isolate }; + const ctx, const v8_context = Context.fromIsolate(isolate); const local = js.Local{ .ctx = ctx, - .isolate = js_isolate, - .handle = v8.v8__Isolate__GetCurrentContext(v8_isolate).?, + .isolate = isolate, + .handle = v8_context, .call_arena = ctx.call_arena, };