fune/dom/base/nsFrameMessageManager.cpp
Kris Maglione 3020a8b0a8 Bug 1480244: Part 2 - Replace ExecuteInGlobalAndReturnScope with ExecuteInScopeChainAndReturnNewScope. r=tcampbell
This patch series replaces message manager globals with ordinary JS objects
which live in the shared JSM global. Once that happens,
ExecuteInGlobalAndReturnScope will no longer have useful behavior for them,
since the base global has none of the methods that they rely on, and it
provides no way to insert another plain object into the scope chain.

This patch changes the scope chain for frame scripts to instead look like:

 -+- Shared JSM global
  |
  +- LexicalEnvironment[this=global]
  |
  +- NonSyntacticVariablesObject
  |
  +- WithEnvironmentObject[target=messageManager]
  |
  +- LexicalEnvironment[this=messageManager]

Where lexical assignments end up on the lexical scope, and both qualified and
unqualified assignments wind up on the NSVO.

This has some slight behavioral differences from the previous model, in that
properties defined on the message manager can mask properties on the NSVO. But
those differences are minor, and probably not worth worrying about, since
frame scripts are being deprecated as part of the Fission project.

MozReview-Commit-ID: ACEOY2hExco

--HG--
extra : rebase_source : 384ccd422b4167e9a4fc550dd691fcc027553544
2018-08-10 13:54:23 -07:00

