/* 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 "BackgroundChild.h"
#include "BackgroundParent.h"

#include "BackgroundChildImpl.h"
#include "BackgroundParentImpl.h"
#include "MainThreadUtils.h"
#include "base/process_util.h"
#include "base/task.h"
#include "FileDescriptor.h"
#include "GeckoProfiler.h"
#include "InputStreamUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/MozPromise.h"
#include "mozilla/Services.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/ProcessIsolation.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/ipc/BackgroundStarterChild.h"
#include "mozilla/ipc/BackgroundStarterParent.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/ipc/PBackgroundStarter.h"
#include "mozilla/ipc/ProtocolTypes.h"
#include "nsCOMPtr.h"
#include "nsIEventTarget.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIRunnable.h"
#include "nsISupportsImpl.h"
#include "nsIThread.h"
#include "nsITimer.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"
#include "nsTraceRefcnt.h"
#include "nsXULAppAPI.h"
#include "nsXPCOMPrivate.h"
#include "prthread.h"

#ifdef RELEASE_OR_BETA
#  define THREADSAFETY_ASSERT MOZ_ASSERT
#else
#  define THREADSAFETY_ASSERT MOZ_RELEASE_ASSERT
#endif

#define CRASH_IN_CHILD_PROCESS(_msg) \
  do {                               \
    if (XRE_IsParentProcess()) {     \
      MOZ_ASSERT(false, _msg);       \
    } else {                         \
      MOZ_CRASH(_msg);               \
    }                                \
  } while (0)

using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::ipc;
using namespace mozilla::net;

namespace {

// This exists so that [Assert]IsOnBackgroundThread() can continue to work
// during shutdown. We can rely on TLS being 0 initialized, so only the
// background thread itself needs to ever set this flag.
static MOZ_THREAD_LOCAL(bool) sTLSIsOnBackgroundThread;

class ChildImpl;

// -----------------------------------------------------------------------------
// Utility Functions
// -----------------------------------------------------------------------------

void AssertIsOnMainThread() { THREADSAFETY_ASSERT(NS_IsMainThread()); }

// -----------------------------------------------------------------------------
// ParentImpl Declaration
// -----------------------------------------------------------------------------

class ParentImpl final : public BackgroundParentImpl {
  friend class ChildImpl;
  friend class mozilla::ipc::BackgroundParent;
  friend class mozilla::ipc::BackgroundStarterParent;

 private:
  class ShutdownObserver;

  struct MOZ_STACK_CLASS TimerCallbackClosure {
    nsIThread* mThread;
    nsTArray<IToplevelProtocol*>* mLiveActors;

    TimerCallbackClosure(nsIThread* aThread,
                         nsTArray<IToplevelProtocol*>* aLiveActors)
        : mThread(aThread), mLiveActors(aLiveActors) {
      AssertIsInMainProcess();
      AssertIsOnMainThread();
      MOZ_ASSERT(aThread);
      MOZ_ASSERT(aLiveActors);
    }
  };

  // The length of time we will wait at shutdown for all actors to clean
  // themselves up before forcing them to be destroyed.
  static const uint32_t kShutdownTimerDelayMS = 10000;

  // This is only modified on the main thread. It is null if the thread does not
  // exist or is shutting down.
  static StaticRefPtr<nsIThread> sBackgroundThread;

  // This is created and destroyed on the main thread but only modified on the
  // background thread. It is specific to each instance of sBackgroundThread.
  static nsTArray<IToplevelProtocol*>* sLiveActorsForBackgroundThread;

  // This is only modified on the main thread.
  static StaticRefPtr<nsITimer> sShutdownTimer;

  // Maintains a count of live actors so that the background thread can be shut
  // down when it is no longer needed.
  // May be incremented on either the background thread (by an existing actor)
  // or on the main thread, but must be decremented on the main thread.
  static Atomic<uint64_t> sLiveActorCount;

  // This is only modified on the main thread. It is true after the shutdown
  // observer is registered and is never unset thereafter.
  static bool sShutdownObserverRegistered;

  // This is only modified on the main thread. It prevents us from trying to
  // create the background thread after application shutdown has started.
  static bool sShutdownHasStarted;

  // null if this is a same-process actor.
  const RefPtr<ThreadsafeContentParentHandle> mContent;

  // Set when the actor is opened successfully and used to handle shutdown
  // hangs. Only touched on the background thread.
  nsTArray<IToplevelProtocol*>* mLiveActorArray;

  // Set at construction to indicate whether this parent actor corresponds to a
  // child actor in another process or to a child actor from a different thread
  // in the same process.
  const bool mIsOtherProcessActor;

  // Set after ActorDestroy has been called. Only touched on the background
  // thread.
  bool mActorDestroyed;

 public:
  static already_AddRefed<nsISerialEventTarget> GetBackgroundThread() {
    AssertIsInMainProcess();
    THREADSAFETY_ASSERT(NS_IsMainThread() || IsOnBackgroundThread());
    return do_AddRef(sBackgroundThread);
  }

  static bool IsOnBackgroundThread() {
    MOZ_ASSERT(sTLSIsOnBackgroundThread.initialized());
    return sTLSIsOnBackgroundThread.get();
  }

  static void AssertIsOnBackgroundThread() {
    THREADSAFETY_ASSERT(IsOnBackgroundThread());
  }

  // `ParentImpl` instances need to be deleted on the main thread, despite IPC
  // controlling them on a background thread. Use `_WITH_DELETE_ON_MAIN_THREAD`
  // to force destruction to occur on the desired thread.
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD(ParentImpl,
                                                                   override)

  void Destroy();

 private:
  // Forwarded from BackgroundParent.
  static bool IsOtherProcessActor(PBackgroundParent* aBackgroundActor);

  // Forwarded from BackgroundParent.
  static ThreadsafeContentParentHandle* GetContentParentHandle(
      PBackgroundParent* aBackgroundActor);

  // Forwarded from BackgroundParent.
  static uint64_t GetChildID(PBackgroundParent* aBackgroundActor);

  // Forwarded from BackgroundParent.
  static void KillHardAsync(PBackgroundParent* aBackgroundActor,
                            const nsACString& aReason);

  // Forwarded from BackgroundParent.
  static bool AllocStarter(ContentParent* aContent,
                           Endpoint<PBackgroundStarterParent>&& aEndpoint,
                           bool aCrossProcess = true);

  static bool CreateBackgroundThread();

  static void ShutdownBackgroundThread();

  static void ShutdownTimerCallback(nsITimer* aTimer, void* aClosure);

  // NOTE: ParentImpl could be used in 2 cases below.
  // 1. Within the parent process.
  // 2. Between parent process and content process.
  // |aContent| should be not null for case 2. For cases 1, it's null.
  explicit ParentImpl(ThreadsafeContentParentHandle* aContent,
                      bool aIsOtherProcessActor)
      : mContent(aContent),
        mLiveActorArray(nullptr),
        mIsOtherProcessActor(aIsOtherProcessActor),
        mActorDestroyed(false) {
    AssertIsInMainProcess();
    MOZ_ASSERT_IF(!aIsOtherProcessActor, XRE_IsParentProcess());
  }

  ~ParentImpl() {
    AssertIsInMainProcess();
    AssertIsOnMainThread();
  }

  void MainThreadActorDestroy();

  void SetLiveActorArray(nsTArray<IToplevelProtocol*>* aLiveActorArray) {
    AssertIsInMainProcess();
    AssertIsOnBackgroundThread();
    MOZ_ASSERT(aLiveActorArray);
    MOZ_ASSERT(!aLiveActorArray->Contains(this));
    MOZ_ASSERT(!mLiveActorArray);
    MOZ_ASSERT(mIsOtherProcessActor);

    mLiveActorArray = aLiveActorArray;
    mLiveActorArray->AppendElement(this);
  }

  // These methods are only called by IPDL.
  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
};

// -----------------------------------------------------------------------------
// ChildImpl Declaration
// -----------------------------------------------------------------------------

class ChildImpl final : public BackgroundChildImpl {
  friend class mozilla::ipc::BackgroundChild;
  friend class mozilla::ipc::BackgroundChildImpl;
  friend class mozilla::ipc::BackgroundStarterChild;

  typedef base::ProcessId ProcessId;

  class ShutdownObserver;

 public:
  struct ThreadLocalInfo {
    ThreadLocalInfo() = default;

    RefPtr<ChildImpl> mActor;
    UniquePtr<BackgroundChildImpl::ThreadLocal> mConsumerThreadLocal;
#ifdef DEBUG
    bool mClosed = false;
#endif
  };

 private:
  // A thread-local index that is not valid.
  static constexpr unsigned int kBadThreadLocalIndex =
      static_cast<unsigned int>(-1);

  // ThreadInfoWrapper encapsulates ThreadLocalInfo and ThreadLocalIndex and
  // also provides some common functions for creating PBackground IPC actor.
  class ThreadInfoWrapper final {
    friend class ChildImpl;

   public:
    using ActorCreateFunc = void (*)(ThreadLocalInfo*, unsigned int,
                                     nsIEventTarget*, ChildImpl**);

    ThreadInfoWrapper() = default;

    void Startup() {
      MOZ_ASSERT(mThreadLocalIndex == kBadThreadLocalIndex,
                 "ThreadInfoWrapper::Startup() called more than once!");

      PRStatus status =
          PR_NewThreadPrivateIndex(&mThreadLocalIndex, ThreadLocalDestructor);
      MOZ_RELEASE_ASSERT(status == PR_SUCCESS,
                         "PR_NewThreadPrivateIndex failed!");

      MOZ_ASSERT(mThreadLocalIndex != kBadThreadLocalIndex);
    }

    void Shutdown() {
      if (sShutdownHasStarted) {
        MOZ_ASSERT_IF(mThreadLocalIndex != kBadThreadLocalIndex,
                      !PR_GetThreadPrivate(mThreadLocalIndex));
        return;
      }

      if (mThreadLocalIndex == kBadThreadLocalIndex) {
        return;
      }

      RefPtr<BackgroundStarterChild> starter;
      {
        auto lock = mStarter.Lock();
        starter = lock->forget();
      }
      if (starter) {
        CloseStarter(starter);
      }

      ThreadLocalInfo* threadLocalInfo;
#ifdef DEBUG
      threadLocalInfo =
          static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(mThreadLocalIndex));
      MOZ_ASSERT(!threadLocalInfo);
#endif

      threadLocalInfo = mMainThreadInfo;
      if (threadLocalInfo) {
#ifdef DEBUG
        MOZ_ASSERT(!threadLocalInfo->mClosed);
        threadLocalInfo->mClosed = true;
#endif

        ThreadLocalDestructor(threadLocalInfo);
        mMainThreadInfo = nullptr;
      }
    }

    template <typename Actor>
    void InitStarter(Actor* aActor) {
      AssertIsOnMainThread();

      // Create a pair of endpoints and send them to the other process.
      Endpoint<PBackgroundStarterParent> parent;
      Endpoint<PBackgroundStarterChild> child;
      MOZ_ALWAYS_SUCCEEDS(PBackgroundStarter::CreateEndpoints(
          aActor->OtherEndpointProcInfo(), EndpointProcInfo::Current(), &parent,
          &child));
      MOZ_ALWAYS_TRUE(aActor->SendInitBackground(std::move(parent)));

      InitStarter(std::move(child));
    }

    void InitStarter(Endpoint<PBackgroundStarterChild>&& aEndpoint) {
      AssertIsOnMainThread();

      EndpointProcInfo otherProcInfo = aEndpoint.OtherEndpointProcInfo();

      nsCOMPtr<nsISerialEventTarget> taskQueue;
      MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue(
          "PBackgroundStarter Queue", getter_AddRefs(taskQueue)));

      RefPtr starter =
          MakeRefPtr<BackgroundStarterChild>(otherProcInfo, taskQueue);

      taskQueue->Dispatch(NS_NewRunnableFunction(
          "PBackgroundStarterChild Init",
          [starter, endpoint = std::move(aEndpoint)]() mutable {
            MOZ_ALWAYS_TRUE(endpoint.Bind(starter));
          }));

      // Swap in the newly initialized `BackgroundStarterChild`, and close the
      // previous one if we're replacing an existing PBackgroundStarterChild
      // instance.
      RefPtr<BackgroundStarterChild> prevStarter;
      {
        auto lock = mStarter.Lock();
        prevStarter = lock->forget();
        *lock = starter.forget();
      }
      if (prevStarter) {
        CloseStarter(prevStarter);
      }
    }

    void CloseForCurrentThread() {
      MOZ_ASSERT(!NS_IsMainThread());

      if (mThreadLocalIndex == kBadThreadLocalIndex) {
        return;
      }

      auto* threadLocalInfo =
          static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(mThreadLocalIndex));

      if (!threadLocalInfo) {
        return;
      }

#ifdef DEBUG
      MOZ_ASSERT(!threadLocalInfo->mClosed);
      threadLocalInfo->mClosed = true;
#endif

      // Clearing the thread local will synchronously close the actor.
      DebugOnly<PRStatus> status =
          PR_SetThreadPrivate(mThreadLocalIndex, nullptr);
      MOZ_ASSERT(status == PR_SUCCESS);
    }

    PBackgroundChild* GetOrCreateForCurrentThread() {
      // Processes can be told to do final CC's during shutdown even though
      // they never finished starting (and thus call this), because they
      // hadn't gotten far enough to call Startup() before shutdown began.
      if (mThreadLocalIndex == kBadThreadLocalIndex) {
        NS_ERROR("BackgroundChild::Startup() was never called");
        return nullptr;
      }
      if (NS_IsMainThread() && ChildImpl::sShutdownHasStarted) {
        return nullptr;
      }

      auto* threadLocalInfo = NS_IsMainThread()
                                  ? mMainThreadInfo
                                  : static_cast<ThreadLocalInfo*>(
                                        PR_GetThreadPrivate(mThreadLocalIndex));

      if (!threadLocalInfo) {
        auto newInfo = MakeUnique<ThreadLocalInfo>();

        if (NS_IsMainThread()) {
          mMainThreadInfo = newInfo.get();
        } else {
          if (PR_SetThreadPrivate(mThreadLocalIndex, newInfo.get()) !=
              PR_SUCCESS) {
            CRASH_IN_CHILD_PROCESS("PR_SetThreadPrivate failed!");
            return nullptr;
          }
        }

        threadLocalInfo = newInfo.release();
      }

      if (threadLocalInfo->mActor) {
        return threadLocalInfo->mActor;
      }

      RefPtr<BackgroundStarterChild> starter;
      {
        auto lock = mStarter.Lock();
        starter = *lock;
      }
      if (!starter) {
        CRASH_IN_CHILD_PROCESS("No BackgroundStarterChild");
        return nullptr;
      }

      Endpoint<PBackgroundParent> parent;
      Endpoint<PBackgroundChild> child;
      nsresult rv;
      rv = PBackground::CreateEndpoints(starter->mOtherProcInfo,
                                        EndpointProcInfo::Current(), &parent,
                                        &child);
      if (NS_FAILED(rv)) {
        NS_WARNING("Failed to create top level actor!");
        return nullptr;
      }

      RefPtr strongActor = MakeRefPtr<ChildImpl>();
      if (!child.Bind(strongActor)) {
        CRASH_IN_CHILD_PROCESS("Failed to bind ChildImpl!");
        return nullptr;
      }
      strongActor->SetActorAlive();
      threadLocalInfo->mActor = strongActor.forget();

      // Dispatch to the background task queue to create the relevant actor in
      // the remote process.
      starter->mTaskQueue->Dispatch(NS_NewRunnableFunction(
          "PBackground GetOrCreateForCurrentThread",
          [starter, endpoint = std::move(parent)]() mutable {
            if (!starter->SendInitBackground(std::move(endpoint))) {
              NS_WARNING("Failed to create toplevel actor");
            }
          }));
      return threadLocalInfo->mActor;
    }

   private:
    static void CloseStarter(BackgroundStarterChild* aStarter) {
      aStarter->mTaskQueue->Dispatch(NS_NewRunnableFunction(
          "PBackgroundStarterChild Close",
          [starter = RefPtr{aStarter}] { starter->Close(); }));
    }

    // This is only modified on the main thread. It is the thread-local index
    // that we use to store the BackgroundChild for each thread.
    unsigned int mThreadLocalIndex = kBadThreadLocalIndex;

    // On the main thread, we store TLS in this global instead of in
    // mThreadLocalIndex. That way, cooperative main threads all share the same
    // thread info.
    ThreadLocalInfo* mMainThreadInfo = nullptr;

    // The starter which will be used to launch PBackground instances of this
    // type. Only modified on the main thread, but may be read by any thread
    // wanting to start background actors.
    StaticDataMutex<StaticRefPtr<BackgroundStarterChild>> mStarter{"mStarter"};
  };

  // For PBackground between parent and content process.
  static ThreadInfoWrapper sParentAndContentProcessThreadInfo;

  // This is only modified on the main thread. It prevents us from trying to
  // create the background thread after application shutdown has started.
  static bool sShutdownHasStarted;

#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
  nsISerialEventTarget* mOwningEventTarget;
#endif

#ifdef DEBUG
  bool mActorWasAlive;
  bool mActorDestroyed;
#endif

 public:
  static void Shutdown();

  void AssertIsOnOwningThread() {
    THREADSAFETY_ASSERT(mOwningEventTarget);

#ifdef RELEASE_OR_BETA
    DebugOnly<bool> current;
#else
    bool current;
#endif
    THREADSAFETY_ASSERT(
        NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(&current)));
    THREADSAFETY_ASSERT(current);
  }

  void AssertActorDestroyed() {
    MOZ_ASSERT(mActorDestroyed, "ChildImpl::ActorDestroy not called in time");
  }

  explicit ChildImpl()
#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
      : mOwningEventTarget(GetCurrentSerialEventTarget())
#endif
#ifdef DEBUG
        ,
        mActorWasAlive(false),
        mActorDestroyed(false)
#endif
  {
    AssertIsOnOwningThread();
  }

  void SetActorAlive() {
    AssertIsOnOwningThread();
    MOZ_ASSERT(!mActorWasAlive);
    MOZ_ASSERT(!mActorDestroyed);

#ifdef DEBUG
    mActorWasAlive = true;
#endif
  }

  // This type is threadsafe refcounted as actors managed by it may be destroyed
  // after the thread it is bound to dies, and hold a reference to this object.
  //
  // It is _not_ safe to use this type or any methods on it from off of the
  // thread it was created for.
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChildImpl, override)

 private:
  // Forwarded from BackgroundChild.
  static void Startup();

  // Forwarded from BackgroundChild.
  static PBackgroundChild* GetForCurrentThread();

  // Forwarded from BackgroundChild.
  static PBackgroundChild* GetOrCreateForCurrentThread();

  static void CloseForCurrentThread();

  // Forwarded from BackgroundChildImpl.
  static BackgroundChildImpl::ThreadLocal* GetThreadLocalForCurrentThread();

  // Forwarded from BackgroundChild.
  static void InitContentStarter(mozilla::dom::ContentChild* aContent);

  static void ThreadLocalDestructor(void* aThreadLocal);

  // This class is reference counted.
  ~ChildImpl() { MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed); }

  // Only called by IPDL.
  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
};

// -----------------------------------------------------------------------------
// ParentImpl Helper Declarations
// -----------------------------------------------------------------------------

class ParentImpl::ShutdownObserver final : public nsIObserver {
 public:
  ShutdownObserver() { AssertIsOnMainThread(); }

  NS_DECL_ISUPPORTS
  NS_DECL_NSIOBSERVER

 private:
  ~ShutdownObserver() { AssertIsOnMainThread(); }
};

// -----------------------------------------------------------------------------
// ChildImpl Helper Declarations
// -----------------------------------------------------------------------------

class ChildImpl::ShutdownObserver final : public nsIObserver {
 public:
  ShutdownObserver() { AssertIsOnMainThread(); }

  NS_DECL_ISUPPORTS
  NS_DECL_NSIOBSERVER

 private:
  ~ShutdownObserver() { AssertIsOnMainThread(); }
};

}  // namespace

namespace mozilla {
namespace ipc {

bool IsOnBackgroundThread() { return ParentImpl::IsOnBackgroundThread(); }

#ifdef DEBUG

void AssertIsOnBackgroundThread() { ParentImpl::AssertIsOnBackgroundThread(); }

#endif  // DEBUG

}  // namespace ipc
}  // namespace mozilla

// -----------------------------------------------------------------------------
// BackgroundParent Public Methods
// -----------------------------------------------------------------------------

// static
already_AddRefed<nsISerialEventTarget> BackgroundParent::GetBackgroundThread() {
  return ParentImpl::GetBackgroundThread();
}

// static
bool BackgroundParent::IsOtherProcessActor(
    PBackgroundParent* aBackgroundActor) {
  return ParentImpl::IsOtherProcessActor(aBackgroundActor);
}

// static
ThreadsafeContentParentHandle* BackgroundParent::GetContentParentHandle(
    PBackgroundParent* aBackgroundActor) {
  return ParentImpl::GetContentParentHandle(aBackgroundActor);
}

// static
uint64_t BackgroundParent::GetChildID(PBackgroundParent* aBackgroundActor) {
  return ParentImpl::GetChildID(aBackgroundActor);
}

// static
nsCString BackgroundParent::GetRemoteType(PBackgroundParent* aBackgroundActor) {
  ThreadsafeContentParentHandle* handle =
      GetContentParentHandle(aBackgroundActor);
  return handle ? handle->GetRemoteType() : NOT_REMOTE_TYPE;
}

// static
bool BackgroundParent::ValidatePrincipal(
    PBackgroundParent* aBackgroundActor, nsIPrincipal* aPrincipal,
    const EnumSet<ValidatePrincipalOptions>& aOptions) {
  return ValidatePrincipalCouldPotentiallyBeLoadedBy(
      aPrincipal, GetRemoteType(aBackgroundActor), aOptions);
}

// static
bool BackgroundParent::ValidatePrincipalInfo(
    PBackgroundParent* aBackgroundActor, const PrincipalInfo& aPrincipal,
    const EnumSet<ValidatePrincipalOptions>& aOptions) {
  auto result = PrincipalInfoToPrincipal(aPrincipal);
  if (NS_WARN_IF(result.isErr())) {
    return false;
  }

  return ValidatePrincipal(aBackgroundActor, result.inspect(), aOptions);
}

// static
void BackgroundParent::KillHardAsync(PBackgroundParent* aBackgroundActor,
                                     const nsACString& aReason) {
  ParentImpl::KillHardAsync(aBackgroundActor, aReason);
}

// static
bool BackgroundParent::AllocStarter(
    ContentParent* aContent, Endpoint<PBackgroundStarterParent>&& aEndpoint) {
  return ParentImpl::AllocStarter(aContent, std::move(aEndpoint));
}

// -----------------------------------------------------------------------------
// BackgroundChild Public Methods
// -----------------------------------------------------------------------------

// static
void BackgroundChild::Startup() { ChildImpl::Startup(); }

// static
PBackgroundChild* BackgroundChild::GetForCurrentThread() {
  return ChildImpl::GetForCurrentThread();
}

// static
PBackgroundChild* BackgroundChild::GetOrCreateForCurrentThread() {
  return ChildImpl::GetOrCreateForCurrentThread();
}

// static
void BackgroundChild::CloseForCurrentThread() {
  ChildImpl::CloseForCurrentThread();
}

// static
void BackgroundChild::InitContentStarter(ContentChild* aContent) {
  ChildImpl::InitContentStarter(aContent);
}

// static
bool BackgroundChild::ValidatePrincipal(
    nsIPrincipal* aPrincipal,
    const EnumSet<ValidatePrincipalOptions>& aOptions) {
  return ValidatePrincipalCouldPotentiallyBeLoadedBy(
      aPrincipal, dom::CurrentRemoteType(), aOptions);
}

// static
bool BackgroundChild::ValidatePrincipalInfo(
    const PrincipalInfo& aPrincipalInfo,
    const EnumSet<ValidatePrincipalOptions>& aOptions) {
  auto result = PrincipalInfoToPrincipal(aPrincipalInfo);
  if (NS_WARN_IF(result.isErr())) {
    return false;
  }

  return ValidatePrincipal(result.inspect(), aOptions);
}

// -----------------------------------------------------------------------------
// BackgroundChildImpl Public Methods
// -----------------------------------------------------------------------------

// static
BackgroundChildImpl::ThreadLocal*
BackgroundChildImpl::GetThreadLocalForCurrentThread() {
  return ChildImpl::GetThreadLocalForCurrentThread();
}

// -----------------------------------------------------------------------------
// ParentImpl Static Members
// -----------------------------------------------------------------------------

StaticRefPtr<nsIThread> ParentImpl::sBackgroundThread;

nsTArray<IToplevelProtocol*>* ParentImpl::sLiveActorsForBackgroundThread;

StaticRefPtr<nsITimer> ParentImpl::sShutdownTimer;

Atomic<uint64_t> ParentImpl::sLiveActorCount;

bool ParentImpl::sShutdownObserverRegistered = false;

bool ParentImpl::sShutdownHasStarted = false;

// -----------------------------------------------------------------------------
// ChildImpl Static Members
// -----------------------------------------------------------------------------

MOZ_GLOBINIT ChildImpl::ThreadInfoWrapper
    ChildImpl::sParentAndContentProcessThreadInfo;

bool ChildImpl::sShutdownHasStarted = false;

// -----------------------------------------------------------------------------
// ParentImpl Implementation
// -----------------------------------------------------------------------------

// static
bool ParentImpl::IsOtherProcessActor(PBackgroundParent* aBackgroundActor) {
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(aBackgroundActor);

  return static_cast<ParentImpl*>(aBackgroundActor)->mIsOtherProcessActor;
}

// static
ThreadsafeContentParentHandle* ParentImpl::GetContentParentHandle(
    PBackgroundParent* aBackgroundActor) {
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(aBackgroundActor);

  return static_cast<ParentImpl*>(aBackgroundActor)->mContent.get();
}

// static
uint64_t ParentImpl::GetChildID(PBackgroundParent* aBackgroundActor) {
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(aBackgroundActor);

  auto actor = static_cast<ParentImpl*>(aBackgroundActor);
  if (actor->mContent) {
    return actor->mContent->ChildID();
  }

  return 0;
}

// static
void ParentImpl::KillHardAsync(PBackgroundParent* aBackgroundActor,
                               const nsACString& aReason) {
  AssertIsInMainProcess();
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(aBackgroundActor);
  MOZ_ASSERT(BackgroundParent::IsOtherProcessActor(aBackgroundActor));

  RefPtr<ThreadsafeContentParentHandle> handle =
      GetContentParentHandle(aBackgroundActor);
  MOZ_ASSERT(handle);

  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
      NS_NewRunnableFunction(
          "ParentImpl::KillHardAsync",
          [handle = std::move(handle), reason = nsCString{aReason}]() {
            mozilla::AssertIsOnMainThread();

            if (RefPtr<ContentParent> contentParent =
                    handle->GetContentParent()) {
              contentParent->KillHard(reason.get());
            }
          }),
      NS_DISPATCH_NORMAL));

  // After we've scheduled killing of the remote process, also ensure we induce
  // a connection error in the IPC channel to immediately stop all IPC
  // communication on this channel.
  if (aBackgroundActor->CanSend()) {
    aBackgroundActor->GetIPCChannel()->InduceConnectionError();
  }
}

// static
bool ParentImpl::AllocStarter(ContentParent* aContent,
                              Endpoint<PBackgroundStarterParent>&& aEndpoint,
                              bool aCrossProcess) {
  AssertIsInMainProcess();
  AssertIsOnMainThread();

  MOZ_ASSERT(aEndpoint.IsValid());

  if (!sBackgroundThread && !CreateBackgroundThread()) {
    NS_WARNING("Failed to create background thread!");
    return false;
  }

  sLiveActorCount++;

  RefPtr actor = MakeRefPtr<BackgroundStarterParent>(
      aContent ? aContent->ThreadsafeHandle() : nullptr, aCrossProcess);

  if (NS_FAILED(sBackgroundThread->Dispatch(NS_NewRunnableFunction(
          "BackgroundStarterParent::ConnectActorRunnable",
          [actor = std::move(actor), endpoint = std::move(aEndpoint),
           liveActorArray = sLiveActorsForBackgroundThread]() mutable {
            MOZ_ASSERT(endpoint.IsValid());
            MOZ_ALWAYS_TRUE(endpoint.Bind(actor));
            actor->SetLiveActorArray(liveActorArray);
          })))) {
    NS_WARNING("Failed to dispatch connect runnable!");

    MOZ_ASSERT(sLiveActorCount);
    sLiveActorCount--;
  }

  return true;
}

// static
bool ParentImpl::CreateBackgroundThread() {
  AssertIsInMainProcess();
  AssertIsOnMainThread();
  MOZ_ASSERT(!sBackgroundThread);
  MOZ_ASSERT(!sLiveActorsForBackgroundThread);

  if (sShutdownHasStarted) {
    NS_WARNING(
        "Trying to create background thread after shutdown has "
        "already begun!");
    return false;
  }

  nsCOMPtr<nsITimer> newShutdownTimer;

  if (!sShutdownTimer) {
    newShutdownTimer = NS_NewTimer();
    if (!newShutdownTimer) {
      return false;
    }
  }

  if (!sShutdownObserverRegistered) {
    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
    if (NS_WARN_IF(!obs)) {
      return false;
    }

    nsCOMPtr<nsIObserver> observer = new ShutdownObserver();

    nsresult rv = obs->AddObserver(
        observer, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return false;
    }

    sShutdownObserverRegistered = true;
  }

  nsCOMPtr<nsIThread> thread;
  if (NS_FAILED(NS_NewNamedThread(
          "IPDL Background", getter_AddRefs(thread),
          NS_NewRunnableFunction(
              "Background::ParentImpl::CreateBackgroundThreadRunnable", []() {
                MOZ_ASSERT(sTLSIsOnBackgroundThread.initialized());
                sTLSIsOnBackgroundThread.set(true);
              })))) {
    NS_WARNING("NS_NewNamedThread failed!");
    return false;
  }

  sBackgroundThread = thread.forget();

  sLiveActorsForBackgroundThread = new nsTArray<IToplevelProtocol*>(1);

  if (!sShutdownTimer) {
    MOZ_ASSERT(newShutdownTimer);
    sShutdownTimer = newShutdownTimer;
  }

  return true;
}

// static
void ParentImpl::ShutdownBackgroundThread() {
  AssertIsInMainProcess();
  AssertIsOnMainThread();
  MOZ_ASSERT(sShutdownHasStarted);
  MOZ_ASSERT_IF(!sBackgroundThread, !sLiveActorCount);
  MOZ_ASSERT_IF(sBackgroundThread, sShutdownTimer);

  nsCOMPtr<nsITimer> shutdownTimer = sShutdownTimer.get();
  sShutdownTimer = nullptr;

  if (sBackgroundThread) {
    nsCOMPtr<nsIThread> thread = sBackgroundThread.get();
    sBackgroundThread = nullptr;

    UniquePtr<nsTArray<IToplevelProtocol*>> liveActors(
        sLiveActorsForBackgroundThread);
    sLiveActorsForBackgroundThread = nullptr;

    MOZ_ASSERT_IF(!sShutdownHasStarted, !sLiveActorCount);

    if (sLiveActorCount) {
      // We need to spin the event loop while we wait for all the actors to be
      // cleaned up. We also set a timeout to force-kill any hanging actors.
      TimerCallbackClosure closure(thread, liveActors.get());

      MOZ_ALWAYS_SUCCEEDS(shutdownTimer->InitWithNamedFuncCallback(
          &ShutdownTimerCallback, &closure, kShutdownTimerDelayMS,
          nsITimer::TYPE_ONE_SHOT, "ParentImpl::ShutdownTimerCallback"_ns));

      SpinEventLoopUntil("ParentImpl::ShutdownBackgroundThread"_ns,
                         [&]() { return !sLiveActorCount; });

      MOZ_ASSERT(liveActors->IsEmpty());

      MOZ_ALWAYS_SUCCEEDS(shutdownTimer->Cancel());
    }

    // Shutdown will process all remaining events.
    MOZ_ALWAYS_SUCCEEDS(thread->Shutdown());
  }
}

// static
void ParentImpl::ShutdownTimerCallback(nsITimer* aTimer, void* aClosure) {
  AssertIsInMainProcess();
  AssertIsOnMainThread();
  MOZ_ASSERT(sShutdownHasStarted);
  MOZ_ASSERT(sLiveActorCount);

  auto closure = static_cast<TimerCallbackClosure*>(aClosure);
  MOZ_ASSERT(closure);

  // Don't let the stack unwind until the ForceCloseBackgroundActorsRunnable has
  // finished.
  sLiveActorCount++;

  InvokeAsync(
      closure->mThread, __func__,
      [liveActors = closure->mLiveActors]() {
        MOZ_ASSERT(liveActors);

        if (!liveActors->IsEmpty()) {
          // Copy the array since calling Close() could mutate the
          // actual array.
          nsTArray<IToplevelProtocol*> actorsToClose(liveActors->Clone());
          for (IToplevelProtocol* actor : actorsToClose) {
            actor->Close();
          }
        }
        return GenericPromise::CreateAndResolve(true, __func__);
      })
      ->Then(GetCurrentSerialEventTarget(), __func__, []() {
        MOZ_ASSERT(sLiveActorCount);
        sLiveActorCount--;
      });
}

void ParentImpl::Destroy() {
  // May be called on any thread!

  AssertIsInMainProcess();

  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
      NewNonOwningRunnableMethod("ParentImpl::MainThreadActorDestroy", this,
                                 &ParentImpl::MainThreadActorDestroy)));
}

void ParentImpl::MainThreadActorDestroy() {
  AssertIsInMainProcess();
  AssertIsOnMainThread();
  MOZ_ASSERT_IF(!mIsOtherProcessActor, !mContent);

  MOZ_ASSERT(sLiveActorCount);
  sLiveActorCount--;

  // This may be the last reference!
  Release();
}

void ParentImpl::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsInMainProcess();
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(!mActorDestroyed);
  MOZ_ASSERT_IF(mIsOtherProcessActor, mLiveActorArray);

  BackgroundParentImpl::ActorDestroy(aWhy);

  mActorDestroyed = true;

  if (mLiveActorArray) {
    MOZ_ALWAYS_TRUE(mLiveActorArray->RemoveElement(this));
    mLiveActorArray = nullptr;
  }

  // This is tricky. We should be able to call Destroy() here directly because
  // we're not going to touch 'this' or our MessageChannel any longer on this
  // thread. Destroy() dispatches the MainThreadActorDestroy runnable and when
  // it runs it will destroy 'this' and our associated MessageChannel. However,
  // IPDL is about to call MessageChannel::Clear() on this thread! To avoid
  // racing with the main thread we must ensure that the MessageChannel lives
  // long enough to be cleared in this call stack.

  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(NewNonOwningRunnableMethod(
      "ParentImpl::Destroy", this, &ParentImpl::Destroy)));
}

NS_IMPL_ISUPPORTS(ParentImpl::ShutdownObserver, nsIObserver)

NS_IMETHODIMP
ParentImpl::ShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
                                      const char16_t* aData) {
  AssertIsInMainProcess();
  AssertIsOnMainThread();
  MOZ_ASSERT(!sShutdownHasStarted);
  MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID));

  sShutdownHasStarted = true;

  // Do this first before calling (and spinning the event loop in)
  // ShutdownBackgroundThread().
  ChildImpl::Shutdown();

  ShutdownBackgroundThread();

  return NS_OK;
}

BackgroundStarterParent::BackgroundStarterParent(
    ThreadsafeContentParentHandle* aContent, bool aCrossProcess)
    : mCrossProcess(aCrossProcess), mContent(aContent) {
  AssertIsOnMainThread();
  AssertIsInMainProcess();
  MOZ_ASSERT_IF(!mCrossProcess, !mContent);
  MOZ_ASSERT_IF(!mCrossProcess, XRE_IsParentProcess());
}

void BackgroundStarterParent::SetLiveActorArray(
    nsTArray<IToplevelProtocol*>* aLiveActorArray) {
  AssertIsInMainProcess();
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(aLiveActorArray);
  MOZ_ASSERT(!aLiveActorArray->Contains(this));
  MOZ_ASSERT(!mLiveActorArray);
  MOZ_ASSERT_IF(!mCrossProcess, OtherPid() == base::GetCurrentProcId());

  mLiveActorArray = aLiveActorArray;
  mLiveActorArray->AppendElement(this);
}

IPCResult BackgroundStarterParent::RecvInitBackground(
    Endpoint<PBackgroundParent>&& aEndpoint) {
  AssertIsOnBackgroundThread();

  if (!aEndpoint.IsValid()) {
    return IPC_FAIL(this,
                    "Cannot initialize PBackground with invalid endpoint");
  }

  ParentImpl* actor = new ParentImpl(mContent, mCrossProcess);

  // Take a reference on this thread. If Open() fails then we will release this
  // reference in Destroy.
  NS_ADDREF(actor);

  ParentImpl::sLiveActorCount++;

  if (!aEndpoint.Bind(actor)) {
    actor->Destroy();
    return IPC_OK();
  }

  if (mCrossProcess) {
    actor->SetLiveActorArray(mLiveActorArray);
  }
  return IPC_OK();
}

void BackgroundStarterParent::ActorDestroy(ActorDestroyReason aReason) {
  AssertIsOnBackgroundThread();

  if (mLiveActorArray) {
    MOZ_ALWAYS_TRUE(mLiveActorArray->RemoveElement(this));
    mLiveActorArray = nullptr;
  }

  // Make sure to decrement `sLiveActorCount` on the main thread.
  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
      NS_NewRunnableFunction("BackgroundStarterParent::MainThreadDestroy",
                             [] { ParentImpl::sLiveActorCount--; })));
}

// -----------------------------------------------------------------------------
// ChildImpl Implementation
// -----------------------------------------------------------------------------

// static
void ChildImpl::Startup() {
  // This happens on the main thread but before XPCOM has started so we can't
  // assert that we're being called on the main thread here.

  sParentAndContentProcessThreadInfo.Startup();

  sTLSIsOnBackgroundThread.infallibleInit();

  nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
  MOZ_RELEASE_ASSERT(observerService);

  nsCOMPtr<nsIObserver> observer = new ShutdownObserver();

  nsresult rv = observerService->AddObserver(
      observer, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false);
  MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));

  // Initialize a starter actor to allow starting PBackground within the parent
  // process.
  if (XRE_IsParentProcess()) {
    Endpoint<PBackgroundStarterParent> parent;
    Endpoint<PBackgroundStarterChild> child;
    MOZ_ALWAYS_SUCCEEDS(PBackgroundStarter::CreateEndpoints(
        EndpointProcInfo::Current(), EndpointProcInfo::Current(), &parent,
        &child));

    MOZ_ALWAYS_TRUE(ParentImpl::AllocStarter(nullptr, std::move(parent),
                                             /* aCrossProcess */ false));
    sParentAndContentProcessThreadInfo.InitStarter(std::move(child));
  }
}

