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

#include "IndexedDBChild.h"

#include "nsIAtom.h"
#include "nsIInputStream.h"

#include "mozilla/Assertions.h"
#include "mozilla/dom/ContentChild.h"

#include "AsyncConnectionHelper.h"
#include "DatabaseInfo.h"
#include "IDBEvents.h"
#include "IDBFactory.h"
#include "IDBIndex.h"
#include "IDBObjectStore.h"
#include "IDBTransaction.h"
#include "IndexedDatabaseManager.h"

USING_INDEXEDDB_NAMESPACE

using namespace mozilla::dom;

namespace {

class IPCOpenDatabaseHelper : public AsyncConnectionHelper
{
public:
  IPCOpenDatabaseHelper(IDBDatabase* aDatabase, IDBOpenDBRequest* aRequest)
  : AsyncConnectionHelper(aDatabase, aRequest)
  {
    MOZ_ASSERT(aRequest);
  }

  virtual nsresult UnpackResponseFromParentProcess(
                                            const ResponseValue& aResponseValue)
                                            MOZ_OVERRIDE;

  virtual ChildProcessSendResult
  SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE;

  virtual nsresult
  GetSuccessResult(JSContext* aCx, jsval* aVal) MOZ_OVERRIDE;

  virtual nsresult
  OnSuccess() MOZ_OVERRIDE
  {
    static_cast<IDBOpenDBRequest*>(mRequest.get())->SetTransaction(NULL);
    return AsyncConnectionHelper::OnSuccess();
  }

  virtual void
  OnError() MOZ_OVERRIDE
  {
    static_cast<IDBOpenDBRequest*>(mRequest.get())->SetTransaction(NULL);
    AsyncConnectionHelper::OnError();
  }

  virtual nsresult
  DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE;
};

class IPCSetVersionHelper : public AsyncConnectionHelper
{
  nsRefPtr<IDBOpenDBRequest> mOpenRequest;
  uint64_t mOldVersion;
  uint64_t mRequestedVersion;

public:
  IPCSetVersionHelper(IDBTransaction* aTransaction, IDBOpenDBRequest* aRequest,
                      uint64_t aOldVersion, uint64_t aRequestedVersion)
  : AsyncConnectionHelper(aTransaction, aRequest),
    mOpenRequest(aRequest), mOldVersion(aOldVersion),
    mRequestedVersion(aRequestedVersion)
  {
    MOZ_ASSERT(aTransaction);
    MOZ_ASSERT(aRequest);
  }

  virtual nsresult UnpackResponseFromParentProcess(
                                            const ResponseValue& aResponseValue)
                                            MOZ_OVERRIDE;

  virtual ChildProcessSendResult
  SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE;

  virtual nsresult
  DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE;

  virtual already_AddRefed<nsDOMEvent>
  CreateSuccessEvent() MOZ_OVERRIDE;

  virtual nsresult
  GetSuccessResult(JSContext* aCx, jsval* aVal) MOZ_OVERRIDE;
};

class IPCDeleteDatabaseHelper : public AsyncConnectionHelper
{
public:
  IPCDeleteDatabaseHelper(IDBRequest* aRequest)
  : AsyncConnectionHelper(static_cast<IDBDatabase*>(NULL), aRequest)
  { }

  virtual nsresult UnpackResponseFromParentProcess(
                                            const ResponseValue& aResponseValue)
                                            MOZ_OVERRIDE;

  virtual ChildProcessSendResult
  SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE;

  virtual nsresult
  GetSuccessResult(JSContext* aCx, jsval* aVal) MOZ_OVERRIDE;

  virtual nsresult
  DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE;
};

class VersionChangeRunnable : public nsRunnable
{
  nsRefPtr<IDBDatabase> mDatabase;
  uint64_t mOldVersion;
  uint64_t mNewVersion;

public:
  VersionChangeRunnable(IDBDatabase* aDatabase, const uint64_t& aOldVersion,
                        const uint64_t& aNewVersion)
  : mDatabase(aDatabase), mOldVersion(aOldVersion), mNewVersion(aNewVersion)
  {
    MOZ_ASSERT(aDatabase);
  }

  NS_IMETHOD Run() MOZ_OVERRIDE
  {
    if (mDatabase->IsClosed()) {
      return NS_OK;
    }

    nsRefPtr<nsDOMEvent> event =
      IDBVersionChangeEvent::Create(mOldVersion, mNewVersion);
    MOZ_ASSERT(event);

    bool dummy;
    nsresult rv = mDatabase->DispatchEvent(event, &dummy);
    NS_ENSURE_SUCCESS(rv, rv);

    return NS_OK;
  }
};

} // anonymous namespace

/*******************************************************************************
 * IndexedDBChild
 ******************************************************************************/

IndexedDBChild::IndexedDBChild(const nsCString& aASCIIOrigin)
: mFactory(nullptr), mASCIIOrigin(aASCIIOrigin)
#ifdef DEBUG
  , mDisconnected(false)
#endif
{
  MOZ_COUNT_CTOR(IndexedDBChild);
}

IndexedDBChild::~IndexedDBChild()
{
  MOZ_COUNT_DTOR(IndexedDBChild);
  MOZ_ASSERT(!mFactory);
}

void
IndexedDBChild::SetFactory(IDBFactory* aFactory)
{
  MOZ_ASSERT(aFactory);
  MOZ_ASSERT(!mFactory);

  aFactory->SetActor(this);
  mFactory = aFactory;
}

