mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 15:13:28 +00:00
Merge pull request #520 from lightpanda-io/navigate_notifications
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / puppeteer-perf (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
wpt / web platform tests (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
Communicate page navigation state via notifications
This commit is contained in:
@@ -40,6 +40,7 @@ const Walker = @import("../dom/walker.zig").WalkerDepthFirst;
|
|||||||
|
|
||||||
const URL = @import("../url.zig").URL;
|
const URL = @import("../url.zig").URL;
|
||||||
const storage = @import("../storage/storage.zig");
|
const storage = @import("../storage/storage.zig");
|
||||||
|
const Notification = @import("../notification.zig").Notification;
|
||||||
|
|
||||||
const http = @import("../http/client.zig");
|
const http = @import("../http/client.zig");
|
||||||
const UserContext = @import("../user_context.zig").UserContext;
|
const UserContext = @import("../user_context.zig").UserContext;
|
||||||
@@ -137,14 +138,32 @@ pub const Session = struct {
|
|||||||
|
|
||||||
jstypes: [Types.len]usize = undefined,
|
jstypes: [Types.len]usize = undefined,
|
||||||
|
|
||||||
|
// recipient of notification, passed as the first parameter to notify
|
||||||
|
notify_ctx: *anyopaque,
|
||||||
|
notify_func: *const fn (ctx: *anyopaque, notification: *const Notification) anyerror!void,
|
||||||
|
|
||||||
fn init(self: *Session, browser: *Browser, ctx: anytype) !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 app = browser.app;
|
||||||
const allocator = app.allocator;
|
const allocator = app.allocator;
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.app = app,
|
.app = app,
|
||||||
.env = undefined,
|
.env = undefined,
|
||||||
.browser = browser,
|
.browser = browser,
|
||||||
|
.notify_ctx = any_ctx,
|
||||||
.inspector = undefined,
|
.inspector = undefined,
|
||||||
|
.notify_func = ContextStruct.notify,
|
||||||
.http_client = browser.http_client,
|
.http_client = browser.http_client,
|
||||||
.storage_shed = storage.Shed.init(allocator),
|
.storage_shed = storage.Shed.init(allocator),
|
||||||
.arena = std.heap.ArenaAllocator.init(allocator),
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
@@ -157,24 +176,15 @@ pub const Session = struct {
|
|||||||
errdefer self.env.deinit();
|
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));
|
// const ctx_opaque = @as(*anyopaque, @ptrCast(ctx));
|
||||||
self.inspector = try jsruntime.Inspector.init(
|
self.inspector = try jsruntime.Inspector.init(
|
||||||
arena,
|
arena,
|
||||||
&self.env,
|
&self.env,
|
||||||
if (@TypeOf(ctx) == void) @constCast(@ptrCast(&{})) else ctx,
|
any_ctx,
|
||||||
InspectorContainer.onInspectorResponse,
|
ContextStruct.onInspectorResponse,
|
||||||
InspectorContainer.onInspectorEvent,
|
ContextStruct.onInspectorEvent,
|
||||||
);
|
);
|
||||||
self.env.setInspector(self.inspector);
|
self.env.setInspector(self.inspector);
|
||||||
|
|
||||||
try self.env.setModuleLoadFn(self, Session.fetchModule);
|
try self.env.setModuleLoadFn(self, Session.fetchModule);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,6 +279,12 @@ pub const Session = struct {
|
|||||||
log.debug("inspector context created", .{});
|
log.debug("inspector context created", .{});
|
||||||
self.inspector.contextCreated(&self.env, "", (page.origin() catch "://") orelse "://", aux_data);
|
self.inspector.contextCreated(&self.env, "", (page.origin() catch "://") orelse "://", aux_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn notify(self: *const Session, notification: *const Notification) void {
|
||||||
|
self.notify_func(self.notify_ctx, notification) catch |err| {
|
||||||
|
log.err("notify {}: {}", .{ std.meta.activeTag(notification.*), err });
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Page navigates to an url.
|
// Page navigates to an url.
|
||||||
@@ -344,37 +360,41 @@ pub const Page = struct {
|
|||||||
// spec reference: https://html.spec.whatwg.org/#document-lifecycle
|
// spec reference: https://html.spec.whatwg.org/#document-lifecycle
|
||||||
// - aux_data: extra data forwarded to the Inspector
|
// - aux_data: extra data forwarded to the Inspector
|
||||||
// see Inspector.contextCreated
|
// 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 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 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;
|
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
|
// later in this function, with the final request url (since we might
|
||||||
// redirect)
|
// redirect)
|
||||||
self.url = try URL.parse(url_string, "https");
|
self.url = request_url;
|
||||||
self.session.app.telemetry.record(.{ .navigate = .{
|
var url = &self.url.?;
|
||||||
|
|
||||||
|
session.app.telemetry.record(.{ .navigate = .{
|
||||||
.proxy = false,
|
.proxy = false,
|
||||||
.tls = std.ascii.eqlIgnoreCase(self.url.?.scheme(), "https"),
|
.tls = std.ascii.eqlIgnoreCase(url.scheme(), "https"),
|
||||||
} });
|
} });
|
||||||
|
|
||||||
// load the data
|
// 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();
|
defer request.deinit();
|
||||||
|
|
||||||
|
session.notify(&.{ .page_navigate = .{ .url = url, .timestamp = timestamp() } });
|
||||||
var response = try request.sendSync(.{});
|
var response = try request.sendSync(.{});
|
||||||
|
|
||||||
// would be different than self.url in the case of a redirect
|
// would be different than self.url in the case of a redirect
|
||||||
self.url = try URL.fromURI(arena, request.uri);
|
self.url = try URL.fromURI(arena, request.uri);
|
||||||
|
url = &self.url.?;
|
||||||
|
|
||||||
const url = &self.url.?;
|
|
||||||
const header = response.header;
|
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.
|
// TODO handle fragment in url.
|
||||||
try self.session.window.replaceLocation(.{ .url = try url.toWebApi(arena) });
|
try self.session.window.replaceLocation(.{ .url = try url.toWebApi(arena) });
|
||||||
@@ -407,6 +427,8 @@ pub const Page = struct {
|
|||||||
// save the body into the page.
|
// save the body into the page.
|
||||||
self.raw_data = arr.items;
|
self.raw_data = arr.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.notify(&.{ .page_navigated = .{ .url = url, .timestamp = timestamp() } });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ClickResult = union(enum) {
|
pub const ClickResult = union(enum) {
|
||||||
@@ -835,7 +857,13 @@ const FlatRenderer = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const NoopInspector = struct {
|
const NoopContext = struct {
|
||||||
pub fn onInspectorResponse(_: *anyopaque, _: u32, _: []const u8) void {}
|
pub fn onInspectorResponse(_: *anyopaque, _: u32, _: []const u8) void {}
|
||||||
pub fn onInspectorEvent(_: *anyopaque, _: []const u8) void {}
|
pub fn onInspectorEvent(_: *anyopaque, _: []const u8) void {}
|
||||||
|
pub fn notify(_: *anyopaque, _: *const Notification) !void {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn timestamp() u32 {
|
||||||
|
const ts = std.posix.clock_gettime(std.posix.CLOCK.MONOTONIC) catch unreachable;
|
||||||
|
return @intCast(ts.sec);
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const json = std.json;
|
|||||||
const App = @import("../app.zig").App;
|
const App = @import("../app.zig").App;
|
||||||
const asUint = @import("../str/parser.zig").asUint;
|
const asUint = @import("../str/parser.zig").asUint;
|
||||||
const Incrementing = @import("../id.zig").Incrementing;
|
const Incrementing = @import("../id.zig").Incrementing;
|
||||||
|
const Notification = @import("../notification.zig").Notification;
|
||||||
|
|
||||||
const log = std.log.scoped(.cdp);
|
const log = std.log.scoped(.cdp);
|
||||||
|
|
||||||
@@ -248,6 +249,17 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
return true;
|
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 {
|
fn sendJSON(self: *Self, message: anytype) !void {
|
||||||
return self.client.sendJSON(message, .{
|
return self.client.sendJSON(message, .{
|
||||||
.emit_null_optional_fields = false,
|
.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;
|
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 {
|
pub fn onInspectorResponse(ctx: *anyopaque, _: u32, msg: []const u8) void {
|
||||||
if (std.log.defaultLogEnabled(.debug)) {
|
if (std.log.defaultLogEnabled(.debug)) {
|
||||||
// msg should be {"id":<id>,...
|
// msg should be {"id":<id>,...
|
||||||
@@ -472,13 +493,9 @@ pub fn Command(comptime CDP_T: type, comptime Sender: type) type {
|
|||||||
const SendEventOpts = struct {
|
const SendEventOpts = struct {
|
||||||
session_id: ?[]const u8 = null,
|
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
|
// Events ALWAYS go to the client. self.sender should not be used
|
||||||
return self.cdp.sendJSON(.{
|
return self.cdp.sendEvent(method, p, opts);
|
||||||
.method = method,
|
|
||||||
.params = if (comptime @typeInfo(@TypeOf(p)) == .null) struct {}{} else p,
|
|
||||||
.sessionId = opts.session_id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendError(self: *Self, code: i32, message: []const u8) !void {
|
pub fn sendError(self: *Self, code: i32, message: []const u8) !void {
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const runtime = @import("runtime.zig");
|
const runtime = @import("runtime.zig");
|
||||||
|
const URL = @import("../../url.zig").URL;
|
||||||
|
const Notification = @import("../../notification.zig").Notification;
|
||||||
|
|
||||||
pub fn processMessage(cmd: anytype) !void {
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
const action = std.meta.stringToEnum(enum {
|
const action = std.meta.stringToEnum(enum {
|
||||||
@@ -137,62 +139,17 @@ fn navigate(cmd: anytype) !void {
|
|||||||
// referrerPolicy: ?[]const u8 = null, // TODO: enum
|
// referrerPolicy: ?[]const u8 = null, // TODO: enum
|
||||||
})) orelse return error.InvalidParams;
|
})) 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;
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
|
|
||||||
// didn't create?
|
// didn't create?
|
||||||
const target_id = bc.target_id orelse return error.TargetIdNotLoaded;
|
const target_id = bc.target_id orelse return error.TargetIdNotLoaded;
|
||||||
|
|
||||||
// didn't attach?
|
// didn't attach?
|
||||||
const session_id = bc.session_id orelse return error.SessionIdNotLoaded;
|
if (bc.session_id == null) {
|
||||||
|
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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// output
|
const url = try URL.parse(params.url, "https");
|
||||||
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 aux_data = try std.fmt.allocPrint(
|
const aux_data = try std.fmt.allocPrint(
|
||||||
cmd.arena,
|
cmd.arena,
|
||||||
@@ -202,70 +159,128 @@ pub fn navigateToUrl(cmd: anytype, url: []const u8, send_result: bool) !void {
|
|||||||
);
|
);
|
||||||
|
|
||||||
var page = bc.session.currentPage().?;
|
var page = bc.session.currentPage().?;
|
||||||
|
bc.loader_id = bc.cdp.loader_id_gen.next();
|
||||||
|
try cmd.sendResult(.{
|
||||||
|
.frameId = target_id,
|
||||||
|
.loaderId = bc.loader_id,
|
||||||
|
}, .{});
|
||||||
|
|
||||||
try page.navigate(url, aux_data);
|
try page.navigate(url, aux_data);
|
||||||
|
}
|
||||||
|
|
||||||
// Events
|
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();
|
||||||
|
|
||||||
|
// frameStartedNavigating event
|
||||||
|
try cdp.sendEvent("Page.frameStartedNavigating", .{
|
||||||
|
.frameId = target_id,
|
||||||
|
.url = event.url.raw,
|
||||||
|
.loaderId = loader_id,
|
||||||
|
.navigationType = "differentDocument",
|
||||||
|
}, .{ .session_id = session_id });
|
||||||
|
|
||||||
|
// 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) {
|
if (bc.page_life_cycle_events) {
|
||||||
life_event.name = "init";
|
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
|
||||||
life_event.timestamp = 343721.796037;
|
.name = "init",
|
||||||
try cmd.sendEvent("Page.lifecycleEvent", life_event, .{ .session_id = session_id });
|
.frameId = target_id,
|
||||||
|
.loaderId = loader_id,
|
||||||
|
.timestamp = event.timestamp,
|
||||||
|
}, .{ .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 timestamp = event.timestamp;
|
||||||
|
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
|
// frameNavigated event
|
||||||
try cmd.sendEvent("Page.frameNavigated", .{
|
try cdp.sendEvent("Page.frameNavigated", .{
|
||||||
.type = "Navigation",
|
.type = "Navigation",
|
||||||
.frame = Frame{
|
.frame = Frame{
|
||||||
.id = target_id,
|
.id = target_id,
|
||||||
.url = url,
|
.url = event.url.raw,
|
||||||
|
.loaderId = bc.loader_id,
|
||||||
.securityOrigin = bc.security_origin,
|
.securityOrigin = bc.security_origin,
|
||||||
.secureContextType = bc.secure_context_type,
|
.secureContextType = bc.secure_context_type,
|
||||||
.loaderId = bc.loader_id,
|
|
||||||
},
|
},
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
|
|
||||||
// domContentEventFired event
|
// domContentEventFired event
|
||||||
// TODO: partially hard coded
|
// TODO: partially hard coded
|
||||||
try cmd.sendEvent(
|
try cdp.sendEvent(
|
||||||
"Page.domContentEventFired",
|
"Page.domContentEventFired",
|
||||||
.{ .timestamp = 343721.803338 },
|
.{ .timestamp = timestamp },
|
||||||
.{ .session_id = session_id },
|
.{ .session_id = session_id },
|
||||||
);
|
);
|
||||||
|
|
||||||
// lifecycle DOMContentLoaded event
|
// lifecycle DOMContentLoaded event
|
||||||
// TODO: partially hard coded
|
// TODO: partially hard coded
|
||||||
if (bc.page_life_cycle_events) {
|
if (bc.page_life_cycle_events) {
|
||||||
life_event.name = "DOMContentLoaded";
|
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
|
||||||
life_event.timestamp = 343721.803338;
|
.timestamp = timestamp,
|
||||||
try cmd.sendEvent("Page.lifecycleEvent", life_event, .{ .session_id = session_id });
|
.name = "DOMContentLoaded",
|
||||||
|
.frameId = target_id,
|
||||||
|
.loaderId = loader_id,
|
||||||
|
}, .{ .session_id = session_id });
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadEventFired event
|
// loadEventFired event
|
||||||
// TODO: partially hard coded
|
try cdp.sendEvent(
|
||||||
try cmd.sendEvent(
|
|
||||||
"Page.loadEventFired",
|
"Page.loadEventFired",
|
||||||
.{ .timestamp = 343721.824655 },
|
.{ .timestamp = timestamp },
|
||||||
.{ .session_id = session_id },
|
.{ .session_id = session_id },
|
||||||
);
|
);
|
||||||
|
|
||||||
// lifecycle DOMContentLoaded event
|
// lifecycle DOMContentLoaded event
|
||||||
// TODO: partially hard coded
|
|
||||||
if (bc.page_life_cycle_events) {
|
if (bc.page_life_cycle_events) {
|
||||||
life_event.name = "load";
|
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
|
||||||
life_event.timestamp = 343721.824655;
|
.timestamp = timestamp,
|
||||||
try cmd.sendEvent("Page.lifecycleEvent", life_event, .{ .session_id = session_id });
|
.name = "load",
|
||||||
|
.frameId = target_id,
|
||||||
|
.loaderId = loader_id,
|
||||||
|
}, .{ .session_id = session_id });
|
||||||
}
|
}
|
||||||
|
|
||||||
// frameStoppedLoading
|
// frameStoppedLoading
|
||||||
return cmd.sendEvent("Page.frameStoppedLoading", .{
|
return cdp.sendEvent("Page.frameStoppedLoading", .{
|
||||||
.frameId = target_id,
|
.frameId = target_id,
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LifecycleEvent = struct {
|
||||||
|
frameId: []const u8,
|
||||||
|
loaderId: ?[]const u8,
|
||||||
|
name: []const u8,
|
||||||
|
timestamp: u32,
|
||||||
|
};
|
||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
test "cdp.page: getFrameTree" {
|
test "cdp.page: getFrameTree" {
|
||||||
var ctx = testing.context();
|
var ctx = testing.context();
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ const Page = struct {
|
|||||||
aux_data: []const u8 = "",
|
aux_data: []const u8 = "",
|
||||||
doc: ?*parser.Document = null,
|
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;
|
_ = url;
|
||||||
_ = aux_data;
|
_ = aux_data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ pub fn main() !void {
|
|||||||
},
|
},
|
||||||
.fetch => |opts| {
|
.fetch => |opts| {
|
||||||
log.debug("Fetch mode: url {s}, dump {any}", .{ opts.url, opts.dump });
|
log.debug("Fetch mode: url {s}, dump {any}", .{ opts.url, opts.dump });
|
||||||
|
const url = try @import("url.zig").URL.parse(opts.url, null);
|
||||||
|
|
||||||
var app = try App.init(alloc, .{
|
var app = try App.init(alloc, .{
|
||||||
.run_mode = args.mode,
|
.run_mode = args.mode,
|
||||||
@@ -107,13 +108,13 @@ pub fn main() !void {
|
|||||||
// page
|
// page
|
||||||
const page = try session.createPage(null);
|
const page = try session.createPage(null);
|
||||||
|
|
||||||
_ = page.navigate(opts.url, null) catch |err| switch (err) {
|
_ = page.navigate(url, null) catch |err| switch (err) {
|
||||||
error.UnsupportedUriScheme, error.UriMissingHost => {
|
error.UnsupportedUriScheme, error.UriMissingHost => {
|
||||||
log.err("'{s}' is not a valid URL ({any})\n", .{ opts.url, err });
|
log.err("'{s}' is not a valid URL ({any})\n", .{ url, err });
|
||||||
return args.printUsageAndExit(false);
|
return args.printUsageAndExit(false);
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
log.err("'{s}' fetching error ({any})\n", .{ opts.url, err });
|
log.err("'{s}' fetching error ({any})\n", .{ url, err });
|
||||||
return err;
|
return err;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
11
src/notification.zig
Normal file
11
src/notification.zig
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const URL = @import("url.zig").URL;
|
||||||
|
|
||||||
|
pub const Notification = union(enum) {
|
||||||
|
page_navigate: PageEvent,
|
||||||
|
page_navigated: PageEvent,
|
||||||
|
|
||||||
|
pub const PageEvent = struct {
|
||||||
|
timestamp: u32,
|
||||||
|
url: *const URL,
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user