/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "IDBRequest.h"

#include "BackgroundChildImpl.h"
#include "IDBCursor.h"
#include "IDBDatabase.h"
#include "IDBEvents.h"
#include "IDBFactory.h"
#include "IDBIndex.h"
#include "IDBObjectStore.h"
#include "IDBTransaction.h"
#include "IndexedDatabaseManager.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/Move.h"
#include "mozilla/dom/DOMError.h"
#include "mozilla/dom/ErrorEventBinding.h"
#include "mozilla/dom/IDBOpenDBRequestBinding.h"
#include "mozilla/dom/ScriptSettings.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsIScriptContext.h"
#include "nsJSUtils.h"
#include "nsPIDOMWindow.h"
#include "nsString.h"
#include "ReportInternalError.h"
#include "WorkerHolder.h"
#include "WorkerPrivate.h"

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

namespace mozilla {
namespace dom {

using namespace mozilla::dom::indexedDB;
using namespace mozilla::dom::workers;
using namespace mozilla::ipc;

namespace {

NS_DEFINE_IID(kIDBRequestIID, PRIVATE_IDBREQUEST_IID);

} // namespace

IDBRequest::IDBRequest(IDBDatabase* aDatabase)
  : IDBWrapperCache(aDatabase)
#ifdef DEBUG
  , mOwningThread(nullptr)
#endif
  , mLoggingSerialNumber(0)
  , mLineNo(0)
  , mColumn(0)
  , mHaveResultOrErrorCode(false)
{
  MOZ_ASSERT(aDatabase);
  aDatabase->AssertIsOnOwningThread();

  InitMembers();
}

IDBRequest::IDBRequest(nsPIDOMWindowInner* aOwner)
  : IDBWrapperCache(aOwner)
#ifdef DEBUG
  , mOwningThread(nullptr)
#endif
  , mLoggingSerialNumber(0)
  , mLineNo(0)
  , mColumn(0)
  , mHaveResultOrErrorCode(false)
{
  InitMembers();
}

IDBRequest::~IDBRequest()
{
  AssertIsOnOwningThread();
}

#ifdef DEBUG

void
IDBRequest::AssertIsOnOwningThread() const
{
  MOZ_ASSERT(mOwningThread);
  MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
}

#endif // DEBUG

void
IDBRequest::InitMembers()
{
#ifdef DEBUG
  mOwningThread = PR_GetCurrentThread();
#endif
  AssertIsOnOwningThread();

  mResultVal.setUndefined();
  mLoggingSerialNumber = NextSerialNumber();
  mErrorCode = NS_OK;
  mLineNo = 0;
  mColumn = 0;
  mHaveResultOrErrorCode = false;
}

// static
already_AddRefed<IDBRequest>
IDBRequest::Create(JSContext* aCx,
                   IDBDatabase* aDatabase,
                   IDBTransaction* aTransaction)
{
  MOZ_ASSERT(aCx);
  MOZ_ASSERT(aDatabase);
  aDatabase->AssertIsOnOwningThread();

  RefPtr<IDBRequest> request = new IDBRequest(aDatabase);
  CaptureCaller(aCx, request->mFilename, &request->mLineNo, &request->mColumn);

  request->mTransaction = aTransaction;
  request->SetScriptOwner(aDatabase->GetScriptOwner());

  return request.forget();
}

// static
already_AddRefed<IDBRequest>
IDBRequest::Create(JSContext* aCx,
                   IDBObjectStore* aSourceAsObjectStore,
                   IDBDatabase* aDatabase,
                   IDBTransaction* aTransaction)
{
  MOZ_ASSERT(aSourceAsObjectStore);
  aSourceAsObjectStore->AssertIsOnOwningThread();

  RefPtr<IDBRequest> request = Create(aCx, aDatabase, aTransaction);

  request->mSourceAsObjectStore = aSourceAsObjectStore;

  return request.forget();
}

// static
already_AddRefed<IDBRequest>
IDBRequest::Create(JSContext* aCx,
                   IDBIndex* aSourceAsIndex,
                   IDBDatabase* aDatabase,
                   IDBTransaction* aTransaction)
{
  MOZ_ASSERT(aSourceAsIndex);
  aSourceAsIndex->AssertIsOnOwningThread();

  RefPtr<IDBRequest> request = Create(aCx, aDatabase, aTransaction);

  request->mSourceAsIndex = aSourceAsIndex;

  return request.forget();
}

// static
uint64_t
IDBRequest::NextSerialNumber()
{
  BackgroundChildImpl::ThreadLocal* threadLocal =
    BackgroundChildImpl::GetThreadLocalForCurrentThread();
  MOZ_ASSERT(threadLocal);

  ThreadLocal* idbThreadLocal = threadLocal->mIndexedDBThreadLocal;
  MOZ_ASSERT(idbThreadLocal);

  return idbThreadLocal->NextRequestSN();
}

void
IDBRequest::SetLoggingSerialNumber(uint64_t aLoggingSerialNumber)
{
  AssertIsOnOwningThread();
  MOZ_ASSERT(aLoggingSerialNumber > mLoggingSerialNumber);

  mLoggingSerialNumber = aLoggingSerialNumber;
}

void
IDBRequest::CaptureCaller(JSContext* aCx, nsAString& aFilename,
                          uint32_t* aLineNo, uint32_t* aColumn)
{
  MOZ_ASSERT(aFilename.IsEmpty());
  MOZ_ASSERT(aLineNo);
  MOZ_ASSERT(aColumn);

  nsJSUtils::GetCallingLocation(aCx, aFilename, aLineNo, aColumn);
}

void
IDBRequest::GetSource(
             Nullable<OwningIDBObjectStoreOrIDBIndexOrIDBCursor>& aSource) const
{
  AssertIsOnOwningThread();

  MOZ_ASSERT_IF(mSourceAsObjectStore, !mSourceAsIndex);
  MOZ_ASSERT_IF(mSourceAsIndex, !mSourceAsObjectStore);
  MOZ_ASSERT_IF(mSourceAsCursor, mSourceAsObjectStore || mSourceAsIndex);

  // Always check cursor first since cursor requests hold both the cursor and
  // the objectStore or index the cursor came from.
  if (mSourceAsCursor) {
    aSource.SetValue().SetAsIDBCursor() = mSourceAsCursor;
  } else if (mSourceAsObjectStore) {
    aSource.SetValue().SetAsIDBObjectStore() = mSourceAsObjectStore;
  } else if (mSourceAsIndex) {
    aSource.SetValue().SetAsIDBIndex() = mSourceAsIndex;
  } else {
    aSource.SetNull();
  }
}

void
IDBRequest::Reset()
{
  AssertIsOnOwningThread();

  mResultVal.setUndefined();
  mHaveResultOrErrorCode = false;
  mError = nullptr;
}

void
IDBRequest::DispatchNonTransactionError(nsresult aErrorCode)
{
  AssertIsOnOwningThread();
  MOZ_ASSERT(NS_FAILED(aErrorCode));
  MOZ_ASSERT(NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_DOM_INDEXEDDB);

  SetError(aErrorCode);

  // Make an error event and fire it at the target.
  nsCOMPtr<nsIDOMEvent> event =
    CreateGenericEvent(this,
                       nsDependentString(kErrorEventType),
                       eDoesBubble,
                       eCancelable);
  MOZ_ASSERT(event);

  bool ignored;
  if (NS_FAILED(DispatchEvent(event, &ignored))) {
    NS_WARNING("Failed to dispatch event!");
  }
}

void
IDBRequest::SetError(nsresult aRv)
{
  AssertIsOnOwningThread();
  MOZ_ASSERT(NS_FAILED(aRv));
  MOZ_ASSERT(NS_ERROR_GET_MODULE(aRv) == NS_ERROR_MODULE_DOM_INDEXEDDB);
  MOZ_ASSERT(!mError);

  mHaveResultOrErrorCode = true;
  mError = new DOMError(GetOwner(), aRv);
  mErrorCode = aRv;

  mResultVal.setUndefined();
}

#ifdef DEBUG

nsresult
IDBRequest::GetErrorCode() const
{
  AssertIsOnOwningThread();
  MOZ_ASSERT(mHaveResultOrErrorCode);

  return mErrorCode;
}

DOMError*
IDBRequest::GetErrorAfterResult() const
{
  AssertIsOnOwningThread();
  MOZ_ASSERT(mHaveResultOrErrorCode);

  return mError;
}

#endif // DEBUG

void
IDBRequest::GetCallerLocation(nsAString& aFilename, uint32_t* aLineNo,
                              uint32_t* aColumn) const
{
  AssertIsOnOwningThread();
  MOZ_ASSERT(aLineNo);
  MOZ_ASSERT(aColumn);

  aFilename = mFilename;
  *aLineNo = mLineNo;
  *aColumn = mColumn;
}

IDBRequestReadyState
IDBRequest::ReadyState() const
{
  AssertIsOnOwningThread();

  return IsPending() ?
    IDBRequestReadyState::Pending :
    IDBRequestReadyState::Done;
}

void
IDBRequest::SetSource(IDBCursor* aSource)
{
  AssertIsOnOwningThread();
  MOZ_ASSERT(aSource);
  MOZ_ASSERT(mSourceAsObjectStore || mSourceAsIndex);
  MOZ_ASSERT(!mSourceAsCursor);

  mSourceAsCursor = aSource;
}

JSObject*
IDBRequest::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
  return IDBRequestBinding::Wrap(aCx, this, aGivenProto);
}

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