void
IndexedDBChild::Disconnect()
{
#ifdef DEBUG
  MOZ_ASSERT(!mDisconnected);
  mDisconnected = true;
#endif

  const InfallibleTArray<PIndexedDBDatabaseChild*>& databases =
    ManagedPIndexedDBDatabaseChild();
  for (uint32_t i = 0; i < databases.Length(); ++i) {
    static_cast<IndexedDBDatabaseChild*>(databases[i])->Disconnect();
  }
}

void
IndexedDBChild::ActorDestroy(ActorDestroyReason aWhy)
{
  if (mFactory) {
    mFactory->SetActor(static_cast<IndexedDBChild*>(NULL));
#ifdef DEBUG
    mFactory = NULL;
#endif
  }
}

PIndexedDBDatabaseChild*
IndexedDBChild::AllocPIndexedDBDatabase(const nsString& aName,
                                        const uint64_t& aVersion)
{
  return new IndexedDBDatabaseChild(aName, aVersion);
}

bool
IndexedDBChild::DeallocPIndexedDBDatabase(PIndexedDBDatabaseChild* aActor)
{
  delete aActor;
  return true;
}

PIndexedDBDeleteDatabaseRequestChild*
IndexedDBChild::AllocPIndexedDBDeleteDatabaseRequest(const nsString& aName)
{
  MOZ_NOT_REACHED("Caller is supposed to manually construct a request!");
  return NULL;
}

bool
IndexedDBChild::DeallocPIndexedDBDeleteDatabaseRequest(
                                   PIndexedDBDeleteDatabaseRequestChild* aActor)
{
  delete aActor;
  return true;
}

/*******************************************************************************
 * IndexedDBDatabaseChild
 ******************************************************************************/

IndexedDBDatabaseChild::IndexedDBDatabaseChild(const nsString& aName,
                                               uint64_t aVersion)
: mDatabase(NULL), mName(aName), mVersion(aVersion)
{
  MOZ_COUNT_CTOR(IndexedDBDatabaseChild);
}

IndexedDBDatabaseChild::~IndexedDBDatabaseChild()
{
  MOZ_COUNT_DTOR(IndexedDBDatabaseChild);
  MOZ_ASSERT(!mDatabase);
  MOZ_ASSERT(!mStrongDatabase);
}

void
IndexedDBDatabaseChild::SetRequest(IDBOpenDBRequest* aRequest)
{
  MOZ_ASSERT(aRequest);
  MOZ_ASSERT(!mRequest);

  mRequest = aRequest;
}

void
IndexedDBDatabaseChild::Disconnect()
{
  const InfallibleTArray<PIndexedDBTransactionChild*>& transactions =
    ManagedPIndexedDBTransactionChild();
  for (uint32_t i = 0; i < transactions.Length(); ++i) {
    static_cast<IndexedDBTransactionChild*>(transactions[i])->Disconnect();
  }
}

bool
IndexedDBDatabaseChild::EnsureDatabase(
                           IDBOpenDBRequest* aRequest,
                           const DatabaseInfoGuts& aDBInfo,
                           const InfallibleTArray<ObjectStoreInfoGuts>& aOSInfo)
{
  nsCOMPtr<nsIAtom> databaseId;
  if (mDatabase) {
    databaseId = mDatabase->Id();
  }
  else {
    databaseId =
      IndexedDatabaseManager::GetDatabaseId(aDBInfo.origin, aDBInfo.name);
  }
  NS_ENSURE_TRUE(databaseId, false);

  nsRefPtr<DatabaseInfo> dbInfo;
  if (DatabaseInfo::Get(databaseId, getter_AddRefs(dbInfo))) {
    dbInfo->version = aDBInfo.version;
  }
  else {
    nsRefPtr<DatabaseInfo> newInfo = new DatabaseInfo();

    *static_cast<DatabaseInfoGuts*>(newInfo.get()) = aDBInfo;
    newInfo->id = databaseId;

    if (!DatabaseInfo::Put(newInfo)) {
      NS_WARNING("Out of memory!");
      return false;
    }

    newInfo.swap(dbInfo);

    // This is more or less copied from IDBFactory::SetDatabaseMetadata.
    for (uint32_t i = 0; i < aOSInfo.Length(); i++) {
      nsRefPtr<ObjectStoreInfo> newInfo = new ObjectStoreInfo();
      *static_cast<ObjectStoreInfoGuts*>(newInfo.get()) = aOSInfo[i];

      if (!dbInfo->PutObjectStore(newInfo)) {
        NS_WARNING("Out of memory!");
        return false;
      }
    }
  }

  if (!mDatabase) {
    nsRefPtr<IDBDatabase> database =
      IDBDatabase::Create(aRequest, aRequest->Factory(), dbInfo.forget(),
                          aDBInfo.origin, NULL, NULL);
    if (!database) {
      NS_WARNING("Failed to create database!");
      return false;
    }

    database->SetActor(this);

    mDatabase = database;
    mStrongDatabase = database.forget();
  }

  return true;
}

void
IndexedDBDatabaseChild::ActorDestroy(ActorDestroyReason aWhy)
{
  MOZ_ASSERT(!mStrongDatabase);
  if (mDatabase) {
    mDatabase->SetActor(static_cast<IndexedDBDatabaseChild*>(NULL));
#ifdef DEBUG
    mDatabase = NULL;
#endif
  }
}

