[dart:js_interop] Add typed array/data buffer constructors

- Adds constructors for JSArrayBuffer, JSDataView, and
  concrete typed array types e.g. JSInt8Array.

While JSDataView and the typed array types can also take in
a SharedArrayBuffer, we currently don't have a type that
represents that type. Therefore we keep it as JSArrayBuffer
and not JSObject as that would be too general.

Change-Id: Iea1e08a9a8225accc7fc3550f0f8fe88039a1eb5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/343940
Reviewed-by: Leaf Petersen <leafp@google.com>
Commit-Queue: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 71b7277..229537a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,13 @@
 
 [#56065]: https://github.com/dart-lang/sdk/issues/56065
 
+### Libraries
+
+#### `dart:js_interop`
+
+- Added constructors for `JSArrayBuffer`, `JSDataView`, and concrete typed array
+  types e.g. `JSInt8Array`.
+
 ## 3.5.0
 
 ### Language
diff --git a/sdk/lib/js_interop/js_interop.dart b/sdk/lib/js_interop/js_interop.dart
index 5c3d1c3..23595f8 100644
--- a/sdk/lib/js_interop/js_interop.dart
+++ b/sdk/lib/js_interop/js_interop.dart
@@ -192,12 +192,19 @@
 /// A JavaScript `ArrayBuffer`.
 @JS('ArrayBuffer')
 extension type JSArrayBuffer._(JSArrayBufferRepType _jsArrayBuffer)
-    implements JSObject {}
+    implements JSObject {
+  /// Creates a JavaScript `ArrayBuffer` of size [length] using an optional
+  /// [options] JavaScript object that sets the `maxByteLength`.
+  external JSArrayBuffer(int length, [JSObject options]);
+}
 
 /// A JavaScript `DataView`.
 @JS('DataView')
-extension type JSDataView._(JSDataViewRepType _jsDataView)
-    implements JSObject {}
+extension type JSDataView._(JSDataViewRepType _jsDataView) implements JSObject {
+  /// Creates a JavaScript `DataView` with [buffer] as its backing storage,
+  /// offset by [byteOffset] bytes, of size [byteLength].
+  external JSDataView(JSArrayBuffer buffer, [int byteOffset, int byteLength]);
+}
 
 /// Abstract supertype of all JavaScript typed arrays.
 extension type JSTypedArray._(JSTypedArrayRepType _jsTypedArray)
@@ -206,47 +213,138 @@
 /// A JavaScript `Int8Array`.
 @JS('Int8Array')
 extension type JSInt8Array._(JSInt8ArrayRepType _jsInt8Array)
-    implements JSTypedArray {}
+    implements JSTypedArray {
+  /// Creates a JavaScript `Int8Array` with [buffer] as its backing storage,
+  /// offset by [byteOffset] bytes, of size [length].
+  ///
+  /// If no [buffer] is provided, creates an empty `Int8Array`.
+  external JSInt8Array([JSArrayBuffer buffer, int byteOffset, int length]);
+
+  /// Creates a JavaScript `Int8Array` of size [length] whose elements are
+  /// initialized to 0.
+  external JSInt8Array.withLength(int length);
+}
 
 /// A JavaScript `Uint8Array`.
 @JS('Uint8Array')
 extension type JSUint8Array._(JSUint8ArrayRepType _jsUint8Array)
-    implements JSTypedArray {}
+    implements JSTypedArray {
+  /// Creates a JavaScript `Uint8Array` with [buffer] as its backing storage,
+  /// offset by [byteOffset] bytes, of size [length].
+  ///
+  /// If no [buffer] is provided, creates an empty `Uint8Array`.
+  external JSUint8Array([JSArrayBuffer buffer, int byteOffset, int length]);
+
+  /// Creates a JavaScript `Uint8Array` of size [length] whose elements are
+  /// initialized to 0.
+  external JSUint8Array.withLength(int length);
+}
 
 /// A JavaScript `Uint8ClampedArray`.
 @JS('Uint8ClampedArray')
 extension type JSUint8ClampedArray._(
-    JSUint8ClampedArrayRepType _jsUint8ClampedArray) implements JSTypedArray {}
+    JSUint8ClampedArrayRepType _jsUint8ClampedArray) implements JSTypedArray {
+  /// Creates a JavaScript `Uint8ClampedArray` with [buffer] as its backing
+  /// storage, offset by [byteOffset] bytes, of size [length].
+  ///
+  /// If no [buffer] is provided, creates an empty `Uint8ClampedArray`.
+  external JSUint8ClampedArray(
+      [JSArrayBuffer buffer, int byteOffset, int length]);
+
+  /// Creates a JavaScript `Uint8ClampedArray` of size [length] whose elements
+  /// are initialized to 0.
+  external JSUint8ClampedArray.withLength(int length);
+}
 
 /// A JavaScript `Int16Array`.
 @JS('Int16Array')
 extension type JSInt16Array._(JSInt16ArrayRepType _jsInt16Array)
