Merge m-c to fx-team

This commit is contained in:
Carsten "Tomcat" Book 2015-10-26 14:47:09 +01:00
commit a79c17bcd4
9 changed files with 115 additions and 369 deletions

View File

@ -319,6 +319,26 @@
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)"

View File

@ -925,6 +925,15 @@ you can use these alternative items. Otherwise, their values should be empty. -
<!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">

View File

@ -472,13 +472,9 @@ 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

View File

@ -19,31 +19,13 @@ Cu.import("resource://gre/modules/Services.jsm");
* the platform interface.
*/
/**
* 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;
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;
}
},
/**
* 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;
}
},
/**
* Collection of hang reports that haven't expired or been dismissed
* by the user. The keys are nsIHangReports and values keys are
@ -52,12 +34,6 @@ var ProcessHangMonitor = {
*/
_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.
*/
@ -94,78 +70,46 @@ var ProcessHangMonitor = {
},
/**
* 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);
if (!report) {
return;
}
switch (report.hangType) {
case report.SLOW_SCRIPT:
this.terminateScript(win);
break;
case report.PLUGIN_HANG:
this.terminatePlugin(win);
break;
}
terminateProcess: function(win) {
this.handleUserInput(win, report => report.terminateProcess());
},
/**
* 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.
* 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.
*/
waitLonger: function(win) {
let report = this.findActiveReport(win.gBrowser.selectedBrowser);
refreshMenu: function(win) {
let report = this.findReport(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.
function setVisible(id, visible) {
let item = win.document.getElementById(id);
item.hidden = !visible;
}
// 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);
}
},
/**
@ -174,11 +118,11 @@ var ProcessHangMonitor = {
* 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);
},
@ -209,9 +153,9 @@ var ProcessHangMonitor = {
},
/**
* 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)) {
@ -221,44 +165,6 @@ var ProcessHangMonitor = {
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
@ -285,7 +191,7 @@ 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);
@ -306,36 +212,19 @@ var ProcessHangMonitor = {
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);
},
@ -381,19 +270,11 @@ var ProcessHangMonitor = {
* 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;
}
@ -410,19 +291,28 @@ var ProcessHangMonitor = {
// 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;
}

View File

@ -33,6 +33,7 @@ EXTRA_JS_MODULES += [
'offlineAppCache.jsm',
'PanelFrame.jsm',
'PluginContent.jsm',
'ProcessHangMonitor.jsm',
'ReaderParent.jsm',
'RecentWindow.jsm',
'RemotePrompt.jsm',
@ -45,10 +46,6 @@ EXTRA_JS_MODULES += [
'webrtcUI.jsm',
]
EXTRA_PP_JS_MODULES += [
'ProcessHangMonitor.jsm'
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
EXTRA_JS_MODULES += [
'Windows8WindowFrameColor.jsm',

View File

@ -3,8 +3,6 @@ support-files =
head.js
[browser_BrowserUITelemetry_buckets.js]
[browser_ProcessHangNotifications.js]
skip-if = !e10s
[browser_ContentSearch.js]
skip-if = e10s
support-files =

View File

@ -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;
});

View File

@ -151,6 +151,7 @@ public:
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;
@ -819,8 +820,6 @@ HangMonitoredProcess::TerminatePlugin()
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);
@ -831,6 +830,24 @@ HangMonitoredProcess::TerminatePlugin()
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)
{

View File

@ -18,7 +18,7 @@ interface nsIFrameLoader;
* 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;
@ -50,6 +50,10 @@ interface nsIHangReport : nsISupports
// 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();