ProcessHangMonitor.cpp (43627B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/ProcessHangMonitor.h" 8 9 #include "MainThreadUtils.h" 10 #include "base/task.h" 11 #include "base/thread.h" 12 #include "jsapi.h" 13 #include "mozilla/AppShutdown.h" 14 #include "mozilla/Atomics.h" 15 #include "mozilla/BackgroundHangMonitor.h" 16 #include "mozilla/BasePrincipal.h" 17 #include "mozilla/Monitor.h" 18 #include "mozilla/Preferences.h" 19 #include "mozilla/ProcessHangMonitorIPC.h" 20 #include "mozilla/ProfilerMarkers.h" 21 #include "mozilla/StaticMonitor.h" 22 #include "mozilla/StaticPrefs_browser.h" 23 #include "mozilla/StaticPrefs_dom.h" 24 #include "mozilla/StaticPtr.h" 25 #include "mozilla/dom/BrowserChild.h" 26 #include "mozilla/dom/BrowserParent.h" 27 #include "mozilla/dom/CancelContentJSOptionsBinding.h" 28 #include "mozilla/dom/CanonicalBrowsingContext.h" 29 #include "mozilla/dom/ContentParent.h" 30 #include "mozilla/dom/Document.h" 31 #include "mozilla/dom/Element.h" 32 #include "mozilla/dom/ScriptSettings.h" 33 #include "mozilla/ipc/Endpoint.h" 34 #include "mozilla/ipc/ProcessChild.h" 35 #include "mozilla/ipc/TaskFactory.h" 36 #include "nsExceptionHandler.h" 37 #include "nsFrameLoader.h" 38 #include "nsIHangReport.h" 39 #include "nsIRemoteTab.h" 40 #include "nsNetUtil.h" 41 #include "nsQueryObject.h" 42 #include "nsThreadUtils.h" 43 #include "xpcprivate.h" 44 45 #ifdef XP_WIN 46 // For IsDebuggerPresent() 47 # include <windows.h> 48 #endif 49 50 #ifdef XP_MACOSX 51 // for qos controls 52 # include <sys/qos.h> 53 #endif 54 55 using namespace mozilla; 56 using namespace mozilla::dom; 57 using namespace mozilla::ipc; 58 59 /* 60 * Basic architecture: 61 * 62 * Each process has its own ProcessHangMonitor singleton. This singleton exists 63 * as long as there is at least one content process in the system. Each content 64 * process has a HangMonitorChild and the chrome process has one 65 * HangMonitorParent per process. Each process (including the chrome process) 66 * runs a hang monitoring thread. The PHangMonitor actors are bound to this 67 * thread so that they never block on the main thread. 68 * 69 * When the content process detects a hang, it posts a task to its hang thread, 70 * which sends an IPC message to the hang thread in the parent. The parent 71 * cancels any ongoing CPOW requests and then posts a runnable to the main 72 * thread that notifies Firefox frontend code of the hang. The frontend code is 73 * passed an nsIHangReport, which can be used to terminate the hang. 74 * 75 * If the user chooses to terminate a script, a task is posted to the chrome 76 * process's hang monitoring thread, which sends an IPC message to the hang 77 * thread in the content process. That thread sets a flag to indicate that JS 78 * execution should be terminated the next time it hits the interrupt 79 * callback. A similar scheme is used for debugging slow scripts. If a content 80 * process or plug-in needs to be terminated, the chrome process does so 81 * directly, without messaging the content process. 82 */ 83 84 namespace { 85 86 LazyLogModule gQoSLog("QoSPriority"); // For RecvSetMainThreadQoSPriority. 87 88 /* Child process objects */ 89 90 class HangMonitorChild : public PProcessHangMonitorChild, 91 public BackgroundHangAnnotator { 92 public: 93 NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD( 94 HangMonitorChild, override) 95 96 void Bind(Endpoint<PProcessHangMonitorChild>&& aEndpoint); 97 98 using SlowScriptAction = ProcessHangMonitor::SlowScriptAction; 99 SlowScriptAction NotifySlowScript(nsIBrowserChild* aBrowserChild, 100 const char* aFileName, 101 const nsString& aAddonId, 102 const double aDuration); 103 void NotifySlowScriptAsync(TabId aTabId, const nsCString& aFileName, 104 const nsString& aAddonId, const double aDuration); 105 106 bool IsDebuggerStartupComplete(); 107 108 void ClearHang(); 109 void ClearHangAsync(); 110 void ClearPaintWhileInterruptingJS(); 111 112 // MaybeStartPaintWhileInterruptingJS will notify the background hang monitor 113 // of activity if this is the first time calling it since 114 // ClearPaintWhileInterruptingJS. It should be callable from any thread, but 115 // you must be holding mMonitor if using it off the main thread, since it 116 // could race with ClearPaintWhileInterruptingJS. 117 void MaybeStartPaintWhileInterruptingJS(); 118 119 mozilla::ipc::IPCResult RecvTerminateScript() override; 120 mozilla::ipc::IPCResult RecvRequestContentJSInterrupt() override; 121 mozilla::ipc::IPCResult RecvBeginStartingDebugger() override; 122 mozilla::ipc::IPCResult RecvEndStartingDebugger() override; 123 124 mozilla::ipc::IPCResult RecvPaintWhileInterruptingJS( 125 const TabId& aTabId) override; 126 127 mozilla::ipc::IPCResult RecvUnloadLayersWhileInterruptingJS( 128 const TabId& aTabId) override; 129 130 mozilla::ipc::IPCResult RecvCancelContentJSExecutionIfRunning( 131 const TabId& aTabId, const nsIRemoteTab::NavigationType& aNavigationType, 132 const int32_t& aNavigationIndex, 133 const mozilla::Maybe<nsCString>& aNavigationURI, 134 const int32_t& aEpoch) override; 135 136 mozilla::ipc::IPCResult RecvSetMainThreadQoSPriority( 137 const nsIThread::QoSPriority& aQoSPriority) override; 138 139 void ActorDestroy(ActorDestroyReason aWhy) override; 140 141 bool InterruptCallback(); 142 void Shutdown(); 143 144 static HangMonitorChild* Get() MOZ_REQUIRES(sMainThreadCapability) { 145 return sInstance; 146 } 147 148 static void CreateAndBind(ProcessHangMonitor* aMonitor, 149 Endpoint<PProcessHangMonitorChild>&& aEndpoint); 150 151 void Dispatch(already_AddRefed<nsIRunnable> aRunnable) { 152 mHangMonitor->Dispatch(std::move(aRunnable)); 153 } 154 bool IsOnThread() { return mHangMonitor->IsOnThread(); } 155 156 void AnnotateHang(BackgroundHangAnnotations& aAnnotations) override; 157 158 protected: 159 friend class mozilla::ProcessHangMonitor; 160 161 private: 162 explicit HangMonitorChild(ProcessHangMonitor* aMonitor); 163 ~HangMonitorChild() override; 164 165 void ShutdownOnThread(); 166 167 static StaticRefPtr<HangMonitorChild> sInstance 168 MOZ_GUARDED_BY(sMainThreadCapability); 169 170 const RefPtr<ProcessHangMonitor> mHangMonitor; 171 172 #ifdef XP_MACOSX 173 // On macOS, the pthread_t is required to start a QoS class override. As we 174 // can't recover this from a PRThread*, we need to record it when the 175 // HangMonitorChild is initially created on the main thread. 176 const pthread_t mMainPThread; 177 #endif 178 179 Monitor mMonitor; 180 181 // Main thread-only. 182 bool mSentReport; 183 184 // These fields must be accessed with mMonitor held. 185 bool mTerminateScript MOZ_GUARDED_BY(mMonitor); 186 bool mStartDebugger MOZ_GUARDED_BY(mMonitor); 187 bool mFinishedStartingDebugger MOZ_GUARDED_BY(mMonitor); 188 189 // this variable is used to paint/unload layers 190 // if not set, no action required 191 // true means, we will paint. false - unload layers 192 Maybe<bool> mPaintWhileInterruptingJS MOZ_GUARDED_BY(mMonitor); 193 TabId mPaintWhileInterruptingJSTab MOZ_GUARDED_BY(mMonitor); 194 bool mCancelContentJS MOZ_GUARDED_BY(mMonitor); 195 TabId mCancelContentJSTab MOZ_GUARDED_BY(mMonitor); 196 nsIRemoteTab::NavigationType mCancelContentJSNavigationType 197 MOZ_GUARDED_BY(mMonitor); 198 int32_t mCancelContentJSNavigationIndex MOZ_GUARDED_BY(mMonitor); 199 mozilla::Maybe<nsCString> mCancelContentJSNavigationURI 200 MOZ_GUARDED_BY(mMonitor); 201 int32_t mCancelContentJSEpoch MOZ_GUARDED_BY(mMonitor); 202 bool mShutdownDone MOZ_GUARDED_BY(mMonitor); 203 204 JSContext* mContext; // const after constructor 205 206 // This field is only accessed on the hang thread. 207 bool mIPCOpen; 208 209 // Allows us to ensure we NotifyActivity only once, allowing 210 // either thread to do so. 211 Atomic<bool> mPaintWhileInterruptingJSActive; 212 }; 213 214 StaticRefPtr<HangMonitorChild> HangMonitorChild::sInstance; 215 216 /* Parent process objects */ 217 218 class HangMonitorParent; 219 220 class HangMonitoredProcess final : public nsIHangReport { 221 public: 222 NS_DECL_THREADSAFE_ISUPPORTS 223 224 HangMonitoredProcess(HangMonitorParent* aActor, ContentParent* aContentParent) 225 : mActor(aActor), mContentParent(aContentParent) {} 226 227 NS_DECL_NSIHANGREPORT 228 229 // Called when a content process shuts down. 230 void Clear() { 231 mContentParent = nullptr; 232 mActor = nullptr; 233 } 234 235 /** 236 * Sets the information associated with this hang: this includes the tab ID, 237 * filename, duration, and an add-on ID if it was caused by an add-on. 238 * 239 * @param aDumpId The ID of a minidump taken when the hang occurred 240 */ 241 void SetSlowScriptData(const SlowScriptData& aSlowScriptData, 242 const nsAString& aDumpId) { 243 mSlowScriptData = aSlowScriptData; 244 mDumpId = aDumpId; 245 } 246 247 void ClearHang() { 248 mSlowScriptData = SlowScriptData(); 249 mDumpId.Truncate(); 250 } 251 252 private: 253 ~HangMonitoredProcess() = default; 254 255 // Everything here is main thread-only. 256 HangMonitorParent* mActor; 257 ContentParent* mContentParent; 258 SlowScriptData mSlowScriptData; 259 nsAutoString mDumpId; 260 }; 261 262 class HangMonitorParent : public PProcessHangMonitorParent { 263 public: 264 NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD( 265 HangMonitorParent, override) 266 267 explicit HangMonitorParent(ProcessHangMonitor* aMonitor); 268 269 void Bind(Endpoint<PProcessHangMonitorParent>&& aEndpoint); 270 271 mozilla::ipc::IPCResult RecvHangEvidence( 272 const SlowScriptData& aSlowScriptData) override; 273 mozilla::ipc::IPCResult RecvClearHang() override; 274 275 void ActorDestroy(ActorDestroyReason aWhy) override; 276 277 void SetProcess(HangMonitoredProcess* aProcess) { mProcess = aProcess; } 278 279 void Shutdown(); 280 281 void PaintWhileInterruptingJS(dom::BrowserParent* aTab); 282 283 void UnloadLayersWhileInterruptingJS(dom::BrowserParent* aTab); 284 void CancelContentJSExecutionIfRunning( 285 dom::BrowserParent* aBrowserParent, 286 nsIRemoteTab::NavigationType aNavigationType, 287 const dom::CancelContentJSOptions& aCancelContentJSOptions); 288 289 void SetMainThreadQoSPriority(nsIThread::QoSPriority aQoSPriority); 290 291 void TerminateScript(); 292 void BeginStartingDebugger(); 293 void EndStartingDebugger(); 294 295 nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable) { 296 return mHangMonitor->Dispatch(std::move(aRunnable)); 297 } 298 bool IsOnThread() { return mHangMonitor->IsOnThread(); } 299 300 private: 301 ~HangMonitorParent() override = default; 302 303 void SendHangNotification(const SlowScriptData& aSlowScriptData, 304 const nsString& aBrowserDumpId); 305 306 void ClearHangNotification(); 307 308 void PaintOrUnloadLayersWhileInterruptingJSOnThread(bool aPaint, 309 TabId aTabId); 310 void CancelContentJSExecutionIfRunningOnThread( 311 TabId aTabId, nsIRemoteTab::NavigationType aNavigationType, 312 int32_t aNavigationIndex, nsIURI* aNavigationURI, int32_t aEpoch); 313 314 #ifdef XP_MACOSX 315 void SetMainThreadQoSPriorityOnThread(nsIThread::QoSPriority aQoSPriority); 316 #endif 317 318 void ShutdownOnThread(); 319 320 const RefPtr<ProcessHangMonitor> mHangMonitor; 321 322 // This field is only accessed on the hang thread. 323 bool mIPCOpen; 324 325 Monitor mMonitor; 326 327 // MainThread only 328 RefPtr<HangMonitoredProcess> mProcess; 329 330 // Must be accessed with mMonitor held. 331 bool mShutdownDone MOZ_GUARDED_BY(mMonitor); 332 mozilla::ipc::TaskFactory<HangMonitorParent> mMainThreadTaskFactory 333 MOZ_GUARDED_BY(mMonitor); 334 }; 335 336 } // namespace 337 338 /* HangMonitorChild implementation */ 339 340 HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor) 341 : mHangMonitor(aMonitor), 342 #ifdef XP_MACOSX 343 mMainPThread(pthread_self()), 344 #endif 345 mMonitor("HangMonitorChild lock"), 346 mSentReport(false), 347 mTerminateScript(false), 348 mStartDebugger(false), 349 mFinishedStartingDebugger(false), 350 mCancelContentJS(false), 351 mCancelContentJSNavigationType(nsIRemoteTab::NAVIGATE_BACK), 352 mCancelContentJSNavigationIndex(0), 353 mCancelContentJSEpoch(0), 354 mShutdownDone(false), 355 mIPCOpen(true), 356 mPaintWhileInterruptingJSActive(false) { 357 ReleaseAssertIsOnMainThread(); 358 MOZ_ASSERT(!sInstance); 359 360 mContext = danger::GetJSContext(); 361 } 362 363 HangMonitorChild::~HangMonitorChild() { 364 ReleaseAssertIsOnMainThread(); 365 MOZ_ASSERT(sInstance != this); 366 } 367 368 void HangMonitorChild::CreateAndBind( 369 ProcessHangMonitor* aMonitor, 370 Endpoint<PProcessHangMonitorChild>&& aEndpoint) { 371 ReleaseAssertIsOnMainThread(); 372 MOZ_ASSERT(!sInstance); 373 374 sInstance = new HangMonitorChild(aMonitor); 375 376 BackgroundHangMonitor::RegisterAnnotator(*sInstance); 377 378 aMonitor->Dispatch(NewRunnableMethod<Endpoint<PProcessHangMonitorChild>&&>( 379 "HangMonitorChild::Bind", sInstance.get(), &HangMonitorChild::Bind, 380 std::move(aEndpoint))); 381 } 382 383 bool HangMonitorChild::InterruptCallback() { 384 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 385 386 if (StaticPrefs::dom_abort_script_on_child_shutdown() && 387 mozilla::AppShutdown::IsShutdownImpending()) { 388 // We preserve chrome JS from cancel, but not extension content JS. 389 if (!nsContentUtils::IsCallerChrome()) { 390 NS_WARNING( 391 "HangMonitorChild::InterruptCallback: ExpectingShutdown, " 392 "canceling content JS execution.\n"); 393 return false; 394 } 395 return true; 396 } 397 398 // Don't start painting if we're not in a good place to run script. We run 399 // chrome script during layout and such, and it wouldn't be good to interrupt 400 // painting code from there. 401 if (!nsContentUtils::IsSafeToRunScript()) { 402 return true; 403 } 404 405 Maybe<bool> paintWhileInterruptingJS; 406 TabId paintWhileInterruptingJSTab; 407 408 { 409 MonitorAutoLock lock(mMonitor); 410 paintWhileInterruptingJS = mPaintWhileInterruptingJS; 411 paintWhileInterruptingJSTab = mPaintWhileInterruptingJSTab; 412 413 mPaintWhileInterruptingJS.reset(); 414 } 415 416 if (paintWhileInterruptingJS.isSome()) { 417 RefPtr<BrowserChild> browserChild = 418 BrowserChild::FindBrowserChild(paintWhileInterruptingJSTab); 419 if (browserChild) { 420 js::AutoAssertNoContentJS nojs(mContext); 421 if (paintWhileInterruptingJS.value()) { 422 AUTO_PROFILER_MARKER_UNTYPED( 423 "InterruptCallback: PaintWhileInterruptingJS", DOM, {}); 424 browserChild->PaintWhileInterruptingJS(); 425 } else { 426 AUTO_PROFILER_MARKER_UNTYPED( 427 "InterruptCallback: UnloadLayersWhileInterruptingJS", DOM, {}); 428 browserChild->UnloadLayersWhileInterruptingJS(); 429 } 430 } 431 } 432 433 // Only handle the interrupt for cancelling content JS if we have a 434 // non-privileged script (i.e. not part of Gecko or an add-on). 435 JS::Rooted<JSObject*> global(mContext, JS::CurrentGlobalOrNull(mContext)); 436 nsIPrincipal* principal = xpc::GetObjectPrincipal(global); 437 if (principal && (principal->IsSystemPrincipal() || 438 principal->GetIsAddonOrExpandedAddonPrincipal())) { 439 return true; 440 } 441 442 nsCOMPtr<nsPIDOMWindowInner> win = xpc::WindowOrNull(global); 443 if (!win) { 444 return true; 445 } 446 447 bool cancelContentJS; 448 TabId cancelContentJSTab; 449 nsIRemoteTab::NavigationType cancelContentJSNavigationType; 450 int32_t cancelContentJSNavigationIndex; 451 mozilla::Maybe<nsCString> cancelContentJSNavigationURI; 452 int32_t cancelContentJSEpoch; 453 454 { 455 MonitorAutoLock lock(mMonitor); 456 cancelContentJS = mCancelContentJS; 457 cancelContentJSTab = mCancelContentJSTab; 458 cancelContentJSNavigationType = mCancelContentJSNavigationType; 459 cancelContentJSNavigationIndex = mCancelContentJSNavigationIndex; 460 cancelContentJSNavigationURI = std::move(mCancelContentJSNavigationURI); 461 cancelContentJSEpoch = mCancelContentJSEpoch; 462 463 mCancelContentJS = false; 464 } 465 466 if (cancelContentJS) { 467 js::AutoAssertNoContentJS nojs(mContext); 468 469 RefPtr<BrowserChild> browserChild = 470 BrowserChild::FindBrowserChild(cancelContentJSTab); 471 RefPtr<BrowserChild> browserChildFromWin = BrowserChild::GetFrom(win); 472 if (!browserChild || !browserChildFromWin) { 473 return true; 474 } 475 476 TabId tabIdFromWin = browserChildFromWin->GetTabId(); 477 if (tabIdFromWin != cancelContentJSTab) { 478 // The currently-executing content JS doesn't belong to the tab that 479 // requested cancellation of JS. Just return and let the JS continue. 480 return true; 481 } 482 483 nsresult rv; 484 nsCOMPtr<nsIURI> uri; 485 486 if (cancelContentJSNavigationURI) { 487 rv = NS_NewURI(getter_AddRefs(uri), cancelContentJSNavigationURI.value()); 488 if (NS_FAILED(rv)) { 489 return true; 490 } 491 } 492 493 bool canCancel; 494 rv = browserChild->CanCancelContentJS(cancelContentJSNavigationType, 495 cancelContentJSNavigationIndex, uri, 496 cancelContentJSEpoch, &canCancel); 497 if (NS_SUCCEEDED(rv) && canCancel) { 498 // Don't add this page to the BF cache, since we're cancelling its JS. 499 if (Document* doc = win->GetExtantDoc()) { 500 doc->DisallowBFCaching(); 501 } 502 503 return false; 504 } 505 } 506 507 return true; 508 } 509 510 void HangMonitorChild::AnnotateHang(BackgroundHangAnnotations& aAnnotations) { 511 if (mPaintWhileInterruptingJSActive) { 512 aAnnotations.AddAnnotation(u"PaintWhileInterruptingJS"_ns, true); 513 } 514 } 515 516 void HangMonitorChild::Shutdown() { 517 ReleaseAssertIsOnMainThread(); 518 519 BackgroundHangMonitor::UnregisterAnnotator(*this); 520 521 { 522 MonitorAutoLock lock(mMonitor); 523 while (!mShutdownDone) { 524 mMonitor.Wait(); 525 } 526 } 527 528 MOZ_ASSERT(sInstance == this); 529 sInstance = nullptr; 530 } 531 532 void HangMonitorChild::ShutdownOnThread() { 533 MOZ_RELEASE_ASSERT(IsOnThread()); 534 535 MonitorAutoLock lock(mMonitor); 536 mShutdownDone = true; 537 mMonitor.Notify(); 538 } 539 540 void HangMonitorChild::ActorDestroy(ActorDestroyReason aWhy) { 541 MOZ_RELEASE_ASSERT(IsOnThread()); 542 543 mIPCOpen = false; 544 545 // We use a task here to ensure that IPDL is finished with this 546 // HangMonitorChild before it gets deleted on the main thread. 547 Dispatch(NewNonOwningRunnableMethod("HangMonitorChild::ShutdownOnThread", 548 this, 549 &HangMonitorChild::ShutdownOnThread)); 550 } 551 552 mozilla::ipc::IPCResult HangMonitorChild::RecvTerminateScript() { 553 MOZ_RELEASE_ASSERT(IsOnThread()); 554 555 MonitorAutoLock lock(mMonitor); 556 mTerminateScript = true; 557 return IPC_OK(); 558 } 559 560 mozilla::ipc::IPCResult HangMonitorChild::RecvRequestContentJSInterrupt() { 561 MOZ_RELEASE_ASSERT(IsOnThread()); 562 563 // In order to cancel JS execution on shutdown, we expect that 564 // ProcessChild::NotifiedImpendingShutdown has been called before. 565 if (AppShutdown::IsShutdownImpending()) { 566 ProcessChild::AppendToIPCShutdownStateAnnotation( 567 "HangMonitorChild::RecvRequestContentJSInterrupt (expected)"_ns); 568 } else { 569 ProcessChild::AppendToIPCShutdownStateAnnotation( 570 "HangMonitorChild::RecvRequestContentJSInterrupt (unexpected)"_ns); 571 } 572 JS_RequestInterruptCallback(mContext); 573 return IPC_OK(); 574 } 575 576 mozilla::ipc::IPCResult HangMonitorChild::RecvBeginStartingDebugger() { 577 MOZ_RELEASE_ASSERT(IsOnThread()); 578 579 MonitorAutoLock lock(mMonitor); 580 mStartDebugger = true; 581 return IPC_OK(); 582 } 583 584 mozilla::ipc::IPCResult HangMonitorChild::RecvEndStartingDebugger() { 585 MOZ_RELEASE_ASSERT(IsOnThread()); 586 587 MonitorAutoLock lock(mMonitor); 588 mFinishedStartingDebugger = true; 589 return IPC_OK(); 590 } 591 592 mozilla::ipc::IPCResult HangMonitorChild::RecvPaintWhileInterruptingJS( 593 const TabId& aTabId) { 594 PROFILER_MARKER_UNTYPED("PaintWhileInterruptingJS", DOM, {}); 595 MOZ_RELEASE_ASSERT(IsOnThread()); 596 597 { 598 MonitorAutoLock lock(mMonitor); 599 MaybeStartPaintWhileInterruptingJS(); 600 mPaintWhileInterruptingJS = Some(true); 601 mPaintWhileInterruptingJSTab = aTabId; 602 } 603 604 JS_RequestInterruptCallback(mContext); 605 606 return IPC_OK(); 607 } 608 609 mozilla::ipc::IPCResult HangMonitorChild::RecvUnloadLayersWhileInterruptingJS( 610 const TabId& aTabId) { 611 MOZ_RELEASE_ASSERT(IsOnThread()); 612 613 { 614 MonitorAutoLock lock(mMonitor); 615 MaybeStartPaintWhileInterruptingJS(); 616 mPaintWhileInterruptingJS = Some(false); 617 mPaintWhileInterruptingJSTab = aTabId; 618 } 619 620 JS_RequestInterruptCallback(mContext); 621 622 return IPC_OK(); 623 } 624 625 void HangMonitorChild::MaybeStartPaintWhileInterruptingJS() { 626 mPaintWhileInterruptingJSActive = true; 627 } 628 629 void HangMonitorChild::ClearPaintWhileInterruptingJS() { 630 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 631 MOZ_RELEASE_ASSERT(XRE_IsContentProcess()); 632 mPaintWhileInterruptingJSActive = false; 633 } 634 635 mozilla::ipc::IPCResult HangMonitorChild::RecvCancelContentJSExecutionIfRunning( 636 const TabId& aTabId, const nsIRemoteTab::NavigationType& aNavigationType, 637 const int32_t& aNavigationIndex, 638 const mozilla::Maybe<nsCString>& aNavigationURI, const int32_t& aEpoch) { 639 MOZ_RELEASE_ASSERT(IsOnThread()); 640 641 { 642 MonitorAutoLock lock(mMonitor); 643 mCancelContentJS = true; 644 mCancelContentJSTab = aTabId; 645 mCancelContentJSNavigationType = aNavigationType; 646 mCancelContentJSNavigationIndex = aNavigationIndex; 647 mCancelContentJSNavigationURI = aNavigationURI; 648 mCancelContentJSEpoch = aEpoch; 649 } 650 651 JS_RequestInterruptCallback(mContext); 652 653 return IPC_OK(); 654 } 655 656 const char* DefineQoS(const nsIThread::QoSPriority& aQoSPriority) { 657 if (aQoSPriority == nsIThread::QOS_PRIORITY_LOW) { 658 return "BACKGROUND"; 659 } 660 // As of right now, all non-low QoS priorities default to the thread's normal 661 // priority. 662 return "NORMAL"; 663 } 664 665 mozilla::ipc::IPCResult HangMonitorChild::RecvSetMainThreadQoSPriority( 666 const nsIThread::QoSPriority& aQoSPriority) { 667 MOZ_RELEASE_ASSERT(IsOnThread()); 668 MOZ_LOG(gQoSLog, LogLevel::Debug, 669 ("Priority change %s recieved by content process.", 670 DefineQoS(aQoSPriority))); 671 672 #ifdef XP_MACOSX 673 // If the new priority is the background (low) priority, we can tell the OS to 674 // put the main thread on low-power cores. Alternately, if we are changing 675 // from the background to a higher priority, we change the main thread back to 676 // the |user-interactive| state, defined in MacOS's QoS documentation as 677 // reserved for main threads. 678 qos_class_t qosClass = aQoSPriority == nsIThread::QOS_PRIORITY_LOW 679 ? QOS_CLASS_BACKGROUND 680 : QOS_CLASS_USER_INTERACTIVE; 681 682 // We can't directly set the main thread's QoS class from off-main-thread. 683 // However, we can start a QoS class override to raise the QoS, then dispatch 684 // a runnable to set the QoS class and clear the override once complete. 685 pthread_override_t qosOverride = 686 pthread_override_qos_class_start_np(mMainPThread, qosClass, 0); 687 if (NS_FAILED(NS_DispatchToMainThread(NS_NewRunnableFunction( 688 "HangMonitorChild::RecvSetMainThreadQoSPriority", 689 [qosClass, qosOverride, aQoSPriority] { 690 MOZ_LOG( 691 gQoSLog, LogLevel::Debug, 692 ("Override %s sent to main thread.", DefineQoS(aQoSPriority))); 693 pthread_set_qos_class_self_np(qosClass, 0); 694 if (qosOverride) { 695 pthread_override_qos_class_end_np(qosOverride); 696 MOZ_LOG(gQoSLog, LogLevel::Debug, 697 ("Override %s removed from main thread.", 698 DefineQoS(aQoSPriority))); 699 } 700 })))) { 701 // If we fail to dispatch, go ahead and end the override anyway. 702 pthread_override_qos_class_end_np(qosOverride); 703 MOZ_LOG(gQoSLog, LogLevel::Debug, 704 ("Override %s removed from main thread.", DefineQoS(aQoSPriority))); 705 } 706 #endif 707 708 return IPC_OK(); 709 } 710 711 void HangMonitorChild::Bind(Endpoint<PProcessHangMonitorChild>&& aEndpoint) { 712 MOZ_RELEASE_ASSERT(IsOnThread()); 713 714 DebugOnly<bool> ok = aEndpoint.Bind(this); 715 MOZ_ASSERT(ok); 716 } 717 718 void HangMonitorChild::NotifySlowScriptAsync(TabId aTabId, 719 const nsCString& aFileName, 720 const nsString& aAddonId, 721 const double aDuration) { 722 if (mIPCOpen) { 723 (void)SendHangEvidence( 724 SlowScriptData(aTabId, aFileName, aAddonId, aDuration)); 725 } 726 } 727 728 HangMonitorChild::SlowScriptAction HangMonitorChild::NotifySlowScript( 729 nsIBrowserChild* aBrowserChild, const char* aFileName, 730 const nsString& aAddonId, const double aDuration) { 731 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 732 733 mSentReport = true; 734 735 { 736 MonitorAutoLock lock(mMonitor); 737 738 if (mTerminateScript) { 739 mTerminateScript = false; 740 return SlowScriptAction::Terminate; 741 } 742 743 if (mStartDebugger) { 744 mStartDebugger = false; 745 return SlowScriptAction::StartDebugger; 746 } 747 } 748 749 TabId id; 750 if (aBrowserChild) { 751 RefPtr<BrowserChild> browserChild = 752 static_cast<BrowserChild*>(aBrowserChild); 753 id = browserChild->GetTabId(); 754 } 755 nsAutoCString filename(aFileName); 756 757 Dispatch(NewNonOwningRunnableMethod<TabId, nsCString, nsString, double>( 758 "HangMonitorChild::NotifySlowScriptAsync", this, 759 &HangMonitorChild::NotifySlowScriptAsync, id, filename, aAddonId, 760 aDuration)); 761 return SlowScriptAction::Continue; 762 } 763 764 bool HangMonitorChild::IsDebuggerStartupComplete() { 765 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 766 767 MonitorAutoLock lock(mMonitor); 768 769 if (mFinishedStartingDebugger) { 770 mFinishedStartingDebugger = false; 771 return true; 772 } 773 774 return false; 775 } 776 777 void HangMonitorChild::ClearHang() { 778 MOZ_ASSERT(NS_IsMainThread()); 779 780 if (mSentReport) { 781 // bounce to background thread 782 Dispatch(NewNonOwningRunnableMethod("HangMonitorChild::ClearHangAsync", 783 this, 784 &HangMonitorChild::ClearHangAsync)); 785 786 MonitorAutoLock lock(mMonitor); 787 mSentReport = false; 788 mTerminateScript = false; 789 mStartDebugger = false; 790 mFinishedStartingDebugger = false; 791 } 792 } 793 794 void HangMonitorChild::ClearHangAsync() { 795 MOZ_RELEASE_ASSERT(IsOnThread()); 796 797 // bounce back to parent on background thread 798 if (mIPCOpen) { 799 (void)SendClearHang(); 800 } 801 } 802 803 /* HangMonitorParent implementation */ 804 805 HangMonitorParent::HangMonitorParent(ProcessHangMonitor* aMonitor) 806 : mHangMonitor(aMonitor), 807 mIPCOpen(true), 808 mMonitor("HangMonitorParent lock"), 809 mShutdownDone(false), 810 mMainThreadTaskFactory(this) { 811 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 812 } 813 814 void HangMonitorParent::Shutdown() { 815 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 816 817 MonitorAutoLock lock(mMonitor); 818 819 if (mProcess) { 820 mProcess->Clear(); 821 mProcess = nullptr; 822 } 823 824 nsresult rv = Dispatch( 825 NewNonOwningRunnableMethod("HangMonitorParent::ShutdownOnThread", this, 826 &HangMonitorParent::ShutdownOnThread)); 827 if (NS_WARN_IF(NS_FAILED(rv))) { 828 return; 829 } 830 831 while (!mShutdownDone) { 832 mMonitor.Wait(); 833 } 834 } 835 836 void HangMonitorParent::ShutdownOnThread() { 837 MOZ_RELEASE_ASSERT(IsOnThread()); 838 839 // mIPCOpen is only written from this thread, so need need to take the lock 840 // here. We'd be shooting ourselves in the foot, because ActorDestroy takes 841 // it. 842 if (mIPCOpen) { 843 Close(); 844 } 845 846 MonitorAutoLock lock(mMonitor); 847 mShutdownDone = true; 848 mMonitor.Notify(); 849 } 850 851 void HangMonitorParent::PaintWhileInterruptingJS(dom::BrowserParent* aTab) { 852 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 853 if (StaticPrefs::browser_tabs_remote_force_paint()) { 854 TabId id = aTab->GetTabId(); 855 Dispatch(NewNonOwningRunnableMethod<bool, TabId>( 856 "HangMonitorParent::PaintOrUnloadLayersWhileInterruptingJSOnThread ", 857 this, 858 &HangMonitorParent::PaintOrUnloadLayersWhileInterruptingJSOnThread, 859 true, id)); 860 } 861 } 862 863 void HangMonitorParent::UnloadLayersWhileInterruptingJS( 864 dom::BrowserParent* aTab) { 865 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 866 TabId id = aTab->GetTabId(); 867 Dispatch(NewNonOwningRunnableMethod<bool, TabId>( 868 "HangMonitorParent::PaintOrUnloadLayersWhileInterruptingJSOnThread ", 869 this, &HangMonitorParent::PaintOrUnloadLayersWhileInterruptingJSOnThread, 870 false, id)); 871 } 872 873 void HangMonitorParent::PaintOrUnloadLayersWhileInterruptingJSOnThread( 874 const bool aPaint, TabId aTabId) { 875 MOZ_RELEASE_ASSERT(IsOnThread()); 876 877 if (mIPCOpen) { 878 if (aPaint) { 879 (void)SendPaintWhileInterruptingJS(aTabId); 880 } else { 881 (void)SendUnloadLayersWhileInterruptingJS(aTabId); 882 } 883 } 884 } 885 886 void HangMonitorParent::CancelContentJSExecutionIfRunning( 887 dom::BrowserParent* aBrowserParent, 888 nsIRemoteTab::NavigationType aNavigationType, 889 const dom::CancelContentJSOptions& aCancelContentJSOptions) { 890 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 891 892 if (!aBrowserParent->CanCancelContentJS(aNavigationType, 893 aCancelContentJSOptions.mIndex, 894 aCancelContentJSOptions.mUri)) { 895 return; 896 } 897 898 TabId id = aBrowserParent->GetTabId(); 899 Dispatch(NewNonOwningRunnableMethod<TabId, nsIRemoteTab::NavigationType, 900 int32_t, nsIURI*, int32_t>( 901 "HangMonitorParent::CancelContentJSExecutionIfRunningOnThread", this, 902 &HangMonitorParent::CancelContentJSExecutionIfRunningOnThread, id, 903 aNavigationType, aCancelContentJSOptions.mIndex, 904 aCancelContentJSOptions.mUri, aCancelContentJSOptions.mEpoch)); 905 } 906 907 void HangMonitorParent::CancelContentJSExecutionIfRunningOnThread( 908 TabId aTabId, nsIRemoteTab::NavigationType aNavigationType, 909 int32_t aNavigationIndex, nsIURI* aNavigationURI, int32_t aEpoch) { 910 MOZ_RELEASE_ASSERT(IsOnThread()); 911 912 mozilla::Maybe<nsCString> spec; 913 if (aNavigationURI) { 914 nsAutoCString tmp; 915 nsresult rv = aNavigationURI->GetSpec(tmp); 916 if (NS_SUCCEEDED(rv)) { 917 spec.emplace(tmp); 918 } 919 } 920 921 if (mIPCOpen) { 922 (void)SendCancelContentJSExecutionIfRunning(aTabId, aNavigationType, 923 aNavigationIndex, spec, aEpoch); 924 } 925 } 926 927 void HangMonitorParent::SetMainThreadQoSPriority( 928 nsIThread::QoSPriority aQoSPriority) { 929 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 930 #ifdef XP_MACOSX // Should not be using outside of MacOS. 931 932 Dispatch(NewNonOwningRunnableMethod<nsIThread::QoSPriority>( 933 "HangMonitorParent::SetMainThreadQoSPriorityOnThread", this, 934 &HangMonitorParent::SetMainThreadQoSPriorityOnThread, aQoSPriority)); 935 #endif 936 } 937 938 #ifdef XP_MACOSX 939 void HangMonitorParent::SetMainThreadQoSPriorityOnThread( 940 nsIThread::QoSPriority aQoSPriority) { 941 MOZ_RELEASE_ASSERT(IsOnThread()); 942 if (mIPCOpen) { 943 (void)SendSetMainThreadQoSPriority(aQoSPriority); 944 } 945 } 946 #endif 947 948 void HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy) { 949 MOZ_RELEASE_ASSERT(IsOnThread()); 950 mIPCOpen = false; 951 } 952 953 void HangMonitorParent::Bind(Endpoint<PProcessHangMonitorParent>&& aEndpoint) { 954 MOZ_RELEASE_ASSERT(IsOnThread()); 955 956 DebugOnly<bool> ok = aEndpoint.Bind(this); 957 MOZ_ASSERT(ok); 958 } 959 960 void HangMonitorParent::SendHangNotification( 961 const SlowScriptData& aSlowScriptData, const nsString& aBrowserDumpId) { 962 // chrome process, main thread 963 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 964 965 nsString dumpId; 966 967 // We already have a full minidump; go ahead and use it. 968 dumpId = aBrowserDumpId; 969 970 mProcess->SetSlowScriptData(aSlowScriptData, dumpId); 971 972 nsCOMPtr<nsIObserverService> observerService = 973 mozilla::services::GetObserverService(); 974 observerService->NotifyObservers(mProcess, "process-hang-report", nullptr); 975 } 976 977 void HangMonitorParent::ClearHangNotification() { 978 // chrome process, main thread 979 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 980 981 nsCOMPtr<nsIObserverService> observerService = 982 mozilla::services::GetObserverService(); 983 observerService->NotifyObservers(mProcess, "clear-hang-report", nullptr); 984 985 mProcess->ClearHang(); 986 } 987 988 mozilla::ipc::IPCResult HangMonitorParent::RecvHangEvidence( 989 const SlowScriptData& aSlowScriptData) { 990 // chrome process, background thread 991 MOZ_RELEASE_ASSERT(IsOnThread()); 992 993 if (!StaticPrefs::dom_ipc_reportProcessHangs()) { 994 return IPC_OK(); 995 } 996 997 #ifdef XP_WIN 998 // Don't report hangs if we're debugging the process. You can comment this 999 // line out for testing purposes. 1000 if (IsDebuggerPresent()) { 1001 return IPC_OK(); 1002 } 1003 #endif 1004 1005 // Before we wake up the browser main thread we want to take a 1006 // browser minidump. 1007 nsAutoString crashId; 1008 1009 mHangMonitor->InitiateCPOWTimeout(); 1010 1011 MonitorAutoLock lock(mMonitor); 1012 1013 NS_DispatchToMainThread(mMainThreadTaskFactory.NewRunnableMethod( 1014 &HangMonitorParent::SendHangNotification, aSlowScriptData, crashId)); 1015 1016 return IPC_OK(); 1017 } 1018 1019 mozilla::ipc::IPCResult HangMonitorParent::RecvClearHang() { 1020 // chrome process, background thread 1021 MOZ_RELEASE_ASSERT(IsOnThread()); 1022 1023 if (!StaticPrefs::dom_ipc_reportProcessHangs()) { 1024 return IPC_OK(); 1025 } 1026 1027 mHangMonitor->InitiateCPOWTimeout(); 1028 1029 MonitorAutoLock lock(mMonitor); 1030 1031 NS_DispatchToMainThread(mMainThreadTaskFactory.NewRunnableMethod( 1032 &HangMonitorParent::ClearHangNotification)); 1033 1034 return IPC_OK(); 1035 } 1036 1037 void HangMonitorParent::TerminateScript() { 1038 MOZ_RELEASE_ASSERT(IsOnThread()); 1039 1040 if (mIPCOpen) { 1041 (void)SendTerminateScript(); 1042 } 1043 } 1044 1045 void HangMonitorParent::BeginStartingDebugger() { 1046 MOZ_RELEASE_ASSERT(IsOnThread()); 1047 1048 if (mIPCOpen) { 1049 (void)SendBeginStartingDebugger(); 1050 } 1051 } 1052 1053 void HangMonitorParent::EndStartingDebugger() { 1054 MOZ_RELEASE_ASSERT(IsOnThread()); 1055 1056 if (mIPCOpen) { 1057 (void)SendEndStartingDebugger(); 1058 } 1059 } 1060 1061 /* HangMonitoredProcess implementation */ 1062 1063 NS_IMPL_ISUPPORTS(HangMonitoredProcess, nsIHangReport) 1064 1065 NS_IMETHODIMP 1066 HangMonitoredProcess::GetHangDuration(double* aHangDuration) { 1067 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1068 *aHangDuration = mSlowScriptData.duration(); 1069 return NS_OK; 1070 } 1071 1072 NS_IMETHODIMP 1073 HangMonitoredProcess::GetScriptBrowser(Element** aBrowser) { 1074 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1075 TabId tabId = mSlowScriptData.tabId(); 1076 if (!mContentParent) { 1077 return NS_ERROR_NOT_AVAILABLE; 1078 } 1079 1080 nsTArray<PBrowserParent*> tabs; 1081 mContentParent->ManagedPBrowserParent(tabs); 1082 for (size_t i = 0; i < tabs.Length(); i++) { 1083 BrowserParent* tp = BrowserParent::GetFrom(tabs[i]); 1084 if (tp->GetTabId() == tabId) { 1085 RefPtr<Element> node = tp->GetOwnerElement(); 1086 node.forget(aBrowser); 1087 return NS_OK; 1088 } 1089 } 1090 1091 *aBrowser = nullptr; 1092 return NS_OK; 1093 } 1094 1095 NS_IMETHODIMP 1096 HangMonitoredProcess::GetScriptFileName(nsACString& aFileName) { 1097 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1098 aFileName = mSlowScriptData.filename(); 1099 return NS_OK; 1100 } 1101 1102 NS_IMETHODIMP 1103 HangMonitoredProcess::GetAddonId(nsAString& aAddonId) { 1104 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1105 aAddonId = mSlowScriptData.addonId(); 1106 return NS_OK; 1107 } 1108 1109 NS_IMETHODIMP 1110 HangMonitoredProcess::TerminateScript() { 1111 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1112 if (!mActor) { 1113 return NS_ERROR_UNEXPECTED; 1114 } 1115 1116 ProcessHangMonitor::Get()->Dispatch( 1117 NewNonOwningRunnableMethod("HangMonitorParent::TerminateScript", mActor, 1118 &HangMonitorParent::TerminateScript)); 1119 return NS_OK; 1120 } 1121 1122 NS_IMETHODIMP 1123 HangMonitoredProcess::BeginStartingDebugger() { 1124 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1125 if (!mActor) { 1126 return NS_ERROR_UNEXPECTED; 1127 } 1128 1129 ProcessHangMonitor::Get()->Dispatch(NewNonOwningRunnableMethod( 1130 "HangMonitorParent::BeginStartingDebugger", mActor, 1131 &HangMonitorParent::BeginStartingDebugger)); 1132 return NS_OK; 1133 } 1134 1135 NS_IMETHODIMP 1136 HangMonitoredProcess::EndStartingDebugger() { 1137 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1138 if (!mActor) { 1139 return NS_ERROR_UNEXPECTED; 1140 } 1141 1142 ProcessHangMonitor::Get()->Dispatch(NewNonOwningRunnableMethod( 1143 "HangMonitorParent::EndStartingDebugger", mActor, 1144 &HangMonitorParent::EndStartingDebugger)); 1145 return NS_OK; 1146 } 1147 1148 NS_IMETHODIMP 1149 HangMonitoredProcess::IsReportForBrowserOrChildren(nsFrameLoader* aFrameLoader, 1150 bool* aResult) { 1151 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1152 MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); 1153 1154 if (!mActor) { 1155 *aResult = false; 1156 return NS_OK; 1157 } 1158 1159 NS_ENSURE_STATE(aFrameLoader); 1160 1161 AutoTArray<RefPtr<BrowsingContext>, 10> bcs; 1162 bcs.AppendElement(aFrameLoader->GetExtantBrowsingContext()); 1163 while (!bcs.IsEmpty()) { 1164 RefPtr<BrowsingContext> bc = bcs[bcs.Length() - 1]; 1165 bcs.RemoveLastElement(); 1166 if (!bc) { 1167 continue; 1168 } 1169 if (mContentParent == bc->Canonical()->GetContentParent()) { 1170 *aResult = true; 1171 return NS_OK; 1172 } 1173 bc->GetChildren(bcs); 1174 } 1175 1176 *aResult = false; 1177 return NS_OK; 1178 } 1179 1180 NS_IMETHODIMP 1181 HangMonitoredProcess::UserCanceled() { return NS_OK; } 1182 1183 NS_IMETHODIMP 1184 HangMonitoredProcess::GetChildID(uint64_t* aChildID) { 1185 if (!mContentParent) { 1186 return NS_ERROR_NOT_AVAILABLE; 1187 } 1188 *aChildID = mContentParent->ChildID(); 1189 return NS_OK; 1190 } 1191 1192 static bool InterruptCallback(JSContext* cx) { 1193 AssertIsOnMainThread(); 1194 if (HangMonitorChild* child = HangMonitorChild::Get()) { 1195 return child->InterruptCallback(); 1196 } 1197 1198 return true; 1199 } 1200 1201 ProcessHangMonitor* ProcessHangMonitor::sInstance; 1202 1203 ProcessHangMonitor::ProcessHangMonitor() : mCPOWTimeout(false) { 1204 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1205 1206 if (XRE_IsContentProcess()) { 1207 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 1208 obs->AddObserver(this, "xpcom-shutdown", false); 1209 } 1210 1211 if (NS_FAILED(NS_NewNamedThread("ProcessHangMon", getter_AddRefs(mThread)))) { 1212 mThread = nullptr; 1213 } 1214 #ifdef XP_MACOSX 1215 // On MacOS, ensure the priority is high enough to handle dispatches at 1216 // high cpu load. USER_INITIATED class threads are prioritized just below 1217 // the main thread. 1218 mThread->Dispatch(NS_NewRunnableFunction( 1219 "ProcessHangMonitor::SetPriority", 1220 [] { pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0); })); 1221 #endif 1222 } 1223 1224 ProcessHangMonitor::~ProcessHangMonitor() { 1225 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1226 1227 MOZ_ASSERT(sInstance == this); 1228 sInstance = nullptr; 1229 1230 mThread->Shutdown(); 1231 mThread = nullptr; 1232 } 1233 1234 ProcessHangMonitor* ProcessHangMonitor::GetOrCreate() { 1235 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1236 if (!sInstance) { 1237 sInstance = new ProcessHangMonitor(); 1238 } 1239 return sInstance; 1240 } 1241 1242 NS_IMPL_ISUPPORTS(ProcessHangMonitor, nsIObserver) 1243 1244 NS_IMETHODIMP 1245 ProcessHangMonitor::Observe(nsISupports* aSubject, const char* aTopic, 1246 const char16_t* aData) { 1247 ReleaseAssertIsOnMainThread(); 1248 if (!strcmp(aTopic, "xpcom-shutdown")) { 1249 if (RefPtr<HangMonitorChild> child = HangMonitorChild::Get()) { 1250 child->Shutdown(); 1251 } 1252 1253 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 1254 obs->RemoveObserver(this, "xpcom-shutdown"); 1255 } 1256 return NS_OK; 1257 } 1258 1259 ProcessHangMonitor::SlowScriptAction ProcessHangMonitor::NotifySlowScript( 1260 nsIBrowserChild* aBrowserChild, const char* aFileName, 1261 const nsString& aAddonId, const double aDuration) { 1262 ReleaseAssertIsOnMainThread(); 1263 return HangMonitorChild::Get()->NotifySlowScript(aBrowserChild, aFileName, 1264 aAddonId, aDuration); 1265 } 1266 1267 bool ProcessHangMonitor::IsDebuggerStartupComplete() { 1268 ReleaseAssertIsOnMainThread(); 1269 return HangMonitorChild::Get()->IsDebuggerStartupComplete(); 1270 } 1271 1272 bool ProcessHangMonitor::ShouldTimeOutCPOWs() { 1273 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1274 1275 if (mCPOWTimeout) { 1276 mCPOWTimeout = false; 1277 return true; 1278 } 1279 return false; 1280 } 1281 1282 void ProcessHangMonitor::InitiateCPOWTimeout() { 1283 MOZ_RELEASE_ASSERT(IsOnThread()); 1284 mCPOWTimeout = true; 1285 } 1286 1287 static already_AddRefed<PProcessHangMonitorParent> CreateHangMonitorParent( 1288 ContentParent* aContentParent, 1289 Endpoint<PProcessHangMonitorParent>&& aEndpoint) { 1290 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1291 1292 ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate(); 1293 RefPtr<HangMonitorParent> parent = new HangMonitorParent(monitor); 1294 1295 auto* process = new HangMonitoredProcess(parent, aContentParent); 1296 parent->SetProcess(process); 1297 1298 monitor->Dispatch( 1299 NewNonOwningRunnableMethod<Endpoint<PProcessHangMonitorParent>&&>( 1300 "HangMonitorParent::Bind", parent, &HangMonitorParent::Bind, 1301 std::move(aEndpoint))); 1302 1303 return parent.forget(); 1304 } 1305 1306 void mozilla::CreateHangMonitorChild( 1307 Endpoint<PProcessHangMonitorChild>&& aEndpoint) { 1308 ReleaseAssertIsOnMainThread(); 1309 1310 JSContext* cx = danger::GetJSContext(); 1311 JS_AddInterruptCallback(cx, InterruptCallback); 1312 1313 ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate(); 1314 HangMonitorChild::CreateAndBind(monitor, std::move(aEndpoint)); 1315 } 1316 1317 nsresult ProcessHangMonitor::Dispatch(already_AddRefed<nsIRunnable> aRunnable) { 1318 return mThread->Dispatch(std::move(aRunnable), 1319 nsIEventTarget::NS_DISPATCH_NORMAL); 1320 } 1321 1322 bool ProcessHangMonitor::IsOnThread() { 1323 bool on; 1324 return NS_SUCCEEDED(mThread->IsOnCurrentThread(&on)) && on; 1325 } 1326 1327 /* static */ 1328 already_AddRefed<PProcessHangMonitorParent> ProcessHangMonitor::AddProcess( 1329 ContentParent* aContentParent) { 1330 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1331 1332 if (!StaticPrefs::dom_ipc_processHangMonitor_AtStartup()) { 1333 return nullptr; 1334 } 1335 1336 Endpoint<PProcessHangMonitorParent> parent; 1337 Endpoint<PProcessHangMonitorChild> child; 1338 nsresult rv; 1339 rv = PProcessHangMonitor::CreateEndpoints(&parent, &child); 1340 if (NS_FAILED(rv)) { 1341 MOZ_ASSERT(false, "PProcessHangMonitor::CreateEndpoints failed"); 1342 return nullptr; 1343 } 1344 1345 if (!aContentParent->SendInitProcessHangMonitor(std::move(child))) { 1346 MOZ_ASSERT(false); 1347 return nullptr; 1348 } 1349 1350 return CreateHangMonitorParent(aContentParent, std::move(parent)); 1351 } 1352 1353 /* static */ 1354 void ProcessHangMonitor::RemoveProcess(PProcessHangMonitorParent* aParent) { 1355 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1356 auto parent = static_cast<HangMonitorParent*>(aParent); 1357 parent->Shutdown(); 1358 } 1359 1360 /* static */ 1361 void ProcessHangMonitor::ClearHang() { 1362 AssertIsOnMainThread(); 1363 if (HangMonitorChild* child = HangMonitorChild::Get()) { 1364 child->ClearHang(); 1365 } 1366 } 1367 1368 /* static */ 1369 void ProcessHangMonitor::PaintWhileInterruptingJS( 1370 PProcessHangMonitorParent* aParent, dom::BrowserParent* aTab) { 1371 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1372 auto* parent = static_cast<HangMonitorParent*>(aParent); 1373 parent->PaintWhileInterruptingJS(aTab); 1374 } 1375 1376 /* static */ 1377 void ProcessHangMonitor::UnloadLayersWhileInterruptingJS( 1378 PProcessHangMonitorParent* aParent, dom::BrowserParent* aTab) { 1379 MOZ_RELEASE_ASSERT(NS_IsMainThread()); 1380 auto* parent = static_cast<HangMonitorParent*>(aParent); 1381 parent->UnloadLayersWhileInterruptingJS(aTab); 1382 } 1383 1384 /* static */ 1385 void ProcessHangMonitor::ClearPaintWhileInterruptingJS() { 1386 ReleaseAssertIsOnMainThread(); 1387 MOZ_RELEASE_ASSERT(XRE_IsContentProcess()); 1388 1389 if (HangMonitorChild* child = HangMonitorChild::Get()) { 1390 child->ClearPaintWhileInterruptingJS(); 1391 } 1392 } 1393 1394 /* static */ 1395 void ProcessHangMonitor::MaybeStartPaintWhileInterruptingJS() { 1396 ReleaseAssertIsOnMainThread(); 1397 MOZ_RELEASE_ASSERT(XRE_IsContentProcess()); 1398 1399 if (HangMonitorChild* child = HangMonitorChild::Get()) { 1400 child->MaybeStartPaintWhileInterruptingJS(); 1401 } 1402 } 1403 1404 /* static */ 1405 void ProcessHangMonitor::CancelContentJSExecutionIfRunning( 1406 PProcessHangMonitorParent* aParent, dom::BrowserParent* aBrowserParent, 1407 nsIRemoteTab::NavigationType aNavigationType, 1408 const dom::CancelContentJSOptions& aCancelContentJSOptions) { 1409 ReleaseAssertIsOnMainThread(); 1410 auto* parent = static_cast<HangMonitorParent*>(aParent); 1411 parent->CancelContentJSExecutionIfRunning(aBrowserParent, aNavigationType, 1412 aCancelContentJSOptions); 1413 } 1414 1415 /* static */ 1416 void ProcessHangMonitor::SetMainThreadQoSPriority( 1417 PProcessHangMonitorParent* aParent, nsIThread::QoSPriority aQoSPriority) { 1418 ReleaseAssertIsOnMainThread(); 1419 auto* parent = static_cast<HangMonitorParent*>(aParent); 1420 parent->SetMainThreadQoSPriority(aQoSPriority); 1421 }