Bug 1118618 - [e10s] Slow script/plugin hang UI (r=mrbkap,mconley)
authorBill McCloskey <wmccloskey@mozilla.com>
Fri, 16 Jan 2015 10:11:18 -0800
changeset 224301 e12319cbdcd386b3d2177745e0be9d29aabf5ebf
parent 224300 cf5c463cd993582d4c31c2743bbce4ef5556837c
child 224302 dc36cbaa1cc395cfa1a8818c65ab847206474521
push id28122
push userkwierso@gmail.com
push dateSat, 17 Jan 2015 01:33:15 +0000
treeherdermozilla-central@369a8f14ccf8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap, mconley
bugs1118618
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1118618 - [e10s] Slow script/plugin hang UI (r=mrbkap,mconley)
browser/app/profile/firefox.js
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/tabbrowser.xml
browser/components/nsBrowserGlue.js
browser/devtools/framework/gDevTools.jsm
browser/locales/en-US/chrome/browser/browser.dtd
browser/locales/en-US/chrome/browser/browser.properties
browser/modules/ProcessHangMonitor.jsm
browser/modules/moz.build
dom/base/SlowScriptDebug.js
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/base/nsISlowScriptDebug.idl
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/ipc/PProcessHangMonitor.ipdl
dom/ipc/ProcessHangMonitor.cpp
dom/ipc/ProcessHangMonitor.h
dom/ipc/ProcessHangMonitorIPC.h
dom/ipc/moz.build
dom/ipc/nsIHangReport.idl
dom/plugins/base/nsPluginHost.h
dom/plugins/ipc/PluginBridge.h
dom/plugins/ipc/PluginModuleParent.cpp
dom/plugins/ipc/PluginModuleParent.h
js/xpconnect/src/XPCJSRuntime.cpp
modules/libpref/init/all.js
testing/marionette/client/marionette/geckoinstance.py
testing/profiles/prefs_general.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1798,10 +1798,16 @@ pref("print.enable_e10s_testing", true);
 #ifdef NIGHTLY_BUILD
 // Enable e10s add-on interposition by default.
 pref("extensions.interposition.enabled", true);
 pref("extensions.interposition.prefetching", true);
 #endif
 
 pref("browser.defaultbrowser.notificationbar", false);
 
-// How many milliseconds to wait for a CPOW response from the child process.
-pref("dom.ipc.cpow.timeout", 0);
+// How often to check for CPOW timeouts. CPOWs are only timed out by
+// the hang monitor.
+pref("dom.ipc.cpow.timeout", 500);
+
+// Enable e10s hang monitoring (slow script checking and plugin hang
+// detection).
+pref("dom.ipc.processHangMonitor", true);
+pref("dom.ipc.reportProcessHangs", true);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -144,16 +144,19 @@ XPCOMUtils.defineLazyGetter(this, "Brows
 });
 
 XPCOMUtils.defineLazyModuleGetter(this, "Social",
   "resource:///modules/Social.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
   "resource://gre/modules/PageThumbs.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "ProcessHangMonitor",
+  "resource:///modules/ProcessHangMonitor.jsm");
+
 #ifdef MOZ_SAFE_BROWSING
 XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
   "resource://gre/modules/SafeBrowsing.jsm");
 #endif
 
 XPCOMUtils.defineLazyModuleGetter(this, "gCustomizationTabPreloader",
   "resource:///modules/CustomizationTabPreloader.jsm", "CustomizationTabPreloader");
 
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -329,16 +329,36 @@
            noautofocus="true"/>
 
     <panel id="loop-panel"
            class="loop-panel social-panel"
            type="arrow"
            orient="horizontal"
            hidden="true"/>
 
+    <menupopup id="processHangOptions"
+               onpopupshowing="ProcessHangMonitor.refreshMenu(window);">
+      <menuitem id="processHangTerminateScript"
+                oncommand="ProcessHangMonitor.terminateScript(window)"
+                accesskey="&processHang.terminateScript.accessKey;"
+                label="&processHang.terminateScript.label;"/>
+      <menuitem id="processHangDebugScript"
+                oncommand="ProcessHangMonitor.debugScript(window)"
+                accesskey="&processHang.debugScript.accessKey;"
+                label="&processHang.debugScript.label;"/>
+      <menuitem id="processHangTerminatePlugin"
+                oncommand="ProcessHangMonitor.terminatePlugin(window)"
+                accesskey="&processHang.terminatePlugin.accessKey;"
+                label="&processHang.terminatePlugin.label;"/>
+      <menuitem id="processHangTerminateProcess"
+                oncommand="ProcessHangMonitor.terminateProcess(window)"
+                accesskey="&processHang.terminateProcess.accessKey;"
+                label="&processHang.terminateProcess.label;"/>
+    </menupopup>
+
     <menupopup id="toolbar-context-menu"
                onpopupshowing="onViewToolbarsPopupShowing(event, document.getElementById('viewToolbarsMenuSeparator'));">
       <menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
                 accesskey="&customizeMenu.moveToPanel.accesskey;"
                 label="&customizeMenu.moveToPanel.label;"
                 contexttype="toolbaritem"
                 class="customize-context-moveToPanel"/>
       <menuitem oncommand="gCustomizeMode.removeFromArea(document.popupNode)"
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1487,16 +1487,20 @@
             } else {
               tab.removeAttribute("remote");
               aBrowser.messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: tab.pinned })
             }
 
             if (wasActive)
               aBrowser.focus();
 
+            let evt = document.createEvent("Events");
+            evt.initEvent("TabRemotenessChange", true, false);
+            tab.dispatchEvent(evt);
+
             return true;
           ]]>
         </body>
       </method>
 
       <method name="updateBrowserRemotenessByURL">
         <parameter name="aBrowser"/>
         <parameter name="aURL"/>
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -54,16 +54,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/NewTabUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizationTabPreloader",
                                   "resource:///modules/CustomizationTabPreloader.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PdfJs",
                                   "resource://pdf.js/PdfJs.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "ProcessHangMonitor",
+                                  "resource:///modules/ProcessHangMonitor.jsm");
+
 #ifdef NIGHTLY_BUILD
 XPCOMUtils.defineLazyModuleGetter(this, "ShumwayUtils",
                                   "resource://shumway/ShumwayUtils.jsm");
 #endif
 
 XPCOMUtils.defineLazyModuleGetter(this, "webrtcUI",
                                   "resource:///modules/webrtcUI.jsm");
 
