/* 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 "mozilla/ipc/IOThread.h"
#include "mozilla/ipc/NodeController.h"
#include "mozilla/Preferences.h"

#if defined(XP_WIN)
#  include <objbase.h>
#endif

#if defined(XP_WIN)
#  include "chrome/common/ipc_channel_win.h"
#else
#  if defined(XP_DARWIN)
#    include "chrome/common/ipc_channel_mach.h"
#  endif
#  include "chrome/common/ipc_channel_posix.h"
#endif

namespace mozilla {
namespace ipc {

//
// IOThread
//

/* static */
IOThread* IOThread::sSingleton = nullptr;

IOThread::IOThread(const char* aName) : base::Thread(aName) {
  sSingleton = this;
}

IOThread::~IOThread() {
  MOZ_ASSERT(!IsRunning());
  sSingleton = nullptr;
}

void IOThread::Startup() {
  if (XRE_IsParentProcess()) {
    // Destroyed in IOThread::Shutdown
    auto* thread = new IOThreadParent();
    MOZ_RELEASE_ASSERT(thread == sSingleton);
  }
  MOZ_ASSERT(sSingleton);
}

void IOThread::Shutdown() {
  if (XRE_IsParentProcess()) {
    delete static_cast<IOThreadParent*>(sSingleton);
    MOZ_ASSERT(!sSingleton);
  }
}

void IOThread::StartThread() {
  // Failure to create the IPC I/O thread is unrecoverable.
  // NOTE: This will block if successful for the `Init()` virtual method to have
  // been run.
  if (!StartWithOptions(
          base::Thread::Options{MessageLoop::TYPE_IO, /* stack size */ 0})) {
    MOZ_CRASH("Failed to create IPC I/O Thread");
  }
}

void IOThread::StopThread() {
  // This will block until CleanUp() has been called, and the IPC I/O thread has
  // been joined.
  Stop();
}

//
// IOThreadParent
//

IOThreadParent::IOThreadParent() : IOThread("IPC I/O Parent") {
  MOZ_RELEASE_ASSERT(XRE_IsParentProcess());

  // Select which type of channel will be used for IPC.
  mChannelKind = [] {
#if defined(XP_WIN)
    return &IPC::ChannelWin::sKind;
#else
#  if defined(XP_DARWIN)
    if (Preferences::GetBool("dom.ipc.backend.mach")) {
      return &IPC::ChannelMach::sKind;
    }
#  endif
    return &IPC::ChannelPosix::sKind;
#endif
  }();

  StartThread();
}

IOThreadParent::~IOThreadParent() { StopThread(); }

void IOThreadParent::Init() {
#if defined(XP_WIN)
  // Initializes the COM library on the current thread.
  CoInitialize(nullptr);
#endif

  // Initialize the ports library in the current thread.
  NodeController::InitBrokerProcess(mChannelKind);
}

void IOThreadParent::CleanUp() {
  NodeController::CleanUp();

#if defined(XP_WIN)
  // Closes the COM library on the current thread. CoInitialize must
  // be balanced by a corresponding call to CoUninitialize.
  CoUninitialize();
#endif
}

//
// IOThreadChild
//

IOThreadChild::IOThreadChild(IPC::Channel::ChannelHandle aClientHandle,
                             base::ProcessId aParentPid)
    : IOThread("IPC I/O Child"),
      mClientHandle(std::move(aClientHandle)),
      mParentPid(std::move(aParentPid)) {
  StartThread();
}

IOThreadChild::~IOThreadChild() { StopThread(); }

void IOThreadChild::Init() {
  mInitialPort =
      NodeController::InitChildProcess(std::move(mClientHandle), mParentPid);
}

void IOThreadChild::CleanUp() { NodeController::CleanUp(); }

}  // namespace ipc
}  // namespace mozilla
