Repository: nodejs/node
Pull request: buffer: extract Blob's .arrayBuffer() & webidl changes

- 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

<!--
Before submitting a pull request, please read:

- the CONTRIBUTING guide at https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md
- the commit message formatting guidelines at
  https://github.com/nodejs/node/blob/HEAD/doc/contributing/pull-requests.md#commit-message-guidelines

For code changes:
1. Include tests for any bug fixes or new features.
2. Update documentation if relevant.
3. Ensure that `make -j4 test` (UNIX), or `vcbuild test` (Windows) passes.

If you believe this PR should be highlighted in the Node.js CHANGELOG
please add the `notable-change` label.

Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
-->


Changed files:
filename: lib/internal/blob.js
patch: @@ -273,54 +273,32 @@ 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<string>}
    */
-  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));
   }
 
   /**
    * @returns {Promise<Uint8Array>}
    */
   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,

filename: test/parallel/test-blob.js
patch: @@ -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());