dom/indexedDB/ActorsChild.cpp
author ffxbld <ffxbld@mozilla.com>
Mon, 03 Aug 2020 14:13:18 +0000
changeset 543106 255b4f5888e9e9cdd40f59fec969af247859d76a
parent 538691 bb6e164924381be127634741162f98542365c3ab
permissions -rw-r--r--
No Bug, mozilla-central repo-update HSTS HPKP remote-settings - a=repo-update r=RyanVM Differential Revision: https://phabricator.services.mozilla.com/D85756

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

#include <type_traits>

#include "BackgroundChildImpl.h"
#include "IDBDatabase.h"
#include "IDBEvents.h"
#include "IDBFactory.h"
#include "IDBFileHandle.h"
#include "IDBIndex.h"
#include "IDBMutableFile.h"
#include "IDBObjectStore.h"
#include "IDBRequest.h"
#include "IDBTransaction.h"
#include "IndexedDatabase.h"
#include "IndexedDatabaseInlines.h"
#include "js/Array.h"  // JS::NewArrayObject, JS::SetArrayLength
#include "js/Date.h"   // JS::NewDateObject, JS::TimeClip
#include <mozIRemoteLazyInputStream.h>
#include "mozilla/ArrayAlgorithm.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/CycleCollectedJSRuntime.h"
#include "mozilla/Maybe.h"
#include "mozilla/SnappyUncompressInputStream.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileChild.h"
#include "mozilla/dom/IPCBlobUtils.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/Encoding.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/TaskQueue.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsIAsyncInputStream.h"
#include "nsIBFCacheEntry.h"
#include "mozilla/dom/Document.h"
#include "nsIEventTarget.h"
#include "nsIFileStreams.h"
#include "nsNetCID.h"
#include "nsPIDOMWindow.h"
#include "nsThreadUtils.h"
#include "nsTraceRefcnt.h"
#include "PermissionRequestBase.h"
#include "ProfilerHelpers.h"
#include "ReportInternalError.h"
#include "ThreadLocal.h"

#ifdef DEBUG
#  include "IndexedDatabaseManager.h"
#endif

#define GC_ON_IPC_MESSAGES 0

#if defined(DEBUG) || GC_ON_IPC_MESSAGES

#  include "js/GCAPI.h"
#  include "nsJSEnvironment.h"

#  define BUILD_GC_ON_IPC_MESSAGES

#endif  // DEBUG || GC_ON_IPC_MESSAGES

namespace mozilla {

using ipc::PrincipalInfo;

namespace dom {

namespace indexedDB {

namespace {

/*******************************************************************************
 * Constants
 ******************************************************************************/

const uint32_t kFileCopyBufferSize = 32768;

}  // namespace

/*******************************************************************************
 * ThreadLocal
 ******************************************************************************/

ThreadLocal::ThreadLocal(const nsID& aBackgroundChildLoggingId)
    : mLoggingInfo(aBackgroundChildLoggingId, 1, -1, 1),
      mLoggingIdString(aBackgroundChildLoggingId) {
  MOZ_COUNT_CTOR(mozilla::dom::indexedDB::ThreadLocal);
}

ThreadLocal::~ThreadLocal() {
  MOZ_COUNT_DTOR(mozilla::dom::indexedDB::ThreadLocal);
}

/*******************************************************************************
 * Helpers
 ******************************************************************************/

namespace {

void MaybeCollectGarbageOnIPCMessage() {
#ifdef BUILD_GC_ON_IPC_MESSAGES
  static const bool kCollectGarbageOnIPCMessages =
#  if GC_ON_IPC_MESSAGES
      true;
#  else
      false;
#  endif  // GC_ON_IPC_MESSAGES

  if (!kCollectGarbageOnIPCMessages) {
    return;
  }

  static bool haveWarnedAboutGC = false;
  static bool haveWarnedAboutNonMainThread = false;

  if (!haveWarnedAboutGC) {
    haveWarnedAboutGC = true;
    NS_WARNING("IndexedDB child actor GC debugging enabled!");
  }

  if (!NS_IsMainThread()) {
    if (!haveWarnedAboutNonMainThread) {
      haveWarnedAboutNonMainThread = true;
      NS_WARNING("Don't know how to GC on a non-main thread yet.");
    }
    return;
  }

  nsJSContext::GarbageCollectNow(JS::GCReason::DOM_IPC);
  nsJSContext::CycleCollectNow();
#endif  // BUILD_GC_ON_IPC_MESSAGES
}

class MOZ_STACK_CLASS AutoSetCurrentTransaction final {
  typedef mozilla::ipc::BackgroundChildImpl BackgroundChildImpl;

  Maybe<IDBTransaction&> const mTransaction;
  Maybe<IDBTransaction&> mPreviousTransaction;
  ThreadLocal* mThreadLocal;

 public:
  AutoSetCurrentTransaction(const AutoSetCurrentTransaction&) = delete;
  AutoSetCurrentTransaction(AutoSetCurrentTransaction&&) = delete;
  AutoSetCurrentTransaction& operator=(const AutoSetCurrentTransaction&) =
      delete;
  AutoSetCurrentTransaction& operator=(AutoSetCurrentTransaction&&) = delete;

  explicit AutoSetCurrentTransaction(Maybe<IDBTransaction&> aTransaction)
      : mTransaction(aTransaction),
        mPreviousTransaction(),
        mThreadLocal(nullptr) {
    if (aTransaction) {
      BackgroundChildImpl::ThreadLocal* threadLocal =
          BackgroundChildImpl::GetThreadLocalForCurrentThread();
      MOZ_ASSERT(threadLocal);

      // Hang onto this for resetting later.
      mThreadLocal = threadLocal->mIndexedDBThreadLocal.get();
      MOZ_ASSERT(mThreadLocal);

      // Save the current value.
      mPreviousTransaction = mThreadLocal->MaybeCurrentTransactionRef();

      // Set the new value.
      mThreadLocal->SetCurrentTransaction(aTransaction);
    }
  }

  ~AutoSetCurrentTransaction() {
    MOZ_ASSERT_IF(mThreadLocal, mTransaction);
    MOZ_ASSERT_IF(mThreadLocal,
                  ReferenceEquals(mThreadLocal->MaybeCurrentTransactionRef(),
                                  mTransaction));

    if (mThreadLocal) {
      // Reset old value.
      mThreadLocal->SetCurrentTransaction(mPreviousTransaction);
    }
  }
};

template <typename T>
void SetResultAndDispatchSuccessEvent(
    const NotNull<RefPtr<IDBRequest>>& aRequest,
    const SafeRefPtr<IDBTransaction>& aTransaction, T& aPtr,
    RefPtr<Event> aEvent = nullptr);

namespace detail {
void DispatchSuccessEvent(const NotNull<RefPtr<IDBRequest>>& aRequest,
                          const SafeRefPtr<IDBTransaction>& aTransaction,
                          const RefPtr<Event>& aEvent);

template <class T>
std::enable_if_t<std::is_same_v<T, IDBDatabase> ||
                     std::is_same_v<T, IDBCursor> ||
                     std::is_same_v<T, IDBMutableFile>,
                 nsresult>
GetResult(JSContext* aCx, T* aDOMObject, JS::MutableHandle<JS::Value> aResult) {
  if (!aDOMObject) {
    aResult.setNull();
    return NS_OK;
  }

  const bool ok = GetOrCreateDOMReflector(aCx, aDOMObject, aResult);
  if (NS_WARN_IF(!ok)) {
    IDB_REPORT_INTERNAL_ERR();
    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
  }

  return NS_OK;
}

nsresult GetResult(JSContext* aCx, const JS::Handle<JS::Value>* aValue,
                   JS::MutableHandle<JS::Value> aResult) {
  aResult.set(*aValue);
  return NS_OK;
}

nsresult GetResult(JSContext* aCx, const uint64_t* aValue,
                   JS::MutableHandle<JS::Value> aResult) {
  aResult.set(JS::NumberValue(*aValue));
  return NS_OK;
}

nsresult GetResult(JSContext* aCx, StructuredCloneReadInfoChild&& aCloneInfo,
                   JS::MutableHandle<JS::Value> aResult) {
  const bool ok =
      IDBObjectStore::DeserializeValue(aCx, std::move(aCloneInfo), aResult);

  if (NS_WARN_IF(!ok)) {
    return NS_ERROR_DOM_DATA_CLONE_ERR;
  }

  return NS_OK;
}

nsresult GetResult(JSContext* aCx, StructuredCloneReadInfoChild* aCloneInfo,
                   JS::MutableHandle<JS::Value> aResult) {
  return GetResult(aCx, std::move(*aCloneInfo), aResult);
}

nsresult GetResult(JSContext* aCx,
                   nsTArray<StructuredCloneReadInfoChild>* aCloneInfos,
                   JS::MutableHandle<JS::Value> aResult) {
  JS::Rooted<JSObject*> array(aCx, JS::NewArrayObject(aCx, 0));
  if (NS_WARN_IF(!array)) {
    IDB_REPORT_INTERNAL_ERR();
    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
  }

  if (!aCloneInfos->IsEmpty()) {
    const uint32_t count = aCloneInfos->Length();

    if (NS_WARN_IF(!JS::SetArrayLength(aCx, array, count))) {
      IDB_REPORT_INTERNAL_ERR();
      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
    }

    for (uint32_t index = 0; index < count; index++) {
      auto& cloneInfo = aCloneInfos->ElementAt(index);

      JS::Rooted<JS::Value> value(aCx);

      const nsresult rv = GetResult(aCx, std::move(cloneInfo), &value);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      if (NS_WARN_IF(
              !JS_DefineElement(aCx, array, index, value, JSPROP_ENUMERATE))) {
        IDB_REPORT_INTERNAL_ERR();
        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
      }
    }
  }

  aResult.setObject(*array);
  return NS_OK;
}

nsresult GetResult(JSContext* aCx, const Key* aKey,
                   JS::MutableHandle<JS::Value> aResult) {
  const nsresult rv = aKey->ToJSVal(aCx, aResult);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

nsresult GetResult(JSContext* aCx, const nsTArray<Key>* aKeys,
                   JS::MutableHandle<JS::Value> aResult) {
  JS::Rooted<JSObject*> array(aCx, JS::NewArrayObject(aCx, 0));
  if (NS_WARN_IF(!array)) {
    IDB_REPORT_INTERNAL_ERR();
    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
  }

  if (!aKeys->IsEmpty()) {
    const uint32_t count = aKeys->Length();

    if (NS_WARN_IF(!JS::SetArrayLength(aCx, array, count))) {
      IDB_REPORT_INTERNAL_ERR();
      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
    }

    for (uint32_t index = 0; index < count; index++) {
      const Key& key = aKeys->ElementAt(index);
      MOZ_ASSERT(!key.IsUnset());

      JS::Rooted<JS::Value> value(aCx);

      const nsresult rv = GetResult(aCx, &key, &value);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      if (NS_WARN_IF(
              !JS_DefineElement(aCx, array, index, value, JSPROP_ENUMERATE))) {
        IDB_REPORT_INTERNAL_ERR();
        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
      }
    }
  }

  aResult.setObject(*array);
  return NS_OK;
}
}  // namespace detail

class PermissionRequestMainProcessHelper final : public PermissionRequestBase {
  BackgroundFactoryRequestChild* mActor;
  SafeRefPtr<IDBFactory> mFactory;

 public:
  PermissionRequestMainProcessHelper(BackgroundFactoryRequestChild* aActor,
                                     SafeRefPtr<IDBFactory> aFactory,
                                     Element* aOwnerElement,
                                     nsIPrincipal* aPrincipal)
      : PermissionRequestBase(aOwnerElement, aPrincipal),
        mActor(aActor),
        mFactory(std::move(aFactory)) {
    MOZ_ASSERT(aActor);
    MOZ_ASSERT(mFactory);
    aActor->AssertIsOnOwningThread();
  }

 protected:
  ~PermissionRequestMainProcessHelper() = default;

 private:
  virtual void OnPromptComplete(PermissionValue aPermissionValue) override;
};

auto DeserializeStructuredCloneFiles(
    IDBDatabase* aDatabase,
    const nsTArray<SerializedStructuredCloneFile>& aSerializedFiles,
    bool aForPreprocess) {
  MOZ_ASSERT_IF(aForPreprocess, aSerializedFiles.Length() == 1);

  return TransformIntoNewArray(
      aSerializedFiles,
      [aForPreprocess, &database = *aDatabase](
          const auto& serializedFile) -> StructuredCloneFileChild {
        MOZ_ASSERT_IF(
            aForPreprocess,
            serializedFile.type() == StructuredCloneFileBase::eStructuredClone);

        const BlobOrMutableFile& blobOrMutableFile = serializedFile.file();

        switch (serializedFile.type()) {
          case StructuredCloneFileBase::eBlob: {
            MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::TIPCBlob);

            const IPCBlob& ipcBlob = blobOrMutableFile.get_IPCBlob();

            const RefPtr<BlobImpl> blobImpl =
                IPCBlobUtils::Deserialize(ipcBlob);
            MOZ_ASSERT(blobImpl);

            RefPtr<Blob> blob =
                Blob::Create(database.GetOwnerGlobal(), blobImpl);
            MOZ_ASSERT(blob);

            return {StructuredCloneFileBase::eBlob, std::move(blob)};
          }

          case StructuredCloneFileBase::eMutableFile: {
            MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t ||
                       blobOrMutableFile.type() ==
                           BlobOrMutableFile::TPBackgroundMutableFileChild);

            switch (blobOrMutableFile.type()) {
              case BlobOrMutableFile::Tnull_t:
                return StructuredCloneFileChild{
                    StructuredCloneFileBase::eMutableFile};

              case BlobOrMutableFile::TPBackgroundMutableFileChild: {
                auto* const actor = static_cast<BackgroundMutableFileChild*>(
                    blobOrMutableFile.get_PBackgroundMutableFileChild());
                MOZ_ASSERT(actor);

                actor->EnsureDOMObject();

                auto* const mutableFile =
                    static_cast<IDBMutableFile*>(actor->GetDOMObject());
                MOZ_ASSERT(mutableFile);

                auto file = StructuredCloneFileChild{mutableFile};

                actor->ReleaseDOMObject();

                return file;
              }

              default:
                MOZ_CRASH("Should never get here!");
            }
          }

          case StructuredCloneFileBase::eStructuredClone: {
            if (aForPreprocess) {
              MOZ_ASSERT(blobOrMutableFile.type() ==
                         BlobOrMutableFile::TIPCBlob);

              const IPCBlob& ipcBlob = blobOrMutableFile.get_IPCBlob();

              const RefPtr<BlobImpl> blobImpl =
                  IPCBlobUtils::Deserialize(ipcBlob);
              MOZ_ASSERT(blobImpl);

              RefPtr<Blob> blob =
                  Blob::Create(database.GetOwnerGlobal(), blobImpl);
              MOZ_ASSERT(blob);

              return {StructuredCloneFileBase::eStructuredClone,
                      std::move(blob)};
            }
            MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t);

            return StructuredCloneFileChild{
                StructuredCloneFileBase::eStructuredClone};
          }

          case StructuredCloneFileBase::eWasmBytecode:
          case StructuredCloneFileBase::eWasmCompiled: {
            MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t);

            return StructuredCloneFileChild{serializedFile.type()};

            // Don't set mBlob, support for storing WebAssembly.Modules has been
            // removed in bug 1469395. Support for de-serialization of
            // WebAssembly.Modules has been removed in bug 1561876. Full removal
            // is tracked in bug 1487479.
          }

          default:
            MOZ_CRASH("Should never get here!");
        }
      });
}

JSStructuredCloneData PreprocessingNotSupported() {
  MOZ_CRASH("Preprocessing not (yet) supported!");
}

template <typename PreprocessInfoAccessor>
StructuredCloneReadInfoChild DeserializeStructuredCloneReadInfo(
    SerializedStructuredCloneReadInfo&& aSerialized,
    IDBDatabase* const aDatabase,
    PreprocessInfoAccessor preprocessInfoAccessor) {
  // XXX Make this a class invariant of SerializedStructuredCloneReadInfo.
  MOZ_ASSERT_IF(aSerialized.hasPreprocessInfo(),
                0 == aSerialized.data().data.Size());
  return {aSerialized.hasPreprocessInfo() ? preprocessInfoAccessor()
                                          : std::move(aSerialized.data().data),
          DeserializeStructuredCloneFiles(aDatabase, aSerialized.files(),
                                          /* aForPreprocess */ false),
          aDatabase};
}

// TODO: Remove duplication between DispatchErrorEvent and DispatchSucessEvent.

void DispatchErrorEvent(
    MovingNotNull<RefPtr<IDBRequest>> aRequest, nsresult aErrorCode,
    const SafeRefPtr<IDBTransaction>& aTransaction = nullptr,
    RefPtr<Event> aEvent = nullptr) {
  const RefPtr<IDBRequest> request = std::move(aRequest);

  request->AssertIsOnOwningThread();
  MOZ_ASSERT(NS_FAILED(aErrorCode));
  MOZ_ASSERT(NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_DOM_INDEXEDDB);

  AUTO_PROFILER_LABEL("IndexedDB:DispatchErrorEvent", DOM);

  request->SetError(aErrorCode);

  if (!aEvent) {
    // Make an error event and fire it at the target.
    aEvent = CreateGenericEvent(request, nsDependentString(kErrorEventType),
                                eDoesBubble, eCancelable);
  }
  MOZ_ASSERT(aEvent);

  // XXX This is redundant if we are called from
  // DispatchSuccessEvent.
  Maybe<AutoSetCurrentTransaction> asct;
  if (aTransaction) {
    asct.emplace(SomeRef(*aTransaction));
  }

  if (aTransaction && aTransaction->IsInactive()) {
    aTransaction->TransitionToActive();
  }

  if (aTransaction) {
    IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
        "Firing %s event with error 0x%x", "%s (0x%" PRIx32 ")",
        aTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
        IDB_LOG_STRINGIFY(aEvent, kErrorEventType),
        static_cast<uint32_t>(aErrorCode));
  } else {
    IDB_LOG_MARK_CHILD_REQUEST("Firing %s event with error 0x%x",
                               "%s (0x%" PRIx32 ")",
                               request->LoggingSerialNumber(),
                               IDB_LOG_STRINGIFY(aEvent, kErrorEventType),
                               static_cast<uint32_t>(aErrorCode));
  }

  IgnoredErrorResult rv;
  const bool doDefault =
      request->DispatchEvent(*aEvent, CallerType::System, rv);
  if (NS_WARN_IF(rv.Failed())) {
    return;
  }

  MOZ_ASSERT(!aTransaction || aTransaction->IsActive() ||
             aTransaction->IsAborted() ||
             aTransaction->WasExplicitlyCommitted());

  if (aTransaction && aTransaction->IsActive()) {
    aTransaction->TransitionToInactive();

    // Do not abort the transaction here if this request is failed due to the
    // abortion of its transaction to ensure that the correct error cause of
    // the abort event be set in IDBTransaction::FireCompleteOrAbortEvents()
    // later.
    if (aErrorCode != NS_ERROR_DOM_INDEXEDDB_ABORT_ERR) {
      WidgetEvent* const internalEvent = aEvent->WidgetEventPtr();
      MOZ_ASSERT(internalEvent);

      if (internalEvent->mFlags.mExceptionWasRaised) {
        aTransaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
      } else if (doDefault) {
        aTransaction->Abort(request);
      }
    }
  }
}

template <typename T>
void SetResultAndDispatchSuccessEvent(
    const NotNull<RefPtr<IDBRequest>>& aRequest,
    const SafeRefPtr<IDBTransaction>& aTransaction, T& aPtr,
    RefPtr<Event> aEvent) {
  const auto autoTransaction = AutoSetCurrentTransaction{
      aTransaction ? SomeRef(*aTransaction) : Nothing()};

  AUTO_PROFILER_LABEL("IndexedDB:SetResultAndDispatchSuccessEvent", DOM);

  aRequest->AssertIsOnOwningThread();

  if (aTransaction && aTransaction->IsAborted()) {
    DispatchErrorEvent(aRequest, aTransaction->AbortCode(), aTransaction);
    return;
  }

  if (!aEvent) {
    aEvent =
        CreateGenericEvent(aRequest.get(), nsDependentString(kSuccessEventType),
                           eDoesNotBubble, eNotCancelable);
  }
  MOZ_ASSERT(aEvent);

  aRequest->SetResult(
      [&aPtr](JSContext* aCx, JS::MutableHandle<JS::Value> aResult) {
        MOZ_ASSERT(aCx);
        return detail::GetResult(aCx, &aPtr, aResult);
      });

  detail::DispatchSuccessEvent(aRequest, aTransaction, aEvent);
}

namespace detail {
void DispatchSuccessEvent(const NotNull<RefPtr<IDBRequest>>& aRequest,
                          const SafeRefPtr<IDBTransaction>& aTransaction,
                          const RefPtr<Event>& aEvent) {
  if (aTransaction && aTransaction->IsInactive()) {
    aTransaction->TransitionToActive();
  }

  if (aTransaction) {
    IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
        "Firing %s event", "%s", aTransaction->LoggingSerialNumber(),
        aRequest->LoggingSerialNumber(),
        IDB_LOG_STRINGIFY(aEvent, kSuccessEventType));
  } else {
    IDB_LOG_MARK_CHILD_REQUEST("Firing %s event", "%s",
                               aRequest->LoggingSerialNumber(),
                               IDB_LOG_STRINGIFY(aEvent, kSuccessEventType));
  }

  MOZ_ASSERT_IF(aTransaction && !aTransaction->WasExplicitlyCommitted(),
                aTransaction->IsActive() && !aTransaction->IsAborted());

  IgnoredErrorResult rv;
  aRequest->DispatchEvent(*aEvent, rv);
  if (NS_WARN_IF(rv.Failed())) {
    return;
  }

  WidgetEvent* const internalEvent = aEvent->WidgetEventPtr();
  MOZ_ASSERT(internalEvent);

  if (aTransaction && aTransaction->IsActive()) {
    aTransaction->TransitionToInactive();

    if (internalEvent->mFlags.mExceptionWasRaised) {
      aTransaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
    } else {
      // To handle upgrade transaction.
      aTransaction->CommitIfNotStarted();
    }
  }
}
}  // namespace detail

PRFileDesc* GetFileDescriptorFromStream(nsIInputStream* aStream) {
  MOZ_ASSERT(aStream);

  const nsCOMPtr<nsIFileMetadata> fileMetadata = do_QueryInterface(aStream);
  if (NS_WARN_IF(!fileMetadata)) {
    return nullptr;
  }

  PRFileDesc* fileDesc;
  const nsresult rv = fileMetadata->GetFileDescriptor(&fileDesc);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return nullptr;
  }

  MOZ_ASSERT(fileDesc);

  return fileDesc;
}

class WorkerPermissionChallenge;

// This class calles WorkerPermissionChallenge::OperationCompleted() in the
// worker thread.
class WorkerPermissionOperationCompleted final : public WorkerControlRunnable {
  RefPtr<WorkerPermissionChallenge> mChallenge;

 public:
  WorkerPermissionOperationCompleted(WorkerPrivate* aWorkerPrivate,
                                     WorkerPermissionChallenge* aChallenge)
      : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
        mChallenge(aChallenge) {
    MOZ_ASSERT(NS_IsMainThread());
  }

  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override;
};

// This class used to do prompting in the main thread and main process.
class WorkerPermissionRequest final : public PermissionRequestBase {
  RefPtr<WorkerPermissionChallenge> mChallenge;

 public:
  WorkerPermissionRequest(Element* aElement, nsIPrincipal* aPrincipal,
                          WorkerPermissionChallenge* aChallenge)
      : PermissionRequestBase(aElement, aPrincipal), mChallenge(aChallenge) {
    MOZ_ASSERT(XRE_IsParentProcess());
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(aChallenge);
  }

 private:
  ~WorkerPermissionRequest() { MOZ_ASSERT(NS_IsMainThread()); }

  virtual void OnPromptComplete(PermissionValue aPermissionValue) override;
};

class WorkerPermissionChallenge final : public Runnable {
 public:
  WorkerPermissionChallenge(WorkerPrivate* aWorkerPrivate,
                            BackgroundFactoryRequestChild* aActor,
                            SafeRefPtr<IDBFactory> aFactory,
                            PrincipalInfo&& aPrincipalInfo)
      : Runnable("indexedDB::WorkerPermissionChallenge"),
        mWorkerPrivate(aWorkerPrivate),
        mActor(aActor),
        mFactory(std::move(aFactory)),
        mPrincipalInfo(std::move(aPrincipalInfo)) {
    MOZ_ASSERT(mWorkerPrivate);
    MOZ_ASSERT(aActor);
    MOZ_ASSERT(mFactory);
    mWorkerPrivate->AssertIsOnWorkerThread();
  }

  bool Dispatch() {
    mWorkerPrivate->AssertIsOnWorkerThread();
    if (NS_WARN_IF(!mWorkerPrivate->ModifyBusyCountFromWorker(true))) {
      return false;
    }

    if (NS_WARN_IF(NS_FAILED(mWorkerPrivate->DispatchToMainThread(this)))) {
      mWorkerPrivate->ModifyBusyCountFromWorker(false);
      return false;
    }

    return true;
  }

  NS_IMETHOD
  Run() override {
    const bool completed = RunInternal();
    if (completed) {
      OperationCompleted();
    }

    return NS_OK;
  }

  void OperationCompleted() {
    if (NS_IsMainThread()) {
      const RefPtr<WorkerPermissionOperationCompleted> runnable =
          new WorkerPermissionOperationCompleted(mWorkerPrivate, this);

      MOZ_ALWAYS_TRUE(runnable->Dispatch());
      return;
    }

    MOZ_ASSERT(mActor);
    mActor->AssertIsOnOwningThread();

    MaybeCollectGarbageOnIPCMessage();

    const SafeRefPtr<IDBFactory> factory = std::move(mFactory);
    Unused << factory;  // XXX see Bug 1605075

    mActor->SendPermissionRetry();
    mActor = nullptr;

    mWorkerPrivate->AssertIsOnWorkerThread();
    mWorkerPrivate->ModifyBusyCountFromWorker(false);
  }

 private:
  bool RunInternal() {
    MOZ_ASSERT(NS_IsMainThread());

    // Walk up to our containing page
    WorkerPrivate* wp = mWorkerPrivate;
    while (wp->GetParent()) {
      wp = wp->GetParent();
    }

    nsPIDOMWindowInner* const window = wp->GetWindow();
    if (!window) {
      return true;
    }

    auto principalOrErr =
        mozilla::ipc::PrincipalInfoToPrincipal(mPrincipalInfo);
    if (NS_WARN_IF(principalOrErr.isErr())) {
      return true;
    }

    const nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();

    if (XRE_IsParentProcess()) {
      const nsCOMPtr<Element> ownerElement =
          do_QueryInterface(window->GetChromeEventHandler());
      if (NS_WARN_IF(!ownerElement)) {
        return true;
      }

      RefPtr<WorkerPermissionRequest> helper =
          new WorkerPermissionRequest(ownerElement, principal, this);

      PermissionRequestBase::PermissionValue permission;
      if (NS_WARN_IF(NS_FAILED(helper->PromptIfNeeded(&permission)))) {
        return true;
      }

      MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed ||
                 permission == PermissionRequestBase::kPermissionDenied ||
                 permission == PermissionRequestBase::kPermissionPrompt);

      return permission != PermissionRequestBase::kPermissionPrompt;
    }

    BrowserChild* browserChild = BrowserChild::GetFrom(window);
    MOZ_ASSERT(browserChild);

    RefPtr<WorkerPermissionChallenge> self(this);
    browserChild->SendIndexedDBPermissionRequest(principal)->Then(
        GetCurrentSerialEventTarget(), __func__,
        [self](const uint32_t& aPermission) { self->OperationCompleted(); },
        [](const mozilla::ipc::ResponseRejectReason) {});
    return false;
  }

 private:
  WorkerPrivate* const mWorkerPrivate;
  BackgroundFactoryRequestChild* mActor;
  SafeRefPtr<IDBFactory> mFactory;
  const PrincipalInfo mPrincipalInfo;
};

void WorkerPermissionRequest::OnPromptComplete(
    PermissionValue aPermissionValue) {
  MOZ_ASSERT(NS_IsMainThread());
  mChallenge->OperationCompleted();
}

bool WorkerPermissionOperationCompleted::WorkerRun(
    JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
  aWorkerPrivate->AssertIsOnWorkerThread();
  mChallenge->OperationCompleted();
  return true;
}

class MOZ_STACK_CLASS AutoSetCurrentFileHandle final {
  typedef mozilla::ipc::BackgroundChildImpl BackgroundChildImpl;

  IDBFileHandle* const mFileHandle;
  IDBFileHandle* mPreviousFileHandle;
  IDBFileHandle** mThreadLocalSlot;

 public:
  explicit AutoSetCurrentFileHandle(IDBFileHandle* aFileHandle)
      : mFileHandle(aFileHandle),
        mPreviousFileHandle(nullptr),
        mThreadLocalSlot(nullptr) {
    if (aFileHandle) {
      BackgroundChildImpl::ThreadLocal* threadLocal =
          BackgroundChildImpl::GetThreadLocalForCurrentThread();
      MOZ_ASSERT(threadLocal);

      // Hang onto this location for resetting later.
      mThreadLocalSlot = &threadLocal->mCurrentFileHandle;

      // Save the current value.
      mPreviousFileHandle = *mThreadLocalSlot;

      // Set the new value.
      *mThreadLocalSlot = aFileHandle;
    }
  }

  ~AutoSetCurrentFileHandle() {
    MOZ_ASSERT_IF(mThreadLocalSlot, mFileHandle);
    MOZ_ASSERT_IF(mThreadLocalSlot, *mThreadLocalSlot == mFileHandle);

    if (mThreadLocalSlot) {
      // Reset old value.
      *mThreadLocalSlot = mPreviousFileHandle;
    }
  }

  IDBFileHandle* FileHandle() const { return mFileHandle; }
};

template <typename T>
void SetFileHandleResultAndDispatchSuccessEvent(
    const RefPtr<IDBFileRequest>& aFileRequest,
    const RefPtr<IDBFileHandle>& aFileHandle, T* aPtr);

namespace detail {
nsresult GetFileHandleResult(const RefPtr<IDBFileRequest>& aFileRequest,
                             JSContext* aCx, const nsCString* aString,
                             JS::MutableHandle<JS::Value> aResult) {
  const nsCString& data = *aString;

  nsresult rv;

  if (!aFileRequest->HasEncoding()) {
    JS::Rooted<JSObject*> arrayBuffer(aCx);
    rv = nsContentUtils::CreateArrayBuffer(aCx, data, arrayBuffer.address());
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
    }

    aResult.setObject(*arrayBuffer);
    return NS_OK;
  }

  // Try the API argument.
  const Encoding* encoding = Encoding::ForLabel(aFileRequest->GetEncoding());
  if (!encoding) {
    // API argument failed. Since we are dealing with a file system file,
    // we don't have a meaningful type attribute for the blob available,
    // so proceeding to the next step, which is defaulting to UTF-8.
    encoding = UTF_8_ENCODING;
  }

  nsString tmpString;
  Tie(rv, encoding) = encoding->Decode(data, tmpString);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
  }

  if (NS_WARN_IF(!xpc::StringToJsval(aCx, tmpString, aResult))) {
    return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
  }

  return NS_OK;
}

nsresult GetFileHandleResult(const RefPtr<IDBFileRequest>& /*aFileRequest*/,
                             JSContext* aCx,
                             const FileRequestMetadata* aMetadata,
                             JS::MutableHandle<JS::Value> aResult) {
  JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
  if (NS_WARN_IF(!obj)) {
    return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
  }

  const Maybe<uint64_t>& size = aMetadata->size();
  if (size.isSome()) {
    JS::Rooted<JS::Value> number(aCx, JS_NumberValue(size.value()));

    if (NS_WARN_IF(!JS_DefineProperty(aCx, obj, "size", number, 0))) {
      return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
    }
  }

  const Maybe<int64_t>& lastModified = aMetadata->lastModified();
  if (lastModified.isSome()) {
    JS::Rooted<JSObject*> date(
        aCx, JS::NewDateObject(aCx, JS::TimeClip(lastModified.value())));
    if (NS_WARN_IF(!date)) {
      return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
    }

    if (NS_WARN_IF(!JS_DefineProperty(aCx, obj, "lastModified", date, 0))) {
      return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
    }
  }

  aResult.setObject(*obj);
  return NS_OK;
}

nsresult GetFileHandleResult(const RefPtr<IDBFileRequest>& /*aFileRequest*/,
                             JSContext* aCx,
                             const JS::Handle<JS::Value>* aValue,
                             JS::MutableHandle<JS::Value> aResult) {
  aResult.set(*aValue);
  return NS_OK;
}
}  // namespace detail

void DispatchFileHandleErrorEvent(IDBFileRequest* aFileRequest,
                                  nsresult aErrorCode,
                                  IDBFileHandle* aFileHandle) {
  MOZ_ASSERT(aFileRequest);
  aFileRequest->AssertIsOnOwningThread();
  MOZ_ASSERT(NS_FAILED(aErrorCode));
  MOZ_ASSERT(NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_DOM_FILEHANDLE);
  MOZ_ASSERT(aFileHandle);

  const RefPtr<IDBFileRequest> fileRequest = aFileRequest;
  const RefPtr<IDBFileHandle> fileHandle = aFileHandle;

  AutoSetCurrentFileHandle ascfh(aFileHandle);

  fileRequest->FireError(aErrorCode);

  MOZ_ASSERT(fileHandle->IsOpen() || fileHandle->IsAborted());
}

template <typename T>
void SetFileHandleResultAndDispatchSuccessEvent(
    const RefPtr<IDBFileRequest>& aFileRequest,
    const RefPtr<IDBFileHandle>& aFileHandle, T* aPtr) {
  MOZ_ASSERT(aFileRequest);
  MOZ_ASSERT(aFileHandle);
  MOZ_ASSERT(aPtr);

  auto autoFileHandle = AutoSetCurrentFileHandle{aFileHandle};

  aFileRequest->AssertIsOnOwningThread();

  if (aFileHandle->IsAborted()) {
    aFileRequest->FireError(NS_ERROR_DOM_FILEHANDLE_ABORT_ERR);
    return;
  }

  MOZ_ASSERT(aFileHandle->IsOpen());

  aFileRequest->SetResult(
      [aFileRequest, aPtr](JSContext* aCx,
                           JS::MutableHandle<JS::Value> aResult) {
        return detail::GetFileHandleResult(aFileRequest, aCx, aPtr, aResult);
      });

  MOZ_ASSERT(aFileHandle->IsOpen() || aFileHandle->IsAborted());
}

auto GetKeyOperator(const IDBCursorDirection aDirection) {
  switch (aDirection) {
    case IDBCursorDirection::Next:
    case IDBCursorDirection::Nextunique:
      return &Key::operator>=;
    case IDBCursorDirection::Prev:
    case IDBCursorDirection::Prevunique:
      return &Key::operator<=;
    default:
      MOZ_CRASH("Should never get here.");
  }
}

// Does not need to be threadsafe since this only runs on one thread, but
// inheriting from CancelableRunnable is easy.
template <typename T>
class DelayedActionRunnable final : public CancelableRunnable {
  using ActionFunc = void (T::*)();

  T* mActor;
  RefPtr<IDBRequest> mRequest;
  ActionFunc mActionFunc;

 public:
  explicit DelayedActionRunnable(T* aActor, ActionFunc aActionFunc)
      : CancelableRunnable("indexedDB::DelayedActionRunnable"),
        mActor(aActor),
        mRequest(aActor->GetRequest()),
        mActionFunc(aActionFunc) {
    MOZ_ASSERT(aActor);
    aActor->AssertIsOnOwningThread();
    MOZ_ASSERT(mRequest);
    MOZ_ASSERT(mActionFunc);
  }

 private:
  ~DelayedActionRunnable() = default;

  NS_DECL_NSIRUNNABLE
  nsresult Cancel() override;
};

}  // namespace

/*******************************************************************************
 * Actor class declarations
 ******************************************************************************/

// CancelableRunnable is used to make workers happy.
class BackgroundRequestChild::PreprocessHelper final
    : public CancelableRunnable,
      public nsIInputStreamCallback,
      public nsIFileMetadataCallback {
  enum class State {
    // Just created on the owning thread, dispatched to the thread pool. Next
    // step is either Finishing if stream was ready to be read or
    // WaitingForStreamReady if the stream is not ready.
    Initial,

    // Waiting for stream to be ready on a thread pool thread. Next state is
    // Finishing.
    WaitingForStreamReady,

    // Waiting to finish/finishing on the owning thread. Next step is Completed.
    Finishing,

    // All done.
    Completed
  };

  const nsCOMPtr<nsIEventTarget> mOwningEventTarget;
  RefPtr<TaskQueue> mTaskQueue;
  nsCOMPtr<nsIInputStream> mStream;
  UniquePtr<JSStructuredCloneData> mCloneData;
  BackgroundRequestChild* mActor;
  const uint32_t mCloneDataIndex;
  nsresult mResultCode;
  State mState;

 public:
  PreprocessHelper(uint32_t aCloneDataIndex, BackgroundRequestChild* aActor)
      : CancelableRunnable(
            "indexedDB::BackgroundRequestChild::PreprocessHelper"),
        mOwningEventTarget(aActor->GetActorEventTarget()),
        mActor(aActor),
        mCloneDataIndex(aCloneDataIndex),
        mResultCode(NS_OK),
        mState(State::Initial) {
    AssertIsOnOwningThread();
    MOZ_ASSERT(aActor);
    aActor->AssertIsOnOwningThread();
  }

  bool IsOnOwningThread() const {
    MOZ_ASSERT(mOwningEventTarget);

    bool current;
    return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(&current)) &&
           current;
  }

  void AssertIsOnOwningThread() const { MOZ_ASSERT(IsOnOwningThread()); }

  void ClearActor() {
    AssertIsOnOwningThread();

    mActor = nullptr;
  }

  nsresult Init(const StructuredCloneFileChild& aFile);

  nsresult Dispatch();

 private:
  ~PreprocessHelper() {
    MOZ_ASSERT(mState == State::Initial || mState == State::Completed);

    if (mTaskQueue) {
      mTaskQueue->BeginShutdown();
    }
  }

  nsresult Start();

  nsresult ProcessStream();

  void Finish();

  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_NSIRUNNABLE
  NS_DECL_NSIINPUTSTREAMCALLBACK
  NS_DECL_NSIFILEMETADATACALLBACK
};

/*******************************************************************************
 * Local class implementations
 ******************************************************************************/

void PermissionRequestMainProcessHelper::OnPromptComplete(
    PermissionValue aPermissionValue) {
  MOZ_ASSERT(mActor);
  mActor->AssertIsOnOwningThread();

  MaybeCollectGarbageOnIPCMessage();

  mActor->SendPermissionRetry();

  mActor = nullptr;
  mFactory = nullptr;
}

/*******************************************************************************
 * BackgroundRequestChildBase
 ******************************************************************************/

BackgroundRequestChildBase::BackgroundRequestChildBase(
    MovingNotNull<RefPtr<IDBRequest>> aRequest)
    : mRequest(std::move(aRequest)) {
  mRequest->AssertIsOnOwningThread();

  MOZ_COUNT_CTOR(indexedDB::BackgroundRequestChildBase);
}

BackgroundRequestChildBase::~BackgroundRequestChildBase() {
  AssertIsOnOwningThread();

  MOZ_COUNT_DTOR(indexedDB::BackgroundRequestChildBase);
}

#ifdef DEBUG

void BackgroundRequestChildBase::AssertIsOnOwningThread() const {
  mRequest->AssertIsOnOwningThread();
}

#endif  // DEBUG

/*******************************************************************************
 * BackgroundFactoryChild
 ******************************************************************************/

BackgroundFactoryChild::BackgroundFactoryChild(IDBFactory& aFactory)
    : mFactory(&aFactory) {
  AssertIsOnOwningThread();
  mFactory->AssertIsOnOwningThread();

  MOZ_COUNT_CTOR(indexedDB::BackgroundFactoryChild);
}

BackgroundFactoryChild::~BackgroundFactoryChild() {
  MOZ_COUNT_DTOR(indexedDB::BackgroundFactoryChild);
}

void BackgroundFactoryChild::SendDeleteMeInternal() {
  AssertIsOnOwningThread();

  if (mFactory) {
    mFactory->ClearBackgroundActor();
    mFactory = nullptr;

    MOZ_ALWAYS_TRUE(PBackgroundIDBFactoryChild::SendDeleteMe());
  }
}

void BackgroundFactoryChild::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();

  MaybeCollectGarbageOnIPCMessage();

  if (mFactory) {
    mFactory->ClearBackgroundActor();
#ifdef DEBUG
    mFactory = nullptr;
#endif
  }
}

PBackgroundIDBFactoryRequestChild*
BackgroundFactoryChild::AllocPBackgroundIDBFactoryRequestChild(
    const FactoryRequestParams& aParams) {
  MOZ_CRASH(
      "PBackgroundIDBFactoryRequestChild actors should be manually "
      "constructed!");
}

bool BackgroundFactoryChild::DeallocPBackgroundIDBFactoryRequestChild(
    PBackgroundIDBFactoryRequestChild* aActor) {
  MOZ_ASSERT(aActor);

  delete static_cast<BackgroundFactoryRequestChild*>(aActor);
  return true;
}

PBackgroundIDBDatabaseChild*
BackgroundFactoryChild::AllocPBackgroundIDBDatabaseChild(
    const DatabaseSpec& aSpec, PBackgroundIDBFactoryRequestChild* aRequest) {
  AssertIsOnOwningThread();

  auto* const request = static_cast<BackgroundFactoryRequestChild*>(aRequest);
  MOZ_ASSERT(request);

  return new BackgroundDatabaseChild(aSpec, request);
}

bool BackgroundFactoryChild::DeallocPBackgroundIDBDatabaseChild(
    PBackgroundIDBDatabaseChild* aActor) {
  MOZ_ASSERT(aActor);

  delete static_cast<BackgroundDatabaseChild*>(aActor);
  return true;
}

mozilla::ipc::IPCResult
BackgroundFactoryChild::RecvPBackgroundIDBDatabaseConstructor(
    PBackgroundIDBDatabaseChild* aActor, const DatabaseSpec& aSpec,
    PBackgroundIDBFactoryRequestChild* aRequest) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aActor);
  MOZ_ASSERT(aActor->GetActorEventTarget(),
             "The event target shall be inherited from its manager actor.");

  return IPC_OK();
}

/*******************************************************************************
 * BackgroundFactoryRequestChild
 ******************************************************************************/

BackgroundFactoryRequestChild::BackgroundFactoryRequestChild(
    SafeRefPtr<IDBFactory> aFactory,
    MovingNotNull<RefPtr<IDBOpenDBRequest>> aOpenRequest, bool aIsDeleteOp,
    uint64_t aRequestedVersion)
    : BackgroundRequestChildBase(std::move(aOpenRequest)),
      mFactory(std::move(aFactory)),
      mDatabaseActor(nullptr),
      mRequestedVersion(aRequestedVersion),
      mIsDeleteOp(aIsDeleteOp) {
  // Can't assert owning thread here because IPDL has not yet set our manager!
  MOZ_ASSERT(mFactory);
  mFactory->AssertIsOnOwningThread();

  MOZ_COUNT_CTOR(indexedDB::BackgroundFactoryRequestChild);
}

BackgroundFactoryRequestChild::~BackgroundFactoryRequestChild() {
  MOZ_COUNT_DTOR(indexedDB::BackgroundFactoryRequestChild);
}

NotNull<IDBOpenDBRequest*> BackgroundFactoryRequestChild::GetOpenDBRequest()
    const {
  AssertIsOnOwningThread();

  // XXX NotNull might provide something to encapsulate this
  return WrapNotNullUnchecked(
      static_cast<IDBOpenDBRequest*>(mRequest.get().get()));
}

void BackgroundFactoryRequestChild::SetDatabaseActor(
    BackgroundDatabaseChild* aActor) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(!aActor || !mDatabaseActor);

  mDatabaseActor = aActor;
}

bool BackgroundFactoryRequestChild::HandleResponse(nsresult aResponse) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(NS_FAILED(aResponse));
  MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB);

  mRequest->Reset();

  DispatchErrorEvent(mRequest, aResponse);

  if (mDatabaseActor) {
    mDatabaseActor->ReleaseDOMObject();
    MOZ_ASSERT(!mDatabaseActor);
  }

  return true;
}

bool BackgroundFactoryRequestChild::HandleResponse(
    const OpenDatabaseRequestResponse& aResponse) {
  AssertIsOnOwningThread();

  mRequest->Reset();

  auto* databaseActor =
      static_cast<BackgroundDatabaseChild*>(aResponse.databaseChild());
  MOZ_ASSERT(databaseActor);

  NotNull<IDBDatabase*> database = [this, databaseActor] {
    IDBDatabase* database = databaseActor->GetDOMObject();
    if (!database) {
      Unused << this;

      databaseActor->EnsureDOMObject();
      MOZ_ASSERT(mDatabaseActor);

      database = databaseActor->GetDOMObject();
      MOZ_ASSERT(database);

      MOZ_ASSERT(!database->IsClosed());
    }

    return WrapNotNullUnchecked(database);
  }();

  MOZ_ASSERT(mDatabaseActor == databaseActor);

  if (database->IsClosed()) {
    // If the database was closed already, which is only possible if we fired an
    // "upgradeneeded" event, then we shouldn't fire a "success" event here.
    // Instead we fire an error event with AbortErr.
    DispatchErrorEvent(mRequest, NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
  } else {
    SetResultAndDispatchSuccessEvent(mRequest, nullptr, *database);
  }

  databaseActor->ReleaseDOMObject();
  MOZ_ASSERT(!mDatabaseActor);

  return true;
}

bool BackgroundFactoryRequestChild::HandleResponse(
    const DeleteDatabaseRequestResponse& aResponse) {
  AssertIsOnOwningThread();

  RefPtr<Event> successEvent = IDBVersionChangeEvent::Create(
      mRequest.get(), nsDependentString(kSuccessEventType),
      aResponse.previousVersion());
  MOZ_ASSERT(successEvent);

  SetResultAndDispatchSuccessEvent(mRequest, nullptr, JS::UndefinedHandleValue,
                                   std::move(successEvent));

  MOZ_ASSERT(!mDatabaseActor);

  return true;
}

void BackgroundFactoryRequestChild::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();

  MaybeCollectGarbageOnIPCMessage();

  if (aWhy != Deletion) {
    GetOpenDBRequest()->NoteComplete();
  }
}

