[rab/gsab] Add WPT tests for resizable ArrayBuffer

Bug: v8:11111
Change-Id: I531f10cae15dd09e7934ad698acf9dcae225ca4c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4082152
Reviewed-by: Domenic Denicola <domenic@chromium.org>
Commit-Queue: Shu-yu Guo <syg@chromium.org>
Reviewed-by: Mason Freed <masonf@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1086557}
diff --git a/common/sab.js b/common/sab.js
index 47d1297..a3ea610 100644
--- a/common/sab.js
+++ b/common/sab.js
@@ -6,14 +6,14 @@
   } catch(e) {
     sabConstructor = null;
   }
-  return (type, length) => {
+  return (type, length, opts) => {
     if (type === "ArrayBuffer") {
-      return new ArrayBuffer(length);
+      return new ArrayBuffer(length, opts);
     } else if (type === "SharedArrayBuffer") {
       if (sabConstructor && sabConstructor.name !== "SharedArrayBuffer") {
         throw new Error("WebAssembly.Memory does not support shared:true");
       }
-      return new sabConstructor(length);
+      return new sabConstructor(length, opts);
     } else {
       throw new Error("type has to be ArrayBuffer or SharedArrayBuffer");
     }
diff --git a/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.js b/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.js
index 1814df3..6ba17f7 100644
--- a/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.js
+++ b/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.js
@@ -1,4 +1,5 @@
 // META: global=window,worker
+// META: script=/common/sab.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js
diff --git a/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.js b/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.js
index ebbda99..2a46d79 100644
--- a/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.js
+++ b/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.js
@@ -1,3 +1,4 @@
+// META: script=/common/sab.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js
diff --git a/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js b/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
index 65e4085..23cf4f6 100644
--- a/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
+++ b/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
@@ -97,3 +97,73 @@
     assert_equals(Object.getPrototypeOf(transfer), ReadableStream.prototype);
   }
 });
