mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
Refactor CDP
CDP is now an struct which contains its own state a browser and a session. When a client connection is made and successfully upgrades, the client creates the CDP instance. There is now a cleaner separation betwen Server, Client and CDP. Removed a number of allocations, especially when writing results/events from CDP to the client. Improved input message parsing. Tried to remove some usage of undefined.
This commit is contained in:
@@ -19,6 +19,8 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const Types = @import("root").Types;
|
const Types = @import("root").Types;
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("netsurf");
|
||||||
@@ -57,30 +59,44 @@ pub const user_agent = "Lightpanda/1.0";
|
|||||||
// A browser contains only one session.
|
// A browser contains only one session.
|
||||||
// TODO allow multiple sessions per browser.
|
// TODO allow multiple sessions per browser.
|
||||||
pub const Browser = struct {
|
pub const Browser = struct {
|
||||||
session: Session = undefined,
|
loop: *Loop,
|
||||||
agent: []const u8 = user_agent,
|
session: ?*Session,
|
||||||
|
allocator: Allocator,
|
||||||
|
session_pool: SessionPool,
|
||||||
|
|
||||||
|
const SessionPool = std.heap.MemoryPool(Session);
|
||||||
|
|
||||||
const uri = "about:blank";
|
const uri = "about:blank";
|
||||||
|
|
||||||
pub fn init(self: *Browser, alloc: std.mem.Allocator, loop: *Loop, vm: jsruntime.VM) !void {
|
pub fn init(allocator: Allocator, loop: *Loop) Browser {
|
||||||
// We want to ensure the caller initialised a VM, but the browser
|
return .{
|
||||||
// doesn't use it directly...
|
.loop = loop,
|
||||||
_ = vm;
|
.session = null,
|
||||||
|
.allocator = allocator,
|
||||||
try Session.init(&self.session, alloc, loop, uri);
|
.session_pool = SessionPool.init(allocator),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Browser) void {
|
pub fn deinit(self: *Browser) void {
|
||||||
self.session.deinit();
|
self.closeSession();
|
||||||
|
self.session_pool.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newSession(
|
pub fn newSession(self: *Browser, ctx: anytype) !*Session {
|
||||||
self: *Browser,
|
self.closeSession();
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
loop: *jsruntime.Loop,
|
const session = try self.session_pool.create();
|
||||||
) !void {
|
try Session.init(session, self.allocator, ctx, self.loop, uri);
|
||||||
self.session.deinit();
|
self.session = session;
|
||||||
try Session.init(&self.session, alloc, loop, uri);
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn closeSession(self: *Browser) void {
|
||||||
|
if (self.session) |session| {
|
||||||
|
session.deinit();
|
||||||
|
self.session_pool.destroy(session);
|
||||||
|
self.session = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -90,7 +106,7 @@ pub const Browser = struct {
|
|||||||
// deinit a page before running another one.
|
// deinit a page before running another one.
|
||||||
pub const Session = struct {
|
pub const Session = struct {
|
||||||
// allocator used to init the arena.
|
// allocator used to init the arena.
|
||||||
alloc: std.mem.Allocator,
|
allocator: Allocator,
|
||||||
|
|
||||||
// The arena is used only to bound the js env init b/c it leaks memory.
|
// The arena is used only to bound the js env init b/c it leaks memory.
|
||||||
// see https://github.com/lightpanda-io/jsruntime-lib/issues/181
|
// see https://github.com/lightpanda-io/jsruntime-lib/issues/181
|
||||||
@@ -103,8 +119,9 @@ pub const Session = struct {
|
|||||||
|
|
||||||
// TODO handle proxy
|
// TODO handle proxy
|
||||||
loader: Loader,
|
loader: Loader,
|
||||||
env: Env = undefined,
|
|
||||||
inspector: ?jsruntime.Inspector = null,
|
env: Env,
|
||||||
|
inspector: jsruntime.Inspector,
|
||||||
|
|
||||||
window: Window,
|
window: Window,
|
||||||
|
|
||||||
@@ -115,20 +132,54 @@ pub const Session = struct {
|
|||||||
|
|
||||||
jstypes: [Types.len]usize = undefined,
|
jstypes: [Types.len]usize = undefined,
|
||||||
|
|
||||||
fn init(self: *Session, alloc: std.mem.Allocator, loop: *Loop, uri: []const u8) !void {
|
fn init(self: *Session, allocator: Allocator, ctx: anytype, loop: *Loop, uri: []const u8) !void {
|
||||||
self.* = Session{
|
self.* = .{
|
||||||
.uri = uri,
|
.uri = uri,
|
||||||
.alloc = alloc,
|
.env = undefined,
|
||||||
.arena = std.heap.ArenaAllocator.init(alloc),
|
.inspector = undefined,
|
||||||
|
.allocator = allocator,
|
||||||
|
.loader = Loader.init(allocator),
|
||||||
|
.httpClient = .{ .allocator = allocator },
|
||||||
|
.storageShed = storage.Shed.init(allocator),
|
||||||
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
.window = Window.create(null, .{ .agent = user_agent }),
|
.window = Window.create(null, .{ .agent = user_agent }),
|
||||||
.loader = Loader.init(alloc),
|
|
||||||
.storageShed = storage.Shed.init(alloc),
|
|
||||||
.httpClient = undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Env.init(&self.env, self.arena.allocator(), loop, null);
|
const arena = self.arena.allocator();
|
||||||
self.httpClient = .{ .allocator = alloc };
|
|
||||||
|
Env.init(&self.env, arena, loop, null);
|
||||||
|
errdefer self.env.deinit();
|
||||||
try self.env.load(&self.jstypes);
|
try self.env.load(&self.jstypes);
|
||||||
|
|
||||||
|
const ContextT = @TypeOf(ctx);
|
||||||
|
const InspectorContainer = switch (@typeInfo(ContextT)) {
|
||||||
|
.Struct => ContextT,
|
||||||
|
.Pointer => |ptr| ptr.child,
|
||||||
|
.Void => NoopInspector,
|
||||||
|
else => @compileError("invalid context type"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// const ctx_opaque = @as(*anyopaque, @ptrCast(ctx));
|
||||||
|
self.inspector = try jsruntime.Inspector.init(
|
||||||
|
arena,
|
||||||
|
self.env,
|
||||||
|
if (@TypeOf(ctx) == void) @constCast(@ptrCast(&{})) else ctx,
|
||||||
|
InspectorContainer.onInspectorResponse,
|
||||||
|
InspectorContainer.onInspectorEvent,
|
||||||
|
);
|
||||||
|
self.env.setInspector(self.inspector);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: *Session) void {
|
||||||
|
if (self.page) |*p| {
|
||||||
|
p.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.env.deinit();
|
||||||
|
self.arena.deinit();
|
||||||
|
self.httpClient.deinit();
|
||||||
|
self.loader.deinit();
|
||||||
|
self.storageShed.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetchModule(ctx: *anyopaque, referrer: ?jsruntime.Module, specifier: []const u8) !jsruntime.Module {
|
fn fetchModule(ctx: *anyopaque, referrer: ?jsruntime.Module, specifier: []const u8) !jsruntime.Module {
|
||||||
@@ -146,47 +197,15 @@ pub const Session = struct {
|
|||||||
return self.env.compileModule(body, specifier);
|
return self.env.compileModule(body, specifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(self: *Session) void {
|
|
||||||
if (self.page) |*p| p.end();
|
|
||||||
|
|
||||||
if (self.inspector) |inspector| {
|
|
||||||
inspector.deinit(self.alloc);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.env.deinit();
|
|
||||||
self.arena.deinit();
|
|
||||||
|
|
||||||
self.httpClient.deinit();
|
|
||||||
self.loader.deinit();
|
|
||||||
self.storageShed.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn initInspector(
|
|
||||||
self: *Session,
|
|
||||||
ctx: anytype,
|
|
||||||
onResp: jsruntime.InspectorOnResponseFn,
|
|
||||||
onEvent: jsruntime.InspectorOnEventFn,
|
|
||||||
) !void {
|
|
||||||
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.?);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn callInspector(self: *Session, msg: []const u8) void {
|
pub fn callInspector(self: *Session, msg: []const u8) void {
|
||||||
if (self.inspector) |inspector| {
|
self.inspector.send(self.env, msg);
|
||||||
inspector.send(msg, self.env);
|
|
||||||
} else {
|
|
||||||
@panic("No Inspector");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: the caller is not the owner of the returned value,
|
// NOTE: the caller is not the owner of the returned value,
|
||||||
// the pointer on Page is just returned as a convenience
|
// the pointer on Page is just returned as a convenience
|
||||||
pub fn createPage(self: *Session) !*Page {
|
pub fn createPage(self: *Session) !*Page {
|
||||||
if (self.page != null) return error.SessionPageExists;
|
if (self.page != null) return error.SessionPageExists;
|
||||||
const p: Page = undefined;
|
self.page = Page.init(self.allocator, self);
|
||||||
self.page = p;
|
|
||||||
Page.init(&self.page.?, self.alloc, self);
|
|
||||||
return &self.page.?;
|
return &self.page.?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -197,8 +216,8 @@ pub const Session = struct {
|
|||||||
// The page handle all its memory in an arena allocator. The arena is reseted
|
// The page handle all its memory in an arena allocator. The arena is reseted
|
||||||
// when end() is called.
|
// when end() is called.
|
||||||
pub const Page = struct {
|
pub const Page = struct {
|
||||||
arena: std.heap.ArenaAllocator,
|
|
||||||
session: *Session,
|
session: *Session,
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
doc: ?*parser.Document = null,
|
doc: ?*parser.Document = null,
|
||||||
|
|
||||||
// handle url
|
// handle url
|
||||||
@@ -212,17 +231,18 @@ pub const Page = struct {
|
|||||||
|
|
||||||
raw_data: ?[]const u8 = null,
|
raw_data: ?[]const u8 = null,
|
||||||
|
|
||||||
fn init(
|
fn init(allocator: Allocator, session: *Session) Page {
|
||||||
self: *Page,
|
return .{
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
session: *Session,
|
|
||||||
) void {
|
|
||||||
self.* = .{
|
|
||||||
.arena = std.heap.ArenaAllocator.init(alloc),
|
|
||||||
.session = session,
|
.session = session,
|
||||||
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Page) void {
|
||||||
|
self.arena.deinit();
|
||||||
|
self.session.page = null;
|
||||||
|
}
|
||||||
|
|
||||||
// start js env.
|
// start js env.
|
||||||
// - auxData: extra data forwarded to the Inspector
|
// - auxData: extra data forwarded to the Inspector
|
||||||
// see Inspector.contextCreated
|
// see Inspector.contextCreated
|
||||||
@@ -242,10 +262,8 @@ pub const Page = struct {
|
|||||||
try polyfill.load(self.arena.allocator(), self.session.env);
|
try polyfill.load(self.arena.allocator(), self.session.env);
|
||||||
|
|
||||||
// inspector
|
// inspector
|
||||||
if (self.session.inspector) |inspector| {
|
log.debug("inspector context created", .{});
|
||||||
log.debug("inspector context created", .{});
|
self.session.inspector.contextCreated(self.session.env, "", self.origin orelse "://", auxData);
|
||||||
inspector.contextCreated(self.session.env, "", self.origin orelse "://", auxData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset js env and mem arena.
|
// reset js env and mem arena.
|
||||||
@@ -253,7 +271,6 @@ pub const Page = struct {
|
|||||||
self.session.env.stop();
|
self.session.env.stop();
|
||||||
// TODO unload document: https://html.spec.whatwg.org/#unloading-documents
|
// TODO unload document: https://html.spec.whatwg.org/#unloading-documents
|
||||||
|
|
||||||
if (self.url) |*u| u.deinit(self.arena.allocator());
|
|
||||||
self.url = null;
|
self.url = null;
|
||||||
self.location.url = null;
|
self.location.url = null;
|
||||||
self.session.window.replaceLocation(&self.location) catch |e| {
|
self.session.window.replaceLocation(&self.location) catch |e| {
|
||||||
@@ -266,13 +283,8 @@ pub const Page = struct {
|
|||||||
_ = self.arena.reset(.free_all);
|
_ = self.arena.reset(.free_all);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Page) void {
|
|
||||||
self.arena.deinit();
|
|
||||||
self.session.page = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dump writes the page content into the given file.
|
// dump writes the page content into the given file.
|
||||||
pub fn dump(self: *Page, out: std.fs.File) !void {
|
pub fn dump(self: *const Page, out: std.fs.File) !void {
|
||||||
|
|
||||||
// if no HTML document pointer available, dump the data content only.
|
// if no HTML document pointer available, dump the data content only.
|
||||||
if (self.doc == null) {
|
if (self.doc == null) {
|
||||||
@@ -320,11 +332,9 @@ pub const Page = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// own the url
|
// own the url
|
||||||
if (self.rawuri) |prev| alloc.free(prev);
|
|
||||||
self.rawuri = try alloc.dupe(u8, uri);
|
self.rawuri = try alloc.dupe(u8, uri);
|
||||||
self.uri = std.Uri.parse(self.rawuri.?) catch try std.Uri.parseAfterScheme("", self.rawuri.?);
|
self.uri = std.Uri.parse(self.rawuri.?) catch try std.Uri.parseAfterScheme("", self.rawuri.?);
|
||||||
|
|
||||||
if (self.url) |*prev| prev.deinit(alloc);
|
|
||||||
self.url = try URL.constructor(alloc, self.rawuri.?, null);
|
self.url = try URL.constructor(alloc, self.rawuri.?, null);
|
||||||
self.location.url = &self.url.?;
|
self.location.url = &self.url.?;
|
||||||
try self.session.window.replaceLocation(&self.location);
|
try self.session.window.replaceLocation(&self.location);
|
||||||
@@ -422,9 +432,7 @@ pub const Page = struct {
|
|||||||
// https://html.spec.whatwg.org/#read-html
|
// https://html.spec.whatwg.org/#read-html
|
||||||
|
|
||||||
// inspector
|
// inspector
|
||||||
if (self.session.inspector) |inspector| {
|
self.session.inspector.contextCreated(self.session.env, "", self.origin.?, auxData);
|
||||||
inspector.contextCreated(self.session.env, "", self.origin.?, auxData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace the user context document with the new one.
|
// replace the user context document with the new one.
|
||||||
try self.session.env.setUserContext(.{
|
try self.session.env.setUserContext(.{
|
||||||
@@ -583,7 +591,7 @@ pub const Page = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// the caller owns the returned string
|
// the caller owns the returned string
|
||||||
fn fetchData(self: *Page, alloc: std.mem.Allocator, src: []const u8) ![]const u8 {
|
fn fetchData(self: *Page, alloc: Allocator, src: []const u8) ![]const u8 {
|
||||||
log.debug("starting fetch {s}", .{src});
|
log.debug("starting fetch {s}", .{src});
|
||||||
|
|
||||||
var buffer: [1024]u8 = undefined;
|
var buffer: [1024]u8 = undefined;
|
||||||
@@ -658,7 +666,7 @@ pub const Page = struct {
|
|||||||
return .unknown;
|
return .unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval(self: Script, alloc: std.mem.Allocator, env: Env, body: []const u8) !void {
|
fn eval(self: Script, alloc: Allocator, env: Env, body: []const u8) !void {
|
||||||
var try_catch: jsruntime.TryCatch = undefined;
|
var try_catch: jsruntime.TryCatch = undefined;
|
||||||
try_catch.init(env);
|
try_catch.init(env);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
@@ -683,3 +691,8 @@ pub const Page = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const NoopInspector = struct {
|
||||||
|
pub fn onInspectorResponse(_: *anyopaque, _: u32, _: []const u8) void {}
|
||||||
|
pub fn onInspectorEvent(_: *anyopaque, _: []const u8) void {}
|
||||||
|
};
|
||||||
|
|||||||
@@ -17,132 +17,66 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const server = @import("../server.zig");
|
|
||||||
const Ctx = server.Ctx;
|
|
||||||
const cdp = @import("cdp.zig");
|
const cdp = @import("cdp.zig");
|
||||||
const result = cdp.result;
|
|
||||||
const IncomingMessage = @import("msg.zig").IncomingMessage;
|
|
||||||
const Input = @import("msg.zig").Input;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.cdp);
|
|
||||||
|
|
||||||
const Methods = enum {
|
|
||||||
getVersion,
|
|
||||||
setDownloadBehavior,
|
|
||||||
getWindowForTarget,
|
|
||||||
setWindowBounds,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn browser(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
action: []const u8,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
const method = std.meta.stringToEnum(Methods, action) orelse
|
|
||||||
return error.UnknownMethod;
|
|
||||||
return switch (method) {
|
|
||||||
.getVersion => getVersion(alloc, msg, ctx),
|
|
||||||
.setDownloadBehavior => setDownloadBehavior(alloc, msg, ctx),
|
|
||||||
.getWindowForTarget => getWindowForTarget(alloc, msg, ctx),
|
|
||||||
.setWindowBounds => setWindowBounds(alloc, msg, ctx),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: hard coded data
|
// TODO: hard coded data
|
||||||
const ProtocolVersion = "1.3";
|
const PROTOCOL_VERSION = "1.3";
|
||||||
const Product = "Chrome/124.0.6367.29";
|
const PRODUCT = "Chrome/124.0.6367.29";
|
||||||
const Revision = "@9e6ded5ac1ff5e38d930ae52bd9aec09bd1a68e4";
|
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 USER_AGENT = "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";
|
const JS_VERSION = "12.4.254.8";
|
||||||
|
const DEV_TOOLS_WINDOW_ID = 1923710101;
|
||||||
|
|
||||||
fn getVersion(
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
const action = std.meta.stringToEnum(enum {
|
||||||
msg: *IncomingMessage,
|
getVersion,
|
||||||
_: *Ctx,
|
setDownloadBehavior,
|
||||||
) ![]const u8 {
|
getWindowForTarget,
|
||||||
// input
|
setWindowBounds,
|
||||||
const input = try Input(void).get(alloc, msg);
|
}, cmd.action) orelse return error.UnknownMethod;
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "browser.getVersion" });
|
|
||||||
|
|
||||||
// ouput
|
switch (action) {
|
||||||
const Res = struct {
|
.getVersion => return getVersion(cmd),
|
||||||
protocolVersion: []const u8 = ProtocolVersion,
|
.setDownloadBehavior => return setDownloadBehavior(cmd),
|
||||||
product: []const u8 = Product,
|
.getWindowForTarget => return getWindowForTarget(cmd),
|
||||||
revision: []const u8 = Revision,
|
.setWindowBounds => return setWindowBounds(cmd),
|
||||||
userAgent: []const u8 = UserAgent,
|
}
|
||||||
jsVersion: []const u8 = JsVersion,
|
}
|
||||||
};
|
|
||||||
return result(alloc, input.id, Res, .{}, null);
|
fn getVersion(cmd: anytype) !void {
|
||||||
|
// TODO: pre-serialize?
|
||||||
|
return cmd.sendResult(.{
|
||||||
|
.protocolVersion = PROTOCOL_VERSION,
|
||||||
|
.product = PRODUCT,
|
||||||
|
.revision = REVISION,
|
||||||
|
.userAgent = USER_AGENT,
|
||||||
|
.jsVersion = JS_VERSION,
|
||||||
|
}, .{ .include_session_id = false });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: noop method
|
// TODO: noop method
|
||||||
fn setDownloadBehavior(
|
fn setDownloadBehavior(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
// const params = (try cmd.params(struct {
|
||||||
msg: *IncomingMessage,
|
// behavior: []const u8,
|
||||||
_: *Ctx,
|
// browserContextId: ?[]const u8 = null,
|
||||||
) ![]const u8 {
|
// downloadPath: ?[]const u8 = null,
|
||||||
// input
|
// eventsEnabled: ?bool = null,
|
||||||
const Params = struct {
|
// })) orelse return error.InvalidParams;
|
||||||
behavior: []const u8,
|
|
||||||
browserContextId: ?[]const u8 = null,
|
|
||||||
downloadPath: ?[]const u8 = null,
|
|
||||||
eventsEnabled: ?bool = null,
|
|
||||||
};
|
|
||||||
const input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("REQ > id {d}, method {s}", .{ input.id, "browser.setDownloadBehavior" });
|
|
||||||
|
|
||||||
// output
|
return cmd.sendResult(null, .{ .include_session_id = false });
|
||||||
return result(alloc, input.id, null, null, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: hard coded ID
|
fn getWindowForTarget(cmd: anytype) !void {
|
||||||
const DevToolsWindowID = 1923710101;
|
// const params = (try cmd.params(struct {
|
||||||
|
// targetId: ?[]const u8 = null,
|
||||||
|
// })) orelse return error.InvalidParams;
|
||||||
|
|
||||||
fn getWindowForTarget(
|
return cmd.sendResult(.{ .windowId = DEV_TOOLS_WINDOW_ID, .bounds = .{
|
||||||
alloc: std.mem.Allocator,
|
.windowState = "normal",
|
||||||
msg: *IncomingMessage,
|
} }, .{});
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
|
|
||||||
// input
|
|
||||||
const Params = struct {
|
|
||||||
targetId: ?[]const u8 = null,
|
|
||||||
};
|
|
||||||
const input = try Input(?Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
std.debug.assert(input.sessionId != null);
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "browser.getWindowForTarget" });
|
|
||||||
|
|
||||||
// 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, input.id, Resp, Resp{}, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: noop method
|
// TODO: noop method
|
||||||
fn setWindowBounds(
|
fn setWindowBounds(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
return cmd.sendResult(null, .{});
|
||||||
msg: *IncomingMessage,
|
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
|
|
||||||
// input
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "browser.setWindowBounds" });
|
|
||||||
|
|
||||||
// output
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
549
src/cdp/cdp.zig
549
src/cdp/cdp.zig
@@ -17,209 +17,387 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const json = std.json;
|
||||||
|
|
||||||
const server = @import("../server.zig");
|
const Loop = @import("jsruntime").Loop;
|
||||||
const Ctx = server.Ctx;
|
const Client = @import("../server.zig").Client;
|
||||||
|
const asUint = @import("../str/parser.zig").asUint;
|
||||||
|
const Browser = @import("../browser/browser.zig").Browser;
|
||||||
|
const Session = @import("../browser/browser.zig").Session;
|
||||||
|
|
||||||
const browser = @import("browser.zig").browser;
|
const log = std.log.scoped(.cdp);
|
||||||
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;
|
|
||||||
const fetch = @import("fetch.zig").fetch;
|
|
||||||
const performance = @import("performance.zig").performance;
|
|
||||||
const IncomingMessage = @import("msg.zig").IncomingMessage;
|
|
||||||
const Input = @import("msg.zig").Input;
|
|
||||||
const inspector = @import("inspector.zig").inspector;
|
|
||||||
const dom = @import("dom.zig").dom;
|
|
||||||
const css = @import("css.zig").css;
|
|
||||||
const security = @import("security.zig").security;
|
|
||||||
|
|
||||||
const log_cdp = std.log.scoped(.cdp);
|
pub const URL_BASE = "chrome://newtab/";
|
||||||
|
pub const LOADER_ID = "LOADERID24DD2FD56CF1EF33C965C79C";
|
||||||
|
pub const FRAME_ID = "FRAMEIDD8AED408A0467AC93100BCDBE";
|
||||||
|
pub const BROWSER_SESSION_ID = @tagName(SessionID.BROWSERSESSIONID597D9875C664CAC0);
|
||||||
|
pub const CONTEXT_SESSION_ID = @tagName(SessionID.CONTEXTSESSIONID0497A05C95417CF4);
|
||||||
|
|
||||||
pub const Error = error{
|
pub const TimestampEvent = struct {
|
||||||
UnknonwDomain,
|
timestamp: f64,
|
||||||
UnknownMethod,
|
|
||||||
NoResponse,
|
|
||||||
RequestWithoutID,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn isCdpError(err: anyerror) ?Error {
|
pub const CDP = struct {
|
||||||
// see https://github.com/ziglang/zig/issues/2473
|
// Used for sending message to the client and closing on error
|
||||||
const errors = @typeInfo(Error).ErrorSet.?;
|
client: *Client,
|
||||||
inline for (errors) |e| {
|
|
||||||
if (std.mem.eql(u8, e.name, @errorName(err))) {
|
// The active browser
|
||||||
return @errorCast(err);
|
browser: Browser,
|
||||||
}
|
|
||||||
|
// The active browser session
|
||||||
|
session: ?*Session,
|
||||||
|
|
||||||
|
allocator: Allocator,
|
||||||
|
|
||||||
|
// Re-used arena for processing a message. We're assuming that we're getting
|
||||||
|
// 1 message at a time.
|
||||||
|
message_arena: std.heap.ArenaAllocator,
|
||||||
|
|
||||||
|
// State
|
||||||
|
url: []const u8,
|
||||||
|
frame_id: []const u8,
|
||||||
|
loader_id: []const u8,
|
||||||
|
session_id: SessionID,
|
||||||
|
context_id: ?[]const u8,
|
||||||
|
execution_context_id: u32,
|
||||||
|
security_origin: []const u8,
|
||||||
|
page_life_cycle_events: bool,
|
||||||
|
secure_context_type: []const u8,
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator, client: *Client, loop: *Loop) CDP {
|
||||||
|
return .{
|
||||||
|
.client = client,
|
||||||
|
.browser = Browser.init(allocator, loop),
|
||||||
|
.session = null,
|
||||||
|
.allocator = allocator,
|
||||||
|
.url = URL_BASE,
|
||||||
|
.execution_context_id = 0,
|
||||||
|
.context_id = null,
|
||||||
|
.frame_id = FRAME_ID,
|
||||||
|
.session_id = .CONTEXTSESSIONID0497A05C95417CF4,
|
||||||
|
.security_origin = URL_BASE,
|
||||||
|
.secure_context_type = "Secure", // TODO = enum
|
||||||
|
.loader_id = LOADER_ID,
|
||||||
|
.message_arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
|
.page_life_cycle_events = false, // TODO; Target based value
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Domains = enum {
|
pub fn deinit(self: *CDP) void {
|
||||||
Browser,
|
self.browser.deinit();
|
||||||
Target,
|
self.message_arena.deinit();
|
||||||
Page,
|
}
|
||||||
Log,
|
|
||||||
Runtime,
|
pub fn newSession(self: *CDP) !void {
|
||||||
Network,
|
self.session = try self.browser.newSession(self);
|
||||||
DOM,
|
}
|
||||||
CSS,
|
|
||||||
Inspector,
|
pub fn processMessage(self: *CDP, msg: []const u8) void {
|
||||||
Emulation,
|
const arena = &self.message_arena;
|
||||||
Fetch,
|
defer _ = arena.reset(.{ .retain_with_limit = 1024 * 16 });
|
||||||
Performance,
|
|
||||||
Security,
|
self.dispatch(arena.allocator(), self, msg) catch |err| {
|
||||||
|
log.err("failed to process message: {}\n{s}", .{ err, msg });
|
||||||
|
self.client.close(null);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from above, in processMessage which handles client messages
|
||||||
|
// but can also be called internally. For example, Target.sendMessageToTarget
|
||||||
|
// calls back into dispatch.
|
||||||
|
pub fn dispatch(
|
||||||
|
self: *CDP,
|
||||||
|
arena: Allocator,
|
||||||
|
sender: anytype,
|
||||||
|
str: []const u8,
|
||||||
|
) anyerror!void {
|
||||||
|
const input = try json.parseFromSliceLeaky(InputMessage, arena, str, .{
|
||||||
|
.ignore_unknown_fields = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const domain, const action = blk: {
|
||||||
|
const method = input.method;
|
||||||
|
const i = std.mem.indexOfScalarPos(u8, method, 0, '.') orelse {
|
||||||
|
return error.InvalidMethod;
|
||||||
|
};
|
||||||
|
break :blk .{ method[0..i], method[i + 1 ..] };
|
||||||
|
};
|
||||||
|
|
||||||
|
var command = Command(@TypeOf(sender)){
|
||||||
|
.json = str,
|
||||||
|
.cdp = self,
|
||||||
|
.id = input.id,
|
||||||
|
.arena = arena,
|
||||||
|
.action = action,
|
||||||
|
._params = input.params,
|
||||||
|
.session_id = input.sessionId,
|
||||||
|
.sender = sender,
|
||||||
|
.session = self.session orelse blk: {
|
||||||
|
try self.newSession();
|
||||||
|
break :blk self.session.?;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (domain.len) {
|
||||||
|
3 => switch (@as(u24, @bitCast(domain[0..3].*))) {
|
||||||
|
asUint("DOM") => return @import("dom.zig").processMessage(&command),
|
||||||
|
asUint("Log") => return @import("log.zig").processMessage(&command),
|
||||||
|
asUint("CSS") => return @import("css.zig").processMessage(&command),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
4 => switch (@as(u32, @bitCast(domain[0..4].*))) {
|
||||||
|
asUint("Page") => return @import("page.zig").processMessage(&command),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
5 => switch (@as(u40, @bitCast(domain[0..5].*))) {
|
||||||
|
asUint("Fetch") => return @import("fetch.zig").processMessage(&command),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
6 => switch (@as(u48, @bitCast(domain[0..6].*))) {
|
||||||
|
asUint("Target") => return @import("target.zig").processMessage(&command),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
7 => switch (@as(u56, @bitCast(domain[0..7].*))) {
|
||||||
|
asUint("Browser") => return @import("browser.zig").processMessage(&command),
|
||||||
|
asUint("Runtime") => return @import("runtime.zig").processMessage(&command),
|
||||||
|
asUint("Network") => return @import("network.zig").processMessage(&command),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
8 => switch (@as(u64, @bitCast(domain[0..8].*))) {
|
||||||
|
asUint("Security") => return @import("security.zig").processMessage(&command),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
9 => switch (@as(u72, @bitCast(domain[0..9].*))) {
|
||||||
|
asUint("Emulation") => return @import("emulation.zig").processMessage(&command),
|
||||||
|
asUint("Inspector") => return @import("inspector.zig").processMessage(&command),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
11 => switch (@as(u88, @bitCast(domain[0..11].*))) {
|
||||||
|
asUint("Performance") => return @import("performance.zig").processMessage(&command),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
return error.UnknownDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sendJSON(self: *CDP, message: anytype) !void {
|
||||||
|
return self.client.sendJSON(message, .{
|
||||||
|
.emit_null_optional_fields = false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn onInspectorResponse(ctx: *anyopaque, _: u32, msg: []const u8) void {
|
||||||
|
if (std.log.defaultLogEnabled(.debug)) {
|
||||||
|
// msg should be {"id":<id>,...
|
||||||
|
std.debug.assert(std.mem.startsWith(u8, msg, "{\"id\":"));
|
||||||
|
|
||||||
|
const id_end = std.mem.indexOfScalar(u8, msg, ',') orelse {
|
||||||
|
log.warn("invalid inspector response message: {s}", .{msg});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const id = msg[6..id_end];
|
||||||
|
log.debug("Res (inspector) > id {s}", .{id});
|
||||||
|
}
|
||||||
|
sendInspectorMessage(@alignCast(@ptrCast(ctx)), msg) catch |err| {
|
||||||
|
log.err("Failed to send inspector response: {any}", .{err});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn onInspectorEvent(ctx: *anyopaque, msg: []const u8) void {
|
||||||
|
if (std.log.defaultLogEnabled(.debug)) {
|
||||||
|
// msg should be {"method":<method>,...
|
||||||
|
std.debug.assert(std.mem.startsWith(u8, msg, "{\"method\":"));
|
||||||
|
const method_end = std.mem.indexOfScalar(u8, msg, ',') orelse {
|
||||||
|
log.warn("invalid inspector event message: {s}", .{msg});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const method = msg[10..method_end];
|
||||||
|
log.debug("Event (inspector) > method {s}", .{method});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendInspectorMessage(@alignCast(@ptrCast(ctx)), msg) catch |err| {
|
||||||
|
log.err("Failed to send inspector event: {any}", .{err});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is hacky * 2. First, we have the JSON payload by gluing our
|
||||||
|
// session_id onto it. Second, we're much more client/websocket aware than
|
||||||
|
// we should be.
|
||||||
|
fn sendInspectorMessage(self: *CDP, msg: []const u8) !void {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(self.allocator);
|
||||||
|
errdefer arena.deinit();
|
||||||
|
|
||||||
|
const field = ",\"sessionId\":\"";
|
||||||
|
const session_id = @tagName(self.session_id);
|
||||||
|
|
||||||
|
// + 1 for the closing quote after the session id
|
||||||
|
// + 10 for the max websocket header
|
||||||
|
|
||||||
|
const message_len = msg.len + session_id.len + 1 + field.len + 10;
|
||||||
|
|
||||||
|
var buf: std.ArrayListUnmanaged(u8) = .{};
|
||||||
|
buf.ensureTotalCapacity(arena.allocator(), message_len) catch |err| {
|
||||||
|
log.err("Failed to expand inspector buffer: {any}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// reserve 10 bytes for websocket header
|
||||||
|
buf.appendSliceAssumeCapacity(&.{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 });
|
||||||
|
|
||||||
|
// -1 because we dont' want the closing brace '}'
|
||||||
|
buf.appendSliceAssumeCapacity(msg[0 .. msg.len - 1]);
|
||||||
|
buf.appendSliceAssumeCapacity(field);
|
||||||
|
buf.appendSliceAssumeCapacity(session_id);
|
||||||
|
buf.appendSliceAssumeCapacity("\"}");
|
||||||
|
std.debug.assert(buf.items.len == message_len);
|
||||||
|
|
||||||
|
try self.client.sendJSONRaw(arena, buf);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// The caller is responsible for calling `free` on the returned slice.
|
// This is a generic because when we send a result we have two different
|
||||||
pub fn do(
|
// behaviors. Normally, we're sending the result to the client. But in some cases
|
||||||
alloc: std.mem.Allocator,
|
// we want to capture the result. So we want the command.sendResult to be
|
||||||
s: []const u8,
|
// generic.
|
||||||
ctx: *Ctx,
|
pub fn Command(comptime Sender: type) type {
|
||||||
) anyerror![]const u8 {
|
return struct {
|
||||||
|
// refernece to our CDP instance
|
||||||
|
cdp: *CDP,
|
||||||
|
|
||||||
// incoming message parser
|
// Comes directly from the input.id field
|
||||||
var msg = IncomingMessage.init(alloc, s);
|
id: ?i64,
|
||||||
defer msg.deinit();
|
|
||||||
|
|
||||||
return dispatch(alloc, &msg, ctx);
|
// A misc arena that can be used for any allocation for processing
|
||||||
}
|
// the message
|
||||||
|
arena: Allocator,
|
||||||
|
|
||||||
pub fn dispatch(
|
// the browser session
|
||||||
alloc: std.mem.Allocator,
|
session: *Session,
|
||||||
msg: *IncomingMessage,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) anyerror![]const u8 {
|
|
||||||
const method = try msg.getMethod();
|
|
||||||
|
|
||||||
// retrieve domain from method
|
// The "action" of the message.Given a method of "LOG.enable", the
|
||||||
var iter = std.mem.splitScalar(u8, method, '.');
|
// action is "enable"
|
||||||
const domain = std.meta.stringToEnum(Domains, iter.first()) orelse
|
action: []const u8,
|
||||||
return error.UnknonwDomain;
|
|
||||||
|
|
||||||
// select corresponding domain
|
// Comes directly from the input.sessionId field
|
||||||
const action = iter.next() orelse return error.BadMethod;
|
session_id: ?[]const u8,
|
||||||
return switch (domain) {
|
|
||||||
.Browser => browser(alloc, msg, action, ctx),
|
// Unparsed / untyped input.params.
|
||||||
.Target => target(alloc, msg, action, ctx),
|
_params: ?InputParams,
|
||||||
.Page => page(alloc, msg, action, ctx),
|
|
||||||
.Log => log(alloc, msg, action, ctx),
|
// The full raw json input
|
||||||
.Runtime => runtime(alloc, msg, action, ctx),
|
json: []const u8,
|
||||||
.Network => network(alloc, msg, action, ctx),
|
|
||||||
.DOM => dom(alloc, msg, action, ctx),
|
sender: Sender,
|
||||||
.CSS => css(alloc, msg, action, ctx),
|
|
||||||
.Inspector => inspector(alloc, msg, action, ctx),
|
const Self = @This();
|
||||||
.Emulation => emulation(alloc, msg, action, ctx),
|
|
||||||
.Fetch => fetch(alloc, msg, action, ctx),
|
pub fn params(self: *const Self, comptime T: type) !?T {
|
||||||
.Performance => performance(alloc, msg, action, ctx),
|
if (self._params) |p| {
|
||||||
.Security => security(alloc, msg, action, ctx),
|
return try json.parseFromSliceLeaky(
|
||||||
|
T,
|
||||||
|
self.arena,
|
||||||
|
p.raw,
|
||||||
|
.{ .ignore_unknown_fields = true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SendResultOpts = struct {
|
||||||
|
include_session_id: bool = true,
|
||||||
|
};
|
||||||
|
pub fn sendResult(self: *Self, result: anytype, opts: SendResultOpts) !void {
|
||||||
|
return self.sender.sendJSON(.{
|
||||||
|
.id = self.id,
|
||||||
|
.result = if (comptime @typeInfo(@TypeOf(result)) == .Null) struct {}{} else result,
|
||||||
|
.sessionId = if (opts.include_session_id) self.session_id else null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const SendEventOpts = struct {
|
||||||
|
session_id: ?[]const u8 = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn sendEvent(self: *Self, method: []const u8, p: anytype, opts: SendEventOpts) !void {
|
||||||
|
// Events ALWAYS go to the client. self.sender should not be used
|
||||||
|
return self.cdp.sendJSON(.{
|
||||||
|
.method = method,
|
||||||
|
.params = if (comptime @typeInfo(@TypeOf(p)) == .Null) struct {}{} else p,
|
||||||
|
.sessionId = opts.session_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const State = struct {
|
// When we parse a JSON message from the client, this is the structure
|
||||||
executionContextId: u32 = 0,
|
// we always expect
|
||||||
contextID: ?[]const u8 = null,
|
const InputMessage = struct {
|
||||||
sessionID: SessionID = .CONTEXTSESSIONID0497A05C95417CF4,
|
id: ?i64,
|
||||||
frameID: []const u8 = FrameID,
|
method: []const u8,
|
||||||
url: []const u8 = URLBase,
|
params: ?InputParams = null,
|
||||||
securityOrigin: []const u8 = URLBase,
|
sessionId: ?[]const u8 = null,
|
||||||
secureContextType: []const u8 = "Secure", // TODO: enum
|
};
|
||||||
loaderID: []const u8 = LoaderID,
|
|
||||||
|
|
||||||
page_life_cycle_events: bool = false, // TODO; Target based value
|
// The JSON "params" field changes based on the "method". Initially, we just
|
||||||
|
// capture the raw json object (including the opening and closing braces).
|
||||||
|
// Then, when we're processing the message, and we know what type it is, we
|
||||||
|
// can parse it (in Disaptch(T).params).
|
||||||
|
const InputParams = struct {
|
||||||
|
raw: []const u8,
|
||||||
|
|
||||||
|
pub fn jsonParse(
|
||||||
|
_: Allocator,
|
||||||
|
scanner: *json.Scanner,
|
||||||
|
_: json.ParseOptions,
|
||||||
|
) !InputParams {
|
||||||
|
const height = scanner.stackHeight();
|
||||||
|
|
||||||
|
const start = scanner.cursor;
|
||||||
|
if (try scanner.next() != .object_begin) {
|
||||||
|
return error.UnexpectedToken;
|
||||||
|
}
|
||||||
|
try scanner.skipUntilStackHeight(height);
|
||||||
|
const end = scanner.cursor;
|
||||||
|
|
||||||
|
return .{ .raw = scanner.input[start..end] };
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
pub fn dumpFile(
|
// pub fn dumpFile(
|
||||||
alloc: std.mem.Allocator,
|
// alloc: std.mem.Allocator,
|
||||||
id: u16,
|
// id: u16,
|
||||||
script: []const u8,
|
// script: []const u8,
|
||||||
) !void {
|
// ) !void {
|
||||||
const name = try std.fmt.allocPrint(alloc, "id_{d}.js", .{id});
|
// const name = try std.fmt.allocPrint(alloc, "id_{d}.js", .{id});
|
||||||
defer alloc.free(name);
|
// defer alloc.free(name);
|
||||||
var dir = try std.fs.cwd().makeOpenPath("zig-cache/tmp", .{});
|
// var dir = try std.fs.cwd().makeOpenPath("zig-cache/tmp", .{});
|
||||||
defer dir.close();
|
// defer dir.close();
|
||||||
const f = try dir.createFile(name, .{});
|
// const f = try dir.createFile(name, .{});
|
||||||
defer f.close();
|
// defer f.close();
|
||||||
const nb = try f.write(script);
|
// const nb = try f.write(script);
|
||||||
std.debug.assert(nb == script.len);
|
// std.debug.assert(nb == script.len);
|
||||||
const p = try dir.realpathAlloc(alloc, name);
|
// const p = try dir.realpathAlloc(alloc, name);
|
||||||
defer alloc.free(p);
|
// defer alloc.free(p);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// caller owns the slice returned
|
// // caller owns the slice returned
|
||||||
pub fn stringify(alloc: std.mem.Allocator, res: anytype) ![]const u8 {
|
// pub fn stringify(alloc: std.mem.Allocator, res: anytype) ![]const u8 {
|
||||||
var out = std.ArrayList(u8).init(alloc);
|
// var out = std.ArrayList(u8).init(alloc);
|
||||||
defer out.deinit();
|
// defer out.deinit();
|
||||||
|
|
||||||
// Do not emit optional null fields
|
// // Do not emit optional null fields
|
||||||
const options: std.json.StringifyOptions = .{ .emit_null_optional_fields = false };
|
// const options: std.json.StringifyOptions = .{ .emit_null_optional_fields = false };
|
||||||
|
|
||||||
try std.json.stringify(res, options, out.writer());
|
// try std.json.stringify(res, options, out.writer());
|
||||||
const ret = try alloc.alloc(u8, out.items.len);
|
// const ret = try alloc.alloc(u8, out.items.len);
|
||||||
@memcpy(ret, out.items);
|
// @memcpy(ret, out.items);
|
||||||
return ret;
|
// 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: u16,
|
|
||||||
comptime T: ?type,
|
|
||||||
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| {
|
|
||||||
return try std.fmt.allocPrint(alloc, resultNullSession, .{ id, sID });
|
|
||||||
}
|
|
||||||
return try std.fmt.allocPrint(alloc, resultNull, .{id});
|
|
||||||
}
|
|
||||||
|
|
||||||
const Resp = struct {
|
|
||||||
id: u16,
|
|
||||||
result: T.?,
|
|
||||||
sessionId: ?[]const u8,
|
|
||||||
};
|
|
||||||
const resp = Resp{ .id = id, .result = res, .sessionId = sessionID };
|
|
||||||
|
|
||||||
return stringify(alloc, resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sendEvent(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
ctx: *Ctx,
|
|
||||||
name: []const u8,
|
|
||||||
comptime T: type,
|
|
||||||
params: T,
|
|
||||||
sessionID: ?[]const u8,
|
|
||||||
) !void {
|
|
||||||
// some clients like chromedp expects empty parameters structs.
|
|
||||||
if (T == void) @compileError("sendEvent: use struct{} instead of void for empty parameters");
|
|
||||||
|
|
||||||
log_cdp.debug("Event > method {s}, sessionID {?s}", .{ name, sessionID });
|
|
||||||
const Resp = struct {
|
|
||||||
method: []const u8,
|
|
||||||
params: T,
|
|
||||||
sessionId: ?[]const u8,
|
|
||||||
};
|
|
||||||
const resp = Resp{ .method = name, .params = params, .sessionId = sessionID };
|
|
||||||
|
|
||||||
const event_msg = try stringify(alloc, resp);
|
|
||||||
try ctx.send(event_msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common
|
// Common
|
||||||
// ------
|
// ------
|
||||||
@@ -230,20 +408,9 @@ pub const SessionID = enum {
|
|||||||
CONTEXTSESSIONID0497A05C95417CF4,
|
CONTEXTSESSIONID0497A05C95417CF4,
|
||||||
|
|
||||||
pub fn parse(str: []const u8) !SessionID {
|
pub fn parse(str: []const u8) !SessionID {
|
||||||
inline for (@typeInfo(SessionID).Enum.fields) |enumField| {
|
return std.meta.stringToEnum(SessionID, str) orelse {
|
||||||
if (std.mem.eql(u8, str, enumField.name)) {
|
log.err("parse sessionID: {s}", .{str});
|
||||||
return @field(SessionID, enumField.name);
|
return error.InvalidSessionID;
|
||||||
}
|
};
|
||||||
}
|
|
||||||
return error.InvalidSessionID;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
pub const BrowserSessionID = @tagName(SessionID.BROWSERSESSIONID597D9875C664CAC0);
|
|
||||||
pub const ContextSessionID = @tagName(SessionID.CONTEXTSESSIONID0497A05C95417CF4);
|
|
||||||
pub const URLBase = "chrome://newtab/";
|
|
||||||
pub const LoaderID = "LOADERID24DD2FD56CF1EF33C965C79C";
|
|
||||||
pub const FrameID = "FRAMEIDD8AED408A0467AC93100BCDBE";
|
|
||||||
|
|
||||||
pub const TimestampEvent = struct {
|
|
||||||
timestamp: f64,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -17,43 +17,14 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const server = @import("../server.zig");
|
|
||||||
const Ctx = server.Ctx;
|
|
||||||
const cdp = @import("cdp.zig");
|
const cdp = @import("cdp.zig");
|
||||||
const result = cdp.result;
|
|
||||||
const IncomingMessage = @import("msg.zig").IncomingMessage;
|
|
||||||
const Input = @import("msg.zig").Input;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.cdp);
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
|
const action = std.meta.stringToEnum(enum {
|
||||||
|
enable,
|
||||||
|
}, cmd.action) orelse return error.UnknownMethod;
|
||||||
|
|
||||||
const Methods = enum {
|
switch (action) {
|
||||||
enable,
|
.enable => return cmd.sendResult(null, .{}),
|
||||||
};
|
}
|
||||||
|
|
||||||
pub fn css(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
action: []const u8,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
const method = std.meta.stringToEnum(Methods, action) orelse
|
|
||||||
return error.UnknownMethod;
|
|
||||||
|
|
||||||
return switch (method) {
|
|
||||||
.enable => enable(alloc, msg, ctx),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "inspector.enable" });
|
|
||||||
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,43 +17,14 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const server = @import("../server.zig");
|
|
||||||
const Ctx = server.Ctx;
|
|
||||||
const cdp = @import("cdp.zig");
|
const cdp = @import("cdp.zig");
|
||||||
const result = cdp.result;
|
|
||||||
const IncomingMessage = @import("msg.zig").IncomingMessage;
|
|
||||||
const Input = @import("msg.zig").Input;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.cdp);
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
|
const action = std.meta.stringToEnum(enum {
|
||||||
|
enable,
|
||||||
|
}, cmd.action) orelse return error.UnknownMethod;
|
||||||
|
|
||||||
const Methods = enum {
|
switch (action) {
|
||||||
enable,
|
.enable => return cmd.sendResult(null, .{}),
|
||||||
};
|
}
|
||||||
|
|
||||||
pub fn dom(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
action: []const u8,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
const method = std.meta.stringToEnum(Methods, action) orelse
|
|
||||||
return error.UnknownMethod;
|
|
||||||
|
|
||||||
return switch (method) {
|
|
||||||
.enable => enable(alloc, msg, ctx),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "inspector.enable" });
|
|
||||||
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,107 +17,52 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const server = @import("../server.zig");
|
|
||||||
const Ctx = server.Ctx;
|
|
||||||
const cdp = @import("cdp.zig");
|
const cdp = @import("cdp.zig");
|
||||||
const result = cdp.result;
|
const Runtime = @import("runtime.zig");
|
||||||
const stringify = cdp.stringify;
|
|
||||||
const IncomingMessage = @import("msg.zig").IncomingMessage;
|
|
||||||
const Input = @import("msg.zig").Input;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.cdp);
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
|
const action = std.meta.stringToEnum(enum {
|
||||||
|
setEmulatedMedia,
|
||||||
|
setFocusEmulationEnabled,
|
||||||
|
setDeviceMetricsOverride,
|
||||||
|
setTouchEmulationEnabled,
|
||||||
|
}, cmd.action) orelse return error.UnknownMethod;
|
||||||
|
|
||||||
const Methods = enum {
|
switch (action) {
|
||||||
setEmulatedMedia,
|
.setEmulatedMedia => return setEmulatedMedia(cmd),
|
||||||
setFocusEmulationEnabled,
|
.setFocusEmulationEnabled => return setFocusEmulationEnabled(cmd),
|
||||||
setDeviceMetricsOverride,
|
.setDeviceMetricsOverride => return setDeviceMetricsOverride(cmd),
|
||||||
setTouchEmulationEnabled,
|
.setTouchEmulationEnabled => return setTouchEmulationEnabled(cmd),
|
||||||
};
|
}
|
||||||
|
|
||||||
pub fn emulation(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
action: []const u8,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
const method = std.meta.stringToEnum(Methods, action) orelse
|
|
||||||
return error.UnknownMethod;
|
|
||||||
return switch (method) {
|
|
||||||
.setEmulatedMedia => setEmulatedMedia(alloc, msg, ctx),
|
|
||||||
.setFocusEmulationEnabled => setFocusEmulationEnabled(alloc, msg, ctx),
|
|
||||||
.setDeviceMetricsOverride => setDeviceMetricsOverride(alloc, msg, ctx),
|
|
||||||
.setTouchEmulationEnabled => setTouchEmulationEnabled(alloc, msg, ctx),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const MediaFeature = struct {
|
|
||||||
name: []const u8,
|
|
||||||
value: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: noop method
|
|
||||||
fn setEmulatedMedia(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
|
|
||||||
// input
|
|
||||||
const Params = struct {
|
|
||||||
media: ?[]const u8 = null,
|
|
||||||
features: ?[]MediaFeature = null,
|
|
||||||
};
|
|
||||||
const input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "emulation.setEmulatedMedia" });
|
|
||||||
|
|
||||||
// output
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: noop method
|
// TODO: noop method
|
||||||
fn setFocusEmulationEnabled(
|
fn setEmulatedMedia(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
// const input = (try const incoming.params(struct {
|
||||||
msg: *IncomingMessage,
|
// media: ?[]const u8 = null,
|
||||||
_: *Ctx,
|
// features: ?[]struct{
|
||||||
) ![]const u8 {
|
// name: []const u8,
|
||||||
// input
|
// value: [] const u8
|
||||||
const Params = struct {
|
// } = null,
|
||||||
enabled: bool,
|
// })) orelse return error.InvalidParams;
|
||||||
};
|
|
||||||
const input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "emulation.setFocusEmulationEnabled" });
|
|
||||||
|
|
||||||
// output
|
return cmd.sendResult(null, .{});
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: noop method
|
// TODO: noop method
|
||||||
fn setDeviceMetricsOverride(
|
fn setFocusEmulationEnabled(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
// const input = (try const incoming.params(struct {
|
||||||
msg: *IncomingMessage,
|
// enabled: bool,
|
||||||
_: *Ctx,
|
// })) orelse return error.InvalidParams;
|
||||||
) ![]const u8 {
|
return cmd.sendResult(null, .{});
|
||||||
// input
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "emulation.setDeviceMetricsOverride" });
|
|
||||||
|
|
||||||
// output
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: noop method
|
// TODO: noop method
|
||||||
fn setTouchEmulationEnabled(
|
fn setDeviceMetricsOverride(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
return cmd.sendResult(null, .{});
|
||||||
msg: *IncomingMessage,
|
}
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
// TODO: noop method
|
||||||
const input = try Input(void).get(alloc, msg);
|
fn setTouchEmulationEnabled(cmd: anytype) !void {
|
||||||
defer input.deinit();
|
return cmd.sendResult(null, .{});
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "emulation.setTouchEmulationEnabled" });
|
|
||||||
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,43 +17,14 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const server = @import("../server.zig");
|
|
||||||
const Ctx = server.Ctx;
|
|
||||||
const cdp = @import("cdp.zig");
|
const cdp = @import("cdp.zig");
|
||||||
const result = cdp.result;
|
|
||||||
const IncomingMessage = @import("msg.zig").IncomingMessage;
|
|
||||||
const Input = @import("msg.zig").Input;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.cdp);
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
|
const action = std.meta.stringToEnum(enum {
|
||||||
|
disable,
|
||||||
|
}, cmd.action) orelse return error.UnknownMethod;
|
||||||
|
|
||||||
const Methods = enum {
|
switch (action) {
|
||||||
disable,
|
.disable => return cmd.sendResult(null, .{}),
|
||||||
};
|
}
|
||||||
|
|
||||||
pub fn fetch(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
action: []const u8,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
const method = std.meta.stringToEnum(Methods, action) orelse
|
|
||||||
return error.UnknownMethod;
|
|
||||||
|
|
||||||
return switch (method) {
|
|
||||||
.disable => disable(alloc, msg, ctx),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: noop method
|
|
||||||
fn disable(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "fetch.disable" });
|
|
||||||
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,43 +17,14 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const server = @import("../server.zig");
|
|
||||||
const Ctx = server.Ctx;
|
|
||||||
const cdp = @import("cdp.zig");
|
const cdp = @import("cdp.zig");
|
||||||
const result = cdp.result;
|
|
||||||
const IncomingMessage = @import("msg.zig").IncomingMessage;
|
|
||||||
const Input = @import("msg.zig").Input;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.cdp);
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
|
const action = std.meta.stringToEnum(enum {
|
||||||
|
enable,
|
||||||
|
}, cmd.action) orelse return error.UnknownMethod;
|
||||||
|
|
||||||
const Methods = enum {
|
switch (action) {
|
||||||
enable,
|
.enable => return cmd.sendResult(null, .{}),
|
||||||
};
|
}
|
||||||
|
|
||||||
pub fn inspector(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
action: []const u8,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
const method = std.meta.stringToEnum(Methods, action) orelse
|
|
||||||
return error.UnknownMethod;
|
|
||||||
|
|
||||||
return switch (method) {
|
|
||||||
.enable => enable(alloc, msg, ctx),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "inspector.enable" });
|
|
||||||
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,43 +17,14 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const server = @import("../server.zig");
|
|
||||||
const Ctx = server.Ctx;
|
|
||||||
const cdp = @import("cdp.zig");
|
const cdp = @import("cdp.zig");
|
||||||
const result = cdp.result;
|
|
||||||
const IncomingMessage = @import("msg.zig").IncomingMessage;
|
|
||||||
const Input = @import("msg.zig").Input;
|
|
||||||
const stringify = cdp.stringify;
|
|
||||||
|
|
||||||
const log_cdp = std.log.scoped(.cdp);
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
|
const action = std.meta.stringToEnum(enum {
|
||||||
|
enable,
|
||||||
|
}, cmd.action) orelse return error.UnknownMethod;
|
||||||
|
|
||||||
const Methods = enum {
|
switch (action) {
|
||||||
enable,
|
.enable => return cmd.sendResult(null, .{}),
|
||||||
};
|
}
|
||||||
|
|
||||||
pub fn log(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
action: []const u8,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
const method = std.meta.stringToEnum(Methods, action) orelse
|
|
||||||
return error.UnknownMethod;
|
|
||||||
|
|
||||||
return switch (method) {
|
|
||||||
.enable => enable(alloc, msg, ctx),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log_cdp.debug("Req > id {d}, method {s}", .{ input.id, "log.enable" });
|
|
||||||
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
293
src/cdp/msg.zig
293
src/cdp/msg.zig
@@ -1,293 +0,0 @@
|
|||||||
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
|
|
||||||
//
|
|
||||||
// Francis Bouvier <francis@lightpanda.io>
|
|
||||||
// Pierre Tachoire <pierre@lightpanda.io>
|
|
||||||
//
|
|
||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
// Parse incoming protocol message in json format.
|
|
||||||
pub const IncomingMessage = struct {
|
|
||||||
scanner: std.json.Scanner,
|
|
||||||
json: []const u8,
|
|
||||||
|
|
||||||
obj_begin: bool = false,
|
|
||||||
obj_end: bool = false,
|
|
||||||
|
|
||||||
id: ?u16 = null,
|
|
||||||
scan_sessionId: bool = false,
|
|
||||||
sessionId: ?[]const u8 = null,
|
|
||||||
method: ?[]const u8 = null,
|
|
||||||
params_skip: bool = false,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, json: []const u8) IncomingMessage {
|
|
||||||
return .{
|
|
||||||
.json = json,
|
|
||||||
.scanner = std.json.Scanner.initCompleteInput(alloc, json),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *IncomingMessage) void {
|
|
||||||
self.scanner.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scanUntil(self: *IncomingMessage, key: []const u8) !void {
|
|
||||||
while (true) {
|
|
||||||
switch (try self.scanner.next()) {
|
|
||||||
.end_of_document => return error.EndOfDocument,
|
|
||||||
.object_begin => {
|
|
||||||
if (self.obj_begin) return error.InvalidObjectBegin;
|
|
||||||
self.obj_begin = true;
|
|
||||||
},
|
|
||||||
.object_end => {
|
|
||||||
if (!self.obj_begin) return error.InvalidObjectEnd;
|
|
||||||
if (self.obj_end) return error.InvalidObjectEnd;
|
|
||||||
self.obj_end = true;
|
|
||||||
},
|
|
||||||
.string => |s| {
|
|
||||||
// is the key what we expects?
|
|
||||||
if (std.mem.eql(u8, s, key)) return;
|
|
||||||
|
|
||||||
// save other known keys
|
|
||||||
if (std.mem.eql(u8, s, "id")) try self.scanId();
|
|
||||||
if (std.mem.eql(u8, s, "sessionId")) try self.scanSessionId();
|
|
||||||
if (std.mem.eql(u8, s, "method")) try self.scanMethod();
|
|
||||||
if (std.mem.eql(u8, s, "params")) try self.scanParams();
|
|
||||||
|
|
||||||
// TODO should we skip unknown key?
|
|
||||||
},
|
|
||||||
else => return error.InvalidToken,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scanId(self: *IncomingMessage) !void {
|
|
||||||
const t = try self.scanner.next();
|
|
||||||
if (t != .number) return error.InvalidId;
|
|
||||||
self.id = try std.fmt.parseUnsigned(u16, t.number, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getId(self: *IncomingMessage) !u16 {
|
|
||||||
if (self.id != null) return self.id.?;
|
|
||||||
|
|
||||||
try self.scanUntil("id");
|
|
||||||
try self.scanId();
|
|
||||||
return self.id.?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scanSessionId(self: *IncomingMessage) !void {
|
|
||||||
switch (try self.scanner.next()) {
|
|
||||||
// session id can be null.
|
|
||||||
.null => return,
|
|
||||||
.string => |s| self.sessionId = s,
|
|
||||||
else => return error.InvalidSessionId,
|
|
||||||
}
|
|
||||||
|
|
||||||
self.scan_sessionId = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getSessionId(self: *IncomingMessage) !?[]const u8 {
|
|
||||||
if (self.scan_sessionId) return self.sessionId;
|
|
||||||
|
|
||||||
self.scanUntil("sessionId") catch |err| {
|
|
||||||
if (err != error.EndOfDocument) return err;
|
|
||||||
// if the document doesn't contains any session id key, we must
|
|
||||||
// return null value.
|
|
||||||
self.scan_sessionId = true;
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
try self.scanSessionId();
|
|
||||||
return self.sessionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scanMethod(self: *IncomingMessage) !void {
|
|
||||||
const t = try self.scanner.next();
|
|
||||||
if (t != .string) return error.InvalidMethod;
|
|
||||||
self.method = t.string;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getMethod(self: *IncomingMessage) ![]const u8 {
|
|
||||||
if (self.method != null) return self.method.?;
|
|
||||||
|
|
||||||
try self.scanUntil("method");
|
|
||||||
try self.scanMethod();
|
|
||||||
return self.method.?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanParams skip found parameters b/c if we encounter params *before*
|
|
||||||
// asking for getParams, we don't know how to parse them.
|
|
||||||
fn scanParams(self: *IncomingMessage) !void {
|
|
||||||
const tt = try self.scanner.peekNextTokenType();
|
|
||||||
// accept object begin or null JSON value.
|
|
||||||
if (tt != .object_begin and tt != .null) return error.InvalidParams;
|
|
||||||
try self.scanner.skipValue();
|
|
||||||
self.params_skip = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// getParams restart the JSON parsing
|
|
||||||
fn getParams(self: *IncomingMessage, alloc: ?std.mem.Allocator, T: type) !T {
|
|
||||||
if (T == void) return void{};
|
|
||||||
std.debug.assert(alloc != null); // if T is not void, alloc should not be null
|
|
||||||
|
|
||||||
if (self.params_skip) {
|
|
||||||
// TODO if the params have been skipped, we have to retart the
|
|
||||||
// parsing from start.
|
|
||||||
return error.SkippedParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.scanUntil("params") catch |err| {
|
|
||||||
// handle nullable type
|
|
||||||
if (@typeInfo(T) == .Optional) {
|
|
||||||
if (err == error.InvalidToken or err == error.EndOfDocument) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
|
|
||||||
// parse "params"
|
|
||||||
const options = std.json.ParseOptions{
|
|
||||||
.ignore_unknown_fields = true,
|
|
||||||
.max_value_len = self.scanner.input.len,
|
|
||||||
.allocate = .alloc_always,
|
|
||||||
};
|
|
||||||
return try std.json.innerParse(T, alloc.?, &self.scanner, options);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn Input(T: type) type {
|
|
||||||
return struct {
|
|
||||||
arena: ?*std.heap.ArenaAllocator = null,
|
|
||||||
id: u16,
|
|
||||||
params: T,
|
|
||||||
sessionId: ?[]const u8,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn get(alloc: std.mem.Allocator, msg: *IncomingMessage) !Self {
|
|
||||||
var arena: ?*std.heap.ArenaAllocator = null;
|
|
||||||
var allocator: ?std.mem.Allocator = null;
|
|
||||||
|
|
||||||
if (T != void) {
|
|
||||||
arena = try alloc.create(std.heap.ArenaAllocator);
|
|
||||||
arena.?.* = std.heap.ArenaAllocator.init(alloc);
|
|
||||||
allocator = arena.?.allocator();
|
|
||||||
}
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
if (arena) |_arena| {
|
|
||||||
_arena.deinit();
|
|
||||||
alloc.destroy(_arena);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.arena = arena,
|
|
||||||
.params = try msg.getParams(allocator, T),
|
|
||||||
.id = try msg.getId(),
|
|
||||||
.sessionId = try msg.getSessionId(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: Self) void {
|
|
||||||
if (self.arena) |arena| {
|
|
||||||
const allocator = arena.child_allocator;
|
|
||||||
arena.deinit();
|
|
||||||
allocator.destroy(arena);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test "read incoming message" {
|
|
||||||
const inputs = [_][]const u8{
|
|
||||||
\\{"id":1,"method":"foo","sessionId":"bar","params":{"bar":"baz"}}
|
|
||||||
,
|
|
||||||
\\{"params":{"bar":"baz"},"id":1,"method":"foo","sessionId":"bar"}
|
|
||||||
,
|
|
||||||
\\{"sessionId":"bar","params":{"bar":"baz"},"id":1,"method":"foo"}
|
|
||||||
,
|
|
||||||
\\{"method":"foo","sessionId":"bar","params":{"bar":"baz"},"id":1}
|
|
||||||
,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (inputs) |input| {
|
|
||||||
var msg = IncomingMessage.init(std.testing.allocator, input);
|
|
||||||
defer msg.deinit();
|
|
||||||
|
|
||||||
try std.testing.expectEqual(1, try msg.getId());
|
|
||||||
try std.testing.expectEqualSlices(u8, "foo", try msg.getMethod());
|
|
||||||
try std.testing.expectEqualSlices(u8, "bar", (try msg.getSessionId()).?);
|
|
||||||
|
|
||||||
const T = struct { bar: []const u8 };
|
|
||||||
const in = Input(T).get(std.testing.allocator, &msg) catch |err| {
|
|
||||||
if (err != error.SkippedParams) return err;
|
|
||||||
// TODO remove this check when params in the beginning is handled.
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
defer in.deinit();
|
|
||||||
try std.testing.expectEqualSlices(u8, "baz", in.params.bar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "read incoming message with null session id" {
|
|
||||||
const inputs = [_][]const u8{
|
|
||||||
\\{"id":1}
|
|
||||||
,
|
|
||||||
\\{"params":{"bar":"baz"},"id":1,"method":"foo"}
|
|
||||||
,
|
|
||||||
\\{"sessionId":null,"params":{"bar":"baz"},"id":1,"method":"foo"}
|
|
||||||
,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (inputs) |input| {
|
|
||||||
var msg = IncomingMessage.init(std.testing.allocator, input);
|
|
||||||
defer msg.deinit();
|
|
||||||
|
|
||||||
try std.testing.expect(try msg.getSessionId() == null);
|
|
||||||
try std.testing.expectEqual(1, try msg.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "message with nullable params" {
|
|
||||||
const T = struct {
|
|
||||||
bar: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
// nullable type, params is present => value
|
|
||||||
const not_null =
|
|
||||||
\\{"id": 1,"method":"foo","params":{"bar":"baz"}}
|
|
||||||
;
|
|
||||||
var msg = IncomingMessage.init(std.testing.allocator, not_null);
|
|
||||||
defer msg.deinit();
|
|
||||||
const input = try Input(?T).get(std.testing.allocator, &msg);
|
|
||||||
defer input.deinit();
|
|
||||||
try std.testing.expectEqualStrings(input.params.?.bar, "baz");
|
|
||||||
|
|
||||||
// nullable type, params is not present => null
|
|
||||||
const is_null =
|
|
||||||
\\{"id": 1,"method":"foo","sessionId":"AAA"}
|
|
||||||
;
|
|
||||||
var msg_null = IncomingMessage.init(std.testing.allocator, is_null);
|
|
||||||
defer msg_null.deinit();
|
|
||||||
const input_null = try Input(?T).get(std.testing.allocator, &msg_null);
|
|
||||||
defer input_null.deinit();
|
|
||||||
try std.testing.expectEqual(null, input_null.params);
|
|
||||||
try std.testing.expectEqualStrings("AAA", input_null.sessionId.?);
|
|
||||||
|
|
||||||
// not nullable type, params is not present => error
|
|
||||||
const params_or_error = msg_null.getParams(std.testing.allocator, T);
|
|
||||||
try std.testing.expectError(error.EndOfDocument, params_or_error);
|
|
||||||
}
|
|
||||||
@@ -17,59 +17,16 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const server = @import("../server.zig");
|
|
||||||
const Ctx = server.Ctx;
|
|
||||||
const cdp = @import("cdp.zig");
|
const cdp = @import("cdp.zig");
|
||||||
const result = cdp.result;
|
|
||||||
const IncomingMessage = @import("msg.zig").IncomingMessage;
|
|
||||||
const Input = @import("msg.zig").Input;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.cdp);
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
|
const action = std.meta.stringToEnum(enum {
|
||||||
|
enable,
|
||||||
|
setCacheDisabled,
|
||||||
|
}, cmd.action) orelse return error.UnknownMethod;
|
||||||
|
|
||||||
const Methods = enum {
|
switch (action) {
|
||||||
enable,
|
.enable => return cmd.sendResult(null, .{}),
|
||||||
setCacheDisabled,
|
.setCacheDisabled => return cmd.sendResult(null, .{}),
|
||||||
};
|
}
|
||||||
|
|
||||||
pub fn network(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
action: []const u8,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
const method = std.meta.stringToEnum(Methods, action) orelse
|
|
||||||
return error.UnknownMethod;
|
|
||||||
|
|
||||||
return switch (method) {
|
|
||||||
.enable => enable(alloc, msg, ctx),
|
|
||||||
.setCacheDisabled => setCacheDisabled(alloc, msg, ctx),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "network.enable" });
|
|
||||||
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: noop method
|
|
||||||
fn setCacheDisabled(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "network.setCacheDisabled" });
|
|
||||||
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
396
src/cdp/page.zig
396
src/cdp/page.zig
@@ -17,58 +17,27 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const server = @import("../server.zig");
|
|
||||||
const Ctx = server.Ctx;
|
|
||||||
const cdp = @import("cdp.zig");
|
const cdp = @import("cdp.zig");
|
||||||
const result = cdp.result;
|
const runtime = @import("runtime.zig");
|
||||||
const stringify = cdp.stringify;
|
|
||||||
const sendEvent = cdp.sendEvent;
|
|
||||||
const IncomingMessage = @import("msg.zig").IncomingMessage;
|
|
||||||
const Input = @import("msg.zig").Input;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.cdp);
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
|
const action = std.meta.stringToEnum(enum {
|
||||||
|
enable,
|
||||||
|
getFrameTree,
|
||||||
|
setLifecycleEventsEnabled,
|
||||||
|
addScriptToEvaluateOnNewDocument,
|
||||||
|
createIsolatedWorld,
|
||||||
|
navigate,
|
||||||
|
}, cmd.action) orelse return error.UnknownMethod;
|
||||||
|
|
||||||
const Runtime = @import("runtime.zig");
|
switch (action) {
|
||||||
|
.enable => return cmd.sendResult(null, .{}),
|
||||||
const Methods = enum {
|
.getFrameTree => return getFrameTree(cmd),
|
||||||
enable,
|
.setLifecycleEventsEnabled => return setLifecycleEventsEnabled(cmd),
|
||||||
getFrameTree,
|
.addScriptToEvaluateOnNewDocument => return addScriptToEvaluateOnNewDocument(cmd),
|
||||||
setLifecycleEventsEnabled,
|
.createIsolatedWorld => return createIsolatedWorld(cmd),
|
||||||
addScriptToEvaluateOnNewDocument,
|
.navigate => return navigate(cmd),
|
||||||
createIsolatedWorld,
|
}
|
||||||
navigate,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn page(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
action: []const u8,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
const method = std.meta.stringToEnum(Methods, action) orelse
|
|
||||||
return error.UnknownMethod;
|
|
||||||
return switch (method) {
|
|
||||||
.enable => enable(alloc, msg, ctx),
|
|
||||||
.getFrameTree => getFrameTree(alloc, msg, ctx),
|
|
||||||
.setLifecycleEventsEnabled => setLifecycleEventsEnabled(alloc, msg, ctx),
|
|
||||||
.addScriptToEvaluateOnNewDocument => addScriptToEvaluateOnNewDocument(alloc, msg, ctx),
|
|
||||||
.createIsolatedWorld => createIsolatedWorld(alloc, msg, ctx),
|
|
||||||
.navigate => navigate(alloc, msg, ctx),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "page.enable" });
|
|
||||||
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Frame = struct {
|
const Frame = struct {
|
||||||
@@ -86,16 +55,7 @@ const Frame = struct {
|
|||||||
gatedAPIFeatures: [][]const u8 = &[0][]const u8{},
|
gatedAPIFeatures: [][]const u8 = &[0][]const u8{},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn getFrameTree(
|
fn getFrameTree(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "page.getFrameTree" });
|
|
||||||
|
|
||||||
// output
|
// output
|
||||||
const FrameTree = struct {
|
const FrameTree = struct {
|
||||||
frameTree: struct {
|
frameTree: struct {
|
||||||
@@ -112,6 +72,7 @@ fn getFrameTree(
|
|||||||
try writer.writeAll("cdp.page.getFrameTree { ");
|
try writer.writeAll("cdp.page.getFrameTree { ");
|
||||||
try writer.writeAll(".frameTree = { ");
|
try writer.writeAll(".frameTree = { ");
|
||||||
try writer.writeAll(".frame = { ");
|
try writer.writeAll(".frame = { ");
|
||||||
|
|
||||||
const frame = self.frameTree.frame;
|
const frame = self.frameTree.frame;
|
||||||
try writer.writeAll(".id = ");
|
try writer.writeAll(".id = ");
|
||||||
try std.fmt.formatText(frame.id, "s", options, writer);
|
try std.fmt.formatText(frame.id, "s", options, writer);
|
||||||
@@ -122,65 +83,40 @@ fn getFrameTree(
|
|||||||
try writer.writeAll(" } } }");
|
try writer.writeAll(" } } }");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const frameTree = FrameTree{
|
|
||||||
|
const state = cmd.cdp;
|
||||||
|
return cmd.sendResult(FrameTree{
|
||||||
.frameTree = .{
|
.frameTree = .{
|
||||||
.frame = .{
|
.frame = .{
|
||||||
.id = ctx.state.frameID,
|
.id = state.frame_id,
|
||||||
.url = ctx.state.url,
|
.url = state.url,
|
||||||
.securityOrigin = ctx.state.securityOrigin,
|
.securityOrigin = state.security_origin,
|
||||||
.secureContextType = ctx.state.secureContextType,
|
.secureContextType = state.secure_context_type,
|
||||||
.loaderId = ctx.state.loaderID,
|
.loaderId = state.loader_id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}, .{});
|
||||||
return result(alloc, input.id, FrameTree, frameTree, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setLifecycleEventsEnabled(
|
fn setLifecycleEventsEnabled(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
// const params = (try cmd.params(struct {
|
||||||
msg: *IncomingMessage,
|
// enabled: bool,
|
||||||
ctx: *Ctx,
|
// })) orelse return error.InvalidParams;
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const Params = struct {
|
|
||||||
enabled: bool,
|
|
||||||
};
|
|
||||||
const input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "page.setLifecycleEventsEnabled" });
|
|
||||||
|
|
||||||
ctx.state.page_life_cycle_events = true;
|
cmd.cdp.page_life_cycle_events = true;
|
||||||
|
return cmd.sendResult(null, .{});
|
||||||
// output
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const LifecycleEvent = struct {
|
|
||||||
frameId: []const u8,
|
|
||||||
loaderId: ?[]const u8,
|
|
||||||
name: []const u8 = undefined,
|
|
||||||
timestamp: f32 = undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: hard coded method
|
// TODO: hard coded method
|
||||||
fn addScriptToEvaluateOnNewDocument(
|
fn addScriptToEvaluateOnNewDocument(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
// const params = (try cmd.params(struct {
|
||||||
msg: *IncomingMessage,
|
// source: []const u8,
|
||||||
_: *Ctx,
|
// worldName: ?[]const u8 = null,
|
||||||
) ![]const u8 {
|
// includeCommandLineAPI: bool = false,
|
||||||
// input
|
// runImmediately: bool = false,
|
||||||
const Params = struct {
|
// })) orelse return error.InvalidParams;
|
||||||
source: []const u8,
|
|
||||||
worldName: ?[]const u8 = null,
|
|
||||||
includeCommandLineAPI: bool = false,
|
|
||||||
runImmediately: bool = false,
|
|
||||||
};
|
|
||||||
const input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "page.addScriptToEvaluateOnNewDocument" });
|
|
||||||
|
|
||||||
// output
|
const Response = struct {
|
||||||
const Res = struct {
|
|
||||||
identifier: []const u8 = "1",
|
identifier: []const u8 = "1",
|
||||||
|
|
||||||
pub fn format(
|
pub fn format(
|
||||||
@@ -195,109 +131,84 @@ fn addScriptToEvaluateOnNewDocument(
|
|||||||
try writer.writeAll(" }");
|
try writer.writeAll(" }");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return result(alloc, input.id, Res, Res{}, input.sessionId);
|
return cmd.sendResult(Response{}, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: hard coded method
|
// TODO: hard coded method
|
||||||
fn createIsolatedWorld(
|
fn createIsolatedWorld(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
const session_id = cmd.session_id orelse return error.SessionIdRequired;
|
||||||
msg: *IncomingMessage,
|
|
||||||
ctx: *Ctx,
|
const params = (try cmd.params(struct {
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const Params = struct {
|
|
||||||
frameId: []const u8,
|
frameId: []const u8,
|
||||||
worldName: []const u8,
|
worldName: []const u8,
|
||||||
grantUniveralAccess: bool,
|
grantUniveralAccess: bool,
|
||||||
};
|
})) orelse return error.InvalidParams;
|
||||||
const input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
std.debug.assert(input.sessionId != null);
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "page.createIsolatedWorld" });
|
|
||||||
|
|
||||||
// noop executionContextCreated event
|
// noop executionContextCreated event
|
||||||
try Runtime.executionContextCreated(
|
try cmd.sendEvent("Runtime.executionContextCreated", .{
|
||||||
alloc,
|
.context = runtime.ExecutionContextCreated{
|
||||||
ctx,
|
.id = 0,
|
||||||
0,
|
.origin = "",
|
||||||
"",
|
.name = params.worldName,
|
||||||
input.params.worldName,
|
// TODO: hard coded ID
|
||||||
// TODO: hard coded ID
|
.uniqueId = "7102379147004877974.3265385113993241162",
|
||||||
"7102379147004877974.3265385113993241162",
|
.auxData = .{
|
||||||
.{
|
.isDefault = false,
|
||||||
.isDefault = false,
|
.type = "isolated",
|
||||||
.type = "isolated",
|
.frameId = params.frameId,
|
||||||
.frameId = input.params.frameId,
|
},
|
||||||
},
|
},
|
||||||
input.sessionId,
|
}, .{ .session_id = session_id });
|
||||||
);
|
|
||||||
|
|
||||||
// output
|
return cmd.sendResult(.{
|
||||||
const Resp = struct {
|
.executionContextId = 0,
|
||||||
executionContextId: u8 = 0,
|
}, .{});
|
||||||
};
|
|
||||||
|
|
||||||
return result(alloc, input.id, Resp, .{}, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn navigate(
|
fn navigate(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
const session_id = cmd.session_id orelse return error.SessionIdRequired;
|
||||||
msg: *IncomingMessage,
|
|
||||||
ctx: *Ctx,
|
const params = (try cmd.params(struct {
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const Params = struct {
|
|
||||||
url: []const u8,
|
url: []const u8,
|
||||||
referrer: ?[]const u8 = null,
|
referrer: ?[]const u8 = null,
|
||||||
transitionType: ?[]const u8 = null, // TODO: enum
|
transitionType: ?[]const u8 = null, // TODO: enum
|
||||||
frameId: ?[]const u8 = null,
|
frameId: ?[]const u8 = null,
|
||||||
referrerPolicy: ?[]const u8 = null, // TODO: enum
|
referrerPolicy: ?[]const u8 = null, // TODO: enum
|
||||||
};
|
})) orelse return error.InvalidParams;
|
||||||
const input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
std.debug.assert(input.sessionId != null);
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "page.navigate" });
|
|
||||||
|
|
||||||
// change state
|
// change state
|
||||||
ctx.state.url = input.params.url;
|
var state = cmd.cdp;
|
||||||
|
state.url = params.url;
|
||||||
|
|
||||||
// TODO: hard coded ID
|
// TODO: hard coded ID
|
||||||
ctx.state.loaderID = "AF8667A203C5392DBE9AC290044AA4C2";
|
state.loader_id = "AF8667A203C5392DBE9AC290044AA4C2";
|
||||||
|
|
||||||
|
const LifecycleEvent = struct {
|
||||||
|
frameId: []const u8,
|
||||||
|
loaderId: ?[]const u8,
|
||||||
|
name: []const u8,
|
||||||
|
timestamp: f32,
|
||||||
|
};
|
||||||
|
|
||||||
var life_event = LifecycleEvent{
|
var life_event = LifecycleEvent{
|
||||||
.frameId = ctx.state.frameID,
|
.frameId = state.frame_id,
|
||||||
.loaderId = ctx.state.loaderID,
|
.loaderId = state.loader_id,
|
||||||
|
.name = "init",
|
||||||
|
.timestamp = 343721.796037,
|
||||||
};
|
};
|
||||||
var ts_event: cdp.TimestampEvent = undefined;
|
|
||||||
|
|
||||||
// frameStartedLoading event
|
// frameStartedLoading event
|
||||||
// TODO: event partially hard coded
|
// TODO: event partially hard coded
|
||||||
const FrameStartedLoading = struct {
|
try cmd.sendEvent("Page.frameStartedLoading", .{
|
||||||
frameId: []const u8,
|
.frameId = state.frame_id,
|
||||||
};
|
}, .{ .session_id = session_id });
|
||||||
const frame_started_loading = FrameStartedLoading{ .frameId = ctx.state.frameID };
|
|
||||||
try sendEvent(
|
if (state.page_life_cycle_events) {
|
||||||
alloc,
|
try cmd.sendEvent("Page.lifecycleEvent", life_event, .{ .session_id = session_id });
|
||||||
ctx,
|
|
||||||
"Page.frameStartedLoading",
|
|
||||||
FrameStartedLoading,
|
|
||||||
frame_started_loading,
|
|
||||||
input.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,
|
|
||||||
input.sessionId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// output
|
// output
|
||||||
const Resp = struct {
|
const Response = struct {
|
||||||
frameId: []const u8,
|
frameId: []const u8,
|
||||||
loaderId: ?[]const u8,
|
loaderId: ?[]const u8,
|
||||||
errorText: ?[]const u8 = null,
|
errorText: ?[]const u8 = null,
|
||||||
@@ -318,136 +229,87 @@ fn navigate(
|
|||||||
try writer.writeAll(" }");
|
try writer.writeAll(" }");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const resp = Resp{
|
|
||||||
.frameId = ctx.state.frameID,
|
try cmd.sendResult(Response{
|
||||||
.loaderId = ctx.state.loaderID,
|
.frameId = state.frame_id,
|
||||||
};
|
.loaderId = state.loader_id,
|
||||||
const res = try result(alloc, input.id, Resp, resp, input.sessionId);
|
}, .{});
|
||||||
try ctx.send(res);
|
|
||||||
|
|
||||||
// TODO: at this point do we need async the following actions to be async?
|
// TODO: at this point do we need async the following actions to be async?
|
||||||
|
|
||||||
// Send Runtime.executionContextsCleared event
|
// Send Runtime.executionContextsCleared event
|
||||||
// TODO: noop event, we have no env context at this point, is it necesarry?
|
// TODO: noop event, we have no env context at this point, is it necesarry?
|
||||||
try sendEvent(alloc, ctx, "Runtime.executionContextsCleared", struct {}, .{}, input.sessionId);
|
try cmd.sendEvent("Runtime.executionContextsCleared", null, .{ .session_id = session_id });
|
||||||
|
|
||||||
// Launch navigate, the page must have been created by a
|
// Launch navigate, the page must have been created by a
|
||||||
// target.createTarget.
|
// target.createTarget.
|
||||||
var p = ctx.browser.session.page orelse return error.NoPage;
|
var p = cmd.session.page orelse return error.NoPage;
|
||||||
ctx.state.executionContextId += 1;
|
state.execution_context_id += 1;
|
||||||
const auxData = try std.fmt.allocPrint(
|
|
||||||
alloc,
|
const aux_data = try std.fmt.allocPrint(
|
||||||
|
cmd.arena,
|
||||||
// NOTE: we assume this is the default web page
|
// NOTE: we assume this is the default web page
|
||||||
"{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}",
|
"{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}",
|
||||||
.{ctx.state.frameID},
|
.{state.frame_id},
|
||||||
);
|
);
|
||||||
defer alloc.free(auxData);
|
try p.navigate(params.url, aux_data);
|
||||||
try p.navigate(input.params.url, auxData);
|
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
|
|
||||||
// lifecycle init event
|
// lifecycle init event
|
||||||
// TODO: partially hard coded
|
// TODO: partially hard coded
|
||||||
if (ctx.state.page_life_cycle_events) {
|
if (state.page_life_cycle_events) {
|
||||||
life_event.name = "init";
|
life_event.name = "init";
|
||||||
life_event.timestamp = 343721.796037;
|
life_event.timestamp = 343721.796037;
|
||||||
try sendEvent(
|
try cmd.sendEvent("Page.lifecycleEvent", life_event, .{ .session_id = session_id });
|
||||||
alloc,
|
|
||||||
ctx,
|
|
||||||
"Page.lifecycleEvent",
|
|
||||||
LifecycleEvent,
|
|
||||||
life_event,
|
|
||||||
input.sessionId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// frameNavigated event
|
// frameNavigated event
|
||||||
const FrameNavigated = struct {
|
try cmd.sendEvent("Page.frameNavigated", .{
|
||||||
frame: Frame,
|
.type = "Navigation",
|
||||||
type: []const u8 = "Navigation",
|
.frame = Frame{
|
||||||
};
|
.id = state.frame_id,
|
||||||
const frame_navigated = FrameNavigated{
|
.url = state.url,
|
||||||
.frame = .{
|
.securityOrigin = state.security_origin,
|
||||||
.id = ctx.state.frameID,
|
.secureContextType = state.secure_context_type,
|
||||||
.url = ctx.state.url,
|
.loaderId = state.loader_id,
|
||||||
.securityOrigin = ctx.state.securityOrigin,
|
|
||||||
.secureContextType = ctx.state.secureContextType,
|
|
||||||
.loaderId = ctx.state.loaderID,
|
|
||||||
},
|
},
|
||||||
};
|
}, .{ .session_id = session_id });
|
||||||
try sendEvent(
|
|
||||||
alloc,
|
|
||||||
ctx,
|
|
||||||
"Page.frameNavigated",
|
|
||||||
FrameNavigated,
|
|
||||||
frame_navigated,
|
|
||||||
input.sessionId,
|
|
||||||
);
|
|
||||||
|
|
||||||
// domContentEventFired event
|
// domContentEventFired event
|
||||||
// TODO: partially hard coded
|
// TODO: partially hard coded
|
||||||
ts_event = .{ .timestamp = 343721.803338 };
|
try cmd.sendEvent(
|
||||||
try sendEvent(
|
|
||||||
alloc,
|
|
||||||
ctx,
|
|
||||||
"Page.domContentEventFired",
|
"Page.domContentEventFired",
|
||||||
cdp.TimestampEvent,
|
cdp.TimestampEvent{ .timestamp = 343721.803338 },
|
||||||
ts_event,
|
.{ .session_id = session_id },
|
||||||
input.sessionId,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// lifecycle DOMContentLoaded event
|
// lifecycle DOMContentLoaded event
|
||||||
// TODO: partially hard coded
|
// TODO: partially hard coded
|
||||||
if (ctx.state.page_life_cycle_events) {
|
if (state.page_life_cycle_events) {
|
||||||
life_event.name = "DOMContentLoaded";
|
life_event.name = "DOMContentLoaded";
|
||||||
life_event.timestamp = 343721.803338;
|
life_event.timestamp = 343721.803338;
|
||||||
try sendEvent(
|
try cmd.sendEvent("Page.lifecycleEvent", life_event, .{ .session_id = session_id });
|
||||||
alloc,
|
|
||||||
ctx,
|
|
||||||
"Page.lifecycleEvent",
|
|
||||||
LifecycleEvent,
|
|
||||||
life_event,
|
|
||||||
input.sessionId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadEventFired event
|
// loadEventFired event
|
||||||
// TODO: partially hard coded
|
// TODO: partially hard coded
|
||||||
ts_event = .{ .timestamp = 343721.824655 };
|
try cmd.sendEvent(
|
||||||
try sendEvent(
|
|
||||||
alloc,
|
|
||||||
ctx,
|
|
||||||
"Page.loadEventFired",
|
"Page.loadEventFired",
|
||||||
cdp.TimestampEvent,
|
cdp.TimestampEvent{ .timestamp = 343721.824655 },
|
||||||
ts_event,
|
.{ .session_id = session_id },
|
||||||
input.sessionId,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// lifecycle DOMContentLoaded event
|
// lifecycle DOMContentLoaded event
|
||||||
// TODO: partially hard coded
|
// TODO: partially hard coded
|
||||||
if (ctx.state.page_life_cycle_events) {
|
if (state.page_life_cycle_events) {
|
||||||
life_event.name = "load";
|
life_event.name = "load";
|
||||||
life_event.timestamp = 343721.824655;
|
life_event.timestamp = 343721.824655;
|
||||||
try sendEvent(
|
try cmd.sendEvent("Page.lifecycleEvent", life_event, .{ .session_id = session_id });
|
||||||
alloc,
|
|
||||||
ctx,
|
|
||||||
"Page.lifecycleEvent",
|
|
||||||
LifecycleEvent,
|
|
||||||
life_event,
|
|
||||||
input.sessionId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// frameStoppedLoading
|
// frameStoppedLoading
|
||||||
const FrameStoppedLoading = struct { frameId: []const u8 };
|
return cmd.sendEvent("Page.frameStoppedLoading", .{
|
||||||
try sendEvent(
|
.frameId = state.frame_id,
|
||||||
alloc,
|
}, .{ .session_id = session_id });
|
||||||
ctx,
|
|
||||||
"Page.frameStoppedLoading",
|
|
||||||
FrameStoppedLoading,
|
|
||||||
.{ .frameId = ctx.state.frameID },
|
|
||||||
input.sessionId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,43 +17,15 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const server = @import("../server.zig");
|
|
||||||
const Ctx = server.Ctx;
|
|
||||||
const cdp = @import("cdp.zig");
|
const cdp = @import("cdp.zig");
|
||||||
const result = cdp.result;
|
const asUint = @import("../str/parser.zig").asUint;
|
||||||
const IncomingMessage = @import("msg.zig").IncomingMessage;
|
|
||||||
const Input = @import("msg.zig").Input;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.cdp);
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
|
const action = std.meta.stringToEnum(enum {
|
||||||
|
enable,
|
||||||
|
}, cmd.action) orelse return error.UnknownMethod;
|
||||||
|
|
||||||
const Methods = enum {
|
switch (action) {
|
||||||
enable,
|
.enable => return cmd.sendResult(null, .{}),
|
||||||
};
|
}
|
||||||
|
|
||||||
pub fn performance(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
action: []const u8,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
const method = std.meta.stringToEnum(Methods, action) orelse
|
|
||||||
return error.UnknownMethod;
|
|
||||||
|
|
||||||
return switch (method) {
|
|
||||||
.enable => enable(alloc, msg, ctx),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "performance.enable" });
|
|
||||||
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,179 +17,106 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
|
||||||
|
|
||||||
const server = @import("../server.zig");
|
|
||||||
const Ctx = server.Ctx;
|
|
||||||
const cdp = @import("cdp.zig");
|
const cdp = @import("cdp.zig");
|
||||||
const result = cdp.result;
|
|
||||||
const IncomingMessage = @import("msg.zig").IncomingMessage;
|
|
||||||
const Input = @import("msg.zig").Input;
|
|
||||||
const stringify = cdp.stringify;
|
|
||||||
const target = @import("target.zig");
|
|
||||||
|
|
||||||
const log = std.log.scoped(.cdp);
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
|
const action = std.meta.stringToEnum(enum {
|
||||||
|
enable,
|
||||||
|
runIfWaitingForDebugger,
|
||||||
|
evaluate,
|
||||||
|
addBinding,
|
||||||
|
callFunctionOn,
|
||||||
|
releaseObject,
|
||||||
|
}, cmd.action) orelse return error.UnknownMethod;
|
||||||
|
|
||||||
const Methods = enum {
|
switch (action) {
|
||||||
enable,
|
.runIfWaitingForDebugger => return cmd.sendResult(null, .{}),
|
||||||
runIfWaitingForDebugger,
|
else => return sendInspector(cmd, action),
|
||||||
evaluate,
|
}
|
||||||
addBinding,
|
|
||||||
callFunctionOn,
|
|
||||||
releaseObject,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn runtime(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
action: []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) {
|
|
||||||
.runIfWaitingForDebugger => runIfWaitingForDebugger(alloc, msg, ctx),
|
|
||||||
else => sendInspector(alloc, method, msg, ctx),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sendInspector(
|
fn sendInspector(cmd: anytype, action: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
method: Methods,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
|
|
||||||
// save script in file at debug mode
|
// save script in file at debug mode
|
||||||
if (std.log.defaultLogEnabled(.debug)) {
|
if (std.log.defaultLogEnabled(.debug)) {
|
||||||
|
try logInspector(cmd, action);
|
||||||
// input
|
|
||||||
var id: u16 = undefined;
|
|
||||||
var script: ?[]const u8 = null;
|
|
||||||
|
|
||||||
if (method == .evaluate) {
|
|
||||||
const Params = struct {
|
|
||||||
expression: []const u8,
|
|
||||||
contextId: ?u8 = null,
|
|
||||||
returnByValue: ?bool = null,
|
|
||||||
awaitPromise: ?bool = null,
|
|
||||||
userGesture: ?bool = null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s} (script saved on cache)", .{ input.id, "runtime.evaluate" });
|
|
||||||
const params = input.params;
|
|
||||||
const func = try alloc.alloc(u8, params.expression.len);
|
|
||||||
@memcpy(func, params.expression);
|
|
||||||
script = func;
|
|
||||||
id = input.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 input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s} (script saved on cache)", .{ input.id, "runtime.callFunctionOn" });
|
|
||||||
const params = input.params;
|
|
||||||
const func = try alloc.alloc(u8, params.functionDeclaration.len);
|
|
||||||
@memcpy(func, params.functionDeclaration);
|
|
||||||
script = func;
|
|
||||||
id = input.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (script) |src| {
|
|
||||||
try cdp.dumpFile(alloc, id, src);
|
|
||||||
alloc.free(src);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.sessionId) |s| {
|
if (cmd.session_id) |s| {
|
||||||
ctx.state.sessionID = cdp.SessionID.parse(s) catch |err| {
|
cmd.cdp.session_id = try cdp.SessionID.parse(s);
|
||||||
log.err("parse sessionID: {s} {any}", .{ s, err });
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove awaitPromise true params
|
// remove awaitPromise true params
|
||||||
// TODO: delete when Promise are correctly handled by zig-js-runtime
|
// TODO: delete when Promise are correctly handled by zig-js-runtime
|
||||||
if (method == .callFunctionOn or method == .evaluate) {
|
if (action == .callFunctionOn or action == .evaluate) {
|
||||||
if (std.mem.indexOf(u8, msg.json, "\"awaitPromise\":true")) |_| {
|
const json = cmd.json;
|
||||||
const buf = try alloc.alloc(u8, msg.json.len + 1);
|
if (std.mem.indexOf(u8, json, "\"awaitPromise\":true")) |_| {
|
||||||
defer alloc.free(buf);
|
// +1 because we'll be turning a true -> false
|
||||||
_ = std.mem.replace(u8, msg.json, "\"awaitPromise\":true", "\"awaitPromise\":false", buf);
|
const buf = try cmd.arena.alloc(u8, json.len + 1);
|
||||||
try ctx.sendInspector(buf);
|
_ = std.mem.replace(u8, json, "\"awaitPromise\":true", "\"awaitPromise\":false", buf);
|
||||||
return "";
|
cmd.session.callInspector(buf);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try ctx.sendInspector(msg.json);
|
cmd.session.callInspector(cmd.json);
|
||||||
|
|
||||||
if (msg.id == null) return "";
|
if (cmd.id != null) {
|
||||||
|
return cmd.sendResult(null, .{});
|
||||||
return result(alloc, msg.id.?, null, null, msg.sessionId);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const AuxData = struct {
|
pub const ExecutionContextCreated = struct {
|
||||||
isDefault: bool = true,
|
id: u64,
|
||||||
type: []const u8 = "default",
|
|
||||||
frameId: []const u8 = cdp.FrameID,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn executionContextCreated(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
ctx: *Ctx,
|
|
||||||
id: u16,
|
|
||||||
origin: []const u8,
|
origin: []const u8,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
uniqueID: []const u8,
|
uniqueId: []const u8,
|
||||||
auxData: ?AuxData,
|
auxData: ?AuxData = null,
|
||||||
sessionID: ?[]const u8,
|
|
||||||
) !void {
|
|
||||||
const Params = struct {
|
|
||||||
context: struct {
|
|
||||||
id: u64,
|
|
||||||
origin: []const u8,
|
|
||||||
name: []const u8,
|
|
||||||
uniqueId: []const u8,
|
|
||||||
auxData: ?AuxData = null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const params = Params{
|
|
||||||
.context = .{
|
|
||||||
.id = id,
|
|
||||||
.origin = origin,
|
|
||||||
.name = name,
|
|
||||||
.uniqueId = uniqueID,
|
|
||||||
.auxData = auxData,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
try cdp.sendEvent(alloc, ctx, "Runtime.executionContextCreated", Params, params, sessionID);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: noop method
|
pub const AuxData = struct {
|
||||||
// should we be passing this also to the JS Inspector?
|
isDefault: bool = true,
|
||||||
fn runIfWaitingForDebugger(
|
type: []const u8 = "default",
|
||||||
alloc: std.mem.Allocator,
|
frameId: []const u8 = cdp.FRAME_ID,
|
||||||
msg: *IncomingMessage,
|
};
|
||||||
_: *Ctx,
|
};
|
||||||
) ![]const u8 {
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "runtime.runIfWaitingForDebugger" });
|
|
||||||
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
fn logInspector(cmd: anytype, action: anytype) !void {
|
||||||
|
const script = switch (action) {
|
||||||
|
.evaluate => blk: {
|
||||||
|
const params = (try cmd.params(struct {
|
||||||
|
expression: []const u8,
|
||||||
|
// contextId: ?u8 = null,
|
||||||
|
// returnByValue: ?bool = null,
|
||||||
|
// awaitPromise: ?bool = null,
|
||||||
|
// userGesture: ?bool = null,
|
||||||
|
})) orelse return error.InvalidParams;
|
||||||
|
|
||||||
|
break :blk params.expression;
|
||||||
|
},
|
||||||
|
.callFunctionOn => blk: {
|
||||||
|
const params = (try cmd.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,
|
||||||
|
})) orelse return error.InvalidParams;
|
||||||
|
|
||||||
|
break :blk params.functionDeclaration;
|
||||||
|
},
|
||||||
|
else => return,
|
||||||
|
};
|
||||||
|
const id = cmd.id orelse return error.RequiredId;
|
||||||
|
const name = try std.fmt.allocPrint(cmd.arena, "id_{d}.js", .{id});
|
||||||
|
|
||||||
|
var dir = try std.fs.cwd().makeOpenPath("zig-cache/tmp", .{});
|
||||||
|
defer dir.close();
|
||||||
|
|
||||||
|
const f = try dir.createFile(name, .{});
|
||||||
|
defer f.close();
|
||||||
|
try f.writeAll(script);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,43 +17,14 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const server = @import("../server.zig");
|
|
||||||
const Ctx = server.Ctx;
|
|
||||||
const cdp = @import("cdp.zig");
|
const cdp = @import("cdp.zig");
|
||||||
const result = cdp.result;
|
|
||||||
const IncomingMessage = @import("msg.zig").IncomingMessage;
|
|
||||||
const Input = @import("msg.zig").Input;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.cdp);
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
|
const action = std.meta.stringToEnum(enum {
|
||||||
|
enable,
|
||||||
|
}, cmd.action) orelse return error.UnknownMethod;
|
||||||
|
|
||||||
const Methods = enum {
|
switch (action) {
|
||||||
enable,
|
.enable => return cmd.sendResult(null, .{}),
|
||||||
};
|
}
|
||||||
|
|
||||||
pub fn security(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
action: []const u8,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
const method = std.meta.stringToEnum(Methods, action) orelse
|
|
||||||
return error.UnknownMethod;
|
|
||||||
|
|
||||||
return switch (method) {
|
|
||||||
.enable => enable(alloc, msg, ctx),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "security.enable" });
|
|
||||||
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,267 +17,174 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const server = @import("../server.zig");
|
|
||||||
const Ctx = server.Ctx;
|
|
||||||
const cdp = @import("cdp.zig");
|
const cdp = @import("cdp.zig");
|
||||||
const result = cdp.result;
|
|
||||||
const stringify = cdp.stringify;
|
|
||||||
const IncomingMessage = @import("msg.zig").IncomingMessage;
|
|
||||||
const Input = @import("msg.zig").Input;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.cdp);
|
const log = std.log.scoped(.cdp);
|
||||||
|
|
||||||
const Methods = enum {
|
|
||||||
setDiscoverTargets,
|
|
||||||
setAutoAttach,
|
|
||||||
attachToTarget,
|
|
||||||
getTargetInfo,
|
|
||||||
getBrowserContexts,
|
|
||||||
createBrowserContext,
|
|
||||||
disposeBrowserContext,
|
|
||||||
createTarget,
|
|
||||||
closeTarget,
|
|
||||||
sendMessageToTarget,
|
|
||||||
detachFromTarget,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn target(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
action: []const u8,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
const method = std.meta.stringToEnum(Methods, action) orelse
|
|
||||||
return error.UnknownMethod;
|
|
||||||
return switch (method) {
|
|
||||||
.setDiscoverTargets => setDiscoverTargets(alloc, msg, ctx),
|
|
||||||
.setAutoAttach => setAutoAttach(alloc, msg, ctx),
|
|
||||||
.attachToTarget => attachToTarget(alloc, msg, ctx),
|
|
||||||
.getTargetInfo => getTargetInfo(alloc, msg, ctx),
|
|
||||||
.getBrowserContexts => getBrowserContexts(alloc, msg, ctx),
|
|
||||||
.createBrowserContext => createBrowserContext(alloc, msg, ctx),
|
|
||||||
.disposeBrowserContext => disposeBrowserContext(alloc, msg, ctx),
|
|
||||||
.createTarget => createTarget(alloc, msg, ctx),
|
|
||||||
.closeTarget => closeTarget(alloc, msg, ctx),
|
|
||||||
.sendMessageToTarget => sendMessageToTarget(alloc, msg, ctx),
|
|
||||||
.detachFromTarget => detachFromTarget(alloc, msg, ctx),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: hard coded IDs
|
// TODO: hard coded IDs
|
||||||
pub const PageTargetID = "PAGETARGETIDB638E9DC0F52DDC";
|
const CONTEXT_ID = "CONTEXTIDDCCDD11109E2D4FEFBE4F89";
|
||||||
pub const BrowserTargetID = "browser9-targ-et6f-id0e-83f3ab73a30c";
|
const PAGE_TARGET_ID = "PAGETARGETIDB638E9DC0F52DDC";
|
||||||
pub const BrowserContextID = "BROWSERCONTEXTIDA95049E9DFE95EA9";
|
const BROWSER_TARGET_ID = "browser9-targ-et6f-id0e-83f3ab73a30c";
|
||||||
|
const BROWER_CONTEXT_ID = "BROWSERCONTEXTIDA95049E9DFE95EA9";
|
||||||
|
const TARGET_ID = "TARGETID460A8F29706A2ADF14316298";
|
||||||
|
const LOADER_ID = "LOADERID42AA389647D702B4D805F49A";
|
||||||
|
|
||||||
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
|
const action = std.meta.stringToEnum(enum {
|
||||||
|
setDiscoverTargets,
|
||||||
|
setAutoAttach,
|
||||||
|
attachToTarget,
|
||||||
|
getTargetInfo,
|
||||||
|
getBrowserContexts,
|
||||||
|
createBrowserContext,
|
||||||
|
disposeBrowserContext,
|
||||||
|
createTarget,
|
||||||
|
closeTarget,
|
||||||
|
sendMessageToTarget,
|
||||||
|
detachFromTarget,
|
||||||
|
}, cmd.action) orelse return error.UnknownMethod;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
.setDiscoverTargets => return setDiscoverTargets(cmd),
|
||||||
|
.setAutoAttach => return setAutoAttach(cmd),
|
||||||
|
.attachToTarget => return attachToTarget(cmd),
|
||||||
|
.getTargetInfo => return getTargetInfo(cmd),
|
||||||
|
.getBrowserContexts => return getBrowserContexts(cmd),
|
||||||
|
.createBrowserContext => return createBrowserContext(cmd),
|
||||||
|
.disposeBrowserContext => return disposeBrowserContext(cmd),
|
||||||
|
.createTarget => return createTarget(cmd),
|
||||||
|
.closeTarget => return closeTarget(cmd),
|
||||||
|
.sendMessageToTarget => return sendMessageToTarget(cmd),
|
||||||
|
.detachFromTarget => return detachFromTarget(cmd),
|
||||||
|
}
|
||||||
|
}
|
||||||
// TODO: noop method
|
// TODO: noop method
|
||||||
fn setDiscoverTargets(
|
fn setDiscoverTargets(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
return cmd.sendResult(null, .{});
|
||||||
msg: *IncomingMessage,
|
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "target.setDiscoverTargets" });
|
|
||||||
|
|
||||||
// output
|
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AttachToTarget = struct {
|
const AttachToTarget = struct {
|
||||||
sessionId: []const u8,
|
sessionId: []const u8,
|
||||||
targetInfo: struct {
|
targetInfo: TargetInfo,
|
||||||
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,
|
waitingForDebugger: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const TargetCreated = struct {
|
const TargetCreated = struct {
|
||||||
sessionId: []const u8,
|
sessionId: []const u8,
|
||||||
targetInfo: struct {
|
targetInfo: TargetInfo,
|
||||||
targetId: []const u8,
|
|
||||||
type: []const u8 = "page",
|
|
||||||
title: []const u8,
|
|
||||||
url: []const u8,
|
|
||||||
attached: bool = true,
|
|
||||||
canAccessOpener: bool = false,
|
|
||||||
browserContextId: []const u8,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const TargetFilter = struct {
|
const TargetInfo = struct {
|
||||||
type: ?[]const u8 = null,
|
targetId: []const u8,
|
||||||
exclude: ?bool = null,
|
type: []const u8 = "page",
|
||||||
|
title: []const u8,
|
||||||
|
url: []const u8,
|
||||||
|
attached: bool = true,
|
||||||
|
canAccessOpener: bool = false,
|
||||||
|
browserContextId: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: noop method
|
// TODO: noop method
|
||||||
fn setAutoAttach(
|
fn setAutoAttach(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
// const TargetFilter = struct {
|
||||||
msg: *IncomingMessage,
|
// type: ?[]const u8 = null,
|
||||||
ctx: *Ctx,
|
// exclude: ?bool = null,
|
||||||
) ![]const u8 {
|
// };
|
||||||
// input
|
|
||||||
const Params = struct {
|
// const params = (try cmd.params(struct {
|
||||||
autoAttach: bool,
|
// autoAttach: bool,
|
||||||
waitForDebuggerOnStart: bool,
|
// waitForDebuggerOnStart: bool,
|
||||||
flatten: bool = true,
|
// flatten: bool = true,
|
||||||
filter: ?[]TargetFilter = null,
|
// filter: ?[]TargetFilter = null,
|
||||||
};
|
// })) orelse return error.InvalidParams;
|
||||||
const input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "target.setAutoAttach" });
|
|
||||||
|
|
||||||
// attachedToTarget event
|
// attachedToTarget event
|
||||||
if (input.sessionId == null) {
|
if (cmd.session_id == null) {
|
||||||
const attached = AttachToTarget{
|
try cmd.sendEvent("Target.attachedToTarget", AttachToTarget{
|
||||||
.sessionId = cdp.BrowserSessionID,
|
.sessionId = cdp.BROWSER_SESSION_ID,
|
||||||
.targetInfo = .{
|
.targetInfo = .{
|
||||||
.targetId = PageTargetID,
|
.targetId = PAGE_TARGET_ID,
|
||||||
.title = "about:blank",
|
.title = "about:blank",
|
||||||
.url = cdp.URLBase,
|
.url = cdp.URL_BASE,
|
||||||
.browserContextId = BrowserContextID,
|
.browserContextId = BROWER_CONTEXT_ID,
|
||||||
},
|
},
|
||||||
};
|
}, .{});
|
||||||
try cdp.sendEvent(alloc, ctx, "Target.attachedToTarget", AttachToTarget, attached, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// output
|
return cmd.sendResult(null, .{});
|
||||||
return result(alloc, input.id, null, null, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: noop method
|
// TODO: noop method
|
||||||
fn attachToTarget(
|
fn attachToTarget(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
const params = (try cmd.params(struct {
|
||||||
msg: *IncomingMessage,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
|
|
||||||
// input
|
|
||||||
const Params = struct {
|
|
||||||
targetId: []const u8,
|
targetId: []const u8,
|
||||||
flatten: bool = true,
|
flatten: bool = true,
|
||||||
};
|
})) orelse return error.InvalidParams;
|
||||||
const input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "target.attachToTarget" });
|
|
||||||
|
|
||||||
// attachedToTarget event
|
// attachedToTarget event
|
||||||
if (input.sessionId == null) {
|
if (cmd.session_id == null) {
|
||||||
const attached = AttachToTarget{
|
try cmd.sendEvent("Target.attachedToTarget", AttachToTarget{
|
||||||
.sessionId = cdp.BrowserSessionID,
|
.sessionId = cdp.BROWSER_SESSION_ID,
|
||||||
.targetInfo = .{
|
.targetInfo = .{
|
||||||
.targetId = input.params.targetId,
|
.targetId = params.targetId,
|
||||||
.title = "about:blank",
|
.title = "about:blank",
|
||||||
.url = cdp.URLBase,
|
.url = cdp.URL_BASE,
|
||||||
.browserContextId = BrowserContextID,
|
.browserContextId = BROWER_CONTEXT_ID,
|
||||||
},
|
},
|
||||||
};
|
}, .{});
|
||||||
try cdp.sendEvent(alloc, ctx, "Target.attachedToTarget", AttachToTarget, attached, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// output
|
return cmd.sendResult(
|
||||||
const SessionId = struct {
|
.{ .sessionId = cmd.session_id orelse cdp.BROWSER_SESSION_ID },
|
||||||
sessionId: []const u8,
|
.{ .include_session_id = false },
|
||||||
};
|
);
|
||||||
const output = SessionId{
|
|
||||||
.sessionId = input.sessionId orelse cdp.BrowserSessionID,
|
|
||||||
};
|
|
||||||
return result(alloc, input.id, SessionId, output, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getTargetInfo(
|
fn getTargetInfo(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
// const params = (try cmd.params(struct {
|
||||||
msg: *IncomingMessage,
|
// targetId: ?[]const u8 = null,
|
||||||
_: *Ctx,
|
// })) orelse return error.InvalidParams;
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const Params = struct {
|
|
||||||
targetId: ?[]const u8 = null,
|
|
||||||
};
|
|
||||||
const input = try Input(?Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "target.getTargetInfo" });
|
|
||||||
|
|
||||||
// output
|
return cmd.sendResult(.{
|
||||||
const TargetInfo = struct {
|
.targetId = BROWSER_TARGET_ID,
|
||||||
targetId: []const u8,
|
|
||||||
type: []const u8,
|
|
||||||
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 = BrowserTargetID,
|
|
||||||
.type = "browser",
|
.type = "browser",
|
||||||
};
|
.title = "",
|
||||||
return result(alloc, input.id, TargetInfo, targetInfo, null);
|
.url = "",
|
||||||
|
.attached = true,
|
||||||
|
.canAccessOpener = false,
|
||||||
|
}, .{ .include_session_id = false });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Browser context are not handled and not in the roadmap for now
|
// Browser context are not handled and not in the roadmap for now
|
||||||
// The following methods are "fake"
|
// The following methods are "fake"
|
||||||
|
|
||||||
// TODO: noop method
|
// TODO: noop method
|
||||||
fn getBrowserContexts(
|
fn getBrowserContexts(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
var context_ids: []const []const u8 = undefined;
|
||||||
msg: *IncomingMessage,
|
if (cmd.cdp.context_id) |context_id| {
|
||||||
ctx: *Ctx,
|
context_ids = &.{context_id};
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "target.getBrowserContexts" });
|
|
||||||
|
|
||||||
// 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 {
|
} else {
|
||||||
const contextIDs = [0][]const u8{};
|
context_ids = &.{};
|
||||||
resp = .{ .browserContextIds = &contextIDs };
|
|
||||||
}
|
}
|
||||||
return result(alloc, input.id, Resp, resp, null);
|
|
||||||
|
return cmd.sendResult(.{
|
||||||
|
.browserContextIds = context_ids,
|
||||||
|
}, .{ .include_session_id = false });
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContextID = "CONTEXTIDDCCDD11109E2D4FEFBE4F89";
|
|
||||||
|
|
||||||
// TODO: noop method
|
// TODO: noop method
|
||||||
fn createBrowserContext(
|
fn createBrowserContext(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
// const params = (try cmd.params(struct {
|
||||||
msg: *IncomingMessage,
|
// disposeOnDetach: bool = false,
|
||||||
ctx: *Ctx,
|
// proxyServer: ?[]const u8 = null,
|
||||||
) ![]const u8 {
|
// proxyBypassList: ?[]const u8 = null,
|
||||||
// input
|
// originsWithUniversalNetworkAccess: ?[][]const u8 = null,
|
||||||
const Params = struct {
|
// })) orelse return error.InvalidParams;
|
||||||
disposeOnDetach: bool = false,
|
|
||||||
proxyServer: ?[]const u8 = null,
|
|
||||||
proxyBypassList: ?[]const u8 = null,
|
|
||||||
originsWithUniversalNetworkAccess: ?[][]const u8 = null,
|
|
||||||
};
|
|
||||||
const input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "target.createBrowserContext" });
|
|
||||||
|
|
||||||
ctx.state.contextID = ContextID;
|
cmd.cdp.context_id = CONTEXT_ID;
|
||||||
|
|
||||||
// output
|
const Response = struct {
|
||||||
const Resp = struct {
|
browserContextId: []const u8,
|
||||||
browserContextId: []const u8 = ContextID,
|
|
||||||
|
|
||||||
pub fn format(
|
pub fn format(
|
||||||
self: @This(),
|
self: @This(),
|
||||||
@@ -291,40 +198,26 @@ fn createBrowserContext(
|
|||||||
try writer.writeAll(" }");
|
try writer.writeAll(" }");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return result(alloc, input.id, Resp, Resp{}, input.sessionId);
|
|
||||||
|
return cmd.sendResult(Response{
|
||||||
|
.browserContextId = CONTEXT_ID,
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn disposeBrowserContext(
|
fn disposeBrowserContext(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
// const params = (try cmd.params(struct {
|
||||||
msg: *IncomingMessage,
|
// browserContextId: []const u8,
|
||||||
ctx: *Ctx,
|
// proxyServer: ?[]const u8 = null,
|
||||||
) ![]const u8 {
|
// proxyBypassList: ?[]const u8 = null,
|
||||||
// input
|
// originsWithUniversalNetworkAccess: ?[][]const u8 = null,
|
||||||
const Params = struct {
|
// })) orelse return error.InvalidParams;
|
||||||
browserContextId: []const u8,
|
|
||||||
};
|
|
||||||
const input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "target.disposeBrowserContext" });
|
|
||||||
|
|
||||||
// output
|
try cmd.cdp.newSession();
|
||||||
const res = try result(alloc, input.id, null, .{}, null);
|
try cmd.sendResult(null, .{});
|
||||||
try ctx.send(res);
|
|
||||||
|
|
||||||
return error.DisposeBrowserContext;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: hard coded IDs
|
fn createTarget(cmd: anytype) !void {
|
||||||
const TargetID = "TARGETID460A8F29706A2ADF14316298";
|
const params = (try cmd.params(struct {
|
||||||
const LoaderID = "LOADERID42AA389647D702B4D805F49A";
|
|
||||||
|
|
||||||
fn createTarget(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
msg: *IncomingMessage,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const Params = struct {
|
|
||||||
url: []const u8,
|
url: []const u8,
|
||||||
width: ?u64 = null,
|
width: ?u64 = null,
|
||||||
height: ?u64 = null,
|
height: ?u64 = null,
|
||||||
@@ -333,71 +226,67 @@ fn createTarget(
|
|||||||
newWindow: bool = false,
|
newWindow: bool = false,
|
||||||
background: bool = false,
|
background: bool = false,
|
||||||
forTab: ?bool = null,
|
forTab: ?bool = null,
|
||||||
};
|
})) orelse return error.InvalidParams;
|
||||||
const input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "target.createTarget" });
|
|
||||||
|
|
||||||
// change CDP state
|
// change CDP state
|
||||||
ctx.state.frameID = TargetID;
|
var state = cmd.cdp;
|
||||||
ctx.state.url = "about:blank";
|
state.frame_id = TARGET_ID;
|
||||||
ctx.state.securityOrigin = "://";
|
state.url = "about:blank";
|
||||||
ctx.state.secureContextType = "InsecureScheme";
|
state.security_origin = "://";
|
||||||
ctx.state.loaderID = LoaderID;
|
state.secure_context_type = "InsecureScheme";
|
||||||
|
state.loader_id = LOADER_ID;
|
||||||
|
|
||||||
if (msg.sessionId) |s| {
|
if (cmd.session_id) |s| {
|
||||||
ctx.state.sessionID = cdp.SessionID.parse(s) catch |err| {
|
state.session_id = try cdp.SessionID.parse(s);
|
||||||
log.err("parse sessionID: {s} {any}", .{ s, err });
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO stop the previous page instead?
|
// TODO stop the previous page instead?
|
||||||
if (ctx.browser.session.page != null) return error.pageAlreadyExists;
|
if (cmd.session.page != null) {
|
||||||
|
return error.pageAlreadyExists;
|
||||||
|
}
|
||||||
|
|
||||||
// create the page
|
// create the page
|
||||||
const p = try ctx.browser.session.createPage();
|
const p = try cmd.session.createPage();
|
||||||
ctx.state.executionContextId += 1;
|
state.execution_context_id += 1;
|
||||||
|
|
||||||
// start the js env
|
// start the js env
|
||||||
const auxData = try std.fmt.allocPrint(
|
const aux_data = try std.fmt.allocPrint(
|
||||||
alloc,
|
cmd.arena,
|
||||||
// NOTE: we assume this is the default web page
|
// NOTE: we assume this is the default web page
|
||||||
"{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}",
|
"{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}",
|
||||||
.{ctx.state.frameID},
|
.{state.frame_id},
|
||||||
);
|
);
|
||||||
defer alloc.free(auxData);
|
try p.start(aux_data);
|
||||||
try p.start(auxData);
|
|
||||||
|
const browser_context_id = params.browserContextId orelse CONTEXT_ID;
|
||||||
|
|
||||||
// send targetCreated event
|
// send targetCreated event
|
||||||
const created = TargetCreated{
|
try cmd.sendEvent("Target.targetCreated", TargetCreated{
|
||||||
.sessionId = cdp.ContextSessionID,
|
.sessionId = cdp.CONTEXT_SESSION_ID,
|
||||||
.targetInfo = .{
|
.targetInfo = .{
|
||||||
.targetId = ctx.state.frameID,
|
.targetId = state.frame_id,
|
||||||
.title = "about:blank",
|
.title = "about:blank",
|
||||||
.url = ctx.state.url,
|
.url = state.url,
|
||||||
.browserContextId = input.params.browserContextId orelse ContextID,
|
.browserContextId = browser_context_id,
|
||||||
.attached = true,
|
.attached = true,
|
||||||
},
|
},
|
||||||
};
|
}, .{ .session_id = cmd.session_id });
|
||||||
try cdp.sendEvent(alloc, ctx, "Target.targetCreated", TargetCreated, created, input.sessionId);
|
|
||||||
|
|
||||||
// send attachToTarget event
|
// send attachToTarget event
|
||||||
const attached = AttachToTarget{
|
try cmd.sendEvent("Target.attachedToTarget", AttachToTarget{
|
||||||
.sessionId = cdp.ContextSessionID,
|
.sessionId = cdp.CONTEXT_SESSION_ID,
|
||||||
|
.waitingForDebugger = true,
|
||||||
.targetInfo = .{
|
.targetInfo = .{
|
||||||
.targetId = ctx.state.frameID,
|
.targetId = state.frame_id,
|
||||||
.title = "about:blank",
|
.title = "about:blank",
|
||||||
.url = ctx.state.url,
|
.url = state.url,
|
||||||
.browserContextId = input.params.browserContextId orelse ContextID,
|
.browserContextId = browser_context_id,
|
||||||
.attached = true,
|
.attached = true,
|
||||||
},
|
},
|
||||||
.waitingForDebugger = true,
|
}, .{ .session_id = cmd.session_id });
|
||||||
};
|
|
||||||
try cdp.sendEvent(alloc, ctx, "Target.attachedToTarget", AttachToTarget, attached, input.sessionId);
|
|
||||||
|
|
||||||
// output
|
const Response = struct {
|
||||||
const Resp = struct {
|
targetId: []const u8 = TARGET_ID,
|
||||||
targetId: []const u8 = TargetID,
|
|
||||||
|
|
||||||
pub fn format(
|
pub fn format(
|
||||||
self: @This(),
|
self: @This(),
|
||||||
@@ -411,119 +300,71 @@ fn createTarget(
|
|||||||
try writer.writeAll(" }");
|
try writer.writeAll(" }");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return result(alloc, input.id, Resp, Resp{}, input.sessionId);
|
return cmd.sendResult(Response{}, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn closeTarget(
|
fn closeTarget(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
const params = (try cmd.params(struct {
|
||||||
msg: *IncomingMessage,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const Params = struct {
|
|
||||||
targetId: []const u8,
|
targetId: []const u8,
|
||||||
};
|
})) orelse return error.InvalidParams;
|
||||||
const input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "target.closeTarget" });
|
|
||||||
|
|
||||||
// output
|
try cmd.sendResult(.{
|
||||||
const Resp = struct {
|
.success = true,
|
||||||
success: bool = true,
|
}, .{ .include_session_id = false });
|
||||||
};
|
|
||||||
const res = try result(alloc, input.id, Resp, Resp{}, null);
|
const session_id = cmd.session_id orelse cdp.CONTEXT_SESSION_ID;
|
||||||
try ctx.send(res);
|
|
||||||
|
|
||||||
// Inspector.detached event
|
// Inspector.detached event
|
||||||
const InspectorDetached = struct {
|
try cmd.sendEvent("Inspector.detached", .{
|
||||||
reason: []const u8 = "Render process gone.",
|
.reason = "Render process gone.",
|
||||||
};
|
}, .{ .session_id = session_id });
|
||||||
try cdp.sendEvent(
|
|
||||||
alloc,
|
|
||||||
ctx,
|
|
||||||
"Inspector.detached",
|
|
||||||
InspectorDetached,
|
|
||||||
.{},
|
|
||||||
input.sessionId orelse cdp.ContextSessionID,
|
|
||||||
);
|
|
||||||
|
|
||||||
// detachedFromTarget event
|
// detachedFromTarget event
|
||||||
const TargetDetached = struct {
|
try cmd.sendEvent("Target.detachedFromTarget", .{
|
||||||
sessionId: []const u8,
|
.sessionId = session_id,
|
||||||
targetId: []const u8,
|
.targetId = params.targetId,
|
||||||
};
|
.reason = "Render process gone.",
|
||||||
try cdp.sendEvent(
|
}, .{});
|
||||||
alloc,
|
|
||||||
ctx,
|
|
||||||
"Target.detachedFromTarget",
|
|
||||||
TargetDetached,
|
|
||||||
.{
|
|
||||||
.sessionId = input.sessionId orelse cdp.ContextSessionID,
|
|
||||||
.targetId = input.params.targetId,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ctx.browser.session.page != null) ctx.browser.session.page.?.end();
|
if (cmd.session.page) |*page| {
|
||||||
|
page.end();
|
||||||
return "";
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sendMessageToTarget(
|
fn sendMessageToTarget(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
const params = (try cmd.params(struct {
|
||||||
msg: *IncomingMessage,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const Params = struct {
|
|
||||||
message: []const u8,
|
message: []const u8,
|
||||||
sessionId: []const u8,
|
sessionId: []const u8,
|
||||||
};
|
})) orelse return error.InvalidParams;
|
||||||
const input = try Input(Params).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s} ({s})", .{ input.id, "target.sendMessageToTarget", input.params.message });
|
|
||||||
|
|
||||||
// get the wrapped message.
|
const Capture = struct {
|
||||||
var wmsg = IncomingMessage.init(alloc, input.params.message);
|
allocator: std.mem.Allocator,
|
||||||
defer wmsg.deinit();
|
buf: std.ArrayListUnmanaged(u8),
|
||||||
|
|
||||||
const res = cdp.dispatch(alloc, &wmsg, ctx) catch |e| {
|
pub fn sendJSON(self: *@This(), message: anytype) !void {
|
||||||
log.err("send message {d} ({s}): {any}", .{ input.id, input.params.message, e });
|
return std.json.stringify(message, .{
|
||||||
// TODO dispatch error correctly.
|
.emit_null_optional_fields = false,
|
||||||
return e;
|
}, self.buf.writer(self.allocator));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// receivedMessageFromTarget event
|
var capture = Capture{
|
||||||
const ReceivedMessageFromTarget = struct {
|
.buf = .{},
|
||||||
message: []const u8,
|
.allocator = cmd.arena,
|
||||||
sessionId: []const u8,
|
|
||||||
};
|
};
|
||||||
try cdp.sendEvent(
|
|
||||||
alloc,
|
|
||||||
ctx,
|
|
||||||
"Target.receivedMessageFromTarget",
|
|
||||||
ReceivedMessageFromTarget,
|
|
||||||
.{
|
|
||||||
.message = res,
|
|
||||||
.sessionId = input.params.sessionId,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
return "";
|
cmd.cdp.dispatch(cmd.arena, &capture, params.message) catch |err| {
|
||||||
|
log.err("send message {d} ({s}): {any}", .{ cmd.id orelse -1, params.message, err });
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
|
||||||
|
try cmd.sendEvent("Target.receivedMessageFromTarget", .{
|
||||||
|
.message = capture.buf.items,
|
||||||
|
.sessionId = params.sessionId,
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
// noop
|
// noop
|
||||||
fn detachFromTarget(
|
fn detachFromTarget(cmd: anytype) !void {
|
||||||
alloc: std.mem.Allocator,
|
return cmd.sendResult(null, .{});
|
||||||
msg: *IncomingMessage,
|
|
||||||
_: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
// input
|
|
||||||
const input = try Input(void).get(alloc, msg);
|
|
||||||
defer input.deinit();
|
|
||||||
log.debug("Req > id {d}, method {s}", .{ input.id, "target.detachFromTarget" });
|
|
||||||
|
|
||||||
// output
|
|
||||||
return result(alloc, input.id, bool, true, input.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -261,12 +261,13 @@ pub fn main() !void {
|
|||||||
defer loop.deinit();
|
defer loop.deinit();
|
||||||
|
|
||||||
// browser
|
// browser
|
||||||
var browser = Browser{};
|
var browser = Browser.init(alloc, &loop);
|
||||||
try Browser.init(&browser, alloc, &loop, vm);
|
|
||||||
defer browser.deinit();
|
defer browser.deinit();
|
||||||
|
|
||||||
|
var session = try browser.newSession({});
|
||||||
|
|
||||||
// page
|
// page
|
||||||
const page = try browser.session.createPage();
|
const page = try session.createPage();
|
||||||
try page.start(null);
|
try page.start(null);
|
||||||
defer page.end();
|
defer page.end();
|
||||||
|
|
||||||
|
|||||||
@@ -336,7 +336,6 @@ test {
|
|||||||
std.testing.refAllDecls(queryTest);
|
std.testing.refAllDecls(queryTest);
|
||||||
|
|
||||||
std.testing.refAllDecls(@import("generate.zig"));
|
std.testing.refAllDecls(@import("generate.zig"));
|
||||||
std.testing.refAllDecls(@import("cdp/msg.zig"));
|
|
||||||
|
|
||||||
// Don't use refAllDecls, as this will pull in the entire project
|
// Don't use refAllDecls, as this will pull in the entire project
|
||||||
// and break the test build.
|
// and break the test build.
|
||||||
|
|||||||
616
src/server.zig
616
src/server.zig
File diff suppressed because it is too large
Load Diff
@@ -334,14 +334,12 @@ test {
|
|||||||
std.testing.refAllDecls(@import("browser/dump.zig"));
|
std.testing.refAllDecls(@import("browser/dump.zig"));
|
||||||
std.testing.refAllDecls(@import("browser/loader.zig"));
|
std.testing.refAllDecls(@import("browser/loader.zig"));
|
||||||
std.testing.refAllDecls(@import("browser/mime.zig"));
|
std.testing.refAllDecls(@import("browser/mime.zig"));
|
||||||
std.testing.refAllDecls(@import("cdp/msg.zig"));
|
|
||||||
std.testing.refAllDecls(@import("css/css.zig"));
|
std.testing.refAllDecls(@import("css/css.zig"));
|
||||||
std.testing.refAllDecls(@import("css/libdom_test.zig"));
|
std.testing.refAllDecls(@import("css/libdom_test.zig"));
|
||||||
std.testing.refAllDecls(@import("css/match_test.zig"));
|
std.testing.refAllDecls(@import("css/match_test.zig"));
|
||||||
std.testing.refAllDecls(@import("css/parser.zig"));
|
std.testing.refAllDecls(@import("css/parser.zig"));
|
||||||
std.testing.refAllDecls(@import("generate.zig"));
|
std.testing.refAllDecls(@import("generate.zig"));
|
||||||
std.testing.refAllDecls(@import("http/Client.zig"));
|
std.testing.refAllDecls(@import("http/Client.zig"));
|
||||||
std.testing.refAllDecls(@import("msg.zig"));
|
|
||||||
std.testing.refAllDecls(@import("storage/storage.zig"));
|
std.testing.refAllDecls(@import("storage/storage.zig"));
|
||||||
std.testing.refAllDecls(@import("iterator/iterator.zig"));
|
std.testing.refAllDecls(@import("server.zig"));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user