bool
IndexedDBDatabaseChild::RecvSuccess(
                           const DatabaseInfoGuts& aDBInfo,
                           const InfallibleTArray<ObjectStoreInfoGuts>& aOSInfo)
{
#ifdef DEBUG
  {
    IndexedDBChild* manager = static_cast<IndexedDBChild*>(Manager());
    MOZ_ASSERT(aDBInfo.origin == manager->ASCIIOrigin());
    MOZ_ASSERT(aDBInfo.name == mName);
    MOZ_ASSERT(!mVersion || aDBInfo.version == mVersion);
  }
#endif

  MOZ_ASSERT(mRequest);

  nsRefPtr<IDBOpenDBRequest> request;
  mRequest.swap(request);

  nsRefPtr<AsyncConnectionHelper> openHelper;
  mOpenHelper.swap(openHelper);

  if (!EnsureDatabase(request, aDBInfo, aOSInfo)) {
    return false;
  }

  MOZ_ASSERT(mStrongDatabase);
  nsRefPtr<IDBDatabase> database;
  mStrongDatabase.swap(database);

  if (openHelper) {
    request->Reset();
  }
  else {
    openHelper = new IPCOpenDatabaseHelper(mDatabase, request);
  }

  MainThreadEventTarget target;
  if (NS_FAILED(openHelper->Dispatch(&target))) {
    NS_WARNING("Dispatch of IPCOpenDatabaseHelper failed!");
    return false;
  }

  return true;
}

bool
IndexedDBDatabaseChild::RecvError(const nsresult& aRv)
{
  MOZ_ASSERT(mRequest);

  nsRefPtr<IDBOpenDBRequest> request;
  mRequest.swap(request);

  nsRefPtr<IDBDatabase> database;
  mStrongDatabase.swap(database);

  nsRefPtr<AsyncConnectionHelper> openHelper;
  mOpenHelper.swap(openHelper);

  if (openHelper) {
    request->Reset();
  }
  else {
    openHelper = new IPCOpenDatabaseHelper(NULL, request);
  }

  openHelper->SetError(aRv);

  MainThreadEventTarget target;
  if (NS_FAILED(openHelper->Dispatch(&target))) {
    NS_WARNING("Dispatch of IPCOpenDatabaseHelper failed!");
    return false;
  }

  return true;
}

bool
IndexedDBDatabaseChild::RecvBlocked(const uint64_t& aOldVersion)
{
  MOZ_ASSERT(mRequest);
  MOZ_ASSERT(!mDatabase);

  nsCOMPtr<nsIRunnable> runnable =
    IDBVersionChangeEvent::CreateBlockedRunnable(aOldVersion, mVersion,
                                                 mRequest);

  MainThreadEventTarget target;
  if (NS_FAILED(target.Dispatch(runnable, NS_DISPATCH_NORMAL))) {
    NS_WARNING("Dispatch of blocked event failed!");
  }

  return true;
}

bool
IndexedDBDatabaseChild::RecvVersionChange(const uint64_t& aOldVersion,
                                          const uint64_t& aNewVersion)
{
  MOZ_ASSERT(mDatabase);

  nsCOMPtr<nsIRunnable> runnable =
    new VersionChangeRunnable(mDatabase, aOldVersion, aNewVersion);

  MainThreadEventTarget target;
  if (NS_FAILED(target.Dispatch(runnable, NS_DISPATCH_NORMAL))) {
    NS_WARNING("Dispatch of versionchange event failed!");
  }

  return true;
}

bool
IndexedDBDatabaseChild::RecvInvalidate()
{
  if (mDatabase) {
    mDatabase->Invalidate();
  }
  return true;
}

bool
IndexedDBDatabaseChild::RecvPIndexedDBTransactionConstructor(
                                             PIndexedDBTransactionChild* aActor,
                                             const TransactionParams& aParams)
{
  // This only happens when the parent has created a version-change transaction
  // for us.

  IndexedDBTransactionChild* actor =
    static_cast<IndexedDBTransactionChild*>(aActor);
  MOZ_ASSERT(!actor->GetTransaction());

  MOZ_ASSERT(aParams.type() ==
             TransactionParams::TVersionChangeTransactionParams);

  const VersionChangeTransactionParams& params =
    aParams.get_VersionChangeTransactionParams();

  const DatabaseInfoGuts& dbInfo = params.dbInfo();
  const InfallibleTArray<ObjectStoreInfoGuts>& osInfo = params.osInfo();
  uint64_t oldVersion = params.oldVersion();

  MOZ_ASSERT(dbInfo.origin ==
             static_cast<IndexedDBChild*>(Manager())->ASCIIOrigin());
  MOZ_ASSERT(dbInfo.name == mName);
  MOZ_ASSERT(!mVersion || dbInfo.version == mVersion);
  MOZ_ASSERT(!mVersion || oldVersion < mVersion);

  MOZ_ASSERT(mRequest);
  MOZ_ASSERT(!mDatabase);
  MOZ_ASSERT(!mOpenHelper);

  if (!EnsureDatabase(mRequest, dbInfo, osInfo)) {
    return false;
  }

  nsRefPtr<IPCOpenDatabaseHelper> helper =
    new IPCOpenDatabaseHelper(mDatabase, mRequest);

  nsTArray<nsString> storesToOpen;
  nsRefPtr<IDBTransaction> transaction =
    IDBTransaction::CreateInternal(mDatabase, storesToOpen,
                                   IDBTransaction::VERSION_CHANGE, false, true);
  NS_ENSURE_TRUE(transaction, false);

  nsRefPtr<IPCSetVersionHelper> versionHelper =
    new IPCSetVersionHelper(transaction, mRequest, oldVersion, mVersion);

  mDatabase->EnterSetVersionTransaction();
  mDatabase->mPreviousDatabaseInfo->version = oldVersion;

  MainThreadEventTarget target;
  if (NS_FAILED(versionHelper->Dispatch(&target))) {
    NS_WARNING("Dispatch of IPCSetVersionHelper failed!");
    return false;
  }

  actor->SetTransaction(transaction);

  mOpenHelper = helper.forget();
  return true;
}

