Merge m-c to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 26 Oct 2015 14:45:35 +0100
changeset 304680 8d96502842a277b9bed903c4e29aa78bd41f8b43
parent 304679 18d52e7a0d80bda0507dca823806204b9595f2e9 (current diff)
parent 304615 28068d907290d1f5138a0b9e59ae2233a1c1b7a3 (diff)
child 304681 0c7e876a69e54646265585c4363794acb9e1aedb
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone44.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
Merge m-c to mozilla-inbound
browser/modules/test/browser_ProcessHangNotifications.js
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -314,16 +314,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/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -920,16 +920,25 @@ you can use these alternative items. Oth
 
 <!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">
+
 <!ENTITY emeLearnMoreContextMenu.label            "Learn more about DRM…">
 <!ENTITY emeLearnMoreContextMenu.accesskey        "D">
 <!ENTITY emeNotificationsNotNow.label             "Not now">
 <!ENTITY emeNotificationsNotNow.accesskey         "N">
 <!ENTITY emeNotificationsDontAskAgain.label       "Don't ask me again">
 <!ENTITY emeNotificationsDontAskAgain.accesskey   "D">
 
 <!-- LOCALIZATION NOTE (saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label): Pocket is a brand name -->
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -467,23 +467,19 @@ syncPromoNotification.addons.description
 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.label = A web page is slowing down your browser. What would you like to do?
-processHang.button_stop.label = Stop It
-processHang.button_stop.accessKey = S
-processHang.button_wait.label = Wait
-processHang.button_wait.accessKey = W
-processHang.button_debug.label = Debug Script
-processHang.button_debug.accessKey = D
+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.requestInstall2) %S is the web app name
 webapps.requestInstall2 = Do you want to install “%S” from this site?
 webapps.install.success = Application Installed
 webapps.install.inprogress = Installation in progress
--- a/browser/modules/ProcessHangMonitor.jsm
+++ b/browser/modules/ProcessHangMonitor.jsm
@@ -14,56 +14,32 @@ this.EXPORTED_SYMBOLS = ["ProcessHangMon
 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.
  */
 
-var ProcessHangMonitor = {
-  /**
-   * If a hang hasn't been reported for more than 10 seconds, assume the
-   * content process has gotten unstuck (and hide the hang notification).
-   */
-  get HANG_EXPIRATION_TIME() {
-    try {
-      return Services.prefs.getIntPref("browser.hangNotification.expiration");
-    } catch (ex) {
-      return 10000;
-    }
-  },
+/**
+ * If a hang hasn't been reported for more than 10 seconds, assume the
+ * content process has gotten unstuck (and hide the hang notification).
+ */
+const HANG_EXPIRATION_TIME = 10000;
 
-  /**
-   * This timeout is the wait period applied after a user selects "Wait" in
-   * an existing notification.
-   */
-  get WAIT_EXPIRATION_TIME() {
-    try {
-      return Services.prefs.getIntPref("browser.hangNotification.waitPeriod");
-    } catch (ex) {
-      return 10000;
-    }
-  },
-
+var 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(),
 
   /**
-   * Collection of hang reports that have been suppressed for a
-   * short period of time.
-   */
-  _pausedReports: 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);
   },
 
@@ -89,101 +65,69 @@ var ProcessHangMonitor = {
 
       let svc = Cc["@mozilla.org/dom/slow-script-debug;1"].getService(Ci.nsISlowScriptDebug);
       let handler = svc.remoteActivationHandler;
       handler.handleSlowScriptDebug(report.scriptBrowser, callback);
     });
   },
 
   /**
-   * Terminate the plugin process associated with a hang being reported
-   * for the selected browser in |win|. Will attempt to generate a combined
-   * crash report for all processes.
+   * Kill the plugin process causing the hang being reported for the
+   * selected browser in |win|.
    */
   terminatePlugin: function(win) {
     this.handleUserInput(win, report => report.terminatePlugin());
   },
 
   /**
-   * Dismiss the browser notification and invoke an appropriate action based on
-   * the hang type.
+   * Kill the content process causing the hang being reported for the selected
+   * browser in |win|.
    */
-  stopIt: function (win) {
-    let report = this.findActiveReport(win.gBrowser.selectedBrowser);
+  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;
     }
 
