Better handle v8 callback with no valid context

In https://github.com/lightpanda-io/browser/pull/1885 we added fallback to the
incumbent context when the current context had be released (by us, but not by
v8).

This now handles the case where there is no incumbent context. It's not clear
exactly why this can happen, but we do see it in some WPT tests (e.g.
/html/browsers/the-window-object/named-access-on-the-window-object/navigated-named-objects.window.html)
This commit is contained in:
Karl Seguin
2026-04-08 16:54:58 +08:00
parent a9e7d75272
commit 4dcb2c997e
4 changed files with 54 additions and 18 deletions

View File

@@ -39,9 +39,22 @@ prev_local: ?*const js.Local,
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 ctx, const v8_context = Context.fromIsolate(.{ .handle = v8_isolate });
// Returns false if the context has been destroyed (e.g., navigated-away iframe),
// in which case a JS exception has been thrown and the caller should return immediately.
pub fn init(self: *Caller, v8_isolate: *v8.Isolate) bool {
const ctx, const v8_context = Context.fromIsolate(.{ .handle = v8_isolate }) orelse {
throwDetachedError(v8_isolate);
return false;
};
initWithContext(self, ctx, v8_context);
return true;
}
fn throwDetachedError(isolate: *v8.Isolate) void {
const message = "Cannot execute in detached context (e.g., navigated-away iframe)";
const v8_message = v8.v8__String__NewFromUtf8(isolate, message.ptr, v8.kNormal, @intCast(message.len));
const js_exception = v8.v8__Exception__Error(v8_message);
_ = v8.v8__Isolate__ThrowException(isolate, js_exception);
}
fn initWithContext(self: *Caller, ctx: *Context, v8_context: *const v8.Context) void {
@@ -60,9 +73,9 @@ fn initWithContext(self: *Caller, ctx: *Context, v8_context: *const v8.Context)
ctx.local = &self.local;
}
pub fn initFromHandle(self: *Caller, handle: ?*const v8.FunctionCallbackInfo) void {
pub fn initFromHandle(self: *Caller, handle: ?*const v8.FunctionCallbackInfo) bool {
const isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
self.init(isolate);
return self.init(isolate);
}
pub fn deinit(self: *Caller) void {
@@ -538,7 +551,10 @@ 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 ctx, const v8_context = Context.fromIsolate(.{ .handle = v8_isolate });
const ctx, const v8_context = Context.fromIsolate(.{ .handle = v8_isolate }) orelse {
throwDetachedError(v8_isolate);
return;
};
const info = FunctionCallbackInfo{ .handle = info_handle };
var hs: js.HandleScope = undefined;

View File

@@ -138,7 +138,8 @@ pub fn fromC(c_context: *const v8.Context) ?*Context {
/// 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 } {
/// Returns null if neither context has a valid Context struct (both were destroyed).
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 };
@@ -146,7 +147,8 @@ pub fn fromIsolate(isolate: js.Isolate) struct { *Context, *const 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 };
const ctx = fromC(v8_incumbent) orelse return null;
return .{ ctx, v8_incumbent };
}
pub fn deinit(self: *Context) void {
@@ -806,7 +808,9 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul
const then_callback = newFunctionWithData(local, struct {
pub fn callback(callback_handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
var c: Caller = undefined;
c.initFromHandle(callback_handle);
if (!c.initFromHandle(callback_handle)) {
return;
}
defer c.deinit();
const info = Caller.FunctionCallbackInfo{ .handle = callback_handle.? };
@@ -830,7 +834,7 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul
const catch_callback = newFunctionWithData(local, struct {
pub fn callback(callback_handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
var c: Caller = undefined;
c.initFromHandle(callback_handle);
if (!c.initFromHandle(callback_handle)) return;
defer c.deinit();
const info = Caller.FunctionCallbackInfo{ .handle = callback_handle.? };

View File

@@ -519,7 +519,7 @@ fn promiseRejectCallback(message_handle: v8.PromiseRejectMessage) callconv(.c) v
const promise_handle = v8.v8__PromiseRejectMessage__GetPromise(&message_handle).?;
const v8_isolate = v8.v8__Object__GetIsolate(@ptrCast(promise_handle)).?;
const isolate = js.Isolate{ .handle = v8_isolate };
const ctx, const v8_context = Context.fromIsolate(isolate);
const ctx, const v8_context = Context.fromIsolate(isolate) orelse return;
const local = js.Local{
.ctx = ctx,

View File

@@ -116,7 +116,9 @@ pub const Constructor = struct {
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
var caller: Caller = undefined;
caller.init(v8_isolate);
if (!caller.init(v8_isolate)) {
return;
}
defer caller.deinit();
caller.constructor(T, func, handle.?, .{
@@ -216,7 +218,9 @@ pub const Indexed = struct {
fn wrap(idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
var caller: Caller = undefined;
caller.init(v8_isolate);
if (!caller.init(v8_isolate)) {
return 0;
}
defer caller.deinit();
return caller.getIndex(T, getter, idx, handle.?, .{
@@ -232,7 +236,9 @@ pub const Indexed = struct {
fn wrap(handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
var caller: Caller = undefined;
caller.init(v8_isolate);
if (!caller.init(v8_isolate)) {
return 0;
}
defer caller.deinit();
return caller.getEnumerator(T, enumerator, handle.?, .{});
}
@@ -258,7 +264,9 @@ pub const NamedIndexed = struct {
fn wrap(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
var caller: Caller = undefined;
caller.init(v8_isolate);
if (!caller.init(v8_isolate)) {
return 0;
}
defer caller.deinit();
return caller.getNamedIndex(T, getter, c_name.?, handle.?, .{
@@ -272,7 +280,9 @@ pub const NamedIndexed = struct {
fn wrap(c_name: ?*const v8.Name, c_value: ?*const v8.Value, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
var caller: Caller = undefined;
caller.init(v8_isolate);
if (!caller.init(v8_isolate)) {
return 0;
}
defer caller.deinit();
return caller.setNamedIndex(T, setter, c_name.?, c_value.?, handle.?, .{
@@ -286,7 +296,9 @@ pub const NamedIndexed = struct {
fn wrap(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
var caller: Caller = undefined;
caller.init(v8_isolate);
if (!caller.init(v8_isolate)) {
return 0;
}
defer caller.deinit();
return caller.deleteNamedIndex(T, deleter, c_name.?, handle.?, .{
@@ -387,7 +399,9 @@ pub const Property = struct {
pub fn unknownWindowPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
var caller: Caller = undefined;
caller.init(v8_isolate);
if (!caller.init(v8_isolate)) {
return 0;
}
defer caller.deinit();
const local = &caller.local;
@@ -465,7 +479,9 @@ pub fn unknownObjectPropertyCallback(comptime JsApi: type) *const fn (?*const v8
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
var caller: Caller = undefined;
caller.init(v8_isolate);
if (!caller.init(v8_isolate)) {
return 0;
}
defer caller.deinit();
const local = &caller.local;