From da5887d8e9910b8eb0cfd7ddbf762fbe6928d0df Mon Sep 17 00:00:00 2001 From: Khafra Date: Thu, 26 Sep 2024 01:53:58 -0400 Subject: [PATCH] buffer: extract Blob's .arrayBuffer() & webidl changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extracts Blob.prototype.arrayBuffer so it cannot be overridden in .text(), etc. - Make .bytes() enumerable. I guess the WPT runner is not running the idlharness tests? - Make .text() return a Promise, rather than being explicitly async. This is a non-documented part of the webidl spec. Refs: #49936 - Have .text(), .arrayBuffer(), and .bytes() reject for an invalid this instead of throwing. Fix the tests regarding this. PR-URL: https://github.com/nodejs/node/pull/53372 Refs: https://github.com/nodejs/node/pull/49936 Reviewed-By: Yagiz Nizipli Reviewed-By: Vinícius Lourenço Claro Cardoso --- lib/internal/blob.js | 67 +++++++++++++++++++++----------------- test/parallel/test-blob.js | 21 ++++++++++-- 2 files changed, 56 insertions(+), 32 deletions(-) diff --git a/lib/internal/blob.js b/lib/internal/blob.js index 4ff2b0e1e7051b..4d443c80278385 100644 --- a/lib/internal/blob.js +++ b/lib/internal/blob.js @@ -273,43 +273,21 @@ class Blob { if (!isBlob(this)) return PromiseReject(new ERR_INVALID_THIS('Blob')); - const { promise, resolve, reject } = createDeferredPromise(); - const reader = this[kHandle].getReader(); - const buffers = []; - const readNext = () => { - reader.pull((status, buffer) => { - if (status === 0) { - // EOS, concat & resolve - // buffer should be undefined here - resolve(concat(buffers)); - return; - } else if (status < 0) { - // The read could fail for many different reasons when reading - // from a non-memory resident blob part (e.g. file-backed blob). - // The error details the system error code. - const error = lazyDOMException('The blob could not be read', 'NotReadableError'); - reject(error); - return; - } - if (buffer !== undefined) - buffers.push(buffer); - queueMicrotask(() => readNext()); - }); - }; - readNext(); - return promise; + return arrayBuffer(this); } /** * @returns {Promise} */ - async text() { + text() { if (!isBlob(this)) - throw new ERR_INVALID_THIS('Blob'); + return PromiseReject(new ERR_INVALID_THIS('Blob')); dec ??= new TextDecoder(); - return dec.decode(await this.arrayBuffer()); + return PromisePrototypeThen( + arrayBuffer(this), + (buffer) => dec.decode(buffer)); } /** @@ -317,10 +295,10 @@ class Blob { */ bytes() { if (!isBlob(this)) - throw new ERR_INVALID_THIS('Blob'); + return PromiseReject(new ERR_INVALID_THIS('Blob')); return PromisePrototypeThen( - this.arrayBuffer(), + arrayBuffer(this), (buffer) => new Uint8Array(buffer)); } @@ -439,6 +417,7 @@ ObjectDefineProperties(Blob.prototype, { stream: kEnumerableProperty, text: kEnumerableProperty, arrayBuffer: kEnumerableProperty, + bytes: kEnumerableProperty, }); function resolveObjectURL(url) { @@ -490,6 +469,34 @@ function createBlobFromFilePath(path, options) { return res; } +function arrayBuffer(blob) { + const { promise, resolve, reject } = createDeferredPromise(); + const reader = blob[kHandle].getReader(); + const buffers = []; + const readNext = () => { + reader.pull((status, buffer) => { + if (status === 0) { + // EOS, concat & resolve + // buffer should be undefined here + resolve(concat(buffers)); + return; + } else if (status < 0) { + // The read could fail for many different reasons when reading + // from a non-memory resident blob part (e.g. file-backed blob). + // The error details the system error code. + const error = lazyDOMException('The blob could not be read', 'NotReadableError'); + reject(error); + return; + } + if (buffer !== undefined) + buffers.push(buffer); + queueMicrotask(() => readNext()); + }); + }; + readNext(); + return promise; +} + module.exports = { Blob, createBlob, diff --git a/test/parallel/test-blob.js b/test/parallel/test-blob.js index 330fd79c742d77..e7332606f64c28 100644 --- a/test/parallel/test-blob.js +++ b/test/parallel/test-blob.js @@ -197,6 +197,7 @@ assert.throws(() => new Blob({}), { 'stream', 'text', 'arrayBuffer', + 'bytes', ]; for (const prop of enumerable) { @@ -409,10 +410,13 @@ assert.throws(() => new Blob({}), { } (async () => { - await assert.rejects(async () => Blob.prototype.arrayBuffer.call(), { + await assert.rejects(() => Blob.prototype.arrayBuffer.call(), { code: 'ERR_INVALID_THIS', }); - await assert.rejects(async () => Blob.prototype.text.call(), { + await assert.rejects(() => Blob.prototype.text.call(), { + code: 'ERR_INVALID_THIS', + }); + await assert.rejects(() => Blob.prototype.bytes.call(), { code: 'ERR_INVALID_THIS', }); })().then(common.mustCall()); @@ -490,3 +494,16 @@ assert.throws(() => new Blob({}), { assert.ok(structuredClone(blob).size === blob.size); assert.ok((await structuredClone(blob).text()) === (await blob.text())); })().then(common.mustCall()); + +(async () => { + const blob = new Blob(['hello']); + const { arrayBuffer } = Blob.prototype; + + Blob.prototype.arrayBuffer = common.mustNotCall(); + + try { + assert.strictEqual(await blob.text(), 'hello'); + } finally { + Blob.prototype.arrayBuffer = arrayBuffer; + } +})().then(common.mustCall());