/* -*- 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 "mozilla/ProcessHangMonitor.h" #include "mozilla/ProcessHangMonitorIPC.h" #include "jsapi.h" #include "js/GCAPI.h" #include "mozilla/Atomics.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/TabChild.h" #include "mozilla/dom/TabParent.h" #include "mozilla/Monitor.h" #include "mozilla/plugins/PluginBridge.h" #include "mozilla/Preferences.h" #include "mozilla/Unused.h" #include "nsIFrameLoader.h" #include "nsIHangReport.h" #include "nsITabParent.h" #include "nsPluginHost.h" #include "nsThreadUtils.h" #ifdef MOZ_CRASHREPORTER #include "nsExceptionHandler.h" #endif #include "base/task.h" #include "base/thread.h" #ifdef XP_WIN // For IsDebuggerPresent() #include #endif using namespace mozilla; using namespace mozilla::dom; /* * Basic architecture: * * Each process has its own ProcessHangMonitor singleton. This singleton exists * as long as there is at least one content process in the system. Each content * process has a HangMonitorChild and the chrome process has one * HangMonitorParent per process. Each process (including the chrome process) * runs a hang monitoring thread. The PHangMonitor actors are bound to this * thread so that they never block on the main thread. * * When the content process detects a hang, it posts a task to its hang thread, * which sends an IPC message to the hang thread in the parent. The parent * cancels any ongoing CPOW requests and then posts a runnable to the main * thread that notifies Firefox frontend code of the hang. The frontend code is * passed an nsIHangReport, which can be used to terminate the hang. * * If the user chooses to terminate a script, a task is posted to the chrome * process's hang monitoring thread, which sends an IPC message to the hang * thread in the content process. That thread sets a flag to indicate that JS * execution should be terminated the next time it hits the interrupt * callback. A similar scheme is used for debugging slow scripts. If a content * process or plug-in needs to be terminated, the chrome process does so * directly, without messaging the content process. */ namespace { /* Child process objects */ class HangMonitorChild : public PProcessHangMonitorChild { public: explicit HangMonitorChild(ProcessHangMonitor* aMonitor); virtual ~HangMonitorChild(); void Open(Transport* aTransport, ProcessId aOtherPid, MessageLoop* aIOLoop); typedef ProcessHangMonitor::SlowScriptAction SlowScriptAction; SlowScriptAction NotifySlowScript(nsITabChild* aTabChild, const char* aFileName, unsigned aLineNo); void NotifySlowScriptAsync(TabId aTabId, const nsCString& aFileName, unsigned aLineNo); bool IsDebuggerStartupComplete(); void NotifyPluginHang(uint32_t aPluginId); void NotifyPluginHangAsync(uint32_t aPluginId); void ClearHang(); void ClearHangAsync(); virtual bool RecvTerminateScript() override; virtual bool RecvBeginStartingDebugger() override; virtual bool RecvEndStartingDebugger() override; virtual bool RecvForcePaint(const TabId& aTabId, const uint64_t& aLayerObserverEpoch) override; virtual void ActorDestroy(ActorDestroyReason aWhy) override; void InterruptCallback(); void Shutdown(); static HangMonitorChild* Get() { return sInstance; } MessageLoop* MonitorLoop() { return mHangMonitor->MonitorLoop(); } private: void ShutdownOnThread(); static Atomic sInstance; const RefPtr mHangMonitor; Monitor mMonitor; // Main thread-only. bool mSentReport; // These fields must be accessed with mMonitor held. bool mTerminateScript; bool mStartDebugger; bool mFinishedStartingDebugger; bool mForcePaint; TabId mForcePaintTab; MOZ_INIT_OUTSIDE_CTOR uint64_t mForcePaintEpoch; JSContext* mContext; bool mShutdownDone; // This field is only accessed on the hang thread. bool mIPCOpen; }; Atomic HangMonitorChild::sInstance; /* Parent process objects */ class HangMonitorParent; class HangMonitoredProcess final : public nsIHangReport { public: NS_DECL_THREADSAFE_ISUPPORTS HangMonitoredProcess(HangMonitorParent* aActor, ContentParent* aContentParent) : mActor(aActor), mContentParent(aContentParent) {} NS_IMETHOD GetHangType(uint32_t* aHangType) override; NS_IMETHOD GetScriptBrowser(nsIDOMElement** aBrowser) override; NS_IMETHOD GetScriptFileName(nsACString& aFileName) override; NS_IMETHOD GetScriptLineNo(uint32_t* aLineNo) override; NS_IMETHOD GetPluginName(nsACString& aPluginName) override; NS_IMETHOD TerminateScript() override; NS_IMETHOD BeginStartingDebugger() override; NS_IMETHOD EndStartingDebugger() override; NS_IMETHOD TerminatePlugin() override; NS_IMETHOD UserCanceled() override; NS_IMETHOD IsReportForBrowser(nsIFrameLoader* aFrameLoader, bool* aResult) override; // Called when a content process shuts down. void Clear() { mContentParent = nullptr; mActor = nullptr; } /** * Sets the information associated with this hang: this includes the ID of * the plugin which caused the hang as well as the content PID. The ID of * a minidump taken during the hang can also be provided. * * @param aHangData The hang information * @param aDumpId The ID of a minidump taken when the hang occurred */ void SetHangData(const HangData& aHangData, const nsAString& aDumpId) { mHangData = aHangData; mDumpId = aDumpId; } void ClearHang() { mHangData = HangData(); mDumpId.Truncate(); } private: ~HangMonitoredProcess() {} // Everything here is main thread-only. HangMonitorParent* mActor; ContentParent* mContentParent; HangData mHangData; nsAutoString mDumpId; }; class HangMonitorParent : public PProcessHangMonitorParent { public: explicit HangMonitorParent(ProcessHangMonitor* aMonitor); virtual ~HangMonitorParent(); void Open(Transport* aTransport, ProcessId aPid, MessageLoop* aIOLoop); virtual bool RecvHangEvidence(const HangData& aHangData) override; virtual bool RecvClearHang() override; virtual void ActorDestroy(ActorDestroyReason aWhy) override; void SetProcess(HangMonitoredProcess* aProcess) { mProcess = aProcess; } void Shutdown(); void ForcePaint(dom::TabParent* aTabParent, uint64_t aLayerObserverEpoch); void TerminateScript(); void BeginStartingDebugger(); void EndStartingDebugger(); void CleanupPluginHang(uint32_t aPluginId, bool aRemoveFiles); /** * Update the dump for the specified plugin. This method is thread-safe and * is used to replace a browser minidump with a full minidump. If aDumpId is * empty this is a no-op. */ void UpdateMinidump(uint32_t aPluginId, const nsString& aDumpId); MessageLoop* MonitorLoop() { return mHangMonitor->MonitorLoop(); } private: bool TakeBrowserMinidump(const PluginHangData& aPhd, nsString& aCrashId); void ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch); void ShutdownOnThread(); const RefPtr mHangMonitor; // This field is read-only after construction. bool mReportHangs; // This field is only accessed on the hang thread. bool mIPCOpen; Monitor mMonitor; // Must be accessed with mMonitor held. RefPtr mProcess; bool mShutdownDone; // Map from plugin ID to crash dump ID. Protected by mBrowserCrashDumpHashLock. nsDataHashtable mBrowserCrashDumpIds; Mutex mBrowserCrashDumpHashLock; }; } // namespace /* HangMonitorChild implementation */ HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor) : mHangMonitor(aMonitor), mMonitor("HangMonitorChild lock"), mSentReport(false), mTerminateScript(false), mStartDebugger(false), mFinishedStartingDebugger(false), mForcePaint(false), mShutdownDone(false), mIPCOpen(true) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); mContext = danger::GetJSContext(); } HangMonitorChild::~HangMonitorChild() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); MOZ_ASSERT(sInstance == this); sInstance = nullptr; } void HangMonitorChild::InterruptCallback() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); bool forcePaint; TabId forcePaintTab; uint64_t forcePaintEpoch; { MonitorAutoLock lock(mMonitor); forcePaint = mForcePaint; forcePaintTab = mForcePaintTab; forcePaintEpoch = mForcePaintEpoch; mForcePaint = false; } if (forcePaint) { RefPtr tabChild = TabChild::FindTabChild(forcePaintTab); if (tabChild) { JS::AutoAssertOnGC aaogc(mContext); tabChild->ForcePaint(forcePaintEpoch); } } } void HangMonitorChild::Shutdown() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); MonitorAutoLock lock(mMonitor); while (!mShutdownDone) { mMonitor.Wait(); } } void HangMonitorChild::ShutdownOnThread() { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); MonitorAutoLock lock(mMonitor); mShutdownDone = true; mMonitor.Notify(); } void HangMonitorChild::ActorDestroy(ActorDestroyReason aWhy) { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); mIPCOpen = false; // We use a task here to ensure that IPDL is finished with this // HangMonitorChild before it gets deleted on the main thread. MonitorLoop()->PostTask(NewNonOwningRunnableMethod(this, &HangMonitorChild::ShutdownOnThread)); } bool HangMonitorChild::RecvTerminateScript() { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); MonitorAutoLock lock(mMonitor); mTerminateScript = true; return true; } bool HangMonitorChild::RecvBeginStartingDebugger() { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); MonitorAutoLock lock(mMonitor); mStartDebugger = true; return true; } bool HangMonitorChild::RecvEndStartingDebugger() { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); MonitorAutoLock lock(mMonitor); mFinishedStartingDebugger = true; return true; } bool HangMonitorChild::RecvForcePaint(const TabId& aTabId, const uint64_t& aLayerObserverEpoch) { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); { MonitorAutoLock lock(mMonitor); mForcePaint = true; mForcePaintTab = aTabId; mForcePaintEpoch = aLayerObserverEpoch; } JS_RequestInterruptCallback(mContext); return true; } void HangMonitorChild::Open(Transport* aTransport, ProcessId aPid, MessageLoop* aIOLoop) { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); MOZ_ASSERT(!sInstance); sInstance = this; DebugOnly ok = PProcessHangMonitorChild::Open(aTransport, aPid, aIOLoop); MOZ_ASSERT(ok); } void HangMonitorChild::NotifySlowScriptAsync(TabId aTabId, const nsCString& aFileName, unsigned aLineNo) { if (mIPCOpen) { Unused << SendHangEvidence(SlowScriptData(aTabId, aFileName, aLineNo)); } } HangMonitorChild::SlowScriptAction HangMonitorChild::NotifySlowScript(nsITabChild* aTabChild, const char* aFileName, unsigned aLineNo) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); mSentReport = true; { MonitorAutoLock lock(mMonitor); if (mTerminateScript) { mTerminateScript = false; return SlowScriptAction::Terminate; } if (mStartDebugger) { mStartDebugger = false; return SlowScriptAction::StartDebugger; } } TabId id; if (aTabChild) { RefPtr tabChild = static_cast(aTabChild); id = tabChild->GetTabId(); } nsAutoCString filename(aFileName); MonitorLoop()->PostTask(NewNonOwningRunnableMethod (this, &HangMonitorChild::NotifySlowScriptAsync, id, filename, aLineNo)); return SlowScriptAction::Continue; } bool HangMonitorChild::IsDebuggerStartupComplete() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); MonitorAutoLock lock(mMonitor); if (mFinishedStartingDebugger) { mFinishedStartingDebugger = false; return true; } return false; } void HangMonitorChild::NotifyPluginHang(uint32_t aPluginId) { // main thread in the child MOZ_RELEASE_ASSERT(NS_IsMainThread()); mSentReport = true; // bounce to background thread MonitorLoop()->PostTask(NewNonOwningRunnableMethod(this, &HangMonitorChild::NotifyPluginHangAsync, aPluginId)); } void HangMonitorChild::NotifyPluginHangAsync(uint32_t aPluginId) { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); // bounce back to parent on background thread if (mIPCOpen) { Unused << SendHangEvidence(PluginHangData(aPluginId, base::GetCurrentProcId())); } } void HangMonitorChild::ClearHang() { MOZ_ASSERT(NS_IsMainThread()); if (mSentReport) { // bounce to background thread MonitorLoop()->PostTask(NewNonOwningRunnableMethod(this, &HangMonitorChild::ClearHangAsync)); MonitorAutoLock lock(mMonitor); mSentReport = false; mTerminateScript = false; mStartDebugger = false; mFinishedStartingDebugger = false; } } void HangMonitorChild::ClearHangAsync() { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); // bounce back to parent on background thread if (mIPCOpen) { Unused << SendClearHang(); } } /* HangMonitorParent implementation */ HangMonitorParent::HangMonitorParent(ProcessHangMonitor* aMonitor) : mHangMonitor(aMonitor), mIPCOpen(true), mMonitor("HangMonitorParent lock"), mShutdownDone(false), mBrowserCrashDumpHashLock("mBrowserCrashDumpIds lock") { MOZ_RELEASE_ASSERT(NS_IsMainThread()); mReportHangs = mozilla::Preferences::GetBool("dom.ipc.reportProcessHangs", false); } HangMonitorParent::~HangMonitorParent() { #ifdef MOZ_CRASHREPORTER MutexAutoLock lock(mBrowserCrashDumpHashLock); for (auto iter = mBrowserCrashDumpIds.Iter(); !iter.Done(); iter.Next()) { nsString crashId = iter.UserData(); if (!crashId.IsEmpty()) { CrashReporter::DeleteMinidumpFilesForID(crashId); } } #endif } void HangMonitorParent::Shutdown() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); MonitorAutoLock lock(mMonitor); if (mProcess) { mProcess->Clear(); mProcess = nullptr; } MonitorLoop()->PostTask(NewNonOwningRunnableMethod(this, &HangMonitorParent::ShutdownOnThread)); while (!mShutdownDone) { mMonitor.Wait(); } } void HangMonitorParent::ShutdownOnThread() { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); // mIPCOpen is only written from this thread, so need need to take the lock // here. We'd be shooting ourselves in the foot, because ActorDestroy takes // it. if (mIPCOpen) { Close(); } MonitorAutoLock lock(mMonitor); mShutdownDone = true; mMonitor.Notify(); } void HangMonitorParent::ForcePaint(dom::TabParent* aTab, uint64_t aLayerObserverEpoch) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); TabId id = aTab->GetTabId(); MonitorLoop()->PostTask(NewNonOwningRunnableMethod( this, &HangMonitorParent::ForcePaintOnThread, id, aLayerObserverEpoch)); } void HangMonitorParent::ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch) { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); if (mIPCOpen) { Unused << SendForcePaint(aTabId, aLayerObserverEpoch); } } void HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy) { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); mIPCOpen = false; } void HangMonitorParent::Open(Transport* aTransport, ProcessId aPid, MessageLoop* aIOLoop) { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); DebugOnly ok = PProcessHangMonitorParent::Open(aTransport, aPid, aIOLoop); MOZ_ASSERT(ok); } class HangObserverNotifier final : public Runnable { public: HangObserverNotifier(HangMonitoredProcess* aProcess, HangMonitorParent *aParent, const HangData& aHangData, const nsString& aBrowserDumpId, bool aTakeMinidump) : mProcess(aProcess), mParent(aParent), mHangData(aHangData), mBrowserDumpId(aBrowserDumpId), mTakeMinidump(aTakeMinidump) {} NS_IMETHOD Run() override { // chrome process, main thread MOZ_RELEASE_ASSERT(NS_IsMainThread()); nsString dumpId; if ((mHangData.type() == HangData::TPluginHangData) && mTakeMinidump) { // We've been handed a partial minidump; complete it with plugin and // content process dumps. const PluginHangData& phd = mHangData.get_PluginHangData(); plugins::TakeFullMinidump(phd.pluginId(), phd.contentProcessId(), mBrowserDumpId, dumpId); mParent->UpdateMinidump(phd.pluginId(), dumpId); } else { // We already have a full minidump; go ahead and use it. dumpId = mBrowserDumpId; } mProcess->SetHangData(mHangData, dumpId); nsCOMPtr observerService = mozilla::services::GetObserverService(); observerService->NotifyObservers(mProcess, "process-hang-report", nullptr); return NS_OK; } private: RefPtr mProcess; HangMonitorParent* mParent; HangData mHangData; nsAutoString mBrowserDumpId; bool mTakeMinidump; }; // Take a minidump of the browser process if one wasn't already taken for the // plugin that caused the hang. Return false if a dump was already available or // true if new one has been taken. bool HangMonitorParent::TakeBrowserMinidump(const PluginHangData& aPhd, nsString& aCrashId) { #ifdef MOZ_CRASHREPORTER MutexAutoLock lock(mBrowserCrashDumpHashLock); if (!mBrowserCrashDumpIds.Get(aPhd.pluginId(), &aCrashId)) { nsCOMPtr browserDump; if (CrashReporter::TakeMinidump(getter_AddRefs(browserDump), true)) { if (!CrashReporter::GetIDFromMinidump(browserDump, aCrashId) || aCrashId.IsEmpty()) { browserDump->Remove(false); NS_WARNING("Failed to generate timely browser stack, " "this is bad for plugin hang analysis!"); } else { mBrowserCrashDumpIds.Put(aPhd.pluginId(), aCrashId); return true; } } } #endif // MOZ_CRASHREPORTER return false; } bool HangMonitorParent::RecvHangEvidence(const HangData& aHangData) { // chrome process, background thread MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); if (!mReportHangs) { return true; } #ifdef XP_WIN // Don't report hangs if we're debugging the process. You can comment this // line out for testing purposes. if (IsDebuggerPresent()) { return true; } #endif // Before we wake up the browser main thread we want to take a // browser minidump. nsAutoString crashId; bool takeMinidump = false; if (aHangData.type() == HangData::TPluginHangData) { takeMinidump = TakeBrowserMinidump(aHangData.get_PluginHangData(), crashId); } mHangMonitor->InitiateCPOWTimeout(); MonitorAutoLock lock(mMonitor); nsCOMPtr notifier = new HangObserverNotifier(mProcess, this, aHangData, crashId, takeMinidump); NS_DispatchToMainThread(notifier); return true; } class ClearHangNotifier final : public Runnable { public: explicit ClearHangNotifier(HangMonitoredProcess* aProcess) : mProcess(aProcess) {} NS_IMETHOD Run() override { // chrome process, main thread MOZ_RELEASE_ASSERT(NS_IsMainThread()); mProcess->ClearHang(); nsCOMPtr observerService = mozilla::services::GetObserverService(); observerService->NotifyObservers(mProcess, "clear-hang-report", nullptr); return NS_OK; } private: RefPtr mProcess; }; bool HangMonitorParent::RecvClearHang() { // chrome process, background thread MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); if (!mReportHangs) { return true; } mHangMonitor->InitiateCPOWTimeout(); MonitorAutoLock lock(mMonitor); nsCOMPtr notifier = new ClearHangNotifier(mProcess); NS_DispatchToMainThread(notifier); return true; } void HangMonitorParent::TerminateScript() { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); if (mIPCOpen) { Unused << SendTerminateScript(); } } void HangMonitorParent::BeginStartingDebugger() { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); if (mIPCOpen) { Unused << SendBeginStartingDebugger(); } } void HangMonitorParent::EndStartingDebugger() { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); if (mIPCOpen) { Unused << SendEndStartingDebugger(); } } void HangMonitorParent::CleanupPluginHang(uint32_t aPluginId, bool aRemoveFiles) { MutexAutoLock lock(mBrowserCrashDumpHashLock); nsAutoString crashId; if (!mBrowserCrashDumpIds.Get(aPluginId, &crashId)) { return; } mBrowserCrashDumpIds.Remove(aPluginId); #ifdef MOZ_CRASHREPORTER if (aRemoveFiles && !crashId.IsEmpty()) { CrashReporter::DeleteMinidumpFilesForID(crashId); } #endif } void HangMonitorParent::UpdateMinidump(uint32_t aPluginId, const nsString& aDumpId) { if (aDumpId.IsEmpty()) { return; } MutexAutoLock lock(mBrowserCrashDumpHashLock); mBrowserCrashDumpIds.Put(aPluginId, aDumpId); } /* HangMonitoredProcess implementation */ NS_IMPL_ISUPPORTS(HangMonitoredProcess, nsIHangReport) NS_IMETHODIMP HangMonitoredProcess::GetHangType(uint32_t* aHangType) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); switch (mHangData.type()) { case HangData::TSlowScriptData: *aHangType = SLOW_SCRIPT; break; case HangData::TPluginHangData: *aHangType = PLUGIN_HANG; break; default: MOZ_ASSERT_UNREACHABLE("Unexpected HangData type"); return NS_ERROR_UNEXPECTED; } return NS_OK; } NS_IMETHODIMP HangMonitoredProcess::GetScriptBrowser(nsIDOMElement** aBrowser) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (mHangData.type() != HangData::TSlowScriptData) { return NS_ERROR_NOT_AVAILABLE; } TabId tabId = mHangData.get_SlowScriptData().tabId(); if (!mContentParent) { return NS_ERROR_NOT_AVAILABLE; } nsTArray tabs; mContentParent->ManagedPBrowserParent(tabs); for (size_t i = 0; i < tabs.Length(); i++) { TabParent* tp = TabParent::GetFrom(tabs[i]); if (tp->GetTabId() == tabId) { nsCOMPtr node = do_QueryInterface(tp->GetOwnerElement()); node.forget(aBrowser); return NS_OK; } } *aBrowser = nullptr; return NS_OK; } NS_IMETHODIMP HangMonitoredProcess::GetScriptFileName(nsACString& aFileName) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (mHangData.type() != HangData::TSlowScriptData) { return NS_ERROR_NOT_AVAILABLE; } aFileName = mHangData.get_SlowScriptData().filename(); return NS_OK; } NS_IMETHODIMP HangMonitoredProcess::GetScriptLineNo(uint32_t* aLineNo) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (mHangData.type() != HangData::TSlowScriptData) { return NS_ERROR_NOT_AVAILABLE; } *aLineNo = mHangData.get_SlowScriptData().lineno(); return NS_OK; } NS_IMETHODIMP HangMonitoredProcess::GetPluginName(nsACString& aPluginName) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (mHangData.type() != HangData::TPluginHangData) { return NS_ERROR_NOT_AVAILABLE; } uint32_t id = mHangData.get_PluginHangData().pluginId(); RefPtr host = nsPluginHost::GetInst(); nsPluginTag* tag = host->PluginWithId(id); if (!tag) { return NS_ERROR_UNEXPECTED; } aPluginName = tag->Name(); return NS_OK; } NS_IMETHODIMP HangMonitoredProcess::TerminateScript() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (mHangData.type() != HangData::TSlowScriptData) { return NS_ERROR_UNEXPECTED; } if (!mActor) { return NS_ERROR_UNEXPECTED; } ProcessHangMonitor::Get()->MonitorLoop()->PostTask(NewNonOwningRunnableMethod(mActor, &HangMonitorParent::TerminateScript)); return NS_OK; } NS_IMETHODIMP HangMonitoredProcess::BeginStartingDebugger() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (mHangData.type() != HangData::TSlowScriptData) { return NS_ERROR_UNEXPECTED; } if (!mActor) { return NS_ERROR_UNEXPECTED; } ProcessHangMonitor::Get()->MonitorLoop()->PostTask(NewNonOwningRunnableMethod(mActor, &HangMonitorParent::BeginStartingDebugger)); return NS_OK; } NS_IMETHODIMP HangMonitoredProcess::EndStartingDebugger() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (mHangData.type() != HangData::TSlowScriptData) { return NS_ERROR_UNEXPECTED; } if (!mActor) { return NS_ERROR_UNEXPECTED; } ProcessHangMonitor::Get()->MonitorLoop()->PostTask(NewNonOwningRunnableMethod(mActor, &HangMonitorParent::EndStartingDebugger)); return NS_OK; } NS_IMETHODIMP HangMonitoredProcess::TerminatePlugin() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (mHangData.type() != HangData::TPluginHangData) { return NS_ERROR_UNEXPECTED; } // Use the multi-process crash report generated earlier. uint32_t id = mHangData.get_PluginHangData().pluginId(); base::ProcessId contentPid = mHangData.get_PluginHangData().contentProcessId(); plugins::TerminatePlugin(id, contentPid, NS_LITERAL_CSTRING("HangMonitor"), mDumpId); if (mActor) { mActor->CleanupPluginHang(id, false); } return NS_OK; } NS_IMETHODIMP HangMonitoredProcess::IsReportForBrowser(nsIFrameLoader* aFrameLoader, bool* aResult) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (!mActor) { *aResult = false; return NS_OK; } TabParent* tp = TabParent::GetFrom(aFrameLoader); if (!tp) { *aResult = false; return NS_OK; } *aResult = mContentParent == tp->Manager(); return NS_OK; } NS_IMETHODIMP HangMonitoredProcess::UserCanceled() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (mHangData.type() != HangData::TPluginHangData) { return NS_OK; } if (mActor) { uint32_t id = mHangData.get_PluginHangData().pluginId(); mActor->CleanupPluginHang(id, true); } return NS_OK; } static bool InterruptCallback(JSContext* cx) { if (HangMonitorChild* child = HangMonitorChild::Get()) { child->InterruptCallback(); } return true; } ProcessHangMonitor* ProcessHangMonitor::sInstance; ProcessHangMonitor::ProcessHangMonitor() : mCPOWTimeout(false) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); MOZ_COUNT_CTOR(ProcessHangMonitor); if (XRE_IsContentProcess()) { nsCOMPtr obs = mozilla::services::GetObserverService(); obs->AddObserver(this, "xpcom-shutdown", false); } mThread = new base::Thread("ProcessHangMonitor"); if (!mThread->Start()) { delete mThread; mThread = nullptr; } } ProcessHangMonitor::~ProcessHangMonitor() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); MOZ_COUNT_DTOR(ProcessHangMonitor); MOZ_ASSERT(sInstance == this); sInstance = nullptr; delete mThread; } ProcessHangMonitor* ProcessHangMonitor::GetOrCreate() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (!sInstance) { sInstance = new ProcessHangMonitor(); } return sInstance; } NS_IMPL_ISUPPORTS(ProcessHangMonitor, nsIObserver) NS_IMETHODIMP ProcessHangMonitor::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (!strcmp(aTopic, "xpcom-shutdown")) { if (HangMonitorChild* child = HangMonitorChild::Get()) { child->Shutdown(); delete child; } nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->RemoveObserver(this, "xpcom-shutdown"); } } return NS_OK; } ProcessHangMonitor::SlowScriptAction ProcessHangMonitor::NotifySlowScript(nsITabChild* aTabChild, const char* aFileName, unsigned aLineNo) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); return HangMonitorChild::Get()->NotifySlowScript(aTabChild, aFileName, aLineNo); } bool ProcessHangMonitor::IsDebuggerStartupComplete() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); return HangMonitorChild::Get()->IsDebuggerStartupComplete(); } bool ProcessHangMonitor::ShouldTimeOutCPOWs() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (mCPOWTimeout) { mCPOWTimeout = false; return true; } return false; } void ProcessHangMonitor::InitiateCPOWTimeout() { MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); mCPOWTimeout = true; } void ProcessHangMonitor::NotifyPluginHang(uint32_t aPluginId) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); return HangMonitorChild::Get()->NotifyPluginHang(aPluginId); } PProcessHangMonitorParent* mozilla::CreateHangMonitorParent(ContentParent* aContentParent, mozilla::ipc::Transport* aTransport, base::ProcessId aOtherPid) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate(); HangMonitorParent* parent = new HangMonitorParent(monitor); HangMonitoredProcess* process = new HangMonitoredProcess(parent, aContentParent); parent->SetProcess(process); monitor->MonitorLoop()->PostTask(NewNonOwningRunnableMethod (parent, &HangMonitorParent::Open, aTransport, aOtherPid, XRE_GetIOMessageLoop())); return parent; } PProcessHangMonitorChild* mozilla::CreateHangMonitorChild(mozilla::ipc::Transport* aTransport, base::ProcessId aOtherPid) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); JSContext* cx = danger::GetJSContext(); JS_AddInterruptCallback(cx, InterruptCallback); ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate(); HangMonitorChild* child = new HangMonitorChild(monitor); monitor->MonitorLoop()->PostTask(NewNonOwningRunnableMethod (child, &HangMonitorChild::Open, aTransport, aOtherPid, XRE_GetIOMessageLoop())); return child; } MessageLoop* ProcessHangMonitor::MonitorLoop() { return mThread->message_loop(); } /* static */ void ProcessHangMonitor::AddProcess(ContentParent* aContentParent) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (mozilla::Preferences::GetBool("dom.ipc.processHangMonitor", false)) { DebugOnly opened = PProcessHangMonitor::Open(aContentParent); MOZ_ASSERT(opened); } } /* static */ void ProcessHangMonitor::RemoveProcess(PProcessHangMonitorParent* aParent) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); auto parent = static_cast(aParent); parent->Shutdown(); delete parent; } /* static */ void ProcessHangMonitor::ClearHang() { MOZ_ASSERT(NS_IsMainThread()); if (HangMonitorChild* child = HangMonitorChild::Get()) { child->ClearHang(); } } /* static */ void ProcessHangMonitor::ForcePaint(PProcessHangMonitorParent* aParent, dom::TabParent* aTabParent, uint64_t aLayerObserverEpoch) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); auto parent = static_cast(aParent); parent->ForcePaint(aTabParent, aLayerObserverEpoch); }