Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 51 additions & 23 deletions src/browser/browser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const Walker = @import("../dom/walker.zig").WalkerDepthFirst;

const URL = @import("../url.zig").URL;
const storage = @import("../storage/storage.zig");
const Notification = @import("../notification.zig").Notification;

const http = @import("../http/client.zig");
const UserContext = @import("../user_context.zig").UserContext;
Expand Down Expand Up @@ -137,14 +138,32 @@ pub const Session = struct {

jstypes: [Types.len]usize = undefined,

// recipient of notification, passed as the first parameter to notify
ctx: *anyopaque,
notify_func: *const fn (ctx: *anyopaque, notification: *const Notification) anyerror!void,

fn init(self: *Session, browser: *Browser, ctx: anytype) !void {
const ContextT = @TypeOf(ctx);
const ContextStruct = switch (@typeInfo(ContextT)) {
.@"struct" => ContextT,
.pointer => |ptr| ptr.child,
.void => NoopContext,
else => @compileError("invalid context type"),
};

// ctx can be void, to be able to store it in our *anyopaque field, we
// need to play a little game.
const any_ctx: *anyopaque = if (@TypeOf(ctx) == void) @constCast(@ptrCast(&{})) else ctx;

const app = browser.app;
const allocator = app.allocator;
self.* = .{
.app = app,
.ctx = any_ctx,
.env = undefined,
.browser = browser,
.inspector = undefined,
.notify_func = ContextStruct.notify,
.http_client = browser.http_client,
.storage_shed = storage.Shed.init(allocator),
.arena = std.heap.ArenaAllocator.init(allocator),
Expand All @@ -157,24 +176,15 @@ pub const Session = struct {
errdefer self.env.deinit();
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,
any_ctx,
ContextStruct.onInspectorResponse,
ContextStruct.onInspectorEvent,
);
self.env.setInspector(self.inspector);

try self.env.setModuleLoadFn(self, Session.fetchModule);
}

Expand Down Expand Up @@ -269,6 +279,12 @@ pub const Session = struct {
log.debug("inspector context created", .{});
self.inspector.contextCreated(&self.env, "", (page.origin() catch "://") orelse "://", aux_data);
}

fn notify(self: *const Session, notification: *const Notification) void {
self.notify_func(self.ctx, notification) catch |err| {
log.err("notify {}: {}", .{ std.meta.activeTag(notification.*), err });
};
}
};

// Page navigates to an url.
Expand Down Expand Up @@ -344,37 +360,41 @@ pub const Page = struct {
// spec reference: https://html.spec.whatwg.org/#document-lifecycle
// - aux_data: extra data forwarded to the Inspector
// see Inspector.contextCreated
pub fn navigate(self: *Page, url_string: []const u8, aux_data: ?[]const u8) !void {
pub fn navigate(self: *Page, request_url: URL, aux_data: ?[]const u8) !void {
const arena = self.arena;
const session = self.session;

log.debug("starting GET {s}", .{url_string});
log.debug("starting GET {s}", .{request_url});

// if the url is about:blank, nothing to do.
if (std.mem.eql(u8, "about:blank", url_string)) {
if (std.mem.eql(u8, "about:blank", request_url.raw)) {
return;
}

// we don't clone url_string, because we're going to replace self.url
// we don't clone url, because we're going to replace self.url
// later in this function, with the final request url (since we might
// redirect)
self.url = try URL.parse(url_string, "https");
self.session.app.telemetry.record(.{ .navigate = .{
self.url = request_url;
var url = &self.url.?;

session.app.telemetry.record(.{ .navigate = .{
.proxy = false,
.tls = std.ascii.eqlIgnoreCase(self.url.?.scheme(), "https"),
.tls = std.ascii.eqlIgnoreCase(url.scheme(), "https"),
} });

// load the data
var request = try self.newHTTPRequest(.GET, &self.url.?, .{ .navigation = true });
var request = try self.newHTTPRequest(.GET, url, .{ .navigation = true });
defer request.deinit();

session.notify(&.{ .page_navigate = .{ .url = url, .ts = timestamp() } });
var response = try request.sendSync(.{});

// would be different than self.url in the case of a redirect
self.url = try URL.fromURI(arena, request.uri);
url = &self.url.?;

const url = &self.url.?;
const header = response.header;
try self.session.cookie_jar.populateFromResponse(&url.uri, &header);
try session.cookie_jar.populateFromResponse(&url.uri, &header);

// TODO handle fragment in url.
try self.session.window.replaceLocation(.{ .url = try url.toWebApi(arena) });
Expand Down Expand Up @@ -407,6 +427,8 @@ pub const Page = struct {
// save the body into the page.
self.raw_data = arr.items;
}

session.notify(&.{ .page_navigated = .{ .url = url, .ts = timestamp() } });
}

pub const ClickResult = union(enum) {
Expand Down Expand Up @@ -835,7 +857,13 @@ const FlatRenderer = struct {
}
};

const NoopInspector = struct {
const NoopContext = struct {
pub fn onInspectorResponse(_: *anyopaque, _: u32, _: []const u8) void {}
pub fn onInspectorEvent(_: *anyopaque, _: []const u8) void {}
pub fn notify(_: *anyopaque, _: *const Notification) !void {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we could eventually use notifications to send also inspector's events...

};

fn timestamp() u32 {
const ts = std.posix.clock_gettime(std.posix.CLOCK.MONOTONIC) catch unreachable;
return @intCast(ts.sec);
}
29 changes: 23 additions & 6 deletions src/cdp/cdp.zig
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const json = std.json;
const App = @import("../app.zig").App;
const asUint = @import("../str/parser.zig").asUint;
const Incrementing = @import("../id.zig").Incrementing;
const Notification = @import("../notification.zig").Notification;

const log = std.log.scoped(.cdp);

Expand Down Expand Up @@ -248,6 +249,17 @@ pub fn CDPT(comptime TypeProvider: type) type {
return true;
}

const SendEventOpts = struct {
session_id: ?[]const u8 = null,
};
pub fn sendEvent(self: *Self, method: []const u8, p: anytype, opts: SendEventOpts) !void {
return self.sendJSON(.{
.method = method,
.params = if (comptime @typeInfo(@TypeOf(p)) == .null) struct {}{} else p,
.sessionId = opts.session_id,
});
}

fn sendJSON(self: *Self, message: anytype) !void {
return self.client.sendJSON(message, .{
.emit_null_optional_fields = false,
Expand Down Expand Up @@ -338,6 +350,15 @@ pub fn BrowserContext(comptime CDP_T: type) type {
return if (page.url) |*url| url.raw else null;
}

pub fn notify(ctx: *anyopaque, notification: *const Notification) !void {
const self: *Self = @alignCast(@ptrCast(ctx));

switch (notification.*) {
.page_navigate => |*pn| return @import("domains/page.zig").pageNavigate(self, pn),
.page_navigated => |*pn| return @import("domains/page.zig").pageNavigated(self, pn),
}
}

pub fn onInspectorResponse(ctx: *anyopaque, _: u32, msg: []const u8) void {
if (std.log.defaultLogEnabled(.debug)) {
// msg should be {"id":<id>,...
Expand Down Expand Up @@ -472,13 +493,9 @@ pub fn Command(comptime CDP_T: type, comptime Sender: type) type {
const SendEventOpts = struct {
session_id: ?[]const u8 = null,
};
pub fn sendEvent(self: *Self, method: []const u8, p: anytype, opts: SendEventOpts) !void {
pub fn sendEvent(self: *Self, method: []const u8, p: anytype, opts: CDP_T.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,
});
return self.cdp.sendEvent(method, p, opts);
}

pub fn sendError(self: *Self, code: i32, message: []const u8) !void {
Expand Down
Loading