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

#include "IDBDatabase.h"
#include "IDBIndex.h"
#include "IDBObjectStore.h"
#include "IDBRequest.h"
#include "IDBTransaction.h"
#include "IndexedDatabaseInlines.h"
#include "ProfilerHelpers.h"
#include "ReportInternalError.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/dom/UnionTypes.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h"
#include "nsString.h"

// Include this last to avoid path problems on Windows.
#include "ActorsChild.h"

namespace mozilla::dom {

using namespace indexedDB;

IDBCursor::IDBCursor(BackgroundCursorChildBase* const aBackgroundActor)
    : mBackgroundActor(WrapNotNull(aBackgroundActor)),
      mRequest(aBackgroundActor->GetRequest()),
      mTransaction(&mRequest->MutableTransactionRef()),
      mCachedKey(JS::UndefinedValue()),
      mCachedPrimaryKey(JS::UndefinedValue()),
      mCachedValue(JS::UndefinedValue()),
      mDirection(aBackgroundActor->GetDirection()),
      mHaveCachedKey(false),
      mHaveCachedPrimaryKey(false),
      mHaveCachedValue(false),
      mRooted(false),
      mContinueCalled(false),
      mHaveValue(true) {
  MOZ_ASSERT(aBackgroundActor);
  aBackgroundActor->AssertIsOnOwningThread();
  MOZ_ASSERT(mRequest);

  mTransaction->RegisterCursor(*this);
}

template <IDBCursor::Type CursorType>
IDBTypedCursor<CursorType>::~IDBTypedCursor() {
  AssertIsOnOwningThread();

  mTransaction->UnregisterCursor(*this);

  DropJSObjects();

  if (mBackgroundActor) {
    (*mBackgroundActor)->SendDeleteMeInternal();
    MOZ_ASSERT(!mBackgroundActor, "SendDeleteMeInternal should have cleared!");
  }

  // Let's explicitly not leave any dangling CheckedUnsafePtr.
  mTransaction = nullptr;
}

// static
RefPtr<IDBObjectStoreCursor> IDBCursor::Create(
    BackgroundCursorChild<Type::ObjectStore>* const aBackgroundActor, Key aKey,
    StructuredCloneReadInfoChild&& aCloneInfo) {
  MOZ_ASSERT(aBackgroundActor);
  aBackgroundActor->AssertIsOnOwningThread();
  MOZ_ASSERT(!aKey.IsUnset());

  return MakeRefPtr<IDBObjectStoreCursor>(aBackgroundActor, std::move(aKey),
                                          std::move(aCloneInfo));
}

// static
RefPtr<IDBObjectStoreKeyCursor> IDBCursor::Create(
    BackgroundCursorChild<Type::ObjectStoreKey>* const aBackgroundActor,
    Key aKey) {
  MOZ_ASSERT(aBackgroundActor);
  aBackgroundActor->AssertIsOnOwningThread();
  MOZ_ASSERT(!aKey.IsUnset());

  return MakeRefPtr<IDBObjectStoreKeyCursor>(aBackgroundActor, std::move(aKey));
}

// static
RefPtr<IDBIndexCursor> IDBCursor::Create(
    BackgroundCursorChild<Type::Index>* const aBackgroundActor, Key aKey,
    Key aSortKey, Key aPrimaryKey, StructuredCloneReadInfoChild&& aCloneInfo) {
  MOZ_ASSERT(aBackgroundActor);
  aBackgroundActor->AssertIsOnOwningThread();
  MOZ_ASSERT(!aKey.IsUnset());
  MOZ_ASSERT(!aPrimaryKey.IsUnset());

  return MakeRefPtr<IDBIndexCursor>(aBackgroundActor, std::move(aKey),
                                    std::move(aSortKey), std::move(aPrimaryKey),
                                    std::move(aCloneInfo));
}

// static
RefPtr<IDBIndexKeyCursor> IDBCursor::Create(
    BackgroundCursorChild<Type::IndexKey>* const aBackgroundActor, Key aKey,
    Key aSortKey, Key aPrimaryKey) {
  MOZ_ASSERT(aBackgroundActor);
  aBackgroundActor->AssertIsOnOwningThread();
  MOZ_ASSERT(!aKey.IsUnset());
  MOZ_ASSERT(!aPrimaryKey.IsUnset());

  return MakeRefPtr<IDBIndexKeyCursor>(aBackgroundActor, std::move(aKey),
                                       std::move(aSortKey),
                                       std::move(aPrimaryKey));
}

#ifdef DEBUG

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

#endif  // DEBUG

template <IDBCursor::Type CursorType>
void IDBTypedCursor<CursorType>::DropJSObjects() {
  AssertIsOnOwningThread();

  Reset();

  if (!mRooted) {
    return;
  }

  mRooted = false;

  mozilla::DropJSObjects(this);
}

template <IDBCursor::Type CursorType>
bool IDBTypedCursor<CursorType>::IsSourceDeleted() const {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mTransaction);
  MOZ_ASSERT(mTransaction->IsActive());

  const auto* const sourceObjectStore = [this]() -> const IDBObjectStore* {
    if constexpr (IsObjectStoreCursor) {
      return mSource;
    } else {
      if (GetSourceRef().IsDeleted()) {
        return nullptr;
      }

      const auto* const res = GetSourceRef().ObjectStore();
      MOZ_ASSERT(res);
      return res;
    }
  }();

  return !sourceObjectStore || sourceObjectStore->IsDeleted();
}

void IDBCursor::ResetBase() {
  AssertIsOnOwningThread();

  mCachedKey.setUndefined();
  mCachedPrimaryKey.setUndefined();
  mCachedValue.setUndefined();

  mHaveCachedKey = false;
  mHaveCachedPrimaryKey = false;
  mHaveCachedValue = false;
  mHaveValue = false;
  mContinueCalled = false;
}

template <IDBCursor::Type CursorType>
void IDBTypedCursor<CursorType>::Reset() {
  AssertIsOnOwningThread();

  if constexpr (!IsKeyOnlyCursor) {
    // May run after GetValue() already deserialized and cleared mCloneInfo in
    // place (via DeserializeValue's scope-exit guard). That's fine:
    // ClearCloneReadInfo early-returns once the files are released, so this is
    // a no-op in that case, and still releases the files when GetValue() was
    // never called.
    IDBObjectStore::ClearCloneReadInfo(mData.mCloneInfo);
  }

  ResetBase();
}

nsIGlobalObject* IDBCursor::GetParentObject() const {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mTransaction);

  return mTransaction->GetParentObject();
}