PIndexedDBTransactionChild*
IndexedDBDatabaseChild::AllocPIndexedDBTransaction(
                                               const TransactionParams& aParams)
{
  MOZ_ASSERT(aParams.type() ==
             TransactionParams::TVersionChangeTransactionParams);
  return new IndexedDBTransactionChild();
}

bool
IndexedDBDatabaseChild::DeallocPIndexedDBTransaction(
                                             PIndexedDBTransactionChild* aActor)
{
  delete aActor;
  return true;
}

/*******************************************************************************
 * IndexedDBTransactionChild
 ******************************************************************************/

IndexedDBTransactionChild::IndexedDBTransactionChild()
: mTransaction(NULL)
{
  MOZ_COUNT_CTOR(IndexedDBTransactionChild);
}

IndexedDBTransactionChild::~IndexedDBTransactionChild()
{
  MOZ_COUNT_DTOR(IndexedDBTransactionChild);
  MOZ_ASSERT(!mTransaction);
  MOZ_ASSERT(!mStrongTransaction);
}

void
IndexedDBTransactionChild::SetTransaction(IDBTransaction* aTransaction)
{
  MOZ_ASSERT(aTransaction);
  MOZ_ASSERT(!mTransaction);

  aTransaction->SetActor(this);

  mTransaction = aTransaction;
  mStrongTransaction = aTransaction;
}

void
IndexedDBTransactionChild::Disconnect()
{
  const InfallibleTArray<PIndexedDBObjectStoreChild*>& objectStores =
    ManagedPIndexedDBObjectStoreChild();
  for (uint32_t i = 0; i < objectStores.Length(); ++i) {
    static_cast<IndexedDBObjectStoreChild*>(objectStores[i])->Disconnect();
  }
}

void
IndexedDBTransactionChild::FireCompleteEvent(nsresult aRv)
{
  MOZ_ASSERT(mTransaction);
  MOZ_ASSERT(mStrongTransaction);

  nsRefPtr<IDBTransaction> transaction;
  mStrongTransaction.swap(transaction);

  if (transaction->GetMode() == IDBTransaction::VERSION_CHANGE) {
    transaction->Database()->ExitSetVersionTransaction();
  }

  nsRefPtr<CommitHelper> helper = new CommitHelper(transaction, aRv);

  MainThreadEventTarget target;
  if (NS_FAILED(target.Dispatch(helper, NS_DISPATCH_NORMAL))) {
    NS_WARNING("Dispatch of CommitHelper failed!");
  }
}

void
IndexedDBTransactionChild::ActorDestroy(ActorDestroyReason aWhy)
{
  if (mStrongTransaction) {
    // We're being torn down before we received a complete event from the parent
    // so fake one here.
    FireCompleteEvent(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);

    MOZ_ASSERT(!mStrongTransaction);
  }

  if (mTransaction) {
    mTransaction->SetActor(static_cast<IndexedDBTransactionChild*>(NULL));
#ifdef DEBUG
    mTransaction = NULL;
#endif
  }
}

bool
IndexedDBTransactionChild::RecvComplete(const CompleteParams& aParams)
{
  MOZ_ASSERT(mTransaction);
  MOZ_ASSERT(mStrongTransaction);

  nsresult resultCode;

  switch (aParams.type()) {
    case CompleteParams::TCompleteResult:
      resultCode = NS_OK;
      break;
    case CompleteParams::TAbortResult:
      resultCode = aParams.get_AbortResult().errorCode();
      if (NS_SUCCEEDED(resultCode)) {
        resultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
      }
      break;

    default:
      MOZ_NOT_REACHED("Unknown union type!");
      return false;
  }

  FireCompleteEvent(resultCode);
  return true;
}

PIndexedDBObjectStoreChild*
IndexedDBTransactionChild::AllocPIndexedDBObjectStore(
                                    const ObjectStoreConstructorParams& aParams)
{
  MOZ_NOT_REACHED("Caller is supposed to manually construct an object store!");
  return NULL;
}

bool
IndexedDBTransactionChild::DeallocPIndexedDBObjectStore(
                                             PIndexedDBObjectStoreChild* aActor)
{
  delete aActor;
  return true;
}

/*******************************************************************************
 * IndexedDBObjectStoreChild
 ******************************************************************************/

IndexedDBObjectStoreChild::IndexedDBObjectStoreChild(
                                                   IDBObjectStore* aObjectStore)
: mObjectStore(aObjectStore)
{
  MOZ_COUNT_CTOR(IndexedDBObjectStoreChild);
  aObjectStore->SetActor(this);
}

IndexedDBObjectStoreChild::~IndexedDBObjectStoreChild()
{
  MOZ_COUNT_DTOR(IndexedDBObjectStoreChild);
  MOZ_ASSERT(!mObjectStore);
}

void
IndexedDBObjectStoreChild::Disconnect()
{
  const InfallibleTArray<PIndexedDBRequestChild*>& requests =
    ManagedPIndexedDBRequestChild();
  for (uint32_t i = 0; i < requests.Length(); ++i) {
    static_cast<IndexedDBRequestChildBase*>(requests[i])->Disconnect();
  }

  const InfallibleTArray<PIndexedDBIndexChild*>& indexes =
    ManagedPIndexedDBIndexChild();
  for (uint32_t i = 0; i < indexes.Length(); ++i) {
    static_cast<IndexedDBIndexChild*>(indexes[i])->Disconnect();
  }

  const InfallibleTArray<PIndexedDBCursorChild*>& cursors =
    ManagedPIndexedDBCursorChild();
  for (uint32_t i = 0; i < cursors.Length(); ++i) {
    static_cast<IndexedDBCursorChild*>(cursors[i])->Disconnect();
  }
}

