feat: add support for SharedArrayBuffer in DataViews (#1714)
diff --git a/doc/dataview.md b/doc/dataview.md
index 66fb289..619ceec 100644
--- a/doc/dataview.md
+++ b/doc/dataview.md
@@ -6,6 +6,11 @@
 [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 @@
 
 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,22 @@
 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. Use `Buffer()` instead to get the backing buffer without assuming its
+type.
+
+### 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 3b82ec8..7ef2e64 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -2302,6 +2302,39 @@
   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");
 
@@ -2325,6 +2358,10 @@
 }
 
 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 */,
@@ -2332,8 +2369,8 @@
                                               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 d92702e..fd1e592 100644
--- a/napi.h
+++ b/napi.h
@@ -1456,13 +1456,37 @@
                       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.
+  // Gets the backing `ArrayBuffer`.
+  //
+  // If this `DataView` is not backed by an `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. Use `Buffer()` instead to get the backing buffer without
+  // assuming its type.
+  Napi::ArrayBuffer ArrayBuffer() const;
+
+  // Gets the backing buffer (an `ArrayBuffer` or `SharedArrayBuffer`).
+  //
+  // Use `IsArrayBuffer()` or `IsSharedArrayBuffer()` to check the type of the
+  // backing buffer prior to casting with `As<T>()`.
+  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 f055d95..cbd5933 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<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();
 }
@@ -37,10 +64,24 @@
 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 5956128..5916f6b 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 @@
     }, RangeError);
   }
 
-  const dataview = binding.dataview;
-  const arrayBuffer = new ArrayBuffer(10);
+  const { hasSharedArrayBuffer, dataview } = binding;
 
-  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);
+  {
+    const arrayBuffer = new ArrayBuffer(10);
 
-  testInvalidRange(dataview.createDataView2, arrayBuffer, 11);
-  testInvalidRange(dataview.createDataView3, arrayBuffer, 11, 0);
-  testInvalidRange(dataview.createDataView3, arrayBuffer, 6, 5);
+    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);
+
+    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;
+      }
+
+      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);
+  }
 }