Bug 1356462 Show a notification when non-MPC extensions are disabled
MozReview-Commit-ID: 8KUhRe91AFt
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -753,16 +753,37 @@ BrowserGlue.prototype = {
},
];
let nb = win.document.getElementById("high-priority-global-notificationbox");
nb.appendNotification(message, "unsigned-addons-disabled", "",
nb.PRIORITY_WARNING_MEDIUM, buttons);
},
+ _notifyDisabledNonMpc() {
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ if (!win)
+ return;
+
+ let message = win.gNavigatorBundle.getString("nonMpcDisabled.message");
+ let buttons = [
+ {
+ label: win.gNavigatorBundle.getString("nonMpcDisabled.manage.label"),
+ accessKey: win.gNavigatorBundle.getString("nonMpcDisabled.manage.accessKey"),
+ callback() {
+ win.BrowserOpenAddonsMgr("addons://list/extension");
+ }
+ },
+ ];
+
+ let nb = win.document.getElementById("high-priority-global-notificationbox");
+ nb.appendNotification(message, "non-mpc-addons-disabled", "",
+ nb.PRIORITY_WARNING_MEDIUM, buttons);
+ },
+
_firstWindowTelemetry(aWindow) {
let SCALING_PROBE_NAME = "";
switch (AppConstants.platform) {
case "win":
SCALING_PROBE_NAME = "DISPLAY_SCALING_MSWIN";
break;
case "macosx":
SCALING_PROBE_NAME = "DISPLAY_SCALING_OSX";
@@ -959,16 +980,20 @@ BrowserGlue.prototype = {
if (addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
this._notifyUnsignedAddonsDisabled();
break;
}
}
});
}
+ if (AddonManager.nonMpcDisabled) {
+ this._notifyDisabledNonMpc();
+ }
+
// Perform default browser checking.
if (ShellService) {
let shouldCheck = AppConstants.DEBUG ? false :
ShellService.shouldCheckDefaultBrowser;
const skipDefaultBrowserCheck =
Services.prefs.getBoolPref("browser.shell.skipDefaultBrowserCheckOnFirstRun") &&
Services.prefs.getBoolPref("browser.shell.skipDefaultBrowserCheck");
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -209,16 +209,20 @@ addonInstallErrorIncompatible=%3$S could
# LOCALIZATION NOTE (addonInstallErrorBlocklisted): %S is add-on name
addonInstallErrorBlocklisted=%S could not be installed because it has a high risk of causing stability or security problems.
unsignedAddonsDisabled.message=One or more installed add-ons cannot be verified and have been disabled.
unsignedAddonsDisabled.learnMore.label=Learn More
unsignedAddonsDisabled.learnMore.accesskey=L
+nonMpcDisabled.message=Due to performance testing, we have disabled some of your add-ons. They can be re-enabled in the Add-ons Manager.
+nonMpcDisabled.manage.label=Manage Add-Ons
+nonMpcDisabled.manage.accessKey=M
+
# LOCALIZATION NOTE (compactLightTheme.name): This is displayed in about:addons -> Appearance
compactLightTheme.name=Compact Light
compactLightTheme.description=A compact theme with a light color scheme.
# LOCALIZATION NOTE (compactDarkTheme.name): This is displayed in about:addons -> Appearance
compactDarkTheme.name=Compact Dark
compactDarkTheme.description=A compact theme with a dark color scheme.
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -620,16 +620,17 @@ var gUpdateEnabled = true;
var gAutoUpdateDefault = true;
var gHotfixID = "";
var gWebExtensionsMinPlatformVersion = "";
var gShutdownBarrier = null;
var gRepoShutdownState = "";
var gShutdownInProgress = false;
var gPluginPageListener = null;
var gBrowserUpdated = null;
+var gNonMpcDisabled = false;
/**
* This is the real manager, kept here rather than in AddonManager to keep its
* contents hidden from API users.
*/
var AddonManagerInternal = {
managerListeners: [],
installListeners: [],
@@ -3243,16 +3244,20 @@ this.AddonManagerPrivate = {
if (!extensionId || typeof extensionId != "string")
throw Components.Exception("extensionId must be a string",
Cr.NS_ERROR_INVALID_ARG);
return AddonManagerInternal._getProviderByName("XPIProvider")
.isTemporaryInstallID(extensionId);
},
+
+ set nonMpcDisabled(val) {
+ gNonMpcDisabled = val;
+ },
};
/**
* This is the public API that UI and developers should be calling. All methods
* just forward to AddonManagerInternal.
*/
this.AddonManager = {
// Constants for the AddonInstall.state property
@@ -3741,16 +3746,20 @@ this.AddonManager = {
getPreferredIconURL(aAddon, aSize, aWindow = undefined) {
return AddonManagerInternal.getPreferredIconURL(aAddon, aSize, aWindow);
},
get webAPI() {
return AddonManagerInternal.webAPI;
},
+ get nonMpcDisabled() {
+ return gNonMpcDisabled;
+ },
+
get shutdown() {
return gShutdownBarrier.client;
},
};
this.AddonManager.init();
// load the timestamps module into AddonManagerInternal
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -30,16 +30,19 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "Blocklist",
"@mozilla.org/extensions/blocklist;1",
Ci.nsIBlocklistService);
+XPCOMUtils.defineLazyPreferenceGetter(this, "ALLOW_NON_MPC",
+ "extensions.allow-non-mpc-extensions");
+
Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.xpi-utils";
const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile");
// Create a new logger for use by the Addons XPI Provider Utils
// (Requires AddonManager.jsm)
var logger = Log.repository.getLogger(LOGGER_ID);
@@ -1928,16 +1931,18 @@ this.XPIDatabaseReconcile = {
oldtime: oldAddon.updateDate
});
} else {
XPIProvider.setTelemetry(oldAddon.id, "modifiedFile",
XPIProvider._mostRecentlyModifiedFile[id]);
}
}
+ let wasDisabled = oldAddon.appDisabled;
+
// The add-on has changed if the modification time has changed, if
// we have an updated manifest for it, or if the schema version has
// changed.
//
// Also reload the metadata for add-ons in the application directory
// when the application version has changed.
let newAddon = loadedManifest(installLocation, id);
if (newAddon || oldAddon.updateDate != xpiState.mtime ||
@@ -1953,16 +1958,28 @@ this.XPIDatabaseReconcile = {
newAddon = this.updateCompatibility(installLocation, oldAddon, xpiState,
aOldAppVersion, aOldPlatformVersion,
aSchemaChange);
} else {
// No change
newAddon = oldAddon;
}
+ // If an extension has just become appDisabled and it appears to
+ // be due to the ALLOW_NON_MPC pref, show a notification. If the
+ // extension is also disabled for some other reason(s), don't
+ // bother with the notification since flipping the pref will leave
+ // the extension disabled.
+ if (!wasDisabled && newAddon.appDisabled &&
+ !ALLOW_NON_MPC && !newAddon.multiprocessCompatible &&
+ (newAddon.blocklistState != Ci.nsIBlocklistService.STATE_BLOCKED) &&
+ newAddon.isPlatformCompatible && newAddon.isCompatible) {
+ AddonManagerPrivate.nonMpcDisabled = true;
+ }
+
if (newAddon)
locationAddonMap.set(newAddon.id, newAddon);
} else {
// The add-on is in the DB, but not in xpiState (and thus not on disk).
this.removeMetadata(oldAddon);
}
}
}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_multiprocessCompatible.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_multiprocessCompatible.js
@@ -1,14 +1,18 @@
Components.utils.import("resource://testing-common/httpd.js");
+Components.utils.import("resource://gre/modules/osfile.jsm");
+
var gServer;
const profileDir = gProfD.clone();
profileDir.append("extensions");
+const NON_MPC_PREF = "extensions.allow-non-mpc-extensions";
+
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
function build_test(multiprocessCompatible, bootstrap, updateMultiprocessCompatible) {
return function* () {
dump("Running test" +
" multiprocessCompatible: " + multiprocessCompatible +
" bootstrap: " + bootstrap +
" updateMultiprocessCompatible: " + updateMultiprocessCompatible +
@@ -98,17 +102,16 @@ for (let bootstrap of [false, true]) {
for (let multiprocessCompatible of [undefined, false, true]) {
for (let updateMultiprocessCompatible of [undefined, false, true]) {
add_task(build_test(multiprocessCompatible, bootstrap, updateMultiprocessCompatible));
}
}
}
add_task(async function test_disable() {
- const PREF = "extensions.allow-non-mpc-extensions";
const ID_MPC = "mpc@tests.mozilla.org";
const ID_NON_MPC = "non-mpc@tests.mozilla.org";
let addonData = {
name: "Test Add-on",
version: "1.0",
bootstrap: true,
targetApplications: [{
@@ -124,17 +127,17 @@ add_task(async function test_disable() {
}, addonData));
let xpi2 = createTempXPIFile(Object.assign({
id: ID_NON_MPC,
multiprocessCompatible: false,
}, addonData));
async function testOnce(initialAllow) {
if (initialAllow !== undefined) {
- Services.prefs.setBoolPref(PREF, initialAllow);
+ Services.prefs.setBoolPref(NON_MPC_PREF, initialAllow);
}
let install1 = await AddonManager.getInstallForFile(xpi1);
let install2 = await AddonManager.getInstallForFile(xpi2);
await promiseCompleteAllInstalls([install1, install2]);
let [addon1, addon2] = await AddonManager.getAddonsByIDs([ID_MPC, ID_NON_MPC]);
do_check_neq(addon1, null);
@@ -142,39 +145,176 @@ add_task(async function test_disable() {
do_check_eq(addon1.appDisabled, false);
do_check_neq(addon2, null);
do_check_eq(addon2.multiprocessCompatible, false);
do_check_eq(addon2.appDisabled, initialAllow === false);
// Flip the allow-non-mpc preference
let newValue = (initialAllow === true) ? false : true;
- Services.prefs.setBoolPref(PREF, newValue);
+ Services.prefs.setBoolPref(NON_MPC_PREF, newValue);
// the mpc extension should never become appDisabled
do_check_eq(addon1.appDisabled, false);
// The non-mpc extension should become disabled if we don't allow non-mpc
do_check_eq(addon2.appDisabled, !newValue);
// Flip the pref back and check appDisabled
- Services.prefs.setBoolPref(PREF, !newValue);
+ Services.prefs.setBoolPref(NON_MPC_PREF, !newValue);
do_check_eq(addon1.appDisabled, false);
do_check_eq(addon2.appDisabled, newValue);
addon1.uninstall();
addon2.uninstall();
}
await testOnce(undefined);
await testOnce(true);
await testOnce(false);
- Services.prefs.clearUserPref(PREF);
+ Services.prefs.clearUserPref(NON_MPC_PREF);
+});
+
+// Test that the nonMpcDisabled flag gets set properly at startup
+// when the allow-non-mpc-extensions pref is flipped.
+add_task(async function test_restart() {
+ const ID = "non-mpc@tests.mozilla.org";
+
+ let xpifile = createTempXPIFile({
+ id: ID,
+ name: "Test Add-on",
+ version: "1.0",
+ bootstrap: true,
+ multiprocessCompatible: false,
+ targetApplications: [{
+ id: "xpcshell@tests.mozilla.org",
+ minVersion: "1",
+ maxVersion: "2"
+ }]
+ });
+
+ Services.prefs.setBoolPref(NON_MPC_PREF, true);
+ let install = await AddonManager.getInstallForFile(xpifile);
+ await promiseCompleteAllInstalls([install]);
+
+ let addon = await AddonManager.getAddonByID(ID);
+ do_check_neq(addon, null);
+ do_check_eq(addon.multiprocessCompatible, false);
+ do_check_eq(addon.appDisabled, false);
+
+ // Simulate a new app version in which the allow-non-mpc-extensions
+ // pref is flipped.
+ await promiseShutdownManager();
+ Services.prefs.setBoolPref(NON_MPC_PREF, false);
+ gAppInfo.version = "1.5";
+ await promiseStartupManager();
+
+ addon = await AddonManager.getAddonByID(ID);
+ do_check_neq(addon, null);
+ do_check_eq(addon.appDisabled, true);
+
+ // The flag we use for startup notification should be true
+ do_check_eq(AddonManager.nonMpcDisabled, true);
+
+ addon.uninstall();
+
+ Services.prefs.clearUserPref(NON_MPC_PREF);
+ AddonManagerPrivate.nonMpcDisabled = false;
+});
+
+// Test that the nonMpcDisabled flag is not set if there are non-mpc
+// extensions that are also disabled for some other reason.
+add_task(async function test_restart2() {
+ const ID1 = "blocked@tests.mozilla.org";
+ let xpi1 = createTempXPIFile({
+ id: ID1,
+ name: "Blocked Add-on",
+ version: "1.0",
+ bootstrap: true,
+ multiprocessCompatible: false,
+ targetApplications: [{
+ id: "xpcshell@tests.mozilla.org",
+ minVersion: "1",
+ maxVersion: "2"
+ }]
+ });
+
+ const ID2 = "incompatible@tests.mozilla.org";
+ let xpi2 = createTempXPIFile({
+ id: ID2,
+ name: "Incompatible Add-on",
+ version: "1.0",
+ bootstrap: true,
+ multiprocessCompatible: false,
+ targetApplications: [{
+ id: "xpcshell@tests.mozilla.org",
+ minVersion: "1",
+ maxVersion: "1.5"
+ }]
+ });
+
+ const BLOCKLIST = `<?xml version="1.0"?>
+ <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1396046918000">
+ <emItems>
+ <emItem blockID="i454" id="${ID1}">
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ </emItems>
+ </blocklist>`;
+
+
+ Services.prefs.setBoolPref(NON_MPC_PREF, true);
+ let install1 = await AddonManager.getInstallForFile(xpi1);
+ let install2 = await AddonManager.getInstallForFile(xpi2);
+ await promiseCompleteAllInstalls([install1, install2]);
+
+ let [addon1, addon2] = await AddonManager.getAddonsByIDs([ID1, ID2]);
+ do_check_neq(addon1, null);
+ do_check_eq(addon1.multiprocessCompatible, false);
+ do_check_eq(addon1.appDisabled, false);
+ do_check_neq(addon2, null);
+ do_check_eq(addon2.multiprocessCompatible, false);
+ do_check_eq(addon2.appDisabled, false);
+
+ await promiseShutdownManager();
+
+ Services.prefs.setBoolPref(NON_MPC_PREF, false);
+ gAppInfo.version = "2";
+
+ // Simulate including a new blocklist with the new version by
+ // flipping the pref below which causes the blocklist to be re-read.
+ let blocklistPath = OS.Path.join(OS.Constants.Path.profileDir, "blocklist.xml");
+ await OS.File.writeAtomic(blocklistPath, BLOCKLIST);
+ let BLOCKLIST_PREF = "extensions.blocklist.enabled";
+ Services.prefs.setBoolPref(BLOCKLIST_PREF, false);
+ Services.prefs.setBoolPref(BLOCKLIST_PREF, true);
+
+ await promiseStartupManager();
+
+ // When we restart, one of the test addons should be blocklisted, and
+ // one is incompatible. Both are MPC=false but that should not trigger
+ // the startup notification since flipping allow-non-mpc-extensions
+ // won't re-enable either extension.
+ const {STATE_BLOCKED} = Components.interfaces.nsIBlocklistService;
+ [addon1, addon2] = await AddonManager.getAddonsByIDs([ID1, ID2]);
+ do_check_neq(addon1, null);
+ do_check_eq(addon1.appDisabled, true);
+ do_check_eq(addon1.blocklistState, STATE_BLOCKED);
+ do_check_neq(addon2, null);
+ do_check_eq(addon2.appDisabled, true);
+ do_check_eq(addon2.isCompatible, false);
+
+ do_check_eq(AddonManager.nonMpcDisabled, false);
+
+ addon1.uninstall();
+ addon2.uninstall();
+
+ Services.prefs.clearUserPref(NON_MPC_PREF);
});
function run_test() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
startupManager();
// Create and configure the HTTP server.
gServer = new HttpServer();