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

#include "ErrorList.h"
#include "LSDatabase.h"
#include "LSObserver.h"
#include "LSSnapshot.h"
#include "LocalStorageCommon.h"
#include "mozilla/Assertions.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/dom/LSValue.h"
#include "mozilla/dom/Storage.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/ThreadUtils.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "nsCOMPtr.h"

namespace mozilla::dom {

/*******************************************************************************
 * LSDatabaseChild
 ******************************************************************************/

LSDatabaseChild::LSDatabaseChild(LSDatabase* aDatabase) : mDatabase(aDatabase) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aDatabase);

  MOZ_COUNT_CTOR(LSDatabaseChild);
}

LSDatabaseChild::~LSDatabaseChild() {
  AssertIsOnOwningThread();

  MOZ_COUNT_DTOR(LSDatabaseChild);
}

void LSDatabaseChild::Shutdown() {
  AssertIsOnOwningThread();

  if (mDatabase) {
    mDatabase->ClearActor();
    mDatabase = nullptr;

    Close();
  }
}

void LSDatabaseChild::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();

  if (mDatabase) {
    mDatabase->ClearActor();
#ifdef DEBUG
    mDatabase = nullptr;
#endif
  }
}

mozilla::ipc::IPCResult LSDatabaseChild::RecvRequestAllowToClose() {
  AssertIsOnOwningThread();

  if (mDatabase) {
    mDatabase->RequestAllowToClose();

    // TODO: A new datastore will be prepared at first LocalStorage API
    //       synchronous call. It would be better to start preparing a new
    //       datastore right here, but asynchronously.
    //       However, we probably shouldn't do that if we are shutting down.
  }

  return IPC_OK();
}

PBackgroundLSSnapshotChild* LSDatabaseChild::AllocPBackgroundLSSnapshotChild(
    const nsAString& aDocumentURI, const nsAString& aKey,
    const bool& aIncreasePeakUsage, const int64_t& aMinSize,
    LSSnapshotInitInfo* aInitInfo) {
  MOZ_CRASH("PBackgroundLSSnapshotChild actor should be manually constructed!");
}

bool LSDatabaseChild::DeallocPBackgroundLSSnapshotChild(
    PBackgroundLSSnapshotChild* aActor) {
  MOZ_ASSERT(aActor);

  delete aActor;
  return true;
}

/*******************************************************************************
 * LSObserverChild
 ******************************************************************************/

LSObserverChild::LSObserverChild(LSObserver* aObserver) : mObserver(aObserver) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aObserver);

  MOZ_COUNT_CTOR(LSObserverChild);
}

LSObserverChild::~LSObserverChild() {
  AssertIsOnOwningThread();

  MOZ_COUNT_DTOR(LSObserverChild);
}

void LSObserverChild::SendDeleteMeInternal() {
  AssertIsOnOwningThread();

  if (mObserver) {
    mObserver->ClearActor();
    mObserver = nullptr;

    MOZ_ALWAYS_TRUE(PBackgroundLSObserverChild::SendDeleteMe());
  }
}

void LSObserverChild::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();

  if (mObserver) {
    mObserver->ClearActor();
#ifdef DEBUG
    mObserver = nullptr;
#endif
  }
}

mozilla::ipc::IPCResult LSObserverChild::RecvObserve(
    const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId,
    const nsAString& aDocumentURI, const nsAString& aKey,
    const LSValue& aOldValue, const LSValue& aNewValue) {
  AssertIsOnOwningThread();

  if (!mObserver) {
    return IPC_OK();
  }

  QM_TRY_INSPECT(const auto& principal,
                 PrincipalInfoToPrincipal(aPrincipalInfo),
                 IPC_FAIL(this, "PrincipalInfoToPrincipal failed!"));

  Storage::NotifyChange(/* aStorage */ nullptr, principal, aKey,
                        aOldValue.AsString(), aNewValue.AsString(),
                        /* aStorageType */ kLocalStorageType, aDocumentURI,
                        /* aIsPrivate */ !!aPrivateBrowsingId,
                        /* aImmediateDispatch */ true);

  return IPC_OK();
}

