Karl Seguin
2025-12-19 16:06:25 +08:00
parent 1278dc28cd
commit f475aa09e8
8 changed files with 191 additions and 69 deletions

View File

@@ -108,14 +108,17 @@ 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,
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,
timestamp: u64, timestamp: u64,
url: [:0]const u8, url: [:0]const u8,
opts: Page.NavigatedOpts,
}; };
pub const PageNetworkIdle = struct { pub const PageNetworkIdle = struct {
@@ -314,6 +317,7 @@ test "Notification" {
// noop // noop
notifier.dispatch(.page_navigate, &.{ notifier.dispatch(.page_navigate, &.{
.req_id = 1,
.timestamp = 4, .timestamp = 4,
.url = undefined, .url = undefined,
.opts = .{}, .opts = .{},
@@ -323,6 +327,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, &.{
.req_id = 1,
.timestamp = 4, .timestamp = 4,
.url = undefined, .url = undefined,
.opts = .{}, .opts = .{},
@@ -331,6 +336,7 @@ test "Notification" {
notifier.unregisterAll(&tc); notifier.unregisterAll(&tc);
notifier.dispatch(.page_navigate, &.{ notifier.dispatch(.page_navigate, &.{
.req_id = 1,
.timestamp = 10, .timestamp = 10,
.url = undefined, .url = undefined,
.opts = .{}, .opts = .{},
@@ -340,21 +346,23 @@ 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, &.{
.req_id = 1,
.timestamp = 10, .timestamp = 10,
.url = undefined, .url = undefined,
.opts = .{}, .opts = .{},
}); });
notifier.dispatch(.page_navigated, &.{ .timestamp = 6, .url = undefined }); notifier.dispatch(.page_navigated, &.{ .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, &.{
.req_id = 1,
.timestamp = 100, .timestamp = 100,
.url = undefined, .url = undefined,
.opts = .{}, .opts = .{},
}); });
notifier.dispatch(.page_navigated, &.{ .timestamp = 100, .url = undefined }); notifier.dispatch(.page_navigated, &.{ .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);
@@ -362,27 +370,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, &.{ .timestamp = 100, .url = undefined, .opts = .{} }); notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined }); notifier.dispatch(.page_navigated, &.{ .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, &.{ .timestamp = 100, .url = undefined, .opts = .{} }); notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined }); notifier.dispatch(.page_navigated, &.{ .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, &.{ .timestamp = 100, .url = undefined, .opts = .{} }); notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined }); notifier.dispatch(.page_navigated, &.{ .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, &.{ .timestamp = 100, .url = undefined, .opts = .{} }); notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined }); notifier.dispatch(.page_navigated, &.{ .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);
} }

View File

@@ -24,6 +24,7 @@ params: []const u8 = "",
// IANA defines max. charset value length as 40. // IANA defines max. charset value length as 40.
// We keep 41 for null-termination since HTML parser expects in this format. // We keep 41 for null-termination since HTML parser expects in this format.
charset: [41]u8 = default_charset, charset: [41]u8 = default_charset,
charset_len: usize = 5,
/// String "UTF-8" continued by null characters. /// String "UTF-8" continued by null characters.
pub const default_charset = .{ 'U', 'T', 'F', '-', '8' } ++ .{0} ** 36; pub const default_charset = .{ 'U', 'T', 'F', '-', '8' } ++ .{0} ** 36;
@@ -53,9 +54,25 @@ pub const ContentType = union(ContentTypeEnum) {
other: struct { type: []const u8, sub_type: []const u8 }, other: struct { type: []const u8, sub_type: []const u8 },
}; };
pub fn contentTypeString(mime: *const Mime) []const u8 {
return switch (mime.content_type) {
.text_xml => "text/xml",
.text_html => "text/html",
.text_javascript => "application/javascript",
.text_plain => "text/plain",
.text_css => "text/css",
.application_json => "application/json",
else => "",
};
}
/// Returns the null-terminated charset value. /// Returns the null-terminated charset value.
pub fn charsetString(mime: *const Mime) [:0]const u8 { pub fn charsetStringZ(mime: *const Mime) [:0]const u8 {
return @ptrCast(&mime.charset); return mime.charset[0..mime.charset_len :0];
}
pub fn charsetString(mime: *const Mime) []const u8 {
return mime.charset[0..mime.charset_len];
} }
/// Removes quotes of value if quotes are given. /// Removes quotes of value if quotes are given.
@@ -99,6 +116,7 @@ pub fn parse(input: []u8) !Mime {
const params = trimLeft(normalized[type_len..]); const params = trimLeft(normalized[type_len..]);
var charset: [41]u8 = undefined; var charset: [41]u8 = undefined;
var charset_len: usize = undefined;
var it = std.mem.splitScalar(u8, params, ';'); var it = std.mem.splitScalar(u8, params, ';');
while (it.next()) |attr| { while (it.next()) |attr| {
@@ -124,6 +142,7 @@ pub fn parse(input: []u8) !Mime {
@memcpy(charset[0..attribute_value.len], attribute_value); @memcpy(charset[0..attribute_value.len], attribute_value);
// Null-terminate right after attribute value. // Null-terminate right after attribute value.
charset[attribute_value.len] = 0; charset[attribute_value.len] = 0;
charset_len = attribute_value.len;
}, },
} }
} }
@@ -131,6 +150,7 @@ pub fn parse(input: []u8) !Mime {
return .{ return .{
.params = params, .params = params,
.charset = charset, .charset = charset,
.charset_len = charset_len,
.content_type = content_type, .content_type = content_type,
}; };
} }
@@ -510,9 +530,9 @@ fn expect(expected: Expectation, input: []const u8) !void {
if (expected.charset) |ec| { if (expected.charset) |ec| {
// We remove the null characters for testing purposes here. // We remove the null characters for testing purposes here.
try testing.expectEqual(ec, actual.charsetString()[0..ec.len]); try testing.expectEqual(ec, actual.charsetString());
} else { } else {
const m: Mime = .unknown; const m: Mime = .unknown;
try testing.expectEqual(m.charsetString(), actual.charsetString()); try testing.expectEqual(m.charsetStringZ(), actual.charsetStringZ());
} }
} }

