Merge pull request #1916 from lightpanda-io/request_abort

Add Request.signal
This commit is contained in:
Karl Seguin
2026-03-19 20:07:12 +08:00
committed by GitHub
3 changed files with 56 additions and 0 deletions

View File

@@ -225,3 +225,17 @@
URL.revokeObjectURL(blobUrl); URL.revokeObjectURL(blobUrl);
}); });
</script> </script>
<script id=abort>
testing.async(async (restore) => {
const controller = new AbortController();
controller.abort();
try {
await fetch('http://127.0.0.1:9582/xhr', { signal: controller.signal });
testain.fail('fetch should have been aborted');
} catch (e) {
restore();
testing.expectEqual("AbortError", e.name);
}
});
</script>

View File

@@ -28,6 +28,8 @@ const URL = @import("../../URL.zig");
const Blob = @import("../Blob.zig"); const Blob = @import("../Blob.zig");
const Request = @import("Request.zig"); const Request = @import("Request.zig");
const Response = @import("Response.zig"); const Response = @import("Response.zig");
const AbortSignal = @import("../AbortSignal.zig");
const DOMException = @import("../DOMException.zig");
const IS_DEBUG = @import("builtin").mode == .Debug; const IS_DEBUG = @import("builtin").mode == .Debug;
@@ -39,6 +41,7 @@ _buf: std.ArrayList(u8),
_response: *Response, _response: *Response,
_resolver: js.PromiseResolver.Global, _resolver: js.PromiseResolver.Global,
_owns_response: bool, _owns_response: bool,
_signal: ?*AbortSignal,
pub const Input = Request.Input; pub const Input = Request.Input;
pub const InitOpts = Request.InitOpts; 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 request = try Request.init(input, options, page);
const resolver = page.js.local.?.createPromiseResolver(); 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:")) { if (std.mem.startsWith(u8, request._url, "blob:")) {
return handleBlobUrl(request._url, resolver, page); 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(), ._resolver = try resolver.persist(),
._response = response, ._response = response,
._owns_response = true, ._owns_response = true,
._signal = request._signal,
}; };
const http_client = page._session.browser.http_client; const http_client = page._session.browser.http_client;
@@ -126,6 +137,12 @@ fn httpStartCallback(transfer: *HttpClient.Transfer) !void {
fn httpHeaderDoneCallback(transfer: *HttpClient.Transfer) !bool { fn httpHeaderDoneCallback(transfer: *HttpClient.Transfer) !bool {
const self: *Fetch = @ptrCast(@alignCast(transfer.ctx)); const self: *Fetch = @ptrCast(@alignCast(transfer.ctx));
if (self._signal) |signal| {
if (signal._aborted) {
return false;
}
}
const arena = self._response._arena; const arena = self._response._arena;
if (transfer.getContentLength()) |cl| { if (transfer.getContentLength()) |cl| {
try self._buf.ensureTotalCapacity(arena, 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 { fn httpDataCallback(transfer: *HttpClient.Transfer, data: []const u8) !void {
const self: *Fetch = @ptrCast(@alignCast(transfer.ctx)); 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); try self._buf.appendSlice(self._response._arena, data);
} }

View File

@@ -25,6 +25,7 @@ const URL = @import("../URL.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const Headers = @import("Headers.zig"); const Headers = @import("Headers.zig");
const Blob = @import("../Blob.zig"); const Blob = @import("../Blob.zig");
const AbortSignal = @import("../AbortSignal.zig");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Request = @This(); const Request = @This();
@@ -36,6 +37,7 @@ _body: ?[]const u8,
_arena: Allocator, _arena: Allocator,
_cache: Cache, _cache: Cache,
_credentials: Credentials, _credentials: Credentials,
_signal: ?*AbortSignal,
pub const Input = union(enum) { pub const Input = union(enum) {
request: *Request, request: *Request,
@@ -48,6 +50,7 @@ pub const InitOpts = struct {
body: ?[]const u8 = null, body: ?[]const u8 = null,
cache: Cache = .default, cache: Cache = .default,
credentials: Credentials = .@"same-origin", credentials: Credentials = .@"same-origin",
signal: ?*AbortSignal = null,
}; };
const Credentials = enum { const Credentials = enum {
@@ -97,6 +100,13 @@ pub fn init(input: Input, opts_: ?InitOpts, page: *Page) !*Request {
.request => |r| r._body, .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{ return page._factory.create(Request{
._url = url, ._url = url,
._arena = arena, ._arena = arena,
@@ -105,6 +115,7 @@ pub fn init(input: Input, opts_: ?InitOpts, page: *Page) !*Request {
._cache = opts.cache, ._cache = opts.cache,
._credentials = opts.credentials, ._credentials = opts.credentials,
._body = body, ._body = body,
._signal = signal,
}); });
} }
@@ -144,6 +155,10 @@ pub fn getCredentials(self: *const Request) []const u8 {
return @tagName(self._credentials); return @tagName(self._credentials);
} }
pub fn getSignal(self: *const Request) ?*AbortSignal {
return self._signal;
}
pub fn getHeaders(self: *Request, page: *Page) !*Headers { pub fn getHeaders(self: *Request, page: *Page) !*Headers {
if (self._headers) |headers| { if (self._headers) |headers| {
return headers; return headers;
@@ -200,6 +215,7 @@ pub fn clone(self: *const Request, page: *Page) !*Request {
._cache = self._cache, ._cache = self._cache,
._credentials = self._credentials, ._credentials = self._credentials,
._body = self._body, ._body = self._body,
._signal = self._signal,
}); });
} }
@@ -218,6 +234,7 @@ pub const JsApi = struct {
pub const headers = bridge.accessor(Request.getHeaders, null, .{}); pub const headers = bridge.accessor(Request.getHeaders, null, .{});
pub const cache = bridge.accessor(Request.getCache, null, .{}); pub const cache = bridge.accessor(Request.getCache, null, .{});
pub const credentials = bridge.accessor(Request.getCredentials, 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 blob = bridge.function(Request.blob, .{});
pub const text = bridge.function(Request.text, .{}); pub const text = bridge.function(Request.text, .{});
pub const json = bridge.function(Request.json, .{}); pub const json = bridge.function(Request.json, .{});