/* 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 https://mozilla.org/MPL/2.0/. */

#include "UntrustedModulesProcessor.h"

#include <windows.h>

#include "GMPPlatform.h"
#include "GMPServiceParent.h"
#include "mozilla/CmdLineAndEnvUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/Likely.h"
#include "mozilla/net/SocketProcessChild.h"
#include "mozilla/net/SocketProcessParent.h"
#include "mozilla/ipc/UtilityProcessParent.h"
#include "mozilla/ipc/UtilityProcessChild.h"
#include "mozilla/RDDChild.h"
#include "mozilla/RDDParent.h"
#include "mozilla/RDDProcessManager.h"
#include "mozilla/Services.h"
#include "mozilla/Telemetry.h"
#include "ModuleEvaluator.h"
#include "nsCOMPtr.h"
#include "nsHashKeys.h"
#include "nsIObserverService.h"
#include "nsTHashtable.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "private/prpriv.h"  // For PR_GetThreadID

namespace mozilla {

class MOZ_RAII BackgroundPriorityRegion final {
 public:
  BackgroundPriorityRegion()
      : mIsBackground(
            ::SetThreadPriority(::GetCurrentThread(), THREAD_PRIORITY_IDLE)) {}

  ~BackgroundPriorityRegion() {
    if (!mIsBackground) {
      return;
    }

    Clear(::GetCurrentThread());
  }

  static void Clear(const nsAutoHandle& aThread) {
    if (!aThread) {
      return;
    }

    Clear(aThread.get());
  }

  BackgroundPriorityRegion(const BackgroundPriorityRegion&) = delete;
  BackgroundPriorityRegion(BackgroundPriorityRegion&&) = delete;
  BackgroundPriorityRegion& operator=(const BackgroundPriorityRegion&) = delete;
  BackgroundPriorityRegion& operator=(BackgroundPriorityRegion&&) = delete;

 private:
  static void Clear(HANDLE aThread) {
    DebugOnly<BOOL> ok = ::SetThreadPriority(aThread, THREAD_PRIORITY_NORMAL);
    MOZ_ASSERT(ok);
  }

 private:
  const BOOL mIsBackground;
};

/* static */
bool UntrustedModulesProcessor::IsSupportedProcessType() {
  switch (XRE_GetProcessType()) {
    case GeckoProcessType_Default:
    case GeckoProcessType_Content:
    case GeckoProcessType_Socket:
      return Telemetry::CanRecordReleaseData();
    case GeckoProcessType_RDD:
    case GeckoProcessType_Utility:
    case GeckoProcessType_GMPlugin:
      // For GMPlugin, RDD and Utility process, we check the telemetry settings
      // in RDDChild::Init() / UtilityProcessChild::Init() / GMPChild::Init()
      // running in the browser process because CanRecordReleaseData() always
      // returns false here.
      return true;
    default:
      return false;
  }
}

/* static */
RefPtr<UntrustedModulesProcessor> UntrustedModulesProcessor::Create(
    bool aIsReadyForBackgroundProcessing) {
  if (!IsSupportedProcessType()) {
    return nullptr;
  }

  RefPtr<UntrustedModulesProcessor> result(
      new UntrustedModulesProcessor(aIsReadyForBackgroundProcessing));
  return result.forget();
}

NS_IMPL_ISUPPORTS(UntrustedModulesProcessor, nsIObserver, nsIThreadPoolListener)

static const uint32_t kThreadTimeoutMS = 120000;  // 2 minutes

UntrustedModulesProcessor::UntrustedModulesProcessor(
    bool aIsReadyForBackgroundProcessing)
    : mThread(new LazyIdleThread(kThreadTimeoutMS, "Untrusted Modules",
                                 LazyIdleThread::ManualShutdown)),
      mThreadHandleMutex(
          "mozilla::UntrustedModulesProcessor::mThreadHandleMutex"),
      mUnprocessedMutex(
          "mozilla::UntrustedModulesProcessor::mUnprocessedMutex"),
      mModuleCacheMutex(
          "mozilla::UntrustedModulesProcessor::mModuleCacheMutex"),
      mStatus(aIsReadyForBackgroundProcessing ? Status::Allowed
                                              : Status::StartingUp) {
  AddObservers();
}

void UntrustedModulesProcessor::AddObservers() {
  nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService());
  obsServ->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false);
  obsServ->AddObserver(this, "xpcom-shutdown-threads", false);
  obsServ->AddObserver(this, "unblock-untrusted-modules-thread", false);
  if (XRE_IsContentProcess()) {
    obsServ->AddObserver(this, "content-child-will-shutdown", false);
  }
  mThread->SetListener(this);
}

bool UntrustedModulesProcessor::IsReadyForBackgroundProcessing() const {
  return mStatus == Status::Allowed;
}

