diff options
Diffstat (limited to 'dom/base/nsFrameMessageManager.cpp')
-rw-r--r-- | dom/base/nsFrameMessageManager.cpp | 2281 |
1 files changed, 2281 insertions, 0 deletions
diff --git a/dom/base/nsFrameMessageManager.cpp b/dom/base/nsFrameMessageManager.cpp new file mode 100644 index 0000000000..a4bba48560 --- /dev/null +++ b/dom/base/nsFrameMessageManager.cpp @@ -0,0 +1,2281 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/basictypes.h" + +#include "nsFrameMessageManager.h" + +#include "AppProcessChecker.h" +#include "ContentChild.h" +#include "nsContentUtils.h" +#include "nsDOMClassInfoID.h" +#include "nsError.h" +#include "nsIXPConnect.h" +#include "jsapi.h" +#include "jsfriendapi.h" +#include "nsJSUtils.h" +#include "nsJSPrincipals.h" +#include "nsNetUtil.h" +#include "nsScriptLoader.h" +#include "nsFrameLoader.h" +#include "nsIXULRuntime.h" +#include "nsIScriptError.h" +#include "nsIConsoleService.h" +#include "nsIMemoryReporter.h" +#include "nsIProtocolHandler.h" +#include "nsIScriptSecurityManager.h" +#include "nsIDOMClassInfo.h" +#include "xpcpublic.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/IntentionalCrash.h" +#include "mozilla/Preferences.h" +#include "mozilla/Telemetry.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/MessagePort.h" +#include "mozilla/dom/nsIContentParent.h" +#include "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/dom/ProcessGlobal.h" +#include "mozilla/dom/SameProcessMessageQueue.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/ipc/BlobChild.h" +#include "mozilla/dom/ipc/BlobParent.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "mozilla/dom/DOMStringList.h" +#include "mozilla/jsipc/CrossProcessObjectWrappers.h" +#include "nsPrintfCString.h" +#include "nsXULAppAPI.h" +#include "nsQueryObject.h" +#include <algorithm> +#include "chrome/common/ipc_channel.h" // for IPC::Channel::kMaximumMessageSize + +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif + +#ifdef ANDROID +#include <android/log.h> +#endif +#ifdef XP_WIN +#include <windows.h> +# if defined(SendMessage) +# undef SendMessage +# endif +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::dom::ipc; + +nsFrameMessageManager::nsFrameMessageManager(mozilla::dom::ipc::MessageManagerCallback* aCallback, + nsFrameMessageManager* aParentManager, + /* mozilla::dom::ipc::MessageManagerFlags */ uint32_t aFlags) + : mChrome(!!(aFlags & mozilla::dom::ipc::MM_CHROME)), + mGlobal(!!(aFlags & mozilla::dom::ipc::MM_GLOBAL)), + mIsProcessManager(!!(aFlags & mozilla::dom::ipc::MM_PROCESSMANAGER)), + mIsBroadcaster(!!(aFlags & mozilla::dom::ipc::MM_BROADCASTER)), + mOwnsCallback(!!(aFlags & mozilla::dom::ipc::MM_OWNSCALLBACK)), + mHandlingMessage(false), + mClosed(false), + mDisconnected(false), + mCallback(aCallback), + mParentManager(aParentManager) +{ + NS_ASSERTION(mChrome || !aParentManager, "Should not set parent manager!"); + NS_ASSERTION(!mIsBroadcaster || !mCallback, + "Broadcasters cannot have callbacks!"); + if (mIsProcessManager && (!mChrome || IsBroadcaster())) { + mozilla::HoldJSObjects(this); + } + // This is a bit hackish. When parent manager is global, we want + // to attach the message manager to it immediately. + // Is it just the frame message manager which waits until the + // content process is running. + if (mParentManager && (mCallback || IsBroadcaster())) { + mParentManager->AddChildManager(this); + } + if (mOwnsCallback) { + mOwnedCallback = aCallback; + } +} + +nsFrameMessageManager::~nsFrameMessageManager() +{ + if (mIsProcessManager && (!mChrome || IsBroadcaster())) { + mozilla::DropJSObjects(this); + } + for (int32_t i = mChildManagers.Count(); i > 0; --i) { + static_cast<nsFrameMessageManager*>(mChildManagers[i - 1])-> + Disconnect(false); + } + if (mIsProcessManager) { + if (this == sParentProcessManager) { + sParentProcessManager = nullptr; + } + if (this == sChildProcessManager) { + sChildProcessManager = nullptr; + delete mozilla::dom::SameProcessMessageQueue::Get(); + } + if (this == sSameProcessParentManager) { + sSameProcessParentManager = nullptr; + } + } +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameMessageManager) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameMessageManager) + for (auto iter = tmp->mListeners.Iter(); !iter.Done(); iter.Next()) { + nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = iter.UserData(); + uint32_t count = listeners->Length(); + for (uint32_t i = 0; i < count; ++i) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "listeners[i] mStrongListener"); + cb.NoteXPCOMChild(listeners->ElementAt(i).mStrongListener.get()); + } + } + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildManagers) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsFrameMessageManager) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mInitialProcessData) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameMessageManager) + tmp->mListeners.Clear(); + for (int32_t i = tmp->mChildManagers.Count(); i > 0; --i) { + static_cast<nsFrameMessageManager*>(tmp->mChildManagers[i - 1])-> + Disconnect(false); + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildManagers) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentManager) + tmp->mInitialProcessData.setNull(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameMessageManager) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentFrameMessageManager) + + /* nsFrameMessageManager implements nsIMessageSender and nsIMessageBroadcaster, + * both of which descend from nsIMessageListenerManager. QI'ing to + * nsIMessageListenerManager is therefore ambiguous and needs explicit casts + * depending on which child interface applies. */ + NS_INTERFACE_MAP_ENTRY_AGGREGATED(nsIMessageListenerManager, + (mIsBroadcaster ? + static_cast<nsIMessageListenerManager*>( + static_cast<nsIMessageBroadcaster*>(this)) : + static_cast<nsIMessageListenerManager*>( + static_cast<nsIMessageSender*>(this)))) + + /* Message managers in child process implement nsIMessageSender and + nsISyncMessageSender. Message managers in the chrome process are + either broadcasters (if they have subordinate/child message + managers) or they're simple message senders. */ + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISyncMessageSender, !mChrome) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMessageSender, !mChrome || !mIsBroadcaster) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMessageBroadcaster, mChrome && mIsBroadcaster) + + /* nsIContentFrameMessageManager is accessible only in TabChildGlobal. */ + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIContentFrameMessageManager, + !mChrome && !mIsProcessManager) + + /* Frame message managers (non-process message managers) support nsIFrameScriptLoader. */ + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFrameScriptLoader, + mChrome && !mIsProcessManager) + + /* Process message managers (process message managers) support nsIProcessScriptLoader. */ + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIProcessScriptLoader, + mChrome && mIsProcessManager) + + /* Global process message managers (process message managers) support nsIGlobalProcessScriptLoader. */ + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIGlobalProcessScriptLoader, + mChrome && mIsProcessManager && mIsBroadcaster) + + /* Message senders in the chrome process support nsIProcessChecker. */ + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIProcessChecker, + mChrome && !mIsBroadcaster) + + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO_CONDITIONAL(ChromeMessageBroadcaster, + mChrome && mIsBroadcaster) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO_CONDITIONAL(ChromeMessageSender, + mChrome && !mIsBroadcaster) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameMessageManager) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameMessageManager) + +enum ActorFlavorEnum { + Parent = 0, + Child +}; + +template <ActorFlavorEnum> +struct BlobTraits +{ }; + +template <> +struct BlobTraits<Parent> +{ + typedef mozilla::dom::BlobParent BlobType; + typedef mozilla::dom::PBlobParent ProtocolType; + typedef mozilla::dom::nsIContentParent ConcreteContentManagerType; +}; + +template <> +struct BlobTraits<Child> +{ + typedef mozilla::dom::BlobChild BlobType; + typedef mozilla::dom::PBlobChild ProtocolType; + typedef mozilla::dom::nsIContentChild ConcreteContentManagerType; +}; + +template<ActorFlavorEnum> +struct DataBlobs +{ }; + +template<> +struct DataBlobs<Parent> +{ + typedef BlobTraits<Parent>::ProtocolType ProtocolType; + + static InfallibleTArray<ProtocolType*>& Blobs(ClonedMessageData& aData) + { + return aData.blobsParent(); + } + + static const InfallibleTArray<ProtocolType*>& Blobs(const ClonedMessageData& aData) + { + return aData.blobsParent(); + } +}; + +template<> +struct DataBlobs<Child> +{ + typedef BlobTraits<Child>::ProtocolType ProtocolType; + + static InfallibleTArray<ProtocolType*>& Blobs(ClonedMessageData& aData) + { + return aData.blobsChild(); + } + + static const InfallibleTArray<ProtocolType*>& Blobs(const ClonedMessageData& aData) + { + return aData.blobsChild(); + } +}; + +template<ActorFlavorEnum Flavor> +static bool +BuildClonedMessageData(typename BlobTraits<Flavor>::ConcreteContentManagerType* aManager, + StructuredCloneData& aData, + ClonedMessageData& aClonedData) +{ + SerializedStructuredCloneBuffer& buffer = aClonedData.data(); + auto iter = aData.Data().Iter(); + size_t size = aData.Data().Size(); + bool success; + buffer.data = aData.Data().Borrow<js::SystemAllocPolicy>(iter, size, &success); + if (NS_WARN_IF(!success)) { + return false; + } + aClonedData.identfiers().AppendElements(aData.PortIdentifiers()); + + const nsTArray<RefPtr<BlobImpl>>& blobImpls = aData.BlobImpls(); + + if (!blobImpls.IsEmpty()) { + typedef typename BlobTraits<Flavor>::ProtocolType ProtocolType; + InfallibleTArray<ProtocolType*>& blobList = DataBlobs<Flavor>::Blobs(aClonedData); + uint32_t length = blobImpls.Length(); + blobList.SetCapacity(length); + for (uint32_t i = 0; i < length; ++i) { + typename BlobTraits<Flavor>::BlobType* protocolActor = + aManager->GetOrCreateActorForBlobImpl(blobImpls[i]); + if (!protocolActor) { + return false; + } + blobList.AppendElement(protocolActor); + } + } + return true; +} + +bool +MessageManagerCallback::BuildClonedMessageDataForParent(nsIContentParent* aParent, + StructuredCloneData& aData, + ClonedMessageData& aClonedData) +{ + return BuildClonedMessageData<Parent>(aParent, aData, aClonedData); +} + +bool +MessageManagerCallback::BuildClonedMessageDataForChild(nsIContentChild* aChild, + StructuredCloneData& aData, + ClonedMessageData& aClonedData) +{ + return BuildClonedMessageData<Child>(aChild, aData, aClonedData); +} + +template<ActorFlavorEnum Flavor> +static void +UnpackClonedMessageData(const ClonedMessageData& aClonedData, + StructuredCloneData& aData) +{ + const SerializedStructuredCloneBuffer& buffer = aClonedData.data(); + typedef typename BlobTraits<Flavor>::ProtocolType ProtocolType; + const InfallibleTArray<ProtocolType*>& blobs = DataBlobs<Flavor>::Blobs(aClonedData); + const InfallibleTArray<MessagePortIdentifier>& identifiers = aClonedData.identfiers(); + + aData.UseExternalData(buffer.data); + + aData.PortIdentifiers().AppendElements(identifiers); + + if (!blobs.IsEmpty()) { + uint32_t length = blobs.Length(); + aData.BlobImpls().SetCapacity(length); + for (uint32_t i = 0; i < length; ++i) { + auto* blob = + static_cast<typename BlobTraits<Flavor>::BlobType*>(blobs[i]); + MOZ_ASSERT(blob); + + RefPtr<BlobImpl> blobImpl = blob->GetBlobImpl(); + MOZ_ASSERT(blobImpl); + + aData.BlobImpls().AppendElement(blobImpl); + } + } +} + +void +mozilla::dom::ipc::UnpackClonedMessageDataForParent(const ClonedMessageData& aClonedData, + StructuredCloneData& aData) +{ + UnpackClonedMessageData<Parent>(aClonedData, aData); +} + +void +mozilla::dom::ipc::UnpackClonedMessageDataForChild(const ClonedMessageData& aClonedData, + StructuredCloneData& aData) +{ + UnpackClonedMessageData<Child>(aClonedData, aData); +} + +bool +SameProcessCpowHolder::ToObject(JSContext* aCx, + JS::MutableHandle<JSObject*> aObjp) +{ + if (!mObj) { + return true; + } + + aObjp.set(mObj); + return JS_WrapObject(aCx, aObjp); +} + +// nsIMessageListenerManager + +NS_IMETHODIMP +nsFrameMessageManager::AddMessageListener(const nsAString& aMessage, + nsIMessageListener* aListener, + bool aListenWhenClosed) +{ + nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = + mListeners.Get(aMessage); + if (!listeners) { + listeners = new nsAutoTObserverArray<nsMessageListenerInfo, 1>(); + mListeners.Put(aMessage, listeners); + } else { + uint32_t len = listeners->Length(); + for (uint32_t i = 0; i < len; ++i) { + if (listeners->ElementAt(i).mStrongListener == aListener) { + return NS_OK; + } + } + } + + nsMessageListenerInfo* entry = listeners->AppendElement(); + NS_ENSURE_TRUE(entry, NS_ERROR_OUT_OF_MEMORY); + entry->mStrongListener = aListener; + entry->mListenWhenClosed = aListenWhenClosed; + return NS_OK; +} + +NS_IMETHODIMP +nsFrameMessageManager::RemoveMessageListener(const nsAString& aMessage, + nsIMessageListener* aListener) +{ + nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = + mListeners.Get(aMessage); + if (!listeners) { + return NS_OK; + } + + uint32_t len = listeners->Length(); + for (uint32_t i = 0; i < len; ++i) { + if (listeners->ElementAt(i).mStrongListener == aListener) { + listeners->RemoveElementAt(i); + return NS_OK; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsFrameMessageManager::AddWeakMessageListener(const nsAString& aMessage, + nsIMessageListener* aListener) +{ + nsWeakPtr weak = do_GetWeakReference(aListener); + NS_ENSURE_TRUE(weak, NS_ERROR_NO_INTERFACE); + +#ifdef DEBUG + // It's technically possible that one object X could give two different + // nsIWeakReference*'s when you do_GetWeakReference(X). We really don't want + // this to happen; it will break e.g. RemoveWeakMessageListener. So let's + // check that we're not getting ourselves into that situation. + nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener); + for (auto iter = mListeners.Iter(); !iter.Done(); iter.Next()) { + nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = iter.UserData(); + uint32_t count = listeners->Length(); + for (uint32_t i = 0; i < count; i++) { + nsWeakPtr weakListener = listeners->ElementAt(i).mWeakListener; + if (weakListener) { + nsCOMPtr<nsISupports> otherCanonical = do_QueryReferent(weakListener); + MOZ_ASSERT((canonical == otherCanonical) == (weak == weakListener)); + } + } + } +#endif + + nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = + mListeners.Get(aMessage); + if (!listeners) { + listeners = new nsAutoTObserverArray<nsMessageListenerInfo, 1>(); + mListeners.Put(aMessage, listeners); + } else { + uint32_t len = listeners->Length(); + for (uint32_t i = 0; i < len; ++i) { + if (listeners->ElementAt(i).mWeakListener == weak) { + return NS_OK; + } + } + } + + nsMessageListenerInfo* entry = listeners->AppendElement(); + entry->mWeakListener = weak; + entry->mListenWhenClosed = false; + return NS_OK; +} + +NS_IMETHODIMP +nsFrameMessageManager::RemoveWeakMessageListener(const nsAString& aMessage, + nsIMessageListener* aListener) +{ + nsWeakPtr weak = do_GetWeakReference(aListener); + NS_ENSURE_TRUE(weak, NS_OK); + + nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = + mListeners.Get(aMessage); + if (!listeners) { + return NS_OK; + } + + uint32_t len = listeners->Length(); + for (uint32_t i = 0; i < len; ++i) { + if (listeners->ElementAt(i).mWeakListener == weak) { + listeners->RemoveElementAt(i); + return NS_OK; + } + } + + return NS_OK; +} + +// nsIFrameScriptLoader + +NS_IMETHODIMP +nsFrameMessageManager::LoadScript(const nsAString& aURL, + bool aAllowDelayedLoad, + bool aRunInGlobalScope) +{ + if (aAllowDelayedLoad) { + // Cache for future windows or frames + mPendingScripts.AppendElement(aURL); + mPendingScriptsGlobalStates.AppendElement(aRunInGlobalScope); + } + + if (mCallback) { +#ifdef DEBUG_smaug + printf("Will load %s \n", NS_ConvertUTF16toUTF8(aURL).get()); +#endif + NS_ENSURE_TRUE(mCallback->DoLoadMessageManagerScript(aURL, aRunInGlobalScope), + NS_ERROR_FAILURE); + } + + for (int32_t i = 0; i < mChildManagers.Count(); ++i) { + RefPtr<nsFrameMessageManager> mm = + static_cast<nsFrameMessageManager*>(mChildManagers[i]); + if (mm) { + // Use false here, so that child managers don't cache the script, which + // is already cached in the parent. + mm->LoadScript(aURL, false, aRunInGlobalScope); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsFrameMessageManager::RemoveDelayedScript(const nsAString& aURL) +{ + for (uint32_t i = 0; i < mPendingScripts.Length(); ++i) { + if (mPendingScripts[i] == aURL) { + mPendingScripts.RemoveElementAt(i); + mPendingScriptsGlobalStates.RemoveElementAt(i); + break; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsFrameMessageManager::GetDelayedScripts(JSContext* aCx, JS::MutableHandle<JS::Value> aList) +{ + // Frame message managers may return an incomplete list because scripts + // that were loaded after it was connected are not added to the list. + if (!IsGlobal() && !IsBroadcaster()) { + NS_WARNING("Cannot retrieve list of pending frame scripts for frame" + "message managers as it may be incomplete"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, mPendingScripts.Length())); + NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY); + + JS::Rooted<JSString*> url(aCx); + JS::Rooted<JSObject*> pair(aCx); + for (uint32_t i = 0; i < mPendingScripts.Length(); ++i) { + url = JS_NewUCStringCopyN(aCx, mPendingScripts[i].get(), mPendingScripts[i].Length()); + NS_ENSURE_TRUE(url, NS_ERROR_OUT_OF_MEMORY); + + JS::AutoValueArray<2> pairElts(aCx); + pairElts[0].setString(url); + pairElts[1].setBoolean(mPendingScriptsGlobalStates[i]); + + pair = JS_NewArrayObject(aCx, pairElts); + NS_ENSURE_TRUE(pair, NS_ERROR_OUT_OF_MEMORY); + + NS_ENSURE_TRUE(JS_DefineElement(aCx, array, i, pair, JSPROP_ENUMERATE), + NS_ERROR_OUT_OF_MEMORY); + } + + aList.setObject(*array); + return NS_OK; +} + +// nsIFrameScriptLoader + +NS_IMETHODIMP +nsFrameMessageManager::LoadFrameScript(const nsAString& aURL, + bool aAllowDelayedLoad, + bool aRunInGlobalScope) +{ + return LoadScript(aURL, aAllowDelayedLoad, aRunInGlobalScope); +} + +NS_IMETHODIMP +nsFrameMessageManager::RemoveDelayedFrameScript(const nsAString& aURL) +{ + return RemoveDelayedScript(aURL); +} + +NS_IMETHODIMP +nsFrameMessageManager::GetDelayedFrameScripts(JSContext* aCx, JS::MutableHandle<JS::Value> aList) +{ + return GetDelayedScripts(aCx, aList); +} + +// nsIProcessScriptLoader + +NS_IMETHODIMP +nsFrameMessageManager::LoadProcessScript(const nsAString& aURL, + bool aAllowDelayedLoad) +{ + return LoadScript(aURL, aAllowDelayedLoad, false); +} + +NS_IMETHODIMP +nsFrameMessageManager::RemoveDelayedProcessScript(const nsAString& aURL) +{ + return RemoveDelayedScript(aURL); +} + +NS_IMETHODIMP +nsFrameMessageManager::GetDelayedProcessScripts(JSContext* aCx, JS::MutableHandle<JS::Value> aList) +{ + return GetDelayedScripts(aCx, aList); +} + +static bool +JSONCreator(const char16_t* aBuf, uint32_t aLen, void* aData) +{ + nsAString* result = static_cast<nsAString*>(aData); + result->Append(static_cast<const char16_t*>(aBuf), + static_cast<uint32_t>(aLen)); + return true; +} + +static bool +GetParamsForMessage(JSContext* aCx, + const JS::Value& aValue, + const JS::Value& aTransfer, + StructuredCloneData& aData) +{ + // First try to use structured clone on the whole thing. + JS::RootedValue v(aCx, aValue); + JS::RootedValue t(aCx, aTransfer); + ErrorResult rv; + aData.Write(aCx, v, t, rv); + if (!rv.Failed()) { + return true; + } + + rv.SuppressException(); + JS_ClearPendingException(aCx); + + nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + if (console) { + nsAutoString filename; + uint32_t lineno = 0, column = 0; + nsJSUtils::GetCallingLocation(aCx, filename, &lineno, &column); + nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); + error->Init(NS_LITERAL_STRING("Sending message that cannot be cloned. Are you trying to send an XPCOM object?"), + filename, EmptyString(), lineno, column, + nsIScriptError::warningFlag, "chrome javascript"); + console->LogMessage(error); + } + + // Not clonable, try JSON + //XXX This is ugly but currently structured cloning doesn't handle + // properly cases when interface is implemented in JS and used + // as a dictionary. + nsAutoString json; + NS_ENSURE_TRUE(JS_Stringify(aCx, &v, nullptr, JS::NullHandleValue, + JSONCreator, &json), false); + NS_ENSURE_TRUE(!json.IsEmpty(), false); + + JS::Rooted<JS::Value> val(aCx, JS::NullValue()); + NS_ENSURE_TRUE(JS_ParseJSON(aCx, static_cast<const char16_t*>(json.get()), + json.Length(), &val), false); + + aData.Write(aCx, val, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return false; + } + + return true; +} + +// nsISyncMessageSender + +static bool sSendingSyncMessage = false; + +NS_IMETHODIMP +nsFrameMessageManager::SendSyncMessage(const nsAString& aMessageName, + JS::Handle<JS::Value> aJSON, + JS::Handle<JS::Value> aObjects, + nsIPrincipal* aPrincipal, + JSContext* aCx, + uint8_t aArgc, + JS::MutableHandle<JS::Value> aRetval) +{ + return SendMessage(aMessageName, aJSON, aObjects, aPrincipal, aCx, aArgc, + aRetval, true); +} + +NS_IMETHODIMP +nsFrameMessageManager::SendRpcMessage(const nsAString& aMessageName, + JS::Handle<JS::Value> aJSON, + JS::Handle<JS::Value> aObjects, + nsIPrincipal* aPrincipal, + JSContext* aCx, + uint8_t aArgc, + JS::MutableHandle<JS::Value> aRetval) +{ + return SendMessage(aMessageName, aJSON, aObjects, aPrincipal, aCx, aArgc, + aRetval, false); +} + +static bool +AllowMessage(size_t aDataLength, const nsAString& aMessageName) +{ + static const size_t kMinTelemetryMessageSize = 8192; + + if (aDataLength < kMinTelemetryMessageSize) { + return true; + } + + NS_ConvertUTF16toUTF8 messageName(aMessageName); + messageName.StripChars("0123456789"); + + Telemetry::Accumulate(Telemetry::MESSAGE_MANAGER_MESSAGE_SIZE2, messageName, + aDataLength); + + // A message includes more than structured clone data, so subtract + // 20KB to make it more likely that a message within this bound won't + // result in an overly large IPC message. + static const size_t kMaxMessageSize = IPC::Channel::kMaximumMessageSize - 20 * 1024; + if (aDataLength < kMaxMessageSize) { + return true; + } + + Telemetry::Accumulate(Telemetry::REJECTED_MESSAGE_MANAGER_MESSAGE, + messageName); + + return false; +} + +nsresult +nsFrameMessageManager::SendMessage(const nsAString& aMessageName, + JS::Handle<JS::Value> aJSON, + JS::Handle<JS::Value> aObjects, + nsIPrincipal* aPrincipal, + JSContext* aCx, + uint8_t aArgc, + JS::MutableHandle<JS::Value> aRetval, + bool aIsSync) +{ + NS_ASSERTION(!IsGlobal(), "Should not call SendSyncMessage in chrome"); + NS_ASSERTION(!IsBroadcaster(), "Should not call SendSyncMessage in chrome"); + NS_ASSERTION(!mParentManager, "Should not have parent manager in content!"); + + aRetval.setUndefined(); + NS_ENSURE_TRUE(mCallback, NS_ERROR_NOT_INITIALIZED); + + if (sSendingSyncMessage && aIsSync) { + // No kind of blocking send should be issued on top of a sync message. + return NS_ERROR_UNEXPECTED; + } + + StructuredCloneData data; + if (aArgc >= 2 && !GetParamsForMessage(aCx, aJSON, JS::UndefinedHandleValue, data)) { + return NS_ERROR_DOM_DATA_CLONE_ERR; + } + + if (!AllowMessage(data.DataLength(), aMessageName)) { + return NS_ERROR_FAILURE; + } + + JS::Rooted<JSObject*> objects(aCx); + if (aArgc >= 3 && aObjects.isObject()) { + objects = &aObjects.toObject(); + } + + nsTArray<StructuredCloneData> retval; + + sSendingSyncMessage |= aIsSync; + bool ok = mCallback->DoSendBlockingMessage(aCx, aMessageName, data, objects, + aPrincipal, &retval, aIsSync); + if (aIsSync) { + sSendingSyncMessage = false; + } + + if (!ok) { + return NS_OK; + } + + uint32_t len = retval.Length(); + JS::Rooted<JSObject*> dataArray(aCx, JS_NewArrayObject(aCx, len)); + NS_ENSURE_TRUE(dataArray, NS_ERROR_OUT_OF_MEMORY); + + for (uint32_t i = 0; i < len; ++i) { + JS::Rooted<JS::Value> ret(aCx); + ErrorResult rv; + retval[i].Read(aCx, &ret, rv); + if (rv.Failed()) { + MOZ_ASSERT(false, "Unable to read structured clone in SendMessage"); + rv.SuppressException(); + return NS_ERROR_UNEXPECTED; + } + + NS_ENSURE_TRUE(JS_DefineElement(aCx, dataArray, i, ret, JSPROP_ENUMERATE), + NS_ERROR_OUT_OF_MEMORY); + } + + aRetval.setObject(*dataArray); + return NS_OK; +} + +nsresult +nsFrameMessageManager::DispatchAsyncMessageInternal(JSContext* aCx, + const nsAString& aMessage, + StructuredCloneData& aData, + JS::Handle<JSObject *> aCpows, + nsIPrincipal* aPrincipal) +{ + if (mIsBroadcaster) { + int32_t len = mChildManagers.Count(); + for (int32_t i = 0; i < len; ++i) { + static_cast<nsFrameMessageManager*>(mChildManagers[i])-> + DispatchAsyncMessageInternal(aCx, aMessage, aData, aCpows, aPrincipal); + } + return NS_OK; + } + + if (!mCallback) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv = mCallback->DoSendAsyncMessage(aCx, aMessage, aData, aCpows, aPrincipal); + if (NS_FAILED(rv)) { + return rv; + } + return NS_OK; +} + +nsresult +nsFrameMessageManager::DispatchAsyncMessage(const nsAString& aMessageName, + const JS::Value& aJSON, + const JS::Value& aObjects, + nsIPrincipal* aPrincipal, + const JS::Value& aTransfers, + JSContext* aCx, + uint8_t aArgc) +{ + StructuredCloneData data; + if (aArgc >= 2 && !GetParamsForMessage(aCx, aJSON, aTransfers, data)) { + return NS_ERROR_DOM_DATA_CLONE_ERR; + } + + if (!AllowMessage(data.DataLength(), aMessageName)) { + return NS_ERROR_FAILURE; + } + + JS::Rooted<JSObject*> objects(aCx); + if (aArgc >= 3 && aObjects.isObject()) { + objects = &aObjects.toObject(); + } + + return DispatchAsyncMessageInternal(aCx, aMessageName, data, objects, + aPrincipal); +} + +// nsIMessageSender + +NS_IMETHODIMP +nsFrameMessageManager::SendAsyncMessage(const nsAString& aMessageName, + JS::Handle<JS::Value> aJSON, + JS::Handle<JS::Value> aObjects, + nsIPrincipal* aPrincipal, + JS::Handle<JS::Value> aTransfers, + JSContext* aCx, + uint8_t aArgc) +{ + return DispatchAsyncMessage(aMessageName, aJSON, aObjects, aPrincipal, + aTransfers, aCx, aArgc); +} + + +// nsIMessageBroadcaster + +NS_IMETHODIMP +nsFrameMessageManager::BroadcastAsyncMessage(const nsAString& aMessageName, + JS::Handle<JS::Value> aJSON, + JS::Handle<JS::Value> aObjects, + JSContext* aCx, + uint8_t aArgc) +{ + return DispatchAsyncMessage(aMessageName, aJSON, aObjects, nullptr, + JS::UndefinedHandleValue, aCx, aArgc); +} + +NS_IMETHODIMP +nsFrameMessageManager::GetChildCount(uint32_t* aChildCount) +{ + *aChildCount = static_cast<uint32_t>(mChildManagers.Count()); + return NS_OK; +} + +NS_IMETHODIMP +nsFrameMessageManager::GetChildAt(uint32_t aIndex, + nsIMessageListenerManager** aMM) +{ + *aMM = nullptr; + nsCOMPtr<nsIMessageListenerManager> mm = + do_QueryInterface(mChildManagers.SafeObjectAt(static_cast<uint32_t>(aIndex))); + mm.swap(*aMM); + return NS_OK; +} + + +// nsIContentFrameMessageManager + +NS_IMETHODIMP +nsFrameMessageManager::Dump(const nsAString& aStr) +{ +#ifdef ANDROID + __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", NS_ConvertUTF16toUTF8(aStr).get()); +#endif +#ifdef XP_WIN + if (IsDebuggerPresent()) { + OutputDebugStringW(PromiseFlatString(aStr).get()); + } +#endif + fputs(NS_ConvertUTF16toUTF8(aStr).get(), stdout); + fflush(stdout); + return NS_OK; +} + +NS_IMETHODIMP +nsFrameMessageManager::PrivateNoteIntentionalCrash() +{ + if (XRE_IsContentProcess()) { + mozilla::NoteIntentionalCrash("tab"); + return NS_OK; + } else { + return NS_ERROR_NOT_IMPLEMENTED; + } +} + +NS_IMETHODIMP +nsFrameMessageManager::GetContent(mozIDOMWindowProxy** aContent) +{ + *aContent = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsFrameMessageManager::GetDocShell(nsIDocShell** aDocShell) +{ + *aDocShell = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsFrameMessageManager::Btoa(const nsAString& aBinaryData, + nsAString& aAsciiBase64String) +{ + return nsContentUtils::Btoa(aBinaryData, aAsciiBase64String); +} + +NS_IMETHODIMP +nsFrameMessageManager::Atob(const nsAString& aAsciiString, + nsAString& aBinaryData) +{ + return nsContentUtils::Atob(aAsciiString, aBinaryData); +} + +// nsIProcessChecker + +NS_IMETHODIMP +nsFrameMessageManager::KillChild(bool *aValid) +{ + if (!mCallback) { + *aValid = false; + return NS_ERROR_NOT_AVAILABLE; + } + + *aValid = mCallback->KillChild(); + return NS_OK; +} + +nsresult +nsFrameMessageManager::AssertProcessInternal(ProcessCheckerType aType, + const nsAString& aCapability, + bool* aValid) +{ + *aValid = false; + + // This API is only supported for message senders in the chrome process. + if (!mChrome || mIsBroadcaster) { + return NS_ERROR_NOT_IMPLEMENTED; + } + if (!mCallback) { + return NS_ERROR_NOT_AVAILABLE; + } + switch (aType) { + case PROCESS_CHECKER_PERMISSION: + *aValid = mCallback->CheckPermission(aCapability); + break; + case PROCESS_CHECKER_MANIFEST_URL: + *aValid = mCallback->CheckManifestURL(aCapability); + break; + case ASSERT_APP_HAS_PERMISSION: + *aValid = mCallback->CheckAppHasPermission(aCapability); + break; + default: + break; + } + return NS_OK; +} + +NS_IMETHODIMP +nsFrameMessageManager::AssertPermission(const nsAString& aPermission, + bool* aHasPermission) +{ + return AssertProcessInternal(PROCESS_CHECKER_PERMISSION, + aPermission, + aHasPermission); +} + +NS_IMETHODIMP +nsFrameMessageManager::AssertContainApp(const nsAString& aManifestURL, + bool* aHasManifestURL) +{ + return AssertProcessInternal(PROCESS_CHECKER_MANIFEST_URL, + aManifestURL, + aHasManifestURL); +} + +NS_IMETHODIMP +nsFrameMessageManager::AssertAppHasPermission(const nsAString& aPermission, + bool* aHasPermission) +{ + return AssertProcessInternal(ASSERT_APP_HAS_PERMISSION, + aPermission, + aHasPermission); +} + +NS_IMETHODIMP +nsFrameMessageManager::AssertAppHasStatus(unsigned short aStatus, + bool* aHasStatus) +{ + *aHasStatus = false; + + // This API is only supported for message senders in the chrome process. + if (!mChrome || mIsBroadcaster) { + return NS_ERROR_NOT_IMPLEMENTED; + } + if (!mCallback) { + return NS_ERROR_NOT_AVAILABLE; + } + *aHasStatus = mCallback->CheckAppHasStatus(aStatus); + + return NS_OK; +} + +class MMListenerRemover +{ +public: + explicit MMListenerRemover(nsFrameMessageManager* aMM) + : mWasHandlingMessage(aMM->mHandlingMessage) + , mMM(aMM) + { + mMM->mHandlingMessage = true; + } + ~MMListenerRemover() + { + if (!mWasHandlingMessage) { + mMM->mHandlingMessage = false; + if (mMM->mDisconnected) { + mMM->mListeners.Clear(); + } + } + } + + bool mWasHandlingMessage; + RefPtr<nsFrameMessageManager> mMM; +}; + + +// nsIMessageListener + +nsresult +nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget, + nsIFrameLoader* aTargetFrameLoader, + const nsAString& aMessage, + bool aIsSync, + StructuredCloneData* aCloneData, + mozilla::jsipc::CpowHolder* aCpows, + nsIPrincipal* aPrincipal, + nsTArray<StructuredCloneData>* aRetVal) +{ + return ReceiveMessage(aTarget, aTargetFrameLoader, mClosed, aMessage, aIsSync, + aCloneData, aCpows, aPrincipal, aRetVal); +} + +nsresult +nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget, + nsIFrameLoader* aTargetFrameLoader, + bool aTargetClosed, + const nsAString& aMessage, + bool aIsSync, + StructuredCloneData* aCloneData, + mozilla::jsipc::CpowHolder* aCpows, + nsIPrincipal* aPrincipal, + nsTArray<StructuredCloneData>* aRetVal) +{ + nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = + mListeners.Get(aMessage); + if (listeners) { + + MMListenerRemover lr(this); + + nsAutoTObserverArray<nsMessageListenerInfo, 1>::EndLimitedIterator + iter(*listeners); + while(iter.HasMore()) { + nsMessageListenerInfo& listener = iter.GetNext(); + // Remove mListeners[i] if it's an expired weak listener. + nsCOMPtr<nsISupports> weakListener; + if (listener.mWeakListener) { + weakListener = do_QueryReferent(listener.mWeakListener); + if (!weakListener) { + listeners->RemoveElement(listener); + continue; + } + } + + if (!listener.mListenWhenClosed && aTargetClosed) { + continue; + } + + nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS; + if (weakListener) { + wrappedJS = do_QueryInterface(weakListener); + } else { + wrappedJS = do_QueryInterface(listener.mStrongListener); + } + + if (!wrappedJS) { + continue; + } + + if (!wrappedJS->GetJSObject()) { + continue; + } + + AutoEntryScript aes(wrappedJS->GetJSObject(), "message manager handler"); + JSContext* cx = aes.cx(); + JS::Rooted<JSObject*> object(cx, wrappedJS->GetJSObject()); + + // The parameter for the listener function. + JS::Rooted<JSObject*> param(cx, JS_NewPlainObject(cx)); + NS_ENSURE_TRUE(param, NS_ERROR_OUT_OF_MEMORY); + + JS::Rooted<JS::Value> targetv(cx); + js::AssertSameCompartment(cx, object); + nsresult rv = nsContentUtils::WrapNative(cx, aTarget, &targetv); + NS_ENSURE_SUCCESS(rv, rv); + + JS::Rooted<JSObject*> cpows(cx); + if (aCpows) { + if (!aCpows->ToObject(cx, &cpows)) { + return NS_ERROR_UNEXPECTED; + } + } + + if (!cpows) { + cpows = JS_NewPlainObject(cx); + if (!cpows) { + return NS_ERROR_UNEXPECTED; + } + } + + JS::Rooted<JS::Value> cpowsv(cx, JS::ObjectValue(*cpows)); + + JS::Rooted<JS::Value> json(cx, JS::NullValue()); + if (aCloneData && aCloneData->DataLength()) { + ErrorResult rv; + aCloneData->Read(cx, &json, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + JS_ClearPendingException(cx); + return NS_OK; + } + } + + // Get cloned MessagePort from StructuredCloneData. + nsTArray<RefPtr<mozilla::dom::MessagePort>> ports; + if (aCloneData) { + ports = aCloneData->TakeTransferredPorts(); + } + + JS::Rooted<JS::Value> transferredList(cx); + if (NS_WARN_IF(!ToJSValue(cx, ports, &transferredList))) { + return NS_ERROR_UNEXPECTED; + } + + JS::Rooted<JSString*> jsMessage(cx, + JS_NewUCStringCopyN(cx, + static_cast<const char16_t*>(aMessage.BeginReading()), + aMessage.Length())); + NS_ENSURE_TRUE(jsMessage, NS_ERROR_OUT_OF_MEMORY); + JS::Rooted<JS::Value> syncv(cx, JS::BooleanValue(aIsSync)); + bool ok = JS_DefineProperty(cx, param, "target", targetv, JSPROP_ENUMERATE) && + JS_DefineProperty(cx, param, "name", jsMessage, JSPROP_ENUMERATE) && + JS_DefineProperty(cx, param, "sync", syncv, JSPROP_ENUMERATE) && + JS_DefineProperty(cx, param, "json", json, JSPROP_ENUMERATE) && // deprecated + JS_DefineProperty(cx, param, "data", json, JSPROP_ENUMERATE) && + JS_DefineProperty(cx, param, "objects", cpowsv, JSPROP_ENUMERATE) && + JS_DefineProperty(cx, param, "ports", transferredList, JSPROP_ENUMERATE); + + NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); + + if (aTargetFrameLoader) { + JS::Rooted<JS::Value> targetFrameLoaderv(cx); + nsresult rv = nsContentUtils::WrapNative(cx, aTargetFrameLoader, &targetFrameLoaderv); + NS_ENSURE_SUCCESS(rv, rv); + + ok = JS_DefineProperty(cx, param, "targetFrameLoader", targetFrameLoaderv, + JSPROP_ENUMERATE); + NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); + } + + // message.principal == null + if (!aPrincipal) { + bool ok = JS_DefineProperty(cx, param, "principal", + JS::UndefinedHandleValue, JSPROP_ENUMERATE); + NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); + } + + // message.principal = the principal + else { + JS::Rooted<JS::Value> principalValue(cx); + nsresult rv = nsContentUtils::WrapNative(cx, aPrincipal, + &NS_GET_IID(nsIPrincipal), + &principalValue); + NS_ENSURE_SUCCESS(rv, rv); + bool ok = JS_DefineProperty(cx, param, "principal", principalValue, + JSPROP_ENUMERATE); + NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); + } + + JS::Rooted<JS::Value> thisValue(cx, JS::UndefinedValue()); + + JS::Rooted<JS::Value> funval(cx); + if (JS::IsCallable(object)) { + // If the listener is a JS function: + funval.setObject(*object); + + // A small hack to get 'this' value right on content side where + // messageManager is wrapped in TabChildGlobal. + nsCOMPtr<nsISupports> defaultThisValue; + if (mChrome) { + defaultThisValue = do_QueryObject(this); + } else { + defaultThisValue = aTarget; + } + js::AssertSameCompartment(cx, object); + nsresult rv = nsContentUtils::WrapNative(cx, defaultThisValue, &thisValue); + NS_ENSURE_SUCCESS(rv, rv); + } else { + // If the listener is a JS object which has receiveMessage function: + if (!JS_GetProperty(cx, object, "receiveMessage", &funval) || + !funval.isObject()) { + return NS_ERROR_UNEXPECTED; + } + + // Check if the object is even callable. + NS_ENSURE_STATE(JS::IsCallable(&funval.toObject())); + thisValue.setObject(*object); + } + + JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue()); + JS::Rooted<JS::Value> argv(cx, JS::ObjectValue(*param)); + + { + JS::Rooted<JSObject*> thisObject(cx, thisValue.toObjectOrNull()); + + JSAutoCompartment tac(cx, thisObject); + if (!JS_WrapValue(cx, &argv)) { + return NS_ERROR_UNEXPECTED; + } + + if (!JS_CallFunctionValue(cx, thisObject, funval, + JS::HandleValueArray(argv), &rval)) { + continue; + } + if (aRetVal) { + ErrorResult rv; + StructuredCloneData* data = aRetVal->AppendElement(); + data->Write(cx, rval, rv); + if (NS_WARN_IF(rv.Failed())) { + aRetVal->RemoveElementAt(aRetVal->Length() - 1); + nsString msg = aMessage + NS_LITERAL_STRING(": message reply cannot be cloned. Are you trying to send an XPCOM object?"); + + nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + if (console) { + nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); + error->Init(msg, EmptyString(), EmptyString(), + 0, 0, nsIScriptError::warningFlag, "chrome javascript"); + console->LogMessage(error); + } + + JS_ClearPendingException(cx); + continue; + } + } + } + } + } + + RefPtr<nsFrameMessageManager> kungFuDeathGrip = mParentManager; + if (kungFuDeathGrip) { + return kungFuDeathGrip->ReceiveMessage(aTarget, aTargetFrameLoader, + aTargetClosed, aMessage, + aIsSync, aCloneData, + aCpows, aPrincipal, + aRetVal); + } + return NS_OK; +} + +void +nsFrameMessageManager::AddChildManager(nsFrameMessageManager* aManager) +{ + mChildManagers.AppendObject(aManager); + + RefPtr<nsFrameMessageManager> kungfuDeathGrip = this; + RefPtr<nsFrameMessageManager> kungfuDeathGrip2 = aManager; + + LoadPendingScripts(this, aManager); +} + +void +nsFrameMessageManager::LoadPendingScripts(nsFrameMessageManager* aManager, + nsFrameMessageManager* aChildMM) +{ + // We have parent manager if we're a message broadcaster. + // In that case we want to load the pending scripts from all parent + // message managers in the hierarchy. Process the parent first so + // that pending scripts higher up in the hierarchy are loaded before others. + if (aManager->mParentManager) { + LoadPendingScripts(aManager->mParentManager, aChildMM); + } + + for (uint32_t i = 0; i < aManager->mPendingScripts.Length(); ++i) { + aChildMM->LoadFrameScript(aManager->mPendingScripts[i], + false, + aManager->mPendingScriptsGlobalStates[i]); + } +} + +void +nsFrameMessageManager::LoadPendingScripts() +{ + RefPtr<nsFrameMessageManager> kungfuDeathGrip = this; + LoadPendingScripts(this, this); +} + +void +nsFrameMessageManager::SetCallback(MessageManagerCallback* aCallback) +{ + MOZ_ASSERT(!mIsBroadcaster || !mCallback, + "Broadcasters cannot have callbacks!"); + if (aCallback && mCallback != aCallback) { + mCallback = aCallback; + if (mOwnsCallback) { + mOwnedCallback = aCallback; + } + } +} + +void +nsFrameMessageManager::InitWithCallback(MessageManagerCallback* aCallback) +{ + if (mCallback) { + // Initialization should only happen once. + return; + } + + SetCallback(aCallback); + + // First load parent scripts by adding this to parent manager. + if (mParentManager) { + mParentManager->AddChildManager(this); + } + + for (uint32_t i = 0; i < mPendingScripts.Length(); ++i) { + LoadFrameScript(mPendingScripts[i], false, mPendingScriptsGlobalStates[i]); + } +} + +void +nsFrameMessageManager::RemoveFromParent() +{ + if (mParentManager) { + mParentManager->RemoveChildManager(this); + } + mParentManager = nullptr; + mCallback = nullptr; + mOwnedCallback = nullptr; +} + +void +nsFrameMessageManager::Close() +{ + if (!mClosed) { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(NS_ISUPPORTS_CAST(nsIContentFrameMessageManager*, this), + "message-manager-close", nullptr); + } + } + mClosed = true; + mCallback = nullptr; + mOwnedCallback = nullptr; +} + +void +nsFrameMessageManager::Disconnect(bool aRemoveFromParent) +{ + // Notify message-manager-close if we haven't already. + Close(); + + if (!mDisconnected) { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(NS_ISUPPORTS_CAST(nsIContentFrameMessageManager*, this), + "message-manager-disconnect", nullptr); + } + } + if (mParentManager && aRemoveFromParent) { + mParentManager->RemoveChildManager(this); + } + mDisconnected = true; + mParentManager = nullptr; + if (!mHandlingMessage) { + mListeners.Clear(); + } +} + +void +nsFrameMessageManager::SetInitialProcessData(JS::HandleValue aInitialData) +{ + MOZ_ASSERT(!mChrome); + MOZ_ASSERT(mIsProcessManager); + mInitialProcessData = aInitialData; +} + +NS_IMETHODIMP +nsFrameMessageManager::GetInitialProcessData(JSContext* aCx, JS::MutableHandleValue aResult) +{ + MOZ_ASSERT(mIsProcessManager); + MOZ_ASSERT_IF(mChrome, IsBroadcaster()); + + JS::RootedValue init(aCx, mInitialProcessData); + if (mChrome && init.isUndefined()) { + // We create the initial object in the junk scope. If we created it in a + // normal compartment, that compartment would leak until shutdown. + JS::RootedObject global(aCx, xpc::PrivilegedJunkScope()); + JSAutoCompartment ac(aCx, global); + + JS::RootedObject obj(aCx, JS_NewPlainObject(aCx)); + if (!obj) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mInitialProcessData.setObject(*obj); + init.setObject(*obj); + } + + if (!mChrome && XRE_IsParentProcess()) { + // This is the cpmm in the parent process. We should use the same object as the ppmm. + nsCOMPtr<nsIGlobalProcessScriptLoader> ppmm = + do_GetService("@mozilla.org/parentprocessmessagemanager;1"); + ppmm->GetInitialProcessData(aCx, &init); + mInitialProcessData = init; + } + + if (!JS_WrapValue(aCx, &init)) { + return NS_ERROR_OUT_OF_MEMORY; + } + aResult.set(init); + return NS_OK; +} + +NS_IMETHODIMP +nsFrameMessageManager::GetProcessMessageManager(nsIMessageSender** aPMM) +{ + *aPMM = nullptr; + if (mCallback) { + nsCOMPtr<nsIMessageSender> pmm = mCallback->GetProcessMessageManager(); + pmm.swap(*aPMM); + } + return NS_OK; +} + +namespace { + +struct MessageManagerReferentCount +{ + MessageManagerReferentCount() : mStrong(0), mWeakAlive(0), mWeakDead(0) {} + size_t mStrong; + size_t mWeakAlive; + size_t mWeakDead; + nsTArray<nsString> mSuspectMessages; + nsDataHashtable<nsStringHashKey, uint32_t> mMessageCounter; +}; + +} // namespace + +namespace mozilla { +namespace dom { + +class MessageManagerReporter final : public nsIMemoryReporter +{ + ~MessageManagerReporter() {} + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + + static const size_t kSuspectReferentCount = 300; +protected: + void CountReferents(nsFrameMessageManager* aMessageManager, + MessageManagerReferentCount* aReferentCount); +}; + +NS_IMPL_ISUPPORTS(MessageManagerReporter, nsIMemoryReporter) + +void +MessageManagerReporter::CountReferents(nsFrameMessageManager* aMessageManager, + MessageManagerReferentCount* aReferentCount) +{ + for (auto it = aMessageManager->mListeners.Iter(); !it.Done(); it.Next()) { + nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = + it.UserData(); + uint32_t listenerCount = listeners->Length(); + if (listenerCount == 0) { + continue; + } + + nsString key(it.Key()); + uint32_t oldCount = 0; + aReferentCount->mMessageCounter.Get(key, &oldCount); + uint32_t currentCount = oldCount + listenerCount; + aReferentCount->mMessageCounter.Put(key, currentCount); + + // Keep track of messages that have a suspiciously large + // number of referents (symptom of leak). + if (currentCount == MessageManagerReporter::kSuspectReferentCount) { + aReferentCount->mSuspectMessages.AppendElement(key); + } + + for (uint32_t i = 0; i < listenerCount; ++i) { + const nsMessageListenerInfo& listenerInfo = listeners->ElementAt(i); + if (listenerInfo.mWeakListener) { + nsCOMPtr<nsISupports> referent = + do_QueryReferent(listenerInfo.mWeakListener); + if (referent) { + aReferentCount->mWeakAlive++; + } else { + aReferentCount->mWeakDead++; + } + } else { + aReferentCount->mStrong++; + } + } + } + + // Add referent count in child managers because the listeners + // participate in messages dispatched from parent message manager. + for (uint32_t i = 0; i < aMessageManager->mChildManagers.Length(); ++i) { + RefPtr<nsFrameMessageManager> mm = + static_cast<nsFrameMessageManager*>(aMessageManager->mChildManagers[i]); + CountReferents(mm, aReferentCount); + } +} + +static void +ReportReferentCount(const char* aManagerType, + const MessageManagerReferentCount& aReferentCount, + nsIHandleReportCallback* aHandleReport, + nsISupports* aData) +{ +#define REPORT(_path, _amount, _desc) \ + do { \ + aHandleReport->Callback(EmptyCString(), _path, \ + nsIMemoryReporter::KIND_OTHER, \ + nsIMemoryReporter::UNITS_COUNT, _amount, \ + _desc, aData); \ + } while (0) + + REPORT(nsPrintfCString("message-manager/referent/%s/strong", aManagerType), + aReferentCount.mStrong, + nsPrintfCString("The number of strong referents held by the message " + "manager in the %s manager.", aManagerType)); + REPORT(nsPrintfCString("message-manager/referent/%s/weak/alive", aManagerType), + aReferentCount.mWeakAlive, + nsPrintfCString("The number of weak referents that are still alive " + "held by the message manager in the %s manager.", + aManagerType)); + REPORT(nsPrintfCString("message-manager/referent/%s/weak/dead", aManagerType), + aReferentCount.mWeakDead, + nsPrintfCString("The number of weak referents that are dead " + "held by the message manager in the %s manager.", + aManagerType)); + + for (uint32_t i = 0; i < aReferentCount.mSuspectMessages.Length(); i++) { + uint32_t totalReferentCount = 0; + aReferentCount.mMessageCounter.Get(aReferentCount.mSuspectMessages[i], + &totalReferentCount); + NS_ConvertUTF16toUTF8 suspect(aReferentCount.mSuspectMessages[i]); + REPORT(nsPrintfCString("message-manager-suspect/%s/referent(message=%s)", + aManagerType, suspect.get()), totalReferentCount, + nsPrintfCString("A message in the %s message manager with a " + "suspiciously large number of referents (symptom " + "of a leak).", aManagerType)); + } + +#undef REPORT +} + +NS_IMETHODIMP +MessageManagerReporter::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) +{ + if (XRE_IsParentProcess()) { + nsCOMPtr<nsIMessageBroadcaster> globalmm = + do_GetService("@mozilla.org/globalmessagemanager;1"); + if (globalmm) { + RefPtr<nsFrameMessageManager> mm = + static_cast<nsFrameMessageManager*>(globalmm.get()); + MessageManagerReferentCount count; + CountReferents(mm, &count); + ReportReferentCount("global-manager", count, aHandleReport, aData); + } + } + + if (nsFrameMessageManager::sParentProcessManager) { + MessageManagerReferentCount count; + CountReferents(nsFrameMessageManager::sParentProcessManager, &count); + ReportReferentCount("parent-process-manager", count, aHandleReport, aData); + } + + if (nsFrameMessageManager::sChildProcessManager) { + MessageManagerReferentCount count; + CountReferents(nsFrameMessageManager::sChildProcessManager, &count); + ReportReferentCount("child-process-manager", count, aHandleReport, aData); + } + + return NS_OK; +} + +} // namespace dom +} // namespace mozilla + +nsresult +NS_NewGlobalMessageManager(nsIMessageBroadcaster** aResult) +{ + NS_ENSURE_TRUE(XRE_IsParentProcess(), + NS_ERROR_NOT_AVAILABLE); + RefPtr<nsFrameMessageManager> mm = new nsFrameMessageManager(nullptr, + nullptr, + MM_CHROME | MM_GLOBAL | MM_BROADCASTER); + RegisterStrongMemoryReporter(new MessageManagerReporter()); + mm.forget(aResult); + return NS_OK; +} + +nsDataHashtable<nsStringHashKey, nsMessageManagerScriptHolder*>* + nsMessageManagerScriptExecutor::sCachedScripts = nullptr; +StaticRefPtr<nsScriptCacheCleaner> nsMessageManagerScriptExecutor::sScriptCacheCleaner; + +void +nsMessageManagerScriptExecutor::DidCreateGlobal() +{ + NS_ASSERTION(mGlobal, "Should have mGlobal!"); + if (!sCachedScripts) { + sCachedScripts = + new nsDataHashtable<nsStringHashKey, nsMessageManagerScriptHolder*>; + sScriptCacheCleaner = new nsScriptCacheCleaner(); + } +} + +// static +void +nsMessageManagerScriptExecutor::PurgeCache() +{ + if (sCachedScripts) { + NS_ASSERTION(sCachedScripts != nullptr, "Need cached scripts"); + for (auto iter = sCachedScripts->Iter(); !iter.Done(); iter.Next()) { + delete iter.Data(); + iter.Remove(); + } + } +} + +// static +void +nsMessageManagerScriptExecutor::Shutdown() +{ + if (sCachedScripts) { + PurgeCache(); + + delete sCachedScripts; + sCachedScripts = nullptr; + sScriptCacheCleaner = nullptr; + } +} + +void +nsMessageManagerScriptExecutor::LoadScriptInternal(const nsAString& aURL, + bool aRunInGlobalScope) +{ + if (!mGlobal || !sCachedScripts) { + return; + } + + JS::RootingContext* rcx = RootingCx(); + JS::Rooted<JSScript*> script(rcx); + + nsMessageManagerScriptHolder* holder = sCachedScripts->Get(aURL); + if (holder && holder->WillRunInGlobalScope() == aRunInGlobalScope) { + script = holder->mScript; + } else { + // Don't put anything in the cache if we already have an entry + // with a different WillRunInGlobalScope() value. + bool shouldCache = !holder; + TryCacheLoadAndCompileScript(aURL, aRunInGlobalScope, + shouldCache, &script); + } + + JS::Rooted<JSObject*> global(rcx, mGlobal->GetJSObject()); + if (global) { + AutoEntryScript aes(global, "message manager script load"); + JSContext* cx = aes.cx(); + if (script) { + if (aRunInGlobalScope) { + JS::RootedValue rval(cx); + JS::CloneAndExecuteScript(cx, script, &rval); + } else { + JS::Rooted<JSObject*> scope(cx); + bool ok = js::ExecuteInGlobalAndReturnScope(cx, global, script, &scope); + if (ok) { + // Force the scope to stay alive. + mAnonymousGlobalScopes.AppendElement(scope); + } + } + } + } +} + +void +nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript( + const nsAString& aURL, + bool aRunInGlobalScope, + bool aShouldCache, + JS::MutableHandle<JSScript*> aScriptp) +{ + nsCString url = NS_ConvertUTF16toUTF8(aURL); + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), url); + if (NS_FAILED(rv)) { + return; + } + + bool hasFlags; + rv = NS_URIChainHasFlags(uri, + nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, + &hasFlags); + if (NS_FAILED(rv) || !hasFlags) { + NS_WARNING("Will not load a frame script!"); + return; + } + + nsCOMPtr<nsIChannel> channel; + NS_NewChannel(getter_AddRefs(channel), + uri, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + + if (!channel) { + return; + } + + nsCOMPtr<nsIInputStream> input; + rv = channel->Open2(getter_AddRefs(input)); + NS_ENSURE_SUCCESS_VOID(rv); + nsString dataString; + char16_t* dataStringBuf = nullptr; + size_t dataStringLength = 0; + uint64_t avail64 = 0; + if (input && NS_SUCCEEDED(input->Available(&avail64)) && avail64) { + if (avail64 > UINT32_MAX) { + return; + } + nsCString buffer; + uint32_t avail = (uint32_t)std::min(avail64, (uint64_t)UINT32_MAX); + if (NS_FAILED(NS_ReadInputStreamToString(input, buffer, avail))) { + return; + } + nsScriptLoader::ConvertToUTF16(channel, (uint8_t*)buffer.get(), avail, + EmptyString(), nullptr, + dataStringBuf, dataStringLength); + } + + JS::SourceBufferHolder srcBuf(dataStringBuf, dataStringLength, + JS::SourceBufferHolder::GiveOwnership); + + if (dataStringBuf && dataStringLength > 0) { + // Compile the script in the compilation scope instead of the current global + // to avoid keeping the current compartment alive. + AutoJSAPI jsapi; + if (!jsapi.Init(xpc::CompilationScope())) { + return; + } + JSContext* cx = jsapi.cx(); + JS::CompileOptions options(cx, JSVERSION_LATEST); + options.setFileAndLine(url.get(), 1); + options.setNoScriptRval(true); + JS::Rooted<JSScript*> script(cx); + + if (aRunInGlobalScope) { + if (!JS::Compile(cx, options, srcBuf, &script)) { + return; + } + // We're going to run these against some non-global scope. + } else if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script)) { + return; + } + + MOZ_ASSERT(script); + aScriptp.set(script); + + nsAutoCString scheme; + uri->GetScheme(scheme); + // We don't cache data: scripts! + if (aShouldCache && !scheme.EqualsLiteral("data")) { + // Root the object also for caching. + nsMessageManagerScriptHolder* holder = + new nsMessageManagerScriptHolder(cx, script, aRunInGlobalScope); + sCachedScripts->Put(aURL, holder); + } + } +} + +void +nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript( + const nsAString& aURL, + bool aRunInGlobalScope) +{ + JS::Rooted<JSScript*> script(RootingCx()); + TryCacheLoadAndCompileScript(aURL, aRunInGlobalScope, true, &script); +} + +void +nsMessageManagerScriptExecutor::Trace(const TraceCallbacks& aCallbacks, void* aClosure) +{ + for (size_t i = 0, length = mAnonymousGlobalScopes.Length(); i < length; ++i) { + aCallbacks.Trace(&mAnonymousGlobalScopes[i], "mAnonymousGlobalScopes[i]", aClosure); + } +} + +bool +nsMessageManagerScriptExecutor::InitChildGlobalInternal( + nsISupports* aScope, + const nsACString& aID) +{ + AutoSafeJSContext cx; + nsContentUtils::GetSecurityManager()->GetSystemPrincipal(getter_AddRefs(mPrincipal)); + + nsIXPConnect* xpc = nsContentUtils::XPConnect(); + const uint32_t flags = nsIXPConnect::INIT_JS_STANDARD_CLASSES; + + JS::CompartmentOptions options; + options.creationOptions().setZone(JS::SystemZone); + options.behaviors().setVersion(JSVERSION_LATEST); + + if (xpc::SharedMemoryEnabled()) { + options.creationOptions().setSharedMemoryAndAtomicsEnabled(true); + } + + nsresult rv = + xpc->InitClassesWithNewWrappedGlobal(cx, aScope, mPrincipal, + flags, options, getter_AddRefs(mGlobal)); + NS_ENSURE_SUCCESS(rv, false); + + + JS::Rooted<JSObject*> global(cx, mGlobal->GetJSObject()); + NS_ENSURE_TRUE(global, false); + + // Set the location information for the new global, so that tools like + // about:memory may use that information. + xpc::SetLocationForGlobal(global, aID); + + DidCreateGlobal(); + return true; +} + +void +nsMessageManagerScriptExecutor::MarkScopesForCC() +{ + for (uint32_t i = 0; i < mAnonymousGlobalScopes.Length(); ++i) { + mAnonymousGlobalScopes[i].exposeToActiveJS(); + } +} + +NS_IMPL_ISUPPORTS(nsScriptCacheCleaner, nsIObserver) + +nsFrameMessageManager* nsFrameMessageManager::sChildProcessManager = nullptr; +nsFrameMessageManager* nsFrameMessageManager::sParentProcessManager = nullptr; +nsFrameMessageManager* nsFrameMessageManager::sSameProcessParentManager = nullptr; + +class nsAsyncMessageToSameProcessChild : public nsSameProcessAsyncMessageBase, + public Runnable +{ +public: + nsAsyncMessageToSameProcessChild(JS::RootingContext* aRootingCx, + JS::Handle<JSObject*> aCpows) + : nsSameProcessAsyncMessageBase(aRootingCx, aCpows) + { } + NS_IMETHOD Run() override + { + nsFrameMessageManager* ppm = nsFrameMessageManager::GetChildProcessManager(); + ReceiveMessage(static_cast<nsIContentFrameMessageManager*>(ppm), nullptr, ppm); + return NS_OK; + } +}; + + +/** + * Send messages to an imaginary child process in a single-process scenario. + */ +class SameParentProcessMessageManagerCallback : public MessageManagerCallback +{ +public: + SameParentProcessMessageManagerCallback() + { + MOZ_COUNT_CTOR(SameParentProcessMessageManagerCallback); + } + virtual ~SameParentProcessMessageManagerCallback() + { + MOZ_COUNT_DTOR(SameParentProcessMessageManagerCallback); + } + + virtual bool DoLoadMessageManagerScript(const nsAString& aURL, + bool aRunInGlobalScope) override + { + ProcessGlobal* global = ProcessGlobal::Get(); + MOZ_ASSERT(!aRunInGlobalScope); + global->LoadScript(aURL); + return true; + } + + virtual nsresult DoSendAsyncMessage(JSContext* aCx, + const nsAString& aMessage, + StructuredCloneData& aData, + JS::Handle<JSObject *> aCpows, + nsIPrincipal* aPrincipal) override + { + JS::RootingContext* rcx = JS::RootingContext::get(aCx); + RefPtr<nsAsyncMessageToSameProcessChild> ev = + new nsAsyncMessageToSameProcessChild(rcx, aCpows); + + nsresult rv = ev->Init(aMessage, aData, aPrincipal); + if (NS_FAILED(rv)) { + return rv; + } + rv = NS_DispatchToCurrentThread(ev); + if (NS_FAILED(rv)) { + return rv; + } + return NS_OK; + } + + bool CheckPermission(const nsAString& aPermission) override + { + // In a single-process scenario, the child always has all capabilities. + return true; + } + + bool CheckManifestURL(const nsAString& aManifestURL) override + { + // In a single-process scenario, the child always has all capabilities. + return true; + } + + bool CheckAppHasPermission(const nsAString& aPermission) override + { + // In a single-process scenario, the child always has all capabilities. + return true; + } + + virtual bool CheckAppHasStatus(unsigned short aStatus) override + { + // In a single-process scenario, the child always has all capabilities. + return true; + } +}; + + +/** + * Send messages to the parent process. + */ +class ChildProcessMessageManagerCallback : public MessageManagerCallback +{ +public: + ChildProcessMessageManagerCallback() + { + MOZ_COUNT_CTOR(ChildProcessMessageManagerCallback); + } + virtual ~ChildProcessMessageManagerCallback() + { + MOZ_COUNT_DTOR(ChildProcessMessageManagerCallback); + } + + virtual bool DoSendBlockingMessage(JSContext* aCx, + const nsAString& aMessage, + StructuredCloneData& aData, + JS::Handle<JSObject *> aCpows, + nsIPrincipal* aPrincipal, + nsTArray<StructuredCloneData>* aRetVal, + bool aIsSync) override + { + mozilla::dom::ContentChild* cc = + mozilla::dom::ContentChild::GetSingleton(); + if (!cc) { + return true; + } + ClonedMessageData data; + if (!BuildClonedMessageDataForChild(cc, aData, data)) { + return false; + } + InfallibleTArray<mozilla::jsipc::CpowEntry> cpows; + if (aCpows && !cc->GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) { + return false; + } + if (aIsSync) { + return cc->SendSyncMessage(PromiseFlatString(aMessage), data, cpows, + IPC::Principal(aPrincipal), aRetVal); + } + return cc->SendRpcMessage(PromiseFlatString(aMessage), data, cpows, + IPC::Principal(aPrincipal), aRetVal); + } + + virtual nsresult DoSendAsyncMessage(JSContext* aCx, + const nsAString& aMessage, + StructuredCloneData& aData, + JS::Handle<JSObject *> aCpows, + nsIPrincipal* aPrincipal) override + { + mozilla::dom::ContentChild* cc = + mozilla::dom::ContentChild::GetSingleton(); + if (!cc) { + return NS_OK; + } + ClonedMessageData data; + if (!BuildClonedMessageDataForChild(cc, aData, data)) { + return NS_ERROR_DOM_DATA_CLONE_ERR; + } + InfallibleTArray<mozilla::jsipc::CpowEntry> cpows; + if (aCpows && !cc->GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) { + return NS_ERROR_UNEXPECTED; + } + if (!cc->SendAsyncMessage(PromiseFlatString(aMessage), cpows, + IPC::Principal(aPrincipal), data)) { + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; + } +}; + + +class nsAsyncMessageToSameProcessParent : public nsSameProcessAsyncMessageBase, + public SameProcessMessageQueue::Runnable +{ +public: + nsAsyncMessageToSameProcessParent(JS::RootingContext* aRootingCx, + JS::Handle<JSObject*> aCpows) + : nsSameProcessAsyncMessageBase(aRootingCx, aCpows) + { } + virtual nsresult HandleMessage() override + { + nsFrameMessageManager* ppm = nsFrameMessageManager::sSameProcessParentManager; + ReceiveMessage(static_cast<nsIContentFrameMessageManager*>(ppm), nullptr, ppm); + return NS_OK; + } +}; + +/** + * Send messages to the imaginary parent process in a single-process scenario. + */ +class SameChildProcessMessageManagerCallback : public MessageManagerCallback +{ +public: + SameChildProcessMessageManagerCallback() + { + MOZ_COUNT_CTOR(SameChildProcessMessageManagerCallback); + } + virtual ~SameChildProcessMessageManagerCallback() + { + MOZ_COUNT_DTOR(SameChildProcessMessageManagerCallback); + } + + virtual bool DoSendBlockingMessage(JSContext* aCx, + const nsAString& aMessage, + StructuredCloneData& aData, + JS::Handle<JSObject *> aCpows, + nsIPrincipal* aPrincipal, + nsTArray<StructuredCloneData>* aRetVal, + bool aIsSync) override + { + SameProcessMessageQueue* queue = SameProcessMessageQueue::Get(); + queue->Flush(); + + if (nsFrameMessageManager::sSameProcessParentManager) { + SameProcessCpowHolder cpows(JS::RootingContext::get(aCx), aCpows); + RefPtr<nsFrameMessageManager> ppm = nsFrameMessageManager::sSameProcessParentManager; + ppm->ReceiveMessage(static_cast<nsIContentFrameMessageManager*>(ppm.get()), nullptr, aMessage, + true, &aData, &cpows, aPrincipal, aRetVal); + } + return true; + } + + virtual nsresult DoSendAsyncMessage(JSContext* aCx, + const nsAString& aMessage, + StructuredCloneData& aData, + JS::Handle<JSObject *> aCpows, + nsIPrincipal* aPrincipal) override + { + SameProcessMessageQueue* queue = SameProcessMessageQueue::Get(); + JS::RootingContext* rcx = JS::RootingContext::get(aCx); + RefPtr<nsAsyncMessageToSameProcessParent> ev = + new nsAsyncMessageToSameProcessParent(rcx, aCpows); + nsresult rv = ev->Init(aMessage, aData, aPrincipal); + + if (NS_FAILED(rv)) { + return rv; + } + queue->Push(ev); + return NS_OK; + } + +}; + + +// This creates the global parent process message manager. +nsresult +NS_NewParentProcessMessageManager(nsIMessageBroadcaster** aResult) +{ + NS_ASSERTION(!nsFrameMessageManager::sParentProcessManager, + "Re-creating sParentProcessManager"); + RefPtr<nsFrameMessageManager> mm = new nsFrameMessageManager(nullptr, + nullptr, + MM_CHROME | MM_PROCESSMANAGER | MM_BROADCASTER); + nsFrameMessageManager::sParentProcessManager = mm; + nsFrameMessageManager::NewProcessMessageManager(false); // Create same process message manager. + mm.forget(aResult); + return NS_OK; +} + + +nsFrameMessageManager* +nsFrameMessageManager::NewProcessMessageManager(bool aIsRemote) +{ + if (!nsFrameMessageManager::sParentProcessManager) { + nsCOMPtr<nsIMessageBroadcaster> dummy = + do_GetService("@mozilla.org/parentprocessmessagemanager;1"); + } + + MOZ_ASSERT(nsFrameMessageManager::sParentProcessManager, + "parent process manager not created"); + nsFrameMessageManager* mm; + if (aIsRemote) { + // Callback is set in ContentParent::InitInternal so that the process has + // already started when we send pending scripts. + mm = new nsFrameMessageManager(nullptr, + nsFrameMessageManager::sParentProcessManager, + MM_CHROME | MM_PROCESSMANAGER); + } else { + mm = new nsFrameMessageManager(new SameParentProcessMessageManagerCallback(), + nsFrameMessageManager::sParentProcessManager, + MM_CHROME | MM_PROCESSMANAGER | MM_OWNSCALLBACK); + sSameProcessParentManager = mm; + } + return mm; +} + +nsresult +NS_NewChildProcessMessageManager(nsISyncMessageSender** aResult) +{ + NS_ASSERTION(!nsFrameMessageManager::GetChildProcessManager(), + "Re-creating sChildProcessManager"); + + MessageManagerCallback* cb; + if (XRE_IsParentProcess()) { + cb = new SameChildProcessMessageManagerCallback(); + } else { + cb = new ChildProcessMessageManagerCallback(); + RegisterStrongMemoryReporter(new MessageManagerReporter()); + } + nsFrameMessageManager* mm = new nsFrameMessageManager(cb, + nullptr, + MM_PROCESSMANAGER | MM_OWNSCALLBACK); + nsFrameMessageManager::SetChildProcessManager(mm); + RefPtr<ProcessGlobal> global = new ProcessGlobal(mm); + NS_ENSURE_TRUE(global->Init(), NS_ERROR_UNEXPECTED); + global.forget(aResult); + return NS_OK; +} + +bool +nsFrameMessageManager::MarkForCC() +{ + for (auto iter = mListeners.Iter(); !iter.Done(); iter.Next()) { + nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = iter.UserData(); + uint32_t count = listeners->Length(); + for (uint32_t i = 0; i < count; i++) { + nsCOMPtr<nsIMessageListener> strongListener = + listeners->ElementAt(i).mStrongListener; + if (strongListener) { + xpc_TryUnmarkWrappedGrayObject(strongListener); + } + } + } + + if (mRefCnt.IsPurple()) { + mRefCnt.RemovePurple(); + } + return true; +} + +nsSameProcessAsyncMessageBase::nsSameProcessAsyncMessageBase(JS::RootingContext* aRootingCx, + JS::Handle<JSObject*> aCpows) + : mRootingCx(aRootingCx) + , mCpows(aRootingCx, aCpows) +#ifdef DEBUG + , mCalledInit(false) +#endif +{ } + + +nsresult +nsSameProcessAsyncMessageBase::Init(const nsAString& aMessage, + StructuredCloneData& aData, + nsIPrincipal* aPrincipal) +{ + if (!mData.Copy(aData)) { + Telemetry::Accumulate(Telemetry::IPC_SAME_PROCESS_MESSAGE_COPY_OOM_KB, aData.DataLength()); + return NS_ERROR_OUT_OF_MEMORY; + } + + mMessage = aMessage; + mPrincipal = aPrincipal; +#ifdef DEBUG + mCalledInit = true; +#endif + + return NS_OK; +} + +void +nsSameProcessAsyncMessageBase::ReceiveMessage(nsISupports* aTarget, + nsIFrameLoader* aTargetFrameLoader, + nsFrameMessageManager* aManager) +{ + // Make sure that we have called Init() and it has succeeded. + MOZ_ASSERT(mCalledInit); + if (aManager) { + SameProcessCpowHolder cpows(mRootingCx, mCpows); + + RefPtr<nsFrameMessageManager> mm = aManager; + mm->ReceiveMessage(aTarget, aTargetFrameLoader, mMessage, false, &mData, + &cpows, mPrincipal, nullptr); + } +} |