1817 lines
58 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 "base/basictypes.h"
#include "nsFrameMessageManager.h"
#include "ContentChild.h"
#include "GeckoProfiler.h"
#include "nsASCIIMask.h"
#include "nsContentUtils.h"
#include "nsError.h"
#include "nsIXPConnect.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "nsJSUtils.h"
#include "nsJSPrincipals.h"
#include "nsNetUtil.h"
#include "mozilla/dom/ScriptLoader.h"
#include "nsFrameLoader.h"
#include "nsIInputStream.h"
#include "nsIXULRuntime.h"
#include "nsIScriptError.h"
#include "nsIConsoleService.h"
#include "nsIMemoryReporter.h"
#include "nsIProtocolHandler.h"
#include "nsIScriptSecurityManager.h"
#include "xpcpublic.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/Preferences.h"
#include "mozilla/ScriptPreloader.h"
#include "mozilla/Telemetry.h"
#include "mozilla/dom/ChildProcessMessageManager.h"
#include "mozilla/dom/ChromeMessageBroadcaster.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/MessageManagerBinding.h"
#include "mozilla/dom/MessagePort.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ContentProcessMessageManager.h"
#include "mozilla/dom/ParentProcessMessageManager.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/ProcessMessageManager.h"
#include "mozilla/dom/ResolveSystemBinding.h"
#include "mozilla/dom/SameProcessMessageQueue.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/ipc/SharedMap.h"
#include "mozilla/dom/ipc/StructuredCloneData.h"
#include "mozilla/dom/DOMStringList.h"
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
#include "nsPrintfCString.h"
#include "nsXULAppAPI.h"
#include "nsQueryObject.h"
#include "xpcprivate.h"
#include <algorithm>
#include "chrome/common/ipc_channel.h" // for IPC::Channel::kMaximumMessageSize
#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 = 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(nsAString& aRemoteType,
ErrorResult& aError) const
{
aRemoteType.Truncate();
mozilla::dom::ProcessMessageManager* parent = GetProcessMessageManager();
if (!parent) {
return;
}
parent->GetRemoteType(aRemoteType, aError);
}
bool
MessageManagerCallback::BuildClonedMessageDataForParent(nsIContentParent* aParent,
StructuredCloneData& aData,
ClonedMessageData& aClonedData)
{
return aData.BuildClonedMessageDataForParent(aParent, aClonedData);
}
bool
MessageManagerCallback::BuildClonedMessageDataForChild(nsIContentChild* 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);
}
bool
SameProcessCpowHolder::ToObject(JSContext* aCx,
JS::MutableHandle<JSObject*> aObjp)
{
if (!mObj) {
return true;
}
aObjp.set(mObj);
return JS_WrapObject(aCx, aObjp);
}
void
nsFrameMessageManager::AddMessageListener(const nsAString& aMessageName,
MessageListener& aListener,
bool aListenWhenClosed,
ErrorResult& aError)
{
auto listeners = mListeners.LookupForAdd(aMessageName).OrInsert([]() {
return new nsAutoTObserverArray<nsMessageListenerInfo, 1>();
});
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 listeners = mListeners.LookupForAdd(aMessageName).OrInsert([]() {
return new nsAutoTObserverArray<nsMessageListenerInfo, 1>();
});
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
GetParamsForMessage(JSContext* aCx,
const JS::Value& aValue,
const JS::Value& aTransfer,
StructuredCloneData& aData)
{
// First try to use structured clone on the whole thing.
JS::RootedValue v(aCx, aValue);
JS::RootedValue t(aCx, aTransfer);
ErrorResult rv;
aData.Write(aCx, v, t, rv);
if (!rv.Failed()) {
return true;
}
rv.SuppressException();
JS_ClearPendingException(aCx);
nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
if (console) {
nsAutoString filename;
uint32_t lineno = 0, column = 0;
nsJSUtils::GetCallingLocation(aCx, filename, &lineno, &column);
nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
error->Init(NS_LITERAL_STRING("Sending message that cannot be cloned. Are you trying to send an XPCOM object?"),
filename, EmptyString(), lineno, column,
nsIScriptError::warningFlag, "chrome javascript",
false /* from private window */);
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;
if (aDataLength < kMaxMessageSize) {
return true;
}
NS_ConvertUTF16toUTF8 messageName(aMessageName);
messageName.StripTaggedASCII(ASCIIMask::Mask0to9());
Telemetry::Accumulate(Telemetry::REJECTED_MESSAGE_MANAGER_MESSAGE,
messageName);
return false;
}
void
nsFrameMessageManager::SendMessage(JSContext* aCx,
const nsAString& aMessageName,
JS::Handle<JS::Value> aObj,
JS::Handle<JSObject*> aObjects,
nsIPrincipal* aPrincipal,
bool aIsSync,
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 && aIsSync) {
// 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 |= aIsSync;
bool ok = mCallback->DoSendBlockingMessage(aCx, aMessageName, data, aObjects,
aPrincipal, &retval, aIsSync);
if (aIsSync) {
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,
JS::Handle<JSObject *> aCpows,
nsIPrincipal* aPrincipal)
{
if (mIsBroadcaster) {
uint32_t len = mChildManagers.Length();
for (uint32_t i = 0; i < len; ++i) {
mChildManagers[i]->
DispatchAsyncMessageInternal(aCx, aMessage, aData, aCpows, aPrincipal);
}
return NS_OK;
}
if (!mCallback) {
return NS_ERROR_NOT_INITIALIZED;
}
nsresult rv = mCallback->DoSendAsyncMessage(aCx, aMessage, aData, aCpows, aPrincipal);
if (NS_FAILED(rv)) {
return rv;
}
return NS_OK;
}
void
nsFrameMessageManager::DispatchAsyncMessage(JSContext* aCx,
const nsAString& aMessageName,
JS::Handle<JS::Value> aObj,
JS::Handle<JSObject*> aObjects,
nsIPrincipal* aPrincipal,
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, aObjects,
aPrincipal);
}
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;
};
// When recording or replaying, return whether a message should be received in
// the middleman process instead of the recording/replaying process.
static bool
DirectMessageToMiddleman(const nsAString& aMessage)
{
// Middleman processes run developer tools server code and need to receive
// debugger related messages. The session store flush message needs to be
// received in order to cleanly shutdown the process.
return StringBeginsWith(aMessage, NS_LITERAL_STRING("debug:"))
|| aMessage.EqualsLiteral("SessionStore:flush");
}
void
nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
nsFrameLoader* aTargetFrameLoader,
bool aTargetClosed,
const nsAString& aMessage,
bool aIsSync,
StructuredCloneData* aCloneData,
mozilla::jsipc::CpowHolder* aCpows,
nsIPrincipal* aPrincipal,
nsTArray<StructuredCloneData>* aRetVal,
ErrorResult& aError)
{
// If we are recording or replaying, we will end up here in both the
// middleman process and the recording/replaying process. Ignore the message
// in one of the processes, so that it is only received in one place.
if (recordreplay::IsRecordingOrReplaying()) {
if (DirectMessageToMiddleman(aMessage)) {
return;
}
} else if (recordreplay::IsMiddleman()) {
if (!DirectMessageToMiddleman(aMessage)) {
return;
}
}
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) {
listeners->RemoveElement(listener);
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<JSObject*> cpows(cx);
if (aCpows && !aCpows->ToObject(cx, &cpows)) {
aError.Throw(NS_ERROR_UNEXPECTED);
return;
}
if (!cpows) {
cpows = JS_NewPlainObject(cx);
if (!cpows) {
aError.Throw(NS_ERROR_UNEXPECTED);
return;
}
}
argument.mObjects = cpows;
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.mPrincipal = aPrincipal;
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 TabChildMessageManager'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 + NS_LITERAL_STRING(": message reply cannot be cloned. Are you trying to send an XPCOM object?");
nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
if (console) {
nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
error->Init(msg, EmptyString(), EmptyString(),
0, 0, nsIScriptError::warningFlag, "chrome javascript",
false /* from private window */);
console->LogMessage(error);
}
JS_ClearPendingException(cx);
continue;
}
}
}
}
RefPtr<nsFrameMessageManager> kungFuDeathGrip = GetParentManager();
if (kungFuDeathGrip) {
kungFuDeathGrip->ReceiveMessage(aTarget, aTargetFrameLoader, aTargetClosed, aMessage,
aIsSync, aCloneData, aCpows, aPrincipal, 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 = aCallback;
}
}
}
void
nsFrameMessageManager::Close()
{
if (!mClosed) {
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->NotifyObservers(this, "message-manager-close", nullptr);
}
}
mClosed = true;
mCallback = nullptr;
mOwnedCallback = nullptr;
}
void
nsFrameMessageManager::Disconnect(bool aRemoveFromParent)
{
// Notify message-manager-close if we haven't already.
Close();
if (!mDisconnected) {
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->NotifyObservers(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(nsAString& 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;
nsDataHashtable<nsStringHashKey, uint32_t> mMessageCounter;
};
} // namespace
namespace mozilla {
namespace 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());
uint32_t oldCount = 0;
aReferentCount->mMessageCounter.Get(key, &oldCount);
uint32_t currentCount = oldCount + listenerCount;
aReferentCount->mMessageCounter.Put(key, currentCount);
// Keep track of messages that have a suspiciously large
// number of referents (symptom of leak).
if (currentCount == MessageManagerReporter::kSuspectReferentCount) {
aReferentCount->mSuspectMessages.AppendElement(key);
}
for (uint32_t i = 0; i < listenerCount; ++i) {
const nsMessageListenerInfo& listenerInfo = listeners->ElementAt(i);
if (listenerInfo.mWeakListener) {
nsCOMPtr<nsISupports> referent =
do_QueryReferent(listenerInfo.mWeakListener);
if (referent) {
aReferentCount->mWeakAlive++;
} else {
aReferentCount->mWeakDead++;
}
} else {
aReferentCount->mStrong++;
}
}
}
// Add referent count in child managers because the listeners
// participate in messages dispatched from parent message manager.
for (uint32_t i = 0; i < aMessageManager->mChildManagers.Length(); ++i) {
RefPtr<nsFrameMessageManager> mm = aMessageManager->mChildManagers[i];
CountReferents(mm, aReferentCount);
}
}
static void
ReportReferentCount(const char* aManagerType,
const MessageManagerReferentCount& aReferentCount,
nsIHandleReportCallback* aHandleReport,
nsISupports* aData)
{
#define REPORT(_path, _amount, _desc) \
do { \
aHandleReport->Callback(EmptyCString(), _path, \
nsIMemoryReporter::KIND_OTHER, \
nsIMemoryReporter::UNITS_COUNT, _amount, \
_desc, aData); \
} while (0)
REPORT(nsPrintfCString("message-manager/referent/%s/strong", aManagerType),
aReferentCount.mStrong,
nsPrintfCString("The number of strong referents held by the message "
"manager in the %s manager.", aManagerType));
REPORT(nsPrintfCString("message-manager/referent/%s/weak/alive", aManagerType),
aReferentCount.mWeakAlive,
nsPrintfCString("The number of weak referents that are still alive "
"held by the message manager in the %s manager.",
aManagerType));
REPORT(nsPrintfCString("message-manager/referent/%s/weak/dead", aManagerType),
aReferentCount.mWeakDead,
nsPrintfCString("The number of weak referents that are dead "
"held by the message manager in the %s manager.",
aManagerType));
for (uint32_t i = 0; i < aReferentCount.mSuspectMessages.Length(); i++) {
uint32_t totalReferentCount = 0;
aReferentCount.mMessageCounter.Get(aReferentCount.mSuspectMessages[i],
&totalReferentCount);
NS_ConvertUTF16toUTF8 suspect(aReferentCount.mSuspectMessages[i]);
REPORT(nsPrintfCString("message-manager-suspect/%s/referent(message=%s)",
aManagerType, suspect.get()), totalReferentCount,
nsPrintfCString("A message in the %s message manager with a "
"suspiciously large number of referents (symptom "
"of a leak).", aManagerType));
}
#undef REPORT
}
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 dom
} // namespace mozilla
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;
}
nsDataHashtable<nsStringHashKey, nsMessageManagerScriptHolder*>*
nsMessageManagerScriptExecutor::sCachedScripts = nullptr;
StaticRefPtr<nsScriptCacheCleaner> nsMessageManagerScriptExecutor::sScriptCacheCleaner;
void
nsMessageManagerScriptExecutor::DidCreateGlobal()
{
if (!sCachedScripts) {
sCachedScripts =
new nsDataHashtable<nsStringHashKey, nsMessageManagerScriptHolder*>;
sScriptCacheCleaner = new nsScriptCacheCleaner();
}
}
// static
void
nsMessageManagerScriptExecutor::PurgeCache()
{
if (sCachedScripts) {
NS_ASSERTION(sCachedScripts != nullptr, "Need cached scripts");
for (auto iter = sCachedScripts->Iter(); !iter.Done(); iter.Next()) {
delete iter.Data();
iter.Remove();
}
}
}
// static
void
nsMessageManagerScriptExecutor::Shutdown()
{
if (sCachedScripts) {
PurgeCache();
delete sCachedScripts;
sCachedScripts = nullptr;
sScriptCacheCleaner = nullptr;
}
}
void
nsMessageManagerScriptExecutor::LoadScriptInternal(JS::Handle<JSObject*> aGlobal,
const nsAString& aURL,
bool aRunInGlobalScope)
{
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 && holder->WillRunInGlobalScope() == aRunInGlobalScope) {
script = holder->mScript;
} else {
// Don't put anything in the cache if we already have an entry
// with a different WillRunInGlobalScope() value.
bool shouldCache = !holder;
TryCacheLoadAndCompileScript(aURL, aRunInGlobalScope,
shouldCache, aGlobal, &script);
}
AutoEntryScript aes(aGlobal, "message manager script load");
JSContext* cx = aes.cx();
if (script) {
if (aRunInGlobalScope) {
JS::RootedValue rval(cx);
JS::CloneAndExecuteScript(cx, script, &rval);
} else {
JS::Rooted<JSObject*> scope(cx);
bool ok = js::ExecuteInFrameScriptEnvironment(cx, aGlobal, script, &scope);
if (ok) {
// Force the scope to stay alive.
mAnonymousGlobalScopes.AppendElement(scope);
}
}
}
}
void
nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript(
const nsAString& aURL,
bool aRunInGlobalScope,
bool aShouldCache,
JS::Handle<JSObject*> aGlobal,
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.
bool isRunOnce = !aShouldCache || IsProcessScoped();
// 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 ? aGlobal : xpc::CompilationScope())) {
return;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JSScript*> script(cx);
script = ScriptPreloader::GetChildSingleton().GetCachedScript(cx, url);
if (!script) {
nsCOMPtr<nsIChannel> channel;
NS_NewChannel(getter_AddRefs(channel),
uri,
nsContentUtils::GetSystemPrincipal(),
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
nsIContentPolicy::TYPE_OTHER);
if (!channel) {
return;
}
nsCOMPtr<nsIInputStream> input;
rv = channel->Open2(getter_AddRefs(input));
NS_ENSURE_SUCCESS_VOID(rv);
nsString dataString;
char16_t* dataStringBuf = nullptr;
size_t dataStringLength = 0;
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,
EmptyString(), nullptr,
dataStringBuf, dataStringLength);
}
JS::SourceBufferHolder srcBuf(dataStringBuf, dataStringLength,
JS::SourceBufferHolder::GiveOwnership);
if (!dataStringBuf || dataStringLength == 0) {
return;
}
JS::CompileOptions options(cx);
options.setFileAndLine(url.get(), 1);
options.setNoScriptRval(true);
if (aRunInGlobalScope) {
if (!JS::Compile(cx, options, srcBuf, &script)) {
return;
}
// We're going to run these against some non-global scope.
} else if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script)) {
return;
}
}
MOZ_ASSERT(script);
aScriptp.set(script);
nsAutoCString scheme;
uri->GetScheme(scheme);
// We don't cache data: scripts!
if (aShouldCache && !scheme.EqualsLiteral("data")) {
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, aRunInGlobalScope);
sCachedScripts->Put(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::InitChildGlobalInternal(const nsACString& aID)
{
AutoSafeJSContext cx;
if (!SystemBindingInitIds(cx)) {
return false;
}
nsContentUtils::GetSecurityManager()->GetSystemPrincipal(getter_AddRefs(mPrincipal));
JS::RealmOptions options;
options.creationOptions().setNewCompartmentInSystemZone();
xpc::InitGlobalObjectOptions(options, mPrincipal);
JS::Rooted<JSObject*> global(cx);
if (!WrapGlobalObject(cx, options, &global)) {
return false;
}
xpc::InitGlobalObject(cx, global, 0);
// Set the location information for the new global, so that tools like
// about:memory may use that information.
xpc::SetLocationForGlobal(global, aID);
DidCreateGlobal();
return true;
}
void
nsMessageManagerScriptExecutor::MarkScopesForCC()
{
for (uint32_t i = 0; i < mAnonymousGlobalScopes.Length(); ++i) {
mAnonymousGlobalScopes[i].exposeToActiveJS();
}
}
NS_IMPL_ISUPPORTS(nsScriptCacheCleaner, nsIObserver)
ChildProcessMessageManager* nsFrameMessageManager::sChildProcessManager = nullptr;
ParentProcessMessageManager* nsFrameMessageManager::sParentProcessManager = nullptr;
nsFrameMessageManager* nsFrameMessageManager::sSameProcessParentManager = nullptr;
class nsAsyncMessageToSameProcessChild : public nsSameProcessAsyncMessageBase,
public Runnable
{
public:
nsAsyncMessageToSameProcessChild(JS::RootingContext* aRootingCx,
JS::Handle<JSObject*> aCpows)
: nsSameProcessAsyncMessageBase(aRootingCx, aCpows)
, 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(JSContext* aCx,
const nsAString& aMessage,
StructuredCloneData& aData,
JS::Handle<JSObject *> aCpows,
nsIPrincipal* aPrincipal) override
{
JS::RootingContext* rcx = JS::RootingContext::get(aCx);
RefPtr<nsAsyncMessageToSameProcessChild> ev =
new nsAsyncMessageToSameProcessChild(rcx, aCpows);
nsresult rv = ev->Init(aMessage, aData, aPrincipal);
if (NS_FAILED(rv)) {
return rv;
}
rv = NS_DispatchToCurrentThread(ev);
if (NS_FAILED(rv)) {
return rv;
}
return NS_OK;
}
};
/**
* Send messages to the parent process.
*/
class ChildProcessMessageManagerCallback : public MessageManagerCallback
{
public:
ChildProcessMessageManagerCallback()
{
MOZ_COUNT_CTOR(ChildProcessMessageManagerCallback);
}
~ChildProcessMessageManagerCallback() override
{
MOZ_COUNT_DTOR(ChildProcessMessageManagerCallback);
}
bool DoSendBlockingMessage(JSContext* aCx,
const nsAString& aMessage,
StructuredCloneData& aData,
JS::Handle<JSObject *> aCpows,
nsIPrincipal* aPrincipal,
nsTArray<StructuredCloneData>* aRetVal,
bool aIsSync) override
{
mozilla::dom::ContentChild* cc =
mozilla::dom::ContentChild::GetSingleton();
if (!cc) {
return true;
}
ClonedMessageData data;
if (!BuildClonedMessageDataForChild(cc, aData, data)) {
return false;
}
InfallibleTArray<mozilla::jsipc::CpowEntry> cpows;
if (aCpows && !cc->GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) {
return false;
}
if (aIsSync) {
return cc->SendSyncMessage(PromiseFlatString(aMessage), data, cpows,
IPC::Principal(aPrincipal), aRetVal);
}
return cc->SendRpcMessage(PromiseFlatString(aMessage), data, cpows,
IPC::Principal(aPrincipal), aRetVal);
}
nsresult DoSendAsyncMessage(JSContext* aCx,
const nsAString& aMessage,
StructuredCloneData& aData,
JS::Handle<JSObject *> aCpows,
nsIPrincipal* aPrincipal) override
{
mozilla::dom::ContentChild* cc =
mozilla::dom::ContentChild::GetSingleton();
if (!cc) {
return NS_OK;
}
ClonedMessageData data;
if (!BuildClonedMessageDataForChild(cc, aData, data)) {
return NS_ERROR_DOM_DATA_CLONE_ERR;
}
InfallibleTArray<mozilla::jsipc::CpowEntry> cpows;
if (aCpows && !cc->GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) {
return NS_ERROR_UNEXPECTED;
}
if (!cc->SendAsyncMessage(PromiseFlatString(aMessage), cpows,
IPC::Principal(aPrincipal), data)) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
};
class nsAsyncMessageToSameProcessParent : public nsSameProcessAsyncMessageBase,
public SameProcessMessageQueue::Runnable
{
public:
nsAsyncMessageToSameProcessParent(JS::RootingContext* aRootingCx,
JS::Handle<JSObject*> aCpows)
: nsSameProcessAsyncMessageBase(aRootingCx, aCpows)
{ }
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(JSContext* aCx,
const nsAString& aMessage,
StructuredCloneData& aData,
JS::Handle<JSObject *> aCpows,
nsIPrincipal* aPrincipal,
nsTArray<StructuredCloneData>* aRetVal,
bool aIsSync) override
{
SameProcessMessageQueue* queue = SameProcessMessageQueue::Get();
queue->Flush();
if (nsFrameMessageManager::sSameProcessParentManager) {
SameProcessCpowHolder cpows(JS::RootingContext::get(aCx), aCpows);
RefPtr<nsFrameMessageManager> ppm = nsFrameMessageManager::sSameProcessParentManager;
ppm->ReceiveMessage(ppm, nullptr, aMessage, true, &aData, &cpows, aPrincipal,
aRetVal, IgnoreErrors());
}
return true;
}
nsresult DoSendAsyncMessage(JSContext* aCx,
const nsAString& aMessage,
StructuredCloneData& aData,
JS::Handle<JSObject *> aCpows,
nsIPrincipal* aPrincipal) override
{
SameProcessMessageQueue* queue = SameProcessMessageQueue::Get();
JS::RootingContext* rcx = JS::RootingContext::get(aCx);
RefPtr<nsAsyncMessageToSameProcessParent> ev =
new nsAsyncMessageToSameProcessParent(rcx, aCpows);
nsresult rv = ev->Init(aMessage, aData, aPrincipal);
if (NS_FAILED(rv)) {
return rv;
}
queue->Push(ev);
return NS_OK;
}
};
// This creates the global parent process message manager.
nsresult
NS_NewParentProcessMessageManager(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);
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(JS::RootingContext* aRootingCx,
JS::Handle<JSObject*> aCpows)
: mCpows(aRootingCx, aCpows)
#ifdef DEBUG
, mCalledInit(false)
#endif
{ }
nsresult
nsSameProcessAsyncMessageBase::Init(const nsAString& aMessage,
StructuredCloneData& aData,
nsIPrincipal* aPrincipal)
{
if (!mData.Copy(aData)) {
Telemetry::Accumulate(Telemetry::IPC_SAME_PROCESS_MESSAGE_COPY_OOM_KB, aData.DataLength());
return NS_ERROR_OUT_OF_MEMORY;
}
mMessage = aMessage;
mPrincipal = aPrincipal;
#ifdef DEBUG
mCalledInit = true;
#endif
return NS_OK;
}
void
nsSameProcessAsyncMessageBase::ReceiveMessage(nsISupports* aTarget,
nsFrameLoader* aTargetFrameLoader,
nsFrameMessageManager* aManager)
{
// Make sure that we have called Init() and it has succeeded.
MOZ_ASSERT(mCalledInit);
if (aManager) {
SameProcessCpowHolder cpows(RootingCx(), mCpows);
RefPtr<nsFrameMessageManager> mm = aManager;
mm->ReceiveMessage(aTarget, aTargetFrameLoader, mMessage, false, &mData,
&cpows, mPrincipal, nullptr, IgnoreErrors());
}
}