diff --git a/src/Notification.zig b/src/Notification.zig index dea1d549..1f364d03 100644 --- a/src/Notification.zig +++ b/src/Notification.zig @@ -108,14 +108,17 @@ const EventType = std.meta.FieldEnum(Events); pub const PageRemove = struct {}; pub const PageNavigate = struct { + req_id: usize, timestamp: u64, url: [:0]const u8, opts: Page.NavigateOpts, }; pub const PageNavigated = struct { + req_id: usize, timestamp: u64, url: [:0]const u8, + opts: Page.NavigatedOpts, }; pub const PageNetworkIdle = struct { @@ -314,6 +317,7 @@ test "Notification" { // noop notifier.dispatch(.page_navigate, &.{ + .req_id = 1, .timestamp = 4, .url = undefined, .opts = .{}, @@ -323,6 +327,7 @@ test "Notification" { try notifier.register(.page_navigate, &tc, TestClient.pageNavigate); notifier.dispatch(.page_navigate, &.{ + .req_id = 1, .timestamp = 4, .url = undefined, .opts = .{}, @@ -331,6 +336,7 @@ test "Notification" { notifier.unregisterAll(&tc); notifier.dispatch(.page_navigate, &.{ + .req_id = 1, .timestamp = 10, .url = undefined, .opts = .{}, @@ -340,21 +346,23 @@ test "Notification" { try notifier.register(.page_navigate, &tc, TestClient.pageNavigate); try notifier.register(.page_navigated, &tc, TestClient.pageNavigated); notifier.dispatch(.page_navigate, &.{ + .req_id = 1, .timestamp = 10, .url = undefined, .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(6, tc.page_navigated); notifier.unregisterAll(&tc); notifier.dispatch(.page_navigate, &.{ + .req_id = 1, .timestamp = 100, .url = undefined, .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(6, tc.page_navigated); @@ -362,27 +370,27 @@ test "Notification" { // unregister try notifier.register(.page_navigate, &tc, TestClient.pageNavigate); try notifier.register(.page_navigated, &tc, TestClient.pageNavigated); - notifier.dispatch(.page_navigate, &.{ .timestamp = 100, .url = undefined, .opts = .{} }); - notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined }); + notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} }); + notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} }); try testing.expectEqual(114, tc.page_navigate); try testing.expectEqual(1006, tc.page_navigated); notifier.unregister(.page_navigate, &tc); - notifier.dispatch(.page_navigate, &.{ .timestamp = 100, .url = undefined, .opts = .{} }); - notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined }); + notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} }); + notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} }); try testing.expectEqual(114, tc.page_navigate); try testing.expectEqual(2006, tc.page_navigated); notifier.unregister(.page_navigated, &tc); - notifier.dispatch(.page_navigate, &.{ .timestamp = 100, .url = undefined, .opts = .{} }); - notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined }); + notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} }); + notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} }); try testing.expectEqual(114, tc.page_navigate); try testing.expectEqual(2006, tc.page_navigated); // already unregistered, try anyways notifier.unregister(.page_navigated, &tc); - notifier.dispatch(.page_navigate, &.{ .timestamp = 100, .url = undefined, .opts = .{} }); - notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined }); + notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} }); + notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} }); try testing.expectEqual(114, tc.page_navigate); try testing.expectEqual(2006, tc.page_navigated); } diff --git a/src/browser/Mime.zig b/src/browser/Mime.zig index 27fe35a8..144f6b23 100644 --- a/src/browser/Mime.zig +++ b/src/browser/Mime.zig @@ -24,6 +24,7 @@ params: []const u8 = "", // IANA defines max. charset value length as 40. // We keep 41 for null-termination since HTML parser expects in this format. charset: [41]u8 = default_charset, +charset_len: usize = 5, /// String "UTF-8" continued by null characters. 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 }, }; +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. -pub fn charsetString(mime: *const Mime) [:0]const u8 { - return @ptrCast(&mime.charset); +pub fn charsetStringZ(mime: *const Mime) [:0]const u8 { + 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. @@ -99,6 +116,7 @@ pub fn parse(input: []u8) !Mime { const params = trimLeft(normalized[type_len..]); var charset: [41]u8 = undefined; + var charset_len: usize = undefined; var it = std.mem.splitScalar(u8, params, ';'); while (it.next()) |attr| { @@ -124,6 +142,7 @@ pub fn parse(input: []u8) !Mime { @memcpy(charset[0..attribute_value.len], attribute_value); // Null-terminate right after attribute value. charset[attribute_value.len] = 0; + charset_len = attribute_value.len; }, } } @@ -131,6 +150,7 @@ pub fn parse(input: []u8) !Mime { return .{ .params = params, .charset = charset, + .charset_len = charset_len, .content_type = content_type, }; } @@ -510,9 +530,9 @@ fn expect(expected: Expectation, input: []const u8) !void { if (expected.charset) |ec| { // 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 { const m: Mime = .unknown; - try testing.expectEqual(m.charsetString(), actual.charsetString()); + try testing.expectEqual(m.charsetStringZ(), actual.charsetStringZ()); } } diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 799052c3..ecff9bec 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -161,6 +161,9 @@ version: usize, scheduler: Scheduler, +_req_id: ?usize = null, +_navigated_options: ?NavigatedOpts = null, + pub fn init(arena: Allocator, call_arena: Allocator, session: *Session) !*Page { if (comptime IS_DEBUG) { log.debug(.page, "page.init", .{}); @@ -290,6 +293,17 @@ pub fn getTitle(self: *Page) !?[]const u8 { 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 { 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); } + const req_id = self._session.browser.http_client.nextReqId(); log.info(.page, "navigate", .{ .url = request_url, .method = opts.method, .reason = opts.reason, .body = opts.body != null, + .req_id = req_id, }); // 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._session.browser.notification.dispatch(.page_navigate, &.{ + .req_id = req_id, .opts = opts, .url = request_url, .timestamp = timestamp(.monotonic), }); 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, .timestamp = timestamp(.monotonic), }); + // force next request id manually b/c we won't create a real req. + _ = self._session.browser.http_client.incrReqId(); 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._req_id = req_id; + self._navigated_options = .{ + .cdp_id = opts.cdp_id, + .reason = opts.reason, + .method = opts.method, + }; + var headers = try http_client.newHeaders(); if (opts.header) |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. // It ensures the event page_navigated is not dispatched before this one. self._session.browser.notification.dispatch(.page_navigate, &.{ + .req_id = req_id, .opts = opts, .url = self.url, .timestamp = timestamp(.monotonic), @@ -439,7 +472,14 @@ pub fn documentIsComplete(self: *Page) void { 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, &.{ + .req_id = self._req_id.?, + .opts = self._navigated_options.?, .url = self.url, .timestamp = timestamp(.monotonic), }); @@ -803,7 +843,6 @@ fn printWaitAnalysis(self: *Page) void { } } - { std.debug.print("\ndeferreds: {d}\n", .{self._script_manager.defer_scripts.len()}); 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 { anchor, address_bar, @@ -2278,6 +2311,12 @@ pub const NavigateOpts = struct { force: bool = false, }; +pub const NavigatedOpts = struct { + cdp_id: ?i64 = null, + reason: NavigateReason = .address_bar, + method: Http.Method = .GET, +}; + const RequestCookieOpts = struct { is_http: bool = true, is_navigation: bool = false, diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 2d1a4cbd..0fc88c0f 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -1028,7 +1028,7 @@ const valueToStringOpts = struct { pub fn valueToString(self: *const Context, js_val: v8.Value, opts: valueToStringOpts) ![]u8 { const allocator = opts.allocator orelse self.call_arena; 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); 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 { const allocator = opts.allocator orelse self.call_arena; 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); 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()) { - 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_str = try self.valueToString(js_sym_desc, .{}); return writer.print("{s} (symbol)", .{js_sym_str}); diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 2fbfe4dd..4bb9dc53 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -552,7 +552,8 @@ pub fn BrowserContext(comptime CDP_T: type) type { pub fn onPageNavigated(ctx: *anyopaque, msg: *const Notification.PageNavigated) !void { 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 { diff --git a/src/cdp/domains/network.zig b/src/cdp/domains/network.zig index ea4ebf60..30f77234 100644 --- a/src/cdp/domains/network.zig +++ b/src/cdp/domains/network.zig @@ -23,6 +23,7 @@ const CdpStorage = @import("storage.zig"); const URL = @import("../../browser/URL.zig"); const Transfer = @import("../../http/Client.zig").Transfer; const Notification = @import("../../Notification.zig"); +const Mime = @import("../../browser/Mime.zig"); pub fn processMessage(cmd: anytype) !void { 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 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 = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}), + .requestId = loader_id, .frameId = target_id, - .loaderId = bc.loader_id, - .documentUrl = page.url, + .loaderId = loader_id, + .type = msg.transfer.req.resource_type.string(), + .documentURL = page.url, .request = TransferAsRequestWriter.init(transfer), .initiator = .{ .type = "other" }, + .redirectHasExtraInfo = false, // TODO change after adding Network.requestWillBeSentExtraInfo + .hasUserGesture = false, }, .{ .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 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 = try std.fmt.allocPrint(arena, "REQ-{d}", .{msg.transfer.id}), - .loaderId = bc.loader_id, + .requestId = loader_id, .frameId = target_id, + .loaderId = loader_id, .response = TransferAsResponseWriter.init(arena, msg.transfer), + .hasExtraInfo = false, // TODO change after adding Network.responseReceivedExtraInfo }, .{ .session_id = session_id }); } @@ -381,6 +391,20 @@ const TransferAsResponseWriter = struct { 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 // common to get these from a server (e.g. for Cache-Control), but diff --git a/src/cdp/domains/page.zig b/src/cdp/domains/page.zig index 7f0928eb..4fdbcbc6 100644 --- a/src/cdp/domains/page.zig +++ b/src/cdp/domains/page.zig @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) // // Francis Bouvier // Pierre Tachoire @@ -18,9 +18,9 @@ const std = @import("std"); const Page = @import("../../browser/Page.zig"); +const timestampF = @import("../../datetime.zig").timestamp; const Notification = @import("../../Notification.zig"); const log = @import("../../log.zig"); -const timestampF = @import("../../datetime.zig").timestamp; const Allocator = std.mem.Allocator; @@ -51,7 +51,7 @@ pub fn processMessage(cmd: anytype) !void { const Frame = struct { id: []const u8, loaderId: []const u8, - url: [:0]const u8, + url: []const u8, domainAndRegistry: []const u8 = "", securityOrigin: []const u8, mimeType: []const u8 = "text/html", @@ -101,11 +101,10 @@ fn setLifecycleEventsEnabled(cmd: anytype) !void { if (page._load_state == .complete) { const now = timestampF(.monotonic); - const http_client = page._session.browser.http_client; - try sendPageLifecycle(bc, "DOMContentLoaded", now); try sendPageLifecycle(bc, "load", now); + 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)) { @@ -176,7 +175,7 @@ fn createIsolatedWorld(cmd: anytype) !void { const params = (try cmd.params(struct { frameId: []const u8, worldName: []const u8, - grantUniveralAccess: bool, + grantUniveralAccess: bool = false, })) orelse return error.InvalidParams; if (!params.grantUniveralAccess) { 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; - bc.loader_id = bc.cdp.loader_id_gen.next(); try page.navigate(params.url, .{ .reason = .address_bar, @@ -231,8 +229,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa // things, but no session. const session_id = bc.session_id orelse return; - bc.loader_id = bc.cdp.loader_id_gen.next(); - const loader_id = bc.loader_id; + const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{event.req_id}); const target_id = bc.target_id orelse unreachable; bc.reset(); @@ -240,13 +237,13 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa var cdp = bc.cdp; const reason_: ?[]const u8 = switch (event.opts.reason) { .anchor => "anchorClick", - .script, .history => "scriptInitiated", + .script, .history, .navigation => "scriptInitiated", .form => switch (event.opts.method) { .GET => "formSubmissionGet", .POST => "formSubmissionPost", else => unreachable, }, - .address_bar, .navigation => null, + .address_bar => null, }; if (reason_) |reason| { 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", .{ .frameId = target_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. // 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 }); } + 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) { try cdp.sendEvent("Page.frameClearedScheduledNavigation", .{ .frameId = target_id, @@ -319,8 +351,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa bc.inspector.contextCreated( page.js, "", - "", // @ZIGDOM - // try page.origin(arena), + try page.getOrigin(arena) orelse "", aux_data, true, ); @@ -336,37 +367,14 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa 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 try cdp.sendEvent("Page.frameNavigated", .{ .type = "Navigation", .frame = Frame{ .id = target_id, .url = event.url, - .loaderId = bc.loader_id, + .loaderId = loader_id, .securityOrigin = bc.security_origin, .secureContextType = bc.secure_context_type, }, diff --git a/src/http/Client.zig b/src/http/Client.zig index e9812cf4..bb407e2b 100644 --- a/src/http/Client.zig +++ b/src/http/Client.zig @@ -260,14 +260,23 @@ pub fn fulfillTransfer(self: *Client, transfer: *Transfer, status: u16, headers: 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 { errdefer req.headers.deinit(); const transfer = try self.transfer_pool.create(); errdefer self.transfer_pool.destroy(transfer); - const id = self.next_request_id + 1; - self.next_request_id = id; + const id = self.incrReqId(); transfer.* = .{ .arena = ArenaAllocator.init(self.allocator), .id = id, @@ -673,6 +682,19 @@ pub const Request = struct { xhr, script, 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", + }; + } }; };