-    switch (report.hangType) {
-      case report.SLOW_SCRIPT:
-        this.terminateScript(win);
-        break;
-      case report.PLUGIN_HANG:
-        this.terminatePlugin(win);
-        break;
+    function setVisible(id, visible) {
+      let item = win.document.getElementById(id);
+      item.hidden = !visible;
     }
-  },
-
-  /**
-   * Dismiss the notification, clear the report from the active list and set up
-   * a new timer to track a wait period during which we won't notify.
-   */
-  waitLonger: function(win) {
-    let report = this.findActiveReport(win.gBrowser.selectedBrowser);
-    if (!report) {
-      return;
-    }
-    // Remove the report from the active list and cancel its timer.
-    this.removeActiveReport(report);
-
-    // NOTE, we didn't call userCanceled on nsIHangReport here. This insures
-    // we don't repeatedly generate and cache crash report data for this hang
-    // in the process hang reporter. It already has one report for the browser
-    // process we want it hold onto.
 
-    // Create a new wait timer with notify callback
-    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-    timer.initWithCallback(() => {
-      for (let [stashedReport, otherTimer] of this._pausedReports) {
-        if (otherTimer === timer) {
-          this.removePausedReport(stashedReport);
-
-          // Create a new notification display timeout timer
-          let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-          timer.initWithCallback(this, this.HANG_EXPIRATION_TIME, timer.TYPE_ONE_SHOT);
-
-          // Store the timer in the active reports map. If we receive a new
-          // observer notification for this hang, we'll redisplay the browser
-          // notification in reportHang below. If we do not receive a new
-          // observer, timer will take care of cleaning up resources associated
-          // with this hang. The observer for active hangs fires about once
-          // a second.
-          this._activeReports.set(report, timer);
-          break;
-        }
-      }
-    }, this.WAIT_EXPIRATION_TIME, timer.TYPE_ONE_SHOT);
-
-    this._pausedReports.set(report, timer);
-
-    // remove the browser notification associated with this hang
-    this.updateWindows();
+    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.findActiveReport(win.gBrowser.selectedBrowser);
+    let report = this.findReport(win.gBrowser.selectedBrowser);
     if (!report) {
       return;
     }
-    this.removeActiveReport(report);
+    this.removeReport(report);
 
     return func(report);
   },
 
   observe: function(subject, topic, data) {
     switch (topic) {
       case "xpcom-shutdown":
         Services.obs.removeObserver(this, "xpcom-shutdown");
@@ -204,67 +148,29 @@ var ProcessHangMonitor = {
           this.updateWindows();
         };
         win.addEventListener("load", listener, true);
         break;
     }
   },
 
   /**
-   * Find a active hang report for the given <browser> element.
+   * Find any active hang reports for the given <browser> element.
    */
-  findActiveReport: function(browser) {
+  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;
   },
 
   /**
-   * Find a paused hang report for the given <browser> element.
-   */
-  findPausedReport: function(browser) {
-    let frameLoader = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
-    for (let [report, timer] of this._pausedReports) {
-      if (report.isReportForBrowser(frameLoader)) {
-        return report;
-      }
-    }
-    return null;
-  },
-
-  /**
-   * Remove an active hang report from the active list and cancel the timer
-   * associated with it.
-   */
-  removeActiveReport: function(report) {
-    let timer = this._activeReports.get(report);
-    if (timer) {
-      timer.cancel();
-    }
-    this._activeReports.delete(report);
-    this.updateWindows();
-  },
-
-  /**
-   * Remove a paused hang report from the paused list and cancel the timer
-   * associated with it.
-   */
-  removePausedReport: function(report) {
-    let timer = this._pausedReports.get(report);
-    if (timer) {
-      timer.cancel();
-    }
-    this._pausedReports.delete(report);
-  },
-
-  /**
    * 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()) {
@@ -280,17 +186,17 @@ var ProcessHangMonitor = {
       }
     }
   },
 
   /**
    * If there is a hang report for the current tab in |win|, display it.
    */
   updateWindow: function(win) {
-    let report = this.findActiveReport(win.gBrowser.selectedBrowser);
+    let report = this.findReport(win.gBrowser.selectedBrowser);
 
     if (report) {
       this.showNotification(win, report);
     } else {
       this.hideNotification(win);
     }
   },
 