-    implements JSTypedArray {}
+    implements JSTypedArray {
+  /// Creates a JavaScript `Int16Array` with [buffer] as its backing storage,
+  /// offset by [byteOffset] bytes, of size [length].
+  ///
+  /// If no [buffer] is provided, creates an empty `Int16Array`.
+  external JSInt16Array([JSArrayBuffer buffer, int byteOffset, int length]);
+
+  /// Creates a JavaScript `Int16Array` of size [length] whose elements are
+  /// initialized to 0.
+  external JSInt16Array.withLength(int length);
+}
 
 /// A JavaScript `Uint16Array`.
 @JS('Uint16Array')
 extension type JSUint16Array._(JSUint16ArrayRepType _jsUint16Array)
-    implements JSTypedArray {}
+    implements JSTypedArray {
+  /// Creates a JavaScript `Uint16Array` with [buffer] as its backing storage,
+  /// offset by [byteOffset] bytes, of size [length].
+  ///
+  /// If no [buffer] is provided, creates an empty `Uint16Array`.
+  external JSUint16Array([JSArrayBuffer buffer, int byteOffset, int length]);
+
+  /// Creates a JavaScript `Uint16Array` of size [length] whose elements are
+  /// initialized to 0.
+  external JSUint16Array.withLength(int length);
+}
 
 /// A JavaScript `Int32Array`.
 @JS('Int32Array')
 extension type JSInt32Array._(JSInt32ArrayRepType _jsInt32Array)
-    implements JSTypedArray {}
+    implements JSTypedArray {
+  /// Creates a JavaScript `Int32Array` with [buffer] as its backing storage,
+  /// offset by [byteOffset] bytes, of size [length].
+  ///
+  /// If no [buffer] is provided, creates an empty `Int32Array`.
+  external JSInt32Array([JSArrayBuffer buffer, int byteOffset, int length]);
+
+  /// Creates a JavaScript `Int32Array` of size [length] whose elements are
+  /// initialized to 0.
+  external JSInt32Array.withLength(int length);
+}
 
 /// A JavaScript `Uint32Array`.
 @JS('Uint32Array')
 extension type JSUint32Array._(JSUint32ArrayRepType _jsUint32Array)
-    implements JSTypedArray {}
+    implements JSTypedArray {
+  /// Creates a JavaScript `Uint32Array` with [buffer] as its backing storage,
+  /// offset by [byteOffset] bytes, of size [length].
+  ///
+  /// If no [buffer] is provided, creates an empty `Uint32Array`.
+  external JSUint32Array([JSArrayBuffer buffer, int byteOffset, int length]);
+
+  /// Creates a JavaScript `Uint32Array` of size [length] whose elements are
+  /// initialized to 0.
+  external JSUint32Array.withLength(int length);
+}
 
 /// A JavaScript `Float32Array`.
 @JS('Float32Array')
 extension type JSFloat32Array._(JSFloat32ArrayRepType _jsFloat32Array)