  if (!mHaveResultOrErrorCode) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  aResult.set(mResultVal);
}

void
IDBRequest::SetResultCallback(ResultCallback* aCallback)
{
  AssertIsOnOwningThread();
  MOZ_ASSERT(aCallback);
  MOZ_ASSERT(!mHaveResultOrErrorCode);
  MOZ_ASSERT(mResultVal.isUndefined());
  MOZ_ASSERT(!mError);

  // See if our window is still valid.
  if (NS_WARN_IF(NS_FAILED(CheckInnerWindowCorrectness()))) {
    SetError(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
    return;
  }

  AutoJSAPI autoJS;
  Maybe<JSAutoCompartment> ac;

  if (GetScriptOwner()) {
    // If we have a script owner we want the SafeJSContext and then to enter the
    // script owner's compartment.
    autoJS.Init();
    ac.emplace(autoJS.cx(), GetScriptOwner());
  } else {
    // Otherwise our owner is a window and we use that to initialize.
    MOZ_ASSERT(GetOwner());
    if (!autoJS.Init(GetOwner())) {
      IDB_WARNING("Failed to initialize AutoJSAPI!");
      SetError(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
      return;
    }
  }

  JSContext* cx = autoJS.cx();

  AssertIsRooted();

  JS::Rooted<JS::Value> result(cx);
  nsresult rv = aCallback->GetResult(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;

  mHaveResultOrErrorCode = true;
}

DOMError*
IDBRequest::GetError(ErrorResult& aRv)
{
  AssertIsOnOwningThread();

  if (!mHaveResultOrErrorCode) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return nullptr;
  }

  return mError;
}

NS_IMPL_CYCLE_COLLECTION_CLASS(IDBRequest)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBRequest, IDBWrapperCache)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceAsObjectStore)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceAsIndex)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceAsCursor)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBRequest, IDBWrapperCache)
  tmp->mResultVal.setUndefined();
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceAsObjectStore)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceAsIndex)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceAsCursor)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransaction)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(IDBRequest, IDBWrapperCache)
  // Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because
  // DOMEventTargetHelper does it for us.
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultVal)
NS_IMPL_CYCLE_COLLECTION_TRACE_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBRequest)
  if (aIID.Equals(kIDBRequestIID)) {
    foundInterface = this;
  } else
