From bc11a48e6b56b726c7f213eb56b3a9f58634dc77 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 1 Jan 2026 16:26:18 +0800 Subject: [PATCH] migrate most cases, merge Caller into bridge --- src/browser/js/Context.zig | 314 ++++++++-------- src/browser/js/Env.zig | 63 ++-- src/browser/js/ExecutionWorld.zig | 86 +++-- src/browser/js/Function.zig | 14 +- src/browser/js/Inspector.zig | 5 +- src/browser/js/Isolate.zig | 62 +++- src/browser/js/Object.zig | 56 +-- src/browser/js/Snapshot.zig | 5 +- src/browser/js/TryCatch.zig | 4 +- src/browser/js/Value.zig | 158 +++++++- src/browser/js/bridge.zig | 586 ++++++++++++++++++++++++++++-- src/browser/js/js.zig | 75 ++-- 12 files changed, 1079 insertions(+), 349 deletions(-) diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 5f19b101..c6aadc9a 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -25,7 +25,7 @@ const js = @import("js.zig"); const v8 = js.v8; const bridge = @import("bridge.zig"); -const Caller = @import("Caller.zig"); +const Caller = bridge.Caller; const Page = @import("../Page.zig"); const ScriptManager = @import("../ScriptManager.zig"); @@ -52,7 +52,7 @@ handle_scope: ?js.HandleScope, cpu_profiler: ?v8.CpuProfiler = null, // references Env.templates -templates: []v8.FunctionTemplate, +templates: []*const v8.c.FunctionTemplate, // Arena for the lifetime of the context arena: Allocator, @@ -88,9 +88,6 @@ global_objects: std.ArrayList(js.Global(js.Object)) = .empty, global_functions: std.ArrayList(js.Global(js.Function)) = .empty, global_promise_resolvers: std.ArrayList(js.Global(js.PromiseResolver)) = .empty, -// Some Zig types have code to execute to cleanup -destructor_callbacks: std.ArrayListUnmanaged(DestructorCallback) = .empty, - // Our module cache: normalized module specifier => module. module_cache: std.StringHashMapUnmanaged(ModuleEntry) = .empty, @@ -138,21 +135,10 @@ pub fn fromIsolate(isolate: js.Isolate) *Context { pub fn setupGlobal(self: *Context) !void { const global = v8.c.v8__Context__Global(self.handle).?; const v8_obj = v8.Object{ .handle = global }; - _ = try self.mapZigInstanceToJs(v8_obj, self.page.window); + _ = try self.mapZigInstanceToJs(v8_obj.handle, self.page.window); } pub fn deinit(self: *Context) void { - { - // reverse order, as this has more chance of respecting any - // dependencies objects might have with each other. - const items = self.destructor_callbacks.items; - var i = items.len; - while (i > 0) { - i -= 1; - items[i].destructor(); - } - } - { var it = self.identity_map.valueIterator(); while (it.next()) |p| { @@ -233,7 +219,7 @@ 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(v8_isolate, src, owned_url); + const m = try compileModule(self.isolate, src, owned_url); if (cacheable) { // compileModule is synchronous - nothing can modify the cache during compilation @@ -261,9 +247,10 @@ pub fn module(self: *Context, comptime want_result: bool, src: []const u8, url: // Some module-loading errors aren't handled by TryCatch. We need to // get the error from the module itself. + const v8_exception = mod.getException(); log.warn(.js, "evaluate module", .{ .specifier = owned_url, - .message = self.valueToString(mod.getException(), .{}) catch "???", + .message = self.valueToString(js.Value{ .ctx = self, .handle = v8_exception.handle }, .{}) catch "???", }); return error.EvaluationError; }; @@ -315,7 +302,7 @@ pub fn stringToFunction(self: *Context, str: []const u8) !js.Function { if (!js_value.isFunction()) { return error.StringFunctionError; } - return self.createFunction(.{ .handle = js_value.handle }); + return self.createFunction(js_value); } // After we compile a module, whether it's a top-level one, or a nested one, @@ -358,28 +345,14 @@ pub fn createArray(self: *Context, len: u32) js.Array { }; } -pub fn createException(self: *const Context, e: v8.Value) js.Exception { - return .{ - .inner = e, - .context = self, - }; -} - -pub fn createValue(self: *Context, value: v8.Value) js.Value { - return .{ - .ctx = self, - .handle = value.handle, - }; -} - -pub fn createObject(self: *Context, js_value: v8.Value) js.Object { +pub fn createObject(self: *Context, js_value: js.Value) js.Object { return .{ .ctx = self, .handle = @ptrCast(js_value.handle), }; } -pub fn createFunction(self: *Context, js_value: v8.Value) !js.Function { +pub fn createFunction(self: *Context, js_value: js.Value) !js.Function { // caller should have made sure this was a function if (comptime IS_DEBUG) { std.debug.assert(js_value.isFunction()); @@ -394,25 +367,27 @@ pub fn createFunction(self: *Context, js_value: v8.Value) !js.Function { pub fn newString(self: *Context, str: []const u8) js.String { return .{ .ctx = self, - .handle = self.isolate.newStringHandle(str), + .handle = self.isolate.createStringHandle(str), }; } pub fn throw(self: *Context, err: []const u8) js.Exception { - const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; - const js_value = js._createException(v8_isolate, err); - return self.createException(js_value); + const handle = self.isolate.createError(err); + return .{ + .ctx = self, + .handle = handle, + }; } -pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOpts) !v8.Value { +pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOpts) !js.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(v8_isolate, value, false, opts.null_as_undefined)) |js_value| { - return js_value; + if (js.simpleZigValueToJs(v8_isolate, value, false, opts.null_as_undefined)) |js_value_handle| { + return .{ .ctx = self, .handle = js_value_handle }; } const v8_context = v8.Context{ .handle = self.handle }; @@ -424,7 +399,7 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp unreachable; }, .array => { - var js_arr = v8.Array.init(v8_isolate, value.len); + var js_arr = isolate.initArray(value.len); var js_obj = js_arr.castTo(v8.Object); for (value, 0..) |v, i| { const js_val = try self.zigValueToJs(v, opts); @@ -432,14 +407,14 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp return error.FailedToCreateArray; } } - return js_obj.toValue(); + return .{ .ctx = self, .handle = @ptrCast(js_obj.handle) }; }, .pointer => |ptr| switch (ptr.size) { .one => { if (@typeInfo(ptr.child) == .@"struct" and @hasDecl(ptr.child, "JsApi")) { if (bridge.JsApiLookup.has(ptr.child.JsApi)) { const js_obj = try self.mapZigInstanceToJs(null, value); - return js_obj.toValue(); + return .{ .ctx = self, .handle = @ptrCast(js_obj.handle) }; } } @@ -463,16 +438,16 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp // have handled it unreachable; } - var js_arr = v8.Array.init(v8_isolate, @intCast(value.len)); + var js_arr = isolate.initArray(@intCast(value.len)); var js_obj = js_arr.castTo(v8.Object); for (value, 0..) |v, i| { const js_val = try self.zigValueToJs(v, opts); - if (js_obj.setValueAtIndex(v8_context, @intCast(i), js_val) == false) { + if (js_obj.setValueAtIndex(v8_context, @intCast(i), .{ .handle = js_val.handle }) == false) { return error.FailedToCreateArray; } } - return js_obj.toValue(); + return .{ .ctx = self, .handle = @ptrCast(js_obj.handle) }; }, else => {}, }, @@ -480,31 +455,31 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp if (@hasDecl(T, "JsApi")) { if (bridge.JsApiLookup.has(T.JsApi)) { const js_obj = try self.mapZigInstanceToJs(null, value); - return js_obj.toValue(); + return .{ .ctx = self, .handle = @ptrCast(js_obj.handle) }; } } if (T == js.Function) { // we're returning a callback - return .{ .handle = @ptrCast(value.handle) }; + return .{ .ctx = self, .handle = @ptrCast(value.handle) }; } if (T == js.Object) { // we're returning a v8.Object - return .{ .handle = @ptrCast(value.handle) }; + return .{ .ctx = self, .handle = @ptrCast(value.handle) }; } if (T == js.Value) { - return .{ .handle = value.handle }; + return value; } if (T == js.Promise) { // we're returning a js.Promise - return .{ .handle = @ptrCast(value.handle) }; + return .{ .ctx = self, .handle = @ptrCast(value.handle) }; } if (T == js.Exception) { - return isolate.throwException(value.inner); + return .{ .ctx = self, .handle = isolate.throwException(value.handle) }; } if (@hasDecl(T, "runtimeGenericWrap")) { @@ -514,31 +489,31 @@ 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(v8_isolate, @intCast(s.fields.len)); + var js_arr = isolate.initArray(@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); - if (js_obj.setValueAtIndex(v8_context, @intCast(i), js_val) == false) { + if (js_obj.setValueAtIndex(v8_context, @intCast(i), .{ .handle = js_val.handle }) == false) { return error.FailedToCreateArray; } } - return js_obj.toValue(); + return .{ .ctx = self, .handle = @ptrCast(js_obj.handle) }; } // return the struct as a JS object - const js_obj = v8.Object.init(v8_isolate); + const js_obj = isolate.initObject(); inline for (s.fields) |f| { const js_val = try self.zigValueToJs(@field(value, f.name), opts); - const key = v8.String.initUtf8(v8_isolate, f.name); - if (!js_obj.setValue(v8_context, key, js_val)) { + const key = isolate.initString(f.name); + if (!js_obj.setValue(v8_context, key, .{ .handle = js_val.handle })) { return error.CreateObjectFailure; } } - return js_obj.toValue(); + return .{ .ctx = self, .handle = @ptrCast(js_obj.handle) }; }, .@"union" => |un| { if (T == std.json.Value) { - return zigJsonToJs(isolate, v8_context, value); + return zigJsonToJs(self, value); } if (un.tag_type) |UnionTagType| { inline for (un.fields) |field| { @@ -577,7 +552,7 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp // 4 - Store our TaggedAnyOpaque into the persistent object // 5 - Update our identity_map (so that, if we return this same instance again, // we can just grab it from the identity_map) -pub fn mapZigInstanceToJs(self: *Context, js_obj_: ?v8.Object, value: anytype) !PersistentObject { +pub fn mapZigInstanceToJs(self: *Context, js_obj_handle: ?*const v8.c.Object, value: anytype) !PersistentObject { const v8_context = v8.Context{ .handle = self.handle }; const arena = self.arena; @@ -587,7 +562,7 @@ pub fn mapZigInstanceToJs(self: *Context, js_obj_: ?v8.Object, value: anytype) ! // Struct, has to be placed on the heap const heap = try arena.create(T); heap.* = value; - return self.mapZigInstanceToJs(js_obj_, heap); + return self.mapZigInstanceToJs(js_obj_handle, heap); }, .pointer => |ptr| { const resolved = resolveValue(value); @@ -610,9 +585,13 @@ pub fn mapZigInstanceToJs(self: *Context, js_obj_: ?v8.Object, value: anytype) ! // Sometimes though we already have the v8.Objct to bind to // for example, when we're executing a constructor, v8 has // already created the "this" object. - const js_obj = js_obj_ orelse blk: { - const template = self.templates[resolved.class_id]; - break :blk template.getInstanceTemplate().initInstance(v8_context); + const js_obj = if (js_obj_handle) |handle| + v8.Object{ .handle = handle } + else blk: { + const function_template_handle = self.templates[resolved.class_id]; + const object_template_handle = v8.c.v8__FunctionTemplate__InstanceTemplate(function_template_handle).?; + const object_handle = v8.c.v8__ObjectTemplate__NewInstance(object_template_handle, v8_context.handle).?; + break :blk v8.Object{ .handle = object_handle }; }; if (!@hasDecl(JsApi.Meta, "empty_with_no_proto")) { @@ -648,8 +627,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 }; +pub fn jsValueToZig(self: *Context, comptime T: type, js_value: js.Value) !T { const v8_context = v8.Context{ .handle = self.handle }; switch (@typeInfo(T)) { .optional => |o| { @@ -688,12 +666,12 @@ pub fn jsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !T { return try self.jsValueToZig(o.child, js_value); }, .float => |f| switch (f.bits) { - 0...32 => return js_value.toF32(v8_context), - 33...64 => return js_value.toF64(v8_context), + 0...32 => return js_value.toF32(), + 33...64 => return js_value.toF64(), else => {}, }, - .int => return jsIntToZig(T, js_value, v8_context), - .bool => return js_value.toBool(v8_isolate), + .int => return jsIntToZig(T, js_value), + .bool => return js_value.toBool(), .pointer => |ptr| switch (ptr.size) { .one => { if (!js_value.isObject()) { @@ -702,7 +680,7 @@ pub fn jsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !T { if (@hasDecl(ptr.child, "JsApi")) { std.debug.assert(bridge.JsApiLookup.has(ptr.child.JsApi)); const js_obj = js_value.castTo(v8.Object); - return typeTaggedAnyOpaque(*ptr.child, js_obj); + return typeTaggedAnyOpaque(*ptr.child, js_obj.handle); } }, .slice => { @@ -732,7 +710,8 @@ pub fn jsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !T { // to do this (V8::Array has an iterate method on it) const arr = try self.call_arena.alloc(ptr.child, js_arr.length()); for (arr, 0..) |*a, i| { - a.* = try self.jsValueToZig(ptr.child, try js_obj.getAtIndex(v8_context, @intCast(i))); + const v8_val = try js_obj.getAtIndex(v8_context, @intCast(i)); + a.* = try self.jsValueToZig(ptr.child, js.Value{ .ctx = self, .handle = v8_val.handle }); } return arr; }, @@ -812,7 +791,7 @@ pub fn jsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !T { // Extracted so that it can be used in both jsValueToZig and in // probeJsValueToZig. Avoids having to duplicate this logic when probing. -fn jsValueToStruct(self: *Context, comptime T: type, js_value: v8.Value) !?T { +fn jsValueToStruct(self: *Context, comptime T: type, js_value: js.Value) !?T { return switch (T) { js.Function => { if (!js_value.isFunction()) { @@ -856,14 +835,14 @@ fn jsValueToStruct(self: *Context, comptime T: type, js_value: v8.Value) !?T { const js_obj = js_value.castTo(v8.Object); const v8_context = v8.Context{ .handle = self.handle }; 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(v8_isolate, name); + const key = isolate.initString(name); if (js_obj.has(v8_context, key.toValue())) { - @field(value, name) = try self.jsValueToZig(field.type, try js_obj.getValue(v8_context, key)); + const v8_val = try js_obj.getValue(v8_context, key); + @field(value, name) = try self.jsValueToZig(field.type, js.Value{ .ctx = self, .handle = v8_val.handle }); } else if (@typeInfo(field.type) == .optional) { @field(value, name) = null; } else { @@ -877,7 +856,7 @@ fn jsValueToStruct(self: *Context, comptime T: type, js_value: v8.Value) !?T { }; } -fn jsValueToTypedArray(_: *Context, comptime T: type, js_value: v8.Value) !?[]T { +fn jsValueToTypedArray(_: *Context, comptime T: type, js_value: js.Value) !?[]T { var force_u8 = false; var array_buffer: ?v8.ArrayBuffer = null; var byte_len: usize = undefined; @@ -1017,61 +996,73 @@ fn resolveT(comptime T: type, value: *anyopaque) Resolved { const valueToStringOpts = struct { allocator: ?Allocator = null, }; -pub fn valueToString(self: *const Context, js_val: v8.Value, opts: valueToStringOpts) ![]u8 { +pub fn valueToString(self: *const Context, js_val: js.Value, opts: valueToStringOpts) ![]u8 { const allocator = opts.allocator orelse self.call_arena; if (js_val.isSymbol()) { const js_sym = v8.Symbol{ .handle = js_val.handle }; const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; const js_sym_desc = js_sym.getDescription(v8_isolate); - return self.valueToString(js_sym_desc, .{}); + return self.valueToString(js.Value{ .ctx = self, .handle = js_sym_desc.handle }, .{}); } - const v8_context = v8.Context{ .handle = self.handle }; - const str = try js_val.toString(v8_context); + const str_handle = v8.c.v8__Value__ToString(js_val.handle, self.handle) orelse { + return error.JsException; + }; + const str = v8.String{ .handle = str_handle }; return self.jsStringToZig(str, .{ .allocator = allocator }); } -pub fn valueToStringZ(self: *const Context, js_val: v8.Value, opts: valueToStringOpts) ![:0]u8 { +pub fn valueToStringZ(self: *const Context, js_val: js.Value, opts: valueToStringOpts) ![:0]u8 { const allocator = opts.allocator orelse self.call_arena; if (js_val.isSymbol()) { const js_sym = v8.Symbol{ .handle = js_val.handle }; const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; const js_sym_desc = js_sym.getDescription(v8_isolate); - return self.valueToStringZ(js_sym_desc, .{}); + return self.valueToStringZ(js.Value{ .ctx = self, .handle = js_sym_desc.handle }, .{}); } - const v8_context = v8.Context{ .handle = self.handle }; - const str = try js_val.toString(v8_context); + const str_handle = v8.c.v8__Value__ToString(js_val.handle, self.handle) orelse { + return error.JsException; + }; + const str = v8.String{ .handle = str_handle }; return self.jsStringToZigZ(str, .{ .allocator = allocator }); } const JsStringToZigOpts = struct { allocator: ?Allocator = null, }; -pub fn jsStringToZig(self: *const Context, str: v8.String, opts: JsStringToZigOpts) ![]u8 { +pub fn jsStringToZig(self: *const Context, str: anytype, opts: JsStringToZigOpts) ![]u8 { const allocator = opts.allocator orelse self.call_arena; + const T = @TypeOf(str); + const handle = if (T == js.String or T == v8.String) str.handle else str; + const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; - const len = str.lenUtf8(v8_isolate); + const v8_str = v8.String{ .handle = handle }; + const len = v8_str.lenUtf8(v8_isolate); const buf = try allocator.alloc(u8, len); - const n = str.writeUtf8(v8_isolate, buf); + const n = v8_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 { +pub fn jsStringToZigZ(self: *const Context, str: anytype, opts: JsStringToZigOpts) ![:0]u8 { const allocator = opts.allocator orelse self.call_arena; + const T = @TypeOf(str); + const handle = if (T == js.String or T == v8.String) str.handle else str; + const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; - const len = str.lenUtf8(v8_isolate); + const v8_str = v8.String{ .handle = handle }; + const len = v8_str.lenUtf8(v8_isolate); const buf = try allocator.allocSentinel(u8, len, 0); - const n = str.writeUtf8(v8_isolate, buf); + const n = v8_str.writeUtf8(v8_isolate, buf); std.debug.assert(n == len); return buf; } -pub fn debugValue(self: *const Context, js_val: v8.Value, writer: *std.Io.Writer) !void { +pub fn debugValue(self: *const Context, js_val: js.Value, writer: *std.Io.Writer) !void { var seen: std.AutoHashMapUnmanaged(u32, void) = .empty; return _debugValue(self, js_val, &seen, 0, writer) catch error.WriteFailed; } -fn _debugValue(self: *const Context, js_val: v8.Value, seen: *std.AutoHashMapUnmanaged(u32, void), depth: usize, writer: *std.Io.Writer) !void { +fn _debugValue(self: *const Context, js_val: js.Value, seen: *std.AutoHashMapUnmanaged(u32, void), depth: usize, writer: *std.Io.Writer) !void { if (js_val.isNull()) { // I think null can sometimes appear as an object, so check this and // handle it first. @@ -1095,10 +1086,10 @@ fn _debugValue(self: *const Context, js_val: v8.Value, seen: *std.AutoHashMapUnm if (js_val.isSymbol()) { const js_sym = v8.Symbol{ .handle = js_val.handle }; const js_sym_desc = js_sym.getDescription(v8_isolate); - const js_sym_str = try self.valueToString(js_sym_desc, .{}); + const js_sym_str = try self.valueToString(js.Value{ .ctx = self, .handle = js_sym_desc.handle }, .{}); return writer.print("{s} (symbol)", .{js_sym_str}); } - const js_type = try self.jsStringToZig(try js_val.typeOf(v8_isolate), .{}); + const js_type = try self.jsStringToZig(js_val.typeOf(), .{}); const js_val_str = try self.valueToString(js_val, .{}); if (js_val_str.len > 2000) { try writer.writeAll(js_val_str[0..2000]); @@ -1144,11 +1135,12 @@ fn _debugValue(self: *const Context, js_val: v8.Value, seen: *std.AutoHashMapUnm try writer.writeByte('\n'); } const field_name = try names_obj.getAtIndex(v8_context, @intCast(i)); - const name = try self.valueToString(field_name, .{}); + const name = try self.valueToString(js.Value{ .ctx = self, .handle = field_name.handle }, .{}); try writer.splatByteAll(' ', depth); try writer.writeAll(name); try writer.writeAll(": "); - try self._debugValue(try js_obj.getValue(v8_context, field_name), seen, depth + 1, writer); + const field_val = try js_obj.getValue(v8_context, field_name); + try self._debugValue(js.Value{ .ctx = self, .handle = field_val.handle }, seen, depth + 1, writer); if (i != len - 1) { try writer.writeByte('\n'); } @@ -1220,7 +1212,7 @@ fn resolveModuleCallback( const self = fromC(c_context.?); - const specifier = self.jsStringToZigZ(.{ .handle = c_specifier.? }, .{}) catch |err| { + const specifier = self.jsStringToZigZ(c_specifier.?, .{}) catch |err| { log.err(.js, "resolve module", .{ .err = err }); return null; }; @@ -1247,12 +1239,12 @@ pub fn dynamicModuleCallback( const self = fromC(c_context.?); - const resource = self.jsStringToZigZ(.{ .handle = resource_name.? }, .{}) catch |err| { + const resource = self.jsStringToZigZ(resource_name.?, .{}) catch |err| { log.err(.app, "OOM", .{ .err = err, .src = "dynamicModuleCallback1" }); return @constCast((self.rejectPromise("Out of memory") catch return null).handle); }; - const specifier = self.jsStringToZigZ(.{ .handle = v8_specifier.? }, .{}) catch |err| { + const specifier = self.jsStringToZigZ(v8_specifier.?, .{}) catch |err| { log.err(.app, "OOM", .{ .err = err, .src = "dynamicModuleCallback2" }); return @constCast((self.rejectPromise("Out of memory") catch return null).handle); }; @@ -1278,7 +1270,7 @@ pub fn dynamicModuleCallback( pub fn metaObjectCallback(c_context: ?*v8.C_Context, c_module: ?*v8.C_Module, c_meta: ?*v8.C_Value) callconv(.c) void { const self = fromC(c_context.?); const m = js.Module{ .handle = c_module.? }; - const meta = v8.Object{ .handle = c_meta.? }; + const meta = js.Object{ .ctx = self, .handle = @ptrCast(c_meta.?) }; const url = self.module_identifier.get(m.getIdentityHash()) orelse { // Shouldn't be possible. @@ -1286,11 +1278,11 @@ pub fn metaObjectCallback(c_context: ?*v8.C_Context, c_module: ?*v8.C_Module, c_ return; }; - 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 v8_context = v8.Context{ .handle = self.handle }; - const res = meta.defineOwnProperty(v8_context, js_key.toName(), js_value, 0) orelse false; + const js_value = self.zigValueToJs(url, .{}) catch { + log.err(.js, "import meta", .{ .err = error.FailedToConvertUrl }); + return; + }; + const res = meta.defineOwnProperty("url", js_value, 0) orelse false; if (!res) { log.err(.js, "import meta", .{ .err = error.FailedToSet }); } @@ -1321,7 +1313,7 @@ fn _resolveModuleCallback(self: *Context, referrer: js.Module, specifier: [:0]co defer try_catch.deinit(); const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; - const mod = try compileModule(v8_isolate, source.src(), normalized_specifier); + const mod = try compileModule(self.isolate, source.src(), normalized_specifier); try self.postCompileModule(mod, normalized_specifier); const v8_module = v8.Module{ .handle = mod.handle }; entry.module = PersistentModule.init(v8_isolate, v8_module); @@ -1385,7 +1377,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(v8_isolate, @errorName(err)); + const error_msg = isolate.initString(@errorName(err)); _ = resolver.reject(v8_context, error_msg.toValue()); }; @@ -1423,7 +1415,7 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c // the module was loaded, but not evaluated, we _have_ to evaluate it now const evaluated = mod.evaluate(v8_context) catch { std.debug.assert(status == .kErrored); - const error_msg = v8.String.initUtf8(v8_isolate, "Module evaluation failed"); + const error_msg = isolate.initString("Module evaluation failed"); _ = resolver.reject(v8_context, error_msg.toValue()); const v8_promise = promise; return .{ .handle = v8_promise.handle }; @@ -1450,8 +1442,7 @@ fn dynamicModuleSourceCallback(ctx: *anyopaque, module_source_: anyerror!ScriptM var self = state.context; var ms = module_source_ catch |err| { - const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; - const error_msg = v8.String.initUtf8(v8_isolate, @errorName(err)); + const error_msg = self.isolate.initString(@errorName(err)); const v8_context = v8.Context{ .handle = self.handle }; _ = state.resolver.castToPromiseResolver().reject(v8_context, error_msg.toValue()); return; @@ -1472,9 +1463,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 v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; const v8_context = v8.Context{ .handle = self.handle }; - const error_msg = v8.String.initUtf8(v8_isolate, ex); + const error_msg = self.isolate.initString(ex); _ = state.resolver.castToPromiseResolver().reject(v8_context, error_msg.toValue()); return; }; @@ -1506,10 +1496,12 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul const then_callback = v8.Function.initWithData(ctx, struct { pub fn callback(raw_info: ?*const v8.c.FunctionCallbackInfo) callconv(.c) void { - var info = v8.FunctionCallbackInfo.initFromV8(raw_info); - var caller = Caller.init(info); + const callback_v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?; + var caller = Caller.init(callback_v8_isolate); defer caller.deinit(); + var info = v8.FunctionCallbackInfo.initFromV8(raw_info); + const s: *DynamicModuleResolveState = @ptrCast(@alignCast(info.getExternalValue())); if (s.context_id != caller.context.id) { @@ -1530,10 +1522,12 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul const catch_callback = v8.Function.initWithData(ctx, struct { pub fn callback(raw_info: ?*const v8.c.FunctionCallbackInfo) callconv(.c) void { - var info = v8.FunctionCallbackInfo.initFromV8(raw_info); - var caller = Caller.init(info); + const callback_v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?; + var caller = Caller.init(callback_v8_isolate); defer caller.deinit(); + var info = v8.FunctionCallbackInfo.initFromV8(raw_info); + const s: *DynamicModuleResolveState = @ptrCast(@alignCast(info.getExternalValue())); if (s.context_id != caller.context.id) { return; @@ -1549,7 +1543,7 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul .err = err, .specifier = state.specifier, }); - const error_msg = v8.String.initUtf8(v8_isolate, "Failed to evaluate promise"); + const error_msg = isolate.initString("Failed to evaluate promise"); _ = state.resolver.castToPromiseResolver().reject(ctx, error_msg.toValue()); }; } @@ -1558,7 +1552,9 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul // Reverses the mapZigInstanceToJs, making sure that our TaggedAnyOpaque // contains a ptr to the correct type. -pub fn typeTaggedAnyOpaque(comptime R: type, js_obj: v8.Object) !R { +pub fn typeTaggedAnyOpaque(comptime R: type, js_obj_handle: *const v8.c.Object) !R { + const js_obj = v8.Object{ .handle = js_obj_handle }; + const ti = @typeInfo(R); if (ti != .pointer) { @compileError("non-pointer Zig parameter type: " ++ @typeName(R)); @@ -1671,7 +1667,7 @@ fn ProbeResult(comptime T: type) type { invalid: void, }; } -fn probeJsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !ProbeResult(T) { +fn probeJsValueToZig(self: *Context, comptime T: type, js_value: js.Value) !ProbeResult(T) { switch (@typeInfo(T)) { .optional => |o| { if (js_value.isNullOrUndefined()) { @@ -1720,7 +1716,7 @@ fn probeJsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !Prob // of having a version of typeTaggedAnyOpaque which // returns a boolean or an optional, we rely on the // main implementation and just handle the error. - const attempt = typeTaggedAnyOpaque(*ptr.child, js_obj); + const attempt = typeTaggedAnyOpaque(*ptr.child, js_obj.handle); if (attempt) |value| { return .{ .value = value }; } else |_| { @@ -1788,7 +1784,8 @@ fn probeJsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !Prob // not tricky in this case either. const v8_context = v8.Context{ .handle = self.handle }; const js_obj = js_arr.castTo(v8.Object); - switch (try self.probeJsValueToZig(ptr.child, try js_obj.getAtIndex(v8_context, 0))) { + const v8_val = try js_obj.getAtIndex(v8_context, 0); + switch (try self.probeJsValueToZig(ptr.child, js.Value{ .ctx = self, .handle = v8_val.handle })) { .value, .ok => return .{ .ok = {} }, .compatible => return .{ .compatible = {} }, .coerce => return .{ .coerce = {} }, @@ -1841,25 +1838,25 @@ fn probeJsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !Prob return .{ .invalid = {} }; } -fn jsIntToZig(comptime T: type, js_value: v8.Value, v8_context: v8.Context) !T { +fn jsIntToZig(comptime T: type, js_value: js.Value) !T { const n = @typeInfo(T).int; switch (n.signedness) { .signed => switch (n.bits) { - 8 => return jsSignedIntToZig(i8, -128, 127, try js_value.toI32(v8_context)), - 16 => return jsSignedIntToZig(i16, -32_768, 32_767, try js_value.toI32(v8_context)), - 32 => return jsSignedIntToZig(i32, -2_147_483_648, 2_147_483_647, try js_value.toI32(v8_context)), + 8 => return jsSignedIntToZig(i8, -128, 127, try js_value.toI32()), + 16 => return jsSignedIntToZig(i16, -32_768, 32_767, try js_value.toI32()), + 32 => return jsSignedIntToZig(i32, -2_147_483_648, 2_147_483_647, try js_value.toI32()), 64 => { if (js_value.isBigInt()) { const v = js_value.castTo(v8.BigInt); return v.getInt64(); } - return jsSignedIntToZig(i64, -2_147_483_648, 2_147_483_647, try js_value.toI32(v8_context)); + return jsSignedIntToZig(i64, -2_147_483_648, 2_147_483_647, try js_value.toI32()); }, else => {}, }, .unsigned => switch (n.bits) { - 8 => return jsUnsignedIntToZig(u8, 255, try js_value.toU32(v8_context)), - 16 => return jsUnsignedIntToZig(u16, 65_535, try js_value.toU32(v8_context)), + 8 => return jsUnsignedIntToZig(u8, 255, try js_value.toU32()), + 16 => return jsUnsignedIntToZig(u16, 65_535, try js_value.toU32()), 32 => { if (js_value.isBigInt()) { const v = js_value.castTo(v8.BigInt); @@ -1869,14 +1866,14 @@ fn jsIntToZig(comptime T: type, js_value: v8.Value, v8_context: v8.Context) !T { } return error.InvalidArgument; } - return jsUnsignedIntToZig(u32, 4_294_967_295, try js_value.toU32(v8_context)); + return jsUnsignedIntToZig(u32, 4_294_967_295, try js_value.toU32()); }, 64 => { if (js_value.isBigInt()) { const v = js_value.castTo(v8.BigInt); return v.getUint64(); } - return jsUnsignedIntToZig(u64, 4_294_967_295, try js_value.toU32(v8_context)); + return jsUnsignedIntToZig(u64, 4_294_967_295, try js_value.toU32()); }, else => {}, }, @@ -1899,8 +1896,8 @@ fn jsUnsignedIntToZig(comptime T: type, max: comptime_int, maybe: u32) !T { } fn compileAndRun(self: *Context, src: []const u8, name: ?[]const u8) !js.Value { - const script_name = self.isolate.newStringHandle(name orelse "anonymous"); - const script_source = self.isolate.newStringHandle(src); + const script_name = self.isolate.createStringHandle(name orelse "anonymous"); + const script_source = self.isolate.createStringHandle(src); // Create ScriptOrigin var origin: v8.c.ScriptOrigin = undefined; @@ -1924,10 +1921,11 @@ fn compileAndRun(self: *Context, src: []const u8, name: ?[]const u8) !js.Value { return .{ .ctx = self, .handle = result }; } -fn compileModule(isolate: v8.Isolate, src: []const u8, name: []const u8) !js.Module { +fn compileModule(isolate: js.Isolate, src: []const u8, name: []const u8) !js.Module { // compile - const script_name = v8.String.initUtf8(isolate, name); - const script_source = v8.String.initUtf8(isolate, src); + const v8_isolate = v8.Isolate{ .handle = isolate.handle }; + const script_name = isolate.initString(name); + const script_source = isolate.initString(src); const origin = v8.ScriptOrigin.init( script_name.toValue(), @@ -1947,7 +1945,7 @@ fn compileModule(isolate: v8.Isolate, src: []const u8, name: []const u8) !js.Mod defer script_comp_source.deinit(); const v8_module = v8.ScriptCompiler.compileModule( - isolate, + v8_isolate, &script_comp_source, .kNoCompileOptions, .kNoCacheNoReason, @@ -1955,40 +1953,44 @@ fn compileModule(isolate: v8.Isolate, src: []const u8, name: []const u8) !js.Mod return .{ .handle = v8_module.handle }; } -fn zigJsonToJs(isolate: v8.Isolate, v8_context: v8.Context, value: std.json.Value) !v8.Value { +fn zigJsonToJs(self: *Context, value: std.json.Value) !js.Value { + const isolate = self.isolate; + const v8_isolate = v8.Isolate{ .handle = isolate.handle }; + const v8_context = v8.Context{ .handle = self.handle }; + switch (value) { - .bool => |v| return js.simpleZigValueToJs(isolate, v, true, false), - .float => |v| return js.simpleZigValueToJs(isolate, v, true, false), - .integer => |v| return js.simpleZigValueToJs(isolate, v, true, false), - .string => |v| return js.simpleZigValueToJs(isolate, v, true, false), - .null => return isolate.initNull().toValue(), + .bool => |v| return .{ .ctx = self, .handle = js.simpleZigValueToJs(v8_isolate, v, true, false) }, + .float => |v| return .{ .ctx = self, .handle = js.simpleZigValueToJs(v8_isolate, v, true, false) }, + .integer => |v| return .{ .ctx = self, .handle = js.simpleZigValueToJs(v8_isolate, v, true, false) }, + .string => |v| return .{ .ctx = self, .handle = js.simpleZigValueToJs(v8_isolate, v, true, false) }, + .null => return .{ .ctx = self, .handle = isolate.initNull() }, // TODO handle number_string. // It is used to represent too big numbers. .number_string => return error.TODO, .array => |v| { - const a = v8.Array.init(isolate, @intCast(v.items.len)); + const a = isolate.initArray(@intCast(v.items.len)); const obj = a.castTo(v8.Object); for (v.items, 0..) |array_value, i| { - const js_val = try zigJsonToJs(isolate, v8_context, array_value); - if (!obj.setValueAtIndex(v8_context, @intCast(i), js_val)) { + const js_val = try zigJsonToJs(self, array_value); + if (!obj.setValueAtIndex(v8_context, @intCast(i), .{ .handle = js_val.handle })) { return error.JSObjectSetValue; } } - return obj.toValue(); + return .{ .ctx = self, .handle = @ptrCast(obj.handle) }; }, .object => |v| { - var obj = v8.Object.init(isolate); + var obj = isolate.initObject(); var it = v.iterator(); while (it.next()) |kv| { - const js_key = v8.String.initUtf8(isolate, kv.key_ptr.*); - const js_val = try zigJsonToJs(isolate, v8_context, kv.value_ptr.*); - if (!obj.setValue(v8_context, js_key, js_val)) { + const js_key = isolate.initString(kv.key_ptr.*); + const js_val = try zigJsonToJs(self, kv.value_ptr.*); + if (!obj.setValue(v8_context, js_key, .{ .handle = js_val.handle })) { return error.JSObjectSetValue; } } - return obj.toValue(); + return .{ .ctx = self, .handle = @ptrCast(obj.handle) }; }, } } diff --git a/src/browser/js/Env.zig b/src/browser/js/Env.zig index 53339348..fcbff781 100644 --- a/src/browser/js/Env.zig +++ b/src/browser/js/Env.zig @@ -49,15 +49,18 @@ platform: *const Platform, isolate: js.Isolate, // just kept around because we need to free it on deinit -isolate_params: *v8.CreateParams, +isolate_params: *v8.c.CreateParams, context_id: usize, +// Global handles that need to be freed on deinit +globals: []v8.c.Global, + // Dynamic slice to avoid circular dependency on JsApis.len at comptime -templates: []v8.FunctionTemplate, +templates: []*const v8.c.FunctionTemplate, pub fn init(allocator: Allocator, platform: *const Platform, snapshot: *Snapshot) !Env { - var params = try allocator.create(v8.CreateParams); + var params = try allocator.create(v8.c.CreateParams); errdefer allocator.destroy(params); v8.c.v8__Isolate__CreateParams__CONSTRUCT(params); params.snapshot_blob = @ptrCast(&snapshot.startup_data); @@ -67,39 +70,42 @@ pub fn init(allocator: Allocator, platform: *const Platform, snapshot: *Snapshot params.external_references = &snapshot.external_references; - var v8_isolate = v8.Isolate.init(params); - errdefer v8_isolate.deinit(); + var isolate = js.Isolate.init(params); + errdefer isolate.deinit(); - // This is the callback that runs whenever a module is dynamically imported. - v8_isolate.setHostImportModuleDynamicallyCallback(Context.dynamicModuleCallback); - v8_isolate.setPromiseRejectCallback(promiseRejectCallback); - v8_isolate.setMicrotasksPolicy(v8.c.kExplicit); + v8.c.v8__Isolate__SetHostImportModuleDynamicallyCallback(isolate.handle, Context.dynamicModuleCallback); + v8.c.v8__Isolate__SetPromiseRejectCallback(isolate.handle, promiseRejectCallback); + v8.c.v8__Isolate__SetMicrotasksPolicy(isolate.handle, v8.c.kExplicit); - v8_isolate.enter(); - errdefer v8_isolate.exit(); + isolate.enter(); + errdefer isolate.exit(); - v8_isolate.setHostInitializeImportMetaObjectCallback(Context.metaObjectCallback); + v8.c.v8__Isolate__SetHostInitializeImportMetaObjectCallback(isolate.handle, Context.metaObjectCallback); - const isolate = js.Isolate{ .handle = v8_isolate.handle }; + // Allocate arrays dynamically to avoid comptime dependency on JsApis.len + const globals = try allocator.alloc(v8.c.Global, JsApis.len); + errdefer allocator.free(globals); - // Allocate templates array dynamically to avoid comptime dependency on JsApis.len - const templates = try allocator.alloc(v8.FunctionTemplate, JsApis.len); + const templates = try allocator.alloc(*const v8.c.FunctionTemplate, JsApis.len); errdefer allocator.free(templates); { var temp_scope: js.HandleScope = undefined; temp_scope.init(isolate); defer temp_scope.deinit(); - const context = v8.Context.init(v8_isolate, null, null); + const context_handle = isolate.createContextHandle(null, null); - context.enter(); - defer context.exit(); + v8.c.v8__Context__Enter(context_handle); + defer v8.c.v8__Context__Exit(context_handle); inline for (JsApis, 0..) |JsApi, i| { 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(v8_isolate, function).castToFunctionTemplate(); + const data = v8.c.v8__Context__GetDataFromSnapshotOnce(context_handle, snapshot.data_start + i); + const function_handle: *const v8.c.FunctionTemplate = @ptrCast(data); + // Make function template global/persistent + v8.c.v8__Global__New(isolate.handle, @ptrCast(function_handle), &globals[i]); + // Extract the local handle from the global for easy access + templates[i] = @ptrCast(@alignCast(@as(*const anyopaque, @ptrFromInt(globals[i].data_ptr)))); } } @@ -108,17 +114,24 @@ pub fn init(allocator: Allocator, platform: *const Platform, snapshot: *Snapshot .isolate = isolate, .platform = platform, .allocator = allocator, + .globals = globals, .templates = templates, .isolate_params = params, }; } pub fn deinit(self: *Env) void { + // Free global handles before destroying the isolate + for (self.globals) |*global| { + v8.c.v8__Global__Reset(global); + } + self.allocator.free(self.globals); + self.allocator.free(self.templates); + self.isolate.exit(); self.isolate.deinit(); v8.destroyArrayBufferAllocator(self.isolate_params.array_buffer_allocator.?); self.allocator.destroy(self.isolate_params); - self.allocator.free(self.templates); } pub fn newInspector(self: *Env, arena: Allocator, ctx: anytype) !*Inspector { @@ -180,13 +193,13 @@ pub fn dumpMemoryStats(self: *Env) void { fn promiseRejectCallback(v8_msg: v8.C_PromiseRejectMessage) callconv(.c) void { const msg = v8.PromiseRejectMessage.initFromC(v8_msg); - const v8_isolate = msg.getPromise().toObject().getIsolate(); - const js_isolate = js.Isolate{ .handle = v8_isolate.handle }; + const isolate_handle = v8.c.v8__Object__GetIsolate(@ptrCast(msg.getPromise().handle)).?; + const js_isolate = js.Isolate{ .handle = isolate_handle }; const context = Context.fromIsolate(js_isolate); const value = if (msg.getValue()) |v8_value| - context.valueToString(v8_value, .{}) catch |err| @errorName(err) + context.valueToString(js.Value{ .ctx = context, .handle = v8_value.handle }, .{}) catch |err| @errorName(err) else "no value"; diff --git a/src/browser/js/ExecutionWorld.zig b/src/browser/js/ExecutionWorld.zig index 9d5daae7..9dcaaf98 100644 --- a/src/browser/js/ExecutionWorld.zig +++ b/src/browser/js/ExecutionWorld.zig @@ -76,24 +76,33 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context const isolate = env.isolate; const arena = self.context_arena.allocator(); - var v8_context: v8.Context = blk: { - const v8_isolate = v8.Isolate{ .handle = isolate.handle }; + const context_handle: *const v8.c.Context = blk: { 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{ + // Getting this into the snapshot is tricky (anything involving the + // global is tricky). Easier to do here. + const func_tmpl_handle = isolate.createFunctionTemplateHandle(); + const global_template = v8.c.v8__FunctionTemplate__InstanceTemplate(func_tmpl_handle).?; + var configuration: v8.c.NamedPropertyHandlerConfiguration = .{ .getter = unknownPropertyCallback, - .flags = v8.PropertyHandlerFlags.NonMasking | v8.PropertyHandlerFlags.OnlyInterceptStrings, - }, null); + .setter = null, + .query = null, + .deleter = null, + .enumerator = null, + .definer = null, + .descriptor = null, + .data = null, + .flags = v8.c.kOnlyInterceptStrings | v8.c.kNonMasking, + }; + v8.c.v8__ObjectTemplate__SetNamedHandler(global_template, &configuration); - - 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; + const context_local = isolate.createContextHandle(null, null); + // Make the context persistent so it survives beyond this handle scope + var persistent_handle: *v8.c.Data = undefined; + v8.c.v8__Persistent__New(isolate.handle, @ptrCast(context_local), @ptrCast(&persistent_handle)); + break :blk @ptrCast(persistent_handle); }; // 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. @@ -103,10 +112,10 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context if (enter) { handle_scope = @as(js.HandleScope, undefined); handle_scope.?.init(isolate); - v8_context.enter(); + v8.c.v8__Context__Enter(context_handle); } errdefer if (enter) { - v8_context.exit(); + v8.c.v8__Context__Exit(context_handle); handle_scope.?.deinit(); }; @@ -117,7 +126,7 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context .page = page, .id = context_id, .isolate = isolate, - .handle = v8_context.handle, + .handle = context_handle, .templates = env.templates, .handle_scope = handle_scope, .script_manager = &page._script_manager, @@ -128,9 +137,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 v8_isolate = v8.Isolate{ .handle = isolate.handle }; - const data = v8_isolate.initBigIntU64(@intCast(@intFromPtr(context))); - v8_context.setEmbedderData(1, data); + const data = isolate.initBigIntU64(@intCast(@intFromPtr(context))); + v8.c.v8__Context__SetEmbedderData(context_handle, 1, @ptrCast(data.handle)); try context.setupGlobal(); return context; @@ -157,10 +165,12 @@ 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 isolate_handle = v8.c.v8__PropertyCallbackInfo__GetIsolate(raw_info).?; + const context = Context.fromIsolate(.{ .handle = isolate_handle }); - const maybe_property: ?[]u8 = context.valueToString(.{ .handle = c_name.? }, .{}) catch null; + const property: ?[]u8 = context.valueToString(.{ .ctx = context, .handle = c_name.? }, .{}) catch { + return v8.Intercepted.No; + }; const ignored = std.StaticStringMap(void).initComptime(.{ .{ "process", {} }, @@ -184,27 +194,25 @@ pub fn unknownPropertyCallback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C .{ "CLOSURE_FLAGS", {} }, }); - if (maybe_property) |prop| { - if (!ignored.has(prop)) { - const page = context.page; - const document = page.document; + if (!ignored.has(property)) { + const page = context.page; + const document = page.document; - if (document.getElementById(prop, page)) |el| { - const js_value = context.zigValueToJs(el, .{}) catch { - return v8.Intercepted.No; - }; + if (document.getElementById(property, page)) |el| { + const js_value = context.zigValueToJs(el, .{}) catch { + return v8.Intercepted.No; + }; - info.getReturnValue().set(js_value); - return v8.Intercepted.Yes; - } + info.getReturnValue().set(js_value); + return v8.Intercepted.Yes; + } - 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, - }); - } + 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 = property, + }); } } diff --git a/src/browser/js/Function.zig b/src/browser/js/Function.zig index 5de7ca39..8ed0c35e 100644 --- a/src/browser/js/Function.zig +++ b/src/browser/js/Function.zig @@ -134,20 +134,20 @@ pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args const aargs = if (comptime @typeInfo(@TypeOf(args)) == .null) struct {}{} else args; - const js_args: []const v8.Value = switch (@typeInfo(@TypeOf(aargs))) { + const js_args: []const *const v8.c.Value = switch (@typeInfo(@TypeOf(aargs))) { .@"struct" => |s| blk: { const fields = s.fields; - var js_args: [fields.len]v8.Value = undefined; + var js_args: [fields.len]*const v8.c.Value = undefined; inline for (fields, 0..) |f, i| { - js_args[i] = try ctx.zigValueToJs(@field(aargs, f.name), .{}); + js_args[i] = (try ctx.zigValueToJs(@field(aargs, f.name), .{})).handle; } - const cargs: [fields.len]v8.Value = js_args; + const cargs: [fields.len]*const v8.c.Value = js_args; break :blk &cargs; }, .pointer => blk: { - var values = try ctx.call_arena.alloc(v8.Value, args.len); + var values = try ctx.call_arena.alloc(*const v8.c.Value, args.len); for (args, 0..) |a, i| { - values[i] = try ctx.zigValueToJs(a, .{}); + values[i] = (try ctx.zigValueToJs(a, .{})).handle; } break :blk values; }, @@ -163,7 +163,7 @@ pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args if (@typeInfo(T) == .void) { return {}; } - return ctx.jsValueToZig(T, .{ .handle = handle }); + return ctx.jsValueToZig(T, .{ .ctx = ctx, .handle = handle }); } fn getThis(self: *const Function) js.Object { diff --git a/src/browser/js/Inspector.zig b/src/browser/js/Inspector.zig index 46746122..3126f62d 100644 --- a/src/browser/js/Inspector.zig +++ b/src/browser/js/Inspector.zig @@ -195,9 +195,8 @@ pub fn getNodePtr(self: *const Inspector, allocator: Allocator, object_id: []con return error.ObjectIdIsNotANode; } const Node = @import("../webapi/Node.zig"); - // Wrap the C handle in a v8.Object for typeTaggedAnyOpaque - const js_obj = v8.Object{ .handle = js_val }; - return Context.typeTaggedAnyOpaque(*Node, js_obj) catch { + // Cast to *const v8.c.Object for typeTaggedAnyOpaque + return Context.typeTaggedAnyOpaque(*Node, @ptrCast(js_val)) catch { return error.ObjectIdIsNotANode; }; } diff --git a/src/browser/js/Isolate.zig b/src/browser/js/Isolate.zig index fa570947..07d37012 100644 --- a/src/browser/js/Isolate.zig +++ b/src/browser/js/Isolate.zig @@ -23,10 +23,20 @@ const Isolate = @This(); handle: *v8.c.Isolate, +pub fn init(params: *v8.c.CreateParams) Isolate { + return .{ + .handle = v8.c.v8__Isolate__New(params).?, + }; +} + pub fn deinit(self: Isolate) void { v8.c.v8__Isolate__Dispose(self.handle); } +pub fn enter(self: Isolate) void { + v8.c.v8__Isolate__Enter(self.handle); +} + pub fn exit(self: Isolate) void { v8.c.v8__Isolate__Exit(self.handle); } @@ -54,16 +64,50 @@ pub fn getHeapStatistics(self: Isolate) v8.c.HeapStatistics { 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).?, - }; +pub fn throwException(self: Isolate, value: *const v8.c.Value) *const v8.c.Value { + return v8.c.v8__Isolate__ThrowException(self.handle, value).?; } -pub fn newStringHandle(self: Isolate, str: []const u8) *const v8.c.String { +pub fn createStringHandle(self: Isolate, str: []const u8) *const v8.c.String { return v8.c.v8__String__NewFromUtf8(self.handle, str.ptr, v8.c.kNormal, @as(c_int, @intCast(str.len))).?; } + +pub fn createError(self: Isolate, msg: []const u8) *const v8.c.Value { + const message = self.createStringHandle(msg); + return v8.c.v8__Exception__Error(message).?; +} + +pub fn createTypeError(self: Isolate, msg: []const u8) *const v8.c.Value { + const message = self.createStringHandle(msg); + return v8.c.v8__Exception__TypeError(message).?; +} + +pub fn initArray(self: Isolate, len: u32) v8.Array { + const handle = v8.c.v8__Array__New(self.handle, @intCast(len)).?; + return .{ .handle = handle }; +} + +pub fn initObject(self: Isolate) v8.Object { + const handle = v8.c.v8__Object__New(self.handle).?; + return .{ .handle = handle }; +} + +pub fn initString(self: Isolate, str: []const u8) v8.String { + return .{ .handle = self.createStringHandle(str) }; +} + +pub fn initNull(self: Isolate) *const v8.c.Value { + return v8.c.v8__Null(self.handle).?; +} + +pub fn initBigIntU64(self: Isolate, val: u64) js.BigInt { + return js.BigInt.initU64(self.handle, val); +} + +pub fn createContextHandle(self: Isolate, global_tmpl: ?*const v8.c.ObjectTemplate, global_obj: ?*const v8.c.Value) *const v8.c.Context { + return v8.c.v8__Context__New(self.handle, global_tmpl, global_obj).?; +} + +pub fn createFunctionTemplateHandle(self: Isolate) *const v8.c.FunctionTemplate { + return v8.c.v8__FunctionTemplate__New__DEFAULT(self.handle).?; +} diff --git a/src/browser/js/Object.zig b/src/browser/js/Object.zig index 1cd6a14d..0f72e5b7 100644 --- a/src/browser/js/Object.zig +++ b/src/browser/js/Object.zig @@ -36,47 +36,32 @@ pub fn getId(self: Object) u32 { return @bitCast(v8.c.v8__Object__GetIdentityHash(self.handle)); } -pub const SetOpts = packed struct(u32) { - READ_ONLY: bool = false, - DONT_ENUM: bool = false, - DONT_DELETE: bool = false, - _: u29 = 0, -}; -pub fn setIndex(self: Object, index: u32, value: anytype, opts: SetOpts) !void { - @setEvalBranchQuota(10000); - const key = switch (index) { - inline 0...20 => |i| std.fmt.comptimePrint("{d}", .{i}), - else => try std.fmt.allocPrint(self.context.arena, "{d}", .{index}), +pub fn get(self: Object, key: []const u8) !js.Value { + const ctx = self.ctx; + const js_key = ctx.isolate.createStringHandle(key); + const js_val_handle = v8.c.v8__Object__Get(self.handle, ctx.handle, js_key) orelse return error.JsException; + return .{ + .ctx = ctx, + .handle = js_val_handle, }; - return self.set(key, value, opts); } -pub fn set(self: Object, key: []const u8, value: anytype, opts: SetOpts) error{ FailedToSet, OutOfMemory }!void { +pub fn defineOwnProperty(self: Object, name: []const u8, value: js.Value, attr: v8.c.PropertyAttribute) ?bool { const ctx = self.ctx; - const js_key = v8.c.v8__String__NewFromUtf8(ctx.isolate.handle, key.ptr, v8.c.kNormal, @intCast(key.len)).?; - const js_value = try ctx.zigValueToJs(value, .{}); + const name_handle = ctx.isolate.createStringHandle(name); var out: v8.c.MaybeBool = undefined; - v8.c.v8__Object__DefineOwnProperty(self.handle, ctx.handle, @ptrCast(js_key), js_value.handle, @bitCast(opts), &out); - - const res = if (out.has_value) out.value else false; - if (!res) { - return error.FailedToSet; + v8.c.v8__Object__DefineOwnProperty(self.handle, ctx.handle, @ptrCast(name_handle), value.handle, attr, &out); + if (out.has_value) { + return out.value; + } else { + return null; } } -pub fn get(self: Object, key: []const u8) !js.Value { - const ctx = self.ctx; - const js_key = ctx.isolate.newStringHandle(key); - const js_val_handle = v8.c.v8__Object__Get(self.handle, ctx.handle, js_key) orelse return error.JsException; - const js_val = v8.Value{ .handle = js_val_handle }; - return ctx.createValue(js_val); -} - pub fn toString(self: Object) ![]const u8 { - const js_value = v8.Value{ .handle = @ptrCast(self.handle) }; - return self.ctx.valueToString(js_value, .{}); + return self.ctx.valueToString(self.toValue(), .{}); } pub fn toValue(self: Object) js.Value { @@ -88,8 +73,7 @@ pub fn toValue(self: Object) js.Value { pub fn format(self: Object, writer: *std.Io.Writer) !void { if (comptime IS_DEBUG) { - const js_value = v8.Value{ .handle = @ptrCast(self.handle) }; - return self.ctx.debugValue(js_value, writer); + return self.ctx.debugValue(self.toValue(), writer); } const str = self.toString() catch return error.WriteFailed; return writer.writeAll(str); @@ -119,9 +103,9 @@ pub fn getFunction(self: Object, name: []const u8) !?js.Function { } const ctx = self.ctx; - const js_name = ctx.isolate.newStringHandle(name); + const js_name = ctx.isolate.createStringHandle(name); const js_val_handle = v8.c.v8__Object__Get(self.handle, ctx.handle, js_name) orelse return error.JsException; - const js_value = v8.Value{ .handle = js_val_handle }; + const js_value = js.Value{ .ctx = ctx, .handle = js_val_handle }; if (!js_value.isFunction()) { return null; @@ -152,7 +136,7 @@ pub fn nameIterator(self: Object) NameIterator { } pub fn toZig(self: Object, comptime T: type) !T { - const js_value = v8.Value{ .handle = @ptrCast(self.handle) }; + const js_value = js.Value{ .ctx = self.ctx, .handle = @ptrCast(self.handle) }; return self.ctx.jsValueToZig(T, js_value); } @@ -170,7 +154,7 @@ pub const NameIterator = struct { self.idx += 1; const js_val_handle = v8.c.v8__Object__GetIndex(@ptrCast(self.handle), self.ctx.handle, idx) orelse return error.JsException; - const js_val = v8.Value{ .handle = js_val_handle }; + const js_val = js.Value{ .ctx = self.ctx, .handle = js_val_handle }; return try self.ctx.valueToString(js_val, .{}); } }; diff --git a/src/browser/js/Snapshot.zig b/src/browser/js/Snapshot.zig index 5fe8b50a..f6dc23e9 100644 --- a/src/browser/js/Snapshot.zig +++ b/src/browser/js/Snapshot.zig @@ -479,12 +479,11 @@ fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *v8.Functio v8.c.v8__Template__Set(@ptrCast(target), js_name, @ptrCast(function_template), v8.c.None); }, bridge.Property => { - // simpleZigValueToJs still uses old v8.Isolate wrapper, so create a temp wrapper + // simpleZigValueToJs now returns raw handle directly const iso_wrapper = v8.Isolate{ .handle = isolate }; - const js_value_wrapper = switch (value) { + const js_value = switch (value) { .int => |v| js.simpleZigValueToJs(iso_wrapper, v, true, false), }; - const js_value = js_value_wrapper.handle; const js_name = v8.c.v8__String__NewFromUtf8(isolate, name.ptr, v8.c.kNormal, @intCast(name.len)); // apply it both to the type itself diff --git a/src/browser/js/TryCatch.zig b/src/browser/js/TryCatch.zig index c1cdec18..4acb5aad 100644 --- a/src/browser/js/TryCatch.zig +++ b/src/browser/js/TryCatch.zig @@ -39,7 +39,7 @@ pub fn hasCaught(self: TryCatch) bool { // the caller needs to deinit the string returned pub fn exception(self: TryCatch, allocator: Allocator) !?[]const u8 { const msg_value = v8.c.v8__TryCatch__Exception(&self.handle) orelse return null; - const msg = v8.Value{ .handle = msg_value }; + const msg = js.Value{ .ctx = self.ctx, .handle = msg_value }; return try self.ctx.valueToString(msg, .{ .allocator = allocator }); } @@ -47,7 +47,7 @@ pub fn exception(self: TryCatch, allocator: Allocator) !?[]const u8 { pub fn stack(self: TryCatch, allocator: Allocator) !?[]const u8 { const ctx = self.ctx; const s_value = v8.c.v8__TryCatch__StackTrace(&self.handle, ctx.handle) orelse return null; - const s = v8.Value{ .handle = s_value }; + const s = js.Value{ .ctx = ctx, .handle = s_value }; return try ctx.valueToString(s, .{ .allocator = allocator }); } diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig index 5e60c6bc..bb823c84 100644 --- a/src/browser/js/Value.zig +++ b/src/browser/js/Value.zig @@ -27,7 +27,7 @@ const Allocator = std.mem.Allocator; const Value = @This(); -ctx: *js.Context, +ctx: *const js.Context, handle: *const v8.c.Value, pub fn isObject(self: Value) bool { @@ -58,6 +58,146 @@ pub fn isFunction(self: Value) bool { return v8.c.v8__Value__IsFunction(self.handle); } +pub fn isNull(self: Value) bool { + return v8.c.v8__Value__IsNull(self.handle); +} + +pub fn isUndefined(self: Value) bool { + return v8.c.v8__Value__IsUndefined(self.handle); +} + +pub fn isNullOrUndefined(self: Value) bool { + return v8.c.v8__Value__IsNullOrUndefined(self.handle); +} + +pub fn isNumber(self: Value) bool { + return v8.c.v8__Value__IsNumber(self.handle); +} + +pub fn isNumberObject(self: Value) bool { + return v8.c.v8__Value__IsNumberObject(self.handle); +} + +pub fn isInt32(self: Value) bool { + return v8.c.v8__Value__IsInt32(self.handle); +} + +pub fn isUint32(self: Value) bool { + return v8.c.v8__Value__IsUint32(self.handle); +} + +pub fn isBigInt(self: Value) bool { + return v8.c.v8__Value__IsBigInt(self.handle); +} + +pub fn isBigIntObject(self: Value) bool { + return v8.c.v8__Value__IsBigIntObject(self.handle); +} + +pub fn isBoolean(self: Value) bool { + return v8.c.v8__Value__IsBoolean(self.handle); +} + +pub fn isBooleanObject(self: Value) bool { + return v8.c.v8__Value__IsBooleanObject(self.handle); +} + +pub fn isTrue(self: Value) bool { + return v8.c.v8__Value__IsTrue(self.handle); +} + +pub fn isFalse(self: Value) bool { + return v8.c.v8__Value__IsFalse(self.handle); +} + +pub fn isTypedArray(self: Value) bool { + return v8.c.v8__Value__IsTypedArray(self.handle); +} + +pub fn isArrayBufferView(self: Value) bool { + return v8.c.v8__Value__IsArrayBufferView(self.handle); +} + +pub fn isArrayBuffer(self: Value) bool { + return v8.c.v8__Value__IsArrayBuffer(self.handle); +} + +pub fn isUint8Array(self: Value) bool { + return v8.c.v8__Value__IsUint8Array(self.handle); +} + +pub fn isUint8ClampedArray(self: Value) bool { + return v8.c.v8__Value__IsUint8ClampedArray(self.handle); +} + +pub fn isInt8Array(self: Value) bool { + return v8.c.v8__Value__IsInt8Array(self.handle); +} + +pub fn isUint16Array(self: Value) bool { + return v8.c.v8__Value__IsUint16Array(self.handle); +} + +pub fn isInt16Array(self: Value) bool { + return v8.c.v8__Value__IsInt16Array(self.handle); +} + +pub fn isUint32Array(self: Value) bool { + return v8.c.v8__Value__IsUint32Array(self.handle); +} + +pub fn isInt32Array(self: Value) bool { + return v8.c.v8__Value__IsInt32Array(self.handle); +} + +pub fn isBigUint64Array(self: Value) bool { + return v8.c.v8__Value__IsBigUint64Array(self.handle); +} + +pub fn isBigInt64Array(self: Value) bool { + return v8.c.v8__Value__IsBigInt64Array(self.handle); +} + +pub fn toBool(self: Value) bool { + return v8.c.v8__Value__BooleanValue(self.handle, self.ctx.isolate.handle); +} + +pub fn typeOf(self: Value) js.String { + const str_handle = v8.c.v8__Value__TypeOf(self.handle, self.ctx.isolate.handle).?; + return js.String{ .ctx = @constCast(self.ctx), .handle = str_handle }; +} + +pub fn toF32(self: Value) !f32 { + return @floatCast(try self.toF64()); +} + +pub fn toF64(self: Value) !f64 { + var maybe: v8.c.MaybeF64 = undefined; + v8.c.v8__Value__NumberValue(self.handle, self.ctx.handle, &maybe); + if (!maybe.has_value) { + return error.JsException; + } + return maybe.value; +} + +pub fn toI32(self: Value) !i32 { + var maybe: v8.c.MaybeI32 = undefined; + v8.c.v8__Value__Int32Value(self.handle, self.ctx.handle, &maybe); + if (!maybe.has_value) { + return error.JsException; + } + return maybe.value; +} + +pub fn toU32(self: Value) !u32 { + var maybe: v8.c.MaybeU32 = undefined; + v8.c.v8__Value__Uint32Value(self.handle, self.ctx.handle, &maybe); + if (!maybe.has_value) { + return error.JsException; + } + return maybe.value; +} + pub fn toString(self: Value, opts: js.String.ToZigOpts) ![]u8 { return self._toString(false, opts); } @@ -66,7 +206,7 @@ pub fn toStringZ(self: Value, opts: js.String.ToZigOpts) ![:0]u8 { } fn _toString(self: Value, comptime null_terminate: bool, opts: js.String.ToZigOpts) !(if (null_terminate) [:0]u8 else []u8) { - const ctx = self.ctx; + const ctx: *js.Context = @constCast(self.ctx); if (self.isSymbol()) { const sym_handle = v8.c.v8__Symbol__Description(@ptrCast(self.handle), ctx.isolate.handle).?; @@ -97,7 +237,7 @@ pub fn fromJson(ctx: *js.Context, json: []const u8) !Value { } pub fn persist(self: Value) !Value { - var ctx = self.ctx; + var ctx: *js.Context = @constCast(self.ctx); const global = js.Global(Value).init(ctx.isolate.handle, self.handle); try ctx.global_values.append(ctx.arena, global); @@ -118,7 +258,7 @@ pub fn toObject(self: Value) js.Object { } return .{ - .ctx = self.ctx, + .ctx = @constCast(self.ctx), .handle = self.handle, }; } @@ -129,14 +269,20 @@ pub fn toArray(self: Value) js.Array { } return .{ - .ctx = self.ctx, + .ctx = @constCast(self.ctx), .handle = self.handle, }; } +pub fn castTo(self: Value, comptime T: type) T { + return .{ + .handle = @ptrCast(self.handle), + }; +} + pub fn format(self: Value, writer: *std.Io.Writer) !void { if (comptime IS_DEBUG) { - return self.ctx.debugValue(.{ .handle = self.handle }, writer); + return self.ctx.debugValue(self, writer); } const str = self.toString(.{}) catch return error.WriteFailed; return writer.writeAll(str); diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index d24b07ac..ee835c01 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -22,7 +22,533 @@ const log = @import("../../log.zig"); const v8 = js.v8; -const Caller = @import("Caller.zig"); +const Context = @import("Context.zig"); +const Page = @import("../Page.zig"); + +const Allocator = std.mem.Allocator; +const ArenaAllocator = std.heap.ArenaAllocator; + +const CALL_ARENA_RETAIN = 1024 * 16; + +// ============================================================================ +// Internal Callback Info Wrappers +// ============================================================================ +// These wrap the raw v8 C API to provide a cleaner interface. +// They are not exported - internal to this module only. + +const Value = struct { + handle: *const v8.c.Value, + + fn isArray(self: Value) bool { + return v8.c.v8__Value__IsArray(self.handle); + } + + fn isTypedArray(self: Value) bool { + return v8.c.v8__Value__IsTypedArray(self.handle); + } + + fn isFunction(self: Value) bool { + return v8.c.v8__Value__IsFunction(self.handle); + } +}; + +const Name = struct { + handle: *const v8.c.Name, +}; + +const CallbackInfo = struct { + raw: *const v8.c.FunctionCallbackInfo, + + fn length(self: CallbackInfo) u32 { + return @intCast(v8.c.v8__FunctionCallbackInfo__Length(self.raw)); + } + + fn getArg(self: CallbackInfo, index: u32) Value { + return .{ .handle = v8.c.v8__FunctionCallbackInfo__INDEX(self.raw, @intCast(index)).? }; + } + + fn getThis(self: CallbackInfo) *const v8.c.Object { + return v8.c.v8__FunctionCallbackInfo__This(self.raw).?; + } + + fn getReturnValue(self: CallbackInfo) ReturnValue { + var rv: v8.c.ReturnValue = undefined; + v8.c.v8__FunctionCallbackInfo__GetReturnValue(self.raw, &rv); + return .{ .raw = rv }; + } +}; + +const PropertyCallbackInfo = struct { + raw: *const v8.c.PropertyCallbackInfo, + + fn getThis(self: PropertyCallbackInfo) *const v8.c.Object { + return v8.c.v8__PropertyCallbackInfo__This(self.raw).?; + } + + fn getReturnValue(self: PropertyCallbackInfo) ReturnValue { + var rv: v8.c.ReturnValue = undefined; + v8.c.v8__PropertyCallbackInfo__GetReturnValue(self.raw, &rv); + return .{ .raw = rv }; + } +}; + +const ReturnValue = struct { + raw: v8.c.ReturnValue, + + fn set(self: ReturnValue, value: anytype) void { + const T = @TypeOf(value); + if (T == Value) { + self.setValueHandle(value.handle); + } else if (T == *const v8.c.Object) { + self.setValueHandle(@ptrCast(value)); + } else if (T == *const v8.c.Value) { + self.setValueHandle(value); + } else if (T == js.Value) { + self.setValueHandle(value.handle); + } else { + @compileError("Unsupported type for ReturnValue.set: " ++ @typeName(T)); + } + } + + fn setValueHandle(self: ReturnValue, handle: *const v8.c.Value) void { + v8.c.v8__ReturnValue__Set(self.raw, handle); + } +}; + +// ============================================================================ +// Caller - Responsible for calling Zig functions from JS invocations +// ============================================================================ + +pub const Caller = struct { + context: *Context, + isolate: js.Isolate, + call_arena: Allocator, + + // Takes the raw v8 isolate and extracts the context from it. + pub fn init(v8_isolate: *v8.c.Isolate) Caller { + const isolate = js.Isolate{ .handle = v8_isolate }; + const v8_context_handle = v8.c.v8__Isolate__GetCurrentContext(v8_isolate); + const embedder_data = v8.c.v8__Context__GetEmbedderData(v8_context_handle, 1); + var lossless: bool = undefined; + const context: *Context = @ptrFromInt(v8.c.v8__BigInt__Uint64Value(embedder_data, &lossless)); + + context.call_depth += 1; + return .{ + .context = context, + .isolate = isolate, + .call_arena = context.call_arena, + }; + } + + pub fn deinit(self: *Caller) void { + const context = self.context; + const call_depth = context.call_depth - 1; + + // Because of callbacks, calls can be nested. Because of this, we + // can't clear the call_arena after _every_ call. Imagine we have + // arr.forEach((i) => { console.log(i); } + // + // First we call forEach. Inside of our forEach call, + // we call console.log. If we reset the call_arena after this call, + // it'll reset it for the `forEach` call after, which might still + // need the data. + // + // Therefore, we keep a call_depth, and only reset the call_arena + // when a top-level (call_depth == 0) function ends. + if (call_depth == 0) { + const arena: *ArenaAllocator = @ptrCast(@alignCast(context.call_arena.ptr)); + _ = arena.reset(.{ .retain_with_limit = CALL_ARENA_RETAIN }); + } + + context.call_depth = call_depth; + } + + pub const CallOpts = struct { + dom_exception: bool = false, + null_as_undefined: bool = false, + as_typed_array: bool = false, + }; + + pub fn constructor(self: *Caller, comptime T: type, func: anytype, info: CallbackInfo, comptime opts: CallOpts) void { + self._constructor(func, info) catch |err| { + self.handleError(T, @TypeOf(func), err, info, opts); + }; + } + + fn _constructor(self: *Caller, func: anytype, info: CallbackInfo) !void { + const F = @TypeOf(func); + const args = try self.getArgs(F, 0, info); + const res = @call(.auto, func, args); + + const ReturnType = @typeInfo(F).@"fn".return_type orelse { + @compileError(@typeName(F) ++ " has a constructor without a return type"); + }; + + const new_this_handle = info.getThis(); + const new_this = v8.Object{ .handle = new_this_handle }; + var this = new_this; + if (@typeInfo(ReturnType) == .error_union) { + const non_error_res = res catch |err| return err; + this = (try self.context.mapZigInstanceToJs(new_this_handle, non_error_res)).castToObject(); + } else { + this = (try self.context.mapZigInstanceToJs(new_this_handle, res)).castToObject(); + } + + // If we got back a different object (existing wrapper), copy the prototype + // from new object. (this happens when we're upgrading an CustomElement) + if (this.handle != new_this.handle) { + const new_prototype = new_this.getPrototype(); + const v8_context = v8.Context{ .handle = self.context.handle }; + _ = this.setPrototype(v8_context, new_prototype.castTo(v8.Object)); + } + + info.getReturnValue().set(this.handle); + } + + pub fn method(self: *Caller, comptime T: type, func: anytype, info: CallbackInfo, comptime opts: CallOpts) void { + self._method(T, func, info, opts) catch |err| { + self.handleError(T, @TypeOf(func), err, info, opts); + }; + } + + fn _method(self: *Caller, comptime T: type, func: anytype, info: CallbackInfo, comptime opts: CallOpts) !void { + const F = @TypeOf(func); + var handle_scope: js.HandleScope = undefined; + handle_scope.init(self.isolate); + defer handle_scope.deinit(); + + var args = try self.getArgs(F, 1, info); + @field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis()); + const res = @call(.auto, func, args); + info.getReturnValue().set(try self.context.zigValueToJs(res, opts)); + } + + pub fn function(self: *Caller, comptime T: type, func: anytype, info: CallbackInfo, comptime opts: CallOpts) void { + self._function(func, info, opts) catch |err| { + self.handleError(T, @TypeOf(func), err, info, opts); + }; + } + + fn _function(self: *Caller, func: anytype, info: CallbackInfo, comptime opts: CallOpts) !void { + const F = @TypeOf(func); + const context = self.context; + const args = try self.getArgs(F, 0, info); + const res = @call(.auto, func, args); + info.getReturnValue().set(try context.zigValueToJs(res, opts)); + } + + pub fn getIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, info: PropertyCallbackInfo, comptime opts: CallOpts) u8 { + return self._getIndex(T, func, idx, info, opts) catch |err| { + self.handleError(T, @TypeOf(func), err, info, opts); + return v8.Intercepted.No; + }; + } + + fn _getIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 { + const F = @TypeOf(func); + var args = try self.getArgs(F, 2, info); + @field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis()); + @field(args, "1") = idx; + const ret = @call(.auto, func, args); + return self.handleIndexedReturn(T, F, true, ret, info, opts); + } + + pub fn getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, info: PropertyCallbackInfo, comptime opts: CallOpts) u8 { + return self._getNamedIndex(T, func, name, info, opts) catch |err| { + self.handleError(T, @TypeOf(func), err, info, opts); + return v8.Intercepted.No; + }; + } + + fn _getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 { + const F = @TypeOf(func); + var args = try self.getArgs(F, 2, info); + @field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis()); + @field(args, "1") = try self.nameToString(name); + const ret = @call(.auto, func, args); + return self.handleIndexedReturn(T, F, true, ret, info, opts); + } + + pub fn setNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, js_value: Value, info: PropertyCallbackInfo, comptime opts: CallOpts) u8 { + return self._setNamedIndex(T, func, name, js_value, info, opts) catch |err| { + self.handleError(T, @TypeOf(func), err, info, opts); + return v8.Intercepted.No; + }; + } + + fn _setNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, js_value: Value, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 { + const F = @TypeOf(func); + var args: ParameterTypes(F) = undefined; + @field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis()); + @field(args, "1") = try self.nameToString(name); + @field(args, "2") = try self.context.jsValueToZig(@TypeOf(@field(args, "2")), js.Value{ .ctx = self.context, .handle = js_value.handle }); + if (@typeInfo(F).@"fn".params.len == 4) { + @field(args, "3") = self.context.page; + } + const ret = @call(.auto, func, args); + return self.handleIndexedReturn(T, F, false, ret, info, opts); + } + + pub fn deleteNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, info: PropertyCallbackInfo, comptime opts: CallOpts) u8 { + return self._deleteNamedIndex(T, func, name, info, opts) catch |err| { + self.handleError(T, @TypeOf(func), err, info, opts); + return v8.Intercepted.No; + }; + } + + fn _deleteNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 { + const F = @TypeOf(func); + var args: ParameterTypes(F) = undefined; + @field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis()); + @field(args, "1") = try self.nameToString(name); + if (@typeInfo(F).@"fn".params.len == 3) { + @field(args, "2") = self.context.page; + } + const ret = @call(.auto, func, args); + return self.handleIndexedReturn(T, F, false, ret, info, opts); + } + + fn handleIndexedReturn(self: *Caller, comptime T: type, comptime F: type, comptime getter: bool, ret: anytype, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 { + // need to unwrap this error immediately for when opts.null_as_undefined == true + // and we need to compare it to null; + const non_error_ret = switch (@typeInfo(@TypeOf(ret))) { + .error_union => |eu| blk: { + break :blk ret catch |err| { + // We can't compare err == error.NotHandled if error.NotHandled + // isn't part of the possible error set. So we first need to check + // if error.NotHandled is part of the error set. + if (isInErrorSet(error.NotHandled, eu.error_set)) { + if (err == error.NotHandled) { + return v8.Intercepted.No; + } + } + self.handleError(T, F, err, info, opts); + return v8.Intercepted.No; + }; + }, + else => ret, + }; + + if (comptime getter) { + info.getReturnValue().set(try self.context.zigValueToJs(non_error_ret, opts)); + } + return v8.Intercepted.Yes; + } + + fn isInErrorSet(err: anyerror, comptime T: type) bool { + inline for (@typeInfo(T).error_set.?) |e| { + if (err == @field(anyerror, e.name)) return true; + } + return false; + } + + fn nameToString(self: *Caller, name: Name) ![]const u8 { + return self.context.valueToString(js.Value{ .ctx = self.context, .handle = @ptrCast(name.handle) }, .{}); + } + + fn handleError(self: *Caller, comptime T: type, comptime F: type, err: anyerror, info: anytype, comptime opts: CallOpts) void { + const isolate = self.isolate; + + if (comptime @import("builtin").mode == .Debug and @TypeOf(info) == CallbackInfo) { + if (log.enabled(.js, .warn)) { + self.logFunctionCallError(@typeName(T), @typeName(F), err, info); + } + } + + const js_err: *const v8.c.Value = switch (err) { + error.InvalidArgument => isolate.createTypeError("invalid argument"), + error.OutOfMemory => isolate.createError("out of memory"), + error.IllegalConstructor => isolate.createError("Illegal Contructor"), + else => blk: { + if (comptime opts.dom_exception) { + const DOMException = @import("../webapi/DOMException.zig"); + if (DOMException.fromError(err)) |ex| { + const value = self.context.zigValueToJs(ex, .{}) catch break :blk isolate.createError("internal error"); + break :blk value.handle; + } + } + break :blk isolate.createError(@errorName(err)); + }, + }; + + const js_exception = isolate.throwException(js_err); + info.getReturnValue().setValueHandle(js_exception); + } + + // If we call a method in javascript: cat.lives('nine'); + // + // Then we'd expect a Zig function with 2 parameters: a self and the string. + // In this case, offset == 1. Offset is always 1 for setters or methods. + // + // Offset is always 0 for constructors. + // + // For constructors, setters and methods, we can further increase offset + 1 + // if the first parameter is an instance of Page. + // + // Finally, if the JS function is called with _more_ parameters and + // the last parameter in Zig is an array, we'll try to slurp the additional + // parameters into the array. + fn getArgs(self: *const Caller, comptime F: type, comptime offset: usize, info: anytype) !ParameterTypes(F) { + const context = self.context; + var args: ParameterTypes(F) = undefined; + + const params = @typeInfo(F).@"fn".params[offset..]; + // Except for the constructor, the first parameter is always `self` + // This isn't something we'll bind from JS, so skip it. + const params_to_map = blk: { + if (params.len == 0) { + return args; + } + + // If the last parameter is the Page, set it, and exclude it + // from our params slice, because we don't want to bind it to + // a JS argument + if (comptime isPage(params[params.len - 1].type.?)) { + @field(args, tupleFieldName(params.len - 1 + offset)) = self.context.page; + break :blk params[0 .. params.len - 1]; + } + + // we have neither a Page nor a JsObject. All params must be + // bound to a JavaScript value. + break :blk params; + }; + + if (params_to_map.len == 0) { + return args; + } + + const js_parameter_count = info.length(); + const last_js_parameter = params_to_map.len - 1; + var is_variadic = false; + + { + // This is going to get complicated. If the last Zig parameter + // is a slice AND the corresponding javascript parameter is + // NOT an an array, then we'll treat it as a variadic. + + const last_parameter_type = params_to_map[params_to_map.len - 1].type.?; + const last_parameter_type_info = @typeInfo(last_parameter_type); + if (last_parameter_type_info == .pointer and last_parameter_type_info.pointer.size == .slice) { + const slice_type = last_parameter_type_info.pointer.child; + const corresponding_js_value = info.getArg(@as(u32, @intCast(last_js_parameter))); + if (corresponding_js_value.isArray() == false and corresponding_js_value.isTypedArray() == false and slice_type != u8) { + is_variadic = true; + if (js_parameter_count == 0) { + @field(args, tupleFieldName(params_to_map.len + offset - 1)) = &.{}; + } else if (js_parameter_count >= params_to_map.len) { + const arr = try self.call_arena.alloc(last_parameter_type_info.pointer.child, js_parameter_count - params_to_map.len + 1); + for (arr, last_js_parameter..) |*a, i| { + const js_value = info.getArg(@as(u32, @intCast(i))); + a.* = try context.jsValueToZig(slice_type, js.Value{ .ctx = context, .handle = js_value.handle }); + } + @field(args, tupleFieldName(params_to_map.len + offset - 1)) = arr; + } else { + @field(args, tupleFieldName(params_to_map.len + offset - 1)) = &.{}; + } + } + } + } + + inline for (params_to_map, 0..) |param, i| { + const field_index = comptime i + offset; + if (comptime i == params_to_map.len - 1) { + if (is_variadic) { + break; + } + } + + if (comptime isPage(param.type.?)) { + @compileError("Page must be the last parameter (or 2nd last if there's a JsThis): " ++ @typeName(F)); + } else if (i >= js_parameter_count) { + if (@typeInfo(param.type.?) != .optional) { + return error.InvalidArgument; + } + @field(args, tupleFieldName(field_index)) = null; + } else { + const js_value = info.getArg(@as(u32, @intCast(i))); + @field(args, tupleFieldName(field_index)) = context.jsValueToZig(param.type.?, js.Value{ .ctx = context, .handle = js_value.handle }) catch { + return error.InvalidArgument; + }; + } + } + + return args; + } + + // This is extracted to speed up compilation. When left inlined in handleError, + // this can add as much as 10 seconds of compilation time. + fn logFunctionCallError(self: *Caller, type_name: []const u8, func: []const u8, err: anyerror, info: CallbackInfo) void { + const args_dump = self.serializeFunctionArgs(info) catch "failed to serialize args"; + log.info(.js, "function call error", .{ + .type = type_name, + .func = func, + .err = err, + .args = args_dump, + .stack = self.context.stackTrace() catch |err1| @errorName(err1), + }); + } + + fn serializeFunctionArgs(self: *Caller, info: CallbackInfo) ![]const u8 { + const context = self.context; + var buf = std.Io.Writer.Allocating.init(context.call_arena); + + const separator = log.separator(); + for (0..info.length()) |i| { + try buf.writer.print("{s}{d} - ", .{ separator, i + 1 }); + const val = info.getArg(@intCast(i)); + try context.debugValue(js.Value{ .ctx = context, .handle = val.handle }, &buf.writer); + } + return buf.written(); + } + + // Takes a function, and returns a tuple for its argument. Used when we + // @call a function + fn ParameterTypes(comptime F: type) type { + const params = @typeInfo(F).@"fn".params; + var fields: [params.len]std.builtin.Type.StructField = undefined; + + inline for (params, 0..) |param, i| { + fields[i] = .{ + .name = tupleFieldName(i), + .type = param.type.?, + .default_value_ptr = null, + .is_comptime = false, + .alignment = @alignOf(param.type.?), + }; + } + + return @Type(.{ .@"struct" = .{ + .layout = .auto, + .decls = &.{}, + .fields = &fields, + .is_tuple = true, + } }); + } + + fn tupleFieldName(comptime i: usize) [:0]const u8 { + return switch (i) { + 0 => "0", + 1 => "1", + 2 => "2", + 3 => "3", + 4 => "4", + 5 => "5", + 6 => "6", + 7 => "7", + 8 => "8", + 9 => "9", + else => std.fmt.comptimePrint("{d}", .{i}), + }; + } + + fn isPage(comptime T: type) bool { + return T == *Page or T == *const Page; + } +}; + +// ============================================================================ +// Bridge Builder Functions +// ============================================================================ pub fn Builder(comptime T: type) type { return struct { @@ -98,10 +624,11 @@ pub const Constructor = struct { fn init(comptime T: type, comptime func: anytype, comptime opts: Opts) Constructor { return .{ .func = struct { fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void { - const info = v8.FunctionCallbackInfo.initFromV8(raw_info); - var caller = Caller.init(info); + const v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?; + var caller = Caller.init(v8_isolate); defer caller.deinit(); + const info = CallbackInfo{ .raw = raw_info.? }; caller.constructor(T, func, info, .{ .dom_exception = opts.dom_exception, }); @@ -126,10 +653,11 @@ pub const Function = struct { .static = opts.static, .func = struct { fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void { - const info = v8.FunctionCallbackInfo.initFromV8(raw_info); - var caller = Caller.init(info); + const v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?; + var caller = Caller.init(v8_isolate); defer caller.deinit(); + const info = CallbackInfo{ .raw = raw_info.? }; if (comptime opts.static) { caller.function(T, func, info, .{ .dom_exception = opts.dom_exception, @@ -169,10 +697,11 @@ pub const Accessor = struct { if (@typeInfo(@TypeOf(getter)) != .null) { accessor.getter = struct { fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void { - const info = v8.FunctionCallbackInfo.initFromV8(raw_info); - var caller = Caller.init(info); + const v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?; + var caller = Caller.init(v8_isolate); defer caller.deinit(); + const info = CallbackInfo{ .raw = raw_info.? }; caller.method(T, getter, info, .{ .as_typed_array = opts.as_typed_array, .null_as_undefined = opts.null_as_undefined, @@ -184,12 +713,13 @@ pub const Accessor = struct { if (@typeInfo(@TypeOf(setter)) != .null) { accessor.setter = struct { fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void { - const info = v8.FunctionCallbackInfo.initFromV8(raw_info); - std.debug.assert(info.length() == 1); - - var caller = Caller.init(info); + const v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?; + var caller = Caller.init(v8_isolate); defer caller.deinit(); + const info = CallbackInfo{ .raw = raw_info.? }; + std.debug.assert(info.length() == 1); + caller.method(T, setter, info, .{ .as_typed_array = opts.as_typed_array, .null_as_undefined = opts.null_as_undefined, @@ -213,9 +743,11 @@ pub const Indexed = struct { fn init(comptime T: type, comptime getter: anytype, comptime opts: Opts) Indexed { return .{ .getter = struct { fn wrap(idx: u32, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 { - const info = v8.PropertyCallbackInfo.initFromV8(raw_info); - var caller = Caller.init(info); + const v8_isolate = v8.c.v8__PropertyCallbackInfo__GetIsolate(raw_info).?; + var caller = Caller.init(v8_isolate); defer caller.deinit(); + + const info = PropertyCallbackInfo{ .raw = raw_info.? }; return caller.getIndex(T, getter, idx, info, .{ .as_typed_array = opts.as_typed_array, .null_as_undefined = opts.null_as_undefined, @@ -238,9 +770,11 @@ pub const NamedIndexed = struct { fn init(comptime T: type, comptime getter: anytype, setter: anytype, deleter: anytype, comptime opts: Opts) NamedIndexed { const getter_fn = struct { fn wrap(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 { - const info = v8.PropertyCallbackInfo.initFromV8(raw_info); - var caller = Caller.init(info); + const v8_isolate = v8.c.v8__PropertyCallbackInfo__GetIsolate(raw_info).?; + var caller = Caller.init(v8_isolate); defer caller.deinit(); + + const info = PropertyCallbackInfo{ .raw = raw_info.? }; return caller.getNamedIndex(T, getter, .{ .handle = c_name.? }, info, .{ .as_typed_array = opts.as_typed_array, .null_as_undefined = opts.null_as_undefined, @@ -250,10 +784,11 @@ pub const NamedIndexed = struct { const setter_fn = if (@typeInfo(@TypeOf(setter)) == .null) null else struct { fn wrap(c_name: ?*const v8.C_Name, c_value: ?*const v8.C_Value, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 { - const info = v8.PropertyCallbackInfo.initFromV8(raw_info); - var caller = Caller.init(info); + const v8_isolate = v8.c.v8__PropertyCallbackInfo__GetIsolate(raw_info).?; + var caller = Caller.init(v8_isolate); defer caller.deinit(); + const info = PropertyCallbackInfo{ .raw = raw_info.? }; return caller.setNamedIndex(T, setter, .{ .handle = c_name.? }, .{ .handle = c_value.? }, info, .{ .as_typed_array = opts.as_typed_array, .null_as_undefined = opts.null_as_undefined, @@ -263,10 +798,11 @@ pub const NamedIndexed = struct { const deleter_fn = if (@typeInfo(@TypeOf(deleter)) == .null) null else struct { fn wrap(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 { - const info = v8.PropertyCallbackInfo.initFromV8(raw_info); - var caller = Caller.init(info); + const v8_isolate = v8.c.v8__PropertyCallbackInfo__GetIsolate(raw_info).?; + var caller = Caller.init(v8_isolate); defer caller.deinit(); + const info = PropertyCallbackInfo{ .raw = raw_info.? }; return caller.deleteNamedIndex(T, deleter, .{ .handle = c_name.? }, info, .{ .as_typed_array = opts.as_typed_array, .null_as_undefined = opts.null_as_undefined, @@ -308,9 +844,11 @@ pub const Iterator = struct { .async = opts.async, .func = struct { fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void { - const info = v8.FunctionCallbackInfo.initFromV8(raw_info); - var caller = Caller.init(info); + const v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?; + var caller = Caller.init(v8_isolate); defer caller.deinit(); + + const info = CallbackInfo{ .raw = raw_info.? }; caller.method(T, struct_or_func, info, .{}); } }.wrap, @@ -328,9 +866,11 @@ pub const Callable = struct { fn init(comptime T: type, comptime func: anytype, comptime opts: Opts) Callable { return .{ .func = struct { fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void { - const info = v8.FunctionCallbackInfo.initFromV8(raw_info); - var caller = Caller.init(info); + const v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?; + var caller = Caller.init(v8_isolate); defer caller.deinit(); + + const info = CallbackInfo{ .raw = raw_info.? }; caller.method(T, func, info, .{ .null_as_undefined = opts.null_as_undefined, }); diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index 77b89a15..c6f3b36f 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -101,7 +101,7 @@ pub const PersistentPromiseResolver = struct { defer context.runMicrotasks(); const v8_context = v8.Context{ .handle = context.handle }; - if (self.resolver.castToPromiseResolver().resolve(v8_context, js_value) == null) { + if (self.resolver.castToPromiseResolver().resolve(v8_context, js_value.handle) == null) { return error.FailedToResolvePromise; } } @@ -119,17 +119,16 @@ pub const PersistentPromiseResolver = struct { defer context.runMicrotasks(); // resolver.reject will return null if the promise isn't pending - if (self.resolver.castToPromiseResolver().reject(v8_context, js_value) == null) { + if (self.resolver.castToPromiseResolver().reject(v8_context, js_value.handle) == null) { return error.FailedToRejectPromise; } } }; pub const Exception = struct { - inner: v8.Value, - context: *const Context, + ctx: *const Context, + handle: *const v8.c.Value, - // the caller needs to deinit the string returned pub fn exception(self: Exception, allocator: Allocator) ![]const u8 { return self.context.valueToString(self.inner, .{ .allocator = allocator }); } @@ -216,30 +215,30 @@ pub fn isComplexAttributeType(ti: std.builtin.Type) bool { // These are simple types that we can convert to JS with only an isolate. This // is separated from the Caller's zigValueToJs to make it available when we // don't have a caller (i.e., when setting static attributes on types) -pub fn simpleZigValueToJs(isolate: v8.Isolate, value: anytype, comptime fail: bool, comptime null_as_undefined: bool) if (fail) v8.Value else ?v8.Value { +pub fn simpleZigValueToJs(isolate: v8.Isolate, value: anytype, comptime fail: bool, comptime null_as_undefined: bool) if (fail) *const v8.c.Value else ?*const v8.c.Value { switch (@typeInfo(@TypeOf(value))) { - .void => return v8.initUndefined(isolate).toValue(), - .null => if (comptime null_as_undefined) return v8.initUndefined(isolate).toValue() else return v8.initNull(isolate).toValue(), - .bool => return v8.getValue(if (value) v8.initTrue(isolate) else v8.initFalse(isolate)), + .void => return @ptrCast(v8.initUndefined(isolate).handle), + .null => if (comptime null_as_undefined) return @ptrCast(v8.initUndefined(isolate).handle) else return @ptrCast(v8.initNull(isolate).handle), + .bool => return if (value) v8.initTrue(isolate).handle else v8.initFalse(isolate).handle, .int => |n| switch (n.signedness) { .signed => { if (value > 0 and value <= 4_294_967_295) { - return v8.Integer.initU32(isolate, @intCast(value)).toValue(); + return @ptrCast(v8.Integer.initU32(isolate, @intCast(value)).handle); } if (value >= -2_147_483_648 and value <= 2_147_483_647) { - return v8.Integer.initI32(isolate, @intCast(value)).toValue(); + return @ptrCast(v8.Integer.initI32(isolate, @intCast(value)).handle); } if (comptime n.bits <= 64) { - return v8.getValue(v8.BigInt.initI64(isolate, @intCast(value))); + return @ptrCast(v8.BigInt.initI64(isolate, @intCast(value)).handle); } @compileError(@typeName(value) ++ " is not supported"); }, .unsigned => { if (value <= 4_294_967_295) { - return v8.Integer.initU32(isolate, @intCast(value)).toValue(); + return @ptrCast(v8.Integer.initU32(isolate, @intCast(value)).handle); } if (comptime n.bits <= 64) { - return v8.getValue(v8.BigInt.initU64(isolate, @intCast(value))); + return @ptrCast(v8.BigInt.initU64(isolate, @intCast(value)).handle); } @compileError(@typeName(value) ++ " is not supported"); }, @@ -247,29 +246,29 @@ pub fn simpleZigValueToJs(isolate: v8.Isolate, value: anytype, comptime fail: bo .comptime_int => { if (value >= 0) { if (value <= 4_294_967_295) { - return v8.Integer.initU32(isolate, @intCast(value)).toValue(); + return @ptrCast(v8.Integer.initU32(isolate, @intCast(value)).handle); } - return v8.BigInt.initU64(isolate, @intCast(value)).toValue(); + return @ptrCast(v8.BigInt.initU64(isolate, @intCast(value)).handle); } if (value >= -2_147_483_648) { - return v8.Integer.initI32(isolate, @intCast(value)).toValue(); + return @ptrCast(v8.Integer.initI32(isolate, @intCast(value)).handle); } - return v8.BigInt.initI64(isolate, @intCast(value)).toValue(); + return @ptrCast(v8.BigInt.initI64(isolate, @intCast(value)).handle); }, - .comptime_float => return v8.Number.init(isolate, value).toValue(), + .comptime_float => return @ptrCast(v8.Number.init(isolate, value).handle), .float => |f| switch (f.bits) { - 64 => return v8.Number.init(isolate, value).toValue(), - 32 => return v8.Number.init(isolate, @floatCast(value)).toValue(), + 64 => return @ptrCast(v8.Number.init(isolate, value).handle), + 32 => return @ptrCast(v8.Number.init(isolate, @floatCast(value)).handle), else => @compileError(@typeName(value) ++ " is not supported"), }, .pointer => |ptr| { if (ptr.size == .slice and ptr.child == u8) { - return v8.String.initUtf8(isolate, value).toValue(); + return @ptrCast(v8.String.initUtf8(isolate, value).handle); } if (ptr.size == .one) { const one_info = @typeInfo(ptr.child); if (one_info == .array and one_info.array.child == u8) { - return v8.String.initUtf8(isolate, value).toValue(); + return @ptrCast(v8.String.initUtf8(isolate, value).handle); } } }, @@ -279,9 +278,9 @@ pub fn simpleZigValueToJs(isolate: v8.Isolate, value: anytype, comptime fail: bo return simpleZigValueToJs(isolate, v, fail, null_as_undefined); } if (comptime null_as_undefined) { - return v8.initUndefined(isolate).toValue(); + return @ptrCast(v8.initUndefined(isolate).handle); } - return v8.initNull(isolate).toValue(); + return @ptrCast(v8.initNull(isolate).handle); }, .@"struct" => { switch (@TypeOf(value)) { @@ -294,7 +293,7 @@ pub fn simpleZigValueToJs(isolate: v8.Isolate, value: anytype, comptime fail: bo @memcpy(data[0..len], @as([]const u8, @ptrCast(values))[0..len]); array_buffer = v8.ArrayBuffer.initWithBackingStore(isolate, &backing_store.toSharedPtr()); - return .{ .handle = array_buffer.handle }; + return @ptrCast(array_buffer.handle); }, // zig fmt: off TypedArray(u8), TypedArray(u16), TypedArray(u32), TypedArray(u64), @@ -325,23 +324,23 @@ pub fn simpleZigValueToJs(isolate: v8.Isolate, value: anytype, comptime fail: bo switch (@typeInfo(value_type)) { .int => |n| switch (n.signedness) { .unsigned => switch (n.bits) { - 8 => return v8.Uint8Array.init(array_buffer, 0, len).toValue(), - 16 => return v8.Uint16Array.init(array_buffer, 0, len).toValue(), - 32 => return v8.Uint32Array.init(array_buffer, 0, len).toValue(), - 64 => return v8.BigUint64Array.init(array_buffer, 0, len).toValue(), + 8 => return @ptrCast(v8.Uint8Array.init(array_buffer, 0, len).handle), + 16 => return @ptrCast(v8.Uint16Array.init(array_buffer, 0, len).handle), + 32 => return @ptrCast(v8.Uint32Array.init(array_buffer, 0, len).handle), + 64 => return @ptrCast(v8.BigUint64Array.init(array_buffer, 0, len).handle), else => {}, }, .signed => switch (n.bits) { - 8 => return v8.Int8Array.init(array_buffer, 0, len).toValue(), - 16 => return v8.Int16Array.init(array_buffer, 0, len).toValue(), - 32 => return v8.Int32Array.init(array_buffer, 0, len).toValue(), - 64 => return v8.BigInt64Array.init(array_buffer, 0, len).toValue(), + 8 => return @ptrCast(v8.Int8Array.init(array_buffer, 0, len).handle), + 16 => return @ptrCast(v8.Int16Array.init(array_buffer, 0, len).handle), + 32 => return @ptrCast(v8.Int32Array.init(array_buffer, 0, len).handle), + 64 => return @ptrCast(v8.BigInt64Array.init(array_buffer, 0, len).handle), else => {}, }, }, .float => |f| switch (f.bits) { - 32 => return v8.Float32Array.init(array_buffer, 0, len).toValue(), - 64 => return v8.Float64Array.init(array_buffer, 0, len).toValue(), + 32 => return @ptrCast(v8.Float32Array.init(array_buffer, 0, len).handle), + 64 => return @ptrCast(v8.Float64Array.init(array_buffer, 0, len).handle), else => {}, }, else => {}, @@ -368,10 +367,6 @@ pub fn simpleZigValueToJs(isolate: v8.Isolate, value: anytype, comptime fail: bo return null; } -pub fn _createException(isolate: v8.Isolate, msg: []const u8) v8.Value { - return v8.Exception.initError(v8.String.initUtf8(isolate, msg)); -} - pub fn classNameForStruct(comptime Struct: type) []const u8 { if (@hasDecl(Struct, "js_name")) { return Struct.js_name;