# HG changeset patch # User Andrew Swan # Date 1519871796 28800 # Node ID 30d415ab237851e61a2bc9d939e7ba81144911d5 # Parent bc6165b4196a6869b3230b77178bd30f5d540012 Bug 1441271 Show permissions notifications for distribution addons r=kmag 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 diff --git a/browser/modules/ExtensionsUI.jsm b/browser/modules/ExtensionsUI.jsm --- 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"); }, diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -2975,16 +2975,27 @@ var AddonManagerPrivate = { * * @returns {Promise>} */ 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 | null} + */ + getNewDistroAddons() { + return AddonManagerInternal._getProviderByName("XPIProvider") + .getNewDistroAddons(); + }, + get browserUpdated() { return gBrowserUpdated; }, registerProvider(aProvider, aTypes) { AddonManagerInternal.registerProvider(aProvider, aTypes); }, diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -75,16 +75,17 @@ const PREF_XPI_FILE_WHITELISTED = // 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_CHECK = "extensions.distroAddons.check"; +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"; @@ -1792,16 +1793,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, @@ -2981,16 +2984,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 @@ -3003,16 +3014,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); }, diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js b/toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js --- 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() {