IDBCursorDirection IDBCursor::GetDirection() const {
  AssertIsOnOwningThread();

  switch (mDirection) {
    case Direction::Next:
      return IDBCursorDirection::Next;

    case Direction::Nextunique:
      return IDBCursorDirection::Nextunique;

    case Direction::Prev:
      return IDBCursorDirection::Prev;

    case Direction::Prevunique:
      return IDBCursorDirection::Prevunique;

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

RefPtr<IDBRequest> IDBCursor::Request() const {
  AssertIsOnOwningThread();
  return mRequest;
}

template <IDBCursor::Type CursorType>
void IDBTypedCursor<CursorType>::GetSource(
    OwningIDBObjectStoreOrIDBIndex& aSource) const {
  AssertIsOnOwningThread();

  if constexpr (IsObjectStoreCursor) {
    aSource.SetAsIDBObjectStore() = mSource;
  } else {
    aSource.SetAsIDBIndex() = mSource;
  }
}

template <IDBCursor::Type CursorType>
void IDBTypedCursor<CursorType>::GetKey(JSContext* const aCx,
                                        JS::MutableHandle<JS::Value> aResult,
                                        ErrorResult& aRv) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(!mData.mKey.IsUnset() || !mHaveValue);

  if (!mHaveValue) {
    aResult.setUndefined();
    return;
  }

  if (!mHaveCachedKey) {
    if (!mRooted) {
      mozilla::HoldJSObjects(this);
      mRooted = true;
    }

    aRv = mData.mKey.ToJSVal(aCx, mCachedKey);
    if (NS_WARN_IF(aRv.Failed())) {
      return;
    }

    mHaveCachedKey = true;
  }

  aResult.set(mCachedKey);
}

template <IDBCursor::Type CursorType>
void IDBTypedCursor<CursorType>::GetPrimaryKey(
    JSContext* const aCx, JS::MutableHandle<JS::Value> aResult,
    ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (!mHaveValue) {
    aResult.setUndefined();
    return;
  }

  if (!mHaveCachedPrimaryKey) {
    if (!mRooted) {
      mozilla::HoldJSObjects(this);
      mRooted = true;
    }

    const Key& key = mData.GetObjectStoreKey();

    MOZ_ASSERT(!key.IsUnset());

    aRv = key.ToJSVal(aCx, mCachedPrimaryKey);
    if (NS_WARN_IF(aRv.Failed())) {
      return;
    }

    mHaveCachedPrimaryKey = true;
  }

  aResult.set(mCachedPrimaryKey);
}

template <IDBCursor::Type CursorType>
void IDBTypedCursor<CursorType>::GetValue(JSContext* const aCx,
                                          JS::MutableHandle<JS::Value> aResult,
                                          ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if constexpr (!IsKeyOnlyCursor) {
    if (!mHaveValue) {
      aResult.setUndefined();
      return;
    }

    if (!mHaveCachedValue) {
      if (!mRooted) {
        mozilla::HoldJSObjects(this);
        mRooted = true;
      }

      JS::Rooted<JS::Value> val(aCx);
      if (NS_WARN_IF(!IDBObjectStore::DeserializeValue(
              aCx, std::move(mData.mCloneInfo), &val))) {
        aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
        return;
      }

      mCachedValue = val;
      mHaveCachedValue = true;
    }

    aResult.set(mCachedValue);
  } else {
    MOZ_CRASH("This shouldn't be callable on a key-only cursor.");
  }
}

template <IDBCursor::Type CursorType>
void IDBTypedCursor<CursorType>::Continue(JSContext* const aCx,
                                          JS::Handle<JS::Value> aKey,
                                          ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (!mTransaction->IsActive()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return;
  }

  if (IsSourceDeleted() || !mHaveValue || mContinueCalled) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return;
  }

  Key key;
  auto result = key.SetFromJSVal(aCx, aKey, mTransaction);
  if (result.isErr()) {
    aRv = result.unwrapErr().ExtractErrorResult(
        InvalidMapsTo<NS_ERROR_DOM_INDEXEDDB_DATA_ERR>);
    return;
  }

  if constexpr (!IsObjectStoreCursor) {
    if (IsLocaleAware() && !key.IsUnset()) {
      auto result = key.ToLocaleAwareKey(GetSourceRef().Locale());
      if (result.isErr()) {
        aRv.Throw(result.inspectErr());
        return;
      }
      key = result.unwrap();
    }
  }

  const Key& sortKey = mData.GetSortKey(IsLocaleAware());

  if (!key.IsUnset()) {
    switch (mDirection) {
      case Direction::Next:
      case Direction::Nextunique:
        if (key <= sortKey) {
          aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
          return;
        }
        break;

      case Direction::Prev:
      case Direction::Prevunique:
        if (key >= sortKey) {
          aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
          return;
        }
        break;

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

  const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
  mRequest->SetLoggingSerialNumber(requestSerialNumber);

  if constexpr (IsObjectStoreCursor) {
    IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
        "database(%s).transaction(%s).objectStore(%s)."
        "cursor(%s).continue(%s)",
        "IDBCursor.continue(%.0s%.0s%.0s%.0s%.0s)",
        mTransaction->LoggingSerialNumber(), requestSerialNumber,
        IDB_LOG_STRINGIFY(mTransaction->Database()),
        IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(mSource),
        IDB_LOG_STRINGIFY(mDirection), IDB_LOG_STRINGIFY(key));
  } else {
    IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
        "database(%s).transaction(%s).objectStore(%s)."
        "index(%s).cursor(%s).continue(%s)",
        "IDBCursor.continue(%.0s%.0s%.0s%.0s%.0s%.0s)",
        mTransaction->LoggingSerialNumber(), requestSerialNumber,
        IDB_LOG_STRINGIFY(mTransaction->Database()),
        IDB_LOG_STRINGIFY(*mTransaction),
        IDB_LOG_STRINGIFY(GetSourceRef().ObjectStore()),
        IDB_LOG_STRINGIFY(mSource), IDB_LOG_STRINGIFY(mDirection),
        IDB_LOG_STRINGIFY(key));
  }

  GetTypedBackgroundActorRef().SendContinueInternal(
      mTransaction->NextRequestId(), ContinueParams(key), mData);

  mContinueCalled = true;
}

