Bug 1441271 Show permissions notifications for distribution addons
As described in the bug, this is intended as a temporary solution to
enable some experiments. If this becomes a real feature, UX will
put some thought into a better startup experience.
MozReview-Commit-ID: 4DGMHj29M3e
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -44,16 +44,17 @@ var ExtensionsUI = {
Services.obs.addObserver(this, "webextension-update-permissions");
Services.obs.addObserver(this, "webextension-install-notify");
Services.obs.addObserver(this, "webextension-optional-permission-prompt");
Services.obs.addObserver(this, "webextension-defaultsearch-prompt");
await Services.wm.getMostRecentWindow("navigator:browser").delayedStartupPromise;
this._checkForSideloaded();
+ this._checkNewDistroAddons();
},
async _checkForSideloaded() {
let sideloaded = await AddonManagerPrivate.getNewSideloads();
if (!sideloaded.length) {
// No new side-loads. We're done.
return;
@@ -92,16 +93,70 @@ var ExtensionsUI = {
// be removed. See bug 1331521.
let win = RecentWindow.getMostRecentBrowserWindow();
for (let addon of sideloaded) {
win.openUILinkIn(`about:newaddon?id=${addon.id}`, "tab");
}
}
},
+ async _checkNewDistroAddons() {
+ let newDistroAddons = AddonManagerPrivate.getNewDistroAddons();
+ if (!newDistroAddons) {
+ return;
+ }
+
+ for (let id of newDistroAddons) {
+ let addon = await AddonManager.getAddonByID(id);
+
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ if (!win) {
+ return;
+ }
+
+ let {gBrowser} = win;
+ let browser = gBrowser.selectedBrowser;
+
+ // The common case here is that we enter this code right after startup
+ // in a brand new profile so we haven't yet loaded a page. That state is
+ // surprisingly difficult to detect but wait until we've actually loaded
+ // a page.
+ if (browser.currentURI.spec == "about:blank" ||
+ browser.webProgress.isLoadingDocument) {
+ await new Promise(resolve => {
+ let listener = {
+ onLocationChange(browser_, webProgress, ...ignored) {
+ if (webProgress.isTopLevel && browser_ == browser) {
+ gBrowser.removeTabsProgressListener(listener);
+ resolve();
+ }
+ },
+ };
+ gBrowser.addTabsProgressListener(listener);
+ });
+ }
+
+ // If we're at about:newtab and the url bar gets focus, that will
+ // prevent a doorhanger from displaying.
+ // Our elegant solution is to ... take focus away from the url bar.
+ win.gURLBar.blur();
+
+ let strings = this._buildStrings({
+ addon,
+ permissions: addon.userPermissions,
+ });
+ let accepted = await this.showPermissionsPrompt(browser, strings,
+ addon.iconURL);
+ if (accepted) {
+ addon.userDisabled = false;
+ }
+ }
+ },
+
+
_updateNotifications() {
if (this.sideloaded.size + this.updates.size == 0) {
AppMenuNotifications.removeNotification("addon-alert");
} else {
AppMenuNotifications.showBadgeOnlyNotification("addon-alert");
}
this.emit("change");
},
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -2978,16 +2978,27 @@ var AddonManagerPrivate = {
*
* @returns {Promise<Array<Addon>>}
*/
getNewSideloads() {
return AddonManagerInternal._getProviderByName("XPIProvider")
.getNewSideloads();
},
+ /**
+ * Gets a set of (ids of) distribution addons which were installed into the
+ * current profile at browser startup, or null if none were installed.
+ *
+ * @return {Set<String> | null}
+ */
+ getNewDistroAddons() {
+ return AddonManagerInternal._getProviderByName("XPIProvider")
+ .getNewDistroAddons();
+ },
+
get browserUpdated() {
return gBrowserUpdated;
},
registerProvider(aProvider, aTypes) {
AddonManagerInternal.registerProvider(aProvider, aTypes);
},
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -79,16 +79,17 @@ const PREF_XPI_DIRECT_WHITELISTED =
const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest";
// xpinstall.signatures.required only supported in dev builds
const PREF_XPI_SIGNATURES_REQUIRED = "xpinstall.signatures.required";
const PREF_XPI_SIGNATURES_DEV_ROOT = "xpinstall.signatures.dev-root";
const PREF_XPI_PERMISSIONS_BRANCH = "xpinstall.";
const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin";
const PREF_INSTALL_DISTRO_ADDONS = "extensions.installDistroAddons";
const PREF_BRANCH_INSTALLED_ADDON = "extensions.installedDistroAddon.";
+const PREF_DISTRO_ADDONS_PERMS = "extensions.distroAddons.promptForPermissions";
const PREF_INTERPOSITION_ENABLED = "extensions.interposition.enabled";
const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet";
const PREF_SYSTEM_ADDON_UPDATE_URL = "extensions.systemAddon.update.url";
const PREF_ALLOW_LEGACY = "extensions.legacy.enabled";
const PREF_ALLOW_NON_MPC = "extensions.allow-non-mpc-extensions";
const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion";
const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
@@ -1809,16 +1810,18 @@ var XPIProvider = {
minCompatiblePlatformVersion: null,
// A Map of active addons to their bootstrapScope by ID
activeAddons: new Map(),
// True if the platform could have activated extensions
extensionsActive: false,
// True if all of the add-ons found during startup were installed in the
// application install location
allAppGlobal: true,
+ // New distribution addons awaiting permissions approval
+ newDistroAddons: null,
// Keep track of startup phases for telemetry
runPhase: XPI_STARTING,
// Per-addon telemetry information
_telemetryDetails: {},
// A Map from an add-on install to its ID
_addonFileMap: new Map(),
// Have we started shutting down bootstrap add-ons?
_closing: false,
@@ -3100,16 +3103,24 @@ var XPIProvider = {
}
} else if (Services.prefs.getBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, false)) {
continue;
}
// Install the add-on
try {
addon._sourceBundle = profileLocation.installAddon({ id, source: entry, action: "copy" });
+ if (Services.prefs.getBoolPref(PREF_DISTRO_ADDONS_PERMS, false)) {
+ addon.userDisabled = true;
+ if (!this.newDistroAddons) {
+ this.newDistroAddons = new Set();
+ }
+ this.newDistroAddons.add(id);
+ }
+
XPIStates.addAddon(addon);
logger.debug("Installed distribution add-on " + id);
Services.prefs.setBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, true);
// aManifests may contain a copy of a newly installed add-on's manifest
// and we'll have overwritten that so instead cache our install manifest
// which will later be put into the database in processFileChanges
@@ -3122,16 +3133,22 @@ var XPIProvider = {
}
}
entries.close();
return changed;
},
+ getNewDistroAddons() {
+ let addons = this.newDistroAddons;
+ this.newDistroAddons = null;
+ return addons;
+ },
+
/**
* Imports the xpinstall permissions from preferences into the permissions
* manager for the user to change later.
*/
importPermissions() {
PermissionsUtils.importFromPrefs(PREF_XPI_PERMISSIONS_BRANCH,
XPI_PERMISSION);
},
--- a/toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js
@@ -13,17 +13,17 @@ const IGNORE = ["getPreferredIconURL", "
"mapURIToAddonID", "shutdown", "init",
"stateToString", "errorToString", "getUpgradeListener",
"addUpgradeListener", "removeUpgradeListener"];
const IGNORE_PRIVATE = ["AddonAuthor", "AddonCompatibilityOverride",
"AddonScreenshot", "AddonType", "startup", "shutdown",
"addonIsActive", "registerProvider", "unregisterProvider",
"addStartupChange", "removeStartupChange",
- "getNewSideloads",
+ "getNewSideloads", "getNewDistroAddons",
"recordTimestamp", "recordSimpleMeasure",
"recordException", "getSimpleMeasures", "simpleTimer",
"setTelemetryDetails", "getTelemetryDetails",
"callNoUpdateListeners", "backgroundUpdateTimerHandler",
"hasUpgradeListener", "getUpgradeListener",
"isDBLoaded", "BOOTSTRAP_REASONS"];
async function test_functions() {