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

#ifndef mozilla_dom_idbdatabase_h__
#define mozilla_dom_idbdatabase_h__

#include "mozilla/Attributes.h"
#include "mozilla/dom/IDBTransactionBinding.h"
#include "mozilla/dom/StorageTypeBinding.h"
#include "mozilla/dom/IDBWrapperCache.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "nsAutoPtr.h"
#include "nsDataHashtable.h"
#include "nsHashKeys.h"
#include "nsString.h"
#include "nsTHashtable.h"

class nsIDocument;
class nsPIDOMWindowInner;

namespace mozilla {

class ErrorResult;
class EventChainPostVisitor;

namespace dom {

class Blob;
class DOMStringList;
class IDBFactory;
class IDBMutableFile;
class IDBObjectStore;
struct IDBObjectStoreParameters;
class IDBOpenDBRequest;
class IDBRequest;
class IDBTransaction;
template <class> class Optional;
class StringOrStringSequence;

namespace indexedDB {
class BackgroundDatabaseChild;
class DatabaseSpec;
class PBackgroundIDBDatabaseFileChild;
}

class IDBDatabase final
  : public IDBWrapperCache
{
  typedef mozilla::dom::indexedDB::DatabaseSpec DatabaseSpec;
  typedef mozilla::dom::StorageType StorageType;
  typedef mozilla::dom::quota::PersistenceType PersistenceType;

  class Observer;
  friend class Observer;

  friend class IDBObjectStore;
  friend class IDBIndex;

  // The factory must be kept alive when IndexedDB is used in multiple
  // processes. If it dies then the entire actor tree will be destroyed with it
  // and the world will explode.
  RefPtr<IDBFactory> mFactory;

  nsAutoPtr<DatabaseSpec> mSpec;

  // Normally null except during a versionchange transaction.
  nsAutoPtr<DatabaseSpec> mPreviousSpec;

  indexedDB::BackgroundDatabaseChild* mBackgroundActor;

  nsTHashtable<nsPtrHashKey<IDBTransaction>> mTransactions;

  nsDataHashtable<nsISupportsHashKey, indexedDB::PBackgroundIDBDatabaseFileChild*>
    mFileActors;

  nsTHashtable<nsISupportsHashKey> mReceivedBlobs;

  RefPtr<Observer> mObserver;

  // Weak refs, IDBMutableFile strongly owns this IDBDatabase object.
  nsTArray<IDBMutableFile*> mLiveMutableFiles;

  const bool mFileHandleDisabled;
  bool mClosed;
  bool mInvalidated;
  bool mQuotaExceeded;

public:
  static already_AddRefed<IDBDatabase>
  Create(IDBOpenDBRequest* aRequest,
         IDBFactory* aFactory,
         indexedDB::BackgroundDatabaseChild* aActor,
         DatabaseSpec* aSpec);

#ifdef DEBUG
  void
  AssertIsOnOwningThread() const;

  PRThread*
  OwningThread() const;
#else
  void
  AssertIsOnOwningThread() const
  { }
#endif

  const nsString&
  Name() const;

  void
  GetName(nsAString& aName) const
  {
    AssertIsOnOwningThread();

    aName = Name();
  }

  uint64_t
  Version() const;

  already_AddRefed<nsIDocument>
  GetOwnerDocument() const;

  void
  Close()
  {
    AssertIsOnOwningThread();

    CloseInternal();
  }

  bool
  IsClosed() const
  {
    AssertIsOnOwningThread();

    return mClosed;
  }

  void
  Invalidate();

  // Whether or not the database has been invalidated. If it has then no further
  // transactions for this database will be allowed to run.
  bool
  IsInvalidated() const
  {
    AssertIsOnOwningThread();

    return mInvalidated;
  }

  void
  SetQuotaExceeded()
  {
    mQuotaExceeded = true;
  }

  void
  EnterSetVersionTransaction(uint64_t aNewVersion);

  void
  ExitSetVersionTransaction();

  // Called when a versionchange transaction is aborted to reset the
  // DatabaseInfo.
  void
  RevertToPreviousState();

  IDBFactory*
  Factory() const
  {
    AssertIsOnOwningThread();

    return mFactory;
  }

  void
  RegisterTransaction(IDBTransaction* aTransaction);

  void
  UnregisterTransaction(IDBTransaction* aTransaction);

  void
  AbortTransactions(bool aShouldWarn);

  indexedDB::PBackgroundIDBDatabaseFileChild*
  GetOrCreateFileActorForBlob(Blob* aBlob);

  void
  NoteFinishedFileActor(indexedDB::PBackgroundIDBDatabaseFileChild* aFileActor);

  void
  NoteReceivedBlob(Blob* aBlob);

  void
  DelayedMaybeExpireFileActors();

  // XXX This doesn't really belong here... It's only needed for IDBMutableFile
  //     serialization and should be removed or fixed someday.
  nsresult
  GetQuotaInfo(nsACString& aOrigin, PersistenceType* aPersistenceType);

  bool
  IsFileHandleDisabled() const
  {
    return mFileHandleDisabled;
  }

  void
  NoteLiveMutableFile(IDBMutableFile* aMutableFile);

  void
  NoteFinishedMutableFile(IDBMutableFile* aMutableFile);

  nsPIDOMWindowInner*
  GetParentObject() const;

  already_AddRefed<DOMStringList>
  ObjectStoreNames() const;

  already_AddRefed<IDBObjectStore>
  CreateObjectStore(const nsAString& aName,
                    const IDBObjectStoreParameters& aOptionalParameters,
                    ErrorResult& aRv);

  void
  DeleteObjectStore(const nsAString& name, ErrorResult& aRv);

  // This will be called from the DOM.
  already_AddRefed<IDBTransaction>
  Transaction(JSContext* aCx,
              const StringOrStringSequence& aStoreNames,
              IDBTransactionMode aMode,
              ErrorResult& aRv);

  // This can be called from C++ to avoid JS exception.
  nsresult
  Transaction(JSContext* aCx,
              const StringOrStringSequence& aStoreNames,
              IDBTransactionMode aMode,
              IDBTransaction** aTransaction);

  StorageType
  Storage() const;

  IMPL_EVENT_HANDLER(abort)
  IMPL_EVENT_HANDLER(close)
  IMPL_EVENT_HANDLER(error)
  IMPL_EVENT_HANDLER(versionchange)

  already_AddRefed<IDBRequest>
  CreateMutableFile(JSContext* aCx,
                    const nsAString& aName,
                    const Optional<nsAString>& aType,
                    ErrorResult& aRv);

  already_AddRefed<IDBRequest>
  MozCreateFileHandle(JSContext* aCx,
                      const nsAString& aName,
                      const Optional<nsAString>& aType,
                      ErrorResult& aRv)
  {
    return CreateMutableFile(aCx, aName, aType, aRv);
  }

  void
  ClearBackgroundActor()
  {
    AssertIsOnOwningThread();

    mBackgroundActor = nullptr;
  }

  const DatabaseSpec*
  Spec() const
  {
    return mSpec;
  }

  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IDBDatabase, IDBWrapperCache)