void
IndexedDBObjectStoreChild::ActorDestroy(ActorDestroyReason aWhy)
{
  if (mObjectStore) {
    mObjectStore->SetActor(static_cast<IndexedDBObjectStoreChild*>(NULL));
#ifdef DEBUG
    mObjectStore = NULL;
#endif
  }
}

bool
IndexedDBObjectStoreChild::RecvPIndexedDBCursorConstructor(
                              PIndexedDBCursorChild* aActor,
                              const ObjectStoreCursorConstructorParams& aParams)
{
  IndexedDBCursorChild* actor = static_cast<IndexedDBCursorChild*>(aActor);

  IndexedDBObjectStoreRequestChild* requestActor =
    static_cast<IndexedDBObjectStoreRequestChild*>(aParams.requestChild());
  NS_ASSERTION(requestActor, "Must have an actor here!");

  nsRefPtr<IDBRequest> request = requestActor->GetRequest();
  NS_ASSERTION(request, "Must have a request here!");

  size_t direction = static_cast<size_t>(aParams.direction());

  nsTArray<StructuredCloneFile> blobs;
  IDBObjectStore::ConvertActorsToBlobs(aParams.blobsChild(), blobs);

  nsRefPtr<IDBCursor> cursor;
  nsresult rv =
    mObjectStore->OpenCursorFromChildProcess(request, direction, aParams.key(),
                                             aParams.cloneInfo(), blobs,
                                             getter_AddRefs(cursor));
  NS_ENSURE_SUCCESS(rv, false);

  MOZ_ASSERT(blobs.IsEmpty(), "Should have swapped blob elements!");

  actor->SetCursor(cursor);
  return true;
}

PIndexedDBRequestChild*
IndexedDBObjectStoreChild::AllocPIndexedDBRequest(
                                        const ObjectStoreRequestParams& aParams)
{
  MOZ_NOT_REACHED("Caller is supposed to manually construct a request!");
  return NULL;
}

bool
IndexedDBObjectStoreChild::DeallocPIndexedDBRequest(
                                                 PIndexedDBRequestChild* aActor)
{
  delete aActor;
  return false;
}

PIndexedDBIndexChild*
IndexedDBObjectStoreChild::AllocPIndexedDBIndex(
                                          const IndexConstructorParams& aParams)
{
  MOZ_NOT_REACHED("Caller is supposed to manually construct an index!");
  return NULL;
}

bool
IndexedDBObjectStoreChild::DeallocPIndexedDBIndex(PIndexedDBIndexChild* aActor)
{
  delete aActor;
  return true;
}

PIndexedDBCursorChild*
IndexedDBObjectStoreChild::AllocPIndexedDBCursor(
                              const ObjectStoreCursorConstructorParams& aParams)
{
  return new IndexedDBCursorChild();
}

bool
IndexedDBObjectStoreChild::DeallocPIndexedDBCursor(
                                                  PIndexedDBCursorChild* aActor)
{
  delete aActor;
  return true;
}

/*******************************************************************************
 * IndexedDBIndexChild
 ******************************************************************************/

IndexedDBIndexChild::IndexedDBIndexChild(IDBIndex* aIndex)
: mIndex(aIndex)
{
  MOZ_COUNT_CTOR(IndexedDBIndexChild);
  aIndex->SetActor(this);
}

IndexedDBIndexChild::~IndexedDBIndexChild()
{
  MOZ_COUNT_DTOR(IndexedDBIndexChild);
  MOZ_ASSERT(!mIndex);
}

void
IndexedDBIndexChild::Disconnect()
{
  const InfallibleTArray<PIndexedDBRequestChild*>& requests =
    ManagedPIndexedDBRequestChild();
  for (uint32_t i = 0; i < requests.Length(); ++i) {
    static_cast<IndexedDBRequestChildBase*>(requests[i])->Disconnect();
  }

  const InfallibleTArray<PIndexedDBCursorChild*>& cursors =
    ManagedPIndexedDBCursorChild();
  for (uint32_t i = 0; i < cursors.Length(); ++i) {
    static_cast<IndexedDBCursorChild*>(cursors[i])->Disconnect();
  }
}

void
IndexedDBIndexChild::ActorDestroy(ActorDestroyReason aWhy)
{
  if (mIndex) {
    mIndex->SetActor(static_cast<IndexedDBIndexChild*>(NULL));
#ifdef DEBUG
    mIndex = NULL;
#endif
  }
}

