summaryrefslogtreecommitdiffstats
path: root/dom/fs/api/FileSystemSyncAccessHandle.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/fs/api/FileSystemSyncAccessHandle.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/fs/api/FileSystemSyncAccessHandle.cpp')
-rw-r--r--dom/fs/api/FileSystemSyncAccessHandle.cpp650
1 files changed, 650 insertions, 0 deletions
diff --git a/dom/fs/api/FileSystemSyncAccessHandle.cpp b/dom/fs/api/FileSystemSyncAccessHandle.cpp
new file mode 100644
index 0000000000..c04f8e11a5
--- /dev/null
+++ b/dom/fs/api/FileSystemSyncAccessHandle.cpp
@@ -0,0 +1,650 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FileSystemSyncAccessHandle.h"
+
+#include "fs/FileSystemAsyncCopy.h"
+#include "fs/FileSystemRequestHandler.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/FixedBufferOutputStream.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/dom/FileSystemAccessHandleChild.h"
+#include "mozilla/dom/FileSystemAccessHandleControlChild.h"
+#include "mozilla/dom/FileSystemHandleBinding.h"
+#include "mozilla/dom/FileSystemLog.h"
+#include "mozilla/dom/FileSystemManager.h"
+#include "mozilla/dom/FileSystemManagerChild.h"
+#include "mozilla/dom/FileSystemSyncAccessHandleBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/fs/IPCRejectReporter.h"
+#include "mozilla/dom/fs/TargetPtrHolder.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "mozilla/ipc/RandomAccessStreamUtils.h"
+#include "nsNetCID.h"
+#include "nsStringStream.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+using SizePromise = Int64Promise;
+const auto CreateAndRejectSizePromise = CreateAndRejectInt64Promise;
+
+} // namespace
+
+FileSystemSyncAccessHandle::FileSystemSyncAccessHandle(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
+ RefPtr<FileSystemAccessHandleChild> aActor,
+ RefPtr<FileSystemAccessHandleControlChild> aControlActor,
+ RefPtr<TaskQueue> aIOTaskQueue,
+ const fs::FileSystemEntryMetadata& aMetadata)
+ : mGlobal(aGlobal),
+ mManager(aManager),
+ mActor(std::move(aActor)),
+ mControlActor(std::move(aControlActor)),
+ mIOTaskQueue(std::move(aIOTaskQueue)),
+ mStreamParams(std::move(aStreamParams)),
+ mMetadata(aMetadata),
+ mState(State::Initial) {
+ LOG(("Created SyncAccessHandle %p", this));
+
+ // Connect with the actor directly in the constructor. This way the actor
+ // can call `FileSystemSyncAccessHandle::ClearActor` when we call
+ // `PFileSystemAccessHandleChild::Send__delete__` even when
+ // FileSystemSyncAccessHandle::Create fails, in which case the not yet
+ // fully constructed FileSystemSyncAccessHandle is being destroyed.
+ mActor->SetAccessHandle(this);
+
+ mControlActor->SetAccessHandle(this);
+}
+
+FileSystemSyncAccessHandle::~FileSystemSyncAccessHandle() {
+ MOZ_ASSERT(!mActor);
+ MOZ_ASSERT(IsClosed());
+}
+
+// static
+Result<RefPtr<FileSystemSyncAccessHandle>, nsresult>
+FileSystemSyncAccessHandle::Create(
+ nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
+ mozilla::ipc::RandomAccessStreamParams&& aStreamParams,
+ mozilla::ipc::ManagedEndpoint<PFileSystemAccessHandleChild>&&
+ aAccessHandleChildEndpoint,
+ mozilla::ipc::Endpoint<PFileSystemAccessHandleControlChild>&&
+ aAccessHandleControlChildEndpoint,
+ const fs::FileSystemEntryMetadata& aMetadata) {
+ WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ auto accessHandleChild = MakeRefPtr<FileSystemAccessHandleChild>();
+
+ QM_TRY(MOZ_TO_RESULT(
+ aManager->ActorStrongRef()->BindPFileSystemAccessHandleEndpoint(
+ std::move(aAccessHandleChildEndpoint), accessHandleChild)));
+
+ auto accessHandleControlChild =
+ MakeRefPtr<FileSystemAccessHandleControlChild>();
+
+ aAccessHandleControlChildEndpoint.Bind(accessHandleControlChild,
+ workerPrivate->ControlEventTarget());
+
+ QM_TRY_UNWRAP(auto streamTransportService,
+ MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIEventTarget>,
+ MOZ_SELECT_OVERLOAD(do_GetService),
+ NS_STREAMTRANSPORTSERVICE_CONTRACTID));
+
+ RefPtr<TaskQueue> ioTaskQueue = TaskQueue::Create(
+ streamTransportService.forget(), "FileSystemSyncAccessHandle");
+ QM_TRY(MOZ_TO_RESULT(ioTaskQueue));
+
+ RefPtr<FileSystemSyncAccessHandle> result = new FileSystemSyncAccessHandle(
+ aGlobal, aManager, std::move(aStreamParams), std::move(accessHandleChild),
+ std::move(accessHandleControlChild), std::move(ioTaskQueue), aMetadata);
+
+ auto autoClose = MakeScopeExit([result] {
+ MOZ_ASSERT(result->mState == State::Initial);
+ result->mState = State::Closed;
+ result->mActor->SendClose();
+ });
+
+ workerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
+ workerPrivate, "FileSystemSyncAccessHandle", [result]() {
+ if (result->IsOpen()) {
+ // We don't need to use the result, we just need to begin the closing
+ // process.
+ Unused << result->BeginClose();
+ }
+ });
+ QM_TRY(MOZ_TO_RESULT(workerRef));
+
+ autoClose.release();
+
+ result->mWorkerRef = std::move(workerRef);
+ result->mState = State::Open;
+
+ return result;
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemSyncAccessHandle)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemSyncAccessHandle)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(FileSystemSyncAccessHandle,
+ LastRelease())
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FileSystemSyncAccessHandle)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FileSystemSyncAccessHandle)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
+ // Don't unlink mManager!
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ if (tmp->IsOpen()) {
+ // We don't need to use the result, we just need to begin the closing
+ // process.
+ Unused << tmp->BeginClose();
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FileSystemSyncAccessHandle)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mManager)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+void FileSystemSyncAccessHandle::LastRelease() {
+ // We can't call `FileSystemSyncAccessHandle::Close` here because it may need
+ // to keep FileSystemSyncAccessHandle object alive which isn't possible when
+ // the object is about to be deleted. There are other mechanisms which ensure
+ // that the object is correctly closed before destruction. For example the
+ // object unlinking and the worker shutdown (we get notified about it via the
+ // callback passed to `StrongWorkerRef`) are used to close the object if it
+ // hasn't been closed yet.
+
+ if (mActor) {
+ PFileSystemAccessHandleChild::Send__delete__(mActor);
+
+ // `PFileSystemAccessHandleChild::Send__delete__` is supposed to call
+ // `FileSystemAccessHandleChild::ActorDestroy` which in turn calls
+ // `FileSystemSyncAccessHandle::ClearActor`, so `mActor` should be be null
+ // at this point.
+ MOZ_ASSERT(!mActor);
+ }
+
+ if (mControlActor) {
+ mControlActor->Close();
+
+ // `FileSystemAccessHandleControlChild::Close` is supposed to call
+ // `FileSystemAccessHandleControlChild::ActorDestroy` which in turn calls
+ // `FileSystemSyncAccessHandle::ClearControlActor`, so `mControlActor`
+ // should be be null at this point.
+ MOZ_ASSERT(!mControlActor);
+ }
+}
+
+void FileSystemSyncAccessHandle::ClearActor() {
+ MOZ_ASSERT(mActor);
+
+ mActor = nullptr;
+}
+
+void FileSystemSyncAccessHandle::ClearControlActor() {
+ // `mControlActor` is initialized in the constructor and this method is
+ // supposed to be called only once.
+ MOZ_ASSERT(mControlActor);
+
+ mControlActor = nullptr;
+}
+
+bool FileSystemSyncAccessHandle::IsOpen() const {
+ MOZ_ASSERT(mState != State::Initial);
+
+ return mState == State::Open;
+}
+
+bool FileSystemSyncAccessHandle::IsClosing() const {
+ MOZ_ASSERT(mState != State::Initial);
+
+ return mState == State::Closing;
+}
+
+bool FileSystemSyncAccessHandle::IsClosed() const {
+ MOZ_ASSERT(mState != State::Initial);
+
+ return mState == State::Closed;
+}
+
+RefPtr<BoolPromise> FileSystemSyncAccessHandle::BeginClose() {
+ MOZ_ASSERT(IsOpen());
+
+ mState = State::Closing;
+
+ InvokeAsync(mIOTaskQueue, __func__,
+ [selfHolder = fs::TargetPtrHolder(this)]() {
+ if (selfHolder->mStream) {
+ LOG(("%p: Closing", selfHolder->mStream.get()));
+
+ selfHolder->mStream->OutputStream()->Close();
+ selfHolder->mStream = nullptr;
+ } else {
+ LOG(("Closing (no stream)"));
+
+ // If the stream was not deserialized, `mStreamParams` still
+ // contains a pre-opened file descriptor which needs to be
+ // closed here by moving `mStreamParams` to a local variable
+ // (the file descriptor will be closed for real when
+ // `streamParams` goes out of scope).
+
+ mozilla::ipc::RandomAccessStreamParams streamParams(
+ std::move(selfHolder->mStreamParams));
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(mWorkerRef->Private()->ControlEventTarget(), __func__,
+ [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue&) {
+ return self->mIOTaskQueue->BeginShutdown();
+ })
+ ->Then(
+ mWorkerRef->Private()->ControlEventTarget(), __func__,
+ [self = RefPtr(this)](const ShutdownPromise::ResolveOrRejectValue&) {
+ if (self->mControlActor) {
+ RefPtr<BoolPromise::Private> promise =
+ new BoolPromise::Private(__func__);
+
+ self->mControlActor->SendClose(
+ [promise](void_t&&) { promise->Resolve(true, __func__); },
+ [promise](const mozilla::ipc::ResponseRejectReason& aReason) {
+ fs::IPCRejectReporter(aReason);
+
+ promise->Reject(NS_ERROR_FAILURE, __func__);
+ });
+
+ return RefPtr<BoolPromise>(promise);
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(mWorkerRef->Private()->ControlEventTarget(), __func__,
+ [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue&) {
+ self->mWorkerRef = nullptr;
+
+ self->mState = State::Closed;
+
+ self->mClosePromiseHolder.ResolveIfExists(true, __func__);
+ });
+
+ return OnClose();
+}
+
+RefPtr<BoolPromise> FileSystemSyncAccessHandle::OnClose() {
+ MOZ_ASSERT(mState == State::Closing);
+
+ return mClosePromiseHolder.Ensure(__func__);
+}
+
+// WebIDL Boilerplate
+
+nsIGlobalObject* FileSystemSyncAccessHandle::GetParentObject() const {
+ return mGlobal;
+}
+
+JSObject* FileSystemSyncAccessHandle::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return FileSystemSyncAccessHandle_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// WebIDL Interface
+
+uint64_t FileSystemSyncAccessHandle::Read(
+ const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer,
+ const FileSystemReadWriteOptions& aOptions, ErrorResult& aRv) {
+ return ReadOrWrite(aBuffer, aOptions, /* aRead */ true, aRv);
+}
+
+uint64_t FileSystemSyncAccessHandle::Write(
+ const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer,
+ const FileSystemReadWriteOptions& aOptions, ErrorResult& aRv) {
+ return ReadOrWrite(aBuffer, aOptions, /* aRead */ false, aRv);
+}
+
+void FileSystemSyncAccessHandle::Truncate(uint64_t aSize, ErrorResult& aError) {
+ if (!IsOpen()) {
+ aError.ThrowInvalidStateError("SyncAccessHandle is closed");
+ return;
+ }
+
+ MOZ_ASSERT(mWorkerRef);
+
+ AutoSyncLoopHolder syncLoop(mWorkerRef->Private(), Canceling);
+
+ nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
+ syncLoop.GetSerialEventTarget();
+ QM_TRY(MOZ_TO_RESULT(syncLoopTarget), [&aError](nsresult) {
+ aError.ThrowInvalidStateError("Worker is shutting down");
+ });
+
+ InvokeAsync(
+ mIOTaskQueue, __func__,
+ [selfHolder = fs::TargetPtrHolder(this), aSize]() {
+ QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
+ CreateAndRejectBoolPromise);
+
+ LOG(("%p: Truncate to %" PRIu64, selfHolder->mStream.get(), aSize));
+ int64_t offset = 0;
+ QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->Tell(&offset)),
+ CreateAndRejectBoolPromise);
+ QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->Seek(
+ nsISeekableStream::NS_SEEK_SET, aSize)),
+ CreateAndRejectBoolPromise);
+
+ QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->SetEOF()),
+ CreateAndRejectBoolPromise);
+ // restore cursor position (clamp to end of file)
+ QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->Seek(
+ nsISeekableStream::NS_SEEK_SET,
+ std::min((uint64_t)offset, aSize))),
+ CreateAndRejectBoolPromise);
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(syncLoopTarget, __func__,
+ [this, &syncLoopTarget](
+ const BoolPromise::ResolveOrRejectValue& aValue) {
+ MOZ_ASSERT(mWorkerRef);
+
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+
+ mWorkerRef->Private()->StopSyncLoop(
+ syncLoopTarget,
+ aValue.IsResolve() ? NS_OK : aValue.RejectValue());
+ });
+
+ QM_TRY(MOZ_TO_RESULT(syncLoop.Run()),
+ [&aError](const nsresult rv) { aError.Throw(rv); });
+}
+
+uint64_t FileSystemSyncAccessHandle::GetSize(ErrorResult& aError) {
+ if (!IsOpen()) {
+ aError.ThrowInvalidStateError("SyncAccessHandle is closed");
+ return 0;
+ }
+
+ MOZ_ASSERT(mWorkerRef);
+
+ AutoSyncLoopHolder syncLoop(mWorkerRef->Private(), Canceling);
+
+ nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
+ syncLoop.GetSerialEventTarget();
+ QM_TRY(MOZ_TO_RESULT(syncLoopTarget), [&aError](nsresult) {
+ aError.ThrowInvalidStateError("Worker is shutting down");
+ return 0;
+ });
+
+ // XXX Could we somehow pass the size to `StopSyncLoop` and then get it via
+ // `QM_TRY_INSPECT(const auto& size, syncLoop.Run)` ?
+ // Could we use Result<UniquePtr<...>, nsresult> for that ?
+ int64_t size;
+
+ InvokeAsync(mIOTaskQueue, __func__,
+ [selfHolder = fs::TargetPtrHolder(this)]() {
+ QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
+ CreateAndRejectSizePromise);
+
+ nsCOMPtr<nsIFileMetadata> fileMetadata =
+ do_QueryInterface(selfHolder->mStream);
+ MOZ_ASSERT(fileMetadata);
+
+ QM_TRY_INSPECT(
+ const auto& size,
+ MOZ_TO_RESULT_INVOKE_MEMBER(fileMetadata, GetSize),
+ CreateAndRejectSizePromise);
+
+ LOG(("%p: GetSize %" PRIu64, selfHolder->mStream.get(), size));
+
+ return SizePromise::CreateAndResolve(size, __func__);
+ })
+ ->Then(syncLoopTarget, __func__,
+ [this, &syncLoopTarget,
+ &size](const Int64Promise::ResolveOrRejectValue& aValue) {
+ MOZ_ASSERT(mWorkerRef);
+
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+
+ if (aValue.IsResolve()) {
+ size = aValue.ResolveValue();
+
+ mWorkerRef->Private()->StopSyncLoop(syncLoopTarget, NS_OK);
+ } else {
+ mWorkerRef->Private()->StopSyncLoop(syncLoopTarget,
+ aValue.RejectValue());
+ }
+ });
+
+ QM_TRY(MOZ_TO_RESULT(syncLoop.Run()), [&aError](const nsresult rv) {
+ aError.Throw(rv);
+ return 0;
+ });
+
+ return size;
+}
+
+void FileSystemSyncAccessHandle::Flush(ErrorResult& aError) {
+ if (!IsOpen()) {
+ aError.ThrowInvalidStateError("SyncAccessHandle is closed");
+ return;
+ }
+
+ MOZ_ASSERT(mWorkerRef);
+
+ AutoSyncLoopHolder syncLoop(mWorkerRef->Private(), Canceling);
+
+ nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
+ syncLoop.GetSerialEventTarget();
+ QM_TRY(MOZ_TO_RESULT(syncLoopTarget), [&aError](nsresult) {
+ aError.ThrowInvalidStateError("Worker is shutting down");
+ });
+
+ InvokeAsync(mIOTaskQueue, __func__,
+ [selfHolder = fs::TargetPtrHolder(this)]() {
+ QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
+ CreateAndRejectBoolPromise);
+
+ LOG(("%p: Flush", selfHolder->mStream.get()));
+
+ QM_TRY(
+ MOZ_TO_RESULT(selfHolder->mStream->OutputStream()->Flush()),
+ CreateAndRejectBoolPromise);
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(syncLoopTarget, __func__,
+ [this, &syncLoopTarget](
+ const BoolPromise::ResolveOrRejectValue& aValue) {
+ MOZ_ASSERT(mWorkerRef);
+
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+
+ mWorkerRef->Private()->StopSyncLoop(
+ syncLoopTarget,
+ aValue.IsResolve() ? NS_OK : aValue.RejectValue());
+ });
+
+ QM_TRY(MOZ_TO_RESULT(syncLoop.Run()),
+ [&aError](const nsresult rv) { aError.Throw(rv); });
+}
+
+void FileSystemSyncAccessHandle::Close() {
+ if (!(IsOpen() || IsClosing())) {
+ return;
+ }
+
+ MOZ_ASSERT(mWorkerRef);
+
+ // Normally mWorkerRef can be used directly for stopping the sync loop, but
+ // the async close is special because mWorkerRef is cleared as part of the
+ // operation. That's why we need to use this extra strong ref to the
+ // `StrongWorkerRef`.
+ RefPtr<StrongWorkerRef> workerRef = mWorkerRef;
+
+ AutoSyncLoopHolder syncLoop(workerRef->Private(), Killing);
+
+ nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
+ syncLoop.GetSerialEventTarget();
+ MOZ_ASSERT(syncLoopTarget);
+
+ InvokeAsync(syncLoopTarget, __func__, [self = RefPtr(this)]() {
+ if (self->IsOpen()) {
+ return self->BeginClose();
+ }
+ return self->OnClose();
+ })->Then(syncLoopTarget, __func__, [&workerRef, &syncLoopTarget]() {
+ MOZ_ASSERT(workerRef);
+
+ workerRef->Private()->AssertIsOnWorkerThread();
+
+ workerRef->Private()->StopSyncLoop(syncLoopTarget, NS_OK);
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(syncLoop.Run());
+}
+
+uint64_t FileSystemSyncAccessHandle::ReadOrWrite(
+ const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer,
+ const FileSystemReadWriteOptions& aOptions, const bool aRead,
+ ErrorResult& aRv) {
+ if (!IsOpen()) {
+ aRv.ThrowInvalidStateError("SyncAccessHandle is closed");
+ return 0;
+ }
+
+ MOZ_ASSERT(mWorkerRef);
+
+ auto throwAndReturn = [&aRv](const nsresult rv) {
+ aRv.Throw(rv);
+ return 0;
+ };
+
+ const auto dataSpan = [&aBuffer]() {
+ if (aBuffer.IsArrayBuffer()) {
+ const ArrayBuffer& buffer = aBuffer.GetAsArrayBuffer();
+ buffer.ComputeState();
+ return Span{buffer.Data(), buffer.Length()};
+ }
+ MOZ_ASSERT(aBuffer.IsArrayBufferView());
+ const ArrayBufferView& buffer = aBuffer.GetAsArrayBufferView();
+ buffer.ComputeState();
+ return Span{buffer.Data(), buffer.Length()};
+ }();
+
+ CheckedInt<int64_t> offset = 0;
+ if (aOptions.mAt.WasPassed()) {
+ // Handle seek before read ('at')
+ offset = CheckedInt<int64_t>(aOptions.mAt.Value());
+ QM_TRY(MOZ_TO_RESULT(offset.isValid()), throwAndReturn);
+ }
+ AutoSyncLoopHolder syncLoop(mWorkerRef->Private(), Canceling);
+
+ nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
+ syncLoop.GetSerialEventTarget();
+ QM_TRY(MOZ_TO_RESULT(syncLoopTarget), [&aRv](nsresult) {
+ aRv.ThrowInvalidStateError("Worker is shutting down");
+ return 0;
+ });
+
+ uint64_t totalCount = 0;
+
+ InvokeAsync(
+ mIOTaskQueue, __func__,
+ [selfHolder = fs::TargetPtrHolder(this), dataSpan,
+ use_offset = aOptions.mAt.WasPassed(), offset, aRead, &totalCount]() {
+ QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
+ CreateAndRejectBoolPromise);
+
+ if (use_offset) {
+ LOG_VERBOSE(("%p: Seeking to %" PRIu64, selfHolder->mStream.get(),
+ offset.value()));
+
+ QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->Seek(
+ nsISeekableStream::NS_SEEK_SET, offset.value())),
+ CreateAndRejectBoolPromise);
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIOutputStream> outputStream;
+
+ if (aRead) {
+ LOG_VERBOSE(("%p: Reading %zu bytes", selfHolder->mStream.get(),
+ dataSpan.Length()));
+
+ inputStream = selfHolder->mStream->InputStream();
+
+ outputStream =
+ FixedBufferOutputStream::Create(AsWritableChars(dataSpan));
+ } else {
+ LOG_VERBOSE(("%p: Writing %zu bytes", selfHolder->mStream.get(),
+ dataSpan.Length()));
+
+ QM_TRY(MOZ_TO_RESULT(NS_NewByteInputStream(
+ getter_AddRefs(inputStream), AsChars(dataSpan),
+ NS_ASSIGNMENT_DEPEND)),
+ CreateAndRejectBoolPromise);
+
+ outputStream = selfHolder->mStream->OutputStream();
+ }
+
+ auto promiseHolder = MakeUnique<MozPromiseHolder<BoolPromise>>();
+ RefPtr<BoolPromise> promise = promiseHolder->Ensure(__func__);
+
+ QM_TRY(MOZ_TO_RESULT(fs::AsyncCopy(
+ inputStream, outputStream, GetCurrentSerialEventTarget(),
+ aRead ? NS_ASYNCCOPY_VIA_WRITESEGMENTS
+ : NS_ASYNCCOPY_VIA_READSEGMENTS,
+ /* aCloseSource */ !aRead, /* aCloseSink */ aRead,
+ [&totalCount](uint32_t count) { totalCount += count; },
+ [promiseHolder = std::move(promiseHolder)](nsresult rv) {
+ promiseHolder->ResolveIfExists(true, __func__);
+ })),
+ CreateAndRejectBoolPromise);
+
+ return promise;
+ })
+ ->Then(syncLoopTarget, __func__,
+ [this, &syncLoopTarget](
+ const BoolPromise::ResolveOrRejectValue& aValue) {
+ MOZ_ASSERT(mWorkerRef);
+
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+
+ mWorkerRef->Private()->StopSyncLoop(syncLoopTarget, NS_OK);
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(syncLoop.Run());
+
+ return totalCount;
+}
+
+nsresult FileSystemSyncAccessHandle::EnsureStream() {
+ if (!mStream) {
+ QM_TRY_UNWRAP(mStream, DeserializeRandomAccessStream(mStreamParams),
+ NS_ERROR_FAILURE);
+
+ mozilla::ipc::RandomAccessStreamParams streamParams(
+ std::move(mStreamParams));
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom