diff --git a/src/browser/tests/net/fetch.html b/src/browser/tests/net/fetch.html index 10ce6677..229d8469 100644 --- a/src/browser/tests/net/fetch.html +++ b/src/browser/tests/net/fetch.html @@ -225,3 +225,17 @@ URL.revokeObjectURL(blobUrl); }); + + diff --git a/src/browser/webapi/net/Fetch.zig b/src/browser/webapi/net/Fetch.zig index 7ab7b8ec..0a44aae2 100644 --- a/src/browser/webapi/net/Fetch.zig +++ b/src/browser/webapi/net/Fetch.zig @@ -28,6 +28,8 @@ const URL = @import("../../URL.zig"); const Blob = @import("../Blob.zig"); const Request = @import("Request.zig"); const Response = @import("Response.zig"); +const AbortSignal = @import("../AbortSignal.zig"); +const DOMException = @import("../DOMException.zig"); const IS_DEBUG = @import("builtin").mode == .Debug; @@ -39,6 +41,7 @@ _buf: std.ArrayList(u8), _response: *Response, _resolver: js.PromiseResolver.Global, _owns_response: bool, +_signal: ?*AbortSignal, pub const Input = Request.Input; pub const InitOpts = Request.InitOpts; @@ -47,6 +50,13 @@ pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise { const request = try Request.init(input, options, page); const resolver = page.js.local.?.createPromiseResolver(); + if (request._signal) |signal| { + if (signal._aborted) { + resolver.reject("fetch aborted", DOMException.init("The operation was aborted.", "AbortError")); + return resolver.promise(); + } + } + if (std.mem.startsWith(u8, request._url, "blob:")) { return handleBlobUrl(request._url, resolver, page); } @@ -62,6 +72,7 @@ pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise { ._resolver = try resolver.persist(), ._response = response, ._owns_response = true, + ._signal = request._signal, }; const http_client = page._session.browser.http_client; @@ -126,6 +137,12 @@ fn httpStartCallback(transfer: *HttpClient.Transfer) !void { fn httpHeaderDoneCallback(transfer: *HttpClient.Transfer) !bool { const self: *Fetch = @ptrCast(@alignCast(transfer.ctx)); + if (self._signal) |signal| { + if (signal._aborted) { + return false; + } + } + const arena = self._response._arena; if (transfer.getContentLength()) |cl| { try self._buf.ensureTotalCapacity(arena, cl); @@ -175,6 +192,14 @@ fn httpHeaderDoneCallback(transfer: *HttpClient.Transfer) !bool { fn httpDataCallback(transfer: *HttpClient.Transfer, data: []const u8) !void { const self: *Fetch = @ptrCast(@alignCast(transfer.ctx)); + + // Check if aborted + if (self._signal) |signal| { + if (signal._aborted) { + return error.Abort; + } + } + try self._buf.appendSlice(self._response._arena, data); } diff --git a/src/browser/webapi/net/Request.zig b/src/browser/webapi/net/Request.zig index 3d3ca825..6ba8f224 100644 --- a/src/browser/webapi/net/Request.zig +++ b/src/browser/webapi/net/Request.zig @@ -25,6 +25,7 @@ const URL = @import("../URL.zig"); const Page = @import("../../Page.zig"); const Headers = @import("Headers.zig"); const Blob = @import("../Blob.zig"); +const AbortSignal = @import("../AbortSignal.zig"); const Allocator = std.mem.Allocator; const Request = @This(); @@ -36,6 +37,7 @@ _body: ?[]const u8, _arena: Allocator, _cache: Cache, _credentials: Credentials, +_signal: ?*AbortSignal, pub const Input = union(enum) { request: *Request, @@ -48,6 +50,7 @@ pub const InitOpts = struct { body: ?[]const u8 = null, cache: Cache = .default, credentials: Credentials = .@"same-origin", + signal: ?*AbortSignal = null, }; const Credentials = enum { @@ -97,6 +100,13 @@ pub fn init(input: Input, opts_: ?InitOpts, page: *Page) !*Request { .request => |r| r._body, }; + const signal = if (opts.signal) |s| + s + else switch (input) { + .url => null, + .request => |r| r._signal, + }; + return page._factory.create(Request{ ._url = url, ._arena = arena, @@ -105,6 +115,7 @@ pub fn init(input: Input, opts_: ?InitOpts, page: *Page) !*Request { ._cache = opts.cache, ._credentials = opts.credentials, ._body = body, + ._signal = signal, }); } @@ -144,6 +155,10 @@ pub fn getCredentials(self: *const Request) []const u8 { return @tagName(self._credentials); } +pub fn getSignal(self: *const Request) ?*AbortSignal { + return self._signal; +} + pub fn getHeaders(self: *Request, page: *Page) !*Headers { if (self._headers) |headers| { return headers; @@ -200,6 +215,7 @@ pub fn clone(self: *const Request, page: *Page) !*Request { ._cache = self._cache, ._credentials = self._credentials, ._body = self._body, + ._signal = self._signal, }); } @@ -218,6 +234,7 @@ pub const JsApi = struct { pub const headers = bridge.accessor(Request.getHeaders, null, .{}); pub const cache = bridge.accessor(Request.getCache, null, .{}); pub const credentials = bridge.accessor(Request.getCredentials, null, .{}); + pub const signal = bridge.accessor(Request.getSignal, null, .{}); pub const blob = bridge.function(Request.blob, .{}); pub const text = bridge.function(Request.text, .{}); pub const json = bridge.function(Request.json, .{});