diff --git a/src/Notification.zig b/src/Notification.zig
index 11d9c74d..91fcb673 100644
--- a/src/Notification.zig
+++ b/src/Notification.zig
@@ -102,24 +102,30 @@ const EventType = std.meta.FieldEnum(Events);
pub const PageRemove = struct {};
pub const PageNavigate = struct {
- req_id: usize,
+ req_id: u32,
+ page_id: u32,
timestamp: u64,
url: [:0]const u8,
opts: Page.NavigateOpts,
};
pub const PageNavigated = struct {
- req_id: usize,
+ req_id: u32,
+ page_id: u32,
timestamp: u64,
url: [:0]const u8,
opts: Page.NavigatedOpts,
};
pub const PageNetworkIdle = struct {
+ req_id: u32,
+ page_id: u32,
timestamp: u64,
};
pub const PageNetworkAlmostIdle = struct {
+ req_id: u32,
+ page_id: u32,
timestamp: u64,
};
@@ -305,6 +311,7 @@ test "Notification" {
// noop
notifier.dispatch(.page_navigate, &.{
+ .page_id = 0,
.req_id = 1,
.timestamp = 4,
.url = undefined,
@@ -315,6 +322,7 @@ test "Notification" {
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
notifier.dispatch(.page_navigate, &.{
+ .page_id = 0,
.req_id = 1,
.timestamp = 4,
.url = undefined,
@@ -324,6 +332,7 @@ test "Notification" {
notifier.unregisterAll(&tc);
notifier.dispatch(.page_navigate, &.{
+ .page_id = 0,
.req_id = 1,
.timestamp = 10,
.url = undefined,
@@ -334,23 +343,25 @@ test "Notification" {
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
notifier.dispatch(.page_navigate, &.{
+ .page_id = 0,
.req_id = 1,
.timestamp = 10,
.url = undefined,
.opts = .{},
});
- notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 6, .url = undefined, .opts = .{} });
+ notifier.dispatch(.page_navigated, &.{ .page_id = 0, .req_id = 1, .timestamp = 6, .url = undefined, .opts = .{} });
try testing.expectEqual(14, tc.page_navigate);
try testing.expectEqual(6, tc.page_navigated);
notifier.unregisterAll(&tc);
notifier.dispatch(.page_navigate, &.{
+ .page_id = 0,
.req_id = 1,
.timestamp = 100,
.url = undefined,
.opts = .{},
});
- notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
+ notifier.dispatch(.page_navigated, &.{ .page_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
try testing.expectEqual(14, tc.page_navigate);
try testing.expectEqual(6, tc.page_navigated);
@@ -358,27 +369,27 @@ test "Notification" {
// unregister
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
- notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
- notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
+ notifier.dispatch(.page_navigate, &.{ .page_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
+ notifier.dispatch(.page_navigated, &.{ .page_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(1006, tc.page_navigated);
notifier.unregister(.page_navigate, &tc);
- notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
- notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
+ notifier.dispatch(.page_navigate, &.{ .page_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
+ notifier.dispatch(.page_navigated, &.{ .page_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(2006, tc.page_navigated);
notifier.unregister(.page_navigated, &tc);
- notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
- notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
+ notifier.dispatch(.page_navigate, &.{ .page_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
+ notifier.dispatch(.page_navigated, &.{ .page_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(2006, tc.page_navigated);
// already unregistered, try anyways
notifier.unregister(.page_navigated, &tc);
- notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
- notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
+ notifier.dispatch(.page_navigate, &.{ .page_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
+ notifier.dispatch(.page_navigated, &.{ .page_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(2006, tc.page_navigated);
}
diff --git a/src/browser/Page.zig b/src/browser/Page.zig
index a1433fbb..a562c65b 100644
--- a/src/browser/Page.zig
+++ b/src/browser/Page.zig
@@ -79,6 +79,10 @@ pub const BUF_SIZE = 1024;
const Page = @This();
+// This is the "id" of the frame. It can be re-used from page-to-page, e.g.
+// when navigating.
+id: u32,
+
_session: *Session,
_event_manager: EventManager,
@@ -218,10 +222,10 @@ document: *Document,
// DOM version used to invalidate cached state of "live" collections
version: usize = 0,
-_req_id: ?usize = null,
+_req_id: u32 = 0,
_navigated_options: ?NavigatedOpts = null,
-pub fn init(self: *Page, session: *Session) !void {
+pub fn init(self: *Page, id: u32, session: *Session) !void {
if (comptime IS_DEBUG) {
log.debug(.page, "page.init", .{});
}
@@ -240,6 +244,7 @@ pub fn init(self: *Page, session: *Session) !void {
})).asDocument();
self.* = .{
+ .id = id,
.js = undefined,
.arena = page_arena,
.document = document,
@@ -413,7 +418,8 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
// This assumption may be false when CDP Page.addScriptToEvaluateOnNewDocument is implemented
self.documentIsComplete();
- self._session.notification.dispatch(.page_navigate, &.{
+ session.notification.dispatch(.page_navigate, &.{
+ .page_id = self.id,
.req_id = req_id,
.opts = opts,
.url = request_url,
@@ -421,14 +427,15 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
});
// Record telemetry for navigation
- self._session.browser.app.telemetry.record(.{
+ session.browser.app.telemetry.record(.{
.navigate = .{
.tls = false, // about:blank is not TLS
- .proxy = self._session.browser.app.config.httpProxy() != null,
+ .proxy = session.browser.app.config.httpProxy() != null,
},
});
- self._session.notification.dispatch(.page_navigated, &.{
+ session.notification.dispatch(.page_navigated, &.{
+ .page_id = self.id,
.req_id = req_id,
.opts = .{
.cdp_id = opts.cdp_id,
@@ -440,11 +447,11 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
});
// force next request id manually b/c we won't create a real req.
- _ = self._session.browser.http_client.incrReqId();
+ _ = session.browser.http_client.incrReqId();
return;
}
- var http_client = self._session.browser.http_client;
+ var http_client = session.browser.http_client;
self.url = try self.arena.dupeZ(u8, request_url);
@@ -463,7 +470,8 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
// We dispatch page_navigate event before sending the request.
// It ensures the event page_navigated is not dispatched before this one.
- self._session.notification.dispatch(.page_navigate, &.{
+ session.notification.dispatch(.page_navigate, &.{
+ .page_id = self.id,
.req_id = req_id,
.opts = opts,
.url = self.url,
@@ -471,9 +479,9 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
});
// Record telemetry for navigation
- self._session.browser.app.telemetry.record(.{ .navigate = .{
+ session.browser.app.telemetry.record(.{ .navigate = .{
.tls = std.ascii.startsWithIgnoreCase(self.url, "https://"),
- .proxy = self._session.browser.app.config.httpProxy() != null,
+ .proxy = session.browser.app.config.httpProxy() != null,
} });
session.navigation._current_navigation_kind = opts.kind;
@@ -481,10 +489,11 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
http_client.request(.{
.ctx = self,
.url = self.url,
+ .page_id = self.id,
.method = opts.method,
.headers = headers,
.body = opts.body,
- .cookie_jar = &self._session.cookie_jar,
+ .cookie_jar = &session.cookie_jar,
.resource_type = .document,
.notification = self._session.notification,
.header_callback = pageHeaderDoneCallback,
@@ -611,12 +620,12 @@ pub fn documentIsComplete(self: *Page) void {
};
if (IS_DEBUG) {
- std.debug.assert(self._req_id != null);
std.debug.assert(self._navigated_options != null);
}
self._session.notification.dispatch(.page_navigated, &.{
- .req_id = self._req_id.?,
+ .page_id = self.id,
+ .req_id = self._req_id,
.opts = self._navigated_options.?,
.url = self.url,
.timestamp = timestamp(.monotonic),
@@ -1422,6 +1431,8 @@ pub fn deliverSlotchangeEvents(self: *Page) void {
fn notifyNetworkIdle(self: *Page) void {
lp.assert(self._notified_network_idle == .done, "Page.notifyNetworkIdle", .{});
self._session.notification.dispatch(.page_network_idle, &.{
+ .page_id = self.id,
+ .req_id = self._req_id,
.timestamp = timestamp(.monotonic),
});
}
@@ -1429,6 +1440,8 @@ fn notifyNetworkIdle(self: *Page) void {
fn notifyNetworkAlmostIdle(self: *Page) void {
lp.assert(self._notified_network_almost_idle == .done, "Page.notifyNetworkAlmostIdle", .{});
self._session.notification.dispatch(.page_network_almost_idle, &.{
+ .page_id = self.id,
+ .req_id = self._req_id,
.timestamp = timestamp(.monotonic),
});
}
diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig
index 4bfec58a..570ef743 100644
--- a/src/browser/ScriptManager.zig
+++ b/src/browser/ScriptManager.zig
@@ -259,6 +259,7 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
.url = url,
.ctx = script,
.method = .GET,
+ .page_id = page.id,
.headers = try self.getHeaders(url),
.blocking = is_blocking,
.cookie_jar = &page._session.cookie_jar,
@@ -358,9 +359,11 @@ pub fn preloadImport(self: *ScriptManager, url: [:0]const u8, referrer: []const
.manager = self,
};
+ const page = self.page;
+
if (comptime IS_DEBUG) {
var ls: js.Local.Scope = undefined;
- self.page.js.localScope(&ls);
+ page.js.localScope(&ls);
defer ls.deinit();
log.debug(.http, "script queue", .{
@@ -375,10 +378,11 @@ pub fn preloadImport(self: *ScriptManager, url: [:0]const u8, referrer: []const
.url = url,
.ctx = script,
.method = .GET,
+ .page_id = page.id,
.headers = try self.getHeaders(url),
- .cookie_jar = &self.page._session.cookie_jar,
+ .cookie_jar = &page._session.cookie_jar,
.resource_type = .script,
- .notification = self.page._session.notification,
+ .notification = page._session.notification,
.start_callback = if (log.enabled(.http, .debug)) Script.startCallback else null,
.header_callback = Script.headerCallback,
.data_callback = Script.dataCallback,
@@ -451,9 +455,10 @@ pub fn getAsyncImport(self: *ScriptManager, url: [:0]const u8, cb: ImportAsync.C
} },
};
+ const page = self.page;
if (comptime IS_DEBUG) {
var ls: js.Local.Scope = undefined;
- self.page.js.localScope(&ls);
+ page.js.localScope(&ls);
defer ls.deinit();
log.debug(.http, "script queue", .{
@@ -476,11 +481,12 @@ pub fn getAsyncImport(self: *ScriptManager, url: [:0]const u8, cb: ImportAsync.C
try self.client.request(.{
.url = url,
.method = .GET,
+ .page_id = page.id,
.headers = try self.getHeaders(url),
.ctx = script,
.resource_type = .script,
- .cookie_jar = &self.page._session.cookie_jar,
- .notification = self.page._session.notification,
+ .cookie_jar = &page._session.cookie_jar,
+ .notification = page._session.notification,
.start_callback = if (log.enabled(.http, .debug)) Script.startCallback else null,
.header_callback = Script.headerCallback,
.data_callback = Script.dataCallback,
diff --git a/src/browser/Session.zig b/src/browser/Session.zig
index aa1f8de7..2d0f87ce 100644
--- a/src/browser/Session.zig
+++ b/src/browser/Session.zig
@@ -63,6 +63,8 @@ navigation: Navigation,
page: ?Page,
+page_id_gen: u32,
+
pub fn init(self: *Session, browser: *Browser, notification: *Notification) !void {
const allocator = browser.app.allocator;
const arena = try browser.arena_pool.acquire();
@@ -75,6 +77,7 @@ pub fn init(self: *Session, browser: *Browser, notification: *Notification) !voi
.page = null,
.arena = arena,
.history = .{},
+ .page_id_gen = 0,
// The prototype (EventTarget) for Navigation is created when a Page is created.
.navigation = .{ ._proto = undefined },
.storage_shed = .{},
@@ -104,7 +107,11 @@ pub fn createPage(self: *Session) !*Page {
self.page = @as(Page, undefined);
const page = &self.page.?;
- try Page.init(page, self);
+
+ const id = self.page_id_gen +% 1;
+ self.page_id_gen = id;
+
+ try Page.init(page, id, self);
// Creates a new NavigationEventTarget for this page.
try self.navigation.onNewPage(page);
@@ -140,12 +147,16 @@ pub fn replacePage(self: *Session) !*Page {
}
lp.assert(self.page != null, "Session.replacePage null page", .{});
- self.page.?.deinit();
+
+ var current = self.page.?;
+ const page_id = current.id;
+ current.deinit();
+
self.browser.env.memoryPressureNotification(.moderate);
self.page = @as(Page, undefined);
const page = &self.page.?;
- try Page.init(page, self);
+ try Page.init(page, page_id, self);
return page;
}
@@ -159,6 +170,11 @@ pub const WaitResult = enum {
cdp_socket,
};
+pub fn findPage(self: *Session, id: u32) ?*Page {
+ const page = self.currentPage() orelse return null;
+ return if (page.id == id) page else null;
+}
+
pub fn wait(self: *Session, wait_ms: u32) WaitResult {
while (true) {
if (self.page) |*page| {
@@ -181,8 +197,9 @@ pub fn wait(self: *Session, wait_ms: u32) WaitResult {
fn processScheduledNavigation(self: *Session) !void {
defer self.browser.arena_pool.reset(self.transfer_arena, 4 * 1024);
- const url, const opts = blk: {
- const qn = self.page.?._queued_navigation.?;
+ const url, const opts, const page_id = blk: {
+ const page = self.page.?;
+ const qn = page._queued_navigation.?;
// qn might not be safe to use after self.removePage is called, hence
// this block;
const url = qn.url;
@@ -193,7 +210,7 @@ fn processScheduledNavigation(self: *Session) !void {
self.browser.http_client.abort();
self.removePage();
- break :blk .{ url, opts };
+ break :blk .{ url, opts, page.id };
};
const page = self.createPage() catch |err| {
@@ -203,6 +220,7 @@ fn processScheduledNavigation(self: *Session) !void {
});
return err;
};
+ page.id = page_id;
page.navigate(url, opts) catch |err| {
log.err(.browser, "queued navigation error", .{ .err = err, .url = url });
diff --git a/src/browser/webapi/net/Fetch.zig b/src/browser/webapi/net/Fetch.zig
index 2eea37e3..47fa8bc5 100644
--- a/src/browser/webapi/net/Fetch.zig
+++ b/src/browser/webapi/net/Fetch.zig
@@ -72,6 +72,7 @@ pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise {
try http_client.request(.{
.ctx = fetch,
+ .page_id = page.id,
.url = request._url,
.method = request._method,
.body = request._body,
diff --git a/src/browser/webapi/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig
index 9044eec9..e39e8ce4 100644
--- a/src/browser/webapi/net/XMLHttpRequest.zig
+++ b/src/browser/webapi/net/XMLHttpRequest.zig
@@ -223,6 +223,7 @@ pub fn send(self: *XMLHttpRequest, body_: ?[]const u8) !void {
try http_client.request(.{
.ctx = self,
.url = self._url,
+ .page_id = page.id,
.method = self._method,
.headers = headers,
.body = self._request_body,
diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig
index cb4cce92..f7a3dd88 100644
--- a/src/cdp/cdp.zig
+++ b/src/cdp/cdp.zig
@@ -30,12 +30,11 @@ const Browser = @import("../browser/Browser.zig");
const Session = @import("../browser/Session.zig");
const HttpClient = @import("../http/Client.zig");
const Page = @import("../browser/Page.zig");
-const Incrementing = @import("../id.zig").Incrementing;
+const Incrementing = @import("id.zig").Incrementing;
const Notification = @import("../Notification.zig");
const InterceptState = @import("domains/fetch.zig").InterceptState;
pub const URL_BASE = "chrome://newtab/";
-pub const LOADER_ID = "LOADERID24DD2FD56CF1EF33C965C79C";
const IS_DEBUG = @import("builtin").mode == .Debug;
@@ -45,7 +44,6 @@ pub const CDP = CDPT(struct {
const SessionIdGen = Incrementing(u32, "SID");
const TargetIdGen = Incrementing(u32, "TID");
-const LoaderIdGen = Incrementing(u32, "LID");
const BrowserContextIdGen = Incrementing(u32, "BID");
// Generic so that we can inject mocks into it.
@@ -63,7 +61,6 @@ pub fn CDPT(comptime TypeProvider: type) type {
target_auto_attach: bool = false,
target_id_gen: TargetIdGen = .{},
- loader_id_gen: LoaderIdGen = .{},
session_id_gen: SessionIdGen = .{},
browser_context_id_gen: BrowserContextIdGen = .{},
@@ -200,7 +197,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
.frameTree = .{
.frame = .{
.id = "TID-STARTUP-B",
- .loaderId = LOADER_ID,
+ .loaderId = "LOADERID24DD2FD56CF1EF33C965C79C",
.securityOrigin = URL_BASE,
.url = "about:blank",
.secureContextType = "Secure",
@@ -350,7 +347,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
// Maps to our Page. (There are other types of targets, but we only
// deal with "pages" for now). Since we only allow 1 open page at a
// time, we only have 1 target_id.
- target_id: ?[]const u8,
+ target_id: ?[14]u8,
// The CDP session_id. After the target/page is created, the client
// "attaches" to it (either explicitly or automatically). We return a
@@ -362,7 +359,6 @@ pub fn BrowserContext(comptime CDP_T: type) type {
// we should reject it.
session_id: ?[]const u8,
- loader_id: []const u8,
security_origin: []const u8,
page_life_cycle_events: bool,
secure_context_type: []const u8,
@@ -416,7 +412,6 @@ pub fn BrowserContext(comptime CDP_T: type) type {
.session = session,
.security_origin = URL_BASE,
.secure_context_type = "Secure", // TODO = enum
- .loader_id = LOADER_ID,
.page_life_cycle_events = false, // TODO; Target based value
.node_registry = registry,
.node_search_list = undefined,
@@ -593,7 +588,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
pub fn onPageNavigate(ctx: *anyopaque, msg: *const Notification.PageNavigate) !void {
const self: *Self = @ptrCast(@alignCast(ctx));
defer self.resetNotificationArena();
- return @import("domains/page.zig").pageNavigate(self.notification_arena, self, msg);
+ return @import("domains/page.zig").pageNavigate(self, msg);
}
pub fn onPageNavigated(ctx: *anyopaque, msg: *const Notification.PageNavigated) !void {
@@ -615,19 +610,19 @@ pub fn BrowserContext(comptime CDP_T: type) type {
pub fn onHttpRequestStart(ctx: *anyopaque, msg: *const Notification.RequestStart) !void {
const self: *Self = @ptrCast(@alignCast(ctx));
defer self.resetNotificationArena();
- try @import("domains/network.zig").httpRequestStart(self.notification_arena, self, msg);
+ try @import("domains/network.zig").httpRequestStart(self, msg);
}
pub fn onHttpRequestIntercept(ctx: *anyopaque, msg: *const Notification.RequestIntercept) !void {
const self: *Self = @ptrCast(@alignCast(ctx));
defer self.resetNotificationArena();
- try @import("domains/fetch.zig").requestIntercept(self.notification_arena, self, msg);
+ try @import("domains/fetch.zig").requestIntercept(self, msg);
}
pub fn onHttpRequestFail(ctx: *anyopaque, msg: *const Notification.RequestFail) !void {
const self: *Self = @ptrCast(@alignCast(ctx));
defer self.resetNotificationArena();
- return @import("domains/network.zig").httpRequestFail(self.notification_arena, self, msg);
+ return @import("domains/network.zig").httpRequestFail(self, msg);
}
pub fn onHttpResponseHeadersDone(ctx: *anyopaque, msg: *const Notification.ResponseHeaderDone) !void {
@@ -639,7 +634,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
pub fn onHttpRequestDone(ctx: *anyopaque, msg: *const Notification.RequestDone) !void {
const self: *Self = @ptrCast(@alignCast(ctx));
defer self.resetNotificationArena();
- return @import("domains/network.zig").httpRequestDone(self.notification_arena, self, msg);
+ return @import("domains/network.zig").httpRequestDone(self, msg);
}
pub fn onHttpResponseData(ctx: *anyopaque, msg: *const Notification.ResponseData) !void {
@@ -657,7 +652,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
pub fn onHttpRequestAuthRequired(ctx: *anyopaque, data: *const Notification.RequestAuthRequired) !void {
const self: *Self = @ptrCast(@alignCast(ctx));
defer self.resetNotificationArena();
- try @import("domains/fetch.zig").requestAuthRequired(self.notification_arena, self, data);
+ try @import("domains/fetch.zig").requestAuthRequired(self, data);
}
fn resetNotificationArena(self: *Self) void {
diff --git a/src/cdp/domains/accessibility.zig b/src/cdp/domains/accessibility.zig
index cfb670fe..f8e8df30 100644
--- a/src/cdp/domains/accessibility.zig
+++ b/src/cdp/domains/accessibility.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const id = @import("../id.zig");
pub fn processMessage(cmd: anytype) !void {
const action = std.meta.stringToEnum(enum {
@@ -46,15 +47,18 @@ fn getFullAXTree(cmd: anytype) !void {
})) orelse return error.InvalidParams;
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
+ const session = bc.session;
- if (params.frameId) |frameId| {
- const target_id = bc.target_id orelse return error.TargetNotLoaded;
- if (std.mem.eql(u8, target_id, frameId) == false) {
+ const page = blk: {
+ const frame_id = params.frameId orelse {
+ break :blk session.currentPage() orelse return error.PageNotLoaded;
+ };
+ const page_id = try id.toPageId(.frame_id, frame_id);
+ break :blk session.findPage(page_id) orelse {
return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
- }
- }
+ };
+ };
- const page = bc.session.currentPage() orelse return error.PageNotLoaded;
const doc = page.window._document.asNode();
const node = try bc.node_registry.register(doc);
diff --git a/src/cdp/domains/dom.zig b/src/cdp/domains/dom.zig
index 28bfb906..45bcbd59 100644
--- a/src/cdp/domains/dom.zig
+++ b/src/cdp/domains/dom.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const id = @import("../id.zig");
const log = @import("../../log.zig");
const Node = @import("../Node.zig");
const DOMNode = @import("../../browser/webapi/Node.zig");
@@ -497,12 +498,11 @@ fn getFrameOwner(cmd: anytype) !void {
})) orelse return error.InvalidParams;
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
- const target_id = bc.target_id orelse return error.TargetNotLoaded;
- if (std.mem.eql(u8, target_id, params.frameId) == false) {
- return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
- }
+ const page_id = try id.toPageId(.frame_id, params.frameId);
- const page = bc.session.currentPage() orelse return error.PageNotLoaded;
+ const page = bc.session.findPage(page_id) orelse {
+ return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
+ };
const node = try bc.node_registry.register(page.window._document.asNode());
return cmd.sendResult(.{ .nodeId = node.id, .backendNodeId = node.id }, .{});
diff --git a/src/cdp/domains/fetch.zig b/src/cdp/domains/fetch.zig
index d98e204a..d6e96f2f 100644
--- a/src/cdp/domains/fetch.zig
+++ b/src/cdp/domains/fetch.zig
@@ -19,6 +19,7 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
+const id = @import("../id.zig");
const log = @import("../../log.zig");
const network = @import("network.zig");
@@ -48,7 +49,7 @@ pub fn processMessage(cmd: anytype) !void {
// Stored in CDP
pub const InterceptState = struct {
allocator: Allocator,
- waiting: std.AutoArrayHashMapUnmanaged(u64, *Http.Transfer),
+ waiting: std.AutoArrayHashMapUnmanaged(u32, *Http.Transfer),
pub fn init(allocator: Allocator) !InterceptState {
return .{
@@ -65,8 +66,8 @@ pub const InterceptState = struct {
return self.waiting.put(self.allocator, transfer.id, transfer);
}
- pub fn remove(self: *InterceptState, id: u64) ?*Http.Transfer {
- const entry = self.waiting.fetchSwapRemove(id) orelse return null;
+ pub fn remove(self: *InterceptState, request_id: u32) ?*Http.Transfer {
+ const entry = self.waiting.fetchSwapRemove(request_id) orelse return null;
return entry.value;
}
@@ -178,13 +179,11 @@ fn arePatternsSupported(patterns: []RequestPattern) bool {
return true;
}
-pub fn requestIntercept(arena: Allocator, bc: anytype, intercept: *const Notification.RequestIntercept) !void {
+pub fn requestIntercept(bc: anytype, intercept: *const Notification.RequestIntercept) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
- const target_id = bc.target_id orelse unreachable;
-
// We keep it around to wait for modifications to the request.
// NOTE: we assume whomever created the request created it with a lifetime of the Page.
// TODO: What to do when receiving replies for a previous page's requests?
@@ -193,16 +192,16 @@ pub fn requestIntercept(arena: Allocator, bc: anytype, intercept: *const Notific
try bc.intercept_state.put(transfer);
try bc.cdp.sendEvent("Fetch.requestPaused", .{
- .requestId = try std.fmt.allocPrint(arena, "INTERCEPT-{d}", .{transfer.id}),
+ .requestId = &id.toInterceptId(transfer.id),
+ .frameId = &id.toFrameId(transfer.req.page_id),
.request = network.TransferAsRequestWriter.init(transfer),
- .frameId = target_id,
.resourceType = switch (transfer.req.resource_type) {
.script => "Script",
.xhr => "XHR",
.document => "Document",
.fetch => "Fetch",
},
- .networkId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}),
+ .networkId = &id.toRequestId(transfer.id), // matches the Network REQ-ID
}, .{ .session_id = session_id });
log.debug(.cdp, "request intercept", .{
@@ -218,7 +217,7 @@ pub fn requestIntercept(arena: Allocator, bc: anytype, intercept: *const Notific
fn continueRequest(cmd: anytype) !void {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const params = (try cmd.params(struct {
- requestId: []const u8, // "INTERCEPT-{d}"
+ requestId: []const u8, // INT-{d}"
url: ?[]const u8 = null,
method: ?[]const u8 = null,
postData: ?[]const u8 = null,
@@ -278,7 +277,7 @@ const AuthChallengeResponse = enum {
fn continueWithAuth(cmd: anytype) !void {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const params = (try cmd.params(struct {
- requestId: []const u8, // "INTERCEPT-{d}"
+ requestId: []const u8, // "INT-{d}"
authChallengeResponse: struct {
response: AuthChallengeResponse,
username: []const u8 = "",
@@ -322,7 +321,7 @@ fn fulfillRequest(cmd: anytype) !void {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const params = (try cmd.params(struct {
- requestId: []const u8, // "INTERCEPT-{d}"
+ requestId: []const u8, // "INT-{d}"
responseCode: u16,
responseHeaders: ?[]const Http.Header = null,
binaryResponseHeaders: ?[]const u8 = null,
@@ -363,7 +362,7 @@ fn fulfillRequest(cmd: anytype) !void {
fn failRequest(cmd: anytype) !void {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const params = (try cmd.params(struct {
- requestId: []const u8, // "INTERCEPT-{d}"
+ requestId: []const u8, // "INT-{d}"
errorReason: ErrorReason,
})) orelse return error.InvalidParams;
@@ -382,13 +381,11 @@ fn failRequest(cmd: anytype) !void {
return cmd.sendResult(null, .{});
}
-pub fn requestAuthRequired(arena: Allocator, bc: anytype, intercept: *const Notification.RequestAuthRequired) !void {
+pub fn requestAuthRequired(bc: anytype, intercept: *const Notification.RequestAuthRequired) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
- const target_id = bc.target_id orelse unreachable;
-
// We keep it around to wait for modifications to the request.
// NOTE: we assume whomever created the request created it with a lifetime of the Page.
// TODO: What to do when receiving replies for a previous page's requests?
@@ -399,9 +396,9 @@ pub fn requestAuthRequired(arena: Allocator, bc: anytype, intercept: *const Noti
const challenge = transfer._auth_challenge orelse return error.NullAuthChallenge;
try bc.cdp.sendEvent("Fetch.authRequired", .{
- .requestId = try std.fmt.allocPrint(arena, "INTERCEPT-{d}", .{transfer.id}),
+ .requestId = &id.toInterceptId(transfer.id),
+ .frameId = &id.toFrameId(transfer.req.page_id),
.request = network.TransferAsRequestWriter.init(transfer),
- .frameId = target_id,
.resourceType = switch (transfer.req.resource_type) {
.script => "Script",
.xhr => "XHR",
@@ -414,7 +411,7 @@ pub fn requestAuthRequired(arena: Allocator, bc: anytype, intercept: *const Noti
.scheme = if (challenge.scheme == .digest) "digest" else "basic",
.realm = challenge.realm,
},
- .networkId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}),
+ .networkId = &id.toRequestId(transfer.id),
}, .{ .session_id = session_id });
log.debug(.cdp, "request auth required", .{
@@ -427,10 +424,10 @@ pub fn requestAuthRequired(arena: Allocator, bc: anytype, intercept: *const Noti
intercept.wait_for_interception.* = true;
}
-// Get u64 from requestId which is formatted as: "INTERCEPT-{d}"
-fn idFromRequestId(request_id: []const u8) !u64 {
- if (!std.mem.startsWith(u8, request_id, "INTERCEPT-")) {
+// Get u32 from requestId which is formatted as: "INT-{d}"
+fn idFromRequestId(request_id: []const u8) !u32 {
+ if (!std.mem.startsWith(u8, request_id, "INT-")) {
return error.InvalidParams;
}
- return std.fmt.parseInt(u64, request_id[10..], 10) catch return error.InvalidParams;
+ return std.fmt.parseInt(u32, request_id[4..], 10) catch return error.InvalidParams;
}
diff --git a/src/cdp/domains/input.zig b/src/cdp/domains/input.zig
index 464a4feb..965b395a 100644
--- a/src/cdp/domains/input.zig
+++ b/src/cdp/domains/input.zig
@@ -116,30 +116,3 @@ fn insertText(cmd: anytype) !void {
try cmd.sendResult(null, .{});
}
-
-fn clickNavigate(cmd: anytype, uri: std.Uri) !void {
- const bc = cmd.browser_context.?;
-
- var url_buf: std.ArrayList(u8) = .{};
- try uri.writeToStream(.{
- .scheme = true,
- .authentication = true,
- .authority = true,
- .port = true,
- .path = true,
- .query = true,
- }, url_buf.writer(cmd.arena));
- const url = url_buf.items;
-
- try cmd.sendEvent("Page.frameRequestedNavigation", .{
- .url = url,
- .frameId = bc.target_id.?,
- .reason = "anchorClick",
- .disposition = "currentTab",
- }, .{ .session_id = bc.session_id.? });
-
- try bc.session.removePage();
- _ = try bc.session.createPage(null);
-
- try @import("page.zig").navigateToUrl(cmd, url, false);
-}
diff --git a/src/cdp/domains/network.zig b/src/cdp/domains/network.zig
index a2fac2da..bd175978 100644
--- a/src/cdp/domains/network.zig
+++ b/src/cdp/domains/network.zig
@@ -21,6 +21,8 @@ const lp = @import("lightpanda");
const Allocator = std.mem.Allocator;
const CdpStorage = @import("storage.zig");
+
+const id = @import("../id.zig");
const URL = @import("../../browser/URL.zig");
const Transfer = @import("../../http/Client.zig").Transfer;
const Notification = @import("../../Notification.zig");
@@ -208,7 +210,7 @@ fn getResponseBody(cmd: anytype) !void {
}, .{});
}
-pub fn httpRequestFail(arena: Allocator, bc: anytype, msg: *const Notification.RequestFail) !void {
+pub fn httpRequestFail(bc: anytype, msg: *const Notification.RequestFail) !void {
// It's possible that the request failed because we aborted when the client
// sent Target.closeTarget. In that case, bc.session_id will be cleared
// already, and we can skip sending these messages to the client.
@@ -220,7 +222,7 @@ pub fn httpRequestFail(arena: Allocator, bc: anytype, msg: *const Notification.R
// We're missing a bunch of fields, but, for now, this seems like enough
try bc.cdp.sendEvent("Network.loadingFailed", .{
- .requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{msg.transfer.id}),
+ .requestId = &id.toRequestId(msg.transfer.id),
// Seems to be what chrome answers with. I assume it depends on the type of error?
.type = "Ping",
.errorText = msg.err,
@@ -228,28 +230,27 @@ pub fn httpRequestFail(arena: Allocator, bc: anytype, msg: *const Notification.R
}, .{ .session_id = session_id });
}
-pub fn httpRequestStart(arena: Allocator, bc: anytype, msg: *const Notification.RequestStart) !void {
+pub fn httpRequestStart(bc: anytype, msg: *const Notification.RequestStart) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
- const target_id = bc.target_id orelse unreachable;
- const page = bc.session.currentPage() orelse unreachable;
+ const transfer = msg.transfer;
+ const req = &transfer.req;
+ const page_id = req.page_id;
+ const page = bc.session.findPage(page_id) orelse return;
// Modify request with extra CDP headers
for (bc.extra_headers.items) |extra| {
- try msg.transfer.req.headers.add(extra);
+ try req.headers.add(extra);
}
- const transfer = msg.transfer;
- const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id});
-
// We're missing a bunch of fields, but, for now, this seems like enough
try bc.cdp.sendEvent("Network.requestWillBeSent", .{
- .requestId = loader_id,
- .frameId = target_id,
- .loaderId = loader_id,
- .type = msg.transfer.req.resource_type.string(),
+ .loaderId = &id.toLoaderId(transfer.id),
+ .requestId = &id.toRequestId(transfer.id),
+ .frameId = &id.toFrameId(page_id),
+ .type = req.resource_type.string(),
.documentURL = page.url,
.request = TransferAsRequestWriter.init(transfer),
.initiator = .{ .type = "other" },
@@ -262,29 +263,27 @@ pub fn httpResponseHeaderDone(arena: Allocator, bc: anytype, msg: *const Notific
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
- const target_id = bc.target_id orelse unreachable;
const transfer = msg.transfer;
- const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id});
// We're missing a bunch of fields, but, for now, this seems like enough
try bc.cdp.sendEvent("Network.responseReceived", .{
- .requestId = loader_id,
- .frameId = target_id,
- .loaderId = loader_id,
+ .loaderId = &id.toLoaderId(transfer.id),
+ .requestId = &id.toRequestId(transfer.id),
+ .frameId = &id.toFrameId(transfer.req.page_id),
.response = TransferAsResponseWriter.init(arena, msg.transfer),
.hasExtraInfo = false, // TODO change after adding Network.responseReceivedExtraInfo
}, .{ .session_id = session_id });
}
-pub fn httpRequestDone(arena: Allocator, bc: anytype, msg: *const Notification.RequestDone) !void {
+pub fn httpRequestDone(bc: anytype, msg: *const Notification.RequestDone) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
-
+ const transfer = msg.transfer;
try bc.cdp.sendEvent("Network.loadingFinished", .{
- .requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{msg.transfer.id}),
- .encodedDataLength = msg.transfer.bytes_received,
+ .requestId = &id.toRequestId(transfer.id),
+ .encodedDataLength = transfer.bytes_received,
}, .{ .session_id = session_id });
}
diff --git a/src/cdp/domains/page.zig b/src/cdp/domains/page.zig
index eee83f77..dad180e5 100644
--- a/src/cdp/domains/page.zig
+++ b/src/cdp/domains/page.zig
@@ -19,6 +19,7 @@
const std = @import("std");
const lp = @import("lightpanda");
+const id = @import("../id.zig");
const log = @import("../../log.zig");
const js = @import("../../browser/js/js.zig");
const Page = @import("../../browser/Page.zig");
@@ -73,9 +74,9 @@ fn getFrameTree(cmd: anytype) !void {
return cmd.sendResult(.{
.frameTree = .{
.frame = Frame{
- .id = target_id,
- .loaderId = bc.loader_id,
+ .id = &target_id,
.securityOrigin = bc.security_origin,
+ .loaderId = "LID-0000000001",
.url = bc.getURL() orelse "about:blank",
.secureContextType = bc.secure_context_type,
},
@@ -103,18 +104,21 @@ fn setLifecycleEventsEnabled(cmd: anytype) !void {
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
if (page._load_state == .complete) {
+ const frame_id = &id.toFrameId(page.id);
+ const loader_id = &id.toLoaderId(page._req_id);
+
const now = timestampF(.monotonic);
- try sendPageLifecycle(bc, "DOMContentLoaded", now);
- try sendPageLifecycle(bc, "load", now);
+ try sendPageLifecycle(bc, "DOMContentLoaded", now, frame_id, loader_id);
+ try sendPageLifecycle(bc, "load", now, frame_id, loader_id);
const http_client = page._session.browser.http_client;
const http_active = http_client.active;
const total_network_activity = http_active + http_client.intercepted;
if (page._notified_network_almost_idle.check(total_network_activity <= 2)) {
- try sendPageLifecycle(bc, "networkAlmostIdle", now);
+ try sendPageLifecycle(bc, "networkAlmostIdle", now, frame_id, loader_id);
}
if (page._notified_network_idle.check(total_network_activity == 0)) {
- try sendPageLifecycle(bc, "networkIdle", now);
+ try sendPageLifecycle(bc, "networkIdle", now, frame_id, loader_id);
}
}
@@ -227,16 +231,15 @@ fn navigate(cmd: anytype) !void {
});
}
-pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.PageNavigate) !void {
+pub fn pageNavigate(bc: anytype, event: *const Notification.PageNavigate) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
-
- const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{event.req_id});
- const target_id = bc.target_id orelse unreachable;
-
bc.reset();
+ const frame_id = &id.toFrameId(event.page_id);
+ const loader_id = &id.toLoaderId(event.req_id);
+
var cdp = bc.cdp;
const reason_: ?[]const u8 = switch (event.opts.reason) {
.anchor => "anchorClick",
@@ -250,14 +253,14 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
};
if (reason_) |reason| {
try cdp.sendEvent("Page.frameScheduledNavigation", .{
- .frameId = target_id,
+ .frameId = frame_id,
.delay = 0,
.reason = reason,
.url = event.url,
}, .{ .session_id = session_id });
try cdp.sendEvent("Page.frameRequestedNavigation", .{
- .frameId = target_id,
+ .frameId = frame_id,
.reason = reason,
.url = event.url,
.disposition = "currentTab",
@@ -266,7 +269,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
// frameStartedNavigating event
try cdp.sendEvent("Page.frameStartedNavigating", .{
- .frameId = target_id,
+ .frameId = frame_id,
.url = event.url,
.loaderId = loader_id,
.navigationType = "differentDocument",
@@ -274,7 +277,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
// frameStartedLoading event
try cdp.sendEvent("Page.frameStartedLoading", .{
- .frameId = target_id,
+ .frameId = frame_id,
}, .{ .session_id = session_id });
}
@@ -301,9 +304,10 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
- const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{event.req_id});
- const target_id = bc.target_id orelse unreachable;
+
const timestamp = event.timestamp;
+ const frame_id = &id.toFrameId(event.page_id);
+ const loader_id = &id.toLoaderId(event.req_id);
var cdp = bc.cdp;
@@ -316,7 +320,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
try cdp.sendJSON(.{
.id = input_id,
.result = .{
- .frameId = target_id,
+ .frameId = frame_id,
.loaderId = loader_id,
},
.sessionId = session_id,
@@ -326,7 +330,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
if (bc.page_life_cycle_events) {
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
.name = "init",
- .frameId = target_id,
+ .frameId = frame_id,
.loaderId = loader_id,
.timestamp = event.timestamp,
}, .{ .session_id = session_id });
@@ -345,7 +349,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
if (reason_ != null) {
try cdp.sendEvent("Page.frameClearedScheduledNavigation", .{
- .frameId = target_id,
+ .frameId = frame_id,
}, .{ .session_id = session_id });
}
@@ -356,7 +360,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
{
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
- const aux_data = try std.fmt.allocPrint(arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id});
+ const aux_data = try std.fmt.allocPrint(arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\",\"loaderId\":\"{s}\"}}", .{ frame_id, loader_id });
var ls: js.Local.Scope = undefined;
page.js.localScope(&ls);
@@ -371,7 +375,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
);
}
for (bc.isolated_worlds.items) |isolated_world| {
- const aux_json = try std.fmt.allocPrint(arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", .{target_id});
+ const aux_json = try std.fmt.allocPrint(arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\",\"loaderId\":\"{s}\"}}", .{ frame_id, loader_id });
// Calling contextCreated will assign a new Id to the context and send the contextCreated event
@@ -392,7 +396,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
try cdp.sendEvent("Page.frameNavigated", .{
.type = "Navigation",
.frame = Frame{
- .id = target_id,
+ .id = frame_id,
.url = event.url,
.loaderId = loader_id,
.securityOrigin = bc.security_origin,
@@ -419,7 +423,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
.timestamp = timestamp,
.name = "DOMContentLoaded",
- .frameId = target_id,
+ .frameId = frame_id,
.loaderId = loader_id,
}, .{ .session_id = session_id });
}
@@ -436,35 +440,33 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
.timestamp = timestamp,
.name = "load",
- .frameId = target_id,
+ .frameId = frame_id,
.loaderId = loader_id,
}, .{ .session_id = session_id });
}
// frameStoppedLoading
return cdp.sendEvent("Page.frameStoppedLoading", .{
- .frameId = target_id,
+ .frameId = frame_id,
}, .{ .session_id = session_id });
}
pub fn pageNetworkIdle(bc: anytype, event: *const Notification.PageNetworkIdle) !void {
- return sendPageLifecycle(bc, "networkIdle", event.timestamp);
+ return sendPageLifecycle(bc, "networkIdle", event.timestamp, &id.toFrameId(event.page_id), &id.toLoaderId(event.req_id));
}
pub fn pageNetworkAlmostIdle(bc: anytype, event: *const Notification.PageNetworkAlmostIdle) !void {
- return sendPageLifecycle(bc, "networkAlmostIdle", event.timestamp);
+ return sendPageLifecycle(bc, "networkAlmostIdle", event.timestamp, &id.toFrameId(event.page_id), &id.toLoaderId(event.req_id));
}
-fn sendPageLifecycle(bc: anytype, name: []const u8, timestamp: u64) !void {
+fn sendPageLifecycle(bc: anytype, name: []const u8, timestamp: u64, frame_id: []const u8, loader_id: []const u8) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
- const loader_id = bc.loader_id;
- const target_id = bc.target_id orelse unreachable;
return bc.cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
.name = name,
- .frameId = target_id,
+ .frameId = frame_id,
.loaderId = loader_id,
.timestamp = timestamp,
}, .{ .session_id = session_id });
@@ -487,15 +489,15 @@ test "cdp.page: getFrameTree" {
try ctx.expectSentError(-31998, "BrowserContextNotLoaded", .{ .id = 10 });
}
- const bc = try ctx.loadBrowserContext(.{ .id = "BID-9", .target_id = "TID-3" });
+ const bc = try ctx.loadBrowserContext(.{ .id = "BID-9", .url = "hi.html", .target_id = "FID-000000000X".* });
{
try ctx.processMessage(.{ .id = 11, .method = "Page.getFrameTree" });
try ctx.expectSentResult(.{
.frameTree = .{
.frame = .{
- .id = "TID-3",
- .loaderId = bc.loader_id,
- .url = "about:blank",
+ .id = "FID-000000000X",
+ .loaderId = "LID-0000000001",
+ .url = "http://127.0.0.1:9582/src/browser/tests/hi.html",
.domainAndRegistry = "",
.securityOrigin = bc.security_origin,
.mimeType = "text/html",
diff --git a/src/cdp/domains/target.zig b/src/cdp/domains/target.zig
index 32b29901..7dfe59ef 100644
--- a/src/cdp/domains/target.zig
+++ b/src/cdp/domains/target.zig
@@ -18,6 +18,8 @@
const std = @import("std");
const lp = @import("lightpanda");
+
+const id = @import("../id.zig");
const log = @import("../../log.zig");
const js = @import("../../browser/js/js.zig");
@@ -66,11 +68,11 @@ fn getTargets(cmd: anytype) !void {
}, .{ .include_session_id = false });
};
- const target_id = bc.target_id orelse {
+ const target_id = &(bc.target_id orelse {
return cmd.sendResult(.{
.targetInfos = [_]TargetInfo{},
}, .{ .include_session_id = false });
- };
+ });
return cmd.sendResult(.{
.targetInfos = [_]TargetInfo{.{
@@ -171,11 +173,12 @@ fn createTarget(cmd: anytype) !void {
// if target_id is null, we should never have a session_id
lp.assert(bc.session_id == null, "CDP.target.createTarget not null session_id", .{});
- const target_id = cmd.cdp.target_id_gen.next();
-
- bc.target_id = target_id;
-
const page = try bc.session.createPage();
+
+ // the target_id == the frame_id of the "root" page
+ const frame_id = id.toFrameId(page.id);
+ bc.target_id = frame_id;
+ const target_id = &bc.target_id.?;
{
var ls: js.Local.Scope = undefined;
page.js.localScope(&ls);
@@ -195,7 +198,6 @@ fn createTarget(cmd: anytype) !void {
// change CDP state
bc.security_origin = "://";
bc.secure_context_type = "InsecureScheme";
- bc.loader_id = LOADER_ID;
// send targetCreated event
// TODO: should this only be sent when Target.setDiscoverTargets
@@ -234,7 +236,7 @@ fn attachToTarget(cmd: anytype) !void {
})) orelse return error.InvalidParams;
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
- const target_id = bc.target_id orelse return error.TargetNotLoaded;
+ const target_id = &(bc.target_id orelse return error.TargetNotLoaded);
if (std.mem.eql(u8, target_id, params.targetId) == false) {
return error.UnknownTargetId;
}
@@ -255,7 +257,7 @@ fn closeTarget(cmd: anytype) !void {
})) orelse return error.InvalidParams;
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
- const target_id = bc.target_id orelse return error.TargetNotLoaded;
+ const target_id = &(bc.target_id orelse return error.TargetNotLoaded);
if (std.mem.eql(u8, target_id, params.targetId) == false) {
return error.UnknownTargetId;
}
@@ -298,7 +300,7 @@ fn getTargetInfo(cmd: anytype) !void {
if (params.targetId) |param_target_id| {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
- const target_id = bc.target_id orelse return error.TargetNotLoaded;
+ const target_id = &(bc.target_id orelse return error.TargetNotLoaded);
if (std.mem.eql(u8, target_id, param_target_id) == false) {
return error.UnknownTargetId;
}
@@ -415,10 +417,11 @@ fn setAutoAttach(cmd: anytype) !void {
// autoAttach is set to true, we must attach to all existing targets.
if (cmd.browser_context) |bc| {
if (bc.target_id == null) {
- // hasn't attached yet
- const target_id = cmd.cdp.target_id_gen.next();
- try doAttachtoTarget(cmd, target_id);
- bc.target_id = target_id;
+ if (bc.session.currentPage()) |page| {
+ // the target_id == the frame_id of the "root" page
+ bc.target_id = id.toFrameId(page.id);
+ try doAttachtoTarget(cmd, &bc.target_id.?);
+ }
}
// should we send something here?
return;
@@ -612,14 +615,14 @@ test "cdp.target: closeTarget" {
// pretend we createdTarget first
_ = try bc.session.createPage();
- bc.target_id = "TID-A";
+ bc.target_id = "TID-000000000A".*;
{
try testing.expectError(error.UnknownTargetId, ctx.processMessage(.{ .id = 10, .method = "Target.closeTarget", .params = .{ .targetId = "TID-8" } }));
try ctx.expectSentError(-31998, "UnknownTargetId", .{ .id = 10 });
}
{
- try ctx.processMessage(.{ .id = 11, .method = "Target.closeTarget", .params = .{ .targetId = "TID-A" } });
+ try ctx.processMessage(.{ .id = 11, .method = "Target.closeTarget", .params = .{ .targetId = "TID-000000000A" } });
try ctx.expectSentResult(.{ .success = true }, .{ .id = 11 });
try testing.expectEqual(null, bc.session.page);
try testing.expectEqual(null, bc.target_id);
@@ -643,14 +646,14 @@ test "cdp.target: attachToTarget" {
// pretend we createdTarget first
_ = try bc.session.createPage();
- bc.target_id = "TID-B";
+ bc.target_id = "TID-000000000B".*;
{
try testing.expectError(error.UnknownTargetId, ctx.processMessage(.{ .id = 10, .method = "Target.attachToTarget", .params = .{ .targetId = "TID-8" } }));
try ctx.expectSentError(-31998, "UnknownTargetId", .{ .id = 10 });
}
{
- try ctx.processMessage(.{ .id = 11, .method = "Target.attachToTarget", .params = .{ .targetId = "TID-B" } });
+ try ctx.processMessage(.{ .id = 11, .method = "Target.attachToTarget", .params = .{ .targetId = "TID-000000000B" } });
const session_id = bc.session_id.?;
try ctx.expectSentResult(.{ .sessionId = session_id }, .{ .id = 11 });
try ctx.expectSentEvent("Target.attachedToTarget", .{ .sessionId = session_id, .targetInfo = .{ .url = "chrome://newtab/", .title = "about:blank", .attached = true, .type = "page", .canAccessOpener = false, .browserContextId = "BID-9", .targetId = bc.target_id.? } }, .{});
@@ -687,17 +690,17 @@ test "cdp.target: getTargetInfo" {
// pretend we createdTarget first
_ = try bc.session.createPage();
- bc.target_id = "TID-A";
+ bc.target_id = "TID-000000000C".*;
{
try testing.expectError(error.UnknownTargetId, ctx.processMessage(.{ .id = 10, .method = "Target.getTargetInfo", .params = .{ .targetId = "TID-8" } }));
try ctx.expectSentError(-31998, "UnknownTargetId", .{ .id = 10 });
}
{
- try ctx.processMessage(.{ .id = 11, .method = "Target.getTargetInfo", .params = .{ .targetId = "TID-A" } });
+ try ctx.processMessage(.{ .id = 11, .method = "Target.getTargetInfo", .params = .{ .targetId = "TID-000000000C" } });
try ctx.expectSentResult(.{
.targetInfo = .{
- .targetId = "TID-A",
+ .targetId = "TID-000000000C",
.type = "page",
.title = "",
.url = "about:blank",
diff --git a/src/cdp/id.zig b/src/cdp/id.zig
new file mode 100644
index 00000000..df580e8c
--- /dev/null
+++ b/src/cdp/id.zig
@@ -0,0 +1,184 @@
+// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
+//
+// Francis Bouvier
+// Pierre Tachoire
+//
+// 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 .
+
+const std = @import("std");
+const IS_DEBUG = @import("builtin").mode == .Debug;
+
+pub fn toPageId(comptime id_type: enum { frame_id, loader_id }, input: []const u8) !u32 {
+ const err = switch (comptime id_type) {
+ .frame_id => error.InvalidFrameId,
+ .loader_id => error.InvalidLoaderId,
+ };
+
+ if (input.len < 4) {
+ return err;
+ }
+
+ return std.fmt.parseInt(u32, input[4..], 10) catch err;
+}
+
+pub fn toFrameId(page_id: u32) [14]u8 {
+ var buf: [14]u8 = undefined;
+ _ = std.fmt.bufPrint(&buf, "FID-{d:0>10}", .{page_id}) catch unreachable;
+ return buf;
+}
+
+pub fn toLoaderId(page_id: u32) [14]u8 {
+ var buf: [14]u8 = undefined;
+ _ = std.fmt.bufPrint(&buf, "LID-{d:0>10}", .{page_id}) catch unreachable;
+ return buf;
+}
+
+pub fn toRequestId(page_id: u32) [14]u8 {
+ var buf: [14]u8 = undefined;
+ _ = std.fmt.bufPrint(&buf, "RID-{d:0>10}", .{page_id}) catch unreachable;
+ return buf;
+}
+
+pub fn toInterceptId(page_id: u32) [14]u8 {
+ var buf: [14]u8 = undefined;
+ _ = std.fmt.bufPrint(&buf, "INT-{d:0>10}", .{page_id}) catch unreachable;
+ return buf;
+}
+
+// Generates incrementing prefixed integers, i.e. CTX-1, CTX-2, CTX-3.
+// Wraps to 0 on overflow.
+// Many caveats for using this:
+// - Not thread-safe.
+// - Information leaking
+// - The slice returned by next() is only valid:
+// - while incrementor is valid
+// - until the next call to next()
+// On the positive, it's zero allocation
+pub fn Incrementing(comptime T: type, comptime prefix: []const u8) type {
+ // +1 for the '-' separator
+ const NUMERIC_START = prefix.len + 1;
+ const MAX_BYTES = NUMERIC_START + switch (T) {
+ u8 => 3,
+ u16 => 5,
+ u32 => 10,
+ u64 => 20,
+ else => @compileError("Incrementing must be given an unsigned int type, got: " ++ @typeName(T)),
+ };
+
+ const buffer = blk: {
+ var b = [_]u8{0} ** MAX_BYTES;
+ @memcpy(b[0..prefix.len], prefix);
+ b[prefix.len] = '-';
+ break :blk b;
+ };
+
+ const PrefixIntType = @Type(.{ .int = .{
+ .bits = NUMERIC_START * 8,
+ .signedness = .unsigned,
+ } });
+
+ const PREFIX_INT_CODE: PrefixIntType = @bitCast(buffer[0..NUMERIC_START].*);
+
+ return struct {
+ counter: T = 0,
+ buffer: [MAX_BYTES]u8 = buffer,
+
+ const Self = @This();
+
+ pub fn next(self: *Self) []const u8 {
+ const counter = self.counter;
+ const n = counter +% 1;
+ defer self.counter = n;
+
+ const size = std.fmt.printInt(self.buffer[NUMERIC_START..], n, 10, .lower, .{});
+ return self.buffer[0 .. NUMERIC_START + size];
+ }
+
+ // extracts the numeric portion from an ID
+ pub fn parse(str: []const u8) !T {
+ if (str.len <= NUMERIC_START) {
+ return error.InvalidId;
+ }
+
+ if (@as(PrefixIntType, @bitCast(str[0..NUMERIC_START].*)) != PREFIX_INT_CODE) {
+ return error.InvalidId;
+ }
+
+ return std.fmt.parseInt(T, str[NUMERIC_START..], 10) catch {
+ return error.InvalidId;
+ };
+ }
+ };
+}
+
+const testing = @import("../testing.zig");
+test "id: Incrementing.next" {
+ var id = Incrementing(u16, "IDX"){};
+ try testing.expectEqual("IDX-1", id.next());
+ try testing.expectEqual("IDX-2", id.next());
+ try testing.expectEqual("IDX-3", id.next());
+
+ // force a wrap
+ id.counter = 65533;
+ try testing.expectEqual("IDX-65534", id.next());
+ try testing.expectEqual("IDX-65535", id.next());
+ try testing.expectEqual("IDX-0", id.next());
+}
+
+test "id: Incrementing.parse" {
+ const ReqId = Incrementing(u32, "REQ");
+ try testing.expectError(error.InvalidId, ReqId.parse(""));
+ try testing.expectError(error.InvalidId, ReqId.parse("R"));
+ try testing.expectError(error.InvalidId, ReqId.parse("RE"));
+ try testing.expectError(error.InvalidId, ReqId.parse("REQ"));
+ try testing.expectError(error.InvalidId, ReqId.parse("REQ-"));
+ try testing.expectError(error.InvalidId, ReqId.parse("REQ--1"));
+ try testing.expectError(error.InvalidId, ReqId.parse("REQ--"));
+ try testing.expectError(error.InvalidId, ReqId.parse("REQ-Nope"));
+ try testing.expectError(error.InvalidId, ReqId.parse("REQ-4294967296"));
+
+ try testing.expectEqual(0, try ReqId.parse("REQ-0"));
+ try testing.expectEqual(99, try ReqId.parse("REQ-99"));
+ try testing.expectEqual(4294967295, try ReqId.parse("REQ-4294967295"));
+}
+
+test "id: toPageId" {
+ try testing.expectEqual(0, toPageId(.frame_id, "FID-0"));
+ try testing.expectEqual(0, toPageId(.loader_id, "LID-0"));
+
+ try testing.expectEqual(4294967295, toPageId(.frame_id, "FID-4294967295"));
+ try testing.expectEqual(4294967295, toPageId(.loader_id, "LID-4294967295"));
+ try testing.expectError(error.InvalidFrameId, toPageId(.frame_id, ""));
+ try testing.expectError(error.InvalidLoaderId, toPageId(.loader_id, "LID-NOPE"));
+}
+
+test "id: toFrameId" {
+ try testing.expectEqual("FID-0000000000", toFrameId(0));
+ try testing.expectEqual("FID-4294967295", toFrameId(4294967295));
+}
+
+test "id: toLoaderId" {
+ try testing.expectEqual("LID-0000000000", toLoaderId(0));
+ try testing.expectEqual("LID-4294967295", toLoaderId(4294967295));
+}
+
+test "id: toRequestId" {
+ try testing.expectEqual("RID-0000000000", toRequestId(0));
+ try testing.expectEqual("RID-4294967295", toRequestId(4294967295));
+}
+
+test "id: toInterceptId" {
+ try testing.expectEqual("INT-0000000000", toInterceptId(0));
+ try testing.expectEqual("INT-4294967295", toInterceptId(4294967295));
+}
diff --git a/src/cdp/testing.zig b/src/cdp/testing.zig
index 6a98a71e..1093afdc 100644
--- a/src/cdp/testing.zig
+++ b/src/cdp/testing.zig
@@ -92,7 +92,7 @@ const TestContext = struct {
const BrowserContextOpts = struct {
id: ?[]const u8 = null,
- target_id: ?[]const u8 = null,
+ target_id: ?[14]u8 = null,
session_id: ?[]const u8 = null,
url: ?[:0]const u8 = null,
};
@@ -122,7 +122,7 @@ const TestContext = struct {
bc.session_id = "SID-X";
}
if (bc.target_id == null) {
- bc.target_id = "TID-X";
+ bc.target_id = "TID-000000000Z".*;
}
const page = try bc.session.createPage();
const full_url = try std.fmt.allocPrintSentinel(
diff --git a/src/http/Client.zig b/src/http/Client.zig
index 1a38ef9e..65c859a8 100644
--- a/src/http/Client.zig
+++ b/src/http/Client.zig
@@ -79,7 +79,7 @@ multi: *c.CURLM,
handles: Handles,
// Use to generate the next request ID
-next_request_id: u64 = 0,
+next_request_id: u32 = 0,
// When handles has no more available easys, requests get queued.
queue: TransferQueue,
@@ -336,6 +336,7 @@ fn fetchRobotsThenProcessRequest(self: *Client, robots_url: [:0]const u8, req: R
.method = .GET,
.headers = headers,
.blocking = false,
+ .page_id = req.page_id,
.cookie_jar = req.cookie_jar,
.notification = req.notification,
.resource_type = .fetch,
@@ -562,12 +563,12 @@ pub fn fulfillTransfer(self: *Client, transfer: *Transfer, status: u16, headers:
transfer._intercept_state = .fulfilled;
}
-pub fn nextReqId(self: *Client) usize {
- return self.next_request_id + 1;
+pub fn nextReqId(self: *Client) u32 {
+ return self.next_request_id +% 1;
}
-pub fn incrReqId(self: *Client) usize {
- const id = self.next_request_id + 1;
+pub fn incrReqId(self: *Client) u32 {
+ const id = self.next_request_id +% 1;
self.next_request_id = id;
return id;
}
@@ -1003,6 +1004,7 @@ pub const RequestCookie = struct {
};
pub const Request = struct {
+ page_id: u32,
method: Method,
url: [:0]const u8,
headers: Http.Headers,
@@ -1093,7 +1095,7 @@ pub const AuthChallenge = struct {
pub const Transfer = struct {
arena: ArenaAllocator,
- id: usize = 0,
+ id: u32 = 0,
req: Request,
url: [:0]const u8,
ctx: *anyopaque, // copied from req.ctx to make it easier for callback handlers
diff --git a/src/id.zig b/src/id.zig
index b45df0ce..f2a9abf8 100644
--- a/src/id.zig
+++ b/src/id.zig
@@ -19,72 +19,6 @@
const std = @import("std");
const lp = @import("lightpanda");
-// Generates incrementing prefixed integers, i.e. CTX-1, CTX-2, CTX-3.
-// Wraps to 0 on overflow.
-// Many caveats for using this:
-// - Not thread-safe.
-// - Information leaking
-// - The slice returned by next() is only valid:
-// - while incrementor is valid
-// - until the next call to next()
-// On the positive, it's zero allocation
-pub fn Incrementing(comptime T: type, comptime prefix: []const u8) type {
- // +1 for the '-' separator
- const NUMERIC_START = prefix.len + 1;
- const MAX_BYTES = NUMERIC_START + switch (T) {
- u8 => 3,
- u16 => 5,
- u32 => 10,
- u64 => 20,
- else => @compileError("Incrementing must be given an unsigned int type, got: " ++ @typeName(T)),
- };
-
- const buffer = blk: {
- var b = [_]u8{0} ** MAX_BYTES;
- @memcpy(b[0..prefix.len], prefix);
- b[prefix.len] = '-';
- break :blk b;
- };
-
- const PrefixIntType = @Type(.{ .int = .{
- .bits = NUMERIC_START * 8,
- .signedness = .unsigned,
- } });
-
- const PREFIX_INT_CODE: PrefixIntType = @bitCast(buffer[0..NUMERIC_START].*);
-
- return struct {
- counter: T = 0,
- buffer: [MAX_BYTES]u8 = buffer,
-
- const Self = @This();
-
- pub fn next(self: *Self) []const u8 {
- const counter = self.counter;
- const n = counter +% 1;
- defer self.counter = n;
-
- const size = std.fmt.printInt(self.buffer[NUMERIC_START..], n, 10, .lower, .{});
- return self.buffer[0 .. NUMERIC_START + size];
- }
-
- // extracts the numeric portion from an ID
- pub fn parse(str: []const u8) !T {
- if (str.len <= NUMERIC_START) {
- return error.InvalidId;
- }
-
- if (@as(PrefixIntType, @bitCast(str[0..NUMERIC_START].*)) != PREFIX_INT_CODE) {
- return error.InvalidId;
- }
-
- return std.fmt.parseInt(T, str[NUMERIC_START..], 10) catch {
- return error.InvalidId;
- };
- }
- };
-}
-
pub fn uuidv4(hex: []u8) void {
lp.assert(hex.len == 36, "uuidv4.len", .{ .len = hex.len });
@@ -108,36 +42,6 @@ pub fn uuidv4(hex: []u8) void {
}
const testing = std.testing;
-test "id: Incrementing.next" {
- var id = Incrementing(u16, "IDX"){};
- try testing.expectEqualStrings("IDX-1", id.next());
- try testing.expectEqualStrings("IDX-2", id.next());
- try testing.expectEqualStrings("IDX-3", id.next());
-
- // force a wrap
- id.counter = 65533;
- try testing.expectEqualStrings("IDX-65534", id.next());
- try testing.expectEqualStrings("IDX-65535", id.next());
- try testing.expectEqualStrings("IDX-0", id.next());
-}
-
-test "id: Incrementing.parse" {
- const ReqId = Incrementing(u32, "REQ");
- try testing.expectError(error.InvalidId, ReqId.parse(""));
- try testing.expectError(error.InvalidId, ReqId.parse("R"));
- try testing.expectError(error.InvalidId, ReqId.parse("RE"));
- try testing.expectError(error.InvalidId, ReqId.parse("REQ"));
- try testing.expectError(error.InvalidId, ReqId.parse("REQ-"));
- try testing.expectError(error.InvalidId, ReqId.parse("REQ--1"));
- try testing.expectError(error.InvalidId, ReqId.parse("REQ--"));
- try testing.expectError(error.InvalidId, ReqId.parse("REQ-Nope"));
- try testing.expectError(error.InvalidId, ReqId.parse("REQ-4294967296"));
-
- try testing.expectEqual(0, try ReqId.parse("REQ-0"));
- try testing.expectEqual(99, try ReqId.parse("REQ-99"));
- try testing.expectEqual(4294967295, try ReqId.parse("REQ-4294967295"));
-}
-
test "id: uuiv4" {
const expectUUID = struct {
fn expect(uuid: [36]u8) !void {