-    implements JSTypedArray {}
+    implements JSTypedArray {
+  /// Creates a JavaScript `Float32Array` with [buffer] as its backing storage,
+  /// offset by [byteOffset] bytes, of size [length].
+  ///
+  /// If no [buffer] is provided, creates an empty `Float32Array`.
+  external JSFloat32Array([JSArrayBuffer buffer, int byteOffset, int length]);
+
+  /// Creates a JavaScript `Float32Array` of size [length] whose elements are
+  /// initialized to 0.
+  external JSFloat32Array.withLength(int length);
+}
 
 /// A JavaScript `Float64Array`.
 @JS('Float64Array')
 extension type JSFloat64Array._(JSFloat64ArrayRepType _jsFloat64Array)
-    implements JSTypedArray {}
+    implements JSTypedArray {
+  /// Creates a JavaScript `Float64Array` with [buffer] as its backing storage,
+  /// offset by [byteOffset] bytes, of size [length].
+  ///
+  /// If no [buffer] is provided, creates an empty `Float64Array`.
+  external JSFloat64Array([JSArrayBuffer buffer, int byteOffset, int length]);
+
+  /// Creates a JavaScript `Float64Array` of size [length] whose elements are
+  /// initialized to 0.
+  external JSFloat64Array.withLength(int length);
+}
 
 // The various JavaScript primitive types. Crucially, unlike the Dart type
 // hierarchy, none of these types are subtypes of [JSObject]. They are just
diff --git a/tests/lib/js/static_interop_test/js_types_test.dart b/tests/lib/js/static_interop_test/js_types_test.dart
index 6d8a3b8..2c20a29 100644
--- a/tests/lib/js/static_interop_test/js_types_test.dart
+++ b/tests/lib/js/static_interop_test/js_types_test.dart
@@ -10,6 +10,7 @@
 
 import 'package:async_helper/async_helper.dart';
 import 'package:expect/expect.dart';
+// TODO(srujzs): Delete this import and replace all uses with expect.dart.
 import 'package:expect/minitest.dart'; // ignore: deprecated_member_use_from_same_package
 
 const isJSBackend = const bool.fromEnvironment('dart.library.html');
@@ -27,7 +28,7 @@
 @staticInterop
 class SimpleObject {}
 