// static
void ChildImpl::Shutdown() {
  AssertIsOnMainThread();

  sParentAndContentProcessThreadInfo.Shutdown();

  sShutdownHasStarted = true;
}

// static
PBackgroundChild* ChildImpl::GetForCurrentThread() {
  MOZ_ASSERT(sParentAndContentProcessThreadInfo.mThreadLocalIndex !=
             kBadThreadLocalIndex);

  auto threadLocalInfo =
      NS_IsMainThread()
          ? sParentAndContentProcessThreadInfo.mMainThreadInfo
          : static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(
                sParentAndContentProcessThreadInfo.mThreadLocalIndex));

  if (!threadLocalInfo) {
    return nullptr;
  }

  return threadLocalInfo->mActor;
}

/* static */
PBackgroundChild* ChildImpl::GetOrCreateForCurrentThread() {
  return sParentAndContentProcessThreadInfo.GetOrCreateForCurrentThread();
}

// static
void ChildImpl::CloseForCurrentThread() {
  MOZ_ASSERT(!NS_IsMainThread(),
             "PBackground for the main thread should be shut down via "
             "ChildImpl::Shutdown().");

  sParentAndContentProcessThreadInfo.CloseForCurrentThread();
}

// static
BackgroundChildImpl::ThreadLocal* ChildImpl::GetThreadLocalForCurrentThread() {
  MOZ_ASSERT(sParentAndContentProcessThreadInfo.mThreadLocalIndex !=
                 kBadThreadLocalIndex,
             "BackgroundChild::Startup() was never called!");

  auto threadLocalInfo =
      NS_IsMainThread()
          ? sParentAndContentProcessThreadInfo.mMainThreadInfo
          : static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(
                sParentAndContentProcessThreadInfo.mThreadLocalIndex));

  if (!threadLocalInfo) {
    return nullptr;
  }

  if (!threadLocalInfo->mConsumerThreadLocal) {
    threadLocalInfo->mConsumerThreadLocal =
        MakeUnique<BackgroundChildImpl::ThreadLocal>();
  }

  return threadLocalInfo->mConsumerThreadLocal.get();
}

// static
void ChildImpl::InitContentStarter(mozilla::dom::ContentChild* aContent) {
  sParentAndContentProcessThreadInfo.InitStarter(aContent);
}

// static
void ChildImpl::ThreadLocalDestructor(void* aThreadLocal) {
  auto threadLocalInfo = static_cast<ThreadLocalInfo*>(aThreadLocal);

  if (threadLocalInfo) {
    MOZ_ASSERT(threadLocalInfo->mClosed);

    if (threadLocalInfo->mActor) {
      threadLocalInfo->mActor->Close();
      threadLocalInfo->mActor->AssertActorDestroyed();
    }

    delete threadLocalInfo;
  }
}

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

#ifdef DEBUG
  MOZ_ASSERT(!mActorDestroyed);
  mActorDestroyed = true;
#endif

  BackgroundChildImpl::ActorDestroy(aWhy);
}

NS_IMPL_ISUPPORTS(ChildImpl::ShutdownObserver, nsIObserver)

NS_IMETHODIMP
ChildImpl::ShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
                                     const char16_t* aData) {
  AssertIsOnMainThread();
  MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID));

  ChildImpl::Shutdown();

  return NS_OK;
}
