summaryrefslogtreecommitdiffstats
path: root/ipc/glue
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /ipc/glue
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ipc/glue')
-rw-r--r--ipc/glue/BackgroundChild.h92
-rw-r--r--ipc/glue/BackgroundChildImpl.cpp708
-rw-r--r--ipc/glue/BackgroundChildImpl.h288
-rw-r--r--ipc/glue/BackgroundImpl.cpp1739
-rw-r--r--ipc/glue/BackgroundParent.h120
-rw-r--r--ipc/glue/BackgroundParentImpl.cpp1411
-rw-r--r--ipc/glue/BackgroundParentImpl.h411
-rw-r--r--ipc/glue/BackgroundUtils.cpp1018
-rw-r--r--ipc/glue/BackgroundUtils.h187
-rw-r--r--ipc/glue/BrowserProcessSubThread.cpp73
-rw-r--r--ipc/glue/BrowserProcessSubThread.h65
-rw-r--r--ipc/glue/ByteBuf.h71
-rw-r--r--ipc/glue/ByteBufUtils.h52
-rw-r--r--ipc/glue/CrashReporterClient.cpp47
-rw-r--r--ipc/glue/CrashReporterClient.h49
-rw-r--r--ipc/glue/CrashReporterHelper.h78
-rw-r--r--ipc/glue/CrashReporterHost.cpp241
-rw-r--r--ipc/glue/CrashReporterHost.h131
-rw-r--r--ipc/glue/CrossProcessMutex.h115
-rw-r--r--ipc/glue/CrossProcessMutex_posix.cpp137
-rw-r--r--ipc/glue/CrossProcessMutex_unimplemented.cpp47
-rw-r--r--ipc/glue/CrossProcessMutex_windows.cpp69
-rw-r--r--ipc/glue/CrossProcessSemaphore.h115
-rw-r--r--ipc/glue/CrossProcessSemaphore_posix.cpp161
-rw-r--r--ipc/glue/CrossProcessSemaphore_unimplemented.cpp65
-rw-r--r--ipc/glue/CrossProcessSemaphore_windows.cpp83
-rw-r--r--ipc/glue/Endpoint.h239
-rw-r--r--ipc/glue/EnumSerializer.h167
-rw-r--r--ipc/glue/EnvironmentMap.h62
-rw-r--r--ipc/glue/FileDescriptor.cpp163
-rw-r--r--ipc/glue/FileDescriptor.h102
-rw-r--r--ipc/glue/FileDescriptorSetChild.cpp31
-rw-r--r--ipc/glue/FileDescriptorSetChild.h56
-rw-r--r--ipc/glue/FileDescriptorSetParent.cpp33
-rw-r--r--ipc/glue/FileDescriptorSetParent.h58
-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.cpp304
-rw-r--r--ipc/glue/ForkServer.h44
-rw-r--r--ipc/glue/ForkServiceChild.cpp182
-rw-r--r--ipc/glue/ForkServiceChild.h103
-rw-r--r--ipc/glue/GeckoChildProcessHost.cpp1814
-rw-r--r--ipc/glue/GeckoChildProcessHost.h299
-rw-r--r--ipc/glue/IOThreadChild.h47
-rw-r--r--ipc/glue/IPCCore.h22
-rw-r--r--ipc/glue/IPCMessageUtils.cpp24
-rw-r--r--ipc/glue/IPCMessageUtils.h258
-rw-r--r--ipc/glue/IPCMessageUtilsSpecializations.h938
-rw-r--r--ipc/glue/IPCStream.ipdlh24
-rw-r--r--ipc/glue/IPCStreamAlloc.h23
-rw-r--r--ipc/glue/IPCStreamChild.cpp157
-rw-r--r--ipc/glue/IPCStreamDestination.cpp400
-rw-r--r--ipc/glue/IPCStreamDestination.h95
-rw-r--r--ipc/glue/IPCStreamParent.cpp158
-rw-r--r--ipc/glue/IPCStreamSource.cpp277
-rw-r--r--ipc/glue/IPCStreamSource.h124
-rw-r--r--ipc/glue/IPCStreamUtils.cpp560
-rw-r--r--ipc/glue/IPCStreamUtils.h218
-rw-r--r--ipc/glue/IPCTypes.h20
-rw-r--r--ipc/glue/IPDLParamTraits.h444
-rw-r--r--ipc/glue/IdleSchedulerChild.cpp93
-rw-r--r--ipc/glue/IdleSchedulerChild.h58
-rw-r--r--ipc/glue/IdleSchedulerParent.cpp301
-rw-r--r--ipc/glue/IdleSchedulerParent.h114
-rw-r--r--ipc/glue/InputStreamParams.ipdlh125
-rw-r--r--ipc/glue/InputStreamUtils.cpp400
-rw-r--r--ipc/glue/InputStreamUtils.h97
-rw-r--r--ipc/glue/LibrarySandboxPreload.cpp82
-rw-r--r--ipc/glue/LibrarySandboxPreload.h18
-rw-r--r--ipc/glue/MessageChannel.cpp2951
-rw-r--r--ipc/glue/MessageChannel.h956
-rw-r--r--ipc/glue/MessageLink.cpp386
-rw-r--r--ipc/glue/MessageLink.h129
-rw-r--r--ipc/glue/MessagePump.cpp449
-rw-r--r--ipc/glue/MessagePump.h174
-rw-r--r--ipc/glue/MiniTransceiver.cpp241
-rw-r--r--ipc/glue/MiniTransceiver.h117
-rw-r--r--ipc/glue/Neutering.h70
-rw-r--r--ipc/glue/PBackground.ipdl289
-rw-r--r--ipc/glue/PBackgroundSharedTypes.ipdlh73
-rw-r--r--ipc/glue/PBackgroundTest.ipdl20
-rw-r--r--ipc/glue/PChildToParentStream.ipdl47
-rw-r--r--ipc/glue/PFileDescriptorSet.ipdl23
-rw-r--r--ipc/glue/PIdleScheduler.ipdl61
-rw-r--r--ipc/glue/PParentToChildStream.ipdl47
-rw-r--r--ipc/glue/ProcessChild.cpp48
-rw-r--r--ipc/glue/ProcessChild.h62
-rw-r--r--ipc/glue/ProcessUtils.h80
-rw-r--r--ipc/glue/ProcessUtils_bsd.cpp27
-rw-r--r--ipc/glue/ProcessUtils_common.cpp210
-rw-r--r--ipc/glue/ProcessUtils_linux.cpp22
-rw-r--r--ipc/glue/ProcessUtils_mac.mm19
-rw-r--r--ipc/glue/ProcessUtils_none.cpp15
-rw-r--r--ipc/glue/ProtocolMessageUtils.h147
-rw-r--r--ipc/glue/ProtocolTypes.ipdlh21
-rw-r--r--ipc/glue/ProtocolUtils.cpp909
-rw-r--r--ipc/glue/ProtocolUtils.h728
-rw-r--r--ipc/glue/ProtocolUtilsFwd.h16
-rw-r--r--ipc/glue/ScopedXREEmbed.cpp93
-rw-r--r--ipc/glue/ScopedXREEmbed.h34
-rw-r--r--ipc/glue/SerializedStructuredCloneBuffer.h130
-rw-r--r--ipc/glue/SharedMemory.cpp84
-rw-r--r--ipc/glue/SharedMemory.h146
-rw-r--r--ipc/glue/SharedMemoryBasic.h18
-rw-r--r--ipc/glue/SharedMemoryBasic_android.cpp135
-rw-r--r--ipc/glue/SharedMemoryBasic_android.h76
-rw-r--r--ipc/glue/SharedMemoryBasic_chromium.h92
-rw-r--r--ipc/glue/SharedMemoryBasic_mach.h111
-rw-r--r--ipc/glue/SharedMemoryBasic_mach.mm676
-rw-r--r--ipc/glue/SharedMemory_posix.cpp55
-rw-r--r--ipc/glue/SharedMemory_windows.cpp41
-rw-r--r--ipc/glue/Shmem.cpp465
-rw-r--r--ipc/glue/Shmem.h210
-rw-r--r--ipc/glue/ShmemMessageUtils.h33
-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/Transport.h43
-rw-r--r--ipc/glue/TransportSecurityInfoUtils.cpp79
-rw-r--r--ipc/glue/TransportSecurityInfoUtils.h32
-rw-r--r--ipc/glue/Transport_posix.cpp83
-rw-r--r--ipc/glue/Transport_posix.h38
-rw-r--r--ipc/glue/Transport_win.cpp115
-rw-r--r--ipc/glue/Transport_win.h109
-rw-r--r--ipc/glue/URIParams.ipdlh116
-rw-r--r--ipc/glue/URIUtils.cpp147
-rw-r--r--ipc/glue/URIUtils.h48
-rw-r--r--ipc/glue/WindowsMessageLoop.cpp1408
-rw-r--r--ipc/glue/WindowsMessageLoop.h134
-rw-r--r--ipc/glue/components.conf22
-rw-r--r--ipc/glue/moz.build267
-rw-r--r--ipc/glue/nsIIPCSerializableInputStream.h125
134 files changed, 30378 insertions, 0 deletions
diff --git a/ipc/glue/BackgroundChild.h b/ipc/glue/BackgroundChild.h
new file mode 100644
index 0000000000..c1206ece22
--- /dev/null
+++ b/ipc/glue/BackgroundChild.h
@@ -0,0 +1,92 @@
+/* -*- 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"
+#include "mozilla/ipc/Transport.h"
+
+class nsIEventTarget;
+
+namespace mozilla {
+namespace dom {
+
+class BlobImpl;
+class ContentChild;
+class ContentParent;
+
+} // namespace dom
+
+namespace net {
+
+class SocketProcessImpl;
+class SocketProcessChild;
+
+} // namespace net
+
+namespace ipc {
+
+class PBackgroundChild;
+
+// 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.
+class BackgroundChild final {
+ friend class mozilla::dom::ContentChild;
+ friend class mozilla::dom::ContentParent;
+ friend class mozilla::net::SocketProcessImpl;
+ friend class mozilla::net::SocketProcessChild;
+
+ typedef mozilla::ipc::Transport Transport;
+
+ public:
+ // See above.
+ static PBackgroundChild* GetForCurrentThread();
+
+ // See above.
+ static PBackgroundChild* GetOrCreateForCurrentThread(
+ nsIEventTarget* aMainEventTarget = nullptr);
+
+ // See above.
+ static void CloseForCurrentThread();
+
+ // See above.
+ static PBackgroundChild* GetOrCreateSocketActorForCurrentThread(
+ nsIEventTarget* aMainEventTarget = nullptr);
+
+ // See above.
+ static PBackgroundChild* GetOrCreateForSocketParentBridgeForCurrentThread(
+ nsIEventTarget* aMainEventTarget = nullptr);
+
+ private:
+ // Only called by ContentChild or ContentParent.
+ 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..e10c90b530
--- /dev/null
+++ b/ipc/glue/BackgroundChildImpl.cpp
@@ -0,0 +1,708 @@
+/* -*- 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 "ActorsChild.h" // IndexedDB
+#include "BroadcastChannelChild.h"
+#include "FileDescriptorSetChild.h"
+#ifdef MOZ_WEBRTC
+# include "CamerasChild.h"
+#endif
+#include "mozilla/media/MediaChild.h"
+#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/FileSystemTaskBase.h"
+#include "mozilla/dom/PMediaTransportChild.h"
+#include "mozilla/dom/TemporaryIPCBlobChild.h"
+#include "mozilla/dom/cache/ActorUtils.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.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/GamepadEventChannelChild.h"
+#include "mozilla/dom/GamepadTestChannelChild.h"
+#include "mozilla/dom/LocalStorage.h"
+#include "mozilla/dom/MessagePortChild.h"
+#include "mozilla/dom/ServiceWorkerActors.h"
+#include "mozilla/dom/ServiceWorkerContainerChild.h"
+#include "mozilla/dom/ServiceWorkerManagerChild.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/VsyncChild.h"
+#include "mozilla/ipc/IPCStreamAlloc.h"
+#include "mozilla/ipc/PBackgroundTestChild.h"
+#include "mozilla/ipc/PChildToParentStreamChild.h"
+#include "mozilla/ipc/PParentToChildStreamChild.h"
+#include "mozilla/net/HttpBackgroundChannelChild.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 "mozilla/RemoteLazyInputStreamChild.h"
+#include "nsID.h"
+#include "nsTraceRefcnt.h"
+
+namespace {
+
+class TestChild final : public mozilla::ipc::PBackgroundTestChild {
+ friend class mozilla::ipc::BackgroundChildImpl;
+
+ nsCString mTestArg;
+
+ explicit TestChild(const nsCString& aTestArg) : mTestArg(aTestArg) {
+ MOZ_COUNT_CTOR(TestChild);
+ }
+
+ protected:
+ ~TestChild() override { MOZ_COUNT_DTOR(TestChild); }
+
+ public:
+ mozilla::ipc::IPCResult Recv__delete__(const nsCString& aTestArg) override;
+};
+
+} // namespace
+
+namespace mozilla::ipc {
+
+using mozilla::dom::UDPSocketChild;
+using mozilla::net::PUDPSocketChild;
+
+using mozilla::dom::PServiceWorkerChild;
+using mozilla::dom::PServiceWorkerContainerChild;
+using mozilla::dom::PServiceWorkerRegistrationChild;
+using mozilla::dom::StorageDBChild;
+using mozilla::dom::cache::PCacheChild;
+using mozilla::dom::cache::PCacheStorageChild;
+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 nsCString& 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 nsCString& 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 nsString& 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;
+}
+
+dom::PRemoteWorkerChild* BackgroundChildImpl::AllocPRemoteWorkerChild(
+ const RemoteWorkerData& aData) {
+ RefPtr<dom::RemoteWorkerChild> agent = new dom::RemoteWorkerChild(aData);
+ return agent.forget().take();
+}
+
+IPCResult BackgroundChildImpl::RecvPRemoteWorkerConstructor(
+ PRemoteWorkerChild* aActor, const RemoteWorkerData& aData) {
+ dom::RemoteWorkerChild* actor = static_cast<dom::RemoteWorkerChild*>(aActor);
+ actor->ExecWorker(aData);
+ return IPC_OK();
+}
+
+bool BackgroundChildImpl::DeallocPRemoteWorkerChild(
+ dom::PRemoteWorkerChild* aActor) {
+ RefPtr<dom::RemoteWorkerChild> actor =
+ dont_AddRef(static_cast<dom::RemoteWorkerChild*>(aActor));
+ return true;
+}
+
+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::PRemoteWorkerServiceChild*
+BackgroundChildImpl::AllocPRemoteWorkerServiceChild() {
+ RefPtr<dom::RemoteWorkerServiceChild> agent =
+ new dom::RemoteWorkerServiceChild();
+ return agent.forget().take();
+}
+
+bool BackgroundChildImpl::DeallocPRemoteWorkerServiceChild(
+ dom::PRemoteWorkerServiceChild* aActor) {
+ RefPtr<dom::RemoteWorkerServiceChild> actor =
+ dont_AddRef(static_cast<dom::RemoteWorkerServiceChild*>(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 nsString& aFullPath, const nsString& aType, const nsString& 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;
+}
+
+already_AddRefed<PRemoteLazyInputStreamChild>
+BackgroundChildImpl::AllocPRemoteLazyInputStreamChild(const nsID& aID,
+ const uint64_t& aSize) {
+ RefPtr<RemoteLazyInputStreamChild> actor =
+ new RemoteLazyInputStreamChild(aID, aSize);
+ return actor.forget();
+}
+
+PFileDescriptorSetChild* BackgroundChildImpl::AllocPFileDescriptorSetChild(
+ const FileDescriptor& aFileDescriptor) {
+ return new FileDescriptorSetChild(aFileDescriptor);
+}
+
+bool BackgroundChildImpl::DeallocPFileDescriptorSetChild(
+ PFileDescriptorSetChild* aActor) {
+ MOZ_ASSERT(aActor);
+
+ delete static_cast<FileDescriptorSetChild*>(aActor);
+ return true;
+}
+
+dom::PVsyncChild* BackgroundChildImpl::AllocPVsyncChild() {
+ RefPtr<dom::VsyncChild> actor = new dom::VsyncChild();
+ // There still has one ref-count after return, and it will be released in
+ // DeallocPVsyncChild().
+ return actor.forget().take();
+}
+
+bool BackgroundChildImpl::DeallocPVsyncChild(PVsyncChild* aActor) {
+ MOZ_ASSERT(aActor);
+
+ // This actor already has one ref-count. Please check AllocPVsyncChild().
+ RefPtr<dom::VsyncChild> actor =
+ dont_AddRef(static_cast<dom::VsyncChild*>(aActor));
+ return true;
+}
+
+PUDPSocketChild* BackgroundChildImpl::AllocPUDPSocketChild(
+ const Maybe<PrincipalInfo>& aPrincipalInfo, const nsCString& 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 nsCString& aOrigin,
+ const nsString& 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
+// -----------------------------------------------------------------------------
+
+PCacheStorageChild* BackgroundChildImpl::AllocPCacheStorageChild(
+ const Namespace& aNamespace, const PrincipalInfo& aPrincipalInfo) {
+ MOZ_CRASH("CacheStorageChild actor must be provided to PBackground manager");
+ return nullptr;
+}
+
+bool BackgroundChildImpl::DeallocPCacheStorageChild(
+ PCacheStorageChild* aActor) {
+ dom::cache::DeallocPCacheStorageChild(aActor);
+ return true;
+}
+
+PCacheChild* BackgroundChildImpl::AllocPCacheChild() {
+ return dom::cache::AllocPCacheChild();
+}
+
+bool BackgroundChildImpl::DeallocPCacheChild(PCacheChild* aActor) {
+ dom::cache::DeallocPCacheChild(aActor);
+ return true;
+}
+
+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;
+}
+
+PChildToParentStreamChild*
+BackgroundChildImpl::AllocPChildToParentStreamChild() {
+ MOZ_CRASH("PChildToParentStreamChild actors should be manually constructed!");
+}
+
+bool BackgroundChildImpl::DeallocPChildToParentStreamChild(
+ PChildToParentStreamChild* aActor) {
+ delete aActor;
+ return true;
+}
+
+PParentToChildStreamChild*
+BackgroundChildImpl::AllocPParentToChildStreamChild() {
+ return mozilla::ipc::AllocPParentToChildStreamChild();
+}
+
+bool BackgroundChildImpl::DeallocPParentToChildStreamChild(
+ PParentToChildStreamChild* aActor) {
+ delete aActor;
+ 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;
+}
+
+// -----------------------------------------------------------------------------
+// WebMIDI API
+// -----------------------------------------------------------------------------
+
+PMIDIPortChild* BackgroundChildImpl::AllocPMIDIPortChild(
+ const MIDIPortInfo& aPortInfo, const bool& aSysexEnabled) {
+ MOZ_CRASH("Should be created manually");
+ return nullptr;
+}
+
+bool BackgroundChildImpl::DeallocPMIDIPortChild(PMIDIPortChild* aActor) {
+ MOZ_ASSERT(aActor);
+ // The reference is increased in dom/midi/MIDIPort.cpp. We should
+ // decrease it after IPC.
+ RefPtr<dom::MIDIPortChild> child =
+ dont_AddRef(static_cast<dom::MIDIPortChild*>(aActor));
+ child->Teardown();
+ return true;
+}
+
+PMIDIManagerChild* BackgroundChildImpl::AllocPMIDIManagerChild() {
+ MOZ_CRASH("Should be created manually");
+ return nullptr;
+}
+
+bool BackgroundChildImpl::DeallocPMIDIManagerChild(PMIDIManagerChild* aActor) {
+ MOZ_ASSERT(aActor);
+ // The reference is increased in dom/midi/MIDIAccessManager.cpp. We should
+ // decrease it after IPC.
+ RefPtr<dom::MIDIManagerChild> child =
+ dont_AddRef(static_cast<dom::MIDIManagerChild*>(aActor));
+ return true;
+}
+
+mozilla::dom::PClientManagerChild*
+BackgroundChildImpl::AllocPClientManagerChild() {
+ return mozilla::dom::AllocClientManagerChild();
+}
+
+bool BackgroundChildImpl::DeallocPClientManagerChild(
+ mozilla::dom::PClientManagerChild* aActor) {
+ return mozilla::dom::DeallocClientManagerChild(aActor);
+}
+
+#ifdef EARLY_BETA_OR_EARLIER
+void BackgroundChildImpl::OnChannelReceivedMessage(const Message& aMsg) {
+ if (aMsg.type() == dom::PVsync::MessageType::Msg_Notify__ID) {
+ // Not really necessary to look at the message payload, it will be
+ // <0.5ms away from TimeStamp::Now()
+ SchedulerGroup::MarkVsyncReceived();
+ }
+}
+#endif
+
+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 nsString& 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;
+}
+
+PChildToParentStreamChild*
+BackgroundChildImpl::SendPChildToParentStreamConstructor(
+ PChildToParentStreamChild* aActor) {
+ return PBackgroundChild::SendPChildToParentStreamConstructor(aActor);
+}
+
+PFileDescriptorSetChild* BackgroundChildImpl::SendPFileDescriptorSetConstructor(
+ const FileDescriptor& aFD) {
+ return PBackgroundChild::SendPFileDescriptorSetConstructor(aFD);
+}
+
+} // namespace mozilla::ipc
+
+mozilla::ipc::IPCResult TestChild::Recv__delete__(const nsCString& 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..22e740e55c
--- /dev/null
+++ b/ipc/glue/BackgroundChildImpl.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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ipc_backgroundchildimpl_h__
+#define mozilla_ipc_backgroundchildimpl_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/UniquePtr.h"
+#include "nsRefPtrHashtable.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 ChildToParentStreamActorManager {
+ 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();
+
+ PChildToParentStreamChild* SendPChildToParentStreamConstructor(
+ PChildToParentStreamChild* aActor) override;
+ PFileDescriptorSetChild* SendPFileDescriptorSetConstructor(
+ const FileDescriptor& aFD) override;
+
+ protected:
+ BackgroundChildImpl();
+ virtual ~BackgroundChildImpl();
+
+ virtual void ProcessingError(Result aCode, const char* aReason) override;
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual PBackgroundTestChild* AllocPBackgroundTestChild(
+ const nsCString& 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 nsCString& aOriginKey,
+ const uint32_t& aPrivateBrowsingId) override;
+
+ virtual bool DeallocPBackgroundLocalStorageCacheChild(
+ PBackgroundLocalStorageCacheChild* aActor) override;
+
+ virtual PBackgroundStorageChild* AllocPBackgroundStorageChild(
+ const nsString& aProfilePath,
+ const uint32_t& aPrivateBrowsingId) override;
+
+ virtual bool DeallocPBackgroundStorageChild(
+ PBackgroundStorageChild* aActor) override;
+
+ virtual already_AddRefed<PRemoteLazyInputStreamChild>
+ AllocPRemoteLazyInputStreamChild(const nsID& aID,
+ const uint64_t& aSize) override;
+
+ virtual PTemporaryIPCBlobChild* AllocPTemporaryIPCBlobChild() override;
+
+ virtual bool DeallocPTemporaryIPCBlobChild(
+ PTemporaryIPCBlobChild* aActor) override;
+
+ virtual PFileCreatorChild* AllocPFileCreatorChild(
+ const nsString& aFullPath, const nsString& aType, const nsString& aName,
+ const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck,
+ const bool& aIsFromNsIFile) override;
+
+ virtual bool DeallocPFileCreatorChild(PFileCreatorChild* aActor) override;
+
+ virtual mozilla::dom::PRemoteWorkerChild* AllocPRemoteWorkerChild(
+ const RemoteWorkerData& aData) override;
+
+ virtual mozilla::ipc::IPCResult RecvPRemoteWorkerConstructor(
+ PRemoteWorkerChild* aActor, const RemoteWorkerData& aData) override;
+
+ virtual bool DeallocPRemoteWorkerChild(
+ mozilla::dom::PRemoteWorkerChild* aActor) override;
+
+ virtual mozilla::dom::PRemoteWorkerControllerChild*
+ AllocPRemoteWorkerControllerChild(
+ const mozilla::dom::RemoteWorkerData& aRemoteWorkerData) override;
+
+ virtual bool DeallocPRemoteWorkerControllerChild(
+ mozilla::dom::PRemoteWorkerControllerChild* aActor) override;
+
+ virtual mozilla::dom::PRemoteWorkerServiceChild*
+ AllocPRemoteWorkerServiceChild() override;
+
+ virtual bool DeallocPRemoteWorkerServiceChild(
+ mozilla::dom::PRemoteWorkerServiceChild* 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 PFileDescriptorSetChild* AllocPFileDescriptorSetChild(
+ const FileDescriptor& aFileDescriptor) override;
+
+ virtual bool DeallocPFileDescriptorSetChild(
+ PFileDescriptorSetChild* aActor) override;
+
+ virtual PCamerasChild* AllocPCamerasChild() override;
+
+ virtual bool DeallocPCamerasChild(PCamerasChild* aActor) override;
+
+ virtual PVsyncChild* AllocPVsyncChild() override;
+
+ virtual bool DeallocPVsyncChild(PVsyncChild* aActor) override;
+
+ virtual PUDPSocketChild* AllocPUDPSocketChild(
+ const Maybe<PrincipalInfo>& aPrincipalInfo,
+ const nsCString& aFilter) override;
+ virtual bool DeallocPUDPSocketChild(PUDPSocketChild* aActor) override;
+
+ virtual PBroadcastChannelChild* AllocPBroadcastChannelChild(
+ const PrincipalInfo& aPrincipalInfo, const nsCString& aOrigin,
+ const nsString& aChannel) override;
+
+ virtual bool DeallocPBroadcastChannelChild(
+ PBroadcastChannelChild* aActor) override;
+
+ virtual PServiceWorkerManagerChild* AllocPServiceWorkerManagerChild()
+ override;
+
+ virtual bool DeallocPServiceWorkerManagerChild(
+ PServiceWorkerManagerChild* aActor) override;
+
+ virtual dom::cache::PCacheStorageChild* AllocPCacheStorageChild(
+ const dom::cache::Namespace& aNamespace,
+ const PrincipalInfo& aPrincipalInfo) override;
+
+ virtual bool DeallocPCacheStorageChild(
+ dom::cache::PCacheStorageChild* aActor) override;
+
+ virtual dom::cache::PCacheChild* AllocPCacheChild() override;
+
+ virtual bool DeallocPCacheChild(dom::cache::PCacheChild* aActor) 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 PChildToParentStreamChild* AllocPChildToParentStreamChild() override;
+
+ virtual bool DeallocPChildToParentStreamChild(
+ PChildToParentStreamChild* aActor) override;
+
+ virtual PParentToChildStreamChild* AllocPParentToChildStreamChild() override;
+
+ virtual bool DeallocPParentToChildStreamChild(
+ PParentToChildStreamChild* aActor) override;
+
+ virtual PQuotaChild* AllocPQuotaChild() override;
+
+ virtual bool DeallocPQuotaChild(PQuotaChild* aActor) override;
+
+ virtual PClientManagerChild* AllocPClientManagerChild() override;
+
+ virtual bool DeallocPClientManagerChild(PClientManagerChild* aActor) override;
+
+#ifdef EARLY_BETA_OR_EARLIER
+ virtual void OnChannelReceivedMessage(const Message& aMsg) override;
+#endif
+
+ virtual PWebAuthnTransactionChild* AllocPWebAuthnTransactionChild() override;
+
+ virtual bool DeallocPWebAuthnTransactionChild(
+ PWebAuthnTransactionChild* aActor) override;
+
+ virtual PMIDIPortChild* AllocPMIDIPortChild(
+ const MIDIPortInfo& aPortInfo, const bool& aSysexEnabled) override;
+ virtual bool DeallocPMIDIPortChild(PMIDIPortChild*) override;
+
+ virtual PMIDIManagerChild* AllocPMIDIManagerChild() override;
+ virtual bool DeallocPMIDIManagerChild(PMIDIManagerChild*) override;
+
+ already_AddRefed<PServiceWorkerChild> AllocPServiceWorkerChild(
+ const IPCServiceWorkerDescriptor&);
+
+ already_AddRefed<PServiceWorkerContainerChild>
+ AllocPServiceWorkerContainerChild();
+
+ already_AddRefed<PServiceWorkerRegistrationChild>
+ AllocPServiceWorkerRegistrationChild(
+ const IPCServiceWorkerRegistrationDescriptor&);
+
+ virtual PEndpointForReportChild* AllocPEndpointForReportChild(
+ const nsString& 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..6d142e5350
--- /dev/null
+++ b/ipc/glue/BackgroundImpl.cpp
@@ -0,0 +1,1739 @@
+/* -*- 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 "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/Endpoint.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 AssertIsInMainProcess() { MOZ_ASSERT(XRE_IsParentProcess()); }
+
+void AssertIsInMainOrSocketProcess() {
+ MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess());
+}
+
+void AssertIsOnMainThread() { THREADSAFETY_ASSERT(NS_IsMainThread()); }
+
+void AssertIsNotOnMainThread() { THREADSAFETY_ASSERT(!NS_IsMainThread()); }
+
+// -----------------------------------------------------------------------------
+// ParentImpl Declaration
+// -----------------------------------------------------------------------------
+
+class ParentImpl final : public BackgroundParentImpl {
+ friend class mozilla::ipc::BackgroundParent;
+
+ private:
+ class ShutdownObserver;
+ class CreateActorHelper;
+
+ struct MOZ_STACK_CLASS TimerCallbackClosure {
+ nsIThread* mThread;
+ nsTArray<ParentImpl*>* mLiveActors;
+
+ TimerCallbackClosure(nsIThread* aThread, nsTArray<ParentImpl*>* 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<ParentImpl*>* 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;
+
+ // This is only modified on the main thread. It maintains a count of live
+ // actors so that the background thread can be shut down when it is no longer
+ // needed.
+ static 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;
+
+ // Only touched on the main thread, null if this is a same-process actor.
+ RefPtr<ContentParent> mContent;
+
+ // Set when the actor is opened successfully and used to handle shutdown
+ // hangs. Only touched on the background thread.
+ nsTArray<ParentImpl*>* 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 already_AddRefed<ChildImpl> CreateActorForSameProcess(
+ nsIEventTarget* aMainEventTarget);
+
+ static bool IsOnBackgroundThread() {
+ return PR_GetCurrentThread() == sBackgroundPRThread;
+ }
+
+ static void AssertIsOnBackgroundThread() {
+ THREADSAFETY_ASSERT(IsOnBackgroundThread());
+ }
+
+ // `ParentImpl` instances are created and 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 already_AddRefed<ContentParent> GetContentParent(
+ PBackgroundParent* aBackgroundActor);
+
+ // Forwarded from BackgroundParent.
+ static intptr_t GetRawContentParentForComparison(
+ PBackgroundParent* aBackgroundActor);
+
+ // Forwarded from BackgroundParent.
+ static uint64_t GetChildID(PBackgroundParent* aBackgroundActor);
+
+ // Forwarded from BackgroundParent.
+ static bool GetLiveActorArray(PBackgroundParent* aBackgroundActor,
+ nsTArray<PBackgroundParent*>& aLiveActorArray);
+
+ // Forwarded from BackgroundParent.
+ static bool Alloc(ContentParent* aContent,
+ Endpoint<PBackgroundParent>&& aEndpoint);
+
+ static bool CreateBackgroundThread();
+
+ static void ShutdownBackgroundThread();
+
+ static void ShutdownTimerCallback(nsITimer* aTimer, void* aClosure);
+
+ // For same-process actors.
+ ParentImpl()
+ : mLiveActorArray(nullptr),
+ mIsOtherProcessActor(false),
+ mActorDestroyed(false) {
+ AssertIsInMainProcess();
+ AssertIsOnMainThread();
+ }
+
+ // For other-process actors.
+ // NOTE: ParentImpl could be used in 3 cases below.
+ // 1. Between parent process and content process.
+ // 2. Between socket process and content process.
+ // 3. Between parent process and socket process.
+ // |mContent| should be not null for case 1. For case 2 and 3, it's null.
+ explicit ParentImpl(ContentParent* aContent)
+ : mContent(aContent),
+ mLiveActorArray(nullptr),
+ mIsOtherProcessActor(true),
+ mActorDestroyed(false) {
+ MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess());
+ AssertIsOnMainThread();
+ }
+
+ ~ParentImpl() {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mContent);
+ }
+
+ void MainThreadActorDestroy();
+
+ void SetLiveActorArray(nsTArray<ParentImpl*>* 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;
+
+ typedef base::ProcessId ProcessId;
+ typedef mozilla::ipc::Transport Transport;
+
+ class ShutdownObserver;
+
+ public:
+ class SendInitBackgroundRunnable;
+
+ struct ThreadLocalInfo {
+ ThreadLocalInfo()
+#ifdef DEBUG
+ : mClosed(false)
+#endif
+ {
+ }
+
+ RefPtr<ChildImpl> mActor;
+ RefPtr<SendInitBackgroundRunnable> mSendInitBackgroundRunnable;
+ 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**);
+
+ constexpr explicit ThreadInfoWrapper(ActorCreateFunc aFunc)
+ : mThreadLocalIndex(kBadThreadLocalIndex),
+ mMainThreadInfo(nullptr),
+ mCreateActorFunc(aFunc) {}
+
+ 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;
+ }
+
+ 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;
+ }
+ }
+
+ 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(
+ nsIEventTarget* aMainEventTarget) {
+ MOZ_ASSERT_IF(NS_IsMainThread(), !aMainEventTarget);
+
+ MOZ_ASSERT(mThreadLocalIndex != kBadThreadLocalIndex,
+ "BackgroundChild::Startup() was never called!");
+
+ 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();
+ }
+
+ PBackgroundChild* bgChild =
+ GetFromThreadInfo(aMainEventTarget, threadLocalInfo);
+ if (bgChild) {
+ return bgChild;
+ }
+
+ RefPtr<ChildImpl> actor;
+ mCreateActorFunc(threadLocalInfo, mThreadLocalIndex, aMainEventTarget,
+ getter_AddRefs(actor));
+ return actor;
+ }
+
+ private:
+ // 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;
+
+ // 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;
+ ActorCreateFunc mCreateActorFunc;
+ };
+
+ // 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();
+
+ // Helper function for getting PBackgroundChild from thread info.
+ static PBackgroundChild* GetFromThreadInfo(nsIEventTarget* aMainEventTarget,
+ ThreadLocalInfo* aThreadLocalInfo);
+
+ // Forwarded from BackgroundChild.
+ static PBackgroundChild* GetOrCreateForCurrentThread(
+ nsIEventTarget* aMainEventTarget);
+
+ // Forwarded from BackgroundChild.
+ static PBackgroundChild* GetOrCreateSocketActorForCurrentThread(
+ nsIEventTarget* aMainEventTarget);
+
+ // Forwarded from BackgroundChild.
+ static PBackgroundChild* GetOrCreateForSocketParentBridgeForCurrentThread(
+ nsIEventTarget* aMainEventTarget);
+
+ static void CloseForCurrentThread();
+
+ // Forwarded from BackgroundChildImpl.
+ static BackgroundChildImpl::ThreadLocal* GetThreadLocalForCurrentThread();
+
+ 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(); }
+};
+
+class ParentImpl::CreateActorHelper final : public Runnable {
+ mozilla::Monitor mMonitor;
+ RefPtr<ParentImpl> mParentActor;
+ nsCOMPtr<nsIThread> mThread;
+ nsresult mMainThreadResultCode;
+ bool mWaiting;
+
+ public:
+ explicit CreateActorHelper()
+ : Runnable("Background::ParentImpl::CreateActorHelper"),
+ mMonitor("CreateActorHelper::mMonitor"),
+ mMainThreadResultCode(NS_OK),
+ mWaiting(true) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsNotOnMainThread();
+ }
+
+ nsresult BlockAndGetResults(nsIEventTarget* aMainEventTarget,
+ RefPtr<ParentImpl>& aParentActor,
+ nsCOMPtr<nsIThread>& aThread);
+
+ private:
+ ~CreateActorHelper() { AssertIsInMainOrSocketProcess(); }
+
+ nsresult RunOnMainThread();
+
+ NS_DECL_NSIRUNNABLE
+};
+
+// -----------------------------------------------------------------------------
+// ChildImpl Helper Declarations
+// -----------------------------------------------------------------------------
+
+class ChildImpl::ShutdownObserver final : public nsIObserver {
+ public:
+ ShutdownObserver() { AssertIsOnMainThread(); }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ private:
+ ~ShutdownObserver() { AssertIsOnMainThread(); }
+};
+
+class ChildImpl::SendInitBackgroundRunnable final : public DiscardableRunnable {
+ nsCOMPtr<nsISerialEventTarget> mOwningEventTarget;
+ RefPtr<StrongWorkerRef> mWorkerRef;
+ Endpoint<PBackgroundParent> mParent;
+ mozilla::Mutex mMutex;
+ bool mSentInitBackground;
+ std::function<void(Endpoint<PBackgroundParent>&& aParent)> mSendInitfunc;
+ unsigned int mThreadLocalIndex;
+
+ public:
+ static already_AddRefed<SendInitBackgroundRunnable> Create(
+ Endpoint<PBackgroundParent>&& aParent,
+ std::function<void(Endpoint<PBackgroundParent>&& aParent)>&& aFunc,
+ unsigned int aThreadLocalIndex);
+
+ void ClearEventTarget() {
+ mWorkerRef = nullptr;
+
+ mozilla::MutexAutoLock lock(mMutex);
+ mOwningEventTarget = nullptr;
+ }
+
+ private:
+ explicit SendInitBackgroundRunnable(
+ Endpoint<PBackgroundParent>&& aParent,
+ std::function<void(Endpoint<PBackgroundParent>&& aParent)>&& aFunc,
+ unsigned int aThreadLocalIndex)
+ : DiscardableRunnable(
+ "Background::ChildImpl::SendInitBackgroundRunnable"),
+ mOwningEventTarget(GetCurrentSerialEventTarget()),
+ mParent(std::move(aParent)),
+ mMutex("SendInitBackgroundRunnable::mMutex"),
+ mSentInitBackground(false),
+ mSendInitfunc(std::move(aFunc)),
+ mThreadLocalIndex(aThreadLocalIndex) {}
+
+ ~SendInitBackgroundRunnable() = default;
+
+ NS_DECL_NSIRUNNABLE
+};
+
+} // 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
+already_AddRefed<ContentParent> BackgroundParent::GetContentParent(
+ PBackgroundParent* aBackgroundActor) {
+ return ParentImpl::GetContentParent(aBackgroundActor);
+}
+
+// static
+intptr_t BackgroundParent::GetRawContentParentForComparison(
+ PBackgroundParent* aBackgroundActor) {
+ return ParentImpl::GetRawContentParentForComparison(aBackgroundActor);
+}
+
+// static
+uint64_t BackgroundParent::GetChildID(PBackgroundParent* aBackgroundActor) {
+ return ParentImpl::GetChildID(aBackgroundActor);
+}
+
+// static
+bool BackgroundParent::GetLiveActorArray(
+ PBackgroundParent* aBackgroundActor,
+ nsTArray<PBackgroundParent*>& aLiveActorArray) {
+ return ParentImpl::GetLiveActorArray(aBackgroundActor, aLiveActorArray);
+}
+
+// static
+bool BackgroundParent::Alloc(ContentParent* aContent,
+ Endpoint<PBackgroundParent>&& aEndpoint) {
+ return ParentImpl::Alloc(aContent, std::move(aEndpoint));
+}
+
+// -----------------------------------------------------------------------------
+// BackgroundChild Public Methods
+// -----------------------------------------------------------------------------
+
+// static
+void BackgroundChild::Startup() { ChildImpl::Startup(); }
+
+// static
+PBackgroundChild* BackgroundChild::GetForCurrentThread() {
+ return ChildImpl::GetForCurrentThread();
+}
+
+// static
+PBackgroundChild* BackgroundChild::GetOrCreateForCurrentThread(
+ nsIEventTarget* aMainEventTarget) {
+ return ChildImpl::GetOrCreateForCurrentThread(aMainEventTarget);
+}
+
+// static
+PBackgroundChild* BackgroundChild::GetOrCreateSocketActorForCurrentThread(
+ nsIEventTarget* aMainEventTarget) {
+ return ChildImpl::GetOrCreateSocketActorForCurrentThread(aMainEventTarget);
+}
+
+// static
+PBackgroundChild*
+BackgroundChild::GetOrCreateForSocketParentBridgeForCurrentThread(
+ nsIEventTarget* aMainEventTarget) {
+ return ChildImpl::GetOrCreateForSocketParentBridgeForCurrentThread(
+ aMainEventTarget);
+}
+
+// static
+void BackgroundChild::CloseForCurrentThread() {
+ ChildImpl::CloseForCurrentThread();
+}
+
+// -----------------------------------------------------------------------------
+// BackgroundChildImpl Public Methods
+// -----------------------------------------------------------------------------
+
+// static
+BackgroundChildImpl::ThreadLocal*
+BackgroundChildImpl::GetThreadLocalForCurrentThread() {
+ return ChildImpl::GetThreadLocalForCurrentThread();
+}
+
+// -----------------------------------------------------------------------------
+// ParentImpl Static Members
+// -----------------------------------------------------------------------------
+
+StaticRefPtr<nsIThread> ParentImpl::sBackgroundThread;
+
+nsTArray<ParentImpl*>* ParentImpl::sLiveActorsForBackgroundThread;
+
+StaticRefPtr<nsITimer> ParentImpl::sShutdownTimer;
+
+Atomic<PRThread*> ParentImpl::sBackgroundPRThread;
+
+uint64_t ParentImpl::sLiveActorCount = 0;
+
+bool ParentImpl::sShutdownObserverRegistered = false;
+
+bool ParentImpl::sShutdownHasStarted = false;
+
+// -----------------------------------------------------------------------------
+// ChildImpl Static Members
+// -----------------------------------------------------------------------------
+
+static void ParentContentActorCreateFunc(
+ ChildImpl::ThreadLocalInfo* aThreadLocalInfo,
+ unsigned int aThreadLocalIndex, nsIEventTarget* aMainEventTarget,
+ ChildImpl** aOutput) {
+ if (XRE_IsParentProcess()) {
+ RefPtr<ChildImpl> strongActor =
+ ParentImpl::CreateActorForSameProcess(aMainEventTarget);
+ if (NS_WARN_IF(!strongActor)) {
+ return;
+ }
+
+ aThreadLocalInfo->mActor = strongActor;
+ strongActor.forget(aOutput);
+ return;
+ }
+
+ RefPtr<ContentChild> content = ContentChild::GetSingleton();
+ MOZ_ASSERT(content);
+
+ if (content->IsShuttingDown()) {
+ // The transport for ContentChild is shut down and can't be used to open
+ // PBackground.
+ return;
+ }
+
+ Endpoint<PBackgroundParent> parent;
+ Endpoint<PBackgroundChild> child;
+ nsresult rv;
+ rv = PBackground::CreateEndpoints(content->OtherPid(),
+ base::GetCurrentProcId(), &parent, &child);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create top level actor!");
+ return;
+ }
+
+ RefPtr<ChildImpl::SendInitBackgroundRunnable> runnable;
+ if (!NS_IsMainThread()) {
+ runnable = ChildImpl::SendInitBackgroundRunnable::Create(
+ std::move(parent),
+ [](Endpoint<PBackgroundParent>&& aParent) {
+ RefPtr<ContentChild> content = ContentChild::GetSingleton();
+ MOZ_ASSERT(content);
+
+ if (!content->SendInitBackground(std::move(aParent))) {
+ NS_WARNING("Failed to create top level actor!");
+ }
+ },
+ aThreadLocalIndex);
+ if (!runnable) {
+ return;
+ }
+ }
+
+ RefPtr<ChildImpl> strongActor = new ChildImpl();
+
+ if (!child.Bind(strongActor)) {
+ CRASH_IN_CHILD_PROCESS("Failed to bind ChildImpl!");
+
+ return;
+ }
+
+ strongActor->SetActorAlive();
+
+ if (NS_IsMainThread()) {
+ if (!content->SendInitBackground(std::move(parent))) {
+ NS_WARNING("Failed to create top level actor!");
+ return;
+ }
+ } else {
+ if (aMainEventTarget) {
+ MOZ_ALWAYS_SUCCEEDS(
+ aMainEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL));
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+ }
+
+ aThreadLocalInfo->mSendInitBackgroundRunnable = runnable;
+ }
+
+ aThreadLocalInfo->mActor = strongActor;
+ strongActor.forget(aOutput);
+}
+
+ChildImpl::ThreadInfoWrapper ChildImpl::sParentAndContentProcessThreadInfo(
+ ParentContentActorCreateFunc);
+
+static void SocketContentActorCreateFunc(
+ ChildImpl::ThreadLocalInfo* aThreadLocalInfo,
+ unsigned int aThreadLocalIndex, nsIEventTarget* aMainEventTarget,
+ ChildImpl** aOutput) {
+ RefPtr<SocketProcessBridgeChild> bridgeChild =
+ SocketProcessBridgeChild::GetSingleton();
+
+ if (!bridgeChild || bridgeChild->IsShuttingDown()) {
+ // The transport for SocketProcessBridgeChild is shut down
+ // and can't be used to open PBackground.
+ return;
+ }
+
+ Endpoint<PBackgroundParent> parent;
+ Endpoint<PBackgroundChild> child;
+ nsresult rv;
+ rv = PBackground::CreateEndpoints(bridgeChild->SocketProcessPid(),
+ base::GetCurrentProcId(), &parent, &child);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create top level actor!");
+ return;
+ }
+
+ RefPtr<ChildImpl::SendInitBackgroundRunnable> runnable;
+ if (!NS_IsMainThread()) {
+ runnable = ChildImpl::SendInitBackgroundRunnable::Create(
+ std::move(parent),
+ [](Endpoint<PBackgroundParent>&& aParent) {
+ RefPtr<SocketProcessBridgeChild> bridgeChild =
+ SocketProcessBridgeChild::GetSingleton();
+
+ if (!bridgeChild->SendInitBackground(std::move(aParent))) {
+ NS_WARNING("Failed to create top level actor!");
+ }
+ },
+ aThreadLocalIndex);
+ if (!runnable) {
+ return;
+ }
+ }
+
+ RefPtr<ChildImpl> strongActor = new ChildImpl();
+
+ if (!child.Bind(strongActor)) {
+ CRASH_IN_CHILD_PROCESS("Failed to bind ChildImpl!");
+
+ return;
+ }
+
+ strongActor->SetActorAlive();
+
+ if (NS_IsMainThread()) {
+ if (!bridgeChild->SendInitBackground(std::move(parent))) {
+ NS_WARNING("Failed to create top level actor!");
+ // Need to close the IPC channel before ChildImpl getting deleted.
+ strongActor->Close();
+ strongActor->AssertActorDestroyed();
+ return;
+ }
+ } else {
+ if (aMainEventTarget) {
+ MOZ_ALWAYS_SUCCEEDS(
+ aMainEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL));
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+ }
+
+ aThreadLocalInfo->mSendInitBackgroundRunnable = runnable;
+ }
+
+ aThreadLocalInfo->mActor = strongActor;
+ strongActor.forget(aOutput);
+}
+
+ChildImpl::ThreadInfoWrapper ChildImpl::sSocketAndContentProcessThreadInfo(
+ SocketContentActorCreateFunc);
+
+static void SocketParentActorCreateFunc(
+ ChildImpl::ThreadLocalInfo* aThreadLocalInfo,
+ unsigned int aThreadLocalIndex, nsIEventTarget* aMainEventTarget,
+ ChildImpl** aOutput) {
+ SocketProcessChild* socketChild = SocketProcessChild::GetSingleton();
+
+ if (!socketChild || socketChild->IsShuttingDown()) {
+ return;
+ }
+
+ Endpoint<PBackgroundParent> parent;
+ Endpoint<PBackgroundChild> child;
+ nsresult rv;
+ rv = PBackground::CreateEndpoints(socketChild->OtherPid(),
+ base::GetCurrentProcId(), &parent, &child);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create top level actor!");
+ return;
+ }
+
+ RefPtr<ChildImpl::SendInitBackgroundRunnable> runnable;
+ if (!NS_IsMainThread()) {
+ runnable = ChildImpl::SendInitBackgroundRunnable::Create(
+ std::move(parent),
+ [](Endpoint<PBackgroundParent>&& aParent) {
+ SocketProcessChild* socketChild = SocketProcessChild::GetSingleton();
+ MOZ_ASSERT(socketChild);
+
+ if (!socketChild->SendInitBackground(std::move(aParent))) {
+ MOZ_CRASH("Failed to create top level actor!");
+ }
+ },
+ aThreadLocalIndex);
+ if (!runnable) {
+ return;
+ }
+ }
+
+ RefPtr<ChildImpl> strongActor = new ChildImpl();
+
+ if (!child.Bind(strongActor)) {
+ CRASH_IN_CHILD_PROCESS("Failed to bind ChildImpl!");
+ return;
+ }
+
+ strongActor->SetActorAlive();
+
+ if (NS_IsMainThread()) {
+ if (!socketChild->SendInitBackground(std::move(parent))) {
+ NS_WARNING("Failed to create top level actor!");
+ return;
+ }
+ } else {
+ if (aMainEventTarget) {
+ MOZ_ALWAYS_SUCCEEDS(
+ aMainEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL));
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+ }
+
+ aThreadLocalInfo->mSendInitBackgroundRunnable = runnable;
+ }
+
+ aThreadLocalInfo->mActor = strongActor;
+ strongActor.forget(aOutput);
+}
+
+ChildImpl::ThreadInfoWrapper ChildImpl::sSocketAndParentProcessThreadInfo(
+ SocketParentActorCreateFunc);
+
+bool ChildImpl::sShutdownHasStarted = false;
+
+// -----------------------------------------------------------------------------
+// ParentImpl Implementation
+// -----------------------------------------------------------------------------
+
+// static
+bool ParentImpl::IsOtherProcessActor(PBackgroundParent* aBackgroundActor) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aBackgroundActor);
+
+ return static_cast<ParentImpl*>(aBackgroundActor)->mIsOtherProcessActor;
+}
+
+// static
+already_AddRefed<ContentParent> ParentImpl::GetContentParent(
+ PBackgroundParent* aBackgroundActor) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aBackgroundActor);
+
+ auto actor = static_cast<ParentImpl*>(aBackgroundActor);
+ if (actor->mActorDestroyed) {
+ MOZ_ASSERT(false, "GetContentParent called after ActorDestroy was called!");
+ return nullptr;
+ }
+
+ if (actor->mContent) {
+ // We need to hand out a reference to our ContentParent but we also need to
+ // keep the one we have. We can't call AddRef here because ContentParent is
+ // not threadsafe so instead we dispatch a runnable to the main thread to do
+ // it for us. This is safe since we are guaranteed that our AddRef runnable
+ // will run before the reference we hand out can be released, and the
+ // ContentParent can't die as long as the existing reference is maintained.
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(NewNonOwningRunnableMethod(
+ "ContentParent::AddRef", actor->mContent, &ContentParent::AddRef)));
+ }
+
+ return already_AddRefed<ContentParent>(actor->mContent.get());
+}
+
+// static
+intptr_t ParentImpl::GetRawContentParentForComparison(
+ PBackgroundParent* aBackgroundActor) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aBackgroundActor);
+
+ auto actor = static_cast<ParentImpl*>(aBackgroundActor);
+ if (actor->mActorDestroyed) {
+ MOZ_ASSERT(false,
+ "GetRawContentParentForComparison called after ActorDestroy was "
+ "called!");
+ return intptr_t(-1);
+ }
+
+ return intptr_t(static_cast<ContentParent*>(actor->mContent.get()));
+}
+
+// static
+uint64_t ParentImpl::GetChildID(PBackgroundParent* aBackgroundActor) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aBackgroundActor);
+
+ auto actor = static_cast<ParentImpl*>(aBackgroundActor);
+ if (actor->mActorDestroyed) {
+ MOZ_ASSERT(false, "GetContentParent called after ActorDestroy was called!");
+ return 0;
+ }
+
+ if (actor->mContent) {
+ return actor->mContent->ChildID();
+ }
+
+ return 0;
+}
+
+// static
+bool ParentImpl::GetLiveActorArray(
+ PBackgroundParent* aBackgroundActor,
+ nsTArray<PBackgroundParent*>& aLiveActorArray) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aBackgroundActor);
+ MOZ_ASSERT(aLiveActorArray.IsEmpty());
+
+ auto actor = static_cast<ParentImpl*>(aBackgroundActor);
+ if (actor->mActorDestroyed) {
+ MOZ_ASSERT(false,
+ "GetLiveActorArray called after ActorDestroy was called!");
+ return false;
+ }
+
+ if (!actor->mLiveActorArray) {
+ return true;
+ }
+
+ for (ParentImpl* liveActor : *actor->mLiveActorArray) {
+ aLiveActorArray.AppendElement(liveActor);
+ }
+
+ return true;
+}
+
+// static
+bool ParentImpl::Alloc(ContentParent* aContent,
+ Endpoint<PBackgroundParent>&& aEndpoint) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aEndpoint.IsValid());
+
+ if (!sBackgroundThread && !CreateBackgroundThread()) {
+ NS_WARNING("Failed to create background thread!");
+ return false;
+ }
+
+ MOZ_ASSERT(sLiveActorsForBackgroundThread);
+
+ sLiveActorCount++;
+
+ RefPtr<ParentImpl> actor = new ParentImpl(aContent);
+
+ if (NS_FAILED(sBackgroundThread->Dispatch(NS_NewRunnableFunction(
+ "Background::ParentImpl::ConnectActorRunnable",
+ [actor = std::move(actor), endpoint = std::move(aEndpoint),
+ liveActorArray = sLiveActorsForBackgroundThread]() mutable {
+ MOZ_ASSERT(endpoint.IsValid());
+ MOZ_ASSERT(liveActorArray);
+ // Transfer ownership to this thread. If Open() fails then we will
+ // release this reference in Destroy.
+ ParentImpl* actorTmp;
+ actor.forget(&actorTmp);
+
+ if (!endpoint.Bind(actorTmp)) {
+ actorTmp->Destroy();
+ return;
+ }
+
+ actorTmp->SetLiveActorArray(liveActorArray);
+ })))) {
+ NS_WARNING("Failed to dispatch connect runnable!");
+
+ MOZ_ASSERT(sLiveActorCount);
+ sLiveActorCount--;
+ }
+
+ return true;
+}
+
+// static
+already_AddRefed<ChildImpl> ParentImpl::CreateActorForSameProcess(
+ nsIEventTarget* aMainEventTarget) {
+ AssertIsInMainProcess();
+
+ RefPtr<ParentImpl> parentActor;
+ nsCOMPtr<nsIThread> backgroundThread;
+
+ if (NS_IsMainThread()) {
+ if (!sBackgroundThread && !CreateBackgroundThread()) {
+ NS_WARNING("Failed to create background thread!");
+ return nullptr;
+ }
+
+ MOZ_ASSERT(!sShutdownHasStarted);
+
+ sLiveActorCount++;
+
+ parentActor = new ParentImpl();
+ backgroundThread = sBackgroundThread.get();
+ } else {
+ RefPtr<CreateActorHelper> helper = new CreateActorHelper();
+
+ nsresult rv = helper->BlockAndGetResults(aMainEventTarget, parentActor,
+ backgroundThread);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ }
+
+ RefPtr<ChildImpl> childActor = new ChildImpl();
+
+ MessageChannel* parentChannel = parentActor->GetIPCChannel();
+ MOZ_ASSERT(parentChannel);
+
+ if (!childActor->Open(parentChannel, backgroundThread, ChildSide)) {
+ NS_WARNING("Failed to open ChildImpl!");
+
+ // Can't release it here, we will release this reference in Destroy.
+ ParentImpl* actor;
+ parentActor.forget(&actor);
+
+ actor->Destroy();
+
+ return nullptr;
+ }
+
+ childActor->SetActorAlive();
+
+ // Make sure the parent knows it is same process.
+ parentActor->SetOtherProcessId(base::GetCurrentProcId());
+
+ // Now that Open() has succeeded transfer the ownership of the actors to IPDL.
+ Unused << parentActor.forget();
+
+ return childActor.forget();
+}
+
+// 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<ParentImpl*>(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<ParentImpl*>> 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([&]() { 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<ParentImpl*> actorsToClose(liveActors->Clone());
+ for (ParentImpl* 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);
+
+ mContent = nullptr;
+
+ 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;
+}
+
+nsresult ParentImpl::CreateActorHelper::BlockAndGetResults(
+ nsIEventTarget* aMainEventTarget, RefPtr<ParentImpl>& aParentActor,
+ nsCOMPtr<nsIThread>& aThread) {
+ AssertIsNotOnMainThread();
+
+ if (aMainEventTarget) {
+ MOZ_ALWAYS_SUCCEEDS(aMainEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+ }
+
+ mozilla::MonitorAutoLock lock(mMonitor);
+ while (mWaiting) {
+ lock.Wait();
+ }
+
+ if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) {
+ return mMainThreadResultCode;
+ }
+
+ aParentActor = std::move(mParentActor);
+ aThread = std::move(mThread);
+ return NS_OK;
+}
+
+nsresult ParentImpl::CreateActorHelper::RunOnMainThread() {
+ AssertIsOnMainThread();
+
+ if (!sBackgroundThread && !CreateBackgroundThread()) {
+ NS_WARNING("Failed to create background thread!");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(!sShutdownHasStarted);
+
+ sLiveActorCount++;
+
+ mParentActor = new ParentImpl();
+ mThread = sBackgroundThread;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ParentImpl::CreateActorHelper::Run() {
+ AssertIsOnMainThread();
+
+ nsresult rv = RunOnMainThread();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mMainThreadResultCode = rv;
+ }
+
+ mozilla::MonitorAutoLock lock(mMonitor);
+ MOZ_ASSERT(mWaiting);
+
+ mWaiting = false;
+ lock.Notify();
+
+ return NS_OK;
+}
+
+// -----------------------------------------------------------------------------
+// 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));
+}
+
+// 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::GetFromThreadInfo(
+ nsIEventTarget* aMainEventTarget, ThreadLocalInfo* aThreadLocalInfo) {
+ MOZ_ASSERT(aThreadLocalInfo);
+
+ if (aThreadLocalInfo->mActor) {
+ RefPtr<SendInitBackgroundRunnable>& runnable =
+ aThreadLocalInfo->mSendInitBackgroundRunnable;
+
+ if (aMainEventTarget && runnable) {
+ // The SendInitBackgroundRunnable was already dispatched to the main
+ // thread to finish initialization of a new background child actor.
+ // However, the caller passed a custom main event target which indicates
+ // that synchronous blocking of the main thread is happening (done by
+ // creating a nested event target and spinning the event loop).
+ // It can happen that the SendInitBackgroundRunnable didn't have a chance
+ // to run before the synchronous blocking has occured. Unblocking of the
+ // main thread can depend on an IPC message received on this thread, so
+ // we have to dispatch the SendInitBackgroundRunnable to the custom main
+ // event target too, otherwise IPC will be only queueing messages on this
+ // thread. The runnable will run twice in the end, but that's a harmless
+ // race between the main and nested event queue of the main thread.
+ // There's a guard in the runnable implementation for calling
+ // SendInitBackground only once.
+
+ MOZ_ALWAYS_SUCCEEDS(
+ aMainEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL));
+ }
+
+ return aThreadLocalInfo->mActor;
+ }
+
+ return nullptr;
+}
+
+/* static */
+PBackgroundChild* ChildImpl::GetOrCreateForCurrentThread(
+ nsIEventTarget* aMainEventTarget) {
+ return sParentAndContentProcessThreadInfo.GetOrCreateForCurrentThread(
+ aMainEventTarget);
+}
+
+/* static */
+PBackgroundChild* ChildImpl::GetOrCreateSocketActorForCurrentThread(
+ nsIEventTarget* aMainEventTarget) {
+ return sSocketAndContentProcessThreadInfo.GetOrCreateForCurrentThread(
+ aMainEventTarget);
+}
+
+/* static */
+PBackgroundChild* ChildImpl::GetOrCreateForSocketParentBridgeForCurrentThread(
+ nsIEventTarget* aMainEventTarget) {
+ return sSocketAndParentProcessThreadInfo.GetOrCreateForCurrentThread(
+ aMainEventTarget);
+}
+
+// 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::ThreadLocalDestructor(void* aThreadLocal) {
+ auto threadLocalInfo = static_cast<ThreadLocalInfo*>(aThreadLocal);
+
+ if (threadLocalInfo) {
+ MOZ_ASSERT(threadLocalInfo->mClosed);
+
+ if (threadLocalInfo->mActor) {
+ threadLocalInfo->mActor->Close();
+ threadLocalInfo->mActor->AssertActorDestroyed();
+ }
+
+ if (threadLocalInfo->mSendInitBackgroundRunnable) {
+ threadLocalInfo->mSendInitBackgroundRunnable->ClearEventTarget();
+ }
+
+ 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;
+}
+
+// static
+already_AddRefed<ChildImpl::SendInitBackgroundRunnable>
+ChildImpl::SendInitBackgroundRunnable::Create(
+ Endpoint<PBackgroundParent>&& aParent,
+ std::function<void(Endpoint<PBackgroundParent>&& aParent)>&& aFunc,
+ unsigned int aThreadLocalIndex) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ RefPtr<SendInitBackgroundRunnable> runnable = new SendInitBackgroundRunnable(
+ std::move(aParent), std::move(aFunc), aThreadLocalIndex);
+
+ WorkerPrivate* workerPrivate = mozilla::dom::GetCurrentThreadWorkerPrivate();
+ if (!workerPrivate) {
+ return runnable.forget();
+ }
+
+ workerPrivate->AssertIsOnWorkerThread();
+
+ runnable->mWorkerRef = StrongWorkerRef::Create(
+ workerPrivate, "ChildImpl::SendInitBackgroundRunnable");
+ if (NS_WARN_IF(!runnable->mWorkerRef)) {
+ return nullptr;
+ }
+
+ return runnable.forget();
+}
+
+NS_IMETHODIMP
+ChildImpl::SendInitBackgroundRunnable::Run() {
+ if (NS_IsMainThread()) {
+ if (mSentInitBackground) {
+ return NS_OK;
+ }
+
+ mSentInitBackground = true;
+
+ mSendInitfunc(std::move(mParent));
+
+ nsCOMPtr<nsISerialEventTarget> owningEventTarget;
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+ owningEventTarget = mOwningEventTarget;
+ }
+
+ if (!owningEventTarget) {
+ return NS_OK;
+ }
+
+ nsresult rv = owningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ ClearEventTarget();
+
+ auto threadLocalInfo =
+ static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(mThreadLocalIndex));
+
+ if (!threadLocalInfo) {
+ return NS_OK;
+ }
+
+ threadLocalInfo->mSendInitBackgroundRunnable = nullptr;
+
+ return NS_OK;
+}
diff --git a/ipc/glue/BackgroundParent.h b/ipc/glue/BackgroundParent.h
new file mode 100644
index 0000000000..1955aabcb6
--- /dev/null
+++ b/ipc/glue/BackgroundParent.h
@@ -0,0 +1,120 @@
+/* -*- 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/ipc/Transport.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 PBackgroundParent;
+
+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::dom::ContentParent;
+
+ typedef base::ProcessId ProcessId;
+ typedef mozilla::dom::BlobImpl BlobImpl;
+ typedef mozilla::dom::ContentParent ContentParent;
+ typedef mozilla::ipc::Transport Transport;
+ friend class mozilla::net::SocketProcessBridgeParent;
+ friend class mozilla::net::SocketProcessParent;
+
+ 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 the ContentParent associated with the parent actor if
+ // the parent actor corresponds to a child actor from another 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. However,
+ // ContentParent is not threadsafe and the returned pointer may not be used on
+ // any thread other than the main thread. Callers must take care to use (and
+ // release) the returned pointer appropriately.
+ static already_AddRefed<ContentParent> GetContentParent(
+ PBackgroundParent* aBackgroundActor);
+
+ // Get a value that represents the ContentParent associated with the parent
+ // actor for comparison. The value is not guaranteed to uniquely identify the
+ // ContentParent after the ContentParent has died. This function may only be
+ // called on the background thread.
+ static intptr_t GetRawContentParentForComparison(
+ PBackgroundParent* aBackgroundActor);
+
+ static uint64_t GetChildID(PBackgroundParent* aBackgroundActor);
+
+ static bool GetLiveActorArray(PBackgroundParent* aBackgroundActor,
+ nsTArray<PBackgroundParent*>& aLiveActorArray);
+
+ private:
+ // Only called by ContentParent for cross-process actors.
+ static bool Alloc(ContentParent* aContent,
+ Endpoint<PBackgroundParent>&& aEndpoint);
+
+ // Called by SocketProcessBridgeParent and SocketProcessParent for
+ // cross-process actors.
+ static bool Alloc(Endpoint<PBackgroundParent>&& 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..ec3b6d4fbe
--- /dev/null
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -0,0 +1,1411 @@
+/* -*- 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"
+#include "FileDescriptorSetParent.h"
+#ifdef MOZ_WEBRTC
+# include "CamerasParent.h"
+#endif
+#include "mozilla/Assertions.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RDDProcessManager.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/RemoteLazyInputStreamParent.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/dom/ClientManagerActors.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/EndpointForReportParent.h"
+#include "mozilla/dom/FileCreatorParent.h"
+#include "mozilla/dom/FileSystemBase.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/RemoteWorkerParent.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/cache/ActorUtils.h"
+#include "mozilla/dom/indexedDB/ActorsParent.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/IPCStreamAlloc.h"
+#include "mozilla/ipc/IdleSchedulerParent.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/ipc/PBackgroundTestParent.h"
+#include "mozilla/ipc/PChildToParentStreamParent.h"
+#include "mozilla/ipc/PParentToChildStreamParent.h"
+#include "mozilla/media/MediaParent.h"
+#include "mozilla/net/BackgroundDataBridgeParent.h"
+#include "mozilla/net/HttpBackgroundChannelParent.h"
+#include "mozilla/psm/VerifySSLServerCertParent.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsTraceRefcnt.h"
+#include "nsXULAppAPI.h"
+
+#ifdef DISABLE_ASSERTS_FOR_FUZZING
+# define ASSERT_UNLESS_FUZZING(...) \
+ do { \
+ } while (0)
+#else
+# define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false)
+#endif
+
+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;
+
+BackgroundParentImpl::BackgroundParentImpl() {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnMainThread();
+
+ 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) {
+ 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 nsCString& aTestArg) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return new TestParent();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPBackgroundTestConstructor(
+ PBackgroundTestParent* aActor, const nsCString& aTestArg) {
+ 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> {
+ using mozilla::dom::indexedDB::AllocPBackgroundIDBFactoryParent;
+
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return AllocPBackgroundIDBFactoryParent(aLoggingInfo);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundIDBFactoryConstructor(
+ PBackgroundIDBFactoryParent* aActor, const LoggingInfo& aLoggingInfo) {
+ 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* {
+ 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() {
+ 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) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::AllocPBackgroundSDBConnectionParent(aPersistenceType,
+ aPrincipalInfo);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundSDBConnectionConstructor(
+ PBackgroundSDBConnectionParent* aActor,
+ const PersistenceType& aPersistenceType,
+ const PrincipalInfo& aPrincipalInfo) {
+ 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) {
+ 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) {
+ 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) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::AllocPBackgroundLSObserverParent(aObserverId);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundLSObserverConstructor(
+ PBackgroundLSObserverParent* aActor, const uint64_t& aObserverId) {
+ 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) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::AllocPBackgroundLSRequestParent(this, aParams);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundLSRequestConstructor(
+ PBackgroundLSRequestParent* aActor, const LSRequestParams& aParams) {
+ 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) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::AllocPBackgroundLSSimpleRequestParent(this, aParams);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundLSSimpleRequestConstructor(
+ PBackgroundLSSimpleRequestParent* aActor,
+ const LSSimpleRequestParams& aParams) {
+ 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);
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvLSClearPrivateBrowsing() {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ if (BackgroundParent::IsOtherProcessActor(this)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ if (!mozilla::dom::RecvLSClearPrivateBrowsing()) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+BackgroundParentImpl::PBackgroundLocalStorageCacheParent*
+BackgroundParentImpl::AllocPBackgroundLocalStorageCacheParent(
+ const PrincipalInfo& aPrincipalInfo, const nsCString& aOriginKey,
+ const uint32_t& aPrivateBrowsingId) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::AllocPBackgroundLocalStorageCacheParent(
+ aPrincipalInfo, aOriginKey, aPrivateBrowsingId);
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPBackgroundLocalStorageCacheConstructor(
+ PBackgroundLocalStorageCacheParent* aActor,
+ const PrincipalInfo& aPrincipalInfo, const nsCString& aOriginKey,
+ const uint32_t& aPrivateBrowsingId) {
+ 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 nsString& aProfilePath, const uint32_t& aPrivateBrowsingId)
+ -> PBackgroundStorageParent* {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return mozilla::dom::AllocPBackgroundStorageParent(aProfilePath,
+ aPrivateBrowsingId);
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPBackgroundStorageConstructor(
+ PBackgroundStorageParent* aActor, const nsString& aProfilePath,
+ const uint32_t& aPrivateBrowsingId) {
+ 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) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return dom::AllocPBackgroundSessionStorageManagerParent(aTopContextId);
+}
+
+already_AddRefed<PIdleSchedulerParent>
+BackgroundParentImpl::AllocPIdleSchedulerParent() {
+ AssertIsOnBackgroundThread();
+ RefPtr<IdleSchedulerParent> actor = new IdleSchedulerParent();
+ return actor.forget();
+}
+
+mozilla::dom::PRemoteWorkerParent*
+BackgroundParentImpl::AllocPRemoteWorkerParent(const RemoteWorkerData& aData) {
+ RefPtr<dom::RemoteWorkerParent> agent = new dom::RemoteWorkerParent();
+ return agent.forget().take();
+}
+
+bool BackgroundParentImpl::DeallocPRemoteWorkerParent(
+ mozilla::dom::PRemoteWorkerParent* aActor) {
+ RefPtr<mozilla::dom::RemoteWorkerParent> actor =
+ dont_AddRef(static_cast<mozilla::dom::RemoteWorkerParent*>(aActor));
+ return true;
+}
+
+dom::PRemoteWorkerControllerParent*
+BackgroundParentImpl::AllocPRemoteWorkerControllerParent(
+ const dom::RemoteWorkerData& aRemoteWorkerData) {
+ RefPtr<dom::RemoteWorkerControllerParent> actor =
+ new dom::RemoteWorkerControllerParent(aRemoteWorkerData);
+ return actor.forget().take();
+}
+
+IPCResult BackgroundParentImpl::RecvPRemoteWorkerControllerConstructor(
+ dom::PRemoteWorkerControllerParent* aActor,
+ const dom::RemoteWorkerData& aRemoteWorkerData) {
+ 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;
+}
+
+mozilla::dom::PRemoteWorkerServiceParent*
+BackgroundParentImpl::AllocPRemoteWorkerServiceParent() {
+ return new mozilla::dom::RemoteWorkerServiceParent();
+}
+
+IPCResult BackgroundParentImpl::RecvPRemoteWorkerServiceConstructor(
+ PRemoteWorkerServiceParent* aActor) {
+ mozilla::dom::RemoteWorkerServiceParent* actor =
+ static_cast<mozilla::dom::RemoteWorkerServiceParent*>(aActor);
+
+ RefPtr<ContentParent> parent = BackgroundParent::GetContentParent(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());
+ NS_ReleaseOnMainThread("ContentParent release", parent.forget());
+ }
+ return IPC_OK();
+}
+
+bool BackgroundParentImpl::DeallocPRemoteWorkerServiceParent(
+ mozilla::dom::PRemoteWorkerServiceParent* aActor) {
+ delete aActor;
+ return true;
+}
+
+mozilla::dom::PSharedWorkerParent*
+BackgroundParentImpl::AllocPSharedWorkerParent(
+ const mozilla::dom::RemoteWorkerData& aData, const uint64_t& aWindowID,
+ const mozilla::dom::MessagePortIdentifier& aPortIdentifier) {
+ 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) {
+ 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 nsString& aFullPath, const nsString& aType, const nsString& aName,
+ const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck,
+ const bool& aIsFromNsIFile) {
+ RefPtr<dom::FileCreatorParent> actor = new dom::FileCreatorParent();
+ return actor.forget().take();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPFileCreatorConstructor(
+ dom::PFileCreatorParent* aActor, const nsString& aFullPath,
+ const nsString& aType, const nsString& aName,
+ const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck,
+ const bool& aIsFromNsIFile) {
+ bool isFileRemoteType = false;
+
+ // If the ContentParent is null we are dealing with a same-process actor.
+ RefPtr<ContentParent> parent = BackgroundParent::GetContentParent(this);
+ if (!parent) {
+ isFileRemoteType = true;
+ } else {
+ isFileRemoteType = parent->GetRemoteType() == FILE_REMOTE_TYPE;
+ NS_ReleaseOnMainThread("ContentParent release", parent.forget());
+ }
+
+ 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() {
+ return new dom::TemporaryIPCBlobParent();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPTemporaryIPCBlobConstructor(
+ dom::PTemporaryIPCBlobParent* aActor) {
+ dom::TemporaryIPCBlobParent* actor =
+ static_cast<dom::TemporaryIPCBlobParent*>(aActor);
+ return actor->CreateAndShareFile();
+}
+
+bool BackgroundParentImpl::DeallocPTemporaryIPCBlobParent(
+ dom::PTemporaryIPCBlobParent* aActor) {
+ delete aActor;
+ return true;
+}
+
+already_AddRefed<PRemoteLazyInputStreamParent>
+BackgroundParentImpl::AllocPRemoteLazyInputStreamParent(const nsID& aID,
+ const uint64_t& aSize) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<RemoteLazyInputStreamParent> actor =
+ RemoteLazyInputStreamParent::Create(aID, aSize, this);
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPRemoteLazyInputStreamConstructor(
+ PRemoteLazyInputStreamParent* aActor, const nsID& aID,
+ const uint64_t& aSize) {
+ if (!static_cast<RemoteLazyInputStreamParent*>(aActor)->HasValidStream()) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ return IPC_OK();
+}
+
+PFileDescriptorSetParent* BackgroundParentImpl::AllocPFileDescriptorSetParent(
+ const FileDescriptor& aFileDescriptor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return new FileDescriptorSetParent(aFileDescriptor);
+}
+
+bool BackgroundParentImpl::DeallocPFileDescriptorSetParent(
+ PFileDescriptorSetParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ delete static_cast<FileDescriptorSetParent*>(aActor);
+ return true;
+}
+
+PChildToParentStreamParent*
+BackgroundParentImpl::AllocPChildToParentStreamParent() {
+ return mozilla::ipc::AllocPChildToParentStreamParent();
+}
+
+bool BackgroundParentImpl::DeallocPChildToParentStreamParent(
+ PChildToParentStreamParent* aActor) {
+ delete aActor;
+ return true;
+}
+
+PParentToChildStreamParent*
+BackgroundParentImpl::AllocPParentToChildStreamParent() {
+ MOZ_CRASH(
+ "PParentToChildStreamParent actors should be manually constructed!");
+}
+
+bool BackgroundParentImpl::DeallocPParentToChildStreamParent(
+ PParentToChildStreamParent* aActor) {
+ delete aActor;
+ return true;
+}
+
+BackgroundParentImpl::PVsyncParent* BackgroundParentImpl::AllocPVsyncParent() {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<mozilla::dom::VsyncParent> actor = new mozilla::dom::VsyncParent();
+ actor->UpdateVsyncSource(nullptr);
+ // There still has one ref-count after return, and it will be released in
+ // DeallocPVsyncParent().
+ return actor.forget().take();
+}
+
+bool BackgroundParentImpl::DeallocPVsyncParent(PVsyncParent* aActor) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ // This actor already has one ref-count. Please check AllocPVsyncParent().
+ RefPtr<mozilla::dom::VsyncParent> actor =
+ dont_AddRef(static_cast<mozilla::dom::VsyncParent*>(aActor));
+ return true;
+}
+
+camera::PCamerasParent* BackgroundParentImpl::AllocPCamerasParent() {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+#ifdef MOZ_WEBRTC
+ RefPtr<mozilla::camera::CamerasParent> actor =
+ mozilla::camera::CamerasParent::Create();
+ return actor.forget().take();
+#else
+ return nullptr;
+#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 nsCString& /* unused */)
+ -> PUDPSocketParent* {
+ RefPtr<UDPSocketParent> p = new UDPSocketParent(this);
+
+ return p.forget().take();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPUDPSocketConstructor(
+ PUDPSocketParent* aActor, const Maybe<PrincipalInfo>& aOptionalPrincipal,
+ const nsCString& aFilter) {
+ 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 ByteArray& aServerCert, const nsTArray<ByteArray>& aPeerCertChain,
+ const nsCString& 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) {
+ RefPtr<mozilla::psm::VerifySSLServerCertParent> parent =
+ new mozilla::psm::VerifySSLServerCertParent();
+ return parent.forget();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPVerifySSLServerCertConstructor(
+ PVerifySSLServerCertParent* aActor, const ByteArray& aServerCert,
+ nsTArray<ByteArray>&& aPeerCertChain, const nsCString& 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) {
+ mozilla::psm::VerifySSLServerCertParent* authCert =
+ static_cast<mozilla::psm::VerifySSLServerCertParent*>(aActor);
+ if (!authCert->Dispatch(aServerCert, std::move(aPeerCertChain), aHostName,
+ aPort, aOriginAttributes, aStapledOCSPResponse,
+ aSctsFromTLSExtension, aDcInfo, aProviderFlags,
+ aCertVerifierFlags)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+mozilla::dom::PBroadcastChannelParent*
+BackgroundParentImpl::AllocPBroadcastChannelParent(
+ const PrincipalInfo& aPrincipalInfo, const nsCString& aOrigin,
+ const nsString& aChannel) {
+ 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 {
+
+struct MOZ_STACK_CLASS NullifyContentParentRAII {
+ explicit NullifyContentParentRAII(RefPtr<ContentParent>& aContentParent)
+ : mContentParent(aContentParent) {}
+
+ ~NullifyContentParentRAII() { mContentParent = nullptr; }
+
+ RefPtr<ContentParent>& mContentParent;
+};
+
+class CheckPrincipalRunnable final : public Runnable {
+ public:
+ CheckPrincipalRunnable(already_AddRefed<ContentParent> aParent,
+ const PrincipalInfo& aPrincipalInfo,
+ const nsCString& aOrigin)
+ : Runnable("ipc::CheckPrincipalRunnable"),
+ mContentParent(aParent),
+ mPrincipalInfo(aPrincipalInfo),
+ mOrigin(aOrigin) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ MOZ_ASSERT(mContentParent);
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NullifyContentParentRAII raii(mContentParent);
+
+ auto principalOrErr = PrincipalInfoToPrincipal(mPrincipalInfo);
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ mContentParent->KillHard(
+ "BroadcastChannel killed: PrincipalInfoToPrincipal failed.");
+ }
+
+ nsAutoCString origin;
+ nsresult rv = principalOrErr.unwrap()->GetOrigin(origin);
+ if (NS_FAILED(rv)) {
+ mContentParent->KillHard(
+ "BroadcastChannel killed: principal::GetOrigin failed.");
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(!mOrigin.Equals(origin))) {
+ mContentParent->KillHard(
+ "BroadcastChannel killed: origins do not match.");
+ return NS_OK;
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<ContentParent> mContentParent;
+ PrincipalInfo mPrincipalInfo;
+ nsCString mOrigin;
+};
+
+} // namespace
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPBroadcastChannelConstructor(
+ PBroadcastChannelParent* actor, const PrincipalInfo& aPrincipalInfo,
+ const nsCString& aOrigin, const nsString& aChannel) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<ContentParent> parent = BackgroundParent::GetContentParent(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() {
+ 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() {
+ 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();
+}
+
+PCacheStorageParent* BackgroundParentImpl::AllocPCacheStorageParent(
+ const Namespace& aNamespace, const PrincipalInfo& aPrincipalInfo) {
+ return dom::cache::AllocPCacheStorageParent(this, aNamespace, aPrincipalInfo);
+}
+
+bool BackgroundParentImpl::DeallocPCacheStorageParent(
+ PCacheStorageParent* aActor) {
+ dom::cache::DeallocPCacheStorageParent(aActor);
+ return true;
+}
+
+PCacheParent* BackgroundParentImpl::AllocPCacheParent() {
+ MOZ_CRASH("CacheParent actor must be provided to PBackground manager");
+ return nullptr;
+}
+
+bool BackgroundParentImpl::DeallocPCacheParent(PCacheParent* aActor) {
+ dom::cache::DeallocPCacheParent(aActor);
+ return true;
+}
+
+already_AddRefed<PCacheStreamControlParent>
+BackgroundParentImpl::AllocPCacheStreamControlParent() {
+ MOZ_CRASH(
+ "CacheStreamControlParent actor must be provided to PBackground manager");
+ return nullptr;
+}
+
+PMessagePortParent* BackgroundParentImpl::AllocPMessagePortParent(
+ const nsID& aUUID, const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ return new MessagePortParent(aUUID);
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPMessagePortConstructor(
+ PMessagePortParent* aActor, const nsID& aUUID, const nsID& aDestinationUUID,
+ const uint32_t& aSequenceID) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ MessagePortParent* mp = static_cast<MessagePortParent*>(aActor);
+ if (!mp->Entangle(aDestinationUUID, aSequenceID)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+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) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ if (!MessagePortParent::ForceClose(aUUID, aDestinationUUID, aSequenceID)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+BackgroundParentImpl::PQuotaParent* BackgroundParentImpl::AllocPQuotaParent() {
+ 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() {
+ 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() {
+ 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) {
+ 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) {
+ 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();
+}
+
+already_AddRefed<dom::PFileSystemRequestParent>
+BackgroundParentImpl::AllocPFileSystemRequestParent(
+ const FileSystemParams& aParams) {
+ 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) {
+ static_cast<FileSystemRequestParent*>(aActor)->Start();
+ return IPC_OK();
+}
+
+// Gamepad API Background IPC
+already_AddRefed<dom::PGamepadEventChannelParent>
+BackgroundParentImpl::AllocPGamepadEventChannelParent() {
+ return dom::GamepadEventChannelParent::Create();
+}
+
+already_AddRefed<dom::PGamepadTestChannelParent>
+BackgroundParentImpl::AllocPGamepadTestChannelParent() {
+ return dom::GamepadTestChannelParent::Create();
+}
+
+dom::PWebAuthnTransactionParent*
+BackgroundParentImpl::AllocPWebAuthnTransactionParent() {
+ 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) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<net::HttpBackgroundChannelParent> actor =
+ new net::HttpBackgroundChannelParent();
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPHttpBackgroundChannelConstructor(
+ net::PHttpBackgroundChannelParent* aActor, const uint64_t& aChannelId) {
+ 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();
+}
+
+PMIDIPortParent* BackgroundParentImpl::AllocPMIDIPortParent(
+ const MIDIPortInfo& aPortInfo, const bool& aSysexEnabled) {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<MIDIPortParent> result = new MIDIPortParent(aPortInfo, aSysexEnabled);
+ return result.forget().take();
+}
+
+bool BackgroundParentImpl::DeallocPMIDIPortParent(PMIDIPortParent* aActor) {
+ MOZ_ASSERT(aActor);
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<MIDIPortParent> parent =
+ dont_AddRef(static_cast<MIDIPortParent*>(aActor));
+ parent->Teardown();
+ return true;
+}
+
+PMIDIManagerParent* BackgroundParentImpl::AllocPMIDIManagerParent() {
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<MIDIManagerParent> result = new MIDIManagerParent();
+ MIDIPlatformService::Get()->AddManager(result);
+ return result.forget().take();
+}
+
+bool BackgroundParentImpl::DeallocPMIDIManagerParent(
+ PMIDIManagerParent* aActor) {
+ MOZ_ASSERT(aActor);
+ AssertIsInMainOrSocketProcess();
+ AssertIsOnBackgroundThread();
+
+ RefPtr<MIDIManagerParent> parent =
+ dont_AddRef(static_cast<MIDIManagerParent*>(aActor));
+ parent->Teardown();
+ return true;
+}
+
+mozilla::dom::PClientManagerParent*
+BackgroundParentImpl::AllocPClientManagerParent() {
+ 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) {
+ mozilla::dom::InitClientManagerParent(aActor);
+ return IPC_OK();
+}
+
+IPCResult BackgroundParentImpl::RecvStorageActivity(
+ const PrincipalInfo& aPrincipalInfo) {
+ dom::StorageActivityService::SendActivity(aPrincipalInfo);
+ return IPC_OK();
+}
+
+already_AddRefed<PServiceWorkerParent>
+BackgroundParentImpl::AllocPServiceWorkerParent(
+ const IPCServiceWorkerDescriptor&) {
+ return MakeAndAddRef<ServiceWorkerParent>();
+}
+
+IPCResult BackgroundParentImpl::RecvPServiceWorkerConstructor(
+ PServiceWorkerParent* aActor,
+ const IPCServiceWorkerDescriptor& aDescriptor) {
+ dom::InitServiceWorkerParent(aActor, aDescriptor);
+ return IPC_OK();
+}
+
+already_AddRefed<PServiceWorkerContainerParent>
+BackgroundParentImpl::AllocPServiceWorkerContainerParent() {
+ return MakeAndAddRef<mozilla::dom::ServiceWorkerContainerParent>();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPServiceWorkerContainerConstructor(
+ PServiceWorkerContainerParent* aActor) {
+ dom::InitServiceWorkerContainerParent(aActor);
+ return IPC_OK();
+}
+
+already_AddRefed<PServiceWorkerRegistrationParent>
+BackgroundParentImpl::AllocPServiceWorkerRegistrationParent(
+ const IPCServiceWorkerRegistrationDescriptor&) {
+ return MakeAndAddRef<mozilla::dom::ServiceWorkerRegistrationParent>();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvPServiceWorkerRegistrationConstructor(
+ PServiceWorkerRegistrationParent* aActor,
+ const IPCServiceWorkerRegistrationDescriptor& aDescriptor) {
+ dom::InitServiceWorkerRegistrationParent(aActor, aDescriptor);
+ return IPC_OK();
+}
+
+dom::PEndpointForReportParent*
+BackgroundParentImpl::AllocPEndpointForReportParent(
+ const nsString& aGroupName, const PrincipalInfo& aPrincipalInfo) {
+ RefPtr<dom::EndpointForReportParent> actor =
+ new dom::EndpointForReportParent();
+ return actor.forget().take();
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvPEndpointForReportConstructor(
+ PEndpointForReportParent* aActor, const nsString& aGroupName,
+ const PrincipalInfo& aPrincipalInfo) {
+ static_cast<dom::EndpointForReportParent*>(aActor)->Run(aGroupName,
+ aPrincipalInfo);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+BackgroundParentImpl::RecvEnsureRDDProcessAndCreateBridge(
+ EnsureRDDProcessAndCreateBridgeResolver&& aResolver) {
+ RDDProcessManager* rdd = RDDProcessManager::Get();
+ using Type =
+ Tuple<const nsresult&, Endpoint<mozilla::PRemoteDecoderManagerChild>&&>;
+ if (!rdd) {
+ aResolver(
+ Type(NS_ERROR_NOT_AVAILABLE, Endpoint<PRemoteDecoderManagerChild>()));
+ } else {
+ rdd->EnsureRDDProcessAndCreateBridge(OtherPid())
+ ->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();
+}
+
+bool BackgroundParentImpl::DeallocPEndpointForReportParent(
+ PEndpointForReportParent* aActor) {
+ RefPtr<dom::EndpointForReportParent> actor =
+ dont_AddRef(static_cast<dom::EndpointForReportParent*>(aActor));
+ return true;
+}
+
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvRemoveEndpoint(
+ const nsString& aGroupName, const nsCString& aEndpointURL,
+ const PrincipalInfo& aPrincipalInfo) {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("BackgroundParentImpl::RecvRemoveEndpoint(",
+ [aGroupName, aEndpointURL, aPrincipalInfo]() {
+ dom::ReportingHeader::RemoveEndpoint(
+ aGroupName, aEndpointURL, aPrincipalInfo);
+ }));
+
+ return IPC_OK();
+}
+
+dom::PMediaTransportParent* BackgroundParentImpl::AllocPMediaTransportParent() {
+#ifdef MOZ_WEBRTC
+ return new MediaTransportParent;
+#else
+ return nullptr;
+#endif
+}
+
+bool BackgroundParentImpl::DeallocPMediaTransportParent(
+ dom::PMediaTransportParent* aActor) {
+#ifdef MOZ_WEBRTC
+ delete aActor;
+#endif
+ return true;
+}
+
+PParentToChildStreamParent*
+BackgroundParentImpl::SendPParentToChildStreamConstructor(
+ PParentToChildStreamParent* aActor) {
+ return PBackgroundParent::SendPParentToChildStreamConstructor(aActor);
+}
+
+PFileDescriptorSetParent*
+BackgroundParentImpl::SendPFileDescriptorSetConstructor(
+ const FileDescriptor& aFD) {
+ return PBackgroundParent::SendPFileDescriptorSetConstructor(aFD);
+}
+
+} // 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..ecd9fb493e
--- /dev/null
+++ b/ipc/glue/BackgroundParentImpl.h
@@ -0,0 +1,411 @@
+/* -*- 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/Attributes.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/PBackgroundParent.h"
+
+namespace mozilla {
+namespace ipc {
+
+// Instances of this class should never be created directly. This class is meant
+// to be inherited in BackgroundImpl.
+class BackgroundParentImpl : public PBackgroundParent,
+ public ParentToChildStreamActorManager {
+ public:
+ PParentToChildStreamParent* SendPParentToChildStreamConstructor(
+ PParentToChildStreamParent* aActor) override;
+ PFileDescriptorSetParent* SendPFileDescriptorSetConstructor(
+ const FileDescriptor& aFD) override;
+
+ protected:
+ BackgroundParentImpl();
+ virtual ~BackgroundParentImpl();
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ PBackgroundTestParent* AllocPBackgroundTestParent(
+ const nsCString& aTestArg) override;
+
+ mozilla::ipc::IPCResult RecvPBackgroundTestConstructor(
+ PBackgroundTestParent* aActor, const nsCString& 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;
+
+ mozilla::ipc::IPCResult RecvLSClearPrivateBrowsing() override;
+
+ PBackgroundLocalStorageCacheParent* AllocPBackgroundLocalStorageCacheParent(
+ const PrincipalInfo& aPrincipalInfo, const nsCString& aOriginKey,
+ const uint32_t& aPrivateBrowsingId) override;
+
+ mozilla::ipc::IPCResult RecvPBackgroundLocalStorageCacheConstructor(
+ PBackgroundLocalStorageCacheParent* aActor,
+ const PrincipalInfo& aPrincipalInfo, const nsCString& aOriginKey,
+ const uint32_t& aPrivateBrowsingId) override;
+
+ bool DeallocPBackgroundLocalStorageCacheParent(
+ PBackgroundLocalStorageCacheParent* aActor) override;
+
+ PBackgroundStorageParent* AllocPBackgroundStorageParent(
+ const nsString& aProfilePath,
+ const uint32_t& aPrivateBrowsingId) override;
+
+ mozilla::ipc::IPCResult RecvPBackgroundStorageConstructor(
+ PBackgroundStorageParent* aActor, const nsString& aProfilePath,
+ const uint32_t& aPrivateBrowsingId) override;
+
+ bool DeallocPBackgroundStorageParent(
+ PBackgroundStorageParent* aActor) override;
+
+ already_AddRefed<PBackgroundSessionStorageManagerParent>
+ AllocPBackgroundSessionStorageManagerParent(
+ const uint64_t& aTopContextId) override;
+
+ already_AddRefed<PIdleSchedulerParent> AllocPIdleSchedulerParent() override;
+
+ already_AddRefed<PRemoteLazyInputStreamParent>
+ AllocPRemoteLazyInputStreamParent(const nsID& aID,
+ const uint64_t& aSize) override;
+
+ mozilla::ipc::IPCResult RecvPRemoteLazyInputStreamConstructor(
+ PRemoteLazyInputStreamParent* aActor, const nsID& aID,
+ const uint64_t& aSize) override;
+
+ PTemporaryIPCBlobParent* AllocPTemporaryIPCBlobParent() override;
+
+ mozilla::ipc::IPCResult RecvPTemporaryIPCBlobConstructor(
+ PTemporaryIPCBlobParent* actor) override;
+
+ bool DeallocPTemporaryIPCBlobParent(PTemporaryIPCBlobParent* aActor) override;
+
+ PFileCreatorParent* AllocPFileCreatorParent(
+ const nsString& aFullPath, const nsString& aType, const nsString& aName,
+ const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck,
+ const bool& aIsFromNsIFile) override;
+
+ mozilla::ipc::IPCResult RecvPFileCreatorConstructor(
+ PFileCreatorParent* actor, const nsString& aFullPath,
+ const nsString& aType, const nsString& aName,
+ const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck,
+ const bool& aIsFromNsIFile) override;
+
+ bool DeallocPFileCreatorParent(PFileCreatorParent* aActor) override;
+
+ mozilla::dom::PRemoteWorkerParent* AllocPRemoteWorkerParent(
+ const RemoteWorkerData& aData) override;
+
+ bool DeallocPRemoteWorkerParent(PRemoteWorkerParent* 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;
+
+ mozilla::dom::PRemoteWorkerServiceParent* AllocPRemoteWorkerServiceParent()
+ override;
+
+ mozilla::ipc::IPCResult RecvPRemoteWorkerServiceConstructor(
+ PRemoteWorkerServiceParent* aActor) override;
+
+ bool DeallocPRemoteWorkerServiceParent(
+ 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;
+
+ PFileDescriptorSetParent* AllocPFileDescriptorSetParent(
+ const FileDescriptor& aFileDescriptor) override;
+
+ bool DeallocPFileDescriptorSetParent(
+ PFileDescriptorSetParent* aActor) override;
+
+ PVsyncParent* AllocPVsyncParent() override;
+
+ bool DeallocPVsyncParent(PVsyncParent* aActor) override;
+
+ already_AddRefed<mozilla::psm::PVerifySSLServerCertParent>
+ AllocPVerifySSLServerCertParent(
+ const ByteArray& aServerCert, const nsTArray<ByteArray>& aPeerCertChain,
+ const nsCString& 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, const ByteArray& aServerCert,
+ nsTArray<ByteArray>&& aPeerCertChain, const nsCString& 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;
+
+ PBroadcastChannelParent* AllocPBroadcastChannelParent(
+ const PrincipalInfo& aPrincipalInfo, const nsCString& aOrigin,
+ const nsString& aChannel) override;
+
+ mozilla::ipc::IPCResult RecvPBroadcastChannelConstructor(
+ PBroadcastChannelParent* actor, const PrincipalInfo& aPrincipalInfo,
+ const nsCString& origin, const nsString& channel) override;
+
+ bool DeallocPBroadcastChannelParent(PBroadcastChannelParent* aActor) override;
+
+ PChildToParentStreamParent* AllocPChildToParentStreamParent() override;
+
+ bool DeallocPChildToParentStreamParent(
+ PChildToParentStreamParent* aActor) override;
+
+ PParentToChildStreamParent* AllocPParentToChildStreamParent() override;
+
+ bool DeallocPParentToChildStreamParent(
+ PParentToChildStreamParent* aActor) override;
+
+ PServiceWorkerManagerParent* AllocPServiceWorkerManagerParent() override;
+
+ bool DeallocPServiceWorkerManagerParent(
+ PServiceWorkerManagerParent* aActor) override;
+
+ PCamerasParent* AllocPCamerasParent() override;
+
+ bool DeallocPCamerasParent(PCamerasParent* aActor) override;
+
+ mozilla::ipc::IPCResult RecvShutdownServiceWorkerRegistrar() override;
+
+ dom::cache::PCacheStorageParent* AllocPCacheStorageParent(
+ const dom::cache::Namespace& aNamespace,
+ const PrincipalInfo& aPrincipalInfo) override;
+
+ bool DeallocPCacheStorageParent(
+ dom::cache::PCacheStorageParent* aActor) override;
+
+ dom::cache::PCacheParent* AllocPCacheParent() override;
+
+ bool DeallocPCacheParent(dom::cache::PCacheParent* aActor) override;
+
+ already_AddRefed<dom::cache::PCacheStreamControlParent>
+ AllocPCacheStreamControlParent();
+
+ PUDPSocketParent* AllocPUDPSocketParent(const Maybe<PrincipalInfo>& pInfo,
+ const nsCString& aFilter) override;
+ mozilla::ipc::IPCResult RecvPUDPSocketConstructor(
+ PUDPSocketParent*, const Maybe<PrincipalInfo>& aPrincipalInfo,
+ const nsCString& 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;
+
+ 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;
+
+ 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;
+
+ PMIDIPortParent* AllocPMIDIPortParent(const MIDIPortInfo& aPortInfo,
+ const bool& aSysexEnabled) override;
+
+ bool DeallocPMIDIPortParent(PMIDIPortParent* aActor) override;
+
+ PMIDIManagerParent* AllocPMIDIManagerParent() override;
+
+ bool DeallocPMIDIManagerParent(PMIDIManagerParent* aActor) override;
+
+ mozilla::ipc::IPCResult RecvStorageActivity(
+ const PrincipalInfo& aPrincipalInfo) override;
+
+ already_AddRefed<PServiceWorkerParent> AllocPServiceWorkerParent(
+ const IPCServiceWorkerDescriptor&) final;
+
+ 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 nsString& aGroupName, const PrincipalInfo& aPrincipalInfo) override;
+
+ mozilla::ipc::IPCResult RecvPEndpointForReportConstructor(
+ PEndpointForReportParent* actor, const nsString& aGroupName,
+ const PrincipalInfo& aPrincipalInfo) override;
+
+ mozilla::ipc::IPCResult RecvEnsureRDDProcessAndCreateBridge(
+ EnsureRDDProcessAndCreateBridgeResolver&& aResolver) override;
+
+ bool DeallocPEndpointForReportParent(
+ PEndpointForReportParent* aActor) override;
+
+ mozilla::ipc::IPCResult RecvRemoveEndpoint(
+ const nsString& aGroupName, const nsCString& aEndpointURL,
+ const PrincipalInfo& aPrincipalInfo) override;
+
+ dom::PMediaTransportParent* AllocPMediaTransportParent() override;
+ bool DeallocPMediaTransportParent(
+ dom::PMediaTransportParent* aActor) override;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_backgroundparentimpl_h__
diff --git a/ipc/glue/BackgroundUtils.cpp b/ipc/glue/BackgroundUtils.cpp
new file mode 100644
index 0000000000..5f9c066713
--- /dev/null
+++ b/ipc/glue/BackgroundUtils.cpp
@@ -0,0 +1,1018 @@
+/* -*- 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/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/CookieJarSettings.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 "URIUtils.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"
+
+namespace mozilla {
+
+using mozilla::BasePrincipal;
+using mozilla::Maybe;
+using mozilla::dom::BrowsingContext;
+using mozilla::dom::ServiceWorkerDescriptor;
+using namespace mozilla::net;
+
+namespace ipc {
+
+Result<nsCOMPtr<nsIPrincipal>, nsresult> PrincipalInfoToPrincipal(
+ const PrincipalInfo& aPrincipalInfo) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipalInfo.type() != PrincipalInfo::T__None);
+
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ nsContentUtils::GetSecurityManager();
+ if (!secMan) {
+ return Err(NS_ERROR_NULL_POINTER);
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv;
+
+ switch (aPrincipalInfo.type()) {
+ case PrincipalInfo::TSystemPrincipalInfo: {
+ rv = secMan->GetSystemPrincipal(getter_AddRefs(principal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Err(rv);
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ principal = BasePrincipal::CreateContentPrincipal(uri, info.attrs());
+ 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.domain()) {
+ nsCOMPtr<nsIURI> domain;
+ rv = NS_NewURI(getter_AddRefs(domain), *info.domain());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Err(rv);
+ }
+
+ rv = principal->SetDomain(domain);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return Err(rv);
+ }
+ }
+
+ 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);
+ }
+ return Err(NS_ERROR_FAILURE);
+}
+
+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(NS_IsMainThread());
+ 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> sandboxedLoadingPrincipalInfo;
+ if (aLoadInfo->GetLoadingSandboxed()) {
+ sandboxedLoadingPrincipalInfo.emplace();
+ rv = PrincipalToPrincipalInfo(aLoadInfo->GetSandboxedLoadingPrincipal(),
+ sandboxedLoadingPrincipalInfo.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<PrincipalInfo> topLevelStorageAreaPrincipalInfo;
+ if (nsIPrincipal* prin = aLoadInfo->GetTopLevelStorageAreaPrincipal()) {
+ topLevelStorageAreaPrincipalInfo.emplace();
+ rv = PrincipalToPrincipalInfo(prin, topLevelStorageAreaPrincipalInfo.ptr());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ Maybe<URIParams> optionalResultPrincipalURI;
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+ Unused << aLoadInfo->GetResultPrincipalURI(
+ getter_AddRefs(resultPrincipalURI));
+ if (resultPrincipalURI) {
+ SerializeURI(resultPrincipalURI, optionalResultPrincipalURI);
+ }
+
+ 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);
+ }
+
+ *aOptionalLoadInfoArgs = Some(LoadInfoArgs(
+ loadingPrincipalInfo, triggeringPrincipalInfo, principalToInheritInfo,
+ sandboxedLoadingPrincipalInfo, topLevelPrincipalInfo,
+ topLevelStorageAreaPrincipalInfo, optionalResultPrincipalURI,
+ 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->GetBypassCORSChecks(),
+ aLoadInfo->GetSkipContentPolicyCheckForWebRequest(),
+ aLoadInfo->GetOriginalFrameSrcLoad(),
+ aLoadInfo->GetForceInheritPrincipalDropped(),
+ aLoadInfo->GetInnerWindowID(), aLoadInfo->GetBrowsingContextID(),
+ aLoadInfo->GetFrameBrowsingContextID(),
+ aLoadInfo->GetInitialSecurityCheckDone(),
+ aLoadInfo->GetIsInThirdPartyContext(),
+ aLoadInfo->GetIsThirdPartyContextToTopWindow(),
+ aLoadInfo->GetIsFormSubmission(), aLoadInfo->GetSendCSPViolationEvents(),
+ aLoadInfo->GetOriginAttributes(), redirectChainIncludingInternalRedirects,
+ redirectChain, ipcClientInfo, ipcReservedClientInfo, ipcInitialClientInfo,
+ ipcController, aLoadInfo->CorsUnsafeHeaders(),
+ aLoadInfo->GetForcePreflight(), aLoadInfo->GetIsPreflight(),
+ aLoadInfo->GetLoadTriggeredFromExternal(),
+ aLoadInfo->GetServiceWorkerTaintingSynthesized(),
+ aLoadInfo->GetDocumentHasUserInteracted(),
+ aLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain(),
+ cspNonce, aLoadInfo->GetSkipContentSniffing(),
+ aLoadInfo->GetHttpsOnlyStatus(),
+ aLoadInfo->GetHasValidUserGestureActivation(),
+ aLoadInfo->GetAllowDeprecatedSystemRequests(),
+ aLoadInfo->GetIsInDevToolsContext(), aLoadInfo->GetParserCreatedScript(),
+ aLoadInfo->GetIsFromProcessingFrameAttributes(), cookieJarSettingsArgs,
+ aLoadInfo->GetRequestBlockingReason(), maybeCspToInheritInfo,
+ aLoadInfo->GetHasStoragePermission(),
+ aLoadInfo->GetLoadingEmbedderPolicy()));
+
+ return NS_OK;
+}
+
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs,
+ nsILoadInfo** outLoadInfo) {
+ return LoadInfoArgsToLoadInfo(aOptionalLoadInfoArgs, nullptr, outLoadInfo);
+}
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs,
+ nsINode* aCspToInheritLoadingContext, nsILoadInfo** outLoadInfo) {
+ RefPtr<LoadInfo> loadInfo;
+ nsresult rv =
+ LoadInfoArgsToLoadInfo(aOptionalLoadInfoArgs, aCspToInheritLoadingContext,
+ getter_AddRefs(loadInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ loadInfo.forget(outLoadInfo);
+ return NS_OK;
+}
+
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs, LoadInfo** outLoadInfo) {
+ return LoadInfoArgsToLoadInfo(aOptionalLoadInfoArgs, nullptr, outLoadInfo);
+}
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs,
+ 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)) {
+ nsCOMPtr<nsIPrincipal> originalTriggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> originalPrincipalToInherit;
+ Tie(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> sandboxedLoadingPrincipal;
+ if (loadInfoArgs.sandboxedLoadingPrincipalInfo().isSome()) {
+ auto sandboxedLoadingPrincipalOrErr = PrincipalInfoToPrincipal(
+ loadInfoArgs.sandboxedLoadingPrincipalInfo().ref());
+ if (NS_WARN_IF(sandboxedLoadingPrincipalOrErr.isErr())) {
+ return sandboxedLoadingPrincipalOrErr.unwrapErr();
+ }
+ sandboxedLoadingPrincipal = sandboxedLoadingPrincipalOrErr.unwrap();
+ }
+
+ nsresult rv = NS_OK;
+ 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<nsIPrincipal> topLevelStorageAreaPrincipal;
+ if (loadInfoArgs.topLevelStorageAreaPrincipalInfo().isSome()) {
+ auto topLevelStorageAreaPrincipalOrErr = PrincipalInfoToPrincipal(
+ loadInfoArgs.topLevelStorageAreaPrincipalInfo().ref());
+ if (NS_WARN_IF(topLevelStorageAreaPrincipalOrErr.isErr())) {
+ return topLevelStorageAreaPrincipalOrErr.unwrapErr();
+ }
+ topLevelStorageAreaPrincipal = topLevelStorageAreaPrincipalOrErr.unwrap();
+ }
+
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+ if (loadInfoArgs.resultPrincipalURI().isSome()) {
+ resultPrincipalURI = DeserializeURI(loadInfoArgs.resultPrincipalURI());
+ NS_ENSURE_TRUE(resultPrincipalURI, NS_ERROR_UNEXPECTED);
+ }
+
+ RedirectHistoryArray redirectChainIncludingInternalRedirects;
+ for (const RedirectHistoryEntryInfo& entryInfo :
+ loadInfoArgs.redirectChainIncludingInternalRedirects()) {
+ nsCOMPtr<nsIRedirectHistoryEntry> redirectHistoryEntry =
+ RHEntryInfoToRHEntry(entryInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ redirectChainIncludingInternalRedirects.AppendElement(
+ redirectHistoryEntry.forget());
+ }
+
+ RedirectHistoryArray redirectChain;
+ for (const RedirectHistoryEntryInfo& entryInfo :
+ loadInfoArgs.redirectChain()) {
+ nsCOMPtr<nsIRedirectHistoryEntry> redirectHistoryEntry =
+ RHEntryInfoToRHEntry(entryInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ 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();
+ }
+
+ RefPtr<mozilla::LoadInfo> loadInfo = new mozilla::LoadInfo(
+ loadingPrincipal, triggeringPrincipal, principalToInherit,
+ sandboxedLoadingPrincipal, topLevelPrincipal,
+ topLevelStorageAreaPrincipal, resultPrincipalURI, cookieJarSettings,
+ cspToInherit, 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.bypassCORSChecks(),
+ loadInfoArgs.skipContentPolicyCheckForWebRequest(),
+ loadInfoArgs.originalFrameSrcLoad(),
+ loadInfoArgs.forceInheritPrincipalDropped(), loadInfoArgs.innerWindowID(),
+ loadInfoArgs.browsingContextID(), loadInfoArgs.frameBrowsingContextID(),
+ loadInfoArgs.initialSecurityCheckDone(),
+ loadInfoArgs.isInThirdPartyContext(),
+ loadInfoArgs.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.cspNonce(), loadInfoArgs.skipContentSniffing(),
+ loadInfoArgs.httpsOnlyStatus(),
+ loadInfoArgs.hasValidUserGestureActivation(),
+ loadInfoArgs.allowDeprecatedSystemRequests(),
+ loadInfoArgs.isInDevToolsContext(), loadInfoArgs.parserCreatedScript(),
+ loadInfoArgs.hasStoragePermission(), loadInfoArgs.requestBlockingReason(),
+ loadingContext, loadInfoArgs.loadingEmbedderPolicy());
+
+ if (loadInfoArgs.isFromProcessingFrameAttributes()) {
+ loadInfo->SetIsFromProcessingFrameAttributes();
+ }
+
+ 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);
+ }
+
+ *aForwarderArgsOut = ParentLoadInfoForwarderArgs(
+ aLoadInfo->GetAllowInsecureRedirectToDataURI(),
+ aLoadInfo->GetBypassCORSChecks(), ipcController, tainting,
+ aLoadInfo->GetSkipContentSniffing(), aLoadInfo->GetHttpsOnlyStatus(),
+ aLoadInfo->GetHasValidUserGestureActivation(),
+ aLoadInfo->GetAllowDeprecatedSystemRequests(),
+ aLoadInfo->GetIsInDevToolsContext(), aLoadInfo->GetParserCreatedScript(),
+ aLoadInfo->GetTriggeringSandboxFlags(),
+ aLoadInfo->GetServiceWorkerTaintingSynthesized(),
+ aLoadInfo->GetDocumentHasUserInteracted(),
+ aLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain(),
+ cookieJarSettingsArgs, aLoadInfo->GetRequestBlockingReason(),
+ aLoadInfo->GetHasStoragePermission(),
+ aLoadInfo->GetIsThirdPartyContextToTopWindow(),
+ aLoadInfo->GetIsInThirdPartyContext());
+}
+
+nsresult MergeParentLoadInfoForwarder(
+ ParentLoadInfoForwarderArgs const& aForwarderArgs, nsILoadInfo* aLoadInfo) {
+ nsresult rv;
+
+ rv = aLoadInfo->SetAllowInsecureRedirectToDataURI(
+ aForwarderArgs.allowInsecureRedirectToDataURI());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetBypassCORSChecks(aForwarderArgs.bypassCORSChecks());
+ 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->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->SetHasStoragePermission(aForwarderArgs.hasStoragePermission());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetIsThirdPartyContextToTopWindow(
+ aForwarderArgs.isThirdPartyContextToTopWindow());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadInfo->SetIsInThirdPartyContext(
+ aForwarderArgs.isInThirdPartyContext());
+ 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..8a9c4e2aa0
--- /dev/null
+++ b/ipc/glue/BackgroundUtils.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_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(Message* aMsg, const paramType& aParam) {
+ nsAutoCString suffix;
+ aParam.CreateSuffix(suffix);
+ WriteParam(aMsg, suffix);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ nsAutoCString suffix;
+ return ReadParam(aMsg, aIter, &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);
+
+/**
+ * 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,
+ nsILoadInfo** outLoadInfo);
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<mozilla::net::LoadInfoArgs>& aOptionalLoadInfoArgs,
+ nsINode* aCspToInheritLoadingContext, nsILoadInfo** outLoadInfo);
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<net::LoadInfoArgs>& aOptionalLoadInfoArgs,
+ mozilla::net::LoadInfo** outLoadInfo);
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<net::LoadInfoArgs>& aOptionalLoadInfoArgs,
+ 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/BrowserProcessSubThread.cpp b/ipc/glue/BrowserProcessSubThread.cpp
new file mode 100644
index 0000000000..2a7947da9e
--- /dev/null
+++ b/ipc/glue/BrowserProcessSubThread.cpp
@@ -0,0 +1,73 @@
+/* -*- 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"
+
+#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
+}
+
+void BrowserProcessSubThread::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..03e56f617f
--- /dev/null
+++ b/ipc/glue/BrowserProcessSubThread.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_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];
+};
+
+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..4ddca81210
--- /dev/null
+++ b/ipc/glue/ByteBufUtils.h
@@ -0,0 +1,52 @@
+/* -*- 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 "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(Message* aMsg, paramType&& aParam) {
+ WriteParam(aMsg, aParam.mLen);
+ // hand over ownership of the buffer to the Message
+ aMsg->WriteBytesZeroCopy(aParam.mData, aParam.mLen, aParam.mCapacity);
+ aParam.mData = nullptr;
+ aParam.mCapacity = 0;
+ aParam.mLen = 0;
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ // We make a copy from the BufferList so that we get a contigous result.
+ // For users the can handle a non-contiguous result using ExtractBuffers
+ // is an option, alternatively if the users don't need to take ownership of
+ // the data they can use the removed FlattenBytes (bug 1297981)
+ size_t length;
+ return ReadParam(aMsg, aIter, &length) && aResult->Allocate(length) &&
+ aMsg->ReadBytesInto(aIter, aResult->mData, length);
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ aLog->append(L"(byte buf)");
+ }
+};
+
+} // 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..02dbfc7e90
--- /dev/null
+++ b/ipc/glue/CrashReporterClient.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_CrashReporterClient_h
+#define mozilla_ipc_CrashReporterClient_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Unused.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;
+};
+
+} // 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..e749ef9a2c
--- /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 = %d 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..8c2581486a
--- /dev/null
+++ b/ipc/glue/CrashReporterHost.cpp
@@ -0,0 +1,241 @@
+/* -*- 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 "nsICrashService.h"
+#include "nsXULAppAPI.h"
+
+// Consistency checking for nsICrashService constants. We depend on the
+// equivalence between nsICrashService values and GeckoProcessType values
+// in the code below. Making them equal also ensures that if new process
+// types are added, people will know they may need to add crash reporting
+// support in various places because compilation errors will be triggered here.
+static_assert(nsICrashService::PROCESS_TYPE_MAIN ==
+ (int)GeckoProcessType_Default,
+ "GeckoProcessType enum is out of sync with nsICrashService!");
+static_assert(nsICrashService::PROCESS_TYPE_PLUGIN ==
+ (int)GeckoProcessType_Plugin,
+ "GeckoProcessType enum is out of sync with nsICrashService!");
+static_assert(nsICrashService::PROCESS_TYPE_CONTENT ==
+ (int)GeckoProcessType_Content,
+ "GeckoProcessType enum is out of sync with nsICrashService!");
+static_assert(nsICrashService::PROCESS_TYPE_IPDLUNITTEST ==
+ (int)GeckoProcessType_IPDLUnitTest,
+ "GeckoProcessType enum is out of sync with nsICrashService!");
+static_assert(nsICrashService::PROCESS_TYPE_GMPLUGIN ==
+ (int)GeckoProcessType_GMPlugin,
+ "GeckoProcessType enum is out of sync with nsICrashService!");
+static_assert(nsICrashService::PROCESS_TYPE_GPU == (int)GeckoProcessType_GPU,
+ "GeckoProcessType enum is out of sync with nsICrashService!");
+static_assert(nsICrashService::PROCESS_TYPE_VR == (int)GeckoProcessType_VR,
+ "GeckoProcessType enum is out of sync with nsICrashService!");
+static_assert(nsICrashService::PROCESS_TYPE_RDD == (int)GeckoProcessType_RDD,
+ "GeckoProcessType enum is out of sync with nsICrashService!");
+static_assert(nsICrashService::PROCESS_TYPE_SOCKET ==
+ (int)GeckoProcessType_Socket,
+ "GeckoProcessType enum is out of sync with nsICrashService!");
+static_assert(nsICrashService::PROCESS_TYPE_SANDBOX_BROKER ==
+ (int)GeckoProcessType_RemoteSandboxBroker,
+ "GeckoProcessType enum is out of sync with nsICrashService!");
+static_assert(nsICrashService::PROCESS_TYPE_FORKSERVER ==
+ (int)GeckoProcessType_ForkServer,
+ "GeckoProcessType enum is out of sync with nsICrashService!");
+// Add new static asserts here if you add more process types.
+// Update this static assert as well.
+static_assert(nsICrashService::PROCESS_TYPE_FORKSERVER + 1 ==
+ (int)GeckoProcessType_End,
+ "GeckoProcessType enum is out of sync with nsICrashService!");
+
+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;
+ }
+ return FinalizeCrashReport();
+}
+
+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;
+}
+
+int32_t CrashReporterHost::GetCrashType() {
+ if (mExtraAnnotations[CrashReporter::Annotation::PluginHang].EqualsLiteral(
+ "1")) {
+ return nsICrashService::CRASH_TYPE_HANG;
+ }
+
+ return nsICrashService::CRASH_TYPE_CRASH;
+}
+
+bool 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);
+
+ RecordCrash(mProcessType, GetCrashType(), mDumpID);
+
+ mFinalized = true;
+ return true;
+}
+
+/* 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;
+
+ if (aProcessType == GeckoProcessType_Plugin &&
+ aCrashType == nsICrashService::CRASH_TYPE_HANG) {
+ key.AssignLiteral("pluginhang");
+ } else {
+ switch (aProcessType) {
+#define GECKO_PROCESS_TYPE(enum_name, string_name, xre_name, bin_type) \
+ 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<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;
+}
+
+bool CrashReporterHost::IsLikelyOOM() {
+ // The data is only populated during the call to `FinalizeCrashReport()`.
+ MOZ_ASSERT(mFinalized);
+
+ // If `OOMAllocationSize` was set, we know that the crash happened
+ // because an allocation failed (`malloc` returned `nullptr`).
+ //
+ // As Unix systems generally allow `malloc` to return a non-null value
+ // even when no virtual memory is available, this doesn't cover all
+ // cases of OOM under Unix (far from it).
+ return mExtraAnnotations[CrashReporter::Annotation::OOMAllocationSize]
+ .Length() > 0;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/CrashReporterHost.h b/ipc/glue/CrashReporterHost.h
new file mode 100644
index 0000000000..77182c0ba5
--- /dev/null
+++ b/ipc/glue/CrashReporterHost.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_CrashReporterHost_h
+#define mozilla_ipc_CrashReporterHost_h
+
+#include <functional>
+
+#include "mozilla/UniquePtr.h"
+#include "base/process.h"
+#include "nsExceptionHandler.h"
+#include "nsThreadUtils.h"
+#include "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.
+ bool FinalizeCrashReport();
+
+ // 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,
+ nsIFile* aMinidumpToPair,
+ 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, aMinidumpToPair,
+ 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];
+ }
+
+ // Return `true` if this crash reporter has been identified as a likely OOM.
+ //
+ // At the time of this writing, OOMs detection is considered reliable under
+ // Windows but other platforms quite often return false negatives.
+ //
+ // `CrashReporterHost::FinalizeCrashReport()` MUST have been called already.
+ bool IsLikelyOOM();
+
+ // 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..0ce23f4e28
--- /dev/null
+++ b/ipc/glue/CrossProcessMutex.h
@@ -0,0 +1,115 @@
+/* -*- 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) && !defined(OS_NETBSD) && !defined(OS_OPENBSD)
+# include <pthread.h>
+# include "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 HANDLE 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();
+
+ /**
+ * ShareToProcess
+ * 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 ShareToProcess(base::ProcessId aTargetPid);
+
+ 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..f7f5c69536
--- /dev/null
+++ b/ipc/glue/CrossProcessMutex_posix.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 "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(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::ShareToProcess(
+ base::ProcessId aTargetPid) {
+ CrossProcessMutexHandle result = ipc::SharedMemoryBasic::NULLHandle();
+
+ if (mSharedBuffer && !mSharedBuffer->ShareToProcess(aTargetPid, &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..165c3b256a
--- /dev/null
+++ b/ipc/glue/CrossProcessMutex_unimplemented.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 "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::ShareToProcess(
+ base::ProcessId aTargetPid) {
+ 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..b5ed066fde
--- /dev/null
+++ b/ipc/glue/CrossProcessMutex_windows.cpp
@@ -0,0 +1,69 @@
+/* -*- 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, &flags)) {
+ MOZ_CRASH("Attempt to construct a mutex from an invalid handle!");
+ }
+ mMutex = aHandle;
+ 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::ShareToProcess(
+ base::ProcessId aTargetPid) {
+ HANDLE newHandle;
+ bool succeeded = ipc::DuplicateHandle(mMutex, aTargetPid, &newHandle, 0,
+ DUPLICATE_SAME_ACCESS);
+
+ if (!succeeded) {
+ return nullptr;
+ }
+
+ return newHandle;
+}
+
+} // namespace mozilla
diff --git a/ipc/glue/CrossProcessSemaphore.h b/ipc/glue/CrossProcessSemaphore.h
new file mode 100644
index 0000000000..1480946d7d
--- /dev/null
+++ b/ipc/glue/CrossProcessSemaphore.h
@@ -0,0 +1,115 @@
+/* -*- 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) && !defined(OS_MACOSX)
+# include <pthread.h>
+# include <semaphore.h>
+# include "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 HANDLE 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();
+
+ /**
+ * ShareToProcess
+ * 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 ShareToProcess(base::ProcessId aTargetPid);
+
+ 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..8c28d897fa
--- /dev/null
+++ b/ipc/glue/CrossProcessSemaphore_posix.cpp
@@ -0,0 +1,161 @@
+/* -*- 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(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::ShareToProcess(
+ base::ProcessId aTargetPid) {
+ CrossProcessSemaphoreHandle result = ipc::SharedMemoryBasic::NULLHandle();
+
+ if (mSharedBuffer && !mSharedBuffer->ShareToProcess(aTargetPid, &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..c8b88d5fbd
--- /dev/null
+++ b/ipc/glue/CrossProcessSemaphore_unimplemented.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 "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::ShareToProcess(
+ base::ProcessId aTargetPid) {
+ 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..66da54be61
--- /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, &flags)) {
+ return nullptr;
+ }
+
+ return new CrossProcessSemaphore(aHandle);
+}
+
+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::ShareToProcess(
+ base::ProcessId aTargetPid) {
+ HANDLE newHandle;
+ bool succeeded = ipc::DuplicateHandle(mSemaphore, aTargetPid, &newHandle, 0,
+ DUPLICATE_SAME_ACCESS);
+
+ if (!succeeded) {
+ return nullptr;
+ }
+
+ return newHandle;
+}
+
+void CrossProcessSemaphore::CloseHandle() {}
+
+} // namespace mozilla
diff --git a/ipc/glue/Endpoint.h b/ipc/glue/Endpoint.h
new file mode 100644
index 0000000000..4a6a214d74
--- /dev/null
+++ b/ipc/glue/Endpoint.h
@@ -0,0 +1,239 @@
+/* -*- 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/Transport.h"
+#include "nsXULAppAPI.h"
+#include "nscore.h"
+
+namespace IPC {
+template <class P>
+struct ParamTraits;
+}
+
+namespace mozilla {
+namespace ipc {
+
+struct PrivateIPDLInterface {};
+
+/**
+ * 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(parentPid, childPid, &parentEp, &childEp);
+ *
+ * You're required to pass in parentPid and childPid, which are the pids of the
+ * processes in which the parent and child endpoints will be used.
+ *
+ * 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.
+ */
+template <class PFooSide>
+class Endpoint {
+ public:
+ typedef base::ProcessId ProcessId;
+
+ Endpoint()
+ : mValid(false),
+ mMode(static_cast<mozilla::ipc::Transport::Mode>(0)),
+ mMyPid(0),
+ mOtherPid(0) {}
+
+ Endpoint(const PrivateIPDLInterface&, mozilla::ipc::Transport::Mode aMode,
+ TransportDescriptor aTransport, ProcessId aMyPid,
+ ProcessId aOtherPid)
+ : mValid(true),
+ mMode(aMode),
+ mTransport(aTransport),
+ mMyPid(aMyPid),
+ mOtherPid(aOtherPid) {}
+
+ Endpoint(Endpoint&& aOther)
+ : mValid(aOther.mValid),
+ mTransport(aOther.mTransport),
+ mMyPid(aOther.mMyPid),
+ mOtherPid(aOther.mOtherPid) {
+ if (aOther.mValid) {
+ mMode = aOther.mMode;
+ }
+ aOther.mValid = false;
+ }
+
+ Endpoint& operator=(Endpoint&& aOther) {
+ mValid = aOther.mValid;
+ if (aOther.mValid) {
+ mMode = aOther.mMode;
+ }
+ mTransport = aOther.mTransport;
+ mMyPid = aOther.mMyPid;
+ mOtherPid = aOther.mOtherPid;
+
+ aOther.mValid = false;
+ return *this;
+ }
+
+ ~Endpoint() {
+ if (mValid) {
+ CloseDescriptor(mTransport);
+ }
+ }
+
+ ProcessId OtherPid() const { 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.
+ bool Bind(PFooSide* aActor) {
+ MOZ_RELEASE_ASSERT(mValid);
+ MOZ_RELEASE_ASSERT(mMyPid == base::GetCurrentProcId());
+
+ UniquePtr<Transport> transport =
+ mozilla::ipc::OpenDescriptor(mTransport, mMode);
+ if (!transport) {
+ return false;
+ }
+ if (!aActor->Open(
+ std::move(transport), mOtherPid, XRE_GetIOMessageLoop(),
+ mMode == Transport::MODE_SERVER ? ParentSide : ChildSide)) {
+ return false;
+ }
+ mValid = false;
+ return true;
+ }
+
+ bool IsValid() const { return mValid; }
+
+ private:
+ friend struct IPC::ParamTraits<Endpoint<PFooSide>>;
+
+ Endpoint(const Endpoint&) = delete;
+ Endpoint& operator=(const Endpoint&) = delete;
+
+ bool mValid;
+ mozilla::ipc::Transport::Mode mMode;
+ TransportDescriptor mTransport;
+ ProcessId mMyPid, mOtherPid;
+};
+
+#if defined(XP_MACOSX)
+void AnnotateCrashReportWithErrno(CrashReporter::Annotation tag, int error);
+#else
+static 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,
+ base::ProcessId aParentDestPid,
+ base::ProcessId aChildDestPid,
+ Endpoint<PFooParent>* aParentEndpoint,
+ Endpoint<PFooChild>* aChildEndpoint) {
+ MOZ_RELEASE_ASSERT(aParentDestPid);
+ MOZ_RELEASE_ASSERT(aChildDestPid);
+
+ TransportDescriptor parentTransport, childTransport;
+ nsresult rv;
+ if (NS_FAILED(rv = CreateTransport(aParentDestPid, &parentTransport,
+ &childTransport))) {
+ AnnotateCrashReportWithErrno(
+ CrashReporter::Annotation::IpcCreateEndpointsNsresult, int(rv));
+ return rv;
+ }
+
+ *aParentEndpoint =
+ Endpoint<PFooParent>(aPrivate, mozilla::ipc::Transport::MODE_SERVER,
+ parentTransport, aParentDestPid, aChildDestPid);
+
+ *aChildEndpoint =
+ Endpoint<PFooChild>(aPrivate, mozilla::ipc::Transport::MODE_CLIENT,
+ childTransport, aChildDestPid, aParentDestPid);
+
+ return NS_OK;
+}
+
+/**
+ * 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:
+ ManagedEndpoint() : mId(0) {}
+
+ ManagedEndpoint(const PrivateIPDLInterface&, int32_t aId) : mId(aId) {}
+
+ ManagedEndpoint(ManagedEndpoint&& aOther) : mId(aOther.mId) {
+ aOther.mId = 0;
+ }
+
+ ManagedEndpoint& operator=(ManagedEndpoint&& aOther) {
+ mId = aOther.mId;
+ aOther.mId = 0;
+ return *this;
+ }
+
+ bool IsValid() const { return mId != 0; }
+
+ Maybe<int32_t> ActorId() const { return IsValid() ? Some(mId) : Nothing(); }
+
+ bool operator==(const ManagedEndpoint& _o) const { return mId == _o.mId; }
+
+ private:
+ friend struct IPC::ParamTraits<ManagedEndpoint<PFooSide>>;
+
+ ManagedEndpoint(const ManagedEndpoint&) = delete;
+ ManagedEndpoint& operator=(const ManagedEndpoint&) = delete;
+
+ // The routing ID for the to-be-created endpoint.
+ int32_t mId;
+
+ // XXX(nika): Might be nice to have other info for assertions?
+ // e.g. mManagerId, mManagerType, etc.
+};
+
+} // 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..661acd7244
--- /dev/null
+++ b/ipc/glue/EnumSerializer.h
@@ -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/. */
+
+#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;
+}
+
+#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;
+ typedef typename mozilla::UnsignedStdintTypeForSize<sizeof(paramType)>::Type
+ uintParamType;
+
+ static void Write(Message* aMsg, const paramType& aValue) {
+ MOZ_RELEASE_ASSERT(EnumValidator::IsLegalValue(aValue));
+ WriteParam(aMsg, uintParamType(aValue));
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ uintParamType value;
+ if (!ReadParam(aMsg, aIter, &value)) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCReadErrorReason, "Bad iter"_ns);
+ return false;
+ } else if (!EnumValidator::IsLegalValue(paramType(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:
+ static bool IsLegalValue(E e) {
+ return IsLessThanOrEqual(MinLegal, e) && e < HighBound;
+ }
+};
+
+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:
+ static bool IsLegalValue(E e) {
+ return IsLessThanOrEqual(MinLegal, e) && e <= MaxLegal;
+ }
+};
+
+template <typename E, E AllBits>
+struct BitFlagsEnumValidator {
+ static bool IsLegalValue(E e) { return (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..f411129de3
--- /dev/null
+++ b/ipc/glue/FileDescriptor.cpp
@@ -0,0 +1,163 @@
+/* -*- 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/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(const IPDLPrivate&, const PickleType& aPickle) {
+#ifdef XP_WIN
+ mHandle.reset(aPickle);
+#else
+ mHandle.reset(aPickle.fd);
+#endif
+}
+
+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;
+}
+
+FileDescriptor::PickleType FileDescriptor::ShareTo(
+ const FileDescriptor::IPDLPrivate&,
+ FileDescriptor::ProcessId aTargetPid) const {
+ PlatformHandleType newHandle;
+#ifdef XP_WIN
+ if (IsValid()) {
+ if (mozilla::ipc::DuplicateHandle(mHandle.get(), aTargetPid, &newHandle, 0,
+ DUPLICATE_SAME_ACCESS)) {
+ return newHandle;
+ }
+ NS_WARNING("Failed to duplicate file handle for other process!");
+ }
+ return INVALID_HANDLE_VALUE;
+#else // XP_WIN
+ if (IsValid()) {
+ newHandle = dup(mHandle.get());
+ if (newHandle >= 0) {
+ return base::FileDescriptor(newHandle, /* auto_close */ true);
+ }
+ NS_WARNING("Failed to duplicate file handle for other process!");
+ }
+ return base::FileDescriptor();
+#endif
+
+ MOZ_CRASH("Must not get here!");
+}
+
+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::Message* aMsg,
+ IProtocol* aActor,
+ const FileDescriptor& aParam) {
+#ifdef XP_WIN
+ FileDescriptor::PickleType pfd =
+ aParam.ShareTo(FileDescriptor::IPDLPrivate(), aActor->OtherPid());
+#else
+ // The pid returned by OtherPID() is only required for Windows to
+ // send file descriptors. For the use case of the fork server,
+ // aActor is always null. Since it is only for the special case of
+ // Windows, here we skip it for other platforms.
+ FileDescriptor::PickleType pfd =
+ aParam.ShareTo(FileDescriptor::IPDLPrivate(), 0);
+#endif
+ WriteIPDLParam(aMsg, aActor, pfd);
+}
+
+bool IPDLParamTraits<FileDescriptor>::Read(const IPC::Message* aMsg,
+ PickleIterator* aIter,
+ IProtocol* aActor,
+ FileDescriptor* aResult) {
+ FileDescriptor::PickleType pfd;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &pfd)) {
+ return false;
+ }
+
+ *aResult = FileDescriptor(FileDescriptor::IPDLPrivate(), pfd);
+ 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..0bc5ae569f
--- /dev/null
+++ b/ipc/glue/FileDescriptor.h
@@ -0,0 +1,102 @@
+/* -*- 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"
+
+#ifdef XP_UNIX
+# include "base/file_descriptor_posix.h"
+#endif
+
+namespace mozilla {
+namespace ipc {
+
+// This class is used by IPDL to share file descriptors across processes. When
+// sending a FileDescriptor IPDL will first duplicate a platform-specific file
+// handle type ('PlatformHandleType') into a handle that is valid in the other
+// process. Then IPDL will convert the duplicated handle into a type suitable
+// for pickling ('PickleType') and then send that through the IPC pipe. In the
+// receiving process the pickled data is converted into a platform-specific file
+// handle and then returned to the receiver.
+//
+// To use this class add 'FileDescriptor' as an argument in the IPDL protocol
+// and then pass a file descriptor from C++ to the Call/Send method. The
+// Answer/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;
+
+#ifdef XP_WIN
+ typedef PlatformHandleType PickleType;
+#else
+ typedef base::FileDescriptor PickleType;
+#endif
+
+ // 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);
+
+ // This constructor WILL NOT duplicate the handle.
+ // FileDescriptor takes the ownership from IPC message.
+ FileDescriptor(const IPDLPrivate&, const PickleType& aPickle);
+
+ ~FileDescriptor();
+
+ FileDescriptor& operator=(const FileDescriptor& aOther);
+
+ FileDescriptor& operator=(FileDescriptor&& aOther);
+
+ // Performs platform-specific actions to duplicate mHandle in the other
+ // process (e.g. dup() on POSIX, DuplicateHandle() on Windows). Returns a
+ // pickled value that can be passed to the other process via IPC.
+ PickleType ShareTo(const IPDLPrivate&, ProcessId aTargetPid) const;
+
+ // 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/FileDescriptorSetChild.cpp b/ipc/glue/FileDescriptorSetChild.cpp
new file mode 100644
index 0000000000..bc19d0d75a
--- /dev/null
+++ b/ipc/glue/FileDescriptorSetChild.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 "FileDescriptorSetChild.h"
+
+namespace mozilla::ipc {
+
+FileDescriptorSetChild::FileDescriptorSetChild(
+ const FileDescriptor& aFileDescriptor) {
+ mFileDescriptors.AppendElement(aFileDescriptor);
+}
+
+FileDescriptorSetChild::~FileDescriptorSetChild() {
+ MOZ_ASSERT(mFileDescriptors.IsEmpty());
+}
+
+void FileDescriptorSetChild::ForgetFileDescriptors(
+ nsTArray<FileDescriptor>& aFileDescriptors) {
+ aFileDescriptors = std::move(mFileDescriptors);
+}
+
+mozilla::ipc::IPCResult FileDescriptorSetChild::RecvAddFileDescriptor(
+ const FileDescriptor& aFileDescriptor) {
+ mFileDescriptors.AppendElement(aFileDescriptor);
+ return IPC_OK();
+}
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/FileDescriptorSetChild.h b/ipc/glue/FileDescriptorSetChild.h
new file mode 100644
index 0000000000..d3c8827751
--- /dev/null
+++ b/ipc/glue/FileDescriptorSetChild.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/. */
+
+#ifndef mozilla_ipc_FileDescriptorSetChild_h__
+#define mozilla_ipc_FileDescriptorSetChild_h__
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ipc/PFileDescriptorSetChild.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+namespace dom {
+
+class ContentChild;
+
+} // namespace dom
+
+namespace net {
+
+class SocketProcessChild;
+
+} // namespace net
+
+namespace ipc {
+
+class BackgroundChildImpl;
+class FileDescriptor;
+
+class FileDescriptorSetChild final : public PFileDescriptorSetChild {
+ friend class BackgroundChildImpl;
+ friend class mozilla::dom::ContentChild;
+ friend class mozilla::net::SocketProcessChild;
+ friend class PFileDescriptorSetChild;
+
+ nsTArray<FileDescriptor> mFileDescriptors;
+
+ public:
+ void ForgetFileDescriptors(nsTArray<FileDescriptor>& aFileDescriptors);
+
+ private:
+ explicit FileDescriptorSetChild(const FileDescriptor& aFileDescriptor);
+ ~FileDescriptorSetChild();
+
+ mozilla::ipc::IPCResult RecvAddFileDescriptor(
+ const FileDescriptor& aFileDescriptor);
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_FileDescriptorSetChild_h__
diff --git a/ipc/glue/FileDescriptorSetParent.cpp b/ipc/glue/FileDescriptorSetParent.cpp
new file mode 100644
index 0000000000..0cd7ea3ea3
--- /dev/null
+++ b/ipc/glue/FileDescriptorSetParent.cpp
@@ -0,0 +1,33 @@
+/* -*- 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 "FileDescriptorSetParent.h"
+
+namespace mozilla::ipc {
+
+FileDescriptorSetParent::FileDescriptorSetParent(
+ const FileDescriptor& aFileDescriptor) {
+ mFileDescriptors.AppendElement(aFileDescriptor);
+}
+
+FileDescriptorSetParent::~FileDescriptorSetParent() = default;
+
+void FileDescriptorSetParent::ForgetFileDescriptors(
+ nsTArray<FileDescriptor>& aFileDescriptors) {
+ aFileDescriptors = std::move(mFileDescriptors);
+}
+
+void FileDescriptorSetParent::ActorDestroy(ActorDestroyReason aWhy) {
+ // Implement me! Bug 1005157
+}
+
+mozilla::ipc::IPCResult FileDescriptorSetParent::RecvAddFileDescriptor(
+ const FileDescriptor& aFileDescriptor) {
+ mFileDescriptors.AppendElement(aFileDescriptor);
+ return IPC_OK();
+}
+
+} // namespace mozilla::ipc
diff --git a/ipc/glue/FileDescriptorSetParent.h b/ipc/glue/FileDescriptorSetParent.h
new file mode 100644
index 0000000000..0510f4982b
--- /dev/null
+++ b/ipc/glue/FileDescriptorSetParent.h
@@ -0,0 +1,58 @@
+/* -*- 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_FileDescriptorSetParent_h__
+#define mozilla_ipc_FileDescriptorSetParent_h__
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ipc/PFileDescriptorSetParent.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+namespace dom {
+
+class ContentParent;
+
+} // namespace dom
+
+namespace net {
+
+class SocketProcessParent;
+
+} // namespace net
+
+namespace ipc {
+
+class BackgroundParentImpl;
+class FileDescriptor;
+
+class FileDescriptorSetParent final : public PFileDescriptorSetParent {
+ friend class BackgroundParentImpl;
+ friend class mozilla::dom::ContentParent;
+ friend class mozilla::net::SocketProcessParent;
+ friend class PFileDescriptorSetParent;
+
+ nsTArray<FileDescriptor> mFileDescriptors;
+
+ public:
+ void ForgetFileDescriptors(nsTArray<FileDescriptor>& aFileDescriptors);
+
+ private:
+ explicit FileDescriptorSetParent(const FileDescriptor& aFileDescriptor);
+ ~FileDescriptorSetParent();
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvAddFileDescriptor(
+ const FileDescriptor& aFileDescriptor);
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_FileDescriptorSetParent_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..049353544d
--- /dev/null
+++ b/ipc/glue/ForkServer.cpp
@@ -0,0 +1,304 @@
+/* -*- 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 "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 {
+
+static const int sClientFd = 3;
+
+LazyLogModule gForkServiceLog("ForkService");
+
+ForkServer::ForkServer() {}
+
+/**
+ * Prepare an environment for running a fork server.
+ */
+void ForkServer::InitProcess(int* aArgc, char*** aArgv) {
+ base::InitForkServerProcess();
+
+ int fd = sClientFd;
+ int fd_flags = fcntl(sClientFd, F_GETFL, 0);
+ fcntl(fd, F_SETFL, fd_flags & ~O_NONBLOCK);
+ mTcver = MakeUnique<MiniTransceiver>(fd, DataBufferClear::AfterReceiving);
+}
+
+/**
+ * Start providing the service at the IPC channel.
+ */
+bool ForkServer::HandleMessages() {
+ // |sClientFd| is created by an instance of |IPC::Channel|.
+ // It sends a HELLO automatically.
+ IPC::Message hello;
+ mTcver->RecvInfallible(
+ hello, "Expect to receive a HELLO message from the parent process!");
+ MOZ_ASSERT(hello.type() == kHELLO_MESSAGE_TYPE);
+
+ // Send it back
+ mTcver->SendInfallible(hello, "Fail to ack the received HELLO!");
+
+ while (true) {
+ 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 = Get<0>(elt);
+ nsCString& val = 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 = Get<0>(elt).ClonePlatformHandle().release();
+ std::pair<int, int> fdmap(fd, Get<1>(elt));
+ aOptions->fds_to_remap.push_back(fdmap);
+ MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
+ ("\t%d => %d", fdmap.first, fdmap.second));
+ }
+}
+
+/**
+ * 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;
+ }
+
+ PickleIterator iter(aMsg);
+ nsTArray<nsCString> argv_array;
+ nsTArray<EnvVar> env_map;
+ nsTArray<FdMapping> fds_remap;
+
+ ReadIPDLParamInfallible(&aMsg, &iter, nullptr, &argv_array,
+ "Error deserializing 'nsCString[]'");
+ ReadIPDLParamInfallible(&aMsg, &iter, nullptr, &env_map,
+ "Error deserializing 'EnvVar[]'");
+ ReadIPDLParamInfallible(&aMsg, &iter, nullptr, &fds_remap,
+ "Error deserializing 'FdMapping[]'");
+ aMsg.EndRead(iter, aMsg.type());
+
+ 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(IPC::Message&& message) {
+ IPC::Message msg(std::move(message));
+
+ std::vector<std::string> argv;
+ base::LaunchOptions options;
+ if (!ParseForkNewSubprocess(msg, 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);
+ WriteIPDLParam(&reply, 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(msg, 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..e01e298f89
--- /dev/null
+++ b/ipc/glue/ForkServer.h
@@ -0,0 +1,44 @@
+/* -*- 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:
+ static const int kHELLO_MESSAGE_TYPE = 65535;
+
+ ForkServer();
+ ~ForkServer(){};
+
+ void InitProcess(int* aArgc, char*** aArgv);
+ bool HandleMessages();
+
+ // Called when a message is received.
+ void OnMessageReceived(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..a808e7389f
--- /dev/null
+++ b/ipc/glue/ForkServiceChild.cpp
@@ -0,0 +1,182 @@
+/* -*- 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 <unistd.h>
+#include <fcntl.h>
+
+namespace mozilla {
+namespace ipc {
+
+extern LazyLogModule gForkServiceLog;
+
+mozilla::UniquePtr<ForkServiceChild> ForkServiceChild::sForkServiceChild;
+
+void ForkServiceChild::StartForkServer() {
+ std::vector<std::string> extraArgs;
+
+ GeckoChildProcessHost* subprocess =
+ new GeckoChildProcessHost(GeckoProcessType_ForkServer, false);
+ subprocess->LaunchAndWaitForProcessHandle(std::move(extraArgs));
+
+ int fd = subprocess->GetChannel()->GetFileDescriptor();
+ fd = dup(fd); // Dup it because the channel will close it.
+ int fs_flags = fcntl(fd, F_GETFL, 0);
+ fcntl(fd, F_SETFL, fs_flags & ~O_NONBLOCK);
+ int fd_flags = fcntl(fd, F_GETFD, 0);
+ fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC);
+
+ sForkServiceChild = mozilla::MakeUnique<ForkServiceChild>(fd, subprocess);
+
+ // Without doing this, IO thread may intercept messages since the
+ // IPC::Channel created by it is still open.
+ subprocess->GetChannel()->Close();
+}
+
+void ForkServiceChild::StopForkServer() { sForkServiceChild = nullptr; }
+
+ForkServiceChild::ForkServiceChild(int aFd, GeckoChildProcessHost* aProcess)
+ : mWaitForHello(true), mFailed(false), mProcess(aProcess) {
+ mTcver = MakeUnique<MiniTransceiver>(aFd);
+}
+
+ForkServiceChild::~ForkServiceChild() {
+ mProcess->Destroy();
+ close(mTcver->GetFD());
+}
+
+bool ForkServiceChild::SendForkNewSubprocess(
+ const nsTArray<nsCString>& aArgv, const nsTArray<EnvVar>& aEnvMap,
+ const nsTArray<FdMapping>& aFdsRemap, pid_t* aPid) {
+ if (mWaitForHello) {
+ // IPC::Channel created by the GeckoChildProcessHost has
+ // already send a HELLO. It is expected to receive a hello
+ // message from the fork server too.
+ IPC::Message hello;
+ mTcver->RecvInfallible(hello, "Fail to receive HELLO message");
+ MOZ_ASSERT(hello.type() == ForkServer::kHELLO_MESSAGE_TYPE);
+ mWaitForHello = false;
+ }
+
+ mRecvPid = -1;
+ IPC::Message msg(MSG_ROUTING_CONTROL, Msg_ForkNewSubprocess__ID);
+
+ WriteIPDLParam(&msg, nullptr, aArgv);
+ WriteIPDLParam(&msg, nullptr, aEnvMap);
+ WriteIPDLParam(&msg, nullptr, aFdsRemap);
+ if (!mTcver->Send(msg)) {
+ MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
+ ("the pipe to the fork server is closed or having errors"));
+ OnError();
+ return false;
+ }
+
+ 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 false;
+ }
+ OnMessageReceived(std::move(reply));
+
+ MOZ_ASSERT(mRecvPid != -1);
+ *aPid = mRecvPid;
+ return true;
+}
+
+void ForkServiceChild::OnMessageReceived(IPC::Message&& message) {
+ if (message.type() != Reply_ForkNewSubprocess__ID) {
+ MOZ_LOG(gForkServiceLog, LogLevel::Verbose,
+ ("unknown reply type %d", message.type()));
+ return;
+ }
+ PickleIterator iter__(message);
+
+ if (!ReadIPDLParam(&message, &iter__, nullptr, &mRecvPid)) {
+ MOZ_CRASH("Error deserializing 'pid_t'");
+ }
+ message.EndRead(iter__, message.type());
+}
+
+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..d257d5e4dc
--- /dev/null
+++ b/ipc/glue/ForkServiceChild.h
@@ -0,0 +1,103 @@
+/* -*- 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 "nsIObserver.h"
+#include "nsString.h"
+#include "mozilla/ipc/MiniTransceiver.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.
+ */
+ bool 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(IPC::Message&& message);
+ void OnError();
+
+ UniquePtr<MiniTransceiver> mTcver;
+ static UniquePtr<ForkServiceChild> sForkServiceChild;
+ pid_t mRecvPid;
+ bool mWaitForHello;
+ 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..14b78072e3
--- /dev/null
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -0,0 +1,1814 @@
+/* -*- 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/string_util.h"
+#include "base/task.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/process_watcher.h"
+#ifdef MOZ_WIDGET_COCOA
+# 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"
+
+#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/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/ipc/BrowserProcessSubThread.h"
+#include "mozilla/ipc/EnvironmentMap.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"
+#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 "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsTArray.h"
+#include "nscore.h" // for NS_FREE_PERMANENT_DATA
+#include "private/pprio.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 {
+
+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),
+ 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, "%d", base::GetCurrentProcId());
+
+ // Compute the serial event target we'll use for launching.
+ nsCOMPtr<nsIEventTarget> threadOrPool = GetIPCLauncher();
+ mLaunchThread = new TaskQueue(threadOrPool.forget());
+
+ 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);
+
+ 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 DoSetup();
+ virtual RefPtr<ProcessHandlePromise> DoLaunch() = 0;
+ virtual bool DoFinishLaunch() { return true; };
+
+ 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;
+ 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;
+ 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];
+
+ // Set during launch.
+ IPC::Channel* mChannel = nullptr;
+ 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)),
+ mProfileDir(aHost->mProfileDir),
+ mCachedNtdllThunk(aHost->sCachedNtDllThunk) {}
+
+ protected:
+ virtual bool DoSetup() override;
+ virtual RefPtr<ProcessHandlePromise> DoLaunch() override;
+ virtual bool DoFinishLaunch() override;
+
+ mozilla::Maybe<CommandLine> mCmdLine;
+ bool mUseSandbox = false;
+
+ nsCOMPtr<nsIFile> mProfileDir;
+
+ const StaticAutoPtr<Buffer<IMAGE_THUNK_DATA>>& mCachedNtdllThunk;
+};
+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) {}
+
+ protected:
+ virtual bool DoSetup() override;
+ virtual RefPtr<ProcessHandlePromise> DoLaunch() override;
+ virtual bool DoFinishLaunch() override;
+
+ nsCOMPtr<nsIFile> mProfileDir;
+
+ std::vector<std::string> mChildArgv;
+};
+
+# 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()))),
+ mParentRecvPort(mMachConnectionName.c_str()) {}
+
+ protected:
+ virtual bool 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.
+ ReceivePort 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 bool 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;
+
+#ifdef XP_WIN
+mozilla::StaticAutoPtr<Buffer<IMAGE_THUNK_DATA>>
+ GeckoChildProcessHost::sCachedNtDllThunk;
+
+// This static method initializes sCachedNtDllThunk. Because it's called in
+// XREMain::XRE_main, which happens long before WindowsProcessLauncher's ctor
+// accesses sCachedNtDllThunk, there is no race on sCachedNtDllThunk, thus
+// no mutex is needed.
+/* static */
+void GeckoChildProcessHost::CacheNtDllThunk() {
+ if (sCachedNtDllThunk) {
+ return;
+ }
+
+ do {
+ nt::PEHeaders ourExeImage(::GetModuleHandleW(nullptr));
+ if (!ourExeImage) {
+ break;
+ }
+
+ nt::PEHeaders ntdllImage(::GetModuleHandleW(L"ntdll.dll"));
+ if (!ntdllImage) {
+ break;
+ }
+
+ Maybe<Range<const uint8_t>> ntdllBoundaries = ntdllImage.GetBounds();
+ if (!ntdllBoundaries) {
+ break;
+ }
+
+ Maybe<Span<IMAGE_THUNK_DATA>> maybeNtDllThunks =
+ ourExeImage.GetIATThunksForModule("ntdll.dll", ntdllBoundaries.ptr());
+ if (maybeNtDllThunks.isNothing()) {
+ break;
+ }
+
+ sCachedNtDllThunk = new Buffer<IMAGE_THUNK_DATA>(maybeNtDllThunks.value());
+ return;
+ } while (false);
+
+ // Failed to cache IAT. Initializing the variable with nullptr.
+ sCachedNtDllThunk = new Buffer<IMAGE_THUNK_DATA>();
+}
+#endif
+
+GeckoChildProcessHost::GeckoChildProcessHost(GeckoProcessType aProcessType,
+ bool aIsFileContent)
+ : mProcessType(aProcessType),
+ mIsFileContent(aIsFileContent),
+ mMonitor("mozilla.ipc.GeckChildProcessHost.mMonitor"),
+ mLaunchOptions(MakeUnique<base::LaunchOptions>()),
+ mProcessState(CREATING_CHANNEL),
+#ifdef XP_WIN
+ mGroupId(u"-"),
+#endif
+#if defined(MOZ_SANDBOX) && defined(XP_WIN)
+ mEnableSandboxLogging(false),
+ mSandboxLevel(0),
+#endif
+ 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)
+ // The content process needs the content temp dir:
+ if (aProcessType == GeckoProcessType_Content) {
+ nsCOMPtr<nsIFile> contentTempDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR,
+ getter_AddRefs(contentTempDir));
+ if (NS_SUCCEEDED(rv)) {
+ contentTempDir->GetNativePath(mTmpDirName);
+ }
+ }
+#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);
+
+ if (mChildProcessHandle != 0) {
+#if defined(MOZ_WIDGET_COCOA)
+ SharedMemoryBasic::CleanupForPidWithLock(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
+ );
+ }
+
+#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
+ }
+#if defined(MOZ_SANDBOX) && defined(XP_WIN)
+ if (mSandboxBroker) {
+ mSandboxBroker->Shutdown();
+ mSandboxBroker = nullptr;
+ }
+#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{}, __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)");
+ }
+# if defined(MOZ_SANDBOX)
+ // We need to start the child process using the real path, so that the
+ // sandbox policy rules will match for DLLs loaded from the bin dir after
+ // we have lowered the sandbox.
+ std::wstring exePathStr = exePathBuf;
+ if (widget::WinUtils::ResolveJunctionPointsAndSymLinks(exePathStr)) {
+ exePath = FilePath::FromWStringHack(exePathStr);
+ } else
+# endif
+ {
+ 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++; }
+
+void GeckoChildProcessHost::PrepareLaunch() {
+ if (CrashReporter::GetEnabled()) {
+ CrashReporter::OOPInit();
+ }
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+ SandboxLaunchPrepare(mProcessType, mLaunchOptions.get());
+#endif
+
+#ifdef XP_WIN
+ if (mProcessType == GeckoProcessType_Plugin) {
+ InitWindowsGroupID();
+ }
+
+# if defined(MOZ_SANDBOX)
+ // We need to get the pref here as the process is launched off main thread.
+ if (mProcessType == GeckoProcessType_Content) {
+ 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());
+ // Before resolving check if path ends with '\' as this indicates we
+ // want to give read access to a directory and so it needs a wildcard.
+ bool addWildcard = (resolvedPath.back() == L'\\');
+ if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(resolvedPath)) {
+ NS_ERROR("Failed to resolve test read policy rule.");
+ continue;
+ }
+
+ if (addWildcard) {
+ 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");
+
+ if (ShouldHaveDirectoryService() && mProcessType == GeckoProcessType_GPU) {
+ mozilla::Unused << NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(mProfileDir));
+ }
+# 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));
+
+ // 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](const LaunchResults& aResults) {
+ {
+ 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");
+ }
+
+#ifdef XP_MACOSX
+ this->mChildTask = aResults.mChildTask;
+#endif
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+ this->mSandboxBroker = 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(aResults.mHandle,
+ __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)));
+ {
+ 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 || mChildProcessHandle);
+
+ return mProcessState < PROCESS_ERROR;
+}
+
+bool GeckoChildProcessHost::LaunchAndWaitForProcessHandle(
+ StringVector aExtraOpts) {
+ if (!AsyncLaunch(std::move(aExtraOpts))) {
+ return false;
+ }
+ return WaitForProcessHandle();
+}
+
+void GeckoChildProcessHost::InitializeChannel() {
+ CreateChannel();
+
+ MonitorAutoLock lock(mMonitor);
+ mProcessState = CHANNEL_INITIALIZED;
+ lock.Notify();
+}
+
+void GeckoChildProcessHost::Join() {
+ AssertIOThread();
+
+ if (!mChildProcessHandle) {
+ return;
+ }
+
+ // If this fails, there's nothing we can do.
+ base::KillProcess(mChildProcessHandle, 0, /*wait*/ true);
+ SetAlreadyDead();
+}
+
+void GeckoChildProcessHost::SetAlreadyDead() {
+ if (mChildProcessHandle && mChildProcessHandle != 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))) {
+# ifdef MOZ_SANDBOX
+ // We need to make sure the child log name doesn't contain any junction
+ // points or symlinks or the sandbox will reject rules to allow writing.
+ std::wstring resolvedPath(NS_ConvertUTF8toUTF16(absPath).get());
+ if (widget::WinUtils::ResolveJunctionPointsAndSymLinks(resolvedPath)) {
+ AppendUTF16toUTF8(
+ Span(reinterpret_cast<const char16_t*>(resolvedPath.data()),
+ resolvedPath.size()),
+ buffer);
+ } else
+# endif
+ {
+ 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(gChildCounter);
+}
+
+// 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;
+
+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("-appdir"));
+ std::wstring wpath(path.get());
+ aCmdLine.AppendLooseValue(wpath);
+#else
+ nsAutoCString path;
+ MOZ_ALWAYS_SUCCEEDS(aAppDir->GetNativePath(path));
+ aCmdLine.push_back("-appdir");
+ aCmdLine.push_back(path.get());
+#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));
+ aCmdLine.push_back("-profile");
+ aCmdLine.push_back(path.get());
+ }
+#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() {
+ if (!DoSetup()) {
+ return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __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__);
+ });
+}
+
+bool BaseProcessLauncher::DoSetup() {
+#if defined(MOZ_GECKO_PROFILER) || defined(MOZ_MEMORY)
+ RefPtr<BaseProcessLauncher> self = this;
+# ifdef MOZ_GECKO_PROFILER
+ GetProfilerEnvVarsForChildProcess([self](const char* key, const char* value) {
+ self->mLaunchOptions->env_map[ENVIRONMENT_STRING(key)] =
+ ENVIRONMENT_STRING(value);
+ });
+# endif
+# 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
+#endif
+
+ MapChildLogging();
+
+ return PR_CreatePipe(&mCrashAnnotationReadPipe.rwget(),
+ &mCrashAnnotationWritePipe.rwget()) == PR_SUCCESS;
+}
+
+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)
+bool LinuxProcessLauncher::DoSetup() {
+ if (!PosixProcessLauncher::DoSetup()) {
+ return false;
+ }
+
+ 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 true;
+}
+#endif // MOZ_WIDGET_GTK
+
+#ifdef OS_POSIX
+bool PosixProcessLauncher::DoSetup() {
+ if (!BaseProcessLauncher::DoSetup()) {
+ return false;
+ }
+
+ // 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());
+
+# ifdef MOZ_WIDGET_GTK
+ if (mProcessType == GeckoProcessType_Plugin) {
+ new_ld_lib_path.AppendLiteral("/gtk2:");
+ new_ld_lib_path.Append(path.get());
+ }
+# endif // MOZ_WIDGET_GTK
+ 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)
+ mLaunchOptions->env_map["DYLD_LIBRARY_PATH"] = path.get();
+ // XXX DYLD_INSERT_LIBRARIES should only be set when launching a plugin
+ // process, and has no effect on other subprocesses (the hooks in
+ // libplugin_child_interpose.dylib become noops). But currently it
+ // gets set when launching any kind of subprocess.
+ //
+ // Trigger "dyld interposing" for the dylib that contains
+ // plugin_child_interpose.mm. This allows us to hook OS calls in the
+ // plugin process (ones that don't work correctly in a background
+ // process). Don't break any other "dyld interposing" that has already
+ // been set up by whatever may have launched the browser.
+ const char* prevInterpose = PR_GetEnv("DYLD_INSERT_LIBRARIES");
+ nsCString interpose;
+ if (prevInterpose && strlen(prevInterpose) > 0) {
+ interpose.Assign(prevInterpose);
+ interpose.Append(':');
+ }
+ interpose.Append(path.get());
+ interpose.AppendLiteral("/libplugin_child_interpose.dylib");
+ mLaunchOptions->env_map["DYLD_INSERT_LIBRARIES"] = interpose.get();
+
+ // 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
+ int srcChannelFd, dstChannelFd;
+ mChannel->GetClientFileDescriptorMapping(&srcChannelFd, &dstChannelFd);
+ mLaunchOptions->fds_to_remap.push_back(
+ std::pair<int, int>(srcChannelFd, dstChannelFd));
+
+ // 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(mPidString);
+
+ if (!CrashReporter::IsDummy()) {
+# if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
+ int childCrashFd, childCrashRemapFd;
+ if (!CrashReporter::CreateNotificationPipeForChild(&childCrashFd,
+ &childCrashRemapFd)) {
+ return false;
+ }
+
+ 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
+ mChildArgv.push_back(
+ static_cast<MacProcessLauncher*>(this)->mMachConnectionName.c_str());
+# endif // MOZ_WIDGET_COCOA
+
+ mChildArgv.push_back(ChildProcessType());
+ return true;
+}
+#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;
+ if (!base::LaunchApp(mChildArgv, *mLaunchOptions, &handle)) {
+ return ProcessHandlePromise::CreateAndReject(LaunchError{}, __func__);
+ }
+ return ProcessHandlePromise::CreateAndResolve(handle, __func__);
+}
+
+bool PosixProcessLauncher::DoFinishLaunch() {
+ if (!BaseProcessLauncher::DoFinishLaunch()) {
+ return false;
+ }
+
+ // 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).
+ mChannel->CloseClientFileDescriptor();
+
+ return true;
+}
+#endif // OS_POSIX
+
+#ifdef XP_MACOSX
+bool MacProcessLauncher::DoFinishLaunch() {
+ if (!PosixProcessLauncher::DoFinishLaunch()) {
+ return false;
+ }
+
+ // Wait for the child process to send us its 'task_t' data.
+ const int kTimeoutMs = 10000;
+
+ MachReceiveMessage child_message;
+ kern_return_t err =
+ mParentRecvPort.WaitForMessage(&child_message, kTimeoutMs);
+ if (err != KERN_SUCCESS) {
+ std::string errString =
+ StringPrintf("0x%x %s", err, mach_error_string(err));
+ CHROMIUM_LOG(ERROR) << "parent WaitForMessage() failed: " << errString;
+ return false;
+ }
+
+ task_t child_task = child_message.GetTranslatedPort(0);
+ if (child_task == MACH_PORT_NULL) {
+ CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(0) failed.";
+ return false;
+ }
+
+ if (child_message.GetTranslatedPort(1) == MACH_PORT_NULL) {
+ CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(1) failed.";
+ return false;
+ }
+ MachPortSender parent_sender(child_message.GetTranslatedPort(1));
+
+ if (child_message.GetTranslatedPort(2) == MACH_PORT_NULL) {
+ CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(2) failed.";
+ }
+ auto* parent_recv_port_memory_ack =
+ new MachPortSender(child_message.GetTranslatedPort(2));
+
+ if (child_message.GetTranslatedPort(3) == MACH_PORT_NULL) {
+ CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(3) failed.";
+ }
+ auto* parent_send_port_memory =
+ new MachPortSender(child_message.GetTranslatedPort(3));
+
+ MachSendMessage parent_message(/* id= */ 0);
+ if (!parent_message.AddDescriptor(MachMsgPortDescriptor(bootstrap_port))) {
+ CHROMIUM_LOG(ERROR) << "parent AddDescriptor(" << bootstrap_port
+ << ") failed.";
+ return false;
+ }
+
+ auto* parent_recv_port_memory = new ReceivePort();
+ if (!parent_message.AddDescriptor(
+ MachMsgPortDescriptor(parent_recv_port_memory->GetPort()))) {
+ CHROMIUM_LOG(ERROR) << "parent AddDescriptor("
+ << parent_recv_port_memory->GetPort() << ") failed.";
+ return false;
+ }
+
+ auto* parent_send_port_memory_ack = new ReceivePort();
+ if (!parent_message.AddDescriptor(
+ MachMsgPortDescriptor(parent_send_port_memory_ack->GetPort()))) {
+ CHROMIUM_LOG(ERROR) << "parent AddDescriptor("
+ << parent_send_port_memory_ack->GetPort()
+ << ") failed.";
+ return false;
+ }
+
+ err = parent_sender.SendMessage(parent_message, kTimeoutMs);
+ if (err != KERN_SUCCESS) {
+ std::string errString =
+ StringPrintf("0x%x %s", err, mach_error_string(err));
+ CHROMIUM_LOG(ERROR) << "parent SendMessage() failed: " << errString;
+ return false;
+ }
+
+ SharedMemoryBasic::SetupMachMemory(
+ mResults.mHandle, parent_recv_port_memory, parent_recv_port_memory_ack,
+ parent_send_port_memory, parent_send_port_memory_ack, false);
+
+ // NB: on OS X, we block much longer than we need to in order to
+ // reach this call, waiting for the child process's task_t. The
+ // best way to fix that is to refactor this file, hard.
+ mResults.mChildTask = child_task;
+
+ return true;
+}
+#endif // XP_MACOSX
+
+#ifdef XP_WIN
+bool WindowsProcessLauncher::DoSetup() {
+ if (!BaseProcessLauncher::DoSetup()) {
+ return false;
+ }
+
+ 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_)
+ const bool isClearKey = isGMP && Contains(mExtraOpts, "gmp-clearkey");
+ const bool isSandboxBroker =
+ mProcessType == GeckoProcessType_RemoteSandboxBroker;
+ if (isClearKey || isWidevine || isSandboxBroker) {
+ // 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());
+ }
+# endif // if defined(_ARM64_)
+# endif // defined(MOZ_SANDBOX) || defined(_ARM64_)
+
+ mCmdLine.emplace(exePath.ToWStringHack());
+
+ if (pathType == BinPathType::Self) {
+ mCmdLine->AppendLooseValue(UTF8ToWide("-contentproc"));
+ }
+
+ 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 (isClearKey || isWidevine)
+ mResults.mSandboxBroker = new RemoteSandboxBroker();
+ 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_Plugin:
+ if (mSandboxLevel > 0 && !PR_GetEnv("MOZ_DISABLE_NPAPI_SANDBOX")) {
+ if (!mResults.mSandboxBroker->SetSecurityLevelForPluginProcess(
+ mSandboxLevel)) {
+ return false;
+ }
+ 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 (!mResults.mSandboxBroker->SetSecurityLevelForGMPlugin(level)) {
+ return false;
+ }
+ 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,
+ mProfileDir);
+ 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 (!mResults.mSandboxBroker->SetSecurityLevelForRDDProcess()) {
+ return false;
+ }
+ mUseSandbox = true;
+ }
+ break;
+ case GeckoProcessType_Socket:
+ if (!PR_GetEnv("MOZ_DISABLE_SOCKET_PROCESS_SANDBOX")) {
+ if (!mResults.mSandboxBroker->SetSecurityLevelForSocketProcess()) {
+ return false;
+ }
+ 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());
+ }
+ }
+# 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());
+
+ // 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));
+ }
+
+ // 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 true;
+}
+
+RefPtr<ProcessHandlePromise> WindowsProcessLauncher::DoLaunch() {
+ ProcessHandle handle = 0;
+# ifdef MOZ_SANDBOX
+ if (mUseSandbox) {
+ const IMAGE_THUNK_DATA* cachedNtdllThunk =
+ mCachedNtdllThunk ? mCachedNtdllThunk->begin() : nullptr;
+ if (mResults.mSandboxBroker->LaunchApp(
+ mCmdLine->program().c_str(),
+ mCmdLine->command_line_string().c_str(), mLaunchOptions->env_map,
+ mProcessType, mEnableSandboxLogging, cachedNtdllThunk, &handle)) {
+ 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(LaunchError{}, __func__);
+ }
+# endif // defined(MOZ_SANDBOX)
+
+ if (!base::LaunchApp(mCmdLine.ref(), *mLaunchOptions, &handle)) {
+ return ProcessHandlePromise::CreateAndReject(LaunchError{}, __func__);
+ }
+ return ProcessHandlePromise::CreateAndResolve(handle, __func__);
+}
+
+bool WindowsProcessLauncher::DoFinishLaunch() {
+ if (!BaseProcessLauncher::DoFinishLaunch()) {
+ return false;
+ }
+
+# 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_Plugin:
+ 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 true;
+}
+#endif // XP_WIN
+
+RefPtr<ProcessLaunchPromise> BaseProcessLauncher::FinishLaunch() {
+ if (!DoFinishLaunch()) {
+ return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __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(int32_t peer_pid) {
+ if (!OpenPrivilegedHandle(peer_pid)) {
+ MOZ_CRASH("can't open handle to child process");
+ }
+ MonitorAutoLock lock(mMonitor);
+ mProcessState = PROCESS_CONNECTED;
+ lock.Notify();
+}
+
+void GeckoChildProcessHost::OnMessageReceived(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<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 (!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;
+ 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);
+ for (GeckoChildProcessHost* gp = sGeckoChildProcessHosts->getFirst(); gp;
+ gp = static_cast<mozilla::LinkedListElement<GeckoChildProcessHost>*>(gp)
+ ->getNext()) {
+ aCallback(gp);
+ }
+}
+
+RefPtr<ProcessLaunchPromise> BaseProcessLauncher::Launch(
+ GeckoChildProcessHost* aHost) {
+ AssertIOThread();
+
+ // 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.
+ //
+ // It's also unfortunate that we need to work with raw pointers to both the
+ // host and the channel. The assumption here is that the host (and therefore
+ // the channel) are never torn down until the return promise is resolved or
+ // rejected.
+ aHost->InitializeChannel();
+ mChannel = aHost->GetChannel();
+ if (!mChannel) {
+ return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __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..482c4d71a1
--- /dev/null
+++ b/ipc/glue/GeckoChildProcessHost.h
@@ -0,0 +1,299 @@
+/* -*- 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 "mozilla/ipc/FileDescriptor.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Buffer.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsCOMPtr.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
+
+struct _MacSandboxInfo;
+typedef _MacSandboxInfo MacSandboxInfo;
+
+namespace mozilla {
+namespace ipc {
+
+struct LaunchError {};
+typedef mozilla::MozPromise<base::ProcessHandle, LaunchError, false>
+ ProcessHandlePromise;
+
+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, false>
+ ProcessLaunchPromise;
+
+class GeckoChildProcessHost : public ChildProcessHost,
+ public LinkedListElement<GeckoChildProcessHost> {
+ protected:
+ typedef mozilla::Monitor Monitor;
+ typedef std::vector<std::string> StringVector;
+
+ public:
+ typedef base::ProcessHandle 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();
+
+ // 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(int32_t peer_pid) override;
+ virtual void OnMessageReceived(IPC::Message&& aMsg) override;
+ virtual void OnChannelError() override;
+ virtual void GetQueuedMessages(std::queue<IPC::Message>& queue) override;
+
+ // Resolves to the process handle when it's available (see
+ // LaunchAndWaitForProcessHandle); use with AsyncLaunch.
+ RefPtr<ProcessHandlePromise> WhenProcessHandleReady();
+
+ virtual void InitializeChannel();
+
+ virtual bool CanShutdown() override { return true; }
+
+ using ChildProcessHost::TakeChannel;
+ IPC::Channel* GetChannel() { return channelp(); }
+ ChannelId GetChannelId() { return channel_id(); }
+
+ // Returns a "borrowed" handle to the child process - the handle returned
+ // by this function must not be closed by the caller.
+ ProcessHandle GetChildProcessHandle() { return mChildProcessHandle; }
+
+ GeckoProcessType GetProcessType() { return mProcessType; }
+
+#ifdef XP_MACOSX
+ task_t GetChildTask() { return mChildTask; }
+#endif
+
+#ifdef XP_WIN
+ static void CacheNtDllThunk();
+
+ 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
+
+ /**
+ * Must run on the IO thread. Cause the OS process to exit and
+ * ensure its OS resources are cleaned up.
+ */
+ void Join();
+
+ // For bug 943174: Skip the EnsureProcessTerminated call in the destructor.
+ void SetAlreadyDead();
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ // 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
+ 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;
+ // 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;
+
+ // 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;
+
+ void PrepareLaunch();
+
+#ifdef XP_WIN
+ void InitWindowsGroupID();
+ nsString mGroupId;
+
+# ifdef MOZ_SANDBOX
+ RefPtr<AbstractSandboxBroker> mSandboxBroker;
+ std::vector<std::wstring> mAllowedFilesRead;
+ bool mEnableSandboxLogging;
+ int32_t mSandboxLevel;
+# endif
+#endif // XP_WIN
+
+ ProcessHandle mChildProcessHandle;
+#if defined(OS_MACOSX)
+ task_t mChildTask;
+#endif
+ RefPtr<ProcessHandlePromise> mHandlePromise;
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+ bool mDisableOSActivityMode;
+#endif
+
+ bool OpenPrivilegedHandle(base::ProcessId aPid);
+
+#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<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;
+#ifdef XP_WIN
+ static StaticAutoPtr<Buffer<IMAGE_THUNK_DATA>> sCachedNtDllThunk;
+#endif
+ 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..edb1708b61
--- /dev/null
+++ b/ipc/glue/IOThreadChild.h
@@ -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/. */
+
+#ifndef dom_plugins_IOThreadChild_h
+#define dom_plugins_IOThreadChild_h 1
+
+#include "chrome/common/child_thread.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();
+ }
+
+ static UniquePtr<IPC::Channel> TakeChannel() {
+ return IOThreadChild::current()->ChildThread::TakeChannel();
+ }
+
+ 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/IPCMessageUtils.cpp b/ipc/glue/IPCMessageUtils.cpp
new file mode 100644
index 0000000000..97cf607b21
--- /dev/null
+++ b/ipc/glue/IPCMessageUtils.cpp
@@ -0,0 +1,24 @@
+/* -*- 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 "IPCMessageUtils.h"
+
+#include <cstddef>
+#include "mozilla/CheckedInt.h"
+
+namespace IPC {
+
+bool ByteLengthIsValid(uint32_t aNumElements, size_t aElementSize,
+ int* aByteLength) {
+ auto length = mozilla::CheckedInt<int>(aNumElements) * aElementSize;
+ if (!length.isValid()) {
+ return false;
+ }
+ *aByteLength = length.value();
+ return true;
+}
+
+} // namespace IPC
diff --git a/ipc/glue/IPCMessageUtils.h b/ipc/glue/IPCMessageUtils.h
new file mode 100644
index 0000000000..afcc5305a7
--- /dev/null
+++ b/ipc/glue/IPCMessageUtils.h
@@ -0,0 +1,258 @@
+/* -*- 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/MacroForEach.h"
+#include "mozilla/ipc/IPCCore.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(Message* aMsg, const paramType& aParam) {
+ aMsg->WriteBytes(&aParam, sizeof(aParam));
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return aMsg->ReadBytesInto(aIter, 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(Message* aMsg, const paramType& aParam) {}
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ *aResult = {};
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<int8_t> {
+ typedef int8_t paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ aMsg->WriteBytes(&aParam, sizeof(aParam));
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return aMsg->ReadBytesInto(aIter, aResult, sizeof(*aResult));
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ // Use 0xff to avoid sign extension.
+ aLog->append(StringPrintf(L"0x%02x", aParam & 0xff));
+ }
+};
+
+template <>
+struct ParamTraits<uint8_t> {
+ typedef uint8_t paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ aMsg->WriteBytes(&aParam, sizeof(aParam));
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return aMsg->ReadBytesInto(aIter, aResult, sizeof(*aResult));
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ aLog->append(StringPrintf(L"0x%02x", aParam));
+ }
+};
+
+#if !defined(OS_POSIX)
+// See above re: keeping definitions in sync
+template <>
+struct ParamTraits<base::FileDescriptor> {
+ typedef base::FileDescriptor paramType;
+ static void Write(Message* aMsg, const paramType& aParam) {
+ MOZ_CRASH("FileDescriptor isn't meaningful on this platform");
+ }
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ 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(Message* aMsg, const paramType& aParam) {}
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ *aResult = paramType();
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::null_t> {
+ typedef mozilla::null_t paramType;
+ static void Write(Message* aMsg, const paramType& aParam) {}
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ 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(const Message* aMsg, PickleIterator* aIter,
+ ParamType* aResult,
+ void (ParamType::*aSetter)(bool)) {
+ bool value;
+ if (ReadParam(aMsg, aIter, &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(Message* aMsg, const Ts&... aArgs) {
+ (WriteParam(aMsg, aArgs), ...);
+}
+
+template <typename... Ts>
+static bool ReadParams(const Message* aMsg, PickleIterator* aIter,
+ Ts&... aArgs) {
+ return (ReadParam(aMsg, aIter, &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(Message* aMsg, const paramType& aParam) { \
+ WriteParams(aMsg, MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), (), \
+ (__VA_ARGS__))); \
+ } \
+ \
+ static bool Read(const Message* aMsg, PickleIterator* aIter, \
+ paramType* aResult) { \
+ paramType& aParam = *aResult; \
+ return ReadParams(aMsg, aIter, \
+ 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(Message* aMsg, const paramType& aParam) { \
+ WriteParam(aMsg, static_cast<const Super&>(aParam)); \
+ WriteParams(aMsg, MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), (), \
+ (__VA_ARGS__))); \
+ } \
+ \
+ static bool Read(const Message* aMsg, PickleIterator* aIter, \
+ paramType* aResult) { \
+ paramType& aParam = *aResult; \
+ return ReadParam(aMsg, aIter, static_cast<Super*>(aResult)) && \
+ ReadParams(aMsg, aIter, \
+ MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), (), \
+ (__VA_ARGS__))); \
+ } \
+ };
+
+#endif /* __IPC_GLUE_IPCMESSAGEUTILS_H__ */
diff --git a/ipc/glue/IPCMessageUtilsSpecializations.h b/ipc/glue/IPCMessageUtilsSpecializations.h
new file mode 100644
index 0000000000..4fcaac046c
--- /dev/null
+++ b/ipc/glue/IPCMessageUtilsSpecializations.h
@@ -0,0 +1,938 @@
+/* -*- 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/Unused.h"
+#include "mozilla/Vector.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "nsCSSPropertyID.h"
+#include "nsDebug.h"
+#include "nsHashKeys.h"
+#include "nsIContentPolicy.h"
+#include "nsID.h"
+#include "nsILoadInfo.h"
+#include "nsIThread.h"
+#include "nsLiteralString.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsTHashtable.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;
+}
+
+namespace IPC {
+
+template <>
+struct ParamTraits<nsACString> {
+ typedef nsACString paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ bool isVoid = aParam.IsVoid();
+ aMsg->WriteBool(isVoid);
+
+ if (isVoid)
+ // represents a nullptr pointer
+ return;
+
+ uint32_t length = aParam.Length();
+ WriteParam(aMsg, length);
+ aMsg->WriteBytes(aParam.BeginReading(), length);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ bool isVoid;
+ if (!aMsg->ReadBool(aIter, &isVoid)) return false;
+
+ if (isVoid) {
+ aResult->SetIsVoid(true);
+ return true;
+ }
+
+ uint32_t length;
+ if (!ReadParam(aMsg, aIter, &length)) {
+ return false;
+ }
+ if (!aMsg->HasBytesAvailable(aIter, length)) {
+ return false;
+ }
+ aResult->SetLength(length);
+
+ return aMsg->ReadBytesInto(aIter, aResult->BeginWriting(), length);
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ if (aParam.IsVoid())
+ aLog->append(L"(NULL)");
+ else
+ aLog->append(UTF8ToWide(aParam.BeginReading()));
+ }
+};
+
+template <>
+struct ParamTraits<nsAString> {
+ typedef nsAString paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ bool isVoid = aParam.IsVoid();
+ aMsg->WriteBool(isVoid);
+
+ if (isVoid)
+ // represents a nullptr pointer
+ return;
+
+ uint32_t length = aParam.Length();
+ WriteParam(aMsg, length);
+ aMsg->WriteBytes(aParam.BeginReading(), length * sizeof(char16_t));
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ bool isVoid;
+ if (!aMsg->ReadBool(aIter, &isVoid)) return false;
+
+ if (isVoid) {
+ aResult->SetIsVoid(true);
+ return true;
+ }
+
+ uint32_t length;
+ if (!ReadParam(aMsg, aIter, &length)) {
+ return false;
+ }
+
+ mozilla::CheckedInt<uint32_t> byteLength =
+ mozilla::CheckedInt<uint32_t>(length) * sizeof(char16_t);
+ if (!byteLength.isValid() ||
+ !aMsg->HasBytesAvailable(aIter, byteLength.value())) {
+ return false;
+ }
+
+ aResult->SetLength(length);
+
+ return aMsg->ReadBytesInto(aIter, aResult->BeginWriting(),
+ byteLength.value());
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ if (aParam.IsVoid())
+ aLog->append(L"(NULL)");
+ else {
+#ifdef WCHAR_T_IS_UTF16
+ aLog->append(reinterpret_cast<const wchar_t*>(aParam.BeginReading()));
+#else
+ uint32_t length = aParam.Length();
+ for (uint32_t index = 0; index < length; index++) {
+ aLog->push_back(std::wstring::value_type(aParam[index]));
+ }
+#endif
+ }
+ }
+};
+
+template <>
+struct ParamTraits<nsCString> : ParamTraits<nsACString> {
+ typedef nsCString paramType;
+};
+
+template <>
+struct ParamTraits<nsLiteralCString> : ParamTraits<nsACString> {
+ typedef nsLiteralCString paramType;
+};
+
+#ifdef MOZILLA_INTERNAL_API
+
+template <>
+struct ParamTraits<nsAutoCString> : ParamTraits<nsCString> {
+ typedef nsAutoCString paramType;
+};
+
+#endif // MOZILLA_INTERNAL_API
+
+template <>
+struct ParamTraits<nsString> : ParamTraits<nsAString> {
+ typedef nsString paramType;
+};
+
+template <>
+struct ParamTraits<nsLiteralString> : ParamTraits<nsAString> {
+ typedef nsLiteralString paramType;
+};
+
+template <>
+struct ParamTraits<nsDependentSubstring> : ParamTraits<nsAString> {
+ typedef nsDependentSubstring paramType;
+};
+
+template <>
+struct ParamTraits<nsDependentCSubstring> : ParamTraits<nsACString> {
+ typedef nsDependentCSubstring paramType;
+};
+
+#ifdef MOZILLA_INTERNAL_API
+
+template <>
+struct ParamTraits<nsAutoString> : ParamTraits<nsString> {
+ typedef nsAutoString paramType;
+};
+
+#endif // MOZILLA_INTERNAL_API
+
+template <>
+struct ParamTraits<nsTHashtable<nsUint64HashKey>> {
+ typedef nsTHashtable<nsUint64HashKey> paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ uint32_t count = aParam.Count();
+ WriteParam(aMsg, count);
+ for (auto iter = aParam.ConstIter(); !iter.Done(); iter.Next()) {
+ WriteParam(aMsg, iter.Get()->GetKey());
+ }
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ uint32_t count;
+ if (!ReadParam(aMsg, aIter, &count)) {
+ return false;
+ }
+ paramType table(count);
+ for (uint32_t i = 0; i < count; ++i) {
+ uint64_t key;
+ if (!ReadParam(aMsg, aIter, &key)) {
+ return false;
+ }
+ table.PutEntry(key);
+ }
+ *aResult = std::move(table);
+ return true;
+ }
+};
+
+// Pickle::ReadBytes and ::WriteBytes take the length in ints, so we must
+// ensure there is no overflow. This returns |false| if it would overflow.
+// Otherwise, it returns |true| and places the byte length in |aByteLength|.
+bool ByteLengthIsValid(uint32_t aNumElements, size_t aElementSize,
+ int* aByteLength);
+
+// Note: IPDL will sometimes codegen specialized implementations of
+// nsTArray serialization and deserialization code in
+// implementSpecialArrayPickling(). This is needed when ParamTraits<E>
+// is not defined.
+template <typename E>
+struct ParamTraits<nsTArray<E>> {
+ typedef nsTArray<E> paramType;
+
+ // We write arrays of integer or floating-point data using a single pickling
+ // call, rather than writing each element individually. We deliberately do
+ // not use mozilla::IsPod here because it is perfectly reasonable to have
+ // a data structure T for which IsPod<T>::value is true, yet also have a
+ // ParamTraits<T> specialization.
+ static const bool sUseWriteBytes =
+ (std::is_integral_v<E> || std::is_floating_point_v<E>);
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ uint32_t length = aParam.Length();
+ WriteParam(aMsg, length);
+
+ if (sUseWriteBytes) {
+ int pickledLength = 0;
+ MOZ_RELEASE_ASSERT(ByteLengthIsValid(length, sizeof(E), &pickledLength));
+ aMsg->WriteBytes(aParam.Elements(), pickledLength);
+ } else {
+ const E* elems = aParam.Elements();
+ for (uint32_t index = 0; index < length; index++) {
+ WriteParam(aMsg, elems[index]);
+ }
+ }
+ }
+
+ // This method uses infallible allocation so that an OOM failure will
+ // show up as an OOM crash rather than an IPC FatalError.
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ uint32_t length;
+ if (!ReadParam(aMsg, aIter, &length)) {
+ return false;
+ }
+
+ if (sUseWriteBytes) {
+ int pickledLength = 0;
+ if (!ByteLengthIsValid(length, sizeof(E), &pickledLength)) {
+ return false;
+ }
+
+ E* elements = aResult->AppendElements(length);
+ return aMsg->ReadBytesInto(aIter, elements, pickledLength);
+ } else {
+ // Each ReadParam<E> may read more than 1 byte each; this is an attempt
+ // to minimally validate that the length isn't much larger than what's
+ // actually available in aMsg.
+ if (!aMsg->HasBytesAvailable(aIter, length)) {
+ return false;
+ }
+
+ aResult->SetCapacity(length);
+
+ for (uint32_t index = 0; index < length; index++) {
+ E* element = aResult->AppendElement();
+ if (!ReadParam(aMsg, aIter, element)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ for (uint32_t index = 0; index < aParam.Length(); index++) {
+ if (index) {
+ aLog->append(L" ");
+ }
+ LogParam(aParam[index], aLog);
+ }
+ }
+};
+
+template <typename E>
+struct ParamTraits<CopyableTArray<E>> : ParamTraits<nsTArray<E>> {};
+
+template <typename E>
+struct ParamTraits<FallibleTArray<E>> {
+ typedef FallibleTArray<E> paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, static_cast<const nsTArray<E>&>(aParam));
+ }
+
+ // Deserialize the array infallibly, but return a FallibleTArray.
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ nsTArray<E> temp;
+ if (!ReadParam(aMsg, aIter, &temp)) return false;
+
+ *aResult = std::move(temp);
+ return true;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ LogParam(static_cast<const nsTArray<E>&>(aParam), aLog);
+ }
+};
+
+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 E, size_t N, typename AP>
+struct ParamTraits<mozilla::Vector<E, N, AP>> {
+ typedef mozilla::Vector<E, N, AP> paramType;
+
+ // We write arrays of integer or floating-point data using a single pickling
+ // call, rather than writing each element individually. We deliberately do
+ // not use mozilla::IsPod here because it is perfectly reasonable to have
+ // a data structure T for which IsPod<T>::value is true, yet also have a
+ // ParamTraits<T> specialization.
+ static const bool sUseWriteBytes =
+ (std::is_integral_v<E> || std::is_floating_point_v<E>);
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ uint32_t length = aParam.length();
+ WriteParam(aMsg, length);
+
+ if (sUseWriteBytes) {
+ int pickledLength = 0;
+ MOZ_RELEASE_ASSERT(ByteLengthIsValid(length, sizeof(E), &pickledLength));
+ aMsg->WriteBytes(aParam.begin(), pickledLength);
+ return;
+ }
+
+ for (const E& elem : aParam) {
+ WriteParam(aMsg, elem);
+ }
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ uint32_t length;
+ if (!ReadParam(aMsg, aIter, &length)) {
+ return false;
+ }
+
+ if (sUseWriteBytes) {
+ int pickledLength = 0;
+ if (!ByteLengthIsValid(length, sizeof(E), &pickledLength)) {
+ return false;
+ }
+
+ if (!aResult->resizeUninitialized(length)) {
+ // So that OOM failure shows up as OOM crash instead of IPC FatalError.
+ NS_ABORT_OOM(length * sizeof(E));
+ }
+
+ E* elements = aResult->begin();
+ return aMsg->ReadBytesInto(aIter, elements, pickledLength);
+ }
+
+ // Each ReadParam<E> may read more than 1 byte each; this is an attempt
+ // to minimally validate that the length isn't much larger than what's
+ // actually available in aMsg.
+ if (!aMsg->HasBytesAvailable(aIter, length)) {
+ return false;
+ }
+
+ if (!aResult->resize(length)) {
+ // So that OOM failure shows up as OOM crash instead of IPC FatalError.
+ NS_ABORT_OOM(length * sizeof(E));
+ }
+
+ for (uint32_t index = 0; index < length; ++index) {
+ if (!ReadParam(aMsg, aIter, &((*aResult)[index]))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ for (uint32_t index = 0, len = aParam.length(); index < len; ++index) {
+ if (index) {
+ aLog->append(L" ");
+ }
+ LogParam(aParam[index], aLog);
+ }
+ }
+};
+
+template <typename E>
+struct ParamTraits<std::vector<E>> {
+ typedef std::vector<E> paramType;
+
+ // We write arrays of integer or floating-point data using a single pickling
+ // call, rather than writing each element individually. We deliberately do
+ // not use mozilla::IsPod here because it is perfectly reasonable to have
+ // a data structure T for which IsPod<T>::value is true, yet also have a
+ // ParamTraits<T> specialization.
+ static const bool sUseWriteBytes =
+ (std::is_integral_v<E> || std::is_floating_point_v<E>);
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ uint32_t length = aParam.size();
+ WriteParam(aMsg, length);
+
+ if (sUseWriteBytes) {
+ int pickledLength = 0;
+ MOZ_RELEASE_ASSERT(ByteLengthIsValid(length, sizeof(E), &pickledLength));
+ aMsg->WriteBytes(aParam.data(), pickledLength);
+ return;
+ }
+
+ for (const E& elem : aParam) {
+ WriteParam(aMsg, elem);
+ }
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ uint32_t length;
+ if (!ReadParam(aMsg, aIter, &length)) {
+ return false;
+ }
+
+ if (sUseWriteBytes) {
+ int pickledLength = 0;
+ if (!ByteLengthIsValid(length, sizeof(E), &pickledLength)) {
+ return false;
+ }
+
+ aResult->resize(length);
+
+ E* elements = aResult->data();
+ return aMsg->ReadBytesInto(aIter, elements, pickledLength);
+ }
+
+ // Each ReadParam<E> may read more than 1 byte each; this is an attempt
+ // to minimally validate that the length isn't much larger than what's
+ // actually available in aMsg.
+ if (!aMsg->HasBytesAvailable(aIter, length)) {
+ return false;
+ }
+
+ aResult->resize(length);
+
+ for (uint32_t index = 0; index < length; ++index) {
+ if (!ReadParam(aMsg, aIter, &((*aResult)[index]))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ for (uint32_t index = 0, len = aParam.size(); index < len; ++index) {
+ if (index) {
+ aLog->append(L" ");
+ }
+ LogParam(aParam[index], aLog);
+ }
+ }
+};
+
+template <typename K, typename V>
+struct ParamTraits<std::unordered_map<K, V>> final {
+ using T = std::unordered_map<K, V>;
+
+ static void Write(Message* const msg, const T& in) {
+ WriteParam(msg, in.size());
+ for (const auto& pair : in) {
+ WriteParam(msg, pair.first);
+ WriteParam(msg, pair.second);
+ }
+ }
+
+ static bool Read(const Message* const msg, PickleIterator* const itr,
+ T* const out) {
+ size_t size = 0;
+ if (!ReadParam(msg, itr, &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(msg, itr, &(pair.first)) ||
+ !ReadParam(msg, itr, &(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(Message* aMsg, const paramType& aParam) {
+ aMsg->WriteBytes(&aParam, sizeof(paramType));
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return aMsg->ReadBytesInto(aIter, aResult, sizeof(*aResult));
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ aLog->append(StringPrintf(L"%g", aParam));
+ }
+};
+
+template <>
+struct ParamTraits<nsCSSPropertyID>
+ : public ContiguousEnumSerializer<nsCSSPropertyID, eCSSProperty_UNKNOWN,
+ eCSSProperty_COUNT> {};
+
+template <>
+struct ParamTraits<nsID> {
+ typedef nsID paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.m0);
+ WriteParam(aMsg, aParam.m1);
+ WriteParam(aMsg, aParam.m2);
+ for (unsigned int i = 0; i < mozilla::ArrayLength(aParam.m3); i++) {
+ WriteParam(aMsg, aParam.m3[i]);
+ }
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ if (!ReadParam(aMsg, aIter, &(aResult->m0)) ||
+ !ReadParam(aMsg, aIter, &(aResult->m1)) ||
+ !ReadParam(aMsg, aIter, &(aResult->m2)))
+ return false;
+
+ for (unsigned int i = 0; i < mozilla::ArrayLength(aResult->m3); i++)
+ if (!ReadParam(aMsg, aIter, &(aResult->m3[i]))) return false;
+
+ return true;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ aLog->append(L"{");
+ aLog->append(
+ StringPrintf(L"%8.8X-%4.4X-%4.4X-", aParam.m0, aParam.m1, aParam.m2));
+ for (unsigned int i = 0; i < mozilla::ArrayLength(aParam.m3); i++)
+ aLog->append(StringPrintf(L"%2.2X", aParam.m3[i]));
+ aLog->append(L"}");
+ }
+};
+
+template <>
+struct ParamTraits<nsContentPolicyType>
+ : public ContiguousEnumSerializerInclusive<
+ nsContentPolicyType, nsIContentPolicy::TYPE_INVALID,
+ nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD> {};
+
+template <>
+struct ParamTraits<mozilla::TimeDuration> {
+ typedef mozilla::TimeDuration paramType;
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mValue);
+ }
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mValue);
+ };
+};
+
+template <>
+struct ParamTraits<mozilla::TimeStamp> {
+ typedef mozilla::TimeStamp paramType;
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mValue);
+ }
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mValue);
+ };
+};
+
+#ifdef XP_WIN
+
+template <>
+struct ParamTraits<mozilla::TimeStampValue> {
+ typedef mozilla::TimeStampValue paramType;
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mGTC);
+ WriteParam(aMsg, aParam.mQPC);
+ WriteParam(aMsg, aParam.mUsedCanonicalNow);
+ WriteParam(aMsg, aParam.mIsNull);
+ WriteParam(aMsg, aParam.mHasQPC);
+ }
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return (ReadParam(aMsg, aIter, &aResult->mGTC) &&
+ ReadParam(aMsg, aIter, &aResult->mQPC) &&
+ ReadParam(aMsg, aIter, &aResult->mUsedCanonicalNow) &&
+ ReadParam(aMsg, aIter, &aResult->mIsNull) &&
+ ReadParam(aMsg, aIter, &aResult->mHasQPC));
+ }
+};
+
+#else
+
+template <>
+struct ParamTraits<mozilla::TimeStamp63Bit> {
+ typedef mozilla::TimeStamp63Bit paramType;
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mUsedCanonicalNow);
+ WriteParam(aMsg, aParam.mTimeStamp);
+ }
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ bool success = true;
+ uint64_t result;
+
+ success &= ReadParam(aMsg, aIter, &result);
+ aResult->mUsedCanonicalNow = result & 0x01;
+
+ success &= ReadParam(aMsg, aIter, &result);
+ aResult->mTimeStamp = result & 0x7FFFFFFFFFFFFFFF;
+
+ return success;
+ }
+};
+
+#endif
+
+template <>
+struct ParamTraits<mozilla::dom::ipc::StructuredCloneData> {
+ typedef mozilla::dom::ipc::StructuredCloneData paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ aParam.WriteIPCParams(aMsg);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return aResult->ReadIPCParams(aMsg, aIter);
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ LogParam(aParam.DataLength(), aLog);
+ }
+};
+
+template <class T>
+struct ParamTraits<mozilla::Maybe<T>> {
+ typedef mozilla::Maybe<T> paramType;
+
+ static void Write(Message* msg, const paramType& param) {
+ if (param.isSome()) {
+ WriteParam(msg, true);
+ WriteParam(msg, param.value());
+ } else {
+ WriteParam(msg, false);
+ }
+ }
+
+ static bool Read(const Message* msg, PickleIterator* iter,
+ paramType* result) {
+ bool isSome;
+ if (!ReadParam(msg, iter, &isSome)) {
+ return false;
+ }
+ if (isSome) {
+ T tmp;
+ if (!ReadParam(msg, iter, &tmp)) {
+ return false;
+ }
+ *result = mozilla::Some(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(Message* msg, const paramType& param) {
+ MOZ_RELEASE_ASSERT(IsLegalValue(param.serialize()));
+ WriteParam(msg, param.serialize());
+ }
+
+ static bool Read(const Message* msg, PickleIterator* iter,
+ paramType* result) {
+ serializedType tmp;
+
+ if (ReadParam(msg, iter, &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(Message* msg, const paramType& param) {
+ WriteParam(msg, param.tag);
+ param.match([msg](const auto& t) { WriteParam(msg, 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(const Message* msg, PickleIterator* iter, 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(msg, iter, &result->template emplace<N - 1>());
+ } else {
+ return Next::Read(msg, iter, 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(const Message* msg, PickleIterator* iter, Tag tag,
+ paramType* result) {
+ return false;
+ }
+ };
+
+ static bool Read(const Message* msg, PickleIterator* iter,
+ paramType* result) {
+ Tag tag;
+ if (ReadParam(msg, iter, &tag)) {
+ return VariantReader<sizeof...(Ts)>::Read(msg, iter, tag, result);
+ }
+ return false;
+ }
+};
+
+template <typename T>
+struct ParamTraits<mozilla::dom::Optional<T>> {
+ typedef mozilla::dom::Optional<T> paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ if (aParam.WasPassed()) {
+ WriteParam(aMsg, true);
+ WriteParam(aMsg, aParam.Value());
+ return;
+ }
+
+ WriteParam(aMsg, false);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ bool wasPassed = false;
+
+ if (!ReadParam(aMsg, aIter, &wasPassed)) {
+ return false;
+ }
+
+ aResult->Reset();
+
+ if (wasPassed) {
+ if (!ReadParam(aMsg, aIter, &aResult->Construct())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+};
+
+struct CrossOriginOpenerPolicyValidator {
+ static bool IsLegalValue(nsILoadInfo::CrossOriginOpenerPolicy e) {
+ return e == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE ||
+ e == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN ||
+ e == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS ||
+ e == nsILoadInfo::
+ OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
+ }
+};
+
+template <>
+struct ParamTraits<nsILoadInfo::CrossOriginOpenerPolicy>
+ : EnumSerializer<nsILoadInfo::CrossOriginOpenerPolicy,
+ CrossOriginOpenerPolicyValidator> {};
+
+struct CrossOriginEmbedderPolicyValidator {
+ static bool IsLegalValue(nsILoadInfo::CrossOriginEmbedderPolicy e) {
+ return e == nsILoadInfo::EMBEDDER_POLICY_NULL ||
+ e == nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP;
+ }
+};
+
+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(Message* aMsg, const paramType& aParam) {
+ for (Word word : aParam.Storage()) {
+ WriteParam(aMsg, word);
+ }
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ for (Word& word : aResult->Storage()) {
+ if (!ReadParam(aMsg, aIter, &word)) {
+ return false;
+ }
+ }
+ return true;
+ }
+};
+
+} /* 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..978ee80a34
--- /dev/null
+++ b/ipc/glue/IPCStream.ipdlh
@@ -0,0 +1,24 @@
+/* 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 PChildToParentStream;
+include protocol PParentToChildStream;
+
+include BlobTypes;
+include InputStreamParams;
+include ProtocolTypes;
+
+namespace mozilla {
+namespace ipc {
+
+// Use IPCStream in your ipdl to represent serialized nsIInputStreams. Then use
+// AutoIPCStream from IPCStreamUtils.h to perform the serialization.
+struct IPCStream
+{
+ InputStreamParams stream;
+ OptionalFileDescriptorSet optionalFds;
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/IPCStreamAlloc.h b/ipc/glue/IPCStreamAlloc.h
new file mode 100644
index 0000000000..c22b35e355
--- /dev/null
+++ b/ipc/glue/IPCStreamAlloc.h
@@ -0,0 +1,23 @@
+/* -*- 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_IPCStreamAlloc_h
+#define mozilla_ipc_IPCStreamAlloc_h
+
+namespace mozilla {
+namespace ipc {
+
+class PChildToParentStreamParent;
+class PParentToChildStreamChild;
+
+PChildToParentStreamParent* AllocPChildToParentStreamParent();
+
+PParentToChildStreamChild* AllocPParentToChildStreamChild();
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_IPCStreamAlloc_h
diff --git a/ipc/glue/IPCStreamChild.cpp b/ipc/glue/IPCStreamChild.cpp
new file mode 100644
index 0000000000..d414a1eb95
--- /dev/null
+++ b/ipc/glue/IPCStreamChild.cpp
@@ -0,0 +1,157 @@
+/* -*- 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 "IPCStreamDestination.h"
+#include "IPCStreamSource.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/ipc/PChildToParentStreamChild.h"
+#include "mozilla/ipc/PParentToChildStreamChild.h"
+
+namespace mozilla {
+namespace ipc {
+
+// Child to Parent implementation
+// ----------------------------------------------------------------------------
+
+namespace {
+
+class IPCStreamSourceChild final : public PChildToParentStreamChild,
+ public IPCStreamSource {
+ public:
+ static IPCStreamSourceChild* Create(nsIAsyncInputStream* aInputStream) {
+ MOZ_ASSERT(aInputStream);
+
+ IPCStreamSourceChild* source = new IPCStreamSourceChild(aInputStream);
+ if (!source->Initialize()) {
+ delete source;
+ return nullptr;
+ }
+
+ return source;
+ }
+
+ // PChildToParentStreamChild methods
+
+ void ActorDestroy(ActorDestroyReason aReason) override { ActorDestroyed(); }
+
+ IPCResult RecvStartReading() override {
+ Start();
+ return IPC_OK();
+ }
+
+ IPCResult RecvRequestClose(const nsresult& aRv) override {
+ OnEnd(aRv);
+ return IPC_OK();
+ }
+
+ void Close(nsresult aRv) override {
+ MOZ_ASSERT(IPCStreamSource::mState == IPCStreamSource::eClosed);
+ Unused << SendClose(aRv);
+ }
+
+ void SendData(const wr::ByteBuffer& aBuffer) override {
+ Unused << SendBuffer(aBuffer);
+ }
+
+ private:
+ explicit IPCStreamSourceChild(nsIAsyncInputStream* aInputStream)
+ : IPCStreamSource(aInputStream) {}
+};
+
+} // anonymous namespace
+
+/* static */
+PChildToParentStreamChild* IPCStreamSource::Create(
+ nsIAsyncInputStream* aInputStream,
+ ChildToParentStreamActorManager* aManager) {
+ MOZ_ASSERT(aInputStream);
+ MOZ_ASSERT(aManager);
+
+ IPCStreamSourceChild* source = IPCStreamSourceChild::Create(aInputStream);
+ if (!source) {
+ return nullptr;
+ }
+
+ if (!aManager->SendPChildToParentStreamConstructor(source)) {
+ return nullptr;
+ }
+
+ source->ActorConstructed();
+ return source;
+}
+
+/* static */
+IPCStreamSource* IPCStreamSource::Cast(PChildToParentStreamChild* aActor) {
+ MOZ_ASSERT(aActor);
+ return static_cast<IPCStreamSourceChild*>(aActor);
+}
+
+// Parent to Child implementation
+// ----------------------------------------------------------------------------
+
+namespace {
+
+class IPCStreamDestinationChild final : public PParentToChildStreamChild,
+ public IPCStreamDestination {
+ public:
+ nsresult Initialize() { return IPCStreamDestination::Initialize(); }
+
+ ~IPCStreamDestinationChild() = default;
+
+ private:
+ // PParentToChildStreamChild methods
+
+ void ActorDestroy(ActorDestroyReason aReason) override { ActorDestroyed(); }
+
+ IPCResult RecvBuffer(const wr::ByteBuffer& aBuffer) override {
+ BufferReceived(aBuffer);
+ return IPC_OK();
+ }
+
+ IPCResult RecvClose(const nsresult& aRv) override {
+ CloseReceived(aRv);
+ return IPC_OK();
+ }
+
+ // IPCStreamDestination methods
+
+ void StartReading() override {
+ MOZ_ASSERT(HasDelayedStart());
+ Unused << SendStartReading();
+ }
+
+ void RequestClose(nsresult aRv) override { Unused << SendRequestClose(aRv); }
+
+ void TerminateDestination() override { Unused << Send__delete__(this); }
+};
+
+} // anonymous namespace
+
+PParentToChildStreamChild* AllocPParentToChildStreamChild() {
+ IPCStreamDestinationChild* actor = new IPCStreamDestinationChild();
+
+ if (NS_WARN_IF(NS_FAILED(actor->Initialize()))) {
+ delete actor;
+ actor = nullptr;
+ }
+
+ return actor;
+}
+
+void DeallocPParentToChildStreamChild(PParentToChildStreamChild* aActor) {
+ delete aActor;
+}
+
+/* static */
+IPCStreamDestination* IPCStreamDestination::Cast(
+ PParentToChildStreamChild* aActor) {
+ MOZ_ASSERT(aActor);
+ return static_cast<IPCStreamDestinationChild*>(aActor);
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/IPCStreamDestination.cpp b/ipc/glue/IPCStreamDestination.cpp
new file mode 100644
index 0000000000..d7071391e6
--- /dev/null
+++ b/ipc/glue/IPCStreamDestination.cpp
@@ -0,0 +1,400 @@
+/* -*- 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 "IPCStreamDestination.h"
+#include "mozilla/InputStreamLengthWrapper.h"
+#include "mozilla/Mutex.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIBufferedStreams.h"
+#include "nsICloneableInputStream.h"
+#include "nsIPipe.h"
+#include "nsThreadUtils.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+
+namespace mozilla {
+namespace ipc {
+
+// ----------------------------------------------------------------------------
+// IPCStreamDestination::DelayedStartInputStream
+//
+// When AutoIPCStream is used with delayedStart, we need to ask for data at the
+// first real use of the nsIInputStream. In order to do so, we wrap the
+// nsIInputStream, created by the nsIPipe, with this wrapper.
+
+class IPCStreamDestination::DelayedStartInputStream final
+ : public nsIAsyncInputStream,
+ public nsIInputStreamCallback,
+ public nsISearchableInputStream,
+ public nsICloneableInputStream,
+ public nsIBufferedInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ DelayedStartInputStream(IPCStreamDestination* aDestination,
+ nsCOMPtr<nsIAsyncInputStream>&& aStream)
+ : mDestination(aDestination),
+ mStream(std::move(aStream)),
+ mMutex("IPCStreamDestination::DelayedStartInputStream::mMutex") {
+ MOZ_ASSERT(mDestination);
+ MOZ_ASSERT(mStream);
+ }
+
+ void DestinationShutdown() {
+ MutexAutoLock lock(mMutex);
+ mDestination = nullptr;
+ }
+
+ // nsIInputStream interface
+
+ NS_IMETHOD
+ Close() override {
+ MaybeCloseDestination();
+ return mStream->Close();
+ }
+
+ NS_IMETHOD
+ Available(uint64_t* aLength) override {
+ MaybeStartReading();
+ return mStream->Available(aLength);
+ }
+
+ NS_IMETHOD
+ Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override {
+ MaybeStartReading();
+ return mStream->Read(aBuffer, aCount, aReadCount);
+ }
+
+ NS_IMETHOD
+ ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount,
+ uint32_t* aResult) override {
+ MaybeStartReading();
+ return mStream->ReadSegments(aWriter, aClosure, aCount, aResult);
+ }
+
+ NS_IMETHOD
+ IsNonBlocking(bool* aNonBlocking) override {
+ MaybeStartReading();
+ return mStream->IsNonBlocking(aNonBlocking);
+ }
+
+ // nsIAsyncInputStream interface
+
+ NS_IMETHOD
+ CloseWithStatus(nsresult aReason) override {
+ MaybeCloseDestination();
+ return mStream->CloseWithStatus(aReason);
+ }
+
+ NS_IMETHOD
+ AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount, nsIEventTarget* aTarget) override {
+ {
+ MutexAutoLock lock(mMutex);
+ if (mAsyncWaitCallback && aCallback) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mAsyncWaitCallback = aCallback;
+
+ MaybeStartReading(lock);
+ }
+
+ nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr;
+ return mStream->AsyncWait(callback, aFlags, aRequestedCount, aTarget);
+ }
+
+ NS_IMETHOD
+ Search(const char* aForString, bool aIgnoreCase, bool* aFound,
+ uint32_t* aOffsetSearchedTo) override {
+ MaybeStartReading();
+ nsCOMPtr<nsISearchableInputStream> searchable = do_QueryInterface(mStream);
+ MOZ_ASSERT(searchable);
+ return searchable->Search(aForString, aIgnoreCase, aFound,
+ aOffsetSearchedTo);
+ }
+
+ // nsICloneableInputStream interface
+
+ NS_IMETHOD
+ GetCloneable(bool* aCloneable) override {
+ MaybeStartReading();
+ nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(mStream);
+ MOZ_ASSERT(cloneable);
+ return cloneable->GetCloneable(aCloneable);
+ }
+
+ NS_IMETHOD
+ Clone(nsIInputStream** aResult) override {
+ MaybeStartReading();
+ nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(mStream);
+ MOZ_ASSERT(cloneable);
+ return cloneable->Clone(aResult);
+ }
+
+ // nsIBufferedInputStream
+
+ NS_IMETHOD
+ Init(nsIInputStream* aStream, uint32_t aBufferSize) override {
+ MaybeStartReading();
+ nsCOMPtr<nsIBufferedInputStream> stream = do_QueryInterface(mStream);
+ MOZ_ASSERT(stream);
+ return stream->Init(aStream, aBufferSize);
+ }
+
+ NS_IMETHODIMP
+ GetData(nsIInputStream** aResult) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // nsIInputStreamCallback
+
+ NS_IMETHOD
+ OnInputStreamReady(nsIAsyncInputStream* aStream) override {
+ nsCOMPtr<nsIInputStreamCallback> callback;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ // We have been canceled in the meanwhile.
+ if (!mAsyncWaitCallback) {
+ return NS_OK;
+ }
+
+ callback.swap(mAsyncWaitCallback);
+ }
+
+ callback->OnInputStreamReady(this);
+ return NS_OK;
+ }
+
+ void MaybeStartReading();
+ void MaybeStartReading(const MutexAutoLock& aProofOfLook);
+
+ void MaybeCloseDestination();
+
+ private:
+ ~DelayedStartInputStream() = default;
+
+ IPCStreamDestination* mDestination;
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+
+ nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback;
+
+ // This protects mDestination: any method can be called by any thread.
+ Mutex mMutex;
+
+ class HelperRunnable;
+};
+
+class IPCStreamDestination::DelayedStartInputStream::HelperRunnable final
+ : public Runnable {
+ public:
+ enum Op {
+ eStartReading,
+ eCloseDestination,
+ };
+
+ HelperRunnable(
+ IPCStreamDestination::DelayedStartInputStream* aDelayedStartInputStream,
+ Op aOp)
+ : Runnable(
+ "ipc::IPCStreamDestination::DelayedStartInputStream::"
+ "HelperRunnable"),
+ mDelayedStartInputStream(aDelayedStartInputStream),
+ mOp(aOp) {
+ MOZ_ASSERT(aDelayedStartInputStream);
+ }
+
+ NS_IMETHOD
+ Run() override {
+ switch (mOp) {
+ case eStartReading:
+ mDelayedStartInputStream->MaybeStartReading();
+ break;
+ case eCloseDestination:
+ mDelayedStartInputStream->MaybeCloseDestination();
+ break;
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<IPCStreamDestination::DelayedStartInputStream>
+ mDelayedStartInputStream;
+ Op mOp;
+};
+
+void IPCStreamDestination::DelayedStartInputStream::MaybeStartReading() {
+ MutexAutoLock lock(mMutex);
+ MaybeStartReading(lock);
+}
+
+void IPCStreamDestination::DelayedStartInputStream::MaybeStartReading(
+ const MutexAutoLock& aProofOfLook) {
+ if (!mDestination) {
+ return;
+ }
+
+ if (mDestination->IsOnOwningThread()) {
+ mDestination->StartReading();
+ mDestination = nullptr;
+ return;
+ }
+
+ RefPtr<Runnable> runnable =
+ new HelperRunnable(this, HelperRunnable::eStartReading);
+ mDestination->DispatchRunnable(runnable.forget());
+}
+
+void IPCStreamDestination::DelayedStartInputStream::MaybeCloseDestination() {
+ MutexAutoLock lock(mMutex);
+ if (!mDestination) {
+ return;
+ }
+
+ if (mDestination->IsOnOwningThread()) {
+ mDestination->RequestClose(NS_ERROR_ABORT);
+ mDestination = nullptr;
+ return;
+ }
+
+ RefPtr<Runnable> runnable =
+ new HelperRunnable(this, HelperRunnable::eCloseDestination);
+ mDestination->DispatchRunnable(runnable.forget());
+}
+
+NS_IMPL_ADDREF(IPCStreamDestination::DelayedStartInputStream);
+NS_IMPL_RELEASE(IPCStreamDestination::DelayedStartInputStream);
+
+NS_INTERFACE_MAP_BEGIN(IPCStreamDestination::DelayedStartInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsISearchableInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsICloneableInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIBufferedInputStream)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIInputStream, nsIAsyncInputStream)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAsyncInputStream)
+NS_INTERFACE_MAP_END
+
+// ----------------------------------------------------------------------------
+// IPCStreamDestination
+
+IPCStreamDestination::IPCStreamDestination()
+ : mOwningThread(NS_GetCurrentThread()),
+ mDelayedStart(false)
+#ifdef MOZ_DEBUG
+ ,
+ mLengthSet(false)
+#endif
+{
+}
+
+IPCStreamDestination::~IPCStreamDestination() = default;
+
+nsresult IPCStreamDestination::Initialize() {
+ MOZ_ASSERT(!mReader);
+ MOZ_ASSERT(!mWriter);
+
+ // use async versions for both reader and writer even though we are
+ // opening the writer as an infinite stream. We want to be able to
+ // use CloseWithStatus() to communicate errors through the pipe.
+
+ // Use an "infinite" pipe because we cannot apply back-pressure through
+ // the async IPC layer at the moment. Blocking the IPC worker thread
+ // is not desirable, either.
+ nsresult rv = NS_NewPipe2(getter_AddRefs(mReader), getter_AddRefs(mWriter),
+ true, true, // non-blocking
+ 0, // segment size
+ UINT32_MAX); // "infinite" pipe
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void IPCStreamDestination::SetDelayedStart(bool aDelayedStart) {
+ mDelayedStart = aDelayedStart;
+}
+
+void IPCStreamDestination::SetLength(int64_t aLength) {
+ MOZ_ASSERT(mReader);
+ MOZ_ASSERT(!mLengthSet);
+
+#ifdef DEBUG
+ mLengthSet = true;
+#endif
+
+ if (aLength != -1) {
+ nsCOMPtr<nsIInputStream> finalStream;
+ finalStream = new InputStreamLengthWrapper(mReader.forget(), aLength);
+ mReader = do_QueryInterface(finalStream);
+ MOZ_ASSERT(mReader);
+ }
+}
+
+already_AddRefed<nsIInputStream> IPCStreamDestination::TakeReader() {
+ MOZ_ASSERT(mReader);
+ MOZ_ASSERT(!mDelayedStartInputStream);
+
+ if (mDelayedStart) {
+ mDelayedStartInputStream =
+ new DelayedStartInputStream(this, std::move(mReader));
+ RefPtr<nsIAsyncInputStream> inputStream = mDelayedStartInputStream;
+ return inputStream.forget();
+ }
+
+ return mReader.forget();
+}
+
+bool IPCStreamDestination::IsOnOwningThread() const {
+ return mOwningThread == NS_GetCurrentThread();
+}
+
+void IPCStreamDestination::DispatchRunnable(
+ already_AddRefed<nsIRunnable>&& aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable = aRunnable;
+ mOwningThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+}
+
+void IPCStreamDestination::ActorDestroyed() {
+ MOZ_ASSERT(mWriter);
+
+ // If we were gracefully closed we should have gotten RecvClose(). In
+ // that case, the writer will already be closed and this will have no
+ // effect. This just aborts the writer in the case where the child process
+ // crashes.
+ mWriter->CloseWithStatus(NS_ERROR_ABORT);
+
+ if (mDelayedStartInputStream) {
+ mDelayedStartInputStream->DestinationShutdown();
+ mDelayedStartInputStream = nullptr;
+ }
+}
+
+void IPCStreamDestination::BufferReceived(const wr::ByteBuffer& aBuffer) {
+ MOZ_ASSERT(mWriter);
+
+ uint32_t numWritten = 0;
+
+ // This should only fail if we hit an OOM condition.
+ nsresult rv = mWriter->Write(reinterpret_cast<char*>(aBuffer.mData),
+ aBuffer.mLength, &numWritten);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ RequestClose(rv);
+ }
+}
+
+void IPCStreamDestination::CloseReceived(nsresult aRv) {
+ MOZ_ASSERT(mWriter);
+ mWriter->CloseWithStatus(aRv);
+ TerminateDestination();
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/IPCStreamDestination.h b/ipc/glue/IPCStreamDestination.h
new file mode 100644
index 0000000000..8bd0cb3f87
--- /dev/null
+++ b/ipc/glue/IPCStreamDestination.h
@@ -0,0 +1,95 @@
+/* -*- 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_IPCStreamDestination_h
+#define mozilla_ipc_IPCStreamDestination_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "nsIRunnable.h"
+#include "nsIThread.h"
+
+class nsIInputStream;
+class nsIAsyncInputStream;
+class nsIAsyncOutputStream;
+
+namespace mozilla {
+
+namespace wr {
+struct ByteBuffer;
+} // namespace wr
+
+namespace ipc {
+
+class PChildToParentStreamParent;
+class PParentToChildStreamChild;
+
+// On the destination side, you must simply call TakeReader() upon receiving a
+// reference to the IPCStream{Child,Parent} actor. You do not need to maintain
+// a reference to the actor itself.
+class IPCStreamDestination {
+ public:
+ static IPCStreamDestination* Cast(PChildToParentStreamParent* aActor);
+
+ static IPCStreamDestination* Cast(PParentToChildStreamChild* aActor);
+
+ void SetDelayedStart(bool aDelayedStart);
+
+ void SetLength(int64_t aLength);
+
+ already_AddRefed<nsIInputStream> TakeReader();
+
+ bool IsOnOwningThread() const;
+
+ void DispatchRunnable(already_AddRefed<nsIRunnable>&& aRunnable);
+
+ protected:
+ IPCStreamDestination();
+ virtual ~IPCStreamDestination();
+
+ nsresult Initialize();
+
+ // The implementation of the actor should call these methods.
+
+ void ActorDestroyed();
+
+ void BufferReceived(const wr::ByteBuffer& aBuffer);
+
+ void CloseReceived(nsresult aRv);
+
+#ifdef DEBUG
+ bool HasDelayedStart() const { return mDelayedStart; }
+#endif
+
+ // These methods will be implemented by the actor.
+
+ virtual void StartReading() = 0;
+
+ virtual void RequestClose(nsresult aRv) = 0;
+
+ virtual void TerminateDestination() = 0;
+
+ private:
+ nsCOMPtr<nsIAsyncInputStream> mReader;
+ nsCOMPtr<nsIAsyncOutputStream> mWriter;
+
+ // This is created by TakeReader() if we need to delay the reading of data.
+ // We keep a reference to the stream in order to inform it when the actor goes
+ // away. If that happens, the reading of data will not be possible anymore.
+ class DelayedStartInputStream;
+ RefPtr<DelayedStartInputStream> mDelayedStartInputStream;
+
+ nsCOMPtr<nsIThread> mOwningThread;
+ bool mDelayedStart;
+
+#ifdef MOZ_DEBUG
+ bool mLengthSet;
+#endif
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_IPCStreamDestination_h
diff --git a/ipc/glue/IPCStreamParent.cpp b/ipc/glue/IPCStreamParent.cpp
new file mode 100644
index 0000000000..a0c95db657
--- /dev/null
+++ b/ipc/glue/IPCStreamParent.cpp
@@ -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/. */
+
+#include "IPCStreamDestination.h"
+#include "mozilla/ipc/IPCStreamSource.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/PChildToParentStreamParent.h"
+#include "mozilla/ipc/PParentToChildStreamParent.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace ipc {
+
+// Child to Parent implementation
+// ----------------------------------------------------------------------------
+
+namespace {
+
+class IPCStreamSourceParent final : public PParentToChildStreamParent,
+ public IPCStreamSource {
+ public:
+ static IPCStreamSourceParent* Create(nsIAsyncInputStream* aInputStream) {
+ MOZ_ASSERT(aInputStream);
+
+ IPCStreamSourceParent* source = new IPCStreamSourceParent(aInputStream);
+ if (!source->Initialize()) {
+ delete source;
+ return nullptr;
+ }
+
+ return source;
+ }
+
+ // PParentToChildStreamParent methods
+
+ void ActorDestroy(ActorDestroyReason aReason) override { ActorDestroyed(); }
+
+ IPCResult RecvStartReading() override {
+ Start();
+ return IPC_OK();
+ }
+
+ IPCResult RecvRequestClose(const nsresult& aRv) override {
+ OnEnd(aRv);
+ return IPC_OK();
+ }
+
+ void Close(nsresult aRv) override {
+ MOZ_ASSERT(IPCStreamSource::mState == IPCStreamSource::eClosed);
+ Unused << SendClose(aRv);
+ }
+
+ void SendData(const wr::ByteBuffer& aBuffer) override {
+ Unused << SendBuffer(aBuffer);
+ }
+
+ private:
+ explicit IPCStreamSourceParent(nsIAsyncInputStream* aInputStream)
+ : IPCStreamSource(aInputStream) {}
+};
+
+} // anonymous namespace
+
+/* static */
+PParentToChildStreamParent* IPCStreamSource::Create(
+ nsIAsyncInputStream* aInputStream,
+ ParentToChildStreamActorManager* aManager) {
+ MOZ_ASSERT(aInputStream);
+ MOZ_ASSERT(aManager);
+
+ IPCStreamSourceParent* source = IPCStreamSourceParent::Create(aInputStream);
+ if (!source) {
+ return nullptr;
+ }
+
+ if (!aManager->SendPParentToChildStreamConstructor(source)) {
+ // no delete here, the manager will delete the actor for us.
+ return nullptr;
+ }
+
+ source->ActorConstructed();
+ return source;
+}
+
+/* static */
+IPCStreamSource* IPCStreamSource::Cast(PParentToChildStreamParent* aActor) {
+ MOZ_ASSERT(aActor);
+ return static_cast<IPCStreamSourceParent*>(aActor);
+}
+
+// Child to Parent implementation
+// ----------------------------------------------------------------------------
+
+namespace {
+
+class IPCStreamDestinationParent final : public PChildToParentStreamParent,
+ public IPCStreamDestination {
+ public:
+ nsresult Initialize() { return IPCStreamDestination::Initialize(); }
+
+ ~IPCStreamDestinationParent() = default;
+
+ private:
+ // PChildToParentStreamParent methods
+
+ void ActorDestroy(ActorDestroyReason aReason) override { ActorDestroyed(); }
+
+ IPCResult RecvBuffer(const wr::ByteBuffer& aBuffer) override {
+ BufferReceived(aBuffer);
+ return IPC_OK();
+ }
+
+ IPCResult RecvClose(const nsresult& aRv) override {
+ CloseReceived(aRv);
+ return IPC_OK();
+ }
+
+ // IPCStreamDestination methods
+
+ void StartReading() override {
+ MOZ_ASSERT(HasDelayedStart());
+ Unused << SendStartReading();
+ }
+
+ void RequestClose(nsresult aRv) override { Unused << SendRequestClose(aRv); }
+
+ void TerminateDestination() override { Unused << Send__delete__(this); }
+};
+
+} // anonymous namespace
+
+PChildToParentStreamParent* AllocPChildToParentStreamParent() {
+ IPCStreamDestinationParent* actor = new IPCStreamDestinationParent();
+
+ if (NS_WARN_IF(NS_FAILED(actor->Initialize()))) {
+ delete actor;
+ actor = nullptr;
+ }
+
+ return actor;
+}
+
+void DeallocPChildToParentStreamParent(PChildToParentStreamParent* aActor) {
+ delete aActor;
+}
+
+/* static */
+IPCStreamDestination* IPCStreamDestination::Cast(
+ PChildToParentStreamParent* aActor) {
+ MOZ_ASSERT(aActor);
+ return static_cast<IPCStreamDestinationParent*>(aActor);
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/IPCStreamSource.cpp b/ipc/glue/IPCStreamSource.cpp
new file mode 100644
index 0000000000..3ddf0c9e52
--- /dev/null
+++ b/ipc/glue/IPCStreamSource.cpp
@@ -0,0 +1,277 @@
+/* -*- 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 "IPCStreamSource.h"
+
+#include "BackgroundParent.h" // for AssertIsOnBackgroundThread
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/RemoteWorkerService.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "nsIAsyncInputStream.h"
+#include "nsICancelableRunnable.h"
+#include "nsIRunnable.h"
+#include "nsISerialEventTarget.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+
+using mozilla::wr::ByteBuffer;
+
+namespace mozilla {
+namespace ipc {
+
+class IPCStreamSource::Callback final : public DiscardableRunnable,
+ public nsIInputStreamCallback {
+ public:
+ explicit Callback(IPCStreamSource* aSource)
+ : DiscardableRunnable("IPCStreamSource::Callback"),
+ mSource(aSource),
+ mOwningEventTarget(GetCurrentSerialEventTarget()) {
+ MOZ_ASSERT(mSource);
+ }
+
+ NS_IMETHOD
+ OnInputStreamReady(nsIAsyncInputStream* aStream) override {
+ // any thread
+ if (mOwningEventTarget->IsOnCurrentThread()) {
+ return Run();
+ }
+
+ // If this fails, then it means the owning thread is a Worker that has
+ // been shutdown. Its ok to lose the event in this case because the
+ // IPCStreamChild listens for this event through the WorkerRef.
+ nsresult rv =
+ mOwningEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch stream readable event to owning thread");
+ }
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ Run() override {
+ MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
+ if (mSource) {
+ mSource->OnStreamReady(this);
+ }
+ return NS_OK;
+ }
+
+ // OnDiscard() gets called when the Worker thread is being shutdown. We have
+ // nothing to do here because IPCStreamChild handles this case via
+ // the WorkerRef.
+
+ void ClearSource() {
+ MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
+ MOZ_ASSERT(mSource);
+ mSource = nullptr;
+ }
+
+ private:
+ ~Callback() {
+ // called on any thread
+
+ // ClearSource() should be called before the Callback is destroyed
+ MOZ_ASSERT(!mSource);
+ }
+
+ // This is a raw pointer because the source keeps alive the callback and,
+ // before beeing destroyed, it nullifies this pointer (this happens when
+ // ActorDestroyed() is called).
+ IPCStreamSource* mSource;
+
+ nsCOMPtr<nsISerialEventTarget> mOwningEventTarget;
+
+ NS_DECL_ISUPPORTS_INHERITED
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(IPCStreamSource::Callback, DiscardableRunnable,
+ nsIInputStreamCallback);
+
+IPCStreamSource::IPCStreamSource(nsIAsyncInputStream* aInputStream)
+ : mStream(aInputStream), mState(ePending) {
+ MOZ_ASSERT(aInputStream);
+}
+
+IPCStreamSource::~IPCStreamSource() {
+ NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
+ MOZ_ASSERT(mState == eClosed);
+ MOZ_ASSERT(!mCallback);
+ MOZ_ASSERT(!mWorkerRef);
+}
+
+bool IPCStreamSource::Initialize() {
+ bool nonBlocking = false;
+ MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mStream->IsNonBlocking(&nonBlocking)));
+ // IPCStreamChild reads in the current thread, so it is only supported on
+ // non-blocking, async channels
+ if (!nonBlocking) {
+ return false;
+ }
+
+ // A source can be used on any thread, but we only support IPCStream on
+ // main thread, Workers, Worker Launcher, and PBackground thread right now.
+ // This is due to the requirement that the thread be guaranteed to live long
+ // enough to receive messages. We can enforce this guarantee with a
+ // StrongWorkerRef on worker threads, but not other threads. Main-thread,
+ // PBackground, and Worker Launcher threads do not need anything special in
+ // order to be kept alive.
+ if (!NS_IsMainThread()) {
+ if (const auto workerPrivate = dom::GetCurrentThreadWorkerPrivate()) {
+ RefPtr<dom::StrongWorkerRef> workerRef =
+ dom::StrongWorkerRef::CreateForcibly(workerPrivate,
+ "IPCStreamSource");
+ if (NS_WARN_IF(!workerRef)) {
+ return false;
+ }
+
+ mWorkerRef = std::move(workerRef);
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(
+ IsOnBackgroundThread() ||
+ dom::RemoteWorkerService::Thread()->IsOnCurrentThread());
+ }
+ }
+
+ return true;
+}
+
+void IPCStreamSource::ActorConstructed() {
+ MOZ_ASSERT(mState == ePending);
+ mState = eActorConstructed;
+}
+
+void IPCStreamSource::ActorDestroyed() {
+ NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
+
+ mState = eClosed;
+
+ if (mCallback) {
+ mCallback->ClearSource();
+ mCallback = nullptr;
+ }
+
+ mWorkerRef = nullptr;
+}
+
+void IPCStreamSource::Start() {
+ NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
+ DoRead();
+}
+
+void IPCStreamSource::StartDestroy() {
+ NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
+ OnEnd(NS_ERROR_ABORT);
+}
+
+void IPCStreamSource::DoRead() {
+ NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
+ MOZ_ASSERT(mState == eActorConstructed);
+ MOZ_ASSERT(!mCallback);
+
+ // The input stream (likely a pipe) probably uses a segment size of
+ // 4kb. If there is data already buffered it would be nice to aggregate
+ // multiple segments into a single IPC call. Conversely, don't send too
+ // too large of a buffer in a single call to avoid spiking memory.
+ static const uint64_t kMaxBytesPerMessage = 32 * 1024;
+ static_assert(kMaxBytesPerMessage <= static_cast<uint64_t>(UINT32_MAX),
+ "kMaxBytesPerMessage must cleanly cast to uint32_t");
+
+ UniquePtr<char[]> buffer(new char[kMaxBytesPerMessage]);
+
+ while (true) {
+ // It should not be possible to transition to closed state without
+ // this loop terminating via a return.
+ MOZ_ASSERT(mState == eActorConstructed);
+
+ // See if the stream is closed by checking the return of Available.
+ uint64_t dummy;
+ nsresult rv = mStream->Available(&dummy);
+ if (NS_FAILED(rv)) {
+ OnEnd(rv);
+ return;
+ }
+
+ uint32_t bytesRead = 0;
+ rv = mStream->Read(buffer.get(), kMaxBytesPerMessage, &bytesRead);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ MOZ_ASSERT(bytesRead == 0);
+ Wait();
+ return;
+ }
+
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(bytesRead == 0);
+ OnEnd(rv);
+ return;
+ }
+
+ // Zero-byte read indicates end-of-stream.
+ if (bytesRead == 0) {
+ OnEnd(NS_BASE_STREAM_CLOSED);
+ return;
+ }
+
+ // We read some data from the stream, send it across.
+ SendData(ByteBuffer(bytesRead, reinterpret_cast<uint8_t*>(buffer.get())));
+ }
+}
+
+void IPCStreamSource::Wait() {
+ NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
+ MOZ_ASSERT(mState == eActorConstructed);
+ MOZ_ASSERT(!mCallback);
+
+ // Set mCallback immediately instead of waiting for success. Its possible
+ // AsyncWait() will callback synchronously.
+ mCallback = new Callback(this);
+ nsresult rv = mStream->AsyncWait(mCallback, 0, 0, nullptr);
+ if (NS_FAILED(rv)) {
+ OnEnd(rv);
+ return;
+ }
+}
+
+void IPCStreamSource::OnStreamReady(Callback* aCallback) {
+ NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
+ MOZ_ASSERT(mCallback);
+ MOZ_ASSERT(aCallback == mCallback);
+ mCallback->ClearSource();
+ mCallback = nullptr;
+
+ // Possibly closed if this callback is (indirectly) called by
+ // IPCStreamSourceParent::RecvRequestClose().
+ if (mState == eClosed) {
+ return;
+ }
+
+ DoRead();
+}
+
+void IPCStreamSource::OnEnd(nsresult aRv) {
+ NS_ASSERT_OWNINGTHREAD(IPCStreamSource);
+ MOZ_ASSERT(aRv != NS_BASE_STREAM_WOULD_BLOCK);
+
+ if (mState == eClosed) {
+ return;
+ }
+
+ mState = eClosed;
+
+ mStream->CloseWithStatus(aRv);
+
+ if (aRv == NS_BASE_STREAM_CLOSED) {
+ aRv = NS_OK;
+ }
+
+ // This will trigger an ActorDestroy() from the other side
+ Close(aRv);
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/IPCStreamSource.h b/ipc/glue/IPCStreamSource.h
new file mode 100644
index 0000000000..b4284a8a12
--- /dev/null
+++ b/ipc/glue/IPCStreamSource.h
@@ -0,0 +1,124 @@
+/* -*- 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_IPCStreamSource_h
+#define mozilla_ipc_IPCStreamSource_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/dom/WorkerRef.h"
+
+class nsIAsyncInputStream;
+
+namespace mozilla {
+
+namespace wr {
+struct ByteBuffer;
+} // namespace wr
+
+namespace ipc {
+
+class ParentToChildStreamActorManager;
+class ChildToParentStreamActorManager;
+class PChildToParentStreamChild;
+class PParentToChildStreamParent;
+
+// The IPCStream IPC actor is designed to push an nsIInputStream from child to
+// parent or parent to child incrementally. This is mainly needed for streams
+// such as nsPipe that may not yet have all their data available when the
+// stream must be sent across an IPC boundary. While many streams are handled
+// by SerializeInputStream(), these streams cannot be serialized and must be
+// sent using this actor.
+//
+// The IPCStream actor only support async, non-blocking streams because they
+// must be read inline on the main thread and Worker threads.
+//
+// In general, the creation and handling of the IPCStream actor cannot be
+// abstracted away behind SerializeInputStream() because the actor must be
+// carefully managed. Specifically:
+//
+// 1) The data flow must be explicitly initiated by calling
+// IPCStreamSource{Child,Parent}::Start() after the actor has been sent to
+// the other-side actor.
+// 2) If the actor is never sent to the other-side, then this code must
+// call IPCStreamSource{Child,Parent}::StartDestroy() to avoid memory leaks.
+// 3) The IPCStreamSource actor can only be used on threads that can be
+// guaranteed to stay alive as long as the actor is alive. Right now
+// this limits IPCStream to the main thread and Worker threads.
+//
+// In general you should probably use the AutoIPCStreamSource RAII class
+// defined in InputStreamUtils.h instead of using IPCStreamSource directly.
+class IPCStreamSource {
+ public:
+ // Create a IPCStreamSource using a ChildToParentStreamActorManager manager.
+ // This can return nullptr if the provided stream is blocking.
+ static PChildToParentStreamChild* Create(
+ nsIAsyncInputStream* aInputStream,
+ ChildToParentStreamActorManager* aManager);
+
+ // Create a IPCStreamSource using a ParentToChildStreamActorManager manager.
+ // This can return nullptr if the provided stream is blocking.
+ static PParentToChildStreamParent* Create(
+ nsIAsyncInputStream* aInputStream,
+ ParentToChildStreamActorManager* aManager);
+
+ static IPCStreamSource* Cast(PChildToParentStreamChild* aActor);
+
+ static IPCStreamSource* Cast(PParentToChildStreamParent* aActor);
+
+ // Start reading data from the nsIAsyncInputStream used to create the actor.
+ // This must be called after the actor is passed to the parent. If you
+ // use AutoIPCStream this is handled automatically.
+ void Start();
+
+ // Start cleaning up the actor. This must be called if the actor is never
+ // sent to the other side. If you use AutoIPCStream this is handled
+ // automatically.
+ void StartDestroy();
+
+ protected:
+ IPCStreamSource(nsIAsyncInputStream* aInputStream);
+ virtual ~IPCStreamSource();
+
+ bool Initialize();
+
+ void ActorDestroyed();
+
+ void OnEnd(nsresult aRv);
+
+ virtual void Close(nsresult aRv) = 0;
+
+ virtual void SendData(const wr::ByteBuffer& aBuffer) = 0;
+
+ void ActorConstructed();
+
+ private:
+ class Callback;
+
+ void DoRead();
+
+ void Wait();
+
+ void OnStreamReady(Callback* aCallback);
+
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+ RefPtr<Callback> mCallback;
+
+ RefPtr<dom::StrongWorkerRef> mWorkerRef;
+
+#ifdef DEBUG
+ protected:
+#endif
+
+ enum { ePending, eActorConstructed, eClosed } mState;
+
+ private:
+ NS_DECL_OWNINGTHREAD
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_IPCStreamSource_h
diff --git a/ipc/glue/IPCStreamUtils.cpp b/ipc/glue/IPCStreamUtils.cpp
new file mode 100644
index 0000000000..fba98891fe
--- /dev/null
+++ b/ipc/glue/IPCStreamUtils.cpp
@@ -0,0 +1,560 @@
+/* -*- 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 "nsIIPCSerializableInputStream.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/ipc/FileDescriptorSetChild.h"
+#include "mozilla/ipc/FileDescriptorSetParent.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/Unused.h"
+#include "nsNetCID.h"
+#include "BackgroundParentImpl.h"
+#include "BackgroundChildImpl.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace ipc {
+
+namespace {
+
+// These serialization and cleanup functions could be externally exposed. For
+// now, though, keep them private to encourage use of the safer RAII
+// AutoIPCStream class.
+
+template <typename M>
+bool SerializeInputStreamWithFdsChild(nsIIPCSerializableInputStream* aStream,
+ IPCStream& aValue, bool aDelayedStart,
+ M* aManager) {
+ MOZ_RELEASE_ASSERT(aStream);
+ MOZ_ASSERT(aManager);
+
+ const uint64_t kTooLargeStream = 1024 * 1024;
+
+ uint32_t sizeUsed = 0;
+ AutoTArray<FileDescriptor, 4> fds;
+ aStream->Serialize(aValue.stream(), fds, aDelayedStart, kTooLargeStream,
+ &sizeUsed, aManager);
+
+ MOZ_ASSERT(sizeUsed <= kTooLargeStream);
+
+ if (aValue.stream().type() == InputStreamParams::T__None) {
+ MOZ_CRASH("Serialize failed!");
+ }
+
+ if (fds.IsEmpty()) {
+ aValue.optionalFds() = void_t();
+ } else {
+ PFileDescriptorSetChild* fdSet =
+ aManager->SendPFileDescriptorSetConstructor(fds[0]);
+ for (uint32_t i = 1; i < fds.Length(); ++i) {
+ Unused << fdSet->SendAddFileDescriptor(fds[i]);
+ }
+
+ aValue.optionalFds() = fdSet;
+ }
+
+ return true;
+}
+
+template <typename M>
+bool SerializeInputStreamWithFdsParent(nsIIPCSerializableInputStream* aStream,
+ IPCStream& aValue, bool aDelayedStart,
+ M* aManager) {
+ MOZ_RELEASE_ASSERT(aStream);
+ MOZ_ASSERT(aManager);
+
+ const uint64_t kTooLargeStream = 1024 * 1024;
+
+ uint32_t sizeUsed = 0;
+ AutoTArray<FileDescriptor, 4> fds;
+ aStream->Serialize(aValue.stream(), fds, aDelayedStart, kTooLargeStream,
+ &sizeUsed, aManager);
+
+ MOZ_ASSERT(sizeUsed <= kTooLargeStream);
+
+ if (aValue.stream().type() == InputStreamParams::T__None) {
+ MOZ_CRASH("Serialize failed!");
+ }
+
+ aValue.optionalFds() = void_t();
+ if (!fds.IsEmpty()) {
+ PFileDescriptorSetParent* fdSet =
+ aManager->SendPFileDescriptorSetConstructor(fds[0]);
+ for (uint32_t i = 1; i < fds.Length(); ++i) {
+ if (NS_WARN_IF(!fdSet->SendAddFileDescriptor(fds[i]))) {
+ Unused << PFileDescriptorSetParent::Send__delete__(fdSet);
+ fdSet = nullptr;
+ break;
+ }
+ }
+
+ if (fdSet) {
+ aValue.optionalFds() = fdSet;
+ }
+ }
+
+ return true;
+}
+
+template <typename M>
+bool SerializeInputStream(nsIInputStream* aStream, IPCStream& aValue,
+ M* aManager, bool aDelayedStart) {
+ MOZ_ASSERT(aStream);
+ MOZ_ASSERT(aManager);
+
+ InputStreamParams params;
+ InputStreamHelper::SerializeInputStreamAsPipe(aStream, params, aDelayedStart,
+ aManager);
+
+ if (params.type() == InputStreamParams::T__None) {
+ return false;
+ }
+
+ aValue.stream() = params;
+ aValue.optionalFds() = void_t();
+
+ return true;
+}
+
+template <typename M>
+bool SerializeInputStreamChild(nsIInputStream* aStream, M* aManager,
+ IPCStream* aValue,
+ Maybe<IPCStream>* aOptionalValue,
+ bool aDelayedStart) {
+ MOZ_ASSERT(aStream);
+ MOZ_ASSERT(aManager);
+ MOZ_ASSERT(aValue || aOptionalValue);
+
+ nsCOMPtr<nsIIPCSerializableInputStream> serializable =
+ do_QueryInterface(aStream);
+
+ if (serializable) {
+ if (aValue) {
+ return SerializeInputStreamWithFdsChild(serializable, *aValue,
+ aDelayedStart, aManager);
+ }
+
+ return SerializeInputStreamWithFdsChild(serializable, aOptionalValue->ref(),
+ aDelayedStart, aManager);
+ }
+
+ if (aValue) {
+ return SerializeInputStream(aStream, *aValue, aManager, aDelayedStart);
+ }
+
+ return SerializeInputStream(aStream, aOptionalValue->ref(), aManager,
+ aDelayedStart);
+}
+
+template <typename M>
+bool SerializeInputStreamParent(nsIInputStream* aStream, M* aManager,
+ IPCStream* aValue,
+ Maybe<IPCStream>* aOptionalValue,
+ bool aDelayedStart) {
+ MOZ_ASSERT(aStream);
+ MOZ_ASSERT(aManager);
+ MOZ_ASSERT(aValue || aOptionalValue);
+
+ nsCOMPtr<nsIIPCSerializableInputStream> serializable =
+ do_QueryInterface(aStream);
+
+ if (serializable) {
+ if (aValue) {
+ return SerializeInputStreamWithFdsParent(serializable, *aValue,
+ aDelayedStart, aManager);
+ }
+
+ return SerializeInputStreamWithFdsParent(
+ serializable, aOptionalValue->ref(), aDelayedStart, aManager);
+ }
+
+ if (aValue) {
+ return SerializeInputStream(aStream, *aValue, aManager, aDelayedStart);
+ }
+
+ return SerializeInputStream(aStream, aOptionalValue->ref(), aManager,
+ aDelayedStart);
+}
+
+void ActivateAndCleanupIPCStream(IPCStream& aValue, bool aConsumedByIPC,
+ bool aDelayedStart) {
+ // Cleanup file descriptors if necessary
+ if (aValue.optionalFds().type() ==
+ OptionalFileDescriptorSet::TPFileDescriptorSetChild) {
+ AutoTArray<FileDescriptor, 4> fds;
+
+ auto fdSetActor = static_cast<FileDescriptorSetChild*>(
+ aValue.optionalFds().get_PFileDescriptorSetChild());
+ MOZ_ASSERT(fdSetActor);
+
+ // FileDescriptorSet doesn't clear its fds in its ActorDestroy, so we
+ // unconditionally forget them here. The fds themselves are auto-closed
+ // in ~FileDescriptor since they originated in this process.
+ fdSetActor->ForgetFileDescriptors(fds);
+
+ if (!aConsumedByIPC) {
+ Unused << FileDescriptorSetChild::Send__delete__(fdSetActor);
+ }
+
+ } else if (aValue.optionalFds().type() ==
+ OptionalFileDescriptorSet::TPFileDescriptorSetParent) {
+ AutoTArray<FileDescriptor, 4> fds;
+
+ auto fdSetActor = static_cast<FileDescriptorSetParent*>(
+ aValue.optionalFds().get_PFileDescriptorSetParent());
+ MOZ_ASSERT(fdSetActor);
+
+ // FileDescriptorSet doesn't clear its fds in its ActorDestroy, so we
+ // unconditionally forget them here. The fds themselves are auto-closed
+ // in ~FileDescriptor since they originated in this process.
+ fdSetActor->ForgetFileDescriptors(fds);
+
+ if (!aConsumedByIPC) {
+ Unused << FileDescriptorSetParent::Send__delete__(fdSetActor);
+ }
+ }
+
+ // Activate IPCRemoteStreamParams.
+ InputStreamHelper::PostSerializationActivation(aValue.stream(),
+ aConsumedByIPC, aDelayedStart);
+}
+
+void ActivateAndCleanupIPCStream(Maybe<IPCStream>& aValue, bool aConsumedByIPC,
+ bool aDelayedStart) {
+ if (aValue.isNothing()) {
+ return;
+ }
+
+ ActivateAndCleanupIPCStream(aValue.ref(), aConsumedByIPC, aDelayedStart);
+}
+
+// Returns false if the serialization should not proceed. This means that the
+// inputStream is null.
+bool NormalizeOptionalValue(nsIInputStream* aStream, IPCStream* aValue,
+ Maybe<IPCStream>* aOptionalValue) {
+ if (aValue) {
+ // if aStream is null, we will crash when serializing.
+ return true;
+ }
+
+ if (!aStream) {
+ aOptionalValue->reset();
+ return false;
+ }
+
+ aOptionalValue->emplace();
+ return true;
+}
+
+} // anonymous namespace
+
+already_AddRefed<nsIInputStream> DeserializeIPCStream(const IPCStream& aValue) {
+ // Note, we explicitly do not support deserializing the PChildToParentStream
+ // actor on the child side nor the PParentToChildStream actor on the parent
+ // side.
+
+ AutoTArray<FileDescriptor, 4> fds;
+ if (aValue.optionalFds().type() ==
+ OptionalFileDescriptorSet::TPFileDescriptorSetParent) {
+ auto fdSetActor = static_cast<FileDescriptorSetParent*>(
+ aValue.optionalFds().get_PFileDescriptorSetParent());
+ MOZ_ASSERT(fdSetActor);
+
+ fdSetActor->ForgetFileDescriptors(fds);
+ MOZ_ASSERT(!fds.IsEmpty());
+
+ if (!FileDescriptorSetParent::Send__delete__(fdSetActor)) {
+ // child process is gone, warn and allow actor to clean up normally
+ NS_WARNING("Failed to delete fd set actor.");
+ }
+ } else if (aValue.optionalFds().type() ==
+ OptionalFileDescriptorSet::TPFileDescriptorSetChild) {
+ auto fdSetActor = static_cast<FileDescriptorSetChild*>(
+ aValue.optionalFds().get_PFileDescriptorSetChild());
+ MOZ_ASSERT(fdSetActor);
+
+ fdSetActor->ForgetFileDescriptors(fds);
+ MOZ_ASSERT(!fds.IsEmpty());
+
+ Unused << FileDescriptorSetChild::Send__delete__(fdSetActor);
+ }
+
+ return InputStreamHelper::DeserializeInputStream(aValue.stream(), fds);
+}
+
+already_AddRefed<nsIInputStream> DeserializeIPCStream(
+ const Maybe<IPCStream>& aValue) {
+ if (aValue.isNothing()) {
+ return nullptr;
+ }
+
+ return DeserializeIPCStream(aValue.ref());
+}
+
+AutoIPCStream::AutoIPCStream(bool aDelayedStart)
+ : mOptionalValue(&mInlineValue), mDelayedStart(aDelayedStart) {}
+
+AutoIPCStream::AutoIPCStream(IPCStream& aTarget, bool aDelayedStart)
+ : mValue(&aTarget), mDelayedStart(aDelayedStart) {}
+
+AutoIPCStream::AutoIPCStream(Maybe<IPCStream>& aTarget, bool aDelayedStart)
+ : mOptionalValue(&aTarget), mDelayedStart(aDelayedStart) {
+ mOptionalValue->reset();
+}
+
+AutoIPCStream::~AutoIPCStream() {
+ MOZ_ASSERT(mValue || mOptionalValue);
+ if (mValue && IsSet()) {
+ ActivateAndCleanupIPCStream(*mValue, mTaken, mDelayedStart);
+ } else {
+ ActivateAndCleanupIPCStream(*mOptionalValue, mTaken, mDelayedStart);
+ }
+}
+
+bool AutoIPCStream::Serialize(nsIInputStream* aStream,
+ dom::ContentChild* aManager) {
+ MOZ_ASSERT(aStream || !mValue);
+ MOZ_ASSERT(aManager);
+ MOZ_ASSERT(mValue || mOptionalValue);
+ MOZ_ASSERT(!mTaken);
+ MOZ_ASSERT(!IsSet());
+
+ // If NormalizeOptionalValue returns false, we don't have to proceed.
+ if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) {
+ return true;
+ }
+
+ if (!SerializeInputStreamChild(aStream, aManager, mValue, mOptionalValue,
+ mDelayedStart)) {
+ MOZ_CRASH("IPCStream creation failed!");
+ }
+
+ return true;
+}
+
+bool AutoIPCStream::Serialize(nsIInputStream* aStream,
+ PBackgroundChild* aManager) {
+ MOZ_ASSERT(aStream || !mValue);
+ MOZ_ASSERT(aManager);
+ MOZ_ASSERT(mValue || mOptionalValue);
+ MOZ_ASSERT(!mTaken);
+ MOZ_ASSERT(!IsSet());
+
+ // If NormalizeOptionalValue returns false, we don't have to proceed.
+ if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) {
+ return true;
+ }
+
+ BackgroundChildImpl* impl = static_cast<BackgroundChildImpl*>(aManager);
+ if (!SerializeInputStreamChild(aStream, impl, mValue, mOptionalValue,
+ mDelayedStart)) {
+ MOZ_CRASH("IPCStream creation failed!");
+ }
+
+ return true;
+}
+
+bool AutoIPCStream::Serialize(nsIInputStream* aStream,
+ net::SocketProcessChild* aManager) {
+ MOZ_ASSERT(aStream || !mValue);
+ MOZ_ASSERT(aManager);
+ MOZ_ASSERT(mValue || mOptionalValue);
+ MOZ_ASSERT(!mTaken);
+ MOZ_ASSERT(!IsSet());
+
+ // If NormalizeOptionalValue returns false, we don't have to proceed.
+ if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) {
+ return true;
+ }
+
+ if (!SerializeInputStreamChild(aStream, aManager, mValue, mOptionalValue,
+ mDelayedStart)) {
+ MOZ_CRASH("IPCStream creation failed!");
+ }
+
+ return true;
+}
+
+bool AutoIPCStream::Serialize(nsIInputStream* aStream,
+ dom::ContentParent* aManager) {
+ MOZ_ASSERT(aStream || !mValue);
+ MOZ_ASSERT(aManager);
+ MOZ_ASSERT(mValue || mOptionalValue);
+ MOZ_ASSERT(!mTaken);
+ MOZ_ASSERT(!IsSet());
+
+ // If NormalizeOptionalValue returns false, we don't have to proceed.
+ if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) {
+ return true;
+ }
+
+ if (!SerializeInputStreamParent(aStream, aManager, mValue, mOptionalValue,
+ mDelayedStart)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool AutoIPCStream::Serialize(nsIInputStream* aStream,
+ PBackgroundParent* aManager) {
+ MOZ_ASSERT(aStream || !mValue);
+ MOZ_ASSERT(aManager);
+ MOZ_ASSERT(mValue || mOptionalValue);
+ MOZ_ASSERT(!mTaken);
+ MOZ_ASSERT(!IsSet());
+
+ // If NormalizeOptionalValue returns false, we don't have to proceed.
+ if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) {
+ return true;
+ }
+
+ BackgroundParentImpl* impl = static_cast<BackgroundParentImpl*>(aManager);
+ if (!SerializeInputStreamParent(aStream, impl, mValue, mOptionalValue,
+ mDelayedStart)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool AutoIPCStream::Serialize(nsIInputStream* aStream,
+ net::SocketProcessParent* aManager) {
+ MOZ_ASSERT(aStream || !mValue);
+ MOZ_ASSERT(aManager);
+ MOZ_ASSERT(mValue || mOptionalValue);
+ MOZ_ASSERT(!mTaken);
+ MOZ_ASSERT(!IsSet());
+
+ // If NormalizeOptionalValue returns false, we don't have to proceed.
+ if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) {
+ return true;
+ }
+
+ if (!SerializeInputStreamParent(aStream, aManager, mValue, mOptionalValue,
+ mDelayedStart)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool AutoIPCStream::IsSet() const {
+ MOZ_ASSERT(mValue || mOptionalValue);
+ if (mValue) {
+ return mValue->stream().type() != InputStreamParams::T__None;
+ } else {
+ return mOptionalValue->isSome() &&
+ mOptionalValue->ref().stream().type() != InputStreamParams::T__None;
+ }
+}
+
+IPCStream& AutoIPCStream::TakeValue() {
+ MOZ_ASSERT(mValue || mOptionalValue);
+ MOZ_ASSERT(!mTaken);
+ MOZ_ASSERT(IsSet());
+
+ mTaken = true;
+
+ if (mValue) {
+ return *mValue;
+ }
+
+ IPCStream& value = mOptionalValue->ref();
+ return value;
+}
+
+Maybe<IPCStream>& AutoIPCStream::TakeOptionalValue() {
+ MOZ_ASSERT(!mTaken);
+ MOZ_ASSERT(!mValue);
+ MOZ_ASSERT(mOptionalValue);
+ mTaken = true;
+ return *mOptionalValue;
+}
+
+void IPDLParamTraits<nsIInputStream*>::Write(IPC::Message* aMsg,
+ IProtocol* aActor,
+ nsIInputStream* aParam) {
+ auto autoStream = MakeRefPtr<HoldIPCStream>();
+
+ bool ok = false;
+ bool found = false;
+
+ // We can only serialize our nsIInputStream if it's going to be sent over one
+ // of the protocols we support, or a protocol which is managed by one of the
+ // protocols we support.
+ IProtocol* actor = aActor;
+ while (!found && actor) {
+ switch (actor->GetProtocolId()) {
+ case PContentMsgStart:
+ if (actor->GetSide() == mozilla::ipc::ParentSide) {
+ ok = autoStream->Serialize(
+ aParam, static_cast<mozilla::dom::ContentParent*>(actor));
+ } else {
+ MOZ_RELEASE_ASSERT(actor->GetSide() == mozilla::ipc::ChildSide);
+ ok = autoStream->Serialize(
+ aParam, static_cast<mozilla::dom::ContentChild*>(actor));
+ }
+ found = true;
+ break;
+ case PBackgroundMsgStart:
+ if (actor->GetSide() == mozilla::ipc::ParentSide) {
+ ok = autoStream->Serialize(
+ aParam, static_cast<mozilla::ipc::PBackgroundParent*>(actor));
+ } else {
+ MOZ_RELEASE_ASSERT(actor->GetSide() == mozilla::ipc::ChildSide);
+ ok = autoStream->Serialize(
+ aParam, static_cast<mozilla::ipc::PBackgroundChild*>(actor));
+ }
+ found = true;
+ break;
+ default:
+ break;
+ }
+
+ // Try the actor's manager.
+ actor = actor->Manager();
+ }
+
+ if (!found) {
+ aActor->FatalError(
+ "Attempt to send nsIInputStream over an unsupported ipdl protocol");
+ }
+ MOZ_RELEASE_ASSERT(ok, "Failed to serialize nsIInputStream");
+
+ WriteIPDLParam(aMsg, aActor, autoStream->TakeOptionalValue());
+
+ // Dispatch the autoStream to an async runnable, so that we guarantee it
+ // outlives this callstack, and doesn't shut down any actors we created
+ // until after we've finished sending the current message.
+ NS_ProxyRelease("IPDLParamTraits<nsIInputStream*>::Write::autoStream",
+ NS_GetCurrentThread(), autoStream.forget(), true);
+}
+
+bool IPDLParamTraits<nsIInputStream*>::Read(const IPC::Message* aMsg,
+ PickleIterator* aIter,
+ IProtocol* aActor,
+ RefPtr<nsIInputStream>* aResult) {
+ mozilla::Maybe<mozilla::ipc::IPCStream> ipcStream;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &ipcStream)) {
+ return false;
+ }
+
+ *aResult = mozilla::ipc::DeserializeIPCStream(ipcStream);
+ return true;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/IPCStreamUtils.h b/ipc/glue/IPCStreamUtils.h
new file mode 100644
index 0000000000..da85472247
--- /dev/null
+++ b/ipc/glue/IPCStreamUtils.h
@@ -0,0 +1,218 @@
+/* -*- 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 {
+
+namespace dom {
+class ContentChild;
+class ContentParent;
+} // namespace dom
+
+namespace net {
+class SocketProcessParent;
+class SocketProcessChild;
+} // namespace net
+
+namespace ipc {
+
+class PBackgroundChild;
+class PBackgroundParent;
+
+// 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);
+
+// RAII helper class that serializes an nsIInputStream into an IPCStream struct.
+// Any file descriptor or PChildToParentStream actors are automatically managed
+// correctly.
+//
+// Here is a simple example:
+//
+// // in ipdl file
+// Protocol PMyStuff
+// {
+// parent:
+// async DoStuff(IPCStream aStream);
+// child:
+// async StuffDone(IPCStream aStream);
+// };
+//
+// // in child c++ code
+// void CallDoStuff(PMyStuffChild* aActor, nsIInputStream* aStream)
+// {
+// AutoIPCStream autoStream;
+// autoStream.Serialize(aStream, aActor->Manager());
+// aActor->SendDoStuff(autoStream.TakeValue());
+// }
+//
+// // in parent c++ code
+// bool
+// MyStuffParent::RecvDoStuff(const IPCStream& aIPCStream) {
+// nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aIPCStream);
+// // Do something with stream...
+//
+// // You can also serialize streams from parent-to-child as long as
+// // they don't require PChildToParentStream actor support.
+// AutoIPCStream anotherStream;
+// anotherStream.Serialize(mFileStream, Manager());
+// SendStuffDone(anotherStream.TakeValue());
+// }
+//
+// The AutoIPCStream RAII class may also be used if your stream is embedded
+// in a more complex IPDL structure. In this case you attach the AutoIPCStream
+// to the embedded IPCStream and call TakeValue() after you pass the structure.
+// For example:
+//
+// // in ipdl file
+// struct Stuff
+// {
+// IPCStream stream;
+// nsCString name;
+// };
+//
+// Protocol PMyStuff
+// {
+// parent:
+// async DoStuff(Stuff aStream);
+// };
+//
+// // in child c++ code
+// void CallDoStuff(PMyStuffChild* aActor, nsIInputStream* aStream)
+// {
+// Stuff stuff;
+// AutoIPCStream autoStream(stuff.stream()); // attach to IPCStream here
+// autoStream.Serialize(aStream, aActor->Manager());
+// aActor->SendDoStuff(stuff);
+// autoStream.TakeValue(); // call take value after send
+// }
+//
+// // in parent c++ code
+// bool
+// MyStuffParent::RecvDoStuff(const Stuff& aStuff) {
+// nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aStuff.stream());
+// /* do something with the nsIInputStream */
+// }
+//
+// Note: This example is about child-to-parent inputStream, but AutoIPCStream
+// works also parent-to-child.
+//
+// The AutoIPCStream class also supports Maybe<IPCStream> values. As long as
+// you did not initialize the object with a non-optional IPCStream, you can call
+// TakeOptionalValue() instead.
+//
+// The AutoIPCStream class can also be used to serialize nsIInputStream objects
+// on the parent side to send to the child. Currently, however, this only
+// works for directly serializable stream types. The PChildToParentStream actor
+// mechanism is not supported in this direction yet.
+//
+// Like SerializeInputStream(), the AutoIPCStream will crash if
+// serialization cannot be completed.
+//
+// NOTE: This is not a MOZ_STACK_CLASS so that it can be more easily integrated
+// with complex ipdl structures. For example, you may want to create an
+// array of RAII AutoIPCStream objects or build your own wrapping
+// RAII object to handle other actors that need to be cleaned up.
+class AutoIPCStream {
+ public:
+ // Implicitly create an Maybe<IPCStream> value. Either
+ // TakeValue() or TakeOptionalValue() can be used.
+ explicit AutoIPCStream(bool aDelayedStart = false);
+
+ // Wrap an existing IPCStream. Only TakeValue() may be
+ // used. If a nullptr nsIInputStream is passed to SerializeOrSend() then
+ // a crash will be forced.
+ explicit AutoIPCStream(IPCStream& aTarget, bool aDelayedStart = false);
+
+ // Wrap an existing Maybe<IPCStream>. Either TakeValue()
+ // or TakeOptionalValue can be used.
+ explicit AutoIPCStream(Maybe<IPCStream>& aTarget, bool aDelayedStart = false);
+
+ AutoIPCStream(const AutoIPCStream&) = delete;
+ AutoIPCStream(AutoIPCStream&&) = delete;
+
+ AutoIPCStream& operator=(const AutoIPCStream&) = delete;
+ AutoIPCStream& operator=(AutoIPCStream&&) = delete;
+
+ ~AutoIPCStream();
+
+ // Serialize the input stream or create a SendStream actor using the PContent
+ // manager. If neither of these succeed, then crash. This should only be
+ // used on the main thread.
+ bool Serialize(nsIInputStream* aStream, dom::ContentChild* aManager);
+
+ // Serialize the input stream or create a SendStream actor using the
+ // PBackground manager. If neither of these succeed, then crash. This can
+ // be called on the main thread or Worker threads.
+ bool Serialize(nsIInputStream* aStream, PBackgroundChild* aManager);
+
+ // Serialize the input stream or create a SendStream actor using the
+ // SocketProcess manager. If neither of these succeed, then crash. This
+ // should only be used on the main thread.
+ bool Serialize(nsIInputStream* aStream, net::SocketProcessChild* aManager);
+
+ // Serialize the input stream.
+ [[nodiscard]] bool Serialize(nsIInputStream* aStream,
+ dom::ContentParent* aManager);
+
+ // Serialize the input stream.
+ [[nodiscard]] bool Serialize(nsIInputStream* aStream,
+ PBackgroundParent* aManager);
+
+ // Serialize the input stream.
+ [[nodiscard]] bool Serialize(nsIInputStream* aStream,
+ net::SocketProcessParent* aManager);
+
+ // Get the IPCStream as a non-optional value. This will
+ // assert if a stream has not been serialized or if it has already been taken.
+ // This should only be called if the value is being, or has already been, sent
+ // to the other side.
+ IPCStream& TakeValue();
+
+ // Get the Maybe<IPCStream> value. This will assert if the value has already
+ // been taken. This should only be called if the value is being, or has
+ // already been, sent to the other side.
+ Maybe<IPCStream>& TakeOptionalValue();
+
+ private:
+ bool IsSet() const;
+
+ Maybe<IPCStream> mInlineValue;
+ IPCStream* const mValue = nullptr;
+ Maybe<IPCStream>* const mOptionalValue = nullptr;
+ bool mTaken = false;
+ const bool mDelayedStart;
+};
+
+class HoldIPCStream final : public AutoIPCStream {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(HoldIPCStream)
+
+ private:
+ ~HoldIPCStream() = default;
+};
+
+template <>
+struct IPDLParamTraits<nsIInputStream*> {
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ nsIInputStream* aParam);
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, RefPtr<nsIInputStream>* aResult);
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#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..f258bdcaf6
--- /dev/null
+++ b/ipc/glue/IPDLParamTraits.h
@@ -0,0 +1,444 @@
+/* -*- 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 "mozilla/UniquePtr.h"
+#include "mozilla/Variant.h"
+#include "mozilla/Tuple.h"
+#include "nsTArray.h"
+
+#include <type_traits>
+
+namespace mozilla {
+namespace ipc {
+
+class IProtocol;
+
+//
+// IPDLParamTraits are an extended version of ParamTraits. Unlike ParamTraits,
+// IPDLParamTraits supports passing an additional IProtocol* argument to the
+// write and read methods.
+//
+// This is important for serializing and deserializing types which require
+// knowledge of which protocol they're being sent over, such as actors and
+// nsIInputStreams.
+//
+// All types which already implement ParamTraits also support IPDLParamTraits.
+//
+template <typename P>
+struct IPDLParamTraits {
+ // This is the default impl which discards the actor parameter and calls into
+ // ParamTraits. Types which want to use the actor parameter must specialize
+ // IPDLParamTraits.
+ template <typename R>
+ static inline void Write(IPC::Message* aMsg, IProtocol*, R&& aParam) {
+ IPC::ParamTraits<P>::Write(aMsg, std::forward<R>(aParam));
+ }
+
+ template <typename R>
+ static inline bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol*, R* aResult) {
+ return IPC::ParamTraits<P>::Read(aMsg, aIter, aResult);
+ }
+};
+
+//
+// WriteIPDLParam and ReadIPDLParam are like IPC::WriteParam and IPC::ReadParam,
+// however, they also accept an extra actor argument, and use IPDLParamTraits
+// rather than ParamTraits.
+//
+// NOTE: WriteIPDLParam takes a universal reference, so that it can support
+// whatever reference type is supported by the underlying IPDLParamTraits::Write
+// implementation. See the comment on IPDLParamTraits<nsTArray<T>>::Write for
+// more information.
+//
+template <typename P>
+static MOZ_NEVER_INLINE void WriteIPDLParam(IPC::Message* aMsg,
+ IProtocol* aActor, P&& aParam) {
+ IPDLParamTraits<std::decay_t<P>>::Write(aMsg, aActor,
+ std::forward<P>(aParam));
+}
+
+template <typename P>
+static MOZ_NEVER_INLINE bool ReadIPDLParam(const IPC::Message* aMsg,
+ PickleIterator* aIter,
+ IProtocol* aActor, P* aResult) {
+ return IPDLParamTraits<P>::Read(aMsg, aIter, aActor, aResult);
+}
+
+template <typename P>
+static MOZ_NEVER_INLINE bool ReadIPDLParamInfallible(
+ const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor,
+ P* aResult, const char* aCrashMessage) {
+ bool ok = ReadIPDLParam(aMsg, aIter, aActor, aResult);
+ if (!ok) {
+ MOZ_CRASH_UNSAFE(aCrashMessage);
+ }
+ return ok;
+}
+
+constexpr void WriteIPDLParamList(IPC::Message*, IProtocol*) {}
+
+template <typename P, typename... Ps>
+static void WriteIPDLParamList(IPC::Message* aMsg, IProtocol* aActor,
+ P&& aParam, Ps&&... aParams) {
+ WriteIPDLParam(aMsg, aActor, std::forward<P>(aParam));
+ WriteIPDLParamList(aMsg, aActor, std::forward<Ps>(aParams)...);
+}
+
+constexpr bool ReadIPDLParamList(const IPC::Message*, PickleIterator*,
+ IProtocol*) {
+ return true;
+}
+
+template <typename P, typename... Ps>
+static bool ReadIPDLParamList(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, P* aResult, Ps*... aResults) {
+ return ReadIPDLParam(aMsg, aIter, aActor, aResult) &&
+ ReadIPDLParamList(aMsg, aIter, aActor, aResults...);
+}
+
+// When being passed `RefPtr<T>` or `nsCOMPtr<T>`, forward to a specialization
+// for the underlying target type. The parameter type will be passed as `T*`,
+// and result as `RefPtr<T>*`.
+//
+// This is done explicitly to ensure that the deleted `&&` overload for
+// `operator T*` is not selected in generic contexts, and to support
+// deserializing into `nsCOMPtr<T>`.
+template <typename T>
+struct IPDLParamTraits<RefPtr<T>> {
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const RefPtr<T>& aParam) {
+ IPDLParamTraits<T*>::Write(aMsg, aActor, aParam.get());
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, RefPtr<T>* aResult) {
+ return IPDLParamTraits<T*>::Read(aMsg, aIter, aActor, aResult);
+ }
+};
+
+template <typename T>
+struct IPDLParamTraits<nsCOMPtr<T>> {
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const nsCOMPtr<T>& aParam) {
+ IPDLParamTraits<T*>::Write(aMsg, aActor, aParam.get());
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, nsCOMPtr<T>* aResult) {
+ RefPtr<T> refptr;
+ if (!IPDLParamTraits<T*>::Read(aMsg, aIter, aActor, &refptr)) {
+ return false;
+ }
+ *aResult = refptr.forget();
+ return true;
+ }
+};
+
+// nsTArray support for IPDLParamTraits
+template <typename T>
+struct IPDLParamTraits<nsTArray<T>> {
+ // Some serializers need to take a mutable reference to their backing object,
+ // such as Shmem segments and Byte Buffers. These serializers take the
+ // backing data and move it into the IPC layer for efficiency. `Write` uses a
+ // forwarding reference as occasionally these types appear inside of IPDL
+ // arrays.
+ template <typename U>
+ static void Write(IPC::Message* aMsg, IProtocol* aActor, U&& aParam) {
+ uint32_t length = aParam.Length();
+ WriteIPDLParam(aMsg, aActor, length);
+
+ if (sUseWriteBytes) {
+ auto pickledLength = CheckedInt<int>(length) * sizeof(T);
+ MOZ_RELEASE_ASSERT(pickledLength.isValid());
+ aMsg->WriteBytes(aParam.Elements(), pickledLength.value());
+ } else {
+ WriteValues(aMsg, aActor, std::forward<U>(aParam));
+ }
+ }
+
+ // This method uses infallible allocation so that an OOM failure will
+ // show up as an OOM crash rather than an IPC FatalError.
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, nsTArray<T>* aResult) {
+ uint32_t length;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &length)) {
+ return false;
+ }
+
+ if (sUseWriteBytes) {
+ auto pickledLength = CheckedInt<int>(length) * sizeof(T);
+ if (!pickledLength.isValid() ||
+ !aMsg->HasBytesAvailable(aIter, pickledLength.value())) {
+ return false;
+ }
+
+ // XXX(nika): This currently default-constructs the backing data before
+ // passing it into ReadBytesInto, which is technically unnecessary here.
+ // Perhaps we should consider using an API which doesn't initialize the
+ // elements?
+ T* elements = aResult->AppendElements(length);
+ return aMsg->ReadBytesInto(aIter, elements, pickledLength.value());
+ }
+
+ // Each ReadIPDLParam<E> may read more than 1 byte each; this is an attempt
+ // to minimally validate that the length isn't much larger than what's
+ // actually available in aMsg. We cannot use |pickledLength|, like in the
+ // codepath above, because ReadIPDLParam can read variable amounts of data
+ // from aMsg.
+ if (!aMsg->HasBytesAvailable(aIter, length)) {
+ return false;
+ }
+
+ aResult->SetCapacity(length);
+
+ for (uint32_t index = 0; index < length; index++) {
+ T* element = aResult->AppendElement();
+ if (!ReadIPDLParam(aMsg, aIter, aActor, element)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private:
+ // Length has already been written. Const overload.
+ static void WriteValues(IPC::Message* aMsg, IProtocol* aActor,
+ const nsTArray<T>& aParam) {
+ for (auto& elt : aParam) {
+ WriteIPDLParam(aMsg, aActor, elt);
+ }
+ }
+
+ // Length has already been written. Rvalue overload.
+ static void WriteValues(IPC::Message* aMsg, IProtocol* aActor,
+ nsTArray<T>&& aParam) {
+ for (auto& elt : aParam) {
+ WriteIPDLParam(aMsg, aActor, std::move(elt));
+ }
+
+ // As we just moved all of our values out, let's clean up after ourselves &
+ // clear the input array. This means our move write method will act more
+ // like a traditional move constructor.
+ aParam.Clear();
+ }
+
+ // We write arrays of integer or floating-point data using a single pickling
+ // call, rather than writing each element individually. We deliberately do
+ // not use mozilla::IsPod here because it is perfectly reasonable to have
+ // a data structure T for which IsPod<T>::value is true, yet also have a
+ // {IPDL,}ParamTraits<T> specialization.
+ static const bool sUseWriteBytes =
+ (std::is_integral_v<T> || std::is_floating_point_v<T>);
+};
+
+template <typename T>
+struct IPDLParamTraits<CopyableTArray<T>> : IPDLParamTraits<nsTArray<T>> {};
+
+// Maybe support for IPDLParamTraits
+template <typename T>
+struct IPDLParamTraits<Maybe<T>> {
+ typedef Maybe<T> paramType;
+
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const Maybe<T>& aParam) {
+ bool isSome = aParam.isSome();
+ WriteIPDLParam(aMsg, aActor, isSome);
+
+ if (isSome) {
+ WriteIPDLParam(aMsg, aActor, aParam.ref());
+ }
+ }
+
+ static void Write(IPC::Message* aMsg, IProtocol* aActor, Maybe<T>&& aParam) {
+ bool isSome = aParam.isSome();
+ WriteIPDLParam(aMsg, aActor, isSome);
+
+ if (isSome) {
+ WriteIPDLParam(aMsg, aActor, std::move(aParam.ref()));
+ }
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, Maybe<T>* aResult) {
+ bool isSome;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &isSome)) {
+ return false;
+ }
+
+ if (isSome) {
+ aResult->emplace();
+ if (!ReadIPDLParam(aMsg, aIter, aActor, aResult->ptr())) {
+ return false;
+ }
+ } else {
+ aResult->reset();
+ }
+ return true;
+ }
+};
+
+template <typename T>
+struct IPDLParamTraits<UniquePtr<T>> {
+ typedef UniquePtr<T> paramType;
+
+ template <typename U>
+ static void Write(IPC::Message* aMsg, IProtocol* aActor, U&& aParam) {
+ bool isNull = aParam == nullptr;
+ WriteIPDLParam(aMsg, aActor, isNull);
+
+ if (!isNull) {
+ WriteValue(aMsg, aActor, std::forward<U>(aParam));
+ }
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, UniquePtr<T>* aResult) {
+ bool isNull = true;
+ if (!ReadParam(aMsg, aIter, &isNull)) {
+ return false;
+ }
+
+ if (isNull) {
+ aResult->reset();
+ } else {
+ *aResult = MakeUnique<T>();
+ if (!ReadIPDLParam(aMsg, aIter, aActor, aResult->get())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private:
+ // If we have an rvalue, clear out our passed-in parameter.
+ static void WriteValue(IPC::Message* aMsg, IProtocol* aActor,
+ UniquePtr<T>&& aParam) {
+ WriteIPDLParam(aMsg, aActor, std::move(*aParam.get()));
+ aParam = nullptr;
+ }
+
+ static void WriteValue(IPC::Message* aMsg, IProtocol* aActor,
+ const UniquePtr<T>& aParam) {
+ WriteIPDLParam(aMsg, aActor, *aParam.get());
+ }
+};
+
+template <typename... Ts>
+struct IPDLParamTraits<Tuple<Ts...>> {
+ typedef Tuple<Ts...> paramType;
+
+ template <typename U>
+ static void Write(IPC::Message* aMsg, IProtocol* aActor, U&& aParam) {
+ WriteInternal(aMsg, aActor, std::forward<U>(aParam),
+ std::index_sequence_for<Ts...>{});
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, Tuple<Ts...>* aResult) {
+ return ReadInternal(aMsg, aIter, aActor, *aResult,
+ std::index_sequence_for<Ts...>{});
+ }
+
+ private:
+ template <size_t... Is>
+ static void WriteInternal(IPC::Message* aMsg, IProtocol* aActor,
+ const Tuple<Ts...>& aParam,
+ std::index_sequence<Is...>) {
+ WriteIPDLParamList(aMsg, aActor, Get<Is>(aParam)...);
+ }
+
+ template <size_t... Is>
+ static void WriteInternal(IPC::Message* aMsg, IProtocol* aActor,
+ Tuple<Ts...>&& aParam, std::index_sequence<Is...>) {
+ WriteIPDLParamList(aMsg, aActor, std::move(Get<Is>(aParam))...);
+ }
+
+ template <size_t... Is>
+ static bool ReadInternal(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, Tuple<Ts...>& aResult,
+ std::index_sequence<Is...>) {
+ return ReadIPDLParamList(aMsg, aIter, aActor, &Get<Is>(aResult)...);
+ }
+};
+
+template <class... Ts>
+struct IPDLParamTraits<mozilla::Variant<Ts...>> {
+ typedef mozilla::Variant<Ts...> paramType;
+ using Tag = typename mozilla::detail::VariantTag<Ts...>::Type;
+
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aMsg, aActor, aParam.tag);
+ aParam.match(
+ [aMsg, aActor](const auto& t) { WriteIPDLParam(aMsg, aActor, t); });
+ }
+
+ static void Write(IPC::Message* aMsg, IProtocol* aActor, paramType&& aParam) {
+ WriteIPDLParam(aMsg, aActor, aParam.tag);
+ aParam.match([aMsg, aActor](auto& t) {
+ WriteIPDLParam(aMsg, aActor, std::move(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(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, Tag aTag, paramType* aResult) {
+ // 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 (aTag == 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 ReadIPDLParam(aMsg, aIter, aActor,
+ &aResult->template emplace<N - 1>());
+ }
+ return Next::Read(aMsg, aIter, aActor, aTag, aResult);
+ }
+
+ }; // 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(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, Tag aTag, paramType* aResult) {
+ return false;
+ }
+ };
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ Tag tag;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &tag)) {
+ return false;
+ }
+
+ return VariantReader<sizeof...(Ts)>::Read(aMsg, aIter, aActor, tag,
+ aResult);
+ }
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // defined(mozilla_ipc_IPDLParamTraits_h)
diff --git a/ipc/glue/IdleSchedulerChild.cpp b/ipc/glue/IdleSchedulerChild.cpp
new file mode 100644
index 0000000000..330d327bac
--- /dev/null
+++ b/ipc/glue/IdleSchedulerChild.cpp
@@ -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/. */
+
+#include "mozilla/ipc/IdleSchedulerChild.h"
+#include "mozilla/ipc/IdleSchedulerParent.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/IdlePeriodState.h"
+#include "BackgroundChild.h"
+
+namespace mozilla {
+namespace ipc {
+
+static IdleSchedulerChild* sMainThreadIdleScheduler = nullptr;
+
+IdleSchedulerChild::~IdleSchedulerChild() {
+ if (sMainThreadIdleScheduler == this) {
+ sMainThreadIdleScheduler = nullptr;
+ }
+ MOZ_ASSERT(!mIdlePeriodState);
+}
+
+void IdleSchedulerChild::Init(IdlePeriodState* aIdlePeriodState) {
+ mIdlePeriodState = aIdlePeriodState;
+
+ RefPtr<IdleSchedulerChild> scheduler = this;
+ auto resolve =
+ [&](Tuple<mozilla::Maybe<SharedMemoryHandle>, uint32_t>&& aResult) {
+ if (Get<0>(aResult)) {
+ mActiveCounter.SetHandle(*Get<0>(aResult), false);
+ mActiveCounter.Map(sizeof(int32_t));
+ mChildId = 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;
+}
+
+IdleSchedulerChild* IdleSchedulerChild::GetMainThreadIdleScheduler() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (sMainThreadIdleScheduler) {
+ return sMainThreadIdleScheduler;
+ }
+
+ ipc::PBackgroundChild* background =
+ ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (background) {
+ sMainThreadIdleScheduler = new ipc::IdleSchedulerChild();
+ background->SendPIdleSchedulerConstructor(sMainThreadIdleScheduler);
+ }
+ return sMainThreadIdleScheduler;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/IdleSchedulerChild.h b/ipc/glue/IdleSchedulerChild.h
new file mode 100644
index 0000000000..5b8aa11b7c
--- /dev/null
+++ b/ipc/glue/IdleSchedulerChild.h
@@ -0,0 +1,58 @@
+/* -*- 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();
+
+ static IdleSchedulerChild* GetMainThreadIdleScheduler();
+
+ private:
+ ~IdleSchedulerChild();
+
+ friend class BackgroundChildImpl;
+
+ // See IdleScheduleParent::sActiveChildCounter
+ base::SharedMemory mActiveCounter;
+
+ IdlePeriodState* mIdlePeriodState = nullptr;
+
+ uint32_t mChildId = 0;
+};
+
+} // 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..292f75eb01
--- /dev/null
+++ b/ipc/glue/IdleSchedulerParent.cpp
@@ -0,0 +1,301 @@
+/* -*- 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/Unused.h"
+#include "mozilla/ipc/IdleSchedulerParent.h"
+#include "nsSystemInfo.h"
+#include "nsThreadUtils.h"
+#include "nsITimer.h"
+
+namespace mozilla {
+namespace ipc {
+
+base::SharedMemory* IdleSchedulerParent::sActiveChildCounter = nullptr;
+std::bitset<NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT>
+ IdleSchedulerParent::sInUseChildCounters;
+LinkedList<IdleSchedulerParent> IdleSchedulerParent::sWaitingForIdle;
+Atomic<int32_t> IdleSchedulerParent::sMaxConcurrentIdleTasksInChildProcesses(
+ -1);
+uint32_t IdleSchedulerParent::sChildProcessesRunningPrioritizedOperation = 0;
+uint32_t IdleSchedulerParent::sChildProcessesAlive = 0;
+nsITimer* IdleSchedulerParent::sStarvationPreventer = nullptr;
+
+IdleSchedulerParent::IdleSchedulerParent() {
+ sChildProcessesAlive++;
+
+ if (sMaxConcurrentIdleTasksInChildProcesses == -1) {
+ // nsISystemInfo can be initialized only on the main thread.
+ // While waiting for the real logical core count behave as if there was just
+ // one core.
+ sMaxConcurrentIdleTasksInChildProcesses = 1;
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction("cpucount getter", [thread]() {
+ // Always pretend that there is at least one core for child processes.
+ // If there are multiple logical cores, reserve one for the parent
+ // process and for the non-main threads.
+ ProcessInfo processInfo = {};
+ if (NS_SUCCEEDED(CollectProcessInfo(processInfo)) &&
+ processInfo.cpuCount > 1) {
+ // On one and two processor (or hardware thread) systems this will
+ // allow one concurrent idle task.
+ sMaxConcurrentIdleTasksInChildProcesses =
+ std::max(processInfo.cpuCount - 1, 1);
+ // We have a new cpu count, reschedule idle scheduler.
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction("IdleSchedulerParent::Schedule", []() {
+ 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);
+ });
+ thread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ }
+ });
+ NS_DispatchBackgroundTask(runnable.forget(), NS_DISPATCH_EVENT_MAY_BLOCK);
+ }
+}
+
+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 (isInList()) {
+ remove();
+ }
+
+ MOZ_ASSERT(sChildProcessesAlive > 0);
+ sChildProcessesAlive--;
+ if (sChildProcessesAlive == 0) {
+ MOZ_ASSERT(sWaitingForIdle.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;
+ SharedMemoryHandle handle;
+ if (sActiveChildCounter &&
+ sActiveChildCounter->ShareToProcess(OtherPid(), &handle)) {
+ activeCounter.emplace(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(Tuple<const mozilla::Maybe<SharedMemoryHandle>&, const uint32_t&>(
+ activeCounter, mChildId));
+ return IPC_OK();
+}
+
+IPCResult IdleSchedulerParent::RecvRequestIdleTime(uint64_t aId,
+ TimeDuration aBudget) {
+ MOZ_ASSERT(aBudget);
+ MOZ_ASSERT(IsNotDoingIdleTask());
+
+ mCurrentRequestId = aId;
+ mRequestedIdleBudget = aBudget;
+
+ sWaitingForIdle.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()) {
+ 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();
+}
+
+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;
+}
+
+void IdleSchedulerParent::SendIdleTime() {
+ // We would assert that IsWaiting() except after removing the task from it's
+ // list this will return false. Instead check IsDoingIdleTask()
+ MOZ_ASSERT(IsDoingIdleTask());
+ Unused << SendIdleTime(mCurrentRequestId, mRequestedIdleBudget);
+}
+
+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) {
+ // If the requester is prioritized, just let it run itself.
+ if (aRequester->isInList()) {
+ aRequester->remove();
+ }
+ aRequester->SendIdleTime();
+ activeCount++;
+ }
+
+ while (!sWaitingForIdle.isEmpty() && HasSpareCycles(activeCount)) {
+ // We can run an idle task.
+ RefPtr<IdleSchedulerParent> idleRequester = sWaitingForIdle.popFirst();
+ idleRequester->SendIdleTime();
+ activeCount++;
+ }
+
+ if (!sWaitingForIdle.isEmpty()) {
+ EnsureStarvationTimer();
+ }
+}
+
+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) {
+ if (!sWaitingForIdle.isEmpty()) {
+ RefPtr<IdleSchedulerParent> first = sWaitingForIdle.getFirst();
+ // Treat the first process waiting for idle time as running prioritized
+ // operation so that it gets run.
+ ++first->mRunningPrioritizedOperation;
+ ++sChildProcessesRunningPrioritizedOperation;
+ Schedule(first);
+ --first->mRunningPrioritizedOperation;
+ --sChildProcessesRunningPrioritizedOperation;
+ }
+ NS_RELEASE(sStarvationPreventer);
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/IdleSchedulerParent.h b/ipc/glue/IdleSchedulerParent.h
new file mode 100644
index 0000000000..01696e6525
--- /dev/null
+++ b/ipc/glue/IdleSchedulerParent.h
@@ -0,0 +1,114 @@
+/* -*- 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();
+
+ private:
+ friend class BackgroundParentImpl;
+ IdleSchedulerParent();
+ ~IdleSchedulerParent();
+
+ static int32_t ActiveCount();
+ static void Schedule(IdleSchedulerParent* aRequester);
+ static bool HasSpareCycles(int32_t aActiveCount);
+ using PIdleSchedulerParent::SendIdleTime;
+ void SendIdleTime();
+
+ 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;
+
+ uint32_t mChildId = 0;
+
+ // Current state, only one of these may be true at a time.
+ bool IsWaitingForIdle() const {
+ MOZ_ASSERT_IF(isInList(), mRequestedIdleBudget);
+ return isInList();
+ }
+ bool IsDoingIdleTask() const { return !isInList() && mRequestedIdleBudget; }
+ bool IsNotDoingIdleTask() const {
+ return !isInList() && !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 idle time, but haven't got it yet.
+ // Their mRequestedIdleBudget field is non-zero.
+ // Child processes not on this list have either been granted their request for
+ // idle time (mRequestedIdleBudget is non-zero) or have not requested idle
+ // time ever or since they last finished an idle task or (mRequestedIdleBudget
+ // is zero),
+ static LinkedList<IdleSchedulerParent> sWaitingForIdle;
+
+ static Atomic<int32_t> sMaxConcurrentIdleTasksInChildProcesses;
+
+ // 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;
+};
+
+} // 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..c7208eaf7f
--- /dev/null
+++ b/ipc/glue/InputStreamParams.ipdlh
@@ -0,0 +1,125 @@
+/* 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;
+
+include protocol PChildToParentStream;
+include protocol PParentToChildStream;
+include protocol PRemoteLazyInputStream;
+
+using struct mozilla::void_t from "mozilla/ipc/IPCCore.h";
+
+namespace mozilla {
+namespace ipc {
+
+struct HeaderEntry
+{
+ nsCString name;
+ nsCString value;
+};
+
+struct StringInputStreamParams
+{
+ nsCString data;
+};
+
+struct FileInputStreamParams
+{
+ uint32_t fileDescriptorIndex;
+ 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 RemoteLazyInputStreamRef
+{
+ nsID id;
+ uint64_t start;
+ uint64_t length;
+};
+
+union RemoteLazyInputStreamParams
+{
+ RemoteLazyInputStreamRef;
+ PRemoteLazyInputStream;
+};
+
+union IPCRemoteStreamType
+{
+ PChildToParentStream;
+ PParentToChildStream;
+};
+
+struct IPCRemoteStreamParams
+{
+ // If this is true, the stream will send data only when the first operation
+ // is done on the destination side. The benefit of this is that we do not
+ // send data if not needed. On the other hand, it could have a performance
+ // issue.
+ bool delayedStart;
+
+ IPCRemoteStreamType stream;
+
+ int64_t length;
+};
+
+union InputStreamParams
+{
+ StringInputStreamParams;
+ FileInputStreamParams;
+ BufferedInputStreamParams;
+ MIMEInputStreamParams;
+ MultiplexInputStreamParams;
+ SlicedInputStreamParams;
+ RemoteLazyInputStreamParams;
+ InputStreamLengthWrapperParams;
+ IPCRemoteStreamParams;
+ EncryptedFileInputStreamParams;
+};
+
+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..fed7bde795
--- /dev/null
+++ b/ipc/glue/InputStreamUtils.cpp
@@ -0,0 +1,400 @@
+/* -*- 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/IPCStreamDestination.h"
+#include "mozilla/ipc/IPCStreamSource.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 {
+
+NS_DEFINE_CID(kStringInputStreamCID, NS_STRINGINPUTSTREAM_CID);
+NS_DEFINE_CID(kFileInputStreamCID, NS_LOCALFILEINPUTSTREAM_CID);
+NS_DEFINE_CID(kBufferedInputStreamCID, NS_BUFFEREDINPUTSTREAM_CID);
+NS_DEFINE_CID(kMIMEInputStreamCID, NS_MIMEINPUTSTREAM_CID);
+NS_DEFINE_CID(kMultiplexInputStreamCID, NS_MULTIPLEXINPUTSTREAM_CID);
+
+} // namespace
+
+namespace mozilla {
+namespace ipc {
+
+namespace {
+
+template <typename M>
+void SerializeInputStreamInternal(nsIInputStream* aInputStream,
+ InputStreamParams& aParams,
+ nsTArray<FileDescriptor>& aFileDescriptors,
+ bool aDelayedStart, uint32_t aMaxSize,
+ uint32_t* aSizeUsed, M* aManager) {
+ MOZ_ASSERT(aInputStream);
+ MOZ_ASSERT(aManager);
+
+ nsCOMPtr<nsIIPCSerializableInputStream> serializable =
+ do_QueryInterface(aInputStream);
+ if (!serializable) {
+ MOZ_CRASH("Input stream is not serializable!");
+ }
+
+ serializable->Serialize(aParams, aFileDescriptors, aDelayedStart, aMaxSize,
+ aSizeUsed, aManager);
+
+ if (aParams.type() == InputStreamParams::T__None) {
+ MOZ_CRASH("Serialize failed!");
+ }
+}
+
+template <typename M>
+void SerializeInputStreamAsPipeInternal(nsIInputStream* aInputStream,
+ InputStreamParams& aParams,
+ bool aDelayedStart, M* aManager) {
+ MOZ_ASSERT(aInputStream);
+ MOZ_ASSERT(aManager);
+
+ // 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;
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aInputStream);
+
+ // As a fallback, attempt to stream the data across using a IPCStream
+ // actor. For blocking streams, create a nonblocking pipe instead,
+ bool nonBlocking = false;
+ MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aInputStream->IsNonBlocking(&nonBlocking)));
+ if (!nonBlocking || !asyncStream) {
+ const uint32_t kBufferSize = 32768; // matches IPCStream buffer size.
+ nsCOMPtr<nsIAsyncOutputStream> sink;
+ nsresult rv = NS_NewPipe2(getter_AddRefs(asyncStream), getter_AddRefs(sink),
+ true, false, kBufferSize, UINT32_MAX);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+
+ // Since the source stream could be used by others, let's not close it when
+ // the copy is done.
+ rv = NS_AsyncCopy(aInputStream, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS,
+ kBufferSize, nullptr, nullptr, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ }
+ MOZ_DIAGNOSTIC_ASSERT(asyncStream);
+
+ auto* streamSource = IPCStreamSource::Create(asyncStream, aManager);
+ if (NS_WARN_IF(!streamSource)) {
+ // Failed to create IPCStreamSource, which would cause a failure should we
+ // attempt to serialize it later. So abort now.
+ return;
+ }
+
+ aParams = IPCRemoteStreamParams(aDelayedStart, streamSource, length);
+}
+
+} // namespace
+
+void InputStreamHelper::SerializeInputStream(
+ nsIInputStream* aInputStream, InputStreamParams& aParams,
+ nsTArray<FileDescriptor>& aFileDescriptors, bool aDelayedStart,
+ uint32_t aMaxSize, uint32_t* aSizeUsed,
+ ParentToChildStreamActorManager* aManager) {
+ SerializeInputStreamInternal(aInputStream, aParams, aFileDescriptors,
+ aDelayedStart, aMaxSize, aSizeUsed, aManager);
+}
+
+void InputStreamHelper::SerializeInputStream(
+ nsIInputStream* aInputStream, InputStreamParams& aParams,
+ nsTArray<FileDescriptor>& aFileDescriptors, bool aDelayedStart,
+ uint32_t aMaxSize, uint32_t* aSizeUsed,
+ ChildToParentStreamActorManager* aManager) {
+ SerializeInputStreamInternal(aInputStream, aParams, aFileDescriptors,
+ aDelayedStart, aMaxSize, aSizeUsed, aManager);
+}
+
+void InputStreamHelper::SerializeInputStreamAsPipe(
+ nsIInputStream* aInputStream, InputStreamParams& aParams,
+ bool aDelayedStart, ParentToChildStreamActorManager* aManager) {
+ SerializeInputStreamAsPipeInternal(aInputStream, aParams, aDelayedStart,
+ aManager);
+}
+
+void InputStreamHelper::SerializeInputStreamAsPipe(
+ nsIInputStream* aInputStream, InputStreamParams& aParams,
+ bool aDelayedStart, ChildToParentStreamActorManager* aManager) {
+ SerializeInputStreamAsPipeInternal(aInputStream, aParams, aDelayedStart,
+ aManager);
+}
+
+void InputStreamHelper::PostSerializationActivation(InputStreamParams& aParams,
+ bool aConsumedByIPC,
+ bool aDelayedStart) {
+ switch (aParams.type()) {
+ case InputStreamParams::TBufferedInputStreamParams: {
+ BufferedInputStreamParams& params =
+ aParams.get_BufferedInputStreamParams();
+ InputStreamHelper::PostSerializationActivation(
+ params.optionalStream(), aConsumedByIPC, aDelayedStart);
+ return;
+ }
+
+ case InputStreamParams::TMIMEInputStreamParams: {
+ MIMEInputStreamParams& params = aParams.get_MIMEInputStreamParams();
+ InputStreamHelper::PostSerializationActivation(
+ params.optionalStream(), aConsumedByIPC, aDelayedStart);
+ return;
+ }
+
+ case InputStreamParams::TMultiplexInputStreamParams: {
+ MultiplexInputStreamParams& params =
+ aParams.get_MultiplexInputStreamParams();
+ for (InputStreamParams& subParams : params.streams()) {
+ InputStreamHelper::PostSerializationActivation(
+ subParams, aConsumedByIPC, aDelayedStart);
+ }
+ return;
+ }
+
+ case InputStreamParams::TSlicedInputStreamParams: {
+ SlicedInputStreamParams& params = aParams.get_SlicedInputStreamParams();
+ InputStreamHelper::PostSerializationActivation(
+ params.stream(), aConsumedByIPC, aDelayedStart);
+ return;
+ }
+
+ case InputStreamParams::TInputStreamLengthWrapperParams: {
+ InputStreamLengthWrapperParams& params =
+ aParams.get_InputStreamLengthWrapperParams();
+ InputStreamHelper::PostSerializationActivation(
+ params.stream(), aConsumedByIPC, aDelayedStart);
+ return;
+ }
+
+ case InputStreamParams::TIPCRemoteStreamParams: {
+ IPCRemoteStreamType& remoteInputStream =
+ aParams.get_IPCRemoteStreamParams().stream();
+
+ IPCStreamSource* source = nullptr;
+ if (remoteInputStream.type() ==
+ IPCRemoteStreamType::TPChildToParentStreamChild) {
+ source = IPCStreamSource::Cast(
+ remoteInputStream.get_PChildToParentStreamChild());
+ } else {
+ MOZ_ASSERT(remoteInputStream.type() ==
+ IPCRemoteStreamType::TPParentToChildStreamParent);
+ source = IPCStreamSource::Cast(
+ remoteInputStream.get_PParentToChildStreamParent());
+ }
+
+ MOZ_ASSERT(source);
+
+ // If the source stream has not been taken to be sent to the other side,
+ // we can destroy it.
+ if (!aConsumedByIPC) {
+ source->StartDestroy();
+ return;
+ }
+
+ if (!aDelayedStart) {
+ // If we don't need to do a delayedStart, we start it now. Otherwise,
+ // the Start() will be called at the first use by the
+ // IPCStreamDestination::DelayedStartInputStream.
+ source->Start();
+ }
+
+ return;
+ }
+
+ case InputStreamParams::TStringInputStreamParams:
+ break;
+
+ case InputStreamParams::TFileInputStreamParams:
+ break;
+
+ case InputStreamParams::TRemoteLazyInputStreamParams:
+ break;
+
+ case InputStreamParams::TEncryptedFileInputStreamParams:
+ break;
+
+ default:
+ MOZ_CRASH(
+ "A new stream? Should decide if it must be processed recursively or "
+ "not.");
+ }
+}
+
+void InputStreamHelper::PostSerializationActivation(
+ Maybe<InputStreamParams>& aParams, bool aConsumedByIPC,
+ bool aDelayedStart) {
+ if (aParams.isSome()) {
+ InputStreamHelper::PostSerializationActivation(
+ aParams.ref(), aConsumedByIPC, aDelayedStart);
+ }
+}
+
+already_AddRefed<nsIInputStream> InputStreamHelper::DeserializeInputStream(
+ const InputStreamParams& aParams,
+ const nsTArray<FileDescriptor>& aFileDescriptors) {
+ if (aParams.type() == InputStreamParams::TRemoteLazyInputStreamParams) {
+ const RemoteLazyInputStreamParams& params =
+ aParams.get_RemoteLazyInputStreamParams();
+
+ // RemoteLazyInputStreamRefs are not deserializable on the parent side,
+ // because the parent is the only one that has a copy of the original stream
+ // in the RemoteLazyInputStreamStorage.
+ if (params.type() ==
+ RemoteLazyInputStreamParams::TRemoteLazyInputStreamRef) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ const RemoteLazyInputStreamRef& ref =
+ params.get_RemoteLazyInputStreamRef();
+
+ auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr);
+ MOZ_ASSERT(storage);
+ nsCOMPtr<nsIInputStream> stream;
+ storage->GetStream(ref.id(), ref.start(), ref.length(),
+ getter_AddRefs(stream));
+ return stream.forget();
+ }
+
+ // parent -> child serializations receive an RemoteLazyInputStream actor.
+ MOZ_ASSERT(params.type() ==
+ RemoteLazyInputStreamParams::TPRemoteLazyInputStreamChild);
+ RemoteLazyInputStreamChild* actor =
+ static_cast<RemoteLazyInputStreamChild*>(
+ params.get_PRemoteLazyInputStreamChild());
+ nsCOMPtr<nsIInputStream> stream = actor->CreateStream();
+ return stream.forget();
+ }
+
+ if (aParams.type() == InputStreamParams::TIPCRemoteStreamParams) {
+ const IPCRemoteStreamParams& remoteStream =
+ aParams.get_IPCRemoteStreamParams();
+ const IPCRemoteStreamType& remoteStreamType = remoteStream.stream();
+ IPCStreamDestination* destinationStream;
+
+ if (remoteStreamType.type() ==
+ IPCRemoteStreamType::TPChildToParentStreamParent) {
+ destinationStream = IPCStreamDestination::Cast(
+ remoteStreamType.get_PChildToParentStreamParent());
+ } else {
+ MOZ_ASSERT(remoteStreamType.type() ==
+ IPCRemoteStreamType::TPParentToChildStreamChild);
+ destinationStream = IPCStreamDestination::Cast(
+ remoteStreamType.get_PParentToChildStreamChild());
+ }
+
+ destinationStream->SetDelayedStart(remoteStream.delayedStart());
+ destinationStream->SetLength(remoteStream.length());
+ return destinationStream->TakeReader();
+ }
+
+ 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(nullptr, NS_GET_IID(nsIFileInputStream),
+ getter_AddRefs(stream));
+ serializable = do_QueryInterface(stream);
+ } break;
+
+ case InputStreamParams::TBufferedInputStreamParams: {
+ nsCOMPtr<nsIBufferedInputStream> stream;
+ nsBufferedInputStream::Create(nullptr, NS_GET_IID(nsIBufferedInputStream),
+ getter_AddRefs(stream));
+ serializable = do_QueryInterface(stream);
+ } break;
+
+ case InputStreamParams::TMIMEInputStreamParams: {
+ nsCOMPtr<nsIMIMEInputStream> stream;
+ nsMIMEInputStreamConstructor(nullptr, NS_GET_IID(nsIMIMEInputStream),
+ getter_AddRefs(stream));
+ serializable = do_QueryInterface(stream);
+ } break;
+
+ case InputStreamParams::TMultiplexInputStreamParams: {
+ nsCOMPtr<nsIMultiplexInputStream> stream;
+ nsMultiplexInputStreamConstructor(
+ nullptr, 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, aFileDescriptors)) {
+ 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..4fafa5bb1a
--- /dev/null
+++ b/ipc/glue/InputStreamUtils.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_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;
+class PFileDescriptorSetChild;
+class PFileDescriptorSetParent;
+class PChildToParentStreamChild;
+class PParentToChildStreamParent;
+
+// Provide two interfaces for sending PParentToChildStream and
+// PFileDescriptorSet constructor messages.
+class ParentToChildStreamActorManager {
+ public:
+ virtual PParentToChildStreamParent* SendPParentToChildStreamConstructor(
+ PParentToChildStreamParent* aActor) = 0;
+ virtual PFileDescriptorSetParent* SendPFileDescriptorSetConstructor(
+ const FileDescriptor& aFD) = 0;
+};
+
+class ChildToParentStreamActorManager {
+ public:
+ virtual PChildToParentStreamChild* SendPChildToParentStreamConstructor(
+ PChildToParentStreamChild* aActor) = 0;
+ virtual PFileDescriptorSetChild* SendPFileDescriptorSetConstructor(
+ const FileDescriptor& aFD) = 0;
+};
+
+// If you want to serialize an inputStream, please use AutoIPCStream.
+class InputStreamHelper {
+ public:
+ // 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
+ // IPCRemoteStream, and, its content will be sent to the other side of the IPC
+ // pipe in chunks. This sending can start immediatelly or at the first read
+ // based on the value of |aDelayedStart|. The IPC message size is returned
+ // into |aSizeUsed|.
+ static void SerializeInputStream(nsIInputStream* aInputStream,
+ InputStreamParams& aParams,
+ nsTArray<FileDescriptor>& aFileDescriptors,
+ bool aDelayedStart, uint32_t aMaxSize,
+ uint32_t* aSizeUsed,
+ ParentToChildStreamActorManager* aManager);
+
+ static void SerializeInputStream(nsIInputStream* aInputStream,
+ InputStreamParams& aParams,
+ nsTArray<FileDescriptor>& aFileDescriptors,
+ bool aDelayedStart, uint32_t aMaxSize,
+ uint32_t* aSizeUsed,
+ ChildToParentStreamActorManager* aManager);
+
+ // When a stream wants to serialize itself as IPCRemoteStream, it uses one of
+ // these methods.
+ static void SerializeInputStreamAsPipe(
+ nsIInputStream* aInputStream, InputStreamParams& aParams,
+ bool aDelayedStart, ParentToChildStreamActorManager* aManager);
+
+ static void SerializeInputStreamAsPipe(
+ nsIInputStream* aInputStream, InputStreamParams& aParams,
+ bool aDelayedStart, ChildToParentStreamActorManager* aManager);
+
+ // After the sending of the inputStream into the IPC pipe, some of the
+ // InputStreamParams data struct needs to be activated (IPCRemoteStream).
+ // These 2 methods do that.
+ static void PostSerializationActivation(InputStreamParams& aParams,
+ bool aConsumedByIPC,
+ bool aDelayedStart);
+
+ static void PostSerializationActivation(Maybe<InputStreamParams>& aParams,
+ bool aConsumedByIPC,
+ bool aDelayedStart);
+
+ static already_AddRefed<nsIInputStream> DeserializeInputStream(
+ const InputStreamParams& aParams,
+ const nsTArray<FileDescriptor>& aFileDescriptors);
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_InputStreamUtils_h
diff --git a/ipc/glue/LibrarySandboxPreload.cpp b/ipc/glue/LibrarySandboxPreload.cpp
new file mode 100644
index 0000000000..a49b8458b3
--- /dev/null
+++ b/ipc/glue/LibrarySandboxPreload.cpp
@@ -0,0 +1,82 @@
+/* -*- 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 "BinaryPath.h"
+#include "prlink.h"
+
+namespace mozilla {
+namespace ipc {
+
+static nsAutoCString GetSandboxedPath(const nsACString& libName) {
+ nsCOMPtr<nsIFile> binaryPath;
+ nsresult rv = mozilla::BinaryPath::GetFile(getter_AddRefs(binaryPath));
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("Library preload failure: Failed to get binary file\n");
+ }
+
+ nsCOMPtr<nsIFile> libFile;
+ rv = binaryPath->GetParent(getter_AddRefs(libFile));
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("Library preload failure: Failed to get binary folder\n");
+ }
+
+ rv = libFile->AppendNative(libName);
+
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("Library preload failure: Failed to get library file\n");
+ }
+
+ nsAutoString fullPath;
+ rv = libFile->GetPath(fullPath);
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("Library preload failure: Failed to get library path\n");
+ }
+
+ nsAutoCString converted_path = NS_ConvertUTF16toUTF8(fullPath);
+ return converted_path;
+}
+
+nsAutoCString GetSandboxedGraphitePath() {
+ return GetSandboxedPath(
+ nsLiteralCString(MOZ_DLL_PREFIX "graphitewasm" MOZ_DLL_SUFFIX));
+}
+
+nsAutoCString GetSandboxedOggPath() {
+ return GetSandboxedPath(
+ nsLiteralCString(MOZ_DLL_PREFIX "oggwasm" MOZ_DLL_SUFFIX));
+}
+
+PRLibrary* PreloadLibrary(const nsAutoCString& path) {
+ PRLibSpec libSpec;
+ libSpec.type = PR_LibSpec_Pathname;
+ libSpec.value.pathname = path.get();
+ PRLibrary* ret = PR_LoadLibraryWithFlags(libSpec, PR_LD_LAZY);
+ return ret;
+}
+
+void PreloadSandboxedDynamicLibraries() {
+ // The process level sandbox does not allow loading of dynamic libraries.
+ // This preloads wasm sandboxed libraries before the process level sandbox is
+ // enabled. Currently, this is only needed for Linux as Mac allows loading
+ // libraries from the package file.
+#if defined(XP_LINUX)
+# if defined(MOZ_WASM_SANDBOXING_GRAPHITE)
+ if (!PreloadLibrary(GetSandboxedGraphitePath())) {
+ MOZ_CRASH("Library preload failure: Failed to load libgraphite\n");
+ }
+# endif
+# if defined(MOZ_WASM_SANDBOXING_OGG)
+ if (!PreloadLibrary(GetSandboxedOggPath())) {
+ MOZ_CRASH("Library preload failure: Failed to load libogg\n");
+ }
+# endif
+#endif
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/LibrarySandboxPreload.h b/ipc/glue/LibrarySandboxPreload.h
new file mode 100644
index 0000000000..0193f2f8a6
--- /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
+
+namespace mozilla {
+namespace ipc {
+nsAutoCString GetSandboxedGraphitePath();
+nsAutoCString GetSandboxedOggPath();
+void PreloadSandboxedDynamicLibraries();
+} // namespace ipc
+} // namespace mozilla
+#endif
diff --git a/ipc/glue/MessageChannel.cpp b/ipc/glue/MessageChannel.cpp
new file mode 100644
index 0000000000..7f90742b44
--- /dev/null
+++ b/ipc/glue/MessageChannel.cpp
@@ -0,0 +1,2951 @@
+/* -*- 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/Logging.h"
+#include "mozilla/Mutex.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/ProcessChild.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "nsAppRunner.h"
+#include "nsContentUtils.h"
+#include "nsDataHashtable.h"
+#include "nsDebug.h"
+#include "nsExceptionHandler.h"
+#include "nsIMemoryReporter.h"
+#include "nsISupportsImpl.h"
+#include "nsPrintfCString.h"
+
+#ifdef OS_WIN
+# include "mozilla/gfx/Logging.h"
+#endif
+
+#ifdef MOZ_TASK_TRACER
+# include "GeckoTaskTracer.h"
+using namespace mozilla::tasktracer;
+#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 three kinds of messages: async, sync, and intr. Sync and intr
+ * 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). Intr messages cannot nest. 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 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.
+ *
+ * Intr messages are blocking and can nest, but they don't participate in the
+ * nesting levels. While waiting for an intr response, all incoming messages are
+ * dispatched until a response is received. When two intr messages race with
+ * each other, a similar scheme is used to ensure that one side wins. The
+ * winning side is chosen based on the message type.
+ *
+ * Intr messages differ from sync messages in that, while sending an intr
+ * message, we may dispatch an async message. This causes some additional
+ * complexity. One issue is that replies can be received out of order. It's also
+ * more difficult to determine whether one message is nested inside
+ * another. Consequently, intr handling uses mOutOfTurnReplies and
+ * mRemoteStackDepthGuess, which are not needed for sync messages.
+ */
+
+using namespace mozilla;
+using namespace mozilla::ipc;
+
+using mozilla::MonitorAutoLock;
+using mozilla::MonitorAutoUnlock;
+using mozilla::dom::AutoNoJSAPI;
+
+#define IPC_ASSERT(_cond, ...) \
+ do { \
+ if (!(_cond)) DebugAbort(__FILE__, __LINE__, #_cond, ##__VA_ARGS__); \
+ } while (0)
+
+static MessageChannel* gParentProcessBlocker;
+
+namespace mozilla {
+namespace ipc {
+
+static const uint32_t kMinTelemetryMessageSize = 4096;
+
+// Note: we round the time we spend to the nearest millisecond. So a min value
+// of 1 ms actually captures from 500us and above.
+static const uint32_t kMinTelemetryIPCWriteLatencyMs = 1;
+
+// 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;
+
+const int32_t MessageChannel::kNoTimeout = INT32_MIN;
+
+// static
+bool MessageChannel::sIsPumpingMessages = false;
+
+enum Direction { IN_MESSAGE, OUT_MESSAGE };
+
+class MessageChannel::InterruptFrame {
+ private:
+ enum Semantics { INTR_SEMS, SYNC_SEMS, ASYNC_SEMS };
+
+ public:
+ InterruptFrame(Direction direction, const Message* msg)
+ : mMessageName(msg->name()),
+ mMessageRoutingId(msg->routing_id()),
+ mMesageSemantics(msg->is_interrupt() ? INTR_SEMS
+ : msg->is_sync() ? SYNC_SEMS
+ : ASYNC_SEMS),
+ mDirection(direction),
+ mMoved(false) {
+ MOZ_RELEASE_ASSERT(mMessageName);
+ }
+
+ InterruptFrame(InterruptFrame&& aOther) {
+ MOZ_RELEASE_ASSERT(aOther.mMessageName);
+ mMessageName = aOther.mMessageName;
+ aOther.mMessageName = nullptr;
+ mMoved = aOther.mMoved;
+ aOther.mMoved = true;
+
+ mMessageRoutingId = aOther.mMessageRoutingId;
+ mMesageSemantics = aOther.mMesageSemantics;
+ mDirection = aOther.mDirection;
+ }
+
+ ~InterruptFrame() { MOZ_RELEASE_ASSERT(mMessageName || mMoved); }
+
+ InterruptFrame& operator=(InterruptFrame&& aOther) {
+ MOZ_RELEASE_ASSERT(&aOther != this);
+ this->~InterruptFrame();
+ new (this) InterruptFrame(std::move(aOther));
+ return *this;
+ }
+
+ bool IsInterruptIncall() const {
+ return INTR_SEMS == mMesageSemantics && IN_MESSAGE == mDirection;
+ }
+
+ bool IsInterruptOutcall() const {
+ return INTR_SEMS == mMesageSemantics && OUT_MESSAGE == mDirection;
+ }
+
+ bool IsOutgoingSync() const {
+ return (mMesageSemantics == INTR_SEMS || mMesageSemantics == SYNC_SEMS) &&
+ mDirection == OUT_MESSAGE;
+ }
+
+ void Describe(int32_t* id, const char** dir, const char** sems,
+ const char** name) const {
+ *id = mMessageRoutingId;
+ *dir = (IN_MESSAGE == mDirection) ? "in" : "out";
+ *sems = (INTR_SEMS == mMesageSemantics) ? "intr"
+ : (SYNC_SEMS == mMesageSemantics) ? "sync"
+ : "async";
+ *name = mMessageName;
+ }
+
+ int32_t GetRoutingId() const { return mMessageRoutingId; }
+
+ private:
+ const char* mMessageName;
+ int32_t mMessageRoutingId;
+ Semantics mMesageSemantics;
+ Direction mDirection;
+ bool mMoved;
+
+ // Disable harmful methods.
+ InterruptFrame(const InterruptFrame& aOther) = delete;
+ InterruptFrame& operator=(const InterruptFrame&) = delete;
+};
+
+class MOZ_STACK_CLASS MessageChannel::CxxStackFrame {
+ public:
+ CxxStackFrame(MessageChannel& that, Direction direction, const Message* msg)
+ : mThat(that) {
+ mThat.AssertWorkerThread();
+
+ if (mThat.mCxxStackFrames.empty()) mThat.EnteredCxxStack();
+
+ if (!mThat.mCxxStackFrames.append(InterruptFrame(direction, msg)))
+ MOZ_CRASH();
+
+ const InterruptFrame& frame = mThat.mCxxStackFrames.back();
+
+ if (frame.IsInterruptIncall()) mThat.EnteredCall();
+
+ if (frame.IsOutgoingSync()) mThat.EnteredSyncSend();
+
+ mThat.mSawInterruptOutMsg |= frame.IsInterruptOutcall();
+ }
+
+ ~CxxStackFrame() {
+ mThat.AssertWorkerThread();
+
+ MOZ_RELEASE_ASSERT(!mThat.mCxxStackFrames.empty());
+
+ const InterruptFrame& frame = mThat.mCxxStackFrames.back();
+ bool exitingSync = frame.IsOutgoingSync();
+ bool exitingCall = frame.IsInterruptIncall();
+ mThat.mCxxStackFrames.shrinkBy(1);
+
+ bool exitingStack = mThat.mCxxStackFrames.empty();
+
+ // According how lifetime is declared, mListener on MessageChannel
+ // lives longer than MessageChannel itself. Hence is expected to
+ // be alive. There is nothing to even assert here, there is no place
+ // we would be nullifying mListener on MessageChannel.
+
+ if (exitingCall) mThat.ExitedCall();
+
+ if (exitingSync) mThat.ExitedSyncSend();
+
+ if (exitingStack) mThat.ExitedCxxStack();
+ }
+
+ private:
+ MessageChannel& mThat;
+
+ // Disable harmful methods.
+ CxxStackFrame() = delete;
+ CxxStackFrame(const CxxStackFrame&) = delete;
+ CxxStackFrame& operator=(const CxxStackFrame&) = delete;
+};
+
+class AutoEnterTransaction {
+ public:
+ explicit AutoEnterTransaction(MessageChannel* aChan, int32_t aMsgSeqno,
+ int32_t aTransactionID, int aNestedLevel)
+ : 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)
+ : 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() {
+ 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(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 = MakeUnique<IPC::Message>(std::move(aMessage));
+ MOZ_RELEASE_ASSERT(IsComplete());
+ }
+
+ void HandleReply(IPC::Message&& aMessage) {
+ 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 = nsDataHashtable<nsDepCharHashKey, ChannelCounts>;
+
+ static StaticMutex sChannelCountMutex;
+ static CountTable* sChannelCounts;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) override {
+ StaticMutexAutoLock countLock(sChannelCountMutex);
+ if (!sChannelCounts) {
+ return NS_OK;
+ }
+ for (auto iter = sChannelCounts->Iter(); !iter.Done(); iter.Next()) {
+ nsPrintfCString pathNow("ipc-channels/%s", iter.Key());
+ nsPrintfCString pathMax("ipc-channels-peak/%s", iter.Key());
+ nsPrintfCString descNow(
+ "Number of IPC channels for"
+ " top-level actor type %s",
+ iter.Key());
+ nsPrintfCString descMax(
+ "Peak number of IPC channels for"
+ " top-level actor type %s",
+ iter.Key());
+
+ aHandleReport->Callback(""_ns, pathNow, KIND_OTHER, UNITS_COUNT,
+ iter.Data().mNow, descNow, aData);
+ aHandleReport->Callback(""_ns, pathMax, KIND_OTHER, UNITS_COUNT,
+ iter.Data().mMax, descMax, aData);
+ }
+ return NS_OK;
+ }
+
+ static void Increment(const char* aName) {
+ StaticMutexAutoLock countLock(sChannelCountMutex);
+ if (!sChannelCounts) {
+ sChannelCounts = new CountTable;
+ }
+ sChannelCounts->GetOrInsert(aName).Inc();
+ }
+
+ static void Decrement(const char* aName) {
+ StaticMutexAutoLock countLock(sChannelCountMutex);
+ MOZ_ASSERT(sChannelCounts);
+ sChannelCounts->GetOrInsert(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),
+ mChannelState(ChannelClosed),
+ mSide(UnknownSide),
+ mIsCrossProcess(false),
+ mChannelErrorTask(nullptr),
+ mTimeoutMs(kNoTimeout),
+ mInTimeoutSecondHalf(false),
+ mNextSeqno(0),
+ mLastSendError(SyncSendError::SendSuccess),
+ mDispatchingAsyncMessage(false),
+ mDispatchingAsyncMessageNestedLevel(0),
+ mTransactionStack(nullptr),
+ mTimedOutMessageSeqno(0),
+ mTimedOutMessageNestedLevel(0),
+ mMaybeDeferredPendingCount(0),
+ mRemoteStackDepthGuess(0),
+ mSawInterruptOutMsg(false),
+ mIsWaitingForIncoming(false),
+ mAbortOnError(false),
+ mNotifiedChannelDone(false),
+ mFlags(REQUIRE_DEFAULT),
+ mPeerPidSet(false),
+ mPeerPid(-1),
+ mIsPostponingSends(false),
+ mBuildIDsConfirmedMatch(false),
+ mIsSameThreadChannel(false) {
+ MOZ_COUNT_CTOR(ipc::MessageChannel);
+
+#ifdef OS_WIN
+ mTopFrame = nullptr;
+ mIsSyncWaitingOnNonMainThread = false;
+#endif
+
+ mOnChannelConnectedTask = NewNonOwningCancelableRunnableMethod(
+ "ipc::MessageChannel::DispatchOnChannelConnected", this,
+ &MessageChannel::DispatchOnChannelConnected);
+
+#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);
+ IPC_ASSERT(mCxxStackFrames.empty(), "mismatched CxxStackFrame ctor/dtors");
+#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
+ Clear();
+}
+
+#ifdef DEBUG
+void MessageChannel::AssertMaybeDeferredCountCorrect() {
+ size_t count = 0;
+ for (MessageTask* task : mPending) {
+ 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 void PrintErrorMessage(Side side, const char* channelName,
+ const char* msg) {
+ const char* from = (side == ChildSide)
+ ? "Child"
+ : ((side == ParentSide) ? "Parent" : "Unknown");
+ printf_stderr("\n###!!! [%s][%s] Error: %s\n\n", from, channelName, msg);
+}
+
+bool MessageChannel::Connected() const {
+ mMonitor->AssertCurrentThreadOwns();
+
+ // The transport layer allows us to send messages before
+ // receiving the "connected" ack from the remote side.
+ return (ChannelOpening == mChannelState || ChannelConnected == mChannelState);
+}
+
+bool MessageChannel::CanSend() const {
+ if (!mMonitor) {
+ return false;
+ }
+ MonitorAutoLock lock(*mMonitor);
+ return Connected();
+}
+
+void MessageChannel::Clear() {
+ // Don't clear mWorkerThread; we use it in AssertLinkThread() and
+ // 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.
+ //
+ // In practice, mListener owns the channel, so the channel gets deleted
+ // before mListener. But just to be safe, mListener is a weak pointer.
+
+#if !defined(ANDROID)
+ if (!Unsound_IsClosed()) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCFatalErrorProtocol,
+ nsDependentCString(mName));
+ switch (mChannelState) {
+ case ChannelOpening:
+ MOZ_CRASH(
+ "MessageChannel destroyed without being closed "
+ "(mChannelState == ChannelOpening).");
+ break;
+ case ChannelConnected:
+ MOZ_CRASH(
+ "MessageChannel destroyed without being closed "
+ "(mChannelState == ChannelConnected).");
+ break;
+ case ChannelTimeout:
+ MOZ_CRASH(
+ "MessageChannel destroyed without being closed "
+ "(mChannelState == ChannelTimeout).");
+ 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.");
+ }
+ }
+#endif
+
+ if (gParentProcessBlocker == this) {
+ gParentProcessBlocker = nullptr;
+ }
+
+ gUnresolvedResponses -= mPendingResponses.size();
+ for (auto& pair : mPendingResponses) {
+ pair.second.get()->Reject(ResponseRejectReason::ChannelClosed);
+ }
+ mPendingResponses.clear();
+
+ if (mLink != nullptr && mIsCrossProcess) {
+ ChannelCountReporter::Decrement(mName);
+ }
+
+ if (mLink) {
+ mLink->PrepareToDestroy();
+ mLink = nullptr;
+ }
+
+ mOnChannelConnectedTask->Cancel();
+
+ if (mChannelErrorTask) {
+ mChannelErrorTask->Cancel();
+ mChannelErrorTask = nullptr;
+ }
+
+ // Free up any memory used by pending messages.
+ for (MessageTask* task : mPending) {
+ task->Clear();
+ }
+ mPending.clear();
+
+ mMaybeDeferredPendingCount = 0;
+
+ mOutOfTurnReplies.clear();
+ while (!mDeferred.empty()) {
+ mDeferred.pop();
+ }
+}
+
+bool MessageChannel::Open(mozilla::UniquePtr<Transport> aTransport,
+ MessageLoop* aIOLoop, Side aSide) {
+ MOZ_ASSERT(!mLink, "Open() called > once");
+
+ mMonitor = new RefCountedMonitor();
+ mWorkerThread = GetCurrentSerialEventTarget();
+ MOZ_ASSERT(mWorkerThread, "We should always be on a nsISerialEventTarget");
+ mListener->OnIPCChannelOpened();
+
+ auto link = MakeUnique<ProcessLink>(this);
+ link->Open(std::move(aTransport), aIOLoop,
+ aSide); // :TODO: n.b.: sets mChild
+ mLink = std::move(link);
+ mIsCrossProcess = true;
+ ChannelCountReporter::Increment(mName);
+ return true;
+}
+
+bool MessageChannel::Open(MessageChannel* aTargetChan,
+ nsISerialEventTarget* aEventTarget, Side aSide) {
+ // Opens a connection to another thread in the same process.
+
+ // This handshake proceeds as follows:
+ // - Let A be the thread initiating the process (either child or parent)
+ // and B be the other thread.
+ // - A spawns thread for B, obtaining B's message loop
+ // - A creates ProtocolChild and ProtocolParent instances.
+ // Let PA be the one appropriate to A and PB the side for B.
+ // - A invokes PA->Open(PB, ...):
+ // - set state to mChannelOpening
+ // - this will place a work item in B's worker loop (see next bullet)
+ // and then spins until PB->mChannelState becomes mChannelConnected
+ // - meanwhile, on PB's worker loop, the work item is removed and:
+ // - invokes PB->OpenAsOtherThread(PA, ...):
+ // - sets its state and that of PA to Connected
+ MOZ_ASSERT(aTargetChan, "Need a target channel");
+ MOZ_ASSERT(ChannelClosed == mChannelState, "Not currently closed");
+
+ CommonThreadOpenInit(aTargetChan, GetCurrentSerialEventTarget(), aSide);
+
+ Side oppSide = UnknownSide;
+ switch (aSide) {
+ case ChildSide:
+ oppSide = ParentSide;
+ break;
+ case ParentSide:
+ oppSide = ChildSide;
+ break;
+ case UnknownSide:
+ break;
+ }
+
+ mMonitor = new RefCountedMonitor();
+
+ MonitorAutoLock lock(*mMonitor);
+ mChannelState = ChannelOpening;
+ MOZ_ALWAYS_SUCCEEDS(aEventTarget->Dispatch(
+ NewNonOwningRunnableMethod<MessageChannel*, nsISerialEventTarget*, Side>(
+ "ipc::MessageChannel::OpenAsOtherThread", aTargetChan,
+ &MessageChannel::OpenAsOtherThread, this, aEventTarget, oppSide)));
+
+ while (ChannelOpening == mChannelState) mMonitor->Wait();
+ MOZ_RELEASE_ASSERT(ChannelConnected == mChannelState,
+ "not connected when awoken");
+ return (ChannelConnected == mChannelState);
+}
+
+void MessageChannel::OpenAsOtherThread(MessageChannel* aTargetChan,
+ nsISerialEventTarget* aThread,
+ Side aSide) {
+ // Invoked when the other side has begun the open.
+ MOZ_ASSERT(ChannelClosed == mChannelState, "Not currently closed");
+ MOZ_ASSERT(ChannelOpening == aTargetChan->mChannelState,
+ "Target channel not in the process of opening");
+
+ CommonThreadOpenInit(aTargetChan, aThread, aSide);
+ mMonitor = aTargetChan->mMonitor;
+
+ MonitorAutoLock lock(*mMonitor);
+ MOZ_RELEASE_ASSERT(ChannelOpening == aTargetChan->mChannelState,
+ "Target channel not in the process of opening");
+ mChannelState = ChannelConnected;
+ aTargetChan->mChannelState = ChannelConnected;
+ aTargetChan->mMonitor->Notify();
+}
+
+void MessageChannel::CommonThreadOpenInit(MessageChannel* aTargetChan,
+ nsISerialEventTarget* aThread,
+ Side aSide) {
+ MOZ_ASSERT(aThread);
+ mWorkerThread = aThread;
+ mListener->OnIPCChannelOpened();
+
+ mLink = MakeUnique<ThreadLink>(this, aTargetChan);
+ mSide = aSide;
+}
+
+bool MessageChannel::OpenOnSameThread(MessageChannel* aTargetChan,
+ mozilla::ipc::Side aSide) {
+ nsCOMPtr<nsISerialEventTarget> currentThread = GetCurrentSerialEventTarget();
+ CommonThreadOpenInit(aTargetChan, currentThread, aSide);
+
+ Side oppSide = UnknownSide;
+ switch (aSide) {
+ case ChildSide:
+ oppSide = ParentSide;
+ break;
+ case ParentSide:
+ oppSide = ChildSide;
+ break;
+ case UnknownSide:
+ break;
+ }
+ mIsSameThreadChannel = true;
+
+ // XXX(nika): Avoid setting up a monitor for same thread channels? We
+ // shouldn't need it.
+ mMonitor = new RefCountedMonitor();
+
+ mChannelState = ChannelOpening;
+ aTargetChan->CommonThreadOpenInit(this, currentThread, oppSide);
+
+ aTargetChan->mIsSameThreadChannel = true;
+ aTargetChan->mMonitor = mMonitor;
+
+ mChannelState = ChannelConnected;
+ aTargetChan->mChannelState = ChannelConnected;
+ return true;
+}
+
+bool MessageChannel::Send(UniquePtr<Message> aMsg) {
+ if (aMsg->size() >= kMinTelemetryMessageSize) {
+ Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE2, aMsg->size());
+ }
+
+ // If the message was created by the IPC bindings, the create time will be
+ // recorded. Use this information to report the
+ // IPC_WRITE_MAIN_THREAD_LATENCY_MS (time from message creation to it being
+ // sent).
+ if (NS_IsMainThread() && aMsg->create_time()) {
+ uint32_t latencyMs = round(
+ (mozilla::TimeStamp::Now() - aMsg->create_time()).ToMilliseconds());
+ if (latencyMs >= kMinTelemetryIPCWriteLatencyMs) {
+ mozilla::Telemetry::Accumulate(
+ mozilla::Telemetry::IPC_WRITE_MAIN_THREAD_LATENCY_MS,
+ nsDependentCString(aMsg->name()), latencyMs);
+ }
+ }
+
+ MOZ_RELEASE_ASSERT(!aMsg->is_sync());
+ MOZ_RELEASE_ASSERT(aMsg->nested_level() != IPC::Message::NESTED_INSIDE_SYNC);
+
+ CxxStackFrame frame(*this, OUT_MESSAGE, aMsg.get());
+
+ 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("MessageChannel", aMsg.get());
+ return false;
+ }
+
+ AddProfilerMarker(*aMsg, MessageDirection::eSending);
+ SendMessageToLink(std::move(aMsg));
+ return true;
+}
+
+void MessageChannel::SendMessageToLink(UniquePtr<Message> aMsg) {
+ if (mIsPostponingSends) {
+ mPostponedSends.push_back(std::move(aMsg));
+ return;
+ }
+ mLink->SendMessage(std::move(aMsg));
+}
+
+void MessageChannel::BeginPostponingSends() {
+ AssertWorkerThread();
+ mMonitor->AssertNotCurrentThreadOwns();
+
+ MonitorAutoLock lock(*mMonitor);
+ {
+ MOZ_ASSERT(!mIsPostponingSends);
+ mIsPostponingSends = true;
+ }
+}
+
+void MessageChannel::StopPostponingSends() {
+ // Note: this can be called from any thread.
+ MonitorAutoLock lock(*mMonitor);
+
+ MOZ_ASSERT(mIsPostponingSends);
+
+ for (UniquePtr<Message>& iter : mPostponedSends) {
+ mLink->SendMessage(std::move(iter));
+ }
+
+ // We unset this after SendMessage so we can make correct thread
+ // assertions in MessageLink.
+ mIsPostponingSends = false;
+ mPostponedSends.clear();
+}
+
+UniquePtr<MessageChannel::UntypedCallbackHolder> MessageChannel::PopCallback(
+ const Message& aMsg) {
+ auto iter = mPendingResponses.find(aMsg.seqno());
+ if (iter != mPendingResponses.end()) {
+ UniquePtr<MessageChannel::UntypedCallbackHolder> ret =
+ std::move(iter->second);
+ mPendingResponses.erase(iter);
+ gUnresolvedResponses--;
+ return ret;
+ }
+ return nullptr;
+}
+
+void MessageChannel::RejectPendingResponsesForActor(ActorIdType 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("MessageChannel", msg.get());
+ return false;
+ }
+ mLink->SendMessage(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) {
+ AssertLinkThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ if (MSG_ROUTING_NONE == aMsg.routing_id()) {
+ if (GOODBYE_MESSAGE_TYPE == aMsg.type()) {
+ // :TODO: Sort out Close() on this side racing with Close() on the
+ // other side
+ mChannelState = ChannelClosing;
+ if (LoggingEnabled()) {
+ printf("NOTE: %s process received `Goodbye', closing down\n",
+ (mSide == ChildSide) ? "child" : "parent");
+ }
+ 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::NotifyImpendingShutdown();
+ 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(Message&& aMsg) {
+ AssertLinkThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ if (MaybeInterceptSpecialIOMessage(aMsg)) return;
+
+ mListener->OnChannelReceivedMessage(aMsg);
+
+ // Regardless of the Interrupt stack, 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);
+
+ bool reuseTask = false;
+ if (aMsg.compress_type() == IPC::Message::COMPRESSION_ENABLED) {
+ bool compress =
+ (!mPending.isEmpty() &&
+ mPending.getLast()->Msg().type() == aMsg.type() &&
+ mPending.getLast()->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(mPending.getLast()->Msg().compress_type() ==
+ IPC::Message::COMPRESSION_ENABLED);
+ mPending.getLast()->Msg() = std::move(aMsg);
+
+ reuseTask = true;
+ }
+ } else if (aMsg.compress_type() == IPC::Message::COMPRESSION_ALL &&
+ !mPending.isEmpty()) {
+ for (MessageTask* p = mPending.getLast(); p; p = p->getPrevious()) {
+ 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 wakeUpSyncSend = AwaitingSyncReply() && !ShouldDeferMessage(aMsg);
+
+ bool shouldWakeUp =
+ AwaitingInterruptReply() || wakeUpSyncSend || AwaitingIncomingMessage();
+
+ // 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.
+ bool shouldPostTask = !shouldWakeUp || wakeUpSyncSend;
+
+ IPC_LOG("Receive on link thread; seqno=%d, xid=%d, shouldWakeUp=%d",
+ aMsg.seqno(), aMsg.transaction_id(), shouldWakeUp);
+
+ if (reuseTask) {
+ return;
+ }
+
+ // There are three cases we're concerned about, relating to the state of the
+ // main thread:
+ //
+ // (1) We are waiting on a sync reply - main thread is blocked on the
+ // IPC monitor.
+ // - If the message is NESTED_INSIDE_SYNC, we wake up the main thread to
+ // deliver the message depending on ShouldDeferMessage. Otherwise, we
+ // leave it in the mPending queue, posting a task to the main event
+ // loop, where it will be processed once the synchronous reply has been
+ // received.
+ //
+ // (2) We are waiting on an Interrupt reply - main thread is blocked on the
+ // IPC monitor.
+ // - Always notify and wake up the main thread.
+ //
+ // (3) We are not waiting on a reply.
+ // - We post a task to the main event loop.
+ //
+ // Note that, we may notify the main thread even though the monitor is not
+ // blocked. This is okay, since we always check for pending events before
+ // blocking again.
+
+#ifdef MOZ_TASK_TRACER
+ aMsg.TaskTracerDispatch();
+#endif
+ RefPtr<MessageTask> task = new MessageTask(this, std::move(aMsg));
+ mPending.insertBack(task);
+
+ if (!alwaysDeferred) {
+ mMaybeDeferredPendingCount++;
+ }
+
+ if (shouldWakeUp) {
+ NotifyWorkerThread();
+ }
+
+ if (shouldPostTask) {
+ 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) {
+ const Message& msg = it->Msg();
+ if (!aInvoke(msg)) {
+ break;
+ }
+ }
+}
+
+void MessageChannel::ProcessPendingRequests(
+ 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;
+ }
+
+ mozilla::Vector<Message> toProcess;
+
+ for (MessageTask* p = mPending.getFirst(); p;) {
+ 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 it = toProcess.begin(); it != toProcess.end(); it++) {
+ ProcessPendingRequest(std::move(*it));
+ }
+ }
+
+ AssertMaybeDeferredCountCorrect();
+}
+
+bool MessageChannel::Send(UniquePtr<Message> aMsg, 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!");
+
+#ifdef OS_WIN
+ SyncStackFrame frame(this, false);
+ NeuteredWindowRegion neuteredRgn(mFlags &
+ REQUIRE_DEFERRED_MESSAGE_PROTECTION);
+#endif
+#ifdef MOZ_TASK_TRACER
+ AutoScopedLabel autolabel("sync message %s", aMsg->name());
+#endif
+
+ CxxStackFrame f(*this, OUT_MESSAGE, aMsg.get());
+
+ 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());
+ MOZ_RELEASE_ASSERT(!mIsPostponingSends);
+ IPC_LOG("Cancel from Send");
+ auto cancel =
+ MakeUnique<CancelMessage>(CurrentNestedInsideSyncTransaction());
+ CancelTransaction(CurrentNestedInsideSyncTransaction());
+ mLink->SendMessage(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("MessageChannel::SendAndWait", aMsg.get());
+ 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);
+
+ bool handleWindowsMessages = mListener->HandleWindowsMessages(*aMsg.get());
+ AutoEnterTransaction transact(this, seqno, transaction, nestedLevel);
+
+ IPC_LOG("Send seqno=%d, xid=%d", seqno, transaction);
+
+ // aMsg will be destroyed soon, but name() is not owned by aMsg.
+ const char* msgName = aMsg->name();
+
+ AddProfilerMarker(*aMsg, MessageDirection::eSending);
+ SendMessageToLink(std::move(aMsg));
+
+ while (true) {
+ MOZ_RELEASE_ASSERT(!transact.IsCanceled());
+ ProcessPendingRequests(transact);
+ if (transact.IsComplete()) {
+ break;
+ }
+ if (!Connected()) {
+ ReportConnectionError("MessageChannel::Send");
+ mLastSendError = SyncSendError::DisconnectedDuringSend;
+ return false;
+ }
+
+ MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);
+ MOZ_RELEASE_ASSERT(!transact.IsComplete());
+ MOZ_RELEASE_ASSERT(mTransactionStack == &transact);
+
+ bool maybeTimedOut = !WaitForSyncNotify(handleWindowsMessages);
+
+ if (mListener->NeedArtificialSleep()) {
+ MonitorAutoUnlock unlock(*mMonitor);
+ mListener->ArtificialSleep();
+ }
+
+ if (!Connected()) {
+ ReportConnectionError("MessageChannel::SendAndWait");
+ 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);
+
+ *aReply = std::move(*reply);
+ if (aReply->size() >= kMinTelemetryMessageSize) {
+ Telemetry::Accumulate(Telemetry::IPC_REPLY_SIZE,
+ nsDependentCString(msgName), aReply->size());
+ }
+
+ // 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::Call(UniquePtr<Message> aMsg, Message* aReply) {
+ AssertWorkerThread();
+ mMonitor->AssertNotCurrentThreadOwns();
+ MOZ_RELEASE_ASSERT(!mIsSameThreadChannel,
+ "intr call send over same-thread channel will deadlock!");
+
+#ifdef OS_WIN
+ SyncStackFrame frame(this, true);
+#endif
+#ifdef MOZ_TASK_TRACER
+ AutoScopedLabel autolabel("sync message %s", aMsg->name());
+#endif
+
+ // This must come before MonitorAutoLock, as its destructor acquires the
+ // monitor lock.
+ CxxStackFrame cxxframe(*this, OUT_MESSAGE, aMsg.get());
+
+ MonitorAutoLock lock(*mMonitor);
+ if (!Connected()) {
+ ReportConnectionError("MessageChannel::Call", aMsg.get());
+ return false;
+ }
+
+ // Sanity checks.
+ IPC_ASSERT(!AwaitingSyncReply(),
+ "cannot issue Interrupt call while blocked on sync request");
+ IPC_ASSERT(!DispatchingSyncMessage(), "violation of sync handler invariant");
+ IPC_ASSERT(aMsg->is_interrupt(), "can only Call() Interrupt messages here");
+ IPC_ASSERT(!mIsPostponingSends, "not postponing sends");
+
+ aMsg->set_seqno(NextSeqno());
+ aMsg->set_interrupt_remote_stack_depth_guess(mRemoteStackDepthGuess);
+ aMsg->set_interrupt_local_stack_depth(1 + InterruptStackDepth());
+ mInterruptStack.push(MessageInfo(*aMsg));
+
+ AddProfilerMarker(*aMsg, MessageDirection::eSending);
+
+ mLink->SendMessage(std::move(aMsg));
+
+ while (true) {
+ // if a handler invoked by *Dispatch*() spun a nested event
+ // loop, and the connection was broken during that loop, we
+ // might have already processed the OnError event. if so,
+ // trying another loop iteration will be futile because
+ // channel state will have been cleared
+ if (!Connected()) {
+ ReportConnectionError("MessageChannel::Call");
+ return false;
+ }
+
+#ifdef OS_WIN
+ // We need to limit the scoped of neuteredRgn to this spot in the code.
+ // Window neutering can't be enabled during some plugin calls because
+ // we then risk the neutered window procedure being subclassed by a
+ // plugin.
+ {
+ NeuteredWindowRegion neuteredRgn(mFlags &
+ REQUIRE_DEFERRED_MESSAGE_PROTECTION);
+ /* We should pump messages at this point to ensure that the IPC
+ peer does not become deadlocked on a pending inter-thread
+ SendMessage() */
+ neuteredRgn.PumpOnce();
+ }
+#endif
+
+ // Now might be the time to process a message deferred because of race
+ // resolution.
+ MaybeUndeferIncall();
+
+ // Wait for an event to occur.
+ while (!InterruptEventOccurred()) {
+ bool maybeTimedOut = !WaitForInterruptNotify();
+
+ // We might have received a "subtly deferred" message in a nested
+ // loop that it's now time to process.
+ if (InterruptEventOccurred() ||
+ (!maybeTimedOut &&
+ (!mDeferred.empty() || !mOutOfTurnReplies.empty()))) {
+ break;
+ }
+
+ if (maybeTimedOut && !ShouldContinueFromTimeout()) return false;
+ }
+
+ Message recvd;
+ MessageMap::iterator it;
+
+ if ((it = mOutOfTurnReplies.find(mInterruptStack.top().seqno())) !=
+ mOutOfTurnReplies.end()) {
+ recvd = std::move(it->second);
+ mOutOfTurnReplies.erase(it);
+ } else if (!mPending.isEmpty()) {
+ RefPtr<MessageTask> task = mPending.popFirst();
+ recvd = std::move(task->Msg());
+ if (!IsAlwaysDeferred(recvd)) {
+ mMaybeDeferredPendingCount--;
+ }
+ } else {
+ // because of subtleties with nested event loops, it's possible
+ // that we got here and nothing happened. or, we might have a
+ // deferred in-call that needs to be processed. either way, we
+ // won't break the inner while loop again until something new
+ // happens.
+ continue;
+ }
+
+ // If the message is not Interrupt, we can dispatch it as normal.
+ if (!recvd.is_interrupt()) {
+ DispatchMessage(std::move(recvd));
+ if (!Connected()) {
+ ReportConnectionError("MessageChannel::DispatchMessage");
+ return false;
+ }
+ continue;
+ }
+
+ // If the message is an Interrupt reply, either process it as a reply to our
+ // call, or add it to the list of out-of-turn replies we've received.
+ if (recvd.is_reply()) {
+ IPC_ASSERT(!mInterruptStack.empty(), "invalid Interrupt stack");
+
+ // If this is not a reply the call we've initiated, add it to our
+ // out-of-turn replies and keep polling for events.
+ {
+ const MessageInfo& outcall = mInterruptStack.top();
+
+ // Note, In the parent, sequence numbers increase from 0, and
+ // in the child, they decrease from 0.
+ if ((mSide == ChildSide && recvd.seqno() > outcall.seqno()) ||
+ (mSide != ChildSide && recvd.seqno() < outcall.seqno())) {
+ mOutOfTurnReplies[recvd.seqno()] = std::move(recvd);
+ continue;
+ }
+
+ IPC_ASSERT(
+ recvd.is_reply_error() || (recvd.type() == (outcall.type() + 1) &&
+ recvd.seqno() == outcall.seqno()),
+ "somebody's misbehavin'", true);
+ }
+
+ // We received a reply to our most recent outstanding call. Pop
+ // this frame and return the reply.
+ mInterruptStack.pop();
+
+ AddProfilerMarker(recvd, MessageDirection::eReceiving);
+
+ bool is_reply_error = recvd.is_reply_error();
+ if (!is_reply_error) {
+ *aReply = std::move(recvd);
+ }
+
+ // If we have no more pending out calls waiting on replies, then
+ // the reply queue should be empty.
+ IPC_ASSERT(!mInterruptStack.empty() || mOutOfTurnReplies.empty(),
+ "still have pending replies with no pending out-calls", true);
+
+ return !is_reply_error;
+ }
+
+ // Dispatch an Interrupt in-call. Snapshot the current stack depth while we
+ // own the monitor.
+ size_t stackDepth = InterruptStackDepth();
+ {
+#ifdef MOZ_TASK_TRACER
+ Message::AutoTaskTracerRun tasktracerRun(recvd);
+#endif
+ MonitorAutoUnlock unlock(*mMonitor);
+
+ CxxStackFrame frame(*this, IN_MESSAGE, &recvd);
+ RefPtr<ActorLifecycleProxy> listenerProxy =
+ mListener->GetLifecycleProxy();
+ DispatchInterruptMessage(listenerProxy, std::move(recvd), stackDepth);
+ }
+ if (!Connected()) {
+ ReportConnectionError("MessageChannel::DispatchInterruptMessage");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool MessageChannel::WaitForIncomingMessage() {
+#ifdef OS_WIN
+ SyncStackFrame frame(this, true);
+ NeuteredWindowRegion neuteredRgn(mFlags &
+ REQUIRE_DEFERRED_MESSAGE_PROTECTION);
+#endif
+
+ MonitorAutoLock lock(*mMonitor);
+ AutoEnterWaitForIncoming waitingForIncoming(*this);
+ if (mChannelState != ChannelConnected) {
+ return false;
+ }
+ if (!HasPendingEvents()) {
+ return WaitForInterruptNotify();
+ }
+
+ MOZ_RELEASE_ASSERT(!mPending.isEmpty());
+ RefPtr<MessageTask> task = mPending.getFirst();
+ RunMessage(*task);
+ return true;
+}
+
+bool MessageChannel::HasPendingEvents() {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+ return Connected() && !mPending.isEmpty();
+}
+
+bool MessageChannel::InterruptEventOccurred() {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+ IPC_ASSERT(InterruptStackDepth() > 0, "not in wait loop");
+
+ return (!Connected() || !mPending.isEmpty() ||
+ (!mOutOfTurnReplies.empty() &&
+ mOutOfTurnReplies.find(mInterruptStack.top().seqno()) !=
+ mOutOfTurnReplies.end()));
+}
+
+bool MessageChannel::ProcessPendingRequest(Message&& aUrgent) {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ IPC_LOG("Process pending: seqno=%d, xid=%d", aUrgent.seqno(),
+ aUrgent.transaction_id());
+
+ DispatchMessage(std::move(aUrgent));
+ if (!Connected()) {
+ ReportConnectionError("MessageChannel::ProcessPendingRequest");
+ 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(MessageTask& aTask) {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ Message& msg = aTask.Msg();
+
+ if (!Connected()) {
+ ReportConnectionError("RunMessage");
+ return;
+ }
+
+ // Check that we're going to run the first message that's valid to run.
+#if 0
+# ifdef DEBUG
+ nsCOMPtr<nsIEventTarget> messageTarget =
+ mListener->GetMessageEventTarget(msg);
+
+ for (MessageTask* task : mPending) {
+ if (task == &aTask) {
+ break;
+ }
+
+ nsCOMPtr<nsIEventTarget> taskTarget =
+ mListener->GetMessageEventTarget(task->Msg());
+
+ MOZ_ASSERT(!ShouldRunMessage(task->Msg()) ||
+ taskTarget != messageTarget ||
+ aTask.Msg().priority() != task->Msg().priority());
+
+ }
+# endif
+#endif
+
+ if (!mDeferred.empty()) {
+ MaybeUndeferIncall();
+ }
+
+ if (!ShouldRunMessage(msg)) {
+ return;
+ }
+
+ MOZ_RELEASE_ASSERT(aTask.isInList());
+ aTask.remove();
+
+ if (!IsAlwaysDeferred(msg)) {
+ mMaybeDeferredPendingCount--;
+ }
+
+ if (IsOnCxxStack() && msg.is_interrupt() && msg.is_reply()) {
+ // We probably just received a reply in a nested loop for an
+ // Interrupt call sent before entering that loop.
+ mOutOfTurnReplies[msg.seqno()] = std::move(msg);
+ return;
+ }
+
+ DispatchMessage(std::move(msg));
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(MessageChannel::MessageTask, CancelableRunnable,
+ nsIRunnablePriority, nsIRunnableIPCMessageType)
+
+MessageChannel::MessageTask::MessageTask(MessageChannel* aChannel,
+ Message&& aMessage)
+ : CancelableRunnable(aMessage.name()),
+ mChannel(aChannel),
+ mMessage(std::move(aMessage)),
+ mScheduled(false) {}
+
+nsresult MessageChannel::MessageTask::Run() {
+ if (!mChannel) {
+ return NS_OK;
+ }
+
+ mChannel->AssertWorkerThread();
+ mChannel->mMonitor->AssertNotCurrentThreadOwns();
+
+ MonitorAutoLock lock(*mChannel->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;
+ }
+
+ mChannel->RunMessage(*this);
+ return NS_OK;
+}
+
+// Warning: This method removes the receiver from whatever list it might be in.
+nsresult MessageChannel::MessageTask::Cancel() {
+ if (!mChannel) {
+ return NS_OK;
+ }
+
+ mChannel->AssertWorkerThread();
+ mChannel->mMonitor->AssertNotCurrentThreadOwns();
+
+ MonitorAutoLock lock(*mChannel->mMonitor);
+
+ if (!isInList()) {
+ return NS_OK;
+ }
+ remove();
+
+ if (!IsAlwaysDeferred(Msg())) {
+ mChannel->mMaybeDeferredPendingCount--;
+ }
+
+ return NS_OK;
+}
+
+void MessageChannel::MessageTask::Post() {
+ MOZ_RELEASE_ASSERT(!mScheduled);
+ MOZ_RELEASE_ASSERT(isInList());
+
+ mScheduled = true;
+
+ RefPtr<MessageTask> self = this;
+ nsCOMPtr<nsISerialEventTarget> eventTarget =
+ mChannel->mListener->GetMessageEventTarget(mMessage);
+
+ if (eventTarget) {
+ eventTarget->Dispatch(self.forget(), NS_DISPATCH_NORMAL);
+ } else {
+ mChannel->mWorkerThread->Dispatch(self.forget());
+ }
+}
+
+void MessageChannel::MessageTask::Clear() {
+ mChannel->AssertWorkerThread();
+
+ mChannel = nullptr;
+}
+
+NS_IMETHODIMP
+MessageChannel::MessageTask::GetPriority(uint32_t* aPriority) {
+ switch (mMessage.priority()) {
+ case Message::NORMAL_PRIORITY:
+ *aPriority = PRIORITY_NORMAL;
+ break;
+ case Message::INPUT_PRIORITY:
+ *aPriority = PRIORITY_INPUT_HIGH;
+ break;
+ case Message::HIGH_PRIORITY:
+ *aPriority = PRIORITY_HIGH;
+ break;
+ case Message::MEDIUMHIGH_PRIORITY:
+ *aPriority = PRIORITY_MEDIUMHIGH;
+ break;
+ default:
+ MOZ_ASSERT(false);
+ break;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MessageChannel::MessageTask::GetType(uint32_t* aType) {
+ if (!Msg().is_valid()) {
+ // If mMessage has been moved already elsewhere, we can't know what the type
+ // has been.
+ return NS_ERROR_FAILURE;
+ }
+
+ *aType = Msg().type();
+ return NS_OK;
+}
+
+void MessageChannel::DispatchMessage(Message&& aMsg) {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ RefPtr<ActorLifecycleProxy> listenerProxy = mListener->GetLifecycleProxy();
+
+ 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());
+
+ {
+#ifdef MOZ_TASK_TRACER
+ Message::AutoTaskTracerRun tasktracerRun(aMsg);
+#endif
+ MonitorAutoUnlock unlock(*mMonitor);
+ CxxStackFrame frame(*this, IN_MESSAGE, &aMsg);
+
+ mListener->ArtificialSleep();
+
+ if (aMsg.is_sync()) {
+ DispatchSyncMessage(listenerProxy, aMsg, *getter_Transfers(reply));
+ } else if (aMsg.is_interrupt()) {
+ DispatchInterruptMessage(listenerProxy, std::move(aMsg), 0);
+ } else {
+ DispatchAsyncMessage(listenerProxy, 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);
+
+ mLink->SendMessage(std::move(reply));
+ }
+}
+
+void MessageChannel::DispatchSyncMessage(ActorLifecycleProxy* aProxy,
+ const Message& aMsg,
+ Message*& aReply) {
+ AssertWorkerThread();
+
+ mozilla::TimeStamp start = TimeStamp::Now();
+
+ int nestedLevel = aMsg.nested_level();
+
+ MOZ_RELEASE_ASSERT(nestedLevel == IPC::Message::NOT_NESTED ||
+ NS_IsMainThread());
+#ifdef MOZ_TASK_TRACER
+ AutoScopedLabel autolabel("sync message %s", aMsg.name());
+#endif
+
+ 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_interrupt() && !aMsg.is_sync());
+
+ if (aMsg.routing_id() == MSG_ROUTING_NONE) {
+ MOZ_CRASH("unhandled special message!");
+ }
+
+ 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::DispatchInterruptMessage(ActorLifecycleProxy* aProxy,
+ Message&& aMsg,
+ size_t stackDepth) {
+ AssertWorkerThread();
+ mMonitor->AssertNotCurrentThreadOwns();
+
+ IPC_ASSERT(aMsg.is_interrupt() && !aMsg.is_reply(), "wrong message type");
+
+ if (ShouldDeferInterruptMessage(aMsg, stackDepth)) {
+ // We now know the other side's stack has one more frame
+ // than we thought.
+ ++mRemoteStackDepthGuess; // decremented in MaybeProcessDeferred()
+ mDeferred.push(std::move(aMsg));
+ return;
+ }
+
+ // If we "lost" a race and need to process the other side's in-call, we
+ // don't need to fix up the mRemoteStackDepthGuess here, because we're just
+ // about to increment it, which will make it correct again.
+
+#ifdef OS_WIN
+ SyncStackFrame frame(this, true);
+#endif
+
+ UniquePtr<Message> reply;
+
+ ++mRemoteStackDepthGuess;
+ Result rv = aProxy->Get()->OnCallReceived(aMsg, *getter_Transfers(reply));
+ --mRemoteStackDepthGuess;
+
+ if (!MaybeHandleError(rv, aMsg, "DispatchInterruptMessage")) {
+ reply = WrapUnique(Message::ForInterruptDispatchError());
+ }
+ reply->set_seqno(aMsg.seqno());
+
+ MonitorAutoLock lock(*mMonitor);
+ if (ChannelConnected == mChannelState) {
+ AddProfilerMarker(*reply, MessageDirection::eSending);
+ mLink->SendMessage(std::move(reply));
+ }
+}
+
+bool MessageChannel::ShouldDeferInterruptMessage(const Message& aMsg,
+ size_t aStackDepth) {
+ AssertWorkerThread();
+
+ // We may or may not own the lock in this function, so don't access any
+ // channel state.
+
+ IPC_ASSERT(aMsg.is_interrupt() && !aMsg.is_reply(), "wrong message type");
+
+ // Race detection: see the long comment near mRemoteStackDepthGuess in
+ // MessageChannel.h. "Remote" stack depth means our side, and "local" means
+ // the other side.
+ if (aMsg.interrupt_remote_stack_depth_guess() ==
+ RemoteViewOfStackDepth(aStackDepth)) {
+ return false;
+ }
+
+ // Interrupt in-calls have raced. The winner, if there is one, gets to defer
+ // processing of the other side's in-call.
+ bool defer;
+ const char* winner;
+ const MessageInfo parentMsgInfo =
+ (mSide == ChildSide) ? MessageInfo(aMsg) : mInterruptStack.top();
+ const MessageInfo childMsgInfo =
+ (mSide == ChildSide) ? mInterruptStack.top() : MessageInfo(aMsg);
+ switch (mListener->MediateInterruptRace(parentMsgInfo, childMsgInfo)) {
+ case RIPChildWins:
+ winner = "child";
+ defer = (mSide == ChildSide);
+ break;
+ case RIPParentWins:
+ winner = "parent";
+ defer = (mSide != ChildSide);
+ break;
+ case RIPError:
+ MOZ_CRASH("NYI: 'Error' Interrupt race policy");
+ default:
+ MOZ_CRASH("not reached");
+ }
+
+ IPC_LOG("race in %s: %s won", (mSide == ChildSide) ? "child" : "parent",
+ winner);
+
+ return defer;
+}
+
+void MessageChannel::MaybeUndeferIncall() {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ if (mDeferred.empty()) return;
+
+ size_t stackDepth = InterruptStackDepth();
+
+ Message& deferred = mDeferred.top();
+
+ // the other side can only *under*-estimate our actual stack depth
+ IPC_ASSERT(deferred.interrupt_remote_stack_depth_guess() <= stackDepth,
+ "fatal logic error");
+
+ if (ShouldDeferInterruptMessage(deferred, stackDepth)) {
+ return;
+ }
+
+ // maybe time to process this message
+ Message call(std::move(deferred));
+ mDeferred.pop();
+
+ // fix up fudge factor we added to account for race
+ IPC_ASSERT(0 < mRemoteStackDepthGuess, "fatal logic error");
+ --mRemoteStackDepthGuess;
+
+ MOZ_RELEASE_ASSERT(call.nested_level() == IPC::Message::NOT_NESTED);
+ RefPtr<MessageTask> task = new MessageTask(this, std::move(call));
+ mPending.insertBack(task);
+ MOZ_ASSERT(IsAlwaysDeferred(task->Msg()));
+ task->Post();
+}
+
+void MessageChannel::EnteredCxxStack() { mListener->EnteredCxxStack(); }
+
+void MessageChannel::ExitedCxxStack() {
+ mListener->ExitedCxxStack();
+ if (mSawInterruptOutMsg) {
+ MonitorAutoLock lock(*mMonitor);
+ // see long comment in OnMaybeDequeueOne()
+ EnqueuePendingMessages();
+ mSawInterruptOutMsg = false;
+ }
+}
+
+void MessageChannel::EnteredCall() { mListener->EnteredCall(); }
+
+void MessageChannel::ExitedCall() { mListener->ExitedCall(); }
+
+void MessageChannel::EnteredSyncSend() { mListener->OnEnteredSyncSend(); }
+
+void MessageChannel::ExitedSyncSend() { mListener->OnExitedSyncSend(); }
+
+void MessageChannel::EnqueuePendingMessages() {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ MaybeUndeferIncall();
+
+ // XXX performance tuning knob: could process all or k pending
+ // messages here, rather than enqueuing for later processing
+
+ RepostAllMessages();
+}
+
+bool MessageChannel::WaitResponse(bool aWaitTimedOut) {
+ 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(bool /* aHandleWindowsMessages */) {
+# ifdef DEBUG
+ // WARNING: We don't release the lock here. We can't because the link thread
+ // 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);
+}
+
+bool MessageChannel::WaitForInterruptNotify() {
+ return WaitForSyncNotify(true);
+}
+
+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::OnChannelConnected(int32_t peer_id) {
+ MOZ_RELEASE_ASSERT(!mPeerPidSet);
+ mPeerPidSet = true;
+ mPeerPid = peer_id;
+ RefPtr<CancelableRunnable> task = mOnChannelConnectedTask;
+ mWorkerThread->Dispatch(task.forget());
+}
+
+void MessageChannel::DispatchOnChannelConnected() {
+ AssertWorkerThread();
+ MOZ_RELEASE_ASSERT(mPeerPidSet);
+ mListener->OnChannelConnected(mPeerPid);
+}
+
+void MessageChannel::ReportMessageRouteError(const char* channelName) const {
+ PrintErrorMessage(mSide, channelName, "Need a route");
+ mListener->ProcessingError(MsgRouteError, "MsgRouteError");
+}
+
+void MessageChannel::ReportConnectionError(const char* aChannelName,
+ Message* aMsg) const {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ const char* errorMsg = nullptr;
+ switch (mChannelState) {
+ case ChannelClosed:
+ errorMsg = "Closed channel: cannot send/recv";
+ break;
+ case ChannelOpening:
+ errorMsg = "Opening channel: not yet ready for send/recv";
+ break;
+ case ChannelTimeout:
+ errorMsg = "Channel timeout: cannot send/recv";
+ break;
+ case ChannelClosing:
+ errorMsg =
+ "Channel closing: too late to send/recv, messages will be lost";
+ break;
+ case ChannelError:
+ errorMsg = "Channel error: cannot send/recv";
+ break;
+
+ default:
+ MOZ_CRASH("unreached");
+ }
+
+ if (aMsg) {
+ char reason[512];
+ SprintfLiteral(reason, "(msgtype=0x%X,name=%s) %s", aMsg->type(),
+ aMsg->name(), errorMsg);
+
+ PrintErrorMessage(mSide, aChannelName, reason);
+ } else {
+ PrintErrorMessage(mSide, aChannelName, errorMsg);
+ }
+
+ MonitorAutoUnlock unlock(*mMonitor);
+ mListener->ProcessingError(MsgDropped, errorMsg);
+}
+
+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() {
+ AssertLinkThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ IPC_LOG("OnChannelErrorFromLink");
+
+ if (InterruptStackDepth() > 0) NotifyWorkerThread();
+
+ if (AwaitingSyncReply() || AwaitingIncomingMessage()) NotifyWorkerThread();
+
+ if (ChannelClosing != mChannelState) {
+ 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() {
+ mMonitor->AssertNotCurrentThreadOwns();
+
+ // TODO sort out Close() on this side racing with Close() on the other side
+ if (ChannelClosing == mChannelState) {
+ // the channel closed, but we received a "Goodbye" message warning us
+ // about it. no worries
+ mChannelState = ChannelClosed;
+ NotifyChannelClosed();
+ return;
+ }
+
+ Clear();
+
+ // Oops, error! Let the listener know about it.
+ mChannelState = ChannelError;
+
+ // IPDL assumes these notifications do not fire twice, so we do not let
+ // that happen.
+ if (mNotifiedChannelDone) {
+ return;
+ }
+ mNotifiedChannelDone = true;
+
+ // After this, the channel may be deleted. Based on the premise that
+ // mListener owns this channel, any calls back to this class that may
+ // work with mListener should still work on living objects.
+ mListener->OnChannelError();
+}
+
+void MessageChannel::OnNotifyMaybeChannelError() {
+ AssertWorkerThread();
+ mMonitor->AssertNotCurrentThreadOwns();
+
+ mChannelErrorTask = nullptr;
+
+ // OnChannelError holds mMonitor when it posts this task and this
+ // task cannot be allowed to run until OnChannelError has
+ // exited. We enforce that order by grabbing the mutex here which
+ // should only continue once OnChannelError has completed.
+ {
+ MonitorAutoLock lock(*mMonitor);
+ // nothing to do here
+ }
+
+ if (IsOnCxxStack()) {
+ mChannelErrorTask = NewNonOwningCancelableRunnableMethod(
+ "ipc::MessageChannel::OnNotifyMaybeChannelError", this,
+ &MessageChannel::OnNotifyMaybeChannelError);
+ RefPtr<Runnable> task = mChannelErrorTask;
+ // This used to post a 10ms delayed patch; however not all
+ // nsISerialEventTarget implementations support delayed dispatch.
+ // The delay being completely arbitrary, we may not as well have any.
+ mWorkerThread->Dispatch(task.forget());
+ return;
+ }
+
+ NotifyMaybeChannelError();
+}
+
+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);
+ RefPtr<Runnable> task = mChannelErrorTask;
+ mWorkerThread->Dispatch(task.forget());
+}
+
+// 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::SynchronouslyClose() {
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+ mLink->SendClose();
+
+ MOZ_RELEASE_ASSERT(!mIsSameThreadChannel || ChannelClosed == mChannelState,
+ "same-thread channel failed to synchronously close?");
+
+ while (ChannelClosed != mChannelState) mMonitor->Wait();
+}
+
+void MessageChannel::CloseWithError() {
+ AssertWorkerThread();
+
+ MonitorAutoLock lock(*mMonitor);
+ if (ChannelConnected != mChannelState) {
+ return;
+ }
+ SynchronouslyClose();
+ mChannelState = ChannelError;
+ PostErrorNotifyTask();
+}
+
+void MessageChannel::CloseWithTimeout() {
+ AssertWorkerThread();
+
+ MonitorAutoLock lock(*mMonitor);
+ if (ChannelConnected != mChannelState) {
+ return;
+ }
+ SynchronouslyClose();
+ mChannelState = ChannelTimeout;
+}
+
+void MessageChannel::NotifyImpendingShutdown() {
+ UniquePtr<Message> msg =
+ MakeUnique<Message>(MSG_ROUTING_NONE, IMPENDING_SHUTDOWN_MESSAGE_TYPE);
+ MonitorAutoLock lock(*mMonitor);
+ if (Connected()) {
+ MOZ_DIAGNOSTIC_ASSERT(mIsCrossProcess);
+ mLink->SendMessage(std::move(msg));
+ }
+}
+
+void MessageChannel::Close() {
+ AssertWorkerThread();
+
+ {
+ // We don't use MonitorAutoLock here as that causes some sort of
+ // deadlock in the error/timeout-with-a-listener state below when
+ // compiling an optimized msvc build.
+ mMonitor->Lock();
+
+ // Instead just use a ScopeExit to manage the unlock.
+ RefPtr<RefCountedMonitor> monitor(mMonitor);
+ auto exit = MakeScopeExit([m = std::move(monitor)]() { m->Unlock(); });
+
+ if (ChannelError == mChannelState || ChannelTimeout == mChannelState) {
+ // 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.
+ if (mListener) {
+ exit.release(); // Explicitly unlocking, clear scope exit.
+ mMonitor->Unlock();
+ NotifyMaybeChannelError();
+ }
+ return;
+ }
+
+ if (ChannelOpening == mChannelState) {
+ // SynchronouslyClose() waits for an ack from the other side, so
+ // the opening sequence should complete before this returns.
+ SynchronouslyClose();
+ mChannelState = ChannelError;
+ NotifyMaybeChannelError();
+ return;
+ }
+
+ if (ChannelClosed == mChannelState) {
+ // Slightly unexpected but harmless; ignore. See bug 1554244.
+ return;
+ }
+
+ // 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) {
+ mLink->SendMessage(MakeUnique<GoodbyeMessage>());
+ }
+ SynchronouslyClose();
+ }
+
+ NotifyChannelClosed();
+}
+
+void MessageChannel::NotifyChannelClosed() {
+ mMonitor->AssertNotCurrentThreadOwns();
+
+ 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;
+
+ // OK, the IO thread just closed the channel normally. Let the
+ // listener know about it. After this point the channel may be
+ // deleted.
+ mListener->OnChannelClose();
+}
+
+void MessageChannel::DebugAbort(const char* file, int line, const char* cond,
+ const char* why, bool reply) {
+ printf_stderr(
+ "###!!! [MessageChannel][%s][%s:%d] "
+ "Assertion (%s) failed. %s %s\n",
+ mSide == ChildSide ? "Child" : "Parent", file, line, cond, why,
+ reply ? "(reply)" : "");
+ // technically we need the mutex for this, but we're dying anyway
+ DumpInterruptStack(" ");
+ printf_stderr(" remote Interrupt stack guess: %zu\n",
+ mRemoteStackDepthGuess);
+ printf_stderr(" deferred stack size: %zu\n", mDeferred.size());
+ printf_stderr(" out-of-turn Interrupt replies stack size: %zu\n",
+ mOutOfTurnReplies.size());
+
+ MessageQueue pending = std::move(mPending);
+ while (!pending.isEmpty()) {
+ printf_stderr(
+ " [ %s%s ]\n",
+ pending.getFirst()->Msg().is_interrupt()
+ ? "intr"
+ : (pending.getFirst()->Msg().is_sync() ? "sync" : "async"),
+ pending.getFirst()->Msg().is_reply() ? "reply" : "");
+ pending.popFirst();
+ }
+
+ MOZ_CRASH_UNSAFE(why);
+}
+
+void MessageChannel::DumpInterruptStack(const char* const pfx) const {
+ NS_WARNING_ASSERTION(!mWorkerThread->IsOnCurrentThread(),
+ "The worker thread had better be paused in a debugger!");
+
+ printf_stderr("%sMessageChannel 'backtrace':\n", pfx);
+
+ // print a python-style backtrace, first frame to last
+ for (uint32_t i = 0; i < mCxxStackFrames.length(); ++i) {
+ int32_t id;
+ const char* dir;
+ const char* sems;
+ const char* name;
+ mCxxStackFrames[i].Describe(&id, &dir, &sems, &name);
+
+ printf_stderr("%s[(%u) %s %s %s(actor=%d) ]\n", pfx, i, dir, sems, name,
+ id);
+ }
+}
+
+void MessageChannel::AddProfilerMarker(const IPC::Message& aMessage,
+ MessageDirection aDirection) {
+ mMonitor->AssertCurrentThreadOwns();
+#ifdef MOZ_GECKO_PROFILER
+ if (profiler_feature_active(ProfilerFeature::IPCMessages)) {
+ int32_t 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 != kInvalidProcessId && !profiler_is_locked_on_current_thread()) {
+ // The current timestamp must be given to the `IPCMarker` payload.
+ const TimeStamp now = TimeStamp::NowUnfuzzed();
+ PROFILER_MARKER("IPC", IPC, MarkerTiming::InstantAt(now), IPCMarker, now,
+ now, pid, aMessage.seqno(), aMessage.type(), mSide,
+ aDirection, MessagePhase::Endpoint, aMessage.is_sync());
+ }
+ }
+#endif
+}
+
+int32_t MessageChannel::GetTopmostMessageRoutingId() const {
+ AssertWorkerThread();
+
+ if (mCxxStackFrames.empty()) {
+ return MSG_ROUTING_NONE;
+ }
+ const InterruptFrame& frame = mCxxStackFrames.back();
+ return frame.GetRoutingId();
+}
+
+void MessageChannel::EndTimeout() {
+ mMonitor->AssertCurrentThreadOwns();
+
+ IPC_LOG("Ending timeout of seqno=%d", mTimedOutMessageSeqno);
+ mTimedOutMessageSeqno = 0;
+ mTimedOutMessageNestedLevel = 0;
+
+ RepostAllMessages();
+}
+
+void MessageChannel::RepostAllMessages() {
+ bool needRepost = false;
+ for (MessageTask* task : mPending) {
+ 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()) {
+ RefPtr<MessageTask> newTask = new MessageTask(this, std::move(task->Msg()));
+ 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;) {
+ 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());
+ mLink->SendMessage(std::move(cancel));
+ }
+}
+
+void CancelCPOWs() {
+ if (gParentProcessBlocker) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::IPC_TRANSACTION_CANCEL,
+ true);
+ gParentProcessBlocker->CancelCurrentTransaction();
+ }
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/MessageChannel.h b/ipc/glue/MessageChannel.h
new file mode 100644
index 0000000000..972615ea76
--- /dev/null
+++ b/ipc/glue/MessageChannel.h
@@ -0,0 +1,956 @@
+/* -*- 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/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/Transport.h"
+
+#ifdef MOZ_GECKO_PROFILER
+# include "mozilla/BaseProfilerMarkers.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") {}
+
+ 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,
+ EndGuard_,
+};
+
+template <typename T>
+using ResolveCallback = std::function<void(T&&)>;
+
+using RejectCallback = std::function<void(ResponseRejectReason)>;
+
+enum ChannelState {
+ ChannelClosed,
+ ChannelOpening,
+ ChannelConnected,
+ ChannelTimeout,
+ ChannelClosing,
+ ChannelError
+};
+
+class AutoEnterTransaction;
+
+class MessageChannel : HasResultCodes {
+ friend class ProcessLink;
+ friend class ThreadLink;
+#ifdef FUZZING
+ friend class ProtocolFuzzerHelper;
+#endif
+
+ class CxxStackFrame;
+ class InterruptFrame;
+
+ typedef mozilla::Monitor Monitor;
+
+ // We could templatize the actor type but it would unnecessarily
+ // expand the code size. Using the actor address as the
+ // identifier is already good enough.
+ typedef void* ActorIdType;
+
+ public:
+ struct UntypedCallbackHolder {
+ UntypedCallbackHolder(ActorIdType aActorId, RejectCallback&& aReject)
+ : mActorId(aActorId), mReject(std::move(aReject)) {}
+
+ virtual ~UntypedCallbackHolder() = default;
+
+ void Reject(ResponseRejectReason&& aReason) { mReject(std::move(aReason)); }
+
+ ActorIdType mActorId;
+ RejectCallback mReject;
+ };
+
+ template <typename Value>
+ struct CallbackHolder : public UntypedCallbackHolder {
+ CallbackHolder(ActorIdType aActorId, ResolveCallback<Value>&& aResolve,
+ RejectCallback&& aReject)
+ : UntypedCallbackHolder(aActorId, 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 const int32_t kNoTimeout;
+
+ typedef IPC::Message Message;
+ typedef IPC::MessageInfo MessageInfo;
+ typedef mozilla::ipc::Transport Transport;
+
+ explicit MessageChannel(const char* aName, IToplevelProtocol* aListener);
+ ~MessageChannel();
+
+ IToplevelProtocol* Listener() const { return mListener; }
+
+ // "Open" from the perspective of the transport layer; the underlying
+ // socketpair/pipe should already be created.
+ //
+ // Returns true if the transport layer was successfully connected,
+ // i.e., mChannelState == ChannelConnected.
+ bool Open(UniquePtr<Transport> aTransport, MessageLoop* aIOLoop = 0,
+ Side aSide = UnknownSide);
+
+ // "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();
+
+ // Close the underlying transport channel.
+ void Close();
+
+ // Force the channel to behave as if a channel error occurred. Valid
+ // for process links only, not thread links.
+ void CloseWithError();
+
+ void CloseWithTimeout();
+
+ void SetAbortOnError(bool abort) { 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);
+
+ // 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,
+ // Windows: When this flag is specified, any wait that occurs during
+ // synchronous IPC will be alertable, thus allowing a11y code in the
+ // chrome process to reenter content while content is waiting on a
+ // synchronous call.
+ REQUIRE_A11Y_REENTRY = 1 << 1,
+ };
+ 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);
+
+ // Asynchronously send a message to the other side of the channel
+ // and wait for asynchronous reply.
+ template <typename Value>
+ void Send(UniquePtr<Message> aMsg, ActorIdType aActorId,
+ ResolveCallback<Value>&& aResolve, RejectCallback&& aReject) {
+ int32_t seqno = NextSeqno();
+ aMsg->set_seqno(seqno);
+ if (!Send(std::move(aMsg))) {
+ aReject(ResponseRejectReason::SendError);
+ return;
+ }
+
+ UniquePtr<UntypedCallbackHolder> callback =
+ MakeUnique<CallbackHolder<Value>>(aActorId, std::move(aResolve),
+ std::move(aReject));
+ mPendingResponses.insert(std::make_pair(seqno, std::move(callback)));
+ gUnresolvedResponses++;
+ }
+
+ bool SendBuildIDsMatchMessage(const char* aParentBuildI);
+ bool DoBuildIDsMatch() { return mBuildIDsConfirmedMatch; }
+
+ // Synchronously send |msg| (i.e., wait for |reply|)
+ bool Send(UniquePtr<Message> aMsg, Message* aReply);
+
+ // Make an Interrupt call to the other side of the channel
+ bool Call(UniquePtr<Message> aMsg, Message* aReply);
+
+ // Wait until a message is received
+ bool WaitForIncomingMessage();
+
+ bool CanSend() const;
+
+ // Remove and return a callback that needs reply
+ UniquePtr<UntypedCallbackHolder> PopCallback(const Message& aMsg);
+
+ // Used to reject and remove pending responses owned by the given
+ // actor when it's about to be destroyed.
+ void RejectPendingResponsesForActor(ActorIdType aActorId);
+
+ // If sending a sync message returns an error, this function gives a more
+ // descriptive error message.
+ SyncSendError LastSendError() const {
+ AssertWorkerThread();
+ return mLastSendError;
+ }
+
+ // Currently only for debugging purposes, doesn't aquire mMonitor.
+ ChannelState GetChannelState__TotallyRacy() const { return mChannelState; }
+
+ void SetReplyTimeoutMs(int32_t aTimeoutMs);
+
+ bool IsOnCxxStack() const { return !mCxxStackFrames.empty(); }
+
+ void CancelCurrentTransaction();
+
+ // Force all calls to Send to defer actually sending messages. This will
+ // cause sync messages to block until another thread calls
+ // StopPostponingSends.
+ //
+ // This must be called from the worker thread.
+ void BeginPostponingSends();
+
+ // Stop postponing sent messages, and immediately flush all postponed
+ // messages to the link. This may be called from any thread.
+ //
+ // Note that there are no ordering guarantees between two different
+ // MessageChannels. If channel B sends a message, then stops postponing
+ // channel A, messages from A may arrive before B. The easiest way to order
+ // this, if needed, is to make B send a sync message.
+ void StopPostponingSends();
+
+ /**
+ * This function is used by hang annotation code to determine which IPDL
+ * actor is highest in the call stack at the time of the hang. It should
+ * be called from the main thread when a sync or intr message is about to
+ * be sent.
+ */
+ int32_t GetTopmostMessageRoutingId() const;
+
+ // Unsound_IsClosed and Unsound_NumQueuedMessages are safe to call from any
+ // thread, but they make no guarantees about whether you'll get an
+ // up-to-date value; the values are written on one thread and read without
+ // locking, on potentially different threads. Thus you should only use
+ // them when you don't particularly care about getting a recent value (e.g.
+ // in a memory report).
+ bool Unsound_IsClosed() const {
+ return mLink ? mLink->Unsound_IsClosed() : true;
+ }
+ uint32_t Unsound_NumQueuedMessages() const {
+ return mLink ? mLink->Unsound_NumQueuedMessages() : 0;
+ }
+
+ static bool IsPumpingMessages() { return sIsPumpingMessages; }
+ static void SetIsPumpingMessages(bool aIsPumping) {
+ sIsPumpingMessages = aIsPumping;
+ }
+
+ /**
+ * Does this MessageChannel cross process boundaries?
+ */
+ bool IsCrossProcess() const { return mIsCrossProcess; }
+
+#ifdef OS_WIN
+ struct MOZ_STACK_CLASS SyncStackFrame {
+ SyncStackFrame(MessageChannel* channel, bool interrupt);
+ ~SyncStackFrame();
+
+ bool mInterrupt;
+ 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;
+
+ bool mIsSyncWaitingOnNonMainThread;
+
+ // The deepest sync stack frame on any channel.
+ static SyncStackFrame* sStaticTopFrame;
+
+ public:
+ void ProcessNativeEventsInInterruptCall();
+ static void NotifyGeckoEventDispatch();
+
+ private:
+ void SpinInternalEventLoop();
+# if defined(ACCESSIBILITY)
+ bool WaitForSyncNotifyWithA11yReentry();
+# endif // defined(ACCESSIBILITY)
+#endif // defined(OS_WIN)
+
+ private:
+ void CommonThreadOpenInit(MessageChannel* aTargetChan,
+ nsISerialEventTarget* aThread, Side aSide);
+ void OpenAsOtherThread(MessageChannel* aTargetChan,
+ nsISerialEventTarget* aThread, Side aSide);
+
+ void PostErrorNotifyTask();
+ void OnNotifyMaybeChannelError();
+ void ReportConnectionError(const char* aChannelName,
+ Message* aMsg = nullptr) const;
+ void ReportMessageRouteError(const char* channelName) const;
+ bool MaybeHandleError(Result code, const Message& aMsg,
+ const char* channelName);
+
+ void Clear();
+
+ // Send OnChannelConnected notification to listeners.
+ void DispatchOnChannelConnected();
+
+ bool InterruptEventOccurred();
+ bool HasPendingEvents();
+
+ void ProcessPendingRequests(AutoEnterTransaction& aTransaction);
+ bool ProcessPendingRequest(Message&& aUrgent);
+
+ void MaybeUndeferIncall();
+ void EnqueuePendingMessages();
+
+ // Dispatches an incoming message to its appropriate handler.
+ void DispatchMessage(Message&& aMsg);
+
+ // DispatchMessage will route to one of these functions depending on the
+ // protocol type of the message.
+ void DispatchSyncMessage(ActorLifecycleProxy* aProxy, const Message& aMsg,
+ Message*& aReply);
+ void DispatchAsyncMessage(ActorLifecycleProxy* aProxy, const Message& aMsg);
+ void DispatchInterruptMessage(ActorLifecycleProxy* aProxy, Message&& aMsg,
+ size_t aStackDepth);
+
+ // 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(bool aHandleWindowsMessages);
+ bool WaitForInterruptNotify();
+
+ bool WaitResponse(bool aWaitTimedOut);
+
+ bool ShouldContinueFromTimeout();
+
+ void EndTimeout();
+ void CancelTransaction(int transaction);
+
+ void RepostAllMessages();
+
+ // The "remote view of stack depth" can be different than the
+ // actual stack depth when there are out-of-turn replies. When we
+ // receive one, our actual Interrupt stack depth doesn't decrease, but
+ // the other side (that sent the reply) thinks it has. So, the
+ // "view" returned here is |stackDepth| minus the number of
+ // out-of-turn replies.
+ //
+ // Only called from the worker thread.
+ size_t RemoteViewOfStackDepth(size_t stackDepth) const {
+ AssertWorkerThread();
+ return stackDepth - mOutOfTurnReplies.size();
+ }
+
+ int32_t NextSeqno() {
+ AssertWorkerThread();
+ return (mSide == ChildSide) ? --mNextSeqno : ++mNextSeqno;
+ }
+
+ // This helper class manages mCxxStackDepth on behalf of MessageChannel.
+ // When the stack depth is incremented from zero to non-zero, it invokes
+ // a callback, and similarly for when the depth goes from non-zero to zero.
+ void EnteredCxxStack();
+ void ExitedCxxStack();
+
+ void EnteredCall();
+ void ExitedCall();
+
+ void EnteredSyncSend();
+ void ExitedSyncSend();
+
+ void DebugAbort(const char* file, int line, const char* cond, const char* why,
+ bool reply = false);
+
+ // This method is only safe to call on the worker thread, or in a
+ // debugger with all threads paused.
+ void DumpInterruptStack(const char* const pfx = "") const;
+
+ void AddProfilerMarker(const IPC::Message& aMessage,
+ MessageDirection aDirection);
+
+ private:
+ // Called from both threads
+ size_t InterruptStackDepth() const {
+ mMonitor->AssertCurrentThreadOwns();
+ return mInterruptStack.size();
+ }
+
+ bool AwaitingInterruptReply() const {
+ mMonitor->AssertCurrentThreadOwns();
+ return !mInterruptStack.empty();
+ }
+ bool AwaitingIncomingMessage() const {
+ mMonitor->AssertCurrentThreadOwns();
+ return mIsWaitingForIncoming;
+ }
+
+ class MOZ_STACK_CLASS AutoEnterWaitForIncoming {
+ public:
+ explicit AutoEnterWaitForIncoming(MessageChannel& aChannel)
+ : mChannel(aChannel) {
+ aChannel.mMonitor->AssertCurrentThreadOwns();
+ aChannel.mIsWaitingForIncoming = true;
+ }
+
+ ~AutoEnterWaitForIncoming() { mChannel.mIsWaitingForIncoming = false; }
+
+ private:
+ MessageChannel& mChannel;
+ };
+ friend class AutoEnterWaitForIncoming;
+
+ // Returns true if we're dispatching an async message's callback.
+ bool DispatchingAsyncMessage() const {
+ AssertWorkerThread();
+ return mDispatchingAsyncMessage;
+ }
+
+ int DispatchingAsyncMessageNestedLevel() const {
+ AssertWorkerThread();
+ return mDispatchingAsyncMessageNestedLevel;
+ }
+
+ bool Connected() const;
+
+ private:
+ // Executed on the IO thread.
+ void NotifyWorkerThread();
+
+ // 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);
+
+ void OnChannelConnected(int32_t peer_id);
+
+ // Tell the IO thread to close the channel and wait for it to ACK.
+ void SynchronouslyClose();
+
+ // 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. This should only be used for
+ // non-special messages that might have to be postponed.
+ void SendMessageToLink(UniquePtr<Message> aMsg);
+
+ bool WasTransactionCanceled(int transaction);
+ bool ShouldDeferMessage(const Message& aMsg);
+ bool ShouldDeferInterruptMessage(const Message& aMsg, size_t aStackDepth);
+ void OnMessageReceivedFromLink(Message&& aMsg);
+ void OnChannelErrorFromLink();
+
+ private:
+ // Run on the not current thread.
+ void NotifyChannelClosed();
+ void NotifyMaybeChannelError();
+
+ private:
+ void AssertWorkerThread() const {
+ MOZ_ASSERT(mWorkerThread, "Channel hasn't been opened yet");
+ MOZ_RELEASE_ASSERT(mWorkerThread && mWorkerThread->IsOnCurrentThread(),
+ "not on worker thread!");
+ }
+
+ // The "link" thread is either the I/O thread (ProcessLink), the other
+ // actor's work thread (ThreadLink), or the worker thread (same-thread
+ // channels).
+ void AssertLinkThread() const {
+ if (mIsSameThreadChannel) {
+ // If we're a same-thread channel, we have to be on our worker
+ // thread.
+ AssertWorkerThread();
+ return;
+ }
+
+ // If we aren't a same-thread channel, our "link" thread is _not_ our
+ // worker thread!
+ MOZ_ASSERT(mWorkerThread, "Channel hasn't been opened yet");
+ MOZ_RELEASE_ASSERT(mWorkerThread && !mWorkerThread->IsOnCurrentThread(),
+ "on worker thread but should not be!");
+ }
+
+ private:
+ class MessageTask : public CancelableRunnable,
+ public LinkedListElement<RefPtr<MessageTask>>,
+ public nsIRunnablePriority,
+ public nsIRunnableIPCMessageType {
+ public:
+ explicit MessageTask(MessageChannel* aChannel, Message&& aMessage);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD Run() override;
+ nsresult Cancel() override;
+ NS_IMETHOD GetPriority(uint32_t* aPriority) override;
+ NS_DECL_NSIRUNNABLEIPCMESSAGETYPE
+ void Post();
+ void Clear();
+
+ bool IsScheduled() const { return mScheduled; }
+
+ Message& Msg() { return mMessage; }
+ const Message& Msg() const { return mMessage; }
+
+ private:
+ MessageTask() = delete;
+ MessageTask(const MessageTask&) = delete;
+ ~MessageTask() = default;
+
+ MessageChannel* mChannel;
+ Message mMessage;
+ bool mScheduled : 1;
+ };
+
+ bool ShouldRunMessage(const Message& aMsg);
+ void RunMessage(MessageTask& aTask);
+
+ typedef LinkedList<RefPtr<MessageTask>> MessageQueue;
+ typedef std::map<size_t, Message> MessageMap;
+ 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* mName;
+
+ // Based on presumption the listener owns and overlives the channel,
+ // this is never nullified.
+ IToplevelProtocol* mListener;
+ ChannelState mChannelState;
+ RefPtr<RefCountedMonitor> mMonitor;
+ Side mSide;
+ bool mIsCrossProcess;
+ UniquePtr<MessageLink> mLink;
+ RefPtr<CancelableRunnable>
+ mChannelErrorTask; // NotifyMaybeChannelError runnable
+
+ // Thread we are allowed to send and receive on.
+ nsCOMPtr<nsISerialEventTarget> mWorkerThread;
+
+ // 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.
+ int32_t mTimeoutMs;
+ bool mInTimeoutSecondHalf;
+
+ // Worker-thread only; sequence numbers for messages that require
+ // replies.
+ int32_t mNextSeqno;
+
+ static bool sIsPumpingMessages;
+
+ // If ::Send returns false, this gives a more descriptive error.
+ SyncSendError mLastSendError;
+
+ 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;
+ int mDispatchingAsyncMessageNestedLevel;
+
+ // 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;
+
+ int32_t CurrentNestedInsideSyncTransaction() const;
+
+ bool AwaitingSyncReply() const;
+ int AwaitingSyncReplyNestedLevel() const;
+
+ bool DispatchingSyncMessage() const;
+ int DispatchingSyncMessageNestedLevel() const;
+
+#ifdef DEBUG
+ void AssertMaybeDeferredCountCorrect();
+#else
+ void AssertMaybeDeferredCountCorrect() {}
+#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;
+ int mTimedOutMessageNestedLevel;
+
+ // Queue of all incoming messages.
+ //
+ // If both this side and the other side are functioning correctly, the queue
+ // can only be in certain configurations. Let
+ //
+ // |A<| be an async in-message,
+ // |S<| be a sync in-message,
+ // |C<| be an Interrupt in-call,
+ // |R<| be an Interrupt reply.
+ //
+ // The queue can only match this configuration
+ //
+ // A<* (S< | C< | R< (?{mInterruptStack.size() == 1} A<* (S< | C<)))
+ //
+ // The other side can send as many async messages |A<*| as it wants before
+ // sending us a blocking message.
+ //
+ // The first case is |S<|, a sync in-msg. The other side must be blocked,
+ // and thus can't send us any more messages until we process the sync
+ // in-msg.
+ //
+ // The second case is |C<|, an Interrupt in-call; the other side must be
+ // blocked. (There's a subtlety here: this in-call might have raced with an
+ // out-call, but we detect that with the mechanism below,
+ // |mRemoteStackDepth|, and races don't matter to the queue.)
+ //
+ // Final case, the other side replied to our most recent out-call |R<|.
+ // If that was the *only* out-call on our stack,
+ // |?{mInterruptStack.size() == 1}|, then other side "finished with us,"
+ // and went back to its own business. That business might have included
+ // sending any number of async message |A<*| until sending a blocking
+ // message |(S< | C<)|. If we had more than one Interrupt call on our
+ // stack, the other side *better* not have sent us another blocking
+ // message, because it's blocked on a reply from us.
+ //
+ MessageQueue mPending;
+
+ // 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;
+
+ // Stack of all the out-calls on which this channel is awaiting responses.
+ // Each stack refers to a different protocol and the stacks are mutually
+ // exclusive: multiple outcalls of the same kind cannot be initiated while
+ // another is active.
+ std::stack<MessageInfo> mInterruptStack;
+
+ // This is what we think the Interrupt stack depth is on the "other side" of
+ // this Interrupt channel. We maintain this variable so that we can detect
+ // racy Interrupt calls. With each Interrupt out-call sent, we send along
+ // what *we* think the stack depth of the remote side is *before* it will
+ // receive the Interrupt call.
+ //
+ // After sending the out-call, our stack depth is "incremented" by pushing
+ // that pending message onto mPending.
+ //
+ // Then when processing an in-call |c|, it must be true that
+ //
+ // mInterruptStack.size() == c.remoteDepth
+ //
+ // I.e., my depth is actually the same as what the other side thought it
+ // was when it sent in-call |c|. If this fails to hold, we have detected
+ // racy Interrupt calls.
+ //
+ // We then increment mRemoteStackDepth *just before* processing the
+ // in-call, since we know the other side is waiting on it, and decrement
+ // it *just after* finishing processing that in-call, since our response
+ // will pop the top of the other side's |mPending|.
+ //
+ // One nice aspect of this race detection is that it is symmetric; if one
+ // side detects a race, then the other side must also detect the same race.
+ size_t mRemoteStackDepthGuess;
+
+ // Approximation of code frames on the C++ stack. It can only be
+ // interpreted as the implication:
+ //
+ // !mCxxStackFrames.empty() => MessageChannel code on C++ stack
+ //
+ // This member is only accessed on the worker thread, and so is not
+ // protected by mMonitor. It is managed exclusively by the helper
+ // |class CxxStackFrame|.
+ mozilla::Vector<InterruptFrame> mCxxStackFrames;
+
+ // Did we process an Interrupt out-call during this stack? Only meaningful in
+ // ExitedCxxStack(), from which this variable is reset.
+ bool mSawInterruptOutMsg;
+
+ // Are we waiting on this channel for an incoming message? This is used
+ // to implement WaitForIncomingMessage(). Must only be accessed while owning
+ // mMonitor.
+ bool mIsWaitingForIncoming;
+
+ // Map of replies received "out of turn", because of Interrupt
+ // in-calls racing with replies to outstanding in-calls. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=521929.
+ MessageMap mOutOfTurnReplies;
+
+ // Map of async Callbacks that are still waiting replies.
+ CallbackMap mPendingResponses;
+
+ // Stack of Interrupt in-calls that were deferred because of race
+ // conditions.
+ std::stack<Message> mDeferred;
+
+#ifdef OS_WIN
+ HANDLE mEvent;
+#endif
+
+ // Should the channel abort the process from the I/O thread when
+ // a channel error occurs?
+ bool mAbortOnError;
+
+ // True if the listener has already been notified of a channel close or
+ // error.
+ bool mNotifiedChannelDone;
+
+ // See SetChannelFlags
+ ChannelFlags mFlags;
+
+ // Task and state used to asynchronously notify channel has been connected
+ // safely. This is necessary to be able to cancel notification if we are
+ // closed at the same time.
+ RefPtr<CancelableRunnable> mOnChannelConnectedTask;
+ bool mPeerPidSet;
+ int32_t mPeerPid;
+
+ // Channels can enter messages are not sent immediately; instead, they are
+ // held in a queue until another thread deems it is safe to send them.
+ bool mIsPostponingSends;
+ std::vector<UniquePtr<Message>> mPostponedSends;
+
+ bool mBuildIDsConfirmedMatch;
+
+ // If this is true, both ends of this message channel have event targets
+ // on the same thread.
+ bool mIsSameThreadChannel;
+};
+
+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
+
+#ifdef MOZ_GECKO_PROFILER
+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) {
+ 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);
+ }
+ 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
+
+#endif // ifndef ipc_glue_MessageChannel_h
diff --git a/ipc/glue/MessageLink.cpp b/ipc/glue/MessageLink.cpp
new file mode 100644
index 0000000000..3c6b8e51c8
--- /dev/null
+++ b/ipc/glue/MessageLink.cpp
@@ -0,0 +1,386 @@
+/* -*- 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 "mozilla/ipc/MessageChannel.h"
+#include "mozilla/ipc/BrowserProcessSubThread.h"
+#include "mozilla/ipc/ProtocolUtils.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;
+
+// We rely on invariants about the lifetime of the transport:
+//
+// - outlives this MessageChannel
+// - deleted on the IO thread
+//
+// These invariants allow us to send messages directly through the
+// transport without having to worry about orphaned Send() tasks on
+// the IO thread touching MessageChannel memory after it's been deleted
+// on the worker thread. We also don't need to refcount the
+// Transport, because whatever task triggers its deletion only runs on
+// the IO thread, and only runs after this MessageChannel is done with
+// the Transport.
+
+namespace mozilla {
+namespace ipc {
+
+MessageLink::MessageLink(MessageChannel* aChan) : mChan(aChan) {}
+
+MessageLink::~MessageLink() {
+#ifdef DEBUG
+ mChan = nullptr;
+#endif
+}
+
+ProcessLink::ProcessLink(MessageChannel* aChan)
+ : MessageLink(aChan), mIOLoop(nullptr), mExistingListener(nullptr) {}
+
+ProcessLink::~ProcessLink() {
+ // Dispatch the delete of the transport to the IO thread.
+ RefPtr<DeleteTask<IPC::Channel>> task =
+ new DeleteTask<IPC::Channel>(mTransport.release());
+ XRE_GetIOMessageLoop()->PostTask(task.forget());
+
+#ifdef DEBUG
+ mIOLoop = nullptr;
+ mExistingListener = nullptr;
+#endif
+}
+
+void ProcessLink::Open(UniquePtr<Transport> aTransport, MessageLoop* aIOLoop,
+ Side aSide) {
+ mChan->AssertWorkerThread();
+
+ MOZ_ASSERT(aTransport, "need transport layer");
+
+ // FIXME need to check for valid channel
+
+ mTransport = std::move(aTransport);
+
+ // FIXME figure out whether we're in parent or child, grab IO loop
+ // appropriately
+ bool needOpen = true;
+ if (aIOLoop) {
+ // We're a child or using the new arguments. Either way, we
+ // need an open.
+ needOpen = true;
+ mChan->mSide = (aSide == UnknownSide) ? ChildSide : aSide;
+ } else {
+ MOZ_ASSERT(aSide == UnknownSide, "expected default side arg");
+
+ // parent
+ mChan->mSide = ParentSide;
+ needOpen = false;
+ aIOLoop = XRE_GetIOMessageLoop();
+ }
+
+ mIOLoop = aIOLoop;
+
+ NS_ASSERTION(mIOLoop, "need an IO loop");
+ NS_ASSERTION(mChan->mWorkerThread, "need a worker thread");
+
+ // If we were never able to open the transport, immediately post an error
+ // message.
+ if (mTransport->Unsound_IsClosed()) {
+ mIOLoop->PostTask(
+ NewNonOwningRunnableMethod("ipc::ProcessLink::OnChannelConnectError",
+ this, &ProcessLink::OnChannelConnectError));
+ return;
+ }
+
+ {
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ if (needOpen) {
+ // Transport::Connect() has not been called. Call it so
+ // we start polling our pipe and processing outgoing
+ // messages.
+ mIOLoop->PostTask(
+ NewNonOwningRunnableMethod("ipc::ProcessLink::OnChannelOpened", this,
+ &ProcessLink::OnChannelOpened));
+ } else {
+ // Transport::Connect() has already been called. Take
+ // over the channel from the previous listener and process
+ // any queued messages.
+ mIOLoop->PostTask(NewNonOwningRunnableMethod(
+ "ipc::ProcessLink::OnTakeConnectedChannel", this,
+ &ProcessLink::OnTakeConnectedChannel));
+ }
+
+ // Wait until one of the runnables above changes the state of the
+ // channel. Note that the state could be changed again after that (to
+ // ChannelClosing, for example, by the IO thread). We can rely on it not
+ // changing back to Closed: only the worker thread changes it to closed,
+ // and we're on the worker thread, blocked.
+ while (mChan->mChannelState == ChannelClosed) {
+ mChan->mMonitor->Wait();
+ }
+ }
+}
+
+void ProcessLink::SendMessage(UniquePtr<Message> msg) {
+ if (msg->size() > IPC::Channel::kMaximumMessageSize) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCMessageName,
+ nsDependentCString(msg->name()));
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCMessageSize,
+ static_cast<unsigned int>(msg->size()));
+ MOZ_CRASH("IPC message size is too large");
+ }
+
+ if (!mChan->mIsPostponingSends) {
+ mChan->AssertWorkerThread();
+ }
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ msg->AssertAsLargeAsHeader();
+
+ mIOLoop->PostTask(NewNonOwningRunnableMethod<UniquePtr<Message>&&>(
+ "IPC::Channel::Send", mTransport.get(), &Transport::Send,
+ std::move(msg)));
+}
+
+void ProcessLink::SendClose() {
+ mChan->AssertWorkerThread();
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ mIOLoop->PostTask(NewNonOwningRunnableMethod(
+ "ipc::ProcessLink::OnCloseChannel", this, &ProcessLink::OnCloseChannel));
+}
+
+ThreadLink::ThreadLink(MessageChannel* aChan, MessageChannel* aTargetChan)
+ : MessageLink(aChan), mTargetChan(aTargetChan) {}
+
+void ThreadLink::PrepareToDestroy() {
+ MOZ_ASSERT(mChan);
+ MOZ_ASSERT(mChan->mMonitor);
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ // Bug 848949: We need to prevent the other side
+ // from sending us any more messages to avoid Use-After-Free.
+ // The setup here is as shown:
+ //
+ // (Us) (Them)
+ // MessageChannel MessageChannel
+ // | ^ \ / ^ |
+ // | | X | |
+ // v | / \ | v
+ // ThreadLink ThreadLink
+ //
+ // We want to null out the diagonal link from their ThreadLink
+ // to our MessageChannel. Note that we must hold the monitor so
+ // that we do this atomically with respect to them trying to send
+ // us a message. Since the channels share the same monitor this
+ // also protects against the two PrepareToDestroy() calls racing.
+ //
+ //
+ // Why splitting is done in a method separate from ~ThreadLink:
+ //
+ // ThreadLinks are destroyed in MessageChannel::Clear(), when
+ // nullptr is assigned to the UniquePtr<> MessageChannel::mLink.
+ // This single line of code gets executed in three separate steps:
+ // 1. Load the value of mLink into a temporary.
+ // 2. Store nullptr in the mLink field.
+ // 3. Call the destructor on the temporary from step 1.
+ // This is all done without holding the monitor.
+ // The splitting operation, among other things, loads the mLink field
+ // of the other thread's MessageChannel while holding the monitor.
+ // If splitting was done in the destructor, and the two sides were
+ // both running MessageChannel::Clear(), then there would be a race
+ // between the store to mLink in Clear() and the load of mLink
+ // during splitting.
+ // Instead, we call PrepareToDestroy() prior to step 1. One thread or
+ // the other will run the entire method before the other thread,
+ // because this method acquires the monitor. Once that is done, the
+ // mTargetChan of both ThreadLink will be null, so they will no
+ // longer be able to access the other and so there won't be any races.
+ //
+ // An alternate approach would be to hold the monitor in Clear() or
+ // make mLink atomic, but MessageLink does not have to worry about
+ // Clear() racing with Clear(), so it would be inefficient.
+ if (mTargetChan) {
+ MOZ_ASSERT(mTargetChan->mLink);
+ static_cast<ThreadLink*>(mTargetChan->mLink.get())->mTargetChan = nullptr;
+ }
+ mTargetChan = nullptr;
+}
+
+void ThreadLink::SendMessage(UniquePtr<Message> msg) {
+ if (!mChan->mIsPostponingSends) {
+ mChan->AssertWorkerThread();
+ }
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ if (mTargetChan) mTargetChan->OnMessageReceivedFromLink(std::move(*msg));
+}
+
+void ThreadLink::SendClose() {
+ mChan->AssertWorkerThread();
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ mChan->mChannelState = ChannelClosed;
+
+ // In a ProcessLink, we would close our half the channel. This
+ // would show up on the other side as an error on the I/O thread.
+ // The I/O thread would then invoke OnChannelErrorFromLink().
+ // As usual, we skip that process and just invoke the
+ // OnChannelErrorFromLink() method directly.
+ if (mTargetChan) mTargetChan->OnChannelErrorFromLink();
+}
+
+bool ThreadLink::Unsound_IsClosed() const {
+ MonitorAutoLock lock(*mChan->mMonitor);
+ return mChan->mChannelState == ChannelClosed;
+}
+
+uint32_t ThreadLink::Unsound_NumQueuedMessages() const {
+ // ThreadLinks don't have a message queue.
+ return 0;
+}
+
+//
+// The methods below run in the context of the IO thread
+//
+
+void ProcessLink::OnMessageReceived(Message&& msg) {
+ AssertIOThread();
+ NS_ASSERTION(mChan->mChannelState != ChannelError, "Shouldn't get here!");
+ MonitorAutoLock lock(*mChan->mMonitor);
+ mChan->OnMessageReceivedFromLink(std::move(msg));
+}
+
+void ProcessLink::OnChannelOpened() {
+ AssertIOThread();
+
+ {
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ mExistingListener = mTransport->set_listener(this);
+#ifdef DEBUG
+ if (mExistingListener) {
+ std::queue<Message> pending;
+ mExistingListener->GetQueuedMessages(pending);
+ MOZ_ASSERT(pending.empty());
+ }
+#endif // DEBUG
+
+ mChan->mChannelState = ChannelOpening;
+ lock.Notify();
+ }
+
+ if (!mTransport->Connect()) {
+ mTransport->Close();
+ OnChannelError();
+ }
+}
+
+void ProcessLink::OnTakeConnectedChannel() {
+ AssertIOThread();
+
+ std::queue<Message> pending;
+ {
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ mChan->mChannelState = ChannelConnected;
+
+ mExistingListener = mTransport->set_listener(this);
+ if (mExistingListener) {
+ mExistingListener->GetQueuedMessages(pending);
+ }
+ lock.Notify();
+ }
+
+ // Dispatch whatever messages the previous listener had queued up.
+ while (!pending.empty()) {
+ OnMessageReceived(std::move(pending.front()));
+ pending.pop();
+ }
+}
+
+void ProcessLink::OnChannelConnected(int32_t peer_pid) {
+ AssertIOThread();
+
+ bool notifyChannel = false;
+
+ {
+ MonitorAutoLock lock(*mChan->mMonitor);
+ // Do not force it into connected if it has errored out, started
+ // closing, etc. Note that we can be in the Connected state already
+ // since the parent starts out Connected.
+ if (mChan->mChannelState == ChannelOpening ||
+ mChan->mChannelState == ChannelConnected) {
+ mChan->mChannelState = ChannelConnected;
+ mChan->mMonitor->Notify();
+ notifyChannel = true;
+ }
+ }
+
+ if (mExistingListener) {
+ mExistingListener->OnChannelConnected(peer_pid);
+ }
+
+ if (notifyChannel) {
+ mChan->OnChannelConnected(peer_pid);
+ }
+}
+
+void ProcessLink::OnChannelConnectError() {
+ AssertIOThread();
+
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ mChan->OnChannelErrorFromLink();
+}
+
+void ProcessLink::OnChannelError() {
+ AssertIOThread();
+
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ MOZ_ALWAYS_TRUE(this == mTransport->set_listener(mExistingListener));
+
+ mChan->OnChannelErrorFromLink();
+}
+
+void ProcessLink::OnCloseChannel() {
+ AssertIOThread();
+
+ mTransport->Close();
+
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ DebugOnly<IPC::Channel::Listener*> previousListener =
+ mTransport->set_listener(mExistingListener);
+
+ // OnChannelError may have reset the listener already.
+ MOZ_ASSERT(previousListener == this || previousListener == mExistingListener);
+
+ mChan->mChannelState = ChannelClosed;
+ mChan->mMonitor->Notify();
+}
+
+bool ProcessLink::Unsound_IsClosed() const {
+ return mTransport->Unsound_IsClosed();
+}
+
+uint32_t ProcessLink::Unsound_NumQueuedMessages() const {
+ return mTransport->Unsound_NumQueuedMessages();
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/MessageLink.h b/ipc/glue/MessageLink.h
new file mode 100644
index 0000000000..bad3930799
--- /dev/null
+++ b/ipc/glue/MessageLink.h
@@ -0,0 +1,129 @@
+/* -*- 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 "mozilla/Assertions.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ipc/Transport.h"
+
+namespace IPC {
+class Message;
+}
+
+namespace mozilla {
+namespace ipc {
+
+class MessageChannel;
+
+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();
+
+ // This is called immediately before the MessageChannel destroys its
+ // MessageLink. See the implementation in ThreadLink for details.
+ virtual void PrepareToDestroy(){};
+
+ // n.b.: These methods all require that the channel monitor is
+ // held when they are invoked.
+ virtual void SendMessage(mozilla::UniquePtr<Message> msg) = 0;
+ virtual void SendClose() = 0;
+
+ virtual bool Unsound_IsClosed() const = 0;
+ virtual uint32_t Unsound_NumQueuedMessages() const = 0;
+
+ protected:
+ MessageChannel* mChan;
+};
+
+class ProcessLink : public MessageLink, public Transport::Listener {
+ void OnCloseChannel();
+ void OnChannelOpened();
+ void OnTakeConnectedChannel();
+
+ void AssertIOThread() const {
+ MOZ_ASSERT(mIOLoop == MessageLoop::current(), "not on I/O thread!");
+ }
+
+ public:
+ explicit ProcessLink(MessageChannel* chan);
+ virtual ~ProcessLink();
+
+ // The ProcessLink will register itself as the IPC::Channel::Listener on the
+ // transport passed here. If the transport already has a listener registered
+ // then a listener chain will be established (the ProcessLink listener
+ // methods will be called first and may call some methods on the original
+ // listener as well). Once the channel is closed (either via normal shutdown
+ // or a pipe error) the chain will be destroyed and the original listener
+ // will again be registered.
+ void Open(UniquePtr<Transport> aTransport, MessageLoop* aIOLoop, Side aSide);
+
+ // Run on the I/O thread, only when using inter-process link.
+ // These methods acquire the monitor and forward to the
+ // similarly named methods in AsyncChannel below
+ // (OnMessageReceivedFromLink(), etc)
+ virtual void OnMessageReceived(Message&& msg) override;
+ virtual void OnChannelConnected(int32_t peer_pid) override;
+ virtual void OnChannelError() override;
+
+ virtual void SendMessage(mozilla::UniquePtr<Message> msg) override;
+ virtual void SendClose() override;
+
+ virtual bool Unsound_IsClosed() const override;
+ virtual uint32_t Unsound_NumQueuedMessages() const override;
+
+ protected:
+ void OnChannelConnectError();
+
+ protected:
+ UniquePtr<Transport> mTransport;
+ MessageLoop* mIOLoop; // thread where IO happens
+ Transport::Listener* mExistingListener; // channel's previous listener
+};
+
+class ThreadLink : public MessageLink {
+ public:
+ ThreadLink(MessageChannel* aChan, MessageChannel* aTargetChan);
+ virtual ~ThreadLink() = default;
+
+ virtual void PrepareToDestroy() override;
+
+ virtual void SendMessage(mozilla::UniquePtr<Message> msg) override;
+ virtual void SendClose() override;
+
+ virtual bool Unsound_IsClosed() const override;
+ virtual uint32_t Unsound_NumQueuedMessages() const override;
+
+ protected:
+ MessageChannel* mTargetChan;
+};
+
+} // 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..d274dbf12f
--- /dev/null
+++ b/ipc/glue/MessagePump.cpp
@@ -0,0 +1,449 @@
+/* -*- 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;
+
+NS_DEFINE_NAMED_CID(NS_TIMER_CID);
+
+#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(nsIEventTarget* 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);
+}
+
+nsIEventTarget* MessagePump::GetXPCOMThread() {
+ if (mEventTarget) {
+ return mEventTarget;
+ }
+
+ // Main thread
+ return GetMainThreadEventTarget();
+}
+
+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;
+}
+
+#if defined(XP_WIN)
+
+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);
+
+ base::ScopedNSAutoreleasePool autoReleasePool;
+ for (;;) {
+ autoReleasePool.Recycle();
+
+ 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;
+}
+
+#endif // XP_WIN
+
+#if defined(MOZ_WIDGET_ANDROID)
+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");
+}
+#endif // defined(MOZ_WIDGET_ANDROID)
diff --git a/ipc/glue/MessagePump.h b/ipc/glue/MessagePump.h
new file mode 100644
index 0000000000..0b7ff11902
--- /dev/null
+++ b/ipc/glue/MessagePump.h
@@ -0,0 +1,174 @@
+/* -*- 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"
+#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(nsIEventTarget* 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 nsIEventTarget* GetXPCOMThread() override;
+
+ protected:
+ virtual ~MessagePump();
+
+ private:
+ // Only called by DoWorkRunnable.
+ void DoDelayedWork(base::MessagePump::Delegate* aDelegate);
+
+ protected:
+ nsIEventTarget* 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(nsIEventTarget* 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. Currently only
+// implemented for Win.
+class MessagePumpForNonMainUIThreads final : public base::MessagePumpForUI,
+ public nsIThreadObserver {
+ public:
+ // We don't want xpcom refing, chromium controls our lifetime via
+ // RefCountedThreadSafe.
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override { return 2; }
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) override { return 1; }
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
+
+ NS_DECL_NSITHREADOBSERVER
+
+ public:
+ explicit MessagePumpForNonMainUIThreads(nsIEventTarget* aEventTarget)
+ : mInWait(false), mWaitLock("mInWait") {}
+
+ // The main run loop for this thread.
+ virtual void DoRunLoop() override;
+
+ virtual nsIEventTarget* 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;
+ mozilla::Mutex mWaitLock;
+};
+#endif // defined(XP_WIN)
+
+#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(nsIEventTarget* aEventTarget)
+ : mEventTarget(aEventTarget) {}
+
+ virtual void Run(Delegate* delegate);
+ virtual void Quit();
+ virtual void ScheduleWork();
+ virtual void ScheduleDelayedWork(const base::TimeTicks& delayed_work_time);
+ virtual nsIEventTarget* GetXPCOMThread() { return mEventTarget; }
+
+ private:
+ ~MessagePumpForAndroidUI() {}
+ MessagePumpForAndroidUI() {}
+
+ nsIEventTarget* mEventTarget;
+};
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+} /* namespace ipc */
+} /* namespace mozilla */
+
+#endif /* __IPC_GLUE_MESSAGEPUMP_H__ */
diff --git a/ipc/glue/MiniTransceiver.cpp b/ipc/glue/MiniTransceiver.cpp
new file mode 100644
index 0000000000..11b376dcd4
--- /dev/null
+++ b/ipc/glue/MiniTransceiver.cpp
@@ -0,0 +1,241 @@
+/* -*- 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 "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>
+
+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, int 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.
+ int num_fds = aMsg.file_descriptor_set()->size();
+
+ cmsghdr* cmsg = CMSG_FIRSTHDR(aHdr);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int) * num_fds);
+ aMsg.file_descriptor_set()->GetDescriptors(
+ reinterpret_cast<int*>(CMSG_DATA(cmsg)));
+
+ // This number will be sent in the header of the message. So, we
+ // can check it at the other side.
+ aMsg.header()->num_fds = 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.file_descriptor_set()->CommitAll(); });
+
+ int num_fds = aMsg.file_descriptor_set()->size();
+ 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(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);
+ msg->file_descriptor_set()->SetDescriptors(all_fds, num_all_fds);
+
+ if (mDataBufClear == DataBufferClear::AfterReceiving) {
+ // Avoid content processes from reading the content of
+ // messages.
+ memset(databuf.get(), 0, msgsz);
+ }
+
+ MOZ_ASSERT(msg->header()->num_fds == msg->file_descriptor_set()->size(),
+ "The number of file descriptors in the header is different from"
+ " the number actually received");
+
+ aMsg = std::move(*msg);
+ return true;
+}
diff --git a/ipc/glue/MiniTransceiver.h b/ipc/glue/MiniTransceiver.h
new file mode 100644
index 0000000000..f7b482e3ca
--- /dev/null
+++ b/ipc/glue/MiniTransceiver.h
@@ -0,0 +1,117 @@
+/* -*- 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(IPC::Message& aMsg);
+ inline bool RecvInfallible(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/PBackground.ipdl b/ipc/glue/PBackground.ipdl
new file mode 100644
index 0000000000..c0de216610
--- /dev/null
+++ b/ipc/glue/PBackground.ipdl
@@ -0,0 +1,289 @@
+/* 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 PBackgroundStorage;
+include protocol PBackgroundTest;
+include protocol PBroadcastChannel;
+include protocol PCache;
+include protocol PCacheStorage;
+include protocol PCacheStreamControl;
+include protocol PClientManager;
+include protocol PEndpointForReport;
+include protocol PFileDescriptorSet;
+include protocol PFileSystemRequest;
+include protocol PGamepadEventChannel;
+include protocol PGamepadTestChannel;
+include protocol PHttpBackgroundChannel;
+include protocol PIdleScheduler;
+include protocol PRemoteLazyInputStream;
+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 PMIDIManager;
+include protocol PMIDIPort;
+include protocol PQuota;
+include protocol PChildToParentStream;
+include protocol PParentToChildStream;
+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 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/layers/LayersMessageUtils.h";
+
+using mozilla::dom::cache::Namespace
+ from "mozilla/dom/cache/Types.h";
+
+namespace mozilla {
+namespace ipc {
+
+sync refcounted protocol PBackground
+{
+ manages PBackgroundDataBridge;
+ manages PBackgroundIDBFactory;
+ manages PBackgroundIndexedDBUtils;
+ manages PBackgroundSDBConnection;
+ manages PBackgroundLSDatabase;
+ manages PBackgroundLSObserver;
+ manages PBackgroundLSRequest;
+ manages PBackgroundLSSimpleRequest;
+ manages PBackgroundLocalStorageCache;
+ manages PBackgroundSessionStorageManager;
+ manages PBackgroundStorage;
+ manages PBackgroundTest;
+ manages PBroadcastChannel;
+ manages PCache;
+ manages PCacheStorage;
+ manages PCacheStreamControl;
+ manages PClientManager;
+ manages PEndpointForReport;
+ manages PFileDescriptorSet;
+ manages PFileSystemRequest;
+ manages PGamepadEventChannel;
+ manages PGamepadTestChannel;
+ manages PHttpBackgroundChannel;
+ manages PIdleScheduler;
+ manages PRemoteLazyInputStream;
+ manages PMediaTransport;
+ manages PRemoteWorker;
+ manages PRemoteWorkerController;
+ manages PRemoteWorkerService;
+ manages PSharedWorker;
+ manages PTemporaryIPCBlob;
+ manages PFileCreator;
+ manages PMessagePort;
+ manages PCameras;
+ manages PMIDIManager;
+ manages PMIDIPort;
+ manages PQuota;
+ manages PChildToParentStream;
+ manages PParentToChildStream;
+ manages PServiceWorker;
+ manages PServiceWorkerContainer;
+ manages PServiceWorkerManager;
+ manages PServiceWorkerRegistration;
+ manages PWebAuthnTransaction;
+ manages PUDPSocket;
+ manages PVerifySSLServerCert;
+ manages PVsync;
+
+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);
+
+ /**
+ * Asynchronously propagates the "last-pb-context-exited" observer
+ * notification to LocalStorage NextGen implementation so it can clear
+ * retained private-browsing in-memory Datastores. Using a (same-process)
+ * IPC message avoids the need for the main-thread nsIObserver to have a
+ * reference to the PBackground thread and directly dispatch a runnable to it.
+ */
+ async LSClearPrivateBrowsing();
+
+ async PBackgroundLocalStorageCache(PrincipalInfo principalInfo,
+ nsCString originKey,
+ uint32_t privateBrowsingId);
+
+ async PBackgroundSessionStorageManager(uint64_t aTopContextId);
+
+ async PBackgroundStorage(nsString profilePath, uint32_t privateBrowsingId);
+
+ 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 PChildToParentStream();
+
+ 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 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 PMIDIManager();
+ async PMIDIPort(MIDIPortInfo portInfo, bool sysexEnabled);
+
+ // 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 PVerifySSLServerCert(ByteArray aServerCert,
+ 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);
+
+child:
+ async PCache();
+ async PCacheStreamControl();
+
+ async PParentToChildStream();
+
+ async PRemoteWorker(RemoteWorkerData data);
+
+both:
+ // PRemoteLazyInputStream is created on the parent side only if the child
+ // starts a migration.
+ async PRemoteLazyInputStream(nsID aID, uint64_t aSize);
+
+ async PFileDescriptorSet(FileDescriptor fd);
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/PBackgroundSharedTypes.ipdlh b/ipc/glue/PBackgroundSharedTypes.ipdlh
new file mode 100644
index 0000000000..00cea24a2b
--- /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/PBackgroundTest.ipdl b/ipc/glue/PBackgroundTest.ipdl
new file mode 100644
index 0000000000..527cd4ce7d
--- /dev/null
+++ b/ipc/glue/PBackgroundTest.ipdl
@@ -0,0 +1,20 @@
+/* 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.
+protocol PBackgroundTest
+{
+ manager PBackground;
+
+child:
+ async __delete__(nsCString testArg);
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/PChildToParentStream.ipdl b/ipc/glue/PChildToParentStream.ipdl
new file mode 100644
index 0000000000..ac0e2ce142
--- /dev/null
+++ b/ipc/glue/PChildToParentStream.ipdl
@@ -0,0 +1,47 @@
+/* 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;
+include protocol PContent;
+include protocol PSocketProcess;
+
+include "mozilla/layers/WebRenderMessageUtils.h";
+
+using mozilla::wr::ByteBuffer from "mozilla/webrender/WebRenderTypes.h";
+
+namespace mozilla {
+namespace ipc {
+
+// This is protocol is the opposite of PParentToChildStream. Please keep these
+// protocols in sync.
+protocol PChildToParentStream
+{
+ manager PBackground or PContent or PSocketProcess;
+
+parent:
+ async Buffer(ByteBuffer aBuffer);
+ async Close(nsresult aRv);
+
+child:
+ // The remote stream can be used in 2 ways: it can start receiving data
+ // immediately after the creation of the child actor, or it can wait until
+ // the child stream is actually used. This second configuration is enabled by
+ // passing 'true' to delayedStart in AutoIPCStream CTOR.
+ // If we are delaying the reading, at the first use of the remote stream, we
+ // must activate the sending of data. This happens by calling this method.
+ async StartReading();
+
+ // The parent side has hit an error condition and has requested the child
+ // actor issue a Close() message. The close must be initiated by the child
+ // to avoid racing with an in-flight Buffer() message.
+ async RequestClose(nsresult aRv);
+
+ // Stream is always destroyed from the parent side. This occurs if the
+ // parent encounters an error while writing to its pipe or if the child
+ // signals the stream should close by SendClose().
+ async __delete__();
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/PFileDescriptorSet.ipdl b/ipc/glue/PFileDescriptorSet.ipdl
new file mode 100644
index 0000000000..e56fa48855
--- /dev/null
+++ b/ipc/glue/PFileDescriptorSet.ipdl
@@ -0,0 +1,23 @@
+/* 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;
+include protocol PContent;
+include protocol PSocketProcess;
+
+namespace mozilla {
+namespace ipc {
+
+protocol PFileDescriptorSet
+{
+ manager PBackground or PContent or PSocketProcess;
+
+both:
+ async AddFileDescriptor(FileDescriptor fd);
+
+ async __delete__();
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/PIdleScheduler.ipdl b/ipc/glue/PIdleScheduler.ipdl
new file mode 100644
index 0000000000..90e0350727
--- /dev/null
+++ b/ipc/glue/PIdleScheduler.ipdl
@@ -0,0 +1,61 @@
+/* -*- 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";
+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 refcounted 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();
+
+ // 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/PParentToChildStream.ipdl b/ipc/glue/PParentToChildStream.ipdl
new file mode 100644
index 0000000000..6040dc8a98
--- /dev/null
+++ b/ipc/glue/PParentToChildStream.ipdl
@@ -0,0 +1,47 @@
+/* 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;
+include protocol PContent;
+include protocol PSocketProcess;
+
+include "mozilla/layers/WebRenderMessageUtils.h";
+
+using mozilla::wr::ByteBuffer from "mozilla/webrender/WebRenderTypes.h";
+
+namespace mozilla {
+namespace ipc {
+
+// This is protocol is the opposite of PChildToParentStream. Please keep these
+// protocols in sync.
+protocol PParentToChildStream
+{
+ manager PBackground or PContent or PSocketProcess;
+
+child:
+ async Buffer(ByteBuffer aBuffer);
+ async Close(nsresult aRv);
+
+parent:
+ // The remote stream can be used in 2 ways: it can start receiving data
+ // immediately after the creation of the child actor, or it can wait until
+ // the child stream is actually used. This second configuration is enabled by
+ // passing 'true' to delayedStart in AutoIPCStream CTOR.
+ // If we are delaying the reading, at the first use of the remote stream, we
+ // must activate the sending of data. This happens by calling this method.
+ async StartReading();
+
+ // The parent side has hit an error condition and has requested the child
+ // actor issue a Close() message. The close must be initiated by the child
+ // to avoid racing with an in-flight Buffer() message.
+ async RequestClose(nsresult aRv);
+
+ // Stream is always destroyed from the parent side. This occurs if the
+ // parent encounters an error while writing to its pipe or if the child
+ // signals the stream should close by SendClose().
+ async __delete__();
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/ProcessChild.cpp b/ipc/glue/ProcessChild.cpp
new file mode 100644
index 0000000000..94ea41c66a
--- /dev/null
+++ b/ipc/glue/ProcessChild.cpp
@@ -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/. */
+
+#include "mozilla/ipc/ProcessChild.h"
+
+#include "nsDebug.h"
+
+#ifdef XP_WIN
+# include <stdlib.h> // for _exit()
+#else
+# include <unistd.h> // for _exit()
+#endif
+
+#include "mozilla/AppShutdown.h"
+#include "mozilla/ipc/IOThreadChild.h"
+
+namespace mozilla {
+namespace ipc {
+
+ProcessChild* ProcessChild::gProcessChild;
+
+static Atomic<bool> sExpectingShutdown(false);
+
+ProcessChild::ProcessChild(ProcessId aParentPid)
+ : ChildProcess(new IOThreadChild()),
+ mUILoop(MessageLoop::current()),
+ mParentPid(aParentPid) {
+ MOZ_ASSERT(mUILoop, "UILoop should be created by now");
+ MOZ_ASSERT(!gProcessChild, "should only be one ProcessChild");
+ gProcessChild = this;
+}
+
+ProcessChild::~ProcessChild() { gProcessChild = nullptr; }
+
+/* static */
+void ProcessChild::NotifyImpendingShutdown() { sExpectingShutdown = true; }
+
+/* static */
+bool ProcessChild::ExpectingShutdown() { return sExpectingShutdown; }
+
+/* static */
+void ProcessChild::QuickExit() { AppShutdown::DoImmediateExit(); }
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/ProcessChild.h b/ipc/glue/ProcessChild.h
new file mode 100644
index 0000000000..c2c49b565b
--- /dev/null
+++ b/ipc/glue/ProcessChild.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: */
+/* 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 "base/message_loop.h"
+#include "base/process.h"
+
+#include "chrome/common/child_process.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);
+ virtual ~ProcessChild();
+
+ virtual bool Init(int aArgc, char* aArgv[]) = 0;
+ virtual void CleanUp() {}
+
+ static MessageLoop* message_loop() { return gProcessChild->mUILoop; }
+
+ static void NotifyImpendingShutdown();
+
+ 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; }
+
+ private:
+ static ProcessChild* gProcessChild;
+
+ MessageLoop* mUILoop;
+ ProcessId mParentPid;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ProcessChild);
+};
+
+} // 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..6805249e11
--- /dev/null
+++ b/ipc/glue/ProcessUtils.h
@@ -0,0 +1,80 @@
+/* -*- 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 "FileDescriptor.h"
+#include "base/shared_memory.h"
+#include "mozilla/Maybe.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:
+ SharedPreferenceSerializer();
+ SharedPreferenceSerializer(SharedPreferenceSerializer&& aOther);
+ ~SharedPreferenceSerializer();
+
+ bool SerializeToSharedMemory();
+
+ 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(char* aPrefsHandleStr,
+ char* aPrefMapHandleStr, char* aPrefsLenStr,
+ char* aPrefMapSizeStr);
+
+ const base::SharedMemoryHandle& GetPrefsHandle() const;
+ const FileDescriptor& GetPrefMapHandle() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SharedPreferenceDeserializer);
+ Maybe<base::SharedMemoryHandle> mPrefsHandle;
+ 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
+
+} // 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..d07faba6f4
--- /dev/null
+++ b/ipc/glue/ProcessUtils_common.cpp
@@ -0,0 +1,210 @@
+/* -*- 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/ipc/GeckoChildProcessHost.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+namespace mozilla {
+namespace ipc {
+
+SharedPreferenceSerializer::SharedPreferenceSerializer() : mPrefMapSize(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() {
+ mPrefMapHandle =
+ Preferences::EnsureSnapshot(&mPrefMapSize).TakePlatformHandle();
+
+ // Serialize the early prefs.
+ nsAutoCStringN<1024> prefs;
+ Preferences::SerializePreferences(prefs);
+ 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 {
+ // Formats a pointer or pointer-sized-integer as a string suitable for passing
+ // in an arguments list.
+ auto formatPtrArg = [](auto arg) {
+ return nsPrintfCString("%zu", uintptr_t(arg));
+ };
+
+#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());
+ aExtraOpts.push_back("-prefsHandle");
+ aExtraOpts.push_back(formatPtrArg(GetPrefsHandle().get()).get());
+ aExtraOpts.push_back("-prefMapHandle");
+ aExtraOpts.push_back(formatPtrArg(GetPrefMapHandle().get()).get());
+#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.
+ aExtraOpts.push_back("-prefsLen");
+ aExtraOpts.push_back(formatPtrArg(GetPrefsLength()).get());
+ aExtraOpts.push_back("-prefMapSize");
+ aExtraOpts.push_back(formatPtrArg(GetPrefMapSize()).get());
+}
+
+#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(
+ char* aPrefsHandleStr, char* aPrefMapHandleStr, char* aPrefsLenStr,
+ char* aPrefMapSizeStr) {
+#ifdef XP_WIN
+ MOZ_ASSERT(aPrefsHandleStr && aPrefMapHandleStr, "Can't be null");
+#endif
+ MOZ_ASSERT(aPrefsLenStr && aPrefMapSizeStr, "Can't be null");
+
+ // Parses an arg containing a pointer-sized-integer.
+ auto parseUIntPtrArg = [](char*& aArg) {
+ // ContentParent uses %zu to print a word-sized unsigned integer. So
+ // even though strtoull() returns a long long int, it will fit in a
+ // uintptr_t.
+ return uintptr_t(strtoull(aArg, &aArg, 10));
+ };
+
+#ifdef XP_WIN
+ auto parseHandleArg = [&](char*& aArg) {
+ return HANDLE(parseUIntPtrArg(aArg));
+ };
+
+ mPrefsHandle = Some(parseHandleArg(aPrefsHandleStr));
+ if (!aPrefsHandleStr || aPrefsHandleStr[0] != '\0') {
+ return false;
+ }
+
+ FileDescriptor::UniquePlatformHandle handle(
+ parseHandleArg(aPrefMapHandleStr));
+ if (!aPrefMapHandleStr || aPrefMapHandleStr[0] != '\0') {
+ return false;
+ }
+
+ mPrefMapHandle.emplace(std::move(handle));
+#endif
+
+ mPrefsLen = Some(parseUIntPtrArg(aPrefsLenStr));
+ if (!aPrefsLenStr || aPrefsLenStr[0] != '\0') {
+ return false;
+ }
+
+ mPrefMapSize = Some(parseUIntPtrArg(aPrefMapSizeStr));
+ if (!aPrefMapSizeStr || aPrefMapSizeStr[0] != '\0') {
+ return false;
+ }
+
+#ifdef ANDROID
+ // Android is different; get the FD via gPrefsFd instead of a fixed fd.
+ MOZ_RELEASE_ASSERT(gPrefsFd != -1);
+ mPrefsHandle = Some(base::FileDescriptor(gPrefsFd, /* auto_close */ true));
+
+ mPrefMapHandle.emplace(UniqueFileHandle(gPrefMapFd));
+#elif XP_UNIX
+ mPrefsHandle = Some(base::FileDescriptor(kPrefsFileDescriptor,
+ /* auto_close */ true));
+
+ mPrefMapHandle.emplace(UniqueFileHandle(kPrefMapFileDescriptor));
+#endif
+
+ if (mPrefsHandle.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(*mPrefsHandle, /* 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 base::SharedMemoryHandle& SharedPreferenceDeserializer::GetPrefsHandle()
+ const {
+ MOZ_ASSERT(mPrefsHandle.isSome());
+
+ return mPrefsHandle.ref();
+}
+
+const FileDescriptor& SharedPreferenceDeserializer::GetPrefMapHandle() const {
+ MOZ_ASSERT(mPrefMapHandle.isSome());
+
+ return mPrefMapHandle.ref();
+}
+
+} // 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..0991738f9a
--- /dev/null
+++ b/ipc/glue/ProcessUtils_mac.mm
@@ -0,0 +1,19 @@
+/* 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 "mozilla/plugins/PluginUtilsOSX.h"
+
+namespace mozilla {
+namespace ipc {
+
+void SetThisProcessName(const char* aName) {
+ mozilla::plugins::PluginUtilsOSX::SetProcessName(aName);
+}
+
+} // 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..6b004dd4e5
--- /dev/null
+++ b/ipc/glue/ProtocolMessageUtils.h
@@ -0,0 +1,147 @@
+/* -*- 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 "mozilla/Assertions.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/ipc/Transport.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;
+
+template <>
+struct ParamTraits<mozilla::ipc::ActorHandle> {
+ typedef mozilla::ipc::ActorHandle paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ IPC::WriteParam(aMsg, aParam.mId);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ int id;
+ if (IPC::ReadParam(aMsg, aIter, &id)) {
+ aResult->mId = id;
+ return true;
+ }
+ return false;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ aLog->append(StringPrintf(L"(%d)", aParam.mId));
+ }
+};
+
+template <class PFooSide>
+struct ParamTraits<mozilla::ipc::Endpoint<PFooSide>> {
+ typedef mozilla::ipc::Endpoint<PFooSide> paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ IPC::WriteParam(aMsg, aParam.mValid);
+ if (!aParam.mValid) {
+ return;
+ }
+
+ IPC::WriteParam(aMsg, static_cast<uint32_t>(aParam.mMode));
+
+ // We duplicate the descriptor so that our own file descriptor remains
+ // valid after the write. An alternative would be to set
+ // aParam.mTransport.mValid to false, but that won't work because aParam
+ // is const.
+ mozilla::ipc::TransportDescriptor desc =
+ mozilla::ipc::DuplicateDescriptor(aParam.mTransport);
+ IPC::WriteParam(aMsg, desc);
+
+ IPC::WriteParam(aMsg, aParam.mMyPid);
+ IPC::WriteParam(aMsg, aParam.mOtherPid);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ MOZ_RELEASE_ASSERT(!aResult->mValid);
+
+ if (!IPC::ReadParam(aMsg, aIter, &aResult->mValid)) {
+ return false;
+ }
+ if (!aResult->mValid) {
+ // Object is empty, but read succeeded.
+ return true;
+ }
+
+ uint32_t mode;
+ if (!IPC::ReadParam(aMsg, aIter, &mode) ||
+ !IPC::ReadParam(aMsg, aIter, &aResult->mTransport) ||
+ !IPC::ReadParam(aMsg, aIter, &aResult->mMyPid) ||
+ !IPC::ReadParam(aMsg, aIter, &aResult->mOtherPid)) {
+ return false;
+ }
+ aResult->mMode = Channel::Mode(mode);
+ return true;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ aLog->append(StringPrintf(L"Endpoint"));
+ }
+};
+
+template <class PFooSide>
+struct ParamTraits<mozilla::ipc::ManagedEndpoint<PFooSide>> {
+ typedef mozilla::ipc::ManagedEndpoint<PFooSide> paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ IPC::WriteParam(aMsg, aParam.mId);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ MOZ_RELEASE_ASSERT(aResult->mId == 0);
+
+ if (!IPC::ReadParam(aMsg, aIter, &aResult->mId)) {
+ return false;
+ }
+ return true;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ aLog->append(StringPrintf(L"ManagedEndpoint"));
+ }
+};
+
+} // namespace IPC
+
+namespace mozilla::ipc {
+template <>
+struct IPDLParamTraits<FileDescriptor> {
+ typedef FileDescriptor paramType;
+
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam);
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ 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..1c85da2f3d
--- /dev/null
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -0,0 +1,909 @@
+/* -*- 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/Transport.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
+
+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());
+ return IPCResult(false);
+}
+
+#if defined(XP_WIN)
+bool DuplicateHandle(HANDLE aSourceHandle, DWORD aTargetProcessId,
+ HANDLE* aTargetHandle, DWORD aDesiredAccess,
+ DWORD aOptions) {
+ // If our process is the target just duplicate the handle.
+ if (aTargetProcessId == base::GetCurrentProcId()) {
+ return !!::DuplicateHandle(::GetCurrentProcess(), aSourceHandle,
+ ::GetCurrentProcess(), aTargetHandle,
+ aDesiredAccess, false, aOptions);
+ }
+
+# if defined(MOZ_SANDBOX)
+ // Try the broker next (will fail if not sandboxed).
+ if (SandboxTarget::Instance()->BrokerDuplicateHandle(
+ aSourceHandle, aTargetProcessId, aTargetHandle, aDesiredAccess,
+ aOptions)) {
+ return true;
+ }
+# endif
+
+ // Finally, see if we already have access to the process.
+ ScopedProcessHandle targetProcess(
+ OpenProcess(PROCESS_DUP_HANDLE, FALSE, aTargetProcessId));
+ if (!targetProcess) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCTransportFailureReason,
+ "Failed to open target process."_ns);
+ return false;
+ }
+
+ return !!::DuplicateHandle(::GetCurrentProcess(), aSourceHandle,
+ targetProcess, aTargetHandle, aDesiredAccess,
+ FALSE, aOptions);
+}
+#endif
+
+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 "][%d%s%d] [%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 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);
+}
+
+void TableToArray(const nsTHashtable<nsPtrHashKey<void>>& aTable,
+ nsTArray<void*>& aArray) {
+ uint32_t i = 0;
+ void** elements = aArray.AppendElements(aTable.Count());
+ for (auto iter = aTable.ConstIter(); !iter.Done(); iter.Next()) {
+ elements[i] = iter.Get()->GetKey();
+ ++i;
+ }
+}
+
+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();
+}
+
+ActorLifecycleProxy::~ActorLifecycleProxy() {
+ // 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;
+}
+
+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) {
+ // FIXME: It would be nice to have this print out the name of the
+ // misbehaving actor, to help people notice it's their fault!
+ NS_WARNING(
+ "Actor destructor called before IPC lifecycle complete!\n"
+ "References to this actor may unexpectedly dangle!");
+
+ 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, SharedMemory::SharedMemoryType aType, bool aUnsafe,
+ int32_t* aId) {
+ return mToplevel->CreateSharedMemory(aSize, aType, 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();
+}
+
+void IProtocol::SetEventTargetForActor(IProtocol* aActor,
+ nsISerialEventTarget* aEventTarget) {
+ // Make sure we have a manager for the internal method to access.
+ aActor->SetManager(this);
+ mToplevel->SetEventTargetForActorInternal(aActor, aEventTarget);
+}
+
+void IProtocol::ReplaceEventTargetForActor(IProtocol* aActor,
+ nsISerialEventTarget* aEventTarget) {
+ MOZ_ASSERT(aActor->Manager());
+ mToplevel->ReplaceEventTargetForActor(aActor, aEventTarget);
+}
+
+nsISerialEventTarget* IProtocol::GetActorEventTarget() {
+ // FIXME: It's a touch sketchy that we don't return a strong reference here.
+ RefPtr<nsISerialEventTarget> target = GetActorEventTarget(this);
+ return target;
+}
+already_AddRefed<nsISerialEventTarget> IProtocol::GetActorEventTarget(
+ IProtocol* aActor) {
+ return mToplevel->GetActorEventTarget(aActor);
+}
+
+ProcessId IProtocol::OtherPid() const { return mToplevel->OtherPid(); }
+
+void IProtocol::SetId(int32_t aId) {
+ MOZ_ASSERT(mId == aId || mLinkStatus == LinkStatus::Inactive);
+ mId = aId;
+}
+
+Maybe<IProtocol*> IProtocol::ReadActor(const IPC::Message* aMessage,
+ PickleIterator* aIter, bool aNullable,
+ const char* aActorDescription,
+ int32_t aProtocolTypeId) {
+ int32_t id;
+ if (!IPC::ReadParam(aMessage, aIter, &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) const {
+ HandleFatalError(aErrorMsg);
+}
+
+void IProtocol::HandleFatalError(const char* aErrorMsg) const {
+ if (IProtocol* manager = Manager()) {
+ manager->HandleFatalError(aErrorMsg);
+ return;
+ }
+
+ mozilla::ipc::FatalError(aErrorMsg, mSide == ParentSide);
+}
+
+bool IProtocol::AllocShmem(size_t aSize,
+ Shmem::SharedMemory::SharedMemoryType aType,
+ 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, aType, false, &id));
+ if (!rawmem) {
+ return false;
+ }
+
+ *aOutMem = Shmem(Shmem::PrivateIPDLCaller(), rawmem, id);
+ return true;
+}
+
+bool IProtocol::AllocUnsafeShmem(size_t aSize,
+ Shmem::SharedMemory::SharedMemoryType aType,
+ 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, aType, true, &id));
+ if (!rawmem) {
+ return false;
+ }
+
+ *aOutMem = Shmem(Shmem::PrivateIPDLCaller(), 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(Shmem::PrivateIPDLCaller());
+ 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(IPC::Message* aMsg) {
+ UniquePtr<IPC::Message> msg(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(msg));
+ return true;
+ }
+
+ NS_WARNING("IPC message discarded: actor cannot send");
+ return false;
+}
+
+bool IProtocol::ChannelSend(IPC::Message* aMsg, IPC::Message* aReply) {
+ UniquePtr<IPC::Message> msg(aMsg);
+ if (CanSend()) {
+ return GetIPCChannel()->Send(std::move(msg), aReply);
+ }
+
+ NS_WARNING("IPC message discarded: actor cannot send");
+ return false;
+}
+
+bool IProtocol::ChannelCall(IPC::Message* aMsg, IPC::Message* aReply) {
+ UniquePtr<IPC::Message> msg(aMsg);
+ if (CanSend()) {
+ return GetIPCChannel()->Call(std::move(msg), aReply);
+ }
+
+ NS_WARNING("IPC message discarded: actor cannot send");
+ return false;
+}
+
+void IProtocol::ActorConnected() {
+ if (mLinkStatus != LinkStatus::Inactive) {
+ return;
+ }
+
+ 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");
+
+ // 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(this);
+ ActorDestroy(aWhy);
+ mLinkStatus = LinkStatus::Destroyed;
+}
+
+IToplevelProtocol::IToplevelProtocol(const char* aName, ProtocolId aProtoId,
+ Side aSide)
+ : IProtocol(aProtoId, aSide),
+ mOtherPid(mozilla::ipc::kInvalidProcessId),
+ mLastLocalId(0),
+ mEventTargetMutex("ProtocolEventTargetMutex"),
+ mChannel(aName, this) {
+ mToplevel = this;
+}
+
+base::ProcessId IToplevelProtocol::OtherPid() const {
+ base::ProcessId pid = OtherPidMaybeInvalid();
+ MOZ_RELEASE_ASSERT(pid != kInvalidProcessId);
+ return pid;
+}
+
+void IToplevelProtocol::SetOtherProcessId(base::ProcessId aOtherPid) {
+ mOtherPid = aOtherPid;
+}
+
+bool IToplevelProtocol::Open(UniquePtr<Transport> aTransport,
+ base::ProcessId aOtherPid, MessageLoop* aThread,
+ mozilla::ipc::Side aSide) {
+ SetOtherProcessId(aOtherPid);
+ return GetIPCChannel()->Open(std::move(aTransport), aThread, aSide);
+}
+
+bool IToplevelProtocol::Open(MessageChannel* aChannel,
+ nsISerialEventTarget* aEventTarget,
+ mozilla::ipc::Side aSide) {
+ SetOtherProcessId(base::GetCurrentProcId());
+ return GetIPCChannel()->Open(aChannel, aEventTarget, aSide);
+}
+
+bool IToplevelProtocol::OpenOnSameThread(MessageChannel* aChannel, Side aSide) {
+ SetOtherProcessId(base::GetCurrentProcId());
+ return GetIPCChannel()->OpenOnSameThread(aChannel, aSide);
+}
+
+void IToplevelProtocol::NotifyImpendingShutdown() {
+ if (CanRecv()) {
+ GetIPCChannel()->NotifyImpendingShutdown();
+ }
+}
+
+void IToplevelProtocol::Close() { GetIPCChannel()->Close(); }
+
+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();
+ }
+ int32_t id = RegisterID(aRouted, NextId());
+
+ // Inherit our event target from our manager.
+ if (IProtocol* manager = aRouted->Manager()) {
+ MutexAutoLock lock(mEventTargetMutex);
+ if (nsCOMPtr<nsISerialEventTarget> target =
+ mEventTargetMap.Get(manager->Id())) {
+ MOZ_ASSERT(!mEventTargetMap.Contains(id),
+ "Don't insert with an existing ID");
+ mEventTargetMap.Put(id, target);
+ }
+ }
+
+ return id;
+}
+
+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.Put(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);
+
+ MutexAutoLock lock(mEventTargetMutex);
+ mEventTargetMap.Remove(aId);
+}
+
+Shmem::SharedMemory* IToplevelProtocol::CreateSharedMemory(
+ size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, bool aUnsafe,
+ Shmem::id_t* aId) {
+ RefPtr<Shmem::SharedMemory> segment(
+ Shmem::Alloc(Shmem::PrivateIPDLCaller(), aSize, aType, aUnsafe));
+ if (!segment) {
+ return nullptr;
+ }
+ int32_t id = NextId();
+ Shmem shmem(Shmem::PrivateIPDLCaller(), segment.get(), id);
+
+ base::ProcessId pid =
+#ifdef ANDROID
+ // We use OtherPidMaybeInvalid() because on Android this method is
+ // actually called on an unconnected protocol, but Android's shared memory
+ // implementation doesn't actually use the PID.
+ OtherPidMaybeInvalid();
+#else
+ OtherPid();
+#endif
+
+ UniquePtr<Message> descriptor =
+ shmem.ShareTo(Shmem::PrivateIPDLCaller(), pid, MSG_ROUTING_CONTROL);
+ if (!descriptor) {
+ return nullptr;
+ }
+ Unused << GetIPCChannel()->Send(std::move(descriptor));
+
+ *aId = shmem.Id(Shmem::PrivateIPDLCaller());
+ Shmem::SharedMemory* rawSegment = segment.get();
+ MOZ_ASSERT(!mShmemMap.Contains(*aId), "Don't insert with an existing ID");
+ mShmemMap.Put(*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& iter : mShmemMap) {
+ if (segment == iter.GetData()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool IToplevelProtocol::DestroySharedMemory(Shmem& shmem) {
+ Shmem::id_t aId = shmem.Id(Shmem::PrivateIPDLCaller());
+ Shmem::SharedMemory* segment = LookupSharedMemory(aId);
+ if (!segment) {
+ return false;
+ }
+
+ UniquePtr<Message> descriptor =
+ shmem.UnshareFrom(Shmem::PrivateIPDLCaller(), MSG_ROUTING_CONTROL);
+
+ MOZ_ASSERT(mShmemMap.Contains(aId),
+ "Attempting to remove an ID not in the shmem map");
+ mShmemMap.Remove(aId);
+ Shmem::Dealloc(Shmem::PrivateIPDLCaller(), segment);
+
+ MessageChannel* channel = GetIPCChannel();
+ if (!channel->CanSend()) {
+ return true;
+ }
+
+ return descriptor && channel->Send(std::move(descriptor));
+}
+
+void IToplevelProtocol::DeallocShmems() {
+ for (const auto& cit : mShmemMap) {
+ Shmem::Dealloc(Shmem::PrivateIPDLCaller(), cit.GetData());
+ }
+ mShmemMap.Clear();
+}
+
+bool IToplevelProtocol::ShmemCreated(const Message& aMsg) {
+ Shmem::id_t id;
+ RefPtr<Shmem::SharedMemory> rawmem(
+ Shmem::OpenExisting(Shmem::PrivateIPDLCaller(), aMsg, &id, true));
+ if (!rawmem) {
+ return false;
+ }
+ MOZ_ASSERT(!mShmemMap.Contains(id), "Don't insert with an existing ID");
+ mShmemMap.Put(id, rawmem.forget().take());
+ return true;
+}
+
+bool IToplevelProtocol::ShmemDestroyed(const Message& aMsg) {
+ Shmem::id_t id;
+ PickleIterator iter = PickleIterator(aMsg);
+ if (!IPC::ReadParam(&aMsg, &iter, &id)) {
+ return false;
+ }
+ aMsg.EndRead(iter);
+
+ 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(Shmem::PrivateIPDLCaller(), rawmem);
+ }
+ return true;
+}
+
+already_AddRefed<nsISerialEventTarget> IToplevelProtocol::GetMessageEventTarget(
+ const Message& aMsg) {
+ int32_t route = aMsg.routing_id();
+
+ Maybe<MutexAutoLock> lock;
+ lock.emplace(mEventTargetMutex);
+
+ nsCOMPtr<nsISerialEventTarget> target = mEventTargetMap.Get(route);
+
+ if (aMsg.is_constructor()) {
+ ActorHandle handle;
+ PickleIterator iter = PickleIterator(aMsg);
+ if (!IPC::ReadParam(&aMsg, &iter, &handle)) {
+ return nullptr;
+ }
+
+#ifdef DEBUG
+ // If this function is called more than once for the same message, the actor
+ // handle ID will already be in the map, but it should have the same target.
+ nsCOMPtr<nsISerialEventTarget> existingTgt =
+ mEventTargetMap.Get(handle.mId);
+ MOZ_ASSERT(existingTgt == target || existingTgt == nullptr);
+#endif /* DEBUG */
+
+ mEventTargetMap.Put(handle.mId, target);
+ }
+
+ return target.forget();
+}
+
+already_AddRefed<nsISerialEventTarget> IToplevelProtocol::GetActorEventTarget(
+ IProtocol* aActor) {
+ MOZ_RELEASE_ASSERT(aActor->Id() != kNullActorId &&
+ aActor->Id() != kFreedActorId);
+
+ MutexAutoLock lock(mEventTargetMutex);
+ nsCOMPtr<nsISerialEventTarget> target = mEventTargetMap.Get(aActor->Id());
+ return target.forget();
+}
+
+nsISerialEventTarget* IToplevelProtocol::GetActorEventTarget() {
+ // The EventTarget of a ToplevelProtocol shall never be set.
+ return nullptr;
+}
+
+void IToplevelProtocol::SetEventTargetForActorInternal(
+ IProtocol* aActor, nsISerialEventTarget* aEventTarget) {
+ // The EventTarget of a ToplevelProtocol shall never be set.
+ MOZ_RELEASE_ASSERT(aActor != this);
+
+ // We should only call this function on actors that haven't been used for IPC
+ // code yet. Otherwise we'll be posting stuff to the wrong event target before
+ // we're called.
+ MOZ_RELEASE_ASSERT(aActor->Id() == kNullActorId ||
+ aActor->Id() == kFreedActorId);
+
+ MOZ_ASSERT(aActor->Manager() && aActor->ToplevelProtocol() == this);
+
+ // Register the actor early. When it's registered again, it will keep the same
+ // ID.
+ int32_t id = Register(aActor);
+ aActor->SetId(id);
+
+ MutexAutoLock lock(mEventTargetMutex);
+ // FIXME bug 1445121 - sometimes the id is already mapped.
+ mEventTargetMap.Put(id, aEventTarget);
+}
+
+void IToplevelProtocol::ReplaceEventTargetForActor(
+ IProtocol* aActor, nsISerialEventTarget* aEventTarget) {
+ // The EventTarget of a ToplevelProtocol shall never be set.
+ MOZ_RELEASE_ASSERT(aActor != this);
+
+ int32_t id = aActor->Id();
+ // The ID of the actor should have existed.
+ MOZ_RELEASE_ASSERT(id != kNullActorId && id != kFreedActorId);
+
+ MutexAutoLock lock(mEventTargetMutex);
+ MOZ_ASSERT(mEventTargetMap.Contains(id), "Only replace an existing ID");
+ mEventTargetMap.Put(id, aEventTarget);
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/ProtocolUtils.h b/ipc/glue/ProtocolUtils.h
new file mode 100644
index 0000000000..4dc14b0a70
--- /dev/null
+++ b/ipc/glue/ProtocolUtils.h
@@ -0,0 +1,728 @@
+/* -*- 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 "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.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 "nsDataHashtable.h"
+#include "nsDebug.h"
+#include "nsISupports.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsTHashtable.h"
+
+// XXX Things that could be replaced by a forward header
+#include "mozilla/ipc/Transport.h" // for Transport
+
+// 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 {
+ 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;
+class nsUint32HashKey;
+
+namespace mozilla {
+class SchedulerGroup;
+
+namespace dom {
+class ContentParent;
+} // namespace dom
+
+namespace net {
+class NeckoParent;
+} // namespace net
+
+namespace ipc {
+
+#ifdef FUZZING
+class ProtocolFuzzerHelper;
+#endif
+
+#ifdef XP_WIN
+const base::ProcessHandle kInvalidProcessHandle = INVALID_HANDLE_VALUE;
+
+// In theory, on Windows, this is a valid process ID, but in practice they are
+// currently divisible by four. Process IDs share the kernel handle allocation
+// code and they are guaranteed to be divisible by four.
+// As this could change for process IDs we shouldn't generally rely on this
+// property, however even if that were to change, it seems safe to rely on this
+// particular value never being used.
+const base::ProcessId kInvalidProcessId = kuint32max;
+#else
+const base::ProcessHandle kInvalidProcessHandle = -1;
+const base::ProcessId kInvalidProcessId = -1;
+#endif
+
+// Scoped base::ProcessHandle to ensure base::CloseProcessHandle is called.
+struct ScopedProcessHandleTraits {
+ typedef base::ProcessHandle type;
+
+ static type empty() { return kInvalidProcessHandle; }
+
+ static void release(type aProcessHandle) {
+ if (aProcessHandle && aProcessHandle != 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 IProtocol : public HasResultCodes {
+ public:
+ enum ActorDestroyReason {
+ FailedConstructor,
+ Deletion,
+ AncestorDeletion,
+ NormalShutdown,
+ AbnormalShutdown
+ };
+
+ typedef base::ProcessId ProcessId;
+ typedef IPC::Message Message;
+ typedef IPC::MessageInfo MessageInfo;
+
+ IProtocol(ProtocolId aProtoId, Side aSide)
+ : mId(0),
+ mProtocolId(aProtoId),
+ mSide(aSide),
+ mLinkStatus(LinkStatus::Inactive),
+ mLifecycleProxy(nullptr),
+ mManager(nullptr),
+ mToplevel(nullptr) {}
+
+ IToplevelProtocol* ToplevelProtocol() { 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,
+ SharedMemory::SharedMemoryType aType,
+ 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;
+
+ // Sets an event target to which all messages for aActor will be
+ // dispatched. This method must be called before right before the SendPFoo
+ // message for aActor is sent. And SendPFoo *must* be called if
+ // SetEventTargetForActor is called. The receiver when calling
+ // SetEventTargetForActor must be the actor that will be the manager for
+ // aActor.
+ void SetEventTargetForActor(IProtocol* aActor,
+ nsISerialEventTarget* aEventTarget);
+
+ // Replace the event target for the messages of aActor. There must not be
+ // any messages of aActor in the task queue, or we might run into some
+ // unexpected behavior.
+ void ReplaceEventTargetForActor(IProtocol* aActor,
+ nsISerialEventTarget* aEventTarget);
+
+ nsISerialEventTarget* GetActorEventTarget();
+ already_AddRefed<nsISerialEventTarget> GetActorEventTarget(IProtocol* aActor);
+
+ ProcessId OtherPid() const;
+
+ // 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; }
+
+ 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(const IPC::Message* aMessage,
+ PickleIterator* aIter, bool aNullable,
+ const char* aActorDescription,
+ int32_t aProtocolTypeId);
+
+ virtual Result OnMessageReceived(const Message& aMessage) = 0;
+ virtual Result OnMessageReceived(const Message& aMessage,
+ Message*& aReply) = 0;
+ virtual Result OnCallReceived(const Message& aMessage, Message*& aReply) = 0;
+ bool AllocShmem(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType,
+ Shmem* aOutMem);
+ bool AllocUnsafeShmem(size_t aSize,
+ Shmem::SharedMemory::SharedMemoryType aType,
+ Shmem* aOutMem);
+ bool DeallocShmem(Shmem& aMem);
+
+ void FatalError(const char* const aErrorMsg) const;
+ virtual void HandleFatalError(const char* aErrorMsg) const;
+
+ protected:
+ virtual ~IProtocol();
+
+ friend class IToplevelProtocol;
+ friend class ActorLifecycleProxy;
+
+ 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(IPC::Message* aMsg);
+ bool ChannelSend(IPC::Message* aMsg, IPC::Message* aReply);
+ bool ChannelCall(IPC::Message* aMsg, IPC::Message* aReply);
+ template <typename Value>
+ void ChannelSend(IPC::Message* aMsg, ResolveCallback<Value>&& aResolve,
+ RejectCallback&& aReject) {
+ UniquePtr<IPC::Message> msg(aMsg);
+ if (CanSend()) {
+ GetIPCChannel()->Send(std::move(msg), this, std::move(aResolve),
+ std::move(aReject));
+ } else {
+ NS_WARNING("IPC message discarded: actor cannot send");
+ 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:
+ 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;
+};
+
+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 {
+#ifdef FUZZING
+ friend class mozilla::ipc::ProtocolFuzzerHelper;
+#endif
+
+ template <class PFooSide>
+ friend class Endpoint;
+
+ protected:
+ explicit IToplevelProtocol(const char* aName, ProtocolId aProtoId,
+ Side aSide);
+ ~IToplevelProtocol() = default;
+
+ public:
+ // 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,
+ SharedMemory::SharedMemoryType aType,
+ 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; }
+
+ // NOTE: The target actor's Manager must already be set.
+ void SetEventTargetForActorInternal(IProtocol* aActor,
+ nsISerialEventTarget* aEventTarget);
+ void ReplaceEventTargetForActor(IProtocol* aActor,
+ nsISerialEventTarget* aEventTarget);
+ nsISerialEventTarget* GetActorEventTarget();
+ already_AddRefed<nsISerialEventTarget> GetActorEventTarget(IProtocol* aActor);
+
+ ProcessId OtherPid() const;
+ void SetOtherProcessId(base::ProcessId aOtherPid);
+
+ virtual void OnChannelClose() = 0;
+ virtual void OnChannelError() = 0;
+ virtual void ProcessingError(Result aError, const char* aMsgName) {}
+ virtual void OnChannelConnected(int32_t peer_pid) {}
+
+ bool Open(UniquePtr<Transport> aTransport, base::ProcessId aOtherPid,
+ MessageLoop* aThread = nullptr,
+ mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide);
+
+ bool Open(MessageChannel* aChannel, 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(MessageChannel* aChannel,
+ 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 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
+
+ virtual void EnteredCxxStack() {}
+ virtual void ExitedCxxStack() {}
+ virtual void EnteredCall() {}
+ virtual void ExitedCall() {}
+
+ bool IsOnCxxStack() const;
+
+ virtual RacyInterruptPolicy MediateInterruptRace(const MessageInfo& parent,
+ const MessageInfo& child) {
+ return RIPChildWins;
+ }
+
+ /**
+ * Return true if windows messages can be handled while waiting for a reply
+ * to a sync IPDL message.
+ */
+ virtual bool HandleWindowsMessages(const Message& aMsg) const { return true; }
+
+ virtual void OnEnteredSyncSend() {}
+ virtual void OnExitedSyncSend() {}
+
+ virtual void ProcessRemoteNativeEventsInInterruptCall() {}
+
+ virtual void OnChannelReceivedMessage(const Message& aMsg) {}
+
+ void OnIPCChannelOpened() { ActorConnected(); }
+
+ already_AddRefed<nsISerialEventTarget> GetMessageEventTarget(
+ const Message& aMsg);
+
+ base::ProcessId OtherPidMaybeInvalid() const { return mOtherPid; }
+
+ private:
+ int32_t NextId();
+
+ template <class T>
+ using IDMap = nsDataHashtable<nsUint32HashKey, T>;
+
+ base::ProcessId mOtherPid;
+
+ // NOTE NOTE NOTE
+ // Used to be on mState
+ int32_t mLastLocalId;
+ IDMap<IProtocol*> mActorMap;
+ IDMap<Shmem::SharedMemory*> mShmemMap;
+
+ // XXX: We no longer need mEventTargetMap for Quantum DOM, so it may be
+ // worthwhile to remove it before people start depending on it for other weird
+ // things.
+ Mutex mEventTargetMutex;
+ IDMap<nsCOMPtr<nsISerialEventTarget>> mEventTargetMap;
+
+ MessageChannel mChannel;
+};
+
+class IShmemAllocator {
+ public:
+ virtual bool AllocShmem(size_t aSize,
+ mozilla::ipc::SharedMemory::SharedMemoryType aShmType,
+ mozilla::ipc::Shmem* aShmem) = 0;
+ virtual bool AllocUnsafeShmem(
+ size_t aSize, mozilla::ipc::SharedMemory::SharedMemoryType aShmType,
+ 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::SharedMemory::SharedMemoryType aShmType, \
+ mozilla::ipc::Shmem* aShmem) override { \
+ return aImplClass::AllocShmem(aSize, aShmType, aShmem); \
+ } \
+ virtual bool AllocUnsafeShmem( \
+ size_t aSize, mozilla::ipc::SharedMemory::SharedMemoryType aShmType, \
+ mozilla::ipc::Shmem* aShmem) override { \
+ return aImplClass::AllocUnsafeShmem(aSize, aShmType, 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);
+
+// 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);
+
+#if defined(XP_WIN)
+// This is a restricted version of Windows' DuplicateHandle() function
+// that works inside the sandbox and can send handles but not retrieve
+// them. Unlike DuplicateHandle(), it takes a process ID rather than
+// a process handle. It returns true on success, false otherwise.
+bool DuplicateHandle(HANDLE aSourceHandle, DWORD aTargetProcessId,
+ HANDLE* aTargetHandle, DWORD aDesiredAccess,
+ DWORD aOptions);
+#endif
+
+/**
+ * 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; }
+
+ 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;
+};
+
+void TableToArray(const nsTHashtable<nsPtrHashKey<void>>& aTable,
+ nsTArray<void*>& aArray);
+
+} // namespace ipc
+
+template <typename Protocol>
+class ManagedContainer : public nsTHashtable<nsPtrHashKey<Protocol>> {
+ typedef nsTHashtable<nsPtrHashKey<Protocol>> BaseClass;
+
+ public:
+ // Having the core logic work on void pointers, rather than typed pointers,
+ // means that we can have one instance of this code out-of-line, rather
+ // than several hundred instances of this code out-of-lined. (Those
+ // repeated instances don't necessarily get folded together by the linker
+ // because they contain member offsets and such that differ between the
+ // functions.) We do have to pay for it with some eye-bleedingly bad casts,
+ // though.
+ void ToArray(nsTArray<Protocol*>& aArray) const {
+ ::mozilla::ipc::TableToArray(
+ *reinterpret_cast<const nsTHashtable<nsPtrHashKey<void>>*>(
+ static_cast<const BaseClass*>(this)),
+ reinterpret_cast<nsTArray<void*>&>(aArray));
+ }
+};
+
+template <typename Protocol>
+Protocol* LoneManagedOrNullAsserts(
+ const ManagedContainer<Protocol>& aManagees) {
+ if (aManagees.IsEmpty()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(aManagees.Count() == 1);
+ return aManagees.ConstIter().Get()->GetKey();
+}
+
+template <typename Protocol>
+Protocol* SingleManagedOrNull(const ManagedContainer<Protocol>& aManagees) {
+ if (aManagees.Count() != 1) {
+ return nullptr;
+ }
+ return aManagees.ConstIter().Get()->GetKey();
+}
+
+} // namespace mozilla
+
+#endif // mozilla_ipc_ProtocolUtils_h
diff --git a/ipc/glue/ProtocolUtilsFwd.h b/ipc/glue/ProtocolUtilsFwd.h
new file mode 100644
index 0000000000..b8143e273d
--- /dev/null
+++ b/ipc/glue/ProtocolUtilsFwd.h
@@ -0,0 +1,16 @@
+/* -*- 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_ProtocolUtilsFwd_h
+#define mozilla_ipc_ProtocolUtilsFwd_h 1
+
+namespace mozilla::ipc {
+
+class IProtocol;
+
+} // namespace mozilla::ipc
+
+#endif // mozilla_ipc_ProtocolUtilsFwd_h
diff --git a/ipc/glue/ScopedXREEmbed.cpp b/ipc/glue/ScopedXREEmbed.cpp
new file mode 100644
index 0000000000..c83bf7e175
--- /dev/null
+++ b/ipc/glue/ScopedXREEmbed.cpp
@@ -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/. */
+
+#include "ScopedXREEmbed.h"
+
+#include "base/command_line.h"
+#include "base/string_util.h"
+
+#include "nsIFile.h"
+
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsXULAppAPI.h"
+
+using mozilla::ipc::ScopedXREEmbed;
+
+ScopedXREEmbed::ScopedXREEmbed() : mShouldKillEmbedding(false) { NS_LogInit(); }
+
+ScopedXREEmbed::~ScopedXREEmbed() {
+ Stop();
+ NS_LogTerm();
+}
+
+void ScopedXREEmbed::SetAppDir(const nsACString& aPath) {
+ bool flag;
+ nsresult rv =
+ XRE_GetFileFromPath(aPath.BeginReading(), getter_AddRefs(mAppDir));
+ if (NS_FAILED(rv) || NS_FAILED(mAppDir->Exists(&flag)) || !flag) {
+ NS_WARNING("Invalid application directory passed to content process.");
+ mAppDir = nullptr;
+ }
+}
+
+void ScopedXREEmbed::Start() {
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = XRE_GetBinaryPath(getter_AddRefs(localFile));
+ if (NS_FAILED(rv)) return;
+
+ nsCOMPtr<nsIFile> parent;
+ rv = localFile->GetParent(getter_AddRefs(parent));
+ if (NS_FAILED(rv)) return;
+
+ localFile = parent;
+ NS_ENSURE_TRUE_VOID(localFile);
+
+#ifdef OS_MACOSX
+ if (XRE_IsContentProcess()) {
+ // We're an XPCOM-using subprocess. Walk out of
+ // [subprocess].app/Contents/MacOS to the real GRE dir.
+ rv = localFile->GetParent(getter_AddRefs(parent));
+ if (NS_FAILED(rv)) return;
+
+ localFile = parent;
+ NS_ENSURE_TRUE_VOID(localFile);
+
+ rv = localFile->GetParent(getter_AddRefs(parent));
+ if (NS_FAILED(rv)) return;
+
+ localFile = parent;
+ NS_ENSURE_TRUE_VOID(localFile);
+
+ rv = localFile->GetParent(getter_AddRefs(parent));
+ if (NS_FAILED(rv)) return;
+
+ localFile = parent;
+ NS_ENSURE_TRUE_VOID(localFile);
+
+ rv = localFile->SetNativeLeafName("Resources"_ns);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+#endif
+
+ if (mAppDir)
+ rv = XRE_InitEmbedding2(localFile, mAppDir, nullptr);
+ else
+ rv = XRE_InitEmbedding2(localFile, localFile, nullptr);
+ if (NS_FAILED(rv)) return;
+
+ mShouldKillEmbedding = true;
+}
+
+void ScopedXREEmbed::Stop() {
+ if (mShouldKillEmbedding) {
+ XRE_TermEmbedding();
+ mShouldKillEmbedding = false;
+ }
+}
diff --git a/ipc/glue/ScopedXREEmbed.h b/ipc/glue/ScopedXREEmbed.h
new file mode 100644
index 0000000000..3e495d8850
--- /dev/null
+++ b/ipc/glue/ScopedXREEmbed.h
@@ -0,0 +1,34 @@
+/* -*- 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_SCOPEDXREEMBED_H__
+#define __IPC_GLUE_SCOPEDXREEMBED_H__
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIFile.h"
+
+namespace mozilla {
+namespace ipc {
+
+class ScopedXREEmbed {
+ public:
+ ScopedXREEmbed();
+ ~ScopedXREEmbed();
+
+ void Start();
+ void Stop();
+ void SetAppDir(const nsACString& aPath);
+
+ private:
+ bool mShouldKillEmbedding;
+ nsCOMPtr<nsIFile> mAppDir;
+};
+
+} /* namespace ipc */
+} /* namespace mozilla */
+
+#endif /* __IPC_GLUE_SCOPEDXREEMBED_H__ */
diff --git a/ipc/glue/SerializedStructuredCloneBuffer.h b/ipc/glue/SerializedStructuredCloneBuffer.h
new file mode 100644
index 0000000000..c81a1525b3
--- /dev/null
+++ b/ipc/glue/SerializedStructuredCloneBuffer.h
@@ -0,0 +1,130 @@
+/* -*- 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(Message* aMsg, const paramType& aParam) {
+ MOZ_ASSERT(!(aParam.Size() % sizeof(uint64_t)));
+ WriteParam(aMsg, aParam.Size());
+ aParam.ForEachDataChunk([&](const char* aData, size_t aSize) {
+ return aMsg->WriteBytes(aData, aSize, sizeof(uint64_t));
+ });
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ size_t length = 0;
+ if (!ReadParam(aMsg, aIter, &length)) {
+ return false;
+ }
+ MOZ_ASSERT(!(length % sizeof(uint64_t)));
+
+ mozilla::BufferList<InfallibleAllocPolicy> buffers(0, 0, 4096);
+
+ // 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. One
+ // case is PContentChild::SendGetXPCOMProcessAttributes. We can't
+ // return a borrowed buffer because the out param outlives the
+ // IPDL callback.
+ if (length &&
+ !aMsg->ExtractBuffers(aIter, length, &buffers, sizeof(uint64_t))) {
+ return false;
+ }
+
+ bool success;
+ mozilla::BufferList<js::SystemAllocPolicy> out =
+ buffers.MoveFallible<js::SystemAllocPolicy>(&success);
+ if (!success) {
+ return false;
+ }
+
+ *aResult = JSStructuredCloneData(
+ std::move(out), JS::StructuredCloneScope::DifferentProcess);
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::SerializedStructuredCloneBuffer> {
+ typedef mozilla::SerializedStructuredCloneBuffer paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.data);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->data);
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ LogParam(aParam.data.Size(), aLog);
+ }
+};
+
+} // 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..c7c230389e
--- /dev/null
+++ b/ipc/glue/SharedMemory.h
@@ -0,0 +1,146 @@
+/* -*- 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 SharedMemoryType { TYPE_BASIC, TYPE_UNKNOWN };
+
+ 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 CloseHandle() = 0;
+
+ virtual SharedMemoryType Type() const = 0;
+
+ virtual bool ShareHandle(base::ProcessId aProcessId,
+ IPC::Message* aMessage) = 0;
+ virtual bool ReadHandle(const IPC::Message* aMessage,
+ PickleIterator* aIter) = 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 bool ShareToProcess(base::ProcessId aProcessId, Handle* aHandle) = 0;
+ virtual bool IsHandleValid(const Handle& aHandle) const = 0;
+ virtual bool SetHandle(const Handle& aHandle, OpenRights aRights) = 0;
+
+ virtual bool ShareHandle(base::ProcessId aProcessId,
+ IPC::Message* aMessage) override {
+ Handle handle;
+ if (!ShareToProcess(aProcessId, &handle)) {
+ return false;
+ }
+ IPC::WriteParam(aMessage, handle);
+ return true;
+ }
+
+ virtual bool ReadHandle(const IPC::Message* aMessage,
+ PickleIterator* aIter) override {
+ Handle handle;
+ return IPC::ReadParam(aMessage, aIter, &handle) && IsHandleValid(handle) &&
+ SetHandle(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..30eb169e99
--- /dev/null
+++ b/ipc/glue/SharedMemoryBasic_android.cpp
@@ -0,0 +1,135 @@
+/* -*- 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(const Handle& aHandle, OpenRights aRights) {
+ MOZ_ASSERT(-1 == mShmFd, "Already Create()d");
+ mShmFd = aHandle.fd;
+ 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;
+}
+
+bool SharedMemoryBasic::ShareToProcess(base::ProcessId /*unused*/,
+ Handle* aNewHandle) {
+ MOZ_ASSERT(mShmFd >= 0, "Should have been Create()d by now");
+
+ int shmfdDup = dup(mShmFd);
+ if (-1 == shmfdDup) {
+ LogError("ShmemAndroid::ShareToProcess()");
+ return false;
+ }
+
+ aNewHandle->fd = shmfdDup;
+ aNewHandle->auto_close = true;
+ return true;
+}
+
+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..6e6eddeac1
--- /dev/null
+++ b/ipc/glue/SharedMemoryBasic_android.h
@@ -0,0 +1,76 @@
+/* -*- 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 "base/file_descriptor_posix.h"
+
+#include "SharedMemory.h"
+
+#ifdef FUZZING
+# include "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<base::FileDescriptor> {
+ public:
+ SharedMemoryBasic();
+
+ virtual bool SetHandle(const 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 CloseHandle() override;
+
+ virtual void* memory() const override {
+#ifdef FUZZING
+ return SharedMemoryFuzzer::MutateSharedMemory(mMemory, mAllocSize);
+#else
+ return mMemory;
+#endif
+ }
+
+ virtual SharedMemoryType Type() const override { return TYPE_BASIC; }
+
+ static Handle NULLHandle() { return Handle(); }
+
+ static void* FindFreeAddressSpace(size_t aSize);
+
+ virtual bool IsHandleValid(const Handle& aHandle) const override {
+ return aHandle.fd >= 0;
+ }
+
+ virtual bool ShareToProcess(base::ProcessId aProcessId,
+ Handle* aNewHandle) override;
+
+ private:
+ ~SharedMemoryBasic();
+
+ void Unmap();
+
+ // 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..09ec1b60e0
--- /dev/null
+++ b/ipc/glue/SharedMemoryBasic_chromium.h
@@ -0,0 +1,92 @@
+/* -*- 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 "SharedMemory.h"
+
+#ifdef FUZZING
+# include "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(const Handle& aHandle, OpenRights aRights) override {
+ return mSharedMemory.SetHandle(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 CloseHandle() override { mSharedMemory.Close(false); }
+
+ virtual void* memory() const override {
+#ifdef FUZZING
+ return SharedMemoryFuzzer::MutateSharedMemory(mSharedMemory.memory(),
+ mAllocSize);
+#else
+ return mSharedMemory.memory();
+#endif
+ }
+
+ virtual SharedMemoryType Type() const override { return TYPE_BASIC; }
+
+ static Handle NULLHandle() { return base::SharedMemory::NULLHandle(); }
+
+ virtual bool IsHandleValid(const Handle& aHandle) const override {
+ return base::SharedMemory::IsHandleValid(aHandle);
+ }
+
+ virtual bool ShareToProcess(base::ProcessId aProcessId,
+ Handle* new_handle) override {
+ base::SharedMemoryHandle handle;
+ bool ret = mSharedMemory.ShareToProcess(aProcessId, &handle);
+ if (ret) *new_handle = handle;
+ return ret;
+ }
+
+ 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..fd2885b8d2
--- /dev/null
+++ b/ipc/glue/SharedMemoryBasic_mach.h
@@ -0,0 +1,111 @@
+/* -*- 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/file_descriptor_posix.h"
+#include "base/process.h"
+
+#include "SharedMemory.h"
+#include <mach/port.h>
+#include "chrome/common/mach_ipc_mac.h"
+
+#ifdef FUZZING
+# include "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 {
+
+enum {
+ kGetPortsMsg = 1,
+ kSharePortsMsg,
+ kWaitForTexturesMsg,
+ kUpdateTextureLocksMsg,
+ kReturnIdMsg,
+ kReturnWaitForTexturesMsg,
+ kReturnPortsMsg,
+ kShutdownMsg,
+ kCleanupMsg,
+};
+
+struct MemoryPorts {
+ MachPortSender* mSender;
+ ReceivePort* mReceiver;
+
+ MemoryPorts() = default;
+ MemoryPorts(MachPortSender* sender, ReceivePort* receiver)
+ : mSender(sender), mReceiver(receiver) {}
+};
+
+class SharedMemoryBasic final : public SharedMemoryCommon<mach_port_t> {
+ public:
+ static void SetupMachMemory(pid_t pid, ReceivePort* listen_port,
+ MachPortSender* listen_port_ack,
+ MachPortSender* send_port,
+ ReceivePort* send_port_ack, bool pidIsParent);
+
+ static void CleanupForPid(pid_t pid);
+ static void CleanupForPidWithLock(pid_t pid);
+
+ static void Shutdown();
+
+ static bool SendMachMessage(pid_t pid, MachSendMessage& message,
+ MachReceiveMessage* response);
+
+ SharedMemoryBasic();
+
+ virtual bool SetHandle(const 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 CloseHandle() override;
+
+ virtual void* memory() const override {
+#ifdef FUZZING
+ return SharedMemoryFuzzer::MutateSharedMemory(mMemory, mAllocSize);
+#else
+ return mMemory;
+#endif
+ }
+
+ virtual SharedMemoryType Type() const override { return TYPE_BASIC; }
+
+ static Handle NULLHandle() { return Handle(); }
+
+ static void* FindFreeAddressSpace(size_t aSize);
+
+ virtual bool IsHandleValid(const Handle& aHandle) const override;
+
+ virtual bool ShareToProcess(base::ProcessId aProcessId,
+ Handle* aNewHandle) override;
+
+ private:
+ ~SharedMemoryBasic();
+
+ void Unmap();
+ mach_port_t 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..306f2504e0
--- /dev/null
+++ b/ipc/glue/SharedMemoryBasic_mach.mm
@@ -0,0 +1,676 @@
+/* -*- 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"
+#include "mozilla/layers/TextureSync.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
+
+#define CHECK_MACH_ERROR(kr, msg) \
+ PR_BEGIN_MACRO \
+ if (kr != KERN_SUCCESS) { \
+ LOG_ERROR("%s %s (%x)\n", msg, mach_error_string(kr), kr); \
+ return false; \
+ } \
+ PR_END_MACRO
+
+/*
+ * This code is responsible for sharing memory between processes. Memory can be
+ * shared between parent and child or between two children. Each memory region is
+ * referenced via a Mach port. Mach ports are also used for messaging when
+ * sharing a memory region.
+ *
+ * When the parent starts a child, it starts a thread whose only purpose is to
+ * communicate with the child about shared memory. Once the child has started,
+ * it starts a similar thread for communicating with the parent. Each side can
+ * communicate with the thread on the other side via Mach ports. When either
+ * side wants to share memory with the other, it sends a Mach message to the
+ * other side. Attached to the message is the port that references the shared
+ * memory region. When the other side receives the message, it automatically
+ * gets access to the region. It sends a reply (also via a Mach port) so that
+ * the originating side can continue.
+ *
+ * The two sides communicate using four ports. Two ports are used when the
+ * parent shares memory with the child. The other two are used when the child
+ * shares memory with the parent. One of these two ports is used for sending the
+ * "share" message and the other is used for the reply.
+ *
+ * If a child wants to share memory with another child, it sends a "GetPorts"
+ * message to the parent. The parent forwards this GetPorts message to the
+ * target child. The message includes some ports so that the children can talk
+ * directly. Both children start up a thread to communicate with the other child,
+ * similar to the way parent and child communicate. In the future, when these
+ * two children want to communicate, they re-use the channels that were created.
+ *
+ * When a child shuts down, the parent notifies all other children. Those
+ * children then have the opportunity to shut down any threads they might have
+ * been using to communicate directly with that child.
+ */
+
+namespace mozilla {
+namespace ipc {
+
+// Protects gMemoryCommPorts and gThreads.
+static StaticMutex gMutex;
+static std::map<pid_t, MemoryPorts> gMemoryCommPorts;
+
+const int kTimeout = 1000;
+const int kLongTimeout = 60 * kTimeout;
+
+pid_t gParentPid = 0;
+
+struct PIDPair {
+ pid_t mRequester;
+ pid_t mRequested;
+
+ PIDPair(pid_t requester, pid_t requested) : mRequester(requester), mRequested(requested) {}
+};
+
+struct ListeningThread {
+ pthread_t mThread;
+ MemoryPorts* mPorts;
+
+ ListeningThread() = default;
+ ListeningThread(pthread_t thread, MemoryPorts* ports) : mThread(thread), mPorts(ports) {}
+};
+
+struct SharePortsReply {
+ uint64_t serial;
+ mach_port_t port;
+};
+
+std::map<pid_t, ListeningThread> gThreads;
+
+static void* PortServerThread(void* argument);
+
+static void SetupMachMemory(pid_t pid, ReceivePort* listen_port, MachPortSender* listen_port_ack,
+ MachPortSender* send_port, ReceivePort* send_port_ack,
+ bool pidIsParent) {
+ if (pidIsParent) {
+ gParentPid = pid;
+ }
+ auto* listen_ports = new MemoryPorts(listen_port_ack, listen_port);
+ pthread_t thread;
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+ int err = pthread_create(&thread, &attr, PortServerThread, listen_ports);
+ if (err) {
+ LOG_ERROR("pthread_create failed with %x\n", err);
+ return;
+ }
+
+ gMutex.AssertCurrentThreadOwns();
+ gThreads[pid] = ListeningThread(thread, listen_ports);
+ gMemoryCommPorts[pid] = MemoryPorts(send_port, send_port_ack);
+}
+
+// Send two communication ports to another process along with the pid of the process that is
+// listening on them.
+bool SendPortsMessage(MachPortSender* sender, mach_port_t ports_in_receiver,
+ mach_port_t ports_out_receiver, PIDPair pid_pair) {
+ MachSendMessage getPortsMsg(kGetPortsMsg);
+ if (!getPortsMsg.AddDescriptor(MachMsgPortDescriptor(ports_in_receiver))) {
+ LOG_ERROR("Adding descriptor to message failed");
+ return false;
+ }
+ if (!getPortsMsg.AddDescriptor(MachMsgPortDescriptor(ports_out_receiver))) {
+ LOG_ERROR("Adding descriptor to message failed");
+ return false;
+ }
+
+ getPortsMsg.SetData(&pid_pair, sizeof(PIDPair));
+ kern_return_t err = sender->SendMessage(getPortsMsg, kTimeout);
+ if (KERN_SUCCESS != err) {
+ LOG_ERROR("Error sending get ports message %s (%x)\n", mach_error_string(err), err);
+ return false;
+ }
+ return true;
+}
+
+// Receive two communication ports from another process
+bool RecvPortsMessage(ReceivePort* receiver, mach_port_t* ports_in_sender,
+ mach_port_t* ports_out_sender) {
+ MachReceiveMessage rcvPortsMsg;
+ kern_return_t err = receiver->WaitForMessage(&rcvPortsMsg, kTimeout);
+ if (KERN_SUCCESS != err) {
+ LOG_ERROR("Error receiving get ports message %s (%x)\n", mach_error_string(err), err);
+ }
+ if (rcvPortsMsg.GetTranslatedPort(0) == MACH_PORT_NULL) {
+ LOG_ERROR("GetTranslatedPort(0) failed");
+ return false;
+ }
+ *ports_in_sender = rcvPortsMsg.GetTranslatedPort(0);
+
+ if (rcvPortsMsg.GetTranslatedPort(1) == MACH_PORT_NULL) {
+ LOG_ERROR("GetTranslatedPort(1) failed");
+ return false;
+ }
+ *ports_out_sender = rcvPortsMsg.GetTranslatedPort(1);
+ return true;
+}
+
+// Send two communication ports to another process and receive two back
+bool RequestPorts(const MemoryPorts& request_ports, mach_port_t ports_in_receiver,
+ mach_port_t* ports_in_sender, mach_port_t* ports_out_sender,
+ mach_port_t ports_out_receiver, PIDPair pid_pair) {
+ if (!SendPortsMessage(request_ports.mSender, ports_in_receiver, ports_out_receiver, pid_pair)) {
+ return false;
+ }
+ return RecvPortsMessage(request_ports.mReceiver, ports_in_sender, ports_out_sender);
+}
+
+MemoryPorts* GetMemoryPortsForPid(pid_t pid) {
+ gMutex.AssertCurrentThreadOwns();
+
+ if (gMemoryCommPorts.find(pid) == gMemoryCommPorts.end()) {
+ // We don't have the ports open to communicate with that pid, so we're going to
+ // ask our parent process over IPC to set them up for us.
+ if (gParentPid == 0) {
+ // If we're the top level parent process, we have no parent to ask.
+ LOG_ERROR("request for ports for pid %d, but we're the chrome process\n", pid);
+ return nullptr;
+ }
+ const MemoryPorts& parent = gMemoryCommPorts[gParentPid];
+
+ // Create two receiving ports in this process to send to the parent. One will be used for
+ // for listening for incoming memory to be shared, the other for getting the Handle of
+ // memory we share to the other process.
+ auto* ports_in_receiver = new ReceivePort();
+ auto* ports_out_receiver = new ReceivePort();
+ mach_port_t raw_ports_in_sender, raw_ports_out_sender;
+ if (!RequestPorts(parent, ports_in_receiver->GetPort(), &raw_ports_in_sender,
+ &raw_ports_out_sender, ports_out_receiver->GetPort(),
+ PIDPair(getpid(), pid))) {
+ LOG_ERROR("failed to request ports\n");
+ return nullptr;
+ }
+ // Our parent process sent us two ports, one is for sending new memory to, the other
+ // is for replying with the Handle when we receive new memory.
+ auto* ports_in_sender = new MachPortSender(raw_ports_in_sender);
+ auto* ports_out_sender = new MachPortSender(raw_ports_out_sender);
+ SetupMachMemory(pid, ports_in_receiver, ports_in_sender, ports_out_sender, ports_out_receiver,
+ false);
+ MOZ_ASSERT(gMemoryCommPorts.find(pid) != gMemoryCommPorts.end());
+ }
+ return &gMemoryCommPorts.at(pid);
+}
+
+// We just received a port representing a region of shared memory, reply to
+// the process that set it with the mach_port_t that represents it in this
+// process. That will be the Handle to be shared over normal IPC.
+//
+// WARNING: this function is called while gMutex is not held and must not
+// reference structures protected by gMutex. See the deadlock warning in
+// ShareToProcess().
+void HandleSharePortsMessage(MachReceiveMessage* rmsg, MemoryPorts* ports) {
+ mach_port_t port = rmsg->GetTranslatedPort(0);
+ uint64_t* serial = reinterpret_cast<uint64_t*>(rmsg->GetData());
+ MachSendMessage msg(kReturnIdMsg);
+ // Construct the reply message, echoing the serial, and adding the port
+ SharePortsReply replydata;
+ replydata.port = port;
+ replydata.serial = *serial;
+ msg.SetData(&replydata, sizeof(SharePortsReply));
+ kern_return_t err = ports->mSender->SendMessage(msg, kTimeout);
+ if (KERN_SUCCESS != err) {
+ LOG_ERROR("SendMessage failed 0x%x %s\n", err, mach_error_string(err));
+ }
+}
+
+// We were asked by another process to get communications ports to some process. Return
+// those ports via an IPC message.
+bool SendReturnPortsMsg(MachPortSender* sender, mach_port_t raw_ports_in_sender,
+ mach_port_t raw_ports_out_sender) {
+ MachSendMessage getPortsMsg(kReturnPortsMsg);
+ if (!getPortsMsg.AddDescriptor(MachMsgPortDescriptor(raw_ports_in_sender))) {
+ LOG_ERROR("Adding descriptor to message failed");
+ return false;
+ }
+
+ if (!getPortsMsg.AddDescriptor(MachMsgPortDescriptor(raw_ports_out_sender))) {
+ LOG_ERROR("Adding descriptor to message failed");
+ return false;
+ }
+ kern_return_t err = sender->SendMessage(getPortsMsg, kTimeout);
+ if (KERN_SUCCESS != err) {
+ LOG_ERROR("Error sending get ports message %s (%x)\n", mach_error_string(err), err);
+ return false;
+ }
+ return true;
+}
+
+// We were asked for communcations ports to a process that isn't us. Assuming that process
+// is one of our children, forward that request on.
+void ForwardGetPortsMessage(MachReceiveMessage* rmsg, MemoryPorts* ports, PIDPair* pid_pair) {
+ if (rmsg->GetTranslatedPort(0) == MACH_PORT_NULL) {
+ LOG_ERROR("GetTranslatedPort(0) failed");
+ return;
+ }
+ if (rmsg->GetTranslatedPort(1) == MACH_PORT_NULL) {
+ LOG_ERROR("GetTranslatedPort(1) failed");
+ return;
+ }
+ mach_port_t raw_ports_in_sender, raw_ports_out_sender;
+ MemoryPorts* requestedPorts = GetMemoryPortsForPid(pid_pair->mRequested);
+ if (!requestedPorts) {
+ LOG_ERROR("failed to find port for process\n");
+ return;
+ }
+ if (!RequestPorts(*requestedPorts, rmsg->GetTranslatedPort(0), &raw_ports_in_sender,
+ &raw_ports_out_sender, rmsg->GetTranslatedPort(1), *pid_pair)) {
+ LOG_ERROR("failed to request ports\n");
+ return;
+ }
+ SendReturnPortsMsg(ports->mSender, raw_ports_in_sender, raw_ports_out_sender);
+}
+
+// We receieved a message asking us to get communications ports for another process
+void HandleGetPortsMessage(MachReceiveMessage* rmsg, MemoryPorts* ports) {
+ PIDPair* pid_pair;
+ if (rmsg->GetDataLength() != sizeof(PIDPair)) {
+ LOG_ERROR("Improperly formatted message\n");
+ return;
+ }
+ pid_pair = reinterpret_cast<PIDPair*>(rmsg->GetData());
+ if (pid_pair->mRequested != getpid()) {
+ // This request is for ports to a process that isn't us, forward it to that process
+ ForwardGetPortsMessage(rmsg, ports, pid_pair);
+ } else {
+ if (rmsg->GetTranslatedPort(0) == MACH_PORT_NULL) {
+ LOG_ERROR("GetTranslatedPort(0) failed");
+ return;
+ }
+
+ if (rmsg->GetTranslatedPort(1) == MACH_PORT_NULL) {
+ LOG_ERROR("GetTranslatedPort(1) failed");
+ return;
+ }
+
+ auto* ports_in_sender = new MachPortSender(rmsg->GetTranslatedPort(0));
+ auto* ports_out_sender = new MachPortSender(rmsg->GetTranslatedPort(1));
+
+ auto* ports_in_receiver = new ReceivePort();
+ auto* ports_out_receiver = new ReceivePort();
+ if (SendReturnPortsMsg(ports->mSender, ports_in_receiver->GetPort(),
+ ports_out_receiver->GetPort())) {
+ SetupMachMemory(pid_pair->mRequester, ports_out_receiver, ports_out_sender, ports_in_sender,
+ ports_in_receiver, false);
+ }
+ }
+}
+
+static void* PortServerThread(void* argument) {
+ pthread_setname_np("PortServerThread");
+ MemoryPorts* ports = static_cast<MemoryPorts*>(argument);
+ MachReceiveMessage child_message;
+ while (true) {
+ MachReceiveMessage rmsg;
+ kern_return_t err = ports->mReceiver->WaitForMessage(&rmsg, MACH_MSG_TIMEOUT_NONE);
+ if (err != KERN_SUCCESS) {
+ LOG_ERROR("Wait for message failed 0x%x %s\n", err, mach_error_string(err));
+ continue;
+ }
+ if (rmsg.GetMessageID() == kShutdownMsg) {
+ delete ports->mSender;
+ delete ports->mReceiver;
+ delete ports;
+ return nullptr;
+ }
+ if (rmsg.GetMessageID() == kWaitForTexturesMsg) {
+ layers::TextureSync::HandleWaitForTexturesMessage(&rmsg, ports);
+ } else if (rmsg.GetMessageID() == kUpdateTextureLocksMsg) {
+ layers::TextureSync::DispatchCheckTexturesForUnlock();
+ } else {
+ switch (rmsg.GetMessageID()) {
+ case kSharePortsMsg: {
+ // Don't acquire gMutex here while calling HandleSharePortsMessage()
+ // to avoid deadlock. If gMutex is held by ShareToProcess(), we will
+ // block and create the following deadlock chain.
+ //
+ // 1) local:PortServerThread() blocked on local:gMutex held by
+ // 2) local:ShareToProcess() waiting for reply from
+ // 3) peer:PortServerThread() blocked on peer:gMutex held by
+ // 4) peer:ShareToProcess() waiting for reply from 1.
+ //
+ // It's safe to call HandleSharePortsMessage() without gMutex
+ // because HandleSharePortsMessage() only sends an outgoing message
+ // without referencing data structures protected by gMutex. The
+ // |ports| struct is deallocated on this thread in the kShutdownMsg
+ // message handling before this thread exits.
+ HandleSharePortsMessage(&rmsg, ports);
+ break;
+ }
+ case kGetPortsMsg: {
+ StaticMutexAutoLock smal(gMutex);
+ HandleGetPortsMessage(&rmsg, ports);
+ break;
+ }
+ case kCleanupMsg: {
+ StaticMutexAutoLock smal(gMutex);
+ if (gParentPid == 0) {
+ LOG_ERROR("Cleanup message not valid for parent process");
+ continue;
+ }
+
+ pid_t* pid;
+ if (rmsg.GetDataLength() != sizeof(pid_t)) {
+ LOG_ERROR("Improperly formatted message\n");
+ continue;
+ }
+ pid = reinterpret_cast<pid_t*>(rmsg.GetData());
+ SharedMemoryBasic::CleanupForPid(*pid);
+ break;
+ }
+ default: {
+ // gMutex not required
+ LOG_ERROR("Unknown message\n");
+ }
+ }
+ }
+ }
+}
+
+void SharedMemoryBasic::SetupMachMemory(pid_t pid, ReceivePort* listen_port,
+ MachPortSender* listen_port_ack, MachPortSender* send_port,
+ ReceivePort* send_port_ack, bool pidIsParent) {
+ StaticMutexAutoLock smal(gMutex);
+ mozilla::ipc::SetupMachMemory(pid, listen_port, listen_port_ack, send_port, send_port_ack,
+ pidIsParent);
+}
+
+void SharedMemoryBasic::Shutdown() {
+ StaticMutexAutoLock smal(gMutex);
+
+ layers::TextureSync::Shutdown();
+
+ for (auto& thread : gThreads) {
+ MachSendMessage shutdownMsg(kShutdownMsg);
+ thread.second.mPorts->mReceiver->SendMessageToSelf(shutdownMsg, kTimeout);
+ }
+ gThreads.clear();
+
+ for (auto& memoryCommPort : gMemoryCommPorts) {
+ delete memoryCommPort.second.mSender;
+ delete memoryCommPort.second.mReceiver;
+ }
+ gMemoryCommPorts.clear();
+}
+
+void SharedMemoryBasic::CleanupForPidWithLock(pid_t pid) {
+ StaticMutexAutoLock smal(gMutex);
+ CleanupForPid(pid);
+}
+
+void SharedMemoryBasic::CleanupForPid(pid_t pid) {
+ gMutex.AssertCurrentThreadOwns();
+
+ if (gThreads.find(pid) == gThreads.end()) {
+ return;
+ }
+
+ layers::TextureSync::CleanupForPid(pid);
+
+ const ListeningThread& listeningThread = gThreads[pid];
+ MachSendMessage shutdownMsg(kShutdownMsg);
+ kern_return_t ret = listeningThread.mPorts->mReceiver->SendMessageToSelf(shutdownMsg, kTimeout);
+ if (ret != KERN_SUCCESS) {
+ LOG_ERROR("sending shutdown msg failed %s %x\n", mach_error_string(ret), ret);
+ }
+ gThreads.erase(pid);
+
+ if (gParentPid == 0) {
+ // We're the parent. Broadcast the cleanup message to everyone else.
+ for (auto& memoryCommPort : gMemoryCommPorts) {
+ MachSendMessage msg(kCleanupMsg);
+ msg.SetData(&pid, sizeof(pid));
+ // We don't really care if this fails, we could be trying to send to an already shut down proc
+ memoryCommPort.second.mSender->SendMessage(msg, kTimeout);
+ }
+ }
+
+ MemoryPorts& ports = gMemoryCommPorts[pid];
+ delete ports.mSender;
+ delete ports.mReceiver;
+ gMemoryCommPorts.erase(pid);
+}
+
+bool SharedMemoryBasic::SendMachMessage(pid_t pid, MachSendMessage& message,
+ MachReceiveMessage* response) {
+ StaticMutexAutoLock smal(gMutex);
+ ipc::MemoryPorts* ports = GetMemoryPortsForPid(pid);
+ if (!ports) {
+ LOG_ERROR("Unable to get ports for process.\n");
+ return false;
+ }
+
+ kern_return_t err = ports->mSender->SendMessage(message, kTimeout);
+ if (err != KERN_SUCCESS) {
+ LOG_ERROR("Failed updating texture locks.\n");
+ return false;
+ }
+
+ if (response) {
+ err = ports->mReceiver->WaitForMessage(response, kTimeout);
+ if (err != KERN_SUCCESS) {
+ LOG_ERROR("short timeout didn't get an id %s %x\n", mach_error_string(err), err);
+ err = ports->mReceiver->WaitForMessage(response, kLongTimeout);
+
+ if (err != KERN_SUCCESS) {
+ LOG_ERROR("long timeout didn't get an id %s %x\n", mach_error_string(err), err);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+SharedMemoryBasic::SharedMemoryBasic()
+ : mPort(MACH_PORT_NULL), mMemory(nullptr), mOpenRights(RightsReadWrite) {}
+
+SharedMemoryBasic::~SharedMemoryBasic() {
+ Unmap();
+ CloseHandle();
+}
+
+bool SharedMemoryBasic::SetHandle(const Handle& aHandle, OpenRights aRights) {
+ MOZ_ASSERT(mPort == MACH_PORT_NULL, "already initialized");
+
+ mPort = 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, &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;
+ }
+
+ Mapped(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, 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(), mPort, 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(), mPort, 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);
+}
+
+bool SharedMemoryBasic::ShareToProcess(base::ProcessId pid, Handle* aNewHandle) {
+ if (pid == getpid()) {
+ *aNewHandle = mPort;
+ return mach_port_mod_refs(mach_task_self(), *aNewHandle, MACH_PORT_RIGHT_SEND, 1) ==
+ KERN_SUCCESS;
+ }
+ StaticMutexAutoLock smal(gMutex);
+
+ // Serially number the messages, to check whether
+ // the reply we get was meant for us.
+ static uint64_t serial = 0;
+ uint64_t my_serial = serial;
+ serial++;
+
+ MemoryPorts* ports = GetMemoryPortsForPid(pid);
+ if (!ports) {
+ LOG_ERROR("Unable to get ports for process.\n");
+ return false;
+ }
+ MachSendMessage smsg(kSharePortsMsg);
+ smsg.AddDescriptor(MachMsgPortDescriptor(mPort, MACH_MSG_TYPE_COPY_SEND));
+ smsg.SetData(&my_serial, sizeof(uint64_t));
+ kern_return_t err = ports->mSender->SendMessage(smsg, kTimeout);
+ if (err != KERN_SUCCESS) {
+ LOG_ERROR("sending port failed %s %x\n", mach_error_string(err), err);
+ return false;
+ }
+ MachReceiveMessage msg;
+ err = ports->mReceiver->WaitForMessage(&msg, kTimeout);
+ if (err != KERN_SUCCESS) {
+ LOG_ERROR("short timeout didn't get an id %s %x\n", mach_error_string(err), err);
+ err = ports->mReceiver->WaitForMessage(&msg, kLongTimeout);
+
+ if (err != KERN_SUCCESS) {
+ LOG_ERROR("long timeout didn't get an id %s %x\n", mach_error_string(err), err);
+ return false;
+ }
+ }
+ if (msg.GetDataLength() != sizeof(SharePortsReply)) {
+ LOG_ERROR("Improperly formatted reply\n");
+ return false;
+ }
+ SharePortsReply* msg_data = reinterpret_cast<SharePortsReply*>(msg.GetData());
+ mach_port_t id = msg_data->port;
+ uint64_t serial_check = msg_data->serial;
+ if (serial_check != my_serial) {
+ LOG_ERROR("Serials do not match up: %" PRIu64 " vs %" PRIu64 "", serial_check, my_serial);
+ return false;
+ }
+ *aNewHandle = id;
+ return true;
+}
+
+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 != MACH_PORT_NULL) {
+ mach_port_deallocate(mach_task_self(), mPort);
+ mPort = MACH_PORT_NULL;
+ mOpenRights = RightsReadWrite;
+ }
+}
+
+bool SharedMemoryBasic::IsHandleValid(const Handle& aHandle) const { return aHandle > 0; }
+
+} // 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..00bec892fa
--- /dev/null
+++ b/ipc/glue/Shmem.cpp
@@ -0,0 +1,465 @@
+/* -*- 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 "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,
+ SharedMemory::SharedMemoryType aType)
+ : IPC::Message(routingId, SHMEM_CREATED_MESSAGE_TYPE, 0,
+ HeaderFlags(NESTED_INSIDE_CPOW)) {
+ MOZ_RELEASE_ASSERT(aSize < std::numeric_limits<uint32_t>::max(),
+ "Tried to create Shmem with size larger than 4GB");
+ IPC::WriteParam(this, aIPDLId);
+ IPC::WriteParam(this, uint32_t(aSize));
+ IPC::WriteParam(this, int32_t(aType));
+ }
+
+ static bool ReadInfo(const Message* msg, PickleIterator* iter, id_t* aIPDLId,
+ size_t* aSize, SharedMemory::SharedMemoryType* aType) {
+ uint32_t size = 0;
+ if (!IPC::ReadParam(msg, iter, aIPDLId) ||
+ !IPC::ReadParam(msg, iter, &size) ||
+ !IPC::ReadParam(msg, iter, reinterpret_cast<int32_t*>(aType))) {
+ 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) {
+ IPC::WriteParam(this, aIPDLId);
+ }
+};
+
+static SharedMemory* NewSegment(SharedMemory::SharedMemoryType aType) {
+ if (SharedMemory::TYPE_BASIC == aType) {
+ return new SharedMemoryBasic;
+ } else {
+ NS_ERROR("unknown Shmem type");
+ return nullptr;
+ }
+}
+
+static already_AddRefed<SharedMemory> CreateSegment(
+ SharedMemory::SharedMemoryType aType, size_t aNBytes, size_t aExtraSize) {
+ RefPtr<SharedMemory> segment = NewSegment(aType);
+ 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;
+ }
+ SharedMemory::SharedMemoryType type;
+ PickleIterator iter(aDescriptor);
+ if (!ShmemCreated::ReadInfo(&aDescriptor, &iter, aId, aNBytes, &type)) {
+ return nullptr;
+ }
+ RefPtr<SharedMemory> segment = NewSegment(type);
+ if (!segment) {
+ return nullptr;
+ }
+ if (!segment->ReadHandle(&aDescriptor, &iter)) {
+ NS_ERROR("trying to open invalid handle");
+ return nullptr;
+ }
+ aDescriptor.EndRead(iter);
+ 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(PrivateIPDLCaller, 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();
+ // 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(PrivateIPDLCaller) {
+ 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(PrivateIPDLCaller,
+ size_t aNBytes,
+ SharedMemoryType aType,
+ 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(aType, 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(
+ PrivateIPDLCaller, 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(PrivateIPDLCaller, 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)
+
+// static
+already_AddRefed<Shmem::SharedMemory> Shmem::Alloc(PrivateIPDLCaller,
+ size_t aNBytes,
+ SharedMemoryType aType,
+ bool /*unused*/,
+ bool /*unused*/) {
+ RefPtr<SharedMemory> segment =
+ CreateSegment(aType, 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(
+ PrivateIPDLCaller, 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(PrivateIPDLCaller, SharedMemory* aSegment) {
+ DestroySegment(aSegment);
+}
+
+#endif // if defined(DEBUG)
+
+UniquePtr<IPC::Message> Shmem::ShareTo(PrivateIPDLCaller,
+ base::ProcessId aTargetPid,
+ int32_t routingId) {
+ AssertInvariants();
+
+ auto msg = MakeUnique<ShmemCreated>(routingId, mId, mSize, mSegment->Type());
+ if (!mSegment->ShareHandle(aTargetPid, msg.get())) {
+ return nullptr;
+ }
+ // close the handle to the segment after it is shared
+ mSegment->CloseHandle();
+ return msg;
+}
+
+UniquePtr<IPC::Message> Shmem::UnshareFrom(PrivateIPDLCaller,
+ int32_t routingId) {
+ AssertInvariants();
+ return MakeUnique<ShmemDestroyed>(routingId, mId);
+}
+
+void IPDLParamTraits<Shmem>::Write(IPC::Message* aMsg, IProtocol* aActor,
+ Shmem&& aParam) {
+ WriteIPDLParam(aMsg, aActor, aParam.mId);
+
+ aParam.RevokeRights(Shmem::PrivateIPDLCaller());
+ aParam.forget(Shmem::PrivateIPDLCaller());
+}
+
+bool IPDLParamTraits<Shmem>::Read(const IPC::Message* aMsg,
+ PickleIterator* aIter, IProtocol* aActor,
+ paramType* aResult) {
+ paramType::id_t id;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &id)) {
+ return false;
+ }
+
+ Shmem::SharedMemory* rawmem = aActor->LookupSharedMemory(id);
+ if (rawmem) {
+ *aResult = Shmem(Shmem::PrivateIPDLCaller(), 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..48a3779d2c
--- /dev/null
+++ b/ipc/glue/Shmem.h
@@ -0,0 +1,210 @@
+/* -*- 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/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 {
+
+template <typename P>
+struct IPDLParamTraits;
+
+class Shmem final {
+ friend struct IPDLParamTraits<mozilla::ipc::Shmem>;
+#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;
+ typedef SharedMemory::SharedMemoryType SharedMemoryType;
+ // Shmem objects should only be constructed directly from SharedMemory
+ // objects by the Shmem implementation itself, or by a select few functions
+ // in ProtocolUtils.{h,cpp}. You should not need to add new instances of
+ // this token.
+ struct PrivateIPDLCaller {};
+
+ Shmem() : mSegment(nullptr), mData(nullptr), mSize(0), mId(0) {}
+
+ Shmem(const Shmem& aOther) = default;
+
+#if !defined(DEBUG)
+ Shmem(PrivateIPDLCaller, SharedMemory* aSegment, id_t aId)
+ : mSegment(aSegment), mData(aSegment->memory()), mSize(0), mId(aId) {
+ mSize = static_cast<size_t>(*PtrToSize(mSegment));
+ }
+#else
+ Shmem(PrivateIPDLCaller, SharedMemory* aSegment, id_t aId);
+#endif
+
+ ~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(PrivateIPDLCaller());
+ }
+
+ 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);
+ }
+
+ // These shouldn't be used directly, use the IPDL interface instead.
+ id_t Id(PrivateIPDLCaller) const { return mId; }
+
+ SharedMemory* Segment(PrivateIPDLCaller) const { return mSegment; }
+
+#ifndef DEBUG
+ void RevokeRights(PrivateIPDLCaller) {}
+#else
+ void RevokeRights(PrivateIPDLCaller);
+#endif
+
+ void forget(PrivateIPDLCaller) {
+ mSegment = nullptr;
+ mData = nullptr;
+ mSize = 0;
+ mId = 0;
+ }
+
+ static already_AddRefed<Shmem::SharedMemory> Alloc(PrivateIPDLCaller,
+ size_t aNBytes,
+ SharedMemoryType aType,
+ bool aUnsafe,
+ bool aProtect = false);
+
+ // Prepare this to be shared with |aProcess|. 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> ShareTo(PrivateIPDLCaller, base::ProcessId aTargetPid,
+ int32_t routingId);
+
+ // Stop sharing this with |aTargetPid|. 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> UnshareFrom(PrivateIPDLCaller, 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(
+ PrivateIPDLCaller, const IPC::Message& aDescriptor, id_t* aId,
+ bool aProtect = false);
+
+ static void Dealloc(PrivateIPDLCaller, SharedMemory* aSegment);
+
+ private:
+ 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..1b84b6558e
--- /dev/null
+++ b/ipc/glue/ShmemMessageUtils.h
@@ -0,0 +1,33 @@
+/* -*- 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::Message* aMsg, IProtocol* aActor, paramType&& aParam);
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult);
+
+ static void Log(const paramType& aParam, std::wstring* aLog) {
+ aLog->append(L"(shmem segment)");
+ }
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // ifndef mozilla_ipc_ShmemMessageUtils_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..0c93c92f81
--- /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::Message* aMsg, IProtocol* aActor,
+ const mozilla::Tainted<T>& aParam) {
+ WriteIPDLParam(aMsg, aActor, aParam.mValue);
+ }
+
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ mozilla::Tainted<T>&& aParam) {
+ WriteIPDLParam(aMsg, aActor, std::move(aParam.mValue));
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, mozilla::Tainted<T>* aResult) {
+ return ReadIPDLParam(aMsg, aIter, 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/Transport.h b/ipc/glue/Transport.h
new file mode 100644
index 0000000000..9621d02354
--- /dev/null
+++ b/ipc/glue/Transport.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 mozilla_ipc_Transport_h
+#define mozilla_ipc_Transport_h 1
+
+#include "base/process_util.h"
+#include "chrome/common/ipc_channel.h"
+
+#ifdef OS_POSIX
+# include "mozilla/ipc/Transport_posix.h"
+#elif OS_WIN
+# include "mozilla/ipc/Transport_win.h"
+#endif
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace ipc {
+
+class FileDescriptor;
+
+typedef IPC::Channel Transport;
+
+nsresult CreateTransport(base::ProcessId aProcIdOne, TransportDescriptor* aOne,
+ TransportDescriptor* aTwo);
+
+UniquePtr<Transport> OpenDescriptor(const TransportDescriptor& aTd,
+ Transport::Mode aMode);
+
+UniquePtr<Transport> OpenDescriptor(const FileDescriptor& aFd,
+ Transport::Mode aMode);
+
+TransportDescriptor DuplicateDescriptor(const TransportDescriptor& aTd);
+
+void CloseDescriptor(const TransportDescriptor& aTd);
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_Transport_h
diff --git a/ipc/glue/TransportSecurityInfoUtils.cpp b/ipc/glue/TransportSecurityInfoUtils.cpp
new file mode 100644
index 0000000000..0fa88dc5c8
--- /dev/null
+++ b/ipc/glue/TransportSecurityInfoUtils.cpp
@@ -0,0 +1,79 @@
+/* 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"
+
+namespace IPC {
+
+void ParamTraits<nsITransportSecurityInfo*>::Write(
+ Message* aMsg, nsITransportSecurityInfo* aParam) {
+ bool nonNull = !!aParam;
+ WriteParam(aMsg, nonNull);
+ if (!nonNull) {
+ return;
+ }
+
+ aParam->SerializeToIPC(aMsg);
+}
+
+bool ParamTraits<nsITransportSecurityInfo*>::Read(
+ const Message* aMsg, PickleIterator* aIter,
+ RefPtr<nsITransportSecurityInfo>* aResult) {
+ *aResult = nullptr;
+
+ bool nonNull = false;
+ if (!ReadParam(aMsg, aIter, &nonNull)) {
+ return false;
+ }
+
+ if (!nonNull) {
+ return true;
+ }
+
+ RefPtr<nsITransportSecurityInfo> info =
+ new mozilla::psm::TransportSecurityInfo();
+ if (!info->DeserializeFromIPC(aMsg, aIter)) {
+ return false;
+ }
+
+ *aResult = std::move(info);
+ return true;
+}
+
+void ParamTraits<nsIX509Cert*>::Write(Message* aMsg, nsIX509Cert* aParam) {
+ bool nonNull = !!aParam;
+ WriteParam(aMsg, nonNull);
+ if (!nonNull) {
+ return;
+ }
+
+ aParam->SerializeToIPC(aMsg);
+}
+
+bool ParamTraits<nsIX509Cert*>::Read(const Message* aMsg, PickleIterator* aIter,
+ RefPtr<nsIX509Cert>* aResult) {
+ *aResult = nullptr;
+
+ bool nonNull = false;
+ if (!ReadParam(aMsg, aIter, &nonNull)) {
+ return false;
+ }
+
+ if (!nonNull) {
+ return true;
+ }
+
+ RefPtr<nsIX509Cert> cert = new nsNSSCertificate();
+ if (!cert->DeserializeFromIPC(aMsg, aIter)) {
+ 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..a782931d6b
--- /dev/null
+++ b/ipc/glue/TransportSecurityInfoUtils.h
@@ -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/. */
+
+#ifndef mozilla_ipc_TransportSecurityInfoUtils_h
+#define mozilla_ipc_TransportSecurityInfoUtils_h
+
+#include "nsCOMPtr.h"
+#include "nsITransportSecurityInfo.h"
+
+namespace IPC {
+
+template <typename>
+struct ParamTraits;
+
+template <>
+struct ParamTraits<nsITransportSecurityInfo*> {
+ static void Write(Message* aMsg, nsITransportSecurityInfo* aParam);
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ RefPtr<nsITransportSecurityInfo>* aResult);
+};
+
+template <>
+struct ParamTraits<nsIX509Cert*> {
+ static void Write(Message* aMsg, nsIX509Cert* aCert);
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ RefPtr<nsIX509Cert>* aResult);
+};
+
+} // namespace IPC
+
+#endif // mozilla_ipc_TransportSecurityInfoUtils_h
diff --git a/ipc/glue/Transport_posix.cpp b/ipc/glue/Transport_posix.cpp
new file mode 100644
index 0000000000..c756c98b0e
--- /dev/null
+++ b/ipc/glue/Transport_posix.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 <unistd.h>
+
+#include <string>
+
+#include "base/eintr_wrapper.h"
+
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/Transport.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "ProtocolUtils.h"
+
+using base::ProcessHandle;
+
+namespace mozilla {
+namespace ipc {
+
+nsresult CreateTransport(base::ProcessId aProcIdOne, TransportDescriptor* aOne,
+ TransportDescriptor* aTwo) {
+ auto id = IPC::Channel::GenerateVerifiedChannelID();
+ // Use MODE_SERVER to force creation of the socketpair
+ Transport t(id, Transport::MODE_SERVER, nullptr);
+ int fd1 = t.GetFileDescriptor();
+ int fd2, dontcare;
+ t.GetClientFileDescriptorMapping(&fd2, &dontcare);
+ if (fd1 < 0 || fd2 < 0) {
+ return NS_ERROR_TRANSPORT_INIT;
+ }
+
+ // The Transport closes these fds when it goes out of scope, so we
+ // dup them here
+ fd1 = dup(fd1);
+ if (fd1 < 0) {
+ AnnotateCrashReportWithErrno(
+ CrashReporter::Annotation::IpcCreateTransportDupErrno, errno);
+ }
+ fd2 = dup(fd2);
+ if (fd2 < 0) {
+ AnnotateCrashReportWithErrno(
+ CrashReporter::Annotation::IpcCreateTransportDupErrno, errno);
+ }
+
+ if (fd1 < 0 || fd2 < 0) {
+ IGNORE_EINTR(close(fd1));
+ IGNORE_EINTR(close(fd2));
+ return NS_ERROR_DUPLICATE_HANDLE;
+ }
+
+ aOne->mFd = base::FileDescriptor(fd1, true /*close after sending*/);
+ aTwo->mFd = base::FileDescriptor(fd2, true /*close after sending*/);
+ return NS_OK;
+}
+
+UniquePtr<Transport> OpenDescriptor(const TransportDescriptor& aTd,
+ Transport::Mode aMode) {
+ return MakeUnique<Transport>(aTd.mFd.fd, aMode, nullptr);
+}
+
+UniquePtr<Transport> OpenDescriptor(const FileDescriptor& aFd,
+ Transport::Mode aMode) {
+ auto rawFD = aFd.ClonePlatformHandle();
+ return MakeUnique<Transport>(rawFD.release(), aMode, nullptr);
+}
+
+TransportDescriptor DuplicateDescriptor(const TransportDescriptor& aTd) {
+ TransportDescriptor result = aTd;
+ result.mFd.fd = dup(aTd.mFd.fd);
+ if (result.mFd.fd == -1) {
+ AnnotateSystemError();
+ }
+ MOZ_RELEASE_ASSERT(result.mFd.fd != -1, "DuplicateDescriptor failed");
+ return result;
+}
+
+void CloseDescriptor(const TransportDescriptor& aTd) { close(aTd.mFd.fd); }
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/Transport_posix.h b/ipc/glue/Transport_posix.h
new file mode 100644
index 0000000000..f0dfeccb49
--- /dev/null
+++ b/ipc/glue/Transport_posix.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_Transport_posix_h
+#define mozilla_ipc_Transport_posix_h 1
+
+#include "ipc/IPCMessageUtils.h"
+
+namespace mozilla {
+namespace ipc {
+
+struct TransportDescriptor {
+ base::FileDescriptor mFd;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::ipc::TransportDescriptor> {
+ typedef mozilla::ipc::TransportDescriptor paramType;
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mFd);
+ }
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->mFd);
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_ipc_Transport_posix_h
diff --git a/ipc/glue/Transport_win.cpp b/ipc/glue/Transport_win.cpp
new file mode 100644
index 0000000000..fddd781b41
--- /dev/null
+++ b/ipc/glue/Transport_win.cpp
@@ -0,0 +1,115 @@
+/* -*- 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/message_loop.h"
+
+#include "mozilla/ipc/Transport.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+
+using base::ProcessHandle;
+
+namespace mozilla {
+namespace ipc {
+
+nsresult CreateTransport(base::ProcessId aProcIdOne, TransportDescriptor* aOne,
+ TransportDescriptor* aTwo) {
+ auto id = IPC::Channel::GenerateVerifiedChannelID();
+ // Use MODE_SERVER to force creation of the pipe
+ Transport t(id, Transport::MODE_SERVER, nullptr);
+ HANDLE serverPipe = t.GetServerPipeHandle();
+ if (!serverPipe) {
+ return NS_ERROR_TRANSPORT_INIT;
+ }
+
+ // NB: we create the server pipe immediately, instead of just
+ // grabbing an ID, on purpose. In the current setup, the client
+ // needs to connect to an existing server pipe, so to prevent race
+ // conditions, we create the server side here. When we send the pipe
+ // to the server, we DuplicateHandle it to the server process to give it
+ // access.
+ HANDLE serverDup;
+ DWORD access = 0;
+ DWORD options = DUPLICATE_SAME_ACCESS;
+ if (!DuplicateHandle(serverPipe, base::GetCurrentProcId(), &serverDup, access,
+ options)) {
+ return NS_ERROR_DUPLICATE_HANDLE;
+ }
+
+ aOne->mPipeName = aTwo->mPipeName = id;
+ aOne->mServerPipeHandle = serverDup;
+ aOne->mDestinationProcessId = aProcIdOne;
+ aTwo->mServerPipeHandle = INVALID_HANDLE_VALUE;
+ aTwo->mDestinationProcessId = 0;
+ return NS_OK;
+}
+
+HANDLE
+TransferHandleToProcess(HANDLE source, base::ProcessId pid) {
+ // At this point we're sending the handle to another process.
+
+ if (source == INVALID_HANDLE_VALUE) {
+ return source;
+ }
+ HANDLE handleDup;
+ DWORD access = 0;
+ DWORD options = DUPLICATE_SAME_ACCESS;
+ bool ok = DuplicateHandle(source, pid, &handleDup, access, options);
+ if (!ok) {
+ return nullptr;
+ }
+
+ // Now close our own copy of the handle (we're supposed to be transferring,
+ // not copying).
+ CloseHandle(source);
+
+ return handleDup;
+}
+
+UniquePtr<Transport> OpenDescriptor(const TransportDescriptor& aTd,
+ Transport::Mode aMode) {
+ if (aTd.mServerPipeHandle != INVALID_HANDLE_VALUE) {
+ MOZ_RELEASE_ASSERT(aTd.mDestinationProcessId == base::GetCurrentProcId());
+ }
+ return MakeUnique<Transport>(aTd.mPipeName, aTd.mServerPipeHandle, aMode,
+ nullptr);
+}
+
+UniquePtr<Transport> OpenDescriptor(const FileDescriptor& aFd,
+ Transport::Mode aMode) {
+ MOZ_ASSERT_UNREACHABLE("Not implemented!");
+ return nullptr;
+}
+
+TransportDescriptor DuplicateDescriptor(const TransportDescriptor& aTd) {
+ // We're duplicating this handle in our own process for bookkeeping purposes.
+
+ if (aTd.mServerPipeHandle == INVALID_HANDLE_VALUE) {
+ return aTd;
+ }
+
+ HANDLE serverDup;
+ DWORD access = 0;
+ DWORD options = DUPLICATE_SAME_ACCESS;
+ bool ok = DuplicateHandle(aTd.mServerPipeHandle, base::GetCurrentProcId(),
+ &serverDup, access, options);
+ if (!ok) {
+ AnnotateSystemError();
+ }
+ MOZ_RELEASE_ASSERT(ok);
+
+ TransportDescriptor desc = aTd;
+ desc.mServerPipeHandle = serverDup;
+ return desc;
+}
+
+void CloseDescriptor(const TransportDescriptor& aTd) {
+ // We're closing our own local copy of the pipe.
+
+ CloseHandle(aTd.mServerPipeHandle);
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/Transport_win.h b/ipc/glue/Transport_win.h
new file mode 100644
index 0000000000..9cc45bcd19
--- /dev/null
+++ b/ipc/glue/Transport_win.h
@@ -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/. */
+
+#ifndef mozilla_ipc_Transport_win_h
+#define mozilla_ipc_Transport_win_h 1
+
+#include <string>
+
+#include "base/process.h"
+#include "ipc/IPCMessageUtils.h"
+#include "nsWindowsHelpers.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+namespace ipc {
+
+struct TransportDescriptor {
+ std::wstring mPipeName;
+ HANDLE mServerPipeHandle;
+ base::ProcessId mDestinationProcessId;
+};
+
+HANDLE
+TransferHandleToProcess(HANDLE source, base::ProcessId pid);
+
+} // namespace ipc
+} // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::ipc::TransportDescriptor> {
+ typedef mozilla::ipc::TransportDescriptor paramType;
+ static void Write(Message* aMsg, const paramType& aParam) {
+ HANDLE pipe = mozilla::ipc::TransferHandleToProcess(
+ aParam.mServerPipeHandle, aParam.mDestinationProcessId);
+ DWORD duplicateFromProcessId = 0;
+ if (!pipe) {
+ if (XRE_IsParentProcess()) {
+ // If we are the parent and failed to transfer then there is no hope,
+ // just close the handle.
+ ::CloseHandle(aParam.mServerPipeHandle);
+ } else {
+ // We are probably sending to parent so it should be able to duplicate.
+ pipe = aParam.mServerPipeHandle;
+ duplicateFromProcessId = ::GetCurrentProcessId();
+ }
+ }
+
+ WriteParam(aMsg, aParam.mPipeName);
+ WriteParam(aMsg, pipe);
+ WriteParam(aMsg, duplicateFromProcessId);
+ WriteParam(aMsg, aParam.mDestinationProcessId);
+ }
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ DWORD duplicateFromProcessId;
+ bool r = (ReadParam(aMsg, aIter, &aResult->mPipeName) &&
+ ReadParam(aMsg, aIter, &aResult->mServerPipeHandle) &&
+ ReadParam(aMsg, aIter, &duplicateFromProcessId) &&
+ ReadParam(aMsg, aIter, &aResult->mDestinationProcessId));
+ if (!r) {
+ return r;
+ }
+
+ MOZ_RELEASE_ASSERT(
+ aResult->mServerPipeHandle,
+ "Main process failed to duplicate pipe handle to child.");
+
+ // If this is a not the "server" side descriptor, we have finished.
+ if (aResult->mServerPipeHandle == INVALID_HANDLE_VALUE) {
+ return true;
+ }
+
+ MOZ_RELEASE_ASSERT(aResult->mDestinationProcessId ==
+ base::GetCurrentProcId());
+
+ // If the pipe has already been duplicated to us, we have finished.
+ if (!duplicateFromProcessId) {
+ return true;
+ }
+
+ // Otherwise duplicate the handle to us.
+ nsAutoHandle sourceProcess(
+ ::OpenProcess(PROCESS_DUP_HANDLE, FALSE, duplicateFromProcessId));
+ if (!sourceProcess) {
+ return false;
+ }
+
+ HANDLE ourHandle;
+ BOOL duped = ::DuplicateHandle(
+ sourceProcess, aResult->mServerPipeHandle, ::GetCurrentProcess(),
+ &ourHandle, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
+ if (!duped) {
+ aResult->mServerPipeHandle = INVALID_HANDLE_VALUE;
+ return false;
+ }
+
+ aResult->mServerPipeHandle = ourHandle;
+ return true;
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_ipc_Transport_win_h
diff --git a/ipc/glue/URIParams.ipdlh b/ipc/glue/URIParams.ipdlh
new file mode 100644
index 0000000000..16aaea257d
--- /dev/null
+++ b/ipc/glue/URIParams.ipdlh
@@ -0,0 +1,116 @@
+/* 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 NullPrincipalURIParams
+{
+ // Purposefully empty. Null principal URIs do not round-trip.
+};
+
+struct HostObjectURIParams
+{
+ SimpleURIParams simpleParams;
+ bool revoked;
+};
+
+union URIParams
+{
+ SimpleURIParams;
+ StandardURLParams;
+ JARURIParams;
+ IconURIParams;
+ NullPrincipalURIParams;
+ JSURIParams;
+ SimpleNestedURIParams;
+ HostObjectURIParams;
+ DefaultURIParams;
+ NestedAboutURIParams;
+};
+
+struct JSURIParams
+{
+ SimpleURIParams simpleParams;
+ URIParams? baseURI;
+};
+
+struct SimpleNestedURIParams
+{
+ SimpleURIParams simpleParams;
+ URIParams innerURI;
+};
+
+struct NestedAboutURIParams
+{
+ SimpleNestedURIParams nestedParams;
+ URIParams? baseURI;
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/URIUtils.cpp b/ipc/glue/URIUtils.cpp
new file mode 100644
index 0000000000..45d88ec071
--- /dev/null
+++ b/ipc/glue/URIUtils.cpp
@@ -0,0 +1,147 @@
+/* -*- 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/SubstitutingURL.h"
+#include "mozilla/NullPrincipalURI.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(NS_IsMainThread());
+ MOZ_ASSERT(aURI);
+
+ aURI->Serialize(aParams);
+ if (aParams.type() == URIParams::T__None) {
+ MOZ_CRASH("Serialize failed!");
+ }
+}
+
+void SerializeURI(nsIURI* aURI, Maybe<URIParams>& aParams) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aURI) {
+ URIParams params;
+ SerializeURI(aURI, params);
+ aParams = Some(std::move(params));
+ } else {
+ aParams = Nothing();
+ }
+}
+
+already_AddRefed<nsIURI> DeserializeURI(const URIParams& aParams) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ 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::TNullPrincipalURIParams:
+ mutator = new NullPrincipalURI::Mutator();
+ 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;
+
+ 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) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ 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..fa595f3575
--- /dev/null
+++ b/ipc/glue/URIUtils.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_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::Message* aMsg, IProtocol* aActor, nsIURI* aParam) {
+ Maybe<URIParams> params;
+ SerializeURI(aParam, params);
+ WriteIPDLParam(aMsg, aActor, params);
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, RefPtr<nsIURI>* aResult) {
+ Maybe<URIParams> params;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &params)) {
+ return false;
+ }
+ *aResult = DeserializeURI(params);
+ return true;
+ }
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_URIUtils_h
diff --git a/ipc/glue/WindowsMessageLoop.cpp b/ipc/glue/WindowsMessageLoop.cpp
new file mode 100644
index 0000000000..2a18346811
--- /dev/null
+++ b/ipc/glue/WindowsMessageLoop.cpp
@@ -0,0 +1,1408 @@
+/* -*- 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"
+
+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) {
+ int idx = 0;
+ while (mozilla::widget::gAllEvents[idx].mId != uMsg &&
+ mozilla::widget::gAllEvents[idx].mStr != nullptr) {
+ idx++;
+ }
+ if (mozilla::widget::gAllEvents[idx].mStr) {
+ log.AppendPrintf("ui message \"%s\"",
+ mozilla::widget::gAllEvents[idx].mStr);
+ } 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%X", 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)) {
+ DWORD objId = static_cast<DWORD>(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
+
+// We need the pointer value of this in PluginInstanceChild.
+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,
+ bool interrupt)
+ : mInterrupt(interrupt),
+ 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;
+
+#if defined(ACCESSIBILITY)
+bool MessageChannel::WaitForSyncNotifyWithA11yReentry() {
+ mMonitor->AssertCurrentThreadOwns();
+ MonitorAutoUnlock unlock(*mMonitor);
+
+ const DWORD waitStart = ::GetTickCount();
+ DWORD elapsed = 0;
+ DWORD timeout =
+ mTimeoutMs == kNoTimeout ? INFINITE : static_cast<DWORD>(mTimeoutMs);
+ bool timedOut = false;
+
+ while (true) {
+ { // Scope for lock
+ MonitorAutoLock lock(*mMonitor);
+ if (!Connected()) {
+ break;
+ }
+ }
+ if (timeout != static_cast<DWORD>(kNoTimeout)) {
+ elapsed = ::GetTickCount() - waitStart;
+ }
+ if (elapsed >= timeout) {
+ timedOut = true;
+ break;
+ }
+ DWORD waitResult = 0;
+ ::SetLastError(ERROR_SUCCESS);
+ HRESULT hr = ::CoWaitForMultipleHandles(COWAIT_ALERTABLE, timeout - elapsed,
+ 1, &mEvent, &waitResult);
+ if (hr == RPC_S_CALLPENDING) {
+ timedOut = true;
+ break;
+ }
+ if (hr == S_OK) {
+ if (waitResult == 0) {
+ // mEvent is signaled
+ BOOL success = ::ResetEvent(mEvent);
+ if (!success) {
+ gfxDevCrash(mozilla::gfx::LogReason::MessageChannelInvalidHandle)
+ << "WindowsMessageChannel::WaitForSyncNotifyWithA11yReentry "
+ "failed to reset event. GetLastError: "
+ << GetLastError();
+ }
+ break;
+ }
+ if (waitResult == WAIT_IO_COMPLETION) {
+ // APC fired, keep waiting
+ continue;
+ }
+ }
+ NS_ERROR("CoWaitForMultipleHandles failed");
+ break;
+ }
+
+ return WaitResponse(timedOut);
+}
+#endif
+
+bool MessageChannel::WaitForSyncNotify(bool aHandleWindowsMessages) {
+ mMonitor->AssertCurrentThreadOwns();
+
+ if (!gUIThreadId) {
+ mozilla::ipc::windows::InitUIThread();
+ }
+
+#if defined(ACCESSIBILITY)
+ if (mFlags & REQUIRE_A11Y_REENTRY) {
+ MOZ_ASSERT(!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION));
+ return WaitForSyncNotifyWithA11yReentry();
+ }
+#endif
+
+ // Use a blocking wait if this channel does not require
+ // Windows message deferral behavior.
+ if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION) ||
+ !aHandleWindowsMessages) {
+ 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 && !mTopFrame->mInterrupt,
+ "Top frame is not a sync 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);
+}
+
+bool MessageChannel::WaitForInterruptNotify() {
+ mMonitor->AssertCurrentThreadOwns();
+
+ // Receiving the interrupt notification may require JS to execute on a
+ // worker.
+ dom::AutoYieldJSThreadExecution yield;
+
+ if (!gUIThreadId) {
+ mozilla::ipc::windows::InitUIThread();
+ }
+
+ // Re-use sync notification wait code if this channel does not require
+ // Windows message deferral behavior.
+ if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) {
+ return WaitForSyncNotify(true);
+ }
+
+ if (!InterruptStackDepth() && !AwaitingIncomingMessage()) {
+ // There is currently no way to recover from this condition.
+ MOZ_CRASH("StackDepth() is 0 in call to MessageChannel::WaitForNotify!");
+ }
+
+ NS_ASSERTION(
+ mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION,
+ "Shouldn't be here for channels that don't use message deferral!");
+ NS_ASSERTION(mTopFrame && mTopFrame->mInterrupt,
+ "Top frame is not a sync frame!");
+
+ MonitorAutoUnlock unlock(*mMonitor);
+
+ bool timedout = false;
+
+ UINT_PTR timerId = 0;
+ TimeoutData timeoutData = {0};
+
+ // gWindowHook is used as a flag variable for the loop below: if it is set
+ // and we start to spin a nested event loop, we need to clear the hook and
+ // process deferred/pending messages.
+ while (1) {
+ NS_ASSERTION((!!gWindowHook) == MessageChannel::IsPumpingMessages(),
+ "gWindowHook out of sync with reality");
+
+ if (mTopFrame->mSpinNestedEvents) {
+ if (gWindowHook && timerId) {
+ KillTimer(nullptr, timerId);
+ timerId = 0;
+ }
+ DeneuteredWindowRegion deneuteredRgn;
+ SpinInternalEventLoop();
+ BOOL success = ResetEvent(mEvent);
+ if (!success) {
+ gfxDevCrash(mozilla::gfx::LogReason::MessageChannelInvalidHandle)
+ << "WindowsMessageChannel::WaitForInterruptNotify::"
+ "SpinNestedEvents failed to reset event. GetLastError: "
+ << GetLastError();
+ }
+ return true;
+ }
+
+ if (mTimeoutMs != kNoTimeout && !timerId) {
+ InitTimeoutData(&timeoutData, mTimeoutMs);
+ timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr);
+ NS_ASSERTION(timerId, "SetTimer failed!");
+ }
+
+ NeuteredWindowRegion neuteredRgn(true);
+
+ MSG msg = {0};
+
+ // Don't get wrapped up in here if the child connection dies.
+ {
+ MonitorAutoLock lock(*mMonitor);
+ if (!Connected()) {
+ break;
+ }
+ }
+
+ 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::WaitForInterruptNotify::"
+ "WaitForMultipleObjects 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;
+ }
+
+ // See MessageChannel's WaitFor*Notify for details.
+ bool haveSentMessagesPending =
+ (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;
+
+ // Run all COM messages *after* looking at the queue status.
+ if (gCOMWindow) {
+ if (PeekMessageW(&msg, gCOMWindow, 0, 0, PM_REMOVE)) {
+ TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ }
+
+ // PeekMessage markes the messages as "old" so that they don't wake up
+ // MsgWaitForMultipleObjects every time.
+ 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..9fd9327f16
--- /dev/null
+++ b/ipc/glue/moz.build
@@ -0,0 +1,267 @@
+# -*- 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 += [
+ "BackgroundChild.h",
+ "BackgroundParent.h",
+ "BackgroundUtils.h",
+ "BrowserProcessSubThread.h",
+ "ByteBuf.h",
+ "ByteBufUtils.h",
+ "CrashReporterClient.h",
+ "CrashReporterHelper.h",
+ "CrashReporterHost.h",
+ "CrossProcessMutex.h",
+ "CrossProcessSemaphore.h",
+ "Endpoint.h",
+ "EnvironmentMap.h",
+ "FileDescriptor.h",
+ "FileDescriptorSetChild.h",
+ "FileDescriptorSetParent.h",
+ "FileDescriptorUtils.h",
+ "GeckoChildProcessHost.h",
+ "IdleSchedulerChild.h",
+ "IdleSchedulerParent.h",
+ "InputStreamUtils.h",
+ "IOThreadChild.h",
+ "IPCCore.h",
+ "IPCStreamAlloc.h",
+ "IPCStreamDestination.h",
+ "IPCStreamSource.h",
+ "IPCStreamUtils.h",
+ "IPCTypes.h",
+ "IPDLParamTraits.h",
+ "LibrarySandboxPreload.h",
+ "MessageChannel.h",
+ "MessageLink.h",
+ "Neutering.h",
+ "ProcessChild.h",
+ "ProtocolMessageUtils.h",
+ "ProtocolUtils.h",
+ "ProtocolUtilsFwd.h",
+ "ScopedXREEmbed.h",
+ "SerializedStructuredCloneBuffer.h",
+ "SharedMemory.h",
+ "SharedMemoryBasic.h",
+ "Shmem.h",
+ "ShmemMessageUtils.h",
+ "TaintingIPCUtils.h",
+ "TaskFactory.h",
+ "Transport.h",
+ "TransportSecurityInfoUtils.h",
+ "URIUtils.h",
+ "WindowsMessageLoop.h",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ EXPORTS.mozilla.ipc += [
+ "Transport_win.h",
+ ]
+ SOURCES += [
+ "SharedMemory_windows.cpp",
+ "Transport_win.cpp",
+ "WindowsMessageLoop.cpp",
+ ]
+else:
+ EXPORTS.mozilla.ipc += [
+ "Transport_posix.h",
+ ]
+ UNIFIED_SOURCES += [
+ "SharedMemory_posix.cpp",
+ "Transport_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",
+ "BrowserProcessSubThread.cpp",
+ "CrashReporterClient.cpp",
+ "CrashReporterHost.cpp",
+ "FileDescriptor.cpp",
+ "FileDescriptorUtils.cpp",
+ "IdleSchedulerChild.cpp",
+ "IdleSchedulerParent.cpp",
+ "InputStreamUtils.cpp",
+ "IPCMessageUtils.cpp",
+ "IPCStreamChild.cpp",
+ "IPCStreamDestination.cpp",
+ "IPCStreamParent.cpp",
+ "IPCStreamSource.cpp",
+ "IPCStreamUtils.cpp",
+ "LibrarySandboxPreload.cpp",
+ "MessageChannel.cpp",
+ "MessageLink.cpp",
+ "MessagePump.cpp",
+ "ProcessChild.cpp",
+ "ProcessUtils_common.cpp",
+ "ProtocolUtils.cpp",
+ "ScopedXREEmbed.cpp",
+ "SharedMemory.cpp",
+ "Shmem.cpp",
+ "StringUtil.cpp",
+ "TransportSecurityInfoUtils.cpp",
+ "URIUtils.cpp",
+]
+
+SOURCES += [
+ "BackgroundChildImpl.cpp",
+ "BackgroundParentImpl.cpp",
+ "FileDescriptorSetChild.cpp",
+ "FileDescriptorSetParent.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",
+ ]
+
+LOCAL_INCLUDES += [
+ "/caps",
+ "/dom/broadcastchannel",
+ "/dom/indexedDB",
+ "/dom/storage",
+ "/netwerk/base",
+ "/third_party/libwebrtc",
+ "/third_party/libwebrtc/webrtc",
+ "/xpcom/build",
+]
+
+IPDL_SOURCES = [
+ "InputStreamParams.ipdlh",
+ "IPCStream.ipdlh",
+ "PBackground.ipdl",
+ "PBackgroundSharedTypes.ipdlh",
+ "PBackgroundTest.ipdl",
+ "PChildToParentStream.ipdl",
+ "PFileDescriptorSet.ipdl",
+ "PIdleScheduler.ipdl",
+ "PParentToChildStream.ipdl",
+ "ProtocolTypes.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"
+
+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["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-shadow"]
+
+# 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..459ba65186
--- /dev/null
+++ b/ipc/glue/nsIIPCSerializableInputStream.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_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;
+class ChildToParentStreamActorManager;
+class ParentToChildStreamActorManager;
+
+} // 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:
+ typedef nsTArray<mozilla::ipc::FileDescriptor> FileDescriptorArray;
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IIPCSERIALIZABLEINPUTSTREAM_IID)
+
+ virtual void Serialize(
+ mozilla::ipc::InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors, bool aDelayedStart,
+ uint32_t aMaxSize, uint32_t* aSizeUsed,
+ mozilla::ipc::ParentToChildStreamActorManager* aManager) = 0;
+
+ virtual void Serialize(
+ mozilla::ipc::InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors, bool aDelayedStart,
+ uint32_t aMaxSize, uint32_t* aSizeUsed,
+ mozilla::ipc::ChildToParentStreamActorManager* aManager) = 0;
+
+ virtual bool Deserialize(const mozilla::ipc::InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIIPCSerializableInputStream,
+ NS_IIPCSERIALIZABLEINPUTSTREAM_IID)
+
+#define NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM \
+ virtual void Serialize( \
+ mozilla::ipc::InputStreamParams&, FileDescriptorArray&, bool, uint32_t, \
+ uint32_t*, mozilla::ipc::ParentToChildStreamActorManager*) override; \
+ \
+ virtual void Serialize( \
+ mozilla::ipc::InputStreamParams&, FileDescriptorArray&, bool, uint32_t, \
+ uint32_t*, mozilla::ipc::ChildToParentStreamActorManager*) override; \
+ \
+ virtual bool Deserialize(const mozilla::ipc::InputStreamParams&, \
+ const FileDescriptorArray&) override;
+
+#define NS_FORWARD_NSIIPCSERIALIZABLEINPUTSTREAM(_to) \
+ virtual void Serialize( \
+ mozilla::ipc::InputStreamParams& aParams, \
+ FileDescriptorArray& aFileDescriptors, bool aDelayedStart, \
+ uint32_t aMaxSize, uint32_t* aSizeUsed, \
+ mozilla::ipc::ParentToChildStreamActorManager* aManager) override { \
+ _to Serialize(aParams, aFileDescriptors, aDelayedStart, aMaxSize, \
+ aSizeUsed, aManager); \
+ } \
+ \
+ virtual void Serialize( \
+ mozilla::ipc::InputStreamParams& aParams, \
+ FileDescriptorArray& aFileDescriptors, bool aDelayedStart, \
+ uint32_t aMaxSize, uint32_t* aSizeUsed, \
+ mozilla::ipc::ChildToParentStreamActorManager* aManager) override { \
+ _to Serialize(aParams, aFileDescriptors, aDelayedStart, aMaxSize, \
+ aSizeUsed, aManager); \
+ } \
+ \
+ virtual bool Deserialize(const mozilla::ipc::InputStreamParams& aParams, \
+ const FileDescriptorArray& aFileDescriptors) \
+ override { \
+ return _to Deserialize(aParams, aFileDescriptors); \
+ }
+
+#define NS_FORWARD_SAFE_NSIIPCSERIALIZABLEINPUTSTREAM(_to) \
+ virtual void Serialize( \
+ mozilla::ipc::InputStreamParams& aParams, \
+ FileDescriptorArray& aFileDescriptors, bool aDelayedStart, \
+ uint32_t aMaxSize, uint32_t* aSizeUsed, \
+ mozilla::ipc::ParentToChildStreamActorManager* aManager) override { \
+ if (_to) { \
+ _to->Serialize(aParams, aFileDescriptors, aDelayedStart, aMaxSize, \
+ aSizeUsed, aManager); \
+ } \
+ } \
+ \
+ virtual void Serialize( \
+ mozilla::ipc::InputStreamParams& aParams, \
+ FileDescriptorArray& aFileDescriptors, bool aDelayedStart, \
+ uint32_t aMaxSize, uint32_t* aSizeUsed, \
+ mozilla::ipc::ChildToParentStreamActorManager* aManager) override { \
+ if (_to) { \
+ _to->Serialize(aParams, aFileDescriptors, aDelayedStart, aMaxSize, \
+ aSizeUsed, aManager); \
+ } \
+ } \
+ \
+ virtual bool Deserialize(const mozilla::ipc::InputStreamParams& aParams, \
+ const FileDescriptorArray& aFileDescriptors) \
+ override { \
+ return _to ? _to->Deserialize(aParams, aFileDescriptors) : false; \
+ }
+
+#endif // mozilla_ipc_nsIIPCSerializableInputStream_h