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

#ifndef mozilla_dom_idbrequest_h_
#define mozilla_dom_idbrequest_h_

#include "ReportInternalError.h"
#include "SafeRefPtr.h"
#include "js/RootingAPI.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/EventForwards.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/SourceLocation.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/IDBRequestBinding.h"
#include "mozilla/dom/ScriptSettings.h"
#include "nsCycleCollectionParticipant.h"

#define PRIVATE_IDBREQUEST_IID \
  {0xe68901e5, 0x1d50, 0x4ee9, {0xaf, 0x49, 0x90, 0x99, 0x4a, 0xff, 0xc8, 0x39}}

class nsIGlobalObject;

namespace mozilla {

class ErrorResult;

namespace dom {

class IDBCursor;
class IDBDatabase;
class IDBFactory;
class IDBIndex;
class IDBObjectStore;
class IDBTransaction;
template <typename>
struct Nullable;
class OwningIDBObjectStoreOrIDBIndexOrIDBCursor;
class StrongWorkerRef;

namespace detail {
// This class holds the IID for use with NS_GET_IID.
class PrivateIDBRequest {
 public:
  NS_INLINE_DECL_STATIC_IID(PRIVATE_IDBREQUEST_IID)
};

}  // namespace detail

class IDBRequest : public DOMEventTargetHelper {
 protected:
  // mSourceAsObjectStore and mSourceAsIndex are exclusive and one must always
  // be set. mSourceAsCursor is sometimes set also.
  RefPtr<IDBObjectStore> mSourceAsObjectStore;
  RefPtr<IDBIndex> mSourceAsIndex;
  RefPtr<IDBCursor> mSourceAsCursor;

  SafeRefPtr<IDBTransaction> mTransaction;

  JS::Heap<JS::Value> mResultVal;
  RefPtr<DOMException> mError;

  JSCallingLocation mCallerLocation;
  uint64_t mLoggingSerialNumber;
  nsresult mErrorCode;
  bool mHaveResultOrErrorCode;

 public:
  [[nodiscard]] static MovingNotNull<RefPtr<IDBRequest>> Create(
      JSContext* aCx, IDBDatabase* aDatabase,
      SafeRefPtr<IDBTransaction> aTransaction);

  [[nodiscard]] static MovingNotNull<RefPtr<IDBRequest>> Create(
      JSContext* aCx, IDBObjectStore* aSource, IDBDatabase* aDatabase,
      SafeRefPtr<IDBTransaction> aTransaction);

  [[nodiscard]] static MovingNotNull<RefPtr<IDBRequest>> Create(
      JSContext* aCx, IDBIndex* aSource, IDBDatabase* aDatabase,
      SafeRefPtr<IDBTransaction> aTransaction);

  static uint64_t NextSerialNumber();

  // EventTarget
  void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;

  void GetSource(
      Nullable<OwningIDBObjectStoreOrIDBIndexOrIDBCursor>& aSource) const;

  void Reset();

