/* -*- 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 "fs/FileSystemRequestHandler.h"

#include "FileSystemEntryMetadataArray.h"
#include "fs/FileSystemConstants.h"
#include "mozilla/ResultVariant.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/dom/BlobImpl.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FileSystemAccessHandleChild.h"
#include "mozilla/dom/FileSystemDirectoryHandle.h"
#include "mozilla/dom/FileSystemFileHandle.h"
#include "mozilla/dom/FileSystemHandle.h"
#include "mozilla/dom/FileSystemHelpers.h"
#include "mozilla/dom/FileSystemLog.h"
#include "mozilla/dom/FileSystemManager.h"
#include "mozilla/dom/FileSystemManagerChild.h"
#include "mozilla/dom/FileSystemSyncAccessHandle.h"
#include "mozilla/dom/FileSystemWritableFileStream.h"
#include "mozilla/dom/FileSystemWritableFileStreamChild.h"
#include "mozilla/dom/IPCBlobUtils.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/dom/fs/IPCRejectReporter.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/ipc/RandomAccessStreamUtils.h"

namespace mozilla::dom::fs {

using mozilla::ipc::RejectCallback;

namespace {

void HandleFailedStatus(nsresult aError, const RefPtr<Promise>& aPromise) {
  switch (aError) {
    case NS_ERROR_FILE_ACCESS_DENIED:
      aPromise->MaybeRejectWithNotAllowedError("Permission denied");
      break;
    case NS_ERROR_FILE_NOT_FOUND:
      [[fallthrough]];
    case NS_ERROR_DOM_NOT_FOUND_ERR:
      aPromise->MaybeRejectWithNotFoundError("Entry not found");
      break;
    case NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR:
      aPromise->MaybeRejectWithInvalidModificationError("Disallowed by system");
      break;
    case NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR:
      aPromise->MaybeRejectWithNoModificationAllowedError(
          "No modification allowed");
      break;
    case NS_ERROR_DOM_TYPE_MISMATCH_ERR:
      aPromise->MaybeRejectWithTypeMismatchError("Wrong type");
      break;
    case NS_ERROR_DOM_INVALID_MODIFICATION_ERR:
      aPromise->MaybeRejectWithInvalidModificationError("Invalid modification");
      break;
    default:
      if (NS_FAILED(aError)) {
        aPromise->MaybeRejectWithUnknownError("Unknown failure");
      } else {
        aPromise->MaybeResolveWithUndefined();
      }
      break;
  }
}

bool MakeResolution(nsIGlobalObject* aGlobal,
                    FileSystemGetEntriesResponse&& aResponse,
                    const bool& /* aResult */,
                    RefPtr<FileSystemEntryMetadataArray>& aSink) {
  // TODO: Add page size to FileSystemConstants, preallocate and handle overflow
  const auto& listing = aResponse.get_FileSystemDirectoryListing();

  for (const auto& it : listing.files()) {
    aSink->AppendElement(it);
  }

  for (const auto& it : listing.directories()) {
    aSink->AppendElement(it);
  }

  return true;
}

RefPtr<FileSystemDirectoryHandle> MakeResolution(
    nsIGlobalObject* aGlobal, FileSystemGetHandleResponse&& aResponse,
    const RefPtr<FileSystemDirectoryHandle>& /* aResult */,
    RefPtr<FileSystemManager>& aManager) {
  RefPtr<FileSystemDirectoryHandle> result = new FileSystemDirectoryHandle(
      aGlobal, aManager,
      FileSystemEntryMetadata(aResponse.get_EntryId(), kRootName,
                              /* directory */ true));
  return result;
}

RefPtr<FileSystemDirectoryHandle> MakeResolution(
    nsIGlobalObject* aGlobal, FileSystemGetHandleResponse&& aResponse,
    const RefPtr<FileSystemDirectoryHandle>& /* aResult */, const Name& aName,
    RefPtr<FileSystemManager>& aManager) {
  RefPtr<FileSystemDirectoryHandle> result = new FileSystemDirectoryHandle(
      aGlobal, aManager,
      FileSystemEntryMetadata(aResponse.get_EntryId(), aName,
                              /* directory */ true));

  return result;
}

