mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Merge pull request #1582 from lightpanda-io/cdp_per_page_frame_id
Rework CDP frameIds (and loaderIds and requestIds and interceptorIds)
This commit is contained in:
@@ -102,24 +102,30 @@ const EventType = std.meta.FieldEnum(Events);
|
|||||||
pub const PageRemove = struct {};
|
pub const PageRemove = struct {};
|
||||||
|
|
||||||
pub const PageNavigate = struct {
|
pub const PageNavigate = struct {
|
||||||
req_id: usize,
|
req_id: u32,
|
||||||
|
page_id: u32,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
url: [:0]const u8,
|
url: [:0]const u8,
|
||||||
opts: Page.NavigateOpts,
|
opts: Page.NavigateOpts,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const PageNavigated = struct {
|
pub const PageNavigated = struct {
|
||||||
req_id: usize,
|
req_id: u32,
|
||||||
|
page_id: u32,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
url: [:0]const u8,
|
url: [:0]const u8,
|
||||||
opts: Page.NavigatedOpts,
|
opts: Page.NavigatedOpts,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const PageNetworkIdle = struct {
|
pub const PageNetworkIdle = struct {
|
||||||
|
req_id: u32,
|
||||||
|
page_id: u32,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const PageNetworkAlmostIdle = struct {
|
pub const PageNetworkAlmostIdle = struct {
|
||||||
|
req_id: u32,
|
||||||
|
page_id: u32,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -305,6 +311,7 @@ test "Notification" {
|
|||||||
|
|
||||||
// noop
|
// noop
|
||||||
notifier.dispatch(.page_navigate, &.{
|
notifier.dispatch(.page_navigate, &.{
|
||||||
|
.page_id = 0,
|
||||||
.req_id = 1,
|
.req_id = 1,
|
||||||
.timestamp = 4,
|
.timestamp = 4,
|
||||||
.url = undefined,
|
.url = undefined,
|
||||||
@@ -315,6 +322,7 @@ test "Notification" {
|
|||||||
|
|
||||||
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
|
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
|
||||||
notifier.dispatch(.page_navigate, &.{
|
notifier.dispatch(.page_navigate, &.{
|
||||||
|
.page_id = 0,
|
||||||
.req_id = 1,
|
.req_id = 1,
|
||||||
.timestamp = 4,
|
.timestamp = 4,
|
||||||
.url = undefined,
|
.url = undefined,
|
||||||
@@ -324,6 +332,7 @@ test "Notification" {
|
|||||||
|
|
||||||
notifier.unregisterAll(&tc);
|
notifier.unregisterAll(&tc);
|
||||||
notifier.dispatch(.page_navigate, &.{
|
notifier.dispatch(.page_navigate, &.{
|
||||||
|
.page_id = 0,
|
||||||
.req_id = 1,
|
.req_id = 1,
|
||||||
.timestamp = 10,
|
.timestamp = 10,
|
||||||
.url = undefined,
|
.url = undefined,
|
||||||
@@ -334,23 +343,25 @@ test "Notification" {
|
|||||||
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
|
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
|
||||||
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
|
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
|
||||||
notifier.dispatch(.page_navigate, &.{
|
notifier.dispatch(.page_navigate, &.{
|
||||||
|
.page_id = 0,
|
||||||
.req_id = 1,
|
.req_id = 1,
|
||||||
.timestamp = 10,
|
.timestamp = 10,
|
||||||
.url = undefined,
|
.url = undefined,
|
||||||
.opts = .{},
|
.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(14, tc.page_navigate);
|
||||||
try testing.expectEqual(6, tc.page_navigated);
|
try testing.expectEqual(6, tc.page_navigated);
|
||||||
|
|
||||||
notifier.unregisterAll(&tc);
|
notifier.unregisterAll(&tc);
|
||||||
notifier.dispatch(.page_navigate, &.{
|
notifier.dispatch(.page_navigate, &.{
|
||||||
|
.page_id = 0,
|
||||||
.req_id = 1,
|
.req_id = 1,
|
||||||
.timestamp = 100,
|
.timestamp = 100,
|
||||||
.url = undefined,
|
.url = undefined,
|
||||||
.opts = .{},
|
.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(14, tc.page_navigate);
|
||||||
try testing.expectEqual(6, tc.page_navigated);
|
try testing.expectEqual(6, tc.page_navigated);
|
||||||
|
|
||||||
@@ -358,27 +369,27 @@ test "Notification" {
|
|||||||
// unregister
|
// unregister
|
||||||
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
|
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
|
||||||
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
|
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
|
||||||
notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
notifier.dispatch(.page_navigate, &.{ .page_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||||
notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .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(114, tc.page_navigate);
|
||||||
try testing.expectEqual(1006, tc.page_navigated);
|
try testing.expectEqual(1006, tc.page_navigated);
|
||||||
|
|
||||||
notifier.unregister(.page_navigate, &tc);
|
notifier.unregister(.page_navigate, &tc);
|
||||||
notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
notifier.dispatch(.page_navigate, &.{ .page_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||||
notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .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(114, tc.page_navigate);
|
||||||
try testing.expectEqual(2006, tc.page_navigated);
|
try testing.expectEqual(2006, tc.page_navigated);
|
||||||
|
|
||||||
notifier.unregister(.page_navigated, &tc);
|
notifier.unregister(.page_navigated, &tc);
|
||||||
notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
notifier.dispatch(.page_navigate, &.{ .page_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||||
notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .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(114, tc.page_navigate);
|
||||||
try testing.expectEqual(2006, tc.page_navigated);
|
try testing.expectEqual(2006, tc.page_navigated);
|
||||||
|
|
||||||
// already unregistered, try anyways
|
// already unregistered, try anyways
|
||||||
notifier.unregister(.page_navigated, &tc);
|
notifier.unregister(.page_navigated, &tc);
|
||||||
notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
notifier.dispatch(.page_navigate, &.{ .page_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||||
notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .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(114, tc.page_navigate);
|
||||||
try testing.expectEqual(2006, tc.page_navigated);
|
try testing.expectEqual(2006, tc.page_navigated);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,10 @@ pub const BUF_SIZE = 1024;
|
|||||||
|
|
||||||
const Page = @This();
|
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,
|
_session: *Session,
|
||||||
|
|
||||||
_event_manager: EventManager,
|
_event_manager: EventManager,
|
||||||
@@ -218,10 +222,10 @@ document: *Document,
|
|||||||
// DOM version used to invalidate cached state of "live" collections
|
// DOM version used to invalidate cached state of "live" collections
|
||||||
version: usize = 0,
|
version: usize = 0,
|
||||||
|
|
||||||
_req_id: ?usize = null,
|
_req_id: u32 = 0,
|
||||||
_navigated_options: ?NavigatedOpts = null,
|
_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) {
|
if (comptime IS_DEBUG) {
|
||||||
log.debug(.page, "page.init", .{});
|
log.debug(.page, "page.init", .{});
|
||||||
}
|
}
|
||||||
@@ -240,6 +244,7 @@ pub fn init(self: *Page, session: *Session) !void {
|
|||||||
})).asDocument();
|
})).asDocument();
|
||||||
|
|
||||||
self.* = .{
|
self.* = .{
|
||||||
|
.id = id,
|
||||||
.js = undefined,
|
.js = undefined,
|
||||||
.arena = page_arena,
|
.arena = page_arena,
|
||||||
.document = document,
|
.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
|
// This assumption may be false when CDP Page.addScriptToEvaluateOnNewDocument is implemented
|
||||||
self.documentIsComplete();
|
self.documentIsComplete();
|
||||||
|
|
||||||
self._session.notification.dispatch(.page_navigate, &.{
|
session.notification.dispatch(.page_navigate, &.{
|
||||||
|
.page_id = self.id,
|
||||||
.req_id = req_id,
|
.req_id = req_id,
|
||||||
.opts = opts,
|
.opts = opts,
|
||||||
.url = request_url,
|
.url = request_url,
|
||||||
@@ -421,14 +427,15 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Record telemetry for navigation
|
// Record telemetry for navigation
|
||||||
self._session.browser.app.telemetry.record(.{
|
session.browser.app.telemetry.record(.{
|
||||||
.navigate = .{
|
.navigate = .{
|
||||||
.tls = false, // about:blank is not TLS
|
.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,
|
.req_id = req_id,
|
||||||
.opts = .{
|
.opts = .{
|
||||||
.cdp_id = opts.cdp_id,
|
.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.
|
// 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;
|
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);
|
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.
|
// We dispatch page_navigate event before sending the request.
|
||||||
// It ensures the event page_navigated is not dispatched before this one.
|
// 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,
|
.req_id = req_id,
|
||||||
.opts = opts,
|
.opts = opts,
|
||||||
.url = self.url,
|
.url = self.url,
|
||||||
@@ -471,9 +479,9 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Record telemetry for navigation
|
// Record telemetry for navigation
|
||||||
self._session.browser.app.telemetry.record(.{ .navigate = .{
|
session.browser.app.telemetry.record(.{ .navigate = .{
|
||||||
.tls = std.ascii.startsWithIgnoreCase(self.url, "https://"),
|
.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;
|
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(.{
|
http_client.request(.{
|
||||||
.ctx = self,
|
.ctx = self,
|
||||||
.url = self.url,
|
.url = self.url,
|
||||||
|
.page_id = self.id,
|
||||||
.method = opts.method,
|
.method = opts.method,
|
||||||
.headers = headers,
|
.headers = headers,
|
||||||
.body = opts.body,
|
.body = opts.body,
|
||||||
.cookie_jar = &self._session.cookie_jar,
|
.cookie_jar = &session.cookie_jar,
|
||||||
.resource_type = .document,
|
.resource_type = .document,
|
||||||
.notification = self._session.notification,
|
.notification = self._session.notification,
|
||||||
.header_callback = pageHeaderDoneCallback,
|
.header_callback = pageHeaderDoneCallback,
|
||||||
@@ -611,12 +620,12 @@ pub fn documentIsComplete(self: *Page) void {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (IS_DEBUG) {
|
if (IS_DEBUG) {
|
||||||
std.debug.assert(self._req_id != null);
|
|
||||||
std.debug.assert(self._navigated_options != null);
|
std.debug.assert(self._navigated_options != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
self._session.notification.dispatch(.page_navigated, &.{
|
self._session.notification.dispatch(.page_navigated, &.{
|
||||||
.req_id = self._req_id.?,
|
.page_id = self.id,
|
||||||
|
.req_id = self._req_id,
|
||||||
.opts = self._navigated_options.?,
|
.opts = self._navigated_options.?,
|
||||||
.url = self.url,
|
.url = self.url,
|
||||||
.timestamp = timestamp(.monotonic),
|
.timestamp = timestamp(.monotonic),
|
||||||
@@ -1422,6 +1431,8 @@ pub fn deliverSlotchangeEvents(self: *Page) void {
|
|||||||
fn notifyNetworkIdle(self: *Page) void {
|
fn notifyNetworkIdle(self: *Page) void {
|
||||||
lp.assert(self._notified_network_idle == .done, "Page.notifyNetworkIdle", .{});
|
lp.assert(self._notified_network_idle == .done, "Page.notifyNetworkIdle", .{});
|
||||||
self._session.notification.dispatch(.page_network_idle, &.{
|
self._session.notification.dispatch(.page_network_idle, &.{
|
||||||
|
.page_id = self.id,
|
||||||
|
.req_id = self._req_id,
|
||||||
.timestamp = timestamp(.monotonic),
|
.timestamp = timestamp(.monotonic),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1429,6 +1440,8 @@ fn notifyNetworkIdle(self: *Page) void {
|
|||||||
fn notifyNetworkAlmostIdle(self: *Page) void {
|
fn notifyNetworkAlmostIdle(self: *Page) void {
|
||||||
lp.assert(self._notified_network_almost_idle == .done, "Page.notifyNetworkAlmostIdle", .{});
|
lp.assert(self._notified_network_almost_idle == .done, "Page.notifyNetworkAlmostIdle", .{});
|
||||||
self._session.notification.dispatch(.page_network_almost_idle, &.{
|
self._session.notification.dispatch(.page_network_almost_idle, &.{
|
||||||
|
.page_id = self.id,
|
||||||
|
.req_id = self._req_id,
|
||||||
.timestamp = timestamp(.monotonic),
|
.timestamp = timestamp(.monotonic),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,6 +259,7 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
|
|||||||
.url = url,
|
.url = url,
|
||||||
.ctx = script,
|
.ctx = script,
|
||||||
.method = .GET,
|
.method = .GET,
|
||||||
|
.page_id = page.id,
|
||||||
.headers = try self.getHeaders(url),
|
.headers = try self.getHeaders(url),
|
||||||
.blocking = is_blocking,
|
.blocking = is_blocking,
|
||||||
.cookie_jar = &page._session.cookie_jar,
|
.cookie_jar = &page._session.cookie_jar,
|
||||||
@@ -358,9 +359,11 @@ pub fn preloadImport(self: *ScriptManager, url: [:0]const u8, referrer: []const
|
|||||||
.manager = self,
|
.manager = self,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const page = self.page;
|
||||||
|
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
var ls: js.Local.Scope = undefined;
|
var ls: js.Local.Scope = undefined;
|
||||||
self.page.js.localScope(&ls);
|
page.js.localScope(&ls);
|
||||||
defer ls.deinit();
|
defer ls.deinit();
|
||||||
|
|
||||||
log.debug(.http, "script queue", .{
|
log.debug(.http, "script queue", .{
|
||||||
@@ -375,10 +378,11 @@ pub fn preloadImport(self: *ScriptManager, url: [:0]const u8, referrer: []const
|
|||||||
.url = url,
|
.url = url,
|
||||||
.ctx = script,
|
.ctx = script,
|
||||||
.method = .GET,
|
.method = .GET,
|
||||||
|
.page_id = page.id,
|
||||||
.headers = try self.getHeaders(url),
|
.headers = try self.getHeaders(url),
|
||||||
.cookie_jar = &self.page._session.cookie_jar,
|
.cookie_jar = &page._session.cookie_jar,
|
||||||
.resource_type = .script,
|
.resource_type = .script,
|
||||||
.notification = self.page._session.notification,
|
.notification = page._session.notification,
|
||||||
.start_callback = if (log.enabled(.http, .debug)) Script.startCallback else null,
|
.start_callback = if (log.enabled(.http, .debug)) Script.startCallback else null,
|
||||||
.header_callback = Script.headerCallback,
|
.header_callback = Script.headerCallback,
|
||||||
.data_callback = Script.dataCallback,
|
.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) {
|
if (comptime IS_DEBUG) {
|
||||||
var ls: js.Local.Scope = undefined;
|
var ls: js.Local.Scope = undefined;
|
||||||
self.page.js.localScope(&ls);
|
page.js.localScope(&ls);
|
||||||
defer ls.deinit();
|
defer ls.deinit();
|
||||||
|
|
||||||
log.debug(.http, "script queue", .{
|
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(.{
|
try self.client.request(.{
|
||||||
.url = url,
|
.url = url,
|
||||||
.method = .GET,
|
.method = .GET,
|
||||||
|
.page_id = page.id,
|
||||||
.headers = try self.getHeaders(url),
|
.headers = try self.getHeaders(url),
|
||||||
.ctx = script,
|
.ctx = script,
|
||||||
.resource_type = .script,
|
.resource_type = .script,
|
||||||
.cookie_jar = &self.page._session.cookie_jar,
|
.cookie_jar = &page._session.cookie_jar,
|
||||||
.notification = self.page._session.notification,
|
.notification = page._session.notification,
|
||||||
.start_callback = if (log.enabled(.http, .debug)) Script.startCallback else null,
|
.start_callback = if (log.enabled(.http, .debug)) Script.startCallback else null,
|
||||||
.header_callback = Script.headerCallback,
|
.header_callback = Script.headerCallback,
|
||||||
.data_callback = Script.dataCallback,
|
.data_callback = Script.dataCallback,
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ navigation: Navigation,
|
|||||||
|
|
||||||
page: ?Page,
|
page: ?Page,
|
||||||
|
|
||||||
|
page_id_gen: u32,
|
||||||
|
|
||||||
pub fn init(self: *Session, browser: *Browser, notification: *Notification) !void {
|
pub fn init(self: *Session, browser: *Browser, notification: *Notification) !void {
|
||||||
const allocator = browser.app.allocator;
|
const allocator = browser.app.allocator;
|
||||||
const arena = try browser.arena_pool.acquire();
|
const arena = try browser.arena_pool.acquire();
|
||||||
@@ -75,6 +77,7 @@ pub fn init(self: *Session, browser: *Browser, notification: *Notification) !voi
|
|||||||
.page = null,
|
.page = null,
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.history = .{},
|
.history = .{},
|
||||||
|
.page_id_gen = 0,
|
||||||
// The prototype (EventTarget) for Navigation is created when a Page is created.
|
// The prototype (EventTarget) for Navigation is created when a Page is created.
|
||||||
.navigation = .{ ._proto = undefined },
|
.navigation = .{ ._proto = undefined },
|
||||||
.storage_shed = .{},
|
.storage_shed = .{},
|
||||||
@@ -104,7 +107,11 @@ pub fn createPage(self: *Session) !*Page {
|
|||||||
|
|
||||||
self.page = @as(Page, undefined);
|
self.page = @as(Page, undefined);
|
||||||
const page = &self.page.?;
|
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.
|
// Creates a new NavigationEventTarget for this page.
|
||||||
try self.navigation.onNewPage(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", .{});
|
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.browser.env.memoryPressureNotification(.moderate);
|
||||||
|
|
||||||
self.page = @as(Page, undefined);
|
self.page = @as(Page, undefined);
|
||||||
const page = &self.page.?;
|
const page = &self.page.?;
|
||||||
try Page.init(page, self);
|
try Page.init(page, page_id, self);
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +170,11 @@ pub const WaitResult = enum {
|
|||||||
cdp_socket,
|
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 {
|
pub fn wait(self: *Session, wait_ms: u32) WaitResult {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (self.page) |*page| {
|
if (self.page) |*page| {
|
||||||
@@ -181,8 +197,9 @@ pub fn wait(self: *Session, wait_ms: u32) WaitResult {
|
|||||||
|
|
||||||
fn processScheduledNavigation(self: *Session) !void {
|
fn processScheduledNavigation(self: *Session) !void {
|
||||||
defer self.browser.arena_pool.reset(self.transfer_arena, 4 * 1024);
|
defer self.browser.arena_pool.reset(self.transfer_arena, 4 * 1024);
|
||||||
const url, const opts = blk: {
|
const url, const opts, const page_id = blk: {
|
||||||
const qn = self.page.?._queued_navigation.?;
|
const page = self.page.?;
|
||||||
|
const qn = page._queued_navigation.?;
|
||||||
// qn might not be safe to use after self.removePage is called, hence
|
// qn might not be safe to use after self.removePage is called, hence
|
||||||
// this block;
|
// this block;
|
||||||
const url = qn.url;
|
const url = qn.url;
|
||||||
@@ -193,7 +210,7 @@ fn processScheduledNavigation(self: *Session) !void {
|
|||||||
self.browser.http_client.abort();
|
self.browser.http_client.abort();
|
||||||
self.removePage();
|
self.removePage();
|
||||||
|
|
||||||
break :blk .{ url, opts };
|
break :blk .{ url, opts, page.id };
|
||||||
};
|
};
|
||||||
|
|
||||||
const page = self.createPage() catch |err| {
|
const page = self.createPage() catch |err| {
|
||||||
@@ -203,6 +220,7 @@ fn processScheduledNavigation(self: *Session) !void {
|
|||||||
});
|
});
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
page.id = page_id;
|
||||||
|
|
||||||
page.navigate(url, opts) catch |err| {
|
page.navigate(url, opts) catch |err| {
|
||||||
log.err(.browser, "queued navigation error", .{ .err = err, .url = url });
|
log.err(.browser, "queued navigation error", .{ .err = err, .url = url });
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise {
|
|||||||
|
|
||||||
try http_client.request(.{
|
try http_client.request(.{
|
||||||
.ctx = fetch,
|
.ctx = fetch,
|
||||||
|
.page_id = page.id,
|
||||||
.url = request._url,
|
.url = request._url,
|
||||||
.method = request._method,
|
.method = request._method,
|
||||||
.body = request._body,
|
.body = request._body,
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ pub fn send(self: *XMLHttpRequest, body_: ?[]const u8) !void {
|
|||||||
try http_client.request(.{
|
try http_client.request(.{
|
||||||
.ctx = self,
|
.ctx = self,
|
||||||
.url = self._url,
|
.url = self._url,
|
||||||
|
.page_id = page.id,
|
||||||
.method = self._method,
|
.method = self._method,
|
||||||
.headers = headers,
|
.headers = headers,
|
||||||
.body = self._request_body,
|
.body = self._request_body,
|
||||||
|
|||||||
@@ -30,12 +30,11 @@ const Browser = @import("../browser/Browser.zig");
|
|||||||
const Session = @import("../browser/Session.zig");
|
const Session = @import("../browser/Session.zig");
|
||||||
const HttpClient = @import("../http/Client.zig");
|
const HttpClient = @import("../http/Client.zig");
|
||||||
const Page = @import("../browser/Page.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 Notification = @import("../Notification.zig");
|
||||||
const InterceptState = @import("domains/fetch.zig").InterceptState;
|
const InterceptState = @import("domains/fetch.zig").InterceptState;
|
||||||
|
|
||||||
pub const URL_BASE = "chrome://newtab/";
|
pub const URL_BASE = "chrome://newtab/";
|
||||||
pub const LOADER_ID = "LOADERID24DD2FD56CF1EF33C965C79C";
|
|
||||||
|
|
||||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||||
|
|
||||||
@@ -45,7 +44,6 @@ pub const CDP = CDPT(struct {
|
|||||||
|
|
||||||
const SessionIdGen = Incrementing(u32, "SID");
|
const SessionIdGen = Incrementing(u32, "SID");
|
||||||
const TargetIdGen = Incrementing(u32, "TID");
|
const TargetIdGen = Incrementing(u32, "TID");
|
||||||
const LoaderIdGen = Incrementing(u32, "LID");
|
|
||||||
const BrowserContextIdGen = Incrementing(u32, "BID");
|
const BrowserContextIdGen = Incrementing(u32, "BID");
|
||||||
|
|
||||||
// Generic so that we can inject mocks into it.
|
// 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_auto_attach: bool = false,
|
||||||
|
|
||||||
target_id_gen: TargetIdGen = .{},
|
target_id_gen: TargetIdGen = .{},
|
||||||
loader_id_gen: LoaderIdGen = .{},
|
|
||||||
session_id_gen: SessionIdGen = .{},
|
session_id_gen: SessionIdGen = .{},
|
||||||
browser_context_id_gen: BrowserContextIdGen = .{},
|
browser_context_id_gen: BrowserContextIdGen = .{},
|
||||||
|
|
||||||
@@ -200,7 +197,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
.frameTree = .{
|
.frameTree = .{
|
||||||
.frame = .{
|
.frame = .{
|
||||||
.id = "TID-STARTUP-B",
|
.id = "TID-STARTUP-B",
|
||||||
.loaderId = LOADER_ID,
|
.loaderId = "LOADERID24DD2FD56CF1EF33C965C79C",
|
||||||
.securityOrigin = URL_BASE,
|
.securityOrigin = URL_BASE,
|
||||||
.url = "about:blank",
|
.url = "about:blank",
|
||||||
.secureContextType = "Secure",
|
.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
|
// 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
|
// deal with "pages" for now). Since we only allow 1 open page at a
|
||||||
// time, we only have 1 target_id.
|
// 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
|
// The CDP session_id. After the target/page is created, the client
|
||||||
// "attaches" to it (either explicitly or automatically). We return a
|
// "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.
|
// we should reject it.
|
||||||
session_id: ?[]const u8,
|
session_id: ?[]const u8,
|
||||||
|
|
||||||
loader_id: []const u8,
|
|
||||||
security_origin: []const u8,
|
security_origin: []const u8,
|
||||||
page_life_cycle_events: bool,
|
page_life_cycle_events: bool,
|
||||||
secure_context_type: []const u8,
|
secure_context_type: []const u8,
|
||||||
@@ -416,7 +412,6 @@ pub fn BrowserContext(comptime CDP_T: type) type {
|
|||||||
.session = session,
|
.session = session,
|
||||||
.security_origin = URL_BASE,
|
.security_origin = URL_BASE,
|
||||||
.secure_context_type = "Secure", // TODO = enum
|
.secure_context_type = "Secure", // TODO = enum
|
||||||
.loader_id = LOADER_ID,
|
|
||||||
.page_life_cycle_events = false, // TODO; Target based value
|
.page_life_cycle_events = false, // TODO; Target based value
|
||||||
.node_registry = registry,
|
.node_registry = registry,
|
||||||
.node_search_list = undefined,
|
.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 {
|
pub fn onPageNavigate(ctx: *anyopaque, msg: *const Notification.PageNavigate) !void {
|
||||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||||
defer self.resetNotificationArena();
|
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 {
|
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 {
|
pub fn onHttpRequestStart(ctx: *anyopaque, msg: *const Notification.RequestStart) !void {
|
||||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||||
defer self.resetNotificationArena();
|
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 {
|
pub fn onHttpRequestIntercept(ctx: *anyopaque, msg: *const Notification.RequestIntercept) !void {
|
||||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||||
defer self.resetNotificationArena();
|
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 {
|
pub fn onHttpRequestFail(ctx: *anyopaque, msg: *const Notification.RequestFail) !void {
|
||||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||||
defer self.resetNotificationArena();
|
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 {
|
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 {
|
pub fn onHttpRequestDone(ctx: *anyopaque, msg: *const Notification.RequestDone) !void {
|
||||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||||
defer self.resetNotificationArena();
|
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 {
|
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 {
|
pub fn onHttpRequestAuthRequired(ctx: *anyopaque, data: *const Notification.RequestAuthRequired) !void {
|
||||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||||
defer self.resetNotificationArena();
|
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 {
|
fn resetNotificationArena(self: *Self) void {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const id = @import("../id.zig");
|
||||||
|
|
||||||
pub fn processMessage(cmd: anytype) !void {
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
const action = std.meta.stringToEnum(enum {
|
const action = std.meta.stringToEnum(enum {
|
||||||
@@ -46,15 +47,18 @@ fn getFullAXTree(cmd: anytype) !void {
|
|||||||
})) orelse return error.InvalidParams;
|
})) orelse return error.InvalidParams;
|
||||||
|
|
||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
|
const session = bc.session;
|
||||||
|
|
||||||
if (params.frameId) |frameId| {
|
const page = blk: {
|
||||||
const target_id = bc.target_id orelse return error.TargetNotLoaded;
|
const frame_id = params.frameId orelse {
|
||||||
if (std.mem.eql(u8, target_id, frameId) == false) {
|
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.", .{});
|
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 doc = page.window._document.asNode();
|
||||||
const node = try bc.node_registry.register(doc);
|
const node = try bc.node_registry.register(doc);
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const id = @import("../id.zig");
|
||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
const Node = @import("../Node.zig");
|
const Node = @import("../Node.zig");
|
||||||
const DOMNode = @import("../../browser/webapi/Node.zig");
|
const DOMNode = @import("../../browser/webapi/Node.zig");
|
||||||
@@ -497,12 +498,11 @@ fn getFrameOwner(cmd: anytype) !void {
|
|||||||
})) orelse return error.InvalidParams;
|
})) orelse return error.InvalidParams;
|
||||||
|
|
||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
const target_id = bc.target_id orelse return error.TargetNotLoaded;
|
const page_id = try id.toPageId(.frame_id, params.frameId);
|
||||||
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 = 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());
|
const node = try bc.node_registry.register(page.window._document.asNode());
|
||||||
return cmd.sendResult(.{ .nodeId = node.id, .backendNodeId = node.id }, .{});
|
return cmd.sendResult(.{ .nodeId = node.id, .backendNodeId = node.id }, .{});
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const id = @import("../id.zig");
|
||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
const network = @import("network.zig");
|
const network = @import("network.zig");
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ pub fn processMessage(cmd: anytype) !void {
|
|||||||
// Stored in CDP
|
// Stored in CDP
|
||||||
pub const InterceptState = struct {
|
pub const InterceptState = struct {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
waiting: std.AutoArrayHashMapUnmanaged(u64, *Http.Transfer),
|
waiting: std.AutoArrayHashMapUnmanaged(u32, *Http.Transfer),
|
||||||
|
|
||||||
pub fn init(allocator: Allocator) !InterceptState {
|
pub fn init(allocator: Allocator) !InterceptState {
|
||||||
return .{
|
return .{
|
||||||
@@ -65,8 +66,8 @@ pub const InterceptState = struct {
|
|||||||
return self.waiting.put(self.allocator, transfer.id, transfer);
|
return self.waiting.put(self.allocator, transfer.id, transfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(self: *InterceptState, id: u64) ?*Http.Transfer {
|
pub fn remove(self: *InterceptState, request_id: u32) ?*Http.Transfer {
|
||||||
const entry = self.waiting.fetchSwapRemove(id) orelse return null;
|
const entry = self.waiting.fetchSwapRemove(request_id) orelse return null;
|
||||||
return entry.value;
|
return entry.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,13 +179,11 @@ fn arePatternsSupported(patterns: []RequestPattern) bool {
|
|||||||
return true;
|
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
|
// detachTarget could be called, in which case, we still have a page doing
|
||||||
// things, but no session.
|
// things, but no session.
|
||||||
const session_id = bc.session_id orelse return;
|
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.
|
// 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.
|
// 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?
|
// 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.intercept_state.put(transfer);
|
||||||
|
|
||||||
try bc.cdp.sendEvent("Fetch.requestPaused", .{
|
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),
|
.request = network.TransferAsRequestWriter.init(transfer),
|
||||||
.frameId = target_id,
|
|
||||||
.resourceType = switch (transfer.req.resource_type) {
|
.resourceType = switch (transfer.req.resource_type) {
|
||||||
.script => "Script",
|
.script => "Script",
|
||||||
.xhr => "XHR",
|
.xhr => "XHR",
|
||||||
.document => "Document",
|
.document => "Document",
|
||||||
.fetch => "Fetch",
|
.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 });
|
}, .{ .session_id = session_id });
|
||||||
|
|
||||||
log.debug(.cdp, "request intercept", .{
|
log.debug(.cdp, "request intercept", .{
|
||||||
@@ -218,7 +217,7 @@ pub fn requestIntercept(arena: Allocator, bc: anytype, intercept: *const Notific
|
|||||||
fn continueRequest(cmd: anytype) !void {
|
fn continueRequest(cmd: anytype) !void {
|
||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
const params = (try cmd.params(struct {
|
const params = (try cmd.params(struct {
|
||||||
requestId: []const u8, // "INTERCEPT-{d}"
|
requestId: []const u8, // INT-{d}"
|
||||||
url: ?[]const u8 = null,
|
url: ?[]const u8 = null,
|
||||||
method: ?[]const u8 = null,
|
method: ?[]const u8 = null,
|
||||||
postData: ?[]const u8 = null,
|
postData: ?[]const u8 = null,
|
||||||
@@ -278,7 +277,7 @@ const AuthChallengeResponse = enum {
|
|||||||
fn continueWithAuth(cmd: anytype) !void {
|
fn continueWithAuth(cmd: anytype) !void {
|
||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
const params = (try cmd.params(struct {
|
const params = (try cmd.params(struct {
|
||||||
requestId: []const u8, // "INTERCEPT-{d}"
|
requestId: []const u8, // "INT-{d}"
|
||||||
authChallengeResponse: struct {
|
authChallengeResponse: struct {
|
||||||
response: AuthChallengeResponse,
|
response: AuthChallengeResponse,
|
||||||
username: []const u8 = "",
|
username: []const u8 = "",
|
||||||
@@ -322,7 +321,7 @@ fn fulfillRequest(cmd: anytype) !void {
|
|||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
|
|
||||||
const params = (try cmd.params(struct {
|
const params = (try cmd.params(struct {
|
||||||
requestId: []const u8, // "INTERCEPT-{d}"
|
requestId: []const u8, // "INT-{d}"
|
||||||
responseCode: u16,
|
responseCode: u16,
|
||||||
responseHeaders: ?[]const Http.Header = null,
|
responseHeaders: ?[]const Http.Header = null,
|
||||||
binaryResponseHeaders: ?[]const u8 = null,
|
binaryResponseHeaders: ?[]const u8 = null,
|
||||||
@@ -363,7 +362,7 @@ fn fulfillRequest(cmd: anytype) !void {
|
|||||||
fn failRequest(cmd: anytype) !void {
|
fn failRequest(cmd: anytype) !void {
|
||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
const params = (try cmd.params(struct {
|
const params = (try cmd.params(struct {
|
||||||
requestId: []const u8, // "INTERCEPT-{d}"
|
requestId: []const u8, // "INT-{d}"
|
||||||
errorReason: ErrorReason,
|
errorReason: ErrorReason,
|
||||||
})) orelse return error.InvalidParams;
|
})) orelse return error.InvalidParams;
|
||||||
|
|
||||||
@@ -382,13 +381,11 @@ fn failRequest(cmd: anytype) !void {
|
|||||||
return cmd.sendResult(null, .{});
|
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
|
// detachTarget could be called, in which case, we still have a page doing
|
||||||
// things, but no session.
|
// things, but no session.
|
||||||
const session_id = bc.session_id orelse return;
|
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.
|
// 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.
|
// 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?
|
// 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;
|
const challenge = transfer._auth_challenge orelse return error.NullAuthChallenge;
|
||||||
|
|
||||||
try bc.cdp.sendEvent("Fetch.authRequired", .{
|
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),
|
.request = network.TransferAsRequestWriter.init(transfer),
|
||||||
.frameId = target_id,
|
|
||||||
.resourceType = switch (transfer.req.resource_type) {
|
.resourceType = switch (transfer.req.resource_type) {
|
||||||
.script => "Script",
|
.script => "Script",
|
||||||
.xhr => "XHR",
|
.xhr => "XHR",
|
||||||
@@ -414,7 +411,7 @@ pub fn requestAuthRequired(arena: Allocator, bc: anytype, intercept: *const Noti
|
|||||||
.scheme = if (challenge.scheme == .digest) "digest" else "basic",
|
.scheme = if (challenge.scheme == .digest) "digest" else "basic",
|
||||||
.realm = challenge.realm,
|
.realm = challenge.realm,
|
||||||
},
|
},
|
||||||
.networkId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}),
|
.networkId = &id.toRequestId(transfer.id),
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
|
|
||||||
log.debug(.cdp, "request auth required", .{
|
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;
|
intercept.wait_for_interception.* = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get u64 from requestId which is formatted as: "INTERCEPT-{d}"
|
// Get u32 from requestId which is formatted as: "INT-{d}"
|
||||||
fn idFromRequestId(request_id: []const u8) !u64 {
|
fn idFromRequestId(request_id: []const u8) !u32 {
|
||||||
if (!std.mem.startsWith(u8, request_id, "INTERCEPT-")) {
|
if (!std.mem.startsWith(u8, request_id, "INT-")) {
|
||||||
return error.InvalidParams;
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,30 +116,3 @@ fn insertText(cmd: anytype) !void {
|
|||||||
|
|
||||||
try cmd.sendResult(null, .{});
|
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);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const lp = @import("lightpanda");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const CdpStorage = @import("storage.zig");
|
const CdpStorage = @import("storage.zig");
|
||||||
|
|
||||||
|
const id = @import("../id.zig");
|
||||||
const URL = @import("../../browser/URL.zig");
|
const URL = @import("../../browser/URL.zig");
|
||||||
const Transfer = @import("../../http/Client.zig").Transfer;
|
const Transfer = @import("../../http/Client.zig").Transfer;
|
||||||
const Notification = @import("../../Notification.zig");
|
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
|
// 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
|
// sent Target.closeTarget. In that case, bc.session_id will be cleared
|
||||||
// already, and we can skip sending these messages to the client.
|
// 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
|
// We're missing a bunch of fields, but, for now, this seems like enough
|
||||||
try bc.cdp.sendEvent("Network.loadingFailed", .{
|
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?
|
// Seems to be what chrome answers with. I assume it depends on the type of error?
|
||||||
.type = "Ping",
|
.type = "Ping",
|
||||||
.errorText = msg.err,
|
.errorText = msg.err,
|
||||||
@@ -228,28 +230,27 @@ pub fn httpRequestFail(arena: Allocator, bc: anytype, msg: *const Notification.R
|
|||||||
}, .{ .session_id = session_id });
|
}, .{ .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
|
// detachTarget could be called, in which case, we still have a page doing
|
||||||
// things, but no session.
|
// things, but no session.
|
||||||
const session_id = bc.session_id orelse return;
|
const session_id = bc.session_id orelse return;
|
||||||
|
|
||||||
const target_id = bc.target_id orelse unreachable;
|
const transfer = msg.transfer;
|
||||||
const page = bc.session.currentPage() orelse unreachable;
|
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
|
// Modify request with extra CDP headers
|
||||||
for (bc.extra_headers.items) |extra| {
|
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
|
// We're missing a bunch of fields, but, for now, this seems like enough
|
||||||
try bc.cdp.sendEvent("Network.requestWillBeSent", .{
|
try bc.cdp.sendEvent("Network.requestWillBeSent", .{
|
||||||
.requestId = loader_id,
|
.loaderId = &id.toLoaderId(transfer.id),
|
||||||
.frameId = target_id,
|
.requestId = &id.toRequestId(transfer.id),
|
||||||
.loaderId = loader_id,
|
.frameId = &id.toFrameId(page_id),
|
||||||
.type = msg.transfer.req.resource_type.string(),
|
.type = req.resource_type.string(),
|
||||||
.documentURL = page.url,
|
.documentURL = page.url,
|
||||||
.request = TransferAsRequestWriter.init(transfer),
|
.request = TransferAsRequestWriter.init(transfer),
|
||||||
.initiator = .{ .type = "other" },
|
.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
|
// detachTarget could be called, in which case, we still have a page doing
|
||||||
// things, but no session.
|
// things, but no session.
|
||||||
const session_id = bc.session_id orelse return;
|
const session_id = bc.session_id orelse return;
|
||||||
const target_id = bc.target_id orelse unreachable;
|
|
||||||
|
|
||||||
const transfer = msg.transfer;
|
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
|
// We're missing a bunch of fields, but, for now, this seems like enough
|
||||||
try bc.cdp.sendEvent("Network.responseReceived", .{
|
try bc.cdp.sendEvent("Network.responseReceived", .{
|
||||||
.requestId = loader_id,
|
.loaderId = &id.toLoaderId(transfer.id),
|
||||||
.frameId = target_id,
|
.requestId = &id.toRequestId(transfer.id),
|
||||||
.loaderId = loader_id,
|
.frameId = &id.toFrameId(transfer.req.page_id),
|
||||||
.response = TransferAsResponseWriter.init(arena, msg.transfer),
|
.response = TransferAsResponseWriter.init(arena, msg.transfer),
|
||||||
.hasExtraInfo = false, // TODO change after adding Network.responseReceivedExtraInfo
|
.hasExtraInfo = false, // TODO change after adding Network.responseReceivedExtraInfo
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .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
|
// detachTarget could be called, in which case, we still have a page doing
|
||||||
// things, but no session.
|
// things, but no session.
|
||||||
const session_id = bc.session_id orelse return;
|
const session_id = bc.session_id orelse return;
|
||||||
|
const transfer = msg.transfer;
|
||||||
try bc.cdp.sendEvent("Network.loadingFinished", .{
|
try bc.cdp.sendEvent("Network.loadingFinished", .{
|
||||||
.requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{msg.transfer.id}),
|
.requestId = &id.toRequestId(transfer.id),
|
||||||
.encodedDataLength = msg.transfer.bytes_received,
|
.encodedDataLength = transfer.bytes_received,
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const lp = @import("lightpanda");
|
const lp = @import("lightpanda");
|
||||||
|
|
||||||
|
const id = @import("../id.zig");
|
||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
const js = @import("../../browser/js/js.zig");
|
const js = @import("../../browser/js/js.zig");
|
||||||
const Page = @import("../../browser/Page.zig");
|
const Page = @import("../../browser/Page.zig");
|
||||||
@@ -73,9 +74,9 @@ fn getFrameTree(cmd: anytype) !void {
|
|||||||
return cmd.sendResult(.{
|
return cmd.sendResult(.{
|
||||||
.frameTree = .{
|
.frameTree = .{
|
||||||
.frame = Frame{
|
.frame = Frame{
|
||||||
.id = target_id,
|
.id = &target_id,
|
||||||
.loaderId = bc.loader_id,
|
|
||||||
.securityOrigin = bc.security_origin,
|
.securityOrigin = bc.security_origin,
|
||||||
|
.loaderId = "LID-0000000001",
|
||||||
.url = bc.getURL() orelse "about:blank",
|
.url = bc.getURL() orelse "about:blank",
|
||||||
.secureContextType = bc.secure_context_type,
|
.secureContextType = bc.secure_context_type,
|
||||||
},
|
},
|
||||||
@@ -103,18 +104,21 @@ fn setLifecycleEventsEnabled(cmd: anytype) !void {
|
|||||||
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
||||||
|
|
||||||
if (page._load_state == .complete) {
|
if (page._load_state == .complete) {
|
||||||
|
const frame_id = &id.toFrameId(page.id);
|
||||||
|
const loader_id = &id.toLoaderId(page._req_id);
|
||||||
|
|
||||||
const now = timestampF(.monotonic);
|
const now = timestampF(.monotonic);
|
||||||
try sendPageLifecycle(bc, "DOMContentLoaded", now);
|
try sendPageLifecycle(bc, "DOMContentLoaded", now, frame_id, loader_id);
|
||||||
try sendPageLifecycle(bc, "load", now);
|
try sendPageLifecycle(bc, "load", now, frame_id, loader_id);
|
||||||
|
|
||||||
const http_client = page._session.browser.http_client;
|
const http_client = page._session.browser.http_client;
|
||||||
const http_active = http_client.active;
|
const http_active = http_client.active;
|
||||||
const total_network_activity = http_active + http_client.intercepted;
|
const total_network_activity = http_active + http_client.intercepted;
|
||||||
if (page._notified_network_almost_idle.check(total_network_activity <= 2)) {
|
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)) {
|
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
|
// detachTarget could be called, in which case, we still have a page doing
|
||||||
// things, but no session.
|
// things, but no session.
|
||||||
const session_id = bc.session_id orelse return;
|
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();
|
bc.reset();
|
||||||
|
|
||||||
|
const frame_id = &id.toFrameId(event.page_id);
|
||||||
|
const loader_id = &id.toLoaderId(event.req_id);
|
||||||
|
|
||||||
var cdp = bc.cdp;
|
var cdp = bc.cdp;
|
||||||
const reason_: ?[]const u8 = switch (event.opts.reason) {
|
const reason_: ?[]const u8 = switch (event.opts.reason) {
|
||||||
.anchor => "anchorClick",
|
.anchor => "anchorClick",
|
||||||
@@ -250,14 +253,14 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
|
|||||||
};
|
};
|
||||||
if (reason_) |reason| {
|
if (reason_) |reason| {
|
||||||
try cdp.sendEvent("Page.frameScheduledNavigation", .{
|
try cdp.sendEvent("Page.frameScheduledNavigation", .{
|
||||||
.frameId = target_id,
|
.frameId = frame_id,
|
||||||
.delay = 0,
|
.delay = 0,
|
||||||
.reason = reason,
|
.reason = reason,
|
||||||
.url = event.url,
|
.url = event.url,
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
|
|
||||||
try cdp.sendEvent("Page.frameRequestedNavigation", .{
|
try cdp.sendEvent("Page.frameRequestedNavigation", .{
|
||||||
.frameId = target_id,
|
.frameId = frame_id,
|
||||||
.reason = reason,
|
.reason = reason,
|
||||||
.url = event.url,
|
.url = event.url,
|
||||||
.disposition = "currentTab",
|
.disposition = "currentTab",
|
||||||
@@ -266,7 +269,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
|
|||||||
|
|
||||||
// frameStartedNavigating event
|
// frameStartedNavigating event
|
||||||
try cdp.sendEvent("Page.frameStartedNavigating", .{
|
try cdp.sendEvent("Page.frameStartedNavigating", .{
|
||||||
.frameId = target_id,
|
.frameId = frame_id,
|
||||||
.url = event.url,
|
.url = event.url,
|
||||||
.loaderId = loader_id,
|
.loaderId = loader_id,
|
||||||
.navigationType = "differentDocument",
|
.navigationType = "differentDocument",
|
||||||
@@ -274,7 +277,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
|
|||||||
|
|
||||||
// frameStartedLoading event
|
// frameStartedLoading event
|
||||||
try cdp.sendEvent("Page.frameStartedLoading", .{
|
try cdp.sendEvent("Page.frameStartedLoading", .{
|
||||||
.frameId = target_id,
|
.frameId = frame_id,
|
||||||
}, .{ .session_id = session_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
|
// detachTarget could be called, in which case, we still have a page doing
|
||||||
// things, but no session.
|
// things, but no session.
|
||||||
const session_id = bc.session_id orelse return;
|
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 timestamp = event.timestamp;
|
||||||
|
const frame_id = &id.toFrameId(event.page_id);
|
||||||
|
const loader_id = &id.toLoaderId(event.req_id);
|
||||||
|
|
||||||
var cdp = bc.cdp;
|
var cdp = bc.cdp;
|
||||||
|
|
||||||
@@ -316,7 +320,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
|
|||||||
try cdp.sendJSON(.{
|
try cdp.sendJSON(.{
|
||||||
.id = input_id,
|
.id = input_id,
|
||||||
.result = .{
|
.result = .{
|
||||||
.frameId = target_id,
|
.frameId = frame_id,
|
||||||
.loaderId = loader_id,
|
.loaderId = loader_id,
|
||||||
},
|
},
|
||||||
.sessionId = session_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) {
|
if (bc.page_life_cycle_events) {
|
||||||
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
|
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
|
||||||
.name = "init",
|
.name = "init",
|
||||||
.frameId = target_id,
|
.frameId = frame_id,
|
||||||
.loaderId = loader_id,
|
.loaderId = loader_id,
|
||||||
.timestamp = event.timestamp,
|
.timestamp = event.timestamp,
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
@@ -345,7 +349,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
|
|||||||
|
|
||||||
if (reason_ != null) {
|
if (reason_ != null) {
|
||||||
try cdp.sendEvent("Page.frameClearedScheduledNavigation", .{
|
try cdp.sendEvent("Page.frameClearedScheduledNavigation", .{
|
||||||
.frameId = target_id,
|
.frameId = frame_id,
|
||||||
}, .{ .session_id = session_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 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;
|
var ls: js.Local.Scope = undefined;
|
||||||
page.js.localScope(&ls);
|
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| {
|
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
|
// 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", .{
|
try cdp.sendEvent("Page.frameNavigated", .{
|
||||||
.type = "Navigation",
|
.type = "Navigation",
|
||||||
.frame = Frame{
|
.frame = Frame{
|
||||||
.id = target_id,
|
.id = frame_id,
|
||||||
.url = event.url,
|
.url = event.url,
|
||||||
.loaderId = loader_id,
|
.loaderId = loader_id,
|
||||||
.securityOrigin = bc.security_origin,
|
.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{
|
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
|
||||||
.timestamp = timestamp,
|
.timestamp = timestamp,
|
||||||
.name = "DOMContentLoaded",
|
.name = "DOMContentLoaded",
|
||||||
.frameId = target_id,
|
.frameId = frame_id,
|
||||||
.loaderId = loader_id,
|
.loaderId = loader_id,
|
||||||
}, .{ .session_id = session_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{
|
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
|
||||||
.timestamp = timestamp,
|
.timestamp = timestamp,
|
||||||
.name = "load",
|
.name = "load",
|
||||||
.frameId = target_id,
|
.frameId = frame_id,
|
||||||
.loaderId = loader_id,
|
.loaderId = loader_id,
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
}
|
}
|
||||||
|
|
||||||
// frameStoppedLoading
|
// frameStoppedLoading
|
||||||
return cdp.sendEvent("Page.frameStoppedLoading", .{
|
return cdp.sendEvent("Page.frameStoppedLoading", .{
|
||||||
.frameId = target_id,
|
.frameId = frame_id,
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pageNetworkIdle(bc: anytype, event: *const Notification.PageNetworkIdle) !void {
|
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 {
|
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
|
// detachTarget could be called, in which case, we still have a page doing
|
||||||
// things, but no session.
|
// things, but no session.
|
||||||
const session_id = bc.session_id orelse return;
|
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{
|
return bc.cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
|
||||||
.name = name,
|
.name = name,
|
||||||
.frameId = target_id,
|
.frameId = frame_id,
|
||||||
.loaderId = loader_id,
|
.loaderId = loader_id,
|
||||||
.timestamp = timestamp,
|
.timestamp = timestamp,
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
@@ -487,15 +489,15 @@ test "cdp.page: getFrameTree" {
|
|||||||
try ctx.expectSentError(-31998, "BrowserContextNotLoaded", .{ .id = 10 });
|
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.processMessage(.{ .id = 11, .method = "Page.getFrameTree" });
|
||||||
try ctx.expectSentResult(.{
|
try ctx.expectSentResult(.{
|
||||||
.frameTree = .{
|
.frameTree = .{
|
||||||
.frame = .{
|
.frame = .{
|
||||||
.id = "TID-3",
|
.id = "FID-000000000X",
|
||||||
.loaderId = bc.loader_id,
|
.loaderId = "LID-0000000001",
|
||||||
.url = "about:blank",
|
.url = "http://127.0.0.1:9582/src/browser/tests/hi.html",
|
||||||
.domainAndRegistry = "",
|
.domainAndRegistry = "",
|
||||||
.securityOrigin = bc.security_origin,
|
.securityOrigin = bc.security_origin,
|
||||||
.mimeType = "text/html",
|
.mimeType = "text/html",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const lp = @import("lightpanda");
|
const lp = @import("lightpanda");
|
||||||
|
|
||||||
|
const id = @import("../id.zig");
|
||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
const js = @import("../../browser/js/js.zig");
|
const js = @import("../../browser/js/js.zig");
|
||||||
|
|
||||||
@@ -66,11 +68,11 @@ fn getTargets(cmd: anytype) !void {
|
|||||||
}, .{ .include_session_id = false });
|
}, .{ .include_session_id = false });
|
||||||
};
|
};
|
||||||
|
|
||||||
const target_id = bc.target_id orelse {
|
const target_id = &(bc.target_id orelse {
|
||||||
return cmd.sendResult(.{
|
return cmd.sendResult(.{
|
||||||
.targetInfos = [_]TargetInfo{},
|
.targetInfos = [_]TargetInfo{},
|
||||||
}, .{ .include_session_id = false });
|
}, .{ .include_session_id = false });
|
||||||
};
|
});
|
||||||
|
|
||||||
return cmd.sendResult(.{
|
return cmd.sendResult(.{
|
||||||
.targetInfos = [_]TargetInfo{.{
|
.targetInfos = [_]TargetInfo{.{
|
||||||
@@ -171,11 +173,12 @@ fn createTarget(cmd: anytype) !void {
|
|||||||
// if target_id is null, we should never have a session_id
|
// 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", .{});
|
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();
|
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;
|
var ls: js.Local.Scope = undefined;
|
||||||
page.js.localScope(&ls);
|
page.js.localScope(&ls);
|
||||||
@@ -195,7 +198,6 @@ fn createTarget(cmd: anytype) !void {
|
|||||||
// change CDP state
|
// change CDP state
|
||||||
bc.security_origin = "://";
|
bc.security_origin = "://";
|
||||||
bc.secure_context_type = "InsecureScheme";
|
bc.secure_context_type = "InsecureScheme";
|
||||||
bc.loader_id = LOADER_ID;
|
|
||||||
|
|
||||||
// send targetCreated event
|
// send targetCreated event
|
||||||
// TODO: should this only be sent when Target.setDiscoverTargets
|
// TODO: should this only be sent when Target.setDiscoverTargets
|
||||||
@@ -234,7 +236,7 @@ fn attachToTarget(cmd: anytype) !void {
|
|||||||
})) orelse return error.InvalidParams;
|
})) orelse return error.InvalidParams;
|
||||||
|
|
||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
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) {
|
if (std.mem.eql(u8, target_id, params.targetId) == false) {
|
||||||
return error.UnknownTargetId;
|
return error.UnknownTargetId;
|
||||||
}
|
}
|
||||||
@@ -255,7 +257,7 @@ fn closeTarget(cmd: anytype) !void {
|
|||||||
})) orelse return error.InvalidParams;
|
})) orelse return error.InvalidParams;
|
||||||
|
|
||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
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) {
|
if (std.mem.eql(u8, target_id, params.targetId) == false) {
|
||||||
return error.UnknownTargetId;
|
return error.UnknownTargetId;
|
||||||
}
|
}
|
||||||
@@ -298,7 +300,7 @@ fn getTargetInfo(cmd: anytype) !void {
|
|||||||
|
|
||||||
if (params.targetId) |param_target_id| {
|
if (params.targetId) |param_target_id| {
|
||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
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) {
|
if (std.mem.eql(u8, target_id, param_target_id) == false) {
|
||||||
return error.UnknownTargetId;
|
return error.UnknownTargetId;
|
||||||
}
|
}
|
||||||
@@ -415,10 +417,11 @@ fn setAutoAttach(cmd: anytype) !void {
|
|||||||
// autoAttach is set to true, we must attach to all existing targets.
|
// autoAttach is set to true, we must attach to all existing targets.
|
||||||
if (cmd.browser_context) |bc| {
|
if (cmd.browser_context) |bc| {
|
||||||
if (bc.target_id == null) {
|
if (bc.target_id == null) {
|
||||||
// hasn't attached yet
|
if (bc.session.currentPage()) |page| {
|
||||||
const target_id = cmd.cdp.target_id_gen.next();
|
// the target_id == the frame_id of the "root" page
|
||||||
try doAttachtoTarget(cmd, target_id);
|
bc.target_id = id.toFrameId(page.id);
|
||||||
bc.target_id = target_id;
|
try doAttachtoTarget(cmd, &bc.target_id.?);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// should we send something here?
|
// should we send something here?
|
||||||
return;
|
return;
|
||||||
@@ -612,14 +615,14 @@ test "cdp.target: closeTarget" {
|
|||||||
|
|
||||||
// pretend we createdTarget first
|
// pretend we createdTarget first
|
||||||
_ = try bc.session.createPage();
|
_ = 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 testing.expectError(error.UnknownTargetId, ctx.processMessage(.{ .id = 10, .method = "Target.closeTarget", .params = .{ .targetId = "TID-8" } }));
|
||||||
try ctx.expectSentError(-31998, "UnknownTargetId", .{ .id = 10 });
|
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 ctx.expectSentResult(.{ .success = true }, .{ .id = 11 });
|
||||||
try testing.expectEqual(null, bc.session.page);
|
try testing.expectEqual(null, bc.session.page);
|
||||||
try testing.expectEqual(null, bc.target_id);
|
try testing.expectEqual(null, bc.target_id);
|
||||||
@@ -643,14 +646,14 @@ test "cdp.target: attachToTarget" {
|
|||||||
|
|
||||||
// pretend we createdTarget first
|
// pretend we createdTarget first
|
||||||
_ = try bc.session.createPage();
|
_ = 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 testing.expectError(error.UnknownTargetId, ctx.processMessage(.{ .id = 10, .method = "Target.attachToTarget", .params = .{ .targetId = "TID-8" } }));
|
||||||
try ctx.expectSentError(-31998, "UnknownTargetId", .{ .id = 10 });
|
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.?;
|
const session_id = bc.session_id.?;
|
||||||
try ctx.expectSentResult(.{ .sessionId = session_id }, .{ .id = 11 });
|
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.? } }, .{});
|
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
|
// pretend we createdTarget first
|
||||||
_ = try bc.session.createPage();
|
_ = 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 testing.expectError(error.UnknownTargetId, ctx.processMessage(.{ .id = 10, .method = "Target.getTargetInfo", .params = .{ .targetId = "TID-8" } }));
|
||||||
try ctx.expectSentError(-31998, "UnknownTargetId", .{ .id = 10 });
|
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(.{
|
try ctx.expectSentResult(.{
|
||||||
.targetInfo = .{
|
.targetInfo = .{
|
||||||
.targetId = "TID-A",
|
.targetId = "TID-000000000C",
|
||||||
.type = "page",
|
.type = "page",
|
||||||
.title = "",
|
.title = "",
|
||||||
.url = "about:blank",
|
.url = "about:blank",
|
||||||
|
|||||||
184
src/cdp/id.zig
Normal file
184
src/cdp/id.zig
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||||
|
//
|
||||||
|
// Francis Bouvier <francis@lightpanda.io>
|
||||||
|
// Pierre Tachoire <pierre@lightpanda.io>
|
||||||
|
//
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
@@ -92,7 +92,7 @@ const TestContext = struct {
|
|||||||
|
|
||||||
const BrowserContextOpts = struct {
|
const BrowserContextOpts = struct {
|
||||||
id: ?[]const u8 = null,
|
id: ?[]const u8 = null,
|
||||||
target_id: ?[]const u8 = null,
|
target_id: ?[14]u8 = null,
|
||||||
session_id: ?[]const u8 = null,
|
session_id: ?[]const u8 = null,
|
||||||
url: ?[:0]const u8 = null,
|
url: ?[:0]const u8 = null,
|
||||||
};
|
};
|
||||||
@@ -122,7 +122,7 @@ const TestContext = struct {
|
|||||||
bc.session_id = "SID-X";
|
bc.session_id = "SID-X";
|
||||||
}
|
}
|
||||||
if (bc.target_id == null) {
|
if (bc.target_id == null) {
|
||||||
bc.target_id = "TID-X";
|
bc.target_id = "TID-000000000Z".*;
|
||||||
}
|
}
|
||||||
const page = try bc.session.createPage();
|
const page = try bc.session.createPage();
|
||||||
const full_url = try std.fmt.allocPrintSentinel(
|
const full_url = try std.fmt.allocPrintSentinel(
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ multi: *c.CURLM,
|
|||||||
handles: Handles,
|
handles: Handles,
|
||||||
|
|
||||||
// Use to generate the next request ID
|
// 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.
|
// When handles has no more available easys, requests get queued.
|
||||||
queue: TransferQueue,
|
queue: TransferQueue,
|
||||||
@@ -336,6 +336,7 @@ fn fetchRobotsThenProcessRequest(self: *Client, robots_url: [:0]const u8, req: R
|
|||||||
.method = .GET,
|
.method = .GET,
|
||||||
.headers = headers,
|
.headers = headers,
|
||||||
.blocking = false,
|
.blocking = false,
|
||||||
|
.page_id = req.page_id,
|
||||||
.cookie_jar = req.cookie_jar,
|
.cookie_jar = req.cookie_jar,
|
||||||
.notification = req.notification,
|
.notification = req.notification,
|
||||||
.resource_type = .fetch,
|
.resource_type = .fetch,
|
||||||
@@ -562,12 +563,12 @@ pub fn fulfillTransfer(self: *Client, transfer: *Transfer, status: u16, headers:
|
|||||||
transfer._intercept_state = .fulfilled;
|
transfer._intercept_state = .fulfilled;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nextReqId(self: *Client) usize {
|
pub fn nextReqId(self: *Client) u32 {
|
||||||
return self.next_request_id + 1;
|
return self.next_request_id +% 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn incrReqId(self: *Client) usize {
|
pub fn incrReqId(self: *Client) u32 {
|
||||||
const id = self.next_request_id + 1;
|
const id = self.next_request_id +% 1;
|
||||||
self.next_request_id = id;
|
self.next_request_id = id;
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -1003,6 +1004,7 @@ pub const RequestCookie = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const Request = struct {
|
pub const Request = struct {
|
||||||
|
page_id: u32,
|
||||||
method: Method,
|
method: Method,
|
||||||
url: [:0]const u8,
|
url: [:0]const u8,
|
||||||
headers: Http.Headers,
|
headers: Http.Headers,
|
||||||
@@ -1093,7 +1095,7 @@ pub const AuthChallenge = struct {
|
|||||||
|
|
||||||
pub const Transfer = struct {
|
pub const Transfer = struct {
|
||||||
arena: ArenaAllocator,
|
arena: ArenaAllocator,
|
||||||
id: usize = 0,
|
id: u32 = 0,
|
||||||
req: Request,
|
req: Request,
|
||||||
url: [:0]const u8,
|
url: [:0]const u8,
|
||||||
ctx: *anyopaque, // copied from req.ctx to make it easier for callback handlers
|
ctx: *anyopaque, // copied from req.ctx to make it easier for callback handlers
|
||||||
|
|||||||
96
src/id.zig
96
src/id.zig
@@ -19,72 +19,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const lp = @import("lightpanda");
|
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 {
|
pub fn uuidv4(hex: []u8) void {
|
||||||
lp.assert(hex.len == 36, "uuidv4.len", .{ .len = hex.len });
|
lp.assert(hex.len == 36, "uuidv4.len", .{ .len = hex.len });
|
||||||
|
|
||||||
@@ -108,36 +42,6 @@ pub fn uuidv4(hex: []u8) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const testing = std.testing;
|
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" {
|
test "id: uuiv4" {
|
||||||
const expectUUID = struct {
|
const expectUUID = struct {
|
||||||
fn expect(uuid: [36]u8) !void {
|
fn expect(uuid: [36]u8) !void {
|
||||||
|
|||||||
Reference in New Issue
Block a user