mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 15:13:28 +00:00
Redirect Runtime domain to JS engine Inspector
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
This commit is contained in:
@@ -51,10 +51,9 @@ const log = std.log.scoped(.browser);
|
|||||||
pub const Browser = struct {
|
pub const Browser = struct {
|
||||||
session: *Session,
|
session: *Session,
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, vm: jsruntime.VM) !Browser {
|
pub fn init(alloc: std.mem.Allocator) !Browser {
|
||||||
// We want to ensure the caller initialised a VM, but the browser
|
// We want to ensure the caller initialised a VM, but the browser
|
||||||
// doesn't use it directly...
|
// doesn't use it directly...
|
||||||
_ = vm;
|
|
||||||
|
|
||||||
return Browser{
|
return Browser{
|
||||||
.session = try Session.init(alloc, "about:blank"),
|
.session = try Session.init(alloc, "about:blank"),
|
||||||
@@ -91,6 +90,7 @@ pub const Session = struct {
|
|||||||
loader: Loader,
|
loader: Loader,
|
||||||
env: Env = undefined,
|
env: Env = undefined,
|
||||||
loop: Loop,
|
loop: Loop,
|
||||||
|
inspector: ?jsruntime.Inspector = null,
|
||||||
window: Window,
|
window: Window,
|
||||||
// TODO move the shed to the browser?
|
// TODO move the shed to the browser?
|
||||||
storageShed: storage.Shed,
|
storageShed: storage.Shed,
|
||||||
@@ -122,6 +122,10 @@ pub const Session = struct {
|
|||||||
fn deinit(self: *Session) void {
|
fn deinit(self: *Session) void {
|
||||||
if (self.page) |page| page.end();
|
if (self.page) |page| page.end();
|
||||||
|
|
||||||
|
if (self.inspector) |inspector| {
|
||||||
|
inspector.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
self.env.deinit();
|
self.env.deinit();
|
||||||
self.arena.deinit();
|
self.arena.deinit();
|
||||||
|
|
||||||
@@ -132,9 +136,25 @@ pub const Session = struct {
|
|||||||
self.alloc.destroy(self);
|
self.alloc.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setInspector(
|
||||||
|
self: *Session,
|
||||||
|
ctx: *anyopaque,
|
||||||
|
onResp: jsruntime.InspectorOnResponseFn,
|
||||||
|
onEvent: jsruntime.InspectorOnEventFn,
|
||||||
|
) !void {
|
||||||
|
self.inspector = try jsruntime.Inspector.init(self.alloc, self.env, ctx, onResp, onEvent);
|
||||||
|
self.env.setInspector(self.inspector.?);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn createPage(self: *Session) !Page {
|
pub fn createPage(self: *Session) !Page {
|
||||||
return Page.init(self.alloc, self);
|
return Page.init(self.alloc, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn callInspector(self: *Session, msg: []const u8) void {
|
||||||
|
if (self.inspector) |inspector| {
|
||||||
|
inspector.send(msg, self.env);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Page navigates to an url.
|
// Page navigates to an url.
|
||||||
@@ -219,7 +239,7 @@ pub const Page = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// spec reference: https://html.spec.whatwg.org/#document-lifecycle
|
// spec reference: https://html.spec.whatwg.org/#document-lifecycle
|
||||||
pub fn navigate(self: *Page, uri: []const u8) !void {
|
pub fn navigate(self: *Page, uri: []const u8, auxData: ?[]const u8) !void {
|
||||||
const alloc = self.arena.allocator();
|
const alloc = self.arena.allocator();
|
||||||
|
|
||||||
log.debug("starting GET {s}", .{uri});
|
log.debug("starting GET {s}", .{uri});
|
||||||
@@ -280,7 +300,7 @@ pub const Page = struct {
|
|||||||
log.debug("header content-type: {s}", .{ct.?});
|
log.debug("header content-type: {s}", .{ct.?});
|
||||||
const mime = try Mime.parse(ct.?);
|
const mime = try Mime.parse(ct.?);
|
||||||
if (mime.eql(Mime.HTML)) {
|
if (mime.eql(Mime.HTML)) {
|
||||||
try self.loadHTMLDoc(req.reader(), mime.charset orelse "utf-8");
|
try self.loadHTMLDoc(req.reader(), mime.charset orelse "utf-8", auxData);
|
||||||
} else {
|
} else {
|
||||||
log.info("non-HTML document: {s}", .{ct.?});
|
log.info("non-HTML document: {s}", .{ct.?});
|
||||||
|
|
||||||
@@ -290,7 +310,7 @@ pub const Page = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/#read-html
|
// https://html.spec.whatwg.org/#read-html
|
||||||
fn loadHTMLDoc(self: *Page, reader: anytype, charset: []const u8) !void {
|
fn loadHTMLDoc(self: *Page, reader: anytype, charset: []const u8, auxData: ?[]const u8) !void {
|
||||||
const alloc = self.arena.allocator();
|
const alloc = self.arena.allocator();
|
||||||
|
|
||||||
// start netsurf memory arena.
|
// start netsurf memory arena.
|
||||||
@@ -327,6 +347,11 @@ pub const Page = struct {
|
|||||||
log.debug("start js env", .{});
|
log.debug("start js env", .{});
|
||||||
try self.session.env.start();
|
try self.session.env.start();
|
||||||
|
|
||||||
|
// inspector
|
||||||
|
if (self.session.inspector) |inspector| {
|
||||||
|
inspector.contextCreated(self.session.env, "", self.origin.?, auxData);
|
||||||
|
}
|
||||||
|
|
||||||
// replace the user context document with the new one.
|
// replace the user context document with the new one.
|
||||||
try self.session.env.setUserContext(.{
|
try self.session.env.setUserContext(.{
|
||||||
.document = html_doc,
|
.document = html_doc,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const performance = @import("performance.zig").performance;
|
|||||||
pub const Error = error{
|
pub const Error = error{
|
||||||
UnknonwDomain,
|
UnknonwDomain,
|
||||||
UnknownMethod,
|
UnknownMethod,
|
||||||
|
NoResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn isCdpError(err: anyerror) ?Error {
|
pub fn isCdpError(err: anyerror) ?Error {
|
||||||
@@ -85,7 +86,7 @@ pub fn do(
|
|||||||
.Target => target(alloc, id, iter.next().?, &scanner, ctx),
|
.Target => target(alloc, id, iter.next().?, &scanner, ctx),
|
||||||
.Page => page(alloc, id, iter.next().?, &scanner, ctx),
|
.Page => page(alloc, id, iter.next().?, &scanner, ctx),
|
||||||
.Log => log(alloc, id, iter.next().?, &scanner, ctx),
|
.Log => log(alloc, id, iter.next().?, &scanner, ctx),
|
||||||
.Runtime => runtime(alloc, id, iter.next().?, &scanner, ctx),
|
.Runtime => runtime(alloc, id, iter.next().?, &scanner, s, ctx),
|
||||||
.Network => network(alloc, id, iter.next().?, &scanner, ctx),
|
.Network => network(alloc, id, iter.next().?, &scanner, ctx),
|
||||||
.Emulation => emulation(alloc, id, iter.next().?, &scanner, ctx),
|
.Emulation => emulation(alloc, id, iter.next().?, &scanner, ctx),
|
||||||
.Fetch => fetch(alloc, id, iter.next().?, &scanner, ctx),
|
.Fetch => fetch(alloc, id, iter.next().?, &scanner, ctx),
|
||||||
@@ -199,7 +200,7 @@ fn getParams(
|
|||||||
key: []const u8,
|
key: []const u8,
|
||||||
) !?T {
|
) !?T {
|
||||||
|
|
||||||
// check key key is "params"
|
// check key is "params"
|
||||||
if (!std.mem.eql(u8, "params", key)) return null;
|
if (!std.mem.eql(u8, "params", key)) return null;
|
||||||
|
|
||||||
// skip "params" if not requested
|
// skip "params" if not requested
|
||||||
@@ -285,7 +286,8 @@ pub fn getMsg(
|
|||||||
// Common
|
// Common
|
||||||
// ------
|
// ------
|
||||||
|
|
||||||
pub const SessionID = "9559320D92474062597D9875C664CAC0";
|
pub const BrowserSessionID = "9559320D92474062597D9875C664CAC0";
|
||||||
|
pub const ContextSessionID = "4FDC2CB760A23A220497A05C95417CF4";
|
||||||
pub const URLBase = "chrome://newtab/";
|
pub const URLBase = "chrome://newtab/";
|
||||||
pub const FrameID = "90D14BBD8AED408A0467AC93100BCDBE";
|
pub const FrameID = "90D14BBD8AED408A0467AC93100BCDBE";
|
||||||
pub const LoaderID = "CFC8BED824DD2FD56CF1EF33C965C79C";
|
pub const LoaderID = "CFC8BED824DD2FD56CF1EF33C965C79C";
|
||||||
|
|||||||
@@ -144,13 +144,38 @@ fn createIsolatedWorld(
|
|||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
id: ?u16,
|
id: ?u16,
|
||||||
scanner: *std.json.Scanner,
|
scanner: *std.json.Scanner,
|
||||||
_: *Ctx,
|
ctx: *Ctx,
|
||||||
) ![]const u8 {
|
) ![]const u8 {
|
||||||
const msg = try getMsg(alloc, void, scanner);
|
|
||||||
|
// input
|
||||||
|
const Params = struct {
|
||||||
|
frameId: []const u8,
|
||||||
|
worldName: []const u8,
|
||||||
|
grantUniveralAccess: bool,
|
||||||
|
};
|
||||||
|
const msg = try getMsg(alloc, Params, scanner);
|
||||||
|
std.debug.assert(msg.sessionID != null);
|
||||||
|
const params = msg.params.?;
|
||||||
|
|
||||||
|
// noop executionContextCreated event
|
||||||
|
try Runtime.executionContextCreated(
|
||||||
|
alloc,
|
||||||
|
ctx,
|
||||||
|
0,
|
||||||
|
"",
|
||||||
|
params.worldName,
|
||||||
|
"7102379147004877974.3265385113993241162",
|
||||||
|
.{
|
||||||
|
.isDefault = false,
|
||||||
|
.type = "isolated",
|
||||||
|
.frameId = params.frameId,
|
||||||
|
},
|
||||||
|
msg.sessionID,
|
||||||
|
);
|
||||||
|
|
||||||
// output
|
// output
|
||||||
const Resp = struct {
|
const Resp = struct {
|
||||||
executionContextId: u8 = 2,
|
executionContextId: u8 = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
return result(alloc, id orelse msg.id.?, Resp, .{}, msg.sessionID);
|
return result(alloc, id orelse msg.id.?, Resp, .{}, msg.sessionID);
|
||||||
@@ -230,20 +255,14 @@ fn navigate(
|
|||||||
|
|
||||||
// Launch navigate
|
// Launch navigate
|
||||||
var p = try ctx.browser.currentSession().createPage();
|
var p = try ctx.browser.currentSession().createPage();
|
||||||
_ = try p.navigate(params.url);
|
|
||||||
|
|
||||||
// Send create runtime context event
|
|
||||||
ctx.state.executionContextId += 1;
|
ctx.state.executionContextId += 1;
|
||||||
try Runtime.executionContextCreated(
|
const auxData = try std.fmt.allocPrint(
|
||||||
alloc,
|
alloc,
|
||||||
ctx,
|
"{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}",
|
||||||
ctx.state.executionContextId,
|
.{ctx.state.frameID},
|
||||||
"http://127.0.0.1:1234", // TODO: real domain
|
|
||||||
"",
|
|
||||||
"7102379147004877974.3265385113993241162",
|
|
||||||
.{ .frameId = ctx.state.frameID },
|
|
||||||
msg.sessionID,
|
|
||||||
);
|
);
|
||||||
|
defer alloc.free(auxData);
|
||||||
|
_ = try p.navigate(params.url, auxData);
|
||||||
|
|
||||||
// frameNavigated event
|
// frameNavigated event
|
||||||
const FrameNavigated = struct {
|
const FrameNavigated = struct {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const Methods = enum {
|
|||||||
evaluate,
|
evaluate,
|
||||||
addBinding,
|
addBinding,
|
||||||
callFunctionOn,
|
callFunctionOn,
|
||||||
|
releaseObject,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn runtime(
|
pub fn runtime(
|
||||||
@@ -23,44 +24,83 @@ pub fn runtime(
|
|||||||
id: ?u16,
|
id: ?u16,
|
||||||
action: []const u8,
|
action: []const u8,
|
||||||
scanner: *std.json.Scanner,
|
scanner: *std.json.Scanner,
|
||||||
|
s: []const u8,
|
||||||
ctx: *Ctx,
|
ctx: *Ctx,
|
||||||
) ![]const u8 {
|
) ![]const u8 {
|
||||||
const method = std.meta.stringToEnum(Methods, action) orelse
|
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 error.UnknownMethod;
|
||||||
return switch (method) {
|
return switch (method) {
|
||||||
.enable => enable(alloc, id, scanner, ctx),
|
|
||||||
.runIfWaitingForDebugger => runIfWaitingForDebugger(alloc, id, scanner, ctx),
|
.runIfWaitingForDebugger => runIfWaitingForDebugger(alloc, id, scanner, ctx),
|
||||||
.evaluate => evaluate(alloc, id, scanner, ctx),
|
else => sendInspector(alloc, method, id, s, scanner, ctx),
|
||||||
.addBinding => addBinding(alloc, id, scanner, ctx),
|
|
||||||
.callFunctionOn => callFunctionOn(alloc, id, scanner, ctx),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enable(
|
fn sendInspector(
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
id: ?u16,
|
method: Methods,
|
||||||
|
_id: ?u16,
|
||||||
|
s: []const u8,
|
||||||
scanner: *std.json.Scanner,
|
scanner: *std.json.Scanner,
|
||||||
_: *Ctx,
|
ctx: *Ctx,
|
||||||
) ![]const u8 {
|
) ![]const u8 {
|
||||||
|
|
||||||
|
// save script in file at debug mode
|
||||||
|
if (std.log.defaultLogEnabled(.debug)) {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
const msg = try getMsg(alloc, void, scanner);
|
var script: ?[]const u8 = null;
|
||||||
|
var id: u16 = undefined;
|
||||||
|
|
||||||
// output
|
if (method == .evaluate) {
|
||||||
// const uniqueID = "1367118932354479079.-1471398151593995849";
|
const Params = struct {
|
||||||
// const mainCtx = try executionContextCreated(
|
expression: []const u8,
|
||||||
// alloc,
|
contextId: ?u8 = null,
|
||||||
// 1,
|
returnByValue: ?bool = null,
|
||||||
// cdp.URLBase,
|
awaitPromise: ?bool = null,
|
||||||
// "",
|
userGesture: ?bool = null,
|
||||||
// uniqueID,
|
};
|
||||||
// .{},
|
|
||||||
// sessionID,
|
|
||||||
// );
|
|
||||||
// std.log.debug("res {s}", .{mainCtx});
|
|
||||||
// try server.sendAsync(ctx, mainCtx);
|
|
||||||
|
|
||||||
return result(alloc, id orelse msg.id.?, null, null, msg.sessionID);
|
const msg = try getMsg(alloc, Params, scanner);
|
||||||
|
const params = msg.params.?;
|
||||||
|
script = params.expression;
|
||||||
|
id = _id orelse msg.id.?;
|
||||||
|
} else if (method == .callFunctionOn) {
|
||||||
|
const Params = struct {
|
||||||
|
functionDeclaration: []const u8,
|
||||||
|
objectId: ?[]const u8 = null,
|
||||||
|
executionContextId: ?u8 = null,
|
||||||
|
arguments: ?[]struct {
|
||||||
|
value: ?[]const u8 = null,
|
||||||
|
objectId: ?[]const u8 = null,
|
||||||
|
} = null,
|
||||||
|
returnByValue: ?bool = null,
|
||||||
|
awaitPromise: ?bool = null,
|
||||||
|
userGesture: ?bool = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const msg = try getMsg(alloc, Params, scanner);
|
||||||
|
const params = msg.params.?;
|
||||||
|
script = params.functionDeclaration;
|
||||||
|
id = _id orelse msg.id.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (script) |src| {
|
||||||
|
try cdp.dumpFile(alloc, id, src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove awaitPromise true params
|
||||||
|
// TODO: delete when Promise are correctly handled by zig-js-runtime
|
||||||
|
if (method == .callFunctionOn or method == .evaluate) {
|
||||||
|
const buf = try alloc.alloc(u8, s.len + 1);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
_ = std.mem.replace(u8, s, "\"awaitPromise\":true", "\"awaitPromise\":false", buf);
|
||||||
|
ctx.sendInspector(buf);
|
||||||
|
} else {
|
||||||
|
ctx.sendInspector(s);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const AuxData = struct {
|
pub const AuxData = struct {
|
||||||
@@ -69,14 +109,6 @@ pub const AuxData = struct {
|
|||||||
frameId: []const u8 = cdp.FrameID,
|
frameId: []const u8 = cdp.FrameID,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ExecutionContextDescription = struct {
|
|
||||||
id: u64,
|
|
||||||
origin: []const u8,
|
|
||||||
name: []const u8,
|
|
||||||
uniqueId: []const u8,
|
|
||||||
auxData: ?AuxData = null,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn executionContextCreated(
|
pub fn executionContextCreated(
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
ctx: *Ctx,
|
ctx: *Ctx,
|
||||||
@@ -88,7 +120,13 @@ pub fn executionContextCreated(
|
|||||||
sessionID: ?[]const u8,
|
sessionID: ?[]const u8,
|
||||||
) !void {
|
) !void {
|
||||||
const Params = struct {
|
const Params = struct {
|
||||||
context: ExecutionContextDescription,
|
context: struct {
|
||||||
|
id: u64,
|
||||||
|
origin: []const u8,
|
||||||
|
name: []const u8,
|
||||||
|
uniqueId: []const u8,
|
||||||
|
auxData: ?AuxData = null,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const params = Params{
|
const params = Params{
|
||||||
.context = .{
|
.context = .{
|
||||||
@@ -112,195 +150,3 @@ fn runIfWaitingForDebugger(
|
|||||||
|
|
||||||
return result(alloc, id orelse msg.id.?, null, null, msg.sessionID);
|
return result(alloc, id orelse msg.id.?, null, null, msg.sessionID);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
_id: ?u16,
|
|
||||||
scanner: *std.json.Scanner,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
|
|
||||||
// ensure a page has been previously created
|
|
||||||
if (ctx.browser.currentSession().page == null) return error.CDPNoPage;
|
|
||||||
|
|
||||||
// input
|
|
||||||
const Params = struct {
|
|
||||||
expression: []const u8,
|
|
||||||
contextId: ?u8 = null,
|
|
||||||
returnByValue: ?bool = null,
|
|
||||||
awaitPromise: ?bool = null,
|
|
||||||
userGesture: ?bool = null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const msg = try getMsg(alloc, Params, scanner);
|
|
||||||
std.debug.assert(msg.sessionID != null);
|
|
||||||
const params = msg.params.?;
|
|
||||||
const id = _id orelse msg.id.?;
|
|
||||||
|
|
||||||
// save script in file at debug mode
|
|
||||||
std.log.debug("script {d} length: {d}", .{ id, params.expression.len });
|
|
||||||
if (std.log.defaultLogEnabled(.debug)) {
|
|
||||||
try cdp.dumpFile(alloc, id, params.expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
// evaluate the script in the context of the current page
|
|
||||||
const session = ctx.browser.currentSession();
|
|
||||||
// TODO: should we use instead the allocator of the page?
|
|
||||||
// the following code does not work with session.page.?.arena.allocator() as alloc
|
|
||||||
const res = try runtimeEvaluate(session.alloc, id, session.env, params.expression, "cdp");
|
|
||||||
|
|
||||||
// check result
|
|
||||||
const res_type = try res.typeOf(session.env);
|
|
||||||
|
|
||||||
// TODO: Resp should depends on JS result returned by the JS engine
|
|
||||||
const Resp = struct {
|
|
||||||
result: struct {
|
|
||||||
type: []const u8,
|
|
||||||
subtype: ?[]const u8 = null,
|
|
||||||
className: ?[]const u8 = null,
|
|
||||||
description: ?[]const u8 = null,
|
|
||||||
objectId: ?[]const u8 = null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
var resp = Resp{
|
|
||||||
.result = .{
|
|
||||||
.type = @tagName(res_type),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (res_type == .object) {
|
|
||||||
resp.result.className = "Object";
|
|
||||||
resp.result.description = "Object";
|
|
||||||
resp.result.objectId = "-9051357107442861868.3.2";
|
|
||||||
}
|
|
||||||
return result(alloc, id, Resp, resp, msg.sessionID);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn addBinding(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
_id: ?u16,
|
|
||||||
scanner: *std.json.Scanner,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
|
|
||||||
// input
|
|
||||||
const Params = struct {
|
|
||||||
name: []const u8,
|
|
||||||
executionContextId: ?u8 = null,
|
|
||||||
};
|
|
||||||
const msg = try getMsg(alloc, Params, scanner);
|
|
||||||
const id = _id orelse msg.id.?;
|
|
||||||
const params = msg.params.?;
|
|
||||||
if (params.executionContextId) |contextId| {
|
|
||||||
std.debug.assert(contextId == ctx.state.executionContextId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const script = try std.fmt.allocPrint(alloc, "globalThis['{s}'] = {{}};", .{params.name});
|
|
||||||
defer alloc.free(script);
|
|
||||||
|
|
||||||
const session = ctx.browser.currentSession();
|
|
||||||
_ = try runtimeEvaluate(session.alloc, id, session.env, script, "addBinding");
|
|
||||||
|
|
||||||
return result(alloc, id, null, null, msg.sessionID);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn callFunctionOn(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
_id: ?u16,
|
|
||||||
scanner: *std.json.Scanner,
|
|
||||||
ctx: *Ctx,
|
|
||||||
) ![]const u8 {
|
|
||||||
|
|
||||||
// input
|
|
||||||
const Params = struct {
|
|
||||||
functionDeclaration: []const u8,
|
|
||||||
objectId: ?[]const u8 = null,
|
|
||||||
executionContextId: ?u8 = null,
|
|
||||||
arguments: ?[]struct {
|
|
||||||
value: ?[]const u8 = null,
|
|
||||||
} = null,
|
|
||||||
returnByValue: ?bool = null,
|
|
||||||
awaitPromise: ?bool = null,
|
|
||||||
userGesture: ?bool = null,
|
|
||||||
};
|
|
||||||
const msg = try getMsg(alloc, Params, scanner);
|
|
||||||
const id = _id orelse msg.id.?;
|
|
||||||
const params = msg.params.?;
|
|
||||||
std.debug.assert(params.objectId != null or params.executionContextId != null);
|
|
||||||
if (params.executionContextId) |contextID| {
|
|
||||||
std.debug.assert(contextID == ctx.state.executionContextId);
|
|
||||||
}
|
|
||||||
const name = "callFunctionOn";
|
|
||||||
|
|
||||||
// save script in file at debug mode
|
|
||||||
std.log.debug("{s} script id {d}, length: {d}", .{ name, id, params.functionDeclaration.len });
|
|
||||||
if (std.log.defaultLogEnabled(.debug)) {
|
|
||||||
try cdp.dumpFile(alloc, id, params.functionDeclaration);
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse function
|
|
||||||
if (!std.mem.startsWith(u8, params.functionDeclaration, "function ")) {
|
|
||||||
return error.CDPRuntimeCallFunctionOnNotFunction;
|
|
||||||
}
|
|
||||||
const pos = std.mem.indexOfScalar(u8, params.functionDeclaration, '(');
|
|
||||||
if (pos == null) return error.CDPRuntimeCallFunctionOnWrongFunction;
|
|
||||||
var function = params.functionDeclaration[9..pos.?];
|
|
||||||
function = try std.fmt.allocPrint(alloc, "{s}(", .{function});
|
|
||||||
defer alloc.free(function);
|
|
||||||
if (params.arguments) |args| {
|
|
||||||
for (args, 0..) |arg, i| {
|
|
||||||
if (i > 0) {
|
|
||||||
function = try std.fmt.allocPrint(alloc, "{s}, ", .{function});
|
|
||||||
}
|
|
||||||
if (arg.value) |value| {
|
|
||||||
function = try std.fmt.allocPrint(alloc, "{s}\"{s}\"", .{ function, value });
|
|
||||||
} else {
|
|
||||||
function = try std.fmt.allocPrint(alloc, "{s}undefined", .{function});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function = try std.fmt.allocPrint(alloc, "{s});", .{function});
|
|
||||||
std.log.debug("{s} id {d}, function parsed: {s}", .{ name, id, function });
|
|
||||||
|
|
||||||
const session = ctx.browser.currentSession();
|
|
||||||
// TODO: should we use the page's allocator instead of the session's allocator?
|
|
||||||
// the following code does not work with session.page.?.arena.allocator() as alloc
|
|
||||||
|
|
||||||
// first evaluate the function declaration
|
|
||||||
_ = try runtimeEvaluate(session.alloc, id, session.env, params.functionDeclaration, name);
|
|
||||||
|
|
||||||
// then call the function on the arguments
|
|
||||||
_ = try runtimeEvaluate(session.alloc, id, session.env, function, name);
|
|
||||||
|
|
||||||
return result(alloc, id, null, "{\"type\":\"undefined\"}", msg.sessionID);
|
|
||||||
}
|
|
||||||
|
|
||||||
// caller is the owner of JSResult returned
|
|
||||||
fn runtimeEvaluate(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
id: u16,
|
|
||||||
env: jsruntime.Env,
|
|
||||||
script: []const u8,
|
|
||||||
comptime name: []const u8,
|
|
||||||
) !jsruntime.JSValue {
|
|
||||||
|
|
||||||
// try catch
|
|
||||||
var try_catch: jsruntime.TryCatch = undefined;
|
|
||||||
try_catch.init(env);
|
|
||||||
defer try_catch.deinit();
|
|
||||||
|
|
||||||
// script exec
|
|
||||||
const res = env.execWait(script, name) catch {
|
|
||||||
if (try try_catch.err(alloc, env)) |err_msg| {
|
|
||||||
defer alloc.free(err_msg);
|
|
||||||
std.log.err("'{s}' id {d}, result: {s}", .{ name, id, err_msg });
|
|
||||||
}
|
|
||||||
return error.CDPRuntimeEvaluate;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (builtin.mode == .Debug) {
|
|
||||||
const res_msg = try res.toString(alloc, env);
|
|
||||||
defer alloc.free(res_msg);
|
|
||||||
std.log.debug("'{s}' id {d}, result: {s}", .{ name, id, res_msg });
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ fn setAutoAttach(
|
|||||||
|
|
||||||
if (msg.sessionID == null) {
|
if (msg.sessionID == null) {
|
||||||
const attached = AttachToTarget{
|
const attached = AttachToTarget{
|
||||||
.sessionId = cdp.SessionID,
|
.sessionId = cdp.BrowserSessionID,
|
||||||
.targetInfo = .{
|
.targetInfo = .{
|
||||||
.targetId = PageTargetID,
|
.targetId = PageTargetID,
|
||||||
.title = "New Incognito tab",
|
.title = "New Incognito tab",
|
||||||
@@ -160,7 +160,6 @@ fn getBrowserContexts(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ContextID = "22648B09EDCCDD11109E2D4FEFBE4F89";
|
const ContextID = "22648B09EDCCDD11109E2D4FEFBE4F89";
|
||||||
const ContextSessionID = "4FDC2CB760A23A220497A05C95417CF4";
|
|
||||||
|
|
||||||
fn createBrowserContext(
|
fn createBrowserContext(
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
@@ -218,7 +217,7 @@ fn createTarget(
|
|||||||
|
|
||||||
// send attachToTarget event
|
// send attachToTarget event
|
||||||
const attached = AttachToTarget{
|
const attached = AttachToTarget{
|
||||||
.sessionId = ContextSessionID,
|
.sessionId = cdp.ContextSessionID,
|
||||||
.targetInfo = .{
|
.targetInfo = .{
|
||||||
.targetId = ctx.state.frameID,
|
.targetId = ctx.state.frameID,
|
||||||
.title = "",
|
.title = "",
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ pub fn main() !void {
|
|||||||
defer srv.close();
|
defer srv.close();
|
||||||
std.debug.print("Listening on: {s}...\n", .{socket_path});
|
std.debug.print("Listening on: {s}...\n", .{socket_path});
|
||||||
|
|
||||||
var browser = try Browser.init(arena.allocator(), vm);
|
var browser = try Browser.init(arena.allocator());
|
||||||
defer browser.deinit();
|
defer browser.deinit();
|
||||||
|
|
||||||
try server.listen(&browser, srv.sockfd.?);
|
try server.listen(&browser, srv.sockfd.?);
|
||||||
|
|||||||
@@ -80,13 +80,13 @@ pub fn main() !void {
|
|||||||
const vm = jsruntime.VM.init();
|
const vm = jsruntime.VM.init();
|
||||||
defer vm.deinit();
|
defer vm.deinit();
|
||||||
|
|
||||||
var browser = try Browser.init(allocator, vm);
|
var browser = try Browser.init(allocator);
|
||||||
defer browser.deinit();
|
defer browser.deinit();
|
||||||
|
|
||||||
var page = try browser.currentSession().createPage();
|
var page = try browser.currentSession().createPage();
|
||||||
defer page.deinit();
|
defer page.deinit();
|
||||||
|
|
||||||
try page.navigate(url);
|
try page.navigate(url, null);
|
||||||
defer page.end();
|
defer page.end();
|
||||||
|
|
||||||
try page.wait();
|
try page.wait();
|
||||||
|
|||||||
@@ -70,16 +70,20 @@ pub const Cmd = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// shortcuts
|
// shortcuts
|
||||||
fn alloc(self: *Cmd) std.mem.Allocator {
|
inline fn alloc(self: *Cmd) std.mem.Allocator {
|
||||||
// TODO: should we return the allocator from the page instead?
|
// TODO: should we return the allocator from the page instead?
|
||||||
return self.browser.currentSession().alloc;
|
return self.browser.currentSession().alloc;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loop(self: *Cmd) public.Loop {
|
inline fn loop(self: *Cmd) public.Loop {
|
||||||
// TODO: pointer instead?
|
// TODO: pointer instead?
|
||||||
return self.browser.currentSession().loop;
|
return self.browser.currentSession().loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fn env(self: Cmd) public.Env {
|
||||||
|
return self.browser.currentSession().env;
|
||||||
|
}
|
||||||
|
|
||||||
fn do(self: *Cmd, cmd: []const u8) anyerror!void {
|
fn do(self: *Cmd, cmd: []const u8) anyerror!void {
|
||||||
const res = try cdp.do(self.alloc(), cmd, self);
|
const res = try cdp.do(self.alloc(), cmd, self);
|
||||||
|
|
||||||
@@ -89,6 +93,49 @@ pub const Cmd = struct {
|
|||||||
return sendAsync(self, res);
|
return sendAsync(self, res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inspector
|
||||||
|
|
||||||
|
pub fn sendInspector(self: *Cmd, msg: []const u8) void {
|
||||||
|
if (self.env().getInspector()) |inspector| {
|
||||||
|
inspector.send(self.env(), msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn onInspectorResp(cmd_opaque: *anyopaque, _: u32, msg: []const u8) void {
|
||||||
|
std.log.debug("onResp biz fn called: {s}", .{msg});
|
||||||
|
const aligned = @as(*align(@alignOf(Cmd)) anyopaque, @alignCast(cmd_opaque));
|
||||||
|
const self = @as(*Cmd, @ptrCast(aligned));
|
||||||
|
|
||||||
|
const tpl = "{s},\"sessionId\":\"{s}\"}}";
|
||||||
|
const msg_open = msg[0 .. msg.len - 1]; // remove closing bracket
|
||||||
|
const s = std.fmt.allocPrint(
|
||||||
|
self.alloc(),
|
||||||
|
tpl,
|
||||||
|
.{ msg_open, cdp.ContextSessionID },
|
||||||
|
) catch unreachable;
|
||||||
|
defer self.alloc().free(s);
|
||||||
|
|
||||||
|
sendSync(self, s) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn onInspectorNotif(cmd_opaque: *anyopaque, msg: []const u8) void {
|
||||||
|
std.log.debug("onNotif biz fn called: {s}", .{msg});
|
||||||
|
const aligned = @as(*align(@alignOf(Cmd)) anyopaque, @alignCast(cmd_opaque));
|
||||||
|
const self = @as(*Cmd, @ptrCast(aligned));
|
||||||
|
|
||||||
|
const tpl = "{s},\"sessionId\":\"{s}\"}}";
|
||||||
|
const msg_open = msg[0 .. msg.len - 1]; // remove closing bracket
|
||||||
|
const s = std.fmt.allocPrint(
|
||||||
|
self.alloc(),
|
||||||
|
tpl,
|
||||||
|
.{ msg_open, cdp.ContextSessionID },
|
||||||
|
) catch unreachable;
|
||||||
|
defer self.alloc().free(s);
|
||||||
|
std.log.debug("event: {s}", .{s});
|
||||||
|
|
||||||
|
sendSync(self, s) catch unreachable;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// I/O Send
|
// I/O Send
|
||||||
@@ -195,6 +242,9 @@ pub fn listen(browser: *Browser, socket: std.posix.socket_t) anyerror!void {
|
|||||||
.buf = &ctxInput,
|
.buf = &ctxInput,
|
||||||
.msg_buf = &msg_buf,
|
.msg_buf = &msg_buf,
|
||||||
};
|
};
|
||||||
|
const cmd_opaque = @as(*anyopaque, @ptrCast(&cmd));
|
||||||
|
try browser.currentSession().setInspector(cmd_opaque, Cmd.onInspectorResp, Cmd.onInspectorNotif);
|
||||||
|
|
||||||
var accept = Accept{
|
var accept = Accept{
|
||||||
.cmd = &cmd,
|
.cmd = &cmd,
|
||||||
.socket = socket,
|
.socket = socket,
|
||||||
|
|||||||
Reference in New Issue
Block a user