mozilla::ipc::IPCResult BackgroundFactoryRequestChild::Recv__delete__(
    const FactoryRequestResponse& aResponse) {
  AssertIsOnOwningThread();

  MaybeCollectGarbageOnIPCMessage();

  bool result;

  switch (aResponse.type()) {
    case FactoryRequestResponse::Tnsresult:
      result = HandleResponse(aResponse.get_nsresult());
      break;

    case FactoryRequestResponse::TOpenDatabaseRequestResponse:
      result = HandleResponse(aResponse.get_OpenDatabaseRequestResponse());
      break;

    case FactoryRequestResponse::TDeleteDatabaseRequestResponse:
      result = HandleResponse(aResponse.get_DeleteDatabaseRequestResponse());
      break;

    default:
      MOZ_CRASH("Unknown response type!");
  }

  auto request = GetOpenDBRequest();

  request->NoteComplete();

  if (NS_WARN_IF(!result)) {
    return IPC_FAIL_NO_REASON(this);
  }

  return IPC_OK();
}

mozilla::ipc::IPCResult BackgroundFactoryRequestChild::RecvPermissionChallenge(
    PrincipalInfo&& aPrincipalInfo) {
  AssertIsOnOwningThread();

  MaybeCollectGarbageOnIPCMessage();

  if (!NS_IsMainThread()) {
    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
    MOZ_ASSERT(workerPrivate);
    workerPrivate->AssertIsOnWorkerThread();

    RefPtr<WorkerPermissionChallenge> challenge = new WorkerPermissionChallenge(
        workerPrivate, this, mFactory.clonePtr(), std::move(aPrincipalInfo));
    if (!challenge->Dispatch()) {
      return IPC_FAIL_NO_REASON(this);
    }
    return IPC_OK();
  }

  auto principalOrErr = mozilla::ipc::PrincipalInfoToPrincipal(aPrincipalInfo);
  if (NS_WARN_IF(principalOrErr.isErr())) {
    return IPC_FAIL_NO_REASON(this);
  }
  nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();

  if (XRE_IsParentProcess()) {
    nsCOMPtr<nsIGlobalObject> global = mFactory->GetParentObject();
    nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
    MOZ_ASSERT(window);

    nsCOMPtr<Element> ownerElement =
        do_QueryInterface(window->GetChromeEventHandler());
    if (NS_WARN_IF(!ownerElement)) {
      // If this fails, the page was navigated. Fail the permission check by
      // forcing an immediate retry.
      if (!SendPermissionRetry()) {
        return IPC_FAIL_NO_REASON(this);
      }
      return IPC_OK();
    }

    RefPtr<PermissionRequestMainProcessHelper> helper =
        new PermissionRequestMainProcessHelper(this, mFactory.clonePtr(),
                                               ownerElement, principal);

    PermissionRequestBase::PermissionValue permission;
    if (NS_WARN_IF(NS_FAILED(helper->PromptIfNeeded(&permission)))) {
      return IPC_FAIL_NO_REASON(this);
    }

    MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed ||
               permission == PermissionRequestBase::kPermissionDenied ||
               permission == PermissionRequestBase::kPermissionPrompt);

    if (permission != PermissionRequestBase::kPermissionPrompt) {
      SendPermissionRetry();
    }
    return IPC_OK();
  }

  RefPtr<BrowserChild> browserChild = mFactory->GetBrowserChild();
  MOZ_ASSERT(browserChild);

  browserChild->SendIndexedDBPermissionRequest(principal)->Then(
      GetCurrentSerialEventTarget(), __func__,
      [this](const uint32_t& aPermission) {
        this->AssertIsOnOwningThread();
        MaybeCollectGarbageOnIPCMessage();
        this->SendPermissionRetry();
      },
      [](const mozilla::ipc::ResponseRejectReason) {});

  return IPC_OK();
}

mozilla::ipc::IPCResult BackgroundFactoryRequestChild::RecvBlocked(
    const uint64_t aCurrentVersion) {
  AssertIsOnOwningThread();

  MaybeCollectGarbageOnIPCMessage();

  const nsDependentString type(kBlockedEventType);

  RefPtr<Event> blockedEvent;
  if (mIsDeleteOp) {
    blockedEvent =
        IDBVersionChangeEvent::Create(mRequest.get(), type, aCurrentVersion);
    MOZ_ASSERT(blockedEvent);
  } else {
    blockedEvent = IDBVersionChangeEvent::Create(
        mRequest.get(), type, aCurrentVersion, mRequestedVersion);
    MOZ_ASSERT(blockedEvent);
  }

  RefPtr<IDBRequest> kungFuDeathGrip = mRequest;

  IDB_LOG_MARK_CHILD_REQUEST("Firing \"blocked\" event", "\"blocked\"",
                             kungFuDeathGrip->LoggingSerialNumber());

  IgnoredErrorResult rv;
  kungFuDeathGrip->DispatchEvent(*blockedEvent, rv);
  if (rv.Failed()) {
    NS_WARNING("Failed to dispatch event!");
  }

  return IPC_OK();
}

/*******************************************************************************
 * BackgroundDatabaseChild
 ******************************************************************************/

BackgroundDatabaseChild::BackgroundDatabaseChild(
    const DatabaseSpec& aSpec, BackgroundFactoryRequestChild* aOpenRequestActor)
    : mSpec(MakeUnique<DatabaseSpec>(aSpec)),
      mOpenRequestActor(aOpenRequestActor),
      mDatabase(nullptr) {
  // Can't assert owning thread here because IPDL has not yet set our manager!
  MOZ_ASSERT(aOpenRequestActor);

  MOZ_COUNT_CTOR(indexedDB::BackgroundDatabaseChild);
}

BackgroundDatabaseChild::~BackgroundDatabaseChild() {
  MOZ_COUNT_DTOR(indexedDB::BackgroundDatabaseChild);
}

#ifdef DEBUG

void BackgroundDatabaseChild::AssertIsOnOwningThread() const {
  static_cast<BackgroundFactoryChild*>(Manager())->AssertIsOnOwningThread();
}

#endif  // DEBUG

void BackgroundDatabaseChild::SendDeleteMeInternal() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(!mTemporaryStrongDatabase);
  MOZ_ASSERT(!mOpenRequestActor);

  if (mDatabase) {
    mDatabase->ClearBackgroundActor();
    mDatabase = nullptr;

    MOZ_ALWAYS_TRUE(PBackgroundIDBDatabaseChild::SendDeleteMe());
  }
}

void BackgroundDatabaseChild::EnsureDOMObject() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mOpenRequestActor);

  if (mTemporaryStrongDatabase) {
    MOZ_ASSERT(!mSpec);
    MOZ_ASSERT(mDatabase == mTemporaryStrongDatabase);
    return;
  }

  MOZ_ASSERT(mSpec);

  const auto request = mOpenRequestActor->GetOpenDBRequest();

  auto& factory =
      static_cast<BackgroundFactoryChild*>(Manager())->GetDOMObject();

  // TODO: This AcquireStrongRefFromRawPtr looks suspicious. This should be
  // changed or at least well explained, see also comment on
  // BackgroundFactoryChild.
  mTemporaryStrongDatabase = IDBDatabase::Create(
      request, SafeRefPtr{&factory, AcquireStrongRefFromRawPtr{}}, this,
      std::move(mSpec));

  MOZ_ASSERT(mTemporaryStrongDatabase);
  mTemporaryStrongDatabase->AssertIsOnOwningThread();

  mDatabase = mTemporaryStrongDatabase;

  mOpenRequestActor->SetDatabaseActor(this);
}

void BackgroundDatabaseChild::ReleaseDOMObject() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mTemporaryStrongDatabase);
  mTemporaryStrongDatabase->AssertIsOnOwningThread();
  MOZ_ASSERT(mOpenRequestActor);
  MOZ_ASSERT(mDatabase == mTemporaryStrongDatabase);

  mOpenRequestActor->SetDatabaseActor(nullptr);

  mOpenRequestActor = nullptr;

  // This may be the final reference to the IDBDatabase object so we may end up
  // calling SendDeleteMeInternal() here. Make sure everything is cleaned up
  // properly before proceeding.
  mTemporaryStrongDatabase = nullptr;

  // XXX Why isn't mDatabase set to nullptr here?
}

void BackgroundDatabaseChild::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();

  MaybeCollectGarbageOnIPCMessage();

  if (mDatabase) {
    mDatabase->ClearBackgroundActor();
#ifdef DEBUG
    mDatabase = nullptr;
#endif
  }
}

PBackgroundIDBDatabaseFileChild*
BackgroundDatabaseChild::AllocPBackgroundIDBDatabaseFileChild(
    const IPCBlob& aIPCBlob) {
  MOZ_CRASH("PBackgroundIDBFileChild actors should be manually constructed!");
}

bool BackgroundDatabaseChild::DeallocPBackgroundIDBDatabaseFileChild(
    PBackgroundIDBDatabaseFileChild* aActor) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aActor);

  delete aActor;
  return true;
}

PBackgroundIDBDatabaseRequestChild*
BackgroundDatabaseChild::AllocPBackgroundIDBDatabaseRequestChild(
    const DatabaseRequestParams& aParams) {
  MOZ_CRASH(
      "PBackgroundIDBDatabaseRequestChild actors should be manually "
      "constructed!");
}

bool BackgroundDatabaseChild::DeallocPBackgroundIDBDatabaseRequestChild(
    PBackgroundIDBDatabaseRequestChild* aActor) {
  MOZ_ASSERT(aActor);

  delete static_cast<BackgroundDatabaseRequestChild*>(aActor);
  return true;
}

already_AddRefed<PBackgroundIDBVersionChangeTransactionChild>
BackgroundDatabaseChild::AllocPBackgroundIDBVersionChangeTransactionChild(
    const uint64_t aCurrentVersion, const uint64_t aRequestedVersion,
    const int64_t aNextObjectStoreId, const int64_t aNextIndexId) {
  AssertIsOnOwningThread();

  return RefPtr{new BackgroundVersionChangeTransactionChild(
                    mOpenRequestActor->GetOpenDBRequest())}
      .forget();
}

mozilla::ipc::IPCResult
BackgroundDatabaseChild::RecvPBackgroundIDBVersionChangeTransactionConstructor(
    PBackgroundIDBVersionChangeTransactionChild* aActor,
    const uint64_t& aCurrentVersion, const uint64_t& aRequestedVersion,
    const int64_t& aNextObjectStoreId, const int64_t& aNextIndexId) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aActor);
  MOZ_ASSERT(aActor->GetActorEventTarget(),
             "The event target shall be inherited from its manager actor.");
  MOZ_ASSERT(mOpenRequestActor);

  MaybeCollectGarbageOnIPCMessage();

  EnsureDOMObject();

  auto* actor = static_cast<BackgroundVersionChangeTransactionChild*>(aActor);

  // XXX NotNull might encapsulate this
  const auto request =
      WrapNotNullUnchecked(RefPtr{mOpenRequestActor->GetOpenDBRequest().get()});

  SafeRefPtr<IDBTransaction> transaction = IDBTransaction::CreateVersionChange(
      mDatabase, actor, request, aNextObjectStoreId, aNextIndexId);
  MOZ_ASSERT(transaction);

  transaction->AssertIsOnOwningThread();

  actor->SetDOMTransaction(transaction.clonePtr());

  const auto database = WrapNotNull(mDatabase);

  database->EnterSetVersionTransaction(aRequestedVersion);

  request->SetTransaction(transaction.clonePtr());

  RefPtr<Event> upgradeNeededEvent = IDBVersionChangeEvent::Create(
      request.get(), nsDependentString(kUpgradeNeededEventType),
      aCurrentVersion, aRequestedVersion);
  MOZ_ASSERT(upgradeNeededEvent);

  SetResultAndDispatchSuccessEvent(
      WrapNotNullUnchecked<RefPtr<IDBRequest>>(request.get()), transaction,
      *database, std::move(upgradeNeededEvent));

  return IPC_OK();
}

PBackgroundMutableFileChild*
BackgroundDatabaseChild::AllocPBackgroundMutableFileChild(
    const nsString& aName, const nsString& aType) {
  AssertIsOnOwningThread();

  return new BackgroundMutableFileChild(aName, aType);
}

bool BackgroundDatabaseChild::DeallocPBackgroundMutableFileChild(
    PBackgroundMutableFileChild* aActor) {
  MOZ_ASSERT(aActor);

  delete static_cast<BackgroundMutableFileChild*>(aActor);
  return true;
}

mozilla::ipc::IPCResult BackgroundDatabaseChild::RecvVersionChange(
    const uint64_t aOldVersion, const Maybe<uint64_t> aNewVersion) {
  AssertIsOnOwningThread();

  MaybeCollectGarbageOnIPCMessage();

  if (!mDatabase || mDatabase->IsClosed()) {
    return IPC_OK();
  }

  RefPtr<IDBDatabase> kungFuDeathGrip = mDatabase;

  // Handle bfcache'd windows.
  if (nsPIDOMWindowInner* owner = kungFuDeathGrip->GetOwner()) {
    // The database must be closed if the window is already frozen.
    bool shouldAbortAndClose = owner->IsFrozen();

    // Anything in the bfcache has to be evicted and then we have to close the
    // database also.
    if (nsCOMPtr<Document> doc = owner->GetExtantDoc()) {
      if (nsCOMPtr<nsIBFCacheEntry> bfCacheEntry = doc->GetBFCacheEntry()) {
        bfCacheEntry->RemoveFromBFCacheSync();
        shouldAbortAndClose = true;
      }
    }

    if (shouldAbortAndClose) {
      // Invalidate() doesn't close the database in the parent, so we have
      // to call Close() and AbortTransactions() manually.
      kungFuDeathGrip->AbortTransactions(/* aShouldWarn */ false);
      kungFuDeathGrip->Close();
      return IPC_OK();
    }
  }

  // Otherwise fire a versionchange event.
  const nsDependentString type(kVersionChangeEventType);

  RefPtr<Event> versionChangeEvent;

  if (aNewVersion.isNothing()) {
    versionChangeEvent =
        IDBVersionChangeEvent::Create(kungFuDeathGrip, type, aOldVersion);
    MOZ_ASSERT(versionChangeEvent);
  } else {
    versionChangeEvent = IDBVersionChangeEvent::Create(
        kungFuDeathGrip, type, aOldVersion, aNewVersion.value());
    MOZ_ASSERT(versionChangeEvent);
  }

  IDB_LOG_MARK("Child : Firing \"versionchange\" event",
               "C: IDBDatabase \"versionchange\" event", IDB_LOG_ID_STRING());

  IgnoredErrorResult rv;
  kungFuDeathGrip->DispatchEvent(*versionChangeEvent, rv);
  if (rv.Failed()) {
    NS_WARNING("Failed to dispatch event!");
  }

  if (!kungFuDeathGrip->IsClosed()) {
    SendBlocked();
  }

  return IPC_OK();
}

mozilla::ipc::IPCResult BackgroundDatabaseChild::RecvInvalidate() {
  AssertIsOnOwningThread();

  MaybeCollectGarbageOnIPCMessage();

  if (mDatabase) {
    mDatabase->Invalidate();
  }

  return IPC_OK();
}

mozilla::ipc::IPCResult
BackgroundDatabaseChild::RecvCloseAfterInvalidationComplete() {
  AssertIsOnOwningThread();

  MaybeCollectGarbageOnIPCMessage();

  if (mDatabase) {
    mDatabase->DispatchTrustedEvent(nsDependentString(kCloseEventType));
  }

  return IPC_OK();
}

/*******************************************************************************
 * BackgroundDatabaseRequestChild
 ******************************************************************************/

