webapi: keep explicit null as AbortSignal abort reason

Fixes 1 failing test in WPT /dom/abort/event.any.html (and its .worker
variant): "AbortController abort(null) should set signal.reason".

abort() reason parameters were typed ?js.Value.Global, and the JS->Zig
conversion collapses an explicit JS null into a missing argument, so
controller.abort(null) fell back to the default "AbortError"
DOMException. Per the DOM spec, the abort reason defaults to a new
AbortError only when the reason is not given (undefined); an explicit
null must be stored as-is.

The parameters are now ?js.Value, which preserves the missing vs
explicit-null distinction, and the new AbortSignal.reasonFromJs helper
only falls back to the default for a missing or undefined reason.

Coverage: /dom/abort/event.any.worker.html 15/16 -> 16/16,
/dom/abort/event.any.html 15/16 -> 16/16.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Francis Bouvier
2026-07-10 16:47:42 +02:00
parent 7d23b62573
commit 1d263aabbd
2 changed files with 15 additions and 4 deletions

View File

@@ -37,8 +37,8 @@ pub fn getSignal(self: *const AbortController) *AbortSignal {
return self._signal;
}
pub fn abort(self: *AbortController, reason_: ?js.Value.Global, exec: *const Execution) !void {
try self._signal.abort(if (reason_) |r| .{ .js_val = r } else null, exec);
pub fn abort(self: *AbortController, reason_: ?js.Value, exec: *const Execution) !void {
try self._signal.abort(try AbortSignal.reasonFromJs(reason_), exec);
}
pub const JsApi = struct {

View File

@@ -149,10 +149,21 @@ fn dispatchAbortEvent(self: *AbortSignal, exec: *const Execution) !void {
}
}
// Converts an abort(reason) JS argument to a Reason. Per spec, only a
// missing or undefined reason falls back to the default "AbortError"
// DOMException: an explicit null (or any other value) is kept as-is.
pub fn reasonFromJs(reason_: ?js.Value) !?Reason {
const reason = reason_ orelse return null;
if (reason.isUndefined()) {
return null;
}
return .{ .js_val = try reason.persist() };
}
// Static method to create an already-aborted signal
pub fn createAborted(reason_: ?js.Value.Global, exec: *const Execution) !*AbortSignal {
pub fn createAborted(reason_: ?js.Value, exec: *const Execution) !*AbortSignal {
const signal = try init(exec);
try signal.abort(if (reason_) |r| .{ .js_val = r } else null, exec);
try signal.abort(try reasonFromJs(reason_), exec);
return signal;
}