Skip to content

Commit

Permalink
buffer: extract Blob's .arrayBuffer() & webidl changes
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
KhafraDev committed Sep 25, 2024
1 parent 18800da commit 9e435c2
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 32 deletions.
67 changes: 37 additions & 30 deletions lib/internal/blob.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down Expand Up @@ -439,6 +417,7 @@ ObjectDefineProperties(Blob.prototype, {
stream: kEnumerableProperty,
text: kEnumerableProperty,
arrayBuffer: kEnumerableProperty,
bytes: kEnumerableProperty,
});

function resolveObjectURL(url) {
Expand Down Expand Up @@ -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,
Expand Down
21 changes: 19 additions & 2 deletions test/parallel/test-blob.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ assert.throws(() => new Blob({}), {
'stream',
'text',
'arrayBuffer',
'bytes',
];

for (const prop of enumerable) {
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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());

0 comments on commit 9e435c2

Please sign in to comment.