@@ -755,16 +758,18 @@ BrowserGlue.prototype = {
     if (WINTASKBAR_CONTRACTID in Cc &&
         Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) {
       let temp = {};
       Cu.import("resource:///modules/WindowsJumpLists.jsm", temp);
       temp.WinTaskbarJumpList.startup();
     }
 #endif
 
+    ProcessHangMonitor.init();
+
     // A channel for "remote troubleshooting" code...
     let channel = new WebChannel("remote-troubleshooting", "remote-troubleshooting");
     channel.listen((id, data, target) => {
       if (data.command == "request") {
         let {Troubleshoot} = Cu.import("resource://gre/modules/Troubleshoot.jsm", {});
         Troubleshoot.snapshot(data => {
           // for privacy we remove crash IDs and all preferences (but bug 1091944
           // exists to expose prefs once we are confident of privacy implications)
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -849,70 +849,88 @@ let gDevToolsBrowser = {
    * Hook the JS debugger tool to the "Debug Script" button of the slow script
    * dialog.
    */
   setSlowScriptDebugHandler: function DT_setSlowScriptDebugHandler() {
     let debugService = Cc["@mozilla.org/dom/slow-script-debug;1"]
                          .getService(Ci.nsISlowScriptDebug);
     let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
 
-    debugService.activationHandler = function(aWindow) {
-      let chromeWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                                .getInterface(Ci.nsIWebNavigation)
-                                .QueryInterface(Ci.nsIDocShellTreeItem)
-                                .rootTreeItem
-                                .QueryInterface(Ci.nsIInterfaceRequestor)
-                                .getInterface(Ci.nsIDOMWindow)
-                                .QueryInterface(Ci.nsIDOMChromeWindow);
-      let target = devtools.TargetFactory.forTab(chromeWindow.gBrowser.selectedTab);
+    function slowScriptDebugHandler(aTab, aCallback) {
+      let target = devtools.TargetFactory.forTab(aTab);
 
-      let setupFinished = false;
       gDevTools.showToolbox(target, "jsdebugger").then(toolbox => {
         let threadClient = toolbox.getCurrentPanel().panelWin.gThreadClient;
 
         // Break in place, which means resuming the debuggee thread and pausing
         // right before the next step happens.
         switch (threadClient.state) {
           case "paused":
             // When the debugger is already paused.
             threadClient.breakOnNext();
-            setupFinished = true;
+            aCallback();
             break;
           case "attached":
             // When the debugger is already open.
             threadClient.interrupt(() => {
               threadClient.breakOnNext();
-              setupFinished = true;
+              aCallback();
             });
             break;
           case "resuming":
             // The debugger is newly opened.
             threadClient.addOneTimeListener("resumed", () => {
               threadClient.interrupt(() => {
                 threadClient.breakOnNext();
-                setupFinished = true;
+                aCallback();
               });
             });
             break;
           default:
             throw Error("invalid thread client state in slow script debug handler: " +
                         threadClient.state);
           }
       });
+    }
+
+    debugService.activationHandler = function(aWindow) {
+      let chromeWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                                .getInterface(Ci.nsIWebNavigation)
+                                .QueryInterface(Ci.nsIDocShellTreeItem)
+                                .rootTreeItem
+                                .QueryInterface(Ci.nsIInterfaceRequestor)
+                                .getInterface(Ci.nsIDOMWindow)
+                                .QueryInterface(Ci.nsIDOMChromeWindow);
+
+      let setupFinished = false;
+      slowScriptDebugHandler(chromeWindow.gBrowser.selectedTab,
+                             () => { setupFinished = true; });
 
       // Don't return from the interrupt handler until the debugger is brought
       // up; no reason to continue executing the slow script.
       let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIDOMWindowUtils);
       utils.enterModalState();
       while (!setupFinished) {
         tm.currentThread.processNextEvent(true);
       }
       utils.leaveModalState();
     };
+
+    debugService.remoteActivationHandler = function(aBrowser, aCallback) {
+      let chromeWindow = aBrowser.ownerDocument.defaultView;
+      let tab = chromeWindow.gBrowser.getTabForBrowser(aBrowser);
+      chromeWindow.gBrowser.selected = tab;
+
+      function callback() {
+        aCallback.finishDebuggerStartup();
+      }
+
+      slowScriptDebugHandler(tab, callback);
+    };
   },
 
   /**
    * Unset the slow script debug handler.
    */
   unsetSlowScriptDebugHandler: function DT_unsetSlowScriptDebugHandler() {
     let debugService = Cc["@mozilla.org/dom/slow-script-debug;1"]
                          .getService(Ci.nsISlowScriptDebug);
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -826,8 +826,16 @@ just addresses the organization to follo
 
 <!ENTITY panicButton.view.undoWarning             "This action cannot be undone.">
 <!ENTITY panicButton.view.forgetButton            "Forget!">
 
 <!ENTITY panicButton.thankyou.msg1                "Your recent history is cleared.">
 <!ENTITY panicButton.thankyou.msg2                "Safe browsing!">
 <!ENTITY panicButton.thankyou.buttonlabel         "Thanks!">
 
+<!ENTITY processHang.terminateScript.label        "Stop Script">
+<!ENTITY processHang.terminateScript.accessKey    "S">
+<!ENTITY processHang.debugScript.label            "Debug Script">
+<!ENTITY processHang.debugScript.accessKey        "D">
+<!ENTITY processHang.terminatePlugin.label        "Kill Plugin">
+<!ENTITY processHang.terminatePlugin.accessKey    "P">
+<!ENTITY processHang.terminateProcess.label       "Kill Web Process">
+<!ENTITY processHang.terminateProcess.accessKey   "K">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -430,16 +430,21 @@ syncPromoNotification.addons.description
 # The final space separates this text from the Learn More link.
 syncPromoNotification.addons-sync-disabled.description=You can use your %S account to synchronize add-ons across multiple devices.\u0020
 
 # Mozilla data reporting notification (Telemetry, Firefox Health Report, etc)
 dataReportingNotification.message       = %1$S automatically sends some data to %2$S so that we can improve your experience.
 dataReportingNotification.button.label  = Choose What I Share
 dataReportingNotification.button.accessKey  = C
 
+# Process hang reporter
+processHang.message = A web page is causing %1$S to run slowly. What would you like to do?
+processHang.button.label = Options
+processHang.button.accessKey = O
+
 # Webapps notification popup
 webapps.install = Install
 webapps.install.accesskey = I
 #LOCALIZATION NOTE (webapps.requestInstall) %1$S is the web app name, %2$S is the site from which the web app is installed
 webapps.requestInstall = Do you want to install "%1$S" from this site (%2$S)?
 webapps.install.success = Application Installed
 webapps.install.inprogress = Installation in progress
 webapps.uninstall = Uninstall
new file mode 100644
--- /dev/null
+++ b/browser/modules/ProcessHangMonitor.jsm
@@ -0,0 +1,309 @@
+/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+"use strict";
+
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = ["ProcessHangMonitor"];
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+/**
+ * This JSM is responsible for observing content process hang reports
+ * and asking the user what to do about them. See nsIHangReport for
+ * the platform interface.
+ */
+
+/**
+ * If a hang hasn't been reported for more than 5 seconds, assume the
+ * content process has gotten unstuck (and hide the hang notification).
+ */
+const HANG_EXPIRATION_TIME = 5000;
+
+let ProcessHangMonitor = {
+  /**
+   * Collection of hang reports that haven't expired or been dismissed
+   * by the user. The keys are nsIHangReports and values keys are
+   * timers. Each time the hang is reported, the timer is refreshed so
+   * it expires after HANG_EXPIRATION_TIME.
+   */
+  _activeReports: new Map(),
+
+  /**
+   * Initialize hang reporting. Called once in the parent process.
+   */
+  init: function() {
+    Services.obs.addObserver(this, "process-hang-report", false);
+    Services.obs.addObserver(this, "xpcom-shutdown", false);
+    Services.ww.registerNotification(this);
+  },
+
+  /**
+   * Terminate JavaScript associated with the hang being reported for
+   * the selected browser in |win|.
+   */
+  terminateScript: function(win) {
+    this.handleUserInput(win, report => report.terminateScript());
+  },
+
+  /**
+   * Start devtools debugger for JavaScript associated with the hang
+   * being reported for the selected browser in |win|.
+   */
+  debugScript: function(win) {
+    this.handleUserInput(win, report => {
+      function callback() {
+        report.endStartingDebugger();
+      }
+
+      report.beginStartingDebugger();
+
+      let svc = Cc["@mozilla.org/dom/slow-script-debug;1"].getService(Ci.nsISlowScriptDebug);
+      let handler = svc.remoteActivationHandler;
+      handler.handleSlowScriptDebug(report.scriptBrowser, callback);
+    });
+  },
+
+  /**
+   * Kill the plugin process causing the hang being reported for the
+   * selected browser in |win|.
+   */
+  terminatePlugin: function(win) {
+    this.handleUserInput(win, report => report.terminatePlugin());
+  },
+
+  /**
+   * Kill the content process causing the hang being reported for the selected
+   * browser in |win|.
+   */
+  terminateProcess: function(win) {
+    this.handleUserInput(win, report => report.terminateProcess());
+  },
+
+  /**
+   * Update the "Options" pop-up menu for the hang notification
+   * associated with the selected browser in |win|. The menu should
+   * display only options that are relevant to the given report.
+   */
+  refreshMenu: function(win) {
+    let report = this.findReport(win.gBrowser.selectedBrowser);
+    if (!report) {
+      return;
+    }
+
+    function setVisible(id, visible) {
+      let item = win.document.getElementById(id);
+      item.hidden = !visible;
+    }
+
+    if (report.hangType == report.SLOW_SCRIPT) {
+      setVisible("processHangTerminateScript", true);
+      setVisible("processHangDebugScript", true);
+      setVisible("processHangTerminatePlugin", false);
+    } else if (report.hangType == report.PLUGIN_HANG) {
+      setVisible("processHangTerminateScript", false);
+      setVisible("processHangDebugScript", false);
+      setVisible("processHangTerminatePlugin", true);
+    }
+  },
+
+  /**
+   * If there is a hang report associated with the selected browser in
+   * |win|, invoke |func| on that report and stop notifying the user
+   * about it.
+   */
+  handleUserInput: function(win, func) {
+    let report = this.findReport(win.gBrowser.selectedBrowser);
+    if (!report) {
+      return;
+    }
+    this.removeReport(report);
+
+    return func(report);
+  },
+
+  observe: function(subject, topic, data) {
+    switch (topic) {
+      case "xpcom-shutdown":
+        Services.obs.removeObserver(this, "xpcom-shutdown");
+        Services.obs.removeObserver(this, "process-hang-report");
+        Services.ww.unregisterNotification(this);
+        break;
+
+      case "process-hang-report":
+        this.reportHang(subject.QueryInterface(Ci.nsIHangReport));
+        break;
+
+      case "domwindowopened":
+        // Install event listeners on the new window in case one of
+        // its tabs is already hung.
+        let win = subject.QueryInterface(Ci.nsIDOMWindow);
+        let listener = (ev) => {
+          win.removeEventListener("load", listener, true);
+          this.updateWindows();
+        };
+        win.addEventListener("load", listener, true);
+        break;
+    }
+  },
+
+  /**
+   * Find any active hang reports for the given <browser> element.
+   */
+  findReport: function(browser) {
+    let frameLoader = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+    for (let [report, timer] of this._activeReports) {
+      if (report.isReportForBrowser(frameLoader)) {
+        return report;
+      }
+    }
+    return null;
+  },
+
+  /**
+   * Iterate over all XUL windows and ensure that the proper hang
+   * reports are shown for each one. Also install event handlers in
+   * each window to watch for events that would cause a different hang
+   * report to be displayed.
+   */
+  updateWindows: function() {
+    let e = Services.wm.getEnumerator("navigator:browser");
+    while (e.hasMoreElements()) {
+      let win = e.getNext();
+
+      this.updateWindow(win);
+
+      // Only listen for these events if there are active hang reports.
+      if (this._activeReports.size) {
+        this.trackWindow(win);
+      } else {
+        this.untrackWindow(win);
+      }
+    }
+  },
+
+  /**
+   * If there is a hang report for the current tab in |win|, display it.
+   */
+  updateWindow: function(win) {
+    let report = this.findReport(win.gBrowser.selectedBrowser);
+
+    if (report) {
+      this.showNotification(win, report);
+    } else {
+      this.hideNotification(win);
+    }
+  },
+
+  /**
+   * Show the notification for a hang.
+   */
+  showNotification: function(win, report) {
+    let nb = win.document.getElementById("high-priority-global-notificationbox");
+    let notification = nb.getNotificationWithValue("process-hang");
+    if (notification) {
+      return;
+    }
+
+    let bundle = win.gNavigatorBundle;
+    let brandBundle = win.document.getElementById("bundle_brand");
+    let appName = brandBundle.getString("brandShortName");
+    let message = bundle.getFormattedString(
+      "processHang.message",
+      [appName]);
+
+    let buttons = [{
+      label: bundle.getString("processHang.button.label"),
+      accessKey: bundle.getString("processHang.button.accessKey"),
+      popup: "processHangOptions",
+      callback: null,
+    }];
+
+    nb.appendNotification(message, "process-hang",
+                          "chrome://browser/content/aboutRobots-icon.png",
+                          nb.PRIORITY_WARNING_HIGH, buttons);
+  },
+
+  /**
+   * Ensure that no hang notifications are visible in |win|.
+   */
+  hideNotification: function(win) {
+    let nb = win.document.getElementById("high-priority-global-notificationbox");
+    let notification = nb.getNotificationWithValue("process-hang");
+    if (notification) {
+      nb.removeNotification(notification);
+    }
+  },
+
+  /**
+   * Install event handlers on |win| to watch for events that would
+   * cause a different hang report to be displayed.
+   */
+  trackWindow: function(win) {
+    win.gBrowser.tabContainer.addEventListener("TabSelect", this, true);
+    win.gBrowser.tabContainer.addEventListener("TabRemotenessChange", this, true);
+  },
+
+  untrackWindow: function(win) {
+    win.gBrowser.tabContainer.removeEventListener("TabSelect", this, true);
+    win.gBrowser.tabContainer.removeEventListener("TabRemotenessChange", this, true);
+  },
+
+  handleEvent: function(event) {
+    let win = event.target.ownerDocument.defaultView;
+
+    // If a new tab is selected or if a tab changes remoteness, then
+    // we may need to show or hide a hang notification.
+
+    if (event.type == "TabSelect" || event.type == "TabRemotenessChange") {
+      this.updateWindow(win);
+    }
+  },
+
+  /**
+   * Handle a potentially new hang report. If it hasn't been seen
+   * before, show a notification for it in all open XUL windows.
+   */
+  reportHang: function(report) {
+    // If this hang was already reported, then reset the timer for it.
+    if (this._activeReports.has(report)) {
+      let timer = this._activeReports.get(report);
+      timer.cancel();
+      timer.initWithCallback(this, HANG_EXPIRATION_TIME, timer.TYPE_ONE_SHOT);
+      return;
+    }
+
+    // Otherwise create a new timer and display the report.
+    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    timer.initWithCallback(this, HANG_EXPIRATION_TIME, timer.TYPE_ONE_SHOT);
+
+    this._activeReports.set(report, timer);
+    this.updateWindows();
+  },
+
+  /**
+   * Dismiss a hang report because the user closed the notification
+   * for it or the report expired.
+   */
+  removeReport: function(report) {
+    this._activeReports.delete(report);
+    this.updateWindows();
+  },
+
+  /**
+   * Callback for when HANG_EXPIRATION_TIME has elapsed.
+   */
+  notify: function(timer) {
+    for (let [otherReport, otherTimer] of this._activeReports) {
+      if (otherTimer === timer) {
+        this.removeReport(otherReport);
+        break;
+      }
+    }
+  },
+};
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -22,16 +22,17 @@ EXTRA_JS_MODULES += [
     'DirectoryLinksProvider.jsm',
     'E10SUtils.jsm',
     'Feeds.jsm',
     'FormSubmitObserver.jsm',
     'FormValidationHandler.jsm',
     'NetworkPrioritizer.jsm',
     'offlineAppCache.jsm',
     'PanelFrame.jsm',
+    'ProcessHangMonitor.jsm',
     'RemotePrompt.jsm',
     'SitePermissions.jsm',
     'Social.jsm',
     'TabCrashReporter.jsm',
     'WebappManager.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
--- a/dom/base/SlowScriptDebug.js
+++ b/dom/base/SlowScriptDebug.js
@@ -11,11 +11,14 @@ function SlowScriptDebug() { }
 SlowScriptDebug.prototype = {
   classID: Components.ID("{e740ddb4-18b4-4aac-8ae1-9b0f4320769d}"),
   classDescription: "Slow script debug handler",
   contractID: "@mozilla.org/dom/slow-script-debug;1",
   QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISlowScriptDebug]),
 
   get activationHandler()   { return this._activationHandler; },
   set activationHandler(cb) { return this._activationHandler = cb; },
+
+  get remoteActivationHandler()   { return this._remoteActivationHandler; },
+  set remoteActivationHandler(cb) { return this._remoteActivationHandler = cb; },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SlowScriptDebug]);
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -62,16 +62,17 @@
 #include "mozilla/dom/MessagePortList.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "nsJSPrincipals.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Debug.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/MouseEvents.h"
+#include "mozilla/ProcessHangMonitor.h"
 #include "AudioChannelService.h"
 #include "MessageEvent.h"
 #include "nsAboutProtocolUtils.h"
 #include "nsCharTraits.h" // NS_IS_HIGH/LOW_SURROGATE
 
 // Interfaces Needed
 #include "nsIFrame.h"
 #include "nsCanvasFrame.h"
@@ -10964,27 +10965,55 @@ nsGlobalWindow::ShowSlowScriptDialog()
     return KillSlowScript;
   }
 
   // If our document is not active, just kill the script: we've been unloaded
   if (!HasActiveDocument()) {
     return KillSlowScript;
   }
 
+  // Check if we should offer the option to debug
+  JS::AutoFilename filename;
+  unsigned lineno;
+  bool hasFrame = JS::DescribeScriptedCaller(cx, &filename, &lineno);
+
+  if (XRE_GetProcessType() == GeckoProcessType_Content &&
+      ProcessHangMonitor::Get()) {
+    ProcessHangMonitor::SlowScriptAction action;
+    nsRefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get();
+    nsCOMPtr<nsITabChild> child = do_GetInterface(GetDocShell());
+    action = monitor->NotifySlowScript(child,
+                                       filename.get(),
+                                       lineno);
+    if (action == ProcessHangMonitor::Terminate) {
+      return KillSlowScript;
+    }
+
+    if (action == ProcessHangMonitor::StartDebugger) {
+      // Spin a nested event loop so that the debugger in the parent can fetch
+      // any information it needs. Once the debugger has started, return to the
+      // script.
+      nsRefPtr<nsGlobalWindow> outer = GetOuterWindowInternal();
+      outer->EnterModalState();
+      while (!monitor->IsDebuggerStartupComplete()) {
+        NS_ProcessNextEvent(nullptr, true);
+      }
+      outer->LeaveModalState();
+      return ContinueSlowScript;
+    }
+
+    return ContinueSlowScriptAndKeepNotifying;
+  }
+
   // Get the nsIPrompt interface from the docshell
   nsCOMPtr<nsIDocShell> ds = GetDocShell();
   NS_ENSURE_TRUE(ds, KillSlowScript);
   nsCOMPtr<nsIPrompt> prompt = do_GetInterface(ds);
   NS_ENSURE_TRUE(prompt, KillSlowScript);
 
-  // Check if we should offer the option to debug
-  JS::AutoFilename filename;
-  unsigned lineno;
-  bool hasFrame = JS::DescribeScriptedCaller(cx, &filename, &lineno);
-
   // Prioritize the SlowScriptDebug interface over JSD1.
   nsCOMPtr<nsISlowScriptDebugCallback> debugCallback;
 
   if (hasFrame) {
     const char *debugCID = "@mozilla.org/dom/slow-script-debug;1";
     nsCOMPtr<nsISlowScriptDebug> debugService = do_GetService(debugCID, &rv);
     if (NS_SUCCEEDED(rv)) {
       debugService->GetActivationHandler(getter_AddRefs(debugCallback));
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -721,16 +721,17 @@ public:
 
   void AllowScriptsToClose()
   {
     mAllowScriptsToClose = true;
   }
 
   enum SlowScriptResponse {
     ContinueSlowScript = 0,
+    ContinueSlowScriptAndKeepNotifying,
     AlwaysContinueSlowScript,
     KillSlowScript
   };
   SlowScriptResponse ShowSlowScriptDialog();
 
 #ifdef MOZ_GAMEPAD
   // Inner windows only.
   void AddGamepad(uint32_t aIndex, mozilla::dom::Gamepad* aGamepad);
--- a/dom/base/nsISlowScriptDebug.idl
+++ b/dom/base/nsISlowScriptDebug.idl
@@ -1,19 +1,34 @@
 /* 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 "nsISupports.idl"
 
 interface nsIDOMWindow;
+interface nsIDOMEventTarget;
 
 [scriptable, function, uuid(f7dbb80c-5d1e-4fd9-b55c-a9ffda4a75b1)]
 interface nsISlowScriptDebugCallback : nsISupports
 {
   void handleSlowScriptDebug(in nsIDOMWindow aWindow);
 };
 
+[scriptable, function, uuid(b1c6ecd0-8fa4-11e4-b4a9-0800200c9a66)]
+interface nsISlowScriptDebugerStartupCallback : nsISupports
+{
+  void finishDebuggerStartup();
+};
+
+[scriptable, function, uuid(dbee14b0-8fa0-11e4-b4a9-0800200c9a66)]
+interface nsISlowScriptDebugRemoteCallback : nsISupports
+{
+  void handleSlowScriptDebug(in nsIDOMEventTarget aBrowser,
+      	                     in nsISlowScriptDebugerStartupCallback aCallback);
+};
+
 [scriptable, uuid(f75d4164-3aa7-4395-ba44-a5f95b2e8427)]
 interface nsISlowScriptDebug : nsISupports
 {
   attribute nsISlowScriptDebugCallback activationHandler;
+  attribute nsISlowScriptDebugRemoteCallback remoteActivationHandler;
 };
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -19,16 +19,17 @@
 #include "GeckoProfiler.h"
 #include "TabChild.h"
 
 #include "mozilla/Attributes.h"
 #ifdef ACCESSIBILITY
 #include "mozilla/a11y/DocAccessibleChild.h"
 #endif
 #include "mozilla/Preferences.h"
+#include "mozilla/ProcessHangMonitorIPC.h"
 #include "mozilla/docshell/OfflineCacheUpdateChild.h"
 #include "mozilla/dom/ContentBridgeChild.h"
 #include "mozilla/dom/ContentBridgeParent.h"
 #include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/ExternalHelperAppChild.h"
 #include "mozilla/dom/PCrashReporterChild.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
@@ -994,16 +995,23 @@ ContentChild::AllocPImageBridgeChild(moz
 
 PBackgroundChild*
 ContentChild::AllocPBackgroundChild(Transport* aTransport,
                                     ProcessId aOtherProcess)
 {
     return BackgroundChild::Alloc(aTransport, aOtherProcess);
 }
 
+PProcessHangMonitorChild*
+ContentChild::AllocPProcessHangMonitorChild(Transport* aTransport,
+                                            ProcessId aOtherProcess)
+{
+    return CreateHangMonitorChild(aTransport, aOtherProcess);
+}
+
 #if defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX)
 static void
 SetUpSandboxEnvironment()
 {
     // Set up a low integrity temp directory. This only makes sense if the
     // delayed integrity level for the content process is INTEGRITY_LEVEL_LOW.
     nsresult rv;
     nsCOMPtr<nsIProperties> directoryService =
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -121,16 +121,20 @@ public:
     PSharedBufferManagerChild*
     AllocPSharedBufferManagerChild(mozilla::ipc::Transport* aTransport,
                                     base::ProcessId aOtherProcess) MOZ_OVERRIDE;
 
     PImageBridgeChild*
     AllocPImageBridgeChild(mozilla::ipc::Transport* aTransport,
                            base::ProcessId aOtherProcess) MOZ_OVERRIDE;
 
+    PProcessHangMonitorChild*
+    AllocPProcessHangMonitorChild(Transport* aTransport,
+                                  ProcessId aOtherProcess) MOZ_OVERRIDE;
+
 #if defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX)
     // Cleans up any resources used by the process when sandboxed.
     void CleanUpSandboxEnvironment();
 #endif
 
     virtual bool RecvSetProcessSandbox() MOZ_OVERRIDE;
 
     PBackgroundChild*
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -68,16 +68,18 @@
 #include "mozilla/ipc/TestShellParent.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/layers/SharedBufferManagerParent.h"
 #include "mozilla/net/NeckoParent.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/ProcessHangMonitor.h"
+#include "mozilla/ProcessHangMonitorIPC.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/unused.h"
 #include "nsAnonymousTemporaryFile.h"
 #include "nsAppRunner.h"
 #include "nsAutoPtr.h"
 #include "nsCDefaultURIFixup.h"
@@ -1789,16 +1791,21 @@ ContentParent::ActorDestroy(ActorDestroy
         mForceKillTimer->Cancel();
         mForceKillTimer = nullptr;
     }
 
     // Signal shutdown completion regardless of error state, so we can
     // finish waiting in the xpcom-shutdown/profile-before-change observer.
     mIPCOpen = false;
 
+    if (mHangMonitorActor) {
+        ProcessHangMonitor::RemoveProcess(mHangMonitorActor);
+        mHangMonitorActor = nullptr;
+    }
+
     if (why == NormalShutdown && !mCalledClose) {
         // If we shut down normally but haven't called Close, assume somebody
         // else called Close on us. In that case, we still need to call
         // ShutDownProcess below to perform other necessary clean up.
         mCalledClose = true;
     }
 
     // Make sure we always clean up.
@@ -2021,16 +2028,17 @@ ContentParent::InitializeMembers()
     mSendPermissionUpdates = false;
     mSendDataStoreInfos = false;
     mCalledClose = false;
     mCalledCloseWithError = false;
     mCalledKillHard = false;
     mCreatedPairedMinidumps = false;
     mShutdownPending = false;
     mIPCOpen = true;
+    mHangMonitorActor = nullptr;
 }
 
 ContentParent::ContentParent(mozIApplication* aApp,
                              ContentParent* aOpener,
                              bool aIsForBrowser,
                              bool aIsForPreallocated,
                              ProcessPriority aInitialPriority /* = PROCESS_PRIORITY_FOREGROUND */,
                              bool aIsNuwaProcess /* = false */)
@@ -2100,16 +2108,18 @@ ContentParent::ContentParent(mozIApplica
     Open(mSubprocess->GetChannel(), mSubprocess->GetOwnedChildProcessHandle());
 
     InitInternal(aInitialPriority,
                  true, /* Setup off-main thread compositing */
                  true  /* Send registered chrome */);
 
     ContentProcessManager::GetSingleton()->AddContentProcess(this);
 
+    ProcessHangMonitor::AddProcess(this);
+
     // Set a reply timeout for CPOWs.
     SetReplyTimeoutMs(Preferences::GetInt("dom.ipc.cpow.timeout", 0));
 }
 
 #ifdef MOZ_NUWA_PROCESS
 static const mozilla::ipc::FileDescriptor*
 FindFdProtocolFdMapping(const nsTArray<ProtocolFdMapping>& aFds,
                         ProtocolId aProtoId)
@@ -3037,16 +3047,24 @@ ContentParent::AllocPImageBridgeParent(m
 
 PBackgroundParent*
 ContentParent::AllocPBackgroundParent(Transport* aTransport,
                                       ProcessId aOtherProcess)
 {
     return BackgroundParent::Alloc(this, aTransport, aOtherProcess);
 }
 
+PProcessHangMonitorParent*
+ContentParent::AllocPProcessHangMonitorParent(Transport* aTransport,
+                                              ProcessId aOtherProcess)
+{
+    mHangMonitorActor = CreateHangMonitorParent(this, aTransport, aOtherProcess);
+    return mHangMonitorActor;
+}
+
 PSharedBufferManagerParent*
 ContentParent::AllocPSharedBufferManagerParent(mozilla::ipc::Transport* aTransport,
                                                 base::ProcessId aOtherProcess)
 {
     return SharedBufferManagerParent::Create(aTransport, aOtherProcess);
 }
 
 bool
@@ -4285,17 +4303,18 @@ ContentParent::RecvNotifyKeywordSearchLo
     }
 #endif
     return true;
 }
 
 bool
 ContentParent::ShouldContinueFromReplyTimeout()
 {
-    return false;
+    nsRefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get();
+    return !monitor || !monitor->ShouldTimeOutCPOWs();
 }
 
 bool
 ContentParent::RecvRecordingDeviceEvents(const nsString& aRecordingStatus,
                                          const nsString& aPageURL,
                                          const bool& aIsAudio,
                                          const bool& aIsVideo)
 {
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -479,16 +479,20 @@ private:
 
     PSharedBufferManagerParent*
     AllocPSharedBufferManagerParent(mozilla::ipc::Transport* aTranport,
                                      base::ProcessId aOtherProcess) MOZ_OVERRIDE;
     PBackgroundParent*
     AllocPBackgroundParent(Transport* aTransport, ProcessId aOtherProcess)
                            MOZ_OVERRIDE;
 
+    PProcessHangMonitorParent*
+    AllocPProcessHangMonitorParent(Transport* aTransport,
+                                   ProcessId aOtherProcess) MOZ_OVERRIDE;
+
     virtual bool RecvGetProcessAttributes(ContentParentId* aCpId,
                                           bool* aIsForApp,
                                           bool* aIsForBrowser) MOZ_OVERRIDE;
     virtual bool RecvGetXPCOMProcessAttributes(bool* aIsOffline,
                                                InfallibleTArray<nsString>* dictionaries,
                                                ClipboardCapabilities* clipboardCaps)
         MOZ_OVERRIDE;
 
@@ -833,16 +837,18 @@ private:
     // object instead of the child process's lifetime.
     ScopedClose mChildXSocketFdDup;
 #endif
 
 #ifdef MOZ_NUWA_PROCESS
     static int32_t sNuwaPid;
     static bool sNuwaReady;
 #endif
+
+    PProcessHangMonitorParent* mHangMonitorActor;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 class ParentIdleListener : public nsIObserver {
 public:
   NS_DECL_ISUPPORTS
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -16,16 +16,17 @@ include protocol PCycleCollectWithLogs;
 include protocol PCrashReporter;
 include protocol PDocAccessible;
 include protocol PExternalHelperApp;
 include protocol PDeviceStorageRequest;
 include protocol PFileDescriptorSet;
 include protocol PFMRadio;
 include protocol PFileSystemRequest;
 include protocol PHal;
+include protocol PProcessHangMonitor;
 include protocol PImageBridge;
 include protocol PMemoryReportRequest;
 include protocol PMobileConnection;
 include protocol PNecko;
 include protocol PPluginModule;
 include protocol PPrinting;
 include protocol POfflineCacheUpdate;
 include protocol PScreenManager;
@@ -340,16 +341,17 @@ union OptionalContentId
   void_t;
 };
 
 prio(normal upto urgent) intr protocol PContent
 {
     parent spawns PPluginModule;
 
     parent opens PCompositor;
+    parent opens PProcessHangMonitor;
     parent opens PSharedBufferManager;
     parent opens PImageBridge;
     child opens PBackground;
 
     manages PAsmJSCacheEntry;
     manages PBlob;
     manages PBluetooth;
     manages PBrowser;
new file mode 100644
--- /dev/null
+++ b/dom/ipc/PProcessHangMonitor.ipdl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* 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/. */
+
+using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
+
+namespace mozilla {
+
+struct SlowScriptData
+{
+  TabId tabId;
+  nsCString filename;
+  uint32_t lineno;
+};
+
+struct PluginHangData
+{
+  uint32_t pluginId;
+};
+
+union HangData
+{
+  SlowScriptData;
+  PluginHangData;
+};
+
+protocol PProcessHangMonitor
+{
+parent:
+  async HangEvidence(HangData data);
+
+child:
+  async TerminateScript();
+
+  async BeginStartingDebugger();
+  async EndStartingDebugger();
+};
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -0,0 +1,954 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "mozilla/Atomics.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Element.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"
+
+#include "base/task.h"
+#include "base/thread.h"
+
+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:
+  HangMonitorChild(ProcessHangMonitor* aMonitor);
+  virtual ~HangMonitorChild();
+
+  void Open(Transport* aTransport, ProcessHandle aHandle,
+            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();
+
+  virtual bool RecvTerminateScript() MOZ_OVERRIDE;
+  virtual bool RecvBeginStartingDebugger() MOZ_OVERRIDE;
+  virtual bool RecvEndStartingDebugger() MOZ_OVERRIDE;
+
+  virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
+
+  void Shutdown();
+
+  static HangMonitorChild* Get() { return sInstance; }
+
+  MessageLoop* MonitorLoop() { return mHangMonitor->MonitorLoop(); }
+
+ private:
+  void ShutdownOnThread();
+
+  static HangMonitorChild* sInstance;
+
+  const nsRefPtr<ProcessHangMonitor> mHangMonitor;
+  Monitor mMonitor;
+
+  // Main thread-only.
+  bool mSentReport;
+
+  // These fields must be accessed with mMonitor held.
+  bool mTerminateScript;
+  bool mStartDebugger;
+  bool mFinishedStartingDebugger;
+  bool mShutdownDone;
+
+  // This field is only accessed on the hang thread.
+  bool mIPCOpen;
+};
+
+HangMonitorChild* HangMonitorChild::sInstance;
+
+/* Parent process objects */
+
+class HangMonitorParent;
+
+class HangMonitoredProcess MOZ_FINAL
+  : public nsIHangReport
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  HangMonitoredProcess(HangMonitorParent* aActor,
+                       ContentParent* aContentParent)
+    : mActor(aActor), mContentParent(aContentParent) {}
+
+  NS_IMETHOD GetHangType(uint32_t* aHangType) MOZ_OVERRIDE;
+  NS_IMETHOD GetScriptBrowser(nsIDOMElement** aBrowser) MOZ_OVERRIDE;
+  NS_IMETHOD GetScriptFileName(nsACString& aFileName) MOZ_OVERRIDE;
+  NS_IMETHOD GetScriptLineNo(uint32_t* aLineNo) MOZ_OVERRIDE;
+
+  NS_IMETHOD GetPluginName(nsACString& aPluginName) MOZ_OVERRIDE;
+
+  NS_IMETHOD TerminateScript() MOZ_OVERRIDE;
+  NS_IMETHOD BeginStartingDebugger() MOZ_OVERRIDE;
+  NS_IMETHOD EndStartingDebugger() MOZ_OVERRIDE;
+  NS_IMETHOD TerminatePlugin() MOZ_OVERRIDE;
+  NS_IMETHOD TerminateProcess() MOZ_OVERRIDE;
+
+  NS_IMETHOD IsReportForBrowser(nsIFrameLoader* aFrameLoader, bool* aResult);
+
+  void Clear() { mContentParent = nullptr; mActor = nullptr; }
+
+  void SetHangData(const HangData& aHangData) { mHangData = aHangData; }
+
+private:
+  ~HangMonitoredProcess() {}
+
+  // Everything here is main thread-only.
+  HangMonitorParent* mActor;
+  ContentParent* mContentParent;
+  HangData mHangData;
+};
+
+class HangMonitorParent
+  : public PProcessHangMonitorParent
+{
+public:
+  HangMonitorParent(ProcessHangMonitor* aMonitor);
+  virtual ~HangMonitorParent();
+
+  void Open(Transport* aTransport, ProcessHandle aHandle,
+            MessageLoop* aIOLoop);
+
+  virtual bool RecvHangEvidence(const HangData& aHangData) MOZ_OVERRIDE;
+
+  virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
+
+  void SetProcess(HangMonitoredProcess* aProcess) { mProcess = aProcess; }
+
+  void Shutdown();
+
+  void TerminateScript();
+  void BeginStartingDebugger();
+  void EndStartingDebugger();
+
+  MessageLoop* MonitorLoop() { return mHangMonitor->MonitorLoop(); }
+
+ private:
+  void ShutdownOnThread();
+
+  const nsRefPtr<ProcessHangMonitor> 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.
+  nsRefPtr<HangMonitoredProcess> mProcess;
+  bool mShutdownDone;
+};
+
+} // namespace
+
+template<>
+struct RunnableMethodTraits<HangMonitorChild>
+{
+  typedef HangMonitorChild Class;
+  static void RetainCallee(Class* obj) { }
+  static void ReleaseCallee(Class* obj) { }
+};
+
+template<>
+struct RunnableMethodTraits<HangMonitorParent>
+{
+  typedef HangMonitorParent Class;
+  static void RetainCallee(Class* obj) { }
+  static void ReleaseCallee(Class* obj) { }
+};
+
+/* HangMonitorChild implementation */
+
+HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor)
+ : mHangMonitor(aMonitor),
+   mMonitor("HangMonitorChild lock"),
+   mSentReport(false),
+   mTerminateScript(false),
+   mStartDebugger(false),
+   mFinishedStartingDebugger(false),
+   mShutdownDone(false),
+   mIPCOpen(true)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+}
+
+HangMonitorChild::~HangMonitorChild()
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(sInstance == this);
+  sInstance = nullptr;
+}
+
+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(
+    FROM_HERE,
+    NewRunnableMethod(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;
+}
+
+void
+HangMonitorChild::Open(Transport* aTransport, ProcessHandle aHandle,
+                       MessageLoop* aIOLoop)
+{
+  MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
+
+  MOZ_ASSERT(!sInstance);
+  sInstance = this;
+
+  DebugOnly<bool> ok = PProcessHangMonitorChild::Open(aTransport, aHandle, 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) {
+    nsRefPtr<TabChild> tabChild = static_cast<TabChild*>(aTabChild);
+    id = tabChild->GetTabId();
+  }
+  nsAutoCString filename(aFileName);
+
+  MonitorLoop()->PostTask(
+    FROM_HERE,
+    NewRunnableMethod(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)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+  mSentReport = true;
+
+  MonitorLoop()->PostTask(
+    FROM_HERE,
+    NewRunnableMethod(this,
+                      &HangMonitorChild::NotifyPluginHangAsync,
+                      aPluginId));
+}
+
+void
+HangMonitorChild::NotifyPluginHangAsync(uint32_t aPluginId)
+{
+  MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
+
+  if (mIPCOpen) {
+    unused << SendHangEvidence(PluginHangData(aPluginId));
+  }
+}
+
+void
+HangMonitorChild::ClearHang()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mSentReport) {
+    MonitorAutoLock lock(mMonitor);
+    mSentReport = false;
+    mTerminateScript = false;
+    mStartDebugger = false;
+    mFinishedStartingDebugger = false;
+  }
+}
+
+/* HangMonitorParent implementation */
+
+HangMonitorParent::HangMonitorParent(ProcessHangMonitor* aMonitor)
+ : mHangMonitor(aMonitor),
+   mIPCOpen(true),
+   mMonitor("HangMonitorParent lock"),
+   mShutdownDone(false)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  mReportHangs = mozilla::Preferences::GetBool("dom.ipc.reportProcessHangs", false);
+}
+
+HangMonitorParent::~HangMonitorParent()
+{
+  // For some reason IPDL doesn't autmatically delete the channel for a
+  // bridged protocol (bug 1090570). So we have to do it ourselves.
+  XRE_GetIOMessageLoop()->PostTask(FROM_HERE, new DeleteTask<Transport>(GetTransport()));
+}
+
+void
+HangMonitorParent::Shutdown()
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+  MonitorAutoLock lock(mMonitor);
+
+  if (mProcess) {
+    mProcess->Clear();
+    mProcess = nullptr;
+  }
+
+  MonitorLoop()->PostTask(
+    FROM_HERE,
+    NewRunnableMethod(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::ActorDestroy(ActorDestroyReason aWhy)
+{
+  MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
+  mIPCOpen = false;
+}
+
+void
+HangMonitorParent::Open(Transport* aTransport, ProcessHandle aHandle,
+                        MessageLoop* aIOLoop)
+{
+  MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
+
+  DebugOnly<bool> ok = PProcessHangMonitorParent::Open(aTransport, aHandle, aIOLoop);
+  MOZ_ASSERT(ok);
+}
+
+class HangObserverNotifier MOZ_FINAL : public nsRunnable
+{
+public:
+  HangObserverNotifier(HangMonitoredProcess* aProcess, const HangData& aHangData)
+    : mProcess(aProcess),
+      mHangData(aHangData)
+  {}
+
+  NS_IMETHOD
+  Run()
+  {
+    MOZ_RELEASE_ASSERT(NS_IsMainThread());
+    mProcess->SetHangData(mHangData);
+
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    observerService->NotifyObservers(mProcess, "process-hang-report", nullptr);
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<HangMonitoredProcess> mProcess;
+  HangData mHangData;
+};
+
+bool
+HangMonitorParent::RecvHangEvidence(const HangData& aHangData)
+{
+  MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
+
+  if (!mReportHangs) {
+    return true;
+  }
+
+  mHangMonitor->InitiateCPOWTimeout();
+
+  MonitorAutoLock lock(mMonitor);
+
+  nsCOMPtr<nsIRunnable> notifier = new HangObserverNotifier(mProcess, aHangData);
+  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();
+  }
+}
+
+/* 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(false);
+    return NS_ERROR_UNEXPECTED;
+    break;
+  }
+  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<PBrowserParent*> tabs;
+  mContentParent->ManagedPBrowserParent(tabs);
+  for (size_t i = 0; i < tabs.Length(); i++) {
+    TabParent* tp = static_cast<TabParent*>(tabs[i]);
+    if (tp->GetTabId() == tabId) {
+      nsCOMPtr<nsIDOMElement> 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();
+
+  nsRefPtr<nsPluginHost> host = nsPluginHost::GetInst();
+  nsPluginTag* tag = host->PluginWithId(id);
+  if (!tag) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  aPluginName = tag->mName;
+  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(
+    FROM_HERE,
+    NewRunnableMethod(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(
+    FROM_HERE,
+    NewRunnableMethod(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(
+    FROM_HERE,
+    NewRunnableMethod(mActor, &HangMonitorParent::EndStartingDebugger));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HangMonitoredProcess::TerminatePlugin()
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  if (mHangData.type() != HangData::TPluginHangData) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  uint32_t id = mHangData.get_PluginHangData().pluginId();
+  plugins::TerminatePlugin(id);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HangMonitoredProcess::TerminateProcess()
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+  if (!mContentParent) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  mContentParent->KillHard();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HangMonitoredProcess::IsReportForBrowser(nsIFrameLoader* aFrameLoader, bool* aResult)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+  if (!mActor) {
+    *aResult = false;
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsITabParent> itp;
+  aFrameLoader->GetTabParent(getter_AddRefs(itp));
+  if (!itp) {
+    *aResult = false;
+    return NS_OK;
+  }
+
+  *aResult = mContentParent == static_cast<TabParent*>(itp.get())->Manager();
+  return NS_OK;
+}
+
+ProcessHangMonitor* ProcessHangMonitor::sInstance;
+
+ProcessHangMonitor::ProcessHangMonitor()
+ : mCPOWTimeout(false)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+  MOZ_COUNT_CTOR(ProcessHangMonitor);
+
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    nsCOMPtr<nsIObserverService> 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<nsIObserverService> 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 aOtherProcess)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+  ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate();
+  HangMonitorParent* parent = new HangMonitorParent(monitor);
+
+  HangMonitoredProcess* process = new HangMonitoredProcess(parent, aContentParent);
+  parent->SetProcess(process);
+
+  base::ProcessHandle handle;
+  if (!base::OpenProcessHandle(aOtherProcess, &handle)) {
+    // XXX need to kill |aOtherProcess|, it's boned
+    return nullptr;
+  }
+
+  monitor->MonitorLoop()->PostTask(
+    FROM_HERE,
+    NewRunnableMethod(parent, &HangMonitorParent::Open,
+                      aTransport, handle, XRE_GetIOMessageLoop()));
+
+  return parent;
+}
+
+PProcessHangMonitorChild*
+mozilla::CreateHangMonitorChild(mozilla::ipc::Transport* aTransport,
+                                base::ProcessId aOtherProcess)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+  ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate();
+  HangMonitorChild* child = new HangMonitorChild(monitor);
+
+  base::ProcessHandle handle;
+  if (!base::OpenProcessHandle(aOtherProcess, &handle)) {
+    // XXX need to kill |aOtherProcess|, it's boned
+    return nullptr;
+  }
+
+  monitor->MonitorLoop()->PostTask(
+    FROM_HERE,
+    NewRunnableMethod(child, &HangMonitorChild::Open,
+                      aTransport, handle, 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<bool> opened = PProcessHangMonitor::Open(aContentParent);
+    MOZ_ASSERT(opened);
+  }
+}
+
+/* static */ void
+ProcessHangMonitor::RemoveProcess(PProcessHangMonitorParent* aParent)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  auto parent = static_cast<HangMonitorParent*>(aParent);
+  parent->Shutdown();
+  delete parent;
+}
+
+/* static */ void
+ProcessHangMonitor::ClearHang()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (HangMonitorChild* child = HangMonitorChild::Get()) {
+    child->ClearHang();
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/ipc/ProcessHangMonitor.h
@@ -0,0 +1,78 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ProcessHangMonitor_h
+#define mozilla_ProcessHangMonitor_h
+
+#include "mozilla/Atomics.h"
+#include "nsIObserver.h"
+
+class nsGlobalWindow;
+class nsITabChild;
+
+class MessageLoop;
+
+namespace base {
+class Thread;
+};
+
+namespace mozilla {
+
+namespace dom {
+class ContentParent;
+}
+
+class PProcessHangMonitorParent;
+class PProcessHangMonitorChild;
+
+class ProcessHangMonitor MOZ_FINAL
+  : public nsIObserver
+{
+ private:
+  ProcessHangMonitor();
+  virtual ~ProcessHangMonitor();
+
+ public:
+  static ProcessHangMonitor* Get() { return sInstance; }
+  static ProcessHangMonitor* GetOrCreate();
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  static void AddProcess(dom::ContentParent* aContentParent);
+  static void RemoveProcess(PProcessHangMonitorParent* aParent);
+
+  static void ClearHang();
+
+  enum SlowScriptAction {
+    Continue,
+    Terminate,
+    StartDebugger
+  };
+  SlowScriptAction NotifySlowScript(nsITabChild* aTabChild,
+                                    const char* aFileName,
+                                    unsigned aLineNo);
+
+  void NotifyPluginHang(uint32_t aPluginId);
+
+  bool IsDebuggerStartupComplete();
+
+  void InitiateCPOWTimeout();
+  bool ShouldTimeOutCPOWs();
+
+  MessageLoop* MonitorLoop();
+
+ private:
+  static ProcessHangMonitor* sInstance;
+
+  Atomic<bool> mCPOWTimeout;
+
+  base::Thread* mThread;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ProcessHangMonitor_h
new file mode 100644
--- /dev/null
+++ b/dom/ipc/ProcessHangMonitorIPC.h
@@ -0,0 +1,34 @@
+/* -*- 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/. */
+
+#ifndef mozilla_ProcessHangMonitorIPC_h
+#define mozilla_ProcessHangMonitorIPC_h
+
+#include "base/task.h"
+#include "base/thread.h"
+
+#include "mozilla/PProcessHangMonitor.h"
+#include "mozilla/PProcessHangMonitorParent.h"
+#include "mozilla/PProcessHangMonitorChild.h"
+
+namespace mozilla {
+
+namespace dom {
+class ContentParent;
+}
+
+PProcessHangMonitorParent*
+CreateHangMonitorParent(mozilla::dom::ContentParent* aContentParent,
+                        mozilla::ipc::Transport* aTransport,
+                        base::ProcessId aOtherProcess);
+
+PProcessHangMonitorChild*
+CreateHangMonitorChild(mozilla::ipc::Transport* aTransport,
+                       base::ProcessId aOtherProcess);
+
+} // namespace mozilla
+
+#endif // mozilla_ProcessHangMonitorIPC_h
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -1,14 +1,20 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+XPIDL_SOURCES += [
+    'nsIHangReport.idl',
+]
+
+XPIDL_MODULE = 'dom'
+
 EXPORTS += [
     'nsICachedFileDescriptorListener.h',
 ]
 
 EXPORTS.mozilla.dom.ipc += [
     'BlobChild.h',
     'BlobParent.h',
     'IdType.h',
@@ -34,16 +40,18 @@ EXPORTS.mozilla.dom += [
     'TabContext.h',
     'TabMessageUtils.h',
     'TabParent.h',
 ]
 
 EXPORTS.mozilla += [
     'AppProcessChecker.h',
     'PreallocatedProcessManager.h',
+    'ProcessHangMonitor.h',
+    'ProcessHangMonitorIPC.h',
     'ProcessPriorityManager.h',
 ]
 
 UNIFIED_SOURCES += [
     'AppProcessChecker.cpp',
     'ColorPickerParent.cpp',
     'ContentBridgeChild.cpp',
     'ContentBridgeParent.cpp',
@@ -68,16 +76,17 @@ UNIFIED_SOURCES += [
 # Blob.cpp cannot be compiled in unified mode because it triggers a fatal gcc warning.
 # CrashReporterChild.cpp cannot be compiled in unified mode because of name clashes
 # in OS X headers.
 # ContentChild.cpp cannot be compiled in unified mode on  linux due to Time conflict
 SOURCES += [
     'Blob.cpp',
     'ContentChild.cpp',
     'CrashReporterChild.cpp',
+    'ProcessHangMonitor.cpp',
 ]
 
 IPDL_SOURCES += [
     'DOMTypes.ipdlh',
     'PBlob.ipdl',
     'PBlobStream.ipdl',
     'PBrowser.ipdl',
     'PBrowserOrId.ipdlh',
@@ -87,16 +96,17 @@ IPDL_SOURCES += [
     'PContentPermission.ipdlh',
     'PContentPermissionRequest.ipdl',
     'PCrashReporter.ipdl',
     'PCycleCollectWithLogs.ipdl',
     'PDocumentRenderer.ipdl',
     'PFilePicker.ipdl',
     'PMemoryReportRequest.ipdl',
     'PPluginWidget.ipdl',
+    'PProcessHangMonitor.ipdl',
     'PScreenManager.ipdl',
     'PTabContext.ipdlh',
 ]
 
 FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
new file mode 100644
--- /dev/null
+++ b/dom/ipc/nsIHangReport.idl
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "nsISupports.idl"
+
+interface nsIDOMElement;
+interface nsIFrameLoader;
+
+/**
+ * When a content process hangs, Gecko notifies "process-hang-report" observers
+ * and passes an nsIHangReport for the subject parameter. There is at most one
+ * nsIHangReport associated with a given content process. As long as the content
+ * process stays stuck, the "process-hang-report" observer will continue to be
+ * notified at regular intervals (approximately once per second). The content
+ * process will continue to run uninhibitedly during this time.
+ */
+
+[scriptable, uuid(3b88d100-8d5b-11e4-b4a9-0800200c9a66)]
+interface nsIHangReport : nsISupports
+{
+  const unsigned long SLOW_SCRIPT = 1;
+  const unsigned long PLUGIN_HANG = 2;
+
+  // The type of hang being reported: SLOW_SCRIPT or PLUGIN_HANG.
+  readonly attribute unsigned long hangType;
+
+  // For SLOW_SCRIPT reports, these fields contain information about the
+  // slow script.
+  // Only valid for SLOW_SCRIPT reports.
+  readonly attribute nsIDOMElement scriptBrowser;
+  readonly attribute ACString scriptFileName;
+  readonly attribute unsigned long scriptLineNo;
+
+  // For PLUGIN_HANGs, this field contains information about the plugin.
+  // Only valid for PLUGIN_HANG reports.
+  readonly attribute ACString pluginName;
+
+  // Terminate the slow script if it is still running.
+  // Only valid for SLOW_SCRIPT reports.
+  void terminateScript();
+
+  // Terminate the plugin if it is still hung.
+  // Only valid for PLUGIN_HANG reports.
+  void terminatePlugin();
+
+  // Terminate the hung content process unconditionally.
+  // Valid for any type of hang.
+  void terminateProcess();
+
+  // Ask the content process to start up the slow script debugger.
+  // Only valid for SLOW_SCRIPT reports.
+  void beginStartingDebugger();
+
+  // Inform the content process that the slow script debugger has finished
+  // spinning up. The content process will run a nested event loop until this
+  // method is called.
+  // Only valid for SLOW_SCRIPT reports.
+  void endStartingDebugger();
+
+  // Inquire whether the report is for a content process loaded by the given
+  // frameloader.
+  bool isReportForBrowser(in nsIFrameLoader aFrameLoader);
+};
--- a/dom/plugins/base/nsPluginHost.h
+++ b/dom/plugins/base/nsPluginHost.h
@@ -196,16 +196,18 @@ public:
   // and don't need to set up a new stream.
   nsresult InstantiatePluginInstance(const char *aMimeType, nsIURI* aURL,
                                      nsObjectLoadingContent *aContent,
                                      nsPluginInstanceOwner** aOwner);
 
   // Does not accept nullptr and should never fail.
   nsPluginTag* TagForPlugin(nsNPAPIPlugin* aPlugin);
 
+  nsPluginTag* PluginWithId(uint32_t aId);
+
   nsresult GetPlugin(const char *aMimeType, nsNPAPIPlugin** aPlugin);
   nsresult GetPluginForContentProcess(uint32_t aPluginId, nsNPAPIPlugin** aPlugin);
   void NotifyContentModuleDestroyed(uint32_t aPluginId);
 
   nsresult NewPluginStreamListener(nsIURI* aURL,
                                    nsNPAPIPluginInstance* aInstance,
                                    nsIStreamListener **aStreamListener);
 
@@ -271,17 +273,16 @@ private:
   // Checks to see if a tag object is in our list of live tags.
   bool IsLiveTag(nsIPluginTag* tag);
   
   // Checks our list of live tags for an equivalent tag.
   nsPluginTag* HaveSamePlugin(const nsPluginTag * aPluginTag);
     
   // Returns the first plugin at |path|
   nsPluginTag* FirstPluginWithPath(const nsCString& path);
-  nsPluginTag* PluginWithId(uint32_t aId);
 
   nsresult EnsurePrivateDirServiceProvider();
 
   void OnPluginInstanceDestroyed(nsPluginTag* aPluginTag);
 
   // To be used by the chrome process whenever the set of plugins changes.
   void IncrementChromeEpoch();
 
--- a/dom/plugins/ipc/PluginBridge.h
+++ b/dom/plugins/ipc/PluginBridge.h
@@ -18,12 +18,15 @@ namespace plugins {
 bool
 SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent);
 
 bool
 FindPluginsForContent(uint32_t aPluginEpoch,
                       nsTArray<PluginTag>* aPlugins,
                       uint32_t* aNewPluginEpoch);
 
+void
+TerminatePlugin(uint32_t aPluginId);
+
 } // namespace plugins
 } // namespace mozilla
 
 #endif // mozilla_plugins_PluginBridge_h
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -16,16 +16,17 @@
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/PCrashReporterParent.h"
 #include "mozilla/ipc/MessageChannel.h"
 #include "mozilla/plugins/BrowserStreamParent.h"
 #include "mozilla/plugins/PluginAsyncSurrogate.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/plugins/PluginInstanceParent.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/unused.h"
 #include "nsAutoPtr.h"
 #include "nsCRT.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsNPAPIPlugin.h"
@@ -64,16 +65,17 @@ using namespace mozilla::plugins;
 using namespace mozilla::plugins::parent;
 
 #ifdef MOZ_CRASHREPORTER
 #include "mozilla/dom/CrashReporterParent.h"
 
 using namespace CrashReporter;
 #endif
 
+static const char kContentTimeoutPref[] = "dom.ipc.plugins.contentTimeoutSecs";
 static const char kChildTimeoutPref[] = "dom.ipc.plugins.timeoutSecs";
 static const char kParentTimeoutPref[] = "dom.ipc.plugins.parentTimeoutSecs";
 static const char kLaunchTimeoutPref[] = "dom.ipc.plugins.processLaunchTimeoutSecs";
 static const char kAsyncInitPref[] = "dom.ipc.plugins.asyncInit";
 #ifdef XP_WIN
 static const char kHangUITimeoutPref[] = "dom.ipc.plugins.hangUITimeoutSecs";
 static const char kHangUIMinDisplayPref[] = "dom.ipc.plugins.hangUIMinDisplaySecs";
 #define CHILD_TIMEOUT_PREF kHangUITimeoutPref
@@ -252,16 +254,32 @@ private:
     static bool sIsLoadModuleOnStack;
 };
 
 PRCList PluginModuleMapping::sModuleListHead =
     PR_INIT_STATIC_CLIST(&PluginModuleMapping::sModuleListHead);
 
 bool PluginModuleMapping::sIsLoadModuleOnStack = false;
 
+void
+mozilla::plugins::TerminatePlugin(uint32_t aPluginId)
+{
+    MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+    nsRefPtr<nsPluginHost> host = nsPluginHost::GetInst();
+    nsPluginTag* pluginTag = host->PluginWithId(aPluginId);
+    if (!pluginTag || !pluginTag->mPlugin) {
+        return;
+    }
+
+    nsRefPtr<nsNPAPIPlugin> plugin = pluginTag->mPlugin;
+    PluginModuleChromeParent* chromeParent = static_cast<PluginModuleChromeParent*>(plugin->GetLibrary());
+    chromeParent->TerminateChildProcess(MessageLoop::current());
+}
+
 /* static */ PluginLibrary*
 PluginModuleContentParent::LoadModule(uint32_t aPluginId)
 {
     PluginModuleMapping::NotifyLoadingModule loadingModule;
     nsAutoPtr<PluginModuleMapping> mapping(new PluginModuleMapping(aPluginId));
 
     MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content);
 
@@ -282,16 +300,18 @@ PluginModuleContentParent::LoadModule(ui
 
     if (!mapping->IsChannelOpened()) {
         // mapping is linked into PluginModuleMapping::sModuleListHead and is
         // needed later, so since this function is returning successfully we
         // forget it here.
         mapping.forget();
     }
 
+    parent->mPluginId = aPluginId;
+
     return parent;
 }
 
 /* static */ void
 PluginModuleContentParent::AssociatePluginId(uint32_t aPluginId,
                                              base::ProcessId aProcessId)
 {
     DebugOnly<PluginModuleMapping*> mapping =
@@ -321,16 +341,18 @@ PluginModuleContentParent::Initialize(mo
 
     moduleMapping->SetChannelOpened();
 
     // Request Windows message deferral behavior on our channel. This
     // applies to the top level and all sub plugin protocols since they
     // all share the same channel.
     parent->GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION);
 
+    TimeoutChanged(kContentTimeoutPref, parent);
+
     // moduleMapping is linked into PluginModuleMapping::sModuleListHead and is
     // needed later, so since this function is returning successfully we
     // forget it here.
     moduleMapping.forget();
     return parent;
 }
 
 /* static */ void
@@ -510,16 +532,22 @@ PluginModuleParent::~PluginModuleParent(
         NPError err;
         NP_Shutdown(&err);
     }
 }
 
 PluginModuleContentParent::PluginModuleContentParent()
     : PluginModuleParent(false)
 {
+    Preferences::RegisterCallback(TimeoutChanged, kContentTimeoutPref, this);
+}
+
+PluginModuleContentParent::~PluginModuleContentParent()
+{
+    Preferences::UnregisterCallback(TimeoutChanged, kContentTimeoutPref, this);
 }
 
 PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath, uint32_t aPluginId)
     : PluginModuleParent(true)
     , mSubprocess(new PluginProcessParent(aFilePath))
     , mPluginId(aPluginId)
     , mChromeTaskFactory(this)
     , mHangAnnotationFlags(0)
@@ -643,42 +671,51 @@ PluginModuleChromeParent::WriteExtraData
 #endif
         }
 #endif
     }
 }
 #endif  // MOZ_CRASHREPORTER
 
 void
-PluginModuleChromeParent::SetChildTimeout(const int32_t aChildTimeout)
+PluginModuleParent::SetChildTimeout(const int32_t aChildTimeout)
 {
     int32_t timeoutMs = (aChildTimeout > 0) ? (1000 * aChildTimeout) :
                       MessageChannel::kNoTimeout;
     SetReplyTimeoutMs(timeoutMs);
 }
 
 void
-PluginModuleChromeParent::TimeoutChanged(const char* aPref, void* aModule)
+PluginModuleParent::TimeoutChanged(const char* aPref, void* aModule)
 {
+    PluginModuleParent* module = static_cast<PluginModuleParent*>(aModule);
+
     NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 #ifndef XP_WIN
     if (!strcmp(aPref, kChildTimeoutPref)) {
+      MOZ_ASSERT(module->IsChrome());
       // The timeout value used by the parent for children
       int32_t timeoutSecs = Preferences::GetInt(kChildTimeoutPref, 0);
-      static_cast<PluginModuleChromeParent*>(aModule)->SetChildTimeout(timeoutSecs);
+      module->SetChildTimeout(timeoutSecs);
 #else
     if (!strcmp(aPref, kChildTimeoutPref) ||
         !strcmp(aPref, kHangUIMinDisplayPref) ||
         !strcmp(aPref, kHangUITimeoutPref)) {
-      static_cast<PluginModuleChromeParent*>(aModule)->EvaluateHangUIState(true);
+      MOZ_ASSERT(module->IsChrome());
+      static_cast<PluginModuleChromeParent*>(module)->EvaluateHangUIState(true);
 #endif // XP_WIN
     } else if (!strcmp(aPref, kParentTimeoutPref)) {
       // The timeout value used by the child for its parent
+      MOZ_ASSERT(module->IsChrome());
       int32_t timeoutSecs = Preferences::GetInt(kParentTimeoutPref, 0);
-      unused << static_cast<PluginModuleChromeParent*>(aModule)->SendSetParentHangTimeout(timeoutSecs);
+      unused << static_cast<PluginModuleChromeParent*>(module)->SendSetParentHangTimeout(timeoutSecs);
+    } else if (!strcmp(aPref, kContentTimeoutPref)) {
+      MOZ_ASSERT(!module->IsChrome());
+      int32_t timeoutSecs = Preferences::GetInt(kContentTimeoutPref, 0);
+      module->SetChildTimeout(timeoutSecs);
     }
 }
 
 void
 PluginModuleChromeParent::CleanupFromTimeout(const bool aFromHangUI)
 {
     if (mShutdown) {
       return;
@@ -852,16 +889,33 @@ PluginModuleChromeParent::ShouldContinue
     // original plugin hang behaviour and kill the plugin container.
     FinishHangUI();
 #endif // XP_WIN
     TerminateChildProcess(MessageLoop::current());
     GetIPCChannel()->CloseWithTimeout();
     return false;
 }
 
+bool
+PluginModuleContentParent::ShouldContinueFromReplyTimeout()
+{
+    nsRefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get();
+    if (!monitor) {
+        return true;
+    }
+    monitor->NotifyPluginHang(mPluginId);
+    return true;
+}
+
+void
+PluginModuleContentParent::OnExitedSyncSend()
+{
+    ProcessHangMonitor::ClearHang();
+}
+
 void
 PluginModuleChromeParent::TerminateChildProcess(MessageLoop* aMsgLoop)
 {
 #ifdef MOZ_CRASHREPORTER
 #ifdef XP_WIN
     mozilla::MutexAutoLock lock(mCrashReporterMutex);
     CrashReporterParent* crashReporter = mCrashReporter;
     if (!crashReporter) {
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -171,16 +171,19 @@ protected:
 
     virtual bool
     RecvNP_InitializeResult(const NPError& aError) MOZ_OVERRIDE;
 
     static BrowserStreamParent* StreamCast(NPP instance, NPStream* s,
                                            PluginAsyncSurrogate** aSurrogate = nullptr);
 
 protected:
+    void SetChildTimeout(const int32_t aChildTimeout);
+    static void TimeoutChanged(const char* aPref, void* aModule);
+
     virtual void UpdatePluginTimeout() {}
 
     virtual bool RecvNotifyContentModuleDestroyed() MOZ_OVERRIDE { return true; }
 
     void SetPluginFuncs(NPPluginFuncs* aFuncs);
 
     nsresult NPP_NewInternal(NPMIMEType pluginType, NPP instance, uint16_t mode,
                              InfallibleTArray<nsCString>& names,
@@ -304,23 +307,29 @@ class PluginModuleContentParent : public
     static PluginLibrary* LoadModule(uint32_t aPluginId);
 
     static PluginModuleContentParent* Initialize(mozilla::ipc::Transport* aTransport,
                                                  base::ProcessId aOtherProcess);
 
     static void OnLoadPluginResult(const uint32_t& aPluginId, const bool& aResult);
     static void AssociatePluginId(uint32_t aPluginId, base::ProcessId aProcessId);
 
+    virtual ~PluginModuleContentParent();
+
   private:
+    virtual bool ShouldContinueFromReplyTimeout() MOZ_OVERRIDE;
+    virtual void OnExitedSyncSend() MOZ_OVERRIDE;
 
 #ifdef MOZ_CRASHREPORTER_INJECTOR
     void OnCrash(DWORD processID) MOZ_OVERRIDE {}
 #endif
 
     static PluginModuleContentParent* sSavedModuleParent;
+
+    uint32_t mPluginId;
 };
 
 class PluginModuleChromeParent
     : public PluginModuleParent
     , public mozilla::HangMonitor::Annotator
 {
   public:
     /**
@@ -338,16 +347,19 @@ class PluginModuleChromeParent
 
 #ifdef XP_WIN
     /**
      * Called by Plugin Hang UI to notify that the user has clicked continue.
      * Used for chrome hang annotations.
      */
     void
     OnHangUIContinue();
+
+    void
+    EvaluateHangUIState(const bool aReset);
 #endif // XP_WIN
 
     virtual bool WaitForIPCConnection() MOZ_OVERRIDE;
 
     virtual bool
     RecvNP_InitializeResult(const NPError& aError) MOZ_OVERRIDE;
 
     void
@@ -397,18 +409,16 @@ private:
     virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
     // aFilePath is UTF8, not native!
     explicit PluginModuleChromeParent(const char* aFilePath, uint32_t aPluginId);
 
     CrashReporterParent* CrashReporter();
 
     void CleanupFromTimeout(const bool aByHangUI);
-    void SetChildTimeout(const int32_t aChildTimeout);
-    static void TimeoutChanged(const char* aPref, void* aModule);
 
     virtual void UpdatePluginTimeout() MOZ_OVERRIDE;
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
     void InitPluginProfiling();
     void ShutdownPluginProfiling();
 #endif
 
@@ -444,19 +454,16 @@ private:
      * the mCrashReporter variable in addition to the CrashReporterParent object
      * that mCrashReporter refers to.
      */
     mozilla::Mutex mCrashReporterMutex;
     CrashReporterParent* mCrashReporter;
 #endif // MOZ_CRASHREPORTER
 
 
-    void
-    EvaluateHangUIState(const bool aReset);
-
     /**
      * Launches the Plugin Hang UI.
      *
      * @return true if plugin-hang-ui.exe has been successfully launched.
      *         false if the Plugin Hang UI is disabled, already showing,
      *               or the launch failed.
      */
     bool
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -35,16 +35,17 @@
 #include "jsprf.h"
 #include "js/MemoryMetrics.h"
 #include "mozilla/dom/GeneratedAtomList.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/ProcessHangMonitor.h"
 #include "AccessCheck.h"
 #include "nsGlobalWindow.h"
 #include "nsAboutProtocolUtils.h"
 
 #include "GeckoProfiler.h"
 #include "nsIXULRuntime.h"
 #include "nsJSPrincipals.h"
 
@@ -1107,16 +1108,17 @@ class Watchdog
     bool mShuttingDown;
     mozilla::Atomic<int32_t> mMinScriptRunTimeSeconds;
 };
 
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 
+#define PREF_MAX_SCRIPT_RUN_TIME_CHILD "dom.max_child_script_run_time"
 #define PREF_MAX_SCRIPT_RUN_TIME_CONTENT "dom.max_script_run_time"
 #define PREF_MAX_SCRIPT_RUN_TIME_CHROME "dom.max_chrome_script_run_time"
 
 class WatchdogManager : public nsIObserver
 {
   public:
 
     NS_DECL_ISUPPORTS
@@ -1129,29 +1131,31 @@ class WatchdogManager : public nsIObserv
 
         // Enable the watchdog, if appropriate.
         RefreshWatchdog();
 
         // Register ourselves as an observer to get updates on the pref.
         mozilla::Preferences::AddStrongObserver(this, "dom.use_watchdog");
         mozilla::Preferences::AddStrongObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CONTENT);
         mozilla::Preferences::AddStrongObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CHROME);
+        mozilla::Preferences::AddStrongObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CHILD);
     }
 
   protected:
 
     virtual ~WatchdogManager()
     {
         // Shutting down the watchdog requires context-switching to the watchdog
         // thread, which isn't great to do in a destructor. So we require
         // consumers to shut it down manually before releasing it.
         MOZ_ASSERT(!mWatchdog);
         mozilla::Preferences::RemoveObserver(this, "dom.use_watchdog");
         mozilla::Preferences::RemoveObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CONTENT);
         mozilla::Preferences::RemoveObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CHROME);
+        mozilla::Preferences::RemoveObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CHILD);
     }
 
   public:
 
     NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
                        const char16_t* aData)
     {
         RefreshWatchdog();
@@ -1219,17 +1223,20 @@ class WatchdogManager : public nsIObserv
 
         if (mWatchdog) {
             int32_t contentTime = Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CONTENT, 10);
             if (contentTime <= 0)
                 contentTime = INT32_MAX;
             int32_t chromeTime = Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CHROME, 20);
             if (chromeTime <= 0)
                 chromeTime = INT32_MAX;