void UntrustedModulesProcessor::Disable() {
  // Ensure that mThread cannot run at low priority anymore
  {
    MutexAutoLock lock(mThreadHandleMutex);
    BackgroundPriorityRegion::Clear(mThreadHandle);
  }

  // No more background processing allowed beyond this point
  if (mStatus.exchange(Status::ShuttingDown) != Status::Allowed) {
    return;
  }

  MutexAutoLock lock(mUnprocessedMutex);
  CancelScheduledProcessing(lock);
}

NS_IMETHODIMP UntrustedModulesProcessor::Observe(nsISupports* aSubject,
                                                 const char* aTopic,
                                                 const char16_t* aData) {
  if (!strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) ||
      !strcmp(aTopic, "content-child-will-shutdown")) {
    Disable();
    return NS_OK;
  }

  if (!strcmp(aTopic, "xpcom-shutdown-threads")) {
    Disable();
    mThread->Shutdown();

    RemoveObservers();

    mThread = nullptr;
    return NS_OK;
  }

  if (!strcmp(aTopic, "unblock-untrusted-modules-thread")) {
    nsCOMPtr<nsIObserverService> obs(services::GetObserverService());
    obs->RemoveObserver(this, "unblock-untrusted-modules-thread");

    mStatus.compareExchange(Status::StartingUp, Status::Allowed);

    if (!IsReadyForBackgroundProcessing()) {
      // If we're shutting down, stop here.
      return NS_OK;
    }

    if (XRE_IsParentProcess()) {
      // Propagate notification to child processes
      nsTArray<dom::ContentParent*> contentProcesses;
      dom::ContentParent::GetAll(contentProcesses);
      for (auto* proc : contentProcesses) {
        (void)proc->SendUnblockUntrustedModulesThread();
      }
      if (RefPtr<net::SocketProcessParent> proc =
              net::SocketProcessParent::GetSingleton()) {
        (void)proc->SendUnblockUntrustedModulesThread();
      }
      if (auto* rddMgr = RDDProcessManager::Get()) {
        if (auto* proc = rddMgr->GetRDDChild()) {
          (void)proc->SendUnblockUntrustedModulesThread();
        }
      }
      if (RefPtr<gmp::GeckoMediaPluginServiceParent> gmps =
              gmp::GeckoMediaPluginServiceParent::GetSingleton()) {
        gmps->SendUnblockUntrustedModulesThread();
      }
    }

    return NS_OK;
  }

  MOZ_ASSERT_UNREACHABLE("Not reachable");

  return NS_OK;
}

NS_IMETHODIMP UntrustedModulesProcessor::OnThreadCreated() {
  // Whenever a backing lazy thread is created, record a thread handle to it.
  HANDLE threadHandle;
  if (!::DuplicateHandle(
          ::GetCurrentProcess(), ::GetCurrentThread(), ::GetCurrentProcess(),
          &threadHandle,
          THREAD_QUERY_LIMITED_INFORMATION | THREAD_SET_LIMITED_INFORMATION,
          FALSE, 0)) {
    MOZ_ASSERT_UNREACHABLE("DuplicateHandle failed on GetCurrentThread()?");
    threadHandle = nullptr;
  }
  MutexAutoLock lock(mThreadHandleMutex);
  mThreadHandle.own(threadHandle);
  return NS_OK;
}

NS_IMETHODIMP UntrustedModulesProcessor::OnThreadShuttingDown() {
  // When a lazy thread shuts down, clean up the thread handle reference unless
  // it's already been replaced.
  MutexAutoLock lock(mThreadHandleMutex);
  if (mThreadHandle && ::GetCurrentThreadId() == ::GetThreadId(mThreadHandle)) {
    mThreadHandle.reset();
  }
  return NS_OK;
}

void UntrustedModulesProcessor::RemoveObservers() {
  nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService());
  obsServ->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
  obsServ->RemoveObserver(this, "xpcom-shutdown-threads");
  obsServ->RemoveObserver(this, "unblock-untrusted-modules-thread");
  if (XRE_IsContentProcess()) {
    obsServ->RemoveObserver(this, "content-child-will-shutdown");
  }
  mThread->SetListener(nullptr);
}

