summaryrefslogtreecommitdiffstats
path: root/dom/base/nsFrameMessageManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/nsFrameMessageManager.cpp')
-rw-r--r--dom/base/nsFrameMessageManager.cpp1657
1 files changed, 1657 insertions, 0 deletions
diff --git a/dom/base/nsFrameMessageManager.cpp b/dom/base/nsFrameMessageManager.cpp
new file mode 100644
index 0000000000..97a1ddedf3
--- /dev/null
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -0,0 +1,1657 @@
+/* -*- 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 "nsFrameMessageManager.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <new>
+#include <utility>
+#include "ContentChild.h"
+#include "ErrorList.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/Unused.h"
+#include "base/process_util.h"
+#include "chrome/common/ipc_channel.h"
+#include "js/CallAndConstruct.h" // JS::IsCallable, JS_CallFunctionValue
+#include "js/CompilationAndEvaluation.h"
+#include "js/CompileOptions.h"
+#include "js/experimental/JSStencil.h"
+#include "js/GCVector.h"
+#include "js/JSON.h"
+#include "js/PropertyAndElement.h" // JS_GetProperty
+#include "js/RootingAPI.h"
+#include "js/SourceText.h"
+#include "js/StructuredClone.h"
+#include "js/TypeDecls.h"
+#include "js/Wrapper.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ScriptPreloader.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryHistogramEnums.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/CallbackObject.h"
+#include "mozilla/dom/ChildProcessMessageManager.h"
+#include "mozilla/dom/ChromeMessageBroadcaster.h"
+#include "mozilla/dom/ContentProcessMessageManager.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/MessageBroadcaster.h"
+#include "mozilla/dom/MessageListenerManager.h"
+#include "mozilla/dom/MessageManagerBinding.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/ParentProcessMessageManager.h"
+#include "mozilla/dom/ProcessMessageManager.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/SameProcessMessageQueue.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/MessageManagerCallback.h"
+#include "mozilla/dom/ipc/SharedMap.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/scache/StartupCacheUtils.h"
+#include "nsASCIIMask.h"
+#include "nsBaseHashtable.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionNoteChild.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTHashMap.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsHashKeys.h"
+#include "nsIChannel.h"
+#include "nsIConsoleService.h"
+#include "nsIContentPolicy.h"
+#include "nsIInputStream.h"
+#include "nsILoadInfo.h"
+#include "nsIMemoryReporter.h"
+#include "nsIMessageManager.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIProtocolHandler.h"
+#include "nsIScriptError.h"
+#include "nsISupports.h"
+#include "nsISupportsUtils.h"
+#include "nsIURI.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIXPConnect.h"
+#include "nsJSUtils.h"
+#include "nsLiteralString.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsQueryObject.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsStringFlags.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsTLiteralString.h"
+#include "nsTObserverArray.h"
+#include "nsTPromiseFlatString.h"
+#include "nsTStringRepr.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "nscore.h"
+#include "xpcpublic.h"
+
+#ifdef XP_WIN
+# if defined(SendMessage)
+# undef SendMessage
+# endif
+#endif
+
+#ifdef FUZZING
+# include "MessageManagerFuzzer.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::ipc;
+
+struct FrameMessageMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("FrameMessage");
+ }
+ static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
+ const ProfilerString16View& aMessageName,
+ bool aIsSync) {
+ aWriter.StringProperty("name", NS_ConvertUTF16toUTF8(aMessageName));
+ aWriter.BoolProperty("sync", aIsSync);
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
+ schema.AddKeyLabelFormatSearchable(
+ "name", "Message Name", MS::Format::String, MS::Searchable::Searchable);
+ schema.AddKeyLabelFormat("sync", "Sync", MS::Format::String);
+ schema.SetTooltipLabel("FrameMessage - {marker.name}");
+ schema.SetTableLabel("{marker.name} - {marker.data.name}");
+ return schema;
+ }
+};
+
+#define CACHE_PREFIX(type) "mm/" type
+
+nsFrameMessageManager::nsFrameMessageManager(MessageManagerCallback* aCallback,
+ MessageManagerFlags aFlags)
+ : mChrome(aFlags & MessageManagerFlags::MM_CHROME),
+ mGlobal(aFlags & MessageManagerFlags::MM_GLOBAL),
+ mIsProcessManager(aFlags & MessageManagerFlags::MM_PROCESSMANAGER),
+ mIsBroadcaster(aFlags & MessageManagerFlags::MM_BROADCASTER),
+ mOwnsCallback(aFlags & MessageManagerFlags::MM_OWNSCALLBACK),
+ mHandlingMessage(false),
+ mClosed(false),
+ mDisconnected(false),
+ mCallback(aCallback) {
+ NS_ASSERTION(!mIsBroadcaster || !mCallback,
+ "Broadcasters cannot have callbacks!");
+ if (mOwnsCallback) {
+ mOwnedCallback = WrapUnique(aCallback);
+ }
+}
+
+nsFrameMessageManager::~nsFrameMessageManager() {
+ for (int32_t i = mChildManagers.Length(); i > 0; --i) {
+ 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;
+ }
+ }
+}
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ nsMessageListenerInfo& aField, const char* aName, uint32_t aFlags = 0) {
+ ImplCycleCollectionTraverse(aCallback, aField.mStrongListener, aName, aFlags);
+ ImplCycleCollectionTraverse(aCallback, aField.mWeakListener, aName, aFlags);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameMessageManager)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildManagers)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSharedData)
+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)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mListeners)
+ for (int32_t i = tmp->mChildManagers.Length(); i > 0; --i) {
+ tmp->mChildManagers[i - 1]->Disconnect(false);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildManagers)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSharedData)
+ tmp->mInitialProcessData.setNull();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameMessageManager)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+
+ /* Message managers in child process implement nsIMessageSender.
+ 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(nsIMessageSender,
+ !mChrome || !mIsBroadcaster)
+
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameMessageManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameMessageManager)
+
+void MessageManagerCallback::DoGetRemoteType(nsACString& aRemoteType,
+ ErrorResult& aError) const {
+ aRemoteType.Truncate();
+ mozilla::dom::ProcessMessageManager* parent = GetProcessMessageManager();
+ if (!parent) {
+ return;
+ }
+
+ parent->GetRemoteType(aRemoteType, aError);
+}
+
+bool MessageManagerCallback::BuildClonedMessageData(
+ StructuredCloneData& aData, ClonedMessageData& aClonedData) {
+ return aData.BuildClonedMessageData(aClonedData);
+}
+
+void mozilla::dom::ipc::UnpackClonedMessageData(
+ const ClonedMessageData& aClonedData, StructuredCloneData& aData) {
+ aData.BorrowFromClonedMessageData(aClonedData);
+}
+
+void nsFrameMessageManager::AddMessageListener(const nsAString& aMessageName,
+ MessageListener& aListener,
+ bool aListenWhenClosed,
+ ErrorResult& aError) {
+ auto* const listeners = mListeners.GetOrInsertNew(aMessageName);
+ uint32_t len = listeners->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ MessageListener* strongListener = listeners->ElementAt(i).mStrongListener;
+ if (strongListener && *strongListener == aListener) {
+ return;
+ }
+ }
+
+ nsMessageListenerInfo* entry = listeners->AppendElement();
+ entry->mStrongListener = &aListener;
+ entry->mListenWhenClosed = aListenWhenClosed;
+}
+
+void nsFrameMessageManager::RemoveMessageListener(const nsAString& aMessageName,
+ MessageListener& aListener,
+ ErrorResult& aError) {
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
+ mListeners.Get(aMessageName);
+ if (listeners) {
+ uint32_t len = listeners->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ MessageListener* strongListener = listeners->ElementAt(i).mStrongListener;
+ if (strongListener && *strongListener == aListener) {
+ listeners->RemoveElementAt(i);
+ return;
+ }
+ }
+ }
+}
+
+static already_AddRefed<nsISupports> ToXPCOMMessageListener(
+ MessageListener& aListener) {
+ return CallbackObjectHolder<mozilla::dom::MessageListener, nsISupports>(
+ &aListener)
+ .ToXPCOMCallback();
+}
+
+void nsFrameMessageManager::AddWeakMessageListener(
+ const nsAString& aMessageName, MessageListener& aListener,
+ ErrorResult& aError) {
+ nsCOMPtr<nsISupports> listener(ToXPCOMMessageListener(aListener));
+ nsWeakPtr weak = do_GetWeakReference(listener);
+ if (!weak) {
+ aError.Throw(NS_ERROR_NO_INTERFACE);
+ return;
+ }
+
+#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(listener);
+ for (const auto& entry : mListeners) {
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = entry.GetWeak();
+ 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
+
+ auto* const listeners = mListeners.GetOrInsertNew(aMessageName);
+ uint32_t len = listeners->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ if (listeners->ElementAt(i).mWeakListener == weak) {
+ return;
+ }
+ }
+
+ nsMessageListenerInfo* entry = listeners->AppendElement();
+ entry->mWeakListener = weak;
+ entry->mListenWhenClosed = false;
+}
+
+void nsFrameMessageManager::RemoveWeakMessageListener(
+ const nsAString& aMessageName, MessageListener& aListener,
+ ErrorResult& aError) {
+ nsCOMPtr<nsISupports> listener(ToXPCOMMessageListener(aListener));
+ nsWeakPtr weak = do_GetWeakReference(listener);
+ if (!weak) {
+ aError.Throw(NS_ERROR_NO_INTERFACE);
+ return;
+ }
+
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
+ mListeners.Get(aMessageName);
+ if (!listeners) {
+ return;
+ }
+
+ uint32_t len = listeners->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ if (listeners->ElementAt(i).mWeakListener == weak) {
+ listeners->RemoveElementAt(i);
+ return;
+ }
+ }
+}
+
+void nsFrameMessageManager::LoadScript(const nsAString& aURL,
+ bool aAllowDelayedLoad,
+ bool aRunInGlobalScope,
+ ErrorResult& aError) {
+ 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
+ if (!mCallback->DoLoadMessageManagerScript(aURL, aRunInGlobalScope)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ }
+
+ for (uint32_t i = 0; i < mChildManagers.Length(); ++i) {
+ RefPtr<nsFrameMessageManager> mm = 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, IgnoreErrors());
+ }
+ }
+}
+
+void 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;
+ }
+ }
+}
+
+void nsFrameMessageManager::GetDelayedScripts(
+ JSContext* aCx, nsTArray<nsTArray<JS::Value>>& aList, ErrorResult& aError) {
+ // 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");
+ aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return;
+ }
+
+ aError.MightThrowJSException();
+
+ aList.SetCapacity(mPendingScripts.Length());
+ for (uint32_t i = 0; i < mPendingScripts.Length(); ++i) {
+ JS::Rooted<JS::Value> url(aCx);
+ if (!ToJSValue(aCx, mPendingScripts[i], &url)) {
+ aError.NoteJSContextException(aCx);
+ return;
+ }
+
+ nsTArray<JS::Value>* array = aList.AppendElement(2);
+ array->AppendElement(url);
+ array->AppendElement(JS::BooleanValue(mPendingScriptsGlobalStates[i]));
+ }
+}
+
+/* static */
+bool nsFrameMessageManager::GetParamsForMessage(JSContext* aCx,
+ const JS::Value& aValue,
+ const JS::Value& aTransfer,
+ StructuredCloneData& aData) {
+ // First try to use structured clone on the whole thing.
+ JS::Rooted<JS::Value> v(aCx, aValue);
+ JS::Rooted<JS::Value> t(aCx, aTransfer);
+ ErrorResult rv;
+ aData.Write(aCx, v, t, JS::CloneDataPolicy(), 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(
+ u"Sending message that cannot be cloned. Are "
+ "you trying to send an XPCOM object?"_ns,
+ filename, u""_ns, lineno, column, nsIScriptError::warningFlag,
+ "chrome javascript"_ns, false /* from private window */,
+ true /* from chrome context */);
+ console->LogMessage(error);
+ }
+
+ // Not clonable, try JSON
+ // Bug 1749037 - 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(
+ nsContentUtils::StringifyJSON(aCx, v, json, UndefinedIsNullStringLiteral),
+ 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;
+}
+
+static bool sSendingSyncMessage = false;
+
+void nsFrameMessageManager::SendSyncMessage(JSContext* aCx,
+ const nsAString& aMessageName,
+ JS::Handle<JS::Value> aObj,
+ nsTArray<JS::Value>& aResult,
+ ErrorResult& aError) {
+ NS_ASSERTION(!IsGlobal(), "Should not call SendSyncMessage in chrome");
+ NS_ASSERTION(!IsBroadcaster(), "Should not call SendSyncMessage in chrome");
+ NS_ASSERTION(!GetParentManager(),
+ "Should not have parent manager in content!");
+
+ AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+ "nsFrameMessageManager::SendMessage", OTHER, aMessageName);
+ profiler_add_marker("SendSyncMessage", geckoprofiler::category::IPC, {},
+ FrameMessageMarker{}, aMessageName, true);
+
+ if (sSendingSyncMessage) {
+ // No kind of blocking send should be issued on top of a sync message.
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ StructuredCloneData data;
+ if (!aObj.isUndefined() &&
+ !GetParamsForMessage(aCx, aObj, JS::UndefinedHandleValue, data)) {
+ aError.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+ return;
+ }
+
+#ifdef FUZZING
+ if (data.DataLength() > 0) {
+ MessageManagerFuzzer::TryMutate(aCx, aMessageName, &data,
+ JS::UndefinedHandleValue);
+ }
+#endif
+
+ if (!mCallback) {
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+
+ nsTArray<StructuredCloneData> retval;
+
+ TimeStamp start = TimeStamp::Now();
+ sSendingSyncMessage = true;
+ bool ok = mCallback->DoSendBlockingMessage(aMessageName, data, &retval);
+ sSendingSyncMessage = false;
+
+ uint32_t latencyMs = round((TimeStamp::Now() - start).ToMilliseconds());
+ if (latencyMs >= kMinTelemetrySyncMessageManagerLatencyMs) {
+ NS_ConvertUTF16toUTF8 messageName(aMessageName);
+ // NOTE: We need to strip digit characters from the message name in order to
+ // avoid a large number of buckets due to generated names from addons (such
+ // as "ublock:sb:{N}"). See bug 1348113 comment 10.
+ messageName.StripTaggedASCII(ASCIIMask::Mask0to9());
+ Telemetry::Accumulate(Telemetry::IPC_SYNC_MESSAGE_MANAGER_LATENCY_MS,
+ messageName, latencyMs);
+ }
+
+ if (!ok) {
+ return;
+ }
+
+ uint32_t len = retval.Length();
+ aResult.SetCapacity(len);
+ for (uint32_t i = 0; i < len; ++i) {
+ JS::Rooted<JS::Value> ret(aCx);
+ retval[i].Read(aCx, &ret, aError);
+ if (aError.Failed()) {
+ MOZ_ASSERT(false, "Unable to read structured clone in SendMessage");
+ return;
+ }
+ aResult.AppendElement(ret);
+ }
+}
+
+nsresult nsFrameMessageManager::DispatchAsyncMessageInternal(
+ JSContext* aCx, const nsAString& aMessage, StructuredCloneData& aData) {
+ if (mIsBroadcaster) {
+ uint32_t len = mChildManagers.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ mChildManagers[i]->DispatchAsyncMessageInternal(aCx, aMessage, aData);
+ }
+ return NS_OK;
+ }
+
+ if (!mCallback) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv = mCallback->DoSendAsyncMessage(aMessage, aData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+void nsFrameMessageManager::DispatchAsyncMessage(
+ JSContext* aCx, const nsAString& aMessageName, JS::Handle<JS::Value> aObj,
+ JS::Handle<JS::Value> aTransfers, ErrorResult& aError) {
+ StructuredCloneData data;
+ if (!aObj.isUndefined() &&
+ !GetParamsForMessage(aCx, aObj, aTransfers, data)) {
+ aError.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+ return;
+ }
+
+ profiler_add_marker("SendAsyncMessage", geckoprofiler::category::IPC, {},
+ FrameMessageMarker{}, aMessageName, false);
+
+#ifdef FUZZING
+ if (data.DataLength()) {
+ MessageManagerFuzzer::TryMutate(aCx, aMessageName, &data, aTransfers);
+ }
+#endif
+
+ aError = DispatchAsyncMessageInternal(aCx, aMessageName, data);
+}
+
+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;
+};
+
+void nsFrameMessageManager::ReceiveMessage(
+ nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader, bool aTargetClosed,
+ const nsAString& aMessage, bool aIsSync, StructuredCloneData* aCloneData,
+ nsTArray<StructuredCloneData>* aRetVal, ErrorResult& aError) {
+ MOZ_ASSERT(aTarget);
+ profiler_add_marker("ReceiveMessage", geckoprofiler::category::IPC, {},
+ FrameMessageMarker{}, aMessage, aIsSync);
+
+ 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) {
+ iter.Remove();
+ continue;
+ }
+ }
+
+ if (!listener.mListenWhenClosed && aTargetClosed) {
+ continue;
+ }
+
+ JS::RootingContext* rcx = RootingCx();
+ JS::Rooted<JSObject*> object(rcx);
+ JS::Rooted<JSObject*> objectGlobal(rcx);
+
+ RefPtr<MessageListener> webIDLListener;
+ if (!weakListener) {
+ webIDLListener = listener.mStrongListener;
+ object = webIDLListener->CallbackOrNull();
+ objectGlobal = webIDLListener->CallbackGlobalOrNull();
+ } else {
+ nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS =
+ do_QueryInterface(weakListener);
+ if (!wrappedJS) {
+ continue;
+ }
+
+ object = wrappedJS->GetJSObject();
+ objectGlobal = wrappedJS->GetJSObjectGlobal();
+ }
+
+ if (!object) {
+ continue;
+ }
+
+ AutoEntryScript aes(js::UncheckedUnwrap(object),
+ "message manager handler");
+ JSContext* cx = aes.cx();
+
+ // We passed the unwrapped object to AutoEntryScript so we now need to
+ // enter the realm of the global object that represents the realm of our
+ // callback.
+ JSAutoRealm ar(cx, objectGlobal);
+
+ RootedDictionary<ReceiveMessageArgument> argument(cx);
+
+ JS::Rooted<JS::Value> json(cx, JS::NullValue());
+ if (aCloneData && aCloneData->DataLength()) {
+ aCloneData->Read(cx, &json, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ aError.SuppressException();
+ JS_ClearPendingException(cx);
+ return;
+ }
+ }
+ argument.mData = json;
+ argument.mJson = json;
+
+ // Get cloned MessagePort from StructuredCloneData.
+ if (aCloneData) {
+ Sequence<OwningNonNull<MessagePort>> ports;
+ if (!aCloneData->TakeTransferredPortsAsSequence(ports)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ argument.mPorts.Construct(std::move(ports));
+ }
+
+ argument.mName = aMessage;
+ argument.mSync = aIsSync;
+ argument.mTarget = aTarget;
+ if (aTargetFrameLoader) {
+ argument.mTargetFrameLoader.Construct(*aTargetFrameLoader);
+ }
+
+ JS::Rooted<JS::Value> thisValue(cx, JS::UndefinedValue());
+
+ if (JS::IsCallable(object)) {
+ // A small hack to get 'this' value right on content side where
+ // messageManager is wrapped in BrowserChildMessageManager's global.
+ nsCOMPtr<nsISupports> defaultThisValue;
+ if (mChrome) {
+ defaultThisValue = do_QueryObject(this);
+ } else {
+ defaultThisValue = aTarget;
+ }
+ js::AssertSameCompartment(cx, object);
+ aError = nsContentUtils::WrapNative(cx, defaultThisValue, &thisValue);
+ if (aError.Failed()) {
+ return;
+ }
+ }
+
+ JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());
+ if (webIDLListener) {
+ webIDLListener->ReceiveMessage(thisValue, argument, &rval, aError);
+ if (aError.Failed()) {
+ // At this point the call to ReceiveMessage will have reported any
+ // exceptions (we kept the default of eReportExceptions). We suppress
+ // the failure in the ErrorResult and continue.
+ aError.SuppressException();
+ continue;
+ }
+ } else {
+ JS::Rooted<JS::Value> funval(cx);
+ if (JS::IsCallable(object)) {
+ // If the listener is a JS function:
+ funval.setObject(*object);
+ } else {
+ // If the listener is a JS object which has receiveMessage function:
+ if (!JS_GetProperty(cx, object, "receiveMessage", &funval) ||
+ !funval.isObject()) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Check if the object is even callable.
+ if (!JS::IsCallable(&funval.toObject())) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ thisValue.setObject(*object);
+ }
+
+ JS::Rooted<JS::Value> argv(cx);
+ if (!ToJSValue(cx, argument, &argv)) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ {
+ JS::Rooted<JSObject*> thisObject(cx, thisValue.toObjectOrNull());
+ js::AssertSameCompartment(cx, thisObject);
+ if (!JS_CallFunctionValue(cx, thisObject, funval,
+ JS::HandleValueArray(argv), &rval)) {
+ // Because the AutoEntryScript is inside the loop this continue will
+ // make us report any exceptions (after which we'll move on to the
+ // next listener).
+ continue;
+ }
+ }
+ }
+
+ if (aRetVal) {
+ StructuredCloneData* data = aRetVal->AppendElement();
+ data->InitScope(JS::StructuredCloneScope::DifferentProcess);
+ data->Write(cx, rval, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ aRetVal->RemoveLastElement();
+ nsString msg =
+ aMessage + nsLiteralString(
+ u": 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, u""_ns, u""_ns, 0, 0, nsIScriptError::warningFlag,
+ "chrome javascript"_ns, false /* from private window */,
+ true /* from chrome context */);
+ console->LogMessage(error);
+ }
+
+ JS_ClearPendingException(cx);
+ continue;
+ }
+ }
+ }
+ }
+
+ RefPtr<nsFrameMessageManager> kungFuDeathGrip = GetParentManager();
+ if (kungFuDeathGrip) {
+ kungFuDeathGrip->ReceiveMessage(aTarget, aTargetFrameLoader, aTargetClosed,
+ aMessage, aIsSync, aCloneData, aRetVal,
+ aError);
+ }
+}
+
+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.
+ nsFrameMessageManager* parentManager = aManager->GetParentManager();
+ if (parentManager) {
+ LoadPendingScripts(parentManager, aChildMM);
+ }
+
+ for (uint32_t i = 0; i < aManager->mPendingScripts.Length(); ++i) {
+ aChildMM->LoadScript(aManager->mPendingScripts[i], false,
+ aManager->mPendingScriptsGlobalStates[i],
+ IgnoreErrors());
+ }
+}
+
+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 = WrapUnique(aCallback);
+ }
+ }
+}
+
+void nsFrameMessageManager::Close() {
+ if (!mClosed) {
+ if (nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService()) {
+ obs->NotifyWhenScriptSafe(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) {
+ if (nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService()) {
+ obs->NotifyWhenScriptSafe(this, "message-manager-disconnect", nullptr);
+ }
+ }
+
+ ClearParentManager(aRemoveFromParent);
+
+ mDisconnected = true;
+ if (!mHandlingMessage) {
+ mListeners.Clear();
+ }
+}
+
+void nsFrameMessageManager::SetInitialProcessData(
+ JS::Handle<JS::Value> aInitialData) {
+ MOZ_ASSERT(!mChrome);
+ MOZ_ASSERT(mIsProcessManager);
+ MOZ_ASSERT(aInitialData.isObject());
+ mInitialProcessData = aInitialData;
+}
+
+void nsFrameMessageManager::GetInitialProcessData(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aInitialProcessData,
+ ErrorResult& aError) {
+ MOZ_ASSERT(mIsProcessManager);
+ MOZ_ASSERT_IF(mChrome, IsBroadcaster());
+
+ JS::Rooted<JS::Value> init(aCx, mInitialProcessData);
+ if (mChrome && init.isUndefined()) {
+ // We create the initial object in the junk scope. If we created it in a
+ // normal realm, that realm would leak until shutdown.
+ JS::Rooted<JSObject*> global(aCx, xpc::PrivilegedJunkScope());
+ JSAutoRealm ar(aCx, global);
+
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+ if (!obj) {
+ aError.NoteJSContextException(aCx);
+ return;
+ }
+
+ 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. Create it first through do_GetService and use the cached
+ // pointer in sParentProcessManager.
+ nsCOMPtr<nsISupports> ppmm =
+ do_GetService("@mozilla.org/parentprocessmessagemanager;1");
+ sParentProcessManager->GetInitialProcessData(aCx, &init, aError);
+ if (aError.Failed()) {
+ return;
+ }
+ mInitialProcessData = init;
+ }
+
+ if (!JS_WrapValue(aCx, &init)) {
+ aError.NoteJSContextException(aCx);
+ return;
+ }
+ aInitialProcessData.set(init);
+}
+
+WritableSharedMap* nsFrameMessageManager::SharedData() {
+ if (!mChrome || !mIsProcessManager) {
+ MOZ_ASSERT(false, "Should only call this binding method on ppmm");
+ return nullptr;
+ }
+ if (!mSharedData) {
+ mSharedData = new WritableSharedMap();
+ }
+ return mSharedData;
+}
+
+already_AddRefed<ProcessMessageManager>
+nsFrameMessageManager::GetProcessMessageManager(ErrorResult& aError) {
+ RefPtr<ProcessMessageManager> pmm;
+ if (mCallback) {
+ pmm = mCallback->GetProcessMessageManager();
+ }
+ return pmm.forget();
+}
+
+void nsFrameMessageManager::GetRemoteType(nsACString& aRemoteType,
+ ErrorResult& aError) const {
+ aRemoteType.Truncate();
+ if (mCallback) {
+ mCallback->DoGetRemoteType(aRemoteType, aError);
+ }
+}
+
+namespace {
+
+struct MessageManagerReferentCount {
+ MessageManagerReferentCount() : mStrong(0), mWeakAlive(0), mWeakDead(0) {}
+ size_t mStrong;
+ size_t mWeakAlive;
+ size_t mWeakDead;
+ nsTArray<nsString> mSuspectMessages;
+ nsTHashMap<nsStringHashKey, uint32_t> mMessageCounter;
+};
+
+} // namespace
+
+namespace mozilla::dom {
+
+class MessageManagerReporter final : public nsIMemoryReporter {
+ ~MessageManagerReporter() = default;
+
+ 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 (const auto& entry : aMessageManager->mListeners) {
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = entry.GetWeak();
+ uint32_t listenerCount = listeners->Length();
+ if (listenerCount == 0) {
+ continue;
+ }
+
+ nsString key(entry.GetKey());
+ const uint32_t currentCount =
+ (aReferentCount->mMessageCounter.LookupOrInsert(key, 0) +=
+ listenerCount);
+
+ // 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 = 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(""_ns, _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++) {
+ const uint32_t totalReferentCount =
+ aReferentCount.mMessageCounter.Get(aReferentCount.mSuspectMessages[i]);
+ 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
+}
+
+static StaticRefPtr<ChromeMessageBroadcaster> sGlobalMessageManager;
+
+NS_IMETHODIMP
+MessageManagerReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ if (XRE_IsParentProcess() && sGlobalMessageManager) {
+ MessageManagerReferentCount count;
+ CountReferents(sGlobalMessageManager, &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 mozilla::dom
+
+already_AddRefed<ChromeMessageBroadcaster>
+nsFrameMessageManager::GetGlobalMessageManager() {
+ RefPtr<ChromeMessageBroadcaster> mm;
+ if (sGlobalMessageManager) {
+ mm = sGlobalMessageManager;
+ } else {
+ sGlobalMessageManager = mm =
+ new ChromeMessageBroadcaster(MessageManagerFlags::MM_GLOBAL);
+ ClearOnShutdown(&sGlobalMessageManager);
+ RegisterStrongMemoryReporter(new MessageManagerReporter());
+ }
+ return mm.forget();
+}
+
+nsresult NS_NewGlobalMessageManager(nsISupports** aResult) {
+ *aResult = nsFrameMessageManager::GetGlobalMessageManager().take();
+ return NS_OK;
+}
+
+nsTHashMap<nsStringHashKey, nsMessageManagerScriptHolder*>*
+ nsMessageManagerScriptExecutor::sCachedScripts = nullptr;
+StaticRefPtr<nsScriptCacheCleaner>
+ nsMessageManagerScriptExecutor::sScriptCacheCleaner;
+
+void nsMessageManagerScriptExecutor::DidCreateScriptLoader() {
+ if (!sCachedScripts) {
+ sCachedScripts =
+ new nsTHashMap<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;
+ }
+}
+
+static void FillCompileOptionsForCachedStencil(JS::CompileOptions& aOptions) {
+ ScriptPreloader::FillCompileOptionsForCachedStencil(aOptions);
+ aOptions.setNonSyntacticScope(true);
+}
+
+void nsMessageManagerScriptExecutor::LoadScriptInternal(
+ JS::Handle<JSObject*> aMessageManager, const nsAString& aURL,
+ bool aRunInUniqueScope) {
+ AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
+ "nsMessageManagerScriptExecutor::LoadScriptInternal", OTHER, aURL);
+
+ if (!sCachedScripts) {
+ return;
+ }
+
+ RefPtr<JS::Stencil> stencil;
+ nsMessageManagerScriptHolder* holder = sCachedScripts->Get(aURL);
+ if (holder) {
+ stencil = holder->mStencil;
+ } else {
+ stencil =
+ TryCacheLoadAndCompileScript(aURL, aRunInUniqueScope, aMessageManager);
+ }
+
+ AutoEntryScript aes(aMessageManager, "message manager script load");
+ JSContext* cx = aes.cx();
+ if (stencil) {
+ JS::CompileOptions options(cx);
+ FillCompileOptionsForCachedStencil(options);
+ JS::InstantiateOptions instantiateOptions(options);
+ JS::Rooted<JSScript*> script(
+ cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
+
+ if (script) {
+ if (aRunInUniqueScope) {
+ JS::Rooted<JSObject*> scope(cx);
+ bool ok = js::ExecuteInFrameScriptEnvironment(cx, aMessageManager,
+ script, &scope);
+ if (ok) {
+ // Force the scope to stay alive.
+ mAnonymousGlobalScopes.AppendElement(scope);
+ }
+ } else {
+ JS::Rooted<JS::Value> rval(cx);
+ JS::RootedVector<JSObject*> envChain(cx);
+ if (!envChain.append(aMessageManager)) {
+ return;
+ }
+ Unused << JS_ExecuteScript(cx, envChain, script, &rval);
+ }
+ }
+ }
+}
+
+already_AddRefed<JS::Stencil>
+nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript(
+ const nsAString& aURL, bool aRunInUniqueScope,
+ JS::Handle<JSObject*> aMessageManager) {
+ nsCString url = NS_ConvertUTF16toUTF8(aURL);
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), url);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ 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 nullptr;
+ }
+
+ // If this script won't be cached, or there is only one of this type of
+ // message manager per process, treat this script as run-once. Run-once
+ // scripts can be compiled directly for the target global, and will be dropped
+ // from the preloader cache after they're executed and serialized.
+ //
+ // NOTE: This does not affect the JS::CompileOptions. We generate the same
+ // bytecode as though it were run multiple times. This is required for the
+ // batch decoding from ScriptPreloader to work.
+ bool isRunOnce = IsProcessScoped();
+
+ // We don't cache data: scripts!
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+ bool isCacheable = !scheme.EqualsLiteral("data");
+ bool useScriptPreloader = isCacheable;
+
+ // If the script will be reused in this session, compile it in the compilation
+ // scope instead of the current global to avoid keeping the current
+ // compartment alive.
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(isRunOnce ? aMessageManager : xpc::CompilationScope())) {
+ return nullptr;
+ }
+ JSContext* cx = jsapi.cx();
+
+ RefPtr<JS::Stencil> stencil;
+ if (useScriptPreloader) {
+ nsAutoCString cachePath;
+ rv = scache::PathifyURI(CACHE_PREFIX("script"), uri, cachePath);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ JS::DecodeOptions decodeOptions;
+ ScriptPreloader::FillDecodeOptionsForCachedStencil(decodeOptions);
+ stencil = ScriptPreloader::GetChildSingleton().GetCachedStencil(
+ cx, decodeOptions, cachePath);
+ }
+
+ if (!stencil) {
+ nsCOMPtr<nsIChannel> channel;
+ NS_NewChannel(getter_AddRefs(channel), uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT);
+
+ if (!channel) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIInputStream> input;
+ rv = channel->Open(getter_AddRefs(input));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ nsString dataString;
+ Utf8Unit* dataStringBuf = nullptr;
+ size_t dataStringLength = 0;
+ if (input) {
+ nsCString buffer;
+ uint64_t written;
+ if (NS_FAILED(NS_ReadInputStreamToString(input, buffer, -1, &written))) {
+ return nullptr;
+ }
+
+ uint32_t size = (uint32_t)std::min(written, (uint64_t)UINT32_MAX);
+ ScriptLoader::ConvertToUTF8(channel, (uint8_t*)buffer.get(), size, u""_ns,
+ nullptr, dataStringBuf, dataStringLength);
+ }
+
+ if (!dataStringBuf) {
+ return nullptr;
+ }
+
+ JS::CompileOptions options(cx);
+ FillCompileOptionsForCachedStencil(options);
+ options.setFileAndLine(url.get(), 1);
+
+ // If we are not encoding to the ScriptPreloader cache, we can now relax the
+ // compile options and use the JS syntax-parser for lower latency.
+ if (!useScriptPreloader || !ScriptPreloader::GetChildSingleton().Active()) {
+ options.setSourceIsLazy(false);
+ }
+
+ JS::SourceText<Utf8Unit> srcBuf;
+ if (!srcBuf.init(cx, dataStringBuf, dataStringLength,
+ JS::SourceOwnership::TakeOwnership)) {
+ return nullptr;
+ }
+
+ stencil = JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
+ if (!stencil) {
+ return nullptr;
+ }
+
+ if (isCacheable && !isRunOnce) {
+ // Store into our cache only when we compile it here.
+ auto* holder = new nsMessageManagerScriptHolder(stencil);
+ sCachedScripts->InsertOrUpdate(aURL, holder);
+ }
+
+#ifdef DEBUG
+ // The above shouldn't touch any options for instantiation.
+ JS::InstantiateOptions instantiateOptions(options);
+ instantiateOptions.assertDefault();
+#endif
+ }
+
+ MOZ_ASSERT(stencil);
+
+ if (useScriptPreloader) {
+ nsAutoCString cachePath;
+ rv = scache::PathifyURI(CACHE_PREFIX("script"), uri, cachePath);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ ScriptPreloader::GetChildSingleton().NoteStencil(url, cachePath, stencil,
+ isRunOnce);
+ }
+
+ return stencil.forget();
+}
+
+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);
+ }
+}
+
+void nsMessageManagerScriptExecutor::Unlink() {
+ ImplCycleCollectionUnlink(mAnonymousGlobalScopes);
+}
+
+bool nsMessageManagerScriptExecutor::Init() {
+ DidCreateScriptLoader();
+ return true;
+}
+
+void nsMessageManagerScriptExecutor::MarkScopesForCC() {
+ for (uint32_t i = 0; i < mAnonymousGlobalScopes.Length(); ++i) {
+ mAnonymousGlobalScopes[i].exposeToActiveJS();
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsScriptCacheCleaner, nsIObserver)
+
+ChildProcessMessageManager* nsFrameMessageManager::sChildProcessManager =
+ nullptr;
+ParentProcessMessageManager* nsFrameMessageManager::sParentProcessManager =
+ nullptr;
+nsFrameMessageManager* nsFrameMessageManager::sSameProcessParentManager =
+ nullptr;
+
+class nsAsyncMessageToSameProcessChild : public nsSameProcessAsyncMessageBase,
+ public Runnable {
+ public:
+ nsAsyncMessageToSameProcessChild()
+ : nsSameProcessAsyncMessageBase(),
+ mozilla::Runnable("nsAsyncMessageToSameProcessChild") {}
+ NS_IMETHOD Run() override {
+ nsFrameMessageManager* ppm =
+ nsFrameMessageManager::GetChildProcessManager();
+ ReceiveMessage(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);
+ }
+ ~SameParentProcessMessageManagerCallback() override {
+ MOZ_COUNT_DTOR(SameParentProcessMessageManagerCallback);
+ }
+
+ bool DoLoadMessageManagerScript(const nsAString& aURL,
+ bool aRunInGlobalScope) override {
+ auto* global = ContentProcessMessageManager::Get();
+ MOZ_ASSERT(!aRunInGlobalScope);
+ global->LoadScript(aURL);
+ return true;
+ }
+
+ nsresult DoSendAsyncMessage(const nsAString& aMessage,
+ StructuredCloneData& aData) override {
+ RefPtr<nsAsyncMessageToSameProcessChild> ev =
+ new nsAsyncMessageToSameProcessChild();
+
+ nsresult rv = ev->Init(aMessage, aData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = NS_DispatchToCurrentThread(ev);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+ }
+};
+
+/**
+ * Send messages to the parent process.
+ */
+class ChildProcessMessageManagerCallback : public MessageManagerCallback {
+ public:
+ ChildProcessMessageManagerCallback() {
+ MOZ_COUNT_CTOR(ChildProcessMessageManagerCallback);
+ }
+ ~ChildProcessMessageManagerCallback() override {
+ MOZ_COUNT_DTOR(ChildProcessMessageManagerCallback);
+ }
+
+ bool DoSendBlockingMessage(const nsAString& aMessage,
+ StructuredCloneData& aData,
+ nsTArray<StructuredCloneData>* aRetVal) override {
+ mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
+ if (!cc) {
+ return true;
+ }
+ ClonedMessageData data;
+ if (!BuildClonedMessageData(aData, data)) {
+ return false;
+ }
+ return cc->SendSyncMessage(PromiseFlatString(aMessage), data, aRetVal);
+ }
+
+ nsresult DoSendAsyncMessage(const nsAString& aMessage,
+ StructuredCloneData& aData) override {
+ mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
+ if (!cc) {
+ return NS_OK;
+ }
+ ClonedMessageData data;
+ if (!BuildClonedMessageData(aData, data)) {
+ return NS_ERROR_DOM_DATA_CLONE_ERR;
+ }
+ if (!cc->SendAsyncMessage(PromiseFlatString(aMessage), data)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+ }
+};
+
+class nsAsyncMessageToSameProcessParent
+ : public nsSameProcessAsyncMessageBase,
+ public SameProcessMessageQueue::Runnable {
+ public:
+ nsAsyncMessageToSameProcessParent() : nsSameProcessAsyncMessageBase() {}
+ nsresult HandleMessage() override {
+ nsFrameMessageManager* ppm =
+ nsFrameMessageManager::sSameProcessParentManager;
+ ReceiveMessage(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);
+ }
+ ~SameChildProcessMessageManagerCallback() override {
+ MOZ_COUNT_DTOR(SameChildProcessMessageManagerCallback);
+ }
+
+ bool DoSendBlockingMessage(const nsAString& aMessage,
+ StructuredCloneData& aData,
+ nsTArray<StructuredCloneData>* aRetVal) override {
+ SameProcessMessageQueue* queue = SameProcessMessageQueue::Get();
+ queue->Flush();
+
+ if (nsFrameMessageManager::sSameProcessParentManager) {
+ RefPtr<nsFrameMessageManager> ppm =
+ nsFrameMessageManager::sSameProcessParentManager;
+ ppm->ReceiveMessage(ppm, nullptr, aMessage, true, &aData, aRetVal,
+ IgnoreErrors());
+ }
+ return true;
+ }
+
+ nsresult DoSendAsyncMessage(const nsAString& aMessage,
+ StructuredCloneData& aData) override {
+ SameProcessMessageQueue* queue = SameProcessMessageQueue::Get();
+ RefPtr<nsAsyncMessageToSameProcessParent> ev =
+ new nsAsyncMessageToSameProcessParent();
+ nsresult rv = ev->Init(aMessage, aData);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ queue->Push(ev);
+ return NS_OK;
+ }
+};
+
+// This creates the global parent process message manager.
+nsresult NS_NewParentProcessMessageManager(nsISupports** aResult) {
+ NS_ASSERTION(!nsFrameMessageManager::sParentProcessManager,
+ "Re-creating sParentProcessManager");
+ RefPtr<ParentProcessMessageManager> mm = new ParentProcessMessageManager();
+ nsFrameMessageManager::sParentProcessManager = mm;
+ nsFrameMessageManager::NewProcessMessageManager(
+ false); // Create same process message manager.
+ mm.forget(aResult);
+ return NS_OK;
+}
+
+ProcessMessageManager* nsFrameMessageManager::NewProcessMessageManager(
+ bool aIsRemote) {
+ if (!nsFrameMessageManager::sParentProcessManager) {
+ nsCOMPtr<nsISupports> dummy =
+ do_GetService("@mozilla.org/parentprocessmessagemanager;1");
+ }
+
+ MOZ_ASSERT(nsFrameMessageManager::sParentProcessManager,
+ "parent process manager not created");
+ ProcessMessageManager* mm;
+ if (aIsRemote) {
+ // Callback is set in ContentParent::InitInternal so that the process has
+ // already started when we send pending scripts.
+ mm = new ProcessMessageManager(
+ nullptr, nsFrameMessageManager::sParentProcessManager);
+ } else {
+ mm =
+ new ProcessMessageManager(new SameParentProcessMessageManagerCallback(),
+ nsFrameMessageManager::sParentProcessManager,
+ MessageManagerFlags::MM_OWNSCALLBACK);
+ mm->SetOsPid(base::GetCurrentProcId());
+ sSameProcessParentManager = mm;
+ }
+ return mm;
+}
+
+nsresult NS_NewChildProcessMessageManager(nsISupports** aResult) {
+ NS_ASSERTION(!nsFrameMessageManager::GetChildProcessManager(),
+ "Re-creating sChildProcessManager");
+
+ MessageManagerCallback* cb;
+ if (XRE_IsParentProcess()) {
+ cb = new SameChildProcessMessageManagerCallback();
+ } else {
+ cb = new ChildProcessMessageManagerCallback();
+ RegisterStrongMemoryReporter(new MessageManagerReporter());
+ }
+ auto* mm = new ChildProcessMessageManager(cb);
+ nsFrameMessageManager::SetChildProcessManager(mm);
+ auto global = MakeRefPtr<ContentProcessMessageManager>(mm);
+ NS_ENSURE_TRUE(global->Init(), NS_ERROR_UNEXPECTED);
+ return CallQueryInterface(global, aResult);
+}
+
+void nsFrameMessageManager::MarkForCC() {
+ for (const auto& entry : mListeners) {
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = entry.GetWeak();
+ uint32_t count = listeners->Length();
+ for (uint32_t i = 0; i < count; i++) {
+ MessageListener* strongListener = listeners->ElementAt(i).mStrongListener;
+ if (strongListener) {
+ strongListener->MarkForCC();
+ }
+ }
+ }
+
+ if (mRefCnt.IsPurple()) {
+ mRefCnt.RemovePurple();
+ }
+}
+
+nsSameProcessAsyncMessageBase::nsSameProcessAsyncMessageBase()
+#ifdef DEBUG
+ : mCalledInit(false)
+#endif
+{
+}
+
+nsresult nsSameProcessAsyncMessageBase::Init(const nsAString& aMessage,
+ StructuredCloneData& aData) {
+ if (!mData.Copy(aData)) {
+ Telemetry::Accumulate(Telemetry::IPC_SAME_PROCESS_MESSAGE_COPY_OOM_KB,
+ aData.DataLength());
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mMessage = aMessage;
+#ifdef DEBUG
+ mCalledInit = true;
+#endif
+
+ return NS_OK;
+}
+
+void nsSameProcessAsyncMessageBase::ReceiveMessage(
+ nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader,
+ nsFrameMessageManager* aManager) {
+ // Make sure that we have called Init() and it has succeeded.
+ MOZ_ASSERT(mCalledInit);
+ if (aManager) {
+ RefPtr<nsFrameMessageManager> mm = aManager;
+ mm->ReceiveMessage(aTarget, aTargetFrameLoader, mMessage, false, &mData,
+ nullptr, IgnoreErrors());
+ }
+}