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>
90 lines
3.4 KiB
JavaScript
90 lines
3.4 KiB
JavaScript
'use strict';
|
|
|
|
require('../common');
|
|
const assert = require('assert');
|
|
|
|
const prefix = "Failed to execute 'structuredClone'";
|
|
const key = 'transfer';
|
|
const context = 'Options';
|
|
const memberConverterError = `${prefix}: ${key} in ${context} cannot be converted to sequence.`;
|
|
const dictionaryConverterError = `${prefix}: ${context} cannot be converted to a dictionary`;
|
|
|
|
assert.throws(() => structuredClone(), { code: 'ERR_MISSING_ARGS' });
|
|
assert.throws(() => structuredClone(undefined, ''),
|
|
{ code: 'ERR_INVALID_ARG_TYPE', message: dictionaryConverterError });
|
|
assert.throws(() => structuredClone(undefined, 1),
|
|
{ code: 'ERR_INVALID_ARG_TYPE', message: dictionaryConverterError });
|
|
assert.throws(() => structuredClone(undefined, { transfer: 1 }),
|
|
{ code: 'ERR_INVALID_ARG_TYPE', message: memberConverterError });
|
|
assert.throws(() => structuredClone(undefined, { transfer: '' }),
|
|
{ code: 'ERR_INVALID_ARG_TYPE', message: memberConverterError });
|
|
assert.throws(() => structuredClone(undefined, { transfer: null }),
|
|
{ code: 'ERR_INVALID_ARG_TYPE', message: memberConverterError });
|
|
|
|
// Options can be null or undefined.
|
|
assert.strictEqual(structuredClone(undefined), undefined);
|
|
assert.strictEqual(structuredClone(undefined, null), undefined);
|
|
// Transfer can be null or undefined.
|
|
assert.strictEqual(structuredClone(undefined, { }), undefined);
|
|
|
|
// Transferables or its subclasses should be received with its closest transferable superclass
|
|
for (const StreamClass of [ReadableStream, WritableStream, TransformStream]) {
|
|
const original = new StreamClass();
|
|
const transfer = structuredClone(original, { transfer: [original] });
|
|
assert.strictEqual(Object.getPrototypeOf(transfer), StreamClass.prototype);
|
|
assert.ok(transfer instanceof StreamClass);
|
|
|
|
const extended = class extends StreamClass {};
|
|
const extendedOriginal = new extended();
|
|
const extendedTransfer = structuredClone(extendedOriginal, { transfer: [extendedOriginal] });
|
|
assert.strictEqual(Object.getPrototypeOf(extendedTransfer), StreamClass.prototype);
|
|
assert.ok(extendedTransfer instanceof StreamClass);
|
|
}
|
|
|
|
for (const Transferrable of [File, Blob]) {
|
|
const a2 = Transferrable === File ? '' : {};
|
|
const original = new Transferrable([], a2);
|
|
const transfer = structuredClone(original);
|
|
assert.strictEqual(Object.getPrototypeOf(transfer), Transferrable.prototype);
|
|
assert.ok(transfer instanceof Transferrable);
|
|
|
|
const extendedOriginal = new (class extends Transferrable {})([], a2);
|
|
const extendedTransfer = structuredClone(extendedOriginal);
|
|
assert.strictEqual(Object.getPrototypeOf(extendedTransfer), Transferrable.prototype);
|
|
assert.ok(extendedTransfer instanceof Transferrable);
|
|
}
|
|
|
|
// Transfer can be iterable
|
|
{
|
|
const value = {
|
|
a: new ReadableStream(),
|
|
b: new WritableStream(),
|
|
};
|
|
const cloned = structuredClone(value, {
|
|
transfer: {
|
|
*[Symbol.iterator]() {
|
|
for (const key in value) {
|
|
yield value[key];
|
|
}
|
|
}
|
|
}
|
|
});
|
|
for (const key in value) {
|
|
assert.ok(value[key].locked);
|
|
assert.ok(!cloned[key].locked);
|
|
}
|
|
}
|
|
|
|
{
|
|
// See: https://github.com/nodejs/node/issues/49940
|
|
const cloned = structuredClone({}, {
|
|
transfer: {
|
|
*[Symbol.iterator]() {}
|
|
}
|
|
});
|
|
|
|
assert.deepStrictEqual(cloned, {});
|
|
}
|
|
|
|
const blob = new Blob();
|
|
assert.throws(() => structuredClone(blob, { transfer: [blob] }), { name: 'DataCloneError' });
|