/* -*- 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