RefPtr<FileSystemFileHandle> MakeResolution(
    nsIGlobalObject* aGlobal, FileSystemGetHandleResponse&& aResponse,
    const RefPtr<FileSystemFileHandle>& /* aResult */, const Name& aName,
    RefPtr<FileSystemManager>& aManager) {
  RefPtr<FileSystemFileHandle> result = new FileSystemFileHandle(
      aGlobal, aManager,
      FileSystemEntryMetadata(aResponse.get_EntryId(), aName,
                              /* directory */ false));
  return result;
}

RefPtr<FileSystemSyncAccessHandle> MakeResolution(
    nsIGlobalObject* aGlobal, FileSystemGetAccessHandleResponse&& aResponse,
    const RefPtr<FileSystemSyncAccessHandle>& /* aReturns */,
    const FileSystemEntryMetadata& aMetadata,
    RefPtr<FileSystemManager>& aManager) {
  auto& properties = aResponse.get_FileSystemAccessHandleProperties();

  QM_TRY_UNWRAP(
      RefPtr<FileSystemSyncAccessHandle> result,
      FileSystemSyncAccessHandle::Create(
          aGlobal, aManager, std::move(properties.streamParams()),
          std::move(properties.accessHandleChildEndpoint()),
          std::move(properties.accessHandleControlChildEndpoint()), aMetadata),
      nullptr);

  return result;
}

RefPtr<File> MakeResolution(nsIGlobalObject* aGlobal,
                            FileSystemGetFileResponse&& aResponse,
                            const RefPtr<File>& /* aResult */,
                            const Name& aName,
                            RefPtr<FileSystemManager>& aManager) {
  auto& fileProperties = aResponse.get_FileSystemFileProperties();

  RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(fileProperties.file());
  MOZ_ASSERT(blobImpl);
  RefPtr<File> result = File::Create(aGlobal, blobImpl);
  return result;
}

template <class TResponse, class... Args>
void ResolveCallback(
    TResponse&& aResponse,
    RefPtr<Promise> aPromise,  // NOLINT(performance-unnecessary-value-param)
    Args&&... args) {
  MOZ_ASSERT(aPromise);
  QM_TRY(OkIf(Promise::PromiseState::Pending == aPromise->State()), QM_VOID);

  if (TResponse::Tnsresult == aResponse.type()) {
    HandleFailedStatus(aResponse.get_nsresult(), aPromise);
    return;
  }

  auto resolution = MakeResolution(aPromise->GetParentObject(),
                                   std::forward<TResponse>(aResponse),
                                   std::forward<Args>(args)...);
  if (!resolution) {
    aPromise->MaybeRejectWithUnknownError("Could not complete request");
    return;
  }

  aPromise->MaybeResolve(resolution);
}

template <>
void ResolveCallback(
    FileSystemRemoveEntryResponse&& aResponse,
    RefPtr<Promise> aPromise) {  // NOLINT(performance-unnecessary-value-param)
  MOZ_ASSERT(aPromise);
  QM_TRY(OkIf(Promise::PromiseState::Pending == aPromise->State()), QM_VOID);

  if (FileSystemRemoveEntryResponse::Tvoid_t == aResponse.type()) {
    aPromise->MaybeResolveWithUndefined();
    return;
  }

  MOZ_ASSERT(FileSystemRemoveEntryResponse::Tnsresult == aResponse.type());
  HandleFailedStatus(aResponse.get_nsresult(), aPromise);
}