NS_INTERFACE_MAP_END_INHERITING(IDBWrapperCache)

NS_IMPL_ADDREF_INHERITED(IDBRequest, IDBWrapperCache)
NS_IMPL_RELEASE_INHERITED(IDBRequest, IDBWrapperCache)

nsresult
IDBRequest::GetEventTargetParent(EventChainPreVisitor& aVisitor)
{
  AssertIsOnOwningThread();

  aVisitor.mCanHandle = true;
  aVisitor.SetParentTarget(mTransaction, false);
  return NS_OK;
}

class IDBOpenDBRequest::WorkerHolder final
  : public mozilla::dom::workers::WorkerHolder
{
  WorkerPrivate* mWorkerPrivate;
#ifdef DEBUG
  // This is only here so that assertions work in the destructor even if
  // NoteAddWorkerHolderFailed was called.
  WorkerPrivate* mWorkerPrivateDEBUG;
#endif

public:
  explicit
  WorkerHolder(WorkerPrivate* aWorkerPrivate)
    : mWorkerPrivate(aWorkerPrivate)
#ifdef DEBUG
    , mWorkerPrivateDEBUG(aWorkerPrivate)
#endif
  {
    MOZ_ASSERT(aWorkerPrivate);
    aWorkerPrivate->AssertIsOnWorkerThread();

    MOZ_COUNT_CTOR(IDBOpenDBRequest::WorkerHolder);
  }

  ~WorkerHolder()
  {
#ifdef DEBUG
    mWorkerPrivateDEBUG->AssertIsOnWorkerThread();
#endif

    MOZ_COUNT_DTOR(IDBOpenDBRequest::WorkerHolder);
  }

  void
  NoteAddWorkerHolderFailed()
  {
    MOZ_ASSERT(mWorkerPrivate);
    mWorkerPrivate->AssertIsOnWorkerThread();

    mWorkerPrivate = nullptr;
  }

private:
  virtual bool
  Notify(Status aStatus) override;
};

