/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.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/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 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().Start(); size_t size = aData.Data().Size(); bool success; buffer.data = aData.Data().Borrow(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"); // 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; } 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->InitScope(JS::StructuredCloneScope::DifferentProcess); 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)) { 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); } }