diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index a01641ed..41130f83 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -256,7 +256,7 @@ _parent_notified: bool = false, _type: enum { root, frame }, // only used for logs right now _req_id: u32 = 0, -_console_messages: std.ArrayList(ConsoleMessage) = .{}, +_console_messages: std.Io.Writer.Allocating, _navigated_options: ?NavigatedOpts = null, pub fn init(self: *Frame, frame_id: u32, page: *Page, parent: ?*Frame) !void { @@ -292,6 +292,7 @@ pub fn init(self: *Frame, frame_id: u32, page: *Page, parent: ?*Frame) !void { ._style_manager = undefined, ._script_manager = undefined, ._event_manager = EventManager.init(arena, self), + ._console_messages = .init(arena), }; self._to_load = &self._to_load_1; @@ -2786,24 +2787,33 @@ fn isXmlNameChar(c: u21) bool { (c >= 0x203F and c <= 0x2040); } -const max_console_messages = 1000; +const max_console_bytes = 64 * 1024; -pub fn appendConsoleMessage(self: *Frame, level: ConsoleMessage.Level, values: []JS.Value) void { - if (self._console_messages.items.len >= max_console_messages) return; - var aw: std.Io.Writer.Allocating = .init(self.arena); - for (values, 0..) |value, i| { - if (i > 0) aw.writer.writeAll(" ") catch return; - value.format(&aw.writer) catch return; - } - const text = aw.written(); - self._console_messages.append(self.arena, .{ .level = level, .text = text }) catch return; +pub fn appendConsoleMessage(self: *Frame, level: ConsoleLevel, values: []JS.Value) void { + const aw = &self._console_messages; + const start = aw.written().len; + if (start >= max_console_bytes) return; + appendConsoleMessageInner(&aw.writer, level, values) catch { + aw.shrinkRetainingCapacity(start); + }; } -/// Returns buffered console messages and clears the buffer. -pub fn drainConsoleMessages(self: *Frame) []const ConsoleMessage { - const items = self._console_messages.items; +fn appendConsoleMessageInner(w: *std.Io.Writer, level: ConsoleLevel, values: []JS.Value) !void { + try w.print("[{s}] ", .{@tagName(level)}); + for (values, 0..) |value, i| { + if (i > 0) try w.writeAll(" "); + try value.format(w); + } + try w.writeByte('\n'); +} + +/// Returns the buffered console output and clears the buffer. The returned +/// slice is valid until the next `appendConsoleMessage` reuses the backing +/// storage, so callers must consume or copy it before that happens. +pub fn drainConsoleMessages(self: *Frame) []const u8 { + const text = self._console_messages.written(); self._console_messages.clearRetainingCapacity(); - return items; + return text; } pub fn dupeString(self: *Frame, value: []const u8) ![]const u8 { @@ -3563,12 +3573,7 @@ pub const NavigateReason = enum { initialFrameNavigation, }; -pub const ConsoleMessage = struct { - level: Level, - text: []const u8, - - pub const Level = enum { log, debug, info, warn, @"error" }; -}; +pub const ConsoleLevel = enum { log, debug, info, warn, @"error" }; pub const NavigateOpts = struct { cdp_id: ?i64 = null, diff --git a/src/browser/tools.zig b/src/browser/tools.zig index 03c4a5fa..4b8a6891 100644 --- a/src/browser/tools.zig +++ b/src/browser/tools.zig @@ -880,15 +880,9 @@ fn execConsoleLogs( arena: std.mem.Allocator, ) ToolError![]const u8 { const page = session.currentFrame() orelse return ToolError.FrameNotLoaded; - const messages = page.drainConsoleMessages(); - if (messages.len == 0) return "No console messages."; - - var aw: std.Io.Writer.Allocating = .init(arena); - const writer = &aw.writer; - for (messages) |msg| { - writer.print("[{s}] {s}\n", .{ @tagName(msg.level), msg.text }) catch return ToolError.InternalError; - } - return aw.written(); + const text = page.drainConsoleMessages(); + if (text.len == 0) return "No console messages."; + return arena.dupe(u8, text) catch ToolError.InternalError; } fn execGetUrl(session: *lp.Session) ToolError![]const u8 { diff --git a/src/browser/webapi/Console.zig b/src/browser/webapi/Console.zig index 50bd207e..5cb38ba0 100644 --- a/src/browser/webapi/Console.zig +++ b/src/browser/webapi/Console.zig @@ -152,7 +152,7 @@ fn timestamp() u64 { // Forwards frame-context console output to the Frame's message buffer (read by // the `consoleLogs` tool / CDP Runtime.consoleAPICalled). Worker contexts are // dropped — no buffer is attached there. -fn appendMessage(exec: *js.Execution, level: Frame.ConsoleMessage.Level, values: []js.Value) void { +fn appendMessage(exec: *js.Execution, level: Frame.ConsoleLevel, values: []js.Value) void { switch (exec.context.global) { .frame => |f| f.appendConsoleMessage(level, values), .worker => {},