From be62641976e1fe7b60d7c1610f36b55b0a0c2cb1 Mon Sep 17 00:00:00 2001 From: Ms2ger Date: Mon, 9 Mar 2026 16:46:08 +0100 Subject: [PATCH] lib: fix sequence argument handling in Blob constructor This uses the existing WebIDL infrastructure to handle the iteration over the argument correctly according to the specification. Note that we can't avoid looping over the input twice: we only know the value of the 'endings' option after converting the blob parts into an array. --- lib/internal/blob.js | 32 ++++++++++++++++++-------------- test/wpt/status/FileAPI/blob.cjs | 8 -------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/lib/internal/blob.js b/lib/internal/blob.js index e1b1dceabd629d..5059b651f467ca 100644 --- a/lib/internal/blob.js +++ b/lib/internal/blob.js @@ -1,7 +1,6 @@ 'use strict'; const { - ArrayFrom, MathMax, MathMin, ObjectDefineProperties, @@ -15,7 +14,6 @@ const { StringPrototypeSplit, StringPrototypeToLowerCase, Symbol, - SymbolIterator, SymbolToStringTag, Uint8Array, } = primordials; @@ -54,12 +52,15 @@ const { lazyDOMException, } = require('internal/util'); const { inspect } = require('internal/util/inspect'); -const { convertToInt } = require('internal/webidl'); +const { + converters, + convertToInt, + createSequenceConverter, +} = require('internal/webidl'); const { codes: { ERR_BUFFER_TOO_LARGE, - ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, ERR_INVALID_STATE, ERR_INVALID_THIS, @@ -112,7 +113,6 @@ function getSource(source, endings) { if (isAnyArrayBuffer(source)) { source = new Uint8Array(source); } else if (!isArrayBufferView(source)) { - source = `${source}`; if (endings === 'native') source = RegExpPrototypeSymbolReplace(/\n|\r\n/g, source, EOL); source = enc.encode(source); @@ -126,6 +126,13 @@ function getSource(source, endings) { return [byteLength, new Uint8Array(slice)]; } +const sourcesConverter = createSequenceConverter((source, opts = kEmptyObject) => { + if (isBlob(source) || isAnyArrayBuffer(source) || isArrayBufferView(source)) { + return source; + } + return converters.DOMString(source, opts); +}); + class Blob { /** * @typedef {string|ArrayBuffer|ArrayBufferView|Blob} SourcePart @@ -142,11 +149,8 @@ class Blob { constructor(sources = [], options) { markTransferMode(this, true, false); - if (sources === null || - typeof sources[SymbolIterator] !== 'function' || - typeof sources === 'string') { - throw new ERR_INVALID_ARG_TYPE('sources', 'a sequence', sources); - } + const sources_ = sourcesConverter(sources); + validateDictionary(options, 'options'); let { endings = 'transparent', @@ -158,11 +162,11 @@ class Blob { throw new ERR_INVALID_ARG_VALUE('options.endings', endings); let length = 0; - const sources_ = ArrayFrom(sources, (source) => { - const { 0: len, 1: src } = getSource(source, endings); + for (let i = 0; i < sources_.length; ++i) { + const { 0: len, 1: src } = getSource(sources_[i], endings); length += len; - return src; - }); + sources_[i] = src; + } if (length > kMaxLength) throw new ERR_BUFFER_TOO_LARGE(kMaxLength); diff --git a/test/wpt/status/FileAPI/blob.cjs b/test/wpt/status/FileAPI/blob.cjs index b59756abc70121..99ec7dd22cea5f 100644 --- a/test/wpt/status/FileAPI/blob.cjs +++ b/test/wpt/status/FileAPI/blob.cjs @@ -25,14 +25,6 @@ module.exports = { }, 'Blob-constructor.any.js': { fail: { - expected: [ - 'blobParts not an object: boolean with Boolean.prototype[Symbol.iterator]', - 'blobParts not an object: number with Number.prototype[Symbol.iterator]', - 'blobParts not an object: BigInt with BigInt.prototype[Symbol.iterator]', - 'blobParts not an object: Symbol with Symbol.prototype[Symbol.iterator]', - 'Getters and value conversions should happen in order until an exception is thrown.', - 'Arguments should be evaluated from left to right.', - ], flaky: [ 'Passing typed arrays as elements of the blobParts array should work.', 'Passing a Float16Array as element of the blobParts array should work.',