View File

@@ -161,6 +161,9 @@ version: usize,
scheduler: Scheduler, scheduler: Scheduler,
_req_id: ?usize = null,
_navigated_options: ?NavigatedOpts = null,
pub fn init(arena: Allocator, call_arena: Allocator, session: *Session) !*Page { pub fn init(arena: Allocator, call_arena: Allocator, session: *Session) !*Page {
if (comptime IS_DEBUG) { if (comptime IS_DEBUG) {
log.debug(.page, "page.init", .{}); log.debug(.page, "page.init", .{});
@@ -290,6 +293,17 @@ pub fn getTitle(self: *Page) !?[]const u8 {
return null; return null;
} }
pub fn getOrigin(self: *Page, allocator: Allocator) !?[]const u8 {
const URLRaw = @import("URL.zig");
return try URLRaw.getOrigin(allocator, self.url);
}
pub fn isSameOrigin(self: *const Page, url: [:0]const u8) !bool {
const URLRaw = @import("URL.zig");
const current_origin = (try URLRaw.getOrigin(self.call_arena, self.url)) orelse return false;
return std.mem.startsWith(u8, url, current_origin);
}
pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts, kind: NavigationKind) !void { pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts, kind: NavigationKind) !void {
const session = self._session; const session = self._session;
@@ -323,11 +337,13 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts, kind
try self.reset(false); try self.reset(false);
} }
const req_id = self._session.browser.http_client.nextReqId();
log.info(.page, "navigate", .{ log.info(.page, "navigate", .{
.url = request_url, .url = request_url,
.method = opts.method, .method = opts.method,
.reason = opts.reason, .reason = opts.reason,
.body = opts.body != null, .body = opts.body != null,
.req_id = req_id,
}); });
// if the url is about:blank, we load an empty HTML document in the // if the url is about:blank, we load an empty HTML document in the
@@ -342,16 +358,25 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts, kind
self.documentIsComplete(); self.documentIsComplete();
self._session.browser.notification.dispatch(.page_navigate, &.{ self._session.browser.notification.dispatch(.page_navigate, &.{
.req_id = req_id,
.opts = opts, .opts = opts,
.url = request_url, .url = request_url,
.timestamp = timestamp(.monotonic), .timestamp = timestamp(.monotonic),
}); });
self._session.browser.notification.dispatch(.page_navigated, &.{ self._session.browser.notification.dispatch(.page_navigated, &.{
.req_id = req_id,
.opts = .{
.cdp_id = opts.cdp_id,
.reason = opts.reason,
.method = opts.method,
},
.url = request_url, .url = request_url,
.timestamp = timestamp(.monotonic), .timestamp = timestamp(.monotonic),
}); });
// force next request id manually b/c we won't create a real req.
_ = self._session.browser.http_client.incrReqId();
return; return;
} }
@@ -359,6 +384,13 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts, kind
self.url = try self.arena.dupeZ(u8, request_url); self.url = try self.arena.dupeZ(u8, request_url);
self._req_id = req_id;
self._navigated_options = .{
.cdp_id = opts.cdp_id,
.reason = opts.reason,
.method = opts.method,
};
var headers = try http_client.newHeaders(); var headers = try http_client.newHeaders();
if (opts.header) |hdr| { if (opts.header) |hdr| {
try headers.add(hdr); try headers.add(hdr);
@@ -368,6 +400,7 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts, kind
// 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.browser.notification.dispatch(.page_navigate, &.{ self._session.browser.notification.dispatch(.page_navigate, &.{
.req_id = req_id,
.opts = opts, .opts = opts,
.url = self.url, .url = self.url,
.timestamp = timestamp(.monotonic), .timestamp = timestamp(.monotonic),
@@ -439,7 +472,14 @@ pub fn documentIsComplete(self: *Page) void {
log.err(.page, "document is complete", .{ .err = err }); log.err(.page, "document is complete", .{ .err = err });
}; };
if (IS_DEBUG) {
std.debug.assert(self._req_id != null);
std.debug.assert(self._navigated_options != null);
}
self._session.browser.notification.dispatch(.page_navigated, &.{ self._session.browser.notification.dispatch(.page_navigated, &.{
.req_id = self._req_id.?,
.opts = self._navigated_options.?,
.url = self.url, .url = self.url,
.timestamp = timestamp(.monotonic), .timestamp = timestamp(.monotonic),
}); });
@@ -803,7 +843,6 @@ fn printWaitAnalysis(self: *Page) void {
} }
} }
{ {
std.debug.print("\ndeferreds: {d}\n", .{self._script_manager.defer_scripts.len()}); std.debug.print("\ndeferreds: {d}\n", .{self._script_manager.defer_scripts.len()});
var n_ = self._script_manager.defer_scripts.first; var n_ = self._script_manager.defer_scripts.first;
@@ -2254,12 +2293,6 @@ const IdleNotification = union(enum) {
} }
}; };
pub fn isSameOrigin(self: *const Page, url: [:0]const u8) !bool {
const URLRaw = @import("URL.zig");
const current_origin = (try URLRaw.getOrigin(self.call_arena, self.url)) orelse return false;
return std.mem.startsWith(u8, url, current_origin);
}
pub const NavigateReason = enum { pub const NavigateReason = enum {
anchor, anchor,
address_bar, address_bar,
@@ -2278,6 +2311,12 @@ pub const NavigateOpts = struct {
force: bool = false, force: bool = false,
}; };
pub const NavigatedOpts = struct {
cdp_id: ?i64 = null,
reason: NavigateReason = .address_bar,
method: Http.Method = .GET,
};
const RequestCookieOpts = struct { const RequestCookieOpts = struct {
is_http: bool = true, is_http: bool = true,
is_navigation: bool = false, is_navigation: bool = false,

View File

@@ -1028,7 +1028,7 @@ const valueToStringOpts = struct {
pub fn valueToString(self: *const Context, js_val: v8.Value, opts: valueToStringOpts) ![]u8 { pub fn valueToString(self: *const Context, js_val: v8.Value, opts: valueToStringOpts) ![]u8 {
const allocator = opts.allocator orelse self.call_arena; const allocator = opts.allocator orelse self.call_arena;
if (js_val.isSymbol()) { if (js_val.isSymbol()) {
const js_sym = v8.Symbol{.handle = js_val.handle}; const js_sym = v8.Symbol{ .handle = js_val.handle };
const js_sym_desc = js_sym.getDescription(self.isolate); const js_sym_desc = js_sym.getDescription(self.isolate);
return self.valueToString(js_sym_desc, .{}); return self.valueToString(js_sym_desc, .{});
} }
@@ -1039,7 +1039,7 @@ pub fn valueToString(self: *const Context, js_val: v8.Value, opts: valueToString
pub fn valueToStringZ(self: *const Context, js_val: v8.Value, opts: valueToStringOpts) ![:0]u8 { pub fn valueToStringZ(self: *const Context, js_val: v8.Value, opts: valueToStringOpts) ![:0]u8 {
const allocator = opts.allocator orelse self.call_arena; const allocator = opts.allocator orelse self.call_arena;
if (js_val.isSymbol()) { if (js_val.isSymbol()) {
const js_sym = v8.Symbol{.handle = js_val.handle}; const js_sym = v8.Symbol{ .handle = js_val.handle };
const js_sym_desc = js_sym.getDescription(self.isolate); const js_sym_desc = js_sym.getDescription(self.isolate);
return self.valueToStringZ(js_sym_desc, .{}); return self.valueToStringZ(js_sym_desc, .{});
} }
@@ -1094,7 +1094,7 @@ fn _debugValue(self: *const Context, js_val: v8.Value, seen: *std.AutoHashMapUnm
} }
if (js_val.isSymbol()) { if (js_val.isSymbol()) {
const js_sym = v8.Symbol{.handle = js_val.handle}; const js_sym = v8.Symbol{ .handle = js_val.handle };
const js_sym_desc = js_sym.getDescription(self.isolate); const js_sym_desc = js_sym.getDescription(self.isolate);
const js_sym_str = try self.valueToString(js_sym_desc, .{}); const js_sym_str = try self.valueToString(js_sym_desc, .{});
return writer.print("{s} (symbol)", .{js_sym_str}); return writer.print("{s} (symbol)", .{js_sym_str});

View File

@@ -552,7 +552,8 @@ pub fn BrowserContext(comptime CDP_T: type) type {
pub fn onPageNavigated(ctx: *anyopaque, msg: *const Notification.PageNavigated) !void { pub fn onPageNavigated(ctx: *anyopaque, msg: *const Notification.PageNavigated) !void {
const self: *Self = @ptrCast(@alignCast(ctx)); const self: *Self = @ptrCast(@alignCast(ctx));
return @import("domains/page.zig").pageNavigated(self, msg); defer self.resetNotificationArena();
return @import("domains/page.zig").pageNavigated(self.notification_arena, self, msg);
} }
pub fn onPageNetworkIdle(ctx: *anyopaque, msg: *const Notification.PageNetworkIdle) !void { pub fn onPageNetworkIdle(ctx: *anyopaque, msg: *const Notification.PageNetworkIdle) !void {

View File

@@ -23,6 +23,7 @@ const CdpStorage = @import("storage.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");
const Mime = @import("../../browser/Mime.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 {
@@ -240,14 +241,19 @@ pub fn httpRequestStart(arena: Allocator, bc: anytype, msg: *const Notification.
} }
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.requestWillBeSent", .{ try bc.cdp.sendEvent("Network.requestWillBeSent", .{
.requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}), .requestId = loader_id,
.frameId = target_id, .frameId = target_id,
.loaderId = bc.loader_id, .loaderId = loader_id,
.documentUrl = page.url, .type = msg.transfer.req.resource_type.string(),
.documentURL = page.url,
.request = TransferAsRequestWriter.init(transfer), .request = TransferAsRequestWriter.init(transfer),
.initiator = .{ .type = "other" }, .initiator = .{ .type = "other" },
.redirectHasExtraInfo = false, // TODO change after adding Network.requestWillBeSentExtraInfo
.hasUserGesture = false,
}, .{ .session_id = session_id }); }, .{ .session_id = session_id });
} }
@@ -257,12 +263,16 @@ pub fn httpResponseHeaderDone(arena: Allocator, bc: anytype, msg: *const Notific
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 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 // 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 = try std.fmt.allocPrint(arena, "REQ-{d}", .{msg.transfer.id}), .requestId = loader_id,
.loaderId = bc.loader_id,
.frameId = target_id, .frameId = target_id,
.loaderId = loader_id,
.response = TransferAsResponseWriter.init(arena, msg.transfer), .response = TransferAsResponseWriter.init(arena, msg.transfer),
.hasExtraInfo = false, // TODO change after adding Network.responseReceivedExtraInfo
}, .{ .session_id = session_id }); }, .{ .session_id = session_id });
} }
@@ -381,6 +391,20 @@ const TransferAsResponseWriter = struct {
try jws.write(@as(std.http.Status, @enumFromInt(status)).phrase() orelse "Unknown"); try jws.write(@as(std.http.Status, @enumFromInt(status)).phrase() orelse "Unknown");
} }
{
const mime: Mime = blk: {
if (transfer.response_header.?.contentType()) |ct| {
break :blk try Mime.parse(ct);
}
break :blk .unknown;
};
try jws.objectField("mimeType");
try jws.write(mime.contentTypeString());
try jws.objectField("charset");
try jws.write(mime.charsetString());
}
{ {
// chromedp doesn't like having duplicate header names. It's pretty // chromedp doesn't like having duplicate header names. It's pretty
// common to get these from a server (e.g. for Cache-Control), but // common to get these from a server (e.g. for Cache-Control), but

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) // Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
// //
// Francis Bouvier <francis@lightpanda.io> // Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io> // Pierre Tachoire <pierre@lightpanda.io>
@@ -18,9 +18,9 @@
const std = @import("std"); const std = @import("std");
const Page = @import("../../browser/Page.zig"); const Page = @import("../../browser/Page.zig");
const timestampF = @import("../../datetime.zig").timestamp;
const Notification = @import("../../Notification.zig"); const Notification = @import("../../Notification.zig");
const log = @import("../../log.zig"); const log = @import("../../log.zig");
const timestampF = @import("../../datetime.zig").timestamp;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
@@ -51,7 +51,7 @@ pub fn processMessage(cmd: anytype) !void {
const Frame = struct { const Frame = struct {
id: []const u8, id: []const u8,
loaderId: []const u8, loaderId: []const u8,
url: [:0]const u8, url: []const u8,
domainAndRegistry: []const u8 = "", domainAndRegistry: []const u8 = "",
securityOrigin: []const u8, securityOrigin: []const u8,
mimeType: []const u8 = "text/html", mimeType: []const u8 = "text/html",
@@ -101,11 +101,10 @@ fn setLifecycleEventsEnabled(cmd: anytype) !void {
if (page._load_state == .complete) { if (page._load_state == .complete) {
const now = timestampF(.monotonic); const now = timestampF(.monotonic);
const http_client = page._session.browser.http_client;
try sendPageLifecycle(bc, "DOMContentLoaded", now); try sendPageLifecycle(bc, "DOMContentLoaded", now);
try sendPageLifecycle(bc, "load", now); try sendPageLifecycle(bc, "load", now);
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)) {
@@ -176,7 +175,7 @@ fn createIsolatedWorld(cmd: anytype) !void {
const params = (try cmd.params(struct { const params = (try cmd.params(struct {
frameId: []const u8, frameId: []const u8,
worldName: []const u8, worldName: []const u8,
grantUniveralAccess: bool, grantUniveralAccess: bool = false,
})) orelse return error.InvalidParams; })) orelse return error.InvalidParams;
if (!params.grantUniveralAccess) { if (!params.grantUniveralAccess) {
log.warn(.cdp, "not implemented", .{ .feature = "grantUniveralAccess == false is not yet implemented" }); log.warn(.cdp, "not implemented", .{ .feature = "grantUniveralAccess == false is not yet implemented" });
@@ -218,7 +217,6 @@ fn navigate(cmd: anytype) !void {
} }
var page = bc.session.currentPage() orelse return error.PageNotLoaded; var page = bc.session.currentPage() orelse return error.PageNotLoaded;
bc.loader_id = bc.cdp.loader_id_gen.next();
try page.navigate(params.url, .{ try page.navigate(params.url, .{
.reason = .address_bar, .reason = .address_bar,
@@ -231,8 +229,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
// things, but no session. // things, but no session.
const session_id = bc.session_id orelse return; const session_id = bc.session_id orelse return;
bc.loader_id = bc.cdp.loader_id_gen.next(); const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{event.req_id});
const loader_id = bc.loader_id;
const target_id = bc.target_id orelse unreachable; const target_id = bc.target_id orelse unreachable;
bc.reset(); bc.reset();
@@ -240,13 +237,13 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
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",
.script, .history => "scriptInitiated", .script, .history, .navigation => "scriptInitiated",
.form => switch (event.opts.method) { .form => switch (event.opts.method) {
.GET => "formSubmissionGet", .GET => "formSubmissionGet",
.POST => "formSubmissionPost", .POST => "formSubmissionPost",
else => unreachable, else => unreachable,
}, },
.address_bar, .navigation => null, .address_bar => null,
}; };
if (reason_) |reason| { if (reason_) |reason| {
try cdp.sendEvent("Page.frameScheduledNavigation", .{ try cdp.sendEvent("Page.frameScheduledNavigation", .{
@@ -276,6 +273,30 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
try cdp.sendEvent("Page.frameStartedLoading", .{ try cdp.sendEvent("Page.frameStartedLoading", .{
.frameId = target_id, .frameId = target_id,
}, .{ .session_id = session_id }); }, .{ .session_id = session_id });
}
pub fn pageRemove(bc: anytype) !void {
// The main page is going to be removed, we need to remove contexts from other worlds first.
for (bc.isolated_worlds.items) |*isolated_world| {
try isolated_world.removeContext();
}
}
pub fn pageCreated(bc: anytype, page: *Page) !void {
for (bc.isolated_worlds.items) |*isolated_world| {
try isolated_world.createContextAndLoadPolyfills(page);
}
}
pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.PageNavigated) !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;
const timestamp = event.timestamp;
var cdp = bc.cdp;
// Drivers are sensitive to the order of events. Some more than others. // Drivers are sensitive to the order of events. Some more than others.
// The result for the Page.navigate seems like it _must_ come after // The result for the Page.navigate seems like it _must_ come after
@@ -302,6 +323,17 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
}, .{ .session_id = session_id }); }, .{ .session_id = session_id });
} }
const reason_: ?[]const u8 = switch (event.opts.reason) {
.anchor => "anchorClick",
.script, .history, .navigation => "scriptInitiated",
.form => switch (event.opts.method) {
.GET => "formSubmissionGet",
.POST => "formSubmissionPost",
else => unreachable,
},
.address_bar => null,
};
if (reason_ != null) { if (reason_ != null) {
try cdp.sendEvent("Page.frameClearedScheduledNavigation", .{ try cdp.sendEvent("Page.frameClearedScheduledNavigation", .{
.frameId = target_id, .frameId = target_id,
@@ -319,8 +351,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
bc.inspector.contextCreated( bc.inspector.contextCreated(
page.js, page.js,
"", "",
"", // @ZIGDOM try page.getOrigin(arena) orelse "",
// try page.origin(arena),
aux_data, aux_data,
true, true,
); );
@@ -336,37 +367,14 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
false, false,
); );
} }
}
pub fn pageRemove(bc: anytype) !void {
// The main page is going to be removed, we need to remove contexts from other worlds first.
for (bc.isolated_worlds.items) |*isolated_world| {
try isolated_world.removeContext();
}
}
pub fn pageCreated(bc: anytype, page: *Page) !void {
for (bc.isolated_worlds.items) |*isolated_world| {
try isolated_world.createContextAndLoadPolyfills(page);
}
}
pub fn pageNavigated(bc: anytype, event: *const Notification.PageNavigated) !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;
const timestamp = event.timestamp;
var cdp = bc.cdp;
// frameNavigated event // frameNavigated event
try cdp.sendEvent("Page.frameNavigated", .{ try cdp.sendEvent("Page.frameNavigated", .{
.type = "Navigation", .type = "Navigation",
.frame = Frame{ .frame = Frame{
.id = target_id, .id = target_id,
.url = event.url, .url = event.url,
.loaderId = bc.loader_id, .loaderId = loader_id,
.securityOrigin = bc.security_origin, .securityOrigin = bc.security_origin,
.secureContextType = bc.secure_context_type, .secureContextType = bc.secure_context_type,
}, },

View File

@@ -260,14 +260,23 @@ pub fn fulfillTransfer(self: *Client, transfer: *Transfer, status: u16, headers:
return transfer.fulfill(status, headers, body); return transfer.fulfill(status, headers, body);
} }
pub fn nextReqId(self: *Client) usize {
return self.next_request_id + 1;
}
pub fn incrReqId(self: *Client) usize {
const id = self.next_request_id + 1;
self.next_request_id = id;
return id;
}
fn makeTransfer(self: *Client, req: Request) !*Transfer { fn makeTransfer(self: *Client, req: Request) !*Transfer {
errdefer req.headers.deinit(); errdefer req.headers.deinit();
const transfer = try self.transfer_pool.create(); const transfer = try self.transfer_pool.create();
errdefer self.transfer_pool.destroy(transfer); errdefer self.transfer_pool.destroy(transfer);
const id = self.next_request_id + 1; const id = self.incrReqId();
self.next_request_id = id;
transfer.* = .{ transfer.* = .{
.arena = ArenaAllocator.init(self.allocator), .arena = ArenaAllocator.init(self.allocator),
.id = id, .id = id,
@@ -673,6 +682,19 @@ pub const Request = struct {
xhr, xhr,
script, script,
fetch, fetch,
// Allowed Values: Document, Stylesheet, Image, Media, Font, Script,
// TextTrack, XHR, Fetch, Prefetch, EventSource, WebSocket, Manifest,
// SignedExchange, Ping, CSPViolationReport, Preflight, FedCM, Other
// https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-ResourceType
pub fn string(self: ResourceType) []const u8 {
return switch (self) {
.document => "Document",
.xhr => "XHR",
.script => "Script",
.fetch => "Fetch",
};
}
}; };
}; };