Emit http_request_fail notification

CDP translate this into a Network.loadingFailed. This is necessary to make sure
every Network.requestWillBeSent is paired with either a Network.loadingFailed
or a Network.responseReceived.
This commit is contained in:
Karl Seguin
2025-06-06 19:12:13 +08:00
parent 305460dedb
commit 2b48902f1b
7 changed files with 72 additions and 4 deletions

View File

@@ -247,6 +247,8 @@ pub const Page = struct {
.content_type = content_type,
.charset = mime.charset,
.url = request_url,
.method = opts.method,
.reason = opts.reason,
});
if (!mime.isHTML()) {
@@ -597,6 +599,10 @@ pub const Page = struct {
// The page.arena is safe to use here, but the transfer_arena exists
// specifically for this type of lifetime.
pub fn navigateFromWebAPI(self: *Page, url: []const u8, opts: NavigateOpts) !void {
log.debug(.browser, "delayed navigation", .{
.url = url,
.reason = opts.reason,
});
self.delayed_navigation = true;
const arena = self.session.transfer_arena;
const navi = try arena.create(DelayedNavigation);

View File

@@ -338,7 +338,11 @@ pub const XMLHttpRequest = struct {
// dispatch request event.
// errors are logged only.
fn dispatchEvt(self: *XMLHttpRequest, typ: []const u8) void {
log.debug(.script_event, "dispatch event", .{ .type = typ, .source = "xhr" });
log.debug(.script_event, "dispatch event", .{
.type = typ,
.source = "xhr",
.url = self.url,
});
self._dispatchEvt(typ) catch |err| {
log.err(.app, "dispatch event error", .{ .err = err, .type = typ, .source = "xhr" });
};
@@ -358,7 +362,11 @@ pub const XMLHttpRequest = struct {
typ: []const u8,
opts: ProgressEvent.EventInit,
) void {
log.debug(.script_event, "dispatch progress event", .{ .type = typ, .source = "xhr" });
log.debug(.script_event, "dispatch progress event", .{
.type = typ,
.source = "xhr",
.url = self.url,
});
self._dispatchProgressEvent(typ, opts) catch |err| {
log.err(.app, "dispatch progress event error", .{ .err = err, .type = typ, .source = "xhr" });
};

View File

@@ -412,11 +412,13 @@ pub fn BrowserContext(comptime CDP_T: type) type {
}
pub fn networkEnable(self: *Self) !void {
try self.cdp.browser.notification.register(.http_request_fail, self, onHttpRequestFail);
try self.cdp.browser.notification.register(.http_request_start, self, onHttpRequestStart);
try self.cdp.browser.notification.register(.http_request_complete, self, onHttpRequestComplete);
}
pub fn networkDisable(self: *Self) void {
self.cdp.browser.notification.unregister(.http_request_fail, self);
self.cdp.browser.notification.unregister(.http_request_start, self);
self.cdp.browser.notification.unregister(.http_request_complete, self);
}
@@ -448,6 +450,12 @@ pub fn BrowserContext(comptime CDP_T: type) type {
return @import("domains/network.zig").httpRequestStart(self.notification_arena, self, data);
}
pub fn onHttpRequestFail(ctx: *anyopaque, data: *const Notification.RequestFail) !void {
const self: *Self = @alignCast(@ptrCast(ctx));
defer self.resetNotificationArena();
return @import("domains/network.zig").httpRequestFail(self.notification_arena, self, data);
}
pub fn onHttpRequestComplete(ctx: *anyopaque, data: *const Notification.RequestComplete) !void {
const self: *Self = @alignCast(@ptrCast(ctx));
defer self.resetNotificationArena();

View File

@@ -84,6 +84,24 @@ fn putAssumeCapacity(headers: *std.ArrayListUnmanaged(std.http.Header), extra: s
return true;
}
pub fn httpRequestFail(arena: Allocator, bc: anytype, request: *const Notification.RequestFail) !void {
// Isn't possible to do a network request within a Browser (which our
// notification is tied to), without a page.
std.debug.assert(bc.session.page != null);
// all unreachable because we _have_ to have a page.
const session_id = bc.session_id orelse unreachable;
// 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}", .{request.id}),
// Seems to be what chrome answers with. I assume it depends on the type of error?
.type = "Ping",
.errorText = request.err,
.canceled = false,
}, .{ .session_id = session_id });
}
pub fn httpRequestStart(arena: Allocator, bc: anytype, request: *const Notification.RequestStart) !void {
// Isn't possible to do a network request within a Browser (which our
// notification is tied to), without a page.

View File

@@ -354,6 +354,7 @@ pub const Request = struct {
// Because of things like redirects and error handling, it is possible for
// the notification functions to be called multiple times, so we guard them
// with these booleans
_notified_fail: bool,
_notified_start: bool,
_notified_complete: bool,
@@ -414,6 +415,7 @@ pub const Request = struct {
._keepalive = false,
._redirect_count = 0,
._has_host_header = false,
._notified_fail = false,
._notified_start = false,
._notified_complete = false,
._connection_from_keepalive = false,
@@ -428,6 +430,7 @@ pub const Request = struct {
}
pub fn abort(self: *Request) void {
self.requestFailed("aborted");
const aborter = self._aborter orelse {
self.deinit();
return;
@@ -555,6 +558,10 @@ pub const Request = struct {
}
fn doSendSync(self: *Request, use_pool: bool) anyerror!Response {
// https://github.com/ziglang/zig/issues/20369
// errdefer |err| self.requestFailed(@errorName(err));
errdefer self.requestFailed("network error");
if (use_pool) {
if (self.findExistingConnection(true)) |connection| {
self._connection = connection;
@@ -847,6 +854,19 @@ pub const Request = struct {
});
}
fn requestFailed(self: *Request, err: []const u8) void {
const notification = self.notification orelse return;
if (self._notified_fail) {
return;
}
self._notified_fail = true;
notification.dispatch(.http_request_fail, &.{
.id = self.id,
.err = err,
.url = self.request_uri,
});
}
fn requestCompleted(self: *Request, response: ResponseHeader) void {
const notification = self.notification orelse return;
if (self._notified_complete) {
@@ -1290,6 +1310,8 @@ fn AsyncHandler(comptime H: type, comptime L: type) type {
self.handler.onHttpResponse(err) catch {};
// just to be safe
self.request._keepalive = false;
self.request.requestFailed(@errorName(err));
self.request.deinit();
}

View File

@@ -59,6 +59,7 @@ pub const Notification = struct {
page_created: List = .{},
page_navigate: List = .{},
page_navigated: List = .{},
http_request_fail: List = .{},
http_request_start: List = .{},
http_request_complete: List = .{},
notification_created: List = .{},
@@ -69,6 +70,7 @@ pub const Notification = struct {
page_created: *page.Page,
page_navigate: *const PageNavigate,
page_navigated: *const PageNavigated,
http_request_fail: *const RequestFail,
http_request_start: *const RequestStart,
http_request_complete: *const RequestComplete,
notification_created: *Notification,
@@ -97,6 +99,12 @@ pub const Notification = struct {
has_body: bool,
};
pub const RequestFail = struct {
id: usize,
url: *const std.Uri,
err: []const u8,
};
pub const RequestComplete = struct {
id: usize,
url: *const std.Uri,

View File

@@ -127,7 +127,6 @@ pub const Loop = struct {
}
}
// JS callbacks APIs
// -----------------
@@ -255,7 +254,6 @@ pub const Loop = struct {
}
}.onConnect;
const callback = try self.event_callback_pool.create();
errdefer self.event_callback_pool.destroy(callback);
callback.* = .{ .loop = self, .ctx = ctx };