Make js.Array and js.Value directly contain their v8 handles.

This commit is contained in:
Karl Seguin
2025-12-29 20:02:06 +08:00
parent d9d8f68bf8
commit 3b1cd06615
8 changed files with 141 additions and 40 deletions

View File

@@ -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" = .{

View File

@@ -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,
};
}

View File

@@ -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.

View File

@@ -0,0 +1,35 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const 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 };
}

View File

@@ -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,
};
}

48
src/browser/js/global.zig Normal file
View File

@@ -0,0 +1,48 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const 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))));
}
};
}

View File

@@ -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 {

View File

@@ -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;