diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 8e4e6806..e16c5a05 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -804,9 +804,8 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult { // it AFTER. const ms_to_next_task = try scheduler.run(); - if (try_catch.hasCaught()) { - const msg = (try try_catch.err(self.arena)) orelse "unknown"; - log.info(.js, "page wait", .{ .err = msg, .src = "scheduler" }); + if (try_catch.caught(self.call_arena)) |caught| { + log.info(.js, "page wait", .{ .caught = caught, .src = "scheduler" }); } const http_active = http_client.active; @@ -1992,9 +1991,9 @@ pub fn createElementNS(self: *Page, namespace: Element.Namespace, name: []const self._upgrading_element = node; defer self._upgrading_element = prev_upgrading; - var result: JS.Function.Result = undefined; - _ = def.constructor.newInstance(&result) catch |err| { - log.warn(.js, "custom element constructor", .{ .name = name, .err = err }); + var caught: JS.TryCatch.Caught = undefined; + _ = def.constructor.newInstance(&caught) catch |err| { + log.warn(.js, "custom element constructor", .{ .name = name, .err = err, .caught = caught }); return node; }; diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig index 8e0f5916..da95bc51 100644 --- a/src/browser/ScriptManager.zig +++ b/src/browser/ScriptManager.zig @@ -834,12 +834,10 @@ pub const Script = struct { return; } - const msg = try_catch.err(page.arena) catch |err| @errorName(err) orelse "unknown"; + const caught = try_catch.caughtOrError(page.call_arena, error.Unknown); log.warn(.js, "eval script", .{ .url = url, - .err = msg, - .stack = try_catch.stack(page.call_arena) catch null, - .line = try_catch.sourceLineNumber() orelse 0, + .caught = caught, .cacheable = cacheable, }); @@ -859,13 +857,12 @@ pub const Script = struct { return; }; - var result: js.Function.Result = undefined; - cb.tryCall(void, .{event}, &result) catch { + var caught: js.TryCatch.Caught = undefined; + cb.tryCall(void, .{event}, &caught) catch { log.warn(.js, "script callback", .{ .url = self.url, .type = typ, - .err = result.exception, - .stack = result.stack, + .caught = caught, }); }; } diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 69603345..13c5b153 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -1386,15 +1386,13 @@ fn dynamicModuleSourceCallback(ctx: *anyopaque, module_source_: anyerror!ScriptM try_catch.init(self); defer try_catch.deinit(); - break :blk self.module(true, ms.src(), state.specifier, true) catch { - const ex = try_catch.exception(self.call_arena) catch |err| @errorName(err) orelse "unknown error"; + break :blk self.module(true, ms.src(), state.specifier, true) catch |err| { + const caught = try_catch.caughtOrError(self.call_arena, err); log.err(.js, "module compilation failed", .{ + .caught = caught, .specifier = state.specifier, - .exception = ex, - .stack = try_catch.stack(self.call_arena) catch null, - .line = try_catch.sourceLineNumber() orelse 0, }); - _ = state.resolver.reject("dynamic compilation failure", self.newString(ex)); + _ = state.resolver.reject("dynamic compilation failure", self.newString(caught.exception orelse "")); return; }; }; diff --git a/src/browser/js/Function.zig b/src/browser/js/Function.zig index 61259fc9..f0ec8950 100644 --- a/src/browser/js/Function.zig +++ b/src/browser/js/Function.zig @@ -50,7 +50,7 @@ pub fn withThis(self: *const Function, value: anytype) !Function { }; } -pub fn newInstance(self: *const Function, result: *Result) !js.Object { +pub fn newInstance(self: *const Function, caught: *js.TryCatch.Caught) !js.Object { const ctx = self.ctx; var try_catch: js.TryCatch = undefined; @@ -60,14 +60,7 @@ pub fn newInstance(self: *const Function, result: *Result) !js.Object { // This creates a new instance using this Function as a constructor. // const c_args = @as(?[*]const ?*c.Value, @ptrCast(&.{})); const handle = v8.v8__Function__NewInstance(self.handle, ctx.handle, 0, null) orelse { - if (try_catch.hasCaught()) { - const allocator = ctx.call_arena; - result.stack = try_catch.stack(allocator) catch null; - result.exception = (try_catch.exception(allocator) catch "???") orelse "???"; - } else { - result.stack = null; - result.exception = "???"; - } + caught.* = try_catch.caughtOrError(ctx.call_arena, error.Unknown); return error.JsConstructorFailed; }; @@ -81,25 +74,18 @@ pub fn call(self: *const Function, comptime T: type, args: anytype) !T { return self.callWithThis(T, self.getThis(), args); } -pub fn tryCall(self: *const Function, comptime T: type, args: anytype, result: *Result) !T { - return self.tryCallWithThis(T, self.getThis(), args, result); +pub fn tryCall(self: *const Function, comptime T: type, args: anytype, caught: *js.TryCatch.Caught) !T { + return self.tryCallWithThis(T, self.getThis(), args, caught); } -pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, result: *Result) !T { +pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, caught: *js.TryCatch.Caught) !T { var try_catch: js.TryCatch = undefined; try_catch.init(self.ctx); defer try_catch.deinit(); return self.callWithThis(T, this, args) catch |err| { - if (try_catch.hasCaught()) { - const allocator = self.ctx.call_arena; - result.stack = try_catch.stack(allocator) catch null; - result.exception = (try_catch.exception(allocator) catch @errorName(err)) orelse @errorName(err); - } else { - result.stack = null; - result.exception = @errorName(err); - } + caught.* = try_catch.caughtOrError(self.ctx.call_arena, err); return err; }; } diff --git a/src/browser/js/TryCatch.zig b/src/browser/js/TryCatch.zig index 7009682b..0b50db0f 100644 --- a/src/browser/js/TryCatch.zig +++ b/src/browser/js/TryCatch.zig @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) // // Francis Bouvier // Pierre Tachoire @@ -36,53 +36,72 @@ pub fn hasCaught(self: TryCatch) bool { return v8.v8__TryCatch__HasCaught(&self.handle); } -// the caller needs to deinit the string returned -pub fn exception(self: TryCatch, allocator: Allocator) !?[]const u8 { - const msg_value = v8.v8__TryCatch__Exception(&self.handle) orelse return null; - const msg = js.Value{ .ctx = self.ctx, .handle = msg_value }; - return try self.ctx.valueToString(msg, .{ .allocator = allocator }); -} - -// the caller needs to deinit the string returned -pub fn stack(self: TryCatch, allocator: Allocator) !?[]const u8 { - const ctx = self.ctx; - const s_value = v8.v8__TryCatch__StackTrace(&self.handle, ctx.handle) orelse return null; - const s = js.Value{ .ctx = ctx, .handle = s_value }; - return try ctx.valueToString(s, .{ .allocator = allocator }); -} - -// the caller needs to deinit the string returned -pub fn sourceLine(self: TryCatch, allocator: Allocator) !?[]const u8 { - const ctx = self.ctx; - const msg = v8.v8__TryCatch__Message(&self.handle) orelse return null; - const source_line_handle = v8.v8__Message__GetSourceLine(msg, ctx.handle) orelse return null; - return try ctx.jsStringToZig(source_line_handle, .{ .allocator = allocator }); -} - -pub fn sourceLineNumber(self: TryCatch) ?u32 { - const ctx = self.ctx; - const msg = v8.v8__TryCatch__Message(&self.handle) orelse return null; - const line = v8.v8__Message__GetLineNumber(msg, ctx.handle); - if (line < 0) { +pub fn caught(self: TryCatch, allocator: Allocator) ?Caught { + if (!self.hasCaught()) { return null; } - return @intCast(line); + + const ctx = self.ctx; + + var hs: js.HandleScope = undefined; + hs.init(ctx.isolate); + defer hs.deinit(); + + const line: ?u32 = blk: { + const handle = v8.v8__TryCatch__Message(&self.handle) orelse return null; + const l = v8.v8__Message__GetLineNumber(handle, ctx.handle); + break :blk if (l < 0) null else @intCast(l); + }; + + const exception: ?[]const u8 = blk: { + const handle = v8.v8__TryCatch__Exception(&self.handle) orelse break :blk null; + break :blk ctx.valueToString(.{ .ctx = ctx, .handle = handle }, .{ .allocator = allocator }) catch |err| @errorName(err); + }; + + const stack: ?[]const u8 = blk: { + const handle = v8.v8__TryCatch__StackTrace(&self.handle, ctx.handle) orelse break :blk null; + break :blk ctx.valueToString(.{ .ctx = ctx, .handle = handle }, .{ .allocator = allocator }) catch |err| @errorName(err); + }; + + return .{ + .line = line, + .stack = stack, + .caught = true, + .exception = exception, + }; } -// a shorthand method to return either the entire stack message -// or just the exception message -// - in Debug mode return the stack if available -// - otherwise return the exception if available -// the caller needs to deinit the string returned -pub fn err(self: TryCatch, allocator: Allocator) !?[]const u8 { - if (comptime @import("builtin").mode == .Debug) { - if (try self.stack(allocator)) |msg| { - return msg; - } - } - return try self.exception(allocator); +pub fn caughtOrError(self: TryCatch, allocator: Allocator, err: anyerror) Caught { + return self.caught(allocator) orelse .{ + .caught = false, + .line = null, + .stack = null, + .exception = @errorName(err), + }; } pub fn deinit(self: *TryCatch) void { v8.v8__TryCatch__DESTRUCT(&self.handle); } + +pub const Caught = struct { + line: ?u32, + caught: bool, + stack: ?[]const u8, + exception: ?[]const u8, + + pub fn format(self: Caught, writer: *std.Io.Writer) !void { + const separator = @import("../../log.zig").separator(); + try writer.print("{s}exception: {?s}", .{ separator, self.exception }); + try writer.print("{s}stack: {?s}", .{ separator, self.stack }); + try writer.print("{s}line: {?d}", .{ separator, self.line }); + try writer.print("{s}caught: {any}", .{ separator, self.caught }); + } + + pub fn logFmt(self: Caught, comptime prefix: []const u8, writer: anytype) !void { + try writer.write(prefix ++ ".exception", self.exception orelse "???"); + try writer.write(prefix ++ ".stack", self.stack orelse "na"); + try writer.write(prefix ++ ".line", self.line); + try writer.write(prefix ++ ".caught", self.caught); + } +}; diff --git a/src/browser/webapi/CustomElementRegistry.zig b/src/browser/webapi/CustomElementRegistry.zig index 749f14a4..3f0aa382 100644 --- a/src/browser/webapi/CustomElementRegistry.zig +++ b/src/browser/webapi/CustomElementRegistry.zig @@ -174,9 +174,9 @@ pub fn upgradeCustomElement(custom: *Custom, definition: *CustomElementDefinitio page._upgrading_element = node; defer page._upgrading_element = prev_upgrading; - var result: js.Function.Result = undefined; - _ = definition.constructor.newInstance(&result) catch |err| { - log.warn(.js, "custom element upgrade", .{ .name = definition.name, .err = err }); + var caught: js.TryCatch.Caught = undefined; + _ = definition.constructor.newInstance(&caught) catch |err| { + log.warn(.js, "custom element upgrade", .{ .name = definition.name, .err = err, .caught = caught }); return error.CustomElementUpgradeFailed; }; diff --git a/src/browser/webapi/collections/DOMTokenList.zig b/src/browser/webapi/collections/DOMTokenList.zig index b47ae78d..b6d7b45f 100644 --- a/src/browser/webapi/collections/DOMTokenList.zig +++ b/src/browser/webapi/collections/DOMTokenList.zig @@ -189,9 +189,9 @@ pub fn forEach(self: *DOMTokenList, cb_: js.Function, js_this_: ?js.Object, page if (gop.found_existing) { continue; } - var result: js.Function.Result = undefined; - cb.tryCall(void, .{ token, i, self }, &result) catch { - log.debug(.js, "forEach callback", .{ .err = result.exception, .stack = result.stack, .source = "DOMTokenList" }); + var caught: js.TryCatch.Caught = undefined; + cb.tryCall(void, .{ token, i, self }, &caught) catch { + log.debug(.js, "forEach callback", .{ .caught = caught, .source = "DOMTokenList" }); return; }; i += 1; diff --git a/src/browser/webapi/collections/NodeList.zig b/src/browser/webapi/collections/NodeList.zig index 095f0fda..1e20fb5b 100644 --- a/src/browser/webapi/collections/NodeList.zig +++ b/src/browser/webapi/collections/NodeList.zig @@ -83,9 +83,9 @@ pub fn forEach(self: *NodeList, cb: js.Function, page: *Page) !void { return; } - var result: js.Function.Result = undefined; - cb.tryCall(void, .{ next.value, i, self }, &result) catch { - log.debug(.js, "forEach callback", .{ .err = result.exception, .stack = result.stack, .source = "nodelist" }); + var caught: js.TryCatch.Caught = undefined; + cb.tryCall(void, .{ next.value, i, self }, &caught) catch { + log.debug(.js, "forEach callback", .{ .caught = caught, .source = "nodelist" }); return; }; } diff --git a/src/browser/webapi/element/html/Custom.zig b/src/browser/webapi/element/html/Custom.zig index 58f72984..247f82f1 100644 --- a/src/browser/webapi/element/html/Custom.zig +++ b/src/browser/webapi/element/html/Custom.zig @@ -195,9 +195,9 @@ pub fn checkAndAttachBuiltIn(element: *Element, page: *Page) !void { page._upgrading_element = node; defer page._upgrading_element = prev_upgrading; - var result: js.Function.Result = undefined; - _ = definition.constructor.newInstance(&result) catch |err| { - log.warn(.js, "custom builtin ctor", .{ .name = is_value, .err = err }); + var caught: js.TryCatch.Caught = undefined; + _ = definition.constructor.newInstance(&caught) catch |err| { + log.warn(.js, "custom builtin ctor", .{ .name = is_value, .err = err, .caught = caught }); return; }; } diff --git a/src/browser/webapi/net/Headers.zig b/src/browser/webapi/net/Headers.zig index 7a2f60ac..e5462d82 100644 --- a/src/browser/webapi/net/Headers.zig +++ b/src/browser/webapi/net/Headers.zig @@ -78,9 +78,9 @@ pub fn forEach(self: *Headers, cb_: js.Function, js_this_: ?js.Object) !void { const cb = if (js_this_) |js_this| try cb_.withThis(js_this) else cb_; for (self._list._entries.items) |entry| { - var result: js.Function.Result = undefined; - cb.tryCall(void, .{ entry.value.str(), entry.name.str(), self }, &result) catch { - log.debug(.js, "forEach callback", .{ .err = result.exception, .stack = result.stack, .source = "headers" }); + var caught: js.TryCatch.Caught = undefined; + cb.tryCall(void, .{ entry.value.str(), entry.name.str(), self }, &caught) catch { + log.debug(.js, "forEach callback", .{ .caught = caught, .source = "headers" }); }; } } diff --git a/src/log.zig b/src/log.zig index f6cce6b1..9936cbb0 100644 --- a/src/log.zig +++ b/src/log.zig @@ -333,10 +333,10 @@ fn writeString(comptime format: Format, value: []const u8, writer: *std.Io.Write pub const LogFormatWriter = struct { writer: *std.Io.Writer, - pub fn write(self: LogFormatWriter, key: []const u8, value: []const u8) !void { + pub fn write(self: LogFormatWriter, key: []const u8, value: anytype) !void { const writer = self.writer; try writer.print(" {s}=", .{key}); - try writeString(.logfmt, value, writer); + try writeValue(.logfmt, value, writer); } }; diff --git a/src/testing.zig b/src/testing.zig index a7a63f23..4d05911a 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -407,8 +407,8 @@ fn runWebApiTest(test_file: [:0]const u8) !void { test_browser.runMicrotasks(); js_context.eval("testing.assertOk()", "testing.assertOk()") catch |err| { - const msg = try_catch.err(arena_allocator) catch @errorName(err) orelse "unknown"; - std.debug.print("{s}: test failure\nError: {s}\n", .{ test_file, msg }); + const caught = try_catch.caughtOrError(arena_allocator, err); + std.debug.print("{s}: test failure\nError: {f}\n", .{ test_file, caught }); return err; }; }