template <>
void ResolveCallback(
    FileSystemMoveEntryResponse&& aResponse,
    RefPtr<Promise> aPromise,  // NOLINT(performance-unnecessary-value-param)
    FileSystemEntryMetadata* const& aEntry, const Name& aName) {
  MOZ_ASSERT(aPromise);
  QM_TRY(OkIf(Promise::PromiseState::Pending == aPromise->State()), QM_VOID);

  if (FileSystemMoveEntryResponse::TEntryId == aResponse.type()) {
    if (aEntry) {
      aEntry->entryId() = std::move(aResponse.get_EntryId());
      aEntry->entryName() = aName;
    }

    aPromise->MaybeResolveWithUndefined();
    return;
  }
  MOZ_ASSERT(FileSystemMoveEntryResponse::Tnsresult == aResponse.type());
  const auto& status = aResponse.get_nsresult();
  MOZ_ASSERT(NS_FAILED(status));
  HandleFailedStatus(status, aPromise);
}

template <>
void ResolveCallback(FileSystemResolveResponse&& aResponse,
                     // NOLINTNEXTLINE(performance-unnecessary-value-param)
                     RefPtr<Promise> aPromise) {
  MOZ_ASSERT(aPromise);
  QM_TRY(OkIf(Promise::PromiseState::Pending == aPromise->State()), QM_VOID);

  if (FileSystemResolveResponse::Tnsresult == aResponse.type()) {
    HandleFailedStatus(aResponse.get_nsresult(), aPromise);
    return;
  }

  auto& maybePath = aResponse.get_MaybeFileSystemPath();
  if (maybePath.isSome()) {
    aPromise->MaybeResolve(maybePath.value().path());
    return;
  }

  // Spec says if there is no parent/child relationship, return null
  aPromise->MaybeResolve(JS::NullHandleValue);
}

// NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name)
template <>
void ResolveCallback(FileSystemGetWritableFileStreamResponse&& aResponse,
                     // NOLINTNEXTLINE(performance-unnecessary-value-param)
                     RefPtr<Promise> aPromise,
                     RefPtr<FileSystemManager>& aManager,
                     const FileSystemEntryMetadata& aMetadata) {
  using CreatePromise = FileSystemWritableFileStream::CreatePromise;
  MOZ_ASSERT(aPromise);
  QM_TRY(OkIf(Promise::PromiseState::Pending == aPromise->State()), QM_VOID);

  if (FileSystemGetWritableFileStreamResponse::Tnsresult == aResponse.type()) {
    HandleFailedStatus(aResponse.get_nsresult(), aPromise);
    return;
  }

  auto& properties = aResponse.get_FileSystemWritableFileStreamProperties();

  auto* const actor = static_cast<FileSystemWritableFileStreamChild*>(
      properties.writableFileStream().AsChild().get());

  mozilla::ipc::RandomAccessStreamParams params =
      std::move(properties.streamParams());

  FileSystemEntryMetadata metadata = aMetadata;

  WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate();
  RefPtr<StrongWorkerRef> buildWorkerRef =
      workerPrivate ? StrongWorkerRef::Create(
                          workerPrivate, "FileSystemWritableFileStream::Create")
                    : nullptr;

  FileSystemWritableFileStream::Create(aPromise->GetParentObject(), aManager,
                                       actor, std::move(params),
                                       std::move(metadata))
      ->Then(GetCurrentSerialEventTarget(), __func__,
             [buildWorkerRef,
              aPromise](CreatePromise::ResolveOrRejectValue&& aValue) {
               if (aValue.IsResolve()) {
                 RefPtr<FileSystemWritableFileStream> stream =
                     aValue.ResolveValue();

                 if (buildWorkerRef) {
                   RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
                       buildWorkerRef->Private(),
                       "FileSystemWritableFileStream", [stream]() {
                         if (stream->IsOpen()) {
                           // We don't need the promise, we just begin the
                           // closing process.
                           Unused << stream->BeginAbort();
                         }
                       });

                   stream->SetWorkerRef(std::move(workerRef));
                 }

                 aPromise->MaybeResolve(stream);
                 return;
               }

               if (aValue.IsReject()) {
                 aPromise->MaybeReject(aValue.RejectValue());
                 return;
               }

               aPromise->MaybeRejectWithUnknownError(
                   "Promise chain resolution is empty");
             });
}

