зеркало из https://github.com/mozilla/gecko-dev.git
244 строки
6.0 KiB
JavaScript
244 строки
6.0 KiB
JavaScript
/* 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";
|
|
|
|
var EXPORTED_SYMBOLS = ["GeckoViewProcessHangMonitor"];
|
|
|
|
const { GeckoViewModule } = ChromeUtils.import(
|
|
"resource://gre/modules/GeckoViewModule.jsm"
|
|
);
|
|
|
|
const { XPCOMUtils } = ChromeUtils.import(
|
|
"resource://gre/modules/XPCOMUtils.jsm"
|
|
);
|
|
|
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
|
Services: "resource://gre/modules/Services.jsm",
|
|
});
|
|
|
|
class GeckoViewProcessHangMonitor extends GeckoViewModule {
|
|
constructor(aModuleInfo) {
|
|
super(aModuleInfo);
|
|
|
|
/**
|
|
* Collection of hang reports that haven't expired or been dismissed
|
|
* by the user. These are nsIHangReports.
|
|
*/
|
|
this._activeReports = new Set();
|
|
|
|
/**
|
|
* Collection of hang reports that have been suppressed for a short
|
|
* period of time. Keys are nsIHangReports. Values are timeouts for
|
|
* when the wait time expires.
|
|
*/
|
|
this._pausedReports = new Map();
|
|
|
|
/**
|
|
* Simple index used for report identification
|
|
*/
|
|
this._nextIndex = 0;
|
|
|
|
/**
|
|
* Map of report IDs to report objects.
|
|
* Keys are numbers. Values are nsIHangReports.
|
|
*/
|
|
this._reportIndex = new Map();
|
|
|
|
/**
|
|
* Map of report objects to report IDs.
|
|
* Keys are nsIHangReports. Values are numbers.
|
|
*/
|
|
this._reportLookupIndex = new Map();
|
|
}
|
|
|
|
onInit() {
|
|
debug`onInit`;
|
|
Services.obs.addObserver(this, "process-hang-report");
|
|
Services.obs.addObserver(this, "clear-hang-report");
|
|
}
|
|
|
|
onDestroy() {
|
|
debug`onDestroy`;
|
|
Services.obs.removeObserver(this, "process-hang-report");
|
|
Services.obs.removeObserver(this, "clear-hang-report");
|
|
}
|
|
|
|
onEnable() {
|
|
debug`onEnable`;
|
|
this.registerListener([
|
|
"GeckoView:HangReportStop",
|
|
"GeckoView:HangReportWait",
|
|
]);
|
|
}
|
|
|
|
onDisable() {
|
|
debug`onDisable`;
|
|
this.unregisterListener();
|
|
}
|
|
|
|
// Bundle event handler.
|
|
onEvent(aEvent, aData, aCallback) {
|
|
debug`onEvent: event=${aEvent}, data=${aData}`;
|
|
|
|
if (this._reportIndex.has(aData.hangId)) {
|
|
const report = this._reportIndex.get(aData.hangId);
|
|
switch (aEvent) {
|
|
case "GeckoView:HangReportStop":
|
|
this.stopHang(report);
|
|
break;
|
|
case "GeckoView:HangReportWait":
|
|
this.pauseHang(report);
|
|
break;
|
|
}
|
|
} else {
|
|
debug`Report not found: reportIndex=${this._reportIndex}`;
|
|
}
|
|
}
|
|
|
|
// nsIObserver event handler
|
|
observe(aSubject, aTopic, aData) {
|
|
debug`observe(aTopic=${aTopic})`;
|
|
aSubject.QueryInterface(Ci.nsIHangReport);
|
|
if (!aSubject.isReportForBrowser(this.browser.frameLoader)) {
|
|
return;
|
|
}
|
|
|
|
switch (aTopic) {
|
|
case "process-hang-report": {
|
|
this.reportHang(aSubject);
|
|
break;
|
|
}
|
|
case "clear-hang-report": {
|
|
this.clearHang(aSubject);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
pauseHang(report) {
|
|
this._activeReports.delete(report);
|
|
|
|
// Create a new timeout with notify callback
|
|
const timer = this.window.setTimeout(() => {
|
|
for (const [stashedReport, otherTimer] of this._pausedReports) {
|
|
if (otherTimer === timer) {
|
|
this._pausedReports.delete(stashedReport);
|
|
|
|
// We're still hung, so move the report back to the active
|
|
// list.
|
|
this._activeReports.add(report);
|
|
break;
|
|
}
|
|
}
|
|
}, this.WAIT_EXPIRATION_TIME);
|
|
|
|
this._pausedReports.set(report, timer);
|
|
}
|
|
|
|
/**
|
|
* construct an information bundle
|
|
*/
|
|
notifyReport(report) {
|
|
const message = {
|
|
type: "GeckoView:HangReport",
|
|
hangId: this._reportLookupIndex.get(report),
|
|
};
|
|
|
|
if (report.hangType == report.SLOW_SCRIPT) {
|
|
message.hangType = "SLOW_SCRIPT";
|
|
message.scriptFileName = report.scriptFileName;
|
|
this.eventDispatcher.sendRequest(message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle a potentially new hang report.
|
|
*/
|
|
reportHang(report) {
|
|
// if we aren't enabled then default to stopping the script
|
|
if (!this.enabled) {
|
|
this.stopHang(report);
|
|
return;
|
|
}
|
|
|
|
// if we have already notified, remind
|
|
if (this._activeReports.has(report)) {
|
|
this.notifyReport(report);
|
|
return;
|
|
}
|
|
|
|
// If this hang was already reported and paused by the user then ignore it.
|
|
if (this._pausedReports.has(report)) {
|
|
return;
|
|
}
|
|
|
|
const index = this._nextIndex++;
|
|
this._reportLookupIndex.set(report, index);
|
|
this._reportIndex.set(index, report);
|
|
this._activeReports.add(report);
|
|
|
|
// Actually notify the new report
|
|
this.notifyReport(report);
|
|
}
|
|
|
|
clearHang(report) {
|
|
this._activeReports.delete(report);
|
|
|
|
const timer = this._pausedReports.get(report);
|
|
if (timer) {
|
|
this.window.clearTimeout(timer);
|
|
}
|
|
this._pausedReports.delete(report);
|
|
|
|
if (this._reportLookupIndex.has(report)) {
|
|
const index = this._reportLookupIndex.get(report);
|
|
this._reportIndex.delete(index);
|
|
}
|
|
this._reportLookupIndex.delete(report);
|
|
report.userCanceled();
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
const { debug, warn } = GeckoViewProcessHangMonitor.initLogging(
|
|
"GeckoViewProcessHangMonitor"
|
|
);
|