summaryrefslogtreecommitdiffstats
path: root/ipc/glue
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /ipc/glue
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ipc/glue')
-rw-r--r--ipc/glue/AsyncBlockers.h82
-rw-r--r--ipc/glue/BackgroundChild.h101
-rw-r--r--ipc/glue/BackgroundChildImpl.cpp540
-rw-r--r--ipc/glue/BackgroundChildImpl.h227
-rw-r--r--ipc/glue/BackgroundImpl.cpp1306
-rw-r--r--ipc/glue/BackgroundParent.h113
-rw-r--r--ipc/glue/BackgroundParentImpl.cpp1893
-rw-r--r--ipc/glue/BackgroundParentImpl.h415
-rw-r--r--ipc/glue/BackgroundStarterChild.h35
-rw-r--r--ipc/glue/BackgroundStarterParent.h48
-rw-r--r--ipc/glue/BackgroundUtils.cpp1115
-rw-r--r--ipc/glue/BackgroundUtils.h195
-rw-r--r--ipc/glue/BigBuffer.cpp105
-rw-r--r--ipc/glue/BigBuffer.h131
-rw-r--r--ipc/glue/BrowserProcessSubThread.cpp83
-rw-r--r--ipc/glue/BrowserProcessSubThread.h66
-rw-r--r--ipc/glue/ByteBuf.h71
-rw-r--r--ipc/glue/ByteBufUtils.h56
-rw-r--r--ipc/glue/CrashReporterClient.cpp47
-rw-r--r--ipc/glue/CrashReporterClient.h51
-rw-r--r--ipc/glue/CrashReporterHelper.h78
-rw-r--r--ipc/glue/CrashReporterHost.cpp181
-rw-r--r--ipc/glue/CrashReporterHost.h125
-rw-r--r--ipc/glue/CrossProcessMutex.h118
-rw-r--r--ipc/glue/CrossProcessMutex_posix.cpp140
-rw-r--r--ipc/glue/CrossProcessMutex_unimplemented.cpp46
-rw-r--r--ipc/glue/CrossProcessMutex_windows.cpp65
-rw-r--r--ipc/glue/CrossProcessSemaphore.h118
-rw-r--r--ipc/glue/CrossProcessSemaphore_posix.cpp164
-rw-r--r--ipc/glue/CrossProcessSemaphore_unimplemented.cpp64
-rw-r--r--ipc/glue/CrossProcessSemaphore_windows.cpp83
-rw-r--r--ipc/glue/DataPipe.cpp741
-rw-r--r--ipc/glue/DataPipe.h188
-rw-r--r--ipc/glue/Endpoint.cpp167
-rw-r--r--ipc/glue/Endpoint.h288
-rw-r--r--ipc/glue/EnumSerializer.h187
-rw-r--r--ipc/glue/EnvironmentMap.h62
-rw-r--r--ipc/glue/FileDescriptor.cpp117
-rw-r--r--ipc/glue/FileDescriptor.h79
-rw-r--r--ipc/glue/FileDescriptorShuffle.cpp113
-rw-r--r--ipc/glue/FileDescriptorShuffle.h68
-rw-r--r--ipc/glue/FileDescriptorUtils.cpp109
-rw-r--r--ipc/glue/FileDescriptorUtils.h53
-rw-r--r--ipc/glue/ForkServer.cpp298
-rw-r--r--ipc/glue/ForkServer.h46
-rw-r--r--ipc/glue/ForkServiceChild.cpp190
-rw-r--r--ipc/glue/ForkServiceChild.h105
-rw-r--r--ipc/glue/GeckoChildProcessHost.cpp1896
-rw-r--r--ipc/glue/GeckoChildProcessHost.h335
-rw-r--r--ipc/glue/IOThreadChild.h46
-rw-r--r--ipc/glue/IPCCore.h22
-rw-r--r--ipc/glue/IPCForwards.h38
-rw-r--r--ipc/glue/IPCMessageUtils.h238
-rw-r--r--ipc/glue/IPCMessageUtilsSpecializations.cpp67
-rw-r--r--ipc/glue/IPCMessageUtilsSpecializations.h802
-rw-r--r--ipc/glue/IPCStream.ipdlh22
-rw-r--r--ipc/glue/IPCStreamUtils.cpp191
-rw-r--r--ipc/glue/IPCStreamUtils.h51
-rw-r--r--ipc/glue/IPCTypes.h20
-rw-r--r--ipc/glue/IPDLParamTraits.h65
-rw-r--r--ipc/glue/IPDLStructMember.h37
-rw-r--r--ipc/glue/IdleSchedulerChild.cpp152
-rw-r--r--ipc/glue/IdleSchedulerChild.h75
-rw-r--r--ipc/glue/IdleSchedulerParent.cpp467
-rw-r--r--ipc/glue/IdleSchedulerParent.h137
-rw-r--r--ipc/glue/InputStreamParams.ipdlh101
-rw-r--r--ipc/glue/InputStreamUtils.cpp211
-rw-r--r--ipc/glue/InputStreamUtils.h50
-rw-r--r--ipc/glue/LaunchError.cpp31
-rw-r--r--ipc/glue/LaunchError.h40
-rw-r--r--ipc/glue/LibrarySandboxPreload.cpp45
-rw-r--r--ipc/glue/LibrarySandboxPreload.h18
-rw-r--r--ipc/glue/MessageChannel.cpp2482
-rw-r--r--ipc/glue/MessageChannel.h878
-rw-r--r--ipc/glue/MessageLink.cpp195
-rw-r--r--ipc/glue/MessageLink.h112
-rw-r--r--ipc/glue/MessagePump.cpp336
-rw-r--r--ipc/glue/MessagePump.h203
-rw-r--r--ipc/glue/MessagePump_android.cpp26
-rw-r--r--ipc/glue/MessagePump_mac.mm95
-rw-r--r--ipc/glue/MessagePump_windows.cpp96
-rw-r--r--ipc/glue/MiniTransceiver.cpp251
-rw-r--r--ipc/glue/MiniTransceiver.h118
-rw-r--r--ipc/glue/Neutering.h70
-rw-r--r--ipc/glue/NodeChannel.cpp328
-rw-r--r--ipc/glue/NodeChannel.h175
-rw-r--r--ipc/glue/NodeController.cpp856
-rw-r--r--ipc/glue/NodeController.h173
-rw-r--r--ipc/glue/PBackground.ipdl330
-rw-r--r--ipc/glue/PBackgroundSharedTypes.ipdlh73
-rw-r--r--ipc/glue/PBackgroundStarter.ipdl18
-rw-r--r--ipc/glue/PBackgroundTest.ipdl21
-rw-r--r--ipc/glue/PIdleScheduler.ipdl70
-rw-r--r--ipc/glue/PUtilityAudioDecoder.ipdl46
-rw-r--r--ipc/glue/PUtilityProcess.ipdl123
-rw-r--r--ipc/glue/ProcessChild.cpp137
-rw-r--r--ipc/glue/ProcessChild.h75
-rw-r--r--ipc/glue/ProcessUtils.h93
-rw-r--r--ipc/glue/ProcessUtils_bsd.cpp27
-rw-r--r--ipc/glue/ProcessUtils_common.cpp275
-rw-r--r--ipc/glue/ProcessUtils_linux.cpp22
-rw-r--r--ipc/glue/ProcessUtils_mac.mm109
-rw-r--r--ipc/glue/ProcessUtils_none.cpp15
-rw-r--r--ipc/glue/ProtocolMessageUtils.h121
-rw-r--r--ipc/glue/ProtocolTypes.ipdlh21
-rw-r--r--ipc/glue/ProtocolUtils.cpp835
-rw-r--r--ipc/glue/ProtocolUtils.h746
-rw-r--r--ipc/glue/RandomAccessStreamParams.ipdlh32
-rw-r--r--ipc/glue/RandomAccessStreamUtils.cpp88
-rw-r--r--ipc/glue/RandomAccessStreamUtils.h50
-rw-r--r--ipc/glue/RawShmem.cpp114
-rw-r--r--ipc/glue/RawShmem.h113
-rw-r--r--ipc/glue/ScopedPort.cpp77
-rw-r--r--ipc/glue/ScopedPort.h79
-rw-r--r--ipc/glue/SerializedStructuredCloneBuffer.cpp79
-rw-r--r--ipc/glue/SerializedStructuredCloneBuffer.h87
-rw-r--r--ipc/glue/SharedMemory.cpp84
-rw-r--r--ipc/glue/SharedMemory.h139
-rw-r--r--ipc/glue/SharedMemoryBasic.h18
-rw-r--r--ipc/glue/SharedMemoryBasic_android.cpp132
-rw-r--r--ipc/glue/SharedMemoryBasic_android.h72
-rw-r--r--ipc/glue/SharedMemoryBasic_chromium.h87
-rw-r--r--ipc/glue/SharedMemoryBasic_mach.h75
-rw-r--r--ipc/glue/SharedMemoryBasic_mach.mm167
-rw-r--r--ipc/glue/SharedMemory_posix.cpp55
-rw-r--r--ipc/glue/SharedMemory_windows.cpp41
-rw-r--r--ipc/glue/Shmem.cpp458
-rw-r--r--ipc/glue/Shmem.h205
-rw-r--r--ipc/glue/ShmemMessageUtils.h30
-rw-r--r--ipc/glue/SideVariant.h187
-rw-r--r--ipc/glue/StringUtil.cpp88
-rw-r--r--ipc/glue/TaintingIPCUtils.h41
-rw-r--r--ipc/glue/TaskFactory.h97
-rw-r--r--ipc/glue/ToplevelActorHolder.h45
-rw-r--r--ipc/glue/TransportSecurityInfoUtils.cpp78
-rw-r--r--ipc/glue/TransportSecurityInfoUtils.h43
-rw-r--r--ipc/glue/URIParams.ipdlh117
-rw-r--r--ipc/glue/URIUtils.cpp140
-rw-r--r--ipc/glue/URIUtils.h49
-rw-r--r--ipc/glue/UtilityAudioDecoder.cpp41
-rw-r--r--ipc/glue/UtilityAudioDecoder.h21
-rw-r--r--ipc/glue/UtilityAudioDecoderChild.cpp176
-rw-r--r--ipc/glue/UtilityAudioDecoderChild.h127
-rw-r--r--ipc/glue/UtilityAudioDecoderParent.cpp173
-rw-r--r--ipc/glue/UtilityAudioDecoderParent.h57
-rw-r--r--ipc/glue/UtilityProcessChild.cpp381
-rw-r--r--ipc/glue/UtilityProcessChild.h104
-rw-r--r--ipc/glue/UtilityProcessHost.cpp353
-rw-r--r--ipc/glue/UtilityProcessHost.h158
-rw-r--r--ipc/glue/UtilityProcessImpl.cpp127
-rw-r--r--ipc/glue/UtilityProcessImpl.h43
-rw-r--r--ipc/glue/UtilityProcessManager.cpp617
-rw-r--r--ipc/glue/UtilityProcessManager.h234
-rw-r--r--ipc/glue/UtilityProcessParent.cpp201
-rw-r--r--ipc/glue/UtilityProcessParent.h77
-rw-r--r--ipc/glue/UtilityProcessSandboxing.h42
-rw-r--r--ipc/glue/WindowsMessageLoop.cpp1205
-rw-r--r--ipc/glue/WindowsMessageLoop.h134
-rw-r--r--ipc/glue/components.conf22
-rw-r--r--ipc/glue/moz.build307
-rw-r--r--ipc/glue/nsIIPCSerializableInputStream.h104
-rw-r--r--ipc/glue/test/browser/browser.ini59
-rw-r--r--ipc/glue/test/browser/browser_audio_shutdown.ini6
-rw-r--r--ipc/glue/test/browser/browser_audio_telemetry_content.js32
-rw-r--r--ipc/glue/test/browser/browser_audio_telemetry_rdd.js29
-rw-r--r--ipc/glue/test/browser/browser_audio_telemetry_utility.js27
-rw-r--r--ipc/glue/test/browser/browser_child_hang.ini7
-rw-r--r--ipc/glue/test/browser/browser_child_hang.js37
-rw-r--r--ipc/glue/test/browser/browser_utility_audioDecodeCrash.js95
-rw-r--r--ipc/glue/test/browser/browser_utility_audio_shutdown.js76
-rw-r--r--ipc/glue/test/browser/browser_utility_crashReporter.js34
-rw-r--r--ipc/glue/test/browser/browser_utility_geolocation_crashed.js70
-rw-r--r--ipc/glue/test/browser/browser_utility_hard_kill.js13
-rw-r--r--ipc/glue/test/browser/browser_utility_hard_kill_delayed.js57
-rw-r--r--ipc/glue/test/browser/browser_utility_memoryReport.js76
-rw-r--r--ipc/glue/test/browser/browser_utility_multipleAudio.js73
-rw-r--r--ipc/glue/test/browser/browser_utility_profiler.js76
-rw-r--r--ipc/glue/test/browser/browser_utility_start_clean_shutdown.js9
-rw-r--r--ipc/glue/test/browser/head-telemetry.js205
-rw-r--r--ipc/glue/test/browser/head.js424
-rw-r--r--ipc/glue/test/browser/mochitest_audio_off.ini12
-rw-r--r--ipc/glue/test/browser/mochitest_audio_on.ini12
-rw-r--r--ipc/glue/test/browser/moz.build12
-rw-r--r--ipc/glue/test/browser/test_utility_audio_off.html44
-rw-r--r--ipc/glue/test/browser/test_utility_audio_on.html45
-rw-r--r--ipc/glue/test/gtest/TestAsyncBlockers.cpp166
-rw-r--r--ipc/glue/test/gtest/TestUtilityProcess.cpp155
-rw-r--r--ipc/glue/test/gtest/moz.build21
-rw-r--r--ipc/glue/test/utility_process_xpcom/UtilityProcessTest.cpp164
-rw-r--r--ipc/glue/test/utility_process_xpcom/UtilityProcessTest.h29
-rw-r--r--ipc/glue/test/utility_process_xpcom/components.conf15
-rw-r--r--ipc/glue/test/utility_process_xpcom/moz.build21
-rw-r--r--ipc/glue/test/utility_process_xpcom/nsIUtilityProcessTest.idl44
193 files changed, 35397 insertions, 0 deletions
diff --git a/ipc/glue/AsyncBlockers.h b/ipc/glue/AsyncBlockers.h
new file mode 100644
index 0000000000..16e7aa7528
--- /dev/null
+++ b/ipc/glue/AsyncBlockers.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* 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/. */
+
+#ifndef mozilla_ipc_AsyncBlockers_h
+#define mozilla_ipc_AsyncBlockers_h
+
+#include "mozilla/MozPromise.h"
+#include "mozilla/ThreadSafety.h"
+#include "nsTArray.h"
+
+// FIXME: when bug 1760855 is fixed, it should not be required anymore
+
+namespace mozilla::ipc {
+
+/**
+ * AsyncBlockers provide a simple registration service that allows to suspend
+ * completion of a particular task until all registered entries have been
+ * cleared. This can be used to implement a similar service to
+ * nsAsyncShutdownService in processes where it wouldn't normally be available.
+ * This class is thread-safe.
+ */
+class AsyncBlockers {
+ public:
+ AsyncBlockers()
+ : mLock("AsyncRegistrar"),
+ mPromise(new GenericPromise::Private(__func__)) {}
+ void Register(void* aBlocker) {
+ MutexAutoLock lock(mLock);
+ mBlockers.InsertElementSorted(aBlocker);
+ }
+ void Deregister(void* aBlocker) {
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(mBlockers.ContainsSorted(aBlocker));
+ MOZ_ALWAYS_TRUE(mBlockers.RemoveElementSorted(aBlocker));
+ MaybeResolve();
+ }
+ RefPtr<GenericPromise> WaitUntilClear(uint32_t aTimeOutInMs = 0) {
+ {
+ MutexAutoLock lock(mLock);
+ MaybeResolve();
+ }
+
+ if (aTimeOutInMs > 0) {
+ GetCurrentSerialEventTarget()->DelayedDispatch(
+ NS_NewRunnableFunction("AsyncBlockers::WaitUntilClear",
+ [promise = mPromise]() {
+ // The AsyncBlockers object may have been
+ // deleted by now and the object isn't
+ // refcounted (nor do we want it to be). We
+ // can unconditionally resolve the promise
+ // even it has already been resolved as
+ // MozPromise are thread-safe and will just
+ // ignore the action if already resolved.
+ promise->Resolve(true, __func__);
+ }),
+ aTimeOutInMs);
+ }
+
+ return mPromise;
+ }
+
+ virtual ~AsyncBlockers() { mPromise->Resolve(true, __func__); }
+
+ private:
+ void MaybeResolve() MOZ_REQUIRES(mLock) {
+ mLock.AssertCurrentThreadOwns();
+ if (!mBlockers.IsEmpty()) {
+ return;
+ }
+ mPromise->Resolve(true, __func__);
+ }
+ Mutex mLock;
+ nsTArray<void*> mBlockers MOZ_GUARDED_BY(mLock);
+ const RefPtr<GenericPromise::Private> mPromise;
+};
+
+} // namespace mozilla::ipc
+
+#endif // mozilla_ipc_AsyncBlockers_h
diff --git a/ipc/glue/BackgroundChild.h b/ipc/glue/BackgroundChild.h
new file mode 100644
index 0000000000..82a28c43d5
--- /dev/null
+++ b/ipc/glue/BackgroundChild.h
@@ -0,0 +1,101 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_backgroundchild_h__
+#define mozilla_ipc_backgroundchild_h__
+
+#include "mozilla/Attributes.h"
+
+class nsIEventTarget;
+
+namespace mozilla {
+namespace dom {
+
+class BlobImpl;
+class ContentChild;
+class ContentParent;
+class ContentProcess;
+
+} // namespace dom
+
+namespace net {
+
+class SocketProcessChild;
+class SocketProcessBridgeChild;
+
+} // namespace net
+
+namespace ipc {
+
+class PBackgroundChild;
+class PBackgroundStarterChild;
+
+// This class allows access to the PBackground protocol. PBackground allows
+// communication between any thread (in the parent or a child process) and a
+// single background thread in the parent process. Each PBackgroundChild
+// instance is tied to the thread on which it is created and must not be shared
+// across threads. Each PBackgroundChild is unique and valid as long as its
+// designated thread lives.
+//
+// Creation of PBackground is synchronous. GetOrCreateForCurrentThread will
+// create the actor if it doesn't exist yet. Thereafter (assuming success)
+// GetForCurrentThread() will return the same actor every time.
+//
+// GetOrCreateSocketActorForCurrentThread, which is like
+// GetOrCreateForCurrentThread, is used to get or create PBackground actor
+// between child process and socket process.
+//
+// CloseForCurrentThread() will close the current PBackground actor. Subsequent
+// calls to GetForCurrentThread will return null. CloseForCurrentThread() may
+// only be called exactly once for each thread-specific actor. Currently it is
+// illegal to call this before the PBackground actor has been created.
+//
+// The PBackgroundChild actor and all its sub-protocol actors will be
+// automatically destroyed when its designated thread completes.
+//
+// Init{Content,Socket,SocketBridge}Starter must be called on the main thread
+// with an actor bridging to the relevant target process type before these
+// methods can be used.
+class BackgroundChild final {
+ friend class mozilla::dom::ContentParent;
+ friend class mozilla::dom::ContentProcess;
+ friend class mozilla::net::SocketProcessChild;
+
+ public:
+ // See above.
+ static PBackgroundChild* GetForCurrentThread();
+
+ // See above.
+ static PBackgroundChild* GetOrCreateForCurrentThread();
+
+ // See above.
+ static PBackgroundChild* GetOrCreateSocketActorForCurrentThread();
+
+ // See above.
+ static PBackgroundChild* GetOrCreateForSocketParentBridgeForCurrentThread();
+
+ // See above.
+ static void CloseForCurrentThread();
+
+ // See above.
+ static void InitContentStarter(mozilla::dom::ContentChild* aContent);
+
+ // See above.
+ static void InitSocketStarter(mozilla::net::SocketProcessChild* aSocket);
+
+ // See above.
+ static void InitSocketBridgeStarter(
+ mozilla::net::SocketProcessBridgeChild* aSocketBridge);
+
+ private:
+ // Only called by this class's friends.
+ static void Startup();
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_backgroundchild_h__
diff --git a/ipc/glue/BackgroundChildImpl.cpp b/ipc/glue/BackgroundChildImpl.cpp
new file mode 100644
index 0000000000..536ef2a10e
--- /dev/null
+++ b/ipc/glue/BackgroundChildImpl.cpp
@@ -0,0 +1,540 @@
+/* -*- 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 "BackgroundChildImpl.h"
+
+#include "BroadcastChannelChild.h"
+#ifdef MOZ_WEBRTC
+# include "CamerasChild.h"
+#endif
+#include "mozilla/Assertions.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/dom/ClientManagerActors.h"
+#include "mozilla/dom/FileCreatorChild.h"
+#include "mozilla/dom/PBackgroundLSDatabaseChild.h"
+#include "mozilla/dom/PBackgroundLSObserverChild.h"
+#include "mozilla/dom/PBackgroundLSRequestChild.h"
+#include "mozilla/dom/PBackgroundLSSimpleRequestChild.h"
+#include "mozilla/dom/PBackgroundSDBConnectionChild.h"
+#include "mozilla/dom/PFileSystemRequestChild.h"
+#include "mozilla/dom/EndpointForReportChild.h"
+#include "mozilla/dom/PMediaTransportChild.h"
+#include "mozilla/dom/PVsync.h"
+#include "mozilla/dom/TemporaryIPCBlobChild.h"
+#include "mozilla/dom/cache/ActorUtils.h"
+#include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsChild.h"
+#include "mozilla/dom/indexedDB/ThreadLocal.h"
+#include "mozilla/dom/quota/PQuotaChild.h"
+#include "mozilla/dom/RemoteWorkerChild.h"
+#include "mozilla/dom/RemoteWorkerControllerChild.h"
+#include "mozilla/dom/RemoteWorkerServiceChild.h"
+#include "mozilla/dom/ServiceWorkerChild.h"
+#include "mozilla/dom/SharedWorkerChild.h"
+#include "mozilla/dom/StorageIPC.h"
+#include "mozilla/dom/MessagePortChild.h"
+#include "mozilla/dom/ServiceWorkerContainerChild.h"
+#include "mozilla/dom/ServiceWorkerManagerChild.h"
+#include "mozilla/ipc/PBackgroundTestChild.h"
+#include "mozilla/net/PUDPSocketChild.h"
+#include "mozilla/dom/network/UDPSocketChild.h"
+#include "mozilla/dom/WebAuthnTransactionChild.h"
+#include "mozilla/dom/MIDIPortChild.h"
+#include "mozilla/dom/MIDIManagerChild.h"
+#include "nsID.h"
+
+namespace {
+
+class TestChild final : public mozilla::ipc::PBackgroundTestChild {
+ friend class mozilla::ipc::BackgroundChildImpl;
+
+ nsCString mTestArg;
+
+ explicit TestChild(const nsACString& aTestArg) : mTestArg(aTestArg) {
+ MOZ_COUNT_CTOR(TestChild);
+ }
+
+ protected:
+ ~TestChild() override { MOZ_COUNT_DTOR(TestChild); }
+
+ public:
+ mozilla::ipc::IPCResult Recv__delete__(const nsACString& aTestArg) override;
+};
+
+} // namespace
+
+namespace mozilla::ipc {
+
+using mozilla::dom::UDPSocketChild;
+using mozilla::net::PUDPSocketChild;
+
+using mozilla::dom::PRemoteWorkerChild;
+using mozilla::dom::PServiceWorkerChild;
+using mozilla::dom::PServiceWorkerContainerChild;
+using mozilla::dom::PServiceWorkerRegistrationChild;
+using mozilla::dom::RemoteWorkerChild;
+using mozilla::dom::StorageDBChild;
+using mozilla::dom::cache::PCacheChild;
+using mozilla::dom::cache::PCacheStreamControlChild;
+
+using mozilla::dom::WebAuthnTransactionChild;
+
+using mozilla::dom::PMIDIManagerChild;
+using mozilla::dom::PMIDIPortChild;
+
+// -----------------------------------------------------------------------------
+// BackgroundChildImpl::ThreadLocal
+// -----------------------------------------------------------------------------
+
+BackgroundChildImpl::ThreadLocal::ThreadLocal() : mCurrentFileHandle(nullptr) {
+ // May happen on any thread!
+ MOZ_COUNT_CTOR(mozilla::ipc::BackgroundChildImpl::ThreadLocal);
+}
+
+BackgroundChildImpl::ThreadLocal::~ThreadLocal() {
+ // May happen on any thread!
+ MOZ_COUNT_DTOR(mozilla::ipc::BackgroundChildImpl::ThreadLocal);
+}
+
+// -----------------------------------------------------------------------------
+// BackgroundChildImpl
+// -----------------------------------------------------------------------------
+
+BackgroundChildImpl::BackgroundChildImpl() {
+ // May happen on any thread!
+ MOZ_COUNT_CTOR(mozilla::ipc::BackgroundChildImpl);
+}
+
+BackgroundChildImpl::~BackgroundChildImpl() {
+ // May happen on any thread!
+ MOZ_COUNT_DTOR(mozilla::ipc::BackgroundChildImpl);
+}
+
+void BackgroundChildImpl::ProcessingError(Result aCode, const char* aReason) {
+ // May happen on any thread!
+
+ nsAutoCString abortMessage;
+
+ switch (aCode) {
+ case MsgDropped:
+ return;
+
+#define HANDLE_CASE(_result) \
+ case _result: \
+ abortMessage.AssignLiteral(#_result); \
+ break
+
+ HANDLE_CASE(MsgNotKnown);
+ HANDLE_CASE(MsgNotAllowed);
+ HANDLE_CASE(MsgPayloadError);
+ HANDLE_CASE(MsgProcessingError);
+ HANDLE_CASE(MsgRouteError);
+ HANDLE_CASE(MsgValueError);
+
+#undef HANDLE_CASE
+
+ default:
+ MOZ_CRASH("Unknown error code!");
+ }
+
+ MOZ_CRASH_UNSAFE_PRINTF("%s: %s", abortMessage.get(), aReason);
+}
+
+void BackgroundChildImpl::ActorDestroy(ActorDestroyReason aWhy) {
+ // May happen on any thread!
+}
+
+PBackgroundTestChild* BackgroundChildImpl::AllocPBackgroundTestChild(
+ const nsACString& aTestArg) {
+ return new TestChild(aTestArg);
+}
+
+bool BackgroundChildImpl::DeallocPBackgroundTestChild(
+ PBackgroundTestChild* aActor) {
+ MOZ_ASSERT(aActor);
+
+ delete static_cast<TestChild*>(aActor);
+ return true;
+}
+
+BackgroundChildImpl::PBackgroundIndexedDBUtilsChild*
+BackgroundChildImpl::AllocPBackgroundIndexedDBUtilsChild() {
+ MOZ_CRASH(
+ "PBackgroundIndexedDBUtilsChild actors should be manually "
+ "constructed!");
+}
+
+bool BackgroundChildImpl::DeallocPBackgroundIndexedDBUtilsChild(
+ PBackgroundIndexedDBUtilsChild* aActor) {
+ MOZ_ASSERT(aActor);
+
+ delete aActor;
+ return true;
+}
+
+BackgroundChildImpl::PBackgroundSDBConnectionChild*
+BackgroundChildImpl::AllocPBackgroundSDBConnectionChild(
+ const PersistenceType& aPersistenceType,
+ const PrincipalInfo& aPrincipalInfo) {
+ MOZ_CRASH(
+ "PBackgroundSDBConnectionChild actor should be manually "
+ "constructed!");
+}
+
+bool BackgroundChildImpl::DeallocPBackgroundSDBConnectionChild(
+ PBackgroundSDBConnectionChild* aActor) {
+ MOZ_ASSERT(aActor);
+
+ delete aActor;
+ return true;
+}
+
+BackgroundChildImpl::PBackgroundLSDatabaseChild*
+BackgroundChildImpl::AllocPBackgroundLSDatabaseChild(
+ const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId,
+ const uint64_t& aDatastoreId) {
+ MOZ_CRASH("PBackgroundLSDatabaseChild actor should be manually constructed!");
+}
+
+bool BackgroundChildImpl::DeallocPBackgroundLSDatabaseChild(
+ PBackgroundLSDatabaseChild* aActor) {
+ MOZ_ASSERT(aActor);
+
+ delete aActor;
+ return true;
+}
+
+BackgroundChildImpl::PBackgroundLSObserverChild*
+BackgroundChildImpl::AllocPBackgroundLSObserverChild(
+ const uint64_t& aObserverId) {
+ MOZ_CRASH("PBackgroundLSObserverChild actor should be manually constructed!");
+}
+
+bool BackgroundChildImpl::DeallocPBackgroundLSObserverChild(
+ PBackgroundLSObserverChild* aActor) {
+ MOZ_ASSERT(aActor);
+
+ delete aActor;
+ return true;
+}
+
+BackgroundChildImpl::PBackgroundLSRequestChild*
+BackgroundChildImpl::AllocPBackgroundLSRequestChild(
+ const LSRequestParams& aParams) {
+ MOZ_CRASH("PBackgroundLSRequestChild actor should be manually constructed!");
+}
+
+bool BackgroundChildImpl::DeallocPBackgroundLSRequestChild(
+ PBackgroundLSRequestChild* aActor) {
+ MOZ_ASSERT(aActor);
+
+ delete aActor;
+ return true;
+}
+
+BackgroundChildImpl::PBackgroundLocalStorageCacheChild*
+BackgroundChildImpl::AllocPBackgroundLocalStorageCacheChild(
+ const PrincipalInfo& aPrincipalInfo, const nsACString& aOriginKey,
+ const uint32_t& aPrivateBrowsingId) {
+ MOZ_CRASH(
+ "PBackgroundLocalStorageChild actors should be manually "
+ "constructed!");
+}
+
+bool BackgroundChildImpl::DeallocPBackgroundLocalStorageCacheChild(
+ PBackgroundLocalStorageCacheChild* aActor) {
+ MOZ_ASSERT(aActor);
+
+ delete aActor;
+ return true;
+}
+
+BackgroundChildImpl::PBackgroundLSSimpleRequestChild*
+BackgroundChildImpl::AllocPBackgroundLSSimpleRequestChild(
+ const LSSimpleRequestParams& aParams) {
+ MOZ_CRASH(
+ "PBackgroundLSSimpleRequestChild actor should be manually "
+ "constructed!");
+}
+
+bool BackgroundChildImpl::DeallocPBackgroundLSSimpleRequestChild(
+ PBackgroundLSSimpleRequestChild* aActor) {
+ MOZ_ASSERT(aActor);
+
+ delete aActor;
+ return true;
+}
+
+BackgroundChildImpl::PBackgroundStorageChild*
+BackgroundChildImpl::AllocPBackgroundStorageChild(
+ const nsAString& aProfilePath, const uint32_t& aPrivateBrowsingId) {
+ MOZ_CRASH("PBackgroundStorageChild actors should be manually constructed!");
+}
+
+bool BackgroundChildImpl::DeallocPBackgroundStorageChild(
+ PBackgroundStorageChild* aActor) {
+ MOZ_ASSERT(aActor);
+
+ StorageDBChild* child = static_cast<StorageDBChild*>(aActor);
+ child->ReleaseIPDLReference();
+ return true;
+}
+
+already_AddRefed<PRemoteWorkerChild>
+BackgroundChildImpl::AllocPRemoteWorkerChild(const RemoteWorkerData& aData) {
+ return MakeAndAddRef<RemoteWorkerChild>(aData);
+}
+
+IPCResult BackgroundChildImpl::RecvPRemoteWorkerConstructor(
+ PRemoteWorkerChild* aActor, const RemoteWorkerData& aData) {
+ dom::RemoteWorkerChild* actor = static_cast<dom::RemoteWorkerChild*>(aActor);
+ actor->ExecWorker(aData);
+ return IPC_OK();
+}
+
+dom::PRemoteWorkerControllerChild*
+BackgroundChildImpl::AllocPRemoteWorkerControllerChild(
+ const dom::RemoteWorkerData& aRemoteWorkerData) {
+ MOZ_CRASH(
+ "PRemoteWorkerControllerChild actors must be manually constructed!");
+ return nullptr;
+}
+
+bool BackgroundChildImpl::DeallocPRemoteWorkerControllerChild(
+ dom::PRemoteWorkerControllerChild* aActor) {
+ MOZ_ASSERT(aActor);
+
+ RefPtr<dom::RemoteWorkerControllerChild> actor =
+ dont_AddRef(static_cast<dom::RemoteWorkerControllerChild*>(aActor));
+ return true;
+}
+
+dom::PSharedWorkerChild* BackgroundChildImpl::AllocPSharedWorkerChild(
+ const dom::RemoteWorkerData& aData, const uint64_t& aWindowID,
+ const dom::MessagePortIdentifier& aPortIdentifier) {
+ RefPtr<dom::SharedWorkerChild> agent = new dom::SharedWorkerChild();
+ return agent.forget().take();
+}
+
+bool BackgroundChildImpl::DeallocPSharedWorkerChild(
+ dom::PSharedWorkerChild* aActor) {
+ RefPtr<dom::SharedWorkerChild> actor =
+ dont_AddRef(static_cast<dom::SharedWorkerChild*>(aActor));
+ return true;
+}
+
+dom::PTemporaryIPCBlobChild*
+BackgroundChildImpl::AllocPTemporaryIPCBlobChild() {
+ MOZ_CRASH("This is not supposed to be called.");
+ return nullptr;
+}
+
+bool BackgroundChildImpl::DeallocPTemporaryIPCBlobChild(
+ dom::PTemporaryIPCBlobChild* aActor) {
+ RefPtr<dom::TemporaryIPCBlobChild> actor =
+ dont_AddRef(static_cast<dom::TemporaryIPCBlobChild*>(aActor));
+ return true;
+}
+
+dom::PFileCreatorChild* BackgroundChildImpl::AllocPFileCreatorChild(
+ const nsAString& aFullPath, const nsAString& aType, const nsAString& aName,
+ const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck,
+ const bool& aIsFromNsIFile) {
+ return new dom::FileCreatorChild();
+}
+
+bool BackgroundChildImpl::DeallocPFileCreatorChild(PFileCreatorChild* aActor) {
+ delete static_cast<dom::FileCreatorChild*>(aActor);
+ return true;
+}
+
+PUDPSocketChild* BackgroundChildImpl::AllocPUDPSocketChild(
+ const Maybe<PrincipalInfo>& aPrincipalInfo, const nsACString& aFilter) {
+ MOZ_CRASH("AllocPUDPSocket should not be called");
+ return nullptr;
+}
+
+bool BackgroundChildImpl::DeallocPUDPSocketChild(PUDPSocketChild* child) {
+ UDPSocketChild* p = static_cast<UDPSocketChild*>(child);
+ p->ReleaseIPDLReference();
+ return true;
+}
+
+// -----------------------------------------------------------------------------
+// BroadcastChannel API
+// -----------------------------------------------------------------------------
+
+dom::PBroadcastChannelChild* BackgroundChildImpl::AllocPBroadcastChannelChild(
+ const PrincipalInfo& aPrincipalInfo, const nsACString& aOrigin,
+ const nsAString& aChannel) {
+ RefPtr<dom::BroadcastChannelChild> agent = new dom::BroadcastChannelChild();
+ return agent.forget().take();
+}
+
+bool BackgroundChildImpl::DeallocPBroadcastChannelChild(
+ PBroadcastChannelChild* aActor) {
+ RefPtr<dom::BroadcastChannelChild> child =
+ dont_AddRef(static_cast<dom::BroadcastChannelChild*>(aActor));
+ MOZ_ASSERT(child);
+ return true;
+}
+
+camera::PCamerasChild* BackgroundChildImpl::AllocPCamerasChild() {
+#ifdef MOZ_WEBRTC
+ RefPtr<camera::CamerasChild> agent = new camera::CamerasChild();
+ return agent.forget().take();
+#else
+ return nullptr;
+#endif
+}
+
+bool BackgroundChildImpl::DeallocPCamerasChild(camera::PCamerasChild* aActor) {
+#ifdef MOZ_WEBRTC
+ RefPtr<camera::CamerasChild> child =
+ dont_AddRef(static_cast<camera::CamerasChild*>(aActor));
+ MOZ_ASSERT(aActor);
+ camera::Shutdown();
+#endif
+ return true;
+}
+
+// -----------------------------------------------------------------------------
+// ServiceWorkerManager
+// -----------------------------------------------------------------------------
+
+dom::PServiceWorkerManagerChild*
+BackgroundChildImpl::AllocPServiceWorkerManagerChild() {
+ RefPtr<dom::ServiceWorkerManagerChild> agent =
+ new dom::ServiceWorkerManagerChild();
+ return agent.forget().take();
+}
+
+bool BackgroundChildImpl::DeallocPServiceWorkerManagerChild(
+ PServiceWorkerManagerChild* aActor) {
+ RefPtr<dom::ServiceWorkerManagerChild> child =
+ dont_AddRef(static_cast<dom::ServiceWorkerManagerChild*>(aActor));
+ MOZ_ASSERT(child);
+ return true;
+}
+
+// -----------------------------------------------------------------------------
+// Cache API
+// -----------------------------------------------------------------------------
+
+already_AddRefed<PCacheChild> BackgroundChildImpl::AllocPCacheChild() {
+ return dom::cache::AllocPCacheChild();
+}
+
+already_AddRefed<PCacheStreamControlChild>
+BackgroundChildImpl::AllocPCacheStreamControlChild() {
+ return dom::cache::AllocPCacheStreamControlChild();
+}
+
+// -----------------------------------------------------------------------------
+// MessageChannel/MessagePort API
+// -----------------------------------------------------------------------------
+
+dom::PMessagePortChild* BackgroundChildImpl::AllocPMessagePortChild(
+ const nsID& aUUID, const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID) {
+ RefPtr<dom::MessagePortChild> agent = new dom::MessagePortChild();
+ return agent.forget().take();
+}
+
+bool BackgroundChildImpl::DeallocPMessagePortChild(PMessagePortChild* aActor) {
+ RefPtr<dom::MessagePortChild> child =
+ dont_AddRef(static_cast<dom::MessagePortChild*>(aActor));
+ MOZ_ASSERT(child);
+ return true;
+}
+
+BackgroundChildImpl::PQuotaChild* BackgroundChildImpl::AllocPQuotaChild() {
+ MOZ_CRASH("PQuotaChild actor should be manually constructed!");
+}
+
+bool BackgroundChildImpl::DeallocPQuotaChild(PQuotaChild* aActor) {
+ MOZ_ASSERT(aActor);
+ delete aActor;
+ return true;
+}
+
+mozilla::dom::PClientManagerChild*
+BackgroundChildImpl::AllocPClientManagerChild() {
+ return mozilla::dom::AllocClientManagerChild();
+}
+
+bool BackgroundChildImpl::DeallocPClientManagerChild(
+ mozilla::dom::PClientManagerChild* aActor) {
+ return mozilla::dom::DeallocClientManagerChild(aActor);
+}
+
+dom::PWebAuthnTransactionChild*
+BackgroundChildImpl::AllocPWebAuthnTransactionChild() {
+ MOZ_CRASH("PWebAuthnTransaction actor should be manually constructed!");
+ return nullptr;
+}
+
+bool BackgroundChildImpl::DeallocPWebAuthnTransactionChild(
+ PWebAuthnTransactionChild* aActor) {
+ MOZ_ASSERT(aActor);
+ RefPtr<dom::WebAuthnTransactionChild> child =
+ dont_AddRef(static_cast<dom::WebAuthnTransactionChild*>(aActor));
+ return true;
+}
+
+already_AddRefed<PServiceWorkerChild>
+BackgroundChildImpl::AllocPServiceWorkerChild(
+ const IPCServiceWorkerDescriptor&) {
+ MOZ_CRASH("Shouldn't be called.");
+ return {};
+}
+
+already_AddRefed<PServiceWorkerContainerChild>
+BackgroundChildImpl::AllocPServiceWorkerContainerChild() {
+ return mozilla::dom::ServiceWorkerContainerChild::Create();
+}
+
+already_AddRefed<PServiceWorkerRegistrationChild>
+BackgroundChildImpl::AllocPServiceWorkerRegistrationChild(
+ const IPCServiceWorkerRegistrationDescriptor&) {
+ MOZ_CRASH("Shouldn't be called.");
+ return {};
+}
+
+dom::PEndpointForReportChild* BackgroundChildImpl::AllocPEndpointForReportChild(
+ const nsAString& aGroupName, const PrincipalInfo& aPrincipalInfo) {
+ return new dom::EndpointForReportChild();
+}
+
+bool BackgroundChildImpl::DeallocPEndpointForReportChild(
+ PEndpointForReportChild* aActor) {
+ MOZ_ASSERT(aActor);
+ delete static_cast<dom::EndpointForReportChild*>(aActor);
+ return true;
+}
+
+dom::PMediaTransportChild* BackgroundChildImpl::AllocPMediaTransportChild() {
+ // We don't allocate here: MediaTransportHandlerIPC is in charge of that,
+ // so we don't need to know the implementation particulars here.
+ MOZ_ASSERT_UNREACHABLE(
+ "The only thing that ought to be creating a PMediaTransportChild is "
+ "MediaTransportHandlerIPC!");
+ return nullptr;
+}
+
+bool BackgroundChildImpl::DeallocPMediaTransportChild(
+ dom::PMediaTransportChild* aActor) {
+ delete aActor;
+ return true;
+}
+
+} // namespace mozilla::ipc
+
+mozilla::ipc::IPCResult TestChild::Recv__delete__(const nsACString& aTestArg) {
+ MOZ_RELEASE_ASSERT(aTestArg == mTestArg,
+ "BackgroundTest message was corrupted!");
+
+ return IPC_OK();
+}
diff --git a/ipc/glue/BackgroundChildImpl.h b/ipc/glue/BackgroundChildImpl.h
new file mode 100644
index 0000000000..28d3dbc14c
--- /dev/null
+++ b/ipc/glue/BackgroundChildImpl.h
@@ -0,0 +1,227 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_backgroundchildimpl_h__
+#define mozilla_ipc_backgroundchildimpl_h__
+
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace dom {
+
+class IDBFileHandle;
+
+namespace indexedDB {
+
+class ThreadLocal;
+
+} // namespace indexedDB
+} // namespace dom
+
+namespace ipc {
+
+// Instances of this class should never be created directly. This class is meant
+// to be inherited in BackgroundImpl.
+class BackgroundChildImpl : public PBackgroundChild {
+ public:
+ class ThreadLocal;
+
+ // Get the ThreadLocal for the current thread if
+ // BackgroundChild::GetOrCreateForCurrentThread() has been called and true was
+ // returned (e.g. a valid PBackgroundChild actor has been created or is in the
+ // process of being created). Otherwise this function returns null.
+ // This functions is implemented in BackgroundImpl.cpp.
+ static ThreadLocal* GetThreadLocalForCurrentThread();
+
+ protected:
+ BackgroundChildImpl();
+ virtual ~BackgroundChildImpl();
+
+ virtual void ProcessingError(Result aCode, const char* aReason) override;
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual PBackgroundTestChild* AllocPBackgroundTestChild(
+ const nsACString& aTestArg) override;
+
+ virtual bool DeallocPBackgroundTestChild(
+ PBackgroundTestChild* aActor) override;
+
+ virtual PBackgroundIndexedDBUtilsChild* AllocPBackgroundIndexedDBUtilsChild()
+ override;
+
+ virtual bool DeallocPBackgroundIndexedDBUtilsChild(
+ PBackgroundIndexedDBUtilsChild* aActor) override;
+
+ virtual PBackgroundSDBConnectionChild* AllocPBackgroundSDBConnectionChild(
+ const PersistenceType& aPersistenceType,
+ const PrincipalInfo& aPrincipalInfo) override;
+
+ virtual bool DeallocPBackgroundSDBConnectionChild(
+ PBackgroundSDBConnectionChild* aActor) override;
+
+ virtual PBackgroundLSDatabaseChild* AllocPBackgroundLSDatabaseChild(
+ const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId,
+ const uint64_t& aDatastoreId) override;
+
+ virtual bool DeallocPBackgroundLSDatabaseChild(
+ PBackgroundLSDatabaseChild* aActor) override;
+
+ virtual PBackgroundLSObserverChild* AllocPBackgroundLSObserverChild(
+ const uint64_t& aObserverId) override;
+
+ virtual bool DeallocPBackgroundLSObserverChild(
+ PBackgroundLSObserverChild* aActor) override;
+
+ virtual PBackgroundLSRequestChild* AllocPBackgroundLSRequestChild(
+ const LSRequestParams& aParams) override;
+
+ virtual bool DeallocPBackgroundLSRequestChild(
+ PBackgroundLSRequestChild* aActor) override;
+
+ virtual PBackgroundLSSimpleRequestChild* AllocPBackgroundLSSimpleRequestChild(
+ const LSSimpleRequestParams& aParams) override;
+
+ virtual bool DeallocPBackgroundLSSimpleRequestChild(
+ PBackgroundLSSimpleRequestChild* aActor) override;
+
+ virtual PBackgroundLocalStorageCacheChild*
+ AllocPBackgroundLocalStorageCacheChild(
+ const PrincipalInfo& aPrincipalInfo, const nsACString& aOriginKey,
+ const uint32_t& aPrivateBrowsingId) override;
+
+ virtual bool DeallocPBackgroundLocalStorageCacheChild(
+ PBackgroundLocalStorageCacheChild* aActor) override;
+
+ virtual PBackgroundStorageChild* AllocPBackgroundStorageChild(
+ const nsAString& aProfilePath,
+ const uint32_t& aPrivateBrowsingId) override;
+
+ virtual bool DeallocPBackgroundStorageChild(
+ PBackgroundStorageChild* aActor) override;
+
+ virtual PTemporaryIPCBlobChild* AllocPTemporaryIPCBlobChild() override;
+
+ virtual bool DeallocPTemporaryIPCBlobChild(
+ PTemporaryIPCBlobChild* aActor) override;
+
+ virtual PFileCreatorChild* AllocPFileCreatorChild(
+ const nsAString& aFullPath, const nsAString& aType,
+ const nsAString& aName, const Maybe<int64_t>& aLastModified,
+ const bool& aExistenceCheck, const bool& aIsFromNsIFile) override;
+
+ virtual bool DeallocPFileCreatorChild(PFileCreatorChild* aActor) override;
+
+ already_AddRefed<mozilla::dom::PRemoteWorkerChild> AllocPRemoteWorkerChild(
+ const RemoteWorkerData& aData) override;
+
+ virtual mozilla::ipc::IPCResult RecvPRemoteWorkerConstructor(
+ PRemoteWorkerChild* aActor, const RemoteWorkerData& aData) override;
+
+ virtual mozilla::dom::PRemoteWorkerControllerChild*
+ AllocPRemoteWorkerControllerChild(
+ const mozilla::dom::RemoteWorkerData& aRemoteWorkerData) override;
+
+ virtual bool DeallocPRemoteWorkerControllerChild(
+ mozilla::dom::PRemoteWorkerControllerChild* aActor) override;
+
+ virtual mozilla::dom::PSharedWorkerChild* AllocPSharedWorkerChild(
+ const mozilla::dom::RemoteWorkerData& aData, const uint64_t& aWindowID,
+ const mozilla::dom::MessagePortIdentifier& aPortIdentifier) override;
+
+ virtual bool DeallocPSharedWorkerChild(
+ mozilla::dom::PSharedWorkerChild* aActor) override;
+
+ virtual PCamerasChild* AllocPCamerasChild() override;
+
+ virtual bool DeallocPCamerasChild(PCamerasChild* aActor) override;
+
+ virtual PUDPSocketChild* AllocPUDPSocketChild(
+ const Maybe<PrincipalInfo>& aPrincipalInfo,
+ const nsACString& aFilter) override;
+ virtual bool DeallocPUDPSocketChild(PUDPSocketChild* aActor) override;
+
+ virtual PBroadcastChannelChild* AllocPBroadcastChannelChild(
+ const PrincipalInfo& aPrincipalInfo, const nsACString& aOrigin,
+ const nsAString& aChannel) override;
+
+ virtual bool DeallocPBroadcastChannelChild(
+ PBroadcastChannelChild* aActor) override;
+
+ virtual PServiceWorkerManagerChild* AllocPServiceWorkerManagerChild()
+ override;
+
+ virtual bool DeallocPServiceWorkerManagerChild(
+ PServiceWorkerManagerChild* aActor) override;
+
+ virtual already_AddRefed<dom::cache::PCacheChild> AllocPCacheChild() override;
+
+ virtual already_AddRefed<dom::cache::PCacheStreamControlChild>
+ AllocPCacheStreamControlChild() override;
+
+ virtual PMessagePortChild* AllocPMessagePortChild(
+ const nsID& aUUID, const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID) override;
+
+ virtual bool DeallocPMessagePortChild(PMessagePortChild* aActor) override;
+
+ virtual PQuotaChild* AllocPQuotaChild() override;
+
+ virtual bool DeallocPQuotaChild(PQuotaChild* aActor) override;
+
+ virtual PClientManagerChild* AllocPClientManagerChild() override;
+
+ virtual bool DeallocPClientManagerChild(PClientManagerChild* aActor) override;
+
+ virtual PWebAuthnTransactionChild* AllocPWebAuthnTransactionChild() override;
+
+ virtual bool DeallocPWebAuthnTransactionChild(
+ PWebAuthnTransactionChild* aActor) override;
+
+ already_AddRefed<PServiceWorkerChild> AllocPServiceWorkerChild(
+ const IPCServiceWorkerDescriptor&);
+
+ already_AddRefed<PServiceWorkerContainerChild>
+ AllocPServiceWorkerContainerChild();
+
+ already_AddRefed<PServiceWorkerRegistrationChild>
+ AllocPServiceWorkerRegistrationChild(
+ const IPCServiceWorkerRegistrationDescriptor&);
+
+ virtual PEndpointForReportChild* AllocPEndpointForReportChild(
+ const nsAString& aGroupName,
+ const PrincipalInfo& aPrincipalInfo) override;
+
+ virtual bool DeallocPEndpointForReportChild(
+ PEndpointForReportChild* aActor) override;
+
+ virtual dom::PMediaTransportChild* AllocPMediaTransportChild() override;
+
+ virtual bool DeallocPMediaTransportChild(
+ dom::PMediaTransportChild* aActor) override;
+};
+
+class BackgroundChildImpl::ThreadLocal final {
+ friend class mozilla::DefaultDelete<ThreadLocal>;
+
+ public:
+ mozilla::UniquePtr<mozilla::dom::indexedDB::ThreadLocal>
+ mIndexedDBThreadLocal;
+ mozilla::dom::IDBFileHandle* mCurrentFileHandle;
+
+ public:
+ ThreadLocal();
+
+ private:
+ // Only destroyed by UniquePtr<ThreadLocal>.
+ ~ThreadLocal();
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_backgroundchildimpl_h__
diff --git a/ipc/glue/BackgroundImpl.cpp b/ipc/glue/BackgroundImpl.cpp
new file mode 100644
index 0000000000..420f047f42
--- /dev/null
+++ b/ipc/glue/BackgroundImpl.cpp
@@ -0,0 +1,1306 @@
+/* -*- 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 "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/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/File.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 "mozilla/net/SocketProcessChild.h"
+#include "mozilla/net/SocketProcessBridgeChild.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"
+
+#include <functional>
+
+#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 {
+
+class ChildImpl;
+
+// -----------------------------------------------------------------------------
+// Utility Functions
+// -----------------------------------------------------------------------------
+
+void AssertIsInMainOrSocketProcess() {
+ MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess());
+}
+
+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) {
+ AssertIsInMainOrSocketProcess();
+ 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;
+
+ // This exists so that that [Assert]IsOnBackgroundThread() can continue to
+ // work during shutdown.
+ static Atomic<PRThread*> sBackgroundPRThread;
+
+ // 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 or socket 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 bool IsOnBackgroundThread() {
+ return PR_GetCurrentThread() == sBackgroundPRThread;
+ }
+
+ 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 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 4 cases below.
+ // 1. Within the parent process.
+ // 2. Between parent process and content process.
+ // 3. Between socket process and content process.
+ // 4. Between parent process and socket process.
+ // |aContent| should be not null for case 2. For cases 1, 3 and 4, it's null.
+ explicit ParentImpl(ThreadsafeContentParentHandle* aContent,
+ bool aIsOtherProcessActor)
+ : mContent(aContent),
+ mLiveActorArray(nullptr),
+ mIsOtherProcessActor(aIsOtherProcessActor),
+ mActorDestroyed(false) {
+ MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess());
+ MOZ_ASSERT_IF(!aIsOtherProcessActor, XRE_IsParentProcess());
+ }
+
+ ~ParentImpl() {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnMainThread();
+ }
+
+ void MainThreadActorDestroy();
+
+ void SetLiveActorArray(nsTArray<IToplevelProtocol*>* aLiveActorArray) {
+ AssertIsInMainOrSocketProcess();
+ 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()
+#ifdef DEBUG
+ : mClosed(false)
+#endif
+ {
+ }
+
+ RefPtr<ChildImpl> mActor;
+ UniquePtr<BackgroundChildImpl::ThreadLocal> mConsumerThreadLocal;
+#ifdef DEBUG
+ bool mClosed;
+#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->OtherPid(), base::GetCurrentProcId(), &parent, &child));
+ MOZ_ALWAYS_TRUE(aActor->SendInitBackground(std::move(parent)));
+
+ InitStarter(std::move(child));
+ }
+
+ void InitStarter(Endpoint<PBackgroundStarterChild>&& aEndpoint) {
+ AssertIsOnMainThread();
+
+ base::ProcessId otherPid = aEndpoint.OtherPid();
+
+ nsCOMPtr<nsISerialEventTarget> taskQueue;
+ MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue(
+ "PBackgroundStarter Queue", getter_AddRefs(taskQueue)));
+
+ RefPtr<BackgroundStarterChild> starter =
+ new BackgroundStarterChild(otherPid, 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->mOtherPid, base::GetCurrentProcId(), &parent, &child);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create top level actor!");
+ return nullptr;
+ }
+
+ RefPtr<ChildImpl> strongActor = new ChildImpl();
+ if (!child.Bind(strongActor)) {
+ CRASH_IN_CHILD_PROCESS("Failed to bind ChildImpl!");
+ return nullptr;
+ }
+ strongActor->SetActorAlive();
+ threadLocalInfo->mActor = strongActor;
+
+ // 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 strongActor;
+ }
+
+ 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;
+
+ // For PBackground between socket and content process.
+ static ThreadInfoWrapper sSocketAndContentProcessThreadInfo;
+
+ // For PBackground between socket and parent process.
+ static ThreadInfoWrapper sSocketAndParentProcessThreadInfo;
+
+ // 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
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(ChildImpl, override)
+
+ private:
+ // Forwarded from BackgroundChild.
+ static void Startup();
+
+ // Forwarded from BackgroundChild.
+ static PBackgroundChild* GetForCurrentThread();
+
+ // Forwarded from BackgroundChild.
+ static PBackgroundChild* GetOrCreateForCurrentThread();
+
+ // Forwarded from BackgroundChild.
+ static PBackgroundChild* GetOrCreateSocketActorForCurrentThread();
+
+ // Forwarded from BackgroundChild.
+ static PBackgroundChild* GetOrCreateForSocketParentBridgeForCurrentThread();
+
+ static void CloseForCurrentThread();
+
+ // Forwarded from BackgroundChildImpl.
+ static BackgroundChildImpl::ThreadLocal* GetThreadLocalForCurrentThread();
+
+ // Forwarded from BackgroundChild.
+ static void InitContentStarter(mozilla::dom::ContentChild* aContent);
+
+ // Forwarded from BackgroundChild.
+ static void InitSocketStarter(mozilla::net::SocketProcessChild* aSocket);
+
+ // Forwarded from BackgroundChild.
+ static void InitSocketBridgeStarter(
+ mozilla::net::SocketProcessBridgeChild* aSocketBridge);
+
+ 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
+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
+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
+PBackgroundChild* BackgroundChild::GetOrCreateSocketActorForCurrentThread() {
+ return ChildImpl::GetOrCreateSocketActorForCurrentThread();
+}
+
+// static
+PBackgroundChild*
+BackgroundChild::GetOrCreateForSocketParentBridgeForCurrentThread() {
+ return ChildImpl::GetOrCreateForSocketParentBridgeForCurrentThread();
+}
+
+// static
+void BackgroundChild::CloseForCurrentThread() {
+ ChildImpl::CloseForCurrentThread();
+}
+
+// static
+void BackgroundChild::InitContentStarter(ContentChild* aContent) {
+ ChildImpl::InitContentStarter(aContent);
+}
+
+// static
+void BackgroundChild::InitSocketStarter(net::SocketProcessChild* aSocket) {
+ ChildImpl::InitSocketStarter(aSocket);
+}
+
+// static
+void BackgroundChild::InitSocketBridgeStarter(
+ net::SocketProcessBridgeChild* aSocketBridge) {
+ ChildImpl::InitSocketBridgeStarter(aSocketBridge);
+}
+
+// -----------------------------------------------------------------------------
+// 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<PRThread*> ParentImpl::sBackgroundPRThread;
+
+Atomic<uint64_t> ParentImpl::sLiveActorCount;
+
+bool ParentImpl::sShutdownObserverRegistered = false;
+
+bool ParentImpl::sShutdownHasStarted = false;
+
+// -----------------------------------------------------------------------------
+// ChildImpl Static Members
+// -----------------------------------------------------------------------------
+
+ChildImpl::ThreadInfoWrapper ChildImpl::sParentAndContentProcessThreadInfo;
+
+ChildImpl::ThreadInfoWrapper ChildImpl::sSocketAndContentProcessThreadInfo;
+
+ChildImpl::ThreadInfoWrapper ChildImpl::sSocketAndParentProcessThreadInfo;
+
+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
+bool ParentImpl::AllocStarter(ContentParent* aContent,
+ Endpoint<PBackgroundStarterParent>&& aEndpoint,
+ bool aCrossProcess) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(aEndpoint.IsValid());
+
+ if (!sBackgroundThread && !CreateBackgroundThread()) {
+ NS_WARNING("Failed to create background thread!");
+ return false;
+ }
+
+ sLiveActorCount++;
+
+ RefPtr<BackgroundStarterParent> actor = new 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() {
+ AssertIsInMainOrSocketProcess();
+ 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", []() {
+ DebugOnly<PRThread*> oldBackgroundThread =
+ sBackgroundPRThread.exchange(PR_GetCurrentThread());
+
+ MOZ_ASSERT_IF(oldBackgroundThread,
+ PR_GetCurrentThread() != oldBackgroundThread);
+ })))) {
+ 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() {
+ AssertIsInMainOrSocketProcess();
+ 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"));
+
+ SpinEventLoopUntil("ParentImpl::ShutdownBackgroundThread"_ns,
+ [&]() { return !sLiveActorCount; });
+
+ MOZ_ASSERT(liveActors->IsEmpty());
+
+ MOZ_ALWAYS_SUCCEEDS(shutdownTimer->Cancel());
+ }
+
+ // Dispatch this runnable to unregister the PR thread from the profiler.
+ MOZ_ALWAYS_SUCCEEDS(thread->Dispatch(NS_NewRunnableFunction(
+ "Background::ParentImpl::ShutdownBackgroundThreadRunnable", []() {
+ // It is possible that another background thread was created while
+ // this thread was shutting down. In that case we can't assert
+ // anything about sBackgroundPRThread and we should not modify it
+ // here.
+ sBackgroundPRThread.compareExchange(PR_GetCurrentThread(), nullptr);
+ })));
+
+ MOZ_ALWAYS_SUCCEEDS(thread->Shutdown());
+ }
+}
+
+// static
+void ParentImpl::ShutdownTimerCallback(nsITimer* aTimer, void* aClosure) {
+ AssertIsInMainOrSocketProcess();
+ 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!
+
+ AssertIsInMainOrSocketProcess();
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
+ NewNonOwningRunnableMethod("ParentImpl::MainThreadActorDestroy", this,
+ &ParentImpl::MainThreadActorDestroy)));
+}
+
+void ParentImpl::MainThreadActorDestroy() {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnMainThread();
+ MOZ_ASSERT_IF(!mIsOtherProcessActor, !mContent);
+
+ MOZ_ASSERT(sLiveActorCount);
+ sLiveActorCount--;
+
+ // This may be the last reference!
+ Release();
+}
+
+void ParentImpl::ActorDestroy(ActorDestroyReason aWhy) {
+ AssertIsInMainOrSocketProcess();
+ 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) {
+ AssertIsInMainOrSocketProcess();
+ 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();
+ AssertIsInMainOrSocketProcess();
+ MOZ_ASSERT_IF(!mCrossProcess, !mContent);
+ MOZ_ASSERT_IF(!mCrossProcess, XRE_IsParentProcess());
+}
+
+void BackgroundStarterParent::SetLiveActorArray(
+ nsTArray<IToplevelProtocol*>* aLiveActorArray) {
+ AssertIsInMainOrSocketProcess();
+ 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();
+ sSocketAndContentProcessThreadInfo.Startup();
+ sSocketAndParentProcessThreadInfo.Startup();
+
+ 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(
+ base::GetCurrentProcId(), base::GetCurrentProcId(), &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();
+ sSocketAndContentProcessThreadInfo.Shutdown();
+ sSocketAndParentProcessThreadInfo.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 */
+PBackgroundChild* ChildImpl::GetOrCreateSocketActorForCurrentThread() {
+ return sSocketAndContentProcessThreadInfo.GetOrCreateForCurrentThread();
+}
+
+/* static */
+PBackgroundChild*
+ChildImpl::GetOrCreateForSocketParentBridgeForCurrentThread() {
+ return sSocketAndParentProcessThreadInfo.GetOrCreateForCurrentThread();
+}
+
+// static
+void ChildImpl::CloseForCurrentThread() {
+ MOZ_ASSERT(!NS_IsMainThread(),
+ "PBackground for the main thread should be shut down via "
+ "ChildImpl::Shutdown().");
+
+ sParentAndContentProcessThreadInfo.CloseForCurrentThread();
+ sSocketAndContentProcessThreadInfo.CloseForCurrentThread();
+ sSocketAndParentProcessThreadInfo.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::InitSocketStarter(mozilla::net::SocketProcessChild* aSocket) {
+ sSocketAndParentProcessThreadInfo.InitStarter(aSocket);
+}
+
+// static
+void ChildImpl::InitSocketBridgeStarter(
+ mozilla::net::SocketProcessBridgeChild* aSocketBridge) {
+ sSocketAndContentProcessThreadInfo.InitStarter(aSocketBridge);
+}
+
+// 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;
+}
diff --git a/ipc/glue/BackgroundParent.h b/ipc/glue/BackgroundParent.h
new file mode 100644
index 0000000000..650cf2ea29
--- /dev/null
+++ b/ipc/glue/BackgroundParent.h
@@ -0,0 +1,113 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_backgroundparent_h__
+#define mozilla_ipc_backgroundparent_h__
+
+#include "base/process.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/ContentParent.h"
+#include "nsStringFwd.h"
+#include "nsTArrayForwardDeclare.h"
+
+#ifdef DEBUG
+# include "nsXULAppAPI.h"
+#endif
+
+template <class>
+struct already_AddRefed;
+
+namespace mozilla {
+
+namespace net {
+
+class SocketProcessBridgeParent;
+class SocketProcessParent;
+
+} // namespace net
+
+namespace dom {
+
+class BlobImpl;
+class ContentParent;
+
+} // namespace dom
+
+namespace ipc {
+
+class BackgroundStarterParent;
+class PBackgroundParent;
+class PBackgroundStarterParent;
+
+template <class PFooSide>
+class Endpoint;
+
+// This class is not designed for public consumption beyond the few static
+// member functions.
+class BackgroundParent final {
+ friend class mozilla::ipc::BackgroundStarterParent;
+ friend class mozilla::dom::ContentParent;
+ friend class mozilla::net::SocketProcessBridgeParent;
+ friend class mozilla::net::SocketProcessParent;
+
+ using ProcessId = base::ProcessId;
+ using BlobImpl = mozilla::dom::BlobImpl;
+ using ContentParent = mozilla::dom::ContentParent;
+ using ThreadsafeContentParentHandle =
+ mozilla::dom::ThreadsafeContentParentHandle;
+
+ public:
+ // This function allows the caller to determine if the given parent actor
+ // corresponds to a child actor from another process or a child actor from a
+ // different thread in the same process.
+ // This function may only be called on the background thread.
+ static bool IsOtherProcessActor(PBackgroundParent* aBackgroundActor);
+
+ // This function returns a handle to the ContentParent associated with the
+ // parent actor if the parent actor corresponds to a child actor from another
+ // content process. If the parent actor corresponds to a child actor from a
+ // different thread in the same process then this function returns null.
+ //
+ // This function may only be called on the background thread.
+ static ThreadsafeContentParentHandle* GetContentParentHandle(
+ PBackgroundParent* aBackgroundActor);
+
+ static uint64_t GetChildID(PBackgroundParent* aBackgroundActor);
+
+ private:
+ // Only called by ContentParent for cross-process actors.
+ static bool AllocStarter(ContentParent* aContent,
+ Endpoint<PBackgroundStarterParent>&& aEndpoint);
+
+ // Called by SocketProcessBridgeParent and SocketProcessParent for
+ // cross-process actors.
+ static bool AllocStarter(Endpoint<PBackgroundStarterParent>&& aEndpoint);
+};
+
+// Implemented in BackgroundImpl.cpp.
+bool IsOnBackgroundThread();
+
+#ifdef DEBUG
+
+// Implemented in BackgroundImpl.cpp.
+void AssertIsOnBackgroundThread();
+
+#else
+
+inline void AssertIsOnBackgroundThread() {}
+
+#endif // DEBUG
+
+inline void AssertIsInMainProcess() { MOZ_ASSERT(XRE_IsParentProcess()); }
+
+inline void AssertIsInMainOrSocketProcess() {
+ MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess());
+}
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_backgroundparent_h__
diff --git a/ipc/glue/BackgroundParentImpl.cpp b/ipc/glue/BackgroundParentImpl.cpp
new file mode 100644
index 0000000000..9baa44488a
--- /dev/null
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -0,0 +1,1893 @@
+/* -*- 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 "BackgroundParentImpl.h"
+
+#include "BroadcastChannelParent.h"
+#ifdef MOZ_WEBRTC
+# include "CamerasParent.h"
+#endif
+#include "mozilla/Assertions.h"
+#include "mozilla/RDDProcessManager.h"
+#include "mozilla/ipc/UtilityProcessManager.h"
+#include "mozilla/RemoteDecodeUtils.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/BackgroundSessionStorageServiceParent.h"
+#include "mozilla/dom/ClientManagerActors.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/EndpointForReportParent.h"
+#include "mozilla/dom/FetchParent.h"
+#include "mozilla/dom/FileCreatorParent.h"
+#include "mozilla/dom/FileSystemManagerParentFactory.h"
+#include "mozilla/dom/FileSystemRequestParent.h"
+#include "mozilla/dom/GamepadEventChannelParent.h"
+#include "mozilla/dom/GamepadTestChannelParent.h"
+#include "mozilla/dom/MIDIManagerParent.h"
+#include "mozilla/dom/MIDIPlatformService.h"
+#include "mozilla/dom/MIDIPortParent.h"
+#include "mozilla/dom/MediaTransportParent.h"
+#include "mozilla/dom/MessagePortParent.h"
+#include "mozilla/dom/PGamepadEventChannelParent.h"
+#include "mozilla/dom/PGamepadTestChannelParent.h"
+#include "mozilla/dom/RemoteWorkerControllerParent.h"
+#include "mozilla/dom/RemoteWorkerServiceParent.h"
+#include "mozilla/dom/ReportingHeader.h"
+#include "mozilla/dom/ServiceWorkerActors.h"
+#include "mozilla/dom/ServiceWorkerContainerParent.h"
+#include "mozilla/dom/ServiceWorkerManagerParent.h"
+#include "mozilla/dom/ServiceWorkerParent.h"
+#include "mozilla/dom/ServiceWorkerRegistrar.h"
+#include "mozilla/dom/ServiceWorkerRegistrationParent.h"
+#include "mozilla/dom/SessionStorageManager.h"
+#include "mozilla/dom/SharedWorkerParent.h"
+#include "mozilla/dom/StorageActivityService.h"
+#include "mozilla/dom/StorageIPC.h"
+#include "mozilla/dom/TemporaryIPCBlobParent.h"
+#include "mozilla/dom/WebAuthnTransactionParent.h"
+#include "mozilla/dom/WebTransportParent.h"
+#include "mozilla/dom/cache/ActorUtils.h"
+#include "mozilla/dom/indexedDB/ActorsParent.h"
+#include "mozilla/dom/locks/LockManagerParent.h"
+#include "mozilla/dom/localstorage/ActorsParent.h"
+#include "mozilla/dom/network/UDPSocketParent.h"
+#include "mozilla/dom/quota/ActorsParent.h"
+#include "mozilla/dom/simpledb/ActorsParent.h"
+#include "mozilla/dom/VsyncParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/IdleSchedulerParent.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/ipc/PBackgroundTestParent.h"
+#include "mozilla/net/BackgroundDataBridgeParent.h"
+#include "mozilla/net/HttpBackgroundChannelParent.h"
+#include "mozilla/net/HttpConnectionMgrParent.h"
+#include "mozilla/net/WebSocketConnectionParent.h"
+#include "mozilla/psm/IPCClientCertsParent.h"
+#include "mozilla/psm/SelectTLSClientAuthCertParent.h"
+#include "mozilla/psm/VerifySSLServerCertParent.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIPrincipal.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+
+using mozilla::AssertIsOnMainThread;
+using mozilla::dom::FileSystemRequestParent;
+using mozilla::dom::MessagePortParent;
+using mozilla::dom::MIDIManagerParent;
+using mozilla::dom::MIDIPlatformService;
+using mozilla::dom::MIDIPortParent;
+using mozilla::dom::PMessagePortParent;
+using mozilla::dom::PMIDIManagerParent;
+using mozilla::dom::PMIDIPortParent;
+using mozilla::dom::PServiceWorkerContainerParent;
+using mozilla::dom::PServiceWorkerParent;
+using mozilla::dom::PServiceWorkerRegistrationParent;
+using mozilla::dom::ServiceWorkerParent;
+using mozilla::dom::UDPSocketParent;
+using mozilla::dom::WebAuthnTransactionParent;
+using mozilla::dom::cache::PCacheParent;
+using mozilla::dom::cache::PCacheStorageParent;
+using mozilla::dom::cache::PCacheStreamControlParent;
+using mozilla::ipc::AssertIsOnBackgroundThread;
+
+namespace {
+
+class TestParent final : public mozilla::ipc::PBackgroundTestParent {
+ friend class mozilla::ipc::BackgroundParentImpl;
+
+ MOZ_COUNTED_DEFAULT_CTOR(TestParent)
+
+ protected:
+ ~TestParent() override { MOZ_COUNT_DTOR(TestParent); }
+
+ public:
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+};
+
+} // namespace
+
+namespace mozilla::ipc {
+
+using mozilla::dom::BroadcastChannelParent;
+using mozilla::dom::ContentParent;
+using mozilla::dom::ThreadsafeContentParentHandle;
+
+// A PBackgroundParent is a normal "content" actor if both the parent
+// side lives in the parent process and it either connects in-process, or
+// to a content process.
+static bool IsContentActor(PBackgroundParent* aActor) {
+ return XRE_IsParentProcess() &&
+ (!BackgroundParent::IsOtherProcessActor(aActor) ||
+ BackgroundParent::GetContentParentHandle(aActor));
+}
+
+// The parent side of socket bridge actors is always in the socket process.
+static bool IsSocketBridgeActor(PBackgroundParent* aActor) {
+ return XRE_IsSocketProcess();
+}
+
+// The parent side of socket actors is the parent process, and it must
+// not have a content process.
+static bool IsSocketActor(PBackgroundParent* aActor) {
+ return XRE_IsParentProcess() && !IsContentActor(aActor);
+}
+
+BackgroundParentImpl::BackgroundParentImpl() {
+ AssertIsInMainOrSocketProcess();
+
+ MOZ_COUNT_CTOR(mozilla::ipc::BackgroundParentImpl);
+}
+
+BackgroundParentImpl::~BackgroundParentImpl() {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnMainThread();
+
+ MOZ_COUNT_DTOR(mozilla::ipc::BackgroundParentImpl);
+}
+
+void BackgroundParentImpl::ActorDestroy(ActorDestroyReason aWhy) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+}
+
+already_AddRefed<net::PBackgroundDataBridgeParent>
+BackgroundParentImpl::AllocPBackgroundDataBridgeParent(
+ const uint64_t& aChannelID) {
+ if (!IsSocketBridgeActor(this)) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(XRE_IsSocketProcess(), "Should be in socket process");
+ AssertIsOnBackgroundThread();
+
+ RefPtr<net::BackgroundDataBridgeParent> actor =
+ new net::BackgroundDataBridgeParent(aChannelID);
+ return actor.forget();
+}
+
+BackgroundParentImpl::PBackgroundTestParent*
+BackgroundParentImpl::AllocPBackgroundTestParent(const nsACString& aTestArg) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return new TestParent();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPBackgroundTestConstructor(
+ PBackgroundTestParent* aActor, const nsACString& aTestArg) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ if (!PBackgroundTestParent::Send__delete__(aActor, aTestArg)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+bool BackgroundParentImpl::DeallocPBackgroundTestParent(
+ PBackgroundTestParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ delete static_cast<TestParent*>(aActor);
+ return true;
+}
+
+auto BackgroundParentImpl::AllocPBackgroundIDBFactoryParent(
+ const LoggingInfo& aLoggingInfo)
+ -> already_AddRefed<PBackgroundIDBFactoryParent> {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ using mozilla::dom::indexedDB::AllocPBackgroundIDBFactoryParent;
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return AllocPBackgroundIDBFactoryParent(aLoggingInfo);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundIDBFactoryConstructor(
+ PBackgroundIDBFactoryParent* aActor, const LoggingInfo& aLoggingInfo) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ using mozilla::dom::indexedDB::RecvPBackgroundIDBFactoryConstructor;
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ if (!RecvPBackgroundIDBFactoryConstructor(aActor, aLoggingInfo)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+auto BackgroundParentImpl::AllocPBackgroundIndexedDBUtilsParent()
+ -> PBackgroundIndexedDBUtilsParent* {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::indexedDB::AllocPBackgroundIndexedDBUtilsParent();
+}
+
+bool BackgroundParentImpl::DeallocPBackgroundIndexedDBUtilsParent(
+ PBackgroundIndexedDBUtilsParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ return mozilla::dom::indexedDB::DeallocPBackgroundIndexedDBUtilsParent(
+ aActor);
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvFlushPendingFileDeletions() {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ if (!mozilla::dom::indexedDB::RecvFlushPendingFileDeletions()) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+BackgroundParentImpl::PBackgroundSDBConnectionParent*
+BackgroundParentImpl::AllocPBackgroundSDBConnectionParent(
+ const PersistenceType& aPersistenceType,
+ const PrincipalInfo& aPrincipalInfo) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::AllocPBackgroundSDBConnectionParent(aPersistenceType,
+ aPrincipalInfo);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundSDBConnectionConstructor(
+ PBackgroundSDBConnectionParent* aActor,
+ const PersistenceType& aPersistenceType,
+ const PrincipalInfo& aPrincipalInfo) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ if (!mozilla::dom::RecvPBackgroundSDBConnectionConstructor(
+ aActor, aPersistenceType, aPrincipalInfo)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+bool BackgroundParentImpl::DeallocPBackgroundSDBConnectionParent(
+ PBackgroundSDBConnectionParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ return mozilla::dom::DeallocPBackgroundSDBConnectionParent(aActor);
+}
+
+BackgroundParentImpl::PBackgroundLSDatabaseParent*
+BackgroundParentImpl::AllocPBackgroundLSDatabaseParent(
+ const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId,
+ const uint64_t& aDatastoreId) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::AllocPBackgroundLSDatabaseParent(
+ aPrincipalInfo, aPrivateBrowsingId, aDatastoreId);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundLSDatabaseConstructor(
+ PBackgroundLSDatabaseParent* aActor, const PrincipalInfo& aPrincipalInfo,
+ const uint32_t& aPrivateBrowsingId, const uint64_t& aDatastoreId) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ if (!mozilla::dom::RecvPBackgroundLSDatabaseConstructor(
+ aActor, aPrincipalInfo, aPrivateBrowsingId, aDatastoreId)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+bool BackgroundParentImpl::DeallocPBackgroundLSDatabaseParent(
+ PBackgroundLSDatabaseParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ return mozilla::dom::DeallocPBackgroundLSDatabaseParent(aActor);
+}
+
+BackgroundParentImpl::PBackgroundLSObserverParent*
+BackgroundParentImpl::AllocPBackgroundLSObserverParent(
+ const uint64_t& aObserverId) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::AllocPBackgroundLSObserverParent(aObserverId);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundLSObserverConstructor(
+ PBackgroundLSObserverParent* aActor, const uint64_t& aObserverId) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ if (!mozilla::dom::RecvPBackgroundLSObserverConstructor(aActor,
+ aObserverId)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+bool BackgroundParentImpl::DeallocPBackgroundLSObserverParent(
+ PBackgroundLSObserverParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ return mozilla::dom::DeallocPBackgroundLSObserverParent(aActor);
+}
+
+BackgroundParentImpl::PBackgroundLSRequestParent*
+BackgroundParentImpl::AllocPBackgroundLSRequestParent(
+ const LSRequestParams& aParams) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::AllocPBackgroundLSRequestParent(this, aParams);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundLSRequestConstructor(
+ PBackgroundLSRequestParent* aActor, const LSRequestParams& aParams) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ if (!mozilla::dom::RecvPBackgroundLSRequestConstructor(aActor, aParams)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+bool BackgroundParentImpl::DeallocPBackgroundLSRequestParent(
+ PBackgroundLSRequestParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ return mozilla::dom::DeallocPBackgroundLSRequestParent(aActor);
+}
+
+BackgroundParentImpl::PBackgroundLSSimpleRequestParent*
+BackgroundParentImpl::AllocPBackgroundLSSimpleRequestParent(
+ const LSSimpleRequestParams& aParams) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::AllocPBackgroundLSSimpleRequestParent(this, aParams);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundLSSimpleRequestConstructor(
+ PBackgroundLSSimpleRequestParent* aActor,
+ const LSSimpleRequestParams& aParams) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ if (!mozilla::dom::RecvPBackgroundLSSimpleRequestConstructor(aActor,
+ aParams)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+bool BackgroundParentImpl::DeallocPBackgroundLSSimpleRequestParent(
+ PBackgroundLSSimpleRequestParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ return mozilla::dom::DeallocPBackgroundLSSimpleRequestParent(aActor);
+}
+
+BackgroundParentImpl::PBackgroundLocalStorageCacheParent*
+BackgroundParentImpl::AllocPBackgroundLocalStorageCacheParent(
+ const PrincipalInfo& aPrincipalInfo, const nsACString& aOriginKey,
+ const uint32_t& aPrivateBrowsingId) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::AllocPBackgroundLocalStorageCacheParent(
+ aPrincipalInfo, aOriginKey, aPrivateBrowsingId);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundLocalStorageCacheConstructor(
+ PBackgroundLocalStorageCacheParent* aActor,
+ const PrincipalInfo& aPrincipalInfo, const nsACString& aOriginKey,
+ const uint32_t& aPrivateBrowsingId) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ return mozilla::dom::RecvPBackgroundLocalStorageCacheConstructor(
+ this, aActor, aPrincipalInfo, aOriginKey, aPrivateBrowsingId);
+}
+
+bool BackgroundParentImpl::DeallocPBackgroundLocalStorageCacheParent(
+ PBackgroundLocalStorageCacheParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ return mozilla::dom::DeallocPBackgroundLocalStorageCacheParent(aActor);
+}
+
+auto BackgroundParentImpl::AllocPBackgroundStorageParent(
+ const nsAString& aProfilePath, const uint32_t& aPrivateBrowsingId)
+ -> PBackgroundStorageParent* {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::AllocPBackgroundStorageParent(aProfilePath,
+ aPrivateBrowsingId);
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPBackgroundStorageConstructor(
+ PBackgroundStorageParent* aActor, const nsAString& aProfilePath,
+ const uint32_t& aPrivateBrowsingId) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ return mozilla::dom::RecvPBackgroundStorageConstructor(aActor, aProfilePath,
+ aPrivateBrowsingId);
+}
+
+bool BackgroundParentImpl::DeallocPBackgroundStorageParent(
+ PBackgroundStorageParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ return mozilla::dom::DeallocPBackgroundStorageParent(aActor);
+}
+
+already_AddRefed<BackgroundParentImpl::PBackgroundSessionStorageManagerParent>
+BackgroundParentImpl::AllocPBackgroundSessionStorageManagerParent(
+ const uint64_t& aTopContextId) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return dom::AllocPBackgroundSessionStorageManagerParent(aTopContextId);
+}
+
+already_AddRefed<mozilla::dom::PBackgroundSessionStorageServiceParent>
+BackgroundParentImpl::AllocPBackgroundSessionStorageServiceParent() {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return MakeAndAddRef<mozilla::dom::BackgroundSessionStorageServiceParent>();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvCreateFileSystemManagerParent(
+ const PrincipalInfo& aPrincipalInfo,
+ Endpoint<PFileSystemManagerParent>&& aParentEndpoint,
+ CreateFileSystemManagerParentResolver&& aResolver) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::CreateFileSystemManagerParent(
+ aPrincipalInfo, std::move(aParentEndpoint), std::move(aResolver));
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvCreateWebTransportParent(
+ const nsAString& aURL, nsIPrincipal* aPrincipal,
+ const mozilla::Maybe<IPCClientInfo>& aClientInfo, const bool& aDedicated,
+ const bool& aRequireUnreliable, const uint32_t& aCongestionControl,
+ // Sequence<WebTransportHash>* aServerCertHashes,
+ Endpoint<PWebTransportParent>&& aParentEndpoint,
+ CreateWebTransportParentResolver&& aResolver) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<mozilla::dom::WebTransportParent> webt =
+ new mozilla::dom::WebTransportParent();
+ webt->Create(aURL, aPrincipal, aClientInfo, aDedicated, aRequireUnreliable,
+ aCongestionControl,
+ /*aServerCertHashes, */ std::move(aParentEndpoint),
+ std::move(aResolver));
+ return IPC_OK();
+}
+
+already_AddRefed<PIdleSchedulerParent>
+BackgroundParentImpl::AllocPIdleSchedulerParent() {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsOnBackgroundThread();
+ RefPtr<IdleSchedulerParent> actor = new IdleSchedulerParent();
+ return actor.forget();
+}
+
+dom::PRemoteWorkerControllerParent*
+BackgroundParentImpl::AllocPRemoteWorkerControllerParent(
+ const dom::RemoteWorkerData& aRemoteWorkerData) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ RefPtr<dom::RemoteWorkerControllerParent> actor =
+ new dom::RemoteWorkerControllerParent(aRemoteWorkerData);
+ return actor.forget().take();
+}
+
+IPCResult BackgroundParentImpl::RecvPRemoteWorkerControllerConstructor(
+ dom::PRemoteWorkerControllerParent* aActor,
+ const dom::RemoteWorkerData& aRemoteWorkerData) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ MOZ_ASSERT(aActor);
+
+ return IPC_OK();
+}
+
+bool BackgroundParentImpl::DeallocPRemoteWorkerControllerParent(
+ dom::PRemoteWorkerControllerParent* aActor) {
+ RefPtr<dom::RemoteWorkerControllerParent> actor =
+ dont_AddRef(static_cast<dom::RemoteWorkerControllerParent*>(aActor));
+ return true;
+}
+
+already_AddRefed<dom::PRemoteWorkerServiceParent>
+BackgroundParentImpl::AllocPRemoteWorkerServiceParent() {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<dom::RemoteWorkerServiceParent>();
+}
+
+IPCResult BackgroundParentImpl::RecvPRemoteWorkerServiceConstructor(
+ PRemoteWorkerServiceParent* aActor) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ mozilla::dom::RemoteWorkerServiceParent* actor =
+ static_cast<mozilla::dom::RemoteWorkerServiceParent*>(aActor);
+
+ RefPtr<ThreadsafeContentParentHandle> parent =
+ BackgroundParent::GetContentParentHandle(this);
+ // If the ContentParent is null we are dealing with a same-process actor.
+ if (!parent) {
+ actor->Initialize(NOT_REMOTE_TYPE);
+ } else {
+ actor->Initialize(parent->GetRemoteType());
+ }
+ return IPC_OK();
+}
+
+mozilla::dom::PSharedWorkerParent*
+BackgroundParentImpl::AllocPSharedWorkerParent(
+ const mozilla::dom::RemoteWorkerData& aData, const uint64_t& aWindowID,
+ const mozilla::dom::MessagePortIdentifier& aPortIdentifier) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ RefPtr<dom::SharedWorkerParent> agent =
+ new mozilla::dom::SharedWorkerParent();
+ return agent.forget().take();
+}
+
+IPCResult BackgroundParentImpl::RecvPSharedWorkerConstructor(
+ PSharedWorkerParent* aActor, const mozilla::dom::RemoteWorkerData& aData,
+ const uint64_t& aWindowID,
+ const mozilla::dom::MessagePortIdentifier& aPortIdentifier) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ mozilla::dom::SharedWorkerParent* actor =
+ static_cast<mozilla::dom::SharedWorkerParent*>(aActor);
+ actor->Initialize(aData, aWindowID, aPortIdentifier);
+ return IPC_OK();
+}
+
+bool BackgroundParentImpl::DeallocPSharedWorkerParent(
+ mozilla::dom::PSharedWorkerParent* aActor) {
+ RefPtr<mozilla::dom::SharedWorkerParent> actor =
+ dont_AddRef(static_cast<mozilla::dom::SharedWorkerParent*>(aActor));
+ return true;
+}
+
+dom::PFileCreatorParent* BackgroundParentImpl::AllocPFileCreatorParent(
+ const nsAString& aFullPath, const nsAString& aType, const nsAString& aName,
+ const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck,
+ const bool& aIsFromNsIFile) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ RefPtr<dom::FileCreatorParent> actor = new dom::FileCreatorParent();
+ return actor.forget().take();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPFileCreatorConstructor(
+ dom::PFileCreatorParent* aActor, const nsAString& aFullPath,
+ const nsAString& aType, const nsAString& aName,
+ const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck,
+ const bool& aIsFromNsIFile) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ bool isFileRemoteType = false;
+
+ // If the ContentParentHandle is null we are dealing with a same-process
+ // actor.
+ RefPtr<ThreadsafeContentParentHandle> parent =
+ BackgroundParent::GetContentParentHandle(this);
+ if (!parent) {
+ isFileRemoteType = true;
+ } else {
+ isFileRemoteType = parent->GetRemoteType() == FILE_REMOTE_TYPE;
+ }
+
+ dom::FileCreatorParent* actor = static_cast<dom::FileCreatorParent*>(aActor);
+
+ // We allow the creation of File via this IPC call only for the 'file' process
+ // or for testing.
+ if (!isFileRemoteType && !StaticPrefs::dom_file_createInChild()) {
+ Unused << dom::FileCreatorParent::Send__delete__(
+ actor, dom::FileCreationErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR));
+ return IPC_OK();
+ }
+
+ return actor->CreateAndShareFile(aFullPath, aType, aName, aLastModified,
+ aExistenceCheck, aIsFromNsIFile);
+}
+
+bool BackgroundParentImpl::DeallocPFileCreatorParent(
+ dom::PFileCreatorParent* aActor) {
+ RefPtr<dom::FileCreatorParent> actor =
+ dont_AddRef(static_cast<dom::FileCreatorParent*>(aActor));
+ return true;
+}
+
+dom::PTemporaryIPCBlobParent*
+BackgroundParentImpl::AllocPTemporaryIPCBlobParent() {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ return new dom::TemporaryIPCBlobParent();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPTemporaryIPCBlobConstructor(
+ dom::PTemporaryIPCBlobParent* aActor) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ dom::TemporaryIPCBlobParent* actor =
+ static_cast<dom::TemporaryIPCBlobParent*>(aActor);
+ return actor->CreateAndShareFile();
+}
+
+bool BackgroundParentImpl::DeallocPTemporaryIPCBlobParent(
+ dom::PTemporaryIPCBlobParent* aActor) {
+ delete aActor;
+ return true;
+}
+
+already_AddRefed<BackgroundParentImpl::PVsyncParent>
+BackgroundParentImpl::AllocPVsyncParent() {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<mozilla::dom::VsyncParent> actor = new mozilla::dom::VsyncParent();
+
+ RefPtr<mozilla::VsyncDispatcher> vsyncDispatcher =
+ gfxPlatform::GetPlatform()->GetGlobalVsyncDispatcher();
+ actor->UpdateVsyncDispatcher(vsyncDispatcher);
+ return actor.forget();
+}
+
+camera::PCamerasParent* BackgroundParentImpl::AllocPCamerasParent() {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+#ifdef MOZ_WEBRTC
+ RefPtr<mozilla::camera::CamerasParent> actor =
+ mozilla::camera::CamerasParent::Create();
+ return actor.forget().take();
+#else
+ return nullptr;
+#endif
+}
+
+#ifdef MOZ_WEBRTC
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPCamerasConstructor(
+ camera::PCamerasParent* aActor) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ return static_cast<camera::CamerasParent*>(aActor)->RecvPCamerasConstructor();
+}
+#endif
+
+bool BackgroundParentImpl::DeallocPCamerasParent(
+ camera::PCamerasParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+#ifdef MOZ_WEBRTC
+ RefPtr<mozilla::camera::CamerasParent> actor =
+ dont_AddRef(static_cast<mozilla::camera::CamerasParent*>(aActor));
+#endif
+ return true;
+}
+
+auto BackgroundParentImpl::AllocPUDPSocketParent(
+ const Maybe<PrincipalInfo>& /* unused */, const nsACString& /* unused */)
+ -> PUDPSocketParent* {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ RefPtr<UDPSocketParent> p = new UDPSocketParent(this);
+
+ return p.forget().take();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPUDPSocketConstructor(
+ PUDPSocketParent* aActor, const Maybe<PrincipalInfo>& aOptionalPrincipal,
+ const nsACString& aFilter) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ if (aOptionalPrincipal.isSome()) {
+ // Support for checking principals (for non-mtransport use) will be handled
+ // in bug 1167039
+ return IPC_FAIL_NO_REASON(this);
+ }
+ // No principal - This must be from mtransport (WebRTC/ICE) - We'd want
+ // to DispatchToMainThread() here, but if we do we must block RecvBind()
+ // until Init() gets run. Since we don't have a principal, and we verify
+ // we have a filter, we can safely skip the Dispatch and just invoke Init()
+ // to install the filter.
+
+ // For mtransport, this will always be "stun", which doesn't allow outbound
+ // packets if they aren't STUN packets until a STUN response is seen.
+ if (!aFilter.EqualsASCII(NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ if (!static_cast<UDPSocketParent*>(aActor)->Init(nullptr, aFilter)) {
+ MOZ_CRASH("UDPSocketCallback - failed init");
+ }
+
+ return IPC_OK();
+}
+
+bool BackgroundParentImpl::DeallocPUDPSocketParent(PUDPSocketParent* actor) {
+ UDPSocketParent* p = static_cast<UDPSocketParent*>(actor);
+ p->Release();
+ return true;
+}
+
+already_AddRefed<mozilla::psm::PVerifySSLServerCertParent>
+BackgroundParentImpl::AllocPVerifySSLServerCertParent(
+ const nsTArray<ByteArray>& aPeerCertChain, const nsACString& aHostName,
+ const int32_t& aPort, const OriginAttributes& aOriginAttributes,
+ const Maybe<ByteArray>& aStapledOCSPResponse,
+ const Maybe<ByteArray>& aSctsFromTLSExtension,
+ const Maybe<DelegatedCredentialInfoArg>& aDcInfo,
+ const uint32_t& aProviderFlags, const uint32_t& aCertVerifierFlags) {
+ if (!IsSocketActor(this)) {
+ return nullptr;
+ }
+
+ RefPtr<mozilla::psm::VerifySSLServerCertParent> parent =
+ new mozilla::psm::VerifySSLServerCertParent();
+ return parent.forget();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPVerifySSLServerCertConstructor(
+ PVerifySSLServerCertParent* aActor, nsTArray<ByteArray>&& aPeerCertChain,
+ const nsACString& aHostName, const int32_t& aPort,
+ const OriginAttributes& aOriginAttributes,
+ const Maybe<ByteArray>& aStapledOCSPResponse,
+ const Maybe<ByteArray>& aSctsFromTLSExtension,
+ const Maybe<DelegatedCredentialInfoArg>& aDcInfo,
+ const uint32_t& aProviderFlags, const uint32_t& aCertVerifierFlags) {
+ if (!IsSocketActor(this)) {
+ return IPC_FAIL(this, "must be a socket actor");
+ }
+
+ mozilla::psm::VerifySSLServerCertParent* authCert =
+ static_cast<mozilla::psm::VerifySSLServerCertParent*>(aActor);
+ if (!authCert->Dispatch(std::move(aPeerCertChain), aHostName, aPort,
+ aOriginAttributes, aStapledOCSPResponse,
+ aSctsFromTLSExtension, aDcInfo, aProviderFlags,
+ aCertVerifierFlags)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+already_AddRefed<mozilla::psm::PSelectTLSClientAuthCertParent>
+BackgroundParentImpl::AllocPSelectTLSClientAuthCertParent(
+ const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
+ const int32_t& aPort, const uint32_t& aProviderFlags,
+ const uint32_t& aProviderTlsFlags, const ByteArray& aServerCertBytes,
+ const nsTArray<ByteArray>& aCANames) {
+ if (!IsSocketActor(this)) {
+ return nullptr;
+ }
+
+ RefPtr<mozilla::psm::SelectTLSClientAuthCertParent> parent =
+ new mozilla::psm::SelectTLSClientAuthCertParent();
+ return parent.forget();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPSelectTLSClientAuthCertConstructor(
+ PSelectTLSClientAuthCertParent* actor, const nsACString& aHostName,
+ const OriginAttributes& aOriginAttributes, const int32_t& aPort,
+ const uint32_t& aProviderFlags, const uint32_t& aProviderTlsFlags,
+ const ByteArray& aServerCertBytes, nsTArray<ByteArray>&& aCANames) {
+ if (!IsSocketActor(this)) {
+ return IPC_FAIL(this, "must be a socket actor");
+ }
+
+ mozilla::psm::SelectTLSClientAuthCertParent* selectTLSClientAuthCertParent =
+ static_cast<mozilla::psm::SelectTLSClientAuthCertParent*>(actor);
+ if (!selectTLSClientAuthCertParent->Dispatch(
+ aHostName, aOriginAttributes, aPort, aProviderFlags,
+ aProviderTlsFlags, aServerCertBytes, std::move(aCANames))) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+mozilla::dom::PBroadcastChannelParent*
+BackgroundParentImpl::AllocPBroadcastChannelParent(
+ const PrincipalInfo& aPrincipalInfo, const nsACString& aOrigin,
+ const nsAString& aChannel) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ nsString originChannelKey;
+
+ // The format of originChannelKey is:
+ // <channelName>|<origin+OriginAttributes>
+
+ originChannelKey.Assign(aChannel);
+
+ originChannelKey.AppendLiteral("|");
+
+ originChannelKey.Append(NS_ConvertUTF8toUTF16(aOrigin));
+
+ return new BroadcastChannelParent(originChannelKey);
+}
+
+namespace {
+
+class CheckPrincipalRunnable final : public Runnable {
+ public:
+ CheckPrincipalRunnable(
+ already_AddRefed<ThreadsafeContentParentHandle> aParent,
+ const PrincipalInfo& aPrincipalInfo, const nsACString& aOrigin)
+ : Runnable("ipc::CheckPrincipalRunnable"),
+ mContentParent(aParent),
+ mPrincipalInfo(aPrincipalInfo),
+ mOrigin(aOrigin) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ MOZ_ASSERT(mContentParent);
+ }
+
+ NS_IMETHOD Run() override {
+ AssertIsOnMainThread();
+ RefPtr<ContentParent> contentParent = mContentParent->GetContentParent();
+ if (!contentParent) {
+ return NS_OK;
+ }
+
+ auto principalOrErr = PrincipalInfoToPrincipal(mPrincipalInfo);
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ contentParent->KillHard(
+ "BroadcastChannel killed: PrincipalInfoToPrincipal failed.");
+ return NS_OK;
+ }
+
+ nsAutoCString origin;
+ nsresult rv = principalOrErr.unwrap()->GetOrigin(origin);
+ if (NS_FAILED(rv)) {
+ contentParent->KillHard(
+ "BroadcastChannel killed: principal::GetOrigin failed.");
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(!mOrigin.Equals(origin))) {
+ contentParent->KillHard("BroadcastChannel killed: origins do not match.");
+ return NS_OK;
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<ThreadsafeContentParentHandle> mContentParent;
+ PrincipalInfo mPrincipalInfo;
+ nsCString mOrigin;
+};
+
+} // namespace
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPBroadcastChannelConstructor(
+ PBroadcastChannelParent* actor, const PrincipalInfo& aPrincipalInfo,
+ const nsACString& aOrigin, const nsAString& aChannel) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<ThreadsafeContentParentHandle> parent =
+ BackgroundParent::GetContentParentHandle(this);
+
+ // If the ContentParent is null we are dealing with a same-process actor.
+ if (!parent) {
+ return IPC_OK();
+ }
+
+ RefPtr<CheckPrincipalRunnable> runnable =
+ new CheckPrincipalRunnable(parent.forget(), aPrincipalInfo, aOrigin);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+
+ return IPC_OK();
+}
+
+bool BackgroundParentImpl::DeallocPBroadcastChannelParent(
+ PBroadcastChannelParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ delete static_cast<BroadcastChannelParent*>(aActor);
+ return true;
+}
+
+mozilla::dom::PServiceWorkerManagerParent*
+BackgroundParentImpl::AllocPServiceWorkerManagerParent() {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<dom::ServiceWorkerManagerParent> agent =
+ new dom::ServiceWorkerManagerParent();
+ return agent.forget().take();
+}
+
+bool BackgroundParentImpl::DeallocPServiceWorkerManagerParent(
+ PServiceWorkerManagerParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ RefPtr<dom::ServiceWorkerManagerParent> parent =
+ dont_AddRef(static_cast<dom::ServiceWorkerManagerParent*>(aActor));
+ MOZ_ASSERT(parent);
+ return true;
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvShutdownServiceWorkerRegistrar() {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ if (BackgroundParent::IsOtherProcessActor(this)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ RefPtr<dom::ServiceWorkerRegistrar> service =
+ dom::ServiceWorkerRegistrar::Get();
+ MOZ_ASSERT(service);
+
+ service->Shutdown();
+ return IPC_OK();
+}
+
+already_AddRefed<PCacheStorageParent>
+BackgroundParentImpl::AllocPCacheStorageParent(
+ const Namespace& aNamespace, const PrincipalInfo& aPrincipalInfo) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ return dom::cache::AllocPCacheStorageParent(this, aNamespace, aPrincipalInfo);
+}
+
+PMessagePortParent* BackgroundParentImpl::AllocPMessagePortParent(
+ const nsID& aUUID, const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return new MessagePortParent(aUUID);
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPMessagePortConstructor(
+ PMessagePortParent* aActor, const nsID& aUUID, const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ MessagePortParent* mp = static_cast<MessagePortParent*>(aActor);
+ if (!mp->Entangle(aDestinationUUID, aSequenceID)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+already_AddRefed<psm::PIPCClientCertsParent>
+BackgroundParentImpl::AllocPIPCClientCertsParent() {
+ if (!IsSocketActor(this)) {
+ return nullptr;
+ }
+
+ // This should only be called in the parent process with the socket process
+ // as the child process, not any content processes, hence the check that the
+ // child ID be 0.
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(mozilla::ipc::BackgroundParent::GetChildID(this) == 0);
+ if (!XRE_IsParentProcess() ||
+ mozilla::ipc::BackgroundParent::GetChildID(this) != 0) {
+ return nullptr;
+ }
+ RefPtr<psm::IPCClientCertsParent> result = new psm::IPCClientCertsParent();
+ return result.forget();
+}
+
+bool BackgroundParentImpl::DeallocPMessagePortParent(
+ PMessagePortParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ delete static_cast<MessagePortParent*>(aActor);
+ return true;
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvMessagePortForceClose(
+ const nsID& aUUID, const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ if (!MessagePortParent::ForceClose(aUUID, aDestinationUUID, aSequenceID)) {
+ return IPC_FAIL(this, "MessagePortParent::ForceClose failed.");
+ }
+
+ return IPC_OK();
+}
+
+BackgroundParentImpl::PQuotaParent* BackgroundParentImpl::AllocPQuotaParent() {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::quota::AllocPQuotaParent();
+}
+
+bool BackgroundParentImpl::DeallocPQuotaParent(PQuotaParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ return mozilla::dom::quota::DeallocPQuotaParent(aActor);
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvShutdownQuotaManager() {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ if (BackgroundParent::IsOtherProcessActor(this)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ if (!mozilla::dom::quota::RecvShutdownQuotaManager()) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvShutdownBackgroundSessionStorageManagers() {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ if (BackgroundParent::IsOtherProcessActor(this)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ if (!mozilla::dom::RecvShutdownBackgroundSessionStorageManagers()) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPropagateBackgroundSessionStorageManager(
+ const uint64_t& aCurrentTopContextId, const uint64_t& aTargetTopContextId) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ if (BackgroundParent::IsOtherProcessActor(this)) {
+ return IPC_FAIL(this, "Wrong actor");
+ }
+
+ mozilla::dom::RecvPropagateBackgroundSessionStorageManager(
+ aCurrentTopContextId, aTargetTopContextId);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvRemoveBackgroundSessionStorageManager(
+ const uint64_t& aTopContextId) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ if (BackgroundParent::IsOtherProcessActor(this)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ if (!mozilla::dom::RecvRemoveBackgroundSessionStorageManager(aTopContextId)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvGetSessionStorageManagerData(
+ const uint64_t& aTopContextId, const uint32_t& aSizeLimit,
+ const bool& aCancelSessionStoreTimer,
+ GetSessionStorageManagerDataResolver&& aResolver) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ if (BackgroundParent::IsOtherProcessActor(this)) {
+ return IPC_FAIL(this, "Wrong actor");
+ }
+
+ if (!mozilla::dom::RecvGetSessionStorageData(aTopContextId, aSizeLimit,
+ aCancelSessionStoreTimer,
+ std::move(aResolver))) {
+ return IPC_FAIL(this, "Couldn't get session storage data");
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvLoadSessionStorageManagerData(
+ const uint64_t& aTopContextId,
+ nsTArray<mozilla::dom::SSCacheCopy>&& aOriginCacheCopy) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ if (BackgroundParent::IsOtherProcessActor(this)) {
+ return IPC_FAIL(this, "Wrong actor");
+ }
+
+ if (!mozilla::dom::RecvLoadSessionStorageData(aTopContextId,
+ std::move(aOriginCacheCopy))) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ return IPC_OK();
+}
+
+already_AddRefed<dom::PFileSystemRequestParent>
+BackgroundParentImpl::AllocPFileSystemRequestParent(
+ const FileSystemParams& aParams) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<FileSystemRequestParent> result = new FileSystemRequestParent();
+
+ if (NS_WARN_IF(!result->Initialize(aParams))) {
+ return nullptr;
+ }
+
+ return result.forget();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPFileSystemRequestConstructor(
+ PFileSystemRequestParent* aActor, const FileSystemParams& params) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ static_cast<FileSystemRequestParent*>(aActor)->Start();
+ return IPC_OK();
+}
+
+// Gamepad API Background IPC
+already_AddRefed<dom::PGamepadEventChannelParent>
+BackgroundParentImpl::AllocPGamepadEventChannelParent() {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ return dom::GamepadEventChannelParent::Create();
+}
+
+already_AddRefed<dom::PGamepadTestChannelParent>
+BackgroundParentImpl::AllocPGamepadTestChannelParent() {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ return dom::GamepadTestChannelParent::Create();
+}
+
+dom::PWebAuthnTransactionParent*
+BackgroundParentImpl::AllocPWebAuthnTransactionParent() {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ return new dom::WebAuthnTransactionParent();
+}
+
+bool BackgroundParentImpl::DeallocPWebAuthnTransactionParent(
+ dom::PWebAuthnTransactionParent* aActor) {
+ MOZ_ASSERT(aActor);
+ delete aActor;
+ return true;
+}
+
+already_AddRefed<net::PHttpBackgroundChannelParent>
+BackgroundParentImpl::AllocPHttpBackgroundChannelParent(
+ const uint64_t& aChannelId) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<net::HttpBackgroundChannelParent> actor =
+ new net::HttpBackgroundChannelParent();
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPHttpBackgroundChannelConstructor(
+ net::PHttpBackgroundChannelParent* aActor, const uint64_t& aChannelId) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ MOZ_ASSERT(aActor);
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ net::HttpBackgroundChannelParent* aParent =
+ static_cast<net::HttpBackgroundChannelParent*>(aActor);
+
+ if (NS_WARN_IF(NS_FAILED(aParent->Init(aChannelId)))) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvCreateMIDIPort(
+ Endpoint<PMIDIPortParent>&& aEndpoint, const MIDIPortInfo& aPortInfo,
+ const bool& aSysexEnabled) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ if (!aEndpoint.IsValid()) {
+ return IPC_FAIL(this, "invalid endpoint for MIDIPort");
+ }
+
+ MIDIPlatformService::OwnerThread()->Dispatch(NS_NewRunnableFunction(
+ "CreateMIDIPortRunnable", [=, endpoint = std::move(aEndpoint)]() mutable {
+ RefPtr<MIDIPortParent> result =
+ new MIDIPortParent(aPortInfo, aSysexEnabled);
+ endpoint.Bind(result);
+ }));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvCreateMIDIManager(
+ Endpoint<PMIDIManagerParent>&& aEndpoint) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ if (!aEndpoint.IsValid()) {
+ return IPC_FAIL(this, "invalid endpoint for MIDIManager");
+ }
+
+ MIDIPlatformService::OwnerThread()->Dispatch(NS_NewRunnableFunction(
+ "CreateMIDIManagerRunnable",
+ [=, endpoint = std::move(aEndpoint)]() mutable {
+ RefPtr<MIDIManagerParent> result = new MIDIManagerParent();
+ endpoint.Bind(result);
+ MIDIPlatformService::Get()->AddManager(result);
+ }));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvHasMIDIDevice(
+ HasMIDIDeviceResolver&& aResolver) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ InvokeAsync(MIDIPlatformService::OwnerThread(), __func__,
+ []() {
+ bool hasDevice = MIDIPlatformService::Get()->HasDevice();
+ return BoolPromise::CreateAndResolve(hasDevice, __func__);
+ })
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [resolver = std::move(aResolver)](
+ const BoolPromise::ResolveOrRejectValue& r) {
+ resolver(r.IsResolve() && r.ResolveValue());
+ });
+
+ return IPC_OK();
+}
+
+mozilla::dom::PClientManagerParent*
+BackgroundParentImpl::AllocPClientManagerParent() {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ return mozilla::dom::AllocClientManagerParent();
+}
+
+bool BackgroundParentImpl::DeallocPClientManagerParent(
+ mozilla::dom::PClientManagerParent* aActor) {
+ return mozilla::dom::DeallocClientManagerParent(aActor);
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPClientManagerConstructor(
+ mozilla::dom::PClientManagerParent* aActor) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ mozilla::dom::InitClientManagerParent(aActor);
+ return IPC_OK();
+}
+
+IPCResult BackgroundParentImpl::RecvStorageActivity(
+ const PrincipalInfo& aPrincipalInfo) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ dom::StorageActivityService::SendActivity(aPrincipalInfo);
+ return IPC_OK();
+}
+
+IPCResult BackgroundParentImpl::RecvPServiceWorkerManagerConstructor(
+ PServiceWorkerManagerParent* const aActor) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ // Only the parent process is allowed to construct this actor.
+ if (BackgroundParent::IsOtherProcessActor(this)) {
+ return IPC_FAIL_NO_REASON(aActor);
+ }
+ return IPC_OK();
+}
+
+already_AddRefed<PServiceWorkerParent>
+BackgroundParentImpl::AllocPServiceWorkerParent(
+ const IPCServiceWorkerDescriptor&) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<ServiceWorkerParent>();
+}
+
+IPCResult BackgroundParentImpl::RecvPServiceWorkerConstructor(
+ PServiceWorkerParent* aActor,
+ const IPCServiceWorkerDescriptor& aDescriptor) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ dom::InitServiceWorkerParent(aActor, aDescriptor);
+ return IPC_OK();
+}
+
+already_AddRefed<PServiceWorkerContainerParent>
+BackgroundParentImpl::AllocPServiceWorkerContainerParent() {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<mozilla::dom::ServiceWorkerContainerParent>();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPServiceWorkerContainerConstructor(
+ PServiceWorkerContainerParent* aActor) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ dom::InitServiceWorkerContainerParent(aActor);
+ return IPC_OK();
+}
+
+already_AddRefed<PServiceWorkerRegistrationParent>
+BackgroundParentImpl::AllocPServiceWorkerRegistrationParent(
+ const IPCServiceWorkerRegistrationDescriptor&) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<mozilla::dom::ServiceWorkerRegistrationParent>();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPServiceWorkerRegistrationConstructor(
+ PServiceWorkerRegistrationParent* aActor,
+ const IPCServiceWorkerRegistrationDescriptor& aDescriptor) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ dom::InitServiceWorkerRegistrationParent(aActor, aDescriptor);
+ return IPC_OK();
+}
+
+dom::PEndpointForReportParent*
+BackgroundParentImpl::AllocPEndpointForReportParent(
+ const nsAString& aGroupName, const PrincipalInfo& aPrincipalInfo) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ RefPtr<dom::EndpointForReportParent> actor =
+ new dom::EndpointForReportParent();
+ return actor.forget().take();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPEndpointForReportConstructor(
+ PEndpointForReportParent* aActor, const nsAString& aGroupName,
+ const PrincipalInfo& aPrincipalInfo) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ static_cast<dom::EndpointForReportParent*>(aActor)->Run(aGroupName,
+ aPrincipalInfo);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvEnsureRDDProcessAndCreateBridge(
+ EnsureRDDProcessAndCreateBridgeResolver&& aResolver) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ using Type = std::tuple<const nsresult&,
+ Endpoint<mozilla::PRemoteDecoderManagerChild>&&>;
+
+ RefPtr<ThreadsafeContentParentHandle> parent =
+ BackgroundParent::GetContentParentHandle(this);
+ if (NS_WARN_IF(!parent)) {
+ aResolver(
+ Type(NS_ERROR_NOT_AVAILABLE, Endpoint<PRemoteDecoderManagerChild>()));
+ return IPC_OK();
+ }
+
+ RDDProcessManager* rdd = RDDProcessManager::Get();
+ if (!rdd) {
+ aResolver(
+ Type(NS_ERROR_NOT_AVAILABLE, Endpoint<PRemoteDecoderManagerChild>()));
+ return IPC_OK();
+ }
+
+ rdd->EnsureRDDProcessAndCreateBridge(OtherPid(), parent->ChildID())
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [resolver = std::move(aResolver)](
+ mozilla::RDDProcessManager::EnsureRDDPromise::
+ ResolveOrRejectValue&& aValue) mutable {
+ if (aValue.IsReject()) {
+ resolver(Type(aValue.RejectValue(),
+ Endpoint<PRemoteDecoderManagerChild>()));
+ return;
+ }
+ resolver(Type(NS_OK, std::move(aValue.ResolveValue())));
+ });
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvEnsureUtilityProcessAndCreateBridge(
+ const RemoteDecodeIn& aLocation,
+ EnsureUtilityProcessAndCreateBridgeResolver&& aResolver) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ base::ProcessId otherPid = OtherPid();
+ RefPtr<ThreadsafeContentParentHandle> parent =
+ BackgroundParent::GetContentParentHandle(this);
+ if (NS_WARN_IF(!parent)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ dom::ContentParentId childId = parent->ChildID();
+ nsCOMPtr<nsISerialEventTarget> managerThread = GetCurrentSerialEventTarget();
+ if (!managerThread) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "BackgroundParentImpl::RecvEnsureUtilityProcessAndCreateBridge()",
+ [aResolver, managerThread, otherPid, childId, aLocation]() {
+ RefPtr<UtilityProcessManager> upm =
+ UtilityProcessManager::GetSingleton();
+ using Type =
+ std::tuple<const nsresult&,
+ Endpoint<mozilla::PRemoteDecoderManagerChild>&&>;
+ if (!upm) {
+ managerThread->Dispatch(NS_NewRunnableFunction(
+ "BackgroundParentImpl::RecvEnsureUtilityProcessAndCreateBridge::"
+ "Failure",
+ [aResolver]() {
+ aResolver(Type(NS_ERROR_NOT_AVAILABLE,
+ Endpoint<PRemoteDecoderManagerChild>()));
+ }));
+ } else {
+ SandboxingKind sbKind = GetSandboxingKindFromLocation(aLocation);
+ upm->StartProcessForRemoteMediaDecoding(otherPid, childId, sbKind)
+ ->Then(managerThread, __func__,
+ [resolver = aResolver](
+ mozilla::ipc::UtilityProcessManager::
+ StartRemoteDecodingUtilityPromise::
+ ResolveOrRejectValue&& aValue) mutable {
+ if (aValue.IsReject()) {
+ resolver(Type(aValue.RejectValue(),
+ Endpoint<PRemoteDecoderManagerChild>()));
+ return;
+ }
+ resolver(Type(NS_OK, std::move(aValue.ResolveValue())));
+ });
+ }
+ }));
+ return IPC_OK();
+}
+
+bool BackgroundParentImpl::DeallocPEndpointForReportParent(
+ PEndpointForReportParent* aActor) {
+ RefPtr<dom::EndpointForReportParent> actor =
+ dont_AddRef(static_cast<dom::EndpointForReportParent*>(aActor));
+ return true;
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvRemoveEndpoint(
+ const nsAString& aGroupName, const nsACString& aEndpointURL,
+ const PrincipalInfo& aPrincipalInfo) {
+ if (!IsContentActor(this)) {
+ return IPC_FAIL(this, "must be a content actor");
+ }
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "BackgroundParentImpl::RecvRemoveEndpoint(",
+ [aGroupName = nsString(aGroupName),
+ aEndpointURL = nsCString(aEndpointURL), aPrincipalInfo]() {
+ dom::ReportingHeader::RemoveEndpoint(aGroupName, aEndpointURL,
+ aPrincipalInfo);
+ }));
+
+ return IPC_OK();
+}
+
+dom::PMediaTransportParent* BackgroundParentImpl::AllocPMediaTransportParent() {
+#ifdef MOZ_WEBRTC
+ if (!IsSocketBridgeActor(this)) {
+ return nullptr;
+ }
+
+ return new MediaTransportParent;
+#else
+ return nullptr;
+#endif
+}
+
+bool BackgroundParentImpl::DeallocPMediaTransportParent(
+ dom::PMediaTransportParent* aActor) {
+#ifdef MOZ_WEBRTC
+ delete aActor;
+#endif
+ return true;
+}
+
+already_AddRefed<dom::locks::PLockManagerParent>
+BackgroundParentImpl::AllocPLockManagerParent(
+ const ContentPrincipalInfo& aPrincipalInfo, const nsID& aClientId) {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<mozilla::dom::locks::LockManagerParent>(aPrincipalInfo,
+ aClientId);
+}
+
+already_AddRefed<dom::PFetchParent> BackgroundParentImpl::AllocPFetchParent() {
+ if (!IsContentActor(this)) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<dom::FetchParent>();
+}
+
+already_AddRefed<mozilla::net::PWebSocketConnectionParent>
+BackgroundParentImpl::AllocPWebSocketConnectionParent(
+ const uint32_t& aListenerId) {
+ if (!IsSocketActor(this)) {
+ return nullptr;
+ }
+
+ Maybe<nsCOMPtr<nsIHttpUpgradeListener>> listener =
+ net::HttpConnectionMgrParent::GetAndRemoveHttpUpgradeListener(
+ aListenerId);
+ if (!listener) {
+ return nullptr;
+ }
+
+ RefPtr<mozilla::net::WebSocketConnectionParent> actor =
+ new mozilla::net::WebSocketConnectionParent(*listener);
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPWebSocketConnectionConstructor(
+ PWebSocketConnectionParent* actor, const uint32_t& aListenerId) {
+ if (!IsSocketActor(this)) {
+ return IPC_FAIL(this, "must be a socket actor");
+ }
+
+ return IPC_OK();
+}
+
+} // namespace mozilla::ipc
+
+void TestParent::ActorDestroy(ActorDestroyReason aWhy) {
+ mozilla::ipc::AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+}
diff --git a/ipc/glue/BackgroundParentImpl.h b/ipc/glue/BackgroundParentImpl.h
new file mode 100644
index 0000000000..50c1607e86
--- /dev/null
+++ b/ipc/glue/BackgroundParentImpl.h
@@ -0,0 +1,415 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_backgroundparentimpl_h__
+#define mozilla_ipc_backgroundparentimpl_h__
+
+#include "mozilla/ipc/PBackgroundParent.h"
+
+namespace mozilla::ipc {
+
+// Instances of this class should never be created directly. This class is meant
+// to be inherited in BackgroundImpl.
+class BackgroundParentImpl : public PBackgroundParent {
+ protected:
+ BackgroundParentImpl();
+ virtual ~BackgroundParentImpl();
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ PBackgroundTestParent* AllocPBackgroundTestParent(
+ const nsACString& aTestArg) override;
+
+ mozilla::ipc::IPCResult RecvPBackgroundTestConstructor(
+ PBackgroundTestParent* aActor, const nsACString& aTestArg) override;
+
+ bool DeallocPBackgroundTestParent(PBackgroundTestParent* aActor) override;
+
+ already_AddRefed<PBackgroundIDBFactoryParent>
+ AllocPBackgroundIDBFactoryParent(const LoggingInfo& aLoggingInfo) override;
+
+ already_AddRefed<net::PBackgroundDataBridgeParent>
+ AllocPBackgroundDataBridgeParent(const uint64_t& aChannelID) override;
+
+ mozilla::ipc::IPCResult RecvPBackgroundIDBFactoryConstructor(
+ PBackgroundIDBFactoryParent* aActor,
+ const LoggingInfo& aLoggingInfo) override;
+
+ PBackgroundIndexedDBUtilsParent* AllocPBackgroundIndexedDBUtilsParent()
+ override;
+
+ bool DeallocPBackgroundIndexedDBUtilsParent(
+ PBackgroundIndexedDBUtilsParent* aActor) override;
+
+ mozilla::ipc::IPCResult RecvFlushPendingFileDeletions() override;
+
+ PBackgroundSDBConnectionParent* AllocPBackgroundSDBConnectionParent(
+ const PersistenceType& aPersistenceType,
+ const PrincipalInfo& aPrincipalInfo) override;
+
+ mozilla::ipc::IPCResult RecvPBackgroundSDBConnectionConstructor(
+ PBackgroundSDBConnectionParent* aActor,
+ const PersistenceType& aPersistenceType,
+ const PrincipalInfo& aPrincipalInfo) override;
+
+ bool DeallocPBackgroundSDBConnectionParent(
+ PBackgroundSDBConnectionParent* aActor) override;
+
+ PBackgroundLSDatabaseParent* AllocPBackgroundLSDatabaseParent(
+ const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId,
+ const uint64_t& aDatastoreId) override;
+
+ mozilla::ipc::IPCResult RecvPBackgroundLSDatabaseConstructor(
+ PBackgroundLSDatabaseParent* aActor, const PrincipalInfo& aPrincipalInfo,
+ const uint32_t& aPrivateBrowsingId,
+ const uint64_t& aDatastoreId) override;
+
+ bool DeallocPBackgroundLSDatabaseParent(
+ PBackgroundLSDatabaseParent* aActor) override;
+
+ PBackgroundLSObserverParent* AllocPBackgroundLSObserverParent(
+ const uint64_t& aObserverId) override;
+
+ mozilla::ipc::IPCResult RecvPBackgroundLSObserverConstructor(
+ PBackgroundLSObserverParent* aActor,
+ const uint64_t& aObserverId) override;
+
+ bool DeallocPBackgroundLSObserverParent(
+ PBackgroundLSObserverParent* aActor) override;
+
+ PBackgroundLSRequestParent* AllocPBackgroundLSRequestParent(
+ const LSRequestParams& aParams) override;
+
+ mozilla::ipc::IPCResult RecvPBackgroundLSRequestConstructor(
+ PBackgroundLSRequestParent* aActor,
+ const LSRequestParams& aParams) override;
+
+ bool DeallocPBackgroundLSRequestParent(
+ PBackgroundLSRequestParent* aActor) override;
+
+ PBackgroundLSSimpleRequestParent* AllocPBackgroundLSSimpleRequestParent(
+ const LSSimpleRequestParams& aParams) override;
+
+ mozilla::ipc::IPCResult RecvPBackgroundLSSimpleRequestConstructor(
+ PBackgroundLSSimpleRequestParent* aActor,
+ const LSSimpleRequestParams& aParams) override;
+
+ bool DeallocPBackgroundLSSimpleRequestParent(
+ PBackgroundLSSimpleRequestParent* aActor) override;
+
+ PBackgroundLocalStorageCacheParent* AllocPBackgroundLocalStorageCacheParent(
+ const PrincipalInfo& aPrincipalInfo, const nsACString& aOriginKey,
+ const uint32_t& aPrivateBrowsingId) override;
+
+ mozilla::ipc::IPCResult RecvPBackgroundLocalStorageCacheConstructor(
+ PBackgroundLocalStorageCacheParent* aActor,
+ const PrincipalInfo& aPrincipalInfo, const nsACString& aOriginKey,
+ const uint32_t& aPrivateBrowsingId) override;
+
+ bool DeallocPBackgroundLocalStorageCacheParent(
+ PBackgroundLocalStorageCacheParent* aActor) override;
+
+ PBackgroundStorageParent* AllocPBackgroundStorageParent(
+ const nsAString& aProfilePath,
+ const uint32_t& aPrivateBrowsingId) override;
+
+ mozilla::ipc::IPCResult RecvPBackgroundStorageConstructor(
+ PBackgroundStorageParent* aActor, const nsAString& aProfilePath,
+ const uint32_t& aPrivateBrowsingId) override;
+
+ bool DeallocPBackgroundStorageParent(
+ PBackgroundStorageParent* aActor) override;
+
+ already_AddRefed<PBackgroundSessionStorageManagerParent>
+ AllocPBackgroundSessionStorageManagerParent(
+ const uint64_t& aTopContextId) override;
+
+ already_AddRefed<PBackgroundSessionStorageServiceParent>
+ AllocPBackgroundSessionStorageServiceParent() override;
+
+ mozilla::ipc::IPCResult RecvCreateFileSystemManagerParent(
+ const PrincipalInfo& aPrincipalInfo,
+ Endpoint<mozilla::dom::PFileSystemManagerParent>&& aParentEndpoint,
+ CreateFileSystemManagerParentResolver&& aResolver) override;
+
+ mozilla::ipc::IPCResult RecvCreateWebTransportParent(
+ const nsAString& aURL, nsIPrincipal* aPrincipal,
+ const mozilla::Maybe<IPCClientInfo>& aClientInfo, const bool& aDedicated,
+ const bool& aRequireUnreliable, const uint32_t& aCongestionControl,
+ // Sequence<WebTransportHash>* aServerCertHashes,
+ Endpoint<PWebTransportParent>&& aParentEndpoint,
+ CreateWebTransportParentResolver&& aResolver) override;
+
+ already_AddRefed<PIdleSchedulerParent> AllocPIdleSchedulerParent() override;
+
+ PTemporaryIPCBlobParent* AllocPTemporaryIPCBlobParent() override;
+
+ mozilla::ipc::IPCResult RecvPTemporaryIPCBlobConstructor(
+ PTemporaryIPCBlobParent* actor) override;
+
+ bool DeallocPTemporaryIPCBlobParent(PTemporaryIPCBlobParent* aActor) override;
+
+ PFileCreatorParent* AllocPFileCreatorParent(
+ const nsAString& aFullPath, const nsAString& aType,
+ const nsAString& aName, const Maybe<int64_t>& aLastModified,
+ const bool& aExistenceCheck, const bool& aIsFromNsIFile) override;
+
+ mozilla::ipc::IPCResult RecvPFileCreatorConstructor(
+ PFileCreatorParent* actor, const nsAString& aFullPath,
+ const nsAString& aType, const nsAString& aName,
+ const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck,
+ const bool& aIsFromNsIFile) override;
+
+ bool DeallocPFileCreatorParent(PFileCreatorParent* aActor) override;
+
+ mozilla::dom::PRemoteWorkerControllerParent*
+ AllocPRemoteWorkerControllerParent(
+ const mozilla::dom::RemoteWorkerData& aRemoteWorkerData) override;
+
+ mozilla::ipc::IPCResult RecvPRemoteWorkerControllerConstructor(
+ mozilla::dom::PRemoteWorkerControllerParent* aActor,
+ const mozilla::dom::RemoteWorkerData& aRemoteWorkerData) override;
+
+ bool DeallocPRemoteWorkerControllerParent(
+ mozilla::dom::PRemoteWorkerControllerParent* aActor) override;
+
+ already_AddRefed<PRemoteWorkerServiceParent> AllocPRemoteWorkerServiceParent()
+ override;
+
+ mozilla::ipc::IPCResult RecvPRemoteWorkerServiceConstructor(
+ PRemoteWorkerServiceParent* aActor) override;
+
+ mozilla::dom::PSharedWorkerParent* AllocPSharedWorkerParent(
+ const mozilla::dom::RemoteWorkerData& aData, const uint64_t& aWindowID,
+ const mozilla::dom::MessagePortIdentifier& aPortIdentifier) override;
+
+ mozilla::ipc::IPCResult RecvPSharedWorkerConstructor(
+ PSharedWorkerParent* aActor, const mozilla::dom::RemoteWorkerData& aData,
+ const uint64_t& aWindowID,
+ const mozilla::dom::MessagePortIdentifier& aPortIdentifier) override;
+
+ bool DeallocPSharedWorkerParent(PSharedWorkerParent* aActor) override;
+
+ already_AddRefed<PVsyncParent> AllocPVsyncParent() override;
+
+ already_AddRefed<mozilla::psm::PVerifySSLServerCertParent>
+ AllocPVerifySSLServerCertParent(
+ const nsTArray<ByteArray>& aPeerCertChain, const nsACString& aHostName,
+ const int32_t& aPort, const OriginAttributes& aOriginAttributes,
+ const Maybe<ByteArray>& aStapledOCSPResponse,
+ const Maybe<ByteArray>& aSctsFromTLSExtension,
+ const Maybe<DelegatedCredentialInfoArg>& aDcInfo,
+ const uint32_t& aProviderFlags,
+ const uint32_t& aCertVerifierFlags) override;
+
+ mozilla::ipc::IPCResult RecvPVerifySSLServerCertConstructor(
+ PVerifySSLServerCertParent* aActor, nsTArray<ByteArray>&& aPeerCertChain,
+ const nsACString& aHostName, const int32_t& aPort,
+ const OriginAttributes& aOriginAttributes,
+ const Maybe<ByteArray>& aStapledOCSPResponse,
+ const Maybe<ByteArray>& aSctsFromTLSExtension,
+ const Maybe<DelegatedCredentialInfoArg>& aDcInfo,
+ const uint32_t& aProviderFlags,
+ const uint32_t& aCertVerifierFlags) override;
+
+ virtual already_AddRefed<mozilla::psm::PSelectTLSClientAuthCertParent>
+ AllocPSelectTLSClientAuthCertParent(
+ const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
+ const int32_t& aPort, const uint32_t& aProviderFlags,
+ const uint32_t& aProviderTlsFlags, const ByteArray& aServerCertBytes,
+ const nsTArray<ByteArray>& aCANames) override;
+ virtual mozilla::ipc::IPCResult RecvPSelectTLSClientAuthCertConstructor(
+ PSelectTLSClientAuthCertParent* actor, const nsACString& aHostName,
+ const OriginAttributes& aOriginAttributes, const int32_t& aPort,
+ const uint32_t& aProviderFlags, const uint32_t& aProviderTlsFlags,
+ const ByteArray& aServerCertBytes,
+ nsTArray<ByteArray>&& aCANames) override;
+
+ PBroadcastChannelParent* AllocPBroadcastChannelParent(
+ const PrincipalInfo& aPrincipalInfo, const nsACString& aOrigin,
+ const nsAString& aChannel) override;
+
+ mozilla::ipc::IPCResult RecvPBroadcastChannelConstructor(
+ PBroadcastChannelParent* actor, const PrincipalInfo& aPrincipalInfo,
+ const nsACString& origin, const nsAString& channel) override;
+
+ bool DeallocPBroadcastChannelParent(PBroadcastChannelParent* aActor) override;
+
+ PServiceWorkerManagerParent* AllocPServiceWorkerManagerParent() override;
+
+ bool DeallocPServiceWorkerManagerParent(
+ PServiceWorkerManagerParent* aActor) override;
+
+ PCamerasParent* AllocPCamerasParent() override;
+#ifdef MOZ_WEBRTC
+ mozilla::ipc::IPCResult RecvPCamerasConstructor(
+ PCamerasParent* aActor) override;
+#endif
+ bool DeallocPCamerasParent(PCamerasParent* aActor) override;
+
+ mozilla::ipc::IPCResult RecvShutdownServiceWorkerRegistrar() override;
+
+ already_AddRefed<dom::cache::PCacheStorageParent> AllocPCacheStorageParent(
+ const dom::cache::Namespace& aNamespace,
+ const PrincipalInfo& aPrincipalInfo) override;
+
+ PUDPSocketParent* AllocPUDPSocketParent(const Maybe<PrincipalInfo>& pInfo,
+ const nsACString& aFilter) override;
+ mozilla::ipc::IPCResult RecvPUDPSocketConstructor(
+ PUDPSocketParent*, const Maybe<PrincipalInfo>& aPrincipalInfo,
+ const nsACString& aFilter) override;
+ bool DeallocPUDPSocketParent(PUDPSocketParent*) override;
+
+ PMessagePortParent* AllocPMessagePortParent(
+ const nsID& aUUID, const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID) override;
+
+ mozilla::ipc::IPCResult RecvPMessagePortConstructor(
+ PMessagePortParent* aActor, const nsID& aUUID,
+ const nsID& aDestinationUUID, const uint32_t& aSequenceID) override;
+
+ already_AddRefed<PIPCClientCertsParent> AllocPIPCClientCertsParent() override;
+
+ bool DeallocPMessagePortParent(PMessagePortParent* aActor) override;
+
+ mozilla::ipc::IPCResult RecvMessagePortForceClose(
+ const nsID& aUUID, const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID) override;
+
+ PQuotaParent* AllocPQuotaParent() override;
+
+ bool DeallocPQuotaParent(PQuotaParent* aActor) override;
+
+ mozilla::ipc::IPCResult RecvShutdownQuotaManager() override;
+
+ mozilla::ipc::IPCResult RecvShutdownBackgroundSessionStorageManagers()
+ override;
+
+ mozilla::ipc::IPCResult RecvPropagateBackgroundSessionStorageManager(
+ const uint64_t& aCurrentTopContextId,
+ const uint64_t& aTargetTopContextId) override;
+
+ mozilla::ipc::IPCResult RecvRemoveBackgroundSessionStorageManager(
+ const uint64_t& aTopContextId) override;
+
+ mozilla::ipc::IPCResult RecvLoadSessionStorageManagerData(
+ const uint64_t& aTopContextId,
+ nsTArray<mozilla::dom::SSCacheCopy>&& aOriginCacheCopy) override;
+
+ mozilla::ipc::IPCResult RecvGetSessionStorageManagerData(
+ const uint64_t& aTopContextId, const uint32_t& aSizeLimit,
+ const bool& aCancelSessionStoreTimer,
+ GetSessionStorageManagerDataResolver&& aResolver) override;
+
+ already_AddRefed<PFileSystemRequestParent> AllocPFileSystemRequestParent(
+ const FileSystemParams&) override;
+
+ mozilla::ipc::IPCResult RecvPFileSystemRequestConstructor(
+ PFileSystemRequestParent* actor, const FileSystemParams& params) override;
+
+ // Gamepad API Background IPC
+ already_AddRefed<PGamepadEventChannelParent> AllocPGamepadEventChannelParent()
+ override;
+
+ already_AddRefed<PGamepadTestChannelParent> AllocPGamepadTestChannelParent()
+ override;
+
+ PWebAuthnTransactionParent* AllocPWebAuthnTransactionParent() override;
+
+ bool DeallocPWebAuthnTransactionParent(
+ PWebAuthnTransactionParent* aActor) override;
+
+ already_AddRefed<PHttpBackgroundChannelParent>
+ AllocPHttpBackgroundChannelParent(const uint64_t& aChannelId) override;
+
+ mozilla::ipc::IPCResult RecvPHttpBackgroundChannelConstructor(
+ PHttpBackgroundChannelParent* aActor,
+ const uint64_t& aChannelId) override;
+
+ PClientManagerParent* AllocPClientManagerParent() override;
+
+ bool DeallocPClientManagerParent(PClientManagerParent* aActor) override;
+
+ mozilla::ipc::IPCResult RecvPClientManagerConstructor(
+ PClientManagerParent* aActor) override;
+
+ mozilla::ipc::IPCResult RecvCreateMIDIPort(
+ Endpoint<PMIDIPortParent>&& aEndpoint, const MIDIPortInfo& aPortInfo,
+ const bool& aSysexEnabled) override;
+
+ mozilla::ipc::IPCResult RecvCreateMIDIManager(
+ Endpoint<PMIDIManagerParent>&& aEndpoint) override;
+
+ mozilla::ipc::IPCResult RecvHasMIDIDevice(
+ HasMIDIDeviceResolver&& aResolver) override;
+
+ mozilla::ipc::IPCResult RecvStorageActivity(
+ const PrincipalInfo& aPrincipalInfo) override;
+
+ already_AddRefed<PServiceWorkerParent> AllocPServiceWorkerParent(
+ const IPCServiceWorkerDescriptor&) final;
+
+ mozilla::ipc::IPCResult RecvPServiceWorkerManagerConstructor(
+ PServiceWorkerManagerParent* aActor) override;
+
+ mozilla::ipc::IPCResult RecvPServiceWorkerConstructor(
+ PServiceWorkerParent* aActor,
+ const IPCServiceWorkerDescriptor& aDescriptor) override;
+
+ already_AddRefed<PServiceWorkerContainerParent>
+ AllocPServiceWorkerContainerParent() final;
+
+ mozilla::ipc::IPCResult RecvPServiceWorkerContainerConstructor(
+ PServiceWorkerContainerParent* aActor) override;
+
+ already_AddRefed<PServiceWorkerRegistrationParent>
+ AllocPServiceWorkerRegistrationParent(
+ const IPCServiceWorkerRegistrationDescriptor&) final;
+
+ mozilla::ipc::IPCResult RecvPServiceWorkerRegistrationConstructor(
+ PServiceWorkerRegistrationParent* aActor,
+ const IPCServiceWorkerRegistrationDescriptor& aDescriptor) override;
+
+ PEndpointForReportParent* AllocPEndpointForReportParent(
+ const nsAString& aGroupName,
+ const PrincipalInfo& aPrincipalInfo) override;
+
+ mozilla::ipc::IPCResult RecvPEndpointForReportConstructor(
+ PEndpointForReportParent* actor, const nsAString& aGroupName,
+ const PrincipalInfo& aPrincipalInfo) override;
+
+ mozilla::ipc::IPCResult RecvEnsureRDDProcessAndCreateBridge(
+ EnsureRDDProcessAndCreateBridgeResolver&& aResolver) override;
+
+ mozilla::ipc::IPCResult RecvEnsureUtilityProcessAndCreateBridge(
+ const RemoteDecodeIn& aLocation,
+ EnsureUtilityProcessAndCreateBridgeResolver&& aResolver) override;
+
+ bool DeallocPEndpointForReportParent(
+ PEndpointForReportParent* aActor) override;
+
+ mozilla::ipc::IPCResult RecvRemoveEndpoint(
+ const nsAString& aGroupName, const nsACString& aEndpointURL,
+ const PrincipalInfo& aPrincipalInfo) override;
+
+ dom::PMediaTransportParent* AllocPMediaTransportParent() override;
+ bool DeallocPMediaTransportParent(
+ dom::PMediaTransportParent* aActor) override;
+
+ already_AddRefed<mozilla::net::PWebSocketConnectionParent>
+ AllocPWebSocketConnectionParent(const uint32_t& aListenerId) override;
+ mozilla::ipc::IPCResult RecvPWebSocketConnectionConstructor(
+ PWebSocketConnectionParent* actor, const uint32_t& aListenerId) override;
+
+ already_AddRefed<PLockManagerParent> AllocPLockManagerParent(
+ const ContentPrincipalInfo& aPrincipalInfo, const nsID& aClientId) final;
+
+ already_AddRefed<PFetchParent> AllocPFetchParent() override;
+};
+
+} // namespace mozilla::ipc
+
+#endif // mozilla_ipc_backgroundparentimpl_h__
diff --git a/ipc/glue/BackgroundStarterChild.h b/ipc/glue/BackgroundStarterChild.h
new file mode 100644
index 0000000000..1a0b5386f8
--- /dev/null
+++ b/ipc/glue/BackgroundStarterChild.h
@@ -0,0 +1,35 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_BackgroundStarterChild_h
+#define mozilla_ipc_BackgroundStarterChild_h
+
+#include "mozilla/ipc/PBackgroundStarterChild.h"
+#include "mozilla/dom/ContentChild.h"
+
+namespace mozilla::ipc {
+
+class BackgroundStarterChild final : public PBackgroundStarterChild {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BackgroundStarterChild, override)
+
+ BackgroundStarterChild(base::ProcessId aOtherPid,
+ nsISerialEventTarget* aTaskQueue)
+ : mOtherPid(aOtherPid), mTaskQueue(aTaskQueue) {}
+
+ // Unlike the methods on `IToplevelProtocol`, may be accessed on any thread
+ // and will not be modified after construction.
+ const base::ProcessId mOtherPid;
+ const nsCOMPtr<nsISerialEventTarget> mTaskQueue;
+
+ private:
+ friend class PBackgroundStarterChild;
+ ~BackgroundStarterChild() = default;
+};
+
+} // namespace mozilla::ipc
+
+#endif // mozilla_ipc_BackgroundStarterChild_h
diff --git a/ipc/glue/BackgroundStarterParent.h b/ipc/glue/BackgroundStarterParent.h
new file mode 100644
index 0000000000..4312b9ab2a
--- /dev/null
+++ b/ipc/glue/BackgroundStarterParent.h
@@ -0,0 +1,48 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_BackgroundStarterParent_h
+#define mozilla_ipc_BackgroundStarterParent_h
+
+#include "mozilla/ipc/PBackgroundStarterParent.h"
+#include "mozilla/dom/ContentParent.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::ipc {
+
+class BackgroundStarterParent final : public PBackgroundStarterParent {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD(
+ BackgroundStarterParent, override)
+
+ // Implemented in BackgroundImpl.cpp
+ BackgroundStarterParent(mozilla::dom::ThreadsafeContentParentHandle* aContent,
+ bool aCrossProcess);
+
+ void SetLiveActorArray(nsTArray<IToplevelProtocol*>* aLiveActorArray);
+
+ private:
+ friend class PBackgroundStarterParent;
+ ~BackgroundStarterParent() = default;
+
+ // Implemented in BackgroundImpl.cpp
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ // Implemented in BackgroundImpl.cpp
+ IPCResult RecvInitBackground(Endpoint<PBackgroundParent>&& aEndpoint);
+
+ const bool mCrossProcess;
+
+ const RefPtr<mozilla::dom::ThreadsafeContentParentHandle> mContent;
+
+ // Set when the actor is opened successfully and used to handle shutdown
+ // hangs. Only touched on the background thread.
+ nsTArray<IToplevelProtocol*>* mLiveActorArray = nullptr;
+};
+
+} // namespace mozilla::ipc
+
+#endif // mozilla_ipc_BackgroundStarterParent_h
diff --git a/ipc/glue/BackgroundUtils.cpp b/ipc/glue/BackgroundUtils.cpp
new file mode 100644
index 0000000000..f3071bb627
--- /dev/null
+++ b/ipc/glue/BackgroundUtils.cpp
@@ -0,0 +1,1115 @@
+/* -*- 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 "BackgroundUtils.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ContentPrincipal.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/SystemPrincipal.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/net/InterceptionInfo.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "ExpandedPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "mozilla/LoadInfo.h"
+#include "nsContentUtils.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/nsRedirectHistoryEntry.h"
+#include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/LoadInfo.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::net;
+
+namespace mozilla {
+
+namespace ipc {
+
+Result<nsCOMPtr<nsIPrincipal>, nsresult> PrincipalInfoToPrincipal(
+ const PrincipalInfo& aPrincipalInfo) {
+ MOZ_ASSERT(aPrincipalInfo.type() != PrincipalInfo::T__None);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv;
+
+ switch (aPrincipalInfo.type()) {
+ case PrincipalInfo::TSystemPrincipalInfo: {
+ principal = SystemPrincipal::Get();
+ if (NS_WARN_IF(!principal)) {
+ return Err(NS_ERROR_NOT_INITIALIZED);
+ }
+
+ return principal;
+ }
+
+ case PrincipalInfo::TNullPrincipalInfo: {
+ const NullPrincipalInfo& info = aPrincipalInfo.get_NullPrincipalInfo();
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), info.spec());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Err(rv);
+ }
+
+ if (!uri->SchemeIs(NS_NULLPRINCIPAL_SCHEME)) {
+ return Err(NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ principal = NullPrincipal::Create(info.attrs(), uri);
+ return principal;
+ }
+
+ case PrincipalInfo::TContentPrincipalInfo: {
+ const ContentPrincipalInfo& info =
+ aPrincipalInfo.get_ContentPrincipalInfo();
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), info.spec());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Err(rv);
+ }
+
+ nsCOMPtr<nsIURI> domain;
+ if (info.domain()) {
+ rv = NS_NewURI(getter_AddRefs(domain), *info.domain());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Err(rv);
+ }
+ }
+
+ principal =
+ BasePrincipal::CreateContentPrincipal(uri, info.attrs(), domain);
+ if (NS_WARN_IF(!principal)) {
+ return Err(NS_ERROR_NULL_POINTER);
+ }
+
+ // Origin must match what the_new_principal.getOrigin returns.
+ nsAutoCString originNoSuffix;
+ rv = principal->GetOriginNoSuffix(originNoSuffix);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Err(rv);
+ }
+
+ if (NS_WARN_IF(!info.originNoSuffix().Equals(originNoSuffix))) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ if (!info.baseDomain().IsVoid()) {
+ nsAutoCString baseDomain;
+ rv = principal->GetBaseDomain(baseDomain);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Err(rv);
+ }
+
+ if (NS_WARN_IF(!info.baseDomain().Equals(baseDomain))) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ }
+ return principal;
+ }
+
+ case PrincipalInfo::TExpandedPrincipalInfo: {
+ const ExpandedPrincipalInfo& info =
+ aPrincipalInfo.get_ExpandedPrincipalInfo();
+
+ nsTArray<nsCOMPtr<nsIPrincipal>> allowlist;
+ nsCOMPtr<nsIPrincipal> alPrincipal;
+
+ for (uint32_t i = 0; i < info.allowlist().Length(); i++) {
+ auto principalOrErr = PrincipalInfoToPrincipal(info.allowlist()[i]);
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ nsresult ret = principalOrErr.unwrapErr();
+ return Err(ret);
+ }
+ // append that principal to the allowlist
+ allowlist.AppendElement(principalOrErr.unwrap());
+ }
+
+ RefPtr<ExpandedPrincipal> expandedPrincipal =
+ ExpandedPrincipal::Create(allowlist, info.attrs());
+ if (!expandedPrincipal) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ principal = expandedPrincipal;
+ return principal;
+ }
+
+ default:
+ return Err(NS_ERROR_FAILURE);
+ }
+}
+
+bool StorageKeysEqual(const PrincipalInfo& aLeft, const PrincipalInfo& aRight) {
+ MOZ_RELEASE_ASSERT(aLeft.type() == PrincipalInfo::TContentPrincipalInfo ||
+ aLeft.type() == PrincipalInfo::TSystemPrincipalInfo);
+ MOZ_RELEASE_ASSERT(aRight.type() == PrincipalInfo::TContentPrincipalInfo ||
+ aRight.type() == PrincipalInfo::TSystemPrincipalInfo);
+
+ if (aLeft.type() != aRight.type()) {
+ return false;
+ }
+
+ if (aLeft.type() == PrincipalInfo::TContentPrincipalInfo) {
+ const ContentPrincipalInfo& leftContent = aLeft.get_ContentPrincipalInfo();
+ const ContentPrincipalInfo& rightContent =
+ aRight.get_ContentPrincipalInfo();
+
+ return leftContent.attrs() == rightContent.attrs() &&
+ leftContent.originNoSuffix() == rightContent.originNoSuffix();
+ }
+
+ // Storage keys for the System principal always equal.
+ return true;
+}
+
+already_AddRefed<nsIContentSecurityPolicy> CSPInfoToCSP(
+ const CSPInfo& aCSPInfo, Document* aRequestingDoc,
+ nsresult* aOptionalResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult stackResult;
+ nsresult& rv = aOptionalResult ? *aOptionalResult : stackResult;
+
+ RefPtr<nsCSPContext> csp = new nsCSPContext();
+
+ if (aRequestingDoc) {
+ rv = csp->SetRequestContextWithDocument(aRequestingDoc);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ } else {
+ auto principalOrErr =
+ PrincipalInfoToPrincipal(aCSPInfo.requestPrincipalInfo());
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> selfURI;
+ if (!aCSPInfo.selfURISpec().IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(selfURI), aCSPInfo.selfURISpec());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+
+ rv = csp->SetRequestContextWithPrincipal(
+ principal, selfURI, aCSPInfo.referrer(), aCSPInfo.innerWindowID());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ }
+ csp->SetSkipAllowInlineStyleCheck(aCSPInfo.skipAllowInlineStyleCheck());
+
+ for (uint32_t i = 0; i < aCSPInfo.policyInfos().Length(); i++) {
+ csp->AddIPCPolicy(aCSPInfo.policyInfos()[i]);
+ }
+ return csp.forget();
+}
+
+nsresult CSPToCSPInfo(nsIContentSecurityPolicy* aCSP, CSPInfo* aCSPInfo) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aCSP);
+ MOZ_ASSERT(aCSPInfo);
+
+ if (!aCSP || !aCSPInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIPrincipal> requestPrincipal = aCSP->GetRequestPrincipal();
+
+ PrincipalInfo requestingPrincipalInfo;
+ nsresult rv =
+ PrincipalToPrincipalInfo(requestPrincipal, &requestingPrincipalInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> selfURI = aCSP->GetSelfURI();
+ nsAutoCString selfURISpec;
+ if (selfURI) {
+ selfURI->GetSpec(selfURISpec);
+ }
+
+ nsAutoString referrer;
+ aCSP->GetReferrer(referrer);
+
+ uint64_t windowID = aCSP->GetInnerWindowID();
+ bool skipAllowInlineStyleCheck = aCSP->GetSkipAllowInlineStyleCheck();
+
+ nsTArray<ContentSecurityPolicy> policies;
+ static_cast<nsCSPContext*>(aCSP)->SerializePolicies(policies);
+
+ *aCSPInfo = CSPInfo(std::move(policies), requestingPrincipalInfo, selfURISpec,
+ referrer, windowID, skipAllowInlineStyleCheck);
+ return NS_OK;
+}
+
+nsresult PrincipalToPrincipalInfo(nsIPrincipal* aPrincipal,
+ PrincipalInfo* aPrincipalInfo,
+ bool aSkipBaseDomain) {
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aPrincipalInfo);
+
+ nsresult rv;
+ if (aPrincipal->GetIsNullPrincipal()) {
+ nsAutoCString spec;
+ rv = aPrincipal->GetAsciiSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aPrincipalInfo =
+ NullPrincipalInfo(aPrincipal->OriginAttributesRef(), spec);
+ return NS_OK;
+ }
+
+ if (aPrincipal->IsSystemPrincipal()) {
+ *aPrincipalInfo = SystemPrincipalInfo();
+ return NS_OK;
+ }
+
+ // might be an expanded principal
+ auto* basePrin = BasePrincipal::Cast(aPrincipal);
+ if (basePrin->Is<ExpandedPrincipal>()) {
+ auto* expanded = basePrin->As<ExpandedPrincipal>();
+
+ nsTArray<PrincipalInfo> allowlistInfo;
+ PrincipalInfo info;
+
+ for (auto& prin : expanded->AllowList()) {
+ rv = PrincipalToPrincipalInfo(prin, &info, aSkipBaseDomain);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ // append that spec to the allowlist
+ allowlistInfo.AppendElement(info);
+ }
+
+ *aPrincipalInfo = ExpandedPrincipalInfo(aPrincipal->OriginAttributesRef(),
+ std::move(allowlistInfo));
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ rv = aPrincipal->GetAsciiSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCString originNoSuffix;
+ rv = aPrincipal->GetOriginNoSuffix(originNoSuffix);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> domainUri;
+ rv = aPrincipal->GetDomain(getter_AddRefs(domainUri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ Maybe<nsCString> domain;
+ if (domainUri) {
+ domain.emplace();
+ rv = domainUri->GetSpec(domain.ref());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // This attribute is not crucial.
+ nsCString baseDomain;
+ if (aSkipBaseDomain) {
+ baseDomain.SetIsVoid(true);
+ } else {
+ if (NS_FAILED(aPrincipal->GetBaseDomain(baseDomain))) {
+ // No warning here. Some principal URLs do not have a base-domain.
+ baseDomain.SetIsVoid(true);
+ }
+ }
+
+ *aPrincipalInfo =
+ ContentPrincipalInfo(aPrincipal->OriginAttributesRef(), originNoSuffix,
+ spec, domain, baseDomain);
+ return NS_OK;
+}
+
+bool IsPrincipalInfoPrivate(const PrincipalInfo& aPrincipalInfo) {
+ if (aPrincipalInfo.type() != ipc::PrincipalInfo::TContentPrincipalInfo) {
+ return false;
+ }
+
+ const ContentPrincipalInfo& info = aPrincipalInfo.get_ContentPrincipalInfo();
+ return !!info.attrs().mPrivateBrowsingId;
+}
+
+already_AddRefed<nsIRedirectHistoryEntry> RHEntryInfoToRHEntry(
+ const RedirectHistoryEntryInfo& aRHEntryInfo) {
+ auto principalOrErr = PrincipalInfoToPrincipal(aRHEntryInfo.principalInfo());
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+ nsCOMPtr<nsIURI> referrerUri = DeserializeURI(aRHEntryInfo.referrerUri());
+
+ nsCOMPtr<nsIRedirectHistoryEntry> entry = new nsRedirectHistoryEntry(
+ principal, referrerUri, aRHEntryInfo.remoteAddress());
+
+ return entry.forget();
+}
+
+nsresult RHEntryToRHEntryInfo(nsIRedirectHistoryEntry* aRHEntry,
+ RedirectHistoryEntryInfo* aRHEntryInfo) {
+ MOZ_ASSERT(aRHEntry);
+ MOZ_ASSERT(aRHEntryInfo);
+
+ nsresult rv;
+ aRHEntry->GetRemoteAddress(aRHEntryInfo->remoteAddress());
+
+ nsCOMPtr<nsIURI> referrerUri;
+ rv = aRHEntry->GetReferrerURI(getter_AddRefs(referrerUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ SerializeURI(referrerUri, aRHEntryInfo->referrerUri());
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = aRHEntry->GetPrincipal(getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return PrincipalToPrincipalInfo(principal, &aRHEntryInfo->principalInfo());
+}
+
+nsresult LoadInfoToLoadInfoArgs(nsILoadInfo* aLoadInfo,
+ Maybe<LoadInfoArgs>* aOptionalLoadInfoArgs) {
+ nsresult rv = NS_OK;
+ Maybe<PrincipalInfo> loadingPrincipalInfo;
+ if (nsIPrincipal* loadingPrin = aLoadInfo->GetLoadingPrincipal()) {
+ loadingPrincipalInfo.emplace();
+ rv = PrincipalToPrincipalInfo(loadingPrin, loadingPrincipalInfo.ptr());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ PrincipalInfo triggeringPrincipalInfo;
+ rv = PrincipalToPrincipalInfo(aLoadInfo->TriggeringPrincipal(),
+ &triggeringPrincipalInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Maybe<PrincipalInfo> principalToInheritInfo;
+ if (nsIPrincipal* principalToInherit = aLoadInfo->PrincipalToInherit()) {
+ principalToInheritInfo.emplace();
+ rv = PrincipalToPrincipalInfo(principalToInherit,
+ principalToInheritInfo.ptr());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ Maybe<PrincipalInfo> topLevelPrincipalInfo;
+ if (nsIPrincipal* topLevenPrin = aLoadInfo->GetTopLevelPrincipal()) {
+ topLevelPrincipalInfo.emplace();
+ rv = PrincipalToPrincipalInfo(topLevenPrin, topLevelPrincipalInfo.ptr());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ Maybe<URIParams> optionalResultPrincipalURI;
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+ Unused << aLoadInfo->GetResultPrincipalURI(
+ getter_AddRefs(resultPrincipalURI));
+ if (resultPrincipalURI) {
+ SerializeURI(resultPrincipalURI, optionalResultPrincipalURI);
+ }
+
+ nsCString triggeringRemoteType;
+ rv = aLoadInfo->GetTriggeringRemoteType(triggeringRemoteType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RedirectHistoryEntryInfo> redirectChainIncludingInternalRedirects;
+ for (const nsCOMPtr<nsIRedirectHistoryEntry>& redirectEntry :
+ aLoadInfo->RedirectChainIncludingInternalRedirects()) {
+ RedirectHistoryEntryInfo* entry =
+ redirectChainIncludingInternalRedirects.AppendElement();
+ rv = RHEntryToRHEntryInfo(redirectEntry, entry);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsTArray<RedirectHistoryEntryInfo> redirectChain;
+ for (const nsCOMPtr<nsIRedirectHistoryEntry>& redirectEntry :
+ aLoadInfo->RedirectChain()) {
+ RedirectHistoryEntryInfo* entry = redirectChain.AppendElement();
+ rv = RHEntryToRHEntryInfo(redirectEntry, entry);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ Maybe<IPCClientInfo> ipcClientInfo;
+ const Maybe<ClientInfo>& clientInfo = aLoadInfo->GetClientInfo();
+ if (clientInfo.isSome()) {
+ ipcClientInfo.emplace(clientInfo.ref().ToIPC());
+ }
+
+ Maybe<IPCClientInfo> ipcReservedClientInfo;
+ const Maybe<ClientInfo>& reservedClientInfo =
+ aLoadInfo->GetReservedClientInfo();
+ if (reservedClientInfo.isSome()) {
+ ipcReservedClientInfo.emplace(reservedClientInfo.ref().ToIPC());
+ }
+
+ Maybe<IPCClientInfo> ipcInitialClientInfo;
+ const Maybe<ClientInfo>& initialClientInfo =
+ aLoadInfo->GetInitialClientInfo();
+ if (initialClientInfo.isSome()) {
+ ipcInitialClientInfo.emplace(initialClientInfo.ref().ToIPC());
+ }
+
+ Maybe<IPCServiceWorkerDescriptor> ipcController;
+ const Maybe<ServiceWorkerDescriptor>& controller = aLoadInfo->GetController();
+ if (controller.isSome()) {
+ ipcController.emplace(controller.ref().ToIPC());
+ }
+
+ nsAutoString cspNonce;
+ Unused << NS_WARN_IF(NS_FAILED(aLoadInfo->GetCspNonce(cspNonce)));
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ rv = aLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CookieJarSettingsArgs cookieJarSettingsArgs;
+ static_cast<CookieJarSettings*>(cookieJarSettings.get())
+ ->Serialize(cookieJarSettingsArgs);
+
+ Maybe<CSPInfo> maybeCspToInheritInfo;
+ nsCOMPtr<nsIContentSecurityPolicy> cspToInherit =
+ aLoadInfo->GetCspToInherit();
+ if (cspToInherit) {
+ CSPInfo cspToInheritInfo;
+ Unused << NS_WARN_IF(
+ NS_FAILED(CSPToCSPInfo(cspToInherit, &cspToInheritInfo)));
+ maybeCspToInheritInfo.emplace(cspToInheritInfo);
+ }
+
+ nsCOMPtr<nsIURI> unstrippedURI;
+ Unused << aLoadInfo->GetUnstrippedURI(getter_AddRefs(unstrippedURI));
+
+ Maybe<bool> isThirdPartyContextToTopWindow;
+ if (static_cast<LoadInfo*>(aLoadInfo)
+ ->HasIsThirdPartyContextToTopWindowSet()) {
+ isThirdPartyContextToTopWindow.emplace(
+ aLoadInfo->GetIsThirdPartyContextToTopWindow());
+ }
+
+ Maybe<InterceptionInfoArg> interceptionInfoArg;
+ nsIInterceptionInfo* interceptionInfo = aLoadInfo->InterceptionInfo();
+ if (interceptionInfo) {
+ Maybe<PrincipalInfo> triggeringPrincipalInfo;
+ if (interceptionInfo->TriggeringPrincipal()) {
+ triggeringPrincipalInfo.emplace();
+ rv = PrincipalToPrincipalInfo(interceptionInfo->TriggeringPrincipal(),
+ triggeringPrincipalInfo.ptr());
+ }
+
+ nsTArray<RedirectHistoryEntryInfo> redirectChain;
+ for (const nsCOMPtr<nsIRedirectHistoryEntry>& redirectEntry :
+ interceptionInfo->RedirectChain()) {
+ RedirectHistoryEntryInfo* entry = redirectChain.AppendElement();
+ rv = RHEntryToRHEntryInfo(redirectEntry, entry);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ interceptionInfoArg = Some(InterceptionInfoArg(
+ triggeringPrincipalInfo, interceptionInfo->ContentPolicyType(),
+ redirectChain, interceptionInfo->FromThirdParty()));
+ }
+
+ *aOptionalLoadInfoArgs = Some(LoadInfoArgs(
+ loadingPrincipalInfo, triggeringPrincipalInfo, principalToInheritInfo,
+ topLevelPrincipalInfo, optionalResultPrincipalURI, triggeringRemoteType,
+ aLoadInfo->GetSandboxedNullPrincipalID(), aLoadInfo->GetSecurityFlags(),
+ aLoadInfo->GetSandboxFlags(), aLoadInfo->GetTriggeringSandboxFlags(),
+ aLoadInfo->InternalContentPolicyType(),
+ static_cast<uint32_t>(aLoadInfo->GetTainting()),
+ aLoadInfo->GetBlockAllMixedContent(),
+ aLoadInfo->GetUpgradeInsecureRequests(),
+ aLoadInfo->GetBrowserUpgradeInsecureRequests(),
+ aLoadInfo->GetBrowserDidUpgradeInsecureRequests(),
+ aLoadInfo->GetBrowserWouldUpgradeInsecureRequests(),
+ aLoadInfo->GetForceAllowDataURI(),
+ aLoadInfo->GetAllowInsecureRedirectToDataURI(),
+ aLoadInfo->GetSkipContentPolicyCheckForWebRequest(),
+ aLoadInfo->GetOriginalFrameSrcLoad(),
+ aLoadInfo->GetForceInheritPrincipalDropped(),
+ aLoadInfo->GetInnerWindowID(), aLoadInfo->GetBrowsingContextID(),
+ aLoadInfo->GetFrameBrowsingContextID(),
+ aLoadInfo->GetInitialSecurityCheckDone(),
+ aLoadInfo->GetIsInThirdPartyContext(), isThirdPartyContextToTopWindow,
+ aLoadInfo->GetIsFormSubmission(), aLoadInfo->GetSendCSPViolationEvents(),
+ aLoadInfo->GetOriginAttributes(), redirectChainIncludingInternalRedirects,
+ redirectChain, aLoadInfo->GetHasInjectedCookieForCookieBannerHandling(),
+ ipcClientInfo, ipcReservedClientInfo, ipcInitialClientInfo, ipcController,
+ aLoadInfo->CorsUnsafeHeaders(), aLoadInfo->GetForcePreflight(),
+ aLoadInfo->GetIsPreflight(), aLoadInfo->GetLoadTriggeredFromExternal(),
+ aLoadInfo->GetServiceWorkerTaintingSynthesized(),
+ aLoadInfo->GetDocumentHasUserInteracted(),
+ aLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain(),
+ aLoadInfo->GetNeedForCheckingAntiTrackingHeuristic(), cspNonce,
+ aLoadInfo->GetSkipContentSniffing(), aLoadInfo->GetHttpsOnlyStatus(),
+ aLoadInfo->GetHstsStatus(), aLoadInfo->GetHasValidUserGestureActivation(),
+ aLoadInfo->GetAllowDeprecatedSystemRequests(),
+ aLoadInfo->GetIsInDevToolsContext(), aLoadInfo->GetParserCreatedScript(),
+ aLoadInfo->GetIsFromProcessingFrameAttributes(),
+ aLoadInfo->GetIsMediaRequest(), aLoadInfo->GetIsMediaInitialRequest(),
+ aLoadInfo->GetIsFromObjectOrEmbed(), cookieJarSettingsArgs,
+ aLoadInfo->GetRequestBlockingReason(), maybeCspToInheritInfo,
+ aLoadInfo->GetStoragePermission(), aLoadInfo->GetIsMetaRefresh(),
+ aLoadInfo->GetLoadingEmbedderPolicy(),
+ aLoadInfo->GetIsOriginTrialCoepCredentiallessEnabledForTopLevel(),
+ unstrippedURI, interceptionInfoArg));
+
+ return NS_OK;
+}
+
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs,
+ const nsACString& aOriginRemoteType, nsILoadInfo** outLoadInfo) {
+ return LoadInfoArgsToLoadInfo(aOptionalLoadInfoArgs, aOriginRemoteType,
+ nullptr, outLoadInfo);
+}
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs,
+ const nsACString& aOriginRemoteType, nsINode* aCspToInheritLoadingContext,
+ nsILoadInfo** outLoadInfo) {
+ RefPtr<LoadInfo> loadInfo;
+ nsresult rv = LoadInfoArgsToLoadInfo(aOptionalLoadInfoArgs, aOriginRemoteType,
+ aCspToInheritLoadingContext,
+ getter_AddRefs(loadInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ loadInfo.forget(outLoadInfo);
+ return NS_OK;
+}
+
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs,
+ const nsACString& aOriginRemoteType, LoadInfo** outLoadInfo) {
+ return LoadInfoArgsToLoadInfo(aOptionalLoadInfoArgs, aOriginRemoteType,
+ nullptr, outLoadInfo);
+}
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs,
+ const nsACString& aOriginRemoteType, nsINode* aCspToInheritLoadingContext,
+ LoadInfo** outLoadInfo) {
+ if (aOptionalLoadInfoArgs.isNothing()) {
+ *outLoadInfo = nullptr;
+ return NS_OK;
+ }
+
+ const LoadInfoArgs& loadInfoArgs = aOptionalLoadInfoArgs.ref();
+
+ nsCOMPtr<nsIPrincipal> loadingPrincipal;
+ if (loadInfoArgs.requestingPrincipalInfo().isSome()) {
+ auto loadingPrincipalOrErr =
+ PrincipalInfoToPrincipal(loadInfoArgs.requestingPrincipalInfo().ref());
+ if (NS_WARN_IF(loadingPrincipalOrErr.isErr())) {
+ return loadingPrincipalOrErr.unwrapErr();
+ }
+ loadingPrincipal = loadingPrincipalOrErr.unwrap();
+ }
+
+ auto triggeringPrincipalOrErr =
+ PrincipalInfoToPrincipal(loadInfoArgs.triggeringPrincipalInfo());
+ if (NS_WARN_IF(triggeringPrincipalOrErr.isErr())) {
+ return triggeringPrincipalOrErr.unwrapErr();
+ }
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ triggeringPrincipalOrErr.unwrap();
+
+ nsCOMPtr<nsIPrincipal> principalToInherit;
+ nsCOMPtr<nsIPrincipal> flattenedPrincipalToInherit;
+ if (loadInfoArgs.principalToInheritInfo().isSome()) {
+ auto principalToInheritOrErr =
+ PrincipalInfoToPrincipal(loadInfoArgs.principalToInheritInfo().ref());
+ if (NS_WARN_IF(principalToInheritOrErr.isErr())) {
+ return principalToInheritOrErr.unwrapErr();
+ }
+ flattenedPrincipalToInherit = principalToInheritOrErr.unwrap();
+ }
+
+ if (XRE_IsContentProcess()) {
+ auto targetBrowsingContextId = loadInfoArgs.frameBrowsingContextID()
+ ? loadInfoArgs.frameBrowsingContextID()
+ : loadInfoArgs.browsingContextID();
+ if (RefPtr<BrowsingContext> bc =
+ BrowsingContext::Get(targetBrowsingContextId)) {
+ auto [originalTriggeringPrincipal, originalPrincipalToInherit] =
+ bc->GetTriggeringAndInheritPrincipalsForCurrentLoad();
+
+ if (originalTriggeringPrincipal &&
+ originalTriggeringPrincipal->Equals(triggeringPrincipal)) {
+ triggeringPrincipal = originalTriggeringPrincipal;
+ }
+ if (originalPrincipalToInherit &&
+ (loadInfoArgs.securityFlags() &
+ nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL) &&
+ originalPrincipalToInherit->Equals(flattenedPrincipalToInherit)) {
+ principalToInherit = originalPrincipalToInherit;
+ }
+ }
+ }
+ if (!principalToInherit && loadInfoArgs.principalToInheritInfo().isSome()) {
+ principalToInherit = flattenedPrincipalToInherit;
+ }
+
+ nsCOMPtr<nsIPrincipal> topLevelPrincipal;
+ if (loadInfoArgs.topLevelPrincipalInfo().isSome()) {
+ auto topLevelPrincipalOrErr =
+ PrincipalInfoToPrincipal(loadInfoArgs.topLevelPrincipalInfo().ref());
+ if (NS_WARN_IF(topLevelPrincipalOrErr.isErr())) {
+ return topLevelPrincipalOrErr.unwrapErr();
+ }
+ topLevelPrincipal = topLevelPrincipalOrErr.unwrap();
+ }
+
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+ if (loadInfoArgs.resultPrincipalURI().isSome()) {
+ resultPrincipalURI = DeserializeURI(loadInfoArgs.resultPrincipalURI());
+ NS_ENSURE_TRUE(resultPrincipalURI, NS_ERROR_UNEXPECTED);
+ }
+
+ // If we received this message from a content process, reset
+ // triggeringRemoteType to the process which sent us the message. If the
+ // parent sent us the message, we trust it to provide the correct triggering
+ // remote type.
+ //
+ // This means that the triggering remote type will be reset if a LoadInfo is
+ // bounced through a content process, as the LoadInfo can no longer be
+ // validated to be coming from the originally specified remote type.
+ nsCString triggeringRemoteType = loadInfoArgs.triggeringRemoteType();
+ if (aOriginRemoteType != NOT_REMOTE_TYPE &&
+ aOriginRemoteType != triggeringRemoteType) {
+ triggeringRemoteType = aOriginRemoteType;
+ }
+
+ RedirectHistoryArray redirectChainIncludingInternalRedirects;
+ for (const RedirectHistoryEntryInfo& entryInfo :
+ loadInfoArgs.redirectChainIncludingInternalRedirects()) {
+ nsCOMPtr<nsIRedirectHistoryEntry> redirectHistoryEntry =
+ RHEntryInfoToRHEntry(entryInfo);
+ NS_ENSURE_TRUE(redirectHistoryEntry, NS_ERROR_UNEXPECTED);
+ redirectChainIncludingInternalRedirects.AppendElement(
+ redirectHistoryEntry.forget());
+ }
+
+ RedirectHistoryArray redirectChain;
+ for (const RedirectHistoryEntryInfo& entryInfo :
+ loadInfoArgs.redirectChain()) {
+ nsCOMPtr<nsIRedirectHistoryEntry> redirectHistoryEntry =
+ RHEntryInfoToRHEntry(entryInfo);
+ NS_ENSURE_TRUE(redirectHistoryEntry, NS_ERROR_UNEXPECTED);
+ redirectChain.AppendElement(redirectHistoryEntry.forget());
+ }
+ nsTArray<nsCOMPtr<nsIPrincipal>> ancestorPrincipals;
+ nsTArray<uint64_t> ancestorBrowsingContextIDs;
+ if (XRE_IsParentProcess() &&
+ (nsContentUtils::InternalContentPolicyTypeToExternal(
+ loadInfoArgs.contentPolicyType()) !=
+ ExtContentPolicy::TYPE_DOCUMENT)) {
+ // Only fill out ancestor principals and browsing context IDs when we
+ // are deserializing LoadInfoArgs to be LoadInfo for a subresource
+ RefPtr<BrowsingContext> parentBC =
+ BrowsingContext::Get(loadInfoArgs.browsingContextID());
+ if (parentBC) {
+ LoadInfo::ComputeAncestors(parentBC->Canonical(), ancestorPrincipals,
+ ancestorBrowsingContextIDs);
+ }
+ }
+
+ Maybe<ClientInfo> clientInfo;
+ if (loadInfoArgs.clientInfo().isSome()) {
+ clientInfo.emplace(ClientInfo(loadInfoArgs.clientInfo().ref()));
+ }
+
+ Maybe<ClientInfo> reservedClientInfo;
+ if (loadInfoArgs.reservedClientInfo().isSome()) {
+ reservedClientInfo.emplace(
+ ClientInfo(loadInfoArgs.reservedClientInfo().ref()));
+ }
+
+ Maybe<ClientInfo> initialClientInfo;
+ if (loadInfoArgs.initialClientInfo().isSome()) {
+ initialClientInfo.emplace(
+ ClientInfo(loadInfoArgs.initialClientInfo().ref()));
+ }
+
+ // We can have an initial client info or a reserved client info, but not both.
+ MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() ||
+ initialClientInfo.isNothing());
+ NS_ENSURE_TRUE(
+ reservedClientInfo.isNothing() || initialClientInfo.isNothing(),
+ NS_ERROR_UNEXPECTED);
+
+ Maybe<ServiceWorkerDescriptor> controller;
+ if (loadInfoArgs.controller().isSome()) {
+ controller.emplace(
+ ServiceWorkerDescriptor(loadInfoArgs.controller().ref()));
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ CookieJarSettings::Deserialize(loadInfoArgs.cookieJarSettings(),
+ getter_AddRefs(cookieJarSettings));
+
+ nsCOMPtr<nsIContentSecurityPolicy> cspToInherit;
+ Maybe<mozilla::ipc::CSPInfo> cspToInheritInfo =
+ loadInfoArgs.cspToInheritInfo();
+ if (cspToInheritInfo.isSome()) {
+ nsCOMPtr<Document> doc = do_QueryInterface(aCspToInheritLoadingContext);
+ cspToInherit = CSPInfoToCSP(cspToInheritInfo.ref(), doc);
+ }
+
+ // Restore the loadingContext for frames using the BrowsingContext's
+ // embedder element. Note that this only works if the embedder is
+ // same-process, so won't be fission compatible.
+ nsCOMPtr<nsINode> loadingContext;
+ RefPtr<BrowsingContext> frameBrowsingContext =
+ BrowsingContext::Get(loadInfoArgs.frameBrowsingContextID());
+ if (frameBrowsingContext) {
+ loadingContext = frameBrowsingContext->GetEmbedderElement();
+ }
+
+ Maybe<bool> isThirdPartyContextToTopWindow;
+ if (loadInfoArgs.isThirdPartyContextToTopWindow().isSome()) {
+ isThirdPartyContextToTopWindow.emplace(
+ loadInfoArgs.isThirdPartyContextToTopWindow().ref());
+ }
+
+ nsCOMPtr<nsIInterceptionInfo> interceptionInfo;
+ if (loadInfoArgs.interceptionInfo().isSome()) {
+ const InterceptionInfoArg& interceptionInfoArg =
+ loadInfoArgs.interceptionInfo().ref();
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ if (interceptionInfoArg.triggeringPrincipalInfo().isSome()) {
+ auto triggeringPrincipalOrErr = PrincipalInfoToPrincipal(
+ interceptionInfoArg.triggeringPrincipalInfo().ref());
+ if (NS_WARN_IF(triggeringPrincipalOrErr.isErr())) {
+ return triggeringPrincipalOrErr.unwrapErr();
+ }
+ triggeringPrincipal = triggeringPrincipalOrErr.unwrap();
+ }
+
+ RedirectHistoryArray redirectChain;
+ for (const RedirectHistoryEntryInfo& entryInfo :
+ interceptionInfoArg.redirectChain()) {
+ nsCOMPtr<nsIRedirectHistoryEntry> redirectHistoryEntry =
+ RHEntryInfoToRHEntry(entryInfo);
+ NS_ENSURE_TRUE(redirectHistoryEntry, NS_ERROR_UNEXPECTED);
+ redirectChain.AppendElement(redirectHistoryEntry.forget());
+ }
+
+ interceptionInfo = new InterceptionInfo(
+ triggeringPrincipal, interceptionInfoArg.contentPolicyType(),
+ redirectChain, interceptionInfoArg.fromThirdParty());
+ }
+
+ RefPtr<mozilla::net::LoadInfo> loadInfo = new mozilla::net::LoadInfo(
+ loadingPrincipal, triggeringPrincipal, principalToInherit,
+ topLevelPrincipal, resultPrincipalURI, cookieJarSettings, cspToInherit,
+ triggeringRemoteType, loadInfoArgs.sandboxedNullPrincipalID(), clientInfo,
+ reservedClientInfo, initialClientInfo, controller,
+ loadInfoArgs.securityFlags(), loadInfoArgs.sandboxFlags(),
+ loadInfoArgs.triggeringSandboxFlags(), loadInfoArgs.contentPolicyType(),
+ static_cast<LoadTainting>(loadInfoArgs.tainting()),
+ loadInfoArgs.blockAllMixedContent(),
+ loadInfoArgs.upgradeInsecureRequests(),
+ loadInfoArgs.browserUpgradeInsecureRequests(),
+ loadInfoArgs.browserDidUpgradeInsecureRequests(),
+ loadInfoArgs.browserWouldUpgradeInsecureRequests(),
+ loadInfoArgs.forceAllowDataURI(),
+ loadInfoArgs.allowInsecureRedirectToDataURI(),
+ loadInfoArgs.skipContentPolicyCheckForWebRequest(),
+ loadInfoArgs.originalFrameSrcLoad(),
+ loadInfoArgs.forceInheritPrincipalDropped(), loadInfoArgs.innerWindowID(),
+ loadInfoArgs.browsingContextID(), loadInfoArgs.frameBrowsingContextID(),
+ loadInfoArgs.initialSecurityCheckDone(),
+ loadInfoArgs.isInThirdPartyContext(), isThirdPartyContextToTopWindow,
+ loadInfoArgs.isFormSubmission(), loadInfoArgs.sendCSPViolationEvents(),
+ loadInfoArgs.originAttributes(),
+ std::move(redirectChainIncludingInternalRedirects),
+ std::move(redirectChain), std::move(ancestorPrincipals),
+ ancestorBrowsingContextIDs, loadInfoArgs.corsUnsafeHeaders(),
+ loadInfoArgs.forcePreflight(), loadInfoArgs.isPreflight(),
+ loadInfoArgs.loadTriggeredFromExternal(),
+ loadInfoArgs.serviceWorkerTaintingSynthesized(),
+ loadInfoArgs.documentHasUserInteracted(),
+ loadInfoArgs.allowListFutureDocumentsCreatedFromThisRedirectChain(),
+ loadInfoArgs.needForCheckingAntiTrackingHeuristic(),
+ loadInfoArgs.cspNonce(), loadInfoArgs.skipContentSniffing(),
+ loadInfoArgs.httpsOnlyStatus(), loadInfoArgs.hstsStatus(),
+ loadInfoArgs.hasValidUserGestureActivation(),
+ loadInfoArgs.allowDeprecatedSystemRequests(),
+ loadInfoArgs.isInDevToolsContext(), loadInfoArgs.parserCreatedScript(),
+ loadInfoArgs.storagePermission(), loadInfoArgs.isMetaRefresh(),
+ loadInfoArgs.requestBlockingReason(), loadingContext,
+ loadInfoArgs.loadingEmbedderPolicy(),
+ loadInfoArgs.originTrialCoepCredentiallessEnabledForTopLevel(),
+ loadInfoArgs.unstrippedURI(), interceptionInfo,
+ loadInfoArgs.hasInjectedCookieForCookieBannerHandling());
+
+ if (loadInfoArgs.isFromProcessingFrameAttributes()) {
+ loadInfo->SetIsFromProcessingFrameAttributes();
+ }
+
+ if (loadInfoArgs.isMediaRequest()) {
+ loadInfo->SetIsMediaRequest(true);
+
+ if (loadInfoArgs.isMediaInitialRequest()) {
+ loadInfo->SetIsMediaInitialRequest(true);
+ }
+ }
+
+ if (loadInfoArgs.isFromObjectOrEmbed()) {
+ loadInfo->SetIsFromObjectOrEmbed(true);
+ }
+
+ loadInfo.forget(outLoadInfo);
+ return NS_OK;
+}
+
+void LoadInfoToParentLoadInfoForwarder(
+ nsILoadInfo* aLoadInfo, ParentLoadInfoForwarderArgs* aForwarderArgsOut) {
+ Maybe<IPCServiceWorkerDescriptor> ipcController;
+ Maybe<ServiceWorkerDescriptor> controller(aLoadInfo->GetController());
+ if (controller.isSome()) {
+ ipcController.emplace(controller.ref().ToIPC());
+ }
+
+ uint32_t tainting = nsILoadInfo::TAINTING_BASIC;
+ Unused << aLoadInfo->GetTainting(&tainting);
+
+ Maybe<CookieJarSettingsArgs> cookieJarSettingsArgs;
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ nsresult rv =
+ aLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+ CookieJarSettings* cs =
+ static_cast<CookieJarSettings*>(cookieJarSettings.get());
+ if (NS_SUCCEEDED(rv) && cookieJarSettings && cs->HasBeenChanged()) {
+ CookieJarSettingsArgs args;
+ cs->Serialize(args);
+ cookieJarSettingsArgs = Some(args);
+ }
+
+ nsCOMPtr<nsIURI> unstrippedURI;
+ Unused << aLoadInfo->GetUnstrippedURI(getter_AddRefs(unstrippedURI));
+
+ Maybe<bool> isThirdPartyContextToTopWindow;
+ if (static_cast<LoadInfo*>(aLoadInfo)
+ ->HasIsThirdPartyContextToTopWindowSet()) {
+ isThirdPartyContextToTopWindow.emplace(
+ aLoadInfo->GetIsThirdPartyContextToTopWindow());
+ }
+
+ *aForwarderArgsOut = ParentLoadInfoForwarderArgs(
+ aLoadInfo->GetAllowInsecureRedirectToDataURI(), ipcController, tainting,
+ aLoadInfo->GetSkipContentSniffing(), aLoadInfo->GetHttpsOnlyStatus(),
+ aLoadInfo->GetHstsStatus(), aLoadInfo->GetHasValidUserGestureActivation(),
+ aLoadInfo->GetAllowDeprecatedSystemRequests(),
+ aLoadInfo->GetIsInDevToolsContext(), aLoadInfo->GetParserCreatedScript(),
+ aLoadInfo->GetTriggeringSandboxFlags(),
+ aLoadInfo->GetServiceWorkerTaintingSynthesized(),
+ aLoadInfo->GetDocumentHasUserInteracted(),
+ aLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain(),
+ cookieJarSettingsArgs, aLoadInfo->GetRequestBlockingReason(),
+ aLoadInfo->GetStoragePermission(), aLoadInfo->GetIsMetaRefresh(),
+ isThirdPartyContextToTopWindow, aLoadInfo->GetIsInThirdPartyContext(),
+ unstrippedURI);
+}
+
+nsresult MergeParentLoadInfoForwarder(
+ ParentLoadInfoForwarderArgs const& aForwarderArgs, nsILoadInfo* aLoadInfo) {
+ nsresult rv;
+
+ rv = aLoadInfo->SetAllowInsecureRedirectToDataURI(
+ aForwarderArgs.allowInsecureRedirectToDataURI());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aLoadInfo->ClearController();
+ auto& controller = aForwarderArgs.controller();
+ if (controller.isSome()) {
+ aLoadInfo->SetController(ServiceWorkerDescriptor(controller.ref()));
+ }
+
+ if (aForwarderArgs.serviceWorkerTaintingSynthesized()) {
+ aLoadInfo->SynthesizeServiceWorkerTainting(
+ static_cast<LoadTainting>(aForwarderArgs.tainting()));
+ } else {
+ aLoadInfo->MaybeIncreaseTainting(aForwarderArgs.tainting());
+ }
+
+ rv = aLoadInfo->SetSkipContentSniffing(aForwarderArgs.skipContentSniffing());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetHttpsOnlyStatus(aForwarderArgs.httpsOnlyStatus());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetHstsStatus(aForwarderArgs.hstsStatus());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetTriggeringSandboxFlags(
+ aForwarderArgs.triggeringSandboxFlags());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetHasValidUserGestureActivation(
+ aForwarderArgs.hasValidUserGestureActivation());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetAllowDeprecatedSystemRequests(
+ aForwarderArgs.allowDeprecatedSystemRequests());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetIsInDevToolsContext(aForwarderArgs.isInDevToolsContext());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetParserCreatedScript(aForwarderArgs.parserCreatedScript());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ALWAYS_SUCCEEDS(aLoadInfo->SetDocumentHasUserInteracted(
+ aForwarderArgs.documentHasUserInteracted()));
+ MOZ_ALWAYS_SUCCEEDS(
+ aLoadInfo->SetAllowListFutureDocumentsCreatedFromThisRedirectChain(
+ aForwarderArgs
+ .allowListFutureDocumentsCreatedFromThisRedirectChain()));
+ MOZ_ALWAYS_SUCCEEDS(aLoadInfo->SetRequestBlockingReason(
+ aForwarderArgs.requestBlockingReason()));
+
+ const Maybe<CookieJarSettingsArgs>& cookieJarSettingsArgs =
+ aForwarderArgs.cookieJarSettings();
+ if (cookieJarSettingsArgs.isSome()) {
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ nsresult rv =
+ aLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+ if (NS_SUCCEEDED(rv) && cookieJarSettings) {
+ static_cast<CookieJarSettings*>(cookieJarSettings.get())
+ ->Merge(cookieJarSettingsArgs.ref());
+ }
+ }
+
+ rv = aLoadInfo->SetStoragePermission(aForwarderArgs.storagePermission());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetIsMetaRefresh(aForwarderArgs.isMetaRefresh());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ static_cast<LoadInfo*>(aLoadInfo)->ClearIsThirdPartyContextToTopWindow();
+ if (aForwarderArgs.isThirdPartyContextToTopWindow().isSome()) {
+ rv = aLoadInfo->SetIsThirdPartyContextToTopWindow(
+ aForwarderArgs.isThirdPartyContextToTopWindow().ref());
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetIsInThirdPartyContext(
+ aForwarderArgs.isInThirdPartyContext());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetUnstrippedURI(aForwarderArgs.unstrippedURI());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+void LoadInfoToChildLoadInfoForwarder(
+ nsILoadInfo* aLoadInfo, ChildLoadInfoForwarderArgs* aForwarderArgsOut) {
+ Maybe<IPCClientInfo> ipcReserved;
+ Maybe<ClientInfo> reserved(aLoadInfo->GetReservedClientInfo());
+ if (reserved.isSome()) {
+ ipcReserved.emplace(reserved.ref().ToIPC());
+ }
+
+ Maybe<IPCClientInfo> ipcInitial;
+ Maybe<ClientInfo> initial(aLoadInfo->GetInitialClientInfo());
+ if (initial.isSome()) {
+ ipcInitial.emplace(initial.ref().ToIPC());
+ }
+
+ Maybe<IPCServiceWorkerDescriptor> ipcController;
+ Maybe<ServiceWorkerDescriptor> controller(aLoadInfo->GetController());
+ if (controller.isSome()) {
+ ipcController.emplace(controller.ref().ToIPC());
+ }
+
+ *aForwarderArgsOut =
+ ChildLoadInfoForwarderArgs(ipcReserved, ipcInitial, ipcController,
+ aLoadInfo->GetRequestBlockingReason());
+}
+
+nsresult MergeChildLoadInfoForwarder(
+ const ChildLoadInfoForwarderArgs& aForwarderArgs, nsILoadInfo* aLoadInfo) {
+ Maybe<ClientInfo> reservedClientInfo;
+ auto& ipcReserved = aForwarderArgs.reservedClientInfo();
+ if (ipcReserved.isSome()) {
+ reservedClientInfo.emplace(ClientInfo(ipcReserved.ref()));
+ }
+
+ Maybe<ClientInfo> initialClientInfo;
+ auto& ipcInitial = aForwarderArgs.initialClientInfo();
+ if (ipcInitial.isSome()) {
+ initialClientInfo.emplace(ClientInfo(ipcInitial.ref()));
+ }
+
+ // There should only be at most one reserved or initial ClientInfo.
+ if (NS_WARN_IF(reservedClientInfo.isSome() && initialClientInfo.isSome())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we received no reserved or initial ClientInfo, then we must not
+ // already have one set. There are no use cases where this should
+ // happen and we don't have a way to clear the current value.
+ if (NS_WARN_IF(reservedClientInfo.isNothing() &&
+ initialClientInfo.isNothing() &&
+ (aLoadInfo->GetReservedClientInfo().isSome() ||
+ aLoadInfo->GetInitialClientInfo().isSome()))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (reservedClientInfo.isSome()) {
+ // We need to override here instead of simply set the value. This
+ // allows us to change the reserved client. This is necessary when
+ // the ClientChannelHelper created a new reserved client in the
+ // child-side of the redirect.
+ aLoadInfo->OverrideReservedClientInfoInParent(reservedClientInfo.ref());
+ } else if (initialClientInfo.isSome()) {
+ aLoadInfo->SetInitialClientInfo(initialClientInfo.ref());
+ }
+
+ aLoadInfo->ClearController();
+ auto& controller = aForwarderArgs.controller();
+ if (controller.isSome()) {
+ aLoadInfo->SetController(ServiceWorkerDescriptor(controller.ref()));
+ }
+
+ uint32_t blockingReason = aForwarderArgs.requestBlockingReason();
+ if (blockingReason) {
+ // We only want to override when non-null, so that any earlier set non-null
+ // value is not reverted to 0.
+ aLoadInfo->SetRequestBlockingReason(blockingReason);
+ }
+
+ return NS_OK;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/BackgroundUtils.h b/ipc/glue/BackgroundUtils.h
new file mode 100644
index 0000000000..db1d48d2e0
--- /dev/null
+++ b/ipc/glue/BackgroundUtils.h
@@ -0,0 +1,195 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_backgroundutils_h__
+#define mozilla_ipc_backgroundutils_h__
+
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/OriginAttributes.h"
+#include "nsCOMPtr.h"
+#include "nscore.h"
+
+class nsIContentSecurityPolicy;
+class nsILoadInfo;
+class nsINode;
+class nsIPrincipal;
+class nsIRedirectHistoryEntry;
+
+namespace IPC {
+
+namespace detail {
+template <class ParamType>
+struct OriginAttributesParamTraits {
+ typedef ParamType paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ nsAutoCString suffix;
+ aParam.CreateSuffix(suffix);
+ WriteParam(aWriter, suffix);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ nsAutoCString suffix;
+ return ReadParam(aReader, &suffix) && aResult->PopulateFromSuffix(suffix);
+ }
+};
+} // namespace detail
+
+template <>
+struct ParamTraits<mozilla::OriginAttributes>
+ : public detail::OriginAttributesParamTraits<mozilla::OriginAttributes> {};
+
+} // namespace IPC
+
+namespace mozilla {
+
+namespace dom {
+class Document;
+}
+
+namespace net {
+class ChildLoadInfoForwarderArgs;
+class LoadInfoArgs;
+class LoadInfo;
+class ParentLoadInfoForwarderArgs;
+class RedirectHistoryEntryInfo;
+} // namespace net
+
+namespace ipc {
+
+class ContentSecurityPolicy;
+class CSPInfo;
+class PrincipalInfo;
+
+/**
+ * Convert a PrincipalInfo to an nsIPrincipal.
+ *
+ * MUST be called on the main thread.
+ */
+Result<nsCOMPtr<nsIPrincipal>, nsresult> PrincipalInfoToPrincipal(
+ const PrincipalInfo& aPrincipalInfo);
+
+/**
+ * Convert an nsIPrincipal to a PrincipalInfo.
+ *
+ * MUST be called on the main thread only.
+ */
+nsresult PrincipalToPrincipalInfo(nsIPrincipal* aPrincipal,
+ PrincipalInfo* aPrincipalInfo,
+ bool aSkipBaseDomain = false);
+
+/**
+ * Compare storage keys for equivalence.
+ *
+ * Only use with storage keys retrieved from nsIGlobalObject::GetStorageKey!
+ * Bug 1776271 tracks enhancing this into a proper type.
+ */
+bool StorageKeysEqual(const PrincipalInfo& aLeft, const PrincipalInfo& aRight);
+
+/**
+ * Convert a CSPInfo to an nsIContentSecurityPolicy.
+ *
+ * MUST be called on the main thread only.
+ *
+ * If possible, provide a requesting doc, so policy violation events can
+ * be dispatched correctly. If aRequestingDoc is null, then the CSPInfo holds
+ * the necessary fallback information, like a serialized requestPrincipal,
+ * to generate a valid nsIContentSecurityPolicy.
+ */
+already_AddRefed<nsIContentSecurityPolicy> CSPInfoToCSP(
+ const CSPInfo& aCSPInfo, mozilla::dom::Document* aRequestingDoc,
+ nsresult* aOptionalResult = nullptr);
+
+/**
+ * Convert an nsIContentSecurityPolicy to a CSPInfo.
+ *
+ * MUST be called on the main thread only.
+ */
+nsresult CSPToCSPInfo(nsIContentSecurityPolicy* aCSP, CSPInfo* aCSPInfo);
+
+/**
+ * Return true if this PrincipalInfo is a content principal and it has
+ * a privateBrowsing id in its OriginAttributes
+ */
+bool IsPrincipalInfoPrivate(const PrincipalInfo& aPrincipalInfo);
+
+/**
+ * Convert an RedirectHistoryEntryInfo to a nsIRedirectHistoryEntry.
+ */
+
+already_AddRefed<nsIRedirectHistoryEntry> RHEntryInfoToRHEntry(
+ const mozilla::net::RedirectHistoryEntryInfo& aRHEntryInfo);
+
+/**
+ * Convert an nsIRedirectHistoryEntry to a RedirectHistoryEntryInfo.
+ */
+
+nsresult RHEntryToRHEntryInfo(
+ nsIRedirectHistoryEntry* aRHEntry,
+ mozilla::net::RedirectHistoryEntryInfo* aRHEntryInfo);
+
+/**
+ * Convert a LoadInfo to LoadInfoArgs struct.
+ */
+nsresult LoadInfoToLoadInfoArgs(
+ nsILoadInfo* aLoadInfo,
+ Maybe<mozilla::net::LoadInfoArgs>* outOptionalLoadInfoArgs);
+
+/**
+ * Convert LoadInfoArgs to a LoadInfo.
+ */
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<mozilla::net::LoadInfoArgs>& aOptionalLoadInfoArgs,
+ const nsACString& aOriginRemoteType, nsILoadInfo** outLoadInfo);
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<mozilla::net::LoadInfoArgs>& aOptionalLoadInfoArgs,
+ const nsACString& aOriginRemoteType, nsINode* aCspToInheritLoadingContext,
+ nsILoadInfo** outLoadInfo);
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<net::LoadInfoArgs>& aOptionalLoadInfoArgs,
+ const nsACString& aOriginRemoteType, mozilla::net::LoadInfo** outLoadInfo);
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<net::LoadInfoArgs>& aOptionalLoadInfoArgs,
+ const nsACString& aOriginRemoteType, nsINode* aCspToInheritLoadingContext,
+ mozilla::net::LoadInfo** outLoadInfo);
+
+/**
+ * Fills ParentLoadInfoForwarderArgs with properties we want to carry to child
+ * processes.
+ */
+void LoadInfoToParentLoadInfoForwarder(
+ nsILoadInfo* aLoadInfo,
+ mozilla::net::ParentLoadInfoForwarderArgs* aForwarderArgsOut);
+
+/**
+ * Merges (replaces) properties of an existing LoadInfo on a child process
+ * with properties carried down through ParentLoadInfoForwarderArgs.
+ */
+nsresult MergeParentLoadInfoForwarder(
+ mozilla::net::ParentLoadInfoForwarderArgs const& aForwarderArgs,
+ nsILoadInfo* aLoadInfo);
+
+/**
+ * Fills ChildLoadInfoForwarderArgs with properties we want to carry to the
+ * parent process after the initial channel creation.
+ */
+void LoadInfoToChildLoadInfoForwarder(
+ nsILoadInfo* aLoadInfo,
+ mozilla::net::ChildLoadInfoForwarderArgs* aForwarderArgsOut);
+
+/**
+ * Merges (replaces) properties of an existing LoadInfo on the parent process
+ * with properties contained in a ChildLoadInfoForwarderArgs.
+ */
+nsresult MergeChildLoadInfoForwarder(
+ const mozilla::net::ChildLoadInfoForwarderArgs& aForwardArgs,
+ nsILoadInfo* aLoadInfo);
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_backgroundutils_h__
diff --git a/ipc/glue/BigBuffer.cpp b/ipc/glue/BigBuffer.cpp
new file mode 100644
index 0000000000..822ffe20fe
--- /dev/null
+++ b/ipc/glue/BigBuffer.cpp
@@ -0,0 +1,105 @@
+/* -*- 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 "mozilla/ipc/BigBuffer.h"
+
+#include "mozilla/ipc/SharedMemoryBasic.h"
+#include "nsDebug.h"
+
+namespace mozilla::ipc {
+
+BigBuffer::BigBuffer(Adopt, SharedMemory* aSharedMemory, size_t aSize)
+ : mSize(aSize), mData(AsVariant(RefPtr{aSharedMemory})) {
+ MOZ_RELEASE_ASSERT(aSharedMemory && aSharedMemory->memory(),
+ "shared memory must be non-null and mapped");
+ MOZ_RELEASE_ASSERT(mSize <= aSharedMemory->Size(),
+ "shared memory region isn't large enough");
+}
+
+BigBuffer::BigBuffer(Adopt, uint8_t* aData, size_t aSize)
+ : mSize(aSize), mData(AsVariant(UniqueFreePtr<uint8_t[]>{aData})) {}
+
+uint8_t* BigBuffer::Data() {
+ return mData.is<0>() ? mData.as<0>().get()
+ : reinterpret_cast<uint8_t*>(mData.as<1>()->memory());
+}
+const uint8_t* BigBuffer::Data() const {
+ return mData.is<0>()
+ ? mData.as<0>().get()
+ : reinterpret_cast<const uint8_t*>(mData.as<1>()->memory());
+}
+
+auto BigBuffer::TryAllocBuffer(size_t aSize) -> Maybe<Storage> {
+ if (aSize <= kShmemThreshold) {
+ auto mem = UniqueFreePtr<uint8_t[]>{
+ reinterpret_cast<uint8_t*>(malloc(aSize))}; // Fallible!
+ if (!mem) return {};
+ return Some(AsVariant(std::move(mem)));
+ }
+
+ RefPtr<SharedMemory> shmem = new SharedMemoryBasic();
+ size_t capacity = SharedMemory::PageAlignedSize(aSize);
+ if (!shmem->Create(capacity) || !shmem->Map(capacity)) {
+ return {};
+ }
+ return Some(AsVariant(shmem));
+}
+
+} // namespace mozilla::ipc
+
+void IPC::ParamTraits<mozilla::ipc::BigBuffer>::Write(MessageWriter* aWriter,
+ paramType&& aParam) {
+ using namespace mozilla::ipc;
+ size_t size = std::exchange(aParam.mSize, 0);
+ auto data = std::exchange(aParam.mData, BigBuffer::NoData());
+
+ WriteParam(aWriter, size);
+ bool isShmem = data.is<1>();
+ WriteParam(aWriter, isShmem);
+
+ if (isShmem) {
+ if (!data.as<1>()->WriteHandle(aWriter)) {
+ aWriter->FatalError("Failed to write data shmem");
+ }
+ } else {
+ aWriter->WriteBytes(data.as<0>().get(), size);
+ }
+}
+
+bool IPC::ParamTraits<mozilla::ipc::BigBuffer>::Read(MessageReader* aReader,
+ paramType* aResult) {
+ using namespace mozilla::ipc;
+ size_t size = 0;
+ bool isShmem = false;
+ if (!ReadParam(aReader, &size) || !ReadParam(aReader, &isShmem)) {
+ aReader->FatalError("Failed to read data size and format");
+ return false;
+ }
+
+ if (isShmem) {
+ RefPtr<SharedMemory> shmem = new SharedMemoryBasic();
+ size_t capacity = SharedMemory::PageAlignedSize(size);
+ if (!shmem->ReadHandle(aReader) || !shmem->Map(capacity)) {
+ aReader->FatalError("Failed to read data shmem");
+ return false;
+ }
+ *aResult = BigBuffer(BigBuffer::Adopt{}, shmem, size);
+ return true;
+ }
+
+ mozilla::UniqueFreePtr<uint8_t[]> buf{
+ reinterpret_cast<uint8_t*>(malloc(size))};
+ if (!buf) {
+ aReader->FatalError("Failed to allocate data buffer");
+ return false;
+ }
+ if (!aReader->ReadBytesInto(buf.get(), size)) {
+ aReader->FatalError("Failed to read data");
+ return false;
+ }
+ *aResult = BigBuffer(BigBuffer::Adopt{}, buf.release(), size);
+ return true;
+}
diff --git a/ipc/glue/BigBuffer.h b/ipc/glue/BigBuffer.h
new file mode 100644
index 0000000000..7136b6c03c
--- /dev/null
+++ b/ipc/glue/BigBuffer.h
@@ -0,0 +1,131 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_BigBuffer_h
+#define mozilla_ipc_BigBuffer_h
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include "mozilla/Span.h"
+#include "mozilla/Variant.h"
+#include "mozilla/ipc/SharedMemory.h"
+
+namespace mozilla::ipc {
+
+class BigBuffer {
+ public:
+ static constexpr size_t kShmemThreshold = 64 * 1024;
+
+ static BigBuffer TryAlloc(const size_t aSize) {
+ auto ret = BigBuffer{};
+ auto data = TryAllocBuffer(aSize);
+ if (data) {
+ ret.mSize = aSize;
+ ret.mData = std::move(data.ref());
+ }
+ return ret;
+ }
+
+ // Return a new BigBuffer which wraps no data.
+ BigBuffer() : mSize(0), mData(NoData()) {}
+
+ BigBuffer(const BigBuffer&) = delete;
+ BigBuffer& operator=(const BigBuffer&) = delete;
+
+ BigBuffer(BigBuffer&& aOther) noexcept
+ : mSize(std::exchange(aOther.mSize, 0)),
+ mData(std::exchange(aOther.mData, NoData())) {}
+
+ BigBuffer& operator=(BigBuffer&& aOther) noexcept {
+ mSize = std::exchange(aOther.mSize, 0);
+ mData = std::exchange(aOther.mData, NoData());
+ return *this;
+ }
+
+ // Create a new BigBuffer with the given size.
+ // The buffer will be created uninitialized and must be fully initialized
+ // before sending over IPC to avoid leaking uninitialized memory to another
+ // process.
+ explicit BigBuffer(size_t aSize) : mSize(aSize), mData(AllocBuffer(aSize)) {}
+
+ // Create a new BigBuffer containing the data from the provided byte slice.
+ explicit BigBuffer(Span<const uint8_t> aData) : BigBuffer(aData.Length()) {
+ memcpy(Data(), aData.Elements(), aData.Length());
+ }
+
+ // Marker to indicate that a particular constructor of BigBuffer adopts
+ // ownership of the provided data.
+ struct Adopt {};
+
+ // Create a new BigBuffer from an existing shared memory region, taking
+ // ownership of that shared memory region. The shared memory region must be
+ // non-null, mapped, and large enough to fit aSize bytes.
+ BigBuffer(Adopt, SharedMemory* aSharedMemory, size_t aSize);
+
+ // Create a new BigBuffer from an existing memory buffer, taking ownership of
+ // that memory region. The region will be freed using `free()` when it is no
+ // longer needed.
+ BigBuffer(Adopt, uint8_t* aData, size_t aSize);
+
+ ~BigBuffer() = default;
+
+ // Returns a pointer to the data stored by this BigBuffer, regardless of
+ // backing storage type.
+ uint8_t* Data();
+ const uint8_t* Data() const;
+
+ // Returns the size of the data stored by this BigBuffer, regardless of
+ // backing storage type.
+ size_t Size() const { return mSize; }
+
+ // Get a view of the BigBuffer's data as a span.
+ Span<uint8_t> AsSpan() { return Span{Data(), Size()}; }
+ Span<const uint8_t> AsSpan() const { return Span{Data(), Size()}; }
+
+ // If the BigBuffer is backed by shared memory, returns a pointer to the
+ // backing SharedMemory region.
+ SharedMemory* GetSharedMemory() const {
+ return mData.is<1>() ? mData.as<1>().get() : nullptr;
+ }
+
+ private:
+ friend struct IPC::ParamTraits<mozilla::ipc::BigBuffer>;
+
+ using Storage = Variant<UniqueFreePtr<uint8_t[]>, RefPtr<SharedMemory>>;
+
+ // Empty storage which holds no data.
+ static Storage NoData() { return AsVariant(UniqueFreePtr<uint8_t[]>{}); }
+
+ // Fallibly allocate a new storage of the given size.
+ static Maybe<Storage> TryAllocBuffer(size_t aSize);
+
+ // Infallibly allocate a new storage of the given size.
+ static Storage AllocBuffer(size_t aSize) {
+ auto ret = TryAllocBuffer(aSize);
+ if (!ret) {
+ NS_ABORT_OOM(aSize);
+ }
+ return std::move(ret.ref());
+ }
+
+ size_t mSize;
+ Storage mData;
+};
+
+} // namespace mozilla::ipc
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::ipc::BigBuffer> {
+ using paramType = mozilla::ipc::BigBuffer;
+ static void Write(MessageWriter* aWriter, paramType&& aParam);
+ static bool Read(MessageReader* aReader, paramType* aResult);
+};
+
+} // namespace IPC
+
+#endif // mozilla_BigBuffer_h
diff --git a/ipc/glue/BrowserProcessSubThread.cpp b/ipc/glue/BrowserProcessSubThread.cpp
new file mode 100644
index 0000000000..bd24bf6e3f
--- /dev/null
+++ b/ipc/glue/BrowserProcessSubThread.cpp
@@ -0,0 +1,83 @@
+/* -*- 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 "mozilla/ipc/BrowserProcessSubThread.h"
+#include "mozilla/ipc/NodeController.h"
+
+#if defined(OS_WIN)
+# include <objbase.h>
+#endif
+
+namespace mozilla {
+namespace ipc {
+
+//
+// BrowserProcessSubThread
+//
+
+// Friendly names for the well-known threads.
+static const char* kBrowserThreadNames[BrowserProcessSubThread::ID_COUNT] = {
+ "IPC I/O Parent", // IO
+};
+
+/* static */
+StaticMutex BrowserProcessSubThread::sLock;
+BrowserProcessSubThread* BrowserProcessSubThread::sBrowserThreads[ID_COUNT] = {
+ nullptr, // IO
+};
+
+BrowserProcessSubThread::BrowserProcessSubThread(ID aId)
+ : base::Thread(kBrowserThreadNames[aId]), mIdentifier(aId) {
+ StaticMutexAutoLock lock(sLock);
+ DCHECK(aId >= 0 && aId < ID_COUNT);
+ DCHECK(sBrowserThreads[aId] == nullptr);
+ sBrowserThreads[aId] = this;
+}
+
+BrowserProcessSubThread::~BrowserProcessSubThread() {
+ Stop();
+ {
+ StaticMutexAutoLock lock(sLock);
+ sBrowserThreads[mIdentifier] = nullptr;
+ }
+}
+
+void BrowserProcessSubThread::Init() {
+#if defined(OS_WIN)
+ // Initializes the COM library on the current thread.
+ CoInitialize(nullptr);
+#endif
+
+ // Initialize the ports library in the current thread.
+ if (mIdentifier == IO) {
+ NodeController::InitBrokerProcess();
+ }
+}
+
+void BrowserProcessSubThread::CleanUp() {
+ if (mIdentifier == IO) {
+ NodeController::CleanUp();
+ }
+
+#if defined(OS_WIN)
+ // Closes the COM library on the current thread. CoInitialize must
+ // be balanced by a corresponding call to CoUninitialize.
+ CoUninitialize();
+#endif
+}
+
+// static
+MessageLoop* BrowserProcessSubThread::GetMessageLoop(ID aId) {
+ StaticMutexAutoLock lock(sLock);
+ DCHECK(aId >= 0 && aId < ID_COUNT);
+
+ if (sBrowserThreads[aId]) return sBrowserThreads[aId]->message_loop();
+
+ return nullptr;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/BrowserProcessSubThread.h b/ipc/glue/BrowserProcessSubThread.h
new file mode 100644
index 0000000000..9be8d1ec3d
--- /dev/null
+++ b/ipc/glue/BrowserProcessSubThread.h
@@ -0,0 +1,66 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_BrowserProcessSubThread_h
+#define mozilla_ipc_BrowserProcessSubThread_h
+
+#include "base/thread.h"
+#include "mozilla/StaticMutex.h"
+
+#include "nsDebug.h"
+
+namespace mozilla {
+namespace ipc {
+
+// Copied from browser_process_impl.cc, modified slightly.
+class BrowserProcessSubThread : public base::Thread {
+ public:
+ // An enumeration of the well-known threads.
+ enum ID {
+ IO,
+
+ // This identifier does not represent a thread. Instead it counts
+ // the number of well-known threads. Insert new well-known
+ // threads before this identifier.
+ ID_COUNT
+ };
+
+ explicit BrowserProcessSubThread(ID aId);
+ ~BrowserProcessSubThread();
+
+ static MessageLoop* GetMessageLoop(ID identifier);
+
+ protected:
+ virtual void Init() override;
+ virtual void CleanUp() override;
+
+ private:
+ // The identifier of this thread. Only one thread can exist with a given
+ // identifier at a given time.
+ ID mIdentifier;
+
+ // This lock protects |browser_threads_|. Do not read or modify that array
+ // without holding this lock. Do not block while holding this lock.
+
+ static StaticMutex sLock;
+
+ // An array of the ChromeThread objects. This array is protected by |lock_|.
+ // The threads are not owned by this array. Typically, the threads are owned
+ // on the UI thread by the g_browser_process object. ChromeThreads remove
+ // themselves from this array upon destruction.
+ static BrowserProcessSubThread* sBrowserThreads[ID_COUNT] MOZ_GUARDED_BY(
+ sLock);
+};
+
+inline void AssertIOThread() {
+ NS_ASSERTION(MessageLoop::TYPE_IO == MessageLoop::current()->type(),
+ "should be on the IO thread!");
+}
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_BrowserProcessSubThread_h
diff --git a/ipc/glue/ByteBuf.h b/ipc/glue/ByteBuf.h
new file mode 100644
index 0000000000..3f3a798b89
--- /dev/null
+++ b/ipc/glue/ByteBuf.h
@@ -0,0 +1,71 @@
+/* -*- 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/. */
+
+/* A type that can be sent without needing to make a copy during
+ * serialization. In addition the receiver can take ownership of the
+ * data to avoid having to make an additional copy. */
+
+#ifndef mozilla_ipc_ByteBuf_h
+#define mozilla_ipc_ByteBuf_h
+
+#include "mozilla/Assertions.h"
+
+namespace IPC {
+template <typename T>
+struct ParamTraits;
+}
+
+namespace mozilla {
+
+namespace ipc {
+
+class ByteBuf final {
+ friend struct IPC::ParamTraits<mozilla::ipc::ByteBuf>;
+
+ public:
+ bool Allocate(size_t aLength) {
+ MOZ_ASSERT(mData == nullptr);
+ mData = (uint8_t*)malloc(aLength);
+ if (!mData) {
+ return false;
+ }
+ mLen = aLength;
+ mCapacity = aLength;
+ return true;
+ }
+
+ ByteBuf() : mData(nullptr), mLen(0), mCapacity(0) {}
+
+ ByteBuf(uint8_t* aData, size_t aLen, size_t aCapacity)
+ : mData(aData), mLen(aLen), mCapacity(aCapacity) {}
+
+ ByteBuf(const ByteBuf& aFrom) = delete;
+
+ ByteBuf(ByteBuf&& aFrom)
+ : mData(aFrom.mData), mLen(aFrom.mLen), mCapacity(aFrom.mCapacity) {
+ aFrom.mData = nullptr;
+ aFrom.mLen = 0;
+ aFrom.mCapacity = 0;
+ }
+
+ ByteBuf& operator=(ByteBuf&& aFrom) {
+ std::swap(mData, aFrom.mData);
+ std::swap(mLen, aFrom.mLen);
+ std::swap(mCapacity, aFrom.mCapacity);
+ return *this;
+ }
+
+ ~ByteBuf() { free(mData); }
+
+ uint8_t* mData;
+ size_t mLen;
+ size_t mCapacity;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // ifndef mozilla_ipc_ByteBuf_h
diff --git a/ipc/glue/ByteBufUtils.h b/ipc/glue/ByteBufUtils.h
new file mode 100644
index 0000000000..ba19864214
--- /dev/null
+++ b/ipc/glue/ByteBufUtils.h
@@ -0,0 +1,56 @@
+/* -*- 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/. */
+
+/* A type that can be sent without needing to make a copy during
+ * serialization. In addition the receiver can take ownership of the
+ * data to avoid having to make an additional copy. */
+
+#ifndef mozilla_ipc_ByteBufUtils_h
+#define mozilla_ipc_ByteBufUtils_h
+
+#include "mozilla/ipc/ByteBuf.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/mozalloc_oom.h"
+#include "ipc/IPCMessageUtils.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::ipc::ByteBuf> {
+ typedef mozilla::ipc::ByteBuf paramType;
+
+ // this is where we transfer the memory from the ByteBuf to IPDL, avoiding a
+ // copy
+ static void Write(MessageWriter* aWriter, paramType&& aParam) {
+ // We need to send the length as a 32-bit value, not a size_t, because on
+ // ARM64 Windows we end up with a 32-bit GMP process sending a ByteBuf to
+ // a 64-bit parent process. WriteBytesZeroCopy takes a uint32_t as an
+ // argument, so it would end up getting truncated anyways. See bug 1757534.
+ mozilla::CheckedInt<uint32_t> length = aParam.mLen;
+ MOZ_RELEASE_ASSERT(length.isValid());
+ WriteParam(aWriter, length.value());
+ // hand over ownership of the buffer to the Message
+ aWriter->WriteBytesZeroCopy(aParam.mData, length.value(), aParam.mCapacity);
+ aParam.mData = nullptr;
+ aParam.mCapacity = 0;
+ aParam.mLen = 0;
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ // We make a copy from the BufferList so that we get a contigous result.
+ uint32_t length;
+ if (!ReadParam(aReader, &length)) return false;
+ if (!aResult->Allocate(length)) {
+ mozalloc_handle_oom(length);
+ return false;
+ }
+ return aReader->ReadBytesInto(aResult->mData, length);
+ }
+};
+
+} // namespace IPC
+
+#endif // ifndef mozilla_ipc_ByteBufUtils_h
diff --git a/ipc/glue/CrashReporterClient.cpp b/ipc/glue/CrashReporterClient.cpp
new file mode 100644
index 0000000000..7e0eabe6ca
--- /dev/null
+++ b/ipc/glue/CrashReporterClient.cpp
@@ -0,0 +1,47 @@
+/* -*- 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 "CrashReporterClient.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace ipc {
+
+StaticMutex CrashReporterClient::sLock;
+StaticRefPtr<CrashReporterClient> CrashReporterClient::sClientSingleton;
+
+CrashReporterClient::CrashReporterClient() {
+ MOZ_COUNT_CTOR(CrashReporterClient);
+}
+
+CrashReporterClient::~CrashReporterClient() {
+ MOZ_COUNT_DTOR(CrashReporterClient);
+}
+
+/* static */
+void CrashReporterClient::InitSingleton() {
+ {
+ StaticMutexAutoLock lock(sLock);
+
+ MOZ_ASSERT(!sClientSingleton);
+ sClientSingleton = new CrashReporterClient();
+ }
+}
+
+/* static */
+void CrashReporterClient::DestroySingleton() {
+ StaticMutexAutoLock lock(sLock);
+ sClientSingleton = nullptr;
+}
+
+/* static */
+RefPtr<CrashReporterClient> CrashReporterClient::GetSingleton() {
+ StaticMutexAutoLock lock(sLock);
+ return sClientSingleton;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/CrashReporterClient.h b/ipc/glue/CrashReporterClient.h
new file mode 100644
index 0000000000..26ada18a03
--- /dev/null
+++ b/ipc/glue/CrashReporterClient.h
@@ -0,0 +1,51 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_CrashReporterClient_h
+#define mozilla_ipc_CrashReporterClient_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Unused.h"
+#include "nsExceptionHandler.h"
+
+namespace mozilla {
+namespace ipc {
+
+class CrashReporterClient {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CrashReporterClient);
+
+ // |aTopLevelProtocol| must have a child-to-parent message:
+ //
+ // async InitCrashReporter(NativeThreadId threadId);
+ template <typename T>
+ static void InitSingleton(T* aToplevelProtocol) {
+ InitSingleton();
+ Unused << aToplevelProtocol->SendInitCrashReporter(
+ CrashReporter::CurrentThreadId());
+ }
+
+ static void InitSingleton();
+
+ static void DestroySingleton();
+ static RefPtr<CrashReporterClient> GetSingleton();
+
+ private:
+ explicit CrashReporterClient();
+ ~CrashReporterClient();
+
+ private:
+ static StaticMutex sLock;
+ static StaticRefPtr<CrashReporterClient> sClientSingleton
+ MOZ_GUARDED_BY(sLock);
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_CrashReporterClient_h
diff --git a/ipc/glue/CrashReporterHelper.h b/ipc/glue/CrashReporterHelper.h
new file mode 100644
index 0000000000..e124643e3e
--- /dev/null
+++ b/ipc/glue/CrashReporterHelper.h
@@ -0,0 +1,78 @@
+/* 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/. */
+
+#ifndef mozilla_ipc_CrashReporterHelper_h
+#define mozilla_ipc_CrashReporterHelper_h
+
+#include "CrashReporterHost.h"
+#include "mozilla/UniquePtr.h"
+#include "nsExceptionHandler.h"
+#include "nsICrashService.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+namespace ipc {
+
+/**
+ * This class encapsulates the common elements of crash report handling for
+ * toplevel protocols representing processes. To use this class, you should:
+ *
+ * 1. Declare a method to initialize the crash reporter in your IPDL:
+ * `async InitCrashReporter(NativeThreadId threadId)`
+ *
+ * 2. Inherit from this class, providing the appropriate `GeckoProcessType`
+ * enum value for the template parameter PT.
+ *
+ * 3. When your protocol actor is destroyed with a reason of `AbnormalShutdown`,
+ * you should call `GenerateCrashReport(OtherPid())`. If you need the crash
+ * report ID it will be copied in the second optional parameter upon
+ * successful crash report generation.
+ */
+template <GeckoProcessType PT>
+class CrashReporterHelper {
+ public:
+ CrashReporterHelper() : mCrashReporter(nullptr) {}
+ IPCResult RecvInitCrashReporter(const CrashReporter::ThreadId& aThreadId) {
+ mCrashReporter = MakeUnique<ipc::CrashReporterHost>(PT, aThreadId);
+ return IPC_OK();
+ }
+
+ protected:
+ void GenerateCrashReport(base::ProcessId aPid,
+ nsString* aMinidumpId = nullptr) {
+ nsAutoString minidumpId;
+ if (!mCrashReporter) {
+ HandleOrphanedMinidump(aPid, minidumpId);
+ } else if (mCrashReporter->GenerateCrashReport(aPid)) {
+ minidumpId = mCrashReporter->MinidumpID();
+ }
+
+ if (aMinidumpId) {
+ *aMinidumpId = minidumpId;
+ }
+
+ mCrashReporter = nullptr;
+ }
+
+ private:
+ void HandleOrphanedMinidump(base::ProcessId aPid, nsString& aMinidumpId) {
+ if (CrashReporter::FinalizeOrphanedMinidump(aPid, PT, &aMinidumpId)) {
+ CrashReporterHost::RecordCrash(PT, nsICrashService::CRASH_TYPE_CRASH,
+ aMinidumpId);
+ } else {
+ NS_WARNING(nsPrintfCString("child process pid = %" PRIPID
+ " crashed without leaving a minidump behind",
+ aPid)
+ .get());
+ }
+ }
+
+ protected:
+ UniquePtr<ipc::CrashReporterHost> mCrashReporter;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_CrashReporterHelper_h
diff --git a/ipc/glue/CrashReporterHost.cpp b/ipc/glue/CrashReporterHost.cpp
new file mode 100644
index 0000000000..d4f0c0f968
--- /dev/null
+++ b/ipc/glue/CrashReporterHost.cpp
@@ -0,0 +1,181 @@
+/* -*- 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 "CrashReporterHost.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/Telemetry.h"
+#include "nsServiceManagerUtils.h"
+#include "nsICrashService.h"
+#include "nsXULAppAPI.h"
+#include "nsIFile.h"
+
+namespace mozilla {
+namespace ipc {
+
+CrashReporterHost::CrashReporterHost(GeckoProcessType aProcessType,
+ CrashReporter::ThreadId aThreadId)
+ : mProcessType(aProcessType),
+ mThreadId(aThreadId),
+ mStartTime(::time(nullptr)),
+ mFinalized(false) {}
+
+bool CrashReporterHost::GenerateCrashReport(base::ProcessId aPid) {
+ if (!TakeCrashedChildMinidump(aPid, nullptr)) {
+ return false;
+ }
+
+ FinalizeCrashReport();
+ RecordCrash(mProcessType, nsICrashService::CRASH_TYPE_CRASH, mDumpID);
+ return true;
+}
+
+RefPtr<nsIFile> CrashReporterHost::TakeCrashedChildMinidump(
+ base::ProcessId aPid, uint32_t* aOutSequence) {
+ CrashReporter::AnnotationTable annotations;
+ MOZ_ASSERT(!HasMinidump());
+
+ RefPtr<nsIFile> crashDump;
+ if (!CrashReporter::TakeMinidumpForChild(aPid, getter_AddRefs(crashDump),
+ annotations, aOutSequence)) {
+ return nullptr;
+ }
+ if (!AdoptMinidump(crashDump, annotations)) {
+ return nullptr;
+ }
+ return crashDump;
+}
+
+bool CrashReporterHost::AdoptMinidump(nsIFile* aFile,
+ const AnnotationTable& aAnnotations) {
+ if (!CrashReporter::GetIDFromMinidump(aFile, mDumpID)) {
+ return false;
+ }
+
+ MergeCrashAnnotations(mExtraAnnotations, aAnnotations);
+ return true;
+}
+
+void CrashReporterHost::FinalizeCrashReport() {
+ MOZ_ASSERT(!mFinalized);
+ MOZ_ASSERT(HasMinidump());
+
+ mExtraAnnotations[CrashReporter::Annotation::ProcessType] =
+ XRE_ChildProcessTypeToAnnotation(mProcessType);
+
+ char startTime[32];
+ SprintfLiteral(startTime, "%lld", static_cast<long long>(mStartTime));
+ mExtraAnnotations[CrashReporter::Annotation::StartupTime] =
+ nsDependentCString(startTime);
+
+ CrashReporter::WriteExtraFile(mDumpID, mExtraAnnotations);
+ mFinalized = true;
+}
+
+void CrashReporterHost::DeleteCrashReport() {
+ if (mFinalized && HasMinidump()) {
+ CrashReporter::DeleteMinidumpFilesForID(mDumpID, Some(u"browser"_ns));
+ }
+}
+
+/* static */
+void CrashReporterHost::RecordCrash(GeckoProcessType aProcessType,
+ int32_t aCrashType,
+ const nsString& aChildDumpID) {
+ if (!NS_IsMainThread()) {
+ RefPtr<Runnable> runnable = NS_NewRunnableFunction(
+ "ipc::CrashReporterHost::RecordCrash", [&]() -> void {
+ CrashReporterHost::RecordCrash(aProcessType, aCrashType,
+ aChildDumpID);
+ });
+ RefPtr<nsIThread> mainThread = do_GetMainThread();
+ SyncRunnable::DispatchToThread(mainThread, runnable);
+ return;
+ }
+
+ RecordCrashWithTelemetry(aProcessType, aCrashType);
+ NotifyCrashService(aProcessType, aCrashType, aChildDumpID);
+}
+
+/* static */
+void CrashReporterHost::RecordCrashWithTelemetry(GeckoProcessType aProcessType,
+ int32_t aCrashType) {
+ nsCString key;
+
+ switch (aProcessType) {
+#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \
+ process_bin_type, procinfo_typename, \
+ webidl_typename, allcaps_name) \
+ case GeckoProcessType_##enum_name: \
+ key.AssignLiteral(string_name); \
+ break;
+#include "mozilla/GeckoProcessTypes.h"
+#undef GECKO_PROCESS_TYPE
+ // We can't really hit this, thanks to the above switch, but having it
+ // here will placate the compiler.
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown process type");
+ }
+
+ Telemetry::Accumulate(Telemetry::SUBPROCESS_CRASHES_WITH_DUMP, key, 1);
+}
+
+/* static */
+void CrashReporterHost::NotifyCrashService(GeckoProcessType aProcessType,
+ int32_t aCrashType,
+ const nsString& aChildDumpID) {
+ MOZ_ASSERT(!aChildDumpID.IsEmpty());
+
+ nsCOMPtr<nsICrashService> crashService =
+ do_GetService("@mozilla.org/crashservice;1");
+ if (!crashService) {
+ return;
+ }
+
+ int32_t processType;
+
+ switch (aProcessType) {
+ case GeckoProcessType_IPDLUnitTest:
+ case GeckoProcessType_Default:
+ NS_ERROR("unknown process type");
+ return;
+ default:
+ processType = (int)aProcessType;
+ break;
+ }
+
+ RefPtr<dom::Promise> promise;
+ crashService->AddCrash(processType, aCrashType, aChildDumpID,
+ getter_AddRefs(promise));
+}
+
+void CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey,
+ bool aValue) {
+ mExtraAnnotations[aKey] = aValue ? "1"_ns : "0"_ns;
+}
+
+void CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey,
+ int aValue) {
+ nsAutoCString valueString;
+ valueString.AppendInt(aValue);
+ mExtraAnnotations[aKey] = valueString;
+}
+
+void CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey,
+ unsigned int aValue) {
+ nsAutoCString valueString;
+ valueString.AppendInt(aValue);
+ mExtraAnnotations[aKey] = valueString;
+}
+
+void CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey,
+ const nsACString& aValue) {
+ mExtraAnnotations[aKey] = aValue;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/CrashReporterHost.h b/ipc/glue/CrashReporterHost.h
new file mode 100644
index 0000000000..2abf59dce7
--- /dev/null
+++ b/ipc/glue/CrashReporterHost.h
@@ -0,0 +1,125 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_CrashReporterHost_h
+#define mozilla_ipc_CrashReporterHost_h
+
+#include <functional>
+
+#include "mozilla/UniquePtr.h"
+#include "base/process.h"
+#include "nsExceptionHandler.h"
+#include "nsThreadUtils.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+
+namespace mozilla {
+namespace ipc {
+
+// This is the newer replacement for CrashReporterParent. It is created in
+// response to a InitCrashReporter message on a top-level actor. When the
+// process terminates abnormally, the top-level should call GenerateCrashReport
+// to automatically integrate metadata.
+class CrashReporterHost {
+ typedef CrashReporter::AnnotationTable AnnotationTable;
+
+ public:
+ CrashReporterHost(GeckoProcessType aProcessType,
+ CrashReporter::ThreadId aThreadId);
+
+ // Helper function for generating a crash report for a process that probably
+ // crashed (i.e., had an AbnormalShutdown in ActorDestroy). Returns true if
+ // the process has a minidump attached and we were able to generate a report.
+ bool GenerateCrashReport(base::ProcessId aPid);
+
+ // Given an existing minidump for a crashed child process, take ownership of
+ // it from IPDL. After this, FinalizeCrashReport may be called.
+ RefPtr<nsIFile> TakeCrashedChildMinidump(base::ProcessId aPid,
+ uint32_t* aOutSequence);
+
+ // Replace the stored minidump with a new one. After this,
+ // FinalizeCrashReport may be called.
+ bool AdoptMinidump(nsIFile* aFile, const AnnotationTable& aAnnotations);
+
+ // If a minidump was already captured (e.g. via the hang reporter), this
+ // finalizes the existing report by attaching metadata, writing out the
+ // .extra file and notifying the crash service.
+ void FinalizeCrashReport();
+
+ // Delete any crash report we might have generated.
+ void DeleteCrashReport();
+
+ // Generate a paired minidump. This does not take the crash report, as
+ // GenerateCrashReport does. After this, FinalizeCrashReport may be called.
+ //
+ // This calls TakeCrashedChildMinidump and FinalizeCrashReport.
+ template <typename Toplevel>
+ bool GenerateMinidumpAndPair(Toplevel* aToplevelProtocol,
+ const nsACString& aPairName) {
+ ScopedProcessHandle childHandle;
+#ifdef XP_MACOSX
+ childHandle = aToplevelProtocol->Process()->GetChildTask();
+#else
+ if (!base::OpenPrivilegedProcessHandle(aToplevelProtocol->OtherPid(),
+ &childHandle.rwget())) {
+ NS_WARNING("Failed to open child process handle.");
+ return false;
+ }
+#endif
+
+ nsCOMPtr<nsIFile> targetDump;
+ if (!CrashReporter::CreateMinidumpsAndPair(childHandle, mThreadId,
+ aPairName, mExtraAnnotations,
+ getter_AddRefs(targetDump))) {
+ return false;
+ }
+
+ return CrashReporter::GetIDFromMinidump(targetDump, mDumpID);
+ }
+
+ void AddAnnotation(CrashReporter::Annotation aKey, bool aValue);
+ void AddAnnotation(CrashReporter::Annotation aKey, int aValue);
+ void AddAnnotation(CrashReporter::Annotation aKey, unsigned int aValue);
+ void AddAnnotation(CrashReporter::Annotation aKey, const nsACString& aValue);
+
+ bool HasMinidump() const { return !mDumpID.IsEmpty(); }
+ const nsString& MinidumpID() const {
+ MOZ_ASSERT(HasMinidump());
+ return mDumpID;
+ }
+ const nsCString& AdditionalMinidumps() const {
+ return mExtraAnnotations[CrashReporter::Annotation::additional_minidumps];
+ }
+
+ // This is a static helper function to notify the crash service that a
+ // crash has occurred and record the crash with telemetry. This can be called
+ // from any thread, and if not called from the main thread, will post a
+ // synchronous message to the main thread.
+ static void RecordCrash(GeckoProcessType aProcessType, int32_t aCrashType,
+ const nsString& aChildDumpID);
+
+ private:
+ // Get the nsICrashService crash type to use for an impending crash.
+ int32_t GetCrashType();
+
+ static void RecordCrashWithTelemetry(GeckoProcessType aProcessType,
+ int32_t aCrashType);
+ static void NotifyCrashService(GeckoProcessType aProcessType,
+ int32_t aCrashType,
+ const nsString& aChildDumpID);
+
+ private:
+ GeckoProcessType mProcessType;
+ CrashReporter::ThreadId mThreadId;
+ time_t mStartTime;
+ AnnotationTable mExtraAnnotations;
+ nsString mDumpID;
+ bool mFinalized;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_CrashReporterHost_h
diff --git a/ipc/glue/CrossProcessMutex.h b/ipc/glue/CrossProcessMutex.h
new file mode 100644
index 0000000000..7376fb1b15
--- /dev/null
+++ b/ipc/glue/CrossProcessMutex.h
@@ -0,0 +1,118 @@
+/* -*- 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/. */
+
+#ifndef mozilla_CrossProcessMutex_h
+#define mozilla_CrossProcessMutex_h
+
+#include "base/process.h"
+#include "mozilla/Mutex.h"
+
+#if defined(OS_WIN)
+# include "mozilla/UniquePtrExtensions.h"
+#endif
+#if !defined(OS_WIN) && !defined(OS_NETBSD) && !defined(OS_OPENBSD)
+# include <pthread.h>
+# include "mozilla/ipc/SharedMemoryBasic.h"
+# include "mozilla/Atomics.h"
+#endif
+
+namespace IPC {
+template <typename T>
+struct ParamTraits;
+} // namespace IPC
+
+//
+// Provides:
+//
+// - CrossProcessMutex, a non-recursive mutex that can be shared across
+// processes
+// - CrossProcessMutexAutoLock, an RAII class for ensuring that Mutexes are
+// properly locked and unlocked
+//
+// Using CrossProcessMutexAutoLock/CrossProcessMutexAutoUnlock is MUCH
+// preferred to making bare calls to CrossProcessMutex.Lock and Unlock.
+//
+namespace mozilla {
+#if defined(OS_WIN)
+typedef mozilla::UniqueFileHandle CrossProcessMutexHandle;
+#elif !defined(OS_NETBSD) && !defined(OS_OPENBSD)
+typedef mozilla::ipc::SharedMemoryBasic::Handle CrossProcessMutexHandle;
+#else
+// Stub for other platforms. We can't use uintptr_t here since different
+// processes could disagree on its size.
+typedef uintptr_t CrossProcessMutexHandle;
+#endif
+
+class CrossProcessMutex {
+ public:
+ /**
+ * CrossProcessMutex
+ * @param name A name which can reference this lock (currently unused)
+ **/
+ explicit CrossProcessMutex(const char* aName);
+ /**
+ * CrossProcessMutex
+ * @param handle A handle of an existing cross process mutex that can be
+ * opened.
+ */
+ explicit CrossProcessMutex(CrossProcessMutexHandle aHandle);
+
+ /**
+ * ~CrossProcessMutex
+ **/
+ ~CrossProcessMutex();
+
+ /**
+ * Lock
+ * This will lock the mutex. Any other thread in any other process that
+ * has access to this mutex calling lock will block execution until the
+ * initial caller of lock has made a call to Unlock.
+ *
+ * If the owning process is terminated unexpectedly the mutex will be
+ * released.
+ **/
+ void Lock();
+
+ /**
+ * Unlock
+ * This will unlock the mutex. A single thread currently waiting on a lock
+ * call will resume execution and aquire ownership of the lock. No
+ * guarantees are made as to the order in which waiting threads will resume
+ * execution.
+ **/
+ void Unlock();
+
+ /**
+ * CloneHandle
+ * This function is called to generate a serializable structure that can
+ * be sent to the specified process and opened on the other side.
+ *
+ * @returns A handle that can be shared to another process
+ */
+ CrossProcessMutexHandle CloneHandle();
+
+ private:
+ friend struct IPC::ParamTraits<CrossProcessMutex>;
+
+ CrossProcessMutex();
+ CrossProcessMutex(const CrossProcessMutex&);
+ CrossProcessMutex& operator=(const CrossProcessMutex&);
+
+#if defined(OS_WIN)
+ HANDLE mMutex;
+#elif !defined(OS_NETBSD) && !defined(OS_OPENBSD)
+ RefPtr<mozilla::ipc::SharedMemoryBasic> mSharedBuffer;
+ pthread_mutex_t* mMutex;
+ mozilla::Atomic<int32_t>* mCount;
+#endif
+};
+
+typedef detail::BaseAutoLock<CrossProcessMutex&> CrossProcessMutexAutoLock;
+typedef detail::BaseAutoUnlock<CrossProcessMutex&> CrossProcessMutexAutoUnlock;
+
+} // namespace mozilla
+
+#endif
diff --git a/ipc/glue/CrossProcessMutex_posix.cpp b/ipc/glue/CrossProcessMutex_posix.cpp
new file mode 100644
index 0000000000..c37fcb797d
--- /dev/null
+++ b/ipc/glue/CrossProcessMutex_posix.cpp
@@ -0,0 +1,140 @@
+/* -*- 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 "CrossProcessMutex.h"
+#include "mozilla/Unused.h"
+#include "nsDebug.h"
+#include "nsISupportsImpl.h"
+
+namespace {
+
+struct MutexData {
+ pthread_mutex_t mMutex;
+ mozilla::Atomic<int32_t> mCount;
+};
+
+} // namespace
+
+namespace mozilla {
+
+static void InitMutex(pthread_mutex_t* mMutex) {
+ pthread_mutexattr_t mutexAttributes;
+ pthread_mutexattr_init(&mutexAttributes);
+ // Make the mutex reentrant so it behaves the same as a win32 mutex
+ if (pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE)) {
+ MOZ_CRASH();
+ }
+ if (pthread_mutexattr_setpshared(&mutexAttributes, PTHREAD_PROCESS_SHARED)) {
+ MOZ_CRASH();
+ }
+
+ if (pthread_mutex_init(mMutex, &mutexAttributes)) {
+ MOZ_CRASH();
+ }
+}
+
+CrossProcessMutex::CrossProcessMutex(const char*)
+ : mMutex(nullptr), mCount(nullptr) {
+#if defined(MOZ_SANDBOX)
+ // POSIX mutexes in shared memory aren't guaranteed to be safe - and
+ // they specifically are not on Linux.
+ MOZ_RELEASE_ASSERT(false);
+#endif
+ mSharedBuffer = new ipc::SharedMemoryBasic;
+ if (!mSharedBuffer->Create(sizeof(MutexData))) {
+ MOZ_CRASH();
+ }
+
+ if (!mSharedBuffer->Map(sizeof(MutexData))) {
+ MOZ_CRASH();
+ }
+
+ MutexData* data = static_cast<MutexData*>(mSharedBuffer->memory());
+
+ if (!data) {
+ MOZ_CRASH();
+ }
+
+ mMutex = &(data->mMutex);
+ mCount = &(data->mCount);
+
+ *mCount = 1;
+ InitMutex(mMutex);
+
+ MOZ_COUNT_CTOR(CrossProcessMutex);
+}
+
+CrossProcessMutex::CrossProcessMutex(CrossProcessMutexHandle aHandle)
+ : mMutex(nullptr), mCount(nullptr) {
+ mSharedBuffer = new ipc::SharedMemoryBasic;
+
+ if (!mSharedBuffer->IsHandleValid(aHandle)) {
+ MOZ_CRASH();
+ }
+
+ if (!mSharedBuffer->SetHandle(std::move(aHandle),
+ ipc::SharedMemory::RightsReadWrite)) {
+ MOZ_CRASH();
+ }
+
+ if (!mSharedBuffer->Map(sizeof(MutexData))) {
+ MOZ_CRASH();
+ }
+
+ MutexData* data = static_cast<MutexData*>(mSharedBuffer->memory());
+
+ if (!data) {
+ MOZ_CRASH();
+ }
+
+ mMutex = &(data->mMutex);
+ mCount = &(data->mCount);
+ int32_t count = (*mCount)++;
+
+ if (count == 0) {
+ // The other side has already let go of their CrossProcessMutex, so now
+ // mMutex is garbage. We need to re-initialize it.
+ InitMutex(mMutex);
+ }
+
+ MOZ_COUNT_CTOR(CrossProcessMutex);
+}
+
+CrossProcessMutex::~CrossProcessMutex() {
+ int32_t count = --(*mCount);
+
+ if (count == 0) {
+ // Nothing can be done if the destroy fails so ignore return code.
+ Unused << pthread_mutex_destroy(mMutex);
+ }
+
+ MOZ_COUNT_DTOR(CrossProcessMutex);
+}
+
+void CrossProcessMutex::Lock() {
+ MOZ_ASSERT(*mCount > 0, "Attempting to lock mutex with zero ref count");
+ pthread_mutex_lock(mMutex);
+}
+
+void CrossProcessMutex::Unlock() {
+ MOZ_ASSERT(*mCount > 0, "Attempting to unlock mutex with zero ref count");
+ pthread_mutex_unlock(mMutex);
+}
+
+CrossProcessMutexHandle CrossProcessMutex::CloneHandle() {
+ CrossProcessMutexHandle result = ipc::SharedMemoryBasic::NULLHandle();
+
+ if (mSharedBuffer) {
+ result = mSharedBuffer->CloneHandle();
+ if (!result) {
+ MOZ_CRASH();
+ }
+ }
+
+ return result;
+}
+
+} // namespace mozilla
diff --git a/ipc/glue/CrossProcessMutex_unimplemented.cpp b/ipc/glue/CrossProcessMutex_unimplemented.cpp
new file mode 100644
index 0000000000..70b63d7ea9
--- /dev/null
+++ b/ipc/glue/CrossProcessMutex_unimplemented.cpp
@@ -0,0 +1,46 @@
+/* -*- 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 "CrossProcessMutex.h"
+
+#include "nsDebug.h"
+
+namespace mozilla {
+
+CrossProcessMutex::CrossProcessMutex(const char*) {
+ MOZ_CRASH("Cross-process mutices not allowed on this platform.");
+}
+
+CrossProcessMutex::CrossProcessMutex(CrossProcessMutexHandle) {
+ MOZ_CRASH("Cross-process mutices not allowed on this platform.");
+}
+
+CrossProcessMutex::~CrossProcessMutex() {
+ MOZ_CRASH(
+ "Cross-process mutices not allowed on this platform - woah! We should've "
+ "aborted by now!");
+}
+
+void CrossProcessMutex::Lock() {
+ MOZ_CRASH(
+ "Cross-process mutices not allowed on this platform - woah! We should've "
+ "aborted by now!");
+}
+
+void CrossProcessMutex::Unlock() {
+ MOZ_CRASH(
+ "Cross-process mutices not allowed on this platform - woah! We should've "
+ "aborted by now!");
+}
+
+CrossProcessMutexHandle CrossProcessMutex::CloneHandle() {
+ MOZ_CRASH(
+ "Cross-process mutices not allowed on this platform - woah! We should've "
+ "aborted by now!");
+ return 0;
+}
+
+} // namespace mozilla
diff --git a/ipc/glue/CrossProcessMutex_windows.cpp b/ipc/glue/CrossProcessMutex_windows.cpp
new file mode 100644
index 0000000000..d4bbe2f2ba
--- /dev/null
+++ b/ipc/glue/CrossProcessMutex_windows.cpp
@@ -0,0 +1,65 @@
+/* -*- 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 <windows.h>
+
+#include "base/process_util.h"
+#include "CrossProcessMutex.h"
+#include "nsDebug.h"
+#include "nsISupportsImpl.h"
+#include "ProtocolUtils.h"
+
+using base::GetCurrentProcessHandle;
+using base::ProcessHandle;
+
+namespace mozilla {
+
+CrossProcessMutex::CrossProcessMutex(const char*) {
+ // We explicitly share this using DuplicateHandle, we do -not- want this to
+ // be inherited by child processes by default! So no security attributes are
+ // given.
+ mMutex = ::CreateMutexA(nullptr, FALSE, nullptr);
+ if (!mMutex) {
+ MOZ_CRASH("This shouldn't happen - failed to create mutex!");
+ }
+ MOZ_COUNT_CTOR(CrossProcessMutex);
+}
+
+CrossProcessMutex::CrossProcessMutex(CrossProcessMutexHandle aHandle) {
+ DWORD flags;
+ if (!::GetHandleInformation(aHandle.get(), &flags)) {
+ MOZ_CRASH("Attempt to construct a mutex from an invalid handle!");
+ }
+ mMutex = aHandle.release();
+ MOZ_COUNT_CTOR(CrossProcessMutex);
+}
+
+CrossProcessMutex::~CrossProcessMutex() {
+ NS_ASSERTION(mMutex, "Improper construction of mutex or double free.");
+ ::CloseHandle(mMutex);
+ MOZ_COUNT_DTOR(CrossProcessMutex);
+}
+
+void CrossProcessMutex::Lock() {
+ NS_ASSERTION(mMutex, "Improper construction of mutex.");
+ ::WaitForSingleObject(mMutex, INFINITE);
+}
+
+void CrossProcessMutex::Unlock() {
+ NS_ASSERTION(mMutex, "Improper construction of mutex.");
+ ::ReleaseMutex(mMutex);
+}
+
+CrossProcessMutexHandle CrossProcessMutex::CloneHandle() {
+ HANDLE newHandle;
+ if (!::DuplicateHandle(GetCurrentProcess(), mMutex, GetCurrentProcess(),
+ &newHandle, 0, false, DUPLICATE_SAME_ACCESS)) {
+ return nullptr;
+ }
+ return mozilla::UniqueFileHandle(newHandle);
+}
+
+} // namespace mozilla
diff --git a/ipc/glue/CrossProcessSemaphore.h b/ipc/glue/CrossProcessSemaphore.h
new file mode 100644
index 0000000000..297552853c
--- /dev/null
+++ b/ipc/glue/CrossProcessSemaphore.h
@@ -0,0 +1,118 @@
+/* -*- 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/. */
+
+#ifndef mozilla_CrossProcessSemaphore_h
+#define mozilla_CrossProcessSemaphore_h
+
+#include "base/process.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Maybe.h"
+
+#if defined(OS_WIN)
+# include "mozilla/UniquePtrExtensions.h"
+#endif
+#if !defined(OS_WIN) && !defined(OS_MACOSX)
+# include <pthread.h>
+# include <semaphore.h>
+# include "mozilla/ipc/SharedMemoryBasic.h"
+# include "mozilla/Atomics.h"
+#endif
+
+namespace IPC {
+template <typename T>
+struct ParamTraits;
+} // namespace IPC
+
+//
+// Provides:
+//
+// - CrossProcessSemaphore, a semaphore that can be shared across processes
+namespace mozilla {
+
+template <typename T>
+inline bool IsHandleValid(const T& handle) {
+ return bool(handle);
+}
+
+#if defined(OS_WIN)
+typedef mozilla::UniqueFileHandle CrossProcessSemaphoreHandle;
+#elif !defined(OS_MACOSX)
+typedef mozilla::ipc::SharedMemoryBasic::Handle CrossProcessSemaphoreHandle;
+
+template <>
+inline bool IsHandleValid<CrossProcessSemaphoreHandle>(
+ const CrossProcessSemaphoreHandle& handle) {
+ return !(handle == mozilla::ipc::SharedMemoryBasic::NULLHandle());
+}
+#else
+// Stub for other platforms. We can't use uintptr_t here since different
+// processes could disagree on its size.
+typedef uintptr_t CrossProcessSemaphoreHandle;
+#endif
+
+class CrossProcessSemaphore {
+ public:
+ /**
+ * CrossProcessSemaphore
+ * @param name A name which can reference this lock (currently unused)
+ **/
+ static CrossProcessSemaphore* Create(const char* aName,
+ uint32_t aInitialValue);
+
+ /**
+ * CrossProcessSemaphore
+ * @param handle A handle of an existing cross process semaphore that can be
+ * opened.
+ */
+ static CrossProcessSemaphore* Create(CrossProcessSemaphoreHandle aHandle);
+
+ ~CrossProcessSemaphore();
+
+ /**
+ * Decrements the current value of the semaphore. This will block if the
+ * current value of the semaphore is 0, up to a maximum of aWaitTime (if
+ * specified).
+ * Returns true if the semaphore was succesfully decremented, false otherwise.
+ **/
+ bool Wait(const Maybe<TimeDuration>& aWaitTime = Nothing());
+
+ /**
+ * Increments the current value of the semaphore.
+ **/
+ void Signal();
+
+ /**
+ * CloneHandle
+ * This function is called to generate a serializable structure that can
+ * be sent to the specified process and opened on the other side.
+ *
+ * @returns A handle that can be shared to another process
+ */
+ CrossProcessSemaphoreHandle CloneHandle();
+
+ void CloseHandle();
+
+ private:
+ friend struct IPC::ParamTraits<CrossProcessSemaphore>;
+
+ CrossProcessSemaphore();
+ CrossProcessSemaphore(const CrossProcessSemaphore&);
+ CrossProcessSemaphore& operator=(const CrossProcessSemaphore&);
+
+#if defined(OS_WIN)
+ explicit CrossProcessSemaphore(HANDLE aSemaphore);
+
+ HANDLE mSemaphore;
+#elif !defined(OS_MACOSX)
+ RefPtr<mozilla::ipc::SharedMemoryBasic> mSharedBuffer;
+ sem_t* mSemaphore;
+ mozilla::Atomic<int32_t>* mRefCount;
+#endif
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/ipc/glue/CrossProcessSemaphore_posix.cpp b/ipc/glue/CrossProcessSemaphore_posix.cpp
new file mode 100644
index 0000000000..d74e9326f9
--- /dev/null
+++ b/ipc/glue/CrossProcessSemaphore_posix.cpp
@@ -0,0 +1,164 @@
+/* -*- 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 "CrossProcessSemaphore.h"
+#include "mozilla/Unused.h"
+#include "nsDebug.h"
+#include "nsISupportsImpl.h"
+#include <errno.h>
+
+static const uint64_t kNsPerMs = 1000000;
+static const uint64_t kNsPerSec = 1000000000;
+
+namespace {
+
+struct SemaphoreData {
+ sem_t mSemaphore;
+ mozilla::Atomic<int32_t> mRefCount;
+ uint32_t mInitialValue;
+};
+
+} // namespace
+
+namespace mozilla {
+
+/* static */
+CrossProcessSemaphore* CrossProcessSemaphore::Create(const char*,
+ uint32_t aInitialValue) {
+ RefPtr<ipc::SharedMemoryBasic> sharedBuffer = new ipc::SharedMemoryBasic;
+ if (!sharedBuffer->Create(sizeof(SemaphoreData))) {
+ return nullptr;
+ }
+
+ if (!sharedBuffer->Map(sizeof(SemaphoreData))) {
+ return nullptr;
+ }
+
+ SemaphoreData* data = static_cast<SemaphoreData*>(sharedBuffer->memory());
+
+ if (!data) {
+ return nullptr;
+ }
+
+ if (sem_init(&data->mSemaphore, 1, aInitialValue)) {
+ return nullptr;
+ }
+
+ CrossProcessSemaphore* sem = new CrossProcessSemaphore;
+ sem->mSharedBuffer = sharedBuffer;
+ sem->mSemaphore = &data->mSemaphore;
+ sem->mRefCount = &data->mRefCount;
+ *sem->mRefCount = 1;
+
+ data->mInitialValue = aInitialValue;
+
+ return sem;
+}
+
+/* static */
+CrossProcessSemaphore* CrossProcessSemaphore::Create(
+ CrossProcessSemaphoreHandle aHandle) {
+ RefPtr<ipc::SharedMemoryBasic> sharedBuffer = new ipc::SharedMemoryBasic;
+
+ if (!sharedBuffer->IsHandleValid(aHandle)) {
+ return nullptr;
+ }
+
+ if (!sharedBuffer->SetHandle(std::move(aHandle),
+ ipc::SharedMemory::RightsReadWrite)) {
+ return nullptr;
+ }
+
+ if (!sharedBuffer->Map(sizeof(SemaphoreData))) {
+ return nullptr;
+ }
+
+ sharedBuffer->CloseHandle();
+
+ SemaphoreData* data = static_cast<SemaphoreData*>(sharedBuffer->memory());
+
+ if (!data) {
+ return nullptr;
+ }
+
+ int32_t oldCount = data->mRefCount++;
+ if (oldCount == 0) {
+ // The other side has already let go of their CrossProcessSemaphore, so now
+ // mSemaphore is garbage. We need to re-initialize it.
+ if (sem_init(&data->mSemaphore, 1, data->mInitialValue)) {
+ data->mRefCount--;
+ return nullptr;
+ }
+ }
+
+ CrossProcessSemaphore* sem = new CrossProcessSemaphore;
+ sem->mSharedBuffer = sharedBuffer;
+ sem->mSemaphore = &data->mSemaphore;
+ sem->mRefCount = &data->mRefCount;
+ return sem;
+}
+
+CrossProcessSemaphore::CrossProcessSemaphore()
+ : mSemaphore(nullptr), mRefCount(nullptr) {
+ MOZ_COUNT_CTOR(CrossProcessSemaphore);
+}
+
+CrossProcessSemaphore::~CrossProcessSemaphore() {
+ int32_t oldCount = --(*mRefCount);
+
+ if (oldCount == 0) {
+ // Nothing can be done if the destroy fails so ignore return code.
+ Unused << sem_destroy(mSemaphore);
+ }
+
+ MOZ_COUNT_DTOR(CrossProcessSemaphore);
+}
+
+bool CrossProcessSemaphore::Wait(const Maybe<TimeDuration>& aWaitTime) {
+ MOZ_ASSERT(*mRefCount > 0,
+ "Attempting to wait on a semaphore with zero ref count");
+ int ret;
+ if (aWaitTime.isSome()) {
+ struct timespec ts;
+ if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
+ return false;
+ }
+
+ ts.tv_nsec += (kNsPerMs * aWaitTime->ToMilliseconds());
+ ts.tv_sec += ts.tv_nsec / kNsPerSec;
+ ts.tv_nsec %= kNsPerSec;
+
+ while ((ret = sem_timedwait(mSemaphore, &ts)) == -1 && errno == EINTR) {
+ }
+ } else {
+ while ((ret = sem_wait(mSemaphore)) == -1 && errno == EINTR) {
+ }
+ }
+ return ret == 0;
+}
+
+void CrossProcessSemaphore::Signal() {
+ MOZ_ASSERT(*mRefCount > 0,
+ "Attempting to signal a semaphore with zero ref count");
+ sem_post(mSemaphore);
+}
+
+CrossProcessSemaphoreHandle CrossProcessSemaphore::CloneHandle() {
+ CrossProcessSemaphoreHandle result = ipc::SharedMemoryBasic::NULLHandle();
+
+ if (mSharedBuffer) {
+ result = mSharedBuffer->CloneHandle();
+ if (!result) {
+ MOZ_CRASH();
+ }
+ }
+
+ return result;
+}
+
+void CrossProcessSemaphore::CloseHandle() { mSharedBuffer->CloseHandle(); }
+
+} // namespace mozilla
diff --git a/ipc/glue/CrossProcessSemaphore_unimplemented.cpp b/ipc/glue/CrossProcessSemaphore_unimplemented.cpp
new file mode 100644
index 0000000000..822aeb76eb
--- /dev/null
+++ b/ipc/glue/CrossProcessSemaphore_unimplemented.cpp
@@ -0,0 +1,64 @@
+/* -*- 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 "CrossProcessSemaphore.h"
+
+#include "nsDebug.h"
+
+namespace mozilla {
+
+/* static */
+CrossProcessSemaphore* CrossProcessSemaphore::Create(const char*, uint32_t) {
+ MOZ_CRASH("Cross-process semaphores not allowed on this platform.");
+ return nullptr;
+}
+
+/* static */
+CrossProcessSemaphore* CrossProcessSemaphore::Create(
+ CrossProcessSemaphoreHandle) {
+ MOZ_CRASH("Cross-process semaphores not allowed on this platform.");
+ return nullptr;
+}
+
+CrossProcessSemaphore::CrossProcessSemaphore() {
+ MOZ_CRASH(
+ "Cross-process semaphores not allowed on this platform - woah! We "
+ "should've aborted by now!");
+}
+
+CrossProcessSemaphore::~CrossProcessSemaphore() {
+ MOZ_CRASH(
+ "Cross-process semaphores not allowed on this platform - woah! We "
+ "should've aborted by now!");
+}
+
+bool CrossProcessSemaphore::Wait(const Maybe<TimeDuration>& aWaitTime) {
+ MOZ_CRASH(
+ "Cross-process semaphores not allowed on this platform - woah! We "
+ "should've aborted by now!");
+ return false;
+}
+
+void CrossProcessSemaphore::Signal() {
+ MOZ_CRASH(
+ "Cross-process semaphores not allowed on this platform - woah! We "
+ "should've aborted by now!");
+}
+
+CrossProcessSemaphoreHandle CrossProcessSemaphore::CloneHandle() {
+ MOZ_CRASH(
+ "Cross-process semaphores not allowed on this platform - woah! We "
+ "should've aborted by now!");
+ return 0;
+}
+
+void CrossProcessSemaphore::CloseHandle() {
+ MOZ_CRASH(
+ "Cross-process semaphores not allowed on this platform - woah! We "
+ "should've aborted by now!");
+}
+
+} // namespace mozilla
diff --git a/ipc/glue/CrossProcessSemaphore_windows.cpp b/ipc/glue/CrossProcessSemaphore_windows.cpp
new file mode 100644
index 0000000000..70644b7d51
--- /dev/null
+++ b/ipc/glue/CrossProcessSemaphore_windows.cpp
@@ -0,0 +1,83 @@
+/* -*- 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 <windows.h>
+
+#include "base/process_util.h"
+#include "CrossProcessSemaphore.h"
+#include "nsDebug.h"
+#include "nsISupportsImpl.h"
+#include "ProtocolUtils.h"
+
+using base::GetCurrentProcessHandle;
+using base::ProcessHandle;
+
+namespace mozilla {
+
+/* static */
+CrossProcessSemaphore* CrossProcessSemaphore::Create(const char*,
+ uint32_t aInitialValue) {
+ // We explicitly share this using DuplicateHandle, we do -not- want this to
+ // be inherited by child processes by default! So no security attributes are
+ // given.
+ HANDLE semaphore =
+ ::CreateSemaphoreA(nullptr, aInitialValue, 0x7fffffff, nullptr);
+ if (!semaphore) {
+ return nullptr;
+ }
+ return new CrossProcessSemaphore(semaphore);
+}
+
+/* static */
+CrossProcessSemaphore* CrossProcessSemaphore::Create(
+ CrossProcessSemaphoreHandle aHandle) {
+ DWORD flags;
+ if (!::GetHandleInformation(aHandle.get(), &flags)) {
+ return nullptr;
+ }
+
+ return new CrossProcessSemaphore(aHandle.release());
+}
+
+CrossProcessSemaphore::CrossProcessSemaphore(HANDLE aSemaphore)
+ : mSemaphore(aSemaphore) {
+ MOZ_COUNT_CTOR(CrossProcessSemaphore);
+}
+
+CrossProcessSemaphore::~CrossProcessSemaphore() {
+ MOZ_ASSERT(mSemaphore, "Improper construction of semaphore or double free.");
+ ::CloseHandle(mSemaphore);
+ MOZ_COUNT_DTOR(CrossProcessSemaphore);
+}
+
+bool CrossProcessSemaphore::Wait(const Maybe<TimeDuration>& aWaitTime) {
+ MOZ_ASSERT(mSemaphore, "Improper construction of semaphore.");
+ HRESULT hr = ::WaitForSingleObject(
+ mSemaphore, aWaitTime.isSome() ? aWaitTime->ToMilliseconds() : INFINITE);
+ return hr == WAIT_OBJECT_0;
+}
+
+void CrossProcessSemaphore::Signal() {
+ MOZ_ASSERT(mSemaphore, "Improper construction of semaphore.");
+ ::ReleaseSemaphore(mSemaphore, 1, nullptr);
+}
+
+CrossProcessSemaphoreHandle CrossProcessSemaphore::CloneHandle() {
+ HANDLE newHandle;
+ bool succeeded =
+ ::DuplicateHandle(GetCurrentProcess(), mSemaphore, GetCurrentProcess(),
+ &newHandle, 0, false, DUPLICATE_SAME_ACCESS);
+
+ if (!succeeded) {
+ return nullptr;
+ }
+
+ return UniqueFileHandle(newHandle);
+}
+
+void CrossProcessSemaphore::CloseHandle() {}
+
+} // namespace mozilla
diff --git a/ipc/glue/DataPipe.cpp b/ipc/glue/DataPipe.cpp
new file mode 100644
index 0000000000..55e38ddd67
--- /dev/null
+++ b/ipc/glue/DataPipe.cpp
@@ -0,0 +1,741 @@
+/* -*- 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 "DataPipe.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MoveOnlyFunction.h"
+#include "mozilla/ipc/InputStreamParams.h"
+#include "nsIAsyncInputStream.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace ipc {
+
+LazyLogModule gDataPipeLog("DataPipe");
+
+namespace data_pipe_detail {
+
+// Helper for queueing up actions to be run once the mutex has been unlocked.
+// Actions will be run in-order.
+class MOZ_SCOPED_CAPABILITY DataPipeAutoLock {
+ public:
+ explicit DataPipeAutoLock(Mutex& aMutex) MOZ_CAPABILITY_ACQUIRE(aMutex)
+ : mMutex(aMutex) {
+ mMutex.Lock();
+ }
+ DataPipeAutoLock(const DataPipeAutoLock&) = delete;
+ DataPipeAutoLock& operator=(const DataPipeAutoLock&) = delete;
+
+ template <typename F>
+ void AddUnlockAction(F aAction) {
+ mActions.AppendElement(std::move(aAction));
+ }
+
+ ~DataPipeAutoLock() MOZ_CAPABILITY_RELEASE() {
+ mMutex.Unlock();
+ for (auto& action : mActions) {
+ action();
+ }
+ }
+
+ private:
+ Mutex& mMutex;
+ AutoTArray<MoveOnlyFunction<void()>, 4> mActions;
+};
+
+static void DoNotifyOnUnlock(DataPipeAutoLock& aLock,
+ already_AddRefed<nsIRunnable> aCallback,
+ already_AddRefed<nsIEventTarget> aTarget) {
+ nsCOMPtr<nsIRunnable> callback{std::move(aCallback)};
+ nsCOMPtr<nsIEventTarget> target{std::move(aTarget)};
+ if (callback) {
+ aLock.AddUnlockAction(
+ [callback = std::move(callback), target = std::move(target)]() mutable {
+ if (target) {
+ target->Dispatch(callback.forget());
+ } else {
+ NS_DispatchBackgroundTask(callback.forget());
+ }
+ });
+ }
+}
+
+class DataPipeLink : public NodeController::PortObserver {
+ public:
+ DataPipeLink(bool aReceiverSide, std::shared_ptr<Mutex> aMutex,
+ ScopedPort aPort, SharedMemory* aShmem, uint32_t aCapacity,
+ nsresult aPeerStatus, uint32_t aOffset, uint32_t aAvailable)
+ : mMutex(std::move(aMutex)),
+ mPort(std::move(aPort)),
+ mShmem(aShmem),
+ mCapacity(aCapacity),
+ mReceiverSide(aReceiverSide),
+ mPeerStatus(aPeerStatus),
+ mOffset(aOffset),
+ mAvailable(aAvailable) {}
+
+ void Init() MOZ_EXCLUDES(*mMutex) {
+ {
+ DataPipeAutoLock lock(*mMutex);
+ if (NS_FAILED(mPeerStatus)) {
+ return;
+ }
+ MOZ_ASSERT(mPort.IsValid());
+ mPort.Controller()->SetPortObserver(mPort.Port(), this);
+ }
+ OnPortStatusChanged();
+ }
+
+ void OnPortStatusChanged() final MOZ_EXCLUDES(*mMutex);
+
+ // Add a task to notify the callback after `aLock` is unlocked.
+ //
+ // This method is safe to call multiple times, as after the first time it is
+ // called, `mCallback` will be cleared.
+ void NotifyOnUnlock(DataPipeAutoLock& aLock) MOZ_REQUIRES(*mMutex) {
+ DoNotifyOnUnlock(aLock, mCallback.forget(), mCallbackTarget.forget());
+ }
+
+ void SendBytesConsumedOnUnlock(DataPipeAutoLock& aLock, uint32_t aBytes)
+ MOZ_REQUIRES(*mMutex) {
+ MOZ_LOG(gDataPipeLog, LogLevel::Verbose,
+ ("SendOnUnlock CONSUMED(%u) %s", aBytes, Describe(aLock).get()));
+ if (NS_FAILED(mPeerStatus)) {
+ return;
+ }
+
+ // `mPort` may be destroyed by `SetPeerError` after the DataPipe is unlocked
+ // but before we send the message. The strong controller and port references
+ // will allow us to try to send the message anyway, and it will be safely
+ // dropped if the port has already been closed. CONSUMED messages are safe
+ // to deliver out-of-order, so we don't need to worry about ordering here.
+ aLock.AddUnlockAction([controller = RefPtr{mPort.Controller()},
+ port = mPort.Port(), aBytes]() mutable {
+ auto message = MakeUnique<IPC::Message>(
+ MSG_ROUTING_NONE, DATA_PIPE_BYTES_CONSUMED_MESSAGE_TYPE);
+ IPC::MessageWriter writer(*message);
+ WriteParam(&writer, aBytes);
+ controller->SendUserMessage(port, std::move(message));
+ });
+ }
+
+ void SetPeerError(DataPipeAutoLock& aLock, nsresult aStatus,
+ bool aSendClosed = false) MOZ_REQUIRES(*mMutex) {
+ MOZ_LOG(gDataPipeLog, LogLevel::Debug,
+ ("SetPeerError(%s%s) %s", GetStaticErrorName(aStatus),
+ aSendClosed ? ", send" : "", Describe(aLock).get()));
+ // The pipe was closed or errored. Clear the observer reference back
+ // to this type from the port layer, and ensure we notify waiters.
+ MOZ_ASSERT(NS_SUCCEEDED(mPeerStatus));
+ mPeerStatus = NS_SUCCEEDED(aStatus) ? NS_BASE_STREAM_CLOSED : aStatus;
+ aLock.AddUnlockAction([port = std::move(mPort), aStatus, aSendClosed] {
+ if (aSendClosed) {
+ auto message = MakeUnique<IPC::Message>(MSG_ROUTING_NONE,
+ DATA_PIPE_CLOSED_MESSAGE_TYPE);
+ IPC::MessageWriter writer(*message);
+ WriteParam(&writer, aStatus);
+ port.Controller()->SendUserMessage(port.Port(), std::move(message));
+ }
+ // The `ScopedPort` being destroyed with this action will close it,
+ // clearing the observer reference from the ports layer.
+ });
+ NotifyOnUnlock(aLock);
+ }
+
+ nsCString Describe(DataPipeAutoLock& aLock) const MOZ_REQUIRES(*mMutex) {
+ return nsPrintfCString(
+ "[%s(%p) c=%u e=%s o=%u a=%u, cb=%s]",
+ mReceiverSide ? "Receiver" : "Sender", this, mCapacity,
+ GetStaticErrorName(mPeerStatus), mOffset, mAvailable,
+ mCallback ? (mCallbackClosureOnly ? "clo" : "yes") : "no");
+ }
+
+ // This mutex is shared with the `DataPipeBase` which owns this
+ // `DataPipeLink`.
+ std::shared_ptr<Mutex> mMutex;
+
+ ScopedPort mPort MOZ_GUARDED_BY(*mMutex);
+ const RefPtr<SharedMemory> mShmem;
+ const uint32_t mCapacity;
+ const bool mReceiverSide;
+
+ bool mProcessingSegment MOZ_GUARDED_BY(*mMutex) = false;
+
+ nsresult mPeerStatus MOZ_GUARDED_BY(*mMutex) = NS_OK;
+ uint32_t mOffset MOZ_GUARDED_BY(*mMutex) = 0;
+ uint32_t mAvailable MOZ_GUARDED_BY(*mMutex) = 0;
+
+ bool mCallbackClosureOnly MOZ_GUARDED_BY(*mMutex) = false;
+ nsCOMPtr<nsIRunnable> mCallback MOZ_GUARDED_BY(*mMutex);
+ nsCOMPtr<nsIEventTarget> mCallbackTarget MOZ_GUARDED_BY(*mMutex);
+};
+
+void DataPipeLink::OnPortStatusChanged() {
+ DataPipeAutoLock lock(*mMutex);
+
+ while (NS_SUCCEEDED(mPeerStatus)) {
+ UniquePtr<IPC::Message> message;
+ if (!mPort.Controller()->GetMessage(mPort.Port(), &message)) {
+ SetPeerError(lock, NS_BASE_STREAM_CLOSED);
+ return;
+ }
+ if (!message) {
+ return; // no more messages
+ }
+
+ IPC::MessageReader reader(*message);
+ switch (message->type()) {
+ case DATA_PIPE_CLOSED_MESSAGE_TYPE: {
+ nsresult status = NS_OK;
+ if (!ReadParam(&reader, &status)) {
+ NS_WARNING("Unable to parse nsresult error from peer");
+ status = NS_ERROR_UNEXPECTED;
+ }
+ MOZ_LOG(gDataPipeLog, LogLevel::Debug,
+ ("Got CLOSED(%s) %s", GetStaticErrorName(status),
+ Describe(lock).get()));
+ SetPeerError(lock, status);
+ return;
+ }
+ case DATA_PIPE_BYTES_CONSUMED_MESSAGE_TYPE: {
+ uint32_t consumed = 0;
+ if (!ReadParam(&reader, &consumed)) {
+ NS_WARNING("Unable to parse bytes consumed from peer");
+ SetPeerError(lock, NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ MOZ_LOG(gDataPipeLog, LogLevel::Verbose,
+ ("Got CONSUMED(%u) %s", consumed, Describe(lock).get()));
+ auto newAvailable = CheckedUint32{mAvailable} + consumed;
+ if (!newAvailable.isValid() || newAvailable.value() > mCapacity) {
+ NS_WARNING("Illegal bytes consumed message received from peer");
+ SetPeerError(lock, NS_ERROR_UNEXPECTED);
+ return;
+ }
+ mAvailable = newAvailable.value();
+ if (!mCallbackClosureOnly) {
+ NotifyOnUnlock(lock);
+ }
+ break;
+ }
+ default: {
+ NS_WARNING("Illegal message type received from peer");
+ SetPeerError(lock, NS_ERROR_UNEXPECTED);
+ return;
+ }
+ }
+ }
+}
+
+DataPipeBase::DataPipeBase(bool aReceiverSide, nsresult aError)
+ : mMutex(std::make_shared<Mutex>(aReceiverSide ? "DataPipeReceiver"
+ : "DataPipeSender")),
+ mStatus(NS_SUCCEEDED(aError) ? NS_BASE_STREAM_CLOSED : aError) {}
+
+DataPipeBase::DataPipeBase(bool aReceiverSide, ScopedPort aPort,
+ SharedMemory* aShmem, uint32_t aCapacity,
+ nsresult aPeerStatus, uint32_t aOffset,
+ uint32_t aAvailable)
+ : mMutex(std::make_shared<Mutex>(aReceiverSide ? "DataPipeReceiver"
+ : "DataPipeSender")),
+ mStatus(NS_OK),
+ mLink(new DataPipeLink(aReceiverSide, mMutex, std::move(aPort), aShmem,
+ aCapacity, aPeerStatus, aOffset, aAvailable)) {
+ mLink->Init();
+}
+
+DataPipeBase::~DataPipeBase() {
+ DataPipeAutoLock lock(*mMutex);
+ CloseInternal(lock, NS_BASE_STREAM_CLOSED);
+}
+
+void DataPipeBase::CloseInternal(DataPipeAutoLock& aLock, nsresult aStatus) {
+ if (NS_FAILED(mStatus)) {
+ return;
+ }
+
+ MOZ_LOG(
+ gDataPipeLog, LogLevel::Debug,
+ ("Closing(%s) %s", GetStaticErrorName(aStatus), Describe(aLock).get()));
+
+ // Set our status to an errored status.
+ mStatus = NS_SUCCEEDED(aStatus) ? NS_BASE_STREAM_CLOSED : aStatus;
+ RefPtr<DataPipeLink> link = mLink.forget();
+ AssertSameMutex(link->mMutex);
+ link->NotifyOnUnlock(aLock);
+
+ // If our peer hasn't disappeared yet, clean up our connection to it.
+ if (NS_SUCCEEDED(link->mPeerStatus)) {
+ link->SetPeerError(aLock, mStatus, /* aSendClosed */ true);
+ }
+}
+
+nsresult DataPipeBase::ProcessSegmentsInternal(
+ uint32_t aCount, ProcessSegmentFun aProcessSegment,
+ uint32_t* aProcessedCount) {
+ *aProcessedCount = 0;
+
+ while (*aProcessedCount < aCount) {
+ DataPipeAutoLock lock(*mMutex);
+ mMutex->AssertCurrentThreadOwns();
+
+ MOZ_LOG(gDataPipeLog, LogLevel::Verbose,
+ ("ProcessSegments(%u of %u) %s", *aProcessedCount, aCount,
+ Describe(lock).get()));
+
+ nsresult status = CheckStatus(lock);
+ if (NS_FAILED(status)) {
+ if (*aProcessedCount > 0) {
+ return NS_OK;
+ }
+ return status == NS_BASE_STREAM_CLOSED ? NS_OK : status;
+ }
+
+ RefPtr<DataPipeLink> link = mLink;
+ AssertSameMutex(link->mMutex);
+ if (!link->mAvailable) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(link->mPeerStatus),
+ "CheckStatus will have returned an error");
+ return *aProcessedCount > 0 ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ MOZ_RELEASE_ASSERT(!link->mProcessingSegment,
+ "Only one thread may be processing a segment at a time");
+
+ // Extract an iterator over the next contiguous region of the shared memory
+ // buffer which will be used .
+ char* start = static_cast<char*>(link->mShmem->memory()) + link->mOffset;
+ char* iter = start;
+ char* end = start + std::min({aCount - *aProcessedCount, link->mAvailable,
+ link->mCapacity - link->mOffset});
+
+ // Record the consumed region from our segment when exiting this scope,
+ // telling our peer how many bytes were consumed. Hold on to `mLink` to keep
+ // the shmem mapped and make sure we can clean up even if we're closed while
+ // processing the shmem region.
+ link->mProcessingSegment = true;
+ auto scopeExit = MakeScopeExit([&] {
+ mMutex->AssertCurrentThreadOwns(); // should still be held
+ AssertSameMutex(link->mMutex);
+
+ MOZ_RELEASE_ASSERT(link->mProcessingSegment);
+ link->mProcessingSegment = false;
+ uint32_t totalProcessed = iter - start;
+ if (totalProcessed > 0) {
+ link->mOffset += totalProcessed;
+ MOZ_RELEASE_ASSERT(link->mOffset <= link->mCapacity);
+ if (link->mOffset == link->mCapacity) {
+ link->mOffset = 0;
+ }
+ link->mAvailable -= totalProcessed;
+ link->SendBytesConsumedOnUnlock(lock, totalProcessed);
+ }
+ MOZ_LOG(gDataPipeLog, LogLevel::Verbose,
+ ("Processed Segment(%u of %zu) %s", totalProcessed, end - start,
+ Describe(lock).get()));
+ });
+
+ {
+ MutexAutoUnlock unlock(*mMutex);
+ while (iter < end) {
+ uint32_t processed = 0;
+ Span segment{iter, end};
+ nsresult rv = aProcessSegment(segment, *aProcessedCount, &processed);
+ if (NS_FAILED(rv) || processed == 0) {
+ return NS_OK;
+ }
+
+ MOZ_RELEASE_ASSERT(processed <= segment.Length());
+ iter += processed;
+ *aProcessedCount += processed;
+ }
+ }
+ }
+ MOZ_DIAGNOSTIC_ASSERT(*aProcessedCount == aCount,
+ "Must have processed exactly aCount");
+ return NS_OK;
+}
+
+void DataPipeBase::AsyncWaitInternal(already_AddRefed<nsIRunnable> aCallback,
+ already_AddRefed<nsIEventTarget> aTarget,
+ bool aClosureOnly) {
+ RefPtr<nsIRunnable> callback = std::move(aCallback);
+ RefPtr<nsIEventTarget> target = std::move(aTarget);
+
+ DataPipeAutoLock lock(*mMutex);
+ MOZ_LOG(gDataPipeLog, LogLevel::Debug,
+ ("AsyncWait %s %p %s", aClosureOnly ? "(closure)" : "(ready)",
+ callback.get(), Describe(lock).get()));
+
+ if (NS_FAILED(CheckStatus(lock))) {
+#ifdef DEBUG
+ if (mLink) {
+ AssertSameMutex(mLink->mMutex);
+ MOZ_ASSERT(!mLink->mCallback);
+ }
+#endif
+ DoNotifyOnUnlock(lock, callback.forget(), target.forget());
+ return;
+ }
+
+ AssertSameMutex(mLink->mMutex);
+
+ // NOTE: After this point, `mLink` may have previously had a callback which is
+ // now being cancelled, make sure we clear `mCallback` even if we're going to
+ // call `aCallback` immediately.
+ mLink->mCallback = callback.forget();
+ mLink->mCallbackTarget = target.forget();
+ mLink->mCallbackClosureOnly = aClosureOnly;
+ if (!aClosureOnly && mLink->mAvailable) {
+ mLink->NotifyOnUnlock(lock);
+ }
+}
+
+nsresult DataPipeBase::CheckStatus(DataPipeAutoLock& aLock) {
+ // If our peer has closed or errored, we may need to close our local side to
+ // reflect the error code our peer provided. If we're a sender, we want to
+ // become closed immediately, whereas if we're a receiver we want to wait
+ // until our available buffer has been exhausted.
+ //
+ // NOTE: There may still be 2-stage writes/reads ongoing at this point, which
+ // will continue due to `mLink` being kept alive by the
+ // `ProcessSegmentsInternal` function.
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+ AssertSameMutex(mLink->mMutex);
+ if (NS_FAILED(mLink->mPeerStatus) &&
+ (!mLink->mReceiverSide || !mLink->mAvailable)) {
+ CloseInternal(aLock, mLink->mPeerStatus);
+ }
+ return mStatus;
+}
+
+nsCString DataPipeBase::Describe(DataPipeAutoLock& aLock) {
+ if (mLink) {
+ AssertSameMutex(mLink->mMutex);
+ return mLink->Describe(aLock);
+ }
+ return nsPrintfCString("[status=%s]", GetStaticErrorName(mStatus));
+}
+
+template <typename T>
+void DataPipeWrite(IPC::MessageWriter* aWriter, T* aParam) {
+ DataPipeAutoLock lock(*aParam->mMutex);
+ MOZ_LOG(gDataPipeLog, LogLevel::Debug,
+ ("IPC Write: %s", aParam->Describe(lock).get()));
+
+ WriteParam(aWriter, aParam->mStatus);
+ if (NS_FAILED(aParam->mStatus)) {
+ return;
+ }
+
+ aParam->AssertSameMutex(aParam->mLink->mMutex);
+ MOZ_RELEASE_ASSERT(!aParam->mLink->mProcessingSegment,
+ "cannot transfer while processing a segment");
+
+ // Serialize relevant parameters to our peer.
+ WriteParam(aWriter, std::move(aParam->mLink->mPort));
+ if (!aParam->mLink->mShmem->WriteHandle(aWriter)) {
+ aWriter->FatalError("failed to write DataPipe shmem handle");
+ MOZ_CRASH("failed to write DataPipe shmem handle");
+ }
+ WriteParam(aWriter, aParam->mLink->mCapacity);
+ WriteParam(aWriter, aParam->mLink->mPeerStatus);
+ WriteParam(aWriter, aParam->mLink->mOffset);
+ WriteParam(aWriter, aParam->mLink->mAvailable);
+
+ // Mark our peer as closed so we don't try to send to it when closing.
+ aParam->mLink->mPeerStatus = NS_ERROR_NOT_INITIALIZED;
+ aParam->CloseInternal(lock, NS_ERROR_NOT_INITIALIZED);
+}
+
+template <typename T>
+bool DataPipeRead(IPC::MessageReader* aReader, RefPtr<T>* aResult) {
+ nsresult rv = NS_OK;
+ if (!ReadParam(aReader, &rv)) {
+ aReader->FatalError("failed to read DataPipe status");
+ return false;
+ }
+ if (NS_FAILED(rv)) {
+ *aResult = new T(rv);
+ MOZ_LOG(gDataPipeLog, LogLevel::Debug,
+ ("IPC Read: [status=%s]", GetStaticErrorName(rv)));
+ return true;
+ }
+
+ ScopedPort port;
+ if (!ReadParam(aReader, &port)) {
+ aReader->FatalError("failed to read DataPipe port");
+ return false;
+ }
+ RefPtr shmem = new SharedMemoryBasic();
+ if (!shmem->ReadHandle(aReader)) {
+ aReader->FatalError("failed to read DataPipe shmem");
+ return false;
+ }
+ uint32_t capacity = 0;
+ nsresult peerStatus = NS_OK;
+ uint32_t offset = 0;
+ uint32_t available = 0;
+ if (!ReadParam(aReader, &capacity) || !ReadParam(aReader, &peerStatus) ||
+ !ReadParam(aReader, &offset) || !ReadParam(aReader, &available)) {
+ aReader->FatalError("failed to read DataPipe fields");
+ return false;
+ }
+ if (!capacity || offset >= capacity || available > capacity) {
+ aReader->FatalError("received DataPipe state values are inconsistent");
+ return false;
+ }
+ if (!shmem->Map(SharedMemory::PageAlignedSize(capacity))) {
+ aReader->FatalError("failed to map DataPipe shared memory region");
+ return false;
+ }
+
+ *aResult =
+ new T(std::move(port), shmem, capacity, peerStatus, offset, available);
+ if (MOZ_LOG_TEST(gDataPipeLog, LogLevel::Debug)) {
+ DataPipeAutoLock lock(*(*aResult)->mMutex);
+ MOZ_LOG(gDataPipeLog, LogLevel::Debug,
+ ("IPC Read: %s", (*aResult)->Describe(lock).get()));
+ }
+ return true;
+}
+
+} // namespace data_pipe_detail
+
+//-----------------------------------------------------------------------------
+// DataPipeSender
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(DataPipeSender, nsIOutputStream, nsIAsyncOutputStream,
+ DataPipeSender)
+
+// nsIOutputStream
+
+NS_IMETHODIMP DataPipeSender::Close() {
+ return CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP DataPipeSender::Flush() { return NS_OK; }
+
+NS_IMETHODIMP DataPipeSender::StreamStatus() {
+ data_pipe_detail::DataPipeAutoLock lock(*mMutex);
+ return CheckStatus(lock);
+}
+
+NS_IMETHODIMP DataPipeSender::Write(const char* aBuf, uint32_t aCount,
+ uint32_t* aWriteCount) {
+ return WriteSegments(NS_CopyBufferToSegment, (void*)aBuf, aCount,
+ aWriteCount);
+}
+
+NS_IMETHODIMP DataPipeSender::WriteFrom(nsIInputStream* aFromStream,
+ uint32_t aCount,
+ uint32_t* aWriteCount) {
+ return WriteSegments(NS_CopyStreamToSegment, aFromStream, aCount,
+ aWriteCount);
+}
+
+NS_IMETHODIMP DataPipeSender::WriteSegments(nsReadSegmentFun aReader,
+ void* aClosure, uint32_t aCount,
+ uint32_t* aWriteCount) {
+ auto processSegment = [&](Span<char> aSpan, uint32_t aToOffset,
+ uint32_t* aReadCount) -> nsresult {
+ return aReader(this, aClosure, aSpan.data(), aToOffset, aSpan.Length(),
+ aReadCount);
+ };
+ return ProcessSegmentsInternal(aCount, processSegment, aWriteCount);
+}
+
+NS_IMETHODIMP DataPipeSender::IsNonBlocking(bool* _retval) {
+ *_retval = true;
+ return NS_OK;
+}
+
+// nsIAsyncOutputStream
+
+NS_IMETHODIMP DataPipeSender::CloseWithStatus(nsresult reason) {
+ data_pipe_detail::DataPipeAutoLock lock(*mMutex);
+ CloseInternal(lock, reason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP DataPipeSender::AsyncWait(nsIOutputStreamCallback* aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget* aTarget) {
+ AsyncWaitInternal(
+ aCallback ? NS_NewCancelableRunnableFunction(
+ "DataPipeReceiver::AsyncWait",
+ [self = RefPtr{this}, callback = RefPtr{aCallback}] {
+ MOZ_LOG(gDataPipeLog, LogLevel::Debug,
+ ("Calling OnOutputStreamReady(%p, %p)",
+ callback.get(), self.get()));
+ callback->OnOutputStreamReady(self);
+ })
+ : nullptr,
+ do_AddRef(aTarget), aFlags & WAIT_CLOSURE_ONLY);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// DataPipeReceiver
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(DataPipeReceiver, nsIInputStream, nsIAsyncInputStream,
+ nsIIPCSerializableInputStream, DataPipeReceiver)
+
+// nsIInputStream
+
+NS_IMETHODIMP DataPipeReceiver::Close() {
+ return CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP DataPipeReceiver::Available(uint64_t* _retval) {
+ data_pipe_detail::DataPipeAutoLock lock(*mMutex);
+ nsresult rv = CheckStatus(lock);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ AssertSameMutex(mLink->mMutex);
+ *_retval = mLink->mAvailable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP DataPipeReceiver::StreamStatus() {
+ data_pipe_detail::DataPipeAutoLock lock(*mMutex);
+ return CheckStatus(lock);
+}
+
+NS_IMETHODIMP DataPipeReceiver::Read(char* aBuf, uint32_t aCount,
+ uint32_t* aReadCount) {
+ return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount);
+}
+
+NS_IMETHODIMP DataPipeReceiver::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure, uint32_t aCount,
+ uint32_t* aReadCount) {
+ auto processSegment = [&](Span<char> aSpan, uint32_t aToOffset,
+ uint32_t* aWriteCount) -> nsresult {
+ return aWriter(this, aClosure, aSpan.data(), aToOffset, aSpan.Length(),
+ aWriteCount);
+ };
+ return ProcessSegmentsInternal(aCount, processSegment, aReadCount);
+}
+
+NS_IMETHODIMP DataPipeReceiver::IsNonBlocking(bool* _retval) {
+ *_retval = true;
+ return NS_OK;
+}
+
+// nsIAsyncInputStream
+
+NS_IMETHODIMP DataPipeReceiver::CloseWithStatus(nsresult aStatus) {
+ data_pipe_detail::DataPipeAutoLock lock(*mMutex);
+ CloseInternal(lock, aStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP DataPipeReceiver::AsyncWait(nsIInputStreamCallback* aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget* aTarget) {
+ AsyncWaitInternal(
+ aCallback ? NS_NewCancelableRunnableFunction(
+ "DataPipeReceiver::AsyncWait",
+ [self = RefPtr{this}, callback = RefPtr{aCallback}] {
+ MOZ_LOG(gDataPipeLog, LogLevel::Debug,
+ ("Calling OnInputStreamReady(%p, %p)",
+ callback.get(), self.get()));
+ callback->OnInputStreamReady(self);
+ })
+ : nullptr,
+ do_AddRef(aTarget), aFlags & WAIT_CLOSURE_ONLY);
+ return NS_OK;
+}
+
+// nsIIPCSerializableInputStream
+
+void DataPipeReceiver::SerializedComplexity(uint32_t aMaxSize,
+ uint32_t* aSizeUsed,
+ uint32_t* aPipes,
+ uint32_t* aTransferables) {
+ // We report DataPipeReceiver as taking one transferrable to serialize, rather
+ // than one pipe, as we aren't starting a new pipe for this purpose, and are
+ // instead transferring an existing pipe.
+ *aTransferables = 1;
+}
+
+void DataPipeReceiver::Serialize(InputStreamParams& aParams, uint32_t aMaxSize,
+ uint32_t* aSizeUsed) {
+ *aSizeUsed = 0;
+ aParams = DataPipeReceiverStreamParams(this);
+}
+
+bool DataPipeReceiver::Deserialize(const InputStreamParams& aParams) {
+ MOZ_CRASH("Handled directly in `DeserializeInputStream`");
+}
+
+//-----------------------------------------------------------------------------
+// NewDataPipe
+//-----------------------------------------------------------------------------
+
+nsresult NewDataPipe(uint32_t aCapacity, DataPipeSender** aSender,
+ DataPipeReceiver** aReceiver) {
+ if (!aCapacity) {
+ aCapacity = kDefaultDataPipeCapacity;
+ }
+
+ RefPtr<NodeController> controller = NodeController::GetSingleton();
+ if (!controller) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ auto [senderPort, receiverPort] = controller->CreatePortPair();
+ auto shmem = MakeRefPtr<SharedMemoryBasic>();
+ size_t alignedCapacity = SharedMemory::PageAlignedSize(aCapacity);
+ if (!shmem->Create(alignedCapacity) || !shmem->Map(alignedCapacity)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ RefPtr sender = new DataPipeSender(std::move(senderPort), shmem, aCapacity,
+ NS_OK, 0, aCapacity);
+ RefPtr receiver = new DataPipeReceiver(std::move(receiverPort), shmem,
+ aCapacity, NS_OK, 0, 0);
+ sender.forget(aSender);
+ receiver.forget(aReceiver);
+ return NS_OK;
+}
+
+} // namespace ipc
+} // namespace mozilla
+
+void IPC::ParamTraits<mozilla::ipc::DataPipeSender*>::Write(
+ MessageWriter* aWriter, mozilla::ipc::DataPipeSender* aParam) {
+ mozilla::ipc::data_pipe_detail::DataPipeWrite(aWriter, aParam);
+}
+
+bool IPC::ParamTraits<mozilla::ipc::DataPipeSender*>::Read(
+ MessageReader* aReader, RefPtr<mozilla::ipc::DataPipeSender>* aResult) {
+ return mozilla::ipc::data_pipe_detail::DataPipeRead(aReader, aResult);
+}
+
+void IPC::ParamTraits<mozilla::ipc::DataPipeReceiver*>::Write(
+ MessageWriter* aWriter, mozilla::ipc::DataPipeReceiver* aParam) {
+ mozilla::ipc::data_pipe_detail::DataPipeWrite(aWriter, aParam);
+}
+
+bool IPC::ParamTraits<mozilla::ipc::DataPipeReceiver*>::Read(
+ MessageReader* aReader, RefPtr<mozilla::ipc::DataPipeReceiver>* aResult) {
+ return mozilla::ipc::data_pipe_detail::DataPipeRead(aReader, aResult);
+}
diff --git a/ipc/glue/DataPipe.h b/ipc/glue/DataPipe.h
new file mode 100644
index 0000000000..d6439189ad
--- /dev/null
+++ b/ipc/glue/DataPipe.h
@@ -0,0 +1,188 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ipc_DataPipe_h
+#define mozilla_ipc_DataPipe_h
+
+#include "mozilla/ipc/SharedMemoryBasic.h"
+#include "mozilla/ipc/NodeController.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsISupports.h"
+
+namespace mozilla {
+namespace ipc {
+
+namespace data_pipe_detail {
+
+class DataPipeAutoLock;
+class DataPipeLink;
+
+class DataPipeBase {
+ public:
+ DataPipeBase(const DataPipeBase&) = delete;
+ DataPipeBase& operator=(const DataPipeBase&) = delete;
+
+ protected:
+ explicit DataPipeBase(bool aReceiverSide, nsresult aError);
+ DataPipeBase(bool aReceiverSide, ScopedPort aPort, SharedMemory* aShmem,
+ uint32_t aCapacity, nsresult aPeerStatus, uint32_t aOffset,
+ uint32_t aAvailable);
+
+ void CloseInternal(DataPipeAutoLock&, nsresult aStatus) MOZ_REQUIRES(*mMutex);
+
+ void AsyncWaitInternal(already_AddRefed<nsIRunnable> aCallback,
+ already_AddRefed<nsIEventTarget> aTarget,
+ bool aClosureOnly) MOZ_EXCLUDES(*mMutex);
+
+ // Like `nsWriteSegmentFun` or `nsReadSegmentFun`.
+ using ProcessSegmentFun =
+ FunctionRef<nsresult(Span<char> aSpan, uint32_t aProcessedThisCall,
+ uint32_t* aProcessedCount)>;
+ nsresult ProcessSegmentsInternal(uint32_t aCount,
+ ProcessSegmentFun aProcessSegment,
+ uint32_t* aProcessedCount)
+ MOZ_EXCLUDES(*mMutex);
+
+ nsresult CheckStatus(DataPipeAutoLock&) MOZ_REQUIRES(*mMutex);
+
+ nsCString Describe(DataPipeAutoLock&) MOZ_REQUIRES(*mMutex);
+
+ // Thread safety helper to tell the analysis that `mLink->mMutex` is held when
+ // `mMutex` is held.
+ void AssertSameMutex(const std::shared_ptr<Mutex>& aMutex)
+ MOZ_REQUIRES(*mMutex) MOZ_ASSERT_CAPABILITY(*aMutex) {
+ MOZ_ASSERT(mMutex == aMutex);
+ }
+
+ virtual ~DataPipeBase();
+
+ const std::shared_ptr<Mutex> mMutex;
+ nsresult mStatus MOZ_GUARDED_BY(*mMutex) = NS_OK;
+ RefPtr<DataPipeLink> mLink MOZ_GUARDED_BY(*mMutex);
+};
+
+template <typename T>
+void DataPipeWrite(IPC::MessageWriter* aWriter, T* aParam);
+
+template <typename T>
+bool DataPipeRead(IPC::MessageReader* aReader, RefPtr<T>* aResult);
+
+} // namespace data_pipe_detail
+
+class DataPipeSender;
+class DataPipeReceiver;
+
+#define NS_DATAPIPESENDER_IID \
+ { \
+ 0x6698ed77, 0x9fff, 0x425d, { \
+ 0xb0, 0xa6, 0x1d, 0x30, 0x66, 0xee, 0xb8, 0x16 \
+ } \
+ }
+
+// Helper class for streaming data to another process.
+class DataPipeSender final : public nsIAsyncOutputStream,
+ public data_pipe_detail::DataPipeBase {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_DATAPIPESENDER_IID)
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ private:
+ friend nsresult NewDataPipe(uint32_t, DataPipeSender**, DataPipeReceiver**);
+ friend void data_pipe_detail::DataPipeWrite<DataPipeSender>(
+ IPC::MessageWriter* aWriter, DataPipeSender* aParam);
+ friend bool data_pipe_detail::DataPipeRead<DataPipeSender>(
+ IPC::MessageReader* aReader, RefPtr<DataPipeSender>* aResult);
+
+ explicit DataPipeSender(nsresult aError)
+ : data_pipe_detail::DataPipeBase(/* aReceiverSide */ false, aError) {}
+ DataPipeSender(ScopedPort aPort, SharedMemory* aShmem, uint32_t aCapacity,
+ nsresult aPeerStatus, uint32_t aOffset, uint32_t aAvailable)
+ : data_pipe_detail::DataPipeBase(/* aReceiverSide */ false,
+ std::move(aPort), aShmem, aCapacity,
+ aPeerStatus, aOffset, aAvailable) {}
+
+ ~DataPipeSender() = default;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(DataPipeSender, NS_DATAPIPESENDER_IID)
+
+#define NS_DATAPIPERECEIVER_IID \
+ { \
+ 0x0a185f83, 0x499e, 0x450c, { \
+ 0x95, 0x82, 0x27, 0x67, 0xad, 0x6d, 0x64, 0xb5 \
+ } \
+ }
+
+// Helper class for streaming data from another process.
+class DataPipeReceiver final : public nsIAsyncInputStream,
+ public nsIIPCSerializableInputStream,
+ public data_pipe_detail::DataPipeBase {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_DATAPIPERECEIVER_IID)
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+
+ private:
+ friend nsresult NewDataPipe(uint32_t, DataPipeSender**, DataPipeReceiver**);
+ friend void data_pipe_detail::DataPipeWrite<DataPipeReceiver>(
+ IPC::MessageWriter* aWriter, DataPipeReceiver* aParam);
+ friend bool data_pipe_detail::DataPipeRead<DataPipeReceiver>(
+ IPC::MessageReader* aReader, RefPtr<DataPipeReceiver>* aResult);
+
+ explicit DataPipeReceiver(nsresult aError)
+ : data_pipe_detail::DataPipeBase(/* aReceiverSide */ true, aError) {}
+ DataPipeReceiver(ScopedPort aPort, SharedMemory* aShmem, uint32_t aCapacity,
+ nsresult aPeerStatus, uint32_t aOffset, uint32_t aAvailable)
+ : data_pipe_detail::DataPipeBase(/* aReceiverSide */ true,
+ std::move(aPort), aShmem, aCapacity,
+ aPeerStatus, aOffset, aAvailable) {}
+
+ ~DataPipeReceiver() = default;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(DataPipeReceiver, NS_DATAPIPERECEIVER_IID)
+
+constexpr uint32_t kDefaultDataPipeCapacity = 64 * 1024;
+
+/**
+ * Create a new DataPipe pair. The sender and receiver ends of the pipe may be
+ * used to transfer data between processes. |aCapacity| is the capacity of the
+ * underlying ring buffer. If `0` is passed, `kDefaultDataPipeCapacity` will be
+ * used.
+ */
+nsresult NewDataPipe(uint32_t aCapacity, DataPipeSender** aSender,
+ DataPipeReceiver** aReceiver);
+
+} // namespace ipc
+} // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::ipc::DataPipeSender*> {
+ static void Write(MessageWriter* aWriter,
+ mozilla::ipc::DataPipeSender* aParam);
+ static bool Read(MessageReader* aReader,
+ RefPtr<mozilla::ipc::DataPipeSender>* aResult);
+};
+
+template <>
+struct ParamTraits<mozilla::ipc::DataPipeReceiver*> {
+ static void Write(MessageWriter* aWriter,
+ mozilla::ipc::DataPipeReceiver* aParam);
+ static bool Read(MessageReader* aReader,
+ RefPtr<mozilla::ipc::DataPipeReceiver>* aResult);
+};
+
+} // namespace IPC
+
+#endif // mozilla_ipc_DataPipe_h
diff --git a/ipc/glue/Endpoint.cpp b/ipc/glue/Endpoint.cpp
new file mode 100644
index 0000000000..3391f8b359
--- /dev/null
+++ b/ipc/glue/Endpoint.cpp
@@ -0,0 +1,167 @@
+/* -*- 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 "mozilla/ipc/Endpoint.h"
+#include "chrome/common/ipc_message.h"
+#include "mozilla/ipc/IPDLParamTraits.h"
+#include "nsThreadUtils.h"
+#include "mozilla/ipc/ProtocolMessageUtils.h"
+
+namespace mozilla::ipc {
+
+UntypedManagedEndpoint::UntypedManagedEndpoint(IProtocol* aActor)
+ : mInner(Some(Inner{
+ /* mOtherSide */ aActor->GetWeakLifecycleProxy(),
+ /* mToplevel */ nullptr,
+ aActor->Id(),
+ aActor->GetProtocolId(),
+ aActor->Manager()->Id(),
+ aActor->Manager()->GetProtocolId(),
+ })) {}
+
+UntypedManagedEndpoint::~UntypedManagedEndpoint() {
+ if (!IsValid()) {
+ return;
+ }
+
+ if (mInner->mOtherSide) {
+ // If this ManagedEndpoint was never sent over IPC, deliver a fake
+ // MANAGED_ENDPOINT_DROPPED_MESSAGE_TYPE message directly to the other side
+ // actor.
+ mInner->mOtherSide->ActorEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "~ManagedEndpoint (Local)",
+ [otherSide = mInner->mOtherSide, id = mInner->mId] {
+ if (IProtocol* actor = otherSide->Get(); actor && actor->CanRecv()) {
+ MOZ_DIAGNOSTIC_ASSERT(actor->Id() == id, "Wrong Actor?");
+ RefPtr<ActorLifecycleProxy> strongProxy(actor->GetLifecycleProxy());
+ strongProxy->Get()->OnMessageReceived(
+ IPC::Message(id, MANAGED_ENDPOINT_DROPPED_MESSAGE_TYPE));
+ }
+ }));
+ } else if (mInner->mToplevel) {
+ // If it was sent over IPC, we'll need to send the message to the sending
+ // side. Let's send the message async.
+ mInner->mToplevel->ActorEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "~ManagedEndpoint (Remote)",
+ [toplevel = mInner->mToplevel, id = mInner->mId] {
+ if (IProtocol* actor = toplevel->Get();
+ actor && actor->CanSend() && actor->GetIPCChannel()) {
+ actor->GetIPCChannel()->Send(MakeUnique<IPC::Message>(
+ id, MANAGED_ENDPOINT_DROPPED_MESSAGE_TYPE));
+ }
+ }));
+ }
+}
+
+bool UntypedManagedEndpoint::BindCommon(IProtocol* aActor,
+ IProtocol* aManager) {
+ MOZ_ASSERT(aManager);
+ if (!mInner) {
+ NS_WARNING("Cannot bind to invalid endpoint");
+ return false;
+ }
+
+ // Perform thread assertions.
+ if (mInner->mToplevel) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ mInner->mToplevel->ActorEventTarget()->IsOnCurrentThread());
+ MOZ_DIAGNOSTIC_ASSERT(aManager->ToplevelProtocol() ==
+ mInner->mToplevel->Get());
+ }
+
+ if (NS_WARN_IF(aManager->Id() != mInner->mManagerId) ||
+ NS_WARN_IF(aManager->GetProtocolId() != mInner->mManagerType) ||
+ NS_WARN_IF(aActor->GetProtocolId() != mInner->mType)) {
+ MOZ_ASSERT_UNREACHABLE("Actor and manager do not match Endpoint");
+ return false;
+ }
+
+ if (!aManager->CanSend() || !aManager->GetIPCChannel()) {
+ NS_WARNING("Manager cannot send");
+ return false;
+ }
+
+ int32_t id = mInner->mId;
+ mInner.reset();
+
+ // Our typed caller will insert the actor into the managed container.
+ aActor->SetManagerAndRegister(aManager, id);
+
+ aManager->GetIPCChannel()->Send(
+ MakeUnique<IPC::Message>(id, MANAGED_ENDPOINT_BOUND_MESSAGE_TYPE));
+ return true;
+}
+
+/* static */
+void IPDLParamTraits<UntypedManagedEndpoint>::Write(IPC::MessageWriter* aWriter,
+ IProtocol* aActor,
+ paramType&& aParam) {
+ bool isValid = aParam.mInner.isSome();
+ WriteIPDLParam(aWriter, aActor, isValid);
+ if (!isValid) {
+ return;
+ }
+
+ auto inner = std::move(*aParam.mInner);
+ aParam.mInner.reset();
+
+ MOZ_RELEASE_ASSERT(inner.mOtherSide, "Has not been sent over IPC yet");
+ MOZ_RELEASE_ASSERT(inner.mOtherSide->ActorEventTarget()->IsOnCurrentThread(),
+ "Must be being sent from the correct thread");
+ MOZ_RELEASE_ASSERT(
+ inner.mOtherSide->Get() && inner.mOtherSide->Get()->ToplevelProtocol() ==
+ aActor->ToplevelProtocol(),
+ "Must be being sent over the same toplevel protocol");
+
+ WriteIPDLParam(aWriter, aActor, inner.mId);
+ WriteIPDLParam(aWriter, aActor, inner.mType);
+ WriteIPDLParam(aWriter, aActor, inner.mManagerId);
+ WriteIPDLParam(aWriter, aActor, inner.mManagerType);
+}
+
+/* static */
+bool IPDLParamTraits<UntypedManagedEndpoint>::Read(IPC::MessageReader* aReader,
+ IProtocol* aActor,
+ paramType* aResult) {
+ *aResult = UntypedManagedEndpoint{};
+ bool isValid = false;
+ if (!aActor || !ReadIPDLParam(aReader, aActor, &isValid)) {
+ return false;
+ }
+ if (!isValid) {
+ return true;
+ }
+
+ aResult->mInner.emplace();
+ auto& inner = *aResult->mInner;
+ inner.mToplevel = aActor->ToplevelProtocol()->GetWeakLifecycleProxy();
+ return ReadIPDLParam(aReader, aActor, &inner.mId) &&
+ ReadIPDLParam(aReader, aActor, &inner.mType) &&
+ ReadIPDLParam(aReader, aActor, &inner.mManagerId) &&
+ ReadIPDLParam(aReader, aActor, &inner.mManagerType);
+}
+
+} // namespace mozilla::ipc
+
+namespace IPC {
+
+void ParamTraits<mozilla::ipc::UntypedEndpoint>::Write(MessageWriter* aWriter,
+ paramType&& aParam) {
+ IPC::WriteParam(aWriter, std::move(aParam.mPort));
+ IPC::WriteParam(aWriter, aParam.mMessageChannelId);
+ IPC::WriteParam(aWriter, aParam.mMyPid);
+ IPC::WriteParam(aWriter, aParam.mOtherPid);
+}
+
+bool ParamTraits<mozilla::ipc::UntypedEndpoint>::Read(MessageReader* aReader,
+ paramType* aResult) {
+ return IPC::ReadParam(aReader, &aResult->mPort) &&
+ IPC::ReadParam(aReader, &aResult->mMessageChannelId) &&
+ IPC::ReadParam(aReader, &aResult->mMyPid) &&
+ IPC::ReadParam(aReader, &aResult->mOtherPid);
+}
+
+} // namespace IPC
diff --git a/ipc/glue/Endpoint.h b/ipc/glue/Endpoint.h
new file mode 100644
index 0000000000..d7eea94dfc
--- /dev/null
+++ b/ipc/glue/Endpoint.h
@@ -0,0 +1,288 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef IPC_GLUE_ENDPOINT_H_
+#define IPC_GLUE_ENDPOINT_H_
+
+#include <utility>
+#include "CrashAnnotations.h"
+#include "base/process.h"
+#include "base/process_util.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ipc/MessageLink.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/ipc/NodeController.h"
+#include "mozilla/ipc/ScopedPort.h"
+#include "nsXULAppAPI.h"
+#include "nscore.h"
+
+namespace IPC {
+template <class P>
+struct ParamTraits;
+}
+
+namespace mozilla {
+namespace ipc {
+
+namespace endpoint_detail {
+
+template <class T>
+static auto ActorNeedsOtherPidHelper(int)
+ -> decltype(std::declval<T>().OtherPid(), std::true_type{});
+template <class>
+static auto ActorNeedsOtherPidHelper(long) -> std::false_type;
+
+template <typename T>
+constexpr bool ActorNeedsOtherPid =
+ decltype(ActorNeedsOtherPidHelper<T>(0))::value;
+
+} // namespace endpoint_detail
+
+struct PrivateIPDLInterface {};
+
+class UntypedEndpoint {
+ public:
+ using ProcessId = base::ProcessId;
+
+ UntypedEndpoint() = default;
+
+ UntypedEndpoint(const PrivateIPDLInterface&, ScopedPort aPort,
+ const nsID& aMessageChannelId,
+ ProcessId aMyPid = base::kInvalidProcessId,
+ ProcessId aOtherPid = base::kInvalidProcessId)
+ : mPort(std::move(aPort)),
+ mMessageChannelId(aMessageChannelId),
+ mMyPid(aMyPid),
+ mOtherPid(aOtherPid) {}
+
+ UntypedEndpoint(const UntypedEndpoint&) = delete;
+ UntypedEndpoint(UntypedEndpoint&& aOther) = default;
+
+ UntypedEndpoint& operator=(const UntypedEndpoint&) = delete;
+ UntypedEndpoint& operator=(UntypedEndpoint&& aOther) = default;
+
+ // This method binds aActor to this endpoint. After this call, the actor can
+ // be used to send and receive messages. The endpoint becomes invalid.
+ //
+ // If specified, aEventTarget is the target the actor will be bound to, and
+ // must be on the current thread. Otherwise, GetCurrentSerialEventTarget() is
+ // used.
+ bool Bind(IToplevelProtocol* aActor,
+ nsISerialEventTarget* aEventTarget = nullptr) {
+ MOZ_RELEASE_ASSERT(IsValid());
+ MOZ_RELEASE_ASSERT(mMyPid == base::kInvalidProcessId ||
+ mMyPid == base::GetCurrentProcId());
+ MOZ_RELEASE_ASSERT(!aEventTarget || aEventTarget->IsOnCurrentThread());
+ return aActor->Open(std::move(mPort), mMessageChannelId, mOtherPid,
+ aEventTarget);
+ }
+
+ bool IsValid() const { return mPort.IsValid(); }
+
+ protected:
+ friend struct IPC::ParamTraits<UntypedEndpoint>;
+
+ ScopedPort mPort;
+ nsID mMessageChannelId{};
+ ProcessId mMyPid = base::kInvalidProcessId;
+ ProcessId mOtherPid = base::kInvalidProcessId;
+};
+
+/**
+ * An endpoint represents one end of a partially initialized IPDL channel. To
+ * set up a new top-level protocol:
+ *
+ * Endpoint<PFooParent> parentEp;
+ * Endpoint<PFooChild> childEp;
+ * nsresult rv;
+ * rv = PFoo::CreateEndpoints(&parentEp, &childEp);
+ *
+ * Endpoints can be passed in IPDL messages or sent to other threads using
+ * PostTask. Once an Endpoint has arrived at its destination process and thread,
+ * you need to create the top-level actor and bind it to the endpoint:
+ *
+ * FooParent* parent = new FooParent();
+ * bool rv1 = parentEp.Bind(parent, processActor);
+ * bool rv2 = parent->SendBar(...);
+ *
+ * (See Bind below for an explanation of processActor.) Once the actor is bound
+ * to the endpoint, it can send and receive messages.
+ *
+ * If creating endpoints for a [NeedsOtherPid] actor, you're required to also
+ * pass in parentPid and childPid, which are the pids of the processes in which
+ * the parent and child endpoints will be used.
+ */
+template <class PFooSide>
+class Endpoint final : public UntypedEndpoint {
+ public:
+ using UntypedEndpoint::IsValid;
+ using UntypedEndpoint::UntypedEndpoint;
+
+ base::ProcessId OtherPid() const {
+ static_assert(
+ endpoint_detail::ActorNeedsOtherPid<PFooSide>,
+ "OtherPid may only be called on Endpoints for actors which are "
+ "[NeedsOtherPid]");
+ MOZ_RELEASE_ASSERT(mOtherPid != base::kInvalidProcessId);
+ return mOtherPid;
+ }
+
+ // This method binds aActor to this endpoint. After this call, the actor can
+ // be used to send and receive messages. The endpoint becomes invalid.
+ //
+ // If specified, aEventTarget is the target the actor will be bound to, and
+ // must be on the current thread. Otherwise, GetCurrentSerialEventTarget() is
+ // used.
+ bool Bind(PFooSide* aActor, nsISerialEventTarget* aEventTarget = nullptr) {
+ return UntypedEndpoint::Bind(aActor, aEventTarget);
+ }
+};
+
+#if defined(XP_MACOSX)
+void AnnotateCrashReportWithErrno(CrashReporter::Annotation tag, int error);
+#else
+inline void AnnotateCrashReportWithErrno(CrashReporter::Annotation tag,
+ int error) {}
+#endif
+
+// This function is used internally to create a pair of Endpoints. See the
+// comment above Endpoint for a description of how it might be used.
+template <class PFooParent, class PFooChild>
+nsresult CreateEndpoints(const PrivateIPDLInterface& aPrivate,
+ Endpoint<PFooParent>* aParentEndpoint,
+ Endpoint<PFooChild>* aChildEndpoint) {
+ static_assert(
+ !endpoint_detail::ActorNeedsOtherPid<PFooParent> &&
+ !endpoint_detail::ActorNeedsOtherPid<PFooChild>,
+ "Pids are required when creating endpoints for [NeedsOtherPid] actors");
+
+ auto [parentPort, childPort] =
+ NodeController::GetSingleton()->CreatePortPair();
+ nsID channelId = nsID::GenerateUUID();
+ *aParentEndpoint =
+ Endpoint<PFooParent>(aPrivate, std::move(parentPort), channelId);
+ *aChildEndpoint =
+ Endpoint<PFooChild>(aPrivate, std::move(childPort), channelId);
+ return NS_OK;
+}
+
+template <class PFooParent, class PFooChild>
+nsresult CreateEndpoints(const PrivateIPDLInterface& aPrivate,
+ base::ProcessId aParentDestPid,
+ base::ProcessId aChildDestPid,
+ Endpoint<PFooParent>* aParentEndpoint,
+ Endpoint<PFooChild>* aChildEndpoint) {
+ MOZ_RELEASE_ASSERT(aParentDestPid != base::kInvalidProcessId);
+ MOZ_RELEASE_ASSERT(aChildDestPid != base::kInvalidProcessId);
+
+ auto [parentPort, childPort] =
+ NodeController::GetSingleton()->CreatePortPair();
+ nsID channelId = nsID::GenerateUUID();
+ *aParentEndpoint =
+ Endpoint<PFooParent>(aPrivate, std::move(parentPort), channelId,
+ aParentDestPid, aChildDestPid);
+ *aChildEndpoint = Endpoint<PFooChild>(
+ aPrivate, std::move(childPort), channelId, aChildDestPid, aParentDestPid);
+ return NS_OK;
+}
+
+class UntypedManagedEndpoint {
+ public:
+ bool IsValid() const { return mInner.isSome(); }
+
+ UntypedManagedEndpoint(const UntypedManagedEndpoint&) = delete;
+ UntypedManagedEndpoint& operator=(const UntypedManagedEndpoint&) = delete;
+
+ protected:
+ UntypedManagedEndpoint() = default;
+ explicit UntypedManagedEndpoint(IProtocol* aActor);
+
+ UntypedManagedEndpoint(UntypedManagedEndpoint&& aOther) noexcept
+ : mInner(std::move(aOther.mInner)) {
+ aOther.mInner = Nothing();
+ }
+ UntypedManagedEndpoint& operator=(UntypedManagedEndpoint&& aOther) noexcept {
+ this->~UntypedManagedEndpoint();
+ new (this) UntypedManagedEndpoint(std::move(aOther));
+ return *this;
+ }
+
+ ~UntypedManagedEndpoint() noexcept;
+
+ bool BindCommon(IProtocol* aActor, IProtocol* aManager);
+
+ private:
+ friend struct IPDLParamTraits<UntypedManagedEndpoint>;
+
+ struct Inner {
+ // Pointers to the toplevel actor which will manage this connection. When
+ // created, only `mOtherSide` will be set, and will reference the
+ // toplevel actor which the other side is managed by. After being sent over
+ // IPC, only `mToplevel` will be set, and will be the toplevel actor for the
+ // channel which received the IPC message.
+ RefPtr<WeakActorLifecycleProxy> mOtherSide;
+ RefPtr<WeakActorLifecycleProxy> mToplevel;
+
+ int32_t mId = 0;
+ ProtocolId mType = LastMsgIndex;
+ int32_t mManagerId = 0;
+ ProtocolId mManagerType = LastMsgIndex;
+ };
+ Maybe<Inner> mInner;
+};
+
+/**
+ * A managed endpoint represents one end of a partially initialized managed
+ * IPDL actor. It is used for situations where the usual IPDL Constructor
+ * methods do not give sufficient control over the construction of actors, such
+ * as when constructing actors within replies, or constructing multiple related
+ * actors simultaneously.
+ *
+ * FooParent* parent = new FooParent();
+ * ManagedEndpoint<PFooChild> childEp = parentMgr->OpenPFooEndpoint(parent);
+ *
+ * ManagedEndpoints should be sent using IPDL messages or other mechanisms to
+ * the other side of the manager channel. Once the ManagedEndpoint has arrived
+ * at its destination, you can create the actor, and bind it to the endpoint.
+ *
+ * FooChild* child = new FooChild();
+ * childMgr->BindPFooEndpoint(childEp, child);
+ *
+ * WARNING: If the remote side of an endpoint has not been bound before it
+ * begins to receive messages, an IPC routing error will occur, likely causing
+ * a browser crash.
+ */
+template <class PFooSide>
+class ManagedEndpoint : public UntypedManagedEndpoint {
+ public:
+ ManagedEndpoint() = default;
+ ManagedEndpoint(ManagedEndpoint&&) noexcept = default;
+ ManagedEndpoint& operator=(ManagedEndpoint&&) noexcept = default;
+
+ ManagedEndpoint(const PrivateIPDLInterface&, IProtocol* aActor)
+ : UntypedManagedEndpoint(aActor) {}
+
+ bool Bind(const PrivateIPDLInterface&, PFooSide* aActor, IProtocol* aManager,
+ ManagedContainer<PFooSide>& aContainer) {
+ if (!BindCommon(aActor, aManager)) {
+ return false;
+ }
+ aContainer.Insert(aActor);
+ return true;
+ }
+
+ // Only invalid ManagedEndpoints can be equal, as valid endpoints are unique.
+ bool operator==(const ManagedEndpoint& _o) const {
+ return !IsValid() && !_o.IsValid();
+ }
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // IPC_GLUE_ENDPOINT_H_
diff --git a/ipc/glue/EnumSerializer.h b/ipc/glue/EnumSerializer.h
new file mode 100644
index 0000000000..13c1de3841
--- /dev/null
+++ b/ipc/glue/EnumSerializer.h
@@ -0,0 +1,187 @@
+/* -*- 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/. */
+
+#ifndef __IPC_GLUE_ENUMSERIALIZER_H__
+#define __IPC_GLUE_ENUMSERIALIZER_H__
+
+#include "CrashAnnotations.h"
+#include "chrome/common/ipc_message_utils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/IntegerTypeTraits.h"
+#include "nsExceptionHandler.h"
+#include "nsLiteralString.h"
+#include "nsString.h"
+#include "nsTLiteralString.h"
+
+class PickleIterator;
+
+namespace IPC {
+class Message;
+class MessageReader;
+class MessageWriter;
+} // namespace IPC
+
+#ifdef _MSC_VER
+# pragma warning(disable : 4800)
+#endif
+
+namespace IPC {
+
+/**
+ * Generic enum serializer.
+ *
+ * Consider using the specializations below, such as ContiguousEnumSerializer.
+ *
+ * This is a generic serializer for any enum type used in IPDL.
+ * Programmers can define ParamTraits<E> for enum type E by deriving
+ * EnumSerializer<E, MyEnumValidator> where MyEnumValidator is a struct
+ * that has to define a static IsLegalValue function returning whether
+ * a given value is a legal value of the enum type at hand.
+ *
+ * \sa https://developer.mozilla.org/en/IPDL/Type_Serialization
+ */
+template <typename E, typename EnumValidator>
+struct EnumSerializer {
+ typedef E paramType;
+
+ // XXX(Bug 1690343) Should this be changed to
+ // std::make_unsigned_t<std::underlying_type_t<paramType>>, to make this more
+ // consistent with the type used for validating values?
+ typedef typename mozilla::UnsignedStdintTypeForSize<sizeof(paramType)>::Type
+ uintParamType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aValue) {
+ // XXX This assertion is somewhat meaningless at least for E that don't have
+ // a fixed underlying type: if aValue weren't a legal value, we would
+ // already have UB where this function is called.
+ MOZ_RELEASE_ASSERT(EnumValidator::IsLegalValue(
+ static_cast<std::underlying_type_t<paramType>>(aValue)));
+ WriteParam(aWriter, uintParamType(aValue));
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uintParamType value;
+ if (!ReadParam(aReader, &value)) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCReadErrorReason, "Bad iter"_ns);
+ return false;
+ } else if (!EnumValidator::IsLegalValue(value)) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCReadErrorReason, "Illegal value"_ns);
+ return false;
+ }
+ *aResult = paramType(value);
+ return true;
+ }
+};
+
+template <typename E, E MinLegal, E HighBound>
+class ContiguousEnumValidator {
+ // Silence overzealous -Wtype-limits bug in GCC fixed in GCC 4.8:
+ // "comparison of unsigned expression >= 0 is always true"
+ // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11856
+ template <typename T>
+ static bool IsLessThanOrEqual(T a, T b) {
+ return a <= b;
+ }
+
+ public:
+ using IntegralType = std::underlying_type_t<E>;
+ static constexpr auto kMinLegalIntegral = static_cast<IntegralType>(MinLegal);
+ static constexpr auto kHighBoundIntegral =
+ static_cast<IntegralType>(HighBound);
+
+ static bool IsLegalValue(const IntegralType e) {
+ return IsLessThanOrEqual(kMinLegalIntegral, e) && e < kHighBoundIntegral;
+ }
+};
+
+template <typename E, E MinLegal, E MaxLegal>
+class ContiguousEnumValidatorInclusive {
+ // Silence overzealous -Wtype-limits bug in GCC fixed in GCC 4.8:
+ // "comparison of unsigned expression >= 0 is always true"
+ // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11856
+ template <typename T>
+ static bool IsLessThanOrEqual(T a, T b) {
+ return a <= b;
+ }
+
+ public:
+ using IntegralType = std::underlying_type_t<E>;
+ static constexpr auto kMinLegalIntegral = static_cast<IntegralType>(MinLegal);
+ static constexpr auto kMaxLegalIntegral = static_cast<IntegralType>(MaxLegal);
+
+ static bool IsLegalValue(const IntegralType e) {
+ return IsLessThanOrEqual(kMinLegalIntegral, e) && e <= kMaxLegalIntegral;
+ }
+};
+
+template <typename E, E AllBits>
+struct BitFlagsEnumValidator {
+ static bool IsLegalValue(const std::underlying_type_t<E> e) {
+ return (e & static_cast<std::underlying_type_t<E>>(AllBits)) == e;
+ }
+};
+
+/**
+ * Specialization of EnumSerializer for enums with contiguous enum values.
+ *
+ * Provide two values: MinLegal, HighBound. An enum value x will be
+ * considered legal if MinLegal <= x < HighBound.
+ *
+ * For example, following is definition of serializer for enum type FOO.
+ * \code
+ * enum FOO { FOO_FIRST, FOO_SECOND, FOO_LAST, NUM_FOO };
+ *
+ * template <>
+ * struct ParamTraits<FOO>:
+ * public ContiguousEnumSerializer<FOO, FOO_FIRST, NUM_FOO> {};
+ * \endcode
+ * FOO_FIRST, FOO_SECOND, and FOO_LAST are valid value.
+ */
+template <typename E, E MinLegal, E HighBound>
+struct ContiguousEnumSerializer
+ : EnumSerializer<E, ContiguousEnumValidator<E, MinLegal, HighBound>> {};
+
+/**
+ * This is similar to ContiguousEnumSerializer, but the last template
+ * parameter is expected to be the highest legal value, rather than a
+ * sentinel value. This is intended to support enumerations that don't
+ * have sentinel values.
+ */
+template <typename E, E MinLegal, E MaxLegal>
+struct ContiguousEnumSerializerInclusive
+ : EnumSerializer<E,
+ ContiguousEnumValidatorInclusive<E, MinLegal, MaxLegal>> {
+};
+
+/**
+ * Specialization of EnumSerializer for enums representing bit flags.
+ *
+ * Provide one value: AllBits. An enum value x will be
+ * considered legal if (x & AllBits) == x;
+ *
+ * Example:
+ * \code
+ * enum FOO {
+ * FOO_FIRST = 1 << 0,
+ * FOO_SECOND = 1 << 1,
+ * FOO_LAST = 1 << 2,
+ * ALL_BITS = (1 << 3) - 1
+ * };
+ *
+ * template <>
+ * struct ParamTraits<FOO>:
+ * public BitFlagsEnumSerializer<FOO, FOO::ALL_BITS> {};
+ * \endcode
+ */
+template <typename E, E AllBits>
+struct BitFlagsEnumSerializer
+ : EnumSerializer<E, BitFlagsEnumValidator<E, AllBits>> {};
+
+} /* namespace IPC */
+
+#endif /* __IPC_GLUE_ENUMSERIALIZER_H__ */
diff --git a/ipc/glue/EnvironmentMap.h b/ipc/glue/EnvironmentMap.h
new file mode 100644
index 0000000000..5cd0ef8c58
--- /dev/null
+++ b/ipc/glue/EnvironmentMap.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SANDBOXING_COMMON_ENVIRONMENTMAP_H_
+#define SANDBOXING_COMMON_ENVIRONMENTMAP_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+namespace base {
+
+#if defined(OS_WIN)
+
+typedef std::wstring NativeEnvironmentString;
+typedef std::map<NativeEnvironmentString, NativeEnvironmentString>
+ EnvironmentMap;
+
+# define ENVIRONMENT_LITERAL(x) L##x
+# define ENVIRONMENT_STRING(x) \
+ ((std::wstring)(NS_ConvertUTF8toUTF16((x)).get()))
+
+// Returns a modified environment vector constructed from the given environment
+// and the list of changes given in |changes|. Each key in the environment is
+// matched against the first element of the pairs. In the event of a match, the
+// value is replaced by the second of the pair, unless the second is empty, in
+// which case the key-value is removed.
+//
+// This Windows version takes and returns a Windows-style environment block
+// which is a concatenated list of null-terminated 16-bit strings. The end is
+// marked by a double-null terminator. The size of the returned string will
+// include the terminators.
+NativeEnvironmentString AlterEnvironment(const wchar_t* env,
+ const EnvironmentMap& changes);
+
+#elif defined(OS_POSIX)
+
+typedef std::string NativeEnvironmentString;
+typedef std::map<NativeEnvironmentString, NativeEnvironmentString>
+ EnvironmentMap;
+
+# define ENVIRONMENT_LITERAL(x) x
+# define ENVIRONMENT_STRING(x) x
+
+// See general comments for the Windows version above.
+//
+// This Posix version takes and returns a Posix-style environment block, which
+// is a null-terminated list of pointers to null-terminated strings. The
+// returned array will have appended to it the storage for the array itself so
+// there is only one pointer to manage, but this means that you can't copy the
+// array without keeping the original around.
+std::unique_ptr<char*[]> AlterEnvironment(const char* const* env,
+ const EnvironmentMap& changes);
+
+#endif
+
+} // namespace base
+
+#endif // SANDBOXING_COMMON_ENVIRONMENTMAP_H_
diff --git a/ipc/glue/FileDescriptor.cpp b/ipc/glue/FileDescriptor.cpp
new file mode 100644
index 0000000000..697de4cc63
--- /dev/null
+++ b/ipc/glue/FileDescriptor.cpp
@@ -0,0 +1,117 @@
+/* -*- 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 "FileDescriptor.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/ipc/IPDLParamTraits.h"
+#include "mozilla/ipc/ProtocolMessageUtils.h"
+#include "nsDebug.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+# include "ProtocolUtils.h"
+#else // XP_WIN
+# include <unistd.h>
+#endif // XP_WIN
+
+namespace mozilla {
+namespace ipc {
+
+FileDescriptor::FileDescriptor() = default;
+
+FileDescriptor::FileDescriptor(const FileDescriptor& aOther)
+ : mHandle(Clone(aOther.mHandle.get())) {}
+
+FileDescriptor::FileDescriptor(FileDescriptor&& aOther)
+ : mHandle(std::move(aOther.mHandle)) {}
+
+FileDescriptor::FileDescriptor(PlatformHandleType aHandle)
+ : mHandle(Clone(aHandle)) {}
+
+FileDescriptor::FileDescriptor(UniquePlatformHandle&& aHandle)
+ : mHandle(std::move(aHandle)) {}
+
+FileDescriptor::~FileDescriptor() = default;
+
+FileDescriptor& FileDescriptor::operator=(const FileDescriptor& aOther) {
+ if (this != &aOther) {
+ mHandle = Clone(aOther.mHandle.get());
+ }
+ return *this;
+}
+
+FileDescriptor& FileDescriptor::operator=(FileDescriptor&& aOther) {
+ if (this != &aOther) {
+ mHandle = std::move(aOther.mHandle);
+ }
+ return *this;
+}
+
+bool FileDescriptor::IsValid() const { return mHandle != nullptr; }
+
+FileDescriptor::UniquePlatformHandle FileDescriptor::ClonePlatformHandle()
+ const {
+ return Clone(mHandle.get());
+}
+
+FileDescriptor::UniquePlatformHandle FileDescriptor::TakePlatformHandle() {
+ return UniquePlatformHandle(mHandle.release());
+}
+
+bool FileDescriptor::operator==(const FileDescriptor& aOther) const {
+ return mHandle == aOther.mHandle;
+}
+
+// static
+FileDescriptor::UniquePlatformHandle FileDescriptor::Clone(
+ PlatformHandleType aHandle) {
+ FileDescriptor::PlatformHandleType newHandle;
+
+#ifdef XP_WIN
+ if (aHandle == INVALID_HANDLE_VALUE || aHandle == nullptr) {
+ return UniqueFileHandle();
+ }
+ if (::DuplicateHandle(GetCurrentProcess(), aHandle, GetCurrentProcess(),
+ &newHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
+ return UniqueFileHandle(newHandle);
+ }
+#else // XP_WIN
+ if (aHandle < 0) {
+ return UniqueFileHandle();
+ }
+ newHandle = dup(aHandle);
+ if (newHandle >= 0) {
+ return UniqueFileHandle(newHandle);
+ }
+#endif
+ NS_WARNING("Failed to duplicate file handle for current process!");
+ return UniqueFileHandle();
+}
+
+void IPDLParamTraits<FileDescriptor>::Write(IPC::MessageWriter* aWriter,
+ IProtocol* aActor,
+ const FileDescriptor& aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.ClonePlatformHandle());
+}
+
+bool IPDLParamTraits<FileDescriptor>::Read(IPC::MessageReader* aReader,
+ IProtocol* aActor,
+ FileDescriptor* aResult) {
+ UniqueFileHandle handle;
+ if (!ReadIPDLParam(aReader, aActor, &handle)) {
+ return false;
+ }
+
+ *aResult = FileDescriptor(std::move(handle));
+ if (!aResult->IsValid()) {
+ printf_stderr("IPDL protocol Error: Received an invalid file descriptor\n");
+ }
+ return true;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/FileDescriptor.h b/ipc/glue/FileDescriptor.h
new file mode 100644
index 0000000000..15160624da
--- /dev/null
+++ b/ipc/glue/FileDescriptor.h
@@ -0,0 +1,79 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_FileDescriptor_h
+#define mozilla_ipc_FileDescriptor_h
+
+#include "base/basictypes.h"
+#include "base/process.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+namespace mozilla {
+namespace ipc {
+
+// This class is used by IPDL to share file descriptors across processes. When
+// sending a FileDescriptor, IPDL will transfer a duplicate of the handle into
+// the remote process.
+//
+// To use this class add 'FileDescriptor' as an argument in the IPDL protocol
+// and then pass a file descriptor from C++ to the Send method. The Recv method
+// will receive a FileDescriptor& on which PlatformHandle() can be called to
+// return the platform file handle.
+class FileDescriptor {
+ public:
+ typedef base::ProcessId ProcessId;
+
+ using UniquePlatformHandle = mozilla::UniqueFileHandle;
+ using PlatformHandleType = UniquePlatformHandle::ElementType;
+
+ // This should only ever be created by IPDL.
+ struct IPDLPrivate {};
+
+ // Represents an invalid handle.
+ FileDescriptor();
+
+ // Copy constructor will duplicate a new handle.
+ FileDescriptor(const FileDescriptor& aOther);
+
+ FileDescriptor(FileDescriptor&& aOther);
+
+ // This constructor will duplicate a new handle.
+ // The caller still have to close aHandle.
+ explicit FileDescriptor(PlatformHandleType aHandle);
+
+ explicit FileDescriptor(UniquePlatformHandle&& aHandle);
+
+ ~FileDescriptor();
+
+ FileDescriptor& operator=(const FileDescriptor& aOther);
+
+ FileDescriptor& operator=(FileDescriptor&& aOther);
+
+ // Tests mHandle against a well-known invalid platform-specific file handle
+ // (e.g. -1 on POSIX, INVALID_HANDLE_VALUE on Windows).
+ bool IsValid() const;
+
+ // Returns a duplicated handle, it is caller's responsibility to close the
+ // handle.
+ UniquePlatformHandle ClonePlatformHandle() const;
+
+ // Extracts the underlying handle and makes this object an invalid handle.
+ // (Compare UniquePtr::release.)
+ UniquePlatformHandle TakePlatformHandle();
+
+ // Only used in nsTArray.
+ bool operator==(const FileDescriptor& aOther) const;
+
+ private:
+ static UniqueFileHandle Clone(PlatformHandleType aHandle);
+
+ UniquePlatformHandle mHandle;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_FileDescriptor_h
diff --git a/ipc/glue/FileDescriptorShuffle.cpp b/ipc/glue/FileDescriptorShuffle.cpp
new file mode 100644
index 0000000000..7ce89e1b16
--- /dev/null
+++ b/ipc/glue/FileDescriptorShuffle.cpp
@@ -0,0 +1,113 @@
+/* -*- 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 "FileDescriptorShuffle.h"
+
+#include "base/eintr_wrapper.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+
+#include <algorithm>
+#include <unistd.h>
+#include <fcntl.h>
+
+namespace mozilla {
+namespace ipc {
+
+// F_DUPFD_CLOEXEC is like F_DUPFD (see below) but atomically makes
+// the new fd close-on-exec. This is useful to prevent accidental fd
+// leaks into processes created by plain fork/exec, but IPC uses
+// CloseSuperfluousFds so it's not essential.
+//
+// F_DUPFD_CLOEXEC is in POSIX 2008, but as of 2018 there are still
+// some OSes that don't support it. (Specifically: Solaris 10 doesn't
+// have it, and Android should have kernel support but doesn't define
+// the constant until API 21 (Lollipop). We also don't use this for
+// IPC child launching on Android, so there's no point in hard-coding
+// the definitions like we do for Android in some other cases.)
+#ifdef F_DUPFD_CLOEXEC
+static const int kDupFdCmd = F_DUPFD_CLOEXEC;
+#else
+static const int kDupFdCmd = F_DUPFD;
+#endif
+
+// This implementation ensures that the *ranges* of the source and
+// destination fds don't overlap, which is unnecessary but sufficient
+// to avoid conflicts or identity mappings.
+//
+// In practice, the source fds will usually be large and the
+// destination fds will all be relatively small, so there mostly won't
+// be temporary fds. This approach has the advantage of being simple
+// (and linear-time, but hopefully there aren't enough fds for that to
+// matter).
+bool FileDescriptorShuffle::Init(MappingRef aMapping) {
+ MOZ_ASSERT(mMapping.IsEmpty());
+
+ // Find the maximum destination fd; any source fds not greater than
+ // this will be duplicated.
+ int maxDst = STDERR_FILENO;
+ for (const auto& elem : aMapping) {
+ maxDst = std::max(maxDst, elem.second);
+ }
+ mMaxDst = maxDst;
+
+#ifdef DEBUG
+ // Increase the limit to make sure the F_DUPFD case gets test coverage.
+ if (!aMapping.IsEmpty()) {
+ // Try to find a value that will create a nontrivial partition.
+ int fd0 = aMapping[0].first;
+ int fdn = aMapping.rbegin()->first;
+ maxDst = std::max(maxDst, (fd0 + fdn) / 2);
+ }
+#endif
+
+ for (const auto& elem : aMapping) {
+ int src = elem.first;
+ // F_DUPFD is like dup() but allows placing a lower bound
+ // on the new fd, which is exactly what we want.
+ if (src <= maxDst) {
+ src = fcntl(src, kDupFdCmd, maxDst + 1);
+ if (src < 0) {
+ return false;
+ }
+ mTempFds.AppendElement(src);
+ }
+ MOZ_ASSERT(src > maxDst);
+#ifdef DEBUG
+ // Check for accidentally mapping two different fds to the same
+ // destination. (This is O(n^2) time, but it shouldn't matter.)
+ for (const auto& otherElem : mMapping) {
+ MOZ_ASSERT(elem.second != otherElem.second);
+ }
+#endif
+ mMapping.AppendElement(std::make_pair(src, elem.second));
+ }
+ return true;
+}
+
+bool FileDescriptorShuffle::MapsTo(int aFd) const {
+ // Prune fds that are too large to be a destination, rather than
+ // searching; this should be the common case.
+ if (aFd > mMaxDst) {
+ return false;
+ }
+ for (const auto& elem : mMapping) {
+ if (elem.second == aFd) {
+ return true;
+ }
+ }
+ return false;
+}
+
+FileDescriptorShuffle::~FileDescriptorShuffle() {
+ for (const auto& fd : mTempFds) {
+ mozilla::DebugOnly<int> rv = IGNORE_EINTR(close(fd));
+ MOZ_ASSERT(rv == 0);
+ }
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/FileDescriptorShuffle.h b/ipc/glue/FileDescriptorShuffle.h
new file mode 100644
index 0000000000..4afa8e0474
--- /dev/null
+++ b/ipc/glue/FileDescriptorShuffle.h
@@ -0,0 +1,68 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_FileDescriptorShuffle_h
+#define mozilla_ipc_FileDescriptorShuffle_h
+
+#include "mozilla/Span.h"
+#include "nsTArray.h"
+
+#include <functional>
+#include <utility>
+
+// This class converts a set of file descriptor mapping, which may
+// contain conflicts (like {a->b, b->c} or {a->b, b->a}) into a
+// sequence of dup2() operations that can be performed between fork
+// and exec, or with posix_spawn_file_actions_adddup2. It may create
+// temporary duplicates of fds to use as the source of a dup2; they
+// are closed on destruction.
+//
+// The dup2 sequence is guaranteed to not contain dup2(x, x) for any
+// x; if such an element is present in the input, it will be dup2()ed
+// from a temporary fd to ensure that the close-on-exec bit is cleared.
+//
+// In general, this is *not* guaranteed to minimize the use of
+// temporary fds.
+
+namespace mozilla {
+namespace ipc {
+
+class FileDescriptorShuffle {
+ public:
+ FileDescriptorShuffle() = default;
+ ~FileDescriptorShuffle();
+
+ using MappingRef = mozilla::Span<const std::pair<int, int>>;
+
+ // Translate the given mapping, creating temporary fds as needed.
+ // Can fail (return false) on failure to duplicate fds.
+ bool Init(MappingRef aMapping);
+
+ // Accessor for the dup2() sequence. Do not use the returned value
+ // or the fds contained in it after this object is destroyed.
+ MappingRef Dup2Sequence() const { return mMapping; }
+
+ // Tests whether the given fd is used as a destination in this mapping.
+ // Can be used to close other fds after performing the dup2()s.
+ bool MapsTo(int aFd) const;
+
+ // Forget the information, so that it's destructor will not try to
+ // delete FDs duped by itself.
+ void Forget() { mTempFds.Clear(); }
+
+ private:
+ nsTArray<std::pair<int, int>> mMapping;
+ nsTArray<int> mTempFds;
+ int mMaxDst;
+
+ FileDescriptorShuffle(const FileDescriptorShuffle&) = delete;
+ void operator=(const FileDescriptorShuffle&) = delete;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_FileDescriptorShuffle_h
diff --git a/ipc/glue/FileDescriptorUtils.cpp b/ipc/glue/FileDescriptorUtils.cpp
new file mode 100644
index 0000000000..e4af78e79e
--- /dev/null
+++ b/ipc/glue/FileDescriptorUtils.cpp
@@ -0,0 +1,109 @@
+/* -*- 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 "FileDescriptorUtils.h"
+
+#include "nsIEventTarget.h"
+
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "prio.h"
+#include "private/pprio.h"
+
+#include <errno.h>
+#ifdef XP_WIN
+# include <io.h>
+#else
+# include <unistd.h>
+#endif
+
+using mozilla::ipc::CloseFileRunnable;
+
+CloseFileRunnable::CloseFileRunnable(const FileDescriptor& aFileDescriptor)
+ : Runnable("CloseFileRunnable"), mFileDescriptor(aFileDescriptor) {
+ MOZ_ASSERT(aFileDescriptor.IsValid());
+}
+
+CloseFileRunnable::~CloseFileRunnable() {
+ if (mFileDescriptor.IsValid()) {
+ // It's probably safer to take the main thread IO hit here rather than leak
+ // the file descriptor.
+ CloseFile();
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(CloseFileRunnable, Runnable)
+
+void CloseFileRunnable::Dispatch() {
+ nsCOMPtr<nsIEventTarget> eventTarget =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ NS_ENSURE_TRUE_VOID(eventTarget);
+
+ nsresult rv = eventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS_VOID(rv);
+}
+
+void CloseFileRunnable::CloseFile() {
+ // It's possible for this to happen on the main thread if the dispatch to the
+ // stream service fails so we can't assert the thread on which we're running.
+ mFileDescriptor = FileDescriptor();
+}
+
+NS_IMETHODIMP
+CloseFileRunnable::Run() {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ CloseFile();
+ return NS_OK;
+}
+
+namespace mozilla {
+namespace ipc {
+
+FILE* FileDescriptorToFILE(const FileDescriptor& aDesc, const char* aOpenMode) {
+ if (!aDesc.IsValid()) {
+ errno = EBADF;
+ return nullptr;
+ }
+ auto handle = aDesc.ClonePlatformHandle();
+#ifdef XP_WIN
+ int fd = _open_osfhandle(static_cast<intptr_t>(handle.get()), 0);
+ if (fd == -1) {
+ return nullptr;
+ }
+ Unused << handle.release();
+#else
+ int fd = handle.release();
+#endif
+ FILE* file = fdopen(fd, aOpenMode);
+ if (!file) {
+ int saved_errno = errno;
+ close(fd);
+ errno = saved_errno;
+ }
+ return file;
+}
+
+FileDescriptor FILEToFileDescriptor(FILE* aStream) {
+ if (!aStream) {
+ errno = EBADF;
+ return FileDescriptor();
+ }
+#ifdef XP_WIN
+ int fd = _fileno(aStream);
+ if (fd == -1) {
+ return FileDescriptor();
+ }
+ return FileDescriptor(reinterpret_cast<HANDLE>(_get_osfhandle(fd)));
+#else
+ return FileDescriptor(fileno(aStream));
+#endif
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/FileDescriptorUtils.h b/ipc/glue/FileDescriptorUtils.h
new file mode 100644
index 0000000000..e8aab0639e
--- /dev/null
+++ b/ipc/glue/FileDescriptorUtils.h
@@ -0,0 +1,53 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_FileDescriptorUtils_h
+#define mozilla_ipc_FileDescriptorUtils_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "nsThreadUtils.h"
+#include <stdio.h>
+
+namespace mozilla {
+namespace ipc {
+
+// When Dispatch() is called (from main thread) this class arranges to close the
+// provided FileDescriptor on one of the socket transport service threads (to
+// avoid main thread I/O).
+class CloseFileRunnable final : public Runnable {
+ typedef mozilla::ipc::FileDescriptor FileDescriptor;
+
+ FileDescriptor mFileDescriptor;
+
+ public:
+ explicit CloseFileRunnable(const FileDescriptor& aFileDescriptor);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRUNNABLE
+
+ void Dispatch();
+
+ private:
+ ~CloseFileRunnable();
+
+ void CloseFile();
+};
+
+// On failure, FileDescriptorToFILE returns nullptr; on success,
+// returns duplicated FILE*.
+// This is meant for use with FileDescriptors received over IPC.
+FILE* FileDescriptorToFILE(const FileDescriptor& aDesc, const char* aOpenMode);
+
+// FILEToFileDescriptor does not consume the given FILE*; it must be
+// fclose()d as normal, and this does not invalidate the returned
+// FileDescriptor.
+FileDescriptor FILEToFileDescriptor(FILE* aStream);
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_FileDescriptorUtils_h
diff --git a/ipc/glue/ForkServer.cpp b/ipc/glue/ForkServer.cpp
new file mode 100644
index 0000000000..23ce9362fb
--- /dev/null
+++ b/ipc/glue/ForkServer.cpp
@@ -0,0 +1,298 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 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 "mozilla/ipc/ForkServer.h"
+#include "mozilla/Logging.h"
+#include "chrome/common/chrome_switches.h"
+#include "mozilla/BlockingResourceBase.h"
+#include "mozilla/ipc/ProtocolMessageUtils.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "mozilla/ipc/IPDLParamTraits.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "nsTraceRefcnt.h"
+
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# include "mozilla/SandboxLaunch.h"
+#endif
+
+#include <algorithm>
+
+namespace mozilla {
+namespace ipc {
+
+LazyLogModule gForkServiceLog("ForkService");
+
+ForkServer::ForkServer() {}
+
+/**
+ * Prepare an environment for running a fork server.
+ */
+void ForkServer::InitProcess(int* aArgc, char*** aArgv) {
+ base::InitForkServerProcess();
+
+ mTcver = MakeUnique<MiniTransceiver>(kClientPipeFd,
+ DataBufferClear::AfterReceiving);
+}
+
+/**
+ * Start providing the service at the IPC channel.
+ */
+bool ForkServer::HandleMessages() {
+ while (true) {
+ UniquePtr<IPC::Message> msg;
+ if (!mTcver->Recv(msg)) {
+ break;
+ }
+
+ OnMessageReceived(std::move(msg));
+
+ if (mAppProcBuilder) {
+ // New process - child
+ return false;
+ }
+ }
+ // Stop the server
+ return true;
+}
+
+inline void CleanCString(nsCString& str) {
+ char* data;
+ int sz = str.GetMutableData(&data);
+
+ memset(data, ' ', sz);
+}
+
+inline void CleanString(std::string& str) {
+ const char deadbeef[] =
+ "\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef"
+ "\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef";
+ int pos = 0;
+ size_t sz = str.size();
+ while (sz > 0) {
+ int toclean = std::min(sz, sizeof(deadbeef) - 1);
+ str.replace(pos, toclean, deadbeef);
+ sz -= toclean;
+ pos += toclean;
+ }
+}
+
+inline void PrepareArguments(std::vector<std::string>& aArgv,
+ nsTArray<nsCString>& aArgvArray) {
+ for (auto& elt : aArgvArray) {
+ aArgv.push_back(elt.get());
+ CleanCString(elt);
+ }
+}
+
+// Prepare aOptions->env_map
+inline void PrepareEnv(base::LaunchOptions* aOptions,
+ nsTArray<EnvVar>& aEnvMap) {
+ for (auto& elt : aEnvMap) {
+ nsCString& var = std::get<0>(elt);
+ nsCString& val = std::get<1>(elt);
+ aOptions->env_map[var.get()] = val.get();
+ CleanCString(var);
+ CleanCString(val);
+ }
+}
+
+// Prepare aOptions->fds_to_remap
+inline void PrepareFdsRemap(base::LaunchOptions* aOptions,
+ nsTArray<FdMapping>& aFdsRemap) {
+ MOZ_LOG(gForkServiceLog, LogLevel::Verbose, ("fds mapping:"));
+ for (auto& elt : aFdsRemap) {
+ // FDs are duplicated here.
+ int fd = std::get<0>(elt).ClonePlatformHandle().release();
+ std::pair<int, int> fdmap(fd, std::get<1>(elt));
+ aOptions->fds_to_remap.push_back(fdmap);
+ MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
+ ("\t%d => %d", fdmap.first, fdmap.second));
+ }
+}
+
+template <class P>
+static void ReadParamInfallible(IPC::MessageReader* aReader, P* aResult,
+ const char* aCrashMessage) {
+ if (!IPC::ReadParam(aReader, aResult)) {
+ MOZ_CRASH_UNSAFE(aCrashMessage);
+ }
+}
+
+/**
+ * Parse a Message to get a list of arguments and fill a LaunchOptions.
+ */
+inline bool ParseForkNewSubprocess(IPC::Message& aMsg,
+ std::vector<std::string>& aArgv,
+ base::LaunchOptions* aOptions) {
+ if (aMsg.type() != Msg_ForkNewSubprocess__ID) {
+ MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
+ ("unknown message type %d\n", aMsg.type()));
+ return false;
+ }
+
+ IPC::MessageReader reader(aMsg);
+ nsTArray<nsCString> argv_array;
+ nsTArray<EnvVar> env_map;
+ nsTArray<FdMapping> fds_remap;
+
+ ReadParamInfallible(&reader, &argv_array,
+ "Error deserializing 'nsCString[]'");
+ ReadParamInfallible(&reader, &env_map, "Error deserializing 'EnvVar[]'");
+ ReadParamInfallible(&reader, &fds_remap, "Error deserializing 'FdMapping[]'");
+ reader.EndRead();
+
+ PrepareArguments(aArgv, argv_array);
+ PrepareEnv(aOptions, env_map);
+ PrepareFdsRemap(aOptions, fds_remap);
+
+ return true;
+}
+
+inline void SanitizeBuffers(IPC::Message& aMsg, std::vector<std::string>& aArgv,
+ base::LaunchOptions& aOptions) {
+ // Clean all buffers in the message to make sure content processes
+ // not peeking others.
+ auto& blist = aMsg.Buffers();
+ for (auto itr = blist.Iter(); !itr.Done();
+ itr.Advance(blist, itr.RemainingInSegment())) {
+ memset(itr.Data(), 0, itr.RemainingInSegment());
+ }
+
+ // clean all data string made from the message.
+ for (auto& var : aOptions.env_map) {
+ // Do it anyway since it is not going to be used anymore.
+ CleanString(*const_cast<std::string*>(&var.first));
+ CleanString(var.second);
+ }
+ for (auto& arg : aArgv) {
+ CleanString(arg);
+ }
+}
+
+/**
+ * Extract parameters from the |Message| to create a
+ * |base::AppProcessBuilder| as |mAppProcBuilder|.
+ *
+ * It will return in both the fork server process and the new content
+ * process. |mAppProcBuilder| is null for the fork server.
+ */
+void ForkServer::OnMessageReceived(UniquePtr<IPC::Message> message) {
+ std::vector<std::string> argv;
+ base::LaunchOptions options;
+ if (!ParseForkNewSubprocess(*message, argv, &options)) {
+ return;
+ }
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+ mozilla::SandboxLaunchForkServerPrepare(argv, options);
+#endif
+
+ base::ProcessHandle child_pid = -1;
+ mAppProcBuilder = MakeUnique<base::AppProcessBuilder>();
+ if (!mAppProcBuilder->ForkProcess(argv, options, &child_pid)) {
+ MOZ_CRASH("fail to fork");
+ }
+ MOZ_ASSERT(child_pid >= 0);
+
+ if (child_pid == 0) {
+ // Content process
+ return;
+ }
+
+ // Fork server process
+
+ mAppProcBuilder = nullptr;
+
+ IPC::Message reply(MSG_ROUTING_CONTROL, Reply_ForkNewSubprocess__ID);
+ IPC::MessageWriter writer(reply);
+ WriteIPDLParam(&writer, nullptr, child_pid);
+ mTcver->SendInfallible(reply, "failed to send a reply message");
+
+ // Without this, the content processes that is forked later are
+ // able to read the content of buffers even the buffers have been
+ // released.
+ SanitizeBuffers(*message, argv, options);
+}
+
+/**
+ * Setup and run a fork server at the main thread.
+ *
+ * This function returns for two reasons:
+ * - the fork server is stopped normally, or
+ * - a new process is forked from the fork server and this function
+ * returned in the child, the new process.
+ *
+ * For the later case, aArgc and aArgv are modified to pass the
+ * arguments from the chrome process.
+ */
+bool ForkServer::RunForkServer(int* aArgc, char*** aArgv) {
+#ifdef DEBUG
+ if (getenv("MOZ_FORKSERVER_WAIT_GDB")) {
+ printf(
+ "Waiting for 30 seconds."
+ " Attach the fork server with gdb %s %d\n",
+ (*aArgv)[0], base::GetCurrentProcId());
+ sleep(30);
+ }
+ bool sleep_newproc = !!getenv("MOZ_FORKSERVER_WAIT_GDB_NEWPROC");
+#endif
+
+ // Do this before NS_LogInit() to avoid log files taking lower
+ // FDs.
+ ForkServer forkserver;
+ forkserver.InitProcess(aArgc, aArgv);
+
+ XRE_SetProcessType("forkserver");
+ NS_LogInit();
+ mozilla::LogModule::Init(0, nullptr);
+ MOZ_LOG(gForkServiceLog, LogLevel::Verbose, ("Start a fork server"));
+ {
+ DebugOnly<base::ProcessHandle> forkserver_pid = base::GetCurrentProcId();
+ if (forkserver.HandleMessages()) {
+ // In the fork server process
+ // The server has stopped.
+ MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
+ ("Terminate the fork server"));
+ NS_LogTerm();
+ return true;
+ }
+ // Now, we are running in a content process just forked from
+ // the fork server process.
+ MOZ_ASSERT(base::GetCurrentProcId() != forkserver_pid);
+ MOZ_LOG(gForkServiceLog, LogLevel::Verbose, ("Fork a new content process"));
+ }
+#ifdef DEBUG
+ if (sleep_newproc) {
+ printf(
+ "Waiting for 30 seconds."
+ " Attach the new process with gdb %s %d\n",
+ (*aArgv)[0], base::GetCurrentProcId());
+ sleep(30);
+ }
+#endif
+ NS_LogTerm();
+
+ MOZ_ASSERT(forkserver.mAppProcBuilder);
+ // |messageloop| has been destroyed. So, we can intialized the
+ // process safely. Message loops may allocates some file
+ // descriptors. If it is destroyed later, it may mess up this
+ // content process by closing wrong file descriptors.
+ forkserver.mAppProcBuilder->InitAppProcess(aArgc, aArgv);
+ forkserver.mAppProcBuilder.reset();
+
+ MOZ_ASSERT("tab"_ns == (*aArgv)[*aArgc - 1], "Only |tab| is allowed!");
+
+ // Open log files again with right names and the new PID.
+ nsTraceRefcnt::ResetLogFiles((*aArgv)[*aArgc - 1]);
+
+ return false;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/ForkServer.h b/ipc/glue/ForkServer.h
new file mode 100644
index 0000000000..ba4ed9d44e
--- /dev/null
+++ b/ipc/glue/ForkServer.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 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/. */
+#ifndef __FORKSERVER_H_
+#define __FORKSERVER_H_
+
+#include "mozilla/UniquePtr.h"
+#include "base/process_util.h"
+#include "mozilla/ipc/MiniTransceiver.h"
+
+namespace mozilla {
+namespace ipc {
+
+class ForkServer {
+ public:
+ // NOTE: This can re-use the same ID as the initial IPC::Channel, as the
+ // initial IPC::Channel will not be used by the fork server.
+ static constexpr int kClientPipeFd = 3;
+
+ ForkServer();
+ ~ForkServer(){};
+
+ void InitProcess(int* aArgc, char*** aArgv);
+ bool HandleMessages();
+
+ // Called when a message is received.
+ void OnMessageReceived(UniquePtr<IPC::Message> message);
+
+ static bool RunForkServer(int* aArgc, char*** aArgv);
+
+ private:
+ UniquePtr<MiniTransceiver> mTcver;
+ UniquePtr<base::AppProcessBuilder> mAppProcBuilder;
+};
+
+enum {
+ Msg_ForkNewSubprocess__ID = 0x7f0, // a random picked number
+ Reply_ForkNewSubprocess__ID,
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // __FORKSERVER_H_
diff --git a/ipc/glue/ForkServiceChild.cpp b/ipc/glue/ForkServiceChild.cpp
new file mode 100644
index 0000000000..db130343cc
--- /dev/null
+++ b/ipc/glue/ForkServiceChild.cpp
@@ -0,0 +1,190 @@
+/* -*- 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 "ForkServiceChild.h"
+#include "ForkServer.h"
+#include "mozilla/ipc/IPDLParamTraits.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+#include "mozilla/ipc/ProtocolMessageUtils.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/Services.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "nsIObserverService.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+namespace mozilla {
+namespace ipc {
+
+extern LazyLogModule gForkServiceLog;
+
+mozilla::UniquePtr<ForkServiceChild> ForkServiceChild::sForkServiceChild;
+
+static bool ConfigurePipeFd(int aFd) {
+ int flags = fcntl(aFd, F_GETFD, 0);
+ return flags != -1 && fcntl(aFd, F_SETFD, flags | FD_CLOEXEC) != -1;
+}
+
+void ForkServiceChild::StartForkServer() {
+ // Create the socket to use for communication, and mark both ends as
+ // FD_CLOEXEC.
+ int fds[2];
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) {
+ MOZ_LOG(gForkServiceLog, LogLevel::Error,
+ ("failed to create fork server socket"));
+ return;
+ }
+ UniqueFileHandle server(fds[0]);
+ UniqueFileHandle client(fds[1]);
+
+ if (!ConfigurePipeFd(server.get()) || !ConfigurePipeFd(client.get())) {
+ MOZ_LOG(gForkServiceLog, LogLevel::Error,
+ ("failed to configure fork server socket"));
+ return;
+ }
+
+ GeckoChildProcessHost* subprocess =
+ new GeckoChildProcessHost(GeckoProcessType_ForkServer, false);
+ subprocess->AddFdToRemap(client.get(), ForkServer::kClientPipeFd);
+ if (!subprocess->LaunchAndWaitForProcessHandle(std::vector<std::string>{})) {
+ MOZ_LOG(gForkServiceLog, LogLevel::Error, ("failed to launch fork server"));
+ return;
+ }
+
+ sForkServiceChild =
+ mozilla::MakeUnique<ForkServiceChild>(server.release(), subprocess);
+}
+
+void ForkServiceChild::StopForkServer() { sForkServiceChild = nullptr; }
+
+ForkServiceChild::ForkServiceChild(int aFd, GeckoChildProcessHost* aProcess)
+ : mFailed(false), mProcess(aProcess) {
+ mTcver = MakeUnique<MiniTransceiver>(aFd);
+}
+
+ForkServiceChild::~ForkServiceChild() {
+ mProcess->Destroy();
+ close(mTcver->GetFD());
+}
+
+Result<Ok, LaunchError> ForkServiceChild::SendForkNewSubprocess(
+ const nsTArray<nsCString>& aArgv, const nsTArray<EnvVar>& aEnvMap,
+ const nsTArray<FdMapping>& aFdsRemap, pid_t* aPid) {
+ mRecvPid = -1;
+ IPC::Message msg(MSG_ROUTING_CONTROL, Msg_ForkNewSubprocess__ID);
+
+ IPC::MessageWriter writer(msg);
+ WriteIPDLParam(&writer, nullptr, aArgv);
+ WriteIPDLParam(&writer, nullptr, aEnvMap);
+ WriteIPDLParam(&writer, nullptr, aFdsRemap);
+ if (!mTcver->Send(msg)) {
+ MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
+ ("the pipe to the fork server is closed or having errors"));
+ OnError();
+ return Err(LaunchError("FSC::SFNS::Send"));
+ }
+
+ UniquePtr<IPC::Message> reply;
+ if (!mTcver->Recv(reply)) {
+ MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
+ ("the pipe to the fork server is closed or having errors"));
+ OnError();
+ return Err(LaunchError("FSC::SFNS::Recv"));
+ }
+ OnMessageReceived(std::move(reply));
+
+ MOZ_ASSERT(mRecvPid != -1);
+ *aPid = mRecvPid;
+ return Ok();
+}
+
+void ForkServiceChild::OnMessageReceived(UniquePtr<IPC::Message> message) {
+ if (message->type() != Reply_ForkNewSubprocess__ID) {
+ MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
+ ("unknown reply type %d", message->type()));
+ return;
+ }
+ IPC::MessageReader reader(*message);
+
+ if (!ReadIPDLParam(&reader, nullptr, &mRecvPid)) {
+ MOZ_CRASH("Error deserializing 'pid_t'");
+ }
+ reader.EndRead();
+}
+
+void ForkServiceChild::OnError() {
+ mFailed = true;
+ ForkServerLauncher::RestartForkServer();
+}
+
+NS_IMPL_ISUPPORTS(ForkServerLauncher, nsIObserver)
+
+bool ForkServerLauncher::mHaveStartedClient = false;
+StaticRefPtr<ForkServerLauncher> ForkServerLauncher::mSingleton;
+
+ForkServerLauncher::ForkServerLauncher() {}
+
+ForkServerLauncher::~ForkServerLauncher() {}
+
+already_AddRefed<ForkServerLauncher> ForkServerLauncher::Create() {
+ if (mSingleton == nullptr) {
+ mSingleton = new ForkServerLauncher();
+ }
+ RefPtr<ForkServerLauncher> launcher = mSingleton;
+ return launcher.forget();
+}
+
+NS_IMETHODIMP
+ForkServerLauncher::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (strcmp(aTopic, NS_XPCOM_STARTUP_CATEGORY) == 0) {
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsSvc != nullptr);
+ // preferences are not available until final-ui-startup
+ obsSvc->AddObserver(this, "final-ui-startup", false);
+ } else if (!mHaveStartedClient && strcmp(aTopic, "final-ui-startup") == 0) {
+ if (StaticPrefs::dom_ipc_forkserver_enable_AtStartup()) {
+ mHaveStartedClient = true;
+ ForkServiceChild::StartForkServer();
+
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ MOZ_ASSERT(obsSvc != nullptr);
+ obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ } else {
+ mSingleton = nullptr;
+ }
+ }
+
+ if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ if (mHaveStartedClient) {
+ mHaveStartedClient = false;
+ ForkServiceChild::StopForkServer();
+ }
+
+ // To make leak checker happy!
+ mSingleton = nullptr;
+ }
+ return NS_OK;
+}
+
+void ForkServerLauncher::RestartForkServer() {
+ // Restart fork server
+ NS_SUCCEEDED(NS_DispatchToMainThreadQueue(
+ NS_NewRunnableFunction("OnForkServerError",
+ [] {
+ if (mSingleton) {
+ ForkServiceChild::StopForkServer();
+ ForkServiceChild::StartForkServer();
+ }
+ }),
+ EventQueuePriority::Idle));
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/ForkServiceChild.h b/ipc/glue/ForkServiceChild.h
new file mode 100644
index 0000000000..40699df061
--- /dev/null
+++ b/ipc/glue/ForkServiceChild.h
@@ -0,0 +1,105 @@
+/* -*- 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/. */
+#ifndef __FORKSERVICE_CHILD_H_
+#define __FORKSERVICE_CHILD_H_
+
+#include "base/process_util.h"
+#include "nsIObserver.h"
+#include "nsString.h"
+#include "mozilla/ipc/MiniTransceiver.h"
+#include "mozilla/ipc/LaunchError.h"
+#include "mozilla/Result.h"
+
+#include <sys/types.h>
+#include <poll.h>
+
+namespace mozilla {
+namespace ipc {
+
+class GeckoChildProcessHost;
+
+/**
+ * This is the interface to the fork server.
+ *
+ * When the chrome process calls |ForkServiceChild| to create a new
+ * process, this class send a message to the fork server through a
+ * pipe and get the PID of the new process from the reply.
+ */
+class ForkServiceChild {
+ public:
+ ForkServiceChild(int aFd, GeckoChildProcessHost* aProcess);
+ virtual ~ForkServiceChild();
+
+ /**
+ * Ask the fork server to create a new process with given parameters.
+ *
+ * The fork server uses |base::LaunchApp()| to create a new
+ * content process with the following parameters.
+ *
+ * \param aArgv assigns |argv| of the content process.
+ * \param aEnvMap sets |LaunchOptions::env_map|.
+ * \param aFdsRemap sets |LaunchOptions::fd_to_remap|.
+ * \param aPid returns the PID of the content process created.
+ * \return true if success.
+ */
+ Result<Ok, LaunchError> SendForkNewSubprocess(
+ const nsTArray<nsCString>& aArgv, const nsTArray<EnvVar>& aEnvMap,
+ const nsTArray<FdMapping>& aFdsRemap, pid_t* aPid);
+
+ /**
+ * Create a fork server process and the singleton of this class.
+ *
+ * This function uses |GeckoChildProcessHost| to launch the fork
+ * server, getting the fd of a pipe/socket to the fork server from
+ * it's |IPC::Channel|.
+ */
+ static void StartForkServer();
+ static void StopForkServer();
+ /**
+ * Return the singleton.
+ */
+ static ForkServiceChild* Get() {
+ auto child = sForkServiceChild.get();
+ return child == nullptr || child->mFailed ? nullptr : child;
+ }
+
+ private:
+ // Called when a message is received.
+ void OnMessageReceived(UniquePtr<IPC::Message> message);
+ void OnError();
+
+ UniquePtr<MiniTransceiver> mTcver;
+ static UniquePtr<ForkServiceChild> sForkServiceChild;
+ pid_t mRecvPid;
+ bool mFailed; // The forkserver has crashed or disconnected.
+ GeckoChildProcessHost* mProcess;
+};
+
+/**
+ * Start a fork server at |xpcom-startup| from the chrome process.
+ */
+class ForkServerLauncher : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ ForkServerLauncher();
+ static already_AddRefed<ForkServerLauncher> Create();
+
+ private:
+ friend class ForkServiceChild;
+ virtual ~ForkServerLauncher();
+
+ static void RestartForkServer();
+
+ static bool mHaveStartedClient;
+ static StaticRefPtr<ForkServerLauncher> mSingleton;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif /* __FORKSERVICE_CHILD_H_ */
diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp
new file mode 100644
index 0000000000..b4895e0e45
--- /dev/null
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -0,0 +1,1896 @@
+/* -*- 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 "GeckoChildProcessHost.h"
+
+#include "base/command_line.h"
+#include "base/process_util.h"
+#include "base/string_util.h"
+#include "base/task.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/process_watcher.h"
+#ifdef MOZ_WIDGET_COCOA
+# include <bsm/libbsm.h>
+# include <mach/mach_traps.h>
+# include <servers/bootstrap.h>
+# include "SharedMemoryBasic.h"
+# include "base/rand_util.h"
+# include "chrome/common/mach_ipc_mac.h"
+# include "nsILocalFileMac.h"
+#endif
+
+#include "GeckoProfiler.h"
+#include "MainThreadUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Sprintf.h"
+#include "nsXPCOMPrivate.h"
+#include "prenv.h"
+#include "prerror.h"
+
+#if defined(MOZ_SANDBOX)
+# include "mozilla/SandboxSettings.h"
+# include "nsAppDirectoryServiceDefs.h"
+#endif
+
+#include <sys/stat.h>
+
+#include "ProtocolUtils.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/GeckoArgs.h"
+#include "mozilla/Omnijar.h"
+#include "mozilla/RDDProcessHost.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/Services.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/ipc/BrowserProcessSubThread.h"
+#include "mozilla/ipc/EnvironmentMap.h"
+#include "mozilla/ipc/NodeController.h"
+#include "mozilla/net/SocketProcessHost.h"
+#include "nsDirectoryService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsExceptionHandler.h"
+#include "nsIFile.h"
+#include "nsIObserverService.h"
+#include "nsPrintfCString.h"
+
+#ifdef XP_WIN
+# include <stdlib.h>
+
+# include "nsIWinTaskbar.h"
+# define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1"
+
+# if defined(MOZ_SANDBOX)
+# include "WinUtils.h"
+# include "mozilla/Preferences.h"
+# include "mozilla/sandboxing/sandboxLogging.h"
+# if defined(_ARM64_)
+# include "mozilla/remoteSandboxBroker.h"
+# endif
+# endif
+
+# include "mozilla/NativeNt.h"
+# include "mozilla/CacheNtDllThunk.h"
+#endif
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# include "mozilla/SandboxLaunch.h"
+#endif
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+# include "GMPProcessParent.h"
+# include "nsMacUtilsImpl.h"
+#endif
+
+#include "mozilla/ipc/UtilityProcessHost.h"
+
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsTArray.h"
+#include "nscore.h" // for NS_FREE_PERMANENT_DATA
+#include "private/pprio.h"
+#include "nsIThread.h"
+
+using mozilla::MonitorAutoLock;
+using mozilla::Preferences;
+using mozilla::StaticMutexAutoLock;
+
+namespace mozilla {
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc,
+ PR_Close)
+}
+
+using mozilla::ScopedPRFileDesc;
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "AndroidBridge.h"
+# include "mozilla/java/GeckoProcessManagerWrappers.h"
+# include "mozilla/java/GeckoProcessTypeWrappers.h"
+# include "mozilla/java/GeckoResultWrappers.h"
+# include "mozilla/jni/Refs.h"
+# include "mozilla/jni/Utils.h"
+#endif
+
+#ifdef MOZ_ENABLE_FORKSERVER
+# include "mozilla/ipc/ForkServiceChild.h"
+#endif
+
+static bool ShouldHaveDirectoryService() {
+ return GeckoProcessType_Default == XRE_GetProcessType();
+}
+
+namespace mozilla {
+namespace ipc {
+
+struct LaunchResults {
+ base::ProcessHandle mHandle = 0;
+#ifdef XP_MACOSX
+ task_t mChildTask = MACH_PORT_NULL;
+#endif
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+ RefPtr<AbstractSandboxBroker> mSandboxBroker;
+#endif
+};
+typedef mozilla::MozPromise<LaunchResults, LaunchError, true>
+ ProcessLaunchPromise;
+
+static Atomic<int32_t> gChildCounter;
+
+static inline nsISerialEventTarget* IOThread() {
+ return XRE_GetIOMessageLoop()->SerialEventTarget();
+}
+
+class BaseProcessLauncher {
+ public:
+ BaseProcessLauncher(GeckoChildProcessHost* aHost,
+ std::vector<std::string>&& aExtraOpts)
+ : mProcessType(aHost->mProcessType),
+ mLaunchOptions(std::move(aHost->mLaunchOptions)),
+ mExtraOpts(std::move(aExtraOpts)),
+#ifdef XP_WIN
+ mGroupId(aHost->mGroupId),
+#endif
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+ mAllowedFilesRead(aHost->mAllowedFilesRead),
+ mSandboxLevel(aHost->mSandboxLevel),
+ mSandbox(aHost->mSandbox),
+ mIsFileContent(aHost->mIsFileContent),
+ mEnableSandboxLogging(aHost->mEnableSandboxLogging),
+#endif
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ mDisableOSActivityMode(aHost->mDisableOSActivityMode),
+#endif
+ mTmpDirName(aHost->mTmpDirName),
+ mChildId(++gChildCounter) {
+ SprintfLiteral(mPidString, "%" PRIPID, base::GetCurrentProcId());
+ aHost->mInitialChannelId.ToProvidedString(mInitialChannelIdString);
+
+ // Compute the serial event target we'll use for launching.
+ nsCOMPtr<nsIEventTarget> threadOrPool = GetIPCLauncher();
+ mLaunchThread =
+ TaskQueue::Create(threadOrPool.forget(), "BaseProcessLauncher");
+
+ if (ShouldHaveDirectoryService()) {
+ // "Current process directory" means the app dir, not the current
+ // working dir or similar.
+ mozilla::Unused
+ << nsDirectoryService::gService->GetCurrentProcessDirectory(
+ getter_AddRefs(mAppDir));
+ }
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BaseProcessLauncher);
+
+#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH
+ void SetLaunchArchitecture(uint32_t aLaunchArch) {
+ mLaunchArch = aLaunchArch;
+ }
+#endif
+
+ RefPtr<ProcessLaunchPromise> Launch(GeckoChildProcessHost*);
+
+ protected:
+ virtual ~BaseProcessLauncher() = default;
+
+ RefPtr<ProcessLaunchPromise> PerformAsyncLaunch();
+ RefPtr<ProcessLaunchPromise> FinishLaunch();
+
+ // Overrideable hooks. If superclass behavior is invoked, it's always at the
+ // top of the override.
+ virtual bool SetChannel(IPC::Channel*) = 0;
+ virtual Result<Ok, LaunchError> DoSetup();
+ virtual RefPtr<ProcessHandlePromise> DoLaunch() = 0;
+ virtual Result<Ok, LaunchError> DoFinishLaunch() { return Ok(); };
+
+ void MapChildLogging();
+
+ static BinPathType GetPathToBinary(FilePath&, GeckoProcessType);
+
+ void GetChildLogName(const char* origLogName, nsACString& buffer);
+
+ const char* ChildProcessType() {
+ return XRE_GeckoProcessTypeToString(mProcessType);
+ }
+
+ nsCOMPtr<nsISerialEventTarget> mLaunchThread;
+ GeckoProcessType mProcessType;
+ UniquePtr<base::LaunchOptions> mLaunchOptions;
+#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH
+ uint32_t mLaunchArch = base::PROCESS_ARCH_INVALID;
+#endif
+ std::vector<std::string> mExtraOpts;
+#ifdef XP_WIN
+ nsString mGroupId;
+#endif
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+ std::vector<std::wstring> mAllowedFilesRead;
+ int32_t mSandboxLevel;
+ SandboxingKind mSandbox;
+ bool mIsFileContent;
+ bool mEnableSandboxLogging;
+#endif
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ // Controls whether or not the process will be launched with
+ // environment variable OS_ACTIVITY_MODE set to "disabled".
+ bool mDisableOSActivityMode;
+#endif
+ nsCString mTmpDirName;
+ LaunchResults mResults = LaunchResults();
+ int32_t mChildId;
+ TimeStamp mStartTimeStamp = TimeStamp::Now();
+ char mPidString[32];
+ char mInitialChannelIdString[NSID_LENGTH];
+
+ // Set during launch.
+ IPC::Channel::ChannelId mChannelId;
+ ScopedPRFileDesc mCrashAnnotationReadPipe;
+ ScopedPRFileDesc mCrashAnnotationWritePipe;
+ nsCOMPtr<nsIFile> mAppDir;
+};
+
+#ifdef XP_WIN
+class WindowsProcessLauncher : public BaseProcessLauncher {
+ public:
+ WindowsProcessLauncher(GeckoChildProcessHost* aHost,
+ std::vector<std::string>&& aExtraOpts)
+ : BaseProcessLauncher(aHost, std::move(aExtraOpts)),
+ mCachedNtdllThunk(GetCachedNtDllThunk()),
+ mWerDataPointer(&(aHost->mWerData)) {}
+
+ protected:
+ bool SetChannel(IPC::Channel*) override { return true; }
+ virtual Result<Ok, LaunchError> DoSetup() override;
+ virtual RefPtr<ProcessHandlePromise> DoLaunch() override;
+ virtual Result<Ok, LaunchError> DoFinishLaunch() override;
+
+ mozilla::Maybe<CommandLine> mCmdLine;
+ bool mUseSandbox = false;
+
+ const Buffer<IMAGE_THUNK_DATA>* mCachedNtdllThunk;
+ CrashReporter::WindowsErrorReportingData const* mWerDataPointer;
+};
+typedef WindowsProcessLauncher ProcessLauncher;
+#endif // XP_WIN
+
+#ifdef OS_POSIX
+class PosixProcessLauncher : public BaseProcessLauncher {
+ public:
+ PosixProcessLauncher(GeckoChildProcessHost* aHost,
+ std::vector<std::string>&& aExtraOpts)
+ : BaseProcessLauncher(aHost, std::move(aExtraOpts)),
+ mProfileDir(aHost->mProfileDir),
+ mChannelDstFd(-1) {}
+
+ protected:
+ bool SetChannel(IPC::Channel* aChannel) override {
+ // The source fd is owned by the channel; take ownership by
+ // dup()ing it and closing the channel's copy. The destination fd
+ // is with respect to the not-yet-launched child process, so for
+ // this purpose it's just a number.
+ int origSrcFd;
+ aChannel->GetClientFileDescriptorMapping(&origSrcFd, &mChannelDstFd);
+# ifndef MOZ_WIDGET_ANDROID
+ MOZ_ASSERT(mChannelDstFd >= 0);
+# endif
+ mChannelSrcFd.reset(dup(origSrcFd));
+ if (NS_WARN_IF(!mChannelSrcFd)) {
+ return false;
+ }
+ aChannel->CloseClientFileDescriptor();
+ return true;
+ }
+
+ virtual Result<Ok, LaunchError> DoSetup() override;
+ virtual RefPtr<ProcessHandlePromise> DoLaunch() override;
+ virtual Result<Ok, LaunchError> DoFinishLaunch() override;
+
+ nsCOMPtr<nsIFile> mProfileDir;
+
+ std::vector<std::string> mChildArgv;
+ UniqueFileHandle mChannelSrcFd;
+ int mChannelDstFd;
+};
+
+# if defined(XP_MACOSX)
+class MacProcessLauncher : public PosixProcessLauncher {
+ public:
+ MacProcessLauncher(GeckoChildProcessHost* aHost,
+ std::vector<std::string>&& aExtraOpts)
+ : PosixProcessLauncher(aHost, std::move(aExtraOpts)),
+ // Put a random number into the channel name, so that
+ // a compromised renderer can't pretend being the child
+ // that's forked off.
+ mMachConnectionName(
+ StringPrintf("org.mozilla.machname.%d",
+ base::RandInt(0, std::numeric_limits<int>::max()))) {
+ MOZ_ASSERT(mMachConnectionName.size() < BOOTSTRAP_MAX_NAME_LEN);
+ }
+
+ protected:
+ virtual Result<Ok, LaunchError> DoFinishLaunch() override;
+
+ std::string mMachConnectionName;
+ // We add a mach port to the command line so the child can communicate its
+ // 'task_t' back to the parent.
+ mozilla::UniqueMachReceiveRight mParentRecvPort;
+
+ friend class PosixProcessLauncher;
+};
+typedef MacProcessLauncher ProcessLauncher;
+# elif defined(MOZ_WIDGET_ANDROID)
+class AndroidProcessLauncher : public PosixProcessLauncher {
+ public:
+ AndroidProcessLauncher(GeckoChildProcessHost* aHost,
+ std::vector<std::string>&& aExtraOpts)
+ : PosixProcessLauncher(aHost, std::move(aExtraOpts)) {}
+
+ protected:
+ virtual RefPtr<ProcessHandlePromise> DoLaunch() override;
+ RefPtr<ProcessHandlePromise> LaunchAndroidService(
+ const GeckoProcessType aType, const std::vector<std::string>& argv,
+ const base::file_handle_mapping_vector& fds_to_remap);
+};
+typedef AndroidProcessLauncher ProcessLauncher;
+// NB: Technically Android is linux (i.e. XP_LINUX is defined), but we want
+// orthogonal IPC machinery there. Conversely, there are tier-3 non-Linux
+// platforms (BSD and Solaris) where we want the "linux" IPC machinery. So
+// we use MOZ_WIDGET_* to choose the platform backend.
+# elif defined(MOZ_WIDGET_GTK)
+class LinuxProcessLauncher : public PosixProcessLauncher {
+ public:
+ LinuxProcessLauncher(GeckoChildProcessHost* aHost,
+ std::vector<std::string>&& aExtraOpts)
+ : PosixProcessLauncher(aHost, std::move(aExtraOpts)) {}
+
+ protected:
+ virtual Result<Ok, LaunchError> DoSetup() override;
+};
+typedef LinuxProcessLauncher ProcessLauncher;
+# elif
+# error "Unknown platform"
+# endif
+#endif // OS_POSIX
+
+using base::ProcessHandle;
+using mozilla::ipc::BaseProcessLauncher;
+using mozilla::ipc::ProcessLauncher;
+
+mozilla::StaticAutoPtr<mozilla::LinkedList<GeckoChildProcessHost>>
+ GeckoChildProcessHost::sGeckoChildProcessHosts;
+
+mozilla::StaticMutex GeckoChildProcessHost::sMutex;
+
+GeckoChildProcessHost::GeckoChildProcessHost(GeckoProcessType aProcessType,
+ bool aIsFileContent)
+ : mProcessType(aProcessType),
+ mIsFileContent(aIsFileContent),
+ mMonitor("mozilla.ipc.GeckoChildProcessHost.mMonitor"),
+ mLaunchOptions(MakeUnique<base::LaunchOptions>()),
+ mInitialChannelId(nsID::GenerateUUID()),
+ mProcessState(CREATING_CHANNEL),
+#ifdef XP_WIN
+ mGroupId(u"-"),
+ mWerData{.mWerNotifyProc = CrashReporter::WerNotifyProc,
+ .mChildPid = 0,
+ .mMinidumpFile = {}},
+#endif
+#if defined(MOZ_SANDBOX) && defined(XP_WIN)
+ mEnableSandboxLogging(false),
+ mSandboxLevel(0),
+#endif
+ mHandleLock("mozilla.ipc.GeckoChildProcessHost.mHandleLock"),
+ mChildProcessHandle(0),
+#if defined(MOZ_WIDGET_COCOA)
+ mChildTask(MACH_PORT_NULL),
+#endif
+#if defined(MOZ_SANDBOX) && defined(XP_MACOSX)
+ mDisableOSActivityMode(false),
+#endif
+ mDestroying(false) {
+ MOZ_COUNT_CTOR(GeckoChildProcessHost);
+ StaticMutexAutoLock lock(sMutex);
+ if (!sGeckoChildProcessHosts) {
+ sGeckoChildProcessHosts = new mozilla::LinkedList<GeckoChildProcessHost>();
+ }
+ sGeckoChildProcessHosts->insertBack(this);
+#if defined(MOZ_SANDBOX) && defined(XP_LINUX)
+ if (aProcessType == GeckoProcessType_Content) {
+# if defined(MOZ_CONTENT_TEMP_DIR)
+ // The content process needs the content temp dir:
+ nsCOMPtr<nsIFile> contentTempDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR,
+ getter_AddRefs(contentTempDir));
+ if (NS_SUCCEEDED(rv)) {
+ contentTempDir->GetNativePath(mTmpDirName);
+ }
+# endif
+ } else if (aProcessType == GeckoProcessType_RDD) {
+ // The RDD process makes limited use of EGL. If Mesa's shader
+ // cache is enabled and the directory isn't explicitly set, then
+ // it will try to getpwuid() the user which can cause problems
+ // with sandboxing. Because we shouldn't need shader caching in
+ // this process, we just disable the cache to prevent that.
+ mLaunchOptions->env_map["MESA_GLSL_CACHE_DISABLE"] = "true";
+ mLaunchOptions->env_map["MESA_SHADER_CACHE_DISABLE"] = "true";
+ // In case the nvidia driver is also loaded:
+ mLaunchOptions->env_map["__GL_SHADER_DISK_CACHE"] = "0";
+ }
+#endif
+#if defined(MOZ_ENABLE_FORKSERVER)
+ if (aProcessType == GeckoProcessType_Content && ForkServiceChild::Get()) {
+ mLaunchOptions->use_forkserver = true;
+ }
+#endif
+}
+
+GeckoChildProcessHost::~GeckoChildProcessHost()
+
+{
+ AssertIOThread();
+ MOZ_RELEASE_ASSERT(mDestroying);
+
+ MOZ_COUNT_DTOR(GeckoChildProcessHost);
+
+ {
+ mozilla::AutoWriteLock hLock(mHandleLock);
+#if defined(MOZ_WIDGET_COCOA)
+ if (mChildTask != MACH_PORT_NULL) {
+ mach_port_deallocate(mach_task_self(), mChildTask);
+ }
+#endif
+
+ if (mChildProcessHandle != 0) {
+#if defined(XP_WIN)
+ CrashReporter::DeregisterChildCrashAnnotationFileDescriptor(
+ base::GetProcId(mChildProcessHandle));
+#else
+ CrashReporter::DeregisterChildCrashAnnotationFileDescriptor(
+ mChildProcessHandle);
+#endif
+
+ ProcessWatcher::EnsureProcessTerminated(
+ mChildProcessHandle
+#ifdef NS_FREE_PERMANENT_DATA
+ // If we're doing leak logging, shutdown can be slow.
+ ,
+ false // don't "force"
+#endif
+ );
+ mChildProcessHandle = 0;
+ }
+ }
+
+#if defined(MOZ_SANDBOX) && defined(XP_WIN)
+ if (mSandboxBroker) {
+ mSandboxBroker->Shutdown();
+ mSandboxBroker = nullptr;
+ }
+#endif
+}
+
+base::ProcessHandle GeckoChildProcessHost::GetChildProcessHandle() {
+ mozilla::AutoReadLock handleLock(mHandleLock);
+ return mChildProcessHandle;
+}
+
+base::ProcessId GeckoChildProcessHost::GetChildProcessId() {
+ mozilla::AutoReadLock handleLock(mHandleLock);
+ if (!mChildProcessHandle) {
+ return 0;
+ }
+ return base::GetProcId(mChildProcessHandle);
+}
+
+#ifdef XP_MACOSX
+task_t GeckoChildProcessHost::GetChildTask() {
+ mozilla::AutoReadLock handleLock(mHandleLock);
+ return mChildTask;
+}
+#endif
+
+void GeckoChildProcessHost::RemoveFromProcessList() {
+ StaticMutexAutoLock lock(sMutex);
+ if (!sGeckoChildProcessHosts) {
+ return;
+ }
+ LinkedListElement<GeckoChildProcessHost>::removeFrom(
+ *sGeckoChildProcessHosts);
+}
+
+void GeckoChildProcessHost::Destroy() {
+ MOZ_RELEASE_ASSERT(!mDestroying);
+ // We can remove from the list before it's really destroyed
+ RemoveFromProcessList();
+ RefPtr<ProcessHandlePromise> whenReady = mHandlePromise;
+
+ if (!whenReady) {
+ // AsyncLaunch not called yet, so dispatch immediately.
+ whenReady = ProcessHandlePromise::CreateAndReject(
+ LaunchError("DestroyEarly"), __func__);
+ }
+
+ using Value = ProcessHandlePromise::ResolveOrRejectValue;
+ mDestroying = true;
+ whenReady->Then(XRE_GetIOMessageLoop()->SerialEventTarget(), __func__,
+ [this](const Value&) { delete this; });
+}
+
+// static
+mozilla::BinPathType BaseProcessLauncher::GetPathToBinary(
+ FilePath& exePath, GeckoProcessType processType) {
+ BinPathType pathType = XRE_GetChildProcBinPathType(processType);
+
+ if (pathType == BinPathType::Self) {
+#if defined(OS_WIN)
+ wchar_t exePathBuf[MAXPATHLEN];
+ if (!::GetModuleFileNameW(nullptr, exePathBuf, MAXPATHLEN)) {
+ MOZ_CRASH("GetModuleFileNameW failed (FIXME)");
+ }
+ exePath = FilePath::FromWStringHack(exePathBuf);
+#elif defined(OS_POSIX)
+ exePath = FilePath(CommandLine::ForCurrentProcess()->argv()[0]);
+#else
+# error Sorry; target OS not supported yet.
+#endif
+ return pathType;
+ }
+
+ if (ShouldHaveDirectoryService()) {
+ MOZ_ASSERT(gGREBinPath);
+#ifdef OS_WIN
+ exePath = FilePath(char16ptr_t(gGREBinPath));
+#elif MOZ_WIDGET_COCOA
+ nsCOMPtr<nsIFile> childProcPath;
+ NS_NewLocalFile(nsDependentString(gGREBinPath), false,
+ getter_AddRefs(childProcPath));
+
+ // We need to use an App Bundle on OS X so that we can hide
+ // the dock icon. See Bug 557225.
+ childProcPath->AppendNative("plugin-container.app"_ns);
+ childProcPath->AppendNative("Contents"_ns);
+ childProcPath->AppendNative("MacOS"_ns);
+ nsCString tempCPath;
+ childProcPath->GetNativePath(tempCPath);
+ exePath = FilePath(tempCPath.get());
+#else
+ nsCString path;
+ NS_CopyUnicodeToNative(nsDependentString(gGREBinPath), path);
+ exePath = FilePath(path.get());
+#endif
+ }
+
+ if (exePath.empty()) {
+#ifdef OS_WIN
+ exePath =
+ FilePath::FromWStringHack(CommandLine::ForCurrentProcess()->program());
+#else
+ exePath = FilePath(CommandLine::ForCurrentProcess()->argv()[0]);
+#endif
+ exePath = exePath.DirName();
+ }
+
+ exePath = exePath.AppendASCII(MOZ_CHILD_PROCESS_NAME);
+
+ return pathType;
+}
+
+#ifdef MOZ_WIDGET_COCOA
+class AutoCFTypeObject {
+ public:
+ explicit AutoCFTypeObject(CFTypeRef object) { mObject = object; }
+ ~AutoCFTypeObject() { ::CFRelease(mObject); }
+
+ private:
+ CFTypeRef mObject;
+};
+#endif
+
+// We start the unique IDs at 1 so that 0 can be used to mean that
+// a component has no unique ID assigned to it.
+uint32_t GeckoChildProcessHost::sNextUniqueID = 1;
+
+/* static */
+uint32_t GeckoChildProcessHost::GetUniqueID() { return sNextUniqueID++; }
+
+/* static */
+void GeckoChildProcessHost::SetEnv(const char* aKey, const char* aValue) {
+ MOZ_ASSERT(mLaunchOptions);
+ mLaunchOptions->env_map[ENVIRONMENT_STRING(aKey)] =
+ ENVIRONMENT_STRING(aValue);
+}
+
+void GeckoChildProcessHost::PrepareLaunch() {
+ if (CrashReporter::GetEnabled()) {
+ CrashReporter::OOPInit();
+ }
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+ SandboxLaunchPrepare(mProcessType, mLaunchOptions.get());
+#endif
+
+#ifdef XP_WIN
+
+# if defined(MOZ_SANDBOX)
+ // We need to get the pref here as the process is launched off main thread.
+ if (mProcessType == GeckoProcessType_Content) {
+ // Win32k Lockdown state must be initialized on the main thread.
+ // This is our last chance to do it before it is read on the IPC Launch
+ // thread
+ GetWin32kLockdownState();
+ mSandboxLevel = GetEffectiveContentSandboxLevel();
+ mEnableSandboxLogging =
+ Preferences::GetBool("security.sandbox.logging.enabled");
+
+ // We currently have to whitelist certain paths for tests to work in some
+ // development configurations.
+ nsAutoString readPaths;
+ nsresult rv = Preferences::GetString(
+ "security.sandbox.content.read_path_whitelist", readPaths);
+ if (NS_SUCCEEDED(rv)) {
+ for (const nsAString& readPath : readPaths.Split(',')) {
+ nsString trimmedPath(readPath);
+ trimmedPath.Trim(" ", true, true);
+ std::wstring resolvedPath(trimmedPath.Data());
+ // Check if path ends with '\' as this indicates we want to give read
+ // access to a directory and so it needs a wildcard.
+ if (resolvedPath.back() == L'\\') {
+ resolvedPath.append(L"*");
+ }
+ mAllowedFilesRead.push_back(resolvedPath);
+ }
+ }
+ }
+# endif
+
+# if defined(MOZ_SANDBOX)
+ // For other process types we can't rely on them being launched on main
+ // thread and they may not have access to prefs in the child process, so allow
+ // them to turn on logging via an environment variable.
+ mEnableSandboxLogging =
+ mEnableSandboxLogging || !!PR_GetEnv("MOZ_SANDBOX_LOGGING");
+
+# endif
+#elif defined(XP_MACOSX)
+# if defined(MOZ_SANDBOX)
+ if (ShouldHaveDirectoryService() &&
+ mProcessType != GeckoProcessType_GMPlugin) {
+ mozilla::Unused << NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(mProfileDir));
+ }
+# endif
+#endif
+}
+
+#ifdef XP_WIN
+void GeckoChildProcessHost::InitWindowsGroupID() {
+ // On Win7+, pass the application user model to the child, so it can
+ // register with it. This insures windows created by the container
+ // properly group with the parent app on the Win7 taskbar.
+ nsCOMPtr<nsIWinTaskbar> taskbarInfo = do_GetService(NS_TASKBAR_CONTRACTID);
+ if (taskbarInfo) {
+ bool isSupported = false;
+ taskbarInfo->GetAvailable(&isSupported);
+ nsAutoString appId;
+ if (isSupported && NS_SUCCEEDED(taskbarInfo->GetDefaultGroupId(appId))) {
+ MOZ_ASSERT(mGroupId.EqualsLiteral("-"));
+ mGroupId.Assign(appId);
+ }
+ }
+}
+#endif
+
+bool GeckoChildProcessHost::SyncLaunch(std::vector<std::string> aExtraOpts,
+ int aTimeoutMs) {
+ if (!AsyncLaunch(std::move(aExtraOpts))) {
+ return false;
+ }
+ return WaitUntilConnected(aTimeoutMs);
+}
+
+// Note: for most process types, we currently call AsyncLaunch, and therefore
+// the *ProcessLauncher constructor, on the main thread, while the
+// ProcessLauncher methods to actually execute the launch are called on the IO
+// or IPC launcher thread. GMP processes are an exception - the GMP code
+// invokes GeckoChildProcessHost from non-main-threads, and therefore we cannot
+// rely on having access to mainthread-only services (like the directory
+// service) from this code if we're launching that type of process.
+bool GeckoChildProcessHost::AsyncLaunch(std::vector<std::string> aExtraOpts) {
+ PrepareLaunch();
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ if (IsMacSandboxLaunchEnabled() && !AppendMacSandboxParams(aExtraOpts)) {
+ return false;
+ }
+#endif
+
+ RefPtr<BaseProcessLauncher> launcher =
+ new ProcessLauncher(this, std::move(aExtraOpts));
+#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH
+ launcher->SetLaunchArchitecture(mLaunchArch);
+#endif
+
+ // Note: Destroy() waits on mHandlePromise to delete |this|. As such, we want
+ // to be sure that all of our post-launch processing on |this| happens before
+ // mHandlePromise notifies.
+ MOZ_ASSERT(mHandlePromise == nullptr);
+ mHandlePromise =
+ mozilla::InvokeAsync<GeckoChildProcessHost*>(
+ IOThread(), launcher.get(), __func__, &BaseProcessLauncher::Launch,
+ this)
+ ->Then(
+ IOThread(), __func__,
+ [this](LaunchResults&& aResults) {
+ {
+ {
+ mozilla::AutoWriteLock handleLock(mHandleLock);
+ if (!OpenPrivilegedHandle(base::GetProcId(aResults.mHandle))
+#ifdef XP_WIN
+ // If we failed in opening the process handle, try
+ // harder by duplicating one.
+ && !::DuplicateHandle(
+ ::GetCurrentProcess(), aResults.mHandle,
+ ::GetCurrentProcess(), &mChildProcessHandle,
+ PROCESS_DUP_HANDLE | PROCESS_TERMINATE |
+ PROCESS_QUERY_INFORMATION | PROCESS_VM_READ |
+ SYNCHRONIZE,
+ FALSE, 0)
+#endif // XP_WIN
+ ) {
+ MOZ_CRASH("cannot open handle to child process");
+ }
+ // The original handle is no longer needed; it must
+ // be closed to prevent a resource leak.
+ base::CloseProcessHandle(aResults.mHandle);
+ // FIXME (bug 1720523): define a cross-platform
+ // "safe" invalid value to use in places like this.
+ aResults.mHandle = 0;
+
+#ifdef XP_MACOSX
+ this->mChildTask = aResults.mChildTask;
+ if (mNodeChannel) {
+ mNodeChannel->SetMachTaskPort(this->mChildTask);
+ }
+#endif
+ }
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+ this->mSandboxBroker = std::move(aResults.mSandboxBroker);
+#endif
+
+ MonitorAutoLock lock(mMonitor);
+ // The OnChannel{Connected,Error} may have already advanced
+ // the state.
+ if (mProcessState < PROCESS_CREATED) {
+ mProcessState = PROCESS_CREATED;
+ }
+ lock.Notify();
+ }
+ return ProcessHandlePromise::CreateAndResolve(
+ GetChildProcessHandle(), __func__);
+ },
+ [this](const LaunchError aError) {
+ // WaitUntilConnected might be waiting for us to signal.
+ // If something failed let's set the error state and notify.
+ CHROMIUM_LOG(ERROR)
+ << "Failed to launch "
+ << XRE_GeckoProcessTypeToString(mProcessType)
+ << " subprocess";
+ Telemetry::Accumulate(
+ Telemetry::SUBPROCESS_LAUNCH_FAILURE,
+ nsDependentCString(
+ XRE_GeckoProcessTypeToString(mProcessType)));
+ nsCString telemetryKey = nsPrintfCString(
+#if defined(XP_WIN)
+ "%s,0x%lx,%s",
+#else
+ "%s,%d,%s",
+#endif
+ aError.FunctionName(), aError.ErrorCode(),
+ XRE_GeckoProcessTypeToString(mProcessType));
+ // Max telemetry key is 72 chars
+ // https://searchfox.org/mozilla-central/rev/c244b16815d1fc827d141472b9faac5610f250e7/toolkit/components/telemetry/core/TelemetryScalar.cpp#105
+ if (telemetryKey.Length() > 72) {
+ NS_WARNING(nsPrintfCString("Truncating telemetry key: %s",
+ telemetryKey.get())
+ .get());
+ telemetryKey.Truncate(72);
+ }
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::
+ DOM_PARENTPROCESS_PROCESS_LAUNCH_ERRORS,
+ NS_ConvertUTF8toUTF16(telemetryKey), 1);
+ {
+ MonitorAutoLock lock(mMonitor);
+ mProcessState = PROCESS_ERROR;
+ lock.Notify();
+ }
+ return ProcessHandlePromise::CreateAndReject(aError, __func__);
+ });
+ return true;
+}
+
+bool GeckoChildProcessHost::WaitUntilConnected(int32_t aTimeoutMs) {
+ AUTO_PROFILER_LABEL("GeckoChildProcessHost::WaitUntilConnected", OTHER);
+
+ // NB: this uses a different mechanism than the chromium parent
+ // class.
+ TimeDuration timeout = (aTimeoutMs > 0)
+ ? TimeDuration::FromMilliseconds(aTimeoutMs)
+ : TimeDuration::Forever();
+
+ MonitorAutoLock lock(mMonitor);
+ TimeStamp waitStart = TimeStamp::Now();
+ TimeStamp current;
+
+ // We'll receive several notifications, we need to exit when we
+ // have either successfully launched or have timed out.
+ while (mProcessState != PROCESS_CONNECTED) {
+ // If there was an error then return it, don't wait out the timeout.
+ if (mProcessState == PROCESS_ERROR) {
+ break;
+ }
+
+ CVStatus status = lock.Wait(timeout);
+ if (status == CVStatus::Timeout) {
+ break;
+ }
+
+ if (timeout != TimeDuration::Forever()) {
+ current = TimeStamp::Now();
+ timeout -= current - waitStart;
+ waitStart = current;
+ }
+ }
+
+ return mProcessState == PROCESS_CONNECTED;
+}
+
+bool GeckoChildProcessHost::WaitForProcessHandle() {
+ MonitorAutoLock lock(mMonitor);
+ while (mProcessState < PROCESS_CREATED) {
+ lock.Wait();
+ }
+ MOZ_ASSERT(mProcessState == PROCESS_ERROR || GetChildProcessHandle());
+
+ return mProcessState < PROCESS_ERROR;
+}
+
+bool GeckoChildProcessHost::LaunchAndWaitForProcessHandle(
+ StringVector aExtraOpts) {
+ if (!AsyncLaunch(std::move(aExtraOpts))) {
+ return false;
+ }
+ return WaitForProcessHandle();
+}
+
+void GeckoChildProcessHost::InitializeChannel(
+ const std::function<void(IPC::Channel*)>& aChannelReady) {
+ CreateChannel();
+
+ aChannelReady(GetChannel());
+
+ mNodeController = NodeController::GetSingleton();
+ std::tie(mInitialPort, mNodeChannel) =
+ mNodeController->InviteChildProcess(TakeChannel());
+
+ MonitorAutoLock lock(mMonitor);
+ mProcessState = CHANNEL_INITIALIZED;
+ lock.Notify();
+}
+
+void GeckoChildProcessHost::SetAlreadyDead() {
+ mozilla::AutoWriteLock handleLock(mHandleLock);
+ if (mChildProcessHandle &&
+ mChildProcessHandle != base::kInvalidProcessHandle) {
+ base::CloseProcessHandle(mChildProcessHandle);
+ }
+
+ mChildProcessHandle = 0;
+}
+
+void BaseProcessLauncher::GetChildLogName(const char* origLogName,
+ nsACString& buffer) {
+#ifdef XP_WIN
+ // On Windows we must expand relative paths because sandboxing rules
+ // bound only to full paths. fopen fowards to NtCreateFile which checks
+ // the path against the sanboxing rules as passed to fopen (left relative).
+ char absPath[MAX_PATH + 2];
+ if (_fullpath(absPath, origLogName, sizeof(absPath))) {
+ buffer.Append(absPath);
+ } else
+#endif
+ {
+ buffer.Append(origLogName);
+ }
+
+ // Remove .moz_log extension to avoid its duplication, it will be added
+ // automatically by the logging backend
+ static constexpr auto kMozLogExt = nsLiteralCString{MOZ_LOG_FILE_EXTENSION};
+ if (StringEndsWith(buffer, kMozLogExt)) {
+ buffer.Truncate(buffer.Length() - kMozLogExt.Length());
+ }
+
+ // Append child-specific postfix to name
+ buffer.AppendLiteral(".child-");
+ buffer.AppendInt(mChildId);
+}
+
+// Windows needs a single dedicated thread for process launching,
+// because of thread-safety restrictions/assertions in the sandbox
+// code.
+//
+// Android also needs a single dedicated thread to simplify thread
+// safety in java.
+//
+// Fork server needs a dedicated thread for accessing
+// |ForkServiceChild|.
+#if defined(XP_WIN) || defined(MOZ_WIDGET_ANDROID) || \
+ defined(MOZ_ENABLE_FORKSERVER)
+
+static mozilla::StaticMutex gIPCLaunchThreadMutex;
+static mozilla::StaticRefPtr<nsIThread> gIPCLaunchThread
+ MOZ_GUARDED_BY(gIPCLaunchThreadMutex);
+
+class IPCLaunchThreadObserver final : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ protected:
+ virtual ~IPCLaunchThreadObserver() = default;
+};
+
+NS_IMPL_ISUPPORTS(IPCLaunchThreadObserver, nsIObserver, nsISupports)
+
+NS_IMETHODIMP
+IPCLaunchThreadObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_RELEASE_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0);
+ StaticMutexAutoLock lock(gIPCLaunchThreadMutex);
+
+ nsresult rv = NS_OK;
+ if (gIPCLaunchThread) {
+ rv = gIPCLaunchThread->Shutdown();
+ gIPCLaunchThread = nullptr;
+ }
+ mozilla::Unused << NS_WARN_IF(NS_FAILED(rv));
+ return rv;
+}
+
+nsCOMPtr<nsIEventTarget> GetIPCLauncher() {
+ StaticMutexAutoLock lock(gIPCLaunchThreadMutex);
+ if (!gIPCLaunchThread) {
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = NS_NewNamedThread("IPC Launch"_ns, getter_AddRefs(thread));
+ if (!NS_WARN_IF(NS_FAILED(rv))) {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("GeckoChildProcessHost::GetIPCLauncher", [] {
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ nsCOMPtr<nsIObserver> obs = new IPCLaunchThreadObserver();
+ obsService->AddObserver(obs, "xpcom-shutdown-threads", false);
+ }));
+ gIPCLaunchThread = thread.forget();
+ }
+ }
+
+ nsCOMPtr<nsIEventTarget> thread = gIPCLaunchThread.get();
+ MOZ_DIAGNOSTIC_ASSERT(thread);
+ return thread;
+}
+
+#else // defined(XP_WIN) || defined(MOZ_WIDGET_ANDROID) ||
+ // defined(MOZ_ENABLE_FORKSERVER)
+
+// Other platforms use an on-demand thread pool.
+
+nsCOMPtr<nsIEventTarget> GetIPCLauncher() {
+ nsCOMPtr<nsIEventTarget> pool =
+ mozilla::SharedThreadPool::Get("IPC Launch"_ns);
+ MOZ_DIAGNOSTIC_ASSERT(pool);
+ return pool;
+}
+
+#endif // XP_WIN || MOZ_WIDGET_ANDROID || MOZ_ENABLE_FORKSERVER
+
+void
+#if defined(XP_WIN)
+AddAppDirToCommandLine(CommandLine& aCmdLine, nsIFile* aAppDir)
+#else
+AddAppDirToCommandLine(std::vector<std::string>& aCmdLine, nsIFile* aAppDir,
+ nsIFile* aProfileDir)
+#endif
+{
+ // Content processes need access to application resources, so pass
+ // the full application directory path to the child process.
+ if (aAppDir) {
+#if defined(XP_WIN)
+ nsString path;
+ MOZ_ALWAYS_SUCCEEDS(aAppDir->GetPath(path));
+ aCmdLine.AppendLooseValue(UTF8ToWide(geckoargs::sAppDir.Name()));
+ std::wstring wpath(path.get());
+ aCmdLine.AppendLooseValue(wpath);
+#else
+ nsAutoCString path;
+ MOZ_ALWAYS_SUCCEEDS(aAppDir->GetNativePath(path));
+ geckoargs::sAppDir.Put(path.get(), aCmdLine);
+#endif
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ // Full path to the profile dir
+ if (aProfileDir) {
+ // If the profile doesn't exist, normalization will
+ // fail. But we don't return an error here because some
+ // tests require startup with a missing profile dir.
+ // For users, almost universally, the profile will be in
+ // the home directory and normalization isn't required.
+ mozilla::Unused << aProfileDir->Normalize();
+ nsAutoCString path;
+ MOZ_ALWAYS_SUCCEEDS(aProfileDir->GetNativePath(path));
+ geckoargs::sProfile.Put(path.get(), aCmdLine);
+ }
+#endif
+ }
+}
+
+#if defined(XP_WIN) && (defined(MOZ_SANDBOX) || defined(_ARM64_))
+static bool Contains(const std::vector<std::string>& aExtraOpts,
+ const char* aValue) {
+ return std::any_of(aExtraOpts.begin(), aExtraOpts.end(),
+ [&](const std::string arg) {
+ return arg.find(aValue) != std::string::npos;
+ });
+}
+#endif // defined(XP_WIN) && (defined(MOZ_SANDBOX) || defined(_ARM64_))
+
+RefPtr<ProcessLaunchPromise> BaseProcessLauncher::PerformAsyncLaunch() {
+ Result<Ok, LaunchError> aError = DoSetup();
+ if (aError.isErr()) {
+ return ProcessLaunchPromise::CreateAndReject(aError.unwrapErr(), __func__);
+ }
+ RefPtr<BaseProcessLauncher> self = this;
+ return DoLaunch()->Then(
+ mLaunchThread, __func__,
+ [self](base::ProcessHandle aHandle) {
+ self->mResults.mHandle = aHandle;
+ return self->FinishLaunch();
+ },
+ [](LaunchError aError) {
+ return ProcessLaunchPromise::CreateAndReject(aError, __func__);
+ });
+}
+
+Result<Ok, LaunchError> BaseProcessLauncher::DoSetup() {
+ RefPtr<BaseProcessLauncher> self = this;
+ GetProfilerEnvVarsForChildProcess([self](const char* key, const char* value) {
+ self->mLaunchOptions->env_map[ENVIRONMENT_STRING(key)] =
+ ENVIRONMENT_STRING(value);
+ });
+#ifdef MOZ_MEMORY
+ if (mProcessType == GeckoProcessType_Content) {
+ nsAutoCString mallocOpts(PR_GetEnv("MALLOC_OPTIONS"));
+ // Disable randomization of small arenas in content.
+ mallocOpts.Append("r");
+ self->mLaunchOptions->env_map[ENVIRONMENT_LITERAL("MALLOC_OPTIONS")] =
+ ENVIRONMENT_STRING(mallocOpts.get());
+ }
+#endif
+
+ MapChildLogging();
+
+ PRStatus status = PR_CreatePipe(&mCrashAnnotationReadPipe.rwget(),
+ &mCrashAnnotationWritePipe.rwget());
+ if (status != PR_SUCCESS) {
+ return Err(LaunchError("PR_CreatePipe", PR_GetError()));
+ }
+ return Ok();
+}
+
+void BaseProcessLauncher::MapChildLogging() {
+ const char* origNSPRLogName = PR_GetEnv("NSPR_LOG_FILE");
+ const char* origMozLogName = PR_GetEnv("MOZ_LOG_FILE");
+
+ if (origNSPRLogName) {
+ nsAutoCString nsprLogName;
+ GetChildLogName(origNSPRLogName, nsprLogName);
+ mLaunchOptions->env_map[ENVIRONMENT_LITERAL("NSPR_LOG_FILE")] =
+ ENVIRONMENT_STRING(nsprLogName.get());
+ }
+ if (origMozLogName) {
+ nsAutoCString mozLogName;
+ GetChildLogName(origMozLogName, mozLogName);
+ mLaunchOptions->env_map[ENVIRONMENT_LITERAL("MOZ_LOG_FILE")] =
+ ENVIRONMENT_STRING(mozLogName.get());
+ }
+
+ // `RUST_LOG_CHILD` is meant for logging child processes only.
+ nsAutoCString childRustLog(PR_GetEnv("RUST_LOG_CHILD"));
+ if (!childRustLog.IsEmpty()) {
+ mLaunchOptions->env_map[ENVIRONMENT_LITERAL("RUST_LOG")] =
+ ENVIRONMENT_STRING(childRustLog.get());
+ }
+}
+
+#if defined(MOZ_WIDGET_GTK)
+Result<Ok, LaunchError> LinuxProcessLauncher::DoSetup() {
+ Result<Ok, LaunchError> aError = PosixProcessLauncher::DoSetup();
+ if (aError.isErr()) {
+ return aError;
+ }
+
+ if (mProcessType == GeckoProcessType_Content) {
+ // disable IM module to avoid sandbox violation
+ mLaunchOptions->env_map["GTK_IM_MODULE"] = "gtk-im-context-simple";
+
+ // Disable ATK accessibility code in content processes because it conflicts
+ // with the sandbox, and we proxy that information through the main process
+ // anyway.
+ mLaunchOptions->env_map["NO_AT_BRIDGE"] = "1";
+ }
+
+# ifdef MOZ_SANDBOX
+ if (!mTmpDirName.IsEmpty()) {
+ // Point a bunch of things that might want to write from content to our
+ // shiny new content-process specific tmpdir
+ mLaunchOptions->env_map[ENVIRONMENT_LITERAL("TMPDIR")] =
+ ENVIRONMENT_STRING(mTmpDirName.get());
+ // Partial fix for bug 1380051 (not persistent - should be)
+ mLaunchOptions->env_map[ENVIRONMENT_LITERAL("MESA_GLSL_CACHE_DIR")] =
+ ENVIRONMENT_STRING(mTmpDirName.get());
+ }
+# endif // MOZ_SANDBOX
+
+ return Ok();
+}
+#endif // MOZ_WIDGET_GTK
+
+#ifdef OS_POSIX
+Result<Ok, LaunchError> PosixProcessLauncher::DoSetup() {
+ Result<Ok, LaunchError> aError = BaseProcessLauncher::DoSetup();
+ if (aError.isErr()) {
+ return aError;
+ }
+
+ // XPCOM may not be initialized in some subprocesses. We don't want
+ // to initialize XPCOM just for the directory service, especially
+ // since LD_LIBRARY_PATH is already set correctly in subprocesses
+ // (meaning that we don't need to set that up in the environment).
+ if (ShouldHaveDirectoryService()) {
+ MOZ_ASSERT(gGREBinPath);
+ nsCString path;
+ NS_CopyUnicodeToNative(nsDependentString(gGREBinPath), path);
+# if defined(OS_LINUX) || defined(OS_BSD)
+ const char* ld_library_path = PR_GetEnv("LD_LIBRARY_PATH");
+ nsCString new_ld_lib_path(path.get());
+
+ if (ld_library_path && *ld_library_path) {
+ new_ld_lib_path.Append(':');
+ new_ld_lib_path.Append(ld_library_path);
+ }
+ mLaunchOptions->env_map["LD_LIBRARY_PATH"] = new_ld_lib_path.get();
+
+# elif OS_MACOSX // defined(OS_LINUX) || defined(OS_BSD)
+ // With signed production Mac builds, the dynamic linker (dyld) will
+ // ignore dyld environment variables preventing the use of variables
+ // such as DYLD_LIBRARY_PATH and DYLD_INSERT_LIBRARIES.
+
+ // If we're running with gtests, add the gtest XUL ahead of normal XUL on
+ // the DYLD_LIBRARY_PATH so that plugin-container.app loads it instead.
+ nsCString new_dyld_lib_path(path.get());
+ if (PR_GetEnv("MOZ_RUN_GTEST")) {
+ new_dyld_lib_path = path + "/gtest:"_ns + new_dyld_lib_path;
+ mLaunchOptions->env_map["DYLD_LIBRARY_PATH"] = new_dyld_lib_path.get();
+ }
+
+ // DYLD_INSERT_LIBRARIES is currently unused by default but we allow
+ // it to be set by the external environment.
+ const char* interpose = PR_GetEnv("DYLD_INSERT_LIBRARIES");
+ if (interpose && strlen(interpose) > 0) {
+ mLaunchOptions->env_map["DYLD_INSERT_LIBRARIES"] = interpose;
+ }
+
+ // Prevent connection attempts to diagnosticd(8) to save cycles. Log
+ // messages can trigger these connection attempts, but access to
+ // diagnosticd is blocked in sandboxed child processes.
+# ifdef MOZ_SANDBOX
+ if (mDisableOSActivityMode) {
+ mLaunchOptions->env_map["OS_ACTIVITY_MODE"] = "disable";
+ }
+# endif // defined(MOZ_SANDBOX)
+# endif // defined(OS_LINUX) || defined(OS_BSD)
+ }
+
+ FilePath exePath;
+ BinPathType pathType = GetPathToBinary(exePath, mProcessType);
+
+ // remap the IPC socket fd to a well-known int, as the OS does for
+ // STDOUT_FILENO, for example
+ // The fork server doesn't use IPC::Channel, so can skip this step.
+ if (mProcessType != GeckoProcessType_ForkServer) {
+# ifdef MOZ_WIDGET_ANDROID
+ // On Android mChannelDstFd is uninitialised and the launching code uses
+ // only the first of each pair.
+ mLaunchOptions->fds_to_remap.push_back(
+ std::pair<int, int>(mChannelSrcFd.get(), -1));
+# else
+ MOZ_ASSERT(mChannelDstFd >= 0);
+ mLaunchOptions->fds_to_remap.push_back(
+ std::pair<int, int>(mChannelSrcFd.get(), mChannelDstFd));
+# endif
+ }
+
+ // no need for kProcessChannelID, the child process inherits the
+ // other end of the socketpair() from us
+
+ mChildArgv.push_back(exePath.value());
+
+ if (pathType == BinPathType::Self) {
+ mChildArgv.push_back("-contentproc");
+ }
+
+ mChildArgv.insert(mChildArgv.end(), mExtraOpts.begin(), mExtraOpts.end());
+
+ if (mProcessType != GeckoProcessType_GMPlugin) {
+# if defined(MOZ_WIDGET_ANDROID)
+ if (Omnijar::IsInitialized()) {
+ // Make sure that child processes can find the omnijar
+ // See XRE_InitCommandLine in nsAppRunner.cpp
+ nsAutoCString path;
+ nsCOMPtr<nsIFile> file = Omnijar::GetPath(Omnijar::GRE);
+ if (file && NS_SUCCEEDED(file->GetNativePath(path))) {
+ mChildArgv.push_back("-greomni");
+ mChildArgv.push_back(path.get());
+ }
+ }
+# endif
+ // Add the application directory path (-appdir path)
+# ifdef XP_MACOSX
+ AddAppDirToCommandLine(mChildArgv, mAppDir, mProfileDir);
+# else
+ AddAppDirToCommandLine(mChildArgv, mAppDir, nullptr);
+# endif
+ }
+
+ mChildArgv.push_back(mInitialChannelIdString);
+
+ mChildArgv.push_back(mPidString);
+
+ if (!CrashReporter::IsDummy()) {
+# if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
+ int childCrashFd, childCrashRemapFd;
+ if (NS_WARN_IF(!CrashReporter::CreateNotificationPipeForChild(
+ &childCrashFd, &childCrashRemapFd))) {
+ return Err(LaunchError("CR::CreateNotificationPipeForChild"));
+ }
+
+ if (0 <= childCrashFd) {
+ mLaunchOptions->fds_to_remap.push_back(
+ std::pair<int, int>(childCrashFd, childCrashRemapFd));
+ // "true" == crash reporting enabled
+ mChildArgv.push_back("true");
+ } else {
+ // "false" == crash reporting disabled
+ mChildArgv.push_back("false");
+ }
+# elif defined(MOZ_WIDGET_COCOA) /* defined(OS_LINUX) || defined(OS_BSD) || \
+ defined(OS_SOLARIS) */
+ mChildArgv.push_back(CrashReporter::GetChildNotificationPipe());
+# endif // defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
+ }
+
+ int fd = PR_FileDesc2NativeHandle(mCrashAnnotationWritePipe);
+ mLaunchOptions->fds_to_remap.push_back(
+ std::make_pair(fd, CrashReporter::GetAnnotationTimeCrashFd()));
+
+# ifdef MOZ_WIDGET_COCOA
+ {
+ auto* thisMac = static_cast<MacProcessLauncher*>(this);
+ kern_return_t kr =
+ bootstrap_check_in(bootstrap_port, thisMac->mMachConnectionName.c_str(),
+ getter_Transfers(thisMac->mParentRecvPort));
+ if (kr != KERN_SUCCESS) {
+ CHROMIUM_LOG(ERROR) << "parent bootstrap_check_in failed: "
+ << mach_error_string(kr);
+ return Err(LaunchError("bootstrap_check_in", kr));
+ }
+ mChildArgv.push_back(thisMac->mMachConnectionName.c_str());
+ }
+# endif // MOZ_WIDGET_COCOA
+
+ mChildArgv.push_back(ChildProcessType());
+ return Ok();
+}
+#endif // OS_POSIX
+
+#if defined(MOZ_WIDGET_ANDROID)
+RefPtr<ProcessHandlePromise> AndroidProcessLauncher::DoLaunch() {
+ return LaunchAndroidService(mProcessType, mChildArgv,
+ mLaunchOptions->fds_to_remap);
+}
+#endif // MOZ_WIDGET_ANDROID
+
+#ifdef OS_POSIX
+RefPtr<ProcessHandlePromise> PosixProcessLauncher::DoLaunch() {
+ ProcessHandle handle = 0;
+ Result<Ok, LaunchError> aError =
+ base::LaunchApp(mChildArgv, *mLaunchOptions, &handle);
+ if (aError.isErr()) {
+ return ProcessHandlePromise::CreateAndReject(aError.unwrapErr(), __func__);
+ }
+ return ProcessHandlePromise::CreateAndResolve(handle, __func__);
+}
+
+Result<Ok, LaunchError> PosixProcessLauncher::DoFinishLaunch() {
+ Result<Ok, LaunchError> aError = BaseProcessLauncher::DoFinishLaunch();
+ if (aError.isErr()) {
+ return aError;
+ }
+
+ // We're in the parent and the child was launched. Close the child FD in the
+ // parent as soon as possible, which will allow the parent to detect when the
+ // child closes its FD (either due to normal exit or due to crash).
+ mChannelSrcFd = nullptr;
+
+ return Ok();
+}
+#endif // OS_POSIX
+
+#ifdef XP_MACOSX
+Result<Ok, LaunchError> MacProcessLauncher::DoFinishLaunch() {
+ Result<Ok, LaunchError> aError = PosixProcessLauncher::DoFinishLaunch();
+ if (aError.isErr()) {
+ return aError;
+ }
+
+ MOZ_ASSERT(mParentRecvPort, "should have been configured during DoSetup()");
+
+ // Wait for the child process to send us its 'task_t' data.
+ const int kTimeoutMs = 10000;
+
+ mozilla::UniqueMachSendRight child_task;
+ audit_token_t audit_token{};
+ kern_return_t kr = MachReceivePortSendRight(
+ mParentRecvPort, mozilla::Some(kTimeoutMs), &child_task, &audit_token);
+ if (kr != KERN_SUCCESS) {
+ std::string errString = StringPrintf("0x%x %s", kr, mach_error_string(kr));
+ CHROMIUM_LOG(ERROR) << "parent MachReceivePortSendRight failed: "
+ << errString;
+ return Err(LaunchError("MachReceivePortSendRight", kr));
+ }
+
+ // Ensure the message was sent by the newly spawned child process.
+ if (audit_token_to_pid(audit_token) != base::GetProcId(mResults.mHandle)) {
+ CHROMIUM_LOG(ERROR) << "task_t was not sent by child process";
+ return Err(LaunchError("audit_token_to_pid"));
+ }
+
+ // Ensure the task_t corresponds to the newly spawned child process.
+ pid_t task_pid = -1;
+ kr = pid_for_task(child_task.get(), &task_pid);
+ if (kr != KERN_SUCCESS) {
+ CHROMIUM_LOG(ERROR) << "pid_for_task failed: " << mach_error_string(kr);
+ return Err(LaunchError("pid_for_task", kr));
+ }
+ if (task_pid != base::GetProcId(mResults.mHandle)) {
+ CHROMIUM_LOG(ERROR) << "task_t is not for child process";
+ return Err(LaunchError("task_pid"));
+ }
+
+ mResults.mChildTask = child_task.release();
+
+ return Ok();
+}
+#endif // XP_MACOSX
+
+#ifdef XP_WIN
+Result<Ok, LaunchError> WindowsProcessLauncher::DoSetup() {
+ Result<Ok, LaunchError> aError = BaseProcessLauncher::DoSetup();
+ if (aError.isErr()) {
+ return aError;
+ }
+
+ FilePath exePath;
+ BinPathType pathType = GetPathToBinary(exePath, mProcessType);
+
+# if defined(MOZ_SANDBOX) || defined(_ARM64_)
+ const bool isGMP = mProcessType == GeckoProcessType_GMPlugin;
+ const bool isWidevine = isGMP && Contains(mExtraOpts, "gmp-widevinecdm");
+# if defined(_ARM64_)
+ bool useRemoteSandboxBroker = false;
+ if (mLaunchArch & (base::PROCESS_ARCH_I386 | base::PROCESS_ARCH_X86_64)) {
+ // On Windows on ARM64 for ClearKey and Widevine, and for the sandbox
+ // launcher process, we want to run the x86 plugin-container.exe in
+ // the "i686" subdirectory, instead of the aarch64 plugin-container.exe.
+ // So insert "i686" into the exePath.
+ exePath = exePath.DirName().AppendASCII("i686").Append(exePath.BaseName());
+ useRemoteSandboxBroker =
+ mProcessType != GeckoProcessType_RemoteSandboxBroker;
+ }
+# endif // if defined(_ARM64_)
+# endif // defined(MOZ_SANDBOX) || defined(_ARM64_)
+
+ mCmdLine.emplace(exePath.ToWStringHack());
+
+ if (pathType == BinPathType::Self) {
+ mCmdLine->AppendLooseValue(UTF8ToWide("-contentproc"));
+ }
+
+# ifdef HAS_DLL_BLOCKLIST
+ if (IsDynamicBlocklistDisabled(
+ gSafeMode,
+ CommandLine::ForCurrentProcess()->HasSwitch(UTF8ToWide(
+ mozilla::geckoargs::sDisableDynamicDllBlocklist.sMatch)))) {
+ mCmdLine->AppendLooseValue(
+ UTF8ToWide(mozilla::geckoargs::sDisableDynamicDllBlocklist.sMatch));
+ }
+# endif // HAS_DLL_BLOCKLIST
+
+ mCmdLine->AppendSwitchWithValue(switches::kProcessChannelID, mChannelId);
+
+ for (std::vector<std::string>::iterator it = mExtraOpts.begin();
+ it != mExtraOpts.end(); ++it) {
+ mCmdLine->AppendLooseValue(UTF8ToWide(*it));
+ }
+
+# if defined(MOZ_SANDBOX)
+# if defined(_ARM64_)
+ if (useRemoteSandboxBroker)
+ mResults.mSandboxBroker = new RemoteSandboxBroker(mLaunchArch);
+ else
+# endif // if defined(_ARM64_)
+ mResults.mSandboxBroker = new SandboxBroker();
+
+ // XXX: Bug 1124167: We should get rid of the process specific logic for
+ // sandboxing in this class at some point. Unfortunately it will take a bit
+ // of reorganizing so I don't think this patch is the right time.
+ switch (mProcessType) {
+ case GeckoProcessType_Content:
+ if (mSandboxLevel > 0) {
+ // For now we treat every failure as fatal in
+ // SetSecurityLevelForContentProcess and just crash there right away.
+ // Should this change in the future then we should also handle the error
+ // here.
+ mResults.mSandboxBroker->SetSecurityLevelForContentProcess(
+ mSandboxLevel, mIsFileContent);
+ mUseSandbox = true;
+ }
+ break;
+ case GeckoProcessType_IPDLUnitTest:
+ // XXX: We don't sandbox this process type yet
+ break;
+ case GeckoProcessType_GMPlugin:
+ if (!PR_GetEnv("MOZ_DISABLE_GMP_SANDBOX")) {
+ // The Widevine CDM on Windows can only load at USER_RESTRICTED,
+ // not at USER_LOCKDOWN. So look in the command line arguments
+ // to see if we're loading the path to the Widevine CDM, and if
+ // so use sandbox level USER_RESTRICTED instead of USER_LOCKDOWN.
+ auto level =
+ isWidevine ? SandboxBroker::Restricted : SandboxBroker::LockDown;
+ if (NS_WARN_IF(
+ !mResults.mSandboxBroker->SetSecurityLevelForGMPlugin(level))) {
+ return Err(LaunchError("SetSecurityLevelForGMPlugin"));
+ }
+ mUseSandbox = true;
+ }
+ break;
+ case GeckoProcessType_GPU:
+ if (mSandboxLevel > 0 && !PR_GetEnv("MOZ_DISABLE_GPU_SANDBOX")) {
+ // For now we treat every failure as fatal in
+ // SetSecurityLevelForGPUProcess and just crash there right away. Should
+ // this change in the future then we should also handle the error here.
+ mResults.mSandboxBroker->SetSecurityLevelForGPUProcess(mSandboxLevel);
+ mUseSandbox = true;
+ }
+ break;
+ case GeckoProcessType_VR:
+ if (mSandboxLevel > 0 && !PR_GetEnv("MOZ_DISABLE_VR_SANDBOX")) {
+ // TODO: Implement sandbox for VR process, Bug 1430043.
+ }
+ break;
+ case GeckoProcessType_RDD:
+ if (!PR_GetEnv("MOZ_DISABLE_RDD_SANDBOX")) {
+ if (NS_WARN_IF(
+ !mResults.mSandboxBroker->SetSecurityLevelForRDDProcess())) {
+ return Err(LaunchError("SetSecurityLevelForRDDProcess"));
+ }
+ mUseSandbox = true;
+ }
+ break;
+ case GeckoProcessType_Socket:
+ if (!PR_GetEnv("MOZ_DISABLE_SOCKET_PROCESS_SANDBOX")) {
+ if (NS_WARN_IF(
+ !mResults.mSandboxBroker->SetSecurityLevelForSocketProcess())) {
+ return Err(LaunchError("SetSecurityLevelForSocketProcess"));
+ }
+ mUseSandbox = true;
+ }
+ break;
+ case GeckoProcessType_Utility:
+ if (!PR_GetEnv("MOZ_DISABLE_UTILITY_SANDBOX")) {
+ if (!mResults.mSandboxBroker->SetSecurityLevelForUtilityProcess(
+ mSandbox)) {
+ return Err(LaunchError("SetSecurityLevelForUtilityProcess"));
+ }
+ mUseSandbox = true;
+ }
+ break;
+ case GeckoProcessType_RemoteSandboxBroker:
+ // We don't sandbox the sandbox launcher...
+ break;
+ case GeckoProcessType_Default:
+ default:
+ MOZ_CRASH("Bad process type in GeckoChildProcessHost");
+ break;
+ };
+
+ if (mUseSandbox) {
+ for (auto it = mAllowedFilesRead.begin(); it != mAllowedFilesRead.end();
+ ++it) {
+ mResults.mSandboxBroker->AllowReadFile(it->c_str());
+ }
+
+ if (mResults.mSandboxBroker->IsWin32kLockedDown()) {
+ mCmdLine->AppendLooseValue(
+ UTF8ToWide(geckoargs::sWin32kLockedDown.Name()));
+ }
+ }
+# endif // defined(MOZ_SANDBOX)
+
+ // Add the application directory path (-appdir path)
+ AddAppDirToCommandLine(mCmdLine.ref(), mAppDir);
+
+ // XXX Command line params past this point are expected to be at
+ // the end of the command line string, and in a specific order.
+ // See XRE_InitChildProcess in nsEmbedFunction.
+
+ // Win app model id
+ mCmdLine->AppendLooseValue(mGroupId.get());
+
+ // Initial MessageChannel id
+ mCmdLine->AppendLooseValue(UTF8ToWide(mInitialChannelIdString));
+
+ // Process id
+ mCmdLine->AppendLooseValue(UTF8ToWide(mPidString));
+
+ mCmdLine->AppendLooseValue(
+ UTF8ToWide(CrashReporter::GetChildNotificationPipe()));
+
+ if (!CrashReporter::IsDummy()) {
+ PROsfd h = PR_FileDesc2NativeHandle(mCrashAnnotationWritePipe);
+ mLaunchOptions->handles_to_inherit.push_back(reinterpret_cast<HANDLE>(h));
+ std::string hStr = std::to_string(h);
+ mCmdLine->AppendLooseValue(UTF8ToWide(hStr));
+
+ char werDataAddress[17] = {};
+ SprintfLiteral(werDataAddress, "%p", mWerDataPointer);
+ mCmdLine->AppendLooseValue(UTF8ToWide(werDataAddress));
+ }
+
+ // Process type
+ mCmdLine->AppendLooseValue(UTF8ToWide(ChildProcessType()));
+
+# ifdef MOZ_SANDBOX
+ if (mUseSandbox) {
+ // Mark the handles to inherit as inheritable.
+ for (HANDLE h : mLaunchOptions->handles_to_inherit) {
+ mResults.mSandboxBroker->AddHandleToShare(h);
+ }
+ }
+# endif // MOZ_SANDBOX
+
+ return Ok();
+}
+
+RefPtr<ProcessHandlePromise> WindowsProcessLauncher::DoLaunch() {
+ ProcessHandle handle = 0;
+# ifdef MOZ_SANDBOX
+ if (mUseSandbox) {
+ const IMAGE_THUNK_DATA* cachedNtdllThunk =
+ mCachedNtdllThunk ? mCachedNtdllThunk->begin() : nullptr;
+ Result<Ok, LaunchError> err = mResults.mSandboxBroker->LaunchApp(
+ mCmdLine->program().c_str(), mCmdLine->command_line_string().c_str(),
+ mLaunchOptions->env_map, mProcessType, mEnableSandboxLogging,
+ cachedNtdllThunk, &handle);
+ if (err.isOk()) {
+ EnvironmentLog("MOZ_PROCESS_LOG")
+ .print("==> process %d launched child process %d (%S)\n",
+ base::GetCurrentProcId(), base::GetProcId(handle),
+ mCmdLine->command_line_string().c_str());
+ return ProcessHandlePromise::CreateAndResolve(handle, __func__);
+ }
+ return ProcessHandlePromise::CreateAndReject(err.unwrapErr(), __func__);
+ }
+# endif // defined(MOZ_SANDBOX)
+
+ Result<Ok, LaunchError> launchErr =
+ base::LaunchApp(mCmdLine.ref(), *mLaunchOptions, &handle);
+ if (launchErr.isErr()) {
+ return ProcessHandlePromise::CreateAndReject(launchErr.unwrapErr(),
+ __func__);
+ }
+ return ProcessHandlePromise::CreateAndResolve(handle, __func__);
+}
+
+Result<Ok, LaunchError> WindowsProcessLauncher::DoFinishLaunch() {
+ Result<Ok, LaunchError> err = BaseProcessLauncher::DoFinishLaunch();
+ if (err.isErr()) {
+ return err;
+ }
+
+# ifdef MOZ_SANDBOX
+ if (!mUseSandbox) {
+ // We need to be able to duplicate handles to some types of non-sandboxed
+ // child processes.
+ switch (mProcessType) {
+ case GeckoProcessType_Default:
+ MOZ_CRASH("shouldn't be launching a parent process");
+ case GeckoProcessType_IPDLUnitTest:
+ // No handle duplication necessary.
+ break;
+ default:
+ if (!SandboxBroker::AddTargetPeer(mResults.mHandle)) {
+ NS_WARNING("Failed to add child process as target peer.");
+ }
+ break;
+ }
+ }
+# endif // MOZ_SANDBOX
+
+ return Ok();
+}
+#endif // XP_WIN
+
+RefPtr<ProcessLaunchPromise> BaseProcessLauncher::FinishLaunch() {
+ Result<Ok, LaunchError> aError = DoFinishLaunch();
+ if (aError.isErr()) {
+ return ProcessLaunchPromise::CreateAndReject(aError.unwrapErr(), __func__);
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mResults.mHandle);
+
+ CrashReporter::RegisterChildCrashAnnotationFileDescriptor(
+ base::GetProcId(mResults.mHandle), mCrashAnnotationReadPipe.forget());
+
+ Telemetry::AccumulateTimeDelta(Telemetry::CHILD_PROCESS_LAUNCH_MS,
+ mStartTimeStamp);
+
+ return ProcessLaunchPromise::CreateAndResolve(mResults, __func__);
+}
+
+bool GeckoChildProcessHost::OpenPrivilegedHandle(base::ProcessId aPid) {
+ if (mChildProcessHandle) {
+ MOZ_ASSERT(aPid == base::GetProcId(mChildProcessHandle));
+ return true;
+ }
+
+ return base::OpenPrivilegedProcessHandle(aPid, &mChildProcessHandle);
+}
+
+void GeckoChildProcessHost::OnChannelConnected(base::ProcessId peer_pid) {
+ {
+ mozilla::AutoWriteLock hLock(mHandleLock);
+ if (!OpenPrivilegedHandle(peer_pid)) {
+ MOZ_CRASH("can't open handle to child process");
+ }
+ }
+ MonitorAutoLock lock(mMonitor);
+ mProcessState = PROCESS_CONNECTED;
+ lock.Notify();
+}
+
+void GeckoChildProcessHost::OnMessageReceived(UniquePtr<IPC::Message> aMsg) {
+ // We never process messages ourself, just save them up for the next
+ // listener.
+ mQueue.push(std::move(aMsg));
+}
+
+void GeckoChildProcessHost::OnChannelError() {
+ // Update the process state to an error state if we have a channel
+ // error before we're connected. This fixes certain failures,
+ // but does not address the full range of possible issues described
+ // in the FIXME comment below.
+ MonitorAutoLock lock(mMonitor);
+ if (mProcessState < PROCESS_CONNECTED) {
+ mProcessState = PROCESS_ERROR;
+ lock.Notify();
+ }
+ // FIXME/bug 773925: save up this error for the next listener.
+}
+
+RefPtr<ProcessHandlePromise> GeckoChildProcessHost::WhenProcessHandleReady() {
+ MOZ_ASSERT(mHandlePromise != nullptr);
+ return mHandlePromise;
+}
+
+void GeckoChildProcessHost::GetQueuedMessages(
+ std::queue<UniquePtr<IPC::Message>>& queue) {
+ // If this is called off the IO thread, bad things will happen.
+ DCHECK(MessageLoopForIO::current());
+ swap(queue, mQueue);
+ // We expect the next listener to take over processing of our queue.
+}
+
+#ifdef MOZ_WIDGET_ANDROID
+RefPtr<ProcessHandlePromise> AndroidProcessLauncher::LaunchAndroidService(
+ const GeckoProcessType aType, const std::vector<std::string>& argv,
+ const base::file_handle_mapping_vector& fds_to_remap) {
+ MOZ_RELEASE_ASSERT((2 <= fds_to_remap.size()) && (fds_to_remap.size() <= 5));
+ JNIEnv* const env = mozilla::jni::GetEnvForThread();
+ MOZ_ASSERT(env);
+
+ const int argvSize = argv.size();
+ jni::ObjectArray::LocalRef jargs =
+ jni::ObjectArray::New<jni::String>(argvSize);
+ for (int ix = 0; ix < argvSize; ix++) {
+ jargs->SetElement(ix, jni::StringParam(argv[ix].c_str(), env));
+ }
+
+ // XXX: this processing depends entirely on the internals of
+ // ContentParent::LaunchSubprocess()
+ // GeckoChildProcessHost::PerformAsyncLaunch(), and the order in
+ // which they append to fds_to_remap. There must be a better way to do it.
+ // See bug 1440207.
+ int32_t prefsFd = fds_to_remap[0].first;
+ int32_t prefMapFd = fds_to_remap[1].first;
+ int32_t ipcFd = fds_to_remap[2].first;
+ int32_t crashFd = -1;
+ int32_t crashAnnotationFd = -1;
+ if (fds_to_remap.size() == 4) {
+ crashAnnotationFd = fds_to_remap[3].first;
+ }
+ if (fds_to_remap.size() == 5) {
+ crashFd = fds_to_remap[3].first;
+ crashAnnotationFd = fds_to_remap[4].first;
+ }
+
+ auto type = java::GeckoProcessType::FromInt(aType);
+ auto genericResult = java::GeckoProcessManager::Start(
+ type, jargs, prefsFd, prefMapFd, ipcFd, crashFd, crashAnnotationFd);
+ auto typedResult = java::GeckoResult::LocalRef(std::move(genericResult));
+ return ProcessHandlePromise::FromGeckoResult(typedResult);
+}
+#endif
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+bool GeckoChildProcessHost::AppendMacSandboxParams(StringVector& aArgs) {
+ MacSandboxInfo info;
+ if (NS_WARN_IF(!FillMacSandboxInfo(info))) {
+ return false;
+ }
+ info.AppendAsParams(aArgs);
+ return true;
+}
+
+// Fill |aInfo| with the flags needed to launch the utility sandbox
+bool GeckoChildProcessHost::FillMacSandboxInfo(MacSandboxInfo& aInfo) {
+ aInfo.type = GetDefaultMacSandboxType();
+ aInfo.shouldLog = Preferences::GetBool("security.sandbox.logging.enabled") ||
+ PR_GetEnv("MOZ_SANDBOX_LOGGING");
+
+ nsAutoCString appPath;
+ if (!nsMacUtilsImpl::GetAppPath(appPath)) {
+ MOZ_CRASH("Failed to get app path");
+ }
+ aInfo.appPath.assign(appPath.get());
+ return true;
+}
+
+void GeckoChildProcessHost::DisableOSActivityMode() {
+ mDisableOSActivityMode = true;
+}
+
+//
+// If early sandbox startup is enabled for this process type, map the
+// process type to the sandbox type and enable the sandbox. Returns true
+// if no errors were encountered or if early sandbox startup is not
+// enabled for this process. Returns false if an error was encountered.
+//
+/* static */
+bool GeckoChildProcessHost::StartMacSandbox(int aArgc, char** aArgv,
+ std::string& aErrorMessage) {
+ MacSandboxType sandboxType = MacSandboxType_Invalid;
+ switch (XRE_GetProcessType()) {
+ // For now, only support early sandbox startup for content,
+ // RDD, and GMP processes. Add case statements for the additional
+ // process types once early sandbox startup is implemented for them.
+ case GeckoProcessType_Content:
+ // Content processes don't use GeckoChildProcessHost
+ // to configure sandboxing so hard code the sandbox type.
+ sandboxType = MacSandboxType_Content;
+ break;
+ case GeckoProcessType_RDD:
+ sandboxType = RDDProcessHost::GetMacSandboxType();
+ break;
+ case GeckoProcessType_Socket:
+ sandboxType = net::SocketProcessHost::GetMacSandboxType();
+ break;
+ case GeckoProcessType_GMPlugin:
+ sandboxType = gmp::GMPProcessParent::GetMacSandboxType();
+ break;
+ case GeckoProcessType_Utility:
+ sandboxType = ipc::UtilityProcessHost::GetMacSandboxType();
+ break;
+ default:
+ return true;
+ }
+
+ return mozilla::StartMacSandboxIfEnabled(sandboxType, aArgc, aArgv,
+ aErrorMessage);
+}
+
+#endif /* XP_MACOSX && MOZ_SANDBOX */
+
+/* static */
+void GeckoChildProcessHost::GetAll(const GeckoProcessCallback& aCallback) {
+ StaticMutexAutoLock lock(sMutex);
+ if (!sGeckoChildProcessHosts) {
+ return;
+ }
+ for (GeckoChildProcessHost* gp = sGeckoChildProcessHosts->getFirst(); gp;
+ gp = static_cast<mozilla::LinkedListElement<GeckoChildProcessHost>*>(gp)
+ ->getNext()) {
+ aCallback(gp);
+ }
+}
+
+RefPtr<ProcessLaunchPromise> BaseProcessLauncher::Launch(
+ GeckoChildProcessHost* aHost) {
+ AssertIOThread();
+
+ // The ForkServer doesn't use IPC::Channel for communication, so we can skip
+ // initializing it.
+ if (mProcessType != GeckoProcessType_ForkServer) {
+ // Initializing the channel needs to happen on the I/O thread, but
+ // everything else can run on the launcher thread (or pool), to avoid
+ // blocking IPC messages.
+ //
+ // We avoid passing the host to the launcher thread to reduce the chances of
+ // data races with the IO thread (where e.g. OnChannelConnected may run
+ // concurrently). The pool currently needs access to the channel, which is
+ // not great.
+ bool failed = false;
+ aHost->InitializeChannel([&](IPC::Channel* channel) {
+ if (NS_WARN_IF(!channel || !SetChannel(channel))) {
+ failed = true;
+ }
+ });
+ if (failed) {
+ return ProcessLaunchPromise::CreateAndReject(LaunchError("SetChannel"),
+ __func__);
+ }
+ mChannelId = aHost->GetChannelId();
+ }
+
+ return InvokeAsync(mLaunchThread, this, __func__,
+ &BaseProcessLauncher::PerformAsyncLaunch);
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/GeckoChildProcessHost.h b/ipc/glue/GeckoChildProcessHost.h
new file mode 100644
index 0000000000..ccf8a4aca4
--- /dev/null
+++ b/ipc/glue/GeckoChildProcessHost.h
@@ -0,0 +1,335 @@
+/* -*- 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/. */
+
+#ifndef __IPC_GLUE_GECKOCHILDPROCESSHOST_H__
+#define __IPC_GLUE_GECKOCHILDPROCESSHOST_H__
+
+#include "base/file_path.h"
+#include "base/process_util.h"
+#include "base/waitable_event.h"
+#include "chrome/common/child_process_host.h"
+#include "chrome/common/ipc_message.h"
+#include "mojo/core/ports/port_ref.h"
+
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "mozilla/ipc/NodeChannel.h"
+#include "mozilla/ipc/LaunchError.h"
+#include "mozilla/ipc/ScopedPort.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Buffer.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RWLock.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsCOMPtr.h"
+#include "nsExceptionHandler.h"
+#include "nsXULAppAPI.h" // for GeckoProcessType
+#include "nsString.h"
+
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+# include "sandboxBroker.h"
+#endif
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+# include "mozilla/Sandbox.h"
+#endif
+
+#if defined(MOZ_SANDBOX)
+# include "mozilla/ipc/UtilityProcessSandboxing.h"
+#endif
+
+#if (defined(XP_WIN) && defined(_ARM64_)) || \
+ (defined(XP_MACOSX) && defined(__aarch64__))
+# define ALLOW_GECKO_CHILD_PROCESS_ARCH
+#endif
+
+struct _MacSandboxInfo;
+typedef _MacSandboxInfo MacSandboxInfo;
+
+namespace mozilla {
+namespace ipc {
+
+typedef mozilla::MozPromise<base::ProcessHandle, LaunchError, false>
+ ProcessHandlePromise;
+
+class GeckoChildProcessHost : public ChildProcessHost,
+ public LinkedListElement<GeckoChildProcessHost> {
+ protected:
+ typedef mozilla::Monitor Monitor;
+ typedef std::vector<std::string> StringVector;
+
+ public:
+ using ProcessId = base::ProcessId;
+ using ProcessHandle = base::ProcessHandle;
+
+ explicit GeckoChildProcessHost(GeckoProcessType aProcessType,
+ bool aIsFileContent = false);
+
+ // Causes the object to be deleted, on the I/O thread, after any
+ // pending asynchronous work (like launching) is complete. This
+ // method can be called from any thread. If called from the I/O
+ // thread itself, deletion won't happen until the event loop spins;
+ // otherwise, it could happen immediately.
+ //
+ // GeckoChildProcessHost instances must not be deleted except
+ // through this method.
+ void Destroy();
+
+ static uint32_t GetUniqueID();
+
+ // Call this before launching to set an environment variable for the
+ // child process. The arguments must be UTF-8.
+ void SetEnv(const char* aKey, const char* aValue);
+
+ // Does not block. The IPC channel may not be initialized yet, and
+ // the child process may or may not have been created when this
+ // method returns.
+ bool AsyncLaunch(StringVector aExtraOpts = StringVector());
+
+ virtual bool WaitUntilConnected(int32_t aTimeoutMs = 0);
+
+ // Block until the IPC channel for our subprocess is initialized and
+ // the OS process is created. The subprocess may or may not have
+ // connected back to us when this method returns.
+ //
+ // NB: on POSIX, this method is relatively cheap, and doesn't
+ // require disk IO. On win32 however, it requires at least the
+ // analogue of stat(). This difference induces a semantic
+ // difference in this method: on POSIX, when we return, we know the
+ // subprocess has been created, but we don't know whether its
+ // executable image can be loaded. On win32, we do know that when
+ // we return. But we don't know if dynamic linking succeeded on
+ // either platform.
+ bool LaunchAndWaitForProcessHandle(StringVector aExtraOpts = StringVector());
+ bool WaitForProcessHandle();
+
+ // Block until the child process has been created and it connects to
+ // the IPC channel, meaning it's fully initialized. (Or until an
+ // error occurs.)
+ bool SyncLaunch(StringVector aExtraOpts = StringVector(),
+ int32_t timeoutMs = 0);
+
+ virtual void OnChannelConnected(base::ProcessId peer_pid) override;
+ virtual void OnMessageReceived(UniquePtr<IPC::Message> aMsg) override;
+ virtual void OnChannelError() override;
+ virtual void GetQueuedMessages(
+ std::queue<UniquePtr<IPC::Message>>& queue) override;
+
+ // Resolves to the process handle when it's available (see
+ // LaunchAndWaitForProcessHandle); use with AsyncLaunch.
+ RefPtr<ProcessHandlePromise> WhenProcessHandleReady();
+
+ void InitializeChannel(
+ const std::function<void(IPC::Channel*)>& aChannelReady);
+
+ virtual bool CanShutdown() override { return true; }
+
+ IPC::Channel* GetChannel() { return channelp(); }
+ ChannelId GetChannelId() { return channel_id(); }
+
+ UntypedEndpoint TakeInitialEndpoint() {
+ return UntypedEndpoint{PrivateIPDLInterface{}, std::move(mInitialPort),
+ mInitialChannelId, base::GetCurrentProcId(),
+ GetChildProcessId()};
+ }
+
+ // Returns a "borrowed" handle to the child process - the handle returned
+ // by this function must not be closed by the caller. The handle is also
+ // not guaranteed to remain valid; if the caller is using it for anything
+ // more than logging or asserting non-null, it will need to deal with
+ // synchronization.
+ //
+ // Warning: the null value here is 0, not kInvalidProcessHandle.
+ ProcessHandle GetChildProcessHandle();
+
+ // Returns the child's process ID; as for GetChildProcessHandle, there is
+ // no inherent guarantee that it will remain valid or continue to
+ // reference the same process.
+ //
+ // The null value here is also 0; this matches the result of
+ // GetProcId on a zero or (on Windows) invalid handle.
+ ProcessId GetChildProcessId();
+
+ GeckoProcessType GetProcessType() { return mProcessType; }
+
+#ifdef XP_MACOSX
+ task_t GetChildTask();
+#endif
+
+#ifdef XP_WIN
+
+ void AddHandleToShare(HANDLE aHandle) {
+ mLaunchOptions->handles_to_inherit.push_back(aHandle);
+ }
+#else
+ void AddFdToRemap(int aSrcFd, int aDstFd) {
+ mLaunchOptions->fds_to_remap.push_back(std::make_pair(aSrcFd, aDstFd));
+ }
+#endif
+
+#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH
+ void SetLaunchArchitecture(uint32_t aArch) { mLaunchArch = aArch; }
+#endif
+
+ // For bug 943174: Skip the EnsureProcessTerminated call in the destructor.
+ void SetAlreadyDead();
+
+#if defined(MOZ_SANDBOX) && defined(XP_MACOSX)
+ // Start the sandbox from the child process.
+ static bool StartMacSandbox(int aArgc, char** aArgv,
+ std::string& aErrorMessage);
+
+ // The sandbox type that will be use when sandboxing is
+ // enabled in the derived class and FillMacSandboxInfo
+ // has not been overridden.
+ static MacSandboxType GetDefaultMacSandboxType() {
+ return MacSandboxType_Utility;
+ };
+
+ // Must be called before the process is launched. Determines if
+ // child processes will be launched with OS_ACTIVITY_MODE set to
+ // "disabled" or not. When |mDisableOSActivityMode| is set to true,
+ // child processes will be launched with OS_ACTIVITY_MODE
+ // disabled to avoid connection attempts to diagnosticd(8) which are
+ // blocked in child processes due to sandboxing.
+ void DisableOSActivityMode();
+#endif // defined(MOZ_SANDBOX) && defined(XP_MACOSX)
+ typedef std::function<void(GeckoChildProcessHost*)> GeckoProcessCallback;
+
+ // Iterates over all instances and calls aCallback with each one of them.
+ // This method will lock any addition/removal of new processes
+ // so you need to make sure the callback is as fast as possible.
+ //
+ // To reiterate: the callbacks are executed synchronously.
+ static void GetAll(const GeckoProcessCallback& aCallback);
+
+ friend class BaseProcessLauncher;
+ friend class PosixProcessLauncher;
+ friend class WindowsProcessLauncher;
+
+ protected:
+ ~GeckoChildProcessHost();
+ GeckoProcessType mProcessType;
+ bool mIsFileContent;
+ Monitor mMonitor;
+ FilePath mProcessPath;
+#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH
+ // Used on platforms where we may launch a child process with a different
+ // architecture than the parent process.
+ uint32_t mLaunchArch = base::PROCESS_ARCH_INVALID;
+#endif
+ // GeckoChildProcessHost holds the launch options so they can be set
+ // up on the main thread using main-thread-only APIs like prefs, and
+ // then used for the actual launch on another thread. This pointer
+ // is set to null to free the options after the child is launched.
+ UniquePtr<base::LaunchOptions> mLaunchOptions;
+ ScopedPort mInitialPort;
+ nsID mInitialChannelId;
+ RefPtr<NodeController> mNodeController;
+ RefPtr<NodeChannel> mNodeChannel;
+
+ // This value must be accessed while holding mMonitor.
+ enum {
+ // This object has been constructed, but the OS process has not
+ // yet.
+ CREATING_CHANNEL = 0,
+ // The IPC channel for our subprocess has been created, but the OS
+ // process has still not been created.
+ CHANNEL_INITIALIZED,
+ // The OS process has been created, but it hasn't yet connected to
+ // our IPC channel.
+ PROCESS_CREATED,
+ // The process is launched and connected to our IPC channel. All
+ // is well.
+ PROCESS_CONNECTED,
+ PROCESS_ERROR
+ } mProcessState MOZ_GUARDED_BY(mMonitor);
+
+ void PrepareLaunch();
+
+#ifdef XP_WIN
+ void InitWindowsGroupID();
+ nsString mGroupId;
+ CrashReporter::WindowsErrorReportingData mWerData;
+# ifdef MOZ_SANDBOX
+ RefPtr<AbstractSandboxBroker> mSandboxBroker;
+ std::vector<std::wstring> mAllowedFilesRead;
+ bool mEnableSandboxLogging;
+ int32_t mSandboxLevel;
+# endif
+#endif // XP_WIN
+
+#if defined(MOZ_SANDBOX)
+ SandboxingKind mSandbox;
+#endif
+
+ mozilla::RWLock mHandleLock;
+ ProcessHandle mChildProcessHandle MOZ_GUARDED_BY(mHandleLock);
+#if defined(OS_MACOSX)
+ task_t mChildTask MOZ_GUARDED_BY(mHandleLock);
+#endif
+ RefPtr<ProcessHandlePromise> mHandlePromise;
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ bool mDisableOSActivityMode;
+#endif
+
+ bool OpenPrivilegedHandle(base::ProcessId aPid) MOZ_REQUIRES(mHandleLock);
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ // Override this method to return true to launch the child process
+ // using the Mac utility (by default) sandbox. Override
+ // FillMacSandboxInfo() to change the sandbox type and settings.
+ virtual bool IsMacSandboxLaunchEnabled() { return false; }
+
+ // Fill a MacSandboxInfo to configure the sandbox
+ virtual bool FillMacSandboxInfo(MacSandboxInfo& aInfo);
+
+ // Adds the command line arguments needed to enable
+ // sandboxing of the child process at startup before
+ // the child event loop is up.
+ virtual bool AppendMacSandboxParams(StringVector& aArgs);
+#endif
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(GeckoChildProcessHost);
+
+ // Removes the instance from sGeckoChildProcessHosts
+ void RemoveFromProcessList();
+
+ // In between launching the subprocess and handing off its IPC
+ // channel, there's a small window of time in which *we* might still
+ // be the channel listener, and receive messages. That's bad
+ // because we have no idea what to do with those messages. So queue
+ // them here until we hand off the eventual listener.
+ //
+ // FIXME/cjones: this strongly indicates bad design. Shame on us.
+ std::queue<UniquePtr<IPC::Message>> mQueue;
+
+ // Linux-Only. Set this up before we're called from a different thread.
+ nsCString mTmpDirName;
+ // Mac and Windows. Set this up before we're called from a different thread.
+ nsCOMPtr<nsIFile> mProfileDir;
+
+ mozilla::Atomic<bool> mDestroying;
+
+ static uint32_t sNextUniqueID;
+ static StaticAutoPtr<LinkedList<GeckoChildProcessHost>>
+ sGeckoChildProcessHosts MOZ_GUARDED_BY(sMutex);
+ static StaticMutex sMutex;
+};
+
+nsCOMPtr<nsIEventTarget> GetIPCLauncher();
+
+} /* namespace ipc */
+} /* namespace mozilla */
+
+#endif /* __IPC_GLUE_GECKOCHILDPROCESSHOST_H__ */
diff --git a/ipc/glue/IOThreadChild.h b/ipc/glue/IOThreadChild.h
new file mode 100644
index 0000000000..632389bf08
--- /dev/null
+++ b/ipc/glue/IOThreadChild.h
@@ -0,0 +1,46 @@
+/* -*- 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/. */
+
+#ifndef dom_plugins_IOThreadChild_h
+#define dom_plugins_IOThreadChild_h
+
+#include "chrome/common/child_thread.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/NodeController.h"
+#include "mozilla/ipc/ProcessChild.h"
+
+namespace mozilla {
+namespace ipc {
+//-----------------------------------------------------------------------------
+
+// The IOThreadChild class represents a background thread where the
+// IPC IO MessageLoop lives.
+class IOThreadChild : public ChildThread {
+ public:
+ IOThreadChild()
+ : ChildThread(base::Thread::Options(MessageLoop::TYPE_IO,
+ 0)) // stack size
+ {}
+
+ ~IOThreadChild() = default;
+
+ static MessageLoop* message_loop() {
+ return IOThreadChild::current()->Thread::message_loop();
+ }
+
+ protected:
+ static IOThreadChild* current() {
+ return static_cast<IOThreadChild*>(ChildThread::current());
+ }
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(IOThreadChild);
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // ifndef dom_plugins_IOThreadChild_h
diff --git a/ipc/glue/IPCCore.h b/ipc/glue/IPCCore.h
new file mode 100644
index 0000000000..bb6b725c35
--- /dev/null
+++ b/ipc/glue/IPCCore.h
@@ -0,0 +1,22 @@
+/* -*- 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/. */
+
+#ifndef IPC_GLUE_IPCCORE_H_
+#define IPC_GLUE_IPCCORE_H_
+
+namespace mozilla {
+
+struct void_t {
+ constexpr bool operator==(const void_t&) const { return true; }
+};
+
+struct null_t {
+ constexpr bool operator==(const null_t&) const { return true; }
+};
+
+} // namespace mozilla
+
+#endif // IPC_GLUE_IPCCORE_H_
diff --git a/ipc/glue/IPCForwards.h b/ipc/glue/IPCForwards.h
new file mode 100644
index 0000000000..8aef885257
--- /dev/null
+++ b/ipc/glue/IPCForwards.h
@@ -0,0 +1,38 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_IPCForwards_h
+#define mozilla_ipc_IPCForwards_h
+
+// A few helpers to avoid having to include lots of stuff in headers.
+
+namespace mozilla {
+template <typename T>
+class Maybe;
+
+namespace ipc {
+class IProtocol;
+}
+} // namespace mozilla
+
+namespace IPC {
+class Message;
+class MessageReader;
+class MessageWriter;
+template <typename T, bool>
+class ReadResult;
+} // namespace IPC
+
+// TODO(bug 1812271): Remove users of this macro.
+#define ALLOW_DEPRECATED_READPARAM \
+ public: \
+ enum { kHasDeprecatedReadParamPrivateConstructor = true }; \
+ template <typename, bool> \
+ friend class IPC::ReadResult; \
+ \
+ private:
+
+#endif
diff --git a/ipc/glue/IPCMessageUtils.h b/ipc/glue/IPCMessageUtils.h
new file mode 100644
index 0000000000..ab9b84e3ea
--- /dev/null
+++ b/ipc/glue/IPCMessageUtils.h
@@ -0,0 +1,238 @@
+/* -*- 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/. */
+
+#ifndef __IPC_GLUE_IPCMESSAGEUTILS_H__
+#define __IPC_GLUE_IPCMESSAGEUTILS_H__
+
+#include <cstdint>
+#include <string>
+#include <type_traits>
+#include "build/build_config.h"
+#include "chrome/common/ipc_message.h"
+#include "chrome/common/ipc_message_utils.h"
+#include "mozilla/ipc/IPCCore.h"
+#include "mozilla/MacroForEach.h"
+
+class PickleIterator;
+
+// XXX Things that are not necessary if moving implementations to the cpp file
+#include "base/string_util.h"
+
+#ifdef _MSC_VER
+# pragma warning(disable : 4800)
+#endif
+
+#if !defined(OS_POSIX)
+// This condition must be kept in sync with the one in
+// ipc_message_utils.h, but this dummy definition of
+// base::FileDescriptor acts as a static assert that we only get one
+// def or the other (or neither, in which case code using
+// FileDescriptor fails to build)
+namespace base {
+struct FileDescriptor {};
+} // namespace base
+#endif
+
+namespace mozilla {
+template <typename...>
+class Variant;
+
+namespace detail {
+template <typename...>
+struct VariantTag;
+}
+} // namespace mozilla
+
+namespace IPC {
+
+/**
+ * A helper class for serializing plain-old data (POD) structures.
+ * The memory representation of the structure is written to and read from
+ * the serialized stream directly, without individual processing of the
+ * structure's members.
+ *
+ * Derive ParamTraits<T> from PlainOldDataSerializer<T> if T is POD.
+ *
+ * Note: For POD structures with enumeration fields, this will not do
+ * validation of the enum values the way serializing the fields
+ * individually would. Prefer serializing the fields individually
+ * in such cases.
+ */
+template <typename T>
+struct PlainOldDataSerializer {
+ static_assert(
+ std::is_trivially_copyable<T>::value,
+ "PlainOldDataSerializer can only be used with trivially copyable types!");
+
+ typedef T paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aWriter->WriteBytes(&aParam, sizeof(aParam));
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return aReader->ReadBytesInto(aResult, sizeof(paramType));
+ }
+};
+
+/**
+ * A helper class for serializing empty structs. Since the struct is empty there
+ * is nothing to write, and a priori we know the result of the read.
+ */
+template <typename T>
+struct EmptyStructSerializer {
+ typedef T paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {}
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ *aResult = {};
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<int8_t> {
+ typedef int8_t paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aWriter->WriteBytes(&aParam, sizeof(aParam));
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return aReader->ReadBytesInto(aResult, sizeof(*aResult));
+ }
+};
+
+template <>
+struct ParamTraits<uint8_t> {
+ typedef uint8_t paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aWriter->WriteBytes(&aParam, sizeof(aParam));
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return aReader->ReadBytesInto(aResult, sizeof(*aResult));
+ }
+};
+
+#if !defined(OS_POSIX)
+// See above re: keeping definitions in sync
+template <>
+struct ParamTraits<base::FileDescriptor> {
+ typedef base::FileDescriptor paramType;
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ MOZ_CRASH("FileDescriptor isn't meaningful on this platform");
+ }
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ MOZ_CRASH("FileDescriptor isn't meaningful on this platform");
+ return false;
+ }
+};
+#endif // !defined(OS_POSIX)
+
+template <>
+struct ParamTraits<mozilla::void_t> {
+ typedef mozilla::void_t paramType;
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {}
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ *aResult = paramType();
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::null_t> {
+ typedef mozilla::null_t paramType;
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {}
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ *aResult = paramType();
+ return true;
+ }
+};
+
+// Helper class for reading bitfields.
+// If T has bitfields members, derive ParamTraits<T> from BitfieldHelper<T>.
+template <typename ParamType>
+struct BitfieldHelper {
+ // We need this helper because we can't get the address of a bitfield to
+ // pass directly to ReadParam. So instead we read it into a temporary bool
+ // and set the bitfield using a setter function
+ static bool ReadBoolForBitfield(MessageReader* aReader, ParamType* aResult,
+ void (ParamType::*aSetter)(bool)) {
+ bool value;
+ if (ReadParam(aReader, &value)) {
+ (aResult->*aSetter)(value);
+ return true;
+ }
+ return false;
+ }
+};
+
+// A couple of recursive helper functions, allows syntax like:
+// WriteParams(aMsg, aParam.foo, aParam.bar, aParam.baz)
+// ReadParams(aMsg, aIter, aParam.foo, aParam.bar, aParam.baz)
+
+template <typename... Ts>
+static void WriteParams(MessageWriter* aWriter, const Ts&... aArgs) {
+ (WriteParam(aWriter, aArgs), ...);
+}
+
+template <typename... Ts>
+static bool ReadParams(MessageReader* aReader, Ts&... aArgs) {
+ return (ReadParam(aReader, &aArgs) && ...);
+}
+
+// Macros that allow syntax like:
+// DEFINE_IPC_SERIALIZER_WITH_FIELDS(SomeType, member1, member2, member3)
+// Makes sure that serialize/deserialize code do the same members in the same
+// order.
+#define ACCESS_PARAM_FIELD(Field) aParam.Field
+
+#define DEFINE_IPC_SERIALIZER_WITH_FIELDS(Type, ...) \
+ template <> \
+ struct ParamTraits<Type> { \
+ typedef Type paramType; \
+ static void Write(MessageWriter* aWriter, const paramType& aParam) { \
+ WriteParams(aWriter, MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), \
+ (), (__VA_ARGS__))); \
+ } \
+ \
+ static bool Read(MessageReader* aReader, paramType* aResult) { \
+ paramType& aParam = *aResult; \
+ return ReadParams(aReader, \
+ MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), (), \
+ (__VA_ARGS__))); \
+ } \
+ };
+
+#define DEFINE_IPC_SERIALIZER_WITHOUT_FIELDS(Type) \
+ template <> \
+ struct ParamTraits<Type> : public EmptyStructSerializer<Type> {};
+
+} /* namespace IPC */
+
+#define DEFINE_IPC_SERIALIZER_WITH_SUPER_CLASS_AND_FIELDS(Type, Super, ...) \
+ template <> \
+ struct ParamTraits<Type> { \
+ typedef Type paramType; \
+ static void Write(MessageWriter* aWriter, const paramType& aParam) { \
+ WriteParam(aWriter, static_cast<const Super&>(aParam)); \
+ WriteParams(aWriter, MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), \
+ (), (__VA_ARGS__))); \
+ } \
+ \
+ static bool Read(MessageReader* aReader, paramType* aResult) { \
+ paramType& aParam = *aResult; \
+ return ReadParam(aReader, static_cast<Super*>(aResult)) && \
+ ReadParams(aReader, \
+ MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), (), \
+ (__VA_ARGS__))); \
+ } \
+ };
+
+#endif /* __IPC_GLUE_IPCMESSAGEUTILS_H__ */
diff --git a/ipc/glue/IPCMessageUtilsSpecializations.cpp b/ipc/glue/IPCMessageUtilsSpecializations.cpp
new file mode 100644
index 0000000000..410b1e730f
--- /dev/null
+++ b/ipc/glue/IPCMessageUtilsSpecializations.cpp
@@ -0,0 +1,67 @@
+/* -*- 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 "IPCMessageUtilsSpecializations.h"
+#include "nsGkAtoms.h"
+
+namespace IPC {
+
+static const uint16_t kDynamicAtomToken = 0xffff;
+static const uint16_t kAtomsCount =
+ static_cast<uint16_t>(mozilla::detail::GkAtoms::Atoms::AtomsCount);
+
+static_assert(static_cast<size_t>(
+ mozilla::detail::GkAtoms::Atoms::AtomsCount) == kAtomsCount,
+ "Number of static atoms must fit in a uint16_t");
+
+static_assert(kDynamicAtomToken >= kAtomsCount,
+ "Exceeded supported number of static atoms");
+
+/* static */
+void ParamTraits<nsAtom*>::Write(MessageWriter* aWriter, const nsAtom* aParam) {
+ MOZ_ASSERT(aParam);
+
+ if (aParam->IsStatic()) {
+ const nsStaticAtom* atom = aParam->AsStatic();
+ uint16_t index = static_cast<uint16_t>(nsGkAtoms::IndexOf(atom));
+ MOZ_ASSERT(index < kAtomsCount);
+ WriteParam(aWriter, index);
+ return;
+ }
+ WriteParam(aWriter, kDynamicAtomToken);
+ nsDependentAtomString atomStr(aParam);
+ // nsDependentAtomString is serialized as its base, nsString, but we
+ // can be explicit about it.
+ nsString& str = atomStr;
+ WriteParam(aWriter, str);
+}
+
+/* static */
+bool ParamTraits<nsAtom*>::Read(MessageReader* aReader,
+ RefPtr<nsAtom>* aResult) {
+ uint16_t token;
+ if (!ReadParam(aReader, &token)) {
+ return false;
+ }
+ if (token != kDynamicAtomToken) {
+ if (token >= kAtomsCount) {
+ return false;
+ }
+ *aResult = nsGkAtoms::GetAtomByIndex(token);
+ return true;
+ }
+
+ nsAutoString str;
+ if (!ReadParam(aReader, static_cast<nsString*>(&str))) {
+ return false;
+ }
+
+ *aResult = NS_Atomize(str);
+ MOZ_ASSERT(*aResult);
+ return true;
+}
+
+} // namespace IPC
diff --git a/ipc/glue/IPCMessageUtilsSpecializations.h b/ipc/glue/IPCMessageUtilsSpecializations.h
new file mode 100644
index 0000000000..4f357405d9
--- /dev/null
+++ b/ipc/glue/IPCMessageUtilsSpecializations.h
@@ -0,0 +1,802 @@
+/* -*- 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/. */
+
+#ifndef __IPC_GLUE_IPCMESSAGEUTILSSPECIALIZATIONS_H__
+#define __IPC_GLUE_IPCMESSAGEUTILSSPECIALIZATIONS_H__
+
+#include <cstdint>
+#include <cstdlib>
+#include <limits>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+#include "chrome/common/ipc_message.h"
+#include "chrome/common/ipc_message_utils.h"
+#include "ipc/EnumSerializer.h"
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/BitSet.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/EnumTypeTraits.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/TimeStamp.h"
+#ifdef XP_WIN
+# include "mozilla/TimeStamp_windows.h"
+#endif
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Vector.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "nsCSSPropertyID.h"
+#include "nsDebug.h"
+#include "nsIContentPolicy.h"
+#include "nsID.h"
+#include "nsILoadInfo.h"
+#include "nsIThread.h"
+#include "nsLiteralString.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsTHashSet.h"
+
+// XXX Includes that are only required by implementations which could be moved
+// to the cpp file.
+#include "base/string_util.h" // for StringPrintf
+#include "mozilla/ArrayUtils.h" // for ArrayLength
+#include "mozilla/CheckedInt.h"
+
+#ifdef _MSC_VER
+# pragma warning(disable : 4800)
+#endif
+
+namespace mozilla {
+template <typename... Ts>
+class Variant;
+
+namespace detail {
+template <typename... Ts>
+struct VariantTag;
+}
+} // namespace mozilla
+
+namespace mozilla::dom {
+template <typename T>
+class Optional;
+}
+
+class nsAtom;
+
+namespace IPC {
+
+template <class T>
+struct ParamTraits<nsTSubstring<T>> {
+ typedef nsTSubstring<T> paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ bool isVoid = aParam.IsVoid();
+ aWriter->WriteBool(isVoid);
+
+ if (isVoid) {
+ // represents a nullptr pointer
+ return;
+ }
+
+ WriteSequenceParam<const T&>(aWriter, aParam.BeginReading(),
+ aParam.Length());
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ bool isVoid;
+ if (!aReader->ReadBool(&isVoid)) {
+ return false;
+ }
+
+ if (isVoid) {
+ aResult->SetIsVoid(true);
+ return true;
+ }
+
+ return ReadSequenceParam<T>(aReader, [&](uint32_t aLength) -> T* {
+ T* data = nullptr;
+ aResult->GetMutableData(&data, aLength);
+ return data;
+ });
+ }
+};
+
+template <class T>
+struct ParamTraits<nsTString<T>> : ParamTraits<nsTSubstring<T>> {};
+
+template <class T>
+struct ParamTraits<nsTLiteralString<T>> : ParamTraits<nsTSubstring<T>> {};
+
+template <class T, size_t N>
+struct ParamTraits<nsTAutoStringN<T, N>> : ParamTraits<nsTSubstring<T>> {};
+
+template <class T>
+struct ParamTraits<nsTDependentString<T>> : ParamTraits<nsTSubstring<T>> {};
+
+// XXX While this has no special dependencies, it's currently only used in
+// GfxMessageUtils and could be moved there, or generalized to potentially work
+// with any nsTHashSet.
+template <>
+struct ParamTraits<nsTHashSet<uint64_t>> {
+ typedef nsTHashSet<uint64_t> paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ uint32_t count = aParam.Count();
+ WriteParam(aWriter, count);
+ for (const auto& key : aParam) {
+ WriteParam(aWriter, key);
+ }
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint32_t count;
+ if (!ReadParam(aReader, &count)) {
+ return false;
+ }
+ paramType table(count);
+ for (uint32_t i = 0; i < count; ++i) {
+ uint64_t key;
+ if (!ReadParam(aReader, &key)) {
+ return false;
+ }
+ table.Insert(key);
+ }
+ *aResult = std::move(table);
+ return true;
+ }
+};
+
+template <typename E>
+struct ParamTraits<nsTArray<E>> {
+ typedef nsTArray<E> paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteSequenceParam<const E&>(aWriter, aParam.Elements(), aParam.Length());
+ }
+
+ static void Write(MessageWriter* aWriter, paramType&& aParam) {
+ WriteSequenceParam<E&&>(aWriter, aParam.Elements(), aParam.Length());
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadSequenceParam<E>(aReader, [&](uint32_t aLength) {
+ if constexpr (std::is_trivially_default_constructible_v<E>) {
+ return aResult->AppendElements(aLength);
+ } else {
+ aResult->SetCapacity(aLength);
+ return mozilla::Some(MakeBackInserter(*aResult));
+ }
+ });
+ }
+};
+
+template <typename E>
+struct ParamTraits<CopyableTArray<E>> : ParamTraits<nsTArray<E>> {};
+
+template <typename E>
+struct ParamTraits<FallibleTArray<E>> {
+ typedef FallibleTArray<E> paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteSequenceParam<const E&>(aWriter, aParam.Elements(), aParam.Length());
+ }
+
+ static void Write(MessageWriter* aWriter, paramType&& aParam) {
+ WriteSequenceParam<E&&>(aWriter, aParam.Elements(), aParam.Length());
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadSequenceParam<E>(aReader, [&](uint32_t aLength) {
+ if constexpr (std::is_trivially_default_constructible_v<E>) {
+ return aResult->AppendElements(aLength, mozilla::fallible);
+ } else {
+ if (!aResult->SetCapacity(aLength, mozilla::fallible)) {
+ return mozilla::Maybe<BackInserter>{};
+ }
+ return mozilla::Some(BackInserter{.mArray = aResult});
+ }
+ });
+ }
+
+ private:
+ struct BackInserter {
+ using iterator_category = std::output_iterator_tag;
+ using value_type = void;
+ using difference_type = void;
+ using pointer = void;
+ using reference = void;
+
+ struct Proxy {
+ paramType& mArray;
+
+ template <typename U>
+ void operator=(U&& aValue) {
+ // This won't fail because we've reserved capacity earlier.
+ MOZ_ALWAYS_TRUE(mArray.AppendElement(aValue, mozilla::fallible));
+ }
+ };
+ Proxy operator*() { return Proxy{.mArray = *mArray}; }
+
+ BackInserter& operator++() { return *this; }
+ BackInserter& operator++(int) { return *this; }
+
+ paramType* mArray = nullptr;
+ };
+};
+
+template <typename E, size_t N>
+struct ParamTraits<AutoTArray<E, N>> : ParamTraits<nsTArray<E>> {
+ typedef AutoTArray<E, N> paramType;
+};
+
+template <typename E, size_t N>
+struct ParamTraits<CopyableAutoTArray<E, N>> : ParamTraits<AutoTArray<E, N>> {};
+
+template <typename T>
+struct ParamTraits<mozilla::dom::Sequence<T>> : ParamTraits<FallibleTArray<T>> {
+};
+
+template <typename E, size_t N, typename AP>
+struct ParamTraits<mozilla::Vector<E, N, AP>> {
+ typedef mozilla::Vector<E, N, AP> paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteSequenceParam<const E&>(aWriter, aParam.Elements(), aParam.Length());
+ }
+
+ static void Write(MessageWriter* aWriter, paramType&& aParam) {
+ WriteSequenceParam<E&&>(aWriter, aParam.Elements(), aParam.Length());
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadSequenceParam<E>(aReader, [&](uint32_t aLength) -> E* {
+ if (!aResult->resize(aLength)) {
+ // So that OOM failure shows up as OOM crash instead of IPC FatalError.
+ NS_ABORT_OOM(aLength * sizeof(E));
+ }
+ return aResult->begin();
+ });
+ }
+};
+
+template <typename E>
+struct ParamTraits<std::vector<E>> {
+ typedef std::vector<E> paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteSequenceParam<const E&>(aWriter, aParam.data(), aParam.size());
+ }
+ static void Write(MessageWriter* aWriter, paramType&& aParam) {
+ WriteSequenceParam<E&&>(aWriter, aParam.data(), aParam.size());
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadSequenceParam<E>(aReader, [&](uint32_t aLength) {
+ if constexpr (std::is_trivially_default_constructible_v<E>) {
+ aResult->resize(aLength);
+ return aResult->data();
+ } else {
+ aResult->reserve(aLength);
+ return mozilla::Some(std::back_inserter(*aResult));
+ }
+ });
+ }
+};
+
+template <typename K, typename V>
+struct ParamTraits<std::unordered_map<K, V>> final {
+ using T = std::unordered_map<K, V>;
+
+ static void Write(MessageWriter* const writer, const T& in) {
+ WriteParam(writer, in.size());
+ for (const auto& pair : in) {
+ WriteParam(writer, pair.first);
+ WriteParam(writer, pair.second);
+ }
+ }
+
+ static bool Read(MessageReader* const reader, T* const out) {
+ size_t size = 0;
+ if (!ReadParam(reader, &size)) return false;
+ T map;
+ map.reserve(size);
+ for (const auto i : mozilla::IntegerRange(size)) {
+ std::pair<K, V> pair;
+ mozilla::Unused << i;
+ if (!ReadParam(reader, &(pair.first)) ||
+ !ReadParam(reader, &(pair.second))) {
+ return false;
+ }
+ map.insert(std::move(pair));
+ }
+ *out = std::move(map);
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<float> {
+ typedef float paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aWriter->WriteBytes(&aParam, sizeof(paramType));
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return aReader->ReadBytesInto(aResult, sizeof(*aResult));
+ }
+};
+
+template <>
+struct ParamTraits<nsCSSPropertyID>
+ : public ContiguousEnumSerializer<nsCSSPropertyID, eCSSProperty_UNKNOWN,
+ eCSSProperty_COUNT> {};
+
+template <>
+struct ParamTraits<nsID> {
+ typedef nsID paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.m0);
+ WriteParam(aWriter, aParam.m1);
+ WriteParam(aWriter, aParam.m2);
+ for (unsigned int i = 0; i < mozilla::ArrayLength(aParam.m3); i++) {
+ WriteParam(aWriter, aParam.m3[i]);
+ }
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &(aResult->m0)) ||
+ !ReadParam(aReader, &(aResult->m1)) ||
+ !ReadParam(aReader, &(aResult->m2)))
+ return false;
+
+ for (unsigned int i = 0; i < mozilla::ArrayLength(aResult->m3); i++)
+ if (!ReadParam(aReader, &(aResult->m3[i]))) return false;
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<nsContentPolicyType>
+ : public ContiguousEnumSerializer<nsContentPolicyType,
+ nsIContentPolicy::TYPE_INVALID,
+ nsIContentPolicy::TYPE_END> {};
+
+template <>
+struct ParamTraits<mozilla::TimeDuration> {
+ typedef mozilla::TimeDuration paramType;
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mValue);
+ }
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->mValue);
+ };
+};
+
+template <>
+struct ParamTraits<mozilla::TimeStamp> {
+ typedef mozilla::TimeStamp paramType;
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mValue);
+ }
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->mValue);
+ };
+};
+
+#ifdef XP_WIN
+
+template <>
+struct ParamTraits<mozilla::TimeStampValue> {
+ typedef mozilla::TimeStampValue paramType;
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mGTC);
+ WriteParam(aWriter, aParam.mQPC);
+ WriteParam(aWriter, aParam.mIsNull);
+ WriteParam(aWriter, aParam.mHasQPC);
+ }
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return (ReadParam(aReader, &aResult->mGTC) &&
+ ReadParam(aReader, &aResult->mQPC) &&
+ ReadParam(aReader, &aResult->mIsNull) &&
+ ReadParam(aReader, &aResult->mHasQPC));
+ }
+};
+
+#endif
+
+template <>
+struct ParamTraits<mozilla::dom::ipc::StructuredCloneData> {
+ typedef mozilla::dom::ipc::StructuredCloneData paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aParam.WriteIPCParams(aWriter);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return aResult->ReadIPCParams(aReader);
+ }
+};
+
+template <class T>
+struct ParamTraits<mozilla::Maybe<T>> {
+ typedef mozilla::Maybe<T> paramType;
+
+ static void Write(MessageWriter* writer, const paramType& param) {
+ if (param.isSome()) {
+ WriteParam(writer, true);
+ WriteParam(writer, param.ref());
+ } else {
+ WriteParam(writer, false);
+ }
+ }
+
+ static void Write(MessageWriter* writer, paramType&& param) {
+ if (param.isSome()) {
+ WriteParam(writer, true);
+ WriteParam(writer, std::move(param.ref()));
+ } else {
+ WriteParam(writer, false);
+ }
+ }
+
+ static bool Read(MessageReader* reader, paramType* result) {
+ bool isSome;
+ if (!ReadParam(reader, &isSome)) {
+ return false;
+ }
+ if (isSome) {
+ mozilla::Maybe<T> tmp = ReadParam<T>(reader).TakeMaybe();
+ if (!tmp) {
+ return false;
+ }
+ *result = std::move(tmp);
+ } else {
+ *result = mozilla::Nothing();
+ }
+ return true;
+ }
+};
+
+template <typename T, typename U>
+struct ParamTraits<mozilla::EnumSet<T, U>> {
+ typedef mozilla::EnumSet<T, U> paramType;
+ typedef U serializedType;
+
+ static void Write(MessageWriter* writer, const paramType& param) {
+ MOZ_RELEASE_ASSERT(IsLegalValue(param.serialize()));
+ WriteParam(writer, param.serialize());
+ }
+
+ static bool Read(MessageReader* reader, paramType* result) {
+ serializedType tmp;
+
+ if (ReadParam(reader, &tmp)) {
+ if (IsLegalValue(tmp)) {
+ result->deserialize(tmp);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ static constexpr serializedType AllEnumBits() {
+ return ~serializedType(0) >> (std::numeric_limits<serializedType>::digits -
+ (mozilla::MaxEnumValue<T>::value + 1));
+ }
+
+ static constexpr bool IsLegalValue(const serializedType value) {
+ static_assert(mozilla::MaxEnumValue<T>::value <
+ std::numeric_limits<serializedType>::digits,
+ "Enum max value is not in the range!");
+ static_assert(
+ std::is_unsigned<decltype(mozilla::MaxEnumValue<T>::value)>::value,
+ "Type of MaxEnumValue<T>::value specialization should be unsigned!");
+
+ return (value & AllEnumBits()) == value;
+ }
+};
+
+template <class... Ts>
+struct ParamTraits<mozilla::Variant<Ts...>> {
+ typedef mozilla::Variant<Ts...> paramType;
+ using Tag = typename mozilla::detail::VariantTag<Ts...>::Type;
+
+ static void Write(MessageWriter* writer, const paramType& param) {
+ WriteParam(writer, param.tag);
+ param.match([writer](const auto& t) { WriteParam(writer, t); });
+ }
+
+ // Because VariantReader is a nested struct, we need the dummy template
+ // parameter to avoid making VariantReader<0> an explicit specialization,
+ // which is not allowed for a nested class template
+ template <size_t N, typename dummy = void>
+ struct VariantReader {
+ using Next = VariantReader<N - 1>;
+
+ static bool Read(MessageReader* reader, Tag tag, paramType* result) {
+ // Since the VariantReader specializations start at N , we need to
+ // subtract one to look at N - 1, the first valid tag. This means our
+ // comparisons are off by 1. If we get to N = 0 then we have failed to
+ // find a match to the tag.
+ if (tag == N - 1) {
+ // Recall, even though the template parameter is N, we are
+ // actually interested in the N - 1 tag.
+ // Default construct our field within the result outparameter and
+ // directly deserialize into the variant. Note that this means that
+ // every type in Ts needs to be default constructible
+ return ReadParam(reader, &result->template emplace<N - 1>());
+ } else {
+ return Next::Read(reader, tag, result);
+ }
+ }
+
+ }; // VariantReader<N>
+
+ // Since we are conditioning on tag = N - 1 in the preceding specialization,
+ // if we get to `VariantReader<0, dummy>` we have failed to find
+ // a matching tag.
+ template <typename dummy>
+ struct VariantReader<0, dummy> {
+ static bool Read(MessageReader* reader, Tag tag, paramType* result) {
+ return false;
+ }
+ };
+
+ static bool Read(MessageReader* reader, paramType* result) {
+ Tag tag;
+ if (ReadParam(reader, &tag)) {
+ return VariantReader<sizeof...(Ts)>::Read(reader, tag, result);
+ }
+ return false;
+ }
+};
+
+template <typename T>
+struct ParamTraits<mozilla::dom::Optional<T>> {
+ typedef mozilla::dom::Optional<T> paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ if (aParam.WasPassed()) {
+ WriteParam(aWriter, true);
+ WriteParam(aWriter, aParam.Value());
+ return;
+ }
+
+ WriteParam(aWriter, false);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ bool wasPassed = false;
+
+ if (!ReadParam(aReader, &wasPassed)) {
+ return false;
+ }
+
+ aResult->Reset();
+
+ if (wasPassed) {
+ if (!ReadParam(aReader, &aResult->Construct())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<nsAtom*> {
+ typedef nsAtom paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType* aParam);
+ static bool Read(MessageReader* aReader, RefPtr<paramType>* aResult);
+};
+
+struct CrossOriginOpenerPolicyValidator {
+ using IntegralType =
+ std::underlying_type_t<nsILoadInfo::CrossOriginOpenerPolicy>;
+
+ static bool IsLegalValue(const IntegralType e) {
+ return AreIntegralValuesEqual(e, nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) ||
+ AreIntegralValuesEqual(e, nsILoadInfo::OPENER_POLICY_SAME_ORIGIN) ||
+ AreIntegralValuesEqual(
+ e, nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS) ||
+ AreIntegralValuesEqual(
+ e, nsILoadInfo::
+ OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP);
+ }
+
+ private:
+ static bool AreIntegralValuesEqual(
+ const IntegralType aLhs,
+ const nsILoadInfo::CrossOriginOpenerPolicy aRhs) {
+ return aLhs == static_cast<IntegralType>(aRhs);
+ }
+};
+
+template <>
+struct ParamTraits<nsILoadInfo::CrossOriginOpenerPolicy>
+ : EnumSerializer<nsILoadInfo::CrossOriginOpenerPolicy,
+ CrossOriginOpenerPolicyValidator> {};
+
+struct CrossOriginEmbedderPolicyValidator {
+ using IntegralType =
+ std::underlying_type_t<nsILoadInfo::CrossOriginEmbedderPolicy>;
+
+ static bool IsLegalValue(const IntegralType e) {
+ return AreIntegralValuesEqual(e, nsILoadInfo::EMBEDDER_POLICY_NULL) ||
+ AreIntegralValuesEqual(e,
+ nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) ||
+ AreIntegralValuesEqual(e,
+ nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS);
+ }
+
+ private:
+ static bool AreIntegralValuesEqual(
+ const IntegralType aLhs,
+ const nsILoadInfo::CrossOriginEmbedderPolicy aRhs) {
+ return aLhs == static_cast<IntegralType>(aRhs);
+ }
+};
+
+template <>
+struct ParamTraits<nsILoadInfo::CrossOriginEmbedderPolicy>
+ : EnumSerializer<nsILoadInfo::CrossOriginEmbedderPolicy,
+ CrossOriginEmbedderPolicyValidator> {};
+
+template <size_t N, typename Word>
+struct ParamTraits<mozilla::BitSet<N, Word>> {
+ typedef mozilla::BitSet<N, Word> paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ for (Word word : aParam.Storage()) {
+ WriteParam(aWriter, word);
+ }
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ for (Word& word : aResult->Storage()) {
+ if (!ReadParam(aReader, &word)) {
+ return false;
+ }
+ }
+ return true;
+ }
+};
+
+template <typename T>
+struct ParamTraits<mozilla::UniquePtr<T>> {
+ typedef mozilla::UniquePtr<T> paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ bool isNull = aParam == nullptr;
+ WriteParam(aWriter, isNull);
+
+ if (!isNull) {
+ WriteParam(aWriter, *aParam.get());
+ }
+ }
+
+ static bool Read(IPC::MessageReader* aReader, paramType* aResult) {
+ bool isNull = true;
+ if (!ReadParam(aReader, &isNull)) {
+ return false;
+ }
+
+ if (isNull) {
+ aResult->reset();
+ } else {
+ *aResult = mozilla::MakeUnique<T>();
+ if (!ReadParam(aReader, aResult->get())) {
+ return false;
+ }
+ }
+ return true;
+ }
+};
+
+template <typename... Ts>
+struct ParamTraits<std::tuple<Ts...>> {
+ typedef std::tuple<Ts...> paramType;
+
+ template <typename U>
+ static void Write(IPC::MessageWriter* aWriter, U&& aParam) {
+ WriteInternal(aWriter, std::forward<U>(aParam),
+ std::index_sequence_for<Ts...>{});
+ }
+
+ static bool Read(IPC::MessageReader* aReader, std::tuple<Ts...>* aResult) {
+ return ReadInternal(aReader, *aResult, std::index_sequence_for<Ts...>{});
+ }
+
+ private:
+ template <size_t... Is>
+ static void WriteInternal(IPC::MessageWriter* aWriter,
+ const std::tuple<Ts...>& aParam,
+ std::index_sequence<Is...>) {
+ WriteParams(aWriter, std::get<Is>(aParam)...);
+ }
+
+ template <size_t... Is>
+ static void WriteInternal(IPC::MessageWriter* aWriter,
+ std::tuple<Ts...>&& aParam,
+ std::index_sequence<Is...>) {
+ WriteParams(aWriter, std::move(std::get<Is>(aParam))...);
+ }
+
+ template <size_t... Is>
+ static bool ReadInternal(IPC::MessageReader* aReader,
+ std::tuple<Ts...>& aResult,
+ std::index_sequence<Is...>) {
+ return ReadParams(aReader, std::get<Is>(aResult)...);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::LinkHeader> {
+ typedef mozilla::net::LinkHeader paramType;
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mHref);
+ WriteParam(aWriter, aParam.mRel);
+ WriteParam(aWriter, aParam.mTitle);
+ WriteParam(aWriter, aParam.mIntegrity);
+ WriteParam(aWriter, aParam.mSrcset);
+ WriteParam(aWriter, aParam.mSizes);
+ WriteParam(aWriter, aParam.mType);
+ WriteParam(aWriter, aParam.mMedia);
+ WriteParam(aWriter, aParam.mCrossOrigin);
+ WriteParam(aWriter, aParam.mReferrerPolicy);
+ WriteParam(aWriter, aParam.mAs);
+ }
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &aResult->mHref)) {
+ return false;
+ }
+ if (!ReadParam(aReader, &aResult->mRel)) {
+ return false;
+ }
+ if (!ReadParam(aReader, &aResult->mTitle)) {
+ return false;
+ }
+ if (!ReadParam(aReader, &aResult->mIntegrity)) {
+ return false;
+ }
+ if (!ReadParam(aReader, &aResult->mSrcset)) {
+ return false;
+ }
+ if (!ReadParam(aReader, &aResult->mSizes)) {
+ return false;
+ }
+ if (!ReadParam(aReader, &aResult->mType)) {
+ return false;
+ }
+ if (!ReadParam(aReader, &aResult->mMedia)) {
+ return false;
+ }
+ if (!ReadParam(aReader, &aResult->mCrossOrigin)) {
+ return false;
+ }
+ if (!ReadParam(aReader, &aResult->mReferrerPolicy)) {
+ return false;
+ }
+ return ReadParam(aReader, &aResult->mAs);
+ };
+};
+
+} /* namespace IPC */
+
+#endif /* __IPC_GLUE_IPCMESSAGEUTILSSPECIALIZATIONS_H__ */
diff --git a/ipc/glue/IPCStream.ipdlh b/ipc/glue/IPCStream.ipdlh
new file mode 100644
index 0000000000..abf7ef7714
--- /dev/null
+++ b/ipc/glue/IPCStream.ipdlh
@@ -0,0 +1,22 @@
+/* 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 InputStreamParams;
+include ProtocolTypes;
+
+namespace mozilla {
+namespace ipc {
+
+// Use IPCStream in your ipdl to represent serialized nsIInputStreams. Then use
+// SerializeIPCStream from IPCStreamUtils.h to perform the serialization.
+//
+// NOTE: If you don't need to handle nsIInputStream serialization failure,
+// `nsIInputStream` may be used directly by IPDL protocols.
+struct IPCStream
+{
+ InputStreamParams stream;
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/IPCStreamUtils.cpp b/ipc/glue/IPCStreamUtils.cpp
new file mode 100644
index 0000000000..265cb1cff8
--- /dev/null
+++ b/ipc/glue/IPCStreamUtils.cpp
@@ -0,0 +1,191 @@
+/* -*- 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 "IPCStreamUtils.h"
+
+#include "ipc/IPCMessageUtilsSpecializations.h"
+
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "mozIRemoteLazyInputStream.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/ipc/IPCStream.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/InputStreamLengthHelper.h"
+#include "mozilla/RemoteLazyInputStreamParent.h"
+#include "mozilla/Unused.h"
+#include "nsIMIMEInputStream.h"
+#include "nsNetCID.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla::ipc {
+
+namespace {
+
+class MIMEStreamHeaderVisitor final : public nsIHttpHeaderVisitor {
+ public:
+ explicit MIMEStreamHeaderVisitor(
+ nsTArray<mozilla::ipc::HeaderEntry>& aHeaders)
+ : mHeaders(aHeaders) {}
+
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD VisitHeader(const nsACString& aName,
+ const nsACString& aValue) override {
+ auto el = mHeaders.AppendElement();
+ el->name() = aName;
+ el->value() = aValue;
+ return NS_OK;
+ }
+
+ private:
+ ~MIMEStreamHeaderVisitor() = default;
+
+ nsTArray<mozilla::ipc::HeaderEntry>& mHeaders;
+};
+
+NS_IMPL_ISUPPORTS(MIMEStreamHeaderVisitor, nsIHttpHeaderVisitor)
+
+static bool SerializeLazyInputStream(nsIInputStream* aStream,
+ IPCStream& aValue) {
+ MOZ_ASSERT(aStream);
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // If we're serializing a MIME stream, ensure we preserve header data which
+ // would not be preserved by a RemoteLazyInputStream wrapper.
+ if (nsCOMPtr<nsIMIMEInputStream> mimeStream = do_QueryInterface(aStream)) {
+ MIMEInputStreamParams params;
+ params.startedReading() = false;
+
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new MIMEStreamHeaderVisitor(params.headers());
+ if (NS_WARN_IF(NS_FAILED(mimeStream->VisitHeaders(visitor)))) {
+ return false;
+ }
+
+ nsCOMPtr<nsIInputStream> dataStream;
+ if (NS_FAILED(mimeStream->GetData(getter_AddRefs(dataStream)))) {
+ return false;
+ }
+ if (dataStream) {
+ IPCStream data;
+ if (!SerializeLazyInputStream(dataStream, data)) {
+ return false;
+ }
+ params.optionalStream().emplace(std::move(data.stream()));
+ }
+
+ aValue.stream() = std::move(params);
+ return true;
+ }
+
+ RefPtr<RemoteLazyInputStream> lazyStream =
+ RemoteLazyInputStream::WrapStream(aStream);
+ if (NS_WARN_IF(!lazyStream)) {
+ return false;
+ }
+
+ aValue.stream() = RemoteLazyInputStreamParams(lazyStream);
+
+ return true;
+}
+
+} // anonymous namespace
+
+bool SerializeIPCStream(already_AddRefed<nsIInputStream> aInputStream,
+ IPCStream& aValue, bool aAllowLazy) {
+ nsCOMPtr<nsIInputStream> stream(std::move(aInputStream));
+ if (!stream) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Use the Maybe<...> overload to serialize optional nsIInputStreams");
+ return false;
+ }
+
+ // When requesting a delayed start stream from the parent process, serialize
+ // it as a remote lazy stream to avoid bloating payloads.
+ if (aAllowLazy && XRE_IsParentProcess()) {
+ return SerializeLazyInputStream(stream, aValue);
+ }
+
+ if (nsCOMPtr<nsIIPCSerializableInputStream> serializable =
+ do_QueryInterface(stream)) {
+ // If you change this size, please also update the payload size in
+ // test_reload_large_postdata.html.
+ const uint64_t kTooLargeStream = 1024 * 1024;
+
+ uint32_t sizeUsed = 0;
+ serializable->Serialize(aValue.stream(), kTooLargeStream, &sizeUsed);
+
+ MOZ_ASSERT(sizeUsed <= kTooLargeStream);
+
+ if (aValue.stream().type() == InputStreamParams::T__None) {
+ MOZ_CRASH("Serialize failed!");
+ }
+ return true;
+ }
+
+ InputStreamHelper::SerializeInputStreamAsPipe(stream, aValue.stream());
+ if (aValue.stream().type() == InputStreamParams::T__None) {
+ MOZ_ASSERT_UNREACHABLE("Serializing as a pipe failed");
+ return false;
+ }
+ return true;
+}
+
+bool SerializeIPCStream(already_AddRefed<nsIInputStream> aInputStream,
+ Maybe<IPCStream>& aValue, bool aAllowLazy) {
+ nsCOMPtr<nsIInputStream> stream(std::move(aInputStream));
+ if (!stream) {
+ aValue.reset();
+ return true;
+ }
+
+ IPCStream value;
+ if (SerializeIPCStream(stream.forget(), value, aAllowLazy)) {
+ aValue = Some(value);
+ return true;
+ }
+ return false;
+}
+
+already_AddRefed<nsIInputStream> DeserializeIPCStream(const IPCStream& aValue) {
+ return InputStreamHelper::DeserializeInputStream(aValue.stream());
+}
+
+already_AddRefed<nsIInputStream> DeserializeIPCStream(
+ const Maybe<IPCStream>& aValue) {
+ if (aValue.isNothing()) {
+ return nullptr;
+ }
+
+ return DeserializeIPCStream(aValue.ref());
+}
+
+} // namespace mozilla::ipc
+
+void IPC::ParamTraits<nsIInputStream*>::Write(IPC::MessageWriter* aWriter,
+ nsIInputStream* aParam) {
+ mozilla::Maybe<mozilla::ipc::IPCStream> stream;
+ if (!mozilla::ipc::SerializeIPCStream(do_AddRef(aParam), stream,
+ /* aAllowLazy */ true)) {
+ MOZ_CRASH("Failed to serialize nsIInputStream");
+ }
+
+ WriteParam(aWriter, stream);
+}
+
+bool IPC::ParamTraits<nsIInputStream*>::Read(IPC::MessageReader* aReader,
+ RefPtr<nsIInputStream>* aResult) {
+ mozilla::Maybe<mozilla::ipc::IPCStream> ipcStream;
+ if (!ReadParam(aReader, &ipcStream)) {
+ return false;
+ }
+
+ *aResult = mozilla::ipc::DeserializeIPCStream(ipcStream);
+ return true;
+}
diff --git a/ipc/glue/IPCStreamUtils.h b/ipc/glue/IPCStreamUtils.h
new file mode 100644
index 0000000000..c681f7cf1a
--- /dev/null
+++ b/ipc/glue/IPCStreamUtils.h
@@ -0,0 +1,51 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_IPCStreamUtils_h
+#define mozilla_ipc_IPCStreamUtils_h
+
+#include "mozilla/ipc/IPCStream.h"
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+
+namespace mozilla::ipc {
+
+// Serialize an IPCStream to be sent over IPC fallibly.
+//
+// If |aAllowLazy| is true the stream may be serialized as a
+// RemoteLazyInputStream when being sent from child to parent.
+//
+// ParamTraits<nsIInputStream> may be used instead if serialization cannot be
+// fallible.
+[[nodiscard]] bool SerializeIPCStream(
+ already_AddRefed<nsIInputStream> aInputStream, IPCStream& aValue,
+ bool aAllowLazy);
+
+// If serialization fails, `aValue` will be initialized to `Nothing()`, so this
+// return value is safe to ignore.
+bool SerializeIPCStream(already_AddRefed<nsIInputStream> aInputStream,
+ Maybe<IPCStream>& aValue, bool aAllowLazy);
+
+// Deserialize an IPCStream received from an actor call. These methods
+// work in both the child and parent.
+already_AddRefed<nsIInputStream> DeserializeIPCStream(const IPCStream& aValue);
+
+already_AddRefed<nsIInputStream> DeserializeIPCStream(
+ const Maybe<IPCStream>& aValue);
+
+} // namespace mozilla::ipc
+
+namespace IPC {
+
+template <>
+struct ParamTraits<nsIInputStream*> {
+ static void Write(MessageWriter* aWriter, nsIInputStream* aParam);
+ static bool Read(MessageReader* aReader, RefPtr<nsIInputStream>* aResult);
+};
+
+} // namespace IPC
+
+#endif // mozilla_ipc_IPCStreamUtils_h
diff --git a/ipc/glue/IPCTypes.h b/ipc/glue/IPCTypes.h
new file mode 100644
index 0000000000..fc6bf765e2
--- /dev/null
+++ b/ipc/glue/IPCTypes.h
@@ -0,0 +1,20 @@
+/* -*- 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/. */
+
+#ifndef IPC_GLUE_IPCTYPES_H_
+#define IPC_GLUE_IPCTYPES_H_
+
+#include <cstdint>
+
+namespace mozilla {
+
+// This is a cross-platform approximation to HANDLE, which we expect
+// to be typedef'd to void* or thereabouts.
+typedef uintptr_t WindowsHandle;
+
+} // namespace mozilla
+
+#endif // IPC_GLUE_IPCTYPES_H_
diff --git a/ipc/glue/IPDLParamTraits.h b/ipc/glue/IPDLParamTraits.h
new file mode 100644
index 0000000000..48649d92de
--- /dev/null
+++ b/ipc/glue/IPDLParamTraits.h
@@ -0,0 +1,65 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_IPDLParamTraits_h
+#define mozilla_ipc_IPDLParamTraits_h
+
+#include "chrome/common/ipc_message_utils.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Variant.h"
+
+#include "nsTArray.h"
+
+#include <type_traits>
+
+namespace mozilla {
+namespace ipc {
+
+class IProtocol;
+
+//
+// IPDLParamTraits was an extended version of ParamTraits. Unlike ParamTraits,
+// IPDLParamTraits passes an additional IProtocol* argument to the
+// write and read methods.
+//
+// Previously this was required for serializing and deserializing types which
+// require knowledge of which protocol they're being sent over, however the
+// actor is now available through `IPC::Message{Writer,Reader}::GetActor()` so
+// the extra argument is no longer necessary. Please use `IPC::ParamTraits` in
+// the future.
+//
+// Types which implement IPDLParamTraits are also supported by ParamTraits.
+//
+template <typename P>
+struct IPDLParamTraits {};
+
+//
+// WriteIPDLParam and ReadIPDLParam are like IPC::WriteParam and IPC::ReadParam,
+// however, they also accept a redundant extra actor argument.
+//
+// NOTE: WriteIPDLParam takes a universal reference, so that it can support
+// whatever reference type is supported by the underlying ParamTraits::Write
+// implementation.
+//
+template <typename P>
+static MOZ_NEVER_INLINE void WriteIPDLParam(IPC::MessageWriter* aWriter,
+ IProtocol* aActor, P&& aParam) {
+ MOZ_ASSERT(aActor == aWriter->GetActor());
+ IPC::WriteParam(aWriter, std::forward<P>(aParam));
+}
+
+template <typename P>
+static MOZ_NEVER_INLINE bool ReadIPDLParam(IPC::MessageReader* aReader,
+ IProtocol* aActor, P* aResult) {
+ MOZ_ASSERT(aActor == aReader->GetActor());
+ return IPC::ReadParam(aReader, aResult);
+}
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // defined(mozilla_ipc_IPDLParamTraits_h)
diff --git a/ipc/glue/IPDLStructMember.h b/ipc/glue/IPDLStructMember.h
new file mode 100644
index 0000000000..d2e3636054
--- /dev/null
+++ b/ipc/glue/IPDLStructMember.h
@@ -0,0 +1,37 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_IPDLStructMember_h
+#define mozilla_ipc_IPDLStructMember_h
+
+#include <type_traits>
+#include <utility>
+#include "mozilla/Attributes.h"
+
+namespace mozilla::ipc {
+
+// For types which are trivially default constructible, like `int`, force the
+// value constructor by wrapping the type in this struct. This is an
+// implementation detail which will be hidden by the generated IPDL compiler
+// code.
+template <typename T>
+struct IPDLStructMemberWrapper {
+ template <typename... Args>
+ MOZ_IMPLICIT IPDLStructMemberWrapper(Args&&... aArgs)
+ : mVal(std::forward<Args>(aArgs)...) {}
+ MOZ_IMPLICIT operator T&() { return mVal; }
+ MOZ_IMPLICIT operator const T&() const { return mVal; }
+ T mVal{};
+};
+
+template <typename T>
+using IPDLStructMember =
+ std::conditional_t<std::is_trivially_default_constructible_v<T>,
+ IPDLStructMemberWrapper<T>, T>;
+
+} // namespace mozilla::ipc
+
+#endif // mozilla_ipc_IPDLStructMember_h
diff --git a/ipc/glue/IdleSchedulerChild.cpp b/ipc/glue/IdleSchedulerChild.cpp
new file mode 100644
index 0000000000..bd05f0958a
--- /dev/null
+++ b/ipc/glue/IdleSchedulerChild.cpp
@@ -0,0 +1,152 @@
+/* -*- 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 "mozilla/ipc/IdleSchedulerChild.h"
+#include "mozilla/ipc/IdleSchedulerParent.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/IdlePeriodState.h"
+#include "mozilla/Telemetry.h"
+#include "BackgroundChild.h"
+
+namespace mozilla::ipc {
+
+static IdleSchedulerChild* sMainThreadIdleScheduler = nullptr;
+static bool sIdleSchedulerDestroyed = false;
+
+IdleSchedulerChild::~IdleSchedulerChild() {
+ if (sMainThreadIdleScheduler == this) {
+ sMainThreadIdleScheduler = nullptr;
+ sIdleSchedulerDestroyed = true;
+ }
+ MOZ_ASSERT(!mIdlePeriodState);
+}
+
+void IdleSchedulerChild::Init(IdlePeriodState* aIdlePeriodState) {
+ mIdlePeriodState = aIdlePeriodState;
+
+ RefPtr<IdleSchedulerChild> scheduler = this;
+ auto resolve =
+ [&](std::tuple<mozilla::Maybe<SharedMemoryHandle>, uint32_t>&& aResult) {
+ if (std::get<0>(aResult)) {
+ mActiveCounter.SetHandle(std::move(*std::get<0>(aResult)), false);
+ mActiveCounter.Map(sizeof(int32_t));
+ mChildId = std::get<1>(aResult);
+ if (mChildId && mIdlePeriodState && mIdlePeriodState->IsActive()) {
+ SetActive();
+ }
+ }
+ };
+
+ auto reject = [&](ResponseRejectReason) {};
+ SendInitForIdleUse(std::move(resolve), std::move(reject));
+}
+
+IPCResult IdleSchedulerChild::RecvIdleTime(uint64_t aId, TimeDuration aBudget) {
+ if (mIdlePeriodState) {
+ mIdlePeriodState->SetIdleToken(aId, aBudget);
+ }
+ return IPC_OK();
+}
+
+void IdleSchedulerChild::SetActive() {
+ if (mChildId && CanSend() && mActiveCounter.memory()) {
+ ++(static_cast<Atomic<int32_t>*>(
+ mActiveCounter.memory())[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER]);
+ ++(static_cast<Atomic<int32_t>*>(mActiveCounter.memory())[mChildId]);
+ }
+}
+
+bool IdleSchedulerChild::SetPaused() {
+ if (mChildId && CanSend() && mActiveCounter.memory()) {
+ --(static_cast<Atomic<int32_t>*>(mActiveCounter.memory())[mChildId]);
+ // The following expression reduces the global activity count and checks if
+ // it drops below the cpu counter limit.
+ return (static_cast<Atomic<int32_t>*>(
+ mActiveCounter
+ .memory())[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER])-- ==
+ static_cast<Atomic<int32_t>*>(
+ mActiveCounter.memory())[NS_IDLE_SCHEDULER_INDEX_OF_CPU_COUNTER];
+ }
+
+ return false;
+}
+
+RefPtr<IdleSchedulerChild::MayGCPromise> IdleSchedulerChild::MayGCNow() {
+ if (mIsRequestingGC || mIsDoingGC) {
+ return MayGCPromise::CreateAndResolve(false, __func__);
+ }
+ TimeStamp wait_since = TimeStamp::Now();
+
+ mIsRequestingGC = true;
+ return SendRequestGC()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self = RefPtr(this), wait_since](bool aIgnored) {
+ // Only one of these may be true at a time.
+ MOZ_ASSERT(!(self->mIsRequestingGC && self->mIsDoingGC));
+
+ // The parent process always says yes, sometimes after a delay.
+ if (self->mIsRequestingGC) {
+ Telemetry::AccumulateTimeDelta(Telemetry::GC_WAIT_FOR_IDLE_MS,
+ wait_since);
+ self->mIsRequestingGC = false;
+ self->mIsDoingGC = true;
+ return MayGCPromise::CreateAndResolve(true, __func__);
+ }
+ return MayGCPromise::CreateAndResolve(false, __func__);
+ },
+ [self = RefPtr(this)](ResponseRejectReason reason) {
+ self->mIsRequestingGC = false;
+ return MayGCPromise::CreateAndReject(reason, __func__);
+ });
+}
+
+void IdleSchedulerChild::StartedGC() {
+ // Only one of these may be true at a time.
+ MOZ_ASSERT(!(mIsRequestingGC && mIsDoingGC));
+
+ // If mRequestingGC was true then when the outstanding GC request returns
+ // it'll see that the GC has already started.
+ mIsRequestingGC = false;
+
+ if (!mIsDoingGC) {
+ if (CanSend()) {
+ SendStartedGC();
+ }
+ mIsDoingGC = true;
+ }
+}
+
+void IdleSchedulerChild::DoneGC() {
+ if (mIsDoingGC) {
+ if (CanSend()) {
+ SendDoneGC();
+ }
+ mIsDoingGC = false;
+ }
+}
+
+IdleSchedulerChild* IdleSchedulerChild::GetMainThreadIdleScheduler() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (sMainThreadIdleScheduler) {
+ return sMainThreadIdleScheduler;
+ }
+
+ if (sIdleSchedulerDestroyed) {
+ return nullptr;
+ }
+
+ ipc::PBackgroundChild* background =
+ ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (background) {
+ sMainThreadIdleScheduler = new ipc::IdleSchedulerChild();
+ background->SendPIdleSchedulerConstructor(sMainThreadIdleScheduler);
+ }
+ return sMainThreadIdleScheduler;
+}
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/IdleSchedulerChild.h b/ipc/glue/IdleSchedulerChild.h
new file mode 100644
index 0000000000..cf93084bfc
--- /dev/null
+++ b/ipc/glue/IdleSchedulerChild.h
@@ -0,0 +1,75 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_IdleSchedulerChild_h__
+#define mozilla_ipc_IdleSchedulerChild_h__
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ipc/PIdleSchedulerChild.h"
+
+class nsIIdlePeriod;
+
+namespace mozilla {
+class IdlePeriodState;
+
+namespace ipc {
+
+class BackgroundChildImpl;
+
+class IdleSchedulerChild final : public PIdleSchedulerChild {
+ public:
+ IdleSchedulerChild() = default;
+
+ NS_INLINE_DECL_REFCOUNTING(IdleSchedulerChild)
+
+ IPCResult RecvIdleTime(uint64_t aId, TimeDuration aBudget);
+
+ void Init(IdlePeriodState* aIdlePeriodState);
+
+ void Disconnect() { mIdlePeriodState = nullptr; }
+
+ // See similar methods on PrioritizedEventQueue.
+ void SetActive();
+ // Returns true if activity state dropped below cpu count.
+ bool SetPaused();
+
+ typedef MozPromise<bool, ResponseRejectReason, true> MayGCPromise;
+
+ // Returns null if a GC or GC request is already in progress.
+ RefPtr<MayGCPromise> MayGCNow();
+
+ // Regardless of how a GC is started we get informed via these.
+ void StartedGC();
+ void DoneGC();
+
+ // Returns nullptr if this is the parent process or the IdleSchedulerChild has
+ // already been destroyed, eg if IPC is shutting down.
+ static IdleSchedulerChild* GetMainThreadIdleScheduler();
+
+ private:
+ ~IdleSchedulerChild();
+
+ friend class BackgroundChildImpl;
+
+ // See IdleScheduleParent::sActiveChildCounter
+ base::SharedMemory mActiveCounter;
+
+ IdlePeriodState* mIdlePeriodState = nullptr;
+
+ uint32_t mChildId = 0;
+
+ // These fields replicate those in IdleSchedulerParent. Tracking them here
+ // ensures we don't send confusing information to the parent, while
+ // nsJSEnvironment is free to tell us about any GCs.
+ bool mIsRequestingGC = false;
+ bool mIsDoingGC = false;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_IdleSchedulerChild_h__
diff --git a/ipc/glue/IdleSchedulerParent.cpp b/ipc/glue/IdleSchedulerParent.cpp
new file mode 100644
index 0000000000..c5cf0b3b20
--- /dev/null
+++ b/ipc/glue/IdleSchedulerParent.cpp
@@ -0,0 +1,467 @@
+/* -*- 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 "mozilla/StaticPrefs_page_load.h"
+#include "mozilla/StaticPrefs_javascript.h"
+#include "mozilla/Unused.h"
+#include "mozilla/ipc/IdleSchedulerParent.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/Telemetry.h"
+#include "nsSystemInfo.h"
+#include "nsThreadUtils.h"
+#include "nsITimer.h"
+#include "nsIThread.h"
+
+namespace mozilla::ipc {
+
+base::SharedMemory* IdleSchedulerParent::sActiveChildCounter = nullptr;
+std::bitset<NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT>
+ IdleSchedulerParent::sInUseChildCounters;
+LinkedList<IdleSchedulerParent> IdleSchedulerParent::sIdleAndGCRequests;
+int32_t IdleSchedulerParent::sMaxConcurrentIdleTasksInChildProcesses = 1;
+uint32_t IdleSchedulerParent::sMaxConcurrentGCs = 1;
+uint32_t IdleSchedulerParent::sActiveGCs = 0;
+bool IdleSchedulerParent::sRecordGCTelemetry = false;
+uint32_t IdleSchedulerParent::sNumWaitingGC = 0;
+uint32_t IdleSchedulerParent::sChildProcessesRunningPrioritizedOperation = 0;
+uint32_t IdleSchedulerParent::sChildProcessesAlive = 0;
+nsITimer* IdleSchedulerParent::sStarvationPreventer = nullptr;
+
+uint32_t IdleSchedulerParent::sNumCPUs = 0;
+uint32_t IdleSchedulerParent::sPrefConcurrentGCsMax = 0;
+uint32_t IdleSchedulerParent::sPrefConcurrentGCsCPUDivisor = 0;
+
+IdleSchedulerParent::IdleSchedulerParent() {
+ MOZ_ASSERT(!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads));
+
+ sChildProcessesAlive++;
+
+ uint32_t max_gcs_pref =
+ StaticPrefs::javascript_options_concurrent_multiprocess_gcs_max();
+ uint32_t cpu_divisor_pref =
+ StaticPrefs::javascript_options_concurrent_multiprocess_gcs_cpu_divisor();
+ if (!max_gcs_pref) {
+ max_gcs_pref = UINT32_MAX;
+ }
+ if (!cpu_divisor_pref) {
+ cpu_divisor_pref = 4;
+ }
+
+ if (!sNumCPUs) {
+ // While waiting for the real logical core count behave as if there was
+ // just one core.
+ sNumCPUs = 1;
+
+ // nsISystemInfo can be initialized only on the main thread.
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction("cpucount getter", [thread]() {
+ ProcessInfo processInfo = {};
+ if (NS_SUCCEEDED(CollectProcessInfo(processInfo))) {
+ uint32_t num_cpus = processInfo.cpuCount;
+ // We have a new cpu count, Update the number of idle tasks.
+ if (MOZ_LIKELY(!AppShutdown::IsInOrBeyond(
+ ShutdownPhase::XPCOMShutdownThreads))) {
+ nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
+ "IdleSchedulerParent::CalculateNumIdleTasks", [num_cpus]() {
+ // We're setting this within this lambda because it's run on
+ // the correct thread and avoids a race.
+ sNumCPUs = num_cpus;
+
+ // This reads the sPrefConcurrentGCsMax and
+ // sPrefConcurrentGCsCPUDivisor values set below, it will
+ // run after the code that sets those.
+ CalculateNumIdleTasks();
+ });
+
+ thread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ }
+ }
+ });
+ NS_DispatchBackgroundTask(runnable.forget(), NS_DISPATCH_EVENT_MAY_BLOCK);
+ }
+
+ if (sPrefConcurrentGCsMax != max_gcs_pref ||
+ sPrefConcurrentGCsCPUDivisor != cpu_divisor_pref) {
+ // We execute this if these preferences have changed. We also want to make
+ // sure it executes for the first IdleSchedulerParent, which it does because
+ // sPrefConcurrentGCsMax and sPrefConcurrentGCsCPUDivisor are initially
+ // zero.
+ sPrefConcurrentGCsMax = max_gcs_pref;
+ sPrefConcurrentGCsCPUDivisor = cpu_divisor_pref;
+
+ CalculateNumIdleTasks();
+ }
+}
+
+void IdleSchedulerParent::CalculateNumIdleTasks() {
+ MOZ_ASSERT(sNumCPUs);
+ MOZ_ASSERT(sPrefConcurrentGCsMax);
+ MOZ_ASSERT(sPrefConcurrentGCsCPUDivisor);
+
+ // On one and two processor (or hardware thread) systems this will
+ // allow one concurrent idle task.
+ sMaxConcurrentIdleTasksInChildProcesses = int32_t(std::max(sNumCPUs, 1u));
+ sMaxConcurrentGCs =
+ std::min(std::max(sNumCPUs / sPrefConcurrentGCsCPUDivisor, 1u),
+ sPrefConcurrentGCsMax);
+
+ if (sActiveChildCounter && sActiveChildCounter->memory()) {
+ static_cast<Atomic<int32_t>*>(
+ sActiveChildCounter->memory())[NS_IDLE_SCHEDULER_INDEX_OF_CPU_COUNTER] =
+ static_cast<int32_t>(sMaxConcurrentIdleTasksInChildProcesses);
+ }
+ IdleSchedulerParent::Schedule(nullptr);
+}
+
+IdleSchedulerParent::~IdleSchedulerParent() {
+ // We can't know if an active process just crashed, so we just always expect
+ // that is the case.
+ if (mChildId) {
+ sInUseChildCounters[mChildId] = false;
+ if (sActiveChildCounter && sActiveChildCounter->memory() &&
+ static_cast<Atomic<int32_t>*>(
+ sActiveChildCounter->memory())[mChildId]) {
+ --static_cast<Atomic<int32_t>*>(
+ sActiveChildCounter
+ ->memory())[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER];
+ static_cast<Atomic<int32_t>*>(sActiveChildCounter->memory())[mChildId] =
+ 0;
+ }
+ }
+
+ if (mRunningPrioritizedOperation) {
+ --sChildProcessesRunningPrioritizedOperation;
+ }
+
+ if (mDoingGC) {
+ // Give back our GC token.
+ sActiveGCs--;
+ }
+
+ if (mRequestingGC) {
+ mRequestingGC.value()(false);
+ mRequestingGC = Nothing();
+ }
+
+ // Remove from the scheduler's queue.
+ if (isInList()) {
+ remove();
+ }
+
+ MOZ_ASSERT(sChildProcessesAlive > 0);
+ sChildProcessesAlive--;
+ if (sChildProcessesAlive == 0) {
+ MOZ_ASSERT(sIdleAndGCRequests.isEmpty());
+ delete sActiveChildCounter;
+ sActiveChildCounter = nullptr;
+
+ if (sStarvationPreventer) {
+ sStarvationPreventer->Cancel();
+ NS_RELEASE(sStarvationPreventer);
+ }
+ }
+
+ Schedule(nullptr);
+}
+
+IPCResult IdleSchedulerParent::RecvInitForIdleUse(
+ InitForIdleUseResolver&& aResolve) {
+ // This must already be non-zero, if it is zero then the cleanup code for the
+ // shared memory (initialised below) will never run. The invariant is that if
+ // the shared memory is initialsed, then this is non-zero.
+ MOZ_ASSERT(sChildProcessesAlive > 0);
+
+ MOZ_ASSERT(IsNotDoingIdleTask());
+
+ // Create a shared memory object which is shared across all the relevant
+ // processes.
+ if (!sActiveChildCounter) {
+ sActiveChildCounter = new base::SharedMemory();
+ size_t shmemSize = NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT * sizeof(int32_t);
+ if (sActiveChildCounter->Create(shmemSize) &&
+ sActiveChildCounter->Map(shmemSize)) {
+ memset(sActiveChildCounter->memory(), 0, shmemSize);
+ sInUseChildCounters[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER] = true;
+ sInUseChildCounters[NS_IDLE_SCHEDULER_INDEX_OF_CPU_COUNTER] = true;
+ static_cast<Atomic<int32_t>*>(
+ sActiveChildCounter
+ ->memory())[NS_IDLE_SCHEDULER_INDEX_OF_CPU_COUNTER] =
+ static_cast<int32_t>(sMaxConcurrentIdleTasksInChildProcesses);
+ } else {
+ delete sActiveChildCounter;
+ sActiveChildCounter = nullptr;
+ }
+ }
+ Maybe<SharedMemoryHandle> activeCounter;
+ if (SharedMemoryHandle handle =
+ sActiveChildCounter ? sActiveChildCounter->CloneHandle() : nullptr) {
+ activeCounter.emplace(std::move(handle));
+ }
+
+ uint32_t unusedId = 0;
+ for (uint32_t i = 0; i < NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT; ++i) {
+ if (!sInUseChildCounters[i]) {
+ sInUseChildCounters[i] = true;
+ unusedId = i;
+ break;
+ }
+ }
+
+ // If there wasn't an empty item, we'll fallback to 0.
+ mChildId = unusedId;
+
+ aResolve(std::tuple<mozilla::Maybe<SharedMemoryHandle>&&, const uint32_t&>(
+ std::move(activeCounter), mChildId));
+ return IPC_OK();
+}
+
+IPCResult IdleSchedulerParent::RecvRequestIdleTime(uint64_t aId,
+ TimeDuration aBudget) {
+ MOZ_ASSERT(aBudget);
+ MOZ_ASSERT(IsNotDoingIdleTask());
+
+ mCurrentRequestId = aId;
+ mRequestedIdleBudget = aBudget;
+
+ if (!isInList()) {
+ sIdleAndGCRequests.insertBack(this);
+ }
+
+ Schedule(this);
+ return IPC_OK();
+}
+
+IPCResult IdleSchedulerParent::RecvIdleTimeUsed(uint64_t aId) {
+ // The client can either signal that they've used the idle time or they're
+ // canceling the request. We cannot use a seperate cancel message because it
+ // could arrive after the parent has granted the request.
+ MOZ_ASSERT(IsWaitingForIdle() || IsDoingIdleTask());
+
+ // The parent process will always know the ID of the current request (since
+ // the IPC channel is reliable). The IDs are provided so that the client can
+ // check them (it's possible for the client to race ahead of the server).
+ MOZ_ASSERT(mCurrentRequestId == aId);
+
+ if (IsWaitingForIdle() && !mRequestingGC) {
+ remove();
+ }
+ mRequestedIdleBudget = TimeDuration();
+ Schedule(nullptr);
+ return IPC_OK();
+}
+
+IPCResult IdleSchedulerParent::RecvSchedule() {
+ Schedule(nullptr);
+ return IPC_OK();
+}
+
+IPCResult IdleSchedulerParent::RecvRunningPrioritizedOperation() {
+ ++mRunningPrioritizedOperation;
+ if (mRunningPrioritizedOperation == 1) {
+ ++sChildProcessesRunningPrioritizedOperation;
+ }
+ return IPC_OK();
+}
+
+IPCResult IdleSchedulerParent::RecvPrioritizedOperationDone() {
+ MOZ_ASSERT(mRunningPrioritizedOperation);
+
+ --mRunningPrioritizedOperation;
+ if (mRunningPrioritizedOperation == 0) {
+ --sChildProcessesRunningPrioritizedOperation;
+ Schedule(nullptr);
+ }
+ return IPC_OK();
+}
+
+IPCResult IdleSchedulerParent::RecvRequestGC(RequestGCResolver&& aResolver) {
+ MOZ_ASSERT(!mDoingGC);
+ MOZ_ASSERT(!mRequestingGC);
+
+ mRequestingGC = Some(aResolver);
+ if (!isInList()) {
+ sIdleAndGCRequests.insertBack(this);
+ }
+
+ sRecordGCTelemetry = true;
+ sNumWaitingGC++;
+ Schedule(nullptr);
+ return IPC_OK();
+}
+
+IPCResult IdleSchedulerParent::RecvStartedGC() {
+ if (mDoingGC) {
+ return IPC_OK();
+ }
+
+ mDoingGC = true;
+ sActiveGCs++;
+
+ if (mRequestingGC) {
+ sNumWaitingGC--;
+ // We have to respond to the request before dropping it, even though the
+ // content process is already doing the GC.
+ mRequestingGC.value()(true);
+ mRequestingGC = Nothing();
+ if (!IsWaitingForIdle()) {
+ remove();
+ }
+ sRecordGCTelemetry = true;
+ }
+
+ return IPC_OK();
+}
+
+IPCResult IdleSchedulerParent::RecvDoneGC() {
+ MOZ_ASSERT(mDoingGC);
+ sActiveGCs--;
+ mDoingGC = false;
+ sRecordGCTelemetry = true;
+ Schedule(nullptr);
+ return IPC_OK();
+}
+
+int32_t IdleSchedulerParent::ActiveCount() {
+ if (sActiveChildCounter) {
+ return (static_cast<Atomic<int32_t>*>(
+ sActiveChildCounter
+ ->memory())[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER]);
+ }
+ return 0;
+}
+
+bool IdleSchedulerParent::HasSpareCycles(int32_t aActiveCount) {
+ // We can run a new task if we have a spare core. If we're running a
+ // prioritised operation we halve the number of regular spare cores.
+ //
+ // sMaxConcurrentIdleTasksInChildProcesses will always be >0 so on 1 and 2
+ // core systems this will allow 1 idle tasks (0 if running a prioritized
+ // operation).
+ MOZ_ASSERT(sMaxConcurrentIdleTasksInChildProcesses > 0);
+ return sChildProcessesRunningPrioritizedOperation
+ ? sMaxConcurrentIdleTasksInChildProcesses / 2 > aActiveCount
+ : sMaxConcurrentIdleTasksInChildProcesses > aActiveCount;
+}
+
+bool IdleSchedulerParent::HasSpareGCCycles() {
+ return sMaxConcurrentGCs > sActiveGCs;
+}
+
+void IdleSchedulerParent::SendIdleTime() {
+ // We would assert that IsWaitingForIdle() except after potentially removing
+ // the task from it's list this will return false. Instead check
+ // mRequestedIdleBudget.
+ MOZ_ASSERT(mRequestedIdleBudget);
+ Unused << SendIdleTime(mCurrentRequestId, mRequestedIdleBudget);
+}
+
+void IdleSchedulerParent::SendMayGC() {
+ MOZ_ASSERT(mRequestingGC);
+ mRequestingGC.value()(true);
+ mRequestingGC = Nothing();
+ mDoingGC = true;
+ sActiveGCs++;
+ sRecordGCTelemetry = true;
+ MOZ_ASSERT(sNumWaitingGC > 0);
+ sNumWaitingGC--;
+}
+
+void IdleSchedulerParent::Schedule(IdleSchedulerParent* aRequester) {
+ // Tasks won't update the active count until after they receive their message
+ // and start to run, so make a copy of it here and increment it for every task
+ // we schedule. It will become an estimate of how many tasks will be active
+ // shortly.
+ int32_t activeCount = ActiveCount();
+
+ if (aRequester && aRequester->mRunningPrioritizedOperation) {
+ // Prioritised operations are requested only for idle time requests, so this
+ // must be an idle time request.
+ MOZ_ASSERT(aRequester->IsWaitingForIdle());
+
+ // If the requester is prioritized, just let it run itself.
+ if (aRequester->isInList() && !aRequester->mRequestingGC) {
+ aRequester->remove();
+ }
+ aRequester->SendIdleTime();
+ activeCount++;
+ }
+
+ RefPtr<IdleSchedulerParent> idleRequester = sIdleAndGCRequests.getFirst();
+
+ bool has_spare_cycles = HasSpareCycles(activeCount);
+ bool has_spare_gc_cycles = HasSpareGCCycles();
+
+ while (idleRequester && (has_spare_cycles || has_spare_gc_cycles)) {
+ // Get the next element before potentially removing the current one from the
+ // list.
+ RefPtr<IdleSchedulerParent> next = idleRequester->getNext();
+
+ if (has_spare_cycles && idleRequester->IsWaitingForIdle()) {
+ // We can run an idle task.
+ activeCount++;
+ if (!idleRequester->mRequestingGC) {
+ idleRequester->remove();
+ }
+ idleRequester->SendIdleTime();
+ has_spare_cycles = HasSpareCycles(activeCount);
+ }
+
+ if (has_spare_gc_cycles && idleRequester->mRequestingGC) {
+ if (!idleRequester->IsWaitingForIdle()) {
+ idleRequester->remove();
+ }
+ idleRequester->SendMayGC();
+ has_spare_gc_cycles = HasSpareGCCycles();
+ }
+
+ idleRequester = next;
+ }
+
+ if (!sIdleAndGCRequests.isEmpty() && HasSpareCycles(activeCount)) {
+ EnsureStarvationTimer();
+ }
+
+ if (sRecordGCTelemetry) {
+ sRecordGCTelemetry = false;
+ Telemetry::Accumulate(Telemetry::GC_WAIT_FOR_IDLE_COUNT, sNumWaitingGC);
+ }
+}
+
+void IdleSchedulerParent::EnsureStarvationTimer() {
+ // Even though idle runnables aren't really guaranteed to get run ever (which
+ // is why most of them have the timer fallback), try to not let any child
+ // process' idle handling to starve forever in case other processes are busy
+ if (!sStarvationPreventer) {
+ // Reuse StaticPrefs::page_load_deprioritization_period(), since that
+ // is used on child side when deciding the minimum idle period.
+ NS_NewTimerWithFuncCallback(
+ &sStarvationPreventer, StarvationCallback, nullptr,
+ StaticPrefs::page_load_deprioritization_period(),
+ nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "StarvationCallback");
+ }
+}
+
+void IdleSchedulerParent::StarvationCallback(nsITimer* aTimer, void* aData) {
+ RefPtr<IdleSchedulerParent> idleRequester = sIdleAndGCRequests.getFirst();
+ while (idleRequester) {
+ if (idleRequester->IsWaitingForIdle()) {
+ // Treat the first process waiting for idle time as running prioritized
+ // operation so that it gets run.
+ ++idleRequester->mRunningPrioritizedOperation;
+ ++sChildProcessesRunningPrioritizedOperation;
+ Schedule(idleRequester);
+ --idleRequester->mRunningPrioritizedOperation;
+ --sChildProcessesRunningPrioritizedOperation;
+ break;
+ }
+
+ idleRequester = idleRequester->getNext();
+ }
+ NS_RELEASE(sStarvationPreventer);
+}
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/IdleSchedulerParent.h b/ipc/glue/IdleSchedulerParent.h
new file mode 100644
index 0000000000..62cf6fb772
--- /dev/null
+++ b/ipc/glue/IdleSchedulerParent.h
@@ -0,0 +1,137 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_IdleSchedulerParent_h__
+#define mozilla_ipc_IdleSchedulerParent_h__
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/ipc/PIdleSchedulerParent.h"
+#include "base/shared_memory.h"
+#include <bitset>
+
+#define NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT 1024
+#define NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER 0
+#define NS_IDLE_SCHEDULER_INDEX_OF_CPU_COUNTER 1
+
+class nsITimer;
+
+namespace mozilla {
+
+namespace ipc {
+
+class BackgroundParentImpl;
+
+class IdleSchedulerParent final
+ : public PIdleSchedulerParent,
+ public LinkedListElement<IdleSchedulerParent> {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(IdleSchedulerParent)
+
+ IPCResult RecvInitForIdleUse(InitForIdleUseResolver&& aResolve);
+ IPCResult RecvRequestIdleTime(uint64_t aId, TimeDuration aBudget);
+ IPCResult RecvIdleTimeUsed(uint64_t aId);
+ IPCResult RecvSchedule();
+ IPCResult RecvRunningPrioritizedOperation();
+ IPCResult RecvPrioritizedOperationDone();
+ IPCResult RecvRequestGC(RequestGCResolver&& aResolve);
+ IPCResult RecvStartedGC();
+ IPCResult RecvDoneGC();
+
+ private:
+ friend class BackgroundParentImpl;
+ IdleSchedulerParent();
+ ~IdleSchedulerParent();
+
+ static void CalculateNumIdleTasks();
+
+ static int32_t ActiveCount();
+ static void Schedule(IdleSchedulerParent* aRequester);
+ static bool HasSpareCycles(int32_t aActiveCount);
+ static bool HasSpareGCCycles();
+ using PIdleSchedulerParent::SendIdleTime;
+ void SendIdleTime();
+ void SendMayGC();
+
+ static void EnsureStarvationTimer();
+ static void StarvationCallback(nsITimer* aTimer, void* aData);
+
+ uint64_t mCurrentRequestId = 0;
+ // For now we don't really use idle budget for scheduling. Zero if the
+ // process isn't requestiong or running an idle task.
+ TimeDuration mRequestedIdleBudget;
+
+ // Counting all the prioritized operations the process is doing.
+ uint32_t mRunningPrioritizedOperation = 0;
+
+ // Only one of these may be true at a time, giving three states:
+ // No active GC request, A pending GC request, or a granted GC request.
+ Maybe<RequestGCResolver> mRequestingGC;
+ bool mDoingGC = false;
+
+ uint32_t mChildId = 0;
+
+ // Current state, only one of these may be true at a time.
+ bool IsWaitingForIdle() const { return isInList() && mRequestedIdleBudget; }
+ bool IsDoingIdleTask() const { return !isInList() && mRequestedIdleBudget; }
+ bool IsNotDoingIdleTask() const { return !mRequestedIdleBudget; }
+
+ // Shared memory for counting how many child processes are running
+ // tasks. This memory is shared across all the child processes.
+ // The [0] is used for counting all the processes and
+ // [childId] is for counting per process activity.
+ // This way the global activity can be checked in a fast way by just looking
+ // at [0] value.
+ // [1] is used for cpu count for child processes.
+ static base::SharedMemory* sActiveChildCounter;
+ // A bit is set if there is a child with child Id as the offset.
+ // The bit is used to check per child specific activity counters in
+ // sActiveChildCounter.
+ static std::bitset<NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT>
+ sInUseChildCounters;
+
+ // Processes on this list have requested (but the request hasn't yet been
+ // granted) idle time or to start a GC or both.
+ //
+ // Either or both their mRequestedIdleBudget or mRequestingGC fields are
+ // non-zero. Child processes not on this list have either been granted all
+ // their requests not made a request ever or since they last finished an idle
+ // or GC task.
+ //
+ // Use the methods above to determine a process' idle time state, or check the
+ // mRequestingGC and mDoingGC fields for the GC state.
+ static LinkedList<IdleSchedulerParent> sIdleAndGCRequests;
+
+ static int32_t sMaxConcurrentIdleTasksInChildProcesses;
+ static uint32_t sMaxConcurrentGCs;
+ static uint32_t sActiveGCs;
+
+ // True if we should record some telemetry for GCs in the next Schedule().
+ // This is set to true by either requesting a GC job or scheduling a GC job.
+ static bool sRecordGCTelemetry;
+ // The current number of waiting GCs.
+ static uint32_t sNumWaitingGC;
+
+ // Counting all the child processes which have at least one prioritized
+ // operation.
+ static uint32_t sChildProcessesRunningPrioritizedOperation;
+
+ // When this hits zero, it's time to free the shared memory and pack up.
+ static uint32_t sChildProcessesAlive;
+
+ static nsITimer* sStarvationPreventer;
+
+ static uint32_t sNumCPUs;
+ static uint32_t sPrefConcurrentGCsMax;
+ static uint32_t sPrefConcurrentGCsCPUDivisor;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_IdleSchedulerParent_h__
diff --git a/ipc/glue/InputStreamParams.ipdlh b/ipc/glue/InputStreamParams.ipdlh
new file mode 100644
index 0000000000..8d1f727eca
--- /dev/null
+++ b/ipc/glue/InputStreamParams.ipdlh
@@ -0,0 +1,101 @@
+/* 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 ProtocolTypes;
+
+using struct mozilla::void_t from "mozilla/ipc/IPCCore.h";
+[RefCounted] using mozilla::ipc::DataPipeReceiver from "mozilla/ipc/DataPipe.h";
+[RefCounted] using mozilla::RemoteLazyInputStream from "mozilla/RemoteLazyInputStream.h";
+
+namespace mozilla {
+namespace ipc {
+
+struct HeaderEntry
+{
+ nsCString name;
+ nsCString value;
+};
+
+struct StringInputStreamParams
+{
+ nsCString data;
+};
+
+struct FileInputStreamParams
+{
+ FileDescriptor fileDescriptor;
+ int32_t behaviorFlags;
+ int32_t ioFlags;
+};
+
+struct MultiplexInputStreamParams
+{
+ InputStreamParams[] streams;
+ uint32_t currentStream;
+ nsresult status;
+ bool startedReadingCurrent;
+};
+
+struct SlicedInputStreamParams
+{
+ InputStreamParams stream;
+ uint64_t start;
+ uint64_t length;
+ uint64_t curPos;
+ bool closed;
+};
+
+struct RemoteLazyInputStreamParams
+{
+ nullable RemoteLazyInputStream stream;
+};
+
+struct DataPipeReceiverStreamParams
+{
+ nullable DataPipeReceiver pipe;
+};
+
+union InputStreamParams
+{
+ StringInputStreamParams;
+ FileInputStreamParams;
+ BufferedInputStreamParams;
+ MIMEInputStreamParams;
+ MultiplexInputStreamParams;
+ SlicedInputStreamParams;
+ RemoteLazyInputStreamParams;
+ InputStreamLengthWrapperParams;
+ EncryptedFileInputStreamParams;
+ DataPipeReceiverStreamParams;
+};
+
+struct EncryptedFileInputStreamParams
+{
+ FileInputStreamParams fileInputStreamParams;
+ uint8_t[] key;
+ uint32_t blockSize;
+};
+
+struct BufferedInputStreamParams
+{
+ InputStreamParams? optionalStream;
+ uint32_t bufferSize;
+};
+
+struct MIMEInputStreamParams
+{
+ InputStreamParams? optionalStream;
+ HeaderEntry[] headers;
+ bool startedReading;
+};
+
+struct InputStreamLengthWrapperParams
+{
+ InputStreamParams stream;
+ int64_t length;
+ bool consumed;
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/InputStreamUtils.cpp b/ipc/glue/InputStreamUtils.cpp
new file mode 100644
index 0000000000..888afce859
--- /dev/null
+++ b/ipc/glue/InputStreamUtils.cpp
@@ -0,0 +1,211 @@
+/* -*- 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 "InputStreamUtils.h"
+
+#include "nsIIPCSerializableInputStream.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/quota/DecryptingInputStream_impl.h"
+#include "mozilla/dom/quota/IPCStreamCipherStrategy.h"
+#include "mozilla/ipc/DataPipe.h"
+#include "mozilla/InputStreamLengthHelper.h"
+#include "mozilla/RemoteLazyInputStream.h"
+#include "mozilla/RemoteLazyInputStreamChild.h"
+#include "mozilla/RemoteLazyInputStreamStorage.h"
+#include "mozilla/SlicedInputStream.h"
+#include "mozilla/InputStreamLengthWrapper.h"
+#include "nsBufferedStreams.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDebug.h"
+#include "nsFileStreams.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsID.h"
+#include "nsIMIMEInputStream.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsIPipe.h"
+#include "nsMIMEInputStream.h"
+#include "nsMultiplexInputStream.h"
+#include "nsNetCID.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsXULAppAPI.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace ipc {
+
+void InputStreamHelper::SerializedComplexity(nsIInputStream* aInputStream,
+ uint32_t aMaxSize,
+ uint32_t* aSizeUsed,
+ uint32_t* aPipes,
+ uint32_t* aTransferables) {
+ MOZ_ASSERT(aInputStream);
+
+ nsCOMPtr<nsIIPCSerializableInputStream> serializable =
+ do_QueryInterface(aInputStream);
+ if (!serializable) {
+ MOZ_CRASH("Input stream is not serializable!");
+ }
+
+ serializable->SerializedComplexity(aMaxSize, aSizeUsed, aPipes,
+ aTransferables);
+}
+
+void InputStreamHelper::SerializeInputStream(nsIInputStream* aInputStream,
+ InputStreamParams& aParams,
+ uint32_t aMaxSize,
+ uint32_t* aSizeUsed) {
+ MOZ_ASSERT(aInputStream);
+
+ nsCOMPtr<nsIIPCSerializableInputStream> serializable =
+ do_QueryInterface(aInputStream);
+ if (!serializable) {
+ MOZ_CRASH("Input stream is not serializable!");
+ }
+
+ serializable->Serialize(aParams, aMaxSize, aSizeUsed);
+
+ if (aParams.type() == InputStreamParams::T__None) {
+ MOZ_CRASH("Serialize failed!");
+ }
+}
+
+void InputStreamHelper::SerializeInputStreamAsPipe(nsIInputStream* aInputStream,
+ InputStreamParams& aParams) {
+ MOZ_ASSERT(aInputStream);
+
+ // Let's try to take the length using InputStreamLengthHelper. If the length
+ // cannot be taken synchronously, and its length is needed, the stream needs
+ // to be fully copied in memory on the deserialization side.
+ int64_t length;
+ if (!InputStreamLengthHelper::GetSyncLength(aInputStream, &length)) {
+ length = -1;
+ }
+
+ RefPtr<DataPipeSender> sender;
+ RefPtr<DataPipeReceiver> receiver;
+ nsresult rv = NewDataPipe(kDefaultDataPipeCapacity, getter_AddRefs(sender),
+ getter_AddRefs(receiver));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+
+ rv =
+ NS_AsyncCopy(aInputStream, sender, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS,
+ kDefaultDataPipeCapacity, nullptr, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ aParams = DataPipeReceiverStreamParams(receiver);
+ if (length != -1) {
+ aParams = InputStreamLengthWrapperParams(aParams, length, false);
+ }
+}
+
+already_AddRefed<nsIInputStream> InputStreamHelper::DeserializeInputStream(
+ const InputStreamParams& aParams) {
+ if (aParams.type() == InputStreamParams::TRemoteLazyInputStreamParams) {
+ const RemoteLazyInputStreamParams& params =
+ aParams.get_RemoteLazyInputStreamParams();
+
+ // If the RemoteLazyInputStream already has an internal stream, unwrap it.
+ // This is required as some code unfortunately depends on the precise
+ // topology of received streams, and cannot handle being passed a
+ // `RemoteLazyInputStream` in the parent process.
+ nsCOMPtr<nsIInputStream> innerStream;
+ if (XRE_IsParentProcess() &&
+ NS_SUCCEEDED(
+ params.stream()->TakeInternalStream(getter_AddRefs(innerStream)))) {
+ return innerStream.forget();
+ }
+ return do_AddRef(params.stream());
+ }
+
+ if (aParams.type() == InputStreamParams::TDataPipeReceiverStreamParams) {
+ const DataPipeReceiverStreamParams& pipeParams =
+ aParams.get_DataPipeReceiverStreamParams();
+ return do_AddRef(pipeParams.pipe());
+ }
+
+ nsCOMPtr<nsIIPCSerializableInputStream> serializable;
+
+ switch (aParams.type()) {
+ case InputStreamParams::TStringInputStreamParams: {
+ nsCOMPtr<nsIInputStream> stream;
+ NS_NewCStringInputStream(getter_AddRefs(stream), ""_ns);
+ serializable = do_QueryInterface(stream);
+ } break;
+
+ case InputStreamParams::TFileInputStreamParams: {
+ nsCOMPtr<nsIFileInputStream> stream;
+ nsFileInputStream::Create(NS_GET_IID(nsIFileInputStream),
+ getter_AddRefs(stream));
+ serializable = do_QueryInterface(stream);
+ } break;
+
+ case InputStreamParams::TBufferedInputStreamParams: {
+ nsCOMPtr<nsIBufferedInputStream> stream;
+ nsBufferedInputStream::Create(NS_GET_IID(nsIBufferedInputStream),
+ getter_AddRefs(stream));
+ serializable = do_QueryInterface(stream);
+ } break;
+
+ case InputStreamParams::TMIMEInputStreamParams: {
+ nsCOMPtr<nsIMIMEInputStream> stream;
+ nsMIMEInputStreamConstructor(NS_GET_IID(nsIMIMEInputStream),
+ getter_AddRefs(stream));
+ serializable = do_QueryInterface(stream);
+ } break;
+
+ case InputStreamParams::TMultiplexInputStreamParams: {
+ nsCOMPtr<nsIMultiplexInputStream> stream;
+ nsMultiplexInputStreamConstructor(NS_GET_IID(nsIMultiplexInputStream),
+ getter_AddRefs(stream));
+ serializable = do_QueryInterface(stream);
+ } break;
+
+ case InputStreamParams::TSlicedInputStreamParams:
+ serializable = new SlicedInputStream();
+ break;
+
+ case InputStreamParams::TInputStreamLengthWrapperParams:
+ serializable = new InputStreamLengthWrapper();
+ break;
+
+ case InputStreamParams::TEncryptedFileInputStreamParams:
+ serializable = new dom::quota::DecryptingInputStream<
+ dom::quota::IPCStreamCipherStrategy>();
+ break;
+
+ default:
+ MOZ_ASSERT(false, "Unknown params!");
+ return nullptr;
+ }
+
+ MOZ_ASSERT(serializable);
+
+ if (!serializable->Deserialize(aParams)) {
+ MOZ_ASSERT(false, "Deserialize failed!");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIInputStream> stream = do_QueryInterface(serializable);
+ MOZ_ASSERT(stream);
+
+ return stream.forget();
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/InputStreamUtils.h b/ipc/glue/InputStreamUtils.h
new file mode 100644
index 0000000000..27a65b02e2
--- /dev/null
+++ b/ipc/glue/InputStreamUtils.h
@@ -0,0 +1,50 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_InputStreamUtils_h
+#define mozilla_ipc_InputStreamUtils_h
+
+#include "mozilla/ipc/InputStreamParams.h"
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace ipc {
+
+class FileDescriptor;
+
+// If you want to serialize an inputStream, please use SerializeIPCStream or
+// nsIInputStream directly.
+class InputStreamHelper {
+ public:
+ static void SerializedComplexity(nsIInputStream* aInputStream,
+ uint32_t aMaxSize, uint32_t* aSizeUsed,
+ uint32_t* aPipes, uint32_t* aTransferables);
+
+ // These 2 methods allow to serialize an inputStream into InputStreamParams.
+ // The manager is needed in case a stream needs to serialize itself as
+ // IPCRemoteStream.
+ // The stream serializes itself fully only if the resulting IPC message will
+ // be smaller than |aMaxSize|. Otherwise, the stream serializes itself as a
+ // DataPipe, and, its content will be sent to the other side of the IPC pipe
+ // in chunks. The IPC message size is returned into |aSizeUsed|.
+ static void SerializeInputStream(nsIInputStream* aInputStream,
+ InputStreamParams& aParams,
+ uint32_t aMaxSize, uint32_t* aSizeUsed);
+
+ // When a stream wants to serialize itself as a DataPipe, it uses this method.
+ static void SerializeInputStreamAsPipe(nsIInputStream* aInputStream,
+ InputStreamParams& aParams);
+
+ static already_AddRefed<nsIInputStream> DeserializeInputStream(
+ const InputStreamParams& aParams);
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_InputStreamUtils_h
diff --git a/ipc/glue/LaunchError.cpp b/ipc/glue/LaunchError.cpp
new file mode 100644
index 0000000000..04ae34cfa6
--- /dev/null
+++ b/ipc/glue/LaunchError.cpp
@@ -0,0 +1,31 @@
+/* -*- 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 "LaunchError.h"
+
+#if defined(XP_WIN)
+# include "mozilla/WinHeaderOnlyUtils.h"
+#endif
+
+namespace mozilla::ipc {
+
+LaunchError::LaunchError(const char* aFunction, OsError aError)
+ : mFunction(aFunction), mError(aError) {}
+
+#if defined(XP_WIN)
+LaunchError::LaunchError(const char* aFunction, PRErrorCode aError)
+ : mFunction(aFunction), mError((int)aError) {}
+
+LaunchError::LaunchError(const char* aFunction, DWORD aError)
+ : mFunction(aFunction),
+ mError(WindowsError::FromWin32Error(aError).AsHResult()) {}
+#endif // defined(XP_WIN)
+
+const char* LaunchError::FunctionName() const { return mFunction; }
+
+OsError LaunchError::ErrorCode() const { return mError; }
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/LaunchError.h b/ipc/glue/LaunchError.h
new file mode 100644
index 0000000000..efa8ba4de4
--- /dev/null
+++ b/ipc/glue/LaunchError.h
@@ -0,0 +1,40 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_LaunchError_h
+#define mozilla_ipc_LaunchError_h
+
+#include "prerror.h"
+
+#if defined(XP_WIN)
+# include <windows.h>
+# include <winerror.h>
+using OsError = HRESULT;
+#else
+using OsError = int;
+#endif
+
+namespace mozilla::ipc {
+
+class LaunchError {
+ public:
+ explicit LaunchError(const char* aFunction, OsError aError = 0);
+#if defined(XP_WIN)
+ explicit LaunchError(const char* aFunction, PRErrorCode aError);
+ explicit LaunchError(const char* aFunction, DWORD aError);
+#endif // defined(XP_WIN)
+
+ const char* FunctionName() const;
+ OsError ErrorCode() const;
+
+ private:
+ const char* mFunction;
+ OsError mError;
+};
+
+} // namespace mozilla::ipc
+
+#endif // ifndef mozilla_ipc_LaunchError_h
diff --git a/ipc/glue/LibrarySandboxPreload.cpp b/ipc/glue/LibrarySandboxPreload.cpp
new file mode 100644
index 0000000000..0d7889d461
--- /dev/null
+++ b/ipc/glue/LibrarySandboxPreload.cpp
@@ -0,0 +1,45 @@
+/* -*- 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 "LibrarySandboxPreload.h"
+#include "nsXPCOMPrivate.h" // for XPCOM_DLL
+
+#include "BinaryPath.h"
+#include "prlink.h"
+
+namespace mozilla {
+namespace ipc {
+
+PathString GetSandboxedRLBoxPath() {
+ nsCOMPtr<nsIFile> libFile;
+ nsresult rv = mozilla::BinaryPath::GetFile(getter_AddRefs(libFile));
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("Library preload failure: Failed to get binary file\n");
+ }
+
+ rv = libFile->SetNativeLeafName(XPCOM_DLL ""_ns);
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("Library preload failure: Failed to get library file\n");
+ }
+
+ return libFile->NativePath();
+}
+
+PRLibrary* PreloadLibrary(const PathString& path) {
+ PRLibSpec libSpec;
+#ifdef XP_WIN
+ libSpec.type = PR_LibSpec_PathnameU;
+ libSpec.value.pathname_u = path.get();
+#else
+ libSpec.type = PR_LibSpec_Pathname;
+ libSpec.value.pathname = path.get();
+#endif
+ PRLibrary* ret = PR_LoadLibraryWithFlags(libSpec, PR_LD_LAZY);
+ return ret;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/LibrarySandboxPreload.h b/ipc/glue/LibrarySandboxPreload.h
new file mode 100644
index 0000000000..6be91c605b
--- /dev/null
+++ b/ipc/glue/LibrarySandboxPreload.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=4 et :
+ */
+/* 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/. */
+
+#ifndef ipc_glue_LibrarySandboxPreload_h
+#define ipc_glue_LibrarySandboxPreload_h
+
+#include "mozilla/FileUtils.h"
+
+namespace mozilla {
+namespace ipc {
+PathString GetSandboxedRLBoxPath();
+} // namespace ipc
+} // namespace mozilla
+#endif
diff --git a/ipc/glue/MessageChannel.cpp b/ipc/glue/MessageChannel.cpp
new file mode 100644
index 0000000000..ea01c54f0d
--- /dev/null
+++ b/ipc/glue/MessageChannel.cpp
@@ -0,0 +1,2482 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=4 et :
+ */
+/* 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/MessageChannel.h"
+
+#include <math.h>
+
+#include <utility>
+
+#include "CrashAnnotations.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Fuzzing.h"
+#include "mozilla/IntentionalCrash.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/ipc/NodeController.h"
+#include "mozilla/ipc/ProcessChild.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "nsAppRunner.h"
+#include "nsContentUtils.h"
+#include "nsIDirectTaskDispatcher.h"
+#include "nsTHashMap.h"
+#include "nsDebug.h"
+#include "nsExceptionHandler.h"
+#include "nsIMemoryReporter.h"
+#include "nsISupportsImpl.h"
+#include "nsPrintfCString.h"
+#include "nsThreadUtils.h"
+
+#ifdef OS_WIN
+# include "mozilla/gfx/Logging.h"
+#endif
+
+#ifdef FUZZING_SNAPSHOT
+# include "mozilla/fuzzing/IPCFuzzController.h"
+#endif
+
+// Undo the damage done by mozzconf.h
+#undef compress
+
+static mozilla::LazyLogModule sLogModule("ipc");
+#define IPC_LOG(...) MOZ_LOG(sLogModule, LogLevel::Debug, (__VA_ARGS__))
+
+/*
+ * IPC design:
+ *
+ * There are two kinds of messages: async and sync. Sync messages are blocking.
+ *
+ * Terminology: To dispatch a message Foo is to run the RecvFoo code for
+ * it. This is also called "handling" the message.
+ *
+ * Sync and async messages can sometimes "nest" inside other sync messages
+ * (i.e., while waiting for the sync reply, we can dispatch the inner
+ * message). The three possible nesting levels are NOT_NESTED,
+ * NESTED_INSIDE_SYNC, and NESTED_INSIDE_CPOW. The intended uses are:
+ * NOT_NESTED - most messages.
+ * NESTED_INSIDE_SYNC - CPOW-related messages, which are always sync
+ * and can go in either direction.
+ * NESTED_INSIDE_CPOW - messages where we don't want to dispatch
+ * incoming CPOWs while waiting for the response.
+ * These nesting levels are ordered: NOT_NESTED, NESTED_INSIDE_SYNC,
+ * NESTED_INSIDE_CPOW. Async messages cannot be NESTED_INSIDE_SYNC but they can
+ * be NESTED_INSIDE_CPOW.
+ *
+ * To avoid jank, the parent process is not allowed to send NOT_NESTED sync
+ * messages. When a process is waiting for a response to a sync message M0, it
+ * will dispatch an incoming message M if:
+ * 1. M has a higher nesting level than M0, or
+ * 2. if M has the same nesting level as M0 and we're in the child, or
+ * 3. if M has the same nesting level as M0 and it was sent by the other side
+ * while dispatching M0.
+ * The idea is that messages with higher nesting should take precendence. The
+ * purpose of rule 2 is to handle a race where both processes send to each other
+ * simultaneously. In this case, we resolve the race in favor of the parent (so
+ * the child dispatches first).
+ *
+ * Messages satisfy the following properties:
+ * A. When waiting for a response to a sync message, we won't dispatch any
+ * messages of a lower nesting level.
+ * B. Messages of the same nesting level will be dispatched roughly in the
+ * order they were sent. The exception is when the parent and child send
+ * sync messages to each other simulataneously. In this case, the parent's
+ * message is dispatched first. While it is dispatched, the child may send
+ * further nested messages, and these messages may be dispatched before the
+ * child's original message. We can consider ordering to be preserved here
+ * because we pretend that the child's original message wasn't sent until
+ * after the parent's message is finished being dispatched.
+ *
+ * When waiting for a sync message reply, we dispatch an async message only if
+ * it is NESTED_INSIDE_CPOW. Normally NESTED_INSIDE_CPOW async
+ * messages are sent only from the child. However, the parent can send
+ * NESTED_INSIDE_CPOW async messages when it is creating a bridged protocol.
+ */
+
+using namespace mozilla;
+using namespace mozilla::ipc;
+
+using mozilla::MonitorAutoLock;
+using mozilla::MonitorAutoUnlock;
+using mozilla::dom::AutoNoJSAPI;
+
+#define IPC_ASSERT(_cond, ...) \
+ do { \
+ AssertWorkerThread(); \
+ mMonitor->AssertCurrentThreadOwns(); \
+ if (!(_cond)) DebugAbort(__FILE__, __LINE__, #_cond, ##__VA_ARGS__); \
+ } while (0)
+
+static MessageChannel* gParentProcessBlocker = nullptr;
+
+namespace mozilla {
+namespace ipc {
+
+static const uint32_t kMinTelemetryMessageSize = 4096;
+
+// Note: we round the time we spend waiting for a response to the nearest
+// millisecond. So a min value of 1 ms actually captures from 500us and above.
+// This is used for both the sending and receiving side telemetry for sync IPC,
+// (IPC_SYNC_MAIN_LATENCY_MS and IPC_SYNC_RECEIVE_MS).
+static const uint32_t kMinTelemetrySyncIPCLatencyMs = 1;
+
+// static
+bool MessageChannel::sIsPumpingMessages = false;
+
+class AutoEnterTransaction {
+ public:
+ explicit AutoEnterTransaction(MessageChannel* aChan, int32_t aMsgSeqno,
+ int32_t aTransactionID, int aNestedLevel)
+ MOZ_REQUIRES(*aChan->mMonitor)
+ : mChan(aChan),
+ mActive(true),
+ mOutgoing(true),
+ mNestedLevel(aNestedLevel),
+ mSeqno(aMsgSeqno),
+ mTransaction(aTransactionID),
+ mNext(mChan->mTransactionStack) {
+ mChan->mMonitor->AssertCurrentThreadOwns();
+ mChan->mTransactionStack = this;
+ }
+
+ explicit AutoEnterTransaction(MessageChannel* aChan,
+ const IPC::Message& aMessage)
+ MOZ_REQUIRES(*aChan->mMonitor)
+ : mChan(aChan),
+ mActive(true),
+ mOutgoing(false),
+ mNestedLevel(aMessage.nested_level()),
+ mSeqno(aMessage.seqno()),
+ mTransaction(aMessage.transaction_id()),
+ mNext(mChan->mTransactionStack) {
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ if (!aMessage.is_sync()) {
+ mActive = false;
+ return;
+ }
+
+ mChan->mTransactionStack = this;
+ }
+
+ ~AutoEnterTransaction() {
+ mChan->mMonitor->AssertCurrentThreadOwns();
+ if (mActive) {
+ mChan->mTransactionStack = mNext;
+ }
+ }
+
+ void Cancel() {
+ mChan->mMonitor->AssertCurrentThreadOwns();
+ AutoEnterTransaction* cur = mChan->mTransactionStack;
+ MOZ_RELEASE_ASSERT(cur == this);
+ while (cur && cur->mNestedLevel != IPC::Message::NOT_NESTED) {
+ // Note that, in the following situation, we will cancel multiple
+ // transactions:
+ // 1. Parent sends NESTED_INSIDE_SYNC message P1 to child.
+ // 2. Child sends NESTED_INSIDE_SYNC message C1 to child.
+ // 3. Child dispatches P1, parent blocks.
+ // 4. Child cancels.
+ // In this case, both P1 and C1 are cancelled. The parent will
+ // remove C1 from its queue when it gets the cancellation message.
+ MOZ_RELEASE_ASSERT(cur->mActive);
+ cur->mActive = false;
+ cur = cur->mNext;
+ }
+
+ mChan->mTransactionStack = cur;
+
+ MOZ_RELEASE_ASSERT(IsComplete());
+ }
+
+ bool AwaitingSyncReply() const {
+ MOZ_RELEASE_ASSERT(mActive);
+ if (mOutgoing) {
+ return true;
+ }
+ return mNext ? mNext->AwaitingSyncReply() : false;
+ }
+
+ int AwaitingSyncReplyNestedLevel() const {
+ MOZ_RELEASE_ASSERT(mActive);
+ if (mOutgoing) {
+ return mNestedLevel;
+ }
+ return mNext ? mNext->AwaitingSyncReplyNestedLevel() : 0;
+ }
+
+ bool DispatchingSyncMessage() const {
+ MOZ_RELEASE_ASSERT(mActive);
+ if (!mOutgoing) {
+ return true;
+ }
+ return mNext ? mNext->DispatchingSyncMessage() : false;
+ }
+
+ int DispatchingSyncMessageNestedLevel() const {
+ MOZ_RELEASE_ASSERT(mActive);
+ if (!mOutgoing) {
+ return mNestedLevel;
+ }
+ return mNext ? mNext->DispatchingSyncMessageNestedLevel() : 0;
+ }
+
+ int NestedLevel() const {
+ MOZ_RELEASE_ASSERT(mActive);
+ return mNestedLevel;
+ }
+
+ int32_t SequenceNumber() const {
+ MOZ_RELEASE_ASSERT(mActive);
+ return mSeqno;
+ }
+
+ int32_t TransactionID() const {
+ MOZ_RELEASE_ASSERT(mActive);
+ return mTransaction;
+ }
+
+ void ReceivedReply(UniquePtr<IPC::Message> aMessage) {
+ MOZ_RELEASE_ASSERT(aMessage->seqno() == mSeqno);
+ MOZ_RELEASE_ASSERT(aMessage->transaction_id() == mTransaction);
+ MOZ_RELEASE_ASSERT(!mReply);
+ IPC_LOG("Reply received on worker thread: seqno=%d", mSeqno);
+ mReply = std::move(aMessage);
+ MOZ_RELEASE_ASSERT(IsComplete());
+ }
+
+ void HandleReply(UniquePtr<IPC::Message> aMessage) {
+ mChan->mMonitor->AssertCurrentThreadOwns();
+ AutoEnterTransaction* cur = mChan->mTransactionStack;
+ MOZ_RELEASE_ASSERT(cur == this);
+ while (cur) {
+ MOZ_RELEASE_ASSERT(cur->mActive);
+ if (aMessage->seqno() == cur->mSeqno) {
+ cur->ReceivedReply(std::move(aMessage));
+ break;
+ }
+ cur = cur->mNext;
+ MOZ_RELEASE_ASSERT(cur);
+ }
+ }
+
+ bool IsComplete() { return !mActive || mReply; }
+
+ bool IsOutgoing() { return mOutgoing; }
+
+ bool IsCanceled() { return !mActive; }
+
+ bool IsBottom() const { return !mNext; }
+
+ bool IsError() {
+ MOZ_RELEASE_ASSERT(mReply);
+ return mReply->is_reply_error();
+ }
+
+ UniquePtr<IPC::Message> GetReply() { return std::move(mReply); }
+
+ private:
+ MessageChannel* mChan;
+
+ // Active is true if this transaction is on the mChan->mTransactionStack
+ // stack. Generally we're not on the stack if the transaction was canceled
+ // or if it was for a message that doesn't require transactions (an async
+ // message).
+ bool mActive;
+
+ // Is this stack frame for an outgoing message?
+ bool mOutgoing;
+
+ // Properties of the message being sent/received.
+ int mNestedLevel;
+ int32_t mSeqno;
+ int32_t mTransaction;
+
+ // Next item in mChan->mTransactionStack.
+ AutoEnterTransaction* mNext;
+
+ // Pointer the a reply received for this message, if one was received.
+ UniquePtr<IPC::Message> mReply;
+};
+
+class PendingResponseReporter final : public nsIMemoryReporter {
+ ~PendingResponseReporter() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) override {
+ MOZ_COLLECT_REPORT(
+ "unresolved-ipc-responses", KIND_OTHER, UNITS_COUNT,
+ MessageChannel::gUnresolvedResponses,
+ "Outstanding IPC async message responses that are still not resolved.");
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(PendingResponseReporter, nsIMemoryReporter)
+
+class ChannelCountReporter final : public nsIMemoryReporter {
+ ~ChannelCountReporter() = default;
+
+ struct ChannelCounts {
+ size_t mNow;
+ size_t mMax;
+
+ ChannelCounts() : mNow(0), mMax(0) {}
+
+ void Inc() {
+ ++mNow;
+ if (mMax < mNow) {
+ mMax = mNow;
+ }
+ }
+
+ void Dec() {
+ MOZ_ASSERT(mNow > 0);
+ --mNow;
+ }
+ };
+
+ using CountTable = nsTHashMap<nsDepCharHashKey, ChannelCounts>;
+
+ static StaticMutex sChannelCountMutex;
+ static CountTable* sChannelCounts MOZ_GUARDED_BY(sChannelCountMutex);
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) override {
+ AutoTArray<std::pair<const char*, ChannelCounts>, 16> counts;
+ {
+ StaticMutexAutoLock countLock(sChannelCountMutex);
+ if (!sChannelCounts) {
+ return NS_OK;
+ }
+ counts.SetCapacity(sChannelCounts->Count());
+ for (const auto& entry : *sChannelCounts) {
+ counts.AppendElement(std::pair{entry.GetKey(), entry.GetData()});
+ }
+ }
+
+ for (const auto& entry : counts) {
+ nsPrintfCString pathNow("ipc-channels/%s", entry.first);
+ nsPrintfCString pathMax("ipc-channels-peak/%s", entry.first);
+ nsPrintfCString descNow(
+ "Number of IPC channels for"
+ " top-level actor type %s",
+ entry.first);
+ nsPrintfCString descMax(
+ "Peak number of IPC channels for"
+ " top-level actor type %s",
+ entry.first);
+
+ aHandleReport->Callback(""_ns, pathNow, KIND_OTHER, UNITS_COUNT,
+ entry.second.mNow, descNow, aData);
+ aHandleReport->Callback(""_ns, pathMax, KIND_OTHER, UNITS_COUNT,
+ entry.second.mMax, descMax, aData);
+ }
+ return NS_OK;
+ }
+
+ static void Increment(const char* aName) {
+ StaticMutexAutoLock countLock(sChannelCountMutex);
+ if (!sChannelCounts) {
+ sChannelCounts = new CountTable;
+ }
+ sChannelCounts->LookupOrInsert(aName).Inc();
+ }
+
+ static void Decrement(const char* aName) {
+ StaticMutexAutoLock countLock(sChannelCountMutex);
+ MOZ_ASSERT(sChannelCounts);
+ sChannelCounts->LookupOrInsert(aName).Dec();
+ }
+};
+
+StaticMutex ChannelCountReporter::sChannelCountMutex;
+ChannelCountReporter::CountTable* ChannelCountReporter::sChannelCounts;
+
+NS_IMPL_ISUPPORTS(ChannelCountReporter, nsIMemoryReporter)
+
+// In child processes, the first MessageChannel is created before
+// XPCOM is initialized enough to construct the memory reporter
+// manager. This retries every time a MessageChannel is constructed,
+// which is good enough in practice.
+template <class Reporter>
+static void TryRegisterStrongMemoryReporter() {
+ static Atomic<bool> registered;
+ if (registered.compareExchange(false, true)) {
+ RefPtr<Reporter> reporter = new Reporter();
+ if (NS_FAILED(RegisterStrongMemoryReporter(reporter))) {
+ registered = false;
+ }
+ }
+}
+
+Atomic<size_t> MessageChannel::gUnresolvedResponses;
+
+MessageChannel::MessageChannel(const char* aName, IToplevelProtocol* aListener)
+ : mName(aName), mListener(aListener), mMonitor(new RefCountedMonitor()) {
+ MOZ_COUNT_CTOR(ipc::MessageChannel);
+
+#ifdef OS_WIN
+ mEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
+ MOZ_RELEASE_ASSERT(mEvent, "CreateEvent failed! Nothing is going to work!");
+#endif
+
+ TryRegisterStrongMemoryReporter<PendingResponseReporter>();
+ TryRegisterStrongMemoryReporter<ChannelCountReporter>();
+}
+
+MessageChannel::~MessageChannel() {
+ MOZ_COUNT_DTOR(ipc::MessageChannel);
+ MonitorAutoLock lock(*mMonitor);
+ MOZ_RELEASE_ASSERT(!mOnCxxStack,
+ "MessageChannel destroyed while code on CxxStack");
+#ifdef OS_WIN
+ if (mEvent) {
+ BOOL ok = CloseHandle(mEvent);
+ mEvent = nullptr;
+
+ if (!ok) {
+ gfxDevCrash(mozilla::gfx::LogReason::MessageChannelCloseFailure)
+ << "MessageChannel failed to close. GetLastError: " << GetLastError();
+ }
+ MOZ_RELEASE_ASSERT(ok);
+ } else {
+ gfxDevCrash(mozilla::gfx::LogReason::MessageChannelCloseFailure)
+ << "MessageChannel destructor ran without an mEvent Handle";
+ }
+#endif
+
+ // Make sure that the MessageChannel was closed (and therefore cleared) before
+ // it was destroyed. We can't properly close the channel at this point, as it
+ // would be unsafe to invoke our listener's callbacks, and we may be being
+ // destroyed on a thread other than `mWorkerThread`.
+ if (!IsClosedLocked()) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCFatalErrorProtocol,
+ nsDependentCString(mName));
+ switch (mChannelState) {
+ case ChannelConnected:
+ MOZ_CRASH(
+ "MessageChannel destroyed without being closed "
+ "(mChannelState == ChannelConnected).");
+ break;
+ case ChannelClosing:
+ MOZ_CRASH(
+ "MessageChannel destroyed without being closed "
+ "(mChannelState == ChannelClosing).");
+ break;
+ case ChannelError:
+ MOZ_CRASH(
+ "MessageChannel destroyed without being closed "
+ "(mChannelState == ChannelError).");
+ break;
+ default:
+ MOZ_CRASH("MessageChannel destroyed without being closed.");
+ }
+ }
+
+ // Double-check other properties for thoroughness.
+ MOZ_RELEASE_ASSERT(!mLink);
+ MOZ_RELEASE_ASSERT(mPendingResponses.empty());
+ MOZ_RELEASE_ASSERT(!mChannelErrorTask);
+ MOZ_RELEASE_ASSERT(mPending.isEmpty());
+ MOZ_RELEASE_ASSERT(!mShutdownTask);
+}
+
+#ifdef DEBUG
+void MessageChannel::AssertMaybeDeferredCountCorrect() {
+ mMonitor->AssertCurrentThreadOwns();
+
+ size_t count = 0;
+ for (MessageTask* task : mPending) {
+ task->AssertMonitorHeld(*mMonitor);
+ if (!IsAlwaysDeferred(*task->Msg())) {
+ count++;
+ }
+ }
+
+ MOZ_ASSERT(count == mMaybeDeferredPendingCount);
+}
+#endif
+
+// This function returns the current transaction ID. Since the notion of a
+// "current transaction" can be hard to define when messages race with each
+// other and one gets canceled and the other doesn't, we require that this
+// function is only called when the current transaction is known to be for a
+// NESTED_INSIDE_SYNC message. In that case, we know for sure what the caller is
+// looking for.
+int32_t MessageChannel::CurrentNestedInsideSyncTransaction() const {
+ mMonitor->AssertCurrentThreadOwns();
+ if (!mTransactionStack) {
+ return 0;
+ }
+ MOZ_RELEASE_ASSERT(mTransactionStack->NestedLevel() ==
+ IPC::Message::NESTED_INSIDE_SYNC);
+ return mTransactionStack->TransactionID();
+}
+
+bool MessageChannel::AwaitingSyncReply() const {
+ mMonitor->AssertCurrentThreadOwns();
+ return mTransactionStack ? mTransactionStack->AwaitingSyncReply() : false;
+}
+
+int MessageChannel::AwaitingSyncReplyNestedLevel() const {
+ mMonitor->AssertCurrentThreadOwns();
+ return mTransactionStack ? mTransactionStack->AwaitingSyncReplyNestedLevel()
+ : 0;
+}
+
+bool MessageChannel::DispatchingSyncMessage() const {
+ mMonitor->AssertCurrentThreadOwns();
+ return mTransactionStack ? mTransactionStack->DispatchingSyncMessage()
+ : false;
+}
+
+int MessageChannel::DispatchingSyncMessageNestedLevel() const {
+ mMonitor->AssertCurrentThreadOwns();
+ return mTransactionStack
+ ? mTransactionStack->DispatchingSyncMessageNestedLevel()
+ : 0;
+}
+
+static const char* StringFromIPCSide(Side side) {
+ switch (side) {
+ case ChildSide:
+ return "Child";
+ case ParentSide:
+ return "Parent";
+ default:
+ return "Unknown";
+ }
+}
+
+static void PrintErrorMessage(Side side, const char* channelName,
+ const char* msg) {
+ printf_stderr("\n###!!! [%s][%s] Error: %s\n\n", StringFromIPCSide(side),
+ channelName, msg);
+}
+
+bool MessageChannel::Connected() const {
+ mMonitor->AssertCurrentThreadOwns();
+ return ChannelConnected == mChannelState;
+}
+
+bool MessageChannel::ConnectedOrClosing() const {
+ mMonitor->AssertCurrentThreadOwns();
+ return ChannelConnected == mChannelState || ChannelClosing == mChannelState;
+}
+
+bool MessageChannel::CanSend() const {
+ if (!mMonitor) {
+ return false;
+ }
+ MonitorAutoLock lock(*mMonitor);
+ return Connected();
+}
+
+void MessageChannel::Clear() {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+ MOZ_DIAGNOSTIC_ASSERT(IsClosedLocked(), "MessageChannel cleared too early?");
+ MOZ_ASSERT(ChannelClosed == mChannelState || ChannelError == mChannelState);
+
+ // Don't clear mWorkerThread; we use it in AssertWorkerThread().
+ //
+ // Also don't clear mListener. If we clear it, then sending a message
+ // through this channel after it's Clear()'ed can cause this process to
+ // crash.
+
+ if (mShutdownTask) {
+ mShutdownTask->Clear();
+ mWorkerThread->UnregisterShutdownTask(mShutdownTask);
+ }
+ mShutdownTask = nullptr;
+
+ if (NS_IsMainThread() && gParentProcessBlocker == this) {
+ gParentProcessBlocker = nullptr;
+ }
+
+ gUnresolvedResponses -= mPendingResponses.size();
+ {
+ CallbackMap map = std::move(mPendingResponses);
+ MonitorAutoUnlock unlock(*mMonitor);
+ for (auto& pair : map) {
+ pair.second->Reject(ResponseRejectReason::ChannelClosed);
+ }
+ }
+ mPendingResponses.clear();
+
+ SetIsCrossProcess(false);
+
+ mLink = nullptr;
+
+ if (mChannelErrorTask) {
+ mChannelErrorTask->Cancel();
+ mChannelErrorTask = nullptr;
+ }
+
+ if (mFlushLazySendTask) {
+ mFlushLazySendTask->Cancel();
+ mFlushLazySendTask = nullptr;
+ }
+
+ // Free up any memory used by pending messages.
+ mPending.clear();
+
+ mMaybeDeferredPendingCount = 0;
+}
+
+bool MessageChannel::Open(ScopedPort aPort, Side aSide,
+ const nsID& aMessageChannelId,
+ nsISerialEventTarget* aEventTarget) {
+ nsCOMPtr<nsISerialEventTarget> eventTarget =
+ aEventTarget ? aEventTarget : GetCurrentSerialEventTarget();
+ MOZ_RELEASE_ASSERT(eventTarget,
+ "Must open MessageChannel on a nsISerialEventTarget");
+ MOZ_RELEASE_ASSERT(eventTarget->IsOnCurrentThread(),
+ "Must open MessageChannel from worker thread");
+
+ auto shutdownTask = MakeRefPtr<WorkerTargetShutdownTask>(eventTarget, this);
+ nsresult rv = eventTarget->RegisterShutdownTask(shutdownTask);
+ MOZ_ASSERT(rv != NS_ERROR_NOT_IMPLEMENTED,
+ "target for MessageChannel must support shutdown tasks");
+ if (rv == NS_ERROR_UNEXPECTED) {
+ // If shutdown tasks have already started running, dispatch our shutdown
+ // task manually.
+ NS_WARNING("Opening MessageChannel on EventTarget in shutdown");
+ rv = eventTarget->Dispatch(shutdownTask->AsRunnable());
+ }
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv),
+ "error registering ShutdownTask for MessageChannel");
+
+ {
+ MonitorAutoLock lock(*mMonitor);
+ MOZ_RELEASE_ASSERT(!mLink, "Open() called > once");
+ MOZ_RELEASE_ASSERT(ChannelClosed == mChannelState, "Not currently closed");
+ MOZ_ASSERT(mSide == UnknownSide);
+
+ mMessageChannelId = aMessageChannelId;
+ mWorkerThread = eventTarget;
+ mShutdownTask = shutdownTask;
+ mLink = MakeUnique<PortLink>(this, std::move(aPort));
+ mChannelState = ChannelConnected;
+ mSide = aSide;
+ }
+
+ // Notify our listener that the underlying IPC channel has been established.
+ // IProtocol will use this callback to create the ActorLifecycleProxy, and
+ // perform an `AddRef` call to keep the actor alive until the channel is
+ // disconnected.
+ //
+ // We unlock our monitor before calling `OnIPCChannelOpened` to ensure that
+ // any calls back into `MessageChannel` do not deadlock. At this point, we may
+ // be receiving messages on the IO thread, however we cannot process them on
+ // the worker thread or have notified our listener until after this function
+ // returns.
+ mListener->OnIPCChannelOpened();
+ return true;
+}
+
+static Side GetOppSide(Side aSide) {
+ switch (aSide) {
+ case ChildSide:
+ return ParentSide;
+ case ParentSide:
+ return ChildSide;
+ default:
+ return UnknownSide;
+ }
+}
+
+bool MessageChannel::Open(MessageChannel* aTargetChan,
+ nsISerialEventTarget* aEventTarget, Side aSide) {
+ // Opens a connection to another thread in the same process.
+
+ MOZ_ASSERT(aTargetChan, "Need a target channel");
+
+ nsID channelId = nsID::GenerateUUID();
+
+ std::pair<ScopedPort, ScopedPort> ports =
+ NodeController::GetSingleton()->CreatePortPair();
+
+ // NOTE: This dispatch must be sync as it captures locals by non-owning
+ // reference, however we can't use `NS_DispatchAndSpinEventLoopUntilComplete`
+ // as that will spin a nested event loop, and doesn't work with certain types
+ // of calling event targets.
+ base::WaitableEvent event(/* manual_reset */ true,
+ /* initially_signaled */ false);
+ MOZ_ALWAYS_SUCCEEDS(aEventTarget->Dispatch(NS_NewCancelableRunnableFunction(
+ "ipc::MessageChannel::OpenAsOtherThread", [&]() {
+ aTargetChan->Open(std::move(ports.second), GetOppSide(aSide), channelId,
+ aEventTarget);
+ event.Signal();
+ })));
+ bool ok = event.Wait();
+ MOZ_RELEASE_ASSERT(ok);
+
+ // Now that the other side has connected, open the port on our side.
+ return Open(std::move(ports.first), aSide, channelId);
+}
+
+bool MessageChannel::OpenOnSameThread(MessageChannel* aTargetChan,
+ mozilla::ipc::Side aSide) {
+ auto [porta, portb] = NodeController::GetSingleton()->CreatePortPair();
+
+ nsID channelId = nsID::GenerateUUID();
+
+ aTargetChan->mIsSameThreadChannel = true;
+ mIsSameThreadChannel = true;
+
+ auto* currentThread = GetCurrentSerialEventTarget();
+ return aTargetChan->Open(std::move(portb), GetOppSide(aSide), channelId,
+ currentThread) &&
+ Open(std::move(porta), aSide, channelId, currentThread);
+}
+
+bool MessageChannel::Send(UniquePtr<Message> aMsg) {
+ if (aMsg->size() >= kMinTelemetryMessageSize) {
+ Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE2, aMsg->size());
+ }
+
+ MOZ_RELEASE_ASSERT(!aMsg->is_sync());
+ MOZ_RELEASE_ASSERT(aMsg->nested_level() != IPC::Message::NESTED_INSIDE_SYNC);
+
+ AutoSetValue<bool> setOnCxxStack(mOnCxxStack, true);
+
+ AssertWorkerThread();
+ mMonitor->AssertNotCurrentThreadOwns();
+ if (MSG_ROUTING_NONE == aMsg->routing_id()) {
+ ReportMessageRouteError("MessageChannel::Send");
+ return false;
+ }
+
+ if (aMsg->seqno() == 0) {
+ aMsg->set_seqno(NextSeqno());
+ }
+
+ MonitorAutoLock lock(*mMonitor);
+ if (!Connected()) {
+ ReportConnectionError("Send", aMsg->type());
+ return false;
+ }
+
+ AddProfilerMarker(*aMsg, MessageDirection::eSending);
+ SendMessageToLink(std::move(aMsg));
+ return true;
+}
+
+void MessageChannel::SendMessageToLink(UniquePtr<Message> aMsg) {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ // If the channel is not cross-process, there's no reason to be lazy, so we
+ // ignore the flag in that case.
+ if (aMsg->is_lazy_send() && mIsCrossProcess) {
+ // If this is the first lazy message in the queue and our worker thread
+ // supports direct task dispatch, dispatch a task to flush messages,
+ // ensuring we don't leave them pending forever.
+ if (!mFlushLazySendTask) {
+ if (nsCOMPtr<nsIDirectTaskDispatcher> dispatcher =
+ do_QueryInterface(mWorkerThread)) {
+ mFlushLazySendTask = new FlushLazySendMessagesRunnable(this);
+ MOZ_ALWAYS_SUCCEEDS(
+ dispatcher->DispatchDirectTask(do_AddRef(mFlushLazySendTask)));
+ }
+ }
+ if (mFlushLazySendTask) {
+ mFlushLazySendTask->PushMessage(std::move(aMsg));
+ return;
+ }
+ }
+
+ if (mFlushLazySendTask) {
+ FlushLazySendMessages();
+ }
+ mLink->SendMessage(std::move(aMsg));
+}
+
+void MessageChannel::FlushLazySendMessages() {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ // Clean up any SendLazyTask which might be pending.
+ auto messages = mFlushLazySendTask->TakeMessages();
+ mFlushLazySendTask = nullptr;
+
+ // Send all lazy messages, then clear the queue.
+ for (auto& msg : messages) {
+ mLink->SendMessage(std::move(msg));
+ }
+}
+
+UniquePtr<MessageChannel::UntypedCallbackHolder> MessageChannel::PopCallback(
+ const Message& aMsg, int32_t aActorId) {
+ auto iter = mPendingResponses.find(aMsg.seqno());
+ if (iter != mPendingResponses.end() && iter->second->mActorId == aActorId &&
+ iter->second->mReplyMsgId == aMsg.type()) {
+ UniquePtr<MessageChannel::UntypedCallbackHolder> ret =
+ std::move(iter->second);
+ mPendingResponses.erase(iter);
+ gUnresolvedResponses--;
+ return ret;
+ }
+ return nullptr;
+}
+
+void MessageChannel::RejectPendingResponsesForActor(int32_t aActorId) {
+ auto itr = mPendingResponses.begin();
+ while (itr != mPendingResponses.end()) {
+ if (itr->second.get()->mActorId != aActorId) {
+ ++itr;
+ continue;
+ }
+ itr->second.get()->Reject(ResponseRejectReason::ActorDestroyed);
+ // Take special care of advancing the iterator since we are
+ // removing it while iterating.
+ itr = mPendingResponses.erase(itr);
+ gUnresolvedResponses--;
+ }
+}
+
+class BuildIDsMatchMessage : public IPC::Message {
+ public:
+ BuildIDsMatchMessage()
+ : IPC::Message(MSG_ROUTING_NONE, BUILD_IDS_MATCH_MESSAGE_TYPE) {}
+ void Log(const std::string& aPrefix, FILE* aOutf) const {
+ fputs("(special `Build IDs match' message)", aOutf);
+ }
+};
+
+// Send the parent a special async message to confirm when the parent and child
+// are of the same buildID. Skips sending the message and returns false if the
+// buildIDs don't match. This is a minor variation on
+// MessageChannel::Send(Message* aMsg).
+bool MessageChannel::SendBuildIDsMatchMessage(const char* aParentBuildID) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+
+ nsCString parentBuildID(aParentBuildID);
+ nsCString childBuildID(mozilla::PlatformBuildID());
+
+ if (parentBuildID != childBuildID) {
+ // The build IDs didn't match, usually because an update occurred in the
+ // background.
+ return false;
+ }
+
+ auto msg = MakeUnique<BuildIDsMatchMessage>();
+
+ MOZ_RELEASE_ASSERT(!msg->is_sync());
+ MOZ_RELEASE_ASSERT(msg->nested_level() != IPC::Message::NESTED_INSIDE_SYNC);
+
+ AssertWorkerThread();
+ mMonitor->AssertNotCurrentThreadOwns();
+ // Don't check for MSG_ROUTING_NONE.
+
+ MonitorAutoLock lock(*mMonitor);
+ if (!Connected()) {
+ ReportConnectionError("SendBuildIDsMatchMessage", msg->type());
+ return false;
+ }
+
+#if defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+ // Technically, the behavior is interesting for any kind of process
+ // but when exercising tests, we want to crash only a content process and
+ // avoid making noise with other kind of processes crashing
+ if (const char* dontSend = PR_GetEnv("MOZ_BUILDID_MATCH_DONTSEND")) {
+ if (dontSend[0] == '1') {
+ // Bug 1732999: We are going to crash, so we need to advise leak check
+ // tooling to avoid intermittent missing leakcheck
+ NoteIntentionalCrash(XRE_GetProcessTypeString());
+ if (XRE_IsContentProcess()) {
+ return false;
+ }
+ }
+ }
+#endif
+
+ SendMessageToLink(std::move(msg));
+ return true;
+}
+
+class CancelMessage : public IPC::Message {
+ public:
+ explicit CancelMessage(int transaction)
+ : IPC::Message(MSG_ROUTING_NONE, CANCEL_MESSAGE_TYPE) {
+ set_transaction_id(transaction);
+ }
+ static bool Read(const Message* msg) { return true; }
+ void Log(const std::string& aPrefix, FILE* aOutf) const {
+ fputs("(special `Cancel' message)", aOutf);
+ }
+};
+
+bool MessageChannel::MaybeInterceptSpecialIOMessage(const Message& aMsg) {
+ mMonitor->AssertCurrentThreadOwns();
+
+ if (MSG_ROUTING_NONE == aMsg.routing_id()) {
+ if (GOODBYE_MESSAGE_TYPE == aMsg.type()) {
+ // We've received a GOODBYE message, close the connection and mark
+ // ourselves as "Closing".
+ mLink->Close();
+ mChannelState = ChannelClosing;
+ if (LoggingEnabled()) {
+ printf(
+ "[%s %u] NOTE: %s actor received `Goodbye' message. Closing "
+ "channel.\n",
+ XRE_GeckoProcessTypeToString(XRE_GetProcessType()),
+ static_cast<uint32_t>(base::GetCurrentProcId()),
+ (mSide == ChildSide) ? "child" : "parent");
+ }
+
+ // Notify the worker thread that the connection has been closed, as we
+ // will not receive an `OnChannelErrorFromLink` after calling
+ // `mLink->Close()`.
+ if (AwaitingSyncReply()) {
+ NotifyWorkerThread();
+ }
+ PostErrorNotifyTask();
+ return true;
+ } else if (CANCEL_MESSAGE_TYPE == aMsg.type()) {
+ IPC_LOG("Cancel from message");
+ CancelTransaction(aMsg.transaction_id());
+ NotifyWorkerThread();
+ return true;
+ } else if (BUILD_IDS_MATCH_MESSAGE_TYPE == aMsg.type()) {
+ IPC_LOG("Build IDs match message");
+ mBuildIDsConfirmedMatch = true;
+ return true;
+ } else if (IMPENDING_SHUTDOWN_MESSAGE_TYPE == aMsg.type()) {
+ IPC_LOG("Impending Shutdown received");
+ ProcessChild::NotifiedImpendingShutdown();
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */
+bool MessageChannel::IsAlwaysDeferred(const Message& aMsg) {
+ // If a message is not NESTED_INSIDE_CPOW and not sync, then we always defer
+ // it.
+ return aMsg.nested_level() != IPC::Message::NESTED_INSIDE_CPOW &&
+ !aMsg.is_sync();
+}
+
+bool MessageChannel::ShouldDeferMessage(const Message& aMsg) {
+ // Never defer messages that have the highest nested level, even async
+ // ones. This is safe because only the child can send these messages, so
+ // they can never nest.
+ if (aMsg.nested_level() == IPC::Message::NESTED_INSIDE_CPOW) {
+ MOZ_ASSERT(!IsAlwaysDeferred(aMsg));
+ return false;
+ }
+
+ // Unless they're NESTED_INSIDE_CPOW, we always defer async messages.
+ // Note that we never send an async NESTED_INSIDE_SYNC message.
+ if (!aMsg.is_sync()) {
+ MOZ_RELEASE_ASSERT(aMsg.nested_level() == IPC::Message::NOT_NESTED);
+ MOZ_ASSERT(IsAlwaysDeferred(aMsg));
+ return true;
+ }
+
+ MOZ_ASSERT(!IsAlwaysDeferred(aMsg));
+
+ int msgNestedLevel = aMsg.nested_level();
+ int waitingNestedLevel = AwaitingSyncReplyNestedLevel();
+
+ // Always defer if the nested level of the incoming message is less than the
+ // nested level of the message we're awaiting.
+ if (msgNestedLevel < waitingNestedLevel) return true;
+
+ // Never defer if the message has strictly greater nested level.
+ if (msgNestedLevel > waitingNestedLevel) return false;
+
+ // When both sides send sync messages of the same nested level, we resolve the
+ // race by dispatching in the child and deferring the incoming message in
+ // the parent. However, the parent still needs to dispatch nested sync
+ // messages.
+ //
+ // Deferring in the parent only sort of breaks message ordering. When the
+ // child's message comes in, we can pretend the child hasn't quite
+ // finished sending it yet. Since the message is sync, we know that the
+ // child hasn't moved on yet.
+ return mSide == ParentSide &&
+ aMsg.transaction_id() != CurrentNestedInsideSyncTransaction();
+}
+
+void MessageChannel::OnMessageReceivedFromLink(UniquePtr<Message> aMsg) {
+ mMonitor->AssertCurrentThreadOwns();
+ MOZ_ASSERT(mChannelState == ChannelConnected);
+
+ if (MaybeInterceptSpecialIOMessage(*aMsg)) {
+ return;
+ }
+
+ mListener->OnChannelReceivedMessage(*aMsg);
+
+ // If we're awaiting a sync reply, we know that it needs to be immediately
+ // handled to unblock us.
+ if (aMsg->is_sync() && aMsg->is_reply()) {
+ IPC_LOG("Received reply seqno=%d xid=%d", aMsg->seqno(),
+ aMsg->transaction_id());
+
+ if (aMsg->seqno() == mTimedOutMessageSeqno) {
+ // Drop the message, but allow future sync messages to be sent.
+ IPC_LOG("Received reply to timedout message; igoring; xid=%d",
+ mTimedOutMessageSeqno);
+ EndTimeout();
+ return;
+ }
+
+ MOZ_RELEASE_ASSERT(AwaitingSyncReply());
+ MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);
+
+ mTransactionStack->HandleReply(std::move(aMsg));
+ NotifyWorkerThread();
+ return;
+ }
+
+ // Nested messages cannot be compressed.
+ MOZ_RELEASE_ASSERT(aMsg->compress_type() == IPC::Message::COMPRESSION_NONE ||
+ aMsg->nested_level() == IPC::Message::NOT_NESTED);
+
+ if (aMsg->compress_type() == IPC::Message::COMPRESSION_ENABLED &&
+ !mPending.isEmpty()) {
+ auto* last = mPending.getLast();
+ last->AssertMonitorHeld(*mMonitor);
+ bool compress = last->Msg()->type() == aMsg->type() &&
+ last->Msg()->routing_id() == aMsg->routing_id();
+ if (compress) {
+ // This message type has compression enabled, and the back of the
+ // queue was the same message type and routed to the same destination.
+ // Replace it with the newer message.
+ MOZ_RELEASE_ASSERT(last->Msg()->compress_type() ==
+ IPC::Message::COMPRESSION_ENABLED);
+ last->Msg() = std::move(aMsg);
+ return;
+ }
+ } else if (aMsg->compress_type() == IPC::Message::COMPRESSION_ALL &&
+ !mPending.isEmpty()) {
+ for (MessageTask* p = mPending.getLast(); p; p = p->getPrevious()) {
+ p->AssertMonitorHeld(*mMonitor);
+ if (p->Msg()->type() == aMsg->type() &&
+ p->Msg()->routing_id() == aMsg->routing_id()) {
+ // This message type has compression enabled, and the queue
+ // holds a message with the same message type and routed to the
+ // same destination. Erase it. Note that, since we always
+ // compress these redundancies, There Can Be Only One.
+ MOZ_RELEASE_ASSERT(p->Msg()->compress_type() ==
+ IPC::Message::COMPRESSION_ALL);
+ MOZ_RELEASE_ASSERT(IsAlwaysDeferred(*p->Msg()));
+ p->remove();
+ break;
+ }
+ }
+ }
+
+ bool alwaysDeferred = IsAlwaysDeferred(*aMsg);
+
+ bool shouldWakeUp = AwaitingSyncReply() && !ShouldDeferMessage(*aMsg);
+
+ IPC_LOG("Receive from link; seqno=%d, xid=%d, shouldWakeUp=%d", aMsg->seqno(),
+ aMsg->transaction_id(), shouldWakeUp);
+
+ // There are two cases we're concerned about, relating to the state of the
+ // worker thread:
+ //
+ // (1) We are waiting on a sync reply - worker thread is blocked on the
+ // IPC monitor.
+ // - If the message is NESTED_INSIDE_SYNC, we wake up the worker thread to
+ // deliver the message depending on ShouldDeferMessage. Otherwise, we
+ // leave it in the mPending queue, posting a task to the worker event
+ // loop, where it will be processed once the synchronous reply has been
+ // received.
+ //
+ // (2) We are not waiting on a reply.
+ // - We post a task to the worker event loop.
+ //
+ // Note that, we may notify the worker thread even though the monitor is not
+ // blocked. This is okay, since we always check for pending events before
+ // blocking again.
+
+ RefPtr<MessageTask> task = new MessageTask(this, std::move(aMsg));
+ mPending.insertBack(task);
+
+ if (!alwaysDeferred) {
+ mMaybeDeferredPendingCount++;
+ }
+
+ if (shouldWakeUp) {
+ NotifyWorkerThread();
+ }
+
+ // Although we usually don't need to post a message task if
+ // shouldWakeUp is true, it's easier to post anyway than to have to
+ // guarantee that every Send call processes everything it's supposed to
+ // before returning.
+ task->AssertMonitorHeld(*mMonitor);
+ task->Post();
+}
+
+void MessageChannel::PeekMessages(
+ const std::function<bool(const Message& aMsg)>& aInvoke) {
+ // FIXME: We shouldn't be holding the lock for aInvoke!
+ MonitorAutoLock lock(*mMonitor);
+
+ for (MessageTask* it : mPending) {
+ it->AssertMonitorHeld(*mMonitor);
+ const Message& msg = *it->Msg();
+ if (!aInvoke(msg)) {
+ break;
+ }
+ }
+}
+
+void MessageChannel::ProcessPendingRequests(
+ ActorLifecycleProxy* aProxy, AutoEnterTransaction& aTransaction) {
+ mMonitor->AssertCurrentThreadOwns();
+
+ AssertMaybeDeferredCountCorrect();
+ if (mMaybeDeferredPendingCount == 0) {
+ return;
+ }
+
+ IPC_LOG("ProcessPendingRequests for seqno=%d, xid=%d",
+ aTransaction.SequenceNumber(), aTransaction.TransactionID());
+
+ // Loop until there aren't any more nested messages to process.
+ for (;;) {
+ // If we canceled during ProcessPendingRequest, then we need to leave
+ // immediately because the results of ShouldDeferMessage will be
+ // operating with weird state (as if no Send is in progress). That could
+ // cause even NOT_NESTED sync messages to be processed (but not
+ // NOT_NESTED async messages), which would break message ordering.
+ if (aTransaction.IsCanceled()) {
+ return;
+ }
+
+ Vector<UniquePtr<Message>> toProcess;
+
+ for (MessageTask* p = mPending.getFirst(); p;) {
+ p->AssertMonitorHeld(*mMonitor);
+ UniquePtr<Message>& msg = p->Msg();
+
+ MOZ_RELEASE_ASSERT(!aTransaction.IsCanceled(),
+ "Calling ShouldDeferMessage when cancelled");
+ bool defer = ShouldDeferMessage(*msg);
+
+ // Only log the interesting messages.
+ if (msg->is_sync() ||
+ msg->nested_level() == IPC::Message::NESTED_INSIDE_CPOW) {
+ IPC_LOG("ShouldDeferMessage(seqno=%d) = %d", msg->seqno(), defer);
+ }
+
+ if (!defer) {
+ MOZ_ASSERT(!IsAlwaysDeferred(*msg));
+
+ if (!toProcess.append(std::move(msg))) MOZ_CRASH();
+
+ mMaybeDeferredPendingCount--;
+
+ p = p->removeAndGetNext();
+ continue;
+ }
+ p = p->getNext();
+ }
+
+ if (toProcess.empty()) {
+ break;
+ }
+
+ // Processing these messages could result in more messages, so we
+ // loop around to check for more afterwards.
+
+ for (auto& msg : toProcess) {
+ ProcessPendingRequest(aProxy, std::move(msg));
+ }
+ }
+
+ AssertMaybeDeferredCountCorrect();
+}
+
+bool MessageChannel::Send(UniquePtr<Message> aMsg, UniquePtr<Message>* aReply) {
+ mozilla::TimeStamp start = TimeStamp::Now();
+ if (aMsg->size() >= kMinTelemetryMessageSize) {
+ Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE2, aMsg->size());
+ }
+
+ // Sanity checks.
+ AssertWorkerThread();
+ mMonitor->AssertNotCurrentThreadOwns();
+ MOZ_RELEASE_ASSERT(!mIsSameThreadChannel,
+ "sync send over same-thread channel will deadlock!");
+
+ RefPtr<ActorLifecycleProxy> proxy = Listener()->GetLifecycleProxy();
+
+#ifdef OS_WIN
+ SyncStackFrame frame(this);
+ NeuteredWindowRegion neuteredRgn(mFlags &
+ REQUIRE_DEFERRED_MESSAGE_PROTECTION);
+#endif
+
+ AutoSetValue<bool> setOnCxxStack(mOnCxxStack, true);
+
+ MonitorAutoLock lock(*mMonitor);
+
+ if (mTimedOutMessageSeqno) {
+ // Don't bother sending another sync message if a previous one timed out
+ // and we haven't received a reply for it. Once the original timed-out
+ // message receives a reply, we'll be able to send more sync messages
+ // again.
+ IPC_LOG("Send() failed due to previous timeout");
+ mLastSendError = SyncSendError::PreviousTimeout;
+ return false;
+ }
+
+ if (DispatchingSyncMessageNestedLevel() == IPC::Message::NOT_NESTED &&
+ aMsg->nested_level() > IPC::Message::NOT_NESTED) {
+ // Don't allow sending CPOWs while we're dispatching a sync message.
+ IPC_LOG("Nested level forbids send");
+ mLastSendError = SyncSendError::SendingCPOWWhileDispatchingSync;
+ return false;
+ }
+
+ if (DispatchingSyncMessageNestedLevel() == IPC::Message::NESTED_INSIDE_CPOW ||
+ DispatchingAsyncMessageNestedLevel() ==
+ IPC::Message::NESTED_INSIDE_CPOW) {
+ // Generally only the parent dispatches urgent messages. And the only
+ // sync messages it can send are NESTED_INSIDE_SYNC. Mainly we want to
+ // ensure here that we don't return false for non-CPOW messages.
+ MOZ_RELEASE_ASSERT(aMsg->nested_level() ==
+ IPC::Message::NESTED_INSIDE_SYNC);
+ IPC_LOG("Sending while dispatching urgent message");
+ mLastSendError = SyncSendError::SendingCPOWWhileDispatchingUrgent;
+ return false;
+ }
+
+ if (aMsg->nested_level() < DispatchingSyncMessageNestedLevel() ||
+ aMsg->nested_level() < AwaitingSyncReplyNestedLevel()) {
+ MOZ_RELEASE_ASSERT(DispatchingSyncMessage() || DispatchingAsyncMessage());
+ IPC_LOG("Cancel from Send");
+ auto cancel =
+ MakeUnique<CancelMessage>(CurrentNestedInsideSyncTransaction());
+ CancelTransaction(CurrentNestedInsideSyncTransaction());
+ SendMessageToLink(std::move(cancel));
+ }
+
+ IPC_ASSERT(aMsg->is_sync(), "can only Send() sync messages here");
+
+ IPC_ASSERT(aMsg->nested_level() >= DispatchingSyncMessageNestedLevel(),
+ "can't send sync message of a lesser nested level than what's "
+ "being dispatched");
+ IPC_ASSERT(AwaitingSyncReplyNestedLevel() <= aMsg->nested_level(),
+ "nested sync message sends must be of increasing nested level");
+ IPC_ASSERT(
+ DispatchingSyncMessageNestedLevel() != IPC::Message::NESTED_INSIDE_CPOW,
+ "not allowed to send messages while dispatching urgent messages");
+
+ IPC_ASSERT(
+ DispatchingAsyncMessageNestedLevel() != IPC::Message::NESTED_INSIDE_CPOW,
+ "not allowed to send messages while dispatching urgent messages");
+
+ if (!Connected()) {
+ ReportConnectionError("SendAndWait", aMsg->type());
+ mLastSendError = SyncSendError::NotConnectedBeforeSend;
+ return false;
+ }
+
+ aMsg->set_seqno(NextSeqno());
+
+ int32_t seqno = aMsg->seqno();
+ int nestedLevel = aMsg->nested_level();
+ msgid_t replyType = aMsg->type() + 1;
+
+ AutoEnterTransaction* stackTop = mTransactionStack;
+
+ // If the most recent message on the stack is NESTED_INSIDE_SYNC, then our
+ // message should nest inside that and we use the same transaction
+ // ID. Otherwise we need a new transaction ID (so we use the seqno of the
+ // message we're sending).
+ bool nest =
+ stackTop && stackTop->NestedLevel() == IPC::Message::NESTED_INSIDE_SYNC;
+ int32_t transaction = nest ? stackTop->TransactionID() : seqno;
+ aMsg->set_transaction_id(transaction);
+
+ AutoEnterTransaction transact(this, seqno, transaction, nestedLevel);
+
+ IPC_LOG("Send seqno=%d, xid=%d", seqno, transaction);
+
+ // aMsg will be destroyed soon, let's keep its type.
+ const char* msgName = aMsg->name();
+ const msgid_t msgType = aMsg->type();
+
+ AddProfilerMarker(*aMsg, MessageDirection::eSending);
+ SendMessageToLink(std::move(aMsg));
+
+ while (true) {
+ MOZ_RELEASE_ASSERT(!transact.IsCanceled());
+ ProcessPendingRequests(proxy, transact);
+ if (transact.IsComplete()) {
+ break;
+ }
+ if (!Connected()) {
+ ReportConnectionError("Send", msgType);
+ mLastSendError = SyncSendError::DisconnectedDuringSend;
+ return false;
+ }
+
+ MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);
+ MOZ_RELEASE_ASSERT(!transact.IsComplete());
+ MOZ_RELEASE_ASSERT(mTransactionStack == &transact);
+
+ bool maybeTimedOut = !WaitForSyncNotify();
+
+ if (mListener->NeedArtificialSleep()) {
+ MonitorAutoUnlock unlock(*mMonitor);
+ mListener->ArtificialSleep();
+ }
+
+ if (!Connected()) {
+ ReportConnectionError("SendAndWait", msgType);
+ mLastSendError = SyncSendError::DisconnectedDuringSend;
+ return false;
+ }
+
+ if (transact.IsCanceled()) {
+ break;
+ }
+
+ MOZ_RELEASE_ASSERT(mTransactionStack == &transact);
+
+ // We only time out a message if it initiated a new transaction (i.e.,
+ // if neither side has any other message Sends on the stack).
+ bool canTimeOut = transact.IsBottom();
+ if (maybeTimedOut && canTimeOut && !ShouldContinueFromTimeout()) {
+ // Since ShouldContinueFromTimeout drops the lock, we need to
+ // re-check all our conditions here. We shouldn't time out if any of
+ // these things happen because there won't be a reply to the timed
+ // out message in these cases.
+ if (transact.IsComplete()) {
+ break;
+ }
+
+ IPC_LOG("Timing out Send: xid=%d", transaction);
+
+ mTimedOutMessageSeqno = seqno;
+ mTimedOutMessageNestedLevel = nestedLevel;
+ mLastSendError = SyncSendError::TimedOut;
+ return false;
+ }
+
+ if (transact.IsCanceled()) {
+ break;
+ }
+ }
+
+ if (transact.IsCanceled()) {
+ IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
+ mLastSendError = SyncSendError::CancelledAfterSend;
+ return false;
+ }
+
+ if (transact.IsError()) {
+ IPC_LOG("Error: seqno=%d, xid=%d", seqno, transaction);
+ mLastSendError = SyncSendError::ReplyError;
+ return false;
+ }
+
+ uint32_t latencyMs = round((TimeStamp::Now() - start).ToMilliseconds());
+ IPC_LOG("Got reply: seqno=%d, xid=%d, msgName=%s, latency=%ums", seqno,
+ transaction, msgName, latencyMs);
+
+ UniquePtr<Message> reply = transact.GetReply();
+
+ MOZ_RELEASE_ASSERT(reply);
+ MOZ_RELEASE_ASSERT(reply->is_reply(), "expected reply");
+ MOZ_RELEASE_ASSERT(!reply->is_reply_error());
+ MOZ_RELEASE_ASSERT(reply->seqno() == seqno);
+ MOZ_RELEASE_ASSERT(reply->type() == replyType, "wrong reply type");
+ MOZ_RELEASE_ASSERT(reply->is_sync());
+
+ AddProfilerMarker(*reply, MessageDirection::eReceiving);
+
+ if (reply->size() >= kMinTelemetryMessageSize) {
+ Telemetry::Accumulate(Telemetry::IPC_REPLY_SIZE,
+ nsDependentCString(msgName), reply->size());
+ }
+
+ *aReply = std::move(reply);
+
+ // NOTE: Only collect IPC_SYNC_MAIN_LATENCY_MS on the main thread (bug
+ // 1343729)
+ if (NS_IsMainThread() && latencyMs >= kMinTelemetrySyncIPCLatencyMs) {
+ Telemetry::Accumulate(Telemetry::IPC_SYNC_MAIN_LATENCY_MS,
+ nsDependentCString(msgName), latencyMs);
+ }
+ return true;
+}
+
+bool MessageChannel::HasPendingEvents() {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+ return ConnectedOrClosing() && !mPending.isEmpty();
+}
+
+bool MessageChannel::ProcessPendingRequest(ActorLifecycleProxy* aProxy,
+ UniquePtr<Message> aUrgent) {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ IPC_LOG("Process pending: seqno=%d, xid=%d", aUrgent->seqno(),
+ aUrgent->transaction_id());
+
+ // keep the error relevant information
+ msgid_t msgType = aUrgent->type();
+
+ DispatchMessage(aProxy, std::move(aUrgent));
+ if (!ConnectedOrClosing()) {
+ ReportConnectionError("ProcessPendingRequest", msgType);
+ return false;
+ }
+
+ return true;
+}
+
+bool MessageChannel::ShouldRunMessage(const Message& aMsg) {
+ if (!mTimedOutMessageSeqno) {
+ return true;
+ }
+
+ // If we've timed out a message and we're awaiting the reply to the timed
+ // out message, we have to be careful what messages we process. Here's what
+ // can go wrong:
+ // 1. child sends a NOT_NESTED sync message S
+ // 2. parent sends a NESTED_INSIDE_SYNC sync message H at the same time
+ // 3. parent times out H
+ // 4. child starts processing H and sends a NESTED_INSIDE_SYNC message H'
+ // nested within the same transaction
+ // 5. parent dispatches S and sends reply
+ // 6. child asserts because it instead expected a reply to H'.
+ //
+ // To solve this, we refuse to process S in the parent until we get a reply
+ // to H. More generally, let the timed out message be M. We don't process a
+ // message unless the child would need the response to that message in order
+ // to process M. Those messages are the ones that have a higher nested level
+ // than M or that are part of the same transaction as M.
+ if (aMsg.nested_level() < mTimedOutMessageNestedLevel ||
+ (aMsg.nested_level() == mTimedOutMessageNestedLevel &&
+ aMsg.transaction_id() != mTimedOutMessageSeqno)) {
+ return false;
+ }
+
+ return true;
+}
+
+void MessageChannel::RunMessage(ActorLifecycleProxy* aProxy,
+ MessageTask& aTask) {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+ aTask.AssertMonitorHeld(*mMonitor);
+
+ UniquePtr<Message>& msg = aTask.Msg();
+
+ if (!ConnectedOrClosing()) {
+ ReportConnectionError("RunMessage", msg->type());
+ return;
+ }
+
+ // Check that we're going to run the first message that's valid to run.
+#if 0
+# ifdef DEBUG
+ for (MessageTask* task : mPending) {
+ if (task == &aTask) {
+ break;
+ }
+
+ MOZ_ASSERT(!ShouldRunMessage(*task->Msg()) ||
+ aTask.Msg()->priority() != task->Msg()->priority());
+
+ }
+# endif
+#endif
+
+ if (!ShouldRunMessage(*msg)) {
+ return;
+ }
+
+ MOZ_RELEASE_ASSERT(aTask.isInList());
+ aTask.remove();
+
+ if (!IsAlwaysDeferred(*msg)) {
+ mMaybeDeferredPendingCount--;
+ }
+
+ DispatchMessage(aProxy, std::move(msg));
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(MessageChannel::MessageTask, CancelableRunnable,
+ nsIRunnablePriority, nsIRunnableIPCMessageType)
+
+static uint32_t ToRunnablePriority(IPC::Message::PriorityValue aPriority) {
+ switch (aPriority) {
+ case IPC::Message::NORMAL_PRIORITY:
+ return nsIRunnablePriority::PRIORITY_NORMAL;
+ case IPC::Message::INPUT_PRIORITY:
+ return nsIRunnablePriority::PRIORITY_INPUT_HIGH;
+ case IPC::Message::VSYNC_PRIORITY:
+ return nsIRunnablePriority::PRIORITY_VSYNC;
+ case IPC::Message::MEDIUMHIGH_PRIORITY:
+ return nsIRunnablePriority::PRIORITY_MEDIUMHIGH;
+ case IPC::Message::CONTROL_PRIORITY:
+ return nsIRunnablePriority::PRIORITY_CONTROL;
+ default:
+ MOZ_ASSERT_UNREACHABLE();
+ return nsIRunnablePriority::PRIORITY_NORMAL;
+ }
+}
+
+MessageChannel::MessageTask::MessageTask(MessageChannel* aChannel,
+ UniquePtr<Message> aMessage)
+ : CancelableRunnable(aMessage->name()),
+ mMonitor(aChannel->mMonitor),
+ mChannel(aChannel),
+ mMessage(std::move(aMessage)),
+ mPriority(ToRunnablePriority(mMessage->priority())),
+ mScheduled(false)
+#ifdef FUZZING_SNAPSHOT
+ ,
+ mIsFuzzMsg(mMessage->IsFuzzMsg()),
+ mFuzzStopped(false)
+#endif
+{
+ MOZ_DIAGNOSTIC_ASSERT(mMessage, "message may not be null");
+#ifdef FUZZING_SNAPSHOT
+ if (mIsFuzzMsg) {
+ MOZ_FUZZING_IPC_MT_CTOR();
+ }
+#endif
+}
+
+MessageChannel::MessageTask::~MessageTask() {
+#ifdef FUZZING_SNAPSHOT
+ // We track fuzzing messages until their run is complete. To make sure
+ // that we don't miss messages that are for some reason destroyed without
+ // being run (e.g. canceled), we catch this condition in the destructor.
+ if (mIsFuzzMsg && !mFuzzStopped) {
+ MOZ_FUZZING_IPC_MT_STOP();
+ } else if (!mIsFuzzMsg && !fuzzing::Nyx::instance().started()) {
+ MOZ_FUZZING_IPC_PRE_FUZZ_MT_STOP();
+ }
+#endif
+}
+
+nsresult MessageChannel::MessageTask::Run() {
+ mMonitor->AssertNotCurrentThreadOwns();
+
+ // Drop the toplevel actor's lifecycle proxy outside of our monitor if we take
+ // it, as destroying our ActorLifecycleProxy reference can acquire the
+ // monitor.
+ RefPtr<ActorLifecycleProxy> proxy;
+
+ MonitorAutoLock lock(*mMonitor);
+
+ // In case we choose not to run this message, we may need to be able to Post
+ // it again.
+ mScheduled = false;
+
+ if (!isInList()) {
+ return NS_OK;
+ }
+
+#ifdef FUZZING_SNAPSHOT
+ if (!mIsFuzzMsg) {
+ if (fuzzing::Nyx::instance().started()) {
+ // Once we started fuzzing, prevent non-fuzzing tasks from being
+ // run and potentially blocking worker threads.
+ //
+ // TODO: This currently blocks all MessageTasks from running, not
+ // just those belonging to the target process pair. We currently
+ // do this for performance reasons, but it should be re-evaluated
+ // at a later stage when we found a better snapshot point.
+ return NS_OK;
+ }
+ // Record all running tasks prior to fuzzing, so we can wait for
+ // them to settle before snapshotting.
+ MOZ_FUZZING_IPC_PRE_FUZZ_MT_RUN();
+ }
+#endif
+
+ Channel()->AssertWorkerThread();
+ mMonitor->AssertSameMonitor(*Channel()->mMonitor);
+ proxy = Channel()->Listener()->GetLifecycleProxy();
+ Channel()->RunMessage(proxy, *this);
+
+#ifdef FUZZING_SNAPSHOT
+ if (mIsFuzzMsg && !mFuzzStopped) {
+ MOZ_FUZZING_IPC_MT_STOP();
+ mFuzzStopped = true;
+ }
+#endif
+ return NS_OK;
+}
+
+// Warning: This method removes the receiver from whatever list it might be in.
+nsresult MessageChannel::MessageTask::Cancel() {
+ mMonitor->AssertNotCurrentThreadOwns();
+
+ MonitorAutoLock lock(*mMonitor);
+
+ if (!isInList()) {
+ return NS_OK;
+ }
+
+ Channel()->AssertWorkerThread();
+ mMonitor->AssertSameMonitor(*Channel()->mMonitor);
+ if (!IsAlwaysDeferred(*Msg())) {
+ Channel()->mMaybeDeferredPendingCount--;
+ }
+
+ remove();
+
+#ifdef FUZZING_SNAPSHOT
+ if (mIsFuzzMsg && !mFuzzStopped) {
+ MOZ_FUZZING_IPC_MT_STOP();
+ mFuzzStopped = true;
+ }
+#endif
+
+ return NS_OK;
+}
+
+void MessageChannel::MessageTask::Post() {
+ mMonitor->AssertCurrentThreadOwns();
+ mMonitor->AssertSameMonitor(*Channel()->mMonitor);
+ MOZ_RELEASE_ASSERT(!mScheduled);
+ MOZ_RELEASE_ASSERT(isInList());
+
+ mScheduled = true;
+
+ Channel()->mWorkerThread->Dispatch(do_AddRef(this));
+}
+
+NS_IMETHODIMP
+MessageChannel::MessageTask::GetPriority(uint32_t* aPriority) {
+ *aPriority = mPriority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MessageChannel::MessageTask::GetType(uint32_t* aType) {
+ mMonitor->AssertNotCurrentThreadOwns();
+
+ MonitorAutoLock lock(*mMonitor);
+ if (!mMessage) {
+ // If mMessage has been moved already elsewhere, we can't know what the type
+ // has been.
+ return NS_ERROR_FAILURE;
+ }
+
+ *aType = mMessage->type();
+ return NS_OK;
+}
+
+void MessageChannel::DispatchMessage(ActorLifecycleProxy* aProxy,
+ UniquePtr<Message> aMsg) {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ Maybe<AutoNoJSAPI> nojsapi;
+ if (NS_IsMainThread() && CycleCollectedJSContext::Get()) {
+ nojsapi.emplace();
+ }
+
+ UniquePtr<Message> reply;
+
+ IPC_LOG("DispatchMessage: seqno=%d, xid=%d", aMsg->seqno(),
+ aMsg->transaction_id());
+ AddProfilerMarker(*aMsg, MessageDirection::eReceiving);
+
+ {
+ AutoEnterTransaction transaction(this, *aMsg);
+
+ int id = aMsg->transaction_id();
+ MOZ_RELEASE_ASSERT(!aMsg->is_sync() || id == transaction.TransactionID());
+
+ {
+ MonitorAutoUnlock unlock(*mMonitor);
+ AutoSetValue<bool> setOnCxxStack(mOnCxxStack, true);
+
+ mListener->ArtificialSleep();
+
+ if (aMsg->is_sync()) {
+ DispatchSyncMessage(aProxy, *aMsg, reply);
+ } else {
+ DispatchAsyncMessage(aProxy, *aMsg);
+ }
+
+ mListener->ArtificialSleep();
+ }
+
+ if (reply && transaction.IsCanceled()) {
+ // The transaction has been canceled. Don't send a reply.
+ IPC_LOG("Nulling out reply due to cancellation, seqno=%d, xid=%d",
+ aMsg->seqno(), id);
+ reply = nullptr;
+ }
+ }
+
+ if (reply && ChannelConnected == mChannelState) {
+ IPC_LOG("Sending reply seqno=%d, xid=%d", aMsg->seqno(),
+ aMsg->transaction_id());
+ AddProfilerMarker(*reply, MessageDirection::eSending);
+
+ SendMessageToLink(std::move(reply));
+ }
+}
+
+void MessageChannel::DispatchSyncMessage(ActorLifecycleProxy* aProxy,
+ const Message& aMsg,
+ UniquePtr<Message>& aReply) {
+ AssertWorkerThread();
+
+ mozilla::TimeStamp start = TimeStamp::Now();
+
+ int nestedLevel = aMsg.nested_level();
+
+ MOZ_RELEASE_ASSERT(nestedLevel == IPC::Message::NOT_NESTED ||
+ NS_IsMainThread());
+
+ MessageChannel* dummy;
+ MessageChannel*& blockingVar =
+ mSide == ChildSide && NS_IsMainThread() ? gParentProcessBlocker : dummy;
+
+ Result rv;
+ {
+ AutoSetValue<MessageChannel*> blocked(blockingVar, this);
+ rv = aProxy->Get()->OnMessageReceived(aMsg, aReply);
+ }
+
+ uint32_t latencyMs = round((TimeStamp::Now() - start).ToMilliseconds());
+ if (latencyMs >= kMinTelemetrySyncIPCLatencyMs) {
+ Telemetry::Accumulate(Telemetry::IPC_SYNC_RECEIVE_MS,
+ nsDependentCString(aMsg.name()), latencyMs);
+ }
+
+ if (!MaybeHandleError(rv, aMsg, "DispatchSyncMessage")) {
+ aReply = Message::ForSyncDispatchError(aMsg.nested_level());
+ }
+ aReply->set_seqno(aMsg.seqno());
+ aReply->set_transaction_id(aMsg.transaction_id());
+}
+
+void MessageChannel::DispatchAsyncMessage(ActorLifecycleProxy* aProxy,
+ const Message& aMsg) {
+ AssertWorkerThread();
+ MOZ_RELEASE_ASSERT(!aMsg.is_sync());
+
+ if (aMsg.routing_id() == MSG_ROUTING_NONE) {
+ NS_WARNING("unhandled special message!");
+ MaybeHandleError(MsgNotKnown, aMsg, "DispatchAsyncMessage");
+ return;
+ }
+
+ Result rv;
+ {
+ int nestedLevel = aMsg.nested_level();
+ AutoSetValue<bool> async(mDispatchingAsyncMessage, true);
+ AutoSetValue<int> nestedLevelSet(mDispatchingAsyncMessageNestedLevel,
+ nestedLevel);
+ rv = aProxy->Get()->OnMessageReceived(aMsg);
+ }
+ MaybeHandleError(rv, aMsg, "DispatchAsyncMessage");
+}
+
+void MessageChannel::EnqueuePendingMessages() {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ // XXX performance tuning knob: could process all or k pending
+ // messages here, rather than enqueuing for later processing
+
+ RepostAllMessages();
+}
+
+bool MessageChannel::WaitResponse(bool aWaitTimedOut) {
+ AssertWorkerThread();
+ if (aWaitTimedOut) {
+ if (mInTimeoutSecondHalf) {
+ // We've really timed out this time.
+ return false;
+ }
+ // Try a second time.
+ mInTimeoutSecondHalf = true;
+ } else {
+ mInTimeoutSecondHalf = false;
+ }
+ return true;
+}
+
+#ifndef OS_WIN
+bool MessageChannel::WaitForSyncNotify() {
+ AssertWorkerThread();
+# ifdef DEBUG
+ // WARNING: We don't release the lock here. We can't because the link
+ // could signal at this time and we would miss it. Instead we require
+ // ArtificialTimeout() to be extremely simple.
+ if (mListener->ArtificialTimeout()) {
+ return false;
+ }
+# endif
+
+ MOZ_RELEASE_ASSERT(!mIsSameThreadChannel,
+ "Wait on same-thread channel will deadlock!");
+
+ TimeDuration timeout = (kNoTimeout == mTimeoutMs)
+ ? TimeDuration::Forever()
+ : TimeDuration::FromMilliseconds(mTimeoutMs);
+ CVStatus status = mMonitor->Wait(timeout);
+
+ // If the timeout didn't expire, we know we received an event. The
+ // converse is not true.
+ return WaitResponse(status == CVStatus::Timeout);
+}
+
+void MessageChannel::NotifyWorkerThread() { mMonitor->Notify(); }
+#endif
+
+bool MessageChannel::ShouldContinueFromTimeout() {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ bool cont;
+ {
+ MonitorAutoUnlock unlock(*mMonitor);
+ cont = mListener->ShouldContinueFromReplyTimeout();
+ mListener->ArtificialSleep();
+ }
+
+ static enum {
+ UNKNOWN,
+ NOT_DEBUGGING,
+ DEBUGGING
+ } sDebuggingChildren = UNKNOWN;
+
+ if (sDebuggingChildren == UNKNOWN) {
+ sDebuggingChildren =
+ getenv("MOZ_DEBUG_CHILD_PROCESS") || getenv("MOZ_DEBUG_CHILD_PAUSE")
+ ? DEBUGGING
+ : NOT_DEBUGGING;
+ }
+ if (sDebuggingChildren == DEBUGGING) {
+ return true;
+ }
+
+ return cont;
+}
+
+void MessageChannel::SetReplyTimeoutMs(int32_t aTimeoutMs) {
+ // Set channel timeout value. Since this is broken up into
+ // two period, the minimum timeout value is 2ms.
+ AssertWorkerThread();
+ mTimeoutMs =
+ (aTimeoutMs <= 0) ? kNoTimeout : (int32_t)ceil((double)aTimeoutMs / 2.0);
+}
+
+void MessageChannel::ReportConnectionError(const char* aFunctionName,
+ const uint32_t aMsgType) const {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ const char* errorMsg = nullptr;
+ switch (mChannelState) {
+ case ChannelClosed:
+ errorMsg = "Closed channel: cannot send/recv";
+ break;
+ case ChannelClosing:
+ errorMsg = "Channel closing: too late to send, messages will be lost";
+ break;
+ case ChannelError:
+ errorMsg = "Channel error: cannot send/recv";
+ break;
+
+ default:
+ MOZ_CRASH("unreached");
+ }
+
+ // IPC connection errors are fairly common, especially "Channel closing: too
+ // late to send/recv, messages will be lost", so shouldn't be being reported
+ // on release builds, as that's misleading as to their severity.
+ NS_WARNING(nsPrintfCString("IPC Connection Error: [%s][%s] %s(msgname=%s) %s",
+ StringFromIPCSide(mSide), mName, aFunctionName,
+ IPC::StringFromIPCMessageType(aMsgType), errorMsg)
+ .get());
+
+ MonitorAutoUnlock unlock(*mMonitor);
+ mListener->ProcessingError(MsgDropped, errorMsg);
+}
+
+void MessageChannel::ReportMessageRouteError(const char* channelName) const {
+ PrintErrorMessage(mSide, channelName, "Need a route");
+ mListener->ProcessingError(MsgRouteError, "MsgRouteError");
+}
+
+bool MessageChannel::MaybeHandleError(Result code, const Message& aMsg,
+ const char* channelName) {
+ if (MsgProcessed == code) return true;
+
+ const char* errorMsg = nullptr;
+ switch (code) {
+ case MsgNotKnown:
+ errorMsg = "Unknown message: not processed";
+ break;
+ case MsgNotAllowed:
+ errorMsg = "Message not allowed: cannot be sent/recvd in this state";
+ break;
+ case MsgPayloadError:
+ errorMsg = "Payload error: message could not be deserialized";
+ break;
+ case MsgProcessingError:
+ errorMsg =
+ "Processing error: message was deserialized, but the handler "
+ "returned false (indicating failure)";
+ break;
+ case MsgRouteError:
+ errorMsg = "Route error: message sent to unknown actor ID";
+ break;
+ case MsgValueError:
+ errorMsg =
+ "Value error: message was deserialized, but contained an illegal "
+ "value";
+ break;
+
+ default:
+ MOZ_CRASH("unknown Result code");
+ return false;
+ }
+
+ char reason[512];
+ const char* msgname = aMsg.name();
+ if (msgname[0] == '?') {
+ SprintfLiteral(reason, "(msgtype=0x%X) %s", aMsg.type(), errorMsg);
+ } else {
+ SprintfLiteral(reason, "%s %s", msgname, errorMsg);
+ }
+
+ PrintErrorMessage(mSide, channelName, reason);
+
+ // Error handled in mozilla::ipc::IPCResult.
+ if (code == MsgProcessingError) {
+ return false;
+ }
+
+ mListener->ProcessingError(code, reason);
+
+ return false;
+}
+
+void MessageChannel::OnChannelErrorFromLink() {
+ mMonitor->AssertCurrentThreadOwns();
+ MOZ_ASSERT(mChannelState == ChannelConnected);
+
+ IPC_LOG("OnChannelErrorFromLink");
+
+ if (AwaitingSyncReply()) {
+ NotifyWorkerThread();
+ }
+
+ if (mAbortOnError) {
+ // mAbortOnError is set by main actors (e.g., ContentChild) to ensure
+ // that the process terminates even if normal shutdown is prevented.
+ // A MOZ_CRASH() here is not helpful because crash reporting relies
+ // on the parent process which we know is dead or otherwise unusable.
+ //
+ // Additionally, the parent process can (and often is) killed on Android
+ // when apps are backgrounded. We don't need to report a crash for
+ // normal behavior in that case.
+ printf_stderr("Exiting due to channel error.\n");
+ ProcessChild::QuickExit();
+ }
+ mChannelState = ChannelError;
+ mMonitor->Notify();
+
+ PostErrorNotifyTask();
+}
+
+void MessageChannel::NotifyMaybeChannelError(ReleasableMonitorAutoLock& aLock) {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+ aLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mChannelState != ChannelConnected);
+
+ if (ChannelClosing == mChannelState || ChannelClosed == mChannelState) {
+ // the channel closed, but we received a "Goodbye" message warning us
+ // about it. no worries
+ mChannelState = ChannelClosed;
+ NotifyChannelClosed(aLock);
+ return;
+ }
+
+ MOZ_ASSERT(ChannelError == mChannelState);
+
+ Clear();
+
+ // IPDL assumes these notifications do not fire twice, so we do not let
+ // that happen.
+ if (mNotifiedChannelDone) {
+ return;
+ }
+ mNotifiedChannelDone = true;
+
+ // Let our listener know that the channel errored. This may cause the
+ // channel to be deleted. Release our caller's `MonitorAutoLock` before
+ // invoking the listener, as this may call back into MessageChannel, and/or
+ // cause the channel to be destroyed.
+ aLock.Unlock();
+ mListener->OnChannelError();
+}
+
+void MessageChannel::OnNotifyMaybeChannelError() {
+ AssertWorkerThread();
+ mMonitor->AssertNotCurrentThreadOwns();
+
+ // This lock guard may be reset by `NotifyMaybeChannelError` before invoking
+ // listener callbacks which may destroy this `MessageChannel`.
+ //
+ // Acquiring the lock here also allows us to ensure that
+ // `OnChannelErrorFromLink` has finished running before this task is allowed
+ // to continue.
+ ReleasableMonitorAutoLock lock(*mMonitor);
+
+ mChannelErrorTask = nullptr;
+
+ if (IsOnCxxStack()) {
+ // This used to post a 10ms delayed task; however not all
+ // nsISerialEventTarget implementations support delayed dispatch.
+ // The delay being completely arbitrary, we may not as well have any.
+ PostErrorNotifyTask();
+ return;
+ }
+
+ // This call may destroy `this`.
+ NotifyMaybeChannelError(lock);
+}
+
+void MessageChannel::PostErrorNotifyTask() {
+ mMonitor->AssertCurrentThreadOwns();
+
+ if (mChannelErrorTask) {
+ return;
+ }
+
+ // This must be the last code that runs on this thread!
+ mChannelErrorTask = NewNonOwningCancelableRunnableMethod(
+ "ipc::MessageChannel::OnNotifyMaybeChannelError", this,
+ &MessageChannel::OnNotifyMaybeChannelError);
+ mWorkerThread->Dispatch(do_AddRef(mChannelErrorTask));
+}
+
+// Special async message.
+class GoodbyeMessage : public IPC::Message {
+ public:
+ GoodbyeMessage() : IPC::Message(MSG_ROUTING_NONE, GOODBYE_MESSAGE_TYPE) {}
+ static bool Read(const Message* msg) { return true; }
+ void Log(const std::string& aPrefix, FILE* aOutf) const {
+ fputs("(special `Goodbye' message)", aOutf);
+ }
+};
+
+void MessageChannel::CloseWithError() {
+ AssertWorkerThread();
+
+ // This lock guard may be reset by `NotifyMaybeChannelError` before invoking
+ // listener callbacks which may destroy this `MessageChannel`.
+ ReleasableMonitorAutoLock lock(*mMonitor);
+
+ switch (mChannelState) {
+ case ChannelError:
+ // Already errored, ensure we notify if we haven't yet.
+ NotifyMaybeChannelError(lock);
+ return;
+ case ChannelClosed:
+ // Already closed, we can't do anything.
+ return;
+ default:
+ // Either connected or closing, immediately convert to an error, and
+ // notify.
+ MOZ_ASSERT(mChannelState == ChannelConnected ||
+ mChannelState == ChannelClosing);
+ mLink->Close();
+ mChannelState = ChannelError;
+ NotifyMaybeChannelError(lock);
+ return;
+ }
+}
+
+void MessageChannel::NotifyImpendingShutdown() {
+ UniquePtr<Message> msg =
+ MakeUnique<Message>(MSG_ROUTING_NONE, IMPENDING_SHUTDOWN_MESSAGE_TYPE);
+ MonitorAutoLock lock(*mMonitor);
+ if (Connected()) {
+ SendMessageToLink(std::move(msg));
+ }
+}
+
+void MessageChannel::Close() {
+ AssertWorkerThread();
+ mMonitor->AssertNotCurrentThreadOwns();
+
+ // This lock guard may be reset by `Notify{ChannelClosed,MaybeChannelError}`
+ // before invoking listener callbacks which may destroy this `MessageChannel`.
+ ReleasableMonitorAutoLock lock(*mMonitor);
+
+ switch (mChannelState) {
+ case ChannelError:
+ // See bug 538586: if the listener gets deleted while the
+ // IO thread's NotifyChannelError event is still enqueued
+ // and subsequently deletes us, then the error event will
+ // also be deleted and the listener will never be notified
+ // of the channel error.
+ NotifyMaybeChannelError(lock);
+ return;
+ case ChannelClosed:
+ // Slightly unexpected but harmless; ignore. See bug 1554244.
+ return;
+
+ default:
+ // Notify the other side that we're about to close our socket. If we've
+ // already received a Goodbye from the other side (and our state is
+ // ChannelClosing), there's no reason to send one.
+ if (ChannelConnected == mChannelState) {
+ SendMessageToLink(MakeUnique<GoodbyeMessage>());
+ }
+ mLink->Close();
+ mChannelState = ChannelClosed;
+ NotifyChannelClosed(lock);
+ return;
+ }
+}
+
+void MessageChannel::NotifyChannelClosed(ReleasableMonitorAutoLock& aLock) {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+ aLock.AssertCurrentThreadOwns();
+
+ if (ChannelClosed != mChannelState) {
+ MOZ_CRASH("channel should have been closed!");
+ }
+
+ Clear();
+
+ // IPDL assumes these notifications do not fire twice, so we do not let
+ // that happen.
+ if (mNotifiedChannelDone) {
+ return;
+ }
+ mNotifiedChannelDone = true;
+
+ // Let our listener know that the channel was closed. This may cause the
+ // channel to be deleted. Release our caller's `MonitorAutoLock` before
+ // invoking the listener, as this may call back into MessageChannel, and/or
+ // cause the channel to be destroyed.
+ aLock.Unlock();
+ mListener->OnChannelClose();
+}
+
+void MessageChannel::DebugAbort(const char* file, int line, const char* cond,
+ const char* why, bool reply) {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ printf_stderr(
+ "###!!! [MessageChannel][%s][%s:%d] "
+ "Assertion (%s) failed. %s %s\n",
+ mSide == ChildSide ? "Child" : "Parent", file, line, cond, why,
+ reply ? "(reply)" : "");
+
+ MessageQueue pending = std::move(mPending);
+ while (!pending.isEmpty()) {
+ pending.getFirst()->AssertMonitorHeld(*mMonitor);
+ printf_stderr(" [ %s%s ]\n",
+ pending.getFirst()->Msg()->is_sync() ? "sync" : "async",
+ pending.getFirst()->Msg()->is_reply() ? "reply" : "");
+ pending.popFirst();
+ }
+
+ MOZ_CRASH_UNSAFE(why);
+}
+
+void MessageChannel::AddProfilerMarker(const IPC::Message& aMessage,
+ MessageDirection aDirection) {
+ mMonitor->AssertCurrentThreadOwns();
+
+ if (profiler_feature_active(ProfilerFeature::IPCMessages)) {
+ base::ProcessId pid = mListener->OtherPidMaybeInvalid();
+ // Only record markers for IPCs with a valid pid.
+ // And if one of the profiler mutexes is locked on this thread, don't record
+ // markers, because we don't want to expose profiler IPCs due to the
+ // profiler itself, and also to avoid possible re-entrancy issues.
+ if (pid != base::kInvalidProcessId &&
+ !profiler_is_locked_on_current_thread()) {
+ // The current timestamp must be given to the `IPCMarker` payload.
+ [[maybe_unused]] const TimeStamp now = TimeStamp::Now();
+ bool isThreadBeingProfiled =
+ profiler_thread_is_being_profiled_for_markers();
+ PROFILER_MARKER(
+ "IPC", IPC,
+ mozilla::MarkerOptions(
+ mozilla::MarkerTiming::InstantAt(now),
+ // If the thread is being profiled, add the marker to
+ // the current thread. If the thread is not being
+ // profiled, add the marker to the main thread. It
+ // will appear in the main thread's IPC track. Profiler analysis
+ // UI correlates all the IPC markers from different threads and
+ // generates processed markers.
+ isThreadBeingProfiled ? mozilla::MarkerThreadId::CurrentThread()
+ : mozilla::MarkerThreadId::MainThread()),
+ IPCMarker, now, now, pid, aMessage.seqno(), aMessage.type(), mSide,
+ aDirection, MessagePhase::Endpoint, aMessage.is_sync(),
+ // aOriginThreadId: If the thread is being profiled, do not include a
+ // thread ID, as it's the same as the markers. Only include this field
+ // when the marker is being sent from another thread.
+ isThreadBeingProfiled ? mozilla::MarkerThreadId{}
+ : mozilla::MarkerThreadId::CurrentThread());
+ }
+ }
+}
+
+void MessageChannel::EndTimeout() {
+ mMonitor->AssertCurrentThreadOwns();
+
+ IPC_LOG("Ending timeout of seqno=%d", mTimedOutMessageSeqno);
+ mTimedOutMessageSeqno = 0;
+ mTimedOutMessageNestedLevel = 0;
+
+ RepostAllMessages();
+}
+
+void MessageChannel::RepostAllMessages() {
+ mMonitor->AssertCurrentThreadOwns();
+
+ bool needRepost = false;
+ for (MessageTask* task : mPending) {
+ task->AssertMonitorHeld(*mMonitor);
+ if (!task->IsScheduled()) {
+ needRepost = true;
+ break;
+ }
+ }
+ if (!needRepost) {
+ // If everything is already scheduled to run, do nothing.
+ return;
+ }
+
+ // In some cases we may have deferred dispatch of some messages in the
+ // queue. Now we want to run them again. However, we can't just re-post
+ // those messages since the messages after them in mPending would then be
+ // before them in the event queue. So instead we cancel everything and
+ // re-post all messages in the correct order.
+ MessageQueue queue = std::move(mPending);
+ while (RefPtr<MessageTask> task = queue.popFirst()) {
+ task->AssertMonitorHeld(*mMonitor);
+ RefPtr<MessageTask> newTask = new MessageTask(this, std::move(task->Msg()));
+ newTask->AssertMonitorHeld(*mMonitor);
+ mPending.insertBack(newTask);
+ newTask->Post();
+ }
+
+ AssertMaybeDeferredCountCorrect();
+}
+
+void MessageChannel::CancelTransaction(int transaction) {
+ mMonitor->AssertCurrentThreadOwns();
+
+ // When we cancel a transaction, we need to behave as if there's no longer
+ // any IPC on the stack. Anything we were dispatching or sending will get
+ // canceled. Consequently, we have to update the state variables below.
+ //
+ // We also need to ensure that when any IPC functions on the stack return,
+ // they don't reset these values using an RAII class like AutoSetValue. To
+ // avoid that, these RAII classes check if the variable they set has been
+ // tampered with (by us). If so, they don't reset the variable to the old
+ // value.
+
+ IPC_LOG("CancelTransaction: xid=%d", transaction);
+
+ // An unusual case: We timed out a transaction which the other side then
+ // cancelled. In this case we just leave the timedout state and try to
+ // forget this ever happened.
+ if (transaction == mTimedOutMessageSeqno) {
+ IPC_LOG("Cancelled timed out message %d", mTimedOutMessageSeqno);
+ EndTimeout();
+
+ // Normally mCurrentTransaction == 0 here. But it can be non-zero if:
+ // 1. Parent sends NESTED_INSIDE_SYNC message H.
+ // 2. Parent times out H.
+ // 3. Child dispatches H and sends nested message H' (same transaction).
+ // 4. Parent dispatches H' and cancels.
+ MOZ_RELEASE_ASSERT(!mTransactionStack ||
+ mTransactionStack->TransactionID() == transaction);
+ if (mTransactionStack) {
+ mTransactionStack->Cancel();
+ }
+ } else {
+ MOZ_RELEASE_ASSERT(mTransactionStack->TransactionID() == transaction);
+ mTransactionStack->Cancel();
+ }
+
+ bool foundSync = false;
+ for (MessageTask* p = mPending.getFirst(); p;) {
+ p->AssertMonitorHeld(*mMonitor);
+ UniquePtr<Message>& msg = p->Msg();
+
+ // If there was a race between the parent and the child, then we may
+ // have a queued sync message. We want to drop this message from the
+ // queue since if will get cancelled along with the transaction being
+ // cancelled. This happens if the message in the queue is
+ // NESTED_INSIDE_SYNC.
+ if (msg->is_sync() && msg->nested_level() != IPC::Message::NOT_NESTED) {
+ MOZ_RELEASE_ASSERT(!foundSync);
+ MOZ_RELEASE_ASSERT(msg->transaction_id() != transaction);
+ IPC_LOG("Removing msg from queue seqno=%d xid=%d", msg->seqno(),
+ msg->transaction_id());
+ foundSync = true;
+ if (!IsAlwaysDeferred(*msg)) {
+ mMaybeDeferredPendingCount--;
+ }
+ p = p->removeAndGetNext();
+ continue;
+ }
+
+ p = p->getNext();
+ }
+
+ AssertMaybeDeferredCountCorrect();
+}
+
+void MessageChannel::CancelCurrentTransaction() {
+ MonitorAutoLock lock(*mMonitor);
+ if (DispatchingSyncMessageNestedLevel() >= IPC::Message::NESTED_INSIDE_SYNC) {
+ if (DispatchingSyncMessageNestedLevel() ==
+ IPC::Message::NESTED_INSIDE_CPOW ||
+ DispatchingAsyncMessageNestedLevel() ==
+ IPC::Message::NESTED_INSIDE_CPOW) {
+ mListener->IntentionalCrash();
+ }
+
+ IPC_LOG("Cancel requested: current xid=%d",
+ CurrentNestedInsideSyncTransaction());
+ MOZ_RELEASE_ASSERT(DispatchingSyncMessage());
+ auto cancel =
+ MakeUnique<CancelMessage>(CurrentNestedInsideSyncTransaction());
+ CancelTransaction(CurrentNestedInsideSyncTransaction());
+ SendMessageToLink(std::move(cancel));
+ }
+}
+
+void CancelCPOWs() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (gParentProcessBlocker) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::IPC_TRANSACTION_CANCEL,
+ true);
+ gParentProcessBlocker->CancelCurrentTransaction();
+ }
+}
+
+bool MessageChannel::IsCrossProcess() const {
+ mMonitor->AssertCurrentThreadOwns();
+ return mIsCrossProcess;
+}
+
+void MessageChannel::SetIsCrossProcess(bool aIsCrossProcess) {
+ mMonitor->AssertCurrentThreadOwns();
+ if (aIsCrossProcess == mIsCrossProcess) {
+ return;
+ }
+ mIsCrossProcess = aIsCrossProcess;
+ if (mIsCrossProcess) {
+ ChannelCountReporter::Increment(mName);
+ } else {
+ ChannelCountReporter::Decrement(mName);
+ }
+}
+
+NS_IMPL_ISUPPORTS(MessageChannel::WorkerTargetShutdownTask,
+ nsITargetShutdownTask)
+
+MessageChannel::WorkerTargetShutdownTask::WorkerTargetShutdownTask(
+ nsISerialEventTarget* aTarget, MessageChannel* aChannel)
+ : mTarget(aTarget), mChannel(aChannel) {}
+
+void MessageChannel::WorkerTargetShutdownTask::TargetShutdown() {
+ MOZ_RELEASE_ASSERT(mTarget->IsOnCurrentThread());
+ IPC_LOG("Closing channel due to event target shutdown");
+ if (MessageChannel* channel = std::exchange(mChannel, nullptr)) {
+ channel->Close();
+ }
+}
+
+void MessageChannel::WorkerTargetShutdownTask::Clear() {
+ MOZ_RELEASE_ASSERT(mTarget->IsOnCurrentThread());
+ mChannel = nullptr;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(MessageChannel::FlushLazySendMessagesRunnable,
+ CancelableRunnable)
+
+MessageChannel::FlushLazySendMessagesRunnable::FlushLazySendMessagesRunnable(
+ MessageChannel* aChannel)
+ : CancelableRunnable("MessageChannel::FlushLazyMessagesRunnable"),
+ mChannel(aChannel) {}
+
+NS_IMETHODIMP MessageChannel::FlushLazySendMessagesRunnable::Run() {
+ if (mChannel) {
+ MonitorAutoLock lock(*mChannel->mMonitor);
+ MOZ_ASSERT(mChannel->mFlushLazySendTask == this);
+ mChannel->FlushLazySendMessages();
+ }
+ return NS_OK;
+}
+
+nsresult MessageChannel::FlushLazySendMessagesRunnable::Cancel() {
+ mQueue.Clear();
+ mChannel = nullptr;
+ return NS_OK;
+}
+
+void MessageChannel::FlushLazySendMessagesRunnable::PushMessage(
+ UniquePtr<Message> aMsg) {
+ MOZ_ASSERT(mChannel);
+ mQueue.AppendElement(std::move(aMsg));
+}
+
+nsTArray<UniquePtr<IPC::Message>>
+MessageChannel::FlushLazySendMessagesRunnable::TakeMessages() {
+ mChannel = nullptr;
+ return std::move(mQueue);
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/MessageChannel.h b/ipc/glue/MessageChannel.h
new file mode 100644
index 0000000000..e52ce7974f
--- /dev/null
+++ b/ipc/glue/MessageChannel.h
@@ -0,0 +1,878 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=4 et :
+ */
+/* 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/. */
+
+#ifndef ipc_glue_MessageChannel_h
+#define ipc_glue_MessageChannel_h 1
+
+#include "ipc/EnumSerializer.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/BaseProfilerMarkers.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Vector.h"
+#if defined(OS_WIN)
+# include "mozilla/ipc/Neutering.h"
+#endif // defined(OS_WIN)
+
+#include <functional>
+#include <map>
+#include <stack>
+#include <vector>
+
+#include "MessageLink.h" // for HasResultCodes
+#include "mozilla/ipc/ScopedPort.h"
+#include "nsITargetShutdownTask.h"
+
+#ifdef FUZZING_SNAPSHOT
+# include "mozilla/fuzzing/IPCFuzzController.h"
+#endif
+
+class MessageLoop;
+
+namespace IPC {
+template <typename T>
+struct ParamTraits;
+}
+
+namespace mozilla {
+namespace ipc {
+
+class IToplevelProtocol;
+class ActorLifecycleProxy;
+
+class RefCountedMonitor : public Monitor {
+ public:
+ RefCountedMonitor() : Monitor("mozilla.ipc.MessageChannel.mMonitor") {}
+
+ void AssertSameMonitor(const RefCountedMonitor& aOther) const
+ MOZ_REQUIRES(*this) MOZ_ASSERT_CAPABILITY(aOther) {
+ MOZ_ASSERT(this == &aOther);
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefCountedMonitor)
+
+ private:
+ ~RefCountedMonitor() = default;
+};
+
+enum class MessageDirection {
+ eSending,
+ eReceiving,
+};
+
+enum class MessagePhase {
+ Endpoint,
+ TransferStart,
+ TransferEnd,
+};
+
+enum class SyncSendError {
+ SendSuccess,
+ PreviousTimeout,
+ SendingCPOWWhileDispatchingSync,
+ SendingCPOWWhileDispatchingUrgent,
+ NotConnectedBeforeSend,
+ DisconnectedDuringSend,
+ CancelledBeforeSend,
+ CancelledAfterSend,
+ TimedOut,
+ ReplyError,
+};
+
+enum class ResponseRejectReason {
+ SendError,
+ ChannelClosed,
+ HandlerRejected,
+ ActorDestroyed,
+ ResolverDestroyed,
+ EndGuard_,
+};
+
+template <typename T>
+using ResolveCallback = std::function<void(T&&)>;
+
+using RejectCallback = std::function<void(ResponseRejectReason)>;
+
+enum ChannelState {
+ ChannelClosed,
+ ChannelConnected,
+ ChannelClosing,
+ ChannelError
+};
+
+class AutoEnterTransaction;
+
+class MessageChannel : HasResultCodes {
+ friend class PortLink;
+
+ typedef mozilla::Monitor Monitor;
+
+ public:
+ using Message = IPC::Message;
+
+ struct UntypedCallbackHolder {
+ UntypedCallbackHolder(int32_t aActorId, Message::msgid_t aReplyMsgId,
+ RejectCallback&& aReject)
+ : mActorId(aActorId),
+ mReplyMsgId(aReplyMsgId),
+ mReject(std::move(aReject)) {}
+
+ virtual ~UntypedCallbackHolder() = default;
+
+ void Reject(ResponseRejectReason&& aReason) { mReject(std::move(aReason)); }
+
+ int32_t mActorId;
+ Message::msgid_t mReplyMsgId;
+ RejectCallback mReject;
+ };
+
+ template <typename Value>
+ struct CallbackHolder : public UntypedCallbackHolder {
+ CallbackHolder(int32_t aActorId, Message::msgid_t aReplyMsgId,
+ ResolveCallback<Value>&& aResolve, RejectCallback&& aReject)
+ : UntypedCallbackHolder(aActorId, aReplyMsgId, std::move(aReject)),
+ mResolve(std::move(aResolve)) {}
+
+ void Resolve(Value&& aReason) { mResolve(std::move(aReason)); }
+
+ ResolveCallback<Value> mResolve;
+ };
+
+ private:
+ static Atomic<size_t> gUnresolvedResponses;
+ friend class PendingResponseReporter;
+
+ public:
+ static constexpr int32_t kNoTimeout = INT32_MIN;
+
+ using ScopedPort = mozilla::ipc::ScopedPort;
+
+ explicit MessageChannel(const char* aName, IToplevelProtocol* aListener);
+ ~MessageChannel();
+
+ IToplevelProtocol* Listener() const { return mListener; }
+
+ // Returns the event target which the worker lives on and must be used for
+ // operations on the current thread. Only safe to access after the
+ // MessageChannel has been opened.
+ nsISerialEventTarget* GetWorkerEventTarget() const { return mWorkerThread; }
+
+ // "Open" a connection using an existing ScopedPort. The ScopedPort must be
+ // valid and connected to a remote.
+ //
+ // The `aEventTarget` parameter must be on the current thread.
+ bool Open(ScopedPort aPort, Side aSide, const nsID& aMessageChannelId,
+ nsISerialEventTarget* aEventTarget = nullptr);
+
+ // "Open" a connection to another thread in the same process.
+ //
+ // Returns true if the transport layer was successfully connected,
+ // i.e., mChannelState == ChannelConnected.
+ //
+ // For more details on the process of opening a channel between
+ // threads, see the extended comment on this function
+ // in MessageChannel.cpp.
+ bool Open(MessageChannel* aTargetChan, nsISerialEventTarget* aEventTarget,
+ Side aSide);
+
+ // "Open" a connection to an actor on the current thread.
+ //
+ // Returns true if the transport layer was successfully connected,
+ // i.e., mChannelState == ChannelConnected.
+ //
+ // Same-thread channels may not perform synchronous or blocking message
+ // sends, to avoid deadlocks.
+ bool OpenOnSameThread(MessageChannel* aTargetChan, Side aSide);
+
+ /**
+ * This sends a special message that is processed on the IO thread, so that
+ * other actors can know that the process will soon shutdown.
+ */
+ void NotifyImpendingShutdown() MOZ_EXCLUDES(*mMonitor);
+
+ // Close the underlying transport channel.
+ void Close() MOZ_EXCLUDES(*mMonitor);
+
+ // Close the underlying transport channel, treating the closure as a
+ // connection error.
+ void CloseWithError() MOZ_EXCLUDES(*mMonitor);
+
+ void SetAbortOnError(bool abort) MOZ_EXCLUDES(*mMonitor) {
+ MonitorAutoLock lock(*mMonitor);
+ mAbortOnError = abort;
+ }
+
+ // Call aInvoke for each pending message until it returns false.
+ // XXX: You must get permission from an IPC peer to use this function
+ // since it requires custom deserialization and re-orders events.
+ void PeekMessages(const std::function<bool(const Message& aMsg)>& aInvoke)
+ MOZ_EXCLUDES(*mMonitor);
+
+ // Misc. behavioral traits consumers can request for this channel
+ enum ChannelFlags {
+ REQUIRE_DEFAULT = 0,
+ // Windows: if this channel operates on the UI thread, indicates
+ // WindowsMessageLoop code should enable deferred native message
+ // handling to prevent deadlocks. Should only be used for protocols
+ // that manage child processes which might create native UI, like
+ // plugins.
+ REQUIRE_DEFERRED_MESSAGE_PROTECTION = 1 << 0,
+ };
+ void SetChannelFlags(ChannelFlags aFlags) { mFlags = aFlags; }
+ ChannelFlags GetChannelFlags() { return mFlags; }
+
+ // Asynchronously send a message to the other side of the channel
+ bool Send(UniquePtr<Message> aMsg) MOZ_EXCLUDES(*mMonitor);
+
+ // Asynchronously send a message to the other side of the channel
+ // and wait for asynchronous reply.
+ template <typename Value>
+ void Send(UniquePtr<Message> aMsg, int32_t aActorId,
+ Message::msgid_t aReplyMsgId, ResolveCallback<Value>&& aResolve,
+ RejectCallback&& aReject) MOZ_EXCLUDES(*mMonitor) {
+ int32_t seqno = NextSeqno();
+ aMsg->set_seqno(seqno);
+ if (!Send(std::move(aMsg))) {
+ aReject(ResponseRejectReason::SendError);
+ return;
+ }
+
+ UniquePtr<UntypedCallbackHolder> callback =
+ MakeUnique<CallbackHolder<Value>>(
+ aActorId, aReplyMsgId, std::move(aResolve), std::move(aReject));
+ mPendingResponses.insert(std::make_pair(seqno, std::move(callback)));
+ gUnresolvedResponses++;
+ }
+
+ bool SendBuildIDsMatchMessage(const char* aParentBuildID)
+ MOZ_EXCLUDES(*mMonitor);
+ bool DoBuildIDsMatch() MOZ_EXCLUDES(*mMonitor) {
+ MonitorAutoLock lock(*mMonitor);
+ return mBuildIDsConfirmedMatch;
+ }
+
+ // Synchronously send |aMsg| (i.e., wait for |aReply|)
+ bool Send(UniquePtr<Message> aMsg, UniquePtr<Message>* aReply)
+ MOZ_EXCLUDES(*mMonitor);
+
+ bool CanSend() const MOZ_EXCLUDES(*mMonitor);
+
+ // Remove and return a callback that needs reply
+ UniquePtr<UntypedCallbackHolder> PopCallback(const Message& aMsg,
+ int32_t aActorId);
+
+ // Used to reject and remove pending responses owned by the given
+ // actor when it's about to be destroyed.
+ void RejectPendingResponsesForActor(int32_t aActorId);
+
+ // If sending a sync message returns an error, this function gives a more
+ // descriptive error message.
+ SyncSendError LastSendError() const {
+ AssertWorkerThread();
+ return mLastSendError;
+ }
+
+ void SetReplyTimeoutMs(int32_t aTimeoutMs);
+
+ bool IsOnCxxStack() const { return mOnCxxStack; }
+
+ void CancelCurrentTransaction() MOZ_EXCLUDES(*mMonitor);
+
+ // IsClosed and NumQueuedMessages are safe to call from any thread, but
+ // may provide an out-of-date value.
+ bool IsClosed() MOZ_EXCLUDES(*mMonitor) {
+ MonitorAutoLock lock(*mMonitor);
+ return IsClosedLocked();
+ }
+ bool IsClosedLocked() const MOZ_REQUIRES(*mMonitor) {
+ mMonitor->AssertCurrentThreadOwns();
+ return mLink ? mLink->IsClosed() : true;
+ }
+
+ static bool IsPumpingMessages() { return sIsPumpingMessages; }
+ static void SetIsPumpingMessages(bool aIsPumping) {
+ sIsPumpingMessages = aIsPumping;
+ }
+
+ /**
+ * Does this MessageChannel currently cross process boundaries?
+ */
+ bool IsCrossProcess() const MOZ_REQUIRES(*mMonitor);
+ void SetIsCrossProcess(bool aIsCrossProcess) MOZ_REQUIRES(*mMonitor);
+
+ nsID GetMessageChannelId() const {
+ MonitorAutoLock lock(*mMonitor);
+ return mMessageChannelId;
+ }
+
+#ifdef FUZZING_SNAPSHOT
+ Maybe<mojo::core::ports::PortName> GetPortName() {
+ MonitorAutoLock lock(*mMonitor);
+ return mLink ? mLink->GetPortName() : Nothing();
+ }
+#endif
+
+#ifdef OS_WIN
+ struct MOZ_STACK_CLASS SyncStackFrame {
+ explicit SyncStackFrame(MessageChannel* channel);
+ ~SyncStackFrame();
+
+ bool mSpinNestedEvents;
+ bool mListenerNotified;
+ MessageChannel* mChannel;
+
+ // The previous stack frame for this channel.
+ SyncStackFrame* mPrev;
+
+ // The previous stack frame on any channel.
+ SyncStackFrame* mStaticPrev;
+ };
+ friend struct MessageChannel::SyncStackFrame;
+
+ static bool IsSpinLoopActive() {
+ for (SyncStackFrame* frame = sStaticTopFrame; frame; frame = frame->mPrev) {
+ if (frame->mSpinNestedEvents) return true;
+ }
+ return false;
+ }
+
+ protected:
+ // The deepest sync stack frame for this channel.
+ SyncStackFrame* mTopFrame = nullptr;
+
+ bool mIsSyncWaitingOnNonMainThread = false;
+
+ // The deepest sync stack frame on any channel.
+ static SyncStackFrame* sStaticTopFrame;
+
+ public:
+ void ProcessNativeEventsInInterruptCall();
+ static void NotifyGeckoEventDispatch();
+
+ private:
+ void SpinInternalEventLoop();
+#endif // defined(OS_WIN)
+
+ private:
+ void PostErrorNotifyTask() MOZ_REQUIRES(*mMonitor);
+ void OnNotifyMaybeChannelError() MOZ_EXCLUDES(*mMonitor);
+ void ReportConnectionError(const char* aFunctionName,
+ const uint32_t aMsgTyp) const
+ MOZ_REQUIRES(*mMonitor);
+ void ReportMessageRouteError(const char* channelName) const
+ MOZ_EXCLUDES(*mMonitor);
+ bool MaybeHandleError(Result code, const Message& aMsg,
+ const char* channelName) MOZ_EXCLUDES(*mMonitor);
+
+ void Clear() MOZ_REQUIRES(*mMonitor);
+
+ bool HasPendingEvents() MOZ_REQUIRES(*mMonitor);
+
+ void ProcessPendingRequests(ActorLifecycleProxy* aProxy,
+ AutoEnterTransaction& aTransaction)
+ MOZ_REQUIRES(*mMonitor);
+ bool ProcessPendingRequest(ActorLifecycleProxy* aProxy,
+ UniquePtr<Message> aUrgent)
+ MOZ_REQUIRES(*mMonitor);
+
+ void EnqueuePendingMessages() MOZ_REQUIRES(*mMonitor);
+
+ // Dispatches an incoming message to its appropriate handler.
+ void DispatchMessage(ActorLifecycleProxy* aProxy, UniquePtr<Message> aMsg)
+ MOZ_REQUIRES(*mMonitor);
+
+ // DispatchMessage will route to one of these functions depending on the
+ // protocol type of the message.
+ void DispatchSyncMessage(ActorLifecycleProxy* aProxy, const Message& aMsg,
+ UniquePtr<Message>& aReply) MOZ_EXCLUDES(*mMonitor);
+ void DispatchAsyncMessage(ActorLifecycleProxy* aProxy, const Message& aMsg)
+ MOZ_EXCLUDES(*mMonitor);
+
+ // Return true if the wait ended because a notification was received.
+ //
+ // Return false if the time elapsed from when we started the process of
+ // waiting until afterwards exceeded the currently allotted timeout.
+ // That *DOES NOT* mean false => "no event" (== timeout); there are many
+ // circumstances that could cause the measured elapsed time to exceed the
+ // timeout EVEN WHEN we were notified.
+ //
+ // So in sum: true is a meaningful return value; false isn't,
+ // necessarily.
+ bool WaitForSyncNotify() MOZ_REQUIRES(*mMonitor);
+
+ bool WaitResponse(bool aWaitTimedOut);
+
+ bool ShouldContinueFromTimeout() MOZ_REQUIRES(*mMonitor);
+
+ void EndTimeout() MOZ_REQUIRES(*mMonitor);
+ void CancelTransaction(int transaction) MOZ_REQUIRES(*mMonitor);
+
+ void RepostAllMessages() MOZ_REQUIRES(*mMonitor);
+
+ int32_t NextSeqno() {
+ AssertWorkerThread();
+ return (mSide == ChildSide) ? --mNextSeqno : ++mNextSeqno;
+ }
+
+ void DebugAbort(const char* file, int line, const char* cond, const char* why,
+ bool reply = false) MOZ_REQUIRES(*mMonitor);
+
+ void AddProfilerMarker(const IPC::Message& aMessage,
+ MessageDirection aDirection) MOZ_REQUIRES(*mMonitor);
+
+ private:
+ // Returns true if we're dispatching an async message's callback.
+ bool DispatchingAsyncMessage() const {
+ AssertWorkerThread();
+ return mDispatchingAsyncMessage;
+ }
+
+ int DispatchingAsyncMessageNestedLevel() const {
+ AssertWorkerThread();
+ return mDispatchingAsyncMessageNestedLevel;
+ }
+
+ // Check if there is still a live connection to our peer. This may change to
+ // `false` at any time due to the connection to our peer being closed or
+ // dropped (e.g. due to a crash).
+ bool Connected() const MOZ_REQUIRES(*mMonitor);
+
+ // Check if there is either still a live connection to our peer, or we have
+ // received a `Goodbye` from our peer, and are actively shutting down our
+ // connection with our peer.
+ bool ConnectedOrClosing() const MOZ_REQUIRES(*mMonitor);
+
+ private:
+ // Executed on the IO thread.
+ void NotifyWorkerThread() MOZ_REQUIRES(*mMonitor);
+
+ // Return true if |aMsg| is a special message targeted at the IO
+ // thread, in which case it shouldn't be delivered to the worker.
+ bool MaybeInterceptSpecialIOMessage(const Message& aMsg)
+ MOZ_REQUIRES(*mMonitor);
+
+ // Returns true if ShouldDeferMessage(aMsg) is guaranteed to return true.
+ // Otherwise, the result of ShouldDeferMessage(aMsg) may be true or false,
+ // depending on context.
+ static bool IsAlwaysDeferred(const Message& aMsg);
+
+ // Helper for sending a message via the link. If the message is [LazySend], it
+ // will be queued, and if the message is not-[LazySend], it will flush any
+ // pending [LazySend] messages.
+ void SendMessageToLink(UniquePtr<Message> aMsg) MOZ_REQUIRES(*mMonitor);
+
+ // Called to flush [LazySend] messages to the link.
+ void FlushLazySendMessages() MOZ_REQUIRES(*mMonitor);
+
+ bool WasTransactionCanceled(int transaction);
+ bool ShouldDeferMessage(const Message& aMsg) MOZ_REQUIRES(*mMonitor);
+ void OnMessageReceivedFromLink(UniquePtr<Message> aMsg)
+ MOZ_REQUIRES(*mMonitor);
+ void OnChannelErrorFromLink() MOZ_REQUIRES(*mMonitor);
+
+ private:
+ // Clear this channel, and notify the listener that the channel has either
+ // closed or errored.
+ //
+ // These methods must be called on the worker thread, passing in a
+ // `ReleasableMonitorAutoLock`. This lock guard will be reset before the
+ // listener is called, allowing for the monitor to be unlocked before the
+ // MessageChannel is potentially destroyed.
+ void NotifyChannelClosed(ReleasableMonitorAutoLock& aLock)
+ MOZ_REQUIRES(*mMonitor);
+ void NotifyMaybeChannelError(ReleasableMonitorAutoLock& aLock)
+ MOZ_REQUIRES(*mMonitor);
+
+ private:
+ void AssertWorkerThread() const {
+ MOZ_ASSERT(mWorkerThread, "Channel hasn't been opened yet");
+ MOZ_RELEASE_ASSERT(mWorkerThread && mWorkerThread->IsOnCurrentThread(),
+ "not on worker thread!");
+ }
+
+ private:
+ class MessageTask : public CancelableRunnable,
+ public LinkedListElement<RefPtr<MessageTask>>,
+ public nsIRunnablePriority,
+ public nsIRunnableIPCMessageType {
+ public:
+ explicit MessageTask(MessageChannel* aChannel, UniquePtr<Message> aMessage);
+ MessageTask() = delete;
+ MessageTask(const MessageTask&) = delete;
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD Run() override;
+ nsresult Cancel() override;
+ NS_IMETHOD GetPriority(uint32_t* aPriority) override;
+ NS_DECL_NSIRUNNABLEIPCMESSAGETYPE
+ void Post() MOZ_REQUIRES(*mMonitor);
+
+ bool IsScheduled() const MOZ_REQUIRES(*mMonitor) {
+ mMonitor->AssertCurrentThreadOwns();
+ return mScheduled;
+ }
+
+ UniquePtr<Message>& Msg() MOZ_REQUIRES(*mMonitor) {
+ MOZ_DIAGNOSTIC_ASSERT(mMessage, "message was moved");
+ return mMessage;
+ }
+ const UniquePtr<Message>& Msg() const MOZ_REQUIRES(*mMonitor) {
+ MOZ_DIAGNOSTIC_ASSERT(mMessage, "message was moved");
+ return mMessage;
+ }
+
+ void AssertMonitorHeld(const RefCountedMonitor& aMonitor)
+ MOZ_REQUIRES(aMonitor) MOZ_ASSERT_CAPABILITY(*mMonitor) {
+ aMonitor.AssertSameMonitor(*mMonitor);
+ }
+
+ private:
+ ~MessageTask();
+
+ MessageChannel* Channel() MOZ_REQUIRES(*mMonitor) {
+ mMonitor->AssertCurrentThreadOwns();
+ MOZ_RELEASE_ASSERT(isInList());
+ return mChannel;
+ }
+
+ // The connected MessageChannel's monitor. Guards `mChannel` and
+ // `mScheduled`.
+ RefPtr<RefCountedMonitor> const mMonitor;
+ // The channel which this MessageTask is associated with. Only valid while
+ // `mMonitor` is held, and this MessageTask `isInList()`.
+ MessageChannel* const mChannel;
+ UniquePtr<Message> mMessage MOZ_GUARDED_BY(*mMonitor);
+ uint32_t const mPriority;
+ bool mScheduled : 1 MOZ_GUARDED_BY(*mMonitor);
+#ifdef FUZZING_SNAPSHOT
+ const bool mIsFuzzMsg;
+ bool mFuzzStopped MOZ_GUARDED_BY(*mMonitor);
+#endif
+ };
+
+ bool ShouldRunMessage(const Message& aMsg) MOZ_REQUIRES(*mMonitor);
+ void RunMessage(ActorLifecycleProxy* aProxy, MessageTask& aTask)
+ MOZ_REQUIRES(*mMonitor);
+
+ class WorkerTargetShutdownTask final : public nsITargetShutdownTask {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ WorkerTargetShutdownTask(nsISerialEventTarget* aTarget,
+ MessageChannel* aChannel);
+
+ void TargetShutdown() override;
+ void Clear();
+
+ private:
+ ~WorkerTargetShutdownTask() = default;
+
+ const nsCOMPtr<nsISerialEventTarget> mTarget;
+ // Cleared by MessageChannel before it is destroyed.
+ MessageChannel* MOZ_NON_OWNING_REF mChannel;
+ };
+
+ class FlushLazySendMessagesRunnable final : public CancelableRunnable {
+ public:
+ explicit FlushLazySendMessagesRunnable(MessageChannel* aChannel);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD Run() override;
+ nsresult Cancel() override;
+
+ void PushMessage(UniquePtr<Message> aMsg);
+ nsTArray<UniquePtr<Message>> TakeMessages();
+
+ private:
+ ~FlushLazySendMessagesRunnable() = default;
+
+ // Cleared by MessageChannel before it is destroyed.
+ MessageChannel* MOZ_NON_OWNING_REF mChannel;
+
+ // LazySend messages which haven't been sent yet, but will be sent as soon
+ // as a non-LazySend message is sent, or this runnable is executed.
+ nsTArray<UniquePtr<Message>> mQueue;
+ };
+
+ typedef LinkedList<RefPtr<MessageTask>> MessageQueue;
+ typedef std::map<size_t, UniquePtr<UntypedCallbackHolder>> CallbackMap;
+ typedef IPC::Message::msgid_t msgid_t;
+
+ private:
+ // This will be a string literal, so lifetime is not an issue.
+ const char* const mName;
+
+ // ID for each MessageChannel. Set when it is opened, and never cleared
+ // afterwards.
+ //
+ // This ID is only intended for diagnostics, debugging, and reporting
+ // purposes, and shouldn't be used for message routing or permissions checks.
+ nsID mMessageChannelId MOZ_GUARDED_BY(*mMonitor) = {};
+
+ // Based on presumption the listener owns and overlives the channel,
+ // this is never nullified.
+ IToplevelProtocol* const mListener;
+
+ // This monitor guards all state in this MessageChannel, except where
+ // otherwise noted. It is refcounted so a reference to it can be shared with
+ // IPC listener objects which need to access weak references to this
+ // `MessageChannel`.
+ RefPtr<RefCountedMonitor> const mMonitor;
+
+ ChannelState mChannelState MOZ_GUARDED_BY(*mMonitor) = ChannelClosed;
+ Side mSide = UnknownSide;
+ bool mIsCrossProcess MOZ_GUARDED_BY(*mMonitor) = false;
+ UniquePtr<MessageLink> mLink MOZ_GUARDED_BY(*mMonitor);
+
+ // NotifyMaybeChannelError runnable
+ RefPtr<CancelableRunnable> mChannelErrorTask MOZ_GUARDED_BY(*mMonitor);
+
+ // Thread we are allowed to send and receive on. Set in Open(); never
+ // changed, and we can only call Open() once. We shouldn't be accessing
+ // from multiple threads before Open().
+ nsCOMPtr<nsISerialEventTarget> mWorkerThread;
+
+ // Shutdown task to close the channel before mWorkerThread goes away.
+ RefPtr<WorkerTargetShutdownTask> mShutdownTask MOZ_GUARDED_BY(*mMonitor);
+
+ // Task to force sending lazy messages when mQueuedLazyMessages is non-empty.
+ RefPtr<FlushLazySendMessagesRunnable> mFlushLazySendTask
+ MOZ_GUARDED_BY(*mMonitor);
+
+ // Timeout periods are broken up in two to prevent system suspension from
+ // triggering an abort. This method (called by WaitForEvent with a 'did
+ // timeout' flag) decides if we should wait again for half of mTimeoutMs
+ // or give up.
+ // only accessed on WorkerThread
+ int32_t mTimeoutMs = kNoTimeout;
+ bool mInTimeoutSecondHalf = false;
+
+ // Worker-thread only; sequence numbers for messages that require
+ // replies.
+ int32_t mNextSeqno = 0;
+
+ static bool sIsPumpingMessages;
+
+ // If ::Send returns false, this gives a more descriptive error.
+ SyncSendError mLastSendError = SyncSendError::SendSuccess;
+
+ template <class T>
+ class AutoSetValue {
+ public:
+ explicit AutoSetValue(T& var, const T& newValue)
+ : mVar(var), mPrev(var), mNew(newValue) {
+ mVar = newValue;
+ }
+ ~AutoSetValue() {
+ // The value may have been zeroed if the transaction was
+ // canceled. In that case we shouldn't return it to its previous
+ // value.
+ if (mVar == mNew) {
+ mVar = mPrev;
+ }
+ }
+
+ private:
+ T& mVar;
+ T mPrev;
+ T mNew;
+ };
+
+ bool mDispatchingAsyncMessage = false;
+ int mDispatchingAsyncMessageNestedLevel = 0;
+
+ // When we send an urgent request from the parent process, we could race
+ // with an RPC message that was issued by the child beforehand. In this
+ // case, if the parent were to wake up while waiting for the urgent reply,
+ // and process the RPC, it could send an additional urgent message. The
+ // child would wake up to process the urgent message (as it always will),
+ // then send a reply, which could be received by the parent out-of-order
+ // with respect to the first urgent reply.
+ //
+ // To address this problem, urgent or RPC requests are associated with a
+ // "transaction". Whenever one side of the channel wishes to start a
+ // chain of RPC/urgent messages, it allocates a new transaction ID. Any
+ // messages the parent receives, not apart of this transaction, are
+ // deferred. When issuing RPC/urgent requests on top of a started
+ // transaction, the initiating transaction ID is used.
+ //
+ // To ensure IDs are unique, we use sequence numbers for transaction IDs,
+ // which grow in opposite directions from child to parent.
+
+ friend class AutoEnterTransaction;
+ AutoEnterTransaction* mTransactionStack MOZ_GUARDED_BY(*mMonitor) = nullptr;
+
+ int32_t CurrentNestedInsideSyncTransaction() const MOZ_REQUIRES(*mMonitor);
+
+ bool AwaitingSyncReply() const MOZ_REQUIRES(*mMonitor);
+ int AwaitingSyncReplyNestedLevel() const MOZ_REQUIRES(*mMonitor);
+
+ bool DispatchingSyncMessage() const MOZ_REQUIRES(*mMonitor);
+ int DispatchingSyncMessageNestedLevel() const MOZ_REQUIRES(*mMonitor);
+
+#ifdef DEBUG
+ void AssertMaybeDeferredCountCorrect() MOZ_REQUIRES(*mMonitor);
+#else
+ void AssertMaybeDeferredCountCorrect() MOZ_REQUIRES(*mMonitor) {}
+#endif
+
+ // If a sync message times out, we store its sequence number here. Any
+ // future sync messages will fail immediately. Once the reply for original
+ // sync message is received, we allow sync messages again.
+ //
+ // When a message times out, nothing is done to inform the other side. The
+ // other side will eventually dispatch the message and send a reply. Our
+ // side is responsible for replying to all sync messages sent by the other
+ // side when it dispatches the timed out message. The response is always an
+ // error.
+ //
+ // A message is only timed out if it initiated a transaction. This avoids
+ // hitting a lot of corner cases with message nesting that we don't really
+ // care about.
+ int32_t mTimedOutMessageSeqno MOZ_GUARDED_BY(*mMonitor) = 0;
+ int mTimedOutMessageNestedLevel MOZ_GUARDED_BY(*mMonitor) = 0;
+
+ // Queue of all incoming messages.
+ //
+ // If both this side and the other side are functioning correctly, the other
+ // side can send as many async messages as it wants before sending us a
+ // blocking message. After sending a blocking message, the other side must be
+ // blocked, and thus can't send us any more messages until we process the sync
+ // in-msg.
+ //
+ MessageQueue mPending MOZ_GUARDED_BY(*mMonitor);
+
+ // The number of messages in mPending for which IsAlwaysDeferred is false
+ // (i.e., the number of messages that might not be deferred, depending on
+ // context).
+ size_t mMaybeDeferredPendingCount MOZ_GUARDED_BY(*mMonitor) = 0;
+
+ // Is there currently MessageChannel logic for this channel on the C++ stack?
+ // This member is only accessed on the worker thread, and so is not protected
+ // by mMonitor.
+ bool mOnCxxStack = false;
+
+ // Map of async Callbacks that are still waiting replies.
+ CallbackMap mPendingResponses;
+
+#ifdef OS_WIN
+ HANDLE mEvent;
+#endif
+
+ // Should the channel abort the process from the I/O thread when
+ // a channel error occurs?
+ bool mAbortOnError MOZ_GUARDED_BY(*mMonitor) = false;
+
+ // True if the listener has already been notified of a channel close or
+ // error.
+ bool mNotifiedChannelDone MOZ_GUARDED_BY(*mMonitor) = false;
+
+ // See SetChannelFlags
+ ChannelFlags mFlags = REQUIRE_DEFAULT;
+
+ bool mBuildIDsConfirmedMatch MOZ_GUARDED_BY(*mMonitor) = false;
+
+ // If this is true, both ends of this message channel have event targets
+ // on the same thread.
+ bool mIsSameThreadChannel = false;
+};
+
+void CancelCPOWs();
+
+} // namespace ipc
+} // namespace mozilla
+
+namespace IPC {
+template <>
+struct ParamTraits<mozilla::ipc::ResponseRejectReason>
+ : public ContiguousEnumSerializer<
+ mozilla::ipc::ResponseRejectReason,
+ mozilla::ipc::ResponseRejectReason::SendError,
+ mozilla::ipc::ResponseRejectReason::EndGuard_> {};
+} // namespace IPC
+
+namespace geckoprofiler::markers {
+
+struct IPCMarker {
+ static constexpr mozilla::Span<const char> MarkerTypeName() {
+ return mozilla::MakeStringSpan("IPC");
+ }
+ static void StreamJSONMarkerData(
+ mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
+ mozilla::TimeStamp aStart, mozilla::TimeStamp aEnd, int32_t aOtherPid,
+ int32_t aMessageSeqno, IPC::Message::msgid_t aMessageType,
+ mozilla::ipc::Side aSide, mozilla::ipc::MessageDirection aDirection,
+ mozilla::ipc::MessagePhase aPhase, bool aSync,
+ mozilla::MarkerThreadId aOriginThreadId) {
+ using namespace mozilla::ipc;
+ // This payload still streams a startTime and endTime property because it
+ // made the migration to MarkerTiming on the front-end easier.
+ aWriter.TimeProperty("startTime", aStart);
+ aWriter.TimeProperty("endTime", aEnd);
+
+ aWriter.IntProperty("otherPid", aOtherPid);
+ aWriter.IntProperty("messageSeqno", aMessageSeqno);
+ aWriter.StringProperty(
+ "messageType",
+ mozilla::MakeStringSpan(IPC::StringFromIPCMessageType(aMessageType)));
+ aWriter.StringProperty("side", IPCSideToString(aSide));
+ aWriter.StringProperty("direction",
+ aDirection == MessageDirection::eSending
+ ? mozilla::MakeStringSpan("sending")
+ : mozilla::MakeStringSpan("receiving"));
+ aWriter.StringProperty("phase", IPCPhaseToString(aPhase));
+ aWriter.BoolProperty("sync", aSync);
+ if (!aOriginThreadId.IsUnspecified()) {
+ // Tech note: If `ToNumber()` returns a uint64_t, the conversion to
+ // int64_t is "implementation-defined" before C++20. This is acceptable
+ // here, because this is a one-way conversion to a unique identifier
+ // that's used to visually separate data by thread on the front-end.
+ aWriter.IntProperty(
+ "threadId",
+ static_cast<int64_t>(aOriginThreadId.ThreadId().ToNumber()));
+ }
+ }
+ static mozilla::MarkerSchema MarkerTypeDisplay() {
+ return mozilla::MarkerSchema::SpecialFrontendLocation{};
+ }
+
+ private:
+ static mozilla::Span<const char> IPCSideToString(mozilla::ipc::Side aSide) {
+ switch (aSide) {
+ case mozilla::ipc::ParentSide:
+ return mozilla::MakeStringSpan("parent");
+ case mozilla::ipc::ChildSide:
+ return mozilla::MakeStringSpan("child");
+ case mozilla::ipc::UnknownSide:
+ return mozilla::MakeStringSpan("unknown");
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid IPC side");
+ return mozilla::MakeStringSpan("<invalid IPC side>");
+ }
+ }
+
+ static mozilla::Span<const char> IPCPhaseToString(
+ mozilla::ipc::MessagePhase aPhase) {
+ switch (aPhase) {
+ case mozilla::ipc::MessagePhase::Endpoint:
+ return mozilla::MakeStringSpan("endpoint");
+ case mozilla::ipc::MessagePhase::TransferStart:
+ return mozilla::MakeStringSpan("transferStart");
+ case mozilla::ipc::MessagePhase::TransferEnd:
+ return mozilla::MakeStringSpan("transferEnd");
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid IPC phase");
+ return mozilla::MakeStringSpan("<invalid IPC phase>");
+ }
+ }
+};
+
+} // namespace geckoprofiler::markers
+
+#endif // ifndef ipc_glue_MessageChannel_h
diff --git a/ipc/glue/MessageLink.cpp b/ipc/glue/MessageLink.cpp
new file mode 100644
index 0000000000..872ff48b27
--- /dev/null
+++ b/ipc/glue/MessageLink.cpp
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=4 et :
+ */
+/* 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/MessageLink.h"
+#include "mojo/core/ports/event.h"
+#include "mojo/core/ports/node.h"
+#include "mozilla/ipc/MessageChannel.h"
+#include "mozilla/ipc/BrowserProcessSubThread.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/ipc/NodeController.h"
+#include "chrome/common/ipc_channel.h"
+#include "base/task.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "nsDebug.h"
+#include "nsExceptionHandler.h"
+#include "nsISupportsImpl.h"
+#include "nsPrintfCString.h"
+#include "nsXULAppAPI.h"
+
+using namespace mozilla;
+
+namespace mozilla {
+namespace ipc {
+
+MessageLink::MessageLink(MessageChannel* aChan) : mChan(aChan) {}
+
+MessageLink::~MessageLink() {
+#ifdef DEBUG
+ mChan = nullptr;
+#endif
+}
+
+class PortLink::PortObserverThunk : public NodeController::PortObserver {
+ public:
+ PortObserverThunk(RefCountedMonitor* aMonitor, PortLink* aLink)
+ : mMonitor(aMonitor), mLink(aLink) {}
+
+ void OnPortStatusChanged() override {
+ MonitorAutoLock lock(*mMonitor);
+ if (mLink) {
+ mLink->OnPortStatusChanged();
+ }
+ }
+
+ private:
+ friend class PortLink;
+
+ // The monitor from our PortLink's MessageChannel. Guards access to `mLink`.
+ RefPtr<RefCountedMonitor> mMonitor;
+
+ // Cleared by `PortLink` in `PortLink::Clear()`.
+ PortLink* MOZ_NON_OWNING_REF mLink;
+};
+
+PortLink::PortLink(MessageChannel* aChan, ScopedPort aPort)
+ : MessageLink(aChan), mNode(aPort.Controller()), mPort(aPort.Release()) {
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ mObserver = new PortObserverThunk(mChan->mMonitor, this);
+ mNode->SetPortObserver(mPort, mObserver);
+
+ // Dispatch an event to the IO loop to trigger an initial
+ // `OnPortStatusChanged` to deliver any pending messages. This needs to be run
+ // asynchronously from a different thread (or in the case of a same-thread
+ // channel, from the current thread), for now due to assertions in
+ // `MessageChannel`.
+ nsCOMPtr<nsIRunnable> openRunnable = NewRunnableMethod(
+ "PortLink::Open", mObserver, &PortObserverThunk::OnPortStatusChanged);
+ if (aChan->mIsSameThreadChannel) {
+ aChan->mWorkerThread->Dispatch(openRunnable.forget());
+ } else {
+ XRE_GetIOMessageLoop()->PostTask(openRunnable.forget());
+ }
+}
+
+PortLink::~PortLink() {
+ MOZ_RELEASE_ASSERT(!mObserver, "PortLink destroyed without being closed!");
+}
+
+void PortLink::SendMessage(UniquePtr<Message> aMessage) {
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ if (aMessage->size() > IPC::Channel::kMaximumMessageSize) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCMessageName,
+ nsDependentCString(aMessage->name()));
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCMessageSize,
+ static_cast<unsigned int>(aMessage->size()));
+ MOZ_CRASH("IPC message size is too large");
+ }
+ aMessage->AssertAsLargeAsHeader();
+
+ RefPtr<PortObserverThunk> observer = mObserver;
+ if (!observer) {
+ NS_WARNING("Ignoring message to closed PortLink");
+ return;
+ }
+
+ // Make local copies of relevant member variables, so we can unlock the
+ // monitor for the rest of this function. This protects us in case `this` is
+ // deleted during the call (although that shouldn't happen in practice).
+ //
+ // We don't want the monitor to be held when calling into ports, as we may be
+ // re-entrantly called by our `PortObserverThunk` which will attempt to
+ // acquire the monitor.
+ RefPtr<RefCountedMonitor> monitor = mChan->mMonitor;
+ RefPtr<NodeController> node = mNode;
+ PortRef port = mPort;
+
+ bool ok = false;
+ monitor->AssertCurrentThreadOwns();
+ {
+ MonitorAutoUnlock guard(*monitor);
+ ok = node->SendUserMessage(port, std::move(aMessage));
+ }
+ if (!ok) {
+ // The send failed, but double-check that we weren't closed racily while
+ // sending, which could lead to an invalid state error.
+ if (observer->mLink) {
+ MOZ_CRASH("Invalid argument to SendUserMessage");
+ }
+ NS_WARNING("Message dropped as PortLink was closed");
+ }
+}
+
+void PortLink::Close() {
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ if (!mObserver) {
+ // We're already being closed.
+ return;
+ }
+
+ Clear();
+}
+
+void PortLink::Clear() {
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ // NOTE: We're calling into `ports` with our monitor held! Usually, this could
+ // lead to deadlocks due to the PortObserverThunk acquiring the lock
+ // re-entrantly, but is OK here as we're immediately clearing the port's
+ // observer. We shouldn't have issues with any re-entrant calls on this thread
+ // acquiring this MessageChannel's monitor.
+ //
+ // We also clear out the reference in `mObserver` back to this type so that
+ // notifications from other threads won't try to call us again once we release
+ // the monitor.
+ mNode->SetPortObserver(mPort, nullptr);
+ mObserver->mLink = nullptr;
+ mObserver = nullptr;
+ mNode->ClosePort(mPort);
+}
+
+void PortLink::OnPortStatusChanged() {
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ // Check if the port's remoteness status has updated, and tell our channel if
+ // it has.
+ if (Maybe<PortStatus> status = mNode->GetStatus(mPort);
+ status && status->peer_remote != mChan->IsCrossProcess()) {
+ mChan->SetIsCrossProcess(status->peer_remote);
+ }
+
+ while (mObserver) {
+ UniquePtr<IPC::Message> message;
+ if (!mNode->GetMessage(mPort, &message)) {
+ Clear();
+ mChan->OnChannelErrorFromLink();
+ return;
+ }
+ if (!message) {
+ return;
+ }
+
+ mChan->OnMessageReceivedFromLink(std::move(message));
+ }
+}
+
+bool PortLink::IsClosed() const {
+ if (Maybe<PortStatus> status = mNode->GetStatus(mPort)) {
+ return !(status->has_messages || status->receiving_messages);
+ }
+ return true;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/MessageLink.h b/ipc/glue/MessageLink.h
new file mode 100644
index 0000000000..854dc6e2f1
--- /dev/null
+++ b/ipc/glue/MessageLink.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=4 et :
+ */
+/* 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/. */
+
+#ifndef ipc_glue_MessageLink_h
+#define ipc_glue_MessageLink_h 1
+
+#include <cstdint>
+#include "base/message_loop.h"
+#include "mojo/core/ports/node.h"
+#include "mojo/core/ports/port_ref.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ipc/ScopedPort.h"
+
+namespace IPC {
+class Message;
+class MessageReader;
+class MessageWriter;
+} // namespace IPC
+
+namespace mozilla {
+namespace ipc {
+
+class MessageChannel;
+class NodeController;
+
+struct HasResultCodes {
+ enum Result {
+ MsgProcessed,
+ MsgDropped,
+ MsgNotKnown,
+ MsgNotAllowed,
+ MsgPayloadError,
+ MsgProcessingError,
+ MsgRouteError,
+ MsgValueError
+ };
+};
+
+enum Side : uint8_t { ParentSide, ChildSide, UnknownSide };
+
+class MessageLink {
+ public:
+ typedef IPC::Message Message;
+
+ explicit MessageLink(MessageChannel* aChan);
+ virtual ~MessageLink();
+
+ // n.b.: These methods all require that the channel monitor is
+ // held when they are invoked.
+ virtual void SendMessage(mozilla::UniquePtr<Message> msg) = 0;
+
+ // Synchronously close the connection, such that no further notifications will
+ // be delivered to the MessageChannel instance. Must be called with the
+ // channel monitor held.
+ virtual void Close() = 0;
+
+ virtual bool IsClosed() const = 0;
+
+#ifdef FUZZING_SNAPSHOT
+ virtual Maybe<mojo::core::ports::PortName> GetPortName() { return Nothing(); }
+#endif
+
+ protected:
+ MessageChannel* mChan;
+};
+
+class PortLink final : public MessageLink {
+ using PortRef = mojo::core::ports::PortRef;
+ using PortStatus = mojo::core::ports::PortStatus;
+ using UserMessage = mojo::core::ports::UserMessage;
+ using UserMessageEvent = mojo::core::ports::UserMessageEvent;
+
+ public:
+ PortLink(MessageChannel* aChan, ScopedPort aPort);
+ virtual ~PortLink();
+
+ void SendMessage(UniquePtr<Message> aMessage) override;
+ void Close() override;
+
+ bool IsClosed() const override;
+
+#ifdef FUZZING_SNAPSHOT
+ Maybe<mojo::core::ports::PortName> GetPortName() override {
+ return Some(mPort.name());
+ }
+#endif
+
+ private:
+ class PortObserverThunk;
+ friend class PortObserverThunk;
+
+ void OnPortStatusChanged();
+
+ // Called either when an error is detected on the port from the port observer,
+ // or when `SendClose()` is called.
+ void Clear();
+
+ const RefPtr<NodeController> mNode;
+ const PortRef mPort;
+
+ RefPtr<PortObserverThunk> mObserver;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // ifndef ipc_glue_MessageLink_h
diff --git a/ipc/glue/MessagePump.cpp b/ipc/glue/MessagePump.cpp
new file mode 100644
index 0000000000..50c916608c
--- /dev/null
+++ b/ipc/glue/MessagePump.cpp
@@ -0,0 +1,336 @@
+/* -*- 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 "MessagePump.h"
+
+#include "nsIThread.h"
+#include "nsITimer.h"
+#include "nsICancelableRunnable.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/scoped_nsautorelease_pool.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDebug.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsTimerImpl.h"
+#include "nsXULAppAPI.h"
+#include "prthread.h"
+
+using base::TimeTicks;
+using namespace mozilla::ipc;
+
+#ifdef DEBUG
+static MessagePump::Delegate* gFirstDelegate;
+#endif
+
+namespace mozilla {
+namespace ipc {
+
+class DoWorkRunnable final : public CancelableRunnable,
+ public nsITimerCallback {
+ public:
+ explicit DoWorkRunnable(MessagePump* aPump)
+ : CancelableRunnable("ipc::DoWorkRunnable"), mPump(aPump) {
+ MOZ_ASSERT(aPump);
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSITIMERCALLBACK
+ nsresult Cancel() override;
+
+ private:
+ ~DoWorkRunnable() = default;
+
+ MessagePump* mPump;
+ // DoWorkRunnable is designed as a stateless singleton. Do not add stateful
+ // members here!
+};
+
+} /* namespace ipc */
+} /* namespace mozilla */
+
+MessagePump::MessagePump(nsISerialEventTarget* aEventTarget)
+ : mEventTarget(aEventTarget) {
+ mDoWorkEvent = new DoWorkRunnable(this);
+}
+
+MessagePump::~MessagePump() = default;
+
+void MessagePump::Run(MessagePump::Delegate* aDelegate) {
+ MOZ_ASSERT(keep_running_);
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(),
+ "Use mozilla::ipc::MessagePumpForNonMainThreads instead!");
+ MOZ_RELEASE_ASSERT(!mEventTarget);
+
+ nsIThread* thisThread = NS_GetCurrentThread();
+ MOZ_ASSERT(thisThread);
+
+ mDelayedWorkTimer = NS_NewTimer();
+ MOZ_ASSERT(mDelayedWorkTimer);
+
+ base::ScopedNSAutoreleasePool autoReleasePool;
+
+ for (;;) {
+ autoReleasePool.Recycle();
+
+ bool did_work = NS_ProcessNextEvent(thisThread, false) ? true : false;
+ if (!keep_running_) break;
+
+ // NB: it is crucial *not* to directly call |aDelegate->DoWork()|
+ // here. To ensure that MessageLoop tasks and XPCOM events have
+ // equal priority, we sensitively rely on processing exactly one
+ // Task per DoWorkRunnable XPCOM event.
+
+ did_work |= aDelegate->DoDelayedWork(&delayed_work_time_);
+
+ if (did_work && delayed_work_time_.is_null()) mDelayedWorkTimer->Cancel();
+
+ if (!keep_running_) break;
+
+ if (did_work) continue;
+
+ did_work = aDelegate->DoIdleWork();
+ if (!keep_running_) break;
+
+ if (did_work) continue;
+
+ // This will either sleep or process an event.
+ NS_ProcessNextEvent(thisThread, true);
+ }
+
+ mDelayedWorkTimer->Cancel();
+
+ keep_running_ = true;
+}
+
+void MessagePump::ScheduleWork() {
+ // Make sure the event loop wakes up.
+ if (mEventTarget) {
+ mEventTarget->Dispatch(mDoWorkEvent, NS_DISPATCH_NORMAL);
+ } else {
+ // Some things (like xpcshell) don't use the app shell and so Run hasn't
+ // been called. We still need to wake up the main thread.
+ NS_DispatchToMainThread(mDoWorkEvent);
+ }
+ event_.Signal();
+}
+
+void MessagePump::ScheduleWorkForNestedLoop() {
+ // This method is called when our MessageLoop has just allowed
+ // nested tasks. In our setup, whenever that happens we know that
+ // DoWork() will be called "soon", so there's no need to pay the
+ // cost of what will be a no-op nsThread::Dispatch(mDoWorkEvent).
+}
+
+void MessagePump::ScheduleDelayedWork(const base::TimeTicks& aDelayedTime) {
+ // To avoid racing on mDelayedWorkTimer, we need to be on the same thread as
+ // ::Run().
+ MOZ_RELEASE_ASSERT((!mEventTarget && NS_IsMainThread()) ||
+ mEventTarget->IsOnCurrentThread());
+
+ if (!mDelayedWorkTimer) {
+ mDelayedWorkTimer = NS_NewTimer();
+ if (!mDelayedWorkTimer) {
+ // Called before XPCOM has started up? We can't do this correctly.
+ NS_WARNING("Delayed task might not run!");
+ delayed_work_time_ = aDelayedTime;
+ return;
+ }
+ }
+
+ if (!delayed_work_time_.is_null()) {
+ mDelayedWorkTimer->Cancel();
+ }
+
+ delayed_work_time_ = aDelayedTime;
+
+ // TimeDelta's constructor initializes to 0
+ base::TimeDelta delay;
+ if (aDelayedTime > base::TimeTicks::Now())
+ delay = aDelayedTime - base::TimeTicks::Now();
+
+ uint32_t delayMS = uint32_t(delay.InMilliseconds());
+ mDelayedWorkTimer->InitWithCallback(mDoWorkEvent, delayMS,
+ nsITimer::TYPE_ONE_SHOT);
+}
+
+nsISerialEventTarget* MessagePump::GetXPCOMThread() {
+ if (mEventTarget) {
+ return mEventTarget;
+ }
+
+ // Main thread
+ return GetMainThreadSerialEventTarget();
+}
+
+void MessagePump::DoDelayedWork(base::MessagePump::Delegate* aDelegate) {
+ aDelegate->DoDelayedWork(&delayed_work_time_);
+ if (!delayed_work_time_.is_null()) {
+ ScheduleDelayedWork(delayed_work_time_);
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(DoWorkRunnable, CancelableRunnable,
+ nsITimerCallback)
+
+NS_IMETHODIMP
+DoWorkRunnable::Run() {
+ MessageLoop* loop = MessageLoop::current();
+ MOZ_ASSERT(loop);
+
+ bool nestableTasksAllowed = loop->NestableTasksAllowed();
+
+ // MessageLoop::RunTask() disallows nesting, but our Frankenventloop will
+ // always dispatch DoWork() below from what looks to MessageLoop like a nested
+ // context. So we unconditionally allow nesting here.
+ loop->SetNestableTasksAllowed(true);
+ loop->DoWork();
+ loop->SetNestableTasksAllowed(nestableTasksAllowed);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DoWorkRunnable::Notify(nsITimer* aTimer) {
+ MessageLoop* loop = MessageLoop::current();
+ MOZ_ASSERT(loop);
+
+ bool nestableTasksAllowed = loop->NestableTasksAllowed();
+ loop->SetNestableTasksAllowed(true);
+ mPump->DoDelayedWork(loop);
+ loop->SetNestableTasksAllowed(nestableTasksAllowed);
+
+ return NS_OK;
+}
+
+nsresult DoWorkRunnable::Cancel() {
+ // Workers require cancelable runnables, but we can't really cancel cleanly
+ // here. If we don't process this runnable then we will leave something
+ // unprocessed in the message_loop. Therefore, eagerly complete our work
+ // instead by immediately calling Run(). Run() should be called separately
+ // after this. Unfortunately we cannot use flags to verify this because
+ // DoWorkRunnable is a stateless singleton that can be in the event queue
+ // multiple times simultaneously.
+ MOZ_ALWAYS_SUCCEEDS(Run());
+ return NS_OK;
+}
+
+void MessagePumpForChildProcess::Run(base::MessagePump::Delegate* aDelegate) {
+ if (mFirstRun) {
+ MOZ_ASSERT(aDelegate && !gFirstDelegate);
+#ifdef DEBUG
+ gFirstDelegate = aDelegate;
+#endif
+
+ mFirstRun = false;
+ if (NS_FAILED(XRE_RunAppShell())) {
+ NS_WARNING("Failed to run app shell?!");
+ }
+
+ MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate);
+#ifdef DEBUG
+ gFirstDelegate = nullptr;
+#endif
+
+ return;
+ }
+
+ MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate);
+
+ // We can get to this point in startup with Tasks in our loop's
+ // incoming_queue_ or pending_queue_, but without a matching
+ // DoWorkRunnable(). In MessagePump::Run() above, we sensitively
+ // depend on *not* directly calling delegate->DoWork(), because that
+ // prioritizes Tasks above XPCOM events. However, from this point
+ // forward, any Task posted to our loop is guaranteed to have a
+ // DoWorkRunnable enqueued for it.
+ //
+ // So we just flush the pending work here and move on.
+ MessageLoop* loop = MessageLoop::current();
+ bool nestableTasksAllowed = loop->NestableTasksAllowed();
+ loop->SetNestableTasksAllowed(true);
+
+ while (aDelegate->DoWork())
+ ;
+
+ loop->SetNestableTasksAllowed(nestableTasksAllowed);
+
+ // Really run.
+ mozilla::ipc::MessagePump::Run(aDelegate);
+}
+
+void MessagePumpForNonMainThreads::Run(base::MessagePump::Delegate* aDelegate) {
+ MOZ_ASSERT(keep_running_);
+ MOZ_RELEASE_ASSERT(!NS_IsMainThread(),
+ "Use mozilla::ipc::MessagePump instead!");
+
+ nsIThread* thread = NS_GetCurrentThread();
+ MOZ_RELEASE_ASSERT(mEventTarget->IsOnCurrentThread());
+
+ mDelayedWorkTimer = NS_NewTimer(mEventTarget);
+ MOZ_ASSERT(mDelayedWorkTimer);
+
+ // Chromium event notifications to be processed will be received by this
+ // event loop as a DoWorkRunnables via ScheduleWork. Chromium events that
+ // were received before our thread is valid, however, will not generate
+ // runnable wrappers. We must process any of these before we enter this
+ // loop, or we will forever have unprocessed chromium messages in our queue.
+ //
+ // Note we would like to request a flush of the chromium event queue
+ // using a runnable on the xpcom side, but some thread implementations
+ // (dom workers) get cranky if we call ScheduleWork here (ScheduleWork
+ // calls dispatch on mEventTarget) before the thread processes an event. As
+ // such, clear the queue manually.
+ while (aDelegate->DoWork()) {
+ }
+
+ base::ScopedNSAutoreleasePool autoReleasePool;
+ for (;;) {
+ autoReleasePool.Recycle();
+
+ bool didWork = NS_ProcessNextEvent(thread, false) ? true : false;
+ if (!keep_running_) {
+ break;
+ }
+
+ didWork |= aDelegate->DoDelayedWork(&delayed_work_time_);
+
+ if (didWork && delayed_work_time_.is_null()) {
+ mDelayedWorkTimer->Cancel();
+ }
+
+ if (!keep_running_) {
+ break;
+ }
+
+ if (didWork) {
+ continue;
+ }
+
+ DebugOnly<bool> didIdleWork = aDelegate->DoIdleWork();
+ MOZ_ASSERT(!didIdleWork);
+ if (!keep_running_) {
+ break;
+ }
+
+ if (didWork) {
+ continue;
+ }
+
+ // This will either sleep or process an event.
+ NS_ProcessNextEvent(thread, true);
+ }
+
+ mDelayedWorkTimer->Cancel();
+
+ keep_running_ = true;
+}
diff --git a/ipc/glue/MessagePump.h b/ipc/glue/MessagePump.h
new file mode 100644
index 0000000000..50b97efd79
--- /dev/null
+++ b/ipc/glue/MessagePump.h
@@ -0,0 +1,203 @@
+/* -*- 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/. */
+
+#ifndef __IPC_GLUE_MESSAGEPUMP_H__
+#define __IPC_GLUE_MESSAGEPUMP_H__
+
+#include "base/message_pump_default.h"
+#if defined(XP_WIN)
+# include "base/message_pump_win.h"
+#elif defined(XP_DARWIN)
+# include "base/message_pump_mac.h"
+#endif
+
+#include "base/time.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+#include "nsCOMPtr.h"
+#include "nsIThreadInternal.h"
+
+class nsIEventTarget;
+class nsITimer;
+
+namespace mozilla {
+namespace ipc {
+
+class DoWorkRunnable;
+
+class MessagePump : public base::MessagePumpDefault {
+ friend class DoWorkRunnable;
+
+ public:
+ explicit MessagePump(nsISerialEventTarget* aEventTarget);
+
+ // From base::MessagePump.
+ virtual void Run(base::MessagePump::Delegate* aDelegate) override;
+
+ // From base::MessagePump.
+ virtual void ScheduleWork() override;
+
+ // From base::MessagePump.
+ virtual void ScheduleWorkForNestedLoop() override;
+
+ // From base::MessagePump.
+ virtual void ScheduleDelayedWork(
+ const base::TimeTicks& aDelayedWorkTime) override;
+
+ virtual nsISerialEventTarget* GetXPCOMThread() override;
+
+ protected:
+ virtual ~MessagePump();
+
+ private:
+ // Only called by DoWorkRunnable.
+ void DoDelayedWork(base::MessagePump::Delegate* aDelegate);
+
+ protected:
+ nsISerialEventTarget* mEventTarget;
+
+ // mDelayedWorkTimer and mEventTarget are set in Run() by this class or its
+ // subclasses.
+ nsCOMPtr<nsITimer> mDelayedWorkTimer;
+
+ private:
+ // Only accessed by this class.
+ RefPtr<DoWorkRunnable> mDoWorkEvent;
+};
+
+class MessagePumpForChildProcess final : public MessagePump {
+ public:
+ MessagePumpForChildProcess() : MessagePump(nullptr), mFirstRun(true) {}
+
+ virtual void Run(base::MessagePump::Delegate* aDelegate) override;
+
+ private:
+ ~MessagePumpForChildProcess() = default;
+
+ bool mFirstRun;
+};
+
+class MessagePumpForNonMainThreads final : public MessagePump {
+ public:
+ explicit MessagePumpForNonMainThreads(nsISerialEventTarget* aEventTarget)
+ : MessagePump(aEventTarget) {}
+
+ virtual void Run(base::MessagePump::Delegate* aDelegate) override;
+
+ private:
+ ~MessagePumpForNonMainThreads() = default;
+};
+
+#if defined(XP_WIN)
+// Extends the TYPE_UI message pump to process xpcom events.
+class MessagePumpForNonMainUIThreads final : public base::MessagePumpForUI,
+ public nsIThreadObserver {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSITHREADOBSERVER
+
+ public:
+ explicit MessagePumpForNonMainUIThreads(nsISerialEventTarget* aEventTarget)
+ : mInWait(false), mWaitLock("mInWait") {}
+
+ // The main run loop for this thread.
+ virtual void DoRunLoop() override;
+
+ virtual nsISerialEventTarget* GetXPCOMThread() override {
+ return nullptr; // not sure what to do with this one
+ }
+
+ protected:
+ void SetInWait() {
+ MutexAutoLock lock(mWaitLock);
+ mInWait = true;
+ }
+
+ void ClearInWait() {
+ MutexAutoLock lock(mWaitLock);
+ mInWait = false;
+ }
+
+ bool GetInWait() {
+ MutexAutoLock lock(mWaitLock);
+ return mInWait;
+ }
+
+ private:
+ ~MessagePumpForNonMainUIThreads() {}
+
+ bool mInWait MOZ_GUARDED_BY(mWaitLock);
+ mozilla::Mutex mWaitLock;
+};
+#elif defined(XP_DARWIN)
+// Extends the CFRunLoopBase message pump to process xpcom events. Based on
+// MessagePumpNSRunLoop.
+class MessagePumpForNonMainUIThreads final
+ : public base::MessagePumpCFRunLoopBase,
+ public nsIThreadObserver {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSITHREADOBSERVER
+
+ public:
+ explicit MessagePumpForNonMainUIThreads(nsISerialEventTarget* aEventTarget);
+
+ void DoRun(base::MessagePump::Delegate* aDelegate) override;
+ void Quit() override;
+
+ nsISerialEventTarget* GetXPCOMThread() override { return mEventTarget; }
+
+ private:
+ ~MessagePumpForNonMainUIThreads();
+
+ nsISerialEventTarget* mEventTarget;
+
+ // A source that doesn't do anything but provide something signalable
+ // attached to the run loop. This source will be signalled when Quit
+ // is called, to cause the loop to wake up so that it can stop.
+ CFRunLoopSourceRef quit_source_;
+
+ // False after Quit is called.
+ bool keep_running_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessagePumpForNonMainUIThreads);
+};
+#endif // defined(XP_DARWIN)
+
+#if defined(MOZ_WIDGET_ANDROID)
+/*`
+ * The MessagePumpForAndroidUI exists to enable IPDL in the Android UI thread.
+ * The Android UI thread event loop is controlled by Android. This prevents
+ * running an existing MessagePump implementation in the Android UI thread. In
+ * order to enable IPDL on the Android UI thread it is necessary to have a
+ * non-looping MessagePump. This class enables forwarding of nsIRunnables from
+ * MessageLoop::PostTask_Helper to the registered nsIEventTarget with out the
+ * need to control the event loop. The only member function that should be
+ * invoked is GetXPCOMThread. All other member functions will invoke MOZ_CRASH
+ */
+class MessagePumpForAndroidUI : public base::MessagePump {
+ public:
+ explicit MessagePumpForAndroidUI(nsISerialEventTarget* aEventTarget)
+ : mEventTarget(aEventTarget) {}
+
+ virtual void Run(Delegate* delegate);
+ virtual void Quit();
+ virtual void ScheduleWork();
+ virtual void ScheduleDelayedWork(const base::TimeTicks& delayed_work_time);
+ virtual nsISerialEventTarget* GetXPCOMThread() { return mEventTarget; }
+
+ private:
+ ~MessagePumpForAndroidUI() {}
+ MessagePumpForAndroidUI() {}
+
+ nsISerialEventTarget* mEventTarget;
+};
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+} /* namespace ipc */
+} /* namespace mozilla */
+
+#endif /* __IPC_GLUE_MESSAGEPUMP_H__ */
diff --git a/ipc/glue/MessagePump_android.cpp b/ipc/glue/MessagePump_android.cpp
new file mode 100644
index 0000000000..0288041ad3
--- /dev/null
+++ b/ipc/glue/MessagePump_android.cpp
@@ -0,0 +1,26 @@
+/* -*- 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 "MessagePump.h"
+
+namespace mozilla::ipc {
+void MessagePumpForAndroidUI::Run(Delegate* delegate) {
+ MOZ_CRASH("MessagePumpForAndroidUI should never be Run.");
+}
+
+void MessagePumpForAndroidUI::Quit() {
+ MOZ_CRASH("MessagePumpForAndroidUI should never be Quit.");
+}
+
+void MessagePumpForAndroidUI::ScheduleWork() {
+ MOZ_CRASH("MessagePumpForAndroidUI should never ScheduleWork");
+}
+
+void MessagePumpForAndroidUI::ScheduleDelayedWork(
+ const TimeTicks& delayed_work_time) {
+ MOZ_CRASH("MessagePumpForAndroidUI should never ScheduleDelayedWork");
+}
+} // namespace mozilla::ipc
diff --git a/ipc/glue/MessagePump_mac.mm b/ipc/glue/MessagePump_mac.mm
new file mode 100644
index 0000000000..2af0e07203
--- /dev/null
+++ b/ipc/glue/MessagePump_mac.mm
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "MessagePump.h"
+
+#include <Foundation/Foundation.h>
+
+#include "base/scoped_nsautorelease_pool.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "nsISupportsImpl.h"
+
+using namespace mozilla::ipc;
+
+static void NoOp(void* info) {}
+
+NS_IMPL_ADDREF_INHERITED(MessagePumpForNonMainUIThreads, MessagePump)
+NS_IMPL_RELEASE_INHERITED(MessagePumpForNonMainUIThreads, MessagePump)
+NS_IMPL_QUERY_INTERFACE(MessagePumpForNonMainUIThreads, nsIThreadObserver)
+
+MessagePumpForNonMainUIThreads::MessagePumpForNonMainUIThreads(nsISerialEventTarget* aEventTarget)
+ : mEventTarget(aEventTarget), keep_running_(true) {
+ MOZ_ASSERT(mEventTarget);
+ CFRunLoopSourceContext source_context = CFRunLoopSourceContext();
+ source_context.perform = NoOp;
+ quit_source_ = CFRunLoopSourceCreate(nullptr, // allocator
+ 0, // priority
+ &source_context);
+ CFRunLoopAddSource(run_loop(), quit_source_, kCFRunLoopCommonModes);
+}
+
+MessagePumpForNonMainUIThreads::~MessagePumpForNonMainUIThreads() {
+ CFRunLoopRemoveSource(run_loop(), quit_source_, kCFRunLoopCommonModes);
+ CFRelease(quit_source_);
+}
+
+void MessagePumpForNonMainUIThreads::DoRun(base::MessagePump::Delegate* aDelegate) {
+ // If this is a chromium thread and no nsThread is associated with it, this call will create a
+ // new nsThread.
+ nsIThread* thread = NS_GetCurrentThread();
+ MOZ_ASSERT(thread);
+
+ // Set the main thread observer so we can wake up when xpcom events need to get processed.
+ nsCOMPtr<nsIThreadInternal> ti(do_QueryInterface(thread));
+ MOZ_ASSERT(ti);
+ ti->SetObserver(this);
+
+ base::ScopedNSAutoreleasePool autoReleasePool;
+
+ while (keep_running_) {
+ // Drain the xpcom event loop first.
+ if (NS_ProcessNextEvent(nullptr, false)) {
+ continue;
+ }
+
+ autoReleasePool.Recycle();
+
+ if (!keep_running_) {
+ break;
+ }
+
+ // Now process the CFRunLoop. It exits after running once.
+ // NSRunLoop manages autorelease pools itself.
+ [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
+ }
+
+ ti->SetObserver(nullptr);
+ keep_running_ = true;
+}
+
+void MessagePumpForNonMainUIThreads::Quit() {
+ keep_running_ = false;
+ CFRunLoopSourceSignal(quit_source_);
+ CFRunLoopWakeUp(run_loop());
+}
+
+NS_IMETHODIMP MessagePumpForNonMainUIThreads::OnDispatchedEvent() {
+ // ScheduleWork will signal an input source to the run loop, making it exit so it can process the
+ // xpcom event.
+ ScheduleWork();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MessagePumpForNonMainUIThreads::OnProcessNextEvent(nsIThreadInternal* thread, bool mayWait) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MessagePumpForNonMainUIThreads::AfterProcessNextEvent(nsIThreadInternal* thread,
+ bool eventWasProcessed) {
+ return NS_OK;
+}
diff --git a/ipc/glue/MessagePump_windows.cpp b/ipc/glue/MessagePump_windows.cpp
new file mode 100644
index 0000000000..c0109f3f43
--- /dev/null
+++ b/ipc/glue/MessagePump_windows.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "MessagePump.h"
+
+using namespace mozilla::ipc;
+
+NS_IMPL_ADDREF_INHERITED(MessagePumpForNonMainUIThreads, MessagePump)
+NS_IMPL_RELEASE_INHERITED(MessagePumpForNonMainUIThreads, MessagePump)
+NS_IMPL_QUERY_INTERFACE(MessagePumpForNonMainUIThreads, nsIThreadObserver)
+
+#define CHECK_QUIT_STATE \
+ { \
+ if (state_->should_quit) { \
+ break; \
+ } \
+ }
+
+void MessagePumpForNonMainUIThreads::DoRunLoop() {
+ MOZ_RELEASE_ASSERT(!NS_IsMainThread(),
+ "Use mozilla::ipc::MessagePump instead!");
+
+ // If this is a chromium thread and no nsThread is associated
+ // with it, this call will create a new nsThread.
+ nsIThread* thread = NS_GetCurrentThread();
+ MOZ_ASSERT(thread);
+
+ // Set the main thread observer so we can wake up when
+ // xpcom events need to get processed.
+ nsCOMPtr<nsIThreadInternal> ti(do_QueryInterface(thread));
+ MOZ_ASSERT(ti);
+ ti->SetObserver(this);
+
+ for (;;) {
+ bool didWork = NS_ProcessNextEvent(thread, false);
+
+ didWork |= ProcessNextWindowsMessage();
+ CHECK_QUIT_STATE
+
+ didWork |= state_->delegate->DoWork();
+ CHECK_QUIT_STATE
+
+ didWork |= state_->delegate->DoDelayedWork(&delayed_work_time_);
+ if (didWork && delayed_work_time_.is_null()) {
+ KillTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this));
+ }
+ CHECK_QUIT_STATE
+
+ if (didWork) {
+ continue;
+ }
+
+ DebugOnly<bool> didIdleWork = state_->delegate->DoIdleWork();
+ MOZ_ASSERT(!didIdleWork);
+ CHECK_QUIT_STATE
+
+ SetInWait();
+ bool hasWork = NS_HasPendingEvents(thread);
+ if (didWork || hasWork) {
+ ClearInWait();
+ continue;
+ }
+ WaitForWork(); // Calls MsgWaitForMultipleObjectsEx(QS_ALLINPUT)
+ ClearInWait();
+ }
+
+ ClearInWait();
+
+ ti->SetObserver(nullptr);
+}
+
+NS_IMETHODIMP
+MessagePumpForNonMainUIThreads::OnDispatchedEvent() {
+ // If our thread is sleeping in DoRunLoop's call to WaitForWork() and an
+ // event posts to the nsIThread event queue - break our thread out of
+ // chromium's WaitForWork.
+ if (GetInWait()) {
+ ScheduleWork();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MessagePumpForNonMainUIThreads::OnProcessNextEvent(nsIThreadInternal* thread,
+ bool mayWait) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MessagePumpForNonMainUIThreads::AfterProcessNextEvent(nsIThreadInternal* thread,
+ bool eventWasProcessed) {
+ return NS_OK;
+}
diff --git a/ipc/glue/MiniTransceiver.cpp b/ipc/glue/MiniTransceiver.cpp
new file mode 100644
index 0000000000..933a727f62
--- /dev/null
+++ b/ipc/glue/MiniTransceiver.cpp
@@ -0,0 +1,251 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 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 "mozilla/ipc/MiniTransceiver.h"
+#include "chrome/common/ipc_message.h"
+#include "chrome/common/ipc_message_utils.h"
+#include "base/eintr_wrapper.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/ScopeExit.h"
+#include "nsDebug.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <errno.h>
+
+namespace mozilla::ipc {
+
+static const size_t kMaxIOVecSize = 64;
+static const size_t kMaxDataSize = 8 * 1024;
+static const size_t kMaxNumFds = 16;
+
+MiniTransceiver::MiniTransceiver(int aFd, DataBufferClear aDataBufClear)
+ : mFd(aFd),
+#ifdef DEBUG
+ mState(STATE_NONE),
+#endif
+ mDataBufClear(aDataBufClear) {
+}
+
+namespace {
+
+/**
+ * Initialize the IO vector for sending data and the control buffer for sending
+ * FDs.
+ */
+static void InitMsgHdr(msghdr* aHdr, int aIOVSize, size_t aMaxNumFds) {
+ aHdr->msg_name = nullptr;
+ aHdr->msg_namelen = 0;
+ aHdr->msg_flags = 0;
+
+ // Prepare the IO vector to receive the content of message.
+ auto* iov = new iovec[aIOVSize];
+ aHdr->msg_iov = iov;
+ aHdr->msg_iovlen = aIOVSize;
+
+ // Prepare the control buffer to receive file descriptors.
+ auto* cbuf = new char[CMSG_SPACE(sizeof(int) * aMaxNumFds)];
+ aHdr->msg_control = cbuf;
+ aHdr->msg_controllen = CMSG_SPACE(sizeof(int) * aMaxNumFds);
+}
+
+/**
+ * Delete resources allocated by InitMsgHdr().
+ */
+static void DeinitMsgHdr(msghdr* aHdr) {
+ delete aHdr->msg_iov;
+ delete static_cast<char*>(aHdr->msg_control);
+}
+
+} // namespace
+
+void MiniTransceiver::PrepareFDs(msghdr* aHdr, IPC::Message& aMsg) {
+ // Set control buffer to send file descriptors of the Message.
+ size_t num_fds = aMsg.attached_handles_.Length();
+
+ cmsghdr* cmsg = CMSG_FIRSTHDR(aHdr);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int) * num_fds);
+ for (size_t i = 0; i < num_fds; ++i) {
+ reinterpret_cast<int*>(CMSG_DATA(cmsg))[i] =
+ aMsg.attached_handles_[i].get();
+ }
+
+ // This number will be sent in the header of the message. So, we
+ // can check it at the other side.
+ aMsg.header()->num_handles = num_fds;
+}
+
+size_t MiniTransceiver::PrepareBuffers(msghdr* aHdr, IPC::Message& aMsg) {
+ // Set iovec to send for all buffers of the Message.
+ iovec* iov = aHdr->msg_iov;
+ size_t iovlen = 0;
+ size_t bytes_to_send = 0;
+ for (Pickle::BufferList::IterImpl iter(aMsg.Buffers()); !iter.Done();
+ iter.Advance(aMsg.Buffers(), iter.RemainingInSegment())) {
+ char* data = iter.Data();
+ size_t size = iter.RemainingInSegment();
+ iov[iovlen].iov_base = data;
+ iov[iovlen].iov_len = size;
+ iovlen++;
+ MOZ_ASSERT(iovlen <= kMaxIOVecSize);
+ bytes_to_send += size;
+ }
+ MOZ_ASSERT(bytes_to_send <= kMaxDataSize);
+ aHdr->msg_iovlen = iovlen;
+
+ return bytes_to_send;
+}
+
+bool MiniTransceiver::Send(IPC::Message& aMsg) {
+#ifdef DEBUG
+ if (mState == STATE_SENDING) {
+ MOZ_CRASH(
+ "STATE_SENDING: It violates of request-response and no concurrent "
+ "rules");
+ }
+ mState = STATE_SENDING;
+#endif
+
+ auto clean_fdset = MakeScopeExit([&] { aMsg.attached_handles_.Clear(); });
+
+ size_t num_fds = aMsg.attached_handles_.Length();
+ msghdr hdr{};
+ InitMsgHdr(&hdr, kMaxIOVecSize, num_fds);
+
+ UniquePtr<msghdr, decltype(&DeinitMsgHdr)> uniq(&hdr, &DeinitMsgHdr);
+
+ PrepareFDs(&hdr, aMsg);
+ DebugOnly<size_t> bytes_to_send = PrepareBuffers(&hdr, aMsg);
+
+ ssize_t bytes_written = HANDLE_EINTR(sendmsg(mFd, &hdr, 0));
+
+ if (bytes_written < 0) {
+ char error[128];
+ SprintfLiteral(error, "sendmsg: %s", strerror(errno));
+ NS_WARNING(error);
+ return false;
+ }
+ MOZ_ASSERT(bytes_written == (ssize_t)bytes_to_send,
+ "The message is too big?!");
+
+ return true;
+}
+
+unsigned MiniTransceiver::RecvFDs(msghdr* aHdr, int* aAllFds,
+ unsigned aMaxFds) {
+ if (aHdr->msg_controllen == 0) {
+ return 0;
+ }
+
+ unsigned num_all_fds = 0;
+ for (cmsghdr* cmsg = CMSG_FIRSTHDR(aHdr); cmsg;
+ cmsg = CMSG_NXTHDR(aHdr, cmsg)) {
+ MOZ_ASSERT(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS,
+ "Accept only SCM_RIGHTS to receive file descriptors");
+
+ unsigned payload_sz = cmsg->cmsg_len - CMSG_LEN(0);
+ MOZ_ASSERT(payload_sz % sizeof(int) == 0);
+
+ // Add fds to |aAllFds|
+ unsigned num_part_fds = payload_sz / sizeof(int);
+ int* part_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+ MOZ_ASSERT(num_all_fds + num_part_fds <= aMaxFds);
+
+ memcpy(aAllFds + num_all_fds, part_fds, num_part_fds * sizeof(int));
+ num_all_fds += num_part_fds;
+ }
+ return num_all_fds;
+}
+
+bool MiniTransceiver::RecvData(char* aDataBuf, size_t aBufSize,
+ uint32_t* aMsgSize, int* aFdsBuf,
+ unsigned aMaxFds, unsigned* aNumFds) {
+ msghdr hdr;
+ InitMsgHdr(&hdr, 1, aMaxFds);
+
+ UniquePtr<msghdr, decltype(&DeinitMsgHdr)> uniq(&hdr, &DeinitMsgHdr);
+
+ // The buffer to collect all fds received from the socket.
+ int* all_fds = aFdsBuf;
+ unsigned num_all_fds = 0;
+
+ size_t total_readed = 0;
+ uint32_t msgsz = 0;
+ while (msgsz == 0 || total_readed < msgsz) {
+ // Set IO vector with the begin of the unused buffer.
+ hdr.msg_iov->iov_base = aDataBuf + total_readed;
+ hdr.msg_iov->iov_len = (msgsz == 0 ? aBufSize : msgsz) - total_readed;
+
+ // Read the socket
+ ssize_t bytes_readed = HANDLE_EINTR(recvmsg(mFd, &hdr, 0));
+ if (bytes_readed <= 0) {
+ // Closed or error!
+ return false;
+ }
+ total_readed += bytes_readed;
+ MOZ_ASSERT(total_readed <= aBufSize);
+
+ if (msgsz == 0) {
+ // Parse the size of the message.
+ // Get 0 if data in the buffer is no enough to get message size.
+ msgsz = IPC::Message::MessageSize(aDataBuf, aDataBuf + total_readed);
+ }
+
+ num_all_fds += RecvFDs(&hdr, all_fds + num_all_fds, aMaxFds - num_all_fds);
+ }
+
+ *aMsgSize = msgsz;
+ *aNumFds = num_all_fds;
+ return true;
+}
+
+bool MiniTransceiver::Recv(UniquePtr<IPC::Message>& aMsg) {
+#ifdef DEBUG
+ if (mState == STATE_RECEIVING) {
+ MOZ_CRASH(
+ "STATE_RECEIVING: It violates of request-response and no concurrent "
+ "rules");
+ }
+ mState = STATE_RECEIVING;
+#endif
+
+ UniquePtr<char[]> databuf = MakeUnique<char[]>(kMaxDataSize);
+ uint32_t msgsz = 0;
+ int all_fds[kMaxNumFds];
+ unsigned num_all_fds = 0;
+
+ if (!RecvData(databuf.get(), kMaxDataSize, &msgsz, all_fds, kMaxDataSize,
+ &num_all_fds)) {
+ return false;
+ }
+
+ // Create Message from databuf & all_fds.
+ UniquePtr<IPC::Message> msg = MakeUnique<IPC::Message>(databuf.get(), msgsz);
+ nsTArray<UniqueFileHandle> handles(num_all_fds);
+ for (unsigned i = 0; i < num_all_fds; ++i) {
+ handles.AppendElement(UniqueFileHandle(all_fds[i]));
+ }
+ msg->SetAttachedFileHandles(std::move(handles));
+
+ if (mDataBufClear == DataBufferClear::AfterReceiving) {
+ // Avoid content processes from reading the content of
+ // messages.
+ memset(databuf.get(), 0, msgsz);
+ }
+
+ MOZ_ASSERT(msg->header()->num_handles == msg->attached_handles_.Length(),
+ "The number of file descriptors in the header is different from"
+ " the number actually received");
+
+ aMsg = std::move(msg);
+ return true;
+}
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/MiniTransceiver.h b/ipc/glue/MiniTransceiver.h
new file mode 100644
index 0000000000..9568dc2685
--- /dev/null
+++ b/ipc/glue/MiniTransceiver.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 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/. */
+#ifndef __MINITRANSCEIVER_H_
+#define __MINITRANSCEIVER_H_
+
+#include "chrome/common/ipc_message.h"
+#include "mozilla/Assertions.h"
+
+struct msghdr;
+
+namespace mozilla {
+namespace ipc {
+
+enum class DataBufferClear { None, AfterReceiving };
+
+/**
+ * This simple implementation handles the transmissions of IPC
+ * messages.
+ *
+ * It works according to a strict request-response paradigm, no
+ * concurrent messaging is allowed. Sending a message from A to B must
+ * be followed by another one from B to A. Because of this we don't
+ * need to handle data crossing the boundaries of a
+ * message. Transmission is done via blocking I/O to avoid the
+ * complexity of asynchronous I/O.
+ */
+class MiniTransceiver {
+ public:
+ /**
+ * \param aFd should be a blocking, no O_NONBLOCK, fd.
+ * \param aClearDataBuf is true to clear data buffers after
+ * receiving a message.
+ */
+ explicit MiniTransceiver(
+ int aFd, DataBufferClear aDataBufClear = DataBufferClear::None);
+
+ bool Send(IPC::Message& aMsg);
+ inline bool SendInfallible(IPC::Message& aMsg, const char* aCrashMessage) {
+ bool Ok = Send(aMsg);
+ if (!Ok) {
+ MOZ_CRASH_UNSAFE(aCrashMessage);
+ }
+ return Ok;
+ }
+
+ /**
+ * \param aMsg will hold the content of the received message.
+ * \return false if the fd is closed or with an error.
+ */
+ bool Recv(UniquePtr<IPC::Message>& aMsg);
+ inline bool RecvInfallible(UniquePtr<IPC::Message>& aMsg,
+ const char* aCrashMessage) {
+ bool Ok = Recv(aMsg);
+ if (!Ok) {
+ MOZ_CRASH_UNSAFE(aCrashMessage);
+ }
+ return Ok;
+ }
+
+ int GetFD() { return mFd; }
+
+ private:
+ /**
+ * Set control buffer to make file descriptors ready to be sent
+ * through a socket.
+ */
+ void PrepareFDs(msghdr* aHdr, IPC::Message& aMsg);
+ /**
+ * Collect buffers of the message and make them ready to be sent.
+ *
+ * \param aHdr is the structure going to be passed to sendmsg().
+ * \param aMsg is the Message to send.
+ */
+ size_t PrepareBuffers(msghdr* aHdr, IPC::Message& aMsg);
+ /**
+ * Collect file descriptors received.
+ *
+ * \param aAllFds is where to store file descriptors.
+ * \param aMaxFds is how many file descriptors can be stored in aAllFds.
+ * \return the number of received file descriptors.
+ */
+ unsigned RecvFDs(msghdr* aHdr, int* aAllFds, unsigned aMaxFds);
+ /**
+ * Received data from the socket.
+ *
+ * \param aDataBuf is where to store the data from the socket.
+ * \param aBufSize is the size of the buffer.
+ * \param aMsgSize returns how many bytes were readed from the socket.
+ * \param aFdsBuf is the buffer to return file desriptors received.
+ * \param aMaxFds is the number of file descriptors that can be held.
+ * \param aNumFds returns the number of file descriptors received.
+ * \return true if sucess, or false for error.
+ */
+ bool RecvData(char* aDataBuf, size_t aBufSize, uint32_t* aMsgSize,
+ int* aFdsBuf, unsigned aMaxFds, unsigned* aNumFds);
+
+ int mFd; // The file descriptor of the socket for IPC.
+
+#ifdef DEBUG
+ enum State {
+ STATE_NONE,
+ STATE_SENDING,
+ STATE_RECEIVING,
+ };
+ State mState;
+#endif
+
+ // Clear all received data in temp buffers to avoid data leaking.
+ DataBufferClear mDataBufClear;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // __MINITRANSCEIVER_H_
diff --git a/ipc/glue/Neutering.h b/ipc/glue/Neutering.h
new file mode 100644
index 0000000000..44cbe042ae
--- /dev/null
+++ b/ipc/glue/Neutering.h
@@ -0,0 +1,70 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_Neutering_h
+#define mozilla_ipc_Neutering_h
+
+/**
+ * This header declares RAII wrappers for Window neutering. See
+ * WindowsMessageLoop.cpp for more details.
+ */
+
+namespace mozilla {
+namespace ipc {
+
+/**
+ * This class is a RAII wrapper around Window neutering. As long as a
+ * NeuteredWindowRegion object is instantiated, Win32 windows belonging to the
+ * current thread will be neutered. It is safe to nest multiple instances of
+ * this class.
+ */
+class MOZ_RAII NeuteredWindowRegion {
+ public:
+ explicit NeuteredWindowRegion(bool aDoNeuter);
+ ~NeuteredWindowRegion();
+
+ /**
+ * This function clears any backlog of nonqueued messages that are pending for
+ * the current thread.
+ */
+ void PumpOnce();
+
+ private:
+ bool mNeuteredByThis;
+};
+
+/**
+ * This class is analagous to MutexAutoUnlock for Mutex; it is an RAII class
+ * that is to be instantiated within a NeuteredWindowRegion, thus temporarily
+ * disabling neutering for the remainder of its enclosing block.
+ * @see NeuteredWindowRegion
+ */
+class MOZ_RAII DeneuteredWindowRegion {
+ public:
+ explicit DeneuteredWindowRegion();
+ ~DeneuteredWindowRegion();
+
+ private:
+ bool mReneuter;
+};
+
+class MOZ_RAII SuppressedNeuteringRegion {
+ public:
+ explicit SuppressedNeuteringRegion();
+ ~SuppressedNeuteringRegion();
+
+ static inline bool IsNeuteringSuppressed() { return sSuppressNeutering; }
+
+ private:
+ bool mReenable;
+
+ static bool sSuppressNeutering;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_Neutering_h
diff --git a/ipc/glue/NodeChannel.cpp b/ipc/glue/NodeChannel.cpp
new file mode 100644
index 0000000000..587c6f9127
--- /dev/null
+++ b/ipc/glue/NodeChannel.cpp
@@ -0,0 +1,328 @@
+/* -*- 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 "mozilla/ipc/NodeChannel.h"
+#include "chrome/common/ipc_message.h"
+#include "chrome/common/ipc_message_utils.h"
+#include "mojo/core/ports/name.h"
+#include "mozilla/ipc/BrowserProcessSubThread.h"
+#include "mozilla/ipc/ProtocolMessageUtils.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+
+#ifdef FUZZING_SNAPSHOT
+# include "mozilla/fuzzing/IPCFuzzController.h"
+#endif
+
+template <>
+struct IPC::ParamTraits<mozilla::ipc::NodeChannel::Introduction> {
+ using paramType = mozilla::ipc::NodeChannel::Introduction;
+ static void Write(MessageWriter* aWriter, paramType&& aParam) {
+ WriteParam(aWriter, aParam.mName);
+ WriteParam(aWriter, std::move(aParam.mHandle));
+ WriteParam(aWriter, aParam.mMode);
+ WriteParam(aWriter, aParam.mMyPid);
+ WriteParam(aWriter, aParam.mOtherPid);
+ }
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->mName) &&
+ ReadParam(aReader, &aResult->mHandle) &&
+ ReadParam(aReader, &aResult->mMode) &&
+ ReadParam(aReader, &aResult->mMyPid) &&
+ ReadParam(aReader, &aResult->mOtherPid);
+ }
+};
+
+namespace mozilla::ipc {
+
+NodeChannel::NodeChannel(const NodeName& aName,
+ UniquePtr<IPC::Channel> aChannel, Listener* aListener,
+ base::ProcessId aPid)
+ : mListener(aListener),
+ mName(aName),
+ mOtherPid(aPid),
+ mChannel(std::move(aChannel)) {}
+
+NodeChannel::~NodeChannel() { Close(); }
+
+// Called when the NodeChannel's refcount drops to `0`.
+void NodeChannel::Destroy() {
+ // Dispatch the `delete` operation to the IO thread. We need to do this even
+ // if we're already on the IO thread, as we could be in an `IPC::Channel`
+ // callback which unfortunately will not hold a strong reference to keep
+ // `this` alive.
+ MessageLoop* ioThread = XRE_GetIOMessageLoop();
+ if (ioThread->IsAcceptingTasks()) {
+ ioThread->PostTask(NewNonOwningRunnableMethod("NodeChannel::Destroy", this,
+ &NodeChannel::FinalDestroy));
+ return;
+ }
+
+ // If the IOThread has already been destroyed, we must be shutting it down and
+ // need to synchronously invoke `FinalDestroy` to ensure we're cleaned up
+ // before the thread dies. This is safe as we can't be in a non-owning
+ // IPC::Channel callback at this point.
+ if (MessageLoop::current() == ioThread) {
+ FinalDestroy();
+ return;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Leaking NodeChannel after IOThread destroyed!");
+}
+
+void NodeChannel::FinalDestroy() {
+ AssertIOThread();
+ delete this;
+}
+
+void NodeChannel::Start(bool aCallConnect) {
+ AssertIOThread();
+
+ mExistingListener = mChannel->set_listener(this);
+
+ std::queue<UniquePtr<IPC::Message>> pending;
+ if (mExistingListener) {
+ mExistingListener->GetQueuedMessages(pending);
+ }
+
+ if (aCallConnect) {
+ MOZ_ASSERT(pending.empty(), "unopened channel with pending messages?");
+ if (!mChannel->Connect()) {
+ OnChannelError();
+ }
+ } else {
+ // Check if our channel has already been connected, and knows the other PID.
+ base::ProcessId otherPid = mChannel->OtherPid();
+ if (otherPid != base::kInvalidProcessId) {
+ SetOtherPid(otherPid);
+ }
+
+ // Handle any events the previous listener had queued up. Make sure to stop
+ // if an error causes our channel to become closed.
+ while (!pending.empty() && mState != State::Closed) {
+ OnMessageReceived(std::move(pending.front()));
+ pending.pop();
+ }
+ }
+}
+
+void NodeChannel::Close() {
+ AssertIOThread();
+
+ if (mState.exchange(State::Closed) != State::Closed) {
+ mChannel->Close();
+ mChannel->set_listener(mExistingListener);
+ }
+}
+
+void NodeChannel::SetOtherPid(base::ProcessId aNewPid) {
+ AssertIOThread();
+ MOZ_ASSERT(aNewPid != base::kInvalidProcessId);
+
+ base::ProcessId previousPid = base::kInvalidProcessId;
+ if (!mOtherPid.compare_exchange_strong(previousPid, aNewPid)) {
+ // The PID was already set before this call, double-check that it's correct.
+ MOZ_RELEASE_ASSERT(previousPid == aNewPid,
+ "Different sources disagree on the correct pid?");
+ }
+}
+
+#ifdef XP_MACOSX
+void NodeChannel::SetMachTaskPort(task_t aTask) {
+ AssertIOThread();
+
+ if (mState != State::Closed) {
+ mChannel->SetOtherMachTask(aTask);
+ }
+}
+#endif
+
+void NodeChannel::SendEventMessage(UniquePtr<IPC::Message> aMessage) {
+ // Make sure we're not sending a message with one of our special internal
+ // types ,as those should only be sent using the corresponding methods on
+ // NodeChannel.
+ MOZ_DIAGNOSTIC_ASSERT(aMessage->type() != BROADCAST_MESSAGE_TYPE &&
+ aMessage->type() != INTRODUCE_MESSAGE_TYPE &&
+ aMessage->type() != REQUEST_INTRODUCTION_MESSAGE_TYPE &&
+ aMessage->type() != ACCEPT_INVITE_MESSAGE_TYPE);
+ SendMessage(std::move(aMessage));
+}
+
+void NodeChannel::RequestIntroduction(const NodeName& aPeerName) {
+ MOZ_ASSERT(aPeerName != mojo::core::ports::kInvalidNodeName);
+ auto message = MakeUnique<IPC::Message>(MSG_ROUTING_CONTROL,
+ REQUEST_INTRODUCTION_MESSAGE_TYPE);
+ IPC::MessageWriter writer(*message);
+ WriteParam(&writer, aPeerName);
+ SendMessage(std::move(message));
+}
+
+void NodeChannel::Introduce(Introduction aIntroduction) {
+ auto message =
+ MakeUnique<IPC::Message>(MSG_ROUTING_CONTROL, INTRODUCE_MESSAGE_TYPE);
+ IPC::MessageWriter writer(*message);
+ WriteParam(&writer, std::move(aIntroduction));
+ SendMessage(std::move(message));
+}
+
+void NodeChannel::Broadcast(UniquePtr<IPC::Message> aMessage) {
+ MOZ_DIAGNOSTIC_ASSERT(aMessage->type() == BROADCAST_MESSAGE_TYPE,
+ "Can only broadcast messages with the correct type");
+ SendMessage(std::move(aMessage));
+}
+
+void NodeChannel::AcceptInvite(const NodeName& aRealName,
+ const PortName& aInitialPort) {
+ MOZ_ASSERT(aRealName != mojo::core::ports::kInvalidNodeName);
+ MOZ_ASSERT(aInitialPort != mojo::core::ports::kInvalidPortName);
+ auto message =
+ MakeUnique<IPC::Message>(MSG_ROUTING_CONTROL, ACCEPT_INVITE_MESSAGE_TYPE);
+ IPC::MessageWriter writer(*message);
+ WriteParam(&writer, aRealName);
+ WriteParam(&writer, aInitialPort);
+ SendMessage(std::move(message));
+}
+
+void NodeChannel::SendMessage(UniquePtr<IPC::Message> aMessage) {
+ if (aMessage->size() > IPC::Channel::kMaximumMessageSize) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCMessageName,
+ nsDependentCString(aMessage->name()));
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCMessageSize,
+ static_cast<unsigned int>(aMessage->size()));
+ MOZ_CRASH("IPC message size is too large");
+ }
+ aMessage->AssertAsLargeAsHeader();
+
+#ifdef FUZZING_SNAPSHOT
+ if (mBlockSendRecv) {
+ return;
+ }
+#endif
+
+ if (mState != State::Active) {
+ NS_WARNING("Dropping message as channel has been closed");
+ return;
+ }
+
+ // NOTE: As this is not guaranteed to be running on the I/O thread, the
+ // channel may have become closed since we checked above. IPC::Channel will
+ // handle that and return `false` here, so we can re-check `mState`.
+ if (!mChannel->Send(std::move(aMessage))) {
+ NS_WARNING("Call to Send() failed");
+
+ // If we're still active, update `mState` to `State::Closing`, and dispatch
+ // a runnable to actually close our channel.
+ State expected = State::Active;
+ if (mState.compare_exchange_strong(expected, State::Closing)) {
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableMethod("NodeChannel::CloseForSendError", this,
+ &NodeChannel::OnChannelError));
+ }
+ }
+}
+
+void NodeChannel::OnMessageReceived(UniquePtr<IPC::Message> aMessage) {
+ AssertIOThread();
+
+#ifdef FUZZING_SNAPSHOT
+ if (mBlockSendRecv && !aMessage->IsFuzzMsg()) {
+ return;
+ }
+#endif
+
+ IPC::MessageReader reader(*aMessage);
+ switch (aMessage->type()) {
+ case REQUEST_INTRODUCTION_MESSAGE_TYPE: {
+ NodeName name;
+ if (IPC::ReadParam(&reader, &name)) {
+ mListener->OnRequestIntroduction(mName, name);
+ return;
+ }
+ break;
+ }
+ case INTRODUCE_MESSAGE_TYPE: {
+ Introduction introduction;
+ if (IPC::ReadParam(&reader, &introduction)) {
+ mListener->OnIntroduce(mName, std::move(introduction));
+ return;
+ }
+ break;
+ }
+ case BROADCAST_MESSAGE_TYPE: {
+ mListener->OnBroadcast(mName, std::move(aMessage));
+ return;
+ }
+ case ACCEPT_INVITE_MESSAGE_TYPE: {
+ NodeName realName;
+ PortName initialPort;
+ if (IPC::ReadParam(&reader, &realName) &&
+ IPC::ReadParam(&reader, &initialPort)) {
+ mListener->OnAcceptInvite(mName, realName, initialPort);
+ return;
+ }
+ break;
+ }
+ // Assume all unrecognized types are intended as user event messages, and
+ // deliver them to our listener as such. This allows us to use the same type
+ // field for both internal messages and protocol messages.
+ //
+ // FIXME: Consider doing something cleaner in the future?
+ case EVENT_MESSAGE_TYPE:
+ default: {
+#ifdef FUZZING_SNAPSHOT
+ if (!fuzzing::IPCFuzzController::instance().ObserveIPCMessage(
+ this, *aMessage)) {
+ return;
+ }
+#endif
+
+ mListener->OnEventMessage(mName, std::move(aMessage));
+ return;
+ }
+ }
+
+ // If we got to this point without early returning the message was malformed
+ // in some way. Report an error.
+
+ NS_WARNING("NodeChannel received a malformed message");
+ OnChannelError();
+}
+
+void NodeChannel::OnChannelConnected(base::ProcessId aPeerPid) {
+ AssertIOThread();
+
+ SetOtherPid(aPeerPid);
+
+ // We may need to tell our original listener (which will be the process launch
+ // code) that the the channel has been connected to unblock completing the
+ // process launch.
+ // FIXME: This is super sketchy, but it's also what we were already doing. We
+ // should swap this out for something less sketchy.
+ if (mExistingListener) {
+ mExistingListener->OnChannelConnected(aPeerPid);
+ }
+}
+
+void NodeChannel::OnChannelError() {
+ AssertIOThread();
+
+ State prev = mState.exchange(State::Closed);
+ if (prev == State::Closed) {
+ return;
+ }
+
+ // Clean up the channel and make sure we're no longer the active listener.
+ mChannel->Close();
+ MOZ_ALWAYS_TRUE(this == mChannel->set_listener(mExistingListener));
+
+ // Tell our listener about the error.
+ mListener->OnChannelError(mName);
+}
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/NodeChannel.h b/ipc/glue/NodeChannel.h
new file mode 100644
index 0000000000..eae6cb655e
--- /dev/null
+++ b/ipc/glue/NodeChannel.h
@@ -0,0 +1,175 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_NodeChannel_h
+#define mozilla_ipc_NodeChannel_h
+
+#include "mojo/core/ports/node.h"
+#include "mojo/core/ports/node_delegate.h"
+#include "base/process.h"
+#include "chrome/common/ipc_message.h"
+#include "chrome/common/ipc_channel.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "nsISupports.h"
+#include "nsTHashMap.h"
+#include "mozilla/Queue.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/UniquePtr.h"
+
+#ifdef FUZZING_SNAPSHOT
+# include "mozilla/fuzzing/IPCFuzzController.h"
+#endif
+
+namespace mozilla::ipc {
+
+class NodeController;
+
+// Represents a live connection between our Node and a remote process. This
+// object acts as an IPC::Channel listener and performs basic processing on
+// messages as they're passed between processes.
+
+class NodeChannel final : public IPC::Channel::Listener {
+ using NodeName = mojo::core::ports::NodeName;
+ using PortName = mojo::core::ports::PortName;
+
+#ifdef FUZZING_SNAPSHOT
+ // Required because IPCFuzzController calls OnMessageReceived.
+ friend class mozilla::fuzzing::IPCFuzzController;
+#endif
+
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(NodeChannel, Destroy())
+
+ struct Introduction {
+ NodeName mName;
+ IPC::Channel::ChannelHandle mHandle;
+ IPC::Channel::Mode mMode;
+ base::ProcessId mMyPid = base::kInvalidProcessId;
+ base::ProcessId mOtherPid = base::kInvalidProcessId;
+ };
+
+ class Listener {
+ public:
+ virtual ~Listener() = default;
+
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ virtual void OnEventMessage(const NodeName& aFromNode,
+ UniquePtr<IPC::Message> aMessage) = 0;
+ virtual void OnBroadcast(const NodeName& aFromNode,
+ UniquePtr<IPC::Message> aMessage) = 0;
+ virtual void OnIntroduce(const NodeName& aFromNode,
+ Introduction aIntroduction) = 0;
+ virtual void OnRequestIntroduction(const NodeName& aFromNode,
+ const NodeName& aName) = 0;
+ virtual void OnAcceptInvite(const NodeName& aFromNode,
+ const NodeName& aRealName,
+ const PortName& aInitialPort) = 0;
+ virtual void OnChannelError(const NodeName& aFromNode) = 0;
+ };
+
+ NodeChannel(const NodeName& aName, UniquePtr<IPC::Channel> aChannel,
+ Listener* aListener,
+ base::ProcessId aPid = base::kInvalidProcessId);
+
+ // Send the given message over this peer channel link. May be called from any
+ // thread.
+ void SendEventMessage(UniquePtr<IPC::Message> aMessage);
+
+ // Ask the broker process to broadcast this message to every node. May be
+ // called from any thread.
+ void Broadcast(UniquePtr<IPC::Message> aMessage);
+
+ // Ask the broker process to introduce this node to another node with the
+ // given name. May be called from any thread.
+ void RequestIntroduction(const NodeName& aPeerName);
+
+ // Send an introduction to the target node. May be called from any thread.
+ void Introduce(Introduction aIntroduction);
+
+ void AcceptInvite(const NodeName& aRealName, const PortName& aInitialPort);
+
+ // The PID of the remote process, once known. May be called from any thread.
+ base::ProcessId OtherPid() const { return mOtherPid; }
+
+ // Start communicating with the remote process using this NodeChannel. MUST BE
+ // CALLED FROM THE IO THREAD.
+ void Start(bool aCallConnect = true);
+
+ // Stop communicating with the remote process using this NodeChannel, MUST BE
+ // CALLED FROM THE IO THREAD.
+ void Close();
+
+ // Only ever called by NodeController to update the name after an invite has
+ // completed. MUST BE CALLED FROM THE IO THREAD.
+ void SetName(const NodeName& aNewName) { mName = aNewName; }
+
+#ifdef FUZZING_SNAPSHOT
+ // MUST BE CALLED FROM THE IO THREAD.
+ const NodeName& GetName() { return mName; }
+#endif
+
+#ifdef XP_MACOSX
+ // Called by the GeckoChildProcessHost to provide the task_t for the peer
+ // process. MUST BE CALLED FROM THE IO THREAD.
+ void SetMachTaskPort(task_t aTask);
+#endif
+
+ private:
+ ~NodeChannel();
+
+ void Destroy();
+ void FinalDestroy();
+
+ // Update the known PID for the remote process. IO THREAD ONLY
+ void SetOtherPid(base::ProcessId aNewPid);
+
+ void SendMessage(UniquePtr<IPC::Message> aMessage);
+
+ // IPC::Channel::Listener implementation
+ void OnMessageReceived(UniquePtr<IPC::Message> aMessage) override;
+ void OnChannelConnected(base::ProcessId aPeerPid) override;
+ void OnChannelError() override;
+
+ // NOTE: This strong reference will create a reference cycle between the
+ // listener and the NodeChannel while it is in use. The Listener must clear
+ // its reference to the NodeChannel to avoid leaks before shutdown.
+ const RefPtr<Listener> mListener;
+
+ // The apparent name of this Node. This may change during the invite process
+ // while waiting for the remote node name to be communicated to us.
+
+ // WARNING: This must only be accessed on the IO thread.
+ NodeName mName;
+
+ // NOTE: This won't change once the connection has been established, but may
+ // be `-1` until then. This will only be written to on the IO thread, but may
+ // be read from other threads.
+ std::atomic<base::ProcessId> mOtherPid;
+
+ // WARNING: Most methods on the IPC::Channel are only safe to call on the IO
+ // thread, however it is safe to call `Send()` and `IsClosed()` from other
+ // threads. See IPC::Channel's documentation for details.
+ const mozilla::UniquePtr<IPC::Channel> mChannel;
+
+ // The state will start out as `State::Active`, and will only transition to
+ // `State::Closed` on the IO thread. If a Send fails, the state will
+ // transition to `State::Closing`, and a runnable will be dispatched to the
+ // I/O thread to notify callbacks.
+ enum class State { Active, Closing, Closed };
+ std::atomic<State> mState = State::Active;
+
+#ifdef FUZZING_SNAPSHOT
+ std::atomic<bool> mBlockSendRecv = false;
+#endif
+
+ // WARNING: Must only be accessed on the IO thread.
+ WeakPtr<IPC::Channel::Listener> mExistingListener;
+};
+
+} // namespace mozilla::ipc
+
+#endif
diff --git a/ipc/glue/NodeController.cpp b/ipc/glue/NodeController.cpp
new file mode 100644
index 0000000000..72e9b9a8fe
--- /dev/null
+++ b/ipc/glue/NodeController.cpp
@@ -0,0 +1,856 @@
+/* -*- 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 "mozilla/ipc/NodeController.h"
+#include "MainThreadUtils.h"
+#include "base/process_util.h"
+#include "chrome/common/ipc_message.h"
+#include "mojo/core/ports/name.h"
+#include "mojo/core/ports/node.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/RandomNum.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ToString.h"
+#include "mozilla/ipc/BrowserProcessSubThread.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/mozalloc.h"
+#include "nsISerialEventTarget.h"
+#include "nsTArray.h"
+#include "nsXULAppAPI.h"
+#include "nsPrintfCString.h"
+
+#define PORTS_ALWAYS_OK(expr) MOZ_ALWAYS_TRUE(mojo::core::ports::OK == (expr))
+
+namespace mozilla::ipc {
+
+static StaticRefPtr<NodeController> gNodeController;
+
+static LazyLogModule gNodeControllerLog{"NodeController"};
+
+// Helper logger macro which includes the name of the `this` NodeController in
+// the logged messages.
+#define NODECONTROLLER_LOG(level_, fmt_, ...) \
+ MOZ_LOG(gNodeControllerLog, level_, \
+ ("[%s]: " fmt_, ToString(mName).c_str(), ##__VA_ARGS__))
+
+// Helper warning macro which both does logger logging and emits NS_WARNING logs
+// under debug mode.
+#ifdef DEBUG
+# define NODECONTROLLER_WARNING(fmt_, ...) \
+ do { \
+ nsPrintfCString warning("[%s]: " fmt_, ToString(mName).c_str(), \
+ ##__VA_ARGS__); \
+ NS_WARNING(warning.get()); \
+ MOZ_LOG(gNodeControllerLog, LogLevel::Debug, ("%s", warning.get())); \
+ } while (0)
+#else
+# define NODECONTROLLER_WARNING(fmt_, ...) \
+ NODECONTROLLER_LOG(LogLevel::Warning, fmt_, ##__VA_ARGS__)
+#endif
+
+NodeController::NodeController(const NodeName& aName)
+ : mName(aName), mNode(MakeUnique<Node>(aName, this)) {}
+
+NodeController::~NodeController() {
+ auto state = mState.Lock();
+ MOZ_RELEASE_ASSERT(state->mPeers.IsEmpty(),
+ "Destroying NodeController before closing all peers");
+ MOZ_RELEASE_ASSERT(state->mInvites.IsEmpty(),
+ "Destroying NodeController before closing all invites");
+};
+
+// FIXME: Actually provide some way to create the thing.
+/* static */ NodeController* NodeController::GetSingleton() {
+ MOZ_ASSERT(gNodeController);
+ return gNodeController;
+}
+
+std::pair<ScopedPort, ScopedPort> NodeController::CreatePortPair() {
+ PortRef port0, port1;
+ PORTS_ALWAYS_OK(mNode->CreatePortPair(&port0, &port1));
+ return {ScopedPort{std::move(port0), this},
+ ScopedPort{std::move(port1), this}};
+}
+
+auto NodeController::GetPort(const PortName& aName) -> PortRef {
+ PortRef port;
+ int rv = mNode->GetPort(aName, &port);
+ if (NS_WARN_IF(rv != mojo::core::ports::OK)) {
+ NODECONTROLLER_WARNING("Call to GetPort(%s) Failed",
+ ToString(aName).c_str());
+ return {};
+ }
+ return port;
+}
+
+void NodeController::SetPortObserver(const PortRef& aPort,
+ PortObserver* aObserver) {
+ PORTS_ALWAYS_OK(mNode->SetUserData(aPort, aObserver));
+}
+
+auto NodeController::GetStatus(const PortRef& aPort) -> Maybe<PortStatus> {
+ PortStatus status{};
+ int rv = mNode->GetStatus(aPort, &status);
+ if (rv != mojo::core::ports::OK) {
+ return Nothing();
+ }
+ return Some(status);
+}
+
+void NodeController::ClosePort(const PortRef& aPort) {
+ PORTS_ALWAYS_OK(mNode->ClosePort(aPort));
+}
+
+bool NodeController::GetMessage(const PortRef& aPort,
+ UniquePtr<IPC::Message>* aMessage) {
+ UniquePtr<UserMessageEvent> messageEvent;
+ int rv = mNode->GetMessage(aPort, &messageEvent, nullptr);
+ if (rv != mojo::core::ports::OK) {
+ if (rv == mojo::core::ports::ERROR_PORT_PEER_CLOSED) {
+ return false;
+ }
+ MOZ_CRASH("GetMessage on port in invalid state");
+ }
+
+ if (messageEvent) {
+ UniquePtr<IPC::Message> message = messageEvent->TakeMessage<IPC::Message>();
+
+ // If our UserMessageEvent has any ports directly attached to it, fetch them
+ // from our node and attach them to the IPC::Message we extracted.
+ //
+ // It's important to only do this if we have nonempty set of ports on the
+ // event, as we may have never serialized our IPC::Message's ports onto the
+ // event if it was routed in-process.
+ if (messageEvent->num_ports() > 0) {
+ nsTArray<ScopedPort> attachedPorts(messageEvent->num_ports());
+ for (size_t i = 0; i < messageEvent->num_ports(); ++i) {
+ attachedPorts.AppendElement(
+ ScopedPort{GetPort(messageEvent->ports()[i]), this});
+ }
+ message->SetAttachedPorts(std::move(attachedPorts));
+ }
+
+ *aMessage = std::move(message);
+ } else {
+ *aMessage = nullptr;
+ }
+ return true;
+}
+
+bool NodeController::SendUserMessage(const PortRef& aPort,
+ UniquePtr<IPC::Message> aMessage) {
+ auto messageEvent = MakeUnique<UserMessageEvent>(0);
+ messageEvent->AttachMessage(std::move(aMessage));
+
+ int rv = mNode->SendUserMessage(aPort, std::move(messageEvent));
+ if (rv == mojo::core::ports::OK) {
+ return true;
+ }
+ if (rv == mojo::core::ports::ERROR_PORT_PEER_CLOSED) {
+ NODECONTROLLER_LOG(LogLevel::Debug,
+ "Ignoring message to port %s as peer was closed",
+ ToString(aPort.name()).c_str());
+ return true;
+ }
+ NODECONTROLLER_WARNING("Failed to send message to port %s",
+ ToString(aPort.name()).c_str());
+ return false;
+}
+
+auto NodeController::SerializeEventMessage(UniquePtr<Event> aEvent,
+ const NodeName* aRelayTarget,
+ uint32_t aType)
+ -> UniquePtr<IPC::Message> {
+ UniquePtr<IPC::Message> message;
+ if (aEvent->type() == Event::kUserMessage) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ aType == EVENT_MESSAGE_TYPE,
+ "Can only send a UserMessage in an EVENT_MESSAGE_TYPE");
+ message = static_cast<UserMessageEvent*>(aEvent.get())
+ ->TakeMessage<IPC::Message>();
+ } else {
+ message = MakeUnique<IPC::Message>(MSG_ROUTING_CONTROL, aType);
+ }
+
+ message->set_relay(aRelayTarget != nullptr);
+
+ size_t length = aEvent->GetSerializedSize();
+ if (aRelayTarget) {
+ length += sizeof(NodeName);
+ }
+
+ // Use an intermediate buffer to serialize to avoid potential issues with the
+ // segmented `IPC::Message` bufferlist. This should be fairly cheap, as the
+ // majority of events are fairly small.
+ Vector<char, 256, InfallibleAllocPolicy> buffer;
+ (void)buffer.initLengthUninitialized(length);
+ if (aRelayTarget) {
+ memcpy(buffer.begin(), aRelayTarget, sizeof(NodeName));
+ aEvent->Serialize(buffer.begin() + sizeof(NodeName));
+ } else {
+ aEvent->Serialize(buffer.begin());
+ }
+
+ message->WriteFooter(buffer.begin(), buffer.length());
+ message->set_event_footer_size(buffer.length());
+
+#ifdef DEBUG
+ // Debug-assert that we can read the same data back out of the buffer.
+ MOZ_ASSERT(message->event_footer_size() == length);
+ Vector<char, 256, InfallibleAllocPolicy> buffer2;
+ (void)buffer2.initLengthUninitialized(message->event_footer_size());
+ MOZ_ASSERT(message->ReadFooter(buffer2.begin(), buffer2.length(),
+ /* truncate */ false));
+ MOZ_ASSERT(!memcmp(buffer2.begin(), buffer.begin(), buffer.length()));
+#endif
+
+ return message;
+}
+
+auto NodeController::DeserializeEventMessage(UniquePtr<IPC::Message> aMessage,
+ NodeName* aRelayTarget)
+ -> UniquePtr<Event> {
+ if (aMessage->is_relay() && !aRelayTarget) {
+ NODECONTROLLER_WARNING("Unexpected relay message '%s'", aMessage->name());
+ return nullptr;
+ }
+
+ Vector<char, 256, InfallibleAllocPolicy> buffer;
+ (void)buffer.initLengthUninitialized(aMessage->event_footer_size());
+ // Truncate the message when reading the footer, so that the extra footer data
+ // is no longer present in the message. This allows future code to eventually
+ // send the same `IPC::Message` to another process.
+ if (!aMessage->ReadFooter(buffer.begin(), buffer.length(),
+ /* truncate */ true)) {
+ NODECONTROLLER_WARNING("Call to ReadFooter for message '%s' Failed",
+ aMessage->name());
+ return nullptr;
+ }
+ aMessage->set_event_footer_size(0);
+
+ UniquePtr<Event> event;
+ if (aRelayTarget) {
+ MOZ_ASSERT(aMessage->is_relay());
+ if (buffer.length() < sizeof(NodeName)) {
+ NODECONTROLLER_WARNING(
+ "Insufficient space in message footer for message '%s'",
+ aMessage->name());
+ return nullptr;
+ }
+ memcpy(aRelayTarget, buffer.begin(), sizeof(NodeName));
+ event = Event::Deserialize(buffer.begin() + sizeof(NodeName),
+ buffer.length() - sizeof(NodeName));
+ } else {
+ event = Event::Deserialize(buffer.begin(), buffer.length());
+ }
+
+ if (!event) {
+ NODECONTROLLER_WARNING("Call to Event::Deserialize for message '%s' Failed",
+ aMessage->name());
+ return nullptr;
+ }
+
+ if (event->type() == Event::kUserMessage) {
+ static_cast<UserMessageEvent*>(event.get())
+ ->AttachMessage(std::move(aMessage));
+ }
+ return event;
+}
+
+already_AddRefed<NodeChannel> NodeController::GetNodeChannel(
+ const NodeName& aName) {
+ auto state = mState.Lock();
+ return do_AddRef(state->mPeers.Get(aName));
+}
+
+void NodeController::DropPeer(NodeName aNodeName) {
+ AssertIOThread();
+
+#ifdef FUZZING_SNAPSHOT
+ MOZ_FUZZING_IPC_DROP_PEER("NodeController::DropPeer");
+#endif
+
+ Invite invite;
+ RefPtr<NodeChannel> channel;
+ nsTArray<PortRef> pendingMerges;
+ {
+ auto state = mState.Lock();
+ state->mPeers.Remove(aNodeName, &channel);
+ state->mPendingMessages.Remove(aNodeName);
+ state->mInvites.Remove(aNodeName, &invite);
+ state->mPendingMerges.Remove(aNodeName, &pendingMerges);
+ }
+
+ NODECONTROLLER_LOG(LogLevel::Info, "Dropping Peer %s (pid: %" PRIPID ")",
+ ToString(aNodeName).c_str(),
+ channel ? channel->OtherPid() : base::kInvalidProcessId);
+
+ if (channel) {
+ channel->Close();
+ }
+ if (invite.mChannel) {
+ invite.mChannel->Close();
+ }
+ if (invite.mToMerge.is_valid()) {
+ // Ignore any possible errors here.
+ (void)mNode->ClosePort(invite.mToMerge);
+ }
+ for (auto& port : pendingMerges) {
+ // Ignore any possible errors here.
+ (void)mNode->ClosePort(port);
+ }
+ mNode->LostConnectionToNode(aNodeName);
+}
+
+void NodeController::ForwardEvent(const NodeName& aNode,
+ UniquePtr<Event> aEvent) {
+ if (aNode == mName) {
+ (void)mNode->AcceptEvent(std::move(aEvent));
+ } else {
+ // On Windows and macOS, messages holding HANDLEs or mach ports must be
+ // relayed via the broker process so it can transfer ownership.
+ bool needsRelay = false;
+#if defined(XP_WIN) || defined(XP_MACOSX)
+ if (!IsBroker() && aNode != kBrokerNodeName &&
+ aEvent->type() == Event::kUserMessage) {
+ auto* userEvent = static_cast<UserMessageEvent*>(aEvent.get());
+ needsRelay =
+ userEvent->HasMessage() &&
+ userEvent->GetMessage<IPC::Message>()->num_relayed_attachments() > 0;
+ }
+#endif
+
+ UniquePtr<IPC::Message> message =
+ SerializeEventMessage(std::move(aEvent), needsRelay ? &aNode : nullptr);
+ MOZ_ASSERT(message->is_relay() == needsRelay,
+ "Message relay status set incorrectly");
+
+ RefPtr<NodeChannel> peer;
+ RefPtr<NodeChannel> broker;
+ bool needsIntroduction = false;
+ {
+ auto state = mState.Lock();
+
+ // Check if we know this peer. If we don't, we'll need to request an
+ // introduction.
+ peer = state->mPeers.Get(aNode);
+ if (!peer || needsRelay) {
+ if (IsBroker()) {
+ NODECONTROLLER_WARNING("Ignoring message '%s' to unknown peer %s",
+ message->name(), ToString(aNode).c_str());
+ return;
+ }
+
+ broker = state->mPeers.Get(kBrokerNodeName);
+ if (!broker) {
+ NODECONTROLLER_WARNING(
+ "Ignoring message '%s' to peer %s due to a missing broker",
+ message->name(), ToString(aNode).c_str());
+ return;
+ }
+
+ if (!needsRelay) {
+ auto& queue =
+ state->mPendingMessages.LookupOrInsertWith(aNode, [&]() {
+ needsIntroduction = true;
+ return Queue<UniquePtr<IPC::Message>, 64>{};
+ });
+ queue.Push(std::move(message));
+ }
+ }
+ }
+
+ MOZ_ASSERT(!needsIntroduction || !needsRelay,
+ "Only one of the two should ever be set");
+
+ if (needsRelay) {
+ NODECONTROLLER_LOG(LogLevel::Info,
+ "Relaying message '%s' for peer %s due to %" PRIu32
+ " attachments",
+ message->name(), ToString(aNode).c_str(),
+ message->num_relayed_attachments());
+ MOZ_ASSERT(message->num_relayed_attachments() > 0 && broker);
+ broker->SendEventMessage(std::move(message));
+ } else if (needsIntroduction) {
+ MOZ_ASSERT(broker);
+ broker->RequestIntroduction(aNode);
+ } else if (peer) {
+ peer->SendEventMessage(std::move(message));
+ }
+ }
+}
+
+void NodeController::BroadcastEvent(UniquePtr<Event> aEvent) {
+ UniquePtr<IPC::Message> message =
+ SerializeEventMessage(std::move(aEvent), nullptr, BROADCAST_MESSAGE_TYPE);
+
+ if (IsBroker()) {
+ OnBroadcast(mName, std::move(message));
+ } else if (RefPtr<NodeChannel> broker = GetNodeChannel(kBrokerNodeName)) {
+ broker->Broadcast(std::move(message));
+ } else {
+ NODECONTROLLER_WARNING(
+ "Trying to broadcast event, but no connection to broker");
+ }
+}
+
+void NodeController::PortStatusChanged(const PortRef& aPortRef) {
+ RefPtr<UserData> userData;
+ int rv = mNode->GetUserData(aPortRef, &userData);
+ if (rv != mojo::core::ports::OK) {
+ NODECONTROLLER_WARNING("GetUserData call for port '%s' failed",
+ ToString(aPortRef.name()).c_str());
+ return;
+ }
+ if (userData) {
+ // All instances of `UserData` attached to ports in this node must be of
+ // type `PortObserver`, so we can call `OnPortStatusChanged` directly on
+ // them.
+ static_cast<PortObserver*>(userData.get())->OnPortStatusChanged();
+ }
+}
+
+void NodeController::OnEventMessage(const NodeName& aFromNode,
+ UniquePtr<IPC::Message> aMessage) {
+ AssertIOThread();
+
+ bool isRelay = aMessage->is_relay();
+ if (isRelay && aMessage->num_relayed_attachments() == 0) {
+ NODECONTROLLER_WARNING(
+ "Invalid relay message without relayed attachments from peer %s!",
+ ToString(aFromNode).c_str());
+ DropPeer(aFromNode);
+ return;
+ }
+
+ NodeName relayTarget;
+ UniquePtr<Event> event = DeserializeEventMessage(
+ std::move(aMessage), isRelay ? &relayTarget : nullptr);
+ if (!event) {
+ NODECONTROLLER_WARNING("Invalid EventMessage from peer %s!",
+ ToString(aFromNode).c_str());
+ DropPeer(aFromNode);
+ return;
+ }
+
+ NodeName fromNode = aFromNode;
+#if defined(XP_WIN) || defined(XP_MACOSX)
+ if (isRelay) {
+ if (event->type() != Event::kUserMessage) {
+ NODECONTROLLER_WARNING(
+ "Unexpected relay of non-UserMessage event from peer %s!",
+ ToString(aFromNode).c_str());
+ DropPeer(aFromNode);
+ return;
+ }
+
+ // If we're the broker, then we'll need to forward this message on to the
+ // true recipient. To do this, we re-serialize the message, passing along
+ // the original source node, and send it to the final node.
+ if (IsBroker()) {
+ UniquePtr<IPC::Message> message =
+ SerializeEventMessage(std::move(event), &aFromNode);
+ if (!message) {
+ NODECONTROLLER_WARNING(
+ "Relaying EventMessage from peer %s failed to re-serialize!",
+ ToString(aFromNode).c_str());
+ DropPeer(aFromNode);
+ return;
+ }
+ MOZ_ASSERT(message->is_relay(), "Message stopped being a relay message?");
+ MOZ_ASSERT(message->num_relayed_attachments() > 0,
+ "Message doesn't have relayed attachments?");
+
+ NODECONTROLLER_LOG(
+ LogLevel::Info,
+ "Relaying message '%s' from peer %s to peer %s (%" PRIu32
+ " attachments)",
+ message->name(), ToString(aFromNode).c_str(),
+ ToString(relayTarget).c_str(), message->num_relayed_attachments());
+
+ RefPtr<NodeChannel> peer;
+ {
+ auto state = mState.Lock();
+ peer = state->mPeers.Get(relayTarget);
+ }
+ if (!peer) {
+ NODECONTROLLER_WARNING(
+ "Dropping relayed message from %s to unknown peer %s",
+ ToString(aFromNode).c_str(), ToString(relayTarget).c_str());
+ return;
+ }
+
+ peer->SendEventMessage(std::move(message));
+ return;
+ }
+
+ // Otherwise, we're the final recipient, so we can continue & process the
+ // message as usual.
+ if (aFromNode != kBrokerNodeName) {
+ NODECONTROLLER_WARNING(
+ "Unexpected relayed EventMessage from non-broker peer %s!",
+ ToString(aFromNode).c_str());
+ DropPeer(aFromNode);
+ return;
+ }
+ fromNode = relayTarget;
+
+ NODECONTROLLER_LOG(LogLevel::Info, "Got relayed message from peer %s",
+ ToString(fromNode).c_str());
+ }
+#endif
+
+ // If we're getting a requested port merge from another process, check to make
+ // sure that we're expecting the request, and record that the merge has
+ // arrived so we don't try to close the port on error.
+ if (event->type() == Event::kMergePort) {
+ // Check that the target port for the merge actually exists.
+ auto targetPort = GetPort(event->port_name());
+ if (!targetPort.is_valid()) {
+ NODECONTROLLER_WARNING(
+ "Unexpected MergePortEvent from peer %s for unknown port %s",
+ ToString(fromNode).c_str(), ToString(event->port_name()).c_str());
+ DropPeer(fromNode);
+ return;
+ }
+
+ // Check if `targetPort` is in our pending merges entry for the given source
+ // node. If this makes the `mPendingMerges` entry empty, remove it.
+ bool expectingMerge = [&] {
+ auto state = mState.Lock();
+ auto pendingMerges = state->mPendingMerges.Lookup(aFromNode);
+ if (!pendingMerges) {
+ return false;
+ }
+ size_t removed = pendingMerges->RemoveElementsBy(
+ [&](auto& port) { return port.name() == targetPort.name(); });
+ if (removed != 0 && pendingMerges->IsEmpty()) {
+ pendingMerges.Remove();
+ }
+ return removed != 0;
+ }();
+
+ if (!expectingMerge) {
+ NODECONTROLLER_WARNING(
+ "Unexpected MergePortEvent from peer %s for port %s",
+ ToString(fromNode).c_str(), ToString(event->port_name()).c_str());
+ DropPeer(fromNode);
+ return;
+ }
+ }
+
+ (void)mNode->AcceptEvent(std::move(event));
+}
+
+void NodeController::OnBroadcast(const NodeName& aFromNode,
+ UniquePtr<IPC::Message> aMessage) {
+ MOZ_DIAGNOSTIC_ASSERT(aMessage->type() == BROADCAST_MESSAGE_TYPE);
+
+ // NOTE: This method may be called off of the IO thread by the
+ // `BroadcastEvent` node callback.
+ if (!IsBroker()) {
+ NODECONTROLLER_WARNING("Broadcast request received by non-broker node");
+ return;
+ }
+
+ UniquePtr<Event> event = DeserializeEventMessage(std::move(aMessage));
+ if (!event) {
+ NODECONTROLLER_WARNING("Invalid broadcast message from peer");
+ return;
+ }
+
+ nsTArray<RefPtr<NodeChannel>> peers;
+ {
+ auto state = mState.Lock();
+ peers.SetCapacity(state->mPeers.Count());
+ for (const auto& peer : state->mPeers.Values()) {
+ peers.AppendElement(peer);
+ }
+ }
+ for (const auto& peer : peers) {
+ // NOTE: This `clone` operation is only supported for a limited number of
+ // message types by the ports API, which provides some extra security by
+ // only allowing those specific types of messages to be broadcasted.
+ // Messages which don't support `Clone` cannot be broadcast, and the ports
+ // library will not attempt to broadcast them.
+ auto clone = event->Clone();
+ if (!clone) {
+ NODECONTROLLER_WARNING("Attempt to broadcast unsupported message");
+ break;
+ }
+
+ peer->SendEventMessage(SerializeEventMessage(std::move(clone)));
+ }
+}
+
+void NodeController::OnIntroduce(const NodeName& aFromNode,
+ NodeChannel::Introduction aIntroduction) {
+ AssertIOThread();
+
+ if (aFromNode != kBrokerNodeName) {
+ NODECONTROLLER_WARNING("Introduction received from non-broker node");
+ DropPeer(aFromNode);
+ return;
+ }
+
+ MOZ_ASSERT(aIntroduction.mMyPid == base::GetCurrentProcId(),
+ "We're the wrong process to receive this?");
+
+ if (!aIntroduction.mHandle) {
+ NODECONTROLLER_WARNING("Could not be introduced to peer %s",
+ ToString(aIntroduction.mName).c_str());
+ mNode->LostConnectionToNode(aIntroduction.mName);
+
+ auto state = mState.Lock();
+ state->mPendingMessages.Remove(aIntroduction.mName);
+ return;
+ }
+
+ auto channel = MakeUnique<IPC::Channel>(std::move(aIntroduction.mHandle),
+ aIntroduction.mMode, nullptr);
+ auto nodeChannel = MakeRefPtr<NodeChannel>(
+ aIntroduction.mName, std::move(channel), this, aIntroduction.mOtherPid);
+
+ {
+ auto state = mState.Lock();
+ bool isNew = false;
+ state->mPeers.LookupOrInsertWith(aIntroduction.mName, [&]() {
+ isNew = true;
+ return nodeChannel;
+ });
+ if (!isNew) {
+ // We got a duplicate introduction. This can happen during normal
+ // execution if both sides request an introduction at the same time. We
+ // can just ignore the second one, as they'll arrive in the same order in
+ // both processes.
+ nodeChannel->Close();
+ return;
+ }
+
+ // Deliver any pending messages, then remove the entry from our table. We do
+ // this while `mState` is still held to ensure that these messages are
+ // all sent before another thread can observe the newly created channel.
+ // As the channel hasn't been `Connect()`-ed yet, this will only queue the
+ // messages up to be sent, so is OK to do with the mutex held. These
+ // messages will be processed to be sent during `Start()` below, which is
+ // performed outside of the lock.
+ if (auto pending = state->mPendingMessages.Lookup(aIntroduction.mName)) {
+ while (!pending->IsEmpty()) {
+ nodeChannel->SendEventMessage(pending->Pop());
+ }
+ pending.Remove();
+ }
+ }
+
+ // NodeChannel::Start must be called with the lock not held, as it may lead to
+ // callbacks being made into `OnChannelError` or `OnMessageReceived`, which
+ // will attempt to re-acquire our lock.
+ nodeChannel->Start();
+}
+
+void NodeController::OnRequestIntroduction(const NodeName& aFromNode,
+ const NodeName& aName) {
+ AssertIOThread();
+ if (NS_WARN_IF(!IsBroker())) {
+ return;
+ }
+
+ RefPtr<NodeChannel> peerA = GetNodeChannel(aFromNode);
+ if (!peerA || aName == mojo::core::ports::kInvalidNodeName) {
+ NODECONTROLLER_WARNING("Invalid OnRequestIntroduction message from node %s",
+ ToString(aFromNode).c_str());
+ DropPeer(aFromNode);
+ return;
+ }
+
+ RefPtr<NodeChannel> peerB = GetNodeChannel(aName);
+ IPC::Channel::ChannelHandle handleA, handleB;
+ if (!peerB || !IPC::Channel::CreateRawPipe(&handleA, &handleB)) {
+ NODECONTROLLER_WARNING(
+ "Rejecting introduction request from '%s' for unknown peer '%s'",
+ ToString(aFromNode).c_str(), ToString(aName).c_str());
+
+ // We don't know this peer, or ran into issues creating the descriptor! Send
+ // an invalid introduction to content to clean up any pending outbound
+ // messages.
+ NodeChannel::Introduction intro{aName, nullptr, IPC::Channel::MODE_SERVER,
+ peerA->OtherPid(), base::kInvalidProcessId};
+ peerA->Introduce(std::move(intro));
+ return;
+ }
+
+ NodeChannel::Introduction introA{aName, std::move(handleA),
+ IPC::Channel::MODE_SERVER, peerA->OtherPid(),
+ peerB->OtherPid()};
+ NodeChannel::Introduction introB{aFromNode, std::move(handleB),
+ IPC::Channel::MODE_CLIENT, peerB->OtherPid(),
+ peerA->OtherPid()};
+ peerA->Introduce(std::move(introA));
+ peerB->Introduce(std::move(introB));
+}
+
+void NodeController::OnAcceptInvite(const NodeName& aFromNode,
+ const NodeName& aRealName,
+ const PortName& aInitialPort) {
+ AssertIOThread();
+ if (!IsBroker()) {
+ NODECONTROLLER_WARNING("Ignoring AcceptInvite message as non-broker");
+ return;
+ }
+
+ if (aRealName == mojo::core::ports::kInvalidNodeName ||
+ aInitialPort == mojo::core::ports::kInvalidPortName) {
+ NODECONTROLLER_WARNING("Invalid name in AcceptInvite message");
+ DropPeer(aFromNode);
+ return;
+ }
+
+ bool inserted = false;
+ Invite invite;
+ {
+ auto state = mState.Lock();
+ MOZ_ASSERT(state->mPendingMessages.IsEmpty(),
+ "Shouldn't have pending messages in broker");
+
+ // Try to remove the source node from our invites list and insert it into
+ // our peers map under the new name.
+ if (state->mInvites.Remove(aFromNode, &invite)) {
+ MOZ_ASSERT(invite.mChannel && invite.mToMerge.is_valid());
+ state->mPeers.LookupOrInsertWith(aRealName, [&]() {
+ inserted = true;
+ return invite.mChannel;
+ });
+ }
+ }
+ if (!inserted) {
+ NODECONTROLLER_WARNING("Invalid AcceptInvite message from node %s",
+ ToString(aFromNode).c_str());
+ DropPeer(aFromNode);
+ return;
+ }
+
+ // Update the name of the node. This field is only accessed from the IO
+ // thread, so it's safe to update it without a lock held.
+ invite.mChannel->SetName(aRealName);
+
+ // Start the port merge to allow our existing initial port to begin
+ // communicating with the remote port.
+ PORTS_ALWAYS_OK(mNode->MergePorts(invite.mToMerge, aRealName, aInitialPort));
+}
+
+void NodeController::OnChannelError(const NodeName& aFromNode) {
+ AssertIOThread();
+ DropPeer(aFromNode);
+}
+
+static mojo::core::ports::NodeName RandomNodeName() {
+ return {RandomUint64OrDie(), RandomUint64OrDie()};
+}
+
+std::tuple<ScopedPort, RefPtr<NodeChannel>> NodeController::InviteChildProcess(
+ UniquePtr<IPC::Channel> aChannel) {
+ MOZ_ASSERT(IsBroker());
+ AssertIOThread();
+
+ // Create the peer with a randomly generated name, and store it in `mInvites`.
+ // This channel and name will be used for communication with the node until it
+ // sends us its' real name in an `AcceptInvite` message.
+ auto ports = CreatePortPair();
+ auto inviteName = RandomNodeName();
+ auto nodeChannel =
+ MakeRefPtr<NodeChannel>(inviteName, std::move(aChannel), this);
+ {
+ auto state = mState.Lock();
+ MOZ_DIAGNOSTIC_ASSERT(!state->mPeers.Contains(inviteName),
+ "UUID conflict?");
+ MOZ_DIAGNOSTIC_ASSERT(!state->mInvites.Contains(inviteName),
+ "UUID conflict?");
+ state->mInvites.InsertOrUpdate(inviteName,
+ Invite{nodeChannel, ports.second.Release()});
+ }
+
+ nodeChannel->Start(/* aCallConnect */ false);
+ return std::tuple{std::move(ports.first), std::move(nodeChannel)};
+}
+
+void NodeController::InitBrokerProcess() {
+ AssertIOThread();
+ MOZ_ASSERT(!gNodeController);
+ gNodeController = new NodeController(kBrokerNodeName);
+}
+
+ScopedPort NodeController::InitChildProcess(UniquePtr<IPC::Channel> aChannel,
+ int32_t aParentPid) {
+ AssertIOThread();
+ MOZ_ASSERT(!gNodeController);
+
+ auto nodeName = RandomNodeName();
+ gNodeController = new NodeController(nodeName);
+
+ auto ports = gNodeController->CreatePortPair();
+ PortRef toMerge = ports.second.Release();
+
+ auto nodeChannel = MakeRefPtr<NodeChannel>(
+ kBrokerNodeName, std::move(aChannel), gNodeController, aParentPid);
+ {
+ auto state = gNodeController->mState.Lock();
+ MOZ_DIAGNOSTIC_ASSERT(!state->mPeers.Contains(kBrokerNodeName));
+ state->mPeers.InsertOrUpdate(kBrokerNodeName, nodeChannel);
+ MOZ_DIAGNOSTIC_ASSERT(!state->mPendingMerges.Contains(kBrokerNodeName));
+ state->mPendingMerges.LookupOrInsert(kBrokerNodeName)
+ .AppendElement(toMerge);
+ }
+
+ nodeChannel->Start(/* aCallConnect */ true);
+ nodeChannel->AcceptInvite(nodeName, toMerge.name());
+ return std::move(ports.first);
+}
+
+void NodeController::CleanUp() {
+ AssertIOThread();
+ MOZ_ASSERT(gNodeController);
+
+ RefPtr<NodeController> nodeController = gNodeController;
+ gNodeController = nullptr;
+
+ // Collect all objects from our state which need to be cleaned up.
+ nsTArray<NodeName> lostConnections;
+ nsTArray<RefPtr<NodeChannel>> channelsToClose;
+ nsTArray<PortRef> portsToClose;
+ {
+ auto state = nodeController->mState.Lock();
+ for (const auto& chan : state->mPeers) {
+ lostConnections.AppendElement(chan.GetKey());
+ channelsToClose.AppendElement(chan.GetData());
+ }
+ for (const auto& invite : state->mInvites.Values()) {
+ channelsToClose.AppendElement(invite.mChannel);
+ portsToClose.AppendElement(invite.mToMerge);
+ }
+ for (const auto& pendingPorts : state->mPendingMerges.Values()) {
+ portsToClose.AppendElements(pendingPorts);
+ }
+ state->mPeers.Clear();
+ state->mPendingMessages.Clear();
+ state->mInvites.Clear();
+ state->mPendingMerges.Clear();
+ }
+ for (auto& nodeChannel : channelsToClose) {
+ nodeChannel->Close();
+ }
+ for (auto& port : portsToClose) {
+ nodeController->mNode->ClosePort(port);
+ }
+ for (auto& name : lostConnections) {
+ nodeController->mNode->LostConnectionToNode(name);
+ }
+}
+
+#undef NODECONTROLLER_LOG
+#undef NODECONTROLLER_WARNING
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/NodeController.h b/ipc/glue/NodeController.h
new file mode 100644
index 0000000000..4b0d305b72
--- /dev/null
+++ b/ipc/glue/NodeController.h
@@ -0,0 +1,173 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_NodeController_h
+#define mozilla_ipc_NodeController_h
+
+#include "mojo/core/ports/event.h"
+#include "mojo/core/ports/name.h"
+#include "mojo/core/ports/node.h"
+#include "mojo/core/ports/node_delegate.h"
+#include "chrome/common/ipc_message.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "nsTHashMap.h"
+#include "mozilla/Queue.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ipc/NodeChannel.h"
+
+namespace mozilla::ipc {
+
+class NodeController final : public mojo::core::ports::NodeDelegate,
+ public NodeChannel::Listener {
+ using NodeName = mojo::core::ports::NodeName;
+ using PortName = mojo::core::ports::PortName;
+ using PortRef = mojo::core::ports::PortRef;
+ using Event = mojo::core::ports::Event;
+ using Node = mojo::core::ports::Node;
+ using UserData = mojo::core::ports::UserData;
+ using PortStatus = mojo::core::ports::PortStatus;
+ using UserMessageEvent = mojo::core::ports::UserMessageEvent;
+ using UserMessage = mojo::core::ports::UserMessage;
+
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NodeController, override)
+
+ // Return the global singleton instance. The returned value is only valid
+ // while the IO thread is alive.
+ static NodeController* GetSingleton();
+
+ class PortObserver : public UserData {
+ public:
+ virtual void OnPortStatusChanged() = 0;
+
+ protected:
+ ~PortObserver() override = default;
+ };
+
+ // NOTE: For now there will always be a single broker process, and all
+ // processes in the graph need to be able to talk to it (the parent process).
+ // Give it a fixed node name for now to simplify things.
+ //
+ // If we ever decide to have multiple node networks intercommunicating (e.g.
+ // multiple instances or background services), we may need to change this.
+ static constexpr NodeName kBrokerNodeName{0x1, 0x1};
+
+ bool IsBroker() const { return mName == kBrokerNodeName; }
+
+ // Mint a new connected pair of ports within the current process.
+ std::pair<ScopedPort, ScopedPort> CreatePortPair();
+
+ // Get a reference to the port with the given name. Returns an invalid
+ // `PortRef` if the name wasn't found.
+ PortRef GetPort(const PortName& aName);
+
+ // Set the observer for the given port. This observer will be notified when
+ // the status of the port changes.
+ void SetPortObserver(const PortRef& aPort, PortObserver* aObserver);
+
+ // See `mojo::core::ports::Node::GetStatus`
+ Maybe<PortStatus> GetStatus(const PortRef& aPort);
+
+ // See `mojo::core::ports::Node::ClosePort`
+ void ClosePort(const PortRef& aPort);
+
+ // Send a message to the the port's connected peer.
+ bool SendUserMessage(const PortRef& aPort, UniquePtr<IPC::Message> aMessage);
+
+ // Get the next message from the port's message queue.
+ // Will set `*aMessage` to the found message, or `nullptr`.
+ // Returns `false` and sets `*aMessage` to `nullptr` if no further messages
+ // will be delivered to this port as its peer has been closed.
+ bool GetMessage(const PortRef& aPort, UniquePtr<IPC::Message>* aMessage);
+
+ // Called in the broker process from GeckoChildProcessHost to introduce a new
+ // child process into the network. Returns a `PortRef` which can be used to
+ // communicate with the `PortRef` returned from `InitChildProcess`, and a
+ // reference to the `NodeChannel` created for the new process. The port can
+ // immediately have messages sent to it.
+ std::tuple<ScopedPort, RefPtr<NodeChannel>> InviteChildProcess(
+ UniquePtr<IPC::Channel> aChannel);
+
+ // Called as the IO thread is started in the parent process.
+ static void InitBrokerProcess();
+
+ // Called as the IO thread is started in a child process.
+ static ScopedPort InitChildProcess(UniquePtr<IPC::Channel> aChannel,
+ int32_t aParentPid = -1);
+
+ // Called when the IO thread is torn down.
+ static void CleanUp();
+
+ private:
+ explicit NodeController(const NodeName& aName);
+ ~NodeController();
+
+ UniquePtr<IPC::Message> SerializeEventMessage(
+ UniquePtr<Event> aEvent, const NodeName* aRelayTarget = nullptr,
+ uint32_t aType = EVENT_MESSAGE_TYPE);
+ UniquePtr<Event> DeserializeEventMessage(UniquePtr<IPC::Message> aMessage,
+ NodeName* aRelayTarget = nullptr);
+
+ // Get the `NodeChannel` for the named node.
+ already_AddRefed<NodeChannel> GetNodeChannel(const NodeName& aName);
+
+ // Stop communicating with this peer. Must be called on the IO thread.
+ void DropPeer(NodeName aNodeName);
+
+ // Message Handlers
+ void OnEventMessage(const NodeName& aFromNode,
+ UniquePtr<IPC::Message> aMessage) override;
+ void OnBroadcast(const NodeName& aFromNode,
+ UniquePtr<IPC::Message> aMessage) override;
+ void OnIntroduce(const NodeName& aFromNode,
+ NodeChannel::Introduction aIntroduction) override;
+ void OnRequestIntroduction(const NodeName& aFromNode,
+ const NodeName& aName) override;
+ void OnAcceptInvite(const NodeName& aFromNode, const NodeName& aRealName,
+ const PortName& aInitialPort) override;
+ void OnChannelError(const NodeName& aFromNode) override;
+
+ // NodeDelegate Implementation
+ void ForwardEvent(const NodeName& aNode, UniquePtr<Event> aEvent) override;
+ void BroadcastEvent(UniquePtr<Event> aEvent) override;
+ void PortStatusChanged(const PortRef& aPortRef) override;
+
+ const NodeName mName;
+ const UniquePtr<Node> mNode;
+
+ template <class T>
+ using NodeMap = nsTHashMap<NodeNameHashKey, T>;
+
+ struct Invite {
+ // The channel which is being invited. This will have a temporary name until
+ // the invite is completed.
+ RefPtr<NodeChannel> mChannel;
+ // The port which will be merged with the port information from the new
+ // child process when recieved.
+ PortRef mToMerge;
+ };
+
+ struct State {
+ // Channels for connecting to all known peers.
+ NodeMap<RefPtr<NodeChannel>> mPeers;
+
+ // Messages which are queued for peers which we been introduced to yet.
+ NodeMap<Queue<UniquePtr<IPC::Message>, 64>> mPendingMessages;
+
+ // Connections for peers being invited to the network.
+ NodeMap<Invite> mInvites;
+
+ // Ports which are waiting to be merged by a particular peer node.
+ NodeMap<nsTArray<PortRef>> mPendingMerges;
+ };
+
+ DataMutex<State> mState{"NodeController::mState"};
+};
+
+} // namespace mozilla::ipc
+
+#endif
diff --git a/ipc/glue/PBackground.ipdl b/ipc/glue/PBackground.ipdl
new file mode 100644
index 0000000000..b242bd13ea
--- /dev/null
+++ b/ipc/glue/PBackground.ipdl
@@ -0,0 +1,330 @@
+/* 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 protocol PBackgroundDataBridge;
+include protocol PBackgroundIDBFactory;
+include protocol PBackgroundIndexedDBUtils;
+include protocol PBackgroundSDBConnection;
+include protocol PBackgroundLSDatabase;
+include protocol PBackgroundLSObserver;
+include protocol PBackgroundLSRequest;
+include protocol PBackgroundLSSimpleRequest;
+include protocol PBackgroundLocalStorageCache;
+include protocol PBackgroundSessionStorageManager;
+include protocol PBackgroundSessionStorageService;
+include protocol PBackgroundStorage;
+include protocol PBackgroundTest;
+include protocol PBroadcastChannel;
+include protocol PCache;
+include protocol PCacheStorage;
+include protocol PCacheStreamControl;
+include protocol PClientManager;
+include protocol PEndpointForReport;
+include protocol PFileSystemManager;
+include protocol PFileSystemRequest;
+include protocol PGamepadEventChannel;
+include protocol PGamepadTestChannel;
+include protocol PHttpBackgroundChannel;
+include protocol PIPCClientCerts;
+include protocol PIdleScheduler;
+include protocol PMediaTransport;
+include protocol PRemoteWorker;
+include protocol PRemoteWorkerController;
+include protocol PRemoteWorkerService;
+include protocol PSharedWorker;
+include protocol PTemporaryIPCBlob;
+include protocol PFileCreator;
+include protocol PMessagePort;
+include protocol PCameras;
+include protocol PLockManager;
+include protocol PMIDIManager;
+include protocol PMIDIPort;
+include protocol PQuota;
+include protocol PSelectTLSClientAuthCert;
+include protocol PServiceWorker;
+include protocol PServiceWorkerContainer;
+include protocol PServiceWorkerManager;
+include protocol PServiceWorkerRegistration;
+include protocol PWebAuthnTransaction;
+include protocol PUDPSocket;
+include protocol PVerifySSLServerCert;
+include protocol PVsync;
+include protocol PRemoteDecoderManager;
+include protocol PWebSocketConnection;
+include protocol PWebTransport;
+include protocol PFetch;
+
+include ClientIPCTypes;
+include DOMTypes;
+include IPCBlob;
+include IPCServiceWorkerDescriptor;
+include IPCServiceWorkerRegistrationDescriptor;
+include PBackgroundLSSharedTypes;
+include PBackgroundSharedTypes;
+include PBackgroundIDBSharedTypes;
+include PFileSystemParams;
+include ProtocolTypes;
+include PSMIPCTypes;
+include RemoteWorkerTypes;
+include MIDITypes;
+
+include "mozilla/dom/cache/IPCUtils.h";
+include "mozilla/dom/quota/SerializationHelpers.h";
+include "mozilla/dom/PermissionMessageUtils.h";
+include "mozilla/layers/LayersMessageUtils.h";
+
+using mozilla::dom::cache::Namespace
+ from "mozilla/dom/cache/Types.h";
+
+using class mozilla::dom::SSCacheCopy from "mozilla/dom/PBackgroundSessionStorageCache.h";
+
+using mozilla::RemoteDecodeIn from "mozilla/RemoteDecoderManagerChild.h";
+
+namespace mozilla {
+namespace ipc {
+
+[NeedsOtherPid, ChildImpl=virtual, ParentImpl=virtual]
+sync protocol PBackground
+{
+ manages PBackgroundDataBridge;
+ manages PBackgroundIDBFactory;
+ manages PBackgroundIndexedDBUtils;
+ manages PBackgroundSDBConnection;
+ manages PBackgroundLSDatabase;
+ manages PBackgroundLSObserver;
+ manages PBackgroundLSRequest;
+ manages PBackgroundLSSimpleRequest;
+ manages PBackgroundLocalStorageCache;
+ manages PBackgroundSessionStorageManager;
+ manages PBackgroundSessionStorageService;
+ manages PBackgroundStorage;
+ manages PBackgroundTest;
+ manages PBroadcastChannel;
+ manages PCache;
+ manages PCacheStorage;
+ manages PCacheStreamControl;
+ manages PClientManager;
+ manages PEndpointForReport;
+ manages PFileSystemRequest;
+ manages PGamepadEventChannel;
+ manages PGamepadTestChannel;
+ manages PHttpBackgroundChannel;
+ manages PIPCClientCerts;
+ manages PIdleScheduler;
+ manages PLockManager;
+ manages PMediaTransport;
+ manages PRemoteWorker;
+ manages PRemoteWorkerController;
+ manages PRemoteWorkerService;
+ manages PSharedWorker;
+ manages PTemporaryIPCBlob;
+ manages PFileCreator;
+ manages PMessagePort;
+ manages PCameras;
+ manages PQuota;
+ manages PSelectTLSClientAuthCert;
+ manages PServiceWorker;
+ manages PServiceWorkerContainer;
+ manages PServiceWorkerManager;
+ manages PServiceWorkerRegistration;
+ manages PWebAuthnTransaction;
+ manages PWebSocketConnection;
+ manages PUDPSocket;
+ manages PVerifySSLServerCert;
+ manages PVsync;
+ manages PFetch;
+
+parent:
+ // Only called at startup during mochitests to check the basic infrastructure.
+ async PBackgroundTest(nsCString testArg);
+
+ async PBackgroundDataBridge(uint64_t channelID);
+
+ async PBackgroundIDBFactory(LoggingInfo loggingInfo);
+
+ async PBackgroundIndexedDBUtils();
+
+ // Use only for testing!
+ async FlushPendingFileDeletions();
+
+ async PBackgroundSDBConnection(PersistenceType persistenceType,
+ PrincipalInfo principalInfo);
+
+ async PBackgroundLSDatabase(PrincipalInfo principalInfo,
+ uint32_t privateBrowsingId,
+ uint64_t datastoreId);
+
+ async PBackgroundLSObserver(uint64_t observerId);
+
+ /**
+ * Issue an asynchronous request that will be used in a synchronous fashion
+ * through complex machinations described in `PBackgroundLSRequest.ipdl` and
+ * `LSObject.h`.
+ */
+ async PBackgroundLSRequest(LSRequestParams params);
+
+ /**
+ * Issues a simple, non-cancelable asynchronous request that's used in an
+ * asynchronous fashion by callers. (LSRequest is not simple because it used
+ * in a synchronous fashion which leads to complexities regarding cancelation,
+ * see `PBackgroundLSRequest.ipdl` for details.)
+ */
+ async PBackgroundLSSimpleRequest(LSSimpleRequestParams params);
+
+ async PBackgroundLocalStorageCache(PrincipalInfo principalInfo,
+ nsCString originKey,
+ uint32_t privateBrowsingId);
+
+ async PBackgroundSessionStorageManager(uint64_t aTopContextId);
+
+ async PBackgroundSessionStorageService();
+
+ async PBackgroundStorage(nsString profilePath, uint32_t privateBrowsingId);
+
+ /**
+ * Finish the setup of a new PFileSystemManager top level protocol.
+ */
+ async CreateFileSystemManagerParent(
+ PrincipalInfo principalInfo,
+ Endpoint<PFileSystemManagerParent> aParentEndpoint)
+ returns(nsresult rv);
+
+ /**
+ * Finish the setup of a new PWebTransport top level protocol.
+ */
+ async CreateWebTransportParent(
+ nsString aURL,
+ nullable nsIPrincipal aPrincipal,
+ IPCClientInfo? aClientInfo,
+ bool aDedicated,
+ bool aRequireUnreliable,
+ uint32_t aCongestionControl,
+ /* Sequence<WebTransportHash>* aServerCertHashes, */
+ Endpoint<PWebTransportParent> aParentEndpoint)
+ returns(nsresult rv, uint8_t aReliability); // Actually WebTransportReliabityMode enum
+
+ async PVsync();
+
+ async PCameras();
+
+ async PUDPSocket(PrincipalInfo? pInfo, nsCString filter);
+ async PBroadcastChannel(PrincipalInfo pInfo, nsCString origin, nsString channel);
+
+ async PServiceWorkerManager();
+
+ async ShutdownServiceWorkerRegistrar();
+
+ async PCacheStorage(Namespace aNamespace, PrincipalInfo aPrincipalInfo);
+
+ async PMessagePort(nsID uuid, nsID destinationUuid, uint32_t sequenceId);
+
+ async MessagePortForceClose(nsID uuid, nsID destinationUuid, uint32_t sequenceId);
+
+ async PQuota();
+
+ async ShutdownQuotaManager();
+
+ async ShutdownBackgroundSessionStorageManagers();
+
+ async PropagateBackgroundSessionStorageManager(uint64_t currentTopContextId, uint64_t targetTopContextId);
+
+ async RemoveBackgroundSessionStorageManager(uint64_t topContextId);
+
+ async GetSessionStorageManagerData(
+ uint64_t aTopContextId, uint32_t aSizeLimit, bool aCancelSessionStoreTimer)
+ returns(SSCacheCopy[] aCacheCopy);
+
+ async LoadSessionStorageManagerData(uint64_t aTopContextId, SSCacheCopy[] aOriginCacheCopy);
+
+ async PFileSystemRequest(FileSystemParams params);
+
+ async PGamepadEventChannel();
+
+ async PGamepadTestChannel();
+
+ async PHttpBackgroundChannel(uint64_t channelId);
+
+ async PWebAuthnTransaction();
+
+ async PSharedWorker(RemoteWorkerData data,
+ uint64_t windowID,
+ MessagePortIdentifier portIdentifier);
+
+ async PTemporaryIPCBlob();
+
+ async PFileCreator(nsString aFullPath, nsString aType, nsString aName,
+ int64_t? lastModified, bool aExistenceCheck,
+ bool aIsFromNsIFile);
+
+ async PClientManager();
+
+ async CreateMIDIManager(Endpoint<PMIDIManagerParent> aEndpoint);
+ async CreateMIDIPort(Endpoint<PMIDIPortParent> aEndpoint,
+ MIDIPortInfo portInfo, bool sysexEnabled);
+ async HasMIDIDevice() returns (bool hasDevice);
+
+ // This method is used to propagate storage activities from the child actor
+ // to the parent actor. See StorageActivityService.
+ async StorageActivity(PrincipalInfo principalInfo);
+
+ async PServiceWorker(IPCServiceWorkerDescriptor aDescriptor);
+
+ async PRemoteWorkerController(RemoteWorkerData aData);
+
+ async PRemoteWorkerService();
+
+ async PServiceWorkerContainer();
+
+ async PServiceWorkerRegistration(IPCServiceWorkerRegistrationDescriptor aDescriptor);
+
+ async PEndpointForReport(nsString aGroupName, PrincipalInfo aPrincipalInfo);
+
+ async RemoveEndpoint(nsString aGroupName, nsCString aEndpointURL,
+ PrincipalInfo aPrincipalInfo);
+
+ async PIdleScheduler();
+
+ async PMediaTransport();
+
+ async PSelectTLSClientAuthCert(nsCString aHostName,
+ OriginAttributes aOriginAttributes,
+ int32_t aPort,
+ uint32_t aProviderFlags,
+ uint32_t aProviderTlsFlags,
+ ByteArray aServerCertBytes,
+ ByteArray[] aCANames);
+
+ async PVerifySSLServerCert(ByteArray[] aPeerCertChain,
+ nsCString aHostName,
+ int32_t aPort,
+ OriginAttributes aOriginAttributes,
+ ByteArray? aStapledOCSPResponse,
+ ByteArray? aSctsFromTLSExtension,
+ DelegatedCredentialInfoArg? aDcInfo,
+ uint32_t aProviderFlags,
+ uint32_t aCertVerifierFlags);
+
+ async EnsureRDDProcessAndCreateBridge()
+ returns (nsresult rv, Endpoint<PRemoteDecoderManagerChild> aEndpoint);
+
+ async EnsureUtilityProcessAndCreateBridge(RemoteDecodeIn aLocation)
+ returns (nsresult rv, Endpoint<PRemoteDecoderManagerChild> aEndpoint);
+
+ async PWebSocketConnection(uint32_t aListenerId);
+
+ async PLockManager(ContentPrincipalInfo aPrincipalInfo, nsID aClientId);
+
+ async PIPCClientCerts();
+
+ async PFetch();
+
+child:
+ async PCache();
+ async PCacheStreamControl();
+
+ async PRemoteWorker(RemoteWorkerData data);
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/PBackgroundSharedTypes.ipdlh b/ipc/glue/PBackgroundSharedTypes.ipdlh
new file mode 100644
index 0000000000..9013b36d94
--- /dev/null
+++ b/ipc/glue/PBackgroundSharedTypes.ipdlh
@@ -0,0 +1,73 @@
+/* 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/. */
+
+using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+using struct mozilla::void_t from "mozilla/ipc/IPCCore.h";
+
+namespace mozilla {
+namespace ipc {
+
+[Comparable] struct ContentSecurityPolicy
+{
+ nsString policy;
+ bool reportOnlyFlag;
+ bool deliveredViaMetaTagFlag;
+};
+
+[Comparable] struct ContentPrincipalInfo
+{
+ OriginAttributes attrs;
+
+ // Origin is not simply a part of the spec. Based on the scheme of the URI
+ // spec, we generate different kind of origins: for instance any file: URL
+ // shares the same origin, about: URLs have the full spec as origin and so
+ // on.
+ // Another important reason why we have this attribute is that
+ // ContentPrincipalInfo is used out of the main-thread. Having this value
+ // here allows us to retrive the origin without creating a full nsIPrincipal.
+ nsCString originNoSuffix;
+
+ nsCString spec;
+
+ nsCString? domain;
+
+ // Like originNoSuffix, baseDomain is used out of the main-thread.
+ nsCString baseDomain;
+};
+
+[Comparable] struct SystemPrincipalInfo
+{ };
+
+[Comparable] struct NullPrincipalInfo
+{
+ OriginAttributes attrs;
+ nsCString spec;
+};
+
+[Comparable] struct ExpandedPrincipalInfo
+{
+ OriginAttributes attrs;
+ PrincipalInfo[] allowlist;
+};
+
+[Comparable] union PrincipalInfo
+{
+ ContentPrincipalInfo;
+ SystemPrincipalInfo;
+ NullPrincipalInfo;
+ ExpandedPrincipalInfo;
+};
+
+[Comparable] struct CSPInfo
+{
+ ContentSecurityPolicy[] policyInfos;
+ PrincipalInfo requestPrincipalInfo;
+ nsCString selfURISpec;
+ nsString referrer;
+ uint64_t innerWindowID;
+ bool skipAllowInlineStyleCheck;
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/PBackgroundStarter.ipdl b/ipc/glue/PBackgroundStarter.ipdl
new file mode 100644
index 0000000000..c53f47cb75
--- /dev/null
+++ b/ipc/glue/PBackgroundStarter.ipdl
@@ -0,0 +1,18 @@
+/* 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 protocol PBackground;
+
+namespace mozilla {
+namespace ipc {
+
+[NeedsOtherPid]
+async protocol PBackgroundStarter
+{
+parent:
+ async InitBackground(Endpoint<PBackgroundParent> aEndpoint);
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/PBackgroundTest.ipdl b/ipc/glue/PBackgroundTest.ipdl
new file mode 100644
index 0000000000..5027010994
--- /dev/null
+++ b/ipc/glue/PBackgroundTest.ipdl
@@ -0,0 +1,21 @@
+/* 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 protocol PBackground;
+
+namespace mozilla {
+namespace ipc {
+
+// This is a very simple testing protocol that is only used during mochitests.
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+protocol PBackgroundTest
+{
+ manager PBackground;
+
+child:
+ async __delete__(nsCString testArg);
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/PIdleScheduler.ipdl b/ipc/glue/PIdleScheduler.ipdl
new file mode 100644
index 0000000000..1857c12eaf
--- /dev/null
+++ b/ipc/glue/PIdleScheduler.ipdl
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 protocol PBackground;
+using mozilla::TimeDuration from "mozilla/TimeStamp.h";
+[MoveOnly] using base::SharedMemoryHandle from "base/shared_memory.h";
+namespace mozilla {
+namespace ipc {
+
+/**
+ * PIdleScheduler is the protocol for cross-process idle scheduling.
+ * Only child processes participate in the scheduling and parent process
+ * can run its idle tasks whenever it needs to.
+ *
+ * The scheduler keeps track of the following things.
+ * - Activity of the main thread of each child process. A process is active
+ * when it is running tasks. Because of performance cross-process
+ * counters in shared memory are used for the activity tracking. There is
+ * one counter counting the activity state of all the processes and one
+ * counter for each process. This way if a child process crashes, the global
+ * counter can be updated by decrementing the per process counter from it.
+ * - Child processes running prioritized operation. Top level page loads is an
+ * example of a prioritized operation. When such is ongoing, idle tasks are
+ * less likely to run.
+ * - Idle requests. When a child process locally has idle tasks to run, it
+ * requests idle time from the scheduler. Initially requests go to a wait list
+ * and the scheduler runs and if there are free logical cores for the child
+ * processes, idle time is given to the child process, and the process goes to
+ * the idle list. Once idle time has been consumed or there are no tasks to
+ * process, child process informs the scheduler and the process is moved back
+ * to the default queue.
+ */
+async protocol PIdleScheduler
+{
+ manager PBackground;
+
+child:
+ async IdleTime(uint64_t id, TimeDuration budget);
+
+parent:
+ async InitForIdleUse() returns (SharedMemoryHandle? state, uint32_t childId);
+ async RequestIdleTime(uint64_t id, TimeDuration budget);
+ async IdleTimeUsed(uint64_t id);
+
+ // Child can send explicit Schedule message to parent if it thinks parent process
+ // might be able to let some other process to use idle time.
+ async Schedule();
+
+ // Note, these two messages can be sent even before InitForIdleUse.
+ async RunningPrioritizedOperation();
+ async PrioritizedOperationDone();
+
+ // Ask if now would be a good time to GC
+ async RequestGC() returns (bool may_gc);
+
+ // Let the parent know when we start a GC without asking first.
+ async StartedGC();
+
+ // Called for ending any kind of GC.
+ async DoneGC();
+
+ // This message is never sent. Each PIdleScheduler actor will stay alive as long as
+ // its PBackground manager.
+ async __delete__();
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/PUtilityAudioDecoder.ipdl b/ipc/glue/PUtilityAudioDecoder.ipdl
new file mode 100644
index 0000000000..cedbd64052
--- /dev/null
+++ b/ipc/glue/PUtilityAudioDecoder.ipdl
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 GraphicsMessages;
+include protocol PRemoteDecoderManager;
+include protocol PVideoBridge;
+
+using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h";
+using mozilla::media::MediaCodecsSupported from "MediaCodecsSupport.h";
+using mozilla::RemoteDecodeIn from "mozilla/RemoteDecoderManagerChild.h";
+
+namespace mozilla {
+
+namespace ipc {
+
+// This protocol allows to run media audio decoding infrastructure on top
+// of the Utility process
+protocol PUtilityAudioDecoder
+{
+parent:
+ async NewContentRemoteDecoderManager(
+ Endpoint<PRemoteDecoderManagerParent> endpoint, ContentParentId parentId);
+
+#ifdef MOZ_WMF_MEDIA_ENGINE
+ async InitVideoBridge(Endpoint<PVideoBridgeChild> endpoint,
+ GfxVarUpdate[] updates,
+ ContentDeviceData contentDeviceData);
+
+ async UpdateVar(GfxVarUpdate var);
+#endif
+
+child:
+ async UpdateMediaCodecsSupported(RemoteDecodeIn aLocation,
+ MediaCodecsSupported aSupported);
+
+#ifdef MOZ_WMF_MEDIA_ENGINE
+ async CompleteCreatedVideoBridge();
+#endif
+
+};
+
+} // namespace ipc
+
+} // namespace mozilla
diff --git a/ipc/glue/PUtilityProcess.ipdl b/ipc/glue/PUtilityProcess.ipdl
new file mode 100644
index 0000000000..29452ed61e
--- /dev/null
+++ b/ipc/glue/PUtilityProcess.ipdl
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 MemoryReportTypes;
+include PrefsTypes;
+
+include protocol PProfiler;
+include protocol PUtilityAudioDecoder;
+include protocol PJSOracle;
+
+#if defined(XP_WIN)
+include protocol PWindowsUtils;
+#endif
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+include protocol PSandboxTesting;
+#endif
+
+include "mozilla/ipc/ByteBufUtils.h";
+
+using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h";
+
+// Telemetry
+using mozilla::Telemetry::HistogramAccumulation from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::KeyedHistogramAccumulation from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::ChildEventData from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h";
+
+#if defined(XP_WIN)
+[MoveOnly] using mozilla::UntrustedModulesData from "mozilla/UntrustedModulesData.h";
+[MoveOnly] using mozilla::ModulePaths from "mozilla/UntrustedModulesData.h";
+[MoveOnly] using mozilla::ModulesMapResult from "mozilla/UntrustedModulesData.h";
+#endif // defined(XP_WIN)
+
+namespace mozilla {
+
+namespace ipc {
+
+// This protocol allows the UI process to talk to the Utility process. There is
+// one instance of this protocol, with the UtilityProcessParent living on the main thread
+// of the main process and the UtilityProcessChild living on the main thread of the Utility
+// process.
+[NeedsOtherPid] protocol PUtilityProcess
+{
+parent:
+ async InitCrashReporter(NativeThreadId threadId);
+
+ async AddMemoryReport(MemoryReport aReport);
+
+ // Sent from time-to-time to limit the amount of telemetry vulnerable to loss
+ // Buffer contains bincoded Rust structs.
+ // https://firefox-source-docs.mozilla.org/toolkit/components/glean/dev/ipc.html
+ async FOGData(ByteBuf buf);
+
+#if defined(XP_WIN)
+ async GetModulesTrust(ModulePaths aModPaths, bool aRunAtNormalPriority)
+ returns (ModulesMapResult? modMapResult);
+#endif // defined(XP_WIN)
+
+ // Messages for sending telemetry to parent process.
+ async AccumulateChildHistograms(HistogramAccumulation[] accumulations);
+ async AccumulateChildKeyedHistograms(KeyedHistogramAccumulation[] accumulations);
+ async UpdateChildScalars(ScalarAction[] actions);
+ async UpdateChildKeyedScalars(KeyedScalarAction[] actions);
+ async RecordChildEvents(ChildEventData[] events);
+ async RecordDiscardedData(DiscardedData data);
+
+ async InitCompleted();
+
+child:
+ async Init(FileDescriptor? sandboxBroker, bool canRecordReleaseTelemetry, bool aIsReadyForBackgroundProcessing);
+
+ async InitProfiler(Endpoint<PProfilerChild> endpoint);
+
+ async RequestMemoryReport(uint32_t generation,
+ bool anonymize,
+ bool minimizeMemoryUsage,
+ FileDescriptor? DMDFile)
+ returns (uint32_t aGeneration);
+
+ async PreferenceUpdate(Pref pref);
+
+ // Tells the Utility process to flush any pending telemetry.
+ // Used in tests and ping assembly. Buffer contains bincoded Rust structs.
+ // https://firefox-source-docs.mozilla.org/toolkit/components/glean/dev/ipc.html
+ async FlushFOGData() returns (ByteBuf buf);
+
+ // Test-only method.
+ // Asks the Utility process to trigger test-only instrumentation.
+ // The unused returned value is to have a promise we can await.
+ async TestTriggerMetrics() returns (bool unused);
+
+ async TestTelemetryProbes();
+
+ async StartUtilityAudioDecoderService(Endpoint<PUtilityAudioDecoderParent> aEndpoint);
+
+ async StartJSOracleService(Endpoint<PJSOracleChild> aEndpoint);
+
+#if defined(XP_WIN)
+ async StartWindowsUtilsService(Endpoint<PWindowsUtilsChild> aEndpoint);
+
+ async GetUntrustedModulesData() returns (UntrustedModulesData? data);
+
+ /**
+ * This method is used to notify a child process to start
+ * processing module loading events in UntrustedModulesProcessor.
+ * This should be called when the parent process has gone idle.
+ */
+ async UnblockUntrustedModulesThread();
+#endif // defined(XP_WIN)
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+ async InitSandboxTesting(Endpoint<PSandboxTestingChild> aEndpoint);
+#endif
+};
+
+} // namespace ipc
+
+} // namespace mozilla
diff --git a/ipc/glue/ProcessChild.cpp b/ipc/glue/ProcessChild.cpp
new file mode 100644
index 0000000000..7a8a74cc1e
--- /dev/null
+++ b/ipc/glue/ProcessChild.cpp
@@ -0,0 +1,137 @@
+/* -*- 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 "mozilla/ipc/ProcessChild.h"
+
+#include "Endpoint.h"
+#include "nsDebug.h"
+
+#ifdef XP_WIN
+# include <stdlib.h> // for _exit()
+# include <synchapi.h>
+#else
+# include <unistd.h> // for _exit()
+# include <time.h>
+# include "base/eintr_wrapper.h"
+# include "prenv.h"
+#endif
+
+#include "nsAppRunner.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/ipc/CrashReporterClient.h"
+#include "mozilla/ipc/IOThreadChild.h"
+#include "mozilla/GeckoArgs.h"
+
+namespace mozilla {
+namespace ipc {
+
+ProcessChild* ProcessChild::gProcessChild;
+
+static Atomic<bool> sExpectingShutdown(false);
+
+ProcessChild::ProcessChild(ProcessId aParentPid, const nsID& aMessageChannelId)
+ : ChildProcess(new IOThreadChild()),
+ mUILoop(MessageLoop::current()),
+ mParentPid(aParentPid),
+ mMessageChannelId(aMessageChannelId) {
+ MOZ_ASSERT(mUILoop, "UILoop should be created by now");
+ MOZ_ASSERT(!gProcessChild, "should only be one ProcessChild");
+ gProcessChild = this;
+}
+
+/* static */
+void ProcessChild::AddPlatformBuildID(std::vector<std::string>& aExtraArgs) {
+ nsCString parentBuildID(mozilla::PlatformBuildID());
+ geckoargs::sParentBuildID.Put(parentBuildID.get(), aExtraArgs);
+}
+
+/* static */
+bool ProcessChild::InitPrefs(int aArgc, char* aArgv[]) {
+ Maybe<uint64_t> prefsHandle = Some(0);
+ Maybe<uint64_t> prefMapHandle = Some(0);
+ Maybe<uint64_t> prefsLen = geckoargs::sPrefsLen.Get(aArgc, aArgv);
+ Maybe<uint64_t> prefMapSize = geckoargs::sPrefMapSize.Get(aArgc, aArgv);
+
+ if (prefsLen.isNothing() || prefMapSize.isNothing()) {
+ return false;
+ }
+
+#ifdef XP_WIN
+ prefsHandle = geckoargs::sPrefsHandle.Get(aArgc, aArgv);
+ prefMapHandle = geckoargs::sPrefMapHandle.Get(aArgc, aArgv);
+
+ if (prefsHandle.isNothing() || prefMapHandle.isNothing()) {
+ return false;
+ }
+#endif
+
+ SharedPreferenceDeserializer deserializer;
+ return deserializer.DeserializeFromSharedMemory(*prefsHandle, *prefMapHandle,
+ *prefsLen, *prefMapSize);
+}
+
+#ifdef ENABLE_TESTS
+// Allow tests to cause a synthetic delay/"hang" during child process
+// shutdown by setting environment variables.
+# ifdef XP_UNIX
+static void ReallySleep(int aSeconds) {
+ struct ::timespec snooze = {aSeconds, 0};
+ HANDLE_EINTR(nanosleep(&snooze, &snooze));
+}
+# elif defined(XP_WIN)
+static void ReallySleep(int aSeconds) { ::Sleep(aSeconds * 1000); }
+# endif // Unix/Win
+static void SleepIfEnv(const char* aName) {
+ if (auto* value = PR_GetEnv(aName)) {
+ ReallySleep(atoi(value));
+ }
+}
+#else // not tests
+static void SleepIfEnv(const char* aName) {}
+#endif
+
+ProcessChild::~ProcessChild() {
+#ifdef NS_FREE_PERMANENT_DATA
+ // In this case, we won't early-exit and we'll wait indefinitely for
+ // child processes to terminate. This sleep is late enough that, in
+ // content processes, it won't block parent process shutdown, so
+ // we'll get into late IPC shutdown with processes still running.
+ SleepIfEnv("MOZ_TEST_CHILD_EXIT_HANG");
+#endif
+ gProcessChild = nullptr;
+}
+
+/* static */
+void ProcessChild::NotifiedImpendingShutdown() {
+ sExpectingShutdown = true;
+ CrashReporter::AppendToCrashReportAnnotation(
+ CrashReporter::Annotation::IPCShutdownState,
+ "NotifiedImpendingShutdown"_ns);
+}
+
+/* static */
+bool ProcessChild::ExpectingShutdown() { return sExpectingShutdown; }
+
+/* static */
+void ProcessChild::QuickExit() {
+#ifndef NS_FREE_PERMANENT_DATA
+ // In this case, we're going to terminate the child process before
+ // we get to ~ProcessChild above (and terminate the parent process
+ // before the shutdown hook in ProcessWatcher). Instead, blocking
+ // earlier will let us exercise ProcessWatcher's kill timer.
+ SleepIfEnv("MOZ_TEST_CHILD_EXIT_HANG");
+#endif
+ AppShutdown::DoImmediateExit();
+}
+
+UntypedEndpoint ProcessChild::TakeInitialEndpoint() {
+ return UntypedEndpoint{PrivateIPDLInterface{},
+ child_thread()->TakeInitialPort(), mMessageChannelId,
+ base::GetCurrentProcId(), mParentPid};
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/ProcessChild.h b/ipc/glue/ProcessChild.h
new file mode 100644
index 0000000000..29948a9127
--- /dev/null
+++ b/ipc/glue/ProcessChild.h
@@ -0,0 +1,75 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_ProcessChild_h
+#define mozilla_ipc_ProcessChild_h
+
+#include "Endpoint.h"
+#include "base/message_loop.h"
+#include "base/process.h"
+
+#include "chrome/common/child_process.h"
+
+#include "mozilla/ipc/ProcessUtils.h"
+
+// ProcessChild is the base class for all subprocesses of the main
+// browser process. Its code runs on the thread that started in
+// main().
+
+namespace mozilla {
+namespace ipc {
+
+class ProcessChild : public ChildProcess {
+ protected:
+ typedef base::ProcessId ProcessId;
+
+ public:
+ explicit ProcessChild(ProcessId aParentPid, const nsID& aMessageChannelId);
+
+ ProcessChild(const ProcessChild&) = delete;
+ ProcessChild& operator=(const ProcessChild&) = delete;
+
+ virtual ~ProcessChild();
+
+ virtual bool Init(int aArgc, char* aArgv[]) = 0;
+
+ static void AddPlatformBuildID(std::vector<std::string>& aExtraArgs);
+
+ static bool InitPrefs(int aArgc, char* aArgv[]);
+
+ virtual void CleanUp() {}
+
+ static MessageLoop* message_loop() { return gProcessChild->mUILoop; }
+
+ static void NotifiedImpendingShutdown();
+
+ static bool ExpectingShutdown();
+
+ /**
+ * Exit *now*. Do not shut down XPCOM, do not pass Go, do not run
+ * static destructors, do not collect $200.
+ */
+ static void QuickExit();
+
+ protected:
+ static ProcessChild* current() { return gProcessChild; }
+
+ ProcessId ParentPid() { return mParentPid; }
+
+ UntypedEndpoint TakeInitialEndpoint();
+
+ private:
+ static ProcessChild* gProcessChild;
+
+ MessageLoop* mUILoop;
+ ProcessId mParentPid;
+ nsID mMessageChannelId;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // ifndef mozilla_ipc_ProcessChild_h
diff --git a/ipc/glue/ProcessUtils.h b/ipc/glue/ProcessUtils.h
new file mode 100644
index 0000000000..b0f146ef6d
--- /dev/null
+++ b/ipc/glue/ProcessUtils.h
@@ -0,0 +1,93 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_ProcessUtils_h
+#define mozilla_ipc_ProcessUtils_h
+
+#include <functional>
+#include <vector>
+
+#include "mozilla/ipc/FileDescriptor.h"
+#include "base/shared_memory.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Preferences.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+namespace ipc {
+
+class GeckoChildProcessHost;
+
+// You probably should call ContentChild::SetProcessName instead of calling
+// this directly.
+void SetThisProcessName(const char* aName);
+
+class SharedPreferenceSerializer final {
+ public:
+ explicit SharedPreferenceSerializer();
+ SharedPreferenceSerializer(SharedPreferenceSerializer&& aOther);
+ ~SharedPreferenceSerializer();
+
+ bool SerializeToSharedMemory(const GeckoProcessType aDestinationProcessType,
+ const nsACString& aDestinationRemoteType);
+
+ size_t GetPrefMapSize() const { return mPrefMapSize; }
+ size_t GetPrefsLength() const { return mPrefsLength; }
+
+ const UniqueFileHandle& GetPrefsHandle() const { return mPrefsHandle; }
+
+ const UniqueFileHandle& GetPrefMapHandle() const { return mPrefMapHandle; }
+
+ void AddSharedPrefCmdLineArgs(GeckoChildProcessHost& procHost,
+ std::vector<std::string>& aExtraOpts) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SharedPreferenceSerializer);
+ size_t mPrefMapSize;
+ size_t mPrefsLength;
+ UniqueFileHandle mPrefMapHandle;
+ UniqueFileHandle mPrefsHandle;
+};
+
+class SharedPreferenceDeserializer final {
+ public:
+ SharedPreferenceDeserializer();
+ ~SharedPreferenceDeserializer();
+
+ bool DeserializeFromSharedMemory(uint64_t aPrefsHandle,
+ uint64_t aPrefMapHandle, uint64_t aPrefsLen,
+ uint64_t aPrefMapSize);
+
+ const FileDescriptor& GetPrefMapHandle() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SharedPreferenceDeserializer);
+ Maybe<FileDescriptor> mPrefMapHandle;
+ Maybe<size_t> mPrefsLen;
+ Maybe<size_t> mPrefMapSize;
+ base::SharedMemory mShmem;
+};
+
+#ifdef ANDROID
+// Android doesn't use -prefsHandle or -prefMapHandle. It gets those FDs
+// another way.
+void SetPrefsFd(int aFd);
+void SetPrefMapFd(int aFd);
+#endif
+
+// Generate command line argument to spawn a child process. If the shared memory
+// is not properly initialized, this would be a no-op.
+void ExportSharedJSInit(GeckoChildProcessHost& procHost,
+ std::vector<std::string>& aExtraOpts);
+
+// Initialize the content used by the JS engine during the initialization of a
+// JS::Runtime.
+bool ImportSharedJSInit(uint64_t aJsInitHandle, uint64_t aJsInitLen);
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // ifndef mozilla_ipc_ProcessUtils_h
diff --git a/ipc/glue/ProcessUtils_bsd.cpp b/ipc/glue/ProcessUtils_bsd.cpp
new file mode 100644
index 0000000000..fa113f2e58
--- /dev/null
+++ b/ipc/glue/ProcessUtils_bsd.cpp
@@ -0,0 +1,27 @@
+/* -*- 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 "ProcessUtils.h"
+
+#include <pthread.h>
+
+#if !defined(OS_NETBSD)
+# include <pthread_np.h>
+#endif
+
+namespace mozilla {
+namespace ipc {
+
+void SetThisProcessName(const char* aName) {
+#if defined(OS_NETBSD)
+ pthread_setname_np(pthread_self(), "%s", (void*)aName);
+#else
+ pthread_set_name_np(pthread_self(), aName);
+#endif
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/ProcessUtils_common.cpp b/ipc/glue/ProcessUtils_common.cpp
new file mode 100644
index 0000000000..69eb4bdf28
--- /dev/null
+++ b/ipc/glue/ProcessUtils_common.cpp
@@ -0,0 +1,275 @@
+/* -*- 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 "ProcessUtils.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/GeckoArgs.h"
+#include "mozilla/dom/RemoteType.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsPrintfCString.h"
+
+#include "XPCSelfHostedShmem.h"
+
+namespace mozilla {
+namespace ipc {
+
+SharedPreferenceSerializer::SharedPreferenceSerializer()
+ : mPrefMapSize(0), mPrefsLength(0) {
+ MOZ_COUNT_CTOR(SharedPreferenceSerializer);
+}
+
+SharedPreferenceSerializer::~SharedPreferenceSerializer() {
+ MOZ_COUNT_DTOR(SharedPreferenceSerializer);
+}
+
+SharedPreferenceSerializer::SharedPreferenceSerializer(
+ SharedPreferenceSerializer&& aOther)
+ : mPrefMapSize(aOther.mPrefMapSize),
+ mPrefsLength(aOther.mPrefsLength),
+ 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(&mPrefMapSize).TakePlatformHandle();
+
+ 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);
+ mPrefsLength = prefs.Length();
+
+ base::SharedMemory shm;
+ // Set up the shared memory.
+ if (!shm.Create(prefs.Length())) {
+ NS_ERROR("failed to create shared memory in the parent");
+ return false;
+ }
+ if (!shm.Map(prefs.Length())) {
+ NS_ERROR("failed to map shared memory in the parent");
+ return false;
+ }
+
+ // Copy the serialized prefs into the shared memory.
+ memcpy(static_cast<char*>(shm.memory()), prefs.get(), mPrefsLength);
+
+ mPrefsHandle = shm.TakeHandle();
+ return true;
+}
+
+void SharedPreferenceSerializer::AddSharedPrefCmdLineArgs(
+ mozilla::ipc::GeckoChildProcessHost& procHost,
+ std::vector<std::string>& aExtraOpts) const {
+#if defined(XP_WIN)
+ // Record the handle as to-be-shared, and pass it via a command flag. This
+ // works because Windows handles are system-wide.
+ procHost.AddHandleToShare(GetPrefsHandle().get());
+ procHost.AddHandleToShare(GetPrefMapHandle().get());
+ geckoargs::sPrefsHandle.Put((uintptr_t)(GetPrefsHandle().get()), aExtraOpts);
+ geckoargs::sPrefMapHandle.Put((uintptr_t)(GetPrefMapHandle().get()),
+ aExtraOpts);
+#else
+ // In contrast, Unix fds are per-process. So remap the fd to a fixed one that
+ // will be used in the child.
+ // XXX: bug 1440207 is about improving how fixed fds are used.
+ //
+ // Note: on Android, AddFdToRemap() sets up the fd to be passed via a Parcel,
+ // and the fixed fd isn't used. However, we still need to mark it for
+ // remapping so it doesn't get closed in the child.
+ procHost.AddFdToRemap(GetPrefsHandle().get(), kPrefsFileDescriptor);
+ procHost.AddFdToRemap(GetPrefMapHandle().get(), kPrefMapFileDescriptor);
+#endif
+
+ // Pass the lengths via command line flags.
+ geckoargs::sPrefsLen.Put((uintptr_t)(GetPrefsLength()), aExtraOpts);
+ geckoargs::sPrefMapSize.Put((uintptr_t)(GetPrefMapSize()), aExtraOpts);
+}
+
+#ifdef ANDROID
+static int gPrefsFd = -1;
+static int gPrefMapFd = -1;
+
+void SetPrefsFd(int aFd) { gPrefsFd = aFd; }
+
+void SetPrefMapFd(int aFd) { gPrefMapFd = aFd; }
+#endif
+
+SharedPreferenceDeserializer::SharedPreferenceDeserializer() {
+ MOZ_COUNT_CTOR(SharedPreferenceDeserializer);
+}
+
+SharedPreferenceDeserializer::~SharedPreferenceDeserializer() {
+ MOZ_COUNT_DTOR(SharedPreferenceDeserializer);
+}
+
+bool SharedPreferenceDeserializer::DeserializeFromSharedMemory(
+ uint64_t aPrefsHandle, uint64_t aPrefMapHandle, uint64_t aPrefsLen,
+ uint64_t aPrefMapSize) {
+ Maybe<base::SharedMemoryHandle> prefsHandle;
+
+#ifdef XP_WIN
+ prefsHandle = Some(UniqueFileHandle(HANDLE((uintptr_t)(aPrefsHandle))));
+ if (!aPrefsHandle) {
+ return false;
+ }
+
+ FileDescriptor::UniquePlatformHandle handle(
+ HANDLE((uintptr_t)(aPrefMapHandle)));
+ if (!aPrefMapHandle) {
+ return false;
+ }
+
+ mPrefMapHandle.emplace(std::move(handle));
+#endif
+
+ mPrefsLen = Some((uintptr_t)(aPrefsLen));
+ if (!aPrefsLen) {
+ return false;
+ }
+
+ mPrefMapSize = Some((uintptr_t)(aPrefMapSize));
+ if (!aPrefMapSize) {
+ return false;
+ }
+
+#ifdef ANDROID
+ // Android is different; get the FD via gPrefsFd instead of a fixed fd.
+ MOZ_RELEASE_ASSERT(gPrefsFd != -1);
+ prefsHandle = Some(UniqueFileHandle(gPrefsFd));
+
+ mPrefMapHandle.emplace(UniqueFileHandle(gPrefMapFd));
+#elif XP_UNIX
+ prefsHandle = Some(UniqueFileHandle(kPrefsFileDescriptor));
+
+ mPrefMapHandle.emplace(UniqueFileHandle(kPrefMapFileDescriptor));
+#endif
+
+ if (prefsHandle.isNothing() || mPrefsLen.isNothing() ||
+ mPrefMapHandle.isNothing() || mPrefMapSize.isNothing()) {
+ return false;
+ }
+
+ // Init the shared-memory base preference mapping first, so that only changed
+ // preferences wind up in heap memory.
+ Preferences::InitSnapshot(mPrefMapHandle.ref(), *mPrefMapSize);
+
+ // Set up early prefs from the shared memory.
+ if (!mShmem.SetHandle(std::move(*prefsHandle), /* read_only */ true)) {
+ NS_ERROR("failed to open shared memory in the child");
+ return false;
+ }
+ if (!mShmem.Map(*mPrefsLen)) {
+ NS_ERROR("failed to map shared memory in the child");
+ return false;
+ }
+ Preferences::DeserializePreferences(static_cast<char*>(mShmem.memory()),
+ *mPrefsLen);
+
+ return true;
+}
+
+const FileDescriptor& SharedPreferenceDeserializer::GetPrefMapHandle() const {
+ MOZ_ASSERT(mPrefMapHandle.isSome());
+
+ return mPrefMapHandle.ref();
+}
+
+#ifdef XP_UNIX
+// On Unix, file descriptors are per-process. This value is used when mapping
+// a parent process handle to a content process handle.
+static const int kJSInitFileDescriptor = 11;
+#endif
+
+void ExportSharedJSInit(mozilla::ipc::GeckoChildProcessHost& procHost,
+ std::vector<std::string>& aExtraOpts) {
+#ifdef ANDROID
+ // The code to support Android is added in a follow-up patch.
+ return;
+#else
+ auto& shmem = xpc::SelfHostedShmem::GetSingleton();
+ const mozilla::UniqueFileHandle& uniqHandle = shmem.Handle();
+ size_t len = shmem.Content().Length();
+
+ // If the file is not found or the content is empty, then we would start the
+ // content process without this optimization.
+ if (!uniqHandle || !len) {
+ return;
+ }
+
+ mozilla::detail::FileHandleType handle = uniqHandle.get();
+ // command line: [-jsInitHandle handle] -jsInitLen length
+# if defined(XP_WIN)
+ // Record the handle as to-be-shared, and pass it via a command flag.
+ procHost.AddHandleToShare(HANDLE(handle));
+ geckoargs::sJsInitHandle.Put((uintptr_t)(HANDLE(handle)), aExtraOpts);
+# else
+ // In contrast, Unix fds are per-process. So remap the fd to a fixed one that
+ // will be used in the child.
+ // XXX: bug 1440207 is about improving how fixed fds are used.
+ //
+ // Note: on Android, AddFdToRemap() sets up the fd to be passed via a Parcel,
+ // and the fixed fd isn't used. However, we still need to mark it for
+ // remapping so it doesn't get closed in the child.
+ procHost.AddFdToRemap(handle, kJSInitFileDescriptor);
+# endif
+
+ // Pass the lengths via command line flags.
+ geckoargs::sJsInitLen.Put((uintptr_t)(len), aExtraOpts);
+#endif
+}
+
+bool ImportSharedJSInit(uint64_t aJsInitHandle, uint64_t aJsInitLen) {
+ // This is an optimization, and as such we can safely recover if the command
+ // line argument are not provided.
+ if (!aJsInitLen) {
+ return true;
+ }
+
+#ifdef XP_WIN
+ if (!aJsInitHandle) {
+ return true;
+ }
+#endif
+
+#ifdef XP_WIN
+ base::SharedMemoryHandle handle(HANDLE((uintptr_t)(aJsInitHandle)));
+ if (!aJsInitHandle) {
+ return false;
+ }
+#endif
+
+ size_t len = (uintptr_t)(aJsInitLen);
+ if (!aJsInitLen) {
+ return false;
+ }
+
+#ifdef XP_UNIX
+ auto handle = UniqueFileHandle(kJSInitFileDescriptor);
+#endif
+
+ // 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(handle), len)) {
+ NS_ERROR("failed to open shared memory in the child");
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/ProcessUtils_linux.cpp b/ipc/glue/ProcessUtils_linux.cpp
new file mode 100644
index 0000000000..da15745753
--- /dev/null
+++ b/ipc/glue/ProcessUtils_linux.cpp
@@ -0,0 +1,22 @@
+/* -*- 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 "ProcessUtils.h"
+
+#include "nsString.h"
+
+#include <sys/prctl.h>
+
+namespace mozilla {
+
+namespace ipc {
+
+void SetThisProcessName(const char* aName) {
+ prctl(PR_SET_NAME, (unsigned long)aName, 0uL, 0uL, 0uL);
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/ProcessUtils_mac.mm b/ipc/glue/ProcessUtils_mac.mm
new file mode 100644
index 0000000000..0f1d8ecbba
--- /dev/null
+++ b/ipc/glue/ProcessUtils_mac.mm
@@ -0,0 +1,109 @@
+/* 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 "nsObjCExceptions.h"
+#include "nsCocoaUtils.h"
+#include "nsString.h"
+#include "mozilla/Sprintf.h"
+
+#define UNDOCUMENTED_SESSION_CONSTANT ((int)-2)
+
+namespace mozilla {
+namespace ipc {
+
+static void* sApplicationASN = NULL;
+static void* sApplicationInfoItem = NULL;
+
+void SetThisProcessName(const char* aProcessName) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+ nsAutoreleasePool localPool;
+
+ if (!aProcessName || strcmp(aProcessName, "") == 0) {
+ return;
+ }
+
+ NSString* currentName =
+ [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:(NSString*)kCFBundleNameKey];
+
+ char formattedName[1024];
+ SprintfLiteral(formattedName, "%s %s", [currentName UTF8String], aProcessName);
+
+ aProcessName = formattedName;
+
+ // This function is based on Chrome/Webkit's and relies on potentially dangerous SPI.
+ typedef CFTypeRef (*LSGetASNType)();
+ typedef OSStatus (*LSSetInformationItemType)(int, CFTypeRef, CFStringRef, CFStringRef,
+ CFDictionaryRef*);
+
+ CFBundleRef launchServices = ::CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices"));
+ if (!launchServices) {
+ NS_WARNING("Failed to set process name: Could not open LaunchServices bundle");
+ return;
+ }
+
+ if (!sApplicationASN) {
+ sApplicationASN =
+ ::CFBundleGetFunctionPointerForName(launchServices, CFSTR("_LSGetCurrentApplicationASN"));
+ if (!sApplicationASN) {
+ NS_WARNING("Failed to set process name: Could not get function pointer "
+ "for LaunchServices");
+ return;
+ }
+ }
+
+ LSGetASNType getASNFunc = reinterpret_cast<LSGetASNType>(sApplicationASN);
+
+ if (!sApplicationInfoItem) {
+ sApplicationInfoItem = ::CFBundleGetFunctionPointerForName(
+ launchServices, CFSTR("_LSSetApplicationInformationItem"));
+ }
+
+ LSSetInformationItemType setInformationItemFunc =
+ reinterpret_cast<LSSetInformationItemType>(sApplicationInfoItem);
+
+ void* displayNameKeyAddr =
+ ::CFBundleGetDataPointerForName(launchServices, CFSTR("_kLSDisplayNameKey"));
+
+ CFStringRef displayNameKey = nil;
+ if (displayNameKeyAddr) {
+ displayNameKey = reinterpret_cast<CFStringRef>(*(CFStringRef*)displayNameKeyAddr);
+ }
+
+ // We need this to ensure we have a connection to the Process Manager, not
+ // doing so will silently fail and process name wont be updated.
+ ProcessSerialNumber psn;
+ if (::GetCurrentProcess(&psn) != noErr) {
+ return;
+ }
+
+ CFTypeRef currentAsn = getASNFunc ? getASNFunc() : nullptr;
+
+ if (!getASNFunc || !setInformationItemFunc || !displayNameKey || !currentAsn) {
+ NS_WARNING("Failed to set process name: Accessing launchServices failed");
+ return;
+ }
+
+ CFStringRef processName = ::CFStringCreateWithCString(nil, aProcessName, kCFStringEncodingASCII);
+ if (!processName) {
+ NS_WARNING("Failed to set process name: Could not create CFStringRef");
+ return;
+ }
+
+ OSErr err =
+ setInformationItemFunc(UNDOCUMENTED_SESSION_CONSTANT, currentAsn, displayNameKey, processName,
+ nil); // Optional out param
+ ::CFRelease(processName);
+ if (err != noErr) {
+ NS_WARNING("Failed to set process name: LSSetInformationItemType err");
+ return;
+ }
+
+ return;
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/ProcessUtils_none.cpp b/ipc/glue/ProcessUtils_none.cpp
new file mode 100644
index 0000000000..49566a0720
--- /dev/null
+++ b/ipc/glue/ProcessUtils_none.cpp
@@ -0,0 +1,15 @@
+/* -*- 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 "ProcessUtils.h"
+
+namespace mozilla {
+namespace ipc {
+
+void SetThisProcessName(const char* aString) { (void)aString; }
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/ProtocolMessageUtils.h b/ipc/glue/ProtocolMessageUtils.h
new file mode 100644
index 0000000000..48485596ea
--- /dev/null
+++ b/ipc/glue/ProtocolMessageUtils.h
@@ -0,0 +1,121 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef IPC_GLUE_PROTOCOLMESSAGEUTILS_H
+#define IPC_GLUE_PROTOCOLMESSAGEUTILS_H
+
+#include <stdint.h>
+#include <string>
+#include "base/string_util.h"
+#include "chrome/common/ipc_channel.h"
+#include "chrome/common/ipc_message_utils.h"
+#include "ipc/EnumSerializer.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+
+class PickleIterator;
+
+namespace mozilla::ipc {
+class FileDescriptor;
+template <class PFooSide>
+class Endpoint;
+template <class PFooSide>
+class ManagedEndpoint;
+template <typename P>
+struct IPDLParamTraits;
+} // namespace mozilla::ipc
+
+namespace IPC {
+
+class Message;
+class MessageReader;
+class MessageWriter;
+
+template <>
+struct ParamTraits<Channel::Mode>
+ : ContiguousEnumSerializerInclusive<Channel::Mode, Channel::MODE_SERVER,
+ Channel::MODE_CLIENT> {};
+
+template <>
+struct ParamTraits<IPCMessageStart>
+ : ContiguousEnumSerializer<IPCMessageStart, IPCMessageStart(0),
+ LastMsgIndex> {};
+
+template <>
+struct ParamTraits<mozilla::ipc::ActorHandle> {
+ typedef mozilla::ipc::ActorHandle paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ IPC::WriteParam(aWriter, aParam.mId);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ int id;
+ if (IPC::ReadParam(aReader, &id)) {
+ aResult->mId = id;
+ return true;
+ }
+ return false;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::ipc::UntypedEndpoint> {
+ using paramType = mozilla::ipc::UntypedEndpoint;
+
+ static void Write(MessageWriter* aWriter, paramType&& aParam);
+
+ static bool Read(MessageReader* aReader, paramType* aResult);
+};
+
+template <class PFooSide>
+struct ParamTraits<mozilla::ipc::Endpoint<PFooSide>>
+ : ParamTraits<mozilla::ipc::UntypedEndpoint> {};
+
+} // namespace IPC
+
+namespace mozilla::ipc {
+
+template <>
+struct IPDLParamTraits<UntypedManagedEndpoint> {
+ using paramType = UntypedManagedEndpoint;
+
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ paramType&& aParam);
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult);
+};
+
+template <class PFooSide>
+struct IPDLParamTraits<ManagedEndpoint<PFooSide>> {
+ using paramType = ManagedEndpoint<PFooSide>;
+
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ paramType&& aParam) {
+ IPDLParamTraits<UntypedManagedEndpoint>::Write(aWriter, aActor,
+ std::move(aParam));
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ return IPDLParamTraits<UntypedManagedEndpoint>::Read(aReader, aActor,
+ aResult);
+ }
+};
+
+template <>
+struct IPDLParamTraits<FileDescriptor> {
+ typedef FileDescriptor paramType;
+
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam);
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult);
+};
+} // namespace mozilla::ipc
+
+#endif // IPC_GLUE_PROTOCOLMESSAGEUTILS_H
diff --git a/ipc/glue/ProtocolTypes.ipdlh b/ipc/glue/ProtocolTypes.ipdlh
new file mode 100644
index 0000000000..b1d5316731
--- /dev/null
+++ b/ipc/glue/ProtocolTypes.ipdlh
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
+/* 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/. */
+
+using struct nsID
+ from "nsID.h";
+
+namespace mozilla {
+namespace ipc {
+
+struct ProtocolFdMapping
+{
+ uint32_t protocolId;
+ FileDescriptor fd;
+};
+
+}
+}
+
diff --git a/ipc/glue/ProtocolUtils.cpp b/ipc/glue/ProtocolUtils.cpp
new file mode 100644
index 0000000000..51fb4d7e56
--- /dev/null
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -0,0 +1,835 @@
+/* -*- 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 "base/process_util.h"
+#include "base/task.h"
+
+#ifdef OS_POSIX
+# include <errno.h>
+#endif
+#include <type_traits>
+
+#include "mozilla/IntegerPrintfMacros.h"
+
+#include "mozilla/ipc/ProtocolMessageUtils.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+
+#include "mozilla/ipc/MessageChannel.h"
+#include "mozilla/ipc/IPDLParamTraits.h"
+#include "mozilla/StaticMutex.h"
+#if defined(DEBUG) || defined(FUZZING)
+# include "mozilla/Tokenizer.h"
+#endif
+#include "mozilla/Unused.h"
+#include "nsPrintfCString.h"
+
+#if defined(MOZ_SANDBOX) && defined(XP_WIN)
+# include "mozilla/sandboxTarget.h"
+#endif
+
+#if defined(XP_WIN)
+# include "aclapi.h"
+# include "sddl.h"
+#endif
+
+#ifdef FUZZING_SNAPSHOT
+# include "mozilla/fuzzing/IPCFuzzController.h"
+#endif
+
+using namespace IPC;
+
+using base::GetCurrentProcId;
+using base::ProcessHandle;
+using base::ProcessId;
+
+namespace mozilla {
+
+#if defined(XP_WIN)
+// Generate RAII classes for LPTSTR and PSECURITY_DESCRIPTOR.
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedLPTStr,
+ std::remove_pointer_t<LPTSTR>,
+ ::LocalFree)
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(
+ ScopedPSecurityDescriptor, std::remove_pointer_t<PSECURITY_DESCRIPTOR>,
+ ::LocalFree)
+#endif
+
+namespace ipc {
+
+IPCResult IPCResult::Fail(NotNull<IProtocol*> actor, const char* where,
+ const char* why) {
+ // Calls top-level protocol to handle the error.
+ nsPrintfCString errorMsg("%s %s\n", where, why);
+ actor->GetIPCChannel()->Listener()->ProcessingError(
+ HasResultCodes::MsgProcessingError, errorMsg.get());
+
+ MOZ_ASSERT_UNLESS_FUZZING(false,
+ "Please ensure to IPC_FAIL only when in an "
+ "unrecoverable, unexpected state.");
+
+ return IPCResult(false);
+}
+
+void AnnotateSystemError() {
+ int64_t error = 0;
+#if defined(XP_WIN)
+ error = ::GetLastError();
+#elif defined(OS_POSIX)
+ error = errno;
+#endif
+ if (error) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCSystemError,
+ nsPrintfCString("%" PRId64, error));
+ }
+}
+
+#if defined(XP_MACOSX)
+void AnnotateCrashReportWithErrno(CrashReporter::Annotation tag, int error) {
+ CrashReporter::AnnotateCrashReport(tag, error);
+}
+#endif // defined(XP_MACOSX)
+
+#if defined(DEBUG) || defined(FUZZING)
+// This overload is for testability; application code should use the single-
+// argument version (defined in the ProtocolUtils.h) which takes the filter from
+// the environment.
+bool LoggingEnabledFor(const char* aTopLevelProtocol, const char* aFilter) {
+ if (!aFilter) {
+ return false;
+ }
+ if (strcmp(aFilter, "1") == 0) {
+ return true;
+ }
+
+ const char kDelimiters[] = ", ";
+ Tokenizer tokens(aFilter, kDelimiters);
+ Tokenizer::Token t;
+ while (tokens.Next(t)) {
+ if (t.Type() == Tokenizer::TOKEN_WORD &&
+ t.AsString() == aTopLevelProtocol) {
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif // defined(DEBUG) || defined(FUZZING)
+
+void LogMessageForProtocol(const char* aTopLevelProtocol,
+ base::ProcessId aOtherPid,
+ const char* aContextDescription, uint32_t aMessageId,
+ MessageDirection aDirection) {
+ nsPrintfCString logMessage(
+ "[time: %" PRId64 "][%" PRIPID "%s%" PRIPID "] [%s] %s %s\n", PR_Now(),
+ base::GetCurrentProcId(),
+ aDirection == MessageDirection::eReceiving ? "<-" : "->", aOtherPid,
+ aTopLevelProtocol, aContextDescription,
+ StringFromIPCMessageType(aMessageId));
+#ifdef ANDROID
+ __android_log_write(ANDROID_LOG_INFO, "GeckoIPC", logMessage.get());
+#endif
+ fputs(logMessage.get(), stderr);
+}
+
+void ProtocolErrorBreakpoint(const char* aMsg) {
+ // Bugs that generate these error messages can be tough to
+ // reproduce. Log always in the hope that someone finds the error
+ // message.
+ printf_stderr("IPDL protocol error: %s\n", aMsg);
+}
+
+void PickleFatalError(const char* aMsg, IProtocol* aActor) {
+ if (aActor) {
+ aActor->FatalError(aMsg);
+ } else {
+ FatalError(aMsg, false);
+ }
+}
+
+void FatalError(const char* aMsg, bool aIsParent) {
+#ifndef FUZZING
+ ProtocolErrorBreakpoint(aMsg);
+#endif
+
+ nsAutoCString formattedMessage("IPDL error: \"");
+ formattedMessage.AppendASCII(aMsg);
+ if (aIsParent) {
+ // We're going to crash the parent process because at this time
+ // there's no other really nice way of getting a minidump out of
+ // this process if we're off the main thread.
+ formattedMessage.AppendLiteral("\". Intentionally crashing.");
+ NS_ERROR(formattedMessage.get());
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCFatalErrorMsg, nsDependentCString(aMsg));
+ AnnotateSystemError();
+#ifndef FUZZING
+ MOZ_CRASH("IPC FatalError in the parent process!");
+#endif
+ } else {
+ formattedMessage.AppendLiteral("\". abort()ing as a result.");
+#ifndef FUZZING
+ MOZ_CRASH_UNSAFE(formattedMessage.get());
+#endif
+ }
+}
+
+void LogicError(const char* aMsg) { MOZ_CRASH_UNSAFE(aMsg); }
+
+void ActorIdReadError(const char* aActorDescription) {
+#ifndef FUZZING
+ MOZ_CRASH_UNSAFE_PRINTF("Error deserializing id for %s", aActorDescription);
+#endif
+}
+
+void BadActorIdError(const char* aActorDescription) {
+ nsPrintfCString message("bad id for %s", aActorDescription);
+ ProtocolErrorBreakpoint(message.get());
+}
+
+void ActorLookupError(const char* aActorDescription) {
+ nsPrintfCString message("could not lookup id for %s", aActorDescription);
+ ProtocolErrorBreakpoint(message.get());
+}
+
+void MismatchedActorTypeError(const char* aActorDescription) {
+ nsPrintfCString message("actor that should be of type %s has different type",
+ aActorDescription);
+ ProtocolErrorBreakpoint(message.get());
+}
+
+void UnionTypeReadError(const char* aUnionName) {
+ MOZ_CRASH_UNSAFE_PRINTF("error deserializing type of union %s", aUnionName);
+}
+
+void ArrayLengthReadError(const char* aElementName) {
+ MOZ_CRASH_UNSAFE_PRINTF("error deserializing length of %s[]", aElementName);
+}
+
+void SentinelReadError(const char* aClassName) {
+ MOZ_CRASH_UNSAFE_PRINTF("incorrect sentinel when reading %s", aClassName);
+}
+
+ActorLifecycleProxy::ActorLifecycleProxy(IProtocol* aActor) : mActor(aActor) {
+ MOZ_ASSERT(mActor);
+ MOZ_ASSERT(mActor->CanSend(),
+ "Cannot create LifecycleProxy for non-connected actor!");
+
+ // Take a reference to our manager's lifecycle proxy to try to hold it &
+ // ensure it doesn't die before us.
+ if (mActor->mManager) {
+ mManager = mActor->mManager->mLifecycleProxy;
+ }
+
+ // Record that we've taken our first reference to our actor.
+ mActor->ActorAlloc();
+}
+
+WeakActorLifecycleProxy* ActorLifecycleProxy::GetWeakProxy() {
+ if (!mWeakProxy) {
+ mWeakProxy = new WeakActorLifecycleProxy(this);
+ }
+ return mWeakProxy;
+}
+
+ActorLifecycleProxy::~ActorLifecycleProxy() {
+ if (mWeakProxy) {
+ mWeakProxy->mProxy = nullptr;
+ mWeakProxy = nullptr;
+ }
+
+ // When the LifecycleProxy's lifetime has come to an end, it means that the
+ // actor should have its `Dealloc` method called on it. In a well-behaved
+ // actor, this will release the IPC-held reference to the actor.
+ //
+ // If the actor has already died before the `LifecycleProxy`, the `IProtocol`
+ // destructor below will clear our reference to it, preventing us from
+ // performing a use-after-free here.
+ if (!mActor) {
+ return;
+ }
+
+ // Clear our actor's state back to inactive, and then invoke ActorDealloc.
+ MOZ_ASSERT(mActor->mLinkStatus == LinkStatus::Destroyed,
+ "Deallocating non-destroyed actor!");
+ mActor->mLifecycleProxy = nullptr;
+ mActor->mLinkStatus = LinkStatus::Inactive;
+ mActor->ActorDealloc();
+ mActor = nullptr;
+}
+
+WeakActorLifecycleProxy::WeakActorLifecycleProxy(ActorLifecycleProxy* aProxy)
+ : mProxy(aProxy), mActorEventTarget(GetCurrentSerialEventTarget()) {}
+
+WeakActorLifecycleProxy::~WeakActorLifecycleProxy() {
+ MOZ_DIAGNOSTIC_ASSERT(!mProxy, "Destroyed before mProxy was cleared?");
+}
+
+IProtocol* WeakActorLifecycleProxy::Get() const {
+ MOZ_DIAGNOSTIC_ASSERT(mActorEventTarget->IsOnCurrentThread());
+ return mProxy ? mProxy->Get() : nullptr;
+}
+
+WeakActorLifecycleProxy* IProtocol::GetWeakLifecycleProxy() {
+ return mLifecycleProxy ? mLifecycleProxy->GetWeakProxy() : nullptr;
+}
+
+IProtocol::~IProtocol() {
+ // If the actor still has a lifecycle proxy when it is being torn down, it
+ // means that IPC was not given control over the lifecycle of the actor
+ // correctly. Usually this means that the actor was destroyed while IPC is
+ // calling a message handler for it, and the actor incorrectly frees itself
+ // during that operation.
+ //
+ // As this happens unfortunately frequently, due to many odd protocols in
+ // Gecko, simply emit a warning and clear the weak backreference from our
+ // LifecycleProxy back to us.
+ if (mLifecycleProxy) {
+ NS_WARNING(
+ nsPrintfCString("Actor destructor for '%s%s' called before IPC "
+ "lifecycle complete!\n"
+ "References to this actor may unexpectedly dangle!",
+ GetProtocolName(),
+ GetSide() == ChildSide ? "Child" : "Parent")
+ .get());
+
+ mLifecycleProxy->mActor = nullptr;
+
+ // If we are somehow being destroyed while active, make sure that the
+ // existing IPC reference has been freed. If the status of the actor is
+ // `Destroyed`, the reference has already been freed, and we shouldn't free
+ // it a second time.
+ MOZ_ASSERT(mLinkStatus != LinkStatus::Inactive);
+ if (mLinkStatus != LinkStatus::Destroyed) {
+ NS_IF_RELEASE(mLifecycleProxy);
+ }
+ mLifecycleProxy = nullptr;
+ }
+}
+
+// The following methods either directly forward to the toplevel protocol, or
+// almost directly do.
+int32_t IProtocol::Register(IProtocol* aRouted) {
+ return mToplevel->Register(aRouted);
+}
+int32_t IProtocol::RegisterID(IProtocol* aRouted, int32_t aId) {
+ return mToplevel->RegisterID(aRouted, aId);
+}
+IProtocol* IProtocol::Lookup(int32_t aId) { return mToplevel->Lookup(aId); }
+void IProtocol::Unregister(int32_t aId) {
+ if (aId == mId) {
+ mId = kFreedActorId;
+ }
+ return mToplevel->Unregister(aId);
+}
+
+Shmem::SharedMemory* IProtocol::CreateSharedMemory(size_t aSize, bool aUnsafe,
+ int32_t* aId) {
+ return mToplevel->CreateSharedMemory(aSize, aUnsafe, aId);
+}
+Shmem::SharedMemory* IProtocol::LookupSharedMemory(int32_t aId) {
+ return mToplevel->LookupSharedMemory(aId);
+}
+bool IProtocol::IsTrackingSharedMemory(Shmem::SharedMemory* aSegment) {
+ return mToplevel->IsTrackingSharedMemory(aSegment);
+}
+bool IProtocol::DestroySharedMemory(Shmem& aShmem) {
+ return mToplevel->DestroySharedMemory(aShmem);
+}
+
+MessageChannel* IProtocol::GetIPCChannel() {
+ return mToplevel->GetIPCChannel();
+}
+const MessageChannel* IProtocol::GetIPCChannel() const {
+ return mToplevel->GetIPCChannel();
+}
+
+nsISerialEventTarget* IProtocol::GetActorEventTarget() {
+ return GetIPCChannel()->GetWorkerEventTarget();
+}
+
+void IProtocol::SetId(int32_t aId) {
+ MOZ_ASSERT(mId == aId || mLinkStatus == LinkStatus::Inactive);
+ mId = aId;
+}
+
+Maybe<IProtocol*> IProtocol::ReadActor(IPC::MessageReader* aReader,
+ bool aNullable,
+ const char* aActorDescription,
+ int32_t aProtocolTypeId) {
+ int32_t id;
+ if (!IPC::ReadParam(aReader, &id)) {
+ ActorIdReadError(aActorDescription);
+ return Nothing();
+ }
+
+ if (id == 1 || (id == 0 && !aNullable)) {
+ BadActorIdError(aActorDescription);
+ return Nothing();
+ }
+
+ if (id == 0) {
+ return Some(static_cast<IProtocol*>(nullptr));
+ }
+
+ IProtocol* listener = this->Lookup(id);
+ if (!listener) {
+ ActorLookupError(aActorDescription);
+ return Nothing();
+ }
+
+ if (listener->GetProtocolId() != aProtocolTypeId) {
+ MismatchedActorTypeError(aActorDescription);
+ return Nothing();
+ }
+
+ return Some(listener);
+}
+
+void IProtocol::FatalError(const char* const aErrorMsg) {
+ HandleFatalError(aErrorMsg);
+}
+
+void IProtocol::HandleFatalError(const char* aErrorMsg) {
+ if (IProtocol* manager = Manager()) {
+ manager->HandleFatalError(aErrorMsg);
+ return;
+ }
+
+ mozilla::ipc::FatalError(aErrorMsg, mSide == ParentSide);
+ if (CanSend()) {
+ GetIPCChannel()->CloseWithError();
+ }
+}
+
+bool IProtocol::AllocShmem(size_t aSize, Shmem* aOutMem) {
+ if (!CanSend()) {
+ NS_WARNING(
+ "Shmem not allocated. Cannot communicate with the other actor.");
+ return false;
+ }
+
+ Shmem::id_t id;
+ Shmem::SharedMemory* rawmem(CreateSharedMemory(aSize, false, &id));
+ if (!rawmem) {
+ return false;
+ }
+
+ *aOutMem = Shmem(rawmem, id);
+ return true;
+}
+
+bool IProtocol::AllocUnsafeShmem(size_t aSize, Shmem* aOutMem) {
+ if (!CanSend()) {
+ NS_WARNING(
+ "Shmem not allocated. Cannot communicate with the other actor.");
+ return false;
+ }
+
+ Shmem::id_t id;
+ Shmem::SharedMemory* rawmem(CreateSharedMemory(aSize, true, &id));
+ if (!rawmem) {
+ return false;
+ }
+
+ *aOutMem = Shmem(rawmem, id);
+ return true;
+}
+
+bool IProtocol::DeallocShmem(Shmem& aMem) {
+ bool ok = DestroySharedMemory(aMem);
+#ifdef DEBUG
+ if (!ok) {
+ if (mSide == ChildSide) {
+ FatalError("bad Shmem");
+ } else {
+ NS_WARNING("bad Shmem");
+ }
+ return false;
+ }
+#endif // DEBUG
+ aMem.forget();
+ return ok;
+}
+
+void IProtocol::SetManager(IProtocol* aManager) {
+ MOZ_RELEASE_ASSERT(!mManager || mManager == aManager);
+ mManager = aManager;
+ mToplevel = aManager->mToplevel;
+}
+
+void IProtocol::SetManagerAndRegister(IProtocol* aManager) {
+ // Set the manager prior to registering so registering properly inherits
+ // the manager's event target.
+ SetManager(aManager);
+
+ aManager->Register(this);
+}
+
+void IProtocol::SetManagerAndRegister(IProtocol* aManager, int32_t aId) {
+ // Set the manager prior to registering so registering properly inherits
+ // the manager's event target.
+ SetManager(aManager);
+
+ aManager->RegisterID(this, aId);
+}
+
+bool IProtocol::ChannelSend(UniquePtr<IPC::Message> aMsg) {
+ if (CanSend()) {
+ // NOTE: This send call failing can only occur during toplevel channel
+ // teardown. As this is an async call, this isn't reasonable to predict or
+ // respond to, so just drop the message on the floor silently.
+ GetIPCChannel()->Send(std::move(aMsg));
+ return true;
+ }
+
+ WarnMessageDiscarded(aMsg.get());
+ return false;
+}
+
+bool IProtocol::ChannelSend(UniquePtr<IPC::Message> aMsg,
+ UniquePtr<IPC::Message>* aReply) {
+ if (CanSend()) {
+ return GetIPCChannel()->Send(std::move(aMsg), aReply);
+ }
+
+ WarnMessageDiscarded(aMsg.get());
+ return false;
+}
+
+#ifdef DEBUG
+void IProtocol::WarnMessageDiscarded(IPC::Message* aMsg) {
+ NS_WARNING(nsPrintfCString("IPC message '%s' discarded: actor cannot send",
+ aMsg->name())
+ .get());
+}
+#endif
+
+void IProtocol::ActorConnected() {
+ if (mLinkStatus != LinkStatus::Inactive) {
+ return;
+ }
+
+#ifdef FUZZING_SNAPSHOT
+ fuzzing::IPCFuzzController::instance().OnActorConnected(this);
+#endif
+
+ mLinkStatus = LinkStatus::Connected;
+
+ MOZ_ASSERT(!mLifecycleProxy, "double-connecting live actor");
+ mLifecycleProxy = new ActorLifecycleProxy(this);
+ NS_ADDREF(mLifecycleProxy); // Reference freed in DestroySubtree();
+}
+
+void IProtocol::DoomSubtree() {
+ MOZ_ASSERT(CanSend(), "dooming non-connected actor");
+ MOZ_ASSERT(mLifecycleProxy, "dooming zombie actor");
+
+ nsTArray<RefPtr<ActorLifecycleProxy>> managed;
+ AllManagedActors(managed);
+ for (ActorLifecycleProxy* proxy : managed) {
+ // Guard against actor being disconnected or destroyed during previous Doom
+ IProtocol* actor = proxy->Get();
+ if (actor && actor->CanSend()) {
+ actor->DoomSubtree();
+ }
+ }
+
+ // ActorDoom is called immediately before changing state, this allows messages
+ // to be sent during ActorDoom immediately before the channel is closed and
+ // sending messages is disabled.
+ ActorDoom();
+ mLinkStatus = LinkStatus::Doomed;
+}
+
+void IProtocol::DestroySubtree(ActorDestroyReason aWhy) {
+ MOZ_ASSERT(CanRecv(), "destroying non-connected actor");
+ MOZ_ASSERT(mLifecycleProxy, "destroying zombie actor");
+
+#ifdef FUZZING_SNAPSHOT
+ fuzzing::IPCFuzzController::instance().OnActorDestroyed(this);
+#endif
+
+ int32_t id = Id();
+
+ // If we're a managed actor, unregister from our manager
+ if (Manager()) {
+ Unregister(id);
+ }
+
+ // Destroy subtree
+ ActorDestroyReason subtreeWhy = aWhy;
+ if (aWhy == Deletion || aWhy == FailedConstructor) {
+ subtreeWhy = AncestorDeletion;
+ }
+
+ nsTArray<RefPtr<ActorLifecycleProxy>> managed;
+ AllManagedActors(managed);
+ for (ActorLifecycleProxy* proxy : managed) {
+ // Guard against actor being disconnected or destroyed during previous
+ // Destroy
+ IProtocol* actor = proxy->Get();
+ if (actor && actor->CanRecv()) {
+ actor->DestroySubtree(subtreeWhy);
+ }
+ }
+
+ // Ensure that we don't send any messages while we're calling `ActorDestroy`
+ // by setting our state to `Doomed`.
+ mLinkStatus = LinkStatus::Doomed;
+
+ // The actor is being destroyed, reject any pending responses, invoke
+ // `ActorDestroy` to destroy it, and then clear our status to
+ // `LinkStatus::Destroyed`.
+ GetIPCChannel()->RejectPendingResponsesForActor(id);
+ ActorDestroy(aWhy);
+ mLinkStatus = LinkStatus::Destroyed;
+}
+
+IToplevelProtocol::IToplevelProtocol(const char* aName, ProtocolId aProtoId,
+ Side aSide)
+ : IProtocol(aProtoId, aSide),
+ mOtherPid(base::kInvalidProcessId),
+ mLastLocalId(0),
+ mChannel(aName, this) {
+ mToplevel = this;
+}
+
+void IToplevelProtocol::SetOtherProcessId(base::ProcessId aOtherPid) {
+ mOtherPid = aOtherPid;
+}
+
+bool IToplevelProtocol::Open(ScopedPort aPort, const nsID& aMessageChannelId,
+ base::ProcessId aOtherPid,
+ nsISerialEventTarget* aEventTarget) {
+ SetOtherProcessId(aOtherPid);
+ return GetIPCChannel()->Open(std::move(aPort), mSide, aMessageChannelId,
+ aEventTarget);
+}
+
+bool IToplevelProtocol::Open(IToplevelProtocol* aTarget,
+ nsISerialEventTarget* aEventTarget,
+ mozilla::ipc::Side aSide) {
+ SetOtherProcessId(base::GetCurrentProcId());
+ aTarget->SetOtherProcessId(base::GetCurrentProcId());
+ return GetIPCChannel()->Open(aTarget->GetIPCChannel(), aEventTarget, aSide);
+}
+
+bool IToplevelProtocol::OpenOnSameThread(IToplevelProtocol* aTarget,
+ Side aSide) {
+ SetOtherProcessId(base::GetCurrentProcId());
+ aTarget->SetOtherProcessId(base::GetCurrentProcId());
+ return GetIPCChannel()->OpenOnSameThread(aTarget->GetIPCChannel(), aSide);
+}
+
+void IToplevelProtocol::NotifyImpendingShutdown() {
+ if (CanRecv()) {
+ GetIPCChannel()->NotifyImpendingShutdown();
+ }
+}
+
+void IToplevelProtocol::Close() { GetIPCChannel()->Close(); }
+
+void IToplevelProtocol::CloseWithError() { GetIPCChannel()->CloseWithError(); }
+
+void IToplevelProtocol::SetReplyTimeoutMs(int32_t aTimeoutMs) {
+ GetIPCChannel()->SetReplyTimeoutMs(aTimeoutMs);
+}
+
+bool IToplevelProtocol::IsOnCxxStack() const {
+ return GetIPCChannel()->IsOnCxxStack();
+}
+
+int32_t IToplevelProtocol::NextId() {
+ // Generate the next ID to use for a shared memory or protocol. Parent and
+ // Child sides of the protocol use different pools.
+ int32_t tag = 0;
+ if (GetSide() == ParentSide) {
+ tag |= 1 << 1;
+ }
+
+ // Check any overflow
+ MOZ_RELEASE_ASSERT(mLastLocalId < (1 << 29));
+
+ // Compute the ID to use with the low two bits as our tag, and the remaining
+ // bits as a monotonic.
+ return (++mLastLocalId << 2) | tag;
+}
+
+int32_t IToplevelProtocol::Register(IProtocol* aRouted) {
+ if (aRouted->Id() != kNullActorId && aRouted->Id() != kFreedActorId) {
+ // If there's already an ID, just return that.
+ return aRouted->Id();
+ }
+ return RegisterID(aRouted, NextId());
+}
+
+int32_t IToplevelProtocol::RegisterID(IProtocol* aRouted, int32_t aId) {
+ aRouted->SetId(aId);
+ aRouted->ActorConnected();
+ MOZ_ASSERT(!mActorMap.Contains(aId), "Don't insert with an existing ID");
+ mActorMap.InsertOrUpdate(aId, aRouted);
+ return aId;
+}
+
+IProtocol* IToplevelProtocol::Lookup(int32_t aId) { return mActorMap.Get(aId); }
+
+void IToplevelProtocol::Unregister(int32_t aId) {
+ MOZ_ASSERT(mActorMap.Contains(aId),
+ "Attempting to remove an ID not in the actor map");
+ mActorMap.Remove(aId);
+}
+
+Shmem::SharedMemory* IToplevelProtocol::CreateSharedMemory(size_t aSize,
+ bool aUnsafe,
+ Shmem::id_t* aId) {
+ RefPtr<Shmem::SharedMemory> segment(Shmem::Alloc(aSize, aUnsafe));
+ if (!segment) {
+ return nullptr;
+ }
+ int32_t id = NextId();
+ Shmem shmem(segment.get(), id);
+
+ UniquePtr<Message> descriptor = shmem.MkCreatedMessage(MSG_ROUTING_CONTROL);
+ if (!descriptor) {
+ return nullptr;
+ }
+ Unused << GetIPCChannel()->Send(std::move(descriptor));
+
+ *aId = shmem.Id();
+ Shmem::SharedMemory* rawSegment = segment.get();
+ MOZ_ASSERT(!mShmemMap.Contains(*aId), "Don't insert with an existing ID");
+ mShmemMap.InsertOrUpdate(*aId, segment.forget().take());
+ return rawSegment;
+}
+
+Shmem::SharedMemory* IToplevelProtocol::LookupSharedMemory(Shmem::id_t aId) {
+ return mShmemMap.Get(aId);
+}
+
+bool IToplevelProtocol::IsTrackingSharedMemory(Shmem::SharedMemory* segment) {
+ for (const auto& shmem : mShmemMap.Values()) {
+ if (segment == shmem) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool IToplevelProtocol::DestroySharedMemory(Shmem& shmem) {
+ Shmem::id_t aId = shmem.Id();
+ Shmem::SharedMemory* segment = LookupSharedMemory(aId);
+ if (!segment) {
+ return false;
+ }
+
+ UniquePtr<Message> descriptor = shmem.MkDestroyedMessage(MSG_ROUTING_CONTROL);
+
+ MOZ_ASSERT(mShmemMap.Contains(aId),
+ "Attempting to remove an ID not in the shmem map");
+ mShmemMap.Remove(aId);
+ Shmem::Dealloc(segment);
+
+ MessageChannel* channel = GetIPCChannel();
+ if (!channel->CanSend()) {
+ return true;
+ }
+
+ return descriptor && channel->Send(std::move(descriptor));
+}
+
+void IToplevelProtocol::DeallocShmems() {
+ for (const auto& shmem : mShmemMap.Values()) {
+ Shmem::Dealloc(shmem);
+ }
+ mShmemMap.Clear();
+}
+
+bool IToplevelProtocol::ShmemCreated(const Message& aMsg) {
+ Shmem::id_t id;
+ RefPtr<Shmem::SharedMemory> rawmem(Shmem::OpenExisting(aMsg, &id, true));
+ if (!rawmem) {
+ return false;
+ }
+ MOZ_ASSERT(!mShmemMap.Contains(id), "Don't insert with an existing ID");
+ mShmemMap.InsertOrUpdate(id, rawmem.forget().take());
+ return true;
+}
+
+bool IToplevelProtocol::ShmemDestroyed(const Message& aMsg) {
+ Shmem::id_t id;
+ MessageReader reader(aMsg);
+ if (!IPC::ReadParam(&reader, &id)) {
+ return false;
+ }
+ reader.EndRead();
+
+ Shmem::SharedMemory* rawmem = LookupSharedMemory(id);
+ if (rawmem) {
+ MOZ_ASSERT(mShmemMap.Contains(id),
+ "Attempting to remove an ID not in the shmem map");
+ mShmemMap.Remove(id);
+ Shmem::Dealloc(rawmem);
+ }
+ return true;
+}
+
+IPDLResolverInner::IPDLResolverInner(UniquePtr<IPC::Message> aReply,
+ IProtocol* aActor)
+ : mReply(std::move(aReply)),
+ mWeakProxy(aActor->GetLifecycleProxy()->GetWeakProxy()) {}
+
+void IPDLResolverInner::ResolveOrReject(
+ bool aResolve, FunctionRef<void(IPC::Message*, IProtocol*)> aWrite) {
+ MOZ_ASSERT(mWeakProxy);
+ MOZ_ASSERT(mWeakProxy->ActorEventTarget()->IsOnCurrentThread());
+ MOZ_ASSERT(mReply);
+
+ UniquePtr<IPC::Message> reply = std::move(mReply);
+
+ IProtocol* actor = mWeakProxy->Get();
+ if (!actor) {
+ NS_WARNING(nsPrintfCString("Not resolving response '%s': actor is dead",
+ reply->name())
+ .get());
+ return;
+ }
+
+ IPC::MessageWriter writer(*reply, actor);
+ WriteIPDLParam(&writer, actor, aResolve);
+ aWrite(reply.get(), actor);
+
+ actor->ChannelSend(std::move(reply));
+}
+
+void IPDLResolverInner::Destroy() {
+ if (mReply) {
+ NS_PROXY_DELETE_TO_EVENT_TARGET(IPDLResolverInner,
+ mWeakProxy->ActorEventTarget());
+ } else {
+ // If we've already been consumed, just delete without proxying. This avoids
+ // leaking the resolver if the actor's thread is already dead.
+ delete this;
+ }
+}
+
+IPDLResolverInner::~IPDLResolverInner() {
+ if (mReply) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Rejecting reply '%s': resolver dropped without being called",
+ mReply->name())
+ .get());
+ ResolveOrReject(false, [](IPC::Message* aMessage, IProtocol* aActor) {
+ IPC::MessageWriter writer(*aMessage, aActor);
+ ResponseRejectReason reason = ResponseRejectReason::ResolverDestroyed;
+ WriteIPDLParam(&writer, aActor, reason);
+ });
+ }
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/ProtocolUtils.h b/ipc/glue/ProtocolUtils.h
new file mode 100644
index 0000000000..aa398453ae
--- /dev/null
+++ b/ipc/glue/ProtocolUtils.h
@@ -0,0 +1,746 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ipc_ProtocolUtils_h
+#define mozilla_ipc_ProtocolUtils_h 1
+
+#include <cstddef>
+#include <cstdint>
+#include <utility>
+#include "IPCMessageStart.h"
+#include "base/basictypes.h"
+#include "base/process.h"
+#include "chrome/common/ipc_message.h"
+#include "mojo/core/ports/port_ref.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/FunctionRef.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ipc/MessageChannel.h"
+#include "mozilla/ipc/MessageLink.h"
+#include "mozilla/ipc/SharedMemory.h"
+#include "mozilla/ipc/Shmem.h"
+#include "nsTHashMap.h"
+#include "nsDebug.h"
+#include "nsISupports.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsTHashSet.h"
+
+// XXX Things that could be moved to ProtocolUtils.cpp
+#include "base/process_util.h" // for CloseProcessHandle
+#include "prenv.h" // for PR_GetEnv
+
+#if defined(ANDROID) && defined(DEBUG)
+# include <android/log.h>
+#endif
+
+template <typename T>
+class nsPtrHashKey;
+
+// WARNING: this takes into account the private, special-message-type
+// enum in ipc_channel.h. They need to be kept in sync.
+namespace {
+// XXX the max message ID is actually kuint32max now ... when this
+// changed, the assumptions of the special message IDs changed in that
+// they're not carving out messages from likely-unallocated space, but
+// rather carving out messages from the end of space allocated to
+// protocol 0. Oops! We can get away with this until protocol 0
+// starts approaching its 65,536th message.
+enum {
+ // Message types used by DataPipe
+ DATA_PIPE_CLOSED_MESSAGE_TYPE = kuint16max - 18,
+ DATA_PIPE_BYTES_CONSUMED_MESSAGE_TYPE = kuint16max - 17,
+
+ // Message types used by NodeChannel
+ ACCEPT_INVITE_MESSAGE_TYPE = kuint16max - 16,
+ REQUEST_INTRODUCTION_MESSAGE_TYPE = kuint16max - 15,
+ INTRODUCE_MESSAGE_TYPE = kuint16max - 14,
+ BROADCAST_MESSAGE_TYPE = kuint16max - 13,
+ EVENT_MESSAGE_TYPE = kuint16max - 12,
+
+ // Message types used by MessageChannel
+ MANAGED_ENDPOINT_DROPPED_MESSAGE_TYPE = kuint16max - 11,
+ MANAGED_ENDPOINT_BOUND_MESSAGE_TYPE = kuint16max - 10,
+ IMPENDING_SHUTDOWN_MESSAGE_TYPE = kuint16max - 9,
+ BUILD_IDS_MATCH_MESSAGE_TYPE = kuint16max - 8,
+ BUILD_ID_MESSAGE_TYPE = kuint16max - 7, // unused
+ CHANNEL_OPENED_MESSAGE_TYPE = kuint16max - 6,
+ SHMEM_DESTROYED_MESSAGE_TYPE = kuint16max - 5,
+ SHMEM_CREATED_MESSAGE_TYPE = kuint16max - 4,
+ GOODBYE_MESSAGE_TYPE = kuint16max - 3,
+ CANCEL_MESSAGE_TYPE = kuint16max - 2,
+
+ // kuint16max - 1 is used by ipc_channel.h.
+};
+
+} // namespace
+
+class MessageLoop;
+class PickleIterator;
+class nsISerialEventTarget;
+
+namespace mozilla {
+class SchedulerGroup;
+
+namespace dom {
+class ContentParent;
+} // namespace dom
+
+namespace net {
+class NeckoParent;
+} // namespace net
+
+namespace ipc {
+
+// Scoped base::ProcessHandle to ensure base::CloseProcessHandle is called.
+struct ScopedProcessHandleTraits {
+ typedef base::ProcessHandle type;
+
+ static type empty() { return base::kInvalidProcessHandle; }
+
+ static void release(type aProcessHandle) {
+ if (aProcessHandle && aProcessHandle != base::kInvalidProcessHandle) {
+ base::CloseProcessHandle(aProcessHandle);
+ }
+ }
+};
+typedef mozilla::Scoped<ScopedProcessHandleTraits> ScopedProcessHandle;
+
+class ProtocolFdMapping;
+class ProtocolCloneContext;
+
+// Used to pass references to protocol actors across the wire.
+// Actors created on the parent-side have a positive ID, and actors
+// allocated on the child side have a negative ID.
+struct ActorHandle {
+ int mId;
+};
+
+// What happens if Interrupt calls race?
+enum RacyInterruptPolicy { RIPError, RIPChildWins, RIPParentWins };
+
+enum class LinkStatus : uint8_t {
+ // The actor has not established a link yet, or the actor is no longer in use
+ // by IPC, and its 'Dealloc' method has been called or is being called.
+ //
+ // NOTE: This state is used instead of an explicit `Freed` state when IPC no
+ // longer holds references to the current actor as we currently re-open
+ // existing actors. Once we fix these poorly behaved actors, this loopback
+ // state can be split to have the final state not be the same as the initial
+ // state.
+ Inactive,
+
+ // A live link is connected to the other side of this actor.
+ Connected,
+
+ // The link has begun being destroyed. Messages may still be received, but
+ // cannot be sent. (exception: sync/intr replies may be sent while Doomed).
+ Doomed,
+
+ // The link has been destroyed, and messages will no longer be sent or
+ // received.
+ Destroyed,
+};
+
+typedef IPCMessageStart ProtocolId;
+
+// Generated by IPDL compiler
+const char* ProtocolIdToName(IPCMessageStart aId);
+
+class IToplevelProtocol;
+class ActorLifecycleProxy;
+class WeakActorLifecycleProxy;
+class IPDLResolverInner;
+class UntypedManagedEndpoint;
+
+class IProtocol : public HasResultCodes {
+ public:
+ enum ActorDestroyReason {
+ FailedConstructor,
+ Deletion,
+ AncestorDeletion,
+ NormalShutdown,
+ AbnormalShutdown,
+ ManagedEndpointDropped
+ };
+
+ typedef base::ProcessId ProcessId;
+ typedef IPC::Message Message;
+
+ IProtocol(ProtocolId aProtoId, Side aSide)
+ : mId(0),
+ mProtocolId(aProtoId),
+ mSide(aSide),
+ mLinkStatus(LinkStatus::Inactive),
+ mLifecycleProxy(nullptr),
+ mManager(nullptr),
+ mToplevel(nullptr) {}
+
+ IToplevelProtocol* ToplevelProtocol() { return mToplevel; }
+ const IToplevelProtocol* ToplevelProtocol() const { return mToplevel; }
+
+ // The following methods either directly forward to the toplevel protocol, or
+ // almost directly do.
+ int32_t Register(IProtocol* aRouted);
+ int32_t RegisterID(IProtocol* aRouted, int32_t aId);
+ IProtocol* Lookup(int32_t aId);
+ void Unregister(int32_t aId);
+
+ Shmem::SharedMemory* CreateSharedMemory(size_t aSize, bool aUnsafe,
+ int32_t* aId);
+ Shmem::SharedMemory* LookupSharedMemory(int32_t aId);
+ bool IsTrackingSharedMemory(Shmem::SharedMemory* aSegment);
+ bool DestroySharedMemory(Shmem& aShmem);
+
+ MessageChannel* GetIPCChannel();
+ const MessageChannel* GetIPCChannel() const;
+
+ // Get the nsISerialEventTarget which all messages sent to this actor will be
+ // processed on. Unless stated otherwise, all operations on IProtocol which
+ // don't occur on this `nsISerialEventTarget` are unsafe.
+ nsISerialEventTarget* GetActorEventTarget();
+
+ // Actor lifecycle and other properties.
+ ProtocolId GetProtocolId() const { return mProtocolId; }
+ const char* GetProtocolName() const { return ProtocolIdToName(mProtocolId); }
+
+ int32_t Id() const { return mId; }
+ IProtocol* Manager() const { return mManager; }
+
+ ActorLifecycleProxy* GetLifecycleProxy() { return mLifecycleProxy; }
+ WeakActorLifecycleProxy* GetWeakLifecycleProxy();
+
+ Side GetSide() const { return mSide; }
+ bool CanSend() const { return mLinkStatus == LinkStatus::Connected; }
+ bool CanRecv() const {
+ return mLinkStatus == LinkStatus::Connected ||
+ mLinkStatus == LinkStatus::Doomed;
+ }
+
+ // Remove or deallocate a managee given its type.
+ virtual void RemoveManagee(int32_t, IProtocol*) = 0;
+ virtual void DeallocManagee(int32_t, IProtocol*) = 0;
+
+ Maybe<IProtocol*> ReadActor(IPC::MessageReader* aReader, bool aNullable,
+ const char* aActorDescription,
+ int32_t aProtocolTypeId);
+
+ virtual Result OnMessageReceived(const Message& aMessage) = 0;
+ virtual Result OnMessageReceived(const Message& aMessage,
+ UniquePtr<Message>& aReply) = 0;
+ virtual Result OnCallReceived(const Message& aMessage,
+ UniquePtr<Message>& aReply) = 0;
+ bool AllocShmem(size_t aSize, Shmem* aOutMem);
+ bool AllocUnsafeShmem(size_t aSize, Shmem* aOutMem);
+ bool DeallocShmem(Shmem& aMem);
+
+ void FatalError(const char* const aErrorMsg);
+ virtual void HandleFatalError(const char* aErrorMsg);
+
+ protected:
+ virtual ~IProtocol();
+
+ friend class IToplevelProtocol;
+ friend class ActorLifecycleProxy;
+ friend class IPDLResolverInner;
+ friend class UntypedManagedEndpoint;
+
+ void SetId(int32_t aId);
+
+ // We have separate functions because the accessibility code manually
+ // calls SetManager.
+ void SetManager(IProtocol* aManager);
+
+ // Sets the manager for the protocol and registers the protocol with
+ // its manager, setting up channels for the protocol as well. Not
+ // for use outside of IPDL.
+ void SetManagerAndRegister(IProtocol* aManager);
+ void SetManagerAndRegister(IProtocol* aManager, int32_t aId);
+
+ // Helpers for calling `Send` on our underlying IPC channel.
+ bool ChannelSend(UniquePtr<IPC::Message> aMsg);
+ bool ChannelSend(UniquePtr<IPC::Message> aMsg,
+ UniquePtr<IPC::Message>* aReply);
+ template <typename Value>
+ void ChannelSend(UniquePtr<IPC::Message> aMsg,
+ IPC::Message::msgid_t aReplyMsgId,
+ ResolveCallback<Value>&& aResolve,
+ RejectCallback&& aReject) {
+ if (CanSend()) {
+ GetIPCChannel()->Send(std::move(aMsg), Id(), aReplyMsgId,
+ std::move(aResolve), std::move(aReject));
+ } else {
+ WarnMessageDiscarded(aMsg.get());
+ aReject(ResponseRejectReason::SendError);
+ }
+ }
+
+ // Collect all actors managed by this object in an array. To make this safer
+ // to iterate, `ActorLifecycleProxy` references are returned rather than raw
+ // actor pointers.
+ virtual void AllManagedActors(
+ nsTArray<RefPtr<ActorLifecycleProxy>>& aActors) const = 0;
+
+ // Internal method called when the actor becomes connected.
+ void ActorConnected();
+
+ // Called immediately before setting the actor state to doomed, and triggering
+ // async actor destruction. Messages may be sent from this callback, but no
+ // later.
+ // FIXME(nika): This is currently unused!
+ virtual void ActorDoom() {}
+ void DoomSubtree();
+
+ // Called when the actor has been destroyed due to an error, a __delete__
+ // message, or a __doom__ reply.
+ virtual void ActorDestroy(ActorDestroyReason aWhy) {}
+ void DestroySubtree(ActorDestroyReason aWhy);
+
+ // Called when IPC has acquired its first reference to the actor. This method
+ // may take references which will later be freed by `ActorDealloc`.
+ virtual void ActorAlloc() {}
+
+ // Called when IPC has released its final reference to the actor. It will call
+ // the dealloc method, causing the actor to be actually freed.
+ //
+ // The actor has been freed after this method returns.
+ virtual void ActorDealloc() {
+ if (Manager()) {
+ Manager()->DeallocManagee(mProtocolId, this);
+ }
+ }
+
+ static const int32_t kNullActorId = 0;
+ static const int32_t kFreedActorId = 1;
+
+ private:
+#ifdef DEBUG
+ void WarnMessageDiscarded(IPC::Message* aMsg);
+#else
+ void WarnMessageDiscarded(IPC::Message*) {}
+#endif
+
+ int32_t mId;
+ ProtocolId mProtocolId;
+ Side mSide;
+ LinkStatus mLinkStatus;
+ ActorLifecycleProxy* mLifecycleProxy;
+ IProtocol* mManager;
+ IToplevelProtocol* mToplevel;
+};
+
+#define IPC_OK() mozilla::ipc::IPCResult::Ok()
+#define IPC_FAIL(actor, why) \
+ mozilla::ipc::IPCResult::Fail(WrapNotNull(actor), __func__, (why))
+#define IPC_FAIL_NO_REASON(actor) \
+ mozilla::ipc::IPCResult::Fail(WrapNotNull(actor), __func__)
+
+/**
+ * All message deserializer and message handler should return this
+ * type via above macros. We use a less generic name here to avoid
+ * conflict with mozilla::Result because we have quite a few using
+ * namespace mozilla::ipc; in the code base.
+ */
+class IPCResult {
+ public:
+ static IPCResult Ok() { return IPCResult(true); }
+ static IPCResult Fail(NotNull<IProtocol*> aActor, const char* aWhere,
+ const char* aWhy = "");
+ MOZ_IMPLICIT operator bool() const { return mSuccess; }
+
+ private:
+ explicit IPCResult(bool aResult) : mSuccess(aResult) {}
+ bool mSuccess;
+};
+
+class UntypedEndpoint;
+
+template <class PFooSide>
+class Endpoint;
+
+template <class PFooSide>
+class ManagedEndpoint;
+
+/**
+ * All top-level protocols should inherit this class.
+ *
+ * IToplevelProtocol tracks all top-level protocol actors created from
+ * this protocol actor.
+ */
+class IToplevelProtocol : public IProtocol {
+ template <class PFooSide>
+ friend class Endpoint;
+
+ protected:
+ explicit IToplevelProtocol(const char* aName, ProtocolId aProtoId,
+ Side aSide);
+ ~IToplevelProtocol() = default;
+
+ public:
+ // All top-level protocols are refcounted.
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ // Shadow methods on IProtocol which are implemented directly on toplevel
+ // actors.
+ int32_t Register(IProtocol* aRouted);
+ int32_t RegisterID(IProtocol* aRouted, int32_t aId);
+ IProtocol* Lookup(int32_t aId);
+ void Unregister(int32_t aId);
+
+ Shmem::SharedMemory* CreateSharedMemory(size_t aSize, bool aUnsafe,
+ int32_t* aId);
+ Shmem::SharedMemory* LookupSharedMemory(int32_t aId);
+ bool IsTrackingSharedMemory(Shmem::SharedMemory* aSegment);
+ bool DestroySharedMemory(Shmem& aShmem);
+
+ MessageChannel* GetIPCChannel() { return &mChannel; }
+ const MessageChannel* GetIPCChannel() const { return &mChannel; }
+
+ void SetOtherProcessId(base::ProcessId aOtherPid);
+
+ virtual void OnChannelClose() = 0;
+ virtual void OnChannelError() = 0;
+ virtual void ProcessingError(Result aError, const char* aMsgName) {}
+
+ bool Open(ScopedPort aPort, const nsID& aMessageChannelId,
+ base::ProcessId aOtherPid,
+ nsISerialEventTarget* aEventTarget = nullptr);
+
+ bool Open(IToplevelProtocol* aTarget, nsISerialEventTarget* aEventTarget,
+ mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide);
+
+ // Open a toplevel actor such that both ends of the actor's channel are on
+ // the same thread. This method should be called on the thread to perform
+ // the link.
+ //
+ // WARNING: Attempting to send a sync or intr message on the same thread
+ // will crash.
+ bool OpenOnSameThread(IToplevelProtocol* aTarget,
+ mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide);
+
+ /**
+ * This sends a special message that is processed on the IO thread, so that
+ * other actors can know that the process will soon shutdown.
+ */
+ void NotifyImpendingShutdown();
+
+ void Close();
+ void CloseWithError();
+
+ void SetReplyTimeoutMs(int32_t aTimeoutMs);
+
+ void DeallocShmems();
+ bool ShmemCreated(const Message& aMsg);
+ bool ShmemDestroyed(const Message& aMsg);
+
+ virtual bool ShouldContinueFromReplyTimeout() { return false; }
+
+ // WARNING: This function is called with the MessageChannel monitor held.
+ virtual void IntentionalCrash() { MOZ_CRASH("Intentional IPDL crash"); }
+
+ // The code here is only useful for fuzzing. It should not be used for any
+ // other purpose.
+#ifdef DEBUG
+ // Returns true if we should simulate a timeout.
+ // WARNING: This is a testing-only function that is called with the
+ // MessageChannel monitor held. Don't do anything fancy here or we could
+ // deadlock.
+ virtual bool ArtificialTimeout() { return false; }
+
+ // Returns true if we want to cause the worker thread to sleep with the
+ // monitor unlocked.
+ virtual bool NeedArtificialSleep() { return false; }
+
+ // This function should be implemented to sleep for some amount of time on
+ // the worker thread. Will only be called if NeedArtificialSleep() returns
+ // true.
+ virtual void ArtificialSleep() {}
+#else
+ bool ArtificialTimeout() { return false; }
+ bool NeedArtificialSleep() { return false; }
+ void ArtificialSleep() {}
+#endif
+
+ bool IsOnCxxStack() const;
+
+ virtual void ProcessRemoteNativeEventsInInterruptCall() {}
+
+ virtual void OnChannelReceivedMessage(const Message& aMsg) {}
+
+ void OnIPCChannelOpened() { ActorConnected(); }
+
+ base::ProcessId OtherPidMaybeInvalid() const { return mOtherPid; }
+
+ private:
+ int32_t NextId();
+
+ template <class T>
+ using IDMap = nsTHashMap<nsUint32HashKey, T>;
+
+ base::ProcessId mOtherPid;
+
+ // NOTE NOTE NOTE
+ // Used to be on mState
+ int32_t mLastLocalId;
+ IDMap<IProtocol*> mActorMap;
+ IDMap<Shmem::SharedMemory*> mShmemMap;
+
+ MessageChannel mChannel;
+};
+
+class IShmemAllocator {
+ public:
+ virtual bool AllocShmem(size_t aSize, mozilla::ipc::Shmem* aShmem) = 0;
+ virtual bool AllocUnsafeShmem(size_t aSize, mozilla::ipc::Shmem* aShmem) = 0;
+ virtual bool DeallocShmem(mozilla::ipc::Shmem& aShmem) = 0;
+};
+
+#define FORWARD_SHMEM_ALLOCATOR_TO(aImplClass) \
+ virtual bool AllocShmem(size_t aSize, mozilla::ipc::Shmem* aShmem) \
+ override { \
+ return aImplClass::AllocShmem(aSize, aShmem); \
+ } \
+ virtual bool AllocUnsafeShmem(size_t aSize, mozilla::ipc::Shmem* aShmem) \
+ override { \
+ return aImplClass::AllocUnsafeShmem(aSize, aShmem); \
+ } \
+ virtual bool DeallocShmem(mozilla::ipc::Shmem& aShmem) override { \
+ return aImplClass::DeallocShmem(aShmem); \
+ }
+
+inline bool LoggingEnabled() {
+#if defined(DEBUG) || defined(FUZZING)
+ return !!PR_GetEnv("MOZ_IPC_MESSAGE_LOG");
+#else
+ return false;
+#endif
+}
+
+#if defined(DEBUG) || defined(FUZZING)
+bool LoggingEnabledFor(const char* aTopLevelProtocol, const char* aFilter);
+#endif
+
+inline bool LoggingEnabledFor(const char* aTopLevelProtocol) {
+#if defined(DEBUG) || defined(FUZZING)
+ return LoggingEnabledFor(aTopLevelProtocol, PR_GetEnv("MOZ_IPC_MESSAGE_LOG"));
+#else
+ return false;
+#endif
+}
+
+MOZ_NEVER_INLINE void LogMessageForProtocol(const char* aTopLevelProtocol,
+ base::ProcessId aOtherPid,
+ const char* aContextDescription,
+ uint32_t aMessageId,
+ MessageDirection aDirection);
+
+MOZ_NEVER_INLINE void ProtocolErrorBreakpoint(const char* aMsg);
+
+// IPC::MessageReader and IPC::MessageWriter call this function for FatalError
+// calls which come from serialization/deserialization.
+MOZ_NEVER_INLINE void PickleFatalError(const char* aMsg, IProtocol* aActor);
+
+// The code generator calls this function for errors which come from the
+// methods of protocols. Doing this saves codesize by making the error
+// cases significantly smaller.
+MOZ_NEVER_INLINE void FatalError(const char* aMsg, bool aIsParent);
+
+// The code generator calls this function for errors which are not
+// protocol-specific: errors in generated struct methods or errors in
+// transition functions, for instance. Doing this saves codesize by
+// by making the error cases significantly smaller.
+MOZ_NEVER_INLINE void LogicError(const char* aMsg);
+
+MOZ_NEVER_INLINE void ActorIdReadError(const char* aActorDescription);
+
+MOZ_NEVER_INLINE void BadActorIdError(const char* aActorDescription);
+
+MOZ_NEVER_INLINE void ActorLookupError(const char* aActorDescription);
+
+MOZ_NEVER_INLINE void MismatchedActorTypeError(const char* aActorDescription);
+
+MOZ_NEVER_INLINE void UnionTypeReadError(const char* aUnionName);
+
+MOZ_NEVER_INLINE void ArrayLengthReadError(const char* aElementName);
+
+MOZ_NEVER_INLINE void SentinelReadError(const char* aElementName);
+
+/**
+ * Annotate the crash reporter with the error code from the most recent system
+ * call. Returns the system error.
+ */
+void AnnotateSystemError();
+
+// The ActorLifecycleProxy is a helper type used internally by IPC to maintain a
+// maybe-owning reference to an IProtocol object. For well-behaved actors
+// which are not freed until after their `Dealloc` method is called, a
+// reference to an actor's `ActorLifecycleProxy` object is an owning one, as the
+// `Dealloc` method will only be called when all references to the
+// `ActorLifecycleProxy` are released.
+//
+// Unfortunately, some actors may be destroyed before their `Dealloc` method
+// is called. For these actors, `ActorLifecycleProxy` acts as a weak pointer,
+// and will begin to return `nullptr` from its `Get()` method once the
+// corresponding actor object has been destroyed.
+//
+// When calling a `Recv` method, IPC will hold a `ActorLifecycleProxy` reference
+// to the target actor, meaning that well-behaved actors can behave as though a
+// strong reference is being held.
+//
+// Generic IPC code MUST treat ActorLifecycleProxy references as weak
+// references!
+class ActorLifecycleProxy {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_ONEVENTTARGET(ActorLifecycleProxy)
+
+ IProtocol* Get() { return mActor; }
+
+ WeakActorLifecycleProxy* GetWeakProxy();
+
+ private:
+ friend class IProtocol;
+
+ explicit ActorLifecycleProxy(IProtocol* aActor);
+ ~ActorLifecycleProxy();
+
+ ActorLifecycleProxy(const ActorLifecycleProxy&) = delete;
+ ActorLifecycleProxy& operator=(const ActorLifecycleProxy&) = delete;
+
+ IProtocol* MOZ_NON_OWNING_REF mActor;
+
+ // Hold a reference to the actor's manager's ActorLifecycleProxy to help
+ // prevent it from dying while we're still alive!
+ RefPtr<ActorLifecycleProxy> mManager;
+
+ // When requested, the current self-referencing weak reference for this
+ // ActorLifecycleProxy.
+ RefPtr<WeakActorLifecycleProxy> mWeakProxy;
+};
+
+// Unlike ActorLifecycleProxy, WeakActorLifecycleProxy only holds a weak
+// reference to both the proxy and the actual actor, meaning that holding this
+// type will not attempt to keep the actor object alive.
+//
+// This type is safe to hold on threads other than the actor's thread, but is
+// _NOT_ safe to access on other threads, as actors and ActorLifecycleProxy
+// objects are not threadsafe.
+class WeakActorLifecycleProxy final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WeakActorLifecycleProxy)
+
+ // May only be called on the actor's event target.
+ // Will return `nullptr` if the actor has already been destroyed from IPC's
+ // point of view.
+ IProtocol* Get() const;
+
+ // Safe to call on any thread.
+ nsISerialEventTarget* ActorEventTarget() const { return mActorEventTarget; }
+
+ private:
+ friend class ActorLifecycleProxy;
+
+ explicit WeakActorLifecycleProxy(ActorLifecycleProxy* aProxy);
+ ~WeakActorLifecycleProxy();
+
+ WeakActorLifecycleProxy(const WeakActorLifecycleProxy&) = delete;
+ WeakActorLifecycleProxy& operator=(const WeakActorLifecycleProxy&) = delete;
+
+ // This field may only be accessed on the actor's thread, and will be
+ // automatically cleared when the ActorLifecycleProxy is destroyed.
+ ActorLifecycleProxy* MOZ_NON_OWNING_REF mProxy;
+
+ // The serial event target which owns the actor, and is the only thread where
+ // it is OK to access the ActorLifecycleProxy.
+ const nsCOMPtr<nsISerialEventTarget> mActorEventTarget;
+};
+
+class IPDLResolverInner final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(IPDLResolverInner,
+ Destroy())
+
+ explicit IPDLResolverInner(UniquePtr<IPC::Message> aReply, IProtocol* aActor);
+
+ template <typename F>
+ void Resolve(F&& aWrite) {
+ ResolveOrReject(true, aWrite);
+ }
+
+ private:
+ void ResolveOrReject(bool aResolve,
+ FunctionRef<void(IPC::Message*, IProtocol*)> aWrite);
+
+ void Destroy();
+ ~IPDLResolverInner();
+
+ UniquePtr<IPC::Message> mReply;
+ RefPtr<WeakActorLifecycleProxy> mWeakProxy;
+};
+
+} // namespace ipc
+
+template <typename Protocol>
+class ManagedContainer {
+ public:
+ using iterator = typename nsTArray<Protocol*>::const_iterator;
+
+ iterator begin() const { return mArray.begin(); }
+ iterator end() const { return mArray.end(); }
+ iterator cbegin() const { return begin(); }
+ iterator cend() const { return end(); }
+
+ bool IsEmpty() const { return mArray.IsEmpty(); }
+ uint32_t Count() const { return mArray.Length(); }
+
+ void ToArray(nsTArray<Protocol*>& aArray) const {
+ aArray.AppendElements(mArray);
+ }
+
+ bool EnsureRemoved(Protocol* aElement) {
+ return mArray.RemoveElementSorted(aElement);
+ }
+
+ void Insert(Protocol* aElement) {
+ // Equivalent to `InsertElementSorted`, avoiding inserting a duplicate
+ // element.
+ size_t index = mArray.IndexOfFirstElementGt(aElement);
+ if (index == 0 || mArray[index - 1] != aElement) {
+ mArray.InsertElementAt(index, aElement);
+ }
+ }
+
+ void Clear() { mArray.Clear(); }
+
+ private:
+ nsTArray<Protocol*> mArray;
+};
+
+template <typename Protocol>
+Protocol* LoneManagedOrNullAsserts(
+ const ManagedContainer<Protocol>& aManagees) {
+ if (aManagees.IsEmpty()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(aManagees.Count() == 1);
+ return *aManagees.cbegin();
+}
+
+template <typename Protocol>
+Protocol* SingleManagedOrNull(const ManagedContainer<Protocol>& aManagees) {
+ if (aManagees.Count() != 1) {
+ return nullptr;
+ }
+ return *aManagees.cbegin();
+}
+
+} // namespace mozilla
+
+#endif // mozilla_ipc_ProtocolUtils_h
diff --git a/ipc/glue/RandomAccessStreamParams.ipdlh b/ipc/glue/RandomAccessStreamParams.ipdlh
new file mode 100644
index 0000000000..8bcf528af2
--- /dev/null
+++ b/ipc/glue/RandomAccessStreamParams.ipdlh
@@ -0,0 +1,32 @@
+/* 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 IPCQuotaObject;
+
+namespace mozilla {
+namespace ipc {
+
+// Use RandomAccessStreamParams in your ipdl to represent serialized
+// nsIRandomAccessStreams. Then use SerializeRandomAccessStream from
+// RandomAccessStreamUtils.h to perform the serialization.
+struct FileRandomAccessStreamParams
+{
+ FileDescriptor fileDescriptor;
+ int32_t behaviorFlags;
+};
+
+struct LimitingFileRandomAccessStreamParams
+{
+ FileRandomAccessStreamParams fileRandomAccessStreamParams;
+ IPCQuotaObject quotaObject;
+};
+
+union RandomAccessStreamParams
+{
+ FileRandomAccessStreamParams;
+ LimitingFileRandomAccessStreamParams;
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/RandomAccessStreamUtils.cpp b/ipc/glue/RandomAccessStreamUtils.cpp
new file mode 100644
index 0000000000..d5e66daae6
--- /dev/null
+++ b/ipc/glue/RandomAccessStreamUtils.cpp
@@ -0,0 +1,88 @@
+/* -*- 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 "RandomAccessStreamUtils.h"
+
+#include "mozilla/NotNull.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/quota/FileStreams.h"
+#include "mozilla/ipc/RandomAccessStreamParams.h"
+#include "nsFileStreams.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIRandomAccessStream.h"
+
+namespace mozilla::ipc {
+
+RandomAccessStreamParams SerializeRandomAccessStream(
+ MovingNotNull<nsCOMPtr<nsIRandomAccessStream>> aStream,
+ nsIInterfaceRequestor* aCallbacks) {
+ NotNull<nsCOMPtr<nsIRandomAccessStream>> stream = std::move(aStream);
+
+ RandomAccessStreamParams streamParams = stream->Serialize(aCallbacks);
+
+ MOZ_ASSERT(streamParams.type() != RandomAccessStreamParams::T__None);
+
+ return streamParams;
+}
+
+Maybe<RandomAccessStreamParams> SerializeRandomAccessStream(
+ nsCOMPtr<nsIRandomAccessStream> aStream,
+ nsIInterfaceRequestor* aCallbacks) {
+ if (!aStream) {
+ return Nothing();
+ }
+
+ return Some(SerializeRandomAccessStream(
+ WrapMovingNotNullUnchecked(std::move(aStream)), aCallbacks));
+}
+
+Result<MovingNotNull<nsCOMPtr<nsIRandomAccessStream>>, bool>
+DeserializeRandomAccessStream(RandomAccessStreamParams& aStreamParams) {
+ nsCOMPtr<nsIRandomAccessStream> stream;
+
+ switch (aStreamParams.type()) {
+ case RandomAccessStreamParams::TFileRandomAccessStreamParams:
+ nsFileRandomAccessStream::Create(NS_GET_IID(nsIFileRandomAccessStream),
+ getter_AddRefs(stream));
+ break;
+
+ case RandomAccessStreamParams::TLimitingFileRandomAccessStreamParams:
+ stream = new dom::quota::FileRandomAccessStream();
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown params!");
+ return Err(false);
+ }
+
+ MOZ_ASSERT(stream);
+
+ if (!stream->Deserialize(aStreamParams)) {
+ MOZ_ASSERT_UNREACHABLE("Deserialize failed!");
+ return Err(false);
+ }
+
+ return WrapMovingNotNullUnchecked(std::move(stream));
+}
+
+Result<nsCOMPtr<nsIRandomAccessStream>, bool> DeserializeRandomAccessStream(
+ Maybe<RandomAccessStreamParams>& aStreamParams) {
+ if (aStreamParams.isNothing()) {
+ return nsCOMPtr<nsIRandomAccessStream>();
+ }
+
+ auto res = DeserializeRandomAccessStream(aStreamParams.ref());
+ if (res.isErr()) {
+ return res.propagateErr();
+ }
+
+ MovingNotNull<nsCOMPtr<nsIRandomAccessStream>> stream = res.unwrap();
+
+ return std::move(stream).unwrapBasePtr();
+}
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/RandomAccessStreamUtils.h b/ipc/glue/RandomAccessStreamUtils.h
new file mode 100644
index 0000000000..3ca79537eb
--- /dev/null
+++ b/ipc/glue/RandomAccessStreamUtils.h
@@ -0,0 +1,50 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_RandomAccessStreamUtils_h
+#define mozilla_ipc_RandomAccessStreamUtils_h
+
+template <class T>
+class nsCOMPtr;
+
+class nsIInterfaceRequestor;
+class nsIRandomAccessStream;
+
+namespace mozilla {
+
+template <class T>
+class Maybe;
+
+template <typename T>
+class MovingNotNull;
+
+template <typename V, typename E>
+class Result;
+
+namespace ipc {
+
+class RandomAccessStreamParams;
+
+// Serialize an nsIRandomAccessStream to be sent over IPC infallibly.
+RandomAccessStreamParams SerializeRandomAccessStream(
+ MovingNotNull<nsCOMPtr<nsIRandomAccessStream>> aStream,
+ nsIInterfaceRequestor* aCallbacks);
+
+Maybe<RandomAccessStreamParams> SerializeRandomAccessStream(
+ nsCOMPtr<nsIRandomAccessStream> aStream, nsIInterfaceRequestor* aCallbacks);
+
+// Deserialize an nsIRandomAccessStream received from an actor call. These
+// methods work in both the child and parent.
+Result<MovingNotNull<nsCOMPtr<nsIRandomAccessStream>>, bool>
+DeserializeRandomAccessStream(RandomAccessStreamParams& aStreamParams);
+
+Result<nsCOMPtr<nsIRandomAccessStream>, bool> DeserializeRandomAccessStream(
+ Maybe<RandomAccessStreamParams>& aStreamParams);
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_RandomAccessStreamUtils_h
diff --git a/ipc/glue/RawShmem.cpp b/ipc/glue/RawShmem.cpp
new file mode 100644
index 0000000000..bfb47c0832
--- /dev/null
+++ b/ipc/glue/RawShmem.cpp
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "RawShmem.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+
+namespace mozilla::ipc {
+
+UnsafeSharedMemoryHandle::UnsafeSharedMemoryHandle()
+ : mHandle(ipc::SharedMemoryBasic::NULLHandle()), mSize(0) {}
+
+UnsafeSharedMemoryHandle::UnsafeSharedMemoryHandle(
+ UnsafeSharedMemoryHandle&& aOther) noexcept
+ : mHandle(std::move(aOther.mHandle)), mSize(aOther.mSize) {
+ aOther.mHandle = ipc::SharedMemoryBasic::NULLHandle();
+ aOther.mSize = 0;
+}
+
+UnsafeSharedMemoryHandle& UnsafeSharedMemoryHandle::operator=(
+ UnsafeSharedMemoryHandle&& aOther) noexcept {
+ if (this == &aOther) {
+ return *this;
+ }
+
+ mHandle = std::move(aOther.mHandle);
+ mSize = aOther.mSize;
+ aOther.mHandle = ipc::SharedMemoryBasic::NULLHandle();
+ aOther.mSize = 0;
+ return *this;
+}
+
+Maybe<std::pair<UnsafeSharedMemoryHandle, WritableSharedMemoryMapping>>
+UnsafeSharedMemoryHandle::CreateAndMap(size_t aSize) {
+ if (aSize == 0) {
+ return Some(std::make_pair(UnsafeSharedMemoryHandle(),
+ WritableSharedMemoryMapping()));
+ }
+
+ RefPtr<ipc::SharedMemoryBasic> shm = MakeAndAddRef<ipc::SharedMemoryBasic>();
+ if (NS_WARN_IF(!shm->Create(aSize)) || NS_WARN_IF(!shm->Map(aSize))) {
+ return Nothing();
+ }
+
+ // TODO(bug 1797039): At the moment the handle/mapping distinction is
+ // implemented on top of a single class SharedMemoryBasic. It leads to a few
+ // awkward or sub-optimal things such as how the following few lines clone
+ // then close the handle. It would be better to separate the underlying
+ // implementation or steal the handle to avoid cloning it.
+ auto handle = shm->CloneHandle();
+
+ shm->CloseHandle();
+ auto size = shm->Size();
+
+ return Some(std::make_pair(UnsafeSharedMemoryHandle(std::move(handle), size),
+ WritableSharedMemoryMapping(std::move(shm))));
+}
+
+WritableSharedMemoryMapping::WritableSharedMemoryMapping(
+ RefPtr<ipc::SharedMemoryBasic>&& aRef)
+ : mRef(aRef) {}
+
+Maybe<WritableSharedMemoryMapping> WritableSharedMemoryMapping::Open(
+ UnsafeSharedMemoryHandle aHandle) {
+ if (aHandle.mSize == 0) {
+ return Some(WritableSharedMemoryMapping(nullptr));
+ }
+
+ RefPtr<ipc::SharedMemoryBasic> shm = MakeAndAddRef<ipc::SharedMemoryBasic>();
+ if (NS_WARN_IF(!shm->SetHandle(std::move(aHandle.mHandle),
+ ipc::SharedMemory::RightsReadWrite)) ||
+ NS_WARN_IF(!shm->Map(aHandle.mSize))) {
+ return Nothing();
+ }
+
+ shm->CloseHandle();
+
+ return Some(WritableSharedMemoryMapping(std::move(shm)));
+}
+
+size_t WritableSharedMemoryMapping::Size() const {
+ if (!mRef) {
+ return 0;
+ }
+
+ return mRef->Size();
+}
+
+Span<uint8_t> WritableSharedMemoryMapping::Bytes() {
+ if (!mRef) {
+ return Span<uint8_t>();
+ }
+
+ uint8_t* mem = static_cast<uint8_t*>(mRef->memory());
+ return Span(mem, mRef->Size());
+}
+
+} // namespace mozilla::ipc
+
+namespace IPC {
+auto ParamTraits<mozilla::ipc::UnsafeSharedMemoryHandle>::Write(
+ IPC::MessageWriter* aWriter, paramType&& aVar) -> void {
+ IPC::WriteParam(aWriter, std::move(aVar.mHandle));
+ IPC::WriteParam(aWriter, aVar.mSize);
+}
+
+auto ParamTraits<mozilla::ipc::UnsafeSharedMemoryHandle>::Read(
+ IPC::MessageReader* aReader, paramType* aVar) -> bool {
+ return IPC::ReadParam(aReader, &aVar->mHandle) &&
+ IPC::ReadParam(aReader, &aVar->mSize);
+}
+
+} // namespace IPC
diff --git a/ipc/glue/RawShmem.h b/ipc/glue/RawShmem.h
new file mode 100644
index 0000000000..cebc59e924
--- /dev/null
+++ b/ipc/glue/RawShmem.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef MOZILLA_IPC_RAWSHMEM_H_
+#define MOZILLA_IPC_RAWSHMEM_H_
+
+#include "mozilla/ipc/SharedMemoryBasic.h"
+#include "mozilla/Span.h"
+#include <utility>
+
+namespace mozilla::ipc {
+
+class WritableSharedMemoryMapping;
+
+/// A handle to shared memory.
+///
+/// See the doc comment for `WritableSharedMemoryMapping` below.
+class UnsafeSharedMemoryHandle {
+ friend class WritableSharedMemoryMapping;
+ friend struct IPC::ParamTraits<UnsafeSharedMemoryHandle>;
+
+ public:
+ UnsafeSharedMemoryHandle();
+ UnsafeSharedMemoryHandle(UnsafeSharedMemoryHandle&& aOther) noexcept;
+ UnsafeSharedMemoryHandle& operator=(
+ UnsafeSharedMemoryHandle&& aOther) noexcept;
+
+ /// Attempts to allocate a shmem.
+ ///
+ /// Returns `Nothing()` if allocation fails.
+ /// If `aSize` is zero, a valid empty WritableSharedMemoryMapping is returned.
+ static Maybe<std::pair<UnsafeSharedMemoryHandle, WritableSharedMemoryMapping>>
+ CreateAndMap(size_t aSize);
+
+ private:
+ UnsafeSharedMemoryHandle(SharedMemoryBasic::Handle&& aHandle, uint64_t aSize)
+ : mHandle(std::move(aHandle)), mSize(aSize) {}
+
+ SharedMemoryBasic::Handle mHandle;
+ uint64_t mSize;
+};
+
+/// A Shared memory buffer mapping.
+///
+/// Unlike `ipc::Shmem`, the underlying shared memory buffer on each side of
+/// the process boundary is only deallocated with there respective
+/// `WritableSharedMemoryMapping`.
+///
+/// ## Usage
+///
+/// Typical usage goes as follows:
+/// - Allocate the memory using `UnsafeSharedMemoryHandle::Create`, returning a
+/// handle and a mapping.
+/// - Send the handle to the other process using an IPDL message.
+/// - On the other process, map the shared memory by creating
+/// WritableSharedMemoryMapping via `WritableSharedMemoryMapping::Open` and
+/// the received handle.
+///
+/// Do not send the shared memory handle again, it is only intended to establish
+/// the mapping on each side during initialization. The user of this class is
+/// responsible for managing the lifetime of the buffers on each side, as well
+/// as their identity, by for example storing them in hash map and referring to
+/// them via IDs in IPDL message if need be.
+///
+/// ## Empty shmems
+///
+/// An empty WritableSharedMemoryMapping is one that was created with size zero.
+/// It is analogous to a null RefPtr. It can be used like a non-empty shmem,
+/// including sending the handle and openning it on another process (resulting
+/// in an empty mapping on the other side).
+class WritableSharedMemoryMapping {
+ friend class UnsafeSharedMemoryHandle;
+
+ public:
+ WritableSharedMemoryMapping() = default;
+
+ WritableSharedMemoryMapping(WritableSharedMemoryMapping&& aMoved) = default;
+
+ WritableSharedMemoryMapping& operator=(WritableSharedMemoryMapping&& aMoved) =
+ default;
+
+ /// Open the shmem and immediately close the handle.
+ static Maybe<WritableSharedMemoryMapping> Open(
+ UnsafeSharedMemoryHandle aHandle);
+
+ // Returns the size in bytes.
+ size_t Size() const;
+
+ // Returns the shared memory as byte range.
+ Span<uint8_t> Bytes();
+
+ private:
+ explicit WritableSharedMemoryMapping(
+ RefPtr<mozilla::ipc::SharedMemoryBasic>&& aRef);
+
+ RefPtr<mozilla::ipc::SharedMemoryBasic> mRef;
+};
+
+} // namespace mozilla::ipc
+
+namespace IPC {
+template <>
+struct ParamTraits<mozilla::ipc::UnsafeSharedMemoryHandle> {
+ typedef mozilla::ipc::UnsafeSharedMemoryHandle paramType;
+ static void Write(IPC::MessageWriter* aWriter, paramType&& aVar);
+ static bool Read(IPC::MessageReader* aReader, paramType* aVar);
+};
+
+} // namespace IPC
+
+#endif
diff --git a/ipc/glue/ScopedPort.cpp b/ipc/glue/ScopedPort.cpp
new file mode 100644
index 0000000000..e874796fb2
--- /dev/null
+++ b/ipc/glue/ScopedPort.cpp
@@ -0,0 +1,77 @@
+/* -*- 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 "mozilla/ipc/ScopedPort.h"
+#include "mozilla/ipc/NodeController.h"
+#include "chrome/common/ipc_message_utils.h"
+
+namespace mozilla::ipc {
+
+void ScopedPort::Reset() {
+ if (mValid) {
+ mController->ClosePort(mPort);
+ }
+ mValid = false;
+ mPort = {};
+ mController = nullptr;
+}
+
+auto ScopedPort::Release() -> PortRef {
+ if (!mValid) {
+ return {};
+ }
+ mValid = false;
+ mController = nullptr;
+ return std::exchange(mPort, PortRef{});
+}
+
+ScopedPort::ScopedPort() = default;
+
+ScopedPort::~ScopedPort() { Reset(); }
+
+ScopedPort::ScopedPort(PortRef aPort, NodeController* aController)
+ : mValid(true), mPort(std::move(aPort)), mController(aController) {
+ MOZ_ASSERT(mPort.is_valid() && mController);
+}
+
+ScopedPort::ScopedPort(ScopedPort&& aOther)
+ : mValid(std::exchange(aOther.mValid, false)),
+ mPort(std::move(aOther.mPort)),
+ mController(std::move(aOther.mController)) {}
+
+ScopedPort& ScopedPort::operator=(ScopedPort&& aOther) {
+ if (this != &aOther) {
+ Reset();
+ mValid = std::exchange(aOther.mValid, false);
+ mPort = std::move(aOther.mPort);
+ mController = std::move(aOther.mController);
+ }
+ return *this;
+}
+
+} // namespace mozilla::ipc
+
+void IPC::ParamTraits<mozilla::ipc::ScopedPort>::Write(MessageWriter* aWriter,
+ paramType&& aParam) {
+ aWriter->WriteBool(aParam.IsValid());
+ if (!aParam.IsValid()) {
+ return;
+ }
+ aWriter->WritePort(std::move(aParam));
+}
+
+bool IPC::ParamTraits<mozilla::ipc::ScopedPort>::Read(MessageReader* aReader,
+ paramType* aResult) {
+ bool isValid = false;
+ if (!aReader->ReadBool(&isValid)) {
+ return false;
+ }
+ if (!isValid) {
+ *aResult = {};
+ return true;
+ }
+ return aReader->ConsumePort(aResult);
+}
diff --git a/ipc/glue/ScopedPort.h b/ipc/glue/ScopedPort.h
new file mode 100644
index 0000000000..e683ba894e
--- /dev/null
+++ b/ipc/glue/ScopedPort.h
@@ -0,0 +1,79 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ipc_RawEndpoint_h
+#define mozilla_ipc_RawEndpoint_h
+
+#include "mojo/core/ports/port_ref.h"
+
+namespace mozilla::ipc {
+
+class NodeController;
+
+/// A uniquely owned raw IPC endpoint, connected to our peers over the node
+/// controller. This type can be sent over IPC channels to establish
+/// connections.
+///
+/// In general, prefer using Endpoint over ScopedPort for type safety reasons.
+class ScopedPort {
+ using PortName = mojo::core::ports::PortName;
+ using PortRef = mojo::core::ports::PortRef;
+
+ public:
+ ScopedPort();
+ ~ScopedPort();
+
+ ScopedPort(PortRef aPort, NodeController* aController);
+
+ ScopedPort(ScopedPort&& aOther);
+ ScopedPort(const ScopedPort&) = delete;
+
+ ScopedPort& operator=(ScopedPort&& aOther);
+ ScopedPort& operator=(const ScopedPort&) = delete;
+
+ // Allow checking if this `ScopedPort` is valid or not.
+ bool IsValid() const { return mValid; }
+ explicit operator bool() const { return IsValid(); }
+
+ // Underlying port and controller which are used by this ScopedPort.
+ const PortName& Name() const { return mPort.name(); }
+ const PortRef& Port() const { return mPort; }
+ NodeController* Controller() const { return mController; }
+
+ // Release ownership over the contained `ScopedPort`, meaning that it will
+ // not be closed when this ScopedPort is destroyed. This will make the
+ // endpoint invalid.
+ PortRef Release();
+
+ private:
+ void Reset();
+
+ bool mValid = false;
+ PortRef mPort;
+ RefPtr<NodeController> mController;
+
+ // NOTE: This type does not contain PID information about the other process,
+ // which will need to be sent separately if necessary.
+};
+
+} // namespace mozilla::ipc
+
+namespace IPC {
+
+template <typename T>
+struct ParamTraits;
+
+template <>
+struct ParamTraits<mozilla::ipc::ScopedPort> {
+ using paramType = mozilla::ipc::ScopedPort;
+
+ static void Write(MessageWriter* aWriter, paramType&& aParam);
+ static bool Read(MessageReader* aReader, paramType* aResult);
+};
+
+} // namespace IPC
+
+#endif
diff --git a/ipc/glue/SerializedStructuredCloneBuffer.cpp b/ipc/glue/SerializedStructuredCloneBuffer.cpp
new file mode 100644
index 0000000000..9cad1ed826
--- /dev/null
+++ b/ipc/glue/SerializedStructuredCloneBuffer.cpp
@@ -0,0 +1,79 @@
+/* -*- 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 "mozilla/ipc/SerializedStructuredCloneBuffer.h"
+#include "js/StructuredClone.h"
+
+namespace IPC {
+
+void ParamTraits<JSStructuredCloneData>::Write(MessageWriter* aWriter,
+ const paramType& aParam) {
+ MOZ_ASSERT(!(aParam.Size() % sizeof(uint64_t)));
+
+ // We can only construct shared memory regions up to 4Gb in size, making that
+ // the maximum possible JSStructuredCloneData size.
+ mozilla::CheckedUint32 size = aParam.Size();
+ if (!size.isValid()) {
+ aWriter->FatalError("JSStructuredCloneData over 4Gb in size");
+ return;
+ }
+ WriteParam(aWriter, size.value());
+
+ MessageBufferWriter bufWriter(aWriter, size.value());
+ aParam.ForEachDataChunk([&](const char* aData, size_t aSize) {
+ return bufWriter.WriteBytes(aData, aSize);
+ });
+}
+
+bool ParamTraits<JSStructuredCloneData>::Read(MessageReader* aReader,
+ paramType* aResult) {
+ uint32_t length = 0;
+ if (!ReadParam(aReader, &length)) {
+ aReader->FatalError("JSStructuredCloneData length read failed");
+ return false;
+ }
+ MOZ_ASSERT(!(length % sizeof(uint64_t)));
+
+ // Borrowing is not suitable to use for IPC to hand out data because we often
+ // want to store the data somewhere for processing after IPC has released the
+ // underlying buffers.
+ //
+ // This deserializer previously used a mechanism to transfer ownership over
+ // the underlying buffers from IPC into the JSStructuredCloneData. This was
+ // removed when support for serializing over shared memory was added, as the
+ // benefit for avoiding copies was limited due to it only functioning for
+ // buffers under 64k in size (as larger buffers would be serialized using
+ // shared memory), and it added substantial complexity to the BufferList type
+ // and the IPC serialization layer due to things like buffer alignment. This
+ // can be revisited in the future if it turns out to be a noticable
+ // performance regression. (bug 1783242)
+
+ mozilla::BufferList<js::SystemAllocPolicy> buffers(0, 0, 4096);
+ MessageBufferReader bufReader(aReader, length);
+ uint32_t read = 0;
+ while (read < length) {
+ size_t bufLen;
+ char* buf = buffers.AllocateBytes(length - read, &bufLen);
+ if (!buf) {
+ // Would be nice to allow actor to control behaviour here (bug 1784307)
+ NS_ABORT_OOM(length - read);
+ return false;
+ }
+ if (!bufReader.ReadBytesInto(buf, bufLen)) {
+ aReader->FatalError("JSStructuredCloneData ReadBytesInto failed");
+ return false;
+ }
+ read += bufLen;
+ }
+
+ MOZ_ASSERT(read == length);
+ *aResult = JSStructuredCloneData(
+ std::move(buffers), JS::StructuredCloneScope::DifferentProcess,
+ OwnTransferablePolicy::IgnoreTransferablesIfAny);
+ return true;
+}
+
+} // namespace IPC
diff --git a/ipc/glue/SerializedStructuredCloneBuffer.h b/ipc/glue/SerializedStructuredCloneBuffer.h
new file mode 100644
index 0000000000..ed4e404dbc
--- /dev/null
+++ b/ipc/glue/SerializedStructuredCloneBuffer.h
@@ -0,0 +1,87 @@
+/* -*- 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/. */
+
+#ifndef __IPC_GLUE_SERIALIZEDSTRUCTUREDCLONEBUFFER_H__
+#define __IPC_GLUE_SERIALIZEDSTRUCTUREDCLONEBUFFER_H__
+
+#include <algorithm>
+#include <cstdint>
+#include <cstdlib>
+#include <string>
+#include <utility>
+#include "chrome/common/ipc_message.h"
+#include "chrome/common/ipc_message_utils.h"
+#include "js/AllocPolicy.h"
+#include "js/StructuredClone.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/BufferList.h"
+#include "mozilla/Vector.h"
+#include "mozilla/mozalloc.h"
+class PickleIterator;
+
+namespace mozilla {
+template <typename...>
+class Variant;
+
+namespace detail {
+template <typename...>
+struct VariantTag;
+}
+} // namespace mozilla
+
+namespace mozilla {
+
+struct SerializedStructuredCloneBuffer final {
+ SerializedStructuredCloneBuffer() = default;
+
+ SerializedStructuredCloneBuffer(SerializedStructuredCloneBuffer&&) = default;
+ SerializedStructuredCloneBuffer& operator=(
+ SerializedStructuredCloneBuffer&&) = default;
+
+ SerializedStructuredCloneBuffer(const SerializedStructuredCloneBuffer&) =
+ delete;
+ SerializedStructuredCloneBuffer& operator=(
+ const SerializedStructuredCloneBuffer& aOther) = delete;
+
+ bool operator==(const SerializedStructuredCloneBuffer& aOther) const {
+ // The copy assignment operator and the equality operator are
+ // needed by the IPDL generated code. We relied on the copy
+ // assignment operator at some places but we never use the
+ // equality operator.
+ return false;
+ }
+
+ JSStructuredCloneData data{JS::StructuredCloneScope::Unassigned};
+};
+
+} // namespace mozilla
+
+namespace IPC {
+template <>
+struct ParamTraits<JSStructuredCloneData> {
+ typedef JSStructuredCloneData paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam);
+
+ static bool Read(MessageReader* aReader, paramType* aResult);
+};
+
+template <>
+struct ParamTraits<mozilla::SerializedStructuredCloneBuffer> {
+ typedef mozilla::SerializedStructuredCloneBuffer paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.data);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->data);
+ }
+};
+
+} // namespace IPC
+
+#endif /* __IPC_GLUE_SERIALIZEDSTRUCTUREDCLONEBUFFER_H__ */
diff --git a/ipc/glue/SharedMemory.cpp b/ipc/glue/SharedMemory.cpp
new file mode 100644
index 0000000000..4761746658
--- /dev/null
+++ b/ipc/glue/SharedMemory.cpp
@@ -0,0 +1,84 @@
+/* -*- 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 <math.h>
+
+#include "nsString.h"
+#include "nsIMemoryReporter.h"
+#include "mozilla/ipc/SharedMemory.h"
+#include "mozilla/Atomics.h"
+
+namespace mozilla {
+namespace ipc {
+
+static Atomic<size_t> gShmemAllocated;
+static Atomic<size_t> gShmemMapped;
+
+class ShmemReporter final : public nsIMemoryReporter {
+ ~ShmemReporter() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) override {
+ MOZ_COLLECT_REPORT(
+ "shmem-allocated", KIND_OTHER, UNITS_BYTES, gShmemAllocated,
+ "Memory shared with other processes that is accessible (but not "
+ "necessarily mapped).");
+
+ MOZ_COLLECT_REPORT(
+ "shmem-mapped", KIND_OTHER, UNITS_BYTES, gShmemMapped,
+ "Memory shared with other processes that is mapped into the address "
+ "space.");
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(ShmemReporter, nsIMemoryReporter)
+
+SharedMemory::SharedMemory() : mAllocSize(0), mMappedSize(0) {
+ static Atomic<bool> registered;
+ if (registered.compareExchange(false, true)) {
+ RegisterStrongMemoryReporter(new ShmemReporter());
+ }
+}
+
+/*static*/
+size_t SharedMemory::PageAlignedSize(size_t aSize) {
+ size_t pageSize = SystemPageSize();
+ size_t nPagesNeeded = size_t(ceil(double(aSize) / double(pageSize)));
+ return pageSize * nPagesNeeded;
+}
+
+void SharedMemory::Created(size_t aNBytes) {
+ mAllocSize = aNBytes;
+ gShmemAllocated += mAllocSize;
+}
+
+void SharedMemory::Mapped(size_t aNBytes) {
+ mMappedSize = aNBytes;
+ gShmemMapped += mMappedSize;
+}
+
+void SharedMemory::Unmapped() {
+ MOZ_ASSERT(gShmemMapped >= mMappedSize, "Can't unmap more than mapped");
+ gShmemMapped -= mMappedSize;
+ mMappedSize = 0;
+}
+
+/*static*/
+void SharedMemory::Destroyed() {
+ MOZ_ASSERT(gShmemAllocated >= mAllocSize,
+ "Can't destroy more than allocated");
+ gShmemAllocated -= mAllocSize;
+ mAllocSize = 0;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/SharedMemory.h b/ipc/glue/SharedMemory.h
new file mode 100644
index 0000000000..820a1d8954
--- /dev/null
+++ b/ipc/glue/SharedMemory.h
@@ -0,0 +1,139 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_SharedMemory_h
+#define mozilla_ipc_SharedMemory_h
+
+#include "nsDebug.h"
+#include "nsISupportsImpl.h" // NS_INLINE_DECL_REFCOUNTING
+#include "mozilla/Attributes.h"
+
+#include "base/process.h"
+#include "chrome/common/ipc_message_utils.h"
+
+//
+// This is a low-level wrapper around platform shared memory. Don't
+// use it directly; use Shmem allocated through IPDL interfaces.
+//
+namespace {
+enum Rights { RightsNone = 0, RightsRead = 1 << 0, RightsWrite = 1 << 1 };
+} // namespace
+
+namespace mozilla {
+
+namespace ipc {
+class SharedMemory;
+} // namespace ipc
+
+namespace ipc {
+
+class SharedMemory {
+ protected:
+ virtual ~SharedMemory() {
+ Unmapped();
+ Destroyed();
+ }
+
+ public:
+ enum OpenRights {
+ RightsReadOnly = RightsRead,
+ RightsReadWrite = RightsRead | RightsWrite,
+ };
+
+ size_t Size() const { return mMappedSize; }
+
+ virtual void* memory() const = 0;
+
+ virtual bool Create(size_t size) = 0;
+ virtual bool Map(size_t nBytes, void* fixed_address = nullptr) = 0;
+ virtual void Unmap() = 0;
+
+ virtual void CloseHandle() = 0;
+
+ virtual bool WriteHandle(IPC::MessageWriter* aWriter) = 0;
+ virtual bool ReadHandle(IPC::MessageReader* aReader) = 0;
+
+ void Protect(char* aAddr, size_t aSize, int aRights) {
+ char* memStart = reinterpret_cast<char*>(memory());
+ if (!memStart) MOZ_CRASH("SharedMemory region points at NULL!");
+ char* memEnd = memStart + Size();
+
+ char* protStart = aAddr;
+ if (!protStart) MOZ_CRASH("trying to Protect() a NULL region!");
+ char* protEnd = protStart + aSize;
+
+ if (!(memStart <= protStart && protEnd <= memEnd))
+ MOZ_CRASH("attempt to Protect() a region outside this SharedMemory");
+
+ // checks alignment etc.
+ SystemProtect(aAddr, aSize, aRights);
+ }
+
+ // bug 1168843, compositor thread may create shared memory instances that are
+ // destroyed by main thread on shutdown, so this must use thread-safe RC to
+ // avoid hitting assertion
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedMemory)
+
+ static void SystemProtect(char* aAddr, size_t aSize, int aRights);
+ [[nodiscard]] static bool SystemProtectFallible(char* aAddr, size_t aSize,
+ int aRights);
+ static size_t SystemPageSize();
+ static size_t PageAlignedSize(size_t aSize);
+
+ protected:
+ SharedMemory();
+
+ // Implementations should call these methods on shmem usage changes,
+ // but *only if* the OS-specific calls are known to have succeeded.
+ // The methods are expected to be called in the pattern
+ //
+ // Created (Mapped Unmapped)* Destroy
+ //
+ // but this isn't checked.
+ void Created(size_t aNBytes);
+ void Mapped(size_t aNBytes);
+ void Unmapped();
+ void Destroyed();
+
+ // The size of the shmem region requested in Create(), if
+ // successful. SharedMemory instances that are opened from a
+ // foreign handle have an alloc size of 0, even though they have
+ // access to the alloc-size information.
+ size_t mAllocSize;
+ // The size of the region mapped in Map(), if successful. All
+ // SharedMemorys that are mapped have a non-zero mapped size.
+ size_t mMappedSize;
+};
+
+template <typename HandleImpl>
+class SharedMemoryCommon : public SharedMemory {
+ public:
+ typedef HandleImpl Handle;
+
+ virtual Handle CloneHandle() = 0;
+ virtual bool IsHandleValid(const Handle& aHandle) const = 0;
+ virtual bool SetHandle(Handle aHandle, OpenRights aRights) = 0;
+
+ virtual bool WriteHandle(IPC::MessageWriter* aWriter) override {
+ Handle handle = CloneHandle();
+ if (!handle) {
+ return false;
+ }
+ IPC::WriteParam(aWriter, std::move(handle));
+ return true;
+ }
+
+ virtual bool ReadHandle(IPC::MessageReader* aReader) override {
+ Handle handle;
+ return IPC::ReadParam(aReader, &handle) && IsHandleValid(handle) &&
+ SetHandle(std::move(handle), RightsReadWrite);
+ }
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // ifndef mozilla_ipc_SharedMemory_h
diff --git a/ipc/glue/SharedMemoryBasic.h b/ipc/glue/SharedMemoryBasic.h
new file mode 100644
index 0000000000..13026be8b2
--- /dev/null
+++ b/ipc/glue/SharedMemoryBasic.h
@@ -0,0 +1,18 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_SharedMemoryBasic_h
+#define mozilla_ipc_SharedMemoryBasic_h
+
+#ifdef ANDROID
+# include "mozilla/ipc/SharedMemoryBasic_android.h"
+#elif defined(XP_DARWIN)
+# include "mozilla/ipc/SharedMemoryBasic_mach.h"
+#else
+# include "mozilla/ipc/SharedMemoryBasic_chromium.h"
+#endif
+
+#endif // ifndef mozilla_ipc_SharedMemoryBasic_h
diff --git a/ipc/glue/SharedMemoryBasic_android.cpp b/ipc/glue/SharedMemoryBasic_android.cpp
new file mode 100644
index 0000000000..5c8279da6d
--- /dev/null
+++ b/ipc/glue/SharedMemoryBasic_android.cpp
@@ -0,0 +1,132 @@
+/* -*- 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 <android/log.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/process_util.h"
+
+#include "SharedMemoryBasic.h"
+
+#include "mozilla/Ashmem.h"
+
+namespace mozilla {
+namespace ipc {
+
+static void LogError(const char* what) {
+ __android_log_print(ANDROID_LOG_ERROR, "Gecko", "%s: %s (%d)", what,
+ strerror(errno), errno);
+}
+
+SharedMemoryBasic::SharedMemoryBasic()
+ : mShmFd(-1), mMemory(nullptr), mOpenRights(RightsReadWrite) {}
+
+SharedMemoryBasic::~SharedMemoryBasic() {
+ Unmap();
+ CloseHandle();
+}
+
+bool SharedMemoryBasic::SetHandle(Handle aHandle, OpenRights aRights) {
+ MOZ_ASSERT(-1 == mShmFd, "Already Create()d");
+ mShmFd = aHandle.release();
+ mOpenRights = aRights;
+ return true;
+}
+
+bool SharedMemoryBasic::Create(size_t aNbytes) {
+ MOZ_ASSERT(-1 == mShmFd, "Already Create()d");
+
+ // Carve a new instance off of /dev/ashmem
+ int shmfd = mozilla::android::ashmem_create(nullptr, aNbytes);
+ if (-1 == shmfd) {
+ LogError("ShmemAndroid::Create():open");
+ return false;
+ }
+
+ mShmFd = shmfd;
+ Created(aNbytes);
+ return true;
+}
+
+bool SharedMemoryBasic::Map(size_t nBytes, void* fixed_address) {
+ MOZ_ASSERT(nullptr == mMemory, "Already Map()d");
+
+ int prot = PROT_READ;
+ if (mOpenRights == RightsReadWrite) {
+ prot |= PROT_WRITE;
+ }
+
+ // Don't use MAP_FIXED when a fixed_address was specified, since that can
+ // replace pages that are alread mapped at that address.
+ mMemory = mmap(fixed_address, nBytes, prot, MAP_SHARED, mShmFd, 0);
+
+ if (MAP_FAILED == mMemory) {
+ if (!fixed_address) {
+ LogError("ShmemAndroid::Map()");
+ }
+ mMemory = nullptr;
+ return false;
+ }
+
+ if (fixed_address && mMemory != fixed_address) {
+ if (munmap(mMemory, nBytes)) {
+ LogError("ShmemAndroid::Map():unmap");
+ mMemory = nullptr;
+ return false;
+ }
+ }
+
+ Mapped(nBytes);
+ return true;
+}
+
+void* SharedMemoryBasic::FindFreeAddressSpace(size_t size) {
+ void* memory =
+ mmap(NULL, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ munmap(memory, size);
+ return memory != (void*)-1 ? memory : NULL;
+}
+
+auto SharedMemoryBasic::CloneHandle() -> Handle {
+ MOZ_ASSERT(mShmFd >= 0, "Should have been Create()d by now");
+
+ int shmfdDup = dup(mShmFd);
+ if (-1 == shmfdDup) {
+ LogError("ShmemAndroid::CloneHandle()");
+ return nullptr;
+ }
+
+ return mozilla::UniqueFileHandle(shmfdDup);
+}
+
+void SharedMemoryBasic::Unmap() {
+ if (!mMemory) {
+ return;
+ }
+
+ if (munmap(mMemory, Size())) {
+ LogError("ShmemAndroid::Unmap()");
+ }
+ mMemory = nullptr;
+}
+
+void SharedMemoryBasic::CloseHandle() {
+ if (mShmFd != -1) {
+ close(mShmFd);
+ mShmFd = -1;
+ mOpenRights = RightsReadWrite;
+ }
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/SharedMemoryBasic_android.h b/ipc/glue/SharedMemoryBasic_android.h
new file mode 100644
index 0000000000..0b9114e2ae
--- /dev/null
+++ b/ipc/glue/SharedMemoryBasic_android.h
@@ -0,0 +1,72 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_SharedMemoryBasic_android_h
+#define mozilla_ipc_SharedMemoryBasic_android_h
+
+#include "mozilla/ipc/SharedMemory.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+#ifdef FUZZING
+# include "mozilla/ipc/SharedMemoryFuzzer.h"
+#endif
+
+//
+// This is a low-level wrapper around platform shared memory. Don't
+// use it directly; use Shmem allocated through IPDL interfaces.
+//
+
+namespace mozilla {
+namespace ipc {
+
+class SharedMemoryBasic final
+ : public SharedMemoryCommon<mozilla::UniqueFileHandle> {
+ public:
+ SharedMemoryBasic();
+
+ virtual bool SetHandle(Handle aHandle, OpenRights aRights) override;
+
+ virtual bool Create(size_t aNbytes) override;
+
+ virtual bool Map(size_t nBytes, void* fixed_address = nullptr) override;
+
+ virtual void Unmap() override;
+
+ virtual void CloseHandle() override;
+
+ virtual void* memory() const override {
+#ifdef FUZZING
+ return SharedMemoryFuzzer::MutateSharedMemory(mMemory, mAllocSize);
+#else
+ return mMemory;
+#endif
+ }
+
+ static Handle NULLHandle() { return Handle(); }
+
+ static void* FindFreeAddressSpace(size_t aSize);
+
+ virtual bool IsHandleValid(const Handle& aHandle) const override {
+ return aHandle != nullptr;
+ }
+
+ virtual Handle CloneHandle() override;
+
+ private:
+ ~SharedMemoryBasic();
+
+ // The /dev/ashmem fd we allocate.
+ int mShmFd;
+ // Pointer to mapped region, null if unmapped.
+ void* mMemory;
+ // Access rights to map an existing region with.
+ OpenRights mOpenRights;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // ifndef mozilla_ipc_SharedMemoryBasic_android_h
diff --git a/ipc/glue/SharedMemoryBasic_chromium.h b/ipc/glue/SharedMemoryBasic_chromium.h
new file mode 100644
index 0000000000..c48898c47d
--- /dev/null
+++ b/ipc/glue/SharedMemoryBasic_chromium.h
@@ -0,0 +1,87 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_SharedMemoryBasic_chromium_h
+#define mozilla_ipc_SharedMemoryBasic_chromium_h
+
+#include "base/shared_memory.h"
+#include "mozilla/ipc/SharedMemory.h"
+
+#ifdef FUZZING
+# include "mozilla/ipc/SharedMemoryFuzzer.h"
+#endif
+
+#include "nsDebug.h"
+
+//
+// This is a low-level wrapper around platform shared memory. Don't
+// use it directly; use Shmem allocated through IPDL interfaces.
+//
+
+namespace mozilla {
+namespace ipc {
+
+class SharedMemoryBasic final
+ : public SharedMemoryCommon<base::SharedMemoryHandle> {
+ public:
+ SharedMemoryBasic() = default;
+
+ virtual bool SetHandle(Handle aHandle, OpenRights aRights) override {
+ return mSharedMemory.SetHandle(std::move(aHandle),
+ aRights == RightsReadOnly);
+ }
+
+ virtual bool Create(size_t aNbytes) override {
+ bool ok = mSharedMemory.Create(aNbytes);
+ if (ok) {
+ Created(aNbytes);
+ }
+ return ok;
+ }
+
+ virtual bool Map(size_t nBytes, void* fixed_address = nullptr) override {
+ bool ok = mSharedMemory.Map(nBytes, fixed_address);
+ if (ok) {
+ Mapped(nBytes);
+ }
+ return ok;
+ }
+
+ virtual void Unmap() override { mSharedMemory.Unmap(); }
+
+ virtual void CloseHandle() override { mSharedMemory.Close(false); }
+
+ virtual void* memory() const override {
+#ifdef FUZZING
+ return SharedMemoryFuzzer::MutateSharedMemory(mSharedMemory.memory(),
+ mAllocSize);
+#else
+ return mSharedMemory.memory();
+#endif
+ }
+
+ static Handle NULLHandle() { return base::SharedMemory::NULLHandle(); }
+
+ virtual bool IsHandleValid(const Handle& aHandle) const override {
+ return base::SharedMemory::IsHandleValid(aHandle);
+ }
+
+ virtual Handle CloneHandle() override { return mSharedMemory.CloneHandle(); }
+
+ static void* FindFreeAddressSpace(size_t size) {
+ return base::SharedMemory::FindFreeAddressSpace(size);
+ }
+
+ private:
+ ~SharedMemoryBasic() = default;
+
+ base::SharedMemory mSharedMemory;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // ifndef mozilla_ipc_SharedMemoryBasic_chromium_h
diff --git a/ipc/glue/SharedMemoryBasic_mach.h b/ipc/glue/SharedMemoryBasic_mach.h
new file mode 100644
index 0000000000..e487c99c84
--- /dev/null
+++ b/ipc/glue/SharedMemoryBasic_mach.h
@@ -0,0 +1,75 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_SharedMemoryBasic_mach_h
+#define mozilla_ipc_SharedMemoryBasic_mach_h
+
+#include "base/process.h"
+
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/ipc/SharedMemory.h"
+#include <mach/port.h>
+
+#ifdef FUZZING
+# include "mozilla/ipc/SharedMemoryFuzzer.h"
+#endif
+
+//
+// This is a low-level wrapper around platform shared memory. Don't
+// use it directly; use Shmem allocated through IPDL interfaces.
+//
+
+class MachPortSender;
+class ReceivePort;
+
+namespace mozilla {
+namespace ipc {
+
+class SharedMemoryBasic final
+ : public SharedMemoryCommon<mozilla::UniqueMachSendRight> {
+ public:
+ SharedMemoryBasic();
+
+ virtual bool SetHandle(Handle aHandle, OpenRights aRights) override;
+
+ virtual bool Create(size_t aNbytes) override;
+
+ virtual bool Map(size_t nBytes, void* fixed_address = nullptr) override;
+
+ virtual void Unmap() override;
+
+ virtual void CloseHandle() override;
+
+ virtual void* memory() const override {
+#ifdef FUZZING
+ return SharedMemoryFuzzer::MutateSharedMemory(mMemory, mAllocSize);
+#else
+ return mMemory;
+#endif
+ }
+
+ static Handle NULLHandle() { return Handle(); }
+
+ static void* FindFreeAddressSpace(size_t aSize);
+
+ virtual bool IsHandleValid(const Handle& aHandle) const override;
+
+ virtual Handle CloneHandle() override;
+
+ private:
+ ~SharedMemoryBasic();
+
+ mozilla::UniqueMachSendRight mPort;
+ // Pointer to mapped region, null if unmapped.
+ void* mMemory;
+ // Access rights to map an existing region with.
+ OpenRights mOpenRights;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // ifndef mozilla_ipc_SharedMemoryBasic_mach_h
diff --git a/ipc/glue/SharedMemoryBasic_mach.mm b/ipc/glue/SharedMemoryBasic_mach.mm
new file mode 100644
index 0000000000..af0d6e6245
--- /dev/null
+++ b/ipc/glue/SharedMemoryBasic_mach.mm
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* 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 <map>
+
+#include <mach/vm_map.h>
+#include <mach/mach_port.h>
+#if defined(XP_IOS)
+# include <mach/vm_map.h>
+# define mach_vm_address_t vm_address_t
+# define mach_vm_map vm_map
+# define mach_vm_read vm_read
+# define mach_vm_region_recurse vm_region_recurse_64
+# define mach_vm_size_t vm_size_t
+#else
+# include <mach/mach_vm.h>
+#endif
+#include <pthread.h>
+#include <unistd.h>
+#include "SharedMemoryBasic.h"
+
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Printf.h"
+#include "mozilla/StaticMutex.h"
+
+#ifdef DEBUG
+# define LOG_ERROR(str, args...) \
+ PR_BEGIN_MACRO \
+ mozilla::SmprintfPointer msg = mozilla::Smprintf(str, ##args); \
+ NS_WARNING(msg.get()); \
+ PR_END_MACRO
+#else
+# define LOG_ERROR(str, args...) \
+ do { /* nothing */ \
+ } while (0)
+#endif
+
+namespace mozilla {
+namespace ipc {
+
+SharedMemoryBasic::SharedMemoryBasic()
+ : mPort(MACH_PORT_NULL), mMemory(nullptr), mOpenRights(RightsReadWrite) {}
+
+SharedMemoryBasic::~SharedMemoryBasic() {
+ Unmap();
+ CloseHandle();
+}
+
+bool SharedMemoryBasic::SetHandle(Handle aHandle, OpenRights aRights) {
+ MOZ_ASSERT(mPort == MACH_PORT_NULL, "already initialized");
+
+ mPort = std::move(aHandle);
+ mOpenRights = aRights;
+ return true;
+}
+
+static inline void* toPointer(mach_vm_address_t address) {
+ return reinterpret_cast<void*>(static_cast<uintptr_t>(address));
+}
+
+static inline mach_vm_address_t toVMAddress(void* pointer) {
+ return static_cast<mach_vm_address_t>(reinterpret_cast<uintptr_t>(pointer));
+}
+
+bool SharedMemoryBasic::Create(size_t size) {
+ MOZ_ASSERT(mPort == MACH_PORT_NULL, "already initialized");
+
+ memory_object_size_t memoryObjectSize = round_page(size);
+
+ kern_return_t kr = mach_make_memory_entry_64(mach_task_self(), &memoryObjectSize, 0,
+ MAP_MEM_NAMED_CREATE | VM_PROT_DEFAULT,
+ getter_Transfers(mPort), MACH_PORT_NULL);
+ if (kr != KERN_SUCCESS || memoryObjectSize < round_page(size)) {
+ LOG_ERROR("Failed to make memory entry (%zu bytes). %s (%x)\n", size, mach_error_string(kr),
+ kr);
+ CloseHandle();
+ return false;
+ }
+
+ Created(size);
+ return true;
+}
+
+bool SharedMemoryBasic::Map(size_t size, void* fixed_address) {
+ MOZ_ASSERT(mMemory == nullptr);
+
+ if (MACH_PORT_NULL == mPort) {
+ return false;
+ }
+
+ kern_return_t kr;
+ mach_vm_address_t address = toVMAddress(fixed_address);
+
+ vm_prot_t vmProtection = VM_PROT_READ;
+ if (mOpenRights == RightsReadWrite) {
+ vmProtection |= VM_PROT_WRITE;
+ }
+
+ kr = mach_vm_map(mach_task_self(), &address, round_page(size), 0,
+ fixed_address ? VM_FLAGS_FIXED : VM_FLAGS_ANYWHERE, mPort.get(), 0, false,
+ vmProtection, vmProtection, VM_INHERIT_NONE);
+ if (kr != KERN_SUCCESS) {
+ if (!fixed_address) {
+ LOG_ERROR("Failed to map shared memory (%zu bytes) into %x, port %x. %s (%x)\n", size,
+ mach_task_self(), mach_port_t(mPort.get()), mach_error_string(kr), kr);
+ }
+ return false;
+ }
+
+ if (fixed_address && fixed_address != toPointer(address)) {
+ kr = vm_deallocate(mach_task_self(), address, size);
+ if (kr != KERN_SUCCESS) {
+ LOG_ERROR("Failed to unmap shared memory at unsuitable address "
+ "(%zu bytes) from %x, port %x. %s (%x)\n",
+ size, mach_task_self(), mach_port_t(mPort.get()), mach_error_string(kr), kr);
+ }
+ return false;
+ }
+
+ mMemory = toPointer(address);
+ Mapped(size);
+ return true;
+}
+
+void* SharedMemoryBasic::FindFreeAddressSpace(size_t size) {
+ mach_vm_address_t address = 0;
+ size = round_page(size);
+ if (mach_vm_map(mach_task_self(), &address, size, 0, VM_FLAGS_ANYWHERE, MEMORY_OBJECT_NULL, 0,
+ false, VM_PROT_NONE, VM_PROT_NONE, VM_INHERIT_NONE) != KERN_SUCCESS ||
+ vm_deallocate(mach_task_self(), address, size) != KERN_SUCCESS) {
+ return nullptr;
+ }
+ return toPointer(address);
+}
+
+auto SharedMemoryBasic::CloneHandle() -> Handle {
+ return mozilla::RetainMachSendRight(mPort.get());
+}
+
+void SharedMemoryBasic::Unmap() {
+ if (!mMemory) {
+ return;
+ }
+ vm_address_t address = toVMAddress(mMemory);
+ kern_return_t kr = vm_deallocate(mach_task_self(), address, round_page(mMappedSize));
+ if (kr != KERN_SUCCESS) {
+ LOG_ERROR("Failed to deallocate shared memory. %s (%x)\n", mach_error_string(kr), kr);
+ return;
+ }
+ mMemory = nullptr;
+}
+
+void SharedMemoryBasic::CloseHandle() {
+ if (mPort) {
+ mPort = nullptr;
+ mOpenRights = RightsReadWrite;
+ }
+}
+
+bool SharedMemoryBasic::IsHandleValid(const Handle& aHandle) const { return aHandle != nullptr; }
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/SharedMemory_posix.cpp b/ipc/glue/SharedMemory_posix.cpp
new file mode 100644
index 0000000000..b634058945
--- /dev/null
+++ b/ipc/glue/SharedMemory_posix.cpp
@@ -0,0 +1,55 @@
+/* -*- 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 <sys/mman.h> // mprotect
+#include <unistd.h> // sysconf
+
+#include "mozilla/ipc/SharedMemory.h"
+
+#if defined(XP_MACOSX) && defined(__x86_64__)
+# include "prenv.h"
+#endif
+
+namespace mozilla {
+namespace ipc {
+
+#if defined(XP_MACOSX) && defined(__x86_64__)
+std::atomic<size_t> sPageSizeOverride = 0;
+#endif
+
+void SharedMemory::SystemProtect(char* aAddr, size_t aSize, int aRights) {
+ if (!SystemProtectFallible(aAddr, aSize, aRights)) {
+ MOZ_CRASH("can't mprotect()");
+ }
+}
+
+bool SharedMemory::SystemProtectFallible(char* aAddr, size_t aSize,
+ int aRights) {
+ int flags = 0;
+ if (aRights & RightsRead) flags |= PROT_READ;
+ if (aRights & RightsWrite) flags |= PROT_WRITE;
+ if (RightsNone == aRights) flags = PROT_NONE;
+
+ return 0 == mprotect(aAddr, aSize, flags);
+}
+
+size_t SharedMemory::SystemPageSize() {
+#if defined(XP_MACOSX) && defined(__x86_64__)
+ if (sPageSizeOverride == 0) {
+ if (PR_GetEnv("MOZ_SHMEM_PAGESIZE_16K")) {
+ sPageSizeOverride = 16 * 1024;
+ } else {
+ sPageSizeOverride = sysconf(_SC_PAGESIZE);
+ }
+ }
+ return sPageSizeOverride;
+#else
+ return sysconf(_SC_PAGESIZE);
+#endif
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/SharedMemory_windows.cpp b/ipc/glue/SharedMemory_windows.cpp
new file mode 100644
index 0000000000..1cc93687af
--- /dev/null
+++ b/ipc/glue/SharedMemory_windows.cpp
@@ -0,0 +1,41 @@
+/* -*- 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 <windows.h>
+
+#include "mozilla/ipc/SharedMemory.h"
+
+namespace mozilla {
+namespace ipc {
+
+void SharedMemory::SystemProtect(char* aAddr, size_t aSize, int aRights) {
+ if (!SystemProtectFallible(aAddr, aSize, aRights)) {
+ MOZ_CRASH("can't VirtualProtect()");
+ }
+}
+
+bool SharedMemory::SystemProtectFallible(char* aAddr, size_t aSize,
+ int aRights) {
+ DWORD flags;
+ if ((aRights & RightsRead) && (aRights & RightsWrite))
+ flags = PAGE_READWRITE;
+ else if (aRights & RightsRead)
+ flags = PAGE_READONLY;
+ else
+ flags = PAGE_NOACCESS;
+
+ DWORD oldflags;
+ return VirtualProtect(aAddr, aSize, flags, &oldflags);
+}
+
+size_t SharedMemory::SystemPageSize() {
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ return si.dwPageSize;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/Shmem.cpp b/ipc/glue/Shmem.cpp
new file mode 100644
index 0000000000..775d852faa
--- /dev/null
+++ b/ipc/glue/Shmem.cpp
@@ -0,0 +1,458 @@
+/* -*- 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 "Shmem.h"
+
+#include "ProtocolUtils.h"
+#include "SharedMemoryBasic.h"
+#include "ShmemMessageUtils.h"
+#include "chrome/common/ipc_message_utils.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace ipc {
+
+class ShmemCreated : public IPC::Message {
+ private:
+ typedef Shmem::id_t id_t;
+
+ public:
+ ShmemCreated(int32_t routingId, id_t aIPDLId, size_t aSize)
+ : IPC::Message(
+ routingId, SHMEM_CREATED_MESSAGE_TYPE, 0,
+ HeaderFlags(NESTED_INSIDE_CPOW, CONTROL_PRIORITY, COMPRESSION_NONE,
+ LAZY_SEND, NOT_CONSTRUCTOR, ASYNC, NOT_REPLY)) {
+ MOZ_RELEASE_ASSERT(aSize < std::numeric_limits<uint32_t>::max(),
+ "Tried to create Shmem with size larger than 4GB");
+ IPC::MessageWriter writer(*this);
+ IPC::WriteParam(&writer, aIPDLId);
+ IPC::WriteParam(&writer, uint32_t(aSize));
+ }
+
+ static bool ReadInfo(IPC::MessageReader* aReader, id_t* aIPDLId,
+ size_t* aSize) {
+ uint32_t size = 0;
+ if (!IPC::ReadParam(aReader, aIPDLId) || !IPC::ReadParam(aReader, &size)) {
+ return false;
+ }
+ *aSize = size;
+ return true;
+ }
+
+ void Log(const std::string& aPrefix, FILE* aOutf) const {
+ fputs("(special ShmemCreated msg)", aOutf);
+ }
+};
+
+class ShmemDestroyed : public IPC::Message {
+ private:
+ typedef Shmem::id_t id_t;
+
+ public:
+ ShmemDestroyed(int32_t routingId, id_t aIPDLId)
+ : IPC::Message(
+ routingId, SHMEM_DESTROYED_MESSAGE_TYPE, 0,
+ HeaderFlags(NOT_NESTED, NORMAL_PRIORITY, COMPRESSION_NONE,
+ LAZY_SEND, NOT_CONSTRUCTOR, ASYNC, NOT_REPLY)) {
+ IPC::MessageWriter writer(*this);
+ IPC::WriteParam(&writer, aIPDLId);
+ }
+};
+
+static SharedMemory* NewSegment() { return new SharedMemoryBasic; }
+
+static already_AddRefed<SharedMemory> CreateSegment(size_t aNBytes,
+ size_t aExtraSize) {
+ RefPtr<SharedMemory> segment = NewSegment();
+ if (!segment) {
+ return nullptr;
+ }
+ size_t size = SharedMemory::PageAlignedSize(aNBytes + aExtraSize);
+ if (!segment->Create(size) || !segment->Map(size)) {
+ return nullptr;
+ }
+ return segment.forget();
+}
+
+static already_AddRefed<SharedMemory> ReadSegment(
+ const IPC::Message& aDescriptor, Shmem::id_t* aId, size_t* aNBytes,
+ size_t aExtraSize) {
+ if (SHMEM_CREATED_MESSAGE_TYPE != aDescriptor.type()) {
+ NS_ERROR("expected 'shmem created' message");
+ return nullptr;
+ }
+ IPC::MessageReader reader(aDescriptor);
+ if (!ShmemCreated::ReadInfo(&reader, aId, aNBytes)) {
+ return nullptr;
+ }
+ RefPtr<SharedMemory> segment = NewSegment();
+ if (!segment) {
+ return nullptr;
+ }
+ if (!segment->ReadHandle(&reader)) {
+ NS_ERROR("trying to open invalid handle");
+ return nullptr;
+ }
+ reader.EndRead();
+ size_t size = SharedMemory::PageAlignedSize(*aNBytes + aExtraSize);
+ if (!segment->Map(size)) {
+ return nullptr;
+ }
+ // close the handle to the segment after it is mapped
+ segment->CloseHandle();
+ return segment.forget();
+}
+
+static void DestroySegment(SharedMemory* aSegment) {
+ // the SharedMemory dtor closes and unmaps the actual OS shmem segment
+ if (aSegment) {
+ aSegment->Release();
+ }
+}
+
+#if defined(DEBUG)
+
+static const char sMagic[] =
+ "This little piggy went to market.\n"
+ "This little piggy stayed at home.\n"
+ "This little piggy has roast beef,\n"
+ "This little piggy had none.\n"
+ "And this little piggy cried \"Wee! Wee! Wee!\" all the way home";
+
+struct Header {
+ // Don't use size_t or bool here because their size depends on the
+ // architecture.
+ uint32_t mSize;
+ uint32_t mUnsafe;
+ char mMagic[sizeof(sMagic)];
+};
+
+static void GetSections(Shmem::SharedMemory* aSegment, Header** aHeader,
+ char** aFrontSentinel, char** aData,
+ char** aBackSentinel) {
+ MOZ_ASSERT(aSegment && aFrontSentinel && aData && aBackSentinel,
+ "null param(s)");
+
+ *aFrontSentinel = reinterpret_cast<char*>(aSegment->memory());
+ MOZ_ASSERT(*aFrontSentinel, "null memory()");
+
+ *aHeader = reinterpret_cast<Header*>(*aFrontSentinel);
+
+ size_t pageSize = Shmem::SharedMemory::SystemPageSize();
+ *aData = *aFrontSentinel + pageSize;
+
+ *aBackSentinel = *aFrontSentinel + aSegment->Size() - pageSize;
+}
+
+static Header* GetHeader(Shmem::SharedMemory* aSegment) {
+ Header* header;
+ char* dontcare;
+ GetSections(aSegment, &header, &dontcare, &dontcare, &dontcare);
+ return header;
+}
+
+static void Protect(SharedMemory* aSegment) {
+ MOZ_ASSERT(aSegment, "null segment");
+ aSegment->Protect(reinterpret_cast<char*>(aSegment->memory()),
+ aSegment->Size(), RightsNone);
+}
+
+static void Unprotect(SharedMemory* aSegment) {
+ MOZ_ASSERT(aSegment, "null segment");
+ aSegment->Protect(reinterpret_cast<char*>(aSegment->memory()),
+ aSegment->Size(), RightsRead | RightsWrite);
+}
+
+//
+// In debug builds, we specially allocate shmem segments. The layout
+// is as follows
+//
+// Page 0: "front sentinel"
+// size of mapping
+// magic bytes
+// Page 1 through n-1:
+// user data
+// Page n: "back sentinel"
+// [nothing]
+//
+// The mapping can be in one of the following states, wrt to the
+// current process.
+//
+// State "unmapped": all pages are mapped with no access rights.
+//
+// State "mapping": all pages are mapped with read/write access.
+//
+// State "mapped": the front and back sentinels are mapped with no
+// access rights, and all the other pages are mapped with
+// read/write access.
+//
+// When a SharedMemory segment is first allocated, it starts out in
+// the "mapping" state for the process that allocates the segment, and
+// in the "unmapped" state for the other process. The allocating
+// process will then create a Shmem, which takes the segment into the
+// "mapped" state, where it can be accessed by clients.
+//
+// When a Shmem is sent to another process in an IPDL message, the
+// segment transitions into the "unmapped" state for the sending
+// process, and into the "mapping" state for the receiving process.
+// The receiving process will then create a Shmem from the underlying
+// segment, and take the segment into the "mapped" state.
+//
+// In the "mapping" state, we use the front sentinel to verify the
+// integrity of the shmem segment. If valid, it has a size_t
+// containing the number of bytes the user allocated followed by the
+// magic bytes above.
+//
+// In the "mapped" state, the front and back sentinels have no access
+// rights. They act as guards against buffer overflows and underflows
+// in client code; if clients touch a sentinel, they die with SIGSEGV.
+//
+// The "unmapped" state is used to enforce single-owner semantics of
+// the shmem segment. If a process other than the current owner tries
+// to touch the segment, it dies with SIGSEGV.
+//
+
+Shmem::Shmem(SharedMemory* aSegment, id_t aId)
+ : mSegment(aSegment), mData(nullptr), mSize(0) {
+ MOZ_ASSERT(mSegment, "null segment");
+ MOZ_ASSERT(aId != 0, "invalid ID");
+
+ Unprotect(mSegment);
+
+ Header* header;
+ char* frontSentinel;
+ char* data;
+ char* backSentinel;
+ GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel);
+
+ // do a quick validity check to avoid weird-looking crashes in libc
+ char check = *frontSentinel;
+ (void)check;
+
+ MOZ_ASSERT(!strncmp(header->mMagic, sMagic, sizeof(sMagic)),
+ "invalid segment");
+ mSize = static_cast<size_t>(header->mSize);
+
+ size_t pageSize = SharedMemory::SystemPageSize();
+ MOZ_ASSERT(mSegment->Size() - (2 * pageSize) >= mSize,
+ "illegal size in shared memory segment");
+
+ // transition into the "mapped" state by protecting the front and
+ // back sentinels (which guard against buffer under/overflows)
+ mSegment->Protect(frontSentinel, pageSize, RightsNone);
+ mSegment->Protect(backSentinel, pageSize, RightsNone);
+
+ // don't set these until we know they're valid
+ mData = data;
+ mId = aId;
+}
+
+void Shmem::AssertInvariants() const {
+ MOZ_ASSERT(mSegment, "null segment");
+ MOZ_ASSERT(mData, "null data pointer");
+ MOZ_ASSERT(mSize > 0, "invalid size");
+ // if the segment isn't owned by the current process, these will
+ // trigger SIGSEGV
+ char checkMappingFront = *reinterpret_cast<char*>(mData);
+ char checkMappingBack = *(reinterpret_cast<char*>(mData) + mSize - 1);
+
+ // avoid "unused" warnings for these variables:
+ Unused << checkMappingFront;
+ Unused << checkMappingBack;
+}
+
+void Shmem::RevokeRights() {
+ AssertInvariants();
+
+ size_t pageSize = SharedMemory::SystemPageSize();
+ Header* header = GetHeader(mSegment);
+
+ // Open this up for reading temporarily
+ mSegment->Protect(reinterpret_cast<char*>(header), pageSize, RightsRead);
+
+ if (!header->mUnsafe) {
+ Protect(mSegment);
+ } else {
+ mSegment->Protect(reinterpret_cast<char*>(header), pageSize, RightsNone);
+ }
+}
+
+// static
+already_AddRefed<Shmem::SharedMemory> Shmem::Alloc(size_t aNBytes, bool aUnsafe,
+ bool aProtect) {
+ NS_ASSERTION(aNBytes <= UINT32_MAX, "Will truncate shmem segment size!");
+ MOZ_ASSERT(!aProtect || !aUnsafe, "protect => !unsafe");
+
+ size_t pageSize = SharedMemory::SystemPageSize();
+ // |2*pageSize| is for the front and back sentinel
+ RefPtr<SharedMemory> segment = CreateSegment(aNBytes, 2 * pageSize);
+ if (!segment) {
+ return nullptr;
+ }
+
+ Header* header;
+ char* frontSentinel;
+ char* data;
+ char* backSentinel;
+ GetSections(segment, &header, &frontSentinel, &data, &backSentinel);
+
+ // initialize the segment with Shmem-internal information
+
+ // NB: this can't be a static assert because technically pageSize
+ // isn't known at compile time, event though in practice it's always
+ // going to be 4KiB
+ MOZ_ASSERT(sizeof(Header) <= pageSize, "Shmem::Header has gotten too big");
+ memcpy(header->mMagic, sMagic, sizeof(sMagic));
+ header->mSize = static_cast<uint32_t>(aNBytes);
+ header->mUnsafe = aUnsafe;
+
+ if (aProtect) Protect(segment);
+
+ return segment.forget();
+}
+
+// static
+already_AddRefed<Shmem::SharedMemory> Shmem::OpenExisting(
+ const IPC::Message& aDescriptor, id_t* aId, bool aProtect) {
+ size_t size;
+ size_t pageSize = SharedMemory::SystemPageSize();
+ // |2*pageSize| is for the front and back sentinels
+ RefPtr<SharedMemory> segment =
+ ReadSegment(aDescriptor, aId, &size, 2 * pageSize);
+ if (!segment) {
+ return nullptr;
+ }
+
+ Header* header = GetHeader(segment);
+
+ if (size != header->mSize) {
+ // Deallocation should zero out the header, so check for that.
+ if (header->mSize || header->mUnsafe || header->mMagic[0] ||
+ memcmp(header->mMagic, &header->mMagic[1],
+ sizeof(header->mMagic) - 1)) {
+ NS_ERROR("Wrong size for this Shmem!");
+ } else {
+ NS_WARNING("Shmem was deallocated");
+ }
+ return nullptr;
+ }
+
+ // The caller of this function may not know whether the segment is
+ // unsafe or not
+ if (!header->mUnsafe && aProtect) Protect(segment);
+
+ return segment.forget();
+}
+
+// static
+void Shmem::Dealloc(SharedMemory* aSegment) {
+ if (!aSegment) return;
+
+ size_t pageSize = SharedMemory::SystemPageSize();
+ Header* header;
+ char* frontSentinel;
+ char* data;
+ char* backSentinel;
+ GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel);
+
+ aSegment->Protect(frontSentinel, pageSize, RightsWrite | RightsRead);
+ memset(header->mMagic, 0, sizeof(sMagic));
+ header->mSize = 0;
+ header->mUnsafe = false; // make it "safe" so as to catch errors
+
+ DestroySegment(aSegment);
+}
+
+#else // !defined(DEBUG)
+
+Shmem::Shmem(SharedMemory* aSegment, id_t aId)
+ : mSegment(aSegment), mData(aSegment->memory()), mSize(0), mId(aId) {
+ mSize = static_cast<size_t>(*PtrToSize(mSegment));
+ MOZ_RELEASE_ASSERT(mSegment->Size() - sizeof(uint32_t) >= mSize,
+ "illegal size in shared memory segment");
+}
+
+// static
+already_AddRefed<Shmem::SharedMemory> Shmem::Alloc(size_t aNBytes,
+ bool /*unused*/,
+ bool /*unused*/) {
+ RefPtr<SharedMemory> segment = CreateSegment(aNBytes, sizeof(uint32_t));
+ if (!segment) {
+ return nullptr;
+ }
+
+ *PtrToSize(segment) = static_cast<uint32_t>(aNBytes);
+
+ return segment.forget();
+}
+
+// static
+already_AddRefed<Shmem::SharedMemory> Shmem::OpenExisting(
+ const IPC::Message& aDescriptor, id_t* aId, bool /*unused*/) {
+ size_t size;
+ RefPtr<SharedMemory> segment =
+ ReadSegment(aDescriptor, aId, &size, sizeof(uint32_t));
+ if (!segment) {
+ return nullptr;
+ }
+
+ // this is the only validity check done in non-DEBUG builds
+ if (size != static_cast<size_t>(*PtrToSize(segment))) {
+ return nullptr;
+ }
+
+ return segment.forget();
+}
+
+// static
+void Shmem::Dealloc(SharedMemory* aSegment) { DestroySegment(aSegment); }
+
+#endif // if defined(DEBUG)
+
+UniquePtr<IPC::Message> Shmem::MkCreatedMessage(int32_t routingId) {
+ AssertInvariants();
+
+ auto msg = MakeUnique<ShmemCreated>(routingId, mId, mSize);
+ IPC::MessageWriter writer(*msg);
+ if (!mSegment->WriteHandle(&writer)) {
+ return nullptr;
+ }
+ // close the handle to the segment after it is shared
+ mSegment->CloseHandle();
+ return msg;
+}
+
+UniquePtr<IPC::Message> Shmem::MkDestroyedMessage(int32_t routingId) {
+ AssertInvariants();
+ return MakeUnique<ShmemDestroyed>(routingId, mId);
+}
+
+void IPDLParamTraits<Shmem>::Write(IPC::MessageWriter* aWriter,
+ IProtocol* aActor, Shmem&& aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.mId);
+
+ aParam.RevokeRights();
+ aParam.forget();
+}
+
+bool IPDLParamTraits<Shmem>::Read(IPC::MessageReader* aReader,
+ IProtocol* aActor, paramType* aResult) {
+ paramType::id_t id;
+ if (!ReadIPDLParam(aReader, aActor, &id)) {
+ return false;
+ }
+
+ Shmem::SharedMemory* rawmem = aActor->LookupSharedMemory(id);
+ if (rawmem) {
+ *aResult = Shmem(rawmem, id);
+ return true;
+ }
+ *aResult = Shmem();
+ return true;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/Shmem.h b/ipc/glue/Shmem.h
new file mode 100644
index 0000000000..3b726f66d9
--- /dev/null
+++ b/ipc/glue/Shmem.h
@@ -0,0 +1,205 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_Shmem_h
+#define mozilla_ipc_Shmem_h
+
+#include "mozilla/Attributes.h"
+
+#include "base/basictypes.h"
+#include "base/process.h"
+
+#include "nscore.h"
+#include "nsDebug.h"
+
+#include "mozilla/ipc/SharedMemory.h"
+#include "mozilla/Range.h"
+#include "mozilla/UniquePtr.h"
+
+/**
+ * |Shmem| is one agent in the IPDL shared memory scheme. The way it
+ works is essentially
+ *
+ * (1) C++ code calls, say, |parentActor->AllocShmem(size)|
+
+ * (2) IPDL-generated code creates a |mozilla::ipc::SharedMemory|
+ * wrapping the bare OS shmem primitives. The code then adds the new
+ * SharedMemory to the set of shmem segments being managed by IPDL.
+ *
+ * (3) IPDL-generated code "shares" the new SharedMemory to the child
+ * process, and then sends a special asynchronous IPC message to the
+ * child notifying it of the creation of the segment. (What this
+ * means is OS specific.)
+ *
+ * (4a) The child receives the special IPC message, and using the
+ * |SharedMemory{Basic}::Handle| it was passed, creates a
+ * |mozilla::ipc::SharedMemory| in the child
+ * process.
+ *
+ * (4b) After sending the "shmem-created" IPC message, IPDL-generated
+ * code in the parent returns a |mozilla::ipc::Shmem| back to the C++
+ * caller of |parentActor->AllocShmem()|. The |Shmem| is a "weak
+ * reference" to the underlying |SharedMemory|, which is managed by
+ * IPDL-generated code. C++ consumers of |Shmem| can't get at the
+ * underlying |SharedMemory|.
+ *
+ * If parent code wants to give access rights to the Shmem to the
+ * child, it does so by sending its |Shmem| to the child, in an IPDL
+ * message. The parent's |Shmem| then "dies", i.e. becomes
+ * inaccessible. This process could be compared to passing a
+ * "shmem-access baton" between parent and child.
+ */
+
+namespace mozilla {
+namespace layers {
+class ShadowLayerForwarder;
+} // namespace layers
+
+namespace ipc {
+
+class IProtocol;
+class IToplevelProtocol;
+
+template <typename P>
+struct IPDLParamTraits;
+
+class Shmem final {
+ friend struct IPDLParamTraits<mozilla::ipc::Shmem>;
+ friend class mozilla::ipc::IProtocol;
+ friend class mozilla::ipc::IToplevelProtocol;
+#ifdef DEBUG
+ // For ShadowLayerForwarder::CheckSurfaceDescriptor
+ friend class mozilla::layers::ShadowLayerForwarder;
+#endif
+
+ public:
+ typedef int32_t id_t;
+ // Low-level wrapper around platform shmem primitives.
+ typedef mozilla::ipc::SharedMemory SharedMemory;
+
+ Shmem() : mSegment(nullptr), mData(nullptr), mSize(0), mId(0) {}
+
+ Shmem(const Shmem& aOther) = default;
+
+ ~Shmem() {
+ // Shmem only holds a "weak ref" to the actual segment, which is
+ // owned by IPDL. So there's nothing interesting to be done here
+ forget();
+ }
+
+ Shmem& operator=(const Shmem& aRhs) = default;
+
+ bool operator==(const Shmem& aRhs) const { return mSegment == aRhs.mSegment; }
+
+ // Returns whether this Shmem is writable by you, and thus whether you can
+ // transfer writability to another actor.
+ bool IsWritable() const { return mSegment != nullptr; }
+
+ // Returns whether this Shmem is readable by you, and thus whether you can
+ // transfer readability to another actor.
+ bool IsReadable() const { return mSegment != nullptr; }
+
+ // Return a pointer to the user-visible data segment.
+ template <typename T>
+ T* get() const {
+ AssertInvariants();
+ AssertAligned<T>();
+
+ return reinterpret_cast<T*>(mData);
+ }
+
+ // Return the size of the segment as requested when this shmem
+ // segment was allocated, in units of T. The underlying mapping may
+ // actually be larger because of page alignment and private data,
+ // but this isn't exposed to clients.
+ template <typename T>
+ size_t Size() const {
+ AssertInvariants();
+ AssertAligned<T>();
+
+ return mSize / sizeof(T);
+ }
+
+ template <typename T>
+ Range<T> Range() const {
+ return {get<T>(), Size<T>()};
+ }
+
+ private:
+ // These shouldn't be used directly, use the IPDL interface instead.
+
+ Shmem(SharedMemory* aSegment, id_t aId);
+
+ id_t Id() const { return mId; }
+
+ SharedMemory* Segment() const { return mSegment; }
+
+#ifndef DEBUG
+ void RevokeRights() {}
+#else
+ void RevokeRights();
+#endif
+
+ void forget() {
+ mSegment = nullptr;
+ mData = nullptr;
+ mSize = 0;
+ mId = 0;
+ }
+
+ static already_AddRefed<Shmem::SharedMemory> Alloc(size_t aNBytes,
+ bool aUnsafe,
+ bool aProtect = false);
+
+ // Prepare this to be shared with another process. Return an IPC message that
+ // contains enough information for the other process to map this segment in
+ // OpenExisting() below. Return a new message if successful (owned by the
+ // caller), nullptr if not.
+ UniquePtr<IPC::Message> MkCreatedMessage(int32_t routingId);
+
+ // Stop sharing this with another process. Return an IPC message that
+ // contains enough information for the other process to unmap this
+ // segment. Return a new message if successful (owned by the
+ // caller), nullptr if not.
+ UniquePtr<IPC::Message> MkDestroyedMessage(int32_t routingId);
+
+ // Return a SharedMemory instance in this process using the descriptor shared
+ // to us by the process that created the underlying OS shmem resource. The
+ // contents of the descriptor depend on the type of SharedMemory that was
+ // passed to us.
+ static already_AddRefed<SharedMemory> OpenExisting(
+ const IPC::Message& aDescriptor, id_t* aId, bool aProtect = false);
+
+ static void Dealloc(SharedMemory* aSegment);
+
+ template <typename T>
+ void AssertAligned() const {
+ if (0 != (mSize % sizeof(T))) MOZ_CRASH("shmem is not T-aligned");
+ }
+
+#if !defined(DEBUG)
+ void AssertInvariants() const {}
+
+ static uint32_t* PtrToSize(SharedMemory* aSegment) {
+ char* endOfSegment =
+ reinterpret_cast<char*>(aSegment->memory()) + aSegment->Size();
+ return reinterpret_cast<uint32_t*>(endOfSegment - sizeof(uint32_t));
+ }
+
+#else
+ void AssertInvariants() const;
+#endif
+
+ RefPtr<SharedMemory> mSegment;
+ void* mData;
+ size_t mSize;
+ id_t mId;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // ifndef mozilla_ipc_Shmem_h
diff --git a/ipc/glue/ShmemMessageUtils.h b/ipc/glue/ShmemMessageUtils.h
new file mode 100644
index 0000000000..c0f8c40215
--- /dev/null
+++ b/ipc/glue/ShmemMessageUtils.h
@@ -0,0 +1,30 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_ShmemMessageUtils_h
+#define mozilla_ipc_ShmemMessageUtils_h
+
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/ipc/IPDLParamTraits.h"
+#include "mozilla/ipc/Shmem.h"
+
+namespace mozilla {
+namespace ipc {
+
+template <>
+struct IPDLParamTraits<Shmem> {
+ typedef Shmem paramType;
+
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ paramType&& aParam);
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult);
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // ifndef mozilla_ipc_ShmemMessageUtils_h
diff --git a/ipc/glue/SideVariant.h b/ipc/glue/SideVariant.h
new file mode 100644
index 0000000000..3082feebde
--- /dev/null
+++ b/ipc/glue/SideVariant.h
@@ -0,0 +1,187 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_SidedVariant_h
+#define mozilla_ipc_SidedVariant_h
+
+#include <variant>
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "ipc/IPCMessageUtils.h"
+
+namespace mozilla {
+namespace ipc {
+
+/**
+ * Helper type used by IPDL structs and unions to hold actor pointers with a
+ * dynamic side.
+ *
+ * When sent over IPC, ParentSide will be used for send/recv on parent actors,
+ * and ChildSide will be used for send/recv on child actors.
+ */
+template <typename ParentSide, typename ChildSide>
+struct SideVariant {
+ public:
+ SideVariant() = default;
+ template <typename U,
+ std::enable_if_t<std::is_convertible_v<U&&, ParentSide>, int> = 0>
+ MOZ_IMPLICIT SideVariant(U&& aParent) : mParent(std::forward<U>(aParent)) {}
+ template <typename U,
+ std::enable_if_t<std::is_convertible_v<U&&, ChildSide>, int> = 0>
+ MOZ_IMPLICIT SideVariant(U&& aChild) : mChild(std::forward<U>(aChild)) {}
+ MOZ_IMPLICIT SideVariant(std::nullptr_t) {}
+
+ MOZ_IMPLICIT SideVariant& operator=(ParentSide aParent) {
+ mParent = aParent;
+ mChild = nullptr;
+ return *this;
+ }
+ MOZ_IMPLICIT SideVariant& operator=(ChildSide aChild) {
+ mChild = aChild;
+ mParent = nullptr;
+ return *this;
+ }
+ MOZ_IMPLICIT SideVariant& operator=(std::nullptr_t) {
+ mChild = nullptr;
+ mParent = nullptr;
+ return *this;
+ }
+
+ MOZ_IMPLICIT operator bool() const { return mParent || mChild; }
+
+ bool IsNull() const { return !operator bool(); }
+ bool IsParent() const { return mParent; }
+ bool IsChild() const { return mChild; }
+
+ ParentSide AsParent() const {
+ MOZ_ASSERT(IsNull() || IsParent());
+ return mParent;
+ }
+ ChildSide AsChild() const {
+ MOZ_ASSERT(IsNull() || IsChild());
+ return mChild;
+ }
+
+ private:
+ // As the values are both pointers, this is the same size as a variant would
+ // be, but has less risk of type confusion, and supports an overall `nullptr`
+ // value which is neither parent nor child.
+ ParentSide mParent = nullptr;
+ ChildSide mChild = nullptr;
+};
+
+} // namespace ipc
+
+// NotNull specialization to expose AsChild and AsParent on the NotNull itself
+// avoiding unnecessary unwrapping.
+template <typename ParentSide, typename ChildSide>
+class NotNull<mozilla::ipc::SideVariant<ParentSide, ChildSide>> {
+ template <typename U>
+ friend constexpr NotNull<U> WrapNotNull(U aBasePtr);
+ template <typename U>
+ friend constexpr NotNull<U> WrapNotNullUnchecked(U aBasePtr);
+ template <typename U>
+ friend class NotNull;
+
+ using BasePtr = mozilla::ipc::SideVariant<ParentSide, ChildSide>;
+
+ BasePtr mBasePtr;
+
+ // This constructor is only used by WrapNotNull() and MakeNotNull<U>().
+ template <typename U>
+ constexpr explicit NotNull(U aBasePtr) : mBasePtr(aBasePtr) {}
+
+ public:
+ // Disallow default construction.
+ NotNull() = delete;
+
+ // Construct/assign from another NotNull with a compatible base pointer type.
+ template <typename U, typename = std::enable_if_t<
+ std::is_convertible_v<const U&, BasePtr>>>
+ constexpr MOZ_IMPLICIT NotNull(const NotNull<U>& aOther)
+ : mBasePtr(aOther.get()) {
+ static_assert(sizeof(BasePtr) == sizeof(NotNull<BasePtr>),
+ "NotNull must have zero space overhead.");
+ static_assert(offsetof(NotNull<BasePtr>, mBasePtr) == 0,
+ "mBasePtr must have zero offset.");
+ }
+
+ template <typename U,
+ typename = std::enable_if_t<std::is_convertible_v<U&&, BasePtr>>>
+ constexpr MOZ_IMPLICIT NotNull(MovingNotNull<U>&& aOther)
+ : mBasePtr(NotNull{std::move(aOther)}) {}
+
+ // Disallow null checks, which are unnecessary for this type.
+ explicit operator bool() const = delete;
+
+ // Explicit conversion to a base pointer. Use only to resolve ambiguity or to
+ // get a castable pointer.
+ constexpr const BasePtr& get() const { return mBasePtr; }
+
+ // Implicit conversion to a base pointer. Preferable to get().
+ constexpr operator const BasePtr&() const { return get(); }
+
+ bool IsParent() const { return get().IsParent(); }
+ bool IsChild() const { return get().IsChild(); }
+
+ NotNull<ParentSide> AsParent() const { return WrapNotNull(get().AsParent()); }
+ NotNull<ChildSide> AsChild() const { return WrapNotNull(get().AsChild()); }
+};
+
+} // namespace mozilla
+
+namespace IPC {
+
+template <typename ParentSide, typename ChildSide>
+struct ParamTraits<mozilla::ipc::SideVariant<ParentSide, ChildSide>> {
+ typedef mozilla::ipc::SideVariant<ParentSide, ChildSide> paramType;
+
+ static void Write(IPC::MessageWriter* aWriter, const paramType& aParam) {
+ if (!aWriter->GetActor()) {
+ aWriter->FatalError("actor required to serialize this type");
+ return;
+ }
+
+ if (aWriter->GetActor()->GetSide() == mozilla::ipc::ParentSide) {
+ if (aParam && !aParam.IsParent()) {
+ aWriter->FatalError("invalid side");
+ return;
+ }
+ WriteParam(aWriter, aParam.AsParent());
+ } else {
+ if (aParam && !aParam.IsChild()) {
+ aWriter->FatalError("invalid side");
+ return;
+ }
+ WriteParam(aWriter, aParam.AsChild());
+ }
+ }
+
+ static ReadResult<paramType> Read(IPC::MessageReader* aReader) {
+ if (!aReader->GetActor()) {
+ aReader->FatalError("actor required to deserialize this type");
+ return {};
+ }
+
+ if (aReader->GetActor()->GetSide() == mozilla::ipc::ParentSide) {
+ auto parentSide = ReadParam<ParentSide>(aReader);
+ if (!parentSide) {
+ return {};
+ }
+ return std::move(*parentSide);
+ }
+ auto childSide = ReadParam<ChildSide>(aReader);
+ if (!childSide) {
+ return {};
+ }
+ return std::move(*childSide);
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_ipc_SidedVariant_h
diff --git a/ipc/glue/StringUtil.cpp b/ipc/glue/StringUtil.cpp
new file mode 100644
index 0000000000..2eae9a1282
--- /dev/null
+++ b/ipc/glue/StringUtil.cpp
@@ -0,0 +1,88 @@
+/* -*- 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 "base/string_util.h"
+
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/sys_string_conversions.h"
+
+#include "base/string_piece.h"
+#include "base/string_util.h"
+
+#include "build/build_config.h"
+
+// FIXME/cjones: these really only pertain to the linux sys string
+// converters.
+#ifdef WCHAR_T_IS_UTF16
+# define ICONV_WCHAR_T_ENCODING "UTF-16"
+#else
+# define ICONV_WCHAR_T_ENCODING "WCHAR_T"
+#endif
+
+// FIXME/cjones: BIG assumption here that std::string is a good
+// container of UTF8-encoded strings. this is probably wrong, as its
+// API doesn't really make sense for UTF8.
+
+namespace base {
+
+// FIXME/cjones: as its name implies, this function is a hack.
+template <typename FromType, typename ToType>
+ToType GhettoStringConvert(const FromType& in) {
+ // FIXME/cjones: assumes no non-ASCII characters in |in|
+ ToType out;
+ out.resize(in.length());
+ for (int i = 0; i < static_cast<int>(in.length()); ++i)
+ out[i] = static_cast<typename ToType::value_type>(in[i]);
+ return out;
+}
+
+} // namespace base
+
+// Implement functions that were in the chromium ICU library, which
+// we're not taking.
+
+std::string WideToUTF8(const std::wstring& wide) {
+ return base::SysWideToUTF8(wide);
+}
+
+std::wstring UTF8ToWide(const StringPiece& utf8) {
+ return base::SysUTF8ToWide(utf8);
+}
+
+namespace base {
+
+// FIXME/cjones: here we're entirely replacing the linux string
+// converters, and implementing the one that doesn't exist for OS X
+// and Windows.
+
+#if !defined(OS_MACOSX) && !defined(OS_WIN)
+std::string SysWideToUTF8(const std::wstring& wide) {
+ // FIXME/cjones: do this with iconv
+ return GhettoStringConvert<std::wstring, std::string>(wide);
+}
+#endif
+
+#if !defined(OS_MACOSX) && !defined(OS_WIN)
+std::wstring SysUTF8ToWide(const StringPiece& utf8) {
+ // FIXME/cjones: do this with iconv
+ return GhettoStringConvert<StringPiece, std::wstring>(utf8);
+}
+
+std::string SysWideToNativeMB(const std::wstring& wide) {
+ // TODO(evanm): we can't assume Linux is UTF-8.
+ return SysWideToUTF8(wide);
+}
+
+std::wstring SysNativeMBToWide(const StringPiece& native_mb) {
+ // TODO(evanm): we can't assume Linux is UTF-8.
+ return SysUTF8ToWide(native_mb);
+}
+#endif
+
+} // namespace base
diff --git a/ipc/glue/TaintingIPCUtils.h b/ipc/glue/TaintingIPCUtils.h
new file mode 100644
index 0000000000..d4f61d95b5
--- /dev/null
+++ b/ipc/glue/TaintingIPCUtils.h
@@ -0,0 +1,41 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_Tainting_h
+#define mozilla_ipc_Tainting_h
+
+#include "mozilla/Tainting.h"
+
+#include "base/basictypes.h"
+#include "base/process.h"
+
+#include "mozilla/ipc/IPDLParamTraits.h"
+
+namespace mozilla {
+namespace ipc {
+
+template <typename T>
+struct IPDLParamTraits<mozilla::Tainted<T>> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const mozilla::Tainted<T>& aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.mValue);
+ }
+
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ mozilla::Tainted<T>&& aParam) {
+ WriteIPDLParam(aWriter, aActor, std::move(aParam.mValue));
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ mozilla::Tainted<T>* aResult) {
+ return ReadIPDLParam(aReader, aActor, &(aResult->mValue));
+ }
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // ifndef mozilla_ipc_Tainting_h
diff --git a/ipc/glue/TaskFactory.h b/ipc/glue/TaskFactory.h
new file mode 100644
index 0000000000..af63244cf0
--- /dev/null
+++ b/ipc/glue/TaskFactory.h
@@ -0,0 +1,97 @@
+/* -*- 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/. */
+
+#ifndef mozilla_plugins_TaskFactory_h
+#define mozilla_plugins_TaskFactory_h
+
+#include <base/task.h>
+
+#include <utility>
+
+/*
+ * This is based on the ScopedRunnableMethodFactory from
+ * ipc/chromium/src/base/task.h Chromium's factories assert if tasks are created
+ * and run on different threads, which is something we need to do in
+ * PluginModuleParent (hang UI vs. main thread). TaskFactory just provides
+ * cancellable tasks that don't assert this. This version also allows both
+ * ScopedMethod and regular Tasks to be generated by the same Factory object.
+ */
+
+namespace mozilla {
+namespace ipc {
+
+template <class T>
+class TaskFactory : public RevocableStore {
+ private:
+ template <class TaskType>
+ class TaskWrapper : public TaskType {
+ public:
+ template <typename... Args>
+ explicit TaskWrapper(RevocableStore* store, Args&&... args)
+ : TaskType(std::forward<Args>(args)...), revocable_(store) {}
+
+ NS_IMETHOD Run() override {
+ if (!revocable_.revoked()) TaskType::Run();
+ return NS_OK;
+ }
+
+ private:
+ Revocable revocable_;
+ };
+
+ public:
+ explicit TaskFactory(T* object) : object_(object) {}
+
+ template <typename TaskParamType, typename... Args>
+ inline already_AddRefed<TaskParamType> NewTask(Args&&... args) {
+ typedef TaskWrapper<TaskParamType> TaskWrapper;
+ RefPtr<TaskWrapper> task =
+ new TaskWrapper(this, std::forward<Args>(args)...);
+ return task.forget();
+ }
+
+ template <class Method, typename... Args>
+ inline already_AddRefed<Runnable> NewRunnableMethod(Method method,
+ Args&&... args) {
+ typedef decltype(base::MakeTuple(std::forward<Args>(args)...)) ArgTuple;
+ typedef RunnableMethod<Method, ArgTuple> RunnableMethod;
+ typedef TaskWrapper<RunnableMethod> TaskWrapper;
+
+ RefPtr<TaskWrapper> task = new TaskWrapper(
+ this, object_, method, base::MakeTuple(std::forward<Args>(args)...));
+
+ return task.forget();
+ }
+
+ protected:
+ template <class Method, class Params>
+ class RunnableMethod : public Runnable {
+ public:
+ RunnableMethod(T* obj, Method meth, const Params& params)
+ : Runnable("ipc::TaskFactory::RunnableMethod"),
+ obj_(obj),
+ meth_(meth),
+ params_(params) {}
+
+ NS_IMETHOD Run() override {
+ DispatchToMethod(obj_, meth_, params_);
+ return NS_OK;
+ }
+
+ private:
+ T* obj_;
+ Method meth_;
+ Params params_;
+ };
+
+ private:
+ T* object_;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_plugins_TaskFactory_h
diff --git a/ipc/glue/ToplevelActorHolder.h b/ipc/glue/ToplevelActorHolder.h
new file mode 100644
index 0000000000..6e95ef8565
--- /dev/null
+++ b/ipc/glue/ToplevelActorHolder.h
@@ -0,0 +1,45 @@
+/* -*- 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/. */
+
+#ifndef MOZILLA_IPC_TOPLEVELACTORHOLDER_H
+#define MOZILLA_IPC_TOPLEVELACTORHOLDER_H
+
+#include "nsISupports.h"
+
+namespace mozilla::ipc {
+
+// Class to let us close the actor when we're not using it anymore. You
+// should create a single instance of this, and when you have no more
+// references it will be destroyed and will Close() the underlying
+// top-level channel.
+// When you want to send something, you use something like
+// aActor->Actor()->SendFoo()
+
+// You can avoid calling Close() on an un-connected Actor (for example if
+// Bind() fails) by calling RemoveActor();
+template <typename T>
+class ToplevelActorHolder final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_ONEVENTTARGET(ToplevelActorHolder)
+
+ explicit ToplevelActorHolder(T* aActor) : mActor(aActor) {}
+
+ constexpr T* Actor() const { return mActor; }
+ inline void RemoveActor() { mActor = nullptr; }
+
+ private:
+ inline ~ToplevelActorHolder() {
+ if (mActor) {
+ mActor->Close();
+ }
+ }
+
+ RefPtr<T> mActor;
+};
+
+} // namespace mozilla::ipc
+
+#endif // MOZILLA_IPC_TOPLEVELACTORHOLDER_H
diff --git a/ipc/glue/TransportSecurityInfoUtils.cpp b/ipc/glue/TransportSecurityInfoUtils.cpp
new file mode 100644
index 0000000000..9898e2b579
--- /dev/null
+++ b/ipc/glue/TransportSecurityInfoUtils.cpp
@@ -0,0 +1,78 @@
+/* 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 "TransportSecurityInfoUtils.h"
+
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/psm/TransportSecurityInfo.h"
+#include "nsNSSCertificate.h"
+
+namespace IPC {
+
+void ParamTraits<nsITransportSecurityInfo*>::Write(
+ MessageWriter* aWriter, nsITransportSecurityInfo* aParam) {
+ bool nonNull = !!aParam;
+ WriteParam(aWriter, nonNull);
+ if (!nonNull) {
+ return;
+ }
+
+ aParam->SerializeToIPC(aWriter);
+}
+
+bool ParamTraits<nsITransportSecurityInfo*>::Read(
+ MessageReader* aReader, RefPtr<nsITransportSecurityInfo>* aResult) {
+ *aResult = nullptr;
+
+ bool nonNull = false;
+ if (!ReadParam(aReader, &nonNull)) {
+ return false;
+ }
+
+ if (!nonNull) {
+ return true;
+ }
+
+ if (!mozilla::psm::TransportSecurityInfo::DeserializeFromIPC(aReader,
+ aResult)) {
+ return false;
+ }
+
+ return true;
+}
+
+void ParamTraits<nsIX509Cert*>::Write(MessageWriter* aWriter,
+ nsIX509Cert* aParam) {
+ bool nonNull = !!aParam;
+ WriteParam(aWriter, nonNull);
+ if (!nonNull) {
+ return;
+ }
+
+ aParam->SerializeToIPC(aWriter);
+}
+
+bool ParamTraits<nsIX509Cert*>::Read(MessageReader* aReader,
+ RefPtr<nsIX509Cert>* aResult) {
+ *aResult = nullptr;
+
+ bool nonNull = false;
+ if (!ReadParam(aReader, &nonNull)) {
+ return false;
+ }
+
+ if (!nonNull) {
+ return true;
+ }
+
+ RefPtr<nsIX509Cert> cert = new nsNSSCertificate();
+ if (!cert->DeserializeFromIPC(aReader)) {
+ return false;
+ }
+
+ *aResult = std::move(cert);
+ return true;
+}
+
+} // namespace IPC
diff --git a/ipc/glue/TransportSecurityInfoUtils.h b/ipc/glue/TransportSecurityInfoUtils.h
new file mode 100644
index 0000000000..009db8e162
--- /dev/null
+++ b/ipc/glue/TransportSecurityInfoUtils.h
@@ -0,0 +1,43 @@
+/* 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/. */
+
+#ifndef mozilla_ipc_TransportSecurityInfoUtils_h
+#define mozilla_ipc_TransportSecurityInfoUtils_h
+
+#include "ipc/EnumSerializer.h"
+#include "mozilla/RefPtr.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIX509Cert.h"
+
+class MessageReader;
+class MessageWriter;
+
+namespace IPC {
+
+template <typename>
+struct ParamTraits;
+
+template <>
+struct ParamTraits<nsITransportSecurityInfo*> {
+ static void Write(MessageWriter* aWriter, nsITransportSecurityInfo* aParam);
+ static bool Read(MessageReader* aReader,
+ RefPtr<nsITransportSecurityInfo>* aResult);
+};
+
+template <>
+struct ParamTraits<nsIX509Cert*> {
+ static void Write(MessageWriter* aWriter, nsIX509Cert* aCert);
+ static bool Read(MessageReader* aReader, RefPtr<nsIX509Cert>* aResult);
+};
+
+template <>
+struct ParamTraits<nsITransportSecurityInfo::OverridableErrorCategory>
+ : public ContiguousEnumSerializerInclusive<
+ nsITransportSecurityInfo::OverridableErrorCategory,
+ nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET,
+ nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME> {};
+
+} // namespace IPC
+
+#endif // mozilla_ipc_TransportSecurityInfoUtils_h
diff --git a/ipc/glue/URIParams.ipdlh b/ipc/glue/URIParams.ipdlh
new file mode 100644
index 0000000000..99b18afd0f
--- /dev/null
+++ b/ipc/glue/URIParams.ipdlh
@@ -0,0 +1,117 @@
+/* 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/. */
+
+
+using struct mozilla::void_t from "mozilla/ipc/IPCCore.h";
+
+include PBackgroundSharedTypes;
+
+namespace mozilla {
+namespace ipc {
+
+struct SimpleURIParams
+{
+ nsCString scheme;
+ nsCString path;
+ nsCString ref;
+ nsCString query;
+};
+
+struct DefaultURIParams
+{
+ nsCString spec;
+};
+
+struct StandardURLSegment
+{
+ uint32_t position;
+ int32_t length;
+};
+
+struct StandardURLParams
+{
+ uint32_t urlType;
+ int32_t port;
+ int32_t defaultPort;
+ nsCString spec;
+ StandardURLSegment scheme;
+ StandardURLSegment authority;
+ StandardURLSegment username;
+ StandardURLSegment password;
+ StandardURLSegment host;
+ StandardURLSegment path;
+ StandardURLSegment filePath;
+ StandardURLSegment directory;
+ StandardURLSegment baseName;
+ StandardURLSegment extension;
+ StandardURLSegment query;
+ StandardURLSegment ref;
+ bool supportsFileURL;
+ bool isSubstituting;
+};
+
+struct JARURIParams
+{
+ URIParams jarFile;
+ URIParams jarEntry;
+ nsCString charset;
+};
+
+struct IconURIParams
+{
+ URIParams? uri;
+ uint32_t size;
+ nsCString contentType;
+ nsCString fileName;
+ nsCString stockIcon;
+ int32_t iconSize;
+ int32_t iconState;
+};
+
+struct HostObjectURIParams
+{
+ SimpleURIParams simpleParams;
+ bool revoked;
+};
+
+union URIParams
+{
+ SimpleURIParams;
+ StandardURLParams;
+ JARURIParams;
+ IconURIParams;
+ JSURIParams;
+ SimpleNestedURIParams;
+ HostObjectURIParams;
+ DefaultURIParams;
+ NestedAboutURIParams;
+ SubstitutingJARURIParams;
+};
+
+struct JSURIParams
+{
+ SimpleURIParams simpleParams;
+ URIParams? baseURI;
+};
+
+struct SimpleNestedURIParams
+{
+ SimpleURIParams simpleParams;
+ URIParams innerURI;
+};
+
+struct NestedAboutURIParams
+{
+ SimpleNestedURIParams nestedParams;
+ URIParams? baseURI;
+};
+
+struct SubstitutingJARURIParams
+{
+ URIParams source;
+ JARURIParams resolved;
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/URIUtils.cpp b/ipc/glue/URIUtils.cpp
new file mode 100644
index 0000000000..690509cecc
--- /dev/null
+++ b/ipc/glue/URIUtils.cpp
@@ -0,0 +1,140 @@
+/* -*- 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 "URIUtils.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/BlobURL.h"
+#include "mozilla/net/DefaultURI.h"
+#include "mozilla/net/SubstitutingJARURI.h"
+#include "mozilla/net/SubstitutingURL.h"
+#include "nsAboutProtocolHandler.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDebug.h"
+#include "nsID.h"
+#include "nsJARURI.h"
+#include "nsIIconURI.h"
+#include "nsJSProtocolHandler.h"
+#include "nsNetCID.h"
+#include "nsSimpleNestedURI.h"
+#include "nsThreadUtils.h"
+#include "nsIURIMutator.h"
+
+using namespace mozilla::ipc;
+using mozilla::ArrayLength;
+
+namespace {
+
+NS_DEFINE_CID(kSimpleURIMutatorCID, NS_SIMPLEURIMUTATOR_CID);
+NS_DEFINE_CID(kStandardURLMutatorCID, NS_STANDARDURLMUTATOR_CID);
+NS_DEFINE_CID(kJARURIMutatorCID, NS_JARURIMUTATOR_CID);
+NS_DEFINE_CID(kIconURIMutatorCID, NS_MOZICONURIMUTATOR_CID);
+
+} // namespace
+
+namespace mozilla {
+namespace ipc {
+
+void SerializeURI(nsIURI* aURI, URIParams& aParams) {
+ MOZ_ASSERT(aURI);
+
+ aURI->Serialize(aParams);
+ if (aParams.type() == URIParams::T__None) {
+ MOZ_CRASH("Serialize failed!");
+ }
+}
+
+void SerializeURI(nsIURI* aURI, Maybe<URIParams>& aParams) {
+ if (aURI) {
+ URIParams params;
+ SerializeURI(aURI, params);
+ aParams = Some(std::move(params));
+ } else {
+ aParams = Nothing();
+ }
+}
+
+already_AddRefed<nsIURI> DeserializeURI(const URIParams& aParams) {
+ nsCOMPtr<nsIURIMutator> mutator;
+
+ switch (aParams.type()) {
+ case URIParams::TSimpleURIParams:
+ mutator = do_CreateInstance(kSimpleURIMutatorCID);
+ break;
+
+ case URIParams::TStandardURLParams:
+ if (aParams.get_StandardURLParams().isSubstituting()) {
+ mutator = new net::SubstitutingURL::Mutator();
+ } else {
+ mutator = do_CreateInstance(kStandardURLMutatorCID);
+ }
+ break;
+
+ case URIParams::TJARURIParams:
+ mutator = do_CreateInstance(kJARURIMutatorCID);
+ break;
+
+ case URIParams::TJSURIParams:
+ mutator = new nsJSURI::Mutator();
+ break;
+
+ case URIParams::TIconURIParams:
+ mutator = do_CreateInstance(kIconURIMutatorCID);
+ break;
+
+ case URIParams::TSimpleNestedURIParams:
+ mutator = new net::nsSimpleNestedURI::Mutator();
+ break;
+
+ case URIParams::THostObjectURIParams:
+ mutator = new mozilla::dom::BlobURL::Mutator();
+ break;
+
+ case URIParams::TDefaultURIParams:
+ mutator = new mozilla::net::DefaultURI::Mutator();
+ break;
+
+ case URIParams::TNestedAboutURIParams:
+ mutator = new net::nsNestedAboutURI::Mutator();
+ break;
+
+ case URIParams::TSubstitutingJARURIParams:
+ mutator = new net::SubstitutingJARURI::Mutator();
+ break;
+
+ default:
+ MOZ_CRASH("Unknown params!");
+ }
+
+ MOZ_ASSERT(mutator);
+
+ nsresult rv = mutator->Deserialize(aParams);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false, "Deserialize failed!");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ DebugOnly<nsresult> rv2 = mutator->Finalize(getter_AddRefs(uri));
+ MOZ_ASSERT(uri);
+ MOZ_ASSERT(NS_SUCCEEDED(rv2));
+
+ return uri.forget();
+}
+
+already_AddRefed<nsIURI> DeserializeURI(const Maybe<URIParams>& aParams) {
+ nsCOMPtr<nsIURI> uri;
+
+ if (aParams.isSome()) {
+ uri = DeserializeURI(aParams.ref());
+ }
+
+ return uri.forget();
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/URIUtils.h b/ipc/glue/URIUtils.h
new file mode 100644
index 0000000000..7cb8687bbe
--- /dev/null
+++ b/ipc/glue/URIUtils.h
@@ -0,0 +1,49 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_URIUtils_h
+#define mozilla_ipc_URIUtils_h
+
+#include "mozilla/ipc/URIParams.h"
+#include "mozilla/ipc/IPDLParamTraits.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+
+namespace mozilla {
+namespace ipc {
+
+void SerializeURI(nsIURI* aURI, URIParams& aParams);
+
+void SerializeURI(nsIURI* aURI, Maybe<URIParams>& aParams);
+
+already_AddRefed<nsIURI> DeserializeURI(const URIParams& aParams);
+
+already_AddRefed<nsIURI> DeserializeURI(const Maybe<URIParams>& aParams);
+
+template <>
+struct IPDLParamTraits<nsIURI*> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ nsIURI* aParam) {
+ Maybe<URIParams> params;
+ SerializeURI(aParam, params);
+ WriteIPDLParam(aWriter, aActor, params);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ RefPtr<nsIURI>* aResult) {
+ Maybe<URIParams> params;
+ if (!ReadIPDLParam(aReader, aActor, &params)) {
+ return false;
+ }
+ *aResult = DeserializeURI(params);
+ return true;
+ }
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_URIUtils_h
diff --git a/ipc/glue/UtilityAudioDecoder.cpp b/ipc/glue/UtilityAudioDecoder.cpp
new file mode 100644
index 0000000000..0b28fd601e
--- /dev/null
+++ b/ipc/glue/UtilityAudioDecoder.cpp
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 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 "mozilla/ProcInfo.h"
+#include "mozilla/ipc/UtilityAudioDecoder.h"
+#include "mozilla/ipc/UtilityProcessChild.h"
+
+namespace mozilla::ipc {
+
+UtilityActorName GetAudioActorName(const SandboxingKind aSandbox) {
+ switch (aSandbox) {
+ case GENERIC_UTILITY:
+ return UtilityActorName::AudioDecoder_Generic;
+#ifdef MOZ_APPLEMEDIA
+ case UTILITY_AUDIO_DECODING_APPLE_MEDIA:
+ return UtilityActorName::AudioDecoder_AppleMedia;
+#endif
+#ifdef XP_WIN
+ case UTILITY_AUDIO_DECODING_WMF:
+ return UtilityActorName::AudioDecoder_WMF;
+#endif
+#ifdef MOZ_WMF_MEDIA_ENGINE
+ case MF_MEDIA_ENGINE_CDM:
+ return UtilityActorName::MfMediaEngineCDM;
+#endif
+ default:
+ MOZ_CRASH("Unexpected mSandbox for GetActorName()");
+ }
+}
+
+nsCString GetChildAudioActorName() {
+ RefPtr<ipc::UtilityProcessChild> s = ipc::UtilityProcessChild::Get();
+ MOZ_ASSERT(s, "Has UtilityProcessChild");
+ return nsCString(dom::WebIDLUtilityActorNameValues::GetString(
+ GetAudioActorName(s->mSandbox)));
+}
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/UtilityAudioDecoder.h b/ipc/glue/UtilityAudioDecoder.h
new file mode 100644
index 0000000000..4fed9aab64
--- /dev/null
+++ b/ipc/glue/UtilityAudioDecoder.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 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/. */
+#ifndef _include_ipc_glue_UtilityAudioDecoder_h__
+#define _include_ipc_glue_UtilityAudioDecoder_h__
+
+#include "mozilla/dom/ChromeUtilsBinding.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+
+namespace mozilla::ipc {
+
+using UtilityActorName = mozilla::dom::WebIDLUtilityActorName;
+
+UtilityActorName GetAudioActorName(const SandboxingKind aSandbox);
+nsCString GetChildAudioActorName();
+
+} // namespace mozilla::ipc
+
+#endif // _include_ipc_glue_UtilityAudioDecoder_h__
diff --git a/ipc/glue/UtilityAudioDecoderChild.cpp b/ipc/glue/UtilityAudioDecoderChild.cpp
new file mode 100644
index 0000000000..a63775390d
--- /dev/null
+++ b/ipc/glue/UtilityAudioDecoderChild.cpp
@@ -0,0 +1,176 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 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 "UtilityAudioDecoderChild.h"
+
+#include "base/basictypes.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/dom/ContentParent.h"
+
+#ifdef MOZ_WMF_MEDIA_ENGINE
+# include "mozilla/StaticPrefs_media.h"
+# include "mozilla/gfx/GPUProcessManager.h"
+# include "mozilla/gfx/gfxVars.h"
+# include "mozilla/ipc/UtilityProcessManager.h"
+# include "mozilla/layers/PVideoBridge.h"
+# include "mozilla/layers/VideoBridgeUtils.h"
+#endif
+
+namespace mozilla::ipc {
+
+NS_IMETHODIMP UtilityAudioDecoderChildShutdownObserver::Observe(
+ nsISupports* aSubject, const char* aTopic, const char16_t* aData) {
+ MOZ_ASSERT(strcmp(aTopic, "ipc:utility-shutdown") == 0);
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, "ipc:utility-shutdown");
+ }
+
+ UtilityAudioDecoderChild::Shutdown(mSandbox);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(UtilityAudioDecoderChildShutdownObserver, nsIObserver);
+
+static EnumeratedArray<SandboxingKind, SandboxingKind::COUNT,
+ StaticRefPtr<UtilityAudioDecoderChild>>
+ sAudioDecoderChilds;
+
+UtilityAudioDecoderChild::UtilityAudioDecoderChild(SandboxingKind aKind)
+ : mSandbox(aKind), mAudioDecoderChildStart(TimeStamp::Now()) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ auto* obs = new UtilityAudioDecoderChildShutdownObserver(aKind);
+ observerService->AddObserver(obs, "ipc:utility-shutdown", false);
+ }
+}
+
+void UtilityAudioDecoderChild::ActorDestroy(ActorDestroyReason aReason) {
+ MOZ_ASSERT(NS_IsMainThread());
+#ifdef MOZ_WMF_MEDIA_ENGINE
+ if (mSandbox == SandboxingKind::MF_MEDIA_ENGINE_CDM) {
+ gfx::gfxVars::RemoveReceiver(this);
+ }
+#endif
+ Shutdown(mSandbox);
+}
+
+void UtilityAudioDecoderChild::Bind(
+ Endpoint<PUtilityAudioDecoderChild>&& aEndpoint) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (NS_WARN_IF(!aEndpoint.Bind(this))) {
+ MOZ_ASSERT_UNREACHABLE("Failed to bind UtilityAudioDecoderChild!");
+ return;
+ }
+#ifdef MOZ_WMF_MEDIA_ENGINE
+ if (mSandbox == SandboxingKind::MF_MEDIA_ENGINE_CDM) {
+ gfx::gfxVars::AddReceiver(this);
+ }
+#endif
+}
+
+/* static */
+void UtilityAudioDecoderChild::Shutdown(SandboxingKind aKind) {
+ sAudioDecoderChilds[aKind] = nullptr;
+}
+
+/* static */
+RefPtr<UtilityAudioDecoderChild> UtilityAudioDecoderChild::GetSingleton(
+ SandboxingKind aKind) {
+ MOZ_ASSERT(NS_IsMainThread());
+ bool shutdown = AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown);
+ if (!sAudioDecoderChilds[aKind] && !shutdown) {
+ sAudioDecoderChilds[aKind] = new UtilityAudioDecoderChild(aKind);
+ }
+ return sAudioDecoderChilds[aKind];
+}
+
+mozilla::ipc::IPCResult
+UtilityAudioDecoderChild::RecvUpdateMediaCodecsSupported(
+ const RemoteDecodeIn& aLocation,
+ const media::MediaCodecsSupported& aSupported) {
+ dom::ContentParent::BroadcastMediaCodecsSupportedUpdate(aLocation,
+ aSupported);
+ return IPC_OK();
+}
+
+#ifdef MOZ_WMF_MEDIA_ENGINE
+mozilla::ipc::IPCResult
+UtilityAudioDecoderChild::RecvCompleteCreatedVideoBridge() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mSandbox == SandboxingKind::MF_MEDIA_ENGINE_CDM);
+ mHasCreatedVideoBridge = true;
+ return IPC_OK();
+}
+
+bool UtilityAudioDecoderChild::HasCreatedVideoBridge() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mHasCreatedVideoBridge;
+}
+
+void UtilityAudioDecoderChild::OnVarChanged(const gfx::GfxVarUpdate& aVar) {
+ MOZ_ASSERT(mSandbox == SandboxingKind::MF_MEDIA_ENGINE_CDM);
+ SendUpdateVar(aVar);
+}
+
+void UtilityAudioDecoderChild::OnCompositorUnexpectedShutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mSandbox == SandboxingKind::MF_MEDIA_ENGINE_CDM);
+ mHasCreatedVideoBridge = false;
+ CreateVideoBridge();
+}
+
+bool UtilityAudioDecoderChild::CreateVideoBridge() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mSandbox == SandboxingKind::MF_MEDIA_ENGINE_CDM);
+
+ if (HasCreatedVideoBridge()) {
+ return true;
+ }
+
+ // Build content device data first; this ensure that the GPU process is fully
+ // ready.
+ gfx::ContentDeviceData contentDeviceData;
+ gfxPlatform::GetPlatform()->BuildContentDeviceData(&contentDeviceData);
+
+ gfx::GPUProcessManager* gpuManager = gfx::GPUProcessManager::Get();
+ if (!gpuManager) {
+ NS_WARNING("Failed to get a gpu mananger!");
+ return false;
+ }
+
+ // The child end is the producer of video frames; the parent end is the
+ // consumer.
+ base::ProcessId childPid = UtilityProcessManager::GetSingleton()
+ ->GetProcessParent(mSandbox)
+ ->OtherPid();
+ base::ProcessId parentPid = gpuManager->GPUProcessPid();
+ if (parentPid == base::kInvalidProcessId) {
+ NS_WARNING("GPU process Id is invald!");
+ return false;
+ }
+
+ ipc::Endpoint<layers::PVideoBridgeParent> parentPipe;
+ ipc::Endpoint<layers::PVideoBridgeChild> childPipe;
+ nsresult rv = layers::PVideoBridge::CreateEndpoints(parentPid, childPid,
+ &parentPipe, &childPipe);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create endpoints for video bridge!");
+ return false;
+ }
+
+ nsTArray<gfx::GfxVarUpdate> updates = gfx::gfxVars::FetchNonDefaultVars();
+ gpuManager->InitVideoBridge(
+ std::move(parentPipe),
+ layers::VideoBridgeSource::MFMediaEngineCDMProcess);
+ SendInitVideoBridge(std::move(childPipe), updates, contentDeviceData);
+ return true;
+}
+#endif
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/UtilityAudioDecoderChild.h b/ipc/glue/UtilityAudioDecoderChild.h
new file mode 100644
index 0000000000..360a7cb6be
--- /dev/null
+++ b/ipc/glue/UtilityAudioDecoderChild.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 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/. */
+#ifndef _include_ipc_glue_UtilityAudioDecoderChild_h__
+#define _include_ipc_glue_UtilityAudioDecoderChild_h__
+
+#include "mozilla/ProcInfo.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/RefPtr.h"
+
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/UtilityProcessParent.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#include "mozilla/ipc/UtilityAudioDecoder.h"
+#include "mozilla/ipc/PUtilityAudioDecoderChild.h"
+
+#ifdef MOZ_WMF_MEDIA_ENGINE
+# include "mozilla/gfx/GPUProcessListener.h"
+# include "mozilla/gfx/gfxVarReceiver.h"
+#endif
+
+#include "PDMFactory.h"
+
+namespace mozilla::ipc {
+
+class UtilityAudioDecoderChildShutdownObserver : public nsIObserver {
+ public:
+ explicit UtilityAudioDecoderChildShutdownObserver(SandboxingKind aKind)
+ : mSandbox(aKind){};
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override;
+
+ private:
+ virtual ~UtilityAudioDecoderChildShutdownObserver() = default;
+
+ const SandboxingKind mSandbox;
+};
+
+// This controls performing audio decoding on the utility process and it is
+// intended to live on the main process side
+class UtilityAudioDecoderChild final : public PUtilityAudioDecoderChild
+#ifdef MOZ_WMF_MEDIA_ENGINE
+ ,
+ public gfx::gfxVarReceiver,
+ public gfx::GPUProcessListener
+#endif
+{
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UtilityAudioDecoderChild, override);
+ mozilla::ipc::IPCResult RecvUpdateMediaCodecsSupported(
+ const RemoteDecodeIn& aLocation,
+ const media::MediaCodecsSupported& aSupported);
+
+ UtilityActorName GetActorName() { return GetAudioActorName(mSandbox); }
+
+ nsresult BindToUtilityProcess(RefPtr<UtilityProcessParent> aUtilityParent) {
+ Endpoint<PUtilityAudioDecoderChild> utilityAudioDecoderChildEnd;
+ Endpoint<PUtilityAudioDecoderParent> utilityAudioDecoderParentEnd;
+ nsresult rv = PUtilityAudioDecoder::CreateEndpoints(
+ aUtilityParent->OtherPid(), base::GetCurrentProcId(),
+ &utilityAudioDecoderParentEnd, &utilityAudioDecoderChildEnd);
+
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false, "Protocol endpoints failure");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aUtilityParent->SendStartUtilityAudioDecoderService(
+ std::move(utilityAudioDecoderParentEnd))) {
+ MOZ_ASSERT(false, "StartUtilityAudioDecoder service failure");
+ return NS_ERROR_FAILURE;
+ }
+
+ Bind(std::move(utilityAudioDecoderChildEnd));
+
+ PROFILER_MARKER_UNTYPED(
+ "UtilityAudioDecoderChild::BindToUtilityProcess", IPC,
+ MarkerOptions(
+ MarkerTiming::IntervalUntilNowFrom(mAudioDecoderChildStart)));
+ return NS_OK;
+ }
+
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ void Bind(Endpoint<PUtilityAudioDecoderChild>&& aEndpoint);
+
+ static void Shutdown(SandboxingKind aKind);
+
+ static RefPtr<UtilityAudioDecoderChild> GetSingleton(SandboxingKind aKind);
+
+#ifdef MOZ_WMF_MEDIA_ENGINE
+ mozilla::ipc::IPCResult RecvCompleteCreatedVideoBridge();
+
+ bool HasCreatedVideoBridge() const;
+
+ void OnVarChanged(const gfx::GfxVarUpdate& aVar) override;
+
+ void OnCompositorUnexpectedShutdown() override;
+
+ // True if creating a video bridge sucessfully. Currently only used for media
+ // engine cdm.
+ bool CreateVideoBridge();
+#endif
+
+ private:
+ explicit UtilityAudioDecoderChild(SandboxingKind aKind);
+ ~UtilityAudioDecoderChild() = default;
+
+ const SandboxingKind mSandbox;
+
+#ifdef MOZ_WMF_MEDIA_ENGINE
+ // True if the utility process has created a video bridge with the GPU prcess.
+ // Currently only used for media egine cdm. Main thread only.
+ bool mHasCreatedVideoBridge = false;
+#endif
+
+ TimeStamp mAudioDecoderChildStart;
+};
+
+} // namespace mozilla::ipc
+
+#endif // _include_ipc_glue_UtilityAudioDecoderChild_h__
diff --git a/ipc/glue/UtilityAudioDecoderParent.cpp b/ipc/glue/UtilityAudioDecoderParent.cpp
new file mode 100644
index 0000000000..edec21c5ee
--- /dev/null
+++ b/ipc/glue/UtilityAudioDecoderParent.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 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 "UtilityAudioDecoderParent.h"
+
+#include "GeckoProfiler.h"
+#include "nsDebugImpl.h"
+
+#include "mozilla/RemoteDecoderManagerParent.h"
+#include "PDMFactory.h"
+
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+# include "WMF.h"
+# include "WMFDecoderModule.h"
+# include "WMFUtils.h"
+
+# include "mozilla/sandboxTarget.h"
+# include "mozilla/ipc/UtilityProcessImpl.h"
+#endif // defined(XP_WIN) && defined(MOZ_SANDBOX)
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "mozilla/StaticPrefs_media.h"
+# include "AndroidDecoderModule.h"
+#endif
+
+#include "mozilla/ipc/UtilityProcessChild.h"
+#include "mozilla/RemoteDecodeUtils.h"
+
+#ifdef MOZ_WMF_MEDIA_ENGINE
+# include "mozilla/gfx/DeviceManagerDx.h"
+# include "mozilla/gfx/gfxVars.h"
+# include "gfxConfig.h"
+#endif
+
+namespace mozilla::ipc {
+
+UtilityAudioDecoderParent::UtilityAudioDecoderParent()
+ : mKind(GetCurrentSandboxingKind()),
+ mAudioDecoderParentStart(TimeStamp::Now()) {
+#ifdef MOZ_WMF_MEDIA_ENGINE
+ if (mKind == SandboxingKind::MF_MEDIA_ENGINE_CDM) {
+ nsDebugImpl::SetMultiprocessMode("MF Media Engine CDM");
+ profiler_set_process_name(nsCString("MF Media Engine CDM"));
+ gfx::gfxConfig::Init();
+ gfx::gfxVars::Initialize();
+ gfx::DeviceManagerDx::Init();
+ return;
+ }
+#endif
+ if (GetCurrentSandboxingKind() != SandboxingKind::GENERIC_UTILITY) {
+ nsDebugImpl::SetMultiprocessMode("Utility AudioDecoder");
+ profiler_set_process_name(nsCString("Utility AudioDecoder"));
+ }
+}
+
+UtilityAudioDecoderParent::~UtilityAudioDecoderParent() {
+#ifdef MOZ_WMF_MEDIA_ENGINE
+ if (mKind == SandboxingKind::MF_MEDIA_ENGINE_CDM) {
+ gfx::gfxConfig::Shutdown();
+ gfx::gfxVars::Shutdown();
+ gfx::DeviceManagerDx::Shutdown();
+ }
+#endif
+}
+
+/* static */
+void UtilityAudioDecoderParent::GenericPreloadForSandbox() {
+#if defined(MOZ_SANDBOX) && defined(XP_WIN) && defined(MOZ_FFVPX)
+ // Preload AV dlls so we can enable Binary Signature Policy
+ // to restrict further dll loads.
+ UtilityProcessImpl::LoadLibraryOrCrash(L"mozavcodec.dll");
+ UtilityProcessImpl::LoadLibraryOrCrash(L"mozavutil.dll");
+#endif // defined(MOZ_SANDBOX) && defined(XP_WIN) && defined(MOZ_FFVPX)
+}
+
+/* static */
+void UtilityAudioDecoderParent::WMFPreloadForSandbox() {
+#if defined(MOZ_SANDBOX) && defined(OS_WIN)
+ // mfplat.dll and mf.dll will be preloaded by
+ // wmf::MediaFoundationInitializer::HasInitialized()
+# if defined(DEBUG)
+ // WMF Shutdown on debug build somehow requires this
+ UtilityProcessImpl::LoadLibraryOrCrash(L"ole32.dll");
+# endif // defined(DEBUG)
+
+ auto rv = wmf::MediaFoundationInitializer::HasInitialized();
+ if (!rv) {
+ NS_WARNING("Failed to init Media Foundation in the Utility process");
+ return;
+ }
+#endif // defined(MOZ_SANDBOX) && defined(OS_WIN)
+}
+
+void UtilityAudioDecoderParent::Start(
+ Endpoint<PUtilityAudioDecoderParent>&& aEndpoint) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ DebugOnly<bool> ok = std::move(aEndpoint).Bind(this);
+ MOZ_ASSERT(ok);
+
+#ifdef MOZ_WIDGET_ANDROID
+ if (StaticPrefs::media_utility_android_media_codec_enabled()) {
+ AndroidDecoderModule::SetSupportedMimeTypes(
+ AndroidDecoderModule::GetSupportedMimeTypes());
+ }
+#endif
+
+ auto supported = PDMFactory::Supported();
+ Unused << SendUpdateMediaCodecsSupported(GetRemoteDecodeInFromKind(mKind),
+ supported);
+ PROFILER_MARKER_UNTYPED("UtilityAudioDecoderParent::Start", IPC,
+ MarkerOptions(MarkerTiming::IntervalUntilNowFrom(
+ mAudioDecoderParentStart)));
+}
+
+mozilla::ipc::IPCResult
+UtilityAudioDecoderParent::RecvNewContentRemoteDecoderManager(
+ Endpoint<PRemoteDecoderManagerParent>&& aEndpoint,
+ const ContentParentId& aParentId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!RemoteDecoderManagerParent::CreateForContent(std::move(aEndpoint),
+ aParentId)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+#ifdef MOZ_WMF_MEDIA_ENGINE
+mozilla::ipc::IPCResult UtilityAudioDecoderParent::RecvInitVideoBridge(
+ Endpoint<PVideoBridgeChild>&& aEndpoint,
+ nsTArray<gfx::GfxVarUpdate>&& aUpdates,
+ const ContentDeviceData& aContentDeviceData) {
+ MOZ_ASSERT(mKind == SandboxingKind::MF_MEDIA_ENGINE_CDM);
+ if (!RemoteDecoderManagerParent::CreateVideoBridgeToOtherProcess(
+ std::move(aEndpoint))) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ for (const auto& update : aUpdates) {
+ gfx::gfxVars::ApplyUpdate(update);
+ }
+
+ gfx::gfxConfig::Inherit(
+ {
+ gfx::Feature::HW_COMPOSITING,
+ gfx::Feature::D3D11_COMPOSITING,
+ gfx::Feature::OPENGL_COMPOSITING,
+ gfx::Feature::DIRECT2D,
+ },
+ aContentDeviceData.prefs());
+
+ if (gfx::gfxConfig::IsEnabled(gfx::Feature::D3D11_COMPOSITING)) {
+ if (auto* devmgr = gfx::DeviceManagerDx::Get()) {
+ devmgr->ImportDeviceInfo(aContentDeviceData.d3d11());
+ }
+ }
+
+ Unused << SendCompleteCreatedVideoBridge();
+ return IPC_OK();
+}
+
+IPCResult UtilityAudioDecoderParent::RecvUpdateVar(
+ const GfxVarUpdate& aUpdate) {
+ MOZ_ASSERT(mKind == SandboxingKind::MF_MEDIA_ENGINE_CDM);
+ gfx::gfxVars::ApplyUpdate(aUpdate);
+ return IPC_OK();
+}
+#endif
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/UtilityAudioDecoderParent.h b/ipc/glue/UtilityAudioDecoderParent.h
new file mode 100644
index 0000000000..37c9c12433
--- /dev/null
+++ b/ipc/glue/UtilityAudioDecoderParent.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 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/. */
+#ifndef _include_ipc_glue_UtilityAudioDecoderParent_h_
+#define _include_ipc_glue_UtilityAudioDecoderParent_h_
+
+#include "mozilla/PRemoteDecoderManagerParent.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/UniquePtr.h"
+
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/PUtilityAudioDecoderParent.h"
+
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+
+#include "nsThreadManager.h"
+
+namespace mozilla::ipc {
+
+// This is in charge of handling the utility child process side to perform
+// audio decoding
+class UtilityAudioDecoderParent final : public PUtilityAudioDecoderParent {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UtilityAudioDecoderParent, override);
+
+ UtilityAudioDecoderParent();
+
+ static void GenericPreloadForSandbox();
+ static void WMFPreloadForSandbox();
+
+ void Start(Endpoint<PUtilityAudioDecoderParent>&& aEndpoint);
+
+ mozilla::ipc::IPCResult RecvNewContentRemoteDecoderManager(
+ Endpoint<PRemoteDecoderManagerParent>&& aEndpoint,
+ const ContentParentId& aParentId);
+
+#ifdef MOZ_WMF_MEDIA_ENGINE
+ mozilla::ipc::IPCResult RecvInitVideoBridge(
+ Endpoint<PVideoBridgeChild>&& aEndpoint,
+ nsTArray<mozilla::gfx::GfxVarUpdate>&& aUpdates,
+ const ContentDeviceData& aContentDeviceData);
+
+ IPCResult RecvUpdateVar(const mozilla::gfx::GfxVarUpdate& aUpdate);
+#endif
+
+ private:
+ ~UtilityAudioDecoderParent();
+
+ const SandboxingKind mKind;
+ TimeStamp mAudioDecoderParentStart;
+};
+
+} // namespace mozilla::ipc
+
+#endif // _include_ipc_glue_UtilityAudioDecoderParent_h_
diff --git a/ipc/glue/UtilityProcessChild.cpp b/ipc/glue/UtilityProcessChild.cpp
new file mode 100644
index 0000000000..b64c20705b
--- /dev/null
+++ b/ipc/glue/UtilityProcessChild.cpp
@@ -0,0 +1,381 @@
+/* -*- 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 "UtilityProcessChild.h"
+
+#include "mozilla/ipc/UtilityProcessManager.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/JSOracleChild.h"
+#include "mozilla/dom/MemoryReportRequest.h"
+#include "mozilla/ipc/CrashReporterClient.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RemoteDecoderManagerParent.h"
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# include "mozilla/Sandbox.h"
+#endif
+
+#if defined(XP_OPENBSD) && defined(MOZ_SANDBOX)
+# include "mozilla/SandboxSettings.h"
+#endif
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+# include "mozilla/SandboxTestingChild.h"
+#endif
+
+#include "mozilla/Telemetry.h"
+
+#if defined(XP_WIN)
+# include "mozilla/WinDllServices.h"
+# include "mozilla/dom/WindowsUtilsChild.h"
+#endif
+
+#include "nsDebugImpl.h"
+#include "nsIXULRuntime.h"
+#include "nsThreadManager.h"
+#include "GeckoProfiler.h"
+
+#include "mozilla/ipc/ProcessChild.h"
+#include "mozilla/FOGIPC.h"
+#include "mozilla/glean/GleanMetrics.h"
+
+#include "mozilla/Services.h"
+
+namespace mozilla::ipc {
+
+using namespace layers;
+
+static StaticMutex sUtilityProcessChildMutex;
+static StaticRefPtr<UtilityProcessChild> sUtilityProcessChild
+ MOZ_GUARDED_BY(sUtilityProcessChildMutex);
+
+UtilityProcessChild::UtilityProcessChild() : mChildStartTime(TimeStamp::Now()) {
+ nsDebugImpl::SetMultiprocessMode("Utility");
+}
+
+UtilityProcessChild::~UtilityProcessChild() = default;
+
+/* static */
+RefPtr<UtilityProcessChild> UtilityProcessChild::GetSingleton() {
+ MOZ_ASSERT(XRE_IsUtilityProcess());
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) {
+ return nullptr;
+ }
+ StaticMutexAutoLock lock(sUtilityProcessChildMutex);
+ if (!sUtilityProcessChild) {
+ sUtilityProcessChild = new UtilityProcessChild();
+ }
+ return sUtilityProcessChild;
+}
+
+/* static */
+RefPtr<UtilityProcessChild> UtilityProcessChild::Get() {
+ StaticMutexAutoLock lock(sUtilityProcessChildMutex);
+ return sUtilityProcessChild;
+}
+
+bool UtilityProcessChild::Init(mozilla::ipc::UntypedEndpoint&& aEndpoint,
+ const nsCString& aParentBuildID,
+ uint64_t aSandboxingKind) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Initialize the thread manager before starting IPC. Otherwise, messages
+ // may be posted to the main thread and we won't be able to process them.
+ if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) {
+ return false;
+ }
+
+ // Now it's safe to start IPC.
+ if (NS_WARN_IF(!aEndpoint.Bind(this))) {
+ return false;
+ }
+
+ // This must be checked before any IPDL message, which may hit sentinel
+ // errors due to parent and content processes having different
+ // versions.
+ MessageChannel* channel = GetIPCChannel();
+ if (channel && !channel->SendBuildIDsMatchMessage(aParentBuildID.get())) {
+ // We need to quit this process if the buildID doesn't match the parent's.
+ // This can occur when an update occurred in the background.
+ ipc::ProcessChild::QuickExit();
+ }
+
+ // Init crash reporter support.
+ ipc::CrashReporterClient::InitSingleton(this);
+
+ if (NS_FAILED(NS_InitMinimalXPCOM())) {
+ return false;
+ }
+
+ mSandbox = (SandboxingKind)aSandboxingKind;
+
+ // At the moment, only ORB uses JSContext in the
+ // Utility Process and ORB uses GENERIC_UTILITY
+ if (mSandbox == SandboxingKind::GENERIC_UTILITY) {
+ JS::DisableJitBackend();
+ if (!JS_Init()) {
+ return false;
+ }
+#if defined(__OpenBSD__) && defined(MOZ_SANDBOX)
+ // Bug 1823458: delay pledge initialization, otherwise
+ // JS_Init triggers sysctl(KERN_PROC_ID) which isnt
+ // permitted with the current pledge.utility config
+ StartOpenBSDSandbox(GeckoProcessType_Utility, mSandbox);
+#endif
+ }
+
+ profiler_set_process_name(nsCString("Utility Process"));
+
+ // Notify the parent process that we have finished our init and that it can
+ // now resolve the pending promise of process startup
+ SendInitCompleted();
+
+ PROFILER_MARKER_UNTYPED(
+ "UtilityProcessChild::SendInitCompleted", IPC,
+ MarkerOptions(MarkerTiming::IntervalUntilNowFrom(mChildStartTime)));
+
+ RunOnShutdown(
+ [sandboxKind = mSandbox] {
+ StaticMutexAutoLock lock(sUtilityProcessChildMutex);
+ sUtilityProcessChild = nullptr;
+ if (sandboxKind == SandboxingKind::GENERIC_UTILITY) {
+ JS_ShutDown();
+ }
+ },
+ ShutdownPhase::XPCOMShutdownFinal);
+
+ return true;
+}
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+extern "C" {
+void CGSShutdownServerConnections();
+};
+#endif
+
+mozilla::ipc::IPCResult UtilityProcessChild::RecvInit(
+ const Maybe<FileDescriptor>& aBrokerFd,
+ const bool& aCanRecordReleaseTelemetry,
+ const bool& aIsReadyForBackgroundProcessing) {
+ // Do this now (before closing WindowServer on macOS) to avoid risking
+ // blocking in GetCurrentProcess() called on that platform
+ mozilla::ipc::SetThisProcessName("Utility Process");
+
+#if defined(MOZ_SANDBOX)
+# if defined(XP_MACOSX)
+ // Close all current connections to the WindowServer. This ensures that the
+ // Activity Monitor will not label the content process as "Not responding"
+ // because it's not running a native event loop. See bug 1384336.
+ CGSShutdownServerConnections();
+
+# elif defined(XP_LINUX)
+ int fd = -1;
+ if (aBrokerFd.isSome()) {
+ fd = aBrokerFd.value().ClonePlatformHandle().release();
+ }
+
+ SetUtilitySandbox(fd, mSandbox);
+
+# endif // XP_MACOSX/XP_LINUX
+#endif // MOZ_SANDBOX
+
+#if defined(XP_WIN)
+ if (aCanRecordReleaseTelemetry) {
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ dllSvc->StartUntrustedModulesProcessor(aIsReadyForBackgroundProcessing);
+ }
+#endif // defined(XP_WIN)
+
+ PROFILER_MARKER_UNTYPED(
+ "UtilityProcessChild::RecvInit", IPC,
+ MarkerOptions(MarkerTiming::IntervalUntilNowFrom(mChildStartTime)));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UtilityProcessChild::RecvPreferenceUpdate(
+ const Pref& aPref) {
+ Preferences::SetPreference(aPref);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UtilityProcessChild::RecvInitProfiler(
+ Endpoint<PProfilerChild>&& aEndpoint) {
+ mProfilerController = ChildProfilerController::Create(std::move(aEndpoint));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UtilityProcessChild::RecvRequestMemoryReport(
+ const uint32_t& aGeneration, const bool& aAnonymize,
+ const bool& aMinimizeMemoryUsage, const Maybe<FileDescriptor>& aDMDFile,
+ const RequestMemoryReportResolver& aResolver) {
+ nsPrintfCString processName("Utility (pid %" PRIPID
+ ", sandboxingKind %" PRIu64 ")",
+ base::GetCurrentProcId(), mSandbox);
+
+ mozilla::dom::MemoryReportRequestClient::Start(
+ aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile, processName,
+ [&](const MemoryReport& aReport) {
+ Unused << GetSingleton()->SendAddMemoryReport(aReport);
+ },
+ aResolver);
+ return IPC_OK();
+}
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+mozilla::ipc::IPCResult UtilityProcessChild::RecvInitSandboxTesting(
+ Endpoint<PSandboxTestingChild>&& aEndpoint) {
+ if (!SandboxTestingChild::Initialize(std::move(aEndpoint))) {
+ return IPC_FAIL(
+ this, "InitSandboxTesting failed to initialise the child process.");
+ }
+ return IPC_OK();
+}
+#endif
+
+mozilla::ipc::IPCResult UtilityProcessChild::RecvFlushFOGData(
+ FlushFOGDataResolver&& aResolver) {
+ glean::FlushFOGData(std::move(aResolver));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UtilityProcessChild::RecvTestTriggerMetrics(
+ TestTriggerMetricsResolver&& aResolve) {
+ mozilla::glean::test_only_ipc::a_counter.Add(
+ nsIXULRuntime::PROCESS_TYPE_UTILITY);
+ aResolve(true);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UtilityProcessChild::RecvTestTelemetryProbes() {
+ const uint32_t kExpectedUintValue = 42;
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UTILITY_ONLY_UINT,
+ kExpectedUintValue);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+UtilityProcessChild::RecvStartUtilityAudioDecoderService(
+ Endpoint<PUtilityAudioDecoderParent>&& aEndpoint) {
+ PROFILER_MARKER_UNTYPED(
+ "UtilityProcessChild::RecvStartUtilityAudioDecoderService", MEDIA,
+ MarkerOptions(MarkerTiming::IntervalUntilNowFrom(mChildStartTime)));
+ mUtilityAudioDecoderInstance = new UtilityAudioDecoderParent();
+ if (!mUtilityAudioDecoderInstance) {
+ return IPC_FAIL(this, "Failing to create UtilityAudioDecoderParent");
+ }
+
+ mUtilityAudioDecoderInstance->Start(std::move(aEndpoint));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UtilityProcessChild::RecvStartJSOracleService(
+ Endpoint<PJSOracleChild>&& aEndpoint) {
+ PROFILER_MARKER_UNTYPED(
+ "UtilityProcessChild::RecvStartJSOracleService", JS,
+ MarkerOptions(MarkerTiming::IntervalUntilNowFrom(mChildStartTime)));
+ mJSOracleInstance = new mozilla::dom::JSOracleChild();
+ if (!mJSOracleInstance) {
+ return IPC_FAIL(this, "Failing to create JSOracleParent");
+ }
+
+ mJSOracleInstance->Start(std::move(aEndpoint));
+ return IPC_OK();
+}
+
+#if defined(XP_WIN)
+mozilla::ipc::IPCResult UtilityProcessChild::RecvStartWindowsUtilsService(
+ Endpoint<dom::PWindowsUtilsChild>&& aEndpoint) {
+ PROFILER_MARKER_UNTYPED(
+ "UtilityProcessChild::RecvStartWindowsUtilsService", OTHER,
+ MarkerOptions(MarkerTiming::IntervalUntilNowFrom(mChildStartTime)));
+ mWindowsUtilsInstance = new dom::WindowsUtilsChild();
+ if (!mWindowsUtilsInstance) {
+ return IPC_FAIL(this, "Failed to create WindowsUtilsChild");
+ }
+
+ [[maybe_unused]] bool ok = std::move(aEndpoint).Bind(mWindowsUtilsInstance);
+ MOZ_ASSERT(ok);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UtilityProcessChild::RecvGetUntrustedModulesData(
+ GetUntrustedModulesDataResolver&& aResolver) {
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ dllSvc->GetUntrustedModulesData()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [aResolver](Maybe<UntrustedModulesData>&& aData) {
+ aResolver(std::move(aData));
+ },
+ [aResolver](nsresult aReason) { aResolver(Nothing()); });
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+UtilityProcessChild::RecvUnblockUntrustedModulesThread() {
+ if (nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService()) {
+ obs->NotifyObservers(nullptr, "unblock-untrusted-modules-thread", nullptr);
+ }
+ return IPC_OK();
+}
+#endif // defined(XP_WIN)
+
+void UtilityProcessChild::ActorDestroy(ActorDestroyReason aWhy) {
+ if (AbnormalShutdown == aWhy) {
+ NS_WARNING("Shutting down Utility process early due to a crash!");
+ ipc::ProcessChild::QuickExit();
+ }
+
+ // Send the last bits of Glean data over to the main process.
+ glean::FlushFOGData(
+ [](ByteBuf&& aBuf) { glean::SendFOGData(std::move(aBuf)); });
+
+#ifndef NS_FREE_PERMANENT_DATA
+ ProcessChild::QuickExit();
+#else
+
+ if (mProfilerController) {
+ mProfilerController->Shutdown();
+ mProfilerController = nullptr;
+ }
+
+ uint32_t timeout = 0;
+ if (mUtilityAudioDecoderInstance) {
+ mUtilityAudioDecoderInstance = nullptr;
+ timeout = 10 * 1000;
+ }
+
+ mJSOracleInstance = nullptr;
+
+# ifdef XP_WIN
+ mWindowsUtilsInstance = nullptr;
+# endif
+
+ // Wait until all RemoteDecoderManagerParent have closed.
+ // It is still possible some may not have clean up yet, and we might hit
+ // timeout. Our xpcom-shutdown listener should take care of cleaning the
+ // reference of our singleton.
+ //
+ // FIXME: Should move from using AsyncBlockers to proper
+ // nsIAsyncShutdownService once it is not JS, see bug 1760855
+ mShutdownBlockers.WaitUntilClear(timeout)->Then(
+ GetCurrentSerialEventTarget(), __func__, [&]() {
+# ifdef XP_WIN
+ {
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ dllSvc->DisableFull();
+ }
+# endif // defined(XP_WIN)
+
+ ipc::CrashReporterClient::DestroySingleton();
+ XRE_ShutdownChildProcess();
+ });
+#endif // NS_FREE_PERMANENT_DATA
+}
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/UtilityProcessChild.h b/ipc/glue/UtilityProcessChild.h
new file mode 100644
index 0000000000..423922cafc
--- /dev/null
+++ b/ipc/glue/UtilityProcessChild.h
@@ -0,0 +1,104 @@
+/* -*- 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/. */
+#ifndef _include_ipc_glue_UtilityProcessChild_h_
+#define _include_ipc_glue_UtilityProcessChild_h_
+#include "mozilla/ipc/PUtilityProcessChild.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#include "mozilla/ipc/UtilityAudioDecoderParent.h"
+#include "mozilla/UniquePtr.h"
+#include "ChildProfilerController.h"
+
+#include "mozilla/PRemoteDecoderManagerParent.h"
+#include "mozilla/ipc/AsyncBlockers.h"
+#include "mozilla/dom/JSOracleChild.h"
+#include "mozilla/ProfilerMarkers.h"
+
+namespace mozilla::dom {
+class PJSOracleChild;
+} // namespace mozilla::dom
+
+namespace mozilla::ipc {
+
+class UtilityProcessHost;
+
+class UtilityProcessChild final : public PUtilityProcessChild {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UtilityProcessChild, override);
+
+ UtilityProcessChild();
+
+ static RefPtr<UtilityProcessChild> GetSingleton();
+ static RefPtr<UtilityProcessChild> Get();
+
+ SandboxingKind mSandbox{};
+
+ bool Init(mozilla::ipc::UntypedEndpoint&& aEndpoint,
+ const nsCString& aParentBuildID, uint64_t aSandboxingKind);
+
+ mozilla::ipc::IPCResult RecvInit(const Maybe<ipc::FileDescriptor>& aBrokerFd,
+ const bool& aCanRecordReleaseTelemetry,
+ const bool& aIsReadyForBackgroundProcessing);
+ mozilla::ipc::IPCResult RecvInitProfiler(
+ Endpoint<PProfilerChild>&& aEndpoint);
+
+ mozilla::ipc::IPCResult RecvPreferenceUpdate(const Pref& pref);
+
+ mozilla::ipc::IPCResult RecvRequestMemoryReport(
+ const uint32_t& generation, const bool& anonymize,
+ const bool& minimizeMemoryUsage,
+ const Maybe<ipc::FileDescriptor>& DMDFile,
+ const RequestMemoryReportResolver& aResolver);
+
+ mozilla::ipc::IPCResult RecvFlushFOGData(FlushFOGDataResolver&& aResolver);
+
+ mozilla::ipc::IPCResult RecvTestTriggerMetrics(
+ TestTriggerMetricsResolver&& aResolve);
+
+ mozilla::ipc::IPCResult RecvTestTelemetryProbes();
+
+ mozilla::ipc::IPCResult RecvStartUtilityAudioDecoderService(
+ Endpoint<PUtilityAudioDecoderParent>&& aEndpoint);
+
+ mozilla::ipc::IPCResult RecvStartJSOracleService(
+ Endpoint<dom::PJSOracleChild>&& aEndpoint);
+
+#if defined(XP_WIN)
+ mozilla::ipc::IPCResult RecvStartWindowsUtilsService(
+ Endpoint<PWindowsUtilsChild>&& aEndpoint);
+
+ mozilla::ipc::IPCResult RecvGetUntrustedModulesData(
+ GetUntrustedModulesDataResolver&& aResolver);
+ mozilla::ipc::IPCResult RecvUnblockUntrustedModulesThread();
+#endif // defined(XP_WIN)
+
+ AsyncBlockers& AsyncShutdownService() { return mShutdownBlockers; }
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+ mozilla::ipc::IPCResult RecvInitSandboxTesting(
+ Endpoint<PSandboxTestingChild>&& aEndpoint);
+#endif
+
+ protected:
+ friend class UtilityProcessImpl;
+ ~UtilityProcessChild();
+
+ private:
+ TimeStamp mChildStartTime;
+ RefPtr<ChildProfilerController> mProfilerController;
+ RefPtr<UtilityAudioDecoderParent> mUtilityAudioDecoderInstance{};
+ RefPtr<dom::JSOracleChild> mJSOracleInstance{};
+#ifdef XP_WIN
+ RefPtr<PWindowsUtilsChild> mWindowsUtilsInstance;
+#endif
+
+ AsyncBlockers mShutdownBlockers;
+};
+
+} // namespace mozilla::ipc
+
+#endif // _include_ipc_glue_UtilityProcessChild_h_
diff --git a/ipc/glue/UtilityProcessHost.cpp b/ipc/glue/UtilityProcessHost.cpp
new file mode 100644
index 0000000000..b5a9c229c5
--- /dev/null
+++ b/ipc/glue/UtilityProcessHost.cpp
@@ -0,0 +1,353 @@
+/* -*- 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 "UtilityProcessHost.h"
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/UtilityProcessManager.h"
+#include "mozilla/Telemetry.h"
+
+#include "chrome/common/process_watcher.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_general.h"
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+# include "mozilla/Sandbox.h"
+#endif
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# include "mozilla/SandboxBrokerPolicyFactory.h"
+#endif
+
+#if defined(XP_WIN)
+# include "mozilla/WinDllServices.h"
+#endif // defined(XP_WIN)
+
+#include "ProfilerParent.h"
+#include "mozilla/PProfilerChild.h"
+
+namespace mozilla::ipc {
+
+LazyLogModule gUtilityProcessLog("utilityproc");
+#define LOGD(...) MOZ_LOG(gUtilityProcessLog, LogLevel::Debug, (__VA_ARGS__))
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+bool UtilityProcessHost::sLaunchWithMacSandbox = false;
+#endif
+
+UtilityProcessHost::UtilityProcessHost(SandboxingKind aSandbox,
+ RefPtr<Listener> aListener)
+ : GeckoChildProcessHost(GeckoProcessType_Utility),
+ mListener(std::move(aListener)),
+ mLiveToken(new media::Refcountable<bool>(true)),
+ mLaunchPromise(
+ MakeRefPtr<GenericNonExclusivePromise::Private>(__func__)) {
+ MOZ_COUNT_CTOR(UtilityProcessHost);
+ LOGD("[%p] UtilityProcessHost::UtilityProcessHost sandboxingKind=%" PRIu64,
+ this, aSandbox);
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ if (!sLaunchWithMacSandbox) {
+ sLaunchWithMacSandbox =
+ (PR_GetEnv("MOZ_DISABLE_UTILITY_SANDBOX") == nullptr);
+ }
+ mDisableOSActivityMode = sLaunchWithMacSandbox;
+#endif
+#if defined(MOZ_SANDBOX)
+ mSandbox = aSandbox;
+#endif
+}
+
+UtilityProcessHost::~UtilityProcessHost() {
+ MOZ_COUNT_DTOR(UtilityProcessHost);
+#if defined(MOZ_SANDBOX)
+ LOGD("[%p] UtilityProcessHost::~UtilityProcessHost sandboxingKind=%" PRIu64,
+ this, mSandbox);
+#else
+ LOGD("[%p] UtilityProcessHost::~UtilityProcessHost", this);
+#endif
+}
+
+bool UtilityProcessHost::Launch(StringVector aExtraOpts) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MOZ_ASSERT(mLaunchPhase == LaunchPhase::Unlaunched);
+ MOZ_ASSERT(!mUtilityProcessParent);
+
+ LOGD("[%p] UtilityProcessHost::Launch", this);
+
+ mPrefSerializer = MakeUnique<ipc::SharedPreferenceSerializer>();
+ if (!mPrefSerializer->SerializeToSharedMemory(GeckoProcessType_Utility,
+ /* remoteType */ ""_ns)) {
+ return false;
+ }
+ mPrefSerializer->AddSharedPrefCmdLineArgs(*this, aExtraOpts);
+
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+ mSandboxLevel = Preferences::GetInt("security.sandbox.utility.level");
+#endif
+
+ mLaunchPhase = LaunchPhase::Waiting;
+
+ if (!GeckoChildProcessHost::AsyncLaunch(aExtraOpts)) {
+ NS_WARNING("UtilityProcess AsyncLaunch failed, aborting.");
+ mLaunchPhase = LaunchPhase::Complete;
+ mPrefSerializer = nullptr;
+ return false;
+ }
+ LOGD("[%p] UtilityProcessHost::Launch launching async", this);
+ return true;
+}
+
+RefPtr<GenericNonExclusivePromise> UtilityProcessHost::LaunchPromise() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mLaunchPromiseLaunched) {
+ return mLaunchPromise;
+ }
+
+ WhenProcessHandleReady()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [this, liveToken = mLiveToken](
+ const ipc::ProcessHandlePromise::ResolveOrRejectValue& aResult) {
+ if (!*liveToken) {
+ // The UtilityProcessHost got deleted. Abort. The promise would have
+ // already been rejected.
+ return;
+ }
+ if (mLaunchCompleted) {
+ return;
+ }
+ mLaunchCompleted = true;
+ if (aResult.IsReject()) {
+ RejectPromise();
+ }
+ // If aResult.IsResolve() then we have succeeded in launching the
+ // Utility process. The promise will be resolved once the channel has
+ // connected (or failed to) later.
+ });
+
+ mLaunchPromiseLaunched = true;
+ return mLaunchPromise;
+}
+
+void UtilityProcessHost::OnChannelConnected(base::ProcessId peer_pid) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ LOGD("[%p] UtilityProcessHost::OnChannelConnected", this);
+
+ GeckoChildProcessHost::OnChannelConnected(peer_pid);
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "UtilityProcessHost::OnChannelConnected",
+ [this, liveToken = mLiveToken]() {
+ if (*liveToken && mLaunchPhase == LaunchPhase::Waiting) {
+ InitAfterConnect(true);
+ }
+ }));
+}
+
+void UtilityProcessHost::OnChannelError() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ LOGD("[%p] UtilityProcessHost::OnChannelError", this);
+
+ GeckoChildProcessHost::OnChannelError();
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "UtilityProcessHost::OnChannelError", [this, liveToken = mLiveToken]() {
+ if (*liveToken && mLaunchPhase == LaunchPhase::Waiting) {
+ InitAfterConnect(false);
+ }
+ }));
+}
+
+void UtilityProcessHost::InitAfterConnect(bool aSucceeded) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MOZ_ASSERT(mLaunchPhase == LaunchPhase::Waiting);
+ MOZ_ASSERT(!mUtilityProcessParent);
+
+ mLaunchPhase = LaunchPhase::Complete;
+
+ if (!aSucceeded) {
+ RejectPromise();
+ return;
+ }
+
+ mUtilityProcessParent = MakeRefPtr<UtilityProcessParent>(this);
+ DebugOnly<bool> rv = TakeInitialEndpoint().Bind(mUtilityProcessParent.get());
+ MOZ_ASSERT(rv);
+
+ // Only clear mPrefSerializer in the success case to avoid a
+ // possible race in the case case of a timeout on Windows launch.
+ // See Bug 1555076 comment 7:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1555076#c7
+ mPrefSerializer = nullptr;
+
+ Maybe<FileDescriptor> brokerFd;
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+ UniquePtr<SandboxBroker::Policy> policy;
+ switch (mSandbox) {
+ case SandboxingKind::GENERIC_UTILITY:
+ policy = SandboxBrokerPolicyFactory::GetUtilityProcessPolicy(
+ GetActor()->OtherPid());
+ break;
+
+ default:
+ MOZ_ASSERT(false, "Invalid SandboxingKind");
+ break;
+ }
+ if (policy != nullptr) {
+ brokerFd = Some(FileDescriptor());
+ mSandboxBroker = SandboxBroker::Create(
+ std::move(policy), GetActor()->OtherPid(), brokerFd.ref());
+ // This is unlikely to fail and probably indicates OS resource
+ // exhaustion, but we can at least try to recover.
+ Unused << NS_WARN_IF(mSandboxBroker == nullptr);
+ MOZ_ASSERT(brokerFd.ref().IsValid());
+ }
+#endif // XP_LINUX && MOZ_SANDBOX
+
+ bool isReadyForBackgroundProcessing = false;
+#if defined(XP_WIN)
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ isReadyForBackgroundProcessing = dllSvc->IsReadyForBackgroundProcessing();
+#endif
+
+ Unused << GetActor()->SendInit(brokerFd, Telemetry::CanRecordReleaseData(),
+ isReadyForBackgroundProcessing);
+
+ Unused << GetActor()->SendInitProfiler(
+ ProfilerParent::CreateForProcess(GetActor()->OtherPid()));
+
+ LOGD("[%p] UtilityProcessHost::InitAfterConnect succeeded", this);
+
+ // Promise will be resolved later, from UtilityProcessParent when the child
+ // will send the InitCompleted message.
+}
+
+void UtilityProcessHost::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mShutdownRequested);
+ LOGD("[%p] UtilityProcessHost::Shutdown", this);
+
+ RejectPromise();
+
+ if (mUtilityProcessParent) {
+ LOGD("[%p] UtilityProcessHost::Shutdown not destroying utility process.",
+ this);
+
+ // OnChannelClosed uses this to check if the shutdown was expected or
+ // unexpected.
+ mShutdownRequested = true;
+
+ // The channel might already be closed if we got here unexpectedly.
+ if (mUtilityProcessParent->CanSend()) {
+ mUtilityProcessParent->Close();
+ }
+
+#ifndef NS_FREE_PERMANENT_DATA
+ // No need to communicate shutdown, the Utility process doesn't need to
+ // communicate anything back.
+ KillHard("NormalShutdown");
+#endif
+
+ // If we're shutting down unexpectedly, we're in the middle of handling an
+ // ActorDestroy for PUtilityProcessParent, which is still on the stack.
+ // We'll return back to OnChannelClosed.
+ //
+ // Otherwise, we'll wait for OnChannelClose to be called whenever
+ // PUtilityProcessParent acknowledges shutdown.
+ return;
+ }
+
+ DestroyProcess();
+}
+
+void UtilityProcessHost::OnChannelClosed() {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOGD("[%p] UtilityProcessHost::OnChannelClosed", this);
+
+ RejectPromise();
+
+ if (!mShutdownRequested && mListener) {
+ // This is an unclean shutdown. Notify our listener that we're going away.
+ mListener->OnProcessUnexpectedShutdown(this);
+ }
+
+ DestroyProcess();
+
+ // Release the actor.
+ UtilityProcessParent::Destroy(std::move(mUtilityProcessParent));
+}
+
+void UtilityProcessHost::KillHard(const char* aReason) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOGD("[%p] UtilityProcessHost::KillHard", this);
+
+ ProcessHandle handle = GetChildProcessHandle();
+ if (!base::KillProcess(handle, base::PROCESS_END_KILLED_BY_USER)) {
+ NS_WARNING("failed to kill subprocess!");
+ }
+
+ SetAlreadyDead();
+}
+
+void UtilityProcessHost::DestroyProcess() {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOGD("[%p] UtilityProcessHost::DestroyProcess", this);
+
+ RejectPromise();
+
+ // Any pending tasks will be cancelled from now on.
+ *mLiveToken = false;
+
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("DestroyProcessRunnable", [this] { Destroy(); }));
+}
+
+void UtilityProcessHost::ResolvePromise() {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOGD("[%p] UtilityProcessHost connected - resolving launch promise", this);
+
+ if (!mLaunchPromiseSettled) {
+ mLaunchPromise->Resolve(true, __func__);
+ mLaunchPromiseSettled = true;
+ }
+
+ mLaunchCompleted = true;
+}
+
+void UtilityProcessHost::RejectPromise() {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOGD("[%p] UtilityProcessHost connection failed - rejecting launch promise",
+ this);
+
+ if (!mLaunchPromiseSettled) {
+ mLaunchPromise->Reject(NS_ERROR_FAILURE, __func__);
+ mLaunchPromiseSettled = true;
+ }
+
+ mLaunchCompleted = true;
+}
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+bool UtilityProcessHost::FillMacSandboxInfo(MacSandboxInfo& aInfo) {
+ GeckoChildProcessHost::FillMacSandboxInfo(aInfo);
+ if (!aInfo.shouldLog && PR_GetEnv("MOZ_SANDBOX_UTILITY_LOGGING")) {
+ aInfo.shouldLog = true;
+ }
+ return true;
+}
+
+/* static */
+MacSandboxType UtilityProcessHost::GetMacSandboxType() {
+ return MacSandboxType_Utility;
+}
+#endif
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/UtilityProcessHost.h b/ipc/glue/UtilityProcessHost.h
new file mode 100644
index 0000000000..6949470155
--- /dev/null
+++ b/ipc/glue/UtilityProcessHost.h
@@ -0,0 +1,158 @@
+/* -*- 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/. */
+
+#ifndef _include_ipc_glue_UtilityProcessHost_h_
+#define _include_ipc_glue_UtilityProcessHost_h_
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ipc/UtilityProcessParent.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/media/MediaUtils.h"
+#include "mozilla/ipc/ProcessUtils.h"
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# include "mozilla/SandboxBroker.h"
+#endif
+
+namespace mozilla::ipc {
+
+class UtilityProcessParent;
+
+// UtilityProcessHost is the "parent process" container for a subprocess handle
+// and IPC connection. It owns the parent process IPDL actor, which in this
+// case, is a UtilityChild.
+//
+// UtilityProcessHosts are allocated and managed by UtilityProcessManager.
+class UtilityProcessHost final : public mozilla::ipc::GeckoChildProcessHost {
+ friend class UtilityProcessParent;
+
+ public:
+ class Listener {
+ protected:
+ virtual ~Listener() = default;
+
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UtilityProcessHost::Listener);
+
+ // The UtilityProcessHost has unexpectedly shutdown or had its connection
+ // severed. This is not called if an error occurs after calling
+ // Shutdown().
+ virtual void OnProcessUnexpectedShutdown(UtilityProcessHost* aHost) {}
+ };
+
+ explicit UtilityProcessHost(SandboxingKind aSandbox,
+ RefPtr<Listener> listener);
+
+ // Launch the subprocess asynchronously. On failure, false is returned.
+ // Otherwise, true is returned. If succeeded, a follow-up call should be made
+ // to LaunchPromise() which will return a promise that will be resolved once
+ // the Utility process has launched and a channel has been established.
+ //
+ // @param aExtraOpts (StringVector)
+ // Extra options to pass to the subprocess.
+ bool Launch(StringVector aExtraOpts);
+
+ // Return a promise that will be resolved once the process has completed its
+ // launch. The promise will be immediately resolved if the launch has already
+ // succeeded.
+ RefPtr<GenericNonExclusivePromise> LaunchPromise();
+
+ // Inform the process that it should clean up its resources and shut
+ // down. This initiates an asynchronous shutdown sequence. After this
+ // method returns, it is safe for the caller to forget its pointer to
+ // the UtilityProcessHost.
+ //
+ // After this returns, the attached Listener is no longer used.
+ void Shutdown();
+
+ // Return the actor for the top-level actor of the process. If the process
+ // has not connected yet, this returns null.
+ RefPtr<UtilityProcessParent> GetActor() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mUtilityProcessParent;
+ }
+
+ bool IsConnected() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return bool(mUtilityProcessParent);
+ }
+
+ // Called on the IO thread.
+ void OnChannelConnected(base::ProcessId peer_pid) override;
+ void OnChannelError() override;
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ // Return the sandbox type to be used with this process type.
+ static MacSandboxType GetMacSandboxType();
+#endif
+
+ private:
+ ~UtilityProcessHost();
+
+ // Called on the main thread with true after a connection has been established
+ // or false if it failed (including if it failed before the timeout kicked in)
+ void InitAfterConnect(bool aSucceeded);
+
+ // Called on the main thread when the mUtilityProcessParent actor is shutting
+ // down.
+ void OnChannelClosed();
+
+ // Kill the remote process, triggering IPC shutdown.
+ void KillHard(const char* aReason);
+
+ void DestroyProcess();
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ static bool sLaunchWithMacSandbox;
+
+ // Sandbox the Utility process at launch for all instances
+ bool IsMacSandboxLaunchEnabled() override { return sLaunchWithMacSandbox; }
+
+ // Override so we can turn on Utility process-specific sandbox logging
+ bool FillMacSandboxInfo(MacSandboxInfo& aInfo) override;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(UtilityProcessHost);
+
+ RefPtr<Listener> mListener;
+
+ // All members below are only ever accessed on the main thread.
+ enum class LaunchPhase { Unlaunched, Waiting, Complete };
+ LaunchPhase mLaunchPhase = LaunchPhase::Unlaunched;
+
+ RefPtr<UtilityProcessParent> mUtilityProcessParent;
+
+ UniquePtr<ipc::SharedPreferenceSerializer> mPrefSerializer{};
+
+ bool mShutdownRequested = false;
+
+ void RejectPromise();
+ void ResolvePromise();
+
+ // Set to true on construction and to false just prior deletion.
+ // The UtilityProcessHost isn't refcounted; so we can capture this by value in
+ // lambdas along with a strong reference to mLiveToken and check if that value
+ // is true before accessing "this".
+ // While a reference to mLiveToken can be taken on any thread; its value can
+ // only be read or written on the main thread.
+ const RefPtr<media::Refcountable<bool>> mLiveToken;
+
+ RefPtr<GenericNonExclusivePromise::Private> mLaunchPromise{};
+ bool mLaunchPromiseSettled = false;
+ bool mLaunchPromiseLaunched = false;
+ // Will be set to true if the Utility process as successfully started.
+ bool mLaunchCompleted = false;
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+ UniquePtr<SandboxBroker> mSandboxBroker{};
+#endif
+};
+
+} // namespace mozilla::ipc
+
+#endif // _include_ipc_glue_UtilityProcessHost_h_
diff --git a/ipc/glue/UtilityProcessImpl.cpp b/ipc/glue/UtilityProcessImpl.cpp
new file mode 100644
index 0000000000..0edfbfdc0b
--- /dev/null
+++ b/ipc/glue/UtilityProcessImpl.cpp
@@ -0,0 +1,127 @@
+/* -*- 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 "UtilityProcessImpl.h"
+
+#include "mozilla/ipc/IOThreadChild.h"
+#include "mozilla/GeckoArgs.h"
+
+#if defined(OS_WIN)
+# include "nsExceptionHandler.h"
+#endif
+
+#if defined(OS_WIN) && defined(MOZ_SANDBOX)
+# include "mozilla/sandboxTarget.h"
+# include "WMF.h"
+# include "WMFDecoderModule.h"
+#endif
+
+#if defined(XP_OPENBSD) && defined(MOZ_SANDBOX)
+# include "mozilla/SandboxSettings.h"
+#endif
+
+namespace mozilla::ipc {
+
+UtilityProcessImpl::~UtilityProcessImpl() = default;
+
+#if defined(XP_WIN)
+/* static */
+void UtilityProcessImpl::LoadLibraryOrCrash(LPCWSTR aLib) {
+ // re-try a few times depending on the error we get ; inspired by both our
+ // results on content process allocations as well as msys2:
+ // https://github.com/git-for-windows/msys2-runtime/blob/b4fed42af089ab955286343835a97e287496b3f8/winsup/cygwin/autoload.cc#L323-L339
+
+ const int kMaxRetries = 10;
+ DWORD err;
+
+ for (int i = 0; i < kMaxRetries; i++) {
+ HMODULE module = ::LoadLibraryW(aLib);
+ if (module) {
+ return;
+ }
+
+ err = ::GetLastError();
+
+ if (err != ERROR_NOACCESS && err != ERROR_DLL_INIT_FAILED) {
+ break;
+ }
+
+ PR_Sleep(0);
+ }
+
+ switch (err) {
+ /* case ERROR_ACCESS_DENIED: */
+ /* case ERROR_BAD_EXE_FORMAT: */
+ /* case ERROR_SHARING_VIOLATION: */
+ case ERROR_MOD_NOT_FOUND:
+ case ERROR_COMMITMENT_LIMIT:
+ // We want to make it explicit in telemetry that this was in fact an
+ // OOM condition, even though we could not detect it on our own
+ CrashReporter::AnnotateOOMAllocationSize(1);
+ break;
+
+ default:
+ break;
+ }
+
+ MOZ_CRASH_UNSAFE_PRINTF("Unable to preload module: 0x%lx", err);
+}
+#endif // defined(XP_WIN)
+
+bool UtilityProcessImpl::Init(int aArgc, char* aArgv[]) {
+ Maybe<uint64_t> sandboxingKind = geckoargs::sSandboxingKind.Get(aArgc, aArgv);
+ if (sandboxingKind.isNothing()) {
+ return false;
+ }
+
+ if (*sandboxingKind >= SandboxingKind::COUNT) {
+ return false;
+ }
+
+#if defined(MOZ_SANDBOX) && defined(OS_WIN)
+ // We delay load winmm.dll so that its dependencies don't interfere with COM
+ // initialization when win32k is locked down. We need to load it before we
+ // lower the sandbox in processes where the policy will prevent loading.
+ LoadLibraryOrCrash(L"winmm.dll");
+
+ if (*sandboxingKind == SandboxingKind::GENERIC_UTILITY) {
+ // Preload audio generic libraries required for ffmpeg only
+ UtilityAudioDecoderParent::GenericPreloadForSandbox();
+ }
+
+ if (*sandboxingKind == SandboxingKind::UTILITY_AUDIO_DECODING_WMF
+# ifdef MOZ_WMF_MEDIA_ENGINE
+ || *sandboxingKind == SandboxingKind::MF_MEDIA_ENGINE_CDM
+# endif
+ ) {
+ UtilityAudioDecoderParent::WMFPreloadForSandbox();
+ }
+
+ // Go for it
+ mozilla::SandboxTarget::Instance()->StartSandbox();
+#elif defined(__OpenBSD__) && defined(MOZ_SANDBOX)
+ if (*sandboxingKind != SandboxingKind::GENERIC_UTILITY) {
+ StartOpenBSDSandbox(GeckoProcessType_Utility,
+ (SandboxingKind)*sandboxingKind);
+ }
+#endif
+
+ Maybe<const char*> parentBuildID =
+ geckoargs::sParentBuildID.Get(aArgc, aArgv);
+ if (parentBuildID.isNothing()) {
+ return false;
+ }
+
+ if (!ProcessChild::InitPrefs(aArgc, aArgv)) {
+ return false;
+ }
+
+ return mUtility->Init(TakeInitialEndpoint(), nsCString(*parentBuildID),
+ *sandboxingKind);
+}
+
+void UtilityProcessImpl::CleanUp() { NS_ShutdownXPCOM(nullptr); }
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/UtilityProcessImpl.h b/ipc/glue/UtilityProcessImpl.h
new file mode 100644
index 0000000000..9b42cd5f74
--- /dev/null
+++ b/ipc/glue/UtilityProcessImpl.h
@@ -0,0 +1,43 @@
+/* -*- 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/. */
+#ifndef _include_ipc_glue_UtilityProcessImpl_h__
+#define _include_ipc_glue_UtilityProcessImpl_h__
+#include "mozilla/ipc/ProcessChild.h"
+
+#if defined(XP_WIN)
+# include "mozilla/mscom/ProcessRuntime.h"
+#endif
+
+#include "mozilla/ipc/UtilityProcessChild.h"
+
+namespace mozilla::ipc {
+
+// This class owns the subprocess instance of a PUtilityProcess - which in this
+// case, is a UtilityProcessParent. It is instantiated as a singleton in
+// XRE_InitChildProcess.
+class UtilityProcessImpl final : public ipc::ProcessChild {
+ public:
+ using ipc::ProcessChild::ProcessChild;
+ ~UtilityProcessImpl();
+
+ bool Init(int aArgc, char* aArgv[]) override;
+ void CleanUp() override;
+
+#if defined(XP_WIN)
+ static void LoadLibraryOrCrash(LPCWSTR aLib);
+#endif // defined(XP_WIN)
+
+ private:
+ RefPtr<UtilityProcessChild> mUtility = UtilityProcessChild::GetSingleton();
+
+#if defined(XP_WIN)
+ mozilla::mscom::ProcessRuntime mCOMRuntime;
+#endif
+};
+
+} // namespace mozilla::ipc
+
+#endif // _include_ipc_glue_UtilityProcessImpl_h__
diff --git a/ipc/glue/UtilityProcessManager.cpp b/ipc/glue/UtilityProcessManager.cpp
new file mode 100644
index 0000000000..59946cd3d3
--- /dev/null
+++ b/ipc/glue/UtilityProcessManager.cpp
@@ -0,0 +1,617 @@
+/* -*- 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/ProfilerMarkers.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"
+
+#ifdef XP_WIN
+# include "mozilla/dom/WindowsUtilsParent.h"
+#endif
+
+#include "mozilla/GeckoArgs.h"
+
+namespace mozilla::ipc {
+
+extern LazyLogModule gUtilityProcessLog;
+#define LOGD(...) MOZ_LOG(gUtilityProcessLog, LogLevel::Debug, (__VA_ARGS__))
+
+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)) {
+ LOGD("[%p] UtilityProcessManager::UtilityProcessManager", 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() {
+ LOGD("[%p] UtilityProcessManager::~UtilityProcessManager", this);
+
+ // 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() {
+ LOGD("[%p] UtilityProcessManager::OnXPCOMShutdown", this);
+
+ 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) {
+ LOGD("[%p] UtilityProcessManager::LaunchProcess SandboxingKind=%" PRIu64,
+ this, 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) {
+ LOGD(
+ "[%p] UtilityProcessManager::StartUtility actor=%p "
+ "SandboxingKind=%" PRIu64,
+ this, aActor.get(), aSandbox);
+
+ TimeStamp utilityStart = TimeStamp::Now();
+
+ 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
+ PROFILER_MARKER_TEXT(
+ "UtilityProcessManager::StartUtility", IPC,
+ MarkerOptions(MarkerTiming::InstantNow()),
+ nsPrintfCString("SandboxingKind=%" PRIu64 " aActor->CanSend()",
+ aSandbox));
+ return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
+ }
+
+ RefPtr<UtilityProcessManager> self = this;
+ return LaunchProcess(aSandbox)->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self, aActor, aSandbox, utilityStart]() {
+ 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());
+ }
+
+ PROFILER_MARKER_TEXT(
+ "UtilityProcessManager::StartUtility", IPC,
+ MarkerOptions(MarkerTiming::IntervalUntilNowFrom(utilityStart)),
+ nsPrintfCString("SandboxingKind=%" PRIu64 " Resolve", aSandbox));
+ return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
+ },
+ [self, aSandbox, utilityStart](nsresult aError) {
+ NS_WARNING("Reject StartUtility() for LaunchProcess() rejection");
+ if (!self->IsShutdown()) {
+ NS_WARNING("Reject StartUtility() when !IsShutdown()");
+ }
+ PROFILER_MARKER_TEXT(
+ "UtilityProcessManager::StartUtility", IPC,
+ MarkerOptions(MarkerTiming::IntervalUntilNowFrom(utilityStart)),
+ nsPrintfCString("SandboxingKind=%" PRIu64 " Reject", aSandbox));
+ return GenericNonExclusivePromise::CreateAndReject(aError, __func__);
+ });
+}
+
+RefPtr<UtilityProcessManager::StartRemoteDecodingUtilityPromise>
+UtilityProcessManager::StartProcessForRemoteMediaDecoding(
+ base::ProcessId aOtherProcess, dom::ContentParentId aChildId,
+ 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__);
+ }
+ TimeStamp remoteDecodingStart = TimeStamp::Now();
+
+ 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, aChildId, aSandbox,
+ remoteDecodingStart]() {
+ 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),
+ aChildId)) {
+ 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
+ PROFILER_MARKER_TEXT(
+ "UtilityProcessManager::StartProcessForRemoteMediaDecoding",
+ MEDIA,
+ MarkerOptions(
+ MarkerTiming::IntervalUntilNowFrom(remoteDecodingStart)),
+ "Resolve"_ns);
+ return StartRemoteDecodingUtilityPromise::CreateAndResolve(
+ std::move(childPipe), __func__);
+ },
+ [self, remoteDecodingStart](nsresult aError) {
+ NS_WARNING(
+ "Reject StartProcessForRemoteMediaDecoding() for "
+ "StartUtility() rejection");
+ PROFILER_MARKER_TEXT(
+ "UtilityProcessManager::StartProcessForRemoteMediaDecoding",
+ MEDIA,
+ MarkerOptions(
+ MarkerTiming::IntervalUntilNowFrom(remoteDecodingStart)),
+ "Reject"_ns);
+ return StartRemoteDecodingUtilityPromise::CreateAndReject(aError,
+ __func__);
+ });
+}
+
+RefPtr<UtilityProcessManager::JSOraclePromise>
+UtilityProcessManager::StartJSOracle(dom::JSOracleParent* aParent) {
+ return StartUtility(RefPtr{aParent}, SandboxingKind::GENERIC_UTILITY);
+}
+
+#ifdef XP_WIN
+
+// Windows Utils
+
+RefPtr<UtilityProcessManager::WindowsUtilsPromise>
+UtilityProcessManager::GetWindowsUtilsPromise() {
+ TimeStamp windowsUtilsStart = TimeStamp::Now();
+ RefPtr<UtilityProcessManager> self = this;
+ if (!mWindowsUtils) {
+ mWindowsUtils = new dom::WindowsUtilsParent();
+ }
+
+ RefPtr<dom::WindowsUtilsParent> wup = mWindowsUtils;
+ MOZ_ASSERT(wup, "Unable to get a singleton for WindowsUtils");
+ return StartUtility(wup, SandboxingKind::WINDOWS_UTILS)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self, wup, windowsUtilsStart]() {
+ if (!wup->CanSend()) {
+ MOZ_ASSERT(false, "WindowsUtilsParent can't send");
+ return WindowsUtilsPromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+ PROFILER_MARKER_TEXT(
+ "UtilityProcessManager::GetWindowsUtilsPromise", OTHER,
+ MarkerOptions(
+ MarkerTiming::IntervalUntilNowFrom(windowsUtilsStart)),
+ "Resolve"_ns);
+ return WindowsUtilsPromise::CreateAndResolve(wup, __func__);
+ },
+ [self, windowsUtilsStart](nsresult aError) {
+ NS_WARNING("StartUtility rejected promise for PWindowsUtils");
+ PROFILER_MARKER_TEXT(
+ "UtilityProcessManager::GetWindowsUtilsPromise", OTHER,
+ MarkerOptions(
+ MarkerTiming::IntervalUntilNowFrom(windowsUtilsStart)),
+ "Reject"_ns);
+ return WindowsUtilsPromise::CreateAndReject(aError, __func__);
+ });
+}
+
+void UtilityProcessManager::ReleaseWindowsUtils() { mWindowsUtils = nullptr; }
+
+#endif // XP_WIN
+
+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() {
+ LOGD("[%p] UtilityProcessManager::CleanShutdownAllProcesses", this);
+
+ for (auto& it : mProcesses) {
+ if (it) {
+ DestroyProcess(it->mSandbox);
+ }
+ }
+}
+
+void UtilityProcessManager::CleanShutdown(SandboxingKind aSandbox) {
+ LOGD("[%p] UtilityProcessManager::CleanShutdown SandboxingKind=%" PRIu64,
+ this, 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) {
+ LOGD("[%p] UtilityProcessManager::DestroyProcess SandboxingKind=%" PRIu64,
+ this, 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
diff --git a/ipc/glue/UtilityProcessManager.h b/ipc/glue/UtilityProcessManager.h
new file mode 100644
index 0000000000..2ad3f5041c
--- /dev/null
+++ b/ipc/glue/UtilityProcessManager.h
@@ -0,0 +1,234 @@
+/* -*- 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/. */
+#ifndef _include_ipc_glue_UtilityProcessManager_h_
+#define _include_ipc_glue_UtilityProcessManager_h_
+#include "mozilla/MozPromise.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/ipc/UtilityProcessHost.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/ProcInfo.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+
+#include "mozilla/PRemoteDecoderManagerChild.h"
+
+namespace mozilla {
+
+class MemoryReportingProcess;
+
+namespace dom {
+class JSOracleParent;
+class WindowsUtilsParent;
+} // namespace dom
+
+namespace ipc {
+
+class UtilityProcessParent;
+
+// The UtilityProcessManager is a singleton responsible for creating
+// Utility-bound objects that may live in another process. Currently, it
+// provides access to the Utility process via ContentParent.
+class UtilityProcessManager final : public UtilityProcessHost::Listener {
+ friend class UtilityProcessParent;
+
+ public:
+ using StartRemoteDecodingUtilityPromise =
+ MozPromise<Endpoint<PRemoteDecoderManagerChild>, nsresult, true>;
+ using JSOraclePromise = GenericNonExclusivePromise;
+
+ using WindowsUtilsPromise =
+ MozPromise<RefPtr<dom::WindowsUtilsParent>, nsresult, true>;
+
+ static void Initialize();
+ static void Shutdown();
+
+ static RefPtr<UtilityProcessManager> GetSingleton();
+
+ static RefPtr<UtilityProcessManager> GetIfExists();
+
+ // Launch a new Utility process asynchronously
+ RefPtr<GenericNonExclusivePromise> LaunchProcess(SandboxingKind aSandbox);
+
+ template <typename Actor>
+ RefPtr<GenericNonExclusivePromise> StartUtility(RefPtr<Actor> aActor,
+ SandboxingKind aSandbox);
+
+ RefPtr<StartRemoteDecodingUtilityPromise> StartProcessForRemoteMediaDecoding(
+ base::ProcessId aOtherProcess, dom::ContentParentId aChildId,
+ SandboxingKind aSandbox);
+
+ RefPtr<JSOraclePromise> StartJSOracle(mozilla::dom::JSOracleParent* aParent);
+
+#ifdef XP_WIN
+ // Get the (possibly already resolved) promise for the Windows utility
+ // process actor. Creates the process if it is not running.
+ RefPtr<WindowsUtilsPromise> GetWindowsUtilsPromise();
+ // Releases the WindowsUtils actor so that it can be destroyed.
+ // Subsequent attempts to use WindowsUtils will create a new process.
+ void ReleaseWindowsUtils();
+#endif
+
+ void OnProcessUnexpectedShutdown(UtilityProcessHost* aHost);
+
+ // Returns the platform pid for this utility sandbox process.
+ Maybe<base::ProcessId> ProcessPid(SandboxingKind aSandbox);
+
+ // Create a MemoryReportingProcess object for this utility process
+ RefPtr<MemoryReportingProcess> GetProcessMemoryReporter(
+ UtilityProcessParent* parent);
+
+ // Returns access to the PUtility protocol if a Utility process for that
+ // sandbox is present.
+ RefPtr<UtilityProcessParent> GetProcessParent(SandboxingKind aSandbox) {
+ RefPtr<ProcessFields> p = GetProcess(aSandbox);
+ if (!p) {
+ return nullptr;
+ }
+ return p->mProcessParent;
+ }
+
+ // Get a list of all valid utility process parent references
+ nsTArray<RefPtr<UtilityProcessParent>> GetAllProcessesProcessParent() {
+ nsTArray<RefPtr<UtilityProcessParent>> rv;
+ for (auto& p : mProcesses) {
+ if (p && p->mProcessParent) {
+ rv.AppendElement(p->mProcessParent);
+ }
+ }
+ return rv;
+ }
+
+ // Returns the Utility Process for that sandbox
+ UtilityProcessHost* Process(SandboxingKind aSandbox) {
+ RefPtr<ProcessFields> p = GetProcess(aSandbox);
+ if (!p) {
+ return nullptr;
+ }
+ return p->mProcess;
+ }
+
+ void RegisterActor(const RefPtr<UtilityProcessParent>& aParent,
+ UtilityActorName aActorName) {
+ for (auto& p : mProcesses) {
+ if (p && p->mProcessParent && p->mProcessParent == aParent) {
+ p->mActors.AppendElement(aActorName);
+ return;
+ }
+ }
+ }
+
+ Span<const UtilityActorName> GetActors(
+ const RefPtr<UtilityProcessParent>& aParent) {
+ for (auto& p : mProcesses) {
+ if (p && p->mProcessParent && p->mProcessParent == aParent) {
+ return p->mActors;
+ }
+ }
+ return {};
+ }
+
+ Span<const UtilityActorName> GetActors(GeckoChildProcessHost* aHost) {
+ for (auto& p : mProcesses) {
+ if (p && p->mProcess == aHost) {
+ return p->mActors;
+ }
+ }
+ return {};
+ }
+
+ Span<const UtilityActorName> GetActors(SandboxingKind aSbKind) {
+ auto proc = GetProcess(aSbKind);
+ if (!proc) {
+ return {};
+ }
+ return proc->mActors;
+ }
+
+ // Shutdown the Utility process for that sandbox.
+ void CleanShutdown(SandboxingKind aSandbox);
+
+ // Shutdown all utility processes
+ void CleanShutdownAllProcesses();
+
+ uint16_t AliveProcesses();
+
+ private:
+ ~UtilityProcessManager();
+
+ bool IsProcessLaunching(SandboxingKind aSandbox);
+ bool IsProcessDestroyed(SandboxingKind aSandbox);
+
+ // Called from our xpcom-shutdown observer.
+ void OnXPCOMShutdown();
+ void OnPreferenceChange(const char16_t* aData);
+
+ UtilityProcessManager();
+
+ void DestroyProcess(SandboxingKind aSandbox);
+
+ bool IsShutdown() const;
+
+ class Observer final : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ explicit Observer(RefPtr<UtilityProcessManager> aManager);
+
+ protected:
+ ~Observer() = default;
+
+ RefPtr<UtilityProcessManager> mManager;
+ };
+ friend class Observer;
+
+ RefPtr<Observer> mObserver;
+
+ class ProcessFields final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProcessFields);
+
+ explicit ProcessFields(SandboxingKind aSandbox) : mSandbox(aSandbox){};
+
+ // Promise will be resolved when this Utility process has been fully started
+ // and configured. Only accessed on the main thread.
+ RefPtr<GenericNonExclusivePromise> mLaunchPromise;
+
+ uint32_t mNumProcessAttempts = 0;
+ uint32_t mNumUnexpectedCrashes = 0;
+
+ // Fields that are associated with the current Utility process.
+ UtilityProcessHost* mProcess = nullptr;
+ RefPtr<UtilityProcessParent> mProcessParent = nullptr;
+
+ // Collects any pref changes that occur during process launch (after
+ // the initial map is passed in command-line arguments) to be sent
+ // when the process can receive IPC messages.
+ nsTArray<dom::Pref> mQueuedPrefs;
+
+ nsTArray<UtilityActorName> mActors;
+
+ SandboxingKind mSandbox = SandboxingKind::COUNT;
+
+ protected:
+ ~ProcessFields() = default;
+ };
+
+ EnumeratedArray<SandboxingKind, SandboxingKind::COUNT, RefPtr<ProcessFields>>
+ mProcesses;
+
+ RefPtr<ProcessFields> GetProcess(SandboxingKind);
+ bool NoMoreProcesses();
+
+#ifdef XP_WIN
+ RefPtr<dom::WindowsUtilsParent> mWindowsUtils;
+#endif // XP_WIN
+};
+
+} // namespace ipc
+
+} // namespace mozilla
+
+#endif // _include_ipc_glue_UtilityProcessManager_h_
diff --git a/ipc/glue/UtilityProcessParent.cpp b/ipc/glue/UtilityProcessParent.cpp
new file mode 100644
index 0000000000..4a98b5deb1
--- /dev/null
+++ b/ipc/glue/UtilityProcessParent.cpp
@@ -0,0 +1,201 @@
+/* -*- 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 "mozilla/ipc/UtilityProcessParent.h"
+#include "mozilla/ipc/UtilityProcessManager.h"
+
+#if defined(XP_WIN)
+# include <dwrite.h>
+# include <process.h>
+# include "mozilla/WinDllServices.h"
+#endif
+
+#include "mozilla/ipc/ProcessChild.h"
+#include "mozilla/FOGIPC.h"
+
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryIPC.h"
+
+#include "nsHashPropertyBag.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+
+namespace mozilla::ipc {
+
+UtilityProcessParent::UtilityProcessParent(UtilityProcessHost* aHost)
+ : mHost(aHost) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mHost);
+}
+
+UtilityProcessParent::~UtilityProcessParent() = default;
+
+bool UtilityProcessParent::SendRequestMemoryReport(
+ const uint32_t& aGeneration, const bool& aAnonymize,
+ const bool& aMinimizeMemoryUsage, const Maybe<FileDescriptor>& aDMDFile) {
+ mMemoryReportRequest = MakeUnique<MemoryReportRequestHost>(aGeneration);
+
+ PUtilityProcessParent::SendRequestMemoryReport(
+ aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile,
+ [self = RefPtr{this}](const uint32_t& aGeneration2) {
+ if (self->mMemoryReportRequest) {
+ self->mMemoryReportRequest->Finish(aGeneration2);
+ self->mMemoryReportRequest = nullptr;
+ }
+ },
+ [self = RefPtr{this}](mozilla::ipc::ResponseRejectReason) {
+ self->mMemoryReportRequest = nullptr;
+ });
+
+ return true;
+}
+
+mozilla::ipc::IPCResult UtilityProcessParent::RecvAddMemoryReport(
+ const MemoryReport& aReport) {
+ if (mMemoryReportRequest) {
+ mMemoryReportRequest->RecvReport(aReport);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UtilityProcessParent::RecvFOGData(ByteBuf&& aBuf) {
+ glean::FOGData(std::move(aBuf));
+ return IPC_OK();
+}
+
+#if defined(XP_WIN)
+mozilla::ipc::IPCResult UtilityProcessParent::RecvGetModulesTrust(
+ ModulePaths&& aModPaths, bool aRunAtNormalPriority,
+ GetModulesTrustResolver&& aResolver) {
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ dllSvc->GetModulesTrust(std::move(aModPaths), aRunAtNormalPriority)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [aResolver](ModulesMapResult&& aResult) {
+ aResolver(Some(ModulesMapResult(std::move(aResult))));
+ },
+ [aResolver](nsresult aRv) { aResolver(Nothing()); });
+ return IPC_OK();
+}
+#endif // defined(XP_WIN)
+
+mozilla::ipc::IPCResult UtilityProcessParent::RecvAccumulateChildHistograms(
+ nsTArray<HistogramAccumulation>&& aAccumulations) {
+ TelemetryIPC::AccumulateChildHistograms(Telemetry::ProcessID::Utility,
+ aAccumulations);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+UtilityProcessParent::RecvAccumulateChildKeyedHistograms(
+ nsTArray<KeyedHistogramAccumulation>&& aAccumulations) {
+ TelemetryIPC::AccumulateChildKeyedHistograms(Telemetry::ProcessID::Utility,
+ aAccumulations);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UtilityProcessParent::RecvUpdateChildScalars(
+ nsTArray<ScalarAction>&& aScalarActions) {
+ TelemetryIPC::UpdateChildScalars(Telemetry::ProcessID::Utility,
+ aScalarActions);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UtilityProcessParent::RecvUpdateChildKeyedScalars(
+ nsTArray<KeyedScalarAction>&& aScalarActions) {
+ TelemetryIPC::UpdateChildKeyedScalars(Telemetry::ProcessID::Utility,
+ aScalarActions);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UtilityProcessParent::RecvRecordChildEvents(
+ nsTArray<mozilla::Telemetry::ChildEventData>&& aEvents) {
+ TelemetryIPC::RecordChildEvents(Telemetry::ProcessID::Utility, aEvents);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UtilityProcessParent::RecvRecordDiscardedData(
+ const mozilla::Telemetry::DiscardedData& aDiscardedData) {
+ TelemetryIPC::RecordDiscardedData(Telemetry::ProcessID::Utility,
+ aDiscardedData);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UtilityProcessParent::RecvInitCompleted() {
+ MOZ_ASSERT(mHost);
+ mHost->ResolvePromise();
+ return IPC_OK();
+}
+
+void UtilityProcessParent::ActorDestroy(ActorDestroyReason aWhy) {
+ RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
+
+ if (aWhy == AbnormalShutdown) {
+ nsAutoString dumpID;
+
+ if (mCrashReporter) {
+#if defined(MOZ_SANDBOX)
+ RefPtr<mozilla::ipc::UtilityProcessManager> upm =
+ mozilla::ipc::UtilityProcessManager::GetSingleton();
+ if (upm) {
+ Span<const UtilityActorName> actors = upm->GetActors(this);
+ nsAutoCString actorsName;
+ if (!actors.IsEmpty()) {
+ actorsName += GetUtilityActorName(actors.First<1>()[0]);
+ for (const auto& actor : actors.From(1)) {
+ actorsName += ", "_ns + GetUtilityActorName(actor);
+ }
+ }
+ mCrashReporter->AddAnnotation(
+ CrashReporter::Annotation::UtilityActorsName, actorsName);
+ }
+#endif
+ }
+
+ GenerateCrashReport(OtherPid(), &dumpID);
+
+ // It's okay for dumpID to be empty if there was no minidump generated
+ // tests like ipc/glue/test/browser/browser_utility_crashReporter.js are
+ // there to verify this
+ if (!dumpID.IsEmpty()) {
+ props->SetPropertyAsAString(u"dumpID"_ns, dumpID);
+ }
+ }
+
+ nsAutoString pid;
+ pid.AppendInt(static_cast<uint64_t>(OtherPid()));
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers((nsIPropertyBag2*)props, "ipc:utility-shutdown",
+ pid.get());
+ } else {
+ NS_WARNING("Could not get a nsIObserverService, ipc:utility-shutdown skip");
+ }
+
+ mHost->OnChannelClosed();
+}
+
+// To ensure that IPDL is finished before UtilityParent gets deleted.
+class DeferredDeleteUtilityProcessParent : public Runnable {
+ public:
+ explicit DeferredDeleteUtilityProcessParent(
+ RefPtr<UtilityProcessParent> aParent)
+ : Runnable("ipc::glue::DeferredDeleteUtilityProcessParent"),
+ mParent(std::move(aParent)) {}
+
+ NS_IMETHODIMP Run() override { return NS_OK; }
+
+ private:
+ RefPtr<UtilityProcessParent> mParent;
+};
+
+/* static */
+void UtilityProcessParent::Destroy(RefPtr<UtilityProcessParent> aParent) {
+ NS_DispatchToMainThread(
+ new DeferredDeleteUtilityProcessParent(std::move(aParent)));
+}
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/UtilityProcessParent.h b/ipc/glue/UtilityProcessParent.h
new file mode 100644
index 0000000000..6ff907583f
--- /dev/null
+++ b/ipc/glue/UtilityProcessParent.h
@@ -0,0 +1,77 @@
+/* -*- 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/. */
+#ifndef _include_ipc_glue_UtilityProcessParent_h__
+#define _include_ipc_glue_UtilityProcessParent_h__
+#include "mozilla/ipc/PUtilityProcessParent.h"
+#include "mozilla/ipc/CrashReporterHelper.h"
+#include "mozilla/ipc/UtilityProcessHost.h"
+#include "mozilla/dom/MemoryReportRequest.h"
+
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+
+namespace ipc {
+
+class UtilityProcessHost;
+
+class UtilityProcessParent final
+ : public PUtilityProcessParent,
+ public ipc::CrashReporterHelper<GeckoProcessType_Utility> {
+ typedef mozilla::dom::MemoryReportRequestHost MemoryReportRequestHost;
+
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UtilityProcessParent, override);
+ friend class UtilityProcessHost;
+
+ explicit UtilityProcessParent(UtilityProcessHost* aHost);
+
+ mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport);
+
+ bool SendRequestMemoryReport(const uint32_t& aGeneration,
+ const bool& aAnonymize,
+ const bool& aMinimizeMemoryUsage,
+ const Maybe<ipc::FileDescriptor>& aDMDFile);
+
+ mozilla::ipc::IPCResult RecvFOGData(ByteBuf&& aBuf);
+
+#if defined(XP_WIN)
+ mozilla::ipc::IPCResult RecvGetModulesTrust(
+ ModulePaths&& aModPaths, bool aRunAtNormalPriority,
+ GetModulesTrustResolver&& aResolver);
+#endif // defined(XP_WIN)
+
+ mozilla::ipc::IPCResult RecvAccumulateChildHistograms(
+ nsTArray<HistogramAccumulation>&& aAccumulations);
+ mozilla::ipc::IPCResult RecvAccumulateChildKeyedHistograms(
+ nsTArray<KeyedHistogramAccumulation>&& aAccumulations);
+ mozilla::ipc::IPCResult RecvUpdateChildScalars(
+ nsTArray<ScalarAction>&& aScalarActions);
+ mozilla::ipc::IPCResult RecvUpdateChildKeyedScalars(
+ nsTArray<KeyedScalarAction>&& aScalarActions);
+ mozilla::ipc::IPCResult RecvRecordChildEvents(
+ nsTArray<ChildEventData>&& events);
+ mozilla::ipc::IPCResult RecvRecordDiscardedData(
+ const DiscardedData& aDiscardedData);
+
+ mozilla::ipc::IPCResult RecvInitCompleted();
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ private:
+ UtilityProcessHost* mHost;
+ UniquePtr<MemoryReportRequestHost> mMemoryReportRequest{};
+
+ ~UtilityProcessParent();
+
+ static void Destroy(RefPtr<UtilityProcessParent> aParent);
+};
+
+} // namespace ipc
+
+} // namespace mozilla
+
+#endif // _include_ipc_glue_UtilityProcessParent_h__
diff --git a/ipc/glue/UtilityProcessSandboxing.h b/ipc/glue/UtilityProcessSandboxing.h
new file mode 100644
index 0000000000..7aac77e0e9
--- /dev/null
+++ b/ipc/glue/UtilityProcessSandboxing.h
@@ -0,0 +1,42 @@
+/* -*- 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/. */
+#ifndef _include_ipc_glue_UtilityProcessSandboxing_h_
+#define _include_ipc_glue_UtilityProcessSandboxing_h_
+
+#include <stdint.h>
+
+namespace mozilla {
+
+namespace ipc {
+
+// When adding a new value, the checks within UtilityProcessImpl::Init() needs
+// to be updated as well.
+enum SandboxingKind : uint64_t {
+
+ GENERIC_UTILITY,
+
+#ifdef MOZ_APPLEMEDIA
+ UTILITY_AUDIO_DECODING_APPLE_MEDIA,
+#endif
+#ifdef XP_WIN
+ UTILITY_AUDIO_DECODING_WMF,
+#endif
+#ifdef MOZ_WMF_MEDIA_ENGINE
+ MF_MEDIA_ENGINE_CDM,
+#endif
+#ifdef XP_WIN
+ WINDOWS_UTILS,
+#endif
+
+ COUNT,
+
+};
+
+} // namespace ipc
+
+} // namespace mozilla
+
+#endif // _include_ipc_glue_UtilityProcessSandboxing_h_
diff --git a/ipc/glue/WindowsMessageLoop.cpp b/ipc/glue/WindowsMessageLoop.cpp
new file mode 100644
index 0000000000..51bb7c8531
--- /dev/null
+++ b/ipc/glue/WindowsMessageLoop.cpp
@@ -0,0 +1,1205 @@
+/* -*- 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 "mozilla/DebugOnly.h"
+
+#include "WindowsMessageLoop.h"
+#include "Neutering.h"
+#include "MessageChannel.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "WinUtils.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/dom/JSExecutionManager.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/mscom/Utils.h"
+#include "mozilla/PaintTracker.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsProcessMitigations.h"
+
+using namespace mozilla;
+using namespace mozilla::ipc;
+using namespace mozilla::ipc::windows;
+
+/**
+ * The Windows-only code below exists to solve a general problem with deadlocks
+ * that we experience when sending synchronous IPC messages to processes that
+ * contain native windows (i.e. HWNDs). Windows (the OS) sends synchronous
+ * messages between parent and child HWNDs in multiple circumstances (e.g.
+ * WM_PARENTNOTIFY, WM_NCACTIVATE, etc.), even when those HWNDs are controlled
+ * by different threads or different processes. Thus we can very easily end up
+ * in a deadlock by a call stack like the following:
+ *
+ * Process A:
+ * - CreateWindow(...) creates a "parent" HWND.
+ * - SendCreateChildWidget(HWND) is a sync IPC message that sends the "parent"
+ * HWND over to Process B. Process A blocks until a response is received
+ * from Process B.
+ *
+ * Process B:
+ * - RecvCreateWidget(HWND) gets the "parent" HWND from Process A.
+ * - CreateWindow(..., HWND) creates a "child" HWND with the parent from
+ * process A.
+ * - Windows (the OS) generates a WM_PARENTNOTIFY message that is sent
+ * synchronously to Process A. Process B blocks until a response is
+ * received from Process A. Process A, however, is blocked and cannot
+ * process the message. Both processes are deadlocked.
+ *
+ * The example above has a few different workarounds (e.g. setting the
+ * WS_EX_NOPARENTNOTIFY style on the child window) but the general problem is
+ * persists. Once two HWNDs are parented we must not block their owning
+ * threads when manipulating either HWND.
+ *
+ * Windows requires any application that hosts native HWNDs to always process
+ * messages or risk deadlock. Given our architecture the only way to meet
+ * Windows' requirement and allow for synchronous IPC messages is to pump a
+ * miniature message loop during a sync IPC call. We avoid processing any
+ * queued messages during the loop (with one exception, see below), but
+ * "nonqueued" messages (see
+ * http://msdn.microsoft.com/en-us/library/ms644927(VS.85).aspx under the
+ * section "Nonqueued messages") cannot be avoided. Those messages are trapped
+ * in a special window procedure where we can either ignore the message or
+ * process it in some fashion.
+ *
+ * Queued and "non-queued" messages will be processed during Interrupt calls if
+ * modal UI related api calls block an Interrupt in-call in the child. To
+ * prevent windows from freezing, and to allow concurrent processing of critical
+ * events (such as painting), we spin a native event dispatch loop while
+ * these in-calls are blocked.
+ */
+
+#if defined(ACCESSIBILITY)
+// pulled from accessibility's win utils
+extern const wchar_t* kPropNameTabContent;
+#endif
+
+// widget related message id constants we need to defer, see nsAppShell.
+extern UINT sAppShellGeckoMsgId;
+
+namespace {
+
+const wchar_t kOldWndProcProp[] = L"MozillaIPCOldWndProc";
+const wchar_t k3rdPartyWindowProp[] = L"Mozilla3rdPartyWindow";
+
+// This isn't defined before Windows XP.
+enum { WM_XP_THEMECHANGED = 0x031A };
+
+nsTArray<HWND>* gNeuteredWindows = nullptr;
+
+typedef nsTArray<UniquePtr<DeferredMessage>> DeferredMessageArray;
+DeferredMessageArray* gDeferredMessages = nullptr;
+
+HHOOK gDeferredGetMsgHook = nullptr;
+HHOOK gDeferredCallWndProcHook = nullptr;
+
+DWORD gUIThreadId = 0;
+HWND gCOMWindow = 0;
+// Once initialized, gWinEventHook is never unhooked. We save the handle so
+// that we can check whether or not the hook is initialized.
+HWINEVENTHOOK gWinEventHook = nullptr;
+const wchar_t kCOMWindowClassName[] = L"OleMainThreadWndClass";
+
+// WM_GETOBJECT id pulled from uia headers
+#define MOZOBJID_UIAROOT -25
+
+HWND FindCOMWindow() {
+ MOZ_ASSERT(gUIThreadId);
+
+ HWND last = 0;
+ while (
+ (last = FindWindowExW(HWND_MESSAGE, last, kCOMWindowClassName, NULL))) {
+ if (GetWindowThreadProcessId(last, NULL) == gUIThreadId) {
+ return last;
+ }
+ }
+
+ return (HWND)0;
+}
+
+void CALLBACK WinEventHook(HWINEVENTHOOK aWinEventHook, DWORD aEvent,
+ HWND aHwnd, LONG aIdObject, LONG aIdChild,
+ DWORD aEventThread, DWORD aMsEventTime) {
+ MOZ_ASSERT(aWinEventHook == gWinEventHook);
+ MOZ_ASSERT(gUIThreadId == aEventThread);
+ switch (aEvent) {
+ case EVENT_OBJECT_CREATE: {
+ if (aIdObject != OBJID_WINDOW || aIdChild != CHILDID_SELF) {
+ // Not an event we're interested in
+ return;
+ }
+ wchar_t classBuf[256] = {0};
+ int result = ::GetClassNameW(aHwnd, classBuf, MOZ_ARRAY_LENGTH(classBuf));
+ if (result != (MOZ_ARRAY_LENGTH(kCOMWindowClassName) - 1) ||
+ wcsncmp(kCOMWindowClassName, classBuf, result)) {
+ // Not a class we're interested in
+ return;
+ }
+ MOZ_ASSERT(FindCOMWindow() == aHwnd);
+ gCOMWindow = aHwnd;
+ break;
+ }
+ case EVENT_OBJECT_DESTROY: {
+ if (aHwnd == gCOMWindow && aIdObject == OBJID_WINDOW) {
+ MOZ_ASSERT(aIdChild == CHILDID_SELF);
+ gCOMWindow = 0;
+ }
+ break;
+ }
+ default: {
+ return;
+ }
+ }
+}
+
+LRESULT CALLBACK DeferredMessageHook(int nCode, WPARAM wParam, LPARAM lParam) {
+ // XXX This function is called for *both* the WH_CALLWNDPROC hook and the
+ // WH_GETMESSAGE hook, but they have different parameters. We don't
+ // use any of them except nCode which has the same meaning.
+
+ // Only run deferred messages if all of these conditions are met:
+ // 1. The |nCode| indicates that this hook should do something.
+ // 2. We have deferred messages to run.
+ // 3. We're not being called from the PeekMessage within the WaitFor*Notify
+ // function (indicated with MessageChannel::IsPumpingMessages). We really
+ // only want to run after returning to the main event loop.
+ if (nCode >= 0 && gDeferredMessages && !MessageChannel::IsPumpingMessages()) {
+ NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook,
+ "These hooks must be set if we're being called!");
+ NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!");
+
+ // Unset hooks first, in case we reenter below.
+ UnhookWindowsHookEx(gDeferredGetMsgHook);
+ UnhookWindowsHookEx(gDeferredCallWndProcHook);
+ gDeferredGetMsgHook = 0;
+ gDeferredCallWndProcHook = 0;
+
+ // Unset the global and make sure we delete it when we're done here.
+ auto messages = WrapUnique(gDeferredMessages);
+ gDeferredMessages = nullptr;
+
+ // Run all the deferred messages in order.
+ uint32_t count = messages->Length();
+ for (uint32_t index = 0; index < count; index++) {
+ messages->ElementAt(index)->Run();
+ }
+ }
+
+ // Always call the next hook.
+ return CallNextHookEx(nullptr, nCode, wParam, lParam);
+}
+
+void ScheduleDeferredMessageRun() {
+ if (gDeferredMessages && !(gDeferredGetMsgHook && gDeferredCallWndProcHook)) {
+ NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!");
+
+ gDeferredGetMsgHook = ::SetWindowsHookEx(WH_GETMESSAGE, DeferredMessageHook,
+ nullptr, gUIThreadId);
+ gDeferredCallWndProcHook = ::SetWindowsHookEx(
+ WH_CALLWNDPROC, DeferredMessageHook, nullptr, gUIThreadId);
+ NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook,
+ "Failed to set hooks!");
+ }
+}
+
+static void DumpNeuteredMessage(HWND hwnd, UINT uMsg) {
+#ifdef DEBUG
+ nsAutoCString log("Received \"nonqueued\" ");
+ // classify messages
+ if (uMsg < WM_USER) {
+ const char* msgText = mozilla::widget::WinUtils::WinEventToEventName(uMsg);
+ if (msgText) {
+ log.AppendPrintf("ui message \"%s\"", msgText);
+ } else {
+ log.AppendPrintf("ui message (0x%X)", uMsg);
+ }
+ } else if (uMsg >= WM_USER && uMsg < WM_APP) {
+ log.AppendPrintf("WM_USER message (0x%X)", uMsg);
+ } else if (uMsg >= WM_APP && uMsg < 0xC000) {
+ log.AppendPrintf("WM_APP message (0x%X)", uMsg);
+ } else if (uMsg >= 0xC000 && uMsg < 0x10000) {
+ log.AppendPrintf("registered windows message (0x%X)", uMsg);
+ } else {
+ log.AppendPrintf("system message (0x%X)", uMsg);
+ }
+
+ log.AppendLiteral(" during a synchronous IPC message for window ");
+ log.AppendPrintf("0x%p", hwnd);
+
+ wchar_t className[256] = {0};
+ if (GetClassNameW(hwnd, className, sizeof(className) - 1) > 0) {
+ log.AppendLiteral(" (\"");
+ log.Append(NS_ConvertUTF16toUTF8((char16_t*)className));
+ log.AppendLiteral("\")");
+ }
+
+ log.AppendLiteral(
+ ", sending it to DefWindowProc instead of the normal "
+ "window procedure.");
+ NS_ERROR(log.get());
+#endif
+}
+
+LRESULT
+ProcessOrDeferMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ UniquePtr<DeferredMessage> deferred;
+
+ // Most messages ask for 0 to be returned if the message is processed.
+ LRESULT res = 0;
+
+ switch (uMsg) {
+ // Messages that can be deferred as-is. These must not contain pointers in
+ // their wParam or lParam arguments!
+ case WM_ACTIVATE:
+ case WM_ACTIVATEAPP:
+ case WM_CANCELMODE:
+ case WM_CAPTURECHANGED:
+ case WM_CHILDACTIVATE:
+ case WM_DESTROY:
+ case WM_ENABLE:
+ case WM_IME_NOTIFY:
+ case WM_IME_SETCONTEXT:
+ case WM_KILLFOCUS:
+ case WM_MOUSEWHEEL:
+ case WM_NCDESTROY:
+ case WM_PARENTNOTIFY:
+ case WM_SETFOCUS:
+ case WM_SYSCOMMAND:
+ case WM_DISPLAYCHANGE:
+ case WM_SHOWWINDOW: // Intentional fall-through.
+ case WM_XP_THEMECHANGED: {
+ deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam);
+ break;
+ }
+
+ case WM_DEVICECHANGE:
+ case WM_POWERBROADCAST:
+ case WM_NCACTIVATE: // Intentional fall-through.
+ case WM_SETCURSOR: {
+ // Friggin unconventional return value...
+ res = TRUE;
+ deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam);
+ break;
+ }
+
+ case WM_MOUSEACTIVATE: {
+ res = MA_NOACTIVATE;
+ deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam);
+ break;
+ }
+
+ // These messages need to use the RedrawWindow function to generate the
+ // right kind of message. We can't simply fake them as the MSDN docs say
+ // explicitly that paint messages should not be sent by an application.
+ case WM_ERASEBKGND: {
+ UINT flags = RDW_INVALIDATE | RDW_ERASE | RDW_NOINTERNALPAINT |
+ RDW_NOFRAME | RDW_NOCHILDREN | RDW_ERASENOW;
+ deferred = MakeUnique<DeferredRedrawMessage>(hwnd, flags);
+ break;
+ }
+
+ // This message will generate a WM_PAINT message if there are invalid
+ // areas.
+ case WM_PAINT: {
+ deferred = MakeUnique<DeferredUpdateMessage>(hwnd);
+ break;
+ }
+
+ // This message holds a string in its lParam that we must copy.
+ case WM_SETTINGCHANGE: {
+ deferred =
+ MakeUnique<DeferredSettingChangeMessage>(hwnd, uMsg, wParam, lParam);
+ break;
+ }
+
+ // These messages are faked via a call to SetWindowPos.
+ case WM_WINDOWPOSCHANGED: {
+ deferred = MakeUnique<DeferredWindowPosMessage>(hwnd, lParam);
+ break;
+ }
+ case WM_NCCALCSIZE: {
+ deferred =
+ MakeUnique<DeferredWindowPosMessage>(hwnd, lParam, true, wParam);
+ break;
+ }
+
+ case WM_COPYDATA: {
+ deferred =
+ MakeUnique<DeferredCopyDataMessage>(hwnd, uMsg, wParam, lParam);
+ res = TRUE;
+ break;
+ }
+
+ case WM_STYLECHANGED: {
+ deferred = MakeUnique<DeferredStyleChangeMessage>(hwnd, wParam, lParam);
+ break;
+ }
+
+ case WM_SETICON: {
+ deferred = MakeUnique<DeferredSetIconMessage>(hwnd, uMsg, wParam, lParam);
+ break;
+ }
+
+ // Messages that are safe to pass to DefWindowProc go here.
+ case WM_ENTERIDLE:
+ case WM_GETICON:
+ case WM_NCPAINT: // (never trap nc paint events)
+ case WM_GETMINMAXINFO:
+ case WM_GETTEXT:
+ case WM_NCHITTEST:
+ case WM_STYLECHANGING: // Intentional fall-through.
+ case WM_WINDOWPOSCHANGING:
+ case WM_GETTEXTLENGTH: {
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+ }
+
+ // Just return, prevents DefWindowProc from messaging the window
+ // syncronously with other events, which may be deferred. Prevents
+ // random shutdown of aero composition on the window.
+ case WM_SYNCPAINT:
+ return 0;
+
+ // This message causes QuickTime to make re-entrant calls.
+ // Simply discarding it doesn't seem to hurt anything.
+ case WM_APP - 1:
+ return 0;
+
+ // We only support a query for our IAccessible or UIA pointers.
+ // This should be safe, and needs to be sync.
+#if defined(ACCESSIBILITY)
+ case WM_GETOBJECT: {
+ if (!::GetPropW(hwnd, k3rdPartyWindowProp)) {
+ LONG objId = static_cast<LONG>(lParam);
+ if (objId == OBJID_CLIENT || objId == MOZOBJID_UIAROOT) {
+ WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp);
+ if (oldWndProc) {
+ return CallWindowProcW(oldWndProc, hwnd, uMsg, wParam, lParam);
+ }
+ }
+ }
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+ }
+#endif // ACCESSIBILITY
+
+ default: {
+ // Unknown messages only are logged in debug builds and sent to
+ // DefWindowProc.
+ if (uMsg && uMsg == sAppShellGeckoMsgId) {
+ // Widget's registered native event callback
+ deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam);
+ }
+ }
+ }
+
+ // No deferred message was created and we land here, this is an
+ // unhandled message.
+ if (!deferred) {
+ DumpNeuteredMessage(hwnd, uMsg);
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+ }
+
+ // Create the deferred message array if it doesn't exist already.
+ if (!gDeferredMessages) {
+ gDeferredMessages = new DeferredMessageArray(20);
+ }
+
+ // Save for later. The array takes ownership of |deferred|.
+ gDeferredMessages->AppendElement(std::move(deferred));
+ return res;
+}
+
+} // namespace
+
+LRESULT CALLBACK NeuteredWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+ LPARAM lParam) {
+ WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp);
+ if (!oldWndProc) {
+ // We should really never ever get here.
+ NS_ERROR("No old wndproc!");
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+ }
+
+ // See if we care about this message. We may either ignore it, send it to
+ // DefWindowProc, or defer it for later.
+ return ProcessOrDeferMessage(hwnd, uMsg, wParam, lParam);
+}
+
+namespace {
+
+static bool WindowIsDeferredWindow(HWND hWnd) {
+ if (!IsWindow(hWnd)) {
+ NS_WARNING("Window has died!");
+ return false;
+ }
+
+ char16_t buffer[256] = {0};
+ int length = GetClassNameW(hWnd, (wchar_t*)buffer, sizeof(buffer) - 1);
+ if (length <= 0) {
+ NS_WARNING("Failed to get class name!");
+ return false;
+ }
+
+#if defined(ACCESSIBILITY)
+ // Tab content creates a window that responds to accessible WM_GETOBJECT
+ // calls. This window can safely be ignored.
+ if (::GetPropW(hWnd, kPropNameTabContent)) {
+ return false;
+ }
+#endif
+
+ // Common mozilla windows we must defer messages to.
+ nsDependentString className(buffer, length);
+ if (StringBeginsWith(className, u"Mozilla"_ns) ||
+ StringBeginsWith(className, u"Gecko"_ns) ||
+ className.EqualsLiteral("nsToolkitClass") ||
+ className.EqualsLiteral("nsAppShell:EventWindowClass")) {
+ return true;
+ }
+
+ // Plugin windows that can trigger ipc calls in child:
+ // 'ShockwaveFlashFullScreen' - flash fullscreen window
+ if (className.EqualsLiteral("ShockwaveFlashFullScreen")) {
+ SetPropW(hWnd, k3rdPartyWindowProp, (HANDLE)1);
+ return true;
+ }
+
+ return false;
+}
+
+bool NeuterWindowProcedure(HWND hWnd) {
+ if (!WindowIsDeferredWindow(hWnd)) {
+ // Some other kind of window, skip.
+ return false;
+ }
+
+ NS_ASSERTION(!GetProp(hWnd, kOldWndProcProp), "This should always be null!");
+
+ // It's possible to get nullptr out of SetWindowLongPtr, and the only way to
+ // know if that's a valid old value is to use GetLastError. Clear the error
+ // here so we can tell.
+ SetLastError(ERROR_SUCCESS);
+
+ LONG_PTR currentWndProc =
+ SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)NeuteredWindowProc);
+ if (!currentWndProc) {
+ if (ERROR_SUCCESS == GetLastError()) {
+ // No error, so we set something and must therefore reset it.
+ SetWindowLongPtr(hWnd, GWLP_WNDPROC, currentWndProc);
+ }
+ return false;
+ }
+
+ NS_ASSERTION(currentWndProc != (LONG_PTR)NeuteredWindowProc,
+ "This shouldn't be possible!");
+
+ if (!SetProp(hWnd, kOldWndProcProp, (HANDLE)currentWndProc)) {
+ // Cleanup
+ NS_WARNING("SetProp failed!");
+ SetWindowLongPtr(hWnd, GWLP_WNDPROC, currentWndProc);
+ RemovePropW(hWnd, kOldWndProcProp);
+ RemovePropW(hWnd, k3rdPartyWindowProp);
+ return false;
+ }
+
+ return true;
+}
+
+void RestoreWindowProcedure(HWND hWnd) {
+ NS_ASSERTION(WindowIsDeferredWindow(hWnd),
+ "Not a deferred window, this shouldn't be in our list!");
+ LONG_PTR oldWndProc = (LONG_PTR)GetProp(hWnd, kOldWndProcProp);
+ if (oldWndProc) {
+ NS_ASSERTION(oldWndProc != (LONG_PTR)NeuteredWindowProc,
+ "This shouldn't be possible!");
+
+ DebugOnly<LONG_PTR> currentWndProc =
+ SetWindowLongPtr(hWnd, GWLP_WNDPROC, oldWndProc);
+ NS_ASSERTION(currentWndProc == (LONG_PTR)NeuteredWindowProc,
+ "This should never be switched out from under us!");
+ }
+ RemovePropW(hWnd, kOldWndProcProp);
+ RemovePropW(hWnd, k3rdPartyWindowProp);
+}
+
+LRESULT CALLBACK CallWindowProcedureHook(int nCode, WPARAM wParam,
+ LPARAM lParam) {
+ if (nCode >= 0) {
+ NS_ASSERTION(gNeuteredWindows, "This should never be null!");
+
+ HWND hWnd = reinterpret_cast<CWPSTRUCT*>(lParam)->hwnd;
+
+ if (!gNeuteredWindows->Contains(hWnd) &&
+ !SuppressedNeuteringRegion::IsNeuteringSuppressed() &&
+ NeuterWindowProcedure(hWnd)) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ gNeuteredWindows->AppendElement(hWnd);
+ }
+ }
+ return CallNextHookEx(nullptr, nCode, wParam, lParam);
+}
+
+inline void AssertWindowIsNotNeutered(HWND hWnd) {
+#ifdef DEBUG
+ // Make sure our neutered window hook isn't still in place.
+ LONG_PTR wndproc = GetWindowLongPtr(hWnd, GWLP_WNDPROC);
+ NS_ASSERTION(wndproc != (LONG_PTR)NeuteredWindowProc, "Window is neutered!");
+#endif
+}
+
+void UnhookNeuteredWindows() {
+ if (!gNeuteredWindows) return;
+ uint32_t count = gNeuteredWindows->Length();
+ for (uint32_t index = 0; index < count; index++) {
+ RestoreWindowProcedure(gNeuteredWindows->ElementAt(index));
+ }
+ gNeuteredWindows->Clear();
+}
+
+// This timeout stuff assumes a sane value of mTimeoutMs (less than the overflow
+// value for GetTickCount(), which is something like 50 days). It uses the
+// cheapest (and least accurate) method supported by Windows 2000.
+
+struct TimeoutData {
+ DWORD startTicks;
+ DWORD targetTicks;
+};
+
+void InitTimeoutData(TimeoutData* aData, int32_t aTimeoutMs) {
+ aData->startTicks = GetTickCount();
+ if (!aData->startTicks) {
+ // How unlikely is this!
+ aData->startTicks++;
+ }
+ aData->targetTicks = aData->startTicks + aTimeoutMs;
+}
+
+bool TimeoutHasExpired(const TimeoutData& aData) {
+ if (!aData.startTicks) {
+ return false;
+ }
+
+ DWORD now = GetTickCount();
+
+ if (aData.targetTicks < aData.startTicks) {
+ // Overflow
+ return now < aData.startTicks && now >= aData.targetTicks;
+ }
+ return now >= aData.targetTicks;
+}
+
+} // namespace
+
+namespace mozilla {
+namespace ipc {
+namespace windows {
+
+void InitUIThread() {
+ if (!XRE_UseNativeEventProcessing()) {
+ return;
+ }
+ // If we aren't setup before a call to NotifyWorkerThread, we'll hang
+ // on startup.
+ if (!gUIThreadId) {
+ gUIThreadId = GetCurrentThreadId();
+ }
+
+ MOZ_ASSERT(gUIThreadId);
+ MOZ_ASSERT(gUIThreadId == GetCurrentThreadId(),
+ "Called InitUIThread multiple times on different threads!");
+
+ if (!gWinEventHook && !mscom::IsCurrentThreadMTA()) {
+ gWinEventHook = SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_DESTROY,
+ NULL, &WinEventHook, GetCurrentProcessId(),
+ gUIThreadId, WINEVENT_OUTOFCONTEXT);
+ MOZ_ASSERT(gWinEventHook);
+
+ // We need to execute this after setting the hook in case the OLE window
+ // already existed.
+ gCOMWindow = FindCOMWindow();
+ }
+}
+
+} // namespace windows
+} // namespace ipc
+} // namespace mozilla
+
+// See SpinInternalEventLoop below
+MessageChannel::SyncStackFrame::SyncStackFrame(MessageChannel* channel)
+ : mSpinNestedEvents(false),
+ mListenerNotified(false),
+ mChannel(channel),
+ mPrev(mChannel->mTopFrame),
+ mStaticPrev(sStaticTopFrame) {
+ // Only track stack frames when Windows message deferral behavior
+ // is request for the channel.
+ if (!(mChannel->GetChannelFlags() & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) {
+ return;
+ }
+
+ mChannel->mTopFrame = this;
+ sStaticTopFrame = this;
+
+ if (!mStaticPrev) {
+ NS_ASSERTION(!gNeuteredWindows, "Should only set this once!");
+ gNeuteredWindows = new AutoTArray<HWND, 20>();
+ NS_ASSERTION(gNeuteredWindows, "Out of memory!");
+ }
+}
+
+MessageChannel::SyncStackFrame::~SyncStackFrame() {
+ if (!(mChannel->GetChannelFlags() & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) {
+ return;
+ }
+
+ NS_ASSERTION(this == mChannel->mTopFrame,
+ "Mismatched interrupt stack frames");
+ NS_ASSERTION(this == sStaticTopFrame,
+ "Mismatched static Interrupt stack frames");
+
+ mChannel->mTopFrame = mPrev;
+ sStaticTopFrame = mStaticPrev;
+
+ if (!mStaticPrev) {
+ NS_ASSERTION(gNeuteredWindows, "Bad pointer!");
+ delete gNeuteredWindows;
+ gNeuteredWindows = nullptr;
+ }
+}
+
+MessageChannel::SyncStackFrame* MessageChannel::sStaticTopFrame;
+
+// nsAppShell's notification that gecko events are being processed.
+// If we are here and there is an Interrupt Incall active, we are spinning
+// a nested gecko event loop. In which case the remote process needs
+// to know about it.
+void /* static */
+MessageChannel::NotifyGeckoEventDispatch() {
+ // sStaticTopFrame is only valid for Interrupt channels
+ if (!sStaticTopFrame || sStaticTopFrame->mListenerNotified) return;
+
+ sStaticTopFrame->mListenerNotified = true;
+ MessageChannel* channel =
+ static_cast<MessageChannel*>(sStaticTopFrame->mChannel);
+ channel->Listener()->ProcessRemoteNativeEventsInInterruptCall();
+}
+
+// invoked by the module that receives the spin event loop
+// message.
+void MessageChannel::ProcessNativeEventsInInterruptCall() {
+ NS_ASSERTION(GetCurrentThreadId() == gUIThreadId,
+ "Shouldn't be on a non-main thread in here!");
+ if (!mTopFrame) {
+ NS_ERROR("Spin logic error: no Interrupt frame");
+ return;
+ }
+
+ mTopFrame->mSpinNestedEvents = true;
+}
+
+// Spin loop is called in place of WaitFor*Notify when modal ui is being shown
+// in a child. There are some intricacies in using it however. Spin loop is
+// enabled for a particular Interrupt frame by the client calling
+// MessageChannel::ProcessNativeEventsInInterrupt().
+// This call can be nested for multiple Interrupt frames in a single plugin or
+// multiple unrelated plugins.
+void MessageChannel::SpinInternalEventLoop() {
+ if (mozilla::PaintTracker::IsPainting()) {
+ MOZ_CRASH("Don't spin an event loop while painting.");
+ }
+
+ NS_ASSERTION(mTopFrame && mTopFrame->mSpinNestedEvents,
+ "Spinning incorrectly");
+
+ // Nested windows event loop we trigger when the child enters into modal
+ // event loops.
+
+ // Note, when we return, we always reset the notify worker event. So there's
+ // no need to reset it on return here.
+
+ do {
+ MSG msg = {0};
+
+ // Don't get wrapped up in here if the child connection dies.
+ {
+ MonitorAutoLock lock(*mMonitor);
+ if (!Connected()) {
+ return;
+ }
+ }
+
+ // Retrieve window or thread messages
+ if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
+ // The child UI should have been destroyed before the app is closed, in
+ // which case, we should never get this here.
+ if (msg.message == WM_QUIT) {
+ NS_ERROR("WM_QUIT received in SpinInternalEventLoop!");
+ } else {
+ TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ return;
+ }
+ }
+
+ // Note, give dispatching windows events priority over checking if
+ // mEvent is signaled, otherwise heavy ipc traffic can cause jittery
+ // playback of video. We'll exit out on each disaptch above, so ipc
+ // won't get starved.
+
+ // Wait for UI events or a signal from the io thread.
+ DWORD result =
+ MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE, QS_ALLINPUT);
+ if (result == WAIT_OBJECT_0) {
+ // Our NotifyWorkerThread event was signaled
+ return;
+ }
+ } while (true);
+}
+
+static HHOOK gWindowHook;
+
+static inline void StartNeutering() {
+ if (!gUIThreadId) {
+ mozilla::ipc::windows::InitUIThread();
+ }
+ MOZ_ASSERT(gUIThreadId);
+ MOZ_ASSERT(!gWindowHook);
+ NS_ASSERTION(!MessageChannel::IsPumpingMessages(),
+ "Shouldn't be pumping already!");
+ MessageChannel::SetIsPumpingMessages(true);
+ gWindowHook = ::SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook,
+ nullptr, gUIThreadId);
+ NS_ASSERTION(gWindowHook, "Failed to set hook!");
+}
+
+static void StopNeutering() {
+ MOZ_ASSERT(MessageChannel::IsPumpingMessages());
+ ::UnhookWindowsHookEx(gWindowHook);
+ gWindowHook = NULL;
+ ::UnhookNeuteredWindows();
+ // Before returning we need to set a hook to run any deferred messages that
+ // we received during the IPC call. The hook will unset itself as soon as
+ // someone else calls GetMessage, PeekMessage, or runs code that generates
+ // a "nonqueued" message.
+ ::ScheduleDeferredMessageRun();
+ MessageChannel::SetIsPumpingMessages(false);
+}
+
+NeuteredWindowRegion::NeuteredWindowRegion(bool aDoNeuter)
+ : mNeuteredByThis(!gWindowHook && aDoNeuter &&
+ XRE_UseNativeEventProcessing()) {
+ if (mNeuteredByThis) {
+ StartNeutering();
+ }
+}
+
+NeuteredWindowRegion::~NeuteredWindowRegion() {
+ if (gWindowHook && mNeuteredByThis) {
+ StopNeutering();
+ }
+}
+
+void NeuteredWindowRegion::PumpOnce() {
+ if (!gWindowHook) {
+ // This should be a no-op if nothing has been neutered.
+ return;
+ }
+
+ MSG msg = {0};
+ // Pump any COM messages so that we don't hang due to STA marshaling.
+ if (gCOMWindow && ::PeekMessageW(&msg, gCOMWindow, 0, 0, PM_REMOVE)) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ // Expunge any nonqueued messages on the current thread.
+ ::PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE);
+}
+
+DeneuteredWindowRegion::DeneuteredWindowRegion()
+ : mReneuter(gWindowHook != NULL) {
+ if (mReneuter) {
+ StopNeutering();
+ }
+}
+
+DeneuteredWindowRegion::~DeneuteredWindowRegion() {
+ if (mReneuter) {
+ StartNeutering();
+ }
+}
+
+SuppressedNeuteringRegion::SuppressedNeuteringRegion()
+ : mReenable(::gUIThreadId == ::GetCurrentThreadId() && ::gWindowHook) {
+ if (mReenable) {
+ MOZ_ASSERT(!sSuppressNeutering);
+ sSuppressNeutering = true;
+ }
+}
+
+SuppressedNeuteringRegion::~SuppressedNeuteringRegion() {
+ if (mReenable) {
+ MOZ_ASSERT(sSuppressNeutering);
+ sSuppressNeutering = false;
+ }
+}
+
+bool SuppressedNeuteringRegion::sSuppressNeutering = false;
+
+bool MessageChannel::WaitForSyncNotify() {
+ mMonitor->AssertCurrentThreadOwns();
+
+ if (!gUIThreadId) {
+ mozilla::ipc::windows::InitUIThread();
+ }
+
+ // Use a blocking wait if this channel does not require
+ // Windows message deferral behavior.
+ if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) {
+ TimeDuration timeout = (kNoTimeout == mTimeoutMs)
+ ? TimeDuration::Forever()
+ : TimeDuration::FromMilliseconds(mTimeoutMs);
+
+ MOZ_ASSERT(!mIsSyncWaitingOnNonMainThread);
+ mIsSyncWaitingOnNonMainThread = true;
+
+ CVStatus status = mMonitor->Wait(timeout);
+
+ MOZ_ASSERT(mIsSyncWaitingOnNonMainThread);
+ mIsSyncWaitingOnNonMainThread = false;
+
+ // If the timeout didn't expire, we know we received an event. The
+ // converse is not true.
+ return WaitResponse(status == CVStatus::Timeout);
+ }
+
+ NS_ASSERTION(
+ mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION,
+ "Shouldn't be here for channels that don't use message deferral!");
+ NS_ASSERTION(mTopFrame, "No top frame!");
+
+ MonitorAutoUnlock unlock(*mMonitor);
+
+ bool timedout = false;
+
+ UINT_PTR timerId = 0;
+ TimeoutData timeoutData = {0};
+
+ if (mTimeoutMs != kNoTimeout) {
+ InitTimeoutData(&timeoutData, mTimeoutMs);
+
+ // We only do this to ensure that we won't get stuck in
+ // MsgWaitForMultipleObjects below.
+ timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr);
+ NS_ASSERTION(timerId, "SetTimer failed!");
+ }
+
+ NeuteredWindowRegion neuteredRgn(true);
+
+ {
+ while (1) {
+ MSG msg = {0};
+ // Don't get wrapped up in here if the child connection dies.
+ {
+ MonitorAutoLock lock(*mMonitor);
+ if (!Connected()) {
+ break;
+ }
+ }
+
+ // Wait until we have a message in the queue. MSDN docs are a bit unclear
+ // but it seems that windows from two different threads (and it should be
+ // noted that a thread in another process counts as a "different thread")
+ // will implicitly have their message queues attached if they are parented
+ // to one another. This wait call, then, will return for a message
+ // delivered to *either* thread.
+ DWORD result =
+ MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE, QS_ALLINPUT);
+ if (result == WAIT_OBJECT_0) {
+ // Our NotifyWorkerThread event was signaled
+ BOOL success = ResetEvent(mEvent);
+ if (!success) {
+ gfxDevCrash(mozilla::gfx::LogReason::MessageChannelInvalidHandle)
+ << "WindowsMessageChannel::WaitForSyncNotify failed to reset "
+ "event. GetLastError: "
+ << GetLastError();
+ }
+ break;
+ } else if (result != (WAIT_OBJECT_0 + 1)) {
+ NS_ERROR("Wait failed!");
+ break;
+ }
+
+ if (TimeoutHasExpired(timeoutData)) {
+ // A timeout was specified and we've passed it. Break out.
+ timedout = true;
+ break;
+ }
+
+ // The only way to know on which thread the message was delivered is to
+ // use some logic on the return values of GetQueueStatus and PeekMessage.
+ // PeekMessage will return false if there are no "queued" messages, but it
+ // will run all "nonqueued" messages before returning. So if PeekMessage
+ // returns false and there are no "nonqueued" messages that were run then
+ // we know that the message we woke for was intended for a window on
+ // another thread.
+ bool haveSentMessagesPending =
+ (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;
+
+ // Either of the PeekMessage calls below will actually process all
+ // "nonqueued" messages that are pending before returning. If we have
+ // "nonqueued" messages pending then we should have switched out all the
+ // window procedures above. In that case this PeekMessage call won't
+ // actually cause any mozilla code (or plugin code) to run.
+
+ // We have to manually pump all COM messages *after* looking at the queue
+ // queue status but before yielding our thread below.
+ if (gCOMWindow) {
+ if (PeekMessageW(&msg, gCOMWindow, 0, 0, PM_REMOVE)) {
+ TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ }
+
+ // If the following PeekMessage call fails to return a message for us (and
+ // returns false) and we didn't run any "nonqueued" messages then we must
+ // have woken up for a message designated for a window in another thread.
+ // If we loop immediately then we could enter a tight loop, so we'll give
+ // up our time slice here to let the child process its message.
+ if (!PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE) &&
+ !haveSentMessagesPending) {
+ // Message was for child, we should wait a bit.
+ SwitchToThread();
+ }
+ }
+ }
+
+ if (timerId) {
+ KillTimer(nullptr, timerId);
+ timerId = 0;
+ }
+
+ return WaitResponse(timedout);
+}
+
+void MessageChannel::NotifyWorkerThread() {
+ mMonitor->AssertCurrentThreadOwns();
+
+ if (mIsSyncWaitingOnNonMainThread) {
+ mMonitor->Notify();
+ return;
+ }
+
+ MOZ_RELEASE_ASSERT(mEvent, "No signal event to set, this is really bad!");
+ if (!SetEvent(mEvent)) {
+ NS_WARNING("Failed to set NotifyWorkerThread event!");
+ gfxDevCrash(mozilla::gfx::LogReason::MessageChannelInvalidHandle)
+ << "WindowsMessageChannel failed to SetEvent. GetLastError: "
+ << GetLastError();
+ }
+}
+
+void DeferredSendMessage::Run() {
+ AssertWindowIsNotNeutered(hWnd);
+ if (!IsWindow(hWnd)) {
+ NS_ERROR("Invalid window!");
+ return;
+ }
+
+ WNDPROC wndproc =
+ reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC));
+ if (!wndproc) {
+ NS_ERROR("Invalid window procedure!");
+ return;
+ }
+
+ CallWindowProc(wndproc, hWnd, message, wParam, lParam);
+}
+
+void DeferredRedrawMessage::Run() {
+ AssertWindowIsNotNeutered(hWnd);
+ if (!IsWindow(hWnd)) {
+ NS_ERROR("Invalid window!");
+ return;
+ }
+
+#ifdef DEBUG
+ BOOL ret =
+#endif
+ RedrawWindow(hWnd, nullptr, nullptr, flags);
+ NS_ASSERTION(ret, "RedrawWindow failed!");
+}
+
+DeferredUpdateMessage::DeferredUpdateMessage(HWND aHWnd) {
+ mWnd = aHWnd;
+ if (!GetUpdateRect(mWnd, &mUpdateRect, FALSE)) {
+ memset(&mUpdateRect, 0, sizeof(RECT));
+ return;
+ }
+ ValidateRect(mWnd, &mUpdateRect);
+}
+
+void DeferredUpdateMessage::Run() {
+ AssertWindowIsNotNeutered(mWnd);
+ if (!IsWindow(mWnd)) {
+ NS_ERROR("Invalid window!");
+ return;
+ }
+
+ InvalidateRect(mWnd, &mUpdateRect, FALSE);
+#ifdef DEBUG
+ BOOL ret =
+#endif
+ UpdateWindow(mWnd);
+ NS_ASSERTION(ret, "UpdateWindow failed!");
+}
+
+DeferredSettingChangeMessage::DeferredSettingChangeMessage(HWND aHWnd,
+ UINT aMessage,
+ WPARAM aWParam,
+ LPARAM aLParam)
+ : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) {
+ NS_ASSERTION(aMessage == WM_SETTINGCHANGE, "Wrong message type!");
+ if (aLParam) {
+ lParamString = _wcsdup(reinterpret_cast<const wchar_t*>(aLParam));
+ lParam = reinterpret_cast<LPARAM>(lParamString);
+ } else {
+ lParamString = nullptr;
+ lParam = 0;
+ }
+}
+
+DeferredSettingChangeMessage::~DeferredSettingChangeMessage() {
+ free(lParamString);
+}
+
+DeferredWindowPosMessage::DeferredWindowPosMessage(HWND aHWnd, LPARAM aLParam,
+ bool aForCalcSize,
+ WPARAM aWParam) {
+ if (aForCalcSize) {
+ if (aWParam) {
+ NCCALCSIZE_PARAMS* arg = reinterpret_cast<NCCALCSIZE_PARAMS*>(aLParam);
+ memcpy(&windowPos, arg->lppos, sizeof(windowPos));
+
+ NS_ASSERTION(aHWnd == windowPos.hwnd, "Mismatched hwnds!");
+ } else {
+ RECT* arg = reinterpret_cast<RECT*>(aLParam);
+ windowPos.hwnd = aHWnd;
+ windowPos.hwndInsertAfter = nullptr;
+ windowPos.x = arg->left;
+ windowPos.y = arg->top;
+ windowPos.cx = arg->right - arg->left;
+ windowPos.cy = arg->bottom - arg->top;
+
+ NS_ASSERTION(arg->right >= arg->left && arg->bottom >= arg->top,
+ "Negative width or height!");
+ }
+ windowPos.flags = SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOOWNERZORDER |
+ SWP_NOZORDER | SWP_DEFERERASE | SWP_NOSENDCHANGING;
+ } else {
+ // Not for WM_NCCALCSIZE
+ WINDOWPOS* arg = reinterpret_cast<WINDOWPOS*>(aLParam);
+ memcpy(&windowPos, arg, sizeof(windowPos));
+
+ NS_ASSERTION(aHWnd == windowPos.hwnd, "Mismatched hwnds!");
+
+ // Windows sends in some private flags sometimes that we can't simply copy.
+ // Filter here.
+ UINT mask = SWP_ASYNCWINDOWPOS | SWP_DEFERERASE | SWP_DRAWFRAME |
+ SWP_FRAMECHANGED | SWP_HIDEWINDOW | SWP_NOACTIVATE |
+ SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREDRAW |
+ SWP_NOREPOSITION | SWP_NOSENDCHANGING | SWP_NOSIZE |
+ SWP_NOZORDER | SWP_SHOWWINDOW;
+ windowPos.flags &= mask;
+ }
+}
+
+void DeferredWindowPosMessage::Run() {
+ AssertWindowIsNotNeutered(windowPos.hwnd);
+ if (!IsWindow(windowPos.hwnd)) {
+ NS_ERROR("Invalid window!");
+ return;
+ }
+
+ if (!IsWindow(windowPos.hwndInsertAfter)) {
+ NS_WARNING("ZOrder change cannot be honored");
+ windowPos.hwndInsertAfter = 0;
+ windowPos.flags |= SWP_NOZORDER;
+ }
+
+#ifdef DEBUG
+ BOOL ret =
+#endif
+ SetWindowPos(windowPos.hwnd, windowPos.hwndInsertAfter, windowPos.x,
+ windowPos.y, windowPos.cx, windowPos.cy, windowPos.flags);
+ NS_ASSERTION(ret, "SetWindowPos failed!");
+}
+
+DeferredCopyDataMessage::DeferredCopyDataMessage(HWND aHWnd, UINT aMessage,
+ WPARAM aWParam, LPARAM aLParam)
+ : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) {
+ NS_ASSERTION(IsWindow(reinterpret_cast<HWND>(aWParam)), "Bad window!");
+
+ COPYDATASTRUCT* source = reinterpret_cast<COPYDATASTRUCT*>(aLParam);
+ NS_ASSERTION(source, "Should never be null!");
+
+ copyData.dwData = source->dwData;
+ copyData.cbData = source->cbData;
+
+ if (source->cbData) {
+ copyData.lpData = malloc(source->cbData);
+ if (copyData.lpData) {
+ memcpy(copyData.lpData, source->lpData, source->cbData);
+ } else {
+ NS_ERROR("Out of memory?!");
+ copyData.cbData = 0;
+ }
+ } else {
+ copyData.lpData = nullptr;
+ }
+
+ lParam = reinterpret_cast<LPARAM>(&copyData);
+}
+
+DeferredCopyDataMessage::~DeferredCopyDataMessage() { free(copyData.lpData); }
+
+DeferredStyleChangeMessage::DeferredStyleChangeMessage(HWND aHWnd,
+ WPARAM aWParam,
+ LPARAM aLParam)
+ : hWnd(aHWnd) {
+ index = static_cast<int>(aWParam);
+ style = reinterpret_cast<STYLESTRUCT*>(aLParam)->styleNew;
+}
+
+void DeferredStyleChangeMessage::Run() { SetWindowLongPtr(hWnd, index, style); }
+
+DeferredSetIconMessage::DeferredSetIconMessage(HWND aHWnd, UINT aMessage,
+ WPARAM aWParam, LPARAM aLParam)
+ : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) {
+ NS_ASSERTION(aMessage == WM_SETICON, "Wrong message type!");
+}
+
+void DeferredSetIconMessage::Run() {
+ AssertWindowIsNotNeutered(hWnd);
+ if (!IsWindow(hWnd)) {
+ NS_ERROR("Invalid window!");
+ return;
+ }
+
+ WNDPROC wndproc =
+ reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC));
+ if (!wndproc) {
+ NS_ERROR("Invalid window procedure!");
+ return;
+ }
+
+ HICON hOld = reinterpret_cast<HICON>(
+ CallWindowProc(wndproc, hWnd, message, wParam, lParam));
+ if (hOld) {
+ DestroyIcon(hOld);
+ }
+}
diff --git a/ipc/glue/WindowsMessageLoop.h b/ipc/glue/WindowsMessageLoop.h
new file mode 100644
index 0000000000..7fa614279b
--- /dev/null
+++ b/ipc/glue/WindowsMessageLoop.h
@@ -0,0 +1,134 @@
+/* -*- 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/. */
+
+#ifndef IPC_GLUE_WINDOWSMESSAGELOOP_H
+#define IPC_GLUE_WINDOWSMESSAGELOOP_H
+
+// This file is only meant to compile on windows
+#include <windows.h>
+
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace ipc {
+namespace windows {
+
+void InitUIThread();
+
+class DeferredMessage {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(DeferredMessage)
+
+ MOZ_COUNTED_DTOR_VIRTUAL(DeferredMessage)
+
+ virtual void Run() = 0;
+};
+
+// Uses CallWndProc to deliver a message at a later time. Messages faked with
+// this class must not have pointers in their wParam or lParam members as they
+// may be invalid by the time the message actually runs.
+class DeferredSendMessage : public DeferredMessage {
+ public:
+ DeferredSendMessage(HWND aHWnd, UINT aMessage, WPARAM aWParam, LPARAM aLParam)
+ : hWnd(aHWnd), message(aMessage), wParam(aWParam), lParam(aLParam) {}
+
+ virtual void Run();
+
+ protected:
+ HWND hWnd;
+ UINT message;
+ WPARAM wParam;
+ LPARAM lParam;
+};
+
+// Uses RedrawWindow to fake several painting-related messages. Flags passed
+// to the constructor go directly to RedrawWindow.
+class DeferredRedrawMessage : public DeferredMessage {
+ public:
+ DeferredRedrawMessage(HWND aHWnd, UINT aFlags) : hWnd(aHWnd), flags(aFlags) {}
+
+ virtual void Run();
+
+ private:
+ HWND hWnd;
+ UINT flags;
+};
+
+// Uses UpdateWindow to generate a WM_PAINT message if needed.
+class DeferredUpdateMessage : public DeferredMessage {
+ public:
+ explicit DeferredUpdateMessage(HWND aHWnd);
+
+ virtual void Run();
+
+ private:
+ HWND mWnd;
+ RECT mUpdateRect;
+};
+
+// This class duplicates a string that may exist in the lParam member of the
+// message.
+class DeferredSettingChangeMessage : public DeferredSendMessage {
+ public:
+ DeferredSettingChangeMessage(HWND aHWnd, UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam);
+
+ ~DeferredSettingChangeMessage();
+
+ private:
+ wchar_t* lParamString;
+};
+
+// This class uses SetWindowPos to fake various size-related messages. Flags
+// passed to the constructor go straight through to SetWindowPos.
+class DeferredWindowPosMessage : public DeferredMessage {
+ public:
+ DeferredWindowPosMessage(HWND aHWnd, LPARAM aLParam,
+ bool aForCalcSize = false, WPARAM aWParam = 0);
+
+ virtual void Run();
+
+ private:
+ WINDOWPOS windowPos;
+};
+
+// This class duplicates a data buffer for a WM_COPYDATA message.
+class DeferredCopyDataMessage : public DeferredSendMessage {
+ public:
+ DeferredCopyDataMessage(HWND aHWnd, UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam);
+
+ ~DeferredCopyDataMessage();
+
+ private:
+ COPYDATASTRUCT copyData;
+};
+
+class DeferredStyleChangeMessage : public DeferredMessage {
+ public:
+ DeferredStyleChangeMessage(HWND aHWnd, WPARAM aWParam, LPARAM aLParam);
+
+ virtual void Run();
+
+ private:
+ HWND hWnd;
+ int index;
+ LONG_PTR style;
+};
+
+class DeferredSetIconMessage : public DeferredSendMessage {
+ public:
+ DeferredSetIconMessage(HWND aHWnd, UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam);
+
+ virtual void Run();
+};
+
+} /* namespace windows */
+} /* namespace ipc */
+} /* namespace mozilla */
+
+#endif /* IPC_GLUE_WINDOWSMESSAGELOOP_H */
diff --git a/ipc/glue/components.conf b/ipc/glue/components.conf
new file mode 100644
index 0000000000..34048dbbfa
--- /dev/null
+++ b/ipc/glue/components.conf
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Headers = [
+ 'mozilla/ipc/ForkServer.h',
+]
+
+Classes = [
+ {
+ 'cid': '{cdb4757f-f51b-40c0-8b38-66d12c3bff7b}',
+ 'contract_ids': ['@mozilla.org/fork-server-launcher;1'],
+ 'singleton': True,
+ 'type': 'mozilla::ipc::ForkServerLauncher',
+ 'headers': ['mozilla/ipc/ForkServiceChild.h'],
+ 'constructor': 'mozilla::ipc::ForkServerLauncher::Create',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ 'categories': {'xpcom-startup': 'Fork Server Launcher'},
+ },
+]
diff --git a/ipc/glue/moz.build b/ipc/glue/moz.build
new file mode 100644
index 0000000000..3651a9794e
--- /dev/null
+++ b/ipc/glue/moz.build
@@ -0,0 +1,307 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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("/dom/media/webrtc/third_party_build/webrtc.mozbuild")
+
+EXPORTS += [
+ "nsIIPCSerializableInputStream.h",
+]
+
+EXPORTS.mozilla.ipc += [
+ "AsyncBlockers.h",
+ "BackgroundChild.h",
+ "BackgroundParent.h",
+ "BackgroundStarterChild.h",
+ "BackgroundStarterParent.h",
+ "BackgroundUtils.h",
+ "BigBuffer.h",
+ "BrowserProcessSubThread.h",
+ "ByteBuf.h",
+ "ByteBufUtils.h",
+ "CrashReporterClient.h",
+ "CrashReporterHelper.h",
+ "CrashReporterHost.h",
+ "CrossProcessMutex.h",
+ "CrossProcessSemaphore.h",
+ "DataPipe.h",
+ "Endpoint.h",
+ "EnvironmentMap.h",
+ "FileDescriptor.h",
+ "FileDescriptorUtils.h",
+ "GeckoChildProcessHost.h",
+ "IdleSchedulerChild.h",
+ "IdleSchedulerParent.h",
+ "InputStreamUtils.h",
+ "IOThreadChild.h",
+ "IPCCore.h",
+ "IPCForwards.h",
+ "IPCStreamUtils.h",
+ "IPCTypes.h",
+ "IPDLParamTraits.h",
+ "IPDLStructMember.h",
+ "LaunchError.h",
+ "LibrarySandboxPreload.h",
+ "MessageChannel.h",
+ "MessageLink.h",
+ "MessagePump.h",
+ "Neutering.h",
+ "NodeChannel.h",
+ "NodeController.h",
+ "ProcessChild.h",
+ "ProcessUtils.h",
+ "ProtocolMessageUtils.h",
+ "ProtocolUtils.h",
+ "RandomAccessStreamUtils.h",
+ "RawShmem.h",
+ "ScopedPort.h",
+ "SerializedStructuredCloneBuffer.h",
+ "SharedMemory.h",
+ "SharedMemoryBasic.h",
+ "Shmem.h",
+ "ShmemMessageUtils.h",
+ "SideVariant.h",
+ "TaintingIPCUtils.h",
+ "TaskFactory.h",
+ "ToplevelActorHolder.h",
+ "TransportSecurityInfoUtils.h",
+ "URIUtils.h",
+ "UtilityAudioDecoder.h",
+ "UtilityAudioDecoderChild.h",
+ "UtilityAudioDecoderParent.h",
+ "UtilityProcessChild.h",
+ "UtilityProcessHost.h",
+ "UtilityProcessImpl.h",
+ "UtilityProcessManager.h",
+ "UtilityProcessParent.h",
+ "UtilityProcessSandboxing.h",
+ "WindowsMessageLoop.h",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ SOURCES += [
+ "SharedMemory_windows.cpp",
+ "WindowsMessageLoop.cpp",
+ ]
+else:
+ UNIFIED_SOURCES += [
+ "SharedMemory_posix.cpp",
+ ]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ SOURCES += [
+ "CrossProcessMutex_windows.cpp",
+ ]
+elif not CONFIG["OS_ARCH"] in ("NetBSD", "OpenBSD"):
+ UNIFIED_SOURCES += [
+ "CrossProcessMutex_posix.cpp",
+ ]
+else:
+ UNIFIED_SOURCES += [
+ "CrossProcessMutex_unimplemented.cpp",
+ ]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ SOURCES += [
+ "CrossProcessSemaphore_windows.cpp",
+ ]
+elif CONFIG["OS_ARCH"] != "Darwin":
+ UNIFIED_SOURCES += [
+ "CrossProcessSemaphore_posix.cpp",
+ ]
+else:
+ UNIFIED_SOURCES += [
+ "CrossProcessSemaphore_unimplemented.cpp",
+ ]
+
+# Android has its own,
+# almost-but-not-quite-compatible-with-POSIX-or-/dev/shm shared memory
+# impl.
+if CONFIG["OS_TARGET"] == "Android":
+ EXPORTS.mozilla.ipc += ["SharedMemoryBasic_android.h"]
+ UNIFIED_SOURCES += [
+ "SharedMemoryBasic_android.cpp",
+ ]
+elif CONFIG["OS_ARCH"] == "Darwin":
+ EXPORTS.mozilla.ipc += ["SharedMemoryBasic_mach.h"]
+ SOURCES += [
+ "SharedMemoryBasic_mach.mm",
+ ]
+else:
+ EXPORTS.mozilla.ipc += ["SharedMemoryBasic_chromium.h"]
+
+if CONFIG["OS_ARCH"] == "Linux":
+ UNIFIED_SOURCES += [
+ "ProcessUtils_linux.cpp",
+ ]
+elif CONFIG["OS_ARCH"] in ("DragonFly", "FreeBSD", "NetBSD", "OpenBSD"):
+ UNIFIED_SOURCES += ["ProcessUtils_bsd.cpp"]
+elif CONFIG["OS_ARCH"] == "Darwin":
+ UNIFIED_SOURCES += ["ProcessUtils_mac.mm"]
+else:
+ UNIFIED_SOURCES += [
+ "ProcessUtils_none.cpp",
+ ]
+
+if CONFIG["OS_ARCH"] != "WINNT":
+ EXPORTS.mozilla.ipc += [
+ "FileDescriptorShuffle.h",
+ ]
+ UNIFIED_SOURCES += [
+ "FileDescriptorShuffle.cpp",
+ ]
+
+EXPORTS.ipc += [
+ "EnumSerializer.h",
+ "IPCMessageUtils.h",
+ "IPCMessageUtilsSpecializations.h",
+]
+
+UNIFIED_SOURCES += [
+ "BackgroundImpl.cpp",
+ "BackgroundUtils.cpp",
+ "BigBuffer.cpp",
+ "BrowserProcessSubThread.cpp",
+ "CrashReporterClient.cpp",
+ "CrashReporterHost.cpp",
+ "DataPipe.cpp",
+ "Endpoint.cpp",
+ "FileDescriptor.cpp",
+ "FileDescriptorUtils.cpp",
+ "IdleSchedulerChild.cpp",
+ "IdleSchedulerParent.cpp",
+ "InputStreamUtils.cpp",
+ "IPCMessageUtilsSpecializations.cpp",
+ "IPCStreamUtils.cpp",
+ "LaunchError.cpp",
+ "LibrarySandboxPreload.cpp",
+ "MessageChannel.cpp",
+ "MessageLink.cpp",
+ "MessagePump.cpp",
+ "NodeChannel.cpp",
+ "NodeController.cpp",
+ "ProcessChild.cpp",
+ "ProcessUtils_common.cpp",
+ "ProtocolUtils.cpp",
+ "RandomAccessStreamUtils.cpp",
+ "RawShmem.cpp",
+ "ScopedPort.cpp",
+ "SerializedStructuredCloneBuffer.cpp",
+ "SharedMemory.cpp",
+ "Shmem.cpp",
+ "StringUtil.cpp",
+ "TransportSecurityInfoUtils.cpp",
+ "URIUtils.cpp",
+ "UtilityAudioDecoder.cpp",
+ "UtilityAudioDecoderChild.cpp",
+ "UtilityAudioDecoderParent.cpp",
+ "UtilityProcessChild.cpp",
+ "UtilityProcessHost.cpp",
+ "UtilityProcessImpl.cpp",
+ "UtilityProcessManager.cpp",
+ "UtilityProcessParent.cpp",
+]
+
+SOURCES += [
+ "BackgroundChildImpl.cpp",
+ "BackgroundParentImpl.cpp",
+]
+
+if CONFIG["OS_ARCH"] == "Darwin":
+ # GeckoChildProcessHost.cpp cannot be built unified due to OSX header
+ # clashes with TextRange.
+ SOURCES += [
+ "GeckoChildProcessHost.cpp",
+ ]
+else:
+ UNIFIED_SOURCES += [
+ "GeckoChildProcessHost.cpp",
+ ]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ UNIFIED_SOURCES += ["MessagePump_windows.cpp"]
+elif CONFIG["OS_ARCH"] == "Darwin":
+ UNIFIED_SOURCES += ["MessagePump_mac.mm"]
+elif CONFIG["OS_TARGET"] == "Android":
+ UNIFIED_SOURCES += ["MessagePump_android.cpp"]
+
+LOCAL_INCLUDES += [
+ "/caps",
+ "/dom/broadcastchannel",
+ "/dom/indexedDB",
+ "/dom/storage",
+ "/netwerk/base",
+ "/third_party/libwebrtc",
+ "/third_party/libwebrtc/third_party/abseil-cpp",
+ "/tools/fuzzing/ipc",
+ "/xpcom/build",
+]
+
+PREPROCESSED_IPDL_SOURCES = [
+ "PUtilityAudioDecoder.ipdl",
+ "PUtilityProcess.ipdl",
+]
+
+IPDL_SOURCES = [
+ "InputStreamParams.ipdlh",
+ "IPCStream.ipdlh",
+ "PBackground.ipdl",
+ "PBackgroundSharedTypes.ipdlh",
+ "PBackgroundStarter.ipdl",
+ "PBackgroundTest.ipdl",
+ "PIdleScheduler.ipdl",
+ "ProtocolTypes.ipdlh",
+ "RandomAccessStreamParams.ipdlh",
+ "URIParams.ipdlh",
+]
+
+if CONFIG["MOZ_ENABLE_FORKSERVER"]:
+ EXPORTS.mozilla.ipc += [
+ "ForkServer.h",
+ "ForkServiceChild.h",
+ "MiniTransceiver.h",
+ ]
+ UNIFIED_SOURCES += [
+ "ForkServer.cpp",
+ "ForkServiceChild.cpp",
+ "MiniTransceiver.cpp",
+ ]
+ XPCOM_MANIFESTS += [
+ "components.conf",
+ ]
+
+LOCAL_INCLUDES += [
+ "/dom/ipc",
+ "/toolkit/crashreporter",
+ "/toolkit/xre",
+ "/xpcom/base",
+ "/xpcom/threads",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["OS_ARCH"] == "Darwin":
+ OS_LIBS += ["bsm"] # for audit_token_to_pid
+
+for var in ("MOZ_CHILD_PROCESS_NAME", "MOZ_CHILD_PROCESS_BUNDLE"):
+ DEFINES[var] = '"%s"' % CONFIG[var]
+
+if CONFIG["MOZ_SANDBOX"] and CONFIG["OS_ARCH"] == "WINNT":
+ LOCAL_INCLUDES += [
+ "/security/sandbox/chromium",
+ "/security/sandbox/chromium-shim",
+ "/security/sandbox/win/src/sandboxbroker",
+ ]
+
+if CONFIG["ENABLE_TESTS"]:
+ DIRS += [
+ "test/gtest",
+ "test/utility_process_xpcom",
+ "test/browser",
+ ]
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
diff --git a/ipc/glue/nsIIPCSerializableInputStream.h b/ipc/glue/nsIIPCSerializableInputStream.h
new file mode 100644
index 0000000000..acc7594f66
--- /dev/null
+++ b/ipc/glue/nsIIPCSerializableInputStream.h
@@ -0,0 +1,104 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ipc_nsIIPCSerializableInputStream_h
+#define mozilla_ipc_nsIIPCSerializableInputStream_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "nsISupports.h"
+#include "nsTArrayForwardDeclare.h"
+
+namespace mozilla {
+namespace ipc {
+
+class FileDescriptor;
+class InputStreamParams;
+
+} // namespace ipc
+
+} // namespace mozilla
+
+#define NS_IIPCSERIALIZABLEINPUTSTREAM_IID \
+ { \
+ 0xb0211b14, 0xea6d, 0x40d4, { \
+ 0x87, 0xb5, 0x7b, 0xe3, 0xdf, 0xac, 0x09, 0xd1 \
+ } \
+ }
+
+class NS_NO_VTABLE nsIIPCSerializableInputStream : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IIPCSERIALIZABLEINPUTSTREAM_IID)
+
+ // Determine the serialized complexity of this input stream, initializing
+ // `*aSizeUsed`, `*aPipes` and `*aTransferables` to the number of inline
+ // bytes/pipes/transferable resources which would be used. This will be used
+ // by other `Serialize` implementations to potentially simplify the resulting
+ // stream, reducing the number of pipes or file descriptors required.
+ //
+ // Each outparameter corresponds to a type of resource which will be included
+ // in the serialized message, as follows:
+ //
+ // *aSizeUsed:
+ // Raw bytes to be included inline in the message's payload, usually in the
+ // form of a nsCString for a StringInputStreamParams. This must be less
+ // than or equal to `aMaxSize`. Larger payloads should instead be
+ // serialized using SerializeInputStreamAsPipe.
+ // *aPipes:
+ // New pipes, created using SerializeInputStreamAsPipe, which will be used
+ // to asynchronously transfer part of the pipe over IPC. Callers such as
+ // nsMultiplexInputStream may choose to serialize themselves as a DataPipe
+ // if they contain DataPipes themselves, so existing DataPipe instances
+ // which are cheaply transferred should be counted as transferrables.
+ // *aTransferables:
+ // Existing objects which can be more cheaply transferred over IPC than by
+ // serializing them inline in a payload or transferring them through a new
+ // DataPipe. This includes RemoteLazyInputStreams, FileDescriptors, and
+ // existing DataPipeReceiver instances.
+ //
+ // Callers of this method must have initialized all of `*aSizeUsed`,
+ // `*aPipes`, and `*aTransferables` to 0, so implementations are not required
+ // to initialize all outparameters. The outparameters must not be null.
+ virtual void SerializedComplexity(uint32_t aMaxSize, uint32_t* aSizeUsed,
+ uint32_t* aPipes,
+ uint32_t* aTransferables) = 0;
+
+ virtual void Serialize(mozilla::ipc::InputStreamParams& aParams,
+ uint32_t aMaxSize, uint32_t* aSizeUsed) = 0;
+
+ virtual bool Deserialize(const mozilla::ipc::InputStreamParams& aParams) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIIPCSerializableInputStream,
+ NS_IIPCSERIALIZABLEINPUTSTREAM_IID)
+
+#define NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM \
+ virtual void SerializedComplexity(uint32_t aMaxSize, uint32_t* aSizeUsed, \
+ uint32_t* aPipes, \
+ uint32_t* aTransferrables) override; \
+ virtual void Serialize(mozilla::ipc::InputStreamParams&, uint32_t, \
+ uint32_t*) override; \
+ \
+ virtual bool Deserialize(const mozilla::ipc::InputStreamParams&) override;
+
+#define NS_FORWARD_NSIIPCSERIALIZABLEINPUTSTREAM(_to) \
+ virtual void SerializedComplexity(uint32_t aMaxSize, uint32_t* aSizeUsed, \
+ uint32_t* aPipes, \
+ uint32_t* aTransferables) override { \
+ _to SerializedComplexity(aMaxSize, aSizeUsed, aPipes, aTransferables); \
+ }; \
+ \
+ virtual void Serialize(mozilla::ipc::InputStreamParams& aParams, \
+ uint32_t aMaxSize, uint32_t* aSizeUsed) override { \
+ _to Serialize(aParams, aMaxSize, aSizeUsed); \
+ } \
+ \
+ virtual bool Deserialize(const mozilla::ipc::InputStreamParams& aParams) \
+ override { \
+ return _to Deserialize(aParams); \
+ }
+
+#endif // mozilla_ipc_nsIIPCSerializableInputStream_h
diff --git a/ipc/glue/test/browser/browser.ini b/ipc/glue/test/browser/browser.ini
new file mode 100644
index 0000000000..9dc84c3914
--- /dev/null
+++ b/ipc/glue/test/browser/browser.ini
@@ -0,0 +1,59 @@
+[DEFAULT]
+support-files = head.js
+
+[browser_audio_telemetry_content.js]
+skip-if =
+ (os == 'win') # gfx blocks us because media.rdd-process.enabled=false disables PDMFactory::AllDecodersAreRemote()
+support-files =
+ head-telemetry.js
+ ../../../../dom/media/test/small-shot.ogg
+ ../../../../dom/media/test/small-shot.mp3
+ ../../../../dom/media/test/small-shot.m4a
+ ../../../../dom/media/test/small-shot.flac
+[browser_audio_telemetry_rdd.js]
+support-files =
+ head-telemetry.js
+ ../../../../dom/media/test/small-shot.ogg
+ ../../../../dom/media/test/small-shot.mp3
+ ../../../../dom/media/test/small-shot.m4a
+ ../../../../dom/media/test/small-shot.flac
+[browser_audio_telemetry_utility.js]
+support-files =
+ head-telemetry.js
+ ../../../../dom/media/test/small-shot.ogg
+ ../../../../dom/media/test/small-shot.mp3
+ ../../../../dom/media/test/small-shot.m4a
+ ../../../../dom/media/test/small-shot.flac
+[browser_utility_audioDecodeCrash.js]
+support-files =
+ ../../../../dom/media/test/small-shot.ogg
+ ../../../../dom/media/test/small-shot.mp3
+ ../../../../dom/media/test/small-shot.m4a
+ ../../../../dom/media/test/small-shot.flac
+skip-if =
+ !crashreporter
+ ccov
+[browser_utility_crashReporter.js]
+skip-if =
+ !crashreporter
+ ccov
+[browser_utility_geolocation_crashed.js]
+skip-if =
+ !(os == 'win' && os_version == '10.0') # Geolocation is remoted only on Windows 8+
+ !crashreporter
+ ccov
+[browser_utility_hard_kill.js]
+[browser_utility_hard_kill_delayed.js] # bug 1754572: we really want hard_kill to be rust before hard_kill_delayed
+[browser_utility_memoryReport.js]
+skip-if = tsan # bug 1754554
+[browser_utility_multipleAudio.js]
+support-files =
+ ../../../../dom/media/test/small-shot.ogg
+ ../../../../dom/media/test/small-shot.mp3
+ ../../../../dom/media/test/small-shot.m4a
+ ../../../../dom/media/test/small-shot.flac
+[browser_utility_profiler.js]
+support-files =
+ ../../../../tools/profiler/tests/shared-head.js
+skip-if = tsan # from tools/profiler/tests/browser/browser.ini, timing out on profiler tests?
+[browser_utility_start_clean_shutdown.js]
diff --git a/ipc/glue/test/browser/browser_audio_shutdown.ini b/ipc/glue/test/browser/browser_audio_shutdown.ini
new file mode 100644
index 0000000000..a0ce9796f0
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_shutdown.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files = head.js
+
+[browser_utility_audio_shutdown.js]
+support-files =
+ ../../../../dom/media/test/small-shot.ogg
diff --git a/ipc/glue/test/browser/browser_audio_telemetry_content.js b/ipc/glue/test/browser/browser_audio_telemetry_content.js
new file mode 100644
index 0000000000..5d69175d5c
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_telemetry_content.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head-telemetry.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/ipc/glue/test/browser/head-telemetry.js",
+ this
+);
+
+add_setup(async function testNoTelemetry() {
+ await Telemetry.clearScalars();
+});
+
+add_task(async function testAudioDecodingInContent() {
+ await runTest({ expectUtility: false, expectRDD: false });
+});
+
+add_task(async function testUtilityTelemetry() {
+ const codecs = ["vorbis", "mp3", "aac", "flac"];
+ const extraKey = ",rdd-disabled,utility-disabled";
+ await verifyTelemetryForProcess("tab", codecs, extraKey);
+
+ const platform = Services.appinfo.OS;
+ for (let exp of utilityPerCodecs[platform]) {
+ await verifyNoTelemetryForProcess(exp.process, exp.codecs, extraKey);
+ }
+
+ await verifyNoTelemetryForProcess("rdd", codecs, extraKey);
+});
diff --git a/ipc/glue/test/browser/browser_audio_telemetry_rdd.js b/ipc/glue/test/browser/browser_audio_telemetry_rdd.js
new file mode 100644
index 0000000000..b4e41b562e
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_telemetry_rdd.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head-telemetry.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/ipc/glue/test/browser/head-telemetry.js",
+ this
+);
+
+add_setup(async function testNoTelemetry() {
+ await Telemetry.clearScalars();
+});
+
+add_task(async function testAudioDecodingInRDD() {
+ await runTest({ expectUtility: false, expectRDD: true });
+});
+
+add_task(async function testRDDTelemetry() {
+ const extraKey = ",utility-disabled";
+ const platform = Services.appinfo.OS;
+ for (let exp of utilityPerCodecs[platform]) {
+ await verifyNoTelemetryForProcess(exp.process, exp.codecs, extraKey);
+ }
+ const codecs = ["vorbis", "mp3", "aac", "flac"];
+ await verifyTelemetryForProcess("rdd", codecs, extraKey);
+});
diff --git a/ipc/glue/test/browser/browser_audio_telemetry_utility.js b/ipc/glue/test/browser/browser_audio_telemetry_utility.js
new file mode 100644
index 0000000000..07b492cc12
--- /dev/null
+++ b/ipc/glue/test/browser/browser_audio_telemetry_utility.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head-telemetry.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/ipc/glue/test/browser/head-telemetry.js",
+ this
+);
+
+add_setup(async function testNoTelemetry() {
+ await Telemetry.clearScalars();
+});
+
+add_task(async function testAudioDecodingInUtility() {
+ await runTest({ expectUtility: true, expectRDD: true });
+});
+
+add_task(async function testUtilityTelemetry() {
+ const platform = Services.appinfo.OS;
+ for (let exp of utilityPerCodecs[platform]) {
+ await verifyTelemetryForProcess(exp.process, exp.codecs);
+ }
+ await verifyNoTelemetryForProcess("rdd", ["vorbis", "mp3", "aac", "flac"]);
+});
diff --git a/ipc/glue/test/browser/browser_child_hang.ini b/ipc/glue/test/browser/browser_child_hang.ini
new file mode 100644
index 0000000000..94931561b0
--- /dev/null
+++ b/ipc/glue/test/browser/browser_child_hang.ini
@@ -0,0 +1,7 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+[DEFAULT]
+tags = ipc
+environment = MOZ_TEST_CHILD_EXIT_HANG=3
+
+[browser_child_hang.js]
diff --git a/ipc/glue/test/browser/browser_child_hang.js b/ipc/glue/test/browser/browser_child_hang.js
new file mode 100644
index 0000000000..cf890a6c61
--- /dev/null
+++ b/ipc/glue/test/browser/browser_child_hang.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+//
+// Try to open a tab. This provides code coverage for a few things,
+// although currently there's no automated functional test of correctness:
+//
+// * On opt builds, when the tab is closed and the process exits, it
+// will hang for 3s and the parent will kill it after 2s.
+//
+// * On debug[*] builds, the parent process will wait until the
+// process exits normally; but also, on browser shutdown, the
+// preallocated content processes will block parent shutdown in
+// WillDestroyCurrentMessageLoop.
+//
+// [*] Also sanitizer and code coverage builds.
+//
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "https://example.com/",
+ forceNewProcess: true,
+ },
+ async function (browser) {
+ // browser.frameLoader.remoteTab.osPid is the child pid; once we
+ // have a way to get notifications about child process termination
+ // events, that could be useful.
+ ok(true, "Browser isn't broken");
+ }
+ );
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 4000));
+ ok(true, "Still running after child process (hopefully) exited");
+});
diff --git a/ipc/glue/test/browser/browser_utility_audioDecodeCrash.js b/ipc/glue/test/browser/browser_utility_audioDecodeCrash.js
new file mode 100644
index 0000000000..1c7551c623
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_audioDecodeCrash.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function getAudioDecoderPid(expectation) {
+ info("Finding a running AudioDecoder");
+
+ const actor = expectation.replace("Utility ", "");
+
+ let audioDecoderProcess = (await ChromeUtils.requestProcInfo()).children.find(
+ p =>
+ p.type === "utility" &&
+ p.utilityActors.find(a => a.actorName === `audioDecoder_${actor}`)
+ );
+ ok(
+ audioDecoderProcess,
+ `Found the AudioDecoder ${actor} process at ${audioDecoderProcess.pid}`
+ );
+ return audioDecoderProcess.pid;
+}
+
+async function crashDecoder(expectation) {
+ const audioPid = await getAudioDecoderPid(expectation);
+ ok(audioPid > 0, `Found an audio decoder ${audioPid}`);
+ const actorIsAudioDecoder = actorNames => {
+ return actorNames
+ .split(",")
+ .some(actorName => actorName.trim().startsWith("audio-decoder-"));
+ };
+ info(`Crashing audio decoder ${audioPid}`);
+ await crashSomeUtility(audioPid, actorIsAudioDecoder);
+}
+
+async function runTest(src, withClose, expectation) {
+ info(`Add media tabs: ${src}`);
+ let tab = await addMediaTab(src);
+
+ info("Play tab");
+ await play(tab, expectation.process, expectation.decoder);
+
+ info("Crash decoder");
+ await crashDecoder(expectation.process);
+
+ if (withClose) {
+ info("Stop tab");
+ await stop(tab);
+
+ info("Remove tab");
+ await BrowserTestUtils.removeTab(tab);
+
+ info("Create tab again");
+ tab = await addMediaTab(src);
+ }
+
+ info("Play tab again");
+ await play(tab, expectation.process, expectation.decoder);
+
+ info("Stop tab");
+ await stop(tab);
+
+ info("Remove tab");
+ await BrowserTestUtils.removeTab(tab);
+}
+
+add_setup(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.utility-process.enabled", true]],
+ });
+});
+
+async function testAudioCrash(withClose) {
+ info(`Running tests for audio decoder process crashing: ${withClose}`);
+
+ SimpleTest.expectChildProcessCrash();
+
+ const platform = Services.appinfo.OS;
+
+ for (let { src, expectations } of audioTestData()) {
+ if (!(platform in expectations)) {
+ info(`Skipping ${src} for ${platform}`);
+ continue;
+ }
+
+ await runTest(src, withClose, expectations[platform]);
+ }
+}
+
+add_task(async function testAudioCrashSimple() {
+ await testAudioCrash(false);
+});
+
+add_task(async function testAudioCrashClose() {
+ await testAudioCrash(true);
+});
diff --git a/ipc/glue/test/browser/browser_utility_audio_shutdown.js b/ipc/glue/test/browser/browser_utility_audio_shutdown.js
new file mode 100644
index 0000000000..31811cfd55
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_audio_shutdown.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// The purpose of that test is to reproduce edge case behaviors that one can
+// have while running whole ipc/glue/test/browser/ suite but that could this
+// way be intermittent and hard to diagnose. By having such a test we make sure
+// it is cleanly reproduced and wont regress somewhat silently.
+
+"use strict";
+
+async function runTest(src, process, decoder) {
+ info(`Add media tabs: ${src}`);
+ let tab = await addMediaTab(src);
+
+ info("Play tab");
+ await play(tab, process, decoder);
+
+ info("Stop tab");
+ await stop(tab);
+
+ info("Remove tab");
+ await BrowserTestUtils.removeTab(tab);
+}
+
+async function findGenericAudioDecoder() {
+ const audioDecoders = (await ChromeUtils.requestProcInfo()).children.filter(
+ p => {
+ return (
+ p.type === "utility" &&
+ p.utilityActors.find(a => a.actorName === "audioDecoder_Generic")
+ );
+ }
+ );
+ ok(audioDecoders.length === 1, "Only one audio decoder present");
+ return audioDecoders[0].pid;
+}
+
+add_setup(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.utility-process.enabled", true]],
+ });
+});
+
+add_task(async function testKill() {
+ await runTest("small-shot.ogg", "Utility Generic", "vorbis audio decoder");
+
+ await cleanUtilityProcessShutdown(
+ "audioDecoder_Generic",
+ true /* preferKill */
+ );
+
+ info("Waiting 15s to trigger mShutdownBlockers assertions");
+ await new Promise((resolve, reject) => {
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ setTimeout(resolve, 15 * 1000);
+ });
+
+ ok(true, "Waited 15s to trigger mShutdownBlockers assertions: over");
+});
+
+add_task(async function testShutdown() {
+ await runTest("small-shot.ogg", "Utility Generic", "vorbis audio decoder");
+
+ const audioDecoderPid = await findGenericAudioDecoder();
+ ok(audioDecoderPid > 0, `Valid PID found: ${audioDecoderPid}`);
+
+ await cleanUtilityProcessShutdown("audioDecoder_Generic");
+
+ info("Waiting 15s to trigger mShutdownBlockers assertions");
+ await new Promise((resolve, reject) => {
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ setTimeout(resolve, 15 * 1000);
+ });
+
+ ok(true, "Waited 15s to trigger mShutdownBlockers assertions: over");
+});
diff --git a/ipc/glue/test/browser/browser_utility_crashReporter.js b/ipc/glue/test/browser/browser_utility_crashReporter.js
new file mode 100644
index 0000000000..73e6c6355a
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_crashReporter.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function startAndCrashUtility(numUnknownActors, actorsCheck) {
+ const actors = Array(numUnknownActors).fill("unknown");
+ const utilityPid = await startUtilityProcess(actors);
+ await crashSomeUtility(utilityPid, actorsCheck);
+}
+
+// When running full suite, previous tests may have left some utility
+// processes running and this might interfere with our testing.
+add_setup(async function ensureNoExistingProcess() {
+ await killUtilityProcesses();
+});
+
+add_task(async function utilityNoActor() {
+ await startAndCrashUtility(0, actorNames => {
+ return actorNames === undefined;
+ });
+});
+
+add_task(async function utilityOneActor() {
+ await startAndCrashUtility(1, actorNames => {
+ return actorNames === kGenericUtilityActor;
+ });
+});
+
+add_task(async function utilityManyActors() {
+ await startAndCrashUtility(42, actorNames => {
+ return actorNames === Array(42).fill("unknown").join(", ");
+ });
+});
diff --git a/ipc/glue/test/browser/browser_utility_geolocation_crashed.js b/ipc/glue/test/browser/browser_utility_geolocation_crashed.js
new file mode 100644
index 0000000000..b0c341b69f
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_geolocation_crashed.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function getGeolocation() {
+ info("Requesting geolocation");
+
+ let resolve;
+ let promise = new Promise((_resolve, _reject) => {
+ resolve = _resolve;
+ });
+
+ navigator.geolocation.getCurrentPosition(
+ () => {
+ ok(true, "geolocation succeeded");
+ resolve(undefined);
+ },
+ () => {
+ ok(false, "geolocation failed");
+ resolve(undefined);
+ }
+ );
+
+ return promise;
+}
+
+add_setup(async function () {
+ // Avoid the permission doorhanger and cache that would trigger instead
+ // of re-requesting location. Setting geo.timeout to 0 causes it to
+ // retry the system geolocation (incl. recreating the utility process)
+ // instead of reusing the MLS geolocation fallback it found the first time.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["geo.prompt.testing", true],
+ ["geo.prompt.testing.allow", true],
+ ["geo.provider.network.debug.requestCache.enabled", false],
+ ["geo.provider.testing", false],
+ ["geo.timeout", 0],
+ ],
+ });
+});
+
+add_task(async function testGeolocationProcessCrash() {
+ info("Start the Windows utility process");
+ await getGeolocation();
+
+ info("Crash the utility process");
+ await crashSomeUtilityActor("windowsUtils");
+
+ info("Restart the Windows utility process");
+ await getGeolocation();
+
+ info("Confirm the restarted process");
+ await checkUtilityExists("windowsUtils");
+
+ info("Kill the utility process");
+ await cleanUtilityProcessShutdown("windowsUtils", true);
+
+ info("Restart the Windows utility process again");
+ await getGeolocation();
+
+ info("Confirm the restarted process");
+ await checkUtilityExists("windowsUtils");
+});
+
+add_task(async function testCleanup() {
+ info("Clean up to avoid confusing future tests");
+ await cleanUtilityProcessShutdown("windowsUtils", true);
+});
diff --git a/ipc/glue/test/browser/browser_utility_hard_kill.js b/ipc/glue/test/browser/browser_utility_hard_kill.js
new file mode 100644
index 0000000000..516ef64045
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_hard_kill.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ await startUtilityProcess(["unknown"]);
+
+ SimpleTest.expectChildProcessCrash();
+
+ info("Hard kill Utility Process");
+ await cleanUtilityProcessShutdown("unknown", true /* preferKill */);
+});
diff --git a/ipc/glue/test/browser/browser_utility_hard_kill_delayed.js b/ipc/glue/test/browser/browser_utility_hard_kill_delayed.js
new file mode 100644
index 0000000000..ffda4d3988
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_hard_kill_delayed.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ const utilityPid = await startUtilityProcess();
+
+ SimpleTest.expectChildProcessCrash();
+
+ const utilityProcessGone = TestUtils.topicObserved("ipc:utility-shutdown");
+
+ info("Hard kill Utility Process");
+ const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService(
+ Ci.nsIProcessToolsService
+ );
+
+ // Here we really want to exercise the fact that kill() might not be done
+ // right now but a bit later, and we should wait for the process to be dead
+ // before considering the test is finished.
+ //
+ // Without this, we get into bug 1754572 (where there was no setTimeout nor
+ // the wait) where the kill() operation ends up really killing the child a
+ // bit after the current test has been finished ; unfortunately, this happened
+ // right after the next test, browser_utility_memoryReport.js did start and
+ // even worse, after it thought it had started a new utility process. We were
+ // in fact re-using the one we started here, and when we wanted to query its
+ // pid in the browser_utility_memoryReport.js then the kill() happened, so
+ // no more process and the test intermittently failed.
+ //
+ // The timeout value of 50ms should be long enough to allow the test to finish
+ // and the next one to start and get a reference on the process we launched,
+ // and yet allow us to kill the process in the middle of the next test. Higher
+ // values would allow browser_utility_memoryReport.js to complete without
+ // reproducing the issue (both locally and on try).
+ //
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(() => {
+ ProcessTools.kill(utilityPid);
+ }, 50);
+
+ info(`Waiting for utility process ${utilityPid} to go away.`);
+ let [subject, data] = await utilityProcessGone;
+ ok(
+ subject instanceof Ci.nsIPropertyBag2,
+ "Subject needs to be a nsIPropertyBag2 to clean up properly"
+ );
+ is(
+ parseInt(data, 10),
+ utilityPid,
+ `Should match the crashed PID ${utilityPid} with ${data}`
+ );
+
+ // Make sure the process is dead, otherwise there is a risk of race for
+ // writing leak logs
+ utilityProcessTest().noteIntentionalCrash(utilityPid);
+});
diff --git a/ipc/glue/test/browser/browser_utility_memoryReport.js b/ipc/glue/test/browser/browser_utility_memoryReport.js
new file mode 100644
index 0000000000..8cec61b8be
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_memoryReport.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// When running full suite, previous audio decoding tests might have left some
+// running and this might interfere with our testing
+add_setup(async function ensureNoExistingProcess() {
+ await killUtilityProcesses();
+});
+
+add_task(async () => {
+ const utilityPid = await startUtilityProcess();
+
+ const gMgr = Cc["@mozilla.org/memory-reporter-manager;1"].getService(
+ Ci.nsIMemoryReporterManager
+ );
+ ok(utilityPid !== undefined, `Utility process is running as ${utilityPid}`);
+
+ var utilityReports = [];
+
+ const performCollection = new Promise((resolve, reject) => {
+ // Record the reports from the live memory reporters then process them.
+ let handleReport = function (
+ aProcess,
+ aUnsafePath,
+ aKind,
+ aUnits,
+ aAmount,
+ aDescription
+ ) {
+ const expectedProcess = `Utility (pid ${utilityPid}, sandboxingKind ${kGenericUtilitySandbox})`;
+ if (aProcess !== expectedProcess) {
+ return;
+ }
+
+ let report = {
+ process: aProcess,
+ path: aUnsafePath,
+ kind: aKind,
+ units: aUnits,
+ amount: aAmount,
+ description: aDescription,
+ };
+
+ utilityReports.push(report);
+ };
+
+ info("Memory report: Perform the call");
+ gMgr.getReports(handleReport, null, resolve, null, false);
+ });
+
+ await performCollection;
+
+ info(
+ `Collected ${utilityReports.length} reports from utility process ${utilityPid}`
+ );
+ ok(!!utilityReports.length, "Collected some reports");
+ ok(
+ utilityReports.filter(r => r.path === "vsize" && r.amount > 0).length === 1,
+ "Collected vsize report"
+ );
+ ok(
+ utilityReports.filter(r => r.path === "resident" && r.amount > 0).length ===
+ 1,
+ "Collected resident report"
+ );
+ ok(
+ !!utilityReports.filter(
+ r => r.path.search(/^explicit\/.*/) >= 0 && r.amount > 0
+ ).length,
+ "Collected some explicit/ report"
+ );
+
+ await cleanUtilityProcessShutdown();
+});
diff --git a/ipc/glue/test/browser/browser_utility_multipleAudio.js b/ipc/glue/test/browser/browser_utility_multipleAudio.js
new file mode 100644
index 0000000000..d88ee014e2
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_multipleAudio.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function runTest(expectUtility) {
+ info(
+ `Running tests with decoding from Utility or RDD: expectUtility=${expectUtility}`
+ );
+
+ // Utility should now be the default, so dont toggle the pref unless we test
+ // RDD
+ if (!expectUtility) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.utility-process.enabled", expectUtility]],
+ });
+ }
+
+ const platform = Services.appinfo.OS;
+
+ for (let { src, expectations } of audioTestData()) {
+ if (!(platform in expectations)) {
+ info(`Skipping ${src} for ${platform}`);
+ continue;
+ }
+
+ const expectation = expectations[platform];
+
+ info(`Add media tabs: ${src}`);
+ let tabs = [await addMediaTab(src), await addMediaTab(src)];
+ let playback = [];
+
+ info("Play tabs");
+ for (let tab of tabs) {
+ playback.push(
+ play(
+ tab,
+ expectUtility ? expectation.process : "RDD",
+ expectation.decoder
+ )
+ );
+ }
+
+ info("Wait all playback");
+ await Promise.all(playback);
+
+ let allstop = [];
+ info("Stop tabs");
+ for (let tab of tabs) {
+ allstop.push(stop(tab));
+ }
+
+ info("Wait all stop");
+ await Promise.all(allstop);
+
+ let remove = [];
+ info("Remove tabs");
+ for (let tab of tabs) {
+ remove.push(BrowserTestUtils.removeTab(tab));
+ }
+
+ info("Wait all tabs to be removed");
+ await Promise.all(remove);
+ }
+}
+
+add_task(async function testAudioDecodingInUtility() {
+ await runTest(true);
+});
+
+add_task(async function testAudioDecodingInRDD() {
+ await runTest(false);
+});
diff --git a/ipc/glue/test/browser/browser_utility_profiler.js b/ipc/glue/test/browser/browser_utility_profiler.js
new file mode 100644
index 0000000000..084cd67747
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_profiler.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from /tools/profiler/tests/shared-head.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/tools/profiler/tests/browser/shared-head.js",
+ this
+);
+
+// When running full suite, previous tests may have left some utility
+// processes running and this might interfere with our testing.
+add_setup(async function ensureNoExistingProcess() {
+ await killUtilityProcesses();
+});
+
+add_task(async () => {
+ const utilityPid = await startUtilityProcess();
+
+ info("Start the profiler");
+ await startProfiler();
+
+ let profile;
+ await TestUtils.waitForCondition(async () => {
+ profile = await Services.profiler.getProfileDataAsync();
+ return (
+ // Search for process name to not be disturbed by other types of utility
+ // e.g. Utility AudioDecoder
+ profile.processes.filter(
+ ps => ps.threads[0].processName === "Utility Process"
+ ).length === 1
+ );
+ }, "Give time for the profiler to start and collect some samples");
+
+ info(`Check that the utility process ${utilityPid} is present.`);
+ let utilityProcessIndex = profile.processes.findIndex(
+ p => p.threads[0].pid == utilityPid
+ );
+ Assert.notEqual(utilityProcessIndex, -1, "Could find index of utility");
+
+ Assert.equal(
+ profile.processes[utilityProcessIndex].threads[0].processType,
+ "utility",
+ "Profile has processType utility"
+ );
+
+ Assert.equal(
+ profile.processes[utilityProcessIndex].threads[0].name,
+ "GeckoMain",
+ "Profile has correct main thread name"
+ );
+
+ Assert.equal(
+ profile.processes[utilityProcessIndex].threads[0].processName,
+ "Utility Process",
+ "Profile has correct process name"
+ );
+
+ Assert.greater(
+ profile.processes[utilityProcessIndex].threads.length,
+ 0,
+ "The utility process should have threads"
+ );
+
+ Assert.equal(
+ profile.threads.length,
+ 1,
+ "The parent process should have only one thread"
+ );
+
+ Services.profiler.StopProfiler();
+
+ await cleanUtilityProcessShutdown();
+});
diff --git a/ipc/glue/test/browser/browser_utility_start_clean_shutdown.js b/ipc/glue/test/browser/browser_utility_start_clean_shutdown.js
new file mode 100644
index 0000000000..62a9e4065b
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_start_clean_shutdown.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ await startUtilityProcess();
+ await cleanUtilityProcessShutdown();
+});
diff --git a/ipc/glue/test/browser/head-telemetry.js b/ipc/glue/test/browser/head-telemetry.js
new file mode 100644
index 0000000000..64a6aee1c4
--- /dev/null
+++ b/ipc/glue/test/browser/head-telemetry.js
@@ -0,0 +1,205 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from head.js */
+
+const Telemetry = Services.telemetry;
+
+const { TelemetryController } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryController.sys.mjs"
+);
+
+/* eslint-disable mozilla/no-redeclare-with-import-autofix */
+const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+);
+
+const MEDIA_AUDIO_PROCESS = "media.audio_process_per_codec_name";
+
+const utilityPerCodecs = {
+ Linux: [
+ {
+ process: "utility+audioDecoder_Generic",
+ codecs: ["vorbis", "mp3", "aac", "flac"],
+ },
+ ],
+ WINNT: [
+ {
+ process: "utility+audioDecoder_Generic",
+ codecs: ["vorbis", "mp3", "flac"],
+ },
+ {
+ process: "utility+audioDecoder_WMF",
+ codecs: ["aac"],
+ },
+ ],
+ Darwin: [
+ {
+ process: "utility+audioDecoder_Generic",
+ codecs: ["vorbis", "mp3", "flac"],
+ },
+ {
+ process: "utility+audioDecoder_AppleMedia",
+ codecs: ["aac"],
+ },
+ ],
+};
+
+const kInterval = 300; /* ms */
+const kRetries = 5;
+
+/**
+ * This function waits until utility scalars are reported into the
+ * scalar snapshot.
+ */
+async function waitForKeyedScalars(process) {
+ await ContentTaskUtils.waitForCondition(
+ () => {
+ const scalars = Telemetry.getSnapshotForKeyedScalars("main", false);
+ return Object.keys(scalars).includes("content");
+ },
+ `Waiting for ${process} scalars to have been set`,
+ kInterval,
+ kRetries
+ );
+}
+
+async function waitForValue(process, codecNames, extra = "") {
+ await ContentTaskUtils.waitForCondition(
+ () => {
+ const telemetry = Telemetry.getSnapshotForKeyedScalars(
+ "main",
+ false
+ ).content;
+ if (telemetry && MEDIA_AUDIO_PROCESS in telemetry) {
+ const keyProcMimeTypes = Object.keys(telemetry[MEDIA_AUDIO_PROCESS]);
+ const found = codecNames.every(item =>
+ keyProcMimeTypes.includes(`${process},${item}${extra}`)
+ );
+ return found;
+ }
+ return false;
+ },
+ `Waiting for ${MEDIA_AUDIO_PROCESS}`,
+ kInterval,
+ kRetries
+ );
+}
+
+async function runTest({ expectUtility = false, expectRDD = false }) {
+ info(
+ `Running tests with decoding from Utility or RDD: expectUtility=${expectUtility} expectRDD=${expectRDD}`
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["media.utility-process.enabled", expectUtility],
+ ["media.rdd-process.enabled", expectRDD],
+ ["toolkit.telemetry.ipcBatchTimeout", 0],
+ ],
+ });
+
+ const platform = Services.appinfo.OS;
+
+ for (let { src, expectations } of audioTestData()) {
+ if (!(platform in expectations)) {
+ info(`Skipping ${src} for ${platform}`);
+ continue;
+ }
+
+ const expectation = expectations[platform];
+
+ info(`Add media tab: ${src}`);
+ let tab = await addMediaTab(src);
+
+ info("Play tab");
+ await play(
+ tab,
+ expectUtility ? expectation.process : "RDD",
+ expectation.decoder,
+ !expectUtility && !expectRDD
+ );
+
+ info("Stop tab");
+ await stop(tab);
+
+ info("Remove tab");
+ await BrowserTestUtils.removeTab(tab);
+ }
+}
+
+function getTelemetry() {
+ const telemetry = Telemetry.getSnapshotForKeyedScalars("main", false).content;
+ return telemetry;
+}
+
+async function verifyTelemetryForProcess(process, codecNames, extraKey = "") {
+ // Once scalars are set by the utility process, they don't immediately get
+ // sent to the parent process. Wait for the Telemetry IPC Timer to trigger
+ // and batch send the data back to the parent process.
+ await waitForKeyedScalars(process);
+ await waitForValue(process, codecNames, extraKey);
+
+ const telemetry = getTelemetry();
+
+ // The amount here depends on how many times RemoteAudioDecoderParent::RemoteAudioDecoderParent
+ // gets called, which might be more than actual audio files being playback, e.g., we would get one for metadata loading, then one for playback etc.
+ // But we dont care really we just want to ensure 0 on RDD, Content and others
+ // in the wild.[${codecName}]
+ codecNames.forEach(codecName => {
+ Assert.equal(
+ telemetry[MEDIA_AUDIO_PROCESS][`${process},${codecName}${extraKey}`],
+ 1,
+ `${MEDIA_AUDIO_PROCESS} must have the correct value (${process}, ${codecName}).`
+ );
+ });
+}
+
+async function verifyNoTelemetryForProcess(process, codecNames, extraKey = "") {
+ try {
+ await waitForKeyedScalars(process);
+ await waitForValue(process, codecNames, extraKey);
+ } catch (ex) {
+ if (ex.indexOf("timed out after") > 0) {
+ Assert.ok(
+ true,
+ `Expected timeout ${process}[${MEDIA_AUDIO_PROCESS}] for ${codecNames}`
+ );
+ } else {
+ Assert.ok(
+ false,
+ `Unexpected exception on ${process}[${MEDIA_AUDIO_PROCESS}] for ${codecNames}: ${ex}`
+ );
+ }
+ }
+
+ const telemetry = getTelemetry();
+
+ // There could be races with telemetry for power usage coming up
+ codecNames.forEach(codecName => {
+ if (telemetry) {
+ if (telemetry && MEDIA_AUDIO_PROCESS in telemetry) {
+ Assert.ok(
+ !(
+ `${process},${codecName}${extraKey}` in
+ telemetry[MEDIA_AUDIO_PROCESS]
+ ),
+ `Some telemetry but no ${process}[${MEDIA_AUDIO_PROCESS}][${codecName}]`
+ );
+ } else {
+ Assert.ok(
+ !(MEDIA_AUDIO_PROCESS in telemetry),
+ `No telemetry for ${process}[${MEDIA_AUDIO_PROCESS}][${codecName}]`
+ );
+ }
+ } else {
+ Assert.equal(
+ undefined,
+ telemetry,
+ `No telemetry for ${process}[${MEDIA_AUDIO_PROCESS}][${codecName}]`
+ );
+ }
+ });
+}
diff --git a/ipc/glue/test/browser/head.js b/ipc/glue/test/browser/head.js
new file mode 100644
index 0000000000..14c0859eb3
--- /dev/null
+++ b/ipc/glue/test/browser/head.js
@@ -0,0 +1,424 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const utilityProcessTest = () => {
+ return Cc["@mozilla.org/utility-process-test;1"].createInstance(
+ Ci.nsIUtilityProcessTest
+ );
+};
+
+const kGenericUtilitySandbox = 0;
+const kGenericUtilityActor = "unknown";
+
+// Start a generic utility process with the given array of utility actor names
+// registered.
+async function startUtilityProcess(actors = []) {
+ info("Start a UtilityProcess");
+ return utilityProcessTest().startProcess(actors);
+}
+
+// Returns an array of process infos for utility processes of the given type
+// or all utility processes if actor is not defined.
+async function getUtilityProcesses(actor = undefined) {
+ let procInfos = (await ChromeUtils.requestProcInfo()).children.filter(p => {
+ return (
+ p.type === "utility" &&
+ (actor == undefined ||
+ p.utilityActors.find(a => a.actorName.startsWith(actor)))
+ );
+ });
+
+ info(`Utility process infos = ${JSON.stringify(procInfos)}`);
+ return procInfos;
+}
+
+async function getUtilityPid(actor) {
+ let process = await getUtilityProcesses(actor);
+ is(process.length, 1, `exactly one ${actor} process exists`);
+ return process[0].pid;
+}
+
+async function checkUtilityExists(actor) {
+ info(`Looking for a running ${actor} utility process`);
+ const utilityPid = await getUtilityPid(actor);
+ ok(utilityPid > 0, `Found ${actor} utility process ${utilityPid}`);
+ return utilityPid;
+}
+
+// "Cleanly stop" a utility process. This will never leave a crash dump file.
+// preferKill will "kill" the process (e.g. SIGABRT) instead of using the
+// UtilityProcessManager.
+// To "crash" -- i.e. shutdown and generate a crash dump -- use
+// crashSomeUtility().
+async function cleanUtilityProcessShutdown(actor, preferKill = false) {
+ info(`${preferKill ? "Kill" : "Clean shutdown"} Utility Process ${actor}`);
+
+ const utilityPid = await getUtilityPid(actor);
+ ok(utilityPid !== undefined, `Must have PID for ${actor} utility process`);
+
+ const utilityProcessGone = TestUtils.topicObserved(
+ "ipc:utility-shutdown",
+ (subject, data) => parseInt(data, 10) === utilityPid
+ );
+
+ if (preferKill) {
+ SimpleTest.expectChildProcessCrash();
+ info(`Kill Utility Process ${utilityPid}`);
+ const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService(
+ Ci.nsIProcessToolsService
+ );
+ ProcessTools.kill(utilityPid);
+ } else {
+ info(`Stopping Utility Process ${utilityPid}`);
+ await utilityProcessTest().stopProcess(actor);
+ }
+
+ let [subject, data] = await utilityProcessGone;
+ ok(
+ subject instanceof Ci.nsIPropertyBag2,
+ "Subject needs to be a nsIPropertyBag2 to clean up properly"
+ );
+ is(
+ parseInt(data, 10),
+ utilityPid,
+ `Should match the crashed PID ${utilityPid} with ${data}`
+ );
+
+ // Make sure the process is dead, otherwise there is a risk of race for
+ // writing leak logs
+ utilityProcessTest().noteIntentionalCrash(utilityPid);
+
+ ok(!subject.hasKey("dumpID"), "There should be no dumpID");
+}
+
+async function killUtilityProcesses() {
+ let utilityProcesses = await getUtilityProcesses();
+ for (const utilityProcess of utilityProcesses) {
+ for (const actor of utilityProcess.utilityActors) {
+ info(`Stopping ${actor.actorName} utility process`);
+ await cleanUtilityProcessShutdown(actor.actorName, /* preferKill */ true);
+ }
+ }
+}
+
+function audioTestData() {
+ return [
+ {
+ src: "small-shot.ogg",
+ expectations: {
+ Android: {
+ process: "Utility Generic",
+ decoder: "vorbis audio decoder",
+ },
+ Linux: {
+ process: "Utility Generic",
+ decoder: "vorbis audio decoder",
+ },
+ WINNT: {
+ process: "Utility Generic",
+ decoder: "vorbis audio decoder",
+ },
+ Darwin: {
+ process: "Utility Generic",
+ decoder: "vorbis audio decoder",
+ },
+ },
+ },
+ {
+ src: "small-shot.mp3",
+ expectations: {
+ Android: { process: "Utility Generic", decoder: "ffvpx audio decoder" },
+ Linux: {
+ process: "Utility Generic",
+ decoder: "ffvpx audio decoder",
+ },
+ WINNT: {
+ process: SpecialPowers.getBoolPref("media.ffvpx.mp3.enabled")
+ ? "Utility Generic"
+ : "Utility WMF",
+ decoder: SpecialPowers.getBoolPref("media.ffvpx.mp3.enabled")
+ ? "ffvpx audio decoder"
+ : "wmf audio decoder",
+ },
+ Darwin: {
+ process: SpecialPowers.getBoolPref("media.ffvpx.mp3.enabled")
+ ? "Utility Generic"
+ : "Utility AppleMedia",
+ decoder: SpecialPowers.getBoolPref("media.ffvpx.mp3.enabled")
+ ? "ffvpx audio decoder"
+ : "apple coremedia decoder",
+ },
+ },
+ },
+ {
+ src: "small-shot.m4a",
+ expectations: {
+ // Add Android after Bug 1771196
+ Linux: {
+ process: "Utility Generic",
+ decoder: "ffmpeg audio decoder",
+ },
+ WINNT: {
+ process: "Utility WMF",
+ decoder: "wmf audio decoder",
+ },
+ Darwin: {
+ process: "Utility AppleMedia",
+ decoder: "apple coremedia decoder",
+ },
+ },
+ },
+ {
+ src: "small-shot.flac",
+ expectations: {
+ Android: { process: "Utility Generic", decoder: "ffvpx audio decoder" },
+ Linux: {
+ process: "Utility Generic",
+ decoder: "ffvpx audio decoder",
+ },
+ WINNT: {
+ process: "Utility Generic",
+ decoder: "ffvpx audio decoder",
+ },
+ Darwin: {
+ process: "Utility Generic",
+ decoder: "ffvpx audio decoder",
+ },
+ },
+ },
+ ];
+}
+
+async function addMediaTab(src) {
+ const tab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
+ forceNewProcess: true,
+ });
+ const browser = gBrowser.getBrowserForTab(tab);
+ await BrowserTestUtils.browserLoaded(browser);
+ await SpecialPowers.spawn(browser, [src], createAudioElement);
+ return tab;
+}
+
+async function play(
+ tab,
+ expectUtility,
+ expectDecoder,
+ expectContent = false,
+ expectJava = false
+) {
+ let browser = tab.linkedBrowser;
+ return SpecialPowers.spawn(
+ browser,
+ [expectUtility, expectDecoder, expectContent, expectJava],
+ checkAudioDecoder
+ );
+}
+
+async function stop(tab) {
+ let browser = tab.linkedBrowser;
+ await SpecialPowers.spawn(browser, [], async function () {
+ let audio = content.document.querySelector("audio");
+ audio.pause();
+ });
+}
+
+async function createAudioElement(src) {
+ const doc = typeof content !== "undefined" ? content.document : document;
+ const ROOT = "https://example.com/browser/ipc/glue/test/browser";
+ let audio = doc.createElement("audio");
+ audio.setAttribute("controls", "true");
+ audio.setAttribute("loop", true);
+ audio.src = `${ROOT}/${src}`;
+ doc.body.appendChild(audio);
+}
+
+async function checkAudioDecoder(
+ expectedProcess,
+ expectedDecoder,
+ expectContent = false,
+ expectJava = false
+) {
+ const doc = typeof content !== "undefined" ? content.document : document;
+ let audio = doc.querySelector("audio");
+ const checkPromise = new Promise((resolve, reject) => {
+ const timeUpdateHandler = async ev => {
+ const debugInfo = await SpecialPowers.wrap(audio).mozRequestDebugInfo();
+ const audioDecoderName = debugInfo.decoder.reader.audioDecoderName;
+
+ const isExpectedDecoder =
+ audioDecoderName.indexOf(`${expectedDecoder}`) == 0;
+ ok(
+ isExpectedDecoder,
+ `playback ${audio.src} was from decoder '${audioDecoderName}', expected '${expectedDecoder}'`
+ );
+
+ const isExpectedProcess =
+ audioDecoderName.indexOf(`(${expectedProcess} remote)`) > 0;
+ const isJavaRemote = audioDecoderName.indexOf("(remote)") > 0;
+ const isOk =
+ (isExpectedProcess && !isJavaRemote && !expectContent && !expectJava) || // Running in Utility/RDD
+ (expectJava && !isExpectedProcess && isJavaRemote) || // Running in Java remote
+ (expectContent && !isExpectedProcess && !isJavaRemote); // Running in Content
+
+ ok(
+ isOk,
+ `playback ${audio.src} was from process '${audioDecoderName}', expected '${expectedProcess}'`
+ );
+
+ if (isOk) {
+ resolve();
+ } else {
+ reject();
+ }
+ };
+
+ const startPlaybackHandler = async ev => {
+ ok(
+ await audio.play().then(
+ _ => true,
+ _ => false
+ ),
+ "audio started playing"
+ );
+
+ audio.addEventListener("timeupdate", timeUpdateHandler, { once: true });
+ };
+
+ audio.addEventListener("canplaythrough", startPlaybackHandler, {
+ once: true,
+ });
+ });
+
+ // We need to make sure the decoder is ready before play()ing otherwise we
+ // could get into bad situations
+ audio.load();
+ return checkPromise;
+}
+
+async function runMochitestUtilityAudio(
+ src,
+ {
+ expectUtility,
+ expectDecoder,
+ expectContent = false,
+ expectJava = false,
+ } = {}
+) {
+ info(`Add media: ${src}`);
+ await createAudioElement(src);
+ let audio = document.querySelector("audio");
+ ok(audio, "Found an audio element created");
+
+ info(`Play media: ${src}`);
+ await checkAudioDecoder(
+ expectUtility,
+ expectDecoder,
+ expectContent,
+ expectJava
+ );
+
+ info(`Pause media: ${src}`);
+ await audio.pause();
+
+ info(`Remove media: ${src}`);
+ document.body.removeChild(audio);
+}
+
+async function crashSomeUtility(utilityPid, actorsCheck) {
+ SimpleTest.expectChildProcessCrash();
+
+ const crashMan = Services.crashmanager;
+ const utilityProcessGone = TestUtils.topicObserved(
+ "ipc:utility-shutdown",
+ (subject, data) => {
+ info(`ipc:utility-shutdown: data=${data} subject=${subject}`);
+ return parseInt(data, 10) === utilityPid;
+ }
+ );
+
+ info("prune any previous crashes");
+ const future = new Date(Date.now() + 1000 * 60 * 60 * 24);
+ await crashMan.pruneOldCrashes(future);
+
+ info("crash Utility Process");
+ const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService(
+ Ci.nsIProcessToolsService
+ );
+
+ info(`Crash Utility Process ${utilityPid}`);
+ ProcessTools.crash(utilityPid);
+
+ info(`Waiting for utility process ${utilityPid} to go away.`);
+ let [subject, data] = await utilityProcessGone;
+ ok(
+ parseInt(data, 10) === utilityPid,
+ `Should match the crashed PID ${utilityPid} with ${data}`
+ );
+ ok(
+ subject instanceof Ci.nsIPropertyBag2,
+ "Subject needs to be a nsIPropertyBag2 to clean up properly"
+ );
+
+ // Make sure the process is dead, otherwise there is a risk of race for
+ // writing leak logs
+ utilityProcessTest().noteIntentionalCrash(utilityPid);
+
+ const dumpID = subject.getPropertyAsAString("dumpID");
+ ok(dumpID, "There should be a dumpID");
+
+ await crashMan.ensureCrashIsPresent(dumpID);
+ await crashMan.getCrashes().then(crashes => {
+ is(crashes.length, 1, "There should be only one record");
+ const crash = crashes[0];
+ ok(
+ crash.isOfType(
+ crashMan.processTypes[Ci.nsIXULRuntime.PROCESS_TYPE_UTILITY],
+ crashMan.CRASH_TYPE_CRASH
+ ),
+ "Record should be a utility process crash"
+ );
+ ok(crash.id === dumpID, "Record should have an ID");
+ ok(
+ actorsCheck(crash.metadata.UtilityActorsName),
+ `Record should have the correct actors name for: ${crash.metadata.UtilityActorsName}`
+ );
+ });
+
+ let minidumpDirectory = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ minidumpDirectory.append("minidumps");
+
+ let dumpfile = minidumpDirectory.clone();
+ dumpfile.append(dumpID + ".dmp");
+ if (dumpfile.exists()) {
+ info(`Removal of ${dumpfile.path}`);
+ dumpfile.remove(false);
+ }
+
+ let extrafile = minidumpDirectory.clone();
+ extrafile.append(dumpID + ".extra");
+ info(`Removal of ${extrafile.path}`);
+ if (extrafile.exists()) {
+ extrafile.remove(false);
+ }
+}
+
+// Crash a utility process and generate a crash dump. To close a utility
+// process (forcefully or not) without a generating a crash, use
+// cleanUtilityProcessShutdown.
+async function crashSomeUtilityActor(
+ actor,
+ actorsCheck = () => {
+ return true;
+ }
+) {
+ // Get PID for utility type
+ const procInfos = await getUtilityProcesses(actor);
+ ok(
+ procInfos.length == 1,
+ `exactly one ${actor} utility process should be found`
+ );
+ const utilityPid = procInfos[0].pid;
+ return crashSomeUtility(utilityPid, actorsCheck);
+}
diff --git a/ipc/glue/test/browser/mochitest_audio_off.ini b/ipc/glue/test/browser/mochitest_audio_off.ini
new file mode 100644
index 0000000000..1e105c5f87
--- /dev/null
+++ b/ipc/glue/test/browser/mochitest_audio_off.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+run-if = toolkit == 'android' && !isolated_process # Bug 1771452
+support-files =
+ head.js
+ ../../../../dom/media/test/small-shot.ogg
+ ../../../../dom/media/test/small-shot.mp3
+ ../../../../dom/media/test/small-shot.m4a
+ ../../../../dom/media/test/small-shot.flac
+prefs =
+ media.utility-process.enabled=false
+
+[test_utility_audio_off.html]
diff --git a/ipc/glue/test/browser/mochitest_audio_on.ini b/ipc/glue/test/browser/mochitest_audio_on.ini
new file mode 100644
index 0000000000..8d74572341
--- /dev/null
+++ b/ipc/glue/test/browser/mochitest_audio_on.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+run-if = toolkit == 'android' && !isolated_process # Bug 1771452
+support-files =
+ head.js
+ ../../../../dom/media/test/small-shot.ogg
+ ../../../../dom/media/test/small-shot.mp3
+ ../../../../dom/media/test/small-shot.m4a
+ ../../../../dom/media/test/small-shot.flac
+prefs =
+ media.utility-process.enabled=true
+
+[test_utility_audio_on.html]
diff --git a/ipc/glue/test/browser/moz.build b/ipc/glue/test/browser/moz.build
new file mode 100644
index 0000000000..d0a85bebcd
--- /dev/null
+++ b/ipc/glue/test/browser/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+BROWSER_CHROME_MANIFESTS += [
+ "browser.ini",
+ "browser_audio_shutdown.ini",
+ "browser_child_hang.ini",
+]
+MOCHITEST_MANIFESTS += ["mochitest_audio_off.ini", "mochitest_audio_on.ini"]
diff --git a/ipc/glue/test/browser/test_utility_audio_off.html b/ipc/glue/test/browser/test_utility_audio_off.html
new file mode 100644
index 0000000000..7df18ed383
--- /dev/null
+++ b/ipc/glue/test/browser/test_utility_audio_off.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Audio decoder not in Utility process</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+(async function() {
+ const platform = SpecialPowers.Services.appinfo.OS;
+ for (let {src, expectations} of audioTestData()) {
+ if (!(platform in expectations)) {
+ info(`Skipping ${src} for ${platform}`);
+ continue;
+ }
+
+ try {
+ await runMochitestUtilityAudio(src, { expectUtility: "", expectDecoder: expectations[platform].decoder, expectContent: true, expectJava: false });
+ } catch (ex) {
+ ok(false, "Failure");
+ }
+ }
+
+ for (let src of [
+ "small-shot.m4a",
+ ]) {
+ try {
+ await runMochitestUtilityAudio(src, { expectUtility: "", expectDecoder: "android decoder", expectContent: false, expectJava: true });
+ } catch (ex) {
+ ok(false, `Failure ${ex}`);
+ }
+ }
+
+ SimpleTest.finish();
+})();
+</script>
+</pre>
+</body>
+</html>
diff --git a/ipc/glue/test/browser/test_utility_audio_on.html b/ipc/glue/test/browser/test_utility_audio_on.html
new file mode 100644
index 0000000000..f473527520
--- /dev/null
+++ b/ipc/glue/test/browser/test_utility_audio_on.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Audio decoder in Utility process</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+(async function() {
+ const platform = SpecialPowers.Services.appinfo.OS;
+ for (let {src, expectations} of audioTestData()) {
+ if (!(platform in expectations)) {
+ info(`Skipping ${src} for ${platform}`);
+ continue;
+ }
+
+ try {
+ await runMochitestUtilityAudio(src, { expectUtility: expectations[platform].process, expectDecoder: expectations[platform].decoder, expectContent: false, expectJava: false });
+ } catch (ex) {
+ ok(false, "Failure");
+ }
+ }
+
+ // Remove all after Bug 1771196
+ for (let src of [
+ "small-shot.m4a",
+ ]) {
+ try {
+ await runMochitestUtilityAudio(src, { expectUtility: "", expectDecoder: "android decoder", expectContent: false, expectJava: true });
+ } catch (ex) {
+ ok(false, `Failure ${ex}`);
+ }
+ }
+
+ SimpleTest.finish();
+})();
+</script>
+</pre>
+</body>
+</html>
diff --git a/ipc/glue/test/gtest/TestAsyncBlockers.cpp b/ipc/glue/test/gtest/TestAsyncBlockers.cpp
new file mode 100644
index 0000000000..6f8d298621
--- /dev/null
+++ b/ipc/glue/test/gtest/TestAsyncBlockers.cpp
@@ -0,0 +1,166 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "gtest/gtest.h"
+
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/ipc/AsyncBlockers.h"
+#include "mozilla/gtest/MozHelpers.h"
+
+#include "nsCOMPtr.h"
+#include "nsITimer.h"
+#include "nsINamed.h"
+
+using namespace mozilla;
+using namespace mozilla::ipc;
+
+#define PROCESS_EVENTS_UNTIL(_done) \
+ SpinEventLoopUntil("TestAsyncBlockers"_ns, [&]() { return _done; });
+
+class TestAsyncBlockers : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ SAVE_GDB_SLEEP(mOldSleepDuration);
+ return;
+ }
+
+ void TearDown() final { RESTORE_GDB_SLEEP(mOldSleepDuration); }
+
+ private:
+#if defined(HAS_GDB_SLEEP_DURATION)
+ unsigned int mOldSleepDuration = 0;
+#endif // defined(HAS_GDB_SLEEP_DURATION)
+};
+
+class Blocker {};
+
+TEST_F(TestAsyncBlockers, Register) {
+ AsyncBlockers blockers;
+ Blocker* blocker = new Blocker();
+ blockers.Register(blocker);
+ EXPECT_TRUE(true);
+}
+
+TEST_F(TestAsyncBlockers, Register_Deregister) {
+ AsyncBlockers blockers;
+ Blocker* blocker = new Blocker();
+ blockers.Register(blocker);
+ blockers.Deregister(blocker);
+ EXPECT_TRUE(true);
+}
+
+TEST_F(TestAsyncBlockers, Register_WaitUntilClear) {
+ AsyncBlockers blockers;
+ bool done = false;
+
+ Blocker* blocker = new Blocker();
+ blockers.Register(blocker);
+
+ blockers.WaitUntilClear(5 * 1000)->Then(GetCurrentSerialEventTarget(),
+ __func__, [&]() {
+ EXPECT_TRUE(true);
+ done = true;
+ });
+
+ NS_ProcessPendingEvents(nullptr);
+
+ blockers.Deregister(blocker);
+
+ PROCESS_EVENTS_UNTIL(done);
+}
+
+class AsyncBlockerTimerCallback : public nsITimerCallback, public nsINamed {
+ protected:
+ virtual ~AsyncBlockerTimerCallback();
+
+ public:
+ explicit AsyncBlockerTimerCallback() {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+};
+
+NS_IMPL_ISUPPORTS(AsyncBlockerTimerCallback, nsITimerCallback, nsINamed)
+
+AsyncBlockerTimerCallback::~AsyncBlockerTimerCallback() = default;
+
+NS_IMETHODIMP
+AsyncBlockerTimerCallback::Notify(nsITimer* timer) {
+ // If we resolve through this, it means
+ // blockers.WaitUntilClear() started to wait for
+ // the completion of the timeout which is not
+ // good.
+ EXPECT_TRUE(false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncBlockerTimerCallback::GetName(nsACString& aName) {
+ aName.AssignLiteral("AsyncBlockerTimerCallback");
+ return NS_OK;
+}
+
+TEST_F(TestAsyncBlockers, NoRegister_WaitUntilClear) {
+ AsyncBlockers blockers;
+ bool done = false;
+
+ nsCOMPtr<nsITimer> timer = NS_NewTimer();
+ ASSERT_TRUE(timer);
+
+ RefPtr<AsyncBlockerTimerCallback> timerCb = new AsyncBlockerTimerCallback();
+ timer->InitWithCallback(timerCb, 1 * 1000, nsITimer::TYPE_ONE_SHOT);
+
+ blockers.WaitUntilClear(10 * 1000)->Then(GetCurrentSerialEventTarget(),
+ __func__, [&]() {
+ // If we resolve through this
+ // before the nsITimer it means we
+ // have been resolved before the 5s
+ // timeout
+ EXPECT_TRUE(true);
+ timer->Cancel();
+ done = true;
+ });
+
+ PROCESS_EVENTS_UNTIL(done);
+}
+
+TEST_F(TestAsyncBlockers, Register_WaitUntilClear_0s) {
+ AsyncBlockers blockers;
+ bool done = false;
+
+ Blocker* blocker = new Blocker();
+ blockers.Register(blocker);
+
+ blockers.WaitUntilClear(0)->Then(GetCurrentSerialEventTarget(), __func__,
+ [&]() {
+ EXPECT_TRUE(true);
+ done = true;
+ });
+
+ NS_ProcessPendingEvents(nullptr);
+
+ blockers.Deregister(blocker);
+
+ PROCESS_EVENTS_UNTIL(done);
+}
+
+#if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) && !defined(ANDROID) && \
+ !(defined(XP_DARWIN) && !defined(MOZ_DEBUG))
+static void DeregisterEmpty_Test() {
+ mozilla::gtest::DisableCrashReporter();
+
+ AsyncBlockers blockers;
+ Blocker* blocker = new Blocker();
+ blockers.Deregister(blocker);
+}
+
+TEST_F(TestAsyncBlockers, DeregisterEmpty) {
+ ASSERT_DEATH_IF_SUPPORTED(DeregisterEmpty_Test(), "");
+}
+#endif // defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) && !defined(ANDROID) &&
+ // !(defined(XP_DARWIN) && !defined(MOZ_DEBUG))
+
+#undef PROCESS_EVENTS_UNTIL
diff --git a/ipc/glue/test/gtest/TestUtilityProcess.cpp b/ipc/glue/test/gtest/TestUtilityProcess.cpp
new file mode 100644
index 0000000000..c5d19c992f
--- /dev/null
+++ b/ipc/glue/test/gtest/TestUtilityProcess.cpp
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "gtest/gtest.h"
+#include "mozilla/SpinEventLoopUntil.h"
+
+#include "mozilla/ipc/UtilityProcessManager.h"
+
+#if defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX)
+# include "nsIAppShellService.h"
+# include "nsServiceManagerUtils.h"
+#endif // defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX)
+
+#if defined(XP_WIN)
+# include "mozilla/gtest/MozHelpers.h"
+# include "mozilla/ipc/UtilityProcessImpl.h"
+#endif // defined(XP_WIN)
+
+#ifdef MOZ_WIDGET_ANDROID
+# define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/widget/appshell/android;1"
+#endif // MOZ_WIDGET_ANDROID
+
+#ifdef XP_MACOSX
+# define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/widget/appshell/mac;1"
+#endif // XP_MACOSX
+
+using namespace mozilla;
+using namespace mozilla::ipc;
+
+#define WAIT_FOR_EVENTS \
+ SpinEventLoopUntil("UtilityProcess::emptyUtil"_ns, [&]() { return done; });
+
+bool setupDone = false;
+
+class UtilityProcess : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ if (setupDone) {
+ return;
+ }
+
+#if defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX)
+ appShell = do_GetService(NS_APPSHELLSERVICE_CONTRACTID);
+#endif // defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX)
+
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+ mozilla::SandboxBroker::GeckoDependentInitialize();
+#endif // defined(XP_WIN) && defined(MOZ_SANDBOX)
+
+ setupDone = true;
+ }
+
+#if defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX)
+ nsCOMPtr<nsIAppShellService> appShell;
+#endif // defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX)
+};
+
+TEST_F(UtilityProcess, ProcessManager) {
+ RefPtr<UtilityProcessManager> utilityProc =
+ UtilityProcessManager::GetSingleton();
+ ASSERT_NE(utilityProc, nullptr);
+}
+
+TEST_F(UtilityProcess, NoProcess) {
+ RefPtr<UtilityProcessManager> utilityProc =
+ UtilityProcessManager::GetSingleton();
+ EXPECT_NE(utilityProc, nullptr);
+
+ Maybe<int32_t> noPid =
+ utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY);
+ ASSERT_TRUE(noPid.isNothing());
+}
+
+TEST_F(UtilityProcess, LaunchProcess) {
+ bool done = false;
+
+ RefPtr<UtilityProcessManager> utilityProc =
+ UtilityProcessManager::GetSingleton();
+ EXPECT_NE(utilityProc, nullptr);
+
+ int32_t thisPid = base::GetCurrentProcId();
+ EXPECT_GE(thisPid, 1);
+
+ utilityProc->LaunchProcess(SandboxingKind::GENERIC_UTILITY)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&]() mutable {
+ EXPECT_TRUE(true);
+
+ Maybe<int32_t> utilityPid =
+ utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY);
+ EXPECT_TRUE(utilityPid.isSome());
+ EXPECT_GE(*utilityPid, 1);
+ EXPECT_NE(*utilityPid, thisPid);
+
+ printf_stderr("UtilityProcess running as %d\n", *utilityPid);
+
+ done = true;
+ },
+ [&](nsresult aError) mutable {
+ EXPECT_TRUE(false);
+ done = true;
+ });
+
+ WAIT_FOR_EVENTS;
+}
+
+TEST_F(UtilityProcess, DestroyProcess) {
+ bool done = false;
+
+ RefPtr<UtilityProcessManager> utilityProc =
+ UtilityProcessManager::GetSingleton();
+
+ utilityProc->LaunchProcess(SandboxingKind::GENERIC_UTILITY)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&]() {
+ Maybe<int32_t> utilityPid =
+ utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY);
+ EXPECT_TRUE(utilityPid.isSome());
+ EXPECT_GE(*utilityPid, 1);
+
+ utilityProc->CleanShutdown(SandboxingKind::GENERIC_UTILITY);
+
+ utilityPid =
+ utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY);
+ EXPECT_TRUE(utilityPid.isNothing());
+
+ EXPECT_TRUE(true);
+ done = true;
+ },
+ [&](nsresult aError) {
+ EXPECT_TRUE(false);
+ done = true;
+ });
+
+ WAIT_FOR_EVENTS;
+}
+
+#if defined(XP_WIN)
+static void LoadLibraryCrash_Test() {
+ mozilla::gtest::DisableCrashReporter();
+ // Just a uuidgen name to have something random
+ UtilityProcessImpl::LoadLibraryOrCrash(
+ L"2b49036e-6ba3-400c-a297-38fa1f6c5255.dll");
+}
+
+TEST_F(UtilityProcess, LoadLibraryCrash) {
+ ASSERT_DEATH_IF_SUPPORTED(LoadLibraryCrash_Test(), "");
+}
+#endif // defined(XP_WIN)
+
+#undef WAIT_FOR_EVENTS
diff --git a/ipc/glue/test/gtest/moz.build b/ipc/glue/test/gtest/moz.build
new file mode 100644
index 0000000000..7b72cc7fe1
--- /dev/null
+++ b/ipc/glue/test/gtest/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Library("ipcgluetest")
+
+UNIFIED_SOURCES = [
+ "TestAsyncBlockers.cpp",
+ "TestUtilityProcess.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/widget",
+ "/widget/android",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.cpp b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.cpp
new file mode 100644
index 0000000000..3828c6971b
--- /dev/null
+++ b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.cpp
@@ -0,0 +1,164 @@
+/* -*- 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/. */
+
+#if defined(ENABLE_TESTS)
+# include "mozilla/ipc/UtilityProcessTest.h"
+# include "mozilla/ipc/UtilityProcessManager.h"
+# include "mozilla/dom/Promise.h"
+# include "mozilla/ProcInfo.h"
+# include "mozilla/IntentionalCrash.h"
+
+namespace mozilla::ipc {
+
+static UtilityActorName UtilityActorNameFromString(
+ const nsACString& aStringName) {
+ using namespace mozilla::dom;
+
+ // We use WebIDLUtilityActorNames because UtilityActorNames is not designed
+ // for iteration.
+ for (size_t i = 0; i < WebIDLUtilityActorNameValues::Count; ++i) {
+ auto idlName = static_cast<UtilityActorName>(i);
+ const nsDependentCSubstring idlNameString(
+ WebIDLUtilityActorNameValues::GetString(idlName));
+ if (idlNameString.Equals(aStringName)) {
+ return idlName;
+ }
+ }
+ MOZ_CRASH("Unknown utility actor name");
+}
+
+// Find the utility process with the given actor or any utility process if
+// the actor is UtilityActorName::EndGuard_.
+static SandboxingKind FindUtilityProcessWithActor(UtilityActorName aActorName) {
+ RefPtr<UtilityProcessManager> utilityProc =
+ UtilityProcessManager::GetSingleton();
+ MOZ_ASSERT(utilityProc, "No UtilityprocessManager?");
+
+ for (size_t i = 0; i < SandboxingKind::COUNT; ++i) {
+ auto sbKind = static_cast<SandboxingKind>(i);
+ if (!utilityProc->Process(sbKind)) {
+ continue;
+ }
+ if (aActorName == UtilityActorName::EndGuard_) {
+ return sbKind;
+ }
+ for (auto actor : utilityProc->GetActors(sbKind)) {
+ if (actor == aActorName) {
+ return sbKind;
+ }
+ }
+ }
+
+ return SandboxingKind::COUNT;
+}
+
+NS_IMETHODIMP
+UtilityProcessTest::StartProcess(const nsTArray<nsCString>& aActorsToRegister,
+ JSContext* aCx,
+ mozilla::dom::Promise** aOutPromise) {
+ NS_ENSURE_ARG(aOutPromise);
+ *aOutPromise = nullptr;
+ nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!global)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult erv;
+ RefPtr<dom::Promise> promise = dom::Promise::Create(global, erv);
+ if (NS_WARN_IF(erv.Failed())) {
+ return erv.StealNSResult();
+ }
+
+ RefPtr<UtilityProcessManager> utilityProc =
+ UtilityProcessManager::GetSingleton();
+ MOZ_ASSERT(utilityProc, "No UtilityprocessManager?");
+
+ auto actors = aActorsToRegister.Clone();
+
+ utilityProc->LaunchProcess(SandboxingKind::GENERIC_UTILITY)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise, utilityProc, actors = std::move(actors)] {
+ RefPtr<UtilityProcessParent> utilityParent =
+ utilityProc->GetProcessParent(SandboxingKind::GENERIC_UTILITY);
+ Maybe<int32_t> utilityPid =
+ utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY);
+ for (size_t i = 0; i < actors.Length(); ++i) {
+ auto uan = UtilityActorNameFromString(actors[i]);
+ utilityProc->RegisterActor(utilityParent, uan);
+ }
+ if (utilityPid.isSome()) {
+ promise->MaybeResolve(*utilityPid);
+ } else {
+ promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+ }
+ },
+ [promise](nsresult aError) {
+ MOZ_ASSERT_UNREACHABLE(
+ "UtilityProcessTest; failure to get Utility process");
+ promise->MaybeReject(aError);
+ });
+
+ promise.forget(aOutPromise);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UtilityProcessTest::NoteIntentionalCrash(uint32_t aPid) {
+ mozilla::NoteIntentionalCrash("utility", aPid);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UtilityProcessTest::StopProcess(const char* aActorName) {
+ using namespace mozilla::dom;
+
+ SandboxingKind sbKind;
+ if (aActorName) {
+ const nsDependentCString actorStringName(aActorName);
+ UtilityActorName actorName = UtilityActorNameFromString(actorStringName);
+ sbKind = FindUtilityProcessWithActor(actorName);
+ } else {
+ sbKind = FindUtilityProcessWithActor(UtilityActorName::EndGuard_);
+ }
+
+ if (sbKind == SandboxingKind::COUNT) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Attempted to stop process for actor when no "
+ "such process exists");
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<UtilityProcessManager> utilityProc =
+ UtilityProcessManager::GetSingleton();
+ MOZ_ASSERT(utilityProc, "No UtilityprocessManager?");
+
+ utilityProc->CleanShutdown(sbKind);
+ Maybe<int32_t> utilityPid = utilityProc->ProcessPid(sbKind);
+ MOZ_RELEASE_ASSERT(utilityPid.isNothing(),
+ "Should not have a utility process PID anymore");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UtilityProcessTest::TestTelemetryProbes() {
+ RefPtr<UtilityProcessManager> utilityProc =
+ UtilityProcessManager::GetSingleton();
+ MOZ_ASSERT(utilityProc, "No UtilityprocessManager?");
+
+ for (RefPtr<UtilityProcessParent>& parent :
+ utilityProc->GetAllProcessesProcessParent()) {
+ Unused << parent->SendTestTelemetryProbes();
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(UtilityProcessTest, nsIUtilityProcessTest)
+
+} // namespace mozilla::ipc
+#endif // defined(ENABLE_TESTS)
diff --git a/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.h b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.h
new file mode 100644
index 0000000000..6c80fce71b
--- /dev/null
+++ b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.h
@@ -0,0 +1,29 @@
+/* -*- 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/. */
+#ifndef _include_ipc_glue_UtilityProcessTest_h_
+#define _include_ipc_glue_UtilityProcessTest_h_
+
+#if defined(ENABLE_TESTS)
+# include "nsServiceManagerUtils.h"
+# include "nsIUtilityProcessTest.h"
+
+namespace mozilla::ipc {
+
+class UtilityProcessTest final : public nsIUtilityProcessTest {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUTILITYPROCESSTEST
+
+ UtilityProcessTest() = default;
+
+ private:
+ ~UtilityProcessTest() = default;
+};
+
+} // namespace mozilla::ipc
+#endif // defined(ENABLE_TESTS)
+
+#endif // _include_ipc_glue_UtilityProcessTest_h_
diff --git a/ipc/glue/test/utility_process_xpcom/components.conf b/ipc/glue/test/utility_process_xpcom/components.conf
new file mode 100644
index 0000000000..25208ba7fc
--- /dev/null
+++ b/ipc/glue/test/utility_process_xpcom/components.conf
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{0a4478f4-c5ae-4fb1-8686-d5b09fb99afb}',
+ 'contract_ids': ['@mozilla.org/utility-process-test;1'],
+ 'type': 'mozilla::ipc::UtilityProcessTest',
+ 'headers': ['mozilla/ipc/UtilityProcessTest.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+]
diff --git a/ipc/glue/test/utility_process_xpcom/moz.build b/ipc/glue/test/utility_process_xpcom/moz.build
new file mode 100644
index 0000000000..f04b436cbe
--- /dev/null
+++ b/ipc/glue/test/utility_process_xpcom/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.ipc += ["UtilityProcessTest.h"]
+
+UNIFIED_SOURCES += ["UtilityProcessTest.cpp"]
+
+XPCOM_MANIFESTS += ["components.conf"]
+
+XPIDL_MODULE = "utility_process_xpcom_test"
+
+XPIDL_SOURCES += [
+ "nsIUtilityProcessTest.idl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/ipc/glue/test/utility_process_xpcom/nsIUtilityProcessTest.idl b/ipc/glue/test/utility_process_xpcom/nsIUtilityProcessTest.idl
new file mode 100644
index 0000000000..839c4f3673
--- /dev/null
+++ b/ipc/glue/test/utility_process_xpcom/nsIUtilityProcessTest.idl
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* 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 "nsISupports.idl"
+
+[scriptable, uuid(0a4478f4-c5ae-4fb1-8686-d5b09fb99afb)]
+interface nsIUtilityProcessTest : nsISupports
+{
+ /**
+ * ** Test-only Method **
+ *
+ * Allowing to start Utility Process from JS code.
+ *
+ * actorsToAdd: An array of actor names, taken from WebIDLUtilityActorName.
+ * Unlike normal utility processes, test processes launched this way do not
+ * have any associated actor names unless specified here. Empty by default.
+ */
+ [implicit_jscontext]
+ Promise startProcess([optional] in Array<ACString> actorsToAdd);
+
+ /**
+ * ** Test-only Method **
+ *
+ * Note that we are going to manually crash a process
+ */
+ void noteIntentionalCrash(in unsigned long pid);
+
+ /**
+ * ** Test-only Method **
+ *
+ * Allowing to stop Utility Process from JS code.
+ * Default behavior is to stop any utility process.
+ */
+ void stopProcess([optional] in string utilityActorName);
+
+ /**
+ * ** Test-only Method **
+ *
+ * Sending Telemetry probes
+ */
+ void testTelemetryProbes();
+};