Isolate and HandleScope

This commit is contained in:
Karl Seguin
2025-12-31 18:52:47 +08:00
parent ca5a385b51
commit 363b95bdef
9 changed files with 224 additions and 91 deletions

View File

@@ -39,15 +39,16 @@ const CALL_ARENA_RETAIN = 1024 * 16;
const Caller = @This();
context: *Context,
v8_context: v8.Context,
isolate: v8.Isolate,
isolate: js.Isolate,
call_arena: Allocator,
// info is a v8.PropertyCallbackInfo or a v8.FunctionCallback
// All we really want from it is the isolate.
// executor = Isolate -> getCurrentContext -> getEmbedderData()
pub fn init(info: anytype) Caller {
const isolate = info.getIsolate();
const v8_context = isolate.getCurrentContext();
const v8_isolate = info.getIsolate();
const isolate = js.Isolate{ .handle = v8_isolate.handle };
const v8_context = v8_isolate.getCurrentContext();
const context: *Context = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
context.call_depth += 1;
@@ -133,7 +134,7 @@ pub fn method(self: *Caller, comptime T: type, func: anytype, info: v8.FunctionC
pub fn _method(self: *Caller, comptime T: type, func: anytype, info: v8.FunctionCallbackInfo, comptime opts: CallOpts) !void {
const F = @TypeOf(func);
var handle_scope: v8.HandleScope = undefined;
var handle_scope: js.HandleScope = undefined;
handle_scope.init(self.isolate);
defer handle_scope.deinit();
@@ -316,6 +317,7 @@ fn assertIsPageArg(comptime T: type, comptime F: type, index: comptime_int) void
fn handleError(self: *Caller, comptime T: type, comptime F: type, err: anyerror, info: anytype, comptime opts: CallOpts) void {
const isolate = self.isolate;
const v8_isolate = v8.Isolate{ .handle = isolate.handle };
if (comptime @import("builtin").mode == .Debug and @hasDecl(@TypeOf(info), "length")) {
if (log.enabled(.js, .warn)) {
@@ -324,21 +326,21 @@ fn handleError(self: *Caller, comptime T: type, comptime F: type, err: anyerror,
}
var js_err: ?v8.Value = switch (err) {
error.InvalidArgument => createTypeException(isolate, "invalid argument"),
error.OutOfMemory => js._createException(isolate, "out of memory"),
error.IllegalConstructor => js._createException(isolate, "Illegal Contructor"),
error.InvalidArgument => createTypeException(v8_isolate, "invalid argument"),
error.OutOfMemory => js._createException(v8_isolate, "out of memory"),
error.IllegalConstructor => js._createException(v8_isolate, "Illegal Contructor"),
else => blk: {
if (!comptime opts.dom_exception) {
break :blk null;
}
const DOMException = @import("../webapi/DOMException.zig");
const ex = DOMException.fromError(err) orelse break :blk null;
break :blk self.context.zigValueToJs(ex, .{}) catch js._createException(isolate, "internal error");
break :blk self.context.zigValueToJs(ex, .{}) catch js._createException(v8_isolate, "internal error");
},
};
if (js_err == null) {
js_err = js._createException(isolate, @errorName(err));
js_err = js._createException(v8_isolate, @errorName(err));
}
const js_exception = isolate.throwException(js_err.?);
info.getReturnValue().setValueHandle(js_exception.handle);

View File

@@ -43,10 +43,10 @@ const Context = @This();
id: usize,
page: *Page,
isolate: v8.Isolate,
isolate: js.Isolate,
// This context is a persistent object. The persistent needs to be recovered and reset.
v8_context: v8.Context,
handle_scope: ?v8.HandleScope,
handle_scope: ?js.HandleScope,
cpu_profiler: ?v8.CpuProfiler = null,
@@ -128,9 +128,10 @@ pub fn fromC(c_context: *const v8.C_Context) *Context {
return @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
}
pub fn fromIsolate(isolate: v8.Isolate) *Context {
const v8_context = isolate.getCurrentContext();
return @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
pub fn fromIsolate(isolate: js.Isolate) *Context {
const v8_context = v8.c.v8__Isolate__GetCurrentContext(isolate.handle).?;
const ctx = v8.Context{ .handle = v8_context };
return @ptrFromInt(ctx.getEmbedderData(1).castTo(v8.BigInt).getUint64());
}
pub fn setupGlobal(self: *Context) !void {
@@ -202,8 +203,9 @@ pub fn eval(self: *Context, src: []const u8, name: ?[]const u8) !void {
pub fn exec(self: *Context, src: []const u8, name: ?[]const u8) !js.Value {
const v8_context = self.v8_context;
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const scr = try compileScript(self.isolate, v8_context, src, name);
const scr = try compileScript(v8_isolate, v8_context, src, name);
const value = scr.run(v8_context) catch {
return error.ExecutionError;
@@ -213,6 +215,7 @@ pub fn exec(self: *Context, src: []const u8, name: ?[]const u8) !js.Value {
}
pub fn module(self: *Context, comptime want_result: bool, src: []const u8, url: []const u8, cacheable: bool) !(if (want_result) ModuleEntry else void) {
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const mod, const owned_url = blk: {
const arena = self.arena;
@@ -231,13 +234,13 @@ pub fn module(self: *Context, comptime want_result: bool, src: []const u8, url:
}
const owned_url = try arena.dupeZ(u8, url);
const m = try compileModule(self.isolate, src, owned_url);
const m = try compileModule(v8_isolate, src, owned_url);
if (cacheable) {
// compileModule is synchronous - nothing can modify the cache during compilation
std.debug.assert(gop.value_ptr.module == null);
gop.value_ptr.module = PersistentModule.init(self.isolate, m);
gop.value_ptr.module = PersistentModule.init(v8_isolate, m);
if (!gop.found_existing) {
gop.key_ptr.* = owned_url;
}
@@ -290,7 +293,7 @@ pub fn module(self: *Context, comptime want_result: bool, src: []const u8, url:
std.debug.assert(entry.module != null);
std.debug.assert(entry.module_promise == null);
entry.module_promise = PersistentPromise.init(self.isolate, .{ .handle = evaluated.handle });
entry.module_promise = PersistentPromise.init(v8_isolate, .{ .handle = evaluated.handle });
return if (comptime want_result) entry.* else {};
}
@@ -309,7 +312,8 @@ pub fn stringToFunction(self: *Context, str: []const u8) !js.Function {
const full = try std.fmt.allocPrintSentinel(self.call_arena, "(function(e) {{ {s}{s} }})", .{ normalized, extra }, 0);
const v8_context = self.v8_context;
const script = try compileScript(self.isolate, v8_context, full, null);
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const script = try compileScript(v8_isolate, v8_context, full, null);
const js_value = script.run(v8_context) catch {
return error.ExecutionError;
};
@@ -391,17 +395,19 @@ pub fn createFunction(self: *Context, js_value: v8.Value) !js.Function {
}
pub fn throw(self: *Context, err: []const u8) js.Exception {
const js_value = js._createException(self.isolate, err);
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const js_value = js._createException(v8_isolate, err);
return self.createException(js_value);
}
pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOpts) !v8.Value {
const isolate = self.isolate;
const v8_isolate = v8.Isolate{ .handle = isolate.handle };
// Check if it's a "simple" type. This is extracted so that it can be
// reused by other parts of the code. "simple" types only require an
// isolate to create (specifically, they don't our templates array)
if (js.simpleZigValueToJs(isolate, value, false, opts.null_as_undefined)) |js_value| {
if (js.simpleZigValueToJs(v8_isolate, value, false, opts.null_as_undefined)) |js_value| {
return js_value;
}
@@ -414,7 +420,7 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp
unreachable;
},
.array => {
var js_arr = v8.Array.init(isolate, value.len);
var js_arr = v8.Array.init(v8_isolate, value.len);
var js_obj = js_arr.castTo(v8.Object);
for (value, 0..) |v, i| {
const js_val = try self.zigValueToJs(v, opts);
@@ -453,7 +459,7 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp
// have handled it
unreachable;
}
var js_arr = v8.Array.init(isolate, @intCast(value.len));
var js_arr = v8.Array.init(v8_isolate, @intCast(value.len));
var js_obj = js_arr.castTo(v8.Object);
for (value, 0..) |v, i| {
@@ -504,7 +510,7 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp
if (s.is_tuple) {
// return the tuple struct as an array
var js_arr = v8.Array.init(isolate, @intCast(s.fields.len));
var js_arr = v8.Array.init(v8_isolate, @intCast(s.fields.len));
var js_obj = js_arr.castTo(v8.Object);
inline for (s.fields, 0..) |f, i| {
const js_val = try self.zigValueToJs(@field(value, f.name), opts);
@@ -516,10 +522,10 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp
}
// return the struct as a JS object
const js_obj = v8.Object.init(isolate);
const js_obj = v8.Object.init(v8_isolate);
inline for (s.fields) |f| {
const js_val = try self.zigValueToJs(@field(value, f.name), opts);
const key = v8.String.initUtf8(isolate, f.name);
const key = v8.String.initUtf8(v8_isolate, f.name);
if (!js_obj.setValue(v8_context, key, js_val)) {
return error.CreateObjectFailure;
}
@@ -590,6 +596,7 @@ pub fn mapZigInstanceToJs(self: *Context, js_obj_: ?v8.Object, value: anytype) !
}
const isolate = self.isolate;
const v8_isolate = v8.Isolate{ .handle = isolate.handle };
const JsApi = bridge.Struct(ptr.child).JsApi;
// Sometimes we're creating a new v8.Object, like when
@@ -619,7 +626,7 @@ pub fn mapZigInstanceToJs(self: *Context, js_obj_: ?v8.Object, value: anytype) !
// Skip setting internal field for the global object (Window)
// Window accessors get the instance from context.page.window instead
if (resolved.class_id != @import("../webapi/Window.zig").JsApi.Meta.class_id) {
js_obj.setInternalField(0, v8.External.init(isolate, tao));
js_obj.setInternalField(0, v8.External.init(v8_isolate, tao));
}
} else {
// If the struct is empty, we don't need to do all
@@ -629,7 +636,7 @@ pub fn mapZigInstanceToJs(self: *Context, js_obj_: ?v8.Object, value: anytype) !
// the type is empty and can create an empty instance.
}
const js_persistent = PersistentObject.init(isolate, js_obj);
const js_persistent = PersistentObject.init(v8_isolate, js_obj);
gop.value_ptr.* = js_persistent;
return js_persistent;
},
@@ -638,6 +645,7 @@ pub fn mapZigInstanceToJs(self: *Context, js_obj_: ?v8.Object, value: anytype) !
}
pub fn jsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !T {
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
switch (@typeInfo(T)) {
.optional => |o| {
// If type type is a ?js.Value or a ?js.Object, then we want to pass
@@ -680,7 +688,7 @@ pub fn jsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !T {
else => {},
},
.int => return jsIntToZig(T, js_value, self.v8_context),
.bool => return js_value.toBool(self.isolate),
.bool => return js_value.toBool(v8_isolate),
.pointer => |ptr| switch (ptr.size) {
.one => {
if (!js_value.isObject()) {
@@ -845,11 +853,12 @@ fn jsValueToStruct(self: *Context, comptime T: type, js_value: v8.Value) !?T {
const js_obj = js_value.castTo(v8.Object);
const v8_context = self.v8_context;
const isolate = self.isolate;
const v8_isolate = v8.Isolate{ .handle = isolate.handle };
var value: T = undefined;
inline for (@typeInfo(T).@"struct".fields) |field| {
const name = field.name;
const key = v8.String.initUtf8(isolate, name);
const key = v8.String.initUtf8(v8_isolate, name);
if (js_obj.has(v8_context, key.toValue())) {
@field(value, name) = try self.jsValueToZig(field.type, try js_obj.getValue(v8_context, key));
} else if (@typeInfo(field.type) == .optional) {
@@ -1009,7 +1018,8 @@ pub fn valueToString(self: *const Context, js_val: v8.Value, opts: valueToString
const allocator = opts.allocator orelse self.call_arena;
if (js_val.isSymbol()) {
const js_sym = v8.Symbol{ .handle = js_val.handle };
const js_sym_desc = js_sym.getDescription(self.isolate);
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const js_sym_desc = js_sym.getDescription(v8_isolate);
return self.valueToString(js_sym_desc, .{});
}
const str = try js_val.toString(self.v8_context);
@@ -1020,7 +1030,8 @@ pub fn valueToStringZ(self: *const Context, js_val: v8.Value, opts: valueToStrin
const allocator = opts.allocator orelse self.call_arena;
if (js_val.isSymbol()) {
const js_sym = v8.Symbol{ .handle = js_val.handle };
const js_sym_desc = js_sym.getDescription(self.isolate);
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const js_sym_desc = js_sym.getDescription(v8_isolate);
return self.valueToStringZ(js_sym_desc, .{});
}
const str = try js_val.toString(self.v8_context);
@@ -1032,18 +1043,20 @@ const JsStringToZigOpts = struct {
};
pub fn jsStringToZig(self: *const Context, str: v8.String, opts: JsStringToZigOpts) ![]u8 {
const allocator = opts.allocator orelse self.call_arena;
const len = str.lenUtf8(self.isolate);
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const len = str.lenUtf8(v8_isolate);
const buf = try allocator.alloc(u8, len);
const n = str.writeUtf8(self.isolate, buf);
const n = str.writeUtf8(v8_isolate, buf);
std.debug.assert(n == len);
return buf;
}
pub fn jsStringToZigZ(self: *const Context, str: v8.String, opts: JsStringToZigOpts) ![:0]u8 {
const allocator = opts.allocator orelse self.call_arena;
const len = str.lenUtf8(self.isolate);
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const len = str.lenUtf8(v8_isolate);
const buf = try allocator.allocSentinel(u8, len, 0);
const n = str.writeUtf8(self.isolate, buf);
const n = str.writeUtf8(v8_isolate, buf);
std.debug.assert(n == len);
return buf;
}
@@ -1073,13 +1086,14 @@ fn _debugValue(self: *const Context, js_val: v8.Value, seen: *std.AutoHashMapUnm
return writer.writeAll("false");
}
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
if (js_val.isSymbol()) {
const js_sym = v8.Symbol{ .handle = js_val.handle };
const js_sym_desc = js_sym.getDescription(self.isolate);
const js_sym_desc = js_sym.getDescription(v8_isolate);
const js_sym_str = try self.valueToString(js_sym_desc, .{});
return writer.print("{s} (symbol)", .{js_sym_str});
}
const js_type = try self.jsStringToZig(try js_val.typeOf(self.isolate), .{});
const js_type = try self.jsStringToZig(try js_val.typeOf(v8_isolate), .{});
const js_val_str = try self.valueToString(js_val, .{});
if (js_val_str.len > 2000) {
try writer.writeAll(js_val_str[0..2000]);
@@ -1142,20 +1156,21 @@ pub fn stackTrace(self: *const Context) !?[]const u8 {
}
const isolate = self.isolate;
const v8_isolate = v8.Isolate{ .handle = isolate.handle };
const separator = log.separator();
var buf: std.ArrayListUnmanaged(u8) = .empty;
var writer = buf.writer(self.call_arena);
const stack_trace = v8.StackTrace.getCurrentStackTrace(isolate, 30);
const stack_trace = v8.StackTrace.getCurrentStackTrace(v8_isolate, 30);
const frame_count = stack_trace.getFrameCount();
if (v8.StackTrace.getCurrentScriptNameOrSourceUrl(isolate)) |script| {
if (v8.StackTrace.getCurrentScriptNameOrSourceUrl(v8_isolate)) |script| {
try writer.print("{s}<{s}>", .{ separator, try self.jsStringToZig(script, .{}) });
}
for (0..frame_count) |i| {
const frame = stack_trace.getFrame(isolate, @intCast(i));
const frame = stack_trace.getFrame(v8_isolate, @intCast(i));
if (frame.getScriptName()) |name| {
const script = try self.jsStringToZig(name, .{});
try writer.print("{s}{s}:{d}", .{ separator, script, frame.getLineNumber() });
@@ -1214,7 +1229,8 @@ pub fn createPromiseResolver(self: *Context, comptime lifetime: PromiseResolverL
return .{ .context = self, .resolver = resolver };
}
const persisted = v8.Persistent(v8.PromiseResolver).init(self.isolate, resolver);
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const persisted = v8.Persistent(v8.PromiseResolver).init(v8_isolate, resolver);
if (comptime lifetime == .page) {
try self.persisted_promise_resolvers.append(self.arena, persisted);
@@ -1305,7 +1321,8 @@ pub fn metaObjectCallback(c_context: ?*v8.C_Context, c_module: ?*v8.C_Module, c_
return;
};
const js_key = v8.String.initUtf8(self.isolate, "url");
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const js_key = v8.String.initUtf8(v8_isolate, "url");
const js_value = try self.zigValueToJs(url, .{});
const res = meta.defineOwnProperty(self.v8_context, js_key.toName(), js_value, 0) orelse false;
if (!res) {
@@ -1337,9 +1354,10 @@ fn _resolveModuleCallback(self: *Context, referrer: v8.Module, specifier: [:0]co
try_catch.init(self);
defer try_catch.deinit();
const mod = try compileModule(self.isolate, source.src(), normalized_specifier);
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const mod = try compileModule(v8_isolate, source.src(), normalized_specifier);
try self.postCompileModule(mod, normalized_specifier);
entry.module = PersistentModule.init(self.isolate, mod);
entry.module = PersistentModule.init(v8_isolate, mod);
return entry.module.?.castToModule().handle;
}
@@ -1357,6 +1375,7 @@ const DynamicModuleResolveState = struct {
fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []const u8) !v8.Promise {
const isolate = self.isolate;
const v8_isolate = v8.Isolate{ .handle = isolate.handle };
const gop = try self.module_cache.getOrPut(self.arena, specifier);
if (gop.found_existing and gop.value_ptr.resolver_promise != null) {
// This is easy, there's already something responsible
@@ -1365,7 +1384,7 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c
return gop.value_ptr.resolver_promise.?.castToPromise();
}
const persistent_resolver = v8.Persistent(v8.PromiseResolver).init(isolate, v8.PromiseResolver.init(self.v8_context));
const persistent_resolver = v8.Persistent(v8.PromiseResolver).init(v8_isolate, v8.PromiseResolver.init(self.v8_context));
try self.persisted_promise_resolvers.append(self.arena, persistent_resolver);
var resolver = persistent_resolver.castToPromiseResolver();
@@ -1379,7 +1398,7 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c
.resolver = persistent_resolver,
};
const persisted_promise = PersistentPromise.init(self.isolate, resolver.getPromise());
const persisted_promise = PersistentPromise.init(v8_isolate, resolver.getPromise());
const promise = persisted_promise.castToPromise();
if (!gop.found_existing) {
@@ -1397,7 +1416,7 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c
// Next, we need to actually load it.
self.script_manager.?.getAsyncImport(specifier, dynamicModuleSourceCallback, state, referrer) catch |err| {
const error_msg = v8.String.initUtf8(isolate, @errorName(err));
const error_msg = v8.String.initUtf8(v8_isolate, @errorName(err));
_ = resolver.reject(self.v8_context, error_msg.toValue());
};
@@ -1425,21 +1444,21 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c
if (status == .kEvaluated or status == .kEvaluating) {
// Module was already evaluated (shouldn't normally happen, but handle it).
// Create a pre-resolved promise with the module namespace.
const persisted_module_resolver = v8.Persistent(v8.PromiseResolver).init(isolate, v8.PromiseResolver.init(self.v8_context));
const persisted_module_resolver = v8.Persistent(v8.PromiseResolver).init(v8_isolate, v8.PromiseResolver.init(self.v8_context));
try self.persisted_promise_resolvers.append(self.arena, persisted_module_resolver);
var module_resolver = persisted_module_resolver.castToPromiseResolver();
_ = module_resolver.resolve(self.v8_context, mod.getModuleNamespace());
gop.value_ptr.module_promise = PersistentPromise.init(self.isolate, module_resolver.getPromise());
gop.value_ptr.module_promise = PersistentPromise.init(v8_isolate, module_resolver.getPromise());
} else {
// the module was loaded, but not evaluated, we _have_ to evaluate it now
const evaluated = mod.evaluate(self.v8_context) catch {
std.debug.assert(status == .kErrored);
const error_msg = v8.String.initUtf8(isolate, "Module evaluation failed");
const error_msg = v8.String.initUtf8(v8_isolate, "Module evaluation failed");
_ = resolver.reject(self.v8_context, error_msg.toValue());
return promise;
};
std.debug.assert(evaluated.isPromise());
gop.value_ptr.module_promise = PersistentPromise.init(self.isolate, .{ .handle = evaluated.handle });
gop.value_ptr.module_promise = PersistentPromise.init(v8_isolate, .{ .handle = evaluated.handle });
}
}
@@ -1459,7 +1478,8 @@ fn dynamicModuleSourceCallback(ctx: *anyopaque, module_source_: anyerror!ScriptM
var self = state.context;
var ms = module_source_ catch |err| {
const error_msg = v8.String.initUtf8(self.isolate, @errorName(err));
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const error_msg = v8.String.initUtf8(v8_isolate, @errorName(err));
_ = state.resolver.castToPromiseResolver().reject(self.v8_context, error_msg.toValue());
return;
};
@@ -1479,7 +1499,8 @@ fn dynamicModuleSourceCallback(ctx: *anyopaque, module_source_: anyerror!ScriptM
.stack = try_catch.stack(self.call_arena) catch null,
.line = try_catch.sourceLineNumber() orelse 0,
});
const error_msg = v8.String.initUtf8(self.isolate, ex);
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const error_msg = v8.String.initUtf8(v8_isolate, ex);
_ = state.resolver.castToPromiseResolver().reject(self.v8_context, error_msg.toValue());
return;
};
@@ -1492,7 +1513,8 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul
defer self.runMicrotasks();
const ctx = self.v8_context;
const isolate = self.isolate;
const external = v8.External.init(self.isolate, @ptrCast(state));
const v8_isolate = v8.Isolate{ .handle = isolate.handle };
const external = v8.External.init(v8_isolate, @ptrCast(state));
// we can only be here if the module has been evaluated and if
// we have a resolve loading this asynchronously.
@@ -1551,7 +1573,7 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul
.err = err,
.specifier = state.specifier,
});
const error_msg = v8.String.initUtf8(isolate, "Failed to evaluate promise");
const error_msg = v8.String.initUtf8(v8_isolate, "Failed to evaluate promise");
_ = state.resolver.castToPromiseResolver().reject(ctx, error_msg.toValue());
};
}
@@ -1582,7 +1604,8 @@ pub fn typeTaggedAnyOpaque(comptime R: type, js_obj: v8.Object) !R {
// Normally, this would be an error. All JsObject that map to a Zig type
// are either `empty_with_no_proto` (handled above) or have an
// interalFieldCount. The only exception to that is the Window...
const isolate = js_obj.getIsolate();
const v8_isolate = js_obj.getIsolate();
const isolate = js.Isolate{ .handle = v8_isolate.handle };
const context = fromIsolate(isolate);
const Window = @import("../webapi/Window.zig");
@@ -2024,7 +2047,7 @@ pub fn queueSlotchangeDelivery(self: *Context) !void {
}
pub fn queueMicrotaskFunc(self: *Context, cb: js.Function) void {
self.isolate.enqueueMicrotaskFunc(.{ .handle = cb.handle });
self.isolate.enqueueMicrotaskFunc(cb);
}
// == Misc ==

View File

@@ -46,7 +46,7 @@ allocator: Allocator,
platform: *const Platform,
// the global isolate
isolate: v8.Isolate,
isolate: js.Isolate,
// just kept around because we need to free it on deinit
isolate_params: *v8.CreateParams,
@@ -67,28 +67,30 @@ pub fn init(allocator: Allocator, platform: *const Platform, snapshot: *Snapshot
params.external_references = &snapshot.external_references;
var isolate = v8.Isolate.init(params);
errdefer isolate.deinit();
var v8_isolate = v8.Isolate.init(params);
errdefer v8_isolate.deinit();
// This is the callback that runs whenever a module is dynamically imported.
isolate.setHostImportModuleDynamicallyCallback(Context.dynamicModuleCallback);
isolate.setPromiseRejectCallback(promiseRejectCallback);
isolate.setMicrotasksPolicy(v8.c.kExplicit);
v8_isolate.setHostImportModuleDynamicallyCallback(Context.dynamicModuleCallback);
v8_isolate.setPromiseRejectCallback(promiseRejectCallback);
v8_isolate.setMicrotasksPolicy(v8.c.kExplicit);
isolate.enter();
errdefer isolate.exit();
v8_isolate.enter();
errdefer v8_isolate.exit();
isolate.setHostInitializeImportMetaObjectCallback(Context.metaObjectCallback);
v8_isolate.setHostInitializeImportMetaObjectCallback(Context.metaObjectCallback);
const isolate = js.Isolate{ .handle = v8_isolate.handle };
// Allocate templates array dynamically to avoid comptime dependency on JsApis.len
const templates = try allocator.alloc(v8.FunctionTemplate, JsApis.len);
errdefer allocator.free(templates);
{
var temp_scope: v8.HandleScope = undefined;
v8.HandleScope.init(&temp_scope, isolate);
var temp_scope: js.HandleScope = undefined;
temp_scope.init(isolate);
defer temp_scope.deinit();
const context = v8.Context.init(isolate, null, null);
const context = v8.Context.init(v8_isolate, null, null);
context.enter();
defer context.exit();
@@ -97,7 +99,7 @@ pub fn init(allocator: Allocator, platform: *const Platform, snapshot: *Snapshot
JsApi.Meta.class_id = i;
const data = context.getDataFromSnapshotOnce(snapshot.data_start + i);
const function = v8.FunctionTemplate{ .handle = @ptrCast(data) };
templates[i] = v8.Persistent(v8.FunctionTemplate).init(isolate, function).castToFunctionTemplate();
templates[i] = v8.Persistent(v8.FunctionTemplate).init(v8_isolate, function).castToFunctionTemplate();
}
}
@@ -149,8 +151,8 @@ pub fn newExecutionWorld(self: *Env) !ExecutionWorld {
// `lowMemoryNotification` call on the isolate to encourage v8 to free
// any contexts which have been freed.
pub fn lowMemoryNotification(self: *Env) void {
var handle_scope: v8.HandleScope = undefined;
v8.HandleScope.init(&handle_scope, self.isolate);
var handle_scope: js.HandleScope = undefined;
handle_scope.init(self.isolate);
defer handle_scope.deinit();
self.isolate.lowMemoryNotification();
}
@@ -178,8 +180,9 @@ pub fn dumpMemoryStats(self: *Env) void {
fn promiseRejectCallback(v8_msg: v8.C_PromiseRejectMessage) callconv(.c) void {
const msg = v8.PromiseRejectMessage.initFromC(v8_msg);
const isolate = msg.getPromise().toObject().getIsolate();
const context = Context.fromIsolate(isolate);
const v8_isolate = msg.getPromise().toObject().getIsolate();
const js_isolate = js.Isolate{ .handle = v8_isolate.handle };
const context = Context.fromIsolate(js_isolate);
const value =
if (msg.getValue()) |v8_value|

View File

@@ -64,7 +64,7 @@ pub fn deinit(self: *ExecutionWorld) void {
}
// Only the top Context in the Main ExecutionWorld should hold a handle_scope.
// A v8.HandleScope is like an arena. Once created, any "Local" that
// A js.HandleScope is like an arena. Once created, any "Local" that
// v8 creates will be released (or at least, releasable by the v8 GC)
// when the handle_scope is freed.
// We also maintain our own "context_arena" which allows us to have
@@ -77,19 +77,20 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
const arena = self.context_arena.allocator();
var v8_context: v8.Context = blk: {
var temp_scope: v8.HandleScope = undefined;
v8.HandleScope.init(&temp_scope, isolate);
const v8_isolate = v8.Isolate{ .handle = isolate.handle };
var temp_scope: js.HandleScope = undefined;
temp_scope.init(isolate);
defer temp_scope.deinit();
// Creates a global template that inherits from Window.
const global_template = @import("Snapshot.zig").createGlobalTemplate(isolate, env.templates);
// Add the named property handler
global_template.setNamedProperty(v8.NamedPropertyHandlerConfiguration{
.getter = unknownPropertyCallback,
.flags = v8.PropertyHandlerFlags.NonMasking | v8.PropertyHandlerFlags.OnlyInterceptStrings,
}, null);
const context_local = v8.Context.init(isolate, global_template, null);
const v8_context = v8.Persistent(v8.Context).init(isolate, context_local).castToContext();
break :blk v8_context;
@@ -98,10 +99,10 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
// For a Page we only create one HandleScope, it is stored in the main World (enter==true). A page can have multple contexts, 1 for each World.
// The main Context that enters and holds the HandleScope should therefore always be created first. Following other worlds for this page
// like isolated Worlds, will thereby place their objects on the main page's HandleScope. Note: In the furure the number of context will multiply multiple frames support
var handle_scope: ?v8.HandleScope = null;
var handle_scope: ?js.HandleScope = null;
if (enter) {
handle_scope = @as(v8.HandleScope, undefined);
v8.HandleScope.init(&handle_scope.?, isolate);
handle_scope = @as(js.HandleScope, undefined);
handle_scope.?.init(isolate);
v8_context.enter();
}
errdefer if (enter) {
@@ -127,7 +128,8 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
var context = &self.context.?;
// Store a pointer to our context inside the v8 context so that, given
// a v8 context, we can get our context out
const data = isolate.initBigIntU64(@intCast(@intFromPtr(context)));
const v8_isolate = v8.Isolate{ .handle = isolate.handle };
const data = v8_isolate.initBigIntU64(@intCast(@intFromPtr(context)));
v8_context.setEmbedderData(1, data);
try context.setupGlobal();
@@ -156,8 +158,8 @@ pub fn resumeExecution(self: *const ExecutionWorld) void {
pub fn unknownPropertyCallback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
const context = Context.fromIsolate(info.getIsolate());
const maybe_property: ?[]u8 = context.valueToString(.{ .handle = c_name.? }, .{}) catch null;
const ignored = std.StaticStringMap(void).initComptime(.{
@@ -196,11 +198,13 @@ pub fn unknownPropertyCallback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C
return v8.Intercepted.Yes;
}
log.debug(.unknown_prop, "unknown global property", .{
.info = "but the property can exist in pure JS",
.stack = context.stackTrace() catch "???",
.property = prop,
});
if (comptime IS_DEBUG) {
log.debug(.unknown_prop, "unknown global property", .{
.info = "but the property can exist in pure JS",
.stack = context.stackTrace() catch "???",
.property = prop,
});
}
}
}

View File

@@ -180,7 +180,8 @@ pub fn src(self: *const Function) ![]const u8 {
pub fn getPropertyValue(self: *const Function, name: []const u8) !?js.Value {
const ctx = self.ctx;
const key = v8.String.initUtf8(ctx.isolate, name);
const v8_isolate = v8.Isolate{ .handle = ctx.isolate.handle };
const key = v8.String.initUtf8(v8_isolate, name);
const handle = v8.c.v8__Object__Get(self.handle, ctx.v8_context.handle, key.handle) orelse {
return error.JsException;
};

View File

@@ -0,0 +1,32 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("js.zig");
const v8 = js.v8;
const HandleScope = @This();
inner: v8.c.HandleScope,
pub fn init(self: *HandleScope, isolate: js.Isolate) void {
v8.c.v8__HandleScope__CONSTRUCT(&self.inner, isolate.handle);
}
pub fn deinit(self: *HandleScope) void {
v8.c.v8__HandleScope__DESTRUCT(&self.inner);
}

View File

@@ -0,0 +1,65 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("js.zig");
const v8 = js.v8;
const Isolate = @This();
handle: *v8.c.Isolate,
pub fn deinit(self: Isolate) void {
v8.c.v8__Isolate__Dispose(self.handle);
}
pub fn exit(self: Isolate) void {
v8.c.v8__Isolate__Exit(self.handle);
}
pub fn performMicrotasksCheckpoint(self: Isolate) void {
v8.c.v8__Isolate__PerformMicrotaskCheckpoint(self.handle);
}
pub fn enqueueMicrotask(self: Isolate, callback: anytype, data: anytype) void {
const v8_isolate = v8.Isolate{ .handle = self.handle };
v8_isolate.enqueueMicrotask(callback, data);
}
pub fn enqueueMicrotaskFunc(self: Isolate, function: js.Function) void {
v8.c.v8__Isolate__EnqueueMicrotaskFunc(self.handle, function.handle);
}
pub fn lowMemoryNotification(self: Isolate) void {
v8.c.v8__Isolate__LowMemoryNotification(self.handle);
}
pub fn getHeapStatistics(self: Isolate) v8.c.HeapStatistics {
var res: v8.c.HeapStatistics = undefined;
v8.c.v8__Isolate__GetHeapStatistics(self.handle, &res);
return res;
}
pub fn throwException(self: Isolate, value: anytype) v8.Value {
const handle = switch (@TypeOf(value)) {
v8.Value => value.handle,
else => @compileError("Unsupported type for throwException"),
};
return .{
.handle = v8.c.v8__Isolate__ThrowException(self.handle, handle).?,
};
}

View File

@@ -85,7 +85,8 @@ pub fn toBool(self: Value) bool {
}
pub fn fromJson(ctx: *js.Context, json: []const u8) !Value {
const json_string = v8.String.initUtf8(ctx.isolate, json);
const v8_isolate = v8.Isolate{ .handle = ctx.isolate.handle };
const json_string = v8.String.initUtf8(v8_isolate, json);
const value = try v8.Json.parse(ctx.v8_context, json_string);
return .{ .ctx = ctx, .handle = value.handle };
}

View File

@@ -28,6 +28,8 @@ pub const Context = @import("Context.zig");
pub const Inspector = @import("Inspector.zig");
pub const Snapshot = @import("Snapshot.zig");
pub const Platform = @import("Platform.zig");
pub const Isolate = @import("Isolate.zig");
pub const HandleScope = @import("HandleScope.zig");
pub const Value = @import("Value.zig");
pub const Array = @import("Array.zig");