BackgroundDatabaseRequestChild::BackgroundDatabaseRequestChild(
    IDBDatabase* aDatabase, MovingNotNull<RefPtr<IDBRequest>> aRequest)
    : BackgroundRequestChildBase(std::move(aRequest)), mDatabase(aDatabase) {
  // Can't assert owning thread here because IPDL has not yet set our manager!
  MOZ_ASSERT(aDatabase);
  aDatabase->AssertIsOnOwningThread();

  MOZ_COUNT_CTOR(indexedDB::BackgroundDatabaseRequestChild);
}

BackgroundDatabaseRequestChild::~BackgroundDatabaseRequestChild() {
  MOZ_COUNT_DTOR(indexedDB::BackgroundDatabaseRequestChild);
}

bool BackgroundDatabaseRequestChild::HandleResponse(nsresult aResponse) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(NS_FAILED(aResponse));
  MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB);

  mRequest->Reset();

  DispatchErrorEvent(mRequest, aResponse);

  return true;
}

bool BackgroundDatabaseRequestChild::HandleResponse(
    const CreateFileRequestResponse& aResponse) {
  AssertIsOnOwningThread();

  mRequest->Reset();

  auto mutableFileActor =
      static_cast<BackgroundMutableFileChild*>(aResponse.mutableFileChild());
  MOZ_ASSERT(mutableFileActor);

  mutableFileActor->EnsureDOMObject();

  SetResultAndDispatchSuccessEvent(mRequest, nullptr,
                                   *WrapNotNull(static_cast<IDBMutableFile*>(
                                       mutableFileActor->GetDOMObject())));

  mutableFileActor->ReleaseDOMObject();

  return true;
}

mozilla::ipc::IPCResult BackgroundDatabaseRequestChild::Recv__delete__(
    const DatabaseRequestResponse& aResponse) {
  AssertIsOnOwningThread();

  switch (aResponse.type()) {
    case DatabaseRequestResponse::Tnsresult:
      if (!HandleResponse(aResponse.get_nsresult())) {
        return IPC_FAIL_NO_REASON(this);
      }
      return IPC_OK();

    case DatabaseRequestResponse::TCreateFileRequestResponse:
      if (!HandleResponse(aResponse.get_CreateFileRequestResponse())) {
        return IPC_FAIL_NO_REASON(this);
      }
      return IPC_OK();

    default:
      MOZ_CRASH("Unknown response type!");
  }

  MOZ_CRASH("Should never get here!");
}

/*******************************************************************************
 * BackgroundTransactionBase
 ******************************************************************************/

BackgroundTransactionBase::BackgroundTransactionBase(
    SafeRefPtr<IDBTransaction> aTransaction)
    : mTemporaryStrongTransaction(std::move(aTransaction)),
      mTransaction(mTemporaryStrongTransaction.unsafeGetRawPtr()) {
  MOZ_ASSERT(mTransaction);
  mTransaction->AssertIsOnOwningThread();

  MOZ_COUNT_CTOR(BackgroundTransactionBase);
}

#ifdef DEBUG

void BackgroundTransactionBase::AssertIsOnOwningThread() const {
  MOZ_ASSERT(mTransaction);
  mTransaction->AssertIsOnOwningThread();
}

#endif  // DEBUG

void BackgroundTransactionBase::NoteActorDestroyed() {
  AssertIsOnOwningThread();
  MOZ_ASSERT_IF(mTemporaryStrongTransaction, mTransaction);

  if (mTransaction) {
    mTransaction->ClearBackgroundActor();

    // Normally this would be DEBUG-only but NoteActorDestroyed is also called
    // from SendDeleteMeInternal. In that case we're going to receive an actual
    // ActorDestroy call later and we don't want to touch a dead object.
    mTemporaryStrongTransaction = nullptr;
    mTransaction = nullptr;
  }
}

void BackgroundTransactionBase::SetDOMTransaction(
    SafeRefPtr<IDBTransaction> aTransaction) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aTransaction);
  aTransaction->AssertIsOnOwningThread();
  MOZ_ASSERT(!mTemporaryStrongTransaction);
  MOZ_ASSERT(!mTransaction);

  mTemporaryStrongTransaction = std::move(aTransaction);
  mTransaction = mTemporaryStrongTransaction.unsafeGetRawPtr();
}

void BackgroundTransactionBase::NoteComplete() {
  AssertIsOnOwningThread();
  MOZ_ASSERT_IF(mTransaction, mTemporaryStrongTransaction);

  mTemporaryStrongTransaction = nullptr;
}

/*******************************************************************************
 * BackgroundTransactionChild
 ******************************************************************************/

BackgroundTransactionChild::BackgroundTransactionChild(
    SafeRefPtr<IDBTransaction> aTransaction)
    : BackgroundTransactionBase(std::move(aTransaction)) {
  MOZ_COUNT_CTOR(indexedDB::BackgroundTransactionChild);
}

BackgroundTransactionChild::~BackgroundTransactionChild() {
  MOZ_COUNT_DTOR(indexedDB::BackgroundTransactionChild);
}

#ifdef DEBUG

void BackgroundTransactionChild::AssertIsOnOwningThread() const {
  static_cast<BackgroundDatabaseChild*>(Manager())->AssertIsOnOwningThread();
}

#endif  // DEBUG

void BackgroundTransactionChild::SendDeleteMeInternal() {
  AssertIsOnOwningThread();

  if (mTransaction) {
    NoteActorDestroyed();

    MOZ_ALWAYS_TRUE(PBackgroundIDBTransactionChild::SendDeleteMe());
  }
}

void BackgroundTransactionChild::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();

  MaybeCollectGarbageOnIPCMessage();

  NoteActorDestroyed();
}

mozilla::ipc::IPCResult BackgroundTransactionChild::RecvComplete(
    const nsresult aResult) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mTransaction);

  MaybeCollectGarbageOnIPCMessage();

  mTransaction->FireCompleteOrAbortEvents(aResult);

  NoteComplete();
  return IPC_OK();
}

PBackgroundIDBRequestChild*
BackgroundTransactionChild::AllocPBackgroundIDBRequestChild(
    const RequestParams& aParams) {
  MOZ_CRASH(
      "PBackgroundIDBRequestChild actors should be manually "
      "constructed!");
}

bool BackgroundTransactionChild::DeallocPBackgroundIDBRequestChild(
    PBackgroundIDBRequestChild* aActor) {
  MOZ_ASSERT(aActor);

  delete static_cast<BackgroundRequestChild*>(aActor);
  return true;
}

PBackgroundIDBCursorChild*
BackgroundTransactionChild::AllocPBackgroundIDBCursorChild(
    const OpenCursorParams& aParams) {
  AssertIsOnOwningThread();

  MOZ_CRASH("PBackgroundIDBCursorChild actors should be manually constructed!");
}

bool BackgroundTransactionChild::DeallocPBackgroundIDBCursorChild(
    PBackgroundIDBCursorChild* aActor) {
  MOZ_ASSERT(aActor);

  delete aActor;
  return true;
}

/*******************************************************************************
 * BackgroundVersionChangeTransactionChild
 ******************************************************************************/

BackgroundVersionChangeTransactionChild::
    BackgroundVersionChangeTransactionChild(IDBOpenDBRequest* aOpenDBRequest)
    : mOpenDBRequest(aOpenDBRequest) {
  MOZ_ASSERT(aOpenDBRequest);
  aOpenDBRequest->AssertIsOnOwningThread();

  MOZ_COUNT_CTOR(indexedDB::BackgroundVersionChangeTransactionChild);
}

BackgroundVersionChangeTransactionChild::
    ~BackgroundVersionChangeTransactionChild() {
  AssertIsOnOwningThread();

  MOZ_COUNT_DTOR(indexedDB::BackgroundVersionChangeTransactionChild);
}

#ifdef DEBUG

void BackgroundVersionChangeTransactionChild::AssertIsOnOwningThread() const {
  static_cast<BackgroundDatabaseChild*>(Manager())->AssertIsOnOwningThread();
}

#endif  // DEBUG

void BackgroundVersionChangeTransactionChild::SendDeleteMeInternal(
    bool aFailedConstructor) {
  AssertIsOnOwningThread();

  if (mTransaction || aFailedConstructor) {
    NoteActorDestroyed();

    MOZ_ALWAYS_TRUE(
        PBackgroundIDBVersionChangeTransactionChild::SendDeleteMe());
  }
}

void BackgroundVersionChangeTransactionChild::ActorDestroy(
    ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();

  MaybeCollectGarbageOnIPCMessage();

  mOpenDBRequest = nullptr;

  NoteActorDestroyed();
}

mozilla::ipc::IPCResult BackgroundVersionChangeTransactionChild::RecvComplete(
    const nsresult aResult) {
  AssertIsOnOwningThread();

  MaybeCollectGarbageOnIPCMessage();

  if (!mTransaction) {
    return IPC_OK();
  }

  MOZ_ASSERT(mOpenDBRequest);

  IDBDatabase* database = mTransaction->Database();
  MOZ_ASSERT(database);

  database->ExitSetVersionTransaction();

  if (NS_FAILED(aResult)) {
    database->Close();
  }

  RefPtr<IDBOpenDBRequest> request = mOpenDBRequest;
  MOZ_ASSERT(request);

  mTransaction->FireCompleteOrAbortEvents(aResult);

  request->SetTransaction(nullptr);
  request = nullptr;

  mOpenDBRequest = nullptr;

  NoteComplete();
  return IPC_OK();
}

PBackgroundIDBRequestChild*
BackgroundVersionChangeTransactionChild::AllocPBackgroundIDBRequestChild(
    const RequestParams& aParams) {
  MOZ_CRASH(
      "PBackgroundIDBRequestChild actors should be manually "
      "constructed!");
}

bool BackgroundVersionChangeTransactionChild::DeallocPBackgroundIDBRequestChild(
    PBackgroundIDBRequestChild* aActor) {
  MOZ_ASSERT(aActor);

  delete static_cast<BackgroundRequestChild*>(aActor);
  return true;
}

PBackgroundIDBCursorChild*
BackgroundVersionChangeTransactionChild::AllocPBackgroundIDBCursorChild(
    const OpenCursorParams& aParams) {
  AssertIsOnOwningThread();

  MOZ_CRASH("PBackgroundIDBCursorChild actors should be manually constructed!");
}

bool BackgroundVersionChangeTransactionChild::DeallocPBackgroundIDBCursorChild(
    PBackgroundIDBCursorChild* aActor) {
  MOZ_ASSERT(aActor);

  delete aActor;
  return true;
}

/*******************************************************************************
 * BackgroundMutableFileChild
 ******************************************************************************/

BackgroundMutableFileChild::BackgroundMutableFileChild(const nsAString& aName,
                                                       const nsAString& aType)
    : mMutableFile(nullptr), mName(aName), mType(aType) {
  // Can't assert owning thread here because IPDL has not yet set our manager!
  MOZ_COUNT_CTOR(indexedDB::BackgroundMutableFileChild);
}

BackgroundMutableFileChild::~BackgroundMutableFileChild() {
  MOZ_COUNT_DTOR(indexedDB::BackgroundMutableFileChild);
}

#ifdef DEBUG

void BackgroundMutableFileChild::AssertIsOnOwningThread() const {
  static_cast<BackgroundDatabaseChild*>(Manager())->AssertIsOnOwningThread();
}

#endif  // DEBUG

void BackgroundMutableFileChild::EnsureDOMObject() {
  AssertIsOnOwningThread();

  if (mTemporaryStrongMutableFile) {
    return;
  }

  auto database =
      static_cast<BackgroundDatabaseChild*>(Manager())->GetDOMObject();
  MOZ_ASSERT(database);

  mTemporaryStrongMutableFile =
      new IDBMutableFile(database, this, mName, mType);

  MOZ_ASSERT(mTemporaryStrongMutableFile);
  mTemporaryStrongMutableFile->AssertIsOnOwningThread();

  mMutableFile = mTemporaryStrongMutableFile;
}

void BackgroundMutableFileChild::ReleaseDOMObject() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mTemporaryStrongMutableFile);
  mTemporaryStrongMutableFile->AssertIsOnOwningThread();
  MOZ_ASSERT(mMutableFile == mTemporaryStrongMutableFile);

  mTemporaryStrongMutableFile = nullptr;
}

void BackgroundMutableFileChild::SendDeleteMeInternal() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(!mTemporaryStrongMutableFile);

  if (mMutableFile) {
    mMutableFile->ClearBackgroundActor();
    mMutableFile = nullptr;

    MOZ_ALWAYS_TRUE(PBackgroundMutableFileChild::SendDeleteMe());
  }
}

void BackgroundMutableFileChild::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();

  if (mMutableFile) {
    mMutableFile->ClearBackgroundActor();
#ifdef DEBUG
    mMutableFile = nullptr;
#endif
  }
}

PBackgroundFileHandleChild*
BackgroundMutableFileChild::AllocPBackgroundFileHandleChild(
    const FileMode& aMode) {
  MOZ_CRASH(
      "PBackgroundFileHandleChild actors should be manually "
      "constructed!");
}

bool BackgroundMutableFileChild::DeallocPBackgroundFileHandleChild(
    PBackgroundFileHandleChild* aActor) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aActor);

  delete static_cast<BackgroundFileHandleChild*>(aActor);
  return true;
}

/*******************************************************************************
 * BackgroundRequestChild
 ******************************************************************************/

BackgroundRequestChild::BackgroundRequestChild(
    MovingNotNull<RefPtr<IDBRequest>> aRequest)
    : BackgroundRequestChildBase(std::move(aRequest)),
      mTransaction(mRequest->AcquireTransaction()),
      mRunningPreprocessHelpers(0),
      mCurrentCloneDataIndex(0),
      mPreprocessResultCode(NS_OK),
      mGetAll(false) {
  MOZ_ASSERT(mTransaction);
  mTransaction->AssertIsOnOwningThread();

  MOZ_COUNT_CTOR(indexedDB::BackgroundRequestChild);
}

BackgroundRequestChild::~BackgroundRequestChild() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(!mTransaction);

  MOZ_COUNT_DTOR(indexedDB::BackgroundRequestChild);
}

void BackgroundRequestChild::MaybeSendContinue() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mRunningPreprocessHelpers > 0);

  if (--mRunningPreprocessHelpers == 0) {
    PreprocessResponse response;

    if (NS_SUCCEEDED(mPreprocessResultCode)) {
      if (mGetAll) {
        response = ObjectStoreGetAllPreprocessResponse();
      } else {
        response = ObjectStoreGetPreprocessResponse();
      }
    } else {
      response = mPreprocessResultCode;
    }

    MOZ_ALWAYS_TRUE(SendContinue(response));
  }
}

void BackgroundRequestChild::OnPreprocessFinished(
    uint32_t aCloneDataIndex, UniquePtr<JSStructuredCloneData> aCloneData) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aCloneDataIndex < mCloneInfos.Length());
  MOZ_ASSERT(aCloneData);

  auto& cloneInfo = mCloneInfos[aCloneDataIndex];
  MOZ_ASSERT(cloneInfo.mPreprocessHelper);
  MOZ_ASSERT(!cloneInfo.mCloneData);

  cloneInfo.mCloneData = std::move(aCloneData);

  MaybeSendContinue();

  cloneInfo.mPreprocessHelper = nullptr;
}

void BackgroundRequestChild::OnPreprocessFailed(uint32_t aCloneDataIndex,
                                                nsresult aErrorCode) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aCloneDataIndex < mCloneInfos.Length());
  MOZ_ASSERT(NS_FAILED(aErrorCode));

  auto& cloneInfo = mCloneInfos[aCloneDataIndex];
  MOZ_ASSERT(cloneInfo.mPreprocessHelper);
  MOZ_ASSERT(!cloneInfo.mCloneData);

  if (NS_SUCCEEDED(mPreprocessResultCode)) {
    mPreprocessResultCode = aErrorCode;
  }

  MaybeSendContinue();

  cloneInfo.mPreprocessHelper = nullptr;
}

