Bug 1118618 - [e10s] Slow script/plugin hang UI (r=mrbkap,mconley)

This commit is contained in:
Bill McCloskey 2015-01-16 10:11:18 -08:00
parent faf7b1fd21
commit 0d4c1b0760
33 changed files with 1776 additions and 37 deletions

View File

@ -1803,5 +1803,11 @@ pref("extensions.interposition.prefetching", true);
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);

View File

@ -149,6 +149,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "Social",
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");

View File

@ -334,6 +334,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

@ -1492,6 +1492,10 @@
if (wasActive)
aBrowser.focus();
let evt = document.createEvent("Events");
evt.initEvent("TabRemotenessChange", true, false);
tab.dispatchEvent(evt);
return true;
]]>
</body>

View File

@ -59,6 +59,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "CustomizationTabPreloader",
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");
@ -760,6 +763,8 @@ BrowserGlue.prototype = {
}
#endif
ProcessHangMonitor.init();
// A channel for "remote troubleshooting" code...
let channel = new WebChannel("remote-troubleshooting", "remote-troubleshooting");
channel.listen((id, data, target) => {

View File

@ -854,17 +854,9 @@ let gDevToolsBrowser = {
.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;
@ -874,13 +866,13 @@ let gDevToolsBrowser = {
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":
@ -888,7 +880,7 @@ let gDevToolsBrowser = {
threadClient.addOneTimeListener("resumed", () => {
threadClient.interrupt(() => {
threadClient.breakOnNext();
setupFinished = true;
aCallback();
});
});
break;
@ -897,6 +889,20 @@ let gDevToolsBrowser = {
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.
@ -908,6 +914,18 @@ let gDevToolsBrowser = {
}
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);
};
},
/**

View File

@ -831,3 +831,11 @@ just addresses the organization to follow, e.g. "This site is run by " -->
<!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">

View File

@ -435,6 +435,11 @@ dataReportingNotification.message = %1$S automatically sends some data to
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

View File

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

View File

@ -27,6 +27,7 @@ EXTRA_JS_MODULES += [
'NetworkPrioritizer.jsm',
'offlineAppCache.jsm',
'PanelFrame.jsm',
'ProcessHangMonitor.jsm',
'RemotePrompt.jsm',
'SitePermissions.jsm',
'Social.jsm',

View File

@ -16,6 +16,9 @@ SlowScriptDebug.prototype = {
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]);

View File

@ -67,6 +67,7 @@
#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"
@ -10969,17 +10970,45 @@ nsGlobalWindow::ShowSlowScriptDialog()
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;

View File

@ -726,6 +726,7 @@ public:
enum SlowScriptResponse {
ContinueSlowScript = 0,
ContinueSlowScriptAndKeepNotifying,
AlwaysContinueSlowScript,
KillSlowScript
};

View File

@ -5,6 +5,7 @@
#include "nsISupports.idl"
interface nsIDOMWindow;
interface nsIDOMEventTarget;
[scriptable, function, uuid(f7dbb80c-5d1e-4fd9-b55c-a9ffda4a75b1)]
interface nsISlowScriptDebugCallback : nsISupports
@ -12,8 +13,22 @@ 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;
};

View File

@ -24,6 +24,7 @@
#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"
@ -999,6 +1000,13 @@ ContentChild::AllocPBackgroundChild(Transport* aTransport,
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()

View File

@ -126,6 +126,10 @@ public:
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();

View File

@ -73,6 +73,8 @@
#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"
@ -1794,6 +1796,11 @@ ContentParent::ActorDestroy(ActorDestroyReason why)
// 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
@ -2026,6 +2033,7 @@ ContentParent::InitializeMembers()
mCreatedPairedMinidumps = false;
mShutdownPending = false;
mIPCOpen = true;
mHangMonitorActor = nullptr;
}
ContentParent::ContentParent(mozIApplication* aApp,
@ -2105,6 +2113,8 @@ ContentParent::ContentParent(mozIApplication* aApp,
ContentProcessManager::GetSingleton()->AddContentProcess(this);
ProcessHangMonitor::AddProcess(this);
// Set a reply timeout for CPOWs.
SetReplyTimeoutMs(Preferences::GetInt("dom.ipc.cpow.timeout", 0));
}
@ -3042,6 +3052,14 @@ ContentParent::AllocPBackgroundParent(Transport* aTransport,
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)
@ -4290,7 +4308,8 @@ ContentParent::RecvNotifyKeywordSearchLoading(const nsString &aProvider,
bool
ContentParent::ShouldContinueFromReplyTimeout()
{
return false;
nsRefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get();
return !monitor || !monitor->ShouldTimeOutCPOWs();
}
bool

View File

@ -484,6 +484,10 @@ private:
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;
@ -838,6 +842,8 @@ private:
static int32_t sNuwaPid;
static bool sNuwaReady;
#endif
PProcessHangMonitorParent* mHangMonitorActor;
};
} // namespace dom

View File

@ -21,6 +21,7 @@ include protocol PFileDescriptorSet;
include protocol PFMRadio;
include protocol PFileSystemRequest;
include protocol PHal;
include protocol PProcessHangMonitor;
include protocol PImageBridge;
include protocol PMemoryReportRequest;
include protocol PMobileConnection;
@ -345,6 +346,7 @@ 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;

View File

@ -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

View File

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

View File

@ -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

View File

@ -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

View File

@ -4,6 +4,12 @@
# 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',
]
@ -39,6 +45,8 @@ EXPORTS.mozilla.dom += [
EXPORTS.mozilla += [
'AppProcessChecker.h',
'PreallocatedProcessManager.h',
'ProcessHangMonitor.h',
'ProcessHangMonitorIPC.h',
'ProcessPriorityManager.h',
]
@ -73,6 +81,7 @@ SOURCES += [
'Blob.cpp',
'ContentChild.cpp',
'CrashReporterChild.cpp',
'ProcessHangMonitor.cpp',
]
IPDL_SOURCES += [
@ -92,6 +101,7 @@ IPDL_SOURCES += [
'PFilePicker.ipdl',
'PMemoryReportRequest.ipdl',
'PPluginWidget.ipdl',
'PProcessHangMonitor.ipdl',
'PScreenManager.ipdl',
'PTabContext.ipdlh',
]

66
dom/ipc/nsIHangReport.idl Normal file
View File

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

View File

@ -201,6 +201,8 @@ public:
// 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);
@ -276,7 +278,6 @@ private:
// Returns the first plugin at |path|
nsPluginTag* FirstPluginWithPath(const nsCString& path);
nsPluginTag* PluginWithId(uint32_t aId);
nsresult EnsurePrivateDirServiceProvider();

View File

@ -23,6 +23,9 @@ FindPluginsForContent(uint32_t aPluginEpoch,
nsTArray<PluginTag>* aPlugins,
uint32_t* aNewPluginEpoch);
void
TerminatePlugin(uint32_t aPluginId);
} // namespace plugins
} // namespace mozilla

View File

@ -21,6 +21,7 @@
#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"
@ -69,6 +70,7 @@ using namespace mozilla::plugins::parent;
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";
@ -257,6 +259,22 @@ PRCList 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)
{
@ -287,6 +305,8 @@ PluginModuleContentParent::LoadModule(uint32_t aPluginId)
mapping.forget();
}
parent->mPluginId = aPluginId;
return parent;
}
@ -326,6 +346,8 @@ PluginModuleContentParent::Initialize(mozilla::ipc::Transport* aTransport,
// 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.
@ -515,6 +537,12 @@ PluginModuleParent::~PluginModuleParent()
PluginModuleContentParent::PluginModuleContentParent()
: PluginModuleParent(false)
{
Preferences::RegisterCallback(TimeoutChanged, kContentTimeoutPref, this);
}
PluginModuleContentParent::~PluginModuleContentParent()
{
Preferences::UnregisterCallback(TimeoutChanged, kContentTimeoutPref, this);
}
PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath, uint32_t aPluginId)
@ -648,7 +676,7 @@ PluginModuleChromeParent::WriteExtraDataForMinidump(AnnotationTable& notes)
#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;
@ -656,24 +684,33 @@ PluginModuleChromeParent::SetChildTimeout(const int32_t aChildTimeout)
}
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);
}
}
@ -857,6 +894,23 @@ PluginModuleChromeParent::ShouldContinueFromReplyTimeout()
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)
{

View File

@ -176,6 +176,9 @@ protected:
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; }
@ -309,13 +312,19 @@ class PluginModuleContentParent : public PluginModuleParent
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
@ -343,6 +352,9 @@ class PluginModuleChromeParent
*/
void
OnHangUIContinue();
void
EvaluateHangUIState(const bool aReset);
#endif // XP_WIN
virtual bool WaitForIPCConnection() MOZ_OVERRIDE;
@ -402,8 +414,6 @@ private:
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;
@ -449,9 +459,6 @@ private:
#endif // MOZ_CRASHREPORTER
void
EvaluateHangUIState(const bool aReset);
/**
* Launches the Plugin Hang UI.
*

View File

@ -40,6 +40,7 @@
#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"
@ -1112,6 +1113,7 @@ class Watchdog
#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"
@ -1134,6 +1136,7 @@ class WatchdogManager : public nsIObserver
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:
@ -1147,6 +1150,7 @@ class WatchdogManager : public nsIObserver
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:
@ -1224,7 +1228,10 @@ class WatchdogManager : public nsIObserver
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));
}
}
@ -1341,6 +1348,10 @@ XPCJSRuntime::DefaultJSContextCallback(JSRuntime *rt)
void
XPCJSRuntime::ActivityCallback(void *arg, bool active)
{
if (!active) {
ProcessHangMonitor::ClearHang();
}
XPCJSRuntime* self = static_cast<XPCJSRuntime*>(arg);
self->mWatchdogManager->RecordRuntimeActivity(active);
}
@ -1379,13 +1390,16 @@ XPCJSRuntime::InterruptCallback(JSContext *cx)
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.
@ -1423,7 +1437,9 @@ XPCJSRuntime::InterruptCallback(JSContext *cx)
// 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);

View File

@ -2278,6 +2278,7 @@ 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
@ -2324,6 +2325,9 @@ pref("dom.ipc.plugins.timeoutSecs", 45);
// 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);
@ -2341,6 +2345,7 @@ 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

View File

@ -28,7 +28,8 @@ class GeckoInstance(object):
"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):

View File

@ -14,6 +14,8 @@ 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);