-            mWatchdog->SetMinScriptRunTimeSeconds(std::min(contentTime, chromeTime));
+            int32_t childTime = Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CHILD, 3);
+            if (childTime <= 0)
+                childTime = INT32_MAX;
+            mWatchdog->SetMinScriptRunTimeSeconds(std::min(std::min(contentTime, chromeTime), childTime));
         }
     }
 
     void StartWatchdog()
     {
         MOZ_ASSERT(!mWatchdog);
         mWatchdog = new Watchdog(this);
         mWatchdog->Init();
@@ -1336,16 +1343,20 @@ XPCJSRuntime::DefaultJSContextCallback(J
     MOZ_ASSERT(rt == Get()->Runtime());
     return Get()->GetJSContextStack()->GetSafeJSContext();
 }
 
 // static
 void
 XPCJSRuntime::ActivityCallback(void *arg, bool active)
 {
+    if (!active) {
+        ProcessHangMonitor::ClearHang();
+    }
+
     XPCJSRuntime* self = static_cast<XPCJSRuntime*>(arg);
     self->mWatchdogManager->RecordRuntimeActivity(active);
 }
 
 // static
 //
 // JS-CTypes creates and caches a JSContext that it uses when executing JS
 // callbacks. When we're notified that ctypes is about to call into some JS,
@@ -1374,23 +1385,26 @@ XPCJSRuntime::InterruptCallback(JSContex
         return true;
     }
 
     // Sometimes we get called back during XPConnect initialization, before Gecko
     // has finished bootstrapping. Avoid crashing in nsContentUtils below.
     if (!nsContentUtils::IsInitialized())
         return true;
 
+    bool contentProcess = XRE_GetProcessType() == GeckoProcessType_Content;
+
     // This is at least the second interrupt callback we've received since
     // returning to the event loop. See how long it's been, and what the limit
     // is.
     TimeDuration duration = TimeStamp::NowLoRes() - self->mSlowScriptCheckpoint;
     bool chrome = nsContentUtils::IsCallerChrome();
-    const char *prefName = chrome ? PREF_MAX_SCRIPT_RUN_TIME_CHROME
-                                  : PREF_MAX_SCRIPT_RUN_TIME_CONTENT;
+    const char *prefName = contentProcess ? PREF_MAX_SCRIPT_RUN_TIME_CHILD
+                                 : chrome ? PREF_MAX_SCRIPT_RUN_TIME_CHROME
+                                          : PREF_MAX_SCRIPT_RUN_TIME_CONTENT;
     int32_t limit = Preferences::GetInt(prefName, chrome ? 20 : 10);
 
     // If there's no limit, or we're within the limit, let it go.
     if (limit == 0 || duration.ToSeconds() < limit)
         return true;
 
     //
     // This has gone on long enough! Time to take action. ;-)
@@ -1418,17 +1432,19 @@ XPCJSRuntime::InterruptCallback(JSContex
 
     // Show the prompt to the user, and kill if requested.
     nsGlobalWindow::SlowScriptResponse response = win->ShowSlowScriptDialog();
     if (response == nsGlobalWindow::KillSlowScript)
         return false;
 
     // The user chose to continue the script. Reset the timer, and disable this
     // machinery with a pref of the user opted out of future slow-script dialogs.
-    self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
+    if (response != nsGlobalWindow::ContinueSlowScriptAndKeepNotifying)
+        self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
+
     if (response == nsGlobalWindow::AlwaysContinueSlowScript)
         Preferences::SetInt(prefName, 0);
 
     return true;
 }
 
 void
 XPCJSRuntime::CustomOutOfMemoryCallback()
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2273,16 +2273,17 @@ pref("viewmanager.do_doublebuffering", t
 // enable single finger gesture input (win7+ tablets)
 pref("gestures.enable_single_finger_input", true);
 
 pref("editor.resizing.preserve_ratio",       true);
 pref("editor.positioning.offset",            0);
 
 pref("dom.use_watchdog", true);
 pref("dom.max_chrome_script_run_time", 20);
+pref("dom.max_child_script_run_time", 2);
 pref("dom.max_script_run_time", 10);
 
 // If true, ArchiveReader will be enabled
 pref("dom.archivereader.enabled", false);
 
 // Hang monitor timeout after which we kill the browser, in seconds
 // (0 is disabled)
 // Disabled on all platforms per bug 705748 until the found issues are
@@ -2319,16 +2320,19 @@ pref("plugin.persistentPermissionAlways.
 #ifndef DEBUG
 // How long a plugin is allowed to process a synchronous IPC message
 // before we consider it "hung".
 pref("dom.ipc.plugins.timeoutSecs", 45);
 // How long a plugin process will wait for a response from the parent
 // to a synchronous request before terminating itself. After this
 // point the child assumes the parent is hung. Currently disabled.
 pref("dom.ipc.plugins.parentTimeoutSecs", 0);
+// How long a plugin in e10s is allowed to process a synchronous IPC
+// message before we notify the chrome process of a hang.
+pref("dom.ipc.plugins.contentTimeoutSecs", 2);
 // How long a plugin launch is allowed to take before
 // we consider it failed.
 pref("dom.ipc.plugins.processLaunchTimeoutSecs", 45);
 #ifdef XP_WIN
 // How long a plugin is allowed to process a synchronous IPC message
 // before we display the plugin hang UI
 pref("dom.ipc.plugins.hangUITimeoutSecs", 11);
 // Minimum time that the plugin hang UI will be displayed
@@ -2336,16 +2340,17 @@ pref("dom.ipc.plugins.hangUIMinDisplaySe
 #endif
 // How long a content process can take before closing its IPC channel
 // after shutdown is initiated.  If the process exceeds the timeout,
 // we fear the worst and kill it.
 pref("dom.ipc.tabs.shutdownTimeoutSecs", 5);
 #else
 // No timeout in DEBUG builds
 pref("dom.ipc.plugins.timeoutSecs", 0);
+pref("dom.ipc.plugins.contentTimeoutSecs", 0);
 pref("dom.ipc.plugins.processLaunchTimeoutSecs", 0);
 pref("dom.ipc.plugins.parentTimeoutSecs", 0);
 #ifdef XP_WIN
 pref("dom.ipc.plugins.hangUITimeoutSecs", 0);
 pref("dom.ipc.plugins.hangUIMinDisplaySecs", 0);
 #endif
 pref("dom.ipc.tabs.shutdownTimeoutSecs", 0);
 #endif
--- a/testing/marionette/client/marionette/geckoinstance.py
+++ b/testing/marionette/client/marionette/geckoinstance.py
@@ -23,17 +23,18 @@ class GeckoInstance(object):
                       "browser.sessionstore.resume_from_crash": False,
                       "browser.warnOnQuit": False,
                       "browser.displayedE10SPrompt": 5,
                       "browser.displayedE10SPrompt.1": 5,
                       "browser.displayedE10SPrompt.2": 5,
                       "browser.displayedE10SPrompt.3": 5,
                       "browser.displayedE10SPrompt.4": 5,
                       "browser.tabs.remote.autostart.1": False,
-                      "browser.tabs.remote.autostart.2": False}
+                      "browser.tabs.remote.autostart.2": False,
+                      "dom.ipc.reportProcessHangs": False}
 
     def __init__(self, host, port, bin, profile=None, app_args=None, symbols_path=None,
                   gecko_log=None, prefs=None):
         self.marionette_host = host
         self.marionette_port = port
         self.bin = bin
         # Check if it is a Profile object or a path to profile
         self.profile = None
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -9,16 +9,18 @@ user_pref("browser.ui.layout.tablet", 0)
 user_pref("dom.allow_scripts_to_close_windows", true);
 user_pref("dom.disable_open_during_load", false);
 user_pref("dom.experimental_forms", true); // on for testing
 user_pref("dom.forms.number", true); // on for testing
 user_pref("dom.forms.color", true); // on for testing
 user_pref("dom.max_script_run_time", 0); // no slow script dialogs
 user_pref("hangmonitor.timeout", 0); // no hang monitor
 user_pref("dom.max_chrome_script_run_time", 0);
+user_pref("dom.max_child_script_run_time", 0);
+user_pref("dom.ipc.reportProcessHangs", false); // process hang monitor
 user_pref("dom.popup_maximum", -1);
 user_pref("dom.send_after_paint_to_content", true);
 user_pref("dom.successive_dialog_time_limit", 0);
 user_pref("signed.applets.codebase_principal_support", true);
 user_pref("browser.shell.checkDefaultBrowser", false);
 user_pref("shell.checkDefaultClient", false);
 user_pref("browser.warnOnQuit", false);
 user_pref("accessibility.typeaheadfind.autostart", false);