UniquePtr<JSStructuredCloneData> BackgroundRequestChild::GetNextCloneData() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mCurrentCloneDataIndex < mCloneInfos.Length());
  MOZ_ASSERT(mCloneInfos[mCurrentCloneDataIndex].mCloneData);

  return std::move(mCloneInfos[mCurrentCloneDataIndex++].mCloneData);
}

void BackgroundRequestChild::HandleResponse(nsresult aResponse) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(NS_FAILED(aResponse));
  MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB);
  MOZ_ASSERT(mTransaction);

  DispatchErrorEvent(mRequest, aResponse, mTransaction.clonePtr());
}

void BackgroundRequestChild::HandleResponse(const Key& aResponse) {
  AssertIsOnOwningThread();

  SetResultAndDispatchSuccessEvent(mRequest, AcquireTransaction(), aResponse);
}

void BackgroundRequestChild::HandleResponse(const nsTArray<Key>& aResponse) {
  AssertIsOnOwningThread();

  SetResultAndDispatchSuccessEvent(mRequest, AcquireTransaction(), aResponse);
}

void BackgroundRequestChild::HandleResponse(
    SerializedStructuredCloneReadInfo&& aResponse) {
  AssertIsOnOwningThread();

  auto cloneReadInfo = DeserializeStructuredCloneReadInfo(
      std::move(aResponse), mTransaction->Database(),
      [this] { return std::move(*GetNextCloneData()); });

  SetResultAndDispatchSuccessEvent(mRequest, AcquireTransaction(),
                                   cloneReadInfo);
}

void BackgroundRequestChild::HandleResponse(
    nsTArray<SerializedStructuredCloneReadInfo>&& aResponse) {
  AssertIsOnOwningThread();

  nsTArray<StructuredCloneReadInfoChild> cloneReadInfos;

  if (!aResponse.IsEmpty()) {
    const uint32_t count = aResponse.Length();

    cloneReadInfos.SetCapacity(count);

    std::transform(
        std::make_move_iterator(aResponse.begin()),
        std::make_move_iterator(aResponse.end()),
        MakeBackInserter(cloneReadInfos),
        [database = mTransaction->Database(),
         this](SerializedStructuredCloneReadInfo&& serializedCloneInfo) {
          return DeserializeStructuredCloneReadInfo(
              std::move(serializedCloneInfo), database,
              [this] { return std::move(*GetNextCloneData()); });
        });
  }

  SetResultAndDispatchSuccessEvent(mRequest, AcquireTransaction(),
                                   cloneReadInfos);
}

void BackgroundRequestChild::HandleResponse(JS::Handle<JS::Value> aResponse) {
  AssertIsOnOwningThread();

  SetResultAndDispatchSuccessEvent(
      mRequest, AcquireTransaction(),
      const_cast<const JS::Handle<JS::Value>&>(aResponse));
}

void BackgroundRequestChild::HandleResponse(const uint64_t aResponse) {
  AssertIsOnOwningThread();

  SetResultAndDispatchSuccessEvent(mRequest, AcquireTransaction(), aResponse);
}

nsresult BackgroundRequestChild::HandlePreprocess(
    const PreprocessInfo& aPreprocessInfo) {
  return HandlePreprocessInternal(
      AutoTArray<PreprocessInfo, 1>{aPreprocessInfo});
}

nsresult BackgroundRequestChild::HandlePreprocess(
    const nsTArray<PreprocessInfo>& aPreprocessInfos) {
  AssertIsOnOwningThread();
  mGetAll = true;

  return HandlePreprocessInternal(aPreprocessInfos);
}

nsresult BackgroundRequestChild::HandlePreprocessInternal(
    const nsTArray<PreprocessInfo>& aPreprocessInfos) {
  AssertIsOnOwningThread();

  IDBDatabase* database = mTransaction->Database();

  const uint32_t count = aPreprocessInfos.Length();

  mCloneInfos.SetLength(count);

  // TODO: Since we use the stream transport service, this can spawn 25 threads
  //       and has the potential to cause some annoying browser hiccups.
  //       Consider using a single thread or a very small threadpool.
  for (uint32_t index = 0; index < count; index++) {
    const PreprocessInfo& preprocessInfo = aPreprocessInfos[index];

    const auto files =
        DeserializeStructuredCloneFiles(database, preprocessInfo.files(),
                                        /* aForPreprocess */ true);

    MOZ_ASSERT(files.Length() == 1);

    auto& preprocessHelper = mCloneInfos[index].mPreprocessHelper;
    preprocessHelper = MakeRefPtr<PreprocessHelper>(index, this);

    nsresult rv = preprocessHelper->Init(files[0]);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    rv = preprocessHelper->Dispatch();
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    mRunningPreprocessHelpers++;
  }

  return NS_OK;
}

void BackgroundRequestChild::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();

  MaybeCollectGarbageOnIPCMessage();

  for (auto& cloneInfo : mCloneInfos) {
    const auto& preprocessHelper = cloneInfo.mPreprocessHelper;

    if (preprocessHelper) {
      preprocessHelper->ClearActor();
    }
  }
  mCloneInfos.Clear();

  if (mTransaction) {
    mTransaction->AssertIsOnOwningThread();

    mTransaction->OnRequestFinished(/* aRequestCompletedSuccessfully */
                                    aWhy == Deletion);
#ifdef DEBUG
    mTransaction = nullptr;
#endif
  }
}

mozilla::ipc::IPCResult BackgroundRequestChild::Recv__delete__(
    RequestResponse&& aResponse) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mTransaction);

  MaybeCollectGarbageOnIPCMessage();

  if (mTransaction->IsAborted()) {
    // Always fire an "error" event with ABORT_ERR if the transaction was
    // aborted, even if the request succeeded or failed with another error.
    HandleResponse(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
  } else {
    switch (aResponse.type()) {
      case RequestResponse::Tnsresult:
        HandleResponse(aResponse.get_nsresult());
        break;

      case RequestResponse::TObjectStoreAddResponse:
        HandleResponse(aResponse.get_ObjectStoreAddResponse().key());
        break;

      case RequestResponse::TObjectStorePutResponse:
        HandleResponse(aResponse.get_ObjectStorePutResponse().key());
        break;

      case RequestResponse::TObjectStoreGetResponse:
        HandleResponse(
            std::move(aResponse.get_ObjectStoreGetResponse().cloneInfo()));
        break;

      case RequestResponse::TObjectStoreGetKeyResponse:
        HandleResponse(aResponse.get_ObjectStoreGetKeyResponse().key());
        break;

      case RequestResponse::TObjectStoreGetAllResponse:
        HandleResponse(
            std::move(aResponse.get_ObjectStoreGetAllResponse().cloneInfos()));
        break;

      case RequestResponse::TObjectStoreGetAllKeysResponse:
        HandleResponse(aResponse.get_ObjectStoreGetAllKeysResponse().keys());
        break;

      case RequestResponse::TObjectStoreDeleteResponse:
      case RequestResponse::TObjectStoreClearResponse:
        HandleResponse(JS::UndefinedHandleValue);
        break;

      case RequestResponse::TObjectStoreCountResponse:
        HandleResponse(aResponse.get_ObjectStoreCountResponse().count());
        break;

      case RequestResponse::TIndexGetResponse:
        HandleResponse(std::move(aResponse.get_IndexGetResponse().cloneInfo()));
        break;

      case RequestResponse::TIndexGetKeyResponse:
        HandleResponse(aResponse.get_IndexGetKeyResponse().key());
        break;

      case RequestResponse::TIndexGetAllResponse:
        HandleResponse(
            std::move(aResponse.get_IndexGetAllResponse().cloneInfos()));
        break;

      case RequestResponse::TIndexGetAllKeysResponse:
        HandleResponse(aResponse.get_IndexGetAllKeysResponse().keys());
        break;

      case RequestResponse::TIndexCountResponse:
        HandleResponse(aResponse.get_IndexCountResponse().count());
        break;

      default:
        MOZ_CRASH("Unknown response type!");
    }
  }

  mTransaction->OnRequestFinished(/* aRequestCompletedSuccessfully */ true);

  // Null this out so that we don't try to call OnRequestFinished() again in
  // ActorDestroy.
  mTransaction = nullptr;

  return IPC_OK();
}

mozilla::ipc::IPCResult BackgroundRequestChild::RecvPreprocess(
    const PreprocessParams& aParams) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mTransaction);

  MaybeCollectGarbageOnIPCMessage();

  nsresult rv;

  switch (aParams.type()) {
    case PreprocessParams::TObjectStoreGetPreprocessParams: {
      const auto& params = aParams.get_ObjectStoreGetPreprocessParams();

      rv = HandlePreprocess(params.preprocessInfo());

      break;
    }

    case PreprocessParams::TObjectStoreGetAllPreprocessParams: {
      const auto& params = aParams.get_ObjectStoreGetAllPreprocessParams();

      rv = HandlePreprocess(params.preprocessInfos());

      break;
    }

    default:
      MOZ_CRASH("Unknown params type!");
  }

  if (NS_WARN_IF(NS_FAILED(rv))) {
    if (!SendContinue(rv)) {
      return IPC_FAIL_NO_REASON(this);
    }
    return IPC_OK();
  }

  return IPC_OK();
}

nsresult BackgroundRequestChild::PreprocessHelper::Init(
    const StructuredCloneFileChild& aFile) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aFile.HasBlob());
  MOZ_ASSERT(aFile.Type() == StructuredCloneFileBase::eStructuredClone);
  MOZ_ASSERT(mState == State::Initial);

  // The stream transport service is used for asynchronous processing. It has a
  // threadpool with a high cap of 25 threads. Fortunately, the service can be
  // used on workers too.
  nsCOMPtr<nsIEventTarget> target =
      do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
  MOZ_ASSERT(target);

  // We use a TaskQueue here in order to be sure that the events are dispatched
  // in the correct order. This is not guaranteed in case we use the I/O thread
  // directly.
  mTaskQueue = MakeRefPtr<TaskQueue>(target.forget());

  ErrorResult errorResult;

  nsCOMPtr<nsIInputStream> stream;
  // XXX After Bug 1620560, MutableBlob is not needed here anymore.
  aFile.MutableBlob().CreateInputStream(getter_AddRefs(stream), errorResult);
  if (NS_WARN_IF(errorResult.Failed())) {
    return errorResult.StealNSResult();
  }

  mStream = std::move(stream);

  mCloneData = MakeUnique<JSStructuredCloneData>(
      JS::StructuredCloneScope::DifferentProcessForIndexedDB);

  return NS_OK;
}

nsresult BackgroundRequestChild::PreprocessHelper::Dispatch() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mState == State::Initial);

  nsresult rv = mTaskQueue->Dispatch(this, NS_DISPATCH_NORMAL);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  return NS_OK;
}

nsresult BackgroundRequestChild::PreprocessHelper::Start() {
  MOZ_ASSERT(!IsOnOwningThread());
  MOZ_ASSERT(mStream);
  MOZ_ASSERT(mState == State::Initial);

  nsresult rv;

  PRFileDesc* fileDesc = GetFileDescriptorFromStream(mStream);
  if (fileDesc) {
    rv = ProcessStream();
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    return NS_OK;
  }

  mState = State::WaitingForStreamReady;

  nsCOMPtr<nsIAsyncFileMetadata> asyncFileMetadata = do_QueryInterface(mStream);
  if (asyncFileMetadata) {
    rv = asyncFileMetadata->AsyncFileMetadataWait(this, mTaskQueue);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    return NS_OK;
  }

  nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mStream);
  if (!asyncStream) {
    return NS_ERROR_NO_INTERFACE;
  }

  rv = asyncStream->AsyncWait(this, 0, 0, mTaskQueue);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  return NS_OK;
}

nsresult BackgroundRequestChild::PreprocessHelper::ProcessStream() {
  MOZ_ASSERT(!IsOnOwningThread());
  MOZ_ASSERT(mStream);
  MOZ_ASSERT(mState == State::Initial ||
             mState == State::WaitingForStreamReady);

  // We need to get the internal stream (which is an nsFileInputStream) because
  // SnappyUncompressInputStream doesn't support reading from async input
  // streams.

  nsCOMPtr<mozIRemoteLazyInputStream> blobInputStream =
      do_QueryInterface(mStream);
  MOZ_ASSERT(blobInputStream);

  nsCOMPtr<nsIInputStream> internalInputStream =
      blobInputStream->GetInternalStream();
  MOZ_ASSERT(internalInputStream);

  RefPtr<SnappyUncompressInputStream> snappyInputStream =
      new SnappyUncompressInputStream(internalInputStream);

  nsresult rv;

  do {
    char buffer[kFileCopyBufferSize];

    uint32_t numRead;
    rv = snappyInputStream->Read(buffer, sizeof(buffer), &numRead);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      break;
    }

    if (!numRead) {
      break;
    }

    if (NS_WARN_IF(!mCloneData->AppendBytes(buffer, numRead))) {
      rv = NS_ERROR_OUT_OF_MEMORY;
      break;
    }
  } while (true);

  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  mState = State::Finishing;

  rv = mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  return NS_OK;
}

void BackgroundRequestChild::PreprocessHelper::Finish() {
  AssertIsOnOwningThread();

  if (mActor) {
    if (NS_SUCCEEDED(mResultCode)) {
      mActor->OnPreprocessFinished(mCloneDataIndex, std::move(mCloneData));

      MOZ_ASSERT(!mCloneData);
    } else {
      mActor->OnPreprocessFailed(mCloneDataIndex, mResultCode);
    }
  }

  mState = State::Completed;
}

NS_IMPL_ISUPPORTS_INHERITED(BackgroundRequestChild::PreprocessHelper,
                            CancelableRunnable, nsIInputStreamCallback,
                            nsIFileMetadataCallback)