+
+structuredCloneBatteryOfTests.push({
+  description: 'Resizable ArrayBuffer is transferable',
+  async f(runner) {
+    const buffer = new ArrayBuffer(16, { maxByteLength: 1024 });
+    const copy = await runner.structuredClone(buffer, [buffer]);
+    assert_equals(buffer.byteLength, 0);
+    assert_equals(copy.byteLength, 16);
+    assert_equals(copy.maxByteLength, 1024);
+    assert_true(copy.resizable);
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'Length-tracking TypedArray is transferable',
+  async f(runner) {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    const ta = new Uint8Array(ab);
+    const copy = await runner.structuredClone(ta, [ab]);
+    assert_equals(ab.byteLength, 0);
+    assert_equals(copy.buffer.byteLength, 16);
+    assert_equals(copy.buffer.maxByteLength, 1024);
+    assert_true(copy.buffer.resizable);
+    copy.buffer.resize(32);
+    assert_equals(copy.byteLength, 32);
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'Length-tracking DataView is transferable',
+  async f(runner) {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    const dv = new DataView(ab);
+    const copy = await runner.structuredClone(dv, [ab]);
+    assert_equals(ab.byteLength, 0);
+    assert_equals(copy.buffer.byteLength, 16);
+    assert_equals(copy.buffer.maxByteLength, 1024);
+    assert_true(copy.buffer.resizable);
+    copy.buffer.resize(32);
+    assert_equals(copy.byteLength, 32);
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'Transferring OOB TypedArray throws',
+  async f(runner, t) {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    const ta = new Uint8Array(ab, 8);
+    ab.resize(0);
+    await promise_rejects_dom(
+      t,
+      "DataCloneError",
+      runner.structuredClone(ta, [ab])
+    );
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'Transferring OOB DataView throws',
+  async f(runner, t) {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    const dv = new DataView(ab, 8);
+    ab.resize(0);
+    await promise_rejects_dom(
+      t,
+      "DataCloneError",
+      runner.structuredClone(dv, [ab])
+    );
+  }
+});
diff --git a/html/webappapis/structured-clone/structured-clone-battery-of-tests.js b/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
index 580a81a..923ac9d 100644
--- a/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
+++ b/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
@@ -379,6 +379,14 @@
 check('Array FileList object, FileList empty', () => ([func_FileList_empty()]), compare_Array(enumerate_props(compare_FileList)), true);
 check('Object FileList object, FileList empty', () => ({'x':func_FileList_empty()}), compare_Object(enumerate_props(compare_FileList)), true);
 
+function compare_ArrayBuffer(actual, input) {
+  assert_true(actual instanceof ArrayBuffer, 'instanceof ArrayBuffer');
+  assert_equals(actual.byteLength, input.byteLength, 'byteLength');
+  assert_equals(actual.maxByteLength, input.maxByteLength, 'maxByteLength');
+  assert_equals(actual.resizable, input.resizable, 'resizable');
+  assert_equals(actual.growable, input.growable, 'growable');
+}
+
 function compare_ArrayBufferView(view) {
   const Type = self[view];
   return function(actual, input) {
@@ -386,6 +394,8 @@
       assert_unreached(actual);
     assert_true(actual instanceof Type, 'instanceof '+view);
     assert_equals(actual.length, input.length, 'length');
+    assert_equals(actual.byteLength, input.byteLength, 'byteLength');
+    assert_equals(actual.byteOffset, input.byteOffset, 'byteOffset');
     assert_not_equals(actual.buffer, input.buffer, 'buffer');
     for (let i = 0; i < actual.length; ++i) {
       assert_equals(actual[i], input[i], 'actual['+i+']');
@@ -667,3 +677,77 @@
     assert_equals(Object.getPrototypeOf(copy), File.prototype);
   }
 );
+
+check(
+  'Resizable ArrayBuffer',
+  () => {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    assert_true(ab.resizable);
+    return ab;
+  },
+  compare_ArrayBuffer);
+
+structuredCloneBatteryOfTests.push({
+  description: 'Growable SharedArrayBuffer',
+  async f(runner) {
+    const sab = createBuffer('SharedArrayBuffer', 16, { maxByteLength: 1024 });
+    assert_true(sab.growable);
+    try {
+      const copy = await runner.structuredClone(sab);
+      compare_ArrayBuffer(sab, copy);
+    } catch (e) {
+      // If we're cross-origin isolated, cloning SABs should not fail.
+      if (e instanceof DOMException && e.code === DOMException.DATA_CLONE_ERR) {
+        assert_false(self.crossOriginIsolated);
+      } else {
+        throw e;
+      }
+    }
+  }
+});
+
+check(
+  'Length-tracking TypedArray',
+  () => {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    assert_true(ab.resizable);
+    return new Uint8Array(ab);
+  },
+  compare_ArrayBufferView('Uint8Array'));
+
+check(
+  'Length-tracking DataView',
+  () => {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    assert_true(ab.resizable);
+    return new DataView(ab);
+  },
+  compare_ArrayBufferView('DataView'));
+
+structuredCloneBatteryOfTests.push({
+  description: 'Serializing OOB TypedArray throws',
+  async f(runner, t) {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    const ta = new Uint8Array(ab, 8);
+    ab.resize(0);
+    await promise_rejects_dom(
+      t,
+      "DataCloneError",
+      runner.structuredClone(ta)
+    );
+  }
+});
+
+structuredCloneBatteryOfTests.push({
+  description: 'Serializing OOB DataView throws',
+  async f(runner, t) {
+    const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+    const dv = new DataView(ab, 8);
+    ab.resize(0);
+    await promise_rejects_dom(
+      t,
+      "DataCloneError",
+      runner.structuredClone(dv)
+    );
+  }
+});
diff --git a/html/webappapis/structured-clone/structured-clone.any.js b/html/webappapis/structured-clone/structured-clone.any.js
index 90ba5df..1358a71 100644
--- a/html/webappapis/structured-clone/structured-clone.any.js
+++ b/html/webappapis/structured-clone/structured-clone.any.js
@@ -1,4 +1,5 @@
 // META: title=structuredClone() tests
+// META: script=/common/sab.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
 // META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js
diff --git a/webidl/ecmascript-binding/allow-resizable.html b/webidl/ecmascript-binding/allow-resizable.html
new file mode 100644
index 0000000..be9df55
--- /dev/null
+++ b/webidl/ecmascript-binding/allow-resizable.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/sab.js"></script>
+<script type="module">
+test(t => {
+  // Fixed-length ABs should not throw
+  const ab = new ArrayBuffer(16);
+  new Response(new Uint8Array(ab));
+
+  const rab = new ArrayBuffer(16, { maxByteLength: 1024 });
+  // Response doesn't have [AllowResizable] or [AllowShared]
+  assert_throws_js(TypeError, () => {
+    new Response(new Uint8Array(rab));
+  });
+}, "APIs without [AllowResizable] throw when passed resizable ArrayBuffers");
+
+test(t => {
+  const enc = new TextEncoder();
+
+  // Fixed-length SABs should not throw
+  const sab = createBuffer('SharedArrayBuffer', 16, { maxByteLength: 1024 });
+  enc.encodeInto("foobar", new Uint8Array(sab));
+
+  const gsab = createBuffer('SharedArrayBuffer', 16, { maxByteLength: 1024 });
+  // TextEncoder.encodeInto doesn't have [AllowResizable] but has [AllowShared]
+  assert_throws_js(TypeError, () => {
+    enc.encodeInto("foobar", new Uint8Array(gsab));
+  });
+}, "APIs with [AllowShared] but without [AllowResizable] throw when passed growable SharedArrayBuffers");
+</script>
diff --git a/workers/semantics/structured-clone/dedicated.html b/workers/semantics/structured-clone/dedicated.html
index 2f1732c..16b6be5 100644
--- a/workers/semantics/structured-clone/dedicated.html
+++ b/workers/semantics/structured-clone/dedicated.html
@@ -2,6 +2,7 @@
 <title>structured clone to dedicated worker</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src=/common/sab.js></script>
 <script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js></script>
 <script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js></script>
 <script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js></script>
diff --git a/workers/semantics/structured-clone/shared.html b/workers/semantics/structured-clone/shared.html
index 793da8f..eb85499 100644
--- a/workers/semantics/structured-clone/shared.html
+++ b/workers/semantics/structured-clone/shared.html
@@ -2,6 +2,7 @@
 <title>structured clone to shared worker</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src=/common/sab.js></script>
 <script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js></script>
 <script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js></script>
 <script src=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js></script>