template <IDBCursor::Type CursorType>
void IDBTypedCursor<CursorType>::ContinuePrimaryKey(
    JSContext* const aCx, JS::Handle<JS::Value> aKey,
    JS::Handle<JS::Value> aPrimaryKey, ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (!mTransaction->IsActive()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return;
  }

  if (IsSourceDeleted()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return;
  }

  if (IsObjectStoreCursor ||
      (mDirection != Direction::Next && mDirection != Direction::Prev)) {
    aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
    return;
  }

  if constexpr (!IsObjectStoreCursor) {
    if (!mHaveValue || mContinueCalled) {
      aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
      return;
    }

    Key key;
    auto result = key.SetFromJSVal(aCx, aKey, mTransaction);
    if (result.isErr()) {
      aRv = result.unwrapErr().ExtractErrorResult(
          InvalidMapsTo<NS_ERROR_DOM_INDEXEDDB_DATA_ERR>);
      return;
    }

    if (IsLocaleAware() && !key.IsUnset()) {
      auto result = key.ToLocaleAwareKey(GetSourceRef().Locale());
      if (result.isErr()) {
        aRv.Throw(result.inspectErr());
        return;
      }
      key = result.unwrap();
    }

    if (key.IsUnset()) {
      aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
      return;
    }

    Key primaryKey;
    result = primaryKey.SetFromJSVal(aCx, aPrimaryKey, mTransaction);
    if (result.isErr()) {
      aRv = result.unwrapErr().ExtractErrorResult(
          InvalidMapsTo<NS_ERROR_DOM_INDEXEDDB_DATA_ERR>);
      return;
    }

    if (primaryKey.IsUnset()) {
      aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
      return;
    }

    const Key& sortKey = mData.GetSortKey(IsLocaleAware());

    switch (mDirection) {
      case Direction::Next:
        if (key < sortKey ||
            (key == sortKey && primaryKey <= mData.mObjectStoreKey)) {
          aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
          return;
        }
        break;

      case Direction::Prev:
        if (key > sortKey ||
            (key == sortKey && primaryKey >= mData.mObjectStoreKey)) {
          aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
          return;
        }
        break;

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

    const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
    mRequest->SetLoggingSerialNumber(requestSerialNumber);

    IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
        "database(%s).transaction(%s).objectStore(%s)."
        "index(%s).cursor(%s).continuePrimaryKey(%s, %s)",
        "IDBCursor.continuePrimaryKey(%.0s%.0s%.0s%.0s%.0s%.0s%.0s)",
        mTransaction->LoggingSerialNumber(), requestSerialNumber,
        IDB_LOG_STRINGIFY(mTransaction->Database()),
        IDB_LOG_STRINGIFY(*mTransaction),
        IDB_LOG_STRINGIFY(&GetSourceObjectStoreRef()),
        IDB_LOG_STRINGIFY(mSource), IDB_LOG_STRINGIFY(mDirection),
        IDB_LOG_STRINGIFY(key), IDB_LOG_STRINGIFY(primaryKey));

    GetTypedBackgroundActorRef().SendContinueInternal(
        mTransaction->NextRequestId(),
        ContinuePrimaryKeyParams(key, primaryKey), mData);

    mContinueCalled = true;
  }
}

