bug 1071880 - Notify user of addons that are slowing their browser down significantly r=mossop
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -133,16 +133,19 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource:///modules/FormValidationHandler.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
"resource://gre/modules/WebChannel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent",
"resource:///modules/ReaderParent.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonWatcher",
+ "resource://gre/modules/AddonWatcher.jsm");
+
const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
const PREF_PLUGINS_UPDATEURL = "plugins.update.url";
// Seconds of idle before trying to create a bookmarks backup.
const BOOKMARKS_BACKUP_IDLE_TIME_SEC = 8 * 60;
// Minimum interval between backups. We try to not create more than one backup
// per interval.
const BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS = 1;
@@ -559,16 +562,86 @@ BrowserGlue.prototype = {
},
_onAppDefaults: function BG__onAppDefaults() {
// apply distribution customizations (prefs)
// other customizations are applied in _finalUIStartup()
this._distributionCustomizer.applyPrefDefaults();
},
+ _notifySlowAddon: function BG_notifySlowAddon(addonId) {
+ let addonCallback = function(addon) {
+ if (!addon) {
+ Cu.reportError("couldn't look up addon: " + addonId);
+ return;
+ }
+ let win = RecentWindow.getMostRecentBrowserWindow();
+
+ if (!win) {
+ return;
+ }
+
+ let brandBundle = win.document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let message = win.gNavigatorBundle.getFormattedString("addonwatch.slow", [addon.name, brandShortName]);
+ let notificationBox = win.document.getElementById("global-notificationbox");
+ let notificationId = 'addon-slow:' + addonId;
+ let notification = notificationBox.getNotificationWithValue(notificationId);
+ if(notification) {
+ notification.label = message;
+ } else {
+ let buttons = [
+ {
+ label: win.gNavigatorBundle.getFormattedString("addonwatch.disable.label", [addon.name]),
+ accessKey: win.gNavigatorBundle.getString("addonwatch.disable.accesskey"),
+ callback: function() {
+ addon.userDisabled = true;
+ if (addon.pendingOperations != addon.PENDING_NONE) {
+ let restartMessage = win.gNavigatorBundle.getFormattedString("addonwatch.restart.message", [addon.name, brandShortName]);
+ let restartButton = [
+ {
+ label: win.gNavigatorBundle.getFormattedString("addonwatch.restart.label", [brandShortName]),
+ accessKey: win.gNavigatorBundle.getString("addonwatch.restart.accesskey"),
+ callback: function() {
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
+ }
+ }
+ ];
+ const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ notificationBox.appendNotification(restartMessage, "restart-" + addonId, "",
+ priority, restartButton);
+ }
+ }
+ },
+ {
+ label: win.gNavigatorBundle.getString("addonwatch.ignoreSession.label"),
+ accessKey: win.gNavigatorBundle.getString("addonwatch.ignoreSession.accesskey"),
+ callback: function() {
+ AddonWatcher.ignoreAddonForSession(addonId);
+ }
+ },
+ {
+ label: win.gNavigatorBundle.getString("addonwatch.ignorePerm.label"),
+ accessKey: win.gNavigatorBundle.getString("addonwatch.ignorePerm.accesskey"),
+ callback: function() {
+ AddonWatcher.ignoreAddonPermanently(addonId);
+ }
+ },
+ ];
+
+ const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ notificationBox.appendNotification(message, notificationId, "",
+ priority, buttons);
+ }
+ };
+ AddonManager.getAddonByID(addonId, addonCallback);
+ },
+
// runs on startup, before the first command line handler is invoked
// (i.e. before the first window is opened)
_finalUIStartup: function BG__finalUIStartup() {
this._sanitizer.onStartup();
// check if we're in safe mode
if (Services.appinfo.inSafeMode) {
Services.ww.openWindow(null, "chrome://browser/content/safeMode.xul",
"_blank", "chrome,centerscreen,modal,resizable=no", null);
@@ -607,16 +680,18 @@ BrowserGlue.prototype = {
LoginManagerParent.init();
ReaderParent.init();
#ifdef NIGHTLY_BUILD
Services.prefs.addObserver(POLARIS_ENABLED, this, false);
#endif
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
+
+ AddonWatcher.init(this._notifySlowAddon);
},
_checkForOldBuildUpdates: function () {
// check for update if our build is old
if (Services.prefs.getBoolPref("app.update.enabled") &&
Services.prefs.getBoolPref("app.update.checkInstallTime")) {
let buildID = Services.appinfo.appBuildID;
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -35,16 +35,27 @@ xpinstallDisabledButton.accesskey=n
# http://developer.mozilla.org/en/docs/Localization_and_Plurals
# Also see https://bugzilla.mozilla.org/show_bug.cgi?id=570012 for mockups
addonDownloading=Add-on downloading;Add-ons downloading
addonDownloadCancelled=Add-on download cancelled.;Add-on downloads cancelled.
addonDownloadRestart=Restart Download;Restart Downloads
addonDownloadRestart.accessKey=R
addonDownloadCancelTooltip=Cancel
+addonwatch.slow=%S might be making %S run slowly
+addonwatch.disable.label=Disable %S
+addonwatch.disable.accesskey=D
+addonwatch.ignoreSession.label=Ignore for now
+addonwatch.ignoreSession.accesskey=I
+addonwatch.ignorePerm.label=Ignore permanently
+addonwatch.ignorePerm.accesskey=p
+addonwatch.restart.message=To disable %S you must restart %S
+addonwatch.restart.label=Restart %s
+addonwatch.restart.accesskey=R
+
# LOCALIZATION NOTE (addonsInstalled, addonsInstalledNeedsRestart):
# Semicolon-separated list of plural forms. See:
# http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 first add-on's name, #2 number of add-ons, #3 application name
addonsInstalled=#1 has been installed successfully.;#2 add-ons have been installed successfully.
addonsInstalledNeedsRestart=#1 will be installed after you restart #3.;#2 add-ons will be installed after you restart #3.
addonInstallRestartButton=Restart Now
addonInstallRestartButton.accesskey=R
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4460,16 +4460,22 @@ pref("dom.mozSettings.SettingsManager.ve
pref("dom.mozSettings.SettingsRequestManager.verbose.enabled", false);
pref("dom.mozSettings.SettingsService.verbose.enabled", false);
// Controlling whether we want to allow forcing some Settings
// IndexedDB transactions to be opened as readonly or keep everything as
// readwrite.
pref("dom.mozSettings.allowForceReadOnly", false);
+// The interval at which to check for slow running addons
+pref("browser.addon-watch.interval", 15000);
+pref("browser.addon-watch.ignore", "[\"mochikit@mozilla.org\"]");
+// the percentage of time addons are allowed to use without being labeled slow
+pref("browser.addon-watch.percentage-limit", 1);
+
// RequestSync API is disabled by default.
pref("dom.requestSync.enabled", false);
// Search service settings
pref("browser.search.log", false);
pref("browser.search.update", true);
pref("browser.search.update.log", false);
pref("browser.search.update.interval", 21600);
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/AddonWatcher.jsm
@@ -0,0 +1,83 @@
+// -*- 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";
+
+this.EXPORTED_SYMBOLS = ["AddonWatcher"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+ "resource://gre/modules/Preferences.jsm");
+
+let AddonWatcher = {
+ _lastAddonTime: {},
+ _timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
+ _callback: null,
+ _interval: 1500,
+ _ignoreList: null,
+ init: function(callback) {
+ if (!callback) {
+ return;
+ }
+
+ if (this._callback) {
+ return;
+ }
+
+ this._callback = callback;
+ try {
+ this._ignoreList = new Set(JSON.parse(Preferences.get("browser.addon-watch.ignore", null)));
+ } catch (ex) {
+ // probably some malformed JSON, ignore and carry on
+ this._ignoreList = new Set();
+ }
+ this._interval = Preferences.get("browser.addon-watch.interval", 15000);
+ this._timer.initWithCallback(this._checkAddons.bind(this), this._interval, Ci.nsITimer.TYPE_REPEATING_SLACK);
+ },
+ _checkAddons: function() {
+ let compartmentInfo = Cc["@mozilla.org/compartment-info;1"]
+ .getService(Ci.nsICompartmentInfo);
+ let compartments = compartmentInfo.getCompartments();
+ let count = compartments.length;
+ let addons = {};
+ for (let i = 0; i < count; i++) {
+ let compartment = compartments.queryElementAt(i, Ci.nsICompartment);
+ if (compartment.addonId) {
+ if (addons[compartment.addonId]) {
+ addons[compartment.addonId] += compartment.time;
+ } else {
+ addons[compartment.addonId] = compartment.time;
+ }
+ }
+ }
+ let limit = this._interval * Preferences.get("browser.addon-watch.percentage-limit", 75) * 10;
+ for (let addonId in addons) {
+ if (!this._ignoreList.has(addonId)) {
+ if (this._lastAddonTime[addonId] && (addons[addonId] - this._lastAddonTime[addonId]) > limit) {
+ this._callback(addonId);
+ }
+ this._lastAddonTime[addonId] = addons[addonId];
+ }
+ }
+ },
+ ignoreAddonForSession: function(addonid) {
+ this._ignoreList.add(addonid);
+ },
+ ignoreAddonPermanently: function(addonid) {
+ this._ignoreList.add(addonid);
+ try {
+ let ignoreList = JSON.parse(Preferences.get("browser.addon-watch.ignore", "[]"))
+ if (!ignoreList.includes(addonid)) {
+ ignoreList.push(addonid);
+ Preferences.set("browser.addon-watch.ignore", JSON.stringify(ignoreList));
+ }
+ } catch (ex) {
+ Preferences.set("browser.addon-watch.ignore", JSON.stringify([addonid]));
+ }
+ }
+};
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -7,16 +7,17 @@
XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini']
MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
SPHINX_TREES['toolkit_modules'] = 'docs'
EXTRA_JS_MODULES += [
+ 'AddonWatcher.jsm',
'Battery.jsm',
'BinarySearch.jsm',
'BrowserUtils.jsm',
'CharsetMenu.jsm',
'debug.js',
'DeferredTask.jsm',
'Deprecated.jsm',
'Dict.jsm',