From cd1a44307bd18f74516df1ae40e3ba27e2e2ce4a Mon Sep 17 00:00:00 2001 From: Daijiro Wachi Date: Sun, 28 Jun 2026 02:04:17 +0900 Subject: [PATCH] fix(abort): throwIfAborted() actually throws the abort reason AbortSignal.prototype.throwIfAborted() set a pending exception via JS_SetPendingException() but then returned true. The JSNative contract requires returning false whenever a pending exception is set, so the throw was not propagated: in release builds the method silently returned undefined instead of throwing, and in debug builds it tripped SpiderMonkey's pending-exception assertion. Return false after setting the exception, matching every other throwing native in this file. Per the DOM spec, throwIfAborted() must throw the signal's abort reason when the signal is aborted. Add an integration test covering: throwing the given reason, the default AbortError, AbortSignal.abort(reason), and the not-aborted no-op case. https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted Signed-off-by: Daijiro Wachi --- builtins/web/abort/abort-signal.cpp | 1 + tests/integration/abort/abort.js | 61 +++++++++++++++++++++++++++++ tests/integration/handlers.js | 1 + tests/tests.cmake | 1 + 4 files changed, 64 insertions(+) create mode 100644 tests/integration/abort/abort.js diff --git a/builtins/web/abort/abort-signal.cpp b/builtins/web/abort/abort-signal.cpp index 2bc6a3a5..aa343fd6 100644 --- a/builtins/web/abort/abort-signal.cpp +++ b/builtins/web/abort/abort-signal.cpp @@ -142,6 +142,7 @@ bool AbortSignal::throwIfAborted(JSContext *cx, unsigned argc, JS::Value *vp) { if (is_aborted(self)) { RootedValue reason(cx, JS::GetReservedSlot(self, std::to_underlying(Slots::Reason))); JS_SetPendingException(cx, reason); + return false; } return true; diff --git a/tests/integration/abort/abort.js b/tests/integration/abort/abort.js new file mode 100644 index 00000000..11bb9489 --- /dev/null +++ b/tests/integration/abort/abort.js @@ -0,0 +1,61 @@ +import { serveTest } from "../test-server.js"; +import { strictEqual, throws } from "../../assert.js"; + +export const handler = serveTest(async (t) => { + await t.test("throwIfAborted-throws-when-aborted", () => { + const controller = new AbortController(); + const reason = new Error("boom"); + controller.abort(reason); + + strictEqual(controller.signal.aborted, true, "signal should be aborted"); + + let thrown; + try { + controller.signal.throwIfAborted(); + } catch (e) { + thrown = e; + } + + strictEqual(thrown, reason, "throwIfAborted must throw the abort reason"); + }); + + await t.test("throwIfAborted-is-noop-when-not-aborted", () => { + const controller = new AbortController(); + strictEqual(controller.signal.aborted, false, "signal should not be aborted"); + // Must not throw when the signal is not aborted. + let threw = false; + try { + controller.signal.throwIfAborted(); + } catch { + threw = true; + } + strictEqual(threw, false, "throwIfAborted must not throw when not aborted"); + }); + + await t.test("throwIfAborted-throws-default-AbortError", () => { + const controller = new AbortController(); + controller.abort(); // no reason -> default AbortError DOMException + + throws(() => controller.signal.throwIfAborted()); + + let name; + try { + controller.signal.throwIfAborted(); + } catch (e) { + name = e.name; + } + strictEqual(name, "AbortError", "default abort reason is an AbortError"); + }); + + await t.test("AbortSignal.abort-throwIfAborted", () => { + const reason = new Error("static-abort"); + const signal = AbortSignal.abort(reason); + let thrown; + try { + signal.throwIfAborted(); + } catch (e) { + thrown = e; + } + strictEqual(thrown, reason, "AbortSignal.abort(reason).throwIfAborted() throws reason"); + }); +}); diff --git a/tests/integration/handlers.js b/tests/integration/handlers.js index fc07035a..ceae4da7 100644 --- a/tests/integration/handlers.js +++ b/tests/integration/handlers.js @@ -1,3 +1,4 @@ +export { handler as abort } from './abort/abort.js'; export { handler as blob } from './blob/blob.js'; export { handler as btoa } from './btoa/btoa.js'; export { handler as performance } from './performance/performance.js'; diff --git a/tests/tests.cmake b/tests/tests.cmake index 7b7840f3..7011d95c 100644 --- a/tests/tests.cmake +++ b/tests/tests.cmake @@ -62,6 +62,7 @@ test_e2e(no-init-location) test_e2e(init-location) integration_tests( + abort blob btoa crypto