/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "UtilityProcessManager.h"
#include "JSOracleParent.h"
#include "mozilla/ipc/UtilityProcessHost.h"
#include "mozilla/MemoryReportingProcess.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/SyncRunnable.h" // for LaunchUtilityProcess
#include "mozilla/ipc/UtilityProcessParent.h"
#include "mozilla/ipc/UtilityAudioDecoderChild.h"
#include "mozilla/ipc/UtilityAudioDecoderParent.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/ipc/UtilityProcessSandboxing.h"
#include "mozilla/ipc/ProcessChild.h"
#include "nsAppRunner.h"
#include "nsContentUtils.h"
#ifdef XP_WIN
# include "mozilla/dom/WindowsUtilsParent.h"
# include "mozilla/widget/filedialog/WinFileDialogParent.h"
#endif
#include "mozilla/GeckoArgs.h"
namespace mozilla::ipc {
extern LazyLogModule gUtilityProcessLog;
#define LOGD(...) MOZ_LOG(gUtilityProcessLog, LogLevel::Debug, (__VA_ARGS__))
static StaticRefPtr<UtilityProcessManager> sSingleton;
static bool sXPCOMShutdown = false ;
bool UtilityProcessManager::IsShutdown() const {
MOZ_ASSERT(NS_IsMainThread());
return sXPCOMShutdown || !sSingleton;
}
RefPtr<UtilityProcessManager> UtilityProcessManager::GetSingleton() {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
if (!sXPCOMShutdown && sSingleton == nullptr) {
sSingleton = new UtilityProcessManager();
sSingleton->Init();
}
return sSingleton;
}
RefPtr<UtilityProcessManager> UtilityProcessManager::GetIfExists() {
MOZ_ASSERT(NS_IsMainThread());
return sSingleton;
}
UtilityProcessManager::UtilityProcessManager() {
LOGD("[%p] UtilityProcessManager::UtilityProcessManager" , this );
}
void UtilityProcessManager::Init() {
// Start listening for pref changes so we can
// forward them to the process once it is running.
mObserver = new Observer(this );
nsContentUtils::RegisterShutdownObserver(mObserver);
Preferences::AddStrongObserver(mObserver, "" );
}
UtilityProcessManager::~UtilityProcessManager() {
LOGD("[%p] UtilityProcessManager::~UtilityProcessManager" , this );
// The Utility process should ALL have already been shut down.
MOZ_ASSERT(NoMoreProcesses());
}
NS_IMPL_ISUPPORTS(UtilityProcessManager::Observer, nsIObserver);
UtilityProcessManager::Observer::Observer(UtilityProcessManager* aManager)
: mManager(aManager) {}
NS_IMETHODIMP
UtilityProcessManager::Observer::Observe(nsISupports* aSubject,
const char * aTopic,
const char16_t* aData) {
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
mManager->OnXPCOMShutdown();
} else if (!strcmp(aTopic, "nsPref:changed" )) {
mManager->OnPreferenceChange(aData);
}
return NS_OK;
}
void UtilityProcessManager::OnXPCOMShutdown() {
LOGD("[%p] UtilityProcessManager::OnXPCOMShutdown" , this );
MOZ_ASSERT(NS_IsMainThread());
sXPCOMShutdown = true ;
nsContentUtils::UnregisterShutdownObserver(mObserver);
CleanShutdownAllProcesses();
}
void UtilityProcessManager::OnPreferenceChange(const char16_t* aData) {
MOZ_ASSERT(NS_IsMainThread());
if (NoMoreProcesses()) {
// Process hasn't been launched yet
return ;
}
// We know prefs are ASCII here.
NS_LossyConvertUTF16toASCII strData(aData);
mozilla::dom::Pref pref(strData, /* isLocked */ false,
/* isSanitized */ false, Nothing(), Nothing());
Preferences::GetPreference(&pref, GeckoProcessType_Utility,
/* remoteType */ ""_ns);
for (auto & p : mProcesses) {
if (!p) {
continue ;
}
if (p->mProcessParent) {
Unused << p->mProcessParent->SendPreferenceUpdate(pref);
} else if (IsProcessLaunching(p->mSandbox)) {
p->mQueuedPrefs.AppendElement(pref);
}
}
}
RefPtr<UtilityProcessManager::ProcessFields> UtilityProcessManager::GetProcess(
SandboxingKind aSandbox) {
if (!mProcesses[aSandbox]) {
return nullptr;
}
return mProcesses[aSandbox];
}
RefPtr<UtilityProcessManager::SharedLaunchPromise<Ok>>
UtilityProcessManager::LaunchProcess(SandboxingKind aSandbox) {
LOGD("[%p] UtilityProcessManager::LaunchProcess SandboxingKind=%" PRIu64,
this , aSandbox);
using RetPromise = SharedLaunchPromise<Ok>;
MOZ_ASSERT(NS_IsMainThread());
if (IsShutdown()) {
NS_WARNING("Reject early LaunchProcess() for Shutdown" );
return RetPromise::CreateAndReject(
LaunchError("UPM::LaunchProcess(): IsShutdown()" ), __func__);
}
RefPtr<ProcessFields> p = GetProcess(aSandbox);
if (p && p->mNumProcessAttempts) {
// We failed to start the Utility process earlier, abort now.
NS_WARNING("Reject LaunchProcess() for earlier mNumProcessAttempts" );
return RetPromise::CreateAndReject(
LaunchError("UPM::LaunchProcess(): p->mNumProcessAttempts" ), __func__);
}
if (p && p->mLaunchPromise && p->mProcess) {
return p->mLaunchPromise;
}
if (!p) {
p = new ProcessFields(aSandbox);
mProcesses[aSandbox] = p;
}
geckoargs::ChildProcessArgs extraArgs;
ProcessChild::AddPlatformBuildID(extraArgs);
geckoargs::sSandboxingKind.Put(aSandbox, extraArgs);
// The subprocess is launched asynchronously, so we
// wait for the promise to be resolved to acquire the IPDL actor.
p->mProcess = new UtilityProcessHost(aSandbox, this );
if (!p->mProcess->Launch(std::move(extraArgs))) {
p->mNumProcessAttempts++;
DestroyProcess(aSandbox);
NS_WARNING("Reject LaunchProcess() for mNumProcessAttempts++" );
return RetPromise::CreateAndReject(
LaunchError("UPM::LaunchProcess(): mNumProcessAttempts++" ), __func__);
}
RefPtr<UtilityProcessManager> self = this ;
p->mLaunchPromise = p->mProcess->LaunchPromise()->Then(
GetMainThreadSerialEventTarget(), __func__,
[self, p, aSandbox](Ok) -> RefPtr<RetPromise> {
if (self->IsShutdown()) {
NS_WARNING(
"Reject LaunchProcess() after LaunchPromise() for Shutdown" );
return RetPromise::CreateAndReject(
LaunchError("UPM::LaunchProcess(): post-await IsShutdown()" ),
__func__);
}
if (self->IsProcessDestroyed(aSandbox)) {
NS_WARNING(
"Reject LaunchProcess() after LaunchPromise() for destroyed "
"process" );
return RetPromise::CreateAndReject(
LaunchError(
"UPM::LaunchProcess(): post-await IsProcessDestroyed()" ),
__func__);
}
p->mProcessParent = p->mProcess->GetActor();
// Flush any pref updates that happened during
// launch and weren't included in the blobs set
// up in LaunchUtilityProcess.
for (const mozilla::dom::Pref& pref : p->mQueuedPrefs) {
Unused << NS_WARN_IF(!p->mProcessParent->SendPreferenceUpdate(pref));
}
p->mQueuedPrefs.Clear();
CrashReporter::RecordAnnotationCString(
CrashReporter::Annotation::UtilityProcessStatus, "Running" );
return RetPromise::CreateAndResolve(Ok{}, __func__);
},
[self, p, aSandbox](LaunchError error) {
if (GetSingleton()) {
p->mNumProcessAttempts++;
self->DestroyProcess(aSandbox);
}
NS_WARNING("Reject LaunchProcess() for LaunchPromise() rejection" );
return RetPromise::CreateAndReject(std::move(error), __func__);
});
return p->mLaunchPromise;
}
template <typename Actor>
RefPtr<UtilityProcessManager::LaunchPromise<Ok>>
UtilityProcessManager::StartUtility(RefPtr<Actor> aActor,
SandboxingKind aSandbox) {
using RetPromise = LaunchPromise<Ok>;
LOGD(
"[%p] UtilityProcessManager::StartUtility actor=%p "
"SandboxingKind=%" PRIu64,
this , aActor.get(), aSandbox);
TimeStamp utilityStart = TimeStamp::Now();
if (!aActor) {
MOZ_ASSERT(false , "Actor singleton failure" );
return RetPromise::CreateAndReject(
LaunchError("UPM::StartUtility: aActor is null" ), __func__);
}
if (aActor->CanSend()) {
// Actor has already been setup, so we:
// - know the process has been launched
// - the ipc actors are ready
PROFILER_MARKER_TEXT(
"UtilityProcessManager::StartUtility" , IPC,
MarkerOptions(MarkerTiming::InstantNow()),
nsPrintfCString("SandboxingKind=%" PRIu64 " aActor->CanSend()" ,
aSandbox));
return RetPromise::CreateAndResolve(Ok{}, __func__);
}
RefPtr<UtilityProcessManager> self = this ;
return LaunchProcess(aSandbox)->Then(
GetMainThreadSerialEventTarget(), __func__,
[self, aActor, aSandbox, utilityStart]() -> RefPtr<RetPromise> {
RefPtr<UtilityProcessParent> utilityParent =
self->GetProcessParent(aSandbox);
if (!utilityParent) {
NS_WARNING("Missing parent in StartUtility" );
return RetPromise::CreateAndReject(
LaunchError("UPM::GetProcessParent" ), __func__);
}
// It is possible if multiple processes concurrently request a utility
// actor that the previous CanSend() check returned false for both but
// that by the time we have started our process for real, one of them
// has already been able to establish the IPC connection and thus we
// would perform more than one Open() call.
//
// The tests within browser_utility_multipleAudio.js should be able to
// catch that behavior.
if (!aActor->CanSend()) {
nsresult rv = aActor->BindToUtilityProcess(utilityParent);
if (NS_FAILED(rv)) {
MOZ_ASSERT(false , "Protocol endpoints failure" );
return RetPromise::CreateAndReject(
LaunchError("BindToUtilityProcess" , rv), __func__);
}
MOZ_DIAGNOSTIC_ASSERT(aActor->CanSend(), "IPC established for actor" );
self->RegisterActor(utilityParent, aActor->GetActorName());
}
PROFILER_MARKER_TEXT(
"UtilityProcessManager::StartUtility" , IPC,
MarkerOptions(MarkerTiming::IntervalUntilNowFrom(utilityStart)),
nsPrintfCString("SandboxingKind=%" PRIu64 " Resolve" , aSandbox));
return RetPromise::CreateAndResolve(Ok{}, __func__);
},
[self, aSandbox, utilityStart](LaunchError const & error) {
NS_WARNING("Reject StartUtility() for LaunchProcess() rejection" );
if (!self->IsShutdown()) {
NS_WARNING("Reject StartUtility() when !IsShutdown()" );
}
PROFILER_MARKER_TEXT(
"UtilityProcessManager::StartUtility" , IPC,
MarkerOptions(MarkerTiming::IntervalUntilNowFrom(utilityStart)),
nsPrintfCString("SandboxingKind=%" PRIu64 " Reject" , aSandbox));
return RetPromise::CreateAndReject(error, __func__);
});
}
RefPtr<UtilityProcessManager::StartRemoteDecodingUtilityPromise>
UtilityProcessManager::StartProcessForRemoteMediaDecoding(
EndpointProcInfo aOtherProcess, dom::ContentParentId aChildId,
SandboxingKind aSandbox) {
using RetPromise = StartRemoteDecodingUtilityPromise;
// Not supported kinds.
if (aSandbox != SandboxingKind::GENERIC_UTILITY
#ifdef MOZ_APPLEMEDIA
&& aSandbox != SandboxingKind::UTILITY_AUDIO_DECODING_APPLE_MEDIA
#endif
#ifdef XP_WIN
&& aSandbox != SandboxingKind::UTILITY_AUDIO_DECODING_WMF
#endif
#ifdef MOZ_WMF_MEDIA_ENGINE
&& aSandbox != SandboxingKind::MF_MEDIA_ENGINE_CDM
#endif
) {
return RetPromise::CreateAndReject(
LaunchError("Start...MediaDecoding: bad sandbox type" ), __func__);
}
TimeStamp remoteDecodingStart = TimeStamp::Now();
RefPtr<UtilityProcessManager> self = this ;
RefPtr<UtilityAudioDecoderChild> uadc =
UtilityAudioDecoderChild::GetSingleton(aSandbox);
MOZ_ASSERT(uadc, "Unable to get a singleton for UtilityAudioDecoderChild" );
return StartUtility(uadc, aSandbox)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[self, uadc, aOtherProcess, aChildId, aSandbox,
remoteDecodingStart]() {
RefPtr<UtilityProcessParent> parent =
self->GetProcessParent(aSandbox);
if (!parent) {
NS_WARNING("UtilityAudioDecoderParent lost in the middle" );
return RetPromise::CreateAndReject(
LaunchError("Start...MediaDecoding: parent lost" ), __func__);
}
if (!uadc->CanSend()) {
NS_WARNING("UtilityAudioDecoderChild lost in the middle" );
return RetPromise::CreateAndReject(
LaunchError("Start...MediaDecoding: child lost" ), __func__);
}
EndpointProcInfo process = parent->OtherEndpointProcInfo();
Endpoint<PRemoteDecoderManagerChild> childPipe;
Endpoint<PRemoteDecoderManagerParent> parentPipe;
if (nsresult const rv = PRemoteDecoderManager::CreateEndpoints(
process, aOtherProcess, &parentPipe, &childPipe);
NS_FAILED(rv)) {
MOZ_ASSERT(false , "Could not create content remote decoder" );
return RetPromise::CreateAndReject(
LaunchError("PRemoteDecoderManager::CreateEndpoints" , rv),
__func__);
}
if (!uadc->SendNewContentRemoteDecoderManager(std::move(parentPipe),
aChildId)) {
MOZ_ASSERT(false , "SendNewContentRemoteDecoderManager failure" );
return RetPromise::CreateAndReject(
LaunchError("UADC::SendNewCRDM" ), __func__);
}
#ifdef MOZ_WMF_MEDIA_ENGINE
if (aSandbox == SandboxingKind::MF_MEDIA_ENGINE_CDM &&
!uadc->CreateVideoBridge()) {
MOZ_ASSERT(false , "Failed to create video bridge" );
return RetPromise::CreateAndReject(
LaunchError("UADC::CreateVideoBridge" ), __func__);
}
#endif
PROFILER_MARKER_TEXT(
"UtilityProcessManager::StartProcessForRemoteMediaDecoding" ,
MEDIA,
MarkerOptions(
MarkerTiming::IntervalUntilNowFrom(remoteDecodingStart)),
"Resolve" _ns);
return RetPromise::CreateAndResolve(std::move(childPipe), __func__);
},
[self, remoteDecodingStart](LaunchError&& error) {
NS_WARNING(
"Reject StartProcessForRemoteMediaDecoding() for "
"StartUtility() rejection" );
PROFILER_MARKER_TEXT(
"UtilityProcessManager::StartProcessForRemoteMediaDecoding" ,
MEDIA,
MarkerOptions(
MarkerTiming::IntervalUntilNowFrom(remoteDecodingStart)),
"Reject" _ns);
return RetPromise::CreateAndReject(std::move(error), __func__);
});
}
RefPtr<UtilityProcessManager::JSOraclePromise>
UtilityProcessManager::StartJSOracle(dom::JSOracleParent* aParent) {
using RetPromise = JSOraclePromise;
return StartUtility(RefPtr{aParent}, SandboxingKind::GENERIC_UTILITY)
->Then(
GetCurrentSerialEventTarget(), __func__,
[]() { return RetPromise::CreateAndResolve(true , __func__); },
[](LaunchError const &) {
return RetPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
__func__);
});
}
#ifdef XP_WIN
// Windows Utils
RefPtr<UtilityProcessManager::WindowsUtilsPromise>
UtilityProcessManager::GetWindowsUtilsPromise() {
TimeStamp windowsUtilsStart = TimeStamp::Now();
RefPtr<UtilityProcessManager> self = this ;
if (!mWindowsUtils) {
mWindowsUtils = new dom::WindowsUtilsParent();
}
RefPtr<dom::WindowsUtilsParent> wup = mWindowsUtils;
MOZ_ASSERT(wup, "Unable to get a singleton for WindowsUtils" );
return StartUtility(wup, SandboxingKind::WINDOWS_UTILS)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[self, wup, windowsUtilsStart]() {
if (!wup->CanSend()) {
MOZ_ASSERT(false , "WindowsUtilsParent can't send" );
return WindowsUtilsPromise::CreateAndReject(
LaunchError("GetWindowsUtilsPromise: !wup->CanSend()" ),
__func__);
}
PROFILER_MARKER_TEXT(
"UtilityProcessManager::GetWindowsUtilsPromise" , OTHER,
MarkerOptions(
MarkerTiming::IntervalUntilNowFrom(windowsUtilsStart)),
"Resolve" _ns);
return WindowsUtilsPromise::CreateAndResolve(wup, __func__);
},
[self, windowsUtilsStart](LaunchError&& error) {
NS_WARNING("StartUtility rejected promise for PWindowsUtils" );
PROFILER_MARKER_TEXT(
"UtilityProcessManager::GetWindowsUtilsPromise" , OTHER,
MarkerOptions(
MarkerTiming::IntervalUntilNowFrom(windowsUtilsStart)),
"Reject" _ns);
return WindowsUtilsPromise::CreateAndReject(std::move(error),
__func__);
});
}
void UtilityProcessManager::ReleaseWindowsUtils() { mWindowsUtils = nullptr; }
RefPtr<UtilityProcessManager::WinFileDialogPromise>
UtilityProcessManager::CreateWinFileDialogActor() {
using RetPromise = WinFileDialogPromise;
TimeStamp startTime = TimeStamp::Now();
auto wfdp = MakeRefPtr<widget::filedialog::WinFileDialogParent>();
return StartUtility(wfdp, SandboxingKind::WINDOWS_FILE_DIALOG)
->Then(
GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__,
[wfdp, startTime]() mutable {
LOGD("CreateWinFileDialogAsync() resolve: wfdp = [%p]" , wfdp.get());
if (!wfdp->CanSend()) {
MOZ_ASSERT(false , "WinFileDialogParent can't send" );
return RetPromise::CreateAndReject(
LaunchError("CreateWinFileDialogActor: !wfdp->CanSend()" ),
__PRETTY_FUNCTION__);
}
PROFILER_MARKER_TEXT(
"UtilityProcessManager::CreateWinFileDialogAsync" , OTHER,
MarkerOptions(MarkerTiming::IntervalUntilNowFrom(startTime)),
"Resolve" _ns);
return RetPromise::CreateAndResolve(
widget::filedialog::ProcessProxy(std::move(wfdp)),
__PRETTY_FUNCTION__);
},
[self = RefPtr(this ), startTime](LaunchError&& error) {
LOGD("CreateWinFileDialogAsync() reject" );
if (!self->IsShutdown()) {
MOZ_ASSERT_UNREACHABLE("failure when starting file-dialog actor" );
}
PROFILER_MARKER_TEXT(
"UtilityProcessManager::CreateWinFileDialogAsync" , OTHER,
MarkerOptions(MarkerTiming::IntervalUntilNowFrom(startTime)),
"Reject" _ns);
return RetPromise::CreateAndReject(std::move(error),
__PRETTY_FUNCTION__);
});
}
#endif // XP_WIN
bool UtilityProcessManager::IsProcessLaunching(SandboxingKind aSandbox) {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<ProcessFields> p = GetProcess(aSandbox);
if (!p) {
MOZ_CRASH("Cannot check process launching with no process" );
return false ;
}
return p->mProcess && !(p->mProcessParent);
}
bool UtilityProcessManager::IsProcessDestroyed(SandboxingKind aSandbox) {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<ProcessFields> p = GetProcess(aSandbox);
if (!p) {
MOZ_CRASH("Cannot check process destroyed with no process" );
return false ;
}
return !p->mProcess && !p->mProcessParent;
}
void UtilityProcessManager::OnProcessUnexpectedShutdown(
UtilityProcessHost* aHost) {
MOZ_ASSERT(NS_IsMainThread());
for (auto & it : mProcesses) {
if (it && it->mProcess && it->mProcess == aHost) {
it->mNumUnexpectedCrashes++;
DestroyProcess(it->mSandbox);
return ;
}
}
MOZ_CRASH(
"Called UtilityProcessManager::OnProcessUnexpectedShutdown with invalid "
"aHost" );
}
void UtilityProcessManager::CleanShutdownAllProcesses() {
LOGD("[%p] UtilityProcessManager::CleanShutdownAllProcesses" , this );
for (auto & it : mProcesses) {
if (it) {
DestroyProcess(it->mSandbox);
}
}
}
void UtilityProcessManager::CleanShutdown(SandboxingKind aSandbox) {
LOGD("[%p] UtilityProcessManager::CleanShutdown SandboxingKind=%" PRIu64,
this , aSandbox);
DestroyProcess(aSandbox);
}
uint16_t UtilityProcessManager::AliveProcesses() {
uint16_t alive = 0;
for (auto & p : mProcesses) {
if (p != nullptr) {
alive++;
}
}
return alive;
}
bool UtilityProcessManager::NoMoreProcesses() { return AliveProcesses() == 0; }
void UtilityProcessManager::DestroyProcess(SandboxingKind aSandbox) {
LOGD("[%p] UtilityProcessManager::DestroyProcess SandboxingKind=%" PRIu64,
this , aSandbox);
MOZ_RELEASE_ASSERT(NS_IsMainThread());
if (AliveProcesses() <= 1) {
if (mObserver) {
Preferences::RemoveObserver(mObserver, "" );
}
mObserver = nullptr;
}
RefPtr<ProcessFields> p = GetProcess(aSandbox);
if (!p) {
return ;
}
p->mQueuedPrefs.Clear();
p->mProcessParent = nullptr;
if (!p->mProcess) {
return ;
}
p->mProcess->Shutdown();
p->mProcess = nullptr;
mProcesses[aSandbox] = nullptr;
CrashReporter::RecordAnnotationCString(
CrashReporter::Annotation::UtilityProcessStatus, "Destroyed" );
if (NoMoreProcesses()) {
sSingleton = nullptr;
}
}
Maybe<base::ProcessId> UtilityProcessManager::ProcessPid(
SandboxingKind aSandbox) {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<ProcessFields> p = GetProcess(aSandbox);
if (!p) {
return Nothing();
}
if (p->mProcessParent) {
return Some(p->mProcessParent->OtherPid());
}
return Nothing();
}
class UtilityMemoryReporter : public MemoryReportingProcess {
public :
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UtilityMemoryReporter, override)
explicit UtilityMemoryReporter(UtilityProcessParent* aParent) {
mParent = aParent;
}
bool IsAlive() const override { return bool (GetParent()); }
bool SendRequestMemoryReport(
const uint32_t& aGeneration, const bool & aAnonymize,
const bool & aMinimizeMemoryUsage,
const Maybe<ipc::FileDescriptor>& aDMDFile) override {
RefPtr<UtilityProcessParent> parent = GetParent();
if (!parent) {
return false ;
}
return parent->SendRequestMemoryReport(aGeneration, aAnonymize,
aMinimizeMemoryUsage, aDMDFile);
}
int32_t Pid() const override {
if (RefPtr<UtilityProcessParent> parent = GetParent()) {
return (int32_t)parent->OtherPid();
}
return 0;
}
private :
RefPtr<UtilityProcessParent> GetParent() const { return mParent; }
RefPtr<UtilityProcessParent> mParent = nullptr;
protected :
~UtilityMemoryReporter() = default ;
};
RefPtr<MemoryReportingProcess> UtilityProcessManager::GetProcessMemoryReporter(
UtilityProcessParent* parent) {
return new UtilityMemoryReporter(parent);
}
} // namespace mozilla::ipc
quality 96%
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet)
¤
*© Formatika GbR, Deutschland