void UntrustedModulesProcessor::ScheduleNonEmptyQueueProcessing(
    const MutexAutoLock& aProofOfLock) {
  // In case something tried to load a DLL during shutdown
  if (!mThread) {
    return;
  }

#if defined(ENABLE_TESTS)
  // Don't bother scheduling background processing in short-lived xpcshell
  // processes; it makes the test suites take too long.
  if (MOZ_UNLIKELY(mozilla::EnvHasValue("XPCSHELL_TEST_PROFILE_DIR"))) {
    return;
  }
#endif  // defined(ENABLE_TESTS)

  if (mIdleRunnable) {
    return;
  }

  if (!IsReadyForBackgroundProcessing()) {
    return;
  }

  // Schedule a runnable to trigger background processing once the main thread
  // has gone idle. We do it this way to ensure that we don't start doing a
  // bunch of processing during periods of heavy main thread activity.
  nsCOMPtr<nsIRunnable> idleRunnable(NewCancelableRunnableMethod(
      "UntrustedModulesProcessor::DispatchBackgroundProcessing", this,
      &UntrustedModulesProcessor::DispatchBackgroundProcessing));

  if (NS_FAILED(NS_DispatchToMainThreadQueue(do_AddRef(idleRunnable),
                                             EventQueuePriority::Idle))) {
    return;
  }

  mIdleRunnable = std::move(idleRunnable);
}

void UntrustedModulesProcessor::CancelScheduledProcessing(
    const MutexAutoLock& aProofOfLock) {
  if (!mIdleRunnable) {
    return;
  }

  nsCOMPtr<nsICancelableRunnable> cancelable(do_QueryInterface(mIdleRunnable));
  if (cancelable) {
    // Stop the pending idle runnable from doing anything
    cancelable->Cancel();
  }

  mIdleRunnable = nullptr;
}

void UntrustedModulesProcessor::DispatchBackgroundProcessing() {
  MOZ_ASSERT(NS_IsMainThread());

  if (!IsReadyForBackgroundProcessing()) {
    return;
  }

  nsCOMPtr<nsIRunnable> runnable(NewRunnableMethod(
      "UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue", this,
      &UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue));

  mThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
}

void UntrustedModulesProcessor::Enqueue(
    glue::EnhancedModuleLoadInfo&& aModLoadInfo) {
  if (mStatus == Status::ShuttingDown) {
    return;
  }

  {
    MutexAutoLock lock(mThreadHandleMutex);
    DWORD bgThreadId = ::GetThreadId(mThreadHandle);
    if (aModLoadInfo.mNtLoadInfo.mThreadId == bgThreadId) {
      // Exclude loads that were caused by our own background thread
      return;
    }
  }

  MutexAutoLock lock(mUnprocessedMutex);

  mUnprocessedModuleLoads.insertBack(
      new UnprocessedModuleLoadInfoContainer(std::move(aModLoadInfo)));

  ScheduleNonEmptyQueueProcessing(lock);
}

void UntrustedModulesProcessor::Enqueue(ModuleLoadInfoVec&& aEvents) {
  if (mStatus == Status::ShuttingDown) {
    return;
  }

  // We do not need to attempt to exclude our background thread in this case
  // because |aEvents| was accumulated prior to |mThread|'s existence.

  MutexAutoLock lock(mUnprocessedMutex);

  for (auto& event : aEvents) {
    mUnprocessedModuleLoads.insertBack(
        new UnprocessedModuleLoadInfoContainer(std::move(event)));
  }

  ScheduleNonEmptyQueueProcessing(lock);
}

void UntrustedModulesProcessor::AssertRunningOnLazyIdleThread() {
#if defined(DEBUG)
  MOZ_ASSERT(mThread->IsOnCurrentThread());
#endif  // defined(DEBUG)
}

RefPtr<UntrustedModulesPromise> UntrustedModulesProcessor::GetProcessedData() {
  MOZ_ASSERT(NS_IsMainThread());

  // Clear any background priority in case background processing is running.
  {
    MutexAutoLock lock(mThreadHandleMutex);
    BackgroundPriorityRegion::Clear(mThreadHandle);
  }

  RefPtr<UntrustedModulesProcessor> self(this);
  return InvokeAsync(mThread, __func__, [self = std::move(self)]() {
    return self->GetProcessedDataInternal();
  });
}

RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrust(
    ModulePaths&& aModPaths, bool aRunAtNormalPriority) {
  MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());

  if (!IsReadyForBackgroundProcessing()) {
    return ModulesTrustPromise::CreateAndReject(
        NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
  }

  RefPtr<UntrustedModulesProcessor> self(this);
  auto run = [self = std::move(self), modPaths = std::move(aModPaths),
              runNormal = aRunAtNormalPriority]() mutable {
    return self->GetModulesTrustInternal(std::move(modPaths), runNormal);
  };

  if (aRunAtNormalPriority) {
    // Clear any background priority in case background processing is running.
    {
      MutexAutoLock lock(mThreadHandleMutex);
      BackgroundPriorityRegion::Clear(mThreadHandle);
    }

    return InvokeAsync(mThread, __func__, std::move(run));
  }

  RefPtr<ModulesTrustPromise::Private> p(
      new ModulesTrustPromise::Private(__func__));
  nsCOMPtr<nsISerialEventTarget> evtTarget(mThread);
  StaticString source = __func__;

  auto runWrap = [evtTarget = std::move(evtTarget), p, source,
                  run = std::move(run)]() mutable -> void {
    InvokeAsync(evtTarget, source, std::move(run))->ChainTo(p.forget(), source);
  };

  nsCOMPtr<nsIRunnable> idleRunnable(
      NS_NewRunnableFunction(source, std::move(runWrap)));

  nsresult rv = NS_DispatchToMainThreadQueue(idleRunnable.forget(),
                                             EventQueuePriority::Idle);
  if (NS_FAILED(rv)) {
    p->Reject(rv, source);
  }

  return p;
}