@@ -301,46 +207,29 @@ var ProcessHangMonitor = {
     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_stop.label"),
-        accessKey: bundle.getString("processHang.button_stop.accessKey"),
-        callback: function() {
-          ProcessHangMonitor.stopIt(win);
-        }
-      },
-      {
-        label: bundle.getString("processHang.button_wait.label"),
-        accessKey: bundle.getString("processHang.button_wait.accessKey"),
-        callback: function() {
-          ProcessHangMonitor.waitLonger(win);
-        }
-      }];
+      label: bundle.getString("processHang.button.label"),
+      accessKey: bundle.getString("processHang.button.accessKey"),
+      popup: "processHangOptions",
+      callback: null,
+    }];
 
-#ifdef MOZ_DEV_EDITION
-    if (report.hangType == report.SLOW_SCRIPT) {
-      buttons.push({
-        label: bundle.getString("processHang.button_debug.label"),
-        accessKey: bundle.getString("processHang.button_debug.accessKey"),
-        callback: function() {
-          ProcessHangMonitor.debugScript(win);
-        }
-      });
-    }
-#endif
-
-    nb.appendNotification(bundle.getString("processHang.label"),
-                          "process-hang",
+    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) {
@@ -376,56 +265,57 @@ var ProcessHangMonitor = {
     }
   },
 
   /**
    * 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 reset the timer for it.
+    // 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, this.HANG_EXPIRATION_TIME, timer.TYPE_ONE_SHOT);
-      // if this report is in active but doesn't have a notification associated
-      // with it, display a notification.
-      this.updateWindows();
-      return;
-    }
-
-    // If this hang was already reported and paused by the user ignore it.
-    if (this._pausedReports.has(report)) {
+      timer.initWithCallback(this, HANG_EXPIRATION_TIME, timer.TYPE_ONE_SHOT);
       return;
     }
 
     // On e10s this counts slow-script/hanged-plugin notice only once.
     // This code is not reached on non-e10s.
     if (report.hangType == report.SLOW_SCRIPT) {
       // On non-e10s, SLOW_SCRIPT_NOTICE_COUNT is probed at nsGlobalWindow.cpp
       Services.telemetry.getHistogramById("SLOW_SCRIPT_NOTICE_COUNT").add();
     } else if (report.hangType == report.PLUGIN_HANG) {
       // On non-e10s we have sufficient plugin telemetry probes,
       // so PLUGIN_HANG_NOTICE_COUNT is only probed on e10s.
       Services.telemetry.getHistogramById("PLUGIN_HANG_NOTICE_COUNT").add();
     }
 
     // Otherwise create a new timer and display the report.
     let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-    timer.initWithCallback(this, this.HANG_EXPIRATION_TIME, timer.TYPE_ONE_SHOT);
+    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.removeActiveReport(otherReport);
+        this.removeReport(otherReport);
         otherReport.userCanceled();
         break;
       }
     }
   },
 };
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -28,32 +28,29 @@ EXTRA_JS_MODULES += [
     'Feeds.jsm',
     'FormSubmitObserver.jsm',
     'FormValidationHandler.jsm',
     'HiddenFrame.jsm',
     'NetworkPrioritizer.jsm',
     'offlineAppCache.jsm',
     'PanelFrame.jsm',
     'PluginContent.jsm',
+    'ProcessHangMonitor.jsm',
     'ReaderParent.jsm',
     'RecentWindow.jsm',
     'RemotePrompt.jsm',
     'Sanitizer.jsm',
     'SelfSupportBackend.jsm',
     'SitePermissions.jsm',
     'Social.jsm',
     'TransientPrefs.jsm',
     'WebappManager.jsm',
     'webrtcUI.jsm',
 ]
 
-EXTRA_PP_JS_MODULES += [
-    'ProcessHangMonitor.jsm'
-]
-
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXTRA_JS_MODULES += [
         'Windows8WindowFrameColor.jsm',
         'WindowsJumpLists.jsm',
         'WindowsPreviewPerTab.jsm',
     ]
 
 if CONFIG['NIGHTLY_BUILD']:
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -1,15 +1,13 @@
 [DEFAULT]
 support-files =
   head.js
 
 [browser_BrowserUITelemetry_buckets.js]
-[browser_ProcessHangNotifications.js]
-skip-if = !e10s
 [browser_ContentSearch.js]
 skip-if = e10s
 support-files =
   contentSearch.js
   contentSearchBadImage.xml
   contentSearchSuggestions.sjs
   contentSearchSuggestions.xml
 [browser_NetworkPrioritizer.js]
deleted file mode 100644
--- a/browser/modules/test/browser_ProcessHangNotifications.js
+++ /dev/null
@@ -1,185 +0,0 @@
-
-Cu.import("resource://gre/modules/UpdateUtils.jsm");
-
-function getNotificationBox(aWindow) {
-  return aWindow.document.getElementById("high-priority-global-notificationbox");
-}
-
-function promiseNotificationShown(aWindow, aName) {
-  return new Promise((resolve) => {
-    let notification = getNotificationBox(aWindow);
-    notification.addEventListener("AlertActive", function active() {
-      notification.removeEventListener("AlertActive", active, true);
-      is(notification.allNotifications.length, 1, "Notification Displayed.");
-      resolve(notification);
-    });
-  });
-}
-
-function promiseReportCallMade(aValue) {
-  return new Promise((resolve) => {
-    let old = gTestHangReport.testCallback;
-    gTestHangReport.testCallback = function (val) {
-      gTestHangReport.testCallback = old;
-      is(aValue, val, "was the correct method call made on the hang report object?");
-      resolve();
-    };
-  });
-}
-
-function pushPrefs(...aPrefs) {
-  return new Promise((resolve) => {
-    SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve);
-    resolve();
-  });
-}
-
-function popPrefs() {
-  return new Promise((resolve) => {
-    SpecialPowers.popPrefEnv(resolve);
-    resolve();
-  });
-}
-
-let gTestHangReport = {
-  SLOW_SCRIPT: 1,
-  PLUGIN_HANG: 2,
-
-  TEST_CALLBACK_CANCELED: 1,
-  TEST_CALLBACK_TERMSCRIPT: 2,
-  TEST_CALLBACK_TERMPLUGIN: 3,
-
-  _hangType: 1,
-  _tcb: function (aCallbackType) {},
-
-  get hangType() {
-    return this._hangType;
-  },
-
-  set hangType(aValue) {
-    this._hangType = aValue;
-  },
-
-  set testCallback(aValue) {
-    this._tcb = aValue;
-  },
-
-  QueryInterface: function (aIID) {
-    if (aIID.equals(Components.interfaces.nsIHangReport) ||
-        aIID.equals(Components.interfaces.nsISupports))
-      return this;
-    throw Components.results.NS_NOINTERFACE;
-  },
-
-  userCanceled: function () {
-    this._tcb(this.TEST_CALLBACK_CANCELED);
-  },
-
-  terminateScript: function () {
-    this._tcb(this.TEST_CALLBACK_TERMSCRIPT);
-  },
-
-  terminatePlugin: function () {
-    this._tcb(this.TEST_CALLBACK_TERMPLUGIN);
-  },
-
-  isReportForBrowser: function(aFrameLoader) {
-    return true;
-  }
-};
-
-// on dev edition we add a button for js debugging of hung scripts.
-let buttonCount = (UpdateUtils.UpdateChannel == "aurora" ? 3 : 2);
-
-/**
- * Test if hang reports receive a terminate script callback when the user selects
- * stop in response to a script hang.
- */
-
-add_task(function* terminateScriptTest() {
-  let promise = promiseNotificationShown(window, "process-hang");
-  Services.obs.notifyObservers(gTestHangReport, "process-hang-report", null);
-  let notification = yield promise;
-
-  let buttons = notification.currentNotification.getElementsByTagName("button");
-  is(buttons.length, buttonCount, "proper number of buttons");
-
-  // Click the "Stop It" button, we should get a terminate script callback
-  gTestHangReport.hangType = gTestHangReport.SLOW_SCRIPT;
-  promise = promiseReportCallMade(gTestHangReport.TEST_CALLBACK_TERMSCRIPT);
-  buttons[0].click();
-  yield promise;
-});
-
-/**
- * Test if hang reports receive user canceled callbacks after a user selects wait
- * and the browser frees up from a script hang on its own.
- */
-
-add_task(function* waitForScriptTest() {
-  let promise = promiseNotificationShown(window, "process-hang");
-  Services.obs.notifyObservers(gTestHangReport, "process-hang-report", null);
-  let notification = yield promise;
-
-  let buttons = notification.currentNotification.getElementsByTagName("button");
-  is(buttons.length, buttonCount, "proper number of buttons");
-
-  yield pushPrefs(["browser.hangNotification.waitPeriod", 1000],
-                  ["browser.hangNotification.expiration", 2000]);
-
-  function nocbcheck() {
-    ok(false, "received a callback?");
-  }
-  let oldcb = gTestHangReport.testCallback;
-  gTestHangReport.testCallback = nocbcheck;
-  // Click the "Wait" button this time, we shouldn't get a callback at all.
-  buttons[1].click();
-  gTestHangReport.testCallback = oldcb;
-
-  // send another hang pulse, we should not get a notification here
-  Services.obs.notifyObservers(gTestHangReport, "process-hang-report", null);
-  is(notification.currentNotification, null, "no notification should be visible");
-
-  // After selecting Wait, we should get a userCanceled callback after
-  // HANG_EXPIRATION_TIME.
-  yield promiseReportCallMade(gTestHangReport.TEST_CALLBACK_CANCELED);
-
-  yield popPrefs();
-});
-
-/**
- * Test if hang reports receive user canceled callbacks after the content
- * process stops sending hang notifications.
- */
-
-add_task(function* hangGoesAwayTest() {
-  yield pushPrefs(["browser.hangNotification.expiration", 1000]);
-
-  let promise = promiseNotificationShown(window, "process-hang");
-  Services.obs.notifyObservers(gTestHangReport, "process-hang-report", null);
-  yield promise;
-
-  yield promiseReportCallMade(gTestHangReport.TEST_CALLBACK_CANCELED);
-
-  yield popPrefs();
-});
-
-/**
- * Tests if hang reports receive a terminate plugin callback when the user selects
- * stop in response to a plugin hang.
- */
-
-add_task(function* terminatePluginTest() {
-  let promise = promiseNotificationShown(window, "process-hang");
-  Services.obs.notifyObservers(gTestHangReport, "process-hang-report", null);
-  let notification = yield promise;
-
-  let buttons = notification.currentNotification.getElementsByTagName("button");
-  is(buttons.length, buttonCount, "proper number of buttons");
-
-  // Click the "Stop It" button, we should get a terminate script callback
-  gTestHangReport.hangType = gTestHangReport.PLUGIN_HANG;
-  promise = promiseReportCallMade(gTestHangReport.TEST_CALLBACK_TERMPLUGIN);
-  buttons[0].click();
-  yield promise;
-});
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -146,16 +146,17 @@ public:
   NS_IMETHOD GetScriptLineNo(uint32_t* aLineNo) override;
 
   NS_IMETHOD GetPluginName(nsACString& aPluginName) override;
 
   NS_IMETHOD TerminateScript() override;
   NS_IMETHOD BeginStartingDebugger() override;
   NS_IMETHOD EndStartingDebugger() override;
   NS_IMETHOD TerminatePlugin() override;