IDBOpenDBRequest::IDBOpenDBRequest(IDBFactory* aFactory,
                                   nsPIDOMWindowInner* aOwner,
                                   bool aFileHandleDisabled)
  : IDBRequest(aOwner)
  , mFactory(aFactory)
  , mFileHandleDisabled(aFileHandleDisabled)
{
  AssertIsOnOwningThread();
  MOZ_ASSERT(aFactory);

  // aOwner may be null.
}

IDBOpenDBRequest::~IDBOpenDBRequest()
{
  AssertIsOnOwningThread();
}

// static
already_AddRefed<IDBOpenDBRequest>
IDBOpenDBRequest::CreateForWindow(JSContext* aCx,
                                  IDBFactory* aFactory,
                                  nsPIDOMWindowInner* aOwner,
                                  JS::Handle<JSObject*> aScriptOwner)
{
  MOZ_ASSERT(aFactory);
  aFactory->AssertIsOnOwningThread();
  MOZ_ASSERT(aOwner);
  MOZ_ASSERT(aScriptOwner);

  bool fileHandleDisabled = !IndexedDatabaseManager::IsFileHandleEnabled();

  RefPtr<IDBOpenDBRequest> request =
    new IDBOpenDBRequest(aFactory, aOwner, fileHandleDisabled);
  CaptureCaller(aCx, request->mFilename, &request->mLineNo, &request->mColumn);

  request->SetScriptOwner(aScriptOwner);

  return request.forget();
}

// static
already_AddRefed<IDBOpenDBRequest>
IDBOpenDBRequest::CreateForJS(JSContext* aCx,
                              IDBFactory* aFactory,
                              JS::Handle<JSObject*> aScriptOwner)
{
  MOZ_ASSERT(aFactory);
  aFactory->AssertIsOnOwningThread();
  MOZ_ASSERT(aScriptOwner);

  bool fileHandleDisabled = !IndexedDatabaseManager::IsFileHandleEnabled();

  RefPtr<IDBOpenDBRequest> request =
    new IDBOpenDBRequest(aFactory, nullptr, fileHandleDisabled);
  CaptureCaller(aCx, request->mFilename, &request->mLineNo, &request->mColumn);

  request->SetScriptOwner(aScriptOwner);

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

    workerPrivate->AssertIsOnWorkerThread();

    nsAutoPtr<WorkerHolder> workerHolder(new WorkerHolder(workerPrivate));
    if (NS_WARN_IF(!workerHolder->HoldWorker(workerPrivate, Canceling))) {
      workerHolder->NoteAddWorkerHolderFailed();
      return nullptr;
    }

    request->mWorkerHolder = Move(workerHolder);
  }

  return request.forget();
}

void
IDBOpenDBRequest::SetTransaction(IDBTransaction* aTransaction)
{
  AssertIsOnOwningThread();

  MOZ_ASSERT(!aTransaction || !mTransaction);

  mTransaction = aTransaction;
}

void
IDBOpenDBRequest::NoteComplete()
{
  AssertIsOnOwningThread();
  MOZ_ASSERT_IF(!NS_IsMainThread(), mWorkerHolder);

  // If we have a WorkerHolder installed on the worker then nulling this out
  // will uninstall it from the worker.
  mWorkerHolder = nullptr;
}

NS_IMPL_CYCLE_COLLECTION_CLASS(IDBOpenDBRequest)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBOpenDBRequest,
                                                  IDBRequest)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFactory)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBOpenDBRequest,
                                                IDBRequest)
  // Don't unlink mFactory!
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IDBOpenDBRequest)
NS_INTERFACE_MAP_END_INHERITING(IDBRequest)

NS_IMPL_ADDREF_INHERITED(IDBOpenDBRequest, IDBRequest)
NS_IMPL_RELEASE_INHERITED(IDBOpenDBRequest, IDBRequest)

nsresult
IDBOpenDBRequest::PostHandleEvent(EventChainPostVisitor& aVisitor)
{
  nsresult rv =
    IndexedDatabaseManager::CommonPostHandleEvent(aVisitor, mFactory);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  return NS_OK;
}

JSObject*
IDBOpenDBRequest::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
  AssertIsOnOwningThread();

  return IDBOpenDBRequestBinding::Wrap(aCx, this, aGivenProto);
}

bool
IDBOpenDBRequest::
WorkerHolder::Notify(Status aStatus)
{
  MOZ_ASSERT(mWorkerPrivate);
  mWorkerPrivate->AssertIsOnWorkerThread();
  MOZ_ASSERT(aStatus > Running);

  // There's nothing we can really do here at the moment...
  NS_WARNING("Worker closing but IndexedDB is waiting to open a database!");

  return true;
}

} // namespace dom
} // namespace mozilla