RefPtr<UntrustedModulesPromise>
UntrustedModulesProcessor::GetProcessedDataInternal() {
  AssertRunningOnLazyIdleThread();
  if (!XRE_IsParentProcess()) {
    return GetProcessedDataInternalChildProcess();
  }

  ProcessModuleLoadQueue();

  return GetAllProcessedData(__func__);
}

RefPtr<UntrustedModulesPromise> UntrustedModulesProcessor::GetAllProcessedData(
    StaticString aSource) {
  AssertRunningOnLazyIdleThread();

  UntrustedModulesData result;

  if (!mProcessedModuleLoads) {
    return UntrustedModulesPromise::CreateAndResolve(Nothing(), aSource);
  }

  result.Swap(mProcessedModuleLoads);

  result.mElapsed = TimeStamp::Now() - TimeStamp::ProcessCreation();

  return UntrustedModulesPromise::CreateAndResolve(
      Some(UntrustedModulesData(std::move(result))), aSource);
}

RefPtr<UntrustedModulesPromise>
UntrustedModulesProcessor::GetProcessedDataInternalChildProcess() {
  AssertRunningOnLazyIdleThread();
  MOZ_ASSERT(!XRE_IsParentProcess());

  RefPtr<GetModulesTrustPromise> whenProcessed(
      ProcessModuleLoadQueueChildProcess(Priority::Default));

  RefPtr<UntrustedModulesProcessor> self(this);
  RefPtr<UntrustedModulesPromise::Private> p(
      new UntrustedModulesPromise::Private(__func__));
  nsCOMPtr<nsISerialEventTarget> evtTarget(mThread);

  StaticString source = __func__;
  auto completionRoutine = [evtTarget = std::move(evtTarget), p,
                            self = std::move(self), source,
                            whenProcessed = std::move(whenProcessed)]() {
    MOZ_ASSERT(NS_IsMainThread());
    if (!self->IsReadyForBackgroundProcessing()) {
      // We can't do any more work, just reject all the things
      whenProcessed->Then(
          GetMainThreadSerialEventTarget(), source,
          [p, source](Maybe<ModulesMapResultWithLoads>&& aResult) {
            p->Reject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, source);
          },
          [p, source](nsresult aRv) { p->Reject(aRv, source); });
      return;
    }

    whenProcessed->Then(
        evtTarget, source,
        [p, self = std::move(self),
         source](Maybe<ModulesMapResultWithLoads>&& aResult) mutable {
          if (aResult.isSome()) {
            self->CompleteProcessing(std::move(aResult.ref()));
          }
          self->GetAllProcessedData(source)->ChainTo(p.forget(), source);
        },
        [p, source](nsresult aRv) { p->Reject(aRv, source); });
  };

  // We always send |completionRoutine| on a trip through the main thread
  // due to some subtlety with |mThread| being a LazyIdleThread: we can only
  // Dispatch or Then to |mThread| from its creating thread, which is the
  // main thread. Hopefully we can get rid of this in the future and just
  // invoke whenProcessed->Then() directly.
  nsresult rv = NS_DispatchToMainThread(
      NS_NewRunnableFunction(__func__, std::move(completionRoutine)));
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  if (NS_FAILED(rv)) {
    p->Reject(rv, __func__);
  }

  return p;
}

void UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue() {
  if (!IsReadyForBackgroundProcessing()) {
    return;
  }

  BackgroundPriorityRegion bgRgn;

  if (!IsReadyForBackgroundProcessing()) {
    return;
  }

  ProcessModuleLoadQueue();
}

RefPtr<ModuleRecord> UntrustedModulesProcessor::GetOrAddModuleRecord(
    const ModuleEvaluator& aModEval, const nsAString& aResolvedNtPath) {
  MOZ_ASSERT(XRE_IsParentProcess());

  MutexAutoLock lock(mModuleCacheMutex);
  return mGlobalModuleCache.WithEntryHandle(
      aResolvedNtPath, [&](auto&& addPtr) -> RefPtr<ModuleRecord> {
        if (addPtr) {
          return addPtr.Data();
        }

        RefPtr<ModuleRecord> newMod(new ModuleRecord(aResolvedNtPath));
        if (!(*newMod)) {
          return nullptr;
        }

        Maybe<ModuleTrustFlags> maybeTrust = aModEval.GetTrust(*newMod);
        if (maybeTrust.isNothing()) {
          return nullptr;
        }

        newMod->mTrustFlags = maybeTrust.value();

        return addPtr.Insert(std::move(newMod));
      });
}

