diff --git a/doc/dataview.md b/doc/dataview.md index 66fb28919..754d16c36 100644 --- a/doc/dataview.md +++ b/doc/dataview.md @@ -6,6 +6,11 @@ The `Napi::DataView` class corresponds to the [JavaScript `DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) class. +**NOTE**: The support for `Napi::DataView::New()` overloads accepting an +`Napi::SharedArrayBuffer` parameter is only available when using +`NAPI_EXPERIMENTAL` and building against Node.js headers that support this +feature. + ## Methods ### New @@ -50,6 +55,48 @@ static Napi::DataView Napi::DataView::New(napi_env env, Napi::ArrayBuffer arrayB Returns a new `Napi::DataView` instance. +### New + +Allocates a new `Napi::DataView` instance with a given `Napi::SharedArrayBuffer`. + +```cpp +static Napi::DataView Napi::DataView::New(napi_env env, Napi::SharedArrayBuffer sharedArrayBuffer); +``` + +- `[in] env`: The environment in which to create the `Napi::DataView` instance. +- `[in] sharedArrayBuffer` : `Napi::SharedArrayBuffer` underlying the `Napi::DataView`. + +Returns a new `Napi::DataView` instance. + +### New + +Allocates a new `Napi::DataView` instance with a given `Napi::SharedArrayBuffer`. + +```cpp +static Napi::DataView Napi::DataView::New(napi_env env, Napi::SharedArrayBuffer sharedArrayBuffer, size_t byteOffset); +``` + +- `[in] env`: The environment in which to create the `Napi::DataView` instance. +- `[in] sharedArrayBuffer` : `Napi::SharedArrayBuffer` underlying the `Napi::DataView`. +- `[in] byteOffset` : The byte offset within the `Napi::SharedArrayBuffer` from which to start projecting the `Napi::DataView`. + +Returns a new `Napi::DataView` instance. + +### New + +Allocates a new `Napi::DataView` instance with a given `Napi::SharedArrayBuffer`. + +```cpp +static Napi::DataView Napi::DataView::New(napi_env env, Napi::SharedArrayBuffer sharedArrayBuffer, size_t byteOffset, size_t byteLength); +``` + +- `[in] env`: The environment in which to create the `Napi::DataView` instance. +- `[in] sharedArrayBuffer` : `Napi::SharedArrayBuffer` underlying the `Napi::DataView`. +- `[in] byteOffset` : The byte offset within the `Napi::SharedArrayBuffer` from which to start projecting the `Napi::DataView`. +- `[in] byteLength` : Number of elements in the `Napi::DataView`. + +Returns a new `Napi::DataView` instance. + ### Constructor Initializes an empty instance of the `Napi::DataView` class. @@ -75,7 +122,21 @@ Napi::DataView(napi_env env, napi_value value); Napi::ArrayBuffer Napi::DataView::ArrayBuffer() const; ``` -Returns the backing array buffer. +Returns the backing array buffer as an `Napi::ArrayBuffer`. + +**NOTE**: If the `Napi::DataView` is not backed by an `Napi::ArrayBuffer`, this +method will terminate the process with a fatal error when using +`NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` or exhibit undefined behavior +otherwise. + +### Buffer + +```cpp +Napi::Value Napi::DataView::Buffer() const; +``` + +Returns the backing array buffer as a generic `Napi::Value`, allowing optional +type-checking with `Is*()` and type-casting with `As<>()` methods. ### ByteOffset diff --git a/napi-inl.h b/napi-inl.h index 0f1717ecd..4c8225a17 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2289,6 +2289,39 @@ inline DataView DataView::New(napi_env env, return DataView(env, value); } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +inline DataView DataView::New(napi_env env, + Napi::SharedArrayBuffer arrayBuffer) { + return New(env, arrayBuffer, 0, arrayBuffer.ByteLength()); +} + +inline DataView DataView::New(napi_env env, + Napi::SharedArrayBuffer arrayBuffer, + size_t byteOffset) { + if (byteOffset > arrayBuffer.ByteLength()) { + NAPI_THROW(RangeError::New( + env, "Start offset is outside the bounds of the buffer"), + DataView()); + } + return New( + env, arrayBuffer, byteOffset, arrayBuffer.ByteLength() - byteOffset); +} + +inline DataView DataView::New(napi_env env, + Napi::SharedArrayBuffer arrayBuffer, + size_t byteOffset, + size_t byteLength) { + if (byteOffset + byteLength > arrayBuffer.ByteLength()) { + NAPI_THROW(RangeError::New(env, "Invalid DataView length"), DataView()); + } + napi_value value; + napi_status status = + napi_create_dataview(env, byteLength, arrayBuffer, byteOffset, &value); + NAPI_THROW_IF_FAILED(env, status, DataView()); + return DataView(env, value); +} +#endif // NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + inline void DataView::CheckCast(napi_env env, napi_value value) { NAPI_CHECK(value != nullptr, "DataView::CheckCast", "empty value"); @@ -2312,6 +2345,10 @@ inline DataView::DataView(napi_env env, napi_value value) : Object(env, value) { } inline Napi::ArrayBuffer DataView::ArrayBuffer() const { + return Buffer().As(); +} + +inline Napi::Value DataView::Buffer() const { napi_value arrayBuffer; napi_status status = napi_get_dataview_info(_env, _value /* dataView */, @@ -2319,8 +2356,8 @@ inline Napi::ArrayBuffer DataView::ArrayBuffer() const { nullptr /* data */, &arrayBuffer /* arrayBuffer */, nullptr /* byteOffset */); - NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer()); - return Napi::ArrayBuffer(_env, arrayBuffer); + NAPI_THROW_IF_FAILED(_env, status, Napi::Value()); + return Napi::Value(_env, arrayBuffer); } inline size_t DataView::ByteOffset() const { diff --git a/napi.h b/napi.h index 013a9114d..66814f657 100644 --- a/napi.h +++ b/napi.h @@ -1450,6 +1450,17 @@ class DataView : public Object { size_t byteOffset, size_t byteLength); +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + static DataView New(napi_env env, Napi::SharedArrayBuffer arrayBuffer); + static DataView New(napi_env env, + Napi::SharedArrayBuffer arrayBuffer, + size_t byteOffset); + static DataView New(napi_env env, + Napi::SharedArrayBuffer arrayBuffer, + size_t byteOffset, + size_t byteLength); +#endif + static void CheckCast(napi_env env, napi_value value); DataView(); ///< Creates a new _empty_ DataView instance. @@ -1457,6 +1468,7 @@ class DataView : public Object { napi_value value); ///< Wraps a Node-API value primitive. Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. + Napi::Value Buffer() const; size_t ByteOffset() const; ///< Gets the offset into the buffer where the array starts. size_t ByteLength() const; ///< Gets the length of the array in bytes. diff --git a/test/dataview/dataview.cc b/test/dataview/dataview.cc index f055d95f1..cbd5933e7 100644 --- a/test/dataview/dataview.cc +++ b/test/dataview/dataview.cc @@ -2,24 +2,51 @@ using namespace Napi; -static Value CreateDataView1(const CallbackInfo& info) { +static Value CreateDataView(const CallbackInfo& info) { ArrayBuffer arrayBuffer = info[0].As(); return DataView::New(info.Env(), arrayBuffer); } -static Value CreateDataView2(const CallbackInfo& info) { +static Value CreateDataViewWithByteOffset(const CallbackInfo& info) { ArrayBuffer arrayBuffer = info[0].As(); size_t byteOffset = info[1].As().Uint32Value(); return DataView::New(info.Env(), arrayBuffer, byteOffset); } -static Value CreateDataView3(const CallbackInfo& info) { +static Value CreateDataViewWithByteOffsetAndByteLength( + const CallbackInfo& info) { ArrayBuffer arrayBuffer = info[0].As(); size_t byteOffset = info[1].As().Uint32Value(); size_t byteLength = info[2].As().Uint32Value(); return DataView::New(info.Env(), arrayBuffer, byteOffset, byteLength); } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +static Value CreateDataViewOnSharedArrayBuffer(const CallbackInfo& info) { + SharedArrayBuffer arrayBuffer = info[0].As(); + return DataView::New(info.Env(), arrayBuffer); +} + +static Value CreateDataViewOnSharedArrayBufferWithByteOffset( + const CallbackInfo& info) { + SharedArrayBuffer arrayBuffer = info[0].As(); + size_t byteOffset = info[1].As().Uint32Value(); + return DataView::New(info.Env(), arrayBuffer, byteOffset); +} + +static Value CreateDataViewOnSharedArrayBufferWithByteOffsetAndByteLength( + const CallbackInfo& info) { + SharedArrayBuffer arrayBuffer = info[0].As(); + size_t byteOffset = info[1].As().Uint32Value(); + size_t byteLength = info[2].As().Uint32Value(); + return DataView::New(info.Env(), arrayBuffer, byteOffset, byteLength); +} +#endif + +static Value GetBuffer(const CallbackInfo& info) { + return info[0].As().Buffer(); +} + static Value GetArrayBuffer(const CallbackInfo& info) { return info[0].As().ArrayBuffer(); } @@ -37,10 +64,24 @@ static Value GetByteLength(const CallbackInfo& info) { Object InitDataView(Env env) { Object exports = Object::New(env); - exports["createDataView1"] = Function::New(env, CreateDataView1); - exports["createDataView2"] = Function::New(env, CreateDataView2); - exports["createDataView3"] = Function::New(env, CreateDataView3); + exports["createDataView"] = Function::New(env, CreateDataView); + exports["createDataViewWithByteOffset"] = + Function::New(env, CreateDataViewWithByteOffset); + exports["createDataViewWithByteOffsetAndByteLength"] = + Function::New(env, CreateDataViewWithByteOffsetAndByteLength); + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + exports["createDataViewOnSharedArrayBuffer"] = + Function::New(env, CreateDataViewOnSharedArrayBuffer); + exports["createDataViewOnSharedArrayBufferWithByteOffset"] = + Function::New(env, CreateDataViewOnSharedArrayBufferWithByteOffset); + exports["createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength"] = + Function::New( + env, CreateDataViewOnSharedArrayBufferWithByteOffsetAndByteLength); +#endif + exports["getArrayBuffer"] = Function::New(env, GetArrayBuffer); + exports["getBuffer"] = Function::New(env, GetBuffer); exports["getByteOffset"] = Function::New(env, GetByteOffset); exports["getByteLength"] = Function::New(env, GetByteLength); diff --git a/test/dataview/dataview.js b/test/dataview/dataview.js index 595612856..5916f6b90 100644 --- a/test/dataview/dataview.js +++ b/test/dataview/dataview.js @@ -3,12 +3,19 @@ const assert = require('assert'); module.exports = require('../common').runTest(test); +let runSharedArrayBufferTests = true; + function test (binding) { function testDataViewCreation (factory, arrayBuffer, offset, length) { const view = factory(arrayBuffer, offset, length); offset = offset || 0; - assert.ok(dataview.getArrayBuffer(view) instanceof ArrayBuffer); - assert.strictEqual(dataview.getArrayBuffer(view), arrayBuffer); + if (arrayBuffer instanceof ArrayBuffer) { + assert.ok(dataview.getArrayBuffer(view) instanceof ArrayBuffer); + assert.strictEqual(dataview.getArrayBuffer(view), arrayBuffer); + } else { + assert.ok(dataview.getBuffer(view) instanceof SharedArrayBuffer); + assert.strictEqual(dataview.getBuffer(view), arrayBuffer); + } assert.strictEqual(dataview.getByteOffset(view), offset); assert.strictEqual(dataview.getByteLength(view), length || arrayBuffer.byteLength - offset); @@ -20,16 +27,48 @@ function test (binding) { }, RangeError); } - const dataview = binding.dataview; - const arrayBuffer = new ArrayBuffer(10); + const { hasSharedArrayBuffer, dataview } = binding; + + { + const arrayBuffer = new ArrayBuffer(10); + + testDataViewCreation(dataview.createDataView, arrayBuffer); + testDataViewCreation(dataview.createDataViewWithByteOffset, arrayBuffer, 2); + testDataViewCreation(dataview.createDataViewWithByteOffset, arrayBuffer, 10); + testDataViewCreation(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 2, 4); + testDataViewCreation(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 10, 0); + + testInvalidRange(dataview.createDataViewWithByteOffset, arrayBuffer, 11); + testInvalidRange(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 11, 0); + testInvalidRange(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 6, 5); + } + + if (hasSharedArrayBuffer && runSharedArrayBufferTests) { + const sab = new SharedArrayBuffer(10); - testDataViewCreation(dataview.createDataView1, arrayBuffer); - testDataViewCreation(dataview.createDataView2, arrayBuffer, 2); - testDataViewCreation(dataview.createDataView2, arrayBuffer, 10); - testDataViewCreation(dataview.createDataView3, arrayBuffer, 2, 4); - testDataViewCreation(dataview.createDataView3, arrayBuffer, 10, 0); + try { + testDataViewCreation(dataview.createDataViewOnSharedArrayBuffer, sab); + } catch (ex) { + // The `napi_create_dataview` API does not have a valid `#define` + // preprocessor guard for SharedArrayBuffer support, so it is + // possible that the API is present but creating a DataView on + // SharedArrayBuffer is not supported in the current version of Node.js. + // In that case, we should skip the test instead of throwing. + if (ex.message === 'Invalid argument') { + console.warn(`The current version of Node.js (${process.version}) does not support creating DataViews on SharedArrayBuffers; skipping tests.`); + runSharedArrayBufferTests = false; + return; + } - testInvalidRange(dataview.createDataView2, arrayBuffer, 11); - testInvalidRange(dataview.createDataView3, arrayBuffer, 11, 0); - testInvalidRange(dataview.createDataView3, arrayBuffer, 6, 5); + throw ex; + } + testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffset, sab, 2); + testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffset, sab, 10); + testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 2, 4); + testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 10, 0); + + testInvalidRange(dataview.createDataViewOnSharedArrayBufferWithByteOffset, sab, 11); + testInvalidRange(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 11, 0); + testInvalidRange(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 6, 5); + } }