bool
IndexedDBIndexChild::RecvPIndexedDBCursorConstructor(
                                    PIndexedDBCursorChild* aActor,
                                    const IndexCursorConstructorParams& aParams)
{
  IndexedDBCursorChild* actor = static_cast<IndexedDBCursorChild*>(aActor);

  IndexedDBObjectStoreRequestChild* requestActor =
    static_cast<IndexedDBObjectStoreRequestChild*>(aParams.requestChild());
  NS_ASSERTION(requestActor, "Must have an actor here!");

  nsRefPtr<IDBRequest> request = requestActor->GetRequest();
  NS_ASSERTION(request, "Must have a request here!");

  size_t direction = static_cast<size_t>(aParams.direction());

  nsRefPtr<IDBCursor> cursor;
  nsresult rv;

  typedef ipc::OptionalStructuredCloneReadInfo CursorUnionType;

  switch (aParams.optionalCloneInfo().type()) {
    case CursorUnionType::TSerializedStructuredCloneReadInfo: {
      nsTArray<StructuredCloneFile> blobs;
      IDBObjectStore::ConvertActorsToBlobs(aParams.blobsChild(), blobs);

      const SerializedStructuredCloneReadInfo& cloneInfo =
        aParams.optionalCloneInfo().get_SerializedStructuredCloneReadInfo();

      rv = mIndex->OpenCursorFromChildProcess(request, direction, aParams.key(),
                                              aParams.objectKey(), cloneInfo,
                                              blobs,
                                              getter_AddRefs(cursor));
      NS_ENSURE_SUCCESS(rv, false);
    } break;

    case CursorUnionType::Tvoid_t:
      MOZ_ASSERT(aParams.blobsChild().IsEmpty());

      rv = mIndex->OpenCursorFromChildProcess(request, direction, aParams.key(),
                                              aParams.objectKey(),
                                              getter_AddRefs(cursor));
      NS_ENSURE_SUCCESS(rv, false);
      break;

    default:
      MOZ_NOT_REACHED("Unknown union type!");
      return false;
  }

  actor->SetCursor(cursor);
  return true;
}

PIndexedDBRequestChild*
IndexedDBIndexChild::AllocPIndexedDBRequest(const IndexRequestParams& aParams)
{
  MOZ_NOT_REACHED("Caller is supposed to manually construct a request!");
  return NULL;
}

bool
IndexedDBIndexChild::DeallocPIndexedDBRequest(PIndexedDBRequestChild* aActor)
{
  delete aActor;
  return true;
}

PIndexedDBCursorChild*
IndexedDBIndexChild::AllocPIndexedDBCursor(
                                    const IndexCursorConstructorParams& aParams)
{
  return new IndexedDBCursorChild();
}

bool
IndexedDBIndexChild::DeallocPIndexedDBCursor(PIndexedDBCursorChild* aActor)
{
  delete aActor;
  return true;
}

/*******************************************************************************
 * IndexedDBCursorChild
 ******************************************************************************/

IndexedDBCursorChild::IndexedDBCursorChild()
: mCursor(NULL)
{
  MOZ_COUNT_CTOR(IndexedDBCursorChild);
}

IndexedDBCursorChild::~IndexedDBCursorChild()
{
  MOZ_COUNT_DTOR(IndexedDBCursorChild);
  MOZ_ASSERT(!mCursor);
  MOZ_ASSERT(!mStrongCursor);
}

void
IndexedDBCursorChild::SetCursor(IDBCursor* aCursor)
{
  MOZ_ASSERT(aCursor);
  MOZ_ASSERT(!mCursor);

  aCursor->SetActor(this);

  mCursor = aCursor;
  mStrongCursor = aCursor;
}

void
IndexedDBCursorChild::Disconnect()
{
  const InfallibleTArray<PIndexedDBRequestChild*>& requests =
    ManagedPIndexedDBRequestChild();
  for (uint32_t i = 0; i < requests.Length(); ++i) {
    static_cast<IndexedDBRequestChildBase*>(requests[i])->Disconnect();
  }
}

void
IndexedDBCursorChild::ActorDestroy(ActorDestroyReason aWhy)
{
  if (mCursor) {
    mCursor->SetActor(static_cast<IndexedDBCursorChild*>(NULL));
#ifdef DEBUG
    mCursor = NULL;
#endif
  }
}

PIndexedDBRequestChild*
IndexedDBCursorChild::AllocPIndexedDBRequest(const CursorRequestParams& aParams)
{
  MOZ_NOT_REACHED("Caller is supposed to manually construct a request!");
  return NULL;
}

bool
IndexedDBCursorChild::DeallocPIndexedDBRequest(PIndexedDBRequestChild* aActor)
{
  delete aActor;
  return true;
}

/*******************************************************************************
 * IndexedDBRequestChildBase
 ******************************************************************************/

IndexedDBRequestChildBase::IndexedDBRequestChildBase(
                                                 AsyncConnectionHelper* aHelper)
: mHelper(aHelper)
{
  MOZ_COUNT_CTOR(IndexedDBRequestChildBase);
}

IndexedDBRequestChildBase::~IndexedDBRequestChildBase()
{
  MOZ_COUNT_DTOR(IndexedDBRequestChildBase);
}

IDBRequest*
IndexedDBRequestChildBase::GetRequest() const
{
  return mHelper ? mHelper->GetRequest() : nullptr;
}

void
IndexedDBRequestChildBase::Disconnect()
{
  if (mHelper) {
    IDBRequest* request = mHelper->GetRequest();

    if (request->IsPending()) {
      request->SetError(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);

      IDBTransaction* transaction = mHelper->GetTransaction();
      if (transaction) {
        transaction->OnRequestDisconnected();
      }
    }
  }
}

bool
IndexedDBRequestChildBase::Recv__delete__(const ResponseValue& aResponse)
{
  MOZ_NOT_REACHED("This should be overridden!");
  return false;
}

/*******************************************************************************
 * IndexedDBObjectStoreRequestChild
 ******************************************************************************/

IndexedDBObjectStoreRequestChild::IndexedDBObjectStoreRequestChild(
                                                 AsyncConnectionHelper* aHelper,
                                                 IDBObjectStore* aObjectStore,
                                                 RequestType aRequestType)
