Skip to content

Commit 447ef83

Browse files
Merge pull request #1265 from lightpanda-io/network-event
cdp: improve network's events
2 parents 7c97620 + 42440f1 commit 447ef83

File tree

8 files changed

+169
-52
lines changed

8 files changed

+169
-52
lines changed

src/browser/mime.zig

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub const Mime = struct {
2424
// IANA defines max. charset value length as 40.
2525
// We keep 41 for null-termination since HTML parser expects in this format.
2626
charset: [41]u8 = default_charset,
27+
charset_len: usize = 5,
2728

2829
/// String "UTF-8" continued by null characters.
2930
pub const default_charset = .{ 'U', 'T', 'F', '-', '8' } ++ .{0} ** 36;
@@ -53,9 +54,25 @@ pub const Mime = struct {
5354
other: struct { type: []const u8, sub_type: []const u8 },
5455
};
5556

57+
pub fn contentTypeString(mime: *const Mime) []const u8 {
58+
return switch (mime.content_type) {
59+
.text_xml => "text/xml",
60+
.text_html => "text/html",
61+
.text_javascript => "application/javascript",
62+
.text_plain => "text/plain",
63+
.text_css => "text/css",
64+
.application_json => "application/json",
65+
else => "",
66+
};
67+
}
68+
5669
/// Returns the null-terminated charset value.
57-
pub fn charsetString(mime: *const Mime) [:0]const u8 {
58-
return @ptrCast(&mime.charset);
70+
pub fn charsetStringZ(mime: *const Mime) [:0]const u8 {
71+
return mime.charset[0..mime.charset_len :0];
72+
}
73+
74+
pub fn charsetString(mime: *const Mime) []const u8 {
75+
return mime.charset[0..mime.charset_len];
5976
}
6077

6178
/// Removes quotes of value if quotes are given.
@@ -99,6 +116,7 @@ pub const Mime = struct {
99116
const params = trimLeft(normalized[type_len..]);
100117

101118
var charset: [41]u8 = undefined;
119+
var charset_len: usize = undefined;
102120

103121
var it = std.mem.splitScalar(u8, params, ';');
104122
while (it.next()) |attr| {
@@ -124,13 +142,15 @@ pub const Mime = struct {
124142
@memcpy(charset[0..attribute_value.len], attribute_value);
125143
// Null-terminate right after attribute value.
126144
charset[attribute_value.len] = 0;
145+
charset_len = attribute_value.len;
127146
},
128147
}
129148
}
130149

131150
return .{
132151
.params = params,
133152
.charset = charset,
153+
.charset_len = charset_len,
134154
.content_type = content_type,
135155
};
136156
}
@@ -511,9 +531,9 @@ fn expect(expected: Expectation, input: []const u8) !void {
511531

512532
if (expected.charset) |ec| {
513533
// We remove the null characters for testing purposes here.
514-
try testing.expectEqual(ec, actual.charsetString()[0..ec.len]);
534+
try testing.expectEqual(ec, actual.charsetString());
515535
} else {
516536
const m: Mime = .unknown;
517-
try testing.expectEqual(m.charsetString(), actual.charsetString());
537+
try testing.expectEqual(m.charsetStringZ(), actual.charsetStringZ());
518538
}
519539
}

src/browser/page.zig

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ pub const Page = struct {
8383

8484
// indicates intention to navigate to another page on the next loop execution.
8585
delayed_navigation: bool = false,
86+
req_id: ?usize = null,
87+
navigated_options: ?NavigatedOpts = null,
8688

8789
state_pool: *std.heap.MemoryPool(State),
8890

@@ -546,11 +548,14 @@ pub const Page = struct {
546548
try self.reset();
547549
}
548550

551+
const req_id = self.http_client.nextReqId();
552+
549553
log.info(.http, "navigate", .{
550554
.url = request_url,
551555
.method = opts.method,
552556
.reason = opts.reason,
553557
.body = opts.body != null,
558+
.req_id = req_id,
554559
});
555560

556561
// if the url is about:blank, we load an empty HTML document in the
@@ -568,29 +573,47 @@ pub const Page = struct {
568573
self.documentIsComplete();
569574

570575
self.session.browser.notification.dispatch(.page_navigate, &.{
576+
.req_id = req_id,
571577
.opts = opts,
572578
.url = request_url,
573579
.timestamp = timestamp(),
574580
});
575581

576582
self.session.browser.notification.dispatch(.page_navigated, &.{
583+
.req_id = req_id,
584+
.opts = .{
585+
.cdp_id = opts.cdp_id,
586+
.reason = opts.reason,
587+
.method = opts.method,
588+
},
577589
.url = request_url,
578590
.timestamp = timestamp(),
579591
});
580592

593+
// force next request id manually b/c we won't create a real req.
594+
_ = self.http_client.incrReqId();
595+
581596
return;
582597
}
583598

584599
const owned_url = try self.arena.dupeZ(u8, request_url);
585600
self.url = try URL.parse(owned_url, null);
586601

602+
self.req_id = req_id;
603+
self.navigated_options = .{
604+
.cdp_id = opts.cdp_id,
605+
.reason = opts.reason,
606+
.method = opts.method,
607+
};
608+
587609
var headers = try self.http_client.newHeaders();
588610
if (opts.header) |hdr| try headers.add(hdr);
589611
try self.requestCookie(.{ .is_navigation = true }).headersForRequest(self.arena, owned_url, &headers);
590612

591613
// We dispatch page_navigate event before sending the request.
592614
// It ensures the event page_navigated is not dispatched before this one.
593615
self.session.browser.notification.dispatch(.page_navigate, &.{
616+
.req_id = req_id,
594617
.opts = opts,
595618
.url = owned_url,
596619
.timestamp = timestamp(),
@@ -656,7 +679,11 @@ pub const Page = struct {
656679
log.err(.browser, "document is complete", .{ .err = err });
657680
};
658681

682+
std.debug.assert(self.req_id != null);
683+
std.debug.assert(self.navigated_options != null);
659684
self.session.browser.notification.dispatch(.page_navigated, &.{
685+
.req_id = self.req_id.?,
686+
.opts = self.navigated_options.?,
660687
.url = self.url.raw,
661688
.timestamp = timestamp(),
662689
});
@@ -713,14 +740,14 @@ pub const Page = struct {
713740
log.debug(.http, "navigate first chunk", .{ .content_type = mime.content_type, .len = data.len });
714741

715742
self.mode = switch (mime.content_type) {
716-
.text_html => .{ .html = try parser.Parser.init(mime.charsetString()) },
743+
.text_html => .{ .html = try parser.Parser.init(mime.charsetStringZ()) },
717744

718745
.application_json,
719746
.text_javascript,
720747
.text_css,
721748
.text_plain,
722749
=> blk: {
723-
var p = try parser.Parser.init(mime.charsetString());
750+
var p = try parser.Parser.init(mime.charsetStringZ());
724751
try p.process("<html><head><meta charset=\"utf-8\"></head><body><pre>");
725752
break :blk .{ .text = p };
726753
},
@@ -1264,6 +1291,12 @@ pub const NavigateOpts = struct {
12641291
force: bool = false,
12651292
};
12661293

1294+
pub const NavigatedOpts = struct {
1295+
cdp_id: ?i64 = null,
1296+
reason: NavigateReason = .address_bar,
1297+
method: Http.Method = .GET,
1298+
};
1299+
12671300
const IdleNotification = union(enum) {
12681301
// hasn't started yet.
12691302
init,

src/browser/xhr/xhr.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ pub const XMLHttpRequest = struct {
678678
}
679679

680680
var fbs = std.io.fixedBufferStream(self.response_bytes.items);
681-
const doc = parser.documentHTMLParse(fbs.reader(), mime.charsetString()) catch {
681+
const doc = parser.documentHTMLParse(fbs.reader(), mime.charsetStringZ()) catch {
682682
self.response_obj = .{ .Failure = {} };
683683
return;
684684
};

src/cdp/cdp.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,8 @@ pub fn BrowserContext(comptime CDP_T: type) type {
551551

552552
pub fn onPageNavigated(ctx: *anyopaque, msg: *const Notification.PageNavigated) !void {
553553
const self: *Self = @ptrCast(@alignCast(ctx));
554-
return @import("domains/page.zig").pageNavigated(self, msg);
554+
defer self.resetNotificationArena();
555+
return @import("domains/page.zig").pageNavigated(self.notification_arena, self, msg);
555556
}
556557

557558
pub fn onPageNetworkIdle(ctx: *anyopaque, msg: *const Notification.PageNetworkIdle) !void {

src/cdp/domains/network.zig

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const Allocator = std.mem.Allocator;
2222
const CdpStorage = @import("storage.zig");
2323
const Transfer = @import("../../http/Client.zig").Transfer;
2424
const Notification = @import("../../notification.zig").Notification;
25+
const Mime = @import("../../browser/mime.zig").Mime;
2526

2627
pub fn processMessage(cmd: anytype) !void {
2728
const action = std.meta.stringToEnum(enum {
@@ -242,14 +243,18 @@ pub fn httpRequestStart(arena: Allocator, bc: anytype, msg: *const Notification.
242243
}
243244

244245
const transfer = msg.transfer;
246+
const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id});
245247
// We're missing a bunch of fields, but, for now, this seems like enough
246248
try bc.cdp.sendEvent("Network.requestWillBeSent", .{
247-
.requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}),
249+
.requestId = loader_id,
248250
.frameId = target_id,
249-
.loaderId = bc.loader_id,
250-
.documentUrl = DocumentUrlWriter.init(&page.url.uri),
251+
.loaderId = loader_id,
252+
.type = msg.transfer.req.resource_type.string(),
253+
.documentURL = DocumentUrlWriter.init(&page.url.uri),
251254
.request = TransferAsRequestWriter.init(transfer),
252255
.initiator = .{ .type = "other" },
256+
.redirectHasExtraInfo = false, // TODO change after adding Network.requestWillBeSentExtraInfo
257+
.hasUserGesture = false,
253258
}, .{ .session_id = session_id });
254259
}
255260

@@ -259,12 +264,16 @@ pub fn httpResponseHeaderDone(arena: Allocator, bc: anytype, msg: *const Notific
259264
const session_id = bc.session_id orelse return;
260265
const target_id = bc.target_id orelse unreachable;
261266

267+
const transfer = msg.transfer;
268+
const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id});
269+
262270
// We're missing a bunch of fields, but, for now, this seems like enough
263271
try bc.cdp.sendEvent("Network.responseReceived", .{
264-
.requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{msg.transfer.id}),
265-
.loaderId = bc.loader_id,
272+
.requestId = loader_id,
266273
.frameId = target_id,
274+
.loaderId = loader_id,
267275
.response = TransferAsResponseWriter.init(arena, msg.transfer),
276+
.hasExtraInfo = false, // TODO change after adding Network.responseReceivedExtraInfo
268277
}, .{ .session_id = session_id });
269278
}
270279

@@ -392,6 +401,20 @@ const TransferAsResponseWriter = struct {
392401
try jws.write(@as(std.http.Status, @enumFromInt(status)).phrase() orelse "Unknown");
393402
}
394403

404+
{
405+
const mime: Mime = blk: {
406+
if (transfer.response_header.?.contentType()) |ct| {
407+
break :blk try Mime.parse(ct);
408+
}
409+
break :blk .unknown;
410+
};
411+
412+
try jws.objectField("mimeType");
413+
try jws.write(mime.contentTypeString());
414+
try jws.objectField("charset");
415+
try jws.write(mime.charsetString());
416+
}
417+
395418
{
396419
// chromedp doesn't like having duplicate header names. It's pretty
397420
// common to get these from a server (e.g. for Cache-Control), but

src/cdp/domains/page.zig

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,6 @@ fn navigate(cmd: anytype) !void {
176176
}
177177

178178
var page = bc.session.currentPage() orelse return error.PageNotLoaded;
179-
bc.loader_id = bc.cdp.loader_id_gen.next();
180179

181180
try page.navigate(params.url, .{
182181
.reason = .address_bar,
@@ -189,8 +188,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
189188
// things, but no session.
190189
const session_id = bc.session_id orelse return;
191190

192-
bc.loader_id = bc.cdp.loader_id_gen.next();
193-
const loader_id = bc.loader_id;
191+
const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{event.req_id});
194192
const target_id = bc.target_id orelse unreachable;
195193

196194
bc.reset();
@@ -234,6 +232,30 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
234232
try cdp.sendEvent("Page.frameStartedLoading", .{
235233
.frameId = target_id,
236234
}, .{ .session_id = session_id });
235+
}
236+
237+
pub fn pageRemove(bc: anytype) !void {
238+
// The main page is going to be removed, we need to remove contexts from other worlds first.
239+
for (bc.isolated_worlds.items) |*isolated_world| {
240+
try isolated_world.removeContext();
241+
}
242+
}
243+
244+
pub fn pageCreated(bc: anytype, page: *Page) !void {
245+
for (bc.isolated_worlds.items) |*isolated_world| {
246+
try isolated_world.createContextAndLoadPolyfills(bc.arena, page);
247+
}
248+
}
249+
250+
pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.PageNavigated) !void {
251+
// detachTarget could be called, in which case, we still have a page doing
252+
// things, but no session.
253+
const session_id = bc.session_id orelse return;
254+
const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{event.req_id});
255+
const target_id = bc.target_id orelse unreachable;
256+
const timestamp = event.timestamp;
257+
258+
var cdp = bc.cdp;
237259

238260
// Drivers are sensitive to the order of events. Some more than others.
239261
// The result for the Page.navigate seems like it _must_ come after
@@ -260,6 +282,17 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
260282
}, .{ .session_id = session_id });
261283
}
262284

285+
const reason_: ?[]const u8 = switch (event.opts.reason) {
286+
.anchor => "anchorClick",
287+
.script, .history, .navigation => "scriptInitiated",
288+
.form => switch (event.opts.method) {
289+
.GET => "formSubmissionGet",
290+
.POST => "formSubmissionPost",
291+
else => unreachable,
292+
},
293+
.address_bar => null,
294+
};
295+
263296
if (reason_ != null) {
264297
try cdp.sendEvent("Page.frameClearedScheduledNavigation", .{
265298
.frameId = target_id,
@@ -293,37 +326,14 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
293326
false,
294327
);
295328
}
296-
}
297-
298-
pub fn pageRemove(bc: anytype) !void {
299-
// The main page is going to be removed, we need to remove contexts from other worlds first.
300-
for (bc.isolated_worlds.items) |*isolated_world| {
301-
try isolated_world.removeContext();
302-
}
303-
}
304-
305-
pub fn pageCreated(bc: anytype, page: *Page) !void {
306-
for (bc.isolated_worlds.items) |*isolated_world| {
307-
try isolated_world.createContextAndLoadPolyfills(bc.arena, page);
308-
}
309-
}
310-
311-
pub fn pageNavigated(bc: anytype, event: *const Notification.PageNavigated) !void {
312-
// detachTarget could be called, in which case, we still have a page doing
313-
// things, but no session.
314-
const session_id = bc.session_id orelse return;
315-
const loader_id = bc.loader_id;
316-
const target_id = bc.target_id orelse unreachable;
317-
const timestamp = event.timestamp;
318329

319-
var cdp = bc.cdp;
320330
// frameNavigated event
321331
try cdp.sendEvent("Page.frameNavigated", .{
322332
.type = "Navigation",
323333
.frame = Frame{
324334
.id = target_id,
325335
.url = event.url,
326-
.loaderId = bc.loader_id,
336+
.loaderId = loader_id,
327337
.securityOrigin = bc.security_origin,
328338
.secureContextType = bc.secure_context_type,
329339
},

0 commit comments

Comments
 (0)