From c57bb9ef7295eec937559c2f068db6a0dcd3c0f1 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Mon, 15 Apr 2024 12:14:33 +0200 Subject: [PATCH 001/117] WIP: CDP Signed-off-by: Francis Bouvier --- src/cdp/browser.zig | 77 +++++++++++++++ src/cdp/cdp.zig | 87 +++++++++++++++++ src/cdp/target.zig | 100 +++++++++++++++++++ src/main.zig | 69 +++---------- src/server.zig | 231 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 506 insertions(+), 58 deletions(-) create mode 100644 src/cdp/browser.zig create mode 100644 src/cdp/cdp.zig create mode 100644 src/cdp/target.zig create mode 100644 src/server.zig diff --git a/src/cdp/browser.zig b/src/cdp/browser.zig new file mode 100644 index 00000000..ff20c329 --- /dev/null +++ b/src/cdp/browser.zig @@ -0,0 +1,77 @@ +const std = @import("std"); + +const server = @import("../server.zig"); +const Ctx = server.CmdContext; +const SendFn = server.SendFn; +const result = @import("cdp.zig").result; +const getParams = @import("cdp.zig").getParams; + +const BrowserMethods = enum { + getVersion, + setDownloadBehavior, +}; + +pub fn browser( + alloc: std.mem.Allocator, + id: u64, + action: []const u8, + scanner: *std.json.Scanner, + ctx: *Ctx, + comptime sendFn: SendFn, +) ![]const u8 { + const method = std.meta.stringToEnum(BrowserMethods, action) orelse + return error.UnknownBrowserMethod; + return switch (method) { + .getVersion => browserGetVersion(alloc, id, scanner, ctx, sendFn), + .setDownloadBehavior => browserSetDownloadBehavior(alloc, id, scanner, ctx, sendFn), + }; +} + +const ProtocolVersion = "1.3"; +const Product = "Chrome/124.0.6367.29"; +const Revision = "@9e6ded5ac1ff5e38d930ae52bd9aec09bd1a68e4"; +const UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"; +const JsVersion = "12.4.254.8"; + +fn browserGetVersion( + alloc: std.mem.Allocator, + id: u64, + _: *std.json.Scanner, + _: *Ctx, + comptime _: SendFn, +) ![]const u8 { + const Res = struct { + protocolVersion: []const u8, + product: []const u8, + revision: []const u8, + userAgent: []const u8, + jsVersion: []const u8, + }; + + const res = Res{ + .protocolVersion = ProtocolVersion, + .product = Product, + .revision = Revision, + .userAgent = UserAgent, + .jsVersion = JsVersion, + }; + return result(alloc, id, Res, res); +} + +fn browserSetDownloadBehavior( + alloc: std.mem.Allocator, + id: u64, + scanner: *std.json.Scanner, + _: *Ctx, + comptime _: SendFn, +) ![]const u8 { + const Params = struct { + behavior: []const u8, + browserContextId: ?[]const u8 = null, + downloadPath: ?[]const u8 = null, + eventsEnabled: ?bool = null, + }; + const params = try getParams(alloc, Params, scanner); + std.log.debug("params {any}", .{params}); + return result(alloc, id, null, null); +} diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig new file mode 100644 index 00000000..7d33bc66 --- /dev/null +++ b/src/cdp/cdp.zig @@ -0,0 +1,87 @@ +const std = @import("std"); + +const server = @import("../server.zig"); +const Ctx = server.CmdContext; +const SendFn = server.SendFn; +const browser = @import("browser.zig").browser; +const target = @import("target.zig").target; + +const Domains = enum { + Browser, + Target, +}; + +// The caller is responsible for calling `free` on the returned slice. +pub fn do( + alloc: std.mem.Allocator, + s: []const u8, + ctx: *Ctx, + comptime sendFn: SendFn, +) ![]const u8 { + var scanner = std.json.Scanner.initCompleteInput(alloc, s); + defer scanner.deinit(); + + std.debug.assert(try scanner.next() == .object_begin); + + try checkKey("id", (try scanner.next()).string); + const id = try std.fmt.parseUnsigned(u64, (try scanner.next()).number, 10); + + try checkKey("method", (try scanner.next()).string); + const method = (try scanner.next()).string; + + std.log.debug("cmd: id {any}, method {s}\n", .{ id, method }); + + var iter = std.mem.splitScalar(u8, method, '.'); + const domain = std.meta.stringToEnum(Domains, iter.first()) orelse + return error.UnknonwDomain; + + return switch (domain) { + .Browser => browser(alloc, id, iter.next().?, &scanner, ctx, sendFn), + .Target => target(alloc, id, iter.next().?, &scanner, ctx, sendFn), + }; +} + +// Utils +// ----- + +fn checkKey(key: []const u8, token: []const u8) !void { + if (!std.mem.eql(u8, key, token)) return error.WrongToken; +} + +const resultNull = "{{\"id\": {d}, \"result\": {{}}}}"; + +pub fn result( + alloc: std.mem.Allocator, + id: u64, + comptime T: ?type, + res: anytype, +) ![]const u8 { + if (T == null) return try std.fmt.allocPrint(alloc, resultNull, .{id}); + + const Resp = struct { + id: u64, + result: T.?, + }; + const resp = Resp{ .id = id, .result = res }; + + var out = std.ArrayList(u8).init(alloc); + defer out.deinit(); + + try std.json.stringify(resp, .{}, out.writer()); + const ret = try alloc.alloc(u8, out.items.len); + @memcpy(ret, out.items); + return ret; +} + +pub fn getParams( + alloc: std.mem.Allocator, + comptime T: type, + scanner: *std.json.Scanner, +) !T { + try checkKey("params", (try scanner.next()).string); + const options = std.json.ParseOptions{ + .max_value_len = scanner.input.len, + .allocate = .alloc_if_needed, + }; + return std.json.innerParse(T, alloc, scanner, options); +} diff --git a/src/cdp/target.zig b/src/cdp/target.zig new file mode 100644 index 00000000..cf861086 --- /dev/null +++ b/src/cdp/target.zig @@ -0,0 +1,100 @@ +const std = @import("std"); + +const server = @import("../server.zig"); +const Ctx = server.CmdContext; +const SendFn = server.SendFn; +const result = @import("cdp.zig").result; +const getParams = @import("cdp.zig").getParams; + +const TargetMethods = enum { + setAutoAttach, + // attachedToTarget, + // getTargetInfo, +}; + +pub fn target( + alloc: std.mem.Allocator, + id: u64, + action: []const u8, + scanner: *std.json.Scanner, + ctx: *Ctx, + comptime sendFn: SendFn, +) ![]const u8 { + const method = std.meta.stringToEnum(TargetMethods, action) orelse + return error.UnknownTargetMethod; + return switch (method) { + .setAutoAttach => tagetSetAutoAttach(alloc, id, scanner, ctx, sendFn), + // .getTargetInfo => tagetGetTargetInfo(alloc, id, scanner), + }; +} + +fn tagetSetAutoAttach( + alloc: std.mem.Allocator, + id: u64, + scanner: *std.json.Scanner, + _: *Ctx, + comptime _: SendFn, +) ![]const u8 { + const Params = struct { + autoAttach: bool, + waitForDebuggerOnStart: bool, + flatten: ?bool = null, + }; + const params = try getParams(alloc, Params, scanner); + std.log.debug("params {any}", .{params}); + return result(alloc, id, null, null); +} + +const TargetID = "CFCD6EC01573CF29BB638E9DC0F52DDC"; + +fn tagetGetTargetInfo( + alloc: std.mem.Allocator, + id: u64, + scanner: *std.json.Scanner, + _: *Ctx, + comptime _: SendFn, +) ![]const u8 { + _ = scanner; + + const TargetInfo = struct { + targetId: []const u8, + type: []const u8, + title: []const u8, + url: []const u8, + attached: bool, + canAccessOpener: bool, + + browserContextId: ?[]const u8 = null, + }; + const targetInfo = TargetInfo{ + .targetId = TargetID, + .type = "page", + }; + _ = targetInfo; + return result(alloc, id, null, null); +} + +// fn tagetGetTargetInfo( +// alloc: std.mem.Allocator, +// id: u64, +// scanner: *std.json.Scanner, +// ) ![]const u8 { +// _ = scanner; + +// const TargetInfo = struct { +// targetId: []const u8, +// type: []const u8, +// title: []const u8, +// url: []const u8, +// attached: bool, +// canAccessOpener: bool, + +// browserContextId: ?[]const u8 = null, +// }; +// const targetInfo = TargetInfo{ +// .targetId = TargetID, +// .type = "page", +// }; +// _ = targetInfo; +// return result(alloc, id, null, null); +// } diff --git a/src/main.zig b/src/main.zig index 40b513fe..5575f815 100644 --- a/src/main.zig +++ b/src/main.zig @@ -20,57 +20,16 @@ const std = @import("std"); const jsruntime = @import("jsruntime"); +const server = @import("server.zig"); + const parser = @import("netsurf"); const apiweb = @import("apiweb.zig"); -const Window = @import("html/window.zig").Window; pub const Types = jsruntime.reflect(apiweb.Interfaces); pub const UserContext = apiweb.UserContext; const socket_path = "/tmp/browsercore-server.sock"; -var doc: *parser.DocumentHTML = undefined; -var server: std.net.Server = undefined; - -fn execJS( - alloc: std.mem.Allocator, - js_env: *jsruntime.Env, -) anyerror!void { - // start JS env - try js_env.start(); - defer js_env.stop(); - - // alias global as self and window - var window = Window.create(null); - window.replaceDocument(doc); - try js_env.bindGlobal(window); - - // try catch - var try_catch: jsruntime.TryCatch = undefined; - try_catch.init(js_env.*); - defer try_catch.deinit(); - - while (true) { - - // read cmd - const conn = try server.accept(); - var buf: [100]u8 = undefined; - const read = try conn.stream.read(&buf); - const cmd = buf[0..read]; - std.debug.print("<- {s}\n", .{cmd}); - if (std.mem.eql(u8, cmd, "exit")) { - break; - } - - const res = try js_env.exec(cmd, "cdp"); - const res_str = try res.toString(alloc, js_env.*); - defer alloc.free(res_str); - std.debug.print("-> {s}\n", .{res_str}); - - _ = try conn.stream.write(res_str); - } -} - pub fn main() !void { // create v8 vm @@ -81,18 +40,6 @@ pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); - try parser.init(); - defer parser.deinit(); - - // document - const file = try std.fs.cwd().openFile("test.html", .{}); - defer file.close(); - - doc = try parser.documentHTMLParse(file.reader(), "UTF-8"); - defer parser.documentHTMLClose(doc) catch |err| { - std.debug.print("documentHTMLClose error: {s}\n", .{@errorName(err)}); - }; - // remove socket file of internal server // reuse_address (SO_REUSEADDR flag) does not seems to work on unix socket // see: https://gavv.net/articles/unix-socket-reuse/ @@ -105,9 +52,15 @@ pub fn main() !void { // server const addr = try std.net.Address.initUnix(socket_path); - server = try addr.listen(.{}); - defer server.deinit(); + var srv = std.net.StreamServer.init(.{ + .reuse_address = true, + .reuse_port = true, + .force_nonblocking = true, + }); + defer srv.deinit(); + try srv.listen(addr); std.debug.print("Listening on: {s}...\n", .{socket_path}); + server.socket_fd = srv.sockfd.?; - try jsruntime.loadEnv(&arena, null, execJS); + try jsruntime.loadEnv(&arena, server.execJS); } diff --git a/src/server.zig b/src/server.zig new file mode 100644 index 00000000..47d3abba --- /dev/null +++ b/src/server.zig @@ -0,0 +1,231 @@ +const std = @import("std"); + +const public = @import("jsruntime"); + +const Window = @import("html/window.zig").Window; + +const cdp = @import("cdp/cdp.zig"); +pub var socket_fd: std.os.socket_t = undefined; + +// I/O input command context +pub const CmdContext = struct { + alloc: std.mem.Allocator, + js_env: *public.Env, + socket: std.os.socket_t, + completion: *public.IO.Completion, + buf: []u8, + close: bool = false, + + try_catch: public.TryCatch, + + // cmds: cdp.Cmds, +}; + +fn respCallback( + ctx: *CmdContext, + _: *public.IO.Completion, + result: public.IO.SendError!usize, +) void { + _ = result catch |err| { + ctx.close = true; + std.debug.print("send error: {s}\n", .{@errorName(err)}); + return; + }; + std.log.debug("send ok", .{}); +} + +pub const SendFn = (fn (*CmdContext, []const u8) anyerror!void); + +fn osSend(ctx: *CmdContext, msg: []const u8) !void { + const s = try std.os.write(ctx.socket, msg); + std.log.debug("send ok {d}", .{s}); +} + +fn loopSend(ctx: *CmdContext, msg: []const u8) !void { + ctx.js_env.nat_ctx.loop.io.send( + *CmdContext, + ctx, + respCallback, + ctx.completion, + ctx.socket, + msg, + ); +} + +// I/O input command callback +fn cmdCallback( + ctx: *CmdContext, + completion: *public.IO.Completion, + result: public.IO.RecvError!usize, +) void { + const size = result catch |err| { + ctx.close = true; + std.debug.print("recv error: {s}\n", .{@errorName(err)}); + return; + }; + + const input = ctx.buf[0..size]; + + // close on exit command + if (std.mem.eql(u8, input, "exit")) { + ctx.close = true; + return; + } + + // continue receving messages asynchronously + defer { + ctx.js_env.nat_ctx.loop.io.recv( + *CmdContext, + ctx, + cmdCallback, + completion, + ctx.socket, + ctx.buf, + ); + } + + std.debug.print("input {s}\n", .{input}); + const res = cdp.do(ctx.alloc, input, ctx, osSend) catch |err| { + std.log.debug("error: {any}\n", .{err}); + loopSend(ctx, "{}") catch unreachable; + // TODO: return proper error + return; + }; + defer ctx.alloc.free(res); + std.log.debug("res {s}", .{res}); + + osSend(ctx, res) catch unreachable; + + // ctx.js_env.nat_ctx.loop.io.send( + // *CmdContext, + // ctx, + // respCallback, + // completion, + // ctx.socket, + // res, + // ); + + // JS execute + // const res = ctx.js_env.exec( + // ctx.alloc, + // input, + // "shell.js", + // ctx.try_catch, + // ) catch |err| { + // ctx.close = true; + // std.debug.print("JS exec error: {s}\n", .{@errorName(err)}); + // return; + // }; + // defer res.deinit(ctx.alloc); + + // // JS print result + // if (res.success) { + // if (std.mem.eql(u8, res.result, "undefined")) { + // std.debug.print("<- \x1b[38;5;242m{s}\x1b[0m\n", .{res.result}); + // } else { + // std.debug.print("<- \x1b[33m{s}\x1b[0m\n", .{res.result}); + // } + // } else { + // std.debug.print("{s}\n", .{res.result}); + // } + + // acknowledge to repl result has been printed + // _ = std.os.write(ctx.socket, "ok") catch unreachable; +} + +// I/O connection context +const ConnContext = struct { + socket: std.os.socket_t, + + cmdContext: *CmdContext, +}; + +// I/O connection callback +fn connCallback( + ctx: *ConnContext, + completion: *public.IO.Completion, + result: public.IO.AcceptError!std.os.socket_t, +) void { + ctx.cmdContext.socket = result catch |err| @panic(@errorName(err)); + + // launch receving messages asynchronously + ctx.cmdContext.js_env.nat_ctx.loop.io.recv( + *CmdContext, + ctx.cmdContext, + cmdCallback, + completion, + ctx.cmdContext.socket, + ctx.cmdContext.buf, + ); +} + +pub fn execJS( + alloc: std.mem.Allocator, + js_env: *public.Env, +) anyerror!void { + + // start JS env + try js_env.start(alloc); + defer js_env.stop(); + + // alias global as self + try js_env.attachObject(try js_env.getGlobal(), "self", null); + + // alias global as self and window + const window = Window.create(null); + // window.replaceDocument(doc); TODO + try js_env.bindGlobal(window); + + // add console object + const console = public.Console{}; + try js_env.addObject(console, "console"); + + // JS try cache + var try_catch = public.TryCatch.init(js_env.*); + defer try_catch.deinit(); + + // create I/O contexts and callbacks + // for accepting connections and receving messages + var completion: public.IO.Completion = undefined; + var input: [1024]u8 = undefined; + var cmd_ctx = CmdContext{ + .alloc = alloc, + .js_env = js_env, + .socket = undefined, + .buf = &input, + .try_catch = try_catch, + .completion = &completion, + // .cmds = .{}, + }; + var conn_ctx = ConnContext{ + .socket = socket_fd, + .cmdContext = &cmd_ctx, + }; + + // launch accepting connection asynchronously on internal server + const loop = js_env.nat_ctx.loop; + loop.io.accept( + *ConnContext, + &conn_ctx, + connCallback, + &completion, + socket_fd, + ); + + // infinite loop on I/O events, either: + // - cmd from incoming connection on server socket + // - JS callbacks events from scripts + while (true) { + try loop.io.tick(); + if (loop.cbk_error) { + if (try try_catch.exception(alloc, js_env.*)) |msg| { + std.debug.print("\n\rUncaught {s}\n\r", .{msg}); + alloc.free(msg); + } + loop.cbk_error = false; + } + if (cmd_ctx.close) { + break; + } + } +} From babac692d58f6600947e6ad49d5a2c7d6c0879f8 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Mon, 15 Apr 2024 15:47:19 +0200 Subject: [PATCH 002/117] Remove alloc from CmdContext struct Signed-off-by: Francis Bouvier --- src/server.zig | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/server.zig b/src/server.zig index 47d3abba..74cfce16 100644 --- a/src/server.zig +++ b/src/server.zig @@ -9,7 +9,6 @@ pub var socket_fd: std.os.socket_t = undefined; // I/O input command context pub const CmdContext = struct { - alloc: std.mem.Allocator, js_env: *public.Env, socket: std.os.socket_t, completion: *public.IO.Completion, @@ -18,7 +17,10 @@ pub const CmdContext = struct { try_catch: public.TryCatch, - // cmds: cdp.Cmds, + // shortcuts + fn alloc(self: *CmdContext) std.mem.Allocator { + return self.js_env.nat_ctx.alloc; + } }; fn respCallback( @@ -34,6 +36,11 @@ fn respCallback( std.log.debug("send ok", .{}); } +pub fn send(ctx: *CmdContext, msg: []const u8) !void { + defer ctx.alloc().free(msg); + return osSend(ctx, msg); +} + pub const SendFn = (fn (*CmdContext, []const u8) anyerror!void); fn osSend(ctx: *CmdContext, msg: []const u8) !void { @@ -85,13 +92,12 @@ fn cmdCallback( } std.debug.print("input {s}\n", .{input}); - const res = cdp.do(ctx.alloc, input, ctx, osSend) catch |err| { + const res = cdp.do(ctx.alloc(), input, ctx, osSend) catch |err| { std.log.debug("error: {any}\n", .{err}); loopSend(ctx, "{}") catch unreachable; // TODO: return proper error return; }; - defer ctx.alloc.free(res); std.log.debug("res {s}", .{res}); osSend(ctx, res) catch unreachable; @@ -189,7 +195,6 @@ pub fn execJS( var completion: public.IO.Completion = undefined; var input: [1024]u8 = undefined; var cmd_ctx = CmdContext{ - .alloc = alloc, .js_env = js_env, .socket = undefined, .buf = &input, From defab0c774690abb4e9e31506def593c6c60b878 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Mon, 15 Apr 2024 15:52:13 +0200 Subject: [PATCH 003/117] Free msg at the right place Signed-off-by: Francis Bouvier --- src/server.zig | 47 +++++++---------------------------------------- 1 file changed, 7 insertions(+), 40 deletions(-) diff --git a/src/server.zig b/src/server.zig index 74cfce16..54f1d695 100644 --- a/src/server.zig +++ b/src/server.zig @@ -44,18 +44,22 @@ pub fn send(ctx: *CmdContext, msg: []const u8) !void { pub const SendFn = (fn (*CmdContext, []const u8) anyerror!void); fn osSend(ctx: *CmdContext, msg: []const u8) !void { + defer ctx.alloc().free(msg); const s = try std.os.write(ctx.socket, msg); std.log.debug("send ok {d}", .{s}); } fn loopSend(ctx: *CmdContext, msg: []const u8) !void { + ctx.buf = ctx.buf[0..msg.len]; + @memcpy(ctx.buf, msg); + ctx.alloc().free(msg); ctx.js_env.nat_ctx.loop.io.send( *CmdContext, ctx, respCallback, ctx.completion, ctx.socket, - msg, + ctx.buf, ); } @@ -92,7 +96,7 @@ fn cmdCallback( } std.debug.print("input {s}\n", .{input}); - const res = cdp.do(ctx.alloc(), input, ctx, osSend) catch |err| { + const res = cdp.do(ctx.alloc(), input, ctx, loopSend) catch |err| { std.log.debug("error: {any}\n", .{err}); loopSend(ctx, "{}") catch unreachable; // TODO: return proper error @@ -100,43 +104,7 @@ fn cmdCallback( }; std.log.debug("res {s}", .{res}); - osSend(ctx, res) catch unreachable; - - // ctx.js_env.nat_ctx.loop.io.send( - // *CmdContext, - // ctx, - // respCallback, - // completion, - // ctx.socket, - // res, - // ); - - // JS execute - // const res = ctx.js_env.exec( - // ctx.alloc, - // input, - // "shell.js", - // ctx.try_catch, - // ) catch |err| { - // ctx.close = true; - // std.debug.print("JS exec error: {s}\n", .{@errorName(err)}); - // return; - // }; - // defer res.deinit(ctx.alloc); - - // // JS print result - // if (res.success) { - // if (std.mem.eql(u8, res.result, "undefined")) { - // std.debug.print("<- \x1b[38;5;242m{s}\x1b[0m\n", .{res.result}); - // } else { - // std.debug.print("<- \x1b[33m{s}\x1b[0m\n", .{res.result}); - // } - // } else { - // std.debug.print("{s}\n", .{res.result}); - // } - - // acknowledge to repl result has been printed - // _ = std.os.write(ctx.socket, "ok") catch unreachable; + loopSend(ctx, res) catch unreachable; } // I/O connection context @@ -200,7 +168,6 @@ pub fn execJS( .buf = &input, .try_catch = try_catch, .completion = &completion, - // .cmds = .{}, }; var conn_ctx = ConnContext{ .socket = socket_fd, From cfd6fc9532444f29d5df14d93943f226434b6fac Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Mon, 15 Apr 2024 17:36:22 +0200 Subject: [PATCH 004/117] Working sendLater (I/O timeout) Signed-off-by: Francis Bouvier --- src/cdp/browser.zig | 7 ++-- src/cdp/cdp.zig | 7 ++-- src/cdp/target.zig | 5 +-- src/server.zig | 87 +++++++++++++++++++++++++++++---------------- 4 files changed, 62 insertions(+), 44 deletions(-) diff --git a/src/cdp/browser.zig b/src/cdp/browser.zig index ff20c329..b3f04c21 100644 --- a/src/cdp/browser.zig +++ b/src/cdp/browser.zig @@ -17,13 +17,12 @@ pub fn browser( action: []const u8, scanner: *std.json.Scanner, ctx: *Ctx, - comptime sendFn: SendFn, ) ![]const u8 { const method = std.meta.stringToEnum(BrowserMethods, action) orelse return error.UnknownBrowserMethod; return switch (method) { - .getVersion => browserGetVersion(alloc, id, scanner, ctx, sendFn), - .setDownloadBehavior => browserSetDownloadBehavior(alloc, id, scanner, ctx, sendFn), + .getVersion => browserGetVersion(alloc, id, scanner, ctx), + .setDownloadBehavior => browserSetDownloadBehavior(alloc, id, scanner, ctx), }; } @@ -38,7 +37,6 @@ fn browserGetVersion( id: u64, _: *std.json.Scanner, _: *Ctx, - comptime _: SendFn, ) ![]const u8 { const Res = struct { protocolVersion: []const u8, @@ -63,7 +61,6 @@ fn browserSetDownloadBehavior( id: u64, scanner: *std.json.Scanner, _: *Ctx, - comptime _: SendFn, ) ![]const u8 { const Params = struct { behavior: []const u8, diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 7d33bc66..dcfb4f9f 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -16,7 +16,6 @@ pub fn do( alloc: std.mem.Allocator, s: []const u8, ctx: *Ctx, - comptime sendFn: SendFn, ) ![]const u8 { var scanner = std.json.Scanner.initCompleteInput(alloc, s); defer scanner.deinit(); @@ -29,15 +28,15 @@ pub fn do( try checkKey("method", (try scanner.next()).string); const method = (try scanner.next()).string; - std.log.debug("cmd: id {any}, method {s}\n", .{ id, method }); + std.log.debug("cmd: id {any}, method {s}", .{ id, method }); var iter = std.mem.splitScalar(u8, method, '.'); const domain = std.meta.stringToEnum(Domains, iter.first()) orelse return error.UnknonwDomain; return switch (domain) { - .Browser => browser(alloc, id, iter.next().?, &scanner, ctx, sendFn), - .Target => target(alloc, id, iter.next().?, &scanner, ctx, sendFn), + .Browser => browser(alloc, id, iter.next().?, &scanner, ctx), + .Target => target(alloc, id, iter.next().?, &scanner, ctx), }; } diff --git a/src/cdp/target.zig b/src/cdp/target.zig index cf861086..bca669cb 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -18,12 +18,11 @@ pub fn target( action: []const u8, scanner: *std.json.Scanner, ctx: *Ctx, - comptime sendFn: SendFn, ) ![]const u8 { const method = std.meta.stringToEnum(TargetMethods, action) orelse return error.UnknownTargetMethod; return switch (method) { - .setAutoAttach => tagetSetAutoAttach(alloc, id, scanner, ctx, sendFn), + .setAutoAttach => tagetSetAutoAttach(alloc, id, scanner, ctx), // .getTargetInfo => tagetGetTargetInfo(alloc, id, scanner), }; } @@ -33,7 +32,6 @@ fn tagetSetAutoAttach( id: u64, scanner: *std.json.Scanner, _: *Ctx, - comptime _: SendFn, ) ![]const u8 { const Params = struct { autoAttach: bool, @@ -52,7 +50,6 @@ fn tagetGetTargetInfo( id: u64, scanner: *std.json.Scanner, _: *Ctx, - comptime _: SendFn, ) ![]const u8 { _ = scanner; diff --git a/src/server.zig b/src/server.zig index 54f1d695..3f4d1443 100644 --- a/src/server.zig +++ b/src/server.zig @@ -12,7 +12,10 @@ pub const CmdContext = struct { js_env: *public.Env, socket: std.os.socket_t, completion: *public.IO.Completion, - buf: []u8, + + read_buf: []u8, + write_buf: []const u8 = undefined, + close: bool = false, try_catch: public.TryCatch, @@ -21,6 +24,10 @@ pub const CmdContext = struct { fn alloc(self: *CmdContext) std.mem.Allocator { return self.js_env.nat_ctx.alloc; } + + fn loop(self: *CmdContext) *public.Loop { + return self.js_env.nat_ctx.loop; + } }; fn respCallback( @@ -36,30 +43,48 @@ fn respCallback( std.log.debug("send ok", .{}); } -pub fn send(ctx: *CmdContext, msg: []const u8) !void { - defer ctx.alloc().free(msg); - return osSend(ctx, msg); +fn timeoutCallback( + ctx: *CmdContext, + completion: *public.IO.Completion, + result: public.IO.TimeoutError!void, +) void { + std.log.debug("sending after", .{}); + _ = result catch |err| { + ctx.close = true; + std.debug.print("timeout error: {s}\n", .{@errorName(err)}); + return; + }; + + ctx.alloc().destroy(completion); + send(ctx, ctx.write_buf) catch unreachable; } -pub const SendFn = (fn (*CmdContext, []const u8) anyerror!void); +pub fn sendLater(ctx: *CmdContext, msg: []const u8) !void { + ctx.write_buf = msg; + // NOTE: it seems we can't use the same completion for concurrent + // recv and timeout operations + // TODO: maybe instead of allocating this each time we can create + // a timeout_completion on the context? + // Not sure if there is several concurrent timeout operations on the same context + const completion = try ctx.alloc().create(public.IO.Completion); + ctx.loop().io.timeout(*CmdContext, ctx, timeoutCallback, completion, 1000); +} -fn osSend(ctx: *CmdContext, msg: []const u8) !void { +fn send(ctx: *CmdContext, msg: []const u8) !void { defer ctx.alloc().free(msg); const s = try std.os.write(ctx.socket, msg); std.log.debug("send ok {d}", .{s}); } fn loopSend(ctx: *CmdContext, msg: []const u8) !void { - ctx.buf = ctx.buf[0..msg.len]; - @memcpy(ctx.buf, msg); - ctx.alloc().free(msg); - ctx.js_env.nat_ctx.loop.io.send( + ctx.write_buf = msg; + ctx.loop().io.send( *CmdContext, ctx, respCallback, ctx.completion, ctx.socket, - ctx.buf, + ctx.write_buf, ); } @@ -69,13 +94,14 @@ fn cmdCallback( completion: *public.IO.Completion, result: public.IO.RecvError!usize, ) void { + // ctx.completion = completion; const size = result catch |err| { ctx.close = true; std.debug.print("recv error: {s}\n", .{@errorName(err)}); return; }; - const input = ctx.buf[0..size]; + const input = ctx.read_buf[0..size]; // close on exit command if (std.mem.eql(u8, input, "exit")) { @@ -83,28 +109,27 @@ fn cmdCallback( return; } - // continue receving messages asynchronously - defer { - ctx.js_env.nat_ctx.loop.io.recv( - *CmdContext, - ctx, - cmdCallback, - completion, - ctx.socket, - ctx.buf, - ); - } - - std.debug.print("input {s}\n", .{input}); - const res = cdp.do(ctx.alloc(), input, ctx, loopSend) catch |err| { + std.debug.print("\ninput {s}\n", .{input}); + const res = cdp.do(ctx.alloc(), input, ctx) catch |err| { std.log.debug("error: {any}\n", .{err}); - loopSend(ctx, "{}") catch unreachable; + send(ctx, "{}") catch unreachable; // TODO: return proper error return; }; std.log.debug("res {s}", .{res}); - loopSend(ctx, res) catch unreachable; + sendLater(ctx, res) catch unreachable; + std.log.debug("finish", .{}); + + // continue receving messages asynchronously + ctx.loop().io.recv( + *CmdContext, + ctx, + cmdCallback, + completion, + ctx.socket, + ctx.read_buf, + ); } // I/O connection context @@ -123,13 +148,13 @@ fn connCallback( ctx.cmdContext.socket = result catch |err| @panic(@errorName(err)); // launch receving messages asynchronously - ctx.cmdContext.js_env.nat_ctx.loop.io.recv( + ctx.cmdContext.loop().io.recv( *CmdContext, ctx.cmdContext, cmdCallback, completion, ctx.cmdContext.socket, - ctx.cmdContext.buf, + ctx.cmdContext.read_buf, ); } @@ -165,7 +190,7 @@ pub fn execJS( var cmd_ctx = CmdContext{ .js_env = js_env, .socket = undefined, - .buf = &input, + .read_buf = &input, .try_catch = try_catch, .completion = &completion, }; From 95a64b7696072d2704916119214c422398700784 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Mon, 15 Apr 2024 17:57:33 +0200 Subject: [PATCH 005/117] Handle concurrent calls to sendLater Signed-off-by: Francis Bouvier --- src/server.zig | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/server.zig b/src/server.zig index 3f4d1443..0001d1f9 100644 --- a/src/server.zig +++ b/src/server.zig @@ -43,31 +43,42 @@ fn respCallback( std.log.debug("send ok", .{}); } -fn timeoutCallback( - ctx: *CmdContext, +const SendLaterContext = struct { + cmd_ctx: *CmdContext, + completion: *public.IO.Completion, + buf: []const u8, +}; + +fn sendLaterCallback( + ctx: *SendLaterContext, completion: *public.IO.Completion, result: public.IO.TimeoutError!void, ) void { std.log.debug("sending after", .{}); _ = result catch |err| { - ctx.close = true; + ctx.cmd_ctx.close = true; std.debug.print("timeout error: {s}\n", .{@errorName(err)}); return; }; - ctx.alloc().destroy(completion); - send(ctx, ctx.write_buf) catch unreachable; + ctx.cmd_ctx.alloc().destroy(completion); + defer ctx.cmd_ctx.alloc().destroy(ctx); + send(ctx.cmd_ctx, ctx.buf) catch unreachable; } pub fn sendLater(ctx: *CmdContext, msg: []const u8) !void { - ctx.write_buf = msg; // NOTE: it seems we can't use the same completion for concurrent - // recv and timeout operations - // TODO: maybe instead of allocating this each time we can create - // a timeout_completion on the context? - // Not sure if there is several concurrent timeout operations on the same context + // recv and timeout operations, that's why we create a new completion here const completion = try ctx.alloc().create(public.IO.Completion); - ctx.loop().io.timeout(*CmdContext, ctx, timeoutCallback, completion, 1000); + // NOTE: to handle concurrent calls to sendLater we create each time a new context + // If no concurrent calls are required we could just use the main CmdContext + const sendLaterCtx = try ctx.alloc().create(SendLaterContext); + sendLaterCtx.* = .{ + .cmd_ctx = ctx, + .completion = completion, + .buf = msg, + }; + ctx.loop().io.timeout(*SendLaterContext, sendLaterCtx, sendLaterCallback, completion, 1000); } fn send(ctx: *CmdContext, msg: []const u8) !void { From e908cb0ec4d5c416448b70c6feea95fbc0e00f30 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Mon, 15 Apr 2024 21:32:14 +0200 Subject: [PATCH 006/117] Use send as normal behavior in cmdCallback + add nanoseconds param in sendLater Signed-off-by: Francis Bouvier --- src/server.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server.zig b/src/server.zig index 0001d1f9..c9cae278 100644 --- a/src/server.zig +++ b/src/server.zig @@ -66,7 +66,7 @@ fn sendLaterCallback( send(ctx.cmd_ctx, ctx.buf) catch unreachable; } -pub fn sendLater(ctx: *CmdContext, msg: []const u8) !void { +pub fn sendLater(ctx: *CmdContext, msg: []const u8, nanoseconds: u63) !void { // NOTE: it seems we can't use the same completion for concurrent // recv and timeout operations, that's why we create a new completion here const completion = try ctx.alloc().create(public.IO.Completion); @@ -78,7 +78,7 @@ pub fn sendLater(ctx: *CmdContext, msg: []const u8) !void { .completion = completion, .buf = msg, }; - ctx.loop().io.timeout(*SendLaterContext, sendLaterCtx, sendLaterCallback, completion, 1000); + ctx.loop().io.timeout(*SendLaterContext, sendLaterCtx, sendLaterCallback, completion, nanoseconds); } fn send(ctx: *CmdContext, msg: []const u8) !void { @@ -129,7 +129,7 @@ fn cmdCallback( }; std.log.debug("res {s}", .{res}); - sendLater(ctx, res) catch unreachable; + send(ctx, res) catch unreachable; std.log.debug("finish", .{}); // continue receving messages asynchronously From ffbfd36502b51a3c169c4a8a480dca758faa32d0 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Mon, 15 Apr 2024 21:33:32 +0200 Subject: [PATCH 007/117] Add stringify function in cdp Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index dcfb4f9f..78ca092e 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -49,6 +49,18 @@ fn checkKey(key: []const u8, token: []const u8) !void { const resultNull = "{{\"id\": {d}, \"result\": {{}}}}"; +// caller owns the slice returned +pub fn stringify(alloc: std.mem.Allocator, res: anytype) ![]const u8 { + var out = std.ArrayList(u8).init(alloc); + defer out.deinit(); + + try std.json.stringify(res, .{}, out.writer()); + const ret = try alloc.alloc(u8, out.items.len); + @memcpy(ret, out.items); + return ret; +} + +// caller owns the slice returned pub fn result( alloc: std.mem.Allocator, id: u64, @@ -63,13 +75,7 @@ pub fn result( }; const resp = Resp{ .id = id, .result = res }; - var out = std.ArrayList(u8).init(alloc); - defer out.deinit(); - - try std.json.stringify(resp, .{}, out.writer()); - const ret = try alloc.alloc(u8, out.items.len); - @memcpy(ret, out.items); - return ret; + return stringify(alloc, resp); } pub fn getParams( From 5e1fe656e805d8f5d43031e007637cd00024f57a Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Mon, 15 Apr 2024 21:34:00 +0200 Subject: [PATCH 008/117] send Target.attachedToTarget after Target.setAutoAttach Signed-off-by: Francis Bouvier --- src/cdp/target.zig | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/cdp/target.zig b/src/cdp/target.zig index bca669cb..716e190f 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -5,6 +5,7 @@ const Ctx = server.CmdContext; const SendFn = server.SendFn; const result = @import("cdp.zig").result; const getParams = @import("cdp.zig").getParams; +const stringify = @import("cdp.zig").stringify; const TargetMethods = enum { setAutoAttach, @@ -27,11 +28,15 @@ pub fn target( }; } +const SessionID = "9559320D92474062597D9875C664CAC0"; +const TargetID = "CFCD6EC01573CF29BB638E9DC0F52DDC"; +const BrowserContextID = "65618675CB7D3585A95049E9DFE95EA9"; + fn tagetSetAutoAttach( alloc: std.mem.Allocator, id: u64, scanner: *std.json.Scanner, - _: *Ctx, + ctx: *Ctx, ) ![]const u8 { const Params = struct { autoAttach: bool, @@ -40,11 +45,29 @@ fn tagetSetAutoAttach( }; const params = try getParams(alloc, Params, scanner); std.log.debug("params {any}", .{params}); + + const AttachToTarget = struct { + method: []const u8 = "Target.attachedToTarget", + params: struct { + sessionId: []const u8 = SessionID, + targetInfo: struct { + targetId: []const u8 = TargetID, + type: []const u8 = "page", + title: []const u8 = "New Incognito tab", + url: []const u8 = "chrome://newtab/", + attached: bool = true, + canAccessOpener: bool = false, + browserContextId: []const u8 = BrowserContextID, + } = .{}, + waitingForDebugger: bool = false, + } = .{}, + }; + const attached = try stringify(alloc, AttachToTarget{}); + try server.sendLater(ctx, attached, 0); + return result(alloc, id, null, null); } -const TargetID = "CFCD6EC01573CF29BB638E9DC0F52DDC"; - fn tagetGetTargetInfo( alloc: std.mem.Allocator, id: u64, From 980571073d329a593fa8b0ea2a7e9368f5cf6853 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 16 Apr 2024 00:38:06 +0200 Subject: [PATCH 009/117] Big refacto Signed-off-by: Francis Bouvier --- src/cdp/browser.zig | 5 +- src/cdp/cdp.zig | 19 ++- src/cdp/target.zig | 35 +----- src/server.zig | 293 +++++++++++++++++++++----------------------- 4 files changed, 160 insertions(+), 192 deletions(-) diff --git a/src/cdp/browser.zig b/src/cdp/browser.zig index b3f04c21..b7071927 100644 --- a/src/cdp/browser.zig +++ b/src/cdp/browser.zig @@ -1,8 +1,7 @@ const std = @import("std"); const server = @import("../server.zig"); -const Ctx = server.CmdContext; -const SendFn = server.SendFn; +const Ctx = server.Cmd; const result = @import("cdp.zig").result; const getParams = @import("cdp.zig").getParams; @@ -19,7 +18,7 @@ pub fn browser( ctx: *Ctx, ) ![]const u8 { const method = std.meta.stringToEnum(BrowserMethods, action) orelse - return error.UnknownBrowserMethod; + return error.UnknownMethod; return switch (method) { .getVersion => browserGetVersion(alloc, id, scanner, ctx), .setDownloadBehavior => browserSetDownloadBehavior(alloc, id, scanner, ctx), diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 78ca092e..5f340407 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -1,11 +1,26 @@ const std = @import("std"); const server = @import("../server.zig"); -const Ctx = server.CmdContext; -const SendFn = server.SendFn; +const Ctx = server.Cmd; const browser = @import("browser.zig").browser; const target = @import("target.zig").target; +pub const Error = error{ + UnknonwDomain, + UnknownMethod, +}; + +pub fn isCdpError(err: anyerror) ?Error { + // see https://github.com/ziglang/zig/issues/2473 + const errors = @typeInfo(Error).ErrorSet.?; + inline for (errors) |e| { + if (std.mem.eql(u8, e.name, @errorName(err))) { + return @errorCast(err); + } + } + return null; +} + const Domains = enum { Browser, Target, diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 716e190f..76776f95 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -1,15 +1,13 @@ const std = @import("std"); const server = @import("../server.zig"); -const Ctx = server.CmdContext; -const SendFn = server.SendFn; +const Ctx = server.Cmd; const result = @import("cdp.zig").result; const getParams = @import("cdp.zig").getParams; const stringify = @import("cdp.zig").stringify; const TargetMethods = enum { setAutoAttach, - // attachedToTarget, // getTargetInfo, }; @@ -21,10 +19,10 @@ pub fn target( ctx: *Ctx, ) ![]const u8 { const method = std.meta.stringToEnum(TargetMethods, action) orelse - return error.UnknownTargetMethod; + return error.UnknownMethod; return switch (method) { .setAutoAttach => tagetSetAutoAttach(alloc, id, scanner, ctx), - // .getTargetInfo => tagetGetTargetInfo(alloc, id, scanner), + // .getTargetInfo => tagetGetTargetInfo(alloc, id, scanner, ctx), }; } @@ -63,7 +61,7 @@ fn tagetSetAutoAttach( } = .{}, }; const attached = try stringify(alloc, AttachToTarget{}); - try server.sendLater(ctx, attached, 0); + try server.sendSync(ctx, attached); return result(alloc, id, null, null); } @@ -93,28 +91,3 @@ fn tagetGetTargetInfo( _ = targetInfo; return result(alloc, id, null, null); } - -// fn tagetGetTargetInfo( -// alloc: std.mem.Allocator, -// id: u64, -// scanner: *std.json.Scanner, -// ) ![]const u8 { -// _ = scanner; - -// const TargetInfo = struct { -// targetId: []const u8, -// type: []const u8, -// title: []const u8, -// url: []const u8, -// attached: bool, -// canAccessOpener: bool, - -// browserContextId: ?[]const u8 = null, -// }; -// const targetInfo = TargetInfo{ -// .targetId = TargetID, -// .type = "page", -// }; -// _ = targetInfo; -// return result(alloc, id, null, null); -// } diff --git a/src/server.zig b/src/server.zig index c9cae278..1ee9f953 100644 --- a/src/server.zig +++ b/src/server.zig @@ -1,178 +1,165 @@ const std = @import("std"); const public = @import("jsruntime"); +const Completion = public.IO.Completion; +const AcceptError = public.IO.AcceptError; +const RecvError = public.IO.RecvError; +const SendError = public.IO.SendError; +const TimeoutError = public.IO.TimeoutError; const Window = @import("html/window.zig").Window; const cdp = @import("cdp/cdp.zig"); pub var socket_fd: std.os.socket_t = undefined; -// I/O input command context -pub const CmdContext = struct { - js_env: *public.Env, +const NoError = error{NoError}; +const Error = AcceptError || RecvError || SendError || TimeoutError || cdp.Error || NoError; + +// I/O Recv +// -------- + +pub const Cmd = struct { + + // internal fields socket: std.os.socket_t, - completion: *public.IO.Completion, - - read_buf: []u8, - write_buf: []const u8 = undefined, - - close: bool = false, + buf: []u8, // only for read operations + err: ?Error = null, + // JS fields + js_env: *public.Env, try_catch: public.TryCatch, + fn cbk(self: *Cmd, completion: *Completion, result: RecvError!usize) void { + const size = result catch |err| { + self.err = err; + return; + }; + + const input = self.buf[0..size]; + + // close on exit command + if (std.mem.eql(u8, input, "exit")) { + self.err = error.NoError; + return; + } + + // input + if (std.log.defaultLogEnabled(.debug)) { + std.debug.print("\ninput {s}\n", .{input}); + } + + // cdp + const res = cdp.do(self.alloc(), input, self) catch |err| { + if (cdp.isCdpError(err)) |e| { + self.err = e; + return; + } + @panic(@errorName(err)); + }; + std.log.debug("res {s}", .{res}); + + sendAsync(self, res) catch unreachable; + + // continue receving incomming messages asynchronously + self.loop().io.recv(*Cmd, self, cbk, completion, self.socket, self.buf); + } + // shortcuts - fn alloc(self: *CmdContext) std.mem.Allocator { + fn alloc(self: *Cmd) std.mem.Allocator { return self.js_env.nat_ctx.alloc; } - fn loop(self: *CmdContext) *public.Loop { + fn loop(self: *Cmd) *public.Loop { return self.js_env.nat_ctx.loop; } }; -fn respCallback( - ctx: *CmdContext, - _: *public.IO.Completion, - result: public.IO.SendError!usize, -) void { - _ = result catch |err| { - ctx.close = true; - std.debug.print("send error: {s}\n", .{@errorName(err)}); - return; - }; - std.log.debug("send ok", .{}); -} +// I/O Send +// -------- -const SendLaterContext = struct { - cmd_ctx: *CmdContext, - completion: *public.IO.Completion, +const Send = struct { + cmd: *Cmd, buf: []const u8, -}; -fn sendLaterCallback( - ctx: *SendLaterContext, - completion: *public.IO.Completion, - result: public.IO.TimeoutError!void, -) void { - std.log.debug("sending after", .{}); - _ = result catch |err| { - ctx.cmd_ctx.close = true; - std.debug.print("timeout error: {s}\n", .{@errorName(err)}); - return; - }; - - ctx.cmd_ctx.alloc().destroy(completion); - defer ctx.cmd_ctx.alloc().destroy(ctx); - send(ctx.cmd_ctx, ctx.buf) catch unreachable; -} - -pub fn sendLater(ctx: *CmdContext, msg: []const u8, nanoseconds: u63) !void { - // NOTE: it seems we can't use the same completion for concurrent - // recv and timeout operations, that's why we create a new completion here - const completion = try ctx.alloc().create(public.IO.Completion); - // NOTE: to handle concurrent calls to sendLater we create each time a new context - // If no concurrent calls are required we could just use the main CmdContext - const sendLaterCtx = try ctx.alloc().create(SendLaterContext); - sendLaterCtx.* = .{ - .cmd_ctx = ctx, - .completion = completion, - .buf = msg, - }; - ctx.loop().io.timeout(*SendLaterContext, sendLaterCtx, sendLaterCallback, completion, nanoseconds); -} - -fn send(ctx: *CmdContext, msg: []const u8) !void { - defer ctx.alloc().free(msg); - const s = try std.os.write(ctx.socket, msg); - std.log.debug("send ok {d}", .{s}); -} - -fn loopSend(ctx: *CmdContext, msg: []const u8) !void { - ctx.write_buf = msg; - ctx.loop().io.send( - *CmdContext, - ctx, - respCallback, - ctx.completion, - ctx.socket, - ctx.write_buf, - ); -} - -// I/O input command callback -fn cmdCallback( - ctx: *CmdContext, - completion: *public.IO.Completion, - result: public.IO.RecvError!usize, -) void { - // ctx.completion = completion; - const size = result catch |err| { - ctx.close = true; - std.debug.print("recv error: {s}\n", .{@errorName(err)}); - return; - }; - - const input = ctx.read_buf[0..size]; - - // close on exit command - if (std.mem.eql(u8, input, "exit")) { - ctx.close = true; - return; + fn init(ctx: *Cmd, msg: []const u8) !struct { + ctx: *Send, + completion: *Completion, + } { + // NOTE: it seems we can't use the same completion for concurrent + // recv and timeout operations, that's why we create a new completion here + const completion = try ctx.alloc().create(Completion); + // NOTE: to handle concurrent calls we create each time a new context + // If no concurrent calls where required we could just use the main CmdCtx + const sd = try ctx.alloc().create(Send); + sd.* = .{ + .cmd = ctx, + .buf = msg, + }; + return .{ .ctx = sd, .completion = completion }; } - std.debug.print("\ninput {s}\n", .{input}); - const res = cdp.do(ctx.alloc(), input, ctx) catch |err| { - std.log.debug("error: {any}\n", .{err}); - send(ctx, "{}") catch unreachable; - // TODO: return proper error - return; - }; - std.log.debug("res {s}", .{res}); + fn deinit(self: *Send, completion: *Completion) void { + self.cmd.alloc().destroy(completion); + self.cmd.alloc().free(self.buf); + self.cmd.alloc().destroy(self); + } - send(ctx, res) catch unreachable; - std.log.debug("finish", .{}); + fn laterCbk(self: *Send, completion: *Completion, result: TimeoutError!void) void { + std.log.debug("sending after", .{}); + _ = result catch |err| { + self.cmd.err = err; + return; + }; - // continue receving messages asynchronously - ctx.loop().io.recv( - *CmdContext, - ctx, - cmdCallback, - completion, - ctx.socket, - ctx.read_buf, - ); -} + self.cmd.loop().io.send(*Send, self, Send.asyncCbk, completion, self.cmd.socket, self.buf); + } -// I/O connection context -const ConnContext = struct { - socket: std.os.socket_t, + fn asyncCbk(self: *Send, completion: *Completion, result: SendError!usize) void { + const size = result catch |err| { + self.cmd.err = err; + return; + }; - cmdContext: *CmdContext, + std.log.debug("send async {d} bytes", .{size}); + self.deinit(completion); + } }; -// I/O connection callback -fn connCallback( - ctx: *ConnContext, - completion: *public.IO.Completion, - result: public.IO.AcceptError!std.os.socket_t, -) void { - ctx.cmdContext.socket = result catch |err| @panic(@errorName(err)); - - // launch receving messages asynchronously - ctx.cmdContext.loop().io.recv( - *CmdContext, - ctx.cmdContext, - cmdCallback, - completion, - ctx.cmdContext.socket, - ctx.cmdContext.read_buf, - ); +pub fn sendLater(ctx: *Cmd, msg: []const u8, ns: u63) !void { + const sd = try Send.init(ctx, msg); + ctx.loop().io.timeout(*Send, sd.ctx, Send.laterCbk, sd.completion, ns); } -pub fn execJS( - alloc: std.mem.Allocator, - js_env: *public.Env, -) anyerror!void { +pub fn sendAsync(ctx: *Cmd, msg: []const u8) !void { + const sd = try Send.init(ctx, msg); + ctx.loop().io.send(*Send, sd.ctx, Send.asyncCbk, sd.completion, ctx.socket, msg); +} + +pub fn sendSync(ctx: *Cmd, msg: []const u8) !void { + defer ctx.alloc().free(msg); + const s = try std.os.write(ctx.socket, msg); + std.log.debug("send sync {d} bytes", .{s}); +} + +// I/O Accept +// ---------- + +const Accept = struct { + cmd: *Cmd, + socket: std.os.socket_t, + + fn cbk(self: *Accept, completion: *Completion, result: AcceptError!std.os.socket_t) void { + self.cmd.socket = result catch |err| { + self.cmd.err = err; + return; + }; + + // receving incomming messages asynchronously + self.cmd.loop().io.recv(*Cmd, self.cmd, Cmd.cbk, completion, self.cmd.socket, self.cmd.buf); + } +}; + +pub fn execJS(alloc: std.mem.Allocator, js_env: *public.Env) anyerror!void { // start JS env try js_env.start(alloc); @@ -196,29 +183,22 @@ pub fn execJS( // create I/O contexts and callbacks // for accepting connections and receving messages - var completion: public.IO.Completion = undefined; var input: [1024]u8 = undefined; - var cmd_ctx = CmdContext{ + var cmd = Cmd{ .js_env = js_env, .socket = undefined, - .read_buf = &input, + .buf = &input, .try_catch = try_catch, - .completion = &completion, }; - var conn_ctx = ConnContext{ + var accept = Accept{ + .cmd = &cmd, .socket = socket_fd, - .cmdContext = &cmd_ctx, }; - // launch accepting connection asynchronously on internal server + // accepting connection asynchronously on internal server const loop = js_env.nat_ctx.loop; - loop.io.accept( - *ConnContext, - &conn_ctx, - connCallback, - &completion, - socket_fd, - ); + var completion: Completion = undefined; + loop.io.accept(*Accept, &accept, Accept.cbk, &completion, socket_fd); // infinite loop on I/O events, either: // - cmd from incoming connection on server socket @@ -232,7 +212,8 @@ pub fn execJS( } loop.cbk_error = false; } - if (cmd_ctx.close) { + if (cmd.err) |err| { + if (err != error.NoError) std.log.err("Server error: {any}", .{err}); break; } } From b1242207a902ee0d50f1dc93f1eadcefca37fe1c Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 16 Apr 2024 00:41:26 +0200 Subject: [PATCH 010/117] Add Page domain Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 3 +++ src/cdp/page.zig | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/cdp/page.zig diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 5f340407..0d479e47 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -4,6 +4,7 @@ const server = @import("../server.zig"); const Ctx = server.Cmd; const browser = @import("browser.zig").browser; const target = @import("target.zig").target; +const page = @import("page.zig").page; pub const Error = error{ UnknonwDomain, @@ -24,6 +25,7 @@ pub fn isCdpError(err: anyerror) ?Error { const Domains = enum { Browser, Target, + Page, }; // The caller is responsible for calling `free` on the returned slice. @@ -52,6 +54,7 @@ pub fn do( return switch (domain) { .Browser => browser(alloc, id, iter.next().?, &scanner, ctx), .Target => target(alloc, id, iter.next().?, &scanner, ctx), + .Page => page(alloc, id, iter.next().?, &scanner, ctx), }; } diff --git a/src/cdp/page.zig b/src/cdp/page.zig new file mode 100644 index 00000000..a22f2dc8 --- /dev/null +++ b/src/cdp/page.zig @@ -0,0 +1,34 @@ +const std = @import("std"); + +const server = @import("../server.zig"); +const Ctx = server.Cmd; +const result = @import("cdp.zig").result; +const getParams = @import("cdp.zig").getParams; +const stringify = @import("cdp.zig").stringify; + +const PageMethods = enum { + enable, +}; + +pub fn page( + alloc: std.mem.Allocator, + id: u64, + action: []const u8, + scanner: *std.json.Scanner, + ctx: *Ctx, +) ![]const u8 { + const method = std.meta.stringToEnum(PageMethods, action) orelse + return error.UnknownMethod; + return switch (method) { + .enable => enable(alloc, id, scanner, ctx), + }; +} + +fn enable( + alloc: std.mem.Allocator, + id: u64, + _: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + return result(alloc, id, null, null); +} From a708a7f3870b716f60fab26133295336c68a6adf Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 16 Apr 2024 00:48:17 +0200 Subject: [PATCH 011/117] Add Page.getFrameTree Signed-off-by: Francis Bouvier --- src/cdp/page.zig | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/cdp/page.zig b/src/cdp/page.zig index a22f2dc8..4df89c43 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -8,6 +8,7 @@ const stringify = @import("cdp.zig").stringify; const PageMethods = enum { enable, + getFrameTree, }; pub fn page( @@ -21,6 +22,7 @@ pub fn page( return error.UnknownMethod; return switch (method) { .enable => enable(alloc, id, scanner, ctx), + .getFrameTree => getFrameTree(alloc, id, scanner, ctx), }; } @@ -32,3 +34,13 @@ fn enable( ) ![]const u8 { return result(alloc, id, null, null); } + +fn getFrameTree( + alloc: std.mem.Allocator, + id: u64, + _: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + // TODO: dummy + return result(alloc, id, null, null); +} From 626fae0da0634ee5cdae19968094e5277e7ea614 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 16 Apr 2024 00:48:40 +0200 Subject: [PATCH 012/117] Add Log domain Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 3 +++ src/cdp/log.zig | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/cdp/log.zig diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 0d479e47..9e34bbee 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -5,6 +5,7 @@ const Ctx = server.Cmd; const browser = @import("browser.zig").browser; const target = @import("target.zig").target; const page = @import("page.zig").page; +const log = @import("log.zig").log; pub const Error = error{ UnknonwDomain, @@ -26,6 +27,7 @@ const Domains = enum { Browser, Target, Page, + Log, }; // The caller is responsible for calling `free` on the returned slice. @@ -55,6 +57,7 @@ pub fn do( .Browser => browser(alloc, id, iter.next().?, &scanner, ctx), .Target => target(alloc, id, iter.next().?, &scanner, ctx), .Page => page(alloc, id, iter.next().?, &scanner, ctx), + .Log => log(alloc, id, iter.next().?, &scanner, ctx), }; } diff --git a/src/cdp/log.zig b/src/cdp/log.zig new file mode 100644 index 00000000..e90693d6 --- /dev/null +++ b/src/cdp/log.zig @@ -0,0 +1,34 @@ +const std = @import("std"); + +const server = @import("../server.zig"); +const Ctx = server.Cmd; +const result = @import("cdp.zig").result; +const getParams = @import("cdp.zig").getParams; +const stringify = @import("cdp.zig").stringify; + +const LogMethods = enum { + enable, +}; + +pub fn log( + alloc: std.mem.Allocator, + id: u64, + action: []const u8, + scanner: *std.json.Scanner, + ctx: *Ctx, +) ![]const u8 { + const method = std.meta.stringToEnum(LogMethods, action) orelse + return error.UnknownMethod; + return switch (method) { + .enable => enable(alloc, id, scanner, ctx), + }; +} + +fn enable( + alloc: std.mem.Allocator, + id: u64, + _: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + return result(alloc, id, null, null); +} From e073e3388d90477f6d84c4f471d8515da16cde86 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 16 Apr 2024 00:50:17 +0200 Subject: [PATCH 013/117] Add Runtime domain Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 4 ++++ src/cdp/runtime.zig | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 src/cdp/runtime.zig diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 9e34bbee..1c07d9d3 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -2,10 +2,12 @@ const std = @import("std"); const server = @import("../server.zig"); const Ctx = server.Cmd; + const browser = @import("browser.zig").browser; const target = @import("target.zig").target; const page = @import("page.zig").page; const log = @import("log.zig").log; +const runtime = @import("runtime.zig").runtime; pub const Error = error{ UnknonwDomain, @@ -28,6 +30,7 @@ const Domains = enum { Target, Page, Log, + Runtime, }; // The caller is responsible for calling `free` on the returned slice. @@ -58,6 +61,7 @@ pub fn do( .Target => target(alloc, id, iter.next().?, &scanner, ctx), .Page => page(alloc, id, iter.next().?, &scanner, ctx), .Log => log(alloc, id, iter.next().?, &scanner, ctx), + .Runtime => runtime(alloc, id, iter.next().?, &scanner, ctx), }; } diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig new file mode 100644 index 00000000..c21fbdf5 --- /dev/null +++ b/src/cdp/runtime.zig @@ -0,0 +1,34 @@ +const std = @import("std"); + +const server = @import("../server.zig"); +const Ctx = server.Cmd; +const result = @import("cdp.zig").result; +const getParams = @import("cdp.zig").getParams; +const stringify = @import("cdp.zig").stringify; + +const RuntimeMethods = enum { + enable, +}; + +pub fn runtime( + alloc: std.mem.Allocator, + id: u64, + action: []const u8, + scanner: *std.json.Scanner, + ctx: *Ctx, +) ![]const u8 { + const method = std.meta.stringToEnum(RuntimeMethods, action) orelse + return error.UnknownMethod; + return switch (method) { + .enable => enable(alloc, id, scanner, ctx), + }; +} + +fn enable( + alloc: std.mem.Allocator, + id: u64, + _: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + return result(alloc, id, null, null); +} From 0a03dcb465d361e54d3b2fdd16b66bf87e4c8fab Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 16 Apr 2024 00:54:40 +0200 Subject: [PATCH 014/117] Add Page.setLifecycleEventsEnabled Signed-off-by: Francis Bouvier --- src/cdp/page.zig | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 4df89c43..1f822afc 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -9,6 +9,7 @@ const stringify = @import("cdp.zig").stringify; const PageMethods = enum { enable, getFrameTree, + setLifecycleEventsEnabled, }; pub fn page( @@ -23,6 +24,7 @@ pub fn page( return switch (method) { .enable => enable(alloc, id, scanner, ctx), .getFrameTree => getFrameTree(alloc, id, scanner, ctx), + .setLifecycleEventsEnabled => setLifecycleEventsEnabled(alloc, id, scanner, ctx), }; } @@ -44,3 +46,13 @@ fn getFrameTree( // TODO: dummy return result(alloc, id, null, null); } + +fn setLifecycleEventsEnabled( + alloc: std.mem.Allocator, + id: u64, + _: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + // TODO: dummy + return result(alloc, id, null, null); +} From 86b1c851c07caf8f710e49f3895bb77d5383009f Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 16 Apr 2024 00:57:25 +0200 Subject: [PATCH 015/117] Add Page.addScriptToEvaluateOnNewDocument Signed-off-by: Francis Bouvier --- src/cdp/page.zig | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 1f822afc..e9ab7e92 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -10,6 +10,7 @@ const PageMethods = enum { enable, getFrameTree, setLifecycleEventsEnabled, + addScriptToEvaluateOnNewDocument, }; pub fn page( @@ -25,6 +26,7 @@ pub fn page( .enable => enable(alloc, id, scanner, ctx), .getFrameTree => getFrameTree(alloc, id, scanner, ctx), .setLifecycleEventsEnabled => setLifecycleEventsEnabled(alloc, id, scanner, ctx), + .addScriptToEvaluateOnNewDocument => addScriptToEvaluateOnNewDocument(alloc, id, scanner, ctx), }; } @@ -56,3 +58,15 @@ fn setLifecycleEventsEnabled( // TODO: dummy return result(alloc, id, null, null); } + +fn addScriptToEvaluateOnNewDocument( + alloc: std.mem.Allocator, + id: u64, + _: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + const Res = struct { + identifier: []const u8 = "1", + }; + return result(alloc, id, Res, .{}); +} From aff2250504e977123e6715cd3ca8e412b497f841 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 16 Apr 2024 01:02:44 +0200 Subject: [PATCH 016/117] Add Emulation domain Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 3 +++ src/cdp/emulation.zig | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/cdp/emulation.zig diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 1c07d9d3..c54b686f 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -8,6 +8,7 @@ const target = @import("target.zig").target; const page = @import("page.zig").page; const log = @import("log.zig").log; const runtime = @import("runtime.zig").runtime; +const emulation = @import("emulation.zig").emulation; pub const Error = error{ UnknonwDomain, @@ -31,6 +32,7 @@ const Domains = enum { Page, Log, Runtime, + Emulation, }; // The caller is responsible for calling `free` on the returned slice. @@ -62,6 +64,7 @@ pub fn do( .Page => page(alloc, id, iter.next().?, &scanner, ctx), .Log => log(alloc, id, iter.next().?, &scanner, ctx), .Runtime => runtime(alloc, id, iter.next().?, &scanner, ctx), + .Emulation => emulation(alloc, id, iter.next().?, &scanner, ctx), }; } diff --git a/src/cdp/emulation.zig b/src/cdp/emulation.zig new file mode 100644 index 00000000..1fd74f69 --- /dev/null +++ b/src/cdp/emulation.zig @@ -0,0 +1,34 @@ +const std = @import("std"); + +const server = @import("../server.zig"); +const Ctx = server.Cmd; +const result = @import("cdp.zig").result; +const getParams = @import("cdp.zig").getParams; +const stringify = @import("cdp.zig").stringify; + +const EmulationMethods = enum { + setEmulatedMedia, +}; + +pub fn emulation( + alloc: std.mem.Allocator, + id: u64, + action: []const u8, + scanner: *std.json.Scanner, + ctx: *Ctx, +) ![]const u8 { + const method = std.meta.stringToEnum(EmulationMethods, action) orelse + return error.UnknownMethod; + return switch (method) { + .setEmulatedMedia => setEmulatedMedia(alloc, id, scanner, ctx), + }; +} + +fn setEmulatedMedia( + alloc: std.mem.Allocator, + id: u64, + _: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + return result(alloc, id, null, null); +} From 67bbd9957dc76dc71fcd15b7c7e807366cae555f Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 16 Apr 2024 01:03:04 +0200 Subject: [PATCH 017/117] Add Network domain Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 3 +++ src/cdp/network.zig | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/cdp/network.zig diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index c54b686f..f9287c1c 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -8,6 +8,7 @@ const target = @import("target.zig").target; const page = @import("page.zig").page; const log = @import("log.zig").log; const runtime = @import("runtime.zig").runtime; +const network = @import("network.zig").network; const emulation = @import("emulation.zig").emulation; pub const Error = error{ @@ -32,6 +33,7 @@ const Domains = enum { Page, Log, Runtime, + Network, Emulation, }; @@ -64,6 +66,7 @@ pub fn do( .Page => page(alloc, id, iter.next().?, &scanner, ctx), .Log => log(alloc, id, iter.next().?, &scanner, ctx), .Runtime => runtime(alloc, id, iter.next().?, &scanner, ctx), + .Network => network(alloc, id, iter.next().?, &scanner, ctx), .Emulation => emulation(alloc, id, iter.next().?, &scanner, ctx), }; } diff --git a/src/cdp/network.zig b/src/cdp/network.zig new file mode 100644 index 00000000..354291be --- /dev/null +++ b/src/cdp/network.zig @@ -0,0 +1,34 @@ +const std = @import("std"); + +const server = @import("../server.zig"); +const Ctx = server.Cmd; +const result = @import("cdp.zig").result; +const getParams = @import("cdp.zig").getParams; +const stringify = @import("cdp.zig").stringify; + +const NetworkMethods = enum { + enable, +}; + +pub fn network( + alloc: std.mem.Allocator, + id: u64, + action: []const u8, + scanner: *std.json.Scanner, + ctx: *Ctx, +) ![]const u8 { + const method = std.meta.stringToEnum(NetworkMethods, action) orelse + return error.UnknownMethod; + return switch (method) { + .enable => enable(alloc, id, scanner, ctx), + }; +} + +fn enable( + alloc: std.mem.Allocator, + id: u64, + _: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + return result(alloc, id, null, null); +} From 211fa3d9470ee919075f10b3c73db6c4c7b0f3db Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 16 Apr 2024 16:38:15 +0200 Subject: [PATCH 018/117] Handle several JSON msg in 1 read Signed-off-by: Francis Bouvier --- src/server.zig | 45 +++++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/server.zig b/src/server.zig index 1ee9f953..79ebba04 100644 --- a/src/server.zig +++ b/src/server.zig @@ -35,7 +35,11 @@ pub const Cmd = struct { return; }; - const input = self.buf[0..size]; + // input + var input = self.buf[0..size]; + if (std.log.defaultLogEnabled(.debug)) { + std.debug.print("\ninput {s}\n", .{input}); + } // close on exit command if (std.mem.eql(u8, input, "exit")) { @@ -43,22 +47,35 @@ pub const Cmd = struct { return; } - // input - if (std.log.defaultLogEnabled(.debug)) { - std.debug.print("\ninput {s}\n", .{input}); - } + // cmds + var cmd: []const u8 = undefined; + while (true) { - // cdp - const res = cdp.do(self.alloc(), input, self) catch |err| { - if (cdp.isCdpError(err)) |e| { - self.err = e; - return; + // handle several JSON msg in 1 read + const pos = std.mem.indexOf(u8, input, "}{"); + if (pos) |p| { + cmd = input[0 .. p + 1]; + input = input[p + 1 ..]; + } else { + cmd = input; } - @panic(@errorName(err)); - }; - std.log.debug("res {s}", .{res}); - sendAsync(self, res) catch unreachable; + // cdp + const res = cdp.do(self.alloc(), cmd, self) catch |err| { + if (cdp.isCdpError(err)) |e| { + self.err = e; + return; + } + @panic(@errorName(err)); + }; + std.log.debug("res {s}", .{res}); + + sendAsync(self, res) catch unreachable; + + if (pos == null) break; + + // TODO: handle 1 read smaller than a complete JSON msg + } // continue receving incomming messages asynchronously self.loop().io.recv(*Cmd, self, cbk, completion, self.socket, self.buf); From 26eda90f7e3504add56ee91642c4d983bd7bdcab Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 16 Apr 2024 16:38:47 +0200 Subject: [PATCH 019/117] Add setFocusEmulationEnabled Signed-off-by: Francis Bouvier --- src/cdp/emulation.zig | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/cdp/emulation.zig b/src/cdp/emulation.zig index 1fd74f69..c5970a93 100644 --- a/src/cdp/emulation.zig +++ b/src/cdp/emulation.zig @@ -8,6 +8,7 @@ const stringify = @import("cdp.zig").stringify; const EmulationMethods = enum { setEmulatedMedia, + setFocusEmulationEnabled, }; pub fn emulation( @@ -21,6 +22,7 @@ pub fn emulation( return error.UnknownMethod; return switch (method) { .setEmulatedMedia => setEmulatedMedia(alloc, id, scanner, ctx), + .setFocusEmulationEnabled => setFocusEmulationEnabled(alloc, id, scanner, ctx), }; } @@ -32,3 +34,12 @@ fn setEmulatedMedia( ) ![]const u8 { return result(alloc, id, null, null); } + +fn setFocusEmulationEnabled( + alloc: std.mem.Allocator, + id: u64, + _: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + return result(alloc, id, null, null); +} From 36dbc28bde1bd33525e3d2d81afe4b2b07c74c8e Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 16 Apr 2024 16:40:50 +0200 Subject: [PATCH 020/117] Add Runtime.runIfWaitingForDebugger Signed-off-by: Francis Bouvier --- src/cdp/runtime.zig | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index c21fbdf5..e2330b39 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -8,6 +8,7 @@ const stringify = @import("cdp.zig").stringify; const RuntimeMethods = enum { enable, + runIfWaitingForDebugger, }; pub fn runtime( @@ -21,6 +22,7 @@ pub fn runtime( return error.UnknownMethod; return switch (method) { .enable => enable(alloc, id, scanner, ctx), + .runIfWaitingForDebugger => runIfWaitingForDebugger(alloc, id, scanner, ctx), }; } @@ -32,3 +34,12 @@ fn enable( ) ![]const u8 { return result(alloc, id, null, null); } + +fn runIfWaitingForDebugger( + alloc: std.mem.Allocator, + id: u64, + _: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + return result(alloc, id, null, null); +} From 9e8b765f7a9153e9c8afc3fbf29e8c26af417649 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 16 Apr 2024 17:19:50 +0200 Subject: [PATCH 021/117] Allow method with sessionId and use it when appropriate (*.enable) Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 30 ++++++++++++++++++++++++++++++ src/cdp/log.zig | 14 +++++++++----- src/cdp/network.zig | 14 +++++++++----- src/cdp/page.zig | 14 +++++++++----- src/cdp/runtime.zig | 14 +++++++++----- 5 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index f9287c1c..8242c796 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -121,3 +121,33 @@ pub fn getParams( }; return std.json.innerParse(T, alloc, scanner, options); } + +pub fn getSessionID( + alloc: std.mem.Allocator, + scanner: *std.json.Scanner, +) ![]const u8 { + var n = (try scanner.next()).string; + if (std.mem.eql(u8, n, "params")) { + // ignore empty params + _ = (try scanner.next()).object_begin; + _ = (try scanner.next()).object_end; + n = (try scanner.next()).string; + } + try checkKey("sessionId", n); + const options = std.json.ParseOptions{ + .max_value_len = scanner.input.len, + .allocate = .alloc_if_needed, + }; + return std.json.innerParse([]const u8, alloc, scanner, options); +} + +// Common +// ------ + +pub const SessionID = "9559320D92474062597D9875C664CAC0"; + +pub const SessionIDResp = struct { + id: u64, + result: struct {} = .{}, + sessionId: []const u8, +}; diff --git a/src/cdp/log.zig b/src/cdp/log.zig index e90693d6..b7c46ce6 100644 --- a/src/cdp/log.zig +++ b/src/cdp/log.zig @@ -2,9 +2,10 @@ const std = @import("std"); const server = @import("../server.zig"); const Ctx = server.Cmd; -const result = @import("cdp.zig").result; -const getParams = @import("cdp.zig").getParams; -const stringify = @import("cdp.zig").stringify; +const cdp = @import("cdp.zig"); +const result = cdp.result; +const getParams = cdp.getParams; +const stringify = cdp.stringify; const LogMethods = enum { enable, @@ -27,8 +28,11 @@ pub fn log( fn enable( alloc: std.mem.Allocator, id: u64, - _: *std.json.Scanner, + scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - return result(alloc, id, null, null); + return stringify(alloc, cdp.SessionIDResp{ + .id = id, + .sessionId = try cdp.getSessionID(alloc, scanner), + }); } diff --git a/src/cdp/network.zig b/src/cdp/network.zig index 354291be..a81bfdd2 100644 --- a/src/cdp/network.zig +++ b/src/cdp/network.zig @@ -2,9 +2,10 @@ const std = @import("std"); const server = @import("../server.zig"); const Ctx = server.Cmd; -const result = @import("cdp.zig").result; -const getParams = @import("cdp.zig").getParams; -const stringify = @import("cdp.zig").stringify; +const cdp = @import("cdp.zig"); +const result = cdp.result; +const getParams = cdp.getParams; +const stringify = cdp.stringify; const NetworkMethods = enum { enable, @@ -27,8 +28,11 @@ pub fn network( fn enable( alloc: std.mem.Allocator, id: u64, - _: *std.json.Scanner, + scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - return result(alloc, id, null, null); + return stringify(alloc, cdp.SessionIDResp{ + .id = id, + .sessionId = try cdp.getSessionID(alloc, scanner), + }); } diff --git a/src/cdp/page.zig b/src/cdp/page.zig index e9ab7e92..006496eb 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -2,9 +2,10 @@ const std = @import("std"); const server = @import("../server.zig"); const Ctx = server.Cmd; -const result = @import("cdp.zig").result; -const getParams = @import("cdp.zig").getParams; -const stringify = @import("cdp.zig").stringify; +const cdp = @import("cdp.zig"); +const result = cdp.result; +const getParams = cdp.getParams; +const stringify = cdp.stringify; const PageMethods = enum { enable, @@ -33,10 +34,13 @@ pub fn page( fn enable( alloc: std.mem.Allocator, id: u64, - _: *std.json.Scanner, + scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - return result(alloc, id, null, null); + return stringify(alloc, cdp.SessionIDResp{ + .id = id, + .sessionId = try cdp.getSessionID(alloc, scanner), + }); } fn getFrameTree( diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index e2330b39..75fdf60f 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -2,9 +2,10 @@ const std = @import("std"); const server = @import("../server.zig"); const Ctx = server.Cmd; -const result = @import("cdp.zig").result; -const getParams = @import("cdp.zig").getParams; -const stringify = @import("cdp.zig").stringify; +const cdp = @import("cdp.zig"); +const result = cdp.result; +const getParams = cdp.getParams; +const stringify = cdp.stringify; const RuntimeMethods = enum { enable, @@ -29,10 +30,13 @@ pub fn runtime( fn enable( alloc: std.mem.Allocator, id: u64, - _: *std.json.Scanner, + scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - return result(alloc, id, null, null); + return stringify(alloc, cdp.SessionIDResp{ + .id = id, + .sessionId = try cdp.getSessionID(alloc, scanner), + }); } fn runIfWaitingForDebugger( From 05c5d06df522db3a368cd1bb760b7c79902556e3 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 16 Apr 2024 17:28:28 +0200 Subject: [PATCH 022/117] Change Page.addScriptToEvaluateOnNewDocument Signed-off-by: Francis Bouvier --- src/cdp/page.zig | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 006496eb..702b79c7 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -66,11 +66,23 @@ fn setLifecycleEventsEnabled( fn addScriptToEvaluateOnNewDocument( alloc: std.mem.Allocator, id: u64, - _: *std.json.Scanner, + scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - const Res = struct { - identifier: []const u8 = "1", + const Params = struct { + source: []const u8, + worldName: ?[]const u8 = null, }; - return result(alloc, id, Res, .{}); + _ = try getParams(alloc, Params, scanner); + const Res = struct { + id: u64, + result: struct { + identifier: []const u8 = "1", + } = .{}, + sessionId: []const u8, + }; + return stringify(alloc, Res{ + .id = id, + .sessionId = try cdp.getSessionID(alloc, scanner), + }); } From 21afa1f4b352e0a065ff265e07f42f928f4b77c0 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 17 Apr 2024 14:04:34 +0200 Subject: [PATCH 023/117] Do not emit optional null value in JSON output Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 8242c796..c67e933c 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -85,7 +85,10 @@ pub fn stringify(alloc: std.mem.Allocator, res: anytype) ![]const u8 { var out = std.ArrayList(u8).init(alloc); defer out.deinit(); - try std.json.stringify(res, .{}, out.writer()); + // Do not emit optional null fields + const options: std.json.StringifyOptions = .{ .emit_null_optional_fields = false }; + + try std.json.stringify(res, options, out.writer()); const ret = try alloc.alloc(u8, out.items.len); @memcpy(ret, out.items); return ret; From 4d8cdc6dc897bf855edac1fa1caea923f19dc221 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 17 Apr 2024 14:18:18 +0200 Subject: [PATCH 024/117] Handle sessionId in result Signed-off-by: Francis Bouvier --- src/cdp/browser.zig | 4 ++-- src/cdp/cdp.zig | 51 +++++++++++++++++++++++++------------------ src/cdp/emulation.zig | 4 ++-- src/cdp/log.zig | 6 ++--- src/cdp/network.zig | 6 ++--- src/cdp/page.zig | 37 +++++++++++++++++-------------- src/cdp/runtime.zig | 8 +++---- src/cdp/target.zig | 9 ++++---- 8 files changed, 67 insertions(+), 58 deletions(-) diff --git a/src/cdp/browser.zig b/src/cdp/browser.zig index b7071927..5ba2d05b 100644 --- a/src/cdp/browser.zig +++ b/src/cdp/browser.zig @@ -52,7 +52,7 @@ fn browserGetVersion( .userAgent = UserAgent, .jsVersion = JsVersion, }; - return result(alloc, id, Res, res); + return result(alloc, id, Res, res, null); } fn browserSetDownloadBehavior( @@ -69,5 +69,5 @@ fn browserSetDownloadBehavior( }; const params = try getParams(alloc, Params, scanner); std.log.debug("params {any}", .{params}); - return result(alloc, id, null, null); + return result(alloc, id, null, null, null); } diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index c67e933c..c3a93e89 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -78,8 +78,6 @@ fn checkKey(key: []const u8, token: []const u8) !void { if (!std.mem.eql(u8, key, token)) return error.WrongToken; } -const resultNull = "{{\"id\": {d}, \"result\": {{}}}}"; - // caller owns the slice returned pub fn stringify(alloc: std.mem.Allocator, res: anytype) ![]const u8 { var out = std.ArrayList(u8).init(alloc); @@ -94,20 +92,31 @@ pub fn stringify(alloc: std.mem.Allocator, res: anytype) ![]const u8 { return ret; } +const resultNull = "{{\"id\": {d}, \"result\": {{}}}}"; +const resultNullSession = "{{\"id\": {d}, \"result\": {{}}, \"sessionId\": \"{s}\"}}"; + // caller owns the slice returned pub fn result( alloc: std.mem.Allocator, id: u64, comptime T: ?type, res: anytype, + sessionID: ?[]const u8, ) ![]const u8 { - if (T == null) return try std.fmt.allocPrint(alloc, resultNull, .{id}); + if (T == null) { + // No need to stringify a custom JSON msg, just use string templates + if (sessionID) |sID| { + return try std.fmt.allocPrint(alloc, resultNullSession, .{ id, sID }); + } + return try std.fmt.allocPrint(alloc, resultNull, .{id}); + } const Resp = struct { id: u64, result: T.?, + sessionId: ?[]const u8, }; - const resp = Resp{ .id = id, .result = res }; + const resp = Resp{ .id = id, .result = res, .sessionId = sessionID }; return stringify(alloc, resp); } @@ -125,32 +134,32 @@ pub fn getParams( return std.json.innerParse(T, alloc, scanner, options); } -pub fn getSessionID( - alloc: std.mem.Allocator, - scanner: *std.json.Scanner, -) ![]const u8 { - var n = (try scanner.next()).string; +pub fn getSessionID(scanner: *std.json.Scanner) !?[]const u8 { + + // if next token is the end of the object, there is no "sessionId" + const t = try scanner.next(); + if (t == .object_end) return null; + + var n = t.string; + + // if next token is "params" ignore them + // NOTE: will panic if it's not an empty "params" object + // TODO: maybe we should return a custom error here if (std.mem.eql(u8, n, "params")) { // ignore empty params _ = (try scanner.next()).object_begin; _ = (try scanner.next()).object_end; n = (try scanner.next()).string; } - try checkKey("sessionId", n); - const options = std.json.ParseOptions{ - .max_value_len = scanner.input.len, - .allocate = .alloc_if_needed, - }; - return std.json.innerParse([]const u8, alloc, scanner, options); + + // if next token is not "sessionId" there is no "sessionId" + if (!std.mem.eql(u8, n, "sessionId")) return null; + + // parse "sessionId" + return (try scanner.next()).string; } // Common // ------ pub const SessionID = "9559320D92474062597D9875C664CAC0"; - -pub const SessionIDResp = struct { - id: u64, - result: struct {} = .{}, - sessionId: []const u8, -}; diff --git a/src/cdp/emulation.zig b/src/cdp/emulation.zig index c5970a93..4276a17d 100644 --- a/src/cdp/emulation.zig +++ b/src/cdp/emulation.zig @@ -32,7 +32,7 @@ fn setEmulatedMedia( _: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - return result(alloc, id, null, null); + return result(alloc, id, null, null, null); } fn setFocusEmulationEnabled( @@ -41,5 +41,5 @@ fn setFocusEmulationEnabled( _: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - return result(alloc, id, null, null); + return result(alloc, id, null, null, null); } diff --git a/src/cdp/log.zig b/src/cdp/log.zig index b7c46ce6..4bbcb140 100644 --- a/src/cdp/log.zig +++ b/src/cdp/log.zig @@ -31,8 +31,6 @@ fn enable( scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - return stringify(alloc, cdp.SessionIDResp{ - .id = id, - .sessionId = try cdp.getSessionID(alloc, scanner), - }); + const sessionID = try cdp.getSessionID(scanner); + return result(alloc, id, null, null, sessionID); } diff --git a/src/cdp/network.zig b/src/cdp/network.zig index a81bfdd2..ebc21cca 100644 --- a/src/cdp/network.zig +++ b/src/cdp/network.zig @@ -31,8 +31,6 @@ fn enable( scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - return stringify(alloc, cdp.SessionIDResp{ - .id = id, - .sessionId = try cdp.getSessionID(alloc, scanner), - }); + const sessionID = try cdp.getSessionID(scanner); + return result(alloc, id, null, null, sessionID); } diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 702b79c7..df490d1a 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -37,10 +37,8 @@ fn enable( scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - return stringify(alloc, cdp.SessionIDResp{ - .id = id, - .sessionId = try cdp.getSessionID(alloc, scanner), - }); + const sessionID = try cdp.getSessionID(scanner); + return result(alloc, id, null, null, sessionID); } fn getFrameTree( @@ -50,17 +48,26 @@ fn getFrameTree( _: *Ctx, ) ![]const u8 { // TODO: dummy - return result(alloc, id, null, null); + return result(alloc, id, null, null, null); } fn setLifecycleEventsEnabled( alloc: std.mem.Allocator, id: u64, - _: *std.json.Scanner, + scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { + + // input + const Params = struct { + enabled: bool, + }; + _ = try getParams(alloc, Params, scanner); + const sessionID = try cdp.getSessionID(scanner); + + // output // TODO: dummy - return result(alloc, id, null, null); + return result(alloc, id, null, null, sessionID); } fn addScriptToEvaluateOnNewDocument( @@ -69,20 +76,18 @@ fn addScriptToEvaluateOnNewDocument( scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { + + // input const Params = struct { source: []const u8, worldName: ?[]const u8 = null, }; _ = try getParams(alloc, Params, scanner); + const sessionID = try cdp.getSessionID(scanner); + + // output const Res = struct { - id: u64, - result: struct { - identifier: []const u8 = "1", - } = .{}, - sessionId: []const u8, + identifier: []const u8 = "1", }; - return stringify(alloc, Res{ - .id = id, - .sessionId = try cdp.getSessionID(alloc, scanner), - }); + return result(alloc, id, Res, Res{}, sessionID); } diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index 75fdf60f..4bfec64e 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -33,10 +33,8 @@ fn enable( scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - return stringify(alloc, cdp.SessionIDResp{ - .id = id, - .sessionId = try cdp.getSessionID(alloc, scanner), - }); + const sessionID = try cdp.getSessionID(scanner); + return result(alloc, id, null, null, sessionID); } fn runIfWaitingForDebugger( @@ -45,5 +43,5 @@ fn runIfWaitingForDebugger( _: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - return result(alloc, id, null, null); + return result(alloc, id, null, null, null); } diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 76776f95..168be940 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -2,9 +2,10 @@ const std = @import("std"); const server = @import("../server.zig"); const Ctx = server.Cmd; -const result = @import("cdp.zig").result; -const getParams = @import("cdp.zig").getParams; -const stringify = @import("cdp.zig").stringify; +const cdp = @import("cdp.zig"); +const result = cdp.result; +const getParams = cdp.getParams; +const stringify = cdp.stringify; const TargetMethods = enum { setAutoAttach, @@ -63,7 +64,7 @@ fn tagetSetAutoAttach( const attached = try stringify(alloc, AttachToTarget{}); try server.sendSync(ctx, attached); - return result(alloc, id, null, null); + return result(alloc, id, null, null, null); } fn tagetGetTargetInfo( From e59fc903f2e0827e58b5f6544fc847e73cc3387b Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 10:20:47 +0200 Subject: [PATCH 025/117] Return a result in Page.getFrameTree Signed-off-by: Francis Bouvier --- src/cdp/page.zig | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/cdp/page.zig b/src/cdp/page.zig index df490d1a..9a47561e 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -41,14 +41,37 @@ fn enable( return result(alloc, id, null, null, sessionID); } +const FrameTreeID = "90D14BBD8AED408A0467AC93100BCDBE"; +const LoaderID = "CFC8BED824DD2FD56CF1EF33C965C79C"; +const URLBase = "chrome://newtab/"; + fn getFrameTree( alloc: std.mem.Allocator, id: u64, - _: *std.json.Scanner, + scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - // TODO: dummy - return result(alloc, id, null, null, null); + const sessionID = try cdp.getSessionID(scanner); + const FrameTree = struct { + frameTree: struct { + frame: struct { + id: []const u8 = FrameTreeID, + loaderId: []const u8 = LoaderID, + url: []const u8 = URLBase, + domainAndRegistry: []const u8 = "", + securityOrigin: []const u8 = URLBase, + mimeType: []const u8 = "mimeType", + adFrameStatus: struct { + adFrameType: []const u8 = "none", + } = .{}, + secureContextType: []const u8 = "Secure", + crossOriginIsolatedContextType: []const u8 = "NotIsolated", + gatedAPIFeatures: [][]const u8 = &[0][]const u8{}, + } = .{}, + } = .{}, + childFrames: ?[]@This() = null, + }; + return result(alloc, id, FrameTree, FrameTree{}, sessionID); } fn setLifecycleEventsEnabled( From 1620138421dd41cb870d80733c58d5f0e13ea97d Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 11:32:11 +0200 Subject: [PATCH 026/117] Return sessionId in Emulation.setFocusEmulationEnabled Signed-off-by: Francis Bouvier --- src/cdp/emulation.zig | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/cdp/emulation.zig b/src/cdp/emulation.zig index 4276a17d..1c8db4d0 100644 --- a/src/cdp/emulation.zig +++ b/src/cdp/emulation.zig @@ -2,9 +2,10 @@ const std = @import("std"); const server = @import("../server.zig"); const Ctx = server.Cmd; -const result = @import("cdp.zig").result; -const getParams = @import("cdp.zig").getParams; -const stringify = @import("cdp.zig").stringify; +const cdp = @import("cdp.zig"); +const result = cdp.result; +const getParams = cdp.getParams; +const stringify = cdp.stringify; const EmulationMethods = enum { setEmulatedMedia, @@ -38,8 +39,18 @@ fn setEmulatedMedia( fn setFocusEmulationEnabled( alloc: std.mem.Allocator, id: u64, - _: *std.json.Scanner, + scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - return result(alloc, id, null, null, null); + + // input + const Params = struct { + enabled: bool, + }; + _ = try getParams(alloc, Params, scanner); + const sessionID = try cdp.getSessionID(scanner); + + // output + // TODO: dummy + return result(alloc, id, null, null, sessionID); } From e4ae2df1a48304160ec2f951f1b2f0d03bbcb7ee Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 11:57:39 +0200 Subject: [PATCH 027/117] Add some optional params in methods Signed-off-by: Francis Bouvier --- src/cdp/emulation.zig | 19 +++++++++++++++++-- src/cdp/page.zig | 2 ++ src/cdp/target.zig | 8 +++++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/cdp/emulation.zig b/src/cdp/emulation.zig index 1c8db4d0..ae759396 100644 --- a/src/cdp/emulation.zig +++ b/src/cdp/emulation.zig @@ -27,13 +27,28 @@ pub fn emulation( }; } +const MediaFeature = struct { + name: []const u8, + value: []const u8, +}; + fn setEmulatedMedia( alloc: std.mem.Allocator, id: u64, - _: *std.json.Scanner, + scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - return result(alloc, id, null, null, null); + // input + const Params = struct { + media: ?[]const u8 = null, + features: ?[]MediaFeature = null, + }; + _ = try getParams(alloc, Params, scanner); + const sessionID = try cdp.getSessionID(scanner); + + // output + // TODO: dummy + return result(alloc, id, null, null, sessionID); } fn setFocusEmulationEnabled( diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 9a47561e..a26fbe8a 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -104,6 +104,8 @@ fn addScriptToEvaluateOnNewDocument( const Params = struct { source: []const u8, worldName: ?[]const u8 = null, + includeCommandLineAPI: bool = false, + runImmediately: bool = false, }; _ = try getParams(alloc, Params, scanner); const sessionID = try cdp.getSessionID(scanner); diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 168be940..9b330025 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -31,6 +31,11 @@ const SessionID = "9559320D92474062597D9875C664CAC0"; const TargetID = "CFCD6EC01573CF29BB638E9DC0F52DDC"; const BrowserContextID = "65618675CB7D3585A95049E9DFE95EA9"; +const TargetFilter = struct { + type: []const u8, + exclude: bool, +}; + fn tagetSetAutoAttach( alloc: std.mem.Allocator, id: u64, @@ -40,7 +45,8 @@ fn tagetSetAutoAttach( const Params = struct { autoAttach: bool, waitForDebuggerOnStart: bool, - flatten: ?bool = null, + flatten: bool = true, + filter: ?[]TargetFilter = null, }; const params = try getParams(alloc, Params, scanner); std.log.debug("params {any}", .{params}); From 43a558f5aefc540ab86cd9974c1fb47a1c1c10dd Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 12:10:20 +0200 Subject: [PATCH 028/117] Make getParams return nullable Signed-off-by: Francis Bouvier --- src/cdp/browser.zig | 3 +-- src/cdp/cdp.zig | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/cdp/browser.zig b/src/cdp/browser.zig index 5ba2d05b..12f6e4d0 100644 --- a/src/cdp/browser.zig +++ b/src/cdp/browser.zig @@ -67,7 +67,6 @@ fn browserSetDownloadBehavior( downloadPath: ?[]const u8 = null, eventsEnabled: ?bool = null, }; - const params = try getParams(alloc, Params, scanner); - std.log.debug("params {any}", .{params}); + _ = try getParams(alloc, Params, scanner); return result(alloc, id, null, null, null); } diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index c3a93e89..def8e2ff 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -125,13 +125,22 @@ pub fn getParams( alloc: std.mem.Allocator, comptime T: type, scanner: *std.json.Scanner, -) !T { - try checkKey("params", (try scanner.next()).string); +) !?T { + + // if next token is the end of the object, there is no "params" + const t = try scanner.next(); + if (t == .object_end) return null; + + // if next token is not "params" there is no "params" + if (!std.mem.eql(u8, "params", t.string)) return null; + + // parse "params" const options = std.json.ParseOptions{ .max_value_len = scanner.input.len, .allocate = .alloc_if_needed, }; - return std.json.innerParse(T, alloc, scanner, options); + const params = try std.json.innerParse(T, alloc, scanner, options); + return params; } pub fn getSessionID(scanner: *std.json.Scanner) !?[]const u8 { From 490eb400283285022aa458a711a54478ca5f5a1f Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 13:18:16 +0200 Subject: [PATCH 029/117] Add method cdp function Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index def8e2ff..2d2ae708 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -52,11 +52,11 @@ pub fn do( const id = try std.fmt.parseUnsigned(u64, (try scanner.next()).number, 10); try checkKey("method", (try scanner.next()).string); - const method = (try scanner.next()).string; + const method_name = (try scanner.next()).string; - std.log.debug("cmd: id {any}, method {s}", .{ id, method }); + std.log.debug("cmd: id {any}, method {s}", .{ id, method_name }); - var iter = std.mem.splitScalar(u8, method, '.'); + var iter = std.mem.splitScalar(u8, method_name, '.'); const domain = std.meta.stringToEnum(Domains, iter.first()) orelse return error.UnknonwDomain; @@ -121,6 +121,24 @@ pub fn result( return stringify(alloc, resp); } +// caller owns the slice returned +pub fn method( + alloc: std.mem.Allocator, + name: []const u8, + comptime T: type, + params: T, + sessionID: ?[]const u8, +) ![]const u8 { + const Resp = struct { + method: []const u8, + params: T, + sessionId: ?[]const u8, + }; + const resp = Resp{ .method = name, .params = params, .sessionId = sessionID }; + + return stringify(alloc, resp); +} + pub fn getParams( alloc: std.mem.Allocator, comptime T: type, From 69f5bb9ed3aa326c81a1f5dd6a13f9570d538228 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 13:18:51 +0200 Subject: [PATCH 030/117] Add sessionId in Runime.runIfWaitingForDebugger response Signed-off-by: Francis Bouvier --- src/cdp/runtime.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index 4bfec64e..d5db891a 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -40,8 +40,9 @@ fn enable( fn runIfWaitingForDebugger( alloc: std.mem.Allocator, id: u64, - _: *std.json.Scanner, + scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - return result(alloc, id, null, null, null); + const sessionID = try cdp.getSessionID(scanner); + return result(alloc, id, null, null, sessionID); } From 06f161c4231d06b15af290263336ef1bcfa6ecf2 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 13:20:23 +0200 Subject: [PATCH 031/117] Add Target.getTargetInfo + do not send attachedToTarget if sessionId Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 2 ++ src/cdp/page.zig | 8 +++---- src/cdp/target.zig | 59 ++++++++++++++++++++++++++-------------------- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 2d2ae708..2f78a23d 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -190,3 +190,5 @@ pub fn getSessionID(scanner: *std.json.Scanner) !?[]const u8 { // ------ pub const SessionID = "9559320D92474062597D9875C664CAC0"; +pub const URLBase = "chrome://newtab/"; +pub const FrameID = "90D14BBD8AED408A0467AC93100BCDBE"; diff --git a/src/cdp/page.zig b/src/cdp/page.zig index a26fbe8a..e71cce21 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -41,9 +41,7 @@ fn enable( return result(alloc, id, null, null, sessionID); } -const FrameTreeID = "90D14BBD8AED408A0467AC93100BCDBE"; const LoaderID = "CFC8BED824DD2FD56CF1EF33C965C79C"; -const URLBase = "chrome://newtab/"; fn getFrameTree( alloc: std.mem.Allocator, @@ -55,11 +53,11 @@ fn getFrameTree( const FrameTree = struct { frameTree: struct { frame: struct { - id: []const u8 = FrameTreeID, + id: []const u8 = cdp.FrameID, loaderId: []const u8 = LoaderID, - url: []const u8 = URLBase, + url: []const u8 = cdp.URLBase, domainAndRegistry: []const u8 = "", - securityOrigin: []const u8 = URLBase, + securityOrigin: []const u8 = cdp.URLBase, mimeType: []const u8 = "mimeType", adFrameStatus: struct { adFrameType: []const u8 = "none", diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 9b330025..79f2b5bf 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -9,7 +9,7 @@ const stringify = cdp.stringify; const TargetMethods = enum { setAutoAttach, - // getTargetInfo, + getTargetInfo, }; pub fn target( @@ -23,12 +23,12 @@ pub fn target( return error.UnknownMethod; return switch (method) { .setAutoAttach => tagetSetAutoAttach(alloc, id, scanner, ctx), - // .getTargetInfo => tagetGetTargetInfo(alloc, id, scanner, ctx), + .getTargetInfo => tagetGetTargetInfo(alloc, id, scanner, ctx), }; } -const SessionID = "9559320D92474062597D9875C664CAC0"; -const TargetID = "CFCD6EC01573CF29BB638E9DC0F52DDC"; +const PageTargetID = "CFCD6EC01573CF29BB638E9DC0F52DDC"; +const BrowserTargetID = "2d2bdef9-1c95-416f-8c0e-83f3ab73a30c"; const BrowserContextID = "65618675CB7D3585A95049E9DFE95EA9"; const TargetFilter = struct { @@ -51,26 +51,28 @@ fn tagetSetAutoAttach( const params = try getParams(alloc, Params, scanner); std.log.debug("params {any}", .{params}); - const AttachToTarget = struct { - method: []const u8 = "Target.attachedToTarget", - params: struct { - sessionId: []const u8 = SessionID, + const sessionID = try cdp.getSessionID(scanner); + + if (sessionID == null) { + const AttachToTarget = struct { + sessionId: []const u8 = cdp.SessionID, targetInfo: struct { - targetId: []const u8 = TargetID, + targetId: []const u8 = PageTargetID, type: []const u8 = "page", title: []const u8 = "New Incognito tab", - url: []const u8 = "chrome://newtab/", + url: []const u8 = cdp.URLBase, attached: bool = true, canAccessOpener: bool = false, browserContextId: []const u8 = BrowserContextID, } = .{}, waitingForDebugger: bool = false, - } = .{}, - }; - const attached = try stringify(alloc, AttachToTarget{}); - try server.sendSync(ctx, attached); + }; + const attached = try cdp.method(alloc, "Target.attachedToTarget", AttachToTarget, .{}, null); + std.log.debug("res {s}", .{attached}); + try server.sendSync(ctx, attached); + } - return result(alloc, id, null, null, null); + return result(alloc, id, null, null, sessionID); } fn tagetGetTargetInfo( @@ -79,22 +81,29 @@ fn tagetGetTargetInfo( scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - _ = scanner; + // input + const Params = struct { + targetId: ?[]const u8 = null, + }; + _ = try getParams(alloc, Params, scanner); + + // output const TargetInfo = struct { targetId: []const u8, type: []const u8, - title: []const u8, - url: []const u8, - attached: bool, - canAccessOpener: bool, - + title: []const u8 = "", + url: []const u8 = "", + attached: bool = true, + openerId: ?[]const u8 = null, + canAccessOpener: bool = false, + openerFrameId: ?[]const u8 = null, browserContextId: ?[]const u8 = null, + subtype: ?[]const u8 = null, }; const targetInfo = TargetInfo{ - .targetId = TargetID, - .type = "page", + .targetId = BrowserTargetID, + .type = "browser", }; - _ = targetInfo; - return result(alloc, id, null, null); + return result(alloc, id, TargetInfo, targetInfo, null); } From 0506a7bb5369df907e9e62da6c5a93d89e7d97bb Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 16:14:19 +0200 Subject: [PATCH 032/117] Add Browser.createBrowserContext Signed-off-by: Francis Bouvier --- src/cdp/target.zig | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 79f2b5bf..f6985569 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -10,6 +10,7 @@ const stringify = cdp.stringify; const TargetMethods = enum { setAutoAttach, getTargetInfo, + createBrowserContext, }; pub fn target( @@ -24,6 +25,7 @@ pub fn target( return switch (method) { .setAutoAttach => tagetSetAutoAttach(alloc, id, scanner, ctx), .getTargetInfo => tagetGetTargetInfo(alloc, id, scanner, ctx), + .createBrowserContext => createBrowserContext(alloc, id, scanner, ctx), }; } @@ -107,3 +109,29 @@ fn tagetGetTargetInfo( }; return result(alloc, id, TargetInfo, targetInfo, null); } + +const ContextID = "22648B09EDCCDD11109E2D4FEFBE4F89"; + +fn createBrowserContext( + alloc: std.mem.Allocator, + id: u64, + scanner: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + + // input + const Params = struct { + disposeOnDetach: bool = false, + proxyServer: ?[]const u8 = null, + proxyBypassList: ?[]const u8 = null, + originsWithUniversalNetworkAccess: ?[][]const u8 = null, + }; + _ = try getParams(alloc, Params, scanner); + const sessionID = try cdp.getSessionID(scanner); + + // output + const Resp = struct { + browserContextId: []const u8 = ContextID, + }; + return result(alloc, id, Resp, Resp{}, sessionID); +} From 9974b56607e614c09dcb61afccdce9f507c59eda Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 16:43:19 +0200 Subject: [PATCH 033/117] Add Target.createTarget Signed-off-by: Francis Bouvier --- src/cdp/target.zig | 100 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 85 insertions(+), 15 deletions(-) diff --git a/src/cdp/target.zig b/src/cdp/target.zig index f6985569..9436fe55 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -11,6 +11,7 @@ const TargetMethods = enum { setAutoAttach, getTargetInfo, createBrowserContext, + createTarget, }; pub fn target( @@ -26,6 +27,7 @@ pub fn target( .setAutoAttach => tagetSetAutoAttach(alloc, id, scanner, ctx), .getTargetInfo => tagetGetTargetInfo(alloc, id, scanner, ctx), .createBrowserContext => createBrowserContext(alloc, id, scanner, ctx), + .createTarget => createTarget(alloc, id, scanner, ctx), }; } @@ -33,6 +35,20 @@ const PageTargetID = "CFCD6EC01573CF29BB638E9DC0F52DDC"; const BrowserTargetID = "2d2bdef9-1c95-416f-8c0e-83f3ab73a30c"; const BrowserContextID = "65618675CB7D3585A95049E9DFE95EA9"; +const AttachToTarget = struct { + sessionId: []const u8, + targetInfo: struct { + targetId: []const u8, + type: []const u8 = "page", + title: []const u8, + url: []const u8, + attached: bool = true, + canAccessOpener: bool = false, + browserContextId: []const u8, + }, + waitingForDebugger: bool = false, +}; + const TargetFilter = struct { type: []const u8, exclude: bool, @@ -56,22 +72,24 @@ fn tagetSetAutoAttach( const sessionID = try cdp.getSessionID(scanner); if (sessionID == null) { - const AttachToTarget = struct { - sessionId: []const u8 = cdp.SessionID, - targetInfo: struct { - targetId: []const u8 = PageTargetID, - type: []const u8 = "page", - title: []const u8 = "New Incognito tab", - url: []const u8 = cdp.URLBase, - attached: bool = true, - canAccessOpener: bool = false, - browserContextId: []const u8 = BrowserContextID, - } = .{}, - waitingForDebugger: bool = false, + const attached = AttachToTarget{ + .sessionId = cdp.SessionID, + .targetInfo = .{ + .targetId = PageTargetID, + .title = "New Incognito tab", + .url = cdp.URLBase, + .browserContextId = BrowserContextID, + }, }; - const attached = try cdp.method(alloc, "Target.attachedToTarget", AttachToTarget, .{}, null); - std.log.debug("res {s}", .{attached}); - try server.sendSync(ctx, attached); + const event = try cdp.method( + alloc, + "Target.attachedToTarget", + AttachToTarget, + attached, + null, + ); + std.log.debug("event {s}", .{event}); + try server.sendSync(ctx, event); } return result(alloc, id, null, null, sessionID); @@ -111,6 +129,7 @@ fn tagetGetTargetInfo( } const ContextID = "22648B09EDCCDD11109E2D4FEFBE4F89"; +const ContextSessionID = "4FDC2CB760A23A220497A05C95417CF4"; fn createBrowserContext( alloc: std.mem.Allocator, @@ -135,3 +154,54 @@ fn createBrowserContext( }; return result(alloc, id, Resp, Resp{}, sessionID); } + +const TargetID = "57356548460A8F29706A2ADF14316298"; + +fn createTarget( + alloc: std.mem.Allocator, + id: u64, + scanner: *std.json.Scanner, + ctx: *Ctx, +) ![]const u8 { + + // input + const Params = struct { + url: []const u8, + width: ?u64 = null, + height: ?u64 = null, + browserContextId: []const u8, + enableBeginFrameControl: bool = false, + newWindow: bool = false, + background: bool = false, + forTab: ?bool = null, + }; + _ = try getParams(alloc, Params, scanner); + const sessionID = try cdp.getSessionID(scanner); + + // send attachToTarget event + const attached = AttachToTarget{ + .sessionId = ContextSessionID, + .targetInfo = .{ + .targetId = TargetID, + .title = "", + .url = "about:blank", + .browserContextId = ContextID, + }, + .waitingForDebugger = true, + }; + const event = try cdp.method( + alloc, + "Target.attachedToTarget", + AttachToTarget, + attached, + sessionID, + ); + std.log.debug("event {s}", .{event}); + try server.sendSync(ctx, event); + + // output + const Resp = struct { + targetId: []const u8 = TargetID, + }; + return result(alloc, id, Resp, Resp{}, sessionID); +} From f02de77295e7c95b6d88fb7dc9e3b269693fbc8d Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 20:38:27 +0200 Subject: [PATCH 034/117] Add getContent Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 2f78a23d..41dbb2ac 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -186,6 +186,54 @@ pub fn getSessionID(scanner: *std.json.Scanner) !?[]const u8 { return (try scanner.next()).string; } +pub fn getContent( + alloc: std.mem.Allocator, + comptime T: type, + scanner: *std.json.Scanner, +) !struct { params: T, sessionID: ?[]const u8 } { + + // if next token is the end of the object, error + const t = try scanner.next(); + if (t == .object_end) return error.CDPNoContent; + + var params: T = undefined; + var sessionID: ?[]const u8 = null; + + var n = t.string; + + // params + if (std.mem.eql(u8, n, "params")) { + if (T == void) { + + // ignore empty params + _ = (try scanner.next()).object_begin; + _ = (try scanner.next()).object_end; + n = (try scanner.next()).string; + params = void{}; + } else { + + // parse "params" + const options = std.json.ParseOptions{ + .max_value_len = scanner.input.len, + .allocate = .alloc_if_needed, + }; + params = try std.json.innerParse(T, alloc, scanner, options); + } + } else { + params = switch (@typeInfo(T)) { + .Void => void{}, + .Optional => null, + else => return error.CDPNoParams, + }; + } + + if (std.mem.eql(u8, n, "sessionId")) { + sessionID = (try scanner.next()).string; + } + + return .{ .params = params, .sessionID = sessionID }; +} + // Common // ------ From 508741c3677dcc7344040cb8c7e2f524a24803e4 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 20:53:18 +0200 Subject: [PATCH 035/117] Add Browser.getWindowForTarget Signed-off-by: Francis Bouvier --- src/cdp/browser.zig | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/cdp/browser.zig b/src/cdp/browser.zig index 12f6e4d0..3e49547b 100644 --- a/src/cdp/browser.zig +++ b/src/cdp/browser.zig @@ -2,12 +2,14 @@ const std = @import("std"); const server = @import("../server.zig"); const Ctx = server.Cmd; -const result = @import("cdp.zig").result; -const getParams = @import("cdp.zig").getParams; +const cdp = @import("cdp.zig"); +const result = cdp.result; +const getParams = cdp.getParams; const BrowserMethods = enum { getVersion, setDownloadBehavior, + getWindowForTarget, }; pub fn browser( @@ -22,6 +24,7 @@ pub fn browser( return switch (method) { .getVersion => browserGetVersion(alloc, id, scanner, ctx), .setDownloadBehavior => browserSetDownloadBehavior(alloc, id, scanner, ctx), + .getWindowForTarget => getWindowForTarget(alloc, id, scanner, ctx), }; } @@ -70,3 +73,33 @@ fn browserSetDownloadBehavior( _ = try getParams(alloc, Params, scanner); return result(alloc, id, null, null, null); } + +const DevToolsWindowID = 1923710101; + +fn getWindowForTarget( + alloc: std.mem.Allocator, + id: u64, + scanner: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + + // input + const Params = struct { + targetId: ?[]const u8 = null, + }; + const content = try cdp.getContent(alloc, ?Params, scanner); + std.debug.assert(content.sessionID != null); + + // output + const Resp = struct { + windowId: u64 = DevToolsWindowID, + bounds: struct { + left: ?u64 = null, + top: ?u64 = null, + width: ?u64 = null, + height: ?u64 = null, + windowState: []const u8 = "normal", + } = .{}, + }; + return result(alloc, id, Resp, Resp{}, content.sessionID.?); +} From fc1b3d539787f131f180e57b45a9936a05ed7763 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 20:54:30 +0200 Subject: [PATCH 036/117] Contextual frameTree Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 9 +++++++++ src/cdp/page.zig | 33 +++++++++++++++++++++------------ src/cdp/target.zig | 11 +++++++++-- src/server.zig | 3 +++ 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 41dbb2ac..a7e3a0fa 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -71,6 +71,14 @@ pub fn do( }; } +pub const State = struct { + frameID: []const u8 = FrameID, + url: []const u8 = URLBase, + securityOrigin: []const u8 = URLBase, + secureContextType: []const u8 = "Secure", // TODO: enum + loaderID: []const u8 = LoaderID, +}; + // Utils // ----- @@ -240,3 +248,4 @@ pub fn getContent( pub const SessionID = "9559320D92474062597D9875C664CAC0"; pub const URLBase = "chrome://newtab/"; pub const FrameID = "90D14BBD8AED408A0467AC93100BCDBE"; +pub const LoaderID = "CFC8BED824DD2FD56CF1EF33C965C79C"; diff --git a/src/cdp/page.zig b/src/cdp/page.zig index e71cce21..6fcd815a 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -41,35 +41,44 @@ fn enable( return result(alloc, id, null, null, sessionID); } -const LoaderID = "CFC8BED824DD2FD56CF1EF33C965C79C"; - fn getFrameTree( alloc: std.mem.Allocator, id: u64, scanner: *std.json.Scanner, - _: *Ctx, + ctx: *Ctx, ) ![]const u8 { const sessionID = try cdp.getSessionID(scanner); const FrameTree = struct { frameTree: struct { frame: struct { - id: []const u8 = cdp.FrameID, - loaderId: []const u8 = LoaderID, - url: []const u8 = cdp.URLBase, + id: []const u8, + loaderId: []const u8, + url: []const u8, domainAndRegistry: []const u8 = "", - securityOrigin: []const u8 = cdp.URLBase, - mimeType: []const u8 = "mimeType", + securityOrigin: []const u8, + mimeType: []const u8 = "text/html", adFrameStatus: struct { adFrameType: []const u8 = "none", } = .{}, - secureContextType: []const u8 = "Secure", + secureContextType: []const u8, crossOriginIsolatedContextType: []const u8 = "NotIsolated", gatedAPIFeatures: [][]const u8 = &[0][]const u8{}, - } = .{}, - } = .{}, + }, + }, childFrames: ?[]@This() = null, }; - return result(alloc, id, FrameTree, FrameTree{}, sessionID); + const frameTree = FrameTree{ + .frameTree = .{ + .frame = .{ + .id = ctx.state.frameID, + .url = ctx.state.url, + .securityOrigin = ctx.state.securityOrigin, + .secureContextType = ctx.state.secureContextType, + .loaderId = ctx.state.loaderID, + }, + }, + }; + return result(alloc, id, FrameTree, frameTree, sessionID); } fn setLifecycleEventsEnabled( diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 9436fe55..5a6ab47a 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -178,13 +178,20 @@ fn createTarget( _ = try getParams(alloc, Params, scanner); const sessionID = try cdp.getSessionID(scanner); + // change CDP state + ctx.state.frameID = TargetID; + ctx.state.url = "about:blank"; + ctx.state.securityOrigin = "://"; + ctx.state.secureContextType = "InsecureScheme"; + ctx.state.loaderID = "DD4A76F842AA389647D702B4D805F49A"; + // send attachToTarget event const attached = AttachToTarget{ .sessionId = ContextSessionID, .targetInfo = .{ - .targetId = TargetID, + .targetId = ctx.state.frameID, .title = "", - .url = "about:blank", + .url = ctx.state.url, .browserContextId = ContextID, }, .waitingForDebugger = true, diff --git a/src/server.zig b/src/server.zig index 79ebba04..105d5d97 100644 --- a/src/server.zig +++ b/src/server.zig @@ -25,6 +25,9 @@ pub const Cmd = struct { buf: []u8, // only for read operations err: ?Error = null, + // CDP + state: cdp.State = .{}, + // JS fields js_env: *public.Env, try_catch: public.TryCatch, From c7ba567d7f11147ad3fde2557eb02a6b2e46e477 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 21:45:46 +0200 Subject: [PATCH 037/117] Handle non-empty void params in getContent Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index a7e3a0fa..9c9b678a 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -213,9 +213,16 @@ pub fn getContent( if (std.mem.eql(u8, n, "params")) { if (T == void) { - // ignore empty params - _ = (try scanner.next()).object_begin; - _ = (try scanner.next()).object_end; + // ignore params + var finished: usize = 0; + while (true) { + switch (try scanner.next()) { + .object_begin => finished += 1, + .object_end => finished -= 1, + else => continue, + } + if (finished == 0) break; + } n = (try scanner.next()).string; params = void{}; } else { From aec74551518a3c3d2abcc99509da24f58c9e3465 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 21:46:43 +0200 Subject: [PATCH 038/117] Add Emulation.setDeviceMetricsOverride Signed-off-by: Francis Bouvier --- src/cdp/emulation.zig | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/cdp/emulation.zig b/src/cdp/emulation.zig index ae759396..f7daff4f 100644 --- a/src/cdp/emulation.zig +++ b/src/cdp/emulation.zig @@ -10,6 +10,7 @@ const stringify = cdp.stringify; const EmulationMethods = enum { setEmulatedMedia, setFocusEmulationEnabled, + setDeviceMetricsOverride, }; pub fn emulation( @@ -24,6 +25,7 @@ pub fn emulation( return switch (method) { .setEmulatedMedia => setEmulatedMedia(alloc, id, scanner, ctx), .setFocusEmulationEnabled => setFocusEmulationEnabled(alloc, id, scanner, ctx), + .setDeviceMetricsOverride => setDeviceMetricsOverride(alloc, id, scanner, ctx), }; } @@ -69,3 +71,17 @@ fn setFocusEmulationEnabled( // TODO: dummy return result(alloc, id, null, null, sessionID); } + +fn setDeviceMetricsOverride( + alloc: std.mem.Allocator, + id: u64, + scanner: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + + // input + const content = try cdp.getContent(alloc, void, scanner); + + // output + return result(alloc, id, null, null, content.sessionID); +} From c54b50eb0ca6d856933ee23394790c2ca237cbb8 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 21:52:06 +0200 Subject: [PATCH 039/117] Add Browser.setWindowBounds Signed-off-by: Francis Bouvier --- src/cdp/browser.zig | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/cdp/browser.zig b/src/cdp/browser.zig index 3e49547b..c0dff5d8 100644 --- a/src/cdp/browser.zig +++ b/src/cdp/browser.zig @@ -10,6 +10,7 @@ const BrowserMethods = enum { getVersion, setDownloadBehavior, getWindowForTarget, + setWindowBounds, }; pub fn browser( @@ -25,6 +26,7 @@ pub fn browser( .getVersion => browserGetVersion(alloc, id, scanner, ctx), .setDownloadBehavior => browserSetDownloadBehavior(alloc, id, scanner, ctx), .getWindowForTarget => getWindowForTarget(alloc, id, scanner, ctx), + .setWindowBounds => setWindowBounds(alloc, id, scanner, ctx), }; } @@ -103,3 +105,14 @@ fn getWindowForTarget( }; return result(alloc, id, Resp, Resp{}, content.sessionID.?); } + +fn setWindowBounds( + alloc: std.mem.Allocator, + id: u64, + scanner: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + // NOTE: noop + const content = try cdp.getContent(alloc, void, scanner); + return result(alloc, id, null, null, content.sessionID); +} From 9ce574a1f06f747939676c7723cffbbcf58188a9 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 18 Apr 2024 21:57:31 +0200 Subject: [PATCH 040/117] Add Page.createIsolatedWorld Signed-off-by: Francis Bouvier --- src/cdp/page.zig | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 6fcd815a..c79d2afb 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -12,6 +12,7 @@ const PageMethods = enum { getFrameTree, setLifecycleEventsEnabled, addScriptToEvaluateOnNewDocument, + createIsolatedWorld, }; pub fn page( @@ -28,6 +29,7 @@ pub fn page( .getFrameTree => getFrameTree(alloc, id, scanner, ctx), .setLifecycleEventsEnabled => setLifecycleEventsEnabled(alloc, id, scanner, ctx), .addScriptToEvaluateOnNewDocument => addScriptToEvaluateOnNewDocument(alloc, id, scanner, ctx), + .createIsolatedWorld => createIsolatedWorld(alloc, id, scanner, ctx), }; } @@ -123,3 +125,21 @@ fn addScriptToEvaluateOnNewDocument( }; return result(alloc, id, Res, Res{}, sessionID); } + +fn createIsolatedWorld( + alloc: std.mem.Allocator, + id: u64, + scanner: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + + // input + const content = try cdp.getContent(alloc, void, scanner); + + // output + const Resp = struct { + executionContextId: u8 = 2, + }; + + return result(alloc, id, Resp, .{}, content.sessionID); +} From 4f0b071c593a720d3b72a67dd807264906c4b59c Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 19 Apr 2024 11:35:47 +0200 Subject: [PATCH 041/117] Fix getContent algo Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 9c9b678a..30a94594 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -223,7 +223,6 @@ pub fn getContent( } if (finished == 0) break; } - n = (try scanner.next()).string; params = void{}; } else { @@ -234,6 +233,9 @@ pub fn getContent( }; params = try std.json.innerParse(T, alloc, scanner, options); } + + // go next + n = (try scanner.next()).string; } else { params = switch (@typeInfo(T)) { .Void => void{}, From 1a1cd0353c6b3abfdeb9191d2d0712a1e35b0181 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 19 Apr 2024 11:36:02 +0200 Subject: [PATCH 042/117] Add dummy Page.navigate Signed-off-by: Francis Bouvier --- src/cdp/page.zig | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/cdp/page.zig b/src/cdp/page.zig index c79d2afb..1292186f 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -13,6 +13,7 @@ const PageMethods = enum { setLifecycleEventsEnabled, addScriptToEvaluateOnNewDocument, createIsolatedWorld, + navigate, }; pub fn page( @@ -30,6 +31,7 @@ pub fn page( .setLifecycleEventsEnabled => setLifecycleEventsEnabled(alloc, id, scanner, ctx), .addScriptToEvaluateOnNewDocument => addScriptToEvaluateOnNewDocument(alloc, id, scanner, ctx), .createIsolatedWorld => createIsolatedWorld(alloc, id, scanner, ctx), + .navigate => navigate(alloc, id, scanner, ctx), }; } @@ -143,3 +145,38 @@ fn createIsolatedWorld( return result(alloc, id, Resp, .{}, content.sessionID); } + +fn navigate( + alloc: std.mem.Allocator, + id: u64, + scanner: *std.json.Scanner, + ctx: *Ctx, +) ![]const u8 { + + // input + const Params = struct { + url: []const u8, + referrer: ?[]const u8 = null, + transitionType: ?[]const u8 = null, // TODO: enum + frameId: ?[]const u8 = null, + referrerPolicy: ?[]const u8 = null, // TODO: enum + }; + const content = try cdp.getContent(alloc, Params, scanner); + std.debug.assert(content.sessionID != null); + + // change state + ctx.state.url = content.params.url; + ctx.state.loaderID = "AF8667A203C5392DBE9AC290044AA4C2"; + + // output + const Resp = struct { + frameId: []const u8, + loaderId: ?[]const u8, + errorText: ?[]const u8 = null, + }; + const resp = Resp{ + .frameId = ctx.state.frameID, + .loaderId = ctx.state.loaderID, + }; + return result(alloc, id, Resp, resp, content.sessionID); +} From ed38705efd9a649710afc47cce45ef5f62e14b68 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 19 Apr 2024 11:57:44 +0200 Subject: [PATCH 043/117] Basic version using Browser Signed-off-by: Francis Bouvier --- src/main.zig | 7 ++++-- src/server.zig | 66 +++++++++++++++++++++----------------------------- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/src/main.zig b/src/main.zig index 5575f815..b9604eb6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -20,6 +20,7 @@ const std = @import("std"); const jsruntime = @import("jsruntime"); +const Browser = @import("browser/browser.zig").Browser; const server = @import("server.zig"); const parser = @import("netsurf"); @@ -60,7 +61,9 @@ pub fn main() !void { defer srv.deinit(); try srv.listen(addr); std.debug.print("Listening on: {s}...\n", .{socket_path}); - server.socket_fd = srv.sockfd.?; - try jsruntime.loadEnv(&arena, server.execJS); + var browser = try Browser.init(arena.allocator(), vm); + defer browser.deinit(); + + try server.listen(&browser, srv.sockfd.?); } diff --git a/src/server.zig b/src/server.zig index 105d5d97..48ddcc72 100644 --- a/src/server.zig +++ b/src/server.zig @@ -7,10 +7,9 @@ const RecvError = public.IO.RecvError; const SendError = public.IO.SendError; const TimeoutError = public.IO.TimeoutError; -const Window = @import("html/window.zig").Window; +const Browser = @import("browser/browser.zig").Browser; const cdp = @import("cdp/cdp.zig"); -pub var socket_fd: std.os.socket_t = undefined; const NoError = error{NoError}; const Error = AcceptError || RecvError || SendError || TimeoutError || cdp.Error || NoError; @@ -29,8 +28,8 @@ pub const Cmd = struct { state: cdp.State = .{}, // JS fields - js_env: *public.Env, - try_catch: public.TryCatch, + browser: *Browser, // TODO: is pointer mandatory here? + // try_catch: public.TryCatch, // TODO fn cbk(self: *Cmd, completion: *Completion, result: RecvError!usize) void { const size = result catch |err| { @@ -38,10 +37,16 @@ pub const Cmd = struct { return; }; + if (size == 0) { + // continue receving incomming messages asynchronously + self.loop().io.recv(*Cmd, self, cbk, completion, self.socket, self.buf); + return; + } + // input var input = self.buf[0..size]; if (std.log.defaultLogEnabled(.debug)) { - std.debug.print("\ninput {s}\n", .{input}); + std.debug.print("\ninput size: {d}, content: {s}\n", .{ size, input }); } // close on exit command @@ -86,11 +91,13 @@ pub const Cmd = struct { // shortcuts fn alloc(self: *Cmd) std.mem.Allocator { - return self.js_env.nat_ctx.alloc; + // TODO: should we return the allocator from the page instead? + return self.browser.currentSession().alloc; } - fn loop(self: *Cmd) *public.Loop { - return self.js_env.nat_ctx.loop; + fn loop(self: *Cmd) public.Loop { + // TODO: pointer instead? + return self.browser.currentSession().loop; } }; @@ -179,46 +186,28 @@ const Accept = struct { } }; -pub fn execJS(alloc: std.mem.Allocator, js_env: *public.Env) anyerror!void { +// Listen +// ------ - // start JS env - try js_env.start(alloc); - defer js_env.stop(); - - // alias global as self - try js_env.attachObject(try js_env.getGlobal(), "self", null); - - // alias global as self and window - const window = Window.create(null); - // window.replaceDocument(doc); TODO - try js_env.bindGlobal(window); - - // add console object - const console = public.Console{}; - try js_env.addObject(console, "console"); - - // JS try cache - var try_catch = public.TryCatch.init(js_env.*); - defer try_catch.deinit(); +pub fn listen(browser: *Browser, socket: std.os.socket_t) anyerror!void { // create I/O contexts and callbacks // for accepting connections and receving messages var input: [1024]u8 = undefined; var cmd = Cmd{ - .js_env = js_env, + .browser = browser, .socket = undefined, .buf = &input, - .try_catch = try_catch, }; var accept = Accept{ .cmd = &cmd, - .socket = socket_fd, + .socket = socket, }; // accepting connection asynchronously on internal server - const loop = js_env.nat_ctx.loop; + const loop = browser.currentSession().loop; var completion: Completion = undefined; - loop.io.accept(*Accept, &accept, Accept.cbk, &completion, socket_fd); + loop.io.accept(*Accept, &accept, Accept.cbk, &completion, socket); // infinite loop on I/O events, either: // - cmd from incoming connection on server socket @@ -226,11 +215,12 @@ pub fn execJS(alloc: std.mem.Allocator, js_env: *public.Env) anyerror!void { while (true) { try loop.io.tick(); if (loop.cbk_error) { - if (try try_catch.exception(alloc, js_env.*)) |msg| { - std.debug.print("\n\rUncaught {s}\n\r", .{msg}); - alloc.free(msg); - } - loop.cbk_error = false; + std.log.err("JS error", .{}); + // if (try try_catch.exception(alloc, js_env.*)) |msg| { + // std.debug.print("\n\rUncaught {s}\n\r", .{msg}); + // alloc.free(msg); + // } + // loop.cbk_error = false; } if (cmd.err) |err| { if (err != error.NoError) std.log.err("Server error: {any}", .{err}); From 9e13ffb8ffe8715287e68266eebfcf9625f5a3a2 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 19 Apr 2024 17:11:31 +0200 Subject: [PATCH 044/117] Add sendEvent utility function Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 10 ++++---- src/cdp/page.zig | 2 ++ src/cdp/runtime.zig | 58 ++++++++++++++++++++++++++++++++++++++++++++- src/cdp/target.zig | 20 ++-------------- 4 files changed, 67 insertions(+), 23 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 30a94594..a2956513 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -129,14 +129,14 @@ pub fn result( return stringify(alloc, resp); } -// caller owns the slice returned -pub fn method( +pub fn sendEvent( alloc: std.mem.Allocator, + ctx: *Ctx, name: []const u8, comptime T: type, params: T, sessionID: ?[]const u8, -) ![]const u8 { +) !void { const Resp = struct { method: []const u8, params: T, @@ -144,7 +144,9 @@ pub fn method( }; const resp = Resp{ .method = name, .params = params, .sessionId = sessionID }; - return stringify(alloc, resp); + const event_msg = try stringify(alloc, resp); + std.log.debug("event {s}", .{event_msg}); + try server.sendSync(ctx, event_msg); } pub fn getParams( diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 1292186f..fd4bdfb9 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -168,6 +168,8 @@ fn navigate( ctx.state.url = content.params.url; ctx.state.loaderID = "AF8667A203C5392DBE9AC290044AA4C2"; + var page = try ctx.browser.currentSession().createPage(); + // output const Resp = struct { frameId: []const u8, diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index d5db891a..b1ebc555 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -31,12 +31,68 @@ fn enable( alloc: std.mem.Allocator, id: u64, scanner: *std.json.Scanner, - _: *Ctx, + ctx: *Ctx, ) ![]const u8 { + _ = ctx; + + // input const sessionID = try cdp.getSessionID(scanner); + + // output + // const uniqueID = "1367118932354479079.-1471398151593995849"; + // const mainCtx = try executionContextCreated( + // alloc, + // 1, + // cdp.URLBase, + // "", + // uniqueID, + // .{}, + // sessionID, + // ); + // std.log.debug("res {s}", .{mainCtx}); + // try server.sendAsync(ctx, mainCtx); + return result(alloc, id, null, null, sessionID); } +const AuxData = struct { + isDefault: bool = true, + type: []const u8 = "default", + frameId: []const u8 = cdp.FrameID, +}; + +const ExecutionContextDescription = struct { + id: u64, + origin: []const u8, + name: []const u8, + uniqueId: []const u8, + auxData: ?AuxData = null, +}; + +fn executionContextCreated( + alloc: std.mem.Allocator, + id: u64, + origin: []const u8, + name: []const u8, + uniqueID: []const u8, + auxData: ?AuxData, + sessionID: ?[]const u8, +) ![]const u8 { + const Params = struct { + context: ExecutionContextDescription, + }; + const params = Params{ + .context = .{ + .id = id, + .origin = origin, + .name = name, + .uniqueId = uniqueID, + .auxData = auxData, + }, + }; + return try cdp.method(alloc, "Runtime.executionContextCreated", Params, params, sessionID); +} + fn runIfWaitingForDebugger( alloc: std.mem.Allocator, id: u64, diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 5a6ab47a..67fd6474 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -81,15 +81,7 @@ fn tagetSetAutoAttach( .browserContextId = BrowserContextID, }, }; - const event = try cdp.method( - alloc, - "Target.attachedToTarget", - AttachToTarget, - attached, - null, - ); - std.log.debug("event {s}", .{event}); - try server.sendSync(ctx, event); + try cdp.sendEvent(alloc, ctx, "Target.attachedToTarget", AttachToTarget, attached, null); } return result(alloc, id, null, null, sessionID); @@ -196,15 +188,7 @@ fn createTarget( }, .waitingForDebugger = true, }; - const event = try cdp.method( - alloc, - "Target.attachedToTarget", - AttachToTarget, - attached, - sessionID, - ); - std.log.debug("event {s}", .{event}); - try server.sendSync(ctx, event); + try cdp.sendEvent(alloc, ctx, "Target.attachedToTarget", AttachToTarget, attached, sessionID); // output const Resp = struct { From 1b1b7cdfb0d7dfdaf0d1bc0f0b5ecaa0d25ae4af Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 19 Apr 2024 17:12:37 +0200 Subject: [PATCH 045/117] Add page_life_cycle_events in CDP state Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 2 ++ src/cdp/page.zig | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index a2956513..fa7ebd67 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -77,6 +77,8 @@ pub const State = struct { securityOrigin: []const u8 = URLBase, secureContextType: []const u8 = "Secure", // TODO: enum loaderID: []const u8 = LoaderID, + + page_life_cycle_events: bool = false, // TODO; Target based value }; // Utils diff --git a/src/cdp/page.zig b/src/cdp/page.zig index fd4bdfb9..f2cefb4a 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -89,7 +89,7 @@ fn setLifecycleEventsEnabled( alloc: std.mem.Allocator, id: u64, scanner: *std.json.Scanner, - _: *Ctx, + ctx: *Ctx, ) ![]const u8 { // input @@ -99,8 +99,9 @@ fn setLifecycleEventsEnabled( _ = try getParams(alloc, Params, scanner); const sessionID = try cdp.getSessionID(scanner); + ctx.state.page_life_cycle_events = true; + // output - // TODO: dummy return result(alloc, id, null, null, sessionID); } From 4a31dd8aa3721f72a0d640cdb440539e4e431cfe Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 19 Apr 2024 17:13:32 +0200 Subject: [PATCH 046/117] Let Page.navigate do actually navigation Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 4 ++ src/cdp/page.zig | 116 +++++++++++++++++++++++++++++++++++++++-------- src/server.zig | 7 ++- 3 files changed, 106 insertions(+), 21 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index fa7ebd67..548eafbe 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -262,3 +262,7 @@ pub const SessionID = "9559320D92474062597D9875C664CAC0"; pub const URLBase = "chrome://newtab/"; pub const FrameID = "90D14BBD8AED408A0467AC93100BCDBE"; pub const LoaderID = "CFC8BED824DD2FD56CF1EF33C965C79C"; + +pub const TimestampEvent = struct { + timestamp: f64, +}; diff --git a/src/cdp/page.zig b/src/cdp/page.zig index f2cefb4a..f513f194 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -6,6 +6,7 @@ const cdp = @import("cdp.zig"); const result = cdp.result; const getParams = cdp.getParams; const stringify = cdp.stringify; +const sendEvent = cdp.sendEvent; const PageMethods = enum { enable, @@ -45,6 +46,21 @@ fn enable( return result(alloc, id, null, null, sessionID); } +const Frame = struct { + id: []const u8, + loaderId: []const u8, + url: []const u8, + domainAndRegistry: []const u8 = "", + securityOrigin: []const u8, + mimeType: []const u8 = "text/html", + adFrameStatus: struct { + adFrameType: []const u8 = "none", + } = .{}, + secureContextType: []const u8, + crossOriginIsolatedContextType: []const u8 = "NotIsolated", + gatedAPIFeatures: [][]const u8 = &[0][]const u8{}, +}; + fn getFrameTree( alloc: std.mem.Allocator, id: u64, @@ -54,20 +70,7 @@ fn getFrameTree( const sessionID = try cdp.getSessionID(scanner); const FrameTree = struct { frameTree: struct { - frame: struct { - id: []const u8, - loaderId: []const u8, - url: []const u8, - domainAndRegistry: []const u8 = "", - securityOrigin: []const u8, - mimeType: []const u8 = "text/html", - adFrameStatus: struct { - adFrameType: []const u8 = "none", - } = .{}, - secureContextType: []const u8, - crossOriginIsolatedContextType: []const u8 = "NotIsolated", - gatedAPIFeatures: [][]const u8 = &[0][]const u8{}, - }, + frame: Frame, }, childFrames: ?[]@This() = null, }; @@ -105,6 +108,13 @@ fn setLifecycleEventsEnabled( return result(alloc, id, null, null, sessionID); } +const LifeCycleEvent = struct { + frameId: []const u8, + loaderId: ?[]const u8, + name: []const u8 = undefined, + timestamp: f32 = undefined, +}; + fn addScriptToEvaluateOnNewDocument( alloc: std.mem.Allocator, id: u64, @@ -162,14 +172,31 @@ fn navigate( frameId: ?[]const u8 = null, referrerPolicy: ?[]const u8 = null, // TODO: enum }; - const content = try cdp.getContent(alloc, Params, scanner); - std.debug.assert(content.sessionID != null); + const input = try cdp.getContent(alloc, Params, scanner); + const sessionID = input.sessionID; + std.debug.assert(sessionID != null); // change state - ctx.state.url = content.params.url; + ctx.state.url = input.params.url; ctx.state.loaderID = "AF8667A203C5392DBE9AC290044AA4C2"; - var page = try ctx.browser.currentSession().createPage(); + var life_event = LifeCycleEvent{ + .frameId = ctx.state.frameID, + .loaderId = ctx.state.loaderID, + }; + var ts_event: cdp.TimestampEvent = undefined; + + // frameStartedLoading event + const FrameStartedLoading = struct { + frameId: []const u8, + }; + const frame_started_loading = FrameStartedLoading{ .frameId = ctx.state.frameID }; + try sendEvent(alloc, ctx, "Page.frameStartedLoading", FrameStartedLoading, frame_started_loading, sessionID); + if (ctx.state.page_life_cycle_events) { + life_event.name = "init"; + life_event.timestamp = 343721.796037; + try sendEvent(alloc, ctx, "Page.lifeCycleEvent", LifeCycleEvent, life_event, sessionID); + } // output const Resp = struct { @@ -181,5 +208,56 @@ fn navigate( .frameId = ctx.state.frameID, .loaderId = ctx.state.loaderID, }; - return result(alloc, id, Resp, resp, content.sessionID); + const res = try result(alloc, id, Resp, resp, sessionID); + std.log.debug("res {s}", .{res}); + try server.sendSync(ctx, res); + + // launch navigate + var p = try ctx.browser.currentSession().createPage(); + _ = try p.navigate(input.params.url); + + // frameNavigated event + const FrameNavigated = struct { + frame: Frame, + type: []const u8 = "Navigation", + }; + const frame_navigated = FrameNavigated{ + .frame = .{ + .id = ctx.state.frameID, + .url = ctx.state.url, + .securityOrigin = ctx.state.securityOrigin, + .secureContextType = ctx.state.secureContextType, + .loaderId = ctx.state.loaderID, + }, + }; + try sendEvent(alloc, ctx, "Page.frameNavigated", FrameNavigated, frame_navigated, sessionID); + if (ctx.state.page_life_cycle_events) { + life_event.name = "load"; + life_event.timestamp = 343721.824655; + try sendEvent(alloc, ctx, "Page.lifeCycleEvent", LifeCycleEvent, life_event, sessionID); + } + + // domContentEventFired event + ts_event = .{ .timestamp = 343721.803338 }; + try sendEvent(alloc, ctx, "Page.domContentEventFired", cdp.TimestampEvent, ts_event, sessionID); + if (ctx.state.page_life_cycle_events) { + life_event.name = "DOMContentLoaded"; + life_event.timestamp = 343721.803338; + try sendEvent(alloc, ctx, "Page.lifeCycleEvent", LifeCycleEvent, life_event, sessionID); + } + + // loadEventFired event + ts_event = .{ .timestamp = 343721.824655 }; + try sendEvent(alloc, ctx, "Page.loadEventFired", cdp.TimestampEvent, ts_event, sessionID); + if (ctx.state.page_life_cycle_events) { + life_event.name = "load"; + life_event.timestamp = 343721.824655; + try sendEvent(alloc, ctx, "Page.lifeCycleEvent", LifeCycleEvent, life_event, sessionID); + } + + // frameStoppedLoading + const FrameStoppedLoading = struct { frameId: []const u8 }; + try sendEvent(alloc, ctx, "Page.frameStoppedLoading", FrameStoppedLoading, .{ .frameId = ctx.state.frameID }, sessionID); + + return ""; } diff --git a/src/server.zig b/src/server.zig index 48ddcc72..4798c158 100644 --- a/src/server.zig +++ b/src/server.zig @@ -76,9 +76,12 @@ pub const Cmd = struct { } @panic(@errorName(err)); }; - std.log.debug("res {s}", .{res}); - sendAsync(self, res) catch unreachable; + // send result + if (!std.mem.eql(u8, res, "")) { + std.log.debug("res {s}", .{res}); + sendAsync(self, res) catch unreachable; + } if (pos == null) break; From 7a03562a33ed12477470f882e1d30d39c2f6c6a6 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 23 Apr 2024 10:43:11 +0200 Subject: [PATCH 047/117] Typo fix Page.LifecycleEvent Signed-off-by: Francis Bouvier --- src/cdp/page.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cdp/page.zig b/src/cdp/page.zig index f513f194..84fc57ba 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -108,7 +108,7 @@ fn setLifecycleEventsEnabled( return result(alloc, id, null, null, sessionID); } -const LifeCycleEvent = struct { +const LifecycleEvent = struct { frameId: []const u8, loaderId: ?[]const u8, name: []const u8 = undefined, @@ -180,7 +180,7 @@ fn navigate( ctx.state.url = input.params.url; ctx.state.loaderID = "AF8667A203C5392DBE9AC290044AA4C2"; - var life_event = LifeCycleEvent{ + var life_event = LifecycleEvent{ .frameId = ctx.state.frameID, .loaderId = ctx.state.loaderID, }; @@ -195,7 +195,7 @@ fn navigate( if (ctx.state.page_life_cycle_events) { life_event.name = "init"; life_event.timestamp = 343721.796037; - try sendEvent(alloc, ctx, "Page.lifeCycleEvent", LifeCycleEvent, life_event, sessionID); + try sendEvent(alloc, ctx, "Page.lifecycleEvent", LifecycleEvent, life_event, sessionID); } // output @@ -234,7 +234,7 @@ fn navigate( if (ctx.state.page_life_cycle_events) { life_event.name = "load"; life_event.timestamp = 343721.824655; - try sendEvent(alloc, ctx, "Page.lifeCycleEvent", LifeCycleEvent, life_event, sessionID); + try sendEvent(alloc, ctx, "Page.lifecycleEvent", LifecycleEvent, life_event, sessionID); } // domContentEventFired event @@ -243,7 +243,7 @@ fn navigate( if (ctx.state.page_life_cycle_events) { life_event.name = "DOMContentLoaded"; life_event.timestamp = 343721.803338; - try sendEvent(alloc, ctx, "Page.lifeCycleEvent", LifeCycleEvent, life_event, sessionID); + try sendEvent(alloc, ctx, "Page.lifecycleEvent", LifecycleEvent, life_event, sessionID); } // loadEventFired event @@ -252,7 +252,7 @@ fn navigate( if (ctx.state.page_life_cycle_events) { life_event.name = "load"; life_event.timestamp = 343721.824655; - try sendEvent(alloc, ctx, "Page.lifeCycleEvent", LifeCycleEvent, life_event, sessionID); + try sendEvent(alloc, ctx, "Page.lifecycleEvent", LifecycleEvent, life_event, sessionID); } // frameStoppedLoading From 28d5c682cd750554be18e9ee6d390efd0cf7ad03 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 23 Apr 2024 10:44:03 +0200 Subject: [PATCH 048/117] Use sendEvent in Runtime.executionContextCreated and expose it Signed-off-by: Francis Bouvier --- src/cdp/runtime.zig | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index b1ebc555..1aa5b7cd 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -55,7 +55,7 @@ fn enable( return result(alloc, id, null, null, sessionID); } -const AuxData = struct { +pub const AuxData = struct { isDefault: bool = true, type: []const u8 = "default", frameId: []const u8 = cdp.FrameID, @@ -69,15 +69,16 @@ const ExecutionContextDescription = struct { auxData: ?AuxData = null, }; -fn executionContextCreated( +pub fn executionContextCreated( alloc: std.mem.Allocator, + ctx: *Ctx, id: u64, origin: []const u8, name: []const u8, uniqueID: []const u8, auxData: ?AuxData, sessionID: ?[]const u8, -) ![]const u8 { +) !void { const Params = struct { context: ExecutionContextDescription, }; @@ -90,7 +91,7 @@ fn executionContextCreated( .auxData = auxData, }, }; - return try cdp.method(alloc, "Runtime.executionContextCreated", Params, params, sessionID); + try cdp.sendEvent(alloc, ctx, "Runtime.executionContextCreated", Params, params, sessionID); } fn runIfWaitingForDebugger( From 3396c70b676bbff09e7864d70779d765145964b4 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 23 Apr 2024 10:44:33 +0200 Subject: [PATCH 049/117] Send Runtime.executionContextCreated events in Page.navigate Signed-off-by: Francis Bouvier --- src/cdp/page.zig | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 84fc57ba..60ec80e7 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -8,6 +8,8 @@ const getParams = cdp.getParams; const stringify = cdp.stringify; const sendEvent = cdp.sendEvent; +const Runtime = @import("runtime.zig"); + const PageMethods = enum { enable, getFrameTree, @@ -212,6 +214,9 @@ fn navigate( std.log.debug("res {s}", .{res}); try server.sendSync(ctx, res); + // Runtime.executionContextsCleared event + try sendEvent(alloc, ctx, "Runtime.executionContextsCleared", void, {}, sessionID); + // launch navigate var p = try ctx.browser.currentSession().createPage(); _ = try p.navigate(input.params.url); @@ -237,6 +242,28 @@ fn navigate( try sendEvent(alloc, ctx, "Page.lifecycleEvent", LifecycleEvent, life_event, sessionID); } + try Runtime.executionContextCreated( + alloc, + ctx, + 3, + "http://127.0.0.1:1234", + "", + "7102379147004877974.3265385113993241162", + .{ .frameId = ctx.state.frameID }, + sessionID, + ); + + try Runtime.executionContextCreated( + alloc, + ctx, + 4, + "://", + "__playwright_utility_world__", + "-4572718120346458707.6016875269626438350", + .{ .isDefault = false, .type = "isolated", .frameId = ctx.state.frameID }, + sessionID, + ); + // domContentEventFired event ts_event = .{ .timestamp = 343721.803338 }; try sendEvent(alloc, ctx, "Page.domContentEventFired", cdp.TimestampEvent, ts_event, sessionID); From 96906df64b800c67327e2898fef602b1f5656bec Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 23 Apr 2024 12:48:35 +0200 Subject: [PATCH 050/117] Implement own protocol to handle msg size Signed-off-by: Francis Bouvier --- src/server.zig | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/server.zig b/src/server.zig index 4798c158..cbbad1be 100644 --- a/src/server.zig +++ b/src/server.zig @@ -12,7 +12,8 @@ const Browser = @import("browser/browser.zig").Browser; const cdp = @import("cdp/cdp.zig"); const NoError = error{NoError}; -const Error = AcceptError || RecvError || SendError || TimeoutError || cdp.Error || NoError; +const IOError = AcceptError || RecvError || SendError || TimeoutError; +const Error = IOError || std.fmt.ParseIntError || cdp.Error || NoError; // I/O Recv // -------- @@ -56,16 +57,35 @@ pub const Cmd = struct { } // cmds - var cmd: []const u8 = undefined; while (true) { + // parse json msg size + const size_pos = std.mem.indexOfScalar(u8, input, ':').?; + std.log.debug("msg size pos: {d}", .{size_pos}); + const size_str = input[0..size_pos]; + input = input[size_pos + 1 ..]; + const size_msg = std.fmt.parseInt(u32, size_str, 10) catch |err| { + self.err = err; + return; + }; + std.log.debug("msg size: {d}", .{size_msg}); + + // part + const is_part = input.len < size_msg; + std.log.debug("is_part: {any}", .{is_part}); + if (is_part) { + std.log.debug("size_msg {d}, input {d}", .{ size_msg, input.len }); + @panic("part msg"); // TODO: implement part + } + // handle several JSON msg in 1 read - const pos = std.mem.indexOf(u8, input, "}{"); - if (pos) |p| { - cmd = input[0 .. p + 1]; - input = input[p + 1 ..]; - } else { - cmd = input; + const is_multi = input.len > size_msg; + std.log.debug("is_multi: {any}", .{is_multi}); + const cmd = input[0..size_msg]; + std.log.debug("cmd: {s}", .{cmd}); + if (is_multi) { + input = input[size_msg..]; + std.log.debug("rest: {s}", .{input}); } // cdp @@ -83,7 +103,7 @@ pub const Cmd = struct { sendAsync(self, res) catch unreachable; } - if (pos == null) break; + if (!is_multi) break; // TODO: handle 1 read smaller than a complete JSON msg } From ba12945e5b062134206107f5cfe3dbe075ff3c4c Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 24 Apr 2024 11:17:55 +0200 Subject: [PATCH 051/117] Move read input from Cmd callback to allow unit tests Signed-off-by: Francis Bouvier --- src/server.zig | 123 ++++++++++++++++++++++++++++--------------------- 1 file changed, 71 insertions(+), 52 deletions(-) diff --git a/src/server.zig b/src/server.zig index cbbad1be..721c2b2f 100644 --- a/src/server.zig +++ b/src/server.zig @@ -45,7 +45,7 @@ pub const Cmd = struct { } // input - var input = self.buf[0..size]; + const input = self.buf[0..size]; if (std.log.defaultLogEnabled(.debug)) { std.debug.print("\ninput size: {d}, content: {s}\n", .{ size, input }); } @@ -56,57 +56,8 @@ pub const Cmd = struct { return; } - // cmds - while (true) { - - // parse json msg size - const size_pos = std.mem.indexOfScalar(u8, input, ':').?; - std.log.debug("msg size pos: {d}", .{size_pos}); - const size_str = input[0..size_pos]; - input = input[size_pos + 1 ..]; - const size_msg = std.fmt.parseInt(u32, size_str, 10) catch |err| { - self.err = err; - return; - }; - std.log.debug("msg size: {d}", .{size_msg}); - - // part - const is_part = input.len < size_msg; - std.log.debug("is_part: {any}", .{is_part}); - if (is_part) { - std.log.debug("size_msg {d}, input {d}", .{ size_msg, input.len }); - @panic("part msg"); // TODO: implement part - } - - // handle several JSON msg in 1 read - const is_multi = input.len > size_msg; - std.log.debug("is_multi: {any}", .{is_multi}); - const cmd = input[0..size_msg]; - std.log.debug("cmd: {s}", .{cmd}); - if (is_multi) { - input = input[size_msg..]; - std.log.debug("rest: {s}", .{input}); - } - - // cdp - const res = cdp.do(self.alloc(), cmd, self) catch |err| { - if (cdp.isCdpError(err)) |e| { - self.err = e; - return; - } - @panic(@errorName(err)); - }; - - // send result - if (!std.mem.eql(u8, res, "")) { - std.log.debug("res {s}", .{res}); - sendAsync(self, res) catch unreachable; - } - - if (!is_multi) break; - - // TODO: handle 1 read smaller than a complete JSON msg - } + // read and execute input + readInput(input, Cmd.do, self) catch unreachable; // continue receving incomming messages asynchronously self.loop().io.recv(*Cmd, self, cbk, completion, self.socket, self.buf); @@ -122,8 +73,76 @@ pub const Cmd = struct { // TODO: pointer instead? return self.browser.currentSession().loop; } + + fn do(self: *Cmd, cmd: []const u8) !void { + const res = try cdp.do(self.alloc(), cmd, self); + + // send result + if (!std.mem.eql(u8, res, "")) { + std.log.debug("res {s}", .{res}); + return sendAsync(self, res); + } + } }; +fn readInput(buf: []const u8, func: anytype, data: anytype) !void { + var input = buf; + + while (true) { + var cmd: []const u8 = undefined; + + // size msg + var size_msg: usize = undefined; + + // parse json msg size + const size_pos = std.mem.indexOfScalar(u8, input, ':').?; + std.log.debug("msg size pos: {d}", .{size_pos}); + const size_str = input[0..size_pos]; + input = input[size_pos + 1 ..]; + size_msg = try std.fmt.parseInt(u32, size_str, 10); + // } + std.log.debug("msg size: {d}", .{size_msg}); + + // handle several JSON msg in 1 read + const is_multi = input.len > size_msg; + std.log.debug("is_multi: {any}", .{is_multi}); + cmd = input[0..size_msg]; + std.log.debug("cmd: {s}", .{cmd[0..@min(BufReadSize, size_msg)]}); + if (is_multi) { + input = input[size_msg..]; + std.log.debug("rest: {s}", .{input}); + } + + try @call(.auto, func, .{ data, cmd }); + + if (!is_multi) break; + + // TODO: handle 1 read smaller than a complete JSON msg + } +} + +fn doTest(nb: *u8, _: []const u8) anyerror!void { + nb.* += 1; +} + +test { + const Case = struct { + input: []const u8, + nb: u8, + }; + const cases = [_]Case{ + // simple + .{ .input = "2:ok", .nb = 1 }, + // multi + .{ .input = "2:ok3:foo7:bar2:ok", .nb = 3 }, // "bar2:ok" is a message, no need to escape "2:" here + }; + for (cases) |case| { + var nb: u8 = 0; + try readInput(case.input, doTest, &nb); + try std.testing.expect(nb == case.nb); + } +} + // I/O Send // -------- From bafdca3ffa61bade57a55097a295fc2830912b61 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 22 May 2024 16:24:39 +0200 Subject: [PATCH 052/117] MsgBuffer to handle both combined and multipart read Signed-off-by: Francis Bouvier --- src/server.zig | 186 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 144 insertions(+), 42 deletions(-) diff --git a/src/server.zig b/src/server.zig index 721c2b2f..65d32543 100644 --- a/src/server.zig +++ b/src/server.zig @@ -18,6 +18,8 @@ const Error = IOError || std.fmt.ParseIntError || cdp.Error || NoError; // I/O Recv // -------- +const BufReadSize = 1024; + pub const Cmd = struct { // internal fields @@ -25,6 +27,8 @@ pub const Cmd = struct { buf: []u8, // only for read operations err: ?Error = null, + msg_buf: *MsgBuffer, + // CDP state: cdp.State = .{}, @@ -57,7 +61,7 @@ pub const Cmd = struct { } // read and execute input - readInput(input, Cmd.do, self) catch unreachable; + self.msg_buf.read(self.alloc(), input, self, Cmd.do) catch unreachable; // continue receving incomming messages asynchronously self.loop().io.recv(*Cmd, self, cbk, completion, self.socket, self.buf); @@ -74,7 +78,7 @@ pub const Cmd = struct { return self.browser.currentSession().loop; } - fn do(self: *Cmd, cmd: []const u8) !void { + fn do(self: *Cmd, cmd: []const u8) anyerror!void { const res = try cdp.do(self.alloc(), cmd, self); // send result @@ -85,60 +89,153 @@ pub const Cmd = struct { } }; -fn readInput(buf: []const u8, func: anytype, data: anytype) !void { - var input = buf; +/// MsgBuffer return messages from a raw text read stream, +/// according to the following format `:`. +/// It handles both: +/// - combined messages in one read +/// - single message in several read (multipart) +/// It is safe (and good practice) to reuse the same MsgBuffer +/// on several reads of the same stream. +const MsgBuffer = struct { + size: usize = 0, + buf: []u8, + pos: usize = 0, - while (true) { - var cmd: []const u8 = undefined; - - // size msg - var size_msg: usize = undefined; - - // parse json msg size - const size_pos = std.mem.indexOfScalar(u8, input, ':').?; - std.log.debug("msg size pos: {d}", .{size_pos}); - const size_str = input[0..size_pos]; - input = input[size_pos + 1 ..]; - size_msg = try std.fmt.parseInt(u32, size_str, 10); - // } - std.log.debug("msg size: {d}", .{size_msg}); - - // handle several JSON msg in 1 read - const is_multi = input.len > size_msg; - std.log.debug("is_multi: {any}", .{is_multi}); - cmd = input[0..size_msg]; - std.log.debug("cmd: {s}", .{cmd[0..@min(BufReadSize, size_msg)]}); - if (is_multi) { - input = input[size_msg..]; - std.log.debug("rest: {s}", .{input}); - } - - try @call(.auto, func, .{ data, cmd }); - - if (!is_multi) break; - - // TODO: handle 1 read smaller than a complete JSON msg + fn init(alloc: std.mem.Allocator, size: usize) std.mem.Allocator.Error!MsgBuffer { + const buf = try alloc.alloc(u8, size); + return .{ .buf = buf }; } -} + + fn deinit(self: MsgBuffer, alloc: std.mem.Allocator) void { + alloc.free(self.buf); + } + + fn isFinished(self: *MsgBuffer) bool { + return self.pos >= self.size; + } + + fn isEmpty(self: MsgBuffer) bool { + return self.size == 0 and self.pos == 0; + } + + fn reset(self: *MsgBuffer) void { + self.size = 0; + self.pos = 0; + } + + // read input + // - `do_func` is a callback to execute on each message of the input + // - `data` is a arbitrary payload that will be passed to the callback along with + // the message itself + fn read( + self: *MsgBuffer, + alloc: std.mem.Allocator, + input: []const u8, + data: anytype, + comptime do_func: fn (data: @TypeOf(data), msg: []const u8) anyerror!void, + ) !void { + var _input = input; // make input writable + + while (true) { + var msg: []const u8 = undefined; + + // msg size + var msg_size: usize = undefined; + if (self.isEmpty()) { + // parse msg size metadata + const size_pos = std.mem.indexOfScalar(u8, _input, ':').?; + const size_str = _input[0..size_pos]; + msg_size = try std.fmt.parseInt(u32, size_str, 10); + _input = _input[size_pos + 1 ..]; + } else { + msg_size = self.size; + } + + // multipart + const is_multipart = !self.isEmpty() or _input.len < msg_size; + if (is_multipart) { + + // set msg size on empty MsgBuffer + if (self.isEmpty()) { + self.size = msg_size; + } + + // get the new position of the cursor + const new_pos = self.pos + _input.len; + + // check if the current input can fit in MsgBuffer + if (new_pos > self.buf.len) { + // max_size is the max between msg size and current new cursor position + const max_size = @max(self.size, new_pos); + // resize the MsgBuffer to fit + self.buf = try alloc.realloc(self.buf, max_size); + } + + // copy the current input into MsgBuffer + @memcpy(self.buf[self.pos..new_pos], _input[0..]); + + // set the new cursor position + self.pos = new_pos; + + // if multipart is not finished, go fetch the next input + if (!self.isFinished()) return; + + // otherwhise multipart is finished, use its buffer as input + _input = self.buf[0..self.pos]; + self.reset(); + } + + // handle several JSON msg in 1 read + const is_combined = _input.len > msg_size; + msg = _input[0..msg_size]; + std.log.debug("msg: {s}", .{msg[0..@min(BufReadSize, msg_size)]}); + if (is_combined) { + _input = _input[msg_size..]; + } + + try @call(.auto, do_func, .{ data, msg }); + + if (!is_combined) break; + } + } +}; fn doTest(nb: *u8, _: []const u8) anyerror!void { nb.* += 1; } -test { +test "MsgBuffer" { const Case = struct { input: []const u8, nb: u8, }; + const alloc = std.testing.allocator; const cases = [_]Case{ // simple .{ .input = "2:ok", .nb = 1 }, - // multi + // combined .{ .input = "2:ok3:foo7:bar2:ok", .nb = 3 }, // "bar2:ok" is a message, no need to escape "2:" here + // multipart + .{ .input = "9:multi", .nb = 0 }, + .{ .input = "part", .nb = 1 }, + // multipart & combined + .{ .input = "9:multi", .nb = 0 }, + .{ .input = "part2:ok", .nb = 2 }, + // several multipart + .{ .input = "23:multi", .nb = 0 }, + .{ .input = "several", .nb = 0 }, + .{ .input = "complex", .nb = 0 }, + .{ .input = "part", .nb = 1 }, + // combined & multipart + .{ .input = "2:ok9:multi", .nb = 1 }, + .{ .input = "part", .nb = 1 }, }; + var nb: u8 = undefined; + var msg_buf = try MsgBuffer.init(alloc, 10); + defer msg_buf.deinit(alloc); for (cases) |case| { - var nb: u8 = 0; - try readInput(case.input, doTest, &nb); + nb = 0; + try msg_buf.read(alloc, case.input, &nb, doTest); try std.testing.expect(nb == case.nb); } } @@ -232,14 +329,20 @@ const Accept = struct { // ------ pub fn listen(browser: *Browser, socket: std.os.socket_t) anyerror!void { + const loop = browser.currentSession().loop; + + // MsgBuffer + var msg_buf = try MsgBuffer.init(loop.alloc, BufReadSize); + defer msg_buf.deinit(loop.alloc); // create I/O contexts and callbacks // for accepting connections and receving messages - var input: [1024]u8 = undefined; + var ctxInput: [BufReadSize]u8 = undefined; var cmd = Cmd{ .browser = browser, .socket = undefined, - .buf = &input, + .buf = &ctxInput, + .msg_buf = &msg_buf, }; var accept = Accept{ .cmd = &cmd, @@ -247,7 +350,6 @@ pub fn listen(browser: *Browser, socket: std.os.socket_t) anyerror!void { }; // accepting connection asynchronously on internal server - const loop = browser.currentSession().loop; var completion: Completion = undefined; loop.io.accept(*Accept, &accept, Accept.cbk, &completion, socket); From c57e50c5b907d25ebac0fc4780e724a97fae295d Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Mon, 27 May 2024 16:02:14 +0200 Subject: [PATCH 053/117] Handle Runtime.evaluate (no-op) Signed-off-by: Francis Bouvier --- src/cdp/runtime.zig | 24 ++++++++++++++++++++++++ src/server.zig | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index 1aa5b7cd..722f9332 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -10,6 +10,7 @@ const stringify = cdp.stringify; const RuntimeMethods = enum { enable, runIfWaitingForDebugger, + evaluate, }; pub fn runtime( @@ -24,6 +25,7 @@ pub fn runtime( return switch (method) { .enable => enable(alloc, id, scanner, ctx), .runIfWaitingForDebugger => runIfWaitingForDebugger(alloc, id, scanner, ctx), + .evaluate => evaluate(alloc, id, scanner, ctx), }; } @@ -103,3 +105,25 @@ fn runIfWaitingForDebugger( const sessionID = try cdp.getSessionID(scanner); return result(alloc, id, null, null, sessionID); } + +fn evaluate( + alloc: std.mem.Allocator, + _: u64, + scanner: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + + // input + const Params = struct { + expression: []const u8, + contextId: ?u8, + }; + + const input = try cdp.getContent(alloc, Params, scanner); + const sessionID = input.sessionID; + std.debug.assert(sessionID != null); + + std.log.debug("expr: len {d}", .{input.params.expression.len}); + + return error.CDPNormal; +} diff --git a/src/server.zig b/src/server.zig index 65d32543..0170d7d1 100644 --- a/src/server.zig +++ b/src/server.zig @@ -18,7 +18,7 @@ const Error = IOError || std.fmt.ParseIntError || cdp.Error || NoError; // I/O Recv // -------- -const BufReadSize = 1024; +const BufReadSize = 1024; // 1KB pub const Cmd = struct { @@ -332,7 +332,7 @@ pub fn listen(browser: *Browser, socket: std.os.socket_t) anyerror!void { const loop = browser.currentSession().loop; // MsgBuffer - var msg_buf = try MsgBuffer.init(loop.alloc, BufReadSize); + var msg_buf = try MsgBuffer.init(loop.alloc, BufReadSize * 256); // 256KB defer msg_buf.deinit(loop.alloc); // create I/O contexts and callbacks From bfb9db235e63f82db2edf9439332905b9c74218a Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 30 May 2024 16:21:18 +0200 Subject: [PATCH 054/117] Basic Runtime.evaluate run Signed-off-by: Francis Bouvier --- src/cdp/runtime.zig | 51 +++++++++++++++++++++++++++++++++++++++++---- src/server.zig | 6 ++++-- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index 722f9332..857a1f72 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -1,5 +1,7 @@ const std = @import("std"); +const jsruntime = @import("jsruntime"); + const server = @import("../server.zig"); const Ctx = server.Cmd; const cdp = @import("cdp.zig"); @@ -108,11 +110,14 @@ fn runIfWaitingForDebugger( fn evaluate( alloc: std.mem.Allocator, - _: u64, + id: u64, scanner: *std.json.Scanner, - _: *Ctx, + ctx: *Ctx, ) ![]const u8 { + // ensure a page has been previously created + if (ctx.browser.currentSession().page == null) return error.CDPNoPage; + // input const Params = struct { expression: []const u8, @@ -123,7 +128,45 @@ fn evaluate( const sessionID = input.sessionID; std.debug.assert(sessionID != null); - std.log.debug("expr: len {d}", .{input.params.expression.len}); + // save script in file at debug mode + std.log.debug("script {d} length: {d}", .{ id, input.params.expression.len }); + if (std.log.defaultLogEnabled(.debug)) { + const name = try std.fmt.allocPrint(alloc, "id_{d}.js", .{id}); + defer alloc.free(name); + const dir = try std.fs.cwd().makeOpenPath("zig-cache/tmp", .{}); + const f = try dir.createFile(name, .{}); + defer f.close(); + const nb = try f.write(input.params.expression); + std.debug.assert(nb == input.params.expression.len); + const p = try dir.realpathAlloc(alloc, name); + defer alloc.free(p); + std.log.debug("Script {d} saved at {s}", .{ id, p }); + } - return error.CDPNormal; + // evaluate the script in the context of the current page + // TODO: should we use instead the allocator of the page? + // the following code does not work + // const page_alloc = ctx.browser.currentSession().page.?.arena.allocator(); + const session_alloc = ctx.browser.currentSession().alloc; + var res = jsruntime.JSResult{}; + try ctx.browser.currentSession().env.run(session_alloc, input.params.expression, "cdp", &res, null); + defer res.deinit(session_alloc); + + if (!res.success) { + std.log.err("script {d} result: {s}", .{ id, res.result }); + if (res.stack) |stack| { + std.log.err("script {d} stack: {s}", .{ id, stack }); + } + return error.CDPRuntimeEvaluate; + } + std.log.debug("script {d} result: {s}", .{ id, res.result }); + + // TODO: Resp should depends on JS result returned by the JS engine + const Resp = struct { + type: []const u8 = "object", + className: []const u8 = "UtilityScript", + description: []const u8 = "UtilityScript", + objectId: []const u8 = "7481631759780215274.3.2", + }; + return result(alloc, id, Resp, Resp{}, sessionID); } diff --git a/src/server.zig b/src/server.zig index 0170d7d1..2943128b 100644 --- a/src/server.zig +++ b/src/server.zig @@ -19,6 +19,7 @@ const Error = IOError || std.fmt.ParseIntError || cdp.Error || NoError; // -------- const BufReadSize = 1024; // 1KB +const MaxStdOutSize = 512; // ensure debug msg are not too long pub const Cmd = struct { @@ -51,7 +52,8 @@ pub const Cmd = struct { // input const input = self.buf[0..size]; if (std.log.defaultLogEnabled(.debug)) { - std.debug.print("\ninput size: {d}, content: {s}\n", .{ size, input }); + const content = input[0..@min(MaxStdOutSize, size)]; + std.debug.print("\ninput size: {d}, content: {s}\n", .{ size, content }); } // close on exit command @@ -188,7 +190,7 @@ const MsgBuffer = struct { // handle several JSON msg in 1 read const is_combined = _input.len > msg_size; msg = _input[0..msg_size]; - std.log.debug("msg: {s}", .{msg[0..@min(BufReadSize, msg_size)]}); + std.log.debug("msg: {s}", .{msg[0..@min(MaxStdOutSize, msg_size)]}); if (is_combined) { _input = _input[msg_size..]; } From 3ad19dffa154d0b017ea89600005175493e565e9 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 30 May 2024 17:43:01 +0200 Subject: [PATCH 055/117] Handle CDP msg with order and Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 548eafbe..32a303e3 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -43,23 +43,43 @@ pub fn do( s: []const u8, ctx: *Ctx, ) ![]const u8 { + + // JSON scanner var scanner = std.json.Scanner.initCompleteInput(alloc, s); defer scanner.deinit(); std.debug.assert(try scanner.next() == .object_begin); - try checkKey("id", (try scanner.next()).string); - const id = try std.fmt.parseUnsigned(u64, (try scanner.next()).number, 10); - - try checkKey("method", (try scanner.next()).string); - const method_name = (try scanner.next()).string; + // handle 2 possible orders: + // - id, method <...> + // - method, id <...> + var id_key = (try scanner.next()).string; + var id_token = try scanner.next(); + var method_key = (try scanner.next()).string; + var method_token = try scanner.next(); + // check swap order + if (!std.mem.eql(u8, id_key, "id")) { + const swap_key = method_key; + const swap_token = method_token; + method_key = id_key; + method_token = id_token; + id_key = swap_key; + id_token = swap_token; + } + try checkKey(id_key, "id"); + try checkKey(method_key, "method"); + // retrieve id and method + const id = try std.fmt.parseUnsigned(u64, id_token.number, 10); + const method_name = method_token.string; std.log.debug("cmd: id {any}, method {s}", .{ id, method_name }); + // retrieve domain from method var iter = std.mem.splitScalar(u8, method_name, '.'); const domain = std.meta.stringToEnum(Domains, iter.first()) orelse return error.UnknonwDomain; + // select corresponding domain return switch (domain) { .Browser => browser(alloc, id, iter.next().?, &scanner, ctx), .Target => target(alloc, id, iter.next().?, &scanner, ctx), From dc1456f4e836cbcfc3645e511aa8429c6baeaae4 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 7 Jun 2024 15:59:57 +0200 Subject: [PATCH 056/117] Handle CDP messages with different order The 'method' still needs to be the first or the second key (in this case after the 'id'). Signed-off-by: Francis Bouvier --- src/cdp/browser.zig | 32 +++++---- src/cdp/cdp.zig | 156 +++++++++++++++++++----------------------- src/cdp/emulation.zig | 25 ++++--- src/cdp/log.zig | 16 +++-- src/cdp/network.zig | 16 +++-- src/cdp/page.zig | 140 ++++++++++++++++++++++++++----------- src/cdp/runtime.zig | 41 +++++------ src/cdp/target.zig | 40 +++++------ 8 files changed, 255 insertions(+), 211 deletions(-) diff --git a/src/cdp/browser.zig b/src/cdp/browser.zig index c0dff5d8..0d8dcd8a 100644 --- a/src/cdp/browser.zig +++ b/src/cdp/browser.zig @@ -4,7 +4,7 @@ const server = @import("../server.zig"); const Ctx = server.Cmd; const cdp = @import("cdp.zig"); const result = cdp.result; -const getParams = cdp.getParams; +const getMsg = cdp.getMsg; const BrowserMethods = enum { getVersion, @@ -15,7 +15,7 @@ const BrowserMethods = enum { pub fn browser( alloc: std.mem.Allocator, - id: u64, + id: ?u16, action: []const u8, scanner: *std.json.Scanner, ctx: *Ctx, @@ -38,10 +38,12 @@ const JsVersion = "12.4.254.8"; fn browserGetVersion( alloc: std.mem.Allocator, - id: u64, - _: *std.json.Scanner, + id: ?u16, + scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { + const msg = try getMsg(alloc, void, scanner); + const Res = struct { protocolVersion: []const u8, product: []const u8, @@ -57,12 +59,12 @@ fn browserGetVersion( .userAgent = UserAgent, .jsVersion = JsVersion, }; - return result(alloc, id, Res, res, null); + return result(alloc, id orelse msg.id.?, Res, res, null); } fn browserSetDownloadBehavior( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { @@ -72,15 +74,15 @@ fn browserSetDownloadBehavior( downloadPath: ?[]const u8 = null, eventsEnabled: ?bool = null, }; - _ = try getParams(alloc, Params, scanner); - return result(alloc, id, null, null, null); + const msg = try getMsg(alloc, Params, scanner); + return result(alloc, id orelse msg.id.?, null, null, null); } const DevToolsWindowID = 1923710101; fn getWindowForTarget( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { @@ -89,8 +91,8 @@ fn getWindowForTarget( const Params = struct { targetId: ?[]const u8 = null, }; - const content = try cdp.getContent(alloc, ?Params, scanner); - std.debug.assert(content.sessionID != null); + const msg = try cdp.getMsg(alloc, ?Params, scanner); + std.debug.assert(msg.sessionID != null); // output const Resp = struct { @@ -103,16 +105,16 @@ fn getWindowForTarget( windowState: []const u8 = "normal", } = .{}, }; - return result(alloc, id, Resp, Resp{}, content.sessionID.?); + return result(alloc, id orelse msg.id.?, Resp, Resp{}, msg.sessionID.?); } fn setWindowBounds( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { // NOTE: noop - const content = try cdp.getContent(alloc, void, scanner); - return result(alloc, id, null, null, content.sessionID); + const msg = try cdp.getMsg(alloc, void, scanner); + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 32a303e3..7853a804 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -53,26 +53,22 @@ pub fn do( // handle 2 possible orders: // - id, method <...> // - method, id <...> - var id_key = (try scanner.next()).string; - var id_token = try scanner.next(); var method_key = (try scanner.next()).string; - var method_token = try scanner.next(); + var method_token: std.json.Token = undefined; + var id: ?u16 = null; // check swap order - if (!std.mem.eql(u8, id_key, "id")) { - const swap_key = method_key; - const swap_token = method_token; - method_key = id_key; - method_token = id_token; - id_key = swap_key; - id_token = swap_token; + if (std.mem.eql(u8, method_key, "id")) { + id = try getId(&scanner, method_key); + method_key = (try scanner.next()).string; + method_token = try scanner.next(); + } else { + method_token = try scanner.next(); } - try checkKey(id_key, "id"); try checkKey(method_key, "method"); - // retrieve id and method - const id = try std.fmt.parseUnsigned(u64, id_token.number, 10); + // retrieve method const method_name = method_token.string; - std.log.debug("cmd: id {any}, method {s}", .{ id, method_name }); + std.log.debug("cmd: method {s}, id {any}", .{ method_name, id }); // retrieve domain from method var iter = std.mem.splitScalar(u8, method_name, '.'); @@ -128,7 +124,7 @@ const resultNullSession = "{{\"id\": {d}, \"result\": {{}}, \"sessionId\": \"{s} // caller owns the slice returned pub fn result( alloc: std.mem.Allocator, - id: u64, + id: u16, comptime T: ?type, res: anytype, sessionID: ?[]const u8, @@ -142,7 +138,7 @@ pub fn result( } const Resp = struct { - id: u64, + id: u16, result: T.?, sessionId: ?[]const u8, }; @@ -171,108 +167,94 @@ pub fn sendEvent( try server.sendSync(ctx, event_msg); } -pub fn getParams( +fn getParams( alloc: std.mem.Allocator, comptime T: type, scanner: *std.json.Scanner, + key: []const u8, ) !?T { - // if next token is the end of the object, there is no "params" - const t = try scanner.next(); - if (t == .object_end) return null; + // check key key is "params" + if (!std.mem.eql(u8, "params", key)) return null; - // if next token is not "params" there is no "params" - if (!std.mem.eql(u8, "params", t.string)) return null; + // skip "params" if not requested + if (T == void) { + var finished: usize = 0; + while (true) { + switch (try scanner.next()) { + .object_begin => finished += 1, + .object_end => finished -= 1, + else => continue, + } + if (finished == 0) break; + } + return void{}; + } // parse "params" const options = std.json.ParseOptions{ .max_value_len = scanner.input.len, .allocate = .alloc_if_needed, }; - const params = try std.json.innerParse(T, alloc, scanner, options); - return params; + return try std.json.innerParse(T, alloc, scanner, options); } -pub fn getSessionID(scanner: *std.json.Scanner) !?[]const u8 { +fn getId(scanner: *std.json.Scanner, key: []const u8) !?u16 { - // if next token is the end of the object, there is no "sessionId" - const t = try scanner.next(); - if (t == .object_end) return null; + // check key is "id" + if (!std.mem.eql(u8, "id", key)) return null; - var n = t.string; + // parse "id" + return try std.fmt.parseUnsigned(u16, (try scanner.next()).number, 10); +} - // if next token is "params" ignore them - // NOTE: will panic if it's not an empty "params" object - // TODO: maybe we should return a custom error here - if (std.mem.eql(u8, n, "params")) { - // ignore empty params - _ = (try scanner.next()).object_begin; - _ = (try scanner.next()).object_end; - n = (try scanner.next()).string; - } +fn getSessionId(scanner: *std.json.Scanner, key: []const u8) !?[]const u8 { - // if next token is not "sessionId" there is no "sessionId" - if (!std.mem.eql(u8, n, "sessionId")) return null; + // check key is "sessionId" + if (!std.mem.eql(u8, "sessionId", key)) return null; // parse "sessionId" return (try scanner.next()).string; } -pub fn getContent( +pub fn getMsg( alloc: std.mem.Allocator, - comptime T: type, + comptime params_T: type, scanner: *std.json.Scanner, -) !struct { params: T, sessionID: ?[]const u8 } { - - // if next token is the end of the object, error - const t = try scanner.next(); - if (t == .object_end) return error.CDPNoContent; - - var params: T = undefined; +) !struct { id: ?u16, params: ?params_T, sessionID: ?[]const u8 } { + var id: ?u16 = null; + var params: ?params_T = null; var sessionID: ?[]const u8 = null; - var n = t.string; + var t: std.json.Token = undefined; - // params - if (std.mem.eql(u8, n, "params")) { - if (T == void) { - - // ignore params - var finished: usize = 0; - while (true) { - switch (try scanner.next()) { - .object_begin => finished += 1, - .object_end => finished -= 1, - else => continue, - } - if (finished == 0) break; - } - params = void{}; - } else { - - // parse "params" - const options = std.json.ParseOptions{ - .max_value_len = scanner.input.len, - .allocate = .alloc_if_needed, - }; - params = try std.json.innerParse(T, alloc, scanner, options); + while (true) { + t = try scanner.next(); + if (t == .object_end) break; + if (t != .string) { + return error.CDPMsgWrong; + } + if (id == null) { + id = try getId(scanner, t.string); + if (id != null) continue; + } + if (params == null) { + params = try getParams(alloc, params_T, scanner, t.string); + if (params != null) continue; + } + if (sessionID == null) { + sessionID = try getSessionId(scanner, t.string); } - - // go next - n = (try scanner.next()).string; - } else { - params = switch (@typeInfo(T)) { - .Void => void{}, - .Optional => null, - else => return error.CDPNoParams, - }; } - if (std.mem.eql(u8, n, "sessionId")) { - sessionID = (try scanner.next()).string; - } - - return .{ .params = params, .sessionID = sessionID }; + // end + std.log.debug( + "id {any}, params {any}, sessionID: {any}, token {any}", + .{ id, params, sessionID, t }, + ); + t = try scanner.next(); + if (t != .end_of_document) return error.CDPMsgEnd; + return .{ .id = id, .params = params, .sessionID = sessionID }; } // Common diff --git a/src/cdp/emulation.zig b/src/cdp/emulation.zig index f7daff4f..2eda18e8 100644 --- a/src/cdp/emulation.zig +++ b/src/cdp/emulation.zig @@ -4,7 +4,7 @@ const server = @import("../server.zig"); const Ctx = server.Cmd; const cdp = @import("cdp.zig"); const result = cdp.result; -const getParams = cdp.getParams; +const getMsg = cdp.getMsg; const stringify = cdp.stringify; const EmulationMethods = enum { @@ -15,7 +15,7 @@ const EmulationMethods = enum { pub fn emulation( alloc: std.mem.Allocator, - id: u64, + id: ?u16, action: []const u8, scanner: *std.json.Scanner, ctx: *Ctx, @@ -36,26 +36,26 @@ const MediaFeature = struct { fn setEmulatedMedia( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { + // input const Params = struct { media: ?[]const u8 = null, features: ?[]MediaFeature = null, }; - _ = try getParams(alloc, Params, scanner); - const sessionID = try cdp.getSessionID(scanner); + const msg = try getMsg(alloc, Params, scanner); // output // TODO: dummy - return result(alloc, id, null, null, sessionID); + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } fn setFocusEmulationEnabled( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { @@ -64,24 +64,23 @@ fn setFocusEmulationEnabled( const Params = struct { enabled: bool, }; - _ = try getParams(alloc, Params, scanner); - const sessionID = try cdp.getSessionID(scanner); + const msg = try getMsg(alloc, Params, scanner); // output // TODO: dummy - return result(alloc, id, null, null, sessionID); + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } fn setDeviceMetricsOverride( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { // input - const content = try cdp.getContent(alloc, void, scanner); + const msg = try cdp.getMsg(alloc, void, scanner); // output - return result(alloc, id, null, null, content.sessionID); + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } diff --git a/src/cdp/log.zig b/src/cdp/log.zig index 4bbcb140..b8ef7cff 100644 --- a/src/cdp/log.zig +++ b/src/cdp/log.zig @@ -4,7 +4,7 @@ const server = @import("../server.zig"); const Ctx = server.Cmd; const cdp = @import("cdp.zig"); const result = cdp.result; -const getParams = cdp.getParams; +const getMsg = cdp.getMsg; const stringify = cdp.stringify; const LogMethods = enum { @@ -13,24 +13,26 @@ const LogMethods = enum { pub fn log( alloc: std.mem.Allocator, - id: u64, + id: ?u16, action: []const u8, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { const method = std.meta.stringToEnum(LogMethods, action) orelse return error.UnknownMethod; + return switch (method) { - .enable => enable(alloc, id, scanner, ctx), + .enable => logEnable(alloc, id, scanner, ctx), }; } -fn enable( +fn logEnable( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - const sessionID = try cdp.getSessionID(scanner); - return result(alloc, id, null, null, sessionID); + const msg = try getMsg(alloc, void, scanner); + + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } diff --git a/src/cdp/network.zig b/src/cdp/network.zig index ebc21cca..830c090c 100644 --- a/src/cdp/network.zig +++ b/src/cdp/network.zig @@ -4,7 +4,7 @@ const server = @import("../server.zig"); const Ctx = server.Cmd; const cdp = @import("cdp.zig"); const result = cdp.result; -const getParams = cdp.getParams; +const getMsg = cdp.getMsg; const stringify = cdp.stringify; const NetworkMethods = enum { @@ -13,24 +13,26 @@ const NetworkMethods = enum { pub fn network( alloc: std.mem.Allocator, - id: u64, + id: ?u16, action: []const u8, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { const method = std.meta.stringToEnum(NetworkMethods, action) orelse return error.UnknownMethod; + return switch (method) { - .enable => enable(alloc, id, scanner, ctx), + .enable => networkEnable(alloc, id, scanner, ctx), }; } -fn enable( +fn networkEnable( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - const sessionID = try cdp.getSessionID(scanner); - return result(alloc, id, null, null, sessionID); + const msg = try getMsg(alloc, void, scanner); + + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 60ec80e7..2c1eddf1 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -4,7 +4,7 @@ const server = @import("../server.zig"); const Ctx = server.Cmd; const cdp = @import("cdp.zig"); const result = cdp.result; -const getParams = cdp.getParams; +const getMsg = cdp.getMsg; const stringify = cdp.stringify; const sendEvent = cdp.sendEvent; @@ -21,7 +21,7 @@ const PageMethods = enum { pub fn page( alloc: std.mem.Allocator, - id: u64, + id: ?u16, action: []const u8, scanner: *std.json.Scanner, ctx: *Ctx, @@ -40,12 +40,12 @@ pub fn page( fn enable( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - const sessionID = try cdp.getSessionID(scanner); - return result(alloc, id, null, null, sessionID); + const msg = try getMsg(alloc, void, scanner); + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } const Frame = struct { @@ -65,11 +65,12 @@ const Frame = struct { fn getFrameTree( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { - const sessionID = try cdp.getSessionID(scanner); + const msg = try cdp.getMsg(alloc, void, scanner); + const FrameTree = struct { frameTree: struct { frame: Frame, @@ -87,12 +88,12 @@ fn getFrameTree( }, }, }; - return result(alloc, id, FrameTree, frameTree, sessionID); + return result(alloc, id orelse msg.id.?, FrameTree, frameTree, msg.sessionID); } fn setLifecycleEventsEnabled( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { @@ -101,13 +102,12 @@ fn setLifecycleEventsEnabled( const Params = struct { enabled: bool, }; - _ = try getParams(alloc, Params, scanner); - const sessionID = try cdp.getSessionID(scanner); + const msg = try getMsg(alloc, Params, scanner); ctx.state.page_life_cycle_events = true; // output - return result(alloc, id, null, null, sessionID); + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } const LifecycleEvent = struct { @@ -119,7 +119,7 @@ const LifecycleEvent = struct { fn addScriptToEvaluateOnNewDocument( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { @@ -131,37 +131,34 @@ fn addScriptToEvaluateOnNewDocument( includeCommandLineAPI: bool = false, runImmediately: bool = false, }; - _ = try getParams(alloc, Params, scanner); - const sessionID = try cdp.getSessionID(scanner); + const msg = try getMsg(alloc, Params, scanner); // output const Res = struct { identifier: []const u8 = "1", }; - return result(alloc, id, Res, Res{}, sessionID); + return result(alloc, id orelse msg.id.?, Res, Res{}, msg.sessionID); } fn createIsolatedWorld( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - - // input - const content = try cdp.getContent(alloc, void, scanner); + const msg = try getMsg(alloc, void, scanner); // output const Resp = struct { executionContextId: u8 = 2, }; - return result(alloc, id, Resp, .{}, content.sessionID); + return result(alloc, id orelse msg.id.?, Resp, .{}, msg.sessionID); } fn navigate( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { @@ -174,12 +171,12 @@ fn navigate( frameId: ?[]const u8 = null, referrerPolicy: ?[]const u8 = null, // TODO: enum }; - const input = try cdp.getContent(alloc, Params, scanner); - const sessionID = input.sessionID; - std.debug.assert(sessionID != null); + const msg = try getMsg(alloc, Params, scanner); + std.debug.assert(msg.sessionID != null); + const params = msg.params.?; // change state - ctx.state.url = input.params.url; + ctx.state.url = params.url; ctx.state.loaderID = "AF8667A203C5392DBE9AC290044AA4C2"; var life_event = LifecycleEvent{ @@ -193,11 +190,25 @@ fn navigate( frameId: []const u8, }; const frame_started_loading = FrameStartedLoading{ .frameId = ctx.state.frameID }; - try sendEvent(alloc, ctx, "Page.frameStartedLoading", FrameStartedLoading, frame_started_loading, sessionID); + try sendEvent( + alloc, + ctx, + "Page.frameStartedLoading", + FrameStartedLoading, + frame_started_loading, + msg.sessionID, + ); if (ctx.state.page_life_cycle_events) { life_event.name = "init"; life_event.timestamp = 343721.796037; - try sendEvent(alloc, ctx, "Page.lifecycleEvent", LifecycleEvent, life_event, sessionID); + try sendEvent( + alloc, + ctx, + "Page.lifecycleEvent", + LifecycleEvent, + life_event, + msg.sessionID, + ); } // output @@ -210,16 +221,16 @@ fn navigate( .frameId = ctx.state.frameID, .loaderId = ctx.state.loaderID, }; - const res = try result(alloc, id, Resp, resp, sessionID); + const res = try result(alloc, id orelse msg.id.?, Resp, resp, msg.sessionID); std.log.debug("res {s}", .{res}); try server.sendSync(ctx, res); // Runtime.executionContextsCleared event - try sendEvent(alloc, ctx, "Runtime.executionContextsCleared", void, {}, sessionID); + try sendEvent(alloc, ctx, "Runtime.executionContextsCleared", void, {}, msg.sessionID); // launch navigate var p = try ctx.browser.currentSession().createPage(); - _ = try p.navigate(input.params.url); + _ = try p.navigate(params.url); // frameNavigated event const FrameNavigated = struct { @@ -235,11 +246,25 @@ fn navigate( .loaderId = ctx.state.loaderID, }, }; - try sendEvent(alloc, ctx, "Page.frameNavigated", FrameNavigated, frame_navigated, sessionID); + try sendEvent( + alloc, + ctx, + "Page.frameNavigated", + FrameNavigated, + frame_navigated, + msg.sessionID, + ); if (ctx.state.page_life_cycle_events) { life_event.name = "load"; life_event.timestamp = 343721.824655; - try sendEvent(alloc, ctx, "Page.lifecycleEvent", LifecycleEvent, life_event, sessionID); + try sendEvent( + alloc, + ctx, + "Page.lifecycleEvent", + LifecycleEvent, + life_event, + msg.sessionID, + ); } try Runtime.executionContextCreated( @@ -250,7 +275,7 @@ fn navigate( "", "7102379147004877974.3265385113993241162", .{ .frameId = ctx.state.frameID }, - sessionID, + msg.sessionID, ); try Runtime.executionContextCreated( @@ -261,30 +286,65 @@ fn navigate( "__playwright_utility_world__", "-4572718120346458707.6016875269626438350", .{ .isDefault = false, .type = "isolated", .frameId = ctx.state.frameID }, - sessionID, + msg.sessionID, ); // domContentEventFired event ts_event = .{ .timestamp = 343721.803338 }; - try sendEvent(alloc, ctx, "Page.domContentEventFired", cdp.TimestampEvent, ts_event, sessionID); + try sendEvent( + alloc, + ctx, + "Page.domContentEventFired", + cdp.TimestampEvent, + ts_event, + msg.sessionID, + ); if (ctx.state.page_life_cycle_events) { life_event.name = "DOMContentLoaded"; life_event.timestamp = 343721.803338; - try sendEvent(alloc, ctx, "Page.lifecycleEvent", LifecycleEvent, life_event, sessionID); + try sendEvent( + alloc, + ctx, + "Page.lifecycleEvent", + LifecycleEvent, + life_event, + msg.sessionID, + ); } // loadEventFired event ts_event = .{ .timestamp = 343721.824655 }; - try sendEvent(alloc, ctx, "Page.loadEventFired", cdp.TimestampEvent, ts_event, sessionID); + try sendEvent( + alloc, + ctx, + "Page.loadEventFired", + cdp.TimestampEvent, + ts_event, + msg.sessionID, + ); if (ctx.state.page_life_cycle_events) { life_event.name = "load"; life_event.timestamp = 343721.824655; - try sendEvent(alloc, ctx, "Page.lifecycleEvent", LifecycleEvent, life_event, sessionID); + try sendEvent( + alloc, + ctx, + "Page.lifecycleEvent", + LifecycleEvent, + life_event, + msg.sessionID, + ); } // frameStoppedLoading const FrameStoppedLoading = struct { frameId: []const u8 }; - try sendEvent(alloc, ctx, "Page.frameStoppedLoading", FrameStoppedLoading, .{ .frameId = ctx.state.frameID }, sessionID); + try sendEvent( + alloc, + ctx, + "Page.frameStoppedLoading", + FrameStoppedLoading, + .{ .frameId = ctx.state.frameID }, + msg.sessionID, + ); return ""; } diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index 857a1f72..bb071db1 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -6,7 +6,7 @@ const server = @import("../server.zig"); const Ctx = server.Cmd; const cdp = @import("cdp.zig"); const result = cdp.result; -const getParams = cdp.getParams; +const getMsg = cdp.getMsg; const stringify = cdp.stringify; const RuntimeMethods = enum { @@ -17,7 +17,7 @@ const RuntimeMethods = enum { pub fn runtime( alloc: std.mem.Allocator, - id: u64, + id: ?u16, action: []const u8, scanner: *std.json.Scanner, ctx: *Ctx, @@ -33,14 +33,13 @@ pub fn runtime( fn enable( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, - ctx: *Ctx, + _: *Ctx, ) ![]const u8 { - _ = ctx; // input - const sessionID = try cdp.getSessionID(scanner); + const msg = try getMsg(alloc, void, scanner); // output // const uniqueID = "1367118932354479079.-1471398151593995849"; @@ -56,7 +55,7 @@ fn enable( // std.log.debug("res {s}", .{mainCtx}); // try server.sendAsync(ctx, mainCtx); - return result(alloc, id, null, null, sessionID); + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } pub const AuxData = struct { @@ -76,7 +75,7 @@ const ExecutionContextDescription = struct { pub fn executionContextCreated( alloc: std.mem.Allocator, ctx: *Ctx, - id: u64, + id: u16, origin: []const u8, name: []const u8, uniqueID: []const u8, @@ -100,17 +99,18 @@ pub fn executionContextCreated( fn runIfWaitingForDebugger( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - const sessionID = try cdp.getSessionID(scanner); - return result(alloc, id, null, null, sessionID); + const msg = try getMsg(alloc, void, scanner); + + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } fn evaluate( alloc: std.mem.Allocator, - id: u64, + _id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { @@ -124,20 +124,21 @@ fn evaluate( contextId: ?u8, }; - const input = try cdp.getContent(alloc, Params, scanner); - const sessionID = input.sessionID; - std.debug.assert(sessionID != null); + const msg = try getMsg(alloc, Params, scanner); + std.debug.assert(msg.sessionID != null); + const params = msg.params.?; + const id = _id orelse msg.id.?; // save script in file at debug mode - std.log.debug("script {d} length: {d}", .{ id, input.params.expression.len }); + std.log.debug("script {d} length: {d}", .{ id, params.expression.len }); if (std.log.defaultLogEnabled(.debug)) { const name = try std.fmt.allocPrint(alloc, "id_{d}.js", .{id}); defer alloc.free(name); const dir = try std.fs.cwd().makeOpenPath("zig-cache/tmp", .{}); const f = try dir.createFile(name, .{}); defer f.close(); - const nb = try f.write(input.params.expression); - std.debug.assert(nb == input.params.expression.len); + const nb = try f.write(params.expression); + std.debug.assert(nb == params.expression.len); const p = try dir.realpathAlloc(alloc, name); defer alloc.free(p); std.log.debug("Script {d} saved at {s}", .{ id, p }); @@ -149,7 +150,7 @@ fn evaluate( // const page_alloc = ctx.browser.currentSession().page.?.arena.allocator(); const session_alloc = ctx.browser.currentSession().alloc; var res = jsruntime.JSResult{}; - try ctx.browser.currentSession().env.run(session_alloc, input.params.expression, "cdp", &res, null); + try ctx.browser.currentSession().env.run(session_alloc, params.expression, "cdp", &res, null); defer res.deinit(session_alloc); if (!res.success) { @@ -168,5 +169,5 @@ fn evaluate( description: []const u8 = "UtilityScript", objectId: []const u8 = "7481631759780215274.3.2", }; - return result(alloc, id, Resp, Resp{}, sessionID); + return result(alloc, id, Resp, Resp{}, msg.sessionID); } diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 67fd6474..2c165c9c 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -4,7 +4,7 @@ const server = @import("../server.zig"); const Ctx = server.Cmd; const cdp = @import("cdp.zig"); const result = cdp.result; -const getParams = cdp.getParams; +const getMsg = cdp.getMsg; const stringify = cdp.stringify; const TargetMethods = enum { @@ -16,7 +16,7 @@ const TargetMethods = enum { pub fn target( alloc: std.mem.Allocator, - id: u64, + id: ?u16, action: []const u8, scanner: *std.json.Scanner, ctx: *Ctx, @@ -56,7 +56,7 @@ const TargetFilter = struct { fn tagetSetAutoAttach( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { @@ -66,12 +66,10 @@ fn tagetSetAutoAttach( flatten: bool = true, filter: ?[]TargetFilter = null, }; - const params = try getParams(alloc, Params, scanner); - std.log.debug("params {any}", .{params}); + const msg = try getMsg(alloc, Params, scanner); + std.log.debug("params {any}", .{msg.params}); - const sessionID = try cdp.getSessionID(scanner); - - if (sessionID == null) { + if (msg.sessionID == null) { const attached = AttachToTarget{ .sessionId = cdp.SessionID, .targetInfo = .{ @@ -84,12 +82,12 @@ fn tagetSetAutoAttach( try cdp.sendEvent(alloc, ctx, "Target.attachedToTarget", AttachToTarget, attached, null); } - return result(alloc, id, null, null, sessionID); + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } fn tagetGetTargetInfo( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { @@ -98,7 +96,7 @@ fn tagetGetTargetInfo( const Params = struct { targetId: ?[]const u8 = null, }; - _ = try getParams(alloc, Params, scanner); + const msg = try getMsg(alloc, Params, scanner); // output const TargetInfo = struct { @@ -117,7 +115,7 @@ fn tagetGetTargetInfo( .targetId = BrowserTargetID, .type = "browser", }; - return result(alloc, id, TargetInfo, targetInfo, null); + return result(alloc, id orelse msg.id.?, TargetInfo, targetInfo, null); } const ContextID = "22648B09EDCCDD11109E2D4FEFBE4F89"; @@ -125,9 +123,9 @@ const ContextSessionID = "4FDC2CB760A23A220497A05C95417CF4"; fn createBrowserContext( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, - _: *Ctx, + ctx: *Ctx, ) ![]const u8 { // input @@ -137,21 +135,20 @@ fn createBrowserContext( proxyBypassList: ?[]const u8 = null, originsWithUniversalNetworkAccess: ?[][]const u8 = null, }; - _ = try getParams(alloc, Params, scanner); - const sessionID = try cdp.getSessionID(scanner); + const msg = try getMsg(alloc, Params, scanner); // output const Resp = struct { browserContextId: []const u8 = ContextID, }; - return result(alloc, id, Resp, Resp{}, sessionID); + return result(alloc, id orelse msg.id.?, Resp, Resp{}, msg.sessionID); } const TargetID = "57356548460A8F29706A2ADF14316298"; fn createTarget( alloc: std.mem.Allocator, - id: u64, + id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { @@ -167,8 +164,7 @@ fn createTarget( background: bool = false, forTab: ?bool = null, }; - _ = try getParams(alloc, Params, scanner); - const sessionID = try cdp.getSessionID(scanner); + const msg = try getMsg(alloc, Params, scanner); // change CDP state ctx.state.frameID = TargetID; @@ -188,11 +184,11 @@ fn createTarget( }, .waitingForDebugger = true, }; - try cdp.sendEvent(alloc, ctx, "Target.attachedToTarget", AttachToTarget, attached, sessionID); + try cdp.sendEvent(alloc, ctx, "Target.attachedToTarget", AttachToTarget, attached, msg.sessionID); // output const Resp = struct { targetId: []const u8 = TargetID, }; - return result(alloc, id, Resp, Resp{}, sessionID); + return result(alloc, id orelse msg.id.?, Resp, Resp{}, msg.sessionID); } From fa82160265a77d493f41e1a0668e21f217ef56b7 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 7 Jun 2024 16:02:02 +0200 Subject: [PATCH 057/117] Add target.getBrowserContexts Signed-off-by: Francis Bouvier --- src/cdp/target.zig | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 2c165c9c..d28beb45 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -10,6 +10,7 @@ const stringify = cdp.stringify; const TargetMethods = enum { setAutoAttach, getTargetInfo, + getBrowserContexts, createBrowserContext, createTarget, }; @@ -26,6 +27,7 @@ pub fn target( return switch (method) { .setAutoAttach => tagetSetAutoAttach(alloc, id, scanner, ctx), .getTargetInfo => tagetGetTargetInfo(alloc, id, scanner, ctx), + .getBrowserContexts => getBrowserContexts(alloc, id, scanner, ctx), .createBrowserContext => createBrowserContext(alloc, id, scanner, ctx), .createTarget => createTarget(alloc, id, scanner, ctx), }; @@ -118,6 +120,32 @@ fn tagetGetTargetInfo( return result(alloc, id orelse msg.id.?, TargetInfo, targetInfo, null); } +// Browser context are not handled and not in the roadmap for now +// The following methods are "fake" + +fn getBrowserContexts( + alloc: std.mem.Allocator, + id: ?u16, + scanner: *std.json.Scanner, + ctx: *Ctx, +) ![]const u8 { + const msg = try getMsg(alloc, void, scanner); + + // ouptut + const Resp = struct { + browserContextIds: [][]const u8, + }; + var resp: Resp = undefined; + if (ctx.state.contextID) |contextID| { + var contextIDs = [1][]const u8{contextID}; + resp = .{ .browserContextIds = &contextIDs }; + } else { + const contextIDs = [0][]const u8{}; + resp = .{ .browserContextIds = &contextIDs }; + } + return result(alloc, id orelse msg.id.?, Resp, resp, null); +} + const ContextID = "22648B09EDCCDD11109E2D4FEFBE4F89"; const ContextSessionID = "4FDC2CB760A23A220497A05C95417CF4"; From ad8c9fac2bd2632761d20dbf27ab6cc5ccca9133 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 7 Jun 2024 16:02:37 +0200 Subject: [PATCH 058/117] Add target.setDiscoverTargets Signed-off-by: Francis Bouvier --- src/cdp/target.zig | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/cdp/target.zig b/src/cdp/target.zig index d28beb45..9f6136a0 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -8,6 +8,7 @@ const getMsg = cdp.getMsg; const stringify = cdp.stringify; const TargetMethods = enum { + setDiscoverTargets, setAutoAttach, getTargetInfo, getBrowserContexts, @@ -25,6 +26,7 @@ pub fn target( const method = std.meta.stringToEnum(TargetMethods, action) orelse return error.UnknownMethod; return switch (method) { + .setDiscoverTargets => targetSetDiscoverTargets(alloc, id, scanner, ctx), .setAutoAttach => tagetSetAutoAttach(alloc, id, scanner, ctx), .getTargetInfo => tagetGetTargetInfo(alloc, id, scanner, ctx), .getBrowserContexts => getBrowserContexts(alloc, id, scanner, ctx), @@ -37,6 +39,17 @@ const PageTargetID = "CFCD6EC01573CF29BB638E9DC0F52DDC"; const BrowserTargetID = "2d2bdef9-1c95-416f-8c0e-83f3ab73a30c"; const BrowserContextID = "65618675CB7D3585A95049E9DFE95EA9"; +fn targetSetDiscoverTargets( + alloc: std.mem.Allocator, + id: ?u16, + scanner: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + const msg = try getMsg(alloc, void, scanner); + + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); +} + const AttachToTarget = struct { sessionId: []const u8, targetInfo: struct { @@ -52,8 +65,8 @@ const AttachToTarget = struct { }; const TargetFilter = struct { - type: []const u8, - exclude: bool, + type: ?[]const u8 = null, + exclude: ?bool = null, }; fn tagetSetAutoAttach( From 1929eed8ac1746c49a1cec04565cefa7c2cfbda8 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 7 Jun 2024 16:03:12 +0200 Subject: [PATCH 059/117] Add contextID in state Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 1 + src/cdp/target.zig | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 7853a804..a0d36918 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -88,6 +88,7 @@ pub fn do( } pub const State = struct { + contextID: ?[]const u8 = null, frameID: []const u8 = FrameID, url: []const u8 = URLBase, securityOrigin: []const u8 = URLBase, diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 9f6136a0..5d5b4135 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -178,6 +178,8 @@ fn createBrowserContext( }; const msg = try getMsg(alloc, Params, scanner); + ctx.state.contextID = ContextID; + // output const Resp = struct { browserContextId: []const u8 = ContextID, From 7d67d131c2f0d81620ab5fdde5eec096fa72c68c Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 7 Jun 2024 16:08:49 +0200 Subject: [PATCH 060/117] Add network.setCacheDisabled Signed-off-by: Francis Bouvier --- src/cdp/network.zig | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/cdp/network.zig b/src/cdp/network.zig index 830c090c..aadcf44c 100644 --- a/src/cdp/network.zig +++ b/src/cdp/network.zig @@ -9,6 +9,7 @@ const stringify = cdp.stringify; const NetworkMethods = enum { enable, + setCacheDisabled, }; pub fn network( @@ -23,6 +24,7 @@ pub fn network( return switch (method) { .enable => networkEnable(alloc, id, scanner, ctx), + .setCacheDisabled => networkSetCacheDisabled(alloc, id, scanner, ctx), }; } @@ -36,3 +38,14 @@ fn networkEnable( return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } + +fn networkSetCacheDisabled( + alloc: std.mem.Allocator, + id: ?u16, + scanner: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + const msg = try getMsg(alloc, void, scanner); + + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); +} From cecc03e1ed2c957ea2b86e85e4473d2433212543 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 7 Jun 2024 16:12:31 +0200 Subject: [PATCH 061/117] Add fetch.disable Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 3 +++ src/cdp/fetch.zig | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/cdp/fetch.zig diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index a0d36918..7d9c670d 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -10,6 +10,7 @@ const log = @import("log.zig").log; const runtime = @import("runtime.zig").runtime; const network = @import("network.zig").network; const emulation = @import("emulation.zig").emulation; +const fetch = @import("fetch.zig").fetch; pub const Error = error{ UnknonwDomain, @@ -35,6 +36,7 @@ const Domains = enum { Runtime, Network, Emulation, + Fetch, }; // The caller is responsible for calling `free` on the returned slice. @@ -84,6 +86,7 @@ pub fn do( .Runtime => runtime(alloc, id, iter.next().?, &scanner, ctx), .Network => network(alloc, id, iter.next().?, &scanner, ctx), .Emulation => emulation(alloc, id, iter.next().?, &scanner, ctx), + .Fetch => fetch(alloc, id, iter.next().?, &scanner, ctx), }; } diff --git a/src/cdp/fetch.zig b/src/cdp/fetch.zig new file mode 100644 index 00000000..46c1fba6 --- /dev/null +++ b/src/cdp/fetch.zig @@ -0,0 +1,37 @@ +const std = @import("std"); + +const server = @import("../server.zig"); +const Ctx = server.Cmd; +const cdp = @import("cdp.zig"); +const result = cdp.result; +const getMsg = cdp.getMsg; + +const FetchMethods = enum { + disable, +}; + +pub fn fetch( + alloc: std.mem.Allocator, + id: ?u16, + action: []const u8, + scanner: *std.json.Scanner, + ctx: *Ctx, +) ![]const u8 { + const method = std.meta.stringToEnum(FetchMethods, action) orelse + return error.UnknownMethod; + + return switch (method) { + .disable => fetchDisable(alloc, id, scanner, ctx), + }; +} + +fn fetchDisable( + alloc: std.mem.Allocator, + id: ?u16, + scanner: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + const msg = try getMsg(alloc, void, scanner); + + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); +} From 08c11ac41f25baf89883d681ec213a6895360f80 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 7 Jun 2024 16:16:15 +0200 Subject: [PATCH 062/117] Add performance.enable Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 3 +++ src/cdp/performance.zig | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/cdp/performance.zig diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 7d9c670d..036621d6 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -11,6 +11,7 @@ const runtime = @import("runtime.zig").runtime; const network = @import("network.zig").network; const emulation = @import("emulation.zig").emulation; const fetch = @import("fetch.zig").fetch; +const performance = @import("performance.zig").performance; pub const Error = error{ UnknonwDomain, @@ -37,6 +38,7 @@ const Domains = enum { Network, Emulation, Fetch, + Performance, }; // The caller is responsible for calling `free` on the returned slice. @@ -87,6 +89,7 @@ pub fn do( .Network => network(alloc, id, iter.next().?, &scanner, ctx), .Emulation => emulation(alloc, id, iter.next().?, &scanner, ctx), .Fetch => fetch(alloc, id, iter.next().?, &scanner, ctx), + .Performance => performance(alloc, id, iter.next().?, &scanner, ctx), }; } diff --git a/src/cdp/performance.zig b/src/cdp/performance.zig new file mode 100644 index 00000000..0a8b17e0 --- /dev/null +++ b/src/cdp/performance.zig @@ -0,0 +1,37 @@ +const std = @import("std"); + +const server = @import("../server.zig"); +const Ctx = server.Cmd; +const cdp = @import("cdp.zig"); +const result = cdp.result; +const getMsg = cdp.getMsg; + +const PerformanceMethods = enum { + enable, +}; + +pub fn performance( + alloc: std.mem.Allocator, + id: ?u16, + action: []const u8, + scanner: *std.json.Scanner, + ctx: *Ctx, +) ![]const u8 { + const method = std.meta.stringToEnum(PerformanceMethods, action) orelse + return error.UnknownMethod; + + return switch (method) { + .enable => performanceEnable(alloc, id, scanner, ctx), + }; +} + +fn performanceEnable( + alloc: std.mem.Allocator, + id: ?u16, + scanner: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + const msg = try getMsg(alloc, void, scanner); + + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); +} From 9120b9c1def437ed31ab0619ed0963ea78805cf4 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 7 Jun 2024 16:19:08 +0200 Subject: [PATCH 063/117] Add emulation.setTouchEmulationEnabled Signed-off-by: Francis Bouvier --- src/cdp/emulation.zig | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/cdp/emulation.zig b/src/cdp/emulation.zig index 2eda18e8..6e84e4a5 100644 --- a/src/cdp/emulation.zig +++ b/src/cdp/emulation.zig @@ -11,6 +11,7 @@ const EmulationMethods = enum { setEmulatedMedia, setFocusEmulationEnabled, setDeviceMetricsOverride, + setTouchEmulationEnabled, }; pub fn emulation( @@ -26,6 +27,7 @@ pub fn emulation( .setEmulatedMedia => setEmulatedMedia(alloc, id, scanner, ctx), .setFocusEmulationEnabled => setFocusEmulationEnabled(alloc, id, scanner, ctx), .setDeviceMetricsOverride => setDeviceMetricsOverride(alloc, id, scanner, ctx), + .setTouchEmulationEnabled => setTouchEmulationEnabled(alloc, id, scanner, ctx), }; } @@ -84,3 +86,14 @@ fn setDeviceMetricsOverride( // output return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } + +fn setTouchEmulationEnabled( + alloc: std.mem.Allocator, + id: ?u16, + scanner: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + const msg = try cdp.getMsg(alloc, void, scanner); + + return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); +} From 7abb7277c93b4d116c1123a8a512aeb109d85666 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 12 Jun 2024 17:56:07 +0200 Subject: [PATCH 064/117] Fix call to Runtime.executionContextCreated in Page.navigate Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 1 + src/cdp/page.zig | 39 +++++++++++++++------------------------ 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 036621d6..d8647a4a 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -94,6 +94,7 @@ pub fn do( } pub const State = struct { + executionContextId: u8 = 0, contextID: ?[]const u8 = null, frameID: []const u8 = FrameID, url: []const u8 = URLBase, diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 2c1eddf1..1867e98c 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -225,13 +225,26 @@ fn navigate( std.log.debug("res {s}", .{res}); try server.sendSync(ctx, res); - // Runtime.executionContextsCleared event + // Send clear runtime contexts event (noop) try sendEvent(alloc, ctx, "Runtime.executionContextsCleared", void, {}, msg.sessionID); - // launch navigate + // Launch navigate var p = try ctx.browser.currentSession().createPage(); _ = try p.navigate(params.url); + // Send create runtime context event + ctx.state.executionContextId += 1; + try Runtime.executionContextCreated( + alloc, + ctx, + ctx.state.executionContextId, + "http://127.0.0.1:1234", // TODO: real domain + "", + "7102379147004877974.3265385113993241162", + .{ .frameId = ctx.state.frameID }, + msg.sessionID, + ); + // frameNavigated event const FrameNavigated = struct { frame: Frame, @@ -267,28 +280,6 @@ fn navigate( ); } - try Runtime.executionContextCreated( - alloc, - ctx, - 3, - "http://127.0.0.1:1234", - "", - "7102379147004877974.3265385113993241162", - .{ .frameId = ctx.state.frameID }, - msg.sessionID, - ); - - try Runtime.executionContextCreated( - alloc, - ctx, - 4, - "://", - "__playwright_utility_world__", - "-4572718120346458707.6016875269626438350", - .{ .isDefault = false, .type = "isolated", .frameId = ctx.state.frameID }, - msg.sessionID, - ); - // domContentEventFired event ts_event = .{ .timestamp = 343721.803338 }; try sendEvent( From 409969621d03eda35f64a0e150eebcc34269e85e Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 12 Jun 2024 17:56:54 +0200 Subject: [PATCH 065/117] Add Runtime.addBinding Signed-off-by: Francis Bouvier --- src/cdp/runtime.zig | 53 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index bb071db1..985b2484 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -13,6 +13,7 @@ const RuntimeMethods = enum { enable, runIfWaitingForDebugger, evaluate, + addBinding, }; pub fn runtime( @@ -28,6 +29,7 @@ pub fn runtime( .enable => enable(alloc, id, scanner, ctx), .runIfWaitingForDebugger => runIfWaitingForDebugger(alloc, id, scanner, ctx), .evaluate => evaluate(alloc, id, scanner, ctx), + .addBinding => addBinding(alloc, id, scanner, ctx), }; } @@ -171,3 +173,54 @@ fn evaluate( }; return result(alloc, id, Resp, Resp{}, msg.sessionID); } + +fn addBinding( + alloc: std.mem.Allocator, + _id: ?u16, + scanner: *std.json.Scanner, + ctx: *Ctx, +) ![]const u8 { + + // input + const Params = struct { + name: []const u8, + executionContextId: ?u8 = null, + }; + const msg = try getMsg(alloc, Params, scanner); + const id = _id orelse msg.id.?; + const params = msg.params.?; + if (params.executionContextId) |contextId| { + std.debug.assert(contextId == ctx.state.executionContextId); + } + + const script = try std.fmt.allocPrint(alloc, "globalThis['{s}'] = {{}};", .{params.name}); + defer alloc.free(script); + + const session = ctx.browser.currentSession(); + const res = try runtimeEvaluate(session.alloc, id, session.env, script, "addBinding"); + defer res.deinit(session.alloc); + + return result(alloc, id, null, null, msg.sessionID); +} + +// caller is the owner of JSResult returned +fn runtimeEvaluate( + alloc: std.mem.Allocator, + id: u16, + env: jsruntime.Env, + script: []const u8, + comptime name: []const u8, +) !jsruntime.JSResult { + var res = jsruntime.JSResult{}; + try env.run(alloc, script, "cdp.Runtime." ++ name, &res, null); + + if (!res.success) { + std.log.err("'{s}' id {d}, result: {s}", .{ name, id, res.result }); + if (res.stack) |stack| { + std.log.err("'{s}' id {d}, stack: {s}", .{ name, id, stack }); + } + return error.CDPRuntimeEvaluate; + } + std.log.debug("'{s}' id {d}, result: {s}", .{ name, id, res.result }); + return res; +} From 4d756b5bfc545285c7205aa3223a7b733a5facce Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Mon, 17 Jun 2024 16:34:47 +0200 Subject: [PATCH 066/117] Add a dumpFile utility function Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 17 +++++++++++++++++ src/cdp/runtime.zig | 11 +---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index d8647a4a..89f1bf3e 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -108,6 +108,23 @@ pub const State = struct { // Utils // ----- +pub fn dumpFile( + alloc: std.mem.Allocator, + id: u16, + script: []const u8, +) !void { + const name = try std.fmt.allocPrint(alloc, "id_{d}.js", .{id}); + defer alloc.free(name); + const dir = try std.fs.cwd().makeOpenPath("zig-cache/tmp", .{}); + const f = try dir.createFile(name, .{}); + defer f.close(); + const nb = try f.write(script); + std.debug.assert(nb == script.len); + const p = try dir.realpathAlloc(alloc, name); + defer alloc.free(p); + std.log.debug("Script {d} saved at {s}", .{ id, p }); +} + fn checkKey(key: []const u8, token: []const u8) !void { if (!std.mem.eql(u8, key, token)) return error.WrongToken; } diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index 985b2484..ab8afdea 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -134,16 +134,7 @@ fn evaluate( // save script in file at debug mode std.log.debug("script {d} length: {d}", .{ id, params.expression.len }); if (std.log.defaultLogEnabled(.debug)) { - const name = try std.fmt.allocPrint(alloc, "id_{d}.js", .{id}); - defer alloc.free(name); - const dir = try std.fs.cwd().makeOpenPath("zig-cache/tmp", .{}); - const f = try dir.createFile(name, .{}); - defer f.close(); - const nb = try f.write(params.expression); - std.debug.assert(nb == params.expression.len); - const p = try dir.realpathAlloc(alloc, name); - defer alloc.free(p); - std.log.debug("Script {d} saved at {s}", .{ id, p }); + try cdp.dumpFile(alloc, id, params.expression); } // evaluate the script in the context of the current page From 9319e4a7f1883ae60d0babf735912cbb1cfe0f4c Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Mon, 17 Jun 2024 16:35:22 +0200 Subject: [PATCH 067/117] Handle Runtime.callFunctionOn Signed-off-by: Francis Bouvier --- src/cdp/runtime.zig | 76 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index ab8afdea..ea39e295 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -14,6 +14,7 @@ const RuntimeMethods = enum { runIfWaitingForDebugger, evaluate, addBinding, + callFunctionOn, }; pub fn runtime( @@ -30,6 +31,7 @@ pub fn runtime( .runIfWaitingForDebugger => runIfWaitingForDebugger(alloc, id, scanner, ctx), .evaluate => evaluate(alloc, id, scanner, ctx), .addBinding => addBinding(alloc, id, scanner, ctx), + .callFunctionOn => callFunctionOn(alloc, id, scanner, ctx), }; } @@ -194,6 +196,80 @@ fn addBinding( return result(alloc, id, null, null, msg.sessionID); } +fn callFunctionOn( + alloc: std.mem.Allocator, + _id: ?u16, + scanner: *std.json.Scanner, + ctx: *Ctx, +) ![]const u8 { + + // input + const Params = struct { + functionDeclaration: []const u8, + objectId: ?[]const u8 = null, + executionContextId: ?u8 = null, + arguments: ?[]struct { + value: ?[]const u8 = null, + } = null, + returnByValue: ?bool = null, + awaitPromise: ?bool = null, + userGesture: ?bool = null, + }; + const msg = try getMsg(alloc, Params, scanner); + const id = _id orelse msg.id.?; + const params = msg.params.?; + std.debug.assert(params.objectId != null or params.executionContextId != null); + if (params.executionContextId) |contextID| { + std.debug.assert(contextID == ctx.state.executionContextId); + } + const name = "callFunctionOn"; + + // save script in file at debug mode + std.log.debug("{s} script id {d}, length: {d}", .{ name, id, params.functionDeclaration.len }); + if (std.log.defaultLogEnabled(.debug)) { + try cdp.dumpFile(alloc, id, params.functionDeclaration); + } + + // parse function + if (!std.mem.startsWith(u8, params.functionDeclaration, "function ")) { + return error.CDPRuntimeCallFunctionOnNotFunction; + } + const pos = std.mem.indexOfScalar(u8, params.functionDeclaration, '('); + if (pos == null) return error.CDPRuntimeCallFunctionOnWrongFunction; + var function = params.functionDeclaration[9..pos.?]; + function = try std.fmt.allocPrint(alloc, "{s}(", .{function}); + defer alloc.free(function); + if (params.arguments) |args| { + for (args, 0..) |arg, i| { + if (i > 0) { + function = try std.fmt.allocPrint(alloc, "{s}, ", .{function}); + } + if (arg.value) |value| { + function = try std.fmt.allocPrint(alloc, "{s}\"{s}\"", .{ function, value }); + } else { + function = try std.fmt.allocPrint(alloc, "{s}undefined", .{function}); + } + } + } + function = try std.fmt.allocPrint(alloc, "{s});", .{function}); + std.log.debug("{s} id {d}, function parsed: {s}", .{ name, id, function }); + + const session = ctx.browser.currentSession(); + // TODO: should we use the page's allocator instead of the session's allocator? + // the following code does not work: + // const page_alloc = ctx.browser.currentSession().page.?.arena.allocator(); + + // first evaluate the function declaration + const decl = try runtimeEvaluate(session.alloc, id, session.env, params.functionDeclaration, name); + defer decl.deinit(session.alloc); + + // then call the function on the arguments + const res = try runtimeEvaluate(session.alloc, id, session.env, function, name); + defer res.deinit(session.alloc); + + return result(alloc, id, null, "{\"type\":\"undefined\"}", msg.sessionID); +} + // caller is the owner of JSResult returned fn runtimeEvaluate( alloc: std.mem.Allocator, From 5eae15889dbb99379a21b1a1822aca73c6b5819f Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 19 Jun 2024 15:23:09 +0200 Subject: [PATCH 068/117] Add some optional fields in Runtime.evaluate Signed-off-by: Francis Bouvier --- src/cdp/runtime.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index ea39e295..8212d706 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -125,7 +125,10 @@ fn evaluate( // input const Params = struct { expression: []const u8, - contextId: ?u8, + contextId: ?u8 = null, + returnByValue: ?bool = null, + awaitPromise: ?bool = null, + userGesture: ?bool = null, }; const msg = try getMsg(alloc, Params, scanner); From 0f8b47b59854dcd833b111fe430a10317c2ef4e7 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 19 Jun 2024 15:48:20 +0200 Subject: [PATCH 069/117] Move MsgBuffer in it's own file for unit test purpose Signed-off-by: Francis Bouvier --- src/msg.zig | 153 ++++++++++++++++++++++++++++++++++++++++++++++ src/run_tests.zig | 3 + src/server.zig | 153 +--------------------------------------------- 3 files changed, 157 insertions(+), 152 deletions(-) create mode 100644 src/msg.zig diff --git a/src/msg.zig b/src/msg.zig new file mode 100644 index 00000000..ec3ec507 --- /dev/null +++ b/src/msg.zig @@ -0,0 +1,153 @@ +const std = @import("std"); + +// pub const MaxStdOutSize = 512; // ensure debug msg are not too long + +/// MsgBuffer return messages from a raw text read stream, +/// according to the following format `:`. +/// It handles both: +/// - combined messages in one read +/// - single message in several read (multipart) +/// It is safe (and good practice) to reuse the same MsgBuffer +/// on several reads of the same stream. +pub const MsgBuffer = struct { + size: usize = 0, + buf: []u8, + pos: usize = 0, + + pub fn init(alloc: std.mem.Allocator, size: usize) std.mem.Allocator.Error!MsgBuffer { + const buf = try alloc.alloc(u8, size); + return .{ .buf = buf }; + } + + pub fn deinit(self: MsgBuffer, alloc: std.mem.Allocator) void { + alloc.free(self.buf); + } + + fn isFinished(self: *MsgBuffer) bool { + return self.pos >= self.size; + } + + fn isEmpty(self: MsgBuffer) bool { + return self.size == 0 and self.pos == 0; + } + + fn reset(self: *MsgBuffer) void { + self.size = 0; + self.pos = 0; + } + + // read input + // - `do_func` is a callback to execute on each message of the input + // - `data` is a arbitrary payload that will be passed to the callback along with + // the message itself + pub fn read( + self: *MsgBuffer, + alloc: std.mem.Allocator, + input: []const u8, + data: anytype, + comptime do_func: fn (data: @TypeOf(data), msg: []const u8) anyerror!void, + ) !void { + var _input = input; // make input writable + + while (true) { + var msg: []const u8 = undefined; + + // msg size + var msg_size: usize = undefined; + if (self.isEmpty()) { + // parse msg size metadata + const size_pos = std.mem.indexOfScalar(u8, _input, ':').?; + const size_str = _input[0..size_pos]; + msg_size = try std.fmt.parseInt(u32, size_str, 10); + _input = _input[size_pos + 1 ..]; + } else { + msg_size = self.size; + } + + // multipart + const is_multipart = !self.isEmpty() or _input.len < msg_size; + if (is_multipart) { + + // set msg size on empty MsgBuffer + if (self.isEmpty()) { + self.size = msg_size; + } + + // get the new position of the cursor + const new_pos = self.pos + _input.len; + + // check if the current input can fit in MsgBuffer + if (new_pos > self.buf.len) { + // max_size is the max between msg size and current new cursor position + const max_size = @max(self.size, new_pos); + // resize the MsgBuffer to fit + self.buf = try alloc.realloc(self.buf, max_size); + } + + // copy the current input into MsgBuffer + @memcpy(self.buf[self.pos..new_pos], _input[0..]); + + // set the new cursor position + self.pos = new_pos; + + // if multipart is not finished, go fetch the next input + if (!self.isFinished()) return; + + // otherwhise multipart is finished, use its buffer as input + _input = self.buf[0..self.pos]; + self.reset(); + } + + // handle several JSON msg in 1 read + const is_combined = _input.len > msg_size; + msg = _input[0..msg_size]; + if (is_combined) { + _input = _input[msg_size..]; + } + + try @call(.auto, do_func, .{ data, msg }); + + if (!is_combined) break; + } + } +}; + +fn doTest(nb: *u8, _: []const u8) anyerror!void { + nb.* += 1; +} + +test "MsgBuffer" { + const Case = struct { + input: []const u8, + nb: u8, + }; + const alloc = std.testing.allocator; + const cases = [_]Case{ + // simple + .{ .input = "2:ok", .nb = 1 }, + // combined + .{ .input = "2:ok3:foo7:bar2:ok", .nb = 3 }, // "bar2:ok" is a message, no need to escape "2:" here + // multipart + .{ .input = "9:multi", .nb = 0 }, + .{ .input = "part", .nb = 1 }, + // multipart & combined + .{ .input = "9:multi", .nb = 0 }, + .{ .input = "part2:ok", .nb = 2 }, + // several multipart + .{ .input = "23:multi", .nb = 0 }, + .{ .input = "several", .nb = 0 }, + .{ .input = "complex", .nb = 0 }, + .{ .input = "part", .nb = 1 }, + // combined & multipart + .{ .input = "2:ok9:multi", .nb = 1 }, + .{ .input = "part", .nb = 1 }, + }; + var nb: u8 = undefined; + var msg_buf = try MsgBuffer.init(alloc, 10); + defer msg_buf.deinit(alloc); + for (cases) |case| { + nb = 0; + try msg_buf.read(alloc, case.input, &nb, doTest); + try std.testing.expect(nb == case.nb); + } +} diff --git a/src/run_tests.zig b/src/run_tests.zig index 3b235677..d3f8fb99 100644 --- a/src/run_tests.zig +++ b/src/run_tests.zig @@ -295,6 +295,9 @@ const kb = 1024; const ms = std.time.ns_per_ms; test { + const msgTest = @import("msg.zig"); + std.testing.refAllDecls(msgTest); + const asyncTest = @import("async/test.zig"); std.testing.refAllDecls(asyncTest); diff --git a/src/server.zig b/src/server.zig index 2943128b..8597912d 100644 --- a/src/server.zig +++ b/src/server.zig @@ -7,8 +7,8 @@ const RecvError = public.IO.RecvError; const SendError = public.IO.SendError; const TimeoutError = public.IO.TimeoutError; +const MsgBuffer = @import("msg.zig").MsgBuffer; const Browser = @import("browser/browser.zig").Browser; - const cdp = @import("cdp/cdp.zig"); const NoError = error{NoError}; @@ -91,157 +91,6 @@ pub const Cmd = struct { } }; -/// MsgBuffer return messages from a raw text read stream, -/// according to the following format `:`. -/// It handles both: -/// - combined messages in one read -/// - single message in several read (multipart) -/// It is safe (and good practice) to reuse the same MsgBuffer -/// on several reads of the same stream. -const MsgBuffer = struct { - size: usize = 0, - buf: []u8, - pos: usize = 0, - - fn init(alloc: std.mem.Allocator, size: usize) std.mem.Allocator.Error!MsgBuffer { - const buf = try alloc.alloc(u8, size); - return .{ .buf = buf }; - } - - fn deinit(self: MsgBuffer, alloc: std.mem.Allocator) void { - alloc.free(self.buf); - } - - fn isFinished(self: *MsgBuffer) bool { - return self.pos >= self.size; - } - - fn isEmpty(self: MsgBuffer) bool { - return self.size == 0 and self.pos == 0; - } - - fn reset(self: *MsgBuffer) void { - self.size = 0; - self.pos = 0; - } - - // read input - // - `do_func` is a callback to execute on each message of the input - // - `data` is a arbitrary payload that will be passed to the callback along with - // the message itself - fn read( - self: *MsgBuffer, - alloc: std.mem.Allocator, - input: []const u8, - data: anytype, - comptime do_func: fn (data: @TypeOf(data), msg: []const u8) anyerror!void, - ) !void { - var _input = input; // make input writable - - while (true) { - var msg: []const u8 = undefined; - - // msg size - var msg_size: usize = undefined; - if (self.isEmpty()) { - // parse msg size metadata - const size_pos = std.mem.indexOfScalar(u8, _input, ':').?; - const size_str = _input[0..size_pos]; - msg_size = try std.fmt.parseInt(u32, size_str, 10); - _input = _input[size_pos + 1 ..]; - } else { - msg_size = self.size; - } - - // multipart - const is_multipart = !self.isEmpty() or _input.len < msg_size; - if (is_multipart) { - - // set msg size on empty MsgBuffer - if (self.isEmpty()) { - self.size = msg_size; - } - - // get the new position of the cursor - const new_pos = self.pos + _input.len; - - // check if the current input can fit in MsgBuffer - if (new_pos > self.buf.len) { - // max_size is the max between msg size and current new cursor position - const max_size = @max(self.size, new_pos); - // resize the MsgBuffer to fit - self.buf = try alloc.realloc(self.buf, max_size); - } - - // copy the current input into MsgBuffer - @memcpy(self.buf[self.pos..new_pos], _input[0..]); - - // set the new cursor position - self.pos = new_pos; - - // if multipart is not finished, go fetch the next input - if (!self.isFinished()) return; - - // otherwhise multipart is finished, use its buffer as input - _input = self.buf[0..self.pos]; - self.reset(); - } - - // handle several JSON msg in 1 read - const is_combined = _input.len > msg_size; - msg = _input[0..msg_size]; - std.log.debug("msg: {s}", .{msg[0..@min(MaxStdOutSize, msg_size)]}); - if (is_combined) { - _input = _input[msg_size..]; - } - - try @call(.auto, do_func, .{ data, msg }); - - if (!is_combined) break; - } - } -}; - -fn doTest(nb: *u8, _: []const u8) anyerror!void { - nb.* += 1; -} - -test "MsgBuffer" { - const Case = struct { - input: []const u8, - nb: u8, - }; - const alloc = std.testing.allocator; - const cases = [_]Case{ - // simple - .{ .input = "2:ok", .nb = 1 }, - // combined - .{ .input = "2:ok3:foo7:bar2:ok", .nb = 3 }, // "bar2:ok" is a message, no need to escape "2:" here - // multipart - .{ .input = "9:multi", .nb = 0 }, - .{ .input = "part", .nb = 1 }, - // multipart & combined - .{ .input = "9:multi", .nb = 0 }, - .{ .input = "part2:ok", .nb = 2 }, - // several multipart - .{ .input = "23:multi", .nb = 0 }, - .{ .input = "several", .nb = 0 }, - .{ .input = "complex", .nb = 0 }, - .{ .input = "part", .nb = 1 }, - // combined & multipart - .{ .input = "2:ok9:multi", .nb = 1 }, - .{ .input = "part", .nb = 1 }, - }; - var nb: u8 = undefined; - var msg_buf = try MsgBuffer.init(alloc, 10); - defer msg_buf.deinit(alloc); - for (cases) |case| { - nb = 0; - try msg_buf.read(alloc, case.input, &nb, doTest); - try std.testing.expect(nb == case.nb); - } -} - // I/O Send // -------- From aca64eedcafd14b078f48d59f8dc6ea0c2ae20f4 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 19 Jun 2024 15:56:44 +0200 Subject: [PATCH 070/117] Uniformize calling name conventions Signed-off-by: Francis Bouvier --- src/cdp/browser.zig | 12 ++++++------ src/cdp/emulation.zig | 4 ++-- src/cdp/fetch.zig | 8 ++++---- src/cdp/log.zig | 8 ++++---- src/cdp/network.zig | 13 ++++++------- src/cdp/page.zig | 4 ++-- src/cdp/performance.zig | 8 ++++---- src/cdp/runtime.zig | 4 ++-- src/cdp/target.zig | 16 ++++++++-------- 9 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/cdp/browser.zig b/src/cdp/browser.zig index 0d8dcd8a..e58a8bd2 100644 --- a/src/cdp/browser.zig +++ b/src/cdp/browser.zig @@ -6,7 +6,7 @@ const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; -const BrowserMethods = enum { +const Methods = enum { getVersion, setDownloadBehavior, getWindowForTarget, @@ -20,11 +20,11 @@ pub fn browser( scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { - const method = std.meta.stringToEnum(BrowserMethods, action) orelse + const method = std.meta.stringToEnum(Methods, action) orelse return error.UnknownMethod; return switch (method) { - .getVersion => browserGetVersion(alloc, id, scanner, ctx), - .setDownloadBehavior => browserSetDownloadBehavior(alloc, id, scanner, ctx), + .getVersion => getVersion(alloc, id, scanner, ctx), + .setDownloadBehavior => setDownloadBehavior(alloc, id, scanner, ctx), .getWindowForTarget => getWindowForTarget(alloc, id, scanner, ctx), .setWindowBounds => setWindowBounds(alloc, id, scanner, ctx), }; @@ -36,7 +36,7 @@ const Revision = "@9e6ded5ac1ff5e38d930ae52bd9aec09bd1a68e4"; const UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"; const JsVersion = "12.4.254.8"; -fn browserGetVersion( +fn getVersion( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, @@ -62,7 +62,7 @@ fn browserGetVersion( return result(alloc, id orelse msg.id.?, Res, res, null); } -fn browserSetDownloadBehavior( +fn setDownloadBehavior( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, diff --git a/src/cdp/emulation.zig b/src/cdp/emulation.zig index 6e84e4a5..1ff18e2e 100644 --- a/src/cdp/emulation.zig +++ b/src/cdp/emulation.zig @@ -7,7 +7,7 @@ const result = cdp.result; const getMsg = cdp.getMsg; const stringify = cdp.stringify; -const EmulationMethods = enum { +const Methods = enum { setEmulatedMedia, setFocusEmulationEnabled, setDeviceMetricsOverride, @@ -21,7 +21,7 @@ pub fn emulation( scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { - const method = std.meta.stringToEnum(EmulationMethods, action) orelse + const method = std.meta.stringToEnum(Methods, action) orelse return error.UnknownMethod; return switch (method) { .setEmulatedMedia => setEmulatedMedia(alloc, id, scanner, ctx), diff --git a/src/cdp/fetch.zig b/src/cdp/fetch.zig index 46c1fba6..ca327e40 100644 --- a/src/cdp/fetch.zig +++ b/src/cdp/fetch.zig @@ -6,7 +6,7 @@ const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; -const FetchMethods = enum { +const Methods = enum { disable, }; @@ -17,15 +17,15 @@ pub fn fetch( scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { - const method = std.meta.stringToEnum(FetchMethods, action) orelse + const method = std.meta.stringToEnum(Methods, action) orelse return error.UnknownMethod; return switch (method) { - .disable => fetchDisable(alloc, id, scanner, ctx), + .disable => disable(alloc, id, scanner, ctx), }; } -fn fetchDisable( +fn disable( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, diff --git a/src/cdp/log.zig b/src/cdp/log.zig index b8ef7cff..1358ecd1 100644 --- a/src/cdp/log.zig +++ b/src/cdp/log.zig @@ -7,7 +7,7 @@ const result = cdp.result; const getMsg = cdp.getMsg; const stringify = cdp.stringify; -const LogMethods = enum { +const Methods = enum { enable, }; @@ -18,15 +18,15 @@ pub fn log( scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { - const method = std.meta.stringToEnum(LogMethods, action) orelse + const method = std.meta.stringToEnum(Methods, action) orelse return error.UnknownMethod; return switch (method) { - .enable => logEnable(alloc, id, scanner, ctx), + .enable => enable(alloc, id, scanner, ctx), }; } -fn logEnable( +fn enable( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, diff --git a/src/cdp/network.zig b/src/cdp/network.zig index aadcf44c..7d55577d 100644 --- a/src/cdp/network.zig +++ b/src/cdp/network.zig @@ -5,9 +5,8 @@ const Ctx = server.Cmd; const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; -const stringify = cdp.stringify; -const NetworkMethods = enum { +const Methods = enum { enable, setCacheDisabled, }; @@ -19,16 +18,16 @@ pub fn network( scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { - const method = std.meta.stringToEnum(NetworkMethods, action) orelse + const method = std.meta.stringToEnum(Methods, action) orelse return error.UnknownMethod; return switch (method) { - .enable => networkEnable(alloc, id, scanner, ctx), - .setCacheDisabled => networkSetCacheDisabled(alloc, id, scanner, ctx), + .enable => enable(alloc, id, scanner, ctx), + .setCacheDisabled => setCacheDisabled(alloc, id, scanner, ctx), }; } -fn networkEnable( +fn enable( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, @@ -39,7 +38,7 @@ fn networkEnable( return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } -fn networkSetCacheDisabled( +fn setCacheDisabled( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 1867e98c..0f9d06bc 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -10,7 +10,7 @@ const sendEvent = cdp.sendEvent; const Runtime = @import("runtime.zig"); -const PageMethods = enum { +const Methods = enum { enable, getFrameTree, setLifecycleEventsEnabled, @@ -26,7 +26,7 @@ pub fn page( scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { - const method = std.meta.stringToEnum(PageMethods, action) orelse + const method = std.meta.stringToEnum(Methods, action) orelse return error.UnknownMethod; return switch (method) { .enable => enable(alloc, id, scanner, ctx), diff --git a/src/cdp/performance.zig b/src/cdp/performance.zig index 0a8b17e0..ecb8972b 100644 --- a/src/cdp/performance.zig +++ b/src/cdp/performance.zig @@ -6,7 +6,7 @@ const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; -const PerformanceMethods = enum { +const Methods = enum { enable, }; @@ -17,15 +17,15 @@ pub fn performance( scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { - const method = std.meta.stringToEnum(PerformanceMethods, action) orelse + const method = std.meta.stringToEnum(Methods, action) orelse return error.UnknownMethod; return switch (method) { - .enable => performanceEnable(alloc, id, scanner, ctx), + .enable => enable(alloc, id, scanner, ctx), }; } -fn performanceEnable( +fn enable( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index 8212d706..8c62e843 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -9,7 +9,7 @@ const result = cdp.result; const getMsg = cdp.getMsg; const stringify = cdp.stringify; -const RuntimeMethods = enum { +const Methods = enum { enable, runIfWaitingForDebugger, evaluate, @@ -24,7 +24,7 @@ pub fn runtime( scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { - const method = std.meta.stringToEnum(RuntimeMethods, action) orelse + const method = std.meta.stringToEnum(Methods, action) orelse return error.UnknownMethod; return switch (method) { .enable => enable(alloc, id, scanner, ctx), diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 5d5b4135..bc2de491 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -7,7 +7,7 @@ const result = cdp.result; const getMsg = cdp.getMsg; const stringify = cdp.stringify; -const TargetMethods = enum { +const Methods = enum { setDiscoverTargets, setAutoAttach, getTargetInfo, @@ -23,12 +23,12 @@ pub fn target( scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { - const method = std.meta.stringToEnum(TargetMethods, action) orelse + const method = std.meta.stringToEnum(Methods, action) orelse return error.UnknownMethod; return switch (method) { - .setDiscoverTargets => targetSetDiscoverTargets(alloc, id, scanner, ctx), - .setAutoAttach => tagetSetAutoAttach(alloc, id, scanner, ctx), - .getTargetInfo => tagetGetTargetInfo(alloc, id, scanner, ctx), + .setDiscoverTargets => setDiscoverTargets(alloc, id, scanner, ctx), + .setAutoAttach => setAutoAttach(alloc, id, scanner, ctx), + .getTargetInfo => getTargetInfo(alloc, id, scanner, ctx), .getBrowserContexts => getBrowserContexts(alloc, id, scanner, ctx), .createBrowserContext => createBrowserContext(alloc, id, scanner, ctx), .createTarget => createTarget(alloc, id, scanner, ctx), @@ -39,7 +39,7 @@ const PageTargetID = "CFCD6EC01573CF29BB638E9DC0F52DDC"; const BrowserTargetID = "2d2bdef9-1c95-416f-8c0e-83f3ab73a30c"; const BrowserContextID = "65618675CB7D3585A95049E9DFE95EA9"; -fn targetSetDiscoverTargets( +fn setDiscoverTargets( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, @@ -69,7 +69,7 @@ const TargetFilter = struct { exclude: ?bool = null, }; -fn tagetSetAutoAttach( +fn setAutoAttach( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, @@ -100,7 +100,7 @@ fn tagetSetAutoAttach( return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } -fn tagetGetTargetInfo( +fn getTargetInfo( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, From ea410c8ced6acb8961e09bf91217b8eb31f60fd7 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 20 Jun 2024 00:32:06 +0200 Subject: [PATCH 071/117] Fix changes in Zig 0.12 std lib Signed-off-by: Francis Bouvier --- src/main.zig | 103 ++++++++++++++++++++++++++++++++++++++++++++++++- src/server.zig | 10 ++--- 2 files changed, 106 insertions(+), 7 deletions(-) diff --git a/src/main.zig b/src/main.zig index b9604eb6..c6363b32 100644 --- a/src/main.zig +++ b/src/main.zig @@ -17,6 +17,7 @@ // along with this program. If not, see . const std = @import("std"); +const posix = std.posix; const jsruntime = @import("jsruntime"); @@ -31,6 +32,102 @@ pub const UserContext = apiweb.UserContext; const socket_path = "/tmp/browsercore-server.sock"; +// Inspired by std.net.StreamServer in Zig < 0.12 +pub const StreamServer = struct { + /// Copied from `Options` on `init`. + kernel_backlog: u31, + reuse_address: bool, + reuse_port: bool, + nonblocking: bool, + + /// `undefined` until `listen` returns successfully. + listen_address: std.net.Address, + + sockfd: ?posix.socket_t, + + pub const Options = struct { + /// How many connections the kernel will accept on the application's behalf. + /// If more than this many connections pool in the kernel, clients will start + /// seeing "Connection refused". + kernel_backlog: u31 = 128, + + /// Enable SO.REUSEADDR on the socket. + reuse_address: bool = false, + + /// Enable SO.REUSEPORT on the socket. + reuse_port: bool = false, + + /// Non-blocking mode. + nonblocking: bool = false, + }; + + /// After this call succeeds, resources have been acquired and must + /// be released with `deinit`. + pub fn init(options: Options) StreamServer { + return StreamServer{ + .sockfd = null, + .kernel_backlog = options.kernel_backlog, + .reuse_address = options.reuse_address, + .reuse_port = options.reuse_port, + .nonblocking = options.nonblocking, + .listen_address = undefined, + }; + } + + /// Release all resources. The `StreamServer` memory becomes `undefined`. + pub fn deinit(self: *StreamServer) void { + self.close(); + self.* = undefined; + } + + pub fn listen(self: *StreamServer, address: std.net.Address) !void { + const sock_flags = posix.SOCK.STREAM | posix.SOCK.CLOEXEC; + var use_sock_flags: u32 = sock_flags; + if (self.nonblocking) use_sock_flags |= posix.SOCK.NONBLOCK; + const proto = if (address.any.family == posix.AF.UNIX) @as(u32, 0) else posix.IPPROTO.TCP; + + const sockfd = try posix.socket(address.any.family, use_sock_flags, proto); + self.sockfd = sockfd; + errdefer { + posix.close(sockfd); + self.sockfd = null; + } + + if (self.reuse_address) { + try posix.setsockopt( + sockfd, + posix.SOL.SOCKET, + posix.SO.REUSEADDR, + &std.mem.toBytes(@as(c_int, 1)), + ); + } + if (@hasDecl(posix.SO, "REUSEPORT") and self.reuse_port) { + try posix.setsockopt( + sockfd, + posix.SOL.SOCKET, + posix.SO.REUSEPORT, + &std.mem.toBytes(@as(c_int, 1)), + ); + } + + var socklen = address.getOsSockLen(); + try posix.bind(sockfd, &address.any, socklen); + try posix.listen(sockfd, self.kernel_backlog); + try posix.getsockname(sockfd, &self.listen_address.any, &socklen); + } + + /// Stop listening. It is still necessary to call `deinit` after stopping listening. + /// Calling `deinit` will automatically call `close`. It is safe to call `close` when + /// not listening. + pub fn close(self: *StreamServer) void { + if (self.sockfd) |fd| { + posix.close(fd); + self.sockfd = null; + self.listen_address = undefined; + } + } +}; + pub fn main() !void { // create v8 vm @@ -53,13 +150,15 @@ pub fn main() !void { // server const addr = try std.net.Address.initUnix(socket_path); - var srv = std.net.StreamServer.init(.{ + var srv = StreamServer.init(.{ .reuse_address = true, .reuse_port = true, - .force_nonblocking = true, + .nonblocking = true, }); defer srv.deinit(); + try srv.listen(addr); + defer srv.close(); std.debug.print("Listening on: {s}...\n", .{socket_path}); var browser = try Browser.init(arena.allocator(), vm); diff --git a/src/server.zig b/src/server.zig index 8597912d..c53f21e3 100644 --- a/src/server.zig +++ b/src/server.zig @@ -24,7 +24,7 @@ const MaxStdOutSize = 512; // ensure debug msg are not too long pub const Cmd = struct { // internal fields - socket: std.os.socket_t, + socket: std.posix.socket_t, buf: []u8, // only for read operations err: ?Error = null, @@ -154,7 +154,7 @@ pub fn sendAsync(ctx: *Cmd, msg: []const u8) !void { pub fn sendSync(ctx: *Cmd, msg: []const u8) !void { defer ctx.alloc().free(msg); - const s = try std.os.write(ctx.socket, msg); + const s = try std.posix.write(ctx.socket, msg); std.log.debug("send sync {d} bytes", .{s}); } @@ -163,9 +163,9 @@ pub fn sendSync(ctx: *Cmd, msg: []const u8) !void { const Accept = struct { cmd: *Cmd, - socket: std.os.socket_t, + socket: std.posix.socket_t, - fn cbk(self: *Accept, completion: *Completion, result: AcceptError!std.os.socket_t) void { + fn cbk(self: *Accept, completion: *Completion, result: AcceptError!std.posix.socket_t) void { self.cmd.socket = result catch |err| { self.cmd.err = err; return; @@ -179,7 +179,7 @@ const Accept = struct { // Listen // ------ -pub fn listen(browser: *Browser, socket: std.os.socket_t) anyerror!void { +pub fn listen(browser: *Browser, socket: std.posix.socket_t) anyerror!void { const loop = browser.currentSession().loop; // MsgBuffer From 41409031fd1bbfcbaeb63c228ff09fcf98cff2fb Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Mon, 8 Jul 2024 22:51:41 +0200 Subject: [PATCH 072/117] Adapt to refacto in js_exec from zig-js-runtime Signed-off-by: Francis Bouvier --- src/cdp/runtime.zig | 55 +++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index 8c62e843..34d1aed1 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const jsruntime = @import("jsruntime"); @@ -143,22 +144,11 @@ fn evaluate( } // evaluate the script in the context of the current page + const session = ctx.browser.currentSession(); // TODO: should we use instead the allocator of the page? - // the following code does not work - // const page_alloc = ctx.browser.currentSession().page.?.arena.allocator(); - const session_alloc = ctx.browser.currentSession().alloc; - var res = jsruntime.JSResult{}; - try ctx.browser.currentSession().env.run(session_alloc, params.expression, "cdp", &res, null); - defer res.deinit(session_alloc); + // the following code does not work with session.page.?.arena.allocator() as alloc - if (!res.success) { - std.log.err("script {d} result: {s}", .{ id, res.result }); - if (res.stack) |stack| { - std.log.err("script {d} stack: {s}", .{ id, stack }); - } - return error.CDPRuntimeEvaluate; - } - std.log.debug("script {d} result: {s}", .{ id, res.result }); + _ = try runtimeEvaluate(session.alloc, id, session.env, params.expression, "cdp"); // TODO: Resp should depends on JS result returned by the JS engine const Resp = struct { @@ -193,8 +183,7 @@ fn addBinding( defer alloc.free(script); const session = ctx.browser.currentSession(); - const res = try runtimeEvaluate(session.alloc, id, session.env, script, "addBinding"); - defer res.deinit(session.alloc); + _ = try runtimeEvaluate(session.alloc, id, session.env, script, "addBinding"); return result(alloc, id, null, null, msg.sessionID); } @@ -259,16 +248,13 @@ fn callFunctionOn( const session = ctx.browser.currentSession(); // TODO: should we use the page's allocator instead of the session's allocator? - // the following code does not work: - // const page_alloc = ctx.browser.currentSession().page.?.arena.allocator(); + // the following code does not work with session.page.?.arena.allocator() as alloc // first evaluate the function declaration - const decl = try runtimeEvaluate(session.alloc, id, session.env, params.functionDeclaration, name); - defer decl.deinit(session.alloc); + _ = try runtimeEvaluate(session.alloc, id, session.env, params.functionDeclaration, name); // then call the function on the arguments - const res = try runtimeEvaluate(session.alloc, id, session.env, function, name); - defer res.deinit(session.alloc); + _ = try runtimeEvaluate(session.alloc, id, session.env, function, name); return result(alloc, id, null, "{\"type\":\"undefined\"}", msg.sessionID); } @@ -280,17 +266,26 @@ fn runtimeEvaluate( env: jsruntime.Env, script: []const u8, comptime name: []const u8, -) !jsruntime.JSResult { - var res = jsruntime.JSResult{}; - try env.run(alloc, script, "cdp.Runtime." ++ name, &res, null); +) !jsruntime.JSValue { - if (!res.success) { - std.log.err("'{s}' id {d}, result: {s}", .{ name, id, res.result }); - if (res.stack) |stack| { - std.log.err("'{s}' id {d}, stack: {s}", .{ name, id, stack }); + // try catch + var try_catch: jsruntime.TryCatch = undefined; + try_catch.init(env); + defer try_catch.deinit(); + + // script exec + const res = env.execWait(script, name) catch { + if (try try_catch.err(alloc, env)) |err_msg| { + defer alloc.free(err_msg); + std.log.err("'{s}' id {d}, result: {s}", .{ name, id, err_msg }); } return error.CDPRuntimeEvaluate; + }; + + if (builtin.mode == .Debug) { + const res_msg = try res.toString(alloc, env); + defer alloc.free(res_msg); + std.log.debug("'{s}' id {d}, result: {s}", .{ name, id, res_msg }); } - std.log.debug("'{s}' id {d}, result: {s}", .{ name, id, res.result }); return res; } From 14a3a662fd48704758e558b24c6f2eebcf34a199 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 9 Jul 2024 16:10:25 +0200 Subject: [PATCH 073/117] Fix response of runtime.Evaluate Signed-off-by: Francis Bouvier --- src/cdp/runtime.zig | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index 34d1aed1..b817dde0 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -147,17 +147,32 @@ fn evaluate( const session = ctx.browser.currentSession(); // TODO: should we use instead the allocator of the page? // the following code does not work with session.page.?.arena.allocator() as alloc + const res = try runtimeEvaluate(session.alloc, id, session.env, params.expression, "cdp"); - _ = try runtimeEvaluate(session.alloc, id, session.env, params.expression, "cdp"); + // check result + const res_type = try res.typeOf(session.env); // TODO: Resp should depends on JS result returned by the JS engine const Resp = struct { - type: []const u8 = "object", - className: []const u8 = "UtilityScript", - description: []const u8 = "UtilityScript", - objectId: []const u8 = "7481631759780215274.3.2", + result: struct { + type: []const u8, + subtype: ?[]const u8 = null, + className: ?[]const u8 = null, + description: ?[]const u8 = null, + objectId: ?[]const u8 = null, + }, }; - return result(alloc, id, Resp, Resp{}, msg.sessionID); + var resp = Resp{ + .result = .{ + .type = @tagName(res_type), + }, + }; + if (res_type == .object) { + resp.result.className = "Object"; + resp.result.description = "Object"; + resp.result.objectId = "-9051357107442861868.3.2"; + } + return result(alloc, id, Resp, resp, msg.sessionID); } fn addBinding( From 94d2d28806f8268c042e2ec5fc38752574732c68 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 1 Oct 2024 17:12:08 +0200 Subject: [PATCH 074/117] Redirect Runtime domain to JS engine Inspector Signed-off-by: Francis Bouvier --- src/browser/browser.zig | 35 ++++- src/cdp/cdp.zig | 8 +- src/cdp/page.zig | 47 +++++-- src/cdp/runtime.zig | 294 ++++++++++------------------------------ src/cdp/target.zig | 5 +- src/main.zig | 2 +- src/main_get.zig | 4 +- src/server.zig | 54 +++++++- 8 files changed, 195 insertions(+), 254 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 100b4498..5cf4f3f7 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -51,10 +51,9 @@ const log = std.log.scoped(.browser); pub const Browser = struct { session: *Session, - pub fn init(alloc: std.mem.Allocator, vm: jsruntime.VM) !Browser { + pub fn init(alloc: std.mem.Allocator) !Browser { // We want to ensure the caller initialised a VM, but the browser // doesn't use it directly... - _ = vm; return Browser{ .session = try Session.init(alloc, "about:blank"), @@ -91,6 +90,7 @@ pub const Session = struct { loader: Loader, env: Env = undefined, loop: Loop, + inspector: ?jsruntime.Inspector = null, window: Window, // TODO move the shed to the browser? storageShed: storage.Shed, @@ -122,6 +122,10 @@ pub const Session = struct { fn deinit(self: *Session) void { if (self.page) |page| page.end(); + if (self.inspector) |inspector| { + inspector.deinit(self.alloc); + } + self.env.deinit(); self.arena.deinit(); @@ -132,9 +136,25 @@ pub const Session = struct { self.alloc.destroy(self); } + pub fn setInspector( + self: *Session, + ctx: *anyopaque, + onResp: jsruntime.InspectorOnResponseFn, + onEvent: jsruntime.InspectorOnEventFn, + ) !void { + self.inspector = try jsruntime.Inspector.init(self.alloc, self.env, ctx, onResp, onEvent); + self.env.setInspector(self.inspector.?); + } + pub fn createPage(self: *Session) !Page { return Page.init(self.alloc, self); } + + pub fn callInspector(self: *Session, msg: []const u8) void { + if (self.inspector) |inspector| { + inspector.send(msg, self.env); + } + } }; // Page navigates to an url. @@ -219,7 +239,7 @@ pub const Page = struct { } // spec reference: https://html.spec.whatwg.org/#document-lifecycle - pub fn navigate(self: *Page, uri: []const u8) !void { + pub fn navigate(self: *Page, uri: []const u8, auxData: ?[]const u8) !void { const alloc = self.arena.allocator(); log.debug("starting GET {s}", .{uri}); @@ -280,7 +300,7 @@ pub const Page = struct { log.debug("header content-type: {s}", .{ct.?}); const mime = try Mime.parse(ct.?); if (mime.eql(Mime.HTML)) { - try self.loadHTMLDoc(req.reader(), mime.charset orelse "utf-8"); + try self.loadHTMLDoc(req.reader(), mime.charset orelse "utf-8", auxData); } else { log.info("non-HTML document: {s}", .{ct.?}); @@ -290,7 +310,7 @@ pub const Page = struct { } // https://html.spec.whatwg.org/#read-html - fn loadHTMLDoc(self: *Page, reader: anytype, charset: []const u8) !void { + fn loadHTMLDoc(self: *Page, reader: anytype, charset: []const u8, auxData: ?[]const u8) !void { const alloc = self.arena.allocator(); // start netsurf memory arena. @@ -327,6 +347,11 @@ pub const Page = struct { log.debug("start js env", .{}); try self.session.env.start(); + // inspector + if (self.session.inspector) |inspector| { + inspector.contextCreated(self.session.env, "", self.origin.?, auxData); + } + // replace the user context document with the new one. try self.session.env.setUserContext(.{ .document = html_doc, diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 89f1bf3e..87f1477b 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -16,6 +16,7 @@ const performance = @import("performance.zig").performance; pub const Error = error{ UnknonwDomain, UnknownMethod, + NoResponse, }; pub fn isCdpError(err: anyerror) ?Error { @@ -85,7 +86,7 @@ pub fn do( .Target => target(alloc, id, iter.next().?, &scanner, ctx), .Page => page(alloc, id, iter.next().?, &scanner, ctx), .Log => log(alloc, id, iter.next().?, &scanner, ctx), - .Runtime => runtime(alloc, id, iter.next().?, &scanner, ctx), + .Runtime => runtime(alloc, id, iter.next().?, &scanner, s, ctx), .Network => network(alloc, id, iter.next().?, &scanner, ctx), .Emulation => emulation(alloc, id, iter.next().?, &scanner, ctx), .Fetch => fetch(alloc, id, iter.next().?, &scanner, ctx), @@ -199,7 +200,7 @@ fn getParams( key: []const u8, ) !?T { - // check key key is "params" + // check key is "params" if (!std.mem.eql(u8, "params", key)) return null; // skip "params" if not requested @@ -285,7 +286,8 @@ pub fn getMsg( // Common // ------ -pub const SessionID = "9559320D92474062597D9875C664CAC0"; +pub const BrowserSessionID = "9559320D92474062597D9875C664CAC0"; +pub const ContextSessionID = "4FDC2CB760A23A220497A05C95417CF4"; pub const URLBase = "chrome://newtab/"; pub const FrameID = "90D14BBD8AED408A0467AC93100BCDBE"; pub const LoaderID = "CFC8BED824DD2FD56CF1EF33C965C79C"; diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 0f9d06bc..38b3b8f4 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -144,13 +144,38 @@ fn createIsolatedWorld( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, - _: *Ctx, + ctx: *Ctx, ) ![]const u8 { - const msg = try getMsg(alloc, void, scanner); + + // input + const Params = struct { + frameId: []const u8, + worldName: []const u8, + grantUniveralAccess: bool, + }; + const msg = try getMsg(alloc, Params, scanner); + std.debug.assert(msg.sessionID != null); + const params = msg.params.?; + + // noop executionContextCreated event + try Runtime.executionContextCreated( + alloc, + ctx, + 0, + "", + params.worldName, + "7102379147004877974.3265385113993241162", + .{ + .isDefault = false, + .type = "isolated", + .frameId = params.frameId, + }, + msg.sessionID, + ); // output const Resp = struct { - executionContextId: u8 = 2, + executionContextId: u8 = 0, }; return result(alloc, id orelse msg.id.?, Resp, .{}, msg.sessionID); @@ -230,20 +255,14 @@ fn navigate( // Launch navigate var p = try ctx.browser.currentSession().createPage(); - _ = try p.navigate(params.url); - - // Send create runtime context event ctx.state.executionContextId += 1; - try Runtime.executionContextCreated( + const auxData = try std.fmt.allocPrint( alloc, - ctx, - ctx.state.executionContextId, - "http://127.0.0.1:1234", // TODO: real domain - "", - "7102379147004877974.3265385113993241162", - .{ .frameId = ctx.state.frameID }, - msg.sessionID, + "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", + .{ctx.state.frameID}, ); + defer alloc.free(auxData); + _ = try p.navigate(params.url, auxData); // frameNavigated event const FrameNavigated = struct { diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index b817dde0..8554cdcc 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -16,6 +16,7 @@ const Methods = enum { evaluate, addBinding, callFunctionOn, + releaseObject, }; pub fn runtime( @@ -23,44 +24,83 @@ pub fn runtime( id: ?u16, action: []const u8, scanner: *std.json.Scanner, + s: []const u8, ctx: *Ctx, ) ![]const u8 { const method = std.meta.stringToEnum(Methods, action) orelse + // NOTE: we could send it anyway to the JS runtime but it's good to check it return error.UnknownMethod; return switch (method) { - .enable => enable(alloc, id, scanner, ctx), .runIfWaitingForDebugger => runIfWaitingForDebugger(alloc, id, scanner, ctx), - .evaluate => evaluate(alloc, id, scanner, ctx), - .addBinding => addBinding(alloc, id, scanner, ctx), - .callFunctionOn => callFunctionOn(alloc, id, scanner, ctx), + else => sendInspector(alloc, method, id, s, scanner, ctx), }; } -fn enable( +fn sendInspector( alloc: std.mem.Allocator, - id: ?u16, + method: Methods, + _id: ?u16, + s: []const u8, scanner: *std.json.Scanner, - _: *Ctx, + ctx: *Ctx, ) ![]const u8 { - // input - const msg = try getMsg(alloc, void, scanner); + // save script in file at debug mode + if (std.log.defaultLogEnabled(.debug)) { - // output - // const uniqueID = "1367118932354479079.-1471398151593995849"; - // const mainCtx = try executionContextCreated( - // alloc, - // 1, - // cdp.URLBase, - // "", - // uniqueID, - // .{}, - // sessionID, - // ); - // std.log.debug("res {s}", .{mainCtx}); - // try server.sendAsync(ctx, mainCtx); + // input + var script: ?[]const u8 = null; + var id: u16 = undefined; - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + if (method == .evaluate) { + const Params = struct { + expression: []const u8, + contextId: ?u8 = null, + returnByValue: ?bool = null, + awaitPromise: ?bool = null, + userGesture: ?bool = null, + }; + + const msg = try getMsg(alloc, Params, scanner); + const params = msg.params.?; + script = params.expression; + id = _id orelse msg.id.?; + } else if (method == .callFunctionOn) { + const Params = struct { + functionDeclaration: []const u8, + objectId: ?[]const u8 = null, + executionContextId: ?u8 = null, + arguments: ?[]struct { + value: ?[]const u8 = null, + objectId: ?[]const u8 = null, + } = null, + returnByValue: ?bool = null, + awaitPromise: ?bool = null, + userGesture: ?bool = null, + }; + + const msg = try getMsg(alloc, Params, scanner); + const params = msg.params.?; + script = params.functionDeclaration; + id = _id orelse msg.id.?; + } + + if (script) |src| { + try cdp.dumpFile(alloc, id, src); + } + } + + // remove awaitPromise true params + // TODO: delete when Promise are correctly handled by zig-js-runtime + if (method == .callFunctionOn or method == .evaluate) { + const buf = try alloc.alloc(u8, s.len + 1); + defer alloc.free(buf); + _ = std.mem.replace(u8, s, "\"awaitPromise\":true", "\"awaitPromise\":false", buf); + ctx.sendInspector(buf); + } else { + ctx.sendInspector(s); + } + return ""; } pub const AuxData = struct { @@ -69,14 +109,6 @@ pub const AuxData = struct { frameId: []const u8 = cdp.FrameID, }; -const ExecutionContextDescription = struct { - id: u64, - origin: []const u8, - name: []const u8, - uniqueId: []const u8, - auxData: ?AuxData = null, -}; - pub fn executionContextCreated( alloc: std.mem.Allocator, ctx: *Ctx, @@ -88,7 +120,13 @@ pub fn executionContextCreated( sessionID: ?[]const u8, ) !void { const Params = struct { - context: ExecutionContextDescription, + context: struct { + id: u64, + origin: []const u8, + name: []const u8, + uniqueId: []const u8, + auxData: ?AuxData = null, + }, }; const params = Params{ .context = .{ @@ -112,195 +150,3 @@ fn runIfWaitingForDebugger( return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } - -fn evaluate( - alloc: std.mem.Allocator, - _id: ?u16, - scanner: *std.json.Scanner, - ctx: *Ctx, -) ![]const u8 { - - // ensure a page has been previously created - if (ctx.browser.currentSession().page == null) return error.CDPNoPage; - - // input - const Params = struct { - expression: []const u8, - contextId: ?u8 = null, - returnByValue: ?bool = null, - awaitPromise: ?bool = null, - userGesture: ?bool = null, - }; - - const msg = try getMsg(alloc, Params, scanner); - std.debug.assert(msg.sessionID != null); - const params = msg.params.?; - const id = _id orelse msg.id.?; - - // save script in file at debug mode - std.log.debug("script {d} length: {d}", .{ id, params.expression.len }); - if (std.log.defaultLogEnabled(.debug)) { - try cdp.dumpFile(alloc, id, params.expression); - } - - // evaluate the script in the context of the current page - const session = ctx.browser.currentSession(); - // TODO: should we use instead the allocator of the page? - // the following code does not work with session.page.?.arena.allocator() as alloc - const res = try runtimeEvaluate(session.alloc, id, session.env, params.expression, "cdp"); - - // check result - const res_type = try res.typeOf(session.env); - - // TODO: Resp should depends on JS result returned by the JS engine - const Resp = struct { - result: struct { - type: []const u8, - subtype: ?[]const u8 = null, - className: ?[]const u8 = null, - description: ?[]const u8 = null, - objectId: ?[]const u8 = null, - }, - }; - var resp = Resp{ - .result = .{ - .type = @tagName(res_type), - }, - }; - if (res_type == .object) { - resp.result.className = "Object"; - resp.result.description = "Object"; - resp.result.objectId = "-9051357107442861868.3.2"; - } - return result(alloc, id, Resp, resp, msg.sessionID); -} - -fn addBinding( - alloc: std.mem.Allocator, - _id: ?u16, - scanner: *std.json.Scanner, - ctx: *Ctx, -) ![]const u8 { - - // input - const Params = struct { - name: []const u8, - executionContextId: ?u8 = null, - }; - const msg = try getMsg(alloc, Params, scanner); - const id = _id orelse msg.id.?; - const params = msg.params.?; - if (params.executionContextId) |contextId| { - std.debug.assert(contextId == ctx.state.executionContextId); - } - - const script = try std.fmt.allocPrint(alloc, "globalThis['{s}'] = {{}};", .{params.name}); - defer alloc.free(script); - - const session = ctx.browser.currentSession(); - _ = try runtimeEvaluate(session.alloc, id, session.env, script, "addBinding"); - - return result(alloc, id, null, null, msg.sessionID); -} - -fn callFunctionOn( - alloc: std.mem.Allocator, - _id: ?u16, - scanner: *std.json.Scanner, - ctx: *Ctx, -) ![]const u8 { - - // input - const Params = struct { - functionDeclaration: []const u8, - objectId: ?[]const u8 = null, - executionContextId: ?u8 = null, - arguments: ?[]struct { - value: ?[]const u8 = null, - } = null, - returnByValue: ?bool = null, - awaitPromise: ?bool = null, - userGesture: ?bool = null, - }; - const msg = try getMsg(alloc, Params, scanner); - const id = _id orelse msg.id.?; - const params = msg.params.?; - std.debug.assert(params.objectId != null or params.executionContextId != null); - if (params.executionContextId) |contextID| { - std.debug.assert(contextID == ctx.state.executionContextId); - } - const name = "callFunctionOn"; - - // save script in file at debug mode - std.log.debug("{s} script id {d}, length: {d}", .{ name, id, params.functionDeclaration.len }); - if (std.log.defaultLogEnabled(.debug)) { - try cdp.dumpFile(alloc, id, params.functionDeclaration); - } - - // parse function - if (!std.mem.startsWith(u8, params.functionDeclaration, "function ")) { - return error.CDPRuntimeCallFunctionOnNotFunction; - } - const pos = std.mem.indexOfScalar(u8, params.functionDeclaration, '('); - if (pos == null) return error.CDPRuntimeCallFunctionOnWrongFunction; - var function = params.functionDeclaration[9..pos.?]; - function = try std.fmt.allocPrint(alloc, "{s}(", .{function}); - defer alloc.free(function); - if (params.arguments) |args| { - for (args, 0..) |arg, i| { - if (i > 0) { - function = try std.fmt.allocPrint(alloc, "{s}, ", .{function}); - } - if (arg.value) |value| { - function = try std.fmt.allocPrint(alloc, "{s}\"{s}\"", .{ function, value }); - } else { - function = try std.fmt.allocPrint(alloc, "{s}undefined", .{function}); - } - } - } - function = try std.fmt.allocPrint(alloc, "{s});", .{function}); - std.log.debug("{s} id {d}, function parsed: {s}", .{ name, id, function }); - - const session = ctx.browser.currentSession(); - // TODO: should we use the page's allocator instead of the session's allocator? - // the following code does not work with session.page.?.arena.allocator() as alloc - - // first evaluate the function declaration - _ = try runtimeEvaluate(session.alloc, id, session.env, params.functionDeclaration, name); - - // then call the function on the arguments - _ = try runtimeEvaluate(session.alloc, id, session.env, function, name); - - return result(alloc, id, null, "{\"type\":\"undefined\"}", msg.sessionID); -} - -// caller is the owner of JSResult returned -fn runtimeEvaluate( - alloc: std.mem.Allocator, - id: u16, - env: jsruntime.Env, - script: []const u8, - comptime name: []const u8, -) !jsruntime.JSValue { - - // try catch - var try_catch: jsruntime.TryCatch = undefined; - try_catch.init(env); - defer try_catch.deinit(); - - // script exec - const res = env.execWait(script, name) catch { - if (try try_catch.err(alloc, env)) |err_msg| { - defer alloc.free(err_msg); - std.log.err("'{s}' id {d}, result: {s}", .{ name, id, err_msg }); - } - return error.CDPRuntimeEvaluate; - }; - - if (builtin.mode == .Debug) { - const res_msg = try res.toString(alloc, env); - defer alloc.free(res_msg); - std.log.debug("'{s}' id {d}, result: {s}", .{ name, id, res_msg }); - } - return res; -} diff --git a/src/cdp/target.zig b/src/cdp/target.zig index bc2de491..b77b0249 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -86,7 +86,7 @@ fn setAutoAttach( if (msg.sessionID == null) { const attached = AttachToTarget{ - .sessionId = cdp.SessionID, + .sessionId = cdp.BrowserSessionID, .targetInfo = .{ .targetId = PageTargetID, .title = "New Incognito tab", @@ -160,7 +160,6 @@ fn getBrowserContexts( } const ContextID = "22648B09EDCCDD11109E2D4FEFBE4F89"; -const ContextSessionID = "4FDC2CB760A23A220497A05C95417CF4"; fn createBrowserContext( alloc: std.mem.Allocator, @@ -218,7 +217,7 @@ fn createTarget( // send attachToTarget event const attached = AttachToTarget{ - .sessionId = ContextSessionID, + .sessionId = cdp.ContextSessionID, .targetInfo = .{ .targetId = ctx.state.frameID, .title = "", diff --git a/src/main.zig b/src/main.zig index c6363b32..1c9de927 100644 --- a/src/main.zig +++ b/src/main.zig @@ -161,7 +161,7 @@ pub fn main() !void { defer srv.close(); std.debug.print("Listening on: {s}...\n", .{socket_path}); - var browser = try Browser.init(arena.allocator(), vm); + var browser = try Browser.init(arena.allocator()); defer browser.deinit(); try server.listen(&browser, srv.sockfd.?); diff --git a/src/main_get.zig b/src/main_get.zig index 6f455553..ac646be6 100644 --- a/src/main_get.zig +++ b/src/main_get.zig @@ -80,13 +80,13 @@ pub fn main() !void { const vm = jsruntime.VM.init(); defer vm.deinit(); - var browser = try Browser.init(allocator, vm); + var browser = try Browser.init(allocator); defer browser.deinit(); var page = try browser.currentSession().createPage(); defer page.deinit(); - try page.navigate(url); + try page.navigate(url, null); defer page.end(); try page.wait(); diff --git a/src/server.zig b/src/server.zig index c53f21e3..401e6524 100644 --- a/src/server.zig +++ b/src/server.zig @@ -70,16 +70,20 @@ pub const Cmd = struct { } // shortcuts - fn alloc(self: *Cmd) std.mem.Allocator { + inline fn alloc(self: *Cmd) std.mem.Allocator { // TODO: should we return the allocator from the page instead? return self.browser.currentSession().alloc; } - fn loop(self: *Cmd) public.Loop { + inline fn loop(self: *Cmd) public.Loop { // TODO: pointer instead? return self.browser.currentSession().loop; } + inline fn env(self: Cmd) public.Env { + return self.browser.currentSession().env; + } + fn do(self: *Cmd, cmd: []const u8) anyerror!void { const res = try cdp.do(self.alloc(), cmd, self); @@ -89,6 +93,49 @@ pub const Cmd = struct { return sendAsync(self, res); } } + + // Inspector + + pub fn sendInspector(self: *Cmd, msg: []const u8) void { + if (self.env().getInspector()) |inspector| { + inspector.send(self.env(), msg); + } + } + + pub fn onInspectorResp(cmd_opaque: *anyopaque, _: u32, msg: []const u8) void { + std.log.debug("onResp biz fn called: {s}", .{msg}); + const aligned = @as(*align(@alignOf(Cmd)) anyopaque, @alignCast(cmd_opaque)); + const self = @as(*Cmd, @ptrCast(aligned)); + + const tpl = "{s},\"sessionId\":\"{s}\"}}"; + const msg_open = msg[0 .. msg.len - 1]; // remove closing bracket + const s = std.fmt.allocPrint( + self.alloc(), + tpl, + .{ msg_open, cdp.ContextSessionID }, + ) catch unreachable; + defer self.alloc().free(s); + + sendSync(self, s) catch unreachable; + } + + pub fn onInspectorNotif(cmd_opaque: *anyopaque, msg: []const u8) void { + std.log.debug("onNotif biz fn called: {s}", .{msg}); + const aligned = @as(*align(@alignOf(Cmd)) anyopaque, @alignCast(cmd_opaque)); + const self = @as(*Cmd, @ptrCast(aligned)); + + const tpl = "{s},\"sessionId\":\"{s}\"}}"; + const msg_open = msg[0 .. msg.len - 1]; // remove closing bracket + const s = std.fmt.allocPrint( + self.alloc(), + tpl, + .{ msg_open, cdp.ContextSessionID }, + ) catch unreachable; + defer self.alloc().free(s); + std.log.debug("event: {s}", .{s}); + + sendSync(self, s) catch unreachable; + } }; // I/O Send @@ -195,6 +242,9 @@ pub fn listen(browser: *Browser, socket: std.posix.socket_t) anyerror!void { .buf = &ctxInput, .msg_buf = &msg_buf, }; + const cmd_opaque = @as(*anyopaque, @ptrCast(&cmd)); + try browser.currentSession().setInspector(cmd_opaque, Cmd.onInspectorResp, Cmd.onInspectorNotif); + var accept = Accept{ .cmd = &cmd, .socket = socket, From 1675f695820f46dc22bf0a6053741ef7a3f5f846 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 1 Oct 2024 17:13:29 +0200 Subject: [PATCH 075/117] Add Target.closeTarget Signed-off-by: Francis Bouvier --- src/cdp/target.zig | 56 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/cdp/target.zig b/src/cdp/target.zig index b77b0249..59e18779 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -14,6 +14,7 @@ const Methods = enum { getBrowserContexts, createBrowserContext, createTarget, + closeTarget, }; pub fn target( @@ -32,6 +33,7 @@ pub fn target( .getBrowserContexts => getBrowserContexts(alloc, id, scanner, ctx), .createBrowserContext => createBrowserContext(alloc, id, scanner, ctx), .createTarget => createTarget(alloc, id, scanner, ctx), + .closeTarget => closeTarget(alloc, id, scanner, ctx), }; } @@ -75,6 +77,8 @@ fn setAutoAttach( scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { + + // input const Params = struct { autoAttach: bool, waitForDebuggerOnStart: bool, @@ -234,3 +238,55 @@ fn createTarget( }; return result(alloc, id orelse msg.id.?, Resp, Resp{}, msg.sessionID); } + +fn closeTarget( + alloc: std.mem.Allocator, + id: ?u16, + scanner: *std.json.Scanner, + ctx: *Ctx, +) ![]const u8 { + + // input + const Params = struct { + targetId: []const u8, + }; + const msg = try getMsg(alloc, Params, scanner); + + // output + const Resp = struct { + success: bool = true, + }; + const res = try result(alloc, id orelse msg.id.?, Resp, Resp{}, null); + try server.sendSync(ctx, res); + + // events + const InspectorDetached = struct { + reason: []const u8 = "Render process gone.", + }; + try cdp.sendEvent( + alloc, + ctx, + "Inspector.detached", + InspectorDetached, + .{}, + msg.sessionID orelse cdp.ContextSessionID, + ); + + const TargetDetached = struct { + sessionId: []const u8, + targetId: []const u8, + }; + try cdp.sendEvent( + alloc, + ctx, + "Target.detachedFromTarget", + TargetDetached, + .{ + .sessionId = msg.sessionID orelse cdp.ContextSessionID, + .targetId = msg.params.?.targetId, + }, + null, + ); + + return ""; +} From 8bdd2a14e8fc9dc48681a7c9c00869c576b4831d Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 1 Oct 2024 17:13:47 +0200 Subject: [PATCH 076/117] Add Target.disposeBrowserContext Signed-off-by: Francis Bouvier --- src/cdp/target.zig | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 59e18779..9e7175aa 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -13,6 +13,7 @@ const Methods = enum { getTargetInfo, getBrowserContexts, createBrowserContext, + disposeBrowserContext, createTarget, closeTarget, }; @@ -32,6 +33,7 @@ pub fn target( .getTargetInfo => getTargetInfo(alloc, id, scanner, ctx), .getBrowserContexts => getBrowserContexts(alloc, id, scanner, ctx), .createBrowserContext => createBrowserContext(alloc, id, scanner, ctx), + .disposeBrowserContext => disposeBrowserContext(alloc, id, scanner, ctx), .createTarget => createTarget(alloc, id, scanner, ctx), .closeTarget => closeTarget(alloc, id, scanner, ctx), }; @@ -190,6 +192,22 @@ fn createBrowserContext( return result(alloc, id orelse msg.id.?, Resp, Resp{}, msg.sessionID); } +fn disposeBrowserContext( + alloc: std.mem.Allocator, + id: ?u16, + scanner: *std.json.Scanner, + _: *Ctx, +) ![]const u8 { + + // input + const Params = struct { + browserContextId: []const u8, + }; + const msg = try getMsg(alloc, Params, scanner); + + return result(alloc, id orelse msg.id.?, null, {}, null); +} + const TargetID = "57356548460A8F29706A2ADF14316298"; fn createTarget( From 2f3a5818596ff972e35755b2a4be3e61e55dfb91 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 1 Oct 2024 17:48:54 +0200 Subject: [PATCH 077/117] Add TODOs and comments Signed-off-by: Francis Bouvier --- src/cdp/browser.zig | 36 +++++++++++++++++++-------------- src/cdp/cdp.zig | 1 + src/cdp/emulation.zig | 6 ++++-- src/cdp/fetch.zig | 1 + src/cdp/network.zig | 1 + src/cdp/page.zig | 47 +++++++++++++++++++++++++++++++------------ src/cdp/runtime.zig | 2 ++ src/cdp/target.zig | 20 ++++++++++++++++-- 8 files changed, 82 insertions(+), 32 deletions(-) diff --git a/src/cdp/browser.zig b/src/cdp/browser.zig index e58a8bd2..e6cfa54e 100644 --- a/src/cdp/browser.zig +++ b/src/cdp/browser.zig @@ -30,6 +30,7 @@ pub fn browser( }; } +// TODO: hard coded data const ProtocolVersion = "1.3"; const Product = "Chrome/124.0.6367.29"; const Revision = "@9e6ded5ac1ff5e38d930ae52bd9aec09bd1a68e4"; @@ -42,32 +43,30 @@ fn getVersion( scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { + + // input const msg = try getMsg(alloc, void, scanner); + // ouput const Res = struct { - protocolVersion: []const u8, - product: []const u8, - revision: []const u8, - userAgent: []const u8, - jsVersion: []const u8, + protocolVersion: []const u8 = ProtocolVersion, + product: []const u8 = Product, + revision: []const u8 = Revision, + userAgent: []const u8 = UserAgent, + jsVersion: []const u8 = JsVersion, }; - - const res = Res{ - .protocolVersion = ProtocolVersion, - .product = Product, - .revision = Revision, - .userAgent = UserAgent, - .jsVersion = JsVersion, - }; - return result(alloc, id orelse msg.id.?, Res, res, null); + return result(alloc, id orelse msg.id.?, Res, .{}, null); } +// TODO: noop method fn setDownloadBehavior( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { + + // input const Params = struct { behavior: []const u8, browserContextId: ?[]const u8 = null, @@ -75,9 +74,12 @@ fn setDownloadBehavior( eventsEnabled: ?bool = null, }; const msg = try getMsg(alloc, Params, scanner); + + // output return result(alloc, id orelse msg.id.?, null, null, null); } +// TODO: hard coded ID const DevToolsWindowID = 1923710101; fn getWindowForTarget( @@ -108,13 +110,17 @@ fn getWindowForTarget( return result(alloc, id orelse msg.id.?, Resp, Resp{}, msg.sessionID.?); } +// TODO: noop method fn setWindowBounds( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - // NOTE: noop + + // input const msg = try cdp.getMsg(alloc, void, scanner); + + // output return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 87f1477b..8dbfde52 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -286,6 +286,7 @@ pub fn getMsg( // Common // ------ +// TODO: hard coded IDs pub const BrowserSessionID = "9559320D92474062597D9875C664CAC0"; pub const ContextSessionID = "4FDC2CB760A23A220497A05C95417CF4"; pub const URLBase = "chrome://newtab/"; diff --git a/src/cdp/emulation.zig b/src/cdp/emulation.zig index 1ff18e2e..7bdf7842 100644 --- a/src/cdp/emulation.zig +++ b/src/cdp/emulation.zig @@ -36,6 +36,7 @@ const MediaFeature = struct { value: []const u8, }; +// TODO: noop method fn setEmulatedMedia( alloc: std.mem.Allocator, id: ?u16, @@ -51,10 +52,10 @@ fn setEmulatedMedia( const msg = try getMsg(alloc, Params, scanner); // output - // TODO: dummy return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } +// TODO: noop method fn setFocusEmulationEnabled( alloc: std.mem.Allocator, id: ?u16, @@ -69,10 +70,10 @@ fn setFocusEmulationEnabled( const msg = try getMsg(alloc, Params, scanner); // output - // TODO: dummy return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } +// TODO: noop method fn setDeviceMetricsOverride( alloc: std.mem.Allocator, id: ?u16, @@ -87,6 +88,7 @@ fn setDeviceMetricsOverride( return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } +// TODO: noop method fn setTouchEmulationEnabled( alloc: std.mem.Allocator, id: ?u16, diff --git a/src/cdp/fetch.zig b/src/cdp/fetch.zig index ca327e40..558552a4 100644 --- a/src/cdp/fetch.zig +++ b/src/cdp/fetch.zig @@ -25,6 +25,7 @@ pub fn fetch( }; } +// TODO: noop method fn disable( alloc: std.mem.Allocator, id: ?u16, diff --git a/src/cdp/network.zig b/src/cdp/network.zig index 7d55577d..c0c79213 100644 --- a/src/cdp/network.zig +++ b/src/cdp/network.zig @@ -38,6 +38,7 @@ fn enable( return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } +// TODO: noop method fn setCacheDisabled( alloc: std.mem.Allocator, id: ?u16, diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 38b3b8f4..95d6da08 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -117,6 +117,7 @@ const LifecycleEvent = struct { timestamp: f32 = undefined, }; +// TODO: hard coded method fn addScriptToEvaluateOnNewDocument( alloc: std.mem.Allocator, id: ?u16, @@ -140,6 +141,7 @@ fn addScriptToEvaluateOnNewDocument( return result(alloc, id orelse msg.id.?, Res, Res{}, msg.sessionID); } +// TODO: hard coded method fn createIsolatedWorld( alloc: std.mem.Allocator, id: ?u16, @@ -202,6 +204,7 @@ fn navigate( // change state ctx.state.url = params.url; + // TODO: hard coded ID ctx.state.loaderID = "AF8667A203C5392DBE9AC290044AA4C2"; var life_event = LifecycleEvent{ @@ -211,6 +214,7 @@ fn navigate( var ts_event: cdp.TimestampEvent = undefined; // frameStartedLoading event + // TODO: event partially hard coded const FrameStartedLoading = struct { frameId: []const u8, }; @@ -250,7 +254,10 @@ fn navigate( std.log.debug("res {s}", .{res}); try server.sendSync(ctx, res); - // Send clear runtime contexts event (noop) + // TODO: at this point do we need async the following actions to be async? + + // Send Runtime.executionContextsCleared event + // TODO: noop event, we have no env context at this point, is it necesarry? try sendEvent(alloc, ctx, "Runtime.executionContextsCleared", void, {}, msg.sessionID); // Launch navigate @@ -258,12 +265,30 @@ fn navigate( ctx.state.executionContextId += 1; const auxData = try std.fmt.allocPrint( alloc, + // TODO: we assume this is the default web page, is it right? "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{ctx.state.frameID}, ); defer alloc.free(auxData); _ = try p.navigate(params.url, auxData); + // Events + + // lifecycle init event + // TODO: partially hard coded + if (ctx.state.page_life_cycle_events) { + life_event.name = "init"; + life_event.timestamp = 343721.796037; + try sendEvent( + alloc, + ctx, + "Page.lifecycleEvent", + LifecycleEvent, + life_event, + msg.sessionID, + ); + } + // frameNavigated event const FrameNavigated = struct { frame: Frame, @@ -286,20 +311,9 @@ fn navigate( frame_navigated, msg.sessionID, ); - if (ctx.state.page_life_cycle_events) { - life_event.name = "load"; - life_event.timestamp = 343721.824655; - try sendEvent( - alloc, - ctx, - "Page.lifecycleEvent", - LifecycleEvent, - life_event, - msg.sessionID, - ); - } // domContentEventFired event + // TODO: partially hard coded ts_event = .{ .timestamp = 343721.803338 }; try sendEvent( alloc, @@ -309,6 +323,9 @@ fn navigate( ts_event, msg.sessionID, ); + + // lifecycle DOMContentLoaded event + // TODO: partially hard coded if (ctx.state.page_life_cycle_events) { life_event.name = "DOMContentLoaded"; life_event.timestamp = 343721.803338; @@ -323,6 +340,7 @@ fn navigate( } // loadEventFired event + // TODO: partially hard coded ts_event = .{ .timestamp = 343721.824655 }; try sendEvent( alloc, @@ -332,6 +350,9 @@ fn navigate( ts_event, msg.sessionID, ); + + // lifecycle DOMContentLoaded event + // TODO: partially hard coded if (ctx.state.page_life_cycle_events) { life_event.name = "load"; life_event.timestamp = 343721.824655; diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index 8554cdcc..63199ffc 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -140,6 +140,8 @@ pub fn executionContextCreated( try cdp.sendEvent(alloc, ctx, "Runtime.executionContextCreated", Params, params, sessionID); } +// TODO: noop method +// should we be passing this also to the JS Inspector? fn runIfWaitingForDebugger( alloc: std.mem.Allocator, id: ?u16, diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 9e7175aa..0531ca26 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -39,18 +39,23 @@ pub fn target( }; } +// TODO: hard coded IDs const PageTargetID = "CFCD6EC01573CF29BB638E9DC0F52DDC"; const BrowserTargetID = "2d2bdef9-1c95-416f-8c0e-83f3ab73a30c"; const BrowserContextID = "65618675CB7D3585A95049E9DFE95EA9"; +// TODO: noop method fn setDiscoverTargets( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { + + // input const msg = try getMsg(alloc, void, scanner); + // output return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } @@ -73,6 +78,7 @@ const TargetFilter = struct { exclude: ?bool = null, }; +// TODO: noop method fn setAutoAttach( alloc: std.mem.Allocator, id: ?u16, @@ -90,6 +96,7 @@ fn setAutoAttach( const msg = try getMsg(alloc, Params, scanner); std.log.debug("params {any}", .{msg.params}); + // attachedToTarget event if (msg.sessionID == null) { const attached = AttachToTarget{ .sessionId = cdp.BrowserSessionID, @@ -103,6 +110,7 @@ fn setAutoAttach( try cdp.sendEvent(alloc, ctx, "Target.attachedToTarget", AttachToTarget, attached, null); } + // output return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); } @@ -142,12 +150,15 @@ fn getTargetInfo( // Browser context are not handled and not in the roadmap for now // The following methods are "fake" +// TODO: noop method fn getBrowserContexts( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { + + // input const msg = try getMsg(alloc, void, scanner); // ouptut @@ -167,6 +178,7 @@ fn getBrowserContexts( const ContextID = "22648B09EDCCDD11109E2D4FEFBE4F89"; +// TODO: noop method fn createBrowserContext( alloc: std.mem.Allocator, id: ?u16, @@ -205,10 +217,13 @@ fn disposeBrowserContext( }; const msg = try getMsg(alloc, Params, scanner); + // output return result(alloc, id orelse msg.id.?, null, {}, null); } +// TODO: hard coded IDs const TargetID = "57356548460A8F29706A2ADF14316298"; +const LoaderID = "DD4A76F842AA389647D702B4D805F49A"; fn createTarget( alloc: std.mem.Allocator, @@ -235,7 +250,7 @@ fn createTarget( ctx.state.url = "about:blank"; ctx.state.securityOrigin = "://"; ctx.state.secureContextType = "InsecureScheme"; - ctx.state.loaderID = "DD4A76F842AA389647D702B4D805F49A"; + ctx.state.loaderID = LoaderID; // send attachToTarget event const attached = AttachToTarget{ @@ -277,7 +292,7 @@ fn closeTarget( const res = try result(alloc, id orelse msg.id.?, Resp, Resp{}, null); try server.sendSync(ctx, res); - // events + // Inspector.detached event const InspectorDetached = struct { reason: []const u8 = "Render process gone.", }; @@ -290,6 +305,7 @@ fn closeTarget( msg.sessionID orelse cdp.ContextSessionID, ); + // detachedFromTarget event const TargetDetached = struct { sessionId: []const u8, targetId: []const u8, From 5ab1d2a8a54fb5ad139b069067318f4a6e1f7255 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 1 Oct 2024 18:02:21 +0200 Subject: [PATCH 078/117] Add License in new cdp files Signed-off-by: Francis Bouvier --- src/cdp/browser.zig | 18 ++++++++++++++++++ src/cdp/cdp.zig | 18 ++++++++++++++++++ src/cdp/emulation.zig | 18 ++++++++++++++++++ src/cdp/fetch.zig | 18 ++++++++++++++++++ src/cdp/log.zig | 18 ++++++++++++++++++ src/cdp/network.zig | 18 ++++++++++++++++++ src/cdp/page.zig | 18 ++++++++++++++++++ src/cdp/performance.zig | 18 ++++++++++++++++++ src/cdp/runtime.zig | 18 ++++++++++++++++++ src/cdp/target.zig | 18 ++++++++++++++++++ src/msg.zig | 18 ++++++++++++++++++ src/server.zig | 18 ++++++++++++++++++ 12 files changed, 216 insertions(+) diff --git a/src/cdp/browser.zig b/src/cdp/browser.zig index e6cfa54e..7a970f8b 100644 --- a/src/cdp/browser.zig +++ b/src/cdp/browser.zig @@ -1,3 +1,21 @@ +// Copyright (C) 2023-2024 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 std = @import("std"); const server = @import("../server.zig"); diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 8dbfde52..1a60b400 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -1,3 +1,21 @@ +// Copyright (C) 2023-2024 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 std = @import("std"); const server = @import("../server.zig"); diff --git a/src/cdp/emulation.zig b/src/cdp/emulation.zig index 7bdf7842..3f8f7b57 100644 --- a/src/cdp/emulation.zig +++ b/src/cdp/emulation.zig @@ -1,3 +1,21 @@ +// Copyright (C) 2023-2024 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 std = @import("std"); const server = @import("../server.zig"); diff --git a/src/cdp/fetch.zig b/src/cdp/fetch.zig index 558552a4..c3c390d9 100644 --- a/src/cdp/fetch.zig +++ b/src/cdp/fetch.zig @@ -1,3 +1,21 @@ +// Copyright (C) 2023-2024 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 std = @import("std"); const server = @import("../server.zig"); diff --git a/src/cdp/log.zig b/src/cdp/log.zig index 1358ecd1..ebecea7a 100644 --- a/src/cdp/log.zig +++ b/src/cdp/log.zig @@ -1,3 +1,21 @@ +// Copyright (C) 2023-2024 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 std = @import("std"); const server = @import("../server.zig"); diff --git a/src/cdp/network.zig b/src/cdp/network.zig index c0c79213..49a6a7d9 100644 --- a/src/cdp/network.zig +++ b/src/cdp/network.zig @@ -1,3 +1,21 @@ +// Copyright (C) 2023-2024 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 std = @import("std"); const server = @import("../server.zig"); diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 95d6da08..f4d7463e 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -1,3 +1,21 @@ +// Copyright (C) 2023-2024 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 std = @import("std"); const server = @import("../server.zig"); diff --git a/src/cdp/performance.zig b/src/cdp/performance.zig index ecb8972b..51395ba7 100644 --- a/src/cdp/performance.zig +++ b/src/cdp/performance.zig @@ -1,3 +1,21 @@ +// Copyright (C) 2023-2024 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 std = @import("std"); const server = @import("../server.zig"); diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index 63199ffc..a0c6b90f 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -1,3 +1,21 @@ +// Copyright (C) 2023-2024 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 std = @import("std"); const builtin = @import("builtin"); diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 0531ca26..5c40ce34 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -1,3 +1,21 @@ +// Copyright (C) 2023-2024 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 std = @import("std"); const server = @import("../server.zig"); diff --git a/src/msg.zig b/src/msg.zig index ec3ec507..85411d3b 100644 --- a/src/msg.zig +++ b/src/msg.zig @@ -1,3 +1,21 @@ +// Copyright (C) 2023-2024 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 std = @import("std"); // pub const MaxStdOutSize = 512; // ensure debug msg are not too long diff --git a/src/server.zig b/src/server.zig index 401e6524..3f732327 100644 --- a/src/server.zig +++ b/src/server.zig @@ -1,3 +1,21 @@ +// Copyright (C) 2023-2024 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 std = @import("std"); const public = @import("jsruntime"); From 9c913b2e6c274c3cea5d689a52f12df3fa6b389b Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Mon, 7 Oct 2024 15:57:16 +0200 Subject: [PATCH 079/117] Move loop outside Browser Signed-off-by: Francis Bouvier --- src/browser/browser.zig | 13 +++++-------- src/main.zig | 7 +++++-- src/main_get.zig | 5 ++++- src/server.zig | 22 +++++++++------------- 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 5cf4f3f7..944d590b 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -51,12 +51,12 @@ const log = std.log.scoped(.browser); pub const Browser = struct { session: *Session, - pub fn init(alloc: std.mem.Allocator) !Browser { + pub fn init(alloc: std.mem.Allocator, loop: *Loop) !Browser { // We want to ensure the caller initialised a VM, but the browser // doesn't use it directly... return Browser{ - .session = try Session.init(alloc, "about:blank"), + .session = try Session.init(alloc, loop, "about:blank"), }; } @@ -89,7 +89,6 @@ pub const Session = struct { // TODO handle proxy loader: Loader, env: Env = undefined, - loop: Loop, inspector: ?jsruntime.Inspector = null, window: Window, // TODO move the shed to the browser? @@ -99,7 +98,7 @@ pub const Session = struct { jstypes: [Types.len]usize = undefined, - fn init(alloc: std.mem.Allocator, uri: []const u8) !*Session { + fn init(alloc: std.mem.Allocator, loop: *Loop, uri: []const u8) !*Session { var self = try alloc.create(Session); self.* = Session{ .uri = uri, @@ -107,13 +106,12 @@ pub const Session = struct { .arena = std.heap.ArenaAllocator.init(alloc), .window = Window.create(null), .loader = Loader.init(alloc), - .loop = try Loop.init(alloc), .storageShed = storage.Shed.init(alloc), .httpClient = undefined, }; - self.env = try Env.init(self.arena.allocator(), &self.loop, null); - self.httpClient = .{ .allocator = alloc, .loop = &self.loop }; + self.env = try Env.init(self.arena.allocator(), loop, null); + self.httpClient = .{ .allocator = alloc, .loop = loop }; try self.env.load(&self.jstypes); return self; @@ -132,7 +130,6 @@ pub const Session = struct { self.httpClient.deinit(); self.loader.deinit(); self.storageShed.deinit(); - self.loop.deinit(); self.alloc.destroy(self); } diff --git a/src/main.zig b/src/main.zig index 1c9de927..42a2b487 100644 --- a/src/main.zig +++ b/src/main.zig @@ -161,8 +161,11 @@ pub fn main() !void { defer srv.close(); std.debug.print("Listening on: {s}...\n", .{socket_path}); - var browser = try Browser.init(arena.allocator()); + var loop = try jsruntime.Loop.init(arena.allocator()); + defer loop.deinit(); + + var browser = try Browser.init(arena.allocator(), &loop); defer browser.deinit(); - try server.listen(&browser, srv.sockfd.?); + try server.listen(&browser, &loop, srv.sockfd.?); } diff --git a/src/main_get.zig b/src/main_get.zig index ac646be6..418f77c9 100644 --- a/src/main_get.zig +++ b/src/main_get.zig @@ -80,7 +80,10 @@ pub fn main() !void { const vm = jsruntime.VM.init(); defer vm.deinit(); - var browser = try Browser.init(allocator); + var loop = try jsruntime.Loop.init(allocator); + defer loop.deinit(); + + var browser = try Browser.init(allocator, &loop); defer browser.deinit(); var page = try browser.currentSession().createPage(); diff --git a/src/server.zig b/src/server.zig index 3f732327..45e40d09 100644 --- a/src/server.zig +++ b/src/server.zig @@ -40,6 +40,7 @@ const BufReadSize = 1024; // 1KB const MaxStdOutSize = 512; // ensure debug msg are not too long pub const Cmd = struct { + loop: *public.Loop, // internal fields socket: std.posix.socket_t, @@ -63,7 +64,7 @@ pub const Cmd = struct { if (size == 0) { // continue receving incomming messages asynchronously - self.loop().io.recv(*Cmd, self, cbk, completion, self.socket, self.buf); + self.loop.io.recv(*Cmd, self, cbk, completion, self.socket, self.buf); return; } @@ -84,7 +85,7 @@ pub const Cmd = struct { self.msg_buf.read(self.alloc(), input, self, Cmd.do) catch unreachable; // continue receving incomming messages asynchronously - self.loop().io.recv(*Cmd, self, cbk, completion, self.socket, self.buf); + self.loop.io.recv(*Cmd, self, cbk, completion, self.socket, self.buf); } // shortcuts @@ -93,11 +94,6 @@ pub const Cmd = struct { return self.browser.currentSession().alloc; } - inline fn loop(self: *Cmd) public.Loop { - // TODO: pointer instead? - return self.browser.currentSession().loop; - } - inline fn env(self: Cmd) public.Env { return self.browser.currentSession().env; } @@ -193,7 +189,7 @@ const Send = struct { return; }; - self.cmd.loop().io.send(*Send, self, Send.asyncCbk, completion, self.cmd.socket, self.buf); + self.cmd.loop.io.send(*Send, self, Send.asyncCbk, completion, self.cmd.socket, self.buf); } fn asyncCbk(self: *Send, completion: *Completion, result: SendError!usize) void { @@ -209,12 +205,12 @@ const Send = struct { pub fn sendLater(ctx: *Cmd, msg: []const u8, ns: u63) !void { const sd = try Send.init(ctx, msg); - ctx.loop().io.timeout(*Send, sd.ctx, Send.laterCbk, sd.completion, ns); + ctx.loop.io.timeout(*Send, sd.ctx, Send.laterCbk, sd.completion, ns); } pub fn sendAsync(ctx: *Cmd, msg: []const u8) !void { const sd = try Send.init(ctx, msg); - ctx.loop().io.send(*Send, sd.ctx, Send.asyncCbk, sd.completion, ctx.socket, msg); + ctx.loop.io.send(*Send, sd.ctx, Send.asyncCbk, sd.completion, ctx.socket, msg); } pub fn sendSync(ctx: *Cmd, msg: []const u8) !void { @@ -237,15 +233,14 @@ const Accept = struct { }; // receving incomming messages asynchronously - self.cmd.loop().io.recv(*Cmd, self.cmd, Cmd.cbk, completion, self.cmd.socket, self.cmd.buf); + self.cmd.loop.io.recv(*Cmd, self.cmd, Cmd.cbk, completion, self.cmd.socket, self.cmd.buf); } }; // Listen // ------ -pub fn listen(browser: *Browser, socket: std.posix.socket_t) anyerror!void { - const loop = browser.currentSession().loop; +pub fn listen(browser: *Browser, loop: *public.Loop, socket: std.posix.socket_t) anyerror!void { // MsgBuffer var msg_buf = try MsgBuffer.init(loop.alloc, BufReadSize * 256); // 256KB @@ -255,6 +250,7 @@ pub fn listen(browser: *Browser, socket: std.posix.socket_t) anyerror!void { // for accepting connections and receving messages var ctxInput: [BufReadSize]u8 = undefined; var cmd = Cmd{ + .loop = loop, .browser = browser, .socket = undefined, .buf = &ctxInput, From 4c225e515dd1c42eb3a07fbaff485a540d47076f Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Mon, 7 Oct 2024 16:04:29 +0200 Subject: [PATCH 080/117] server: let the caller of sendSync free the string Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 1 + src/cdp/page.zig | 1 + src/cdp/target.zig | 1 + src/server.zig | 1 - 4 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 1a60b400..245659cc 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -207,6 +207,7 @@ pub fn sendEvent( const resp = Resp{ .method = name, .params = params, .sessionId = sessionID }; const event_msg = try stringify(alloc, resp); + defer alloc.free(event_msg); std.log.debug("event {s}", .{event_msg}); try server.sendSync(ctx, event_msg); } diff --git a/src/cdp/page.zig b/src/cdp/page.zig index f4d7463e..57591088 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -269,6 +269,7 @@ fn navigate( .loaderId = ctx.state.loaderID, }; const res = try result(alloc, id orelse msg.id.?, Resp, resp, msg.sessionID); + defer alloc.free(res); std.log.debug("res {s}", .{res}); try server.sendSync(ctx, res); diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 5c40ce34..0221f0e4 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -308,6 +308,7 @@ fn closeTarget( success: bool = true, }; const res = try result(alloc, id orelse msg.id.?, Resp, Resp{}, null); + defer alloc.free(res); try server.sendSync(ctx, res); // Inspector.detached event diff --git a/src/server.zig b/src/server.zig index 45e40d09..a2249bec 100644 --- a/src/server.zig +++ b/src/server.zig @@ -214,7 +214,6 @@ pub fn sendAsync(ctx: *Cmd, msg: []const u8) !void { } pub fn sendSync(ctx: *Cmd, msg: []const u8) !void { - defer ctx.alloc().free(msg); const s = try std.posix.write(ctx.socket, msg); std.log.debug("send sync {d} bytes", .{s}); } From 76a9034668ba2d9ff4bdc4e964e3fa10dd1e61f9 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Mon, 7 Oct 2024 21:14:55 +0200 Subject: [PATCH 081/117] server: newSession on disposeBrowserContext Signed-off-by: Francis Bouvier --- src/browser/browser.zig | 5 +++++ src/cdp/target.zig | 8 ++++++-- src/server.zig | 20 ++++++++++++++++++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 944d590b..8c13d63e 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -64,6 +64,11 @@ pub const Browser = struct { self.session.deinit(); } + pub fn newSession(self: *Browser, alloc: std.mem.Allocator, loop: *jsruntime.Loop) !void { + self.session.deinit(); + self.session = try Session.init(alloc, loop, "about:blank"); + } + pub fn currentSession(self: *Browser) *Session { return self.session; } diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 0221f0e4..5774f570 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -226,7 +226,7 @@ fn disposeBrowserContext( alloc: std.mem.Allocator, id: ?u16, scanner: *std.json.Scanner, - _: *Ctx, + ctx: *Ctx, ) ![]const u8 { // input @@ -236,7 +236,11 @@ fn disposeBrowserContext( const msg = try getMsg(alloc, Params, scanner); // output - return result(alloc, id orelse msg.id.?, null, {}, null); + const res = try result(alloc, id orelse msg.id.?, null, .{}, null); + defer alloc.free(res); + try server.sendSync(ctx, res); + + return error.DisposeBrowserContext; } // TODO: hard coded IDs diff --git a/src/server.zig b/src/server.zig index a2249bec..d13d4863 100644 --- a/src/server.zig +++ b/src/server.zig @@ -82,7 +82,10 @@ pub const Cmd = struct { } // read and execute input - self.msg_buf.read(self.alloc(), input, self, Cmd.do) catch unreachable; + self.msg_buf.read(self.alloc(), input, self, Cmd.do) catch |err| { + std.log.err("do error: {any}", .{err}); + return; + }; // continue receving incomming messages asynchronously self.loop.io.recv(*Cmd, self, cbk, completion, self.socket, self.buf); @@ -99,7 +102,13 @@ pub const Cmd = struct { } fn do(self: *Cmd, cmd: []const u8) anyerror!void { - const res = try cdp.do(self.alloc(), cmd, self); + const res = cdp.do(self.alloc(), cmd, self) catch |err| { + if (err == error.DisposeBrowserContext) { + try self.newSession(); + return; + } + return err; + }; // send result if (!std.mem.eql(u8, res, "")) { @@ -108,6 +117,13 @@ pub const Cmd = struct { } } + fn newSession(self: *Cmd) !void { + std.log.info("new session", .{}); + try self.browser.newSession(self.alloc(), self.loop); + const cmd_opaque = @as(*anyopaque, @ptrCast(self)); + try self.browser.currentSession().setInspector(cmd_opaque, Cmd.onInspectorResp, Cmd.onInspectorNotif); + } + // Inspector pub fn sendInspector(self: *Cmd, msg: []const u8) void { From 49adb6114630062f2845c4e5aa24586667bdbd1d Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 8 Oct 2024 16:22:24 +0200 Subject: [PATCH 082/117] server: handle close and re-open connection Signed-off-by: Francis Bouvier --- src/server.zig | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/server.zig b/src/server.zig index d13d4863..21a97128 100644 --- a/src/server.zig +++ b/src/server.zig @@ -23,6 +23,7 @@ const Completion = public.IO.Completion; const AcceptError = public.IO.AcceptError; const RecvError = public.IO.RecvError; const SendError = public.IO.SendError; +const CloseError = public.IO.CloseError; const TimeoutError = public.IO.TimeoutError; const MsgBuffer = @import("msg.zig").MsgBuffer; @@ -30,7 +31,7 @@ const Browser = @import("browser/browser.zig").Browser; const cdp = @import("cdp/cdp.zig"); const NoError = error{NoError}; -const IOError = AcceptError || RecvError || SendError || TimeoutError; +const IOError = AcceptError || RecvError || SendError || CloseError || TimeoutError; const Error = IOError || std.fmt.ParseIntError || cdp.Error || NoError; // I/O Recv @@ -47,6 +48,9 @@ pub const Cmd = struct { buf: []u8, // only for read operations err: ?Error = null, + completion: *Completion, + acceptCtx: *Accept = undefined, + msg_buf: *MsgBuffer, // CDP @@ -83,7 +87,9 @@ pub const Cmd = struct { // read and execute input self.msg_buf.read(self.alloc(), input, self, Cmd.do) catch |err| { - std.log.err("do error: {any}", .{err}); + if (err != error.Closed) { + std.log.err("do error: {any}", .{err}); + } return; }; @@ -101,8 +107,20 @@ pub const Cmd = struct { return self.browser.currentSession().env; } + // actions + // ------- + fn do(self: *Cmd, cmd: []const u8) anyerror!void { + + // close cmd + if (std.mem.eql(u8, cmd, "close")) { + self.loop.io.close(*Cmd, self, Cmd.closeCbk, self.completion, self.socket); + return error.Closed; + } + + // cdp cmd const res = cdp.do(self.alloc(), cmd, self) catch |err| { + // cdp end if (err == error.DisposeBrowserContext) { try self.newSession(); return; @@ -124,6 +142,17 @@ pub const Cmd = struct { try self.browser.currentSession().setInspector(cmd_opaque, Cmd.onInspectorResp, Cmd.onInspectorNotif); } + fn closeCbk(self: *Cmd, completion: *Completion, result: CloseError!void) void { + _ = result catch |err| { + self.err = err; + return; + }; + std.log.debug("conn closed", .{}); + + // continue accepting incoming requests + self.loop.io.accept(*Accept, self.acceptCtx, Accept.cbk, completion, self.acceptCtx.socket); + } + // Inspector pub fn sendInspector(self: *Cmd, msg: []const u8) void { @@ -264,12 +293,14 @@ pub fn listen(browser: *Browser, loop: *public.Loop, socket: std.posix.socket_t) // create I/O contexts and callbacks // for accepting connections and receving messages var ctxInput: [BufReadSize]u8 = undefined; + var completion: Completion = undefined; var cmd = Cmd{ .loop = loop, .browser = browser, .socket = undefined, .buf = &ctxInput, .msg_buf = &msg_buf, + .completion = &completion, }; const cmd_opaque = @as(*anyopaque, @ptrCast(&cmd)); try browser.currentSession().setInspector(cmd_opaque, Cmd.onInspectorResp, Cmd.onInspectorNotif); @@ -278,9 +309,9 @@ pub fn listen(browser: *Browser, loop: *public.Loop, socket: std.posix.socket_t) .cmd = &cmd, .socket = socket, }; + cmd.acceptCtx = &accept; // accepting connection asynchronously on internal server - var completion: Completion = undefined; loop.io.accept(*Accept, &accept, Accept.cbk, &completion, socket); // infinite loop on I/O events, either: From c35c09db60f565e8daf336a2a4d5ee49f5f4562b Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 8 Oct 2024 23:40:50 +0200 Subject: [PATCH 083/117] server: timeout mechanism Signed-off-by: Francis Bouvier --- src/server.zig | 158 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 126 insertions(+), 32 deletions(-) diff --git a/src/server.zig b/src/server.zig index 21a97128..25ceddab 100644 --- a/src/server.zig +++ b/src/server.zig @@ -34,6 +34,9 @@ const NoError = error{NoError}; const IOError = AcceptError || RecvError || SendError || CloseError || TimeoutError; const Error = IOError || std.fmt.ParseIntError || cdp.Error || NoError; +const TimeoutCheck = std.time.ns_per_ms * 100; +const TimeoutRead = std.time.ns_per_s * 3; + // I/O Recv // -------- @@ -48,19 +51,28 @@ pub const Cmd = struct { buf: []u8, // only for read operations err: ?Error = null, - completion: *Completion, - acceptCtx: *Accept = undefined, - msg_buf: *MsgBuffer, + // I/O fields + read_completion: *Completion, + timeout_completion: *Completion, + acceptCtx: *Accept = undefined, + last_active: ?std.time.Instant = null, + // CDP state: cdp.State = .{}, // JS fields browser: *Browser, // TODO: is pointer mandatory here? + sessionNew: bool, // try_catch: public.TryCatch, // TODO - fn cbk(self: *Cmd, completion: *Completion, result: RecvError!usize) void { + // callbacks + // --------- + + fn readCbk(self: *Cmd, completion: *Completion, result: RecvError!usize) void { + std.debug.assert(completion == self.read_completion); + const size = result catch |err| { self.err = err; return; @@ -68,7 +80,7 @@ pub const Cmd = struct { if (size == 0) { // continue receving incomming messages asynchronously - self.loop.io.recv(*Cmd, self, cbk, completion, self.socket, self.buf); + self.loop.io.recv(*Cmd, self, Cmd.readCbk, self.read_completion, self.socket, self.buf); return; } @@ -79,12 +91,6 @@ pub const Cmd = struct { std.debug.print("\ninput size: {d}, content: {s}\n", .{ size, content }); } - // close on exit command - if (std.mem.eql(u8, input, "exit")) { - self.err = error.NoError; - return; - } - // read and execute input self.msg_buf.read(self.alloc(), input, self, Cmd.do) catch |err| { if (err != error.Closed) { @@ -93,16 +99,93 @@ pub const Cmd = struct { return; }; + // set connection timestamp + self.last_active = std.time.Instant.now() catch |err| { + std.log.err("read timestamp error: {any}", .{err}); + return; + }; + // continue receving incomming messages asynchronously - self.loop.io.recv(*Cmd, self, cbk, completion, self.socket, self.buf); + self.loop.io.recv(*Cmd, self, Cmd.readCbk, self.read_completion, self.socket, self.buf); + } + + fn timeoutCbk(self: *Cmd, completion: *Completion, result: TimeoutError!void) void { + std.debug.assert(completion == self.timeout_completion); + + _ = result catch |err| { + self.err = err; + return; + }; + + if (self.isClosed()) { + // conn is already closed, ignore timeout + return; + } + + // check time since last read + const now = std.time.Instant.now() catch |err| { + std.log.err("timeout timestamp error: {any}", .{err}); + return; + }; + + if (now.since(self.last_active.?) > TimeoutRead) { + // closing + std.log.debug("conn timeout, closing...", .{}); + + // NOTE: we should cancel the current read + // but it seems that's just closing the connection is enough + // (and cancel does not work on MacOS) + + // close current connection + self.loop.io.close(*Cmd, self, Cmd.closeCbk, self.timeout_completion, self.socket); + return; + } + + // continue checking timeout + self.loop.io.timeout(*Cmd, self, Cmd.timeoutCbk, self.timeout_completion, TimeoutCheck); + } + + fn closeCbk(self: *Cmd, completion: *Completion, result: CloseError!void) void { + _ = completion; + // NOTE: completion can be either self.completion or self.timeout_completion + + _ = result catch |err| { + self.err = err; + return; + }; + + // conn is closed + self.last_active = null; + + // restart a new browser session in case of re-connect + if (!self.sessionNew) { + self.newSession() catch |err| { + std.log.err("new session error: {any}", .{err}); + return; + }; + } + + std.log.debug("conn closed", .{}); + std.log.debug("accepting new conn...", .{}); + + // continue accepting incoming requests + self.loop.io.accept(*Accept, self.acceptCtx, Accept.cbk, self.read_completion, self.acceptCtx.socket); } // shortcuts + // --------- + + inline fn isClosed(self: *Cmd) bool { + // last_active is first saved on acceptCbk + return self.last_active == null; + } + + // allocator of the current session inline fn alloc(self: *Cmd) std.mem.Allocator { - // TODO: should we return the allocator from the page instead? return self.browser.currentSession().alloc; } + // JS env of the current session inline fn env(self: Cmd) public.Env { return self.browser.currentSession().env; } @@ -114,14 +197,19 @@ pub const Cmd = struct { // close cmd if (std.mem.eql(u8, cmd, "close")) { - self.loop.io.close(*Cmd, self, Cmd.closeCbk, self.completion, self.socket); + // close connection + std.log.debug("close cmd, closing...", .{}); + self.loop.io.close(*Cmd, self, Cmd.closeCbk, self.read_completion, self.socket); return error.Closed; } - // cdp cmd + if (self.sessionNew) self.sessionNew = false; + + // cdp end cmd const res = cdp.do(self.alloc(), cmd, self) catch |err| { - // cdp end if (err == error.DisposeBrowserContext) { + // restart a new browser session + std.log.debug("cdp end cmd", .{}); try self.newSession(); return; } @@ -136,24 +224,15 @@ pub const Cmd = struct { } fn newSession(self: *Cmd) !void { - std.log.info("new session", .{}); try self.browser.newSession(self.alloc(), self.loop); const cmd_opaque = @as(*anyopaque, @ptrCast(self)); try self.browser.currentSession().setInspector(cmd_opaque, Cmd.onInspectorResp, Cmd.onInspectorNotif); + self.sessionNew = true; + std.log.debug("new session", .{}); } - fn closeCbk(self: *Cmd, completion: *Completion, result: CloseError!void) void { - _ = result catch |err| { - self.err = err; - return; - }; - std.log.debug("conn closed", .{}); - - // continue accepting incoming requests - self.loop.io.accept(*Accept, self.acceptCtx, Accept.cbk, completion, self.acceptCtx.socket); - } - - // Inspector + // inspector + // --------- pub fn sendInspector(self: *Cmd, msg: []const u8) void { if (self.env().getInspector()) |inspector| { @@ -271,13 +350,22 @@ const Accept = struct { socket: std.posix.socket_t, fn cbk(self: *Accept, completion: *Completion, result: AcceptError!std.posix.socket_t) void { + std.debug.assert(completion == self.cmd.read_completion); + self.cmd.socket = result catch |err| { self.cmd.err = err; return; }; + // set connection timestamp and timeout + self.cmd.last_active = std.time.Instant.now() catch |err| { + std.log.err("accept timestamp error: {any}", .{err}); + return; + }; + self.cmd.loop.io.timeout(*Cmd, self.cmd, Cmd.timeoutCbk, self.cmd.timeout_completion, TimeoutCheck); + // receving incomming messages asynchronously - self.cmd.loop.io.recv(*Cmd, self.cmd, Cmd.cbk, completion, self.cmd.socket, self.cmd.buf); + self.cmd.loop.io.recv(*Cmd, self.cmd, Cmd.readCbk, self.cmd.read_completion, self.cmd.socket, self.cmd.buf); } }; @@ -290,17 +378,22 @@ pub fn listen(browser: *Browser, loop: *public.Loop, socket: std.posix.socket_t) var msg_buf = try MsgBuffer.init(loop.alloc, BufReadSize * 256); // 256KB defer msg_buf.deinit(loop.alloc); + // create I/O completions + var completion: Completion = undefined; + var timeout_completion: Completion = undefined; + // create I/O contexts and callbacks // for accepting connections and receving messages var ctxInput: [BufReadSize]u8 = undefined; - var completion: Completion = undefined; var cmd = Cmd{ .loop = loop, .browser = browser, + .sessionNew = true, .socket = undefined, .buf = &ctxInput, .msg_buf = &msg_buf, - .completion = &completion, + .read_completion = &completion, + .timeout_completion = &timeout_completion, }; const cmd_opaque = @as(*anyopaque, @ptrCast(&cmd)); try browser.currentSession().setInspector(cmd_opaque, Cmd.onInspectorResp, Cmd.onInspectorNotif); @@ -312,6 +405,7 @@ pub fn listen(browser: *Browser, loop: *public.Loop, socket: std.posix.socket_t) cmd.acceptCtx = &accept; // accepting connection asynchronously on internal server + std.log.debug("accepting new conn...", .{}); loop.io.accept(*Accept, &accept, Accept.cbk, &completion, socket); // infinite loop on I/O events, either: From b0ff325125cf933c312fcedcea56ac71acda3970 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 8 Oct 2024 23:44:47 +0200 Subject: [PATCH 084/117] server: move to TCP conn Signed-off-by: Francis Bouvier --- src/main.zig | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/main.zig b/src/main.zig index 42a2b487..fe0ff340 100644 --- a/src/main.zig +++ b/src/main.zig @@ -30,7 +30,9 @@ const apiweb = @import("apiweb.zig"); pub const Types = jsruntime.reflect(apiweb.Interfaces); pub const UserContext = apiweb.UserContext; -const socket_path = "/tmp/browsercore-server.sock"; +// TODO: move to cli options +const host = "127.0.0.1"; +const port = 3245; // Inspired by std.net.StreamServer in Zig < 0.12 pub const StreamServer = struct { @@ -138,18 +140,8 @@ pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); - // remove socket file of internal server - // reuse_address (SO_REUSEADDR flag) does not seems to work on unix socket - // see: https://gavv.net/articles/unix-socket-reuse/ - // TODO: use a lock file instead - std.posix.unlink(socket_path) catch |err| { - if (err != error.FileNotFound) { - return err; - } - }; - // server - const addr = try std.net.Address.initUnix(socket_path); + const addr = try std.net.Address.parseIp4(host, port); var srv = StreamServer.init(.{ .reuse_address = true, .reuse_port = true, @@ -159,7 +151,7 @@ pub fn main() !void { try srv.listen(addr); defer srv.close(); - std.debug.print("Listening on: {s}...\n", .{socket_path}); + std.log.info("Listening on: {s}:{d}...", .{ host, port }); var loop = try jsruntime.Loop.init(arena.allocator()); defer loop.deinit(); From c8a91d4cf6beb152724e9e831d7c17e11b944103 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 00:01:49 +0200 Subject: [PATCH 085/117] server: merge Cmd and Accept in Ctx Signed-off-by: Francis Bouvier --- src/cdp/browser.zig | 2 +- src/cdp/cdp.zig | 2 +- src/cdp/emulation.zig | 2 +- src/cdp/fetch.zig | 2 +- src/cdp/log.zig | 2 +- src/cdp/network.zig | 2 +- src/cdp/page.zig | 2 +- src/cdp/performance.zig | 2 +- src/cdp/runtime.zig | 2 +- src/cdp/target.zig | 2 +- src/server.zig | 160 ++++++++++++++++++---------------------- 11 files changed, 83 insertions(+), 97 deletions(-) diff --git a/src/cdp/browser.zig b/src/cdp/browser.zig index 7a970f8b..60da80e1 100644 --- a/src/cdp/browser.zig +++ b/src/cdp/browser.zig @@ -19,7 +19,7 @@ const std = @import("std"); const server = @import("../server.zig"); -const Ctx = server.Cmd; +const Ctx = server.Ctx; const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 245659cc..94fd8e9d 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -19,7 +19,7 @@ const std = @import("std"); const server = @import("../server.zig"); -const Ctx = server.Cmd; +const Ctx = server.Ctx; const browser = @import("browser.zig").browser; const target = @import("target.zig").target; diff --git a/src/cdp/emulation.zig b/src/cdp/emulation.zig index 3f8f7b57..7d3e1191 100644 --- a/src/cdp/emulation.zig +++ b/src/cdp/emulation.zig @@ -19,7 +19,7 @@ const std = @import("std"); const server = @import("../server.zig"); -const Ctx = server.Cmd; +const Ctx = server.Ctx; const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; diff --git a/src/cdp/fetch.zig b/src/cdp/fetch.zig index c3c390d9..c9def093 100644 --- a/src/cdp/fetch.zig +++ b/src/cdp/fetch.zig @@ -19,7 +19,7 @@ const std = @import("std"); const server = @import("../server.zig"); -const Ctx = server.Cmd; +const Ctx = server.Ctx; const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; diff --git a/src/cdp/log.zig b/src/cdp/log.zig index ebecea7a..26758e0c 100644 --- a/src/cdp/log.zig +++ b/src/cdp/log.zig @@ -19,7 +19,7 @@ const std = @import("std"); const server = @import("../server.zig"); -const Ctx = server.Cmd; +const Ctx = server.Ctx; const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; diff --git a/src/cdp/network.zig b/src/cdp/network.zig index 49a6a7d9..9e366167 100644 --- a/src/cdp/network.zig +++ b/src/cdp/network.zig @@ -19,7 +19,7 @@ const std = @import("std"); const server = @import("../server.zig"); -const Ctx = server.Cmd; +const Ctx = server.Ctx; const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 57591088..7bf3eceb 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -19,7 +19,7 @@ const std = @import("std"); const server = @import("../server.zig"); -const Ctx = server.Cmd; +const Ctx = server.Ctx; const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; diff --git a/src/cdp/performance.zig b/src/cdp/performance.zig index 51395ba7..5c761681 100644 --- a/src/cdp/performance.zig +++ b/src/cdp/performance.zig @@ -19,7 +19,7 @@ const std = @import("std"); const server = @import("../server.zig"); -const Ctx = server.Cmd; +const Ctx = server.Ctx; const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index a0c6b90f..d0fa1dd0 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -22,7 +22,7 @@ const builtin = @import("builtin"); const jsruntime = @import("jsruntime"); const server = @import("../server.zig"); -const Ctx = server.Cmd; +const Ctx = server.Ctx; const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 5774f570..84b3b1a6 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -19,7 +19,7 @@ const std = @import("std"); const server = @import("../server.zig"); -const Ctx = server.Cmd; +const Ctx = server.Ctx; const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; diff --git a/src/server.zig b/src/server.zig index 25ceddab..0950b60c 100644 --- a/src/server.zig +++ b/src/server.zig @@ -35,28 +35,28 @@ const IOError = AcceptError || RecvError || SendError || CloseError || TimeoutEr const Error = IOError || std.fmt.ParseIntError || cdp.Error || NoError; const TimeoutCheck = std.time.ns_per_ms * 100; -const TimeoutRead = std.time.ns_per_s * 3; +const TimeoutRead = std.time.ns_per_s * 3; // TODO: cli option -// I/O Recv +// I/O Main // -------- const BufReadSize = 1024; // 1KB const MaxStdOutSize = 512; // ensure debug msg are not too long -pub const Cmd = struct { +pub const Ctx = struct { loop: *public.Loop, // internal fields - socket: std.posix.socket_t, + accept_socket: std.posix.socket_t, + conn_socket: std.posix.socket_t = undefined, buf: []u8, // only for read operations err: ?Error = null, msg_buf: *MsgBuffer, // I/O fields - read_completion: *Completion, + conn_completion: *Completion, timeout_completion: *Completion, - acceptCtx: *Accept = undefined, last_active: ?std.time.Instant = null, // CDP @@ -70,8 +70,27 @@ pub const Cmd = struct { // callbacks // --------- - fn readCbk(self: *Cmd, completion: *Completion, result: RecvError!usize) void { - std.debug.assert(completion == self.read_completion); + fn acceptCbk(self: *Ctx, completion: *Completion, result: AcceptError!std.posix.socket_t) void { + std.debug.assert(completion == self.conn_completion); + + self.conn_socket = result catch |err| { + self.err = err; + return; + }; + + // set connection timestamp and timeout + self.last_active = std.time.Instant.now() catch |err| { + std.log.err("accept timestamp error: {any}", .{err}); + return; + }; + self.loop.io.timeout(*Ctx, self, Ctx.timeoutCbk, self.timeout_completion, TimeoutCheck); + + // receving incomming messages asynchronously + self.loop.io.recv(*Ctx, self, Ctx.readCbk, self.conn_completion, self.conn_socket, self.buf); + } + + fn readCbk(self: *Ctx, completion: *Completion, result: RecvError!usize) void { + std.debug.assert(completion == self.conn_completion); const size = result catch |err| { self.err = err; @@ -80,7 +99,7 @@ pub const Cmd = struct { if (size == 0) { // continue receving incomming messages asynchronously - self.loop.io.recv(*Cmd, self, Cmd.readCbk, self.read_completion, self.socket, self.buf); + self.loop.io.recv(*Ctx, self, Ctx.readCbk, self.conn_completion, self.conn_socket, self.buf); return; } @@ -92,7 +111,7 @@ pub const Cmd = struct { } // read and execute input - self.msg_buf.read(self.alloc(), input, self, Cmd.do) catch |err| { + self.msg_buf.read(self.alloc(), input, self, Ctx.do) catch |err| { if (err != error.Closed) { std.log.err("do error: {any}", .{err}); } @@ -106,10 +125,10 @@ pub const Cmd = struct { }; // continue receving incomming messages asynchronously - self.loop.io.recv(*Cmd, self, Cmd.readCbk, self.read_completion, self.socket, self.buf); + self.loop.io.recv(*Ctx, self, Ctx.readCbk, self.conn_completion, self.conn_socket, self.buf); } - fn timeoutCbk(self: *Cmd, completion: *Completion, result: TimeoutError!void) void { + fn timeoutCbk(self: *Ctx, completion: *Completion, result: TimeoutError!void) void { std.debug.assert(completion == self.timeout_completion); _ = result catch |err| { @@ -137,17 +156,17 @@ pub const Cmd = struct { // (and cancel does not work on MacOS) // close current connection - self.loop.io.close(*Cmd, self, Cmd.closeCbk, self.timeout_completion, self.socket); + self.loop.io.close(*Ctx, self, Ctx.closeCbk, self.timeout_completion, self.conn_socket); return; } // continue checking timeout - self.loop.io.timeout(*Cmd, self, Cmd.timeoutCbk, self.timeout_completion, TimeoutCheck); + self.loop.io.timeout(*Ctx, self, Ctx.timeoutCbk, self.timeout_completion, TimeoutCheck); } - fn closeCbk(self: *Cmd, completion: *Completion, result: CloseError!void) void { + fn closeCbk(self: *Ctx, completion: *Completion, result: CloseError!void) void { _ = completion; - // NOTE: completion can be either self.completion or self.timeout_completion + // NOTE: completion can be either self.conn_completion or self.timeout_completion _ = result catch |err| { self.err = err; @@ -169,37 +188,37 @@ pub const Cmd = struct { std.log.debug("accepting new conn...", .{}); // continue accepting incoming requests - self.loop.io.accept(*Accept, self.acceptCtx, Accept.cbk, self.read_completion, self.acceptCtx.socket); + self.loop.io.accept(*Ctx, self, Ctx.acceptCbk, self.conn_completion, self.accept_socket); } // shortcuts // --------- - inline fn isClosed(self: *Cmd) bool { + inline fn isClosed(self: *Ctx) bool { // last_active is first saved on acceptCbk return self.last_active == null; } // allocator of the current session - inline fn alloc(self: *Cmd) std.mem.Allocator { + inline fn alloc(self: *Ctx) std.mem.Allocator { return self.browser.currentSession().alloc; } // JS env of the current session - inline fn env(self: Cmd) public.Env { + inline fn env(self: Ctx) public.Env { return self.browser.currentSession().env; } // actions // ------- - fn do(self: *Cmd, cmd: []const u8) anyerror!void { + fn do(self: *Ctx, cmd: []const u8) anyerror!void { // close cmd if (std.mem.eql(u8, cmd, "close")) { // close connection std.log.debug("close cmd, closing...", .{}); - self.loop.io.close(*Cmd, self, Cmd.closeCbk, self.read_completion, self.socket); + self.loop.io.close(*Ctx, self, Ctx.closeCbk, self.conn_completion, self.conn_socket); return error.Closed; } @@ -223,10 +242,10 @@ pub const Cmd = struct { } } - fn newSession(self: *Cmd) !void { + fn newSession(self: *Ctx) !void { try self.browser.newSession(self.alloc(), self.loop); - const cmd_opaque = @as(*anyopaque, @ptrCast(self)); - try self.browser.currentSession().setInspector(cmd_opaque, Cmd.onInspectorResp, Cmd.onInspectorNotif); + const ctx_opaque = @as(*anyopaque, @ptrCast(self)); + try self.browser.currentSession().setInspector(ctx_opaque, Ctx.onInspectorResp, Ctx.onInspectorNotif); self.sessionNew = true; std.log.debug("new session", .{}); } @@ -234,7 +253,7 @@ pub const Cmd = struct { // inspector // --------- - pub fn sendInspector(self: *Cmd, msg: []const u8) void { + pub fn sendInspector(self: *Ctx, msg: []const u8) void { if (self.env().getInspector()) |inspector| { inspector.send(self.env(), msg); } @@ -242,8 +261,8 @@ pub const Cmd = struct { pub fn onInspectorResp(cmd_opaque: *anyopaque, _: u32, msg: []const u8) void { std.log.debug("onResp biz fn called: {s}", .{msg}); - const aligned = @as(*align(@alignOf(Cmd)) anyopaque, @alignCast(cmd_opaque)); - const self = @as(*Cmd, @ptrCast(aligned)); + const aligned = @as(*align(@alignOf(Ctx)) anyopaque, @alignCast(cmd_opaque)); + const self = @as(*Ctx, @ptrCast(aligned)); const tpl = "{s},\"sessionId\":\"{s}\"}}"; const msg_open = msg[0 .. msg.len - 1]; // remove closing bracket @@ -259,8 +278,8 @@ pub const Cmd = struct { pub fn onInspectorNotif(cmd_opaque: *anyopaque, msg: []const u8) void { std.log.debug("onNotif biz fn called: {s}", .{msg}); - const aligned = @as(*align(@alignOf(Cmd)) anyopaque, @alignCast(cmd_opaque)); - const self = @as(*Cmd, @ptrCast(aligned)); + const aligned = @as(*align(@alignOf(Ctx)) anyopaque, @alignCast(cmd_opaque)); + const self = @as(*Ctx, @ptrCast(aligned)); const tpl = "{s},\"sessionId\":\"{s}\"}}"; const msg_open = msg[0 .. msg.len - 1]; // remove closing bracket @@ -280,10 +299,10 @@ pub const Cmd = struct { // -------- const Send = struct { - cmd: *Cmd, + ctx: *Ctx, buf: []const u8, - fn init(ctx: *Cmd, msg: []const u8) !struct { + fn init(ctx: *Ctx, msg: []const u8) !struct { ctx: *Send, completion: *Completion, } { @@ -291,34 +310,34 @@ const Send = struct { // recv and timeout operations, that's why we create a new completion here const completion = try ctx.alloc().create(Completion); // NOTE: to handle concurrent calls we create each time a new context - // If no concurrent calls where required we could just use the main CmdCtx + // If no concurrent calls where required we could just use the main Ctx const sd = try ctx.alloc().create(Send); sd.* = .{ - .cmd = ctx, + .ctx = ctx, .buf = msg, }; return .{ .ctx = sd, .completion = completion }; } fn deinit(self: *Send, completion: *Completion) void { - self.cmd.alloc().destroy(completion); - self.cmd.alloc().free(self.buf); - self.cmd.alloc().destroy(self); + self.ctx.alloc().destroy(completion); + self.ctx.alloc().free(self.buf); + self.ctx.alloc().destroy(self); } fn laterCbk(self: *Send, completion: *Completion, result: TimeoutError!void) void { std.log.debug("sending after", .{}); _ = result catch |err| { - self.cmd.err = err; + self.ctx.err = err; return; }; - self.cmd.loop.io.send(*Send, self, Send.asyncCbk, completion, self.cmd.socket, self.buf); + self.ctx.loop.io.send(*Send, self, Send.asyncCbk, completion, self.ctx.socket, self.buf); } fn asyncCbk(self: *Send, completion: *Completion, result: SendError!usize) void { const size = result catch |err| { - self.cmd.err = err; + self.ctx.err = err; return; }; @@ -327,86 +346,53 @@ const Send = struct { } }; -pub fn sendLater(ctx: *Cmd, msg: []const u8, ns: u63) !void { +pub fn sendLater(ctx: *Ctx, msg: []const u8, ns: u63) !void { const sd = try Send.init(ctx, msg); ctx.loop.io.timeout(*Send, sd.ctx, Send.laterCbk, sd.completion, ns); } -pub fn sendAsync(ctx: *Cmd, msg: []const u8) !void { +pub fn sendAsync(ctx: *Ctx, msg: []const u8) !void { const sd = try Send.init(ctx, msg); - ctx.loop.io.send(*Send, sd.ctx, Send.asyncCbk, sd.completion, ctx.socket, msg); + ctx.loop.io.send(*Send, sd.ctx, Send.asyncCbk, sd.completion, ctx.conn_socket, msg); } -pub fn sendSync(ctx: *Cmd, msg: []const u8) !void { - const s = try std.posix.write(ctx.socket, msg); +pub fn sendSync(ctx: *Ctx, msg: []const u8) !void { + const s = try std.posix.write(ctx.conn_socket, msg); std.log.debug("send sync {d} bytes", .{s}); } -// I/O Accept -// ---------- - -const Accept = struct { - cmd: *Cmd, - socket: std.posix.socket_t, - - fn cbk(self: *Accept, completion: *Completion, result: AcceptError!std.posix.socket_t) void { - std.debug.assert(completion == self.cmd.read_completion); - - self.cmd.socket = result catch |err| { - self.cmd.err = err; - return; - }; - - // set connection timestamp and timeout - self.cmd.last_active = std.time.Instant.now() catch |err| { - std.log.err("accept timestamp error: {any}", .{err}); - return; - }; - self.cmd.loop.io.timeout(*Cmd, self.cmd, Cmd.timeoutCbk, self.cmd.timeout_completion, TimeoutCheck); - - // receving incomming messages asynchronously - self.cmd.loop.io.recv(*Cmd, self.cmd, Cmd.readCbk, self.cmd.read_completion, self.cmd.socket, self.cmd.buf); - } -}; - // Listen // ------ -pub fn listen(browser: *Browser, loop: *public.Loop, socket: std.posix.socket_t) anyerror!void { +pub fn listen(browser: *Browser, loop: *public.Loop, server_socket: std.posix.socket_t) anyerror!void { // MsgBuffer var msg_buf = try MsgBuffer.init(loop.alloc, BufReadSize * 256); // 256KB defer msg_buf.deinit(loop.alloc); // create I/O completions - var completion: Completion = undefined; + var conn_completion: Completion = undefined; var timeout_completion: Completion = undefined; // create I/O contexts and callbacks // for accepting connections and receving messages var ctxInput: [BufReadSize]u8 = undefined; - var cmd = Cmd{ + var ctx = Ctx{ .loop = loop, .browser = browser, .sessionNew = true, - .socket = undefined, .buf = &ctxInput, .msg_buf = &msg_buf, - .read_completion = &completion, + .accept_socket = server_socket, + .conn_completion = &conn_completion, .timeout_completion = &timeout_completion, }; - const cmd_opaque = @as(*anyopaque, @ptrCast(&cmd)); - try browser.currentSession().setInspector(cmd_opaque, Cmd.onInspectorResp, Cmd.onInspectorNotif); - - var accept = Accept{ - .cmd = &cmd, - .socket = socket, - }; - cmd.acceptCtx = &accept; + const ctx_opaque = @as(*anyopaque, @ptrCast(ctx)); + try browser.currentSession().setInspector(ctx_opaque, Ctx.onInspectorResp, Ctx.onInspectorNotif); // accepting connection asynchronously on internal server std.log.debug("accepting new conn...", .{}); - loop.io.accept(*Accept, &accept, Accept.cbk, &completion, socket); + loop.io.accept(*Ctx, &ctx, Ctx.acceptCbk, ctx.conn_completion, ctx.accept_socket); // infinite loop on I/O events, either: // - cmd from incoming connection on server socket @@ -421,7 +407,7 @@ pub fn listen(browser: *Browser, loop: *public.Loop, socket: std.posix.socket_t) // } // loop.cbk_error = false; } - if (cmd.err) |err| { + if (ctx.err) |err| { if (err != error.NoError) std.log.err("Server error: {any}", .{err}); break; } From cea38a10e9553e3243432d4874c64ed607e3633d Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 00:07:49 +0200 Subject: [PATCH 086/117] server: rename buf in read_buf Signed-off-by: Francis Bouvier --- src/server.zig | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/server.zig b/src/server.zig index 0950b60c..ceee52b4 100644 --- a/src/server.zig +++ b/src/server.zig @@ -49,10 +49,9 @@ pub const Ctx = struct { // internal fields accept_socket: std.posix.socket_t, conn_socket: std.posix.socket_t = undefined, - buf: []u8, // only for read operations - err: ?Error = null, - + read_buf: []u8, // only for read operations msg_buf: *MsgBuffer, + err: ?Error = null, // I/O fields conn_completion: *Completion, @@ -86,7 +85,7 @@ pub const Ctx = struct { self.loop.io.timeout(*Ctx, self, Ctx.timeoutCbk, self.timeout_completion, TimeoutCheck); // receving incomming messages asynchronously - self.loop.io.recv(*Ctx, self, Ctx.readCbk, self.conn_completion, self.conn_socket, self.buf); + self.loop.io.recv(*Ctx, self, Ctx.readCbk, self.conn_completion, self.conn_socket, self.read_buf); } fn readCbk(self: *Ctx, completion: *Completion, result: RecvError!usize) void { @@ -99,16 +98,16 @@ pub const Ctx = struct { if (size == 0) { // continue receving incomming messages asynchronously - self.loop.io.recv(*Ctx, self, Ctx.readCbk, self.conn_completion, self.conn_socket, self.buf); + self.loop.io.recv(*Ctx, self, Ctx.readCbk, self.conn_completion, self.conn_socket, self.read_buf); return; } // input - const input = self.buf[0..size]; if (std.log.defaultLogEnabled(.debug)) { const content = input[0..@min(MaxStdOutSize, size)]; std.debug.print("\ninput size: {d}, content: {s}\n", .{ size, content }); } + const input = self.read_buf[0..size]; // read and execute input self.msg_buf.read(self.alloc(), input, self, Ctx.do) catch |err| { @@ -125,7 +124,7 @@ pub const Ctx = struct { }; // continue receving incomming messages asynchronously - self.loop.io.recv(*Ctx, self, Ctx.readCbk, self.conn_completion, self.conn_socket, self.buf); + self.loop.io.recv(*Ctx, self, Ctx.readCbk, self.conn_completion, self.conn_socket, self.read_buf); } fn timeoutCbk(self: *Ctx, completion: *Completion, result: TimeoutError!void) void { @@ -224,14 +223,16 @@ pub const Ctx = struct { if (self.sessionNew) self.sessionNew = false; - // cdp end cmd const res = cdp.do(self.alloc(), cmd, self) catch |err| { + + // cdp end cmd if (err == error.DisposeBrowserContext) { // restart a new browser session std.log.debug("cdp end cmd", .{}); try self.newSession(); return; } + return err; }; @@ -366,7 +367,8 @@ pub fn sendSync(ctx: *Ctx, msg: []const u8) !void { pub fn listen(browser: *Browser, loop: *public.Loop, server_socket: std.posix.socket_t) anyerror!void { - // MsgBuffer + // create buffers + var read_buf: [BufReadSize]u8 = undefined; var msg_buf = try MsgBuffer.init(loop.alloc, BufReadSize * 256); // 256KB defer msg_buf.deinit(loop.alloc); @@ -376,12 +378,11 @@ pub fn listen(browser: *Browser, loop: *public.Loop, server_socket: std.posix.so // create I/O contexts and callbacks // for accepting connections and receving messages - var ctxInput: [BufReadSize]u8 = undefined; var ctx = Ctx{ .loop = loop, .browser = browser, .sessionNew = true, - .buf = &ctxInput, + .read_buf = &read_buf, .msg_buf = &msg_buf, .accept_socket = server_socket, .conn_completion = &conn_completion, From a2f65eb540d52f0c42cd08dffac7d638d6f29645 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 00:22:41 +0200 Subject: [PATCH 087/117] server: simplify onInspector methods Signed-off-by: Francis Bouvier --- src/server.zig | 48 ++++++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/src/server.zig b/src/server.zig index ceee52b4..bbe41d3f 100644 --- a/src/server.zig +++ b/src/server.zig @@ -260,39 +260,31 @@ pub const Ctx = struct { } } - pub fn onInspectorResp(cmd_opaque: *anyopaque, _: u32, msg: []const u8) void { - std.log.debug("onResp biz fn called: {s}", .{msg}); - const aligned = @as(*align(@alignOf(Ctx)) anyopaque, @alignCast(cmd_opaque)); - const self = @as(*Ctx, @ptrCast(aligned)); - - const tpl = "{s},\"sessionId\":\"{s}\"}}"; - const msg_open = msg[0 .. msg.len - 1]; // remove closing bracket - const s = std.fmt.allocPrint( - self.alloc(), - tpl, - .{ msg_open, cdp.ContextSessionID }, - ) catch unreachable; - defer self.alloc().free(s); - - sendSync(self, s) catch unreachable; + inline fn inspectorCtx(ctx_opaque: *anyopaque) *Ctx { + const aligned = @as(*align(@alignOf(Ctx)) anyopaque, @alignCast(ctx_opaque)); + return @as(*Ctx, @ptrCast(aligned)); } - pub fn onInspectorNotif(cmd_opaque: *anyopaque, msg: []const u8) void { - std.log.debug("onNotif biz fn called: {s}", .{msg}); - const aligned = @as(*align(@alignOf(Ctx)) anyopaque, @alignCast(cmd_opaque)); - const self = @as(*Ctx, @ptrCast(aligned)); - + fn inspectorMsg(allocator: std.mem.Allocator, ctx: *Ctx, msg: []const u8) !void { + // inject sessionID in cdp msg const tpl = "{s},\"sessionId\":\"{s}\"}}"; const msg_open = msg[0 .. msg.len - 1]; // remove closing bracket - const s = std.fmt.allocPrint( - self.alloc(), - tpl, - .{ msg_open, cdp.ContextSessionID }, - ) catch unreachable; - defer self.alloc().free(s); - std.log.debug("event: {s}", .{s}); + const s = try std.fmt.allocPrint(allocator, tpl, .{ msg_open, cdp.ContextSessionID }); + defer ctx.alloc().free(s); - sendSync(self, s) catch unreachable; + try sendSync(ctx, s); + } + + pub fn onInspectorResp(ctx_opaque: *anyopaque, _: u32, msg: []const u8) void { + std.log.debug("inspector resp: {s}", .{msg}); + const ctx = inspectorCtx(ctx_opaque); + inspectorMsg(ctx.alloc(), ctx, msg) catch unreachable; + } + + pub fn onInspectorNotif(ctx_opaque: *anyopaque, msg: []const u8) void { + std.log.debug("inspector event: {s}", .{msg}); + const ctx = inspectorCtx(ctx_opaque); + inspectorMsg(ctx.alloc(), ctx, msg) catch unreachable; } }; From f9b097794ff877773feed4cf349b858d6c76a363 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 00:58:13 +0200 Subject: [PATCH 088/117] Simplify browser session.setInspector Signed-off-by: Francis Bouvier --- src/browser/browser.zig | 5 +++-- src/server.zig | 6 ++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 8c13d63e..5649638d 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -140,11 +140,12 @@ pub const Session = struct { pub fn setInspector( self: *Session, - ctx: *anyopaque, + ctx: anytype, onResp: jsruntime.InspectorOnResponseFn, onEvent: jsruntime.InspectorOnEventFn, ) !void { - self.inspector = try jsruntime.Inspector.init(self.alloc, self.env, ctx, onResp, onEvent); + const ctx_opaque = @as(*anyopaque, @ptrCast(ctx)); + self.inspector = try jsruntime.Inspector.init(self.alloc, self.env, ctx_opaque, onResp, onEvent); self.env.setInspector(self.inspector.?); } diff --git a/src/server.zig b/src/server.zig index bbe41d3f..22b9f9d9 100644 --- a/src/server.zig +++ b/src/server.zig @@ -245,8 +245,7 @@ pub const Ctx = struct { fn newSession(self: *Ctx) !void { try self.browser.newSession(self.alloc(), self.loop); - const ctx_opaque = @as(*anyopaque, @ptrCast(self)); - try self.browser.currentSession().setInspector(ctx_opaque, Ctx.onInspectorResp, Ctx.onInspectorNotif); + try self.browser.currentSession().setInspector(self, Ctx.onInspectorResp, Ctx.onInspectorNotif); self.sessionNew = true; std.log.debug("new session", .{}); } @@ -380,8 +379,7 @@ pub fn listen(browser: *Browser, loop: *public.Loop, server_socket: std.posix.so .conn_completion = &conn_completion, .timeout_completion = &timeout_completion, }; - const ctx_opaque = @as(*anyopaque, @ptrCast(ctx)); - try browser.currentSession().setInspector(ctx_opaque, Ctx.onInspectorResp, Ctx.onInspectorNotif); + try browser.currentSession().setInspector(&ctx, Ctx.onInspectorResp, Ctx.onInspectorNotif); // accepting connection asynchronously on internal server std.log.debug("accepting new conn...", .{}); From 15414f5ee41cb62453ea7ea663403926546ac373 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 01:00:12 +0200 Subject: [PATCH 089/117] server: remove unused sendLater Signed-off-by: Francis Bouvier --- src/server.zig | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/server.zig b/src/server.zig index 22b9f9d9..fd5cf8fd 100644 --- a/src/server.zig +++ b/src/server.zig @@ -317,16 +317,6 @@ const Send = struct { self.ctx.alloc().destroy(self); } - fn laterCbk(self: *Send, completion: *Completion, result: TimeoutError!void) void { - std.log.debug("sending after", .{}); - _ = result catch |err| { - self.ctx.err = err; - return; - }; - - self.ctx.loop.io.send(*Send, self, Send.asyncCbk, completion, self.ctx.socket, self.buf); - } - fn asyncCbk(self: *Send, completion: *Completion, result: SendError!usize) void { const size = result catch |err| { self.ctx.err = err; @@ -338,11 +328,6 @@ const Send = struct { } }; -pub fn sendLater(ctx: *Ctx, msg: []const u8, ns: u63) !void { - const sd = try Send.init(ctx, msg); - ctx.loop.io.timeout(*Send, sd.ctx, Send.laterCbk, sd.completion, ns); -} - pub fn sendAsync(ctx: *Ctx, msg: []const u8) !void { const sd = try Send.init(ctx, msg); ctx.loop.io.send(*Send, sd.ctx, Send.asyncCbk, sd.completion, ctx.conn_socket, msg); From ff0bbc3f961f5702ffb835863f29bd00e7ea1a4c Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 01:21:24 +0200 Subject: [PATCH 090/117] server: simplify Send I/O Signed-off-by: Francis Bouvier --- src/server.zig | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/server.zig b/src/server.zig index fd5cf8fd..0cbae978 100644 --- a/src/server.zig +++ b/src/server.zig @@ -290,47 +290,40 @@ pub const Ctx = struct { // I/O Send // -------- +// NOTE: to allow concurrent send we create each time a dedicated context +// (with its own completion), allocated on the heap. +// After the send (on the sendCbk) the dedicated context will be destroy +// and the msg slice will be free. const Send = struct { ctx: *Ctx, - buf: []const u8, + msg: []const u8, + completion: Completion = undefined, - fn init(ctx: *Ctx, msg: []const u8) !struct { - ctx: *Send, - completion: *Completion, - } { - // NOTE: it seems we can't use the same completion for concurrent - // recv and timeout operations, that's why we create a new completion here - const completion = try ctx.alloc().create(Completion); - // NOTE: to handle concurrent calls we create each time a new context - // If no concurrent calls where required we could just use the main Ctx + fn init(ctx: *Ctx, msg: []const u8) !*Send { const sd = try ctx.alloc().create(Send); - sd.* = .{ - .ctx = ctx, - .buf = msg, - }; - return .{ .ctx = sd, .completion = completion }; + sd.* = .{ .ctx = ctx, .msg = msg }; + return sd; } - fn deinit(self: *Send, completion: *Completion) void { - self.ctx.alloc().destroy(completion); - self.ctx.alloc().free(self.buf); + fn deinit(self: *Send) void { + self.ctx.alloc().free(self.msg); self.ctx.alloc().destroy(self); } - fn asyncCbk(self: *Send, completion: *Completion, result: SendError!usize) void { + fn asyncCbk(self: *Send, _: *Completion, result: SendError!usize) void { const size = result catch |err| { self.ctx.err = err; return; }; std.log.debug("send async {d} bytes", .{size}); - self.deinit(completion); + self.deinit(); } }; pub fn sendAsync(ctx: *Ctx, msg: []const u8) !void { const sd = try Send.init(ctx, msg); - ctx.loop.io.send(*Send, sd.ctx, Send.asyncCbk, sd.completion, ctx.conn_socket, msg); + ctx.loop.io.send(*Send, sd, Send.asyncCbk, &sd.completion, ctx.conn_socket, msg); } pub fn sendSync(ctx: *Ctx, msg: []const u8) !void { From 9400dd799e20eaf9d1a4956ed139cb62f367aa29 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 12:06:39 +0200 Subject: [PATCH 091/117] Add cli options for server (host, port, timeout) Signed-off-by: Francis Bouvier --- src/main.zig | 100 +++++++++++++++++++++++++++++++++++++++++++------ src/server.zig | 12 ++++-- 2 files changed, 98 insertions(+), 14 deletions(-) diff --git a/src/main.zig b/src/main.zig index fe0ff340..7636a622 100644 --- a/src/main.zig +++ b/src/main.zig @@ -30,9 +30,21 @@ const apiweb = @import("apiweb.zig"); pub const Types = jsruntime.reflect(apiweb.Interfaces); pub const UserContext = apiweb.UserContext; -// TODO: move to cli options -const host = "127.0.0.1"; -const port = 3245; +// Default options +const Host = "127.0.0.1"; +const Port = 3245; +const Timeout = 3; // in seconds + +const usage = + \\usage: {s} [options] + \\ start Lightpanda browser in CDP server mode + \\ + \\ -h, --help Print this help message and exit. + \\ --host Host of the server (default "127.0.0.1") + \\ --port Port of the server (default "3245") + \\ --timeout Timeout for incoming connections in seconds (default "3") + \\ +; // Inspired by std.net.StreamServer in Zig < 0.12 pub const StreamServer = struct { @@ -130,18 +142,74 @@ pub const StreamServer = struct { } }; +fn printUsageExit(execname: []const u8, res: u8) void { + std.io.getStdErr().writer().print(usage, .{execname}) catch |err| { + std.log.err("Print usage error: {any}", .{err}); + std.posix.exit(1); + }; + std.posix.exit(res); +} + pub fn main() !void { - // create v8 vm - const vm = jsruntime.VM.init(); - defer vm.deinit(); - - // alloc + // allocator var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); + // args + var args = try std.process.argsWithAllocator(arena.allocator()); + defer args.deinit(); + + const execname = args.next().?; + var host: []const u8 = Host; + var port: u16 = Port; + var addr: std.net.Address = undefined; + var timeout: u8 = undefined; + + while (args.next()) |opt| { + if (std.mem.eql(u8, "-h", opt) or std.mem.eql(u8, "--help", opt)) { + printUsageExit(execname, 0); + } + if (std.mem.eql(u8, "--host", opt)) { + if (args.next()) |arg| { + host = arg; + continue; + } else { + std.log.err("--host not provided\n", .{}); + return printUsageExit(execname, 1); + } + } + if (std.mem.eql(u8, "--port", opt)) { + if (args.next()) |arg| { + port = std.fmt.parseInt(u16, arg, 10) catch |err| { + std.log.err("--port {any}\n", .{err}); + return printUsageExit(execname, 1); + }; + continue; + } else { + std.log.err("--port not provided\n", .{}); + return printUsageExit(execname, 1); + } + } + if (std.mem.eql(u8, "--timeout", opt)) { + if (args.next()) |arg| { + timeout = std.fmt.parseInt(u8, arg, 10) catch |err| { + std.log.err("--timeout {any}\n", .{err}); + return printUsageExit(execname, 1); + }; + continue; + } else { + std.log.err("--timeout not provided\n", .{}); + return printUsageExit(execname, 1); + } + } + } + addr = std.net.Address.parseIp4(host, port) catch |err| { + std.log.err("address (host:port) {any}\n", .{err}); + return printUsageExit(execname, 1); + }; + // server - const addr = try std.net.Address.parseIp4(host, port); var srv = StreamServer.init(.{ .reuse_address = true, .reuse_port = true, @@ -149,15 +217,25 @@ pub fn main() !void { }); defer srv.deinit(); - try srv.listen(addr); + srv.listen(addr) catch |err| { + std.log.err("address (host:port) {any}\n", .{err}); + return printUsageExit(execname, 1); + }; defer srv.close(); std.log.info("Listening on: {s}:{d}...", .{ host, port }); + // create v8 vm + const vm = jsruntime.VM.init(); + defer vm.deinit(); + + // loop var loop = try jsruntime.Loop.init(arena.allocator()); defer loop.deinit(); + // browser var browser = try Browser.init(arena.allocator(), &loop); defer browser.deinit(); - try server.listen(&browser, &loop, srv.sockfd.?); + // listen + try server.listen(&browser, &loop, srv.sockfd.?, std.time.ns_per_s * @as(u64, timeout)); } diff --git a/src/server.zig b/src/server.zig index 0cbae978..8e710148 100644 --- a/src/server.zig +++ b/src/server.zig @@ -35,7 +35,6 @@ const IOError = AcceptError || RecvError || SendError || CloseError || TimeoutEr const Error = IOError || std.fmt.ParseIntError || cdp.Error || NoError; const TimeoutCheck = std.time.ns_per_ms * 100; -const TimeoutRead = std.time.ns_per_s * 3; // TODO: cli option // I/O Main // -------- @@ -56,6 +55,7 @@ pub const Ctx = struct { // I/O fields conn_completion: *Completion, timeout_completion: *Completion, + timeout: u64, last_active: ?std.time.Instant = null, // CDP @@ -146,7 +146,7 @@ pub const Ctx = struct { return; }; - if (now.since(self.last_active.?) > TimeoutRead) { + if (now.since(self.last_active.?) > self.timeout) { // closing std.log.debug("conn timeout, closing...", .{}); @@ -334,7 +334,12 @@ pub fn sendSync(ctx: *Ctx, msg: []const u8) !void { // Listen // ------ -pub fn listen(browser: *Browser, loop: *public.Loop, server_socket: std.posix.socket_t) anyerror!void { +pub fn listen( + browser: *Browser, + loop: *public.Loop, + server_socket: std.posix.socket_t, + timeout: u64, +) anyerror!void { // create buffers var read_buf: [BufReadSize]u8 = undefined; @@ -354,6 +359,7 @@ pub fn listen(browser: *Browser, loop: *public.Loop, server_socket: std.posix.so .read_buf = &read_buf, .msg_buf = &msg_buf, .accept_socket = server_socket, + .timeout = timeout, .conn_completion = &conn_completion, .timeout_completion = &timeout_completion, }; From c564702eac1b9ef2893f2f2993473c54061e1686 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 12:10:54 +0200 Subject: [PATCH 092/117] server: formatting Signed-off-by: Francis Bouvier --- src/server.zig | 91 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 79 insertions(+), 12 deletions(-) diff --git a/src/server.zig b/src/server.zig index 8e710148..faa418bf 100644 --- a/src/server.zig +++ b/src/server.zig @@ -69,7 +69,11 @@ pub const Ctx = struct { // callbacks // --------- - fn acceptCbk(self: *Ctx, completion: *Completion, result: AcceptError!std.posix.socket_t) void { + fn acceptCbk( + self: *Ctx, + completion: *Completion, + result: AcceptError!std.posix.socket_t, + ) void { std.debug.assert(completion == self.conn_completion); self.conn_socket = result catch |err| { @@ -82,10 +86,23 @@ pub const Ctx = struct { std.log.err("accept timestamp error: {any}", .{err}); return; }; - self.loop.io.timeout(*Ctx, self, Ctx.timeoutCbk, self.timeout_completion, TimeoutCheck); + self.loop.io.timeout( + *Ctx, + self, + Ctx.timeoutCbk, + self.timeout_completion, + TimeoutCheck, + ); // receving incomming messages asynchronously - self.loop.io.recv(*Ctx, self, Ctx.readCbk, self.conn_completion, self.conn_socket, self.read_buf); + self.loop.io.recv( + *Ctx, + self, + Ctx.readCbk, + self.conn_completion, + self.conn_socket, + self.read_buf, + ); } fn readCbk(self: *Ctx, completion: *Completion, result: RecvError!usize) void { @@ -98,7 +115,14 @@ pub const Ctx = struct { if (size == 0) { // continue receving incomming messages asynchronously - self.loop.io.recv(*Ctx, self, Ctx.readCbk, self.conn_completion, self.conn_socket, self.read_buf); + self.loop.io.recv( + *Ctx, + self, + Ctx.readCbk, + self.conn_completion, + self.conn_socket, + self.read_buf, + ); return; } @@ -124,7 +148,14 @@ pub const Ctx = struct { }; // continue receving incomming messages asynchronously - self.loop.io.recv(*Ctx, self, Ctx.readCbk, self.conn_completion, self.conn_socket, self.read_buf); + self.loop.io.recv( + *Ctx, + self, + Ctx.readCbk, + self.conn_completion, + self.conn_socket, + self.read_buf, + ); } fn timeoutCbk(self: *Ctx, completion: *Completion, result: TimeoutError!void) void { @@ -155,12 +186,24 @@ pub const Ctx = struct { // (and cancel does not work on MacOS) // close current connection - self.loop.io.close(*Ctx, self, Ctx.closeCbk, self.timeout_completion, self.conn_socket); + self.loop.io.close( + *Ctx, + self, + Ctx.closeCbk, + self.timeout_completion, + self.conn_socket, + ); return; } // continue checking timeout - self.loop.io.timeout(*Ctx, self, Ctx.timeoutCbk, self.timeout_completion, TimeoutCheck); + self.loop.io.timeout( + *Ctx, + self, + Ctx.timeoutCbk, + self.timeout_completion, + TimeoutCheck, + ); } fn closeCbk(self: *Ctx, completion: *Completion, result: CloseError!void) void { @@ -187,7 +230,13 @@ pub const Ctx = struct { std.log.debug("accepting new conn...", .{}); // continue accepting incoming requests - self.loop.io.accept(*Ctx, self, Ctx.acceptCbk, self.conn_completion, self.accept_socket); + self.loop.io.accept( + *Ctx, + self, + Ctx.acceptCbk, + self.conn_completion, + self.accept_socket, + ); } // shortcuts @@ -217,7 +266,13 @@ pub const Ctx = struct { if (std.mem.eql(u8, cmd, "close")) { // close connection std.log.debug("close cmd, closing...", .{}); - self.loop.io.close(*Ctx, self, Ctx.closeCbk, self.conn_completion, self.conn_socket); + self.loop.io.close( + *Ctx, + self, + Ctx.closeCbk, + self.conn_completion, + self.conn_socket, + ); return error.Closed; } @@ -245,7 +300,11 @@ pub const Ctx = struct { fn newSession(self: *Ctx) !void { try self.browser.newSession(self.alloc(), self.loop); - try self.browser.currentSession().setInspector(self, Ctx.onInspectorResp, Ctx.onInspectorNotif); + try self.browser.currentSession().setInspector( + self, + Ctx.onInspectorResp, + Ctx.onInspectorNotif, + ); self.sessionNew = true; std.log.debug("new session", .{}); } @@ -268,7 +327,11 @@ pub const Ctx = struct { // inject sessionID in cdp msg const tpl = "{s},\"sessionId\":\"{s}\"}}"; const msg_open = msg[0 .. msg.len - 1]; // remove closing bracket - const s = try std.fmt.allocPrint(allocator, tpl, .{ msg_open, cdp.ContextSessionID }); + const s = try std.fmt.allocPrint( + allocator, + tpl, + .{ msg_open, cdp.ContextSessionID }, + ); defer ctx.alloc().free(s); try sendSync(ctx, s); @@ -363,7 +426,11 @@ pub fn listen( .conn_completion = &conn_completion, .timeout_completion = &timeout_completion, }; - try browser.currentSession().setInspector(&ctx, Ctx.onInspectorResp, Ctx.onInspectorNotif); + try browser.currentSession().setInspector( + &ctx, + Ctx.onInspectorResp, + Ctx.onInspectorNotif, + ); // accepting connection asynchronously on internal server std.log.debug("accepting new conn...", .{}); From 2bc58bebce57d1cef59b54ddc9050b6df4208e35 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 12:11:43 +0200 Subject: [PATCH 093/117] server: rename public -> jsruntime Signed-off-by: Francis Bouvier --- src/server.zig | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/server.zig b/src/server.zig index faa418bf..c3fd8019 100644 --- a/src/server.zig +++ b/src/server.zig @@ -18,13 +18,13 @@ const std = @import("std"); -const public = @import("jsruntime"); -const Completion = public.IO.Completion; -const AcceptError = public.IO.AcceptError; -const RecvError = public.IO.RecvError; -const SendError = public.IO.SendError; -const CloseError = public.IO.CloseError; -const TimeoutError = public.IO.TimeoutError; +const jsruntime = @import("jsruntime"); +const Completion = jsruntime.IO.Completion; +const AcceptError = jsruntime.IO.AcceptError; +const RecvError = jsruntime.IO.RecvError; +const SendError = jsruntime.IO.SendError; +const CloseError = jsruntime.IO.CloseError; +const TimeoutError = jsruntime.IO.TimeoutError; const MsgBuffer = @import("msg.zig").MsgBuffer; const Browser = @import("browser/browser.zig").Browser; @@ -43,7 +43,7 @@ const BufReadSize = 1024; // 1KB const MaxStdOutSize = 512; // ensure debug msg are not too long pub const Ctx = struct { - loop: *public.Loop, + loop: *jsruntime.Loop, // internal fields accept_socket: std.posix.socket_t, @@ -64,7 +64,7 @@ pub const Ctx = struct { // JS fields browser: *Browser, // TODO: is pointer mandatory here? sessionNew: bool, - // try_catch: public.TryCatch, // TODO + // try_catch: jsruntime.TryCatch, // TODO // callbacks // --------- @@ -253,7 +253,7 @@ pub const Ctx = struct { } // JS env of the current session - inline fn env(self: Ctx) public.Env { + inline fn env(self: Ctx) jsruntime.Env { return self.browser.currentSession().env; } @@ -399,7 +399,7 @@ pub fn sendSync(ctx: *Ctx, msg: []const u8) !void { pub fn listen( browser: *Browser, - loop: *public.Loop, + loop: *jsruntime.Loop, server_socket: std.posix.socket_t, timeout: u64, ) anyerror!void { From bf56345e4851cc168de24341d7359d77cf84be39 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 12:19:35 +0200 Subject: [PATCH 094/117] msg: comments typos Signed-off-by: Francis Bouvier --- src/msg.zig | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/msg.zig b/src/msg.zig index 85411d3b..927f4ae1 100644 --- a/src/msg.zig +++ b/src/msg.zig @@ -20,12 +20,12 @@ const std = @import("std"); // pub const MaxStdOutSize = 512; // ensure debug msg are not too long -/// MsgBuffer return messages from a raw text read stream, +/// MsgBuffer returns messages from a raw text read stream, /// according to the following format `:`. /// It handles both: /// - combined messages in one read -/// - single message in several read (multipart) -/// It is safe (and good practice) to reuse the same MsgBuffer +/// - single message in several reads (multipart) +/// It's safe (and a good practice) to reuse the same MsgBuffer /// on several reads of the same stream. pub const MsgBuffer = struct { size: usize = 0, @@ -56,8 +56,7 @@ pub const MsgBuffer = struct { // read input // - `do_func` is a callback to execute on each message of the input - // - `data` is a arbitrary payload that will be passed to the callback along with - // the message itself + // - `data` is an arbitrary user data that will be forwarded to the do_func callback pub fn read( self: *MsgBuffer, alloc: std.mem.Allocator, From 0d89b98badaf762fa7e4d9a6d33f4f0174e43ec4 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 12:35:56 +0200 Subject: [PATCH 095/117] cdp: ensure token is a string when needed in parser Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 94fd8e9d..19d029bc 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -76,13 +76,13 @@ pub fn do( // handle 2 possible orders: // - id, method <...> // - method, id <...> - var method_key = (try scanner.next()).string; + var method_key = try nextString(&scanner); var method_token: std.json.Token = undefined; var id: ?u16 = null; // check swap order if (std.mem.eql(u8, method_key, "id")) { id = try getId(&scanner, method_key); - method_key = (try scanner.next()).string; + method_key = try nextString(&scanner); method_token = try scanner.next(); } else { method_token = try scanner.next(); @@ -90,6 +90,9 @@ pub fn do( try checkKey(method_key, "method"); // retrieve method + if (method_token != .string) { + return error.WrongTokenType; + } const method_name = method_token.string; std.log.debug("cmd: method {s}, id {any}", .{ method_name, id }); @@ -127,6 +130,14 @@ pub const State = struct { // Utils // ----- +fn nextString(scanner: *std.json.Scanner) ![]const u8 { + const token = try scanner.next(); + if (token != .string) { + return error.WrongTokenType; + } + return token.string; +} + pub fn dumpFile( alloc: std.mem.Allocator, id: u16, @@ -259,7 +270,7 @@ fn getSessionId(scanner: *std.json.Scanner, key: []const u8) !?[]const u8 { if (!std.mem.eql(u8, "sessionId", key)) return null; // parse "sessionId" - return (try scanner.next()).string; + return try nextString(scanner); } pub fn getMsg( @@ -277,7 +288,7 @@ pub fn getMsg( t = try scanner.next(); if (t == .object_end) break; if (t != .string) { - return error.CDPMsgWrong; + return error.WrongTokenType; } if (id == null) { id = try getId(scanner, t.string); From eaf5c6f86f4eff371098e6152378249a0206ae2d Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 12:40:17 +0200 Subject: [PATCH 096/117] cdp: ensure method action is present Signed-off-by: Francis Bouvier --- src/cdp/cdp.zig | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 19d029bc..bd406254 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -102,16 +102,17 @@ pub fn do( return error.UnknonwDomain; // select corresponding domain + const action = iter.next() orelse return error.BadMethod; return switch (domain) { - .Browser => browser(alloc, id, iter.next().?, &scanner, ctx), - .Target => target(alloc, id, iter.next().?, &scanner, ctx), - .Page => page(alloc, id, iter.next().?, &scanner, ctx), - .Log => log(alloc, id, iter.next().?, &scanner, ctx), - .Runtime => runtime(alloc, id, iter.next().?, &scanner, s, ctx), - .Network => network(alloc, id, iter.next().?, &scanner, ctx), - .Emulation => emulation(alloc, id, iter.next().?, &scanner, ctx), - .Fetch => fetch(alloc, id, iter.next().?, &scanner, ctx), - .Performance => performance(alloc, id, iter.next().?, &scanner, ctx), + .Browser => browser(alloc, id, action, &scanner, ctx), + .Target => target(alloc, id, action, &scanner, ctx), + .Page => page(alloc, id, action, &scanner, ctx), + .Log => log(alloc, id, action, &scanner, ctx), + .Runtime => runtime(alloc, id, action, &scanner, s, ctx), + .Network => network(alloc, id, action, &scanner, ctx), + .Emulation => emulation(alloc, id, action, &scanner, ctx), + .Fetch => fetch(alloc, id, action, &scanner, ctx), + .Performance => performance(alloc, id, action, &scanner, ctx), }; } From fa4920bd948f7d97db995da4c21fb2f36a78de43 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 12:45:28 +0200 Subject: [PATCH 097/117] browser: rename setInspector -> initInspector Signed-off-by: Francis Bouvier --- src/browser/browser.zig | 10 +++++----- src/server.zig | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 5649638d..4f42c423 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -138,7 +138,7 @@ pub const Session = struct { self.alloc.destroy(self); } - pub fn setInspector( + pub fn initInspector( self: *Session, ctx: anytype, onResp: jsruntime.InspectorOnResponseFn, @@ -149,15 +149,15 @@ pub const Session = struct { self.env.setInspector(self.inspector.?); } - pub fn createPage(self: *Session) !Page { - return Page.init(self.alloc, self); - } - pub fn callInspector(self: *Session, msg: []const u8) void { if (self.inspector) |inspector| { inspector.send(msg, self.env); } } + + pub fn createPage(self: *Session) !Page { + return Page.init(self.alloc, self); + } }; // Page navigates to an url. diff --git a/src/server.zig b/src/server.zig index c3fd8019..6ae6f618 100644 --- a/src/server.zig +++ b/src/server.zig @@ -300,7 +300,7 @@ pub const Ctx = struct { fn newSession(self: *Ctx) !void { try self.browser.newSession(self.alloc(), self.loop); - try self.browser.currentSession().setInspector( + try self.browser.currentSession().initInspector( self, Ctx.onInspectorResp, Ctx.onInspectorNotif, @@ -426,7 +426,7 @@ pub fn listen( .conn_completion = &conn_completion, .timeout_completion = &timeout_completion, }; - try browser.currentSession().setInspector( + try browser.currentSession().initInspector( &ctx, Ctx.onInspectorResp, Ctx.onInspectorNotif, From 28593d93ffdec0863cffb26bd847383273d2ca5f Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 12:47:14 +0200 Subject: [PATCH 098/117] browser: panic if callInspector without Inspector Signed-off-by: Francis Bouvier --- src/browser/browser.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 4f42c423..562bd998 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -152,6 +152,8 @@ pub const Session = struct { pub fn callInspector(self: *Session, msg: []const u8) void { if (self.inspector) |inspector| { inspector.send(msg, self.env); + } else { + @panic("No Inspector"); } } From e53b9d984b7ab5c17d17a2dba424608c79150f88 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 15:10:30 +0200 Subject: [PATCH 099/117] browser: add comment for auxData param in page.navigate Signed-off-by: Francis Bouvier --- src/browser/browser.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 562bd998..7c82d563 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -244,6 +244,8 @@ pub const Page = struct { } // spec reference: https://html.spec.whatwg.org/#document-lifecycle + // - auxData: extra data forwarded to the Inspector + // see Inspector.contextCreated pub fn navigate(self: *Page, uri: []const u8, auxData: ?[]const u8) !void { const alloc = self.arena.allocator(); From 17c641845e25918f60282a540f0de898d173f804 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 15:13:06 +0200 Subject: [PATCH 100/117] msg: return error if input does not have "size:" Signed-off-by: Francis Bouvier --- src/msg.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/msg.zig b/src/msg.zig index 927f4ae1..6bcaa182 100644 --- a/src/msg.zig +++ b/src/msg.zig @@ -73,7 +73,7 @@ pub const MsgBuffer = struct { var msg_size: usize = undefined; if (self.isEmpty()) { // parse msg size metadata - const size_pos = std.mem.indexOfScalar(u8, _input, ':').?; + const size_pos = std.mem.indexOfScalar(u8, _input, ':') orelse return error.InputWithoutSize; const size_str = _input[0..size_pos]; msg_size = try std.fmt.parseInt(u32, size_str, 10); _input = _input[size_pos + 1 ..]; From 7ad03fb5484edc9c5e589d845f6749265049ffe1 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 15:18:55 +0200 Subject: [PATCH 101/117] cdp: fix a comment on page.navigate Signed-off-by: Francis Bouvier --- src/cdp/page.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 7bf3eceb..4e21090c 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -284,7 +284,7 @@ fn navigate( ctx.state.executionContextId += 1; const auxData = try std.fmt.allocPrint( alloc, - // TODO: we assume this is the default web page, is it right? + // NOTE: we assume this is the default web page "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{ctx.state.frameID}, ); From 4b495f213faab436636cd9d87e10554764d8bde2 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 9 Oct 2024 15:21:09 +0200 Subject: [PATCH 102/117] cdp: add comment on hard coded ID for page.createIsolatedWorld Signed-off-by: Francis Bouvier --- src/cdp/page.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 4e21090c..ae59c85d 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -184,6 +184,7 @@ fn createIsolatedWorld( 0, "", params.worldName, + // TODO: hard coded ID "7102379147004877974.3265385113993241162", .{ .isDefault = false, From fd6c25daaa8fe7fe52abcc4ca6b3c3f2b3d72444 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 11 Oct 2024 18:05:04 +0200 Subject: [PATCH 103/117] msg: improve comments on reallocation Signed-off-by: Francis Bouvier --- src/msg.zig | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/msg.zig b/src/msg.zig index 6bcaa182..f76b5339 100644 --- a/src/msg.zig +++ b/src/msg.zig @@ -95,8 +95,11 @@ pub const MsgBuffer = struct { // check if the current input can fit in MsgBuffer if (new_pos > self.buf.len) { - // max_size is the max between msg size and current new cursor position - const max_size = @max(self.size, new_pos); + // we want to realloc at least: + // - a size equals to new_pos to fit the entire input + // - a size big enough (ie. current size + starting size) + // to avoid multiple reallocation + const max_size = @max(self.buf.len + self.size, new_pos); // resize the MsgBuffer to fit self.buf = try alloc.realloc(self.buf, max_size); } From ec680593b08f50cf4ea30d4fae15c5a69d0c6323 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 11 Oct 2024 18:13:20 +0200 Subject: [PATCH 104/117] msg: set a hard limit max size Signed-off-by: Francis Bouvier --- src/msg.zig | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/msg.zig b/src/msg.zig index f76b5339..4cabe44a 100644 --- a/src/msg.zig +++ b/src/msg.zig @@ -32,6 +32,8 @@ pub const MsgBuffer = struct { buf: []u8, pos: usize = 0, + const MaxSize = 1024 * 1024; // 1MB + pub fn init(alloc: std.mem.Allocator, size: usize) std.mem.Allocator.Error!MsgBuffer { const buf = try alloc.alloc(u8, size); return .{ .buf = buf }; @@ -93,15 +95,20 @@ pub const MsgBuffer = struct { // get the new position of the cursor const new_pos = self.pos + _input.len; + // check max limit size + if (new_pos > MaxSize) { + return error.MsgTooBig; + } + // check if the current input can fit in MsgBuffer if (new_pos > self.buf.len) { // we want to realloc at least: - // - a size equals to new_pos to fit the entire input + // - a size big enough to fit the entire input (ie. new_pos) // - a size big enough (ie. current size + starting size) // to avoid multiple reallocation - const max_size = @max(self.buf.len + self.size, new_pos); + const new_size = @max(self.buf.len + self.size, new_pos); // resize the MsgBuffer to fit - self.buf = try alloc.realloc(self.buf, max_size); + self.buf = try alloc.realloc(self.buf, new_size); } // copy the current input into MsgBuffer From cbf63480554afca2ae7dd9387b4a18c0676331c6 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Sat, 12 Oct 2024 10:38:53 +0200 Subject: [PATCH 105/117] server: panic if sendInspector without an inspector Signed-off-by: Francis Bouvier --- src/server.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.zig b/src/server.zig index 6ae6f618..4f925b2b 100644 --- a/src/server.zig +++ b/src/server.zig @@ -315,7 +315,7 @@ pub const Ctx = struct { pub fn sendInspector(self: *Ctx, msg: []const u8) void { if (self.env().getInspector()) |inspector| { inspector.send(self.env(), msg); - } + } else @panic("Inspector has not been set"); } inline fn inspectorCtx(ctx_opaque: *anyopaque) *Ctx { From efca71510a121e45d56bcae1f936aa624066ffe1 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Sat, 12 Oct 2024 10:39:48 +0200 Subject: [PATCH 106/117] browser: put back VM is an arg for browser init Signed-off-by: Francis Bouvier --- src/browser/browser.zig | 3 ++- src/main.zig | 2 +- src/main_get.zig | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 7c82d563..42c705b1 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -51,9 +51,10 @@ const log = std.log.scoped(.browser); pub const Browser = struct { session: *Session, - pub fn init(alloc: std.mem.Allocator, loop: *Loop) !Browser { + pub fn init(alloc: std.mem.Allocator, loop: *Loop, vm: jsruntime.VM) !Browser { // We want to ensure the caller initialised a VM, but the browser // doesn't use it directly... + _ = vm; return Browser{ .session = try Session.init(alloc, loop, "about:blank"), diff --git a/src/main.zig b/src/main.zig index 7636a622..fc486afb 100644 --- a/src/main.zig +++ b/src/main.zig @@ -233,7 +233,7 @@ pub fn main() !void { defer loop.deinit(); // browser - var browser = try Browser.init(arena.allocator(), &loop); + var browser = try Browser.init(arena.allocator(), &loop, vm); defer browser.deinit(); // listen diff --git a/src/main_get.zig b/src/main_get.zig index 418f77c9..8368dd47 100644 --- a/src/main_get.zig +++ b/src/main_get.zig @@ -83,7 +83,7 @@ pub fn main() !void { var loop = try jsruntime.Loop.init(allocator); defer loop.deinit(); - var browser = try Browser.init(allocator, &loop); + var browser = try Browser.init(allocator, &loop, vm); defer browser.deinit(); var page = try browser.currentSession().createPage(); From ea9af210f91f48ceb37d05eb6924e1155ddd0571 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 15 Oct 2024 15:52:48 +0200 Subject: [PATCH 107/117] Remove heap allocation for Session And adapt to similar changes on zig-js-runtime for Env Signed-off-by: Francis Bouvier --- src/browser/browser.zig | 53 +++++++++++++++++++++++------------------ src/cdp/page.zig | 4 ++-- src/main.zig | 10 +------- src/main_get.zig | 14 +++++------ src/server.zig | 21 +++++++++++----- 5 files changed, 55 insertions(+), 47 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 42c705b1..d09b9d95 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -49,29 +49,33 @@ const log = std.log.scoped(.browser); // A browser contains only one session. // TODO allow multiple sessions per browser. pub const Browser = struct { - session: *Session, + session: Session = undefined, - pub fn init(alloc: std.mem.Allocator, loop: *Loop, vm: jsruntime.VM) !Browser { + const uri = "about:blank"; + + pub fn init(self: *Browser, alloc: std.mem.Allocator, loop: *Loop, vm: jsruntime.VM) !void { // We want to ensure the caller initialised a VM, but the browser // doesn't use it directly... _ = vm; - return Browser{ - .session = try Session.init(alloc, loop, "about:blank"), - }; + try Session.init(&self.session, alloc, loop, uri); } pub fn deinit(self: *Browser) void { self.session.deinit(); } - pub fn newSession(self: *Browser, alloc: std.mem.Allocator, loop: *jsruntime.Loop) !void { + pub fn newSession( + self: *Browser, + alloc: std.mem.Allocator, + loop: *jsruntime.Loop, + ) !void { self.session.deinit(); - self.session = try Session.init(alloc, loop, "about:blank"); + try Session.init(&self.session, alloc, loop, uri); } pub fn currentSession(self: *Browser) *Session { - return self.session; + return &self.session; } }; @@ -99,13 +103,12 @@ pub const Session = struct { window: Window, // TODO move the shed to the browser? storageShed: storage.Shed, - page: ?*Page = null, + _page: ?Page = null, httpClient: HttpClient, jstypes: [Types.len]usize = undefined, - fn init(alloc: std.mem.Allocator, loop: *Loop, uri: []const u8) !*Session { - var self = try alloc.create(Session); + fn init(self: *Session, alloc: std.mem.Allocator, loop: *Loop, uri: []const u8) !void { self.* = Session{ .uri = uri, .alloc = alloc, @@ -116,15 +119,13 @@ pub const Session = struct { .httpClient = undefined, }; - self.env = try Env.init(self.arena.allocator(), loop, null); + Env.init(&self.env, self.arena.allocator(), loop, null); self.httpClient = .{ .allocator = alloc, .loop = loop }; try self.env.load(&self.jstypes); - - return self; } fn deinit(self: *Session) void { - if (self.page) |page| page.end(); + if (self._page) |*p| p.end(); if (self.inspector) |inspector| { inspector.deinit(self.alloc); @@ -136,7 +137,6 @@ pub const Session = struct { self.httpClient.deinit(); self.loader.deinit(); self.storageShed.deinit(); - self.alloc.destroy(self); } pub fn initInspector( @@ -158,8 +158,17 @@ pub const Session = struct { } } - pub fn createPage(self: *Session) !Page { - return Page.init(self.alloc, self); + pub fn createPage(self: *Session) !void { + if (self._page != null) return error.SessionPageExists; + const p: Page = undefined; + self._page = p; + Page.init(&self._page.?, self.alloc, self); + } + + // shortcut + pub fn page(self: *Session) *Page { + if (self._page) |*p| return p; + @panic("No Page on this session"); } }; @@ -181,16 +190,14 @@ pub const Page = struct { raw_data: ?[]const u8 = null, fn init( + self: *Page, alloc: std.mem.Allocator, session: *Session, - ) !Page { - if (session.page != null) return error.SessionPageExists; - var page = Page{ + ) void { + self.* = .{ .arena = std.heap.ArenaAllocator.init(alloc), .session = session, }; - session.page = &page; - return page; } // reset js env and mem arena. diff --git a/src/cdp/page.zig b/src/cdp/page.zig index ae59c85d..a76b4e7f 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -281,7 +281,7 @@ fn navigate( try sendEvent(alloc, ctx, "Runtime.executionContextsCleared", void, {}, msg.sessionID); // Launch navigate - var p = try ctx.browser.currentSession().createPage(); + try ctx.browser.session.createPage(); ctx.state.executionContextId += 1; const auxData = try std.fmt.allocPrint( alloc, @@ -290,7 +290,7 @@ fn navigate( .{ctx.state.frameID}, ); defer alloc.free(auxData); - _ = try p.navigate(params.url, auxData); + try ctx.browser.session.page().navigate(params.url, auxData); // Events diff --git a/src/main.zig b/src/main.zig index fc486afb..08745b30 100644 --- a/src/main.zig +++ b/src/main.zig @@ -224,18 +224,10 @@ pub fn main() !void { defer srv.close(); std.log.info("Listening on: {s}:{d}...", .{ host, port }); - // create v8 vm - const vm = jsruntime.VM.init(); - defer vm.deinit(); - // loop var loop = try jsruntime.Loop.init(arena.allocator()); defer loop.deinit(); - // browser - var browser = try Browser.init(arena.allocator(), &loop, vm); - defer browser.deinit(); - // listen - try server.listen(&browser, &loop, srv.sockfd.?, std.time.ns_per_s * @as(u64, timeout)); + try server.listen(arena.allocator(), &loop, srv.sockfd.?, std.time.ns_per_s * @as(u64, timeout)); } diff --git a/src/main_get.zig b/src/main_get.zig index 8368dd47..2814c077 100644 --- a/src/main_get.zig +++ b/src/main_get.zig @@ -83,18 +83,18 @@ pub fn main() !void { var loop = try jsruntime.Loop.init(allocator); defer loop.deinit(); - var browser = try Browser.init(allocator, &loop, vm); + var browser = Browser{}; + try Browser.init(&browser, allocator, &loop, vm); defer browser.deinit(); - var page = try browser.currentSession().createPage(); - defer page.deinit(); + try browser.session.createPage(); - try page.navigate(url, null); - defer page.end(); + try browser.session.page().navigate(url, null); + defer browser.session.page().end(); - try page.wait(); + try browser.session.page().wait(); if (dump) { - try page.dump(std.io.getStdOut()); + try browser.session.page().dump(std.io.getStdOut()); } } diff --git a/src/server.zig b/src/server.zig index 4f925b2b..e2eece71 100644 --- a/src/server.zig +++ b/src/server.zig @@ -249,12 +249,12 @@ pub const Ctx = struct { // allocator of the current session inline fn alloc(self: *Ctx) std.mem.Allocator { - return self.browser.currentSession().alloc; + return self.browser.session.alloc; } // JS env of the current session inline fn env(self: Ctx) jsruntime.Env { - return self.browser.currentSession().env; + return self.browser.session.env; } // actions @@ -300,7 +300,7 @@ pub const Ctx = struct { fn newSession(self: *Ctx) !void { try self.browser.newSession(self.alloc(), self.loop); - try self.browser.currentSession().initInspector( + try self.browser.session.initInspector( self, Ctx.onInspectorResp, Ctx.onInspectorNotif, @@ -398,12 +398,21 @@ pub fn sendSync(ctx: *Ctx, msg: []const u8) !void { // ------ pub fn listen( - browser: *Browser, + alloc: std.mem.Allocator, loop: *jsruntime.Loop, server_socket: std.posix.socket_t, timeout: u64, ) anyerror!void { + // create v8 vm + const vm = jsruntime.VM.init(); + defer vm.deinit(); + + // browser + var browser: Browser = undefined; + try Browser.init(&browser, alloc, loop, vm); + defer browser.deinit(); + // create buffers var read_buf: [BufReadSize]u8 = undefined; var msg_buf = try MsgBuffer.init(loop.alloc, BufReadSize * 256); // 256KB @@ -417,7 +426,7 @@ pub fn listen( // for accepting connections and receving messages var ctx = Ctx{ .loop = loop, - .browser = browser, + .browser = &browser, .sessionNew = true, .read_buf = &read_buf, .msg_buf = &msg_buf, @@ -426,7 +435,7 @@ pub fn listen( .conn_completion = &conn_completion, .timeout_completion = &timeout_completion, }; - try browser.currentSession().initInspector( + try browser.session.initInspector( &ctx, Ctx.onInspectorResp, Ctx.onInspectorNotif, From 7750956c7b5d62192958b2905c3c8a5c2e15a357 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 15 Oct 2024 16:07:46 +0200 Subject: [PATCH 108/117] msg: Add a more complex test case with 2 multipart messages combined Signed-off-by: Francis Bouvier --- src/msg.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/msg.zig b/src/msg.zig index 4cabe44a..bdd97c62 100644 --- a/src/msg.zig +++ b/src/msg.zig @@ -160,6 +160,10 @@ test "MsgBuffer" { // multipart & combined .{ .input = "9:multi", .nb = 0 }, .{ .input = "part2:ok", .nb = 2 }, + // multipart & combined with other multipart + .{ .input = "9:multi", .nb = 0 }, + .{ .input = "part8:co", .nb = 1 }, + .{ .input = "mbined", .nb = 1 }, // several multipart .{ .input = "23:multi", .nb = 0 }, .{ .input = "several", .nb = 0 }, From 84c49fbe34eee9bcd800c4d2bf813634515f9822 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 15 Oct 2024 17:28:18 +0200 Subject: [PATCH 109/117] cdp: ensure there is an ID on each request Signed-off-by: Francis Bouvier --- src/cdp/browser.zig | 24 ++++++++++----------- src/cdp/cdp.zig | 18 ++++++++++------ src/cdp/emulation.zig | 24 ++++++++++----------- src/cdp/fetch.zig | 6 +++--- src/cdp/log.zig | 6 +++--- src/cdp/network.zig | 12 +++++------ src/cdp/page.zig | 36 +++++++++++++++---------------- src/cdp/performance.zig | 6 +++--- src/cdp/runtime.zig | 14 ++++++------ src/cdp/target.zig | 48 ++++++++++++++++++++--------------------- 10 files changed, 100 insertions(+), 94 deletions(-) diff --git a/src/cdp/browser.zig b/src/cdp/browser.zig index 60da80e1..59593226 100644 --- a/src/cdp/browser.zig +++ b/src/cdp/browser.zig @@ -57,13 +57,13 @@ const JsVersion = "12.4.254.8"; fn getVersion( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { // input - const msg = try getMsg(alloc, void, scanner); + const msg = try getMsg(alloc, _id, void, scanner); // ouput const Res = struct { @@ -73,13 +73,13 @@ fn getVersion( userAgent: []const u8 = UserAgent, jsVersion: []const u8 = JsVersion, }; - return result(alloc, id orelse msg.id.?, Res, .{}, null); + return result(alloc, msg.id, Res, .{}, null); } // TODO: noop method fn setDownloadBehavior( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { @@ -91,10 +91,10 @@ fn setDownloadBehavior( downloadPath: ?[]const u8 = null, eventsEnabled: ?bool = null, }; - const msg = try getMsg(alloc, Params, scanner); + const msg = try getMsg(alloc, _id, Params, scanner); // output - return result(alloc, id orelse msg.id.?, null, null, null); + return result(alloc, msg.id, null, null, null); } // TODO: hard coded ID @@ -102,7 +102,7 @@ const DevToolsWindowID = 1923710101; fn getWindowForTarget( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { @@ -111,7 +111,7 @@ fn getWindowForTarget( const Params = struct { targetId: ?[]const u8 = null, }; - const msg = try cdp.getMsg(alloc, ?Params, scanner); + const msg = try cdp.getMsg(alloc, _id, ?Params, scanner); std.debug.assert(msg.sessionID != null); // output @@ -125,20 +125,20 @@ fn getWindowForTarget( windowState: []const u8 = "normal", } = .{}, }; - return result(alloc, id orelse msg.id.?, Resp, Resp{}, msg.sessionID.?); + return result(alloc, msg.id, Resp, Resp{}, msg.sessionID.?); } // TODO: noop method fn setWindowBounds( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { // input - const msg = try cdp.getMsg(alloc, void, scanner); + const msg = try cdp.getMsg(alloc, _id, void, scanner); // output - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + return result(alloc, msg.id, null, null, msg.sessionID); } diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index bd406254..3d242ce0 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -35,6 +35,7 @@ pub const Error = error{ UnknonwDomain, UnknownMethod, NoResponse, + RequestWithoutID, }; pub fn isCdpError(err: anyerror) ?Error { @@ -276,10 +277,11 @@ fn getSessionId(scanner: *std.json.Scanner, key: []const u8) !?[]const u8 { pub fn getMsg( alloc: std.mem.Allocator, + _id: ?u16, comptime params_T: type, scanner: *std.json.Scanner, -) !struct { id: ?u16, params: ?params_T, sessionID: ?[]const u8 } { - var id: ?u16 = null; +) !struct { id: u16, params: ?params_T, sessionID: ?[]const u8 } { + var id_msg: ?u16 = null; var params: ?params_T = null; var sessionID: ?[]const u8 = null; @@ -291,9 +293,9 @@ pub fn getMsg( if (t != .string) { return error.WrongTokenType; } - if (id == null) { - id = try getId(scanner, t.string); - if (id != null) continue; + if (_id == null and id_msg == null) { + id_msg = try getId(scanner, t.string); + if (id_msg != null) continue; } if (params == null) { params = try getParams(alloc, params_T, scanner, t.string); @@ -311,7 +313,11 @@ pub fn getMsg( ); t = try scanner.next(); if (t != .end_of_document) return error.CDPMsgEnd; - return .{ .id = id, .params = params, .sessionID = sessionID }; + + // check id + if (_id == null and id_msg == null) return error.RequestWithoutID; + + return .{ .id = _id orelse id_msg.?, .params = params, .sessionID = sessionID }; } // Common diff --git a/src/cdp/emulation.zig b/src/cdp/emulation.zig index 7d3e1191..f7367956 100644 --- a/src/cdp/emulation.zig +++ b/src/cdp/emulation.zig @@ -57,7 +57,7 @@ const MediaFeature = struct { // TODO: noop method fn setEmulatedMedia( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { @@ -67,16 +67,16 @@ fn setEmulatedMedia( media: ?[]const u8 = null, features: ?[]MediaFeature = null, }; - const msg = try getMsg(alloc, Params, scanner); + const msg = try getMsg(alloc, _id, Params, scanner); // output - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + return result(alloc, msg.id, null, null, msg.sessionID); } // TODO: noop method fn setFocusEmulationEnabled( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { @@ -85,35 +85,35 @@ fn setFocusEmulationEnabled( const Params = struct { enabled: bool, }; - const msg = try getMsg(alloc, Params, scanner); + const msg = try getMsg(alloc, _id, Params, scanner); // output - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + return result(alloc, msg.id, null, null, msg.sessionID); } // TODO: noop method fn setDeviceMetricsOverride( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { // input - const msg = try cdp.getMsg(alloc, void, scanner); + const msg = try cdp.getMsg(alloc, _id, void, scanner); // output - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + return result(alloc, msg.id, null, null, msg.sessionID); } // TODO: noop method fn setTouchEmulationEnabled( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - const msg = try cdp.getMsg(alloc, void, scanner); + const msg = try cdp.getMsg(alloc, _id, void, scanner); - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + return result(alloc, msg.id, null, null, msg.sessionID); } diff --git a/src/cdp/fetch.zig b/src/cdp/fetch.zig index c9def093..b4fa9941 100644 --- a/src/cdp/fetch.zig +++ b/src/cdp/fetch.zig @@ -46,11 +46,11 @@ pub fn fetch( // TODO: noop method fn disable( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - const msg = try getMsg(alloc, void, scanner); + const msg = try getMsg(alloc, _id, void, scanner); - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + return result(alloc, msg.id, null, null, msg.sessionID); } diff --git a/src/cdp/log.zig b/src/cdp/log.zig index 26758e0c..b6695fd1 100644 --- a/src/cdp/log.zig +++ b/src/cdp/log.zig @@ -46,11 +46,11 @@ pub fn log( fn enable( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - const msg = try getMsg(alloc, void, scanner); + const msg = try getMsg(alloc, _id, void, scanner); - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + return result(alloc, msg.id, null, null, msg.sessionID); } diff --git a/src/cdp/network.zig b/src/cdp/network.zig index 9e366167..a9c9a6be 100644 --- a/src/cdp/network.zig +++ b/src/cdp/network.zig @@ -47,23 +47,23 @@ pub fn network( fn enable( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - const msg = try getMsg(alloc, void, scanner); + const msg = try getMsg(alloc, _id, void, scanner); - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + return result(alloc, msg.id, null, null, msg.sessionID); } // TODO: noop method fn setCacheDisabled( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - const msg = try getMsg(alloc, void, scanner); + const msg = try getMsg(alloc, _id, void, scanner); - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + return result(alloc, msg.id, null, null, msg.sessionID); } diff --git a/src/cdp/page.zig b/src/cdp/page.zig index a76b4e7f..b4d7f5f5 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -58,12 +58,12 @@ pub fn page( fn enable( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - const msg = try getMsg(alloc, void, scanner); - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + const msg = try getMsg(alloc, _id, void, scanner); + return result(alloc, msg.id, null, null, msg.sessionID); } const Frame = struct { @@ -83,11 +83,11 @@ const Frame = struct { fn getFrameTree( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { - const msg = try cdp.getMsg(alloc, void, scanner); + const msg = try cdp.getMsg(alloc, _id, void, scanner); const FrameTree = struct { frameTree: struct { @@ -106,12 +106,12 @@ fn getFrameTree( }, }, }; - return result(alloc, id orelse msg.id.?, FrameTree, frameTree, msg.sessionID); + return result(alloc, msg.id, FrameTree, frameTree, msg.sessionID); } fn setLifecycleEventsEnabled( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { @@ -120,12 +120,12 @@ fn setLifecycleEventsEnabled( const Params = struct { enabled: bool, }; - const msg = try getMsg(alloc, Params, scanner); + const msg = try getMsg(alloc, _id, Params, scanner); ctx.state.page_life_cycle_events = true; // output - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + return result(alloc, msg.id, null, null, msg.sessionID); } const LifecycleEvent = struct { @@ -138,7 +138,7 @@ const LifecycleEvent = struct { // TODO: hard coded method fn addScriptToEvaluateOnNewDocument( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { @@ -150,19 +150,19 @@ fn addScriptToEvaluateOnNewDocument( includeCommandLineAPI: bool = false, runImmediately: bool = false, }; - const msg = try getMsg(alloc, Params, scanner); + const msg = try getMsg(alloc, _id, Params, scanner); // output const Res = struct { identifier: []const u8 = "1", }; - return result(alloc, id orelse msg.id.?, Res, Res{}, msg.sessionID); + return result(alloc, msg.id, Res, Res{}, msg.sessionID); } // TODO: hard coded method fn createIsolatedWorld( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { @@ -173,7 +173,7 @@ fn createIsolatedWorld( worldName: []const u8, grantUniveralAccess: bool, }; - const msg = try getMsg(alloc, Params, scanner); + const msg = try getMsg(alloc, _id, Params, scanner); std.debug.assert(msg.sessionID != null); const params = msg.params.?; @@ -199,12 +199,12 @@ fn createIsolatedWorld( executionContextId: u8 = 0, }; - return result(alloc, id orelse msg.id.?, Resp, .{}, msg.sessionID); + return result(alloc, msg.id, Resp, .{}, msg.sessionID); } fn navigate( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { @@ -217,7 +217,7 @@ fn navigate( frameId: ?[]const u8 = null, referrerPolicy: ?[]const u8 = null, // TODO: enum }; - const msg = try getMsg(alloc, Params, scanner); + const msg = try getMsg(alloc, _id, Params, scanner); std.debug.assert(msg.sessionID != null); const params = msg.params.?; @@ -269,7 +269,7 @@ fn navigate( .frameId = ctx.state.frameID, .loaderId = ctx.state.loaderID, }; - const res = try result(alloc, id orelse msg.id.?, Resp, resp, msg.sessionID); + const res = try result(alloc, msg.id, Resp, resp, msg.sessionID); defer alloc.free(res); std.log.debug("res {s}", .{res}); try server.sendSync(ctx, res); diff --git a/src/cdp/performance.zig b/src/cdp/performance.zig index 5c761681..1123aa00 100644 --- a/src/cdp/performance.zig +++ b/src/cdp/performance.zig @@ -45,11 +45,11 @@ pub fn performance( fn enable( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - const msg = try getMsg(alloc, void, scanner); + const msg = try getMsg(alloc, _id, void, scanner); - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + return result(alloc, msg.id, null, null, msg.sessionID); } diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index d0fa1dd0..3a0fe254 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -79,10 +79,10 @@ fn sendInspector( userGesture: ?bool = null, }; - const msg = try getMsg(alloc, Params, scanner); + const msg = try getMsg(alloc, _id, Params, scanner); const params = msg.params.?; script = params.expression; - id = _id orelse msg.id.?; + id = msg.id; } else if (method == .callFunctionOn) { const Params = struct { functionDeclaration: []const u8, @@ -97,10 +97,10 @@ fn sendInspector( userGesture: ?bool = null, }; - const msg = try getMsg(alloc, Params, scanner); + const msg = try getMsg(alloc, _id, Params, scanner); const params = msg.params.?; script = params.functionDeclaration; - id = _id orelse msg.id.?; + id = msg.id; } if (script) |src| { @@ -162,11 +162,11 @@ pub fn executionContextCreated( // should we be passing this also to the JS Inspector? fn runIfWaitingForDebugger( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { - const msg = try getMsg(alloc, void, scanner); + const msg = try getMsg(alloc, _id, void, scanner); - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + return result(alloc, msg.id, null, null, msg.sessionID); } diff --git a/src/cdp/target.zig b/src/cdp/target.zig index 84b3b1a6..b037af3e 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -65,16 +65,16 @@ const BrowserContextID = "65618675CB7D3585A95049E9DFE95EA9"; // TODO: noop method fn setDiscoverTargets( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { // input - const msg = try getMsg(alloc, void, scanner); + const msg = try getMsg(alloc, _id, void, scanner); // output - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + return result(alloc, msg.id, null, null, msg.sessionID); } const AttachToTarget = struct { @@ -99,7 +99,7 @@ const TargetFilter = struct { // TODO: noop method fn setAutoAttach( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { @@ -111,7 +111,7 @@ fn setAutoAttach( flatten: bool = true, filter: ?[]TargetFilter = null, }; - const msg = try getMsg(alloc, Params, scanner); + const msg = try getMsg(alloc, _id, Params, scanner); std.log.debug("params {any}", .{msg.params}); // attachedToTarget event @@ -129,12 +129,12 @@ fn setAutoAttach( } // output - return result(alloc, id orelse msg.id.?, null, null, msg.sessionID); + return result(alloc, msg.id, null, null, msg.sessionID); } fn getTargetInfo( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { @@ -143,7 +143,7 @@ fn getTargetInfo( const Params = struct { targetId: ?[]const u8 = null, }; - const msg = try getMsg(alloc, Params, scanner); + const msg = try getMsg(alloc, _id, Params, scanner); // output const TargetInfo = struct { @@ -162,7 +162,7 @@ fn getTargetInfo( .targetId = BrowserTargetID, .type = "browser", }; - return result(alloc, id orelse msg.id.?, TargetInfo, targetInfo, null); + return result(alloc, msg.id, TargetInfo, targetInfo, null); } // Browser context are not handled and not in the roadmap for now @@ -171,13 +171,13 @@ fn getTargetInfo( // TODO: noop method fn getBrowserContexts( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { // input - const msg = try getMsg(alloc, void, scanner); + const msg = try getMsg(alloc, _id, void, scanner); // ouptut const Resp = struct { @@ -191,7 +191,7 @@ fn getBrowserContexts( const contextIDs = [0][]const u8{}; resp = .{ .browserContextIds = &contextIDs }; } - return result(alloc, id orelse msg.id.?, Resp, resp, null); + return result(alloc, msg.id, Resp, resp, null); } const ContextID = "22648B09EDCCDD11109E2D4FEFBE4F89"; @@ -199,7 +199,7 @@ const ContextID = "22648B09EDCCDD11109E2D4FEFBE4F89"; // TODO: noop method fn createBrowserContext( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { @@ -211,7 +211,7 @@ fn createBrowserContext( proxyBypassList: ?[]const u8 = null, originsWithUniversalNetworkAccess: ?[][]const u8 = null, }; - const msg = try getMsg(alloc, Params, scanner); + const msg = try getMsg(alloc, _id, Params, scanner); ctx.state.contextID = ContextID; @@ -219,12 +219,12 @@ fn createBrowserContext( const Resp = struct { browserContextId: []const u8 = ContextID, }; - return result(alloc, id orelse msg.id.?, Resp, Resp{}, msg.sessionID); + return result(alloc, msg.id, Resp, Resp{}, msg.sessionID); } fn disposeBrowserContext( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { @@ -233,10 +233,10 @@ fn disposeBrowserContext( const Params = struct { browserContextId: []const u8, }; - const msg = try getMsg(alloc, Params, scanner); + const msg = try getMsg(alloc, _id, Params, scanner); // output - const res = try result(alloc, id orelse msg.id.?, null, .{}, null); + const res = try result(alloc, msg.id, null, .{}, null); defer alloc.free(res); try server.sendSync(ctx, res); @@ -249,7 +249,7 @@ const LoaderID = "DD4A76F842AA389647D702B4D805F49A"; fn createTarget( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { @@ -265,7 +265,7 @@ fn createTarget( background: bool = false, forTab: ?bool = null, }; - const msg = try getMsg(alloc, Params, scanner); + const msg = try getMsg(alloc, _id, Params, scanner); // change CDP state ctx.state.frameID = TargetID; @@ -291,12 +291,12 @@ fn createTarget( const Resp = struct { targetId: []const u8 = TargetID, }; - return result(alloc, id orelse msg.id.?, Resp, Resp{}, msg.sessionID); + return result(alloc, msg.id, Resp, Resp{}, msg.sessionID); } fn closeTarget( alloc: std.mem.Allocator, - id: ?u16, + _id: ?u16, scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { @@ -305,13 +305,13 @@ fn closeTarget( const Params = struct { targetId: []const u8, }; - const msg = try getMsg(alloc, Params, scanner); + const msg = try getMsg(alloc, _id, Params, scanner); // output const Resp = struct { success: bool = true, }; - const res = try result(alloc, id orelse msg.id.?, Resp, Resp{}, null); + const res = try result(alloc, msg.id, Resp, Resp{}, null); defer alloc.free(res); try server.sendSync(ctx, res); From 8e05f09fc841b416b8e1c636238bd02e85e438db Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 15 Oct 2024 22:57:56 +0200 Subject: [PATCH 110/117] server, cdp: improve logging Signed-off-by: Francis Bouvier --- src/cdp/browser.zig | 6 ++++ src/cdp/cdp.zig | 14 ++++----- src/cdp/emulation.zig | 6 ++++ src/cdp/fetch.zig | 3 ++ src/cdp/log.zig | 3 ++ src/cdp/network.zig | 8 ++++++ src/cdp/page.zig | 64 +++++++++++++++++++++++++++++++++++++++-- src/cdp/performance.zig | 5 ++++ src/cdp/runtime.zig | 7 +++++ src/cdp/target.zig | 35 +++++++++++++++++++++- src/main.zig | 20 +++++++------ src/server.zig | 54 +++++++++++++++++----------------- 12 files changed, 180 insertions(+), 45 deletions(-) diff --git a/src/cdp/browser.zig b/src/cdp/browser.zig index 59593226..4bfe17a2 100644 --- a/src/cdp/browser.zig +++ b/src/cdp/browser.zig @@ -24,6 +24,8 @@ const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; +const log = std.log.scoped(.cdp); + const Methods = enum { getVersion, setDownloadBehavior, @@ -64,6 +66,7 @@ fn getVersion( // input const msg = try getMsg(alloc, _id, void, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "browser.getVersion" }); // ouput const Res = struct { @@ -92,6 +95,7 @@ fn setDownloadBehavior( eventsEnabled: ?bool = null, }; const msg = try getMsg(alloc, _id, Params, scanner); + log.debug("REQ > id {d}, method {s}", .{ msg.id, "browser.setDownloadBehavior" }); // output return result(alloc, msg.id, null, null, null); @@ -113,6 +117,7 @@ fn getWindowForTarget( }; const msg = try cdp.getMsg(alloc, _id, ?Params, scanner); std.debug.assert(msg.sessionID != null); + log.debug("Req > id {d}, method {s}", .{ msg.id, "browser.getWindowForTarget" }); // output const Resp = struct { @@ -138,6 +143,7 @@ fn setWindowBounds( // input const msg = try cdp.getMsg(alloc, _id, void, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "browser.setWindowBounds" }); // output return result(alloc, msg.id, null, null, msg.sessionID); diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 3d242ce0..692b1109 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -31,6 +31,8 @@ const emulation = @import("emulation.zig").emulation; const fetch = @import("fetch.zig").fetch; const performance = @import("performance.zig").performance; +const log_cdp = std.log.scoped(.cdp); + pub const Error = error{ UnknonwDomain, UnknownMethod, @@ -95,7 +97,6 @@ pub fn do( return error.WrongTokenType; } const method_name = method_token.string; - std.log.debug("cmd: method {s}, id {any}", .{ method_name, id }); // retrieve domain from method var iter = std.mem.splitScalar(u8, method_name, '.'); @@ -154,7 +155,6 @@ pub fn dumpFile( std.debug.assert(nb == script.len); const p = try dir.realpathAlloc(alloc, name); defer alloc.free(p); - std.log.debug("Script {d} saved at {s}", .{ id, p }); } fn checkKey(key: []const u8, token: []const u8) !void { @@ -186,6 +186,10 @@ pub fn result( res: anytype, sessionID: ?[]const u8, ) ![]const u8 { + log_cdp.debug( + "Res > id {d}, sessionID {?s}, result {any}", + .{ id, sessionID, res }, + ); if (T == null) { // No need to stringify a custom JSON msg, just use string templates if (sessionID) |sID| { @@ -212,6 +216,7 @@ pub fn sendEvent( params: T, sessionID: ?[]const u8, ) !void { + log_cdp.debug("Event > method {s}, sessionID {?s}", .{ name, sessionID }); const Resp = struct { method: []const u8, params: T, @@ -221,7 +226,6 @@ pub fn sendEvent( const event_msg = try stringify(alloc, resp); defer alloc.free(event_msg); - std.log.debug("event {s}", .{event_msg}); try server.sendSync(ctx, event_msg); } @@ -307,10 +311,6 @@ pub fn getMsg( } // end - std.log.debug( - "id {any}, params {any}, sessionID: {any}, token {any}", - .{ id, params, sessionID, t }, - ); t = try scanner.next(); if (t != .end_of_document) return error.CDPMsgEnd; diff --git a/src/cdp/emulation.zig b/src/cdp/emulation.zig index f7367956..4bb32bc3 100644 --- a/src/cdp/emulation.zig +++ b/src/cdp/emulation.zig @@ -25,6 +25,8 @@ const result = cdp.result; const getMsg = cdp.getMsg; const stringify = cdp.stringify; +const log = std.log.scoped(.cdp); + const Methods = enum { setEmulatedMedia, setFocusEmulationEnabled, @@ -68,6 +70,7 @@ fn setEmulatedMedia( features: ?[]MediaFeature = null, }; const msg = try getMsg(alloc, _id, Params, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "emulation.setEmulatedMedia" }); // output return result(alloc, msg.id, null, null, msg.sessionID); @@ -86,6 +89,7 @@ fn setFocusEmulationEnabled( enabled: bool, }; const msg = try getMsg(alloc, _id, Params, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "emulation.setFocusEmulationEnabled" }); // output return result(alloc, msg.id, null, null, msg.sessionID); @@ -101,6 +105,7 @@ fn setDeviceMetricsOverride( // input const msg = try cdp.getMsg(alloc, _id, void, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "emulation.setDeviceMetricsOverride" }); // output return result(alloc, msg.id, null, null, msg.sessionID); @@ -114,6 +119,7 @@ fn setTouchEmulationEnabled( _: *Ctx, ) ![]const u8 { const msg = try cdp.getMsg(alloc, _id, void, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "emulation.setTouchEmulationEnabled" }); return result(alloc, msg.id, null, null, msg.sessionID); } diff --git a/src/cdp/fetch.zig b/src/cdp/fetch.zig index b4fa9941..2bdeecab 100644 --- a/src/cdp/fetch.zig +++ b/src/cdp/fetch.zig @@ -24,6 +24,8 @@ const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; +const log = std.log.scoped(.cdp); + const Methods = enum { disable, }; @@ -51,6 +53,7 @@ fn disable( _: *Ctx, ) ![]const u8 { const msg = try getMsg(alloc, _id, void, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "fetch.disable" }); return result(alloc, msg.id, null, null, msg.sessionID); } diff --git a/src/cdp/log.zig b/src/cdp/log.zig index b6695fd1..d2cf5ccc 100644 --- a/src/cdp/log.zig +++ b/src/cdp/log.zig @@ -25,6 +25,8 @@ const result = cdp.result; const getMsg = cdp.getMsg; const stringify = cdp.stringify; +const log_cdp = std.log.scoped(.cdp); + const Methods = enum { enable, }; @@ -51,6 +53,7 @@ fn enable( _: *Ctx, ) ![]const u8 { const msg = try getMsg(alloc, _id, void, scanner); + log_cdp.debug("Req > id {d}, method {s}", .{ msg.id, "log.enable" }); return result(alloc, msg.id, null, null, msg.sessionID); } diff --git a/src/cdp/network.zig b/src/cdp/network.zig index a9c9a6be..15c93df3 100644 --- a/src/cdp/network.zig +++ b/src/cdp/network.zig @@ -24,6 +24,8 @@ const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; +const log = std.log.scoped(.cdp); + const Methods = enum { enable, setCacheDisabled, @@ -51,7 +53,10 @@ fn enable( scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { + + // input const msg = try getMsg(alloc, _id, void, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "network.enable" }); return result(alloc, msg.id, null, null, msg.sessionID); } @@ -63,7 +68,10 @@ fn setCacheDisabled( scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { + + // input const msg = try getMsg(alloc, _id, void, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "network.setCacheDisabled" }); return result(alloc, msg.id, null, null, msg.sessionID); } diff --git a/src/cdp/page.zig b/src/cdp/page.zig index b4d7f5f5..5fd864ac 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -26,6 +26,8 @@ const getMsg = cdp.getMsg; const stringify = cdp.stringify; const sendEvent = cdp.sendEvent; +const log = std.log.scoped(.cdp); + const Runtime = @import("runtime.zig"); const Methods = enum { @@ -62,7 +64,11 @@ fn enable( scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { + + // input const msg = try getMsg(alloc, _id, void, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "page.enable" }); + return result(alloc, msg.id, null, null, msg.sessionID); } @@ -87,13 +93,36 @@ fn getFrameTree( scanner: *std.json.Scanner, ctx: *Ctx, ) ![]const u8 { - const msg = try cdp.getMsg(alloc, _id, void, scanner); + // input + const msg = try cdp.getMsg(alloc, _id, void, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "page.getFrameTree" }); + + // output const FrameTree = struct { frameTree: struct { frame: Frame, }, childFrames: ?[]@This() = null, + + pub fn format( + self: @This(), + comptime _: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + try writer.writeAll("cdp.page.getFrameTree { "); + try writer.writeAll(".frameTree = { "); + try writer.writeAll(".frame = { "); + const frame = self.frameTree.frame; + try writer.writeAll(".id = "); + try std.fmt.formatText(frame.id, "s", options, writer); + try writer.writeAll(", .loaderId = "); + try std.fmt.formatText(frame.loaderId, "s", options, writer); + try writer.writeAll(", .url = "); + try std.fmt.formatText(frame.url, "s", options, writer); + try writer.writeAll(" } } }"); + } }; const frameTree = FrameTree{ .frameTree = .{ @@ -121,6 +150,7 @@ fn setLifecycleEventsEnabled( enabled: bool, }; const msg = try getMsg(alloc, _id, Params, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "page.setLifecycleEventsEnabled" }); ctx.state.page_life_cycle_events = true; @@ -151,10 +181,23 @@ fn addScriptToEvaluateOnNewDocument( runImmediately: bool = false, }; const msg = try getMsg(alloc, _id, Params, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "page.addScriptToEvaluateOnNewDocument" }); // output const Res = struct { identifier: []const u8 = "1", + + pub fn format( + self: @This(), + comptime _: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + try writer.writeAll("cdp.page.addScriptToEvaluateOnNewDocument { "); + try writer.writeAll(".identifier = "); + try std.fmt.formatText(self.identifier, "s", options, writer); + try writer.writeAll(" }"); + } }; return result(alloc, msg.id, Res, Res{}, msg.sessionID); } @@ -175,6 +218,7 @@ fn createIsolatedWorld( }; const msg = try getMsg(alloc, _id, Params, scanner); std.debug.assert(msg.sessionID != null); + log.debug("Req > id {d}, method {s}", .{ msg.id, "page.createIsolatedWorld" }); const params = msg.params.?; // noop executionContextCreated event @@ -219,6 +263,7 @@ fn navigate( }; const msg = try getMsg(alloc, _id, Params, scanner); std.debug.assert(msg.sessionID != null); + log.debug("Req > id {d}, method {s}", .{ msg.id, "page.navigate" }); const params = msg.params.?; // change state @@ -264,6 +309,22 @@ fn navigate( frameId: []const u8, loaderId: ?[]const u8, errorText: ?[]const u8 = null, + + pub fn format( + self: @This(), + comptime _: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + try writer.writeAll("cdp.page.navigate.Resp { "); + try writer.writeAll(".frameId = "); + try std.fmt.formatText(self.frameId, "s", options, writer); + if (self.loaderId) |loaderId| { + try writer.writeAll(", .loaderId = '"); + try std.fmt.formatText(loaderId, "s", options, writer); + } + try writer.writeAll(" }"); + } }; const resp = Resp{ .frameId = ctx.state.frameID, @@ -271,7 +332,6 @@ fn navigate( }; const res = try result(alloc, msg.id, Resp, resp, msg.sessionID); defer alloc.free(res); - std.log.debug("res {s}", .{res}); try server.sendSync(ctx, res); // TODO: at this point do we need async the following actions to be async? diff --git a/src/cdp/performance.zig b/src/cdp/performance.zig index 1123aa00..ce8e670d 100644 --- a/src/cdp/performance.zig +++ b/src/cdp/performance.zig @@ -24,6 +24,8 @@ const cdp = @import("cdp.zig"); const result = cdp.result; const getMsg = cdp.getMsg; +const log = std.log.scoped(.cdp); + const Methods = enum { enable, }; @@ -49,7 +51,10 @@ fn enable( scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { + + // input const msg = try getMsg(alloc, _id, void, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "performance.enable" }); return result(alloc, msg.id, null, null, msg.sessionID); } diff --git a/src/cdp/runtime.zig b/src/cdp/runtime.zig index 3a0fe254..d2723044 100644 --- a/src/cdp/runtime.zig +++ b/src/cdp/runtime.zig @@ -28,6 +28,8 @@ const result = cdp.result; const getMsg = cdp.getMsg; const stringify = cdp.stringify; +const log = std.log.scoped(.cdp); + const Methods = enum { enable, runIfWaitingForDebugger, @@ -80,6 +82,7 @@ fn sendInspector( }; const msg = try getMsg(alloc, _id, Params, scanner); + log.debug("Req > id {d}, method {s} (script saved on cache)", .{ msg.id, "runtime.evaluate" }); const params = msg.params.?; script = params.expression; id = msg.id; @@ -98,6 +101,7 @@ fn sendInspector( }; const msg = try getMsg(alloc, _id, Params, scanner); + log.debug("Req > id {d}, method {s} (script saved on cache)", .{ msg.id, "runtime.callFunctionOn" }); const params = msg.params.?; script = params.functionDeclaration; id = msg.id; @@ -166,7 +170,10 @@ fn runIfWaitingForDebugger( scanner: *std.json.Scanner, _: *Ctx, ) ![]const u8 { + + // input const msg = try getMsg(alloc, _id, void, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "runtime.runIfWaitingForDebugger" }); return result(alloc, msg.id, null, null, msg.sessionID); } diff --git a/src/cdp/target.zig b/src/cdp/target.zig index b037af3e..ba0e977c 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -25,6 +25,8 @@ const result = cdp.result; const getMsg = cdp.getMsg; const stringify = cdp.stringify; +const log = std.log.scoped(.cdp); + const Methods = enum { setDiscoverTargets, setAutoAttach, @@ -72,6 +74,7 @@ fn setDiscoverTargets( // input const msg = try getMsg(alloc, _id, void, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "target.setDiscoverTargets" }); // output return result(alloc, msg.id, null, null, msg.sessionID); @@ -112,7 +115,7 @@ fn setAutoAttach( filter: ?[]TargetFilter = null, }; const msg = try getMsg(alloc, _id, Params, scanner); - std.log.debug("params {any}", .{msg.params}); + log.debug("Req > id {d}, method {s}", .{ msg.id, "target.setAutoAttach" }); // attachedToTarget event if (msg.sessionID == null) { @@ -144,6 +147,7 @@ fn getTargetInfo( targetId: ?[]const u8 = null, }; const msg = try getMsg(alloc, _id, Params, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "target.getTargetInfo" }); // output const TargetInfo = struct { @@ -178,6 +182,7 @@ fn getBrowserContexts( // input const msg = try getMsg(alloc, _id, void, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "target.getBrowserContexts" }); // ouptut const Resp = struct { @@ -212,12 +217,25 @@ fn createBrowserContext( originsWithUniversalNetworkAccess: ?[][]const u8 = null, }; const msg = try getMsg(alloc, _id, Params, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "target.createBrowserContext" }); ctx.state.contextID = ContextID; // output const Resp = struct { browserContextId: []const u8 = ContextID, + + pub fn format( + self: @This(), + comptime _: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + try writer.writeAll("cdp.target.createBrowserContext { "); + try writer.writeAll(".browserContextId = "); + try std.fmt.formatText(self.browserContextId, "s", options, writer); + try writer.writeAll(" }"); + } }; return result(alloc, msg.id, Resp, Resp{}, msg.sessionID); } @@ -234,6 +252,7 @@ fn disposeBrowserContext( browserContextId: []const u8, }; const msg = try getMsg(alloc, _id, Params, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "target.disposeBrowserContext" }); // output const res = try result(alloc, msg.id, null, .{}, null); @@ -266,6 +285,7 @@ fn createTarget( forTab: ?bool = null, }; const msg = try getMsg(alloc, _id, Params, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "target.createTarget" }); // change CDP state ctx.state.frameID = TargetID; @@ -290,6 +310,18 @@ fn createTarget( // output const Resp = struct { targetId: []const u8 = TargetID, + + pub fn format( + self: @This(), + comptime _: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + try writer.writeAll("cdp.target.createTarget { "); + try writer.writeAll(".targetId = "); + try std.fmt.formatText(self.targetId, "s", options, writer); + try writer.writeAll(" }"); + } }; return result(alloc, msg.id, Resp, Resp{}, msg.sessionID); } @@ -306,6 +338,7 @@ fn closeTarget( targetId: []const u8, }; const msg = try getMsg(alloc, _id, Params, scanner); + log.debug("Req > id {d}, method {s}", .{ msg.id, "target.closeTarget" }); // output const Resp = struct { diff --git a/src/main.zig b/src/main.zig index 08745b30..f00fbbc0 100644 --- a/src/main.zig +++ b/src/main.zig @@ -27,6 +27,8 @@ const server = @import("server.zig"); const parser = @import("netsurf"); const apiweb = @import("apiweb.zig"); +const log = std.log.scoped(.server); + pub const Types = jsruntime.reflect(apiweb.Interfaces); pub const UserContext = apiweb.UserContext; @@ -144,7 +146,7 @@ pub const StreamServer = struct { fn printUsageExit(execname: []const u8, res: u8) void { std.io.getStdErr().writer().print(usage, .{execname}) catch |err| { - std.log.err("Print usage error: {any}", .{err}); + log.err("Print usage error: {any}", .{err}); std.posix.exit(1); }; std.posix.exit(res); @@ -175,37 +177,37 @@ pub fn main() !void { host = arg; continue; } else { - std.log.err("--host not provided\n", .{}); + log.err("--host not provided\n", .{}); return printUsageExit(execname, 1); } } if (std.mem.eql(u8, "--port", opt)) { if (args.next()) |arg| { port = std.fmt.parseInt(u16, arg, 10) catch |err| { - std.log.err("--port {any}\n", .{err}); + log.err("--port {any}\n", .{err}); return printUsageExit(execname, 1); }; continue; } else { - std.log.err("--port not provided\n", .{}); + log.err("--port not provided\n", .{}); return printUsageExit(execname, 1); } } if (std.mem.eql(u8, "--timeout", opt)) { if (args.next()) |arg| { timeout = std.fmt.parseInt(u8, arg, 10) catch |err| { - std.log.err("--timeout {any}\n", .{err}); + log.err("--timeout {any}\n", .{err}); return printUsageExit(execname, 1); }; continue; } else { - std.log.err("--timeout not provided\n", .{}); + log.err("--timeout not provided\n", .{}); return printUsageExit(execname, 1); } } } addr = std.net.Address.parseIp4(host, port) catch |err| { - std.log.err("address (host:port) {any}\n", .{err}); + log.err("address (host:port) {any}\n", .{err}); return printUsageExit(execname, 1); }; @@ -218,11 +220,11 @@ pub fn main() !void { defer srv.deinit(); srv.listen(addr) catch |err| { - std.log.err("address (host:port) {any}\n", .{err}); + log.err("address (host:port) {any}\n", .{err}); return printUsageExit(execname, 1); }; defer srv.close(); - std.log.info("Listening on: {s}:{d}...", .{ host, port }); + log.info("Listening on: {s}:{d}...", .{ host, port }); // loop var loop = try jsruntime.Loop.init(arena.allocator()); diff --git a/src/server.zig b/src/server.zig index e2eece71..2b8ee379 100644 --- a/src/server.zig +++ b/src/server.zig @@ -36,6 +36,8 @@ const Error = IOError || std.fmt.ParseIntError || cdp.Error || NoError; const TimeoutCheck = std.time.ns_per_ms * 100; +const log = std.log.scoped(.server); + // I/O Main // -------- @@ -83,7 +85,7 @@ pub const Ctx = struct { // set connection timestamp and timeout self.last_active = std.time.Instant.now() catch |err| { - std.log.err("accept timestamp error: {any}", .{err}); + log.err("accept timestamp error: {any}", .{err}); return; }; self.loop.io.timeout( @@ -127,23 +129,19 @@ pub const Ctx = struct { } // input - if (std.log.defaultLogEnabled(.debug)) { - const content = input[0..@min(MaxStdOutSize, size)]; - std.debug.print("\ninput size: {d}, content: {s}\n", .{ size, content }); - } const input = self.read_buf[0..size]; // read and execute input self.msg_buf.read(self.alloc(), input, self, Ctx.do) catch |err| { if (err != error.Closed) { - std.log.err("do error: {any}", .{err}); + log.err("do error: {any}", .{err}); } return; }; // set connection timestamp self.last_active = std.time.Instant.now() catch |err| { - std.log.err("read timestamp error: {any}", .{err}); + log.err("read timestamp error: {any}", .{err}); return; }; @@ -173,13 +171,13 @@ pub const Ctx = struct { // check time since last read const now = std.time.Instant.now() catch |err| { - std.log.err("timeout timestamp error: {any}", .{err}); + log.err("timeout timestamp error: {any}", .{err}); return; }; if (now.since(self.last_active.?) > self.timeout) { // closing - std.log.debug("conn timeout, closing...", .{}); + log.debug("conn timeout, closing...", .{}); // NOTE: we should cancel the current read // but it seems that's just closing the connection is enough @@ -221,13 +219,12 @@ pub const Ctx = struct { // restart a new browser session in case of re-connect if (!self.sessionNew) { self.newSession() catch |err| { - std.log.err("new session error: {any}", .{err}); + log.err("new session error: {any}", .{err}); return; }; } - std.log.debug("conn closed", .{}); - std.log.debug("accepting new conn...", .{}); + log.info("accepting new conn...", .{}); // continue accepting incoming requests self.loop.io.accept( @@ -265,7 +262,7 @@ pub const Ctx = struct { // close cmd if (std.mem.eql(u8, cmd, "close")) { // close connection - std.log.debug("close cmd, closing...", .{}); + log.info("close cmd, closing conn...", .{}); self.loop.io.close( *Ctx, self, @@ -283,7 +280,7 @@ pub const Ctx = struct { // cdp end cmd if (err == error.DisposeBrowserContext) { // restart a new browser session - std.log.debug("cdp end cmd", .{}); + std.log.scoped(.cdp).debug("end cmd, restarting a new session...", .{}); try self.newSession(); return; } @@ -293,7 +290,6 @@ pub const Ctx = struct { // send result if (!std.mem.eql(u8, res, "")) { - std.log.debug("res {s}", .{res}); return sendAsync(self, res); } } @@ -306,7 +302,6 @@ pub const Ctx = struct { Ctx.onInspectorNotif, ); self.sessionNew = true; - std.log.debug("new session", .{}); } // inspector @@ -338,13 +333,23 @@ pub const Ctx = struct { } pub fn onInspectorResp(ctx_opaque: *anyopaque, _: u32, msg: []const u8) void { - std.log.debug("inspector resp: {s}", .{msg}); + if (std.log.defaultLogEnabled(.debug)) { + // msg should be {"id":,... + const id_end = std.mem.indexOfScalar(u8, msg, ',') orelse unreachable; + const id = msg[6..id_end]; + std.log.scoped(.cdp).debug("Res (inspector) > id {s}", .{id}); + } const ctx = inspectorCtx(ctx_opaque); inspectorMsg(ctx.alloc(), ctx, msg) catch unreachable; } pub fn onInspectorNotif(ctx_opaque: *anyopaque, msg: []const u8) void { - std.log.debug("inspector event: {s}", .{msg}); + if (std.log.defaultLogEnabled(.debug)) { + // msg should be {"method":,... + const method_end = std.mem.indexOfScalar(u8, msg, ',') orelse unreachable; + const method = msg[10..method_end]; + std.log.scoped(.cdp).debug("Event (inspector) > method {s}", .{method}); + } const ctx = inspectorCtx(ctx_opaque); inspectorMsg(ctx.alloc(), ctx, msg) catch unreachable; } @@ -374,12 +379,10 @@ const Send = struct { } fn asyncCbk(self: *Send, _: *Completion, result: SendError!usize) void { - const size = result catch |err| { + _ = result catch |err| { self.ctx.err = err; return; }; - - std.log.debug("send async {d} bytes", .{size}); self.deinit(); } }; @@ -390,8 +393,7 @@ pub fn sendAsync(ctx: *Ctx, msg: []const u8) !void { } pub fn sendSync(ctx: *Ctx, msg: []const u8) !void { - const s = try std.posix.write(ctx.conn_socket, msg); - std.log.debug("send sync {d} bytes", .{s}); + _ = try std.posix.write(ctx.conn_socket, msg); } // Listen @@ -442,7 +444,7 @@ pub fn listen( ); // accepting connection asynchronously on internal server - std.log.debug("accepting new conn...", .{}); + log.info("accepting new conn...", .{}); loop.io.accept(*Ctx, &ctx, Ctx.acceptCbk, ctx.conn_completion, ctx.accept_socket); // infinite loop on I/O events, either: @@ -451,7 +453,7 @@ pub fn listen( while (true) { try loop.io.tick(); if (loop.cbk_error) { - std.log.err("JS error", .{}); + log.err("JS error", .{}); // if (try try_catch.exception(alloc, js_env.*)) |msg| { // std.debug.print("\n\rUncaught {s}\n\r", .{msg}); // alloc.free(msg); @@ -459,7 +461,7 @@ pub fn listen( // loop.cbk_error = false; } if (ctx.err) |err| { - if (err != error.NoError) std.log.err("Server error: {any}", .{err}); + if (err != error.NoError) log.err("Server error: {any}", .{err}); break; } } From 7bc7da5499adb7a3889ece401897bf23828da1d7 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 16 Oct 2024 14:53:11 +0200 Subject: [PATCH 111/117] browser: back on createPage returning a Page (pointer) Signed-off-by: Francis Bouvier --- src/browser/browser.zig | 21 +++++++++------------ src/cdp/page.zig | 4 ++-- src/main_get.zig | 9 ++++----- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index d09b9d95..6ed625ec 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -103,7 +103,7 @@ pub const Session = struct { window: Window, // TODO move the shed to the browser? storageShed: storage.Shed, - _page: ?Page = null, + page: ?Page = null, httpClient: HttpClient, jstypes: [Types.len]usize = undefined, @@ -125,7 +125,7 @@ pub const Session = struct { } fn deinit(self: *Session) void { - if (self._page) |*p| p.end(); + if (self.page) |*p| p.end(); if (self.inspector) |inspector| { inspector.deinit(self.alloc); @@ -158,17 +158,14 @@ pub const Session = struct { } } - pub fn createPage(self: *Session) !void { - if (self._page != null) return error.SessionPageExists; + // NOTE: the caller is not the owner of the returned value, + // the pointer on Page is just returned as a convenience + pub fn createPage(self: *Session) !*Page { + if (self.page != null) return error.SessionPageExists; const p: Page = undefined; - self._page = p; - Page.init(&self._page.?, self.alloc, self); - } - - // shortcut - pub fn page(self: *Session) *Page { - if (self._page) |*p| return p; - @panic("No Page on this session"); + self.page = p; + Page.init(&self.page.?, self.alloc, self); + return &self.page.?; } }; diff --git a/src/cdp/page.zig b/src/cdp/page.zig index 5fd864ac..1c5076c5 100644 --- a/src/cdp/page.zig +++ b/src/cdp/page.zig @@ -341,7 +341,7 @@ fn navigate( try sendEvent(alloc, ctx, "Runtime.executionContextsCleared", void, {}, msg.sessionID); // Launch navigate - try ctx.browser.session.createPage(); + const p = try ctx.browser.session.createPage(); ctx.state.executionContextId += 1; const auxData = try std.fmt.allocPrint( alloc, @@ -350,7 +350,7 @@ fn navigate( .{ctx.state.frameID}, ); defer alloc.free(auxData); - try ctx.browser.session.page().navigate(params.url, auxData); + try p.navigate(params.url, auxData); // Events diff --git a/src/main_get.zig b/src/main_get.zig index 2814c077..1607b95a 100644 --- a/src/main_get.zig +++ b/src/main_get.zig @@ -87,14 +87,13 @@ pub fn main() !void { try Browser.init(&browser, allocator, &loop, vm); defer browser.deinit(); - try browser.session.createPage(); + const page = try browser.session.createPage(); - try browser.session.page().navigate(url, null); - defer browser.session.page().end(); + try page.navigate(url, null); - try browser.session.page().wait(); + try page.wait(); if (dump) { - try browser.session.page().dump(std.io.getStdOut()); + try page.dump(std.io.getStdOut()); } } From 23117652896a5a150a0b6bcffc3e1579ff084841 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 16 Oct 2024 14:53:50 +0200 Subject: [PATCH 112/117] Remove some dead code Signed-off-by: Francis Bouvier --- src/browser/browser.zig | 4 ---- src/msg.zig | 2 -- 2 files changed, 6 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 6ed625ec..9308ed82 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -73,10 +73,6 @@ pub const Browser = struct { self.session.deinit(); try Session.init(&self.session, alloc, loop, uri); } - - pub fn currentSession(self: *Browser) *Session { - return &self.session; - } }; // Session is like a browser's tab. diff --git a/src/msg.zig b/src/msg.zig index bdd97c62..c7b6d353 100644 --- a/src/msg.zig +++ b/src/msg.zig @@ -18,8 +18,6 @@ const std = @import("std"); -// pub const MaxStdOutSize = 512; // ensure debug msg are not too long - /// MsgBuffer returns messages from a raw text read stream, /// according to the following format `:`. /// It handles both: From 462485bfcb87f81eea0323fafd9a8df5199f2914 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 16 Oct 2024 14:56:04 +0200 Subject: [PATCH 113/117] Update zig-v8 and zig-js-runtime deps Signed-off-by: Francis Bouvier --- .github/actions/install/action.yml | 2 +- vendor/zig-js-runtime | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/install/action.yml b/.github/actions/install/action.yml index f402ead7..7955410d 100644 --- a/.github/actions/install/action.yml +++ b/.github/actions/install/action.yml @@ -17,7 +17,7 @@ inputs: zig-v8: description: 'zig v8 version to install' required: false - default: 'v0.1.6' + default: 'v0.1.7' v8: description: 'v8 version to install' required: false diff --git a/vendor/zig-js-runtime b/vendor/zig-js-runtime index f2a6e94a..0d6a38bc 160000 --- a/vendor/zig-js-runtime +++ b/vendor/zig-js-runtime @@ -1 +1 @@ -Subproject commit f2a6e94a18488cd2d5ae296b74ce70ba4af0afbf +Subproject commit 0d6a38bc3b52fc099a8d891b2601c34ae04c0d80 From b0634cd8716a730052eda22dbfaf98480754826d Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 16 Oct 2024 15:21:03 +0200 Subject: [PATCH 114/117] Adapt wpt and shell to zig-js-runtime changes Signed-off-by: Francis Bouvier --- src/wpt/run.zig | 3 ++- vendor/zig-js-runtime | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wpt/run.zig b/src/wpt/run.zig index 6a503e66..e2b58470 100644 --- a/src/wpt/run.zig +++ b/src/wpt/run.zig @@ -56,7 +56,8 @@ pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const var cli = Client{ .allocator = alloc, .loop = &loop }; defer cli.deinit(); - var js_env = try Env.init(alloc, &loop, UserContext{ + var js_env: Env = undefined; + Env.init(&js_env, alloc, &loop, UserContext{ .document = html_doc, .httpClient = &cli, }); diff --git a/vendor/zig-js-runtime b/vendor/zig-js-runtime index 0d6a38bc..38d9649c 160000 --- a/vendor/zig-js-runtime +++ b/vendor/zig-js-runtime @@ -1 +1 @@ -Subproject commit 0d6a38bc3b52fc099a8d891b2601c34ae04c0d80 +Subproject commit 38d9649cd4b5f7a98d4bedd0ae86516e61c64d3e From 7f08d08a78024a1e6dfe1453728544d366ad2d6c Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 16 Oct 2024 17:46:55 +0200 Subject: [PATCH 115/117] Update zig-v8 again Signed-off-by: Francis Bouvier --- .github/actions/install/action.yml | 2 +- vendor/zig-js-runtime | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/install/action.yml b/.github/actions/install/action.yml index 7955410d..1f43069b 100644 --- a/.github/actions/install/action.yml +++ b/.github/actions/install/action.yml @@ -17,7 +17,7 @@ inputs: zig-v8: description: 'zig v8 version to install' required: false - default: 'v0.1.7' + default: 'v0.1.8' v8: description: 'v8 version to install' required: false diff --git a/vendor/zig-js-runtime b/vendor/zig-js-runtime index 38d9649c..31d188d4 160000 --- a/vendor/zig-js-runtime +++ b/vendor/zig-js-runtime @@ -1 +1 @@ -Subproject commit 38d9649cd4b5f7a98d4bedd0ae86516e61c64d3e +Subproject commit 31d188d4fbfb0da38cd448b9a9d1ca7720cd340d From e450072f4585f60e9d25e240117f7ee0d6299198 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Wed, 16 Oct 2024 20:56:32 +0200 Subject: [PATCH 116/117] ci: add zig v8 version into the cache key --- .github/actions/install/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/install/action.yml b/.github/actions/install/action.yml index 1f43069b..86e16cc4 100644 --- a/.github/actions/install/action.yml +++ b/.github/actions/install/action.yml @@ -47,7 +47,7 @@ runs: cache-name: cache-v8 with: path: ${{ inputs.cache-dir }}/v8 - key: libc_v8_${{ inputs.v8 }}_${{ inputs.os }}_${{ inputs.arch }}.a + key: libc_v8_${{ inputs.v8 }}_${{ inputs.os }}_${{ inputs.arch }}_${{ inputs.zig-v8 }}.a - if: ${{ steps.cache-v8.outputs.cache-hit != 'true' }} shell: bash From 8d83dfad452797ee0e01f0b68d2cc263af9cf05f Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Thu, 17 Oct 2024 10:24:00 +0200 Subject: [PATCH 117/117] ci: force ubuntu version (24.04) Signed-off-by: Francis Bouvier --- .github/workflows/build.yml | 2 +- .github/workflows/wpt.yml | 4 ++-- .github/workflows/zig-fmt.yml | 2 +- .github/workflows/zig-test.yml | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 757d1a1c..b4e8d3d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: ARCH: x86_64 OS: linux - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/wpt.yml b/.github/workflows/wpt.yml index 10fdfeaf..67f1bc5d 100644 --- a/.github/workflows/wpt.yml +++ b/.github/workflows/wpt.yml @@ -44,7 +44,7 @@ jobs: # Don't run the CI with draft PR. if: github.event.pull_request.draft == false - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -86,7 +86,7 @@ jobs: # Don't execute on PR if: github.event_name != 'pull_request' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: ghcr.io/lightpanda-io/perf-fmt:latest credentials: diff --git a/.github/workflows/zig-fmt.yml b/.github/workflows/zig-fmt.yml index a425f988..60b90e04 100644 --- a/.github/workflows/zig-fmt.yml +++ b/.github/workflows/zig-fmt.yml @@ -28,7 +28,7 @@ jobs: # Don't run the CI with draft PR. if: github.event.pull_request.draft == false - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: mlugg/setup-zig@v1 diff --git a/.github/workflows/zig-test.yml b/.github/workflows/zig-test.yml index a341c9c0..ee6c3d1a 100644 --- a/.github/workflows/zig-test.yml +++ b/.github/workflows/zig-test.yml @@ -42,7 +42,7 @@ jobs: # Don't run the CI with draft PR. if: github.event.pull_request.draft == false - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -63,7 +63,7 @@ jobs: # Don't run the CI on PR if: github.event_name != 'pull_request' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -84,7 +84,7 @@ jobs: # Don't run the CI with draft PR. if: github.event.pull_request.draft == false - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -119,7 +119,7 @@ jobs: # Don't execute on PR if: github.event_name != 'pull_request' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 container: image: ghcr.io/lightpanda-io/perf-fmt:latest credentials: