Rework CDP frameIds (and loaderIds and requestIds and interceptorIds)

Our BrowsingContext currently supports 1 target. So we have a per-BC target_id.
Previously, our target had 1 "frame" - our page. So we often treated the
targetId as the frameId. But to work with frames, we need page-specific
frameIds and loaderIds.

This tries to clean up our ids (a little). frameIds are now ids derived from
a new incrementing page.id. This page.id has to be passed around (via http
Requests and through notifications) in order to properly generate messages with
a frameId.
This commit is contained in:
Karl Seguin
2026-02-18 18:52:15 +08:00
parent 5fd95788f9
commit e2a1ce623c
18 changed files with 409 additions and 296 deletions

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const id = @import("../id.zig");
pub fn processMessage(cmd: anytype) !void {
const action = std.meta.stringToEnum(enum {
@@ -46,15 +47,18 @@ fn getFullAXTree(cmd: anytype) !void {
})) orelse return error.InvalidParams;
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const session = bc.session;
if (params.frameId) |frameId| {
const target_id = bc.target_id orelse return error.TargetNotLoaded;
if (std.mem.eql(u8, target_id, frameId) == false) {
const page = blk: {
const frame_id = params.frameId orelse {
break :blk session.currentPage() orelse return error.PageNotLoaded;
};
const page_id = try id.toPageId(.frame_id, frame_id);
break :blk session.findPage(page_id) orelse {
return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
}
}
};
};
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
const doc = page.window._document.asNode();
const node = try bc.node_registry.register(doc);

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const id = @import("../id.zig");
const log = @import("../../log.zig");
const Node = @import("../Node.zig");
const DOMNode = @import("../../browser/webapi/Node.zig");
@@ -499,12 +500,11 @@ fn getFrameOwner(cmd: anytype) !void {
})) orelse return error.InvalidParams;
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const target_id = bc.target_id orelse return error.TargetNotLoaded;
if (std.mem.eql(u8, target_id, params.frameId) == false) {
return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
}
const page_id = try id.toPageId(.frame_id, params.frameId);
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
const page = bc.session.findPage(page_id) orelse {
return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
};
const node = try bc.node_registry.register(page.window._document.asNode());
return cmd.sendResult(.{ .nodeId = node.id, .backendNodeId = node.id }, .{});

View File

@@ -19,6 +19,7 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const id = @import("../id.zig");
const log = @import("../../log.zig");
const network = @import("network.zig");
@@ -48,7 +49,7 @@ pub fn processMessage(cmd: anytype) !void {
// Stored in CDP
pub const InterceptState = struct {
allocator: Allocator,
waiting: std.AutoArrayHashMapUnmanaged(u64, *Http.Transfer),
waiting: std.AutoArrayHashMapUnmanaged(u32, *Http.Transfer),
pub fn init(allocator: Allocator) !InterceptState {
return .{
@@ -65,8 +66,8 @@ pub const InterceptState = struct {
return self.waiting.put(self.allocator, transfer.id, transfer);
}
pub fn remove(self: *InterceptState, id: u64) ?*Http.Transfer {
const entry = self.waiting.fetchSwapRemove(id) orelse return null;
pub fn remove(self: *InterceptState, request_id: u32) ?*Http.Transfer {
const entry = self.waiting.fetchSwapRemove(request_id) orelse return null;
return entry.value;
}
@@ -178,13 +179,11 @@ fn arePatternsSupported(patterns: []RequestPattern) bool {
return true;
}
pub fn requestIntercept(arena: Allocator, bc: anytype, intercept: *const Notification.RequestIntercept) !void {
pub fn requestIntercept(bc: anytype, intercept: *const Notification.RequestIntercept) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
const target_id = bc.target_id orelse unreachable;
// We keep it around to wait for modifications to the request.
// NOTE: we assume whomever created the request created it with a lifetime of the Page.
// TODO: What to do when receiving replies for a previous page's requests?
@@ -193,16 +192,16 @@ pub fn requestIntercept(arena: Allocator, bc: anytype, intercept: *const Notific
try bc.intercept_state.put(transfer);
try bc.cdp.sendEvent("Fetch.requestPaused", .{
.requestId = try std.fmt.allocPrint(arena, "INTERCEPT-{d}", .{transfer.id}),
.requestId = &id.toInterceptId(transfer.id),
.frameId = &id.toFrameId(transfer.req.page_id),
.request = network.TransferAsRequestWriter.init(transfer),
.frameId = target_id,
.resourceType = switch (transfer.req.resource_type) {
.script => "Script",
.xhr => "XHR",
.document => "Document",
.fetch => "Fetch",
},
.networkId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}),
.networkId = &id.toRequestId(transfer.id), // matches the Network REQ-ID
}, .{ .session_id = session_id });
log.debug(.cdp, "request intercept", .{
@@ -218,7 +217,7 @@ pub fn requestIntercept(arena: Allocator, bc: anytype, intercept: *const Notific
fn continueRequest(cmd: anytype) !void {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const params = (try cmd.params(struct {
requestId: []const u8, // "INTERCEPT-{d}"
requestId: []const u8, // INT-{d}"
url: ?[]const u8 = null,
method: ?[]const u8 = null,
postData: ?[]const u8 = null,
@@ -278,7 +277,7 @@ const AuthChallengeResponse = enum {
fn continueWithAuth(cmd: anytype) !void {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const params = (try cmd.params(struct {
requestId: []const u8, // "INTERCEPT-{d}"
requestId: []const u8, // "INT-{d}"
authChallengeResponse: struct {
response: AuthChallengeResponse,
username: []const u8 = "",
@@ -322,7 +321,7 @@ fn fulfillRequest(cmd: anytype) !void {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const params = (try cmd.params(struct {
requestId: []const u8, // "INTERCEPT-{d}"
requestId: []const u8, // "INT-{d}"
responseCode: u16,
responseHeaders: ?[]const Http.Header = null,
binaryResponseHeaders: ?[]const u8 = null,
@@ -363,7 +362,7 @@ fn fulfillRequest(cmd: anytype) !void {
fn failRequest(cmd: anytype) !void {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const params = (try cmd.params(struct {
requestId: []const u8, // "INTERCEPT-{d}"
requestId: []const u8, // "INT-{d}"
errorReason: ErrorReason,
})) orelse return error.InvalidParams;
@@ -382,13 +381,11 @@ fn failRequest(cmd: anytype) !void {
return cmd.sendResult(null, .{});
}
pub fn requestAuthRequired(arena: Allocator, bc: anytype, intercept: *const Notification.RequestAuthRequired) !void {
pub fn requestAuthRequired(bc: anytype, intercept: *const Notification.RequestAuthRequired) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
const target_id = bc.target_id orelse unreachable;
// We keep it around to wait for modifications to the request.
// NOTE: we assume whomever created the request created it with a lifetime of the Page.
// TODO: What to do when receiving replies for a previous page's requests?
@@ -399,9 +396,9 @@ pub fn requestAuthRequired(arena: Allocator, bc: anytype, intercept: *const Noti
const challenge = transfer._auth_challenge orelse return error.NullAuthChallenge;
try bc.cdp.sendEvent("Fetch.authRequired", .{
.requestId = try std.fmt.allocPrint(arena, "INTERCEPT-{d}", .{transfer.id}),
.requestId = &id.toInterceptId(transfer.id),
.frameId = &id.toFrameId(transfer.req.page_id),
.request = network.TransferAsRequestWriter.init(transfer),
.frameId = target_id,
.resourceType = switch (transfer.req.resource_type) {
.script => "Script",
.xhr => "XHR",
@@ -414,7 +411,7 @@ pub fn requestAuthRequired(arena: Allocator, bc: anytype, intercept: *const Noti
.scheme = if (challenge.scheme == .digest) "digest" else "basic",
.realm = challenge.realm,
},
.networkId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}),
.networkId = &id.toRequestId(transfer.id),
}, .{ .session_id = session_id });
log.debug(.cdp, "request auth required", .{
@@ -427,10 +424,10 @@ pub fn requestAuthRequired(arena: Allocator, bc: anytype, intercept: *const Noti
intercept.wait_for_interception.* = true;
}
// Get u64 from requestId which is formatted as: "INTERCEPT-{d}"
fn idFromRequestId(request_id: []const u8) !u64 {
if (!std.mem.startsWith(u8, request_id, "INTERCEPT-")) {
// Get u32 from requestId which is formatted as: "INT-{d}"
fn idFromRequestId(request_id: []const u8) !u32 {
if (!std.mem.startsWith(u8, request_id, "INT-")) {
return error.InvalidParams;
}
return std.fmt.parseInt(u64, request_id[10..], 10) catch return error.InvalidParams;
return std.fmt.parseInt(u32, request_id[4..], 10) catch return error.InvalidParams;
}

View File

@@ -116,30 +116,3 @@ fn insertText(cmd: anytype) !void {
try cmd.sendResult(null, .{});
}
fn clickNavigate(cmd: anytype, uri: std.Uri) !void {
const bc = cmd.browser_context.?;
var url_buf: std.ArrayList(u8) = .{};
try uri.writeToStream(.{
.scheme = true,
.authentication = true,
.authority = true,
.port = true,
.path = true,
.query = true,
}, url_buf.writer(cmd.arena));
const url = url_buf.items;
try cmd.sendEvent("Page.frameRequestedNavigation", .{
.url = url,
.frameId = bc.target_id.?,
.reason = "anchorClick",
.disposition = "currentTab",
}, .{ .session_id = bc.session_id.? });
try bc.session.removePage();
_ = try bc.session.createPage(null);
try @import("page.zig").navigateToUrl(cmd, url, false);
}

View File

@@ -21,6 +21,8 @@ const lp = @import("lightpanda");
const Allocator = std.mem.Allocator;
const CdpStorage = @import("storage.zig");
const id = @import("../id.zig");
const URL = @import("../../browser/URL.zig");
const Transfer = @import("../../http/Client.zig").Transfer;
const Notification = @import("../../Notification.zig");
@@ -208,7 +210,7 @@ fn getResponseBody(cmd: anytype) !void {
}, .{});
}
pub fn httpRequestFail(arena: Allocator, bc: anytype, msg: *const Notification.RequestFail) !void {
pub fn httpRequestFail(bc: anytype, msg: *const Notification.RequestFail) !void {
// It's possible that the request failed because we aborted when the client
// sent Target.closeTarget. In that case, bc.session_id will be cleared
// already, and we can skip sending these messages to the client.
@@ -220,7 +222,7 @@ pub fn httpRequestFail(arena: Allocator, bc: anytype, msg: *const Notification.R
// We're missing a bunch of fields, but, for now, this seems like enough
try bc.cdp.sendEvent("Network.loadingFailed", .{
.requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{msg.transfer.id}),
.requestId = &id.toRequestId(msg.transfer.id),
// Seems to be what chrome answers with. I assume it depends on the type of error?
.type = "Ping",
.errorText = msg.err,
@@ -228,28 +230,27 @@ pub fn httpRequestFail(arena: Allocator, bc: anytype, msg: *const Notification.R
}, .{ .session_id = session_id });
}
pub fn httpRequestStart(arena: Allocator, bc: anytype, msg: *const Notification.RequestStart) !void {
pub fn httpRequestStart(bc: anytype, msg: *const Notification.RequestStart) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
const target_id = bc.target_id orelse unreachable;
const page = bc.session.currentPage() orelse unreachable;
const transfer = msg.transfer;
const req = &transfer.req;
const page_id = req.page_id;
const page = bc.session.findPage(page_id) orelse return;
// Modify request with extra CDP headers
for (bc.extra_headers.items) |extra| {
try msg.transfer.req.headers.add(extra);
try req.headers.add(extra);
}
const transfer = msg.transfer;
const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id});
// We're missing a bunch of fields, but, for now, this seems like enough
try bc.cdp.sendEvent("Network.requestWillBeSent", .{
.requestId = loader_id,
.frameId = target_id,
.loaderId = loader_id,
.type = msg.transfer.req.resource_type.string(),
.loaderId = &id.toLoaderId(transfer.id),
.requestId = &id.toRequestId(transfer.id),
.frameId = &id.toFrameId(page_id),
.type = req.resource_type.string(),
.documentURL = page.url,
.request = TransferAsRequestWriter.init(transfer),
.initiator = .{ .type = "other" },
@@ -262,29 +263,27 @@ pub fn httpResponseHeaderDone(arena: Allocator, bc: anytype, msg: *const Notific
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
const target_id = bc.target_id orelse unreachable;
const transfer = msg.transfer;
const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id});
// We're missing a bunch of fields, but, for now, this seems like enough
try bc.cdp.sendEvent("Network.responseReceived", .{
.requestId = loader_id,
.frameId = target_id,
.loaderId = loader_id,
.loaderId = &id.toLoaderId(transfer.id),
.requestId = &id.toRequestId(transfer.id),
.frameId = &id.toFrameId(transfer.req.page_id),
.response = TransferAsResponseWriter.init(arena, msg.transfer),
.hasExtraInfo = false, // TODO change after adding Network.responseReceivedExtraInfo
}, .{ .session_id = session_id });
}
pub fn httpRequestDone(arena: Allocator, bc: anytype, msg: *const Notification.RequestDone) !void {
pub fn httpRequestDone(bc: anytype, msg: *const Notification.RequestDone) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
const transfer = msg.transfer;
try bc.cdp.sendEvent("Network.loadingFinished", .{
.requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{msg.transfer.id}),
.encodedDataLength = msg.transfer.bytes_received,
.requestId = &id.toRequestId(transfer.id),
.encodedDataLength = transfer.bytes_received,
}, .{ .session_id = session_id });
}

View File

@@ -19,6 +19,7 @@
const std = @import("std");
const lp = @import("lightpanda");
const id = @import("../id.zig");
const log = @import("../../log.zig");
const js = @import("../../browser/js/js.zig");
const Page = @import("../../browser/Page.zig");
@@ -73,9 +74,9 @@ fn getFrameTree(cmd: anytype) !void {
return cmd.sendResult(.{
.frameTree = .{
.frame = Frame{
.id = target_id,
.loaderId = bc.loader_id,
.id = &target_id,
.securityOrigin = bc.security_origin,
.loaderId = "LID-0000000001",
.url = bc.getURL() orelse "about:blank",
.secureContextType = bc.secure_context_type,
},
@@ -103,18 +104,21 @@ fn setLifecycleEventsEnabled(cmd: anytype) !void {
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
if (page._load_state == .complete) {
const frame_id = &id.toFrameId(page.id);
const loader_id = &id.toLoaderId(page._req_id);
const now = timestampF(.monotonic);
try sendPageLifecycle(bc, "DOMContentLoaded", now);
try sendPageLifecycle(bc, "load", now);
try sendPageLifecycle(bc, "DOMContentLoaded", now, frame_id, loader_id);
try sendPageLifecycle(bc, "load", now, frame_id, loader_id);
const http_client = page._session.browser.http_client;
const http_active = http_client.active;
const total_network_activity = http_active + http_client.intercepted;
if (page._notified_network_almost_idle.check(total_network_activity <= 2)) {
try sendPageLifecycle(bc, "networkAlmostIdle", now);
try sendPageLifecycle(bc, "networkAlmostIdle", now, frame_id, loader_id);
}
if (page._notified_network_idle.check(total_network_activity == 0)) {
try sendPageLifecycle(bc, "networkIdle", now);
try sendPageLifecycle(bc, "networkIdle", now, frame_id, loader_id);
}
}
@@ -227,16 +231,15 @@ fn navigate(cmd: anytype) !void {
});
}
pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.PageNavigate) !void {
pub fn pageNavigate(bc: anytype, event: *const Notification.PageNavigate) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{event.req_id});
const target_id = bc.target_id orelse unreachable;
bc.reset();
const frame_id = &id.toFrameId(event.page_id);
const loader_id = &id.toLoaderId(event.req_id);
var cdp = bc.cdp;
const reason_: ?[]const u8 = switch (event.opts.reason) {
.anchor => "anchorClick",
@@ -250,14 +253,14 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
};
if (reason_) |reason| {
try cdp.sendEvent("Page.frameScheduledNavigation", .{
.frameId = target_id,
.frameId = frame_id,
.delay = 0,
.reason = reason,
.url = event.url,
}, .{ .session_id = session_id });
try cdp.sendEvent("Page.frameRequestedNavigation", .{
.frameId = target_id,
.frameId = frame_id,
.reason = reason,
.url = event.url,
.disposition = "currentTab",
@@ -266,7 +269,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
// frameStartedNavigating event
try cdp.sendEvent("Page.frameStartedNavigating", .{
.frameId = target_id,
.frameId = frame_id,
.url = event.url,
.loaderId = loader_id,
.navigationType = "differentDocument",
@@ -274,7 +277,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
// frameStartedLoading event
try cdp.sendEvent("Page.frameStartedLoading", .{
.frameId = target_id,
.frameId = frame_id,
}, .{ .session_id = session_id });
}
@@ -301,9 +304,10 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{event.req_id});
const target_id = bc.target_id orelse unreachable;
const timestamp = event.timestamp;
const frame_id = &id.toFrameId(event.page_id);
const loader_id = &id.toLoaderId(event.req_id);
var cdp = bc.cdp;
@@ -316,7 +320,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
try cdp.sendJSON(.{
.id = input_id,
.result = .{
.frameId = target_id,
.frameId = frame_id,
.loaderId = loader_id,
},
.sessionId = session_id,
@@ -326,7 +330,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
if (bc.page_life_cycle_events) {
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
.name = "init",
.frameId = target_id,
.frameId = frame_id,
.loaderId = loader_id,
.timestamp = event.timestamp,
}, .{ .session_id = session_id });
@@ -345,7 +349,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
if (reason_ != null) {
try cdp.sendEvent("Page.frameClearedScheduledNavigation", .{
.frameId = target_id,
.frameId = frame_id,
}, .{ .session_id = session_id });
}
@@ -356,7 +360,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
{
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
const aux_data = try std.fmt.allocPrint(arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id});
const aux_data = try std.fmt.allocPrint(arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\",\"loaderId\":\"{s}\"}}", .{ frame_id, loader_id });
var ls: js.Local.Scope = undefined;
page.js.localScope(&ls);
@@ -371,7 +375,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
);
}
for (bc.isolated_worlds.items) |isolated_world| {
const aux_json = try std.fmt.allocPrint(arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", .{target_id});
const aux_json = try std.fmt.allocPrint(arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\",\"loaderId\":\"{s}\"}}", .{ frame_id, loader_id });
// Calling contextCreated will assign a new Id to the context and send the contextCreated event
@@ -392,7 +396,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
try cdp.sendEvent("Page.frameNavigated", .{
.type = "Navigation",
.frame = Frame{
.id = target_id,
.id = frame_id,
.url = event.url,
.loaderId = loader_id,
.securityOrigin = bc.security_origin,
@@ -419,7 +423,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
.timestamp = timestamp,
.name = "DOMContentLoaded",
.frameId = target_id,
.frameId = frame_id,
.loaderId = loader_id,
}, .{ .session_id = session_id });
}
@@ -436,35 +440,33 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
.timestamp = timestamp,
.name = "load",
.frameId = target_id,
.frameId = frame_id,
.loaderId = loader_id,
}, .{ .session_id = session_id });
}
// frameStoppedLoading
return cdp.sendEvent("Page.frameStoppedLoading", .{
.frameId = target_id,
.frameId = frame_id,
}, .{ .session_id = session_id });
}
pub fn pageNetworkIdle(bc: anytype, event: *const Notification.PageNetworkIdle) !void {
return sendPageLifecycle(bc, "networkIdle", event.timestamp);
return sendPageLifecycle(bc, "networkIdle", event.timestamp, &id.toFrameId(event.page_id), &id.toLoaderId(event.req_id));
}
pub fn pageNetworkAlmostIdle(bc: anytype, event: *const Notification.PageNetworkAlmostIdle) !void {
return sendPageLifecycle(bc, "networkAlmostIdle", event.timestamp);
return sendPageLifecycle(bc, "networkAlmostIdle", event.timestamp, &id.toFrameId(event.page_id), &id.toLoaderId(event.req_id));
}
fn sendPageLifecycle(bc: anytype, name: []const u8, timestamp: u64) !void {
fn sendPageLifecycle(bc: anytype, name: []const u8, timestamp: u64, frame_id: []const u8, loader_id: []const u8) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
const loader_id = bc.loader_id;
const target_id = bc.target_id orelse unreachable;
return bc.cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
.name = name,
.frameId = target_id,
.frameId = frame_id,
.loaderId = loader_id,
.timestamp = timestamp,
}, .{ .session_id = session_id });
@@ -487,15 +489,15 @@ test "cdp.page: getFrameTree" {
try ctx.expectSentError(-31998, "BrowserContextNotLoaded", .{ .id = 10 });
}
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9", .target_id = "TID-3" });
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9", .url = "hi.html", .target_id = "FID-000000000X".* });
{
try ctx.processMessage(.{ .id = 11, .method = "Page.getFrameTree" });
try ctx.expectSentResult(.{
.frameTree = .{
.frame = .{
.id = "TID-3",
.loaderId = bc.loader_id,
.url = "about:blank",
.id = "FID-000000000X",
.loaderId = "LID-0000000001",
.url = "http://127.0.0.1:9582/src/browser/tests/hi.html",
.domainAndRegistry = "",
.securityOrigin = bc.security_origin,
.mimeType = "text/html",

View File

@@ -18,6 +18,8 @@
const std = @import("std");
const lp = @import("lightpanda");
const id = @import("../id.zig");
const log = @import("../../log.zig");
const js = @import("../../browser/js/js.zig");
@@ -66,11 +68,11 @@ fn getTargets(cmd: anytype) !void {
}, .{ .include_session_id = false });
};
const target_id = bc.target_id orelse {
const target_id = &(bc.target_id orelse {
return cmd.sendResult(.{
.targetInfos = [_]TargetInfo{},
}, .{ .include_session_id = false });
};
});
return cmd.sendResult(.{
.targetInfos = [_]TargetInfo{.{
@@ -171,11 +173,12 @@ fn createTarget(cmd: anytype) !void {
// if target_id is null, we should never have a session_id
lp.assert(bc.session_id == null, "CDP.target.createTarget not null session_id", .{});
const target_id = cmd.cdp.target_id_gen.next();
bc.target_id = target_id;
const page = try bc.session.createPage();
// the target_id == the frame_id of the "root" page
const frame_id = id.toFrameId(page.id);
bc.target_id = frame_id;
const target_id = &bc.target_id.?;
{
var ls: js.Local.Scope = undefined;
page.js.localScope(&ls);
@@ -195,7 +198,6 @@ fn createTarget(cmd: anytype) !void {
// change CDP state
bc.security_origin = "://";
bc.secure_context_type = "InsecureScheme";
bc.loader_id = LOADER_ID;
// send targetCreated event
// TODO: should this only be sent when Target.setDiscoverTargets
@@ -234,7 +236,7 @@ fn attachToTarget(cmd: anytype) !void {
})) orelse return error.InvalidParams;
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const target_id = bc.target_id orelse return error.TargetNotLoaded;
const target_id = &(bc.target_id orelse return error.TargetNotLoaded);
if (std.mem.eql(u8, target_id, params.targetId) == false) {
return error.UnknownTargetId;
}
@@ -255,7 +257,7 @@ fn closeTarget(cmd: anytype) !void {
})) orelse return error.InvalidParams;
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const target_id = bc.target_id orelse return error.TargetNotLoaded;
const target_id = &(bc.target_id orelse return error.TargetNotLoaded);
if (std.mem.eql(u8, target_id, params.targetId) == false) {
return error.UnknownTargetId;
}
@@ -298,7 +300,7 @@ fn getTargetInfo(cmd: anytype) !void {
if (params.targetId) |param_target_id| {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const target_id = bc.target_id orelse return error.TargetNotLoaded;
const target_id = &(bc.target_id orelse return error.TargetNotLoaded);
if (std.mem.eql(u8, target_id, param_target_id) == false) {
return error.UnknownTargetId;
}
@@ -415,10 +417,11 @@ fn setAutoAttach(cmd: anytype) !void {
// autoAttach is set to true, we must attach to all existing targets.
if (cmd.browser_context) |bc| {
if (bc.target_id == null) {
// hasn't attached yet
const target_id = cmd.cdp.target_id_gen.next();
try doAttachtoTarget(cmd, target_id);
bc.target_id = target_id;
if (bc.session.currentPage()) |page| {
// the target_id == the frame_id of the "root" page
bc.target_id = id.toFrameId(page.id);
try doAttachtoTarget(cmd, &bc.target_id.?);
}
}
// should we send something here?
return;
@@ -612,14 +615,14 @@ test "cdp.target: closeTarget" {
// pretend we createdTarget first
_ = try bc.session.createPage();
bc.target_id = "TID-A";
bc.target_id = "TID-000000000A".*;
{
try testing.expectError(error.UnknownTargetId, ctx.processMessage(.{ .id = 10, .method = "Target.closeTarget", .params = .{ .targetId = "TID-8" } }));
try ctx.expectSentError(-31998, "UnknownTargetId", .{ .id = 10 });
}
{
try ctx.processMessage(.{ .id = 11, .method = "Target.closeTarget", .params = .{ .targetId = "TID-A" } });
try ctx.processMessage(.{ .id = 11, .method = "Target.closeTarget", .params = .{ .targetId = "TID-000000000A" } });
try ctx.expectSentResult(.{ .success = true }, .{ .id = 11 });
try testing.expectEqual(null, bc.session.page);
try testing.expectEqual(null, bc.target_id);
@@ -643,14 +646,14 @@ test "cdp.target: attachToTarget" {
// pretend we createdTarget first
_ = try bc.session.createPage();
bc.target_id = "TID-B";
bc.target_id = "TID-000000000B".*;
{
try testing.expectError(error.UnknownTargetId, ctx.processMessage(.{ .id = 10, .method = "Target.attachToTarget", .params = .{ .targetId = "TID-8" } }));
try ctx.expectSentError(-31998, "UnknownTargetId", .{ .id = 10 });
}
{
try ctx.processMessage(.{ .id = 11, .method = "Target.attachToTarget", .params = .{ .targetId = "TID-B" } });
try ctx.processMessage(.{ .id = 11, .method = "Target.attachToTarget", .params = .{ .targetId = "TID-000000000B" } });
const session_id = bc.session_id.?;
try ctx.expectSentResult(.{ .sessionId = session_id }, .{ .id = 11 });
try ctx.expectSentEvent("Target.attachedToTarget", .{ .sessionId = session_id, .targetInfo = .{ .url = "chrome://newtab/", .title = "about:blank", .attached = true, .type = "page", .canAccessOpener = false, .browserContextId = "BID-9", .targetId = bc.target_id.? } }, .{});
@@ -687,17 +690,17 @@ test "cdp.target: getTargetInfo" {
// pretend we createdTarget first
_ = try bc.session.createPage();
bc.target_id = "TID-A";
bc.target_id = "TID-000000000C".*;
{
try testing.expectError(error.UnknownTargetId, ctx.processMessage(.{ .id = 10, .method = "Target.getTargetInfo", .params = .{ .targetId = "TID-8" } }));
try ctx.expectSentError(-31998, "UnknownTargetId", .{ .id = 10 });
}
{
try ctx.processMessage(.{ .id = 11, .method = "Target.getTargetInfo", .params = .{ .targetId = "TID-A" } });
try ctx.processMessage(.{ .id = 11, .method = "Target.getTargetInfo", .params = .{ .targetId = "TID-000000000C" } });
try ctx.expectSentResult(.{
.targetInfo = .{
.targetId = "TID-A",
.targetId = "TID-000000000C",
.type = "page",
.title = "",
.url = "about:blank",