mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
refactor(browser): move console log buffering to Session
Moves the console message buffer from Frame to Session and populates it via the notification system. This centralizes log collection for the MCP tool and simplifies the Console Web API implementation.
This commit is contained in:
@@ -291,7 +291,7 @@ pub fn disableSubframes(self: *const Config) bool {
|
||||
|
||||
pub fn disableWorkers(self: *const Config) bool {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp => |opts| opts.disable_workers,
|
||||
inline .serve, .fetch, .mcp, .agent => |opts| opts.disable_workers,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -255,7 +255,6 @@ _parent_notified: bool = false,
|
||||
|
||||
_type: enum { root, frame }, // only used for logs right now
|
||||
_req_id: u32 = 0,
|
||||
_console_messages: std.Io.Writer.Allocating,
|
||||
_navigated_options: ?NavigatedOpts = null,
|
||||
|
||||
pub fn init(self: *Frame, frame_id: u32, page: *Page, parent: ?*Frame) !void {
|
||||
@@ -291,7 +290,6 @@ 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;
|
||||
|
||||
@@ -2867,35 +2865,6 @@ fn isXmlNameChar(c: u21) bool {
|
||||
(c >= 0x203F and c <= 0x2040);
|
||||
}
|
||||
|
||||
const max_console_bytes = 64 * 1024;
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
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 text;
|
||||
}
|
||||
|
||||
pub fn dupeString(self: *Frame, value: []const u8) ![]const u8 {
|
||||
if (String.intern(value)) |v| {
|
||||
return v;
|
||||
@@ -3669,8 +3638,6 @@ pub const NavigateReason = enum {
|
||||
initialFrameNavigation,
|
||||
};
|
||||
|
||||
pub const ConsoleLevel = enum { log, debug, info, warn, @"error" };
|
||||
|
||||
pub const NavigateOpts = struct {
|
||||
cdp_id: ?i64 = null,
|
||||
reason: NavigateReason = .address_bar,
|
||||
|
||||
@@ -89,6 +89,13 @@ subframe_loading_enabled: bool = true,
|
||||
// session init; the LP.configureLoading CDP method can flip it per-session.
|
||||
worker_loading_enabled: bool = true,
|
||||
|
||||
// Session-scoped capture of console.* output for the `consoleLogs` MCP tool.
|
||||
// Fed by an `console_message` notification listener; drained on tool call.
|
||||
// Capped at `max_console_bytes` so a runaway page can't grow it unbounded.
|
||||
_console_messages: std.Io.Writer.Allocating,
|
||||
|
||||
const max_console_bytes = 64 * 1024;
|
||||
|
||||
pub fn init(self: *Session, browser: *Browser, notification: *Notification) !void {
|
||||
const allocator = browser.app.allocator;
|
||||
const arena_pool = browser.arena_pool;
|
||||
@@ -110,10 +117,16 @@ pub fn init(self: *Session, browser: *Browser, notification: *Notification) !voi
|
||||
// CLI defaults; LP.configureLoading can flip these per-session.
|
||||
.subframe_loading_enabled = !browser.app.config.disableSubframes(),
|
||||
.worker_loading_enabled = !browser.app.config.disableWorkers(),
|
||||
._console_messages = .init(allocator),
|
||||
};
|
||||
errdefer self._console_messages.deinit();
|
||||
|
||||
try notification.register(.console_message, self, onConsoleMessage);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Session) void {
|
||||
self.notification.unregister(.console_message, self);
|
||||
|
||||
if (self._pending != null) {
|
||||
self.discardPendingPage();
|
||||
}
|
||||
@@ -132,9 +145,38 @@ pub fn deinit(self: *Session) void {
|
||||
self.fc_identity_pool.deinit();
|
||||
|
||||
self.storage_shed.deinit(self.browser.app.allocator);
|
||||
self._console_messages.deinit();
|
||||
self.arena_pool.release(self.arena);
|
||||
}
|
||||
|
||||
fn onConsoleMessage(ctx: *anyopaque, msg: *const Notification.ConsoleMessage) !void {
|
||||
const self: *Session = @ptrCast(@alignCast(ctx));
|
||||
const aw = &self._console_messages;
|
||||
const start = aw.written().len;
|
||||
if (start >= max_console_bytes) return;
|
||||
appendConsoleMessageInner(&aw.writer, msg) catch {
|
||||
aw.shrinkRetainingCapacity(start);
|
||||
};
|
||||
}
|
||||
|
||||
fn appendConsoleMessageInner(w: *std.Io.Writer, msg: *const Notification.ConsoleMessage) !void {
|
||||
try w.print("[{s}] ", .{@tagName(msg.type)});
|
||||
for (msg.values, 0..) |value, i| {
|
||||
if (i > 0) try w.writeAll(" ");
|
||||
try value.format(w);
|
||||
}
|
||||
try w.writeByte('\n');
|
||||
}
|
||||
|
||||
/// Drains and clears the buffered console output. The returned slice is valid
|
||||
/// until the next dispatched `console_message` reuses the backing storage,
|
||||
/// so callers must consume or copy it before that happens.
|
||||
pub fn drainConsoleMessages(self: *Session) []const u8 {
|
||||
const text = self._console_messages.written();
|
||||
self._console_messages.clearRetainingCapacity();
|
||||
return text;
|
||||
}
|
||||
|
||||
pub fn processQueuedDestroyed(self: *Session) void {
|
||||
for (self._queued_destroy.items) |page| {
|
||||
page.deinit();
|
||||
|
||||
@@ -1037,8 +1037,7 @@ fn lookupLpEnv(name: []const u8) ?[:0]const u8 {
|
||||
}
|
||||
|
||||
fn execConsoleLogs(arena: std.mem.Allocator, session: *lp.Session) ToolError![]const u8 {
|
||||
const page = try requireFrame(session);
|
||||
const text = page.drainConsoleMessages();
|
||||
const text = session.drainConsoleMessages();
|
||||
if (text.len == 0) return "No console messages.";
|
||||
return arena.dupe(u8, text) catch ToolError.InternalError;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Notification = @import("../../Notification.zig");
|
||||
const datetime = @import("../../datetime.zig");
|
||||
|
||||
@@ -63,25 +62,21 @@ pub fn trace(_: *const Console, values: []js.Value, exec: *js.Execution) !void {
|
||||
|
||||
pub fn debug(_: *const Console, values: []js.Value, exec: *js.Execution) void {
|
||||
logger.debug(.js, "console.debug", .{ValueWriter{ .values = values }});
|
||||
appendMessage(exec, .debug, values);
|
||||
dispatchConsoleMessage(values, .debug, exec);
|
||||
}
|
||||
|
||||
pub fn info(_: *const Console, values: []js.Value, exec: *js.Execution) void {
|
||||
logger.info(.js, "console.info", .{ValueWriter{ .values = values }});
|
||||
appendMessage(exec, .info, values);
|
||||
dispatchConsoleMessage(values, .info, exec);
|
||||
}
|
||||
|
||||
pub fn log(_: *const Console, values: []js.Value, exec: *js.Execution) void {
|
||||
logger.info(.js, "console.log", .{ValueWriter{ .values = values }});
|
||||
appendMessage(exec, .log, values);
|
||||
dispatchConsoleMessage(values, .info, exec);
|
||||
}
|
||||
|
||||
pub fn warn(_: *const Console, values: []js.Value, exec: *js.Execution) void {
|
||||
logger.warn(.js, "console.warn", .{ValueWriter{ .values = values }});
|
||||
appendMessage(exec, .warn, values);
|
||||
dispatchConsoleMessage(values, .info, exec);
|
||||
}
|
||||
|
||||
@@ -92,12 +87,11 @@ pub fn assert(_: *const Console, assertion: js.Value, values: []js.Value, exec:
|
||||
return;
|
||||
}
|
||||
logger.warn(.js, "console.assert", .{ValueWriter{ .values = values }});
|
||||
appendMessage(exec, .warn, values);
|
||||
dispatchConsoleMessage(values, .warn, exec);
|
||||
}
|
||||
|
||||
pub fn @"error"(_: *const Console, values: []js.Value, exec: *js.Execution) void {
|
||||
logger.warn(.js, "console.error", .{ValueWriter{ .values = values, .stack = exec.context.local.?.stackTrace() catch |err| @errorName(err) orelse "???" }});
|
||||
appendMessage(exec, .@"error", values);
|
||||
dispatchConsoleMessage(values, .@"error", exec);
|
||||
}
|
||||
|
||||
@@ -178,16 +172,6 @@ fn timestamp() u64 {
|
||||
return @import("../../datetime.zig").timestamp(.monotonic);
|
||||
}
|
||||
|
||||
// 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.ConsoleLevel, values: []js.Value) void {
|
||||
switch (exec.context.global) {
|
||||
.frame => |f| f.appendConsoleMessage(level, values),
|
||||
.worker => {},
|
||||
}
|
||||
}
|
||||
|
||||
const ValueWriter = struct {
|
||||
values: []js.Value,
|
||||
stack: ?[]const u8 = null,
|
||||
|
||||
Reference in New Issue
Block a user