template <class TResponse, class TReturns, class... Args,
          std::enable_if_t<std::is_same<TReturns, void>::value, bool> = true>
mozilla::ipc::ResolveCallback<TResponse> SelectResolveCallback(
    RefPtr<Promise> aPromise,  // NOLINT(performance-unnecessary-value-param)
    Args&&... args) {
  using TOverload = void (*)(TResponse&&, RefPtr<Promise>, Args...);
  return static_cast<std::function<void(TResponse&&)>>(
      // NOLINTNEXTLINE(modernize-avoid-bind)
      std::bind(static_cast<TOverload>(ResolveCallback), std::placeholders::_1,
                aPromise, std::forward<Args>(args)...));
}

template <class TResponse, class TReturns, class... Args,
          std::enable_if_t<!std::is_same<TReturns, void>::value, bool> = true>
mozilla::ipc::ResolveCallback<TResponse> SelectResolveCallback(
    RefPtr<Promise> aPromise,  // NOLINT(performance-unnecessary-value-param)
    Args&&... args) {
  using TOverload =
      void (*)(TResponse&&, RefPtr<Promise>, const TReturns&, Args...);
  return static_cast<std::function<void(TResponse&&)>>(
      // NOLINTNEXTLINE(modernize-avoid-bind)
      std::bind(static_cast<TOverload>(ResolveCallback), std::placeholders::_1,
                aPromise, TReturns(), std::forward<Args>(args)...));
}

void RejectCallback(
    RefPtr<Promise> aPromise,  // NOLINT(performance-unnecessary-value-param)
    mozilla::ipc::ResponseRejectReason aReason) {
  IPCRejectReporter(aReason);
  QM_TRY(OkIf(Promise::PromiseState::Pending == aPromise->State()), QM_VOID);
  aPromise->MaybeRejectWithUndefined();
}

mozilla::ipc::RejectCallback GetRejectCallback(
    RefPtr<Promise> aPromise) {  // NOLINT(performance-unnecessary-value-param)
  return static_cast<mozilla::ipc::RejectCallback>(
      // NOLINTNEXTLINE(modernize-avoid-bind)
      std::bind(RejectCallback, aPromise, std::placeholders::_1));
}

struct BeginRequestFailureCallback {
  explicit BeginRequestFailureCallback(RefPtr<Promise> aPromise)
      : mPromise(std::move(aPromise)) {}

  void operator()(nsresult aRv) const {
    if (aRv == NS_ERROR_DOM_SECURITY_ERR) {
      mPromise->MaybeRejectWithSecurityError(
          "Security error when calling GetDirectory");
      return;
    }
    mPromise->MaybeRejectWithUnknownError("Could not create actor");
  }

  RefPtr<Promise> mPromise;
};

}  // namespace

void FileSystemRequestHandler::GetRootHandle(
    RefPtr<FileSystemManager>
        aManager,              // NOLINT(performance-unnecessary-value-param)
    RefPtr<Promise> aPromise,  // NOLINT(performance-unnecessary-value-param)
    ErrorResult& aError) {
  MOZ_ASSERT(aManager);
  MOZ_ASSERT(aPromise);
  LOG(("GetRootHandle"));

  if (aManager->IsShutdown()) {
    aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
    return;
  }

  aManager->BeginRequest(
      [onResolve = SelectResolveCallback<FileSystemGetHandleResponse,
                                         RefPtr<FileSystemDirectoryHandle>>(
           aPromise, aManager),
       onReject = GetRejectCallback(aPromise)](const auto& actor) mutable {
        actor->SendGetRootHandle(std::move(onResolve), std::move(onReject));
      },
      BeginRequestFailureCallback(aPromise));
}

