--- a/browser/modules/ProcessHangMonitor.jsm
+++ b/browser/modules/ProcessHangMonitor.jsm
@@ -29,16 +29,22 @@ var ProcessHangMonitor = {
try {
return Services.prefs.getIntPref("browser.hangNotification.waitPeriod");
} catch (ex) {
return 10000;
}
},
/**
+ * Should only be set to true once the quit-application-granted notification
+ * has been fired.
+ */
+ _shuttingDown: false,
+
+ /**
* Collection of hang reports that haven't expired or been dismissed
* by the user. These are nsIHangReports.
*/
_activeReports: new Set(),
/**
* Collection of hang reports that have been suppressed for a short
* period of time. Value is an nsITimer for when the wait time
@@ -47,16 +53,17 @@ var ProcessHangMonitor = {
_pausedReports: new Map(),
/**
* Initialize hang reporting. Called once in the parent process.
*/
init() {
Services.obs.addObserver(this, "process-hang-report");
Services.obs.addObserver(this, "clear-hang-report");
+ Services.obs.addObserver(this, "quit-application-granted");
Services.obs.addObserver(this, "xpcom-shutdown");
Services.ww.registerNotification(this);
},
/**
* Terminate JavaScript associated with the hang being reported for
* the selected browser in |win|.
*/
@@ -132,16 +139,37 @@ var ProcessHangMonitor = {
switch (report.hangType) {
case report.SLOW_SCRIPT:
this.terminateGlobal(win);
break;
}
},
/**
+ * Terminate whatever is causing this report, be it an add-on, page script,
+ * or plug-in. This is done without updating any report notifications.
+ */
+ stopHang(report) {
+ switch (report.hangType) {
+ case report.SLOW_SCRIPT: {
+ if (report.addonId) {
+ report.terminateGlobal();
+ } else {
+ report.terminateScript();
+ }
+ break;
+ }
+ case report.PLUGIN_HANG: {
+ report.terminatePlugin();
+ break;
+ }
+ }
+ },
+
+ /**
* 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(win) {
let report = this.findActiveReport(win.gBrowser.selectedBrowser);
if (!report) {
return;
}
@@ -187,41 +215,76 @@ var ProcessHangMonitor = {
}
this.removeActiveReport(report);
return func(report);
},
observe(subject, topic, data) {
switch (topic) {
- case "xpcom-shutdown":
+ case "xpcom-shutdown": {
Services.obs.removeObserver(this, "xpcom-shutdown");
Services.obs.removeObserver(this, "process-hang-report");
Services.obs.removeObserver(this, "clear-hang-report");
+ Services.obs.removeObserver(this, "quit-application-granted");
Services.ww.unregisterNotification(this);
break;
+ }
- case "process-hang-report":
+ case "quit-application-granted": {
+ this.onQuitApplicationGranted();
+ break;
+ }
+
+ case "process-hang-report": {
this.reportHang(subject.QueryInterface(Ci.nsIHangReport));
break;
+ }
- case "clear-hang-report":
+ case "clear-hang-report": {
this.clearHang(subject.QueryInterface(Ci.nsIHangReport));
break;
+ }
- case "domwindowopened":
+ 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;
+ }
+ }
+ },
+
+ /**
+ * Called early on in the shutdown sequence. We take this opportunity to
+ * take any pre-existing hang reports, and terminate them. We also put
+ * ourselves in a state so that if any more hang reports show up while
+ * we're shutting down, we terminate them immediately.
+ */
+ onQuitApplicationGranted() {
+ this._shuttingDown = true;
+ this.stopAllHangs();
+ this.updateWindows();
+ },
+
+ stopAllHangs() {
+ for (let report of this._activeReports) {
+ this.stopHang(report);
+ }
+
+ this._activeReports = new Set();
+
+ for (let [pausedReport, ] of this._pausedReports) {
+ this.stopHang(pausedReport);
+ this.removePausedReport(pausedReport);
}
},
/**
* Find a active hang report for the given <browser> element.
*/
findActiveReport(browser) {
let frameLoader = browser.frameLoader;
@@ -270,16 +333,25 @@ var ProcessHangMonitor = {
/**
* 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() {
let e = Services.wm.getEnumerator("navigator:browser");
+
+ // If it turns out we have no windows (this can happen on macOS),
+ // we have no opportunity to ask the user whether or not they want
+ // to stop the hang or wait, so we'll opt for stopping the hang.
+ if (!e.hasMoreElements()) {
+ this.stopAllHangs();
+ return;
+ }
+
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);
@@ -414,16 +486,21 @@ 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(report) {
+ if (this._shuttingDown) {
+ this.stopHang(report);
+ return;
+ }
+
// If this hang was already reported reset the timer for it.
if (this._activeReports.has(report)) {
// if this report is in active but doesn't have a notification associated
// with it, display a notification.
this.updateWindows();
return;
}