fune/dom/base/nsFrameMessageManager.cpp
Simon Giesecke ad01a10a3b Bug 1634281 - Use nsTHashMap instead of nsDataHashtable. r=xpcom-reviewers,necko-reviewers,jgilbert,nika,valentin
Note that this patch only transforms the use of the nsDataHashtable type alias
to a directly equivalent use of nsTHashMap. It does not change the specification
of the hash key type to make use of the key class deduction that nsTHashMap
allows for in some cases. That can be done in a separate step, but requires more
attention.

Differential Revision: https://phabricator.services.mozilla.com/D106008
2021-03-10 10:47:47 +00:00

1622 lines
53 KiB
C++

/* -*- 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 "base/process_util.h"
#include "chrome/common/ipc_channel.h"
#include "js/CompilationAndEvaluation.h"
#include "js/JSON.h"
#include "js/SourceText.h"
#include "js/StructuredClone.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 "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 "nsFrameMessageManager.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;
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::BuildClonedMessageDataForParent(
ContentParent* aParent, StructuredCloneData& aData,
ClonedMessageData& aClonedData) {
return aData.BuildClonedMessageDataForParent(aParent, aClonedData);
}
bool MessageManagerCallback::BuildClonedMessageDataForChild(
ContentChild* aChild, StructuredCloneData& aData,
ClonedMessageData& aClonedData) {
return aData.BuildClonedMessageDataForChild(aChild, aClonedData);
}
void mozilla::dom::ipc::UnpackClonedMessageDataForParent(
const ClonedMessageData& aClonedData, StructuredCloneData& aData) {
aData.BorrowFromClonedMessageDataForParent(aClonedData);
}
void mozilla::dom::ipc::UnpackClonedMessageDataForChild(
const ClonedMessageData& aClonedData, StructuredCloneData& aData) {
aData.BorrowFromClonedMessageDataForChild(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 (auto iter = mListeners.Iter(); !iter.Done(); iter.Next()) {
nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = iter.UserData();
uint32_t count = listeners->Length();
for (uint32_t i = 0; i < count; i++) {
nsWeakPtr weakListener = listeners->ElementAt(i).mWeakListener;
if (weakListener) {
nsCOMPtr<nsISupports> otherCanonical = do_QueryReferent(weakListener);
MOZ_ASSERT((canonical == otherCanonical) == (weak == weakListener));
}
}
}
#endif
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::RootedValue v(aCx, aValue);
JS::RootedValue 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", false /* from private window */,
true /* from chrome context */);
console->LogMessage(error);
}
// Not clonable, try JSON
// XXX This is ugly but currently structured cloning doesn't handle
// properly cases when interface is implemented in JS and used
// as a dictionary.
nsAutoString json;
NS_ENSURE_TRUE(nsContentUtils::StringifyJSON(aCx, &v, json), false);
NS_ENSURE_TRUE(!json.IsEmpty(), false);
JS::Rooted<JS::Value> val(aCx, JS::NullValue());
NS_ENSURE_TRUE(JS_ParseJSON(aCx, static_cast<const char16_t*>(json.get()),
json.Length(), &val),
false);
aData.Write(aCx, val, rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
return false;
}
return true;
}
static bool sSendingSyncMessage = false;
static bool AllowMessage(size_t aDataLength, const nsAString& aMessageName) {
// A message includes more than structured clone data, so subtract
// 20KB to make it more likely that a message within this bound won't
// result in an overly large IPC message.
static const size_t kMaxMessageSize =
IPC::Channel::kMaximumMessageSize - 20 * 1024;
return aDataLength < kMaxMessageSize;
}
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);
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 (!AllowMessage(data.DataLength(), aMessageName)) {
aError.Throw(NS_ERROR_FAILURE);
return;
}
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;
}
#ifdef FUZZING
if (data.DataLength()) {
MessageManagerFuzzer::TryMutate(aCx, aMessageName, &data, aTransfers);
}
#endif
if (!AllowMessage(data.DataLength(), aMessageName)) {
aError.Throw(NS_ERROR_FAILURE);
return;
}
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);
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", 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::HandleValue 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::RootedValue 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::RootedObject global(aCx, xpc::PrivilegedJunkScope());
JSAutoRealm ar(aCx, global);
JS::RootedObject 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 (auto it = aMessageManager->mListeners.Iter(); !it.Done(); it.Next()) {
nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = it.UserData();
uint32_t listenerCount = listeners->Length();
if (listenerCount == 0) {
continue;
}
nsString key(it.Key());
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;
}
}
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;
}
JS::RootingContext* rcx = RootingCx();
JS::Rooted<JSScript*> script(rcx);
nsMessageManagerScriptHolder* holder = sCachedScripts->Get(aURL);
if (holder) {
script = holder->mScript;
} else {
TryCacheLoadAndCompileScript(aURL, aRunInUniqueScope, true, aMessageManager,
&script);
}
AutoEntryScript aes(aMessageManager, "message manager script load");
JSContext* cx = aes.cx();
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::RootedValue rval(cx);
JS::RootedVector<JSObject*> envChain(cx);
if (!envChain.append(aMessageManager)) {
return;
}
JS::CloneAndExecuteScript(cx, envChain, script, &rval);
}
}
}
void nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript(
const nsAString& aURL, bool aRunInUniqueScope, bool aShouldCache,
JS::Handle<JSObject*> aMessageManager,
JS::MutableHandle<JSScript*> aScriptp) {
nsCString url = NS_ConvertUTF16toUTF8(aURL);
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), url);
if (NS_FAILED(rv)) {
return;
}
bool hasFlags;
rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
&hasFlags);
if (NS_FAILED(rv) || !hasFlags) {
NS_WARNING("Will not load a frame script!");
return;
}
// 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 = !aShouldCache || IsProcessScoped();
// We don't cache data: scripts!
nsAutoCString scheme;
uri->GetScheme(scheme);
bool useScriptPreloader = aShouldCache && !scheme.EqualsLiteral("data");
// 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;
}
JSContext* cx = jsapi.cx();
JS::CompileOptions options(cx);
ScriptPreloader::FillCompileOptionsForCachedScript(options);
options.setFileAndLine(url.get(), 1);
options.setNonSyntacticScope(true);
JS::Rooted<JSScript*> script(cx);
script =
ScriptPreloader::GetChildSingleton().GetCachedScript(cx, options, url);
if (!script) {
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;
}
nsCOMPtr<nsIInputStream> input;
rv = channel->Open(getter_AddRefs(input));
NS_ENSURE_SUCCESS_VOID(rv);
nsString dataString;
char16_t* dataStringBuf = nullptr;
size_t dataStringLength = 0;
if (input) {
nsCString buffer;
uint64_t written;
if (NS_FAILED(NS_ReadInputStreamToString(input, buffer, -1, &written))) {
return;
}
uint32_t size = (uint32_t)std::min(written, (uint64_t)UINT32_MAX);
ScriptLoader::ConvertToUTF16(channel, (uint8_t*)buffer.get(), size,
u""_ns, nullptr, dataStringBuf,
dataStringLength);
}
if (!dataStringBuf || dataStringLength == 0) {
return;
}
// 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::UniqueTwoByteChars srcChars(dataStringBuf);
JS::SourceText<char16_t> srcBuf;
if (!srcBuf.init(cx, std::move(srcChars), dataStringLength)) {
return;
}
script = JS::Compile(cx, options, srcBuf);
if (!script) {
return;
}
}
MOZ_ASSERT(script);
aScriptp.set(script);
if (useScriptPreloader) {
ScriptPreloader::GetChildSingleton().NoteScript(url, url, script,
isRunOnce);
// If this script will only run once per process, only cache it in the
// preloader cache, not the session cache.
if (!isRunOnce) {
// Root the object also for caching.
auto* holder = new nsMessageManagerScriptHolder(cx, script);
sCachedScripts->InsertOrUpdate(aURL, holder);
}
}
}
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 (!BuildClonedMessageDataForChild(cc, 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 (!BuildClonedMessageDataForChild(cc, 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 (auto iter = mListeners.Iter(); !iter.Done(); iter.Next()) {
nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = iter.UserData();
uint32_t count = listeners->Length();
for (uint32_t i = 0; i < count; i++) {
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());
}
}