template <IDBCursor::Type CursorType>
void IDBTypedCursor<CursorType>::Advance(const uint32_t aCount,
                                         ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (!aCount) {
    aRv.ThrowTypeError("0 (Zero) is not a valid advance count.");
    return;
  }

  if (!mTransaction->IsActive()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return;
  }

  if (IsSourceDeleted() || !mHaveValue || mContinueCalled) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return;
  }

  const uint64_t requestSerialNumber = IDBRequest::NextSerialNumber();
  mRequest->SetLoggingSerialNumber(requestSerialNumber);

  if constexpr (IsObjectStoreCursor) {
    IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
        "database(%s).transaction(%s).objectStore(%s)."
        "cursor(%s).advance(%" PRIi32 ")",
        "IDBCursor.advance(%.0s%.0s%.0s%.0s%" PRIi32 ")",
        mTransaction->LoggingSerialNumber(), requestSerialNumber,
        IDB_LOG_STRINGIFY(mTransaction->Database()),
        IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(mSource),
        IDB_LOG_STRINGIFY(mDirection), aCount);
  } else {
    IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
        "database(%s).transaction(%s).objectStore(%s)."
        "index(%s).cursor(%s).advance(%" PRIi32 ")",
        "IDBCursor.advance(%.0s%.0s%.0s%.0s%.0s%" PRIi32 ")",
        mTransaction->LoggingSerialNumber(), requestSerialNumber,
        IDB_LOG_STRINGIFY(mTransaction->Database()),
        IDB_LOG_STRINGIFY(*mTransaction),
        IDB_LOG_STRINGIFY(GetSourceRef().ObjectStore()),
        IDB_LOG_STRINGIFY(mSource), IDB_LOG_STRINGIFY(mDirection), aCount);
  }

  GetTypedBackgroundActorRef().SendContinueInternal(
      mTransaction->NextRequestId(), AdvanceParams(aCount), mData);

  mContinueCalled = true;
}

template <IDBCursor::Type CursorType>
RefPtr<IDBRequest> IDBTypedCursor<CursorType>::Update(
    JSContext* const aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (!mTransaction->IsActive()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return nullptr;
  }

  if (!mTransaction->IsWriteAllowed()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
    return nullptr;
  }

  if (mTransaction->GetMode() == IDBTransaction::Mode::Cleanup ||
      IsSourceDeleted() || !mHaveValue || IsKeyOnlyCursor || mContinueCalled) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return nullptr;
  }

  if constexpr (!IsKeyOnlyCursor) {
    MOZ_ASSERT(!mData.mKey.IsUnset());
    if constexpr (!IsObjectStoreCursor) {
      MOZ_ASSERT(!mData.mObjectStoreKey.IsUnset());
    }

    mTransaction->InvalidateCursorCaches();

    IDBObjectStore::ValueWrapper valueWrapper(aCx, aValue);

    const Key& primaryKey = mData.GetObjectStoreKey();

    RefPtr<IDBRequest> request;

    IDBObjectStore& objectStore = GetSourceObjectStoreRef();
    if (objectStore.HasValidKeyPath()) {
      if (!valueWrapper.Clone(aCx, mTransaction)) {
        aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
        return nullptr;
      }

      // Make sure the object given has the correct keyPath value set on it.
      const KeyPath& keyPath = objectStore.GetKeyPath();
      Key key;

      aRv = keyPath.ExtractKey(aCx, valueWrapper.Value(), key);
      if (aRv.Failed()) {
        return nullptr;
      }

      if (key != primaryKey) {
        aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
        return nullptr;
      }

      request = objectStore.AddOrPut(aCx, valueWrapper,
                                     /* aKey */ JS::UndefinedHandleValue,
                                     /* aOverwrite */ true,
                                     /* aFromCursor */ true, aRv);
      if (aRv.Failed()) {
        return nullptr;
      }
    } else {
      JS::Rooted<JS::Value> keyVal(aCx);
      aRv = primaryKey.ToJSVal(aCx, &keyVal);
      if (aRv.Failed()) {
        return nullptr;
      }

      request = objectStore.AddOrPut(aCx, valueWrapper, keyVal,
                                     /* aOverwrite */ true,
                                     /* aFromCursor */ true, aRv);
      if (aRv.Failed()) {
        return nullptr;
      }
    }

    request->SetSource(this);

    if (IsObjectStoreCursor) {
      IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
          "database(%s).transaction(%s).objectStore(%s)."
          "cursor(%s).update(%s)",
          "IDBCursor.update(%.0s%.0s%.0s%.0s%.0s)",
          mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
          IDB_LOG_STRINGIFY(mTransaction->Database()),
          IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(&objectStore),
          IDB_LOG_STRINGIFY(mDirection),
          IDB_LOG_STRINGIFY(&objectStore, primaryKey));
    } else {
      IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
          "database(%s).transaction(%s).objectStore(%s)."
          "index(%s).cursor(%s).update(%s)",
          "IDBCursor.update(%.0s%.0s%.0s%.0s%.0s%.0s)",
          mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
          IDB_LOG_STRINGIFY(mTransaction->Database()),
          IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(&objectStore),
          IDB_LOG_STRINGIFY(mSource), IDB_LOG_STRINGIFY(mDirection),
          IDB_LOG_STRINGIFY(&objectStore, primaryKey));
    }

    return request;
  } else {
    // XXX: Just to work around a bug in gcc, which otherwise claims 'control
    // reaches end of non-void function', which is not true.
    return nullptr;
  }
}