void FileSystemRequestHandler::GetDirectoryHandle(
    RefPtr<FileSystemManager>& aManager,
    const FileSystemChildMetadata& aDirectory, bool aCreate,
    RefPtr<Promise> aPromise,  // NOLINT(performance-unnecessary-value-param)
    ErrorResult& aError) {
  MOZ_ASSERT(aManager);
  MOZ_ASSERT(!aDirectory.parentId().IsEmpty());
  MOZ_ASSERT(aPromise);
  LOG(("GetDirectoryHandle"));

  if (aManager->IsShutdown()) {
    aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
    return;
  }

  if (!IsValidName(aDirectory.childName())) {
    aPromise->MaybeRejectWithTypeError("Invalid directory name");
    return;
  }

  aManager->BeginRequest(
      [request = FileSystemGetHandleRequest(aDirectory, aCreate),
       onResolve = SelectResolveCallback<FileSystemGetHandleResponse,
                                         RefPtr<FileSystemDirectoryHandle>>(
           aPromise, aDirectory.childName(), aManager),
       onReject = GetRejectCallback(aPromise)](const auto& actor) mutable {
        actor->SendGetDirectoryHandle(request, std::move(onResolve),
                                      std::move(onReject));
      },
      BeginRequestFailureCallback(aPromise));
}

void FileSystemRequestHandler::GetFileHandle(
    RefPtr<FileSystemManager>& aManager, const FileSystemChildMetadata& aFile,
    bool aCreate,
    RefPtr<Promise> aPromise,  // NOLINT(performance-unnecessary-value-param)
    ErrorResult& aError) {
  MOZ_ASSERT(aManager);
  MOZ_ASSERT(!aFile.parentId().IsEmpty());
  MOZ_ASSERT(aPromise);
  LOG(("GetFileHandle"));

  if (aManager->IsShutdown()) {
    aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
    return;
  }

  if (!IsValidName(aFile.childName())) {
    aPromise->MaybeRejectWithTypeError("Invalid filename");
    return;
  }

  aManager->BeginRequest(
      [request = FileSystemGetHandleRequest(aFile, aCreate),
       onResolve = SelectResolveCallback<FileSystemGetHandleResponse,
                                         RefPtr<FileSystemFileHandle>>(
           aPromise, aFile.childName(), aManager),
       onReject = GetRejectCallback(aPromise)](const auto& actor) mutable {
        actor->SendGetFileHandle(request, std::move(onResolve),
                                 std::move(onReject));
      },
      BeginRequestFailureCallback(aPromise));
}

void FileSystemRequestHandler::GetAccessHandle(
    RefPtr<FileSystemManager>& aManager, const FileSystemEntryMetadata& aFile,
    const RefPtr<Promise>& aPromise, ErrorResult& aError) {
  MOZ_ASSERT(aManager);
  MOZ_ASSERT(aPromise);
  LOG(("GetAccessHandle %s", NS_ConvertUTF16toUTF8(aFile.entryName()).get()));

  if (aManager->IsShutdown()) {
    aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
    return;
  }

  aManager->BeginRequest(
      [request = FileSystemGetAccessHandleRequest(aFile.entryId()),
       onResolve = SelectResolveCallback<FileSystemGetAccessHandleResponse,
                                         RefPtr<FileSystemSyncAccessHandle>>(
           aPromise, aFile, aManager),
       onReject = GetRejectCallback(aPromise)](const auto& actor) mutable {
        actor->SendGetAccessHandle(request, std::move(onResolve),
                                   std::move(onReject));
      },
      BeginRequestFailureCallback(aPromise));
}

