diff --git a/patches/chromium/.patches b/patches/chromium/.patches index cac1e6ce514be..ea93d4b16f160 100644 --- a/patches/chromium/.patches +++ b/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 diff --git a/patches/chromium/cherry-pick-a5f54612590d.patch b/patches/chromium/cherry-pick-a5f54612590d.patch new file mode 100644 index 0000000000000..b0aee4ca531dd --- /dev/null +++ b/patches/chromium/cherry-pick-a5f54612590d.patch @@ -0,0 +1,649 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Victor Costan +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 +Reviewed-by: enne +Reviewed-by: Joshua Bell +Cr-Original-Commit-Position: refs/heads/main@{#940070} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3272377 +Bot-Commit: Rubber Stamper +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 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(result_buffer->BaseAddress()); +- + auto* resolver = MakeGarbageCollected(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 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(result_buffer->BaseAddress()); +- + auto* resolver = MakeGarbageCollected(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 native_io_file, + CrossThreadPersistent resolver, +- CrossThreadPersistent result_buffer, + NativeIOFile::FileState* file_state, + scoped_refptr resolver_task_runner, +- char* result_buffer_data, ++ std::unique_ptr 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(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 resolver, +- CrossThreadPersistent result_buffer, ++ std::unique_ptr 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(); +- read_result->setBuffer(NotShared(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 native_io_file, + CrossThreadPersistent resolver, +- CrossThreadPersistent result_buffer, + NativeIOFile::FileState* file_state, + scoped_refptr resolver_task_runner, +- const char* result_buffer_data, ++ std::unique_ptr 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(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 resolver, +- CrossThreadPersistent result_buffer, ++ std::unique_ptr 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(); +- write_result->setBuffer(NotShared(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 native_io_file, + CrossThreadPersistent resolver, +- CrossThreadPersistent result_buffer, + NativeIOFile::FileState* file_state, + scoped_refptr file_task_runner, +- char* result_buffer_data, ++ std::unique_ptr 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 resolver, +- CrossThreadPersistent result_buffer, ++ std::unique_ptr result_buffer_data, + int read_bytes, + base::File::Error read_error); + +@@ -143,10 +143,9 @@ class NativeIOFile final : public ScriptWrappable { + static void DoWrite( + CrossThreadPersistent native_io_file, + CrossThreadPersistent resolver, +- CrossThreadPersistent result_buffer, + NativeIOFile::FileState* file_state, + scoped_refptr resolver_task_runner, +- const char* result_buffer_data, ++ std::unique_ptr 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 resolver, +- CrossThreadPersistent result_buffer, ++ std::unique_ptr 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::Create( ++ ScriptState* script_state, ++ NotShared 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( ++ std::move(contents), type, offset, ++#if DCHECK_IS_ON() ++ byte_length, ++#endif // DCHECK_IS_ON() ++ length, base::PassKey()); ++} ++ ++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) ++ : 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 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(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 ++#include ++ ++#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 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 Create( ++ ScriptState* script_state, ++ NotShared 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(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 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(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_