RefPtr<ModuleRecord> UntrustedModulesProcessor::GetModuleRecord(
    const ModulesMap& aModules,
    const glue::EnhancedModuleLoadInfo& aModuleLoadInfo) {
  MOZ_ASSERT(!XRE_IsParentProcess());

  return aModules.Get(aModuleLoadInfo.mNtLoadInfo.mSectionName.AsString());
}

void UntrustedModulesProcessor::BackgroundProcessModuleLoadQueueChildProcess() {
  RefPtr<GetModulesTrustPromise> whenProcessed(
      ProcessModuleLoadQueueChildProcess(Priority::Background));

  RefPtr<UntrustedModulesProcessor> self(this);
  nsCOMPtr<nsISerialEventTarget> evtTarget(mThread);

  constexpr StaticString const source = __func__;
  auto completionRoutine = [evtTarget = std::move(evtTarget),
                            self = std::move(self), source,
                            whenProcessed = std::move(whenProcessed)]() {
    MOZ_ASSERT(NS_IsMainThread());
    if (!self->IsReadyForBackgroundProcessing()) {
      // We can't do any more work, just no-op
      whenProcessed->Then(
          GetMainThreadSerialEventTarget(), source,
          [](Maybe<ModulesMapResultWithLoads>&& aResult) {},
          [](nsresult aRv) {});
      return;
    }

    whenProcessed->Then(
        evtTarget, source,
        [self = std::move(self)](Maybe<ModulesMapResultWithLoads>&& aResult) {
          if (aResult.isNothing() || !self->IsReadyForBackgroundProcessing()) {
            // Nothing to do
            return;
          }

          BackgroundPriorityRegion bgRgn;
          self->CompleteProcessing(std::move(aResult.ref()));
        },
        [](nsresult aRv) {});
  };

  // We always send |completionRoutine| on a trip through the main thread
  // due to some subtlety with |mThread| being a LazyIdleThread: we can only
  // Dispatch or Then to |mThread| from its creating thread, which is the
  // main thread. Hopefully we can get rid of this in the future and just
  // invoke whenProcessed->Then() directly.
  DebugOnly<nsresult> rv = NS_DispatchToMainThread(
      NS_NewRunnableFunction(__func__, std::move(completionRoutine)));
  MOZ_ASSERT(NS_SUCCEEDED(rv));
}

UnprocessedModuleLoads UntrustedModulesProcessor::ExtractLoadingEventsToProcess(
    size_t aMaxLength) {
  UnprocessedModuleLoads loadsToProcess;

  MutexAutoLock lock(mUnprocessedMutex);
  CancelScheduledProcessing(lock);

  loadsToProcess.splice(0, mUnprocessedModuleLoads, 0, aMaxLength);
  return loadsToProcess;
}

