diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig index 8e60dab7..96bbae56 100644 --- a/src/browser/js/Caller.zig +++ b/src/browser/js/Caller.zig @@ -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); diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index e9ad00be..1864ee94 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -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 == diff --git a/src/browser/js/Env.zig b/src/browser/js/Env.zig index c025234d..53339348 100644 --- a/src/browser/js/Env.zig +++ b/src/browser/js/Env.zig @@ -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| diff --git a/src/browser/js/ExecutionWorld.zig b/src/browser/js/ExecutionWorld.zig index 0dccd76e..9becd216 100644 --- a/src/browser/js/ExecutionWorld.zig +++ b/src/browser/js/ExecutionWorld.zig @@ -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, + }); + } } } diff --git a/src/browser/js/Function.zig b/src/browser/js/Function.zig index 06aa68ba..f0b52a58 100644 --- a/src/browser/js/Function.zig +++ b/src/browser/js/Function.zig @@ -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; }; diff --git a/src/browser/js/HandleScope.zig b/src/browser/js/HandleScope.zig new file mode 100644 index 00000000..feb76534 --- /dev/null +++ b/src/browser/js/HandleScope.zig @@ -0,0 +1,32 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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); +} diff --git a/src/browser/js/Isolate.zig b/src/browser/js/Isolate.zig new file mode 100644 index 00000000..a2d865f1 --- /dev/null +++ b/src/browser/js/Isolate.zig @@ -0,0 +1,65 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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).?, + }; +} diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig index 39ca9bde..5cdc69bc 100644 --- a/src/browser/js/Value.zig +++ b/src/browser/js/Value.zig @@ -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 }; } diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index 733768c9..ad0c7710 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -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");