Browse Source

chore: cherry-pick a5f54612590d from chromium (#31899)

Pedro Pontes 3 years ago
parent
commit
16be926e0a
2 changed files with 650 additions and 0 deletions
  1. 1 0
      patches/chromium/.patches
  2. 649 0
      patches/chromium/cherry-pick-a5f54612590d.patch

+ 1 - 0
patches/chromium/.patches

@@ -114,3 +114,4 @@ cherry-pick-2e7c9b33453b.patch
 move_networkstateobserver_from_document_to_window.patch
 cherry-pick-0894af410c4e.patch
 cherry-pick-91dd4f79ab5b.patch
+cherry-pick-a5f54612590d.patch

+ 649 - 0
patches/chromium/cherry-pick-a5f54612590d.patch

@@ -0,0 +1,649 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Victor Costan <[email protected]>
+Date: Wed, 10 Nov 2021 21:14:03 +0000
+Subject: M96: Storage Foundation: Avoid cross-thread access of
+ DOMArrayBufferView.
+
+blink::NativeIOFile::{read, write}() (in the Storage Foundation API
+implementation) pass DOMArrayBufferView instances to
+blink::NativeIOFile::Do{Read,Write}() via CrossThreadPersistent.
+blink::NativeIOFile::Do{Read,Write}() accesses these instances.
+
+CrossThreadPersistent can be used across threads to keep a garbage
+collected object alive. However, accessing the object on a different
+thread is not safe. cppgc::subtle::CrossThreadPersistent
+(blink::CrossThreadPersistent is an alias to that) has comments
+explaining that the garbage collected heap can go away while the
+CrossThreadPersistent instance exists.
+
+This CL bypasses the problem by having Do{Read,Write}() receive a
+ArrayBufferContents that has the DOMArrayBufferView's backing buffer.
+ArrayBufferContents is not garbage-collected, so it can be safely used
+across threads.
+
+This CL introduces a NativeIODataBuffer class that contains the logic
+and state for tearing a DOMArrayBufferView apart into its components
+(backing buffer, view type, view offset, view length) and putting it
+back together into a new DOMArrayBufferView, after it doesn't need to be
+accessed cross-thread anymore.
+
+(cherry picked from commit 5200793c2aea5979cc79f3350a4e3d6c0795d6f2)
+
+Bug: 1268274
+Change-Id: I51588f5bfe963de96ce426e0f480e8c5b4902688
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3269366
+Commit-Queue: Victor Costan <[email protected]>
+Reviewed-by: enne <[email protected]>
+Reviewed-by: Joshua Bell <[email protected]>
+Cr-Original-Commit-Position: refs/heads/main@{#940070}
+Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3272377
+Bot-Commit: Rubber Stamper <[email protected]>
+Cr-Commit-Position: refs/branch-heads/4664@{#941}
+Cr-Branched-From: 24dc4ee75e01a29d390d43c9c264372a169273a7-refs/heads/main@{#929512}
+
+diff --git a/third_party/blink/renderer/modules/native_io/native_io_file.cc b/third_party/blink/renderer/modules/native_io/native_io_file.cc
+index b25cf909f05be73f690fabee7942ee1fa83c1e04..4d5aa4efa13930aea4886bac0fd8ba892ce8b5a5 100644
+--- a/third_party/blink/renderer/modules/native_io/native_io_file.cc
++++ b/third_party/blink/renderer/modules/native_io/native_io_file.cc
+@@ -24,7 +24,9 @@
+ #include "third_party/blink/renderer/core/execution_context/execution_context.h"
+ #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
+ #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_state_observer.h"
++#include "third_party/blink/renderer/core/typed_arrays/array_buffer/array_buffer_contents.h"
+ #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
++#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer_view.h"
+ #include "third_party/blink/renderer/modules/native_io/native_io_error.h"
+ #include "third_party/blink/renderer/modules/native_io/native_io_file_utils.h"
+ #include "third_party/blink/renderer/platform/bindings/exception_code.h"
+@@ -256,39 +258,41 @@ ScriptPromise NativeIOFile::read(ScriptState* script_state,
+                                "The file was already closed"));
+     return ScriptPromise();
+   }
++
++  // TODO(pwnall): This assignment should move right before the
++  // worker_pool::PostTask() call.
++  //
++  // `io_pending_` should only be set to true when we know for sure we'll post a
++  // task that eventually results in getting `io_pending_` set back to false.
++  // Having `io_pending_` set to true in an early return case (rejecting with an
++  // exception) leaves the NativeIOFile "stuck" in a state where all future I/O
++  // method calls will reject.
+   io_pending_ = true;
+ 
+   int read_size = NativeIOOperationSize(*buffer);
+ 
+-  DOMArrayBufferView* result_buffer =
+-      TransferToNewArrayBufferView(script_state->GetIsolate(), buffer);
+-  if (!result_buffer) {
++  std::unique_ptr<NativeIODataBuffer> result_buffer_data =
++      NativeIODataBuffer::Create(script_state, buffer);
++  if (!result_buffer_data) {
+     exception_state.ThrowTypeError("Could not transfer buffer");
+     return ScriptPromise();
+   }
++  DCHECK(result_buffer_data->IsValid());
+   DCHECK(buffer->IsDetached());
+ 
+-  char* result_buffer_data = static_cast<char*>(result_buffer->BaseAddress());
+-
+   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+   // The first CrossThreadUnretained() is safe here because the
+   // NativeIOFile::FileState instance is owned by this NativeIOFile, which is
+   // also passed to the task via WrapCrossThreadPersistent. Therefore, the
+   // FileState instance is guaranteed to remain alive during the task's
+   // execution.
+-  //
+-  // The second CrossThreadUnretained() is safe here because result_buffer_data
+-  // is backed by a DOMArrayBufferView that is also passed to the task via
+-  // WrapCrossThreadPersistent. Therefore, the buffer is guaranteed to remain
+-  // alive during the task's execution.
+   worker_pool::PostTask(
+       FROM_HERE, {base::MayBlock()},
+-      CrossThreadBindOnce(
+-          &DoRead, WrapCrossThreadPersistent(this),
+-          WrapCrossThreadPersistent(resolver),
+-          WrapCrossThreadPersistent(result_buffer),
+-          CrossThreadUnretained(file_state_.get()), resolver_task_runner_,
+-          CrossThreadUnretained(result_buffer_data), file_offset, read_size));
++      CrossThreadBindOnce(&DoRead, WrapCrossThreadPersistent(this),
++                          WrapCrossThreadPersistent(resolver),
++                          CrossThreadUnretained(file_state_.get()),
++                          resolver_task_runner_, std::move(result_buffer_data),
++                          file_offset, read_size));
+   return resolver->Promise();
+ }
+ 
+@@ -344,35 +348,28 @@ ScriptPromise NativeIOFile::write(ScriptState* script_state,
+ 
+   io_pending_ = true;
+ 
+-  DOMArrayBufferView* result_buffer =
+-      TransferToNewArrayBufferView(script_state->GetIsolate(), buffer);
+-  if (!result_buffer) {
++  std::unique_ptr<NativeIODataBuffer> result_buffer_data =
++      NativeIODataBuffer::Create(script_state, buffer);
++  if (!result_buffer_data) {
+     exception_state.ThrowTypeError("Could not transfer buffer");
+     return ScriptPromise();
+   }
++  DCHECK(result_buffer_data->IsValid());
+   DCHECK(buffer->IsDetached());
+ 
+-  char* result_buffer_data = static_cast<char*>(result_buffer->BaseAddress());
+-
+   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+   // The first CrossThreadUnretained() is safe here because the
+   // NativeIOFile::FileState instance is owned by this NativeIOFile, which is
+   // also passed to the task via WrapCrossThreadPersistent. Therefore, the
+   // FileState instance is guaranteed to remain alive during the task's
+   // execution.
+-  //
+-  // The second CrossThreadUnretained() is safe here because result_buffer_data
+-  // is backed by a DOMArrayBufferView that is also passed to the task via
+-  // WrapCrossThreadPersistent. Therefore, the data is guaranteed to remain
+-  // alive during the task's execution.
+   worker_pool::PostTask(
+       FROM_HERE, {base::MayBlock()},
+-      CrossThreadBindOnce(
+-          &DoWrite, WrapCrossThreadPersistent(this),
+-          WrapCrossThreadPersistent(resolver),
+-          WrapCrossThreadPersistent(result_buffer),
+-          CrossThreadUnretained(file_state_.get()), resolver_task_runner_,
+-          CrossThreadUnretained(result_buffer_data), file_offset, write_size));
++      CrossThreadBindOnce(&DoWrite, WrapCrossThreadPersistent(this),
++                          WrapCrossThreadPersistent(resolver),
++                          CrossThreadUnretained(file_state_.get()),
++                          resolver_task_runner_, std::move(result_buffer_data),
++                          file_offset, write_size));
+   return resolver->Promise();
+ }
+ 
+@@ -676,22 +673,29 @@ void NativeIOFile::DidSetLengthIpc(
+ void NativeIOFile::DoRead(
+     CrossThreadPersistent<NativeIOFile> native_io_file,
+     CrossThreadPersistent<ScriptPromiseResolver> resolver,
+-    CrossThreadPersistent<DOMArrayBufferView> result_buffer,
+     NativeIOFile::FileState* file_state,
+     scoped_refptr<base::SequencedTaskRunner> resolver_task_runner,
+-    char* result_buffer_data,
++    std::unique_ptr<NativeIODataBuffer> result_buffer_data,
+     uint64_t file_offset,
+     int read_size) {
+   DCHECK(!IsMainThread()) << "File I/O should not happen on the main thread";
+ 
++  DCHECK(resolver_task_runner);
++  DCHECK(result_buffer_data);
++  DCHECK(result_buffer_data->IsValid());
++  DCHECK_GE(read_size, 0);
++#if DCHECK_IS_ON()
++  DCHECK_LE(static_cast<size_t>(read_size), result_buffer_data->DataLength());
++#endif  // DCHECK_IS_ON()
++
+   int read_bytes;
+   base::File::Error read_error;
+   {
+     WTF::MutexLocker mutex_locker(file_state->mutex);
+     DCHECK(file_state->file.IsValid())
+         << "file I/O operation queued after file closed";
+-    read_bytes =
+-        file_state->file.Read(file_offset, result_buffer_data, read_size);
++    read_bytes = file_state->file.Read(file_offset, result_buffer_data->Data(),
++                                       read_size);
+     read_error = (read_bytes < 0) ? file_state->file.GetLastFileError()
+                                   : base::File::FILE_OK;
+   }
+@@ -699,15 +703,18 @@ void NativeIOFile::DoRead(
+   PostCrossThreadTask(
+       *resolver_task_runner, FROM_HERE,
+       CrossThreadBindOnce(&NativeIOFile::DidRead, std::move(native_io_file),
+-                          std::move(resolver), std::move(result_buffer),
++                          std::move(resolver), std::move(result_buffer_data),
+                           read_bytes, read_error));
+ }
+ 
+ void NativeIOFile::DidRead(
+     CrossThreadPersistent<ScriptPromiseResolver> resolver,
+-    CrossThreadPersistent<DOMArrayBufferView> result_buffer,
++    std::unique_ptr<NativeIODataBuffer> result_buffer_data,
+     int read_bytes,
+     base::File::Error read_error) {
++  DCHECK(result_buffer_data);
++  DCHECK(result_buffer_data->IsValid());
++
+   ScriptState* script_state = resolver->GetScriptState();
+   if (!script_state->ContextIsValid())
+     return;
+@@ -727,7 +734,7 @@ void NativeIOFile::DidRead(
+   DCHECK_EQ(read_error, base::File::FILE_OK)
+       << "Error set but positive number of bytes read.";
+   NativeIOReadResult* read_result = MakeGarbageCollected<NativeIOReadResult>();
+-  read_result->setBuffer(NotShared<DOMArrayBufferView>(result_buffer));
++  read_result->setBuffer(result_buffer_data->Take());
+   read_result->setReadBytes(read_bytes);
+   resolver->Resolve(read_result);
+ }
+@@ -736,13 +743,19 @@ void NativeIOFile::DidRead(
+ void NativeIOFile::DoWrite(
+     CrossThreadPersistent<NativeIOFile> native_io_file,
+     CrossThreadPersistent<ScriptPromiseResolver> resolver,
+-    CrossThreadPersistent<DOMArrayBufferView> result_buffer,
+     NativeIOFile::FileState* file_state,
+     scoped_refptr<base::SequencedTaskRunner> resolver_task_runner,
+-    const char* result_buffer_data,
++    std::unique_ptr<NativeIODataBuffer> result_buffer_data,
+     uint64_t file_offset,
+     int write_size) {
+   DCHECK(!IsMainThread()) << "File I/O should not happen on the main thread";
++  DCHECK(resolver_task_runner);
++  DCHECK(result_buffer_data);
++  DCHECK(result_buffer_data->IsValid());
++  DCHECK_GE(write_size, 0);
++#if DCHECK_IS_ON()
++  DCHECK_LE(static_cast<size_t>(write_size), result_buffer_data->DataLength());
++#endif  // DCHECK_IS_ON()
+ 
+   int written_bytes;
+   int64_t actual_file_length_on_failure = 0;
+@@ -751,8 +764,8 @@ void NativeIOFile::DoWrite(
+     WTF::MutexLocker mutex_locker(file_state->mutex);
+     DCHECK(file_state->file.IsValid())
+         << "file I/O operation queued after file closed";
+-    written_bytes =
+-        file_state->file.Write(file_offset, result_buffer_data, write_size);
++    written_bytes = file_state->file.Write(
++        file_offset, result_buffer_data->Data(), write_size);
+     write_error = (written_bytes < 0) ? file_state->file.GetLastFileError()
+                                       : base::File::FILE_OK;
+     if (written_bytes < write_size || write_error != base::File::FILE_OK) {
+@@ -767,18 +780,21 @@ void NativeIOFile::DoWrite(
+   PostCrossThreadTask(
+       *resolver_task_runner, FROM_HERE,
+       CrossThreadBindOnce(&NativeIOFile::DidWrite, std::move(native_io_file),
+-                          std::move(resolver), std::move(result_buffer),
++                          std::move(resolver), std::move(result_buffer_data),
+                           written_bytes, write_error, write_size,
+                           actual_file_length_on_failure));
+ }
+ 
+ void NativeIOFile::DidWrite(
+     CrossThreadPersistent<ScriptPromiseResolver> resolver,
+-    CrossThreadPersistent<DOMArrayBufferView> result_buffer,
++    std::unique_ptr<NativeIODataBuffer> result_buffer_data,
+     int written_bytes,
+     base::File::Error write_error,
+     int write_size,
+     int64_t actual_file_length_on_failure) {
++  DCHECK(result_buffer_data);
++  DCHECK(result_buffer_data->IsValid());
++
+   ScriptState* script_state = resolver->GetScriptState();
+   if (!script_state->ContextIsValid())
+     return;
+@@ -821,7 +837,7 @@ void NativeIOFile::DidWrite(
+   DCHECK_EQ(write_error, base::File::FILE_OK);
+   NativeIOWriteResult* write_result =
+       MakeGarbageCollected<NativeIOWriteResult>();
+-  write_result->setBuffer(NotShared<DOMArrayBufferView>(result_buffer));
++  write_result->setBuffer(result_buffer_data->Take());
+   write_result->setWrittenBytes(written_bytes);
+   resolver->Resolve(write_result);
+ }
+diff --git a/third_party/blink/renderer/modules/native_io/native_io_file.h b/third_party/blink/renderer/modules/native_io/native_io_file.h
+index 2e41efeefbcf9805ec2b2ed70d018c717c5c75d1..8ae49ebc2d36d547d152d4e56192e30f8cacd641 100644
+--- a/third_party/blink/renderer/modules/native_io/native_io_file.h
++++ b/third_party/blink/renderer/modules/native_io/native_io_file.h
+@@ -16,6 +16,7 @@
+ #include "third_party/blink/renderer/core/typed_arrays/array_buffer_view_helpers.h"
+ #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer_view.h"
+ #include "third_party/blink/renderer/modules/native_io/native_io_capacity_tracker.h"
++#include "third_party/blink/renderer/modules/native_io/native_io_file_utils.h"
+ #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
+ #include "third_party/blink/renderer/platform/heap/handle.h"
+ #include "third_party/blink/renderer/platform/heap/persistent.h"
+@@ -127,15 +128,14 @@ class NativeIOFile final : public ScriptWrappable {
+   // Performs the file I/O part of read(), off the main thread.
+   static void DoRead(CrossThreadPersistent<NativeIOFile> native_io_file,
+                      CrossThreadPersistent<ScriptPromiseResolver> resolver,
+-                     CrossThreadPersistent<DOMArrayBufferView> result_buffer,
+                      NativeIOFile::FileState* file_state,
+                      scoped_refptr<base::SequencedTaskRunner> file_task_runner,
+-                     char* result_buffer_data,
++                     std::unique_ptr<NativeIODataBuffer> result_buffer_data,
+                      uint64_t file_offset,
+                      int read_size);
+   // Performs the post file I/O part of read(), on the main thread.
+   void DidRead(CrossThreadPersistent<ScriptPromiseResolver> resolver,
+-               CrossThreadPersistent<DOMArrayBufferView> result_buffer,
++               std::unique_ptr<NativeIODataBuffer> result_buffer_data,
+                int read_bytes,
+                base::File::Error read_error);
+ 
+@@ -143,10 +143,9 @@ class NativeIOFile final : public ScriptWrappable {
+   static void DoWrite(
+       CrossThreadPersistent<NativeIOFile> native_io_file,
+       CrossThreadPersistent<ScriptPromiseResolver> resolver,
+-      CrossThreadPersistent<DOMArrayBufferView> result_buffer,
+       NativeIOFile::FileState* file_state,
+       scoped_refptr<base::SequencedTaskRunner> resolver_task_runner,
+-      const char* result_buffer_data,
++      std::unique_ptr<NativeIODataBuffer> result_buffer_data,
+       uint64_t file_offset,
+       int write_size);
+   // Performs the post file I/O part of write(), on the main thread.
+@@ -154,7 +153,7 @@ class NativeIOFile final : public ScriptWrappable {
+   // `actual_file_length_on_failure` is negative if the I/O operation was
+   // unsuccessful and the correct length of the file could not be determined.
+   void DidWrite(CrossThreadPersistent<ScriptPromiseResolver> resolver,
+-                CrossThreadPersistent<DOMArrayBufferView> result_buffer,
++                std::unique_ptr<NativeIODataBuffer> result_buffer_data,
+                 int written_bytes,
+                 base::File::Error write_error,
+                 int write_size,
+diff --git a/third_party/blink/renderer/modules/native_io/native_io_file_utils.cc b/third_party/blink/renderer/modules/native_io/native_io_file_utils.cc
+index c50a0a94d111d9ea4eb1eac8a7da920936e0d1a3..3e98a12059374d41b22c8d5c706c31e81581aeae 100644
+--- a/third_party/blink/renderer/modules/native_io/native_io_file_utils.cc
++++ b/third_party/blink/renderer/modules/native_io/native_io_file_utils.cc
+@@ -3,9 +3,16 @@
+ // found in the LICENSE file.
+ 
+ #include "third_party/blink/renderer/modules/native_io/native_io_file_utils.h"
++
+ #include "base/numerics/safe_conversions.h"
++#include "base/sequence_checker.h"
++#include "base/types/pass_key.h"
++#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
++#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer_view.h"
+ #include "third_party/blink/renderer/core/typed_arrays/dom_data_view.h"
+ #include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
++#include "third_party/blink/renderer/platform/bindings/script_state.h"
++#include "v8/include/v8.h"
+ 
+ namespace blink {
+ 
+@@ -72,4 +79,140 @@ DOMArrayBufferView* TransferToNewArrayBufferView(
+   return target;
+ }
+ 
++// static
++std::unique_ptr<NativeIODataBuffer> NativeIODataBuffer::Create(
++    ScriptState* script_state,
++    NotShared<DOMArrayBufferView> source) {
++  DCHECK(script_state);
++  DCHECK(source);
++
++  DOMArrayBufferView::ViewType type = source->GetType();
++  size_t offset = source->byteOffset();
++  size_t byte_length = source->byteLength();
++  size_t length = byte_length / source->TypeSize();
++
++  // Explicitly fail if the source buffer is not detachable. On its own,
++  // Transfer() copies non-detachable input buffers.
++  DOMArrayBuffer* buffer = source->buffer();
++  v8::Isolate* isolate = script_state->GetIsolate();
++  if (!buffer->IsDetachable(isolate))
++    return nullptr;
++
++  ArrayBufferContents contents;
++  if (!buffer->Transfer(isolate, contents))
++    return nullptr;
++  DCHECK(source->IsDetached());
++
++  return std::make_unique<NativeIODataBuffer>(
++      std::move(contents), type, offset,
++#if DCHECK_IS_ON()
++      byte_length,
++#endif  // DCHECK_IS_ON()
++      length, base::PassKey<NativeIODataBuffer>());
++}
++
++NativeIODataBuffer::NativeIODataBuffer(ArrayBufferContents contents,
++                                       DOMArrayBufferView::ViewType type,
++                                       size_t offset,
++#if DCHECK_IS_ON()
++                                       size_t byte_length,
++#endif  // DCHECK_IS_ON()
++                                       size_t length,
++                                       base::PassKey<NativeIODataBuffer>)
++    : contents_(std::move(contents)),
++      type_(type),
++      offset_(offset),
++#if DCHECK_IS_ON()
++      byte_length_(byte_length),
++#endif  // DCHECK_IS_ON()
++      length_(length) {
++  DCHECK(IsValid());
++  DCHECK(!contents_.IsShared());
++
++  // DataLength() returns 0 when called on an invalid ArrayBufferContents
++  // (backing an empty array). This works as expected.
++  DCHECK_LE(offset, contents_.DataLength());
++#if DCHECK_IS_ON()
++  DCHECK_LE(length, byte_length);
++  DCHECK_LE(byte_length, contents_.DataLength() - offset);
++#endif  // DCHECK_IS_ON()
++}
++
++NativeIODataBuffer::~NativeIODataBuffer() {
++  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
++}
++
++bool NativeIODataBuffer::IsValid() const {
++  // The ArrayBufferContents is not shared when this instance is constructed. It
++  // should not become shared while the instance is valid, because no other code
++  // can gain access and make it shared.
++  //
++  // ArrayBufferContents::IsShared() returns false for invalid instances, which
++  // works out well for this check.
++  DCHECK(!contents_.IsShared());
++
++  // Transferring the data out of an empty ArrayBuffer yields an invalid
++  // ArrayBufferContents.
++  return length_ == 0 || contents_.IsValid();
++}
++
++NotShared<DOMArrayBufferView> NativeIODataBuffer::Take() {
++  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
++  DCHECK(IsValid());
++
++  DOMArrayBuffer* array_buffer = DOMArrayBuffer::Create(std::move(contents_));
++
++  DOMArrayBufferView* view = nullptr;
++  switch (type_) {
++    case DOMArrayBufferView::kTypeInt8:
++      view = DOMInt8Array::Create(array_buffer, offset_, length_);
++      break;
++
++    case DOMArrayBufferView::kTypeUint8:
++      view = DOMUint8Array::Create(array_buffer, offset_, length_);
++      break;
++
++    case DOMArrayBufferView::kTypeUint8Clamped:
++      view = DOMUint8ClampedArray::Create(array_buffer, offset_, length_);
++      break;
++
++    case DOMArrayBufferView::kTypeInt16:
++      view = DOMInt16Array::Create(array_buffer, offset_, length_);
++      break;
++
++    case DOMArrayBufferView::kTypeUint16:
++      view = DOMUint16Array::Create(array_buffer, offset_, length_);
++      break;
++
++    case DOMArrayBufferView::kTypeInt32:
++      view = DOMInt32Array::Create(array_buffer, offset_, length_);
++      break;
++
++    case DOMArrayBufferView::kTypeUint32:
++      view = DOMUint32Array::Create(array_buffer, offset_, length_);
++      break;
++
++    case DOMArrayBufferView::kTypeFloat32:
++      view = DOMFloat32Array::Create(array_buffer, offset_, length_);
++      break;
++
++    case DOMArrayBufferView::kTypeFloat64:
++      view = DOMFloat64Array::Create(array_buffer, offset_, length_);
++      break;
++
++    case DOMArrayBufferView::kTypeBigInt64:
++      view = DOMBigInt64Array::Create(array_buffer, offset_, length_);
++      break;
++
++    case DOMArrayBufferView::kTypeBigUint64:
++      view = DOMBigUint64Array::Create(array_buffer, offset_, length_);
++      break;
++
++    case DOMArrayBufferView::kTypeDataView:
++      view = DOMDataView::Create(array_buffer, offset_, length_);
++      break;
++  }
++  return NotShared<DOMArrayBufferView>(view);
++}
++
+ }  // namespace blink
+diff --git a/third_party/blink/renderer/modules/native_io/native_io_file_utils.h b/third_party/blink/renderer/modules/native_io/native_io_file_utils.h
+index 355a67a8125ea11158dfe435a71c1c01b1ece361..a500d38bcdf8340e7c747cbde949db8f980ea272 100644
+--- a/third_party/blink/renderer/modules/native_io/native_io_file_utils.h
++++ b/third_party/blink/renderer/modules/native_io/native_io_file_utils.h
+@@ -5,11 +5,19 @@
+ #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_NATIVE_IO_NATIVE_IO_FILE_UTILS_H_
+ #define THIRD_PARTY_BLINK_RENDERER_MODULES_NATIVE_IO_NATIVE_IO_FILE_UTILS_H_
+ 
++#include <cstddef>
++#include <memory>
++
++#include "base/sequence_checker.h"
++#include "base/types/pass_key.h"
++#include "third_party/blink/renderer/core/typed_arrays/array_buffer/array_buffer_contents.h"
+ #include "third_party/blink/renderer/core/typed_arrays/array_buffer_view_helpers.h"
+ #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer_view.h"
+ 
+ namespace blink {
+ 
++class ScriptState;
++
+ // Extracts the read/write operation size from the buffer size.
+ int NativeIOOperationSize(const DOMArrayBufferView& buffer);
+ 
+@@ -20,6 +28,121 @@ DOMArrayBufferView* TransferToNewArrayBufferView(
+     v8::Isolate* isolate,
+     NotShared<DOMArrayBufferView> source);
+ 
++// Provides cross-thread access to the buffer backing a DOMArrayBufferView.
++//
++// This class is necessary because DOMArrayBufferView is garbage-collected,
++// which entails that each DOMArrayBufferView instance can only be safely
++// accessed on the thread where it was created. Note that CrossThreadPersistent
++// can be used to keep a DOMArrayBufferView alive across threads, but the
++// instance cannot be safely accessed on a different thread. See the comments on
++// cppgc::subtle::CrossThreadPersistent for details.
++//
++// An instance takes over a DOMArrayBufferView's backing buffer at construction.
++// The instance exposes the backing buffer via the Data() and DataLength()
++// methods. At some point, the backing buffer is turned back into a
++// DOMArrayBufferView via the Take() method. Once Take() is called, the instance
++// is invalid, and Data() / DataLength() must not be called anymore.
++//
++// An instance should be owned by a single sequence at a time. Changing the
++// owning sequence should be accomplished by PostTask-ing an owning pointer to
++// the instance.
++//
++// Each instance must be destroyed on the same sequence where it was created.
++// Take() must be called on the same sequence where the instance was created.
++class NativeIODataBuffer {
++ public:
++  // Detaches the buffer backing `source`.
++  //
++  // Returns nullptr if detaching failed.
++  static std::unique_ptr<NativeIODataBuffer> Create(
++      ScriptState* script_state,
++      NotShared<DOMArrayBufferView> source);
++
++  // Exposed for std::make_unique. Instances should be obtained from Create().
++  NativeIODataBuffer(ArrayBufferContents contents,
++                     DOMArrayBufferView::ViewType type,
++                     size_t offset,
++#if DCHECK_IS_ON()
++                     size_t byte_length,
++#endif  // DCHECK_IS_ON()
++                     size_t length,
++                     base::PassKey<NativeIODataBuffer>);
++
++  NativeIODataBuffer(const NativeIODataBuffer&) = delete;
++  NativeIODataBuffer& operator=(const NativeIODataBuffer&) = delete;
++
++  ~NativeIODataBuffer();
++
++  // Re-creates the DOMArrayBufferView.
++  //
++  // Must only be called while this instance is onwed by the same sequence where
++  // Create() was called. Must only be called if IsValid() is true.
++  // After the call, IsValid() will return false.
++  NotShared<DOMArrayBufferView> Take();
++
++  // Exposed for DCHECKs.
++  //
++  // Can be called while this instance is owned by any sequence.
++  bool IsValid() const;
++
++  // Returns a raw pointer to the DOMArrayBufferView's view.
++  //
++  // The return type was chosen so that the raw pointer can be conveniently
++  // passed to base::File methods.
++  //
++  // Can be called while this instance is owned by any sequence. Must only be
++  // called if IsValid() is true.
++  char* Data() {
++    DCHECK(IsValid());
++
++    // An invalid ArrayBufferContents (backing an empty array) returns nullptr
++    // when Data() is called. However, in that case, the offset must be zero.
++    DCHECK(contents_.Data() || contents_.DataLength() == 0);
++    DCHECK(contents_.Data() || offset_ == 0);
++
++    // According to the DCHECKs above, this branch isn't strictly needed. The
++    // return statement below the branch will never do pointer arithmetic on
++    // nullptr, because `offset_` is guaranteed to be zero when
++    // the ArrayBufferContents is not valid but this instance is.
++    char* data = static_cast<char*>(contents_.Data());
++    if (!data) {
++      DCHECK_EQ(offset_, 0u);
++      return data;
++    }
++
++    return data + offset_;
++  }
++
++#if DCHECK_IS_ON()
++  // Returns the size of the DOMArrayBufferView's view, in bytes.
++  //
++  // Exposed for DCHECKs around base::File calls.
++  //
++  // Can be called while this instance is owned by any sequence. Must only be
++  // called if IsValid() is true.
++  size_t DataLength() const {
++    DCHECK(IsValid());
++    return byte_length_;
++  }
++#endif  // DCHECK_IS_ON()
++
++ private:
++  SEQUENCE_CHECKER(sequence_checker_);
++
++  // May not be valid, as reported by ArrayBufferContents::IsValid().
++  //
++  // If valid, guaranteed not to be shared, as reported by
++  // ArrayBufferContents::IsShared().
++  ArrayBufferContents contents_;
++
++  DOMArrayBufferView::ViewType type_;
++  const size_t offset_;
++#if DCHECK_IS_ON()
++  const size_t byte_length_;
++#endif  // DCHECK_IS_ON()
++  const size_t length_;
++};
++
+ }  // namespace blink
+ 
+ #endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_NATIVE_IO_NATIVE_IO_FILE_UTILS_H_