зеркало из https://github.com/mozilla/gecko-dev.git
2036 строки
66 KiB
C++
2036 строки
66 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 "nsContentUtils.h"
|
|
#include "nsDOMClassInfoID.h"
|
|
#include "nsError.h"
|
|
#include "nsIXPConnect.h"
|
|
#include "jsapi.h"
|
|
#include "jsfriendapi.h"
|
|
#include "nsJSUtils.h"
|
|
#include "nsJSPrincipals.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsScriptLoader.h"
|
|
#include "nsFrameLoader.h"
|
|
#include "nsIXULRuntime.h"
|
|
#include "nsIScriptError.h"
|
|
#include "nsIConsoleService.h"
|
|
#include "nsIMemoryReporter.h"
|
|
#include "nsIProtocolHandler.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsIDOMClassInfo.h"
|
|
#include "xpcpublic.h"
|
|
#include "mozilla/CycleCollectedJSContext.h"
|
|
#include "mozilla/IntentionalCrash.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "mozilla/dom/File.h"
|
|
#include "mozilla/dom/MessagePort.h"
|
|
#include "mozilla/dom/nsIContentParent.h"
|
|
#include "mozilla/dom/PermissionMessageUtils.h"
|
|
#include "mozilla/dom/ProcessGlobal.h"
|
|
#include "mozilla/dom/SameProcessMessageQueue.h"
|
|
#include "mozilla/dom/ScriptSettings.h"
|
|
#include "mozilla/dom/ipc/BlobChild.h"
|
|
#include "mozilla/dom/ipc/BlobParent.h"
|
|
#include "mozilla/dom/ipc/StructuredCloneData.h"
|
|
#include "mozilla/dom/DOMStringList.h"
|
|
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsXULAppAPI.h"
|
|
#include "nsQueryObject.h"
|
|
#include <algorithm>
|
|
#include "chrome/common/ipc_channel.h" // for IPC::Channel::kMaximumMessageSize
|
|
|
|
#ifdef MOZ_CRASHREPORTER
|
|
#include "nsExceptionHandler.h"
|
|
#endif
|
|
|
|
#ifdef ANDROID
|
|
#include <android/log.h>
|
|
#endif
|
|
#ifdef XP_WIN
|
|
#include <windows.h>
|
|
# if defined(SendMessage)
|
|
# undef SendMessage
|
|
# endif
|
|
#endif
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
using namespace mozilla::dom::ipc;
|
|
|
|
nsFrameMessageManager::nsFrameMessageManager(mozilla::dom::ipc::MessageManagerCallback* aCallback,
|
|
nsFrameMessageManager* aParentManager,
|
|
/* mozilla::dom::ipc::MessageManagerFlags */ uint32_t aFlags)
|
|
: mChrome(!!(aFlags & mozilla::dom::ipc::MM_CHROME)),
|
|
mGlobal(!!(aFlags & mozilla::dom::ipc::MM_GLOBAL)),
|
|
mIsProcessManager(!!(aFlags & mozilla::dom::ipc::MM_PROCESSMANAGER)),
|
|
mIsBroadcaster(!!(aFlags & mozilla::dom::ipc::MM_BROADCASTER)),
|
|
mOwnsCallback(!!(aFlags & mozilla::dom::ipc::MM_OWNSCALLBACK)),
|
|
mHandlingMessage(false),
|
|
mClosed(false),
|
|
mDisconnected(false),
|
|
mCallback(aCallback),
|
|
mParentManager(aParentManager)
|
|
{
|
|
NS_ASSERTION(mChrome || !aParentManager, "Should not set parent manager!");
|
|
NS_ASSERTION(!mIsBroadcaster || !mCallback,
|
|
"Broadcasters cannot have callbacks!");
|
|
if (mIsProcessManager && (!mChrome || IsBroadcaster())) {
|
|
mozilla::HoldJSObjects(this);
|
|
}
|
|
// This is a bit hackish. When parent manager is global, we want
|
|
// to attach the message manager to it immediately.
|
|
// Is it just the frame message manager which waits until the
|
|
// content process is running.
|
|
if (mParentManager && (mCallback || IsBroadcaster())) {
|
|
mParentManager->AddChildManager(this);
|
|
}
|
|
if (mOwnsCallback) {
|
|
mOwnedCallback = aCallback;
|
|
}
|
|
}
|
|
|
|
nsFrameMessageManager::~nsFrameMessageManager()
|
|
{
|
|
if (mIsProcessManager && (!mChrome || IsBroadcaster())) {
|
|
mozilla::DropJSObjects(this);
|
|
}
|
|
for (int32_t i = mChildManagers.Count(); i > 0; --i) {
|
|
static_cast<nsFrameMessageManager*>(mChildManagers[i - 1])->
|
|
Disconnect(false);
|
|
}
|
|
if (mIsProcessManager) {
|
|
if (this == sParentProcessManager) {
|
|
sParentProcessManager = nullptr;
|
|
}
|
|
if (this == sChildProcessManager) {
|
|
sChildProcessManager = nullptr;
|
|
delete mozilla::dom::SameProcessMessageQueue::Get();
|
|
}
|
|
if (this == sSameProcessParentManager) {
|
|
sSameProcessParentManager = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameMessageManager)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameMessageManager)
|
|
for (auto iter = tmp->mListeners.Iter(); !iter.Done(); iter.Next()) {
|
|
nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = iter.UserData();
|
|
uint32_t count = listeners->Length();
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "listeners[i] mStrongListener");
|
|
cb.NoteXPCOMChild(listeners->ElementAt(i).mStrongListener.get());
|
|
}
|
|
}
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildManagers)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentManager)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsFrameMessageManager)
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mInitialProcessData)
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameMessageManager)
|
|
tmp->mListeners.Clear();
|
|
for (int32_t i = tmp->mChildManagers.Count(); i > 0; --i) {
|
|
static_cast<nsFrameMessageManager*>(tmp->mChildManagers[i - 1])->
|
|
Disconnect(false);
|
|
}
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildManagers)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentManager)
|
|
tmp->mInitialProcessData.setNull();
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameMessageManager)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentFrameMessageManager)
|
|
|
|
/* nsFrameMessageManager implements nsIMessageSender and nsIMessageBroadcaster,
|
|
* both of which descend from nsIMessageListenerManager. QI'ing to
|
|
* nsIMessageListenerManager is therefore ambiguous and needs explicit casts
|
|
* depending on which child interface applies. */
|
|
NS_INTERFACE_MAP_ENTRY_AGGREGATED(nsIMessageListenerManager,
|
|
(mIsBroadcaster ?
|
|
static_cast<nsIMessageListenerManager*>(
|
|
static_cast<nsIMessageBroadcaster*>(this)) :
|
|
static_cast<nsIMessageListenerManager*>(
|
|
static_cast<nsIMessageSender*>(this))))
|
|
|
|
/* Message managers in child process implement nsIMessageSender and
|
|
nsISyncMessageSender. Message managers in the chrome process are
|
|
either broadcasters (if they have subordinate/child message
|
|
managers) or they're simple message senders. */
|
|
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISyncMessageSender, !mChrome)
|
|
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMessageSender, !mChrome || !mIsBroadcaster)
|
|
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMessageBroadcaster, mChrome && mIsBroadcaster)
|
|
|
|
/* nsIContentFrameMessageManager is accessible only in TabChildGlobal. */
|
|
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIContentFrameMessageManager,
|
|
!mChrome && !mIsProcessManager)
|
|
|
|
/* Frame message managers (non-process message managers) support nsIFrameScriptLoader. */
|
|
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFrameScriptLoader,
|
|
mChrome && !mIsProcessManager)
|
|
|
|
/* Process message managers (process message managers) support nsIProcessScriptLoader. */
|
|
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIProcessScriptLoader,
|
|
mChrome && mIsProcessManager)
|
|
|
|
/* Global process message managers (process message managers) support nsIGlobalProcessScriptLoader. */
|
|
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIGlobalProcessScriptLoader,
|
|
mChrome && mIsProcessManager && mIsBroadcaster)
|
|
|
|
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO_CONDITIONAL(ChromeMessageBroadcaster,
|
|
mChrome && mIsBroadcaster)
|
|
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO_CONDITIONAL(ChromeMessageSender,
|
|
mChrome && !mIsBroadcaster)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameMessageManager)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameMessageManager)
|
|
|
|
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);
|
|
}
|
|
|
|
// nsIMessageListenerManager
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::AddMessageListener(const nsAString& aMessage,
|
|
nsIMessageListener* aListener,
|
|
bool aListenWhenClosed)
|
|
{
|
|
nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
|
|
mListeners.Get(aMessage);
|
|
if (!listeners) {
|
|
listeners = new nsAutoTObserverArray<nsMessageListenerInfo, 1>();
|
|
mListeners.Put(aMessage, listeners);
|
|
} else {
|
|
uint32_t len = listeners->Length();
|
|
for (uint32_t i = 0; i < len; ++i) {
|
|
if (listeners->ElementAt(i).mStrongListener == aListener) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
nsMessageListenerInfo* entry = listeners->AppendElement();
|
|
NS_ENSURE_TRUE(entry, NS_ERROR_OUT_OF_MEMORY);
|
|
entry->mStrongListener = aListener;
|
|
entry->mListenWhenClosed = aListenWhenClosed;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::RemoveMessageListener(const nsAString& aMessage,
|
|
nsIMessageListener* aListener)
|
|
{
|
|
nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
|
|
mListeners.Get(aMessage);
|
|
if (!listeners) {
|
|
return NS_OK;
|
|
}
|
|
|
|
uint32_t len = listeners->Length();
|
|
for (uint32_t i = 0; i < len; ++i) {
|
|
if (listeners->ElementAt(i).mStrongListener == aListener) {
|
|
listeners->RemoveElementAt(i);
|
|
return NS_OK;
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::AddWeakMessageListener(const nsAString& aMessage,
|
|
nsIMessageListener* aListener)
|
|
{
|
|
nsWeakPtr weak = do_GetWeakReference(aListener);
|
|
NS_ENSURE_TRUE(weak, NS_ERROR_NO_INTERFACE);
|
|
|
|
#ifdef DEBUG
|
|
// It's technically possible that one object X could give two different
|
|
// nsIWeakReference*'s when you do_GetWeakReference(X). We really don't want
|
|
// this to happen; it will break e.g. RemoveWeakMessageListener. So let's
|
|
// check that we're not getting ourselves into that situation.
|
|
nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener);
|
|
for (auto iter = mListeners.Iter(); !iter.Done(); iter.Next()) {
|
|
nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = iter.UserData();
|
|
uint32_t count = listeners->Length();
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
nsWeakPtr weakListener = listeners->ElementAt(i).mWeakListener;
|
|
if (weakListener) {
|
|
nsCOMPtr<nsISupports> otherCanonical = do_QueryReferent(weakListener);
|
|
MOZ_ASSERT((canonical == otherCanonical) == (weak == weakListener));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
|
|
mListeners.Get(aMessage);
|
|
if (!listeners) {
|
|
listeners = new nsAutoTObserverArray<nsMessageListenerInfo, 1>();
|
|
mListeners.Put(aMessage, listeners);
|
|
} else {
|
|
uint32_t len = listeners->Length();
|
|
for (uint32_t i = 0; i < len; ++i) {
|
|
if (listeners->ElementAt(i).mWeakListener == weak) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
nsMessageListenerInfo* entry = listeners->AppendElement();
|
|
entry->mWeakListener = weak;
|
|
entry->mListenWhenClosed = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::RemoveWeakMessageListener(const nsAString& aMessage,
|
|
nsIMessageListener* aListener)
|
|
{
|
|
nsWeakPtr weak = do_GetWeakReference(aListener);
|
|
NS_ENSURE_TRUE(weak, NS_OK);
|
|
|
|
nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
|
|
mListeners.Get(aMessage);
|
|
if (!listeners) {
|
|
return NS_OK;
|
|
}
|
|
|
|
uint32_t len = listeners->Length();
|
|
for (uint32_t i = 0; i < len; ++i) {
|
|
if (listeners->ElementAt(i).mWeakListener == weak) {
|
|
listeners->RemoveElementAt(i);
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsIFrameScriptLoader
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::LoadScript(const nsAString& aURL,
|
|
bool aAllowDelayedLoad,
|
|
bool aRunInGlobalScope)
|
|
{
|
|
if (aAllowDelayedLoad) {
|
|
// Cache for future windows or frames
|
|
mPendingScripts.AppendElement(aURL);
|
|
mPendingScriptsGlobalStates.AppendElement(aRunInGlobalScope);
|
|
}
|
|
|
|
if (mCallback) {
|
|
#ifdef DEBUG_smaug
|
|
printf("Will load %s \n", NS_ConvertUTF16toUTF8(aURL).get());
|
|
#endif
|
|
NS_ENSURE_TRUE(mCallback->DoLoadMessageManagerScript(aURL, aRunInGlobalScope),
|
|
NS_ERROR_FAILURE);
|
|
}
|
|
|
|
for (int32_t i = 0; i < mChildManagers.Count(); ++i) {
|
|
RefPtr<nsFrameMessageManager> mm =
|
|
static_cast<nsFrameMessageManager*>(mChildManagers[i]);
|
|
if (mm) {
|
|
// Use false here, so that child managers don't cache the script, which
|
|
// is already cached in the parent.
|
|
mm->LoadScript(aURL, false, aRunInGlobalScope);
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::RemoveDelayedScript(const nsAString& aURL)
|
|
{
|
|
for (uint32_t i = 0; i < mPendingScripts.Length(); ++i) {
|
|
if (mPendingScripts[i] == aURL) {
|
|
mPendingScripts.RemoveElementAt(i);
|
|
mPendingScriptsGlobalStates.RemoveElementAt(i);
|
|
break;
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::GetDelayedScripts(JSContext* aCx, JS::MutableHandle<JS::Value> aList)
|
|
{
|
|
// Frame message managers may return an incomplete list because scripts
|
|
// that were loaded after it was connected are not added to the list.
|
|
if (!IsGlobal() && !IsBroadcaster()) {
|
|
NS_WARNING("Cannot retrieve list of pending frame scripts for frame"
|
|
"message managers as it may be incomplete");
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, mPendingScripts.Length()));
|
|
NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
JS::Rooted<JSString*> url(aCx);
|
|
JS::Rooted<JSObject*> pair(aCx);
|
|
for (uint32_t i = 0; i < mPendingScripts.Length(); ++i) {
|
|
url = JS_NewUCStringCopyN(aCx, mPendingScripts[i].get(), mPendingScripts[i].Length());
|
|
NS_ENSURE_TRUE(url, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
JS::AutoValueArray<2> pairElts(aCx);
|
|
pairElts[0].setString(url);
|
|
pairElts[1].setBoolean(mPendingScriptsGlobalStates[i]);
|
|
|
|
pair = JS_NewArrayObject(aCx, pairElts);
|
|
NS_ENSURE_TRUE(pair, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
NS_ENSURE_TRUE(JS_DefineElement(aCx, array, i, pair, JSPROP_ENUMERATE),
|
|
NS_ERROR_OUT_OF_MEMORY);
|
|
}
|
|
|
|
aList.setObject(*array);
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsIFrameScriptLoader
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::LoadFrameScript(const nsAString& aURL,
|
|
bool aAllowDelayedLoad,
|
|
bool aRunInGlobalScope)
|
|
{
|
|
return LoadScript(aURL, aAllowDelayedLoad, aRunInGlobalScope);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::RemoveDelayedFrameScript(const nsAString& aURL)
|
|
{
|
|
return RemoveDelayedScript(aURL);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::GetDelayedFrameScripts(JSContext* aCx, JS::MutableHandle<JS::Value> aList)
|
|
{
|
|
return GetDelayedScripts(aCx, aList);
|
|
}
|
|
|
|
// nsIProcessScriptLoader
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::LoadProcessScript(const nsAString& aURL,
|
|
bool aAllowDelayedLoad)
|
|
{
|
|
return LoadScript(aURL, aAllowDelayedLoad, false);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::RemoveDelayedProcessScript(const nsAString& aURL)
|
|
{
|
|
return RemoveDelayedScript(aURL);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::GetDelayedProcessScripts(JSContext* aCx, JS::MutableHandle<JS::Value> aList)
|
|
{
|
|
return GetDelayedScripts(aCx, aList);
|
|
}
|
|
|
|
static bool
|
|
JSONCreator(const char16_t* aBuf, uint32_t aLen, void* aData)
|
|
{
|
|
nsAString* result = static_cast<nsAString*>(aData);
|
|
result->Append(static_cast<const char16_t*>(aBuf),
|
|
static_cast<uint32_t>(aLen));
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
GetParamsForMessage(JSContext* aCx,
|
|
const JS::Value& aValue,
|
|
const JS::Value& aTransfer,
|
|
StructuredCloneData& aData)
|
|
{
|
|
// First try to use structured clone on the whole thing.
|
|
JS::RootedValue v(aCx, aValue);
|
|
JS::RootedValue t(aCx, aTransfer);
|
|
ErrorResult rv;
|
|
aData.Write(aCx, v, t, rv);
|
|
if (!rv.Failed()) {
|
|
return true;
|
|
}
|
|
|
|
rv.SuppressException();
|
|
JS_ClearPendingException(aCx);
|
|
|
|
nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
|
|
if (console) {
|
|
nsAutoString filename;
|
|
uint32_t lineno = 0, column = 0;
|
|
nsJSUtils::GetCallingLocation(aCx, filename, &lineno, &column);
|
|
nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
|
|
error->Init(NS_LITERAL_STRING("Sending message that cannot be cloned. Are you trying to send an XPCOM object?"),
|
|
filename, EmptyString(), lineno, column,
|
|
nsIScriptError::warningFlag, "chrome javascript");
|
|
console->LogMessage(error);
|
|
}
|
|
|
|
// Not clonable, try JSON
|
|
//XXX This is ugly but currently structured cloning doesn't handle
|
|
// properly cases when interface is implemented in JS and used
|
|
// as a dictionary.
|
|
nsAutoString json;
|
|
NS_ENSURE_TRUE(JS_Stringify(aCx, &v, nullptr, JS::NullHandleValue,
|
|
JSONCreator, &json), false);
|
|
NS_ENSURE_TRUE(!json.IsEmpty(), false);
|
|
|
|
JS::Rooted<JS::Value> val(aCx, JS::NullValue());
|
|
NS_ENSURE_TRUE(JS_ParseJSON(aCx, static_cast<const char16_t*>(json.get()),
|
|
json.Length(), &val), false);
|
|
|
|
aData.Write(aCx, val, rv);
|
|
if (NS_WARN_IF(rv.Failed())) {
|
|
rv.SuppressException();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// nsISyncMessageSender
|
|
|
|
static bool sSendingSyncMessage = false;
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::SendSyncMessage(const nsAString& aMessageName,
|
|
JS::Handle<JS::Value> aJSON,
|
|
JS::Handle<JS::Value> aObjects,
|
|
nsIPrincipal* aPrincipal,
|
|
JSContext* aCx,
|
|
uint8_t aArgc,
|
|
JS::MutableHandle<JS::Value> aRetval)
|
|
{
|
|
return SendMessage(aMessageName, aJSON, aObjects, aPrincipal, aCx, aArgc,
|
|
aRetval, true);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::SendRpcMessage(const nsAString& aMessageName,
|
|
JS::Handle<JS::Value> aJSON,
|
|
JS::Handle<JS::Value> aObjects,
|
|
nsIPrincipal* aPrincipal,
|
|
JSContext* aCx,
|
|
uint8_t aArgc,
|
|
JS::MutableHandle<JS::Value> aRetval)
|
|
{
|
|
return SendMessage(aMessageName, aJSON, aObjects, aPrincipal, aCx, aArgc,
|
|
aRetval, false);
|
|
}
|
|
|
|
static bool
|
|
AllowMessage(size_t aDataLength, const nsAString& aMessageName)
|
|
{
|
|
static const size_t kMinTelemetryMessageSize = 8192;
|
|
|
|
if (aDataLength < kMinTelemetryMessageSize) {
|
|
return true;
|
|
}
|
|
|
|
NS_ConvertUTF16toUTF8 messageName(aMessageName);
|
|
messageName.StripChars("0123456789");
|
|
|
|
Telemetry::Accumulate(Telemetry::MESSAGE_MANAGER_MESSAGE_SIZE2, messageName,
|
|
aDataLength);
|
|
|
|
// 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;
|
|
}
|
|
|
|
Telemetry::Accumulate(Telemetry::REJECTED_MESSAGE_MANAGER_MESSAGE,
|
|
messageName);
|
|
|
|
return false;
|
|
}
|
|
|
|
nsresult
|
|
nsFrameMessageManager::SendMessage(const nsAString& aMessageName,
|
|
JS::Handle<JS::Value> aJSON,
|
|
JS::Handle<JS::Value> aObjects,
|
|
nsIPrincipal* aPrincipal,
|
|
JSContext* aCx,
|
|
uint8_t aArgc,
|
|
JS::MutableHandle<JS::Value> aRetval,
|
|
bool aIsSync)
|
|
{
|
|
NS_ASSERTION(!IsGlobal(), "Should not call SendSyncMessage in chrome");
|
|
NS_ASSERTION(!IsBroadcaster(), "Should not call SendSyncMessage in chrome");
|
|
NS_ASSERTION(!mParentManager, "Should not have parent manager in content!");
|
|
|
|
aRetval.setUndefined();
|
|
NS_ENSURE_TRUE(mCallback, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
if (sSendingSyncMessage && aIsSync) {
|
|
// No kind of blocking send should be issued on top of a sync message.
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
StructuredCloneData data;
|
|
if (aArgc >= 2 && !GetParamsForMessage(aCx, aJSON, JS::UndefinedHandleValue, data)) {
|
|
return NS_ERROR_DOM_DATA_CLONE_ERR;
|
|
}
|
|
|
|
if (!AllowMessage(data.DataLength(), aMessageName)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> objects(aCx);
|
|
if (aArgc >= 3 && aObjects.isObject()) {
|
|
objects = &aObjects.toObject();
|
|
}
|
|
|
|
nsTArray<StructuredCloneData> retval;
|
|
|
|
sSendingSyncMessage |= aIsSync;
|
|
bool ok = mCallback->DoSendBlockingMessage(aCx, aMessageName, data, objects,
|
|
aPrincipal, &retval, aIsSync);
|
|
if (aIsSync) {
|
|
sSendingSyncMessage = false;
|
|
}
|
|
|
|
if (!ok) {
|
|
return NS_OK;
|
|
}
|
|
|
|
uint32_t len = retval.Length();
|
|
JS::Rooted<JSObject*> dataArray(aCx, JS_NewArrayObject(aCx, len));
|
|
NS_ENSURE_TRUE(dataArray, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
for (uint32_t i = 0; i < len; ++i) {
|
|
JS::Rooted<JS::Value> ret(aCx);
|
|
ErrorResult rv;
|
|
retval[i].Read(aCx, &ret, rv);
|
|
if (rv.Failed()) {
|
|
MOZ_ASSERT(false, "Unable to read structured clone in SendMessage");
|
|
rv.SuppressException();
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
NS_ENSURE_TRUE(JS_DefineElement(aCx, dataArray, i, ret, JSPROP_ENUMERATE),
|
|
NS_ERROR_OUT_OF_MEMORY);
|
|
}
|
|
|
|
aRetval.setObject(*dataArray);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsFrameMessageManager::DispatchAsyncMessageInternal(JSContext* aCx,
|
|
const nsAString& aMessage,
|
|
StructuredCloneData& aData,
|
|
JS::Handle<JSObject *> aCpows,
|
|
nsIPrincipal* aPrincipal)
|
|
{
|
|
if (mIsBroadcaster) {
|
|
int32_t len = mChildManagers.Count();
|
|
for (int32_t i = 0; i < len; ++i) {
|
|
static_cast<nsFrameMessageManager*>(mChildManagers[i])->
|
|
DispatchAsyncMessageInternal(aCx, aMessage, aData, aCpows, aPrincipal);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!mCallback) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
nsresult rv = mCallback->DoSendAsyncMessage(aCx, aMessage, aData, aCpows, aPrincipal);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsFrameMessageManager::DispatchAsyncMessage(const nsAString& aMessageName,
|
|
const JS::Value& aJSON,
|
|
const JS::Value& aObjects,
|
|
nsIPrincipal* aPrincipal,
|
|
const JS::Value& aTransfers,
|
|
JSContext* aCx,
|
|
uint8_t aArgc)
|
|
{
|
|
StructuredCloneData data;
|
|
if (aArgc >= 2 && !GetParamsForMessage(aCx, aJSON, aTransfers, data)) {
|
|
return NS_ERROR_DOM_DATA_CLONE_ERR;
|
|
}
|
|
|
|
if (!AllowMessage(data.DataLength(), aMessageName)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> objects(aCx);
|
|
if (aArgc >= 3 && aObjects.isObject()) {
|
|
objects = &aObjects.toObject();
|
|
}
|
|
|
|
return DispatchAsyncMessageInternal(aCx, aMessageName, data, objects,
|
|
aPrincipal);
|
|
}
|
|
|
|
// nsIMessageSender
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::SendAsyncMessage(const nsAString& aMessageName,
|
|
JS::Handle<JS::Value> aJSON,
|
|
JS::Handle<JS::Value> aObjects,
|
|
nsIPrincipal* aPrincipal,
|
|
JS::Handle<JS::Value> aTransfers,
|
|
JSContext* aCx,
|
|
uint8_t aArgc)
|
|
{
|
|
return DispatchAsyncMessage(aMessageName, aJSON, aObjects, aPrincipal,
|
|
aTransfers, aCx, aArgc);
|
|
}
|
|
|
|
|
|
// nsIMessageBroadcaster
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::BroadcastAsyncMessage(const nsAString& aMessageName,
|
|
JS::Handle<JS::Value> aJSON,
|
|
JS::Handle<JS::Value> aObjects,
|
|
JSContext* aCx,
|
|
uint8_t aArgc)
|
|
{
|
|
return DispatchAsyncMessage(aMessageName, aJSON, aObjects, nullptr,
|
|
JS::UndefinedHandleValue, aCx, aArgc);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::GetChildCount(uint32_t* aChildCount)
|
|
{
|
|
*aChildCount = static_cast<uint32_t>(mChildManagers.Count());
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::GetChildAt(uint32_t aIndex,
|
|
nsIMessageListenerManager** aMM)
|
|
{
|
|
*aMM = nullptr;
|
|
nsCOMPtr<nsIMessageListenerManager> mm =
|
|
do_QueryInterface(mChildManagers.SafeObjectAt(static_cast<uint32_t>(aIndex)));
|
|
mm.swap(*aMM);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsIContentFrameMessageManager
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::Dump(const nsAString& aStr)
|
|
{
|
|
#ifdef ANDROID
|
|
__android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", NS_ConvertUTF16toUTF8(aStr).get());
|
|
#endif
|
|
#ifdef XP_WIN
|
|
if (IsDebuggerPresent()) {
|
|
OutputDebugStringW(PromiseFlatString(aStr).get());
|
|
}
|
|
#endif
|
|
fputs(NS_ConvertUTF16toUTF8(aStr).get(), stdout);
|
|
fflush(stdout);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::PrivateNoteIntentionalCrash()
|
|
{
|
|
if (XRE_IsContentProcess()) {
|
|
mozilla::NoteIntentionalCrash("tab");
|
|
return NS_OK;
|
|
} else {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::GetContent(mozIDOMWindowProxy** aContent)
|
|
{
|
|
*aContent = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::GetDocShell(nsIDocShell** aDocShell)
|
|
{
|
|
*aDocShell = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::Btoa(const nsAString& aBinaryData,
|
|
nsAString& aAsciiBase64String)
|
|
{
|
|
return nsContentUtils::Btoa(aBinaryData, aAsciiBase64String);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::Atob(const nsAString& aAsciiString,
|
|
nsAString& aBinaryData)
|
|
{
|
|
return nsContentUtils::Atob(aAsciiString, aBinaryData);
|
|
}
|
|
|
|
class MMListenerRemover
|
|
{
|
|
public:
|
|
explicit MMListenerRemover(nsFrameMessageManager* aMM)
|
|
: mWasHandlingMessage(aMM->mHandlingMessage)
|
|
, mMM(aMM)
|
|
{
|
|
mMM->mHandlingMessage = true;
|
|
}
|
|
~MMListenerRemover()
|
|
{
|
|
if (!mWasHandlingMessage) {
|
|
mMM->mHandlingMessage = false;
|
|
if (mMM->mDisconnected) {
|
|
mMM->mListeners.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool mWasHandlingMessage;
|
|
RefPtr<nsFrameMessageManager> mMM;
|
|
};
|
|
|
|
|
|
// nsIMessageListener
|
|
|
|
nsresult
|
|
nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
|
|
nsIFrameLoader* aTargetFrameLoader,
|
|
const nsAString& aMessage,
|
|
bool aIsSync,
|
|
StructuredCloneData* aCloneData,
|
|
mozilla::jsipc::CpowHolder* aCpows,
|
|
nsIPrincipal* aPrincipal,
|
|
nsTArray<StructuredCloneData>* aRetVal)
|
|
{
|
|
return ReceiveMessage(aTarget, aTargetFrameLoader, mClosed, aMessage, aIsSync,
|
|
aCloneData, aCpows, aPrincipal, aRetVal);
|
|
}
|
|
|
|
nsresult
|
|
nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
|
|
nsIFrameLoader* aTargetFrameLoader,
|
|
bool aTargetClosed,
|
|
const nsAString& aMessage,
|
|
bool aIsSync,
|
|
StructuredCloneData* aCloneData,
|
|
mozilla::jsipc::CpowHolder* aCpows,
|
|
nsIPrincipal* aPrincipal,
|
|
nsTArray<StructuredCloneData>* aRetVal)
|
|
{
|
|
nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
|
|
mListeners.Get(aMessage);
|
|
if (listeners) {
|
|
|
|
MMListenerRemover lr(this);
|
|
|
|
nsAutoTObserverArray<nsMessageListenerInfo, 1>::EndLimitedIterator
|
|
iter(*listeners);
|
|
while(iter.HasMore()) {
|
|
nsMessageListenerInfo& listener = iter.GetNext();
|
|
// Remove mListeners[i] if it's an expired weak listener.
|
|
nsCOMPtr<nsISupports> weakListener;
|
|
if (listener.mWeakListener) {
|
|
weakListener = do_QueryReferent(listener.mWeakListener);
|
|
if (!weakListener) {
|
|
listeners->RemoveElement(listener);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!listener.mListenWhenClosed && aTargetClosed) {
|
|
continue;
|
|
}
|
|
|
|
nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS;
|
|
if (weakListener) {
|
|
wrappedJS = do_QueryInterface(weakListener);
|
|
} else {
|
|
wrappedJS = do_QueryInterface(listener.mStrongListener);
|
|
}
|
|
|
|
if (!wrappedJS) {
|
|
continue;
|
|
}
|
|
|
|
if (!wrappedJS->GetJSObject()) {
|
|
continue;
|
|
}
|
|
|
|
AutoEntryScript aes(wrappedJS->GetJSObject(), "message manager handler");
|
|
JSContext* cx = aes.cx();
|
|
JS::Rooted<JSObject*> object(cx, wrappedJS->GetJSObject());
|
|
|
|
// The parameter for the listener function.
|
|
JS::Rooted<JSObject*> param(cx, JS_NewPlainObject(cx));
|
|
NS_ENSURE_TRUE(param, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
JS::Rooted<JS::Value> targetv(cx);
|
|
js::AssertSameCompartment(cx, object);
|
|
nsresult rv = nsContentUtils::WrapNative(cx, aTarget, &targetv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
JS::Rooted<JSObject*> cpows(cx);
|
|
if (aCpows) {
|
|
if (!aCpows->ToObject(cx, &cpows)) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
}
|
|
|
|
if (!cpows) {
|
|
cpows = JS_NewPlainObject(cx);
|
|
if (!cpows) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
}
|
|
|
|
JS::Rooted<JS::Value> cpowsv(cx, JS::ObjectValue(*cpows));
|
|
|
|
JS::Rooted<JS::Value> json(cx, JS::NullValue());
|
|
if (aCloneData && aCloneData->DataLength()) {
|
|
ErrorResult rv;
|
|
aCloneData->Read(cx, &json, rv);
|
|
if (NS_WARN_IF(rv.Failed())) {
|
|
rv.SuppressException();
|
|
JS_ClearPendingException(cx);
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
// Get cloned MessagePort from StructuredCloneData.
|
|
nsTArray<RefPtr<mozilla::dom::MessagePort>> ports;
|
|
if (aCloneData) {
|
|
ports = aCloneData->TakeTransferredPorts();
|
|
}
|
|
|
|
JS::Rooted<JS::Value> transferredList(cx);
|
|
if (NS_WARN_IF(!ToJSValue(cx, ports, &transferredList))) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
JS::Rooted<JSString*> jsMessage(cx,
|
|
JS_NewUCStringCopyN(cx,
|
|
static_cast<const char16_t*>(aMessage.BeginReading()),
|
|
aMessage.Length()));
|
|
NS_ENSURE_TRUE(jsMessage, NS_ERROR_OUT_OF_MEMORY);
|
|
JS::Rooted<JS::Value> syncv(cx, JS::BooleanValue(aIsSync));
|
|
bool ok = JS_DefineProperty(cx, param, "target", targetv, JSPROP_ENUMERATE) &&
|
|
JS_DefineProperty(cx, param, "name", jsMessage, JSPROP_ENUMERATE) &&
|
|
JS_DefineProperty(cx, param, "sync", syncv, JSPROP_ENUMERATE) &&
|
|
JS_DefineProperty(cx, param, "json", json, JSPROP_ENUMERATE) && // deprecated
|
|
JS_DefineProperty(cx, param, "data", json, JSPROP_ENUMERATE) &&
|
|
JS_DefineProperty(cx, param, "objects", cpowsv, JSPROP_ENUMERATE) &&
|
|
JS_DefineProperty(cx, param, "ports", transferredList, JSPROP_ENUMERATE);
|
|
|
|
NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
|
|
|
|
if (aTargetFrameLoader) {
|
|
JS::Rooted<JS::Value> targetFrameLoaderv(cx);
|
|
nsresult rv = nsContentUtils::WrapNative(cx, aTargetFrameLoader, &targetFrameLoaderv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
ok = JS_DefineProperty(cx, param, "targetFrameLoader", targetFrameLoaderv,
|
|
JSPROP_ENUMERATE);
|
|
NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
|
|
}
|
|
|
|
// message.principal == null
|
|
if (!aPrincipal) {
|
|
bool ok = JS_DefineProperty(cx, param, "principal",
|
|
JS::UndefinedHandleValue, JSPROP_ENUMERATE);
|
|
NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
|
|
}
|
|
|
|
// message.principal = the principal
|
|
else {
|
|
JS::Rooted<JS::Value> principalValue(cx);
|
|
nsresult rv = nsContentUtils::WrapNative(cx, aPrincipal,
|
|
&NS_GET_IID(nsIPrincipal),
|
|
&principalValue);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
bool ok = JS_DefineProperty(cx, param, "principal", principalValue,
|
|
JSPROP_ENUMERATE);
|
|
NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
|
|
}
|
|
|
|
JS::Rooted<JS::Value> thisValue(cx, JS::UndefinedValue());
|
|
|
|
JS::Rooted<JS::Value> funval(cx);
|
|
if (JS::IsCallable(object)) {
|
|
// If the listener is a JS function:
|
|
funval.setObject(*object);
|
|
|
|
// A small hack to get 'this' value right on content side where
|
|
// messageManager is wrapped in TabChildGlobal.
|
|
nsCOMPtr<nsISupports> defaultThisValue;
|
|
if (mChrome) {
|
|
defaultThisValue = do_QueryObject(this);
|
|
} else {
|
|
defaultThisValue = aTarget;
|
|
}
|
|
js::AssertSameCompartment(cx, object);
|
|
nsresult rv = nsContentUtils::WrapNative(cx, defaultThisValue, &thisValue);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
} else {
|
|
// If the listener is a JS object which has receiveMessage function:
|
|
if (!JS_GetProperty(cx, object, "receiveMessage", &funval) ||
|
|
!funval.isObject()) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
// Check if the object is even callable.
|
|
NS_ENSURE_STATE(JS::IsCallable(&funval.toObject()));
|
|
thisValue.setObject(*object);
|
|
}
|
|
|
|
JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());
|
|
JS::Rooted<JS::Value> argv(cx, JS::ObjectValue(*param));
|
|
|
|
{
|
|
JS::Rooted<JSObject*> thisObject(cx, thisValue.toObjectOrNull());
|
|
|
|
JSAutoCompartment tac(cx, thisObject);
|
|
if (!JS_WrapValue(cx, &argv)) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
if (!JS_CallFunctionValue(cx, thisObject, funval,
|
|
JS::HandleValueArray(argv), &rval)) {
|
|
continue;
|
|
}
|
|
if (aRetVal) {
|
|
ErrorResult rv;
|
|
StructuredCloneData* data = aRetVal->AppendElement();
|
|
data->Write(cx, rval, rv);
|
|
if (NS_WARN_IF(rv.Failed())) {
|
|
aRetVal->RemoveElementAt(aRetVal->Length() - 1);
|
|
nsString msg = aMessage + NS_LITERAL_STRING(": message reply cannot be cloned. Are you trying to send an XPCOM object?");
|
|
|
|
nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
|
|
if (console) {
|
|
nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
|
|
error->Init(msg, EmptyString(), EmptyString(),
|
|
0, 0, nsIScriptError::warningFlag, "chrome javascript");
|
|
console->LogMessage(error);
|
|
}
|
|
|
|
JS_ClearPendingException(cx);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RefPtr<nsFrameMessageManager> kungFuDeathGrip = mParentManager;
|
|
if (kungFuDeathGrip) {
|
|
return kungFuDeathGrip->ReceiveMessage(aTarget, aTargetFrameLoader,
|
|
aTargetClosed, aMessage,
|
|
aIsSync, aCloneData,
|
|
aCpows, aPrincipal,
|
|
aRetVal);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsFrameMessageManager::AddChildManager(nsFrameMessageManager* aManager)
|
|
{
|
|
mChildManagers.AppendObject(aManager);
|
|
|
|
RefPtr<nsFrameMessageManager> kungfuDeathGrip = this;
|
|
RefPtr<nsFrameMessageManager> kungfuDeathGrip2 = aManager;
|
|
|
|
LoadPendingScripts(this, aManager);
|
|
}
|
|
|
|
void
|
|
nsFrameMessageManager::LoadPendingScripts(nsFrameMessageManager* aManager,
|
|
nsFrameMessageManager* aChildMM)
|
|
{
|
|
// We have parent manager if we're a message broadcaster.
|
|
// In that case we want to load the pending scripts from all parent
|
|
// message managers in the hierarchy. Process the parent first so
|
|
// that pending scripts higher up in the hierarchy are loaded before others.
|
|
if (aManager->mParentManager) {
|
|
LoadPendingScripts(aManager->mParentManager, aChildMM);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < aManager->mPendingScripts.Length(); ++i) {
|
|
aChildMM->LoadFrameScript(aManager->mPendingScripts[i],
|
|
false,
|
|
aManager->mPendingScriptsGlobalStates[i]);
|
|
}
|
|
}
|
|
|
|
void
|
|
nsFrameMessageManager::LoadPendingScripts()
|
|
{
|
|
RefPtr<nsFrameMessageManager> kungfuDeathGrip = this;
|
|
LoadPendingScripts(this, this);
|
|
}
|
|
|
|
void
|
|
nsFrameMessageManager::SetCallback(MessageManagerCallback* aCallback)
|
|
{
|
|
MOZ_ASSERT(!mIsBroadcaster || !mCallback,
|
|
"Broadcasters cannot have callbacks!");
|
|
if (aCallback && mCallback != aCallback) {
|
|
mCallback = aCallback;
|
|
if (mOwnsCallback) {
|
|
mOwnedCallback = aCallback;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
nsFrameMessageManager::InitWithCallback(MessageManagerCallback* aCallback)
|
|
{
|
|
if (mCallback) {
|
|
// Initialization should only happen once.
|
|
return;
|
|
}
|
|
|
|
SetCallback(aCallback);
|
|
|
|
// First load parent scripts by adding this to parent manager.
|
|
if (mParentManager) {
|
|
mParentManager->AddChildManager(this);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < mPendingScripts.Length(); ++i) {
|
|
LoadFrameScript(mPendingScripts[i], false, mPendingScriptsGlobalStates[i]);
|
|
}
|
|
}
|
|
|
|
void
|
|
nsFrameMessageManager::RemoveFromParent()
|
|
{
|
|
if (mParentManager) {
|
|
mParentManager->RemoveChildManager(this);
|
|
}
|
|
mParentManager = nullptr;
|
|
mCallback = nullptr;
|
|
mOwnedCallback = nullptr;
|
|
}
|
|
|
|
void
|
|
nsFrameMessageManager::Close()
|
|
{
|
|
if (!mClosed) {
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (obs) {
|
|
obs->NotifyObservers(NS_ISUPPORTS_CAST(nsIContentFrameMessageManager*, this),
|
|
"message-manager-close", nullptr);
|
|
}
|
|
}
|
|
mClosed = true;
|
|
mCallback = nullptr;
|
|
mOwnedCallback = nullptr;
|
|
}
|
|
|
|
void
|
|
nsFrameMessageManager::Disconnect(bool aRemoveFromParent)
|
|
{
|
|
// Notify message-manager-close if we haven't already.
|
|
Close();
|
|
|
|
if (!mDisconnected) {
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (obs) {
|
|
obs->NotifyObservers(NS_ISUPPORTS_CAST(nsIContentFrameMessageManager*, this),
|
|
"message-manager-disconnect", nullptr);
|
|
}
|
|
}
|
|
if (mParentManager && aRemoveFromParent) {
|
|
mParentManager->RemoveChildManager(this);
|
|
}
|
|
mDisconnected = true;
|
|
mParentManager = nullptr;
|
|
if (!mHandlingMessage) {
|
|
mListeners.Clear();
|
|
}
|
|
}
|
|
|
|
void
|
|
nsFrameMessageManager::SetInitialProcessData(JS::HandleValue aInitialData)
|
|
{
|
|
MOZ_ASSERT(!mChrome);
|
|
MOZ_ASSERT(mIsProcessManager);
|
|
mInitialProcessData = aInitialData;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::GetInitialProcessData(JSContext* aCx, JS::MutableHandleValue aResult)
|
|
{
|
|
MOZ_ASSERT(mIsProcessManager);
|
|
MOZ_ASSERT_IF(mChrome, IsBroadcaster());
|
|
|
|
JS::RootedValue init(aCx, mInitialProcessData);
|
|
if (mChrome && init.isUndefined()) {
|
|
// We create the initial object in the junk scope. If we created it in a
|
|
// normal compartment, that compartment would leak until shutdown.
|
|
JS::RootedObject global(aCx, xpc::PrivilegedJunkScope());
|
|
JSAutoCompartment ac(aCx, global);
|
|
|
|
JS::RootedObject obj(aCx, JS_NewPlainObject(aCx));
|
|
if (!obj) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
mInitialProcessData.setObject(*obj);
|
|
init.setObject(*obj);
|
|
}
|
|
|
|
if (!mChrome && XRE_IsParentProcess()) {
|
|
// This is the cpmm in the parent process. We should use the same object as the ppmm.
|
|
nsCOMPtr<nsIGlobalProcessScriptLoader> ppmm =
|
|
do_GetService("@mozilla.org/parentprocessmessagemanager;1");
|
|
ppmm->GetInitialProcessData(aCx, &init);
|
|
mInitialProcessData = init;
|
|
}
|
|
|
|
if (!JS_WrapValue(aCx, &init)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
aResult.set(init);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFrameMessageManager::GetProcessMessageManager(nsIMessageSender** aPMM)
|
|
{
|
|
*aPMM = nullptr;
|
|
if (mCallback) {
|
|
nsCOMPtr<nsIMessageSender> pmm = mCallback->GetProcessMessageManager();
|
|
pmm.swap(*aPMM);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct MessageManagerReferentCount
|
|
{
|
|
MessageManagerReferentCount() : mStrong(0), mWeakAlive(0), mWeakDead(0) {}
|
|
size_t mStrong;
|
|
size_t mWeakAlive;
|
|
size_t mWeakDead;
|
|
nsTArray<nsString> mSuspectMessages;
|
|
nsDataHashtable<nsStringHashKey, uint32_t> mMessageCounter;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
class MessageManagerReporter final : public nsIMemoryReporter
|
|
{
|
|
~MessageManagerReporter() = 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 =
|
|
static_cast<nsFrameMessageManager*>(aMessageManager->mChildManagers[i]);
|
|
CountReferents(mm, aReferentCount);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ReportReferentCount(const char* aManagerType,
|
|
const MessageManagerReferentCount& aReferentCount,
|
|
nsIHandleReportCallback* aHandleReport,
|
|
nsISupports* aData)
|
|
{
|
|
#define REPORT(_path, _amount, _desc) \
|
|
do { \
|
|
aHandleReport->Callback(EmptyCString(), _path, \
|
|
nsIMemoryReporter::KIND_OTHER, \
|
|
nsIMemoryReporter::UNITS_COUNT, _amount, \
|
|
_desc, aData); \
|
|
} while (0)
|
|
|
|
REPORT(nsPrintfCString("message-manager/referent/%s/strong", aManagerType),
|
|
aReferentCount.mStrong,
|
|
nsPrintfCString("The number of strong referents held by the message "
|
|
"manager in the %s manager.", aManagerType));
|
|
REPORT(nsPrintfCString("message-manager/referent/%s/weak/alive", aManagerType),
|
|
aReferentCount.mWeakAlive,
|
|
nsPrintfCString("The number of weak referents that are still alive "
|
|
"held by the message manager in the %s manager.",
|
|
aManagerType));
|
|
REPORT(nsPrintfCString("message-manager/referent/%s/weak/dead", aManagerType),
|
|
aReferentCount.mWeakDead,
|
|
nsPrintfCString("The number of weak referents that are dead "
|
|
"held by the message manager in the %s manager.",
|
|
aManagerType));
|
|
|
|
for (uint32_t i = 0; i < aReferentCount.mSuspectMessages.Length(); i++) {
|
|
uint32_t totalReferentCount = 0;
|
|
aReferentCount.mMessageCounter.Get(aReferentCount.mSuspectMessages[i],
|
|
&totalReferentCount);
|
|
NS_ConvertUTF16toUTF8 suspect(aReferentCount.mSuspectMessages[i]);
|
|
REPORT(nsPrintfCString("message-manager-suspect/%s/referent(message=%s)",
|
|
aManagerType, suspect.get()), totalReferentCount,
|
|
nsPrintfCString("A message in the %s message manager with a "
|
|
"suspiciously large number of referents (symptom "
|
|
"of a leak).", aManagerType));
|
|
}
|
|
|
|
#undef REPORT
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
MessageManagerReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
|
|
nsISupports* aData, bool aAnonymize)
|
|
{
|
|
if (XRE_IsParentProcess()) {
|
|
nsCOMPtr<nsIMessageBroadcaster> globalmm =
|
|
do_GetService("@mozilla.org/globalmessagemanager;1");
|
|
if (globalmm) {
|
|
RefPtr<nsFrameMessageManager> mm =
|
|
static_cast<nsFrameMessageManager*>(globalmm.get());
|
|
MessageManagerReferentCount count;
|
|
CountReferents(mm, &count);
|
|
ReportReferentCount("global-manager", count, aHandleReport, aData);
|
|
}
|
|
}
|
|
|
|
if (nsFrameMessageManager::sParentProcessManager) {
|
|
MessageManagerReferentCount count;
|
|
CountReferents(nsFrameMessageManager::sParentProcessManager, &count);
|
|
ReportReferentCount("parent-process-manager", count, aHandleReport, aData);
|
|
}
|
|
|
|
if (nsFrameMessageManager::sChildProcessManager) {
|
|
MessageManagerReferentCount count;
|
|
CountReferents(nsFrameMessageManager::sChildProcessManager, &count);
|
|
ReportReferentCount("child-process-manager", count, aHandleReport, aData);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|
|
|
|
nsresult
|
|
NS_NewGlobalMessageManager(nsIMessageBroadcaster** aResult)
|
|
{
|
|
NS_ENSURE_TRUE(XRE_IsParentProcess(),
|
|
NS_ERROR_NOT_AVAILABLE);
|
|
RefPtr<nsFrameMessageManager> mm = new nsFrameMessageManager(nullptr,
|
|
nullptr,
|
|
MM_CHROME | MM_GLOBAL | MM_BROADCASTER);
|
|
RegisterStrongMemoryReporter(new MessageManagerReporter());
|
|
mm.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsDataHashtable<nsStringHashKey, nsMessageManagerScriptHolder*>*
|
|
nsMessageManagerScriptExecutor::sCachedScripts = nullptr;
|
|
StaticRefPtr<nsScriptCacheCleaner> nsMessageManagerScriptExecutor::sScriptCacheCleaner;
|
|
|
|
void
|
|
nsMessageManagerScriptExecutor::DidCreateGlobal()
|
|
{
|
|
NS_ASSERTION(mGlobal, "Should have mGlobal!");
|
|
if (!sCachedScripts) {
|
|
sCachedScripts =
|
|
new nsDataHashtable<nsStringHashKey, nsMessageManagerScriptHolder*>;
|
|
sScriptCacheCleaner = new nsScriptCacheCleaner();
|
|
}
|
|
}
|
|
|
|
// static
|
|
void
|
|
nsMessageManagerScriptExecutor::PurgeCache()
|
|
{
|
|
if (sCachedScripts) {
|
|
NS_ASSERTION(sCachedScripts != nullptr, "Need cached scripts");
|
|
for (auto iter = sCachedScripts->Iter(); !iter.Done(); iter.Next()) {
|
|
delete iter.Data();
|
|
iter.Remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
// static
|
|
void
|
|
nsMessageManagerScriptExecutor::Shutdown()
|
|
{
|
|
if (sCachedScripts) {
|
|
PurgeCache();
|
|
|
|
delete sCachedScripts;
|
|
sCachedScripts = nullptr;
|
|
sScriptCacheCleaner = nullptr;
|
|
}
|
|
}
|
|
|
|
void
|
|
nsMessageManagerScriptExecutor::LoadScriptInternal(const nsAString& aURL,
|
|
bool aRunInGlobalScope)
|
|
{
|
|
if (!mGlobal || !sCachedScripts) {
|
|
return;
|
|
}
|
|
|
|
JS::RootingContext* rcx = RootingCx();
|
|
JS::Rooted<JSScript*> script(rcx);
|
|
|
|
nsMessageManagerScriptHolder* holder = sCachedScripts->Get(aURL);
|
|
if (holder && holder->WillRunInGlobalScope() == aRunInGlobalScope) {
|
|
script = holder->mScript;
|
|
} else {
|
|
// Don't put anything in the cache if we already have an entry
|
|
// with a different WillRunInGlobalScope() value.
|
|
bool shouldCache = !holder;
|
|
TryCacheLoadAndCompileScript(aURL, aRunInGlobalScope,
|
|
shouldCache, &script);
|
|
}
|
|
|
|
JS::Rooted<JSObject*> global(rcx, mGlobal->GetJSObject());
|
|
if (global) {
|
|
AutoEntryScript aes(global, "message manager script load");
|
|
JSContext* cx = aes.cx();
|
|
if (script) {
|
|
if (aRunInGlobalScope) {
|
|
JS::RootedValue rval(cx);
|
|
JS::CloneAndExecuteScript(cx, script, &rval);
|
|
} else {
|
|
JS::Rooted<JSObject*> scope(cx);
|
|
bool ok = js::ExecuteInGlobalAndReturnScope(cx, global, script, &scope);
|
|
if (ok) {
|
|
// Force the scope to stay alive.
|
|
mAnonymousGlobalScopes.AppendElement(scope);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript(
|
|
const nsAString& aURL,
|
|
bool aRunInGlobalScope,
|
|
bool aShouldCache,
|
|
JS::MutableHandle<JSScript*> aScriptp)
|
|
{
|
|
nsCString url = NS_ConvertUTF16toUTF8(aURL);
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = NS_NewURI(getter_AddRefs(uri), url);
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
|
|
bool hasFlags;
|
|
rv = NS_URIChainHasFlags(uri,
|
|
nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
|
|
&hasFlags);
|
|
if (NS_FAILED(rv) || !hasFlags) {
|
|
NS_WARNING("Will not load a frame script!");
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIChannel> channel;
|
|
NS_NewChannel(getter_AddRefs(channel),
|
|
uri,
|
|
nsContentUtils::GetSystemPrincipal(),
|
|
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
|
|
nsIContentPolicy::TYPE_OTHER);
|
|
|
|
if (!channel) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIInputStream> input;
|
|
rv = channel->Open2(getter_AddRefs(input));
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
nsString dataString;
|
|
char16_t* dataStringBuf = nullptr;
|
|
size_t dataStringLength = 0;
|
|
uint64_t avail64 = 0;
|
|
if (input && NS_SUCCEEDED(input->Available(&avail64)) && avail64) {
|
|
if (avail64 > UINT32_MAX) {
|
|
return;
|
|
}
|
|
nsCString buffer;
|
|
uint32_t avail = (uint32_t)std::min(avail64, (uint64_t)UINT32_MAX);
|
|
if (NS_FAILED(NS_ReadInputStreamToString(input, buffer, avail))) {
|
|
return;
|
|
}
|
|
nsScriptLoader::ConvertToUTF16(channel, (uint8_t*)buffer.get(), avail,
|
|
EmptyString(), nullptr,
|
|
dataStringBuf, dataStringLength);
|
|
}
|
|
|
|
JS::SourceBufferHolder srcBuf(dataStringBuf, dataStringLength,
|
|
JS::SourceBufferHolder::GiveOwnership);
|
|
|
|
if (dataStringBuf && dataStringLength > 0) {
|
|
// Compile the script in the compilation scope instead of the current global
|
|
// to avoid keeping the current compartment alive.
|
|
AutoJSAPI jsapi;
|
|
if (!jsapi.Init(xpc::CompilationScope())) {
|
|
return;
|
|
}
|
|
JSContext* cx = jsapi.cx();
|
|
JS::CompileOptions options(cx, JSVERSION_LATEST);
|
|
options.setFileAndLine(url.get(), 1);
|
|
options.setNoScriptRval(true);
|
|
JS::Rooted<JSScript*> script(cx);
|
|
|
|
if (aRunInGlobalScope) {
|
|
if (!JS::Compile(cx, options, srcBuf, &script)) {
|
|
return;
|
|
}
|
|
// We're going to run these against some non-global scope.
|
|
} else if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script)) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(script);
|
|
aScriptp.set(script);
|
|
|
|
nsAutoCString scheme;
|
|
uri->GetScheme(scheme);
|
|
// We don't cache data: scripts!
|
|
if (aShouldCache && !scheme.EqualsLiteral("data")) {
|
|
// Root the object also for caching.
|
|
auto* holder = new nsMessageManagerScriptHolder(cx, script, aRunInGlobalScope);
|
|
sCachedScripts->Put(aURL, holder);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript(
|
|
const nsAString& aURL,
|
|
bool aRunInGlobalScope)
|
|
{
|
|
JS::Rooted<JSScript*> script(RootingCx());
|
|
TryCacheLoadAndCompileScript(aURL, aRunInGlobalScope, true, &script);
|
|
}
|
|
|
|
void
|
|
nsMessageManagerScriptExecutor::Trace(const TraceCallbacks& aCallbacks, void* aClosure)
|
|
{
|
|
for (size_t i = 0, length = mAnonymousGlobalScopes.Length(); i < length; ++i) {
|
|
aCallbacks.Trace(&mAnonymousGlobalScopes[i], "mAnonymousGlobalScopes[i]", aClosure);
|
|
}
|
|
}
|
|
|
|
bool
|
|
nsMessageManagerScriptExecutor::InitChildGlobalInternal(
|
|
nsISupports* aScope,
|
|
const nsACString& aID)
|
|
{
|
|
AutoSafeJSContext cx;
|
|
nsContentUtils::GetSecurityManager()->GetSystemPrincipal(getter_AddRefs(mPrincipal));
|
|
|
|
nsIXPConnect* xpc = nsContentUtils::XPConnect();
|
|
const uint32_t flags = nsIXPConnect::INIT_JS_STANDARD_CLASSES;
|
|
|
|
JS::CompartmentOptions options;
|
|
options.creationOptions().setZone(JS::SystemZone);
|
|
options.behaviors().setVersion(JSVERSION_LATEST);
|
|
|
|
if (xpc::SharedMemoryEnabled()) {
|
|
options.creationOptions().setSharedMemoryAndAtomicsEnabled(true);
|
|
}
|
|
|
|
nsresult rv =
|
|
xpc->InitClassesWithNewWrappedGlobal(cx, aScope, mPrincipal,
|
|
flags, options, getter_AddRefs(mGlobal));
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
|
|
JS::Rooted<JSObject*> global(cx, mGlobal->GetJSObject());
|
|
NS_ENSURE_TRUE(global, false);
|
|
|
|
// Set the location information for the new global, so that tools like
|
|
// about:memory may use that information.
|
|
xpc::SetLocationForGlobal(global, aID);
|
|
|
|
DidCreateGlobal();
|
|
return true;
|
|
}
|
|
|
|
void
|
|
nsMessageManagerScriptExecutor::MarkScopesForCC()
|
|
{
|
|
for (uint32_t i = 0; i < mAnonymousGlobalScopes.Length(); ++i) {
|
|
mAnonymousGlobalScopes[i].exposeToActiveJS();
|
|
}
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(nsScriptCacheCleaner, nsIObserver)
|
|
|
|
nsFrameMessageManager* nsFrameMessageManager::sChildProcessManager = nullptr;
|
|
nsFrameMessageManager* nsFrameMessageManager::sParentProcessManager = nullptr;
|
|
nsFrameMessageManager* nsFrameMessageManager::sSameProcessParentManager = nullptr;
|
|
|
|
class nsAsyncMessageToSameProcessChild : public nsSameProcessAsyncMessageBase,
|
|
public Runnable
|
|
{
|
|
public:
|
|
nsAsyncMessageToSameProcessChild(JS::RootingContext* aRootingCx,
|
|
JS::Handle<JSObject*> aCpows)
|
|
: nsSameProcessAsyncMessageBase(aRootingCx, aCpows)
|
|
{ }
|
|
NS_IMETHOD Run() override
|
|
{
|
|
nsFrameMessageManager* ppm = nsFrameMessageManager::GetChildProcessManager();
|
|
ReceiveMessage(static_cast<nsIContentFrameMessageManager*>(ppm), nullptr, ppm);
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Send messages to an imaginary child process in a single-process scenario.
|
|
*/
|
|
class SameParentProcessMessageManagerCallback : public MessageManagerCallback
|
|
{
|
|
public:
|
|
SameParentProcessMessageManagerCallback()
|
|
{
|
|
MOZ_COUNT_CTOR(SameParentProcessMessageManagerCallback);
|
|
}
|
|
~SameParentProcessMessageManagerCallback() override
|
|
{
|
|
MOZ_COUNT_DTOR(SameParentProcessMessageManagerCallback);
|
|
}
|
|
|
|
bool DoLoadMessageManagerScript(const nsAString& aURL,
|
|
bool aRunInGlobalScope) override
|
|
{
|
|
ProcessGlobal* global = ProcessGlobal::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(static_cast<nsIContentFrameMessageManager*>(ppm), nullptr, ppm);
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Send messages to the imaginary parent process in a single-process scenario.
|
|
*/
|
|
class SameChildProcessMessageManagerCallback : public MessageManagerCallback
|
|
{
|
|
public:
|
|
SameChildProcessMessageManagerCallback()
|
|
{
|
|
MOZ_COUNT_CTOR(SameChildProcessMessageManagerCallback);
|
|
}
|
|
~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(static_cast<nsIContentFrameMessageManager*>(ppm.get()), nullptr, aMessage,
|
|
true, &aData, &cpows, aPrincipal, aRetVal);
|
|
}
|
|
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(nsIMessageBroadcaster** aResult)
|
|
{
|
|
NS_ASSERTION(!nsFrameMessageManager::sParentProcessManager,
|
|
"Re-creating sParentProcessManager");
|
|
RefPtr<nsFrameMessageManager> mm = new nsFrameMessageManager(nullptr,
|
|
nullptr,
|
|
MM_CHROME | MM_PROCESSMANAGER | MM_BROADCASTER);
|
|
nsFrameMessageManager::sParentProcessManager = mm;
|
|
nsFrameMessageManager::NewProcessMessageManager(false); // Create same process message manager.
|
|
mm.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsFrameMessageManager*
|
|
nsFrameMessageManager::NewProcessMessageManager(bool aIsRemote)
|
|
{
|
|
if (!nsFrameMessageManager::sParentProcessManager) {
|
|
nsCOMPtr<nsIMessageBroadcaster> dummy =
|
|
do_GetService("@mozilla.org/parentprocessmessagemanager;1");
|
|
}
|
|
|
|
MOZ_ASSERT(nsFrameMessageManager::sParentProcessManager,
|
|
"parent process manager not created");
|
|
nsFrameMessageManager* mm;
|
|
if (aIsRemote) {
|
|
// Callback is set in ContentParent::InitInternal so that the process has
|
|
// already started when we send pending scripts.
|
|
mm = new nsFrameMessageManager(nullptr,
|
|
nsFrameMessageManager::sParentProcessManager,
|
|
MM_CHROME | MM_PROCESSMANAGER);
|
|
} else {
|
|
mm = new nsFrameMessageManager(new SameParentProcessMessageManagerCallback(),
|
|
nsFrameMessageManager::sParentProcessManager,
|
|
MM_CHROME | MM_PROCESSMANAGER | MM_OWNSCALLBACK);
|
|
sSameProcessParentManager = mm;
|
|
}
|
|
return mm;
|
|
}
|
|
|
|
nsresult
|
|
NS_NewChildProcessMessageManager(nsISyncMessageSender** aResult)
|
|
{
|
|
NS_ASSERTION(!nsFrameMessageManager::GetChildProcessManager(),
|
|
"Re-creating sChildProcessManager");
|
|
|
|
MessageManagerCallback* cb;
|
|
if (XRE_IsParentProcess()) {
|
|
cb = new SameChildProcessMessageManagerCallback();
|
|
} else {
|
|
cb = new ChildProcessMessageManagerCallback();
|
|
RegisterStrongMemoryReporter(new MessageManagerReporter());
|
|
}
|
|
auto* mm = new nsFrameMessageManager(cb, nullptr,
|
|
MM_PROCESSMANAGER | MM_OWNSCALLBACK);
|
|
nsFrameMessageManager::SetChildProcessManager(mm);
|
|
RefPtr<ProcessGlobal> global = new ProcessGlobal(mm);
|
|
NS_ENSURE_TRUE(global->Init(), NS_ERROR_UNEXPECTED);
|
|
global.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
nsFrameMessageManager::MarkForCC()
|
|
{
|
|
for (auto iter = mListeners.Iter(); !iter.Done(); iter.Next()) {
|
|
nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = iter.UserData();
|
|
uint32_t count = listeners->Length();
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
nsCOMPtr<nsIMessageListener> strongListener =
|
|
listeners->ElementAt(i).mStrongListener;
|
|
if (strongListener) {
|
|
xpc_TryUnmarkWrappedGrayObject(strongListener);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mRefCnt.IsPurple()) {
|
|
mRefCnt.RemovePurple();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
nsSameProcessAsyncMessageBase::nsSameProcessAsyncMessageBase(JS::RootingContext* aRootingCx,
|
|
JS::Handle<JSObject*> aCpows)
|
|
: mRootingCx(aRootingCx)
|
|
, mCpows(aRootingCx, aCpows)
|
|
#ifdef DEBUG
|
|
, mCalledInit(false)
|
|
#endif
|
|
{ }
|
|
|
|
|
|
nsresult
|
|
nsSameProcessAsyncMessageBase::Init(const nsAString& aMessage,
|
|
StructuredCloneData& aData,
|
|
nsIPrincipal* aPrincipal)
|
|
{
|
|
if (!mData.Copy(aData)) {
|
|
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,
|
|
nsIFrameLoader* aTargetFrameLoader,
|
|
nsFrameMessageManager* aManager)
|
|
{
|
|
// Make sure that we have called Init() and it has succeeded.
|
|
MOZ_ASSERT(mCalledInit);
|
|
if (aManager) {
|
|
SameProcessCpowHolder cpows(mRootingCx, mCpows);
|
|
|
|
RefPtr<nsFrameMessageManager> mm = aManager;
|
|
mm->ReceiveMessage(aTarget, aTargetFrameLoader, mMessage, false, &mData,
|
|
&cpows, mPrincipal, nullptr);
|
|
}
|
|
}
|