☠☠ backed out by 23cc21e9bfef ☠ ☠ | |
author | Bill McCloskey <wmccloskey@mozilla.com> |
Mon, 12 Jan 2015 23:06:54 -0800 | |
changeset 223497 | 8ab6c26d26f5404a742ab245ad2115e09e01d5d7 |
parent 223496 | f7d82a8a8e94a74ff4afd522602a39120658e44e |
child 223498 | 422367d84ec6acedb1b8811bc0d2e69f449b5384 |
push id | 28095 |
push user | cbook@mozilla.com |
push date | Tue, 13 Jan 2015 13:24:48 +0000 |
treeherder | mozilla-central@a5700bec72e1 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | mrbkap, mconley |
bugs | 1118618 |
milestone | 38.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1794,10 +1794,16 @@ pref("print.enable_e10s_testing", true); #ifdef NIGHTLY_BUILD // Enable e10s add-on interposition by default. pref("extensions.interposition.enabled", true); pref("extensions.interposition.prefetching", true); #endif 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);
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -144,16 +144,19 @@ XPCOMUtils.defineLazyGetter(this, "Brows }); XPCOMUtils.defineLazyModuleGetter(this, "Social", "resource:///modules/Social.jsm"); 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"); #endif XPCOMUtils.defineLazyModuleGetter(this, "gCustomizationTabPreloader", "resource:///modules/CustomizationTabPreloader.jsm", "CustomizationTabPreloader");
--- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -329,16 +329,36 @@ noautofocus="true"/> <panel id="loop-panel" class="loop-panel social-panel" type="arrow" orient="horizontal" hidden="true"/> + <menupopup id="processHangOptions" + onpopupshowing="ProcessHangMonitor.refreshMenu(window);"> + <menuitem id="processHangTerminateScript" + oncommand="ProcessHangMonitor.terminateScript(window)" + accesskey="&processHang.terminateScript.accessKey;" + label="&processHang.terminateScript.label;"/> + <menuitem id="processHangDebugScript" + oncommand="ProcessHangMonitor.debugScript(window)" + accesskey="&processHang.debugScript.accessKey;" + label="&processHang.debugScript.label;"/> + <menuitem id="processHangTerminatePlugin" + oncommand="ProcessHangMonitor.terminatePlugin(window)" + accesskey="&processHang.terminatePlugin.accessKey;" + label="&processHang.terminatePlugin.label;"/> + <menuitem id="processHangTerminateProcess" + oncommand="ProcessHangMonitor.terminateProcess(window)" + accesskey="&processHang.terminateProcess.accessKey;" + label="&processHang.terminateProcess.label;"/> + </menupopup> + <menupopup id="toolbar-context-menu" onpopupshowing="onViewToolbarsPopupShowing(event, document.getElementById('viewToolbarsMenuSeparator'));"> <menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)" accesskey="&customizeMenu.moveToPanel.accesskey;" label="&customizeMenu.moveToPanel.label;" contexttype="toolbaritem" class="customize-context-moveToPanel"/> <menuitem oncommand="gCustomizeMode.removeFromArea(document.popupNode)"
--- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -1487,16 +1487,20 @@ } else { tab.removeAttribute("remote"); aBrowser.messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: tab.pinned }) } if (wasActive) aBrowser.focus(); + let evt = document.createEvent("Events"); + evt.initEvent("TabRemotenessChange", true, false); + tab.dispatchEvent(evt); + return true; ]]> </body> </method> <method name="updateBrowserRemotenessByURL"> <parameter name="aBrowser"/> <parameter name="aURL"/>
--- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -54,16 +54,19 @@ XPCOMUtils.defineLazyModuleGetter(this, "resource://gre/modules/NewTabUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "CustomizationTabPreloader", "resource:///modules/CustomizationTabPreloader.jsm"); 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"); #endif XPCOMUtils.defineLazyModuleGetter(this, "webrtcUI", "resource:///modules/webrtcUI.jsm"); @@ -755,16 +758,18 @@ BrowserGlue.prototype = { if (WINTASKBAR_CONTRACTID in Cc && Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) { let temp = {}; Cu.import("resource:///modules/WindowsJumpLists.jsm", temp); temp.WinTaskbarJumpList.startup(); } #endif + ProcessHangMonitor.init(); + // A channel for "remote troubleshooting" code... let channel = new WebChannel("remote-troubleshooting", "remote-troubleshooting"); channel.listen((id, data, target) => { if (data.command == "request") { let {Troubleshoot} = Cu.import("resource://gre/modules/Troubleshoot.jsm", {}); Troubleshoot.snapshot(data => { // for privacy we remove crash IDs and all preferences (but bug 1091944 // exists to expose prefs once we are confident of privacy implications)
--- a/browser/devtools/framework/gDevTools.jsm +++ b/browser/devtools/framework/gDevTools.jsm @@ -849,70 +849,88 @@ let gDevToolsBrowser = { * Hook the JS debugger tool to the "Debug Script" button of the slow script * dialog. */ setSlowScriptDebugHandler: function DT_setSlowScriptDebugHandler() { let debugService = Cc["@mozilla.org/dom/slow-script-debug;1"] .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; // Break in place, which means resuming the debuggee thread and pausing // right before the next step happens. switch (threadClient.state) { 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": // The debugger is newly opened. threadClient.addOneTimeListener("resumed", () => { threadClient.interrupt(() => { threadClient.breakOnNext(); - setupFinished = true; + aCallback(); }); }); break; default: throw Error("invalid thread client state in slow script debug handler: " + 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. let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); utils.enterModalState(); while (!setupFinished) { tm.currentThread.processNextEvent(true); } 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); + }; }, /** * Unset the slow script debug handler. */ unsetSlowScriptDebugHandler: function DT_unsetSlowScriptDebugHandler() { let debugService = Cc["@mozilla.org/dom/slow-script-debug;1"] .getService(Ci.nsISlowScriptDebug);
--- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -826,8 +826,16 @@ just addresses the organization to follo <!ENTITY panicButton.view.undoWarning "This action cannot be undone."> <!ENTITY panicButton.view.forgetButton "Forget!"> <!ENTITY panicButton.thankyou.msg1 "Your recent history is cleared."> <!ENTITY panicButton.thankyou.msg2 "Safe browsing!"> <!ENTITY panicButton.thankyou.buttonlabel "Thanks!"> +<!ENTITY processHang.terminateScript.label "Stop Script"> +<!ENTITY processHang.terminateScript.accessKey "S"> +<!ENTITY processHang.debugScript.label "Debug Script"> +<!ENTITY processHang.debugScript.accessKey "D"> +<!ENTITY processHang.terminatePlugin.label "Kill Plugin"> +<!ENTITY processHang.terminatePlugin.accessKey "P"> +<!ENTITY processHang.terminateProcess.label "Kill Web Process"> +<!ENTITY processHang.terminateProcess.accessKey "K">
--- a/browser/locales/en-US/chrome/browser/browser.properties +++ b/browser/locales/en-US/chrome/browser/browser.properties @@ -430,16 +430,21 @@ syncPromoNotification.addons.description # The final space separates this text from the Learn More link. syncPromoNotification.addons-sync-disabled.description=You can use your %S account to synchronize add-ons across multiple devices.\u0020 # Mozilla data reporting notification (Telemetry, Firefox Health Report, etc) dataReportingNotification.message = %1$S automatically sends some data to %2$S so that we can improve your experience. dataReportingNotification.button.label = Choose What I Share dataReportingNotification.button.accessKey = C +# Process hang reporter +processHang.message = A web page is causing %1$S to run slowly. What would you like to do? +processHang.button.label = Options +processHang.button.accessKey = O + # Webapps notification popup webapps.install = Install webapps.install.accesskey = I #LOCALIZATION NOTE (webapps.requestInstall) %1$S is the web app name, %2$S is the site from which the web app is installed webapps.requestInstall = Do you want to install "%1$S" from this site (%2$S)? webapps.install.success = Application Installed webapps.install.inprogress = Installation in progress webapps.uninstall = Uninstall
new file mode 100644 --- /dev/null +++ b/browser/modules/ProcessHangMonitor.jsm @@ -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; + } + } + }, +};
--- a/browser/modules/moz.build +++ b/browser/modules/moz.build @@ -22,16 +22,17 @@ EXTRA_JS_MODULES += [ 'DirectoryLinksProvider.jsm', 'E10SUtils.jsm', 'Feeds.jsm', 'FormSubmitObserver.jsm', 'FormValidationHandler.jsm', 'NetworkPrioritizer.jsm', 'offlineAppCache.jsm', 'PanelFrame.jsm', + 'ProcessHangMonitor.jsm', 'RemotePrompt.jsm', 'SitePermissions.jsm', 'Social.jsm', 'TabCrashReporter.jsm', 'WebappManager.jsm', ] if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
--- a/dom/base/SlowScriptDebug.js +++ b/dom/base/SlowScriptDebug.js @@ -11,11 +11,14 @@ function SlowScriptDebug() { } SlowScriptDebug.prototype = { classID: Components.ID("{e740ddb4-18b4-4aac-8ae1-9b0f4320769d}"), classDescription: "Slow script debug handler", contractID: "@mozilla.org/dom/slow-script-debug;1", QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISlowScriptDebug]), 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]);
--- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -62,16 +62,17 @@ #include "mozilla/dom/MessagePortList.h" #include "mozilla/dom/ToJSValue.h" #include "nsJSPrincipals.h" #include "mozilla/Attributes.h" #include "mozilla/Debug.h" #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" #include "nsCharTraits.h" // NS_IS_HIGH/LOW_SURROGATE // Interfaces Needed #include "nsIFrame.h" #include "nsCanvasFrame.h" @@ -10964,27 +10965,55 @@ nsGlobalWindow::ShowSlowScriptDialog() return KillSlowScript; } // If our document is not active, just kill the script: we've been unloaded if (!HasActiveDocument()) { 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; if (hasFrame) { const char *debugCID = "@mozilla.org/dom/slow-script-debug;1"; nsCOMPtr<nsISlowScriptDebug> debugService = do_GetService(debugCID, &rv); if (NS_SUCCEEDED(rv)) { debugService->GetActivationHandler(getter_AddRefs(debugCallback));
--- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -721,16 +721,17 @@ public: void AllowScriptsToClose() { mAllowScriptsToClose = true; } enum SlowScriptResponse { ContinueSlowScript = 0, + ContinueSlowScriptAndKeepNotifying, AlwaysContinueSlowScript, KillSlowScript }; SlowScriptResponse ShowSlowScriptDialog(); #ifdef MOZ_GAMEPAD // Inner windows only. void AddGamepad(uint32_t aIndex, mozilla::dom::Gamepad* aGamepad);
--- a/dom/base/nsISlowScriptDebug.idl +++ b/dom/base/nsISlowScriptDebug.idl @@ -1,19 +1,34 @@ /* 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 nsIDOMWindow; +interface nsIDOMEventTarget; [scriptable, function, uuid(f7dbb80c-5d1e-4fd9-b55c-a9ffda4a75b1)] 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; };
--- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -19,16 +19,17 @@ #include "GeckoProfiler.h" #include "TabChild.h" #include "mozilla/Attributes.h" #ifdef ACCESSIBILITY #include "mozilla/a11y/DocAccessibleChild.h" #endif #include "mozilla/Preferences.h" +#include "mozilla/ProcessHangMonitor.h" #include "mozilla/docshell/OfflineCacheUpdateChild.h" #include "mozilla/dom/ContentBridgeChild.h" #include "mozilla/dom/ContentBridgeParent.h" #include "mozilla/dom/DOMStorageIPC.h" #include "mozilla/dom/ExternalHelperAppChild.h" #include "mozilla/dom/PCrashReporterChild.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/asmjscache/AsmJSCache.h" @@ -994,16 +995,24 @@ ContentChild::AllocPImageBridgeChild(moz PBackgroundChild* ContentChild::AllocPBackgroundChild(Transport* aTransport, ProcessId aOtherProcess) { return BackgroundChild::Alloc(aTransport, aOtherProcess); } +PProcessHangMonitorChild* +ContentChild::AllocPProcessHangMonitorChild(Transport* aTransport, + ProcessId aOtherProcess) +{ + nsRefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::GetOrCreate(); + return monitor->CreateChild(aTransport, aOtherProcess); +} + #if defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX) static void SetUpSandboxEnvironment() { // Set up a low integrity temp directory. This only makes sense if the // delayed integrity level for the content process is INTEGRITY_LEVEL_LOW. nsresult rv; nsCOMPtr<nsIProperties> directoryService =
--- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -121,16 +121,20 @@ public: PSharedBufferManagerChild* AllocPSharedBufferManagerChild(mozilla::ipc::Transport* aTransport, base::ProcessId aOtherProcess) MOZ_OVERRIDE; PImageBridgeChild* 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(); #endif virtual bool RecvSetProcessSandbox() MOZ_OVERRIDE; PBackgroundChild*
--- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -68,16 +68,17 @@ #include "mozilla/ipc/TestShellParent.h" #include "mozilla/ipc/InputStreamUtils.h" #include "mozilla/layers/CompositorParent.h" #include "mozilla/layers/ImageBridgeParent.h" #include "mozilla/layers/SharedBufferManagerParent.h" #include "mozilla/net/NeckoParent.h" #include "mozilla/plugins/PluginBridge.h" #include "mozilla/Preferences.h" +#include "mozilla/ProcessHangMonitor.h" #include "mozilla/Services.h" #include "mozilla/StaticPtr.h" #include "mozilla/Telemetry.h" #include "mozilla/unused.h" #include "nsAnonymousTemporaryFile.h" #include "nsAppRunner.h" #include "nsAutoPtr.h" #include "nsCDefaultURIFixup.h" @@ -1789,16 +1790,21 @@ ContentParent::ActorDestroy(ActorDestroy mForceKillTimer->Cancel(); mForceKillTimer = nullptr; } // Signal shutdown completion regardless of error state, so we can // 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 // ShutDownProcess below to perform other necessary clean up. mCalledClose = true; } // Make sure we always clean up. @@ -2021,16 +2027,17 @@ ContentParent::InitializeMembers() mSendPermissionUpdates = false; mSendDataStoreInfos = false; mCalledClose = false; mCalledCloseWithError = false; mCalledKillHard = false; mCreatedPairedMinidumps = false; mShutdownPending = false; mIPCOpen = true; + mHangMonitorActor = nullptr; } ContentParent::ContentParent(mozIApplication* aApp, ContentParent* aOpener, bool aIsForBrowser, bool aIsForPreallocated, ProcessPriority aInitialPriority /* = PROCESS_PRIORITY_FOREGROUND */, bool aIsNuwaProcess /* = false */) @@ -2100,16 +2107,18 @@ ContentParent::ContentParent(mozIApplica Open(mSubprocess->GetChannel(), mSubprocess->GetOwnedChildProcessHandle()); InitInternal(aInitialPriority, true, /* Setup off-main thread compositing */ true /* Send registered chrome */); ContentProcessManager::GetSingleton()->AddContentProcess(this); + ProcessHangMonitor::AddProcess(this); + // Set a reply timeout for CPOWs. SetReplyTimeoutMs(Preferences::GetInt("dom.ipc.cpow.timeout", 0)); } #ifdef MOZ_NUWA_PROCESS static const mozilla::ipc::FileDescriptor* FindFdProtocolFdMapping(const nsTArray<ProtocolFdMapping>& aFds, ProtocolId aProtoId) @@ -3037,16 +3046,25 @@ ContentParent::AllocPImageBridgeParent(m PBackgroundParent* ContentParent::AllocPBackgroundParent(Transport* aTransport, ProcessId aOtherProcess) { return BackgroundParent::Alloc(this, aTransport, aOtherProcess); } +PProcessHangMonitorParent* +ContentParent::AllocPProcessHangMonitorParent(Transport* aTransport, + ProcessId aOtherProcess) +{ + nsRefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::GetOrCreate(); + mHangMonitorActor = monitor->CreateParent(this, aTransport, aOtherProcess); + return mHangMonitorActor; +} + PSharedBufferManagerParent* ContentParent::AllocPSharedBufferManagerParent(mozilla::ipc::Transport* aTransport, base::ProcessId aOtherProcess) { return SharedBufferManagerParent::Create(aTransport, aOtherProcess); } bool @@ -4285,17 +4303,18 @@ ContentParent::RecvNotifyKeywordSearchLo } #endif return true; } bool ContentParent::ShouldContinueFromReplyTimeout() { - return false; + nsRefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get(); + return !monitor || !monitor->ShouldTimeOutCPOWs(); } bool ContentParent::RecvRecordingDeviceEvents(const nsString& aRecordingStatus, const nsString& aPageURL, const bool& aIsAudio, const bool& aIsVideo) {
--- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -479,16 +479,20 @@ private: PSharedBufferManagerParent* AllocPSharedBufferManagerParent(mozilla::ipc::Transport* aTranport, base::ProcessId aOtherProcess) MOZ_OVERRIDE; PBackgroundParent* 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; virtual bool RecvGetXPCOMProcessAttributes(bool* aIsOffline, InfallibleTArray<nsString>* dictionaries, ClipboardCapabilities* clipboardCaps) MOZ_OVERRIDE; @@ -833,16 +837,18 @@ private: // object instead of the child process's lifetime. ScopedClose mChildXSocketFdDup; #endif #ifdef MOZ_NUWA_PROCESS static int32_t sNuwaPid; static bool sNuwaReady; #endif + + PProcessHangMonitorParent* mHangMonitorActor; }; } // namespace dom } // namespace mozilla class ParentIdleListener : public nsIObserver { public: NS_DECL_ISUPPORTS
--- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -16,16 +16,17 @@ include protocol PCycleCollectWithLogs; include protocol PCrashReporter; include protocol PDocAccessible; include protocol PExternalHelperApp; include protocol PDeviceStorageRequest; include protocol PFileDescriptorSet; include protocol PFMRadio; include protocol PFileSystemRequest; include protocol PHal; +include protocol PProcessHangMonitor; include protocol PImageBridge; include protocol PMemoryReportRequest; include protocol PMobileConnection; include protocol PNecko; include protocol PPluginModule; include protocol PPrinting; include protocol POfflineCacheUpdate; include protocol PScreenManager; @@ -340,16 +341,17 @@ union OptionalContentId void_t; }; 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; manages PAsmJSCacheEntry; manages PBlob; manages PBluetooth; manages PBrowser;
new file mode 100644 --- /dev/null +++ b/dom/ipc/PProcessHangMonitor.ipdl @@ -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
new file mode 100644 --- /dev/null +++ b/dom/ipc/ProcessHangMonitor.cpp @@ -0,0 +1,914 @@ +/* -*- 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/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: + 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 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: + const nsRefPtr<ProcessHangMonitor> mHangMonitor; + + // This field is read-only after construction. + bool mReportHangs; + + Monitor mMonitor; + + // Must be accessed with mMonitor held. + nsRefPtr<HangMonitoredProcess> mProcess; + bool mIPCOpen; +}; + +} // 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), + 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 (mIPCOpen) { + mMonitor.Wait(); + } +} + +void +HangMonitorChild::ActorDestroy(ActorDestroyReason aWhy) +{ + MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); + + MonitorAutoLock lock(mMonitor); + mIPCOpen = false; + mMonitor.Notify(); +} + +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) +{ + MonitorAutoLock lock(mMonitor); + 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()); + + MonitorAutoLock lock(mMonitor); + 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), + mMonitor("HangMonitorParent lock"), + mIPCOpen(true) +{ + 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; + } + + if (!mIPCOpen) { + return; + } + + MonitorLoop()->PostTask( + FROM_HERE, + NewRunnableMethod(this, &HangMonitorParent::Close)); + + while (mIPCOpen) { + mMonitor.Wait(); + } +} + +void +HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy) +{ + MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); + + MonitorAutoLock lock(mMonitor); + mIPCOpen = false; + mMonitor.Notify(); +} + +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()); + + MonitorAutoLock lock(mMonitor); + if (mIPCOpen) { + unused << SendTerminateScript(); + } +} + +void +HangMonitorParent::BeginStartingDebugger() +{ + MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); + + MonitorAutoLock lock(mMonitor); + if (mIPCOpen) { + unused << SendBeginStartingDebugger(); + } +} + +void +HangMonitorParent::EndStartingDebugger() +{ + MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop()); + + MonitorAutoLock lock(mMonitor); + 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* +ProcessHangMonitor::CreateParent(ContentParent* aContentParent, + mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherProcess) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + HangMonitorParent* parent = new HangMonitorParent(this); + + 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; + } + + mThread->message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(parent, &HangMonitorParent::Open, + aTransport, handle, XRE_GetIOMessageLoop())); + + return parent; +} + +PProcessHangMonitorChild* +ProcessHangMonitor::CreateChild(mozilla::ipc::Transport* aTransport, base::ProcessId aOtherProcess) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + HangMonitorChild* child = new HangMonitorChild(this); + + base::ProcessHandle handle; + if (!base::OpenProcessHandle(aOtherProcess, &handle)) { + // XXX need to kill |aOtherProcess|, it's boned + return nullptr; + } + + mThread->message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(child, &HangMonitorChild::Open, + aTransport, handle, XRE_GetIOMessageLoop())); + + return child; +} + +/* 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(); + } +}
new file mode 100644 --- /dev/null +++ b/dom/ipc/ProcessHangMonitor.h @@ -0,0 +1,84 @@ +/* -*- 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 "base/task.h" +#include "base/thread.h" + +#include "mozilla/Atomics.h" +#include "mozilla/PProcessHangMonitor.h" +#include "mozilla/PProcessHangMonitorParent.h" +#include "mozilla/PProcessHangMonitorChild.h" +#include "nsIObserver.h" + +class nsGlobalWindow; +class nsITabChild; + +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(); + + PProcessHangMonitorParent* CreateParent(mozilla::dom::ContentParent* aContentParent, + mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherProcess); + PProcessHangMonitorChild* CreateChild(mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherProcess); + + 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() { return mThread->message_loop(); } + + private: + static ProcessHangMonitor* sInstance; + + Atomic<bool> mCPOWTimeout; + + base::Thread* mThread; +}; + +} // namespace mozilla + +#endif // mozilla_ProcessHangMonitor_h
--- a/dom/ipc/moz.build +++ b/dom/ipc/moz.build @@ -1,14 +1,20 @@ # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- # vim: set filetype=python: # 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/. +XPIDL_SOURCES += [ + 'nsIHangReport.idl', +] + +XPIDL_MODULE = 'dom' + EXPORTS += [ 'nsICachedFileDescriptorListener.h', ] EXPORTS.mozilla.dom.ipc += [ 'BlobChild.h', 'BlobParent.h', 'IdType.h', @@ -34,16 +40,17 @@ EXPORTS.mozilla.dom += [ 'TabContext.h', 'TabMessageUtils.h', 'TabParent.h', ] EXPORTS.mozilla += [ 'AppProcessChecker.h', 'PreallocatedProcessManager.h', + 'ProcessHangMonitor.h', 'ProcessPriorityManager.h', ] UNIFIED_SOURCES += [ 'AppProcessChecker.cpp', 'ColorPickerParent.cpp', 'ContentBridgeChild.cpp', 'ContentBridgeParent.cpp', @@ -68,16 +75,17 @@ UNIFIED_SOURCES += [ # Blob.cpp cannot be compiled in unified mode because it triggers a fatal gcc warning. # CrashReporterChild.cpp cannot be compiled in unified mode because of name clashes # in OS X headers. # ContentChild.cpp cannot be compiled in unified mode on linux due to Time conflict SOURCES += [ 'Blob.cpp', 'ContentChild.cpp', 'CrashReporterChild.cpp', + 'ProcessHangMonitor.cpp', ] IPDL_SOURCES += [ 'DOMTypes.ipdlh', 'PBlob.ipdl', 'PBlobStream.ipdl', 'PBrowser.ipdl', 'PBrowserOrId.ipdlh', @@ -87,16 +95,17 @@ IPDL_SOURCES += [ 'PContentPermission.ipdlh', 'PContentPermissionRequest.ipdl', 'PCrashReporter.ipdl', 'PCycleCollectWithLogs.ipdl', 'PDocumentRenderer.ipdl', 'PFilePicker.ipdl', 'PMemoryReportRequest.ipdl', 'PPluginWidget.ipdl', + 'PProcessHangMonitor.ipdl', 'PScreenManager.ipdl', 'PTabContext.ipdlh', ] FAIL_ON_WARNINGS = True include('/ipc/chromium/chromium-config.mozbuild')
new file mode 100644 --- /dev/null +++ b/dom/ipc/nsIHangReport.idl @@ -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); +};
--- a/dom/plugins/base/nsPluginHost.h +++ b/dom/plugins/base/nsPluginHost.h @@ -196,16 +196,18 @@ public: // and don't need to set up a new stream. nsresult InstantiatePluginInstance(const char *aMimeType, nsIURI* aURL, nsObjectLoadingContent *aContent, nsPluginInstanceOwner** aOwner); // 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); nsresult NewPluginStreamListener(nsIURI* aURL, nsNPAPIPluginInstance* aInstance, nsIStreamListener **aStreamListener); @@ -271,17 +273,16 @@ private: // Checks to see if a tag object is in our list of live tags. bool IsLiveTag(nsIPluginTag* tag); // Checks our list of live tags for an equivalent tag. nsPluginTag* HaveSamePlugin(const nsPluginTag * aPluginTag); // Returns the first plugin at |path| nsPluginTag* FirstPluginWithPath(const nsCString& path); - nsPluginTag* PluginWithId(uint32_t aId); nsresult EnsurePrivateDirServiceProvider(); void OnPluginInstanceDestroyed(nsPluginTag* aPluginTag); // To be used by the chrome process whenever the set of plugins changes. void IncrementChromeEpoch();
--- a/dom/plugins/ipc/PluginBridge.h +++ b/dom/plugins/ipc/PluginBridge.h @@ -18,12 +18,15 @@ namespace plugins { bool SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent); bool FindPluginsForContent(uint32_t aPluginEpoch, nsTArray<PluginTag>* aPlugins, uint32_t* aNewPluginEpoch); +void +TerminatePlugin(uint32_t aPluginId); + } // namespace plugins } // namespace mozilla #endif // mozilla_plugins_PluginBridge_h
--- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -16,16 +16,17 @@ #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/PCrashReporterParent.h" #include "mozilla/ipc/MessageChannel.h" #include "mozilla/plugins/BrowserStreamParent.h" #include "mozilla/plugins/PluginAsyncSurrogate.h" #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" #include "nsAutoPtr.h" #include "nsCRT.h" #include "nsIFile.h" #include "nsIObserverService.h" #include "nsNPAPIPlugin.h" @@ -64,16 +65,17 @@ using namespace mozilla::plugins; using namespace mozilla::plugins::parent; #ifdef MOZ_CRASHREPORTER #include "mozilla/dom/CrashReporterParent.h" 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"; static const char kAsyncInitPref[] = "dom.ipc.plugins.asyncInit"; #ifdef XP_WIN static const char kHangUITimeoutPref[] = "dom.ipc.plugins.hangUITimeoutSecs"; static const char kHangUIMinDisplayPref[] = "dom.ipc.plugins.hangUIMinDisplaySecs"; #define CHILD_TIMEOUT_PREF kHangUITimeoutPref @@ -252,16 +254,32 @@ private: static bool sIsLoadModuleOnStack; }; PRCList PluginModuleMapping::sModuleListHead = PR_INIT_STATIC_CLIST(&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) { PluginModuleMapping::NotifyLoadingModule loadingModule; nsAutoPtr<PluginModuleMapping> mapping(new PluginModuleMapping(aPluginId)); MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content); @@ -282,16 +300,18 @@ PluginModuleContentParent::LoadModule(ui if (!mapping->IsChannelOpened()) { // mapping is linked into PluginModuleMapping::sModuleListHead and is // needed later, so since this function is returning successfully we // forget it here. mapping.forget(); } + parent->mPluginId = aPluginId; + return parent; } /* static */ void PluginModuleContentParent::AssociatePluginId(uint32_t aPluginId, base::ProcessId aProcessId) { DebugOnly<PluginModuleMapping*> mapping = @@ -321,16 +341,18 @@ PluginModuleContentParent::Initialize(mo moduleMapping->SetChannelOpened(); // Request Windows message deferral behavior on our channel. This // applies to the top level and all sub plugin protocols since they // 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. moduleMapping.forget(); return parent; } /* static */ void @@ -510,16 +532,22 @@ PluginModuleParent::~PluginModuleParent( NPError err; NP_Shutdown(&err); } } PluginModuleContentParent::PluginModuleContentParent() : PluginModuleParent(false) { + Preferences::RegisterCallback(TimeoutChanged, kContentTimeoutPref, this); +} + +PluginModuleContentParent::~PluginModuleContentParent() +{ + Preferences::UnregisterCallback(TimeoutChanged, kContentTimeoutPref, this); } PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath, uint32_t aPluginId) : PluginModuleParent(true) , mSubprocess(new PluginProcessParent(aFilePath)) , mPluginId(aPluginId) , mChromeTaskFactory(this) , mHangAnnotationFlags(0) @@ -643,42 +671,51 @@ PluginModuleChromeParent::WriteExtraData #endif } #endif } } #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; SetReplyTimeoutMs(timeoutMs); } 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); } } void PluginModuleChromeParent::CleanupFromTimeout(const bool aFromHangUI) { if (mShutdown) { return; @@ -852,16 +889,33 @@ PluginModuleChromeParent::ShouldContinue // original plugin hang behaviour and kill the plugin container. FinishHangUI(); #endif // XP_WIN TerminateChildProcess(MessageLoop::current()); GetIPCChannel()->CloseWithTimeout(); 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) { #ifdef MOZ_CRASHREPORTER #ifdef XP_WIN mozilla::MutexAutoLock lock(mCrashReporterMutex); CrashReporterParent* crashReporter = mCrashReporter; if (!crashReporter) {
--- a/dom/plugins/ipc/PluginModuleParent.h +++ b/dom/plugins/ipc/PluginModuleParent.h @@ -171,16 +171,19 @@ protected: virtual bool RecvNP_InitializeResult(const NPError& aError) MOZ_OVERRIDE; static BrowserStreamParent* StreamCast(NPP instance, NPStream* s, 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; } void SetPluginFuncs(NPPluginFuncs* aFuncs); nsresult NPP_NewInternal(NPMIMEType pluginType, NPP instance, uint16_t mode, InfallibleTArray<nsCString>& names, @@ -304,23 +307,29 @@ class PluginModuleContentParent : public static PluginLibrary* LoadModule(uint32_t aPluginId); static PluginModuleContentParent* Initialize(mozilla::ipc::Transport* aTransport, base::ProcessId aOtherProcess); 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 : public PluginModuleParent , public mozilla::HangMonitor::Annotator { public: /** @@ -397,18 +406,16 @@ private: virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE; // aFilePath is UTF8, not native! explicit PluginModuleChromeParent(const char* aFilePath, uint32_t aPluginId); 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; #ifdef MOZ_ENABLE_PROFILER_SPS void InitPluginProfiling(); void ShutdownPluginProfiling(); #endif
--- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -35,16 +35,17 @@ #include "jsprf.h" #include "js/MemoryMetrics.h" #include "mozilla/dom/GeneratedAtomList.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/Element.h" #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" #include "GeckoProfiler.h" #include "nsIXULRuntime.h" #include "nsJSPrincipals.h" @@ -1107,16 +1108,17 @@ class Watchdog bool mShuttingDown; mozilla::Atomic<int32_t> mMinScriptRunTimeSeconds; }; #ifdef MOZ_NUWA_PROCESS #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" class WatchdogManager : public nsIObserver { public: NS_DECL_ISUPPORTS @@ -1129,29 +1131,31 @@ class WatchdogManager : public nsIObserv // Enable the watchdog, if appropriate. RefreshWatchdog(); // Register ourselves as an observer to get updates on the pref. 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: virtual ~WatchdogManager() { // Shutting down the watchdog requires context-switching to the watchdog // thread, which isn't great to do in a destructor. So we require // consumers to shut it down manually before releasing it. MOZ_ASSERT(!mWatchdog); 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: NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { RefreshWatchdog(); @@ -1219,17 +1223,20 @@ class WatchdogManager : public nsIObserv if (mWatchdog) { int32_t contentTime = Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CONTENT, 10); if (contentTime <= 0) contentTime = INT32_MAX; 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)); } } void StartWatchdog() { MOZ_ASSERT(!mWatchdog); mWatchdog = new Watchdog(this); mWatchdog->Init(); @@ -1336,16 +1343,20 @@ XPCJSRuntime::DefaultJSContextCallback(J MOZ_ASSERT(rt == Get()->Runtime()); return Get()->GetJSContextStack()->GetSafeJSContext(); } // static void XPCJSRuntime::ActivityCallback(void *arg, bool active) { + if (!active) { + ProcessHangMonitor::ClearHang(); + } + XPCJSRuntime* self = static_cast<XPCJSRuntime*>(arg); self->mWatchdogManager->RecordRuntimeActivity(active); } // static // // JS-CTypes creates and caches a JSContext that it uses when executing JS // callbacks. When we're notified that ctypes is about to call into some JS, @@ -1374,23 +1385,26 @@ XPCJSRuntime::InterruptCallback(JSContex return true; } // Sometimes we get called back during XPConnect initialization, before Gecko // has finished bootstrapping. Avoid crashing in nsContentUtils below. 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. if (limit == 0 || duration.ToSeconds() < limit) return true; // // This has gone on long enough! Time to take action. ;-) @@ -1418,17 +1432,19 @@ XPCJSRuntime::InterruptCallback(JSContex // Show the prompt to the user, and kill if requested. nsGlobalWindow::SlowScriptResponse response = win->ShowSlowScriptDialog(); if (response == nsGlobalWindow::KillSlowScript) return false; // 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); return true; } void XPCJSRuntime::CustomOutOfMemoryCallback()
--- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2285,16 +2285,17 @@ pref("viewmanager.do_doublebuffering", t // enable single finger gesture input (win7+ tablets) pref("gestures.enable_single_finger_input", true); pref("editor.resizing.preserve_ratio", true); 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 pref("dom.archivereader.enabled", false); // Hang monitor timeout after which we kill the browser, in seconds // (0 is disabled) // Disabled on all platforms per bug 705748 until the found issues are @@ -2331,16 +2332,19 @@ pref("plugin.persistentPermissionAlways. #ifndef DEBUG // How long a plugin is allowed to process a synchronous IPC message // before we consider it "hung". pref("dom.ipc.plugins.timeoutSecs", 45); // How long a plugin process will wait for a response from the parent // 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); #ifdef XP_WIN // How long a plugin is allowed to process a synchronous IPC message // before we display the plugin hang UI pref("dom.ipc.plugins.hangUITimeoutSecs", 11); // Minimum time that the plugin hang UI will be displayed @@ -2348,16 +2352,17 @@ pref("dom.ipc.plugins.hangUIMinDisplaySe #endif // How long a content process can take before closing its IPC channel // after shutdown is initiated. If the process exceeds the timeout, // we fear the worst and kill it. 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 pref("dom.ipc.plugins.hangUITimeoutSecs", 0); pref("dom.ipc.plugins.hangUIMinDisplaySecs", 0); #endif pref("dom.ipc.tabs.shutdownTimeoutSecs", 0); #endif
--- a/testing/marionette/client/marionette/geckoinstance.py +++ b/testing/marionette/client/marionette/geckoinstance.py @@ -23,17 +23,18 @@ class GeckoInstance(object): "browser.sessionstore.resume_from_crash": False, "browser.warnOnQuit": False, "browser.displayedE10SPrompt": 5, "browser.displayedE10SPrompt.1": 5, "browser.displayedE10SPrompt.2": 5, "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, ): self.marionette_host = host self.marionette_port = port self.bin = bin # Check if it is a Profile object or a path to profile self.profile = None
--- a/testing/profiles/prefs_general.js +++ b/testing/profiles/prefs_general.js @@ -9,16 +9,18 @@ user_pref("browser.ui.layout.tablet", 0) user_pref("dom.allow_scripts_to_close_windows", true); user_pref("dom.disable_open_during_load", false); user_pref("dom.experimental_forms", true); // on for testing user_pref("dom.forms.number", true); // on for testing 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); user_pref("signed.applets.codebase_principal_support", true); user_pref("browser.shell.checkDefaultBrowser", false); user_pref("shell.checkDefaultClient", false); user_pref("browser.warnOnQuit", false); user_pref("accessibility.typeaheadfind.autostart", false);