mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-30 15:41:48 +00:00
Communicate page navigation state via notifications
In order to support click handling on anchors from JavaScript, we need some hook from the page/session to the CDP instance. This first phase adds notifications in page.navigate, as well as a primitive notification hook to the session. CDP's existing Page.navigate uses this new notifiation system.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
@@ -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>,...
|
||||
@@ -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 {
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
const std = @import("std");
|
||||
const runtime = @import("runtime.zig");
|
||||
const URL = @import("../../url.zig").URL;
|
||||
const Notification = @import("../../notification.zig").Notification;
|
||||
|
||||
pub fn processMessage(cmd: anytype) !void {
|
||||
const action = std.meta.stringToEnum(enum {
|
||||
@@ -137,62 +139,17 @@ fn navigate(cmd: anytype) !void {
|
||||
// referrerPolicy: ?[]const u8 = null, // TODO: enum
|
||||
})) orelse return error.InvalidParams;
|
||||
|
||||
return navigateToUrl(cmd, params.url, true);
|
||||
}
|
||||
|
||||
pub fn navigateToUrl(cmd: anytype, url: []const u8, send_result: bool) !void {
|
||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||
|
||||
// didn't create?
|
||||
const target_id = bc.target_id orelse return error.TargetIdNotLoaded;
|
||||
|
||||
// didn't attach?
|
||||
const session_id = bc.session_id orelse return error.SessionIdNotLoaded;
|
||||
|
||||
// if we have a target_id we have to have a page;
|
||||
std.debug.assert(bc.session.page != null);
|
||||
|
||||
// change state
|
||||
bc.reset();
|
||||
bc.loader_id = cmd.cdp.loader_id_gen.next();
|
||||
|
||||
const LifecycleEvent = struct {
|
||||
frameId: []const u8,
|
||||
loaderId: ?[]const u8,
|
||||
name: []const u8,
|
||||
timestamp: f32,
|
||||
};
|
||||
|
||||
var life_event = LifecycleEvent{
|
||||
.frameId = target_id,
|
||||
.loaderId = bc.loader_id,
|
||||
.name = "init",
|
||||
.timestamp = 343721.796037,
|
||||
};
|
||||
|
||||
// frameStartedLoading event
|
||||
// TODO: event partially hard coded
|
||||
try cmd.sendEvent("Page.frameStartedLoading", .{
|
||||
.frameId = target_id,
|
||||
}, .{ .session_id = session_id });
|
||||
|
||||
if (bc.page_life_cycle_events) {
|
||||
try cmd.sendEvent("Page.lifecycleEvent", life_event, .{ .session_id = session_id });
|
||||
if (bc.session_id == null) {
|
||||
return error.SessionIdNotLoaded;
|
||||
}
|
||||
|
||||
// output
|
||||
if (send_result) {
|
||||
try cmd.sendResult(.{
|
||||
.frameId = target_id,
|
||||
.loaderId = bc.loader_id,
|
||||
}, .{});
|
||||
}
|
||||
|
||||
// TODO: at this point do we need async the following actions to be async?
|
||||
|
||||
// Send Runtime.executionContextsCleared event
|
||||
// TODO: noop event, we have no env context at this point, is it necesarry?
|
||||
try cmd.sendEvent("Runtime.executionContextsCleared", null, .{ .session_id = session_id });
|
||||
const url = try URL.parse(params.url, "https");
|
||||
|
||||
const aux_data = try std.fmt.allocPrint(
|
||||
cmd.arena,
|
||||
@@ -204,68 +161,118 @@ pub fn navigateToUrl(cmd: anytype, url: []const u8, send_result: bool) !void {
|
||||
var page = bc.session.currentPage().?;
|
||||
try page.navigate(url, aux_data);
|
||||
|
||||
// Events
|
||||
bc.loader_id = bc.cdp.loader_id_gen.next();
|
||||
try cmd.sendResult(.{
|
||||
.frameId = target_id,
|
||||
.loaderId = bc.loader_id,
|
||||
}, .{});
|
||||
}
|
||||
|
||||
pub fn pageNavigate(bc: anytype, event: *const Notification.PageEvent) !void {
|
||||
// I don't think it's possible that we get these notifications and don't
|
||||
// have these things setup.
|
||||
std.debug.assert(bc.session.page != null);
|
||||
|
||||
var cdp = bc.cdp;
|
||||
const loader_id = bc.loader_id;
|
||||
const target_id = bc.target_id orelse unreachable;
|
||||
const session_id = bc.session_id orelse unreachable;
|
||||
|
||||
bc.reset();
|
||||
|
||||
// frameStartedLoading event
|
||||
try cdp.sendEvent("Page.frameStartedLoading", .{
|
||||
.frameId = target_id,
|
||||
}, .{ .session_id = session_id });
|
||||
|
||||
// lifecycle init event
|
||||
// TODO: partially hard coded
|
||||
if (bc.page_life_cycle_events) {
|
||||
life_event.name = "init";
|
||||
life_event.timestamp = 343721.796037;
|
||||
try cmd.sendEvent("Page.lifecycleEvent", life_event, .{ .session_id = session_id });
|
||||
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
|
||||
.name = "init",
|
||||
.frameId = target_id,
|
||||
.loaderId = loader_id,
|
||||
.timestamp = event.ts,
|
||||
}, .{ .session_id = session_id });
|
||||
}
|
||||
|
||||
try cmd.sendEvent("DOM.documentUpdated", null, .{ .session_id = session_id });
|
||||
// Send Runtime.executionContextsCleared event
|
||||
// TODO: noop event, we have no env context at this point, is it necesarry?
|
||||
try cdp.sendEvent("Runtime.executionContextsCleared", null, .{ .session_id = session_id });
|
||||
}
|
||||
|
||||
pub fn pageNavigated(bc: anytype, event: *const Notification.PageEvent) !void {
|
||||
// I don't think it's possible that we get these notifications and don't
|
||||
// have these things setup.
|
||||
std.debug.assert(bc.session.page != null);
|
||||
|
||||
var cdp = bc.cdp;
|
||||
const ts = event.ts;
|
||||
const loader_id = bc.loader_id;
|
||||
const target_id = bc.target_id orelse unreachable;
|
||||
const session_id = bc.session_id orelse unreachable;
|
||||
|
||||
try cdp.sendEvent("DOM.documentUpdated", null, .{ .session_id = session_id });
|
||||
|
||||
// frameNavigated event
|
||||
try cmd.sendEvent("Page.frameNavigated", .{
|
||||
try cdp.sendEvent("Page.frameNavigated", .{
|
||||
.type = "Navigation",
|
||||
.frame = Frame{
|
||||
.id = target_id,
|
||||
.url = url,
|
||||
.url = event.url.raw,
|
||||
.loaderId = bc.loader_id,
|
||||
.securityOrigin = bc.security_origin,
|
||||
.secureContextType = bc.secure_context_type,
|
||||
.loaderId = bc.loader_id,
|
||||
},
|
||||
}, .{ .session_id = session_id });
|
||||
|
||||
// domContentEventFired event
|
||||
// TODO: partially hard coded
|
||||
try cmd.sendEvent(
|
||||
try cdp.sendEvent(
|
||||
"Page.domContentEventFired",
|
||||
.{ .timestamp = 343721.803338 },
|
||||
.{ .timestamp = ts },
|
||||
.{ .session_id = session_id },
|
||||
);
|
||||
|
||||
// lifecycle DOMContentLoaded event
|
||||
// TODO: partially hard coded
|
||||
if (bc.page_life_cycle_events) {
|
||||
life_event.name = "DOMContentLoaded";
|
||||
life_event.timestamp = 343721.803338;
|
||||
try cmd.sendEvent("Page.lifecycleEvent", life_event, .{ .session_id = session_id });
|
||||
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
|
||||
.timestamp = ts,
|
||||
.name = "DOMContentLoaded",
|
||||
.frameId = target_id,
|
||||
.loaderId = loader_id,
|
||||
}, .{ .session_id = session_id });
|
||||
}
|
||||
|
||||
// loadEventFired event
|
||||
// TODO: partially hard coded
|
||||
try cmd.sendEvent(
|
||||
try cdp.sendEvent(
|
||||
"Page.loadEventFired",
|
||||
.{ .timestamp = 343721.824655 },
|
||||
.{ .timestamp = ts },
|
||||
.{ .session_id = session_id },
|
||||
);
|
||||
|
||||
// lifecycle DOMContentLoaded event
|
||||
// TODO: partially hard coded
|
||||
if (bc.page_life_cycle_events) {
|
||||
life_event.name = "load";
|
||||
life_event.timestamp = 343721.824655;
|
||||
try cmd.sendEvent("Page.lifecycleEvent", life_event, .{ .session_id = session_id });
|
||||
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
|
||||
.timestamp = ts,
|
||||
.name = "load",
|
||||
.frameId = target_id,
|
||||
.loaderId = loader_id,
|
||||
}, .{ .session_id = session_id });
|
||||
}
|
||||
|
||||
// frameStoppedLoading
|
||||
return cmd.sendEvent("Page.frameStoppedLoading", .{
|
||||
return cdp.sendEvent("Page.frameStoppedLoading", .{
|
||||
.frameId = target_id,
|
||||
}, .{ .session_id = session_id });
|
||||
}
|
||||
|
||||
const LifecycleEvent = struct {
|
||||
frameId: []const u8,
|
||||
loaderId: ?[]const u8,
|
||||
name: []const u8,
|
||||
timestamp: u32,
|
||||
};
|
||||
|
||||
const testing = @import("../testing.zig");
|
||||
test "cdp.page: getFrameTree" {
|
||||
var ctx = testing.context();
|
||||
|
||||
@@ -161,7 +161,7 @@ const Page = struct {
|
||||
aux_data: []const u8 = "",
|
||||
doc: ?*parser.Document = null,
|
||||
|
||||
pub fn navigate(_: *Page, url: []const u8, aux_data: []const u8) !void {
|
||||
pub fn navigate(_: *Page, url: URL, aux_data: []const u8) !void {
|
||||
_ = url;
|
||||
_ = aux_data;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user