// This function contains multiple IsReadyForBackgroundProcessing() checks so
// that we can quickly bail out at the first sign of shutdown. This may be
// important when the current thread is running under background priority.
void UntrustedModulesProcessor::ProcessModuleLoadQueue() {
  AssertRunningOnLazyIdleThread();
  if (!XRE_IsParentProcess()) {
    BackgroundProcessModuleLoadQueueChildProcess();
    return;
  }

  UnprocessedModuleLoads loadsToProcess =
      ExtractLoadingEventsToProcess(UntrustedModulesData::kMaxEvents);
  if (!IsReadyForBackgroundProcessing() || loadsToProcess.isEmpty()) {
    return;
  }

  ModuleEvaluator modEval;
  MOZ_ASSERT(!!modEval);
  if (!modEval) {
    return;
  }

  Telemetry::BatchProcessedStackGenerator stackProcessor;
  Maybe<double> maybeXulLoadDuration;
  Vector<Telemetry::ProcessedStack> processedStacks;
  UntrustedModuleLoadingEvents processedEvents;
  uint32_t sanitizationFailures = 0;
  uint32_t trustTestFailures = 0;

  for (UnprocessedModuleLoadInfoContainer* container : loadsToProcess) {
    glue::EnhancedModuleLoadInfo& entry = container->mInfo;

    if (!IsReadyForBackgroundProcessing()) {
      return;
    }

    RefPtr<ModuleRecord> module(GetOrAddModuleRecord(
        modEval, entry.mNtLoadInfo.mSectionName.AsString()));
    if (!module) {
      // We failed to obtain trust information about the module.
      // Don't include test failures in the ping to avoid flooding it.
      ++trustTestFailures;
      continue;
    }

    if (!IsReadyForBackgroundProcessing()) {
      return;
    }

    glue::EnhancedModuleLoadInfo::BacktraceType backtrace =
        std::move(entry.mNtLoadInfo.mBacktrace);
    ProcessedModuleLoadEvent event(std::move(entry), std::move(module));

    if (!event) {
      // We don't have a sanitized DLL path, so we cannot include this event
      // for privacy reasons.
      ++sanitizationFailures;
      continue;
    }

    if (!IsReadyForBackgroundProcessing()) {
      return;
    }

    if (event.IsTrusted()) {
      if (event.IsXULLoad()) {
        maybeXulLoadDuration = event.mLoadDurationMS;
      }

      // Trusted modules are not included in the ping
      continue;
    }

    mProcessedModuleLoads.mModules.LookupOrInsert(
        event.mModule->mResolvedNtName, event.mModule);

    if (!IsReadyForBackgroundProcessing()) {
      return;
    }

    Telemetry::ProcessedStack processedStack =
        stackProcessor.GetStackAndModules(backtrace);

    if (!IsReadyForBackgroundProcessing()) {
      return;
    }

    (void)processedStacks.emplaceBack(std::move(processedStack));
    processedEvents.insertBack(
        new ProcessedModuleLoadEventContainer(std::move(event)));
  }

  if (processedStacks.empty() && processedEvents.isEmpty() &&
      !sanitizationFailures && !trustTestFailures) {
    // Nothing to save
    return;
  }

  if (!IsReadyForBackgroundProcessing()) {
    return;
  }

  // Modules have been added to mProcessedModuleLoads.mModules
  // in the loop above.  Passing an empty ModulesMap to AddNewLoads.
  mProcessedModuleLoads.AddNewLoads(ModulesMap{}, std::move(processedEvents),
                                    std::move(processedStacks));
  if (maybeXulLoadDuration) {
    MOZ_ASSERT(!mProcessedModuleLoads.mXULLoadDurationMS);
    mProcessedModuleLoads.mXULLoadDurationMS = maybeXulLoadDuration;
  }

  mProcessedModuleLoads.mSanitizationFailures += sanitizationFailures;
  mProcessedModuleLoads.mTrustTestFailures += trustTestFailures;
}

template <typename ActorT>
static RefPtr<GetModulesTrustIpcPromise> SendGetModulesTrust(
    ActorT* aActor, ModulePaths&& aModPaths, bool aRunAtNormalPriority) {
  MOZ_ASSERT(NS_IsMainThread());
  return aActor->SendGetModulesTrust(std::move(aModPaths),
                                     aRunAtNormalPriority);
}

RefPtr<GetModulesTrustIpcPromise>
UntrustedModulesProcessor::SendGetModulesTrust(ModulePaths&& aModules,
                                               Priority aPriority) {
  MOZ_ASSERT(NS_IsMainThread());
  bool runNormal = aPriority == Priority::Default;

  switch (XRE_GetProcessType()) {
    case GeckoProcessType_Content: {
      return ::mozilla::SendGetModulesTrust(dom::ContentChild::GetSingleton(),
                                            std::move(aModules), runNormal);
    }
    case GeckoProcessType_RDD: {
      return ::mozilla::SendGetModulesTrust(RDDParent::GetSingleton(),
                                            std::move(aModules), runNormal);
    }
    case GeckoProcessType_Socket: {
      return ::mozilla::SendGetModulesTrust(
          net::SocketProcessChild::GetSingleton(), std::move(aModules),
          runNormal);
    }
    case GeckoProcessType_Utility: {
      return ::mozilla::SendGetModulesTrust(
          ipc::UtilityProcessChild::GetSingleton().get(), std::move(aModules),
          runNormal);
    }
    case GeckoProcessType_GMPlugin: {
      return ::mozilla::gmp::SendGetModulesTrust(std::move(aModules),
                                                 runNormal);
    }
    default: {
      MOZ_ASSERT_UNREACHABLE("Unsupported process type");
      return GetModulesTrustIpcPromise::CreateAndReject(
          ipc::ResponseRejectReason::SendError, __func__);
    }
  }
}

/**
 * This method works very similarly to ProcessModuleLoadQueue, with the
 * exception that a sandboxed child process does not have sufficient rights to
 * be able to evaluate a module's trustworthiness. Instead, we accumulate the
 * resolved paths for all of the modules in this batch and send them to the
 * parent to determine trustworthiness.
 *
 * The parent process returns a list of untrusted modules and invokes
 * CompleteProcessing to handle the remainder of the process.
 *
 * By doing it this way, we minimize the amount of data that needs to be sent
 * over IPC and avoid the need to process every load's metadata only
 * to throw most of it away (since most modules will be trusted).
 */