: IndexedDBRequestChildBase(aHelper), mObjectStore(aObjectStore),
  mRequestType(aRequestType)
{
  MOZ_COUNT_CTOR(IndexedDBObjectStoreRequestChild);
  MOZ_ASSERT(aHelper);
  MOZ_ASSERT(aObjectStore);
  MOZ_ASSERT(aRequestType > ParamsUnionType::T__None &&
             aRequestType <= ParamsUnionType::T__Last);
}

IndexedDBObjectStoreRequestChild::~IndexedDBObjectStoreRequestChild()
{
  MOZ_COUNT_DTOR(IndexedDBObjectStoreRequestChild);
}

bool
IndexedDBObjectStoreRequestChild::Recv__delete__(const ResponseValue& aResponse)
{
  switch (aResponse.type()) {
    case ResponseValue::Tnsresult:
      break;
    case ResponseValue::TGetResponse:
      MOZ_ASSERT(mRequestType == ParamsUnionType::TGetParams);
      break;
    case ResponseValue::TGetAllResponse:
      MOZ_ASSERT(mRequestType == ParamsUnionType::TGetAllParams);
      break;
    case ResponseValue::TAddResponse:
      MOZ_ASSERT(mRequestType == ParamsUnionType::TAddParams);
      break;
    case ResponseValue::TPutResponse:
      MOZ_ASSERT(mRequestType == ParamsUnionType::TPutParams);
      break;
    case ResponseValue::TDeleteResponse:
      MOZ_ASSERT(mRequestType == ParamsUnionType::TDeleteParams);
      break;
    case ResponseValue::TClearResponse:
      MOZ_ASSERT(mRequestType == ParamsUnionType::TClearParams);
      break;
    case ResponseValue::TCountResponse:
      MOZ_ASSERT(mRequestType == ParamsUnionType::TCountParams);
      break;
    case ResponseValue::TOpenCursorResponse:
      MOZ_ASSERT(mRequestType == ParamsUnionType::TOpenCursorParams);
      break;

    default:
      MOZ_NOT_REACHED("Received invalid response parameters!");
      return false;
  }

  nsresult rv = mHelper->OnParentProcessRequestComplete(aResponse);
  NS_ENSURE_SUCCESS(rv, false);

  return true;
}

/*******************************************************************************
 * IndexedDBIndexRequestChild
 ******************************************************************************/

IndexedDBIndexRequestChild::IndexedDBIndexRequestChild(
                                                 AsyncConnectionHelper* aHelper,
                                                 IDBIndex* aIndex,
                                                 RequestType aRequestType)
: IndexedDBRequestChildBase(aHelper), mIndex(aIndex), mRequestType(aRequestType)
{
  MOZ_COUNT_CTOR(IndexedDBIndexRequestChild);
  MOZ_ASSERT(aHelper);
  MOZ_ASSERT(aIndex);
  MOZ_ASSERT(aRequestType > ParamsUnionType::T__None &&
             aRequestType <= ParamsUnionType::T__Last);
}

IndexedDBIndexRequestChild::~IndexedDBIndexRequestChild()
{
  MOZ_COUNT_DTOR(IndexedDBIndexRequestChild);
}

bool
IndexedDBIndexRequestChild::Recv__delete__(const ResponseValue& aResponse)
{
  switch (aResponse.type()) {
    case ResponseValue::Tnsresult:
      break;
    case ResponseValue::TGetResponse:
      MOZ_ASSERT(mRequestType == ParamsUnionType::TGetParams);
      break;
    case ResponseValue::TGetKeyResponse:
      MOZ_ASSERT(mRequestType == ParamsUnionType::TGetKeyParams);
      break;
    case ResponseValue::TGetAllResponse:
      MOZ_ASSERT(mRequestType == ParamsUnionType::TGetAllParams);
      break;
    case ResponseValue::TGetAllKeysResponse:
      MOZ_ASSERT(mRequestType == ParamsUnionType::TGetAllKeysParams);
      break;
    case ResponseValue::TCountResponse:
      MOZ_ASSERT(mRequestType == ParamsUnionType::TCountParams);
      break;
    case ResponseValue::TOpenCursorResponse:
      MOZ_ASSERT(mRequestType == ParamsUnionType::TOpenCursorParams ||
                 mRequestType == ParamsUnionType::TOpenKeyCursorParams);
      break;

    default:
      MOZ_NOT_REACHED("Received invalid response parameters!");
      return false;
  }

  nsresult rv = mHelper->OnParentProcessRequestComplete(aResponse);
  NS_ENSURE_SUCCESS(rv, false);

  return true;
}

/*******************************************************************************
 * IndexedDBCursorRequestChild
 ******************************************************************************/

IndexedDBCursorRequestChild::IndexedDBCursorRequestChild(
                                                 AsyncConnectionHelper* aHelper,
                                                 IDBCursor* aCursor,
                                                 RequestType aRequestType)
: IndexedDBRequestChildBase(aHelper), mCursor(aCursor),
  mRequestType(aRequestType)
{
  MOZ_COUNT_CTOR(IndexedDBCursorRequestChild);
  MOZ_ASSERT(aHelper);
  MOZ_ASSERT(aCursor);
  MOZ_ASSERT(aRequestType > ParamsUnionType::T__None &&
             aRequestType <= ParamsUnionType::T__Last);
}

IndexedDBCursorRequestChild::~IndexedDBCursorRequestChild()
{
  MOZ_COUNT_DTOR(IndexedDBCursorRequestChild);
}

