diff --git a/build.zig.zon b/build.zig.zon index 2053538f..805a294c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -6,8 +6,8 @@ .minimum_zig_version = "0.15.2", .dependencies = .{ .v8 = .{ - .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/d6b5f89cfc7feece29359e8c848bb916e8ecfab6.tar.gz", - .hash = "v8-0.0.0-xddH6_0gBABrJc5cL6-P2wGvvweTTCgWdpmClr9r-C-s", + .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/direct_v8.tar.gz", + .hash = "v8-0.0.0-xddH69smBABCCW8Q-9pislHtX8OolAmcuHk8QoTPx78F", }, //.v8 = .{ .path = "../zig-v8-fork" }, .@"boringssl-zig" = .{ diff --git a/src/browser/js/Array.zig b/src/browser/js/Array.zig index 95bc0e32..95ccf4e3 100644 --- a/src/browser/js/Array.zig +++ b/src/browser/js/Array.zig @@ -21,18 +21,24 @@ const js = @import("js.zig"); const v8 = js.v8; const Array = @This(); -js_arr: v8.Array, -context: *js.Context, + +ctx: *js.Context, +handle: *const v8.c.Array, pub fn len(self: Array) usize { - return @intCast(self.js_arr.length()); + return v8.c.v8__Array__Length(self.handle); } -pub fn get(self: Array, index: usize) !js.Value { - const idx_key = v8.Integer.initU32(self.context.isolate, @intCast(index)); - const js_obj = self.js_arr.castTo(v8.Object); +pub fn get(self: Array, index: u32) !js.Value { + const ctx = self.ctx; + + const idx = js.Integer.init(ctx.isolate.handle, index); + const handle = v8.c.v8__Object__Get(@ptrCast(self.handle), ctx.v8_context.handle, idx.handle) orelse { + return error.JsException; + }; + return .{ - .context = self.context, - .js_val = try js_obj.getValue(self.context.v8_context, idx_key.toValue()), + .ctx = self.ctx, + .handle = handle, }; } diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index e80a658b..c3e397cc 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -86,8 +86,8 @@ identity_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .empty, // we now simply persist every time persist() is called. js_object_list: std.ArrayListUnmanaged(PersistentObject) = .empty, -// js_value_list tracks persisted js values. -js_value_list: std.ArrayListUnmanaged(PersistentValue) = .empty, +// tracks Global(v8.c.Value). +global_values: std.ArrayList(js.Global(js.Value)) = .empty, // Various web APIs depend on having a persistent promise resolver. They // require for this PromiseResolver to be valid for a lifetime longer than @@ -165,8 +165,8 @@ pub fn deinit(self: *Context) void { p.deinit(); } - for (self.js_value_list.items) |*p| { - p.deinit(); + for (self.global_values.items) |*global| { + global.deinit(); } for (self.persisted_promise_resolvers.items) |*p| { @@ -374,12 +374,10 @@ pub fn createException(self: *const Context, e: v8.Value) js.Exception { }; } -// Wrap a v8.Value, largely so that we can provide a convenient -// toString function pub fn createValue(self: *Context, value: v8.Value) js.Value { return .{ - .js_val = value, - .context = self, + .ctx = self, + .handle = value.handle, }; } @@ -499,7 +497,7 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp } if (T == js.Value) { - return value.js_val; + return .{ .handle = value.handle }; } if (T == js.Promise) { @@ -837,8 +835,8 @@ fn jsValueToStruct(self: *Context, comptime T: type, js_value: v8.Value) !?T { // Caller wants an opaque js.Object. Probably a parameter // that it needs to pass back into a callback. js.Value => js.Value{ - .js_val = js_value, - .context = self, + .ctx = self, + .handle = js_value.handle, }, // Caller wants an opaque js.Object. Probably a parameter // that it needs to pass back into a callback. diff --git a/src/browser/js/Integer.zig b/src/browser/js/Integer.zig new file mode 100644 index 00000000..5d7cef5b --- /dev/null +++ b/src/browser/js/Integer.zig @@ -0,0 +1,35 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); +const js = @import("js.zig"); + +const v8 = js.v8; + +const Integer = @This(); + +handle: *const v8.c.Integer, + +pub fn init(isolate: *v8.c.Isolate, value: anytype) Integer { + const handle = switch (@TypeOf(value)) { + i8, i16, i32 => v8.c.v8__Integer__New(isolate, value).?, + u8, u16, u32 => v8.c.v8__Integer__NewFromUnsigned(isolate, value).?, + else => |T| @compileError("cannot create v8::Integer from: " ++ @typeName(T)), + }; + return .{ .handle = handle }; +} diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig index 90962415..43892733 100644 --- a/src/browser/js/Value.zig +++ b/src/browser/js/Value.zig @@ -21,24 +21,25 @@ const js = @import("js.zig"); const v8 = js.v8; +const IS_DEBUG = @import("builtin").mode == .Debug; + const Allocator = std.mem.Allocator; -const PersistentValue = v8.Persistent(v8.Value); - const Value = @This(); -js_val: v8.Value, -context: *js.Context, + +ctx: *js.Context, +handle: *const v8.c.Value, pub fn isObject(self: Value) bool { - return self.js_val.isObject(); + return v8.c.v8__Value__IsObject(self.handle); } pub fn isString(self: Value) bool { - return self.js_val.isString(); + return v8.c.v8__Value__IsString(self.handle); } pub fn isArray(self: Value) bool { - return self.js_val.isArray(); + return v8.c.v8__Value__IsArray(self.handle); } pub fn isNull(self: Value) bool { @@ -50,7 +51,7 @@ pub fn isUndefined(self: Value) bool { } pub fn toString(self: Value, allocator: Allocator) ![]const u8 { - return self.context.valueToString(self.js_val, .{ .allocator = allocator }); + return self.ctx.valueToString(.{ .handle = self.handle }, .{ .allocator = allocator }); } pub fn toBool(self: Value) bool { @@ -60,17 +61,19 @@ pub fn toBool(self: Value) bool { pub fn fromJson(ctx: *js.Context, json: []const u8) !Value { const json_string = v8.String.initUtf8(ctx.isolate, json); const value = try v8.Json.parse(ctx.v8_context, json_string); - return Value{ .context = ctx, .js_val = value }; + return .{ .ctx = ctx, .handle = value.handle }; } pub fn persist(self: Value) !Value { - const js_val = self.js_val; - var context = self.context; + var ctx = self.ctx; - const persisted = PersistentValue.init(context.isolate, js_val); - try context.js_value_list.append(context.arena, persisted); + const global = js.Global(Value).init(ctx.isolate.handle, self); + try ctx.global_values.append(ctx.arena, global); - return Value{ .context = context, .js_val = persisted.toValue() }; + return .{ + .ctx = ctx, + .handle = global.local(), + }; } pub fn toZig(self: Value, comptime T: type) !T { @@ -78,15 +81,23 @@ pub fn toZig(self: Value, comptime T: type) !T { } pub fn toObject(self: Value) js.Object { + if (comptime IS_DEBUG) { + std.debug.assert(self.isObject()); + } + return .{ - .context = self.context, - .js_obj = self.js_val.castTo(v8.Object), + .context = self.ctx, + .js_obj = .{ .handle = self.handle }, }; } pub fn toArray(self: Value) js.Array { + if (comptime IS_DEBUG) { + std.debug.assert(self.isArray()); + } + return .{ - .context = self.context, - .js_arr = self.js_val.castTo(v8.Array), + .ctx = self.ctx, + .handle = self.handle, }; } diff --git a/src/browser/js/global.zig b/src/browser/js/global.zig new file mode 100644 index 00000000..94540b65 --- /dev/null +++ b/src/browser/js/global.zig @@ -0,0 +1,48 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); +const js = @import("js.zig"); + +const v8 = js.v8; + +pub fn Global(comptime T: type) type { + const H = @FieldType(T, "handle"); + + return struct { + global: v8.c.Global, + + const Self = @This(); + + pub fn init(isolate: *v8.c.Isolate, data: T) Self { + var global: v8.c.Global = undefined; + v8.c.v8__Global__New(isolate, data.handle, &global); + return .{ + .global = global, + }; + } + + pub fn deinit(self: *Self) void { + v8.c.v8__Global__Reset(&self.global); + } + + pub fn local(self: *const Self) H { + return @ptrCast(@alignCast(@as(*const anyopaque, @ptrFromInt(self.global.data_ptr)))); + } + }; +} diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index f3e30c9b..82a81979 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -37,6 +37,9 @@ pub const Object = @import("Object.zig"); pub const TryCatch = @import("TryCatch.zig"); pub const Function = @import("Function.zig"); +pub const Integer = @import("Integer.zig"); +pub const Global = @import("global.zig").Global; + const Allocator = std.mem.Allocator; pub fn Bridge(comptime T: type) type { diff --git a/src/browser/webapi/CustomElementRegistry.zig b/src/browser/webapi/CustomElementRegistry.zig index b0646c9a..2cb8c2b1 100644 --- a/src/browser/webapi/CustomElementRegistry.zig +++ b/src/browser/webapi/CustomElementRegistry.zig @@ -72,7 +72,7 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu if (observed_attrs.isArray()) { var js_arr = observed_attrs.toArray(); for (0..js_arr.len()) |i| { - const attr_val = js_arr.get(i) catch continue; + const attr_val = js_arr.get(@intCast(i)) catch continue; const attr_name = attr_val.toString(page.arena) catch continue; const owned_attr = page.dupeString(attr_name) catch continue; definition.observed_attributes.put(page.arena, owned_attr, {}) catch continue;