  template <typename ResultCallback>
  void SetResult(const ResultCallback& aCallback) {
    AssertIsOnOwningThread();
    MOZ_ASSERT(!mHaveResultOrErrorCode);
    MOZ_ASSERT(mResultVal.isUndefined());
    MOZ_ASSERT(!mError);

    // Already disconnected from the owner.
    if (!GetRelevantGlobal()) {
      SetError(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
      return;
    }

    // See this global is still valid.
    if (NS_WARN_IF(NS_FAILED(CheckCurrentGlobalCorrectness()))) {
      SetError(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
      return;
    }

    AutoJSAPI autoJS;
    if (!autoJS.Init(GetRelevantGlobal())) {
      IDB_WARNING("Failed to initialize AutoJSAPI!");
      SetError(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
      return;
    }

    JSContext* cx = autoJS.cx();

    JS::Rooted<JS::Value> result(cx);
    nsresult rv = aCallback(cx, &result);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      // This can only fail if the structured clone contains a mutable file
      // and the child is not in the main thread and main process.
      // In that case CreateAndWrapMutableFile() returns false which shows up
      // as NS_ERROR_DOM_DATA_CLONE_ERR here.
      MOZ_ASSERT(rv == NS_ERROR_DOM_DATA_CLONE_ERR);

      // We are not setting a result or an error object here since we want to
      // throw an exception when the 'result' property is being touched.
      return;
    }

    mError = nullptr;

    mResultVal = result;
    mozilla::HoldJSObjects(this);

    mHaveResultOrErrorCode = true;
  }

  void SetError(nsresult aRv, const nsACString& aMessage = EmptyCString());

  nsresult GetErrorCode() const
#ifdef DEBUG
      ;
#else
  {
    return mErrorCode;
  }
#endif

  DOMException* GetErrorAfterResult() const
#ifdef DEBUG
      ;
#else
  {
    return mError;
  }
#endif

  DOMException* GetError(ErrorResult& aRv);

  const JSCallingLocation& GetCallerLocation() const { return mCallerLocation; }

  bool IsPending() const { return !mHaveResultOrErrorCode; }

  uint64_t LoggingSerialNumber() const {
    AssertIsOnOwningThread();

    return mLoggingSerialNumber;
  }

  void SetLoggingSerialNumber(uint64_t aLoggingSerialNumber);

  nsIGlobalObject* GetParentObject() const { return GetRelevantGlobal(); }

  void GetResult(JS::MutableHandle<JS::Value> aResult, ErrorResult& aRv) const;

  void GetResult(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
                 ErrorResult& aRv) const {
    GetResult(aResult, aRv);
  }

  Maybe<IDBTransaction&> MaybeTransactionRef() const {
    AssertIsOnOwningThread();

    return mTransaction.maybeDeref();
  }

  IDBTransaction& MutableTransactionRef() const {
    AssertIsOnOwningThread();

    return *mTransaction;
  }

  SafeRefPtr<IDBTransaction> AcquireTransaction() const {
    AssertIsOnOwningThread();

    return mTransaction.clonePtr();
  }

  // For WebIDL binding.
  RefPtr<IDBTransaction> GetTransaction() const {
    AssertIsOnOwningThread();

    return AsRefPtr(mTransaction.clonePtr());
  }

  IDBRequestReadyState ReadyState() const;

  void SetSource(IDBCursor* aSource);

  IMPL_EVENT_HANDLER(success);
  IMPL_EVENT_HANDLER(error);

  void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(IDBRequest); }

  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(IDBRequest,
                                                         DOMEventTargetHelper)

  // nsWrapperCache
  virtual JSObject* WrapObject(JSContext* aCx,
                               JS::Handle<JSObject*> aGivenProto) override;

 protected:
  explicit IDBRequest(IDBDatabase* aDatabase);
  explicit IDBRequest(nsIGlobalObject* aGlobal);
  ~IDBRequest();

  void InitMembers();

  void ConstructResult();
};

class IDBOpenDBRequest final : public IDBRequest {
  // Only touched on the owning thread.
  SafeRefPtr<IDBFactory> mFactory;

  RefPtr<StrongWorkerRef> mWorkerRef;

  bool mIncreasedActiveDatabaseCount;

 public:
  [[nodiscard]] static RefPtr<IDBOpenDBRequest> Create(
      JSContext* aCx, SafeRefPtr<IDBFactory> aFactory,
      nsIGlobalObject* aGlobal);

  void SetTransaction(SafeRefPtr<IDBTransaction> aTransaction);

  void DispatchNonTransactionError(nsresult aErrorCode);

  void NoteComplete();

  // EventTarget
  IMPL_EVENT_HANDLER(blocked);
  IMPL_EVENT_HANDLER(upgradeneeded);

  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IDBOpenDBRequest, IDBRequest)

  // nsWrapperCache
  virtual JSObject* WrapObject(JSContext* aCx,
                               JS::Handle<JSObject*> aGivenProto) override;

 private:
  IDBOpenDBRequest(SafeRefPtr<IDBFactory> aFactory, nsIGlobalObject* aGlobal);

  ~IDBOpenDBRequest();

  void IncreaseActiveDatabaseCount();

  void MaybeDecreaseActiveDatabaseCount();
};

}  // namespace dom
}  // namespace mozilla

#endif  // mozilla_dom_idbrequest_h_
