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

#include "mozilla/Preferences.h"
#include "mozilla/GeckoArgs.h"
#include "mozilla/dom/RemoteType.h"
#include "mozilla/ipc/GeckoChildProcessHost.h"
#include "nsPrintfCString.h"

#include "XPCSelfHostedShmem.h"

namespace mozilla {
namespace ipc {

SharedPreferenceSerializer::SharedPreferenceSerializer() {
  MOZ_COUNT_CTOR(SharedPreferenceSerializer);
}

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

SharedPreferenceSerializer::SharedPreferenceSerializer(
    SharedPreferenceSerializer&& aOther)
    : mPrefMapHandle(std::move(aOther.mPrefMapHandle)),
      mPrefsHandle(std::move(aOther.mPrefsHandle)) {
  MOZ_COUNT_CTOR(SharedPreferenceSerializer);
}

bool SharedPreferenceSerializer::SerializeToSharedMemory(
    const GeckoProcessType aDestinationProcessType,
    const nsACString& aDestinationRemoteType) {
  mPrefMapHandle = Preferences::EnsureSnapshot();

  bool destIsWebContent =
      aDestinationProcessType == GeckoProcessType_Content &&
      (StringBeginsWith(aDestinationRemoteType, WEB_REMOTE_TYPE) ||
       StringBeginsWith(aDestinationRemoteType, PREALLOC_REMOTE_TYPE));

  // Serialize the early prefs.
  nsAutoCStringN<1024> prefs;
  Preferences::SerializePreferences(prefs, destIsWebContent);
  auto prefsLength = prefs.Length();

  // Set up the shared memory.
  auto handle = shared_memory::Create(prefsLength);
  if (!handle) {
    NS_ERROR("failed to create shared memory in the parent");
    return false;
  }
  auto mapping = handle.Map();
  if (!mapping) {
    NS_ERROR("failed to map shared memory in the parent");
    return false;
  }

  // Copy the serialized prefs into the shared memory.
  memcpy(mapping.DataAs<char>(), prefs.get(), prefsLength);

  mPrefsHandle = std::move(handle).ToReadOnly();
  return true;
}

void SharedPreferenceSerializer::AddSharedPrefCmdLineArgs(
    mozilla::ipc::GeckoChildProcessHost& procHost,
    geckoargs::ChildProcessArgs& aExtraOpts) const {
  auto prefsHandle = GetPrefsHandle().Clone();
  MOZ_RELEASE_ASSERT(prefsHandle, "failed to clone prefs handle");
  auto prefMapHandle = GetPrefMapHandle().Clone();
  MOZ_RELEASE_ASSERT(prefMapHandle, "failed to clone pref map handle");

  // Pass the handles via command line flags.
  geckoargs::sPrefsHandle.Put(std::move(prefsHandle), aExtraOpts);
  geckoargs::sPrefMapHandle.Put(std::move(prefMapHandle), aExtraOpts);
}

SharedPreferenceDeserializer::SharedPreferenceDeserializer() {
  MOZ_COUNT_CTOR(SharedPreferenceDeserializer);
}

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

bool SharedPreferenceDeserializer::DeserializeFromSharedMemory(
    ReadOnlySharedMemoryHandle&& aPrefsHandle,
    ReadOnlySharedMemoryHandle&& aPrefMapHandle) {
  if (!aPrefsHandle || !aPrefMapHandle) {
    return false;
  }

  mPrefMapHandle = std::move(aPrefMapHandle);

  // Init the shared-memory base preference mapping first, so that only changed
  // preferences wind up in heap memory.
  Preferences::InitSnapshot(mPrefMapHandle);

  // Set up early prefs from the shared memory.
  mShmem = aPrefsHandle.Map();
  if (!mShmem) {
    NS_ERROR("failed to map shared memory in the child");
    return false;
  }
  Preferences::DeserializePreferences(mShmem.DataAs<char>(), mShmem.Size());

  return true;
}

void ExportSharedJSInit(mozilla::ipc::GeckoChildProcessHost& procHost,
                        geckoargs::ChildProcessArgs& aExtraOpts) {
  auto& shmem = xpc::SelfHostedShmem::GetSingleton();
  auto handle = shmem.Handle().Clone();

  // If the file is not found or the content is empty, then we would start the
  // content process without this optimization.
  if (!handle) {
    NS_ERROR("Can't use SelfHosted shared memory handle.");
    return;
  }

  // command line: -jsInitHandle handle
  geckoargs::sJsInitHandle.Put(std::move(handle), aExtraOpts);
}

bool ImportSharedJSInit(ReadOnlySharedMemoryHandle&& aJsInitHandle) {
  // This is an optimization, and as such we can safely recover if the command
  // line argument are not provided.
  if (!aJsInitHandle) {
    return true;
  }

  // Initialize the shared memory with the file handle and size of the content
  // of the self-hosted Xdr.
  auto& shmem = xpc::SelfHostedShmem::GetSingleton();
  if (!shmem.InitFromChild(std::move(aJsInitHandle))) {
    NS_ERROR("failed to open shared memory in the child");
    return false;
  }

  return true;
}

}  // namespace ipc
}  // namespace mozilla
