From ce65d653e50ad71a9398c713e23babeedee9bb3b Mon Sep 17 00:00:00 2001 From: Jason Zhang Date: Tue, 17 Sep 2024 20:41:02 +0930 Subject: [PATCH 1/2] lib: implement interface converter in webidl --- lib/internal/webidl.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/internal/webidl.js b/lib/internal/webidl.js index 9e4b20435bf831..4f0fe6d8543b1e 100644 --- a/lib/internal/webidl.js +++ b/lib/internal/webidl.js @@ -12,6 +12,7 @@ const { NumberMAX_SAFE_INTEGER, NumberMIN_SAFE_INTEGER, ObjectAssign, + ObjectPrototypeIsPrototypeOf, SafeSet, String, SymbolIterator, @@ -20,6 +21,7 @@ const { const { codes: { + ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, }, } = require('internal/errors'); @@ -275,7 +277,7 @@ function createSequenceConverter(converter) { const val = converter(res.value, { __proto__: null, ...opts, - context: `${opts.context}, index ${array.length}`, + context: `${opts.context}[${array.length}]`, }); ArrayPrototypePush(array, val); }; @@ -283,12 +285,26 @@ function createSequenceConverter(converter) { }; } +// https://webidl.spec.whatwg.org/#js-interface +function createInterfaceConverter(name, I) { + return (V, opts = kEmptyObject) => { + // 1. If V implements I, then return the IDL interface type value that + // represents a reference to that platform object. + if (ObjectPrototypeIsPrototypeOf(I, V)) return V; + // 2. Throw a TypeError. + throw new ERR_INVALID_ARG_TYPE( + typeof opts.context === 'string' ? opts.context : 'value', name, V, + ); + }; +} + module.exports = { type, converters, convertToInt, createEnumConverter, + createInterfaceConverter, createSequenceConverter, evenRound, makeException, From 82da5e9c3159998cf9503f3a9c61a15bfb5f6991 Mon Sep 17 00:00:00 2001 From: Jason Zhang Date: Tue, 17 Sep 2024 21:18:37 +0930 Subject: [PATCH 2/2] lib: validate signals with interface converter --- lib/internal/abort_controller.js | 19 +++++++++---------- test/parallel/test-abortsignal-any.mjs | 13 +++++++++++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js index ce05571c067398..ec7a10e6ff69c7 100644 --- a/lib/internal/abort_controller.js +++ b/lib/internal/abort_controller.js @@ -41,12 +41,11 @@ const { } = require('internal/errors'); const { converters, + createInterfaceConverter, createSequenceConverter, } = require('internal/webidl'); const { - validateAbortSignal, - validateAbortSignalArray, validateObject, validateUint32, kValidateObjectAllowObjects, @@ -229,11 +228,11 @@ class AbortSignal extends EventTarget { * @returns {AbortSignal} */ static any(signals) { - const signalsArray = createSequenceConverter( - converters.any, - )(signals); + const signalsArray = converters['sequence']( + signals, + { __proto__: null, context: 'signals' }, + ); - validateAbortSignalArray(signalsArray, 'signals'); const resultSignal = new AbortSignal(kDontThrowSymbol, { composite: true }); if (!signalsArray.length) { return resultSignal; @@ -353,6 +352,9 @@ class AbortSignal extends EventTarget { } } +converters.AbortSignal = createInterfaceConverter('AbortSignal', AbortSignal.prototype); +converters['sequence'] = createSequenceConverter(converters.AbortSignal); + function ClonedAbortSignal() { return new AbortSignal(kDontThrowSymbol, { transferable: true }); } @@ -442,10 +444,7 @@ function transferableAbortController() { * @returns {Promise} */ async function aborted(signal, resource) { - if (signal === undefined) { - throw new ERR_INVALID_ARG_TYPE('signal', 'AbortSignal', signal); - } - validateAbortSignal(signal, 'signal'); + converters.AbortSignal(signal, { __proto__: null, context: 'signal' }); validateObject(resource, 'resource', kValidateObjectAllowObjects); if (signal.aborted) return PromiseResolve(); diff --git a/test/parallel/test-abortsignal-any.mjs b/test/parallel/test-abortsignal-any.mjs index 4378c44d987f50..19b5569c4779d1 100644 --- a/test/parallel/test-abortsignal-any.mjs +++ b/test/parallel/test-abortsignal-any.mjs @@ -118,4 +118,17 @@ describe('AbortSignal.any()', { concurrency: !process.env.TEST_PARALLEL }, () => controller.abort(); assert.strictEqual(result, 1); }); + + it('throws TypeError if any value does not implement AbortSignal', () => { + const expectedError = { code: 'ERR_INVALID_ARG_TYPE' }; + assert.throws(() => AbortSignal.any([ null ]), expectedError); + assert.throws(() => AbortSignal.any([ undefined ]), expectedError); + assert.throws(() => AbortSignal.any([ '123' ]), expectedError); + assert.throws(() => AbortSignal.any([ 123 ]), expectedError); + assert.throws(() => AbortSignal.any([{}]), expectedError); + assert.throws(() => AbortSignal.any([{ aborted: true }]), expectedError); + assert.throws(() => AbortSignal.any([{ + aborted: true, reason: '', throwIfAborted: null, + }]), expectedError); + }); });