void FileSystemRequestHandler::GetWritable(RefPtr<FileSystemManager>& aManager,
                                           const FileSystemEntryMetadata& aFile,
                                           bool aKeepData,
                                           const RefPtr<Promise>& aPromise,
                                           ErrorResult& aError) {
  MOZ_ASSERT(aManager);
  MOZ_ASSERT(aPromise);
  LOG(("GetWritable %s keep %d", NS_ConvertUTF16toUTF8(aFile.entryName()).get(),
       aKeepData));

  // XXX This should be removed once bug 1798513 is fixed.
  if (!StaticPrefs::dom_fs_writable_file_stream_enabled()) {
    aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
    return;
  }

  if (aManager->IsShutdown()) {
    aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
    return;
  }

  aManager->BeginRequest(
      [request = FileSystemGetWritableRequest(aFile.entryId(), aKeepData),
       onResolve =
           SelectResolveCallback<FileSystemGetWritableFileStreamResponse, void>(
               aPromise, aManager, aFile),
       onReject = GetRejectCallback(aPromise)](const auto& actor) mutable {
        actor->SendGetWritable(request, std::move(onResolve),
                               std::move(onReject));
      },
      [promise = aPromise](const auto&) {
        promise->MaybeRejectWithUnknownError("Could not create actor");
      });
}

void FileSystemRequestHandler::GetFile(
    RefPtr<FileSystemManager>& aManager, const FileSystemEntryMetadata& aFile,
    RefPtr<Promise> aPromise,  // NOLINT(performance-unnecessary-value-param)
    ErrorResult& aError) {
  MOZ_ASSERT(aManager);
  MOZ_ASSERT(!aFile.entryId().IsEmpty());
  MOZ_ASSERT(aPromise);
  LOG(("GetFile %s", NS_ConvertUTF16toUTF8(aFile.entryName()).get()));

  if (aManager->IsShutdown()) {
    aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
    return;
  }

  aManager->BeginRequest(
      [request = FileSystemGetFileRequest(aFile.entryId()),
       onResolve =
           SelectResolveCallback<FileSystemGetFileResponse, RefPtr<File>>(
               aPromise, aFile.entryName(), aManager),
       onReject = GetRejectCallback(aPromise)](const auto& actor) mutable {
        actor->SendGetFile(request, std::move(onResolve), std::move(onReject));
      },
      BeginRequestFailureCallback(aPromise));
}

void FileSystemRequestHandler::GetEntries(
    RefPtr<FileSystemManager>& aManager, const EntryId& aDirectory,
    PageNumber aPage,
    RefPtr<Promise> aPromise,  // NOLINT(performance-unnecessary-value-param)
    RefPtr<FileSystemEntryMetadataArray>& aSink, ErrorResult& aError) {
  MOZ_ASSERT(aManager);
  MOZ_ASSERT(!aDirectory.IsEmpty());
  MOZ_ASSERT(aPromise);
  LOG(("GetEntries, page %u", aPage));

  if (aManager->IsShutdown()) {
    aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
    return;
  }

  aManager->BeginRequest(
      [request = FileSystemGetEntriesRequest(aDirectory, aPage),
       onResolve = SelectResolveCallback<FileSystemGetEntriesResponse, bool>(
           aPromise, aSink),
       onReject = GetRejectCallback(aPromise)](const auto& actor) mutable {
        actor->SendGetEntries(request, std::move(onResolve),
                              std::move(onReject));
      },
      BeginRequestFailureCallback(aPromise));
}

void FileSystemRequestHandler::RemoveEntry(
    RefPtr<FileSystemManager>& aManager, const FileSystemChildMetadata& aEntry,
    bool aRecursive,
    RefPtr<Promise> aPromise,  // NOLINT(performance-unnecessary-value-param)
    ErrorResult& aError) {
  MOZ_ASSERT(aManager);
  MOZ_ASSERT(!aEntry.parentId().IsEmpty());
  MOZ_ASSERT(aPromise);
  LOG(("RemoveEntry"));

  if (aManager->IsShutdown()) {
    aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
    return;
  }

  if (!IsValidName(aEntry.childName())) {
    aPromise->MaybeRejectWithTypeError("Invalid name");
    return;
  }

  aManager->BeginRequest(
      [request = FileSystemRemoveEntryRequest(aEntry, aRecursive),
       onResolve =
           SelectResolveCallback<FileSystemRemoveEntryResponse, void>(aPromise),
       onReject = GetRejectCallback(aPromise)](const auto& actor) mutable {
        actor->SendRemoveEntry(request, std::move(onResolve),
                               std::move(onReject));
      },
      BeginRequestFailureCallback(aPromise));
}