-extension SimpleObjectExtension on SimpleObject {
+extension on SimpleObject {
   external JSString get foo;
 }
 
@@ -52,12 +53,30 @@
 @JS()
 external JSArrayBuffer buf;
 
+extension on JSArrayBuffer {
+  external int get byteLength;
+  external int get maxByteLength;
+}
+
 @JS()
 external JSDataView dat;
 
+extension on JSDataView {
+  external JSArrayBuffer get buffer;
+  external int get byteLength;
+  external int get byteOffset;
+}
+
 @JS()
 external JSTypedArray tar;
 
+extension on JSTypedArray {
+  external JSArrayBuffer get buffer;
+  external int get byteLength;
+  external int get byteOffset;
+  external int get length;
+}
+
 @JS()
 external JSInt8Array ai8;
 
@@ -234,22 +253,67 @@
   expect(confuse(buf) is JSArrayBuffer, true);
   ByteBuffer dartBuf = buf.toDart;
   expect(dartBuf.asUint8List(), equals([0, 255, 0, 255]));
+  buf = JSArrayBuffer(5);
+  expect(buf.byteLength, 5);
+  buf = JSArrayBuffer(5, {'maxByteLength': 12}.jsify() as JSObject);
+  expect(buf.maxByteLength, 12);
 
   // [DataView] <-> [ByteData]
-  dat = Uint8List.fromList([0, 255, 0, 255]).buffer.asByteData().toJS;
+  final datBuf = Uint8List.fromList([0, 255, 0, 255]).buffer.toJS;
+  dat = datBuf.toDart.asByteData().toJS;
   expect(dat is JSDataView, true);
   expect(confuse(dat) is JSDataView, true);
   ByteData dartDat = dat.toDart;
   expect(dartDat.getUint8(0), 0);
   expect(dartDat.getUint8(1), 255);
+  dat = JSDataView(datBuf);
+  expect(dat.buffer, datBuf);
+  final dat2 = JSDataView(datBuf, 1, 3);
+  expect(dat2.byteOffset, 1);
+  expect(dat2.byteLength, 3);
 
   // [TypedArray]s <-> [TypedData]s
+  // Test common TypedArray constructors for different subtypes.
+  void testTypedArrayConstructors(
+      JSTypedArray Function(JSArrayBuffer) createFromBuffer,
+      JSTypedArray Function(JSArrayBuffer, int, int)
+          createFromBufferOffsetAndLength,
+      JSTypedArray Function(int) createFromLength,
+      int byteSize) {
+    var byteLength = 16;
+    final buf = JSArrayBuffer(byteLength);
+    var typedArray = createFromBuffer(buf);
+    expect(typedArray.buffer, buf);
+    expect(typedArray.byteLength, byteLength);
+    expect(typedArray.byteOffset, 0);
+    expect(typedArray.length, byteLength ~/ byteSize);
+
+    byteLength = 8;
+    typedArray =
+        createFromBufferOffsetAndLength(buf, 8, byteLength ~/ byteSize);
+    expect(typedArray.buffer, buf);
+    expect(typedArray.byteLength, byteLength);
+    expect(typedArray.byteOffset, 8);
+    expect(typedArray.length, byteLength ~/ byteSize);
+
+    typedArray = createFromLength(byteLength ~/ byteSize);
+    expect(typedArray.byteLength, byteLength);
+    expect(typedArray.byteOffset, 0);
+    expect(typedArray.length, byteLength ~/ byteSize);
+  }
+
   // Int8
   ai8 = Int8List.fromList([-128, 0, 127]).toJS;
   expect(ai8 is JSInt8Array, true);
   expect(confuse(ai8) is JSInt8Array, true);
   Int8List dartAi8 = ai8.toDart;
   expect(dartAi8, equals([-128, 0, 127]));
+  testTypedArrayConstructors(
+      (JSArrayBuffer obj) => JSInt8Array(obj),
+      (JSArrayBuffer obj, int byteOffset, int length) =>
+          JSInt8Array(obj, byteOffset, length),
+      (int length) => JSInt8Array.withLength(length),
+      1);
 
   // Uint8
   au8 = Uint8List.fromList([-1, 0, 255, 256]).toJS;
@@ -257,6 +321,12 @@
   expect(confuse(au8) is JSUint8Array, true);
   Uint8List dartAu8 = au8.toDart;
   expect(dartAu8, equals([255, 0, 255, 0]));
+  testTypedArrayConstructors(
+      (JSArrayBuffer obj) => JSUint8Array(obj),
+      (JSArrayBuffer obj, int byteOffset, int length) =>
+          JSUint8Array(obj, byteOffset, length),
+      (int length) => JSUint8Array.withLength(length),
+      1);
 
   // Uint8Clamped
   ac8 = Uint8ClampedList.fromList([-1, 0, 255, 256]).toJS;
@@ -264,6 +334,12 @@
   expect(confuse(ac8) is JSUint8ClampedArray, true);
   Uint8ClampedList dartAc8 = ac8.toDart;
   expect(dartAc8, equals([0, 0, 255, 255]));
+  testTypedArrayConstructors(
+      (JSArrayBuffer obj) => JSUint8ClampedArray(obj),
+      (JSArrayBuffer obj, int byteOffset, int length) =>
+          JSUint8ClampedArray(obj, byteOffset, length),
+      (int length) => JSUint8ClampedArray.withLength(length),
+      1);
 
   // Int16
   ai16 = Int16List.fromList([-32769, -32768, 0, 32767, 32768]).toJS;
@@ -271,6 +347,12 @@
   expect(confuse(ai16) is JSInt16Array, true);
   Int16List dartAi16 = ai16.toDart;
   expect(dartAi16, equals([32767, -32768, 0, 32767, -32768]));
+  testTypedArrayConstructors(
+      (JSArrayBuffer obj) => JSInt16Array(obj),
+      (JSArrayBuffer obj, int byteOffset, int length) =>
+          JSInt16Array(obj, byteOffset, length),
+      (int length) => JSInt16Array.withLength(length),
+      2);
 
   // Uint16
   au16 = Uint16List.fromList([-1, 0, 65535, 65536]).toJS;
@@ -278,6 +360,12 @@
   expect(confuse(au16) is JSUint16Array, true);
   Uint16List dartAu16 = au16.toDart;
   expect(dartAu16, equals([65535, 0, 65535, 0]));
+  testTypedArrayConstructors(
+      (JSArrayBuffer obj) => JSUint16Array(obj),
+      (JSArrayBuffer obj, int byteOffset, int length) =>
+          JSUint16Array(obj, byteOffset, length),
+      (int length) => JSUint16Array.withLength(length),
+      2);
 
   // Int32
   ai32 = Int32List.fromList([-2147483648, 0, 2147483647]).toJS;
@@ -285,6 +373,12 @@
   expect(confuse(ai32) is JSInt32Array, true);
   Int32List dartAi32 = ai32.toDart;
   expect(dartAi32, equals([-2147483648, 0, 2147483647]));
+  testTypedArrayConstructors(
+      (JSArrayBuffer obj) => JSInt32Array(obj),
+      (JSArrayBuffer obj, int byteOffset, int length) =>
+          JSInt32Array(obj, byteOffset, length),
+      (int length) => JSInt32Array.withLength(length),
+      4);
 
   // Uint32
   au32 = Uint32List.fromList([-1, 0, 4294967295, 4294967296]).toJS;
@@ -292,6 +386,12 @@
   expect(confuse(au32) is JSUint32Array, true);
   Uint32List dartAu32 = au32.toDart;
   expect(dartAu32, equals([4294967295, 0, 4294967295, 0]));
+  testTypedArrayConstructors(
+      (JSArrayBuffer obj) => JSUint32Array(obj),
+      (JSArrayBuffer obj, int byteOffset, int length) =>
+          JSUint32Array(obj, byteOffset, length),
+      (int length) => JSUint32Array.withLength(length),
+      4);
 
   // Float32
   af32 = Float32List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]).toJS;
@@ -300,6 +400,12 @@
   Float32List dartAf32 = af32.toDart;
   expect(dartAf32,
       equals(Float32List.fromList([-1000.488, -0.00001, 0.0001, 10004.888])));
+  testTypedArrayConstructors(
+      (JSArrayBuffer obj) => JSFloat32Array(obj),
+      (JSArrayBuffer obj, int byteOffset, int length) =>
+          JSFloat32Array(obj, byteOffset, length),
+      (int length) => JSFloat32Array.withLength(length),
+      4);
 
   // Float64
   af64 = Float64List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]).toJS;
@@ -307,6 +413,12 @@
   expect(confuse(af64) is JSFloat64Array, true);
   Float64List dartAf64 = af64.toDart;
   expect(dartAf64, equals([-1000.488, -0.00001, 0.0001, 10004.888]));
+  testTypedArrayConstructors(
+      (JSArrayBuffer obj) => JSFloat64Array(obj),
+      (JSArrayBuffer obj, int byteOffset, int length) =>
+          JSFloat64Array(obj, byteOffset, length),
+      (int length) => JSFloat64Array.withLength(length),
+      8);
 
   // [JSNumber] <-> [double]
   nbr = 4.5.toJS;