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", .minimum_zig_version = "0.15.2",
.dependencies = .{ .dependencies = .{
.v8 = .{ .v8 = .{
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/d6b5f89cfc7feece29359e8c848bb916e8ecfab6.tar.gz", .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/direct_v8.tar.gz",
.hash = "v8-0.0.0-xddH6_0gBABrJc5cL6-P2wGvvweTTCgWdpmClr9r-C-s", .hash = "v8-0.0.0-xddH69smBABCCW8Q-9pislHtX8OolAmcuHk8QoTPx78F",
}, },
//.v8 = .{ .path = "../zig-v8-fork" }, //.v8 = .{ .path = "../zig-v8-fork" },
.@"boringssl-zig" = .{ .@"boringssl-zig" = .{

View File

@@ -21,18 +21,24 @@ const js = @import("js.zig");
const v8 = js.v8; const v8 = js.v8;
const Array = @This(); const Array = @This();
js_arr: v8.Array,
context: *js.Context, ctx: *js.Context,
handle: *const v8.c.Array,
pub fn len(self: Array) usize { 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 { pub fn get(self: Array, index: u32) !js.Value {
const idx_key = v8.Integer.initU32(self.context.isolate, @intCast(index)); const ctx = self.ctx;
const js_obj = self.js_arr.castTo(v8.Object);
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 .{ return .{
.context = self.context, .ctx = self.ctx,
.js_val = try js_obj.getValue(self.context.v8_context, idx_key.toValue()), .handle = handle,
}; };
} }

View File

@@ -86,8 +86,8 @@ identity_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .empty,
// we now simply persist every time persist() is called. // we now simply persist every time persist() is called.
js_object_list: std.ArrayListUnmanaged(PersistentObject) = .empty, js_object_list: std.ArrayListUnmanaged(PersistentObject) = .empty,
// js_value_list tracks persisted js values. // tracks Global(v8.c.Value).
js_value_list: std.ArrayListUnmanaged(PersistentValue) = .empty, global_values: std.ArrayList(js.Global(js.Value)) = .empty,
// Various web APIs depend on having a persistent promise resolver. They // Various web APIs depend on having a persistent promise resolver. They
// require for this PromiseResolver to be valid for a lifetime longer than // require for this PromiseResolver to be valid for a lifetime longer than
@@ -165,8 +165,8 @@ pub fn deinit(self: *Context) void {
p.deinit(); p.deinit();
} }
for (self.js_value_list.items) |*p| { for (self.global_values.items) |*global| {
p.deinit(); global.deinit();
} }
for (self.persisted_promise_resolvers.items) |*p| { 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 { pub fn createValue(self: *Context, value: v8.Value) js.Value {
return .{ return .{
.js_val = value, .ctx = self,
.context = self, .handle = value.handle,
}; };
} }
@@ -499,7 +497,7 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp
} }
if (T == js.Value) { if (T == js.Value) {
return value.js_val; return .{ .handle = value.handle };
} }
if (T == js.Promise) { 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 // Caller wants an opaque js.Object. Probably a parameter
// that it needs to pass back into a callback. // that it needs to pass back into a callback.
js.Value => js.Value{ js.Value => js.Value{
.js_val = js_value, .ctx = self,
.context = self, .handle = js_value.handle,
}, },
// Caller wants an opaque js.Object. Probably a parameter // Caller wants an opaque js.Object. Probably a parameter
// that it needs to pass back into a callback. // 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 v8 = js.v8;
const IS_DEBUG = @import("builtin").mode == .Debug;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const PersistentValue = v8.Persistent(v8.Value);
const Value = @This(); const Value = @This();
js_val: v8.Value,
context: *js.Context, ctx: *js.Context,
handle: *const v8.c.Value,
pub fn isObject(self: Value) bool { 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 { 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 { 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 { 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 { 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 { 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 { pub fn fromJson(ctx: *js.Context, json: []const u8) !Value {
const json_string = v8.String.initUtf8(ctx.isolate, json); const json_string = v8.String.initUtf8(ctx.isolate, json);
const value = try v8.Json.parse(ctx.v8_context, json_string); 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 { pub fn persist(self: Value) !Value {
const js_val = self.js_val; var ctx = self.ctx;
var context = self.context;
const persisted = PersistentValue.init(context.isolate, js_val); const global = js.Global(Value).init(ctx.isolate.handle, self);
try context.js_value_list.append(context.arena, persisted); 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 { 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 { pub fn toObject(self: Value) js.Object {
if (comptime IS_DEBUG) {
std.debug.assert(self.isObject());
}
return .{ return .{
.context = self.context, .context = self.ctx,
.js_obj = self.js_val.castTo(v8.Object), .js_obj = .{ .handle = self.handle },
}; };
} }
pub fn toArray(self: Value) js.Array { pub fn toArray(self: Value) js.Array {
if (comptime IS_DEBUG) {
std.debug.assert(self.isArray());
}
return .{ return .{
.context = self.context, .ctx = self.ctx,
.js_arr = self.js_val.castTo(v8.Array), .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 TryCatch = @import("TryCatch.zig");
pub const Function = @import("Function.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; const Allocator = std.mem.Allocator;
pub fn Bridge(comptime T: type) type { 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()) { if (observed_attrs.isArray()) {
var js_arr = observed_attrs.toArray(); var js_arr = observed_attrs.toArray();
for (0..js_arr.len()) |i| { 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 attr_name = attr_val.toString(page.arena) catch continue;
const owned_attr = page.dupeString(attr_name) catch continue; const owned_attr = page.dupeString(attr_name) catch continue;
definition.observed_attributes.put(page.arena, owned_attr, {}) catch continue; definition.observed_attributes.put(page.arena, owned_attr, {}) catch continue;