NS_IMETHODIMP
BackgroundRequestChild::PreprocessHelper::Run() {
  nsresult rv;

  switch (mState) {
    case State::Initial:
      rv = Start();
      break;

    case State::WaitingForStreamReady:
      rv = ProcessStream();
      break;

    case State::Finishing:
      Finish();
      return NS_OK;

    default:
      MOZ_CRASH("Bad state!");
  }

  if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::Finishing) {
    if (NS_SUCCEEDED(mResultCode)) {
      mResultCode = rv;
    }

    // Must set mState before dispatching otherwise we will race with the owning
    // thread.
    mState = State::Finishing;

    if (IsOnOwningThread()) {
      Finish();
    } else {
      MOZ_ALWAYS_SUCCEEDS(
          mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
BackgroundRequestChild::PreprocessHelper::OnInputStreamReady(
    nsIAsyncInputStream* aStream) {
  MOZ_ASSERT(!IsOnOwningThread());
  MOZ_ASSERT(mState == State::WaitingForStreamReady);

  MOZ_ALWAYS_SUCCEEDS(this->Run());

  return NS_OK;
}

NS_IMETHODIMP
BackgroundRequestChild::PreprocessHelper::OnFileMetadataReady(
    nsIAsyncFileMetadata* aObject) {
  MOZ_ASSERT(!IsOnOwningThread());
  MOZ_ASSERT(mState == State::WaitingForStreamReady);

  MOZ_ALWAYS_SUCCEEDS(this->Run());

  return NS_OK;
}

/*******************************************************************************
 * BackgroundCursorChild
 ******************************************************************************/

BackgroundCursorChildBase::BackgroundCursorChildBase(
    const NotNull<IDBRequest*> aRequest, const Direction aDirection)
    : mRequest(aRequest),
      mTransaction(aRequest->MaybeTransactionRef()),
      mStrongRequest(aRequest),
      mDirection(aDirection) {
  MOZ_ASSERT(mTransaction);
}

MovingNotNull<RefPtr<IDBRequest>> BackgroundCursorChildBase::AcquireRequest()
    const {
  AssertIsOnOwningThread();

  // XXX This could be encapsulated by NotNull
  return WrapNotNullUnchecked(RefPtr{mRequest->get()});
}

template <IDBCursorType CursorType>
BackgroundCursorChild<CursorType>::BackgroundCursorChild(
    const NotNull<IDBRequest*> aRequest, SourceType* aSource,
    Direction aDirection)
    : BackgroundCursorChildBase(aRequest, aDirection),
      mSource(WrapNotNull(aSource)),
      mCursor(nullptr),
      mInFlightResponseInvalidationNeeded(false) {
  aSource->AssertIsOnOwningThread();

  MOZ_COUNT_CTOR(indexedDB::BackgroundCursorChild<CursorType>);
}

template <IDBCursorType CursorType>
BackgroundCursorChild<CursorType>::~BackgroundCursorChild() {
  MOZ_COUNT_DTOR(indexedDB::BackgroundCursorChild<CursorType>);
}

template <IDBCursorType CursorType>
void BackgroundCursorChild<CursorType>::SendContinueInternal(
    const CursorRequestParams& aParams,
    const CursorData<CursorType>& aCurrentData) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mRequest);
  MOZ_ASSERT(mTransaction);
  MOZ_ASSERT(mCursor);
  MOZ_ASSERT(!mStrongRequest);
  MOZ_ASSERT(!mStrongCursor);

  // Make sure all our DOM objects stay alive.
  mStrongCursor = mCursor;

  MOZ_ASSERT(GetRequest()->ReadyState() == IDBRequestReadyState::Done);
  GetRequest()->Reset();

  mTransaction->OnNewRequest();

  CursorRequestParams params = aParams;
  Key currentKey = aCurrentData.mKey;
  Key currentObjectStoreKey;
  // TODO: This is still not nice.
  if constexpr (!CursorTypeTraits<CursorType>::IsObjectStoreCursor) {
    currentObjectStoreKey = aCurrentData.mObjectStoreKey;
  }

  switch (params.type()) {
    case CursorRequestParams::TContinueParams: {
      const auto& key = params.get_ContinueParams().key();
      if (key.IsUnset()) {
        break;
      }

      // Discard cache entries before the target key.
      DiscardCachedResponses(
          [&key, isLocaleAware = mCursor->IsLocaleAware(),
           keyOperator = GetKeyOperator(mDirection),
           transactionSerialNumber = mTransaction->LoggingSerialNumber(),
           requestSerialNumber = GetRequest()->LoggingSerialNumber()](
              const auto& currentCachedResponse) {
            // This duplicates the logic from the parent. We could avoid this
            // duplication if we invalidated the cached records always for any
            // continue-with-key operation, but would lose the benefits of
            // preloading then.
            const auto& cachedSortKey =
                currentCachedResponse.GetSortKey(isLocaleAware);
            const bool discard = !(cachedSortKey.*keyOperator)(key);
            if (discard) {
              IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
                  "PRELOAD: Continue to key %s, discarding cached key %s/%s",
                  "Continue, discarding%.0s%.0s%.0s", transactionSerialNumber,
                  requestSerialNumber, key.GetBuffer().get(),
                  cachedSortKey.GetBuffer().get(),
                  currentCachedResponse.GetObjectStoreKeyForLogging());
            } else {
              IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
                  "PRELOAD: Continue to key %s, keeping cached key %s/%s and "
                  "further",
                  "Continue, keeping%.0s%.0s%.0s", transactionSerialNumber,
                  requestSerialNumber, key.GetBuffer().get(),
                  cachedSortKey.GetBuffer().get(),
                  currentCachedResponse.GetObjectStoreKeyForLogging());
            }

            return discard;
          });

      break;
    }

    case CursorRequestParams::TContinuePrimaryKeyParams: {
      if constexpr (!CursorTypeTraits<CursorType>::IsObjectStoreCursor) {
        const auto& key = params.get_ContinuePrimaryKeyParams().key();
        const auto& primaryKey =
            params.get_ContinuePrimaryKeyParams().primaryKey();
        if (key.IsUnset() || primaryKey.IsUnset()) {
          break;
        }

        // Discard cache entries before the target key.
        DiscardCachedResponses([&key, &primaryKey,
                                isLocaleAware = mCursor->IsLocaleAware(),
                                keyCompareOperator = GetKeyOperator(mDirection),
                                transactionSerialNumber =
                                    mTransaction->LoggingSerialNumber(),
                                requestSerialNumber =
                                    GetRequest()->LoggingSerialNumber()](
                                   const auto& currentCachedResponse) {
          // This duplicates the logic from the parent. We could avoid this
          // duplication if we invalidated the cached records always for any
          // continue-with-key operation, but would lose the benefits of
          // preloading then.
          const auto& cachedSortKey =
              currentCachedResponse.GetSortKey(isLocaleAware);
          const auto& cachedSortPrimaryKey =
              currentCachedResponse.mObjectStoreKey;

          const bool discard =
              (cachedSortKey == key &&
               !(cachedSortPrimaryKey.*keyCompareOperator)(primaryKey)) ||
              !(cachedSortKey.*keyCompareOperator)(key);

          if (discard) {
            IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
                "PRELOAD: Continue to key %s with primary key %s, discarding "
                "cached key %s with cached primary key %s",
                "Continue, discarding%.0s%.0s%.0s%.0s", transactionSerialNumber,
                requestSerialNumber, key.GetBuffer().get(),
                primaryKey.GetBuffer().get(), cachedSortKey.GetBuffer().get(),
                cachedSortPrimaryKey.GetBuffer().get());
          } else {
            IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
                "PRELOAD: Continue to key %s with primary key %s, keeping "
                "cached key %s with cached primary key %s and further",
                "Continue, keeping%.0s%.0s%.0s%.0s", transactionSerialNumber,
                requestSerialNumber, key.GetBuffer().get(),
                primaryKey.GetBuffer().get(), cachedSortKey.GetBuffer().get(),
                cachedSortPrimaryKey.GetBuffer().get());
          }

          return discard;
        });
      } else {
        MOZ_CRASH("Shouldn't get here");
      }

      break;
    }

    case CursorRequestParams::TAdvanceParams: {
      uint32_t& advanceCount = params.get_AdvanceParams().count();
      IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
          "PRELOAD: Advancing %" PRIu32 " records", "Advancing %" PRIu32,
          mTransaction->LoggingSerialNumber(),
          GetRequest()->LoggingSerialNumber(), advanceCount);

      // Discard cache entries.
      DiscardCachedResponses([&advanceCount, &currentKey,
                              &currentObjectStoreKey](
                                 const auto& currentCachedResponse) {
        const bool res = advanceCount > 1;
        if (res) {
          --advanceCount;

          // TODO: We only need to update currentKey on the last entry, the
          // others are overwritten in the next iteration anyway.
          currentKey = currentCachedResponse.mKey;
          if constexpr (!CursorTypeTraits<CursorType>::IsObjectStoreCursor) {
            currentObjectStoreKey = currentCachedResponse.mObjectStoreKey;
          } else {
            Unused << currentObjectStoreKey;
          }
        }
        return res;
      });
      break;
    }

    default:
      MOZ_CRASH("Should never get here!");
  }

  if (!mCachedResponses.empty()) {
    // We need to remove the response here from mCachedResponses, since when
    // requests are interleaved, other events may be processed before
    // CompleteContinueRequestFromCache, which may modify mCachedResponses.
    mDelayedResponses.emplace_back(std::move(mCachedResponses.front()));
    mCachedResponses.pop_front();

    // We cannot send the response right away, as we must preserve the request
    // order. Dispatching a DelayedActionRunnable only partially addresses this.
    // This is accompanied by invalidating cached entries at proper locations to
    // make it correct. To avoid this, further changes are necessary, see Bug
    // 1580499.
    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(
        MakeAndAddRef<DelayedActionRunnable<BackgroundCursorChild<CursorType>>>(
            this, &BackgroundCursorChild::CompleteContinueRequestFromCache)));

    // TODO: Could we preload further entries in the background when the size of
    // mCachedResponses falls under some threshold? Or does the response
    // handling model disallow this?
  } else {
    MOZ_ALWAYS_TRUE(PBackgroundIDBCursorChild::SendContinue(
        params, currentKey, currentObjectStoreKey));
  }
}

template <IDBCursorType CursorType>
void BackgroundCursorChild<CursorType>::CompleteContinueRequestFromCache() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mTransaction);
  MOZ_ASSERT(mCursor);
  MOZ_ASSERT(mStrongCursor);
  MOZ_ASSERT(!mDelayedResponses.empty());
  MOZ_ASSERT(mCursor->GetType() == CursorType);

  const RefPtr<IDBCursor> cursor = std::move(mStrongCursor);

  mCursor->Reset(std::move(mDelayedResponses.front()));
  mDelayedResponses.pop_front();

  IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
      "PRELOAD: Consumed 1 cached response, %zu cached responses remaining",
      "Consumed cached response, %zu remaining",
      mTransaction->LoggingSerialNumber(), GetRequest()->LoggingSerialNumber(),
      mDelayedResponses.size() + mCachedResponses.size());

  SetResultAndDispatchSuccessEvent(
      GetRequest(),
      mTransaction
          ? SafeRefPtr{&mTransaction.ref(), AcquireStrongRefFromRawPtr{}}
          : nullptr,
      *cursor);

  mTransaction->OnRequestFinished(/* aRequestCompletedSuccessfully */ true);
}

template <IDBCursorType CursorType>
void BackgroundCursorChild<CursorType>::SendDeleteMeInternal() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(!mStrongRequest);
  MOZ_ASSERT(!mStrongCursor);

  mRequest.destroy();
  mTransaction = Nothing();
  // TODO: The things until here could be pulled up to
  // BackgroundCursorChildBase.

  mSource.destroy();

  if (mCursor) {
    mCursor->ClearBackgroundActor();
    mCursor = nullptr;

    MOZ_ALWAYS_TRUE(PBackgroundIDBCursorChild::SendDeleteMe());
  }
}

template <IDBCursorType CursorType>
void BackgroundCursorChild<CursorType>::InvalidateCachedResponses() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mTransaction);
  MOZ_ASSERT(mRequest);

  // TODO: With more information on the reason for the invalidation, we might
  // only selectively invalidate cached responses. If the reason is an updated
  // value, we do not need to care for key-only cursors. If the key of the
  // changed entry is not in the remaining range of the cursor, we also do not
  // need to care, etc.

  IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
      "PRELOAD: Invalidating all %zu cached responses", "Invalidating %zu",
      mTransaction->LoggingSerialNumber(), GetRequest()->LoggingSerialNumber(),
      mCachedResponses.size());

  mCachedResponses.clear();

  // We only hold a strong cursor reference in mStrongCursor when
  // continue()/similar has been called. In those cases we expect a response
  // that will be received in the future, and it may include prefetched data
  // that needs to be discarded.
  if (mStrongCursor) {
    IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
        "PRELOAD: Setting flag to invalidate in-flight responses",
        "Set flag to invalidate in-flight responses",
        mTransaction->LoggingSerialNumber(),
        GetRequest()->LoggingSerialNumber());

    mInFlightResponseInvalidationNeeded = true;
  }
}

template <IDBCursorType CursorType>
template <typename Condition>
void BackgroundCursorChild<CursorType>::DiscardCachedResponses(
    const Condition& aConditionFunc) {
  size_t discardedCount = 0;
  while (!mCachedResponses.empty() &&
         aConditionFunc(mCachedResponses.front())) {
    mCachedResponses.pop_front();
    ++discardedCount;
  }
  IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
      "PRELOAD: Discarded %zu cached responses, %zu remaining",
      "Discarded %zu; remaining %zu", mTransaction->LoggingSerialNumber(),
      GetRequest()->LoggingSerialNumber(), discardedCount,
      mCachedResponses.size());
}

void BackgroundCursorChildBase::HandleResponse(nsresult aResponse) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(NS_FAILED(aResponse));
  MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB);
  MOZ_ASSERT(mRequest);
  MOZ_ASSERT(mTransaction);
  MOZ_ASSERT(!mStrongRequest);
  MOZ_ASSERT(!mStrongCursor);

  DispatchErrorEvent(
      GetRequest(), aResponse,
      SafeRefPtr{&mTransaction.ref(), AcquireStrongRefFromRawPtr{}});
}

template <IDBCursorType CursorType>
void BackgroundCursorChild<CursorType>::HandleResponse(
    const void_t& aResponse) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mRequest);
  MOZ_ASSERT(mTransaction);
  MOZ_ASSERT(!mStrongRequest);
  MOZ_ASSERT(!mStrongCursor);

  if (mCursor) {
    mCursor->Reset();
  }

  SetResultAndDispatchSuccessEvent(
      GetRequest(),
      mTransaction
          ? SafeRefPtr{&mTransaction.ref(), AcquireStrongRefFromRawPtr{}}
          : nullptr,
      JS::NullHandleValue);

  if (!mCursor) {
    MOZ_ALWAYS_SUCCEEDS(this->GetActorEventTarget()->Dispatch(
        MakeAndAddRef<DelayedActionRunnable<BackgroundCursorChild<CursorType>>>(
            this, &BackgroundCursorChild::SendDeleteMeInternal),
        NS_DISPATCH_NORMAL));
  }
}

template <IDBCursorType CursorType>
template <typename... Args>
RefPtr<IDBCursor>
BackgroundCursorChild<CursorType>::HandleIndividualCursorResponse(
    const bool aUseAsCurrentResult, Args&&... aArgs) {
  if (mCursor) {
    if (aUseAsCurrentResult) {
      mCursor->Reset(CursorData<CursorType>{std::forward<Args>(aArgs)...});
    } else {
      mCachedResponses.emplace_back(std::forward<Args>(aArgs)...);
    }
    return nullptr;
  }

  MOZ_ASSERT(aUseAsCurrentResult);

  // TODO: This still looks quite dangerous to me. Why is mCursor not a
  // RefPtr?
  auto newCursor = IDBCursor::Create(this, std::forward<Args>(aArgs)...);
  mCursor = newCursor;
  return newCursor;
}

template <IDBCursorType CursorType>
template <typename Func>
void BackgroundCursorChild<CursorType>::HandleMultipleCursorResponses(
    nsTArray<ResponseType>&& aResponses, const Func& aHandleRecord) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mRequest);
  MOZ_ASSERT(mTransaction);
  MOZ_ASSERT(!mStrongRequest);
  MOZ_ASSERT(!mStrongCursor);
  MOZ_ASSERT(aResponses.Length() > 0);

  IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
      "PRELOAD: Received %zu cursor responses", "Received %zu",
      mTransaction->LoggingSerialNumber(), GetRequest()->LoggingSerialNumber(),
      aResponses.Length());
  MOZ_ASSERT_IF(aResponses.Length() > 1, mCachedResponses.empty());

  // If a new cursor is created, we need to keep a reference to it until the
  // SetResultAndDispatchSuccessEvent creates a DOM Binding.
  RefPtr<IDBCursor> strongNewCursor;

  bool isFirst = true;
  for (auto& response : aResponses) {
    IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
        "PRELOAD: Processing response for key %s", "Processing%.0s",
        mTransaction->LoggingSerialNumber(),
        GetRequest()->LoggingSerialNumber(), response.key().GetBuffer().get());

    // TODO: At the moment, we only send a cursor request to the parent if
    // requested by the user code. Therefore, the first result is always used
    // as the current result, and the potential extra results are cached. If
    // we extended this towards preloading in the background, all results
    // might need to be cached.
    auto maybeNewCursor =
        aHandleRecord(/* aUseAsCurrentResult */ isFirst, std::move(response));
    if (maybeNewCursor) {
      MOZ_ASSERT(!strongNewCursor);
      strongNewCursor = std::move(maybeNewCursor);
    }
    isFirst = false;

    if (mInFlightResponseInvalidationNeeded) {
      IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
          "PRELOAD: Discarding remaining responses since "
          "mInFlightResponseInvalidationNeeded is set",
          "Discarding responses", mTransaction->LoggingSerialNumber(),
          GetRequest()->LoggingSerialNumber());

      mInFlightResponseInvalidationNeeded = false;
      break;
    }
  }

  SetResultAndDispatchSuccessEvent(
      GetRequest(),
      mTransaction
          ? SafeRefPtr{&mTransaction.ref(), AcquireStrongRefFromRawPtr{}}
          : nullptr,
      *static_cast<IDBCursor*>(mCursor));
}

