cdp: add runtime.consoleAPICalled

This commit is contained in:
Pierre Tachoire
2026-05-06 18:33:13 +02:00
parent 2c28023168
commit d6c9a5fb83
5 changed files with 120 additions and 20 deletions

View File

@@ -19,6 +19,7 @@
const std = @import("std");
const lp = @import("lightpanda");
const js = @import("browser/js/js.zig");
const Frame = @import("browser/Frame.zig");
const Transfer = @import("browser/HttpClient.zig").Transfer;
const Request = @import("browser/HttpClient.zig").Request;
@@ -87,6 +88,7 @@ const EventListeners = struct {
http_response_header_done: List = .{},
javascript_dialog_opening: List = .{},
console_message: List = .{},
runtime_console_message: List = .{},
};
const Events = union(enum) {
@@ -108,6 +110,7 @@ const Events = union(enum) {
http_response_header_done: *const ResponseHeaderDone,
javascript_dialog_opening: *const JavascriptDialogOpening,
console_message: *const ConsoleMessage,
runtime_console_message: *const ConsoleMessage,
};
const EventType = std.meta.FieldEnum(Events);
@@ -226,6 +229,7 @@ pub const DialogResponse = struct {
};
pub const ConsoleMessage = struct {
timestamp: u64,
source: enum {
xml,
javascript,
@@ -240,7 +244,7 @@ pub const ConsoleMessage = struct {
worker,
},
level: log.Level,
text: []const u8,
values: []js.Value,
url: ?[]const u8 = null,
line: ?u32 = null,
columns: ?u32 = null,

View File

@@ -21,6 +21,7 @@ const lp = @import("lightpanda");
const js = @import("../js/js.zig");
const Notification = @import("../../Notification.zig");
const datetime = @import("../../datetime.zig");
const logger = lp.log;
const LogLevel = lp.log.Level;
@@ -34,24 +35,21 @@ pub const init: Console = .{};
fn dispatchConsoleMessage(values: []js.Value, level: LogLevel, exec: *js.Execution) void {
const notification = exec.context.page.session.notification;
const text = formatValues(values, exec.call_arena) catch return;
const ts = datetime.timestamp(.monotonic);
notification.dispatch(.console_message, &.{
.source = .javascript,
.level = level,
.text = text,
.values = values,
.timestamp = ts,
});
}
fn formatValues(values: []js.Value, allocator: std.mem.Allocator) ![]const u8 {
var aw: std.io.Writer.Allocating = .init(allocator);
const w = &aw.writer;
for (values, 0..) |v, i| {
if (i != 0) try w.writeByte(' ');
const js_str = try v.toString();
try js_str.format(w);
}
return aw.written();
notification.dispatch(.runtime_console_message, &.{
.source = .javascript,
.level = level,
.values = values,
.timestamp = ts,
});
}
pub fn trace(_: *const Console, values: []js.Value, exec: *js.Execution) !void {

View File

@@ -641,6 +641,14 @@ pub const BrowserContext = struct {
self.notification.unregister(.console_message, self);
}
pub fn runtimeEnable(self: *BrowserContext) !void {
try self.notification.register(.runtime_console_message, self, onRuntimeConsoleMessage);
}
pub fn runtimeDisable(self: *BrowserContext) void {
self.notification.unregister(.runtime_console_message, self);
}
pub fn onFrameRemove(ctx: *anyopaque, _: Notification.FrameRemove) !void {
const self: *BrowserContext = @ptrCast(@alignCast(ctx));
@import("domains/page.zig").frameRemove(self);
@@ -802,11 +810,6 @@ pub const BrowserContext = struct {
};
}
pub fn onConsoleMessage(ctx: *anyopaque, msg: *const Notification.ConsoleMessage) !void {
const self: *BrowserContext = @ptrCast(@alignCast(ctx));
return @import("domains/console.zig").consoleMessage(self, msg);
}
// This is hacky x 2. First, we create the JSON payload by gluing our
// session_id onto it. Second, we're much more client/websocket aware than
// we should be.
@@ -846,6 +849,20 @@ pub const BrowserContext = struct {
try cdp.client.sendJSONRaw(buf);
}
pub fn onConsoleMessage(ctx: *anyopaque, msg: *const Notification.ConsoleMessage) !void {
const self: *BrowserContext = @ptrCast(@alignCast(ctx));
defer self.resetNotificationArena();
return @import("domains/console.zig").consoleMessage(self, msg);
}
pub fn onRuntimeConsoleMessage(ctx: *anyopaque, msg: *const Notification.ConsoleMessage) !void {
const self: *BrowserContext = @ptrCast(@alignCast(ctx));
defer self.resetNotificationArena();
return @import("domains/runtime.zig").consoleMessage(self, msg);
}
};
/// see: https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/core/v8/V8BindingDesign.md#world

View File

@@ -60,9 +60,19 @@ const ConsoleMessage = struct {
pub fn consoleMessage(bc: *CDP.BrowserContext, event: *const Notification.ConsoleMessage) !void {
const session_id = bc.session_id orelse return;
// format values
var aw: std.io.Writer.Allocating = .init(bc.notification_arena);
const w = &aw.writer;
for (event.values, 0..) |v, i| {
if (i != 0) try w.writeByte(' ');
const js_str = try v.toString();
try js_str.format(w);
}
return bc.cdp.sendEvent("Console.messageAdded", ConsoleMessage{
.source = @tagName(event.source),
.level = @tagName(event.level),
.text = event.text,
.text = aw.written(),
}, .{ .session_id = session_id });
}

View File

@@ -19,11 +19,14 @@
const std = @import("std");
const builtin = @import("builtin");
const js = @import("../../browser/js/js.zig");
const CDP = @import("../CDP.zig");
const Notification = @import("../../Notification.zig");
pub fn processMessage(cmd: *CDP.Command) !void {
const action = std.meta.stringToEnum(enum {
enable,
disable,
runIfWaitingForDebugger,
evaluate,
addBinding,
@@ -34,10 +37,24 @@ pub fn processMessage(cmd: *CDP.Command) !void {
switch (action) {
.runIfWaitingForDebugger => return cmd.sendResult(null, .{}),
.enable => return enable(cmd),
.disable => return disable(cmd),
else => return sendInspector(cmd, action),
}
}
fn enable(cmd: *CDP.Command) !void {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
try bc.runtimeEnable();
return sendInspector(cmd, .enable);
}
fn disable(cmd: *CDP.Command) !void {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
bc.runtimeDisable();
return sendInspector(cmd, .disable);
}
fn sendInspector(cmd: *CDP.Command, action: anytype) !void {
// save script in file at debug mode
if (builtin.mode == .Debug) {
@@ -91,3 +108,57 @@ fn logInspector(cmd: *CDP.Command, action: anytype) !void {
defer f.close();
try f.writeAll(script);
}
const RemoteObject = struct {
type: []const u8,
subtype: ?[]const u8,
className: ?[]const u8,
description: ?[]const u8,
objectId: ?[]const u8,
value: js.Value,
};
const ConsoleMessage = struct {
type: []const u8,
executionContextId: i32,
timestamp: u64,
args: []RemoteObject,
};
pub fn consoleMessage(bc: *CDP.BrowserContext, event: *const Notification.ConsoleMessage) !void {
const session_id = bc.session_id orelse return;
const frame = bc.session.currentFrame() orelse return error.FrameNotLoaded;
var ls: js.Local.Scope = undefined;
frame.js.localScope(&ls);
defer ls.deinit();
const context_id = bc.inspector_session.inspector.getContextId(&ls.local);
const arena = bc.notification_arena;
var args: std.ArrayList(RemoteObject) = .empty;
for (event.values) |value| {
const remote_object = try bc.inspector_session.getRemoteObject(
&ls.local,
"",
value,
);
defer remote_object.deinit();
try args.append(arena, .{
.type = try remote_object.getType(arena),
.subtype = try remote_object.getSubtype(arena),
.className = try remote_object.getClassName(arena),
.description = try remote_object.getDescription(arena),
.objectId = try remote_object.getObjectId(arena),
.value = value,
});
}
return bc.cdp.sendEvent("Runtime.consoleAPICalled", ConsoleMessage{
.type = @tagName(event.level),
.timestamp = event.timestamp,
.executionContextId = context_id,
.args = args.items,
}, .{ .session_id = session_id });
}