RefPtr<UntrustedModulesProcessor::GetModulesTrustPromise>
UntrustedModulesProcessor::ProcessModuleLoadQueueChildProcess(
    UntrustedModulesProcessor::Priority aPriority) {
  AssertRunningOnLazyIdleThread();
  MOZ_ASSERT(!XRE_IsParentProcess());

  UnprocessedModuleLoads loadsToProcess =
      ExtractLoadingEventsToProcess(UntrustedModulesData::kMaxEvents);
  if (loadsToProcess.isEmpty()) {
    // Nothing to process
    return GetModulesTrustPromise::CreateAndResolve(Nothing(), __func__);
  }

  if (!IsReadyForBackgroundProcessing()) {
    return GetModulesTrustPromise::CreateAndReject(
        NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
  }

  nsTHashtable<nsStringCaseInsensitiveHashKey> moduleNtPathSet;

  // Build a set of modules to be processed by the parent
  for (UnprocessedModuleLoadInfoContainer* container : loadsToProcess) {
    glue::EnhancedModuleLoadInfo& entry = container->mInfo;

    if (!IsReadyForBackgroundProcessing()) {
      return GetModulesTrustPromise::CreateAndReject(
          NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
    }

    moduleNtPathSet.PutEntry(entry.mNtLoadInfo.mSectionName.AsString());
  }

  if (!IsReadyForBackgroundProcessing()) {
    return GetModulesTrustPromise::CreateAndReject(
        NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
  }

  MOZ_ASSERT(!moduleNtPathSet.IsEmpty());
  if (moduleNtPathSet.IsEmpty()) {
    // Nothing to process
    return GetModulesTrustPromise::CreateAndResolve(Nothing(), __func__);
  }

  ModulePaths moduleNtPaths(std::move(moduleNtPathSet));

  if (!IsReadyForBackgroundProcessing()) {
    return GetModulesTrustPromise::CreateAndReject(
        NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
  }

  RefPtr<UntrustedModulesProcessor> self(this);

  auto invoker = [self = std::move(self),
                  moduleNtPaths = std::move(moduleNtPaths),
                  priority = aPriority]() mutable {
    return self->SendGetModulesTrust(std::move(moduleNtPaths), priority);
  };

  RefPtr<GetModulesTrustPromise::Private> p(
      new GetModulesTrustPromise::Private(__func__));

  if (!IsReadyForBackgroundProcessing()) {
    p->Reject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
    return p;
  }

  // Send the IPC request via the main thread
  InvokeAsync(GetMainThreadSerialEventTarget(), __func__, std::move(invoker))
      ->Then(
          GetMainThreadSerialEventTarget(), __func__,
          [p, loads = std::move(loadsToProcess)](
              Maybe<ModulesMapResult>&& aResult) mutable {
            ModulesMapResultWithLoads result(std::move(aResult),
                                             std::move(loads));
            p->Resolve(Some(ModulesMapResultWithLoads(std::move(result))),
                       __func__);
          },
          [p](ipc::ResponseRejectReason aReason) {
            p->Reject(NS_ERROR_FAILURE, __func__);
          });

  return p;
}

void UntrustedModulesProcessor::CompleteProcessing(
    UntrustedModulesProcessor::ModulesMapResultWithLoads&& aModulesAndLoads) {
  MOZ_ASSERT(!XRE_IsParentProcess());
  AssertRunningOnLazyIdleThread();

  if (!IsReadyForBackgroundProcessing()) {
    return;
  }

  if (aModulesAndLoads.mModMapResult.isNothing()) {
    // No untrusted modules in this batch, nothing to save.
    return;
  }

  // This map only contains information about modules deemed to be untrusted,
  // plus xul.dll. Any module referenced by load requests that is *not* in the
  // map is deemed to be trusted.
  ModulesMap& modules = aModulesAndLoads.mModMapResult.ref().mModules;
  const uint32_t& trustTestFailures =
      aModulesAndLoads.mModMapResult.ref().mTrustTestFailures;
  UnprocessedModuleLoads& loads = aModulesAndLoads.mLoads;

  if (modules.IsEmpty() && !trustTestFailures) {
    // No data, nothing to save.
    return;
  }

  if (!IsReadyForBackgroundProcessing()) {
    return;
  }

  Telemetry::BatchProcessedStackGenerator stackProcessor;

  Maybe<double> maybeXulLoadDuration;
  Vector<Telemetry::ProcessedStack> processedStacks;
  UntrustedModuleLoadingEvents processedEvents;
  uint32_t sanitizationFailures = 0;

  if (!modules.IsEmpty()) {
    for (UnprocessedModuleLoadInfoContainer* container : loads) {
      glue::EnhancedModuleLoadInfo& item = container->mInfo;
      if (!IsReadyForBackgroundProcessing()) {
        return;
      }

      RefPtr<ModuleRecord> module(GetModuleRecord(modules, item));
      if (!module) {
        // If module is null then |item| is trusted
        continue;
      }

      if (!IsReadyForBackgroundProcessing()) {
        return;
      }

      glue::EnhancedModuleLoadInfo::BacktraceType backtrace =
          std::move(item.mNtLoadInfo.mBacktrace);
      ProcessedModuleLoadEvent event(std::move(item), std::move(module));

      if (!IsReadyForBackgroundProcessing()) {
        return;
      }

      if (!event) {
        // We don't have a sanitized DLL path, so we cannot include this event
        // for privacy reasons.
        ++sanitizationFailures;
        continue;
      }

      if (!IsReadyForBackgroundProcessing()) {
        return;
      }

      if (event.IsXULLoad()) {
        maybeXulLoadDuration = event.mLoadDurationMS;
        // We saved the XUL load duration, but it is still trusted, so we
        // continue.
        continue;
      }

      if (!IsReadyForBackgroundProcessing()) {
        return;
      }

      Telemetry::ProcessedStack processedStack =
          stackProcessor.GetStackAndModules(backtrace);

      (void)processedStacks.emplaceBack(std::move(processedStack));
      processedEvents.insertBack(
          new ProcessedModuleLoadEventContainer(std::move(event)));
    }
  }

  if (processedStacks.empty() && processedEvents.isEmpty() &&
      !sanitizationFailures && !trustTestFailures) {
    // Nothing to save
    return;
  }

  if (!IsReadyForBackgroundProcessing()) {
    return;
  }

  mProcessedModuleLoads.AddNewLoads(modules, std::move(processedEvents),
                                    std::move(processedStacks));
  if (maybeXulLoadDuration) {
    MOZ_ASSERT(!mProcessedModuleLoads.mXULLoadDurationMS);
    mProcessedModuleLoads.mXULLoadDurationMS = maybeXulLoadDuration;
  }

  mProcessedModuleLoads.mSanitizationFailures += sanitizationFailures;
  mProcessedModuleLoads.mTrustTestFailures += trustTestFailures;
}

// The thread priority of this job should match the priority that the child
// process is running with, as specified by |aRunAtNormalPriority|.
RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrustInternal(
    ModulePaths&& aModPaths, bool aRunAtNormalPriority) {
  MOZ_ASSERT(XRE_IsParentProcess());
  AssertRunningOnLazyIdleThread();

  if (!IsReadyForBackgroundProcessing()) {
    return ModulesTrustPromise::CreateAndReject(
        NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
  }

  if (aRunAtNormalPriority) {
    return GetModulesTrustInternal(std::move(aModPaths));
  }

  BackgroundPriorityRegion bgRgn;
  return GetModulesTrustInternal(std::move(aModPaths));
}

// For each module in |aModPaths|, evaluate its trustworthiness and only send
// ModuleRecords for untrusted modules back to the child process. We also save
// XUL's ModuleRecord so that the child process may report XUL's load time.
RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrustInternal(
    ModulePaths&& aModPaths) {
  MOZ_ASSERT(XRE_IsParentProcess());
  AssertRunningOnLazyIdleThread();

  ModulesMapResult result;

  ModulesMap& modMap = result.mModules;
  uint32_t& trustTestFailures = result.mTrustTestFailures;

  ModuleEvaluator modEval;
  MOZ_ASSERT(!!modEval);
  if (!modEval) {
    return ModulesTrustPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
  }

  for (auto& resolvedNtPath :
       aModPaths.mModuleNtPaths.as<ModulePaths::VecType>()) {
    if (!IsReadyForBackgroundProcessing()) {
      return ModulesTrustPromise::CreateAndReject(
          NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
    }

    MOZ_ASSERT(!resolvedNtPath.IsEmpty());
    if (resolvedNtPath.IsEmpty()) {
      continue;
    }

    RefPtr<ModuleRecord> module(GetOrAddModuleRecord(modEval, resolvedNtPath));
    if (!module) {
      // We failed to obtain trust information.
      ++trustTestFailures;
      continue;
    }

    if (!IsReadyForBackgroundProcessing()) {
      return ModulesTrustPromise::CreateAndReject(
          NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
    }

    if (module->IsTrusted() && !module->IsXUL()) {
      // If the module is trusted we exclude it from results, unless it's XUL.
      // (We save XUL so that the child process may report XUL's load time)
      continue;
    }

    if (!IsReadyForBackgroundProcessing()) {
      return ModulesTrustPromise::CreateAndReject(
          NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
    }

    modMap.InsertOrUpdate(resolvedNtPath, std::move(module));
  }

  return ModulesTrustPromise::CreateAndResolve(std::move(result), __func__);
}

}  // namespace mozilla
