From e6af7d1bd01008265e7fca66abfb185ee6551d57 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 31 Dec 2025 20:58:26 +0800 Subject: [PATCH] import more types --- src/browser/js/BigInt.zig | 51 ++++++++++++++++++ src/browser/js/Context.zig | 85 ++++++++++++++---------------- src/browser/js/Module.zig | 82 ++++++++++++++++++++++++++++ src/browser/js/Name.zig | 31 +++++++++++ src/browser/js/Object.zig | 7 +++ src/browser/js/Promise.zig | 49 +++++++++++++++++ src/browser/js/PromiseResolver.zig | 75 ++++++++++++++++++++++++++ src/browser/js/Script.zig | 38 +++++++++++++ src/browser/js/js.zig | 52 +++--------------- 9 files changed, 381 insertions(+), 89 deletions(-) create mode 100644 src/browser/js/BigInt.zig create mode 100644 src/browser/js/Module.zig create mode 100644 src/browser/js/Name.zig create mode 100644 src/browser/js/Promise.zig create mode 100644 src/browser/js/PromiseResolver.zig create mode 100644 src/browser/js/Script.zig diff --git a/src/browser/js/BigInt.zig b/src/browser/js/BigInt.zig new file mode 100644 index 00000000..39a238d8 --- /dev/null +++ b/src/browser/js/BigInt.zig @@ -0,0 +1,51 @@ +// 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 js = @import("js.zig"); +const v8 = js.v8; + +const BigInt = @This(); + +handle: *const v8.c.Integer, + +pub fn initI64(isolate_handle: *v8.c.Isolate, val: i64) BigInt { + return .{ + .handle = v8.c.v8__BigInt__New(isolate_handle, val).?, + }; +} + +pub fn initU64(isolate_handle: *v8.c.Isolate, val: u64) BigInt { + return .{ + .handle = v8.c.v8__BigInt__NewFromUnsigned(isolate_handle, val).?, + }; +} + +pub fn getUint64(self: BigInt) u64 { + return v8.c.v8__BigInt__Uint64Value(self.handle, null); +} + +pub fn getInt64(self: BigInt) i64 { + return v8.c.v8__BigInt__Int64Value(self.handle, null); +} + +pub fn toValue(self: BigInt) js.Value { + return .{ + .ctx = undefined, // Will be set by caller if needed + .handle = @ptrCast(self.handle), + }; +} diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 1a816153..b164bfb2 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -32,10 +32,8 @@ const ScriptManager = @import("../ScriptManager.zig"); const Allocator = std.mem.Allocator; const PersistentObject = v8.Persistent(v8.Object); -const PersistentValue = v8.Persistent(v8.Value); const PersistentModule = v8.Persistent(v8.Module); const PersistentPromise = v8.Persistent(v8.Promise); -const PersistentFunction = v8.Persistent(v8.Function); const TaggedAnyOpaque = js.TaggedAnyOpaque; // Loosely maps to a Browser Page. @@ -125,14 +123,14 @@ const ModuleEntry = struct { pub fn fromC(c_context: *const v8.C_Context) *Context { const data = v8.c.v8__Context__GetEmbedderData(c_context, 1).?; - const big_int = v8.BigInt{ .handle = @ptrCast(data) }; + const big_int = js.BigInt{ .handle = @ptrCast(data) }; return @ptrFromInt(big_int.getUint64()); } pub fn fromIsolate(isolate: js.Isolate) *Context { const v8_context = v8.c.v8__Isolate__GetCurrentContext(isolate.handle).?; const data = v8.c.v8__Context__GetEmbedderData(v8_context, 1).?; - const big_int = v8.BigInt{ .handle = @ptrCast(data) }; + const big_int = js.BigInt{ .handle = @ptrCast(data) }; return @ptrFromInt(big_int.getUint64()); } @@ -212,7 +210,7 @@ pub fn exec(self: *Context, src: []const u8, name: ?[]const u8) !js.Value { const scr = try compileScript(v8_isolate, v8_context, src, name); - const value = scr.run(v8_context) catch { + const value = scr.run(v8_context.handle) catch { return error.ExecutionError; }; @@ -245,7 +243,8 @@ pub fn module(self: *Context, comptime want_result: bool, src: []const u8, url: // compileModule is synchronous - nothing can modify the cache during compilation std.debug.assert(gop.value_ptr.module == null); - gop.value_ptr.module = PersistentModule.init(v8_isolate, m); + const v8_module = v8.Module{ .handle = m.handle }; + gop.value_ptr.module = PersistentModule.init(v8_isolate, v8_module); if (!gop.found_existing) { gop.key_ptr.* = owned_url; } @@ -257,11 +256,11 @@ pub fn module(self: *Context, comptime want_result: bool, src: []const u8, url: try self.postCompileModule(mod, owned_url); const v8_context = v8.Context{ .handle = self.handle }; - if (try mod.instantiate(v8_context, resolveModuleCallback) == false) { + if (try mod.instantiate(v8_context.handle, resolveModuleCallback) == false) { return error.ModuleInstantiationError; } - const evaluated = mod.evaluate(v8_context) catch { + const evaluated = mod.evaluate(v8_context.handle) catch { std.debug.assert(mod.getStatus() == .kErrored); // Some module-loading errors aren't handled by TryCatch. We need to @@ -319,7 +318,7 @@ pub fn stringToFunction(self: *Context, str: []const u8) !js.Function { const v8_context = v8.Context{ .handle = self.handle }; const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; const script = try compileScript(v8_isolate, v8_context, full, null); - const js_value = script.run(v8_context) catch { + const js_value = script.run(v8_context.handle) catch { return error.ExecutionError; }; if (!js_value.isFunction()) { @@ -331,7 +330,7 @@ pub fn stringToFunction(self: *Context, str: []const u8) !js.Function { // After we compile a module, whether it's a top-level one, or a nested one, // we always want to track its identity (so that, if this module imports other // modules, we can resolve the full URL), and preload any dependent modules. -fn postCompileModule(self: *Context, mod: v8.Module, url: [:0]const u8) !void { +fn postCompileModule(self: *Context, mod: js.Module, url: [:0]const u8) !void { try self.module_identifier.putNoClobber(self.arena, mod.getIdentityHash(), url); const v8_context = v8.Context{ .handle = self.handle }; @@ -500,8 +499,8 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp } if (T == js.Promise) { - // we're returning a v8.Promise - return value.toObject().toValue(); + // we're returning a js.Promise + return .{ .handle = @ptrCast(value.handle) }; } if (T == js.Exception) { @@ -1189,26 +1188,15 @@ pub fn stackTrace(self: *const Context) !?[]const u8 { // == Promise Helpers == pub fn rejectPromise(self: *Context, value: anytype) !js.Promise { - const ctx = v8.Context{ .handle = self.handle }; - var resolver = v8.PromiseResolver.init(ctx); - const js_value = try self.zigValueToJs(value, .{}); - if (resolver.reject(ctx, js_value) == null) { - return error.FailedToResolvePromise; - } - self.runMicrotasks(); - return resolver.getPromise(); + var resolver = js.PromiseResolver.init(self); + resolver.reject("Context.rejectPromise", value); + return resolver.promise(); } pub fn resolvePromise(self: *Context, value: anytype) !js.Promise { - const ctx = v8.Context{ .handle = self.handle }; - const js_value = try self.zigValueToJs(value, .{}); - - var resolver = v8.PromiseResolver.init(ctx); - if (resolver.resolve(ctx, js_value) == null) { - return error.FailedToResolvePromise; - } - self.runMicrotasks(); - return resolver.getPromise(); + var resolver = js.PromiseResolver.init(self); + resolver.resolve("Context.resolvePromise", value); + return resolver.promise(); } pub fn runMicrotasks(self: *Context) void { @@ -1230,12 +1218,12 @@ fn PromiseResolverType(comptime lifetime: PromiseResolverLifetime) type { return error{OutOfMemory}!js.PersistentPromiseResolver; } pub fn createPromiseResolver(self: *Context, comptime lifetime: PromiseResolverLifetime) PromiseResolverType(lifetime) { - const v8_context = v8.Context{ .handle = self.handle }; - const resolver = v8.PromiseResolver.init(v8_context); if (comptime lifetime == .none) { - return .{ .context = self, .resolver = resolver }; + return js.PromiseResolver.init(self); } + const v8_context = v8.Context{ .handle = self.handle }; + const resolver = v8.PromiseResolver.init(v8_context); const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; const persisted = v8.Persistent(v8.PromiseResolver).init(v8_isolate, resolver); @@ -1266,7 +1254,7 @@ fn resolveModuleCallback( log.err(.js, "resolve module", .{ .err = err }); return null; }; - const referrer = v8.Module{ .handle = c_referrer.? }; + const referrer = js.Module{ .handle = c_referrer.? }; return self._resolveModuleCallback(referrer, specifier) catch |err| { log.err(.js, "resolve module", .{ @@ -1319,7 +1307,7 @@ pub fn dynamicModuleCallback( pub fn metaObjectCallback(c_context: ?*v8.C_Context, c_module: ?*v8.C_Module, c_meta: ?*v8.C_Value) callconv(.c) void { const self = fromC(c_context.?); - const m = v8.Module{ .handle = c_module.? }; + const m = js.Module{ .handle = c_module.? }; const meta = v8.Object{ .handle = c_meta.? }; const url = self.module_identifier.get(m.getIdentityHash()) orelse { @@ -1338,7 +1326,7 @@ pub fn metaObjectCallback(c_context: ?*v8.C_Context, c_module: ?*v8.C_Module, c_ } } -fn _resolveModuleCallback(self: *Context, referrer: v8.Module, specifier: [:0]const u8) !?*const v8.C_Module { +fn _resolveModuleCallback(self: *Context, referrer: js.Module, specifier: [:0]const u8) !?*const v8.C_Module { const referrer_path = self.module_identifier.get(referrer.getIdentityHash()) orelse { // Shouldn't be possible. return error.UnknownModuleReferrer; @@ -1365,7 +1353,8 @@ fn _resolveModuleCallback(self: *Context, referrer: v8.Module, specifier: [:0]co const v8_isolate = v8.Isolate{ .handle = self.isolate.handle }; const mod = try compileModule(v8_isolate, source.src(), normalized_specifier); try self.postCompileModule(mod, normalized_specifier); - entry.module = PersistentModule.init(v8_isolate, mod); + const v8_module = v8.Module{ .handle = mod.handle }; + entry.module = PersistentModule.init(v8_isolate, v8_module); return entry.module.?.castToModule().handle; } @@ -1381,7 +1370,7 @@ const DynamicModuleResolveState = struct { resolver: v8.Persistent(v8.PromiseResolver), }; -fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []const u8) !v8.Promise { +fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []const u8) !js.Promise { const isolate = self.isolate; const v8_isolate = v8.Isolate{ .handle = isolate.handle }; const gop = try self.module_cache.getOrPut(self.arena, specifier); @@ -1389,7 +1378,8 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c // This is easy, there's already something responsible // for loading the module. Maybe it's still loading, maybe // it's complete. Whatever, we can just return that promise. - return gop.value_ptr.resolver_promise.?.castToPromise(); + const v8_promise = gop.value_ptr.resolver_promise.?.castToPromise(); + return .{ .handle = v8_promise.handle }; } const v8_context = v8.Context{ .handle = self.handle }; @@ -1432,7 +1422,8 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c // For now, we're done. but this will be continued in // `dynamicModuleSourceCallback`, once the source for the // moduel is loaded. - return promise; + const v8_promise = promise; + return .{ .handle = v8_promise.handle }; } // So we have a module, but no async resolver. This can only @@ -1464,7 +1455,8 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c std.debug.assert(status == .kErrored); const error_msg = v8.String.initUtf8(v8_isolate, "Module evaluation failed"); _ = resolver.reject(v8_context, error_msg.toValue()); - return promise; + const v8_promise = promise; + return .{ .handle = v8_promise.handle }; }; std.debug.assert(evaluated.isPromise()); gop.value_ptr.module_promise = PersistentPromise.init(v8_isolate, .{ .handle = evaluated.handle }); @@ -1479,7 +1471,8 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8, referrer: []c // But we can skip direclty to `resolveDynamicModule` which is // what the above callback will eventually do. self.resolveDynamicModule(state, gop.value_ptr.*); - return promise; + const v8_promise = promise; + return .{ .handle = v8_promise.handle }; } fn dynamicModuleSourceCallback(ctx: *anyopaque, module_source_: anyerror!ScriptManager.ModuleSource) void { @@ -1935,7 +1928,7 @@ fn jsUnsignedIntToZig(comptime T: type, max: comptime_int, maybe: u32) !T { return error.InvalidArgument; } -fn compileScript(isolate: v8.Isolate, ctx: v8.Context, src: []const u8, name: ?[]const u8) !v8.Script { +fn compileScript(isolate: v8.Isolate, ctx: v8.Context, src: []const u8, name: ?[]const u8) !js.Script { // compile const script_name = v8.String.initUtf8(isolate, name orelse "anonymous"); const script_source = v8.String.initUtf8(isolate, src); @@ -1946,15 +1939,16 @@ fn compileScript(isolate: v8.Isolate, ctx: v8.Context, src: []const u8, name: ?[ v8.ScriptCompilerSource.init(&script_comp_source, script_source, origin, null); defer script_comp_source.deinit(); - return v8.ScriptCompiler.compile( + const v8_script = v8.ScriptCompiler.compile( ctx, &script_comp_source, .kNoCompileOptions, .kNoCacheNoReason, ) catch return error.CompilationError; + return .{ .handle = v8_script.handle }; } -fn compileModule(isolate: v8.Isolate, src: []const u8, name: []const u8) !v8.Module { +fn compileModule(isolate: v8.Isolate, src: []const u8, name: []const u8) !js.Module { // compile const script_name = v8.String.initUtf8(isolate, name); const script_source = v8.String.initUtf8(isolate, src); @@ -1976,12 +1970,13 @@ fn compileModule(isolate: v8.Isolate, src: []const u8, name: []const u8) !v8.Mod v8.ScriptCompilerSource.init(&script_comp_source, script_source, origin, null); defer script_comp_source.deinit(); - return v8.ScriptCompiler.compileModule( + const v8_module = v8.ScriptCompiler.compileModule( isolate, &script_comp_source, .kNoCompileOptions, .kNoCacheNoReason, ) catch return error.CompilationError; + return .{ .handle = v8_module.handle }; } fn zigJsonToJs(isolate: v8.Isolate, v8_context: v8.Context, value: std.json.Value) !v8.Value { diff --git a/src/browser/js/Module.zig b/src/browser/js/Module.zig new file mode 100644 index 00000000..2723f2f9 --- /dev/null +++ b/src/browser/js/Module.zig @@ -0,0 +1,82 @@ +// 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 js = @import("js.zig"); +const v8 = js.v8; + +const Module = @This(); + +pub const Status = enum(u32) { + kUninstantiated = v8.c.kUninstantiated, + kInstantiating = v8.c.kInstantiating, + kInstantiated = v8.c.kInstantiated, + kEvaluating = v8.c.kEvaluating, + kEvaluated = v8.c.kEvaluated, + kErrored = v8.c.kErrored, +}; + +handle: *const v8.c.Module, + +pub fn getStatus(self: Module) Status { + return @enumFromInt(v8.c.v8__Module__GetStatus(self.handle)); +} + +pub fn getException(self: Module) v8.Value { + return .{ + .handle = v8.c.v8__Module__GetException(self.handle).?, + }; +} + +pub fn getModuleRequests(self: Module) v8.FixedArray { + return .{ + .handle = v8.c.v8__Module__GetModuleRequests(self.handle).?, + }; +} + +pub fn instantiate(self: Module, ctx_handle: *const v8.c.Context, cb: v8.c.ResolveModuleCallback) !bool { + var out: v8.c.MaybeBool = undefined; + v8.c.v8__Module__InstantiateModule(self.handle, ctx_handle, cb, &out); + if (out.has_value) { + return out.value; + } + return error.JsException; +} + +pub fn evaluate(self: Module, ctx_handle: *const v8.c.Context) !v8.Value { + const res = v8.c.v8__Module__Evaluate(self.handle, ctx_handle) orelse return error.JsException; + + if (self.getStatus() == .kErrored) { + return error.JsException; + } + + return .{ .handle = res }; +} + +pub fn getIdentityHash(self: Module) u32 { + return @bitCast(v8.c.v8__Module__GetIdentityHash(self.handle)); +} + +pub fn getModuleNamespace(self: Module) v8.Value { + return .{ + .handle = v8.c.v8__Module__GetModuleNamespace(self.handle).?, + }; +} + +pub fn getScriptId(self: Module) u32 { + return @intCast(v8.c.v8__Module__ScriptId(self.handle)); +} diff --git a/src/browser/js/Name.zig b/src/browser/js/Name.zig new file mode 100644 index 00000000..17b04173 --- /dev/null +++ b/src/browser/js/Name.zig @@ -0,0 +1,31 @@ +// 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 js = @import("js.zig"); +const v8 = js.v8; + +const Name = @This(); + +handle: *const v8.c.Name, + +pub fn toValue(self: Name) js.Value { + return .{ + .ctx = undefined, // Will be set by caller if needed + .handle = @ptrCast(self.handle), + }; +} diff --git a/src/browser/js/Object.zig b/src/browser/js/Object.zig index 25490560..5b62474f 100644 --- a/src/browser/js/Object.zig +++ b/src/browser/js/Object.zig @@ -79,6 +79,13 @@ pub fn toString(self: Object) ![]const u8 { return self.ctx.valueToString(js_value, .{}); } +pub fn toValue(self: Object) js.Value { + return .{ + .ctx = self.ctx, + .handle = @ptrCast(self.handle), + }; +} + pub fn format(self: Object, writer: *std.Io.Writer) !void { if (comptime IS_DEBUG) { const js_value = v8.Value{ .handle = @ptrCast(self.handle) }; diff --git a/src/browser/js/Promise.zig b/src/browser/js/Promise.zig new file mode 100644 index 00000000..adfdc752 --- /dev/null +++ b/src/browser/js/Promise.zig @@ -0,0 +1,49 @@ +// 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 js = @import("js.zig"); +const v8 = js.v8; + +const Promise = @This(); + +handle: *const v8.c.Promise, + +pub fn toObject(self: Promise) js.Object { + return .{ + .ctx = undefined, // Will be set by caller if needed + .handle = @ptrCast(self.handle), + }; +} + +pub fn toValue(self: Promise) js.Value { + return .{ + .ctx = undefined, // Will be set by caller if needed + .handle = @ptrCast(self.handle), + }; +} + +pub fn thenAndCatch(self: Promise, ctx_handle: *const v8.c.Context, on_fulfilled: js.Function, on_rejected: js.Function) !Promise { + const v8_context = v8.Context{ .handle = ctx_handle }; + const v8_on_fulfilled = v8.Function{ .handle = on_fulfilled.handle }; + const v8_on_rejected = v8.Function{ .handle = on_rejected.handle }; + + if (v8.c.v8__Promise__Then2(self.handle, v8_context.handle, v8_on_fulfilled.handle, v8_on_rejected.handle)) |handle| { + return Promise{ .handle = handle }; + } + return error.PromiseChainFailed; +} diff --git a/src/browser/js/PromiseResolver.zig b/src/browser/js/PromiseResolver.zig new file mode 100644 index 00000000..583bf39c --- /dev/null +++ b/src/browser/js/PromiseResolver.zig @@ -0,0 +1,75 @@ +// 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 js = @import("js.zig"); +const v8 = js.v8; +const log = @import("../../log.zig"); + +const PromiseResolver = @This(); + +ctx: *const js.Context, +handle: *const v8.c.PromiseResolver, + +pub fn init(ctx: *const js.Context) PromiseResolver { + return .{ + .ctx = ctx, + .handle = v8.c.v8__Promise__Resolver__New(ctx.handle).?, + }; +} + +pub fn promise(self: PromiseResolver) js.Promise { + return .{ + .handle = v8.c.v8__Promise__Resolver__GetPromise(self.handle).?, + }; +} + +pub fn resolve(self: PromiseResolver, comptime source: []const u8, value: anytype) void { + self._resolve(value) catch |err| { + log.err(.bug, "resolve", .{ .source = source, .err = err, .persistent = false }); + }; +} + +fn _resolve(self: PromiseResolver, value: anytype) !void { + const ctx: *js.Context = @constCast(self.ctx); + const js_value = try ctx.zigValueToJs(value, .{}); + + var out: v8.c.MaybeBool = undefined; + v8.c.v8__Promise__Resolver__Resolve(self.handle, self.ctx.handle, js_value.handle, &out); + if (!out.has_value or !out.value) { + return error.FailedToResolvePromise; + } + ctx.runMicrotasks(); +} + +pub fn reject(self: PromiseResolver, comptime source: []const u8, value: anytype) void { + self._reject(value) catch |err| { + log.err(.bug, "reject", .{ .source = source, .err = err, .persistent = false }); + }; +} + +fn _reject(self: PromiseResolver, value: anytype) !void { + const ctx: *js.Context = @constCast(self.ctx); + const js_value = try ctx.zigValueToJs(value, .{}); + + var out: v8.c.MaybeBool = undefined; + v8.c.v8__Promise__Resolver__Reject(self.handle, self.ctx.handle, js_value.handle, &out); + if (!out.has_value or !out.value) { + return error.FailedToRejectPromise; + } + ctx.runMicrotasks(); +} diff --git a/src/browser/js/Script.zig b/src/browser/js/Script.zig new file mode 100644 index 00000000..8b9c5676 --- /dev/null +++ b/src/browser/js/Script.zig @@ -0,0 +1,38 @@ +// 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 js = @import("js.zig"); +const v8 = js.v8; + +const Script = @This(); + +handle: *const v8.c.Script, + +pub fn compile(ctx_handle: *const v8.c.Context, src_handle: *const v8.c.String, origin: ?*const v8.c.ScriptOrigin) !Script { + if (v8.c.v8__Script__Compile(ctx_handle, src_handle, origin)) |handle| { + return .{ .handle = handle }; + } + return error.JsException; +} + +pub fn run(self: Script, ctx_handle: *const v8.c.Context) !v8.Value { + if (v8.c.v8__Script__Run(self.handle, ctx_handle)) |value| { + return .{ .handle = value }; + } + return error.JsException; +} diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index c6c92515..8b9680db 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -37,6 +37,12 @@ pub const String = @import("String.zig"); pub const Object = @import("Object.zig"); pub const TryCatch = @import("TryCatch.zig"); pub const Function = @import("Function.zig"); +pub const Promise = @import("Promise.zig"); +pub const PromiseResolver = @import("PromiseResolver.zig"); +pub const Module = @import("Module.zig"); +pub const BigInt = @import("BigInt.zig"); +pub const Name = @import("Name.zig"); +pub const Script = @import("Script.zig"); pub const Integer = @import("Integer.zig"); pub const Global = @import("global.zig").Global; @@ -72,47 +78,6 @@ pub const ArrayBuffer = struct { } }; -pub const PromiseResolver = struct { - context: *Context, - resolver: v8.PromiseResolver, - - pub fn promise(self: PromiseResolver) Promise { - return self.resolver.getPromise(); - } - - pub fn resolve(self: PromiseResolver, comptime source: []const u8, value: anytype) void { - self._resolve(value) catch |err| { - log.err(.bug, "resolve", .{ .source = source, .err = err, .persistent = false }); - }; - } - fn _resolve(self: PromiseResolver, value: anytype) !void { - const context = self.context; - const js_value = try context.zigValueToJs(value, .{}); - - const v8_context = v8.Context{ .handle = context.handle }; - if (self.resolver.resolve(v8_context, js_value) == null) { - return error.FailedToResolvePromise; - } - self.context.runMicrotasks(); - } - - pub fn reject(self: PromiseResolver, comptime source: []const u8, value: anytype) void { - self._reject(value) catch |err| { - log.err(.bug, "reject", .{ .source = source, .err = err, .persistent = false }); - }; - } - fn _reject(self: PromiseResolver, value: anytype) !void { - const context = self.context; - const js_value = try context.zigValueToJs(value); - - const v8_context = v8.Context{ .handle = context.handle }; - if (self.resolver.reject(v8_context, js_value) == null) { - return error.FailedToRejectPromise; - } - self.context.runMicrotasks(); - } -}; - pub const PersistentPromiseResolver = struct { context: *Context, resolver: v8.Persistent(v8.PromiseResolver), @@ -122,7 +87,8 @@ pub const PersistentPromiseResolver = struct { } pub fn promise(self: PersistentPromiseResolver) Promise { - return self.resolver.castToPromiseResolver().getPromise(); + const v8_promise = self.resolver.castToPromiseResolver().getPromise(); + return .{ .handle = v8_promise.handle }; } pub fn resolve(self: PersistentPromiseResolver, comptime source: []const u8, value: anytype) void { @@ -160,8 +126,6 @@ pub const PersistentPromiseResolver = struct { } }; -pub const Promise = v8.Promise; - pub const Exception = struct { inner: v8.Value, context: *const Context,