diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js index 88348a37bba61d..2fba1e3fdbe81b 100644 --- a/lib/internal/abort_controller.js +++ b/lib/internal/abort_controller.js @@ -39,6 +39,10 @@ const { ERR_INVALID_THIS, }, } = require('internal/errors'); +const { + converters, + createSequenceConverter, +} = require('internal/webidl'); const { validateAbortSignal, @@ -225,15 +229,19 @@ class AbortSignal extends EventTarget { * @returns {AbortSignal} */ static any(signals) { - validateAbortSignalArray(signals, 'signals'); + const signalsArray = createSequenceConverter( + converters.any, + )(signals); + + validateAbortSignalArray(signalsArray, 'signals'); const resultSignal = new AbortSignal(kDontThrowSymbol, { composite: true }); - if (!signals.length) { + if (!signalsArray.length) { return resultSignal; } const resultSignalWeakRef = new WeakRef(resultSignal); resultSignal[kSourceSignals] = new SafeSet(); - for (let i = 0; i < signals.length; i++) { - const signal = signals[i]; + for (let i = 0; i < signalsArray.length; i++) { + const signal = signalsArray[i]; if (signal.aborted) { abortSignal(resultSignal, signal.reason); return resultSignal; diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js index 326d520b844d23..ee9b7b69b73077 100644 --- a/lib/internal/crypto/webidl.js +++ b/lib/internal/crypto/webidl.js @@ -22,7 +22,6 @@ const { ObjectPrototypeIsPrototypeOf, SafeArrayIterator, String, - SymbolIterator, TypedArrayPrototypeGetBuffer, TypedArrayPrototypeGetSymbolToStringTag, globalThis: { @@ -33,6 +32,7 @@ const { const { makeException, createEnumConverter, + createSequenceConverter, } = require('internal/webidl'); const { @@ -293,39 +293,6 @@ function createDictionaryConverter(name, dictionaries) { }; } -function createSequenceConverter(converter) { - return function(V, opts = kEmptyObject) { - if (type(V) !== 'Object') { - throw makeException( - 'can not be converted to sequence.', - opts); - } - const iter = V?.[SymbolIterator]?.(); - if (iter === undefined) { - throw makeException( - 'can not be converted to sequence.', - opts); - } - const array = []; - while (true) { - const res = iter?.next?.(); - if (res === undefined) { - throw makeException( - 'can not be converted to sequence.', - opts); - } - if (res.done === true) break; - const val = converter(res.value, { - __proto__: null, - ...opts, - context: `${opts.context}, index ${array.length}`, - }); - ArrayPrototypePush(array, val); - } - return array; - }; -} - function createInterfaceConverter(name, prototype) { return (V, opts) => { if (!ObjectPrototypeIsPrototypeOf(prototype, V)) { diff --git a/lib/internal/webidl.js b/lib/internal/webidl.js index eeb0f35586a2ed..9e4b20435bf831 100644 --- a/lib/internal/webidl.js +++ b/lib/internal/webidl.js @@ -1,6 +1,7 @@ 'use strict'; const { + ArrayPrototypePush, MathAbs, MathMax, MathMin, @@ -13,6 +14,7 @@ const { ObjectAssign, SafeSet, String, + SymbolIterator, TypeError, } = primordials; @@ -25,6 +27,15 @@ const { kEmptyObject } = require('internal/util'); const converters = { __proto__: null }; +/** + * @see https://webidl.spec.whatwg.org/#es-any + * @param {any} V + * @returns {any} + */ +converters.any = (V) => { + return V; +}; + // https://webidl.spec.whatwg.org/#abstract-opdef-integerpart const integerPart = MathTrunc; @@ -209,10 +220,76 @@ function createEnumConverter(name, values) { }; } +// https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values +function type(V) { + if (V === null) + return 'Null'; + + switch (typeof V) { + case 'undefined': + return 'Undefined'; + case 'boolean': + return 'Boolean'; + case 'number': + return 'Number'; + case 'string': + return 'String'; + case 'symbol': + return 'Symbol'; + case 'bigint': + return 'BigInt'; + case 'object': // Fall through + case 'function': // Fall through + default: + // Per ES spec, typeof returns an implemention-defined value that is not + // any of the existing ones for uncallable non-standard exotic objects. + // Yet Type() which the Web IDL spec depends on returns Object for such + // cases. So treat the default case as an object. + return 'Object'; + } +} + +// https://webidl.spec.whatwg.org/#es-sequence +function createSequenceConverter(converter) { + return function(V, opts = kEmptyObject) { + if (type(V) !== 'Object') { + throw makeException( + 'can not be converted to sequence.', + opts); + } + const iter = V?.[SymbolIterator]?.(); + if (iter === undefined) { + throw makeException( + 'can not be converted to sequence.', + opts); + } + const array = []; + while (true) { + const res = iter?.next?.(); + if (res === undefined) { + throw makeException( + 'can not be converted to sequence.', + opts); + } + if (res.done === true) break; + const val = converter(res.value, { + __proto__: null, + ...opts, + context: `${opts.context}, index ${array.length}`, + }); + ArrayPrototypePush(array, val); + }; + return array; + }; +} + + module.exports = { + type, converters, convertToInt, createEnumConverter, + createSequenceConverter, evenRound, makeException, }; diff --git a/test/parallel/test-abortsignal-any.mjs b/test/parallel/test-abortsignal-any.mjs index 9e2af49ebf43cc..4378c44d987f50 100644 --- a/test/parallel/test-abortsignal-any.mjs +++ b/test/parallel/test-abortsignal-any.mjs @@ -101,4 +101,21 @@ describe('AbortSignal.any()', { concurrency: !process.env.TEST_PARALLEL }, () => controller.abort(); assert.strictEqual(result, '01234'); }); + + it('must accept WebIDL sequence', () => { + const controller = new AbortController(); + const iterable = { + *[Symbol.iterator]() { + yield controller.signal; + yield new AbortController().signal; + yield new AbortController().signal; + yield new AbortController().signal; + }, + }; + const signal = AbortSignal.any(iterable); + let result = 0; + signal.addEventListener('abort', () => result += 1); + controller.abort(); + assert.strictEqual(result, 1); + }); });