template <IDBCursor::Type CursorType>
RefPtr<IDBRequest> IDBTypedCursor<CursorType>::Delete(JSContext* const aCx,
                                                      ErrorResult& aRv) {
  AssertIsOnOwningThread();

  if (!mTransaction->IsActive()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
    return nullptr;
  }

  if (!mTransaction->IsWriteAllowed()) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
    return nullptr;
  }

  if (IsSourceDeleted() || !mHaveValue || IsKeyOnlyCursor || mContinueCalled) {
    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
    return nullptr;
  }

  if constexpr (!IsKeyOnlyCursor) {
    MOZ_ASSERT(!mData.mKey.IsUnset());

    mTransaction->InvalidateCursorCaches();

    const Key& primaryKey = mData.GetObjectStoreKey();

    JS::Rooted<JS::Value> key(aCx);
    aRv = primaryKey.ToJSVal(aCx, &key);
    if (NS_WARN_IF(aRv.Failed())) {
      return nullptr;
    }

    auto& objectStore = GetSourceObjectStoreRef();
    RefPtr<IDBRequest> request =
        objectStore.DeleteInternal(aCx, key, /* aFromCursor */ true, aRv);
    if (aRv.Failed()) {
      return nullptr;
    }

    request->SetSource(this);

    if (IsObjectStoreCursor) {
      IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
          "database(%s).transaction(%s).objectStore(%s)."
          "cursor(%s).delete(%s)",
          "IDBCursor.delete(%.0s%.0s%.0s%.0s%.0s)",
          mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
          IDB_LOG_STRINGIFY(mTransaction->Database()),
          IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(&objectStore),
          IDB_LOG_STRINGIFY(mDirection),
          IDB_LOG_STRINGIFY(&objectStore, primaryKey));
    } else {
      IDB_LOG_MARK_CHILD_TRANSACTION_REQUEST(
          "database(%s).transaction(%s).objectStore(%s)."
          "index(%s).cursor(%s).delete(%s)",
          "IDBCursor.delete(%.0s%.0s%.0s%.0s%.0s%.0s)",
          mTransaction->LoggingSerialNumber(), request->LoggingSerialNumber(),
          IDB_LOG_STRINGIFY(mTransaction->Database()),
          IDB_LOG_STRINGIFY(*mTransaction), IDB_LOG_STRINGIFY(&objectStore),
          IDB_LOG_STRINGIFY(mSource), IDB_LOG_STRINGIFY(mDirection),
          IDB_LOG_STRINGIFY(&objectStore, primaryKey));
    }

    return request;
  } else {
    // XXX: Just to work around a bug in gcc, which otherwise claims 'control
    // reaches end of non-void function', which is not true.
    return nullptr;
  }
}

template <IDBCursor::Type CursorType>
void IDBTypedCursor<CursorType>::Reset(CursorData<CursorType>&& aCursorData) {
  this->AssertIsOnOwningThread();

  Reset();

  mData = std::move(aCursorData);

  mHaveValue = !mData.mKey.IsUnset();
}