+  NS_IMETHOD TerminateProcess() override;
   NS_IMETHOD UserCanceled() override;
 
   NS_IMETHOD IsReportForBrowser(nsIFrameLoader* aFrameLoader, bool* aResult) override;
 
   // Called when a content process shuts down.
   void Clear() {
     mContentParent = nullptr;
     mActor = nullptr;
@@ -814,29 +815,45 @@ HangMonitoredProcess::EndStartingDebugge
 NS_IMETHODIMP
 HangMonitoredProcess::TerminatePlugin()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   if (mHangData.type() != HangData::TPluginHangData) {
     return NS_ERROR_UNEXPECTED;
   }
 
-  // generates a crash report that includes a browser report taken here
-  // earlier, the content process, and any plugin process(es).
   uint32_t id = mHangData.get_PluginHangData().pluginId();
   plugins::TerminatePlugin(id, NS_LITERAL_CSTRING("HangMonitor"),
                            mBrowserDumpId);
 
   if (mActor) {
     mActor->CleanupPluginHang(id, false);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
+HangMonitoredProcess::TerminateProcess()
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+  if (!mContentParent) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  if (mActor && mHangData.type() == HangData::TPluginHangData) {
+    uint32_t id = mHangData.get_PluginHangData().pluginId();
+    mActor->CleanupPluginHang(id, true);
+  }
+
+  mContentParent->KillHard("HangMonitor");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 HangMonitoredProcess::IsReportForBrowser(nsIFrameLoader* aFrameLoader, bool* aResult)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   if (!mActor) {
     *aResult = false;
     return NS_OK;
   }
--- a/dom/ipc/nsIHangReport.idl
+++ b/dom/ipc/nsIHangReport.idl
@@ -13,17 +13,17 @@ 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(5fcffbb9-be62-49b1-b8a1-36e820787a74)]
+[scriptable, uuid(90cea731-dd3e-459e-b017-f9a14697b56e)]
 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;
 
@@ -45,16 +45,20 @@ interface nsIHangReport : nsISupports
   // 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.