  // nsIDOMEventTarget
  virtual void
  LastRelease() override;

  virtual nsresult
  PostHandleEvent(EventChainPostVisitor& aVisitor) override;

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

private:
  IDBDatabase(IDBOpenDBRequest* aRequest,
              IDBFactory* aFactory,
              indexedDB::BackgroundDatabaseChild* aActor,
              DatabaseSpec* aSpec);

  ~IDBDatabase();

  void
  CloseInternal();

  void
  InvalidateInternal();

  bool
  RunningVersionChangeTransaction() const
  {
    AssertIsOnOwningThread();

    return !!mPreviousSpec;
  }

  void
  RefreshSpec(bool aMayDelete);

  void
  ExpireFileActors(bool aExpireAll);

  void
  InvalidateMutableFiles();

  void
  LogWarning(const char* aMessageName,
             const nsAString& aFilename,
             uint32_t aLineNumber,
             uint32_t aColumnNumber);

  // Only accessed by IDBObjectStore.
  nsresult
  RenameObjectStore(int64_t aObjectStoreId, const nsAString& aName);

  // Only accessed by IDBIndex.
  nsresult
  RenameIndex(int64_t aObjectStoreId, int64_t aIndexId, const nsAString& aName);
};

} // namespace dom
} // namespace mozilla

#endif // mozilla_dom_idbdatabase_h__