gecko-dev/dom/ipc/ContentParent.cpp

8289 lines
279 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=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/. */
#ifdef MOZ_WIDGET_ANDROID
# include "AndroidDecoderModule.h"
#endif
#include "mozilla/AppShutdown.h"
#include "mozilla/DebugOnly.h"
#include "base/basictypes.h"
#include "base/shared_memory.h"
#include "ContentParent.h"
#include "mozilla/ipc/ProcessUtils.h"
#include "mozilla/CmdLineAndEnvUtils.h"
#include "BrowserParent.h"
#include "chrome/common/process_watcher.h"
#include "mozilla/Result.h"
#include "mozilla/XREAppData.h"
#include "nsComponentManagerUtils.h"
#include "nsIBrowserDOMWindow.h"
#ifdef ACCESSIBILITY
# include "mozilla/a11y/PDocAccessible.h"
#endif
#include "GMPServiceParent.h"
#include "HandlerServiceParent.h"
#include "IHistory.h"
#if defined(XP_WIN) && defined(ACCESSIBILITY)
# include "mozilla/a11y/AccessibleWrap.h"
# include "mozilla/a11y/Compatibility.h"
# include "mozilla/mscom/ActCtxResource.h"
# include "mozilla/StaticPrefs_accessibility.h"
#endif
#include <map>
#include <utility>
#include "ContentProcessManager.h"
#include "GeckoProfiler.h"
#include "Geolocation.h"
#include "GfxInfoBase.h"
#include "MMPrinter.h"
#include "PreallocatedProcessManager.h"
#include "ProcessPriorityManager.h"
#include "ProfilerParent.h"
#include "SandboxHal.h"
#include "SourceSurfaceRawData.h"
#include "mozilla/ipc/URIUtils.h"
#include "gfxPlatform.h"
#include "gfxPlatformFontList.h"
#include "nsDNSService2.h"
#include "nsPIDNSService.h"
#include "mozilla/AntiTrackingUtils.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/BenchmarkStorageParent.h"
#include "mozilla/ContentBlockingUserInteraction.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/FOGIPC.h"
#include "mozilla/GlobalStyleSheetCache.h"
#include "mozilla/GeckoArgs.h"
#include "mozilla/HangDetails.h"
#include "mozilla/LoginReputationIPC.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/Maybe.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/PerformanceMetricsCollector.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/ProcessHangMonitor.h"
#include "mozilla/ProcessHangMonitorIPC.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/ScriptPreloader.h"
#include "mozilla/Components.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_fission.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/StaticPrefs_widget.h"
#include "mozilla/StorageAccessAPIHelper.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/StyleSheetInlines.h"
#include "mozilla/TaskController.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TelemetryIPC.h"
#include "mozilla/Unused.h"
#include "mozilla/WebBrowserPersistDocumentParent.h"
#include "mozilla/devtools/HeapSnapshotTempFileHelperParent.h"
#include "mozilla/dom/BlobURLProtocolHandler.h"
#include "mozilla/dom/BrowserHost.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/BrowsingContextGroup.h"
#include "mozilla/dom/CancelContentJSOptionsBinding.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/ClientManager.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/DataTransfer.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ExternalHelperAppParent.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FileSystemSecurity.h"
#include "mozilla/dom/GeolocationBinding.h"
#include "mozilla/dom/GeolocationPositionError.h"
#include "mozilla/dom/GetFilesHelper.h"
#include "mozilla/dom/IPCBlobUtils.h"
#include "mozilla/dom/JSActorService.h"
#include "mozilla/dom/JSProcessActorBinding.h"
#include "mozilla/dom/LocalStorageCommon.h"
#include "mozilla/dom/MediaController.h"
#include "mozilla/dom/MemoryReportRequest.h"
#include "mozilla/dom/MediaStatusManager.h"
#include "mozilla/dom/Notification.h"
#include "mozilla/dom/PContentPermissionRequestParent.h"
#include "mozilla/dom/PCycleCollectWithLogsParent.h"
#include "mozilla/dom/ParentProcessMessageManager.h"
#include "mozilla/dom/Permissions.h"
#include "mozilla/dom/ProcessMessageManager.h"
#include "mozilla/dom/PushNotifier.h"
#include "mozilla/dom/ServiceWorkerManager.h"
#include "mozilla/dom/ServiceWorkerRegistrar.h"
#include "mozilla/dom/ServiceWorkerUtils.h"
#include "mozilla/dom/SessionHistoryEntry.h"
#include "mozilla/dom/SessionStorageManager.h"
#include "mozilla/dom/StorageIPC.h"
#include "mozilla/dom/URLClassifierParent.h"
#include "mozilla/dom/WakeLock.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/ipc/SharedMap.h"
#include "mozilla/dom/ipc/StructuredCloneData.h"
#include "mozilla/dom/nsMixedContentBlocker.h"
#include "mozilla/dom/power/PowerManagerService.h"
#include "mozilla/dom/quota/QuotaManagerService.h"
#include "mozilla/extensions/ExtensionsParent.h"
#include "mozilla/extensions/StreamFilterParent.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/glean/GleanPings.h"
#include "mozilla/hal_sandbox/PHalParent.h"
#include "mozilla/intl/L10nRegistry.h"
#include "mozilla/intl/LocaleService.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/ByteBuf.h"
#include "mozilla/ipc/CrashReporterHost.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/ipc/FileDescriptorUtils.h"
#include "mozilla/ipc/IPCStreamUtils.h"
#include "mozilla/ipc/TestShellParent.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/layers/ImageBridgeParent.h"
#include "mozilla/layers/LayerTreeOwnerTracker.h"
#include "mozilla/layers/PAPZParent.h"
#include "mozilla/loader/ScriptCacheActors.h"
#include "mozilla/media/MediaParent.h"
#include "mozilla/mozSpellChecker.h"
#include "mozilla/net/CookieServiceParent.h"
#include "mozilla/net/NeckoMessageUtils.h"
#include "mozilla/net/NeckoParent.h"
#include "mozilla/net/PCookieServiceParent.h"
#include "mozilla/net/CookieKey.h"
#include "mozilla/net/TRRService.h"
#include "mozilla/TelemetryComms.h"
#include "mozilla/TelemetryEventEnums.h"
#include "mozilla/RemoteLazyInputStreamParent.h"
#include "mozilla/widget/RemoteLookAndFeel.h"
#include "mozilla/widget/ScreenManager.h"
#include "mozilla/widget/TextRecognition.h"
#include "nsAnonymousTemporaryFile.h"
#include "nsAppRunner.h"
#include "nsCExternalHandlerService.h"
#include "nsCOMPtr.h"
#include "nsChromeRegistryChrome.h"
#include "nsConsoleMessage.h"
#include "nsConsoleService.h"
#include "nsContentPermissionHelper.h"
#include "nsContentUtils.h"
#include "nsCRT.h"
#include "nsDebugImpl.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDocShell.h"
#include "nsEmbedCID.h"
#include "nsFocusManager.h"
#include "nsFrameLoader.h"
#include "nsFrameMessageManager.h"
#include "nsHashPropertyBag.h"
#include "nsHyphenationManager.h"
#include "nsIAlertsService.h"
#include "nsIAppShell.h"
#include "nsIAppWindow.h"
#include "nsIAsyncInputStream.h"
#include "nsIBidiKeyboard.h"
#include "nsICaptivePortalService.h"
#include "nsICertOverrideService.h"
#include "nsIClipboard.h"
#include "nsIContentProcess.h"
#include "nsIContentSecurityPolicy.h"
#include "nsICookie.h"
#include "nsICrashService.h"
#include "nsICycleCollectorListener.h"
#include "nsIDOMChromeWindow.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIDragService.h"
#include "nsIExternalProtocolService.h"
#include "nsIGfxInfo.h"
#include "nsIUserIdleService.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsILocalStorageManager.h"
#include "nsIMemoryInfoDumper.h"
#include "nsIMemoryReporter.h"
#include "nsIMozBrowserFrame.h"
#include "nsINetworkLinkService.h"
#include "nsIObserverService.h"
#include "nsIParentChannel.h"
#include "nsIScriptError.h"
#include "nsIScriptSecurityManager.h"
#include "nsIServiceWorkerManager.h"
#include "nsISiteSecurityService.h"
#include "nsISound.h"
#include "nsIStringBundle.h"
#include "nsITimer.h"
#include "nsIURL.h"
#include "nsIWebBrowserChrome.h"
#include "nsIX509Cert.h"
#include "nsIXULRuntime.h"
#if defined(MOZ_WIDGET_GTK) || defined(XP_WIN)
# include "nsIconChannel.h"
#endif
#include "nsMemoryInfoDumper.h"
#include "nsMemoryReporterManager.h"
#include "nsOpenURIInFrameParams.h"
#include "nsPIWindowWatcher.h"
#include "nsPluginHost.h"
#include "nsPluginTags.h"
#include "nsQueryObject.h"
#include "nsReadableUtils.h"
#include "nsSHistory.h"
#include "nsScriptError.h"
#include "nsServiceManagerUtils.h"
#include "nsStreamUtils.h"
#include "nsStyleSheetService.h"
#include "nsThread.h"
#include "nsThreadUtils.h"
#include "nsWidgetsCID.h"
#include "nsWindowWatcher.h"
#include "prio.h"
#include "private/pprio.h"
#include "xpcpublic.h"
#include "nsOpenWindowInfo.h"
#include "nsFrameLoaderOwner.h"
#ifdef MOZ_WEBRTC
# include "jsapi/WebrtcGlobalParent.h"
#endif
#if defined(XP_MACOSX)
# include "nsMacUtilsImpl.h"
# include "mozilla/AvailableMemoryWatcher.h"
#endif
#if defined(ANDROID) || defined(LINUX)
# include "nsSystemInfo.h"
#endif
#if defined(XP_LINUX)
# include "mozilla/Hal.h"
#endif
#ifdef ANDROID
# include "gfxAndroidPlatform.h"
#endif
#include "mozilla/PermissionManager.h"
#ifdef MOZ_WIDGET_ANDROID
# include "AndroidBridge.h"
# include "mozilla/java/GeckoProcessManagerWrappers.h"
# include "mozilla/java/GeckoProcessTypeWrappers.h"
#endif
#ifdef MOZ_WIDGET_GTK
# include <gdk/gdk.h>
# include "mozilla/WidgetUtilsGtk.h"
#endif
#include "mozilla/RemoteSpellCheckEngineParent.h"
#include "Crypto.h"
#ifdef MOZ_WEBSPEECH
# include "mozilla/dom/SpeechSynthesisParent.h"
#endif
#if defined(MOZ_SANDBOX)
# include "mozilla/SandboxSettings.h"
# if defined(XP_LINUX)
# include "mozilla/SandboxInfo.h"
# include "mozilla/SandboxBroker.h"
# include "mozilla/SandboxBrokerPolicyFactory.h"
# endif
# if defined(XP_MACOSX)
# include "mozilla/Sandbox.h"
# endif
#endif
#ifdef XP_WIN
# include "mozilla/widget/AudioSession.h"
# include "mozilla/WinDllServices.h"
#endif
#ifdef ACCESSIBILITY
# include "nsAccessibilityService.h"
#endif
#ifdef MOZ_CODE_COVERAGE
# include "mozilla/CodeCoverageHandler.h"
#endif
#ifdef FUZZING_SNAPSHOT
# include "mozilla/fuzzing/IPCFuzzController.h"
#endif
// For VP9Benchmark::sBenchmarkFpsPref
#include "Benchmark.h"
#include "nsIToolkitProfileService.h"
#include "nsIToolkitProfile.h"
static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
using base::KillProcess;
using namespace CrashReporter;
using namespace mozilla::dom::power;
using namespace mozilla::media;
using namespace mozilla::embedding;
using namespace mozilla::gfx;
using namespace mozilla::gmp;
using namespace mozilla::hal;
using namespace mozilla::ipc;
using namespace mozilla::intl;
using namespace mozilla::layers;
using namespace mozilla::layout;
using namespace mozilla::net;
using namespace mozilla::psm;
using namespace mozilla::widget;
using namespace mozilla::Telemetry;
using mozilla::loader::PScriptCacheParent;
using mozilla::Telemetry::ProcessID;
extern mozilla::LazyLogModule gFocusLog;
#define LOGFOCUS(args) MOZ_LOG(gFocusLog, mozilla::LogLevel::Debug, args)
namespace mozilla {
namespace CubebUtils {
extern FileDescriptor CreateAudioIPCConnection();
}
namespace dom {
LazyLogModule gProcessLog("Process");
static std::map<RemoteDecodeIn, media::MediaCodecsSupported> sCodecsSupported;
/* static */
uint32_t ContentParent::sMaxContentProcesses = 0;
/* static */
Maybe<TimeStamp> ContentParent::sLastContentProcessLaunch = Nothing();
/* static */
LogModule* ContentParent::GetLog() { return gProcessLog; }
/* static */
uint32_t ContentParent::sPageLoadEventCounter = 0;
#define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
#define NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC "ipc:network:set-connectivity"
// IPC receiver for remote GC/CC logging.
class CycleCollectWithLogsParent final : public PCycleCollectWithLogsParent {
public:
MOZ_COUNTED_DTOR(CycleCollectWithLogsParent)
static bool AllocAndSendConstructor(ContentParent* aManager,
bool aDumpAllTraces,
nsICycleCollectorLogSink* aSink,
nsIDumpGCAndCCLogsCallback* aCallback) {
CycleCollectWithLogsParent* actor;
FILE* gcLog;
FILE* ccLog;
nsresult rv;
actor = new CycleCollectWithLogsParent(aSink, aCallback);
rv = actor->mSink->Open(&gcLog, &ccLog);
if (NS_WARN_IF(NS_FAILED(rv))) {
delete actor;
return false;
}
return aManager->SendPCycleCollectWithLogsConstructor(
actor, aDumpAllTraces, FILEToFileDescriptor(gcLog),
FILEToFileDescriptor(ccLog));
}
private:
virtual mozilla::ipc::IPCResult RecvCloseGCLog() override {
Unused << mSink->CloseGCLog();
return IPC_OK();
}
virtual mozilla::ipc::IPCResult RecvCloseCCLog() override {
Unused << mSink->CloseCCLog();
return IPC_OK();
}
virtual mozilla::ipc::IPCResult Recv__delete__() override {
// Report completion to mCallback only on successful
// completion of the protocol.
nsCOMPtr<nsIFile> gcLog, ccLog;
mSink->GetGcLog(getter_AddRefs(gcLog));
mSink->GetCcLog(getter_AddRefs(ccLog));
Unused << mCallback->OnDump(gcLog, ccLog, /* parent = */ false);
return IPC_OK();
}
virtual void ActorDestroy(ActorDestroyReason aReason) override {
// If the actor is unexpectedly destroyed, we deliberately
// don't call Close[GC]CLog on the sink, because the logs may
// be incomplete. See also the nsCycleCollectorLogSinkToFile
// implementaiton of those methods, and its destructor.
}
CycleCollectWithLogsParent(nsICycleCollectorLogSink* aSink,
nsIDumpGCAndCCLogsCallback* aCallback)
: mSink(aSink), mCallback(aCallback) {
MOZ_COUNT_CTOR(CycleCollectWithLogsParent);
}
nsCOMPtr<nsICycleCollectorLogSink> mSink;
nsCOMPtr<nsIDumpGCAndCCLogsCallback> mCallback;
};
// A memory reporter for ContentParent objects themselves.
class ContentParentsMemoryReporter final : public nsIMemoryReporter {
~ContentParentsMemoryReporter() = default;
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIMEMORYREPORTER
};
NS_IMPL_ISUPPORTS(ContentParentsMemoryReporter, nsIMemoryReporter)
NS_IMETHODIMP
ContentParentsMemoryReporter::CollectReports(
nsIHandleReportCallback* aHandleReport, nsISupports* aData,
bool aAnonymize) {
AutoTArray<ContentParent*, 16> cps;
ContentParent::GetAllEvenIfDead(cps);
for (uint32_t i = 0; i < cps.Length(); i++) {
ContentParent* cp = cps[i];
MessageChannel* channel = cp->GetIPCChannel();
nsString friendlyName;
cp->FriendlyName(friendlyName, aAnonymize);
cp->AddRef();
nsrefcnt refcnt = cp->Release();
const char* channelStr = "no channel";
uint32_t numQueuedMessages = 0;
if (channel) {
if (channel->IsClosed()) {
channelStr = "closed channel";
} else {
channelStr = "open channel";
}
numQueuedMessages =
0; // XXX was channel->Unsound_NumQueuedMessages(); Bug 1754876
}
nsPrintfCString path(
"queued-ipc-messages/content-parent"
"(%s, pid=%d, %s, 0x%p, refcnt=%" PRIuPTR ")",
NS_ConvertUTF16toUTF8(friendlyName).get(), cp->Pid(), channelStr,
static_cast<nsIObserver*>(cp), refcnt);
constexpr auto desc =
"The number of unset IPC messages held in this ContentParent's "
"channel. A large value here might indicate that we're leaking "
"messages. Similarly, a ContentParent object for a process that's no "
"longer running could indicate that we're leaking ContentParents."_ns;
aHandleReport->Callback(/* process */ ""_ns, path, KIND_OTHER, UNITS_COUNT,
numQueuedMessages, desc, aData);
}
return NS_OK;
}
// A hashtable (by type) of processes/ContentParents. This includes
// processes that are in the Preallocator cache (which would be type
// 'prealloc'), and recycled processes ('web' and in the future
// eTLD+1-locked) processes).
nsClassHashtable<nsCStringHashKey, nsTArray<ContentParent*>>*
ContentParent::sBrowserContentParents;
namespace {
uint64_t ComputeLoadedOriginHash(nsIPrincipal* aPrincipal) {
uint32_t originNoSuffix =
BasePrincipal::Cast(aPrincipal)->GetOriginNoSuffixHash();
uint32_t originSuffix =
BasePrincipal::Cast(aPrincipal)->GetOriginSuffixHash();
return ((uint64_t)originNoSuffix) << 32 | originSuffix;
}
class ScriptableCPInfo final : public nsIContentProcessInfo {
public:
explicit ScriptableCPInfo(ContentParent* aParent) : mContentParent(aParent) {
MOZ_ASSERT(mContentParent);
}
NS_DECL_ISUPPORTS
NS_DECL_NSICONTENTPROCESSINFO
void ProcessDied() { mContentParent = nullptr; }
private:
~ScriptableCPInfo() { MOZ_ASSERT(!mContentParent, "must call ProcessDied"); }
ContentParent* mContentParent;
};
NS_IMPL_ISUPPORTS(ScriptableCPInfo, nsIContentProcessInfo)
NS_IMETHODIMP
ScriptableCPInfo::GetIsAlive(bool* aIsAlive) {
*aIsAlive = mContentParent != nullptr;
return NS_OK;
}
NS_IMETHODIMP
ScriptableCPInfo::GetProcessId(int32_t* aPID) {
if (!mContentParent) {
*aPID = -1;
return NS_ERROR_NOT_INITIALIZED;
}
*aPID = mContentParent->Pid();
if (*aPID == -1) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHODIMP
ScriptableCPInfo::GetTabCount(int32_t* aTabCount) {
if (!mContentParent) {
return NS_ERROR_NOT_INITIALIZED;
}
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
*aTabCount = cpm->GetBrowserParentCountByProcessId(mContentParent->ChildID());
return NS_OK;
}
NS_IMETHODIMP
ScriptableCPInfo::GetMessageManager(nsISupports** aMessenger) {
*aMessenger = nullptr;
if (!mContentParent) {
return NS_ERROR_NOT_INITIALIZED;
}
RefPtr<ProcessMessageManager> manager = mContentParent->GetMessageManager();
manager.forget(aMessenger);
return NS_OK;
}
ProcessID GetTelemetryProcessID(const nsACString& remoteType) {
// OOP WebExtensions run in a content process.
// For Telemetry though we want to break out collected data from the
// WebExtensions process into a separate bucket, to make sure we can analyze
// it separately and avoid skewing normal content process metrics.
return remoteType == EXTENSION_REMOTE_TYPE ? ProcessID::Extension
: ProcessID::Content;
}
} // anonymous namespace
StaticAutoPtr<nsTHashMap<nsUint32HashKey, ContentParent*>>
ContentParent::sJSPluginContentParents;
StaticAutoPtr<LinkedList<ContentParent>> ContentParent::sContentParents;
StaticRefPtr<ContentParent> ContentParent::sRecycledE10SProcess;
#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
StaticAutoPtr<SandboxBrokerPolicyFactory>
ContentParent::sSandboxBrokerPolicyFactory;
#endif
#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
StaticAutoPtr<std::vector<std::string>> ContentParent::sMacSandboxParams;
#endif
// Set to true when the first content process gets created.
static bool sCreatedFirstContentProcess = false;
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
// True when we're running the process selection code, and do not expect to
// enter code paths where processes may die.
static bool sInProcessSelector = false;
#endif
// The first content child has ID 1, so the chrome process can have ID 0.
static uint64_t gContentChildID = 1;
static const char* sObserverTopics[] = {
NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC,
NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC,
NS_IPC_CAPTIVE_PORTAL_SET_STATE,
"application-background",
"application-foreground",
"memory-pressure",
"child-gc-request",
"child-cc-request",
"child-mmu-request",
"child-ghost-request",
"last-pb-context-exited",
"file-watcher-update",
#ifdef ACCESSIBILITY
"a11y-init-or-shutdown",
#endif
"cacheservice:empty-cache",
"intl:app-locales-changed",
"intl:requested-locales-changed",
"cookie-changed",
"private-cookie-changed",
NS_NETWORK_LINK_TYPE_TOPIC,
NS_NETWORK_TRR_MODE_CHANGED_TOPIC,
"network:socket-process-crashed",
DEFAULT_TIMEZONE_CHANGED_OBSERVER_TOPIC,
};
// PreallocateProcess is called by the PreallocatedProcessManager.
// ContentParent then takes this process back within GetNewOrUsedBrowserProcess.
/*static*/ already_AddRefed<ContentParent>
ContentParent::MakePreallocProcess() {
RefPtr<ContentParent> process = new ContentParent(PREALLOC_REMOTE_TYPE);
return process.forget();
}
/*static*/
void ContentParent::StartUp() {
// FIXME Bug 1023701 - Stop using ContentParent static methods in
// child process
if (!XRE_IsParentProcess()) {
return;
}
// From this point on, NS_WARNING, NS_ASSERTION, etc. should print out the
// PID along with the warning.
nsDebugImpl::SetMultiprocessMode("Parent");
// Note: This reporter measures all ContentParents.
RegisterStrongMemoryReporter(new ContentParentsMemoryReporter());
BackgroundChild::Startup();
ClientManager::Startup();
Preferences::RegisterCallbackAndCall(&OnFissionBlocklistPrefChange,
kFissionEnforceBlockList);
Preferences::RegisterCallbackAndCall(&OnFissionBlocklistPrefChange,
kFissionOmitBlockListValues);
#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
sSandboxBrokerPolicyFactory = new SandboxBrokerPolicyFactory();
#endif
#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
sMacSandboxParams = new std::vector<std::string>();
#endif
}
/*static*/
void ContentParent::ShutDown() {
// For the most, we rely on normal process shutdown and
// ClearOnShutdown() to clean up our state.
#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
sSandboxBrokerPolicyFactory = nullptr;
#endif
#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
sMacSandboxParams = nullptr;
#endif
}
/*static*/
uint32_t ContentParent::GetPoolSize(const nsACString& aContentProcessType) {
if (!sBrowserContentParents) {
return 0;
}
nsTArray<ContentParent*>* parents =
sBrowserContentParents->Get(aContentProcessType);
return parents ? parents->Length() : 0;
}
/*static*/ nsTArray<ContentParent*>& ContentParent::GetOrCreatePool(
const nsACString& aContentProcessType) {
if (!sBrowserContentParents) {
sBrowserContentParents =
new nsClassHashtable<nsCStringHashKey, nsTArray<ContentParent*>>;
}
return *sBrowserContentParents->GetOrInsertNew(aContentProcessType);
}
const nsDependentCSubstring RemoteTypePrefix(
const nsACString& aContentProcessType) {
// The suffix after a `=` in a remoteType is dynamic, and used to control the
// process pool to use.
int32_t equalIdx = aContentProcessType.FindChar(L'=');
if (equalIdx == kNotFound) {
equalIdx = aContentProcessType.Length();
}
return StringHead(aContentProcessType, equalIdx);
}
bool IsWebRemoteType(const nsACString& aContentProcessType) {
// Note: matches webIsolated, web, and webCOOP+COEP types.
return StringBeginsWith(aContentProcessType, DEFAULT_REMOTE_TYPE);
}
bool IsWebCoopCoepRemoteType(const nsACString& aContentProcessType) {
return StringBeginsWith(aContentProcessType,
WITH_COOP_COEP_REMOTE_TYPE_PREFIX);
}
bool IsPrivilegedMozillaRemoteType(const nsACString& aContentProcessType) {
return aContentProcessType == PRIVILEGEDMOZILLA_REMOTE_TYPE;
}
bool IsExtensionRemoteType(const nsACString& aContentProcessType) {
return aContentProcessType == EXTENSION_REMOTE_TYPE;
}
/*static*/
uint32_t ContentParent::GetMaxProcessCount(
const nsACString& aContentProcessType) {
// Max process count is based only on the prefix.
const nsDependentCSubstring processTypePrefix =
RemoteTypePrefix(aContentProcessType);
// Check for the default remote type of "web", as it uses different prefs.
if (processTypePrefix == DEFAULT_REMOTE_TYPE) {
return GetMaxWebProcessCount();
}
// Read the pref controling this remote type. `dom.ipc.processCount` is not
// used as a fallback, as it is intended to control the number of "web"
// content processes, checked in `mozilla::GetMaxWebProcessCount()`.
nsAutoCString processCountPref("dom.ipc.processCount.");
processCountPref.Append(processTypePrefix);
int32_t maxContentParents = Preferences::GetInt(processCountPref.get(), 1);
if (maxContentParents < 1) {
maxContentParents = 1;
}
return static_cast<uint32_t>(maxContentParents);
}
/*static*/
bool ContentParent::IsMaxProcessCountReached(
const nsACString& aContentProcessType) {
return GetPoolSize(aContentProcessType) >=
GetMaxProcessCount(aContentProcessType);
}
// Really more ReleaseUnneededProcesses()
/*static*/
void ContentParent::ReleaseCachedProcesses() {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("ReleaseCachedProcesses:"));
if (!sBrowserContentParents) {
return;
}
#ifdef DEBUG
for (const auto& cps : *sBrowserContentParents) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("%s: %zu processes", PromiseFlatCString(cps.GetKey()).get(),
cps.GetData()->Length()));
}
#endif
// We process the toRelease array outside of the iteration to avoid modifying
// the list (via RemoveFromList()) while we're iterating it.
nsTArray<ContentParent*> toRelease;
for (const auto& contentParents : sBrowserContentParents->Values()) {
// Shutting down these processes will change the array so let's use another
// array for the removal.
for (auto* cp : *contentParents) {
if (cp->ManagedPBrowserParent().Count() == 0 &&
!cp->HasActiveWorkerOrJSPlugin() &&
cp->mRemoteType == DEFAULT_REMOTE_TYPE) {
toRelease.AppendElement(cp);
} else {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
(" Skipping %p (%s), count %d, HasActiveWorkerOrJSPlugin %d",
cp, cp->mRemoteType.get(), cp->ManagedPBrowserParent().Count(),
cp->HasActiveWorkerOrJSPlugin()));
}
}
}
for (auto* cp : toRelease) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
(" Shutdown %p (%s)", cp, cp->mRemoteType.get()));
PreallocatedProcessManager::Erase(cp);
// Start a soft shutdown.
cp->ShutDownProcess(SEND_SHUTDOWN_MESSAGE);
// Make sure we don't select this process for new tabs.
cp->MarkAsDead();
// Make sure that this process is no longer accessible from JS by its
// message manager.
cp->ShutDownMessageManager();
}
}
/*static*/
already_AddRefed<ContentParent> ContentParent::MinTabSelect(
const nsTArray<ContentParent*>& aContentParents,
int32_t aMaxContentParents) {
uint32_t maxSelectable =
std::min(static_cast<uint32_t>(aContentParents.Length()),
static_cast<uint32_t>(aMaxContentParents));
uint32_t min = INT_MAX;
RefPtr<ContentParent> candidate;
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
for (uint32_t i = 0; i < maxSelectable; i++) {
ContentParent* p = aContentParents[i];
MOZ_DIAGNOSTIC_ASSERT(!p->IsDead());
// Ignore processes that were slated for removal but not yet removed from
// the pool (see also GetUsedBrowserProcess and BlockShutdown).
if (!p->IsShuttingDown()) {
uint32_t tabCount = cpm->GetBrowserParentCountByProcessId(p->ChildID());
if (tabCount < min) {
candidate = p;
min = tabCount;
}
}
}
// If all current processes have at least one tab and we have not yet reached
// the maximum, use a new process.
if (min > 0 &&
aContentParents.Length() < static_cast<uint32_t>(aMaxContentParents)) {
return nullptr;
}
// Otherwise we return candidate.
return candidate.forget();
}
/* static */
already_AddRefed<nsIPrincipal>
ContentParent::CreateRemoteTypeIsolationPrincipal(
const nsACString& aRemoteType) {
if ((RemoteTypePrefix(aRemoteType) != FISSION_WEB_REMOTE_TYPE) &&
!StringBeginsWith(aRemoteType, WITH_COOP_COEP_REMOTE_TYPE_PREFIX)) {
return nullptr;
}
int32_t offset = aRemoteType.FindChar('=') + 1;
MOZ_ASSERT(offset > 1, "can not extract origin from that remote type");
nsAutoCString origin(
Substring(aRemoteType, offset, aRemoteType.Length() - offset));
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
nsCOMPtr<nsIPrincipal> principal;
ssm->CreateContentPrincipalFromOrigin(origin, getter_AddRefs(principal));
return principal.forget();
}
/*static*/
already_AddRefed<ContentParent> ContentParent::GetUsedBrowserProcess(
const nsACString& aRemoteType, nsTArray<ContentParent*>& aContentParents,
uint32_t aMaxContentParents, bool aPreferUsed, ProcessPriority aPriority) {
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
AutoRestore ar(sInProcessSelector);
sInProcessSelector = true;
#endif
uint32_t numberOfParents = aContentParents.Length();
nsTArray<RefPtr<nsIContentProcessInfo>> infos(numberOfParents);
for (auto* cp : aContentParents) {
infos.AppendElement(cp->mScriptableHelper);
}
if (aPreferUsed && numberOfParents) {
// If we prefer re-using existing content processes, we don't want to create
// a new process, and instead re-use an existing one, so pretend the process
// limit is at the current number of processes.
aMaxContentParents = numberOfParents;
}
nsCOMPtr<nsIContentProcessProvider> cpp =
do_GetService("@mozilla.org/ipc/processselector;1");
int32_t index;
if (cpp && NS_SUCCEEDED(cpp->ProvideProcess(aRemoteType, infos,
aMaxContentParents, &index))) {
// If the provider returned an existing ContentParent, use that one.
if (0 <= index && static_cast<uint32_t>(index) <= aMaxContentParents) {
RefPtr<ContentParent> retval = aContentParents[index];
// Ignore processes that were slated for removal but not yet removed from
// the pool.
if (!retval->IsShuttingDown()) {
if (profiler_thread_is_being_profiled_for_markers()) {
nsPrintfCString marker("Reused process %u",
(unsigned int)retval->ChildID());
PROFILER_MARKER_TEXT("Process", DOM, {}, marker);
}
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("GetUsedProcess: Reused process %p (%u) for %s", retval.get(),
(unsigned int)retval->ChildID(),
PromiseFlatCString(aRemoteType).get()));
retval->AssertAlive();
retval->StopRecyclingE10SOnly(true);
return retval.forget();
}
}
} else {
// If there was a problem with the JS chooser, fall back to a random
// selection.
NS_WARNING("nsIContentProcessProvider failed to return a process");
RefPtr<ContentParent> random;
if ((random = MinTabSelect(aContentParents, aMaxContentParents))) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("GetUsedProcess: Reused random process %p (%d) for %s",
random.get(), (unsigned int)random->ChildID(),
PromiseFlatCString(aRemoteType).get()));
random->AssertAlive();
random->StopRecyclingE10SOnly(true);
return random.forget();
}
}
// If we are loading into the "web" remote type, are choosing to launch a new
// tab, and have a recycled E10S process, we should launch into that process.
if (aRemoteType == DEFAULT_REMOTE_TYPE && sRecycledE10SProcess) {
RefPtr<ContentParent> recycled = sRecycledE10SProcess;
MOZ_DIAGNOSTIC_ASSERT(recycled->GetRemoteType() == DEFAULT_REMOTE_TYPE);
recycled->AssertAlive();
recycled->StopRecyclingE10SOnly(true);
if (profiler_thread_is_being_profiled_for_markers()) {
nsPrintfCString marker("Recycled process %u (%p)",
(unsigned int)recycled->ChildID(), recycled.get());
PROFILER_MARKER_TEXT("Process", DOM, {}, marker);
}
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Recycled process %p", recycled.get()));
return recycled.forget();
}
// Try to take a preallocated process except for certain remote types.
// Note: this process may not have finished launching yet
RefPtr<ContentParent> preallocated;
if (aRemoteType != FILE_REMOTE_TYPE &&
aRemoteType != PRIVILEGEDABOUT_REMOTE_TYPE &&
aRemoteType != EXTENSION_REMOTE_TYPE && // Bug 1638119
(preallocated = PreallocatedProcessManager::Take(aRemoteType))) {
MOZ_DIAGNOSTIC_ASSERT(preallocated->GetRemoteType() ==
PREALLOC_REMOTE_TYPE);
MOZ_DIAGNOSTIC_ASSERT(sRecycledE10SProcess != preallocated);
preallocated->AssertAlive();
if (profiler_thread_is_being_profiled_for_markers()) {
nsPrintfCString marker(
"Assigned preallocated process %u%s",
(unsigned int)preallocated->ChildID(),
preallocated->IsLaunching() ? " (still launching)" : "");
PROFILER_MARKER_TEXT("Process", DOM, {}, marker);
}
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Adopted preallocated process %p for type %s%s",
preallocated.get(), PromiseFlatCString(aRemoteType).get(),
preallocated->IsLaunching() ? " (still launching)" : ""));
// This ensures that the preallocator won't shut down the process once
// it finishes starting
preallocated->mRemoteType.Assign(aRemoteType);
{
MutexAutoLock lock(preallocated->mThreadsafeHandle->mMutex);
preallocated->mThreadsafeHandle->mRemoteType = preallocated->mRemoteType;
}
preallocated->mRemoteTypeIsolationPrincipal =
CreateRemoteTypeIsolationPrincipal(aRemoteType);
preallocated->mActivateTS = TimeStamp::Now();
preallocated->AddToPool(aContentParents);
// rare, but will happen
if (!preallocated->IsLaunching()) {
// Specialize this process for the appropriate remote type, and activate
// it.
Unused << preallocated->SendRemoteType(preallocated->mRemoteType,
preallocated->mProfile);
nsCOMPtr<nsIObserverService> obs =
mozilla::services::GetObserverService();
if (obs) {
nsAutoString cpId;
cpId.AppendInt(static_cast<uint64_t>(preallocated->ChildID()));
obs->NotifyObservers(static_cast<nsIObserver*>(preallocated),
"process-type-set", cpId.get());
preallocated->AssertAlive();
}
}
return preallocated.forget();
}
return nullptr;
}
/*static*/
already_AddRefed<ContentParent>
ContentParent::GetNewOrUsedLaunchingBrowserProcess(
const nsACString& aRemoteType, BrowsingContextGroup* aGroup,
ProcessPriority aPriority, bool aPreferUsed) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("GetNewOrUsedProcess for type %s",
PromiseFlatCString(aRemoteType).get()));
// Fallback check (we really want our callers to avoid this).
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
MOZ_DIAGNOSTIC_ASSERT(
false, "Late attempt to GetNewOrUsedLaunchingBrowserProcess!");
return nullptr;
}
// If we have an existing host process attached to this BrowsingContextGroup,
// always return it, as we can never have multiple host processes within a
// single BrowsingContextGroup.
RefPtr<ContentParent> contentParent;
if (aGroup) {
contentParent = aGroup->GetHostProcess(aRemoteType);
Unused << NS_WARN_IF(contentParent && contentParent->IsShuttingDown());
if (contentParent && !contentParent->IsShuttingDown()) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("GetNewOrUsedProcess: Existing host process %p (launching %d)",
contentParent.get(), contentParent->IsLaunching()));
contentParent->AssertAlive();
contentParent->StopRecyclingE10SOnly(true);
return contentParent.forget();
}
}
nsTArray<ContentParent*>& contentParents = GetOrCreatePool(aRemoteType);
uint32_t maxContentParents = GetMaxProcessCount(aRemoteType);
// Let's try and reuse an existing process.
contentParent = GetUsedBrowserProcess(
aRemoteType, contentParents, maxContentParents, aPreferUsed, aPriority);
if (!contentParent) {
// No reusable process. Let's create and launch one.
// The life cycle will be set to `LifecycleState::LAUNCHING`.
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Launching new process immediately for type %s",
PromiseFlatCString(aRemoteType).get()));
contentParent = new ContentParent(aRemoteType);
if (NS_WARN_IF(!contentParent->BeginSubprocessLaunch(aPriority))) {
// Launch aborted because of shutdown. Bailout.
contentParent->LaunchSubprocessReject();
return nullptr;
}
// Until the new process is ready let's not allow to start up any
// preallocated processes. The blocker will be removed once we receive
// the first idle message.
contentParent->mIsAPreallocBlocker = true;
PreallocatedProcessManager::AddBlocker(aRemoteType, contentParent);
// Store this process for future reuse.
contentParent->AddToPool(contentParents);
MOZ_LOG(
ContentParent::GetLog(), LogLevel::Debug,
("GetNewOrUsedProcess: new immediate process %p", contentParent.get()));
}
// else we have an existing or preallocated process (which may be
// still launching)
contentParent->AssertAlive();
contentParent->StopRecyclingE10SOnly(true);
if (aGroup) {
aGroup->EnsureHostProcess(contentParent);
}
return contentParent.forget();
}
/*static*/
RefPtr<ContentParent::LaunchPromise>
ContentParent::GetNewOrUsedBrowserProcessAsync(const nsACString& aRemoteType,
BrowsingContextGroup* aGroup,
ProcessPriority aPriority,
bool aPreferUsed) {
// Obtain a `ContentParent` launched asynchronously.
RefPtr<ContentParent> contentParent = GetNewOrUsedLaunchingBrowserProcess(
aRemoteType, aGroup, aPriority, aPreferUsed);
if (!contentParent) {
// In case of launch error, stop here.
return LaunchPromise::CreateAndReject(LaunchError(), __func__);
}
return contentParent->WaitForLaunchAsync(aPriority);
}
/*static*/
already_AddRefed<ContentParent> ContentParent::GetNewOrUsedBrowserProcess(
const nsACString& aRemoteType, BrowsingContextGroup* aGroup,
ProcessPriority aPriority, bool aPreferUsed) {
RefPtr<ContentParent> contentParent = GetNewOrUsedLaunchingBrowserProcess(
aRemoteType, aGroup, aPriority, aPreferUsed);
if (!contentParent || !contentParent->WaitForLaunchSync(aPriority)) {
// In case of launch error, stop here.
return nullptr;
}
return contentParent.forget();
}
RefPtr<ContentParent::LaunchPromise> ContentParent::WaitForLaunchAsync(
ProcessPriority aPriority) {
MOZ_DIAGNOSTIC_ASSERT(!IsDead());
if (!IsLaunching()) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("WaitForLaunchAsync: launched"));
return LaunchPromise::CreateAndResolve(this, __func__);
}
// We've started an async content process launch.
Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_IS_SYNC, 0);
// We have located a process that hasn't finished initializing, then attempt
// to finish initializing. Both `LaunchSubprocessResolve` and
// `LaunchSubprocessReject` are safe to call multiple times if we race with
// other `WaitForLaunchAsync` callbacks.
return mSubprocess->WhenProcessHandleReady()->Then(
GetCurrentSerialEventTarget(), __func__,
[self = RefPtr{this}, aPriority] {
if (self->LaunchSubprocessResolve(/* aIsSync = */ false, aPriority)) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("WaitForLaunchAsync: async, now launched"));
self->mActivateTS = TimeStamp::Now();
return LaunchPromise::CreateAndResolve(self, __func__);
}
self->LaunchSubprocessReject();
return LaunchPromise::CreateAndReject(LaunchError(), __func__);
},
[self = RefPtr{this}] {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("WaitForLaunchAsync: async, rejected"));
self->LaunchSubprocessReject();
return LaunchPromise::CreateAndReject(LaunchError(), __func__);
});
}
bool ContentParent::WaitForLaunchSync(ProcessPriority aPriority) {
MOZ_DIAGNOSTIC_ASSERT(!IsDead());
if (!IsLaunching()) {
return true;
}
// We've started a sync content process launch.
Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_IS_SYNC, 1);
// We're a process which hasn't finished initializing. We may be racing
// against whoever launched it (and whoever else is already racing). Since
// we're sync, we win the race and finish the initialization.
bool launchSuccess = mSubprocess->WaitForProcessHandle();
if (launchSuccess &&
LaunchSubprocessResolve(/* aIsSync = */ true, aPriority)) {
mActivateTS = TimeStamp::Now();
return true;
}
// In case of failure.
LaunchSubprocessReject();
return false;
}
/*static*/
already_AddRefed<ContentParent> ContentParent::GetNewOrUsedJSPluginProcess(
uint32_t aPluginID, const hal::ProcessPriority& aPriority) {
RefPtr<ContentParent> p;
if (sJSPluginContentParents) {
p = sJSPluginContentParents->Get(aPluginID);
} else {
sJSPluginContentParents = new nsTHashMap<nsUint32HashKey, ContentParent*>();
}
if (p) {
return p.forget();
}
p = new ContentParent(aPluginID);
if (!p->LaunchSubprocessSync(aPriority)) {
return nullptr;
}
sJSPluginContentParents->InsertOrUpdate(aPluginID, p);
return p.forget();
}
static nsIDocShell* GetOpenerDocShellHelper(Element* aFrameElement) {
// Propagate the private-browsing status of the element's parent
// docshell to the remote docshell, via the chrome flags.
MOZ_ASSERT(aFrameElement);
nsPIDOMWindowOuter* win = aFrameElement->OwnerDoc()->GetWindow();
if (!win) {
NS_WARNING("Remote frame has no window");
return nullptr;
}
nsIDocShell* docShell = win->GetDocShell();
if (!docShell) {
NS_WARNING("Remote frame has no docshell");
return nullptr;
}
return docShell;
}
mozilla::ipc::IPCResult ContentParent::RecvCreateGMPService() {
Endpoint<PGMPServiceParent> parent;
Endpoint<PGMPServiceChild> child;
if (mGMPCreated) {
return IPC_FAIL(this, "GMP Service already created");
}
nsresult rv;
rv = PGMPService::CreateEndpoints(base::GetCurrentProcId(), OtherPid(),
&parent, &child);
if (NS_FAILED(rv)) {
return IPC_FAIL(this, "CreateEndpoints failed");
}
if (!GMPServiceParent::Create(std::move(parent))) {
return IPC_FAIL(this, "GMPServiceParent::Create failed");
}
if (!SendInitGMPService(std::move(child))) {
return IPC_FAIL(this, "SendInitGMPService failed");
}
mGMPCreated = true;
return IPC_OK();
}
Atomic<bool, mozilla::Relaxed> sContentParentTelemetryEventEnabled(false);
/*static*/
void ContentParent::LogAndAssertFailedPrincipalValidationInfo(
nsIPrincipal* aPrincipal, const char* aMethod) {
// nsContentSecurityManager may also enable this same event, but that's okay
if (!sContentParentTelemetryEventEnabled.exchange(true)) {
sContentParentTelemetryEventEnabled = true;
Telemetry::SetEventRecordingEnabled("security"_ns, true);
}
// Send Telemetry
nsAutoCString principalScheme, principalType, spec;
CopyableTArray<EventExtraEntry> extra(2);
if (!aPrincipal) {
principalType.AssignLiteral("NullPtr");
} else if (aPrincipal->IsSystemPrincipal()) {
principalType.AssignLiteral("SystemPrincipal");
} else if (aPrincipal->GetIsExpandedPrincipal()) {
principalType.AssignLiteral("ExpandedPrincipal");
} else if (aPrincipal->GetIsContentPrincipal()) {
principalType.AssignLiteral("ContentPrincipal");
aPrincipal->GetSpec(spec);
aPrincipal->GetScheme(principalScheme);
extra.AppendElement(EventExtraEntry{"scheme"_ns, principalScheme});
} else {
principalType.AssignLiteral("Unknown");
}
extra.AppendElement(EventExtraEntry{"principalType"_ns, principalType});
// Do not send telemetry when chrome-debugging is enabled
bool isChromeDebuggingEnabled =
Preferences::GetBool("devtools.chrome.enabled", false);
if (!isChromeDebuggingEnabled) {
Telemetry::EventID eventType =
Telemetry::EventID::Security_Fissionprincipals_Contentparent;
Telemetry::RecordEvent(eventType, mozilla::Some(aMethod),
mozilla::Some(extra));
}
// And log it
MOZ_LOG(
ContentParent::GetLog(), LogLevel::Error,
(" Receiving unexpected Principal (%s) within %s",
aPrincipal && aPrincipal->GetIsContentPrincipal() ? spec.get()
: principalType.get(),
aMethod));
#ifdef DEBUG
// Not only log but also ensure we do not receive an unexpected
// principal when running in debug mode.
MOZ_ASSERT(false, "Receiving unexpected Principal");
#endif
}
bool ContentParent::ValidatePrincipal(
nsIPrincipal* aPrincipal,
const EnumSet<ValidatePrincipalOptions>& aOptions) {
// If the pref says we should not validate, then there is nothing to do
if (!StaticPrefs::dom_security_enforceIPCBasedPrincipalVetting()) {
return true;
}
// If there is no principal, then there is nothing to validate!
if (!aPrincipal) {
return aOptions.contains(ValidatePrincipalOptions::AllowNullPtr);
}
// We currently do not track relationships between specific null principals
// and content processes, so we can not validate much here - just allow all
// null principals we see because they are generally safe anyway!
if (aPrincipal->GetIsNullPrincipal()) {
return true;
}
// Only allow the system principal if the passed in options flags
// request permitting the system principal.
if (aPrincipal->IsSystemPrincipal()) {
return aOptions.contains(ValidatePrincipalOptions::AllowSystem);
}
// XXXckerschb: we should eliminate the resource carve-out here and always
// validate the Principal, see Bug 1686200: Investigate Principal for pdf.js
if (aPrincipal->SchemeIs("resource")) {
return true;
}
// Validate each inner principal individually, allowing us to catch expanded
// principals containing the system principal, etc.
if (aPrincipal->GetIsExpandedPrincipal()) {
if (!aOptions.contains(ValidatePrincipalOptions::AllowExpanded)) {
return false;
}
// FIXME: There are more constraints on expanded principals in-practice,
// such as the structure of extension expanded principals. This may need
// to be investigated more in the future.
nsCOMPtr<nsIExpandedPrincipal> expandedPrincipal =
do_QueryInterface(aPrincipal);
const auto& allowList = expandedPrincipal->AllowList();
for (const auto& innerPrincipal : allowList) {
if (!ValidatePrincipal(innerPrincipal, aOptions)) {
return false;
}
}
return true;
}
// A URI with a file:// scheme can never load in a non-file content process
// due to sandboxing.
if (aPrincipal->SchemeIs("file")) {
// If we don't support a separate 'file' process, then we can return here.
if (!StaticPrefs::browser_tabs_remote_separateFileUriProcess()) {
return true;
}
return mRemoteType == FILE_REMOTE_TYPE;
}
if (aPrincipal->SchemeIs("about")) {
uint32_t flags = 0;
if (NS_FAILED(aPrincipal->GetAboutModuleFlags(&flags))) {
return false;
}
// Block principals for about: URIs which can't load in this process.
if (!(flags & (nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
nsIAboutModule::URI_MUST_LOAD_IN_CHILD))) {
return false;
}
if (flags & nsIAboutModule::URI_MUST_LOAD_IN_EXTENSION_PROCESS) {
return mRemoteType == EXTENSION_REMOTE_TYPE;
}
return true;
}
if (!mRemoteTypeIsolationPrincipal ||
RemoteTypePrefix(mRemoteType) != FISSION_WEB_REMOTE_TYPE) {
return true;
}
// Web content can contain extension content frames, so a content process may
// send us an extension's principal.
auto* addonPolicy = BasePrincipal::Cast(aPrincipal)->AddonPolicy();
if (addonPolicy) {
return true;
}
// Ensure that the expected site-origin matches the one specified by our
// mRemoteTypeIsolationPrincipal.
nsAutoCString siteOriginNoSuffix;
if (NS_FAILED(aPrincipal->GetSiteOriginNoSuffix(siteOriginNoSuffix))) {
return false;
}
nsAutoCString remoteTypeSiteOriginNoSuffix;
if (NS_FAILED(mRemoteTypeIsolationPrincipal->GetSiteOriginNoSuffix(
remoteTypeSiteOriginNoSuffix))) {
return false;
}
return remoteTypeSiteOriginNoSuffix.Equals(siteOriginNoSuffix);
}
mozilla::ipc::IPCResult ContentParent::RecvRemovePermission(
nsIPrincipal* aPrincipal, const nsACString& aPermissionType,
nsresult* aRv) {
if (!aPrincipal) {
return IPC_FAIL(this, "No principal");
}
if (!ValidatePrincipal(aPrincipal)) {
LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__);
}
*aRv = Permissions::RemovePermission(aPrincipal, aPermissionType);
return IPC_OK();
}
/*static*/
already_AddRefed<RemoteBrowser> ContentParent::CreateBrowser(
const TabContext& aContext, Element* aFrameElement,
const nsACString& aRemoteType, BrowsingContext* aBrowsingContext,
ContentParent* aOpenerContentParent) {
AUTO_PROFILER_LABEL("ContentParent::CreateBrowser", OTHER);
MOZ_DIAGNOSTIC_ASSERT(
!aBrowsingContext->Canonical()->GetBrowserParent(),
"BrowsingContext must not have BrowserParent, or have previous "
"BrowserParent cleared");
nsAutoCString remoteType(aRemoteType);
if (remoteType.IsEmpty()) {
remoteType = DEFAULT_REMOTE_TYPE;
}
TabId tabId(nsContentUtils::GenerateTabId());
nsIDocShell* docShell = GetOpenerDocShellHelper(aFrameElement);
TabId openerTabId;
if (docShell) {
openerTabId = BrowserParent::GetTabIdFrom(docShell);
}
RefPtr<ContentParent> constructorSender;
MOZ_RELEASE_ASSERT(XRE_IsParentProcess(),
"Cannot allocate BrowserParent in content process");
if (aOpenerContentParent && !aOpenerContentParent->IsShuttingDown()) {
constructorSender = aOpenerContentParent;
} else {
if (aContext.IsJSPlugin()) {
constructorSender = GetNewOrUsedJSPluginProcess(
aContext.JSPluginId(), PROCESS_PRIORITY_FOREGROUND);
} else {
constructorSender = GetNewOrUsedBrowserProcess(
remoteType, aBrowsingContext->Group(), PROCESS_PRIORITY_FOREGROUND);
}
if (!constructorSender) {
return nullptr;
}
}
aBrowsingContext->SetEmbedderElement(aFrameElement);
// Ensure that the process which we're using to launch is set as the host
// process for this BrowsingContextGroup.
aBrowsingContext->Group()->EnsureHostProcess(constructorSender);
nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
docShell->GetTreeOwner(getter_AddRefs(treeOwner));
if (!treeOwner) {
return nullptr;
}
nsCOMPtr<nsIWebBrowserChrome> wbc = do_GetInterface(treeOwner);
if (!wbc) {
return nullptr;
}
uint32_t chromeFlags = 0;
wbc->GetChromeFlags(&chromeFlags);
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
if (loadContext && loadContext->UsePrivateBrowsing()) {
chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
}
if (loadContext && loadContext->UseRemoteTabs()) {
chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW;
}
if (loadContext && loadContext->UseRemoteSubframes()) {
chromeFlags |= nsIWebBrowserChrome::CHROME_FISSION_WINDOW;
}
if (tabId == 0) {
return nullptr;
}
aBrowsingContext->Canonical()->SetOwnerProcessId(
constructorSender->ChildID());
RefPtr<BrowserParent> browserParent =
new BrowserParent(constructorSender, tabId, aContext,
aBrowsingContext->Canonical(), chromeFlags);
// Open a remote endpoint for our PBrowser actor.
ManagedEndpoint<PBrowserChild> childEp =
constructorSender->OpenPBrowserEndpoint(browserParent);
if (NS_WARN_IF(!childEp.IsValid())) {
return nullptr;
}
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (NS_WARN_IF(!cpm)) {
return nullptr;
}
cpm->RegisterRemoteFrame(browserParent);
nsCOMPtr<nsIPrincipal> initialPrincipal =
NullPrincipal::Create(aBrowsingContext->OriginAttributesRef());
WindowGlobalInit windowInit = WindowGlobalActor::AboutBlankInitializer(
aBrowsingContext, initialPrincipal);
RefPtr<WindowGlobalParent> windowParent =
WindowGlobalParent::CreateDisconnected(windowInit);
if (NS_WARN_IF(!windowParent)) {
return nullptr;
}
// Open a remote endpoint for the initial PWindowGlobal actor.
ManagedEndpoint<PWindowGlobalChild> windowEp =
browserParent->OpenPWindowGlobalEndpoint(windowParent);
if (NS_WARN_IF(!windowEp.IsValid())) {
return nullptr;
}
// Tell the content process to set up its PBrowserChild.
bool ok = constructorSender->SendConstructBrowser(
std::move(childEp), std::move(windowEp), tabId,
aContext.AsIPCTabContext(), windowInit, chromeFlags,
constructorSender->ChildID(), constructorSender->IsForBrowser(),
/* aIsTopLevel */ true);
if (NS_WARN_IF(!ok)) {
return nullptr;
}
// Ensure that we're marked as the current BrowserParent on our
// CanonicalBrowsingContext.
aBrowsingContext->Canonical()->SetCurrentBrowserParent(browserParent);
windowParent->Init();
RefPtr<BrowserHost> browserHost = new BrowserHost(browserParent);
browserParent->SetOwnerElement(aFrameElement);
return browserHost.forget();
}
void ContentParent::GetAll(nsTArray<ContentParent*>& aArray) {
aArray.Clear();
for (auto* cp : AllProcesses(eLive)) {
aArray.AppendElement(cp);
}
}
void ContentParent::GetAllEvenIfDead(nsTArray<ContentParent*>& aArray) {
aArray.Clear();
for (auto* cp : AllProcesses(eAll)) {
aArray.AppendElement(cp);
}
}
void ContentParent::BroadcastStringBundle(
const StringBundleDescriptor& aBundle) {
AutoTArray<StringBundleDescriptor, 1> array;
array.AppendElement(aBundle);
for (auto* cp : AllProcesses(eLive)) {
Unused << cp->SendRegisterStringBundles(array);
}
}
void ContentParent::BroadcastFontListChanged() {
for (auto* cp : AllProcesses(eLive)) {
Unused << cp->SendFontListChanged();
}
}
void ContentParent::BroadcastShmBlockAdded(uint32_t aGeneration,
uint32_t aIndex) {
auto* pfl = gfxPlatformFontList::PlatformFontList();
for (auto* cp : AllProcesses(eLive)) {
base::SharedMemoryHandle handle =
pfl->ShareShmBlockToProcess(aIndex, cp->Pid());
if (handle == base::SharedMemory::NULLHandle()) {
// If something went wrong here, we just skip it; the child will need to
// request the block as needed, at some performance cost.
continue;
}
Unused << cp->SendFontListShmBlockAdded(aGeneration, aIndex,
std::move(handle));
}
}
void ContentParent::BroadcastThemeUpdate(widget::ThemeChangeKind aKind) {
const FullLookAndFeel& lnf = *RemoteLookAndFeel::ExtractData();
for (auto* cp : AllProcesses(eLive)) {
Unused << cp->SendThemeChanged(lnf, aKind);
}
}
/*static */
void ContentParent::BroadcastMediaCodecsSupportedUpdate(
RemoteDecodeIn aLocation, const media::MediaCodecsSupported& aSupported) {
// Merge incoming codec support with existing support list
media::MCSInfo::AddSupport(aSupported);
auto support = media::MCSInfo::GetSupport();
// Update processes
sCodecsSupported[aLocation] = support;
for (auto* cp : AllProcesses(eAll)) {
Unused << cp->SendUpdateMediaCodecsSupported(aLocation, support);
}
// Generate + save support string for display in about:support
nsCString supportString;
media::MCSInfo::GetMediaCodecsSupportedString(supportString, support);
gfx::gfxVars::SetCodecSupportInfo(supportString);
}
const nsACString& ContentParent::GetRemoteType() const { return mRemoteType; }
static StaticRefPtr<nsIAsyncShutdownClient> sXPCOMShutdownClient;
static StaticRefPtr<nsIAsyncShutdownClient> sProfileBeforeChangeClient;
static StaticRefPtr<nsIAsyncShutdownClient> sQuitApplicationGrantedClient;
void ContentParent::Init() {
MOZ_ASSERT(sXPCOMShutdownClient);
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
size_t length = ArrayLength(sObserverTopics);
for (size_t i = 0; i < length; ++i) {
obs->AddObserver(this, sObserverTopics[i], false);
}
}
if (obs) {
nsAutoString cpId;
cpId.AppendInt(static_cast<uint64_t>(this->ChildID()));
obs->NotifyObservers(static_cast<nsIObserver*>(this), "ipc:content-created",
cpId.get());
}
#ifdef ACCESSIBILITY
// If accessibility is running in chrome process then start it in content
// process.
if (GetAccService()) {
# if defined(XP_WIN)
// Don't init content a11y if we detect an incompat version of JAWS in use.
if (!mozilla::a11y::Compatibility::IsOldJAWS()) {
Unused << SendActivateA11y(
::GetCurrentThreadId(),
a11y::MsaaAccessible::GetContentProcessIdFor(ChildID()));
}
# else
Unused << SendActivateA11y(0, 0);
# endif
}
#endif // #ifdef ACCESSIBILITY
Unused << SendInitProfiler(ProfilerParent::CreateForProcess(OtherPid()));
RefPtr<GeckoMediaPluginServiceParent> gmps(
GeckoMediaPluginServiceParent::GetSingleton());
gmps->UpdateContentProcessGMPCapabilities(this);
// Flush any pref updates that happened during launch and weren't
// included in the blobs set up in BeginSubprocessLaunch.
for (const Pref& pref : mQueuedPrefs) {
Unused << NS_WARN_IF(!SendPreferenceUpdate(pref));
}
mQueuedPrefs.Clear();
Unused << SendInitNextGenLocalStorageEnabled(NextGenLocalStorageEnabled());
}
// Note that for E10S we can get a false here that will be overruled by
// TryToRecycleE10SOnly as late as MaybeBeginShutdown. We cannot really
// foresee its result here.
bool ContentParent::CheckTabDestroyWillKeepAlive(
uint32_t aExpectedBrowserCount) {
return ManagedPBrowserParent().Count() != aExpectedBrowserCount ||
ShouldKeepProcessAlive();
}
void ContentParent::NotifyTabWillDestroy() {
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)
#if !defined(MOZ_WIDGET_ANDROID)
/* on Android we keep processes alive more agressively, see
NotifyTabDestroying where we omit MaybeBeginShutdown */
|| (/* we cannot trust CheckTabDestroyWillKeepAlive in E10S mode */
mozilla::FissionAutostart() &&
!CheckTabDestroyWillKeepAlive(mNumDestroyingTabs + 1))
#endif
) {
// Once we notify the impending shutdown, the content process will stop
// to process content JS on interrupt (among other things), so we need to
// be sure that the process will not be re-used after this point.
// The inverse is harmless, that is if we decide later to shut it down
// but did not notify here, it will be just notified later (but in rare
// cases too late to avoid a hang).
NotifyImpendingShutdown();
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
mNotifiedImpendingShutdownOnTabWillDestroy = true;
#endif
}
}
void ContentParent::MaybeBeginShutDown(uint32_t aExpectedBrowserCount,
bool aSendShutDown) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
("MaybeBeginShutdown %p, %u vs %u", this,
ManagedPBrowserParent().Count(), aExpectedBrowserCount));
MOZ_ASSERT(NS_IsMainThread());
// Both CheckTabDestroyWillKeepAlive and TryToRecycleE10SOnly will return
// false if IsInOrBeyond(AppShutdownConfirmed), so if the parent shuts
// down we will always shutdown the child.
if (CheckTabDestroyWillKeepAlive(aExpectedBrowserCount) ||
TryToRecycleE10SOnly()) {
return;
}
MOZ_LOG(
ContentParent::GetLog(), LogLevel::Debug,
("Beginning ContentParent Shutdown %p (%s)", this, mRemoteType.get()));
// We're dying now, prevent anything from re-using this process.
MarkAsDead();
SignalImpendingShutdownToContentJS();
StartForceKillTimer();
if (aSendShutDown) {
MaybeAsyncSendShutDownMessage();
}
}
void ContentParent::MaybeAsyncSendShutDownMessage() {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
("MaybeAsyncSendShutDownMessage %p", this));
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(sRecycledE10SProcess != this);
#ifdef DEBUG
// Calling this below while the lock is acquired will deadlock.
bool shouldKeepProcessAlive = ShouldKeepProcessAlive();
#endif
{
MutexAutoLock lock(mThreadsafeHandle->mMutex);
MOZ_ASSERT_IF(!mThreadsafeHandle->mRemoteWorkerActorCount,
!shouldKeepProcessAlive);
if (mThreadsafeHandle->mRemoteWorkerActorCount) {
return;
}
MOZ_ASSERT(!mThreadsafeHandle->mShutdownStarted);
mThreadsafeHandle->mShutdownStarted = true;
}
// In the case of normal shutdown, send a shutdown message to child to
// allow it to perform shutdown tasks.
GetCurrentSerialEventTarget()->Dispatch(NewRunnableMethod<ShutDownMethod>(
"dom::ContentParent::ShutDownProcess", this,
&ContentParent::ShutDownProcess, SEND_SHUTDOWN_MESSAGE));
}
void MaybeLogBlockShutdownDiagnostics(ContentParent* aSelf, const char* aMsg,
const char* aFile, int32_t aLine) {
#if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED)
if (aSelf->IsBlockingShutdown()) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Info,
("ContentParent: id=%p - %s at %s(%d)", aSelf, aMsg, aFile, aLine));
}
#else
Unused << aSelf;
Unused << aMsg;
Unused << aFile;
Unused << aLine;
#endif
}
bool ContentParent::ShutDownProcess(ShutDownMethod aMethod) {
bool result = false;
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("ShutDownProcess: %p", this));
// NB: must MarkAsDead() here so that this isn't accidentally
// returned from Get*() while in the midst of shutdown.
MarkAsDead();
// Shutting down by sending a shutdown message works differently than the
// other methods. We first call Shutdown() in the child. After the child is
// ready, it calls FinishShutdown() on us. Then we close the channel.
if (aMethod == SEND_SHUTDOWN_MESSAGE) {
if (!mShutdownPending) {
if (CanSend()) {
// Stop sending input events with input priority when shutting down.
SetInputPriorityEventEnabled(false);
// Send a high priority announcement first. If this fails, SendShutdown
// will also fail.
Unused << SendShutdownConfirmedHP();
// Send the definite message with normal priority.
if (SendShutdown()) {
MaybeLogBlockShutdownDiagnostics(
this, "ShutDownProcess: Sent shutdown message.", __FILE__,
__LINE__);
mShutdownPending = true;
// Start the force-kill timer if we haven't already.
// This can happen if we shutdown a process while launching or
// because it is removed from the cached processes pool.
if (!mForceKillTimer) {
SignalImpendingShutdownToContentJS();
StartForceKillTimer();
}
result = true;
} else {
MaybeLogBlockShutdownDiagnostics(
this, "ShutDownProcess: !!! Send shutdown message failed! !!!",
__FILE__, __LINE__);
}
} else {
MaybeLogBlockShutdownDiagnostics(
this, "ShutDownProcess: !!! !CanSend !!!", __FILE__, __LINE__);
}
} else {
MaybeLogBlockShutdownDiagnostics(
this, "ShutDownProcess: Shutdown already pending.", __FILE__,
__LINE__);
result = true;
}
// If call was not successful, the channel must have been broken
// somehow, and we will clean up the error in ActorDestroy.
return result;
}
using mozilla::dom::quota::QuotaManagerService;
if (QuotaManagerService* qms = QuotaManagerService::GetOrCreate()) {
qms->AbortOperationsForProcess(mChildID);
}
// If Close() fails with an error, we'll end up back in this function, but
// with aMethod = CLOSE_CHANNEL_WITH_ERROR.
if (aMethod == CLOSE_CHANNEL) {
if (!mCalledClose) {
MaybeLogBlockShutdownDiagnostics(
this, "ShutDownProcess: Closing channel.", __FILE__, __LINE__);
// Close() can only be called once: It kicks off the destruction
// sequence.
mCalledClose = true;
Close();
}
result = true;
}
// A ContentParent object might not get freed until after XPCOM shutdown has
// shut down the cycle collector. But by then it's too late to release any
// CC'ed objects, so we need to null them out here, while we still can. See
// bug 899761.
ShutDownMessageManager();
return result;
}
mozilla::ipc::IPCResult ContentParent::RecvNotifyShutdownSuccess() {
if (!mShutdownPending) {
return IPC_FAIL(this, "RecvNotifyShutdownSuccess without mShutdownPending");
}
mIsNotifiedShutdownSuccess = true;
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvFinishShutdown() {
if (!mShutdownPending) {
return IPC_FAIL(this, "RecvFinishShutdown without mShutdownPending");
}
// At this point, we already called ShutDownProcess once with
// SEND_SHUTDOWN_MESSAGE. To actually close the channel, we call
// ShutDownProcess again with CLOSE_CHANNEL.
if (mCalledClose) {
MaybeLogBlockShutdownDiagnostics(
this, "RecvFinishShutdown: Channel already closed.", __FILE__,
__LINE__);
}
ShutDownProcess(CLOSE_CHANNEL);
return IPC_OK();
}
void ContentParent::ShutDownMessageManager() {
if (!mMessageManager) {
return;
}
mMessageManager->ReceiveMessage(mMessageManager, nullptr,
CHILD_PROCESS_SHUTDOWN_MESSAGE, false,
nullptr, nullptr, IgnoreErrors());
mMessageManager->SetOsPid(-1);
mMessageManager->Disconnect();
mMessageManager = nullptr;
}
void ContentParent::AddToPool(nsTArray<ContentParent*>& aPool) {
MOZ_DIAGNOSTIC_ASSERT(!mIsInPool);
AssertAlive();
MOZ_DIAGNOSTIC_ASSERT(!mCalledKillHard);
aPool.AppendElement(this);
mIsInPool = true;
}
void ContentParent::RemoveFromPool(nsTArray<ContentParent*>& aPool) {
MOZ_DIAGNOSTIC_ASSERT(mIsInPool);
aPool.RemoveElement(this);
mIsInPool = false;
}
void ContentParent::AssertNotInPool() {
MOZ_RELEASE_ASSERT(!mIsInPool);
MOZ_RELEASE_ASSERT(sRecycledE10SProcess != this);
if (IsForJSPlugin()) {
MOZ_RELEASE_ASSERT(!sJSPluginContentParents ||
!sJSPluginContentParents->Get(mJSPluginID));
} else {
MOZ_RELEASE_ASSERT(
!sBrowserContentParents ||
!sBrowserContentParents->Contains(mRemoteType) ||
!sBrowserContentParents->Get(mRemoteType)->Contains(this));
for (const auto& group : mGroups) {
MOZ_RELEASE_ASSERT(group->GetHostProcess(mRemoteType) != this,
"still a host process for one of our groups?");
}
}
}
void ContentParent::AssertAlive() {
MOZ_DIAGNOSTIC_ASSERT(!mNotifiedImpendingShutdownOnTabWillDestroy);
MOZ_DIAGNOSTIC_ASSERT(!mIsSignaledImpendingShutdown);
MOZ_DIAGNOSTIC_ASSERT(!IsDead());
}
void ContentParent::RemoveFromList() {
if (IsForJSPlugin()) {
if (sJSPluginContentParents) {
sJSPluginContentParents->Remove(mJSPluginID);
if (!sJSPluginContentParents->Count()) {
sJSPluginContentParents = nullptr;
}
}
return;
}
if (!mIsInPool) {
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
AssertNotInPool();
#endif
return;
}
// Ensure that this BrowsingContextGroup is no longer used to host new
// documents from any associated BrowsingContextGroups. It may become a host
// again in the future, if it is restored to the pool.
for (const auto& group : mGroups) {
group->RemoveHostProcess(this);
}
StopRecyclingE10SOnly(/* aForeground */ false);
if (sBrowserContentParents) {
if (auto entry = sBrowserContentParents->Lookup(mRemoteType)) {
const auto& contentParents = entry.Data();
RemoveFromPool(*contentParents);
if (contentParents->IsEmpty()) {
entry.Remove();
}
}
if (sBrowserContentParents->IsEmpty()) {
delete sBrowserContentParents;
sBrowserContentParents = nullptr;
}
}
}
void ContentParent::MarkAsDead() {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
("Marking ContentProcess %p as dead", this));
MOZ_DIAGNOSTIC_ASSERT(!sInProcessSelector);
RemoveFromList();
// Prevent this process from being re-used.
PreallocatedProcessManager::Erase(this);
StopRecyclingE10SOnly(false);
#ifdef MOZ_WIDGET_ANDROID
if (IsAlive()) {
// We're intentionally killing the content process at this point to ensure
// that we never have a "dead" content process sitting around and occupying
// an Android Service.
nsCOMPtr<nsIEventTarget> launcherThread(GetIPCLauncher());
MOZ_ASSERT(launcherThread);
auto procType = java::GeckoProcessType::CONTENT();
auto selector =
java::GeckoProcessManager::Selector::New(procType, OtherPid());
launcherThread->Dispatch(NS_NewRunnableFunction(
"ContentParent::MarkAsDead",
[selector =
java::GeckoProcessManager::Selector::GlobalRef(selector)]() {
java::GeckoProcessManager::ShutdownProcess(selector);
}));
}
#endif
if (mScriptableHelper) {
static_cast<ScriptableCPInfo*>(mScriptableHelper.get())->ProcessDied();
mScriptableHelper = nullptr;
}
mLifecycleState = LifecycleState::DEAD;
}
void ContentParent::OnChannelError() {
RefPtr<ContentParent> kungFuDeathGrip(this);
PContentParent::OnChannelError();
}
void ContentParent::ProcessingError(Result aCode, const char* aReason) {
if (MsgDropped == aCode) {
return;
}
#ifndef FUZZING
// Other errors are big deals.
KillHard(aReason);
#endif
}
void ContentParent::ActorDestroy(ActorDestroyReason why) {
MOZ_RELEASE_ASSERT(mSelfRef);
if (mForceKillTimer) {
mForceKillTimer->Cancel();
mForceKillTimer = nullptr;
}
// Signal shutdown completion regardless of error state, so we can
// finish waiting in the xpcom-shutdown/profile-before-change observer.
RemoveShutdownBlockers();
if (mHangMonitorActor) {
ProcessHangMonitor::RemoveProcess(mHangMonitorActor);
mHangMonitorActor = nullptr;
}
RefPtr<FileSystemSecurity> fss = FileSystemSecurity::Get();
if (fss) {
fss->Forget(ChildID());
}
if (why == NormalShutdown && !mCalledClose) {
// If we shut down normally but haven't called Close, assume somebody
// else called Close on us. In that case, we still need to call
// ShutDownProcess below to perform other necessary clean up.
mCalledClose = true;
}
// Make sure we always clean up.
ShutDownProcess(why == NormalShutdown ? CLOSE_CHANNEL
: CLOSE_CHANNEL_WITH_ERROR);
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
size_t length = ArrayLength(sObserverTopics);
for (size_t i = 0; i < length; ++i) {
obs->RemoveObserver(static_cast<nsIObserver*>(this), sObserverTopics[i]);
}
}
// remove the global remote preferences observers
Preferences::RemoveObserver(this, "");
gfxVars::RemoveReceiver(this);
if (GPUProcessManager* gpu = GPUProcessManager::Get()) {
// Note: the manager could have shutdown already.
gpu->RemoveListener(this);
}
RecvRemoveGeolocationListener();
// Destroy our JSProcessActors, and reject any pending queries.
JSActorDidDestroy();
if (obs) {
RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
props->SetPropertyAsUint64(u"childID"_ns, mChildID);
if (AbnormalShutdown == why) {
Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT, "content"_ns,
1);
props->SetPropertyAsBool(u"abnormal"_ns, true);
nsAutoString dumpID;
// There's a window in which child processes can crash
// after IPC is established, but before a crash reporter
// is created.
if (mCrashReporter) {
// if mCreatedPairedMinidumps is true, we've already generated
// parent/child dumps for desktop crashes.
if (!mCreatedPairedMinidumps) {
#if defined(XP_MACOSX)
RefPtr<nsAvailableMemoryWatcherBase> memWatcher;
memWatcher = nsAvailableMemoryWatcherBase::GetSingleton();
memWatcher->AddChildAnnotations(mCrashReporter);
#endif
mCrashReporter->GenerateCrashReport(OtherPid());
}
if (mCrashReporter->HasMinidump()) {
dumpID = mCrashReporter->MinidumpID();
}
} else {
HandleOrphanedMinidump(&dumpID);
}
if (!dumpID.IsEmpty()) {
props->SetPropertyAsAString(u"dumpID"_ns, dumpID);
}
}
nsAutoString cpId;
cpId.AppendInt(static_cast<uint64_t>(this->ChildID()));
obs->NotifyObservers((nsIPropertyBag2*)props, "ipc:content-shutdown",
cpId.get());
}
// Remove any and all idle listeners.
if (mIdleListeners.Length() > 0) {
nsCOMPtr<nsIUserIdleService> idleService =
do_GetService("@mozilla.org/widget/useridleservice;1");
if (idleService) {
RefPtr<ParentIdleListener> listener;
for (const auto& lentry : mIdleListeners) {
listener = static_cast<ParentIdleListener*>(lentry.get());
idleService->RemoveIdleObserver(listener, listener->mTime);
}
}
mIdleListeners.Clear();
}
MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
("destroying Subprocess in ActorDestroy: ContentParent %p "
"mSubprocess %p handle %" PRIuPTR,
this, mSubprocess,
mSubprocess ? (uintptr_t)mSubprocess->GetChildProcessHandle() : -1));
// FIXME (bug 1520997): does this really need an additional dispatch?
if (GetCurrentSerialEventTarget()) {
GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
"DelayedDeleteSubprocessRunnable", [subprocess = mSubprocess] {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("destroyed Subprocess in ActorDestroy: Subprocess %p handle "
"%" PRIuPTR,
subprocess,
subprocess ? (uintptr_t)subprocess->GetChildProcessHandle()
: -1));
subprocess->Destroy();
}));
}
mSubprocess = nullptr;
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (cpm) {
cpm->RemoveContentProcess(this->ChildID());
}
if (mDriverCrashGuard) {
mDriverCrashGuard->NotifyCrashed();
}
// Unregister all the BlobURLs registered by the ContentChild.
for (uint32_t i = 0; i < mBlobURLs.Length(); ++i) {
BlobURLProtocolHandler::RemoveDataEntry(mBlobURLs[i]);
}
mBlobURLs.Clear();
#if defined(XP_WIN) && defined(ACCESSIBILITY)
a11y::MsaaAccessible::ReleaseContentProcessIdFor(ChildID());
#endif
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
AssertNotInPool();
#endif
// As this process is going away, ensure that every BrowsingContext hosted by
// it has been detached, and every BrowsingContextGroup has been fully
// unsubscribed.
BrowsingContext::DiscardFromContentParent(this);
const nsTHashSet<RefPtr<BrowsingContextGroup>> groups = std::move(mGroups);
for (const auto& group : groups) {
group->Unsubscribe(this);
}
MOZ_DIAGNOSTIC_ASSERT(mGroups.IsEmpty());
mPendingLoadStates.Clear();
}
void ContentParent::ActorDealloc() { mSelfRef = nullptr; }
bool ContentParent::TryToRecycleE10SOnly() {
// Only try to recycle "web" content processes, as other remote types are
// generally more unique, and cannot be effectively re-used. This is disabled
// with Fission, as "web" content processes are no longer frequently used.
//
// Disabling the process pre-allocator will also disable process recycling,
// allowing for more consistent process counts under testing.
if (mRemoteType != DEFAULT_REMOTE_TYPE || mozilla::FissionAutostart() ||
!PreallocatedProcessManager::Enabled()) {
return false;
}
// This life time check should be replaced by a memory health check (memory
// usage + fragmentation).
// Note that this is specifically to help with edge cases that rapidly
// create-and-destroy processes
const double kMaxLifeSpan = 5;
MOZ_LOG(
ContentParent::GetLog(), LogLevel::Debug,
("TryToRecycle ContentProcess %p (%u) with lifespan %f seconds", this,
(unsigned int)ChildID(), (TimeStamp::Now() - mActivateTS).ToSeconds()));
if (mCalledKillHard || !IsAlive() ||
(TimeStamp::Now() - mActivateTS).ToSeconds() > kMaxLifeSpan) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("TryToRecycle did not recycle %p", this));
// It's possible that the process was already cached, and we're being called
// from a different path, and we're now past kMaxLifeSpan (or some other).
// Ensure that if we're going to kill this process we don't recycle it.
StopRecyclingE10SOnly(/* aForeground */ false);
return false;
}
if (!sRecycledE10SProcess) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("TryToRecycle began recycling %p", this));
sRecycledE10SProcess = this;
ProcessPriorityManager::SetProcessPriority(this,
PROCESS_PRIORITY_BACKGROUND);
return true;
}
if (sRecycledE10SProcess == this) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("TryToRecycle continue recycling %p", this));
return true;
}
// Some other process is already being recycled, just shut this one down.
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("TryToRecycle did not recycle %p (already recycling %p)", this,
sRecycledE10SProcess.get()));
return false;
}
void ContentParent::StopRecyclingE10SOnly(bool aForeground) {
if (sRecycledE10SProcess != this) {
return;
}
sRecycledE10SProcess = nullptr;
if (aForeground) {
ProcessPriorityManager::SetProcessPriority(this,
PROCESS_PRIORITY_FOREGROUND);
}
}
bool ContentParent::HasActiveWorkerOrJSPlugin() {
if (IsForJSPlugin()) {
return true;
}
// If we have active workers, we need to stay alive.
{
MutexAutoLock lock(mThreadsafeHandle->mMutex);
if (mThreadsafeHandle->mRemoteWorkerActorCount) {
return true;
}
}
return false;
}
bool ContentParent::ShouldKeepProcessAlive() {
if (HasActiveWorkerOrJSPlugin()) {
return true;
}
if (mNumKeepaliveCalls > 0) {
return true;
}
if (IsLaunching()) {
return true;
}
// If we have already been marked as dead, don't prevent shutdown.
if (IsDead()) {
return false;
}
// If everything is going down, there is no need to keep us alive, neither.
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
return false;
}
if (!sBrowserContentParents) {
return false;
}
auto contentParents = sBrowserContentParents->Get(mRemoteType);
if (!contentParents) {
return false;
}
// We might want to keep some content processes alive for performance reasons.
// e.g. test runs and privileged content process for some about: pages.
// We don't want to alter behavior if the pref is not set, so default to 0.
int32_t processesToKeepAlive = 0;
nsAutoCString keepAlivePref("dom.ipc.keepProcessesAlive.");
if (StringBeginsWith(mRemoteType, FISSION_WEB_REMOTE_TYPE) &&
xpc::IsInAutomation()) {
keepAlivePref.Append(FISSION_WEB_REMOTE_TYPE);
keepAlivePref.AppendLiteral(".perOrigin");
} else {
keepAlivePref.Append(mRemoteType);
}
if (NS_FAILED(
Preferences::GetInt(keepAlivePref.get(), &processesToKeepAlive))) {
return false;
}
int32_t numberOfAliveProcesses = contentParents->Length();
return numberOfAliveProcesses <= processesToKeepAlive;
}
void ContentParent::NotifyTabDestroying() {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("NotifyTabDestroying %p:", this));
// There can be more than one PBrowser for a given app process
// because of popup windows. PBrowsers can also destroy
// concurrently. When all the PBrowsers are destroying, kick off
// another task to ensure the child process *really* shuts down,
// even if the PBrowsers themselves never finish destroying.
++mNumDestroyingTabs;
/**
* We intentionally skip this code on Android:
* 1. Android has a fixed upper bound on the number of content processes, so
* we prefer to re-use them whenever possible (as opposed to letting an
* old process wind down while we launch a new one).
* 2. GeckoView always hard-kills content processes (and if it does not,
* Android itself will), so we don't concern ourselves with the ForceKill
* timer either.
*/
#if !defined(MOZ_WIDGET_ANDROID)
MaybeBeginShutDown(/* aExpectedBrowserCount */ mNumDestroyingTabs,
/* aSendShutDown */ false);
#endif // !defined(MOZ_WIDGET_ANDROID)
}
void ContentParent::AddKeepAlive() {
AssertAlive();
// Something wants to keep this content process alive.
++mNumKeepaliveCalls;
}
void ContentParent::RemoveKeepAlive() {
MOZ_DIAGNOSTIC_ASSERT(mNumKeepaliveCalls > 0);
--mNumKeepaliveCalls;
MaybeBeginShutDown();
}
void ContentParent::StartForceKillTimer() {
if (mForceKillTimer || !CanSend()) {
return;
}
int32_t timeoutSecs = StaticPrefs::dom_ipc_tabs_shutdownTimeoutSecs();
if (timeoutSecs > 0) {
NS_NewTimerWithFuncCallback(getter_AddRefs(mForceKillTimer),
ContentParent::ForceKillTimerCallback, this,
timeoutSecs * 1000, nsITimer::TYPE_ONE_SHOT,
"dom::ContentParent::StartForceKillTimer");
MOZ_ASSERT(mForceKillTimer);
}
}
void ContentParent::NotifyTabDestroyed(const TabId& aTabId,
bool aNotifiedDestroying) {
if (aNotifiedDestroying) {
--mNumDestroyingTabs;
}
nsTArray<PContentPermissionRequestParent*> parentArray =
nsContentPermissionUtils::GetContentPermissionRequestParentById(aTabId);
// Need to close undeleted ContentPermissionRequestParents before tab is
// closed.
for (auto& permissionRequestParent : parentArray) {
Unused << PContentPermissionRequestParent::Send__delete__(
permissionRequestParent);
}
// There can be more than one PBrowser for a given app process
// because of popup windows. When the last one closes, shut
// us down.
MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
("NotifyTabDestroyed %p", this));
MaybeBeginShutDown(/* aExpectedBrowserCount */ 1);
}
TestShellParent* ContentParent::CreateTestShell() {
return static_cast<TestShellParent*>(SendPTestShellConstructor());
}
bool ContentParent::DestroyTestShell(TestShellParent* aTestShell) {
return PTestShellParent::Send__delete__(aTestShell);
}
TestShellParent* ContentParent::GetTestShellSingleton() {
PTestShellParent* p = LoneManagedOrNullAsserts(ManagedPTestShellParent());
return static_cast<TestShellParent*>(p);
}
#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
// Append the sandbox command line parameters that are not static. i.e.,
// parameters that can be different for different child processes.
void ContentParent::AppendDynamicSandboxParams(
std::vector<std::string>& aArgs) {
// For file content processes
if (GetRemoteType() == FILE_REMOTE_TYPE) {
MacSandboxInfo::AppendFileAccessParam(aArgs, true);
}
}
// Generate the static sandbox command line parameters and store
// them in the provided params vector to be used each time a new
// content process is launched.
static void CacheSandboxParams(std::vector<std::string>& aCachedParams) {
// This must only be called once and we should
// be starting with an empty list of parameters.
MOZ_ASSERT(aCachedParams.empty());
MacSandboxInfo info;
info.type = MacSandboxType_Content;
info.level = GetEffectiveContentSandboxLevel();
// Sandbox logging
if (Preferences::GetBool("security.sandbox.logging.enabled") ||
PR_GetEnv("MOZ_SANDBOX_LOGGING")) {
info.shouldLog = true;
}
// Audio access
if (!StaticPrefs::media_cubeb_sandbox()) {
info.hasAudio = true;
}
// Window server access. If the disconnect-windowserver pref is not
// "true" or out-of-process WebGL is not enabled, allow window server
// access in the sandbox policy.
if (!Preferences::GetBool(
"security.sandbox.content.mac.disconnect-windowserver") ||
!Preferences::GetBool("webgl.out-of-process")) {
info.hasWindowServer = true;
}
// .app path (normalized)
nsAutoCString appPath;
if (!nsMacUtilsImpl::GetAppPath(appPath)) {
MOZ_CRASH("Failed to get app dir paths");
}
info.appPath = appPath.get();
// TESTING_READ_PATH1
nsAutoCString testingReadPath1;
Preferences::GetCString("security.sandbox.content.mac.testing_read_path1",
testingReadPath1);
if (!testingReadPath1.IsEmpty()) {
info.testingReadPath1 = testingReadPath1.get();
}
// TESTING_READ_PATH2
nsAutoCString testingReadPath2;
Preferences::GetCString("security.sandbox.content.mac.testing_read_path2",
testingReadPath2);
if (!testingReadPath2.IsEmpty()) {
info.testingReadPath2 = testingReadPath2.get();
}
// TESTING_READ_PATH3, TESTING_READ_PATH4. In development builds,
// these are used to whitelist the repo dir and object dir respectively.
nsresult rv;
if (mozilla::IsDevelopmentBuild()) {
// Repo dir
nsCOMPtr<nsIFile> repoDir;
rv = nsMacUtilsImpl::GetRepoDir(getter_AddRefs(repoDir));
if (NS_FAILED(rv)) {
MOZ_CRASH("Failed to get path to repo dir");
}
nsCString repoDirPath;
Unused << repoDir->GetNativePath(repoDirPath);
info.testingReadPath3 = repoDirPath.get();
// Object dir
nsCOMPtr<nsIFile> objDir;
rv = nsMacUtilsImpl::GetObjDir(getter_AddRefs(objDir));
if (NS_FAILED(rv)) {
MOZ_CRASH("Failed to get path to build object dir");
}
nsCString objDirPath;
Unused << objDir->GetNativePath(objDirPath);
info.testingReadPath4 = objDirPath.get();
}
// DEBUG_WRITE_DIR
# ifdef DEBUG
// For bloat/leak logging or when a content process dies intentionally
// (|NoteIntentionalCrash|) for tests, it wants to log that it did this.
// Allow writing to this location.
nsAutoCString bloatLogDirPath;
if (NS_SUCCEEDED(nsMacUtilsImpl::GetBloatLogDir(bloatLogDirPath))) {
info.debugWriteDir = bloatLogDirPath.get();
}
# endif // DEBUG
info.AppendAsParams(aCachedParams);
}
// Append sandboxing command line parameters.
void ContentParent::AppendSandboxParams(std::vector<std::string>& aArgs) {
MOZ_ASSERT(sMacSandboxParams != nullptr);
// An empty sMacSandboxParams indicates this is the
// first invocation and we don't have cached params yet.
if (sMacSandboxParams->empty()) {
CacheSandboxParams(*sMacSandboxParams);
MOZ_ASSERT(!sMacSandboxParams->empty());
}
// Append cached arguments.
aArgs.insert(aArgs.end(), sMacSandboxParams->begin(),
sMacSandboxParams->end());
// Append remaining arguments.
AppendDynamicSandboxParams(aArgs);
}
#endif // XP_MACOSX && MOZ_SANDBOX
bool ContentParent::BeginSubprocessLaunch(ProcessPriority aPriority) {
AUTO_PROFILER_LABEL("ContentParent::LaunchSubprocess", OTHER);
// Ensure we will not rush through our shutdown phases while launching.
// LaunchSubprocessReject will remove them in case of failure,
// otherwise ActorDestroy will take care.
AddShutdownBlockers();
if (!ContentProcessManager::GetSingleton()) {
MOZ_ASSERT(false, "Unable to acquire ContentProcessManager singleton!");
return false;
}
std::vector<std::string> extraArgs;
geckoargs::sChildID.Put(mChildID, extraArgs);
geckoargs::sIsForBrowser.Put(IsForBrowser(), extraArgs);
geckoargs::sNotForBrowser.Put(!IsForBrowser(), extraArgs);
// Prefs information is passed via anonymous shared memory to avoid bloating
// the command line.
// Instantiate the pref serializer. It will be cleaned up in
// `LaunchSubprocessReject`/`LaunchSubprocessResolve`.
mPrefSerializer = MakeUnique<mozilla::ipc::SharedPreferenceSerializer>();
if (!mPrefSerializer->SerializeToSharedMemory(GeckoProcessType_Content,
GetRemoteType())) {
NS_WARNING("SharedPreferenceSerializer::SerializeToSharedMemory failed");
MarkAsDead();
return false;
}
mPrefSerializer->AddSharedPrefCmdLineArgs(*mSubprocess, extraArgs);
// The JS engine does some computation during the initialization which can be
// shared across processes. We add command line arguments to pass a file
// handle and its content length, to minimize the startup time of content
// processes.
::mozilla::ipc::ExportSharedJSInit(*mSubprocess, extraArgs);
#if defined(XP_WIN) && defined(ACCESSIBILITY)
// Determining the accessibility resource ID causes problems with the sandbox,
// so we pass it on the command line as it is required very early in process
// start up. It is not required when the caching mechanism is being used.
if (!StaticPrefs::accessibility_cache_enabled_AtStartup()) {
// The accessibility resource ID may not be set in some cases, for example
// in xpcshell tests.
auto resourceId = mscom::ActCtxResource::GetAccessibilityResourceId();
if (resourceId) {
geckoargs::sA11yResourceId.Put(resourceId, extraArgs);
}
}
#endif
// Register ContentParent as an observer for changes to any pref
// whose prefix matches the empty string, i.e. all of them. The
// observation starts here in order to capture pref updates that
// happen during async launch.
Preferences::AddStrongObserver(this, "");
if (gSafeMode) {
geckoargs::sSafeMode.Put(extraArgs);
}
#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
if (IsContentSandboxEnabled()) {
AppendSandboxParams(extraArgs);
mSubprocess->DisableOSActivityMode();
}
#endif
nsCString parentBuildID(mozilla::PlatformBuildID());
geckoargs::sParentBuildID.Put(parentBuildID.get(), extraArgs);
#ifdef MOZ_WIDGET_GTK
// This is X11-only pending a solution for WebGL in Wayland mode.
if (StaticPrefs::dom_ipc_avoid_gtk() &&
StaticPrefs::widget_non_native_theme_enabled() &&
widget::GdkIsX11Display()) {
mSubprocess->SetEnv("MOZ_HEADLESS", "1");
}
#endif
// See also ActorDealloc.
mSelfRef = this;
mLaunchYieldTS = TimeStamp::Now();
return mSubprocess->AsyncLaunch(std::move(extraArgs));
}
void ContentParent::LaunchSubprocessReject() {
NS_WARNING("failed to launch child in the parent");
MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
("failed to launch child in the parent"));
// Now that communication with the child is complete, we can cleanup
// the preference serializer.
mPrefSerializer = nullptr;
if (mIsAPreallocBlocker) {
PreallocatedProcessManager::RemoveBlocker(mRemoteType, this);
mIsAPreallocBlocker = false;
}
MarkAsDead();
RemoveShutdownBlockers();
}
bool ContentParent::LaunchSubprocessResolve(bool aIsSync,
ProcessPriority aPriority) {
AUTO_PROFILER_LABEL("ContentParent::LaunchSubprocess::resolve", OTHER);
if (mLaunchResolved) {
// We've already been called, return.
MOZ_ASSERT(sCreatedFirstContentProcess);
MOZ_ASSERT(!mPrefSerializer);
MOZ_ASSERT(mLifecycleState != LifecycleState::LAUNCHING);
return mLaunchResolvedOk;
}
mLaunchResolved = true;
// Now that communication with the child is complete, we can cleanup
// the preference serializer.
mPrefSerializer = nullptr;
const auto launchResumeTS = TimeStamp::Now();
if (profiler_thread_is_being_profiled_for_markers()) {
nsPrintfCString marker("Process start%s for %u",
mIsAPreallocBlocker ? " (immediate)" : "",
(unsigned int)ChildID());
PROFILER_MARKER_TEXT(
mIsAPreallocBlocker ? ProfilerString8View("Process Immediate Launch")
: ProfilerString8View("Process Launch"),
DOM, MarkerTiming::Interval(mLaunchTS, launchResumeTS), marker);
}
if (!sCreatedFirstContentProcess) {
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
obs->NotifyObservers(nullptr, "ipc:first-content-process-created", nullptr);
sCreatedFirstContentProcess = true;
}
mSubprocess->TakeInitialEndpoint().Bind(this);
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (!cpm) {
NS_WARNING("immediately shutting-down caused by our shutdown");
ShutDownProcess(SEND_SHUTDOWN_MESSAGE);
return false;
}
cpm->AddContentProcess(this);
#ifdef MOZ_CODE_COVERAGE
Unused << SendShareCodeCoverageMutex(
CodeCoverageHandler::Get()->GetMutexHandle());
#endif
// We must be in the LAUNCHING state still. If we've somehow already been
// marked as DEAD, fail the process launch, and immediately begin tearing down
// the content process.
if (IsDead()) {
NS_WARNING("immediately shutting-down already-dead process");
ShutDownProcess(SEND_SHUTDOWN_MESSAGE);
return false;
}
MOZ_ASSERT(mLifecycleState == LifecycleState::LAUNCHING);
mLifecycleState = LifecycleState::ALIVE;
if (!InitInternal(aPriority)) {
NS_WARNING("failed to initialize child in the parent");
// We've already called Open() by this point, so we need to close the
// channel to avoid leaking the process.
ShutDownProcess(SEND_SHUTDOWN_MESSAGE);
return false;
}
mHangMonitorActor = ProcessHangMonitor::AddProcess(this);
// Set a reply timeout for CPOWs.
SetReplyTimeoutMs(StaticPrefs::dom_ipc_cpow_timeout());
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
nsAutoString cpId;
cpId.AppendInt(static_cast<uint64_t>(this->ChildID()));
obs->NotifyObservers(static_cast<nsIObserver*>(this),
"ipc:content-initializing", cpId.get());
}
Init();
mLifecycleState = LifecycleState::INITIALIZED;
if (aIsSync) {
Telemetry::AccumulateTimeDelta(Telemetry::CONTENT_PROCESS_SYNC_LAUNCH_MS,
mLaunchTS);
} else {
Telemetry::AccumulateTimeDelta(Telemetry::CONTENT_PROCESS_LAUNCH_TOTAL_MS,
mLaunchTS);
Telemetry::Accumulate(
Telemetry::CONTENT_PROCESS_LAUNCH_MAINTHREAD_MS,
static_cast<uint32_t>(
((mLaunchYieldTS - mLaunchTS) + (TimeStamp::Now() - launchResumeTS))
.ToMilliseconds()));
}
mLaunchResolvedOk = true;
return true;
}
bool ContentParent::LaunchSubprocessSync(
hal::ProcessPriority aInitialPriority) {
// We've started a sync content process launch.
Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_IS_SYNC, 1);
if (BeginSubprocessLaunch(aInitialPriority)) {
const bool ok = mSubprocess->WaitForProcessHandle();
if (ok && LaunchSubprocessResolve(/* aIsSync = */ true, aInitialPriority)) {
ContentParent::DidLaunchSubprocess();
return true;
}
}
LaunchSubprocessReject();
return false;
}
RefPtr<ContentParent::LaunchPromise> ContentParent::LaunchSubprocessAsync(
hal::ProcessPriority aInitialPriority) {
// We've started an async content process launch.
Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_IS_SYNC, 0);
if (!BeginSubprocessLaunch(aInitialPriority)) {
// Launch aborted because of shutdown. Bailout.
LaunchSubprocessReject();
return LaunchPromise::CreateAndReject(LaunchError(), __func__);
}
// Otherwise, wait until the process is ready.
RefPtr<ProcessHandlePromise> ready = mSubprocess->WhenProcessHandleReady();
RefPtr<ContentParent> self = this;
mLaunchYieldTS = TimeStamp::Now();
return ready->Then(
GetCurrentSerialEventTarget(), __func__,
[self, aInitialPriority](
const ProcessHandlePromise::ResolveOrRejectValue& aValue) {
if (aValue.IsResolve() &&
self->LaunchSubprocessResolve(/* aIsSync = */ false,
aInitialPriority)) {
ContentParent::DidLaunchSubprocess();
return LaunchPromise::CreateAndResolve(self, __func__);
}
self->LaunchSubprocessReject();
return LaunchPromise::CreateAndReject(LaunchError(), __func__);
});
}
ContentParent::ContentParent(const nsACString& aRemoteType, int32_t aJSPluginID)
: mSelfRef(nullptr),
mSubprocess(nullptr),
mLaunchTS(TimeStamp::Now()),
mLaunchYieldTS(mLaunchTS),
mActivateTS(mLaunchTS),
mIsAPreallocBlocker(false),
mRemoteType(aRemoteType),
mChildID(gContentChildID++),
mGeolocationWatchID(-1),
mJSPluginID(aJSPluginID),
mThreadsafeHandle(
new ThreadsafeContentParentHandle(this, mChildID, mRemoteType)),
mNumDestroyingTabs(0),
mNumKeepaliveCalls(0),
mLifecycleState(LifecycleState::LAUNCHING),
mIsForBrowser(!mRemoteType.IsEmpty()),
mCalledClose(false),
mCalledKillHard(false),
mCreatedPairedMinidumps(false),
mShutdownPending(false),
mLaunchResolved(false),
mLaunchResolvedOk(false),
mIsRemoteInputEventQueueEnabled(false),
mIsInputPriorityEventEnabled(false),
mIsInPool(false),
mGMPCreated(false),
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
mBlockShutdownCalled(false),
#endif
mHangMonitorActor(nullptr) {
MOZ_DIAGNOSTIC_ASSERT(!IsForJSPlugin(),
"XXX(nika): How are we creating a JSPlugin?");
mRemoteTypeIsolationPrincipal =
CreateRemoteTypeIsolationPrincipal(aRemoteType);
// Insert ourselves into the global linked list of ContentParent objects.
if (!sContentParents) {
sContentParents = new LinkedList<ContentParent>();
}
sContentParents->insertBack(this);
mMessageManager = nsFrameMessageManager::NewProcessMessageManager(true);
#if defined(XP_WIN)
// Request Windows message deferral behavior on our side of the PContent
// channel. Generally only applies to the situation where we get caught in
// a deadlock with the plugin process when sending CPOWs.
GetIPCChannel()->SetChannelFlags(
MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION);
#endif
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
bool isFile = mRemoteType == FILE_REMOTE_TYPE;
mSubprocess = new GeckoChildProcessHost(GeckoProcessType_Content, isFile);
MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
("CreateSubprocess: ContentParent %p mSubprocess %p handle %" PRIuPTR,
this, mSubprocess,
mSubprocess ? (uintptr_t)mSubprocess->GetChildProcessHandle() : -1));
// This is safe to do in the constructor, as it doesn't take a strong
// reference.
mScriptableHelper = new ScriptableCPInfo(this);
}
ContentParent::~ContentParent() {
if (mForceKillTimer) {
mForceKillTimer->Cancel();
}
AssertIsOnMainThread();
// Clear the weak reference from the threadsafe handle back to this actor.
mThreadsafeHandle->mWeakActor = nullptr;
if (mIsAPreallocBlocker) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("Removing blocker on ContentProcess destruction"));
PreallocatedProcessManager::RemoveBlocker(mRemoteType, this);
mIsAPreallocBlocker = false;
}
// We should be removed from all these lists in ActorDestroy.
AssertNotInPool();
// Normally mSubprocess is destroyed in ActorDestroy, but that won't
// happen if the process wasn't launched or if it failed to launch.
if (mSubprocess) {
MOZ_LOG(
ContentParent::GetLog(), LogLevel::Verbose,
("DestroySubprocess: ContentParent %p mSubprocess %p handle %" PRIuPTR,
this, mSubprocess,
mSubprocess ? (uintptr_t)mSubprocess->GetChildProcessHandle() : -1));
mSubprocess->Destroy();
}
// Make sure to clear the connection from `mScriptableHelper` if it hasn't
// been cleared yet.
if (mScriptableHelper) {
static_cast<ScriptableCPInfo*>(mScriptableHelper.get())->ProcessDied();
mScriptableHelper = nullptr;
}
}
bool ContentParent::InitInternal(ProcessPriority aInitialPriority) {
// We can't access the locale service after shutdown has started. Since we
// can't init the process without it, and since we're going to be canceling
// whatever load attempt that initiated this process creation anyway, just
// bail out now if shutdown has already started.
if (PastShutdownPhase(ShutdownPhase::XPCOMShutdown)) {
return false;
}
XPCOMInitData xpcomInit;
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("ContentParent::InitInternal: %p", (void*)this));
nsCOMPtr<nsIIOService> io(do_GetIOService());
MOZ_ASSERT(io, "No IO service?");
DebugOnly<nsresult> rv = io->GetOffline(&xpcomInit.isOffline());
MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting offline?");
rv = io->GetConnectivity(&xpcomInit.isConnected());
MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting connectivity?");
xpcomInit.captivePortalState() = nsICaptivePortalService::UNKNOWN;
nsCOMPtr<nsICaptivePortalService> cps =
do_GetService(NS_CAPTIVEPORTAL_CONTRACTID);
if (cps) {
cps->GetState(&xpcomInit.captivePortalState());
}
if (StaticPrefs::fission_processProfileName()) {
nsCOMPtr<nsIToolkitProfileService> profileSvc =
do_GetService(NS_PROFILESERVICE_CONTRACTID);
if (profileSvc) {
nsCOMPtr<nsIToolkitProfile> currentProfile;
nsresult rv =
profileSvc->GetCurrentProfile(getter_AddRefs(currentProfile));
if (NS_SUCCEEDED(rv) && currentProfile) {
currentProfile->GetName(mProfile);
}
}
}
nsIBidiKeyboard* bidi = nsContentUtils::GetBidiKeyboard();
xpcomInit.isLangRTL() = false;
xpcomInit.haveBidiKeyboards() = false;
if (bidi) {
bidi->IsLangRTL(&xpcomInit.isLangRTL());
bidi->GetHaveBidiKeyboards(&xpcomInit.haveBidiKeyboards());
}
RefPtr<mozSpellChecker> spellChecker(mozSpellChecker::Create());
MOZ_ASSERT(spellChecker, "No spell checker?");
spellChecker->GetDictionaryList(&xpcomInit.dictionaries());
LocaleService::GetInstance()->GetAppLocalesAsBCP47(xpcomInit.appLocales());
LocaleService::GetInstance()->GetRequestedLocales(
xpcomInit.requestedLocales());
L10nRegistry::GetParentProcessFileSourceDescriptors(
xpcomInit.l10nFileSources());
nsCOMPtr<nsIClipboard> clipboard(
do_GetService("@mozilla.org/widget/clipboard;1"));
MOZ_ASSERT(clipboard, "No clipboard?");
MOZ_ASSERT(
clipboard->IsClipboardTypeSupported(nsIClipboard::kGlobalClipboard),
"We should always support the global clipboard.");
xpcomInit.clipboardCaps().supportsSelectionClipboard() =
clipboard->IsClipboardTypeSupported(nsIClipboard::kSelectionClipboard);
xpcomInit.clipboardCaps().supportsFindClipboard() =
clipboard->IsClipboardTypeSupported(nsIClipboard::kFindClipboard);
xpcomInit.clipboardCaps().supportsSelectionCache() =
clipboard->IsClipboardTypeSupported(nsIClipboard::kSelectionCache);
// Let's copy the domain policy from the parent to the child (if it's active).
StructuredCloneData initialData;
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
if (ssm) {
ssm->CloneDomainPolicy(&xpcomInit.domainPolicy());
if (ParentProcessMessageManager* mm =
nsFrameMessageManager::sParentProcessManager) {
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(xpc::PrivilegedJunkScope()))) {
MOZ_CRASH();
}
JS::Rooted<JS::Value> init(jsapi.cx());
// We'll crash on failure, so use a IgnoredErrorResult (which also
// auto-suppresses exceptions).
IgnoredErrorResult rv;
mm->GetInitialProcessData(jsapi.cx(), &init, rv);
if (NS_WARN_IF(rv.Failed())) {
MOZ_CRASH();
}
initialData.Write(jsapi.cx(), init, rv);
if (NS_WARN_IF(rv.Failed())) {
MOZ_CRASH();
}
}
}
// This is only implemented (returns a non-empty list) by MacOSX and Linux
// at present.
SystemFontList fontList;
gfxPlatform::GetPlatform()->ReadSystemFontList(&fontList);
const FullLookAndFeel& lnf = *RemoteLookAndFeel::ExtractData();
// If the shared fontlist is in use, collect its shmem block handles to pass
// to the child.
nsTArray<SharedMemoryHandle> sharedFontListBlocks;
gfxPlatformFontList::PlatformFontList()->ShareFontListToProcess(
&sharedFontListBlocks, OtherPid());
// Content processes have no permission to access profile directory, so we
// send the file URL instead.
auto* sheetCache = GlobalStyleSheetCache::Singleton();
if (StyleSheet* ucs = sheetCache->GetUserContentSheet()) {
xpcomInit.userContentSheetURL() = ucs->GetSheetURI();
} else {
xpcomInit.userContentSheetURL() = nullptr;
}
// 1. Build ContentDeviceData first, as it may affect some gfxVars.
gfxPlatform::GetPlatform()->BuildContentDeviceData(
&xpcomInit.contentDeviceData());
// 2. Gather non-default gfxVars.
xpcomInit.gfxNonDefaultVarUpdates() = gfxVars::FetchNonDefaultVars();
// 3. Start listening for gfxVars updates, to notify content process later on.
gfxVars::AddReceiver(this);
nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
if (gfxInfo) {
GfxInfoBase* gfxInfoRaw = static_cast<GfxInfoBase*>(gfxInfo.get());
xpcomInit.gfxFeatureStatus() = gfxInfoRaw->GetAllFeatures();
}
// Send the dynamic scalar definitions to the new process.
TelemetryIPC::GetDynamicScalarDefinitions(xpcomInit.dynamicScalarDefs());
for (auto const& [location, supported] : sCodecsSupported) {
Unused << SendUpdateMediaCodecsSupported(location, supported);
}
#ifdef MOZ_WIDGET_ANDROID
if (!(StaticPrefs::media_utility_process_enabled() &&
StaticPrefs::media_utility_android_media_codec_enabled())) {
Unused << SendDecoderSupportedMimeTypes(
AndroidDecoderModule::GetSupportedMimeTypes());
}
#endif
// Must send screen info before send initialData
ScreenManager& screenManager = ScreenManager::GetSingleton();
screenManager.CopyScreensToRemote(this);
// Send the UA sheet shared memory buffer and the address it is mapped at.
Maybe<SharedMemoryHandle> sharedUASheetHandle;
uintptr_t sharedUASheetAddress = sheetCache->GetSharedMemoryAddress();
if (SharedMemoryHandle handle = sheetCache->CloneHandle()) {
sharedUASheetHandle.emplace(std::move(handle));
} else {
sharedUASheetAddress = 0;
}
bool isReadyForBackgroundProcessing = false;
#if defined(XP_WIN)
RefPtr<DllServices> dllSvc(DllServices::Get());
isReadyForBackgroundProcessing = dllSvc->IsReadyForBackgroundProcessing();
#endif
xpcomInit.perfStatsMask() = PerfStats::GetCollectionMask();
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
dns->GetTrrDomain(xpcomInit.trrDomain());
nsIDNSService::ResolverMode mode;
dns->GetCurrentTrrMode(&mode);
xpcomInit.trrMode() = mode;
Unused << SendSetXPCOMProcessAttributes(
xpcomInit, initialData, lnf, fontList, std::move(sharedUASheetHandle),
sharedUASheetAddress, std::move(sharedFontListBlocks),
isReadyForBackgroundProcessing);
ipc::WritableSharedMap* sharedData =
nsFrameMessageManager::sParentProcessManager->SharedData();
sharedData->Flush();
sharedData->SendTo(this);
nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService();
nsChromeRegistryChrome* chromeRegistry =
static_cast<nsChromeRegistryChrome*>(registrySvc.get());
chromeRegistry->SendRegisteredChrome(this);
nsCOMPtr<nsIStringBundleService> stringBundleService =
components::StringBundle::Service();
stringBundleService->SendContentBundles(this);
if (gAppData) {
nsCString version(gAppData->version);
nsCString buildID(gAppData->buildID);
nsCString name(gAppData->name);
nsCString UAName(gAppData->UAName);
nsCString ID(gAppData->ID);
nsCString vendor(gAppData->vendor);
nsCString sourceURL(gAppData->sourceURL);
nsCString updateURL(gAppData->updateURL);
// Sending all information to content process.
Unused << SendAppInfo(version, buildID, name, UAName, ID, vendor, sourceURL,
updateURL);
}
// Send the child its remote type. On Mac, this needs to be sent prior
// to the message we send to enable the Sandbox (SendStartProcessSandbox)
// because different remote types require different sandbox privileges.
Unused << SendRemoteType(mRemoteType, mProfile);
ScriptPreloader::InitContentChild(*this);
// Initialize the message manager (and load delayed scripts) now that we
// have established communications with the child.
mMessageManager->InitWithCallback(this);
mMessageManager->SetOsPid(Pid());
// Set the subprocess's priority. We do this early on because we're likely
// /lowering/ the process's CPU and memory priority, which it has inherited
// from this process.
//
// This call can cause us to send IPC messages to the child process, so it
// must come after the Open() call above.
ProcessPriorityManager::SetProcessPriority(this, aInitialPriority);
// NB: internally, this will send an IPC message to the child
// process to get it to create the CompositorBridgeChild. This
// message goes through the regular IPC queue for this
// channel, so delivery will happen-before any other messages
// we send. The CompositorBridgeChild must be created before any
// PBrowsers are created, because they rely on the Compositor
// already being around. (Creation is async, so can't happen
// on demand.)
GPUProcessManager* gpm = GPUProcessManager::Get();
Endpoint<PCompositorManagerChild> compositor;
Endpoint<PImageBridgeChild> imageBridge;
Endpoint<PVRManagerChild> vrBridge;
Endpoint<PRemoteDecoderManagerChild> videoManager;
AutoTArray<uint32_t, 3> namespaces;
if (!gpm->CreateContentBridges(OtherPid(), &compositor, &imageBridge,
&vrBridge, &videoManager, &namespaces)) {
// This can fail if we've already started shutting down the compositor
// thread. See Bug 1562763 comment 8.
return false;
}
Unused << SendInitRendering(std::move(compositor), std::move(imageBridge),
std::move(vrBridge), std::move(videoManager),
namespaces);
gpm->AddListener(this);
nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
if (sheetService) {
// This looks like a lot of work, but in a normal browser session we just
// send two loads.
//
// The URIs of the Gecko and Servo sheets should be the same, so it
// shouldn't matter which we look at.
for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) {
Unused << SendLoadAndRegisterSheet(sheet->GetSheetURI(),
nsIStyleSheetService::AGENT_SHEET);
}
for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
Unused << SendLoadAndRegisterSheet(sheet->GetSheetURI(),
nsIStyleSheetService::USER_SHEET);
}
for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
Unused << SendLoadAndRegisterSheet(sheet->GetSheetURI(),
nsIStyleSheetService::AUTHOR_SHEET);
}
}
#ifdef MOZ_SANDBOX
bool shouldSandbox = true;
Maybe<FileDescriptor> brokerFd;
// XXX: Checking the pref here makes it possible to enable/disable sandboxing
// during an active session. Currently the pref is only used for testing
// purpose. If the decision is made to permanently rely on the pref, this
// should be changed so that it is required to restart firefox for the change
// of value to take effect. Always send SetProcessSandbox message on macOS.
# if !defined(XP_MACOSX)
shouldSandbox = IsContentSandboxEnabled();
# endif
# ifdef XP_LINUX
if (shouldSandbox) {
MOZ_ASSERT(!mSandboxBroker);
bool isFileProcess = mRemoteType == FILE_REMOTE_TYPE;
UniquePtr<SandboxBroker::Policy> policy =
sSandboxBrokerPolicyFactory->GetContentPolicy(Pid(), isFileProcess);
if (policy) {
brokerFd = Some(FileDescriptor());
mSandboxBroker =
SandboxBroker::Create(std::move(policy), Pid(), brokerFd.ref());
if (!mSandboxBroker) {
KillHard("SandboxBroker::Create failed");
return false;
}
MOZ_ASSERT(brokerFd.ref().IsValid());
}
}
# endif
if (shouldSandbox && !SendSetProcessSandbox(brokerFd)) {
KillHard("SandboxInitFailed");
}
#endif
// Ensure that the default set of permissions are avaliable in the content
// process before we try to load any URIs in it.
//
// NOTE: All default permissions has to be transmitted to the child process
// before the blob urls in the for loop below (See Bug 1738713 comment 12).
EnsurePermissionsByKey(""_ns, ""_ns);
{
nsTArray<BlobURLRegistrationData> registrations;
BlobURLProtocolHandler::ForEachBlobURL(
[&](BlobImpl* aBlobImpl, nsIPrincipal* aPrincipal,
const Maybe<nsID>& aAgentClusterId, const nsACString& aURI,
bool aRevoked) {
// We send all moz-extension Blob URL's to all content processes
// because content scripts mean that a moz-extension can live in any
// process. Same thing for system principal Blob URLs. Content Blob
// URL's are sent for content principals on-demand by
// AboutToLoadHttpFtpDocumentForChild and RemoteWorkerManager.
if (!BlobURLProtocolHandler::IsBlobURLBroadcastPrincipal(
aPrincipal)) {
return true;
}
IPCBlob ipcBlob;
nsresult rv = IPCBlobUtils::Serialize(aBlobImpl, ipcBlob);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
registrations.AppendElement(BlobURLRegistrationData(
nsCString(aURI), ipcBlob, aPrincipal, aAgentClusterId, aRevoked));
rv = TransmitPermissionsForPrincipal(aPrincipal);
Unused << NS_WARN_IF(NS_FAILED(rv));
return true;
});
if (!registrations.IsEmpty()) {
Unused << SendInitBlobURLs(registrations);
}
}
// Send down { Parent, Window }ActorOptions at startup to content process.
RefPtr<JSActorService> actorSvc = JSActorService::GetSingleton();
if (actorSvc) {
nsTArray<JSProcessActorInfo> contentInfos;
actorSvc->GetJSProcessActorInfos(contentInfos);
nsTArray<JSWindowActorInfo> windowInfos;
actorSvc->GetJSWindowActorInfos(windowInfos);
Unused << SendInitJSActorInfos(contentInfos, windowInfos);
}
// Begin subscribing to any BrowsingContextGroups which were hosted by this
// process before it finished launching.
for (const auto& group : mGroups) {
group->Subscribe(this);
}
MaybeEnableRemoteInputEventQueue();
return true;
}
bool ContentParent::IsAlive() const {
return mLifecycleState == LifecycleState::ALIVE ||
mLifecycleState == LifecycleState::INITIALIZED;
}
bool ContentParent::IsInitialized() const {
return mLifecycleState == LifecycleState::INITIALIZED;
}
int32_t ContentParent::Pid() const {
if (!mSubprocess || !mSubprocess->GetChildProcessHandle()) {
return -1;
}
return base::GetProcId(mSubprocess->GetChildProcessHandle());
}
mozilla::ipc::IPCResult ContentParent::RecvGetGfxVars(
nsTArray<GfxVarUpdate>* aVars) {
// Ensure gfxVars is initialized (for xpcshell tests).
gfxVars::Initialize();
*aVars = gfxVars::FetchNonDefaultVars();
// Now that content has initialized gfxVars, we can start listening for
// updates.
gfxVars::AddReceiver(this);
return IPC_OK();
}
void ContentParent::OnCompositorUnexpectedShutdown() {
GPUProcessManager* gpm = GPUProcessManager::Get();
Endpoint<PCompositorManagerChild> compositor;
Endpoint<PImageBridgeChild> imageBridge;
Endpoint<PVRManagerChild> vrBridge;
Endpoint<PRemoteDecoderManagerChild> videoManager;
AutoTArray<uint32_t, 3> namespaces;
DebugOnly<bool> opened =
gpm->CreateContentBridges(OtherPid(), &compositor, &imageBridge,
&vrBridge, &videoManager, &namespaces);
MOZ_ASSERT(opened);
Unused << SendReinitRendering(std::move(compositor), std::move(imageBridge),
std::move(vrBridge), std::move(videoManager),
namespaces);
}
void ContentParent::OnCompositorDeviceReset() {
Unused << SendReinitRenderingForDeviceReset();
}
void ContentParent::MaybeEnableRemoteInputEventQueue() {
MOZ_ASSERT(!mIsRemoteInputEventQueueEnabled);
if (!IsInputEventQueueSupported()) {
return;
}
mIsRemoteInputEventQueueEnabled = true;
Unused << SendSetInputEventQueueEnabled();
SetInputPriorityEventEnabled(true);
}
void ContentParent::SetInputPriorityEventEnabled(bool aEnabled) {
if (!IsInputEventQueueSupported() || !mIsRemoteInputEventQueueEnabled ||
mIsInputPriorityEventEnabled == aEnabled) {
return;
}
mIsInputPriorityEventEnabled = aEnabled;
// Send IPC messages to flush the pending events in the input event queue and
// the normal event queue. See PContent.ipdl for more details.
Unused << SendSuspendInputEventQueue();
Unused << SendFlushInputEventQueue();
Unused << SendResumeInputEventQueue();
}
/*static*/
bool ContentParent::IsInputEventQueueSupported() {
static bool sSupported = false;
static bool sInitialized = false;
if (!sInitialized) {
MOZ_ASSERT(Preferences::IsServiceAvailable());
sSupported = Preferences::GetBool("input_event_queue.supported", false);
sInitialized = true;
}
return sSupported;
}
void ContentParent::OnVarChanged(const GfxVarUpdate& aVar) {
if (!CanSend()) {
return;
}
Unused << SendVarUpdate(aVar);
}
mozilla::ipc::IPCResult ContentParent::RecvSetClipboard(
const IPCDataTransfer& aDataTransfer, const bool& aIsPrivateData,
nsIPrincipal* aRequestingPrincipal,
const nsContentPolicyType& aContentPolicyType,
const int32_t& aWhichClipboard) {
// aRequestingPrincipal is allowed to be nullptr here.
if (!ValidatePrincipal(aRequestingPrincipal,
{ValidatePrincipalOptions::AllowNullPtr})) {
LogAndAssertFailedPrincipalValidationInfo(aRequestingPrincipal, __func__);
}
nsresult rv;
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
NS_ENSURE_SUCCESS(rv, IPC_OK());
nsCOMPtr<nsITransferable> trans =
do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
NS_ENSURE_SUCCESS(rv, IPC_OK());
trans->Init(nullptr);
rv = nsContentUtils::IPCTransferableToTransferable(
aDataTransfer, aIsPrivateData, aRequestingPrincipal, aContentPolicyType,
true /* aAddDataFlavor */, trans, true /* aFilterUnknownFlavors */);
NS_ENSURE_SUCCESS(rv, IPC_OK());
clipboard->SetData(trans, nullptr, aWhichClipboard);
return IPC_OK();
}
namespace {
static Result<nsCOMPtr<nsITransferable>, nsresult> CreateTransferable(
const nsTArray<nsCString>& aTypes) {
nsresult rv;
nsCOMPtr<nsITransferable> trans =
do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
if (NS_FAILED(rv)) {
return Err(rv);
}
MOZ_TRY(trans->Init(nullptr));
// The private flag is only used to prevent the data from being cached to the
// disk. The flag is not exported to the IPCDataTransfer object.
// The flag is set because we are not sure whether the clipboard data is used
// in a private browsing context. The transferable is only used in this scope,
// so the cache would not reduce memory consumption anyway.
trans->SetIsPrivateData(true);
// Fill out flavors for transferable
for (uint32_t t = 0; t < aTypes.Length(); t++) {
MOZ_TRY(trans->AddDataFlavor(aTypes[t].get()));
}
return std::move(trans);
}
} // anonymous namespace
mozilla::ipc::IPCResult ContentParent::RecvGetClipboard(
nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard,
IPCDataTransfer* aDataTransfer) {
nsresult rv;
// Retrieve clipboard
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
if (NS_FAILED(rv)) {
return IPC_OK();
}
// Create transferable
auto result = CreateTransferable(aTypes);
if (result.isErr()) {
return IPC_OK();
}
// Get data from clipboard
nsCOMPtr<nsITransferable> trans = result.unwrap();
clipboard->GetData(trans, aWhichClipboard);
nsContentUtils::TransferableToIPCTransferable(
trans, aDataTransfer, true /* aInSyncMessage */, nullptr, this);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvEmptyClipboard(
const int32_t& aWhichClipboard) {
nsresult rv;
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
NS_ENSURE_SUCCESS(rv, IPC_OK());
clipboard->EmptyClipboard(aWhichClipboard);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvClipboardHasType(
nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard,
bool* aHasType) {
nsresult rv;
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
NS_ENSURE_SUCCESS(rv, IPC_OK());
clipboard->HasDataMatchingFlavors(aTypes, aWhichClipboard, aHasType);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvClipboardHasTypesAsync(
nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard,
ClipboardHasTypesAsyncResolver&& aResolver) {
nsresult rv;
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
if (NS_FAILED(rv)) {
return IPC_FAIL(this, "RecvGetClipboardTypes failed.");
}
clipboard->AsyncHasDataMatchingFlavors(aTypes, aWhichClipboard)
->Then(
GetMainThreadSerialEventTarget(), __func__,
/* resolve */
[aResolver](nsTArray<nsCString> types) { aResolver(types); },
/* reject */
[aResolver](nsresult rv) { aResolver(nsTArray<nsCString>{}); });
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvGetExternalClipboardFormats(
const int32_t& aWhichClipboard, const bool& aPlainTextOnly,
nsTArray<nsCString>* aTypes) {
MOZ_ASSERT(aTypes);
DataTransfer::GetExternalClipboardFormats(aWhichClipboard, aPlainTextOnly,
aTypes);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvGetClipboardAsync(
nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard,
GetClipboardAsyncResolver&& aResolver) {
nsresult rv;
// Retrieve clipboard
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
if (NS_FAILED(rv)) {
aResolver(rv);
return IPC_OK();
}
// Create transferable
auto result = CreateTransferable(aTypes);
if (result.isErr()) {
aResolver(result.unwrapErr());
return IPC_OK();
}
// Get data from clipboard
nsCOMPtr<nsITransferable> trans = result.unwrap();
clipboard->AsyncGetData(trans, nsIClipboard::kGlobalClipboard)
->Then(GetMainThreadSerialEventTarget(), __func__,
[trans, aResolver, self = RefPtr{this}](
GenericPromise::ResolveOrRejectValue&& aValue) {
IPCDataTransfer ipcDataTransfer;
nsContentUtils::TransferableToIPCTransferable(
trans, &ipcDataTransfer, false /* aInSyncMessage */, nullptr,
self);
aResolver(std::move(ipcDataTransfer));
});
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvPlaySound(nsIURI* aURI) {
// If the check here fails, it can only mean that this message was spoofed.
if (!aURI || !aURI->SchemeIs("chrome")) {
// PlaySound only accepts a valid chrome URI.
return IPC_FAIL(this, "Invalid aURI passed.");
}
nsCOMPtr<nsIURL> soundURL(do_QueryInterface(aURI));
if (!soundURL) {
return IPC_OK();
}
nsresult rv;
nsCOMPtr<nsISound> sound(do_GetService(NS_SOUND_CID, &rv));
NS_ENSURE_SUCCESS(rv, IPC_OK());
sound->Play(soundURL);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvBeep() {
nsresult rv;
nsCOMPtr<nsISound> sound(do_GetService(NS_SOUND_CID, &rv));
NS_ENSURE_SUCCESS(rv, IPC_OK());
sound->Beep();
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvPlayEventSound(
const uint32_t& aEventId) {
nsresult rv;
nsCOMPtr<nsISound> sound(do_GetService(NS_SOUND_CID, &rv));
NS_ENSURE_SUCCESS(rv, IPC_OK());
sound->PlayEventSound(aEventId);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvGetIconForExtension(
const nsACString& aFileExt, const uint32_t& aIconSize,
nsTArray<uint8_t>* bits) {
#ifdef MOZ_WIDGET_ANDROID
NS_ASSERTION(AndroidBridge::Bridge() != nullptr,
"AndroidBridge is not available");
if (AndroidBridge::Bridge() == nullptr) {
// Do not fail - just no icon will be shown
return IPC_OK();
}
bits->AppendElements(aIconSize * aIconSize * 4);
AndroidBridge::Bridge()->GetIconForExtension(aFileExt, aIconSize,
bits->Elements());
#endif
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvFirstIdle() {
// When the ContentChild goes idle, it sends us a FirstIdle message
// which we use as a good time to signal the PreallocatedProcessManager
// that it can start allocating processes from now on.
if (mIsAPreallocBlocker) {
MOZ_LOG(
ContentParent::GetLog(), LogLevel::Verbose,
("RecvFirstIdle %p: Removing Blocker for %s", this, mRemoteType.get()));
PreallocatedProcessManager::RemoveBlocker(mRemoteType, this);
mIsAPreallocBlocker = false;
}
return IPC_OK();
}
already_AddRefed<nsDocShellLoadState> ContentParent::TakePendingLoadStateForId(
uint64_t aLoadIdentifier) {
return mPendingLoadStates.Extract(aLoadIdentifier).valueOr(nullptr).forget();
}
void ContentParent::StorePendingLoadState(nsDocShellLoadState* aLoadState) {
MOZ_DIAGNOSTIC_ASSERT(
!mPendingLoadStates.Contains(aLoadState->GetLoadIdentifier()),
"The same nsDocShellLoadState was sent to the same content process "
"twice? This will mess with cross-process tracking of loads");
mPendingLoadStates.InsertOrUpdate(aLoadState->GetLoadIdentifier(),
aLoadState);
}
mozilla::ipc::IPCResult ContentParent::RecvCleanupPendingLoadState(
uint64_t aLoadIdentifier) {
mPendingLoadStates.Remove(aLoadIdentifier);
return IPC_OK();
}
// We want ContentParent to show up in CC logs for debugging purposes, but we
// don't actually cycle collect it.
NS_IMPL_CYCLE_COLLECTION_0(ContentParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(ContentParent)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ContentParent)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ContentParent)
NS_INTERFACE_MAP_ENTRY_CONCRETE(ContentParent)
NS_INTERFACE_MAP_ENTRY(nsIDOMProcessParent)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsIDOMGeoPositionCallback)
NS_INTERFACE_MAP_ENTRY(nsIDOMGeoPositionErrorCallback)
NS_INTERFACE_MAP_ENTRY(nsIAsyncShutdownBlocker)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMProcessParent)
NS_INTERFACE_MAP_END
class RequestContentJSInterruptRunnable final : public Runnable {
public:
explicit RequestContentJSInterruptRunnable(PProcessHangMonitorParent* aActor)
: Runnable("dom::RequestContentJSInterruptRunnable"),
mHangMonitorActor(aActor) {}
NS_IMETHOD Run() override {
MOZ_ASSERT(mHangMonitorActor);
Unused << mHangMonitorActor->SendRequestContentJSInterrupt();
return NS_OK;
}
private:
// The end-of-life of ContentParent::mHangMonitorActor is bound to
// ContentParent::ActorDestroy and then HangMonitorParent::Shutdown
// dispatches a shutdown runnable to this queue and waits for it to be
// executed. So the runnable needs not to care about keeping it alive,
// as it is surely dispatched earlier than the
// HangMonitorParent::ShutdownOnThread.
PProcessHangMonitorParent* mHangMonitorActor;
};
void ContentParent::SignalImpendingShutdownToContentJS() {
if (!mIsSignaledImpendingShutdown &&
!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown)) {
MaybeLogBlockShutdownDiagnostics(
this, "BlockShutdown: NotifyImpendingShutdown.", __FILE__, __LINE__);
NotifyImpendingShutdown();
mIsSignaledImpendingShutdown = true;
if (mHangMonitorActor &&
StaticPrefs::dom_abort_script_on_child_shutdown()) {
MaybeLogBlockShutdownDiagnostics(
this, "BlockShutdown: RequestContentJSInterrupt.", __FILE__,
__LINE__);
RefPtr<RequestContentJSInterruptRunnable> r =
new RequestContentJSInterruptRunnable(mHangMonitorActor);
ProcessHangMonitor::Get()->Dispatch(r.forget());
}
}
}
// Async shutdown blocker
NS_IMETHODIMP
ContentParent::BlockShutdown(nsIAsyncShutdownClient* aClient) {
if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown)) {
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
mBlockShutdownCalled = true;
#endif
// Our real shutdown has not yet started. Just notify the impending
// shutdown and eventually cancel content JS.
SignalImpendingShutdownToContentJS();
// This will make our process unusable for normal content, so we need to
// ensure we won't get re-used by GetUsedBrowserProcess as we have not yet
// done MarkAsDead.
PreallocatedProcessManager::Erase(this);
StopRecyclingE10SOnly(false);
if (sQuitApplicationGrantedClient) {
Unused << sQuitApplicationGrantedClient->RemoveBlocker(this);
}
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
mBlockShutdownCalled = false;
#endif
return NS_OK;
}
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
// We register two final shutdown blockers and both would call us, but if
// things go well we will unregister both as (delayed) reaction to the first
// call we get and thus never receive a second call. Thus we believe that we
// will get called only once except for quit-application-granted, which is
// handled above.
MOZ_ASSERT(!mBlockShutdownCalled);
mBlockShutdownCalled = true;
#endif
if (CanSend()) {
MaybeLogBlockShutdownDiagnostics(this, "BlockShutdown: CanSend.", __FILE__,
__LINE__);
// Make sure that our process will get scheduled.
ProcessPriorityManager::SetProcessPriority(this,
PROCESS_PRIORITY_FOREGROUND);
// The normal shutdown sequence is to send a shutdown message
// to the child and then just wait for ActorDestroy which will
// cleanup everything and remove our blockers.
if (!ShutDownProcess(SEND_SHUTDOWN_MESSAGE)) {
KillHard("Failed to send Shutdown message. Destroying the process...");
return NS_OK;
}
} else if (IsLaunching()) {
MaybeLogBlockShutdownDiagnostics(
this, "BlockShutdown: !CanSend && IsLaunching.", __FILE__, __LINE__);
// If we get here while we are launching, we must wait for the child to
// be able to react on our commands. Mark this process as dead. This
// will make bail out LaunchSubprocessResolve and kick off the normal
// shutdown sequence.
MarkAsDead();
} else {
MOZ_ASSERT(IsDead());
if (!IsDead()) {
MaybeLogBlockShutdownDiagnostics(
this, "BlockShutdown: !!! !CanSend && !IsLaunching && !IsDead !!!",
__FILE__, __LINE__);
} else {
MaybeLogBlockShutdownDiagnostics(
this, "BlockShutdown: !CanSend && !IsLaunching && IsDead.", __FILE__,
__LINE__);
}
// Nothing left we can do. We must assume that we race with an ongoing
// process shutdown, such that we can expect our shutdown blockers to be
// removed normally.
}
return NS_OK;
}
NS_IMETHODIMP
ContentParent::GetName(nsAString& aName) {
aName.AssignLiteral("ContentParent:");
aName.AppendPrintf(" id=%p", this);
return NS_OK;
}
NS_IMETHODIMP
ContentParent::GetState(nsIPropertyBag** aResult) {
auto props = MakeRefPtr<nsHashPropertyBag>();
props->SetPropertyAsACString(u"remoteTypePrefix"_ns,
RemoteTypePrefix(mRemoteType));
*aResult = props.forget().downcast<nsIWritablePropertyBag>().take();
return NS_OK;
}
static void InitShutdownClients() {
if (!sXPCOMShutdownClient) {
nsresult rv;
nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
if (!svc) {
return;
}
nsCOMPtr<nsIAsyncShutdownClient> client;
// TODO: It seems as if getPhase from AsyncShutdown.sys.mjs does not check
// if we are beyond our phase already. See bug 1762840.
if (!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) {
rv = svc->GetXpcomWillShutdown(getter_AddRefs(client));
if (NS_SUCCEEDED(rv)) {
sXPCOMShutdownClient = client.forget();
ClearOnShutdown(&sXPCOMShutdownClient);
}
}
if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown)) {
rv = svc->GetProfileBeforeChange(getter_AddRefs(client));
if (NS_SUCCEEDED(rv)) {
sProfileBeforeChangeClient = client.forget();
ClearOnShutdown(&sProfileBeforeChangeClient);
}
}
// TODO: ShutdownPhase::AppShutdownConfirmed is not mapping to
// QuitApplicationGranted, see bug 1762840 comment 4.
if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
rv = svc->GetQuitApplicationGranted(getter_AddRefs(client));
if (NS_SUCCEEDED(rv)) {
sQuitApplicationGrantedClient = client.forget();
ClearOnShutdown(&sQuitApplicationGrantedClient);
}
}
}
}
void ContentParent::AddShutdownBlockers() {
InitShutdownClients();
MOZ_ASSERT(sXPCOMShutdownClient);
MOZ_ASSERT(sProfileBeforeChangeClient);
if (sXPCOMShutdownClient) {
sXPCOMShutdownClient->AddBlocker(
this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns);
}
if (sProfileBeforeChangeClient) {
sProfileBeforeChangeClient->AddBlocker(
this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns);
}
if (sQuitApplicationGrantedClient) {
sQuitApplicationGrantedClient->AddBlocker(
this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns);
}
}
void ContentParent::RemoveShutdownBlockers() {
MOZ_ASSERT(sXPCOMShutdownClient);
MOZ_ASSERT(sProfileBeforeChangeClient);
MaybeLogBlockShutdownDiagnostics(this, "RemoveShutdownBlockers", __FILE__,
__LINE__);
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
mBlockShutdownCalled = false;
#endif
if (sXPCOMShutdownClient) {
Unused << sXPCOMShutdownClient->RemoveBlocker(this);
}
if (sProfileBeforeChangeClient) {
Unused << sProfileBeforeChangeClient->RemoveBlocker(this);
}
if (sQuitApplicationGrantedClient) {
Unused << sQuitApplicationGrantedClient->RemoveBlocker(this);
}
}
NS_IMETHODIMP
ContentParent::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (IsDead() || !mSubprocess) {
return NS_OK;
}
if (!strcmp(aTopic, "nsPref:changed")) {
// We know prefs are ASCII here.
NS_LossyConvertUTF16toASCII strData(aData);
Pref pref(strData, /* isLocked */ false,
/* isSanitized */ false, Nothing(), Nothing());
Preferences::GetPreference(&pref, GeckoProcessType_Content,
GetRemoteType());
if (IsInitialized()) {
MOZ_ASSERT(mQueuedPrefs.IsEmpty());
if (!SendPreferenceUpdate(pref)) {
return NS_ERROR_NOT_AVAILABLE;
}
} else {
MOZ_ASSERT(!IsDead());
mQueuedPrefs.AppendElement(pref);
}
}
if (!IsAlive()) {
return NS_OK;
}
// listening for memory pressure event
if (!strcmp(aTopic, "memory-pressure")) {
Unused << SendFlushMemory(nsDependentString(aData));
} else if (!strcmp(aTopic, "application-background")) {
Unused << SendApplicationBackground();
} else if (!strcmp(aTopic, "application-foreground")) {
Unused << SendApplicationForeground();
} else if (!strcmp(aTopic, NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC)) {
NS_ConvertUTF16toUTF8 dataStr(aData);
const char* offline = dataStr.get();
if (!SendSetOffline(!strcmp(offline, "true"))) {
return NS_ERROR_NOT_AVAILABLE;
}
} else if (!strcmp(aTopic, NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC)) {
if (!SendSetConnectivity(u"true"_ns.Equals(aData))) {
return NS_ERROR_NOT_AVAILABLE;
}
} else if (!strcmp(aTopic, NS_IPC_CAPTIVE_PORTAL_SET_STATE)) {
nsCOMPtr<nsICaptivePortalService> cps = do_QueryInterface(aSubject);
MOZ_ASSERT(cps, "Should QI to a captive portal service");
if (!cps) {
return NS_ERROR_FAILURE;
}
int32_t state;
cps->GetState(&state);
if (!SendSetCaptivePortalState(state)) {
return NS_ERROR_NOT_AVAILABLE;
}
}
// listening for alert notifications
else if (!strcmp(aTopic, "alertfinished") ||
!strcmp(aTopic, "alertclickcallback") ||
!strcmp(aTopic, "alertshow") ||
!strcmp(aTopic, "alertdisablecallback") ||
!strcmp(aTopic, "alertsettingscallback")) {
if (!SendNotifyAlertsObserver(nsDependentCString(aTopic),
nsDependentString(aData)))
return NS_ERROR_NOT_AVAILABLE;
} else if (!strcmp(aTopic, "child-gc-request")) {
Unused << SendGarbageCollect();
} else if (!strcmp(aTopic, "child-cc-request")) {
Unused << SendCycleCollect();
} else if (!strcmp(aTopic, "child-mmu-request")) {
Unused << SendMinimizeMemoryUsage();
} else if (!strcmp(aTopic, "child-ghost-request")) {
Unused << SendUnlinkGhosts();
} else if (!strcmp(aTopic, "last-pb-context-exited")) {
Unused << SendLastPrivateDocShellDestroyed();
}
#ifdef ACCESSIBILITY
else if (aData && !strcmp(aTopic, "a11y-init-or-shutdown")) {
if (*aData == '1') {
// Make sure accessibility is running in content process when
// accessibility gets initiated in chrome process.
# if defined(XP_WIN)
// Don't init content a11y if we detect an incompat version of JAWS in
// use.
if (!mozilla::a11y::Compatibility::IsOldJAWS()) {
Unused << SendActivateA11y(
::GetCurrentThreadId(),
a11y::MsaaAccessible::GetContentProcessIdFor(ChildID()));
}
# else
Unused << SendActivateA11y(0, 0);
# endif
} else {
// If possible, shut down accessibility in content process when
// accessibility gets shutdown in chrome process.
Unused << SendShutdownA11y();
}
}
#endif
else if (!strcmp(aTopic, "cacheservice:empty-cache")) {
Unused << SendNotifyEmptyHTTPCache();
} else if (!strcmp(aTopic, "intl:app-locales-changed")) {
nsTArray<nsCString> appLocales;
LocaleService::GetInstance()->GetAppLocalesAsBCP47(appLocales);
Unused << SendUpdateAppLocales(appLocales);
} else if (!strcmp(aTopic, "intl:requested-locales-changed")) {
nsTArray<nsCString> requestedLocales;
LocaleService::GetInstance()->GetRequestedLocales(requestedLocales);
Unused << SendUpdateRequestedLocales(requestedLocales);
} else if (!strcmp(aTopic, "cookie-changed") ||
!strcmp(aTopic, "private-cookie-changed")) {
if (!aData) {
return NS_ERROR_UNEXPECTED;
}
PNeckoParent* neckoParent = LoneManagedOrNullAsserts(ManagedPNeckoParent());
if (!neckoParent) {
return NS_OK;
}
PCookieServiceParent* csParent =
LoneManagedOrNullAsserts(neckoParent->ManagedPCookieServiceParent());
if (!csParent) {
return NS_OK;
}
auto* cs = static_cast<CookieServiceParent*>(csParent);
// Do not push these cookie updates to the same process they originated
// from.
if (cs->ProcessingCookie()) {
return NS_OK;
}
if (!nsCRT::strcmp(aData, u"batch-deleted")) {
nsCOMPtr<nsIArray> cookieList = do_QueryInterface(aSubject);
NS_ASSERTION(cookieList, "couldn't get cookie list");
cs->RemoveBatchDeletedCookies(cookieList);
return NS_OK;
}
if (!nsCRT::strcmp(aData, u"cleared")) {
cs->RemoveAll();
return NS_OK;
}
nsCOMPtr<nsICookie> xpcCookie = do_QueryInterface(aSubject);
NS_ASSERTION(xpcCookie, "couldn't get cookie");
// only broadcast the cookie change to content processes that need it
const Cookie& cookie = xpcCookie->AsCookie();
if (!cs->CookieMatchesContentList(cookie)) {
return NS_OK;
}
if (!nsCRT::strcmp(aData, u"deleted")) {
cs->RemoveCookie(cookie);
} else if ((!nsCRT::strcmp(aData, u"added")) ||
(!nsCRT::strcmp(aData, u"changed"))) {
cs->AddCookie(cookie);
}
} else if (!strcmp(aTopic, NS_NETWORK_LINK_TYPE_TOPIC)) {
UpdateNetworkLinkType();
} else if (!strcmp(aTopic, "network:socket-process-crashed")) {
Unused << SendSocketProcessCrashed();
} else if (!strcmp(aTopic, DEFAULT_TIMEZONE_CHANGED_OBSERVER_TOPIC)) {
Unused << SendSystemTimezoneChanged();
} else if (!strcmp(aTopic, NS_NETWORK_TRR_MODE_CHANGED_TOPIC)) {
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
nsIDNSService::ResolverMode mode;
dns->GetCurrentTrrMode(&mode);
Unused << SendSetTRRMode(mode);
}
return NS_OK;
}
void ContentParent::UpdateNetworkLinkType() {
nsresult rv;
nsCOMPtr<nsINetworkLinkService> nls =
do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
return;
}
uint32_t linkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
rv = nls->GetLinkType(&linkType);
if (NS_FAILED(rv)) {
return;
}
Unused << SendNetworkLinkTypeChange(linkType);
}
NS_IMETHODIMP
ContentParent::GetInterface(const nsIID& aIID, void** aResult) {
NS_ENSURE_ARG_POINTER(aResult);
if (aIID.Equals(NS_GET_IID(nsIMessageSender))) {
nsCOMPtr<nsIMessageSender> mm = GetMessageManager();
mm.forget(aResult);
return NS_OK;
}
return NS_NOINTERFACE;
}
mozilla::ipc::IPCResult ContentParent::RecvInitBackground(
Endpoint<PBackgroundStarterParent>&& aEndpoint) {
if (!BackgroundParent::AllocStarter(this, std::move(aEndpoint))) {
NS_WARNING("BackgroundParent::Alloc failed");
}
return IPC_OK();
}
bool ContentParent::CanOpenBrowser(const IPCTabContext& aContext) {
// (PopupIPCTabContext lets the child process prove that it has access to
// the app it's trying to open.)
// On e10s we also allow UnsafeTabContext to allow service workers to open
// windows. This is enforced in MaybeInvalidTabContext.
if (aContext.type() != IPCTabContext::TPopupIPCTabContext) {
MOZ_CRASH_UNLESS_FUZZING(
"Unexpected IPCTabContext type. Aborting AllocPBrowserParent.");
return false;
}
if (aContext.type() == IPCTabContext::TPopupIPCTabContext) {
const PopupIPCTabContext& popupContext = aContext.get_PopupIPCTabContext();
auto opener = BrowserParent::GetFrom(popupContext.openerParent());
if (!opener) {
MOZ_CRASH_UNLESS_FUZZING(
"Got null opener from child; aborting AllocPBrowserParent.");
return false;
}
}
MaybeInvalidTabContext tc(aContext);
if (!tc.IsValid()) {
NS_ERROR(nsPrintfCString("Child passed us an invalid TabContext. (%s) "
"Aborting AllocPBrowserParent.",
tc.GetInvalidReason())
.get());
return false;
}
return true;
}
static bool CloneIsLegal(ContentParent* aCp, CanonicalBrowsingContext& aSource,
CanonicalBrowsingContext& aTarget) {
// Source and target must be in the same BCG
if (NS_WARN_IF(aSource.Group() != aTarget.Group())) {
return false;
}
// The source and target must be in different toplevel <browser>s
if (NS_WARN_IF(aSource.Top() == aTarget.Top())) {
return false;
}
// Neither source nor target must be toplevel.
if (NS_WARN_IF(aSource.IsTop()) || NS_WARN_IF(aTarget.IsTop())) {
return false;
}
// Both should be embedded by the same process.
auto* sourceEmbedder = aSource.GetParentWindowContext();
if (NS_WARN_IF(!sourceEmbedder) ||
NS_WARN_IF(sourceEmbedder->GetContentParent() != aCp)) {
return false;
}
auto* targetEmbedder = aSource.GetParentWindowContext();
if (NS_WARN_IF(!targetEmbedder) ||
NS_WARN_IF(targetEmbedder->GetContentParent() != aCp)) {
return false;
}
// All seems sane.
return true;
}
mozilla::ipc::IPCResult ContentParent::RecvCloneDocumentTreeInto(
const MaybeDiscarded<BrowsingContext>& aSource,
const MaybeDiscarded<BrowsingContext>& aTarget, PrintData&& aPrintData) {
if (aSource.IsNullOrDiscarded() || aTarget.IsNullOrDiscarded()) {
return IPC_OK();
}
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
// All existing processes have potentially been slated for removal already,
// such that any subsequent call to GetNewOrUsedLaunchingBrowserProcess
// (normally supposed to find an existing process here) will try to create
// a new process (but fail) that nobody would ever really use. Let's avoid
// this together with the expensive CloneDocumentTreeInto operation.
return IPC_OK();
}
auto* source = aSource.get_canonical();
auto* target = aTarget.get_canonical();
if (!CloneIsLegal(this, *source, *target)) {
return IPC_FAIL(this, "Illegal subframe clone");
}
ContentParent* cp = source->GetContentParent();
if (NS_WARN_IF(!cp)) {
return IPC_OK();
}
if (NS_WARN_IF(cp->GetRemoteType() == GetRemoteType())) {
// Wanted to switch to a target browsing context that's already local again.
// See bug 1676996 for how this can happen.
//
// Dropping the switch on the floor seems fine for this case, though we
// could also try to clone the local document.
//
// If the remote type matches & it's in the same group (which was confirmed
// by CloneIsLegal), it must be the exact same process.
MOZ_DIAGNOSTIC_ASSERT(cp == this);
return IPC_OK();
}
target->CloneDocumentTreeInto(source, cp->GetRemoteType(),
std::move(aPrintData));
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvUpdateRemotePrintSettings(
const MaybeDiscarded<BrowsingContext>& aTarget, PrintData&& aPrintData) {
if (aTarget.IsNullOrDiscarded()) {
return IPC_OK();
}
auto* target = aTarget.get_canonical();
auto* bp = target->GetBrowserParent();
if (NS_WARN_IF(!bp)) {
return IPC_OK();
}
Unused << bp->SendUpdateRemotePrintSettings(std::move(aPrintData));
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvConstructPopupBrowser(
ManagedEndpoint<PBrowserParent>&& aBrowserEp,
ManagedEndpoint<PWindowGlobalParent>&& aWindowEp, const TabId& aTabId,
const IPCTabContext& aContext, const WindowGlobalInit& aInitialWindowInit,
const uint32_t& aChromeFlags) {
MOZ_ASSERT(XRE_IsParentProcess());
if (!CanOpenBrowser(aContext)) {
return IPC_FAIL(this, "CanOpenBrowser Failed");
}
RefPtr<CanonicalBrowsingContext> browsingContext =
CanonicalBrowsingContext::Get(
aInitialWindowInit.context().mBrowsingContextId);
if (!browsingContext || browsingContext->IsDiscarded()) {
return IPC_FAIL(this, "Null or discarded initial BrowsingContext");
}
if (!aInitialWindowInit.principal()) {
return IPC_FAIL(this, "Cannot create without valid initial principal");
}
if (!ValidatePrincipal(aInitialWindowInit.principal())) {
LogAndAssertFailedPrincipalValidationInfo(aInitialWindowInit.principal(),
__func__);
}
if (browsingContext->GetBrowserParent()) {
return IPC_FAIL(this, "BrowsingContext already has a BrowserParent");
}
uint32_t chromeFlags = aChromeFlags;
TabId openerTabId(0);
ContentParentId openerCpId(0);
if (aContext.type() == IPCTabContext::TPopupIPCTabContext) {
// CanOpenBrowser has ensured that the IPCTabContext is of
// type PopupIPCTabContext, and that the opener BrowserParent is
// reachable.
const PopupIPCTabContext& popupContext = aContext.get_PopupIPCTabContext();
auto opener = BrowserParent::GetFrom(popupContext.openerParent());
openerTabId = opener->GetTabId();
openerCpId = opener->Manager()->ChildID();
// We must ensure that the private browsing and remoteness flags
// match those of the opener.
nsCOMPtr<nsILoadContext> loadContext = opener->GetLoadContext();
if (!loadContext) {
return IPC_FAIL(this, "Missing Opener LoadContext");
}
if (loadContext->UsePrivateBrowsing()) {
chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
}
if (loadContext->UseRemoteSubframes()) {
chromeFlags |= nsIWebBrowserChrome::CHROME_FISSION_WINDOW;
}
}
// And because we're allocating a remote browser, of course the
// window is remote.
chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW;
if (NS_WARN_IF(!browsingContext->IsOwnedByProcess(ChildID()))) {
return IPC_FAIL(this, "BrowsingContext Owned by Incorrect Process!");
}
MaybeInvalidTabContext tc(aContext);
MOZ_ASSERT(tc.IsValid());
RefPtr<WindowGlobalParent> initialWindow =
WindowGlobalParent::CreateDisconnected(aInitialWindowInit);
if (!initialWindow) {
return IPC_FAIL(this, "Failed to create WindowGlobalParent");
}
auto parent = MakeRefPtr<BrowserParent>(this, aTabId, tc.GetTabContext(),
browsingContext, chromeFlags);
// Bind the created BrowserParent to IPC to actually link the actor.
if (NS_WARN_IF(!BindPBrowserEndpoint(std::move(aBrowserEp), parent))) {
return IPC_FAIL(this, "BindPBrowserEndpoint failed");
}
// XXX: Why are we checking these requirements? It seems we should register
// the created frame unconditionally?
if (openerTabId > 0) {
// The creation of PBrowser was triggered from content process through
// window.open().
// We need to register remote frame with the child generated tab id.
auto* cpm = ContentProcessManager::GetSingleton();
if (!cpm || !cpm->RegisterRemoteFrame(parent)) {
return IPC_FAIL(this, "RegisterRemoteFrame Failed");
}
}
if (NS_WARN_IF(!parent->BindPWindowGlobalEndpoint(std::move(aWindowEp),
initialWindow))) {
return IPC_FAIL(this, "BindPWindowGlobalEndpoint failed");
}
browsingContext->SetCurrentBrowserParent(parent);
initialWindow->Init();
// When enabling input event prioritization, input events may preempt other
// normal priority IPC messages. To prevent the input events preempt
// PBrowserConstructor, we use an IPC 'RemoteIsReadyToHandleInputEvents' to
// notify parent that BrowserChild is created. In this case, PBrowser is
// initiated from content so that we can set BrowserParent as ready to handle
// input
parent->SetReadyToHandleInputEvents();
return IPC_OK();
}
mozilla::PRemoteSpellcheckEngineParent*
ContentParent::AllocPRemoteSpellcheckEngineParent() {
mozilla::RemoteSpellcheckEngineParent* parent =
new mozilla::RemoteSpellcheckEngineParent();
return parent;
}
bool ContentParent::DeallocPRemoteSpellcheckEngineParent(
PRemoteSpellcheckEngineParent* parent) {
delete parent;
return true;
}
/* static */
void ContentParent::ForceKillTimerCallback(nsITimer* aTimer, void* aClosure) {
// We don't want to time out the content process during XPCShell tests. This
// is the easiest way to ensure that.
if (PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) {
return;
}
auto self = static_cast<ContentParent*>(aClosure);
self->KillHard("ShutDownKill");
}
void ContentParent::GeneratePairedMinidump(const char* aReason) {
// We're about to kill the child process associated with this content.
// Something has gone wrong to get us here, so we generate a minidump
// of the parent and child for submission to the crash server unless we're
// already shutting down.
if (mCrashReporter &&
!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed) &&
StaticPrefs::dom_ipc_tabs_createKillHardCrashReports_AtStartup()) {
// GeneratePairedMinidump creates two minidumps for us - the main
// one is for the content process we're about to kill, and the other
// one is for the main browser process. That second one is the extra
// minidump tagging along, so we have to tell the crash reporter that
// it exists and is being appended.
nsAutoCString additionalDumps("browser");
mCrashReporter->AddAnnotation(
CrashReporter::Annotation::additional_minidumps, additionalDumps);
nsDependentCString reason(aReason);
mCrashReporter->AddAnnotation(CrashReporter::Annotation::ipc_channel_error,
reason);
// Generate the report and insert into the queue for submittal.
if (mCrashReporter->GenerateMinidumpAndPair(this, "browser"_ns)) {
mCrashReporter->FinalizeCrashReport();
mCreatedPairedMinidumps = true;
}
}
}
void ContentParent::HandleOrphanedMinidump(nsString* aDumpId) {
if (CrashReporter::FinalizeOrphanedMinidump(
OtherPid(), GeckoProcessType_Content, aDumpId)) {
CrashReporterHost::RecordCrash(GeckoProcessType_Content,
nsICrashService::CRASH_TYPE_CRASH, *aDumpId);
} else {
NS_WARNING(nsPrintfCString("content process pid = %" PRIPID
" crashed without leaving a minidump behind",
OtherPid())
.get());
}
}
// WARNING: aReason appears in telemetry, so any new value passed in requires
// data review.
void ContentParent::KillHard(const char* aReason) {
AUTO_PROFILER_LABEL("ContentParent::KillHard", OTHER);
// On Windows, calling KillHard multiple times causes problems - the
// process handle becomes invalid on the first call, causing a second call
// to crash our process - more details in bug 890840.
if (mCalledKillHard) {
return;
}
mCalledKillHard = true;
mForceKillTimer = nullptr;
RemoveShutdownBlockers();
nsCString reason = nsDependentCString(aReason);
// If we find mIsNotifiedShutdownSuccess there is no reason to blame this
// content process, most probably our parent process is just slow in
// processing its own main thread queue.
if (!mIsNotifiedShutdownSuccess) {
GeneratePairedMinidump(aReason);
} else {
reason = nsDependentCString("KillHard after IsNotifiedShutdownSuccess.");
}
Telemetry::Accumulate(Telemetry::SUBPROCESS_KILL_HARD, reason, 1);
ProcessHandle otherProcessHandle;
if (!base::OpenProcessHandle(OtherPid(), &otherProcessHandle)) {
NS_ERROR("Failed to open child process when attempting kill.");
return;
}
if (!KillProcess(otherProcessHandle, base::PROCESS_END_KILLED_BY_USER)) {
if (mCrashReporter) {
mCrashReporter->DeleteCrashReport();
}
NS_WARNING("failed to kill subprocess!");
}
if (mSubprocess) {
MOZ_LOG(
ContentParent::GetLog(), LogLevel::Verbose,
("KillHard Subprocess(%s): ContentParent %p mSubprocess %p handle "
"%" PRIuPTR,
aReason, this, mSubprocess,
mSubprocess ? (uintptr_t)mSubprocess->GetChildProcessHandle() : -1));
mSubprocess->SetAlreadyDead();
}
// EnsureProcessTerminated has responsibilty for closing otherProcessHandle.
XRE_GetIOMessageLoop()->PostTask(
NewRunnableFunction("EnsureProcessTerminatedRunnable",
&ProcessWatcher::EnsureProcessTerminated,
otherProcessHandle, /*force=*/true));
}
void ContentParent::FriendlyName(nsAString& aName, bool aAnonymize) {
aName.Truncate();
if (mIsForBrowser) {
aName.AssignLiteral("Browser");
} else if (aAnonymize) {
aName.AssignLiteral("<anonymized-name>");
} else {
aName.AssignLiteral("???");
}
}
mozilla::ipc::IPCResult ContentParent::RecvInitCrashReporter(
const NativeThreadId& aThreadId) {
mCrashReporter =
MakeUnique<CrashReporterHost>(GeckoProcessType_Content, aThreadId);
return IPC_OK();
}
hal_sandbox::PHalParent* ContentParent::AllocPHalParent() {
return hal_sandbox::CreateHalParent();
}
bool ContentParent::DeallocPHalParent(hal_sandbox::PHalParent* aHal) {
delete aHal;
return true;
}
devtools::PHeapSnapshotTempFileHelperParent*
ContentParent::AllocPHeapSnapshotTempFileHelperParent() {
return devtools::HeapSnapshotTempFileHelperParent::Create();
}
bool ContentParent::DeallocPHeapSnapshotTempFileHelperParent(
devtools::PHeapSnapshotTempFileHelperParent* aHeapSnapshotHelper) {
delete aHeapSnapshotHelper;
return true;
}
bool ContentParent::SendRequestMemoryReport(
const uint32_t& aGeneration, const bool& aAnonymize,
const bool& aMinimizeMemoryUsage, const Maybe<FileDescriptor>& aDMDFile) {
// This automatically cancels the previous request.
mMemoryReportRequest = MakeUnique<MemoryReportRequestHost>(aGeneration);
// If we run the callback in response to a reply, then by definition |this|
// is still alive, so the ref pointer is redundant, but it seems easier
// to hold a strong reference than to worry about that.
RefPtr<ContentParent> self(this);
PContentParent::SendRequestMemoryReport(
aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile,
[&, self](const uint32_t& aGeneration2) {
if (self->mMemoryReportRequest) {
self->mMemoryReportRequest->Finish(aGeneration2);
self->mMemoryReportRequest = nullptr;
}
},
[&, self](mozilla::ipc::ResponseRejectReason) {
self->mMemoryReportRequest = nullptr;
});
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvAddMemoryReport(
const MemoryReport& aReport) {
if (mMemoryReportRequest) {
mMemoryReportRequest->RecvReport(aReport);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvAddPerformanceMetrics(
const nsID& aID, nsTArray<PerformanceInfo>&& aMetrics) {
nsresult rv = PerformanceMetricsCollector::DataReceived(aID, aMetrics);
Unused << NS_WARN_IF(NS_FAILED(rv));
return IPC_OK();
}
PCycleCollectWithLogsParent* ContentParent::AllocPCycleCollectWithLogsParent(
const bool& aDumpAllTraces, const FileDescriptor& aGCLog,
const FileDescriptor& aCCLog) {
MOZ_CRASH("Don't call this; use ContentParent::CycleCollectWithLogs");
}
bool ContentParent::DeallocPCycleCollectWithLogsParent(
PCycleCollectWithLogsParent* aActor) {
delete aActor;
return true;
}
bool ContentParent::CycleCollectWithLogs(
bool aDumpAllTraces, nsICycleCollectorLogSink* aSink,
nsIDumpGCAndCCLogsCallback* aCallback) {
return CycleCollectWithLogsParent::AllocAndSendConstructor(
this, aDumpAllTraces, aSink, aCallback);
}
PTestShellParent* ContentParent::AllocPTestShellParent() {
return new TestShellParent();
}
bool ContentParent::DeallocPTestShellParent(PTestShellParent* shell) {
delete shell;
return true;
}
PScriptCacheParent* ContentParent::AllocPScriptCacheParent(
const FileDescOrError& cacheFile, const bool& wantCacheData) {
return new loader::ScriptCacheParent(wantCacheData);
}
bool ContentParent::DeallocPScriptCacheParent(PScriptCacheParent* cache) {
delete static_cast<loader::ScriptCacheParent*>(cache);
return true;
}
already_AddRefed<PNeckoParent> ContentParent::AllocPNeckoParent() {
RefPtr<NeckoParent> actor = new NeckoParent();
return actor.forget();
}
mozilla::ipc::IPCResult ContentParent::RecvInitStreamFilter(
const uint64_t& aChannelId, const nsAString& aAddonId,
InitStreamFilterResolver&& aResolver) {
extensions::StreamFilterParent::Create(this, aChannelId, aAddonId)
->Then(
GetCurrentSerialEventTarget(), __func__,
[aResolver](mozilla::ipc::Endpoint<PStreamFilterChild>&& aEndpoint) {
aResolver(std::move(aEndpoint));
},
[aResolver](bool aDummy) {
aResolver(mozilla::ipc::Endpoint<PStreamFilterChild>());
});
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvAddSecurityState(
const MaybeDiscarded<WindowContext>& aContext, uint32_t aStateFlags) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
aContext.get()->AddSecurityState(aStateFlags);
return IPC_OK();
}
already_AddRefed<PExternalHelperAppParent>
ContentParent::AllocPExternalHelperAppParent(
nsIURI* uri, const Maybe<mozilla::net::LoadInfoArgs>& aLoadInfoArgs,
const nsACString& aMimeContentType, const nsACString& aContentDisposition,
const uint32_t& aContentDispositionHint,
const nsAString& aContentDispositionFilename, const bool& aForceSave,
const int64_t& aContentLength, const bool& aWasFileChannel,
nsIURI* aReferrer, const MaybeDiscarded<BrowsingContext>& aContext,
const bool& aShouldCloseWindow) {
RefPtr<ExternalHelperAppParent> parent = new ExternalHelperAppParent(
uri, aContentLength, aWasFileChannel, aContentDisposition,
aContentDispositionHint, aContentDispositionFilename);
return parent.forget();
}
mozilla::ipc::IPCResult ContentParent::RecvPExternalHelperAppConstructor(
PExternalHelperAppParent* actor, nsIURI* uri,
const Maybe<LoadInfoArgs>& loadInfoArgs, const nsACString& aMimeContentType,
const nsACString& aContentDisposition,
const uint32_t& aContentDispositionHint,
const nsAString& aContentDispositionFilename, const bool& aForceSave,
const int64_t& aContentLength, const bool& aWasFileChannel,
nsIURI* aReferrer, const MaybeDiscarded<BrowsingContext>& aContext,
const bool& aShouldCloseWindow) {
BrowsingContext* context = aContext.IsDiscarded() ? nullptr : aContext.get();
if (!static_cast<ExternalHelperAppParent*>(actor)->Init(
loadInfoArgs, aMimeContentType, aForceSave, aReferrer, context,
aShouldCloseWindow)) {
return IPC_FAIL(this, "Init failed.");
}
return IPC_OK();
}
already_AddRefed<PHandlerServiceParent>
ContentParent::AllocPHandlerServiceParent() {
RefPtr<HandlerServiceParent> actor = new HandlerServiceParent();
return actor.forget();
}
media::PMediaParent* ContentParent::AllocPMediaParent() {
return media::AllocPMediaParent();
}
bool ContentParent::DeallocPMediaParent(media::PMediaParent* aActor) {
return media::DeallocPMediaParent(aActor);
}
PBenchmarkStorageParent* ContentParent::AllocPBenchmarkStorageParent() {
return new BenchmarkStorageParent;
}
bool ContentParent::DeallocPBenchmarkStorageParent(
PBenchmarkStorageParent* aActor) {
delete aActor;
return true;
}
#ifdef MOZ_WEBSPEECH
PSpeechSynthesisParent* ContentParent::AllocPSpeechSynthesisParent() {
if (!StaticPrefs::media_webspeech_synth_enabled()) {
return nullptr;
}
return new mozilla::dom::SpeechSynthesisParent();
}
bool ContentParent::DeallocPSpeechSynthesisParent(
PSpeechSynthesisParent* aActor) {
delete aActor;
return true;
}
mozilla::ipc::IPCResult ContentParent::RecvPSpeechSynthesisConstructor(
PSpeechSynthesisParent* aActor) {
if (!static_cast<SpeechSynthesisParent*>(aActor)->SendInit()) {
return IPC_FAIL(this, "SpeechSynthesisParent::SendInit failed.");
}
return IPC_OK();
}
#endif
mozilla::ipc::IPCResult ContentParent::RecvStartVisitedQueries(
const nsTArray<RefPtr<nsIURI>>& aUris) {
nsCOMPtr<IHistory> history = components::History::Service();
if (!history) {
return IPC_OK();
}
for (const auto& uri : aUris) {
if (NS_WARN_IF(!uri)) {
continue;
}
history->ScheduleVisitedQuery(uri, this);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSetURITitle(nsIURI* uri,
const nsAString& title) {
if (!uri) {
return IPC_FAIL(this, "uri must not be null.");
}
nsCOMPtr<IHistory> history = components::History::Service();
if (history) {
history->SetURITitle(uri, title);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvIsSecureURI(
nsIURI* aURI, const OriginAttributes& aOriginAttributes,
bool* aIsSecureURI) {
nsCOMPtr<nsISiteSecurityService> sss(do_GetService(NS_SSSERVICE_CONTRACTID));
if (!sss) {
return IPC_FAIL(this, "Failed to get nsISiteSecurityService.");
}
if (!aURI) {
return IPC_FAIL(this, "aURI must not be null.");
}
nsresult rv = sss->IsSecureURI(aURI, aOriginAttributes, aIsSecureURI);
if (NS_FAILED(rv)) {
return IPC_FAIL(this, "IsSecureURI failed.");
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvAccumulateMixedContentHSTS(
nsIURI* aURI, const bool& aActive,
const OriginAttributes& aOriginAttributes) {
if (!aURI) {
return IPC_FAIL(this, "aURI must not be null.");
}
nsMixedContentBlocker::AccumulateMixedContentHSTS(aURI, aActive,
aOriginAttributes);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvLoadURIExternal(
nsIURI* uri, nsIPrincipal* aTriggeringPrincipal,
nsIPrincipal* aRedirectPrincipal,
const MaybeDiscarded<BrowsingContext>& aContext,
bool aWasExternallyTriggered, bool aHasValidUserGestureActivation) {
if (aContext.IsDiscarded()) {
return IPC_OK();
}
nsCOMPtr<nsIExternalProtocolService> extProtService(
do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID));
if (!extProtService) {
return IPC_OK();
}
if (!uri) {
return IPC_FAIL(this, "uri must not be null.");
}
BrowsingContext* bc = aContext.get();
extProtService->LoadURI(uri, aTriggeringPrincipal, aRedirectPrincipal, bc,
aWasExternallyTriggered,
aHasValidUserGestureActivation);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvExtProtocolChannelConnectParent(
const uint64_t& registrarId) {
nsresult rv;
// First get the real channel created before redirect on the parent.
nsCOMPtr<nsIChannel> channel;
rv = NS_LinkRedirectChannels(registrarId, nullptr, getter_AddRefs(channel));
NS_ENSURE_SUCCESS(rv, IPC_OK());
nsCOMPtr<nsIParentChannel> parent = do_QueryInterface(channel, &rv);
NS_ENSURE_SUCCESS(rv, IPC_OK());
// The channel itself is its own (faked) parent, link it.
rv = NS_LinkRedirectChannels(registrarId, parent, getter_AddRefs(channel));
NS_ENSURE_SUCCESS(rv, IPC_OK());
// Signal the parent channel that it's a redirect-to parent. This will
// make AsyncOpen on it do nothing (what we want).
// Yes, this is a bit of a hack, but I don't think it's necessary to invent
// a new interface just to set this flag on the channel.
parent->SetParentListener(nullptr);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvShowAlert(
nsIAlertNotification* aAlert) {
if (!aAlert) {
return IPC_FAIL(this, "aAlert must not be null.");
}
nsCOMPtr<nsIAlertsService> sysAlerts(components::Alerts::Service());
if (sysAlerts) {
sysAlerts->ShowAlert(aAlert, this);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvCloseAlert(const nsAString& aName,
bool aContextClosed) {
nsCOMPtr<nsIAlertsService> sysAlerts(components::Alerts::Service());
if (sysAlerts) {
sysAlerts->CloseAlert(aName, aContextClosed);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvDisableNotifications(
nsIPrincipal* aPrincipal) {
if (!aPrincipal) {
return IPC_FAIL(this, "No principal");
}
if (!ValidatePrincipal(aPrincipal)) {
LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__);
}
Unused << Notification::RemovePermission(aPrincipal);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvOpenNotificationSettings(
nsIPrincipal* aPrincipal) {
if (!aPrincipal) {
return IPC_FAIL(this, "No principal");
}
if (!ValidatePrincipal(aPrincipal)) {
LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__);
}
Unused << Notification::OpenSettings(aPrincipal);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvNotificationEvent(
const nsAString& aType, const NotificationEventData& aData) {
nsCOMPtr<nsIServiceWorkerManager> swm =
mozilla::components::ServiceWorkerManager::Service();
if (NS_WARN_IF(!swm)) {
// Probably shouldn't happen, but no need to crash the child process.
return IPC_OK();
}
if (aType.EqualsLiteral("click")) {
nsresult rv = swm->SendNotificationClickEvent(
aData.originSuffix(), aData.scope(), aData.ID(), aData.title(),
aData.dir(), aData.lang(), aData.body(), aData.tag(), aData.icon(),
aData.data(), aData.behavior());
Unused << NS_WARN_IF(NS_FAILED(rv));
} else {
MOZ_ASSERT(aType.EqualsLiteral("close"));
nsresult rv = swm->SendNotificationCloseEvent(
aData.originSuffix(), aData.scope(), aData.ID(), aData.title(),
aData.dir(), aData.lang(), aData.body(), aData.tag(), aData.icon(),
aData.data(), aData.behavior());
Unused << NS_WARN_IF(NS_FAILED(rv));
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSyncMessage(
const nsAString& aMsg, const ClonedMessageData& aData,
nsTArray<StructuredCloneData>* aRetvals) {
AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING("ContentParent::RecvSyncMessage",
OTHER, aMsg);
MMPrinter::Print("ContentParent::RecvSyncMessage", aMsg, aData);
RefPtr<nsFrameMessageManager> ppm = mMessageManager;
if (ppm) {
ipc::StructuredCloneData data;
ipc::UnpackClonedMessageData(aData, data);
ppm->ReceiveMessage(ppm, nullptr, aMsg, true, &data, aRetvals,
IgnoreErrors());
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvAsyncMessage(
const nsAString& aMsg, const ClonedMessageData& aData) {
AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING("ContentParent::RecvAsyncMessage",
OTHER, aMsg);
MMPrinter::Print("ContentParent::RecvAsyncMessage", aMsg, aData);
RefPtr<nsFrameMessageManager> ppm = mMessageManager;
if (ppm) {
ipc::StructuredCloneData data;
ipc::UnpackClonedMessageData(aData, data);
ppm->ReceiveMessage(ppm, nullptr, aMsg, false, &data, nullptr,
IgnoreErrors());
}
return IPC_OK();
}
MOZ_CAN_RUN_SCRIPT
static int32_t AddGeolocationListener(
nsIDOMGeoPositionCallback* watcher,
nsIDOMGeoPositionErrorCallback* errorCallBack, bool highAccuracy) {
RefPtr<Geolocation> geo = Geolocation::NonWindowSingleton();
UniquePtr<PositionOptions> options = MakeUnique<PositionOptions>();
options->mTimeout = 0;
options->mMaximumAge = 0;
options->mEnableHighAccuracy = highAccuracy;
return geo->WatchPosition(watcher, errorCallBack, std::move(options));
}
mozilla::ipc::IPCResult ContentParent::RecvAddGeolocationListener(
const bool& aHighAccuracy) {
// To ensure no geolocation updates are skipped, we always force the
// creation of a new listener.
RecvRemoveGeolocationListener();
mGeolocationWatchID = AddGeolocationListener(this, this, aHighAccuracy);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvRemoveGeolocationListener() {
if (mGeolocationWatchID != -1) {
RefPtr<Geolocation> geo = Geolocation::NonWindowSingleton();
if (geo) {
geo->ClearWatch(mGeolocationWatchID);
}
mGeolocationWatchID = -1;
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSetGeolocationHigherAccuracy(
const bool& aEnable) {
// This should never be called without a listener already present,
// so this check allows us to forgo securing privileges.
if (mGeolocationWatchID != -1) {
RecvRemoveGeolocationListener();
mGeolocationWatchID = AddGeolocationListener(this, this, aEnable);
}
return IPC_OK();
}
NS_IMETHODIMP
ContentParent::HandleEvent(nsIDOMGeoPosition* postion) {
Unused << SendGeolocationUpdate(postion);
return NS_OK;
}
NS_IMETHODIMP
ContentParent::HandleEvent(GeolocationPositionError* positionError) {
Unused << SendGeolocationError(positionError->Code());
return NS_OK;
}
mozilla::ipc::IPCResult ContentParent::RecvConsoleMessage(
const nsAString& aMessage) {
nsresult rv;
nsCOMPtr<nsIConsoleService> consoleService =
do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv)) {
RefPtr<nsConsoleMessage> msg(new nsConsoleMessage(aMessage));
msg->SetIsForwardedFromContentProcess(true);
consoleService->LogMessageWithMode(msg, nsIConsoleService::SuppressLog);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvReportFrameTimingData(
const mozilla::Maybe<LoadInfoArgs>& loadInfoArgs,
const nsAString& entryName, const nsAString& initiatorType,
UniquePtr<PerformanceTimingData>&& aData) {
if (!aData) {
return IPC_FAIL(this, "aData should not be null");
}
if (loadInfoArgs.isNothing()) {
return IPC_FAIL(this, "loadInfoArgs should not be null");
}
RefPtr<WindowGlobalParent> parent =
WindowGlobalParent::GetByInnerWindowId(loadInfoArgs->innerWindowID());
if (!parent || !parent->GetContentParent()) {
return IPC_OK();
}
MOZ_ASSERT(parent->GetContentParent() != this,
"No need to bounce around if in the same process");
Unused << parent->GetContentParent()->SendReportFrameTimingData(
loadInfoArgs, entryName, initiatorType, std::move(aData));
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvScriptError(
const nsAString& aMessage, const nsAString& aSourceName,
const nsAString& aSourceLine, const uint32_t& aLineNumber,
const uint32_t& aColNumber, const uint32_t& aFlags,
const nsACString& aCategory, const bool& aFromPrivateWindow,
const uint64_t& aInnerWindowId, const bool& aFromChromeContext) {
return RecvScriptErrorInternal(aMessage, aSourceName, aSourceLine,
aLineNumber, aColNumber, aFlags, aCategory,
aFromPrivateWindow, aFromChromeContext);
}
mozilla::ipc::IPCResult ContentParent::RecvScriptErrorWithStack(
const nsAString& aMessage, const nsAString& aSourceName,
const nsAString& aSourceLine, const uint32_t& aLineNumber,
const uint32_t& aColNumber, const uint32_t& aFlags,
const nsACString& aCategory, const bool& aFromPrivateWindow,
const bool& aFromChromeContext, const ClonedMessageData& aFrame) {
return RecvScriptErrorInternal(
aMessage, aSourceName, aSourceLine, aLineNumber, aColNumber, aFlags,
aCategory, aFromPrivateWindow, aFromChromeContext, &aFrame);
}
mozilla::ipc::IPCResult ContentParent::RecvScriptErrorInternal(
const nsAString& aMessage, const nsAString& aSourceName,
const nsAString& aSourceLine, const uint32_t& aLineNumber,
const uint32_t& aColNumber, const uint32_t& aFlags,
const nsACString& aCategory, const bool& aFromPrivateWindow,
const bool& aFromChromeContext, const ClonedMessageData* aStack) {
nsresult rv;
nsCOMPtr<nsIConsoleService> consoleService =
do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
return IPC_OK();
}
nsCOMPtr<nsIScriptError> msg;
if (aStack) {
StructuredCloneData data;
UnpackClonedMessageData(*aStack, data);
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(xpc::PrivilegedJunkScope()))) {
MOZ_CRASH();
}
JSContext* cx = jsapi.cx();
JS::Rooted<JS::Value> stack(cx);
ErrorResult rv;
data.Read(cx, &stack, rv);
if (rv.Failed() || !stack.isObject()) {
rv.SuppressException();
return IPC_OK();
}
JS::Rooted<JSObject*> stackObj(cx, &stack.toObject());
MOZ_ASSERT(JS::IsUnwrappedSavedFrame(stackObj));
JS::Rooted<JSObject*> stackGlobal(cx, JS::GetNonCCWObjectGlobal(stackObj));
msg = new nsScriptErrorWithStack(JS::NothingHandleValue, stackObj,
stackGlobal);
} else {
msg = new nsScriptError();
}
rv = msg->Init(aMessage, aSourceName, aSourceLine, aLineNumber, aColNumber,
aFlags, aCategory, aFromPrivateWindow, aFromChromeContext);
if (NS_FAILED(rv)) return IPC_OK();
msg->SetIsForwardedFromContentProcess(true);
consoleService->LogMessageWithMode(msg, nsIConsoleService::SuppressLog);
return IPC_OK();
}
bool ContentParent::DoLoadMessageManagerScript(const nsAString& aURL,
bool aRunInGlobalScope) {
MOZ_ASSERT(!aRunInGlobalScope);
return SendLoadProcessScript(aURL);
}
nsresult ContentParent::DoSendAsyncMessage(const nsAString& aMessage,
StructuredCloneData& aHelper) {
ClonedMessageData data;
if (!BuildClonedMessageData(aHelper, data)) {
return NS_ERROR_DOM_DATA_CLONE_ERR;
}
if (!SendAsyncMessage(aMessage, data)) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
mozilla::ipc::IPCResult ContentParent::RecvCopyFavicon(
nsIURI* aOldURI, nsIURI* aNewURI, const bool& aInPrivateBrowsing) {
if (!aOldURI) {
return IPC_FAIL(this, "aOldURI should not be null");
}
if (!aNewURI) {
return IPC_FAIL(this, "aNewURI should not be null");
}
nsDocShell::CopyFavicon(aOldURI, aNewURI, aInPrivateBrowsing);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvFindImageText(
IPCImage&& aImage, nsTArray<nsCString>&& aLanguages,
FindImageTextResolver&& aResolver) {
RefPtr<DataSourceSurface> surf =
nsContentUtils::IPCImageToSurface(std::move(aImage));
if (!surf) {
aResolver(TextRecognitionResultOrError("Failed to read image"_ns));
return IPC_OK();
}
TextRecognition::FindText(*surf, aLanguages)
->Then(
GetCurrentSerialEventTarget(), __func__,
[resolver = std::move(aResolver)](
TextRecognition::NativePromise::ResolveOrRejectValue&& aValue) {
if (aValue.IsResolve()) {
resolver(TextRecognitionResultOrError(aValue.ResolveValue()));
} else {
resolver(TextRecognitionResultOrError(aValue.RejectValue()));
}
});
return IPC_OK();
}
bool ContentParent::ShouldContinueFromReplyTimeout() {
RefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get();
return !monitor || !monitor->ShouldTimeOutCPOWs();
}
mozilla::ipc::IPCResult ContentParent::RecvRecordingDeviceEvents(
const nsAString& aRecordingStatus, const nsAString& aPageURL,
const bool& aIsAudio, const bool& aIsVideo) {
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
// recording-device-ipc-events needs to gather more information from content
// process
RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
props->SetPropertyAsUint64(u"childID"_ns, ChildID());
props->SetPropertyAsBool(u"isAudio"_ns, aIsAudio);
props->SetPropertyAsBool(u"isVideo"_ns, aIsVideo);
props->SetPropertyAsAString(u"requestURL"_ns, aPageURL);
obs->NotifyObservers((nsIPropertyBag2*)props, "recording-device-ipc-events",
PromiseFlatString(aRecordingStatus).get());
} else {
NS_WARNING(
"Could not get the Observer service for "
"ContentParent::RecvRecordingDeviceEvents.");
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvAddIdleObserver(
const uint64_t& aObserver, const uint32_t& aIdleTimeInS) {
nsresult rv;
nsCOMPtr<nsIUserIdleService> idleService =
do_GetService("@mozilla.org/widget/useridleservice;1", &rv);
NS_ENSURE_SUCCESS(rv, IPC_FAIL(this, "Failed to get UserIdleService."));
RefPtr<ParentIdleListener> listener =
new ParentIdleListener(this, aObserver, aIdleTimeInS);
rv = idleService->AddIdleObserver(listener, aIdleTimeInS);
NS_ENSURE_SUCCESS(rv, IPC_FAIL(this, "AddIdleObserver failed."));
mIdleListeners.AppendElement(listener);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvRemoveIdleObserver(
const uint64_t& aObserver, const uint32_t& aIdleTimeInS) {
RefPtr<ParentIdleListener> listener;
for (int32_t i = mIdleListeners.Length() - 1; i >= 0; --i) {
listener = static_cast<ParentIdleListener*>(mIdleListeners[i].get());
if (listener->mObserver == aObserver && listener->mTime == aIdleTimeInS) {
nsresult rv;
nsCOMPtr<nsIUserIdleService> idleService =
do_GetService("@mozilla.org/widget/useridleservice;1", &rv);
NS_ENSURE_SUCCESS(rv, IPC_FAIL(this, "Failed to get UserIdleService."));
idleService->RemoveIdleObserver(listener, aIdleTimeInS);
mIdleListeners.RemoveElementAt(i);
break;
}
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvBackUpXResources(
const FileDescriptor& aXSocketFd) {
#ifndef MOZ_X11
MOZ_CRASH("This message only makes sense on X11 platforms");
#else
MOZ_ASSERT(0 > mChildXSocketFdDup.get(), "Already backed up X resources??");
if (aXSocketFd.IsValid()) {
auto rawFD = aXSocketFd.ClonePlatformHandle();
mChildXSocketFdDup.reset(rawFD.release());
}
#endif
return IPC_OK();
}
class AnonymousTemporaryFileRequestor final : public Runnable {
public:
AnonymousTemporaryFileRequestor(ContentParent* aCP, const uint64_t& aID)
: Runnable("dom::AnonymousTemporaryFileRequestor"),
mCP(aCP),
mID(aID),
mRv(NS_OK),
mPRFD(nullptr) {}
NS_IMETHOD Run() override {
if (NS_IsMainThread()) {
FileDescOrError result;
if (NS_WARN_IF(NS_FAILED(mRv))) {
// Returning false will kill the child process; instead
// propagate the error and let the child handle it.
result = mRv;
} else {
result = FileDescriptor(FileDescriptor::PlatformHandleType(
PR_FileDesc2NativeHandle(mPRFD)));
// The FileDescriptor object owns a duplicate of the file handle; we
// must close the original (and clean up the NSPR descriptor).
PR_Close(mPRFD);
}
Unused << mCP->SendProvideAnonymousTemporaryFile(mID, result);
// It's important to release this reference while wr're on the main
// thread!
mCP = nullptr;
} else {
mRv = NS_OpenAnonymousTemporaryFile(&mPRFD);
NS_DispatchToMainThread(this);
}
return NS_OK;
}
private:
RefPtr<ContentParent> mCP;
uint64_t mID;
nsresult mRv;
PRFileDesc* mPRFD;
};
mozilla::ipc::IPCResult ContentParent::RecvRequestAnonymousTemporaryFile(
const uint64_t& aID) {
// Make sure to send a callback to the child if we bail out early.
nsresult rv = NS_OK;
RefPtr<ContentParent> self(this);
auto autoNotifyChildOnError = MakeScopeExit([&, self]() {
if (NS_FAILED(rv)) {
FileDescOrError result(rv);
Unused << self->SendProvideAnonymousTemporaryFile(aID, result);
}
});
// We use a helper runnable to open the anonymous temporary file on the IO
// thread. The same runnable will call us back on the main thread when the
// file has been opened.
nsCOMPtr<nsIEventTarget> target =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
if (!target) {
return IPC_OK();
}
rv = target->Dispatch(new AnonymousTemporaryFileRequestor(this, aID),
NS_DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) {
return IPC_OK();
}
rv = NS_OK;
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvCreateAudioIPCConnection(
CreateAudioIPCConnectionResolver&& aResolver) {
FileDescriptor fd = CubebUtils::CreateAudioIPCConnection();
FileDescOrError result;
if (fd.IsValid()) {
result = fd;
} else {
result = NS_ERROR_FAILURE;
}
aResolver(std::move(result));
return IPC_OK();
}
already_AddRefed<extensions::PExtensionsParent>
ContentParent::AllocPExtensionsParent() {
return MakeAndAddRef<extensions::ExtensionsParent>();
}
void ContentParent::NotifyUpdatedDictionaries() {
RefPtr<mozSpellChecker> spellChecker(mozSpellChecker::Create());
MOZ_ASSERT(spellChecker, "No spell checker?");
nsTArray<nsCString> dictionaries;
spellChecker->GetDictionaryList(&dictionaries);
for (auto* cp : AllProcesses(eLive)) {
Unused << cp->SendUpdateDictionaryList(dictionaries);
}
}
void ContentParent::NotifyUpdatedFonts(bool aFullRebuild) {
if (gfxPlatformFontList::PlatformFontList()->SharedFontList()) {
for (auto* cp : AllProcesses(eLive)) {
Unused << cp->SendRebuildFontList(aFullRebuild);
}
return;
}
SystemFontList fontList;
gfxPlatform::GetPlatform()->ReadSystemFontList(&fontList);
for (auto* cp : AllProcesses(eLive)) {
Unused << cp->SendUpdateFontList(fontList);
}
}
#ifdef MOZ_WEBRTC
PWebrtcGlobalParent* ContentParent::AllocPWebrtcGlobalParent() {
return WebrtcGlobalParent::Alloc();
}
bool ContentParent::DeallocPWebrtcGlobalParent(PWebrtcGlobalParent* aActor) {
WebrtcGlobalParent::Dealloc(static_cast<WebrtcGlobalParent*>(aActor));
return true;
}
#endif
void ContentParent::MaybeInvokeDragSession(BrowserParent* aParent) {
// dnd uses IPCBlob to transfer data to the content process and the IPC
// message is sent as normal priority. When sending input events with input
// priority, the message may be preempted by the later dnd events. To make
// sure the input events and the blob message are processed in time order
// on the content process, we temporarily send the input events with normal
// priority when there is an active dnd session.
SetInputPriorityEventEnabled(false);
nsCOMPtr<nsIDragService> dragService =
do_GetService("@mozilla.org/widget/dragservice;1");
if (dragService && dragService->MaybeAddChildProcess(this)) {
// We need to send transferable data to child process.
nsCOMPtr<nsIDragSession> session;
dragService->GetCurrentSession(getter_AddRefs(session));
if (session) {
nsTArray<IPCDataTransfer> dataTransfers;
RefPtr<DataTransfer> transfer = session->GetDataTransfer();
if (!transfer) {
// Pass eDrop to get DataTransfer with external
// drag formats cached.
transfer = new DataTransfer(nullptr, eDrop, true, -1);
session->SetDataTransfer(transfer);
}
// Note, even though this fills the DataTransfer object with
// external data, the data is usually transfered over IPC lazily when
// needed.
transfer->FillAllExternalData();
nsCOMPtr<nsILoadContext> lc =
aParent ? aParent->GetLoadContext() : nullptr;
nsCOMPtr<nsIArray> transferables = transfer->GetTransferables(lc);
nsContentUtils::TransferablesToIPCTransferables(
transferables, dataTransfers, false, nullptr, this);
uint32_t action;
session->GetDragAction(&action);
RefPtr<WindowContext> sourceWC;
session->GetSourceWindowContext(getter_AddRefs(sourceWC));
RefPtr<WindowContext> sourceTopWC;
session->GetSourceTopWindowContext(getter_AddRefs(sourceTopWC));
mozilla::Unused << SendInvokeDragSession(
sourceWC, sourceTopWC, std::move(dataTransfers), action);
}
}
}
mozilla::ipc::IPCResult ContentParent::RecvUpdateDropEffect(
const uint32_t& aDragAction, const uint32_t& aDropEffect) {
nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
if (dragSession) {
dragSession->SetDragAction(aDragAction);
RefPtr<DataTransfer> dt = dragSession->GetDataTransfer();
if (dt) {
dt->SetDropEffectInt(aDropEffect);
}
dragSession->UpdateDragEffect();
}
return IPC_OK();
}
PContentPermissionRequestParent*
ContentParent::AllocPContentPermissionRequestParent(
const nsTArray<PermissionRequest>& aRequests, nsIPrincipal* aPrincipal,
nsIPrincipal* aTopLevelPrincipal, const bool& aIsHandlingUserInput,
const bool& aMaybeUnsafePermissionDelegate, const TabId& aTabId) {
RefPtr<BrowserParent> tp;
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (cpm) {
tp =
cpm->GetTopLevelBrowserParentByProcessAndTabId(this->ChildID(), aTabId);
}
if (!tp) {
return nullptr;
}
nsIPrincipal* topPrincipal = aTopLevelPrincipal;
if (!topPrincipal) {
nsCOMPtr<nsIPrincipal> principal = tp->GetContentPrincipal();
topPrincipal = principal;
}
return nsContentPermissionUtils::CreateContentPermissionRequestParent(
aRequests, tp->GetOwnerElement(), aPrincipal, topPrincipal,
aIsHandlingUserInput, aMaybeUnsafePermissionDelegate, aTabId);
}
bool ContentParent::DeallocPContentPermissionRequestParent(
PContentPermissionRequestParent* actor) {
nsContentPermissionUtils::NotifyRemoveContentPermissionRequestParent(actor);
delete actor;
return true;
}
PWebBrowserPersistDocumentParent*
ContentParent::AllocPWebBrowserPersistDocumentParent(
PBrowserParent* aBrowser, const MaybeDiscarded<BrowsingContext>& aContext) {
return new WebBrowserPersistDocumentParent();
}
bool ContentParent::DeallocPWebBrowserPersistDocumentParent(
PWebBrowserPersistDocumentParent* aActor) {
delete aActor;
return true;
}
mozilla::ipc::IPCResult ContentParent::CommonCreateWindow(
PBrowserParent* aThisTab, BrowsingContext& aParent, bool aSetOpener,
const uint32_t& aChromeFlags, const bool& aCalledFromJS,
const bool& aForPrinting, const bool& aForWindowDotPrint,
nsIURI* aURIToLoad, const nsACString& aFeatures,
BrowserParent* aNextRemoteBrowser, const nsAString& aName,
nsresult& aResult, nsCOMPtr<nsIRemoteTab>& aNewRemoteTab,
bool* aWindowIsNew, int32_t& aOpenLocation,
nsIPrincipal* aTriggeringPrincipal, nsIReferrerInfo* aReferrerInfo,
bool aLoadURI, nsIContentSecurityPolicy* aCsp,
const OriginAttributes& aOriginAttributes) {
// The content process should never be in charge of computing whether or
// not a window should be private - the parent will do that.
const uint32_t badFlags = nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW |
nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW |
nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME;
if (!!(aChromeFlags & badFlags)) {
return IPC_FAIL(this, "Forbidden aChromeFlags passed");
}
RefPtr<nsOpenWindowInfo> openInfo = new nsOpenWindowInfo();
openInfo->mForceNoOpener = !aSetOpener;
openInfo->mParent = &aParent;
openInfo->mIsRemote = true;
openInfo->mIsForPrinting = aForPrinting;
openInfo->mIsForWindowDotPrint = aForWindowDotPrint;
openInfo->mNextRemoteBrowser = aNextRemoteBrowser;
openInfo->mOriginAttributes = aOriginAttributes;
MOZ_ASSERT_IF(aForWindowDotPrint, aForPrinting);
RefPtr<BrowserParent> topParent = BrowserParent::GetFrom(aThisTab);
while (topParent && topParent->GetBrowserBridgeParent()) {
topParent = topParent->GetBrowserBridgeParent()->Manager();
}
RefPtr<BrowserHost> thisBrowserHost =
topParent ? topParent->GetBrowserHost() : nullptr;
MOZ_ASSERT_IF(topParent, thisBrowserHost);
RefPtr<BrowsingContext> topBC =
topParent ? topParent->GetBrowsingContext() : nullptr;
MOZ_ASSERT_IF(topParent, topBC);
// The content process should have set its remote and fission flags correctly.
if (topBC) {
if ((!!(aChromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW) !=
topBC->UseRemoteTabs()) ||
(!!(aChromeFlags & nsIWebBrowserChrome::CHROME_FISSION_WINDOW) !=
topBC->UseRemoteSubframes())) {
return IPC_FAIL(this, "Unexpected aChromeFlags passed");
}
if (!aOriginAttributes.EqualsIgnoringFPD(topBC->OriginAttributesRef())) {
return IPC_FAIL(this, "Passed-in OriginAttributes does not match opener");
}
}
nsCOMPtr<nsIContent> frame;
if (topParent) {
frame = topParent->GetOwnerElement();
}
nsCOMPtr<nsPIDOMWindowOuter> outerWin;
if (frame) {
outerWin = frame->OwnerDoc()->GetWindow();
// If our chrome window is in the process of closing, don't try to open a
// new tab in it.
if (outerWin && outerWin->Closed()) {
outerWin = nullptr;
}
}
nsCOMPtr<nsIBrowserDOMWindow> browserDOMWin;
if (topParent) {
browserDOMWin = topParent->GetBrowserDOMWindow();
}
// If we haven't found a chrome window to open in, just use the most recently
// opened one.
if (!outerWin) {
outerWin = nsContentUtils::GetMostRecentNonPBWindow();
if (NS_WARN_IF(!outerWin)) {
aResult = NS_ERROR_FAILURE;
return IPC_OK();
}
nsCOMPtr<nsIDOMChromeWindow> rootChromeWin = do_QueryInterface(outerWin);
if (rootChromeWin) {
rootChromeWin->GetBrowserDOMWindow(getter_AddRefs(browserDOMWin));
}
}
aOpenLocation = nsWindowWatcher::GetWindowOpenLocation(
outerWin, aChromeFlags, aCalledFromJS, aForPrinting);
MOZ_ASSERT(aOpenLocation == nsIBrowserDOMWindow::OPEN_NEWTAB ||
aOpenLocation == nsIBrowserDOMWindow::OPEN_NEWWINDOW ||
aOpenLocation == nsIBrowserDOMWindow::OPEN_PRINT_BROWSER);
if (NS_WARN_IF(!browserDOMWin)) {
// Opening in the same window or headless requires an nsIBrowserDOMWindow.
aOpenLocation = nsIBrowserDOMWindow::OPEN_NEWWINDOW;
}
if (aOpenLocation == nsIBrowserDOMWindow::OPEN_NEWTAB ||
aOpenLocation == nsIBrowserDOMWindow::OPEN_PRINT_BROWSER) {
RefPtr<Element> openerElement = do_QueryObject(frame);
nsCOMPtr<nsIOpenURIInFrameParams> params =
new nsOpenURIInFrameParams(openInfo, openerElement);
params->SetReferrerInfo(aReferrerInfo);
MOZ_ASSERT(aTriggeringPrincipal, "need a valid triggeringPrincipal");
params->SetTriggeringPrincipal(aTriggeringPrincipal);
params->SetCsp(aCsp);
RefPtr<Element> el;
if (aLoadURI) {
aResult = browserDOMWin->OpenURIInFrame(aURIToLoad, params, aOpenLocation,
nsIBrowserDOMWindow::OPEN_NEW,
aName, getter_AddRefs(el));
} else {
aResult = browserDOMWin->CreateContentWindowInFrame(
aURIToLoad, params, aOpenLocation, nsIBrowserDOMWindow::OPEN_NEW,
aName, getter_AddRefs(el));
}
RefPtr<nsFrameLoaderOwner> frameLoaderOwner = do_QueryObject(el);
if (NS_SUCCEEDED(aResult) && frameLoaderOwner) {
RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
if (frameLoader) {
aNewRemoteTab = frameLoader->GetRemoteTab();
// At this point, it's possible the inserted frameloader hasn't gone
// through layout yet. To ensure that the dimensions that we send down
// when telling the frameloader to display will be correct (instead of
// falling back to a 10x10 default), we force layout if necessary to get
// the most up-to-date dimensions. See bug 1358712 for details.
frameLoader->ForceLayoutIfNecessary();
}
} else if (NS_SUCCEEDED(aResult) && !frameLoaderOwner) {
// Fall through to the normal window opening code path when there is no
// window which we can open a new tab in.
aOpenLocation = nsIBrowserDOMWindow::OPEN_NEWWINDOW;
} else {
*aWindowIsNew = false;
}
// If we didn't retarget our window open into a new window, we should return
// now.
if (aOpenLocation != nsIBrowserDOMWindow::OPEN_NEWWINDOW) {
return IPC_OK();
}
}
nsCOMPtr<nsPIWindowWatcher> pwwatch =
do_GetService(NS_WINDOWWATCHER_CONTRACTID, &aResult);
if (NS_WARN_IF(NS_FAILED(aResult))) {
return IPC_OK();
}
WindowFeatures features;
features.Tokenize(aFeatures);
aResult = pwwatch->OpenWindowWithRemoteTab(
thisBrowserHost, features, aCalledFromJS, aParent.FullZoom(), openInfo,
getter_AddRefs(aNewRemoteTab));
if (NS_WARN_IF(NS_FAILED(aResult))) {
return IPC_OK();
}
MOZ_ASSERT(aNewRemoteTab);
RefPtr<BrowserHost> newBrowserHost = BrowserHost::GetFrom(aNewRemoteTab);
RefPtr<BrowserParent> newBrowserParent = newBrowserHost->GetActor();
// At this point, it's possible the inserted frameloader hasn't gone through
// layout yet. To ensure that the dimensions that we send down when telling
// the frameloader to display will be correct (instead of falling back to a
// 10x10 default), we force layout if necessary to get the most up-to-date
// dimensions. See bug 1358712 for details.
nsCOMPtr<Element> frameElement = newBrowserHost->GetOwnerElement();
MOZ_ASSERT(frameElement);
if (nsWindowWatcher::HaveSpecifiedSize(features)) {
// We want to flush the layout anyway because of the resize to the specified
// size. (Bug 1793605).
RefPtr<Document> chromeDoc = frameElement->OwnerDoc();
MOZ_ASSERT(chromeDoc);
chromeDoc->FlushPendingNotifications(FlushType::Layout);
} else {
RefPtr<nsFrameLoaderOwner> frameLoaderOwner = do_QueryObject(frameElement);
MOZ_ASSERT(frameLoaderOwner);
RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
MOZ_ASSERT(frameLoader);
frameLoader->ForceLayoutIfNecessary();
}
// If we were passed a name for the window which would override the default,
// we should send it down to the new tab.
if (nsContentUtils::IsOverridingWindowName(aName)) {
MOZ_ALWAYS_SUCCEEDS(newBrowserHost->GetBrowsingContext()->SetName(aName));
}
MOZ_ASSERT(newBrowserHost->GetBrowsingContext()->OriginAttributesRef() ==
aOriginAttributes);
if (aURIToLoad && aLoadURI) {
nsCOMPtr<mozIDOMWindowProxy> openerWindow;
if (aSetOpener && topParent) {
openerWindow = topParent->GetParentWindowOuter();
}
nsCOMPtr<nsIBrowserDOMWindow> newBrowserDOMWin =
newBrowserParent->GetBrowserDOMWindow();
if (NS_WARN_IF(!newBrowserDOMWin)) {
aResult = NS_ERROR_ABORT;
return IPC_OK();
}
RefPtr<BrowsingContext> bc;
aResult = newBrowserDOMWin->OpenURI(
aURIToLoad, openInfo, nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
nsIBrowserDOMWindow::OPEN_NEW, aTriggeringPrincipal, aCsp,
getter_AddRefs(bc));
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvCreateWindow(
PBrowserParent* aThisTab, const MaybeDiscarded<BrowsingContext>& aParent,
PBrowserParent* aNewTab, const uint32_t& aChromeFlags,
const bool& aCalledFromJS, const bool& aForPrinting,
const bool& aForPrintPreview, nsIURI* aURIToLoad,
const nsACString& aFeatures, nsIPrincipal* aTriggeringPrincipal,
nsIContentSecurityPolicy* aCsp, nsIReferrerInfo* aReferrerInfo,
const OriginAttributes& aOriginAttributes,
CreateWindowResolver&& aResolve) {
if (!aTriggeringPrincipal) {
return IPC_FAIL(this, "No principal");
}
if (!ValidatePrincipal(aTriggeringPrincipal)) {
LogAndAssertFailedPrincipalValidationInfo(aTriggeringPrincipal, __func__);
}
nsresult rv = NS_OK;
CreatedWindowInfo cwi;
// We always expect to open a new window here. If we don't, it's an error.
cwi.windowOpened() = true;
cwi.maxTouchPoints() = 0;
cwi.hasSiblings() = false;
// Make sure to resolve the resolver when this function exits, even if we
// failed to generate a valid response.
auto resolveOnExit = MakeScopeExit([&] {
// Copy over the nsresult, and then resolve.
cwi.rv() = rv;
aResolve(cwi);
});
RefPtr<BrowserParent> thisTab = BrowserParent::GetFrom(aThisTab);
RefPtr<BrowserParent> newTab = BrowserParent::GetFrom(aNewTab);
MOZ_ASSERT(newTab);
auto destroyNewTabOnError = MakeScopeExit([&] {
// We always expect to open a new window here. If we don't, it's an error.
if (!cwi.windowOpened() || NS_FAILED(rv)) {
if (newTab) {
newTab->Destroy();
}
}
});
// Don't continue to try to create a new window if we've been fully discarded.
RefPtr<BrowsingContext> parent = aParent.GetMaybeDiscarded();
if (NS_WARN_IF(!parent)) {
rv = NS_ERROR_FAILURE;
return IPC_OK();
}
// Validate that our new BrowsingContext looks as we would expect it.
RefPtr<BrowsingContext> newBC = newTab->GetBrowsingContext();
if (!newBC) {
return IPC_FAIL(this, "Missing BrowsingContext for new tab");
}
uint64_t newBCOpenerId = newBC->GetOpenerId();
if (newBCOpenerId != 0 && parent->Id() != newBCOpenerId) {
return IPC_FAIL(this, "Invalid opener BrowsingContext for new tab");
}
if (newBC->GetParent() != nullptr) {
return IPC_FAIL(this,
"Unexpected non-toplevel BrowsingContext for new tab");
}
if (!!(aChromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW) !=
newBC->UseRemoteTabs() ||
!!(aChromeFlags & nsIWebBrowserChrome::CHROME_FISSION_WINDOW) !=
newBC->UseRemoteSubframes()) {
return IPC_FAIL(this, "Unexpected aChromeFlags passed");
}
if (!aOriginAttributes.EqualsIgnoringFPD(newBC->OriginAttributesRef())) {
return IPC_FAIL(this, "Opened tab has mismatched OriginAttributes");
}
if (thisTab && BrowserParent::GetFrom(thisTab)->GetBrowsingContext()) {
BrowsingContext* thisTabBC = thisTab->GetBrowsingContext();
if (thisTabBC->UseRemoteTabs() != newBC->UseRemoteTabs() ||
thisTabBC->UseRemoteSubframes() != newBC->UseRemoteSubframes() ||
thisTabBC->UsePrivateBrowsing() != newBC->UsePrivateBrowsing()) {
return IPC_FAIL(this, "New BrowsingContext has mismatched LoadContext");
}
}
BrowserParent::AutoUseNewTab aunt(newTab);
nsCOMPtr<nsIRemoteTab> newRemoteTab;
int32_t openLocation = nsIBrowserDOMWindow::OPEN_NEWWINDOW;
mozilla::ipc::IPCResult ipcResult = CommonCreateWindow(
aThisTab, *parent, newBCOpenerId != 0, aChromeFlags, aCalledFromJS,
aForPrinting, aForPrintPreview, aURIToLoad, aFeatures, newTab,
VoidString(), rv, newRemoteTab, &cwi.windowOpened(), openLocation,
aTriggeringPrincipal, aReferrerInfo, /* aLoadUri = */ false, aCsp,
aOriginAttributes);
if (!ipcResult) {
return ipcResult;
}
if (NS_WARN_IF(NS_FAILED(rv)) || !newRemoteTab) {
return IPC_OK();
}
MOZ_ASSERT(BrowserHost::GetFrom(newRemoteTab.get()) ==
newTab->GetBrowserHost());
newTab->SwapFrameScriptsFrom(cwi.frameScripts());
newTab->MaybeShowFrame();
nsCOMPtr<nsIWidget> widget = newTab->GetWidget();
if (widget) {
cwi.dimensions() = newTab->GetDimensionInfo();
}
cwi.maxTouchPoints() = newTab->GetMaxTouchPoints();
cwi.hasSiblings() = (openLocation == nsIBrowserDOMWindow::OPEN_NEWTAB);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvCreateWindowInDifferentProcess(
PBrowserParent* aThisTab, const MaybeDiscarded<BrowsingContext>& aParent,
const uint32_t& aChromeFlags, const bool& aCalledFromJS, nsIURI* aURIToLoad,
const nsACString& aFeatures, const nsAString& aName,
nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp,
nsIReferrerInfo* aReferrerInfo, const OriginAttributes& aOriginAttributes) {
MOZ_DIAGNOSTIC_ASSERT(!nsContentUtils::IsSpecialName(aName));
// Don't continue to try to create a new window if we've been fully discarded.
RefPtr<BrowsingContext> parent = aParent.GetMaybeDiscarded();
if (NS_WARN_IF(!parent)) {
return IPC_OK();
}
nsCOMPtr<nsIRemoteTab> newRemoteTab;
bool windowIsNew;
int32_t openLocation = nsIBrowserDOMWindow::OPEN_NEWWINDOW;
// If we have enough data, check the schemes of the loader and loadee
// to make sure they make sense.
if (aURIToLoad && aURIToLoad->SchemeIs("file") &&
GetRemoteType() != FILE_REMOTE_TYPE &&
Preferences::GetBool("browser.tabs.remote.enforceRemoteTypeRestrictions",
false)) {
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
# ifdef DEBUG
nsAutoCString uriToLoadStr;
nsAutoCString triggeringUriStr;
aURIToLoad->GetAsciiSpec(uriToLoadStr);
aTriggeringPrincipal->GetAsciiSpec(triggeringUriStr);
NS_WARNING(nsPrintfCString(
"RecvCreateWindowInDifferentProcess blocked loading file "
"scheme from non-file remotetype: %s tried to load %s",
triggeringUriStr.get(), uriToLoadStr.get())
.get());
# endif
MOZ_CRASH(
"RecvCreateWindowInDifferentProcess blocked loading improper scheme");
#endif
return IPC_OK();
}
nsresult rv;
mozilla::ipc::IPCResult ipcResult = CommonCreateWindow(
aThisTab, *parent, /* aSetOpener = */ false, aChromeFlags, aCalledFromJS,
/* aForPrinting = */ false,
/* aForPrintPreview = */ false, aURIToLoad, aFeatures,
/* aNextRemoteBrowser = */ nullptr, aName, rv, newRemoteTab, &windowIsNew,
openLocation, aTriggeringPrincipal, aReferrerInfo,
/* aLoadUri = */ true, aCsp, aOriginAttributes);
if (!ipcResult) {
return ipcResult;
}
if (NS_FAILED(rv)) {
NS_WARNING("Call to CommonCreateWindow failed.");
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvShutdownProfile(
const nsACString& aProfile) {
profiler_received_exit_profile(aProfile);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvShutdownPerfStats(
const nsACString& aPerfStats) {
PerfStats::StorePerfStats(this, aPerfStats);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvGetGraphicsDeviceInitData(
ContentDeviceData* aOut) {
gfxPlatform::GetPlatform()->BuildContentDeviceData(aOut);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvGetOutputColorProfileData(
nsTArray<uint8_t>* aOutputColorProfileData) {
(*aOutputColorProfileData) =
gfxPlatform::GetPlatform()->GetPlatformCMSOutputProfileData();
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvGetFontListShmBlock(
const uint32_t& aGeneration, const uint32_t& aIndex,
base::SharedMemoryHandle* aOut) {
auto* fontList = gfxPlatformFontList::PlatformFontList();
MOZ_RELEASE_ASSERT(fontList, "gfxPlatformFontList not initialized?");
fontList->ShareFontListShmBlockToProcess(aGeneration, aIndex, Pid(), aOut);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvInitializeFamily(
const uint32_t& aGeneration, const uint32_t& aFamilyIndex,
const bool& aLoadCmaps) {
auto* fontList = gfxPlatformFontList::PlatformFontList();
MOZ_RELEASE_ASSERT(fontList, "gfxPlatformFontList not initialized?");
fontList->InitializeFamily(aGeneration, aFamilyIndex, aLoadCmaps);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSetCharacterMap(
const uint32_t& aGeneration, const mozilla::fontlist::Pointer& aFacePtr,
const gfxSparseBitSet& aMap) {
auto* fontList = gfxPlatformFontList::PlatformFontList();
MOZ_RELEASE_ASSERT(fontList, "gfxPlatformFontList not initialized?");
fontList->SetCharacterMap(aGeneration, aFacePtr, aMap);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvInitOtherFamilyNames(
const uint32_t& aGeneration, const bool& aDefer, bool* aLoaded) {
auto* fontList = gfxPlatformFontList::PlatformFontList();
MOZ_RELEASE_ASSERT(fontList, "gfxPlatformFontList not initialized?");
*aLoaded = fontList->InitOtherFamilyNames(aGeneration, aDefer);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSetupFamilyCharMap(
const uint32_t& aGeneration, const mozilla::fontlist::Pointer& aFamilyPtr) {
auto* fontList = gfxPlatformFontList::PlatformFontList();
MOZ_RELEASE_ASSERT(fontList, "gfxPlatformFontList not initialized?");
fontList->SetupFamilyCharMap(aGeneration, aFamilyPtr);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvStartCmapLoading(
const uint32_t& aGeneration, const uint32_t& aStartIndex) {
auto* fontList = gfxPlatformFontList::PlatformFontList();
MOZ_RELEASE_ASSERT(fontList, "gfxPlatformFontList not initialized?");
fontList->StartCmapLoading(aGeneration, aStartIndex);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvGetHyphDict(
nsIURI* aURI, base::SharedMemoryHandle* aOutHandle, uint32_t* aOutSize) {
if (!aURI) {
return IPC_FAIL(this, "aURI must not be null.");
}
nsHyphenationManager::Instance()->ShareHyphDictToProcess(
aURI, Pid(), aOutHandle, aOutSize);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvGraphicsError(
const nsACString& aError) {
if (gfx::LogForwarder* lf = gfx::Factory::GetLogForwarder()) {
std::stringstream message;
message << "CP+" << aError;
lf->UpdateStringsVector(message.str());
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvBeginDriverCrashGuard(
const uint32_t& aGuardType, bool* aOutCrashed) {
// Only one driver crash guard should be active at a time, per-process.
MOZ_ASSERT(!mDriverCrashGuard);
UniquePtr<gfx::DriverCrashGuard> guard;
switch (gfx::CrashGuardType(aGuardType)) {
case gfx::CrashGuardType::D3D11Layers:
guard = MakeUnique<gfx::D3D11LayersCrashGuard>(this);
break;
case gfx::CrashGuardType::GLContext:
guard = MakeUnique<gfx::GLContextCrashGuard>(this);
break;
case gfx::CrashGuardType::WMFVPXVideo:
guard = MakeUnique<gfx::WMFVPXVideoCrashGuard>(this);
break;
default:
return IPC_FAIL(this, "unknown crash guard type");
}
if (guard->Crashed()) {
*aOutCrashed = true;
return IPC_OK();
}
*aOutCrashed = false;
mDriverCrashGuard = std::move(guard);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvEndDriverCrashGuard(
const uint32_t& aGuardType) {
mDriverCrashGuard = nullptr;
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvNotifyBenchmarkResult(
const nsAString& aCodecName, const uint32_t& aDecodeFPS)
{
if (aCodecName.EqualsLiteral("VP9")) {
Preferences::SetUint(VP9Benchmark::sBenchmarkFpsPref, aDecodeFPS);
Preferences::SetUint(VP9Benchmark::sBenchmarkFpsVersionCheck,
VP9Benchmark::sBenchmarkVersionID);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvNotifyPushObservers(
const nsACString& aScope, nsIPrincipal* aPrincipal,
const nsAString& aMessageId) {
if (!aPrincipal) {
return IPC_FAIL(this, "No principal");
}
if (!ValidatePrincipal(aPrincipal)) {
LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__);
}
PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Nothing());
Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers()));
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvNotifyPushObserversWithData(
const nsACString& aScope, nsIPrincipal* aPrincipal,
const nsAString& aMessageId, nsTArray<uint8_t>&& aData) {
if (!aPrincipal) {
return IPC_FAIL(this, "No principal");
}
if (!ValidatePrincipal(aPrincipal)) {
LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__);
}
PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId,
Some(std::move(aData)));
Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers()));
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentParent::RecvNotifyPushSubscriptionChangeObservers(
const nsACString& aScope, nsIPrincipal* aPrincipal) {
if (!aPrincipal) {
return IPC_FAIL(this, "No principal");
}
if (!ValidatePrincipal(aPrincipal)) {
LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__);
}
PushSubscriptionChangeDispatcher dispatcher(aScope, aPrincipal);
Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers()));
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvPushError(const nsACString& aScope,
nsIPrincipal* aPrincipal,
const nsAString& aMessage,
const uint32_t& aFlags) {
if (!aPrincipal) {
return IPC_FAIL(this, "No principal");
}
if (!ValidatePrincipal(aPrincipal)) {
LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__);
}
PushErrorDispatcher dispatcher(aScope, aPrincipal, aMessage, aFlags);
Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers()));
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentParent::RecvNotifyPushSubscriptionModifiedObservers(
const nsACString& aScope, nsIPrincipal* aPrincipal) {
if (!aPrincipal) {
return IPC_FAIL(this, "No principal");
}
if (!ValidatePrincipal(aPrincipal)) {
LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__);
}
PushSubscriptionModifiedDispatcher dispatcher(aScope, aPrincipal);
Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObservers()));
return IPC_OK();
}
/* static */
void ContentParent::BroadcastBlobURLRegistration(
const nsACString& aURI, BlobImpl* aBlobImpl, nsIPrincipal* aPrincipal,
const Maybe<nsID>& aAgentClusterId, ContentParent* aIgnoreThisCP) {
uint64_t originHash = ComputeLoadedOriginHash(aPrincipal);
bool toBeSent =
BlobURLProtocolHandler::IsBlobURLBroadcastPrincipal(aPrincipal);
nsCString uri(aURI);
for (auto* cp : AllProcesses(eLive)) {
if (cp != aIgnoreThisCP) {
if (!toBeSent && !cp->mLoadedOriginHashes.Contains(originHash)) {
continue;
}
nsresult rv = cp->TransmitPermissionsForPrincipal(aPrincipal);
if (NS_WARN_IF(NS_FAILED(rv))) {
break;
}
IPCBlob ipcBlob;
rv = IPCBlobUtils::Serialize(aBlobImpl, ipcBlob);
if (NS_WARN_IF(NS_FAILED(rv))) {
break;
}
Unused << cp->SendBlobURLRegistration(uri, ipcBlob, aPrincipal,
aAgentClusterId);
}
}
}
/* static */
void ContentParent::BroadcastBlobURLUnregistration(
const nsACString& aURI, nsIPrincipal* aPrincipal,
ContentParent* aIgnoreThisCP) {
uint64_t originHash = ComputeLoadedOriginHash(aPrincipal);
bool toBeSent =
BlobURLProtocolHandler::IsBlobURLBroadcastPrincipal(aPrincipal);
nsCString uri(aURI);
for (auto* cp : AllProcesses(eLive)) {
if (cp != aIgnoreThisCP &&
(toBeSent || cp->mLoadedOriginHashes.Contains(originHash))) {
Unused << cp->SendBlobURLUnregistration(uri);
}
}
}
mozilla::ipc::IPCResult ContentParent::RecvStoreAndBroadcastBlobURLRegistration(
const nsACString& aURI, const IPCBlob& aBlob, nsIPrincipal* aPrincipal,
const Maybe<nsID>& aAgentClusterId) {
if (!aPrincipal) {
return IPC_FAIL(this, "No principal");
}
if (!ValidatePrincipal(aPrincipal, {ValidatePrincipalOptions::AllowSystem})) {
LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__);
}
RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(aBlob);
if (NS_WARN_IF(!blobImpl)) {
return IPC_FAIL(this, "Blob deserialization failed.");
}
BlobURLProtocolHandler::AddDataEntry(aURI, aPrincipal, aAgentClusterId,
blobImpl);
BroadcastBlobURLRegistration(aURI, blobImpl, aPrincipal, aAgentClusterId,
this);
// We want to store this blobURL, so we can unregister it if the child
// crashes.
mBlobURLs.AppendElement(aURI);
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentParent::RecvUnstoreAndBroadcastBlobURLUnregistration(
const nsACString& aURI, nsIPrincipal* aPrincipal) {
if (!aPrincipal) {
return IPC_FAIL(this, "No principal");
}
if (!ValidatePrincipal(aPrincipal, {ValidatePrincipalOptions::AllowSystem})) {
LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__);
}
BlobURLProtocolHandler::RemoveDataEntry(aURI, false /* Don't broadcast */);
BroadcastBlobURLUnregistration(aURI, aPrincipal, this);
mBlobURLs.RemoveElement(aURI);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvGetA11yContentId(
uint32_t* aContentId) {
#if defined(XP_WIN) && defined(ACCESSIBILITY)
*aContentId = a11y::MsaaAccessible::GetContentProcessIdFor(ChildID());
MOZ_ASSERT(*aContentId);
return IPC_OK();
#else
return IPC_FAIL_NO_REASON(this);
#endif
}
mozilla::ipc::IPCResult ContentParent::RecvA11yHandlerControl(
const uint32_t& aPid, const IHandlerControlHolder& aHandlerControl) {
#if defined(XP_WIN) && defined(ACCESSIBILITY)
MOZ_ASSERT(!aHandlerControl.IsNull());
RefPtr<IHandlerControl> proxy(aHandlerControl.Get());
a11y::AccessibleWrap::SetHandlerControl(aPid, std::move(proxy));
return IPC_OK();
#else
return IPC_FAIL_NO_REASON(this);
#endif
}
bool ContentParent::HandleWindowsMessages(const Message& aMsg) const {
MOZ_ASSERT(aMsg.is_sync());
#ifdef ACCESSIBILITY
// a11y messages can be triggered by windows messages, which means if we
// allow handling windows messages while we wait for the response to a sync
// a11y message we can reenter the ipc message sending code.
if (a11y::PDocAccessible::PDocAccessibleStart < aMsg.type() &&
a11y::PDocAccessible::PDocAccessibleEnd > aMsg.type()) {
return false;
}
#endif
return true;
}
mozilla::ipc::IPCResult ContentParent::RecvGetFilesRequest(
const nsID& aUUID, const nsAString& aDirectoryPath,
const bool& aRecursiveFlag) {
MOZ_ASSERT(!mGetFilesPendingRequests.GetWeak(aUUID));
if (!mozilla::Preferences::GetBool("dom.filesystem.pathcheck.disabled",
false)) {
RefPtr<FileSystemSecurity> fss = FileSystemSecurity::Get();
if (!fss) {
return IPC_FAIL(this, "Failed to get FileSystemSecurity.");
}
if (!fss->ContentProcessHasAccessTo(ChildID(), aDirectoryPath)) {
return IPC_FAIL(this, "ContentProcessHasAccessTo failed.");
}
}
ErrorResult rv;
RefPtr<GetFilesHelper> helper = GetFilesHelperParent::Create(
aUUID, aDirectoryPath, aRecursiveFlag, this, rv);
if (NS_WARN_IF(rv.Failed())) {
if (!SendGetFilesResponse(aUUID,
GetFilesResponseFailure(rv.StealNSResult()))) {
return IPC_FAIL(this, "SendGetFilesResponse failed.");
}
return IPC_OK();
}
mGetFilesPendingRequests.InsertOrUpdate(aUUID, std::move(helper));
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvDeleteGetFilesRequest(
const nsID& aUUID) {
mGetFilesPendingRequests.Remove(aUUID);
return IPC_OK();
}
void ContentParent::SendGetFilesResponseAndForget(
const nsID& aUUID, const GetFilesResponseResult& aResult) {
if (mGetFilesPendingRequests.Remove(aUUID)) {
Unused << SendGetFilesResponse(aUUID, aResult);
}
}
void ContentParent::PaintTabWhileInterruptingJS(
BrowserParent* aBrowserParent, const layers::LayersObserverEpoch& aEpoch) {
if (!mHangMonitorActor) {
return;
}
ProcessHangMonitor::PaintWhileInterruptingJS(mHangMonitorActor,
aBrowserParent, aEpoch);
}
void ContentParent::UnloadLayersWhileInterruptingJS(
BrowserParent* aBrowserParent, const layers::LayersObserverEpoch& aEpoch) {
if (!mHangMonitorActor) {
return;
}
ProcessHangMonitor::UnloadLayersWhileInterruptingJS(mHangMonitorActor,
aBrowserParent, aEpoch);
}
void ContentParent::CancelContentJSExecutionIfRunning(
BrowserParent* aBrowserParent, nsIRemoteTab::NavigationType aNavigationType,
const CancelContentJSOptions& aCancelContentJSOptions) {
if (!mHangMonitorActor) {
return;
}
ProcessHangMonitor::CancelContentJSExecutionIfRunning(
mHangMonitorActor, aBrowserParent, aNavigationType,
aCancelContentJSOptions);
}
void ContentParent::UpdateCookieStatus(nsIChannel* aChannel) {
PNeckoParent* neckoParent = LoneManagedOrNullAsserts(ManagedPNeckoParent());
PCookieServiceParent* csParent =
LoneManagedOrNullAsserts(neckoParent->ManagedPCookieServiceParent());
if (csParent) {
auto* cs = static_cast<CookieServiceParent*>(csParent);
cs->TrackCookieLoad(aChannel);
}
}
nsresult ContentParent::AboutToLoadHttpFtpDocumentForChild(
nsIChannel* aChannel, bool* aShouldWaitForPermissionCookieUpdate) {
MOZ_ASSERT(aChannel);
if (aShouldWaitForPermissionCookieUpdate) {
*aShouldWaitForPermissionCookieUpdate = false;
}
nsresult rv;
bool isDocument = aChannel->IsDocument();
if (!isDocument) {
// We may be looking at a nsIHttpChannel which has isMainDocumentChannel set
// (e.g. the internal http channel for a view-source: load.).
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
if (httpChannel) {
rv = httpChannel->GetIsMainDocumentChannel(&isDocument);
NS_ENSURE_SUCCESS(rv, rv);
}
}
if (!isDocument) {
return NS_OK;
}
// Get the principal for the channel result, so that we can get the permission
// key for the document which will be created from this response.
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
if (NS_WARN_IF(!ssm)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsIPrincipal> partitionedPrincipal;
rv = ssm->GetChannelResultPrincipals(aChannel, getter_AddRefs(principal),
getter_AddRefs(partitionedPrincipal));
NS_ENSURE_SUCCESS(rv, rv);
// Let the caller know we're going to send main thread IPC for updating
// permisssions/cookies.
if (aShouldWaitForPermissionCookieUpdate) {
*aShouldWaitForPermissionCookieUpdate = true;
}
TransmitBlobURLsForPrincipal(principal);
// Tranmit permissions for both regular and partitioned principal so that the
// content process can get permissions for the partitioned principal. For
// example, the desk-notification permission for a partitioned service worker.
rv = TransmitPermissionsForPrincipal(principal);
NS_ENSURE_SUCCESS(rv, rv);
rv = TransmitPermissionsForPrincipal(partitionedPrincipal);
NS_ENSURE_SUCCESS(rv, rv);
nsLoadFlags newLoadFlags;
aChannel->GetLoadFlags(&newLoadFlags);
if (newLoadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE) {
UpdateCookieStatus(aChannel);
}
RefPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
RefPtr<BrowsingContext> browsingContext;
rv = loadInfo->GetTargetBrowsingContext(getter_AddRefs(browsingContext));
NS_ENSURE_SUCCESS(rv, rv);
if (!NextGenLocalStorageEnabled()) {
return NS_OK;
}
if (principal->GetIsContentPrincipal()) {
nsCOMPtr<nsILocalStorageManager> lsm =
do_GetService("@mozilla.org/dom/localStorage-manager;1");
if (NS_WARN_IF(!lsm)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIPrincipal> storagePrincipal;
rv = ssm->GetChannelResultStoragePrincipal(
aChannel, getter_AddRefs(storagePrincipal));
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<Promise> dummy;
rv = lsm->Preload(storagePrincipal, nullptr, getter_AddRefs(dummy));
if (NS_FAILED(rv)) {
NS_WARNING("Failed to preload local storage!");
}
}
return NS_OK;
}
nsresult ContentParent::TransmitPermissionsForPrincipal(
nsIPrincipal* aPrincipal) {
// Create the key, and send it down to the content process.
nsTArray<std::pair<nsCString, nsCString>> pairs =
PermissionManager::GetAllKeysForPrincipal(aPrincipal);
MOZ_ASSERT(pairs.Length() >= 1);
for (auto& pair : pairs) {
EnsurePermissionsByKey(pair.first, pair.second);
}
return NS_OK;
}
void ContentParent::TransmitBlobURLsForPrincipal(nsIPrincipal* aPrincipal) {
// If we're already broadcasting BlobURLs with this principal, we don't need
// to send them here.
if (BlobURLProtocolHandler::IsBlobURLBroadcastPrincipal(aPrincipal)) {
return;
}
// We shouldn't have any Blob URLs with expanded principals, so transmit URLs
// for each principal in the AllowList instead.
if (nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal)) {
for (const auto& prin : ep->AllowList()) {
TransmitBlobURLsForPrincipal(prin);
}
return;
}
uint64_t originHash = ComputeLoadedOriginHash(aPrincipal);
if (!mLoadedOriginHashes.Contains(originHash)) {
mLoadedOriginHashes.AppendElement(originHash);
nsTArray<BlobURLRegistrationData> registrations;
BlobURLProtocolHandler::ForEachBlobURL(
[&](BlobImpl* aBlobImpl, nsIPrincipal* aBlobPrincipal,
const Maybe<nsID>& aAgentClusterId, const nsACString& aURI,
bool aRevoked) {
// This check uses `ComputeLoadedOriginHash` to compare, rather than
// doing the more accurate `Equals` check, as it needs to match the
// behaviour of the logic to broadcast new registrations.
if (originHash != ComputeLoadedOriginHash(aBlobPrincipal)) {
return true;
}
IPCBlob ipcBlob;
nsresult rv = IPCBlobUtils::Serialize(aBlobImpl, ipcBlob);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
registrations.AppendElement(
BlobURLRegistrationData(nsCString(aURI), ipcBlob, aBlobPrincipal,
aAgentClusterId, aRevoked));
rv = TransmitPermissionsForPrincipal(aBlobPrincipal);
Unused << NS_WARN_IF(NS_FAILED(rv));
return true;
});
if (!registrations.IsEmpty()) {
Unused << SendInitBlobURLs(registrations);
}
}
}
void ContentParent::TransmitBlobDataIfBlobURL(nsIURI* aURI) {
MOZ_ASSERT(aURI);
nsCOMPtr<nsIPrincipal> principal;
if (BlobURLProtocolHandler::GetBlobURLPrincipal(aURI,
getter_AddRefs(principal))) {
TransmitBlobURLsForPrincipal(principal);
}
}
void ContentParent::EnsurePermissionsByKey(const nsACString& aKey,
const nsACString& aOrigin) {
// NOTE: Make sure to initialize the permission manager before updating the
// mActivePermissionKeys list. If the permission manager is being initialized
// by this call to GetPermissionManager, and we've added the key to
// mActivePermissionKeys, then the permission manager will send down a
// SendAddPermission before receiving the SendSetPermissionsWithKey message.
RefPtr<PermissionManager> permManager = PermissionManager::GetInstance();
if (!permManager) {
return;
}
if (!mActivePermissionKeys.EnsureInserted(aKey)) {
return;
}
nsTArray<IPC::Permission> perms;
if (permManager->GetPermissionsFromOriginOrKey(aOrigin, aKey, perms)) {
Unused << SendSetPermissionsWithKey(aKey, perms);
}
}
bool ContentParent::NeedsPermissionsUpdate(
const nsACString& aPermissionKey) const {
return mActivePermissionKeys.Contains(aPermissionKey);
}
mozilla::ipc::IPCResult ContentParent::RecvAccumulateChildHistograms(
nsTArray<HistogramAccumulation>&& aAccumulations) {
TelemetryIPC::AccumulateChildHistograms(GetTelemetryProcessID(mRemoteType),
aAccumulations);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvAccumulateChildKeyedHistograms(
nsTArray<KeyedHistogramAccumulation>&& aAccumulations) {
TelemetryIPC::AccumulateChildKeyedHistograms(
GetTelemetryProcessID(mRemoteType), aAccumulations);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvUpdateChildScalars(
nsTArray<ScalarAction>&& aScalarActions) {
TelemetryIPC::UpdateChildScalars(GetTelemetryProcessID(mRemoteType),
aScalarActions);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvUpdateChildKeyedScalars(
nsTArray<KeyedScalarAction>&& aScalarActions) {
TelemetryIPC::UpdateChildKeyedScalars(GetTelemetryProcessID(mRemoteType),
aScalarActions);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvRecordChildEvents(
nsTArray<mozilla::Telemetry::ChildEventData>&& aEvents) {
TelemetryIPC::RecordChildEvents(GetTelemetryProcessID(mRemoteType), aEvents);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvRecordDiscardedData(
const mozilla::Telemetry::DiscardedData& aDiscardedData) {
TelemetryIPC::RecordDiscardedData(GetTelemetryProcessID(mRemoteType),
aDiscardedData);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvRecordPageLoadEvent(
const mozilla::glean::perf::PageLoadExtra& aPageLoadEventExtra) {
mozilla::glean::perf::page_load.Record(mozilla::Some(aPageLoadEventExtra));
// Send the PageLoadPing after every 30 page loads, or on startup.
if (++sPageLoadEventCounter >= 30) {
NS_SUCCEEDED(NS_DispatchToMainThreadQueue(
NS_NewRunnableFunction(
"PageLoadPingIdleTask",
[] { mozilla::glean_pings::Pageload.Submit("threshold"_ns); }),
EventQueuePriority::Idle));
sPageLoadEventCounter = 0;
}
return IPC_OK();
}
//////////////////////////////////////////////////////////////////
// PURLClassifierParent
PURLClassifierParent* ContentParent::AllocPURLClassifierParent(
nsIPrincipal* aPrincipal, bool* aSuccess) {
MOZ_ASSERT(NS_IsMainThread());
*aSuccess = true;
RefPtr<URLClassifierParent> actor = new URLClassifierParent();
return actor.forget().take();
}
mozilla::ipc::IPCResult ContentParent::RecvPURLClassifierConstructor(
PURLClassifierParent* aActor, nsIPrincipal* aPrincipal, bool* aSuccess) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aActor);
*aSuccess = false;
auto* actor = static_cast<URLClassifierParent*>(aActor);
nsCOMPtr<nsIPrincipal> principal(aPrincipal);
if (!principal) {
actor->ClassificationFailed();
return IPC_OK();
}
if (!ValidatePrincipal(aPrincipal)) {
LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__);
}
return actor->StartClassify(principal, aSuccess);
}
bool ContentParent::DeallocPURLClassifierParent(PURLClassifierParent* aActor) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aActor);
RefPtr<URLClassifierParent> actor =
dont_AddRef(static_cast<URLClassifierParent*>(aActor));
return true;
}
//////////////////////////////////////////////////////////////////
// PURLClassifierLocalParent
PURLClassifierLocalParent* ContentParent::AllocPURLClassifierLocalParent(
nsIURI* aURI, const nsTArray<IPCURLClassifierFeature>& aFeatures) {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<URLClassifierLocalParent> actor = new URLClassifierLocalParent();
return actor.forget().take();
}
mozilla::ipc::IPCResult ContentParent::RecvPURLClassifierLocalConstructor(
PURLClassifierLocalParent* aActor, nsIURI* aURI,
nsTArray<IPCURLClassifierFeature>&& aFeatures) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aActor);
nsTArray<IPCURLClassifierFeature> features = std::move(aFeatures);
if (!aURI) {
return IPC_FAIL(this, "aURI should not be null");
}
auto* actor = static_cast<URLClassifierLocalParent*>(aActor);
return actor->StartClassify(aURI, features);
}
bool ContentParent::DeallocPURLClassifierLocalParent(
PURLClassifierLocalParent* aActor) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aActor);
RefPtr<URLClassifierLocalParent> actor =
dont_AddRef(static_cast<URLClassifierLocalParent*>(aActor));
return true;
}
PLoginReputationParent* ContentParent::AllocPLoginReputationParent(
nsIURI* aURI) {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<LoginReputationParent> actor = new LoginReputationParent();
return actor.forget().take();
}
mozilla::ipc::IPCResult ContentParent::RecvPLoginReputationConstructor(
PLoginReputationParent* aActor, nsIURI* aURI) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aActor);
if (!aURI) {
return IPC_FAIL(this, "aURI should not be null");
}
auto* actor = static_cast<LoginReputationParent*>(aActor);
return actor->QueryReputation(aURI);
}
bool ContentParent::DeallocPLoginReputationParent(
PLoginReputationParent* aActor) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aActor);
RefPtr<LoginReputationParent> actor =
dont_AddRef(static_cast<LoginReputationParent*>(aActor));
return true;
}
PSessionStorageObserverParent*
ContentParent::AllocPSessionStorageObserverParent() {
MOZ_ASSERT(NS_IsMainThread());
return mozilla::dom::AllocPSessionStorageObserverParent();
}
mozilla::ipc::IPCResult ContentParent::RecvPSessionStorageObserverConstructor(
PSessionStorageObserverParent* aActor) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aActor);
if (!mozilla::dom::RecvPSessionStorageObserverConstructor(aActor)) {
return IPC_FAIL(this, "RecvPSessionStorageObserverConstructor failed.");
}
return IPC_OK();
}
bool ContentParent::DeallocPSessionStorageObserverParent(
PSessionStorageObserverParent* aActor) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aActor);
return mozilla::dom::DeallocPSessionStorageObserverParent(aActor);
}
mozilla::ipc::IPCResult ContentParent::RecvDeviceReset() {
GPUProcessManager* pm = GPUProcessManager::Get();
if (pm) {
pm->SimulateDeviceReset();
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvBHRThreadHang(
const HangDetails& aDetails) {
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
// Copy the HangDetails recieved over the network into a nsIHangDetails, and
// then fire our own observer notification.
// XXX: We should be able to avoid this potentially expensive copy here by
// moving our deserialized argument.
nsCOMPtr<nsIHangDetails> hangDetails =
new nsHangDetails(HangDetails(aDetails), PersistedToDisk::No);
obs->NotifyObservers(hangDetails, "bhr-thread-hang", nullptr);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvAddCertException(
nsIX509Cert* aCert, const nsACString& aHostName, int32_t aPort,
const OriginAttributes& aOriginAttributes, bool aIsTemporary,
AddCertExceptionResolver&& aResolver) {
nsCOMPtr<nsICertOverrideService> overrideService =
do_GetService(NS_CERTOVERRIDE_CONTRACTID);
if (!overrideService) {
aResolver(NS_ERROR_FAILURE);
return IPC_OK();
}
nsresult rv = overrideService->RememberValidityOverride(
aHostName, aPort, aOriginAttributes, aCert, aIsTemporary);
aResolver(rv);
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentParent::RecvAutomaticStorageAccessPermissionCanBeGranted(
nsIPrincipal* aPrincipal,
AutomaticStorageAccessPermissionCanBeGrantedResolver&& aResolver) {
if (!aPrincipal) {
return IPC_FAIL(this, "No principal");
}
if (!ValidatePrincipal(aPrincipal)) {
LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__);
}
aResolver(Document::AutomaticStorageAccessPermissionCanBeGranted(aPrincipal));
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentParent::RecvStorageAccessPermissionGrantedForOrigin(
uint64_t aTopLevelWindowId,
const MaybeDiscarded<BrowsingContext>& aParentContext,
nsIPrincipal* aTrackingPrincipal, const nsACString& aTrackingOrigin,
const int& aAllowMode,
const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>&
aReason,
StorageAccessPermissionGrantedForOriginResolver&& aResolver) {
if (aParentContext.IsNullOrDiscarded()) {
return IPC_OK();
}
if (!aTrackingPrincipal) {
return IPC_FAIL(this, "No principal");
}
// We only report here if we cannot report the console directly in the content
// process. In that case, the `aReason` would be given a value. Otherwise, it
// will be nothing.
if (aReason) {
ContentBlockingNotifier::ReportUnblockingToConsole(
aParentContext.get_canonical(), NS_ConvertUTF8toUTF16(aTrackingOrigin),
aReason.value());
}
StorageAccessAPIHelper::SaveAccessForOriginOnParentProcess(
aTopLevelWindowId, aParentContext.get_canonical(), aTrackingPrincipal,
aAllowMode)
->Then(GetCurrentSerialEventTarget(), __func__,
[aResolver = std::move(aResolver)](
StorageAccessAPIHelper::ParentAccessGrantPromise::
ResolveOrRejectValue&& aValue) {
bool success =
aValue.IsResolve() && NS_SUCCEEDED(aValue.ResolveValue());
aResolver(success);
});
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvCompleteAllowAccessFor(
const MaybeDiscarded<BrowsingContext>& aParentContext,
uint64_t aTopLevelWindowId, nsIPrincipal* aTrackingPrincipal,
const nsACString& aTrackingOrigin, uint32_t aCookieBehavior,
const ContentBlockingNotifier::StorageAccessPermissionGrantedReason&
aReason,
CompleteAllowAccessForResolver&& aResolver) {
if (aParentContext.IsNullOrDiscarded()) {
return IPC_OK();
}
StorageAccessAPIHelper::CompleteAllowAccessFor(
aParentContext.get_canonical(), aTopLevelWindowId, aTrackingPrincipal,
aTrackingOrigin, aCookieBehavior, aReason, nullptr)
->Then(GetCurrentSerialEventTarget(), __func__,
[aResolver = std::move(aResolver)](
StorageAccessAPIHelper::StorageAccessPermissionGrantPromise::
ResolveOrRejectValue&& aValue) {
Maybe<StorageAccessPromptChoices> choice;
if (aValue.IsResolve()) {
choice.emplace(static_cast<StorageAccessPromptChoices>(
aValue.ResolveValue()));
}
aResolver(choice);
});
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSetAllowStorageAccessRequestFlag(
nsIPrincipal* aEmbeddedPrincipal, nsIURI* aEmbeddingOrigin,
SetAllowStorageAccessRequestFlagResolver&& aResolver) {
MOZ_ASSERT(aEmbeddedPrincipal);
MOZ_ASSERT(aEmbeddingOrigin);
// Get the permission manager and build the key.
RefPtr<PermissionManager> permManager = PermissionManager::GetInstance();
if (!permManager) {
aResolver(false);
return IPC_OK();
}
nsCOMPtr<nsIURI> embeddedURI = aEmbeddedPrincipal->GetURI();
nsCString permissionKey;
bool success = AntiTrackingUtils::CreateStorageRequestPermissionKey(
embeddedURI, permissionKey);
if (!success) {
aResolver(false);
return IPC_OK();
}
// Set the permission to ALLOW for a prefence specified amount of seconds.
// Time units are inconsistent, be careful
int64_t when = (PR_Now() / PR_USEC_PER_MSEC) +
StaticPrefs::dom_storage_access_forward_declared_lifetime() *
PR_MSEC_PER_SEC;
nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
aEmbeddingOrigin, aEmbeddedPrincipal->OriginAttributesRef());
nsresult rv = permManager->AddFromPrincipal(
principal, permissionKey, nsIPermissionManager::ALLOW_ACTION,
nsIPermissionManager::EXPIRE_TIME, when);
if (NS_FAILED(rv)) {
aResolver(false);
return IPC_OK();
}
// Resolve with success if we set the permission.
aResolver(true);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvTestAllowStorageAccessRequestFlag(
nsIPrincipal* aEmbeddingPrincipal, nsIURI* aEmbeddedOrigin,
TestAllowStorageAccessRequestFlagResolver&& aResolver) {
MOZ_ASSERT(aEmbeddingPrincipal);
MOZ_ASSERT(aEmbeddedOrigin);
// Get the permission manager and build the key.
RefPtr<PermissionManager> permManager = PermissionManager::GetInstance();
if (!permManager) {
aResolver(false);
return IPC_OK();
}
nsCString requestPermissionKey;
bool success = AntiTrackingUtils::CreateStorageRequestPermissionKey(
aEmbeddedOrigin, requestPermissionKey);
if (!success) {
aResolver(false);
return IPC_OK();
}
// Get the permission and resolve false if it is not set to ALLOW.
uint32_t access = nsIPermissionManager::UNKNOWN_ACTION;
nsresult rv = permManager->TestPermissionFromPrincipal(
aEmbeddingPrincipal, requestPermissionKey, &access);
if (NS_FAILED(rv)) {
aResolver(false);
return IPC_OK();
}
if (access != nsIPermissionManager::ALLOW_ACTION) {
aResolver(false);
return IPC_OK();
}
// Remove the permission, failing if the permission manager fails
rv = permManager->RemoveFromPrincipal(aEmbeddingPrincipal,
requestPermissionKey);
if (NS_FAILED(rv)) {
aResolver(false);
return IPC_OK();
}
// At this point, signal to our caller that the permission was set
aResolver(true);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvStoreUserInteractionAsPermission(
nsIPrincipal* aPrincipal) {
if (!aPrincipal) {
return IPC_FAIL(this, "No principal");
}
if (!ValidatePrincipal(aPrincipal)) {
LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__);
}
ContentBlockingUserInteraction::Observe(aPrincipal);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvTestCookiePermissionDecided(
const MaybeDiscarded<BrowsingContext>& aContext, nsIPrincipal* aPrincipal,
const TestCookiePermissionDecidedResolver&& aResolver) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
if (!aPrincipal) {
return IPC_FAIL(this, "No principal");
}
RefPtr<WindowGlobalParent> wgp =
aContext.get_canonical()->GetCurrentWindowGlobal();
nsCOMPtr<nsICookieJarSettings> cjs = wgp->CookieJarSettings();
Maybe<bool> result =
StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
cjs, aPrincipal);
aResolver(result);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvTestStorageAccessPermission(
nsIPrincipal* aEmbeddingPrincipal, const nsCString& aEmbeddedOrigin,
const TestStorageAccessPermissionResolver&& aResolver) {
// Get the permission manager and build the key.
RefPtr<PermissionManager> permManager = PermissionManager::GetInstance();
if (!permManager) {
aResolver(Nothing());
return IPC_OK();
}
nsCString requestPermissionKey;
AntiTrackingUtils::CreateStoragePermissionKey(aEmbeddedOrigin,
requestPermissionKey);
// Test the permission
uint32_t access = nsIPermissionManager::UNKNOWN_ACTION;
nsresult rv = permManager->TestPermissionFromPrincipal(
aEmbeddingPrincipal, requestPermissionKey, &access);
if (NS_FAILED(rv)) {
aResolver(Nothing());
return IPC_OK();
}
if (access == nsIPermissionManager::ALLOW_ACTION) {
aResolver(Some(true));
} else if (access == nsIPermissionManager::DENY_ACTION) {
aResolver(Some(false));
} else {
aResolver(Nothing());
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvNotifyMediaPlaybackChanged(
const MaybeDiscarded<BrowsingContext>& aContext,
MediaPlaybackState aState) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
if (RefPtr<IMediaInfoUpdater> updater =
aContext.get_canonical()->GetMediaController()) {
updater->NotifyMediaPlaybackChanged(aContext.ContextId(), aState);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvNotifyMediaAudibleChanged(
const MaybeDiscarded<BrowsingContext>& aContext, MediaAudibleState aState) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
if (RefPtr<IMediaInfoUpdater> updater =
aContext.get_canonical()->GetMediaController()) {
updater->NotifyMediaAudibleChanged(aContext.ContextId(), aState);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvNotifyPictureInPictureModeChanged(
const MaybeDiscarded<BrowsingContext>& aContext, bool aEnabled) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
if (RefPtr<MediaController> controller =
aContext.get_canonical()->GetMediaController()) {
controller->SetIsInPictureInPictureMode(aContext.ContextId(), aEnabled);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvAbortOtherOrientationPendingPromises(
const MaybeDiscarded<BrowsingContext>& aContext) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
CanonicalBrowsingContext* context = aContext.get_canonical();
context->Group()->EachOtherParent(this, [&](ContentParent* aParent) {
Unused << aParent->SendAbortOrientationPendingPromises(context);
});
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvNotifyMediaSessionUpdated(
const MaybeDiscarded<BrowsingContext>& aContext, bool aIsCreated) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
RefPtr<IMediaInfoUpdater> updater =
aContext.get_canonical()->GetMediaController();
if (!updater) {
return IPC_OK();
}
if (aIsCreated) {
updater->NotifySessionCreated(aContext->Id());
} else {
updater->NotifySessionDestroyed(aContext->Id());
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvNotifyUpdateMediaMetadata(
const MaybeDiscarded<BrowsingContext>& aContext,
const Maybe<MediaMetadataBase>& aMetadata) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
if (RefPtr<IMediaInfoUpdater> updater =
aContext.get_canonical()->GetMediaController()) {
updater->UpdateMetadata(aContext.ContextId(), aMetadata);
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentParent::RecvNotifyMediaSessionPlaybackStateChanged(
const MaybeDiscarded<BrowsingContext>& aContext,
MediaSessionPlaybackState aPlaybackState) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
if (RefPtr<IMediaInfoUpdater> updater =
aContext.get_canonical()->GetMediaController()) {
updater->SetDeclaredPlaybackState(aContext.ContextId(), aPlaybackState);
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentParent::RecvNotifyMediaSessionSupportedActionChanged(
const MaybeDiscarded<BrowsingContext>& aContext, MediaSessionAction aAction,
bool aEnabled) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
RefPtr<IMediaInfoUpdater> updater =
aContext.get_canonical()->GetMediaController();
if (!updater) {
return IPC_OK();
}
if (aEnabled) {
updater->EnableAction(aContext.ContextId(), aAction);
} else {
updater->DisableAction(aContext.ContextId(), aAction);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvNotifyMediaFullScreenState(
const MaybeDiscarded<BrowsingContext>& aContext, bool aIsInFullScreen) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
if (RefPtr<IMediaInfoUpdater> updater =
aContext.get_canonical()->GetMediaController()) {
updater->NotifyMediaFullScreenState(aContext.ContextId(), aIsInFullScreen);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvNotifyPositionStateChanged(
const MaybeDiscarded<BrowsingContext>& aContext,
const PositionState& aState) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
if (RefPtr<IMediaInfoUpdater> updater =
aContext.get_canonical()->GetMediaController()) {
updater->UpdatePositionState(aContext.ContextId(), aState);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvAddOrRemovePageAwakeRequest(
const MaybeDiscarded<BrowsingContext>& aContext,
const bool& aShouldAddCount) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
if (aShouldAddCount) {
aContext.get_canonical()->AddPageAwakeRequest();
} else {
aContext.get_canonical()->RemovePageAwakeRequest();
}
return IPC_OK();
}
#if defined(XP_WIN)
mozilla::ipc::IPCResult ContentParent::RecvGetModulesTrust(
ModulePaths&& aModPaths, bool aRunAtNormalPriority,
GetModulesTrustResolver&& aResolver) {
RefPtr<DllServices> dllSvc(DllServices::Get());
dllSvc->GetModulesTrust(std::move(aModPaths), aRunAtNormalPriority)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[aResolver](ModulesMapResult&& aResult) {
aResolver(Some(ModulesMapResult(std::move(aResult))));
},
[aResolver](nsresult aRv) { aResolver(Nothing()); });
return IPC_OK();
}
#endif // defined(XP_WIN)
mozilla::ipc::IPCResult ContentParent::RecvCreateBrowsingContext(
uint64_t aGroupId, BrowsingContext::IPCInitializer&& aInit) {
RefPtr<WindowGlobalParent> parent;
if (aInit.mParentId != 0) {
parent = WindowGlobalParent::GetByInnerWindowId(aInit.mParentId);
if (!parent) {
return IPC_FAIL(this, "Parent doesn't exist in parent process");
}
}
if (parent && parent->GetContentParent() != this) {
// We're trying attach a child BrowsingContext to a parent
// WindowContext in another process. This is illegal since the
// only thing that could create that child BrowsingContext is the parent
// window's process.
return IPC_FAIL(this,
"Must create BrowsingContext from the parent's process");
}
RefPtr<BrowsingContext> opener;
if (aInit.GetOpenerId() != 0) {
opener = BrowsingContext::Get(aInit.GetOpenerId());
if (!opener) {
return IPC_FAIL(this, "Opener doesn't exist in parent process");
}
}
RefPtr<BrowsingContext> child = BrowsingContext::Get(aInit.mId);
if (child) {
// This is highly suspicious. BrowsingContexts should only be created once,
// so finding one indicates that someone is doing something they shouldn't.
return IPC_FAIL(this, "A BrowsingContext with this ID already exists");
}
// Ensure that the passed-in BrowsingContextGroup is valid.
RefPtr<BrowsingContextGroup> group =
BrowsingContextGroup::GetOrCreate(aGroupId);
if (parent && parent->Group() != group) {
if (parent->Group()->Id() != aGroupId) {
return IPC_FAIL(this, "Parent has different group ID");
} else {
return IPC_FAIL(this, "Parent has different group object");
}
}
if (opener && opener->Group() != group) {
if (opener->Group()->Id() != aGroupId) {
return IPC_FAIL(this, "Opener has different group ID");
} else {
return IPC_FAIL(this, "Opener has different group object");
}
}
if (!parent && !opener && !group->Toplevels().IsEmpty()) {
return IPC_FAIL(this, "Unrelated context from child in stale group");
}
BrowsingContext::CreateFromIPC(std::move(aInit), group, this);
return IPC_OK();
}
bool ContentParent::CheckBrowsingContextEmbedder(CanonicalBrowsingContext* aBC,
const char* aOperation) const {
if (!aBC->IsEmbeddedInProcess(ChildID())) {
MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Warning,
("ParentIPC: Trying to %s out of process context 0x%08" PRIx64,
aOperation, aBC->Id()));
return false;
}
return true;
}
mozilla::ipc::IPCResult ContentParent::RecvDiscardBrowsingContext(
const MaybeDiscarded<BrowsingContext>& aContext, bool aDoDiscard,
DiscardBrowsingContextResolver&& aResolve) {
if (CanonicalBrowsingContext* context =
CanonicalBrowsingContext::Cast(aContext.GetMaybeDiscarded())) {
if (aDoDiscard && !context->IsDiscarded()) {
if (!CheckBrowsingContextEmbedder(context, "discard")) {
return IPC_FAIL(this, "Illegal Discard attempt");
}
context->Detach(/* aFromIPC */ true);
}
context->AddFinalDiscardListener(aResolve);
return IPC_OK();
}
// Resolve the promise, as we've received and handled the message. This will
// allow the content process to fully-discard references to this BC.
aResolve(true);
return IPC_OK();
}
void ContentParent::UnregisterRemoveWorkerActor() {
MOZ_ASSERT(NS_IsMainThread());
{
MutexAutoLock lock(mThreadsafeHandle->mMutex);
if (--mThreadsafeHandle->mRemoteWorkerActorCount) {
return;
}
}
MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
("UnregisterRemoveWorkerActor %p", this));
MaybeBeginShutDown();
}
mozilla::ipc::IPCResult ContentParent::RecvWindowClose(
const MaybeDiscarded<BrowsingContext>& aContext, bool aTrustedCaller) {
if (aContext.IsNullOrDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send a message to dead or detached context"));
return IPC_OK();
}
CanonicalBrowsingContext* context = aContext.get_canonical();
// FIXME Need to check that the sending process has access to the unit of
// related
// browsing contexts of bc.
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (cpm) {
ContentParent* cp =
cpm->GetContentProcessById(ContentParentId(context->OwnerProcessId()));
Unused << cp->SendWindowClose(context, aTrustedCaller);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvWindowFocus(
const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType,
uint64_t aActionId) {
if (aContext.IsNullOrDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send a message to dead or detached context"));
return IPC_OK();
}
LOGFOCUS(("ContentParent::RecvWindowFocus actionid: %" PRIu64, aActionId));
CanonicalBrowsingContext* context = aContext.get_canonical();
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (cpm) {
ContentParent* cp =
cpm->GetContentProcessById(ContentParentId(context->OwnerProcessId()));
Unused << cp->SendWindowFocus(context, aCallerType, aActionId);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvWindowBlur(
const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType) {
if (aContext.IsNullOrDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send a message to dead or detached context"));
return IPC_OK();
}
CanonicalBrowsingContext* context = aContext.get_canonical();
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (cpm) {
ContentParent* cp =
cpm->GetContentProcessById(ContentParentId(context->OwnerProcessId()));
Unused << cp->SendWindowBlur(context, aCallerType);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvRaiseWindow(
const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType,
uint64_t aActionId) {
if (aContext.IsNullOrDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send a message to dead or detached context"));
return IPC_OK();
}
LOGFOCUS(("ContentParent::RecvRaiseWindow actionid: %" PRIu64, aActionId));
CanonicalBrowsingContext* context = aContext.get_canonical();
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (cpm) {
ContentParent* cp =
cpm->GetContentProcessById(ContentParentId(context->OwnerProcessId()));
Unused << cp->SendRaiseWindow(context, aCallerType, aActionId);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvAdjustWindowFocus(
const MaybeDiscarded<BrowsingContext>& aContext, bool aIsVisible,
uint64_t aActionId) {
if (aContext.IsNullOrDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send a message to dead or detached context"));
return IPC_OK();
}
LOGFOCUS(
("ContentParent::RecvAdjustWindowFocus isVisible %d actionid: %" PRIu64,
aIsVisible, aActionId));
nsTHashMap<nsPtrHashKey<ContentParent>, bool> processes(2);
processes.InsertOrUpdate(this, true);
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (cpm) {
CanonicalBrowsingContext* context = aContext.get_canonical();
while (context) {
BrowsingContext* parent = context->GetParent();
if (!parent) {
break;
}
CanonicalBrowsingContext* canonicalParent = parent->Canonical();
ContentParent* cp = cpm->GetContentProcessById(
ContentParentId(canonicalParent->OwnerProcessId()));
if (cp && !processes.Get(cp)) {
Unused << cp->SendAdjustWindowFocus(context, aIsVisible, aActionId);
processes.InsertOrUpdate(cp, true);
}
context = canonicalParent;
}
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvClearFocus(
const MaybeDiscarded<BrowsingContext>& aContext) {
if (aContext.IsNullOrDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send a message to dead or detached context"));
return IPC_OK();
}
CanonicalBrowsingContext* context = aContext.get_canonical();
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (cpm) {
ContentParent* cp =
cpm->GetContentProcessById(ContentParentId(context->OwnerProcessId()));
Unused << cp->SendClearFocus(context);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSetFocusedBrowsingContext(
const MaybeDiscarded<BrowsingContext>& aContext, uint64_t aActionId) {
if (aContext.IsNullOrDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send a message to dead or detached context"));
return IPC_OK();
}
LOGFOCUS(("ContentParent::RecvSetFocusedBrowsingContext actionid: %" PRIu64,
aActionId));
CanonicalBrowsingContext* context = aContext.get_canonical();
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (!fm) {
return IPC_OK();
}
if (!fm->SetFocusedBrowsingContextInChrome(context, aActionId)) {
LOGFOCUS((
"Ignoring out-of-sequence attempt [%p] to set focused browsing context "
"in parent.",
context));
Unused << SendReviseFocusedBrowsingContext(
aActionId, fm->GetFocusedBrowsingContextInChrome(),
fm->GetActionIdForFocusedBrowsingContextInChrome());
return IPC_OK();
}
BrowserParent::UpdateFocusFromBrowsingContext();
context->Group()->EachOtherParent(this, [&](ContentParent* aParent) {
Unused << aParent->SendSetFocusedBrowsingContext(context, aActionId);
});
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSetActiveBrowsingContext(
const MaybeDiscarded<BrowsingContext>& aContext, uint64_t aActionId) {
if (aContext.IsNullOrDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send a message to dead or detached context"));
return IPC_OK();
}
LOGFOCUS(("ContentParent::RecvSetActiveBrowsingContext actionid: %" PRIu64,
aActionId));
CanonicalBrowsingContext* context = aContext.get_canonical();
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (!fm) {
return IPC_OK();
}
if (!fm->SetActiveBrowsingContextInChrome(context, aActionId)) {
LOGFOCUS(
("Ignoring out-of-sequence attempt [%p] to set active browsing context "
"in parent.",
context));
Unused << SendReviseActiveBrowsingContext(
aActionId, fm->GetActiveBrowsingContextInChrome(),
fm->GetActionIdForActiveBrowsingContextInChrome());
return IPC_OK();
}
context->Group()->EachOtherParent(this, [&](ContentParent* aParent) {
Unused << aParent->SendSetActiveBrowsingContext(context, aActionId);
});
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvUnsetActiveBrowsingContext(
const MaybeDiscarded<BrowsingContext>& aContext, uint64_t aActionId) {
if (aContext.IsNullOrDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send a message to dead or detached context"));
return IPC_OK();
}
LOGFOCUS(("ContentParent::RecvUnsetActiveBrowsingContext actionid: %" PRIu64,
aActionId));
CanonicalBrowsingContext* context = aContext.get_canonical();
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (!fm) {
return IPC_OK();
}
if (!fm->SetActiveBrowsingContextInChrome(nullptr, aActionId)) {
LOGFOCUS(
("Ignoring out-of-sequence attempt to unset active browsing context in "
"parent [%p].",
context));
Unused << SendReviseActiveBrowsingContext(
aActionId, fm->GetActiveBrowsingContextInChrome(),
fm->GetActionIdForActiveBrowsingContextInChrome());
return IPC_OK();
}
context->Group()->EachOtherParent(this, [&](ContentParent* aParent) {
Unused << aParent->SendUnsetActiveBrowsingContext(context, aActionId);
});
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSetFocusedElement(
const MaybeDiscarded<BrowsingContext>& aContext, bool aNeedsFocus) {
if (aContext.IsNullOrDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send a message to dead or detached context"));
return IPC_OK();
}
LOGFOCUS(("ContentParent::RecvSetFocusedElement"));
CanonicalBrowsingContext* context = aContext.get_canonical();
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (cpm) {
ContentParent* cp =
cpm->GetContentProcessById(ContentParentId(context->OwnerProcessId()));
Unused << cp->SendSetFocusedElement(context, aNeedsFocus);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvFinalizeFocusOuter(
const MaybeDiscarded<BrowsingContext>& aContext, bool aCanFocus,
CallerType aCallerType) {
if (aContext.IsNullOrDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send a message to dead or detached context"));
return IPC_OK();
}
LOGFOCUS(("ContentParent::RecvFinalizeFocusOuter"));
CanonicalBrowsingContext* context = aContext.get_canonical();
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (cpm) {
ContentParent* cp = cpm->GetContentProcessById(
ContentParentId(context->EmbedderProcessId()));
if (cp) {
Unused << cp->SendFinalizeFocusOuter(context, aCanFocus, aCallerType);
}
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvInsertNewFocusActionId(
uint64_t aActionId) {
LOGFOCUS(("ContentParent::RecvInsertNewFocusActionId actionid: %" PRIu64,
aActionId));
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
fm->InsertNewFocusActionId(aActionId);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvBlurToParent(
const MaybeDiscarded<BrowsingContext>& aFocusedBrowsingContext,
const MaybeDiscarded<BrowsingContext>& aBrowsingContextToClear,
const MaybeDiscarded<BrowsingContext>& aAncestorBrowsingContextToFocus,
bool aIsLeavingDocument, bool aAdjustWidget,
bool aBrowsingContextToClearHandled,
bool aAncestorBrowsingContextToFocusHandled, uint64_t aActionId) {
if (aFocusedBrowsingContext.IsNullOrDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send a message to dead or detached context"));
return IPC_OK();
}
LOGFOCUS(
("ContentParent::RecvBlurToParent isLeavingDocument %d adjustWidget %d "
"browsingContextToClearHandled %d ancestorBrowsingContextToFocusHandled "
"%d actionid: %" PRIu64,
aIsLeavingDocument, aAdjustWidget, aBrowsingContextToClearHandled,
aAncestorBrowsingContextToFocusHandled, aActionId));
CanonicalBrowsingContext* focusedBrowsingContext =
aFocusedBrowsingContext.get_canonical();
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (!cpm) {
return IPC_OK();
}
// If aBrowsingContextToClear and aAncestorBrowsingContextToFocusHandled
// didn't get handled in the process that sent this IPC message and they
// aren't in the same process as aFocusedBrowsingContext, we need to split
// off their handling here and use SendSetFocusedElement to send them
// elsewhere than the blurring itself.
bool ancestorDifferent =
(!aAncestorBrowsingContextToFocusHandled &&
!aAncestorBrowsingContextToFocus.IsNullOrDiscarded() &&
(focusedBrowsingContext->OwnerProcessId() !=
aAncestorBrowsingContextToFocus.get_canonical()->OwnerProcessId()));
if (!aBrowsingContextToClearHandled &&
!aBrowsingContextToClear.IsNullOrDiscarded() &&
(focusedBrowsingContext->OwnerProcessId() !=
aBrowsingContextToClear.get_canonical()->OwnerProcessId())) {
MOZ_RELEASE_ASSERT(!ancestorDifferent,
"This combination is not supposed to happen.");
ContentParent* cp = cpm->GetContentProcessById(ContentParentId(
aBrowsingContextToClear.get_canonical()->OwnerProcessId()));
Unused << cp->SendSetFocusedElement(aBrowsingContextToClear, false);
} else if (ancestorDifferent) {
ContentParent* cp = cpm->GetContentProcessById(ContentParentId(
aAncestorBrowsingContextToFocus.get_canonical()->OwnerProcessId()));
Unused << cp->SendSetFocusedElement(aAncestorBrowsingContextToFocus, true);
}
ContentParent* cp = cpm->GetContentProcessById(
ContentParentId(focusedBrowsingContext->OwnerProcessId()));
Unused << cp->SendBlurToChild(aFocusedBrowsingContext,
aBrowsingContextToClear,
aAncestorBrowsingContextToFocus,
aIsLeavingDocument, aAdjustWidget, aActionId);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvMaybeExitFullscreen(
const MaybeDiscarded<BrowsingContext>& aContext) {
if (aContext.IsNullOrDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send a message to dead or detached context"));
return IPC_OK();
}
CanonicalBrowsingContext* context = aContext.get_canonical();
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (cpm) {
ContentParent* cp =
cpm->GetContentProcessById(ContentParentId(context->OwnerProcessId()));
Unused << cp->SendMaybeExitFullscreen(context);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvWindowPostMessage(
const MaybeDiscarded<BrowsingContext>& aContext,
const ClonedOrErrorMessageData& aMessage, const PostMessageData& aData) {
if (aContext.IsNullOrDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send a message to dead or detached context"));
return IPC_OK();
}
CanonicalBrowsingContext* context = aContext.get_canonical();
if (aData.source().IsDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send a message from dead or detached context"));
return IPC_OK();
}
RefPtr<ContentParent> cp = context->GetContentParent();
if (!cp) {
MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
("ParentIPC: Trying to send PostMessage to dead content process"));
return IPC_OK();
}
ClonedOrErrorMessageData message;
StructuredCloneData messageFromChild;
if (aMessage.type() == ClonedOrErrorMessageData::TClonedMessageData) {
UnpackClonedMessageData(aMessage, messageFromChild);
ClonedMessageData clonedMessageData;
if (BuildClonedMessageData(messageFromChild, clonedMessageData)) {
message = std::move(clonedMessageData);
} else {
// FIXME Logging?
message = ErrorMessageData();
}
} else {
MOZ_ASSERT(aMessage.type() == ClonedOrErrorMessageData::TErrorMessageData);
message = ErrorMessageData();
}
Unused << cp->SendWindowPostMessage(context, message, aData);
return IPC_OK();
}
void ContentParent::AddBrowsingContextGroup(BrowsingContextGroup* aGroup) {
MOZ_DIAGNOSTIC_ASSERT(aGroup);
// Ensure that the group has been inserted, and if we're not launching
// anymore, also begin subscribing. Launching processes will be subscribed if
// they finish launching in `LaunchSubprocessResolve`.
if (mGroups.EnsureInserted(aGroup) && !IsLaunching()) {
aGroup->Subscribe(this);
}
}
void ContentParent::RemoveBrowsingContextGroup(BrowsingContextGroup* aGroup) {
MOZ_DIAGNOSTIC_ASSERT(aGroup);
// Remove the group from our list. This is called from the
// BrowsingContextGroup when unsubscribing, so we don't need to do it here.
if (mGroups.EnsureRemoved(aGroup) && CanSend()) {
// If we're removing the entry for the first time, tell the content process
// to clean up the group.
Unused << SendDestroyBrowsingContextGroup(aGroup->Id());
}
}
mozilla::ipc::IPCResult ContentParent::RecvCommitBrowsingContextTransaction(
const MaybeDiscarded<BrowsingContext>& aContext,
BrowsingContext::BaseTransaction&& aTransaction, uint64_t aEpoch) {
// Record the new BrowsingContextFieldEpoch associated with this transaction.
// This should be done unconditionally, so that we're always in-sync.
//
// The order the parent process receives transactions is considered the
// "canonical" ordering, so we don't need to worry about doing any
// epoch-related validation.
MOZ_ASSERT(aEpoch == mBrowsingContextFieldEpoch + 1,
"Child process skipped an epoch?");
mBrowsingContextFieldEpoch = aEpoch;
return aTransaction.CommitFromIPC(aContext, this);
}
mozilla::ipc::IPCResult ContentParent::RecvBlobURLDataRequest(
const nsACString& aBlobURL, nsIPrincipal* aTriggeringPrincipal,
nsIPrincipal* aLoadingPrincipal, const OriginAttributes& aOriginAttributes,
uint64_t aInnerWindowId, const Maybe<nsID>& aAgentClusterId,
BlobURLDataRequestResolver&& aResolver) {
RefPtr<BlobImpl> blobImpl;
// Since revoked blobs are also retrieved, it is possible that the blob no
// longer exists (due to the 5 second timeout) when execution reaches here
if (!BlobURLProtocolHandler::GetDataEntry(
aBlobURL, getter_AddRefs(blobImpl), aLoadingPrincipal,
aTriggeringPrincipal, aOriginAttributes, aInnerWindowId,
aAgentClusterId, true /* AlsoIfRevoked */)) {
aResolver(NS_ERROR_DOM_BAD_URI);
return IPC_OK();
}
IPCBlob ipcBlob;
nsresult rv = IPCBlobUtils::Serialize(blobImpl, ipcBlob);
if (NS_WARN_IF(NS_FAILED(rv))) {
aResolver(rv);
return IPC_OK();
}
aResolver(ipcBlob);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvReportServiceWorkerShutdownProgress(
uint32_t aShutdownStateId, ServiceWorkerShutdownState::Progress aProgress) {
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
MOZ_RELEASE_ASSERT(swm, "ServiceWorkers should shutdown before SWM.");
swm->ReportServiceWorkerShutdownProgress(aShutdownStateId, aProgress);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvNotifyOnHistoryReload(
const MaybeDiscarded<BrowsingContext>& aContext, const bool& aForceReload,
NotifyOnHistoryReloadResolver&& aResolver) {
bool canReload = false;
Maybe<RefPtr<nsDocShellLoadState>> loadState;
Maybe<bool> reloadActiveEntry;
if (!aContext.IsDiscarded()) {
aContext.get_canonical()->NotifyOnHistoryReload(
aForceReload, canReload, loadState, reloadActiveEntry);
}
aResolver(Tuple<const bool&, const Maybe<RefPtr<nsDocShellLoadState>>&,
const Maybe<bool>&>(canReload, loadState, reloadActiveEntry));
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvHistoryCommit(
const MaybeDiscarded<BrowsingContext>& aContext, const uint64_t& aLoadID,
const nsID& aChangeID, const uint32_t& aLoadType, const bool& aPersist,
const bool& aCloneEntryChildren, const bool& aChannelExpired,
const uint32_t& aCacheKey) {
if (!aContext.IsDiscarded()) {
CanonicalBrowsingContext* canonical = aContext.get_canonical();
if (!canonical) {
return IPC_FAIL(
this, "Could not get canonical. aContext.get_canonical() fails.");
}
canonical->SessionHistoryCommit(aLoadID, aChangeID, aLoadType, aPersist,
aCloneEntryChildren, aChannelExpired,
aCacheKey);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvHistoryGo(
const MaybeDiscarded<BrowsingContext>& aContext, int32_t aOffset,
uint64_t aHistoryEpoch, bool aRequireUserInteraction, bool aUserActivation,
HistoryGoResolver&& aResolveRequestedIndex) {
if (!aContext.IsDiscarded()) {
aResolveRequestedIndex(aContext.get_canonical()->HistoryGo(
aOffset, aHistoryEpoch, aRequireUserInteraction, aUserActivation,
Some(ChildID())));
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSynchronizeLayoutHistoryState(
const MaybeDiscarded<BrowsingContext>& aContext,
nsILayoutHistoryState* aState) {
if (aContext.IsNull()) {
return IPC_OK();
}
BrowsingContext* bc = aContext.GetMaybeDiscarded();
if (!bc) {
return IPC_OK();
}
SessionHistoryEntry* entry = bc->Canonical()->GetActiveSessionHistoryEntry();
if (entry) {
entry->SetLayoutHistoryState(aState);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSessionHistoryEntryTitle(
const MaybeDiscarded<BrowsingContext>& aContext, const nsAString& aTitle) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
SessionHistoryEntry* entry =
aContext.get_canonical()->GetActiveSessionHistoryEntry();
if (entry) {
entry->SetTitle(aTitle);
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentParent::RecvSessionHistoryEntryScrollRestorationIsManual(
const MaybeDiscarded<BrowsingContext>& aContext, const bool& aIsManual) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
SessionHistoryEntry* entry =
aContext.get_canonical()->GetActiveSessionHistoryEntry();
if (entry) {
entry->SetScrollRestorationIsManual(aIsManual);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSessionHistoryEntryScrollPosition(
const MaybeDiscarded<BrowsingContext>& aContext, const int32_t& aX,
const int32_t& aY) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
SessionHistoryEntry* entry =
aContext.get_canonical()->GetActiveSessionHistoryEntry();
if (entry) {
entry->SetScrollPosition(aX, aY);
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentParent::RecvSessionHistoryEntryStoreWindowNameInContiguousEntries(
const MaybeDiscarded<BrowsingContext>& aContext, const nsAString& aName) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
// Per https://html.spec.whatwg.org/#history-traversal 4.2.1, we need to set
// the name to all contiguous entries. This has to be called before
// CanonicalBrowsingContext::SessionHistoryCommit(), so the active entry is
// still the old entry that we want to set.
SessionHistoryEntry* entry =
aContext.get_canonical()->GetActiveSessionHistoryEntry();
if (entry) {
nsSHistory::WalkContiguousEntries(
entry, [&](nsISHEntry* aEntry) { aEntry->SetName(aName); });
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSessionHistoryEntryCacheKey(
const MaybeDiscarded<BrowsingContext>& aContext,
const uint32_t& aCacheKey) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
SessionHistoryEntry* entry =
aContext.get_canonical()->GetActiveSessionHistoryEntry();
if (entry) {
entry->SetCacheKey(aCacheKey);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSessionHistoryEntryWireframe(
const MaybeDiscarded<BrowsingContext>& aContext,
const Wireframe& aWireframe) {
if (aContext.IsNull()) {
return IPC_OK();
}
BrowsingContext* bc = aContext.GetMaybeDiscarded();
if (!bc) {
return IPC_OK();
}
SessionHistoryEntry* entry = bc->Canonical()->GetActiveSessionHistoryEntry();
if (entry) {
entry->SetWireframe(Some(aWireframe));
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentParent::RecvGetLoadingSessionHistoryInfoFromParent(
const MaybeDiscarded<BrowsingContext>& aContext,
GetLoadingSessionHistoryInfoFromParentResolver&& aResolver) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
Maybe<LoadingSessionHistoryInfo> info;
aContext.get_canonical()->GetLoadingSessionHistoryInfoFromParent(info);
aResolver(info);
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvRemoveFromBFCache(
const MaybeDiscarded<BrowsingContext>& aContext) {
if (aContext.IsNullOrDiscarded()) {
return IPC_OK();
}
nsCOMPtr<nsFrameLoaderOwner> owner =
do_QueryInterface(aContext.get_canonical()->GetEmbedderElement());
if (!owner) {
return IPC_OK();
}
RefPtr<nsFrameLoader> frameLoader = owner->GetFrameLoader();
if (!frameLoader || !frameLoader->GetMaybePendingBrowsingContext()) {
return IPC_OK();
}
nsCOMPtr<nsISHistory> shistory = frameLoader->GetMaybePendingBrowsingContext()
->Canonical()
->GetSessionHistory();
if (!shistory) {
return IPC_OK();
}
uint32_t count = shistory->GetCount();
for (uint32_t i = 0; i < count; ++i) {
nsCOMPtr<nsISHEntry> entry;
shistory->GetEntryAtIndex(i, getter_AddRefs(entry));
nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(entry);
if (she) {
if (RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader()) {
if (frameLoader->GetMaybePendingBrowsingContext() == aContext.get()) {
she->SetFrameLoader(nullptr);
frameLoader->Destroy();
break;
}
}
}
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSetActiveSessionHistoryEntry(
const MaybeDiscarded<BrowsingContext>& aContext,
const Maybe<nsPoint>& aPreviousScrollPos, SessionHistoryInfo&& aInfo,
uint32_t aLoadType, uint32_t aUpdatedCacheKey, const nsID& aChangeID) {
if (!aContext.IsDiscarded()) {
aContext.get_canonical()->SetActiveSessionHistoryEntry(
aPreviousScrollPos, &aInfo, aLoadType, aUpdatedCacheKey, aChangeID);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvReplaceActiveSessionHistoryEntry(
const MaybeDiscarded<BrowsingContext>& aContext,
SessionHistoryInfo&& aInfo) {
if (!aContext.IsDiscarded()) {
aContext.get_canonical()->ReplaceActiveSessionHistoryEntry(&aInfo);
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentParent::RecvRemoveDynEntriesFromActiveSessionHistoryEntry(
const MaybeDiscarded<BrowsingContext>& aContext) {
if (!aContext.IsDiscarded()) {
aContext.get_canonical()->RemoveDynEntriesFromActiveSessionHistoryEntry();
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvRemoveFromSessionHistory(
const MaybeDiscarded<BrowsingContext>& aContext, const nsID& aChangeID) {
if (!aContext.IsDiscarded()) {
aContext.get_canonical()->RemoveFromSessionHistory(aChangeID);
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvHistoryReload(
const MaybeDiscarded<BrowsingContext>& aContext,
const uint32_t aReloadFlags) {
if (!aContext.IsDiscarded()) {
nsISHistory* shistory = aContext.get_canonical()->GetSessionHistory();
if (shistory) {
shistory->Reload(aReloadFlags);
}
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvCommitWindowContextTransaction(
const MaybeDiscarded<WindowContext>& aContext,
WindowContext::BaseTransaction&& aTransaction, uint64_t aEpoch) {
// Record the new BrowsingContextFieldEpoch associated with this transaction.
// This should be done unconditionally, so that we're always in-sync.
//
// The order the parent process receives transactions is considered the
// "canonical" ordering, so we don't need to worry about doing any
// epoch-related validation.
MOZ_ASSERT(aEpoch == mBrowsingContextFieldEpoch + 1,
"Child process skipped an epoch?");
mBrowsingContextFieldEpoch = aEpoch;
return aTransaction.CommitFromIPC(aContext, this);
}
NS_IMETHODIMP ContentParent::GetChildID(uint64_t* aOut) {
*aOut = this->ChildID();
return NS_OK;
}
NS_IMETHODIMP ContentParent::GetOsPid(int32_t* aOut) {
*aOut = Pid();
return NS_OK;
}
NS_IMETHODIMP ContentParent::GetRemoteType(nsACString& aRemoteType) {
aRemoteType = GetRemoteType();
return NS_OK;
}
IPCResult ContentParent::RecvRawMessage(
const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData,
const Maybe<ClonedMessageData>& aStack) {
Maybe<StructuredCloneData> data;
if (aData) {
data.emplace();
data->BorrowFromClonedMessageData(*aData);
}
Maybe<StructuredCloneData> stack;
if (aStack) {
stack.emplace();
stack->BorrowFromClonedMessageData(*aStack);
}
ReceiveRawMessage(aMeta, std::move(data), std::move(stack));
return IPC_OK();
}
NS_IMETHODIMP ContentParent::GetActor(const nsACString& aName, JSContext* aCx,
JSProcessActorParent** retval) {
ErrorResult error;
RefPtr<JSProcessActorParent> actor =
JSActorManager::GetActor(aCx, aName, error)
.downcast<JSProcessActorParent>();
if (error.MaybeSetPendingException(aCx)) {
return NS_ERROR_FAILURE;
}
actor.forget(retval);
return NS_OK;
}
NS_IMETHODIMP ContentParent::GetExistingActor(const nsACString& aName,
JSProcessActorParent** retval) {
RefPtr<JSProcessActorParent> actor =
JSActorManager::GetExistingActor(aName).downcast<JSProcessActorParent>();
actor.forget(retval);
return NS_OK;
}
already_AddRefed<JSActor> ContentParent::InitJSActor(
JS::Handle<JSObject*> aMaybeActor, const nsACString& aName,
ErrorResult& aRv) {
RefPtr<JSProcessActorParent> actor;
if (aMaybeActor.get()) {
aRv = UNWRAP_OBJECT(JSProcessActorParent, aMaybeActor.get(), actor);
if (aRv.Failed()) {
return nullptr;
}
} else {
actor = new JSProcessActorParent();
}
MOZ_RELEASE_ASSERT(!actor->Manager(),
"mManager was already initialized once!");
actor->Init(aName, this);
return actor.forget();
}
IPCResult ContentParent::RecvFOGData(ByteBuf&& buf) {
glean::FOGData(std::move(buf));
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvSetContainerFeaturePolicy(
const MaybeDiscardedBrowsingContext& aContainerContext,
FeaturePolicy* aContainerFeaturePolicy) {
if (aContainerContext.IsNullOrDiscarded()) {
return IPC_OK();
}
auto* context = aContainerContext.get_canonical();
context->SetContainerFeaturePolicy(aContainerFeaturePolicy);
return IPC_OK();
}
NS_IMETHODIMP ContentParent::GetCanSend(bool* aCanSend) {
*aCanSend = CanSend();
return NS_OK;
}
ContentParent* ContentParent::AsContentParent() { return this; }
JSActorManager* ContentParent::AsJSActorManager() { return this; }
/* static */
void ContentParent::DidLaunchSubprocess() {
TimeStamp now = TimeStamp::Now();
uint32_t count = 0;
for (auto* parent : ContentParent::AllProcesses(ContentParent::eLive)) {
Unused << parent;
count += 1;
}
if (count > sMaxContentProcesses) {
sMaxContentProcesses = count;
Telemetry::ScalarSet(Telemetry::ScalarID::CONTENT_PROCESS_MAX_PRECISE,
count);
}
if (sLastContentProcessLaunch) {
TimeStamp last = *sLastContentProcessLaunch;
Telemetry::AccumulateTimeDelta(
Telemetry::CONTENT_PROCESS_TIME_SINCE_LAST_LAUNCH_MS, last, now);
}
sLastContentProcessLaunch = Some(now);
}
IPCResult ContentParent::RecvGetSystemIcon(nsIURI* aURI,
GetSystemIconResolver&& aResolver) {
#if defined(MOZ_WIDGET_GTK)
Maybe<ByteBuf> bytebuf = Some(ByteBuf{});
nsresult rv = nsIconChannel::GetIcon(aURI, bytebuf.ptr());
if (NS_WARN_IF(NS_FAILED(rv))) {
bytebuf = Nothing();
}
using ResolverArgs = Tuple<const nsresult&, mozilla::Maybe<ByteBuf>&&>;
aResolver(ResolverArgs(rv, std::move(bytebuf)));
return IPC_OK();
#elif defined(XP_WIN)
using ResolverArgs = Tuple<const nsresult&, mozilla::Maybe<ByteBuf>&&>;
nsIconChannel::GetIconAsync(aURI)->Then(
GetCurrentSerialEventTarget(), __func__,
[aResolver](ByteBuf&& aByteBuf) {
Maybe<ByteBuf> bytebuf = Some(std::move(aByteBuf));
aResolver(ResolverArgs(NS_OK, std::move(bytebuf)));
},
[aResolver](nsresult aErr) {
Maybe<ByteBuf> bytebuf = Nothing();
aResolver(ResolverArgs(aErr, std::move(bytebuf)));
});
return IPC_OK();
#else
MOZ_CRASH(
"This message is currently implemented only on GTK and Windows "
"platforms");
#endif
}
#ifdef FUZZING_SNAPSHOT
IPCResult ContentParent::RecvSignalFuzzingReady() {
// No action needed here, we already observe this message directly
// on the channel and act accordingly.
return IPC_OK();
}
#endif
nsCString ThreadsafeContentParentHandle::GetRemoteType() {
MutexAutoLock lock(mMutex);
return mRemoteType;
}
bool ThreadsafeContentParentHandle::MaybeRegisterRemoteWorkerActor(
MoveOnlyFunction<bool(uint32_t, bool)> aCallback) {
MutexAutoLock lock(mMutex);
if (aCallback(mRemoteWorkerActorCount, mShutdownStarted)) {
++mRemoteWorkerActorCount;
return true;
}
return false;
}
} // namespace dom
} // namespace mozilla
NS_IMPL_ISUPPORTS(ParentIdleListener, nsIObserver)
NS_IMETHODIMP
ParentIdleListener::Observe(nsISupports*, const char* aTopic,
const char16_t* aData) {
mozilla::Unused << mParent->SendNotifyIdleObserver(
mObserver, nsDependentCString(aTopic), nsDependentString(aData));
return NS_OK;
}