template <IDBCursorType CursorType>
void BackgroundCursorChild<CursorType>::HandleResponse(
    nsTArray<ResponseType>&& aResponses) {
  AssertIsOnOwningThread();

  if constexpr (CursorType == IDBCursorType::ObjectStore) {
    MOZ_ASSERT(mTransaction);

    HandleMultipleCursorResponses(
        std::move(aResponses), [this](const bool useAsCurrentResult,
                                      ObjectStoreCursorResponse&& response) {
          // TODO: Maybe move the deserialization of the clone-read-info into
          // the cursor, so that it is only done for records actually accessed,
          // which might not be the case for all cached records.
          return HandleIndividualCursorResponse(
              useAsCurrentResult, std::move(response.key()),
              DeserializeStructuredCloneReadInfo(
                  std::move(response.cloneInfo()), mTransaction->Database(),
                  PreprocessingNotSupported));
        });
  }
  if constexpr (CursorType == IDBCursorType::ObjectStoreKey) {
    HandleMultipleCursorResponses(
        std::move(aResponses), [this](const bool useAsCurrentResult,
                                      ObjectStoreKeyCursorResponse&& response) {
          return HandleIndividualCursorResponse(useAsCurrentResult,
                                                std::move(response.key()));
        });
  }
  if constexpr (CursorType == IDBCursorType::Index) {
    MOZ_ASSERT(mTransaction);

    HandleMultipleCursorResponses(
        std::move(aResponses),
        [this](const bool useAsCurrentResult, IndexCursorResponse&& response) {
          return HandleIndividualCursorResponse(
              useAsCurrentResult, std::move(response.key()),
              std::move(response.sortKey()), std::move(response.objectKey()),
              DeserializeStructuredCloneReadInfo(
                  std::move(response.cloneInfo()), mTransaction->Database(),
                  PreprocessingNotSupported));
        });
  }
  if constexpr (CursorType == IDBCursorType::IndexKey) {
    HandleMultipleCursorResponses(
        std::move(aResponses), [this](const bool useAsCurrentResult,
                                      IndexKeyCursorResponse&& response) {
          return HandleIndividualCursorResponse(
              useAsCurrentResult, std::move(response.key()),
              std::move(response.sortKey()), std::move(response.objectKey()));
        });
  }
}

template <IDBCursorType CursorType>
void BackgroundCursorChild<CursorType>::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();
  MOZ_ASSERT_IF(aWhy == Deletion, !mStrongRequest);
  MOZ_ASSERT_IF(aWhy == Deletion, !mStrongCursor);

  MaybeCollectGarbageOnIPCMessage();

  if (mStrongRequest && !mStrongCursor && mTransaction) {
    mTransaction->OnRequestFinished(/* aRequestCompletedSuccessfully */
                                    aWhy == Deletion);
  }

  if (mCursor) {
    mCursor->ClearBackgroundActor();
#ifdef DEBUG
    mCursor = nullptr;
#endif
  }

#ifdef DEBUG
  mRequest.maybeDestroy();
  mTransaction = Nothing();
  mSource.maybeDestroy();
#endif
}

template <IDBCursorType CursorType>
mozilla::ipc::IPCResult BackgroundCursorChild<CursorType>::RecvResponse(
    CursorResponse&& aResponse) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aResponse.type() != CursorResponse::T__None);
  MOZ_ASSERT(mRequest);
  MOZ_ASSERT(mTransaction);
  MOZ_ASSERT_IF(mCursor, mStrongCursor);
  MOZ_ASSERT_IF(!mCursor, mStrongRequest);

  MaybeCollectGarbageOnIPCMessage();

  const RefPtr<IDBRequest> request = std::move(mStrongRequest);
  Unused << request;  // XXX see Bug 1605075
  const RefPtr<IDBCursor> cursor = std::move(mStrongCursor);
  Unused << cursor;  // XXX see Bug 1605075

  const auto transaction =
      SafeRefPtr{&mTransaction.ref(), AcquireStrongRefFromRawPtr{}};

  switch (aResponse.type()) {
    case CursorResponse::Tnsresult:
      HandleResponse(aResponse.get_nsresult());
      break;

    case CursorResponse::Tvoid_t:
      HandleResponse(aResponse.get_void_t());
      break;

    case CursorResponse::TArrayOfObjectStoreCursorResponse:
      if constexpr (CursorType == IDBCursorType::ObjectStore) {
        HandleResponse(
            std::move(aResponse.get_ArrayOfObjectStoreCursorResponse()));
      } else {
        MOZ_CRASH("Response type mismatch");
      }
      break;

    case CursorResponse::TArrayOfObjectStoreKeyCursorResponse:
      if constexpr (CursorType == IDBCursorType::ObjectStoreKey) {
        HandleResponse(
            std::move(aResponse.get_ArrayOfObjectStoreKeyCursorResponse()));
      } else {
        MOZ_CRASH("Response type mismatch");
      }
      break;

    case CursorResponse::TArrayOfIndexCursorResponse:
      if constexpr (CursorType == IDBCursorType::Index) {
        HandleResponse(std::move(aResponse.get_ArrayOfIndexCursorResponse()));
      } else {
        MOZ_CRASH("Response type mismatch");
      }
      break;

    case CursorResponse::TArrayOfIndexKeyCursorResponse:
      if constexpr (CursorType == IDBCursorType::IndexKey) {
        HandleResponse(
            std::move(aResponse.get_ArrayOfIndexKeyCursorResponse()));
      } else {
        MOZ_CRASH("Response type mismatch");
      }
      break;

    default:
      MOZ_CRASH("Should never get here!");
  }

  transaction->OnRequestFinished(/* aRequestCompletedSuccessfully */ true);

  return IPC_OK();
}

template class BackgroundCursorChild<IDBCursorType::ObjectStore>;
template class BackgroundCursorChild<IDBCursorType::ObjectStoreKey>;
template class BackgroundCursorChild<IDBCursorType::Index>;
template class BackgroundCursorChild<IDBCursorType::IndexKey>;

template <typename T>
NS_IMETHODIMP DelayedActionRunnable<T>::Run() {
  MOZ_ASSERT(mActor);
  mActor->AssertIsOnOwningThread();
  MOZ_ASSERT(mRequest);
  MOZ_ASSERT(mActionFunc);

  (mActor->*mActionFunc)();

  mActor = nullptr;
  mRequest = nullptr;

  return NS_OK;
}

template <typename T>
nsresult DelayedActionRunnable<T>::Cancel() {
  if (NS_WARN_IF(!mActor)) {
    return NS_ERROR_UNEXPECTED;
  }

  // This must always run to clean up our state.
  Run();

  return NS_OK;
}

/*******************************************************************************
 * BackgroundFileHandleChild
 ******************************************************************************/

BackgroundFileHandleChild::BackgroundFileHandleChild(IDBFileHandle* aFileHandle)
    : mTemporaryStrongFileHandle(aFileHandle), mFileHandle(aFileHandle) {
  MOZ_ASSERT(aFileHandle);
  aFileHandle->AssertIsOnOwningThread();

  MOZ_COUNT_CTOR(BackgroundFileHandleChild);
}

BackgroundFileHandleChild::~BackgroundFileHandleChild() {
  AssertIsOnOwningThread();

  MOZ_COUNT_DTOR(BackgroundFileHandleChild);
}

#ifdef DEBUG

void BackgroundFileHandleChild::AssertIsOnOwningThread() const {
  static_cast<BackgroundMutableFileChild*>(Manager())->AssertIsOnOwningThread();
}

#endif  // DEBUG

void BackgroundFileHandleChild::SendDeleteMeInternal() {
  AssertIsOnOwningThread();

  if (mFileHandle) {
    NoteActorDestroyed();

    MOZ_ALWAYS_TRUE(PBackgroundFileHandleChild::SendDeleteMe());
  }
}

void BackgroundFileHandleChild::NoteActorDestroyed() {
  AssertIsOnOwningThread();
  MOZ_ASSERT_IF(mTemporaryStrongFileHandle, mFileHandle);

  if (mFileHandle) {
    mFileHandle->ClearBackgroundActor();

    // Normally this would be DEBUG-only but NoteActorDestroyed is also called
    // from SendDeleteMeInternal. In that case we're going to receive an
    // actual ActorDestroy call later and we don't want to touch a dead
    // object.
    mTemporaryStrongFileHandle = nullptr;
    mFileHandle = nullptr;
  }
}

void BackgroundFileHandleChild::NoteComplete() {
  AssertIsOnOwningThread();
  MOZ_ASSERT_IF(mFileHandle, mTemporaryStrongFileHandle);

  mTemporaryStrongFileHandle = nullptr;
}

void BackgroundFileHandleChild::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();

  NoteActorDestroyed();
}

mozilla::ipc::IPCResult BackgroundFileHandleChild::RecvComplete(
    const bool aAborted) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mFileHandle);

  mFileHandle->FireCompleteOrAbortEvents(aAborted);

  NoteComplete();
  return IPC_OK();
}

PBackgroundFileRequestChild*
BackgroundFileHandleChild::AllocPBackgroundFileRequestChild(
    const FileRequestParams& aParams) {
  MOZ_CRASH(
      "PBackgroundFileRequestChild actors should be manually "
      "constructed!");
}

bool BackgroundFileHandleChild::DeallocPBackgroundFileRequestChild(
    PBackgroundFileRequestChild* aActor) {
  MOZ_ASSERT(aActor);

  delete static_cast<BackgroundFileRequestChild*>(aActor);
  return true;
}

/*******************************************************************************
 * BackgroundFileRequestChild
 ******************************************************************************/

BackgroundFileRequestChild::BackgroundFileRequestChild(
    IDBFileRequest* aFileRequest)
    : mFileRequest(aFileRequest),
      mFileHandle(aFileRequest->GetFileHandle()),
      mActorDestroyed(false) {
  MOZ_ASSERT(aFileRequest);
  aFileRequest->AssertIsOnOwningThread();
  MOZ_ASSERT(mFileHandle);
  mFileHandle->AssertIsOnOwningThread();

  MOZ_COUNT_CTOR(BackgroundFileRequestChild);
}

BackgroundFileRequestChild::~BackgroundFileRequestChild() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(!mFileHandle);

  MOZ_COUNT_DTOR(BackgroundFileRequestChild);
}

#ifdef DEBUG

void BackgroundFileRequestChild::AssertIsOnOwningThread() const {
  MOZ_ASSERT(mFileRequest);
  mFileRequest->AssertIsOnOwningThread();
}

#endif  // DEBUG

void BackgroundFileRequestChild::HandleResponse(nsresult aResponse) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(NS_FAILED(aResponse));
  MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_FILEHANDLE);
  MOZ_ASSERT(mFileHandle);

  DispatchFileHandleErrorEvent(mFileRequest, aResponse, mFileHandle);
}

void BackgroundFileRequestChild::HandleResponse(const nsCString& aResponse) {
  AssertIsOnOwningThread();

  SetFileHandleResultAndDispatchSuccessEvent(mFileRequest, mFileHandle,
                                             &aResponse);
}

void BackgroundFileRequestChild::HandleResponse(
    const FileRequestMetadata& aResponse) {
  AssertIsOnOwningThread();

  SetFileHandleResultAndDispatchSuccessEvent(mFileRequest, mFileHandle,
                                             &aResponse);
}

void BackgroundFileRequestChild::HandleResponse(
    const JS::Handle<JS::Value> aResponse) {
  AssertIsOnOwningThread();

  SetFileHandleResultAndDispatchSuccessEvent(mFileRequest, mFileHandle,
                                             &aResponse);
}

void BackgroundFileRequestChild::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();

  MOZ_ASSERT(!mActorDestroyed);

  mActorDestroyed = true;

  if (mFileHandle) {
    mFileHandle->AssertIsOnOwningThread();

    mFileHandle->OnRequestFinished(/* aActorDestroyedNormally */
                                   aWhy == Deletion);

#ifdef DEBUG
    mFileHandle = nullptr;
#endif
  }
}

mozilla::ipc::IPCResult BackgroundFileRequestChild::Recv__delete__(
    const FileRequestResponse& aResponse) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mFileRequest);
  MOZ_ASSERT(mFileHandle);

  if (mFileHandle->IsAborted()) {
    // Always handle an "error" with ABORT_ERR if the file handle was aborted,
    // even if the request succeeded or failed with another error.
    HandleResponse(NS_ERROR_DOM_FILEHANDLE_ABORT_ERR);
  } else {
    switch (aResponse.type()) {
      case FileRequestResponse::Tnsresult:
        HandleResponse(aResponse.get_nsresult());
        break;

      case FileRequestResponse::TFileRequestReadResponse:
        HandleResponse(aResponse.get_FileRequestReadResponse().data());
        break;

      case FileRequestResponse::TFileRequestWriteResponse:
      case FileRequestResponse::TFileRequestTruncateResponse:
      case FileRequestResponse::TFileRequestFlushResponse:
        HandleResponse(JS::UndefinedHandleValue);
        break;

      case FileRequestResponse::TFileRequestGetMetadataResponse:
        HandleResponse(
            aResponse.get_FileRequestGetMetadataResponse().metadata());
        break;

      default:
        MOZ_CRASH("Unknown response type!");
    }
  }

  mFileHandle->OnRequestFinished(/* aActorDestroyedNormally */ true);

  // Null this out so that we don't try to call OnRequestFinished() again in
  // ActorDestroy.
  mFileHandle = nullptr;

  return IPC_OK();
}

mozilla::ipc::IPCResult BackgroundFileRequestChild::RecvProgress(
    const uint64_t aProgress, const uint64_t aProgressMax) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mFileRequest);

  mFileRequest->FireProgressEvent(aProgress, aProgressMax);

  return IPC_OK();
}

/*******************************************************************************
 * BackgroundUtilsChild
 ******************************************************************************/

BackgroundUtilsChild::BackgroundUtilsChild(IndexedDatabaseManager* aManager)
    : mManager(aManager) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aManager);

  MOZ_COUNT_CTOR(indexedDB::BackgroundUtilsChild);
}

BackgroundUtilsChild::~BackgroundUtilsChild() {
  MOZ_COUNT_DTOR(indexedDB::BackgroundUtilsChild);
}

void BackgroundUtilsChild::SendDeleteMeInternal() {
  AssertIsOnOwningThread();

  if (mManager) {
    mManager->ClearBackgroundActor();
    mManager = nullptr;

    MOZ_ALWAYS_TRUE(PBackgroundIndexedDBUtilsChild::SendDeleteMe());
  }
}

void BackgroundUtilsChild::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();

  if (mManager) {
    mManager->ClearBackgroundActor();
#ifdef DEBUG
    mManager = nullptr;
#endif
  }
}

}  // namespace indexedDB
}  // namespace dom
}  // namespace mozilla