Rework lib/internal/webidl.js into a documented shared converter module that follows the Web IDL conversion algorithms more closely. Improvements: - Add documented converters and helper factories for primitive values, dictionaries, enums, sequences, interfaces, required arguments, integers, `Uint8Array`, and `BufferSource`. - Move WebCrypto onto the shared converters, while keeping compatibility wrappers for its existing `BufferSource` and `BigInteger` behavior. - Use shared converters from Blob, Performance, Web Locks, and structured clone option handling. - Add benchmarks for `ConvertToInt` and WebCrypto Web IDL converter hot paths. - Add focused tests for core converters, WebCrypto converters, integer conversion, and buffer source behavior. Fixes: - Make the shared `BufferSource` and `Uint8Array` converters reject resizable `ArrayBuffer` and growable `SharedArrayBuffer` backing stores unless explicitly allowed. WebCrypto preserves its legacy resizable backing-store behavior through compatibility wrappers until a semver-major follow-up can opt in to the stricter behavior. - Use Web IDL `ToNumber` and `ToString` behavior for BigInt, Symbol, and object primitive conversion. - Use exact BigInt modulo for 64-bit `ConvertToInt` wrapping and document the final Number approximation behavior. - Normalize mathematical modulo results to `+0` where Web IDL requires it. - Process inherited dictionaries in least-derived to most-derived order, sorting members only within each dictionary level. - Use `IteratorComplete` truthiness for sequence conversion. - Cover detached buffers, resizable-backed views, growable-backed views, cross-realm buffer sources, mutation-after-call behavior, inherited dictionary member order, and sequence iterator completion behavior. Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: https://github.com/nodejs/node/pull/62979 Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
184 lines
5 KiB
JavaScript
184 lines
5 KiB
JavaScript
'use strict';
|
|
// Flags: --expose-internals
|
|
const common = require('../common');
|
|
const assert = require('assert');
|
|
const test = require('node:test');
|
|
const { Blob } = require('buffer');
|
|
const { ReadableStream } = require('stream/web');
|
|
|
|
const sab = new SharedArrayBuffer(8);
|
|
const sabView = new Uint8Array(sab);
|
|
const sabDataView = new DataView(sab);
|
|
|
|
// -- ReadableStreamBYOBReader.read() --
|
|
|
|
test('ReadableStreamBYOBReader.read() rejects SAB-backed Uint8Array', async () => {
|
|
const rs = new ReadableStream({
|
|
type: 'bytes',
|
|
pull(controller) {
|
|
controller.enqueue(new Uint8Array([1, 2, 3]));
|
|
},
|
|
});
|
|
const reader = rs.getReader({ mode: 'byob' });
|
|
await assert.rejects(
|
|
reader.read(new Uint8Array(sab)),
|
|
{ code: 'ERR_INVALID_ARG_VALUE' },
|
|
);
|
|
reader.releaseLock();
|
|
});
|
|
|
|
test('ReadableStreamBYOBReader.read() rejects SAB-backed DataView', async () => {
|
|
const rs = new ReadableStream({
|
|
type: 'bytes',
|
|
pull(controller) {
|
|
controller.enqueue(new Uint8Array([1, 2, 3]));
|
|
},
|
|
});
|
|
const reader = rs.getReader({ mode: 'byob' });
|
|
await assert.rejects(
|
|
reader.read(sabDataView),
|
|
{ code: 'ERR_INVALID_ARG_VALUE' },
|
|
);
|
|
reader.releaseLock();
|
|
});
|
|
|
|
test('ReadableStreamBYOBReader.read() accepts regular view', async () => {
|
|
const rs = new ReadableStream({
|
|
type: 'bytes',
|
|
pull(controller) {
|
|
controller.enqueue(new Uint8Array([1, 2, 3]));
|
|
},
|
|
});
|
|
const reader = rs.getReader({ mode: 'byob' });
|
|
const { value, done } = await reader.read(new Uint8Array(3));
|
|
assert.strictEqual(done, false);
|
|
assert.deepStrictEqual(value, new Uint8Array([1, 2, 3]));
|
|
reader.releaseLock();
|
|
});
|
|
|
|
// -- ReadableByteStreamController.enqueue() --
|
|
|
|
test('ReadableByteStreamController.enqueue() rejects SAB-backed Uint8Array', async () => {
|
|
const sabForEnqueue = new SharedArrayBuffer(4);
|
|
const sabViewForEnqueue = new Uint8Array(sabForEnqueue);
|
|
sabViewForEnqueue[0] = 42;
|
|
|
|
const rs = new ReadableStream({
|
|
type: 'bytes',
|
|
pull: common.mustCall((controller) => {
|
|
assert.throws(
|
|
() => controller.enqueue(sabViewForEnqueue),
|
|
{ code: 'ERR_INVALID_ARG_VALUE' },
|
|
);
|
|
controller.enqueue(new Uint8Array([1]));
|
|
}),
|
|
});
|
|
const reader = rs.getReader();
|
|
const { value } = await reader.read();
|
|
assert.deepStrictEqual(value, new Uint8Array([1]));
|
|
reader.releaseLock();
|
|
});
|
|
|
|
test('ReadableByteStreamController.enqueue() rejects SAB-backed DataView', async () => {
|
|
const sabForDv = new SharedArrayBuffer(4);
|
|
const dvForEnqueue = new DataView(sabForDv);
|
|
|
|
const rs = new ReadableStream({
|
|
type: 'bytes',
|
|
pull: common.mustCall((controller) => {
|
|
assert.throws(
|
|
() => controller.enqueue(dvForEnqueue),
|
|
{ code: 'ERR_INVALID_ARG_VALUE' },
|
|
);
|
|
controller.enqueue(new Uint8Array([2]));
|
|
}),
|
|
});
|
|
const reader = rs.getReader();
|
|
const { value } = await reader.read();
|
|
assert.deepStrictEqual(value, new Uint8Array([2]));
|
|
reader.releaseLock();
|
|
});
|
|
|
|
// -- Blob --
|
|
|
|
test('Blob rejects SharedArrayBuffer part', () => {
|
|
assert.throws(
|
|
() => new Blob([sab]),
|
|
{ code: 'ERR_INVALID_ARG_TYPE' },
|
|
);
|
|
});
|
|
|
|
test('Blob rejects SAB-backed Uint8Array part', () => {
|
|
assert.throws(
|
|
() => new Blob([sabView]),
|
|
{ code: 'ERR_INVALID_ARG_TYPE' },
|
|
);
|
|
});
|
|
|
|
test('Blob rejects SAB-backed DataView part', () => {
|
|
assert.throws(
|
|
() => new Blob([sabDataView]),
|
|
{ code: 'ERR_INVALID_ARG_TYPE' },
|
|
);
|
|
});
|
|
|
|
// -- SharedWebIDL converters --
|
|
|
|
const { converters } = require('internal/webidl');
|
|
|
|
test('webidl converters.BufferSource rejects SharedArrayBuffer', () => {
|
|
assert.throws(
|
|
() => converters.BufferSource(sab),
|
|
{ code: 'ERR_INVALID_ARG_TYPE' },
|
|
);
|
|
});
|
|
|
|
test('webidl converters.BufferSource rejects SAB-backed Uint8Array', () => {
|
|
assert.throws(
|
|
() => converters.BufferSource(sabView),
|
|
{ code: 'ERR_INVALID_ARG_TYPE' },
|
|
);
|
|
});
|
|
|
|
test('webidl converters.BufferSource rejects SAB-backed DataView', () => {
|
|
assert.throws(
|
|
() => converters.BufferSource(sabDataView),
|
|
{ code: 'ERR_INVALID_ARG_TYPE' },
|
|
);
|
|
});
|
|
|
|
test('webidl converters.BufferSource accepts ArrayBuffer', () => {
|
|
const ab = new ArrayBuffer(4);
|
|
assert.strictEqual(converters.BufferSource(ab), ab);
|
|
});
|
|
|
|
test('webidl converters.BufferSource accepts regular TypedArray', () => {
|
|
const ta = new Uint8Array(4);
|
|
assert.strictEqual(converters.BufferSource(ta), ta);
|
|
});
|
|
|
|
test('webidl converters.Uint8Array rejects SAB-backed Uint8Array', () => {
|
|
assert.throws(
|
|
() => converters.Uint8Array(sabView),
|
|
{ code: 'ERR_INVALID_ARG_TYPE' },
|
|
);
|
|
});
|
|
|
|
test('webidl converters.Uint8Array rejects DataView', () => {
|
|
assert.throws(
|
|
() => converters.Uint8Array(sabDataView),
|
|
{ code: 'ERR_INVALID_ARG_TYPE' },
|
|
);
|
|
});
|
|
|
|
test('webidl converters.Uint8Array rejects non-view', () => {
|
|
assert.throws(
|
|
() => converters.Uint8Array('not a view'),
|
|
{ code: 'ERR_INVALID_ARG_TYPE' },
|
|
);
|
|
});
|
|
|
|
test('webidl converters.Uint8Array accepts regular Uint8Array', () => {
|
|
const ta = new Uint8Array(4);
|
|
assert.strictEqual(converters.Uint8Array(ta), ta);
|
|
});
|