/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "base/basictypes.h" #include "nsFrameMessageManager.h" #include "AppProcessChecker.h" #include "ContentChild.h" #include "nsContentUtils.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 "nsIJSRuntimeService.h" #include "nsIDOMClassInfo.h" #include "xpcpublic.h" #include "mozilla/CycleCollectedJSRuntime.h" #include "mozilla/Preferences.h" #include "mozilla/dom/File.h" #include "mozilla/dom/nsIContentParent.h" #include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/StructuredCloneUtils.h" #include "mozilla/dom/ipc/BlobChild.h" #include "mozilla/dom/ipc/BlobParent.h" #include "mozilla/dom/DOMStringList.h" #include "mozilla/jsipc/CrossProcessObjectWrappers.h" #include "nsPrintfCString.h" #include "nsXULAppAPI.h" #include #ifdef ANDROID #include #endif #ifdef XP_WIN #include # if defined(SendMessage) # undef SendMessage # endif #endif using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::dom::ipc; static PLDHashOperator CycleCollectorTraverseListeners(const nsAString& aKey, nsAutoTObserverArray* aListeners, void* aCb) { nsCycleCollectionTraversalCallback* cb = static_cast (aCb); uint32_t count = aListeners->Length(); for (uint32_t i = 0; i < count; ++i) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "listeners[i] mStrongListener"); cb->NoteXPCOMChild(aListeners->ElementAt(i).mStrongListener.get()); } return PL_DHASH_NEXT; } NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameMessageManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameMessageManager) tmp->mListeners.EnumerateRead(CycleCollectorTraverseListeners, static_cast(&cb)); NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildManagers) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameMessageManager) tmp->mListeners.Clear(); for (int32_t i = tmp->mChildManagers.Count(); i > 0; --i) { static_cast(tmp->mChildManagers[i - 1])-> Disconnect(false); } NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildManagers) 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( static_cast(this)) : static_cast( static_cast(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) /* 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 struct BlobTraits { }; template <> struct BlobTraits { typedef mozilla::dom::BlobParent BlobType; typedef mozilla::dom::PBlobParent ProtocolType; typedef mozilla::dom::nsIContentParent ConcreteContentManagerType; }; template <> struct BlobTraits { typedef mozilla::dom::BlobChild BlobType; typedef mozilla::dom::PBlobChild ProtocolType; typedef mozilla::dom::nsIContentChild ConcreteContentManagerType; }; template struct DataBlobs { }; template<> struct DataBlobs { typedef BlobTraits::ProtocolType ProtocolType; static InfallibleTArray& Blobs(ClonedMessageData& aData) { return aData.blobsParent(); } static const InfallibleTArray& Blobs(const ClonedMessageData& aData) { return aData.blobsParent(); } }; template<> struct DataBlobs { typedef BlobTraits::ProtocolType ProtocolType; static InfallibleTArray& Blobs(ClonedMessageData& aData) { return aData.blobsChild(); } static const InfallibleTArray& Blobs(const ClonedMessageData& aData) { return aData.blobsChild(); } }; template static bool BuildClonedMessageData(typename BlobTraits::ConcreteContentManagerType* aManager, const StructuredCloneData& aData, ClonedMessageData& aClonedData) { SerializedStructuredCloneBuffer& buffer = aClonedData.data(); buffer.data = aData.mData; buffer.dataLength = aData.mDataLength; const nsTArray>& blobs = aData.mClosure.mBlobs; if (!blobs.IsEmpty()) { typedef typename BlobTraits::ProtocolType ProtocolType; InfallibleTArray& blobList = DataBlobs::Blobs(aClonedData); uint32_t length = blobs.Length(); blobList.SetCapacity(length); for (uint32_t i = 0; i < length; ++i) { typename BlobTraits::BlobType* protocolActor = aManager->GetOrCreateActorForBlob(blobs[i]); if (!protocolActor) { return false; } blobList.AppendElement(protocolActor); } } return true; } bool MessageManagerCallback::BuildClonedMessageDataForParent(nsIContentParent* aParent, const StructuredCloneData& aData, ClonedMessageData& aClonedData) { return BuildClonedMessageData(aParent, aData, aClonedData); } bool MessageManagerCallback::BuildClonedMessageDataForChild(nsIContentChild* aChild, const StructuredCloneData& aData, ClonedMessageData& aClonedData) { return BuildClonedMessageData(aChild, aData, aClonedData); } template static StructuredCloneData UnpackClonedMessageData(const ClonedMessageData& aData) { const SerializedStructuredCloneBuffer& buffer = aData.data(); typedef typename BlobTraits::ProtocolType ProtocolType; const InfallibleTArray& blobs = DataBlobs::Blobs(aData); StructuredCloneData cloneData; cloneData.mData = buffer.data; cloneData.mDataLength = buffer.dataLength; if (!blobs.IsEmpty()) { uint32_t length = blobs.Length(); cloneData.mClosure.mBlobs.SetCapacity(length); for (uint32_t i = 0; i < length; ++i) { auto* blob = static_cast::BlobType*>(blobs[i]); MOZ_ASSERT(blob); nsRefPtr blobImpl = blob->GetBlobImpl(); MOZ_ASSERT(blobImpl); // This object will be duplicated with a correct parent before being // exposed to JS. nsRefPtr domBlob = new File(nullptr, blobImpl); cloneData.mClosure.mBlobs.AppendElement(domBlob); } } return cloneData; } StructuredCloneData mozilla::dom::ipc::UnpackClonedMessageDataForParent(const ClonedMessageData& aData) { return UnpackClonedMessageData(aData); } StructuredCloneData mozilla::dom::ipc::UnpackClonedMessageDataForChild(const ClonedMessageData& aData) { return UnpackClonedMessageData(aData); } bool SameProcessCpowHolder::ToObject(JSContext* aCx, JS::MutableHandle aObjp) { if (!mObj) { return true; } aObjp.set(mObj); return JS_WrapObject(aCx, aObjp); } // nsIMessageListenerManager NS_IMETHODIMP nsFrameMessageManager::AddMessageListener(const nsAString& aMessage, nsIMessageListener* aListener) { nsAutoTObserverArray* listeners = mListeners.Get(aMessage); if (!listeners) { listeners = new nsAutoTObserverArray(); 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; return NS_OK; } NS_IMETHODIMP nsFrameMessageManager::RemoveMessageListener(const nsAString& aMessage, nsIMessageListener* aListener) { nsAutoTObserverArray* 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; } #ifdef DEBUG typedef struct { nsCOMPtr mCanonical; nsWeakPtr mWeak; } CanonicalCheckerParams; static PLDHashOperator CanonicalChecker(const nsAString& aKey, nsAutoTObserverArray* aListeners, void* aParams) { CanonicalCheckerParams* params = static_cast (aParams); uint32_t count = aListeners->Length(); for (uint32_t i = 0; i < count; i++) { if (!aListeners->ElementAt(i).mWeakListener) { continue; } nsCOMPtr otherCanonical = do_QueryReferent(aListeners->ElementAt(i).mWeakListener); MOZ_ASSERT((params->mCanonical == otherCanonical) == (params->mWeak == aListeners->ElementAt(i).mWeakListener)); } return PL_DHASH_NEXT; } #endif 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 canonical = do_QueryInterface(aListener); CanonicalCheckerParams params; params.mCanonical = canonical; params.mWeak = weak; mListeners.EnumerateRead(CanonicalChecker, (void*)¶ms); #endif nsAutoTObserverArray* listeners = mListeners.Get(aMessage); if (!listeners) { listeners = new nsAutoTObserverArray(); 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; return NS_OK; } NS_IMETHODIMP nsFrameMessageManager::RemoveWeakMessageListener(const nsAString& aMessage, nsIMessageListener* aListener) { nsWeakPtr weak = do_GetWeakReference(aListener); NS_ENSURE_TRUE(weak, NS_OK); nsAutoTObserverArray* 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::LoadFrameScript(const nsAString& aURL, bool aAllowDelayedLoad, bool aRunInGlobalScope) { if (aAllowDelayedLoad) { if (IsGlobal() || IsBroadcaster()) { // Cache for future windows or frames mPendingScripts.AppendElement(aURL); mPendingScriptsGlobalStates.AppendElement(aRunInGlobalScope); } else if (!mCallback) { // We're frame message manager, which isn't connected yet. mPendingScripts.AppendElement(aURL); mPendingScriptsGlobalStates.AppendElement(aRunInGlobalScope); return NS_OK; } } if (mCallback) { #ifdef DEBUG_smaug printf("Will load %s \n", NS_ConvertUTF16toUTF8(aURL).get()); #endif NS_ENSURE_TRUE(mCallback->DoLoadFrameScript(aURL, aRunInGlobalScope), NS_ERROR_FAILURE); } for (int32_t i = 0; i < mChildManagers.Count(); ++i) { nsRefPtr mm = static_cast(mChildManagers[i]); if (mm) { // Use false here, so that child managers don't cache the script, which // is already cached in the parent. mm->LoadFrameScript(aURL, false, aRunInGlobalScope); } } return NS_OK; } NS_IMETHODIMP nsFrameMessageManager::RemoveDelayedFrameScript(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::GetDelayedFrameScripts(JSContext* aCx, JS::MutableHandle 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 array(aCx, JS_NewArrayObject(aCx, mPendingScripts.Length())); NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY); JS::Rooted url(aCx); JS::Rooted 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_SetElement(aCx, array, i, pair), NS_ERROR_OUT_OF_MEMORY); } aList.setObject(*array); return NS_OK; } static bool JSONCreator(const char16_t* aBuf, uint32_t aLen, void* aData) { nsAString* result = static_cast(aData); result->Append(static_cast(aBuf), static_cast(aLen)); return true; } static bool GetParamsForMessage(JSContext* aCx, const JS::Value& aJSON, JSAutoStructuredCloneBuffer& aBuffer, StructuredCloneClosure& aClosure) { JS::Rooted v(aCx, aJSON); if (WriteStructuredClone(aCx, v, aBuffer, aClosure)) { return true; } JS_ClearPendingException(aCx); // 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, JS::NullPtr(), JS::NullHandleValue, JSONCreator, &json), false); NS_ENSURE_TRUE(!json.IsEmpty(), false); JS::Rooted val(aCx, JS::NullValue()); NS_ENSURE_TRUE(JS_ParseJSON(aCx, static_cast(json.get()), json.Length(), &val), false); return WriteStructuredClone(aCx, val, aBuffer, aClosure); } // nsISyncMessageSender static bool sSendingSyncMessage = false; NS_IMETHODIMP nsFrameMessageManager::SendSyncMessage(const nsAString& aMessageName, JS::Handle aJSON, JS::Handle aObjects, nsIPrincipal* aPrincipal, JSContext* aCx, uint8_t aArgc, JS::MutableHandle aRetval) { return SendMessage(aMessageName, aJSON, aObjects, aPrincipal, aCx, aArgc, aRetval, true); } NS_IMETHODIMP nsFrameMessageManager::SendRpcMessage(const nsAString& aMessageName, JS::Handle aJSON, JS::Handle aObjects, nsIPrincipal* aPrincipal, JSContext* aCx, uint8_t aArgc, JS::MutableHandle aRetval) { return SendMessage(aMessageName, aJSON, aObjects, aPrincipal, aCx, aArgc, aRetval, false); } nsresult nsFrameMessageManager::SendMessage(const nsAString& aMessageName, JS::Handle aJSON, JS::Handle aObjects, nsIPrincipal* aPrincipal, JSContext* aCx, uint8_t aArgc, JS::MutableHandle 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; JSAutoStructuredCloneBuffer buffer; if (aArgc >= 2 && !GetParamsForMessage(aCx, aJSON, buffer, data.mClosure)) { return NS_ERROR_DOM_DATA_CLONE_ERR; } data.mData = buffer.data(); data.mDataLength = buffer.nbytes(); JS::Rooted objects(aCx); if (aArgc >= 3 && aObjects.isObject()) { objects = &aObjects.toObject(); } InfallibleTArray retval; sSendingSyncMessage |= aIsSync; bool rv = mCallback->DoSendBlockingMessage(aCx, aMessageName, data, objects, aPrincipal, &retval, aIsSync); if (aIsSync) { sSendingSyncMessage = false; } if (!rv) { return NS_OK; } uint32_t len = retval.Length(); JS::Rooted dataArray(aCx, JS_NewArrayObject(aCx, len)); NS_ENSURE_TRUE(dataArray, NS_ERROR_OUT_OF_MEMORY); for (uint32_t i = 0; i < len; ++i) { if (retval[i].IsEmpty()) { continue; } JS::Rooted ret(aCx); if (!JS_ParseJSON(aCx, static_cast(retval[i].get()), retval[i].Length(), &ret)) { return NS_ERROR_UNEXPECTED; } NS_ENSURE_TRUE(JS_SetElement(aCx, dataArray, i, ret), NS_ERROR_OUT_OF_MEMORY); } aRetval.setObject(*dataArray); return NS_OK; } nsresult nsFrameMessageManager::DispatchAsyncMessageInternal(JSContext* aCx, const nsAString& aMessage, const StructuredCloneData& aData, JS::Handle aCpows, nsIPrincipal* aPrincipal) { if (mIsBroadcaster) { int32_t len = mChildManagers.Count(); for (int32_t i = 0; i < len; ++i) { static_cast(mChildManagers[i])-> DispatchAsyncMessageInternal(aCx, aMessage, aData, aCpows, aPrincipal); } return NS_OK; } NS_ENSURE_TRUE(mCallback, NS_ERROR_NOT_INITIALIZED); if (!mCallback->DoSendAsyncMessage(aCx, aMessage, aData, aCpows, aPrincipal)) { return NS_ERROR_FAILURE; } return NS_OK; } nsresult nsFrameMessageManager::DispatchAsyncMessage(const nsAString& aMessageName, const JS::Value& aJSON, const JS::Value& aObjects, nsIPrincipal* aPrincipal, JSContext* aCx, uint8_t aArgc) { StructuredCloneData data; JSAutoStructuredCloneBuffer buffer; if (aArgc >= 2 && !GetParamsForMessage(aCx, aJSON, buffer, data.mClosure)) { return NS_ERROR_DOM_DATA_CLONE_ERR; } JS::Rooted objects(aCx); if (aArgc >= 3 && aObjects.isObject()) { objects = &aObjects.toObject(); } data.mData = buffer.data(); data.mDataLength = buffer.nbytes(); return DispatchAsyncMessageInternal(aCx, aMessageName, data, objects, aPrincipal); } // nsIMessageSender NS_IMETHODIMP nsFrameMessageManager::SendAsyncMessage(const nsAString& aMessageName, JS::Handle aJSON, JS::Handle aObjects, nsIPrincipal* aPrincipal, JSContext* aCx, uint8_t aArgc) { return DispatchAsyncMessage(aMessageName, aJSON, aObjects, aPrincipal, aCx, aArgc); } // nsIMessageBroadcaster NS_IMETHODIMP nsFrameMessageManager::BroadcastAsyncMessage(const nsAString& aMessageName, JS::Handle aJSON, JS::Handle aObjects, JSContext* aCx, uint8_t aArgc) { return DispatchAsyncMessage(aMessageName, aJSON, aObjects, nullptr, aCx, aArgc); } NS_IMETHODIMP nsFrameMessageManager::GetChildCount(uint32_t* aChildCount) { *aChildCount = static_cast(mChildManagers.Count()); return NS_OK; } NS_IMETHODIMP nsFrameMessageManager::GetChildAt(uint32_t aIndex, nsIMessageListenerManager** aMM) { *aMM = nullptr; nsCOMPtr mm = do_QueryInterface(mChildManagers.SafeObjectAt(static_cast(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() { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsFrameMessageManager::GetContent(nsIDOMWindow** 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 NS_OK; } NS_IMETHODIMP nsFrameMessageManager::Atob(const nsAString& aAsciiString, nsAString& aBinaryData) { return NS_OK; } // 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; nsRefPtr mMM; }; // nsIMessageListener nsresult nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget, const nsAString& aMessage, bool aIsSync, const StructuredCloneData* aCloneData, mozilla::jsipc::CpowHolder* aCpows, nsIPrincipal* aPrincipal, InfallibleTArray* aJSONRetVal) { nsAutoTObserverArray* listeners = mListeners.Get(aMessage); if (listeners) { MMListenerRemover lr(this); nsAutoTObserverArray::EndLimitedIterator iter(*listeners); while(iter.HasMore()) { nsMessageListenerInfo& listener = iter.GetNext(); // Remove mListeners[i] if it's an expired weak listener. nsCOMPtr weakListener; if (listener.mWeakListener) { weakListener = do_QueryReferent(listener.mWeakListener); if (!weakListener) { listeners->RemoveElement(listener); continue; } } nsCOMPtr wrappedJS; if (weakListener) { wrappedJS = do_QueryInterface(weakListener); } else { wrappedJS = do_QueryInterface(listener.mStrongListener); } if (!wrappedJS) { continue; } if (!wrappedJS->GetJSObject()) { continue; } // Note - The ergonomics here will get a lot better with bug 971673: // // AutoEntryScript aes; // if (!aes.Init(wrappedJS->GetJSObject())) { // continue; // } // JSContext* cx = aes.cx(); nsIGlobalObject* nativeGlobal = xpc::NativeGlobal(js::GetGlobalForObjectCrossCompartment(wrappedJS->GetJSObject())); AutoEntryScript aes(nativeGlobal); aes.TakeOwnershipOfErrorReporting(); JSContext* cx = aes.cx(); JS::Rooted object(cx, wrappedJS->GetJSObject()); // The parameter for the listener function. JS::Rooted param(cx, JS_NewPlainObject(cx)); NS_ENSURE_TRUE(param, NS_ERROR_OUT_OF_MEMORY); JS::Rooted targetv(cx); js::AssertSameCompartment(cx, object); nsresult rv = nsContentUtils::WrapNative(cx, aTarget, &targetv); NS_ENSURE_SUCCESS(rv, rv); JS::Rooted 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 cpowsv(cx, JS::ObjectValue(*cpows)); JS::Rooted json(cx, JS::NullValue()); if (aCloneData && aCloneData->mDataLength && !ReadStructuredClone(cx, *aCloneData, &json)) { JS_ClearPendingException(cx); return NS_OK; } JS::Rooted jsMessage(cx, JS_NewUCStringCopyN(cx, static_cast(aMessage.BeginReading()), aMessage.Length())); NS_ENSURE_TRUE(jsMessage, NS_ERROR_OUT_OF_MEMORY); JS::Rooted 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); 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 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 thisValue(cx, JS::UndefinedValue()); JS::Rooted 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 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 rval(cx, JS::UndefinedValue()); JS::Rooted argv(cx, JS::ObjectValue(*param)); { JS::Rooted 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 (aJSONRetVal) { nsString json; if (!JS_Stringify(cx, &rval, JS::NullPtr(), JS::NullHandleValue, JSONCreator, &json)) { continue; } aJSONRetVal->AppendElement(json); } } } } nsRefPtr kungfuDeathGrip = mParentManager; return mParentManager ? mParentManager->ReceiveMessage(aTarget, aMessage, aIsSync, aCloneData, aCpows, aPrincipal, aJSONRetVal) : NS_OK; } void nsFrameMessageManager::AddChildManager(nsFrameMessageManager* aManager) { mChildManagers.AppendObject(aManager); nsRefPtr kungfuDeathGrip = this; nsRefPtr 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::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::Disconnect(bool aRemoveFromParent) { if (!mDisconnected) { nsCOMPtr 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; mCallback = nullptr; mOwnedCallback = nullptr; if (!mHandlingMessage) { mListeners.Clear(); } } namespace { struct MessageManagerReferentCount { MessageManagerReferentCount() : mStrong(0), mWeakAlive(0), mWeakDead(0) {} size_t mStrong; size_t mWeakAlive; size_t mWeakDead; nsTArray mSuspectMessages; nsDataHashtable mMessageCounter; }; } // anonymous namespace namespace mozilla { namespace dom { class MessageManagerReporter MOZ_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) static PLDHashOperator CollectMessageListenerData(const nsAString& aKey, nsAutoTObserverArray* aListeners, void* aData) { MessageManagerReferentCount* referentCount = static_cast(aData); uint32_t listenerCount = aListeners->Length(); if (!listenerCount) { return PL_DHASH_NEXT; } nsString key(aKey); uint32_t oldCount = 0; referentCount->mMessageCounter.Get(key, &oldCount); uint32_t currentCount = oldCount + listenerCount; referentCount->mMessageCounter.Put(key, currentCount); // Keep track of messages that have a suspiciously large // number of referents (symptom of leak). if (currentCount == MessageManagerReporter::kSuspectReferentCount) { referentCount->mSuspectMessages.AppendElement(key); } for (uint32_t i = 0; i < listenerCount; ++i) { const nsMessageListenerInfo& listenerInfo = aListeners->ElementAt(i); if (listenerInfo.mWeakListener) { nsCOMPtr referent = do_QueryReferent(listenerInfo.mWeakListener); if (referent) { referentCount->mWeakAlive++; } else { referentCount->mWeakDead++; } } else { referentCount->mStrong++; } } return PL_DHASH_NEXT; } void MessageManagerReporter::CountReferents(nsFrameMessageManager* aMessageManager, MessageManagerReferentCount* aReferentCount) { aMessageManager->mListeners.EnumerateRead(CollectMessageListenerData, aReferentCount); // 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) { nsRefPtr mm = static_cast(aMessageManager->mChildManagers[i]); CountReferents(mm, aReferentCount); } } static nsresult ReportReferentCount(const char* aManagerType, const MessageManagerReferentCount& aReferentCount, nsIMemoryReporterCallback* aCb, nsISupports* aClosure) { #define REPORT(_path, _amount, _desc) \ do { \ nsresult rv; \ rv = aCb->Callback(EmptyCString(), _path, \ nsIMemoryReporter::KIND_OTHER, \ nsIMemoryReporter::UNITS_COUNT, _amount, \ _desc, aClosure); \ NS_ENSURE_SUCCESS(rv, rv); \ } 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 return NS_OK; } NS_IMETHODIMP MessageManagerReporter::CollectReports(nsIMemoryReporterCallback* aCb, nsISupports* aClosure, bool aAnonymize) { nsresult rv; if (XRE_GetProcessType() == GeckoProcessType_Default) { nsCOMPtr globalmm = do_GetService("@mozilla.org/globalmessagemanager;1"); if (globalmm) { nsRefPtr mm = static_cast(globalmm.get()); MessageManagerReferentCount count; CountReferents(mm, &count); rv = ReportReferentCount("global-manager", count, aCb, aClosure); NS_ENSURE_SUCCESS(rv, rv); } } if (nsFrameMessageManager::sParentProcessManager) { MessageManagerReferentCount count; CountReferents(nsFrameMessageManager::sParentProcessManager, &count); rv = ReportReferentCount("parent-process-manager", count, aCb, aClosure); NS_ENSURE_SUCCESS(rv, rv); } if (nsFrameMessageManager::sChildProcessManager) { MessageManagerReferentCount count; CountReferents(nsFrameMessageManager::sChildProcessManager, &count); rv = ReportReferentCount("child-process-manager", count, aCb, aClosure); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } } // namespace dom } // namespace mozilla nsresult NS_NewGlobalMessageManager(nsIMessageBroadcaster** aResult) { NS_ENSURE_TRUE(XRE_GetProcessType() == GeckoProcessType_Default, NS_ERROR_NOT_AVAILABLE); nsFrameMessageManager* mm = new nsFrameMessageManager(nullptr, nullptr, MM_CHROME | MM_GLOBAL | MM_BROADCASTER); RegisterStrongMemoryReporter(new MessageManagerReporter()); return CallQueryInterface(mm, aResult); } nsDataHashtable* nsFrameScriptExecutor::sCachedScripts = nullptr; nsScriptCacheCleaner* nsFrameScriptExecutor::sScriptCacheCleaner = nullptr; void nsFrameScriptExecutor::DidCreateGlobal() { NS_ASSERTION(mGlobal, "Should have mGlobal!"); if (!sCachedScripts) { sCachedScripts = new nsDataHashtable; nsRefPtr scriptCacheCleaner = new nsScriptCacheCleaner(); scriptCacheCleaner.forget(&sScriptCacheCleaner); } } static PLDHashOperator RemoveCachedScriptEntry(const nsAString& aKey, nsFrameScriptObjectExecutorHolder*& aData, void* aUserArg) { delete aData; return PL_DHASH_REMOVE; } // static void nsFrameScriptExecutor::Shutdown() { if (sCachedScripts) { AutoSafeJSContext cx; NS_ASSERTION(sCachedScripts != nullptr, "Need cached scripts"); sCachedScripts->Enumerate(RemoveCachedScriptEntry, nullptr); delete sCachedScripts; sCachedScripts = nullptr; nsRefPtr scriptCacheCleaner; scriptCacheCleaner.swap(sScriptCacheCleaner); } } void nsFrameScriptExecutor::LoadFrameScriptInternal(const nsAString& aURL, bool aRunInGlobalScope) { if (!mGlobal || !sCachedScripts) { return; } JSRuntime* rt = CycleCollectedJSRuntime::Get()->Runtime(); JS::Rooted script(rt); nsFrameScriptObjectExecutorHolder* 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 global(rt, mGlobal->GetJSObject()); if (global) { AutoEntryScript aes(xpc::NativeGlobal(global)); aes.TakeOwnershipOfErrorReporting(); JSContext* cx = aes.cx(); if (script) { if (aRunInGlobalScope) { JS::CloneAndExecuteScript(cx, global, script); } else { JS::Rooted scope(cx); bool ok = js::ExecuteInGlobalAndReturnScope(cx, global, script, &scope); if (ok) { // Force the scope to stay alive. mAnonymousGlobalScopes.AppendElement(scope); } } } } } void nsFrameScriptExecutor::TryCacheLoadAndCompileScript(const nsAString& aURL, bool aRunInGlobalScope, bool aShouldCache, JS::MutableHandle aScriptp) { nsCString url = NS_ConvertUTF16toUTF8(aURL); nsCOMPtr 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 channel; NS_NewChannel(getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(), nsILoadInfo::SEC_NORMAL, nsIContentPolicy::TYPE_OTHER); if (!channel) { return; } nsCOMPtr input; channel->Open(getter_AddRefs(input)); 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) { AutoSafeJSContext cx; // Compile the script in the compilation scope instead of the current global // to avoid keeping the current compartment alive. JS::Rooted global(cx, xpc::CompilationScope()); JSAutoCompartment ac(cx, global); JS::CompileOptions options(cx, JSVERSION_LATEST); options.setFileAndLine(url.get(), 1); options.setNoScriptRval(true); JS::Rooted script(cx); if (aRunInGlobalScope) { if (!JS::Compile(cx, JS::NullPtr(), options, srcBuf, &script)) { return; } } else { // We can't clone compile-and-go scripts. options.setCompileAndGo(false); if (!JS::Compile(cx, JS::NullPtr(), options, srcBuf, &script)) { return; } } aScriptp.set(script); nsAutoCString scheme; uri->GetScheme(scheme); // We don't cache data: scripts! if (aShouldCache && !scheme.EqualsLiteral("data")) { nsFrameScriptObjectExecutorHolder* holder; // Root the object also for caching. if (script) { holder = new nsFrameScriptObjectExecutorHolder(cx, script, aRunInGlobalScope); } sCachedScripts->Put(aURL, holder); } } } void nsFrameScriptExecutor::TryCacheLoadAndCompileScript(const nsAString& aURL, bool aRunInGlobalScope) { AutoSafeJSContext cx; JS::Rooted script(cx); TryCacheLoadAndCompileScript(aURL, aRunInGlobalScope, true, &script); } bool nsFrameScriptExecutor::InitTabChildGlobalInternal(nsISupports* aScope, const nsACString& aID) { nsCOMPtr runtimeSvc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1"); NS_ENSURE_TRUE(runtimeSvc, false); JSRuntime* rt = nullptr; runtimeSvc->GetRuntime(&rt); NS_ENSURE_TRUE(rt, false); 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.setZone(JS::SystemZone) .setVersion(JSVERSION_LATEST); nsresult rv = xpc->InitClassesWithNewWrappedGlobal(cx, aScope, mPrincipal, flags, options, getter_AddRefs(mGlobal)); NS_ENSURE_SUCCESS(rv, false); JS::Rooted 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; } NS_IMPL_ISUPPORTS(nsScriptCacheCleaner, nsIObserver) nsFrameMessageManager* nsFrameMessageManager::sChildProcessManager = nullptr; nsFrameMessageManager* nsFrameMessageManager::sParentProcessManager = nullptr; nsFrameMessageManager* nsFrameMessageManager::sSameProcessParentManager = nullptr; nsTArray >* nsFrameMessageManager::sPendingSameProcessAsyncMessages = nullptr; class nsAsyncMessageToSameProcessChild : public nsSameProcessAsyncMessageBase, public nsRunnable { public: nsAsyncMessageToSameProcessChild(JSContext* aCx, const nsAString& aMessage, const StructuredCloneData& aData, JS::Handle aCpows, nsIPrincipal* aPrincipal) : nsSameProcessAsyncMessageBase(aCx, aMessage, aData, aCpows, aPrincipal) { } NS_IMETHOD Run() { nsFrameMessageManager* ppm = nsFrameMessageManager::sChildProcessManager; ReceiveMessage(static_cast(ppm), 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 DoSendAsyncMessage(JSContext* aCx, const nsAString& aMessage, const StructuredCloneData& aData, JS::Handle aCpows, nsIPrincipal* aPrincipal) { nsRefPtr ev = new nsAsyncMessageToSameProcessChild(aCx, aMessage, aData, aCpows, aPrincipal); NS_DispatchToCurrentThread(ev); return true; } bool CheckPermission(const nsAString& aPermission) { // In a single-process scenario, the child always has all capabilities. return true; } bool CheckManifestURL(const nsAString& aManifestURL) { // In a single-process scenario, the child always has all capabilities. return true; } bool CheckAppHasPermission(const nsAString& aPermission) { // In a single-process scenario, the child always has all capabilities. return true; } virtual bool CheckAppHasStatus(unsigned short aStatus) { // 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, const mozilla::dom::StructuredCloneData& aData, JS::Handle aCpows, nsIPrincipal* aPrincipal, InfallibleTArray* aJSONRetVal, bool aIsSync) MOZ_OVERRIDE { mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton(); if (!cc) { return true; } ClonedMessageData data; if (!BuildClonedMessageDataForChild(cc, aData, data)) { return false; } InfallibleTArray cpows; if (aCpows && !cc->GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) { return false; } if (aIsSync) { return cc->SendSyncMessage(PromiseFlatString(aMessage), data, cpows, IPC::Principal(aPrincipal), aJSONRetVal); } return cc->SendRpcMessage(PromiseFlatString(aMessage), data, cpows, IPC::Principal(aPrincipal), aJSONRetVal); } virtual bool DoSendAsyncMessage(JSContext* aCx, const nsAString& aMessage, const mozilla::dom::StructuredCloneData& aData, JS::Handle aCpows, nsIPrincipal* aPrincipal) MOZ_OVERRIDE { mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton(); if (!cc) { return true; } ClonedMessageData data; if (!BuildClonedMessageDataForChild(cc, aData, data)) { return false; } InfallibleTArray cpows; if (aCpows && !cc->GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) { return false; } return cc->SendAsyncMessage(PromiseFlatString(aMessage), data, cpows, IPC::Principal(aPrincipal)); } }; class nsAsyncMessageToSameProcessParent : public nsSameProcessAsyncMessageBase, public nsRunnable { public: nsAsyncMessageToSameProcessParent(JSContext* aCx, const nsAString& aMessage, const StructuredCloneData& aData, JS::Handle aCpows, nsIPrincipal* aPrincipal) : nsSameProcessAsyncMessageBase(aCx, aMessage, aData, aCpows, aPrincipal) , mDelivered(false) { } NS_IMETHOD Run() { if (nsFrameMessageManager::sPendingSameProcessAsyncMessages) { nsFrameMessageManager::sPendingSameProcessAsyncMessages->RemoveElement(this); } if (!mDelivered) { mDelivered = true; nsFrameMessageManager* ppm = nsFrameMessageManager::sSameProcessParentManager; ReceiveMessage(static_cast(ppm), ppm); } return NS_OK; } private: bool mDelivered; }; /** * 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, const mozilla::dom::StructuredCloneData& aData, JS::Handle aCpows, nsIPrincipal* aPrincipal, InfallibleTArray* aJSONRetVal, bool aIsSync) MOZ_OVERRIDE { nsTArray > asyncMessages; if (nsFrameMessageManager::sPendingSameProcessAsyncMessages) { asyncMessages.SwapElements(*nsFrameMessageManager::sPendingSameProcessAsyncMessages); uint32_t len = asyncMessages.Length(); for (uint32_t i = 0; i < len; ++i) { nsCOMPtr async = asyncMessages[i]; async->Run(); } } if (nsFrameMessageManager::sSameProcessParentManager) { SameProcessCpowHolder cpows(js::GetRuntime(aCx), aCpows); nsRefPtr ppm = nsFrameMessageManager::sSameProcessParentManager; ppm->ReceiveMessage(static_cast(ppm.get()), aMessage, true, &aData, &cpows, aPrincipal, aJSONRetVal); } return true; } virtual bool DoSendAsyncMessage(JSContext* aCx, const nsAString& aMessage, const mozilla::dom::StructuredCloneData& aData, JS::Handle aCpows, nsIPrincipal* aPrincipal) MOZ_OVERRIDE { if (!nsFrameMessageManager::sPendingSameProcessAsyncMessages) { nsFrameMessageManager::sPendingSameProcessAsyncMessages = new nsTArray >; } nsCOMPtr ev = new nsAsyncMessageToSameProcessParent(aCx, aMessage, aData, aCpows, aPrincipal); nsFrameMessageManager::sPendingSameProcessAsyncMessages->AppendElement(ev); NS_DispatchToCurrentThread(ev); return true; } }; // This creates the global parent process message manager. nsresult NS_NewParentProcessMessageManager(nsIMessageBroadcaster** aResult) { NS_ASSERTION(!nsFrameMessageManager::sParentProcessManager, "Re-creating sParentProcessManager"); nsRefPtr mm = new nsFrameMessageManager(nullptr, nullptr, MM_CHROME | MM_PROCESSMANAGER | MM_BROADCASTER); nsFrameMessageManager::sParentProcessManager = mm; nsFrameMessageManager::NewProcessMessageManager(nullptr); // Create same process message manager. return CallQueryInterface(mm, aResult); } nsFrameMessageManager* nsFrameMessageManager::NewProcessMessageManager(mozilla::dom::nsIContentParent* aProcess) { if (!nsFrameMessageManager::sParentProcessManager) { nsCOMPtr dummy = do_GetService("@mozilla.org/parentprocessmessagemanager;1"); } MOZ_ASSERT(nsFrameMessageManager::sParentProcessManager, "parent process manager not created"); nsFrameMessageManager* mm; if (aProcess) { mm = new nsFrameMessageManager(aProcess, 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::sChildProcessManager, "Re-creating sChildProcessManager"); MessageManagerCallback* cb; if (XRE_GetProcessType() == GeckoProcessType_Default) { cb = new SameChildProcessMessageManagerCallback(); } else { cb = new ChildProcessMessageManagerCallback(); RegisterStrongMemoryReporter(new MessageManagerReporter()); } nsFrameMessageManager* mm = new nsFrameMessageManager(cb, nullptr, MM_PROCESSMANAGER | MM_OWNSCALLBACK); nsFrameMessageManager::sChildProcessManager = mm; return CallQueryInterface(mm, aResult); } static PLDHashOperator CycleCollectorMarkListeners(const nsAString& aKey, nsAutoTObserverArray* aListeners, void* aData) { uint32_t count = aListeners->Length(); for (uint32_t i = 0; i < count; i++) { if (aListeners->ElementAt(i).mStrongListener) { xpc_TryUnmarkWrappedGrayObject(aListeners->ElementAt(i).mStrongListener); } } return PL_DHASH_NEXT; } bool nsFrameMessageManager::MarkForCC() { mListeners.EnumerateRead(CycleCollectorMarkListeners, nullptr); if (mRefCnt.IsPurple()) { mRefCnt.RemovePurple(); } return true; } nsSameProcessAsyncMessageBase::nsSameProcessAsyncMessageBase(JSContext* aCx, const nsAString& aMessage, const StructuredCloneData& aData, JS::Handle aCpows, nsIPrincipal* aPrincipal) : mRuntime(js::GetRuntime(aCx)), mMessage(aMessage), mCpows(aCx, aCpows), mPrincipal(aPrincipal) { if (aData.mDataLength && !mData.copy(aData.mData, aData.mDataLength)) { NS_RUNTIMEABORT("OOM"); } mClosure = aData.mClosure; } void nsSameProcessAsyncMessageBase::ReceiveMessage(nsISupports* aTarget, nsFrameMessageManager* aManager) { if (aManager) { StructuredCloneData data; data.mData = mData.data(); data.mDataLength = mData.nbytes(); data.mClosure = mClosure; SameProcessCpowHolder cpows(mRuntime, mCpows); nsRefPtr mm = aManager; mm->ReceiveMessage(aTarget, mMessage, false, &data, &cpows, mPrincipal, nullptr); } }