Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ContentParent.h"
#include <map>
#include <utility>
#include "BrowserParent.h"
#include "ContentProcessManager.h"
#include "GMPServiceParent.h"
#include "GeckoProfiler.h"
#include "Geolocation.h"
#include "GfxInfoBase.h"
#include "HandlerServiceParent.h"
#include "IHistory.h"
#include "MMPrinter.h"
#include "PreallocatedProcessManager.h"
#include "ProcessPriorityManager.h"
#include "ProfilerParent.h"
#include "SandboxHal.h"
#include "SourceSurfaceRawData.h"
#include "base/basictypes.h"
#include "chrome/common/process_watcher.h"
#include "gfxPlatform.h"
#include "gfxPlatformFontList.h"
#include "mozilla/AntiTrackingUtils.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/Casting.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ClipboardContentAnalysisParent.h"
#include "mozilla/ClipboardReadRequestParent.h"
#include "mozilla/ClipboardWriteRequestParent.h"
#include "mozilla/CmdLineAndEnvUtils.h"
#include "mozilla/Components.h"
#include "mozilla/ContentBlockingUserInteraction.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/FOGIPC.h"
#include "mozilla/GeckoArgs.h"
#include "mozilla/GeckoTrace.h"
#include "mozilla/GlobalStyleSheetCache.h"
#include "mozilla/HangDetails.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/Maybe.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/PageloadEvent.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/RDDProcessManager.h"
#include "mozilla/RecursiveMutex.h"
#include "mozilla/RemoteLazyInputStreamParent.h"
#include "mozilla/Result.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/ScriptPreloader.h"
#include "mozilla/Services.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_fission.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/StaticPrefs_telemetry.h"
#include "mozilla/StaticPrefs_threads.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/TelemetryComms.h"
#include "mozilla/TelemetryIPC.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/WebBrowserPersistDocumentParent.h"
#include "mozilla/XREAppData.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/GeolocationSystem.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/MediaStatusManager.h"
#include "mozilla/dom/MemoryReportRequest.h"
#include "mozilla/dom/PContentPermissionRequestParent.h"
#include "mozilla/dom/PCycleCollectWithLogsParent.h"
#include "mozilla/dom/ParentProcessChannelHandle.h"
#include "mozilla/dom/ParentProcessMessageManager.h"
#include "mozilla/dom/PermissionObserver.h"
#include "mozilla/dom/PermissionStatusBinding.h"
#include "mozilla/dom/Permissions.h"
#include "mozilla/dom/ProcessMessageManager.h"
#include "mozilla/dom/PushNotifier.h"
#include "mozilla/dom/RemoteWorkerDebuggerManagerParent.h"
#include "mozilla/dom/RemoteWorkerServiceParent.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/SharedScriptCache.h"
#include "mozilla/dom/StorageIPC.h"
#include "mozilla/dom/URLClassifierParent.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/ipc/SharedMap.h"
#include "mozilla/dom/ipc/StructuredCloneData.h"
#include "mozilla/dom/notification/NotificationUtils.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"
#ifdef MOZ_WMF
# include "mozilla/glean/DomMediaPlatformsWmfMetrics.h"
#endif
#include "mozilla/glean/DomMetrics.h"
#include "mozilla/glean/FOGTransportParent.h"
#include "mozilla/glean/GleanPings.h"
#include "mozilla/glean/IpcMetrics.h"
#include "mozilla/glean/PFOGTransport.h"
#include "mozilla/hal_sandbox/PHalParent.h"
#include "mozilla/intl/L10nRegistry.h"
#include "mozilla/intl/LocaleService.h"
#include "mozilla/intl/OSPreferences.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/ProcessUtils.h"
#include "mozilla/ipc/SharedMemoryHandle.h"
#include "mozilla/ipc/TestShellParent.h"
#include "mozilla/ipc/URIUtils.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/CookieKey.h"
#include "mozilla/net/CookieServiceParent.h"
#include "mozilla/net/NeckoMessageUtils.h"
#include "mozilla/net/NeckoParent.h"
#include "mozilla/net/PCookieServiceParent.h"
#include "mozilla/net/TRRService.h"
#include "mozilla/net/UrlClassifierFeatureFactory.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 "nsCRT.h"
#include "nsChromeRegistryChrome.h"
#include "nsComponentManagerUtils.h"
#include "nsConsoleMessage.h"
#include "nsConsoleService.h"
#include "nsContentPermissionHelper.h"
#include "nsContentUtils.h"
#include "nsDNSService2.h"
#include "nsDebugImpl.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDocShell.h"
#include "nsEmbedCID.h"
#include "nsFocusManager.h"
#include "nsFrameLoader.h"
#include "nsFrameMessageManager.h"
#include "nsGlobalWindowOuter.h"
#include "nsHashPropertyBag.h"
#include "nsHyphenationManager.h"
#include "nsIAppShell.h"
#include "nsIAppWindow.h"
#include "nsIAsyncInputStream.h"
#include "nsIBidiKeyboard.h"
#include "nsIBrowserDOMWindow.h"
#include "nsICaptivePortalService.h"
#include "nsIClipboard.h"
#include "nsIContentAnalysis.h"
#include "nsIContentSecurityPolicy.h"
#include "nsICookie.h"
#include "nsICookieNotification.h"
#include "nsICrashService.h"
#include "nsICycleCollectorListener.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIDragService.h"
#include "nsIExternalProtocolService.h"
#include "nsIGfxInfo.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsILocalStorageManager.h"
#include "nsIMemoryInfoDumper.h"
#include "nsIMemoryReporter.h"
#include "nsINetworkLinkService.h"
#include "nsIObserverService.h"
#include "nsIParentChannel.h"
#include "nsIPrivateAttributionService.h"
#include "nsIScriptError.h"
#include "nsIScriptSecurityManager.h"
#include "nsIServiceWorkerManager.h"
#include "nsISiteSecurityService.h"
#include "nsIStringBundle.h"
#include "nsITimer.h"
#include "nsIURL.h"
#include "nsIUserIdleService.h"
#include "nsIWebBrowserChrome.h"
#include "nsIXULRuntime.h"
#include "nsPIDNSService.h"
#if defined(MOZ_WIDGET_GTK) || defined(XP_WIN)
# include "nsIconChannel.h"
#endif
#include "XPCSelfHostedShmem.h"
#include "nsFrameLoaderOwner.h"
#include "nsMemoryInfoDumper.h"
#include "nsMemoryReporterManager.h"
#include "nsOpenURIInFrameParams.h"
#include "nsOpenWindowInfo.h"
#include "nsPIWindowWatcher.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 "nsURLHelper.h"
#include "nsWidgetsCID.h"
#include "nsWindowWatcher.h"
#include "prenv.h"
#include "prio.h"
#include "private/pprio.h"
#include "xpcpublic.h"
#ifdef MOZ_WEBRTC
# include "jsapi/WebrtcGlobalParent.h"
#endif
#if defined(XP_MACOSX)
# include "mozilla/AvailableMemoryWatcher.h"
# include "nsMacUtilsImpl.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 "Crypto.h"
#include "mozilla/RemoteSpellCheckEngineParent.h"
#ifdef MOZ_WEBSPEECH
# include "mozilla/dom/SpeechSynthesisParent.h"
#endif
#if defined(MOZ_SANDBOX)
# include "mozilla/SandboxSettings.h"
# if defined(XP_LINUX)
# include "mozilla/SandboxBroker.h"
# include "mozilla/SandboxBrokerPolicyFactory.h"
# include "mozilla/SandboxInfo.h"
# endif
# if defined(XP_MACOSX)
# include "mozilla/Sandbox.h"
# endif
#endif
#ifdef XP_WIN
# include "mozilla/WinDllServices.h"
#endif
#ifdef MOZ_CODE_COVERAGE
# include "mozilla/CodeCoverageHandler.h"
#endif
#ifdef FUZZING_SNAPSHOT
# include "mozilla/fuzzing/IPCFuzzController.h"
#endif
#ifdef ENABLE_WEBDRIVER
# include "nsIMarionette.h"
# include "nsIRemoteAgent.h"
#endif
#include "mozilla/RemoteDecodeUtils.h"
#include "nsIToolkitProfile.h"
#include "nsIToolkitProfileService.h"
#ifdef MOZ_WMF_CDM
# include "mozilla/EMEUtils.h"
# include "nsIWindowsMediaFoundationCDMOriginsListService.h"
namespace mozilla {
class OriginsListLoadCallback final : public nsIOriginsListLoadCallback {
public:
explicit OriginsListLoadCallback(ContentParent* aContentParent)
: mContentParent(aContentParent) {
MOZ_ASSERT(mContentParent);
}
NS_DECL_ISUPPORTS
// nsIOriginsListLoadCallback
NS_IMETHODIMP OnOriginsListLoaded(nsIArray* aEntries) {
if (NS_WARN_IF(!mContentParent)) {
return NS_ERROR_FAILURE;
}
uint32_t length = 0;
nsresult rv = aEntries->GetLength(&length);
if (NS_FAILED(rv)) {
return rv;
}
nsTArray<dom::IPCOriginStatusEntry> ipcEntries;
for (uint32_t i = 0; i < length; ++i) {
nsCOMPtr<nsIOriginStatusEntry> entry;
aEntries->QueryElementAt(i, NS_GET_IID(nsIOriginStatusEntry),
getter_AddRefs(entry));
if (!entry) {
NS_WARNING("OriginsListLoadCallback, skip bad entry?");
continue;
}
nsAutoCString origin;
int32_t status = 0;
entry->GetOrigin(origin);
entry->GetStatus(&status);
dom::IPCOriginStatusEntry ipcEntry(origin, status);
ipcEntries.AppendElement(ipcEntry);
}
(void)mContentParent->SendUpdateMFCDMOriginEntries(ipcEntries);
return NS_OK;
}
private:
~OriginsListLoadCallback() = default;
RefPtr<ContentParent> mContentParent;
};
NS_IMPL_ISUPPORTS(OriginsListLoadCallback, nsIOriginsListLoadCallback)
} // namespace mozilla
#endif
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)
extern mozilla::LazyLogModule sPDMLog;
#define LOGPDM(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
namespace mozilla {
namespace CubebUtils {
extern FileDescriptor CreateAudioIPCConnection(uint32_t aRemotePid);
}
namespace dom {
LazyLogModule gProcessLog("Process");
MOZ_RUNINIT static std::map<RemoteMediaIn, media::MediaCodecsSupported>
sCodecsSupported;
/* static */
uint32_t ContentParent::sMaxContentProcesses = 0;
/* static */
LogModule* ContentParent::GetLog() { return gProcessLog; }
#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 {
(void)mSink->CloseGCLog();
return IPC_OK();
}
virtual mozilla::ipc::IPCResult RecvCloseCCLog() override {
(void)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));
(void)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;
}
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<LinkedList<ContentParent>> ContentParent::sContentParents;
#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
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",
"memory-pressure-stop",
"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,
};
void ContentParent_NotifyUpdatedDictionaries() {
ContentParent::NotifyUpdatedDictionaries();
}
// PreallocateProcess is called by the PreallocatedProcessManager.
// ContentParent then takes this process back within GetNewOrUsedBrowserProcess.
/*static*/ UniqueContentParentKeepAlive ContentParent::MakePreallocProcess() {
RefPtr<ContentParent> process = new ContentParent(PREALLOC_REMOTE_TYPE);
if (NS_WARN_IF(!process->BeginSubprocessLaunch(PROCESS_PRIORITY_PREALLOC))) {
process->LaunchSubprocessReject();
return nullptr;
}
return process->AddKeepAlive(/* aBrowserId */ 0);
}
/*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(MakeAndAddRef<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);
}
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);
}
static bool IsRemoteTypeJitDisabled(const nsACString& aContentProcessType) {
if (!StringEndsWith(aContentProcessType, DISABLE_JIT_REMOTE_TYPE_SUFFIX)) {
return false;
}
auto remoteTypePrefix = RemoteTypePrefix(aContentProcessType);
if (remoteTypePrefix != FISSION_WEB_REMOTE_TYPE &&
remoteTypePrefix != SERVICEWORKER_REMOTE_TYPE &&
remoteTypePrefix != WITH_COOP_COEP_REMOTE_TYPE) {
return false;
}
auto suffixStart =
aContentProcessType.Length() - DISABLE_JIT_REMOTE_TYPE_SUFFIX.Length();
if (suffixStart > 0) {
char priorChar = aContentProcessType[suffixStart - 1];
if (priorChar != '&' && priorChar != '^') {
return false;
}
} else {
return false;
}
return true;
}
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 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
// First let's collect all processes and keep a grip.
AutoTArray<RefPtr<ContentParent>, 32> fixArray;
for (const auto& contentParents : sBrowserContentParents->Values()) {
for (auto* cp : *contentParents) {
fixArray.AppendElement(cp);
}
}
for (const auto& cp : fixArray) {
cp->MaybeBeginShutDown(/* aImmediate */ true,
/* aIgnoreKeepAlivePref */ true);
if (cp->IsDead()) {
// 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,
uint64_t aBrowserId) {
uint32_t maxSelectable =
std::min(static_cast<uint32_t>(aContentParents.Length()),
static_cast<uint32_t>(aMaxContentParents));
uint32_t min = INT_MAX;
RefPtr<ContentParent> candidate;
for (uint32_t i = 0; i < maxSelectable; i++) {
ContentParent* p = aContentParents[i];
MOZ_DIAGNOSTIC_ASSERT(!p->IsDead());
if (p->IsShuttingDown()) {
continue;
}
// Check how many other tabs are already hosted by this process. Ignore
// keepalives without a BrowserId as well as keepalives corresponding to
// `aBrowserId` when doing this calculation.
ThreadsafeContentParentHandle* handle = p->ThreadsafeHandle();
RecursiveMutexAutoLock lock(handle->mMutex);
uint32_t keepAliveCount = handle->mKeepAlivesPerBrowserId.Count();
if (handle->mKeepAlivesPerBrowserId.Contains(0)) {
--keepAliveCount;
}
if (aBrowserId != 0 &&
handle->mKeepAlivesPerBrowserId.Contains(aBrowserId)) {
--keepAliveCount;
}
if (keepAliveCount < min) {
candidate = p;
min = keepAliveCount;
}
}
// 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*/
UniqueContentParentKeepAlive ContentParent::GetUsedBrowserProcess(
const nsACString& aRemoteType, nsTArray<ContentParent*>& aContentParents,
uint32_t aMaxContentParents, bool aPreferUsed, ProcessPriority aPriority,
uint64_t aBrowserId) {
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
AutoRestore ar(sInProcessSelector);
sInProcessSelector = true;
#endif
uint32_t numberOfParents = aContentParents.Length();
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;
}
// Use MinTabSelect to choose a content process unless content process re-use
// has been disabled.
RefPtr<ContentParent> selected;
if (!StaticPrefs::dom_ipc_disableContentProcessReuse() &&
(selected =
MinTabSelect(aContentParents, aMaxContentParents, aBrowserId))) {
if (profiler_thread_is_being_profiled_for_markers()) {
nsPrintfCString marker("Reused process %u",
(unsigned int)selected->ChildID());
PROFILER_MARKER_TEXT("Process", DOM, {}, marker);
}
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("GetUsedProcess: Reused process id=%p childID=%" PRIu64 " for %s",
selected.get(), (uint64_t)selected->ChildID(),
PromiseFlatCString(aRemoteType).get()));
selected->AssertAlive();
return selected->AddKeepAlive(aBrowserId);
}
// Try to take a preallocated process except for certain remote types.
// Note: this process may not have finished launching yet
UniqueContentParentKeepAlive preallocated;
if (aRemoteType != FILE_REMOTE_TYPE &&
aRemoteType != PRIVILEGEDABOUT_REMOTE_TYPE &&
aRemoteType != EXTENSION_REMOTE_TYPE && // Bug 1638119
!IsRemoteTypeJitDisabled(aRemoteType) &&
(preallocated = PreallocatedProcessManager::Take(aRemoteType))) {
MOZ_DIAGNOSTIC_ASSERT(preallocated->GetRemoteType() ==
PREALLOC_REMOTE_TYPE);
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 id=%p childID=%" PRIu64 " for type %s%s",
preallocated.get(), (uint64_t)preallocated->ChildID(),
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);
{
RecursiveMutexAutoLock lock(preallocated->mThreadsafeHandle->mMutex);
preallocated->mThreadsafeHandle->mRemoteType = preallocated->mRemoteType;
}
preallocated->mRemoteTypeIsolationPrincipal =
CreateRemoteTypeIsolationPrincipal(aRemoteType);
preallocated->AddToPool(aContentParents);
// rare, but will happen
if (!preallocated->IsLaunching()) {
// Specialize this process for the appropriate remote type, and activate
// it.
(void)preallocated->SendRemoteType(preallocated->mRemoteType,
preallocated->mProfile);
preallocated->StartRemoteWorkerService();
nsCOMPtr<nsIObserverService> obs =
mozilla::services::GetObserverService();
if (obs) {
nsAutoString cpId;
cpId.AppendInt(static_cast<uint64_t>(preallocated->ChildID()));
obs->NotifyObservers(static_cast<nsIObserver*>(preallocated.get()),
"process-type-set", cpId.get());
preallocated->AssertAlive();
}
}
// NOTE: Make sure to return a keepalive for the requested aBrowserId. The
// keepalive used by the preallocated process manager will be released upon
// returning.
return preallocated->AddKeepAlive(aBrowserId);
}
return nullptr;
}
/*static*/
UniqueContentParentKeepAlive ContentParent::GetNewOrUsedLaunchingBrowserProcess(
const nsACString& aRemoteType, BrowsingContextGroup* aGroup,
ProcessPriority aPriority, bool aPreferUsed, uint64_t aBrowserId) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("GetNewOrUsedProcess for type %s",
PromiseFlatCString(aRemoteType).get()));
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
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.
UniqueContentParentKeepAlive contentParent;
if (aGroup) {
if (RefPtr<ContentParent> candidate = aGroup->GetHostProcess(aRemoteType)) {
MOZ_DIAGNOSTIC_ASSERT(!candidate->IsShuttingDown());
MOZ_LOG(
ContentParent::GetLog(), LogLevel::Debug,
("GetNewOrUsedProcess: Existing host process id=%p childID=%" PRIu64
" (launching %d)",
candidate.get(), (uint64_t)candidate->ChildID(),
candidate->IsLaunching()));
contentParent = candidate->TryAddKeepAlive(aBrowserId);
}
}
nsTArray<ContentParent*>& contentParents = GetOrCreatePool(aRemoteType);
if (!contentParent) {
// No host process. Let's try to re-use an existing process.
uint32_t maxContentParents = GetMaxProcessCount(aRemoteType);
contentParent =
GetUsedBrowserProcess(aRemoteType, contentParents, maxContentParents,
aPreferUsed, aPriority, aBrowserId);
MOZ_DIAGNOSTIC_ASSERT_IF(contentParent, !contentParent->IsShuttingDown());
}
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()));
RefPtr<ContentParent> newCp = new ContentParent(aRemoteType);
if (NS_WARN_IF(!newCp->BeginSubprocessLaunch(aPriority))) {
// Launch aborted because of shutdown. Bailout.
newCp->LaunchSubprocessReject();
return nullptr;
}
contentParent = newCp->AddKeepAlive(aBrowserId);
// 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.get());
// Store this process for future reuse.
contentParent->AddToPool(contentParents);
MOZ_LOG(
ContentParent::GetLog(), LogLevel::Debug,
("GetNewOrUsedProcess: new immediate process id=%p childID=%" PRIu64,
contentParent.get(), (uint64_t)contentParent->ChildID()));
}
// else we have an existing or preallocated process (which may be
// still launching)
contentParent->AssertAlive();
if (aGroup) {
aGroup->EnsureHostProcess(contentParent.get());
}
return contentParent;
}
/*static*/
RefPtr<ContentParent::LaunchPromise>
ContentParent::GetNewOrUsedBrowserProcessAsync(const nsACString& aRemoteType,
BrowsingContextGroup* aGroup,
ProcessPriority aPriority,
bool aPreferUsed,
uint64_t aBrowserId) {
// Obtain a `ContentParent` launched asynchronously.
UniqueContentParentKeepAlive contentParent =
GetNewOrUsedLaunchingBrowserProcess(aRemoteType, aGroup, aPriority,
aPreferUsed, aBrowserId);
if (!contentParent) {
// In case of launch error, stop here.
return LaunchPromise::CreateAndReject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
__func__);
}
return contentParent->WaitForLaunchAsync(aPriority, aBrowserId);
}
/*static*/
UniqueContentParentKeepAlive ContentParent::GetNewOrUsedBrowserProcess(
const nsACString& aRemoteType, BrowsingContextGroup* aGroup,
ProcessPriority aPriority, bool aPreferUsed, uint64_t aBrowserId) {
UniqueContentParentKeepAlive contentParent =
GetNewOrUsedLaunchingBrowserProcess(aRemoteType, aGroup, aPriority,
aPreferUsed, aBrowserId);
if (!contentParent || !contentParent->WaitForLaunchSync(aPriority)) {
// In case of launch error, stop here.
return nullptr;
}
return contentParent;
}
RefPtr<ContentParent::LaunchPromise> ContentParent::WaitForLaunchAsync(
ProcessPriority aPriority, uint64_t aBrowserId) {
MOZ_DIAGNOSTIC_ASSERT(!IsDead());
UniqueContentParentKeepAlive self = AddKeepAlive(aBrowserId);
if (!IsLaunching()) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("WaitForLaunchAsync: launched"));
return LaunchPromise::CreateAndResolve(std::move(self), __func__);
}
// We've started an async content process launch.
glean::dom_contentprocess::launch_is_sync
.EnumGet(glean::dom_contentprocess::LaunchIsSyncLabel::eFalse)
.Add();
// 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 = std::move(self), aPriority](
const ProcessHandlePromise::ResolveOrRejectValue& aValue) mutable {
if (aValue.IsResolve() &&
self->LaunchSubprocessResolve(/* aIsSync = */ false, aPriority)) {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("WaitForLaunchAsync: async, now launched, process id=%p, "
"childID=%" PRIu64,
self.get(), (uint64_t)self->ChildID()));
return LaunchPromise::CreateAndResolve(std::move(self), __func__);
}
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("WaitForLaunchAsync: async, rejected"));
self->LaunchSubprocessReject();
return LaunchPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
});
}
bool ContentParent::WaitForLaunchSync(ProcessPriority aPriority) {
MOZ_DIAGNOSTIC_ASSERT(!IsDead());
if (!IsLaunching()) {
return true;
}
// We've started a sync content process launch.
glean::dom_contentprocess::launch_is_sync
.EnumGet(glean::dom_contentprocess::LaunchIsSyncLabel::eTrue)
.Add();
// 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)) {
return true;
}
// In case of failure.
LaunchSubprocessReject();
return false;
}
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::RecvCreateClipboardContentAnalysis(
Endpoint<PClipboardContentAnalysisParent>&& aParentEndpoint) {
if (mClipboardContentAnalysisCreated) {
return IPC_FAIL(this, "ClipboardContentAnalysisParent already created");
}
mClipboardContentAnalysisCreated = true;
if (!mClipboardContentAnalysisThread) {
nsresult rv = NS_NewNamedThread(
"BkgrndClipboard", getter_AddRefs(mClipboardContentAnalysisThread));
if (NS_WARN_IF(NS_FAILED(rv))) {
return IPC_FAIL(this, "NS_NewNamedThread failed");
}
}
// Bind the new endpoint to the backgroundClipboardContentAnalysis thread.
mClipboardContentAnalysisThread->Dispatch(
NS_NewRunnableFunction(
"Create ClipboardContentAnalysisParent",
[threadsafeHandle = RefPtr{ThreadsafeHandle()},
parentEndpoint = std::move(aParentEndpoint)]() mutable {
// Use a threadsafe handle here, so that it can
// be used to validate that the WindowContext comes from the
// correct content process.
RefPtr<ClipboardContentAnalysisParent> actor =
new ClipboardContentAnalysisParent(std::move(threadsafeHandle));
parentEndpoint.Bind(actor);
}),
NS_DISPATCH_NORMAL);
return IPC_OK();
}
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(EndpointProcInfo::Current(),
OtherEndpointProcInfo(), &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();
}
IPCResult ContentParent::RecvAttributionEvent(
const nsACString& aHost, PrivateAttributionImpressionType aType,
uint32_t aIndex, const nsAString& aAd, const nsACString& aTargetHost) {
nsCOMPtr<nsIPrivateAttributionService> pa =
components::PrivateAttribution::Service();
if (NS_WARN_IF(!pa)) {
return IPC_OK();
}
pa->OnAttributionEvent(aHost, GetEnumString(aType), aIndex, aAd, aTargetHost);
return IPC_OK();
}
IPCResult ContentParent::RecvAttributionConversion(
const nsACString& aHost, const nsAString& aTask, uint32_t aHistogramSize,
const Maybe<uint32_t>& aLookbackDays,
const Maybe<PrivateAttributionImpressionType>& aImpressionType,
const nsTArray<nsString>& aAds, const nsTArray<nsCString>& aSourceHosts) {
nsCOMPtr<nsIPrivateAttributionService> pa =
components::PrivateAttribution::Service();
if (NS_WARN_IF(!pa)) {
return IPC_OK();
}
pa->OnAttributionConversion(
aHost, aTask, aHistogramSize, aLookbackDays.valueOr(0),
aImpressionType ? GetEnumString(*aImpressionType) : EmptyCString(), aAds,
aSourceHosts);
return IPC_OK();
}
static mozilla::glean::security::FissionPrincipalsExtra
GetAndLogPrincipalValidationFailure(nsIPrincipal* aPrincipal,
const char* aMethod) {
mozilla::glean::security::FissionPrincipalsExtra extra = {};
extra.value = Some(aMethod);
if (!aPrincipal) {
extra.principaltype = Some("NullPtr"_ns);
} else if (aPrincipal->IsSystemPrincipal()) {
extra.principaltype = Some("SystemPrincipal"_ns);
} else if (aPrincipal->GetIsExpandedPrincipal()) {
extra.principaltype = Some("ExpandedPrincipal"_ns);
} else if (aPrincipal->GetIsContentPrincipal()) {
extra.principaltype = Some("ContentPrincipal"_ns);
// NOTE: Pull the scheme from the origin, rather than from our URI.
// This can be relevant in cases where the URI is unwrapped to determine the
// origin (e.g. view-source).
nsAutoCString scheme;
nsAutoCString origin;
MOZ_ALWAYS_SUCCEEDS(aPrincipal->GetOriginNoSuffix(origin));
MOZ_ALWAYS_SUCCEEDS(net_ExtractURLScheme(origin, scheme));
extra.scheme = Some(scheme);
} else {
extra.principaltype = Some("Unknown"_ns);
}
// NOTE: This log can contain PII (the full origin) as it's local-only.
if (MOZ_LOG_TEST(ContentParent::GetLog(), LogLevel::Error)) {
nsAutoCString origin;
if (aPrincipal) {
MOZ_ALWAYS_SUCCEEDS(aPrincipal->GetOrigin(origin));
}
MOZ_LOG(ContentParent::GetLog(), LogLevel::Error,
(" Receiving unexpected Principal (%s) within %s", origin.get(),
aMethod));
}
return extra;
}
/*static*/
void ContentParent::LogAndAssertFailedPrincipalValidationInfo(
nsIPrincipal* aPrincipal, const char* aMethod) {
auto gleanExtra = GetAndLogPrincipalValidationFailure(aPrincipal, aMethod);
// Do not send telemetry when chrome-debugging is enabled
bool isChromeDebuggingEnabled =
Preferences::GetBool("devtools.chrome.enabled", false);
if (!isChromeDebuggingEnabled) {
glean::security::fission_principals.Record(mozilla::Some(gleanExtra));
}
#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
}
/*static*/
mozilla::ipc::IPCResult ContentParent::PrincipalValidationIpcFail(
nsIPrincipal* aPrincipal, mozilla::ipc::IProtocol* aActor,
const char* aMethod) {
// Always get the telemetry extra, as we'll use it for building the crash
// report as well.
auto gleanExtra = GetAndLogPrincipalValidationFailure(aPrincipal, aMethod);
// Do not send telemetry when chrome-debugging is enabled
bool isChromeDebuggingEnabled =
Preferences::GetBool("devtools.chrome.enabled", false);
if (!isChromeDebuggingEnabled) {
glean::security::fission_principals.Record(mozilla::Some(gleanExtra));
}
return IPC_FAIL_UNSAFE_PRINTF(
aActor, "Invalid principal (%s%s%s) in %s%s",
gleanExtra.principaltype->get(), gleanExtra.scheme ? " " : "",
gleanExtra.scheme ? gleanExtra.scheme->get() : "", aMethod,
isChromeDebuggingEnabled ? " (chrome debug on)" : "");
}
bool ContentParent::ValidatePrincipal(
nsIPrincipal* aPrincipal,
const EnumSet<ValidatePrincipalOptions>& aOptions) {
return ValidatePrincipalCouldPotentiallyBeLoadedBy(aPrincipal, mRemoteType,
aOptions);
}
/*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");
// Don't bother creating new content browsers after entering shutdown. This
// could lead to starting a new content process, which may significantly delay
// shutdown, and the content is unlikely to be displayed.
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
NS_WARNING("Ignoring remote browser creation request during shutdown");
return nullptr;
}
nsAutoCString remoteType(aRemoteType);
if (remoteType.IsEmpty()) {
remoteType = SharedWebRemoteType(aBrowsingContext->OriginAttributesRef());
}
TabId tabId(nsContentUtils::GenerateTabId());
nsIDocShell* docShell = GetOpenerDocShellHelper(aFrameElement);
TabId openerTabId;
if (docShell) {
openerTabId = BrowserParent::GetTabIdFrom(docShell);
}
// Hold a KeepAlive on our ContentParent throughout this function. Once the
// `BrowserParent` has been created, it can be cleared, as that BrowserParent
// will establish its own KeepAlive.
UniqueContentParentKeepAlive constructorSender;
MOZ_RELEASE_ASSERT(XRE_IsParentProcess(),
"Cannot allocate BrowserParent in content process");
if (aOpenerContentParent && !aOpenerContentParent->IsShuttingDown()) {
constructorSender =
aOpenerContentParent->AddKeepAlive(aBrowsingContext->BrowserId());
} else {
constructorSender = GetNewOrUsedBrowserProcess(
remoteType, aBrowsingContext->Group(), PROCESS_PRIORITY_FOREGROUND,
/* aPreferUsed */ false,
/* aBrowserId */ aBrowsingContext->BrowserId());
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.get());
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.get(), tabId, aContext,
aBrowsingContext->Canonical(), chromeFlags);
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
if (NS_WARN_IF(!cpm)) {
return nullptr;
}
cpm->RegisterRemoteFrame(browserParent);
// Open a remote endpoint for our PBrowser actor.
ManagedEndpoint<PBrowserChild> childEp =
constructorSender->OpenPBrowserEndpoint(browserParent);
if (NS_WARN_IF(!childEp.IsValid())) {
return nullptr;
}
nsCOMPtr<nsIPrincipal> initialPrincipal =
NullPrincipal::Create(aBrowsingContext->OriginAttributesRef());
WindowGlobalInit windowInit = WindowGlobalActor::AboutBlankInitializer(
aBrowsingContext, initialPrincipal);
RefPtr<WindowGlobalParent> windowParent =
WindowGlobalParent::CreateDisconnected(windowInit,
constructorSender.get());
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;
}
windowParent->Init();
// 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);
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) {
for (auto* cp : AllProcesses(eLive)) {
AutoTArray<StringBundleDescriptor, 1> array;
array.AppendElement(StringBundleDescriptor(aBundle.bundleURL(),
aBundle.mapHandle().Clone()));
(void)cp->SendRegisterStringBundles(std::move(array));
}
}
void ContentParent::BroadcastShmBlockAdded(uint32_t aGeneration,
uint32_t aIndex) {
auto* pfl = gfxPlatformFontList::PlatformFontList();
for (auto* cp : AllProcesses(eLive)) {
ReadOnlySharedMemoryHandle handle =
pfl->ShareShmBlockToProcess(aIndex, cp->Pid());
if (!handle.IsValid()) {
// If something went wrong here, we just skip it; the child will need to
// request the block as needed, at some performance cost.
continue;
}
(void)cp->SendFontListShmBlockAdded(aGeneration, aIndex, std::move(handle));
}
}
void ContentParent::BroadcastThemeUpdate(widget::ThemeChangeKind aKind) {
const FullLookAndFeel& lnf = *RemoteLookAndFeel::ExtractData();
for (auto* cp : AllProcesses(eLive)) {
(void)cp->SendThemeChanged(lnf, aKind);
}
}
/*static */
void ContentParent::BroadcastMediaCodecsSupportedUpdate(
RemoteMediaIn aLocation, const media::MediaCodecsSupported& aSupported) {
// Update processes and print the support info from the given location.
sCodecsSupported[aLocation] = aSupported;
for (auto* cp : AllProcesses(eAll)) {
(void)cp->SendUpdateMediaCodecsSupported(aLocation, aSupported);
}
nsCString supportString;
media::MCSInfo::GetMediaCodecsSupportedString(supportString, aSupported);
LOGPDM("Broadcast support from '%s', support=%s",
RemoteMediaInToStr(aLocation), supportString.get());
// Merge incoming support with existing support list from other locations
media::MCSInfo::AddSupport(aSupported);
#ifdef MOZ_WMF
if (aSupported.contains(media::MediaCodecsSupport::AV1LackOfExtension)) {
glean::media::wmf_codec_no_extension.Get("av1"_ns).Set(true);
}
if (aSupported.contains(media::MediaCodecsSupport::HEVCLackOfExtension)) {
glean::media::wmf_codec_no_extension.Get("hevc"_ns).Set(true);
}
#endif
auto fullSupport = media::MCSInfo::GetSupport();
// Generate + save FULL support string for display in about:support
supportString.Truncate();
media::MCSInfo::GetMediaCodecsSupportedString(supportString, fullSupport);
if (nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service()) {
gfxInfo->SetCodecSupportInfo(supportString);
}
}
const nsACString& ContentParent::GetRemoteType() const { return mRemoteType; }
static StaticRefPtr<nsIAsyncShutdownClient> sXPCOMShutdownClient;
static StaticRefPtr<nsIAsyncShutdownClient> sProfileBeforeChangeClient;
static StaticRefPtr<nsIAsyncShutdownClient> sAppShutdownConfirmedClient;
void ContentParent::Init() {
MOZ_ASSERT(sXPCOMShutdownClient);
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
size_t length = std::size(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. We skip this when the only consumer is ePdfOutput. In that mode,
// the service exists purely to build the accessibility tree for a document
// being printed in some specific process and unrelated content processes
// shouldn't bring up accessibility.
if (GetAccService() && !nsAccessibilityService::IsOnlyForPdfOutput()) {
(void)SendActivateA11y(nsAccessibilityService::GetActiveCacheDomains());
}
#endif // #ifdef ACCESSIBILITY
(void)SendInitProfiler(ProfilerParent::CreateForProcess(OtherPid()));
RefPtr<GeckoMediaPluginServiceParent> gmps(
GeckoMediaPluginServiceParent::GetSingleton());
if (gmps) {
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) {
(void)NS_WARN_IF(!SendPreferenceUpdate(pref));
}
mQueuedPrefs.Clear();
(void)SendInitNextGenLocalStorageEnabled(NextGenLocalStorageEnabled());
// sending only the remote settings schemes to the content process
nsCOMPtr<nsIIOService> io(do_GetIOService());
MOZ_ASSERT(io, "No IO service for SimpleURI scheme broadcast to content");
nsTArray<nsCString> remoteSchemes;
MOZ_ALWAYS_SUCCEEDS(io->GetSimpleURIUnknownRemoteSchemes(remoteSchemes));
(void)SendSimpleURIUnknownRemoteSchemes(std::move(remoteSchemes));
}
void ContentParent::AsyncSendShutDownMessage() {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
("AsyncSendShutDownMessage id=%p, childID=%" PRIu64, this,
(uint64_t)this->ChildID()));
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(IsDead());
if (mShutdownPending || !CanSend()) {
// Don't bother dispatching a runnable if shutdown is already pending or the
// channel has already been disconnected, as it won't do anything.
// We could be very late in shutdown and unable to dispatch.
return;
}
// 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 childID=%" PRIu64 " pid=%d - %s at %s(%d)",
aSelf, (uint64_t)aSelf->ChildID(), aSelf->Pid(), aMsg, aFile, aLine));
}
#else
(void)aSelf;
(void)aMsg;
(void)aFile;
(void)aLine;
#endif
}
bool ContentParent::ShutDownProcess(ShutDownMethod aMethod) {
bool result = false;
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("ShutDownProcess: id=%p childID=%" PRIu64, this,
(uint64_t)this->ChildID()));
// 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);
// If we did not earlier, let's signal the shutdown to JS now.
SignalImpendingShutdownToContentJS();
// Adjust the QoS priorities for shutdown, if they exist.
if (StaticPrefs::threads_use_low_power_enabled() &&
StaticPrefs::
threads_lower_mainthread_priority_in_background_enabled()) {
SetMainThreadQoSPriority(nsIThread::QOS_PRIORITY_NORMAL);
}
// Send the shutdown message with normal priority.
if (SendShutdown()) {
MaybeLogBlockShutdownDiagnostics(
this, "ShutDownProcess: Sent shutdown message.", __FILE__,
__LINE__);
mShutdownPending = true;
// We start the kill timer only after we asked our process to
// shutdown.
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 (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
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->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(!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(!mIsSignaledImpendingShutdown);
MOZ_DIAGNOSTIC_ASSERT(!IsDead());
}
void ContentParent::RemoveFromList() {
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);
}
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 id=%p childID=%" PRIu64 " as dead", this,
(uint64_t)this->ChildID()));
MOZ_DIAGNOSTIC_ASSERT(!sInProcessSelector);
RemoveFromList();
// Flag shutdown has started for us to our threadsafe handle.
{
// Depending on how we get here, the lock might or might not be set.
RecursiveMutexAutoLock lock(mThreadsafeHandle->mMutex);
mThreadsafeHandle->mShutdownStarted = true;
}
// Prevent this process from being re-used.
PreallocatedProcessManager::Erase(this);
#if defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_PROFILE_GENERATE)
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.
//
// The exception is in MOZ_PROFILE_GENERATE builds where we must allow the
// process to shutdown cleanly so that profile data can be dumped. This is
// okay as we will not reach our process limit during the profile run.
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
mLifecycleState = LifecycleState::DEAD;
}
void ContentParent::ProcessingError(Result aCode, const char* aReason) {
if (MsgDropped == aCode) {
return;
}
// Other errors are big deals.
#ifndef FUZZING
KillHard(aReason);
#endif
if (CanSend()) {
GetIPCChannel()->InduceConnectionError();
}
}
void ContentParent::ActorDestroy(ActorDestroyReason why) {
#ifdef FUZZING_SNAPSHOT
MOZ_FUZZING_IPC_DROP_PEER("ContentParent::ActorDestroy");
#endif
if (mSendShutdownTimer) {
mSendShutdownTimer->Cancel();
mSendShutdownTimer = nullptr;
}
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(CLOSE_CHANNEL);
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
size_t length = std::size(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();
#ifdef MOZ_WMF_CDM
if (mOriginsListCallback) {
nsCOMPtr<nsIWindowsMediaFoundationCDMOriginsListService> rsService =
do_GetService("@mozilla.org/media/wmfcdm-origins-list;1");
if (rsService) {
rsService->RemoveCallback(mOriginsListCallback);
}
mOriginsListCallback = nullptr;
}
#endif
// 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) {
glean::subprocess::abnormal_abort.Get("content"_ns).Add(1);
props->SetPropertyAsBool(u"abnormal"_ns, true);
nsAutoString dumpID;
nsAutoCString processType;
// 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();
}
if (mCrashReporter->HasMinidump()) {
dumpID = mCrashReporter->MinidumpID();
}
processType = mCrashReporter->ProcessType();
} else {
HandleOrphanedMinidump(&dumpID);
processType = XRE_GeckoProcessTypeToString(GeckoProcessType_Content);
}
if (!dumpID.IsEmpty()) {
props->SetPropertyAsAString(u"dumpID"_ns, dumpID);
}
if (!processType.IsEmpty()) {
props->SetPropertyAsACString(u"processType"_ns, processType);
}
}
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 id=%p "
"mSubprocess id=%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 id=%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.
BlobURLProtocolHandler::RemoveDataEntriesPerContentParent(ChildID());
#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();
mPendingParentProcessChannelHandles.Clear();
}
UniqueContentParentKeepAlive ContentParent::TryAddKeepAlive(
uint64_t aBrowserId) {
UniqueContentParentKeepAlive keepAlive =
UniqueContentParentKeepAliveFromThreadsafe(
mThreadsafeHandle->TryAddKeepAlive(aBrowserId));
// If we successfully added a KeepAlive, we can cancel any pending
// MaybeBeginShutDown call (as it will no longer begin process shutdown due to
// outstanding KeepAlives).
// This is just an optimization and the MaybeBeginShutDown call will be a
// no-op if it is called with the KeepAlive held.
if (keepAlive && mMaybeBeginShutdownRunner) {
mMaybeBeginShutdownRunner->Cancel();
mMaybeBeginShutdownRunner = nullptr;
}
return keepAlive;
}
UniqueContentParentKeepAlive ContentParent::AddKeepAlive(uint64_t aBrowserId) {
UniqueContentParentKeepAlive keepAlive = TryAddKeepAlive(aBrowserId);
MOZ_DIAGNOSTIC_ASSERT(keepAlive, "ContentParent is already dead");
return keepAlive;
}
void ContentParent::RemoveKeepAlive(uint64_t aBrowserId) {
AssertIsOnMainThread();
{
RecursiveMutexAutoLock lock(mThreadsafeHandle->mMutex);
auto entry = mThreadsafeHandle->mKeepAlivesPerBrowserId.Lookup(aBrowserId);
MOZ_RELEASE_ASSERT(entry, "No KeepAlive for this BrowserId");
if (!--entry.Data()) {
entry.Remove();
}
}
MaybeBeginShutDown();
}
void ContentParent::MaybeBeginShutDown(bool aImmediate,
bool aIgnoreKeepAlivePref) {
AssertIsOnMainThread();
MOZ_ASSERT(!aIgnoreKeepAlivePref || aImmediate,
"aIgnoreKeepAlivePref requires aImmediate");
// Don't bother waiting, even if `aImmediate` is not true, if the process
// can no longer be re-used (e.g. because it is dead, or we're in shutdown).
bool immediate =
aImmediate || IsDead() ||
AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed) ||
StaticPrefs::dom_ipc_processReuse_unusedGraceMs() == 0 ||
IsRemoteTypeJitDisabled(mRemoteType);
// Clean up any scheduled idle task unless we schedule a new one.
auto cancelIdleTask = MakeScopeExit([&] {
if (mMaybeBeginShutdownRunner) {
mMaybeBeginShutdownRunner->Cancel();
mMaybeBeginShutdownRunner = nullptr;
}
});
{
RecursiveMutexAutoLock lock(mThreadsafeHandle->mMutex);
// If we still have keepalives or are still launching, we're not shutting
// down. Return.
if (IsLaunching() ||
!mThreadsafeHandle->mKeepAlivesPerBrowserId.IsEmpty()) {
return;
}
// If we're not in main process shutdown, 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.
if (!aIgnoreKeepAlivePref && mIsInPool && !mRemoteType.Contains('=') &&
!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
auto* contentParents = sBrowserContentParents->Get(mRemoteType);
MOZ_RELEASE_ASSERT(
contentParents,
"mIsInPool, yet no entry for mRemoteType in sBrowserContentParents?");
nsAutoCString keepAlivePref("dom.ipc.keepProcessesAlive.");
keepAlivePref.Append(mRemoteType);
int32_t processesToKeepAlive = 0;
if (NS_SUCCEEDED(Preferences::GetInt(keepAlivePref.get(),
&processesToKeepAlive)) &&
contentParents->Length() <=
static_cast<size_t>(processesToKeepAlive)) {
// We're keeping this process alive even though there are no keepalives
// for it due to the keepalive pref.
return;
}
}
if (immediate) {
// We're not keeping this process alive, begin shutdown.
mThreadsafeHandle->mShutdownStarted = true;
}
}
// If we're not beginning shutdown immediately, make sure an idle task runner
// is scheduled to call us back. This delay is intended to avoid unnecessary
// process churn when a process becomes momentarily unused (which can happen
// frequently when running tests).
if (!immediate) {
// We want an idle task to call us back, don't cancel it.
cancelIdleTask.release();
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("MaybeBeginShutDown(%d) would begin shutdown, %s", OtherChildID(),
mMaybeBeginShutdownRunner ? "already delayed" : "delaying"));
if (!mMaybeBeginShutdownRunner) {
TimeDuration startDelay = TimeDuration::FromMilliseconds(
StaticPrefs::dom_ipc_processReuse_unusedGraceMs());
TimeDuration maxDelay = startDelay + TimeDuration::FromSeconds(1);
mMaybeBeginShutdownRunner = IdleTaskRunner::Create(
[self = RefPtr{this}](TimeStamp) -> bool {
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("MaybeBeginShutDown(%d) resuming after delay",
self->OtherChildID()));
self->MaybeBeginShutDown(/* aImmediate */ true);
return true;
},
"ContentParent::IdleMaybeBeginShutdown"_ns, startDelay, maxDelay,
/* aMinimumUsefulBudget */ TimeDuration::FromMilliseconds(3),
/* aRepeating */ false, [] { return false; });
}
return;
}
MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
("MaybeBeginShutDown(%d) shutdown starting (%u bps)", OtherChildID(),
ManagedPBrowserParent().Count()));
MarkAsDead();
SignalImpendingShutdownToContentJS();
if (ManagedPBrowserParent().Count() > 0) {
// We still have PBrowser instances which have not been shut down.
// Wait for them to be destroyed before we follow-through and shut down this
// process, but start a shutdown timer to kill them if this takes too long.
StartSendShutdownTimer();
} else {
// All tabs are dead, we can fully begin shutting down.
AsyncSendShutDownMessage();
}
}
void ContentParent::StartSendShutdownTimer() {
if (mSendShutdownTimer || !CanSend()) {
return;
}
uint32_t timeoutSecs = StaticPrefs::dom_ipc_tabs_shutdownTimeoutSecs();
if (timeoutSecs > 0) {
NS_NewTimerWithFuncCallback(
getter_AddRefs(mSendShutdownTimer),
ContentParent::SendShutdownTimerCallback, this, timeoutSecs * 1000,
nsITimer::TYPE_ONE_SHOT,
"dom::ContentParent::StartSendShutdownTimer"_ns);
MOZ_ASSERT(mSendShutdownTimer);
}
}
void ContentParent::StartForceKillTimer() {
if (mForceKillTimer || !CanSend()) {
return;
}
uint32_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"_ns);
MOZ_ASSERT(mForceKillTimer);
}
}
already_AddRefed<TestShellParent> ContentParent::CreateTestShell() {
RefPtr<TestShellParent> actor = new TestShellParent();
if (!SendPTestShellConstructor(actor)) {
return nullptr;
}
return actor.forget();
}
bool ContentParent::DestroyTestShell(TestShellParent* aTestShell) {
return PTestShellParent::Send__delete__(aTestShell);
}
already_AddRefed<TestShellParent> ContentParent::GetTestShellSingleton() {
PTestShellParent* p = LoneManagedOrNullAsserts(ManagedPTestShellParent());
return do_AddRef(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")) {
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 non-packaged builds,
// these are used to whitelist the repo dir and object dir respectively.
nsresult rv;
if (!mozilla::IsPackagedBuild()) {
// 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;
(void)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;
(void)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;
}
geckoargs::ChildProcessArgs 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.
if (xpc::SelfHostedShmem::SelfHostedUseSharedMemory()) {
::mozilla::ipc::ExportSharedJSInit(*mSubprocess, extraArgs);
}
// 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, "");
geckoargs::sSafeMode.Put(gSafeMode, extraArgs);
geckoargs::sDisableJit.Put(IsRemoteTypeJitDisabled(mRemoteType), extraArgs);
#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
if (IsContentSandboxEnabled()) {
AppendSandboxParams(extraArgs.mArgs);
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() && widget::GdkIsX11Display()) {
mSubprocess->SetEnv("MOZ_HEADLESS", "1");
}
#endif
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
(void)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) {
glean::dom_contentprocess::sync_launch.AccumulateRawDuration(
TimeStamp::Now() - mLaunchTS);
} else {
glean::dom_contentprocess::launch_total.AccumulateRawDuration(
TimeStamp::Now() - mLaunchTS);
glean::dom_contentprocess::launch_mainthread.AccumulateRawDuration(
(mLaunchYieldTS - mLaunchTS) + (TimeStamp::Now() - launchResumeTS));
}
mLaunchResolvedOk = true;
return true;
}
static bool IsFileContent(const nsACString& aRemoteType) {
return aRemoteType == FILE_REMOTE_TYPE;
}
ContentParent::ContentParent(const nsACString& aRemoteType)
: mSubprocess(new GeckoChildProcessHost(GeckoProcessType_Content,
IsFileContent(aRemoteType))),
mLaunchTS(TimeStamp::Now()),
mLaunchYieldTS(mLaunchTS),
mIsAPreallocBlocker(false),
mRemoteType(aRemoteType),
mChildID(mSubprocess->GetChildID()),
mGeolocationWatchID(-1),
mThreadsafeHandle(
new ThreadsafeContentParentHandle(this, mChildID, mRemoteType)),
mLifecycleState(LifecycleState::LAUNCHING),
mIsForBrowser(!mRemoteType.IsEmpty()),
mCalledClose(false),
mCalledKillHard(false),
mCreatedPairedMinidumps(false),
mShutdownPending(false),
mLaunchResolved(false),
mLaunchResolvedOk(false),
mIsUntrusted(false),
mIsRemoteInputEventQueueEnabled(false),
mIsInputPriorityEventEnabled(false),
mIsInPool(false),
mGMPCreated(false),
mClipboardContentAnalysisCreated(false),
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
mBlockShutdownCalled(false),
#endif
mHangMonitorActor(nullptr) {
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
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
MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose,
("CreateSubprocess: ContentParent id=%p mSubprocess id=%p childID=%d",
this, mSubprocess, mSubprocess->GetChildID()));
}
ContentParent::~ContentParent() {
if (mSendShutdownTimer) {
mSendShutdownTimer->Cancel();
}
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 id=%p childID=%" PRIu64
" destruction",
this, (uint64_t)this->ChildID()));
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 id=%p childID=%" PRIu64
" mSubprocess id=%p handle "
"%" PRIuPTR,
this, (uint64_t)this->ChildID(), mSubprocess,
mSubprocess ? (uintptr_t)mSubprocess->GetChildProcessHandle() : -1));
mSubprocess->Destroy();
}
}
static nsIDNSService::ResolverMode TrrModeFromPref() {
auto mode =
static_cast<nsIDNSService::ResolverMode>(StaticPrefs::network_trr_mode());
if (mode > nsIDNSService::MODE_TRROFF) {
mode = nsIDNSService::MODE_TRROFF;
}
return mode;
}
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: id=%p, childID=%" PRIu64, (void*)this,
(uint64_t)this->ChildID()));
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());
OSPreferences::GetInstance()->GetSystemLocales(xpcomInit.sysLocales());
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).
auto initialData = MakeNotNull<RefPtr<StructuredCloneData>>(
JS::StructuredCloneScope::DifferentProcess,
StructuredCloneHolder::TransferringNotSupported);
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<ReadOnlySharedMemoryHandle> 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->GetOriginalURI();
} else {
xpcomInit.userContentSheetURL() = nullptr;
}
// 1. Ensure the GPU process is ready, as we know we are not yet in shutdown.
GPUProcessManager* gpm = GPUProcessManager::Get();
nsresult gpuReadyRv = gpm->EnsureGPUReady();
// 2. Build ContentDeviceData first, as it may affect some gfxVars.
gfxPlatform::GetPlatform()->BuildContentDeviceData(
&xpcomInit.contentDeviceData());
// 3. Gather non-default gfxVars.
xpcomInit.gfxNonDefaultVarUpdates() = gfxVars::FetchNonDefaultVars();
// 4. 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) {
(void)SendUpdateMediaCodecsSupported(location, supported);
}
// 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<ReadOnlySharedMemoryHandle> sharedUASheetHandle;
uintptr_t sharedUASheetAddress = sheetCache->GetSharedMemoryAddress();
if (ReadOnlySharedMemoryHandle 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;
xpcomInit.trrModeFromPref() = TrrModeFromPref();
(void)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.
(void)SendAppInfo(version, buildID, name, UAName, ID, vendor, sourceURL,
updateURL);
}
MOZ_LOG(
ContentParent::GetLog(), LogLevel::Verbose,
("FOG transport enabled: %s",
StaticPrefs::telemetry_fog_enable_fog_transport() ? "true" : "false"));
if (StaticPrefs::telemetry_fog_enable_fog_transport()) {
// Set up FOGTransport
Endpoint<PFOGTransportParent> parentEndpoint;
Endpoint<PFOGTransportChild> childEndpoint;
MOZ_ALWAYS_SUCCEEDS(
glean::PFOGTransport::CreateEndpoints(&parentEndpoint, &childEndpoint));
(void)SendCreateFOGTransport(std::move(childEndpoint));
nsCOMPtr<nsISerialEventTarget> queue =
glean::FOGTransportParent::GetQueue();
mFOGTransportParentActor = MakeRefPtr<glean::FOGTransportParent>();
queue->Dispatch(
NS_NewRunnableFunction("Bind FOGTransportParent",
[parentEndpoint = std::move(parentEndpoint),
actor = mFOGTransportParentActor]() mutable {
parentEndpoint.Bind(actor);
}),
NS_DISPATCH_NORMAL);
}
// 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.
(void)SendRemoteType(mRemoteType, mProfile);
if (mRemoteType != PREALLOC_REMOTE_TYPE) {
StartRemoteWorkerService();
}
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.)
Endpoint<PCompositorManagerChild> compositor;
Endpoint<PImageBridgeChild> imageBridge;
Endpoint<PVRManagerChild> vrBridge;
Endpoint<PRemoteMediaManagerChild> videoManager;
AutoTArray<uint32_t, 4> namespaces;
if (NS_SUCCEEDED(gpuReadyRv) &&
gpm->CreateContentBridges(OtherEndpointProcInfo(), &compositor,
&imageBridge, &vrBridge, &videoManager,
mChildID, &namespaces)) {
(void)SendInitRendering(std::move(compositor), std::move(imageBridge),
std::move(vrBridge), std::move(videoManager),
namespaces);
} else {
// This can fail if we've already started shutting down the compositor
// thread. See Bug 1562763 comment 8. On Android this can fail we are are
// put in the background, making it unlikely the content process will remain
// alive for much longer, but if it does, we will do the necessary
// operations for InitRendering in ReinitRendering when the GPU process is
// available.
if (gpuReadyRv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN) {
return false;
}
}
gpm->AddListener(this);
if (StaticPrefs::media_rdd_process_enabled()) {
// Ensure the RDD process has been started.
RDDProcessManager* rdd = RDDProcessManager::Get();
rdd->LaunchRDDProcess();
}
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()) {
(void)SendLoadAndRegisterSheet(sheet->GetOriginalURI(),
nsIStyleSheetService::AGENT_SHEET);
}
for (StyleSheet* sheet : *sheetService->UserStyleSheets()) {
(void)SendLoadAndRegisterSheet(sheet->GetOriginalURI(),
nsIStyleSheetService::USER_SHEET);
}
for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
(void)SendLoadAndRegisterSheet(sheet->GetOriginalURI(),
nsIStyleSheetService::AUTHOR_SHEET);
}
}
#ifdef MOZ_WMF_CDM
if (!mOriginsListCallback && IsMediaFoundationCDMPlaybackEnabled()) {
mOriginsListCallback = new OriginsListLoadCallback(this);
nsCOMPtr<nsIWindowsMediaFoundationCDMOriginsListService> rsService =
do_GetService("@mozilla.org/media/wmfcdm-origins-list;1");
if (rsService) {
rsService->SetCallback(mOriginsListCallback);
}
}
#endif
#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 nsCString& aPartitionKey,
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
// AboutToLoadDocumentForChild and RemoteWorkerManager.
if (!BlobURLProtocolHandler::IsBlobURLBroadcastPrincipal(aPrincipal)) {
return true;
}
registrations.AppendElement(
BlobURLRegistrationData(nsCString(aURI), WrapNotNull(aPrincipal),
nsCString(aPartitionKey), aRevoked));
nsresult rv = TransmitPermissionsForPrincipal(aPrincipal);
(void)NS_WARN_IF(NS_FAILED(rv));
return true;
});
if (!registrations.IsEmpty()) {
(void)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);
(void)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) {
return -1;
}
auto pid = mSubprocess->GetChildProcessId();
if (pid == 0) {
return -1;
}
return ReleaseAssertedCast<int32_t>(pid);
}
void ContentParent::OnCompositorUnexpectedShutdown() {
GPUProcessManager* gpm = GPUProcessManager::Get();
Endpoint<PCompositorManagerChild> compositor;
Endpoint<PImageBridgeChild> imageBridge;
Endpoint<PVRManagerChild> vrBridge;
Endpoint<PRemoteMediaManagerChild> videoManager;
AutoTArray<uint32_t, 4> namespaces;
if (!gpm->CreateContentBridges(OtherEndpointProcInfo(), &compositor,
&imageBridge, &vrBridge, &videoManager,
mChildID, &namespaces)) {
return;
}
(void)SendReinitRendering(std::move(compositor), std::move(imageBridge),
std::move(vrBridge), std::move(videoManager),
namespaces);
}
void ContentParent::OnCompositorDeviceReset() {
(void)SendReinitRenderingForDeviceReset();
}
void ContentParent::MaybeEnableRemoteInputEventQueue() {
MOZ_ASSERT(!mIsRemoteInputEventQueueEnabled);
mIsRemoteInputEventQueueEnabled = true;
(void)SendSetInputEventQueueEnabled();
SetInputPriorityEventEnabled(true);
}
void ContentParent::SetInputPriorityEventEnabled(bool aEnabled) {
if (!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.
(void)SendSuspendInputEventQueue();
(void)SendFlushInputEventQueue();
(void)SendResumeInputEventQueue();
}
void ContentParent::OnVarChanged(const nsTArray<GfxVarUpdate>& aVar) {
if (!CanSend()) {
return;
}
(void)SendVarUpdate(aVar);
}
mozilla::ipc::IPCResult ContentParent::RecvSetClipboard(
const IPCTransferable& aTransferable,
const nsIClipboard::ClipboardType& aWhichClipboard,
const MaybeDiscarded<WindowContext>& aRequestingWindowContext) {
// aRequestingPrincipal is allowed to be nullptr here.
if (!ValidatePrincipal(aTransferable.dataPrincipal(),
{ValidatePrincipalOptions::AllowNullPtr,
ValidatePrincipalOptions::AllowExpanded,
ValidatePrincipalOptions::AllowSystem})) {
return PrincipalValidationIpcFail(aTransferable.dataPrincipal(), this,
__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(
aTransferable, true /* aAddDataFlavor */, trans,
true /* aFilterUnknownFlavors */);
NS_ENSURE_SUCCESS(rv, IPC_OK());
// OK if this is null
RefPtr<WindowGlobalParent> window;
if (!aRequestingWindowContext.IsDiscarded()) {
window = aRequestingWindowContext.get_canonical();
}
clipboard->SetData(trans, nullptr, aWhichClipboard, window);
return IPC_OK();
}
/* static */ Result<nsCOMPtr<nsITransferable>, nsresult>
ContentParent::CreateClipboardTransferable(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);
}
template <typename GetClipboardDataFunction>
nsresult ContentParent::GetClipboardDataInternal(
nsTArray<nsCString>&& aTypes,
const nsIClipboard::ClipboardType& aWhichClipboard,
const MaybeDiscarded<WindowContext>& aRequestingWindowContext,
IPCTransferableDataOrError* aResult, GetClipboardDataFunction&& aFunction) {
// We expect content processes to always pass a non-null window so Content
// Analysis can analyze it. (if Content Analysis is active)
// There may be some cases when a window is closing, etc., in
// which case returning no clipboard content should not be a problem.
if (aRequestingWindowContext.IsDiscarded()) {
*aResult = NS_ERROR_NOT_AVAILABLE;
return NS_OK;
}
if (aRequestingWindowContext.IsNull()) {
return NS_ERROR_INVALID_ARG;
}
RefPtr<WindowGlobalParent> window = aRequestingWindowContext.get_canonical();
if (window && window->GetContentParent() != this) {
return NS_ERROR_ILLEGAL_VALUE;
}
nsresult rv;
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
if (NS_FAILED(rv)) {
*aResult = rv;
return NS_OK;
}
auto result = CreateClipboardTransferable(aTypes);
if (result.isErr()) {
*aResult = result.unwrapErr();
return NS_OK;
}
nsCOMPtr<nsITransferable> transferable = result.unwrap();
rv = aFunction(clipboard, transferable, aWhichClipboard, window);
if (NS_FAILED(rv)) {
*aResult = rv;
return NS_OK;
}
IPCTransferableData transferableData;
nsContentUtils::TransferableToIPCTransferableData(
transferable, &transferableData, true, this);
*aResult = std::move(transferableData);
return NS_OK;
}
mozilla::ipc::IPCResult ContentParent::RecvGetClipboard(
nsTArray<nsCString>&& aTypes,
const nsIClipboard::ClipboardType& aWhichClipboard,
const MaybeDiscarded<WindowContext>& aRequestingWindowContext,
IPCTransferableDataOrError* aTransferableDataOrError) {
nsresult rv = GetClipboardDataInternal(
std::move(aTypes), aWhichClipboard, aRequestingWindowContext,
aTransferableDataOrError,
[](nsIClipboard* clipboard, nsITransferable* trans, ClipboardType type,
WindowGlobalParent* window) {
return clipboard->GetData(trans, type, window);
});
if (rv == NS_ERROR_INVALID_ARG) {
return IPC_FAIL(this, "passed null window to RecvGetClipboard()");
}
if (rv == NS_ERROR_ILLEGAL_VALUE) {
return IPC_FAIL(
this, "attempt to paste into WindowContext loaded in another process");
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvGetClipboardDataIfSmallerThan(
nsTArray<nsCString>&& aTypes, uint64_t aThreshold,
const nsIClipboard::ClipboardType& aWhichClipboard,
const MaybeDiscarded<WindowContext>& aRequestingWindowContext,
GetClipboardDataIfSmallerThanResolver&& aResolver) {
IPCTransferableDataOrError result;
nsresult rv = GetClipboardDataInternal(
std::move(aTypes), aWhichClipboard, aRequestingWindowContext, &result,
[aThreshold](nsIClipboard* clipboard, nsITransferable* trans,
ClipboardType type, WindowGlobalParent* window) {
return clipboard->GetDataIfSmallerThanNative(trans, aThreshold, type,
window);
});
if (rv == NS_ERROR_INVALID_ARG) {
return IPC_FAIL(
this, "passed null window to RecvGetClipboardDataIfSmallerThan()");
}
if (rv == NS_ERROR_ILLEGAL_VALUE) {
return IPC_FAIL(
this, "attempt to paste into WindowContext loaded in another process");
}
aResolver(std::move(result));
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvEmptyClipboard(
const nsIClipboard::ClipboardType& 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 nsIClipboard::ClipboardType& 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();
}
namespace {
static Result<ClipboardReadRequest, nsresult> CreateClipboardReadRequest(
ContentParent& aContentParent,
nsIClipboardDataSnapshot& aClipboardDataSnapshot) {
nsTArray<nsCString> flavors;
nsresult rv = aClipboardDataSnapshot.GetFlavorList(flavors);
if (NS_FAILED(rv)) {
return Err(rv);
}
auto requestParent = MakeNotNull<RefPtr<ClipboardReadRequestParent>>(
&aContentParent, &aClipboardDataSnapshot);
// Open a remote endpoint for our PClipboardReadRequest actor.
ManagedEndpoint<PClipboardReadRequestChild> childEndpoint =
aContentParent.OpenPClipboardReadRequestEndpoint(requestParent);
if (NS_WARN_IF(!childEndpoint.IsValid())) {
return Err(NS_ERROR_FAILURE);
}
return ClipboardReadRequest(std::move(childEndpoint), std::move(flavors));
}
class ClipboardGetCallback final : public nsIClipboardGetDataSnapshotCallback {
public:
ClipboardGetCallback(
ContentParent* aContentParent,
ContentParent::GetClipboardDataSnapshotResolver&& aResolver)
: mContentParent(aContentParent), mResolver(std::move(aResolver)) {}
// This object will never be held by a cycle-collected object, so it doesn't
// need to be cycle-collected despite holding alive cycle-collected objects.
NS_DECL_ISUPPORTS
// nsIClipboardGetDataSnapshotCallback
NS_IMETHOD OnSuccess(
nsIClipboardDataSnapshot* aClipboardDataSnapshot) override {
MOZ_ASSERT(mContentParent);
MOZ_ASSERT(aClipboardDataSnapshot);
auto result =
CreateClipboardReadRequest(*mContentParent, *aClipboardDataSnapshot);
if (result.isErr()) {
return OnError(result.unwrapErr());
}
mResolver(result.unwrap());
return NS_OK;
}
NS_IMETHOD OnError(nsresult aResult) override {
mResolver(aResult);
return NS_OK;
}
protected:
~ClipboardGetCallback() = default;
RefPtr<ContentParent> mContentParent;
ContentParent::GetClipboardDataSnapshotResolver mResolver;
};
NS_IMPL_ISUPPORTS(ClipboardGetCallback, nsIClipboardGetDataSnapshotCallback)
} // namespace
mozilla::ipc::IPCResult ContentParent::RecvGetClipboardDataSnapshot(
nsTArray<nsCString>&& aTypes,
const nsIClipboard::ClipboardType& aWhichClipboard,
const MaybeDiscarded<WindowContext>& aRequestingWindowContext,
mozilla::NotNull<nsIPrincipal*> aRequestingPrincipal,
GetClipboardDataSnapshotResolver&& aResolver) {
if (!ValidatePrincipal(aRequestingPrincipal,
{ValidatePrincipalOptions::AllowSystem,
ValidatePrincipalOptions::AllowExpanded})) {
return PrincipalValidationIpcFail(aRequestingPrincipal, this, __func__);
}
// If the requesting context has been discarded, cancel the paste.
if (aRequestingWindowContext.IsDiscarded()) {
aResolver(NS_ERROR_NOT_AVAILABLE);
return IPC_OK();
}
RefPtr<WindowGlobalParent> requestingWindow =
aRequestingWindowContext.get_canonical();
if (requestingWindow && requestingWindow->GetContentParent() != this) {
return IPC_FAIL(
this, "attempt to paste into WindowContext loaded in another process");
}
nsresult rv;
// Retrieve clipboard
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
if (NS_FAILED(rv)) {
aResolver(rv);
return IPC_OK();
}
auto callback = MakeRefPtr<ClipboardGetCallback>(this, std::move(aResolver));
rv = clipboard->GetDataSnapshot(aTypes, aWhichClipboard, requestingWindow,
aRequestingPrincipal, callback);
if (NS_FAILED(rv)) {
callback->OnError(rv);
return IPC_OK();
}
return IPC_OK();
}
mozilla::ipc::IPCResult ContentParent::RecvGetClipboardDataSnapshotSync(
nsTArray<nsCString>&& aTypes,
const nsIClipboard::ClipboardType& aWhichClipboard,
const MaybeDiscarded<WindowContext>& aRequestingWindowContext,
ClipboardReadRequestOrError* aRequestOrError) {
// If the requesting context has been discarded, cancel the paste.
if (aRequestingWindowContext.IsDiscarded()) {
*aRequestOrError = NS_ERROR_FAILURE;
return IPC_OK();
}
RefPtr<WindowGlobalParent> requestingWindow =
aRequestingWindowContext.get_canonical();
if (requestingWindow && requestingWindow->GetContentParent() != this) {
return IPC_FAIL(
this, "attempt to paste into WindowContext loaded in another process");
}
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID));
if (!clipboard) {
*aRequestOrError = NS_ERROR_FAILURE;
return IPC_OK();
}
nsCOMPtr<nsIClipboardDataSnapshot> clipboardDataSnapshot;
nsresult rv =
clipboard->GetDataSnapshotSync(aTypes, aWhichClipboard, requestingWindow,
getter_AddRefs(clipboardDataSnapshot));
if (NS_FAILED(rv)) {
*aRequestOrError = rv;
return IPC_OK();
}
auto result = CreateClipboardReadRequest(*this, *clipboardDataSnapshot);
if (result.isErr()) {
*aRequestOrError = result.unwrapErr();
return IPC_OK();
}
*aRequestOrError = result.unwrap();
return IPC_OK();
}
already_AddRefed<PClipboardWriteRequestParent>
ContentParent::AllocPClipboardWriteRequestParent(
const nsIClipboard::ClipboardType& aClipboardType,
const MaybeDiscarded<WindowContext>& aSettingWindowContext) {
WindowContext* settingWindowContext = nullptr;
if (!aSettingWindowContext.IsDiscarded()) {
settingWindowContext = aSettingWindowContext.get();
}
RefPtr<ClipboardWriteRequestParent> request =
MakeAndAddRef<ClipboardWriteRequestParent>(this);
request->Init(aClipboardType, settingWindowContext);
return request.forget();
}
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();
}
// Explicitly zero-initialize the output to ensure we do not send
// uninitialized heap memory over IPC to the child process to avoid heap data
// leakage.
bits->InsertElementsAt(0, aIconSize * aIconSize * 4, 0);
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 id=%p childID=%" PRIu64 ": Removing Blocker for %s",
this, (uint64_t)this->ChildID(), 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();
}
nsID ContentParent::AddParentProcessChannelHandle(
ParentProcessChannelHandle* aHandle) {
nsID uuid = nsID::GenerateUUID();
mPendingParentProcessChannelHandles.InsertOrUpdate(uuid, aHandle);
return uuid;
}
already_AddRefed<ParentProcessChannelHandle>
ContentParent::ReadParentProcessChannelHandle(const nsID& aUuid) {
RefPtr<ParentProcessChannelHandle> handle =
mPendingParentProcessChannelHandles.Get(aUuid);
return handle.forget();
}
mozilla::ipc::IPCResult ContentParent::RecvDropParentProcessChannelHandle(
const nsID& aUuid) {
mPendingParentProcessChannelHandles.Remove(aUuid);
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);
(void)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.
RefPtr<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
// 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);
{
RecursiveMutexAutoLock lock(mThreadsafeHandle->mMutex);
mThreadsafeHandle->mShutdownStarted = true;
}
// Our real shutdown has not yet started. Just notify the impending
// shutdown and eventually cancel content JS.
SignalImpendingShutdownToContentJS();
if (sAppShutdownConfirmedClient) {
(void)sAppShutdownConfirmedClient->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=%" PRIu64, (uint64_t)ChildID());
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);
}
}
if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
rv = svc->GetAppShutdownConfirmed(getter_AddRefs(client));
if (NS_SUCCEEDED(rv)) {
sAppShutdownConfirmedClient = client.forget();
ClearOnShutdown(&sAppShutdownConfirmedClient);
}
}
}
}
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 (sAppShutdownConfirmedClient) {
sAppShutdownConfirmedClient->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) {
(void)sXPCOMShutdownClient->RemoveBlocker(this);
}
if (sProfileBeforeChangeClient) {
(void)sProfileBeforeChangeClient->RemoveBlocker(this);
}
if (sAppShutdownConfirmedClient) {
(void)sAppShutdownConfirmedClient->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());
// This check is a bit of a hack. We want to avoid sending excessive
// preference updates to subprocesses for performance reasons, but we
// currently don't have a great mechanism for doing so. (See Bug 1819714)
// We're going to hijack the sanitization mechanism to accomplish our goal
// but it imposes the following complications:
// 1) It doesn't avoid sending anything to other (non-web-content)
// subprocesses so we're not getting any perf gains there
// 2) It confuses the subprocesses w.r.t. sanitization. The point of
// sending a preference update of a sanitized preference is so that
// content process knows when it's asked to resolve a sanitized
// preference, and it can send telemetry and/or crash. With this
// change, a sanitized pref that is created during the browser session
// will not be sent to the content process, and therefore the content
// process won't know it should telemetry/crash on access - it'll just
// silently fail to resolve it. After browser restart, the sanitized
// pref will be populated into the content process via the shared pref
// map and _then_ if it is accessed, the content process will crash.
// We're seeing good telemetry/crash rates right now, so we're okay with
// this limitation.
if (pref.isSanitized()) {
return NS_OK;
}
if (IsInitialized()) {
MOZ_ASSERT(mQueuedPrefs.IsEmpty());
if (!SendPreferenceUpdate(pref)) {
return NS_ERROR_NOT_AVAILABLE;
}
} else {
MOZ_ASSERT(!IsDead());
mQueuedPrefs.AppendElement(pref);
}
return NS_OK;
}
if (!IsAlive()) {
return NS_OK;
}
// listening for memory pressure event
if (!strcmp(aTopic, "memory-pressure")) {
(void)SendMemoryPressure(nsDependentString(aData));
} else if (!strcmp(aTopic, "memory-pressure-stop")) {
(void)SendMemoryPressureStop();
} else if (!strcmp(aTopic, "application-background")) {
(void)SendApplicationBackground();
} else if (!strcmp(aTopic, "application-foreground")) {
(void)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;
}
} else if (!strcmp(aTopic, "child-gc-request")) {
(void)SendGarbageCollect();
} else if (!strcmp(aTopic, "child-cc-request")) {
(void)SendCycleCollect();
} else if (!strcmp(aTopic, "child-mmu-request")) {
(void)SendMinimizeMemoryUsage();
} else if (!strcmp(aTopic, "child-ghost-request")) {
(void)SendUnlinkGhosts();
} else if (!strcmp(aTopic, "last-pb-context-exited")) {
(void)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 fully initiated in chrome process.
MOZ_ASSERT(!nsAccessibilityService::IsOnlyForPdfOutput());
(void)SendActivateA11y(nsAccessibilityService::GetActiveCacheDomains());
} else if (*aData == '0') {
// If possible, shut down accessibility in content process when
// accessibility gets shutdown in chrome process.
(void)SendShutdownA11y();
}
}
#endif
else if (!strcmp(aTopic, "cacheservice:empty-cache")) {
(void)SendNotifyEmptyHTTPCache();
} else if (!strcmp(aTopic, "intl:app-locales-changed")) {
nsTArray<nsCString> appLocales;
LocaleService::GetInstance()->GetAppLocalesAsBCP47(appLocales);
(void)SendUpdateAppLocales(appLocales);
} else if (!strcmp(aTopic, "intl:requested-locales-changed")) {
nsTArray<nsCString> requestedLocales;
LocaleService::GetInstance()->GetRequestedLocales(requestedLocales);
(void)SendUpdateRequestedLocales(requestedLocales);
} else if (!strcmp(aTopic, "cookie-changed") ||
!strcmp(aTopic, "private-cookie-changed")) {
MOZ_ASSERT(aSubject, "cookie changed notification must have subject.");
nsCOMPtr<nsICookieNotification> notification = do_QueryInterface(aSubject);
MOZ_ASSERT(notification,
"cookie changed notification must have nsICookieNotification.");
if (!notification) {
return NS_OK;
}
nsICookieNotification::Action action = notification->GetAction();
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);
MOZ_ASSERT(mCookieInContentListCache.IsEmpty());
if (action == nsICookieNotification::COOKIES_BATCH_DELETED) {
nsCOMPtr<nsIArray> cookieList;
DebugOnly<nsresult> rv =
notification->GetBatchDeletedCookies(getter_AddRefs(cookieList));
NS_ASSERTION(NS_SUCCEEDED(rv) && cookieList, "couldn't get cookie list");
cs->RemoveBatchDeletedCookies(cookieList);
return NS_OK;
}
if (action == nsICookieNotification::ALL_COOKIES_CLEARED) {
cs->RemoveAll();
return NS_OK;
}
// Do not push these cookie updates to the same process they originated
// from.
if (cs->ProcessingCookie()) {
return NS_OK;
}
nsCOMPtr<nsICookie> xpcCookie;
nsresult rv = notification->GetCookie(getter_AddRefs(xpcCookie));
NS_ASSERTION(NS_SUCCEEDED(rv) && xpcCookie, "couldn't get cookie");
// only broadcast the cookie change to content processes that need it
const Cookie& cookie = xpcCookie->AsCookie();
// do not send cookie if content process does not have similar cookie
if (!cs->ContentProcessHasCookie(cookie)) {
return NS_OK;
}
UniqueFreePtr<nsID> operationID;
rv = notification->GetOperationID(TempPtrToSetter(&operationID));
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_OK;
}
if (action == nsICookieNotification::COOKIE_DELETED) {
cs->RemoveCookie(cookie, operationID.get());
} else if (action == nsICookieNotification::COOKIE_ADDED ||
action == nsICookieNotification::COOKIE_CHANGED) {
cs->AddCookie(cookie, operationID.get());
}
} else if (!strcmp(aTopic, NS_NETWORK_LINK_TYPE_TOPIC)) {
UpdateNetworkLinkType();
} else if (!strcmp(aTopic, "network:socket-process-crashed")) {
(void)SendSocketProcessCrashed();
} else if (!strcmp(aTopic, DEFAULT_TIMEZONE_CHANGED_OBSERVER_TOPIC)) {
(void)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);
(void)SendSetTRRMode(mode, TrrModeFromPref());
}
return NS_OK;
}
uint32_t ContentParent::UpdateNetworkLinkType() {
uint32_t linkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
nsresult rv;
nsCOMPtr<nsINetworkLinkService> nls =
do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv)) {
rv = nls->GetLinkType(&linkType);
if (NS_SUCCEEDED(rv)) {
(void)SendNetworkLinkTypeChange(linkType);
}
}
return 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.opener().AsParent());
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 = aTarget.GetParentWindowContext();
if (NS_WARN_IF(!targetEmbedder) ||
NS_WARN_IF(targetEmbedder->GetContentParent() != aCp)) {
return false;
}