void FileSystemRequestHandler::MoveEntry(
    RefPtr<FileSystemManager>& aManager, FileSystemHandle* aHandle,
    FileSystemEntryMetadata* const aEntry,
    const FileSystemChildMetadata& aNewEntry,
    RefPtr<Promise> aPromise,  // NOLINT(performance-unnecessary-value-param)
    ErrorResult& aError) {
  MOZ_ASSERT(aEntry);
  MOZ_ASSERT(!aEntry->entryId().IsEmpty());
  MOZ_ASSERT(aPromise);
  LOG(("MoveEntry"));

  if (aManager->IsShutdown()) {
    aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
    return;
  }

  // reject invalid names: empty, path separators, current & parent directories
  if (!IsValidName(aNewEntry.childName())) {
    aPromise->MaybeRejectWithTypeError("Invalid name");
    return;
  }

  aManager->BeginRequest(
      [request = FileSystemMoveEntryRequest(*aEntry, aNewEntry),
       onResolve = SelectResolveCallback<FileSystemMoveEntryResponse, void>(
           aPromise, aEntry, aNewEntry.childName()),
       onReject = GetRejectCallback(aPromise)](const auto& actor) mutable {
        actor->SendMoveEntry(request, std::move(onResolve),
                             std::move(onReject));
      },
      BeginRequestFailureCallback(aPromise));
}

void FileSystemRequestHandler::RenameEntry(
    RefPtr<FileSystemManager>& aManager, FileSystemHandle* aHandle,
    FileSystemEntryMetadata* const aEntry, const Name& aName,
    RefPtr<Promise> aPromise,  // NOLINT(performance-unnecessary-value-param)
    ErrorResult& aError) {
  MOZ_ASSERT(aEntry);
  MOZ_ASSERT(!aEntry->entryId().IsEmpty());
  MOZ_ASSERT(aPromise);
  LOG(("RenameEntry"));

  if (aManager->IsShutdown()) {
    aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
    return;
  }

  // reject invalid names: empty, path separators, current & parent directories
  if (!IsValidName(aName)) {
    aPromise->MaybeRejectWithTypeError("Invalid name");
    return;
  }

  aManager->BeginRequest(
      [request = FileSystemRenameEntryRequest(*aEntry, aName),
       onResolve = SelectResolveCallback<FileSystemMoveEntryResponse, void>(
           aPromise, aEntry, aName),
       onReject = GetRejectCallback(aPromise)](const auto& actor) mutable {
        actor->SendRenameEntry(request, std::move(onResolve),
                               std::move(onReject));
      },
      BeginRequestFailureCallback(aPromise));
}

void FileSystemRequestHandler::Resolve(
    RefPtr<FileSystemManager>& aManager,
    // NOLINTNEXTLINE(performance-unnecessary-value-param)
    const FileSystemEntryPair& aEndpoints, RefPtr<Promise> aPromise,
    ErrorResult& aError) {
  MOZ_ASSERT(aManager);
  MOZ_ASSERT(!aEndpoints.parentId().IsEmpty());
  MOZ_ASSERT(!aEndpoints.childId().IsEmpty());
  MOZ_ASSERT(aPromise);
  LOG(("Resolve"));

  if (aManager->IsShutdown()) {
    aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
    return;
  }

  aManager->BeginRequest(
      [request = FileSystemResolveRequest(aEndpoints),
       onResolve =
           SelectResolveCallback<FileSystemResolveResponse, void>(aPromise),
       onReject = GetRejectCallback(aPromise)](const auto& actor) mutable {
        actor->SendResolve(request, std::move(onResolve), std::move(onReject));
      },
      BeginRequestFailureCallback(aPromise));
}

}  // namespace mozilla::dom::fs
