diff --git a/src/browser/page.zig b/src/browser/page.zig index 7044a4d9..69d96e54 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -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); diff --git a/src/browser/xhr/xhr.zig b/src/browser/xhr/xhr.zig index 52428a0f..907e1057 100644 --- a/src/browser/xhr/xhr.zig +++ b/src/browser/xhr/xhr.zig @@ -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" }); }; diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 8a474c37..fd8a1da6 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -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(); diff --git a/src/cdp/domains/network.zig b/src/cdp/domains/network.zig index 203131e8..3c0c985d 100644 --- a/src/cdp/domains/network.zig +++ b/src/cdp/domains/network.zig @@ -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. diff --git a/src/http/client.zig b/src/http/client.zig index 8d11a013..f060176c 100644 --- a/src/http/client.zig +++ b/src/http/client.zig @@ -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(); } diff --git a/src/notification.zig b/src/notification.zig index 5a32f559..e3170231 100644 --- a/src/notification.zig +++ b/src/notification.zig @@ -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, diff --git a/src/runtime/loop.zig b/src/runtime/loop.zig index a1af200e..cd4f1464 100644 --- a/src/runtime/loop.zig +++ b/src/runtime/loop.zig @@ -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 };