summaryrefslogtreecommitdiffstats
path: root/ipc/glue/UtilityProcessManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/glue/UtilityProcessManager.cpp')
-rw-r--r--ipc/glue/UtilityProcessManager.cpp515
1 files changed, 515 insertions, 0 deletions
diff --git a/ipc/glue/UtilityProcessManager.cpp b/ipc/glue/UtilityProcessManager.cpp
new file mode 100644
index 0000000000..50b0d23102
--- /dev/null
+++ b/ipc/glue/UtilityProcessManager.cpp
@@ -0,0 +1,515 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "UtilityProcessManager.h"
+
+#include "JSOracleParent.h"
+#include "mozilla/ipc/UtilityProcessHost.h"
+#include "mozilla/MemoryReportingProcess.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/SyncRunnable.h" // for LaunchUtilityProcess
+#include "mozilla/ipc/UtilityProcessParent.h"
+#include "mozilla/ipc/UtilityAudioDecoderChild.h"
+#include "mozilla/ipc/UtilityAudioDecoderParent.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#include "mozilla/ipc/ProcessChild.h"
+#include "nsAppRunner.h"
+#include "nsContentUtils.h"
+
+#include "mozilla/GeckoArgs.h"
+
+namespace mozilla::ipc {
+
+static StaticRefPtr<UtilityProcessManager> sSingleton;
+
+static bool sXPCOMShutdown = false;
+
+bool UtilityProcessManager::IsShutdown() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return sXPCOMShutdown || !sSingleton;
+}
+
+RefPtr<UtilityProcessManager> UtilityProcessManager::GetSingleton() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sXPCOMShutdown && sSingleton == nullptr) {
+ sSingleton = new UtilityProcessManager();
+ }
+ return sSingleton;
+}
+
+RefPtr<UtilityProcessManager> UtilityProcessManager::GetIfExists() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return sSingleton;
+}
+
+UtilityProcessManager::UtilityProcessManager() : mObserver(new Observer(this)) {
+ // Start listening for pref changes so we can
+ // forward them to the process once it is running.
+ nsContentUtils::RegisterShutdownObserver(mObserver);
+ Preferences::AddStrongObserver(mObserver, "");
+}
+
+UtilityProcessManager::~UtilityProcessManager() {
+ // The Utility process should ALL have already been shut down.
+ MOZ_ASSERT(NoMoreProcesses());
+}
+
+NS_IMPL_ISUPPORTS(UtilityProcessManager::Observer, nsIObserver);
+
+UtilityProcessManager::Observer::Observer(
+ RefPtr<UtilityProcessManager> aManager)
+ : mManager(std::move(aManager)) {}
+
+NS_IMETHODIMP
+UtilityProcessManager::Observer::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ mManager->OnXPCOMShutdown();
+ } else if (!strcmp(aTopic, "nsPref:changed")) {
+ mManager->OnPreferenceChange(aData);
+ }
+ return NS_OK;
+}
+
+void UtilityProcessManager::OnXPCOMShutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ sXPCOMShutdown = true;
+ nsContentUtils::UnregisterShutdownObserver(mObserver);
+ CleanShutdownAllProcesses();
+}
+
+void UtilityProcessManager::OnPreferenceChange(const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (NoMoreProcesses()) {
+ // Process hasn't been launched yet
+ return;
+ }
+ // We know prefs are ASCII here.
+ NS_LossyConvertUTF16toASCII strData(aData);
+
+ mozilla::dom::Pref pref(strData, /* isLocked */ false,
+ /* isSanitized */ false, Nothing(), Nothing());
+ Preferences::GetPreference(&pref, GeckoProcessType_Utility,
+ /* remoteType */ ""_ns);
+
+ for (auto& p : mProcesses) {
+ if (!p) {
+ continue;
+ }
+
+ if (p->mProcessParent) {
+ Unused << p->mProcessParent->SendPreferenceUpdate(pref);
+ } else if (IsProcessLaunching(p->mSandbox)) {
+ p->mQueuedPrefs.AppendElement(pref);
+ }
+ }
+}
+
+RefPtr<UtilityProcessManager::ProcessFields> UtilityProcessManager::GetProcess(
+ SandboxingKind aSandbox) {
+ if (!mProcesses[aSandbox]) {
+ return nullptr;
+ }
+
+ return mProcesses[aSandbox];
+}
+
+RefPtr<GenericNonExclusivePromise> UtilityProcessManager::LaunchProcess(
+ SandboxingKind aSandbox) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (IsShutdown()) {
+ NS_WARNING("Reject early LaunchProcess() for Shutdown");
+ return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
+ __func__);
+ }
+
+ RefPtr<ProcessFields> p = GetProcess(aSandbox);
+ if (p && p->mNumProcessAttempts) {
+ // We failed to start the Utility process earlier, abort now.
+ NS_WARNING("Reject LaunchProcess() for earlier mNumProcessAttempts");
+ return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
+ __func__);
+ }
+
+ if (p && p->mLaunchPromise && p->mProcess) {
+ return p->mLaunchPromise;
+ }
+
+ if (!p) {
+ p = new ProcessFields(aSandbox);
+ mProcesses[aSandbox] = p;
+ }
+
+ std::vector<std::string> extraArgs;
+ ProcessChild::AddPlatformBuildID(extraArgs);
+ geckoargs::sSandboxingKind.Put(aSandbox, extraArgs);
+
+ // The subprocess is launched asynchronously, so we
+ // wait for the promise to be resolved to acquire the IPDL actor.
+ p->mProcess = new UtilityProcessHost(aSandbox, this);
+ if (!p->mProcess->Launch(extraArgs)) {
+ p->mNumProcessAttempts++;
+ DestroyProcess(aSandbox);
+ NS_WARNING("Reject LaunchProcess() for mNumProcessAttempts++");
+ return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
+ __func__);
+ }
+
+ RefPtr<UtilityProcessManager> self = this;
+ p->mLaunchPromise = p->mProcess->LaunchPromise()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self, p, aSandbox](bool) {
+ if (self->IsShutdown()) {
+ NS_WARNING(
+ "Reject LaunchProcess() after LaunchPromise() for Shutdown");
+ return GenericNonExclusivePromise::CreateAndReject(
+ NS_ERROR_NOT_AVAILABLE, __func__);
+ }
+
+ if (self->IsProcessDestroyed(aSandbox)) {
+ NS_WARNING(
+ "Reject LaunchProcess() after LaunchPromise() for destroyed "
+ "process");
+ return GenericNonExclusivePromise::CreateAndReject(
+ NS_ERROR_NOT_AVAILABLE, __func__);
+ }
+
+ p->mProcessParent = p->mProcess->GetActor();
+
+ // Flush any pref updates that happened during
+ // launch and weren't included in the blobs set
+ // up in LaunchUtilityProcess.
+ for (const mozilla::dom::Pref& pref : p->mQueuedPrefs) {
+ Unused << NS_WARN_IF(!p->mProcessParent->SendPreferenceUpdate(pref));
+ }
+ p->mQueuedPrefs.Clear();
+
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::UtilityProcessStatus, "Running"_ns);
+
+ return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
+ },
+ [self, p, aSandbox](nsresult aError) {
+ if (GetSingleton()) {
+ p->mNumProcessAttempts++;
+ self->DestroyProcess(aSandbox);
+ }
+ NS_WARNING("Reject LaunchProcess() for LaunchPromise() rejection");
+ return GenericNonExclusivePromise::CreateAndReject(aError, __func__);
+ });
+
+ return p->mLaunchPromise;
+}
+
+template <typename Actor>
+RefPtr<GenericNonExclusivePromise> UtilityProcessManager::StartUtility(
+ RefPtr<Actor> aActor, SandboxingKind aSandbox) {
+ if (!aActor) {
+ MOZ_ASSERT(false, "Actor singleton failure");
+ return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+
+ if (aActor->CanSend()) {
+ // Actor has already been setup, so we:
+ // - know the process has been launched
+ // - the ipc actors are ready
+ return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
+ }
+
+ RefPtr<UtilityProcessManager> self = this;
+ return LaunchProcess(aSandbox)->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self, aActor, aSandbox]() {
+ RefPtr<UtilityProcessParent> utilityParent =
+ self->GetProcessParent(aSandbox);
+ if (!utilityParent) {
+ NS_WARNING("Missing parent in StartUtility");
+ return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+
+ // It is possible if multiple processes concurrently request a utility
+ // actor that the previous CanSend() check returned false for both but
+ // that by the time we have started our process for real, one of them
+ // has already been able to establish the IPC connection and thus we
+ // would perform more than one Open() call.
+ //
+ // The tests within browser_utility_multipleAudio.js should be able to
+ // catch that behavior.
+ if (!aActor->CanSend()) {
+ nsresult rv = aActor->BindToUtilityProcess(utilityParent);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false, "Protocol endpoints failure");
+ return GenericNonExclusivePromise::CreateAndReject(rv, __func__);
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aActor->CanSend(), "IPC established for actor");
+ self->RegisterActor(utilityParent, aActor->GetActorName());
+ }
+
+ return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
+ },
+ [self](nsresult aError) {
+ if (!self->IsShutdown()) {
+ MOZ_ASSERT_UNREACHABLE("Failure when starting actor");
+ }
+ NS_WARNING("Reject StartUtility() for LaunchProcess() rejection");
+ return GenericNonExclusivePromise::CreateAndReject(aError, __func__);
+ });
+}
+
+RefPtr<UtilityProcessManager::StartRemoteDecodingUtilityPromise>
+UtilityProcessManager::StartProcessForRemoteMediaDecoding(
+ base::ProcessId aOtherProcess, SandboxingKind aSandbox) {
+ // Not supported kinds.
+ if (aSandbox != SandboxingKind::GENERIC_UTILITY
+#ifdef MOZ_APPLEMEDIA
+ && aSandbox != SandboxingKind::UTILITY_AUDIO_DECODING_APPLE_MEDIA
+#endif
+#ifdef XP_WIN
+ && aSandbox != SandboxingKind::UTILITY_AUDIO_DECODING_WMF
+#endif
+#ifdef MOZ_WMF_MEDIA_ENGINE
+ && aSandbox != SandboxingKind::MF_MEDIA_ENGINE_CDM
+#endif
+ ) {
+ return StartRemoteDecodingUtilityPromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+ RefPtr<UtilityProcessManager> self = this;
+ RefPtr<UtilityAudioDecoderChild> uadc =
+ UtilityAudioDecoderChild::GetSingleton(aSandbox);
+ MOZ_ASSERT(uadc, "Unable to get a singleton for UtilityAudioDecoderChild");
+ return StartUtility(uadc, aSandbox)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self, uadc, aOtherProcess, aSandbox]() {
+ RefPtr<UtilityProcessParent> parent =
+ self->GetProcessParent(aSandbox);
+ if (!parent) {
+ NS_WARNING("UtilityAudioDecoderParent lost in the middle");
+ return StartRemoteDecodingUtilityPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ }
+
+ if (!uadc->CanSend()) {
+ NS_WARNING("UtilityAudioDecoderChild lost in the middle");
+ return StartRemoteDecodingUtilityPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ }
+
+ base::ProcessId process = parent->OtherPid();
+
+ Endpoint<PRemoteDecoderManagerChild> childPipe;
+ Endpoint<PRemoteDecoderManagerParent> parentPipe;
+ nsresult rv = PRemoteDecoderManager::CreateEndpoints(
+ process, aOtherProcess, &parentPipe, &childPipe);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false, "Could not create content remote decoder");
+ return StartRemoteDecodingUtilityPromise::CreateAndReject(
+ rv, __func__);
+ }
+
+ if (!uadc->SendNewContentRemoteDecoderManager(
+ std::move(parentPipe))) {
+ MOZ_ASSERT(false, "SendNewContentRemoteDecoderManager failure");
+ return StartRemoteDecodingUtilityPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ }
+
+#ifdef MOZ_WMF_MEDIA_ENGINE
+ if (aSandbox == SandboxingKind::MF_MEDIA_ENGINE_CDM &&
+ !uadc->CreateVideoBridge()) {
+ MOZ_ASSERT(false, "Failed to create video bridge");
+ return StartRemoteDecodingUtilityPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ }
+#endif
+ return StartRemoteDecodingUtilityPromise::CreateAndResolve(
+ std::move(childPipe), __func__);
+ },
+ [self](nsresult aError) {
+ if (!self->IsShutdown()) {
+ MOZ_ASSERT_UNREACHABLE(
+ "PUtilityAudioDecoder: failure when starting actor");
+ }
+ NS_WARNING(
+ "Reject StartProcessForRemoteMediaDecoding() for "
+ "StartUtility() rejection");
+ return StartRemoteDecodingUtilityPromise::CreateAndReject(aError,
+ __func__);
+ });
+}
+
+RefPtr<UtilityProcessManager::JSOraclePromise>
+UtilityProcessManager::StartJSOracle(dom::JSOracleParent* aParent) {
+ return StartUtility(RefPtr{aParent}, SandboxingKind::GENERIC_UTILITY);
+}
+
+bool UtilityProcessManager::IsProcessLaunching(SandboxingKind aSandbox) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<ProcessFields> p = GetProcess(aSandbox);
+ if (!p) {
+ MOZ_CRASH("Cannot check process launching with no process");
+ return false;
+ }
+
+ return p->mProcess && !(p->mProcessParent);
+}
+
+bool UtilityProcessManager::IsProcessDestroyed(SandboxingKind aSandbox) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<ProcessFields> p = GetProcess(aSandbox);
+ if (!p) {
+ MOZ_CRASH("Cannot check process destroyed with no process");
+ return false;
+ }
+ return !p->mProcess && !p->mProcessParent;
+}
+
+void UtilityProcessManager::OnProcessUnexpectedShutdown(
+ UtilityProcessHost* aHost) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ for (auto& it : mProcesses) {
+ if (it && it->mProcess && it->mProcess == aHost) {
+ it->mNumUnexpectedCrashes++;
+ DestroyProcess(it->mSandbox);
+ return;
+ }
+ }
+
+ MOZ_CRASH(
+ "Called UtilityProcessManager::OnProcessUnexpectedShutdown with invalid "
+ "aHost");
+}
+
+void UtilityProcessManager::CleanShutdownAllProcesses() {
+ for (auto& it : mProcesses) {
+ if (it) {
+ DestroyProcess(it->mSandbox);
+ }
+ }
+}
+
+void UtilityProcessManager::CleanShutdown(SandboxingKind aSandbox) {
+ DestroyProcess(aSandbox);
+}
+
+uint16_t UtilityProcessManager::AliveProcesses() {
+ uint16_t alive = 0;
+ for (auto& p : mProcesses) {
+ if (p != nullptr) {
+ alive++;
+ }
+ }
+ return alive;
+}
+
+bool UtilityProcessManager::NoMoreProcesses() { return AliveProcesses() == 0; }
+
+void UtilityProcessManager::DestroyProcess(SandboxingKind aSandbox) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ if (AliveProcesses() <= 1) {
+ if (mObserver) {
+ Preferences::RemoveObserver(mObserver, "");
+ }
+
+ mObserver = nullptr;
+ }
+
+ RefPtr<ProcessFields> p = GetProcess(aSandbox);
+ if (!p) {
+ return;
+ }
+
+ p->mQueuedPrefs.Clear();
+ p->mProcessParent = nullptr;
+
+ if (!p->mProcess) {
+ return;
+ }
+
+ p->mProcess->Shutdown();
+ p->mProcess = nullptr;
+
+ mProcesses[aSandbox] = nullptr;
+
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::UtilityProcessStatus, "Destroyed"_ns);
+
+ if (NoMoreProcesses()) {
+ sSingleton = nullptr;
+ }
+}
+
+Maybe<base::ProcessId> UtilityProcessManager::ProcessPid(
+ SandboxingKind aSandbox) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<ProcessFields> p = GetProcess(aSandbox);
+ if (!p) {
+ return Nothing();
+ }
+ if (p->mProcessParent) {
+ return Some(p->mProcessParent->OtherPid());
+ }
+ return Nothing();
+}
+
+class UtilityMemoryReporter : public MemoryReportingProcess {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UtilityMemoryReporter, override)
+
+ explicit UtilityMemoryReporter(UtilityProcessParent* aParent) {
+ mParent = aParent;
+ }
+
+ bool IsAlive() const override { return bool(GetParent()); }
+
+ bool SendRequestMemoryReport(
+ const uint32_t& aGeneration, const bool& aAnonymize,
+ const bool& aMinimizeMemoryUsage,
+ const Maybe<ipc::FileDescriptor>& aDMDFile) override {
+ RefPtr<UtilityProcessParent> parent = GetParent();
+ if (!parent) {
+ return false;
+ }
+
+ return parent->SendRequestMemoryReport(aGeneration, aAnonymize,
+ aMinimizeMemoryUsage, aDMDFile);
+ }
+
+ int32_t Pid() const override {
+ if (RefPtr<UtilityProcessParent> parent = GetParent()) {
+ return (int32_t)parent->OtherPid();
+ }
+ return 0;
+ }
+
+ private:
+ RefPtr<UtilityProcessParent> GetParent() const { return mParent; }
+
+ RefPtr<UtilityProcessParent> mParent = nullptr;
+
+ protected:
+ ~UtilityMemoryReporter() = default;
+};
+
+RefPtr<MemoryReportingProcess> UtilityProcessManager::GetProcessMemoryReporter(
+ UtilityProcessParent* parent) {
+ return new UtilityMemoryReporter(parent);
+}
+
+} // namespace mozilla::ipc