From 3a0fec2d15429e2e275be1af764155e2e812b1c3 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Mon, 1 Sep 2025 19:33:57 +0200 Subject: [PATCH 1/4] Add `allowResizable` option --- lib/index.js | 33 +++++++++ test/buffer-source.js | 166 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 191 insertions(+), 8 deletions(-) diff --git a/lib/index.js b/lib/index.js index f2b410e..77061a1 100644 --- a/lib/index.js +++ b/lib/index.js @@ -322,6 +322,25 @@ function isSharedArrayBuffer(value) { } } +const abResizableGetter = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "resizable").get; +const sabGrowableGetter = Object.getOwnPropertyDescriptor(SharedArrayBuffer.prototype, "growable").get; + +function isNonSharedArrayBufferResizable(value) { + try { + return abResizableGetter.call(value); + } catch { + return false; + } +} + +function isSharedArrayBufferGrowable(value) { + try { + return sabGrowableGetter.call(value); + } catch { + return false; + } +} + function isArrayBufferDetached(value) { try { // eslint-disable-next-line no-new @@ -336,6 +355,9 @@ exports.ArrayBuffer = (value, options = {}) => { if (!isNonSharedArrayBuffer(value)) { throw makeException(TypeError, "is not an ArrayBuffer", options); } + if (!options.allowResizable && isNonSharedArrayBufferResizable(value)) { + throw makeException(TypeError, "is a resizable ArrayBuffer", options); + } if (isArrayBufferDetached(value)) { throw makeException(TypeError, "is a detached ArrayBuffer", options); } @@ -347,6 +369,9 @@ exports.SharedArrayBuffer = (value, options = {}) => { if (!isSharedArrayBuffer(value)) { throw makeException(TypeError, "is not a SharedArrayBuffer", options); } + if (!options.allowResizable && isSharedArrayBufferGrowable(value)) { + throw makeException(TypeError, "is a growable SharedArrayBuffer", options); + } if (isArrayBufferDetached(value)) { throw makeException(TypeError, "is a detached SharedArrayBuffer", options); } @@ -405,6 +430,14 @@ exports.ArrayBufferView = (value, options = {}) => { throw makeException(TypeError, "is a view on a SharedArrayBuffer, which is not allowed", options); } + if (!options.allowResizable) { + if (isNonSharedArrayBufferResizable(value.buffer)) { + throw makeException(TypeError, "is a view on a resizable ArrayBuffer, which is not allowed", options); + } else if (isSharedArrayBufferGrowable(value.buffer)) { + throw makeException(TypeError, "is a view on a growable SharedArrayBuffer, which is not allowed", options); + } + } + if (isArrayBufferDetached(value.buffer)) { throw makeException(TypeError, "is a view on a detached ArrayBuffer", options); } diff --git a/test/buffer-source.js b/test/buffer-source.js index 30b8492..fc8a3d3 100644 --- a/test/buffer-source.js +++ b/test/buffer-source.js @@ -86,6 +86,7 @@ const bufferSourceCreators = [ { typeName: "ArrayBuffer", isShared: false, + isResizable: false, isDetached: false, label: "ArrayBuffer same realm", creator: () => new ArrayBuffer(0) @@ -93,6 +94,15 @@ const bufferSourceCreators = [ { typeName: "ArrayBuffer", isShared: false, + isResizable: true, + isDetached: false, + label: "resizable ArrayBuffer same realm", + creator: () => new ArrayBuffer(0, { maxByteLength: 1 }) + }, + { + typeName: "ArrayBuffer", + isShared: false, + isResizable: false, isDetached: true, label: "detached ArrayBuffer", creator: () => { @@ -105,6 +115,7 @@ const bufferSourceCreators = [ { typeName: "SharedArrayBuffer", isShared: true, + isResizable: false, isDetached: false, label: "SharedArrayBuffer same realm", creator: () => new SharedArrayBuffer(0) @@ -112,9 +123,26 @@ const bufferSourceCreators = [ { typeName: "SharedArrayBuffer", isShared: true, + isResizable: true, + isDetached: false, + label: "growable SharedArrayBuffer same realm", + creator: () => new SharedArrayBuffer(0, { maxByteLength: 1 }) + }, + { + typeName: "SharedArrayBuffer", + isShared: true, + isResizable: false, isDetached: false, label: "SharedArrayBuffer different realm", creator: () => vm.runInContext(`new SharedArrayBuffer(0)`, differentRealm) + }, + { + typeName: "SharedArrayBuffer", + isShared: true, + isResizable: true, + isDetached: false, + label: "growable SharedArrayBuffer different realm", + creator: () => vm.runInContext(`new SharedArrayBuffer(0, { maxByteLength: 1 })`, differentRealm) } ]; @@ -128,6 +156,7 @@ for (const constructor of bufferSourceConstructors) { { typeName: name, isShared: false, + isResizable: false, isDetached: false, isForged: false, label: `${name} same realm`, @@ -136,6 +165,7 @@ for (const constructor of bufferSourceConstructors) { { typeName: name, isShared: false, + isResizable: false, isDetached: false, isForged: false, label: `${name} different realm`, @@ -144,6 +174,28 @@ for (const constructor of bufferSourceConstructors) { { typeName: name, isShared: false, + isResizable: true, + isDetached: false, + isForged: false, + label: `resizable ${name} same realm`, + creator: () => new constructor(new ArrayBuffer(0, { maxByteLength: 1 })) + }, + { + typeName: name, + isShared: false, + isResizable: true, + isDetached: false, + isForged: false, + label: `resizable ${name} different realm`, + creator: () => vm.runInContext( + `new ${constructor.name}(new ArrayBuffer(0, { maxByteLength: 1 }))`, + differentRealm + ) + }, + { + typeName: name, + isShared: false, + isResizable: false, isDetached: false, isForged: true, label: `forged ${name}`, @@ -152,6 +204,7 @@ for (const constructor of bufferSourceConstructors) { { typeName: name, isShared: false, + isResizable: false, isDetached: true, isForged: false, label: `detached ${name}`, @@ -165,6 +218,7 @@ for (const constructor of bufferSourceConstructors) { { typeName: name, isShared: true, + isResizable: false, isDetached: false, isForged: false, label: `${name} SharedArrayBuffer same realm`, @@ -173,10 +227,32 @@ for (const constructor of bufferSourceConstructors) { { typeName: name, isShared: true, + isResizable: false, isDetached: false, isForged: false, label: `${name} SharedArrayBuffer different realm`, creator: () => vm.runInContext(`new ${constructor.name}(new SharedArrayBuffer(0))`, differentRealm) + }, + { + typeName: name, + isShared: true, + isResizable: true, + isDetached: false, + isForged: false, + label: `${name} growable SharedArrayBuffer same realm`, + creator: () => new constructor(new SharedArrayBuffer(0, { maxByteLength: 1 })) + }, + { + typeName: name, + isShared: true, + isResizable: true, + isDetached: false, + isForged: false, + label: `${name} growable SharedArrayBuffer different realm`, + creator: () => vm.runInContext( + `new ${constructor.name}(new SharedArrayBuffer(0, { maxByteLength: 1 }))`, + differentRealm + ) } ); } @@ -190,6 +266,7 @@ for (const type of bufferSourceConstructors) { const testFunction = innerType.typeName === typeName && !innerType.isShared && + !innerType.isResizable && !innerType.isDetached && !innerType.isForged ? testOk : @@ -203,13 +280,38 @@ for (const type of bufferSourceConstructors) { describe("with [AllowShared]", () => { const allowSharedSUT = (v, opts) => conversions[typeName](v, { ...opts, allowShared: true }); - for (const { label, creator, typeName: innerTypeName, isDetached, isForged } of bufferSourceCreators) { - const testFunction = innerTypeName === typeName && !isDetached && !isForged ? testOk : testNotOk; + for (const { + label, creator, typeName: innerTypeName, isResizable, isDetached, isForged + } of bufferSourceCreators) { + const testFunction = innerTypeName === typeName && + !isResizable && + !isDetached && + !isForged ? + testOk : + testNotOk; testFunction(label, allowSharedSUT, creator); } commonNotOk(allowSharedSUT); }); + + describe("with [AllowResizable]", () => { + const allowResizableSUT = (v, opts) => conversions[typeName](v, { ...opts, allowResizable: true }); + + for (const { + label, creator, typeName: innerTypeName, isShared, isDetached, isForged + } of bufferSourceCreators) { + const testFunction = innerTypeName === typeName && + !isShared && + !isDetached && + !isForged ? + testOk : + testNotOk; + testFunction(label, allowResizableSUT, creator); + } + + commonNotOk(allowResizableSUT); + }); }); } @@ -220,6 +322,7 @@ describe(`WebIDL SharedArrayBuffer type`, () => { const testFunction = innerType.typeName === "SharedArrayBuffer" && innerType.isShared && + !innerType.isResizable && !innerType.isDetached && !innerType.isForged ? testOk : @@ -229,16 +332,32 @@ describe(`WebIDL SharedArrayBuffer type`, () => { } commonNotOk(sut); + + describe("with [AllowResizable]", () => { + const allowResizableSUT = (v, opts) => sut(v, { ...opts, allowResizable: true }); + + for (const { label, creator, typeName: innerTypeName, isDetached, isForged } of bufferSourceCreators) { + const testFunction = innerTypeName === "SharedArrayBuffer" && + !isDetached && + !isForged ? + testOk : + testNotOk; + testFunction(label, allowResizableSUT, creator); + } + + commonNotOk(allowResizableSUT); + }); }); describe("WebIDL ArrayBufferView type", () => { const sut = conversions.ArrayBufferView; - for (const { label, typeName, isShared, isDetached, isForged, creator } of bufferSourceCreators) { + for (const { label, typeName, isShared, isResizable, isDetached, isForged, creator } of bufferSourceCreators) { const testFunction = typeName !== "ArrayBuffer" && typeName !== "SharedArrayBuffer" && !isShared && + !isResizable && !isDetached && !isForged ? testOk : @@ -252,10 +371,11 @@ describe("WebIDL ArrayBufferView type", () => { describe("with [AllowShared]", () => { const allowSharedSUT = (v, opts) => conversions.ArrayBufferView(v, { ...opts, allowShared: true }); - for (const { label, creator, typeName, isDetached, isForged } of bufferSourceCreators) { + for (const { label, creator, typeName, isResizable, isDetached, isForged } of bufferSourceCreators) { const testFunction = typeName !== "ArrayBuffer" && typeName !== "SharedArrayBuffer" && + !isResizable && !isDetached && !isForged ? testOk : @@ -266,13 +386,32 @@ describe("WebIDL ArrayBufferView type", () => { commonNotOk(allowSharedSUT); }); + + describe("with [AllowResizable]", () => { + const allowResizableSUT = (v, opts) => conversions.ArrayBufferView(v, { ...opts, allowResizable: true }); + + for (const { label, creator, typeName, isShared, isDetached, isForged } of bufferSourceCreators) { + const testFunction = + typeName !== "ArrayBuffer" && + typeName !== "SharedArrayBuffer" && + !isShared && + !isDetached && + !isForged ? + testOk : + testNotOk; + + testFunction(label, allowResizableSUT, creator); + } + + commonNotOk(allowResizableSUT); + }); }); describe("WebIDL BufferSource type", () => { const sut = conversions.BufferSource; - for (const { label, creator, isShared, isDetached, isForged } of bufferSourceCreators) { - const testFunction = !isShared && !isDetached && !isForged ? testOk : testNotOk; + for (const { label, creator, isShared, isResizable, isDetached, isForged } of bufferSourceCreators) { + const testFunction = !isShared && !isResizable && !isDetached && !isForged ? testOk : testNotOk; testFunction(label, sut, creator); } @@ -281,11 +420,22 @@ describe("WebIDL BufferSource type", () => { describe("with [AllowShared]", () => { const allowSharedSUT = (v, opts) => conversions.BufferSource(v, { ...opts, allowShared: true }); - for (const { label, creator, isDetached, isForged } of bufferSourceCreators) { - const testFunction = !isDetached && !isForged ? testOk : testNotOk; + for (const { label, creator, isResizable, isDetached, isForged } of bufferSourceCreators) { + const testFunction = !isResizable && !isDetached && !isForged ? testOk : testNotOk; testFunction(label, allowSharedSUT, creator); } commonNotOk(allowSharedSUT); }); + + describe("with [AllowResizable]", () => { + const allowResizableSUT = (v, opts) => conversions.BufferSource(v, { ...opts, allowResizable: true }); + + for (const { label, creator, isShared, isDetached, isForged } of bufferSourceCreators) { + const testFunction = !isShared && !isDetached && !isForged ? testOk : testNotOk; + testFunction(label, allowResizableSUT, creator); + } + + commonNotOk(allowResizableSUT); + }); }); From 405df60521db68add74d536b5085472affdb7bdd Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 4 Sep 2025 22:25:57 +0200 Subject: [PATCH 2/4] Test with both `allowShared` and `allowResizable` --- test/buffer-source.js | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/buffer-source.js b/test/buffer-source.js index fc8a3d3..55436e7 100644 --- a/test/buffer-source.js +++ b/test/buffer-source.js @@ -312,6 +312,23 @@ for (const type of bufferSourceConstructors) { commonNotOk(allowResizableSUT); }); + + describe("with [AllowShared, AllowResizable]", () => { + const allowSharedAndResizableSUT = (v, opts) => { + return conversions[typeName](v, { ...opts, allowShared: true, allowResizable: true }); + }; + + for (const { label, creator, typeName: innerTypeName, isDetached, isForged } of bufferSourceCreators) { + const testFunction = innerTypeName === typeName && + !isDetached && + !isForged ? + testOk : + testNotOk; + testFunction(label, allowSharedAndResizableSUT, creator); + } + + commonNotOk(allowSharedAndResizableSUT); + }); }); } @@ -405,6 +422,26 @@ describe("WebIDL ArrayBufferView type", () => { commonNotOk(allowResizableSUT); }); + + describe("with [AllowShared, AllowResizable]", () => { + const allowResizableSUT = (v, opts) => { + return conversions.ArrayBufferView(v, { ...opts, allowShared: true, allowResizable: true }); + }; + + for (const { label, creator, typeName, isDetached, isForged } of bufferSourceCreators) { + const testFunction = + typeName !== "ArrayBuffer" && + typeName !== "SharedArrayBuffer" && + !isDetached && + !isForged ? + testOk : + testNotOk; + + testFunction(label, allowResizableSUT, creator); + } + + commonNotOk(allowResizableSUT); + }); }); describe("WebIDL BufferSource type", () => { @@ -438,4 +475,17 @@ describe("WebIDL BufferSource type", () => { commonNotOk(allowResizableSUT); }); + + describe("with [AllowShared, AllowResizable]", () => { + const allowResizableSUT = (v, opts) => { + return conversions.BufferSource(v, { ...opts, allowShared: true, allowResizable: true }); + }; + + for (const { label, creator, isDetached, isForged } of bufferSourceCreators) { + const testFunction = !isDetached && !isForged ? testOk : testNotOk; + testFunction(label, allowResizableSUT, creator); + } + + commonNotOk(allowResizableSUT); + }); }); From db3ef940fec1b9c5a912365dd8153026699234e9 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Mon, 8 Sep 2025 22:14:37 +0200 Subject: [PATCH 3/4] Update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 16cc393..267f692 100644 --- a/README.md +++ b/README.md @@ -56,11 +56,11 @@ Conversions for all of the basic types from the Web IDL specification are implem - [`DOMString`](https://heycam.github.io/webidl/#es-DOMString), which can additionally be provided the boolean option `{ treatNullAsEmptyString }` as a second parameter - [`ByteString`](https://heycam.github.io/webidl/#es-ByteString), [`USVString`](https://heycam.github.io/webidl/#es-USVString) - [`object`](https://heycam.github.io/webidl/#es-object) -- [Buffer source types](https://heycam.github.io/webidl/#es-buffer-source-types), which can additionally be provided with the boolean option `{ allowShared }` as a second parameter +- [Buffer source types](https://heycam.github.io/webidl/#es-buffer-source-types), which can additionally be provided with the boolean option bag `{ allowShared, allowResizable }` as a second parameter Additionally, for convenience, the following derived type definitions are implemented: -- [`ArrayBufferView`](https://heycam.github.io/webidl/#ArrayBufferView), which can additionally be provided with the boolean option `{ allowShared }` as a second parameter +- [`ArrayBufferView`](https://heycam.github.io/webidl/#ArrayBufferView), which can additionally be provided with the boolean option bag `{ allowShared, allowResizable }` as a second parameter - [`BufferSource`](https://heycam.github.io/webidl/#BufferSource) - [`DOMTimeStamp`](https://heycam.github.io/webidl/#DOMTimeStamp) From ac006f1a3ed9ebadfb7c82e3489914474e50a607 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Mon, 8 Sep 2025 22:16:02 +0200 Subject: [PATCH 4/4] Remove detached check in SharedArrayBuffer conversion --- lib/index.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/index.js b/lib/index.js index 77061a1..55f56c8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -372,9 +372,6 @@ exports.SharedArrayBuffer = (value, options = {}) => { if (!options.allowResizable && isSharedArrayBufferGrowable(value)) { throw makeException(TypeError, "is a growable SharedArrayBuffer", options); } - if (isArrayBufferDetached(value)) { - throw makeException(TypeError, "is a detached SharedArrayBuffer", options); - } return value; };