bool
IndexedDBCursorRequestChild::Recv__delete__(const ResponseValue& aResponse)
{
  switch (aResponse.type()) {
    case ResponseValue::Tnsresult:
      break;
    case ResponseValue::TContinueResponse:
      MOZ_ASSERT(mRequestType == ParamsUnionType::TContinueParams);
      break;

    default:
      MOZ_NOT_REACHED("Received invalid response parameters!");
      return false;
  }

  nsresult rv = mHelper->OnParentProcessRequestComplete(aResponse);
  NS_ENSURE_SUCCESS(rv, false);

  return true;
}

/*******************************************************************************
 * IndexedDBDeleteDatabaseRequestChild
 ******************************************************************************/

IndexedDBDeleteDatabaseRequestChild::IndexedDBDeleteDatabaseRequestChild(
                                                 IDBFactory* aFactory,
                                                 IDBOpenDBRequest* aOpenRequest,
                                                 nsIAtom* aDatabaseId)
: mFactory(aFactory), mOpenRequest(aOpenRequest), mDatabaseId(aDatabaseId)
{
  MOZ_COUNT_CTOR(IndexedDBDeleteDatabaseRequestChild);
  MOZ_ASSERT(aFactory);
  MOZ_ASSERT(aOpenRequest);
  MOZ_ASSERT(aDatabaseId);
}

IndexedDBDeleteDatabaseRequestChild::~IndexedDBDeleteDatabaseRequestChild()
{
  MOZ_COUNT_DTOR(IndexedDBDeleteDatabaseRequestChild);
}

bool
IndexedDBDeleteDatabaseRequestChild::Recv__delete__(const nsresult& aRv)
{
  nsRefPtr<IPCDeleteDatabaseHelper> helper =
    new IPCDeleteDatabaseHelper(mOpenRequest);

  if (NS_SUCCEEDED(aRv)) {
    DatabaseInfo::Remove(mDatabaseId);
  }
  else {
    helper->SetError(aRv);
  }

  MainThreadEventTarget target;
  if (NS_FAILED(helper->Dispatch(&target))) {
    NS_WARNING("Dispatch of IPCSetVersionHelper failed!");
    return false;
  }

  return true;
}

bool
IndexedDBDeleteDatabaseRequestChild::RecvBlocked(
                                                const uint64_t& aCurrentVersion)
{
  MOZ_ASSERT(mOpenRequest);

  nsCOMPtr<nsIRunnable> runnable =
    IDBVersionChangeEvent::CreateBlockedRunnable(aCurrentVersion, 0,
                                                 mOpenRequest);

  MainThreadEventTarget target;
  if (NS_FAILED(target.Dispatch(runnable, NS_DISPATCH_NORMAL))) {
    NS_WARNING("Dispatch of blocked event failed!");
  }

  return true;
}

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

nsresult
IPCOpenDatabaseHelper::UnpackResponseFromParentProcess(
                                            const ResponseValue& aResponseValue)
{
  NS_NOTREACHED("Should never get here!");
  return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}

AsyncConnectionHelper::ChildProcessSendResult
IPCOpenDatabaseHelper::SendResponseToChildProcess(nsresult aResultCode)
{
  MOZ_NOT_REACHED("Don't call me!");
  return Error;
}

nsresult
IPCOpenDatabaseHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
{
  MOZ_NOT_REACHED("Don't call me!");
  return NS_ERROR_FAILURE;
}

nsresult
IPCOpenDatabaseHelper::GetSuccessResult(JSContext* aCx, jsval* aVal)
{
  return WrapNative(aCx, NS_ISUPPORTS_CAST(nsIDOMEventTarget*, mDatabase),
                    aVal);
}

nsresult
IPCSetVersionHelper::UnpackResponseFromParentProcess(
                                            const ResponseValue& aResponseValue)
{
  NS_NOTREACHED("Should never get here!");
  return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}

AsyncConnectionHelper::ChildProcessSendResult
IPCSetVersionHelper::SendResponseToChildProcess(nsresult aResultCode)
{
  MOZ_NOT_REACHED("Don't call me!");
  return Error;
}

nsresult
IPCSetVersionHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
{
  MOZ_NOT_REACHED("Don't call me!");
  return NS_ERROR_FAILURE;
}

already_AddRefed<nsDOMEvent>
IPCSetVersionHelper::CreateSuccessEvent()
{
  return IDBVersionChangeEvent::CreateUpgradeNeeded(mOldVersion,
                                                    mRequestedVersion);
}

nsresult
IPCSetVersionHelper::GetSuccessResult(JSContext* aCx, jsval* aVal)
{
  mOpenRequest->SetTransaction(mTransaction);

  return WrapNative(aCx, NS_ISUPPORTS_CAST(nsIDOMEventTarget*, mDatabase),
                    aVal);
}

nsresult
IPCDeleteDatabaseHelper::UnpackResponseFromParentProcess(
                                            const ResponseValue& aResponseValue)
{
  MOZ_NOT_REACHED("Don't call me!");
  return NS_ERROR_FAILURE;
}

AsyncConnectionHelper::ChildProcessSendResult
IPCDeleteDatabaseHelper::SendResponseToChildProcess(nsresult aResultCode)
{
  MOZ_NOT_REACHED("Don't call me!");
  return Error;
}

nsresult
IPCDeleteDatabaseHelper::GetSuccessResult(JSContext* aCx, jsval* aVal)
{
  *aVal = JSVAL_VOID;
  return NS_OK;
}

nsresult
IPCDeleteDatabaseHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
{
  MOZ_NOT_REACHED("Don't call me!");
  return NS_ERROR_FAILURE;
}
