diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /ipc/glue | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'ipc/glue')
98 files changed, 23343 insertions, 0 deletions
diff --git a/ipc/glue/BackgroundChild.h b/ipc/glue/BackgroundChild.h new file mode 100644 index 000000000..16c444313 --- /dev/null +++ b/ipc/glue/BackgroundChild.h @@ -0,0 +1,99 @@ +/* -*- 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 "base/process.h" +#include "mozilla/Attributes.h" +#include "mozilla/ipc/Transport.h" + +class nsIDOMBlob; +class nsIIPCBackgroundChildCreateCallback; + +namespace mozilla { +namespace dom { + +class BlobImpl; +class ContentChild; +class ContentParent; +class PBlobChild; + +} // namespace dom + +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 asynchronous. GetForCurrentThread() will return +// null until the sequence is complete. GetOrCreateForCurrentThread() will start +// the creation sequence and will call back via the +// nsIIPCBackgroundChildCreateCallback interface when completed. Thereafter +// (assuming success) GetForCurrentThread() will return the same actor every +// time. SynchronouslyCreateForCurrentThread() will spin the event loop until +// the BackgroundChild until the creation sequence is complete. +// +// 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; + + typedef base::ProcessId ProcessId; + typedef mozilla::ipc::Transport Transport; + +public: + // See above. + static PBackgroundChild* + GetForCurrentThread(); + + // See above. + static bool + GetOrCreateForCurrentThread(nsIIPCBackgroundChildCreateCallback* aCallback); + + // See above. + static PBackgroundChild* + SynchronouslyCreateForCurrentThread(); + + static mozilla::dom::PBlobChild* + GetOrCreateActorForBlob(PBackgroundChild* aBackgroundActor, + nsIDOMBlob* aBlob); + + static mozilla::dom::PBlobChild* + GetOrCreateActorForBlobImpl(PBackgroundChild* aBackgroundActor, + mozilla::dom::BlobImpl* aBlobImpl); + + // See above. + static void + CloseForCurrentThread(); + +private: + // Only called by ContentChild or ContentParent. + static void + Startup(); + + // Only called by ContentChild. + static PBackgroundChild* + Alloc(Transport* aTransport, ProcessId aOtherProcess); +}; + +} // 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 000000000..b157048a4 --- /dev/null +++ b/ipc/glue/BackgroundChildImpl.cpp @@ -0,0 +1,518 @@ +/* -*- 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 "ServiceWorkerManagerChild.h" +#include "FileDescriptorSetChild.h" +#ifdef MOZ_WEBRTC +#include "CamerasChild.h" +#endif +#include "mozilla/media/MediaChild.h" +#include "mozilla/Assertions.h" +#include "mozilla/dom/PBlobChild.h" +#include "mozilla/dom/PFileSystemRequestChild.h" +#include "mozilla/dom/FileSystemTaskBase.h" +#include "mozilla/dom/asmjscache/AsmJSCache.h" +#include "mozilla/dom/cache/ActorUtils.h" +#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.h" +#include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsChild.h" +#include "mozilla/dom/ipc/BlobChild.h" +#include "mozilla/dom/quota/PQuotaChild.h" +#ifdef MOZ_GAMEPAD +#include "mozilla/dom/GamepadEventChannelChild.h" +#include "mozilla/dom/GamepadTestChannelChild.h" +#endif +#include "mozilla/dom/MessagePortChild.h" +#include "mozilla/ipc/PBackgroundTestChild.h" +#include "mozilla/ipc/PSendStreamChild.h" +#include "mozilla/layout/VsyncChild.h" +#include "mozilla/net/PUDPSocketChild.h" +#include "mozilla/dom/network/UDPSocketChild.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() + { + MOZ_COUNT_DTOR(TestChild); + } + +public: + virtual bool + Recv__delete__(const nsCString& aTestArg) override; +}; + +} // namespace + +namespace mozilla { +namespace ipc { + +using mozilla::dom::UDPSocketChild; +using mozilla::net::PUDPSocketChild; + +using mozilla::dom::asmjscache::PAsmJSCacheEntryChild; +using mozilla::dom::cache::PCacheChild; +using mozilla::dom::cache::PCacheStorageChild; +using mozilla::dom::cache::PCacheStreamControlChild; + +// ----------------------------------------------------------------------------- +// 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) { + +#define HANDLE_CASE(_result) \ + case _result: \ + abortMessage.AssignLiteral(#_result); \ + break + + HANDLE_CASE(MsgDropped); + 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::PBackgroundIDBFactoryChild* +BackgroundChildImpl::AllocPBackgroundIDBFactoryChild( + const LoggingInfo& aLoggingInfo) +{ + MOZ_CRASH("PBackgroundIDBFactoryChild actors should be manually " + "constructed!"); +} + +bool +BackgroundChildImpl::DeallocPBackgroundIDBFactoryChild( + PBackgroundIDBFactoryChild* aActor) +{ + MOZ_ASSERT(aActor); + + delete 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; +} + +auto +BackgroundChildImpl::AllocPBlobChild(const BlobConstructorParams& aParams) + -> PBlobChild* +{ + MOZ_ASSERT(aParams.type() != BlobConstructorParams::T__None); + + return mozilla::dom::BlobChild::Create(this, aParams); +} + +bool +BackgroundChildImpl::DeallocPBlobChild(PBlobChild* aActor) +{ + MOZ_ASSERT(aActor); + + mozilla::dom::BlobChild::Destroy(aActor); + return true; +} + +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; +} + +BackgroundChildImpl::PVsyncChild* +BackgroundChildImpl::AllocPVsyncChild() +{ + RefPtr<mozilla::layout::VsyncChild> actor = new mozilla::layout::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<mozilla::layout::VsyncChild> actor = + dont_AddRef(static_cast<mozilla::layout::VsyncChild*>(aActor)); + return true; +} + +PUDPSocketChild* +BackgroundChildImpl::AllocPUDPSocketChild(const OptionalPrincipalInfo& 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(aOrigin); + 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); +#endif + return true; +} + +// ----------------------------------------------------------------------------- +// ServiceWorkerManager +// ----------------------------------------------------------------------------- + +dom::PServiceWorkerManagerChild* +BackgroundChildImpl::AllocPServiceWorkerManagerChild() +{ + RefPtr<dom::workers::ServiceWorkerManagerChild> agent = + new dom::workers::ServiceWorkerManagerChild(); + return agent.forget().take(); +} + +bool +BackgroundChildImpl::DeallocPServiceWorkerManagerChild( + PServiceWorkerManagerChild* aActor) +{ + RefPtr<dom::workers::ServiceWorkerManagerChild> child = + dont_AddRef(static_cast<dom::workers::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; +} + +PCacheStreamControlChild* +BackgroundChildImpl::AllocPCacheStreamControlChild() +{ + return dom::cache::AllocPCacheStreamControlChild(); +} + +bool +BackgroundChildImpl::DeallocPCacheStreamControlChild(PCacheStreamControlChild* aActor) +{ + dom::cache::DeallocPCacheStreamControlChild(aActor); + return true; +} + +// ----------------------------------------------------------------------------- +// 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; +} + +PSendStreamChild* +BackgroundChildImpl::AllocPSendStreamChild() +{ + MOZ_CRASH("PSendStreamChild actors should be manually constructed!"); +} + +bool +BackgroundChildImpl::DeallocPSendStreamChild(PSendStreamChild* aActor) +{ + delete aActor; + return true; +} + +PAsmJSCacheEntryChild* +BackgroundChildImpl::AllocPAsmJSCacheEntryChild( + const dom::asmjscache::OpenMode& aOpenMode, + const dom::asmjscache::WriteParams& aWriteParams, + const PrincipalInfo& aPrincipalInfo) +{ + MOZ_CRASH("PAsmJSCacheEntryChild actors should be manually constructed!"); +} + +bool +BackgroundChildImpl::DeallocPAsmJSCacheEntryChild(PAsmJSCacheEntryChild* aActor) +{ + MOZ_ASSERT(aActor); + + dom::asmjscache::DeallocEntryChild(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; +} + +dom::PFileSystemRequestChild* +BackgroundChildImpl::AllocPFileSystemRequestChild(const FileSystemParams& aParams) +{ + MOZ_CRASH("Should never get here!"); + return nullptr; +} + +bool +BackgroundChildImpl::DeallocPFileSystemRequestChild(PFileSystemRequestChild* aActor) +{ + // The reference is increased in FileSystemTaskBase::Start of + // FileSystemTaskBase.cpp. We should decrease it after IPC. + RefPtr<dom::FileSystemTaskChildBase> child = + dont_AddRef(static_cast<dom::FileSystemTaskChildBase*>(aActor)); + return true; +} + +// Gamepad API Background IPC +dom::PGamepadEventChannelChild* +BackgroundChildImpl::AllocPGamepadEventChannelChild() +{ + MOZ_CRASH("PGamepadEventChannelChild actor should be manually constructed!"); + return nullptr; +} + +bool +BackgroundChildImpl::DeallocPGamepadEventChannelChild(PGamepadEventChannelChild* aActor) +{ +#ifdef MOZ_GAMEPAD + MOZ_ASSERT(aActor); + delete static_cast<dom::GamepadEventChannelChild*>(aActor); +#endif + return true; +} + +dom::PGamepadTestChannelChild* +BackgroundChildImpl::AllocPGamepadTestChannelChild() +{ +#ifdef MOZ_GAMEPAD + MOZ_CRASH("PGamepadTestChannelChild actor should be manually constructed!"); +#endif + return nullptr; +} + +bool +BackgroundChildImpl::DeallocPGamepadTestChannelChild(PGamepadTestChannelChild* aActor) +{ +#ifdef MOZ_GAMEPAD + MOZ_ASSERT(aActor); + delete static_cast<dom::GamepadTestChannelChild*>(aActor); +#endif + return true; +} + +} // namespace ipc +} // namespace mozilla + +bool +TestChild::Recv__delete__(const nsCString& aTestArg) +{ + MOZ_RELEASE_ASSERT(aTestArg == mTestArg, + "BackgroundTest message was corrupted!"); + + return true; +} diff --git a/ipc/glue/BackgroundChildImpl.h b/ipc/glue/BackgroundChildImpl.h new file mode 100644 index 000000000..f8c2a5576 --- /dev/null +++ b/ipc/glue/BackgroundChildImpl.h @@ -0,0 +1,202 @@ +/* -*- 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/PBackgroundChild.h" +#include "nsAutoPtr.h" + +namespace mozilla { +namespace dom { + +class FileHandleBase; + +namespace indexedDB { + +class ThreadLocal; + +} // namespace indexedDB +} // namespace dom + +namespace ipc { + +// Instances of this class should never be created directly. This class is meant +// to be inherited in BackgroundImpl. +class BackgroundChildImpl : public PBackgroundChild +{ +public: + class ThreadLocal; + + // Get the ThreadLocal for the current thread if + // BackgroundChild::GetOrCreateForCurrentThread() has been called and true was + // returned (e.g. a valid PBackgroundChild actor has been created or is in the + // process of being created). Otherwise this function returns null. + // This functions is implemented in BackgroundImpl.cpp. + static ThreadLocal* + GetThreadLocalForCurrentThread(); + +protected: + BackgroundChildImpl(); + virtual ~BackgroundChildImpl(); + + virtual void + ProcessingError(Result aCode, const char* aReason) override; + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + virtual PBackgroundTestChild* + AllocPBackgroundTestChild(const nsCString& aTestArg) override; + + virtual bool + DeallocPBackgroundTestChild(PBackgroundTestChild* aActor) override; + + virtual PBackgroundIDBFactoryChild* + AllocPBackgroundIDBFactoryChild(const LoggingInfo& aLoggingInfo) override; + + virtual bool + DeallocPBackgroundIDBFactoryChild(PBackgroundIDBFactoryChild* aActor) + override; + + virtual PBackgroundIndexedDBUtilsChild* + AllocPBackgroundIndexedDBUtilsChild() override; + + virtual bool + DeallocPBackgroundIndexedDBUtilsChild(PBackgroundIndexedDBUtilsChild* aActor) + override; + + virtual PBlobChild* + AllocPBlobChild(const BlobConstructorParams& aParams) override; + + virtual bool + DeallocPBlobChild(PBlobChild* 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 OptionalPrincipalInfo& 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 dom::cache::PCacheStreamControlChild* + AllocPCacheStreamControlChild() override; + + virtual bool + DeallocPCacheStreamControlChild(dom::cache::PCacheStreamControlChild* aActor) override; + + virtual PMessagePortChild* + AllocPMessagePortChild(const nsID& aUUID, const nsID& aDestinationUUID, + const uint32_t& aSequenceID) override; + + virtual bool + DeallocPMessagePortChild(PMessagePortChild* aActor) override; + + virtual PSendStreamChild* + AllocPSendStreamChild() override; + + virtual bool + DeallocPSendStreamChild(PSendStreamChild* aActor) override; + + virtual PAsmJSCacheEntryChild* + AllocPAsmJSCacheEntryChild(const dom::asmjscache::OpenMode& aOpenMode, + const dom::asmjscache::WriteParams& aWriteParams, + const PrincipalInfo& aPrincipalInfo) override; + + virtual bool + DeallocPAsmJSCacheEntryChild(PAsmJSCacheEntryChild* aActor) override; + + virtual PQuotaChild* + AllocPQuotaChild() override; + + virtual bool + DeallocPQuotaChild(PQuotaChild* aActor) override; + + virtual PFileSystemRequestChild* + AllocPFileSystemRequestChild(const FileSystemParams&) override; + + virtual bool + DeallocPFileSystemRequestChild(PFileSystemRequestChild*) override; + + // Gamepad API Background IPC + virtual PGamepadEventChannelChild* + AllocPGamepadEventChannelChild() override; + + virtual bool + DeallocPGamepadEventChannelChild(PGamepadEventChannelChild* aActor) override; + + virtual PGamepadTestChannelChild* + AllocPGamepadTestChannelChild() override; + + virtual bool + DeallocPGamepadTestChannelChild(PGamepadTestChannelChild* aActor) override; +}; + +class BackgroundChildImpl::ThreadLocal final +{ + friend class nsAutoPtr<ThreadLocal>; + +public: + nsAutoPtr<mozilla::dom::indexedDB::ThreadLocal> mIndexedDBThreadLocal; + mozilla::dom::FileHandleBase* mCurrentFileHandle; + +public: + ThreadLocal(); + +private: + // Only destroyed by nsAutoPtr<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 000000000..2f8e073f8 --- /dev/null +++ b/ipc/glue/BackgroundImpl.cpp @@ -0,0 +1,2092 @@ +/* -*- 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/Services.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/ipc/BlobChild.h" +#include "mozilla/dom/ipc/BlobParent.h" +#include "mozilla/dom/ipc/nsIRemoteBlob.h" +#include "mozilla/ipc/ProtocolTypes.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsIEventTarget.h" +#include "nsIIPCBackgroundChildCreateCallback.h" +#include "nsIMutable.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" + +#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; + +namespace { + +// ----------------------------------------------------------------------------- +// Utility Functions +// ----------------------------------------------------------------------------- + + +void +AssertIsInMainProcess() +{ + MOZ_ASSERT(XRE_IsParentProcess()); +} + +void +AssertIsInChildProcess() +{ + MOZ_ASSERT(!XRE_IsParentProcess()); +} + +void +AssertIsOnMainThread() +{ + THREADSAFETY_ASSERT(NS_IsMainThread()); +} + +// ----------------------------------------------------------------------------- +// ParentImpl Declaration +// ----------------------------------------------------------------------------- + +class ParentImpl final : public BackgroundParentImpl +{ + friend class mozilla::ipc::BackgroundParent; + +public: + class CreateCallback; + +private: + class ShutdownObserver; + class RequestMessageLoopRunnable; + class ShutdownBackgroundThreadRunnable; + class ForceCloseBackgroundActorsRunnable; + class CreateCallbackRunnable; + class ConnectActorRunnable; + + struct MOZ_STACK_CLASS TimerCallbackClosure + { + nsIThread* mThread; + nsTArray<ParentImpl*>* mLiveActors; + + TimerCallbackClosure(nsIThread* aThread, nsTArray<ParentImpl*>* aLiveActors) + : mThread(aThread), mLiveActors(aLiveActors) + { + AssertIsInMainProcess(); + 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 is null if the thread does not + // exist or is shutting down. + static MessageLoop* sBackgroundThreadMessageLoop; + + // 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; + + // This is only modified on the main thread. It is a FIFO queue for callbacks + // waiting for the background thread to be created. + static StaticAutoPtr<nsTArray<RefPtr<CreateCallback>>> sPendingCallbacks; + + // 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 bool + CreateActorForSameProcess(CreateCallback* aCallback); + + static bool + IsOnBackgroundThread() + { + return PR_GetCurrentThread() == sBackgroundPRThread; + } + + static void + AssertIsOnBackgroundThread() + { + THREADSAFETY_ASSERT(IsOnBackgroundThread()); + } + + NS_INLINE_DECL_REFCOUNTING(ParentImpl) + + 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 PBackgroundParent* + Alloc(ContentParent* aContent, + Transport* aTransport, + ProcessId aOtherPid); + + 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. + explicit ParentImpl(ContentParent* aContent) + : mContent(aContent), mLiveActorArray(nullptr), + mIsOtherProcessActor(true), mActorDestroyed(false) + { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(aContent); + } + + ~ParentImpl() + { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(!mContent); + } + + void + MainThreadActorDestroy(); + + void + SetLiveActorArray(nsTArray<ParentImpl*>* aLiveActorArray) + { + AssertIsInMainProcess(); + 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; + class CreateActorRunnable; + class ParentCreateCallback; + class AlreadyCreatedCallbackRunnable; + class FailedCreateCallbackRunnable; + class OpenChildProcessActorRunnable; + class OpenMainProcessActorRunnable; + + // A thread-local index that is not valid. + static const unsigned int kBadThreadLocalIndex = + static_cast<unsigned int>(-1); + + // This is only modified on the main thread. It is the thread-local index that + // we use to store the BackgroundChild for each thread. + static unsigned int sThreadLocalIndex; + + struct ThreadLocalInfo + { + explicit ThreadLocalInfo(nsIIPCBackgroundChildCreateCallback* aCallback) +#ifdef DEBUG + : mClosed(false) +#endif + { + mCallbacks.AppendElement(aCallback); + } + + RefPtr<ChildImpl> mActor; + nsTArray<nsCOMPtr<nsIIPCBackgroundChildCreateCallback>> mCallbacks; + nsAutoPtr<BackgroundChildImpl::ThreadLocal> mConsumerThreadLocal; +#ifdef DEBUG + bool mClosed; +#endif + }; + + // This is only modified on the main thread. It is a FIFO queue for actors + // that are in the process of construction. + static StaticAutoPtr<nsTArray<nsCOMPtr<nsIEventTarget>>> sPendingTargets; + + // 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) + nsIThread* mBoundThread; +#endif + +#ifdef DEBUG + bool mActorDestroyed; +#endif + +public: + static bool + OpenProtocolOnMainThread(nsIEventTarget* aEventTarget); + + static void + Shutdown(); + + void + AssertIsOnBoundThread() + { + THREADSAFETY_ASSERT(mBoundThread); + +#ifdef RELEASE_OR_BETA + DebugOnly<bool> current; +#else + bool current; +#endif + THREADSAFETY_ASSERT( + NS_SUCCEEDED(mBoundThread->IsOnCurrentThread(¤t))); + THREADSAFETY_ASSERT(current); + } + + void + AssertActorDestroyed() + { + MOZ_ASSERT(mActorDestroyed, "ChildImpl::ActorDestroy not called in time"); + } + + ChildImpl() +#if defined(DEBUG) || !defined(RELEASE_OR_BETA) + : mBoundThread(nullptr) +#endif +#ifdef DEBUG + , mActorDestroyed(false) +#endif + { + AssertIsOnMainThread(); + } + + NS_INLINE_DECL_REFCOUNTING(ChildImpl) + +private: + // Forwarded from BackgroundChild. + static void + Startup(); + + // Forwarded from BackgroundChild. + static PBackgroundChild* + Alloc(Transport* aTransport, ProcessId aOtherPid); + + // Forwarded from BackgroundChild. + static PBackgroundChild* + GetForCurrentThread(); + + // Forwarded from BackgroundChild. + static bool + GetOrCreateForCurrentThread(nsIIPCBackgroundChildCreateCallback* aCallback); + + // Forwarded from BackgroundChild. + static PBackgroundChild* + SynchronouslyCreateForCurrentThread(); + + // Forwarded from BackgroundChild. + static void + CloseForCurrentThread(); + + // Forwarded from BackgroundChildImpl. + static BackgroundChildImpl::ThreadLocal* + GetThreadLocalForCurrentThread(); + + static void + ThreadLocalDestructor(void* aThreadLocal) + { + auto threadLocalInfo = static_cast<ThreadLocalInfo*>(aThreadLocal); + + if (threadLocalInfo) { + MOZ_ASSERT(threadLocalInfo->mClosed); + + if (threadLocalInfo->mActor) { + threadLocalInfo->mActor->Close(); + threadLocalInfo->mActor->AssertActorDestroyed(); + + // Since the actor is created on the main thread it must only + // be released on the main thread as well. + if (!NS_IsMainThread()) { + ChildImpl* actor; + threadLocalInfo->mActor.forget(&actor); + + MOZ_ALWAYS_SUCCEEDS( + NS_DispatchToMainThread(NewNonOwningRunnableMethod(actor, &ChildImpl::Release))); + } + } + delete threadLocalInfo; + } + } + + static void + DispatchFailureCallback(nsIEventTarget* aEventTarget); + + // This class is reference counted. + ~ChildImpl() + { + AssertActorDestroyed(); + } + + void + SetBoundThread() + { + THREADSAFETY_ASSERT(!mBoundThread); + +#if defined(DEBUG) || !defined(RELEASE_OR_BETA) + mBoundThread = NS_GetCurrentThread(); +#endif + + THREADSAFETY_ASSERT(mBoundThread); + } + + // Only called by IPDL. + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + static already_AddRefed<nsIIPCBackgroundChildCreateCallback> + GetNextCallback(); +}; + +// ----------------------------------------------------------------------------- +// ParentImpl Helper Declarations +// ----------------------------------------------------------------------------- + +class ParentImpl::ShutdownObserver final : public nsIObserver +{ +public: + ShutdownObserver() + { + AssertIsOnMainThread(); + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + +private: + ~ShutdownObserver() + { + AssertIsOnMainThread(); + } +}; + +class ParentImpl::RequestMessageLoopRunnable final : public Runnable +{ + nsCOMPtr<nsIThread> mTargetThread; + MessageLoop* mMessageLoop; + +public: + explicit RequestMessageLoopRunnable(nsIThread* aTargetThread) + : mTargetThread(aTargetThread), mMessageLoop(nullptr) + { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(aTargetThread); + } + +private: + ~RequestMessageLoopRunnable() + { } + + NS_DECL_NSIRUNNABLE +}; + +class ParentImpl::ShutdownBackgroundThreadRunnable final : public Runnable +{ +public: + ShutdownBackgroundThreadRunnable() + { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + } + +private: + ~ShutdownBackgroundThreadRunnable() + { } + + NS_DECL_NSIRUNNABLE +}; + +class ParentImpl::ForceCloseBackgroundActorsRunnable final : public Runnable +{ + nsTArray<ParentImpl*>* mActorArray; + +public: + explicit ForceCloseBackgroundActorsRunnable(nsTArray<ParentImpl*>* aActorArray) + : mActorArray(aActorArray) + { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(aActorArray); + } + +private: + ~ForceCloseBackgroundActorsRunnable() + { } + + NS_DECL_NSIRUNNABLE +}; + +class ParentImpl::CreateCallbackRunnable final : public Runnable +{ + RefPtr<CreateCallback> mCallback; + +public: + explicit CreateCallbackRunnable(CreateCallback* aCallback) + : mCallback(aCallback) + { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(aCallback); + } + +private: + ~CreateCallbackRunnable() + { } + + NS_DECL_NSIRUNNABLE +}; + +class ParentImpl::ConnectActorRunnable final : public Runnable +{ + RefPtr<ParentImpl> mActor; + Transport* mTransport; + ProcessId mOtherPid; + nsTArray<ParentImpl*>* mLiveActorArray; + +public: + ConnectActorRunnable(ParentImpl* aActor, + Transport* aTransport, + ProcessId aOtherPid, + nsTArray<ParentImpl*>* aLiveActorArray) + : mActor(aActor), mTransport(aTransport), mOtherPid(aOtherPid), + mLiveActorArray(aLiveActorArray) + { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(aActor); + MOZ_ASSERT(aTransport); + MOZ_ASSERT(aLiveActorArray); + } + +private: + ~ConnectActorRunnable() + { + AssertIsInMainProcess(); + } + + NS_DECL_NSIRUNNABLE +}; + +class NS_NO_VTABLE ParentImpl::CreateCallback +{ +public: + NS_INLINE_DECL_REFCOUNTING(CreateCallback) + + virtual void + Success(already_AddRefed<ParentImpl> aActor, MessageLoop* aMessageLoop) = 0; + + virtual void + Failure() = 0; + +protected: + virtual ~CreateCallback() + { } +}; + +// ----------------------------------------------------------------------------- +// ChildImpl Helper Declarations +// ----------------------------------------------------------------------------- + +class ChildImpl::ShutdownObserver final : public nsIObserver +{ +public: + ShutdownObserver() + { + AssertIsOnMainThread(); + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + +private: + ~ShutdownObserver() + { + AssertIsOnMainThread(); + } +}; + +class ChildImpl::CreateActorRunnable final : public Runnable +{ + nsCOMPtr<nsIEventTarget> mEventTarget; + +public: + CreateActorRunnable() + : mEventTarget(NS_GetCurrentThread()) + { + MOZ_ASSERT(mEventTarget); + } + +private: + ~CreateActorRunnable() + { } + + NS_DECL_NSIRUNNABLE +}; + +class ChildImpl::ParentCreateCallback final : + public ParentImpl::CreateCallback +{ + nsCOMPtr<nsIEventTarget> mEventTarget; + +public: + explicit ParentCreateCallback(nsIEventTarget* aEventTarget) + : mEventTarget(aEventTarget) + { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(aEventTarget); + } + +private: + ~ParentCreateCallback() + { } + + virtual void + Success(already_AddRefed<ParentImpl> aActor, MessageLoop* aMessageLoop) + override; + + virtual void + Failure() override; +}; + +// Must be cancelable in order to dispatch on active worker threads +class ChildImpl::AlreadyCreatedCallbackRunnable final : + public CancelableRunnable +{ +public: + AlreadyCreatedCallbackRunnable() + { + // May be created on any thread! + } + +protected: + virtual ~AlreadyCreatedCallbackRunnable() + { } + + NS_DECL_NSIRUNNABLE + nsresult Cancel() override; +}; + +class ChildImpl::FailedCreateCallbackRunnable final : public Runnable +{ +public: + FailedCreateCallbackRunnable() + { + // May be created on any thread! + } + +protected: + virtual ~FailedCreateCallbackRunnable() + { } + + NS_DECL_NSIRUNNABLE +}; + +class ChildImpl::OpenChildProcessActorRunnable final : public Runnable +{ + RefPtr<ChildImpl> mActor; + nsAutoPtr<Transport> mTransport; + ProcessId mOtherPid; + +public: + OpenChildProcessActorRunnable(already_AddRefed<ChildImpl>&& aActor, + Transport* aTransport, + ProcessId aOtherPid) + : mActor(aActor), mTransport(aTransport), + mOtherPid(aOtherPid) + { + AssertIsOnMainThread(); + MOZ_ASSERT(mActor); + MOZ_ASSERT(aTransport); + } + +private: + ~OpenChildProcessActorRunnable() + { + if (mTransport) { + CRASH_IN_CHILD_PROCESS("Leaking transport!"); + Unused << mTransport.forget(); + } + } + + NS_DECL_NSIRUNNABLE +}; + +class ChildImpl::OpenMainProcessActorRunnable final : public Runnable +{ + RefPtr<ChildImpl> mActor; + RefPtr<ParentImpl> mParentActor; + MessageLoop* mParentMessageLoop; + +public: + OpenMainProcessActorRunnable(already_AddRefed<ChildImpl>&& aChildActor, + already_AddRefed<ParentImpl> aParentActor, + MessageLoop* aParentMessageLoop) + : mActor(aChildActor), mParentActor(aParentActor), + mParentMessageLoop(aParentMessageLoop) + { + AssertIsOnMainThread(); + MOZ_ASSERT(mParentActor); + MOZ_ASSERT(aParentMessageLoop); + } + +private: + ~OpenMainProcessActorRunnable() + { } + + 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 +PBlobParent* +BackgroundParent::GetOrCreateActorForBlobImpl( + PBackgroundParent* aBackgroundActor, + BlobImpl* aBlobImpl) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aBackgroundActor); + MOZ_ASSERT(aBlobImpl); + + BlobParent* actor = BlobParent::GetOrCreate(aBackgroundActor, aBlobImpl); + if (NS_WARN_IF(!actor)) { + return nullptr; + } + + return actor; +} + +// static +intptr_t +BackgroundParent::GetRawContentParentForComparison( + PBackgroundParent* aBackgroundActor) +{ + return ParentImpl::GetRawContentParentForComparison(aBackgroundActor); +} + +// static +PBackgroundParent* +BackgroundParent::Alloc(ContentParent* aContent, + Transport* aTransport, + ProcessId aOtherPid) +{ + return ParentImpl::Alloc(aContent, aTransport, aOtherPid); +} + +// ----------------------------------------------------------------------------- +// BackgroundChild Public Methods +// ----------------------------------------------------------------------------- + +// static +void +BackgroundChild::Startup() +{ + ChildImpl::Startup(); +} + +// static +PBackgroundChild* +BackgroundChild::Alloc(Transport* aTransport, ProcessId aOtherPid) +{ + return ChildImpl::Alloc(aTransport, aOtherPid); +} + +// static +PBackgroundChild* +BackgroundChild::GetForCurrentThread() +{ + return ChildImpl::GetForCurrentThread(); +} + +// static +bool +BackgroundChild::GetOrCreateForCurrentThread( + nsIIPCBackgroundChildCreateCallback* aCallback) +{ + return ChildImpl::GetOrCreateForCurrentThread(aCallback); +} + +// static +PBackgroundChild* +BackgroundChild::SynchronouslyCreateForCurrentThread() +{ + return ChildImpl::SynchronouslyCreateForCurrentThread(); +} + +// static +PBlobChild* +BackgroundChild::GetOrCreateActorForBlob(PBackgroundChild* aBackgroundActor, + nsIDOMBlob* aBlob) +{ + MOZ_ASSERT(aBlob); + + RefPtr<BlobImpl> blobImpl = static_cast<Blob*>(aBlob)->Impl(); + MOZ_ASSERT(blobImpl); + + return GetOrCreateActorForBlobImpl(aBackgroundActor, blobImpl); +} + +// static +PBlobChild* +BackgroundChild::GetOrCreateActorForBlobImpl(PBackgroundChild* aBackgroundActor, + BlobImpl* aBlobImpl) +{ + MOZ_ASSERT(aBackgroundActor); + MOZ_ASSERT(aBlobImpl); + MOZ_ASSERT(GetForCurrentThread(), + "BackgroundChild not created on this thread yet!"); + MOZ_ASSERT(aBackgroundActor == GetForCurrentThread(), + "BackgroundChild is bound to a different thread!"); + + BlobChild* actor = BlobChild::GetOrCreate(aBackgroundActor, aBlobImpl); + if (NS_WARN_IF(!actor)) { + return nullptr; + } + + return actor; +} + +// 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; + +MessageLoop* ParentImpl::sBackgroundThreadMessageLoop = nullptr; + +uint64_t ParentImpl::sLiveActorCount = 0; + +bool ParentImpl::sShutdownObserverRegistered = false; + +bool ParentImpl::sShutdownHasStarted = false; + +StaticAutoPtr<nsTArray<RefPtr<ParentImpl::CreateCallback>>> + ParentImpl::sPendingCallbacks; + +// ----------------------------------------------------------------------------- +// ChildImpl Static Members +// ----------------------------------------------------------------------------- + +unsigned int ChildImpl::sThreadLocalIndex = kBadThreadLocalIndex; + +StaticAutoPtr<nsTArray<nsCOMPtr<nsIEventTarget>>> ChildImpl::sPendingTargets; + +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(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<nsIContentParent*>(actor->mContent.get())); +} + +// static +PBackgroundParent* +ParentImpl::Alloc(ContentParent* aContent, + Transport* aTransport, + ProcessId aOtherPid) +{ + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(aTransport); + + if (!sBackgroundThread && !CreateBackgroundThread()) { + NS_WARNING("Failed to create background thread!"); + return nullptr; + } + + MOZ_ASSERT(sLiveActorsForBackgroundThread); + + sLiveActorCount++; + + RefPtr<ParentImpl> actor = new ParentImpl(aContent); + + nsCOMPtr<nsIRunnable> connectRunnable = + new ConnectActorRunnable(actor, aTransport, aOtherPid, + sLiveActorsForBackgroundThread); + + if (NS_FAILED(sBackgroundThread->Dispatch(connectRunnable, + NS_DISPATCH_NORMAL))) { + NS_WARNING("Failed to dispatch connect runnable!"); + + MOZ_ASSERT(sLiveActorCount); + sLiveActorCount--; + + return nullptr; + } + + return actor; +} + +// static +bool +ParentImpl::CreateActorForSameProcess(CreateCallback* aCallback) +{ + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(aCallback); + + if (!sBackgroundThread && !CreateBackgroundThread()) { + NS_WARNING("Failed to create background thread!"); + return false; + } + + MOZ_ASSERT(!sShutdownHasStarted); + + sLiveActorCount++; + + if (sBackgroundThreadMessageLoop) { + nsCOMPtr<nsIRunnable> callbackRunnable = + new CreateCallbackRunnable(aCallback); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(callbackRunnable)); + return true; + } + + if (!sPendingCallbacks) { + sPendingCallbacks = new nsTArray<RefPtr<CreateCallback>>(); + } + + sPendingCallbacks->AppendElement(aCallback); + return true; +} + +// static +bool +ParentImpl::CreateBackgroundThread() +{ + AssertIsInMainProcess(); + 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) { + nsresult rv; + newShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + 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_WARNING("NS_NewNamedThread failed!"); + return false; + } + + nsCOMPtr<nsIRunnable> messageLoopRunnable = + new RequestMessageLoopRunnable(thread); + if (NS_FAILED(thread->Dispatch(messageLoopRunnable, NS_DISPATCH_NORMAL))) { + NS_WARNING("Failed to dispatch RequestMessageLoopRunnable!"); + return false; + } + + sBackgroundThread = thread; + sLiveActorsForBackgroundThread = new nsTArray<ParentImpl*>(1); + + if (!sShutdownTimer) { + MOZ_ASSERT(newShutdownTimer); + sShutdownTimer = newShutdownTimer; + } + + return true; +} + +// static +void +ParentImpl::ShutdownBackgroundThread() +{ + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT_IF(!sBackgroundThread, !sBackgroundThreadMessageLoop); + MOZ_ASSERT(sShutdownHasStarted); + MOZ_ASSERT_IF(!sBackgroundThread, !sLiveActorCount); + MOZ_ASSERT_IF(sBackgroundThread, sShutdownTimer); + + if (sPendingCallbacks) { + if (!sPendingCallbacks->IsEmpty()) { + nsTArray<RefPtr<CreateCallback>> callbacks; + sPendingCallbacks->SwapElements(callbacks); + + for (uint32_t index = 0; index < callbacks.Length(); index++) { + RefPtr<CreateCallback> callback; + callbacks[index].swap(callback); + MOZ_ASSERT(callback); + + callback->Failure(); + } + } + + sPendingCallbacks = nullptr; + } + + nsCOMPtr<nsITimer> shutdownTimer = sShutdownTimer.get(); + sShutdownTimer = nullptr; + + if (sBackgroundThread) { + nsCOMPtr<nsIThread> thread = sBackgroundThread.get(); + sBackgroundThread = nullptr; + + nsAutoPtr<nsTArray<ParentImpl*>> liveActors(sLiveActorsForBackgroundThread); + sLiveActorsForBackgroundThread = nullptr; + + sBackgroundThreadMessageLoop = 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); + + MOZ_ALWAYS_SUCCEEDS( + shutdownTimer->InitWithFuncCallback(&ShutdownTimerCallback, + &closure, + kShutdownTimerDelayMS, + nsITimer::TYPE_ONE_SHOT)); + + nsIThread* currentThread = NS_GetCurrentThread(); + MOZ_ASSERT(currentThread); + + while (sLiveActorCount) { + NS_ProcessNextEvent(currentThread); + } + + MOZ_ASSERT(liveActors->IsEmpty()); + + MOZ_ALWAYS_SUCCEEDS(shutdownTimer->Cancel()); + } + + // Dispatch this runnable to unregister the thread from the profiler. + nsCOMPtr<nsIRunnable> shutdownRunnable = + new ShutdownBackgroundThreadRunnable(); + MOZ_ALWAYS_SUCCEEDS(thread->Dispatch(shutdownRunnable, NS_DISPATCH_NORMAL)); + + MOZ_ALWAYS_SUCCEEDS(thread->Shutdown()); + } +} + +// static +void +ParentImpl::ShutdownTimerCallback(nsITimer* aTimer, void* aClosure) +{ + AssertIsInMainProcess(); + 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++; + + nsCOMPtr<nsIRunnable> forceCloseRunnable = + new ForceCloseBackgroundActorsRunnable(closure->mLiveActors); + MOZ_ALWAYS_SUCCEEDS(closure->mThread->Dispatch(forceCloseRunnable, + NS_DISPATCH_NORMAL)); +} + +void +ParentImpl::Destroy() +{ + // May be called on any thread! + + AssertIsInMainProcess(); + + MOZ_ALWAYS_SUCCEEDS( + NS_DispatchToMainThread(NewNonOwningRunnableMethod(this, &ParentImpl::MainThreadActorDestroy))); +} + +void +ParentImpl::MainThreadActorDestroy() +{ + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT_IF(mIsOtherProcessActor, mContent); + MOZ_ASSERT_IF(!mIsOtherProcessActor, !mContent); + + mContent = nullptr; + + MOZ_ASSERT(sLiveActorCount); + sLiveActorCount--; + + // This may be the last reference! + Release(); +} + +void +ParentImpl::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsInMainProcess(); + 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(this, &ParentImpl::Destroy))); +} + +NS_IMPL_ISUPPORTS(ParentImpl::ShutdownObserver, nsIObserver) + +NS_IMETHODIMP +ParentImpl::ShutdownObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + AssertIsInMainProcess(); + 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; +} + +NS_IMETHODIMP +ParentImpl::RequestMessageLoopRunnable::Run() +{ + AssertIsInMainProcess(); + MOZ_ASSERT(mTargetThread); + + char stackBaseGuess; + + if (NS_IsMainThread()) { + MOZ_ASSERT(mMessageLoop); + + if (!sBackgroundThread || + !SameCOMIdentity(mTargetThread.get(), sBackgroundThread.get())) { + return NS_OK; + } + + MOZ_ASSERT(!sBackgroundThreadMessageLoop); + sBackgroundThreadMessageLoop = mMessageLoop; + + if (sPendingCallbacks && !sPendingCallbacks->IsEmpty()) { + nsTArray<RefPtr<CreateCallback>> callbacks; + sPendingCallbacks->SwapElements(callbacks); + + for (uint32_t index = 0; index < callbacks.Length(); index++) { + MOZ_ASSERT(callbacks[index]); + + nsCOMPtr<nsIRunnable> callbackRunnable = + new CreateCallbackRunnable(callbacks[index]); + if (NS_FAILED(NS_DispatchToCurrentThread(callbackRunnable))) { + NS_WARNING("Failed to dispatch callback runnable!"); + } + } + } + + return NS_OK; + } + + profiler_register_thread("IPDL Background", &stackBaseGuess); + +#ifdef DEBUG + { + bool correctThread; + MOZ_ASSERT(NS_SUCCEEDED(mTargetThread->IsOnCurrentThread(&correctThread))); + MOZ_ASSERT(correctThread); + } +#endif + + DebugOnly<PRThread*> oldBackgroundThread = + sBackgroundPRThread.exchange(PR_GetCurrentThread()); + + MOZ_ASSERT_IF(oldBackgroundThread, + PR_GetCurrentThread() != oldBackgroundThread); + + MOZ_ASSERT(!mMessageLoop); + + mMessageLoop = MessageLoop::current(); + MOZ_ASSERT(mMessageLoop); + + if (NS_FAILED(NS_DispatchToMainThread(this))) { + NS_WARNING("Failed to dispatch RequestMessageLoopRunnable to main thread!"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +ParentImpl::ShutdownBackgroundThreadRunnable::Run() +{ + AssertIsInMainProcess(); + + // 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); + + profiler_unregister_thread(); + + return NS_OK; +} + +NS_IMETHODIMP +ParentImpl::ForceCloseBackgroundActorsRunnable::Run() +{ + AssertIsInMainProcess(); + MOZ_ASSERT(mActorArray); + + if (NS_IsMainThread()) { + MOZ_ASSERT(sLiveActorCount); + sLiveActorCount--; + return NS_OK; + } + + AssertIsOnBackgroundThread(); + + if (!mActorArray->IsEmpty()) { + // Copy the array since calling Close() could mutate the actual array. + nsTArray<ParentImpl*> actorsToClose(*mActorArray); + + for (uint32_t index = 0; index < actorsToClose.Length(); index++) { + actorsToClose[index]->Close(); + } + } + + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); + + return NS_OK; +} + +NS_IMETHODIMP +ParentImpl::CreateCallbackRunnable::Run() +{ + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(sBackgroundThreadMessageLoop); + MOZ_ASSERT(mCallback); + + RefPtr<CreateCallback> callback; + mCallback.swap(callback); + + RefPtr<ParentImpl> actor = new ParentImpl(); + + callback->Success(actor.forget(), sBackgroundThreadMessageLoop); + + return NS_OK; +} + +NS_IMETHODIMP +ParentImpl::ConnectActorRunnable::Run() +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + // Transfer ownership to this thread. If Open() fails then we will release + // this reference in Destroy. + ParentImpl* actor; + mActor.forget(&actor); + + if (!actor->Open(mTransport, mOtherPid, XRE_GetIOMessageLoop(), ParentSide)) { + actor->Destroy(); + return NS_ERROR_FAILURE; + } + + actor->SetLiveActorArray(mLiveActorArray); + + 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. + + MOZ_ASSERT(sThreadLocalIndex == kBadThreadLocalIndex, + "BackgroundChild::Startup() called more than once!"); + + PRStatus status = + PR_NewThreadPrivateIndex(&sThreadLocalIndex, ThreadLocalDestructor); + MOZ_RELEASE_ASSERT(status == PR_SUCCESS, "PR_NewThreadPrivateIndex failed!"); + + MOZ_ASSERT(sThreadLocalIndex != kBadThreadLocalIndex); + + 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(); + + if (sShutdownHasStarted) { + MOZ_ASSERT_IF(sThreadLocalIndex != kBadThreadLocalIndex, + !PR_GetThreadPrivate(sThreadLocalIndex)); + return; + } + + sShutdownHasStarted = true; + +#ifdef DEBUG + MOZ_ASSERT(sThreadLocalIndex != kBadThreadLocalIndex); + + auto threadLocalInfo = + static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(sThreadLocalIndex)); + + if (threadLocalInfo) { + MOZ_ASSERT(!threadLocalInfo->mClosed); + threadLocalInfo->mClosed = true; + } +#endif + + DebugOnly<PRStatus> status = PR_SetThreadPrivate(sThreadLocalIndex, nullptr); + MOZ_ASSERT(status == PR_SUCCESS); +} + +// static +PBackgroundChild* +ChildImpl::Alloc(Transport* aTransport, ProcessId aOtherPid) +{ + AssertIsInChildProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(aTransport); + MOZ_ASSERT(sPendingTargets); + MOZ_ASSERT(!sPendingTargets->IsEmpty()); + + nsCOMPtr<nsIEventTarget> eventTarget; + sPendingTargets->ElementAt(0).swap(eventTarget); + + sPendingTargets->RemoveElementAt(0); + + RefPtr<ChildImpl> actor = new ChildImpl(); + + ChildImpl* weakActor = actor; + + nsCOMPtr<nsIRunnable> openRunnable = + new OpenChildProcessActorRunnable(actor.forget(), aTransport, + aOtherPid); + if (NS_FAILED(eventTarget->Dispatch(openRunnable, NS_DISPATCH_NORMAL))) { + MOZ_CRASH("Failed to dispatch OpenActorRunnable!"); + } + + // This value is only checked against null to determine success/failure, so + // there is no need to worry about the reference count here. + return weakActor; +} + +// static +PBackgroundChild* +ChildImpl::GetForCurrentThread() +{ + MOZ_ASSERT(sThreadLocalIndex != kBadThreadLocalIndex); + + auto threadLocalInfo = + static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(sThreadLocalIndex)); + + if (!threadLocalInfo) { + return nullptr; + } + + return threadLocalInfo->mActor; +} + +// static +bool +ChildImpl::GetOrCreateForCurrentThread( + nsIIPCBackgroundChildCreateCallback* aCallback) +{ + MOZ_ASSERT(aCallback); + MOZ_ASSERT(sThreadLocalIndex != kBadThreadLocalIndex, + "BackgroundChild::Startup() was never called!"); + + bool created = false; + + auto threadLocalInfo = + static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(sThreadLocalIndex)); + + if (threadLocalInfo) { + threadLocalInfo->mCallbacks.AppendElement(aCallback); + } else { + nsAutoPtr<ThreadLocalInfo> newInfo(new ThreadLocalInfo(aCallback)); + + if (PR_SetThreadPrivate(sThreadLocalIndex, newInfo) != PR_SUCCESS) { + CRASH_IN_CHILD_PROCESS("PR_SetThreadPrivate failed!"); + return false; + } + + created = true; + threadLocalInfo = newInfo.forget(); + } + + if (threadLocalInfo->mActor) { + // Runnable will use GetForCurrentThread() to retrieve actor again. This + // allows us to avoid addref'ing on the wrong thread. + nsCOMPtr<nsIRunnable> runnable = new AlreadyCreatedCallbackRunnable(); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(runnable)); + + return true; + } + + if (!created) { + // We have already started the sequence for opening the actor so there's + // nothing else we need to do here. This callback will be called after the + // first callback in the schedule runnable. + return true; + } + + if (NS_IsMainThread()) { + if (NS_WARN_IF(!OpenProtocolOnMainThread(NS_GetCurrentThread()))) { + return false; + } + + return true; + } + + RefPtr<CreateActorRunnable> runnable = new CreateActorRunnable(); + if (NS_FAILED(NS_DispatchToMainThread(runnable))) { + CRASH_IN_CHILD_PROCESS("Failed to dispatch to main thread!"); + return false; + } + + return true; +} + +namespace { + +class Callback final : public nsIIPCBackgroundChildCreateCallback +{ + bool* mDone; + +public: + explicit Callback(bool* aDone) + : mDone(aDone) + { + MOZ_ASSERT(mDone); + } + + NS_DECL_ISUPPORTS + +private: + ~Callback() + { } + + virtual void + ActorCreated(PBackgroundChild* aActor) override + { + *mDone = true; + } + + virtual void + ActorFailed() override + { + *mDone = true; + } +}; + +NS_IMPL_ISUPPORTS(Callback, nsIIPCBackgroundChildCreateCallback) + +} // anonymous namespace + +/* static */ +PBackgroundChild* +ChildImpl::SynchronouslyCreateForCurrentThread() +{ + MOZ_ASSERT(!GetForCurrentThread()); + + bool done = false; + nsCOMPtr<nsIIPCBackgroundChildCreateCallback> callback = new Callback(&done); + + if (NS_WARN_IF(!GetOrCreateForCurrentThread(callback))) { + return nullptr; + } + + nsIThread* currentThread = NS_GetCurrentThread(); + MOZ_ASSERT(currentThread); + + while (!done) { + if (NS_WARN_IF(!NS_ProcessNextEvent(currentThread, true /* aMayWait */))) { + return nullptr; + } + } + + return GetForCurrentThread(); +} + +// static +void +ChildImpl::CloseForCurrentThread() +{ + if (sThreadLocalIndex == kBadThreadLocalIndex) { + return; + } + + auto threadLocalInfo = + static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(sThreadLocalIndex)); + + 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(sThreadLocalIndex, nullptr); + MOZ_ASSERT(status == PR_SUCCESS); +} + +// static +BackgroundChildImpl::ThreadLocal* +ChildImpl::GetThreadLocalForCurrentThread() +{ + MOZ_ASSERT(sThreadLocalIndex != kBadThreadLocalIndex, + "BackgroundChild::Startup() was never called!"); + + auto threadLocalInfo = + static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(sThreadLocalIndex)); + + if (!threadLocalInfo) { + return nullptr; + } + + if (!threadLocalInfo->mConsumerThreadLocal) { + threadLocalInfo->mConsumerThreadLocal = + new BackgroundChildImpl::ThreadLocal(); + } + + return threadLocalInfo->mConsumerThreadLocal; +} + +// static +already_AddRefed<nsIIPCBackgroundChildCreateCallback> +ChildImpl::GetNextCallback() +{ + // May run on any thread! + + auto threadLocalInfo = + static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(sThreadLocalIndex)); + MOZ_ASSERT(threadLocalInfo); + + if (threadLocalInfo->mCallbacks.IsEmpty()) { + return nullptr; + } + + nsCOMPtr<nsIIPCBackgroundChildCreateCallback> callback; + threadLocalInfo->mCallbacks[0].swap(callback); + + threadLocalInfo->mCallbacks.RemoveElementAt(0); + + return callback.forget(); +} + +NS_IMETHODIMP +ChildImpl::AlreadyCreatedCallbackRunnable::Run() +{ + // May run on any thread! + + // Report the current actor back in the callback. + PBackgroundChild* actor = ChildImpl::GetForCurrentThread(); + + // If the current actor is null, do not create a new actor here. This likely + // means we are in the process of cleaning up a worker thread and do not want + // a new actor created. Unfortunately we cannot report back to the callback + // because the thread local is gone at this point. Instead simply do nothing + // and return. + if (NS_WARN_IF(!actor)) { + return NS_OK; + } + + nsCOMPtr<nsIIPCBackgroundChildCreateCallback> callback = + ChildImpl::GetNextCallback(); + while (callback) { + callback->ActorCreated(actor); + callback = ChildImpl::GetNextCallback(); + } + + return NS_OK; +} + +nsresult +ChildImpl::AlreadyCreatedCallbackRunnable::Cancel() +{ + // These are IPC infrastructure objects and need to run unconditionally. + Run(); + return NS_OK; +} + +NS_IMETHODIMP +ChildImpl::FailedCreateCallbackRunnable::Run() +{ + // May run on any thread! + + nsCOMPtr<nsIIPCBackgroundChildCreateCallback> callback = + ChildImpl::GetNextCallback(); + while (callback) { + callback->ActorFailed(); + callback = ChildImpl::GetNextCallback(); + } + + return NS_OK; +} + +NS_IMETHODIMP +ChildImpl::OpenChildProcessActorRunnable::Run() +{ + // May be run on any thread! + + AssertIsInChildProcess(); + MOZ_ASSERT(mActor); + MOZ_ASSERT(mTransport); + + nsCOMPtr<nsIIPCBackgroundChildCreateCallback> callback = + ChildImpl::GetNextCallback(); + MOZ_ASSERT(callback, + "There should be at least one callback when first creating the " + "actor!"); + + RefPtr<ChildImpl> strongActor; + mActor.swap(strongActor); + + if (!strongActor->Open(mTransport.forget(), mOtherPid, + XRE_GetIOMessageLoop(), ChildSide)) { + CRASH_IN_CHILD_PROCESS("Failed to open ChildImpl!"); + + while (callback) { + callback->ActorFailed(); + callback = ChildImpl::GetNextCallback(); + } + + return NS_OK; + } + + // Now that Open() has succeeded transfer the ownership of the actor to IPDL. + auto threadLocalInfo = + static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(sThreadLocalIndex)); + + MOZ_ASSERT(threadLocalInfo); + MOZ_ASSERT(!threadLocalInfo->mActor); + + RefPtr<ChildImpl>& actor = threadLocalInfo->mActor; + strongActor.swap(actor); + + actor->SetBoundThread(); + + while (callback) { + callback->ActorCreated(actor); + callback = ChildImpl::GetNextCallback(); + } + + return NS_OK; +} + +NS_IMETHODIMP +ChildImpl::OpenMainProcessActorRunnable::Run() +{ + // May run on any thread! + + AssertIsInMainProcess(); + MOZ_ASSERT(mActor); + MOZ_ASSERT(mParentActor); + MOZ_ASSERT(mParentMessageLoop); + + nsCOMPtr<nsIIPCBackgroundChildCreateCallback> callback = + ChildImpl::GetNextCallback(); + MOZ_ASSERT(callback, + "There should be at least one callback when first creating the " + "actor!"); + + RefPtr<ChildImpl> strongChildActor; + mActor.swap(strongChildActor); + + RefPtr<ParentImpl> parentActor; + mParentActor.swap(parentActor); + + MessageChannel* parentChannel = parentActor->GetIPCChannel(); + MOZ_ASSERT(parentChannel); + + if (!strongChildActor->Open(parentChannel, mParentMessageLoop, ChildSide)) { + NS_WARNING("Failed to open ChildImpl!"); + + parentActor->Destroy(); + + while (callback) { + callback->ActorFailed(); + callback = ChildImpl::GetNextCallback(); + } + + return NS_OK; + } + + // 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(); + + auto threadLocalInfo = + static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(sThreadLocalIndex)); + + MOZ_ASSERT(threadLocalInfo); + MOZ_ASSERT(!threadLocalInfo->mActor); + + RefPtr<ChildImpl>& childActor = threadLocalInfo->mActor; + strongChildActor.swap(childActor); + + childActor->SetBoundThread(); + + while (callback) { + callback->ActorCreated(childActor); + callback = ChildImpl::GetNextCallback(); + } + + return NS_OK; +} + +NS_IMETHODIMP +ChildImpl::CreateActorRunnable::Run() +{ + AssertIsOnMainThread(); + + if (!OpenProtocolOnMainThread(mEventTarget)) { + NS_WARNING("OpenProtocolOnMainThread failed!"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void +ChildImpl::ParentCreateCallback::Success( + already_AddRefed<ParentImpl> aParentActor, + MessageLoop* aParentMessageLoop) +{ + AssertIsInMainProcess(); + AssertIsOnMainThread(); + + RefPtr<ParentImpl> parentActor = aParentActor; + MOZ_ASSERT(parentActor); + MOZ_ASSERT(aParentMessageLoop); + MOZ_ASSERT(mEventTarget); + + RefPtr<ChildImpl> childActor = new ChildImpl(); + + nsCOMPtr<nsIEventTarget> target; + mEventTarget.swap(target); + + nsCOMPtr<nsIRunnable> openRunnable = + new OpenMainProcessActorRunnable(childActor.forget(), parentActor.forget(), + aParentMessageLoop); + if (NS_FAILED(target->Dispatch(openRunnable, NS_DISPATCH_NORMAL))) { + NS_WARNING("Failed to dispatch open runnable!"); + } +} + +void +ChildImpl::ParentCreateCallback::Failure() +{ + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(mEventTarget); + + nsCOMPtr<nsIEventTarget> target; + mEventTarget.swap(target); + + DispatchFailureCallback(target); +} + +// static +bool +ChildImpl::OpenProtocolOnMainThread(nsIEventTarget* aEventTarget) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aEventTarget); + + if (sShutdownHasStarted) { + MOZ_CRASH("Called BackgroundChild::GetOrCreateForCurrentThread after " + "shutdown has started!"); + } + + if (XRE_IsParentProcess()) { + RefPtr<ParentImpl::CreateCallback> parentCallback = + new ParentCreateCallback(aEventTarget); + + if (!ParentImpl::CreateActorForSameProcess(parentCallback)) { + NS_WARNING("BackgroundParent::CreateActor() failed!"); + DispatchFailureCallback(aEventTarget); + return false; + } + + return true; + } + + 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. + DispatchFailureCallback(aEventTarget); + return false; + } + + if (!PBackground::Open(content)) { + MOZ_CRASH("Failed to create top level actor!"); + return false; + } + + if (!sPendingTargets) { + sPendingTargets = new nsTArray<nsCOMPtr<nsIEventTarget>>(1); + ClearOnShutdown(&sPendingTargets); + } + + sPendingTargets->AppendElement(aEventTarget); + + return true; +} + +// static +void +ChildImpl::DispatchFailureCallback(nsIEventTarget* aEventTarget) +{ + MOZ_ASSERT(aEventTarget); + + nsCOMPtr<nsIRunnable> callbackRunnable = new FailedCreateCallbackRunnable(); + if (NS_FAILED(aEventTarget->Dispatch(callbackRunnable, NS_DISPATCH_NORMAL))) { + NS_WARNING("Failed to dispatch CreateCallbackRunnable!"); + } +} + +void +ChildImpl::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnBoundThread(); + +#ifdef DEBUG + MOZ_ASSERT(!mActorDestroyed); + mActorDestroyed = true; +#endif + + BackgroundChildImpl::ActorDestroy(aWhy); +} + +NS_IMPL_ISUPPORTS(ChildImpl::ShutdownObserver, nsIObserver) + +NS_IMETHODIMP +ChildImpl::ShutdownObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)); + + ChildImpl::Shutdown(); + + return NS_OK; +} diff --git a/ipc/glue/BackgroundParent.h b/ipc/glue/BackgroundParent.h new file mode 100644 index 000000000..64530af99 --- /dev/null +++ b/ipc/glue/BackgroundParent.h @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ipc_backgroundparent_h__ +#define mozilla_ipc_backgroundparent_h__ + +#include "base/process.h" +#include "mozilla/Attributes.h" +#include "mozilla/ipc/Transport.h" + +template <class> struct already_AddRefed; + +namespace mozilla { +namespace dom { + +class BlobImpl; +class ContentParent; +class PBlobParent; + +} // namespace dom + +namespace ipc { + +class PBackgroundParent; + +// 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; + +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); + + static mozilla::dom::PBlobParent* + GetOrCreateActorForBlobImpl(PBackgroundParent* aBackgroundActor, + BlobImpl* aBlobImpl); + + // 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); + +private: + // Only called by ContentParent for cross-process actors. + static PBackgroundParent* + Alloc(ContentParent* aContent, + Transport* aTransport, + ProcessId aOtherProcess); +}; + +// 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()); +} + +} // 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 000000000..ef5dc1cab --- /dev/null +++ b/ipc/glue/BackgroundParentImpl.cpp @@ -0,0 +1,848 @@ +/* -*- 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/media/MediaParent.h" +#include "mozilla/AppProcessChecker.h" +#include "mozilla/Assertions.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/DOMTypes.h" +#include "mozilla/dom/FileSystemBase.h" +#include "mozilla/dom/FileSystemRequestParent.h" +#ifdef MOZ_GAMEPAD +#include "mozilla/dom/GamepadEventChannelParent.h" +#include "mozilla/dom/GamepadTestChannelParent.h" +#endif +#include "mozilla/dom/PBlobParent.h" +#include "mozilla/dom/PGamepadEventChannelParent.h" +#include "mozilla/dom/PGamepadTestChannelParent.h" +#include "mozilla/dom/MessagePortParent.h" +#include "mozilla/dom/ServiceWorkerRegistrar.h" +#include "mozilla/dom/asmjscache/AsmJSCache.h" +#include "mozilla/dom/cache/ActorUtils.h" +#include "mozilla/dom/indexedDB/ActorsParent.h" +#include "mozilla/dom/ipc/BlobParent.h" +#include "mozilla/dom/quota/ActorsParent.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "mozilla/ipc/PBackgroundTestParent.h" +#include "mozilla/ipc/PSendStreamParent.h" +#include "mozilla/ipc/SendStreamAlloc.h" +#include "mozilla/layout/VsyncParent.h" +#include "mozilla/dom/network/UDPSocketParent.h" +#include "mozilla/Preferences.h" +#include "nsIAppsService.h" +#include "nsNetUtil.h" +#include "nsIScriptSecurityManager.h" +#include "nsProxyRelease.h" +#include "mozilla/RefPtr.h" +#include "nsThreadUtils.h" +#include "nsTraceRefcnt.h" +#include "nsXULAppAPI.h" +#include "ServiceWorkerManagerParent.h" + +#ifdef DISABLE_ASSERTS_FOR_FUZZING +#define ASSERT_UNLESS_FUZZING(...) do { } while (0) +#else +#define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false) +#endif + +using mozilla::ipc::AssertIsOnBackgroundThread; +using mozilla::dom::asmjscache::PAsmJSCacheEntryParent; +using mozilla::dom::cache::PCacheParent; +using mozilla::dom::cache::PCacheStorageParent; +using mozilla::dom::cache::PCacheStreamControlParent; +using mozilla::dom::FileSystemBase; +using mozilla::dom::FileSystemRequestParent; +using mozilla::dom::MessagePortParent; +using mozilla::dom::PMessagePortParent; +using mozilla::dom::UDPSocketParent; + +namespace { + +void +AssertIsOnMainThread() +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +class TestParent final : public mozilla::ipc::PBackgroundTestParent +{ + friend class mozilla::ipc::BackgroundParentImpl; + + TestParent() + { + MOZ_COUNT_CTOR(TestParent); + } + +protected: + ~TestParent() + { + MOZ_COUNT_DTOR(TestParent); + } + +public: + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; +}; + +} // namespace + +namespace mozilla { +namespace ipc { + +using mozilla::dom::ContentParent; +using mozilla::dom::BroadcastChannelParent; +using mozilla::dom::ServiceWorkerRegistrationData; +using mozilla::dom::workers::ServiceWorkerManagerParent; + +BackgroundParentImpl::BackgroundParentImpl() +{ + AssertIsInMainProcess(); + AssertIsOnMainThread(); + + MOZ_COUNT_CTOR(mozilla::ipc::BackgroundParentImpl); +} + +BackgroundParentImpl::~BackgroundParentImpl() +{ + AssertIsInMainProcess(); + AssertIsOnMainThread(); + + MOZ_COUNT_DTOR(mozilla::ipc::BackgroundParentImpl); +} + +void +BackgroundParentImpl::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); +} + +BackgroundParentImpl::PBackgroundTestParent* +BackgroundParentImpl::AllocPBackgroundTestParent(const nsCString& aTestArg) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return new TestParent(); +} + +bool +BackgroundParentImpl::RecvPBackgroundTestConstructor( + PBackgroundTestParent* aActor, + const nsCString& aTestArg) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return PBackgroundTestParent::Send__delete__(aActor, aTestArg); +} + +bool +BackgroundParentImpl::DeallocPBackgroundTestParent( + PBackgroundTestParent* aActor) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + delete static_cast<TestParent*>(aActor); + return true; +} + +auto +BackgroundParentImpl::AllocPBackgroundIDBFactoryParent( + const LoggingInfo& aLoggingInfo) + -> PBackgroundIDBFactoryParent* +{ + using mozilla::dom::indexedDB::AllocPBackgroundIDBFactoryParent; + + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return AllocPBackgroundIDBFactoryParent(aLoggingInfo); +} + +bool +BackgroundParentImpl::RecvPBackgroundIDBFactoryConstructor( + PBackgroundIDBFactoryParent* aActor, + const LoggingInfo& aLoggingInfo) +{ + using mozilla::dom::indexedDB::RecvPBackgroundIDBFactoryConstructor; + + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return RecvPBackgroundIDBFactoryConstructor(aActor, aLoggingInfo); +} + +bool +BackgroundParentImpl::DeallocPBackgroundIDBFactoryParent( + PBackgroundIDBFactoryParent* aActor) +{ + using mozilla::dom::indexedDB::DeallocPBackgroundIDBFactoryParent; + + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return DeallocPBackgroundIDBFactoryParent(aActor); +} + +auto +BackgroundParentImpl::AllocPBackgroundIndexedDBUtilsParent() + -> PBackgroundIndexedDBUtilsParent* +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::indexedDB::AllocPBackgroundIndexedDBUtilsParent(); +} + +bool +BackgroundParentImpl::DeallocPBackgroundIndexedDBUtilsParent( + PBackgroundIndexedDBUtilsParent* aActor) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return + mozilla::dom::indexedDB::DeallocPBackgroundIndexedDBUtilsParent(aActor); +} + +bool +BackgroundParentImpl::RecvFlushPendingFileDeletions() +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::indexedDB::RecvFlushPendingFileDeletions(); +} + +auto +BackgroundParentImpl::AllocPBlobParent(const BlobConstructorParams& aParams) + -> PBlobParent* +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (NS_WARN_IF(aParams.type() != + BlobConstructorParams::TParentBlobConstructorParams)) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + return mozilla::dom::BlobParent::Create(this, aParams); +} + +bool +BackgroundParentImpl::DeallocPBlobParent(PBlobParent* aActor) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + mozilla::dom::BlobParent::Destroy(aActor); + return true; +} + +bool +BackgroundParentImpl::RecvPBlobConstructor(PBlobParent* aActor, + const BlobConstructorParams& aParams) +{ + const ParentBlobConstructorParams& params = aParams; + if (params.blobParams().type() == AnyBlobConstructorParams::TKnownBlobConstructorParams) { + return aActor->SendCreatedFromKnownBlob(); + } + + return true; +} + +PFileDescriptorSetParent* +BackgroundParentImpl::AllocPFileDescriptorSetParent( + const FileDescriptor& aFileDescriptor) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return new FileDescriptorSetParent(aFileDescriptor); +} + +bool +BackgroundParentImpl::DeallocPFileDescriptorSetParent( + PFileDescriptorSetParent* aActor) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + delete static_cast<FileDescriptorSetParent*>(aActor); + return true; +} + +PSendStreamParent* +BackgroundParentImpl::AllocPSendStreamParent() +{ + return mozilla::ipc::AllocPSendStreamParent(); +} + +bool +BackgroundParentImpl::DeallocPSendStreamParent(PSendStreamParent* aActor) +{ + delete aActor; + return true; +} + +BackgroundParentImpl::PVsyncParent* +BackgroundParentImpl::AllocPVsyncParent() +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<mozilla::layout::VsyncParent> actor = + mozilla::layout::VsyncParent::Create(); + // There still has one ref-count after return, and it will be released in + // DeallocPVsyncParent(). + return actor.forget().take(); +} + +bool +BackgroundParentImpl::DeallocPVsyncParent(PVsyncParent* aActor) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + // This actor already has one ref-count. Please check AllocPVsyncParent(). + RefPtr<mozilla::layout::VsyncParent> actor = + dont_AddRef(static_cast<mozilla::layout::VsyncParent*>(aActor)); + return true; +} + +camera::PCamerasParent* +BackgroundParentImpl::AllocPCamerasParent() +{ + AssertIsInMainProcess(); + 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) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + +#ifdef MOZ_WEBRTC + RefPtr<mozilla::camera::CamerasParent> actor = + dont_AddRef(static_cast<mozilla::camera::CamerasParent*>(aActor)); +#endif + return true; +} + +namespace { + +class InitUDPSocketParentCallback final : public Runnable +{ +public: + InitUDPSocketParentCallback(UDPSocketParent* aActor, + const nsACString& aFilter) + : mActor(aActor) + , mFilter(aFilter) + { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + } + + NS_IMETHOD + Run() override + { + AssertIsInMainProcess(); + + IPC::Principal principal; + if (!mActor->Init(principal, mFilter)) { + MOZ_CRASH("UDPSocketCallback - failed init"); + } + return NS_OK; + } + +private: + ~InitUDPSocketParentCallback() {}; + + RefPtr<UDPSocketParent> mActor; + nsCString mFilter; +}; + +} // namespace + +auto +BackgroundParentImpl::AllocPUDPSocketParent(const OptionalPrincipalInfo& /* unused */, + const nsCString& /* unused */) + -> PUDPSocketParent* +{ + RefPtr<UDPSocketParent> p = new UDPSocketParent(this); + + return p.forget().take(); +} + +bool +BackgroundParentImpl::RecvPUDPSocketConstructor(PUDPSocketParent* aActor, + const OptionalPrincipalInfo& aOptionalPrincipal, + const nsCString& aFilter) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (aOptionalPrincipal.type() == OptionalPrincipalInfo::TPrincipalInfo) { + // Support for checking principals (for non-mtransport use) will be handled in + // bug 1167039 + return false; + } + // 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 false; + } + + IPC::Principal principal; + if (!static_cast<UDPSocketParent*>(aActor)->Init(principal, aFilter)) { + MOZ_CRASH("UDPSocketCallback - failed init"); + } + + return true; +} + +bool +BackgroundParentImpl::DeallocPUDPSocketParent(PUDPSocketParent* actor) +{ + UDPSocketParent* p = static_cast<UDPSocketParent*>(actor); + p->Release(); + return true; +} + +mozilla::dom::PBroadcastChannelParent* +BackgroundParentImpl::AllocPBroadcastChannelParent( + const PrincipalInfo& aPrincipalInfo, + const nsCString& aOrigin, + const nsString& aChannel) +{ + AssertIsInMainProcess(); + 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) + : mContentParent(aParent) + , mPrincipalInfo(aPrincipalInfo) + , mOrigin(aOrigin) + { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + MOZ_ASSERT(mContentParent); + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + + NullifyContentParentRAII raii(mContentParent); + + nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(mPrincipalInfo); + AssertAppPrincipal(mContentParent, principal); + + if (principal->GetIsNullPrincipal()) { + mContentParent->KillHard("BroadcastChannel killed: no null principal."); + return NS_OK; + } + + nsAutoCString origin; + nsresult rv = principal->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 + +bool +BackgroundParentImpl::RecvPBroadcastChannelConstructor( + PBroadcastChannelParent* actor, + const PrincipalInfo& aPrincipalInfo, + const nsCString& aOrigin, + const nsString& aChannel) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<ContentParent> parent = BackgroundParent::GetContentParent(this); + + // If the ContentParent is null we are dealing with a same-process actor. + if (!parent) { + MOZ_ASSERT(aPrincipalInfo.type() != PrincipalInfo::TNullPrincipalInfo); + return true; + } + + RefPtr<CheckPrincipalRunnable> runnable = + new CheckPrincipalRunnable(parent.forget(), aPrincipalInfo, aOrigin); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable)); + + return true; +} + +bool +BackgroundParentImpl::DeallocPBroadcastChannelParent( + PBroadcastChannelParent* aActor) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + delete static_cast<BroadcastChannelParent*>(aActor); + return true; +} + +mozilla::dom::PServiceWorkerManagerParent* +BackgroundParentImpl::AllocPServiceWorkerManagerParent() +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<dom::workers::ServiceWorkerManagerParent> agent = + new dom::workers::ServiceWorkerManagerParent(); + return agent.forget().take(); +} + +bool +BackgroundParentImpl::DeallocPServiceWorkerManagerParent( + PServiceWorkerManagerParent* aActor) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + RefPtr<dom::workers::ServiceWorkerManagerParent> parent = + dont_AddRef(static_cast<dom::workers::ServiceWorkerManagerParent*>(aActor)); + MOZ_ASSERT(parent); + return true; +} + +bool +BackgroundParentImpl::RecvShutdownServiceWorkerRegistrar() +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (BackgroundParent::IsOtherProcessActor(this)) { + return false; + } + + RefPtr<dom::ServiceWorkerRegistrar> service = + dom::ServiceWorkerRegistrar::Get(); + MOZ_ASSERT(service); + + service->Shutdown(); + return true; +} + +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; +} + +PCacheStreamControlParent* +BackgroundParentImpl::AllocPCacheStreamControlParent() +{ + MOZ_CRASH("CacheStreamControlParent actor must be provided to PBackground manager"); + return nullptr; +} + +bool +BackgroundParentImpl::DeallocPCacheStreamControlParent(PCacheStreamControlParent* aActor) +{ + dom::cache::DeallocPCacheStreamControlParent(aActor); + return true; +} + +PMessagePortParent* +BackgroundParentImpl::AllocPMessagePortParent(const nsID& aUUID, + const nsID& aDestinationUUID, + const uint32_t& aSequenceID) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return new MessagePortParent(aUUID); +} + +bool +BackgroundParentImpl::RecvPMessagePortConstructor(PMessagePortParent* aActor, + const nsID& aUUID, + const nsID& aDestinationUUID, + const uint32_t& aSequenceID) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + MessagePortParent* mp = static_cast<MessagePortParent*>(aActor); + return mp->Entangle(aDestinationUUID, aSequenceID); +} + +bool +BackgroundParentImpl::DeallocPMessagePortParent(PMessagePortParent* aActor) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + delete static_cast<MessagePortParent*>(aActor); + return true; +} + +bool +BackgroundParentImpl::RecvMessagePortForceClose(const nsID& aUUID, + const nsID& aDestinationUUID, + const uint32_t& aSequenceID) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return MessagePortParent::ForceClose(aUUID, aDestinationUUID, aSequenceID); +} + +PAsmJSCacheEntryParent* +BackgroundParentImpl::AllocPAsmJSCacheEntryParent( + const dom::asmjscache::OpenMode& aOpenMode, + const dom::asmjscache::WriteParams& aWriteParams, + const PrincipalInfo& aPrincipalInfo) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return + dom::asmjscache::AllocEntryParent(aOpenMode, aWriteParams, aPrincipalInfo); +} + +bool +BackgroundParentImpl::DeallocPAsmJSCacheEntryParent( + PAsmJSCacheEntryParent* aActor) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + dom::asmjscache::DeallocEntryParent(aActor); + return true; +} + +BackgroundParentImpl::PQuotaParent* +BackgroundParentImpl::AllocPQuotaParent() +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::quota::AllocPQuotaParent(); +} + +bool +BackgroundParentImpl::DeallocPQuotaParent(PQuotaParent* aActor) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::quota::DeallocPQuotaParent(aActor); +} + +dom::PFileSystemRequestParent* +BackgroundParentImpl::AllocPFileSystemRequestParent( + const FileSystemParams& aParams) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<FileSystemRequestParent> result = new FileSystemRequestParent(); + + if (NS_WARN_IF(!result->Initialize(aParams))) { + return nullptr; + } + + return result.forget().take(); +} + +bool +BackgroundParentImpl::RecvPFileSystemRequestConstructor( + PFileSystemRequestParent* aActor, + const FileSystemParams& params) +{ + static_cast<FileSystemRequestParent*>(aActor)->Start(); + return true; +} + +bool +BackgroundParentImpl::DeallocPFileSystemRequestParent( + PFileSystemRequestParent* aDoomed) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<FileSystemRequestParent> parent = + dont_AddRef(static_cast<FileSystemRequestParent*>(aDoomed)); + return true; +} + +// Gamepad API Background IPC +dom::PGamepadEventChannelParent* +BackgroundParentImpl::AllocPGamepadEventChannelParent() +{ +#ifdef MOZ_GAMEPAD + RefPtr<dom::GamepadEventChannelParent> parent = + new dom::GamepadEventChannelParent(); + + return parent.forget().take(); +#else + return nullptr; +#endif +} + +bool +BackgroundParentImpl::DeallocPGamepadEventChannelParent(dom::PGamepadEventChannelParent *aActor) +{ +#ifdef MOZ_GAMEPAD + MOZ_ASSERT(aActor); + RefPtr<dom::GamepadEventChannelParent> parent = + dont_AddRef(static_cast<dom::GamepadEventChannelParent*>(aActor)); +#endif + return true; +} + +dom::PGamepadTestChannelParent* +BackgroundParentImpl::AllocPGamepadTestChannelParent() +{ +#ifdef MOZ_GAMEPAD + RefPtr<dom::GamepadTestChannelParent> parent = + new dom::GamepadTestChannelParent(); + + return parent.forget().take(); +#else + return nullptr; +#endif +} + +bool +BackgroundParentImpl::DeallocPGamepadTestChannelParent(dom::PGamepadTestChannelParent *aActor) +{ +#ifdef MOZ_GAMEPAD + MOZ_ASSERT(aActor); + RefPtr<dom::GamepadTestChannelParent> parent = + dont_AddRef(static_cast<dom::GamepadTestChannelParent*>(aActor)); +#endif + return true; +} + +} // namespace ipc +} // namespace mozilla + +void +TestParent::ActorDestroy(ActorDestroyReason aWhy) +{ + mozilla::ipc::AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); +} diff --git a/ipc/glue/BackgroundParentImpl.h b/ipc/glue/BackgroundParentImpl.h new file mode 100644 index 000000000..8d0ac06a6 --- /dev/null +++ b/ipc/glue/BackgroundParentImpl.h @@ -0,0 +1,214 @@ +/* -*- 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/PBackgroundParent.h" + +namespace mozilla { + +namespace layout { +class VsyncParent; +} // namespace layout + +namespace ipc { + +// Instances of this class should never be created directly. This class is meant +// to be inherited in BackgroundImpl. +class BackgroundParentImpl : public PBackgroundParent +{ +protected: + BackgroundParentImpl(); + virtual ~BackgroundParentImpl(); + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + virtual PBackgroundTestParent* + AllocPBackgroundTestParent(const nsCString& aTestArg) override; + + virtual bool + RecvPBackgroundTestConstructor(PBackgroundTestParent* aActor, + const nsCString& aTestArg) override; + + virtual bool + DeallocPBackgroundTestParent(PBackgroundTestParent* aActor) override; + + virtual PBackgroundIDBFactoryParent* + AllocPBackgroundIDBFactoryParent(const LoggingInfo& aLoggingInfo) + override; + + virtual bool + RecvPBackgroundIDBFactoryConstructor(PBackgroundIDBFactoryParent* aActor, + const LoggingInfo& aLoggingInfo) + override; + + virtual bool + DeallocPBackgroundIDBFactoryParent(PBackgroundIDBFactoryParent* aActor) + override; + + virtual PBackgroundIndexedDBUtilsParent* + AllocPBackgroundIndexedDBUtilsParent() override; + + virtual bool + DeallocPBackgroundIndexedDBUtilsParent( + PBackgroundIndexedDBUtilsParent* aActor) + override; + + virtual bool + RecvFlushPendingFileDeletions() override; + + virtual PBlobParent* + AllocPBlobParent(const BlobConstructorParams& aParams) override; + + virtual bool + DeallocPBlobParent(PBlobParent* aActor) override; + + virtual bool + RecvPBlobConstructor(PBlobParent* aActor, + const BlobConstructorParams& params) override; + + virtual PFileDescriptorSetParent* + AllocPFileDescriptorSetParent(const FileDescriptor& aFileDescriptor) + override; + + virtual bool + DeallocPFileDescriptorSetParent(PFileDescriptorSetParent* aActor) + override; + + virtual PVsyncParent* + AllocPVsyncParent() override; + + virtual bool + DeallocPVsyncParent(PVsyncParent* aActor) override; + + virtual PBroadcastChannelParent* + AllocPBroadcastChannelParent(const PrincipalInfo& aPrincipalInfo, + const nsCString& aOrigin, + const nsString& aChannel) override; + + virtual bool + RecvPBroadcastChannelConstructor(PBroadcastChannelParent* actor, + const PrincipalInfo& aPrincipalInfo, + const nsCString& origin, + const nsString& channel) override; + + virtual bool + DeallocPBroadcastChannelParent(PBroadcastChannelParent* aActor) override; + + virtual PSendStreamParent* + AllocPSendStreamParent() override; + + virtual bool + DeallocPSendStreamParent(PSendStreamParent* aActor) override; + + virtual PServiceWorkerManagerParent* + AllocPServiceWorkerManagerParent() override; + + virtual bool + DeallocPServiceWorkerManagerParent(PServiceWorkerManagerParent* aActor) override; + + virtual PCamerasParent* + AllocPCamerasParent() override; + + virtual bool + DeallocPCamerasParent(PCamerasParent* aActor) override; + + virtual bool + RecvShutdownServiceWorkerRegistrar() override; + + virtual dom::cache::PCacheStorageParent* + AllocPCacheStorageParent(const dom::cache::Namespace& aNamespace, + const PrincipalInfo& aPrincipalInfo) override; + + virtual bool + DeallocPCacheStorageParent(dom::cache::PCacheStorageParent* aActor) override; + + virtual dom::cache::PCacheParent* AllocPCacheParent() override; + + virtual bool + DeallocPCacheParent(dom::cache::PCacheParent* aActor) override; + + virtual dom::cache::PCacheStreamControlParent* + AllocPCacheStreamControlParent() override; + + virtual bool + DeallocPCacheStreamControlParent(dom::cache::PCacheStreamControlParent* aActor) + override; + + virtual PUDPSocketParent* + AllocPUDPSocketParent(const OptionalPrincipalInfo& pInfo, + const nsCString& aFilter) override; + virtual bool + RecvPUDPSocketConstructor(PUDPSocketParent*, + const OptionalPrincipalInfo& aPrincipalInfo, + const nsCString& aFilter) override; + virtual bool + DeallocPUDPSocketParent(PUDPSocketParent*) override; + + virtual PMessagePortParent* + AllocPMessagePortParent(const nsID& aUUID, + const nsID& aDestinationUUID, + const uint32_t& aSequenceID) override; + + virtual bool + RecvPMessagePortConstructor(PMessagePortParent* aActor, + const nsID& aUUID, + const nsID& aDestinationUUID, + const uint32_t& aSequenceID) override; + + virtual bool + DeallocPMessagePortParent(PMessagePortParent* aActor) override; + + virtual bool + RecvMessagePortForceClose(const nsID& aUUID, + const nsID& aDestinationUUID, + const uint32_t& aSequenceID) override; + + virtual PAsmJSCacheEntryParent* + AllocPAsmJSCacheEntryParent(const dom::asmjscache::OpenMode& aOpenMode, + const dom::asmjscache::WriteParams& aWriteParams, + const PrincipalInfo& aPrincipalInfo) override; + + virtual bool + DeallocPAsmJSCacheEntryParent(PAsmJSCacheEntryParent* aActor) override; + + virtual PQuotaParent* + AllocPQuotaParent() override; + + virtual bool + DeallocPQuotaParent(PQuotaParent* aActor) override; + + virtual PFileSystemRequestParent* + AllocPFileSystemRequestParent(const FileSystemParams&) override; + + virtual bool + RecvPFileSystemRequestConstructor(PFileSystemRequestParent* actor, + const FileSystemParams& params) override; + + virtual bool + DeallocPFileSystemRequestParent(PFileSystemRequestParent*) override; + + // Gamepad API Background IPC + virtual PGamepadEventChannelParent* + AllocPGamepadEventChannelParent() override; + + virtual bool + DeallocPGamepadEventChannelParent(PGamepadEventChannelParent *aActor) override; + + virtual PGamepadTestChannelParent* + AllocPGamepadTestChannelParent() override; + + virtual bool + DeallocPGamepadTestChannelParent(PGamepadTestChannelParent* 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 000000000..b335f5c23 --- /dev/null +++ b/ipc/glue/BackgroundUtils.cpp @@ -0,0 +1,382 @@ +/* -*- 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/ipc/PBackgroundSharedTypes.h" +#include "mozilla/net/NeckoChannelParams.h" +#include "nsPrincipal.h" +#include "nsIScriptSecurityManager.h" +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "mozilla/LoadInfo.h" +#include "nsNullPrincipal.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +namespace net { +class OptionalLoadInfoArgs; +} + +using mozilla::BasePrincipal; +using namespace mozilla::net; + +namespace ipc { + +already_AddRefed<nsIPrincipal> +PrincipalInfoToPrincipal(const PrincipalInfo& aPrincipalInfo, + nsresult* aOptionalResult) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPrincipalInfo.type() != PrincipalInfo::T__None); + + nsresult stackResult; + nsresult& rv = aOptionalResult ? *aOptionalResult : stackResult; + + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + nsCOMPtr<nsIPrincipal> principal; + + switch (aPrincipalInfo.type()) { + case PrincipalInfo::TSystemPrincipalInfo: { + rv = secMan->GetSystemPrincipal(getter_AddRefs(principal)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + return principal.forget(); + } + + case PrincipalInfo::TNullPrincipalInfo: { + const NullPrincipalInfo& info = + aPrincipalInfo.get_NullPrincipalInfo(); + principal = nsNullPrincipal::Create(info.attrs()); + + return principal.forget(); + } + + 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 nullptr; + } + + PrincipalOriginAttributes attrs; + if (info.attrs().mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID) { + attrs = info.attrs(); + } + principal = BasePrincipal::CreateCodebasePrincipal(uri, attrs); + rv = principal ? NS_OK : NS_ERROR_FAILURE; + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + // When the principal is serialized, the origin is extract from it. This + // can fail, and in case, here we will havea Tvoid_t. If we have a string, + // it must match with what the_new_principal.getOrigin returns. + if (info.originNoSuffix().type() == ContentPrincipalInfoOriginNoSuffix::TnsCString) { + nsAutoCString originNoSuffix; + rv = principal->GetOriginNoSuffix(originNoSuffix); + if (NS_WARN_IF(NS_FAILED(rv)) || + !info.originNoSuffix().get_nsCString().Equals(originNoSuffix)) { + MOZ_CRASH("If the origin was in the contentPrincipalInfo, it must be available when deserialized"); + } + } + + return principal.forget(); + } + + case PrincipalInfo::TExpandedPrincipalInfo: { + const ExpandedPrincipalInfo& info = aPrincipalInfo.get_ExpandedPrincipalInfo(); + + nsTArray<nsCOMPtr<nsIPrincipal>> whitelist; + nsCOMPtr<nsIPrincipal> wlPrincipal; + + for (uint32_t i = 0; i < info.whitelist().Length(); i++) { + wlPrincipal = PrincipalInfoToPrincipal(info.whitelist()[i], &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + // append that principal to the whitelist + whitelist.AppendElement(wlPrincipal); + } + + RefPtr<nsExpandedPrincipal> expandedPrincipal = new nsExpandedPrincipal(whitelist, info.attrs()); + if (!expandedPrincipal) { + NS_WARNING("could not instantiate expanded principal"); + return nullptr; + } + + principal = expandedPrincipal; + return principal.forget(); + } + + default: + MOZ_CRASH("Unknown PrincipalInfo type!"); + } + + MOZ_CRASH("Should never get here!"); +} + +nsresult +PrincipalToPrincipalInfo(nsIPrincipal* aPrincipal, + PrincipalInfo* aPrincipalInfo) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(aPrincipalInfo); + + if (aPrincipal->GetIsNullPrincipal()) { + *aPrincipalInfo = NullPrincipalInfo(BasePrincipal::Cast(aPrincipal)->OriginAttributesRef()); + return NS_OK; + } + + nsresult rv; + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool isSystemPrincipal; + rv = secMan->IsSystemPrincipal(aPrincipal, &isSystemPrincipal); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (isSystemPrincipal) { + *aPrincipalInfo = SystemPrincipalInfo(); + return NS_OK; + } + + // might be an expanded principal + nsCOMPtr<nsIExpandedPrincipal> expanded = + do_QueryInterface(aPrincipal); + + if (expanded) { + nsTArray<PrincipalInfo> whitelistInfo; + PrincipalInfo info; + + nsTArray< nsCOMPtr<nsIPrincipal> >* whitelist; + MOZ_ALWAYS_SUCCEEDS(expanded->GetWhiteList(&whitelist)); + + for (uint32_t i = 0; i < whitelist->Length(); i++) { + rv = PrincipalToPrincipalInfo((*whitelist)[i], &info); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + // append that spec to the whitelist + whitelistInfo.AppendElement(info); + } + + *aPrincipalInfo = + ExpandedPrincipalInfo(BasePrincipal::Cast(aPrincipal)->OriginAttributesRef(), + Move(whitelistInfo)); + return NS_OK; + } + + // must be a content principal + + nsCOMPtr<nsIURI> uri; + rv = aPrincipal->GetURI(getter_AddRefs(uri)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_WARN_IF(!uri)) { + return NS_ERROR_FAILURE; + } + + nsCString spec; + rv = uri->GetSpec(spec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + ContentPrincipalInfoOriginNoSuffix infoOriginNoSuffix; + + nsCString originNoSuffix; + rv = aPrincipal->GetOriginNoSuffix(originNoSuffix); + if (NS_WARN_IF(NS_FAILED(rv))) { + infoOriginNoSuffix = void_t(); + } else { + infoOriginNoSuffix = originNoSuffix; + } + + *aPrincipalInfo = ContentPrincipalInfo(BasePrincipal::Cast(aPrincipal)->OriginAttributesRef(), + infoOriginNoSuffix, spec); + return NS_OK; +} + +nsresult +LoadInfoToLoadInfoArgs(nsILoadInfo *aLoadInfo, + OptionalLoadInfoArgs* aOptionalLoadInfoArgs) +{ + if (!aLoadInfo) { + // if there is no loadInfo, then there is nothing to serialize + *aOptionalLoadInfoArgs = void_t(); + return NS_OK; + } + + nsresult rv = NS_OK; + OptionalPrincipalInfo loadingPrincipalInfo = mozilla::void_t(); + if (aLoadInfo->LoadingPrincipal()) { + PrincipalInfo loadingPrincipalInfoTemp; + rv = PrincipalToPrincipalInfo(aLoadInfo->LoadingPrincipal(), + &loadingPrincipalInfoTemp); + NS_ENSURE_SUCCESS(rv, rv); + loadingPrincipalInfo = loadingPrincipalInfoTemp; + } + + PrincipalInfo triggeringPrincipalInfo; + rv = PrincipalToPrincipalInfo(aLoadInfo->TriggeringPrincipal(), + &triggeringPrincipalInfo); + + OptionalPrincipalInfo principalToInheritInfo = mozilla::void_t(); + if (aLoadInfo->PrincipalToInherit()) { + PrincipalInfo principalToInheritInfoTemp; + rv = PrincipalToPrincipalInfo(aLoadInfo->PrincipalToInherit(), + &principalToInheritInfoTemp); + NS_ENSURE_SUCCESS(rv, rv); + principalToInheritInfo = principalToInheritInfoTemp; + } + + nsTArray<PrincipalInfo> redirectChainIncludingInternalRedirects; + for (const nsCOMPtr<nsIPrincipal>& principal : aLoadInfo->RedirectChainIncludingInternalRedirects()) { + rv = PrincipalToPrincipalInfo(principal, redirectChainIncludingInternalRedirects.AppendElement()); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsTArray<PrincipalInfo> redirectChain; + for (const nsCOMPtr<nsIPrincipal>& principal : aLoadInfo->RedirectChain()) { + rv = PrincipalToPrincipalInfo(principal, redirectChain.AppendElement()); + NS_ENSURE_SUCCESS(rv, rv); + } + + *aOptionalLoadInfoArgs = + LoadInfoArgs( + loadingPrincipalInfo, + triggeringPrincipalInfo, + principalToInheritInfo, + aLoadInfo->GetSecurityFlags(), + aLoadInfo->InternalContentPolicyType(), + static_cast<uint32_t>(aLoadInfo->GetTainting()), + aLoadInfo->GetUpgradeInsecureRequests(), + aLoadInfo->GetVerifySignedContent(), + aLoadInfo->GetEnforceSRI(), + aLoadInfo->GetForceInheritPrincipalDropped(), + aLoadInfo->GetInnerWindowID(), + aLoadInfo->GetOuterWindowID(), + aLoadInfo->GetParentOuterWindowID(), + aLoadInfo->GetFrameOuterWindowID(), + aLoadInfo->GetEnforceSecurity(), + aLoadInfo->GetInitialSecurityCheckDone(), + aLoadInfo->GetIsInThirdPartyContext(), + aLoadInfo->GetOriginAttributes(), + redirectChainIncludingInternalRedirects, + redirectChain, + aLoadInfo->CorsUnsafeHeaders(), + aLoadInfo->GetForcePreflight(), + aLoadInfo->GetIsPreflight(), + aLoadInfo->GetForceHSTSPriming(), + aLoadInfo->GetMixedContentWouldBlock()); + + return NS_OK; +} + +nsresult +LoadInfoArgsToLoadInfo(const OptionalLoadInfoArgs& aOptionalLoadInfoArgs, + nsILoadInfo** outLoadInfo) +{ + if (aOptionalLoadInfoArgs.type() == OptionalLoadInfoArgs::Tvoid_t) { + *outLoadInfo = nullptr; + return NS_OK; + } + + const LoadInfoArgs& loadInfoArgs = + aOptionalLoadInfoArgs.get_LoadInfoArgs(); + + nsresult rv = NS_OK; + nsCOMPtr<nsIPrincipal> loadingPrincipal; + if (loadInfoArgs.requestingPrincipalInfo().type() != OptionalPrincipalInfo::Tvoid_t) { + loadingPrincipal = PrincipalInfoToPrincipal(loadInfoArgs.requestingPrincipalInfo(), &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIPrincipal> triggeringPrincipal = + PrincipalInfoToPrincipal(loadInfoArgs.triggeringPrincipalInfo(), &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrincipal> principalToInherit; + if (loadInfoArgs.principalToInheritInfo().type() != OptionalPrincipalInfo::Tvoid_t) { + principalToInherit = PrincipalInfoToPrincipal(loadInfoArgs.principalToInheritInfo(), &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsTArray<nsCOMPtr<nsIPrincipal>> redirectChainIncludingInternalRedirects; + for (const PrincipalInfo& principalInfo : loadInfoArgs.redirectChainIncludingInternalRedirects()) { + nsCOMPtr<nsIPrincipal> redirectedPrincipal = + PrincipalInfoToPrincipal(principalInfo, &rv); + NS_ENSURE_SUCCESS(rv, rv); + redirectChainIncludingInternalRedirects.AppendElement(redirectedPrincipal.forget()); + } + + nsTArray<nsCOMPtr<nsIPrincipal>> redirectChain; + for (const PrincipalInfo& principalInfo : loadInfoArgs.redirectChain()) { + nsCOMPtr<nsIPrincipal> redirectedPrincipal = + PrincipalInfoToPrincipal(principalInfo, &rv); + NS_ENSURE_SUCCESS(rv, rv); + redirectChain.AppendElement(redirectedPrincipal.forget()); + } + + nsCOMPtr<nsILoadInfo> loadInfo = + new mozilla::LoadInfo(loadingPrincipal, + triggeringPrincipal, + principalToInherit, + loadInfoArgs.securityFlags(), + loadInfoArgs.contentPolicyType(), + static_cast<LoadTainting>(loadInfoArgs.tainting()), + loadInfoArgs.upgradeInsecureRequests(), + loadInfoArgs.verifySignedContent(), + loadInfoArgs.enforceSRI(), + loadInfoArgs.forceInheritPrincipalDropped(), + loadInfoArgs.innerWindowID(), + loadInfoArgs.outerWindowID(), + loadInfoArgs.parentOuterWindowID(), + loadInfoArgs.frameOuterWindowID(), + loadInfoArgs.enforceSecurity(), + loadInfoArgs.initialSecurityCheckDone(), + loadInfoArgs.isInThirdPartyContext(), + loadInfoArgs.originAttributes(), + redirectChainIncludingInternalRedirects, + redirectChain, + loadInfoArgs.corsUnsafeHeaders(), + loadInfoArgs.forcePreflight(), + loadInfoArgs.isPreflight(), + loadInfoArgs.forceHSTSPriming(), + loadInfoArgs.mixedContentWouldBlock() + ); + + loadInfo.forget(outLoadInfo); + return NS_OK; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/BackgroundUtils.h b/ipc/glue/BackgroundUtils.h new file mode 100644 index 000000000..7ae0f5226 --- /dev/null +++ b/ipc/glue/BackgroundUtils.h @@ -0,0 +1,107 @@ +/* -*- 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/BasePrincipal.h" +#include "nsCOMPtr.h" +#include "nscore.h" + +class nsILoadInfo; +class nsIPrincipal; + +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::PrincipalOriginAttributes> + : public detail::OriginAttributesParamTraits<mozilla::PrincipalOriginAttributes> {}; + +template<> +struct ParamTraits<mozilla::DocShellOriginAttributes> + : public detail::OriginAttributesParamTraits<mozilla::DocShellOriginAttributes> {}; + +template<> +struct ParamTraits<mozilla::NeckoOriginAttributes> + : public detail::OriginAttributesParamTraits<mozilla::NeckoOriginAttributes> {}; + +template<> +struct ParamTraits<mozilla::GenericOriginAttributes> + : public detail::OriginAttributesParamTraits<mozilla::GenericOriginAttributes> {}; + +} // namespace IPC + +namespace mozilla { +namespace net { +class OptionalLoadInfoArgs; +} // namespace net + +using namespace mozilla::net; + +namespace ipc { + +class PrincipalInfo; + +/** + * Convert a PrincipalInfo to an nsIPrincipal. + * + * MUST be called on the main thread only. + */ +already_AddRefed<nsIPrincipal> +PrincipalInfoToPrincipal(const PrincipalInfo& aPrincipalInfo, + nsresult* aOptionalResult = nullptr); + +/** + * Convert an nsIPrincipal to a PrincipalInfo. + * + * MUST be called on the main thread only. + */ +nsresult +PrincipalToPrincipalInfo(nsIPrincipal* aPrincipal, + PrincipalInfo* aPrincipalInfo); + +/** + * Convert a LoadInfo to LoadInfoArgs struct. + */ +nsresult +LoadInfoToLoadInfoArgs(nsILoadInfo *aLoadInfo, + OptionalLoadInfoArgs* outOptionalLoadInfoArgs); + +/** + * Convert LoadInfoArgs to a LoadInfo. + */ +nsresult +LoadInfoArgsToLoadInfo(const OptionalLoadInfoArgs& aOptionalLoadInfoArgs, + nsILoadInfo** outLoadInfo); + +} // 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 000000000..7618dc934 --- /dev/null +++ b/ipc/glue/BrowserProcessSubThread.cpp @@ -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/. */ + +#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] = { + "Gecko_IOThread", // IO +// "Chrome_FileThread", // FILE +// "Chrome_DBThread", // DB +// "Chrome_HistoryThread", // HISTORY +#if defined(OS_LINUX) + "Gecko_Background_X11Thread", // BACKGROUND_X11 +#endif +}; + +/* static */ StaticMutex BrowserProcessSubThread::sLock; +BrowserProcessSubThread* BrowserProcessSubThread::sBrowserThreads[ID_COUNT] = { + nullptr, // IO +// nullptr, // FILE +// nullptr, // DB +// nullptr, // HISTORY +#if defined(OS_LINUX) + nullptr, // BACKGROUND_X11 +#endif +}; + +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 000000000..e1b4aef46 --- /dev/null +++ b/ipc/glue/BrowserProcessSubThread.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_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, + //FILE, + //DB, + //HISTORY, +#if defined(OS_LINUX) + // This thread has a second connection to the X server and is used + // to process UI requests when routing the request to the UI + // thread would risk deadlock. + BACKGROUND_X11, +#endif + + // 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(); + virtual void CleanUp(); + +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/CrashReporterClient.cpp b/ipc/glue/CrashReporterClient.cpp new file mode 100644 index 000000000..004ca3b57 --- /dev/null +++ b/ipc/glue/CrashReporterClient.cpp @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CrashReporterClient.h" +#include "CrashReporterMetadataShmem.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace ipc { + +StaticMutex CrashReporterClient::sLock; +StaticRefPtr<CrashReporterClient> CrashReporterClient::sClientSingleton; + +CrashReporterClient::CrashReporterClient(const Shmem& aShmem) + : mMetadata(new CrashReporterMetadataShmem(aShmem)) +{ + MOZ_COUNT_CTOR(CrashReporterClient); +} + +CrashReporterClient::~CrashReporterClient() +{ + MOZ_COUNT_DTOR(CrashReporterClient); +} + +void +CrashReporterClient::AnnotateCrashReport(const nsCString& aKey, const nsCString& aData) +{ + StaticMutexAutoLock lock(sLock); + mMetadata->AnnotateCrashReport(aKey, aData); +} + +void +CrashReporterClient::AppendAppNotes(const nsCString& aData) +{ + StaticMutexAutoLock lock(sLock); + mMetadata->AppendAppNotes(aData); +} + +/* static */ void +CrashReporterClient::InitSingletonWithShmem(const Shmem& aShmem) +{ + StaticMutexAutoLock lock(sLock); + + MOZ_ASSERT(!sClientSingleton); + sClientSingleton = new CrashReporterClient(aShmem); +} + +/* 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 000000000..512533da8 --- /dev/null +++ b/ipc/glue/CrashReporterClient.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_CrashReporterClient_h +#define mozilla_ipc_CrashReporterClient_h + +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Unused.h" +#include "mozilla/ipc/Shmem.h" + +namespace mozilla { +namespace ipc { + +class CrashReporterMetadataShmem; + +class CrashReporterClient +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CrashReporterClient); + + // |aTopLevelProtocol| must be a top-level protocol instance, as sub-actors + // do not have AllocUnsafeShmem. It must also have a child-to-parent message: + // + // async SetCrashReporterClient(Shmem shmem); + // + // The parent-side receive function of this message should save the shmem + // somewhere, and when the top-level actor's ActorDestroy runs (or when the + // crash reporter needs metadata), the shmem should be parsed. + template <typename T> + static bool InitSingleton(T* aToplevelProtocol) { + // 16KB should be enough for most metadata - see bug 1278717 comment #11. + static const size_t kShmemSize = 16 * 1024; + + Shmem shmem; + bool rv = aToplevelProtocol->AllocUnsafeShmem( + kShmemSize, + SharedMemory::TYPE_BASIC, + &shmem); + if (!rv) { + return false; + } + + InitSingletonWithShmem(shmem); + Unused << aToplevelProtocol->SendInitCrashReporter(shmem); + return true; + } + + static void DestroySingleton(); + static RefPtr<CrashReporterClient> GetSingleton(); + + void AnnotateCrashReport(const nsCString& aKey, const nsCString& aData); + void AppendAppNotes(const nsCString& aData); + +private: + explicit CrashReporterClient(const Shmem& aShmem); + ~CrashReporterClient(); + + static void InitSingletonWithShmem(const Shmem& aShmem); + +private: + static StaticMutex sLock; + static StaticRefPtr<CrashReporterClient> sClientSingleton; + +private: + UniquePtr<CrashReporterMetadataShmem> mMetadata; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_CrashReporterClient_h + diff --git a/ipc/glue/CrashReporterHost.cpp b/ipc/glue/CrashReporterHost.cpp new file mode 100644 index 000000000..76052ae66 --- /dev/null +++ b/ipc/glue/CrashReporterHost.cpp @@ -0,0 +1,128 @@ +/* -*- 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 "CrashReporterMetadataShmem.h" +#include "mozilla/Sprintf.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/Telemetry.h" +#ifdef MOZ_CRASHREPORTER +# include "nsICrashService.h" +#endif + +namespace mozilla { +namespace ipc { + +CrashReporterHost::CrashReporterHost(GeckoProcessType aProcessType, + const Shmem& aShmem) + : mProcessType(aProcessType), + mShmem(aShmem), + mStartTime(::time(nullptr)) +{ +} + +#ifdef MOZ_CRASHREPORTER +void +CrashReporterHost::GenerateCrashReport(RefPtr<nsIFile> aCrashDump) +{ + nsString dumpID; + if (!CrashReporter::GetIDFromMinidump(aCrashDump, dumpID)) { + return; + } + + CrashReporter::AnnotationTable notes; + + nsAutoCString type; + switch (mProcessType) { + case GeckoProcessType_Content: + type = NS_LITERAL_CSTRING("content"); + break; + case GeckoProcessType_Plugin: + case GeckoProcessType_GMPlugin: + type = NS_LITERAL_CSTRING("plugin"); + break; + case GeckoProcessType_GPU: + type = NS_LITERAL_CSTRING("gpu"); + break; + default: + NS_ERROR("unknown process type"); + break; + } + notes.Put(NS_LITERAL_CSTRING("ProcessType"), type); + + char startTime[32]; + SprintfLiteral(startTime, "%lld", static_cast<long long>(mStartTime)); + notes.Put(NS_LITERAL_CSTRING("StartupTime"), nsDependentCString(startTime)); + + CrashReporterMetadataShmem::ReadAppNotes(mShmem, ¬es); + + CrashReporter::AppendExtraData(dumpID, notes); + NotifyCrashService(mProcessType, dumpID, ¬es); +} + +/* static */ void +CrashReporterHost::NotifyCrashService(GeckoProcessType aProcessType, + const nsString& aChildDumpID, + const AnnotationTable* aNotes) +{ + if (!NS_IsMainThread()) { + RefPtr<Runnable> runnable = NS_NewRunnableFunction([=] () -> void { + CrashReporterHost::NotifyCrashService(aProcessType, aChildDumpID, aNotes); + }); + RefPtr<nsIThread> mainThread = do_GetMainThread(); + SyncRunnable::DispatchToThread(mainThread, runnable); + return; + } + + MOZ_ASSERT(!aChildDumpID.IsEmpty()); + + nsCOMPtr<nsICrashService> crashService = + do_GetService("@mozilla.org/crashservice;1"); + if (!crashService) { + return; + } + + int32_t processType; + int32_t crashType = nsICrashService::CRASH_TYPE_CRASH; + + nsCString telemetryKey; + + switch (aProcessType) { + case GeckoProcessType_Content: + processType = nsICrashService::PROCESS_TYPE_CONTENT; + telemetryKey.AssignLiteral("content"); + break; + case GeckoProcessType_Plugin: { + processType = nsICrashService::PROCESS_TYPE_PLUGIN; + telemetryKey.AssignLiteral("plugin"); + nsAutoCString val; + if (aNotes->Get(NS_LITERAL_CSTRING("PluginHang"), &val) && + val.Equals(NS_LITERAL_CSTRING("1"))) { + crashType = nsICrashService::CRASH_TYPE_HANG; + telemetryKey.AssignLiteral("pluginhang"); + } + break; + } + case GeckoProcessType_GMPlugin: + processType = nsICrashService::PROCESS_TYPE_GMPLUGIN; + telemetryKey.AssignLiteral("gmplugin"); + break; + case GeckoProcessType_GPU: + processType = nsICrashService::PROCESS_TYPE_GPU; + telemetryKey.AssignLiteral("gpu"); + break; + default: + NS_ERROR("unknown process type"); + return; + } + + crashService->AddCrash(processType, crashType, aChildDumpID); + Telemetry::Accumulate(Telemetry::SUBPROCESS_CRASHES_WITH_DUMP, telemetryKey, 1); +} +#endif + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/CrashReporterHost.h b/ipc/glue/CrashReporterHost.h new file mode 100644 index 000000000..36c5923c2 --- /dev/null +++ b/ipc/glue/CrashReporterHost.h @@ -0,0 +1,63 @@ +/* -*- 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 "mozilla/UniquePtr.h" +#include "mozilla/ipc/Shmem.h" +#include "base/process.h" +#include "nsExceptionHandler.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, and simply +// holds the metadata shmem alive until the process ends. When the process +// terminates abnormally, the top-level should call GenerateCrashReport to +// automatically integrate metadata. +class CrashReporterHost +{ + typedef mozilla::ipc::Shmem Shmem; + typedef CrashReporter::AnnotationTable AnnotationTable; + +public: + CrashReporterHost(GeckoProcessType aProcessType, const Shmem& aShmem); + +#ifdef MOZ_CRASHREPORTER + void GenerateCrashReport(base::ProcessId aPid) { + RefPtr<nsIFile> crashDump; + if (!XRE_TakeMinidumpForChild(aPid, getter_AddRefs(crashDump), nullptr)) { + return; + } + GenerateCrashReport(crashDump); + } + + // This is a static helper function to notify the crash service that a + // crash has occurred. When PCrashReporter is removed, we can make this + // a member function. 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 NotifyCrashService( + GeckoProcessType aProcessType, + const nsString& aChildDumpID, + const AnnotationTable* aNotes); +#endif + +private: + void GenerateCrashReport(RefPtr<nsIFile> aCrashDump); + +private: + GeckoProcessType mProcessType; + Shmem mShmem; + time_t mStartTime; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_CrashReporterHost_h diff --git a/ipc/glue/CrashReporterMetadataShmem.cpp b/ipc/glue/CrashReporterMetadataShmem.cpp new file mode 100644 index 000000000..f579d5bb0 --- /dev/null +++ b/ipc/glue/CrashReporterMetadataShmem.cpp @@ -0,0 +1,235 @@ +/* -*- 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 "CrashReporterMetadataShmem.h" +#include "mozilla/Attributes.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace ipc { + +enum class EntryType : uint8_t { + None, + Annotation, +}; + +CrashReporterMetadataShmem::CrashReporterMetadataShmem(const Shmem& aShmem) + : mShmem(aShmem) +{ + MOZ_COUNT_CTOR(CrashReporterMetadataShmem); +} + +CrashReporterMetadataShmem::~CrashReporterMetadataShmem() +{ + MOZ_COUNT_DTOR(CrashReporterMetadataShmem); +} + +void +CrashReporterMetadataShmem::AnnotateCrashReport(const nsCString& aKey, const nsCString& aData) +{ + mNotes.Put(aKey, aData); + SyncNotesToShmem(); +} + +void +CrashReporterMetadataShmem::AppendAppNotes(const nsCString& aData) +{ + mAppNotes.Append(aData); + mNotes.Put(NS_LITERAL_CSTRING("Notes"), mAppNotes); + SyncNotesToShmem(); +} + +class MOZ_STACK_CLASS MetadataShmemWriter +{ +public: + explicit MetadataShmemWriter(const Shmem& aShmem) + : mCursor(aShmem.get<uint8_t>()), + mEnd(mCursor + aShmem.Size<uint8_t>()) + { + *mCursor = uint8_t(EntryType::None); + } + + MOZ_MUST_USE bool WriteAnnotation(const nsCString& aKey, const nsCString& aValue) { + // This shouldn't happen because Commit() guarantees mCursor < mEnd. But + // we might as well be safe. + if (mCursor >= mEnd) { + return false; + } + + // Save the current position so we can write the entry type if the entire + // entry fits. + uint8_t* start = mCursor++; + if (!Write(aKey) || !Write(aValue)) { + return false; + } + return Commit(start, EntryType::Annotation); + } + +private: + // On success, append a new terminal byte. On failure, rollback the cursor. + MOZ_MUST_USE bool Commit(uint8_t* aStart, EntryType aType) { + MOZ_ASSERT(aStart < mEnd); + MOZ_ASSERT(EntryType(*aStart) == EntryType::None); + + if (mCursor >= mEnd) { + // No room for a terminating byte - rollback. + mCursor = aStart; + return false; + } + + // Commit the entry and write a new terminal byte. + *aStart = uint8_t(aType); + *mCursor = uint8_t(EntryType::None); + return true; + } + + MOZ_MUST_USE bool Write(const nsCString& aString) { + // 32-bit length is okay since our shmems are very small (16K), + // a huge write would fail anyway. + return Write(static_cast<uint32_t>(aString.Length())) && + Write(aString.get(), aString.Length()); + } + + template <typename T> + MOZ_MUST_USE bool Write(const T& aT) { + return Write(&aT, sizeof(T)); + } + + MOZ_MUST_USE bool Write(const void* aData, size_t aLength) { + if (size_t(mEnd - mCursor) < aLength) { + return false; + } + memcpy(mCursor, aData, aLength); + mCursor += aLength; + return true; + } + + private: + // The cursor (beginning at start) always points to a single byte + // representing the next EntryType. An EntryType is either None, + // indicating there are no more entries, or Annotation, meaning + // two strings follow. + // + // Strings are written as a 32-bit length and byte sequence. After each new + // entry, a None entry is always appended, and a subsequent entry will + // overwrite this byte. + uint8_t* mCursor; + uint8_t* mEnd; +}; + +void +CrashReporterMetadataShmem::SyncNotesToShmem() +{ + MetadataShmemWriter writer(mShmem); + + for (auto it = mNotes.Iter(); !it.Done(); it.Next()) { + nsCString key = nsCString(it.Key()); + nsCString value = nsCString(it.Data()); + if (!writer.WriteAnnotation(key, value)) { + return; + } + } +} + +// Helper class to iterate over metadata entries encoded in shmem. +class MOZ_STACK_CLASS MetadataShmemReader +{ +public: + explicit MetadataShmemReader(const Shmem& aShmem) + : mEntryType(EntryType::None) + { + mCursor = aShmem.get<uint8_t>(); + mEnd = mCursor + aShmem.Size<uint8_t>(); + + // Advance to the first item, if any. + Next(); + } + + bool Done() const { + return mCursor >= mEnd || Type() == EntryType::None; + } + EntryType Type() const { + return mEntryType; + } + void Next() { + if (mCursor < mEnd) { + mEntryType = EntryType(*mCursor++); + } else { + mEntryType = EntryType::None; + } + } + + bool Read(nsCString& aOut) { + uint32_t length = 0; + if (!Read(&length)) { + return false; + } + + const uint8_t* src = Read(length); + if (!src) { + return false; + } + + aOut.Assign((const char *)src, length); + return true; + } + +private: + template <typename T> + bool Read(T* aOut) { + return Read(aOut, sizeof(T)); + } + bool Read(void* aOut, size_t aLength) { + const uint8_t* src = Read(aLength); + if (!src) { + return false; + } + memcpy(aOut, src, aLength); + return true; + } + + // If buffer has |aLength| bytes, return cursor and then advance it. + // Otherwise, return null. + const uint8_t* Read(size_t aLength) { + if (size_t(mEnd - mCursor) < aLength) { + return nullptr; + } + const uint8_t* result = mCursor; + mCursor += aLength; + return result; + } + +private: + const uint8_t* mCursor; + const uint8_t* mEnd; + EntryType mEntryType; +}; + +#ifdef MOZ_CRASHREPORTER +void +CrashReporterMetadataShmem::ReadAppNotes(const Shmem& aShmem, CrashReporter::AnnotationTable* aNotes) +{ + for (MetadataShmemReader reader(aShmem); !reader.Done(); reader.Next()) { + switch (reader.Type()) { + case EntryType::Annotation: { + nsCString key, value; + if (!reader.Read(key) || !reader.Read(value)) { + return; + } + + aNotes->Put(key, value); + break; + } + default: + NS_ASSERTION(false, "Unknown metadata entry type"); + break; + } + } +} +#endif + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/CrashReporterMetadataShmem.h b/ipc/glue/CrashReporterMetadataShmem.h new file mode 100644 index 000000000..d2d8670a2 --- /dev/null +++ b/ipc/glue/CrashReporterMetadataShmem.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_CrashReporterMetadataShmem_h +#define mozilla_ipc_CrashReporterMetadataShmem_h + +#include <stdint.h> +#include "mozilla/ipc/Shmem.h" +#include "nsExceptionHandler.h" +#include "nsString.h" + +namespace mozilla { +namespace ipc { + +class CrashReporterMetadataShmem +{ + typedef mozilla::ipc::Shmem Shmem; + typedef CrashReporter::AnnotationTable AnnotationTable; + +public: + explicit CrashReporterMetadataShmem(const Shmem& aShmem); + ~CrashReporterMetadataShmem(); + + // Metadata writers. These must only be called in child processes. + void AnnotateCrashReport(const nsCString& aKey, const nsCString& aData); + void AppendAppNotes(const nsCString& aData); + +#ifdef MOZ_CRASHREPORTER + static void ReadAppNotes(const Shmem& aShmem, CrashReporter::AnnotationTable* aNotes); +#endif + +private: + void SyncNotesToShmem(); + +private: + Shmem mShmem; + + AnnotationTable mNotes; + nsCString mAppNotes; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_CrashReporterMetadataShmem_h diff --git a/ipc/glue/CrossProcessMutex.h b/ipc/glue/CrossProcessMutex.h new file mode 100644 index 000000000..b7379a9bb --- /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 BaseAutoLock<CrossProcessMutex> CrossProcessMutexAutoLock; +typedef 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 000000000..79775a045 --- /dev/null +++ b/ipc/glue/CrossProcessMutex_posix.cpp @@ -0,0 +1,144 @@ +/* -*- 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) +{ + 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)) { + 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 000000000..6e8beb15b --- /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*) +{ + NS_RUNTIMEABORT("Cross-process mutices not allowed on this platform."); +} + +CrossProcessMutex::CrossProcessMutex(CrossProcessMutexHandle) +{ + NS_RUNTIMEABORT("Cross-process mutices not allowed on this platform."); +} + +CrossProcessMutex::~CrossProcessMutex() +{ + NS_RUNTIMEABORT("Cross-process mutices not allowed on this platform - woah! We should've aborted by now!"); +} + +void +CrossProcessMutex::Lock() +{ + NS_RUNTIMEABORT("Cross-process mutices not allowed on this platform - woah! We should've aborted by now!"); +} + +void +CrossProcessMutex::Unlock() +{ + NS_RUNTIMEABORT("Cross-process mutices not allowed on this platform - woah! We should've aborted by now!"); +} + +CrossProcessMutexHandle +CrossProcessMutex::ShareToProcess(base::ProcessId aTargetPid) +{ + NS_RUNTIMEABORT("Cross-process mutices not allowed on this platform - woah! We should've aborted by now!"); + return 0; +} + +} diff --git a/ipc/glue/CrossProcessMutex_windows.cpp b/ipc/glue/CrossProcessMutex_windows.cpp new file mode 100644 index 000000000..bc818562b --- /dev/null +++ b/ipc/glue/CrossProcessMutex_windows.cpp @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <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) { + NS_RUNTIMEABORT("This shouldn't happen - failed to create mutex!"); + } + MOZ_COUNT_CTOR(CrossProcessMutex); +} + +CrossProcessMutex::CrossProcessMutex(CrossProcessMutexHandle aHandle) +{ + DWORD flags; + if (!::GetHandleInformation(aHandle, &flags)) { + NS_RUNTIMEABORT("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; +} + +} diff --git a/ipc/glue/Faulty.cpp b/ipc/glue/Faulty.cpp new file mode 100644 index 000000000..5c326cd3f --- /dev/null +++ b/ipc/glue/Faulty.cpp @@ -0,0 +1,654 @@ +/* -*- 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/Faulty.h" +#include <cerrno> +#include <prinrval.h> +#include "nsXULAppAPI.h" +#include "base/string_util.h" +#include "chrome/common/ipc_message.h" +#include "chrome/common/ipc_channel.h" +#include "prenv.h" +#include "mozilla/TypeTraits.h" +#include <cmath> +#include <climits> + +namespace mozilla { +namespace ipc { + +const unsigned int Faulty::sDefaultProbability = Faulty::DefaultProbability(); +const bool Faulty::sIsLoggingEnabled = Faulty::Logging(); + +/** + * RandomNumericValue generates negative and positive integrals. + */ +template <typename T> +T RandomIntegral() +{ + static_assert(mozilla::IsIntegral<T>::value == true, + "T must be an integral type"); + double r = static_cast<double>(random() % ((sizeof(T) * CHAR_BIT) + 1)); + T x = static_cast<T>(pow(2.0, r)) - 1; + if (std::numeric_limits<T>::is_signed && random() % 2 == 0) { + return (x * -1) - 1; + } + return x; +} + +/** + * RandomNumericLimit returns either the min or max limit of an arithmetic + * data type. + */ +template <typename T> +T RandomNumericLimit() { + static_assert(mozilla::IsArithmetic<T>::value == true, + "T must be an arithmetic type"); + return random() % 2 == 0 ? std::numeric_limits<T>::min() + : std::numeric_limits<T>::max(); +} + +/** + * RandomIntegerRange returns a random integral within a user defined range. + */ +template <typename T> +T RandomIntegerRange(T min, T max) +{ + static_assert(mozilla::IsIntegral<T>::value == true, + "T must be an integral type"); + MOZ_ASSERT(min < max); + return static_cast<T>(random() % (max - min) + min); +} + +/** + * RandomFloatingPointRange returns a random floating-point number within a + * user defined range. + */ +template <typename T> +T RandomFloatingPointRange(T min, T max) +{ + static_assert(mozilla::IsFloatingPoint<T>::value == true, + "T must be a floating point type"); + MOZ_ASSERT(min < max); + T x = static_cast<T>(random()) / static_cast<T>(RAND_MAX); + return min + x * (max - min); +} + +/** + * RandomFloatingPoint returns a random floating-point number. + */ +template <typename T> +T RandomFloatingPoint() +{ + static_assert(mozilla::IsFloatingPoint<T>::value == true, + "T must be a floating point type"); + int radix = RandomIntegerRange<int>(std::numeric_limits<T>::min_exponent, + std::numeric_limits<T>::max_exponent); + T x = static_cast<T>(pow(2.0, static_cast<double>(radix))); + return x * RandomFloatingPointRange<T>(1.0, 2.0); +} + +/** + * FuzzIntegralType mutates an incercepted integral type of a pickled message. + */ +template <typename T> +void FuzzIntegralType(T* v, bool largeValues) +{ + static_assert(mozilla::IsIntegral<T>::value == true, + "T must be an integral type"); + switch (random() % 6) { + case 0: + if (largeValues) { + (*v) = RandomIntegral<T>(); + break; + } + // Fall through + case 1: + if (largeValues) { + (*v) = RandomNumericLimit<T>(); + break; + } + // Fall through + case 2: + if (largeValues) { + (*v) = RandomIntegerRange<T>(std::numeric_limits<T>::min(), + std::numeric_limits<T>::max()); + break; + } + // Fall through + default: + switch(random() % 2) { + case 0: + // Prevent underflow + if (*v != std::numeric_limits<T>::min()) { + (*v)--; + break; + } + // Fall through + case 1: + // Prevent overflow + if (*v != std::numeric_limits<T>::max()) { + (*v)++; + break; + } + } + } +} + +/** + * FuzzFloatingPointType mutates an incercepted floating-point type of a + * pickled message. + */ +template <typename T> +void FuzzFloatingPointType(T* v, bool largeValues) +{ + static_assert(mozilla::IsFloatingPoint<T>::value == true, + "T must be a floating point type"); + switch (random() % 6) { + case 0: + if (largeValues) { + (*v) = RandomNumericLimit<T>(); + break; + } + // Fall through + case 1: + if (largeValues) { + (*v) = RandomFloatingPointRange<T>(std::numeric_limits<T>::min(), + std::numeric_limits<T>::max()); + break; + } + // Fall through + default: + (*v) = RandomFloatingPoint<T>(); + } +} + +/** + * FuzzStringType mutates an incercepted string type of a pickled message. + */ +template <typename T> +void FuzzStringType(T& v, const T& literal1, const T& literal2) +{ + switch (random() % 5) { + case 4: + v = v + v; + // Fall through + case 3: + v = v + v; + // Fall through + case 2: + v = v + v; + break; + case 1: + v += literal1; + break; + case 0: + v = literal2; + break; + } +} + + +Faulty::Faulty() + // Enables the strategy for fuzzing pipes. + : mFuzzPipes(!!PR_GetEnv("FAULTY_PIPE")) + // Enables the strategy for fuzzing pickled messages. + , mFuzzPickle(!!PR_GetEnv("FAULTY_PICKLE")) + // Uses very large values while fuzzing pickled messages. + // This may cause a high amount of malloc_abort() / NS_ABORT_OOM crashes. + , mUseLargeValues(!!PR_GetEnv("FAULTY_LARGE_VALUES")) + // Sets up our target process. + , mIsValidProcessType(IsValidProcessType()) +{ + FAULTY_LOG("Initializing."); + + const char* userSeed = PR_GetEnv("FAULTY_SEED"); + unsigned long randomSeed = static_cast<unsigned long>(PR_IntervalNow()); + if (userSeed) { + long n = std::strtol(userSeed, nullptr, 10); + if (n != 0) { + randomSeed = static_cast<unsigned long>(n); + } + } + srandom(randomSeed); + + FAULTY_LOG("Fuzz probability = %u", sDefaultProbability); + FAULTY_LOG("Random seed = %lu", randomSeed); + FAULTY_LOG("Strategy: pickle = %s", mFuzzPickle ? "enabled" : "disabled"); + FAULTY_LOG("Strategy: pipe = %s", mFuzzPipes ? "enabled" : "disabled"); +} + +// static +bool +Faulty::IsValidProcessType(void) +{ + bool isValidProcessType; + const bool targetChildren = !!PR_GetEnv("FAULTY_CHILDREN"); + const bool targetParent = !!PR_GetEnv("FAULTY_PARENT"); + + if (targetChildren && !targetParent) { + // Fuzz every process type but not the content process. + isValidProcessType = XRE_GetProcessType() != GeckoProcessType_Content; + } else if (targetChildren && targetParent) { + // Fuzz every process type. + isValidProcessType = true; + } else { + // Fuzz the content process only. + isValidProcessType = XRE_GetProcessType() == GeckoProcessType_Content; + } + + // Parent and children are different threads in the same process on + // desktop builds. + if (!isValidProcessType) { + FAULTY_LOG("Invalid process type for pid=%d", getpid()); + } + + return isValidProcessType; +} + +// static +unsigned int +Faulty::DefaultProbability(void) +{ + // Defines the likelihood of fuzzing a message. + const char* probability = PR_GetEnv("FAULTY_PROBABILITY"); + if (probability) { + long n = std::strtol(probability, nullptr, 10); + if (n != 0) { + return n; + } + } + return FAULTY_DEFAULT_PROBABILITY; +} + +// static +bool +Faulty::Logging(void) +{ + // Enables logging of sendmsg() calls even in optimized builds. + return !!PR_GetEnv("FAULTY_ENABLE_LOGGING"); +} + +unsigned int +Faulty::Random(unsigned int aMax) +{ + MOZ_ASSERT(aMax > 0); + return static_cast<unsigned int>(random() % aMax); +} + +bool +Faulty::GetChance(unsigned int aProbability) +{ + return Random(aProbability) == 0; +} + +// +// Strategy: Pipes +// + +void +Faulty::MaybeCollectAndClosePipe(int aPipe, unsigned int aProbability) +{ + if (!mFuzzPipes) { + return; + } + + if (aPipe > -1) { + FAULTY_LOG("collecting pipe %d to bucket of pipes (count: %ld)", + aPipe, mFds.size()); + mFds.insert(aPipe); + } + + if (mFds.size() > 0 && GetChance(aProbability)) { + std::set<int>::iterator it(mFds.begin()); + std::advance(it, Random(mFds.size())); + FAULTY_LOG("trying to close collected pipe: %d", *it); + errno = 0; + while ((close(*it) == -1 && (errno == EINTR))) { + ; + } + FAULTY_LOG("pipe status after attempt to close: %d", errno); + mFds.erase(it); + } +} + +// +// Strategy: Pickle +// + +void +Faulty::MutateBool(bool* aValue) +{ + *aValue = !(*aValue); +} + +void +Faulty::FuzzBool(bool* aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + bool oldValue = *aValue; + MutateBool(aValue); + FAULTY_LOG("pickle field {bool} of value: %d changed to: %d", + (int)oldValue, (int)*aValue); + } + } +} + +void +Faulty::MutateChar(char* aValue) +{ + FuzzIntegralType<char>(aValue, true); +} + +void +Faulty::FuzzChar(char* aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + char oldValue = *aValue; + MutateChar(aValue); + FAULTY_LOG("pickle field {char} of value: %c changed to: %c", + oldValue, *aValue); + } + } +} + +void +Faulty::MutateUChar(unsigned char* aValue) +{ + FuzzIntegralType<unsigned char>(aValue, true); +} + +void +Faulty::FuzzUChar(unsigned char* aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + unsigned char oldValue = *aValue; + MutateUChar(aValue); + FAULTY_LOG("pickle field {unsigned char} of value: %u changed to: %u", + oldValue, *aValue); + } + } +} + +void +Faulty::MutateInt16(int16_t* aValue) +{ + FuzzIntegralType<int16_t>(aValue, true); +} + +void +Faulty::FuzzInt16(int16_t* aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + int16_t oldValue = *aValue; + MutateInt16(aValue); + FAULTY_LOG("pickle field {Int16} of value: %d changed to: %d", + oldValue, *aValue); + } + } +} + +void +Faulty::MutateUInt16(uint16_t* aValue) +{ + FuzzIntegralType<uint16_t>(aValue, true); +} + +void +Faulty::FuzzUInt16(uint16_t* aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + uint16_t oldValue = *aValue; + MutateUInt16(aValue); + FAULTY_LOG("pickle field {UInt16} of value: %d changed to: %d", + oldValue, *aValue); + } + } +} + +void +Faulty::MutateInt(int* aValue) +{ + FuzzIntegralType<int>(aValue, mUseLargeValues); +} + +void +Faulty::FuzzInt(int* aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + int oldValue = *aValue; + MutateInt(aValue); + FAULTY_LOG("pickle field {int} of value: %d changed to: %d", + oldValue, *aValue); + } + } +} + +void +Faulty::MutateUInt32(uint32_t* aValue) +{ + FuzzIntegralType<uint32_t>(aValue, mUseLargeValues); +} + +void +Faulty::FuzzUInt32(uint32_t* aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + uint32_t oldValue = *aValue; + MutateUInt32(aValue); + FAULTY_LOG("pickle field {UInt32} of value: %u changed to: %u", + oldValue, *aValue); + } + } +} + +void +Faulty::MutateLong(long* aValue) +{ + FuzzIntegralType<long>(aValue, mUseLargeValues); +} + +void +Faulty::FuzzLong(long* aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + long oldValue = *aValue; + MutateLong(aValue); + FAULTY_LOG("pickle field {long} of value: %ld changed to: %ld", + oldValue, *aValue); + } + } +} + +void +Faulty::MutateULong(unsigned long* aValue) +{ + FuzzIntegralType<unsigned long>(aValue, mUseLargeValues); +} + +void +Faulty::FuzzULong(unsigned long* aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + unsigned long oldValue = *aValue; + MutateULong(aValue); + FAULTY_LOG("pickle field {unsigned long} of value: %lu changed to: %lu", + oldValue, *aValue); + } + } +} + +void +Faulty::MutateSize(size_t* aValue) +{ + FuzzIntegralType<size_t>(aValue, mUseLargeValues); +} + +void +Faulty::FuzzSize(size_t* aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + size_t oldValue = *aValue; + MutateSize(aValue); + FAULTY_LOG("pickle field {size_t} of value: %zu changed to: %zu", + oldValue, *aValue); + } + } +} + +void +Faulty::MutateUInt64(uint64_t* aValue) +{ + FuzzIntegralType<uint64_t>(aValue, mUseLargeValues); +} + +void +Faulty::FuzzUInt64(uint64_t* aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + uint64_t oldValue = *aValue; + MutateUInt64(aValue); + FAULTY_LOG("pickle field {UInt64} of value: %llu changed to: %llu", + oldValue, *aValue); + } + } +} + +void +Faulty::MutateInt64(int64_t* aValue) +{ + FuzzIntegralType<int64_t>(aValue, mUseLargeValues); +} + +void +Faulty::FuzzInt64(int64_t* aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + int64_t oldValue = *aValue; + MutateInt64(aValue); + FAULTY_LOG("pickle field {Int64} of value: %lld changed to: %lld", + oldValue, *aValue); + } + } +} + +void +Faulty::MutateDouble(double* aValue) +{ + FuzzFloatingPointType<double>(aValue, mUseLargeValues); +} + +void +Faulty::FuzzDouble(double* aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + double oldValue = *aValue; + MutateDouble(aValue); + FAULTY_LOG("pickle field {double} of value: %f changed to: %f", + oldValue, *aValue); + } + } +} + +void +Faulty::MutateFloat(float* aValue) +{ + FuzzFloatingPointType<float>(aValue, mUseLargeValues); +} + +void +Faulty::FuzzFloat(float* aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + float oldValue = *aValue; + MutateFloat(aValue); + FAULTY_LOG("pickle field {float} of value: %f changed to: %f", + oldValue, *aValue); + } + } +} + +void +Faulty::FuzzString(std::string& aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + std::string oldValue = aValue; + FuzzStringType<std::string>(aValue, "xoferiF", std::string()); + FAULTY_LOG("pickle field {string} of value: %s changed to: %s", + oldValue.c_str(), aValue.c_str()); + } + } +} + +void +Faulty::FuzzWString(std::wstring& aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + std::wstring oldValue = aValue; + FAULTY_LOG("pickle field {wstring}"); + FuzzStringType<std::wstring>(aValue, L"xoferiF", std::wstring()); + } + } +} + +void +Faulty::FuzzString16(string16& aValue, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + string16 oldValue = aValue; + FAULTY_LOG("pickle field {string16}"); + FuzzStringType<string16>(aValue, + string16(ASCIIToUTF16(std::string("xoferiF"))), + string16(ASCIIToUTF16(std::string()))); + } + } +} + +void +Faulty::FuzzBytes(void* aData, int aLength, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + FAULTY_LOG("pickle field {bytes}"); + // Too destructive. |WriteBytes| is used in many of the above data + // types as base function. + //FuzzData(static_cast<char*>(aData), aLength); + } + } +} + +void +Faulty::FuzzData(std::string& aValue, int aLength, unsigned int aProbability) +{ + if (mIsValidProcessType) { + if (mFuzzPickle && GetChance(aProbability)) { + FAULTY_LOG("pickle field {data}"); + for (int i = 0; i < aLength; ++i) { + if (GetChance(aProbability)) { + FuzzIntegralType<char>(&aValue[i], true); + } + } + } + } +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/Faulty.h b/ipc/glue/Faulty.h new file mode 100644 index 000000000..8afe86b06 --- /dev/null +++ b/ipc/glue/Faulty.h @@ -0,0 +1,100 @@ +/* -*- 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_Faulty_h +#define mozilla_ipc_Faulty_h + +#include <set> +#include <string> +#include "nsDebug.h" +#include "base/string16.h" +#include "base/singleton.h" + +#define FAULTY_DEFAULT_PROBABILITY 1000 +#define FAULTY_LOG(fmt, args...) \ + if (mozilla::ipc::Faulty::IsLoggingEnabled()) { \ + printf_stderr("[Faulty] " fmt "\n", ## args); \ + } + +namespace IPC { + // Needed for blacklisting messages. + class Message; +} + +namespace mozilla { +namespace ipc { + +class Faulty +{ + public: + // Used as a default argument for the Fuzz|datatype| methods. + static const unsigned int sDefaultProbability; + + static unsigned int DefaultProbability(void); + static bool Logging(void); + static bool IsLoggingEnabled(void) { return sIsLoggingEnabled; } + + void FuzzBool(bool* aValue, unsigned int aProbability=sDefaultProbability); + void FuzzChar(char* aValue, unsigned int aProbability=sDefaultProbability); + void FuzzUChar(unsigned char* aValue, unsigned int aProbability=sDefaultProbability); + void FuzzInt16(int16_t* aValue, unsigned int aProbability=sDefaultProbability); + void FuzzUInt16(uint16_t* aValue, unsigned int aProbability=sDefaultProbability); + void FuzzInt(int* aValue, unsigned int aProbability=sDefaultProbability); + void FuzzUInt32(uint32_t* aValue, unsigned int aProbability=sDefaultProbability); + void FuzzLong(long* aValue, unsigned int aProbability=sDefaultProbability); + void FuzzULong(unsigned long* aValue, unsigned int aProbability=sDefaultProbability); + void FuzzInt64(int64_t* aValue, unsigned int aProbability=sDefaultProbability); + void FuzzUInt64(uint64_t* aValue, unsigned int aProbability=sDefaultProbability); + void FuzzSize(size_t* aValue, unsigned int aProbability=sDefaultProbability); + void FuzzFloat(float* aValue, unsigned int aProbability=sDefaultProbability); + void FuzzDouble(double* aValue, unsigned int aProbability=sDefaultProbability); + void FuzzString(std::string& aValue, unsigned int aProbability=sDefaultProbability); + void FuzzWString(std::wstring& aValue, unsigned int aProbability=sDefaultProbability); + void FuzzString16(string16& aValue, unsigned int aProbability=sDefaultProbability); + void FuzzData(std::string& aData, int aLength, unsigned int aProbability=sDefaultProbability); + void FuzzBytes(void* aData, int aLength, unsigned int aProbability=sDefaultProbability); + + void MaybeCollectAndClosePipe(int aPipe, unsigned int aProbability=sDefaultProbability); + + private: + std::set<int> mFds; + + const bool mFuzzPipes; + const bool mFuzzPickle; + const bool mUseLargeValues; + const bool mIsValidProcessType; + + static const bool sIsLoggingEnabled; + + Faulty(); + friend struct DefaultSingletonTraits<Faulty>; + DISALLOW_EVIL_CONSTRUCTORS(Faulty); + + static bool IsValidProcessType(void); + + unsigned int Random(unsigned int aMax); + bool GetChance(unsigned int aProbability); + + void MutateBool(bool* aValue); + void MutateChar(char* aValue); + void MutateUChar(unsigned char* aValue); + void MutateInt16(int16_t* aValue); + void MutateUInt16(uint16_t* aValue); + void MutateInt(int* aValue); + void MutateUInt32(uint32_t* aValue); + void MutateLong(long* aValue); + void MutateULong(unsigned long* aValue); + void MutateInt64(int64_t* aValue); + void MutateUInt64(uint64_t* aValue); + void MutateSize(size_t* aValue); + void MutateFloat(float* aValue); + void MutateDouble(double* aValue); +}; + +} // namespace ipc +} // namespace mozilla + +#endif diff --git a/ipc/glue/FileDescriptor.cpp b/ipc/glue/FileDescriptor.cpp new file mode 100644 index 000000000..1a8743d86 --- /dev/null +++ b/ipc/glue/FileDescriptor.cpp @@ -0,0 +1,226 @@ +/* -*- 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/Move.h" +#include "nsDebug.h" + +#ifdef XP_WIN + +#include <windows.h> +#include "ProtocolUtils.h" +#define INVALID_HANDLE INVALID_HANDLE_VALUE + +#else // XP_WIN + +#include <unistd.h> + +#ifndef OS_POSIX +#define OS_POSIX +#endif + +#include "base/eintr_wrapper.h" +#define INVALID_HANDLE -1 + +#endif // XP_WIN + +using mozilla::ipc::FileDescriptor; + +FileDescriptor::FileDescriptor() + : mHandle(INVALID_HANDLE) +{ +} + +FileDescriptor::FileDescriptor(const FileDescriptor& aOther) + : mHandle(INVALID_HANDLE) +{ + Assign(aOther); +} + +FileDescriptor::FileDescriptor(FileDescriptor&& aOther) + : mHandle(INVALID_HANDLE) +{ + *this = mozilla::Move(aOther); +} + +FileDescriptor::FileDescriptor(PlatformHandleType aHandle) + : mHandle(INVALID_HANDLE) +{ + mHandle = Clone(aHandle); +} + +FileDescriptor::FileDescriptor(const IPDLPrivate&, const PickleType& aPickle) + : mHandle(INVALID_HANDLE) +{ +#ifdef XP_WIN + mHandle = aPickle; +#else + mHandle = aPickle.fd; +#endif +} + +FileDescriptor::~FileDescriptor() +{ + Close(); +} + +FileDescriptor& +FileDescriptor::operator=(const FileDescriptor& aOther) +{ + if (this != &aOther) { + Assign(aOther); + } + return *this; +} + +FileDescriptor& +FileDescriptor::operator=(FileDescriptor&& aOther) +{ + if (this != &aOther) { + Close(); + mHandle = aOther.mHandle; + aOther.mHandle = INVALID_HANDLE; + } + return *this; +} + +bool +FileDescriptor::IsValid() const +{ + return IsValid(mHandle); +} + +void +FileDescriptor::Assign(const FileDescriptor& aOther) +{ + Close(); + mHandle = Clone(aOther.mHandle); +} + +void +FileDescriptor::Close() +{ + Close(mHandle); + mHandle = INVALID_HANDLE; +} + +FileDescriptor::PickleType +FileDescriptor::ShareTo(const FileDescriptor::IPDLPrivate&, + FileDescriptor::ProcessId aTargetPid) const +{ + PlatformHandleType newHandle; +#ifdef XP_WIN + if (IsValid()) { + if (mozilla::ipc::DuplicateHandle(mHandle, aTargetPid, &newHandle, 0, + DUPLICATE_SAME_ACCESS)) { + return newHandle; + } + NS_WARNING("Failed to duplicate file handle for other process!"); + } + return INVALID_HANDLE; +#else // XP_WIN + if (IsValid()) { + newHandle = dup(mHandle); + if (IsValid(newHandle)) { + 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!"); +} + +FileDescriptor::UniquePlatformHandle +FileDescriptor::ClonePlatformHandle() const +{ + return UniquePlatformHandle(Clone(mHandle)); +} + +bool +FileDescriptor::operator==(const FileDescriptor& aOther) const +{ + return mHandle == aOther.mHandle; +} + +// static +bool +FileDescriptor::IsValid(PlatformHandleType aHandle) +{ + return aHandle != INVALID_HANDLE; +} + +// static +FileDescriptor::PlatformHandleType +FileDescriptor::Clone(PlatformHandleType aHandle) +{ + if (!IsValid(aHandle)) { + return INVALID_HANDLE; + } + FileDescriptor::PlatformHandleType newHandle; +#ifdef XP_WIN + if (::DuplicateHandle(GetCurrentProcess(), aHandle, GetCurrentProcess(), + &newHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) { +#else // XP_WIN + if ((newHandle = dup(aHandle)) != INVALID_HANDLE) { +#endif + return newHandle; + } + NS_WARNING("Failed to duplicate file handle for current process!"); + return INVALID_HANDLE; +} + +// static +void +FileDescriptor::Close(PlatformHandleType aHandle) +{ + if (IsValid(aHandle)) { +#ifdef XP_WIN + if (!CloseHandle(aHandle)) { + NS_WARNING("Failed to close file handle for current process!"); + } +#else // XP_WIN + HANDLE_EINTR(close(aHandle)); +#endif + } +} + +FileDescriptor::PlatformHandleHelper::PlatformHandleHelper(FileDescriptor::PlatformHandleType aHandle) + :mHandle(aHandle) +{ +} + +FileDescriptor::PlatformHandleHelper::PlatformHandleHelper(std::nullptr_t) + :mHandle(INVALID_HANDLE) +{ +} + +bool +FileDescriptor::PlatformHandleHelper::operator!=(std::nullptr_t) const +{ + return mHandle != INVALID_HANDLE; +} + +FileDescriptor::PlatformHandleHelper::operator FileDescriptor::PlatformHandleType () const +{ + return mHandle; +} + +#ifdef XP_WIN +FileDescriptor::PlatformHandleHelper::operator std::intptr_t () const +{ + return reinterpret_cast<std::intptr_t>(mHandle); +} +#endif + +void +FileDescriptor::PlatformHandleDeleter::operator()(FileDescriptor::PlatformHandleHelper aHelper) +{ + FileDescriptor::Close(aHelper); +} diff --git a/ipc/glue/FileDescriptor.h b/ipc/glue/FileDescriptor.h new file mode 100644 index 000000000..b30c5db1a --- /dev/null +++ b/ipc/glue/FileDescriptor.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ipc_FileDescriptor_h +#define mozilla_ipc_FileDescriptor_h + +#include "base/basictypes.h" +#include "base/process.h" +#include "mozilla/UniquePtr.h" + +#ifdef XP_WIN +// Need the HANDLE typedef. +#include <winnt.h> +#else +#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; + +#ifdef XP_WIN + typedef HANDLE PlatformHandleType; + typedef HANDLE PickleType; +#else + typedef int PlatformHandleType; + typedef base::FileDescriptor PickleType; +#endif + + struct PlatformHandleHelper + { + MOZ_IMPLICIT PlatformHandleHelper(PlatformHandleType aHandle); + MOZ_IMPLICIT PlatformHandleHelper(std::nullptr_t); + bool operator != (std::nullptr_t) const; + operator PlatformHandleType () const; +#ifdef XP_WIN + operator std::intptr_t () const; +#endif + private: + PlatformHandleType mHandle; + }; + struct PlatformHandleDeleter + { + typedef PlatformHandleHelper pointer; + void operator () (PlatformHandleHelper aHelper); + }; + typedef UniquePtr<PlatformHandleType, PlatformHandleDeleter> UniquePlatformHandle; + + // 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); + + // 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; + + // Only used in nsTArray. + bool + operator==(const FileDescriptor& aOther) const; + +private: + friend struct PlatformHandleTrait; + + void + Assign(const FileDescriptor& aOther); + + void + Close(); + + static bool + IsValid(PlatformHandleType aHandle); + + static PlatformHandleType + Clone(PlatformHandleType aHandle); + + static void + Close(PlatformHandleType aHandle); + + PlatformHandleType 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 000000000..95fcbcf19 --- /dev/null +++ b/ipc/glue/FileDescriptorSetChild.cpp @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileDescriptorSetChild.h" + +namespace mozilla { +namespace ipc { + +FileDescriptorSetChild::FileDescriptorSetChild( + const FileDescriptor& aFileDescriptor) +{ + mFileDescriptors.AppendElement(aFileDescriptor); +} + +FileDescriptorSetChild::~FileDescriptorSetChild() +{ + MOZ_ASSERT(mFileDescriptors.IsEmpty()); +} + +void +FileDescriptorSetChild::ForgetFileDescriptors( + nsTArray<FileDescriptor>& aFileDescriptors) +{ + aFileDescriptors.Clear(); + mFileDescriptors.SwapElements(aFileDescriptors); +} + +bool +FileDescriptorSetChild::RecvAddFileDescriptor( + const FileDescriptor& aFileDescriptor) +{ + mFileDescriptors.AppendElement(aFileDescriptor); + return true; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/FileDescriptorSetChild.h b/ipc/glue/FileDescriptorSetChild.h new file mode 100644 index 000000000..5c334a8f7 --- /dev/null +++ b/ipc/glue/FileDescriptorSetChild.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ipc_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 nsIContentChild; + +} // namespace dom + +namespace ipc { + +class BackgroundChildImpl; +class FileDescriptor; + +class FileDescriptorSetChild final + : public PFileDescriptorSetChild +{ + friend class BackgroundChildImpl; + friend class mozilla::dom::nsIContentChild; + + nsTArray<FileDescriptor> mFileDescriptors; + +public: + void + ForgetFileDescriptors(nsTArray<FileDescriptor>& aFileDescriptors); + +private: + explicit FileDescriptorSetChild(const FileDescriptor& aFileDescriptor); + ~FileDescriptorSetChild(); + + virtual bool + RecvAddFileDescriptor(const FileDescriptor& aFileDescriptor) override; +}; + +} // 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 000000000..685c433f5 --- /dev/null +++ b/ipc/glue/FileDescriptorSetParent.cpp @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileDescriptorSetParent.h" + +namespace mozilla { +namespace ipc { + +FileDescriptorSetParent::FileDescriptorSetParent( + const FileDescriptor& aFileDescriptor) +{ + mFileDescriptors.AppendElement(aFileDescriptor); +} + +FileDescriptorSetParent::~FileDescriptorSetParent() +{ +} + +void +FileDescriptorSetParent::ForgetFileDescriptors( + nsTArray<FileDescriptor>& aFileDescriptors) +{ + aFileDescriptors.Clear(); + mFileDescriptors.SwapElements(aFileDescriptors); +} + +void +FileDescriptorSetParent::ActorDestroy(ActorDestroyReason aWhy) +{ + // Implement me! Bug 1005157 +} + +bool +FileDescriptorSetParent::RecvAddFileDescriptor( + const FileDescriptor& aFileDescriptor) +{ + mFileDescriptors.AppendElement(aFileDescriptor); + return true; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/FileDescriptorSetParent.h b/ipc/glue/FileDescriptorSetParent.h new file mode 100644 index 000000000..58f254e47 --- /dev/null +++ b/ipc/glue/FileDescriptorSetParent.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_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 nsIContentParent; + +} // namespace dom + +namespace ipc { + +class BackgroundParentImpl; +class FileDescriptor; + +class FileDescriptorSetParent final + : public PFileDescriptorSetParent +{ + friend class BackgroundParentImpl; + friend class mozilla::dom::nsIContentParent; + + nsTArray<FileDescriptor> mFileDescriptors; + +public: + void + ForgetFileDescriptors(nsTArray<FileDescriptor>& aFileDescriptors); + +private: + explicit FileDescriptorSetParent(const FileDescriptor& aFileDescriptor); + ~FileDescriptorSetParent(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual bool + RecvAddFileDescriptor(const FileDescriptor& aFileDescriptor) override; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_FileDescriptorSetParent_h__ diff --git a/ipc/glue/FileDescriptorUtils.cpp b/ipc/glue/FileDescriptorUtils.cpp new file mode 100644 index 000000000..e30bc97d8 --- /dev/null +++ b/ipc/glue/FileDescriptorUtils.cpp @@ -0,0 +1,126 @@ +/* -*- 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 "nsThreadUtils.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; + +#ifdef DEBUG + +CloseFileRunnable::CloseFileRunnable(const FileDescriptor& aFileDescriptor) +: mFileDescriptor(aFileDescriptor) +{ + MOZ_ASSERT(aFileDescriptor.IsValid()); +} + +#endif // DEBUG + +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(CloseFileRunnable, nsIRunnable) + +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 000000000..34562c361 --- /dev/null +++ b/ipc/glue/FileDescriptorUtils.h @@ -0,0 +1,61 @@ +/* -*- 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 "nsIRunnable.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 nsIRunnable +{ + typedef mozilla::ipc::FileDescriptor FileDescriptor; + + FileDescriptor mFileDescriptor; + +public: + explicit CloseFileRunnable(const FileDescriptor& aFileDescriptor) +#ifdef DEBUG + ; +#else + : mFileDescriptor(aFileDescriptor) + { } +#endif + + NS_DECL_THREADSAFE_ISUPPORTS + 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/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp new file mode 100644 index 000000000..48051472a --- /dev/null +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -0,0 +1,1268 @@ +/* -*- 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 "chrome/common/mach_ipc_mac.h" +#include "base/rand_util.h" +#include "nsILocalFileMac.h" +#include "SharedMemoryBasic.h" +#endif + +#include "MainThreadUtils.h" +#include "mozilla/Sprintf.h" +#include "prenv.h" +#include "nsXPCOMPrivate.h" + +#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX) +#include "nsAppDirectoryServiceDefs.h" +#endif + +#include "nsExceptionHandler.h" + +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsPrintfCString.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/Omnijar.h" +#include "mozilla/Telemetry.h" +#include "ProtocolUtils.h" +#include <sys/stat.h> + +#ifdef XP_WIN +#include "nsIWinTaskbar.h" +#define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1" + +#if defined(MOZ_SANDBOX) +#include "mozilla/Preferences.h" +#include "mozilla/sandboxing/sandboxLogging.h" +#include "nsDirectoryServiceUtils.h" +#endif +#endif + +#include "nsTArray.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "nsNativeCharsetUtils.h" +#include "nscore.h" // for NS_FREE_PERMANENT_DATA + +using mozilla::MonitorAutoLock; +using mozilla::ipc::GeckoChildProcessHost; + +#ifdef ANDROID +// Like its predecessor in nsExceptionHandler.cpp, this is +// the magic number of a file descriptor remapping we must +// preserve for the child process. +static const int kMagicAndroidSystemPropFd = 5; +#endif + +#ifdef MOZ_WIDGET_ANDROID +#include "AndroidBridge.h" +#endif + +static const bool kLowRightsSubprocesses = + // We currently only attempt to drop privileges on gonk, because we + // have no plugins or extensions to worry about breaking. +#ifdef MOZ_WIDGET_GONK + true +#else + false +#endif + ; + +static bool +ShouldHaveDirectoryService() +{ + return GeckoProcessType_Default == XRE_GetProcessType(); +} + +/*static*/ +base::ChildPrivileges +GeckoChildProcessHost::DefaultChildPrivileges() +{ + return (kLowRightsSubprocesses ? + base::PRIVILEGES_UNPRIVILEGED : base::PRIVILEGES_INHERIT); +} + +GeckoChildProcessHost::GeckoChildProcessHost(GeckoProcessType aProcessType, + ChildPrivileges aPrivileges) + : mProcessType(aProcessType), + mPrivileges(aPrivileges), + mMonitor("mozilla.ipc.GeckChildProcessHost.mMonitor"), + mProcessState(CREATING_CHANNEL), +#if defined(MOZ_SANDBOX) && defined(XP_WIN) + mEnableSandboxLogging(false), + mSandboxLevel(0), +#endif + mChildProcessHandle(0) +#if defined(MOZ_WIDGET_COCOA) + , mChildTask(MACH_PORT_NULL) +#endif +{ + MOZ_COUNT_CTOR(GeckoChildProcessHost); +} + +GeckoChildProcessHost::~GeckoChildProcessHost() + +{ + AssertIOThread(); + + MOZ_COUNT_DTOR(GeckoChildProcessHost); + + if (mChildProcessHandle != 0) { +#if defined(MOZ_WIDGET_COCOA) + SharedMemoryBasic::CleanupForPid(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 +} + +//static +auto +GeckoChildProcessHost::GetPathToBinary(FilePath& exePath, GeckoProcessType processType) -> BinaryPathType +{ + if (sRunSelfAsContentProc && + (processType == GeckoProcessType_Content || processType == GeckoProcessType_GPU)) { +#if defined(OS_WIN) + wchar_t exePathBuf[MAXPATHLEN]; + if (!::GetModuleFileNameW(nullptr, exePathBuf, MAXPATHLEN)) { + MOZ_CRASH("GetModuleFileNameW failed (FIXME)"); + } + exePath = FilePath::FromWStringHack(exePathBuf); +#elif defined(OS_POSIX) + exePath = FilePath(CommandLine::ForCurrentProcess()->argv()[0]); +#else +# error Sorry; target OS not supported yet. +#endif + return BinaryPathType::Self; + } + + 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(NS_LITERAL_CSTRING("plugin-container.app")); + childProcPath->AppendNative(NS_LITERAL_CSTRING("Contents")); + childProcPath->AppendNative(NS_LITERAL_CSTRING("MacOS")); + 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(); + } + +#ifdef MOZ_WIDGET_ANDROID + exePath = exePath.AppendASCII("lib"); + + // We must use the PIE binary on 5.0 and higher + const char* processName = mozilla::AndroidBridge::Bridge()->GetAPIVersion() >= 21 ? + MOZ_CHILD_PROCESS_NAME_PIE : MOZ_CHILD_PROCESS_NAME; + + exePath = exePath.AppendASCII(processName); +#else + exePath = exePath.AppendASCII(MOZ_CHILD_PROCESS_NAME); +#endif + + return BinaryPathType::PluginContainer; +} + +#ifdef MOZ_WIDGET_COCOA +class AutoCFTypeObject { +public: + explicit AutoCFTypeObject(CFTypeRef object) + { + mObject = object; + } + ~AutoCFTypeObject() + { + ::CFRelease(mObject); + } +private: + CFTypeRef mObject; +}; +#endif + +nsresult GeckoChildProcessHost::GetArchitecturesForBinary(const char *path, uint32_t *result) +{ + *result = 0; + +#ifdef MOZ_WIDGET_COCOA + CFURLRef url = ::CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, + (const UInt8*)path, + strlen(path), + false); + if (!url) { + return NS_ERROR_FAILURE; + } + AutoCFTypeObject autoPluginContainerURL(url); + + CFArrayRef pluginContainerArchs = ::CFBundleCopyExecutableArchitecturesForURL(url); + if (!pluginContainerArchs) { + return NS_ERROR_FAILURE; + } + AutoCFTypeObject autoPluginContainerArchs(pluginContainerArchs); + + CFIndex pluginArchCount = ::CFArrayGetCount(pluginContainerArchs); + for (CFIndex i = 0; i < pluginArchCount; i++) { + CFNumberRef currentArch = static_cast<CFNumberRef>(::CFArrayGetValueAtIndex(pluginContainerArchs, i)); + int currentArchInt = 0; + if (!::CFNumberGetValue(currentArch, kCFNumberIntType, ¤tArchInt)) { + continue; + } + switch (currentArchInt) { + case kCFBundleExecutableArchitectureI386: + *result |= base::PROCESS_ARCH_I386; + break; + case kCFBundleExecutableArchitectureX86_64: + *result |= base::PROCESS_ARCH_X86_64; + break; + case kCFBundleExecutableArchitecturePPC: + *result |= base::PROCESS_ARCH_PPC; + break; + default: + break; + } + } + + return (*result ? NS_OK : NS_ERROR_FAILURE); +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +uint32_t GeckoChildProcessHost::GetSupportedArchitecturesForProcessType(GeckoProcessType type) +{ +#ifdef MOZ_WIDGET_COCOA + if (type == GeckoProcessType_Plugin) { + + // Cache this, it shouldn't ever change. + static uint32_t pluginContainerArchs = 0; + if (pluginContainerArchs == 0) { + FilePath exePath; + GetPathToBinary(exePath, type); + nsresult rv = GetArchitecturesForBinary(exePath.value().c_str(), &pluginContainerArchs); + NS_ASSERTION(NS_SUCCEEDED(rv) && pluginContainerArchs != 0, "Getting architecture of plugin container failed!"); + if (NS_FAILED(rv) || pluginContainerArchs == 0) { + pluginContainerArchs = base::GetCurrentProcessArchitecture(); + } + } + return pluginContainerArchs; + } +#endif + + return base::GetCurrentProcessArchitecture(); +} + +// 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() +{ +#ifdef MOZ_CRASHREPORTER + if (CrashReporter::GetEnabled()) { + CrashReporter::OOPInit(); + } +#endif + +#ifdef XP_WIN + if (mProcessType == GeckoProcessType_Plugin) { + InitWindowsGroupID(); + } + +#if defined(MOZ_CONTENT_SANDBOX) + // We need to get the pref here as the process is launched off main thread. + if (mProcessType == GeckoProcessType_Content) { + mSandboxLevel = Preferences::GetInt("security.sandbox.content.level"); + mEnableSandboxLogging = + Preferences::GetBool("security.sandbox.windows.log"); + } +#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_WIN_SANDBOX_LOGGING"); +#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))) { + mGroupId.Append(appId); + } else { + mGroupId.Assign('-'); + } + } +} +#endif + +bool +GeckoChildProcessHost::SyncLaunch(std::vector<std::string> aExtraOpts, int aTimeoutMs, base::ProcessArchitecture arch) +{ + PrepareLaunch(); + + MessageLoop* ioLoop = XRE_GetIOMessageLoop(); + NS_ASSERTION(MessageLoop::current() != ioLoop, "sync launch from the IO thread NYI"); + + ioLoop->PostTask(NewNonOwningRunnableMethod + <std::vector<std::string>, base::ProcessArchitecture> + (this, &GeckoChildProcessHost::RunPerformAsyncLaunch, + aExtraOpts, arch)); + + return WaitUntilConnected(aTimeoutMs); +} + +bool +GeckoChildProcessHost::AsyncLaunch(std::vector<std::string> aExtraOpts, + base::ProcessArchitecture arch) +{ + PrepareLaunch(); + + MessageLoop* ioLoop = XRE_GetIOMessageLoop(); + + ioLoop->PostTask(NewNonOwningRunnableMethod + <std::vector<std::string>, base::ProcessArchitecture> + (this, &GeckoChildProcessHost::RunPerformAsyncLaunch, + aExtraOpts, arch)); + + // This may look like the sync launch wait, but we only delay as + // long as it takes to create the channel. + MonitorAutoLock lock(mMonitor); + while (mProcessState < CHANNEL_INITIALIZED) { + lock.Wait(); + } + + return true; +} + +bool +GeckoChildProcessHost::WaitUntilConnected(int32_t aTimeoutMs) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + + // NB: this uses a different mechanism than the chromium parent + // class. + PRIntervalTime timeoutTicks = (aTimeoutMs > 0) ? + PR_MillisecondsToInterval(aTimeoutMs) : PR_INTERVAL_NO_TIMEOUT; + + MonitorAutoLock lock(mMonitor); + PRIntervalTime waitStart = PR_IntervalNow(); + PRIntervalTime 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; + } + + lock.Wait(timeoutTicks); + + if (timeoutTicks != PR_INTERVAL_NO_TIMEOUT) { + current = PR_IntervalNow(); + PRIntervalTime elapsed = current - waitStart; + if (elapsed > timeoutTicks) { + break; + } + timeoutTicks = timeoutTicks - elapsed; + waitStart = current; + } + } + + return mProcessState == PROCESS_CONNECTED; +} + +bool +GeckoChildProcessHost::LaunchAndWaitForProcessHandle(StringVector aExtraOpts) +{ + PrepareLaunch(); + + MessageLoop* ioLoop = XRE_GetIOMessageLoop(); + ioLoop->PostTask(NewNonOwningRunnableMethod + <std::vector<std::string>, base::ProcessArchitecture> + (this, &GeckoChildProcessHost::RunPerformAsyncLaunch, + aExtraOpts, base::GetCurrentProcessArchitecture())); + + MonitorAutoLock lock(mMonitor); + while (mProcessState < PROCESS_CREATED) { + lock.Wait(); + } + MOZ_ASSERT(mProcessState == PROCESS_ERROR || mChildProcessHandle); + + return mProcessState < PROCESS_ERROR; +} + +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; +} + +int32_t GeckoChildProcessHost::mChildCounter = 0; + +void +GeckoChildProcessHost::SetChildLogName(const char* varName, const char* origLogName, + nsACString &buffer) +{ + // We currently have no portable way to launch child with environment + // different than parent. So temporarily change NSPR_LOG_FILE so child + // inherits value we want it to have. (NSPR only looks at NSPR_LOG_FILE at + // startup, so it's 'safe' to play with the parent's environment this way.) + buffer.Assign(varName); + buffer.Append(origLogName); + + // Append child-specific postfix to name + buffer.AppendLiteral(".child-"); + buffer.AppendInt(mChildCounter); + + // Passing temporary to PR_SetEnv is ok here if we keep the temporary + // for the time we launch the sub-process. It's copied to the new + // environment. + PR_SetEnv(buffer.BeginReading()); +} + +bool +GeckoChildProcessHost::PerformAsyncLaunch(std::vector<std::string> aExtraOpts, base::ProcessArchitecture arch) +{ + // If NSPR log files are not requested, we're done. + const char* origNSPRLogName = PR_GetEnv("NSPR_LOG_FILE"); + const char* origMozLogName = PR_GetEnv("MOZ_LOG_FILE"); + if (!origNSPRLogName && !origMozLogName) { + return PerformAsyncLaunchInternal(aExtraOpts, arch); + } + + // - Note: this code is not called re-entrantly, nor are restoreOrig*LogName + // or mChildCounter touched by any other thread, so this is safe. + ++mChildCounter; + + // Must keep these on the same stack where from we call PerformAsyncLaunchInternal + // so that PR_DuplicateEnvironment() still sees a valid memory. + nsAutoCString nsprLogName; + nsAutoCString mozLogName; + + if (origNSPRLogName) { + if (mRestoreOrigNSPRLogName.IsEmpty()) { + mRestoreOrigNSPRLogName.AssignLiteral("NSPR_LOG_FILE="); + mRestoreOrigNSPRLogName.Append(origNSPRLogName); + } + SetChildLogName("NSPR_LOG_FILE=", origNSPRLogName, nsprLogName); + } + if (origMozLogName) { + if (mRestoreOrigMozLogName.IsEmpty()) { + mRestoreOrigMozLogName.AssignLiteral("MOZ_LOG_FILE="); + mRestoreOrigMozLogName.Append(origMozLogName); + } + SetChildLogName("MOZ_LOG_FILE=", origMozLogName, mozLogName); + } + + bool retval = PerformAsyncLaunchInternal(aExtraOpts, arch); + + // Revert to original value + if (origNSPRLogName) { + PR_SetEnv(mRestoreOrigNSPRLogName.get()); + } + if (origMozLogName) { + PR_SetEnv(mRestoreOrigMozLogName.get()); + } + + return retval; +} + +bool +GeckoChildProcessHost::RunPerformAsyncLaunch(std::vector<std::string> aExtraOpts, + base::ProcessArchitecture aArch) +{ + InitializeChannel(); + + bool ok = PerformAsyncLaunch(aExtraOpts, aArch); + if (!ok) { + // WaitUntilConnected might be waiting for us to signal. + // If something failed let's set the error state and notify. + MonitorAutoLock lock(mMonitor); + mProcessState = PROCESS_ERROR; + lock.Notify(); + CHROMIUM_LOG(ERROR) << "Failed to launch " << + XRE_ChildProcessTypeToString(mProcessType) << " subprocess"; + Telemetry::Accumulate(Telemetry::SUBPROCESS_LAUNCH_FAILURE, + nsDependentCString(XRE_ChildProcessTypeToString(mProcessType))); + } + return ok; +} + +void +#if defined(XP_WIN) +AddAppDirToCommandLine(CommandLine& aCmdLine) +#else +AddAppDirToCommandLine(std::vector<std::string>& aCmdLine) +#endif +{ + // Content processes need access to application resources, so pass + // the full application directory path to the child process. + if (ShouldHaveDirectoryService()) { + nsCOMPtr<nsIProperties> directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID)); + NS_ASSERTION(directoryService, "Expected XPCOM to be available"); + if (directoryService) { + nsCOMPtr<nsIFile> appDir; + // NS_XPCOM_CURRENT_PROCESS_DIR really means the app dir, not the + // current process dir. + nsresult rv = directoryService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, + NS_GET_IID(nsIFile), + getter_AddRefs(appDir)); + if (NS_SUCCEEDED(rv)) { +#if defined(XP_WIN) + nsString path; + MOZ_ALWAYS_SUCCEEDS(appDir->GetPath(path)); + aCmdLine.AppendLooseValue(UTF8ToWide("-appdir")); + std::wstring wpath(path.get()); + aCmdLine.AppendLooseValue(wpath); +#else + nsAutoCString path; + MOZ_ALWAYS_SUCCEEDS(appDir->GetNativePath(path)); + aCmdLine.push_back("-appdir"); + aCmdLine.push_back(path.get()); +#endif + } + +#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX) + // Full path to the profile dir + nsCOMPtr<nsIFile> profileDir; + rv = directoryService->Get(NS_APP_USER_PROFILE_50_DIR, + NS_GET_IID(nsIFile), + getter_AddRefs(profileDir)); + if (NS_SUCCEEDED(rv)) { + nsAutoCString path; + MOZ_ALWAYS_SUCCEEDS(profileDir->GetNativePath(path)); + aCmdLine.push_back("-profile"); + aCmdLine.push_back(path.get()); + } +#endif + } + } +} + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +static void +MaybeAddNsprLogFileAccess(std::vector<std::wstring>& aAllowedFilesReadWrite) +{ + const char* nsprLogFileEnv = PR_GetEnv("NSPR_LOG_FILE"); + if (!nsprLogFileEnv) { + return; + } + + nsDependentCString nsprLogFilePath(nsprLogFileEnv); + nsCOMPtr<nsIFile> nsprLogFile; + nsresult rv = NS_NewNativeLocalFile(nsprLogFilePath, true, + getter_AddRefs(nsprLogFile)); + if (NS_FAILED(rv)) { + // Not an absolute path, try it as a relative one. + nsresult rv = NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, + getter_AddRefs(nsprLogFile)); + if (NS_FAILED(rv) || !nsprLogFile) { + NS_WARNING("Failed to get current working directory"); + return; + } + + rv = nsprLogFile->AppendRelativeNativePath(nsprLogFilePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + } + + nsAutoString resolvedFilePath; + rv = nsprLogFile->GetPath(resolvedFilePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + // Update the environment variable as well as adding the rule, because the + // Chromium sandbox can only allow access to fully qualified file paths. This + // only affects the environment for the child process we're about to create, + // because this will get reset to the original value in PerformAsyncLaunch. + aAllowedFilesReadWrite.push_back(std::wstring(resolvedFilePath.get())); + nsAutoCString resolvedEnvVar("NSPR_LOG_FILE="); + AppendUTF16toUTF8(resolvedFilePath, resolvedEnvVar); + PR_SetEnv(resolvedEnvVar.get()); +} + +static void +AddContentSandboxAllowedFiles(int32_t aSandboxLevel, + std::vector<std::wstring>& aAllowedFilesRead) +{ + if (aSandboxLevel < 1) { + return; + } + + nsCOMPtr<nsIFile> binDir; + nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(binDir)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsAutoString binDirPath; + rv = binDir->GetPath(binDirPath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + // If bin directory is on a remote drive add read access. + wchar_t volPath[MAX_PATH]; + if (!::GetVolumePathNameW(binDirPath.get(), volPath, MAX_PATH)) { + return; + } + + if (::GetDriveTypeW(volPath) != DRIVE_REMOTE) { + return; + } + + // Convert network share path to format for sandbox policy. + if (Substring(binDirPath, 0, 2).Equals(L"\\\\")) { + binDirPath.InsertLiteral(u"??\\UNC", 1); + } + + binDirPath.AppendLiteral(u"\\*"); + + aAllowedFilesRead.push_back(std::wstring(binDirPath.get())); +} +#endif + +bool +GeckoChildProcessHost::PerformAsyncLaunchInternal(std::vector<std::string>& aExtraOpts, base::ProcessArchitecture arch) +{ + // We rely on the fact that InitializeChannel() has already been processed + // on the IO thread before this point is reached. + if (!GetChannel()) { + return false; + } + + base::ProcessHandle process = 0; + + // send the child the PID so that it can open a ProcessHandle back to us. + // probably don't want to do this in the long run + char pidstring[32]; + SprintfLiteral(pidstring,"%d", base::Process::Current().pid()); + + const char* const childProcessType = + XRE_ChildProcessTypeToString(mProcessType); + +//-------------------------------------------------- +#if defined(OS_POSIX) + // For POSIX, we have to be extremely anal about *not* using + // std::wstring in code compiled with Mozilla's -fshort-wchar + // configuration, because chromium is compiled with -fno-short-wchar + // and passing wstrings from one config to the other is unsafe. So + // we split the logic here. + +#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) + base::environment_map newEnvVars; + ChildPrivileges privs = mPrivileges; + if (privs == base::PRIVILEGES_DEFAULT) { + privs = DefaultChildPrivileges(); + } + +#if defined(MOZ_WIDGET_GTK) + if (mProcessType == GeckoProcessType_Content) { + // disable IM module to avoid sandbox violation + newEnvVars["GTK_IM_MODULE"] = "gtk-im-context-simple"; + } +#endif + + // 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) +# if defined(MOZ_WIDGET_ANDROID) + path += "/lib"; +# endif // MOZ_WIDGET_ANDROID + const char *ld_library_path = PR_GetEnv("LD_LIBRARY_PATH"); + nsCString new_ld_lib_path(path.get()); + +# if (MOZ_WIDGET_GTK == 3) + if (mProcessType == GeckoProcessType_Plugin) { + new_ld_lib_path.Append("/gtk2:"); + new_ld_lib_path.Append(path.get()); + } +#endif + if (ld_library_path && *ld_library_path) { + new_ld_lib_path.Append(':'); + new_ld_lib_path.Append(ld_library_path); + } + newEnvVars["LD_LIBRARY_PATH"] = new_ld_lib_path.get(); + +# elif OS_MACOSX + newEnvVars["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"); + newEnvVars["DYLD_INSERT_LIBRARIES"] = interpose.get(); +# endif // OS_LINUX + } +#endif // OS_LINUX || OS_MACOSX + + FilePath exePath; + BinaryPathType pathType = GetPathToBinary(exePath, mProcessType); + +#ifdef MOZ_WIDGET_ANDROID + // The java wrapper unpacks this for us but can't make it executable + chmod(exePath.value().c_str(), 0700); +#endif // MOZ_WIDGET_ANDROID + +#ifdef ANDROID + // Remap the Android property workspace to a well-known int, + // and update the environment to reflect the new value for the + // child process. + const char *apws = getenv("ANDROID_PROPERTY_WORKSPACE"); + if (apws) { + int fd = atoi(apws); + mFileMap.push_back(std::pair<int, int>(fd, kMagicAndroidSystemPropFd)); + + char buf[32]; + char *szptr = strchr(apws, ','); + + snprintf(buf, sizeof(buf), "%d%s", kMagicAndroidSystemPropFd, szptr); + newEnvVars["ANDROID_PROPERTY_WORKSPACE"] = buf; + } +#endif // ANDROID + +#ifdef MOZ_WIDGET_GONK + if (const char *ldPreloadPath = getenv("LD_PRELOAD")) { + newEnvVars["LD_PRELOAD"] = ldPreloadPath; + } +#endif // MOZ_WIDGET_GONK + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + // Preload libmozsandbox.so so that sandbox-related interpositions + // can be defined there instead of in the executable. + // (This could be made conditional on intent to use sandboxing, but + // it's harmless for non-sandboxed processes.) + { + nsAutoCString preload; + // Prepend this, because people can and do preload libpthread. + // (See bug 1222500.) + preload.AssignLiteral("libmozsandbox.so"); + if (const char* oldPreload = PR_GetEnv("LD_PRELOAD")) { + // Doesn't matter if oldPreload is ""; extra separators are ignored. + preload.Append(' '); + preload.Append(oldPreload); + } + // Explicitly construct the std::string to make it clear that this + // isn't retaining a pointer to the nsCString's buffer. + newEnvVars["LD_PRELOAD"] = std::string(preload.get()); + } +#endif + + // remap the IPC socket fd to a well-known int, as the OS does for + // STDOUT_FILENO, for example + int srcChannelFd, dstChannelFd; + channel().GetClientFileDescriptorMapping(&srcChannelFd, &dstChannelFd); + mFileMap.push_back(std::pair<int,int>(srcChannelFd, dstChannelFd)); + + // no need for kProcessChannelID, the child process inherits the + // other end of the socketpair() from us + + std::vector<std::string> childArgv; + + childArgv.push_back(exePath.value()); + + if (pathType == BinaryPathType::Self) { + childArgv.push_back("-contentproc"); + } + + childArgv.insert(childArgv.end(), aExtraOpts.begin(), aExtraOpts.end()); + + 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))) { + childArgv.push_back("-greomni"); + childArgv.push_back(path.get()); + } + file = Omnijar::GetPath(Omnijar::APP); + if (file && NS_SUCCEEDED(file->GetNativePath(path))) { + childArgv.push_back("-appomni"); + childArgv.push_back(path.get()); + } + } + + // Add the application directory path (-appdir path) + AddAppDirToCommandLine(childArgv); + + childArgv.push_back(pidstring); + +#if defined(MOZ_CRASHREPORTER) +# if defined(OS_LINUX) || defined(OS_BSD) + int childCrashFd, childCrashRemapFd; + if (!CrashReporter::CreateNotificationPipeForChild( + &childCrashFd, &childCrashRemapFd)) + return false; + if (0 <= childCrashFd) { + mFileMap.push_back(std::pair<int,int>(childCrashFd, childCrashRemapFd)); + // "true" == crash reporting enabled + childArgv.push_back("true"); + } + else { + // "false" == crash reporting disabled + childArgv.push_back("false"); + } +# elif defined(MOZ_WIDGET_COCOA) + childArgv.push_back(CrashReporter::GetChildNotificationPipe()); +# endif // OS_LINUX +#endif + +#ifdef MOZ_WIDGET_COCOA + // Add a mach port to the command line so the child can communicate its + // 'task_t' back to the parent. + // + // Put a random number into the channel name, so that a compromised renderer + // can't pretend being the child that's forked off. + std::string mach_connection_name = StringPrintf("org.mozilla.machname.%d", + base::RandInt(0, std::numeric_limits<int>::max())); + childArgv.push_back(mach_connection_name.c_str()); +#endif + + childArgv.push_back(childProcessType); + + base::LaunchApp(childArgv, mFileMap, +#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_BSD) + newEnvVars, privs, +#endif + false, &process, arch); + + // 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). + GetChannel()->CloseClientFileDescriptor(); + +#ifdef MOZ_WIDGET_COCOA + // Wait for the child process to send us its 'task_t' data. + const int kTimeoutMs = 10000; + + MachReceiveMessage child_message; + ReceivePort parent_recv_port(mach_connection_name.c_str()); + kern_return_t err = parent_recv_port.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."; + } + MachPortSender* 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."; + } + MachPortSender* 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; + } + + ReceivePort* 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; + } + + ReceivePort* 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(process, parent_recv_port_memory, parent_recv_port_memory_ack, + parent_send_port_memory, parent_send_port_memory_ack, false); + +#endif + +//-------------------------------------------------- +#elif defined(OS_WIN) + + FilePath exePath; + BinaryPathType pathType = GetPathToBinary(exePath, mProcessType); + + CommandLine cmdLine(exePath.ToWStringHack()); + + if (pathType == BinaryPathType::Self) { + cmdLine.AppendLooseValue(UTF8ToWide("-contentproc")); + } + + cmdLine.AppendSwitchWithValue(switches::kProcessChannelID, channel_id()); + + for (std::vector<std::string>::iterator it = aExtraOpts.begin(); + it != aExtraOpts.end(); + ++it) { + cmdLine.AppendLooseValue(UTF8ToWide(*it)); + } + + if (Omnijar::IsInitialized()) { + // Make sure the child process can find the omnijar + // See XRE_InitCommandLine in nsAppRunner.cpp + nsAutoString path; + nsCOMPtr<nsIFile> file = Omnijar::GetPath(Omnijar::GRE); + if (file && NS_SUCCEEDED(file->GetPath(path))) { + cmdLine.AppendLooseValue(UTF8ToWide("-greomni")); + cmdLine.AppendLooseValue(path.get()); + } + file = Omnijar::GetPath(Omnijar::APP); + if (file && NS_SUCCEEDED(file->GetPath(path))) { + cmdLine.AppendLooseValue(UTF8ToWide("-appomni")); + cmdLine.AppendLooseValue(path.get()); + } + } + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + bool shouldSandboxCurrentProcess = false; + + // 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 defined(MOZ_CONTENT_SANDBOX) + if (mSandboxLevel > 0 && + !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) { + // 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. + mSandboxBroker.SetSecurityLevelForContentProcess(mSandboxLevel); + shouldSandboxCurrentProcess = true; + AddContentSandboxAllowedFiles(mSandboxLevel, mAllowedFilesRead); + } +#endif // MOZ_CONTENT_SANDBOX + break; + case GeckoProcessType_Plugin: + if (mSandboxLevel > 0 && + !PR_GetEnv("MOZ_DISABLE_NPAPI_SANDBOX")) { + bool ok = mSandboxBroker.SetSecurityLevelForPluginProcess(mSandboxLevel); + if (!ok) { + return false; + } + shouldSandboxCurrentProcess = 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. + bool isWidevine = std::any_of(aExtraOpts.begin(), aExtraOpts.end(), + [](const std::string arg) { return arg.find("gmp-widevinecdm") != std::string::npos; }); + auto level = isWidevine ? SandboxBroker::Restricted : SandboxBroker::LockDown; + bool ok = mSandboxBroker.SetSecurityLevelForGMPlugin(level); + if (!ok) { + return false; + } + shouldSandboxCurrentProcess = true; + } + break; + case GeckoProcessType_GPU: + break; + case GeckoProcessType_Default: + default: + MOZ_CRASH("Bad process type in GeckoChildProcessHost"); + break; + }; + + if (shouldSandboxCurrentProcess) { + MaybeAddNsprLogFileAccess(mAllowedFilesReadWrite); + for (auto it = mAllowedFilesRead.begin(); + it != mAllowedFilesRead.end(); + ++it) { + mSandboxBroker.AllowReadFile(it->c_str()); + } + + for (auto it = mAllowedFilesReadWrite.begin(); + it != mAllowedFilesReadWrite.end(); + ++it) { + mSandboxBroker.AllowReadWriteFile(it->c_str()); + } + + for (auto it = mAllowedDirectories.begin(); + it != mAllowedDirectories.end(); + ++it) { + mSandboxBroker.AllowDirectory(it->c_str()); + } + } +#endif // XP_WIN && MOZ_SANDBOX + + // Add the application directory path (-appdir path) + AddAppDirToCommandLine(cmdLine); + + // 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 + cmdLine.AppendLooseValue(mGroupId.get()); + + // Process id + cmdLine.AppendLooseValue(UTF8ToWide(pidstring)); + +#if defined(MOZ_CRASHREPORTER) + cmdLine.AppendLooseValue( + UTF8ToWide(CrashReporter::GetChildNotificationPipe())); +#endif + + // Process type + cmdLine.AppendLooseValue(UTF8ToWide(childProcessType)); + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + if (shouldSandboxCurrentProcess) { + if (mSandboxBroker.LaunchApp(cmdLine.program().c_str(), + cmdLine.command_line_string().c_str(), + mEnableSandboxLogging, + &process)) { + EnvironmentLog("MOZ_PROCESS_LOG").print( + "==> process %d launched child process %d (%S)\n", + base::GetCurrentProcId(), base::GetProcId(process), + cmdLine.command_line_string().c_str()); + } + } else +#endif + { + base::LaunchApp(cmdLine, false, false, &process); + +#ifdef MOZ_SANDBOX + // We need to be able to duplicate handles to some types of non-sandboxed + // child processes. + if (mProcessType == GeckoProcessType_Content || + mProcessType == GeckoProcessType_GPU || + mProcessType == GeckoProcessType_GMPlugin) { + if (!mSandboxBroker.AddTargetPeer(process)) { + NS_WARNING("Failed to add content process as target peer."); + } + } +#endif + } + +#else +# error Sorry +#endif + + if (!process) { + return 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. +#if defined(MOZ_WIDGET_COCOA) + mChildTask = child_task; +#endif + + if (!OpenPrivilegedHandle(base::GetProcId(process)) +#ifdef XP_WIN + // If we failed in opening the process handle, try harder by duplicating + // one. + && !::DuplicateHandle(::GetCurrentProcess(), process, + ::GetCurrentProcess(), &mChildProcessHandle, + PROCESS_DUP_HANDLE | PROCESS_TERMINATE | + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | + SYNCHRONIZE, + FALSE, 0) +#endif + ) { + NS_RUNTIMEABORT("cannot open handle to child process"); + } + MonitorAutoLock lock(mMonitor); + mProcessState = PROCESS_CREATED; + lock.Notify(); + + return true; +} + +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)) { + NS_RUNTIMEABORT("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(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. +} + +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. +} + +bool GeckoChildProcessHost::sRunSelfAsContentProc(false); diff --git a/ipc/glue/GeckoChildProcessHost.h b/ipc/glue/GeckoChildProcessHost.h new file mode 100644 index 000000000..3d55564ac --- /dev/null +++ b/ipc/glue/GeckoChildProcessHost.h @@ -0,0 +1,223 @@ +/* -*- 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 "mozilla/DebugOnly.h" +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/Monitor.h" +#include "mozilla/StaticPtr.h" + +#include "nsCOMPtr.h" +#include "nsXULAppAPI.h" // for GeckoProcessType +#include "nsString.h" + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +#include "sandboxBroker.h" +#endif + +namespace mozilla { +namespace ipc { + +class GeckoChildProcessHost : public ChildProcessHost +{ +protected: + typedef mozilla::Monitor Monitor; + typedef std::vector<std::string> StringVector; + +public: + typedef base::ChildPrivileges ChildPrivileges; + typedef base::ProcessHandle ProcessHandle; + + static ChildPrivileges DefaultChildPrivileges(); + + explicit GeckoChildProcessHost(GeckoProcessType aProcessType, + ChildPrivileges aPrivileges=base::PRIVILEGES_DEFAULT); + + ~GeckoChildProcessHost(); + + static nsresult GetArchitecturesForBinary(const char *path, uint32_t *result); + + static uint32_t GetSupportedArchitecturesForProcessType(GeckoProcessType type); + + static uint32_t GetUniqueID(); + + // Block until the IPC channel for our subprocess is initialized, + // but no longer. The child process may or may not have been + // created when this method returns. + bool AsyncLaunch(StringVector aExtraOpts=StringVector(), + base::ProcessArchitecture arch=base::GetCurrentProcessArchitecture()); + + 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()); + + // 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, + base::ProcessArchitecture arch=base::GetCurrentProcessArchitecture()); + + virtual bool PerformAsyncLaunch(StringVector aExtraOpts=StringVector(), + base::ProcessArchitecture aArch=base::GetCurrentProcessArchitecture()); + + virtual void OnChannelConnected(int32_t peer_pid); + virtual void OnMessageReceived(IPC::Message&& aMsg); + virtual void OnChannelError(); + virtual void GetQueuedMessages(std::queue<IPC::Message>& queue); + + virtual void InitializeChannel(); + + virtual bool CanShutdown() { return true; } + + IPC::Channel* GetChannel() { + return channelp(); + } + + // 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 + + /** + * 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(); + + static void EnableSameExecutableForContentProc() { sRunSelfAsContentProc = true; } + +protected: + GeckoProcessType mProcessType; + ChildPrivileges mPrivileges; + Monitor mMonitor; + FilePath mProcessPath; + + // 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; + + static int32_t mChildCounter; + + void PrepareLaunch(); + +#ifdef XP_WIN + void InitWindowsGroupID(); + nsString mGroupId; + +#ifdef MOZ_SANDBOX + SandboxBroker mSandboxBroker; + std::vector<std::wstring> mAllowedFilesRead; + std::vector<std::wstring> mAllowedFilesReadWrite; + std::vector<std::wstring> mAllowedDirectories; + bool mEnableSandboxLogging; + int32_t mSandboxLevel; +#endif +#endif // XP_WIN + +#if defined(OS_POSIX) + base::file_handle_mapping_vector mFileMap; +#endif + + ProcessHandle mChildProcessHandle; +#if defined(OS_MACOSX) + task_t mChildTask; +#endif + + bool OpenPrivilegedHandle(base::ProcessId aPid); + +private: + DISALLOW_EVIL_CONSTRUCTORS(GeckoChildProcessHost); + + // Does the actual work for AsyncLaunch, on the IO thread. + bool PerformAsyncLaunchInternal(std::vector<std::string>& aExtraOpts, + base::ProcessArchitecture arch); + + bool RunPerformAsyncLaunch(StringVector aExtraOpts=StringVector(), + base::ProcessArchitecture aArch=base::GetCurrentProcessArchitecture()); + + enum class BinaryPathType { + Self, + PluginContainer + }; + + static BinaryPathType GetPathToBinary(FilePath& exePath, GeckoProcessType processType); + + // The buffer is passed to preserve its lifetime until we are done + // with launching the sub-process. + void SetChildLogName(const char* varName, const char* origLogName, + nsACString &buffer); + + // 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; + + // Remember original env values so we can restore it (there is no other + // simple way how to change environment of a child process than to modify + // the current environment). + nsCString mRestoreOrigNSPRLogName; + nsCString mRestoreOrigMozLogName; + + static uint32_t sNextUniqueID; + + static bool sRunSelfAsContentProc; +}; + +} /* 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 000000000..0a595ff3f --- /dev/null +++ b/ipc/glue/IOThreadChild.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 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() + { } + + static MessageLoop* message_loop() { + return IOThreadChild::current()->Thread::message_loop(); + } + + // IOThreadChild owns the returned IPC::Channel. + static IPC::Channel* channel() { + return IOThreadChild::current()->ChildThread::channel(); + } + +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/IPCMessageUtils.cpp b/ipc/glue/IPCMessageUtils.cpp new file mode 100644 index 000000000..62af265ed --- /dev/null +++ b/ipc/glue/IPCMessageUtils.cpp @@ -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/. */ + +#include "IPCMessageUtils.h" +#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 000000000..094aa978a --- /dev/null +++ b/ipc/glue/IPCMessageUtils.h @@ -0,0 +1,830 @@ +/* -*- 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 "base/process_util.h" +#include "chrome/common/ipc_message_utils.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "mozilla/Maybe.h" +#include "mozilla/net/WebSocketFrame.h" +#include "mozilla/TimeStamp.h" +#ifdef XP_WIN +#include "mozilla/TimeStamp_windows.h" +#endif +#include "mozilla/TypeTraits.h" +#include "mozilla/IntegerTypeTraits.h" + +#include <stdint.h> + +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif +#include "nsID.h" +#include "nsIWidget.h" +#include "nsMemory.h" +#include "nsString.h" +#include "nsTArray.h" +#include "js/StructuredClone.h" +#include "nsCSSPropertyID.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 { }; } +#endif + +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; + +// XXX there are out of place and might be generally useful. Could +// move to nscore.h or something. +struct void_t { + bool operator==(const void_t&) const { return true; } +}; +struct null_t { + bool operator==(const null_t&) const { return true; } +}; + +struct SerializedStructuredCloneBuffer final +{ + SerializedStructuredCloneBuffer& + operator=(const SerializedStructuredCloneBuffer& aOther) + { + data.Clear(); + auto iter = aOther.data.Iter(); + while (!iter.Done()) { + data.WriteBytes(iter.Data(), iter.RemainingInSegment()); + iter.Advance(aOther.data, iter.RemainingInSegment()); + } + return *this; + } + + 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; +}; + +} // namespace mozilla + +namespace IPC { + +/** + * Maximum size, in bytes, of a single IPC message. + */ +static const uint32_t MAX_MESSAGE_SIZE = 65536; + +/** + * 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_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)) { +#ifdef MOZ_CRASHREPORTER + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCReadErrorReason"), + NS_LITERAL_CSTRING("Bad iter")); +#endif + return false; + } else if (!EnumValidator::IsLegalValue(paramType(value))) { +#ifdef MOZ_CRASHREPORTER + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCReadErrorReason"), + NS_LITERAL_CSTRING("Illegal value")); +#endif + 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 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>> +{}; + +/** + * 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>> +{}; + +template <> +struct ParamTraits<base::ChildPrivileges> + : public ContiguousEnumSerializer<base::ChildPrivileges, + base::PRIVILEGES_DEFAULT, + base::PRIVILEGES_LAST> +{ }; + +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)); + } +}; + +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)); + } +}; + +#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) { + NS_RUNTIMEABORT("FileDescriptor isn't meaningful on this platform"); + } + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) { + NS_RUNTIMEABORT("FileDescriptor isn't meaningful on this platform"); + return false; + } +}; +#endif // !defined(OS_POSIX) + +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; + } + 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; + } + aResult->SetLength(length); + + return aMsg->ReadBytesInto(aIter, aResult->BeginWriting(), length * sizeof(char16_t)); + } + + 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; +}; + +// 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 = (mozilla::IsIntegral<E>::value || + mozilla::IsFloatingPoint<E>::value); + + 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 { + 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<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->SwapElements(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<> +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<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; + } +}; + +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<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.mHasQPC); + WriteParam(aMsg, aParam.mIsNull); + } + 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->mHasQPC) && + ReadParam(aMsg, aIter, &aResult->mIsNull)); + } +}; + +#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 <> +struct ParamTraits<mozilla::net::WebSocketFrameData> +{ + typedef mozilla::net::WebSocketFrameData 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); + } +}; + +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()); + auto iter = aParam.Iter(); + while (!iter.Done()) { + aMsg->WriteBytes(iter.Data(), iter.RemainingInSegment(), sizeof(uint64_t)); + iter.Advance(aParam, iter.RemainingInSegment()); + } + } + + 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(Move(out)); + + 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); + } +}; + +template <> +struct ParamTraits<nsIWidget::TouchPointerState> + : public BitFlagsEnumSerializer<nsIWidget::TouchPointerState, + nsIWidget::TouchPointerState::ALL_BITS> +{ +}; + +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(mozilla::Move(tmp)); + } else { + *result = mozilla::Nothing(); + } + return true; + } +}; + +} /* namespace IPC */ + +#endif /* __IPC_GLUE_IPCMESSAGEUTILS_H__ */ diff --git a/ipc/glue/IPCStream.ipdlh b/ipc/glue/IPCStream.ipdlh new file mode 100644 index 000000000..3033ad7ff --- /dev/null +++ b/ipc/glue/IPCStream.ipdlh @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 PSendStream; +include BlobTypes; +include InputStreamParams; + +namespace mozilla { +namespace ipc { + +// Do not use this directly. See IPCStream below. +struct InputStreamParamsWithFds +{ + InputStreamParams stream; + OptionalFileDescriptorSet optionalFds; +}; + +// Use IPCStream or OptionalIPCStream in your ipdl to represent serialized +// nsIInputStreams. Then use AutoIPCStream from IPCStreamUtils.h to perform +// the serialization. +union IPCStream +{ + InputStreamParamsWithFds; + PSendStream; +}; + +union OptionalIPCStream +{ + IPCStream; + void_t; +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/IPCStreamUtils.cpp b/ipc/glue/IPCStreamUtils.cpp new file mode 100644 index 000000000..3bb351184 --- /dev/null +++ b/ipc/glue/IPCStreamUtils.cpp @@ -0,0 +1,495 @@ +/* -*- 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/nsIContentChild.h" +#include "mozilla/dom/PContentParent.h" +#include "mozilla/dom/File.h" +#include "mozilla/ipc/FileDescriptorSetChild.h" +#include "mozilla/ipc/FileDescriptorSetParent.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/ipc/PBackgroundParent.h" +#include "mozilla/ipc/SendStream.h" +#include "mozilla/Unused.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIPipe.h" +#include "nsStreamUtils.h" + +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> +void +SerializeInputStreamWithFdsChild(nsIInputStream* aStream, + IPCStream& aValue, + M* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + + // First attempt simple stream serialization + nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(aStream); + if (!serializable) { + MOZ_CRASH("Input stream is not serializable!"); + } + + aValue = InputStreamParamsWithFds(); + InputStreamParamsWithFds& streamWithFds = + aValue.get_InputStreamParamsWithFds(); + + AutoTArray<FileDescriptor, 4> fds; + serializable->Serialize(streamWithFds.stream(), fds); + + if (streamWithFds.stream().type() == InputStreamParams::T__None) { + MOZ_CRASH("Serialize failed!"); + } + + if (fds.IsEmpty()) { + streamWithFds.optionalFds() = void_t(); + } else { + PFileDescriptorSetChild* fdSet = + aManager->SendPFileDescriptorSetConstructor(fds[0]); + for (uint32_t i = 1; i < fds.Length(); ++i) { + Unused << fdSet->SendAddFileDescriptor(fds[i]); + } + + streamWithFds.optionalFds() = fdSet; + } +} + +template<typename M> +void +SerializeInputStreamWithFdsParent(nsIInputStream* aStream, + IPCStream& aValue, + M* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + + // First attempt simple stream serialization + nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(aStream); + if (!serializable) { + MOZ_CRASH("Input stream is not serializable!"); + } + + aValue = InputStreamParamsWithFds(); + InputStreamParamsWithFds& streamWithFds = + aValue.get_InputStreamParamsWithFds(); + + AutoTArray<FileDescriptor, 4> fds; + serializable->Serialize(streamWithFds.stream(), fds); + + if (streamWithFds.stream().type() == InputStreamParams::T__None) { + MOZ_CRASH("Serialize failed!"); + } + + streamWithFds.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) { + streamWithFds.optionalFds() = fdSet; + } + } +} + +template<typename M> +void +SerializeInputStream(nsIInputStream* aStream, IPCStream& aValue, M* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + + // If a stream is known to be larger than 1MB, prefer sending it in chunks. + const uint64_t kTooLargeStream = 1024 * 1024; + + // First attempt simple stream serialization + nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(aStream); + uint64_t expectedLength = + serializable ? serializable->ExpectedSerializedLength().valueOr(0) : 0; + if (serializable && expectedLength < kTooLargeStream) { + SerializeInputStreamWithFdsChild(aStream, aValue, aManager); + return; + } + + // As a fallback, attempt to stream the data across using a SendStream + // actor. For blocking streams, create a nonblocking pipe instead, + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aStream); + if (!asyncStream) { + const uint32_t kBufferSize = 32768; // matches SendStream buffer size. + nsCOMPtr<nsIAsyncOutputStream> sink; + DebugOnly<nsresult> rv = NS_NewPipe2(getter_AddRefs(asyncStream), + getter_AddRefs(sink), + true, + false, + kBufferSize, + UINT32_MAX); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + + rv = NS_AsyncCopy(aStream, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS, kBufferSize); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + + MOZ_ASSERT(asyncStream); + aValue = SendStreamChild::Create(asyncStream, aManager); + + if (!aValue.get_PSendStreamChild()) { + MOZ_CRASH("SendStream creation failed!"); + } +} + +template<typename M> +void +SerializeInputStream(nsIInputStream* aStream, OptionalIPCStream& aValue, + M* aManager) +{ + if (!aStream) { + aValue = void_t(); + return; + } + + aValue = IPCStream(); + SerializeInputStream(aStream, aValue.get_IPCStream(), + aManager); +} + +void +CleanupIPCStream(IPCStream& aValue, bool aConsumedByIPC) +{ + if (aValue.type() == IPCStream::T__None) { + return; + } + + if (aValue.type() == IPCStream::TInputStreamParamsWithFds) { + + InputStreamParamsWithFds& streamWithFds = + aValue.get_InputStreamParamsWithFds(); + + // Cleanup file descriptors if necessary + if (streamWithFds.optionalFds().type() == + OptionalFileDescriptorSet::TPFileDescriptorSetChild) { + + AutoTArray<FileDescriptor, 4> fds; + + auto fdSetActor = static_cast<FileDescriptorSetChild*>( + streamWithFds.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 << fdSetActor->Send__delete__(fdSetActor); + } + + } else if (streamWithFds.optionalFds().type() == + OptionalFileDescriptorSet::TPFileDescriptorSetParent) { + + AutoTArray<FileDescriptor, 4> fds; + + auto fdSetActor = static_cast<FileDescriptorSetParent*>( + streamWithFds.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 << fdSetActor->Send__delete__(fdSetActor); + } + } + + return; + } + + MOZ_ASSERT(aValue.type() == IPCStream::TPSendStreamChild); + + auto sendStream = + static_cast<SendStreamChild*>(aValue.get_PSendStreamChild()); + + if (!aConsumedByIPC) { + sendStream->StartDestroy(); + return; + } + + // If the SendStream was taken to be sent to the parent, then we need to + // start it before forgetting about it. + sendStream->Start(); +} + +void +CleanupIPCStream(OptionalIPCStream& aValue, bool aConsumedByIPC) +{ + if (aValue.type() == OptionalIPCStream::Tvoid_t) { + return; + } + + CleanupIPCStream(aValue.get_IPCStream(), aConsumedByIPC); +} + +} // anonymous namespace + +already_AddRefed<nsIInputStream> +DeserializeIPCStream(const IPCStream& aValue) +{ + if (aValue.type() == IPCStream::TPSendStreamParent) { + auto sendStream = + static_cast<SendStreamParent*>(aValue.get_PSendStreamParent()); + return sendStream->TakeReader(); + } + + // Note, we explicitly do not support deserializing the PSendStream actor on + // the child side. It can only be sent from child to parent. + MOZ_ASSERT(aValue.type() == IPCStream::TInputStreamParamsWithFds); + + const InputStreamParamsWithFds& streamWithFds = + aValue.get_InputStreamParamsWithFds(); + + AutoTArray<FileDescriptor, 4> fds; + if (streamWithFds.optionalFds().type() == + OptionalFileDescriptorSet::TPFileDescriptorSetParent) { + + auto fdSetActor = static_cast<FileDescriptorSetParent*>( + streamWithFds.optionalFds().get_PFileDescriptorSetParent()); + MOZ_ASSERT(fdSetActor); + + fdSetActor->ForgetFileDescriptors(fds); + MOZ_ASSERT(!fds.IsEmpty()); + + if (!fdSetActor->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 (streamWithFds.optionalFds().type() == + OptionalFileDescriptorSet::TPFileDescriptorSetChild) { + + auto fdSetActor = static_cast<FileDescriptorSetChild*>( + streamWithFds.optionalFds().get_PFileDescriptorSetChild()); + MOZ_ASSERT(fdSetActor); + + fdSetActor->ForgetFileDescriptors(fds); + MOZ_ASSERT(!fds.IsEmpty()); + + Unused << fdSetActor->Send__delete__(fdSetActor); + } + + return DeserializeInputStream(streamWithFds.stream(), fds); +} + +already_AddRefed<nsIInputStream> +DeserializeIPCStream(const OptionalIPCStream& aValue) +{ + if (aValue.type() == OptionalIPCStream::Tvoid_t) { + return nullptr; + } + + return DeserializeIPCStream(aValue.get_IPCStream()); +} + +namespace { + +void +AssertValidValueToTake(const IPCStream& aVal) +{ + MOZ_ASSERT(aVal.type() == IPCStream::TPSendStreamChild || + aVal.type() == IPCStream::TInputStreamParamsWithFds); +} + +void +AssertValidValueToTake(const OptionalIPCStream& aVal) +{ + MOZ_ASSERT(aVal.type() == OptionalIPCStream::Tvoid_t || + aVal.type() == OptionalIPCStream::TIPCStream); + if (aVal.type() == OptionalIPCStream::TIPCStream) { + AssertValidValueToTake(aVal.get_IPCStream()); + } +} + +} // anonymous namespace + +AutoIPCStream::AutoIPCStream() + : mInlineValue(void_t()) + , mValue(nullptr) + , mOptionalValue(&mInlineValue) + , mTaken(false) +{ +} + +AutoIPCStream::AutoIPCStream(IPCStream& aTarget) + : mInlineValue(void_t()) + , mValue(&aTarget) + , mOptionalValue(nullptr) + , mTaken(false) +{ +} + +AutoIPCStream::AutoIPCStream(OptionalIPCStream& aTarget) + : mInlineValue(void_t()) + , mValue(nullptr) + , mOptionalValue(&aTarget) + , mTaken(false) +{ + *mOptionalValue = void_t(); +} + +AutoIPCStream::~AutoIPCStream() +{ + MOZ_ASSERT(mValue || mOptionalValue); + if (mValue && IsSet()) { + CleanupIPCStream(*mValue, mTaken); + } else { + CleanupIPCStream(*mOptionalValue, mTaken); + } +} + +void +AutoIPCStream::Serialize(nsIInputStream* aStream, dom::nsIContentChild* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + if (mValue) { + SerializeInputStream(aStream, *mValue, aManager); + AssertValidValueToTake(*mValue); + } else { + SerializeInputStream(aStream, *mOptionalValue, aManager); + AssertValidValueToTake(*mOptionalValue); + } +} + +void +AutoIPCStream::Serialize(nsIInputStream* aStream, PBackgroundChild* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + if (mValue) { + SerializeInputStream(aStream, *mValue, aManager); + AssertValidValueToTake(*mValue); + } else { + SerializeInputStream(aStream, *mOptionalValue, aManager); + AssertValidValueToTake(*mOptionalValue); + } +} + +void +AutoIPCStream::Serialize(nsIInputStream* aStream, dom::PContentParent* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + if (mValue) { + SerializeInputStreamWithFdsParent(aStream, *mValue, aManager); + AssertValidValueToTake(*mValue); + } else { + SerializeInputStreamWithFdsParent(aStream, *mOptionalValue, aManager); + AssertValidValueToTake(*mOptionalValue); + } +} + +void +AutoIPCStream::Serialize(nsIInputStream* aStream, PBackgroundParent* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + if (mValue) { + SerializeInputStreamWithFdsParent(aStream, *mValue, aManager); + AssertValidValueToTake(*mValue); + } else { + SerializeInputStreamWithFdsParent(aStream, *mOptionalValue, aManager); + AssertValidValueToTake(*mOptionalValue); + } +} + +bool +AutoIPCStream::IsSet() const +{ + MOZ_ASSERT(mValue || mOptionalValue); + if (mValue) { + return mValue->type() != IPCStream::T__None; + } else { + return mOptionalValue->type() != OptionalIPCStream::Tvoid_t && + mOptionalValue->get_IPCStream().type() != IPCStream::T__None; + } +} + +IPCStream& +AutoIPCStream::TakeValue() +{ + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(IsSet()); + + mTaken = true; + + if (mValue) { + AssertValidValueToTake(*mValue); + return *mValue; + } + + IPCStream& value = + mOptionalValue->get_IPCStream(); + + AssertValidValueToTake(value); + return value; +} + +OptionalIPCStream& +AutoIPCStream::TakeOptionalValue() +{ + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!mValue); + MOZ_ASSERT(mOptionalValue); + mTaken = true; + AssertValidValueToTake(*mOptionalValue); + return *mOptionalValue; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/IPCStreamUtils.h b/ipc/glue/IPCStreamUtils.h new file mode 100644 index 000000000..a20f8a651 --- /dev/null +++ b/ipc/glue/IPCStreamUtils.h @@ -0,0 +1,185 @@ +/* -*- 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 "nsIInputStream.h" + +namespace mozilla { + +namespace dom { +class nsIContentChild; +class PContentParent; +} + +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 OptionalIPCStream& aValue); + +// RAII helper class that serializes an nsIInputStream into an IPCStream struct. +// Any file descriptor or PSendStream 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 PSendStream 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 */ +// } +// +// The AutoIPCStream class also supports OptionalIPCStream 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 PSendStream 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 final +{ + OptionalIPCStream mInlineValue; + IPCStream* mValue; + OptionalIPCStream* mOptionalValue; + bool mTaken; + + bool + IsSet() const; + +public: + // Implicitly create an OptionalIPCStream value. Either + // TakeValue() or TakeOptionalValue() can be used. + AutoIPCStream(); + + // 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); + + // Wrap an existing OptionalIPCStream. Either TakeValue() + // or TakeOptionalValue can be used. + explicit AutoIPCStream(OptionalIPCStream& aTarget); + + ~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. + void + Serialize(nsIInputStream* aStream, dom::nsIContentChild* 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. + void + Serialize(nsIInputStream* aStream, PBackgroundChild* aManager); + + // Serialize the input stream. A PSendStream cannot be used when going + // from parent-to-child. + void + Serialize(nsIInputStream* aStream, dom::PContentParent* aManager); + + // Serialize the input stream. A PSendStream cannot be used when going + // from parent-to-child. + void + Serialize(nsIInputStream* aStream, PBackgroundParent* 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 parent + IPCStream& + TakeValue(); + + // Get the OptionalIPCStream 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 parent + OptionalIPCStream& + TakeOptionalValue(); +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_IPCStreamUtils_h diff --git a/ipc/glue/InputStreamParams.ipdlh b/ipc/glue/InputStreamParams.ipdlh new file mode 100644 index 000000000..eb6869c17 --- /dev/null +++ b/ipc/glue/InputStreamParams.ipdlh @@ -0,0 +1,96 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 PBlob; +include ProtocolTypes; + +using struct mozilla::void_t + from "ipc/IPCMessageUtils.h"; + +namespace mozilla { +namespace ipc { + +struct StringInputStreamParams +{ + nsCString data; +}; + +struct FileInputStreamParams +{ + uint32_t fileDescriptorIndex; + int32_t behaviorFlags; + int32_t ioFlags; +}; + +struct PartialFileInputStreamParams +{ + FileInputStreamParams fileStreamParams; + uint64_t begin; + uint64_t length; +}; + +struct TemporaryFileInputStreamParams +{ + uint32_t fileDescriptorIndex; + uint64_t startPos; + uint64_t endPos; +}; + +struct MultiplexInputStreamParams +{ + InputStreamParams[] streams; + uint32_t currentStream; + nsresult status; + bool startedReadingCurrent; +}; + +struct RemoteInputStreamParams +{ + nsID id; +}; + +// XXX This may only be used for same-process inter-thread communication! The +// value should be reinterpret_cast'd to nsIInputStream. It carries a +// reference. +struct SameProcessInputStreamParams +{ + intptr_t addRefedInputStream; +}; + +union InputStreamParams +{ + StringInputStreamParams; + FileInputStreamParams; + PartialFileInputStreamParams; + TemporaryFileInputStreamParams; + BufferedInputStreamParams; + MIMEInputStreamParams; + MultiplexInputStreamParams; + RemoteInputStreamParams; + SameProcessInputStreamParams; +}; + +union OptionalInputStreamParams +{ + void_t; + InputStreamParams; +}; + +struct BufferedInputStreamParams +{ + OptionalInputStreamParams optionalStream; + uint32_t bufferSize; +}; + +struct MIMEInputStreamParams +{ + OptionalInputStreamParams optionalStream; + nsCString headers; + nsCString contentLength; + bool startedReading; + bool addContentLength; +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/InputStreamUtils.cpp b/ipc/glue/InputStreamUtils.cpp new file mode 100644 index 000000000..bbc863efd --- /dev/null +++ b/ipc/glue/InputStreamUtils.cpp @@ -0,0 +1,192 @@ +/* -*- 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/ipc/BlobChild.h" +#include "mozilla/dom/ipc/BlobParent.h" +#include "nsComponentManagerUtils.h" +#include "nsDebug.h" +#include "nsID.h" +#include "nsIXULRuntime.h" +#include "nsMIMEInputStream.h" +#include "nsMultiplexInputStream.h" +#include "nsNetCID.h" +#include "nsStringStream.h" +#include "nsXULAppAPI.h" + +using namespace mozilla::dom; + +namespace { + +NS_DEFINE_CID(kStringInputStreamCID, NS_STRINGINPUTSTREAM_CID); +NS_DEFINE_CID(kFileInputStreamCID, NS_LOCALFILEINPUTSTREAM_CID); +NS_DEFINE_CID(kPartialFileInputStreamCID, NS_PARTIALLOCALFILEINPUTSTREAM_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 { + +void +SerializeInputStream(nsIInputStream* aInputStream, + InputStreamParams& aParams, + nsTArray<FileDescriptor>& aFileDescriptors) +{ + MOZ_ASSERT(aInputStream); + + nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(aInputStream); + if (!serializable) { + MOZ_CRASH("Input stream is not serializable!"); + } + + serializable->Serialize(aParams, aFileDescriptors); + + if (aParams.type() == InputStreamParams::T__None) { + MOZ_CRASH("Serialize failed!"); + } +} + +void +SerializeInputStream(nsIInputStream* aInputStream, + OptionalInputStreamParams& aParams, + nsTArray<FileDescriptor>& aFileDescriptors) +{ + if (aInputStream) { + InputStreamParams params; + SerializeInputStream(aInputStream, params, aFileDescriptors); + aParams = params; + } + else { + aParams = mozilla::void_t(); + } +} + +already_AddRefed<nsIInputStream> +DeserializeInputStream(const InputStreamParams& aParams, + const nsTArray<FileDescriptor>& aFileDescriptors) +{ + nsCOMPtr<nsIInputStream> stream; + nsCOMPtr<nsIIPCSerializableInputStream> serializable; + + switch (aParams.type()) { + case InputStreamParams::TStringInputStreamParams: + serializable = do_CreateInstance(kStringInputStreamCID); + break; + + case InputStreamParams::TFileInputStreamParams: + serializable = do_CreateInstance(kFileInputStreamCID); + break; + + case InputStreamParams::TPartialFileInputStreamParams: + serializable = do_CreateInstance(kPartialFileInputStreamCID); + break; + + case InputStreamParams::TTemporaryFileInputStreamParams: + serializable = new nsTemporaryFileInputStream(); + break; + + case InputStreamParams::TBufferedInputStreamParams: + serializable = do_CreateInstance(kBufferedInputStreamCID); + break; + + case InputStreamParams::TMIMEInputStreamParams: + serializable = do_CreateInstance(kMIMEInputStreamCID); + break; + + case InputStreamParams::TMultiplexInputStreamParams: + serializable = do_CreateInstance(kMultiplexInputStreamCID); + break; + + // When the input stream already exists in this process, all we need to do + // is retrieve the original instead of sending any data over the wire. + case InputStreamParams::TRemoteInputStreamParams: { + if (NS_WARN_IF(!XRE_IsParentProcess())) { + return nullptr; + } + + const nsID& id = aParams.get_RemoteInputStreamParams().id(); + + RefPtr<BlobImpl> blobImpl = BlobParent::GetBlobImplForID(id); + + MOZ_ASSERT(blobImpl, "Invalid blob contents"); + + // If fetching the internal stream fails, we ignore it and return a + // null stream. + ErrorResult rv; + nsCOMPtr<nsIInputStream> stream; + blobImpl->GetInternalStream(getter_AddRefs(stream), rv); + if (NS_WARN_IF(rv.Failed()) || !stream) { + NS_WARNING("Couldn't obtain a valid stream from the blob"); + rv.SuppressException(); + } + return stream.forget(); + } + + case InputStreamParams::TSameProcessInputStreamParams: { + MOZ_ASSERT(aFileDescriptors.IsEmpty()); + + const SameProcessInputStreamParams& params = + aParams.get_SameProcessInputStreamParams(); + + stream = dont_AddRef( + reinterpret_cast<nsIInputStream*>(params.addRefedInputStream())); + MOZ_ASSERT(stream); + + return stream.forget(); + } + + default: + MOZ_ASSERT(false, "Unknown params!"); + return nullptr; + } + + MOZ_ASSERT(serializable); + + if (!serializable->Deserialize(aParams, aFileDescriptors)) { + MOZ_ASSERT(false, "Deserialize failed!"); + return nullptr; + } + + stream = do_QueryInterface(serializable); + MOZ_ASSERT(stream); + + return stream.forget(); +} + +already_AddRefed<nsIInputStream> +DeserializeInputStream(const OptionalInputStreamParams& aParams, + const nsTArray<FileDescriptor>& aFileDescriptors) +{ + nsCOMPtr<nsIInputStream> stream; + + switch (aParams.type()) { + case OptionalInputStreamParams::Tvoid_t: + // Leave stream null. + break; + + case OptionalInputStreamParams::TInputStreamParams: + stream = DeserializeInputStream(aParams.get_InputStreamParams(), + aFileDescriptors); + break; + + default: + MOZ_ASSERT(false, "Unknown params!"); + } + + return stream.forget(); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/InputStreamUtils.h b/ipc/glue/InputStreamUtils.h new file mode 100644 index 000000000..215a8cb23 --- /dev/null +++ b/ipc/glue/InputStreamUtils.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_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; + +void +SerializeInputStream(nsIInputStream* aInputStream, + InputStreamParams& aParams, + nsTArray<FileDescriptor>& aFileDescriptors); + +void +SerializeInputStream(nsIInputStream* aInputStream, + OptionalInputStreamParams& aParams, + nsTArray<FileDescriptor>& aFileDescriptors); + +already_AddRefed<nsIInputStream> +DeserializeInputStream(const InputStreamParams& aParams, + const nsTArray<FileDescriptor>& aFileDescriptors); + +already_AddRefed<nsIInputStream> +DeserializeInputStream(const OptionalInputStreamParams& aParams, + const nsTArray<FileDescriptor>& aFileDescriptors); + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_InputStreamUtils_h diff --git a/ipc/glue/MessageChannel.cpp b/ipc/glue/MessageChannel.cpp new file mode 100644 index 000000000..70e2387d5 --- /dev/null +++ b/ipc/glue/MessageChannel.cpp @@ -0,0 +1,2560 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 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 "mozilla/ipc/ProtocolUtils.h" + +#include "mozilla/dom/ScriptSettings.h" + +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Move.h" +#include "mozilla/SizePrintfMacros.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Logging.h" +#include "nsAutoPtr.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" +#include "nsContentUtils.h" + +using mozilla::Move; + +// Undo the damage done by mozzconf.h +#undef compress + +// Logging seems to be somewhat broken on b2g. +#ifdef MOZ_B2G +#define IPC_LOG(...) +#else +static mozilla::LazyLogModule sLogModule("ipc"); +#define IPC_LOG(...) MOZ_LOG(sLogModule, LogLevel::Debug, (__VA_ARGS__)) +#endif + +/* + * 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 namespace std; + +using mozilla::dom::AutoNoJSAPI; +using mozilla::dom::ScriptSettingsInitialized; +using mozilla::MonitorAutoLock; +using mozilla::MonitorAutoUnlock; + +#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 = 8192; + +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(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 = new IPC::Message(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(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(); + } + + nsAutoPtr<IPC::Message> GetReply() { + return 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. + nsAutoPtr<IPC::Message> mReply; +}; + +MessageChannel::MessageChannel(IToplevelProtocol *aListener) + : mListener(aListener), + mChannelState(ChannelClosed), + mSide(UnknownSide), + mLink(nullptr), + mWorkerLoop(nullptr), + mChannelErrorTask(nullptr), + mWorkerLoopID(-1), + mTimeoutMs(kNoTimeout), + mInTimeoutSecondHalf(false), + mNextSeqno(0), + mLastSendError(SyncSendError::SendSuccess), + mDispatchingAsyncMessage(false), + mDispatchingAsyncMessageNestedLevel(0), + mTransactionStack(nullptr), + mTimedOutMessageSeqno(0), + mTimedOutMessageNestedLevel(0), + mRemoteStackDepthGuess(0), + mSawInterruptOutMsg(false), + mIsWaitingForIncoming(false), + mAbortOnError(false), + mNotifiedChannelDone(false), + mFlags(REQUIRE_DEFAULT), + mPeerPidSet(false), + mPeerPid(-1) +{ + MOZ_COUNT_CTOR(ipc::MessageChannel); + +#ifdef OS_WIN + mTopFrame = nullptr; + mIsSyncWaitingOnNonMainThread = false; +#endif + + mOnChannelConnectedTask = + NewNonOwningCancelableRunnableMethod(this, &MessageChannel::DispatchOnChannelConnected); + +#ifdef OS_WIN + mEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + MOZ_RELEASE_ASSERT(mEvent, "CreateEvent failed! Nothing is going to work!"); +#endif +} + +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(); +} + +// 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 mWorkerLoopID; 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 (gParentProcessBlocker == this) { + gParentProcessBlocker = nullptr; + } + + mWorkerLoop = nullptr; + delete mLink; + mLink = nullptr; + + mOnChannelConnectedTask->Cancel(); + + if (mChannelErrorTask) { + mChannelErrorTask->Cancel(); + mChannelErrorTask = nullptr; + } + + // Free up any memory used by pending messages. + for (RefPtr<MessageTask> task : mPending) { + task->Clear(); + } + mPending.clear(); + + mOutOfTurnReplies.clear(); + while (!mDeferred.empty()) { + mDeferred.pop(); + } +} + +bool +MessageChannel::Open(Transport* aTransport, MessageLoop* aIOLoop, Side aSide) +{ + NS_PRECONDITION(!mLink, "Open() called > once"); + + mMonitor = new RefCountedMonitor(); + mWorkerLoop = MessageLoop::current(); + mWorkerLoopID = mWorkerLoop->id(); + + ProcessLink *link = new ProcessLink(this); + link->Open(aTransport, aIOLoop, aSide); // :TODO: n.b.: sets mChild + mLink = link; + return true; +} + +bool +MessageChannel::Open(MessageChannel *aTargetChan, MessageLoop *aTargetLoop, 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->SlaveOpen(PA, ...): + // - sets its state and that of PA to Connected + NS_PRECONDITION(aTargetChan, "Need a target channel"); + NS_PRECONDITION(ChannelClosed == mChannelState, "Not currently closed"); + + CommonThreadOpenInit(aTargetChan, 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; + aTargetLoop->PostTask(NewNonOwningRunnableMethod + <MessageChannel*, Side>(aTargetChan, + &MessageChannel::OnOpenAsSlave, + this, oppSide)); + + while (ChannelOpening == mChannelState) + mMonitor->Wait(); + MOZ_RELEASE_ASSERT(ChannelConnected == mChannelState, "not connected when awoken"); + return (ChannelConnected == mChannelState); +} + +void +MessageChannel::OnOpenAsSlave(MessageChannel *aTargetChan, Side aSide) +{ + // Invoked when the other side has begun the open. + NS_PRECONDITION(ChannelClosed == mChannelState, + "Not currently closed"); + NS_PRECONDITION(ChannelOpening == aTargetChan->mChannelState, + "Target channel not in the process of opening"); + + CommonThreadOpenInit(aTargetChan, 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, Side aSide) +{ + mWorkerLoop = MessageLoop::current(); + mWorkerLoopID = mWorkerLoop->id(); + mLink = new ThreadLink(this, aTargetChan); + mSide = aSide; +} + +bool +MessageChannel::Echo(Message* aMsg) +{ + nsAutoPtr<Message> msg(aMsg); + AssertWorkerThread(); + mMonitor->AssertNotCurrentThreadOwns(); + if (MSG_ROUTING_NONE == msg->routing_id()) { + ReportMessageRouteError("MessageChannel::Echo"); + return false; + } + + MonitorAutoLock lock(*mMonitor); + + if (!Connected()) { + ReportConnectionError("MessageChannel", msg); + return false; + } + + mLink->EchoMessage(msg.forget()); + return true; +} + +bool +MessageChannel::Send(Message* aMsg) +{ + if (aMsg->size() >= kMinTelemetryMessageSize) { + Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE, + nsDependentCString(aMsg->name()), aMsg->size()); + } + + MOZ_RELEASE_ASSERT(!aMsg->is_sync()); + MOZ_RELEASE_ASSERT(aMsg->nested_level() != IPC::Message::NESTED_INSIDE_SYNC); + + CxxStackFrame frame(*this, OUT_MESSAGE, aMsg); + + nsAutoPtr<Message> msg(aMsg); + AssertWorkerThread(); + mMonitor->AssertNotCurrentThreadOwns(); + if (MSG_ROUTING_NONE == msg->routing_id()) { + ReportMessageRouteError("MessageChannel::Send"); + return false; + } + + MonitorAutoLock lock(*mMonitor); + if (!Connected()) { + ReportConnectionError("MessageChannel", msg); + return false; + } + mLink->SendMessage(msg.forget()); + 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; + } + } + return false; +} + +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) + 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); + return true; + } + + 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; + + // 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(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() = Move(aMsg); + + reuseTask = true; + } + } else if (aMsg.compress_type() == IPC::Message::COMPRESSION_ALL && !mPending.isEmpty()) { + for (RefPtr<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); + p->remove(); + break; + } + } + } + + 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. + + RefPtr<MessageTask> task = new MessageTask(this, Move(aMsg)); + mPending.insertBack(task); + + if (shouldWakeUp) { + NotifyWorkerThread(); + } + + if (shouldPostTask) { + task->Post(); + } +} + +void +MessageChannel::PeekMessages(mozilla::function<bool(const Message& aMsg)> aInvoke) +{ + // FIXME: We shouldn't be holding the lock for aInvoke! + MonitorAutoLock lock(*mMonitor); + + for (RefPtr<MessageTask> it : mPending) { + const Message &msg = it->Msg(); + if (!aInvoke(msg)) { + break; + } + } +} + +void +MessageChannel::ProcessPendingRequests(AutoEnterTransaction& aTransaction) +{ + mMonitor->AssertCurrentThreadOwns(); + + 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 (RefPtr<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) { + if (!toProcess.append(Move(msg))) + MOZ_CRASH(); + + 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(Move(*it)); + } + } +} + +bool +MessageChannel::Send(Message* aMsg, Message* aReply) +{ + if (aMsg->size() >= kMinTelemetryMessageSize) { + Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE, + nsDependentCString(aMsg->name()), aMsg->size()); + } + + nsAutoPtr<Message> msg(aMsg); + + // Sanity checks. + AssertWorkerThread(); + mMonitor->AssertNotCurrentThreadOwns(); + +#ifdef OS_WIN + SyncStackFrame frame(this, false); + NeuteredWindowRegion neuteredRgn(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION); +#endif + + CxxStackFrame f(*this, OUT_MESSAGE, msg); + + 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 && + msg->nested_level() > IPC::Message::NOT_NESTED) + { + // Don't allow sending CPOWs while we're dispatching a sync message. + // If you want to do that, use sendRpcMessage instead. + 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(msg->nested_level() == IPC::Message::NESTED_INSIDE_SYNC); + IPC_LOG("Sending while dispatching urgent message"); + mLastSendError = SyncSendError::SendingCPOWWhileDispatchingUrgent; + return false; + } + + if (msg->nested_level() < DispatchingSyncMessageNestedLevel() || + msg->nested_level() < AwaitingSyncReplyNestedLevel()) + { + MOZ_RELEASE_ASSERT(DispatchingSyncMessage() || DispatchingAsyncMessage()); + IPC_LOG("Cancel from Send"); + CancelMessage *cancel = new CancelMessage(CurrentNestedInsideSyncTransaction()); + CancelTransaction(CurrentNestedInsideSyncTransaction()); + mLink->SendMessage(cancel); + } + + IPC_ASSERT(msg->is_sync(), "can only Send() sync messages here"); + + IPC_ASSERT(msg->nested_level() >= DispatchingSyncMessageNestedLevel(), + "can't send sync message of a lesser nested level than what's being dispatched"); + IPC_ASSERT(AwaitingSyncReplyNestedLevel() <= msg->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", msg); + mLastSendError = SyncSendError::NotConnectedBeforeSend; + return false; + } + + msg->set_seqno(NextSeqno()); + + int32_t seqno = msg->seqno(); + int nestedLevel = msg->nested_level(); + msgid_t replyType = msg->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; + msg->set_transaction_id(transaction); + + bool handleWindowsMessages = mListener->HandleWindowsMessages(*aMsg); + AutoEnterTransaction transact(this, seqno, transaction, nestedLevel); + + IPC_LOG("Send seqno=%d, xid=%d", seqno, transaction); + + // msg will be destroyed soon, but name() is not owned by msg. + const char* msgName = msg->name(); + + mLink->SendMessage(msg.forget()); + + 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; + } + + IPC_LOG("Got reply: seqno=%d, xid=%d", seqno, transaction); + + nsAutoPtr<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()); + + *aReply = Move(*reply); + if (aReply->size() >= kMinTelemetryMessageSize) { + Telemetry::Accumulate(Telemetry::IPC_REPLY_SIZE, + nsDependentCString(msgName), aReply->size()); + } + return true; +} + +bool +MessageChannel::Call(Message* aMsg, Message* aReply) +{ + nsAutoPtr<Message> msg(aMsg); + AssertWorkerThread(); + mMonitor->AssertNotCurrentThreadOwns(); + +#ifdef OS_WIN + SyncStackFrame frame(this, true); +#endif + + // This must come before MonitorAutoLock, as its destructor acquires the + // monitor lock. + CxxStackFrame cxxframe(*this, OUT_MESSAGE, msg); + + MonitorAutoLock lock(*mMonitor); + if (!Connected()) { + ReportConnectionError("MessageChannel::Call", msg); + 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(msg->is_interrupt(), "can only Call() Interrupt messages here"); + + msg->set_seqno(NextSeqno()); + msg->set_interrupt_remote_stack_depth_guess(mRemoteStackDepthGuess); + msg->set_interrupt_local_stack_depth(1 + InterruptStackDepth()); + mInterruptStack.push(MessageInfo(*msg)); + mLink->SendMessage(msg.forget()); + + 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 = Move(it->second); + mOutOfTurnReplies.erase(it); + } else if (!mPending.isEmpty()) { + RefPtr<MessageTask> task = mPending.popFirst(); + recvd = Move(task->Msg()); + } 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(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()] = 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(); + + bool is_reply_error = recvd.is_reply_error(); + if (!is_reply_error) { + *aReply = 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(); + { + MonitorAutoUnlock unlock(*mMonitor); + + CxxStackFrame frame(*this, IN_MESSAGE, &recvd); + DispatchInterruptMessage(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(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. +#ifdef DEBUG + for (RefPtr<MessageTask> task : mPending) { + if (task == &aTask) { + break; + } + + MOZ_ASSERT(!ShouldRunMessage(task->Msg()) || + aTask.Msg().priority() != task->Msg().priority()); + + } +#endif + + if (!mDeferred.empty()) { + MaybeUndeferIncall(); + } + + if (!ShouldRunMessage(msg)) { + return; + } + + MOZ_RELEASE_ASSERT(aTask.isInList()); + aTask.remove(); + + 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()] = Move(msg); + return; + } + + DispatchMessage(Move(msg)); +} + +NS_IMPL_ISUPPORTS_INHERITED(MessageChannel::MessageTask, CancelableRunnable, nsIRunnablePriority) + +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(); + + return NS_OK; +} + +void +MessageChannel::MessageTask::Post() +{ + MOZ_RELEASE_ASSERT(!mScheduled); + MOZ_RELEASE_ASSERT(isInList()); + + mScheduled = true; + + RefPtr<MessageTask> self = this; + mChannel->mWorkerLoop->PostTask(self.forget()); +} + +void +MessageChannel::MessageTask::Clear() +{ + mChannel->AssertWorkerThread(); + + mChannel = nullptr; +} + +NS_IMETHODIMP +MessageChannel::MessageTask::GetPriority(uint32_t* aPriority) +{ + *aPriority = mMessage.priority() == Message::HIGH_PRIORITY ? + PRIORITY_HIGH : PRIORITY_NORMAL; + return NS_OK; +} + +void +MessageChannel::DispatchMessage(Message &&aMsg) +{ + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + + Maybe<AutoNoJSAPI> nojsapi; + if (ScriptSettingsInitialized() && NS_IsMainThread()) + nojsapi.emplace(); + + nsAutoPtr<Message> reply; + + IPC_LOG("DispatchMessage: seqno=%d, xid=%d", aMsg.seqno(), aMsg.transaction_id()); + + { + AutoEnterTransaction transaction(this, aMsg); + + int id = aMsg.transaction_id(); + MOZ_RELEASE_ASSERT(!aMsg.is_sync() || id == transaction.TransactionID()); + + { + MonitorAutoUnlock unlock(*mMonitor); + CxxStackFrame frame(*this, IN_MESSAGE, &aMsg); + + mListener->ArtificialSleep(); + + if (aMsg.is_sync()) + DispatchSyncMessage(aMsg, *getter_Transfers(reply)); + else if (aMsg.is_interrupt()) + DispatchInterruptMessage(Move(aMsg), 0); + else + DispatchAsyncMessage(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()); + mLink->SendMessage(reply.forget()); + } +} + +void +MessageChannel::DispatchSyncMessage(const Message& aMsg, Message*& aReply) +{ + AssertWorkerThread(); + + int nestedLevel = aMsg.nested_level(); + + MOZ_RELEASE_ASSERT(nestedLevel == IPC::Message::NOT_NESTED || NS_IsMainThread()); + + MessageChannel* dummy; + MessageChannel*& blockingVar = mSide == ChildSide && NS_IsMainThread() ? gParentProcessBlocker : dummy; + + Result rv; + { + AutoSetValue<MessageChannel*> blocked(blockingVar, this); + rv = mListener->OnMessageReceived(aMsg, aReply); + } + + if (!MaybeHandleError(rv, aMsg, "DispatchSyncMessage")) { + aReply = new Message(); + aReply->set_sync(); + aReply->set_nested_level(aMsg.nested_level()); + aReply->set_reply(); + aReply->set_reply_error(); + } + aReply->set_seqno(aMsg.seqno()); + aReply->set_transaction_id(aMsg.transaction_id()); +} + +void +MessageChannel::DispatchAsyncMessage(const Message& aMsg) +{ + AssertWorkerThread(); + MOZ_RELEASE_ASSERT(!aMsg.is_interrupt() && !aMsg.is_sync()); + + if (aMsg.routing_id() == MSG_ROUTING_NONE) { + NS_RUNTIMEABORT("unhandled special message!"); + } + + Result rv; + { + int nestedLevel = aMsg.nested_level(); + AutoSetValue<bool> async(mDispatchingAsyncMessage, true); + AutoSetValue<int> nestedLevelSet(mDispatchingAsyncMessageNestedLevel, nestedLevel); + rv = mListener->OnMessageReceived(aMsg); + } + MaybeHandleError(rv, aMsg, "DispatchAsyncMessage"); +} + +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 MessageInfo parentMsgInfo = + (mSide == ChildSide) ? MessageInfo(aMsg) : mInterruptStack.top(); + const MessageInfo childMsgInfo = + (mSide == ChildSide) ? mInterruptStack.top() : MessageInfo(aMsg); + switch (mListener->MediateInterruptRace(parentMsgInfo, childMsgInfo)) + { + case RIPChildWins: + defer = (mSide == ChildSide); + break; + case RIPParentWins: + defer = (mSide != ChildSide); + break; + case RIPError: + MOZ_CRASH("NYI: 'Error' Interrupt race policy"); + default: + MOZ_CRASH("not reached"); + } + + return defer; +} + +void +MessageChannel::DispatchInterruptMessage(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(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 + + nsAutoPtr<Message> reply; + + ++mRemoteStackDepthGuess; + Result rv = mListener->OnCallReceived(aMsg, *getter_Transfers(reply)); + --mRemoteStackDepthGuess; + + if (!MaybeHandleError(rv, aMsg, "DispatchInterruptMessage")) { + reply = new Message(); + reply->set_interrupt(); + reply->set_reply(); + reply->set_reply_error(); + } + reply->set_seqno(aMsg.seqno()); + + MonitorAutoLock lock(*mMonitor); + if (ChannelConnected == mChannelState) { + mLink->SendMessage(reply.forget()); + } +} + +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(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, Move(call)); + mPending.insertBack(task); + 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(); +} + +static inline bool +IsTimeoutExpired(PRIntervalTime aStart, PRIntervalTime aTimeout) +{ + return (aTimeout != PR_INTERVAL_NO_TIMEOUT) && + (aTimeout <= (PR_IntervalNow() - aStart)); +} + +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 + + PRIntervalTime timeout = (kNoTimeout == mTimeoutMs) ? + PR_INTERVAL_NO_TIMEOUT : + PR_MillisecondsToInterval(mTimeoutMs); + // XXX could optimize away this syscall for "no timeout" case if desired + PRIntervalTime waitStart = PR_IntervalNow(); + + mMonitor->Wait(timeout); + + // If the timeout didn't expire, we know we received an event. The + // converse is not true. + return WaitResponse(IsTimeoutExpired(waitStart, 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") ? 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; + mWorkerLoop->PostTask(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: + NS_RUNTIMEABORT("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: + NS_RUNTIMEABORT("unknown Result code"); + return false; + } + + char reason[512]; + const char* msgname = StringFromIPCMessageType(aMsg.type()); + if (msgname[0] == '?') { + SprintfLiteral(reason,"(msgtype=0x%X) %s", aMsg.type(), errorMsg); + } else { + SprintfLiteral(reason,"%s %s", msgname, errorMsg); + } + + PrintErrorMessage(mSide, channelName, reason); + + 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) { + NS_RUNTIMEABORT("Aborting on channel error."); + } + 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(this, &MessageChannel::OnNotifyMaybeChannelError); + RefPtr<Runnable> task = mChannelErrorTask; + // 10 ms delay is completely arbitrary + mWorkerLoop->PostDelayedTask(task.forget(), 10); + return; + } + + NotifyMaybeChannelError(); +} + +void +MessageChannel::PostErrorNotifyTask() +{ + mMonitor->AssertCurrentThreadOwns(); + + if (mChannelErrorTask) + return; + + // This must be the last code that runs on this thread! + mChannelErrorTask = + NewNonOwningCancelableRunnableMethod(this, &MessageChannel::OnNotifyMaybeChannelError); + RefPtr<Runnable> task = mChannelErrorTask; + mWorkerLoop->PostTask(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(); + 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::Close() +{ + AssertWorkerThread(); + + { + MonitorAutoLock lock(*mMonitor); + + 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) { + MonitorAutoUnlock unlock(*mMonitor); + 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) { + // XXX be strict about this until there's a compelling reason + // to relax + NS_RUNTIMEABORT("Close() called on closed channel!"); + } + + // 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(new GoodbyeMessage()); + } + SynchronouslyClose(); + } + + NotifyChannelClosed(); +} + +void +MessageChannel::NotifyChannelClosed() +{ + mMonitor->AssertNotCurrentThreadOwns(); + + if (ChannelClosed != mChannelState) + NS_RUNTIMEABORT("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: %" PRIuSIZE "\n", + mRemoteStackDepthGuess); + printf_stderr(" deferred stack size: %" PRIuSIZE "\n", + mDeferred.size()); + printf_stderr(" out-of-turn Interrupt replies stack size: %" PRIuSIZE "\n", + mOutOfTurnReplies.size()); + + MessageQueue pending = 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(); + } + + NS_RUNTIMEABORT(why); +} + +void +MessageChannel::DumpInterruptStack(const char* const pfx) const +{ + NS_WARNING_ASSERTION( + MessageLoop::current() != mWorkerLoop, + "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); + } +} + +int32_t +MessageChannel::GetTopmostMessageRoutingId() const +{ + MOZ_RELEASE_ASSERT(MessageLoop::current() == mWorkerLoop); + 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 (RefPtr<MessageTask> task : mPending) { + if (!task->IsScheduled()) { + needRepost = true; + } + } + 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 = Move(mPending); + while (RefPtr<MessageTask> task = queue.popFirst()) { + RefPtr<MessageTask> newTask = new MessageTask(this, Move(task->Msg())); + mPending.insertBack(newTask); + newTask->Post(); + } +} + +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 (RefPtr<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; + p = p->removeAndGetNext(); + continue; + } + + p = p->getNext(); + } +} + +bool +MessageChannel::IsInTransaction() const +{ + MonitorAutoLock lock(*mMonitor); + return !!mTransactionStack; +} + +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()); + CancelMessage *cancel = new CancelMessage(CurrentNestedInsideSyncTransaction()); + CancelTransaction(CurrentNestedInsideSyncTransaction()); + mLink->SendMessage(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 000000000..df70899df --- /dev/null +++ b/ipc/glue/MessageChannel.h @@ -0,0 +1,718 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 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 "base/basictypes.h" +#include "base/message_loop.h" + +#include "mozilla/Function.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Monitor.h" +#include "mozilla/Vector.h" +#if defined(OS_WIN) +#include "mozilla/ipc/Neutering.h" +#endif // defined(OS_WIN) +#include "mozilla/ipc/Transport.h" +#if defined(MOZ_CRASHREPORTER) && defined(OS_WIN) +#include "mozilla/mozalloc_oom.h" +#include "nsExceptionHandler.h" +#endif +#include "MessageLink.h" + +#include <deque> +#include <stack> +#include <math.h> + +namespace mozilla { +namespace ipc { + +class MessageChannel; +class IToplevelProtocol; + +class RefCountedMonitor : public Monitor +{ + public: + RefCountedMonitor() + : Monitor("mozilla.ipc.MessageChannel.mMonitor") + {} + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefCountedMonitor) + + private: + ~RefCountedMonitor() {} +}; + +enum class SyncSendError { + SendSuccess, + PreviousTimeout, + SendingCPOWWhileDispatchingSync, + SendingCPOWWhileDispatchingUrgent, + NotConnectedBeforeSend, + DisconnectedDuringSend, + CancelledBeforeSend, + CancelledAfterSend, + TimedOut, + ReplyError, +}; + +enum ChannelState { + ChannelClosed, + ChannelOpening, + ChannelConnected, + ChannelTimeout, + ChannelClosing, + ChannelError +}; + +class AutoEnterTransaction; + +class MessageChannel : HasResultCodes +{ + friend class ProcessLink; + friend class ThreadLink; + + class CxxStackFrame; + class InterruptFrame; + + typedef mozilla::Monitor Monitor; + + public: + static const int32_t kNoTimeout; + + typedef IPC::Message Message; + typedef IPC::MessageInfo MessageInfo; + typedef mozilla::ipc::Transport Transport; + + explicit MessageChannel(IToplevelProtocol *aListener); + ~MessageChannel(); + + // "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(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, MessageLoop *aTargetLoop, Side aSide); + + // 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(mozilla::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(Message* aMsg); + + // Asynchronously deliver a message back to this side of the + // channel + bool Echo(Message* aMsg); + + // Synchronously send |msg| (i.e., wait for |reply|) + bool Send(Message* aMsg, Message* aReply); + + // Make an Interrupt call to the other side of the channel + bool Call(Message* aMsg, Message* aReply); + + // Wait until a message is received + bool WaitForIncomingMessage(); + + bool CanSend() const; + + // 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(); + } + + bool IsInTransaction() const; + void CancelCurrentTransaction(); + + /** + * 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; + } + +#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, Side aSide); + void OnOpenAsSlave(MessageChannel *aTargetChan, 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(const Message &aMsg, Message*& aReply); + void DispatchUrgentMessage(const Message &aMsg); + void DispatchAsyncMessage(const Message &aMsg); + void DispatchRPCMessage(const Message &aMsg); + void DispatchInterruptMessage(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(); + + IToplevelProtocol *Listener() const { + return mListener; + } + + 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; + + 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(); + + 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: + // Can be run on either thread + void AssertWorkerThread() const + { + MOZ_RELEASE_ASSERT(mWorkerLoopID == MessageLoop::current()->id(), + "not on worker thread!"); + } + + // The "link" thread is either the I/O thread (ProcessLink) or the + // other actor's work thread (ThreadLink). In either case, it is + // NOT our worker thread. + void AssertLinkThread() const + { + MOZ_RELEASE_ASSERT(mWorkerLoopID != MessageLoop::current()->id(), + "on worker thread but should not be!"); + } + + private: + class MessageTask : + public CancelableRunnable, + public LinkedListElement<RefPtr<MessageTask>>, + public nsIRunnablePriority + { + public: + explicit MessageTask(MessageChannel* aChannel, Message&& aMessage) + : mChannel(aChannel), mMessage(Move(aMessage)), mScheduled(false) + {} + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD Run() override; + nsresult Cancel() override; + NS_IMETHOD GetPriority(uint32_t* aPriority) override; + 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() {} + + 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 IPC::Message::msgid_t msgid_t; + + private: + // Based on presumption the listener owns and overlives the channel, + // this is never nullified. + IToplevelProtocol* mListener; + ChannelState mChannelState; + RefPtr<RefCountedMonitor> mMonitor; + Side mSide; + MessageLink* mLink; + MessageLoop* mWorkerLoop; // thread where work is done + RefPtr<CancelableRunnable> mChannelErrorTask; // NotifyMaybeChannelError runnable + + // id() of mWorkerLoop. This persists even after mWorkerLoop is cleared + // during channel shutdown. + int mWorkerLoopID; + + // 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 + // synchronous 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; + + // 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; + + // 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; + + // 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; +}; + +void +CancelCPOWs(); + +} // namespace ipc +} // namespace mozilla + +#endif // ifndef ipc_glue_MessageChannel_h diff --git a/ipc/glue/MessageLink.cpp b/ipc/glue/MessageLink.cpp new file mode 100644 index 000000000..6a1bda02d --- /dev/null +++ b/ipc/glue/MessageLink.cpp @@ -0,0 +1,387 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 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 "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "nsDebug.h" +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif +#include "nsISupportsImpl.h" +#include "nsXULAppAPI.h" + +using namespace mozilla; +using namespace std; + +// 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) + , mTransport(nullptr) + , mIOLoop(nullptr) + , mExistingListener(nullptr) +{ +} + +ProcessLink::~ProcessLink() +{ +#ifdef DEBUG + mTransport = nullptr; + mIOLoop = nullptr; + mExistingListener = nullptr; +#endif +} + +void +ProcessLink::Open(mozilla::ipc::Transport* aTransport, MessageLoop *aIOLoop, Side aSide) +{ + NS_PRECONDITION(aTransport, "need transport layer"); + + // FIXME need to check for valid channel + + mTransport = 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 { + NS_PRECONDITION(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->mWorkerLoop, "need a worker loop"); + + { + 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(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(this, &ProcessLink::OnTakeConnectedChannel)); + } + + // Should not wait here if something goes wrong with the channel. + while (!mChan->Connected() && mChan->mChannelState != ChannelError) { + mChan->mMonitor->Wait(); + } + } +} + +void +ProcessLink::EchoMessage(Message *msg) +{ + mChan->AssertWorkerThread(); + mChan->mMonitor->AssertCurrentThreadOwns(); + + mIOLoop->PostTask(NewNonOwningRunnableMethod<Message*>(this, &ProcessLink::OnEchoMessage, msg)); + // OnEchoMessage takes ownership of |msg| +} + +void +ProcessLink::SendMessage(Message *msg) +{ + if (msg->size() > IPC::Channel::kMaximumMessageSize) { +#ifdef MOZ_CRASHREPORTER + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCMessageName"), nsDependentCString(msg->name())); + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCMessageSize"), nsPrintfCString("%d", msg->size())); +#endif + MOZ_CRASH("IPC message size is too large"); + } + + mChan->AssertWorkerThread(); + mChan->mMonitor->AssertCurrentThreadOwns(); + + mIOLoop->PostTask(NewNonOwningRunnableMethod<Message*>(mTransport, &Transport::Send, msg)); +} + +void +ProcessLink::SendClose() +{ + mChan->AssertWorkerThread(); + mChan->mMonitor->AssertCurrentThreadOwns(); + + mIOLoop->PostTask(NewNonOwningRunnableMethod(this, &ProcessLink::OnCloseChannel)); +} + +ThreadLink::ThreadLink(MessageChannel *aChan, MessageChannel *aTargetChan) + : MessageLink(aChan), + mTargetChan(aTargetChan) +{ +} + +ThreadLink::~ThreadLink() +{ + 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 ~ThreadLink() calls racing. + if (mTargetChan) { + MOZ_ASSERT(mTargetChan->mLink); + static_cast<ThreadLink*>(mTargetChan->mLink)->mTargetChan = nullptr; + } + mTargetChan = nullptr; +} + +void +ThreadLink::EchoMessage(Message *msg) +{ + mChan->AssertWorkerThread(); + mChan->mMonitor->AssertCurrentThreadOwns(); + + mChan->OnMessageReceivedFromLink(Move(*msg)); + delete msg; +} + +void +ThreadLink::SendMessage(Message *msg) +{ + mChan->AssertWorkerThread(); + mChan->mMonitor->AssertCurrentThreadOwns(); + + if (mTargetChan) + mTargetChan->OnMessageReceivedFromLink(Move(*msg)); + delete 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(Move(msg)); +} + +void +ProcessLink::OnEchoMessage(Message* msg) +{ + AssertIOThread(); + OnMessageReceived(Move(*msg)); + delete msg; +} + +void +ProcessLink::OnChannelOpened() +{ + AssertIOThread(); + + { + MonitorAutoLock lock(*mChan->mMonitor); + + mExistingListener = mTransport->set_listener(this); +#ifdef DEBUG + if (mExistingListener) { + queue<Message> pending; + mExistingListener->GetQueuedMessages(pending); + MOZ_ASSERT(pending.empty()); + } +#endif // DEBUG + + mChan->mChannelState = ChannelOpening; + lock.Notify(); + } + /*assert*/mTransport->Connect(); +} + +void +ProcessLink::OnTakeConnectedChannel() +{ + AssertIOThread(); + + 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(Move(pending.front())); + pending.pop(); + } +} + +void +ProcessLink::OnChannelConnected(int32_t peer_pid) +{ + AssertIOThread(); + + bool notifyChannel = false; + + { + MonitorAutoLock lock(*mChan->mMonitor); + // Only update channel state if its still thinks its opening. Do not + // force it into connected if it has errored out, started closing, etc. + if (mChan->mChannelState == ChannelOpening) { + mChan->mChannelState = ChannelConnected; + mChan->mMonitor->Notify(); + notifyChannel = true; + } + } + + if (mExistingListener) + mExistingListener->OnChannelConnected(peer_pid); + + if (notifyChannel) { + mChan->OnChannelConnected(peer_pid); + } +} + +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 000000000..d0e05fc63 --- /dev/null +++ b/ipc/glue/MessageLink.h @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 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 "base/basictypes.h" +#include "base/message_loop.h" + +#include "mozilla/WeakPtr.h" +#include "mozilla/ipc/Transport.h" + +namespace mozilla { +namespace ipc { + +class MessageChannel; + +struct HasResultCodes +{ + enum Result { + MsgProcessed, + MsgDropped, + MsgNotKnown, + MsgNotAllowed, + MsgPayloadError, + MsgProcessingError, + MsgRouteError, + MsgValueError + }; +}; + +enum Side { + ParentSide, + ChildSide, + UnknownSide +}; + +class MessageLink +{ + public: + typedef IPC::Message Message; + + explicit MessageLink(MessageChannel *aChan); + virtual ~MessageLink(); + + // n.b.: These methods all require that the channel monitor is + // held when they are invoked. + virtual void EchoMessage(Message *msg) = 0; + virtual void SendMessage(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 OnEchoMessage(Message* msg); + + 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(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 EchoMessage(Message *msg) override; + virtual void SendMessage(Message *msg) override; + virtual void SendClose() override; + + virtual bool Unsound_IsClosed() const override; + virtual uint32_t Unsound_NumQueuedMessages() const override; + + protected: + 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(); + + virtual void EchoMessage(Message *msg) override; + virtual void SendMessage(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 000000000..15c17b8f4 --- /dev/null +++ b/ipc/glue/MessagePump.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 "MessagePump.h" + +#include "nsIRunnable.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) + : mPump(aPump) + { + MOZ_ASSERT(aPump); + } + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSITIMERCALLBACK + nsresult Cancel() override; + +private: + ~DoWorkRunnable() + { } + + MessagePump* mPump; + // DoWorkRunnable is designed as a stateless singleton. Do not add stateful + // members here! +}; + +} /* namespace ipc */ +} /* namespace mozilla */ + +MessagePump::MessagePump(nsIThread* aThread) +: mThread(aThread) +{ + mDoWorkEvent = new DoWorkRunnable(this); +} + +MessagePump::~MessagePump() +{ +} + +void +MessagePump::Run(MessagePump::Delegate* aDelegate) +{ + MOZ_ASSERT(keep_running_); + MOZ_RELEASE_ASSERT(NS_IsMainThread(), + "Use mozilla::ipc::MessagePumpForNonMainThreads instead!"); + MOZ_RELEASE_ASSERT(!mThread); + + nsIThread* thisThread = NS_GetCurrentThread(); + MOZ_ASSERT(thisThread); + + mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID); + 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 (mThread) { + mThread->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(NS_GetCurrentThread() == mThread || + (!mThread && NS_IsMainThread())); + + if (!mDelayedWorkTimer) { + mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID); + 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 (mThread) { + return mThread; + } + + // Main thread + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); + return mainThread; +} + +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(mThread == thread); + + mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID); + MOZ_ASSERT(mDelayedWorkTimer); + + if (NS_FAILED(mDelayedWorkTimer->SetTarget(thread))) { + MOZ_CRASH("Failed to set timer target!"); + } + + // 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 mThread) 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(nsIThreadInternal *thread) +{ + // 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 diff --git a/ipc/glue/MessagePump.h b/ipc/glue/MessagePump.h new file mode 100644 index 000000000..3e48624ed --- /dev/null +++ b/ipc/glue/MessagePump.h @@ -0,0 +1,170 @@ +/* -*- 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 nsIThread; +class nsITimer; + +namespace mozilla { +namespace ipc { + +class DoWorkRunnable; + +class MessagePump : public base::MessagePumpDefault +{ + friend class DoWorkRunnable; + +public: + explicit MessagePump(nsIThread* aThread); + + // 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: + nsIThread* mThread; + + // mDelayedWorkTimer and mThread 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() + { } + + bool mFirstRun; +}; + +class MessagePumpForNonMainThreads final : public MessagePump +{ +public: + explicit MessagePumpForNonMainThreads(nsIThread* aThread) + : MessagePump(aThread) + { } + + virtual void Run(base::MessagePump::Delegate* aDelegate) override; + +private: + ~MessagePumpForNonMainThreads() + { } +}; + +#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(nsIThread* aThread) : + 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) + +} /* namespace ipc */ +} /* namespace mozilla */ + +#endif /* __IPC_GLUE_MESSAGEPUMP_H__ */ diff --git a/ipc/glue/Neutering.h b/ipc/glue/Neutering.h new file mode 100644 index 000000000..d4ca816da --- /dev/null +++ b/ipc/glue/Neutering.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ipc_Neutering_h +#define mozilla_ipc_Neutering_h + +#include "mozilla/GuardObjects.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 MOZ_GUARD_OBJECT_NOTIFIER_PARAM); + ~NeuteredWindowRegion(); + + /** + * This function clears any backlog of nonqueued messages that are pending for + * the current thread. + */ + void PumpOnce(); + +private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + 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: + DeneuteredWindowRegion(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); + ~DeneuteredWindowRegion(); + +private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + bool mReneuter; +}; + +class MOZ_RAII SuppressedNeuteringRegion +{ +public: + SuppressedNeuteringRegion(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); + ~SuppressedNeuteringRegion(); + + static inline bool IsNeuteringSuppressed() { return sSuppressNeutering; } + +private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + 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 000000000..eacb42769 --- /dev/null +++ b/ipc/glue/PBackground.ipdl @@ -0,0 +1,122 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 PAsmJSCacheEntry; +include protocol PBackgroundIDBFactory; +include protocol PBackgroundIndexedDBUtils; +include protocol PBackgroundTest; +include protocol PBlob; +include protocol PBroadcastChannel; +include protocol PCache; +include protocol PCacheStorage; +include protocol PCacheStreamControl; +include protocol PFileDescriptorSet; +include protocol PFileSystemRequest; +include protocol PGamepadEventChannel; +include protocol PGamepadTestChannel; +include protocol PMessagePort; +include protocol PCameras; +include protocol PQuota; +include protocol PSendStream; +include protocol PServiceWorkerManager; +include protocol PUDPSocket; +include protocol PVsync; + +include DOMTypes; +include PBackgroundSharedTypes; +include PBackgroundIDBSharedTypes; +include PFileSystemParams; +include ProtocolTypes; + +include "mozilla/dom/cache/IPCUtils.h"; + +using mozilla::dom::cache::Namespace + from "mozilla/dom/cache/Types.h"; + +using mozilla::dom::asmjscache::OpenMode + from "mozilla/dom/asmjscache/AsmJSCache.h"; + +using mozilla::dom::asmjscache::WriteParams + from "mozilla/dom/asmjscache/AsmJSCache.h"; + +namespace mozilla { +namespace ipc { + +sync protocol PBackground +{ + manages PAsmJSCacheEntry; + manages PBackgroundIDBFactory; + manages PBackgroundIndexedDBUtils; + manages PBackgroundTest; + manages PBlob; + manages PBroadcastChannel; + manages PCache; + manages PCacheStorage; + manages PCacheStreamControl; + manages PFileDescriptorSet; + manages PFileSystemRequest; + manages PGamepadEventChannel; + manages PGamepadTestChannel; + manages PMessagePort; + manages PCameras; + manages PQuota; + manages PSendStream; + manages PServiceWorkerManager; + manages PUDPSocket; + manages PVsync; + +parent: + // Only called at startup during mochitests to check the basic infrastructure. + async PBackgroundTest(nsCString testArg); + + async PBackgroundIDBFactory(LoggingInfo loggingInfo); + + async PBackgroundIndexedDBUtils(); + + // Use only for testing! + async FlushPendingFileDeletions(); + + async PVsync(); + + async PCameras(); + + async PUDPSocket(OptionalPrincipalInfo 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 PSendStream(); + + async MessagePortForceClose(nsID uuid, nsID destinationUuid, uint32_t sequenceId); + + async PAsmJSCacheEntry(OpenMode openMode, + WriteParams write, + PrincipalInfo principalInfo); + + async PQuota(); + + async PFileSystemRequest(FileSystemParams params); + + async PGamepadEventChannel(); + + async PGamepadTestChannel(); + +child: + async PCache(); + async PCacheStreamControl(); + +both: + async PBlob(BlobConstructorParams params); + + 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 000000000..ccca1decd --- /dev/null +++ b/ipc/glue/PBackgroundSharedTypes.ipdlh @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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::PrincipalOriginAttributes from "mozilla/ipc/BackgroundUtils.h"; +using struct mozilla::void_t from "ipc/IPCMessageUtils.h"; + +namespace mozilla { +namespace ipc { + +union ContentPrincipalInfoOriginNoSuffix +{ + nsCString; + void_t; +}; + +struct ContentPrincipalInfo +{ + PrincipalOriginAttributes attrs; + + // nsIPrincipal.originNoSuffix can fail. In case this happens, this value + // will be set to void_t. So far, this is used only for dom/media. + // It will be removed in bug 1347817. + ContentPrincipalInfoOriginNoSuffix originNoSuffix; + + nsCString spec; +}; + +struct SystemPrincipalInfo +{ }; + +struct NullPrincipalInfo +{ + PrincipalOriginAttributes attrs; +}; + +struct ExpandedPrincipalInfo +{ + PrincipalOriginAttributes attrs; + PrincipalInfo[] whitelist; +}; + +union PrincipalInfo +{ + ContentPrincipalInfo; + SystemPrincipalInfo; + NullPrincipalInfo; + ExpandedPrincipalInfo; +}; + +union OptionalPrincipalInfo +{ + void_t; + PrincipalInfo; +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/PBackgroundTest.ipdl b/ipc/glue/PBackgroundTest.ipdl new file mode 100644 index 000000000..527cd4ce7 --- /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/PFileDescriptorSet.ipdl b/ipc/glue/PFileDescriptorSet.ipdl new file mode 100644 index 000000000..763f72778 --- /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 PContentBridge; + +namespace mozilla { +namespace ipc { + +protocol PFileDescriptorSet +{ + manager PBackground or PContent or PContentBridge; + +both: + async AddFileDescriptor(FileDescriptor fd); + + async __delete__(); +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/PSendStream.ipdl b/ipc/glue/PSendStream.ipdl new file mode 100644 index 000000000..8f171672b --- /dev/null +++ b/ipc/glue/PSendStream.ipdl @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 PContentBridge; + +namespace mozilla { +namespace ipc { + +protocol PSendStream +{ + manager PBackground or PContent or PContentBridge; + +parent: + async Buffer(nsCString aBuffer); + async Close(nsresult aRv); + +child: + // 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 000000000..2a84d5ed2 --- /dev/null +++ b/ipc/glue/ProcessChild.cpp @@ -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/. */ + +#include "nsDebug.h" + +#ifdef XP_WIN +#include <stdlib.h> // for _exit() +#else +#include <unistd.h> // for _exit() +#endif + +#include "mozilla/ipc/IOThreadChild.h" +#include "mozilla/ipc/ProcessChild.h" + +namespace mozilla { +namespace ipc { + +ProcessChild* ProcessChild::gProcessChild; + +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::QuickExit() +{ +#ifdef XP_WIN + // In bug 1254829, the destructor got called when dll got detached on windows, + // switch to TerminateProcess to bypass dll detach handler during the process + // termination. + TerminateProcess(GetCurrentProcess(), 0); +#else + _exit(0); +#endif +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/ProcessChild.h b/ipc/glue/ProcessChild.h new file mode 100644 index 000000000..4d1d38659 --- /dev/null +++ b/ipc/glue/ProcessChild.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ipc_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() = 0; + virtual void CleanUp() + { } + + static MessageLoop* message_loop() { + return gProcessChild->mUILoop; + } + + /** + * 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 000000000..2908d9876 --- /dev/null +++ b/ipc/glue/ProcessUtils.h @@ -0,0 +1,21 @@ +/* -*- 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 + +namespace mozilla { +namespace ipc { + +// You probably should call ContentChild::SetProcessName instead of calling +// this directly. +void SetThisProcessName(const char *aName); + +} // 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 000000000..f6c7de227 --- /dev/null +++ b/ipc/glue/ProcessUtils_bsd.cpp @@ -0,0 +1,28 @@ +/* -*- 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_linux.cpp b/ipc/glue/ProcessUtils_linux.cpp new file mode 100644 index 000000000..d95527a4f --- /dev/null +++ b/ipc/glue/ProcessUtils_linux.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 "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 000000000..6c5738871 --- /dev/null +++ b/ipc/glue/ProcessUtils_mac.mm @@ -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 "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 000000000..721bd4a74 --- /dev/null +++ b/ipc/glue/ProcessUtils_none.cpp @@ -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/. */ + +#include "ProcessUtils.h" + +namespace mozilla { +namespace ipc { + +void SetThisProcessName(const char *aString) +{ + (void)aString; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/ProtocolTypes.ipdlh b/ipc/glue/ProtocolTypes.ipdlh new file mode 100644 index 000000000..b1d531673 --- /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 000000000..1a022048f --- /dev/null +++ b/ipc/glue/ProtocolUtils.cpp @@ -0,0 +1,720 @@ +/* -*- 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 "mozilla/ipc/ProtocolUtils.h" + +#include "mozilla/dom/ContentParent.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/ipc/Transport.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/Unused.h" +#include "nsPrintfCString.h" + +#if defined(MOZ_SANDBOX) && defined(XP_WIN) +#define TARGET_SANDBOX_EXPORTS +#include "mozilla/sandboxTarget.h" +#endif + +#if defined(MOZ_CRASHREPORTER) && defined(XP_WIN) +#include "aclapi.h" +#include "sddl.h" + +#include "mozilla/TypeTraits.h" +#endif + +#include "nsAutoPtr.h" + +using namespace IPC; + +using base::GetCurrentProcId; +using base::ProcessHandle; +using base::ProcessId; + +namespace mozilla { + +#if defined(MOZ_CRASHREPORTER) && defined(XP_WIN) +// Generate RAII classes for LPTSTR and PSECURITY_DESCRIPTOR. +MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedLPTStr, \ + RemovePointer<LPTSTR>::Type, \ + ::LocalFree) +MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPSecurityDescriptor, \ + RemovePointer<PSECURITY_DESCRIPTOR>::Type, \ + ::LocalFree) +#endif + +namespace ipc { + +class ChannelOpened : public IPC::Message +{ +public: + ChannelOpened(TransportDescriptor aDescriptor, + ProcessId aOtherProcess, + ProtocolId aProtocol, + NestedLevel aNestedLevel = NOT_NESTED) + : IPC::Message(MSG_ROUTING_CONTROL, // these only go to top-level actors + CHANNEL_OPENED_MESSAGE_TYPE, + aNestedLevel) + { + IPC::WriteParam(this, aDescriptor); + IPC::WriteParam(this, aOtherProcess); + IPC::WriteParam(this, static_cast<uint32_t>(aProtocol)); + } + + static bool Read(const IPC::Message& aMsg, + TransportDescriptor* aDescriptor, + ProcessId* aOtherProcess, + ProtocolId* aProtocol) + { + PickleIterator iter(aMsg); + if (!IPC::ReadParam(&aMsg, &iter, aDescriptor) || + !IPC::ReadParam(&aMsg, &iter, aOtherProcess) || + !IPC::ReadParam(&aMsg, &iter, reinterpret_cast<uint32_t*>(aProtocol))) { + return false; + } + aMsg.EndRead(iter); + return true; + } +}; + +nsresult +Bridge(const PrivateIPDLInterface&, + MessageChannel* aParentChannel, ProcessId aParentPid, + MessageChannel* aChildChannel, ProcessId aChildPid, + ProtocolId aProtocol, ProtocolId aChildProtocol) +{ + if (!aParentPid || !aChildPid) { + return NS_ERROR_INVALID_ARG; + } + + TransportDescriptor parentSide, childSide; + nsresult rv; + if (NS_FAILED(rv = CreateTransport(aParentPid, &parentSide, &childSide))) { + return rv; + } + + if (!aParentChannel->Send(new ChannelOpened(parentSide, + aChildPid, + aProtocol, + IPC::Message::NESTED_INSIDE_CPOW))) { + CloseDescriptor(parentSide); + CloseDescriptor(childSide); + return NS_ERROR_BRIDGE_OPEN_PARENT; + } + + if (!aChildChannel->Send(new ChannelOpened(childSide, + aParentPid, + aChildProtocol, + IPC::Message::NESTED_INSIDE_CPOW))) { + CloseDescriptor(parentSide); + CloseDescriptor(childSide); + return NS_ERROR_BRIDGE_OPEN_CHILD; + } + + return NS_OK; +} + +bool +Open(const PrivateIPDLInterface&, + MessageChannel* aOpenerChannel, ProcessId aOtherProcessId, + Transport::Mode aOpenerMode, + ProtocolId aProtocol, ProtocolId aChildProtocol) +{ + bool isParent = (Transport::MODE_SERVER == aOpenerMode); + ProcessId thisPid = GetCurrentProcId(); + ProcessId parentId = isParent ? thisPid : aOtherProcessId; + ProcessId childId = !isParent ? thisPid : aOtherProcessId; + if (!parentId || !childId) { + return false; + } + + TransportDescriptor parentSide, childSide; + if (NS_FAILED(CreateTransport(parentId, &parentSide, &childSide))) { + return false; + } + + Message* parentMsg = new ChannelOpened(parentSide, childId, aProtocol); + Message* childMsg = new ChannelOpened(childSide, parentId, aChildProtocol); + nsAutoPtr<Message> messageForUs(isParent ? parentMsg : childMsg); + nsAutoPtr<Message> messageForOtherSide(!isParent ? parentMsg : childMsg); + if (!aOpenerChannel->Echo(messageForUs.forget()) || + !aOpenerChannel->Send(messageForOtherSide.forget())) { + CloseDescriptor(parentSide); + CloseDescriptor(childSide); + return false; + } + return true; +} + +bool +UnpackChannelOpened(const PrivateIPDLInterface&, + const Message& aMsg, + TransportDescriptor* aTransport, + ProcessId* aOtherProcess, + ProtocolId* aProtocol) +{ + return ChannelOpened::Read(aMsg, aTransport, aOtherProcess, aProtocol); +} + +#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) { +#ifdef MOZ_CRASHREPORTER + CrashReporter::AnnotateCrashReport( + NS_LITERAL_CSTRING("IPCTransportFailureReason"), + NS_LITERAL_CSTRING("Failed to open target process.")); +#endif + return false; + } + + return !!::DuplicateHandle(::GetCurrentProcess(), aSourceHandle, + targetProcess, aTargetHandle, + aDesiredAccess, FALSE, aOptions); +} +#endif + +#ifdef MOZ_CRASHREPORTER +void +AnnotateSystemError() +{ + int64_t error = 0; +#if defined(XP_WIN) + error = ::GetLastError(); +#elif defined(OS_POSIX) + error = errno; +#endif + if (error) { + CrashReporter::AnnotateCrashReport( + NS_LITERAL_CSTRING("IPCSystemError"), + nsPrintfCString("%lld", error)); + } +} +#endif + +#if defined(MOZ_CRASHREPORTER) && defined(XP_MACOSX) +void +AnnotateCrashReportWithErrno(const char* tag, int error) +{ + CrashReporter::AnnotateCrashReport( + nsCString(tag), + nsPrintfCString("%d", error)); +} +#endif + +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* aProtocolName, const char* aMsg, bool aIsParent) +{ + ProtocolErrorBreakpoint(aMsg); + + nsAutoCString formattedMessage("IPDL error ["); + formattedMessage.AppendASCII(aProtocolName); + formattedMessage.AppendLiteral("]: \""); + formattedMessage.AppendASCII(aMsg); + if (aIsParent) { +#ifdef MOZ_CRASHREPORTER + // 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(NS_LITERAL_CSTRING("IPCFatalErrorProtocol"), + nsDependentCString(aProtocolName)); + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCFatalErrorMsg"), + nsDependentCString(aMsg)); + AnnotateSystemError(); +#endif + MOZ_CRASH("IPC FatalError in the parent process!"); + } else { + formattedMessage.AppendLiteral("\". abort()ing as a result."); + NS_RUNTIMEABORT(formattedMessage.get()); + } +} + +void +LogicError(const char* aMsg) +{ + NS_RUNTIMEABORT(aMsg); +} + +void +ActorIdReadError(const char* aActorDescription) +{ + nsPrintfCString message("Error deserializing id for %s", aActorDescription); + NS_RUNTIMEABORT(message.get()); +} + +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) +{ + nsPrintfCString message("error deserializing type of union %s", aUnionName); + NS_RUNTIMEABORT(message.get()); +} + +void ArrayLengthReadError(const char* aElementName) +{ + nsPrintfCString message("error deserializing length of %s[]", aElementName); + NS_RUNTIMEABORT(message.get()); +} + +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; + } +} + +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->GetProtocolTypeId() != aProtocolTypeId) { + MismatchedActorTypeError(aActorDescription); + return Nothing(); + } + + return Some(listener); +} + +int32_t +IProtocol::Register(IProtocol* aRouted) +{ + return Manager()->Register(aRouted); +} + +int32_t +IProtocol::RegisterID(IProtocol* aRouted, int32_t aId) +{ + return Manager()->RegisterID(aRouted, aId); +} + +IProtocol* +IProtocol::Lookup(int32_t aId) +{ + return Manager()->Lookup(aId); +} + +void +IProtocol::Unregister(int32_t aId) +{ + Manager()->Unregister(aId); +} + +Shmem::SharedMemory* +IProtocol::CreateSharedMemory(size_t aSize, + SharedMemory::SharedMemoryType aType, + bool aUnsafe, + int32_t* aId) +{ + return Manager()->CreateSharedMemory(aSize, aType, aUnsafe, aId); +} + +Shmem::SharedMemory* +IProtocol::LookupSharedMemory(int32_t aId) +{ + return Manager()->LookupSharedMemory(aId); +} + +bool +IProtocol::IsTrackingSharedMemory(Shmem::SharedMemory* aSegment) +{ + return Manager()->IsTrackingSharedMemory(aSegment); +} + +bool +IProtocol::DestroySharedMemory(Shmem& aShmem) +{ + return Manager()->DestroySharedMemory(aShmem); +} + +ProcessId +IProtocol::OtherPid() const +{ + return Manager()->OtherPid(); +} + +void +IProtocol::FatalError(const char* const aErrorMsg) const +{ + HandleFatalError(ProtocolName(), aErrorMsg); +} + +void +IProtocol::HandleFatalError(const char* aProtocolName, const char* aErrorMsg) const +{ + if (IProtocol* manager = Manager()) { + manager->HandleFatalError(aProtocolName, aErrorMsg); + return; + } + + mozilla::ipc::FatalError(aProtocolName, aErrorMsg, mSide == ParentSide); +} + +bool +IProtocol::AllocShmem(size_t aSize, + Shmem::SharedMemory::SharedMemoryType aType, + Shmem* aOutMem) +{ + Shmem::id_t id; + Shmem::SharedMemory* rawmem(CreateSharedMemory(aSize, aType, false, &id)); + if (!rawmem) { + return false; + } + + *aOutMem = Shmem(Shmem::IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead(), rawmem, id); + return true; +} + +bool +IProtocol::AllocUnsafeShmem(size_t aSize, + Shmem::SharedMemory::SharedMemoryType aType, + Shmem* aOutMem) +{ + Shmem::id_t id; + Shmem::SharedMemory* rawmem(CreateSharedMemory(aSize, aType, true, &id)); + if (!rawmem) { + return false; + } + + *aOutMem = Shmem(Shmem::IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead(), 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::IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead()); + return ok; +} + +IToplevelProtocol::IToplevelProtocol(ProtocolId aProtoId, Side aSide) + : IProtocol(aSide), + mProtocolId(aProtoId), + mOtherPid(mozilla::ipc::kInvalidProcessId), + mLastRouteId(aSide == ParentSide ? 1 : 0), + mLastShmemId(aSide == ParentSide ? 1 : 0) +{ +} + +IToplevelProtocol::~IToplevelProtocol() +{ + if (mTrans) { + RefPtr<DeleteTask<Transport>> task = new DeleteTask<Transport>(mTrans.release()); + XRE_GetIOMessageLoop()->PostTask(task.forget()); + } +} + +base::ProcessId +IToplevelProtocol::OtherPid() const +{ + return mOtherPid; +} + +void +IToplevelProtocol::SetOtherProcessId(base::ProcessId aOtherPid) +{ + mOtherPid = aOtherPid; +} + +bool +IToplevelProtocol::TakeMinidump(nsIFile** aDump, uint32_t* aSequence) +{ + MOZ_RELEASE_ASSERT(GetSide() == ParentSide); +#ifdef MOZ_CRASHREPORTER + return XRE_TakeMinidumpForChild(OtherPid(), aDump, aSequence); +#else + return false; +#endif +} + +bool +IToplevelProtocol::Open(mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherPid, + MessageLoop* aThread, + mozilla::ipc::Side aSide) +{ + SetOtherProcessId(aOtherPid); + return GetIPCChannel()->Open(aTransport, aThread, aSide); +} + +bool +IToplevelProtocol::Open(MessageChannel* aChannel, + MessageLoop* aMessageLoop, + mozilla::ipc::Side aSide) +{ + SetOtherProcessId(base::GetCurrentProcId()); + return GetIPCChannel()->Open(aChannel, aMessageLoop, aSide); +} + +void +IToplevelProtocol::Close() +{ + GetIPCChannel()->Close(); +} + +void +IToplevelProtocol::SetReplyTimeoutMs(int32_t aTimeoutMs) +{ + GetIPCChannel()->SetReplyTimeoutMs(aTimeoutMs); +} + +bool +IToplevelProtocol::IsOnCxxStack() const +{ + return GetIPCChannel()->IsOnCxxStack(); +} + +int32_t +IToplevelProtocol::Register(IProtocol* aRouted) +{ + int32_t id = GetSide() == ParentSide ? ++mLastRouteId : --mLastRouteId; + mActorMap.AddWithID(aRouted, id); + return id; +} + +int32_t +IToplevelProtocol::RegisterID(IProtocol* aRouted, + int32_t aId) +{ + mActorMap.AddWithID(aRouted, aId); + return aId; +} + +IProtocol* +IToplevelProtocol::Lookup(int32_t aId) +{ + return mActorMap.Lookup(aId); +} + +void +IToplevelProtocol::Unregister(int32_t aId) +{ + return mActorMap.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::IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead(), aSize, aType, aUnsafe)); + if (!segment) { + return nullptr; + } + int32_t id = GetSide() == ParentSide ? ++mLastShmemId : --mLastShmemId; + Shmem shmem( + Shmem::IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead(), + segment.get(), + id); + Message* descriptor = shmem.ShareTo( + Shmem::IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead(), OtherPid(), MSG_ROUTING_CONTROL); + if (!descriptor) { + return nullptr; + } + Unused << GetIPCChannel()->Send(descriptor); + + *aId = shmem.Id(Shmem::IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead()); + Shmem::SharedMemory* rawSegment = segment.get(); + mShmemMap.AddWithID(segment.forget().take(), *aId); + return rawSegment; +} + +Shmem::SharedMemory* +IToplevelProtocol::LookupSharedMemory(Shmem::id_t aId) +{ + return mShmemMap.Lookup(aId); +} + +bool +IToplevelProtocol::IsTrackingSharedMemory(Shmem::SharedMemory* segment) +{ + return mShmemMap.HasData(segment); +} + +bool +IToplevelProtocol::DestroySharedMemory(Shmem& shmem) +{ + Shmem::id_t aId = shmem.Id(Shmem::IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead()); + Shmem::SharedMemory* segment = LookupSharedMemory(aId); + if (!segment) { + return false; + } + + Message* descriptor = shmem.UnshareFrom( + Shmem::IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead(), OtherPid(), MSG_ROUTING_CONTROL); + + mShmemMap.Remove(aId); + Shmem::Dealloc(Shmem::IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead(), segment); + + if (!GetIPCChannel()->CanSend()) { + delete descriptor; + return true; + } + + return descriptor && GetIPCChannel()->Send(descriptor); +} + +void +IToplevelProtocol::DeallocShmems() +{ + for (IDMap<SharedMemory>::const_iterator cit = mShmemMap.begin(); cit != mShmemMap.end(); ++cit) { + Shmem::Dealloc(Shmem::IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead(), cit->second); + } + mShmemMap.Clear(); +} + +bool +IToplevelProtocol::ShmemCreated(const Message& aMsg) +{ + Shmem::id_t id; + RefPtr<Shmem::SharedMemory> rawmem(Shmem::OpenExisting(Shmem::IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead(), aMsg, &id, true)); + if (!rawmem) { + return false; + } + mShmemMap.AddWithID(rawmem.forget().take(), id); + 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) { + mShmemMap.Remove(id); + Shmem::Dealloc(Shmem::IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead(), rawmem); + } + return true; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/ProtocolUtils.h b/ipc/glue/ProtocolUtils.h new file mode 100644 index 000000000..9184aae54 --- /dev/null +++ b/ipc/glue/ProtocolUtils.h @@ -0,0 +1,783 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 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 mozilla_ipc_ProtocolUtils_h +#define mozilla_ipc_ProtocolUtils_h 1 + +#include "base/id_map.h" +#include "base/process.h" +#include "base/process_util.h" +#include "chrome/common/ipc_message_utils.h" + +#include "prenv.h" + +#include "IPCMessageStart.h" +#include "mozilla/Attributes.h" +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/ipc/Shmem.h" +#include "mozilla/ipc/Transport.h" +#include "mozilla/ipc/MessageLink.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/UniquePtr.h" +#include "MainThreadUtils.h" + +#if defined(ANDROID) && defined(DEBUG) +#include <android/log.h> +#endif + +template<typename T> class nsTHashtable; +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 { + 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 + +namespace mozilla { +namespace dom { +class ContentParent; +} // namespace dom + +namespace net { +class NeckoParent; +} // namespace net + +namespace ipc { + +class MessageChannel; + +#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; +}; + +// Used internally to represent a "trigger" that might cause a state +// transition. Triggers are normalized across parent+child to Send +// and Recv (instead of child-in, child-out, parent-in, parent-out) so +// that they can share the same state machine implementation. To +// further normalize, |Send| is used for 'call', |Recv| for 'answer'. +struct Trigger +{ + enum Action { Send, Recv }; + + Trigger(Action action, int32_t msg) : + mAction(action), + mMessage(msg) + { + MOZ_ASSERT(0 <= msg && msg < INT32_MAX); + } + + uint32_t mAction : 1; + uint32_t mMessage : 31; +}; + +// What happens if Interrupt calls race? +enum RacyInterruptPolicy { + RIPError, + RIPChildWins, + RIPParentWins +}; + +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(Side aSide) : mId(0), mSide(aSide), mManager(nullptr), mChannel(nullptr) {} + + virtual int32_t Register(IProtocol*); + virtual int32_t RegisterID(IProtocol*, int32_t); + virtual IProtocol* Lookup(int32_t); + virtual void Unregister(int32_t); + virtual void RemoveManagee(int32_t, IProtocol*) = 0; + + virtual Shmem::SharedMemory* CreateSharedMemory( + size_t, SharedMemory::SharedMemoryType, bool, int32_t*); + virtual Shmem::SharedMemory* LookupSharedMemory(int32_t); + virtual bool IsTrackingSharedMemory(Shmem::SharedMemory*); + virtual bool DestroySharedMemory(Shmem&); + + // XXX odd ducks, acknowledged + virtual ProcessId OtherPid() const; + Side GetSide() const { return mSide; } + + virtual const char* ProtocolName() const = 0; + void FatalError(const char* const aErrorMsg) const; + virtual void HandleFatalError(const char* aProtocolName, const char* aErrorMsg) const; + + 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; + + virtual int32_t GetProtocolTypeId() = 0; + + int32_t Id() const { return mId; } + IProtocol* Manager() const { return mManager; } + virtual const MessageChannel* GetIPCChannel() const { return mChannel; } + virtual MessageChannel* GetIPCChannel() { return mChannel; } + + 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); + +protected: + void SetId(int32_t aId) { mId = aId; } + void SetManager(IProtocol* aManager) { mManager = aManager; } + void SetIPCChannel(MessageChannel* aChannel) { mChannel = aChannel; } + +private: + int32_t mId; + Side mSide; + IProtocol* mManager; + MessageChannel* mChannel; +}; + +typedef IPCMessageStart ProtocolId; + +template<class PFooSide> +class Endpoint; + +/** + * All top-level protocols should inherit this class. + * + * IToplevelProtocol tracks all top-level protocol actors created from + * this protocol actor. + */ +class IToplevelProtocol : public IProtocol +{ + template<class PFooSide> friend class Endpoint; + +protected: + explicit IToplevelProtocol(ProtocolId aProtoId, Side aSide); + ~IToplevelProtocol(); + +public: + void SetTransport(UniquePtr<Transport> aTrans) + { + mTrans = Move(aTrans); + } + + Transport* GetTransport() const { return mTrans.get(); } + + ProtocolId GetProtocolId() const { return mProtocolId; } + + base::ProcessId OtherPid() const; + void SetOtherProcessId(base::ProcessId aOtherPid); + + bool TakeMinidump(nsIFile** aDump, uint32_t* aSequence); + + 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(mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherPid, + MessageLoop* aThread = nullptr, + mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide); + + bool Open(MessageChannel* aChannel, + MessageLoop* aMessageLoop, + mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide); + + void Close(); + + void SetReplyTimeoutMs(int32_t aTimeoutMs); + + virtual int32_t Register(IProtocol*); + virtual int32_t RegisterID(IProtocol*, int32_t); + virtual IProtocol* Lookup(int32_t); + virtual void Unregister(int32_t); + + virtual Shmem::SharedMemory* CreateSharedMemory( + size_t, SharedMemory::SharedMemoryType, bool, int32_t*); + virtual Shmem::SharedMemory* LookupSharedMemory(int32_t); + virtual bool IsTrackingSharedMemory(Shmem::SharedMemory*); + virtual bool DestroySharedMemory(Shmem&); + + 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() { + } + +private: + ProtocolId mProtocolId; + UniquePtr<Transport> mTrans; + base::ProcessId mOtherPid; + IDMap<IProtocol> mActorMap; + int32_t mLastRouteId; + IDMap<Shmem::SharedMemory> mShmemMap; + Shmem::id_t mLastShmemId; +}; + +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) + return !!PR_GetEnv("MOZ_IPC_MESSAGE_LOG"); +#else + return false; +#endif +} + +inline bool +LoggingEnabledFor(const char *aTopLevelProtocol) +{ +#if defined(DEBUG) + const char *filter = PR_GetEnv("MOZ_IPC_MESSAGE_LOG"); + if (!filter) { + return false; + } + return strcmp(filter, "1") == 0 || strcmp(filter, aTopLevelProtocol) == 0; +#else + return false; +#endif +} + +enum class MessageDirection { + eSending, + eReceiving, +}; + +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* aProtocolName, 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); + +struct PrivateIPDLInterface {}; + +nsresult +Bridge(const PrivateIPDLInterface&, + MessageChannel*, base::ProcessId, MessageChannel*, base::ProcessId, + ProtocolId, ProtocolId); + +bool +Open(const PrivateIPDLInterface&, + MessageChannel*, base::ProcessId, Transport::Mode, + ProtocolId, ProtocolId); + +bool +UnpackChannelOpened(const PrivateIPDLInterface&, + const IPC::Message&, + TransportDescriptor*, base::ProcessId*, ProtocolId*); + +#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. + */ +#ifdef MOZ_CRASHREPORTER +void AnnotateSystemError(); +#else +#define AnnotateSystemError() do { } while (0) +#endif + +/** + * 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) + {} + + Endpoint(const PrivateIPDLInterface&, + mozilla::ipc::Transport::Mode aMode, + TransportDescriptor aTransport, + ProcessId aMyPid, + ProcessId aOtherPid, + ProtocolId aProtocolId) + : mValid(true) + , mMode(aMode) + , mTransport(aTransport) + , mMyPid(aMyPid) + , mOtherPid(aOtherPid) + , mProtocolId(aProtocolId) + {} + + Endpoint(Endpoint&& aOther) + : mValid(aOther.mValid) + , mMode(aOther.mMode) + , mTransport(aOther.mTransport) + , mMyPid(aOther.mMyPid) + , mOtherPid(aOther.mOtherPid) + , mProtocolId(aOther.mProtocolId) + { + aOther.mValid = false; + } + + Endpoint& operator=(Endpoint&& aOther) + { + mValid = aOther.mValid; + mMode = aOther.mMode; + mTransport = aOther.mTransport; + mMyPid = aOther.mMyPid; + mOtherPid = aOther.mOtherPid; + mProtocolId = aOther.mProtocolId; + + 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> t = mozilla::ipc::OpenDescriptor(mTransport, mMode); + if (!t) { + return false; + } + if (!aActor->Open(t.get(), mOtherPid, XRE_GetIOMessageLoop(), + mMode == Transport::MODE_SERVER ? ParentSide : ChildSide)) { + return false; + } + mValid = false; + aActor->SetTransport(Move(t)); + 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; + ProtocolId mProtocolId; +}; + +#if defined(MOZ_CRASHREPORTER) && defined(XP_MACOSX) +void AnnotateCrashReportWithErrno(const char* tag, int error); +#else +static inline void AnnotateCrashReportWithErrno(const char* 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, + ProtocolId aProtocol, + ProtocolId aChildProtocol, + 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("IpcCreateEndpointsNsresult", int(rv)); + return rv; + } + + *aParentEndpoint = Endpoint<PFooParent>(aPrivate, mozilla::ipc::Transport::MODE_SERVER, + parentTransport, aParentDestPid, aChildDestPid, aProtocol); + + *aChildEndpoint = Endpoint<PFooChild>(aPrivate, mozilla::ipc::Transport::MODE_CLIENT, + childTransport, aChildDestPid, aParentDestPid, aChildProtocol); + + return NS_OK; +} + +void +TableToArray(const nsTHashtable<nsPtrHashKey<void>>& aTable, + nsTArray<void*>& aArray); + +const char* StringFromIPCMessageType(uint32_t aMessageType); + +} // 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(); +} + +// appId's are for B2G only currently, where managees.Count() == 1. This is +// not guaranteed currently in Desktop, so for paths used for desktop, +// don't assert there's one managee. +template<typename Protocol> +Protocol* +SingleManagedOrNull(const ManagedContainer<Protocol>& aManagees) +{ + if (aManagees.Count() != 1) { + return nullptr; + } + return aManagees.ConstIter().Get()->GetKey(); +} + +} // namespace mozilla + + +namespace IPC { + +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); + IPC::WriteParam(aMsg, static_cast<uint32_t>(aParam.mProtocolId)); + } + + 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, protocolId; + if (!IPC::ReadParam(aMsg, aIter, &mode) || + !IPC::ReadParam(aMsg, aIter, &aResult->mTransport) || + !IPC::ReadParam(aMsg, aIter, &aResult->mMyPid) || + !IPC::ReadParam(aMsg, aIter, &aResult->mOtherPid) || + !IPC::ReadParam(aMsg, aIter, &protocolId)) { + return false; + } + aResult->mMode = Channel::Mode(mode); + aResult->mProtocolId = mozilla::ipc::ProtocolId(protocolId); + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) + { + aLog->append(StringPrintf(L"Endpoint")); + } +}; + +} // namespace IPC + + +#endif // mozilla_ipc_ProtocolUtils_h diff --git a/ipc/glue/ScopedXREEmbed.cpp b/ipc/glue/ScopedXREEmbed.cpp new file mode 100644 index 000000000..b419fdb42 --- /dev/null +++ b/ipc/glue/ScopedXREEmbed.cpp @@ -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/. */ + +#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() +{ + std::string path; +#if defined(OS_WIN) + path = WideToUTF8(CommandLine::ForCurrentProcess()->program()); +#elif defined(OS_POSIX) + path = CommandLine::ForCurrentProcess()->argv()[0]; +#else +# error Sorry +#endif + + nsCOMPtr<nsIFile> localFile; + nsresult rv = XRE_GetBinaryPath(path.c_str(), getter_AddRefs(localFile)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr<nsIFile> parent; + rv = localFile->GetParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) + return; + + localFile = do_QueryInterface(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 = do_QueryInterface(parent); + NS_ENSURE_TRUE_VOID(localFile); + + rv = localFile->GetParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) + return; + + localFile = do_QueryInterface(parent); + NS_ENSURE_TRUE_VOID(localFile); + + rv = localFile->GetParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) + return; + + localFile = do_QueryInterface(parent); + NS_ENSURE_TRUE_VOID(localFile); + + rv = localFile->SetNativeLeafName(NS_LITERAL_CSTRING("Resources")); + 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 000000000..60f8fb396 --- /dev/null +++ b/ipc/glue/ScopedXREEmbed.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __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/SendStream.h b/ipc/glue/SendStream.h new file mode 100644 index 000000000..b9bee5dec --- /dev/null +++ b/ipc/glue/SendStream.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ipc_SendStream_h +#define mozilla_ipc_SendStream_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/ipc/PSendStreamChild.h" +#include "mozilla/ipc/PSendStreamParent.h" + +class nsIInputStream; +class nsIAsyncInputStream; + +namespace mozilla { + +namespace dom { +class nsIContentChild; +} // dom namespace + +namespace ipc { + +class PBackgroundChild; + +// The SendStream IPC actor is designed to push an nsIInputStream from child to +// parent 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 SendStream actor only supports sending data from child to parent. +// +// The SendStream 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 SendStream 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 +// SendStreamChild::Start() after the actor has been sent to the parent. +// 2) If the actor is never sent to the parent, then the child code must +// call SendStreamChild::StartDestroy() to avoid memory leaks. +// 3) The SendStreamChild 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 SendStream to the main thread and Worker threads. +// +// In general you should probably use the AutoIPCStreamChild RAII class +// defined in InputStreamUtils.h instead of using SendStreamChild directly. +class SendStreamChild : public PSendStreamChild +{ +public: + // Create a SendStreamChild using a PContent IPC manager on the + // main thread. This can return nullptr if the provided stream is + // blocking. + static SendStreamChild* + Create(nsIAsyncInputStream* aInputStream, dom::nsIContentChild* aManager); + + // Create a SendStreamChild using a PBackground IPC manager on the + // main thread or a Worker thread. This can return nullptr if the provided + // stream is blocking or if the Worker thread is already shutting down. + static SendStreamChild* + Create(nsIAsyncInputStream* aInputStream, PBackgroundChild* aManager); + + // 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. + virtual void + Start() = 0; + + // Start cleaning up the actor. This must be called if the actor is never + // sent to the parent. If you use AutoIPCStream this is handled + // automatically. + virtual void + StartDestroy() = 0; + +protected: + virtual + ~SendStreamChild() = 0; +}; + +// On the parent side, you must simply call TakeReader() upon receiving a +// reference to the SendStreamParent actor. You do not need to maintain a +// reference to the actor itself. +class SendStreamParent : public PSendStreamParent +{ +public: + virtual already_AddRefed<nsIInputStream> + TakeReader() = 0; + +protected: + virtual + ~SendStreamParent() = 0; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_SendStream_h diff --git a/ipc/glue/SendStreamAlloc.h b/ipc/glue/SendStreamAlloc.h new file mode 100644 index 000000000..e33639ced --- /dev/null +++ b/ipc/glue/SendStreamAlloc.h @@ -0,0 +1,21 @@ +/* -*- 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_SendStreamAlloc_h +#define mozilla_ipc_SendStreamAlloc_h + +namespace mozilla { +namespace ipc { + +class PSendStreamParent; + +PSendStreamParent* +AllocPSendStreamParent(); + +} // ipc namespace +} // mozilla namespace + +#endif // mozilla_ipc_SendStreamAlloc_h diff --git a/ipc/glue/SendStreamChild.cpp b/ipc/glue/SendStreamChild.cpp new file mode 100644 index 000000000..02e8726e8 --- /dev/null +++ b/ipc/glue/SendStreamChild.cpp @@ -0,0 +1,429 @@ +/* -*- 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/SendStream.h" + +#include "mozilla/Unused.h" +#include "mozilla/dom/nsIContentChild.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/workers/bindings/WorkerHolder.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "nsIAsyncInputStream.h" +#include "nsICancelableRunnable.h" +#include "nsIRunnable.h" +#include "nsIThread.h" +#include "nsStreamUtils.h" + +namespace mozilla { +namespace ipc { + +using mozilla::dom::nsIContentChild; +using mozilla::dom::workers::Canceling; +using mozilla::dom::workers::GetCurrentThreadWorkerPrivate; +using mozilla::dom::workers::Status; +using mozilla::dom::workers::WorkerHolder; +using mozilla::dom::workers::WorkerPrivate; + +namespace { + +class SendStreamChildImpl final : public SendStreamChild + , public WorkerHolder +{ +public: + explicit SendStreamChildImpl(nsIAsyncInputStream* aStream); + ~SendStreamChildImpl(); + + void Start() override; + void StartDestroy() override; + + bool + AddAsWorkerHolder(dom::workers::WorkerPrivate* aWorkerPrivate); + +private: + class Callback; + + // PSendStreamChild methods + virtual void + ActorDestroy(ActorDestroyReason aReason) override; + + virtual bool + RecvRequestClose(const nsresult& aRv) override; + + // WorkerHolder methods + virtual bool + Notify(Status aStatus) override; + + void DoRead(); + + void Wait(); + + void OnStreamReady(Callback* aCallback); + + void OnEnd(nsresult aRv); + + nsCOMPtr<nsIAsyncInputStream> mStream; + RefPtr<Callback> mCallback; + WorkerPrivate* mWorkerPrivate; + bool mClosed; + + NS_DECL_OWNINGTHREAD +}; + +class SendStreamChildImpl::Callback final : public nsIInputStreamCallback + , public nsIRunnable + , public nsICancelableRunnable +{ +public: + explicit Callback(SendStreamChildImpl* aActor) + : mActor(aActor) + , mOwningThread(NS_GetCurrentThread()) + { + MOZ_ASSERT(mActor); + } + + NS_IMETHOD + OnInputStreamReady(nsIAsyncInputStream* aStream) override + { + // any thread + if (mOwningThread == NS_GetCurrentThread()) { + 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 + // SendStreamChild listens for this event through the WorkerHolder. + nsresult rv = mOwningThread->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(mOwningThread == NS_GetCurrentThread()); + if (mActor) { + mActor->OnStreamReady(this); + } + return NS_OK; + } + + nsresult + Cancel() override + { + // Cancel() gets called when the Worker thread is being shutdown. We have + // nothing to do here because SendStreamChild handles this case via + // the WorkerHolder. + return NS_OK; + } + + void + ClearActor() + { + MOZ_ASSERT(mOwningThread == NS_GetCurrentThread()); + MOZ_ASSERT(mActor); + mActor = nullptr; + } + +private: + ~Callback() + { + // called on any thread + + // ClearActor() should be called before the Callback is destroyed + MOZ_ASSERT(!mActor); + } + + SendStreamChildImpl* mActor; + nsCOMPtr<nsIThread> mOwningThread; + + NS_DECL_THREADSAFE_ISUPPORTS +}; + +NS_IMPL_ISUPPORTS(SendStreamChildImpl::Callback, nsIInputStreamCallback, + nsIRunnable, + nsICancelableRunnable); + +SendStreamChildImpl::SendStreamChildImpl(nsIAsyncInputStream* aStream) + : mStream(aStream) + , mWorkerPrivate(nullptr) + , mClosed(false) +{ + MOZ_ASSERT(mStream); +} + +SendStreamChildImpl::~SendStreamChildImpl() +{ + NS_ASSERT_OWNINGTHREAD(SendStreamChild); + MOZ_ASSERT(mClosed); + MOZ_ASSERT(!mCallback); + MOZ_ASSERT(!mWorkerPrivate); +} + +void +SendStreamChildImpl::Start() +{ + NS_ASSERT_OWNINGTHREAD(SendStreamChild); + MOZ_ASSERT_IF(!NS_IsMainThread(), mWorkerPrivate); + DoRead(); +} + +void +SendStreamChildImpl::StartDestroy() +{ + NS_ASSERT_OWNINGTHREAD(SendStreamChild); + OnEnd(NS_ERROR_ABORT); +} + +bool +SendStreamChildImpl::AddAsWorkerHolder(WorkerPrivate* aWorkerPrivate) +{ + NS_ASSERT_OWNINGTHREAD(SendStreamChild); + MOZ_ASSERT(aWorkerPrivate); + bool result = HoldWorker(aWorkerPrivate, Canceling); + if (result) { + mWorkerPrivate = aWorkerPrivate; + } + return result; +} + +void +SendStreamChildImpl::ActorDestroy(ActorDestroyReason aReason) +{ + NS_ASSERT_OWNINGTHREAD(SendStreamChild); + + // If the parent side runs into a problem it will ask the child to + // close the connection via RequestClose(). Therefore OnEnd() should + // always run before the actor is destroyed. + MOZ_ASSERT(mClosed); + + if (mCallback) { + mCallback->ClearActor(); + mCallback = nullptr; + } + + if (mWorkerPrivate) { + ReleaseWorker(); + mWorkerPrivate = nullptr; + } +} + +bool +SendStreamChildImpl::RecvRequestClose(const nsresult& aRv) +{ + NS_ASSERT_OWNINGTHREAD(SendStreamChild); + OnEnd(aRv); + return true; +} + +bool +SendStreamChildImpl::Notify(Status aStatus) +{ + NS_ASSERT_OWNINGTHREAD(SendStreamChild); + + // Keep the worker thread alive until the stream is finished. + return true; +} + +void +SendStreamChildImpl::DoRead() +{ + NS_ASSERT_OWNINGTHREAD(SendStreamChild); + MOZ_ASSERT(!mClosed); + 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"); + + while (true) { + // It should not be possible to transition to closed state without + // this loop terminating via a return. + MOZ_ASSERT(!mClosed); + + // Use non-auto here as we're unlikely to hit stack storage with the + // sizes we are sending. Also, it would be nice to avoid another copy + // to the IPC layer which we avoid if we use COW strings. Unfortunately + // IPC does not seem to support passing dependent storage types. + nsCString buffer; + + uint64_t available = 0; + nsresult rv = mStream->Available(&available); + if (NS_FAILED(rv)) { + OnEnd(rv); + return; + } + + if (available == 0) { + Wait(); + return; + } + + uint32_t expectedBytes = + static_cast<uint32_t>(std::min(available, kMaxBytesPerMessage)); + + buffer.SetLength(expectedBytes); + + uint32_t bytesRead = 0; + rv = mStream->Read(buffer.BeginWriting(), buffer.Length(), &bytesRead); + MOZ_ASSERT_IF(NS_FAILED(rv), bytesRead == 0); + buffer.SetLength(bytesRead); + + // If we read any data from the stream, send it across. + if (!buffer.IsEmpty()) { + Unused << SendBuffer(buffer); + } + + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + Wait(); + return; + } + + // Any other error or zero-byte read indicates end-of-stream + if (NS_FAILED(rv) || buffer.IsEmpty()) { + OnEnd(rv); + return; + } + } +} + +void +SendStreamChildImpl::Wait() +{ + NS_ASSERT_OWNINGTHREAD(SendStreamChild); + MOZ_ASSERT(!mClosed); + 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 +SendStreamChildImpl::OnStreamReady(Callback* aCallback) +{ + NS_ASSERT_OWNINGTHREAD(SendStreamChild); + MOZ_ASSERT(mCallback); + MOZ_ASSERT(aCallback == mCallback); + mCallback->ClearActor(); + mCallback = nullptr; + DoRead(); +} + +void +SendStreamChildImpl::OnEnd(nsresult aRv) +{ + NS_ASSERT_OWNINGTHREAD(SendStreamChild); + MOZ_ASSERT(aRv != NS_BASE_STREAM_WOULD_BLOCK); + + if (mClosed) { + return; + } + + mClosed = true; + + mStream->CloseWithStatus(aRv); + + if (aRv == NS_BASE_STREAM_CLOSED) { + aRv = NS_OK; + } + + // This will trigger an ActorDestroy() from the parent side + Unused << SendClose(aRv); +} + +bool +IsBlocking(nsIAsyncInputStream* aInputStream) +{ + bool nonBlocking = false; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aInputStream->IsNonBlocking(&nonBlocking))); + return !nonBlocking; +} + +} // anonymous namespace + +// static +SendStreamChild* +SendStreamChild::Create(nsIAsyncInputStream* aInputStream, + nsIContentChild* aManager) +{ + MOZ_ASSERT(aInputStream); + MOZ_ASSERT(aManager); + + // PContent can only be used on the main thread + MOZ_ASSERT(NS_IsMainThread()); + + // SendStreamChild reads in the current thread, so it is only supported + // on non-blocking, async channels + if (NS_WARN_IF(IsBlocking(aInputStream))) { + return nullptr; + } + + SendStreamChild* actor = new SendStreamChildImpl(aInputStream); + aManager->SendPSendStreamConstructor(actor); + + return actor; +} + +// static +SendStreamChild* +SendStreamChild::Create(nsIAsyncInputStream* aInputStream, + PBackgroundChild* aManager) +{ + MOZ_ASSERT(aInputStream); + MOZ_ASSERT(aManager); + + // PBackground can be used on any thread, but we only support SendStream on + // main thread and Worker threads right now. This is due to the requirement + // that the thread be guaranteed to live long enough to receive messages + // sent from parent to child. We can enforce this guarantee with a feature + // on worker threads, but not other threads. + WorkerPrivate* workerPrivate = nullptr; + if (!NS_IsMainThread()) { + workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + } + + // SendStreamChild reads in the current thread, so it is only supported + // on non-blocking, async channels + if (NS_WARN_IF(IsBlocking(aInputStream))) { + return nullptr; + } + + SendStreamChildImpl* actor = new SendStreamChildImpl(aInputStream); + + if (workerPrivate && !actor->AddAsWorkerHolder(workerPrivate)) { + delete actor; + return nullptr; + } + + aManager->SendPSendStreamConstructor(actor); + return actor; +} + +SendStreamChild::~SendStreamChild() +{ +} + +void +DeallocPSendStreamChild(PSendStreamChild* aActor) +{ + delete aActor; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/SendStreamParent.cpp b/ipc/glue/SendStreamParent.cpp new file mode 100644 index 000000000..3ed2d1b2b --- /dev/null +++ b/ipc/glue/SendStreamParent.cpp @@ -0,0 +1,136 @@ +/* -*- 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/SendStream.h" + +#include "mozilla/Unused.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIPipe.h" + +namespace mozilla { +namespace ipc { + +namespace { + +class SendStreamParentImpl final : public SendStreamParent +{ +public: + SendStreamParentImpl(nsIAsyncInputStream* aReader, + nsIAsyncOutputStream* aWriter); + ~SendStreamParentImpl(); + +private: + // PSendStreamParentImpl methods + virtual void + ActorDestroy(ActorDestroyReason aReason) override; + + // SendStreamparent methods + already_AddRefed<nsIInputStream> + TakeReader() override; + + virtual bool + RecvBuffer(const nsCString& aBuffer) override; + + virtual bool + RecvClose(const nsresult& aRv) override; + + nsCOMPtr<nsIAsyncInputStream> mReader; + nsCOMPtr<nsIAsyncOutputStream> mWriter; + + NS_DECL_OWNINGTHREAD +}; + +SendStreamParentImpl::~SendStreamParentImpl() +{ +} + +already_AddRefed<nsIInputStream> +SendStreamParentImpl::TakeReader() +{ + MOZ_ASSERT(mReader); + return mReader.forget(); +} + +void +SendStreamParentImpl::ActorDestroy(ActorDestroyReason aReason) +{ + // 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); +} + +bool +SendStreamParentImpl::RecvBuffer(const nsCString& aBuffer) +{ + uint32_t numWritten = 0; + + // This should only fail if we hit an OOM condition. + nsresult rv = mWriter->Write(aBuffer.get(), aBuffer.Length(), &numWritten); + if (NS_WARN_IF(NS_FAILED(rv))) { + Unused << SendRequestClose(rv); + } + + return true; +} + +bool +SendStreamParentImpl::RecvClose(const nsresult& aRv) +{ + mWriter->CloseWithStatus(aRv); + Unused << Send__delete__(this); + return true; +} + +SendStreamParentImpl::SendStreamParentImpl(nsIAsyncInputStream* aReader, + nsIAsyncOutputStream* aWriter) + : mReader(aReader) + , mWriter(aWriter) +{ + MOZ_ASSERT(mReader); + MOZ_ASSERT(mWriter); +} + +} // anonymous namespace + +SendStreamParent::~SendStreamParent() +{ +} + +PSendStreamParent* +AllocPSendStreamParent() +{ + // 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. + nsCOMPtr<nsIAsyncInputStream> reader; + nsCOMPtr<nsIAsyncOutputStream> writer; + + // 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(reader), + getter_AddRefs(writer), + true, true, // non-blocking + 0, // segment size + UINT32_MAX); // "infinite" pipe + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + return new SendStreamParentImpl(reader, writer); +} + +void +DeallocPSendStreamParent(PSendStreamParent* aActor) +{ + delete aActor; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/SharedMemory.cpp b/ipc/glue/SharedMemory.cpp new file mode 100644 index 000000000..afc8894d0 --- /dev/null +++ b/ipc/glue/SharedMemory.cpp @@ -0,0 +1,98 @@ +/* -*- 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() {} + +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 000000000..82f89ae4b --- /dev/null +++ b/ipc/glue/SharedMemory.h @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 + }; + + size_t Size() const { return mMappedSize; } + + virtual void* memory() const = 0; + + virtual bool Create(size_t size) = 0; + virtual bool Map(size_t nBytes) = 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) + NS_RUNTIMEABORT("SharedMemory region points at NULL!"); + char* memEnd = memStart + Size(); + + char* protStart = aAddr; + if (!protStart) + NS_RUNTIMEABORT("trying to Protect() a NULL region!"); + char* protEnd = protStart + aSize; + + if (!(memStart <= protStart + && protEnd <= memEnd)) + NS_RUNTIMEABORT("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); + 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) = 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); + } +}; + +} // 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 000000000..d8720271f --- /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 000000000..05c9ca9ad --- /dev/null +++ b/ipc/glue/SharedMemoryBasic_android.cpp @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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" + +// +// Temporarily go directly to the kernel interface until we can +// interact better with libcutils. +// +#include <linux/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) +{ } + +SharedMemoryBasic::~SharedMemoryBasic() +{ + Unmap(); + CloseHandle(); +} + +bool +SharedMemoryBasic::SetHandle(const Handle& aHandle) +{ + MOZ_ASSERT(-1 == mShmFd, "Already Create()d"); + mShmFd = aHandle.fd; + 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 = open("/" ASHMEM_NAME_DEF, O_RDWR, 0600); + if (-1 == shmfd) { + LogError("ShmemAndroid::Create():open"); + return false; + } + + if (ioctl(shmfd, ASHMEM_SET_SIZE, aNbytes)) { + LogError("ShmemAndroid::Unmap():ioctl(SET_SIZE)"); + close(shmfd); + return false; + } + + mShmFd = shmfd; + Created(aNbytes); + return true; +} + +bool +SharedMemoryBasic::Map(size_t nBytes) +{ + MOZ_ASSERT(nullptr == mMemory, "Already Map()d"); + + mMemory = mmap(nullptr, nBytes, + PROT_READ | PROT_WRITE, + MAP_SHARED, + mShmFd, + 0); + if (MAP_FAILED == mMemory) { + LogError("ShmemAndroid::Map()"); + mMemory = nullptr; + return false; + } + + Mapped(nBytes); + return true; +} + +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; + } +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/SharedMemoryBasic_android.h b/ipc/glue/SharedMemoryBasic_android.h new file mode 100644 index 000000000..488390d9f --- /dev/null +++ b/ipc/glue/SharedMemoryBasic_android.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ipc_SharedMemoryBasic_android_h +#define mozilla_ipc_SharedMemoryBasic_android_h + +#include "base/file_descriptor_posix.h" + +#include "SharedMemory.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::FileDescriptor> +{ +public: + SharedMemoryBasic(); + + virtual bool SetHandle(const Handle& aHandle) override; + + virtual bool Create(size_t aNbytes) override; + + virtual bool Map(size_t nBytes) override; + + virtual void CloseHandle() override; + + virtual void* memory() const override + { + return mMemory; + } + + virtual SharedMemoryType Type() const override + { + return TYPE_BASIC; + } + + static Handle NULLHandle() + { + return Handle(); + } + + 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; +}; + +} // 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 000000000..b930a6e66 --- /dev/null +++ b/ipc/glue/SharedMemoryBasic_chromium.h @@ -0,0 +1,99 @@ +/* -*- 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" + +#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() + { + } + + virtual bool SetHandle(const Handle& aHandle) override { + return mSharedMemory.SetHandle(aHandle, false); + } + + virtual bool Create(size_t aNbytes) override + { + bool ok = mSharedMemory.Create("", false, false, aNbytes); + if (ok) { + Created(aNbytes); + } + return ok; + } + + virtual bool Map(size_t nBytes) override + { + bool ok = mSharedMemory.Map(nBytes); + if (ok) { + Mapped(nBytes); + } + return ok; + } + + virtual void CloseHandle() override + { + mSharedMemory.Close(false); + } + + virtual void* memory() const override + { + return mSharedMemory.memory(); + } + + 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; + } + +private: + ~SharedMemoryBasic() + { + } + + 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 000000000..0b03683ef --- /dev/null +++ b/ipc/glue/SharedMemoryBasic_mach.h @@ -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/. */ + +#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> + +// +// This is a low-level wrapper around platform shared memory. Don't +// use it directly; use Shmem allocated through IPDL interfaces. +// + +class MachPortSender; +class ReceivePort; + +namespace mozilla { +namespace ipc { + +class SharedMemoryBasic final : public SharedMemoryCommon<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 Shutdown(); + + SharedMemoryBasic(); + + virtual bool SetHandle(const Handle& aHandle) override; + + virtual bool Create(size_t aNbytes) override; + + virtual bool Map(size_t nBytes) override; + + virtual void CloseHandle() override; + + virtual void* memory() const override + { + return mMemory; + } + + virtual SharedMemoryType Type() const override + { + return TYPE_BASIC; + } + + static Handle NULLHandle() + { + return Handle(); + } + + + 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; +}; + +} // 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 000000000..88753ee2e --- /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_allocate vm_allocate +#define mach_vm_deallocate vm_deallocate +#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 "chrome/common/mach_ipc_mac.h" + +#include "mozilla/StaticMutex.h" + +#ifdef DEBUG +#define LOG_ERROR(str, args...) \ + PR_BEGIN_MACRO \ + char *msg = PR_smprintf(str, ## args); \ + NS_WARNING(msg); \ + PR_smprintf_free(msg); \ + 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 { + +struct MemoryPorts { + MachPortSender* mSender; + ReceivePort* mReceiver; + + MemoryPorts() {} + MemoryPorts(MachPortSender* sender, ReceivePort* receiver) + : mSender(sender), mReceiver(receiver) {} +}; + +// Protects gMemoryCommPorts and gThreads. +static StaticMutex gMutex; + +static std::map<pid_t, MemoryPorts> gMemoryCommPorts; + +enum { + kGetPortsMsg = 1, + kSharePortsMsg, + kReturnIdMsg, + kReturnPortsMsg, + kShutdownMsg, + kCleanupMsg, +}; + +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() {} + 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; + } + MemoryPorts* 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. + ReceivePort* ports_in_receiver = new ReceivePort(); + ReceivePort* 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. + MachPortSender* ports_in_sender = new MachPortSender(raw_ports_in_sender); + MachPortSender* 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 +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; + } + + MachPortSender* ports_in_sender = new MachPortSender(rmsg->GetTranslatedPort(0)); + MachPortSender* ports_out_sender = new MachPortSender(rmsg->GetTranslatedPort(1)); + + ReceivePort* ports_in_receiver = new ReceivePort(); + ReceivePort* 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) +{ + 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; + } + StaticMutexAutoLock smal(gMutex); + switch (rmsg.GetMessageID()) { + case kSharePortsMsg: + HandleSharePortsMessage(&rmsg, ports); + break; + case kGetPortsMsg: + HandleGetPortsMessage(&rmsg, ports); + break; + case kCleanupMsg: + 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: + 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); + + for (auto it = gThreads.begin(); it != gThreads.end(); ++it) { + MachSendMessage shutdownMsg(kShutdownMsg); + it->second.mPorts->mReceiver->SendMessageToSelf(shutdownMsg, kTimeout); + } + gThreads.clear(); + + for (auto it = gMemoryCommPorts.begin(); it != gMemoryCommPorts.end(); ++it) { + delete it->second.mSender; + delete it->second.mReceiver; + } + gMemoryCommPorts.clear(); +} + +void +SharedMemoryBasic::CleanupForPid(pid_t pid) +{ + if (gThreads.find(pid) == gThreads.end()) { + return; + } + 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 it = gMemoryCommPorts.begin(); it != gMemoryCommPorts.end(); ++it) { + 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 + it->second.mSender->SendMessage(msg, kTimeout); + } + } + + MemoryPorts& ports = gMemoryCommPorts[pid]; + delete ports.mSender; + delete ports.mReceiver; + gMemoryCommPorts.erase(pid); +} + +SharedMemoryBasic::SharedMemoryBasic() + : mPort(MACH_PORT_NULL) + , mMemory(nullptr) +{ +} + +SharedMemoryBasic::~SharedMemoryBasic() +{ + Unmap(); + CloseHandle(); +} + +bool +SharedMemoryBasic::SetHandle(const Handle& aHandle) +{ + MOZ_ASSERT(mPort == MACH_PORT_NULL, "already initialized"); + + mPort = aHandle; + 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) +{ + mach_vm_address_t address; + + kern_return_t kr = mach_vm_allocate(mach_task_self(), &address, round_page(size), VM_FLAGS_ANYWHERE); + if (kr != KERN_SUCCESS) { + LOG_ERROR("Failed to allocate mach_vm_allocate shared memory (%zu bytes). %s (%x)\n", + size, mach_error_string(kr), kr); + return false; + } + + memory_object_size_t memoryObjectSize = round_page(size); + + kr = mach_make_memory_entry_64(mach_task_self(), + &memoryObjectSize, + address, + VM_PROT_DEFAULT, + &mPort, + MACH_PORT_NULL); + if (kr != KERN_SUCCESS) { + LOG_ERROR("Failed to make memory entry (%zu bytes). %s (%x)\n", + size, mach_error_string(kr), kr); + return false; + } + + mMemory = toPointer(address); + Mapped(size); + return true; +} + +bool +SharedMemoryBasic::Map(size_t size) +{ + if (mMemory) { + return true; + } + + if (MACH_PORT_NULL == mPort) { + return false; + } + + kern_return_t kr; + mach_vm_address_t address = 0; + + vm_prot_t vmProtection = VM_PROT_READ | VM_PROT_WRITE; + + kr = mach_vm_map(mach_task_self(), &address, round_page(size), 0, VM_FLAGS_ANYWHERE, + mPort, 0, false, vmProtection, vmProtection, VM_INHERIT_NONE); + if (kr != KERN_SUCCESS) { + 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; + } + + mMemory = toPointer(address); + Mapped(size); + return true; +} + +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: %d vs %d", 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; + } +} + +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 000000000..ca7833ce0 --- /dev/null +++ b/ipc/glue/SharedMemory_posix.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <sys/mman.h> // mprotect +#include <unistd.h> // sysconf + +#include "mozilla/ipc/SharedMemory.h" + +namespace mozilla { +namespace ipc { + +void +SharedMemory::SystemProtect(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; + + if (0 < mprotect(aAddr, aSize, flags)) + NS_RUNTIMEABORT("can't mprotect()"); +} + +size_t +SharedMemory::SystemPageSize() +{ + return sysconf(_SC_PAGESIZE); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/SharedMemory_windows.cpp b/ipc/glue/SharedMemory_windows.cpp new file mode 100644 index 000000000..f38977497 --- /dev/null +++ b/ipc/glue/SharedMemory_windows.cpp @@ -0,0 +1,39 @@ +/* -*- 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) +{ + DWORD flags; + if ((aRights & RightsRead) && (aRights & RightsWrite)) + flags = PAGE_READWRITE; + else if (aRights & RightsRead) + flags = PAGE_READONLY; + else + flags = PAGE_NOACCESS; + + DWORD oldflags; + if (!VirtualProtect(aAddr, aSize, flags, &oldflags)) + NS_RUNTIMEABORT("can't VirtualProtect()"); +} + +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 000000000..f0cc3bf39 --- /dev/null +++ b/ipc/glue/Shmem.cpp @@ -0,0 +1,497 @@ +/* -*- 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 "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, NESTED_INSIDE_CPOW) + { + IPC::WriteParam(this, aIPDLId); + IPC::WriteParam(this, aSize); + IPC::WriteParam(this, int32_t(aType)); + } + + static bool + ReadInfo(const Message* msg, PickleIterator* iter, + id_t* aIPDLId, + size_t* aSize, + SharedMemory::SharedMemoryType* aType) + { + if (!IPC::ReadParam(msg, iter, aIPDLId) || + !IPC::ReadParam(msg, iter, aSize) || + !IPC::ReadParam(msg, iter, reinterpret_cast<int32_t*>(aType))) + return false; + 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(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + 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(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead) +{ + 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(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + 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(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + 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(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + 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(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + 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(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + 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(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + SharedMemory* aSegment) +{ + DestroySegment(aSegment); +} + +#endif // if defined(DEBUG) + +IPC::Message* +Shmem::ShareTo(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + base::ProcessId aTargetPid, + int32_t routingId) +{ + AssertInvariants(); + + IPC::Message *msg = new ShmemCreated(routingId, mId, mSize, mSegment->Type()); + if (!mSegment->ShareHandle(aTargetPid, msg)) { + return nullptr; + } + // close the handle to the segment after it is shared + mSegment->CloseHandle(); + return msg; +} + +IPC::Message* +Shmem::UnshareFrom(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + base::ProcessId aTargetPid, + int32_t routingId) +{ + AssertInvariants(); + return new ShmemDestroyed(routingId, mId); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/Shmem.h b/ipc/glue/Shmem.h new file mode 100644 index 000000000..2736c9ac1 --- /dev/null +++ b/ipc/glue/Shmem.h @@ -0,0 +1,304 @@ +/* -*- 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 "ipc/IPCMessageUtils.h" +#include "mozilla/ipc/SharedMemory.h" + +/** + * |Shmem| is one agent in the IPDL shared memory scheme. The way it + works is essentially + * + * (1) C++ code calls, say, |parentActor->AllocShmem(size)| + + * (2) IPDL-generated code creates a |mozilla::ipc::SharedMemory| + * wrapping the bare OS shmem primitives. The code then adds the new + * SharedMemory to the set of shmem segments being managed by IPDL. + * + * (3) IPDL-generated code "shares" the new SharedMemory to the child + * process, and then sends a special asynchronous IPC message to the + * child notifying it of the creation of the segment. (What this + * means is OS specific.) + * + * (4a) The child receives the special IPC message, and using the + * |SharedMemory{Basic}::Handle| it was passed, creates a + * |mozilla::ipc::SharedMemory| in the child + * process. + * + * (4b) After sending the "shmem-created" IPC message, IPDL-generated + * code in the parent returns a |mozilla::ipc::Shmem| back to the C++ + * caller of |parentActor->AllocShmem()|. The |Shmem| is a "weak + * reference" to the underlying |SharedMemory|, which is managed by + * IPDL-generated code. C++ consumers of |Shmem| can't get at the + * underlying |SharedMemory|. + * + * If parent code wants to give access rights to the Shmem to the + * child, it does so by sending its |Shmem| to the child, in an IPDL + * message. The parent's |Shmem| then "dies", i.e. becomes + * inaccessible. This process could be compared to passing a + * "shmem-access baton" between parent and child. + */ + +namespace mozilla { +namespace layers { +class ShadowLayerForwarder; +} // namespace layers + +namespace ipc { + +class Shmem final +{ + friend struct IPC::ParamTraits<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; + struct IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead {}; + + Shmem() : + mSegment(nullptr), + mData(nullptr), + mSize(0), + mId(0) + { + } + + Shmem(const Shmem& aOther) : + mSegment(aOther.mSegment), + mData(aOther.mData), + mSize(aOther.mSize), + mId(aOther.mId) + { + } + +#if !defined(DEBUG) + Shmem(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + SharedMemory* aSegment, id_t aId) : + mSegment(aSegment), + mData(aSegment->memory()), + mSize(0), + mId(aId) + { + mSize = static_cast<size_t>(*PtrToSize(mSegment)); + } +#else + Shmem(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + 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(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead()); + } + + Shmem& operator=(const Shmem& aRhs) + { + mSegment = aRhs.mSegment; + mData = aRhs.mData; + mSize = aRhs.mSize; + mId = aRhs.mId; + return *this; + } + + 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(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead) const { + return mId; + } + + SharedMemory* Segment(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead) const { + return mSegment; + } + +#ifndef DEBUG + void RevokeRights(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead) + { + } +#else + void RevokeRights(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead); +#endif + + void forget(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead) + { + mSegment = nullptr; + mData = nullptr; + mSize = 0; + mId = 0; + } + + static already_AddRefed<Shmem::SharedMemory> + Alloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + 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. + IPC::Message* + ShareTo(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + 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. + IPC::Message* + UnshareFrom(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + base::ProcessId aTargetPid, + 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(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + const IPC::Message& aDescriptor, + id_t* aId, + bool aProtect=false); + + static void + Dealloc(IHadBetterBeIPDLCodeCallingThis_OtherwiseIAmADoodyhead, + SharedMemory* aSegment); + +private: + template<typename T> + void AssertAligned() const + { + if (0 != (mSize % sizeof(T))) + NS_RUNTIMEABORT("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 + + +namespace IPC { + +template<> +struct ParamTraits<mozilla::ipc::Shmem> +{ + typedef mozilla::ipc::Shmem paramType; + + // NB: Read()/Write() look creepy in that Shmems have a pointer + // member, but IPDL internally uses mId to properly initialize a + // "real" Shmem + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, aParam.mId); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + paramType::id_t id; + if (!ReadParam(aMsg, aIter, &id)) + return false; + aResult->mId = id; + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) + { + aLog->append(L"(shmem segment)"); + } +}; + + +} // namespace IPC + + +#endif // ifndef mozilla_ipc_Shmem_h diff --git a/ipc/glue/StringUtil.cpp b/ipc/glue/StringUtil.cpp new file mode 100644 index 000000000..17e4edcd2 --- /dev/null +++ b/ipc/glue/StringUtil.cpp @@ -0,0 +1,94 @@ +/* -*- 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/TaskFactory.h b/ipc/glue/TaskFactory.h new file mode 100644 index 000000000..66d9d58cd --- /dev/null +++ b/ipc/glue/TaskFactory.h @@ -0,0 +1,110 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/Move.h" + +/* + * 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(mozilla::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, mozilla::Forward<Args>(args)...); + return task.forget(); + } + + template <class Method> + inline already_AddRefed<Runnable> NewRunnableMethod(Method method) { + typedef TaskWrapper<RunnableMethod<Method, Tuple0> > TaskWrapper; + + RefPtr<TaskWrapper> task = new TaskWrapper(this, object_, method, + base::MakeTuple()); + + return task.forget(); + } + + template <class Method, class A> + inline already_AddRefed<Runnable> NewRunnableMethod(Method method, const A& a) { + typedef TaskWrapper<RunnableMethod<Method, Tuple1<A> > > TaskWrapper; + + RefPtr<TaskWrapper> task = new TaskWrapper(this, object_, method, + base::MakeTuple(a)); + + return task.forget(); + } + +protected: + template <class Method, class Params> + class RunnableMethod : public Runnable { + public: + RunnableMethod(T* obj, Method meth, const Params& params) + : 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 000000000..699a22260 --- /dev/null +++ b/ipc/glue/Transport.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ipc_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/Transport_posix.cpp b/ipc/glue/Transport_posix.cpp new file mode 100644 index 000000000..a8ac08464 --- /dev/null +++ b/ipc/glue/Transport_posix.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 <unistd.h> + +#include <string> + +#include "base/eintr_wrapper.h" + +#include "mozilla/ipc/Transport.h" +#include "mozilla/ipc/FileDescriptor.h" +#include "ProtocolUtils.h" + +using namespace std; + +using base::ProcessHandle; + +namespace mozilla { +namespace ipc { + +nsresult +CreateTransport(base::ProcessId aProcIdOne, + TransportDescriptor* aOne, + TransportDescriptor* aTwo) +{ + wstring id = IPC::Channel::GenerateVerifiedChannelID(std::wstring()); + // 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("IpcCreateTransportDupErrno", errno); + } + fd2 = dup(fd2); + if (fd2 < 0) { + AnnotateCrashReportWithErrno("IpcCreateTransportDupErrno", errno); + } + + if (fd1 < 0 || fd2 < 0) { + HANDLE_EINTR(close(fd1)); + HANDLE_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 000000000..fc2c89aaa --- /dev/null +++ b/ipc/glue/Transport_posix.h @@ -0,0 +1,44 @@ +/* -*- 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 000000000..28e9026c3 --- /dev/null +++ b/ipc/glue/Transport_win.cpp @@ -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/. */ + +#include "base/message_loop.h" + +#include "mozilla/ipc/Transport.h" +#include "mozilla/ipc/ProtocolUtils.h" + +using namespace std; + +using base::ProcessHandle; + +namespace mozilla { +namespace ipc { + +nsresult +CreateTransport(base::ProcessId aProcIdOne, + TransportDescriptor* aOne, + TransportDescriptor* aTwo) +{ + wstring id = IPC::Channel::GenerateVerifiedChannelID(std::wstring()); + // 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) +{ + NS_NOTREACHED("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 000000000..26aad6b34 --- /dev/null +++ b/ipc/glue/Transport_win.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_Transport_win_h +#define mozilla_ipc_Transport_win_h 1 + +#include <string> + +#include "base/process.h" +#include "ipc/IPCMessageUtils.h" +#include "nsWindowsHelpers.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 000000000..8b8ba3559 --- /dev/null +++ b/ipc/glue/URIParams.ipdlh @@ -0,0 +1,112 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "ipc/IPCMessageUtils.h"; + +include PBackgroundSharedTypes; + +namespace mozilla { +namespace ipc { + +struct SimpleURIParams +{ + nsCString scheme; + nsCString path; + nsCString ref; + nsCString query; + bool isMutable; +}; + +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; + nsCString originCharset; + bool isMutable; + bool supportsFileURL; + uint32_t hostEncoding; +}; + +struct JARURIParams +{ + URIParams jarFile; + URIParams jarEntry; + nsCString charset; +}; + +struct IconURIParams +{ + OptionalURIParams 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; + OptionalPrincipalInfo principal; +}; + +union URIParams +{ + SimpleURIParams; + StandardURLParams; + JARURIParams; + IconURIParams; + NullPrincipalURIParams; + JSURIParams; + SimpleNestedURIParams; + HostObjectURIParams; +}; + +union OptionalURIParams +{ + void_t; + URIParams; +}; + +struct JSURIParams +{ + SimpleURIParams simpleParams; + OptionalURIParams baseURI; +}; + +struct SimpleNestedURIParams +{ + SimpleURIParams simpleParams; + URIParams innerURI; +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/URIUtils.cpp b/ipc/glue/URIUtils.cpp new file mode 100644 index 000000000..ec7656d74 --- /dev/null +++ b/ipc/glue/URIUtils.cpp @@ -0,0 +1,154 @@ +/* -*- 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 "nsIIPCSerializableURI.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "nsComponentManagerUtils.h" +#include "nsDebug.h" +#include "nsID.h" +#include "nsJARURI.h" +#include "nsIIconURI.h" +#include "nsHostObjectURI.h" +#include "nsNullPrincipalURI.h" +#include "nsJSProtocolHandler.h" +#include "nsNetCID.h" +#include "nsSimpleNestedURI.h" +#include "nsThreadUtils.h" + +using namespace mozilla::ipc; +using mozilla::ArrayLength; + +namespace { + +NS_DEFINE_CID(kSimpleURICID, NS_SIMPLEURI_CID); +NS_DEFINE_CID(kStandardURLCID, NS_STANDARDURL_CID); +NS_DEFINE_CID(kJARURICID, NS_JARURI_CID); +NS_DEFINE_CID(kIconURICID, NS_MOZICONURI_CID); + +} // namespace + +namespace mozilla { +namespace ipc { + +void +SerializeURI(nsIURI* aURI, + URIParams& aParams) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aURI); + + nsCOMPtr<nsIIPCSerializableURI> serializable = do_QueryInterface(aURI); + if (!serializable) { + MOZ_CRASH("All IPDL URIs must be serializable!"); + } + + serializable->Serialize(aParams); + if (aParams.type() == URIParams::T__None) { + MOZ_CRASH("Serialize failed!"); + } +} + +void +SerializeURI(nsIURI* aURI, + OptionalURIParams& aParams) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (aURI) { + URIParams params; + SerializeURI(aURI, params); + aParams = params; + } + else { + aParams = mozilla::void_t(); + } +} + +already_AddRefed<nsIURI> +DeserializeURI(const URIParams& aParams) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIIPCSerializableURI> serializable; + + switch (aParams.type()) { + case URIParams::TSimpleURIParams: + serializable = do_CreateInstance(kSimpleURICID); + break; + + case URIParams::TStandardURLParams: + serializable = do_CreateInstance(kStandardURLCID); + break; + + case URIParams::TJARURIParams: + serializable = do_CreateInstance(kJARURICID); + break; + + case URIParams::TJSURIParams: + serializable = new nsJSURI(); + break; + + case URIParams::TIconURIParams: + serializable = do_CreateInstance(kIconURICID); + break; + + case URIParams::TNullPrincipalURIParams: + serializable = new nsNullPrincipalURI(); + break; + + case URIParams::TSimpleNestedURIParams: + serializable = new nsSimpleNestedURI(); + break; + + case URIParams::THostObjectURIParams: + serializable = new nsHostObjectURI(); + break; + + default: + MOZ_CRASH("Unknown params!"); + } + + MOZ_ASSERT(serializable); + + if (!serializable->Deserialize(aParams)) { + MOZ_ASSERT(false, "Deserialize failed!"); + return nullptr; + } + + nsCOMPtr<nsIURI> uri = do_QueryInterface(serializable); + MOZ_ASSERT(uri); + + return uri.forget(); +} + +already_AddRefed<nsIURI> +DeserializeURI(const OptionalURIParams& aParams) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIURI> uri; + + switch (aParams.type()) { + case OptionalURIParams::Tvoid_t: + break; + + case OptionalURIParams::TURIParams: + uri = DeserializeURI(aParams.get_URIParams()); + break; + + default: + MOZ_CRASH("Unknown params!"); + } + + return uri.forget(); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/URIUtils.h b/ipc/glue/URIUtils.h new file mode 100644 index 000000000..74f82a13b --- /dev/null +++ b/ipc/glue/URIUtils.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 mozilla_ipc_URIUtils_h +#define mozilla_ipc_URIUtils_h + +#include "mozilla/ipc/URIParams.h" +#include "nsCOMPtr.h" +#include "nsIURI.h" + +namespace mozilla { +namespace ipc { + +void +SerializeURI(nsIURI* aURI, + URIParams& aParams); + +void +SerializeURI(nsIURI* aURI, + OptionalURIParams& aParams); + +already_AddRefed<nsIURI> +DeserializeURI(const URIParams& aParams); + +already_AddRefed<nsIURI> +DeserializeURI(const OptionalURIParams& aParams); + +} // 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 000000000..8057ee25d --- /dev/null +++ b/ipc/glue/WindowsMessageLoop.cpp @@ -0,0 +1,1553 @@ +/* -*- 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 "nsAutoPtr.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsIXULAppInfo.h" +#include "WinUtils.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/PaintTracker.h" +#include "mozilla/WindowsVersion.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 +namespace mozilla { +namespace widget { +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 }; + +char16_t gAppMessageWindowName[256] = { 0 }; +int32_t gAppMessageWindowNameLength = 0; + +nsTArray<HWND>* gNeuteredWindows = nullptr; + +typedef nsTArray<nsAutoPtr<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. + nsAutoPtr<DeferredMessageArray> messages(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 != (long)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) +{ + DeferredMessage* deferred = nullptr; + + // 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 = new 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 = new DeferredSendMessage(hwnd, uMsg, wParam, lParam); + break; + } + + case WM_MOUSEACTIVATE: { + res = MA_NOACTIVATE; + deferred = new 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 = new DeferredRedrawMessage(hwnd, flags); + break; + } + + // This message will generate a WM_PAINT message if there are invalid + // areas. + case WM_PAINT: { + deferred = new DeferredUpdateMessage(hwnd); + break; + } + + // This message holds a string in its lParam that we must copy. + case WM_SETTINGCHANGE: { + deferred = new DeferredSettingChangeMessage(hwnd, uMsg, wParam, lParam); + break; + } + + // These messages are faked via a call to SetWindowPos. + case WM_WINDOWPOSCHANGED: { + deferred = new DeferredWindowPosMessage(hwnd, lParam); + break; + } + case WM_NCCALCSIZE: { + deferred = new DeferredWindowPosMessage(hwnd, lParam, true, wParam); + break; + } + + case WM_COPYDATA: { + deferred = new DeferredCopyDataMessage(hwnd, uMsg, wParam, lParam); + res = TRUE; + break; + } + + case WM_STYLECHANGED: { + deferred = new DeferredStyleChangeMessage(hwnd, wParam, lParam); + break; + } + + case WM_SETICON: { + deferred = new 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 == mozilla::widget::sAppShellGeckoMsgId) { + // Widget's registered native event callback + deferred = new 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 nsTArray<nsAutoPtr<DeferredMessage> >(20); + NS_ASSERTION(gDeferredMessages, "Out of memory!"); + } + + // Save for later. The array takes ownership of |deferred|. + gDeferredMessages->AppendElement(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, NS_LITERAL_STRING("Mozilla")) || + StringBeginsWith(className, NS_LITERAL_STRING("Gecko")) || + className.EqualsLiteral("nsToolkitClass") || + className.EqualsLiteral("nsAppShell:EventWindowClass")) { + return true; + } + + // Plugin windows that can trigger ipc calls in child: + // 'ShockwaveFlashFullScreen' - flash fullscreen window + // 'QTNSHIDDEN' - QuickTime + // 'AGFullScreenWinClass' - silverlight fullscreen window + if (className.EqualsLiteral("ShockwaveFlashFullScreen") || + className.EqualsLiteral("QTNSHIDDEN") || + className.EqualsLiteral("AGFullScreenWinClass")) { + SetPropW(hWnd, k3rdPartyWindowProp, (HANDLE)1); + return true; + } + + // Google Earth bridging msg window between the plugin instance and a separate + // earth process. The earth process can trigger a plugin incall on the browser + // at any time, which is badness if the instance is already making an incall. + if (className.EqualsLiteral("__geplugin_bridge_window__")) { + SetPropW(hWnd, k3rdPartyWindowProp, (HANDLE)1); + return true; + } + + // nsNativeAppSupport makes a window like "FirefoxMessageWindow" based on the + // toolkit app's name. It's pretty expensive to calculate this so we only try + // once. + if (gAppMessageWindowNameLength == 0) { + nsCOMPtr<nsIXULAppInfo> appInfo = + do_GetService("@mozilla.org/xre/app-info;1"); + if (appInfo) { + nsAutoCString appName; + if (NS_SUCCEEDED(appInfo->GetName(appName))) { + appName.AppendLiteral("MessageWindow"); + nsDependentString windowName(gAppMessageWindowName); + CopyUTF8toUTF16(appName, windowName); + gAppMessageWindowNameLength = windowName.Length(); + } + } + + // Don't try again if that failed. + if (gAppMessageWindowNameLength == 0) { + gAppMessageWindowNameLength = -1; + } + } + + if (gAppMessageWindowNameLength != -1 && + className.Equals(nsDependentString(gAppMessageWindowName, + gAppMessageWindowNameLength))) { + 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)) { + if (!gNeuteredWindows->AppendElement(hWnd)) { + NS_ERROR("Out of memory!"); + RestoreWindowProcedure(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 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) { + gWinEventHook = SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_DESTROY, + NULL, &WinEventHook, GetCurrentProcessId(), + gUIThreadId, WINEVENT_OUTOFCONTEXT); + + // We need to execute this after setting the hook in case the OLE window + // already existed. + gCOMWindow = FindCOMWindow(); + } + MOZ_ASSERT(gWinEventHook); +} + +} // 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()) { + NS_RUNTIMEABORT("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 inline bool +IsTimeoutExpired(PRIntervalTime aStart, PRIntervalTime aTimeout) +{ + return (aTimeout != PR_INTERVAL_NO_TIMEOUT) && + (aTimeout <= (PR_IntervalNow() - aStart)); +} + +static HHOOK gWindowHook; + +static inline void +StartNeutering() +{ + 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 MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) + : mNeuteredByThis(!gWindowHook && aDoNeuter) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + 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(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) + : mReneuter(gWindowHook != NULL) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + if (mReneuter) { + StopNeutering(); + } +} + +DeneuteredWindowRegion::~DeneuteredWindowRegion() +{ + if (mReneuter) { + StartNeutering(); + } +} + +SuppressedNeuteringRegion::SuppressedNeuteringRegion(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) + : mReenable(::gUIThreadId == ::GetCurrentThreadId() && ::gWindowHook) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + 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(); + + MOZ_ASSERT(gUIThreadId, "InitUIThread was not called!"); + +#if defined(ACCESSIBILITY) + if (IsVistaOrLater() && (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) { + PRIntervalTime timeout = (kNoTimeout == mTimeoutMs) ? + PR_INTERVAL_NO_TIMEOUT : + PR_MillisecondsToInterval(mTimeoutMs); + PRIntervalTime waitStart = 0; + + if (timeout != PR_INTERVAL_NO_TIMEOUT) { + waitStart = PR_IntervalNow(); + } + + MOZ_ASSERT(!mIsSyncWaitingOnNonMainThread); + mIsSyncWaitingOnNonMainThread = true; + + 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(timeout == PR_INTERVAL_NO_TIMEOUT ? + false : IsTimeoutExpired(waitStart, 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(); + + MOZ_ASSERT(gUIThreadId, "InitUIThread was not called!"); + + // 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. + NS_RUNTIMEABORT("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>(©Data); +} + +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 000000000..80577a712 --- /dev/null +++ b/ipc/glue/WindowsMessageLoop.h @@ -0,0 +1,168 @@ +/* -*- 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: + DeferredMessage() + { + MOZ_COUNT_CTOR(DeferredMessage); + } + + virtual ~DeferredMessage() + { + MOZ_COUNT_DTOR(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: + 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/moz.build b/ipc/glue/moz.build new file mode 100644 index 000000000..dd3a2e1ba --- /dev/null +++ b/ipc/glue/moz.build @@ -0,0 +1,207 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'nsIIPCBackgroundChildCreateCallback.h', + 'nsIIPCSerializableInputStream.h', + 'nsIIPCSerializableURI.h', +] + +EXPORTS.mozilla.ipc += [ + 'BackgroundChild.h', + 'BackgroundParent.h', + 'BackgroundUtils.h', + 'BrowserProcessSubThread.h', + 'CrashReporterClient.h', + 'CrashReporterHost.h', + 'CrashReporterMetadataShmem.h', + 'CrossProcessMutex.h', + 'FileDescriptor.h', + 'FileDescriptorSetChild.h', + 'FileDescriptorSetParent.h', + 'FileDescriptorUtils.h', + 'GeckoChildProcessHost.h', + 'InputStreamUtils.h', + 'IOThreadChild.h', + 'IPCStreamUtils.h', + 'MessageChannel.h', + 'MessageLink.h', + 'Neutering.h', + 'ProcessChild.h', + 'ProtocolUtils.h', + 'ScopedXREEmbed.h', + 'SendStream.h', + 'SendStreamAlloc.h', + 'SharedMemory.h', + 'SharedMemoryBasic.h', + 'Shmem.h', + 'TaskFactory.h', + 'Transport.h', + 'URIUtils.h', + 'WindowsMessageLoop.h', +] + +if CONFIG['MOZ_FAULTY'] == '1': + EXPORTS.mozilla.ipc += ['Faulty.h'] + SOURCES += ['Faulty.cpp'] + +if CONFIG['OS_ARCH'] == 'WINNT': + DEFINES['WEBRTC_WIN'] = True + EXPORTS.mozilla.ipc += [ + 'Transport_win.h', + ] + SOURCES += [ + 'SharedMemory_windows.cpp', + 'Transport_win.cpp', + 'WindowsMessageLoop.cpp', + ] +else: + DEFINES['WEBRTC_POSIX'] = True + 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', + ] + +# 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', + ] + +EXPORTS.ipc += [ + 'IPCMessageUtils.h', +] + +UNIFIED_SOURCES += [ + 'BackgroundImpl.cpp', + 'BackgroundUtils.cpp', + 'BrowserProcessSubThread.cpp', + 'CrashReporterClient.cpp', + 'CrashReporterHost.cpp', + 'CrashReporterMetadataShmem.cpp', + 'FileDescriptor.cpp', + 'FileDescriptorUtils.cpp', + 'InputStreamUtils.cpp', + 'IPCMessageUtils.cpp', + 'IPCStreamUtils.cpp', + 'MessageChannel.cpp', + 'MessageLink.cpp', + 'MessagePump.cpp', + 'ProcessChild.cpp', + 'ProtocolUtils.cpp', + 'ScopedXREEmbed.cpp', + 'SendStreamChild.cpp', + 'SendStreamParent.cpp', + 'SharedMemory.cpp', + 'Shmem.cpp', + 'StringUtil.cpp', +] + +# GeckoChildProcessHost.cpp cannot be built in unified mode because it uses plarena.h. +# URIUtils.cpp cannot be built in unified mode because of name clashes on strdup. +SOURCES += [ + 'BackgroundChildImpl.cpp', + 'BackgroundParentImpl.cpp', + 'FileDescriptorSetChild.cpp', + 'FileDescriptorSetParent.cpp', + 'GeckoChildProcessHost.cpp', + 'URIUtils.cpp', +] + +if CONFIG['_MSC_VER']: + # This is intended as a temporary hack to support building with VS2015. + # 'reinterpret_cast': conversion from 'DWORD' to 'HANDLE' of greater size + SOURCES['BackgroundChildImpl.cpp'].flags += ['-wd4312'] + SOURCES['BackgroundParentImpl.cpp'].flags += ['-wd4312'] + +LOCAL_INCLUDES += [ + '/caps', + '/dom/broadcastchannel', + '/dom/indexedDB', + '/dom/workers', + '/media/webrtc/trunk', + '/xpcom/build', +] + +IPDL_SOURCES = [ + 'InputStreamParams.ipdlh', + 'IPCStream.ipdlh', + 'PBackground.ipdl', + 'PBackgroundSharedTypes.ipdlh', + 'PBackgroundTest.ipdl', + 'PFileDescriptorSet.ipdl', + 'ProtocolTypes.ipdlh', + 'PSendStream.ipdl', + 'URIParams.ipdlh', +] + +LOCAL_INCLUDES += [ + '/dom/ipc', + '/toolkit/crashreporter', + '/toolkit/xre', + '/xpcom/threads', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +for var in ('MOZ_CHILD_PROCESS_NAME', 'MOZ_CHILD_PROCESS_NAME_PIE', + 'MOZ_CHILD_PROCESS_BUNDLE', 'DLL_PREFIX', 'DLL_SUFFIX'): + 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['GNU_CXX']: + CXXFLAGS += ['-Wno-shadow'] diff --git a/ipc/glue/nsIIPCBackgroundChildCreateCallback.h b/ipc/glue/nsIIPCBackgroundChildCreateCallback.h new file mode 100644 index 000000000..5522fa75e --- /dev/null +++ b/ipc/glue/nsIIPCBackgroundChildCreateCallback.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ipc_nsiipcbackgroundchildcreatecallback_h +#define mozilla_ipc_nsiipcbackgroundchildcreatecallback_h + +#include "mozilla/Attributes.h" +#include "nsISupports.h" + +namespace mozilla { +namespace ipc { + +class PBackgroundChild; + +} // namespace ipc +} // namespace mozilla + +#define NS_IIPCBACKGROUNDCHILDCREATECALLBACK_IID \ + {0x4de01707, 0x70e3, 0x4181, {0xbc, 0x9f, 0xa3, 0xec, 0xfe, 0x74, 0x1a, 0xe3}} + +class NS_NO_VTABLE nsIIPCBackgroundChildCreateCallback : public nsISupports +{ +public: + typedef mozilla::ipc::PBackgroundChild PBackgroundChild; + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IIPCBACKGROUNDCHILDCREATECALLBACK_IID) + + // This will be called upon successful creation of a PBackgroundChild actor. + // The actor is unique per-thread and must not be shared across threads. It + // may be saved and reused on the same thread for as long as the thread lives. + // After this callback BackgroundChild::GetForCurrentThread() will return the + // same actor. + virtual void + ActorCreated(PBackgroundChild*) = 0; + + // This will be called if for some reason the PBackgroundChild actor cannot be + // created. This should never be called in child processes as the failure to + // create the actor should result in the termination of the child process + // first. This may be called for cross-thread actors in the main process. + virtual void + ActorFailed() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIIPCBackgroundChildCreateCallback, + NS_IIPCBACKGROUNDCHILDCREATECALLBACK_IID) + +#define NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK \ + virtual void \ + ActorCreated(mozilla::ipc::PBackgroundChild*) override; \ + virtual void \ + ActorFailed() override; + +#define NS_FORWARD_NSIIPCBACKGROUNDCHILDCREATECALLBACK(_to) \ + virtual void \ + ActorCreated(mozilla::ipc::PBackgroundChild* aActor) override \ + { _to ActorCreated(aActor); } \ + virtual void \ + ActorFailed() override \ + { _to ActorFailed(); } + +#define NS_FORWARD_SAFE_NSIIPCBACKGROUNDCHILDCREATECALLBACK(_to) \ + virtual void \ + ActorCreated(mozilla::ipc::PBackgroundChild* aActor) override \ + { if (_to) { _to->ActorCreated(aActor); } } \ + virtual void \ + ActorFailed() override \ + { if (_to) { _to->ActorFailed(); } } + +#endif // mozilla_ipc_nsiipcbackgroundchildcreatecallback_h diff --git a/ipc/glue/nsIIPCSerializableInputStream.h b/ipc/glue/nsIIPCSerializableInputStream.h new file mode 100644 index 000000000..365e490b1 --- /dev/null +++ b/ipc/glue/nsIIPCSerializableInputStream.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_nsIIPCSerializableInputStream_h +#define mozilla_ipc_nsIIPCSerializableInputStream_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "nsISupports.h" +#include "nsTArrayForwardDeclare.h" + +namespace mozilla { +namespace ipc { + +class FileDescriptor; +class InputStreamParams; + +} // namespace ipc +} // namespace mozilla + +#define NS_IIPCSERIALIZABLEINPUTSTREAM_IID \ + {0xb0211b14, 0xea6d, 0x40d4, {0x87, 0xb5, 0x7b, 0xe3, 0xdf, 0xac, 0x09, 0xd1}} + +class NS_NO_VTABLE nsIIPCSerializableInputStream : public nsISupports +{ +public: + typedef nsTArray<mozilla::ipc::FileDescriptor> + FileDescriptorArray; + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IIPCSERIALIZABLEINPUTSTREAM_IID) + + virtual void + Serialize(mozilla::ipc::InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors) = 0; + + virtual bool + Deserialize(const mozilla::ipc::InputStreamParams& aParams, + const FileDescriptorArray& aFileDescriptors) = 0; + + // The number of bytes that are expected to be written when this + // stream is serialized. A value of Some(N) indicates that N bytes + // will be written to the IPC buffer, and will be used to decide + // upon an optimal transmission mechanism. A value of Nothing + // indicates that either serializing this stream will not require + // serializing its contents (eg. a file-backed stream, or a stream + // backed by an IPC actor), or the length of the stream's contents + // cannot be determined. + virtual mozilla::Maybe<uint64_t> + ExpectedSerializedLength() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIIPCSerializableInputStream, + NS_IIPCSERIALIZABLEINPUTSTREAM_IID) + +#define NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM \ + virtual void \ + Serialize(mozilla::ipc::InputStreamParams&, \ + FileDescriptorArray&) override; \ + \ + virtual bool \ + Deserialize(const mozilla::ipc::InputStreamParams&, \ + const FileDescriptorArray&) override; \ + \ + virtual mozilla::Maybe<uint64_t> \ + ExpectedSerializedLength() override; + +#define NS_FORWARD_NSIIPCSERIALIZABLEINPUTSTREAM(_to) \ + virtual void \ + Serialize(mozilla::ipc::InputStreamParams& aParams, \ + FileDescriptorArray& aFileDescriptors) override \ + { \ + _to Serialize(aParams, aFileDescriptors); \ + } \ + \ + virtual bool \ + Deserialize(const mozilla::ipc::InputStreamParams& aParams, \ + const FileDescriptorArray& aFileDescriptors) override \ + { \ + return _to Deserialize(aParams, aFileDescriptors); \ + } \ + \ + virtual mozilla::Maybe<uint64_t> \ + ExpectedSerializedLength() override \ + { \ + return _to ExpectedSerializedLength(); \ + } + +#define NS_FORWARD_SAFE_NSIIPCSERIALIZABLEINPUTSTREAM(_to) \ + virtual void \ + Serialize(mozilla::ipc::InputStreamParams& aParams, \ + FileDescriptorArray& aFileDescriptors) override \ + { \ + if (_to) { \ + _to->Serialize(aParams, aFileDescriptors); \ + } \ + } \ + \ + virtual bool \ + Deserialize(const mozilla::ipc::InputStreamParams& aParams, \ + const FileDescriptorArray& aFileDescriptors) override \ + { \ + return _to ? _to->Deserialize(aParams, aFileDescriptors) : false; \ + } \ + \ + virtual mozilla::Maybe<uint64_t> \ + ExpectedSerializedLength() override \ + { \ + return _to ? _to->ExpectedSerializedLength() : Nothing(); \ + } + +#endif // mozilla_ipc_nsIIPCSerializableInputStream_h diff --git a/ipc/glue/nsIIPCSerializableURI.h b/ipc/glue/nsIIPCSerializableURI.h new file mode 100644 index 000000000..2008ce838 --- /dev/null +++ b/ipc/glue/nsIIPCSerializableURI.h @@ -0,0 +1,59 @@ +/* -*- 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_nsIIPCSerializableURI_h +#define mozilla_ipc_nsIIPCSerializableURI_h + +#include "nsISupports.h" +#include "mozilla/Attributes.h" + +namespace mozilla { +namespace ipc { +class URIParams; +} // namespace ipc +} // namespace mozilla + +#define NS_IIPCSERIALIZABLEURI_IID \ + {0xfee3437d, 0x3daf, 0x411f, {0xb0, 0x1d, 0xdc, 0xd4, 0x88, 0x55, 0xe3, 0xd}} + +class NS_NO_VTABLE nsIIPCSerializableURI : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IIPCSERIALIZABLEURI_IID) + + virtual void + Serialize(mozilla::ipc::URIParams& aParams) = 0; + + virtual bool + Deserialize(const mozilla::ipc::URIParams& aParams) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIIPCSerializableURI, + NS_IIPCSERIALIZABLEURI_IID) + +#define NS_DECL_NSIIPCSERIALIZABLEURI \ + virtual void \ + Serialize(mozilla::ipc::URIParams&) override; \ + virtual bool \ + Deserialize(const mozilla::ipc::URIParams&) override; + +#define NS_FORWARD_NSIIPCSERIALIZABLEURI(_to) \ + virtual void \ + Serialize(mozilla::ipc::URIParams& aParams) override \ + { _to Serialize(aParams); } \ + virtual bool \ + Deserialize(const mozilla::ipc::URIParams& aParams) override \ + { return _to Deserialize(aParams); } + +#define NS_FORWARD_SAFE_NSIIPCSERIALIZABLEURI(_to) \ + virtual void \ + Serialize(mozilla::ipc::URIParams& aParams) override \ + { if (_to) { _to->Serialize(aParams); } } \ + virtual bool \ + Deserialize(const mozilla::ipc::URIParams& aParams) override \ + { if (_to) { return _to->Deserialize(aParams); } return false; } + +#endif // mozilla_ipc_nsIIPCSerializableURI_h |