migrate most cases, merge Caller into bridge

This commit is contained in:
Karl Seguin
2026-01-01 16:26:18 +08:00
parent 01ecd725b8
commit bc11a48e6b
12 changed files with 1079 additions and 349 deletions

View File

@@ -25,7 +25,7 @@ const js = @import("js.zig");
const v8 = js.v8; const v8 = js.v8;
const bridge = @import("bridge.zig"); const bridge = @import("bridge.zig");
const Caller = @import("Caller.zig"); const Caller = bridge.Caller;
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const ScriptManager = @import("../ScriptManager.zig"); const ScriptManager = @import("../ScriptManager.zig");
@@ -52,7 +52,7 @@ handle_scope: ?js.HandleScope,
cpu_profiler: ?v8.CpuProfiler = null, cpu_profiler: ?v8.CpuProfiler = null,
// references Env.templates // references Env.templates
templates: []v8.FunctionTemplate, templates: []*const v8.c.FunctionTemplate,
// Arena for the lifetime of the context // Arena for the lifetime of the context
arena: Allocator, 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_functions: std.ArrayList(js.Global(js.Function)) = .empty,
global_promise_resolvers: std.ArrayList(js.Global(js.PromiseResolver)) = .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. // Our module cache: normalized module specifier => module.
module_cache: std.StringHashMapUnmanaged(ModuleEntry) = .empty, module_cache: std.StringHashMapUnmanaged(ModuleEntry) = .empty,
@@ -138,21 +135,10 @@ pub fn fromIsolate(isolate: js.Isolate) *Context {
pub fn setupGlobal(self: *Context) !void { pub fn setupGlobal(self: *Context) !void {
const global = v8.c.v8__Context__Global(self.handle).?; const global = v8.c.v8__Context__Global(self.handle).?;
const v8_obj = v8.Object{ .handle = global }; 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 { 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(); var it = self.identity_map.valueIterator();
while (it.next()) |p| { 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 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) { if (cacheable) {
// compileModule is synchronous - nothing can modify the cache during compilation // 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 // Some module-loading errors aren't handled by TryCatch. We need to
// get the error from the module itself. // get the error from the module itself.
const v8_exception = mod.getException();
log.warn(.js, "evaluate module", .{ log.warn(.js, "evaluate module", .{
.specifier = owned_url, .specifier = owned_url,
.message = self.valueToString(mod.getException(), .{}) catch "???", .message = self.valueToString(js.Value{ .ctx = self, .handle = v8_exception.handle }, .{}) catch "???",
}); });
return error.EvaluationError; return error.EvaluationError;
}; };
@@ -315,7 +302,7 @@ pub fn stringToFunction(self: *Context, str: []const u8) !js.Function {
if (!js_value.isFunction()) { if (!js_value.isFunction()) {
return error.StringFunctionError; 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, // 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 { pub fn createObject(self: *Context, js_value: js.Value) js.Object {
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 {
return .{ return .{
.ctx = self, .ctx = self,
.handle = @ptrCast(js_value.handle), .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 // caller should have made sure this was a function
if (comptime IS_DEBUG) { if (comptime IS_DEBUG) {
std.debug.assert(js_value.isFunction()); 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 { pub fn newString(self: *Context, str: []const u8) js.String {
return .{ return .{
.ctx = self, .ctx = self,
.handle = self.isolate.newStringHandle(str), .handle = self.isolate.createStringHandle(str),
}; };
} }
pub fn throw(self: *Context, err: []const u8) js.Exception { pub fn throw(self: *Context, err: []const u8) js.Exception {
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; const handle = self.isolate.createError(err);
const js_value = js._createException(v8_isolate, err); return .{
return self.createException(js_value); .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 isolate = self.isolate;
const v8_isolate = v8.Isolate{ .handle = isolate.handle }; const v8_isolate = v8.Isolate{ .handle = isolate.handle };
// Check if it's a "simple" type. This is extracted so that it can be // 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 // reused by other parts of the code. "simple" types only require an
// isolate to create (specifically, they don't our templates array) // isolate to create (specifically, they don't our templates array)
if (js.simpleZigValueToJs(v8_isolate, value, false, opts.null_as_undefined)) |js_value| { if (js.simpleZigValueToJs(v8_isolate, value, false, opts.null_as_undefined)) |js_value_handle| {
return js_value; return .{ .ctx = self, .handle = js_value_handle };
} }
const v8_context = v8.Context{ .handle = self.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; unreachable;
}, },
.array => { .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); var js_obj = js_arr.castTo(v8.Object);
for (value, 0..) |v, i| { for (value, 0..) |v, i| {
const js_val = try self.zigValueToJs(v, opts); 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 error.FailedToCreateArray;
} }
} }
return js_obj.toValue(); return .{ .ctx = self, .handle = @ptrCast(js_obj.handle) };
}, },
.pointer => |ptr| switch (ptr.size) { .pointer => |ptr| switch (ptr.size) {
.one => { .one => {
if (@typeInfo(ptr.child) == .@"struct" and @hasDecl(ptr.child, "JsApi")) { if (@typeInfo(ptr.child) == .@"struct" and @hasDecl(ptr.child, "JsApi")) {
if (bridge.JsApiLookup.has(ptr.child.JsApi)) { if (bridge.JsApiLookup.has(ptr.child.JsApi)) {
const js_obj = try self.mapZigInstanceToJs(null, value); 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 // have handled it
unreachable; 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); var js_obj = js_arr.castTo(v8.Object);
for (value, 0..) |v, i| { for (value, 0..) |v, i| {
const js_val = try self.zigValueToJs(v, opts); 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 error.FailedToCreateArray;
} }
} }
return js_obj.toValue(); return .{ .ctx = self, .handle = @ptrCast(js_obj.handle) };
}, },
else => {}, else => {},
}, },
@@ -480,31 +455,31 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp
if (@hasDecl(T, "JsApi")) { if (@hasDecl(T, "JsApi")) {
if (bridge.JsApiLookup.has(T.JsApi)) { if (bridge.JsApiLookup.has(T.JsApi)) {
const js_obj = try self.mapZigInstanceToJs(null, value); const js_obj = try self.mapZigInstanceToJs(null, value);
return js_obj.toValue(); return .{ .ctx = self, .handle = @ptrCast(js_obj.handle) };
} }
} }
if (T == js.Function) { if (T == js.Function) {
// we're returning a callback // we're returning a callback
return .{ .handle = @ptrCast(value.handle) }; return .{ .ctx = self, .handle = @ptrCast(value.handle) };
} }
if (T == js.Object) { if (T == js.Object) {
// we're returning a v8.Object // we're returning a v8.Object
return .{ .handle = @ptrCast(value.handle) }; return .{ .ctx = self, .handle = @ptrCast(value.handle) };
} }
if (T == js.Value) { if (T == js.Value) {
return .{ .handle = value.handle }; return value;
} }
if (T == js.Promise) { if (T == js.Promise) {
// we're returning a js.Promise // we're returning a js.Promise
return .{ .handle = @ptrCast(value.handle) }; return .{ .ctx = self, .handle = @ptrCast(value.handle) };
} }
if (T == js.Exception) { if (T == js.Exception) {
return isolate.throwException(value.inner); return .{ .ctx = self, .handle = isolate.throwException(value.handle) };
} }
if (@hasDecl(T, "runtimeGenericWrap")) { if (@hasDecl(T, "runtimeGenericWrap")) {
@@ -514,31 +489,31 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp
if (s.is_tuple) { if (s.is_tuple) {
// return the tuple struct as an array // 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); var js_obj = js_arr.castTo(v8.Object);
inline for (s.fields, 0..) |f, i| { inline for (s.fields, 0..) |f, i| {
const js_val = try self.zigValueToJs(@field(value, f.name), opts); 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 error.FailedToCreateArray;
} }
} }
return js_obj.toValue(); return .{ .ctx = self, .handle = @ptrCast(js_obj.handle) };
} }
// return the struct as a JS object // 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| { inline for (s.fields) |f| {
const js_val = try self.zigValueToJs(@field(value, f.name), opts); const js_val = try self.zigValueToJs(@field(value, f.name), opts);
const key = v8.String.initUtf8(v8_isolate, f.name); const key = isolate.initString(f.name);
if (!js_obj.setValue(v8_context, key, js_val)) { if (!js_obj.setValue(v8_context, key, .{ .handle = js_val.handle })) {
return error.CreateObjectFailure; return error.CreateObjectFailure;
} }
} }
return js_obj.toValue(); return .{ .ctx = self, .handle = @ptrCast(js_obj.handle) };
}, },
.@"union" => |un| { .@"union" => |un| {
if (T == std.json.Value) { if (T == std.json.Value) {
return zigJsonToJs(isolate, v8_context, value); return zigJsonToJs(self, value);
} }
if (un.tag_type) |UnionTagType| { if (un.tag_type) |UnionTagType| {
inline for (un.fields) |field| { 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 // 4 - Store our TaggedAnyOpaque into the persistent object
// 5 - Update our identity_map (so that, if we return this same instance again, // 5 - Update our identity_map (so that, if we return this same instance again,
// we can just grab it from the identity_map) // 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 v8_context = v8.Context{ .handle = self.handle };
const arena = self.arena; 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 // Struct, has to be placed on the heap
const heap = try arena.create(T); const heap = try arena.create(T);
heap.* = value; heap.* = value;
return self.mapZigInstanceToJs(js_obj_, heap); return self.mapZigInstanceToJs(js_obj_handle, heap);
}, },
.pointer => |ptr| { .pointer => |ptr| {
const resolved = resolveValue(value); 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 // Sometimes though we already have the v8.Objct to bind to
// for example, when we're executing a constructor, v8 has // for example, when we're executing a constructor, v8 has
// already created the "this" object. // already created the "this" object.
const js_obj = js_obj_ orelse blk: { const js_obj = if (js_obj_handle) |handle|
const template = self.templates[resolved.class_id]; v8.Object{ .handle = handle }
break :blk template.getInstanceTemplate().initInstance(v8_context); 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")) { 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 { pub fn jsValueToZig(self: *Context, comptime T: type, js_value: js.Value) !T {
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const v8_context = v8.Context{ .handle = self.handle }; const v8_context = v8.Context{ .handle = self.handle };
switch (@typeInfo(T)) { switch (@typeInfo(T)) {
.optional => |o| { .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); return try self.jsValueToZig(o.child, js_value);
}, },
.float => |f| switch (f.bits) { .float => |f| switch (f.bits) {
0...32 => return js_value.toF32(v8_context), 0...32 => return js_value.toF32(),
33...64 => return js_value.toF64(v8_context), 33...64 => return js_value.toF64(),
else => {}, else => {},
}, },
.int => return jsIntToZig(T, js_value, v8_context), .int => return jsIntToZig(T, js_value),
.bool => return js_value.toBool(v8_isolate), .bool => return js_value.toBool(),
.pointer => |ptr| switch (ptr.size) { .pointer => |ptr| switch (ptr.size) {
.one => { .one => {
if (!js_value.isObject()) { 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")) { if (@hasDecl(ptr.child, "JsApi")) {
std.debug.assert(bridge.JsApiLookup.has(ptr.child.JsApi)); std.debug.assert(bridge.JsApiLookup.has(ptr.child.JsApi));
const js_obj = js_value.castTo(v8.Object); const js_obj = js_value.castTo(v8.Object);
return typeTaggedAnyOpaque(*ptr.child, js_obj); return typeTaggedAnyOpaque(*ptr.child, js_obj.handle);
} }
}, },
.slice => { .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) // to do this (V8::Array has an iterate method on it)
const arr = try self.call_arena.alloc(ptr.child, js_arr.length()); const arr = try self.call_arena.alloc(ptr.child, js_arr.length());
for (arr, 0..) |*a, i| { 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; 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 // Extracted so that it can be used in both jsValueToZig and in
// probeJsValueToZig. Avoids having to duplicate this logic when probing. // 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) { return switch (T) {
js.Function => { js.Function => {
if (!js_value.isFunction()) { 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 js_obj = js_value.castTo(v8.Object);
const v8_context = v8.Context{ .handle = self.handle }; const v8_context = v8.Context{ .handle = self.handle };
const isolate = self.isolate; const isolate = self.isolate;
const v8_isolate = v8.Isolate{ .handle = isolate.handle };
var value: T = undefined; var value: T = undefined;
inline for (@typeInfo(T).@"struct".fields) |field| { inline for (@typeInfo(T).@"struct".fields) |field| {
const name = field.name; 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())) { 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) { } else if (@typeInfo(field.type) == .optional) {
@field(value, name) = null; @field(value, name) = null;
} else { } 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 force_u8 = false;
var array_buffer: ?v8.ArrayBuffer = null; var array_buffer: ?v8.ArrayBuffer = null;
var byte_len: usize = undefined; var byte_len: usize = undefined;
@@ -1017,61 +996,73 @@ fn resolveT(comptime T: type, value: *anyopaque) Resolved {
const valueToStringOpts = struct { const valueToStringOpts = struct {
allocator: ?Allocator = null, 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; const allocator = opts.allocator orelse self.call_arena;
if (js_val.isSymbol()) { if (js_val.isSymbol()) {
const js_sym = v8.Symbol{ .handle = js_val.handle }; const js_sym = v8.Symbol{ .handle = js_val.handle };
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const js_sym_desc = js_sym.getDescription(v8_isolate); 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_handle = v8.c.v8__Value__ToString(js_val.handle, self.handle) orelse {
const str = try js_val.toString(v8_context); return error.JsException;
};
const str = v8.String{ .handle = str_handle };
return self.jsStringToZig(str, .{ .allocator = allocator }); 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; const allocator = opts.allocator orelse self.call_arena;
if (js_val.isSymbol()) { if (js_val.isSymbol()) {
const js_sym = v8.Symbol{ .handle = js_val.handle }; const js_sym = v8.Symbol{ .handle = js_val.handle };
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const js_sym_desc = js_sym.getDescription(v8_isolate); 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_handle = v8.c.v8__Value__ToString(js_val.handle, self.handle) orelse {
const str = try js_val.toString(v8_context); return error.JsException;
};
const str = v8.String{ .handle = str_handle };
return self.jsStringToZigZ(str, .{ .allocator = allocator }); return self.jsStringToZigZ(str, .{ .allocator = allocator });
} }
const JsStringToZigOpts = struct { const JsStringToZigOpts = struct {
allocator: ?Allocator = null, 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 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 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 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); std.debug.assert(n == len);
return buf; 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 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 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 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); std.debug.assert(n == len);
return buf; 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; var seen: std.AutoHashMapUnmanaged(u32, void) = .empty;
return _debugValue(self, js_val, &seen, 0, writer) catch error.WriteFailed; 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()) { if (js_val.isNull()) {
// I think null can sometimes appear as an object, so check this and // I think null can sometimes appear as an object, so check this and
// handle it first. // handle it first.
@@ -1095,10 +1086,10 @@ fn _debugValue(self: *const Context, js_val: v8.Value, seen: *std.AutoHashMapUnm
if (js_val.isSymbol()) { if (js_val.isSymbol()) {
const js_sym = v8.Symbol{ .handle = js_val.handle }; const js_sym = v8.Symbol{ .handle = js_val.handle };
const js_sym_desc = js_sym.getDescription(v8_isolate); 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}); 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, .{}); const js_val_str = try self.valueToString(js_val, .{});
if (js_val_str.len > 2000) { if (js_val_str.len > 2000) {
try writer.writeAll(js_val_str[0..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'); try writer.writeByte('\n');
} }
const field_name = try names_obj.getAtIndex(v8_context, @intCast(i)); 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.splatByteAll(' ', depth);
try writer.writeAll(name); try writer.writeAll(name);
try writer.writeAll(": "); 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) { if (i != len - 1) {
try writer.writeByte('\n'); try writer.writeByte('\n');
} }
@@ -1220,7 +1212,7 @@ fn resolveModuleCallback(
const self = fromC(c_context.?); 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 }); log.err(.js, "resolve module", .{ .err = err });
return null; return null;
}; };
@@ -1247,12 +1239,12 @@ pub fn dynamicModuleCallback(
const self = fromC(c_context.?); 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" }); log.err(.app, "OOM", .{ .err = err, .src = "dynamicModuleCallback1" });
return @constCast((self.rejectPromise("Out of memory") catch return null).handle); 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" }); log.err(.app, "OOM", .{ .err = err, .src = "dynamicModuleCallback2" });
return @constCast((self.rejectPromise("Out of memory") catch return null).handle); 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 { 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 self = fromC(c_context.?);
const m = js.Module{ .handle = c_module.? }; 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 { const url = self.module_identifier.get(m.getIdentityHash()) orelse {
// Shouldn't be possible. // Shouldn't be possible.
@@ -1286,11 +1278,11 @@ pub fn metaObjectCallback(c_context: ?*v8.C_Context, c_module: ?*v8.C_Module, c_
return; return;
}; };
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; const js_value = self.zigValueToJs(url, .{}) catch {
const js_key = v8.String.initUtf8(v8_isolate, "url"); log.err(.js, "import meta", .{ .err = error.FailedToConvertUrl });
const js_value = try self.zigValueToJs(url, .{}); return;
const v8_context = v8.Context{ .handle = self.handle }; };
const res = meta.defineOwnProperty(v8_context, js_key.toName(), js_value, 0) orelse false; const res = meta.defineOwnProperty("url", js_value, 0) orelse false;
if (!res) { if (!res) {
log.err(.js, "import meta", .{ .err = error.FailedToSet }); 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(); defer try_catch.deinit();
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; 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); try self.postCompileModule(mod, normalized_specifier);
const v8_module = v8.Module{ .handle = mod.handle }; const v8_module = v8.Module{ .handle = mod.handle };
entry.module = PersistentModule.init(v8_isolate, v8_module); 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. // Next, we need to actually load it.
self.script_manager.?.getAsyncImport(specifier, dynamicModuleSourceCallback, state, referrer) catch |err| { 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()); _ = 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 // the module was loaded, but not evaluated, we _have_ to evaluate it now
const evaluated = mod.evaluate(v8_context) catch { const evaluated = mod.evaluate(v8_context) catch {
std.debug.assert(status == .kErrored); 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()); _ = resolver.reject(v8_context, error_msg.toValue());
const v8_promise = promise; const v8_promise = promise;
return .{ .handle = v8_promise.handle }; return .{ .handle = v8_promise.handle };
@@ -1450,8 +1442,7 @@ fn dynamicModuleSourceCallback(ctx: *anyopaque, module_source_: anyerror!ScriptM
var self = state.context; var self = state.context;
var ms = module_source_ catch |err| { var ms = module_source_ catch |err| {
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; const error_msg = self.isolate.initString(@errorName(err));
const error_msg = v8.String.initUtf8(v8_isolate, @errorName(err));
const v8_context = v8.Context{ .handle = self.handle }; const v8_context = v8.Context{ .handle = self.handle };
_ = state.resolver.castToPromiseResolver().reject(v8_context, error_msg.toValue()); _ = state.resolver.castToPromiseResolver().reject(v8_context, error_msg.toValue());
return; return;
@@ -1472,9 +1463,8 @@ fn dynamicModuleSourceCallback(ctx: *anyopaque, module_source_: anyerror!ScriptM
.stack = try_catch.stack(self.call_arena) catch null, .stack = try_catch.stack(self.call_arena) catch null,
.line = try_catch.sourceLineNumber() orelse 0, .line = try_catch.sourceLineNumber() orelse 0,
}); });
const v8_isolate = v8.Isolate{ .handle = self.isolate.handle };
const v8_context = v8.Context{ .handle = self.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()); _ = state.resolver.castToPromiseResolver().reject(v8_context, error_msg.toValue());
return; return;
}; };
@@ -1506,10 +1496,12 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul
const then_callback = v8.Function.initWithData(ctx, struct { const then_callback = v8.Function.initWithData(ctx, struct {
pub fn callback(raw_info: ?*const v8.c.FunctionCallbackInfo) callconv(.c) void { pub fn callback(raw_info: ?*const v8.c.FunctionCallbackInfo) callconv(.c) void {
var info = v8.FunctionCallbackInfo.initFromV8(raw_info); const callback_v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?;
var caller = Caller.init(info); var caller = Caller.init(callback_v8_isolate);
defer caller.deinit(); defer caller.deinit();
var info = v8.FunctionCallbackInfo.initFromV8(raw_info);
const s: *DynamicModuleResolveState = @ptrCast(@alignCast(info.getExternalValue())); const s: *DynamicModuleResolveState = @ptrCast(@alignCast(info.getExternalValue()));
if (s.context_id != caller.context.id) { 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 { const catch_callback = v8.Function.initWithData(ctx, struct {
pub fn callback(raw_info: ?*const v8.c.FunctionCallbackInfo) callconv(.c) void { pub fn callback(raw_info: ?*const v8.c.FunctionCallbackInfo) callconv(.c) void {
var info = v8.FunctionCallbackInfo.initFromV8(raw_info); const callback_v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?;
var caller = Caller.init(info); var caller = Caller.init(callback_v8_isolate);
defer caller.deinit(); defer caller.deinit();
var info = v8.FunctionCallbackInfo.initFromV8(raw_info);
const s: *DynamicModuleResolveState = @ptrCast(@alignCast(info.getExternalValue())); const s: *DynamicModuleResolveState = @ptrCast(@alignCast(info.getExternalValue()));
if (s.context_id != caller.context.id) { if (s.context_id != caller.context.id) {
return; return;
@@ -1549,7 +1543,7 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul
.err = err, .err = err,
.specifier = state.specifier, .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()); _ = 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 // Reverses the mapZigInstanceToJs, making sure that our TaggedAnyOpaque
// contains a ptr to the correct type. // 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); const ti = @typeInfo(R);
if (ti != .pointer) { if (ti != .pointer) {
@compileError("non-pointer Zig parameter type: " ++ @typeName(R)); @compileError("non-pointer Zig parameter type: " ++ @typeName(R));
@@ -1671,7 +1667,7 @@ fn ProbeResult(comptime T: type) type {
invalid: void, 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)) { switch (@typeInfo(T)) {
.optional => |o| { .optional => |o| {
if (js_value.isNullOrUndefined()) { 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 // of having a version of typeTaggedAnyOpaque which
// returns a boolean or an optional, we rely on the // returns a boolean or an optional, we rely on the
// main implementation and just handle the error. // 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| { if (attempt) |value| {
return .{ .value = value }; return .{ .value = value };
} else |_| { } else |_| {
@@ -1788,7 +1784,8 @@ fn probeJsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !Prob
// not tricky in this case either. // not tricky in this case either.
const v8_context = v8.Context{ .handle = self.handle }; const v8_context = v8.Context{ .handle = self.handle };
const js_obj = js_arr.castTo(v8.Object); 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 = {} }, .value, .ok => return .{ .ok = {} },
.compatible => return .{ .compatible = {} }, .compatible => return .{ .compatible = {} },
.coerce => return .{ .coerce = {} }, .coerce => return .{ .coerce = {} },
@@ -1841,25 +1838,25 @@ fn probeJsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !Prob
return .{ .invalid = {} }; 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; const n = @typeInfo(T).int;
switch (n.signedness) { switch (n.signedness) {
.signed => switch (n.bits) { .signed => switch (n.bits) {
8 => return jsSignedIntToZig(i8, -128, 127, 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(v8_context)), 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(v8_context)), 32 => return jsSignedIntToZig(i32, -2_147_483_648, 2_147_483_647, try js_value.toI32()),
64 => { 64 => {
if (js_value.isBigInt()) { if (js_value.isBigInt()) {
const v = js_value.castTo(v8.BigInt); const v = js_value.castTo(v8.BigInt);
return v.getInt64(); 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 => {}, else => {},
}, },
.unsigned => switch (n.bits) { .unsigned => switch (n.bits) {
8 => return jsUnsignedIntToZig(u8, 255, 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(v8_context)), 16 => return jsUnsignedIntToZig(u16, 65_535, try js_value.toU32()),
32 => { 32 => {
if (js_value.isBigInt()) { if (js_value.isBigInt()) {
const v = js_value.castTo(v8.BigInt); 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 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 => { 64 => {
if (js_value.isBigInt()) { if (js_value.isBigInt()) {
const v = js_value.castTo(v8.BigInt); const v = js_value.castTo(v8.BigInt);
return v.getUint64(); 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 => {}, 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 { fn compileAndRun(self: *Context, src: []const u8, name: ?[]const u8) !js.Value {
const script_name = self.isolate.newStringHandle(name orelse "anonymous"); const script_name = self.isolate.createStringHandle(name orelse "anonymous");
const script_source = self.isolate.newStringHandle(src); const script_source = self.isolate.createStringHandle(src);
// Create ScriptOrigin // Create ScriptOrigin
var origin: v8.c.ScriptOrigin = undefined; 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 }; 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 // compile
const script_name = v8.String.initUtf8(isolate, name); const v8_isolate = v8.Isolate{ .handle = isolate.handle };
const script_source = v8.String.initUtf8(isolate, src); const script_name = isolate.initString(name);
const script_source = isolate.initString(src);
const origin = v8.ScriptOrigin.init( const origin = v8.ScriptOrigin.init(
script_name.toValue(), 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(); defer script_comp_source.deinit();
const v8_module = v8.ScriptCompiler.compileModule( const v8_module = v8.ScriptCompiler.compileModule(
isolate, v8_isolate,
&script_comp_source, &script_comp_source,
.kNoCompileOptions, .kNoCompileOptions,
.kNoCacheNoReason, .kNoCacheNoReason,
@@ -1955,40 +1953,44 @@ fn compileModule(isolate: v8.Isolate, src: []const u8, name: []const u8) !js.Mod
return .{ .handle = v8_module.handle }; 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) { switch (value) {
.bool => |v| return js.simpleZigValueToJs(isolate, v, true, false), .bool => |v| return .{ .ctx = self, .handle = js.simpleZigValueToJs(v8_isolate, v, true, false) },
.float => |v| return js.simpleZigValueToJs(isolate, v, true, false), .float => |v| return .{ .ctx = self, .handle = js.simpleZigValueToJs(v8_isolate, v, true, false) },
.integer => |v| return js.simpleZigValueToJs(isolate, v, true, false), .integer => |v| return .{ .ctx = self, .handle = js.simpleZigValueToJs(v8_isolate, v, true, false) },
.string => |v| return js.simpleZigValueToJs(isolate, v, true, false), .string => |v| return .{ .ctx = self, .handle = js.simpleZigValueToJs(v8_isolate, v, true, false) },
.null => return isolate.initNull().toValue(), .null => return .{ .ctx = self, .handle = isolate.initNull() },
// TODO handle number_string. // TODO handle number_string.
// It is used to represent too big numbers. // It is used to represent too big numbers.
.number_string => return error.TODO, .number_string => return error.TODO,
.array => |v| { .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); const obj = a.castTo(v8.Object);
for (v.items, 0..) |array_value, i| { for (v.items, 0..) |array_value, i| {
const js_val = try zigJsonToJs(isolate, v8_context, array_value); const js_val = try zigJsonToJs(self, array_value);
if (!obj.setValueAtIndex(v8_context, @intCast(i), js_val)) { if (!obj.setValueAtIndex(v8_context, @intCast(i), .{ .handle = js_val.handle })) {
return error.JSObjectSetValue; return error.JSObjectSetValue;
} }
} }
return obj.toValue(); return .{ .ctx = self, .handle = @ptrCast(obj.handle) };
}, },
.object => |v| { .object => |v| {
var obj = v8.Object.init(isolate); var obj = isolate.initObject();
var it = v.iterator(); var it = v.iterator();
while (it.next()) |kv| { while (it.next()) |kv| {
const js_key = v8.String.initUtf8(isolate, kv.key_ptr.*); const js_key = isolate.initString(kv.key_ptr.*);
const js_val = try zigJsonToJs(isolate, v8_context, kv.value_ptr.*); const js_val = try zigJsonToJs(self, kv.value_ptr.*);
if (!obj.setValue(v8_context, js_key, js_val)) { if (!obj.setValue(v8_context, js_key, .{ .handle = js_val.handle })) {
return error.JSObjectSetValue; return error.JSObjectSetValue;
} }
} }
return obj.toValue(); return .{ .ctx = self, .handle = @ptrCast(obj.handle) };
}, },
} }
} }

View File

@@ -49,15 +49,18 @@ platform: *const Platform,
isolate: js.Isolate, isolate: js.Isolate,
// just kept around because we need to free it on deinit // just kept around because we need to free it on deinit
isolate_params: *v8.CreateParams, isolate_params: *v8.c.CreateParams,
context_id: usize, 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 // 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 { 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); errdefer allocator.destroy(params);
v8.c.v8__Isolate__CreateParams__CONSTRUCT(params); v8.c.v8__Isolate__CreateParams__CONSTRUCT(params);
params.snapshot_blob = @ptrCast(&snapshot.startup_data); 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; params.external_references = &snapshot.external_references;
var v8_isolate = v8.Isolate.init(params); var isolate = js.Isolate.init(params);
errdefer v8_isolate.deinit(); errdefer isolate.deinit();
// This is the callback that runs whenever a module is dynamically imported. v8.c.v8__Isolate__SetHostImportModuleDynamicallyCallback(isolate.handle, Context.dynamicModuleCallback);
v8_isolate.setHostImportModuleDynamicallyCallback(Context.dynamicModuleCallback); v8.c.v8__Isolate__SetPromiseRejectCallback(isolate.handle, promiseRejectCallback);
v8_isolate.setPromiseRejectCallback(promiseRejectCallback); v8.c.v8__Isolate__SetMicrotasksPolicy(isolate.handle, v8.c.kExplicit);
v8_isolate.setMicrotasksPolicy(v8.c.kExplicit);
v8_isolate.enter(); isolate.enter();
errdefer v8_isolate.exit(); 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(*const v8.c.FunctionTemplate, JsApis.len);
const templates = try allocator.alloc(v8.FunctionTemplate, JsApis.len);
errdefer allocator.free(templates); errdefer allocator.free(templates);
{ {
var temp_scope: js.HandleScope = undefined; var temp_scope: js.HandleScope = undefined;
temp_scope.init(isolate); temp_scope.init(isolate);
defer temp_scope.deinit(); defer temp_scope.deinit();
const context = v8.Context.init(v8_isolate, null, null); const context_handle = isolate.createContextHandle(null, null);
context.enter(); v8.c.v8__Context__Enter(context_handle);
defer context.exit(); defer v8.c.v8__Context__Exit(context_handle);
inline for (JsApis, 0..) |JsApi, i| { inline for (JsApis, 0..) |JsApi, i| {
JsApi.Meta.class_id = i; JsApi.Meta.class_id = i;
const data = context.getDataFromSnapshotOnce(snapshot.data_start + i); const data = v8.c.v8__Context__GetDataFromSnapshotOnce(context_handle, snapshot.data_start + i);
const function = v8.FunctionTemplate{ .handle = @ptrCast(data) }; const function_handle: *const v8.c.FunctionTemplate = @ptrCast(data);
templates[i] = v8.Persistent(v8.FunctionTemplate).init(v8_isolate, function).castToFunctionTemplate(); // 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, .isolate = isolate,
.platform = platform, .platform = platform,
.allocator = allocator, .allocator = allocator,
.globals = globals,
.templates = templates, .templates = templates,
.isolate_params = params, .isolate_params = params,
}; };
} }
pub fn deinit(self: *Env) void { 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.exit();
self.isolate.deinit(); self.isolate.deinit();
v8.destroyArrayBufferAllocator(self.isolate_params.array_buffer_allocator.?); v8.destroyArrayBufferAllocator(self.isolate_params.array_buffer_allocator.?);
self.allocator.destroy(self.isolate_params); self.allocator.destroy(self.isolate_params);
self.allocator.free(self.templates);
} }
pub fn newInspector(self: *Env, arena: Allocator, ctx: anytype) !*Inspector { 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 { fn promiseRejectCallback(v8_msg: v8.C_PromiseRejectMessage) callconv(.c) void {
const msg = v8.PromiseRejectMessage.initFromC(v8_msg); const msg = v8.PromiseRejectMessage.initFromC(v8_msg);
const v8_isolate = msg.getPromise().toObject().getIsolate(); const isolate_handle = v8.c.v8__Object__GetIsolate(@ptrCast(msg.getPromise().handle)).?;
const js_isolate = js.Isolate{ .handle = v8_isolate.handle }; const js_isolate = js.Isolate{ .handle = isolate_handle };
const context = Context.fromIsolate(js_isolate); const context = Context.fromIsolate(js_isolate);
const value = const value =
if (msg.getValue()) |v8_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 else
"no value"; "no value";

View File

@@ -76,24 +76,33 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
const isolate = env.isolate; const isolate = env.isolate;
const arena = self.context_arena.allocator(); const arena = self.context_arena.allocator();
var v8_context: v8.Context = blk: { const context_handle: *const v8.c.Context = blk: {
const v8_isolate = v8.Isolate{ .handle = isolate.handle };
var temp_scope: js.HandleScope = undefined; var temp_scope: js.HandleScope = undefined;
temp_scope.init(isolate); temp_scope.init(isolate);
defer temp_scope.deinit(); defer temp_scope.deinit();
// Creates a global template that inherits from Window. // Getting this into the snapshot is tricky (anything involving the
const global_template = @import("Snapshot.zig").createGlobalTemplate(isolate, env.templates); // global is tricky). Easier to do here.
// Add the named property handler const func_tmpl_handle = isolate.createFunctionTemplateHandle();
global_template.setNamedProperty(v8.NamedPropertyHandlerConfiguration{ const global_template = v8.c.v8__FunctionTemplate__InstanceTemplate(func_tmpl_handle).?;
var configuration: v8.c.NamedPropertyHandlerConfiguration = .{
.getter = unknownPropertyCallback, .getter = unknownPropertyCallback,
.flags = v8.PropertyHandlerFlags.NonMasking | v8.PropertyHandlerFlags.OnlyInterceptStrings, .setter = null,
}, 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 = isolate.createContextHandle(null, null);
const context_local = v8.Context.init(isolate, global_template, null); // Make the context persistent so it survives beyond this handle scope
const v8_context = v8.Persistent(v8.Context).init(isolate, context_local).castToContext(); var persistent_handle: *v8.c.Data = undefined;
break :blk v8_context; 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. // 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) { if (enter) {
handle_scope = @as(js.HandleScope, undefined); handle_scope = @as(js.HandleScope, undefined);
handle_scope.?.init(isolate); handle_scope.?.init(isolate);
v8_context.enter(); v8.c.v8__Context__Enter(context_handle);
} }
errdefer if (enter) { errdefer if (enter) {
v8_context.exit(); v8.c.v8__Context__Exit(context_handle);
handle_scope.?.deinit(); handle_scope.?.deinit();
}; };
@@ -117,7 +126,7 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
.page = page, .page = page,
.id = context_id, .id = context_id,
.isolate = isolate, .isolate = isolate,
.handle = v8_context.handle, .handle = context_handle,
.templates = env.templates, .templates = env.templates,
.handle_scope = handle_scope, .handle_scope = handle_scope,
.script_manager = &page._script_manager, .script_manager = &page._script_manager,
@@ -128,9 +137,8 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
var context = &self.context.?; var context = &self.context.?;
// Store a pointer to our context inside the v8 context so that, given // Store a pointer to our context inside the v8 context so that, given
// a v8 context, we can get our context out // a v8 context, we can get our context out
const v8_isolate = v8.Isolate{ .handle = isolate.handle }; const data = isolate.initBigIntU64(@intCast(@intFromPtr(context)));
const data = v8_isolate.initBigIntU64(@intCast(@intFromPtr(context))); v8.c.v8__Context__SetEmbedderData(context_handle, 1, @ptrCast(data.handle));
v8_context.setEmbedderData(1, data);
try context.setupGlobal(); try context.setupGlobal();
return context; 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 { 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 isolate_handle = v8.c.v8__PropertyCallbackInfo__GetIsolate(raw_info).?;
const context = Context.fromIsolate(info.getIsolate()); 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(.{ const ignored = std.StaticStringMap(void).initComptime(.{
.{ "process", {} }, .{ "process", {} },
@@ -184,27 +194,25 @@ pub fn unknownPropertyCallback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C
.{ "CLOSURE_FLAGS", {} }, .{ "CLOSURE_FLAGS", {} },
}); });
if (maybe_property) |prop| { if (!ignored.has(property)) {
if (!ignored.has(prop)) { const page = context.page;
const page = context.page; const document = page.document;
const document = page.document;
if (document.getElementById(prop, page)) |el| { if (document.getElementById(property, page)) |el| {
const js_value = context.zigValueToJs(el, .{}) catch { const js_value = context.zigValueToJs(el, .{}) catch {
return v8.Intercepted.No; return v8.Intercepted.No;
}; };
info.getReturnValue().set(js_value); info.getReturnValue().set(js_value);
return v8.Intercepted.Yes; return v8.Intercepted.Yes;
} }
if (comptime IS_DEBUG) { if (comptime IS_DEBUG) {
log.debug(.unknown_prop, "unknown global property", .{ log.debug(.unknown_prop, "unknown global property", .{
.info = "but the property can exist in pure JS", .info = "but the property can exist in pure JS",
.stack = context.stackTrace() catch "???", .stack = context.stackTrace() catch "???",
.property = prop, .property = property,
}); });
}
} }
} }

View File

@@ -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 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: { .@"struct" => |s| blk: {
const fields = s.fields; 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| { 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; break :blk &cargs;
}, },
.pointer => blk: { .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| { for (args, 0..) |a, i| {
values[i] = try ctx.zigValueToJs(a, .{}); values[i] = (try ctx.zigValueToJs(a, .{})).handle;
} }
break :blk values; break :blk values;
}, },
@@ -163,7 +163,7 @@ pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args
if (@typeInfo(T) == .void) { if (@typeInfo(T) == .void) {
return {}; return {};
} }
return ctx.jsValueToZig(T, .{ .handle = handle }); return ctx.jsValueToZig(T, .{ .ctx = ctx, .handle = handle });
} }
fn getThis(self: *const Function) js.Object { fn getThis(self: *const Function) js.Object {

View File

@@ -195,9 +195,8 @@ pub fn getNodePtr(self: *const Inspector, allocator: Allocator, object_id: []con
return error.ObjectIdIsNotANode; return error.ObjectIdIsNotANode;
} }
const Node = @import("../webapi/Node.zig"); const Node = @import("../webapi/Node.zig");
// Wrap the C handle in a v8.Object for typeTaggedAnyOpaque // Cast to *const v8.c.Object for typeTaggedAnyOpaque
const js_obj = v8.Object{ .handle = js_val }; return Context.typeTaggedAnyOpaque(*Node, @ptrCast(js_val)) catch {
return Context.typeTaggedAnyOpaque(*Node, js_obj) catch {
return error.ObjectIdIsNotANode; return error.ObjectIdIsNotANode;
}; };
} }

View File

@@ -23,10 +23,20 @@ const Isolate = @This();
handle: *v8.c.Isolate, 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 { pub fn deinit(self: Isolate) void {
v8.c.v8__Isolate__Dispose(self.handle); 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 { pub fn exit(self: Isolate) void {
v8.c.v8__Isolate__Exit(self.handle); v8.c.v8__Isolate__Exit(self.handle);
} }
@@ -54,16 +64,50 @@ pub fn getHeapStatistics(self: Isolate) v8.c.HeapStatistics {
return res; return res;
} }
pub fn throwException(self: Isolate, value: anytype) v8.Value { pub fn throwException(self: Isolate, value: *const v8.c.Value) *const v8.c.Value {
const handle = switch (@TypeOf(value)) { return v8.c.v8__Isolate__ThrowException(self.handle, value).?;
v8.Value => value.handle,
else => @compileError("Unsupported type for throwException"),
};
return .{
.handle = v8.c.v8__Isolate__ThrowException(self.handle, handle).?,
};
} }
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))).?; 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).?;
}

View File

@@ -36,47 +36,32 @@ pub fn getId(self: Object) u32 {
return @bitCast(v8.c.v8__Object__GetIdentityHash(self.handle)); return @bitCast(v8.c.v8__Object__GetIdentityHash(self.handle));
} }
pub const SetOpts = packed struct(u32) { pub fn get(self: Object, key: []const u8) !js.Value {
READ_ONLY: bool = false, const ctx = self.ctx;
DONT_ENUM: bool = false, const js_key = ctx.isolate.createStringHandle(key);
DONT_DELETE: bool = false, const js_val_handle = v8.c.v8__Object__Get(self.handle, ctx.handle, js_key) orelse return error.JsException;
_: u29 = 0, return .{
}; .ctx = ctx,
pub fn setIndex(self: Object, index: u32, value: anytype, opts: SetOpts) !void { .handle = js_val_handle,
@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}),
}; };
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 ctx = self.ctx;
const js_key = v8.c.v8__String__NewFromUtf8(ctx.isolate.handle, key.ptr, v8.c.kNormal, @intCast(key.len)).?; const name_handle = ctx.isolate.createStringHandle(name);
const js_value = try ctx.zigValueToJs(value, .{});
var out: v8.c.MaybeBool = undefined; var out: v8.c.MaybeBool = undefined;
v8.c.v8__Object__DefineOwnProperty(self.handle, ctx.handle, @ptrCast(js_key), js_value.handle, @bitCast(opts), &out); v8.c.v8__Object__DefineOwnProperty(self.handle, ctx.handle, @ptrCast(name_handle), value.handle, attr, &out);
if (out.has_value) {
const res = if (out.has_value) out.value else false; return out.value;
if (!res) { } else {
return error.FailedToSet; 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 { pub fn toString(self: Object) ![]const u8 {
const js_value = v8.Value{ .handle = @ptrCast(self.handle) }; return self.ctx.valueToString(self.toValue(), .{});
return self.ctx.valueToString(js_value, .{});
} }
pub fn toValue(self: Object) js.Value { 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 { pub fn format(self: Object, writer: *std.Io.Writer) !void {
if (comptime IS_DEBUG) { if (comptime IS_DEBUG) {
const js_value = v8.Value{ .handle = @ptrCast(self.handle) }; return self.ctx.debugValue(self.toValue(), writer);
return self.ctx.debugValue(js_value, writer);
} }
const str = self.toString() catch return error.WriteFailed; const str = self.toString() catch return error.WriteFailed;
return writer.writeAll(str); return writer.writeAll(str);
@@ -119,9 +103,9 @@ pub fn getFunction(self: Object, name: []const u8) !?js.Function {
} }
const ctx = self.ctx; 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_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()) { if (!js_value.isFunction()) {
return null; return null;
@@ -152,7 +136,7 @@ pub fn nameIterator(self: Object) NameIterator {
} }
pub fn toZig(self: Object, comptime T: type) !T { 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); return self.ctx.jsValueToZig(T, js_value);
} }
@@ -170,7 +154,7 @@ pub const NameIterator = struct {
self.idx += 1; 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_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, .{}); return try self.ctx.valueToString(js_val, .{});
} }
}; };

View File

@@ -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); v8.c.v8__Template__Set(@ptrCast(target), js_name, @ptrCast(function_template), v8.c.None);
}, },
bridge.Property => { 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 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), .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)); const js_name = v8.c.v8__String__NewFromUtf8(isolate, name.ptr, v8.c.kNormal, @intCast(name.len));
// apply it both to the type itself // apply it both to the type itself

View File

@@ -39,7 +39,7 @@ pub fn hasCaught(self: TryCatch) bool {
// the caller needs to deinit the string returned // the caller needs to deinit the string returned
pub fn exception(self: TryCatch, allocator: Allocator) !?[]const u8 { pub fn exception(self: TryCatch, allocator: Allocator) !?[]const u8 {
const msg_value = v8.c.v8__TryCatch__Exception(&self.handle) orelse return null; 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 }); 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 { pub fn stack(self: TryCatch, allocator: Allocator) !?[]const u8 {
const ctx = self.ctx; const ctx = self.ctx;
const s_value = v8.c.v8__TryCatch__StackTrace(&self.handle, ctx.handle) orelse return null; 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 }); return try ctx.valueToString(s, .{ .allocator = allocator });
} }

View File

@@ -27,7 +27,7 @@ const Allocator = std.mem.Allocator;
const Value = @This(); const Value = @This();
ctx: *js.Context, ctx: *const js.Context,
handle: *const v8.c.Value, handle: *const v8.c.Value,
pub fn isObject(self: Value) bool { pub fn isObject(self: Value) bool {
@@ -58,6 +58,146 @@ pub fn isFunction(self: Value) bool {
return v8.c.v8__Value__IsFunction(self.handle); 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 { pub fn toString(self: Value, opts: js.String.ToZigOpts) ![]u8 {
return self._toString(false, opts); 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) { 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()) { if (self.isSymbol()) {
const sym_handle = v8.c.v8__Symbol__Description(@ptrCast(self.handle), ctx.isolate.handle).?; 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 { 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); const global = js.Global(Value).init(ctx.isolate.handle, self.handle);
try ctx.global_values.append(ctx.arena, global); try ctx.global_values.append(ctx.arena, global);
@@ -118,7 +258,7 @@ pub fn toObject(self: Value) js.Object {
} }
return .{ return .{
.ctx = self.ctx, .ctx = @constCast(self.ctx),
.handle = self.handle, .handle = self.handle,
}; };
} }
@@ -129,14 +269,20 @@ pub fn toArray(self: Value) js.Array {
} }
return .{ return .{
.ctx = self.ctx, .ctx = @constCast(self.ctx),
.handle = self.handle, .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 { pub fn format(self: Value, writer: *std.Io.Writer) !void {
if (comptime IS_DEBUG) { 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; const str = self.toString(.{}) catch return error.WriteFailed;
return writer.writeAll(str); return writer.writeAll(str);

View File

@@ -22,7 +22,533 @@ const log = @import("../../log.zig");
const v8 = js.v8; 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 { pub fn Builder(comptime T: type) type {
return struct { return struct {
@@ -98,10 +624,11 @@ pub const Constructor = struct {
fn init(comptime T: type, comptime func: anytype, comptime opts: Opts) Constructor { fn init(comptime T: type, comptime func: anytype, comptime opts: Opts) Constructor {
return .{ .func = struct { return .{ .func = struct {
fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void { fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info); const v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?;
var caller = Caller.init(info); var caller = Caller.init(v8_isolate);
defer caller.deinit(); defer caller.deinit();
const info = CallbackInfo{ .raw = raw_info.? };
caller.constructor(T, func, info, .{ caller.constructor(T, func, info, .{
.dom_exception = opts.dom_exception, .dom_exception = opts.dom_exception,
}); });
@@ -126,10 +653,11 @@ pub const Function = struct {
.static = opts.static, .static = opts.static,
.func = struct { .func = struct {
fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void { fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info); const v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?;
var caller = Caller.init(info); var caller = Caller.init(v8_isolate);
defer caller.deinit(); defer caller.deinit();
const info = CallbackInfo{ .raw = raw_info.? };
if (comptime opts.static) { if (comptime opts.static) {
caller.function(T, func, info, .{ caller.function(T, func, info, .{
.dom_exception = opts.dom_exception, .dom_exception = opts.dom_exception,
@@ -169,10 +697,11 @@ pub const Accessor = struct {
if (@typeInfo(@TypeOf(getter)) != .null) { if (@typeInfo(@TypeOf(getter)) != .null) {
accessor.getter = struct { accessor.getter = struct {
fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void { fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info); const v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?;
var caller = Caller.init(info); var caller = Caller.init(v8_isolate);
defer caller.deinit(); defer caller.deinit();
const info = CallbackInfo{ .raw = raw_info.? };
caller.method(T, getter, info, .{ caller.method(T, getter, info, .{
.as_typed_array = opts.as_typed_array, .as_typed_array = opts.as_typed_array,
.null_as_undefined = opts.null_as_undefined, .null_as_undefined = opts.null_as_undefined,
@@ -184,12 +713,13 @@ pub const Accessor = struct {
if (@typeInfo(@TypeOf(setter)) != .null) { if (@typeInfo(@TypeOf(setter)) != .null) {
accessor.setter = struct { accessor.setter = struct {
fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void { fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info); const v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?;
std.debug.assert(info.length() == 1); var caller = Caller.init(v8_isolate);
var caller = Caller.init(info);
defer caller.deinit(); defer caller.deinit();
const info = CallbackInfo{ .raw = raw_info.? };
std.debug.assert(info.length() == 1);
caller.method(T, setter, info, .{ caller.method(T, setter, info, .{
.as_typed_array = opts.as_typed_array, .as_typed_array = opts.as_typed_array,
.null_as_undefined = opts.null_as_undefined, .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 { fn init(comptime T: type, comptime getter: anytype, comptime opts: Opts) Indexed {
return .{ .getter = struct { return .{ .getter = struct {
fn wrap(idx: u32, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 { fn wrap(idx: u32, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
const info = v8.PropertyCallbackInfo.initFromV8(raw_info); const v8_isolate = v8.c.v8__PropertyCallbackInfo__GetIsolate(raw_info).?;
var caller = Caller.init(info); var caller = Caller.init(v8_isolate);
defer caller.deinit(); defer caller.deinit();
const info = PropertyCallbackInfo{ .raw = raw_info.? };
return caller.getIndex(T, getter, idx, info, .{ return caller.getIndex(T, getter, idx, info, .{
.as_typed_array = opts.as_typed_array, .as_typed_array = opts.as_typed_array,
.null_as_undefined = opts.null_as_undefined, .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 { fn init(comptime T: type, comptime getter: anytype, setter: anytype, deleter: anytype, comptime opts: Opts) NamedIndexed {
const getter_fn = struct { const getter_fn = struct {
fn wrap(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 { fn wrap(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
const info = v8.PropertyCallbackInfo.initFromV8(raw_info); const v8_isolate = v8.c.v8__PropertyCallbackInfo__GetIsolate(raw_info).?;
var caller = Caller.init(info); var caller = Caller.init(v8_isolate);
defer caller.deinit(); defer caller.deinit();
const info = PropertyCallbackInfo{ .raw = raw_info.? };
return caller.getNamedIndex(T, getter, .{ .handle = c_name.? }, info, .{ return caller.getNamedIndex(T, getter, .{ .handle = c_name.? }, info, .{
.as_typed_array = opts.as_typed_array, .as_typed_array = opts.as_typed_array,
.null_as_undefined = opts.null_as_undefined, .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 { 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 { 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); const v8_isolate = v8.c.v8__PropertyCallbackInfo__GetIsolate(raw_info).?;
var caller = Caller.init(info); var caller = Caller.init(v8_isolate);
defer caller.deinit(); defer caller.deinit();
const info = PropertyCallbackInfo{ .raw = raw_info.? };
return caller.setNamedIndex(T, setter, .{ .handle = c_name.? }, .{ .handle = c_value.? }, info, .{ return caller.setNamedIndex(T, setter, .{ .handle = c_name.? }, .{ .handle = c_value.? }, info, .{
.as_typed_array = opts.as_typed_array, .as_typed_array = opts.as_typed_array,
.null_as_undefined = opts.null_as_undefined, .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 { 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 { fn wrap(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
const info = v8.PropertyCallbackInfo.initFromV8(raw_info); const v8_isolate = v8.c.v8__PropertyCallbackInfo__GetIsolate(raw_info).?;
var caller = Caller.init(info); var caller = Caller.init(v8_isolate);
defer caller.deinit(); defer caller.deinit();
const info = PropertyCallbackInfo{ .raw = raw_info.? };
return caller.deleteNamedIndex(T, deleter, .{ .handle = c_name.? }, info, .{ return caller.deleteNamedIndex(T, deleter, .{ .handle = c_name.? }, info, .{
.as_typed_array = opts.as_typed_array, .as_typed_array = opts.as_typed_array,
.null_as_undefined = opts.null_as_undefined, .null_as_undefined = opts.null_as_undefined,
@@ -308,9 +844,11 @@ pub const Iterator = struct {
.async = opts.async, .async = opts.async,
.func = struct { .func = struct {
fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void { fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info); const v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?;
var caller = Caller.init(info); var caller = Caller.init(v8_isolate);
defer caller.deinit(); defer caller.deinit();
const info = CallbackInfo{ .raw = raw_info.? };
caller.method(T, struct_or_func, info, .{}); caller.method(T, struct_or_func, info, .{});
} }
}.wrap, }.wrap,
@@ -328,9 +866,11 @@ pub const Callable = struct {
fn init(comptime T: type, comptime func: anytype, comptime opts: Opts) Callable { fn init(comptime T: type, comptime func: anytype, comptime opts: Opts) Callable {
return .{ .func = struct { return .{ .func = struct {
fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void { fn wrap(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info); const v8_isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info).?;
var caller = Caller.init(info); var caller = Caller.init(v8_isolate);
defer caller.deinit(); defer caller.deinit();
const info = CallbackInfo{ .raw = raw_info.? };
caller.method(T, func, info, .{ caller.method(T, func, info, .{
.null_as_undefined = opts.null_as_undefined, .null_as_undefined = opts.null_as_undefined,
}); });

View File

@@ -101,7 +101,7 @@ pub const PersistentPromiseResolver = struct {
defer context.runMicrotasks(); defer context.runMicrotasks();
const v8_context = v8.Context{ .handle = context.handle }; 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; return error.FailedToResolvePromise;
} }
} }
@@ -119,17 +119,16 @@ pub const PersistentPromiseResolver = struct {
defer context.runMicrotasks(); defer context.runMicrotasks();
// resolver.reject will return null if the promise isn't pending // 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; return error.FailedToRejectPromise;
} }
} }
}; };
pub const Exception = struct { pub const Exception = struct {
inner: v8.Value, ctx: *const Context,
context: *const Context, handle: *const v8.c.Value,
// the caller needs to deinit the string returned
pub fn exception(self: Exception, allocator: Allocator) ![]const u8 { pub fn exception(self: Exception, allocator: Allocator) ![]const u8 {
return self.context.valueToString(self.inner, .{ .allocator = allocator }); 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 // 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 // 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) // 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))) { switch (@typeInfo(@TypeOf(value))) {
.void => return v8.initUndefined(isolate).toValue(), .void => return @ptrCast(v8.initUndefined(isolate).handle),
.null => if (comptime null_as_undefined) return v8.initUndefined(isolate).toValue() else return v8.initNull(isolate).toValue(), .null => if (comptime null_as_undefined) return @ptrCast(v8.initUndefined(isolate).handle) else return @ptrCast(v8.initNull(isolate).handle),
.bool => return v8.getValue(if (value) v8.initTrue(isolate) else v8.initFalse(isolate)), .bool => return if (value) v8.initTrue(isolate).handle else v8.initFalse(isolate).handle,
.int => |n| switch (n.signedness) { .int => |n| switch (n.signedness) {
.signed => { .signed => {
if (value > 0 and value <= 4_294_967_295) { 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) { 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) { 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"); @compileError(@typeName(value) ++ " is not supported");
}, },
.unsigned => { .unsigned => {
if (value <= 4_294_967_295) { 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) { 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"); @compileError(@typeName(value) ++ " is not supported");
}, },
@@ -247,29 +246,29 @@ pub fn simpleZigValueToJs(isolate: v8.Isolate, value: anytype, comptime fail: bo
.comptime_int => { .comptime_int => {
if (value >= 0) { if (value >= 0) {
if (value <= 4_294_967_295) { 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) { 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) { .float => |f| switch (f.bits) {
64 => return v8.Number.init(isolate, value).toValue(), 64 => return @ptrCast(v8.Number.init(isolate, value).handle),
32 => return v8.Number.init(isolate, @floatCast(value)).toValue(), 32 => return @ptrCast(v8.Number.init(isolate, @floatCast(value)).handle),
else => @compileError(@typeName(value) ++ " is not supported"), else => @compileError(@typeName(value) ++ " is not supported"),
}, },
.pointer => |ptr| { .pointer => |ptr| {
if (ptr.size == .slice and ptr.child == u8) { 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) { if (ptr.size == .one) {
const one_info = @typeInfo(ptr.child); const one_info = @typeInfo(ptr.child);
if (one_info == .array and one_info.array.child == u8) { 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); return simpleZigValueToJs(isolate, v, fail, null_as_undefined);
} }
if (comptime 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" => { .@"struct" => {
switch (@TypeOf(value)) { 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]); @memcpy(data[0..len], @as([]const u8, @ptrCast(values))[0..len]);
array_buffer = v8.ArrayBuffer.initWithBackingStore(isolate, &backing_store.toSharedPtr()); array_buffer = v8.ArrayBuffer.initWithBackingStore(isolate, &backing_store.toSharedPtr());
return .{ .handle = array_buffer.handle }; return @ptrCast(array_buffer.handle);
}, },
// zig fmt: off // zig fmt: off
TypedArray(u8), TypedArray(u16), TypedArray(u32), TypedArray(u64), 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)) { switch (@typeInfo(value_type)) {
.int => |n| switch (n.signedness) { .int => |n| switch (n.signedness) {
.unsigned => switch (n.bits) { .unsigned => switch (n.bits) {
8 => return v8.Uint8Array.init(array_buffer, 0, len).toValue(), 8 => return @ptrCast(v8.Uint8Array.init(array_buffer, 0, len).handle),
16 => return v8.Uint16Array.init(array_buffer, 0, len).toValue(), 16 => return @ptrCast(v8.Uint16Array.init(array_buffer, 0, len).handle),
32 => return v8.Uint32Array.init(array_buffer, 0, len).toValue(), 32 => return @ptrCast(v8.Uint32Array.init(array_buffer, 0, len).handle),
64 => return v8.BigUint64Array.init(array_buffer, 0, len).toValue(), 64 => return @ptrCast(v8.BigUint64Array.init(array_buffer, 0, len).handle),
else => {}, else => {},
}, },
.signed => switch (n.bits) { .signed => switch (n.bits) {
8 => return v8.Int8Array.init(array_buffer, 0, len).toValue(), 8 => return @ptrCast(v8.Int8Array.init(array_buffer, 0, len).handle),
16 => return v8.Int16Array.init(array_buffer, 0, len).toValue(), 16 => return @ptrCast(v8.Int16Array.init(array_buffer, 0, len).handle),
32 => return v8.Int32Array.init(array_buffer, 0, len).toValue(), 32 => return @ptrCast(v8.Int32Array.init(array_buffer, 0, len).handle),
64 => return v8.BigInt64Array.init(array_buffer, 0, len).toValue(), 64 => return @ptrCast(v8.BigInt64Array.init(array_buffer, 0, len).handle),
else => {}, else => {},
}, },
}, },
.float => |f| switch (f.bits) { .float => |f| switch (f.bits) {
32 => return v8.Float32Array.init(array_buffer, 0, len).toValue(), 32 => return @ptrCast(v8.Float32Array.init(array_buffer, 0, len).handle),
64 => return v8.Float64Array.init(array_buffer, 0, len).toValue(), 64 => return @ptrCast(v8.Float64Array.init(array_buffer, 0, len).handle),
else => {}, else => {},
}, },
else => {}, else => {},
@@ -368,10 +367,6 @@ pub fn simpleZigValueToJs(isolate: v8.Isolate, value: anytype, comptime fail: bo
return null; 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 { pub fn classNameForStruct(comptime Struct: type) []const u8 {
if (@hasDecl(Struct, "js_name")) { if (@hasDecl(Struct, "js_name")) {
return Struct.js_name; return Struct.js_name;