Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 62 additions & 1 deletion doc/dataview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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

Expand Down
41 changes: 39 additions & 2 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -2312,15 +2345,19 @@ inline DataView::DataView(napi_env env, napi_value value) : Object(env, value) {
}

inline Napi::ArrayBuffer DataView::ArrayBuffer() const {
return Buffer().As<Napi::ArrayBuffer>();
}

inline Napi::Value DataView::Buffer() const {
napi_value arrayBuffer;
napi_status status = napi_get_dataview_info(_env,
_value /* dataView */,
nullptr /* byteLength */,
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 {
Expand Down
12 changes: 12 additions & 0 deletions napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -1450,13 +1450,25 @@ 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.
DataView(napi_env env,
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.
Expand Down
53 changes: 47 additions & 6 deletions test/dataview/dataview.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,51 @@

using namespace Napi;

static Value CreateDataView1(const CallbackInfo& info) {
static Value CreateDataView(const CallbackInfo& info) {
ArrayBuffer arrayBuffer = info[0].As<ArrayBuffer>();
return DataView::New(info.Env(), arrayBuffer);
}

static Value CreateDataView2(const CallbackInfo& info) {
static Value CreateDataViewWithByteOffset(const CallbackInfo& info) {
ArrayBuffer arrayBuffer = info[0].As<ArrayBuffer>();
size_t byteOffset = info[1].As<Number>().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<ArrayBuffer>();
size_t byteOffset = info[1].As<Number>().Uint32Value();
size_t byteLength = info[2].As<Number>().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<SharedArrayBuffer>();
return DataView::New(info.Env(), arrayBuffer);
}

static Value CreateDataViewOnSharedArrayBufferWithByteOffset(
const CallbackInfo& info) {
SharedArrayBuffer arrayBuffer = info[0].As<SharedArrayBuffer>();
size_t byteOffset = info[1].As<Number>().Uint32Value();
return DataView::New(info.Env(), arrayBuffer, byteOffset);
}

static Value CreateDataViewOnSharedArrayBufferWithByteOffsetAndByteLength(
const CallbackInfo& info) {
SharedArrayBuffer arrayBuffer = info[0].As<SharedArrayBuffer>();
size_t byteOffset = info[1].As<Number>().Uint32Value();
size_t byteLength = info[2].As<Number>().Uint32Value();
return DataView::New(info.Env(), arrayBuffer, byteOffset, byteLength);
}
#endif

static Value GetBuffer(const CallbackInfo& info) {
return info[0].As<DataView>().Buffer();
}

static Value GetArrayBuffer(const CallbackInfo& info) {
return info[0].As<DataView>().ArrayBuffer();
}
Expand All @@ -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);

Expand Down
63 changes: 51 additions & 12 deletions test/dataview/dataview.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
}