template <IDBCursor::Type CursorType>
void IDBTypedCursor<CursorType>::InvalidateCachedResponses() {
  AssertIsOnOwningThread();

  // TODO: Can mBackgroundActor actually be empty at this point?
  if (mBackgroundActor) {
    GetTypedBackgroundActorRef().InvalidateCachedResponses();
  }
}

NS_IMPL_CYCLE_COLLECTING_ADDREF(IDBCursor)
NS_IMPL_CYCLE_COLLECTING_RELEASE(IDBCursor)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBCursor)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTION_CLASS(IDBCursor)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IDBCursor)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRequest)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(IDBCursor)
  MOZ_ASSERT_IF(!tmp->mHaveCachedKey, tmp->mCachedKey.isUndefined());
  MOZ_ASSERT_IF(!tmp->mHaveCachedPrimaryKey,
                tmp->mCachedPrimaryKey.isUndefined());
  MOZ_ASSERT_IF(!tmp->mHaveCachedValue, tmp->mCachedValue.isUndefined());

  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedKey)
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedPrimaryKey)
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedValue)
NS_IMPL_CYCLE_COLLECTION_TRACE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IDBCursor)
// Unlinking is done in the subclasses.
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

// Don't unlink mRequest or mSource in
// NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED!
#define NS_IMPL_CYCLE_COLLECTION_IDBCURSOR_SUBCLASS_METHODS(_subclassName)    \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(_subclassName, IDBCursor) \
    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource)                                \
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END                                       \
                                                                              \
  NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(_subclassName, IDBCursor)   \
    NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER                         \
    tmp->DropJSObjects();                                                     \
  NS_IMPL_CYCLE_COLLECTION_UNLINK_END                                         \
                                                                              \
  NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(_subclassName)                      \
  NS_INTERFACE_MAP_END_INHERITING(IDBCursor)                                  \
                                                                              \
  NS_IMPL_ADDREF_INHERITED(_subclassName, IDBCursor)                          \
  NS_IMPL_RELEASE_INHERITED(_subclassName, IDBCursor)

#define NS_IMPL_CYCLE_COLLECTION_IDBCURSOR_SUBCLASS(_subclassName) \
  NS_IMPL_CYCLE_COLLECTION_CLASS(_subclassName)                    \
  NS_IMPL_CYCLE_COLLECTION_IDBCURSOR_SUBCLASS_METHODS(_subclassName)

NS_IMPL_CYCLE_COLLECTION_IDBCURSOR_SUBCLASS(IDBObjectStoreCursor)
NS_IMPL_CYCLE_COLLECTION_IDBCURSOR_SUBCLASS(IDBObjectStoreKeyCursor)
NS_IMPL_CYCLE_COLLECTION_IDBCURSOR_SUBCLASS(IDBIndexCursor)
NS_IMPL_CYCLE_COLLECTION_IDBCURSOR_SUBCLASS(IDBIndexKeyCursor)

template <IDBCursor::Type CursorType>
JSObject* IDBTypedCursor<CursorType>::WrapObject(
    JSContext* const aCx, JS::Handle<JSObject*> aGivenProto) {
  AssertIsOnOwningThread();

  return IsKeyOnlyCursor
             ? IDBCursor_Binding::Wrap(aCx, this, aGivenProto)
             : IDBCursorWithValue_Binding::Wrap(aCx, this, aGivenProto);
}

template <IDBCursor::Type CursorType>
template <typename... DataArgs>
IDBTypedCursor<CursorType>::IDBTypedCursor(
    indexedDB::BackgroundCursorChild<CursorType>* const aBackgroundActor,
    DataArgs&&... aDataArgs)
    : IDBCursor{aBackgroundActor},
      mData{std::forward<DataArgs>(aDataArgs)...},
      mSource(aBackgroundActor->GetSource()) {}

template <IDBCursor::Type CursorType>
bool IDBTypedCursor<CursorType>::IsLocaleAware() const {
  if constexpr (IsObjectStoreCursor) {
    return false;
  } else {
    return !GetSourceRef().Locale().IsEmpty();
  }
}

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

}  // namespace mozilla::dom