/*******************************************************************************
 * LocalStorageRequestChild
 ******************************************************************************/

LSRequestChild::LSRequestChild() : mFinishing(false) {
  AssertIsOnOwningThread();

  MOZ_COUNT_CTOR(LSRequestChild);
}

LSRequestChild::~LSRequestChild() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(!mCallback);

  MOZ_COUNT_DTOR(LSRequestChild);
}

void LSRequestChild::SetCallback(LSRequestChildCallback* aCallback) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aCallback);
  MOZ_ASSERT(!mCallback);
  MOZ_ASSERT(Manager());

  mCallback = aCallback;
}

bool LSRequestChild::Finishing() const {
  AssertIsOnOwningThread();

  return mFinishing;
}

void LSRequestChild::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();

  if (mCallback) {
    MOZ_ASSERT(aWhy != Deletion);

    mCallback->OnResponse(NS_ERROR_FAILURE);

    mCallback = nullptr;
  }
}

mozilla::ipc::IPCResult LSRequestChild::Recv__delete__(
    LSRequestResponse&& aResponse) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mCallback);

  mCallback->OnResponse(std::move(aResponse));

  mCallback = nullptr;

  return IPC_OK();
}

mozilla::ipc::IPCResult LSRequestChild::RecvReady() {
  AssertIsOnOwningThread();

  mFinishing = true;

  quota::SleepIfEnabled(
      StaticPrefs::dom_storage_requestFinalization_pauseOnDOMFileThreadMs());

  // We only expect this to return false if the channel has been closed, but
  // PBackground's channel never gets shutdown.
  MOZ_ALWAYS_TRUE(SendFinish());

  return IPC_OK();
}

/*******************************************************************************
 * LSSimpleRequestChild
 ******************************************************************************/

LSSimpleRequestChild::LSSimpleRequestChild() {
  AssertIsOnOwningThread();

  MOZ_COUNT_CTOR(LSSimpleRequestChild);
}

LSSimpleRequestChild::~LSSimpleRequestChild() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(!mCallback);

  MOZ_COUNT_DTOR(LSSimpleRequestChild);
}

void LSSimpleRequestChild::SetCallback(
    LSSimpleRequestChildCallback* aCallback) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aCallback);
  MOZ_ASSERT(!mCallback);
  MOZ_ASSERT(Manager());

  mCallback = aCallback;
}

void LSSimpleRequestChild::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();

  if (mCallback) {
    MOZ_ASSERT(aWhy != Deletion);

    mCallback->OnResponse(NS_ERROR_FAILURE);

    mCallback = nullptr;
  }
}

mozilla::ipc::IPCResult LSSimpleRequestChild::Recv__delete__(
    const LSSimpleRequestResponse& aResponse) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mCallback);

  mCallback->OnResponse(aResponse);

  mCallback = nullptr;

  return IPC_OK();
}

/*******************************************************************************
 * LSSnapshotChild
 ******************************************************************************/

LSSnapshotChild::LSSnapshotChild(LSSnapshot* aSnapshot) : mSnapshot(aSnapshot) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(aSnapshot);

  MOZ_COUNT_CTOR(LSSnapshotChild);
}

LSSnapshotChild::~LSSnapshotChild() {
  AssertIsOnOwningThread();

  MOZ_COUNT_DTOR(LSSnapshotChild);
}

void LSSnapshotChild::SendDeleteMeInternal() {
  AssertIsOnOwningThread();

  if (mSnapshot) {
    mSnapshot->ClearActor();
    mSnapshot = nullptr;

    MOZ_ALWAYS_TRUE(PBackgroundLSSnapshotChild::SendDeleteMe());
  }
}

void LSSnapshotChild::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnOwningThread();

  if (mSnapshot) {
    mSnapshot->ClearActor();
#ifdef DEBUG
    mSnapshot = nullptr;
#endif
  }
}

mozilla::ipc::IPCResult LSSnapshotChild::RecvMarkDirty() {
  AssertIsOnOwningThread();

  if (!mSnapshot) {
    return IPC_OK();
  }

  mSnapshot->MarkDirty();

  return IPC_OK();
}

}  // namespace mozilla::dom
