From e9fe1f07de37efb71091c9b7cef2466cb418fd26 Mon Sep 17 00:00:00 2001 From: Cosmin Sabou Date: Sat, 19 May 2018 23:09:19 +0300 Subject: [PATCH 1/7] Bug 1217701 - Disable /html/browsers/history/the-history-interface/history_go_zero.html on Linux debug for frequent failures. r=nbeleuzu,cbrindusan --HG-- extra : rebase_source : cc9e58c469c04eee7a371021fe228349fd48203a --- .../history/the-history-interface/history_go_zero.html.ini | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 testing/web-platform/meta/html/browsers/history/the-history-interface/history_go_zero.html.ini diff --git a/testing/web-platform/meta/html/browsers/history/the-history-interface/history_go_zero.html.ini b/testing/web-platform/meta/html/browsers/history/the-history-interface/history_go_zero.html.ini new file mode 100644 index 000000000000..94f80e5afc7c --- /dev/null +++ b/testing/web-platform/meta/html/browsers/history/the-history-interface/history_go_zero.html.ini @@ -0,0 +1,3 @@ +[history_go_zero.html] + disabled: + if debug and (os == "linux"): https://bugzilla.mozilla.org/show_bug.cgi?id=1217701 From a21fc0dffbc6c6a08dd8f2b3ce92b5be2d52d685 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sun, 13 May 2018 13:24:09 -0700 Subject: [PATCH 2/7] Bug 1461216: Remove minCompatible*Version preferences. r=aswan MozReview-Commit-ID: 4W9MV20rDt9 --HG-- extra : rebase_source : 58bd193687de1db2c0f03d0233cf574efe7609d2 --- browser/app/profile/firefox.js | 3 -- modules/libpref/init/all.js | 1 - .../extensions/internal/XPIDatabase.jsm | 11 ---- .../extensions/internal/XPIProvider.jsm | 24 --------- .../extensions/test/xpcshell/head_addons.js | 6 --- .../test/xpcshell/test_strictcompatibility.js | 52 ++++++++++--------- 6 files changed, 28 insertions(+), 69 deletions(-) diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index fe0e50f72a83..fbe6a8d498af 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -30,9 +30,6 @@ pref("extensions.logging.enabled", false); // Disables strict compatibility, making addons compatible-by-default. pref("extensions.strictCompatibility", false); -// Specifies a minimum maxVersion an addon needs to say it's compatible with -// for it to be compatible by default. -pref("extensions.minCompatibleAppVersion", "4.0"); // Temporary preference to forcibly make themes more safe with Australis even if // extensions.checkCompatibility=false has been set. pref("extensions.checkCompatibility.temporaryThemeOverride_minAppVersion", "29.0a1"); diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index c089d1ebfc48..ea9b8c88ff6f 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4899,7 +4899,6 @@ pref("xpinstall.whitelist.required", true); // Only Firefox requires add-on signatures pref("xpinstall.signatures.required", false); pref("extensions.langpacks.signatures.required", false); -pref("extensions.minCompatiblePlatformVersion", "2.0"); pref("extensions.webExtensionsMinPlatformVersion", "42.0a1"); pref("extensions.legacy.enabled", true); diff --git a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm index 7b970a1e69e4..dfae3f8070af 100644 --- a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm +++ b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm @@ -467,17 +467,6 @@ class AddonInternal { } } - // Extremely old extensions should not be compatible by default. - let minCompatVersion; - if (app.id == Services.appinfo.ID) - minCompatVersion = XPIProvider.minCompatibleAppVersion; - else if (app.id == TOOLKIT_ID) - minCompatVersion = XPIProvider.minCompatiblePlatformVersion; - - if (minCompatVersion && - Services.vc.compare(minCompatVersion, maxVersion) > 0) - return false; - return Services.vc.compare(version, minVersion) >= 0; } diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index d09ea8e761f2..4db729ced5f2 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -69,9 +69,6 @@ const PREF_BRANCH_INSTALLED_ADDON = "extensions.installedDistroAddon."; const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet"; const PREF_ALLOW_LEGACY = "extensions.legacy.enabled"; -const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion"; -const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion"; - const PREF_EM_LAST_APP_BUILD_ID = "extensions.lastAppBuildId"; // Specify a list of valid built-in add-ons to load. @@ -1645,10 +1642,6 @@ var XPIProvider = { installLocations: null, // A dictionary of known install locations by name installLocationsByName: null, - // The value of the minCompatibleAppVersion preference - minCompatibleAppVersion: null, - // The value of the minCompatiblePlatformVersion preference - minCompatiblePlatformVersion: null, // A Map of active addons to their bootstrapScope by ID activeAddons: new Map(), // True if the platform could have activated extensions @@ -1891,13 +1884,6 @@ var XPIProvider = { this.setupInstallLocations(aAppChanged); - this.minCompatibleAppVersion = Services.prefs.getStringPref(PREF_EM_MIN_COMPAT_APP_VERSION, - null); - this.minCompatiblePlatformVersion = Services.prefs.getStringPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, - null); - - Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this); - Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this); if (!AppConstants.MOZ_REQUIRE_SIGNING || Cu.isInAutomation) Services.prefs.addObserver(PREF_XPI_SIGNATURES_REQUIRED, this); Services.prefs.addObserver(PREF_LANGPACK_SIGNATURES, this); @@ -2685,16 +2671,6 @@ var XPIProvider = { if (aTopic == "nsPref:changed") { switch (aData) { - case PREF_EM_MIN_COMPAT_APP_VERSION: - this.minCompatibleAppVersion = Services.prefs.getStringPref(PREF_EM_MIN_COMPAT_APP_VERSION, - null); - this.updateAddonAppDisabledStates(); - break; - case PREF_EM_MIN_COMPAT_PLATFORM_VERSION: - this.minCompatiblePlatformVersion = Services.prefs.getStringPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, - null); - this.updateAddonAppDisabledStates(); - break; case PREF_XPI_SIGNATURES_REQUIRED: case PREF_LANGPACK_SIGNATURES: case PREF_ALLOW_LEGACY: diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index 3629905715f8..0b02e2e34864 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -13,8 +13,6 @@ Cu.importGlobalProperties(["TextEncoder"]); const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility"; -const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion"; -const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion"; const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url"; const PREF_COMPAT_OVERRIDES = "extensions.getAddons.compatOverides.url"; const PREF_XPI_SIGNATURES_REQUIRED = "xpinstall.signatures.required"; @@ -1127,10 +1125,6 @@ function promiseInstallWebExtension(aData) { // By default use strict compatibility Services.prefs.setBoolPref("extensions.strictCompatibility", true); -// By default, set min compatible versions to 0 -Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_APP_VERSION, "0"); -Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, "0"); - // Ensure signature checks are enabled by default Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_strictcompatibility.js b/toolkit/mozapps/extensions/test/xpcshell/test_strictcompatibility.js index 667f1945393e..7f0edcfdf030 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_strictcompatibility.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_strictcompatibility.js @@ -25,7 +25,10 @@ const ADDONS = { expected: { strictCompatibility: false, }, - compatible: [true, true, true, true], + compatible: { + nonStrict: true, + strict: true, + }, }, // Incompatible in strict compatibility mode @@ -44,7 +47,10 @@ const ADDONS = { expected: { strictCompatibility: false, }, - compatible: [false, true, false, true], + compatible: { + nonStrict: true, + strict: false, + }, }, // Opt-in to strict compatibility - always incompatible @@ -64,7 +70,10 @@ const ADDONS = { expected: { strictCompatibility: true, }, - compatible: [false, false, false, false], + compatible: { + nonStrict: false, + strict: false, + }, }, // Addon from the future - would be marked as compatibile-by-default, @@ -84,7 +93,10 @@ const ADDONS = { expected: { strictCompatibility: false, }, - compatible: [false, false, false, false], + compatible: { + nonStrict: false, + strict: false, + }, }, // Extremely old addon - maxVersion is less than the minimum compat version @@ -104,7 +116,10 @@ const ADDONS = { expected: { strictCompatibility: false, }, - compatible: [false, true, false, false], + compatible: { + nonStrict: true, + strict: false, + }, }, // Dictionary - incompatible in strict compatibility mode @@ -123,7 +138,10 @@ const ADDONS = { expected: { strictCompatibility: false, }, - compatible: [false, true, false, true], + compatible: { + nonStrict: true, + strict: false, + }, }, }; @@ -154,37 +172,23 @@ add_task(async function setup() { await promiseWriteInstallRDFForExtension(addon["install.rdf"], profileDir); } - Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_APP_VERSION, "0.1"); - await promiseStartupManager(); }); -add_task(async function test_0() { - // Should default to enabling strict compat. - await checkCompatStatus(true, 0); -}); - add_task(async function test_1() { info("Test 1"); Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false); - await checkCompatStatus(false, 1); + await checkCompatStatus(false, "nonStrict"); await promiseRestartManager(); - await checkCompatStatus(false, 1); + await checkCompatStatus(false, "nonStrict"); }); add_task(async function test_2() { info("Test 2"); Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, true); - await checkCompatStatus(true, 2); + await checkCompatStatus(true, "strict"); await promiseRestartManager(); - await checkCompatStatus(true, 2); -}); - -add_task(async function test_3() { - info("Test 3"); - Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false); - Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_APP_VERSION, "0.4"); - await checkCompatStatus(false, 3); + await checkCompatStatus(true, "strict"); }); const CHECK_COMPAT_ADDONS = { From b060e51d52d88c9d677587b72155d82e0307bdc2 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sun, 13 May 2018 16:05:03 -0700 Subject: [PATCH 3/7] Bug 1461217: Fold InstallLocation classes into XPIStateLocation sub-classes. r=aswan MozReview-Commit-ID: KaAQhXv5B7u --HG-- extra : rebase_source : d2e07577feab4249203e32611d2820a507df197a --- .../extensions/internal/XPIDatabase.jsm | 228 ++- .../extensions/internal/XPIInstall.jsm | 304 ++-- .../extensions/internal/XPIProvider.jsm | 1241 ++++++++--------- .../test/xpcshell/test_XPIStates.js | 4 +- .../test_delay_update_webextension.js | 2 +- 5 files changed, 861 insertions(+), 918 deletions(-) diff --git a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm index dfae3f8070af..e231061dd2f6 100644 --- a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm +++ b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm @@ -121,7 +121,7 @@ const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"]; const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"]; // Properties to save in JSON file -const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type", +const PROP_JSON_FIELDS = ["id", "syncGUID", "version", "type", "updateURL", "optionsURL", "optionsType", "optionsBrowserStyle", "aboutURL", "defaultLocale", "visible", "active", "userDisabled", @@ -269,12 +269,15 @@ class AddonInternal { } copyProperties(addonData, PROP_JSON_FIELDS, this); + this.location = addonData.location; if (!this.dependencies) this.dependencies = []; Object.freeze(this.dependencies); - this.addedToDatabase(); + if (this.location) { + this.addedToDatabase(); + } if (!addonData._sourceBundle) { throw new Error("Expected passed argument to contain a path"); @@ -292,14 +295,7 @@ class AddonInternal { } addedToDatabase() { - if (this._installLocation) { - this.location = this._installLocation.name; - } else if (this.location) { - this._installLocation = XPIProvider.installLocationsByName[this.location]; - } - - this._key = `${this.location}:${this.id}`; - + this._key = `${this.location.name}:${this.id}`; this.inDatabase = true; } @@ -357,7 +353,7 @@ class AddonInternal { } get isCorrectlySigned() { - switch (this._installLocation.name) { + switch (this.location.name) { case KEY_APP_SYSTEM_ADDONS: // System add-ons must be signed by the system key. return this.signedState == AddonManager.SIGNEDSTATE_SYSTEM; @@ -386,6 +382,10 @@ class AddonInternal { return this.isCompatibleWith(); } + get hidden() { + return this.location.isSystem; + } + get disabled() { return (this.userDisabled || this.appDisabled || this.softDisabled); } @@ -560,7 +560,9 @@ class AddonInternal { } toJSON() { - return copyProperties(this, PROP_JSON_FIELDS); + let obj = copyProperties(this, PROP_JSON_FIELDS); + obj.location = this.location.name; + return obj; } /** @@ -600,11 +602,11 @@ class AddonInternal { // Add-ons that are in locked install locations, or are pending uninstall // cannot be upgraded or uninstalled - if (!this._installLocation.locked && !this.pendingUninstall) { + if (!this.location.locked && !this.pendingUninstall) { // System add-on upgrades are triggered through a different mechanism (see updateSystemAddons()) - let isSystem = this._installLocation.isSystem; + let isSystem = this.location.isSystem; // Add-ons that are installed by a file link cannot be upgraded. - if (!this._installLocation.isLinkedAddon(this.id) && !isSystem) { + if (!this.location.isLinkedAddon(this.id) && !isSystem) { permissions |= AddonManager.PERM_CAN_UPGRADE; } @@ -659,7 +661,7 @@ AddonWrapper = class { } get temporarilyInstalled() { - return addonFor(this)._installLocation == XPIInternal.TemporaryInstallLocation; + return addonFor(this).location.isTemporary; } get aboutURL() { @@ -832,8 +834,8 @@ AddonWrapper = class { get scope() { let addon = addonFor(this); - if (addon._installLocation) - return addon._installLocation.scope; + if (addon.location) + return addon.location.scope; return AddonManager.SCOPE_PROFILE; } @@ -952,22 +954,22 @@ AddonWrapper = class { get hidden() { let addon = addonFor(this); - if (addon._installLocation.name == KEY_APP_TEMPORARY) + if (addon.location.isTemporary) return false; - return addon._installLocation.isSystem; + return addon.location.isSystem; } get isSystem() { let addon = addonFor(this); - return addon._installLocation.isSystem; + return addon.location.isSystem; } // Returns true if Firefox Sync should sync this addon. Only addons // in the profile install location are considered syncable. get isSyncable() { let addon = addonFor(this); - return (addon._installLocation.name == KEY_APP_PROFILE); + return (addon.location.name == KEY_APP_PROFILE); } get userPermissions() { @@ -980,12 +982,12 @@ AddonWrapper = class { uninstall(alwaysAllowUndo) { let addon = addonFor(this); - XPIProvider.uninstallAddon(addon, alwaysAllowUndo); + XPIInstall.uninstallAddon(addon, alwaysAllowUndo); } cancelUninstall() { let addon = addonFor(this); - XPIProvider.cancelUninstallAddon(addon); + XPIInstall.cancelUninstallAddon(addon); } findUpdates(aListener, aReason, aAppVersion, aPlatformVersion) { @@ -1274,6 +1276,9 @@ this.XPIDatabase = { jsonFile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_JSON_DB], true), rebuildingDatabase: false, syncLoadingDB: false, + // Add-ons from the database in locations which are no longer + // supported. + orphanedAddons: [], _saveTask: null, @@ -1362,7 +1367,7 @@ this.XPIDatabase = { let toSave = { schemaVersion: DB_SCHEMA, addons: Array.from(this.addonDB.values()) - .filter(addon => addon.location != KEY_APP_TEMPORARY), + .filter(addon => !addon.location.isTemporary), }; return toSave; }, @@ -1436,9 +1441,14 @@ this.XPIDatabase = { // wrong OS logger.warn("Could not find source bundle for add-on " + loadedAddon.id, e); } + loadedAddon.location = XPIStates.getLocation(loadedAddon.location); let newAddon = new AddonInternal(loadedAddon); - addonDB.set(newAddon._key, newAddon); + if (loadedAddon.location) { + addonDB.set(newAddon._key, newAddon); + } else { + this.orphanedAddons.push(newAddon); + } }); parseTimer.done(); @@ -1776,7 +1786,7 @@ this.XPIDatabase = { * @returns {Promise>} */ getAddonsInLocation(aLocation) { - return this.getAddonList(aAddon => aAddon._installLocation.name == aLocation); + return this.getAddonList(aAddon => aAddon.location.name == aLocation); }, /** @@ -1900,12 +1910,12 @@ this.XPIDatabase = { LEGACY_TYPES.has(addon.type) && // Legacy add-ons are allowed in the system location. - !addon._installLocation.isSystem && + !addon.location.isSystem && // Legacy extensions may be installed temporarily in // non-release builds. !(AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS && - addon._installLocation.name == KEY_APP_TEMPORARY) && + addon.location.isTemporary) && // Properly signed legacy extensions are allowed. addon.signedState !== AddonManager.SIGNEDSTATE_PRIVILEGED); @@ -2045,9 +2055,9 @@ this.XPIDatabase = { }, updateXPIStates(addon) { - let xpiState = XPIStates.getAddon(addon.location, addon.id); - if (xpiState) { - xpiState.syncWithDB(addon); + let state = addon.location && addon.location.get(addon.id); + if (state) { + state.syncWithDB(addon); XPIStates.save(); } }, @@ -2081,7 +2091,7 @@ this.XPIDatabase = { * * @param {string} aId * The ID of the add-on to make visible - * @param {InstallLocation} aLocation + * @param {XPIStateLocation} aLocation * The location in which to make the add-on visible. * @returns {AddonInternal?} * The add-on instance which was marked visible, if any. @@ -2265,15 +2275,7 @@ this.XPIDatabase = { // Flag that active states in the database need to be updated on shutdown Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); - // Sync with XPIStates. - let xpiState = XPIStates.getAddon(aAddon.location, aAddon.id); - if (xpiState) { - xpiState.syncWithDB(aAddon); - XPIStates.save(); - } else { - // There should always be an xpiState - logger.warn("No XPIState for ${id} in ${location}", aAddon); - } + this.updateXPIStates(aAddon); // Have we just gone back to the current state? if (isDisabled != aAddon.active) { @@ -2301,11 +2303,7 @@ this.XPIDatabase = { if (isTheme(aAddon.type)) { if (!isDisabled) { AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type); - - if (xpiState) { - xpiState.syncWithDB(aAddon); - XPIStates.save(); - } + this.updateXPIStates(aAddon); } else if (isDisabled && !aBecauseSelecting) { AddonManagerPrivate.notifyAddonChanged(null, "theme"); } @@ -2348,11 +2346,11 @@ this.XPIDatabaseReconcile = { flattenByID(addonMap, hideLocation) { let map = new Map(); - for (let installLocation of XPIProvider.installLocations) { - if (installLocation.name == hideLocation) + for (let loc of XPIStates.locations()) { + if (loc.name == hideLocation) continue; - let locationMap = addonMap.get(installLocation.name); + let locationMap = addonMap.get(loc.name); if (!locationMap) continue; @@ -2400,7 +2398,7 @@ this.XPIDatabaseReconcile = { * has been upgraded or become corrupt and add-on data has to be reloaded * into it. * - * @param {InstallLocation} aInstallLocation + * @param {XPIStateLocation} aLocation * The install location containing the add-on * @param {string} aId * The ID of the add-on @@ -2418,9 +2416,9 @@ this.XPIDatabaseReconcile = { * A boolean indicating if flushing caches is required to complete * changing this add-on */ - addMetadata(aInstallLocation, aId, aAddonState, aNewAddon, aOldAppVersion, + addMetadata(aLocation, aId, aAddonState, aNewAddon, aOldAppVersion, aOldPlatformVersion) { - logger.debug("New add-on " + aId + " installed in " + aInstallLocation.name); + logger.debug(`New add-on ${aId} installed in ${aLocation.name}`); // We treat this is a new install if, // @@ -2441,12 +2439,11 @@ this.XPIDatabaseReconcile = { if (!aNewAddon) { // Load the manifest from the add-on. let file = new nsIFile(aAddonState.path); - aNewAddon = XPIInstall.syncLoadManifestFromFile(file, aInstallLocation); + aNewAddon = XPIInstall.syncLoadManifestFromFile(file, aLocation); } // The add-on in the manifest should match the add-on ID. if (aNewAddon.id != aId) { - throw new Error("Invalid addon ID: expected addon ID " + aId + - ", found " + aNewAddon.id + " in manifest"); + throw new Error(`Invalid addon ID: expected addon ID ${aId}, found ${aNewAddon.id} in manifest`); } unsigned = XPIDatabase.mustSign(aNewAddon.type) && !aNewAddon.isCorrectlySigned; @@ -2454,18 +2451,18 @@ this.XPIDatabaseReconcile = { throw Error(`Extension ${aNewAddon.id} is not correctly signed`); } } catch (e) { - logger.warn("addMetadata: Add-on " + aId + " is invalid", e); + logger.warn(`addMetadata: Add-on ${aId} is invalid`, e); // Remove the invalid add-on from the install location if the install // location isn't locked - if (aInstallLocation.isLinkedAddon(aId)) + if (aLocation.isLinkedAddon(aId)) logger.warn("Not uninstalling invalid item because it is a proxy file"); - else if (aInstallLocation.locked) + else if (aLocation.locked) logger.warn("Could not uninstall invalid item from locked install location"); else if (unsigned && !isNewInstall) logger.warn("Not uninstalling existing unsigned add-on"); else - aInstallLocation.uninstallAddon(aId); + aLocation.installer.uninstallAddon(aId); return null; } @@ -2475,9 +2472,7 @@ this.XPIDatabaseReconcile = { // Assume that add-ons in the system add-ons install location aren't // foreign and should default to enabled. - aNewAddon.foreignInstall = isDetectedInstall && - aInstallLocation.name != KEY_APP_SYSTEM_ADDONS && - aInstallLocation.name != KEY_APP_SYSTEM_DEFAULTS; + aNewAddon.foreignInstall = isDetectedInstall && !aLocation.isSystem; // appDisabled depends on whether the add-on is a foreignInstall so update aNewAddon.appDisabled = !XPIDatabase.isUsableAddon(aNewAddon); @@ -2486,9 +2481,8 @@ this.XPIDatabaseReconcile = { // If the add-on is a foreign install and is in a scope where add-ons // that were dropped in should default to disabled then disable it let disablingScopes = Services.prefs.getIntPref(PREF_EM_AUTO_DISABLED_SCOPES, 0); - if (aInstallLocation.scope & disablingScopes) { - logger.warn("Disabling foreign installed add-on " + aNewAddon.id + " in " - + aInstallLocation.name); + if (aLocation.scope & disablingScopes) { + logger.warn(`Disabling foreign installed add-on ${aNewAddon.id} in ${aLocation.name}`); aNewAddon.userDisabled = true; aNewAddon.seen = false; } @@ -2506,7 +2500,7 @@ this.XPIDatabaseReconcile = { */ removeMetadata(aOldAddon) { // This add-on has disappeared - logger.debug("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location); + logger.debug("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location.name); XPIDatabase.removeAddonMetadata(aOldAddon); }, @@ -2514,7 +2508,7 @@ this.XPIDatabaseReconcile = { * Updates an add-on's metadata and determines. This is called when either the * add-on's install directory path or last modified time has changed. * - * @param {InstallLocation} aInstallLocation + * @param {XPIStateLocation} aLocation * The install location containing the add-on * @param {AddonInternal} aOldAddon * The AddonInternal as it appeared the last time the application @@ -2527,26 +2521,28 @@ this.XPIDatabaseReconcile = { * A boolean indicating if flushing caches is required to complete * changing this add-on */ - updateMetadata(aInstallLocation, aOldAddon, aAddonState, aNewAddon) { - logger.debug("Add-on " + aOldAddon.id + " modified in " + aInstallLocation.name); + updateMetadata(aLocation, aOldAddon, aAddonState, aNewAddon) { + logger.debug(`Add-on ${aOldAddon.id} modified in ${aLocation.name}`); try { // If there isn't an updated install manifest for this add-on then load it. if (!aNewAddon) { let file = new nsIFile(aAddonState.path); - aNewAddon = XPIInstall.syncLoadManifestFromFile(file, aInstallLocation, aOldAddon); + aNewAddon = XPIInstall.syncLoadManifestFromFile(file, aLocation, aOldAddon); } // The ID in the manifest that was loaded must match the ID of the old // add-on. if (aNewAddon.id != aOldAddon.id) - throw new Error("Incorrect id in install manifest for existing add-on " + aOldAddon.id); + throw new Error(`Incorrect id in install manifest for existing add-on ${aOldAddon.id}`); } catch (e) { - logger.warn("updateMetadata: Add-on " + aOldAddon.id + " is invalid", e); + logger.warn(`updateMetadata: Add-on ${aOldAddon.id} is invalid`, e); + XPIDatabase.removeAddonMetadata(aOldAddon); - XPIStates.removeAddon(aOldAddon.location, aOldAddon.id); - if (!aInstallLocation.locked) - aInstallLocation.uninstallAddon(aOldAddon.id); + aOldAddon.location.removeAddon(aOldAddon.id); + + if (!aLocation.locked) + aLocation.installer.uninstallAddon(aOldAddon.id); else logger.warn("Could not uninstall invalid item from locked install location"); @@ -2564,7 +2560,7 @@ this.XPIDatabaseReconcile = { * Updates an add-on's path for when the add-on has moved in the * filesystem but hasn't changed in any other way. * - * @param {InstallLocation} aInstallLocation + * @param {XPIStateLocation} aLocation * The install location containing the add-on * @param {AddonInternal} aOldAddon * The AddonInternal as it appeared the last time the application @@ -2573,8 +2569,8 @@ this.XPIDatabaseReconcile = { * The new state of the add-on * @returns {AddonInternal} */ - updatePath(aInstallLocation, aOldAddon, aAddonState) { - logger.debug("Add-on " + aOldAddon.id + " moved to " + aAddonState.path); + updatePath(aLocation, aOldAddon, aAddonState) { + logger.debug(`Add-on ${aOldAddon.id} moved to ${aAddonState.path}`); aOldAddon.path = aAddonState.path; aOldAddon._sourceBundle = new nsIFile(aAddonState.path); @@ -2585,7 +2581,7 @@ this.XPIDatabaseReconcile = { * Called when no change has been detected for an add-on's metadata but the * application has changed so compatibility may have changed. * - * @param {InstallLocation} aInstallLocation + * @param {XPIStateLocation} aLocation * The install location containing the add-on * @param {AddonInternal} aOldAddon * The AddonInternal as it appeared the last time the application @@ -2598,8 +2594,8 @@ this.XPIDatabaseReconcile = { * @returns {AddonInternal} * The new addon. */ - updateCompatibility(aInstallLocation, aOldAddon, aAddonState, aReloadMetadata) { - logger.debug("Updating compatibility for add-on " + aOldAddon.id + " in " + aInstallLocation.name); + updateCompatibility(aLocation, aOldAddon, aAddonState, aReloadMetadata) { + logger.debug(`Updating compatibility for add-on ${aOldAddon.id} in ${aLocation.name}`); let checkSigning = (aOldAddon.signedState === undefined && AddonSettings.ADDON_SIGNING && @@ -2609,7 +2605,7 @@ this.XPIDatabaseReconcile = { if (checkSigning || aReloadMetadata) { try { let file = new nsIFile(aAddonState.path); - manifest = XPIInstall.syncLoadManifestFromFile(file, aInstallLocation); + manifest = XPIInstall.syncLoadManifestFromFile(file, aLocation); } catch (err) { // If we can no longer read the manifest, it is no longer compatible. aOldAddon.brokenManifest = true; @@ -2648,7 +2644,7 @@ this.XPIDatabaseReconcile = { * bundle. Add-ons in these locations are expected to change whenever * the application updates. * - * @param {InstallLocation} location + * @param {XPIStateLocation} location * The install location to check. * @returns {boolean} * True if this location is part of the application bundle. @@ -2681,7 +2677,7 @@ this.XPIDatabaseReconcile = { updateExistingAddon(oldAddon, xpiState, newAddon, aUpdateCompatibility, aSchemaChange) { XPIDatabase.recordAddonTelemetry(oldAddon); - let installLocation = oldAddon._installLocation; + let installLocation = oldAddon.location; if (xpiState.mtime < oldAddon.updateDate) { XPIProvider.setTelemetry(oldAddon.id, "olderFile", { @@ -2739,9 +2735,9 @@ this.XPIDatabaseReconcile = { */ processFileChanges(aManifests, aUpdateCompatibility, aOldAppVersion, aOldPlatformVersion, aSchemaChange) { - let findManifest = (aInstallLocation, aId) => { - return (aManifests[aInstallLocation.name] && - aManifests[aInstallLocation.name][aId]) || null; + let findManifest = (loc, id) => { + return (aManifests[loc.name] && + aManifests[loc.name][id]) || null; }; let addonExists = addon => addon._sourceBundle.exists(); @@ -2751,7 +2747,7 @@ this.XPIDatabaseReconcile = { // Get the previous add-ons from the database and put them into maps by location for (let addon of XPIDatabase.getAddons()) { - previousAddons.get(addon.location).set(addon.id, addon); + previousAddons.get(addon.location.name).set(addon.id, addon); } // Keep track of add-ons whose blocklist status may have changed. We'll check this @@ -2762,19 +2758,18 @@ this.XPIDatabaseReconcile = { // present we re-use the add-on objects from the database and update their // details directly let addonStates = new Map(); - for (let installLocation of XPIProvider.installLocations) { - let locationAddons = currentAddons.get(installLocation.name); + for (let location of XPIStates.locations()) { + let locationAddons = currentAddons.get(location.name); // Get all the on-disk XPI states for this location, and keep track of which // ones we see in the database. - let states = XPIStates.getLocation(installLocation.name) || new Map(); - let dbAddons = previousAddons.get(installLocation.name) || new Map(); + let dbAddons = previousAddons.get(location.name) || new Map(); for (let [id, oldAddon] of dbAddons) { // Check if the add-on is still installed - let xpiState = states.get(id); + let xpiState = location.get(id); if (xpiState) { let newAddon = this.updateExistingAddon(oldAddon, xpiState, - findManifest(installLocation, id), + findManifest(location, id), aUpdateCompatibility, aSchemaChange); if (newAddon) { locationAddons.set(newAddon.id, newAddon); @@ -2789,11 +2784,11 @@ this.XPIDatabaseReconcile = { } } - for (let [id, xpiState] of states) { + for (let [id, xpiState] of location) { if (locationAddons.has(id)) continue; - let newAddon = findManifest(installLocation, id); - let addon = this.addMetadata(installLocation, id, xpiState, newAddon, + let newAddon = findManifest(location, id); + let addon = this.addMetadata(location, id, xpiState, newAddon, aOldAppVersion, aOldPlatformVersion); if (addon) { locationAddons.set(addon.id, addon); @@ -2802,22 +2797,13 @@ this.XPIDatabaseReconcile = { } } - // Remove metadata for any add-ons in install locations that are no - // longer supported. - for (let [locationName, addons] of previousAddons) { - if (!currentAddons.has(locationName)) { - for (let oldAddon of addons.values()) - this.removeMetadata(oldAddon); - } - } - // Validate the updated system add-ons let hideLocation; { - let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS]; + let systemAddonLocation = XPIStates.getLocation(KEY_APP_SYSTEM_ADDONS); let addons = currentAddons.get(systemAddonLocation.name); - if (!systemAddonLocation.isValid(addons)) { + if (!systemAddonLocation.installer.isValid(addons)) { // Hide the system add-on updates if any are invalid. logger.info("One or more updated system add-ons invalid, falling back to defaults."); hideLocation = systemAddonLocation.name; @@ -2829,10 +2815,16 @@ this.XPIDatabaseReconcile = { let previousVisible = this.getVisibleAddons(previousAddons); let currentVisible = this.flattenByID(currentAddons, hideLocation); + for (let addon of XPIDatabase.orphanedAddons.splice(0)) { + if (addon.visible) { + previousVisible.set(addon.id, addon); + } + } + for (let [id, addon] of currentVisible) { // If we have a stored manifest for the add-on, it came from the // startup data cache, and supersedes any previous XPIStates entry. - let xpiState = (!findManifest(addon._installLocation, id) && + let xpiState = (!findManifest(addon.location, id) && addonStates.get(addon)); this.applyStartupChange(addon, previousVisible.get(id), xpiState); @@ -2840,14 +2832,16 @@ this.XPIDatabaseReconcile = { } for (let [id, addon] of previousVisible) { - if (addonExists(addon)) { - XPIInternal.BootstrapScope.get(addon).uninstall(); + if (addon.location) { + if (addonExists(addon)) { + XPIInternal.BootstrapScope.get(addon).uninstall(); + } + addon.location.removeAddon(id); + addon.visible = false; + addon.active = false; } - AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED, id); - XPIStates.removeAddon(addon.location, id); - addon.visible = false; - addon.active = false; + AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED, id); } if (previousVisible.size) { XPIInstall.flushChromeCaches(); @@ -2907,7 +2901,7 @@ this.XPIDatabaseReconcile = { if (previousAddon !== currentAddon) { AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, id); - if (previousAddon._installLocation && + if (previousAddon.location && previousAddon._sourceBundle.exists() && !previousAddon._sourceBundle.equals(currentAddon._sourceBundle)) { XPIInternal.BootstrapScope.get(previousAddon).update( diff --git a/toolkit/mozapps/extensions/internal/XPIInstall.jsm b/toolkit/mozapps/extensions/internal/XPIInstall.jsm index 46f0c9fc02da..53f87be7a646 100644 --- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm +++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm @@ -88,12 +88,11 @@ const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest"; const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest"; const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required"; -/* globals BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, KEY_APP_TEMPORARY, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPI_PERMISSION, XPIStates, getExternalType, isTheme, isWebExtension */ +/* globals BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPI_PERMISSION, XPIStates, getExternalType, isTheme, isWebExtension */ const XPI_INTERNAL_SYMBOLS = [ "BOOTSTRAP_REASONS", "KEY_APP_SYSTEM_ADDONS", "KEY_APP_SYSTEM_DEFAULTS", - "KEY_APP_TEMPORARY", "PREF_BRANCH_INSTALLED_ADDON", "PREF_SYSTEM_ADDON_SET", "SIGNED_TYPES", @@ -780,7 +779,7 @@ function generateTemporaryInstallID(aFile) { return id; } -var loadManifest = async function(aPackage, aInstallLocation, aOldAddon) { +var loadManifest = async function(aPackage, aLocation, aOldAddon) { async function loadFromRDF(aUri) { let manifest = await aPackage.readString("install.rdf"); let addon = await loadManifestFromRDF(aUri, manifest, aPackage); @@ -809,7 +808,7 @@ var loadManifest = async function(aPackage, aInstallLocation, aOldAddon) { await loadFromRDF(aPackage.getURI("install.rdf")); addon._sourceBundle = aPackage.file; - addon._installLocation = aInstallLocation; + addon.location = aLocation; let {signedState, cert} = await aPackage.verifySignedState(addon); addon.signedState = signedState; @@ -821,7 +820,7 @@ var loadManifest = async function(aPackage, aInstallLocation, aOldAddon) { throw new Error(`Webextension is signed with an invalid id (${addon.id})`); } } - if (!addon.id && aInstallLocation.name == KEY_APP_TEMPORARY) { + if (!addon.id && aLocation.isTemporary) { addon.id = generateTemporaryInstallID(aPackage.file); } } @@ -839,7 +838,7 @@ var loadManifest = async function(aPackage, aInstallLocation, aOldAddon) { * * @param {nsIFile} aFile * The file to load the manifest from. - * @param {InstallLocation} aInstallLocation + * @param {XPIStateLocation} aLocation * The install location the add-on is installed in, or will be * installed to. * @param {AddonInternal?} aOldAddon @@ -849,10 +848,10 @@ var loadManifest = async function(aPackage, aInstallLocation, aOldAddon) { * @returns {AddonInternal} * The parsed Addon object for the file's manifest. */ -var loadManifestFromFile = async function(aFile, aInstallLocation, aOldAddon) { +var loadManifestFromFile = async function(aFile, aLocation, aOldAddon) { let pkg = Package.get(aFile); try { - let addon = await loadManifest(pkg, aInstallLocation, aOldAddon); + let addon = await loadManifest(pkg, aLocation, aOldAddon); return addon; } finally { pkg.close(); @@ -863,8 +862,8 @@ var loadManifestFromFile = async function(aFile, aInstallLocation, aOldAddon) { * A synchronous method for loading an add-on's manifest. Do not use * this. */ -function syncLoadManifestFromFile(aFile, aInstallLocation, aOldAddon) { - return XPIInternal.awaitPromise(loadManifestFromFile(aFile, aInstallLocation, aOldAddon)); +function syncLoadManifestFromFile(aFile, aLocation, aOldAddon) { + return XPIInternal.awaitPromise(loadManifestFromFile(aFile, aLocation, aOldAddon)); } function flushChromeCaches() { @@ -953,11 +952,11 @@ function getSignedStatus(aRv, aCert, aAddonID) { function shouldVerifySignedState(aAddon) { // Updated system add-ons should always have their signature checked - if (aAddon._installLocation.name == KEY_APP_SYSTEM_ADDONS) + if (aAddon.location.name == KEY_APP_SYSTEM_ADDONS) return true; // We don't care about signatures for default system add-ons - if (aAddon._installLocation.name == KEY_APP_SYSTEM_DEFAULTS) + if (aAddon.location.name == KEY_APP_SYSTEM_DEFAULTS) return false; // Otherwise only check signatures if signing is enabled and the add-on is one @@ -1328,7 +1327,7 @@ class AddonInstall { /** * Instantiates an AddonInstall. * - * @param {InstallLocation} installLocation + * @param {XPIStateLocation} installLocation * The install location the add-on will be installed into * @param {nsIURL} url * The nsIURL to get the add-on from. If this is an nsIFileURL then @@ -1352,7 +1351,7 @@ class AddonInstall { */ constructor(installLocation, url, options = {}) { this.wrapper = new AddonInstallWrapper(this); - this.installLocation = installLocation; + this.location = installLocation; this.sourceURI = url; if (options.hash) { @@ -1464,9 +1463,9 @@ class AddonInstall { break; case AddonManager.STATE_INSTALLED: logger.debug("Cancelling install of " + this.addon.id); - let xpi = getFile(`${this.addon.id}.xpi`, this.installLocation.getStagingDir()); + let xpi = getFile(`${this.addon.id}.xpi`, this.location.installer.getStagingDir()); flushJarCache(xpi); - this.installLocation.cleanStagingDir([this.addon.id, this.addon.id + ".xpi"]); + this.location.installer.cleanStagingDir([this.addon.id, this.addon.id + ".xpi"]); this.state = AddonManager.STATE_CANCELLED; XPIInstall.installs.delete(this); @@ -1486,7 +1485,7 @@ class AddonInstall { this._callInstallListeners("onInstallCancelled"); this.removeTemporaryFile(); - let stagingDir = this.installLocation.getStagingDir(); + let stagingDir = this.location.installer.getStagingDir(); let stagedAddon = stagingDir.clone(); this.unstageInstall(stagedAddon); @@ -1526,19 +1525,19 @@ class AddonInstall { removeTemporaryFile() { // Only proceed if this AddonInstall owns its XPI file if (!this.ownsTempFile) { - this.logger.debug("removeTemporaryFile: " + this.sourceURI.spec + " does not own temp file"); + this.logger.debug(`removeTemporaryFile: ${this.sourceURI.spec} does not own temp file`); return; } try { - this.logger.debug("removeTemporaryFile: " + this.sourceURI.spec + " removing temp file " + - this.file.path); + this.logger.debug(`removeTemporaryFile: ${this.sourceURI.spec} removing temp file ` + + this.file.path); this.file.remove(true); this.ownsTempFile = false; } catch (e) { - this.logger.warn("Failed to remove temporary file " + this.file.path + " for addon " + - this.sourceURI.spec, - e); + this.logger.warn(`Failed to remove temporary file ${this.file.path} for addon ` + + this.sourceURI.spec, + e); } } @@ -1570,7 +1569,7 @@ class AddonInstall { try { try { - this.addon = await loadManifest(pkg, this.installLocation, this.existingAddon); + this.addon = await loadManifest(pkg, this.location, this.existingAddon); } catch (e) { return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]); } @@ -1727,7 +1726,7 @@ class AddonInstall { // install location for (let install of XPIInstall.installs) { if (install.state == AddonManager.STATE_INSTALLED && - install.installLocation == this.installLocation && + install.location == this.location && install.addon.id == this.addon.id) { logger.debug(`Cancelling previous pending install of ${install.addon.id}`); install.cancel(); @@ -1735,17 +1734,17 @@ class AddonInstall { } let isUpgrade = this.existingAddon && - this.existingAddon._installLocation == this.installLocation; + this.existingAddon.location == this.location; logger.debug("Starting install of " + this.addon.id + " from " + this.sourceURI.spec); AddonManagerPrivate.callAddonListeners("onInstalling", this.addon.wrapper, false); - let stagedAddon = this.installLocation.getStagingDir(); + let stagedAddon = this.location.installer.getStagingDir(); (async () => { - await this.installLocation.requestStagingDir(); + await this.location.installer.requestStagingDir(); // remove any previously staged files await this.unstageInstall(stagedAddon); @@ -1764,7 +1763,7 @@ class AddonInstall { // Install the new add-on into its final location let existingAddonID = this.existingAddon ? this.existingAddon.id : null; - let file = this.installLocation.installAddon({ + let file = this.location.installer.installAddon({ id: this.addon.id, source: stagedAddon, existingAddonID @@ -1777,7 +1776,7 @@ class AddonInstall { if (isUpgrade) { this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon, file.path); - let state = XPIStates.getAddon(this.installLocation.name, this.addon.id); + let state = this.location.get(this.addon.id); if (state) { state.syncWithDB(this.addon, true); } else { @@ -1826,7 +1825,7 @@ class AddonInstall { this._callInstallListeners("onInstallFailed"); }).then(() => { this.removeTemporaryFile(); - return this.installLocation.releaseStagingDir(); + return this.location.installer.releaseStagingDir(); }); } @@ -1853,8 +1852,7 @@ class AddonInstall { this.addon._sourceBundle = stagedAddon; // Cache the AddonInternal as it may have updated compatibility info - XPIStates.getLocation(this.installLocation.name).stageAddon(this.addon.id, - this.addon.toJSON()); + this.location.stageAddon(this.addon.id, this.addon.toJSON()); logger.debug(`Staged install of ${this.addon.id} from ${this.sourceURI.spec} ready; waiting for restart.`); if (isUpgrade) { @@ -1871,7 +1869,7 @@ class AddonInstall { * The staging directory from which to unstage the install. */ async unstageInstall(stagingDir) { - XPIStates.getLocation(this.installLocation.name).unstageAddon(this.addon.id); + this.location.unstageAddon(this.addon.id); await removeAsync(getFile(this.addon.id, stagingDir)); @@ -1887,9 +1885,9 @@ class AddonInstall { async postpone(resumeFn) { this.state = AddonManager.STATE_POSTPONED; - let stagingDir = this.installLocation.getStagingDir(); + let stagingDir = this.location.installer.getStagingDir(); - await this.installLocation.requestStagingDir(); + await this.location.installer.requestStagingDir(); await this.unstageInstall(stagingDir); let stagedAddon = getFile(`${this.addon.id}.xpi`, stagingDir); @@ -1921,7 +1919,7 @@ class AddonInstall { // Release the staging directory lock, but since the staging dir is populated // it will not be removed until resumed or installed by restart. // See also cleanStagingDir() - this.installLocation.releaseStagingDir(); + this.location.installer.releaseStagingDir(); } _callInstallListeners(event, ...args) { @@ -2042,8 +2040,8 @@ var DownloadAddonInstall = class extends AddonInstall { /** * Instantiates a DownloadAddonInstall * - * @param {InstallLocation} installLocation - * The InstallLocation the add-on will be installed into + * @param {XPIStateLocation} installLocation + * The XPIStateLocation the add-on will be installed into * @param {nsIURL} url * The nsIURL to get the add-on from * @param {Object} [options = {}] @@ -2452,10 +2450,10 @@ function createUpdate(aCallback, aAddon, aUpdate) { }; let install; if (url instanceof Ci.nsIFileURL) { - install = new LocalAddonInstall(aAddon._installLocation, url, opts); + install = new LocalAddonInstall(aAddon.location, url, opts); await install.init(); } else { - install = new DownloadAddonInstall(aAddon._installLocation, url, opts); + install = new DownloadAddonInstall(aAddon.location, url, opts); } try { if (aUpdate.updateInfoURL) @@ -2688,7 +2686,7 @@ UpdateChecker.prototype = { ignoreMaxVersion, ignoreStrictCompat, compatOverrides); if (update && Services.vc.compare(this.addon.version, update.version) < 0 - && !this.addon._installLocation.locked) { + && !this.addon.location.locked) { for (let currentInstall of XPIInstall.installs) { // Skip installs that don't match the available update if (currentInstall.existingAddon != this.addon || @@ -2746,14 +2744,14 @@ UpdateChecker.prototype = { * * @param {nsIFile} file * The file to install - * @param {InstallLocation} location + * @param {XPIStateLocation} location * The location to install to * @returns {Promise} * A Promise that resolves with the new install object. */ function createLocalInstall(file, location) { if (!location) { - location = XPIProvider.installLocationsByName[KEY_APP_PROFILE]; + location = XPIStates.getLocation(KEY_APP_PROFILE); } let url = Services.io.newFileURI(file); @@ -2767,13 +2765,26 @@ function createLocalInstall(file, location) { } } -// These are partial classes which contain the install logic for the -// homonymous classes in XPIProvider.jsm. Those classes forward calls to -// their install methods to these classes, with the `this` value set to -// an instance the class as defined in XPIProvider. -class DirectoryInstallLocation {} +class DirectoryInstaller { + constructor(location) { + this.location = location; + + this._stagingDirLock = 0; + this._stagingDirPromise = null; + } + + get name() { + return this.location.name; + } + + get dir() { + return this.location.dir; + } + set dir(val) { + this.location.dir = val; + this.location.path = val.path; + } -class MutableDirectoryInstallLocation extends DirectoryInstallLocation { /** * Gets the staging directory to put add-ons that are pending install and * uninstall into. @@ -2781,7 +2792,7 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { * @returns {nsIFile} */ getStagingDir() { - return getFile(DIR_STAGE, this._directory); + return getFile(DIR_STAGE, this.dir); } requestStagingDir() { @@ -2790,8 +2801,8 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { if (this._stagingDirPromise) return this._stagingDirPromise; - OS.File.makeDir(this._directory.path); - let stagepath = OS.Path.join(this._directory.path, DIR_STAGE); + OS.File.makeDir(this.dir.path); + let stagepath = OS.Path.join(this.dir.path, DIR_STAGE); return this._stagingDirPromise = OS.File.makeDir(stagepath).catch((e) => { if (e instanceof OS.File.Error && e.becauseExists) return; @@ -2856,7 +2867,7 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { * @returns {nsIFile} */ getTrashDir() { - let trashDir = getFile(DIR_TRASH, this._directory); + let trashDir = getFile(DIR_TRASH, this.dir); let trashDirExists = trashDir.exists(); try { if (trashDirExists) @@ -2900,11 +2911,11 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { let transaction = new SafeInstallOperation(); let moveOldAddon = aId => { - let file = getFile(aId, this._directory); + let file = getFile(aId, this.dir); if (file.exists()) transaction.moveUnder(file, trashDir); - file = getFile(`${aId}.xpi`, this._directory); + file = getFile(`${aId}.xpi`, this.dir); if (file.exists()) { flushJarCache(file); transaction.moveUnder(file, trashDir); @@ -2943,10 +2954,10 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { } if (action == "copy") { - transaction.copy(source, this._directory); + transaction.copy(source, this.dir); } else if (action == "move") { flushJarCache(source); - transaction.moveUnder(source, this._directory); + transaction.moveUnder(source, this.dir); } // Do nothing for the proxy file as we sideload an addon permanently } finally { @@ -2955,11 +2966,11 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { try { recursiveRemove(trashDir); } catch (e) { - logger.warn("Failed to remove trash directory when installing " + id, e); + logger.warn(`Failed to remove trash directory when installing ${id}`, e); } } - let newFile = this._directory.clone(); + let newFile = this.dir.clone(); if (action == "proxy") { // When permanently installing sideloaded addon, we just put a proxy file @@ -2974,13 +2985,7 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { try { newFile.lastModifiedTime = Date.now(); } catch (e) { - logger.warn("failed to set lastModifiedTime on " + newFile.path, e); - } - this._IDToFileMap[id] = newFile; - - if (existingAddonID && existingAddonID != id && - existingAddonID in this._IDToFileMap) { - delete this._IDToFileMap[existingAddonID]; + logger.warn(`failed to set lastModifiedTime on ${newFile.path}`, e); } return newFile; @@ -2994,29 +2999,20 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { * @throws if the ID does not match any of the add-ons installed */ uninstallAddon(aId) { - let file = this._IDToFileMap[aId]; - if (!file) { - logger.warn("Attempted to remove " + aId + " from " + - this._name + " but it was already gone"); - return; - } - - file = getFile(aId, this._directory); + let file = getFile(aId, this.dir); if (!file.exists()) file.leafName += ".xpi"; if (!file.exists()) { - logger.warn("Attempted to remove " + aId + " from " + - this._name + " but it was already gone"); - - delete this._IDToFileMap[aId]; + logger.warn(`Attempted to remove ${aId} from ${this.name} but it was already gone`); + this.location.delete(aId); return; } let trashDir = this.getTrashDir(); if (file.leafName != aId) { - logger.debug("uninstallAddon: flushing jar cache " + file.path + " for addon " + aId); + logger.debug(`uninstallAddon: flushing jar cache ${file.path} for addon ${aId}`); flushJarCache(file); } @@ -3030,17 +3026,29 @@ class MutableDirectoryInstallLocation extends DirectoryInstallLocation { try { recursiveRemove(trashDir); } catch (e) { - logger.warn("Failed to remove trash directory when uninstalling " + aId, e); + logger.warn(`Failed to remove trash directory when uninstalling ${aId}`, e); } } - XPIStates.removeAddon(this.name, aId); - - delete this._IDToFileMap[aId]; + this.location.removeAddon(aId); } } -class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { +class SystemAddonInstaller extends DirectoryInstaller { + constructor(location) { + super(location); + + this._baseDir = location._baseDir; + this._nextDir = null; + } + + get _addonSet() { + return this.location._addonSet; + } + set _addonSet(val) { + this.location._addonSet = val; + } + /** * Saves the current set of system add-ons * @@ -3052,7 +3060,7 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { } static _loadAddonSet() { - return XPIInternal.SystemAddonInstallLocation._loadAddonSet(); + return XPIInternal.SystemAddonLocation._loadAddonSet(); } /** @@ -3063,22 +3071,22 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { * Staging directory for system add-on upgrades. */ getStagingDir() { - this._addonSet = SystemAddonInstallLocation._loadAddonSet(); + this._addonSet = SystemAddonInstaller._loadAddonSet(); let dir = null; if (this._addonSet.directory) { - this._directory = getFile(this._addonSet.directory, this._baseDir); - dir = getFile(DIR_STAGE, this._directory); + this.dir = getFile(this._addonSet.directory, this._baseDir); + dir = getFile(DIR_STAGE, this.dir); } else { - logger.info("SystemAddonInstallLocation directory is missing"); + logger.info("SystemAddonInstaller directory is missing"); } return dir; } requestStagingDir() { - this._addonSet = SystemAddonInstallLocation._loadAddonSet(); + this._addonSet = SystemAddonInstaller._loadAddonSet(); if (this._addonSet.directory) { - this._directory = getFile(this._addonSet.directory, this._baseDir); + this.dir = getFile(this._addonSet.directory, this._baseDir); } return super.requestStagingDir(); } @@ -3130,7 +3138,7 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { // fails then at least they will not be re-activated on // next restart. this._addonSet = { schema: 1, addons: {} }; - SystemAddonInstallLocation._saveAddonSet(this._addonSet); + SystemAddonInstaller._saveAddonSet(this._addonSet); // If this is running at app startup, the pref being cleared // will cause later stages of startup to notice that the @@ -3176,7 +3184,7 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { } // Skip the directory currently in use - if (this._directory && this._directory.path == entry.path) { + if (this.dir && this.dir.path == entry.path) { continue; } @@ -3214,7 +3222,7 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { // Make sure the base dir exists await OS.File.makeDir(this._baseDir.path, { ignoreExisting: true }); - let addonSet = SystemAddonInstallLocation._loadAddonSet(); + let addonSet = SystemAddonInstaller._loadAddonSet(); // Remove any add-ons that are no longer part of the set. for (let addonID of Object.keys(addonSet.addons)) { @@ -3238,14 +3246,13 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { // Record the new upgrade directory. let state = { schema: 1, directory: newDir.leafName, addons: {} }; - SystemAddonInstallLocation._saveAddonSet(state); + SystemAddonInstaller._saveAddonSet(state); this._nextDir = newDir; - let location = this; let installs = []; for (let addon of aAddons) { - let install = await createLocalInstall(addon._sourceBundle, location); + let install = await createLocalInstall(addon._sourceBundle, this.location); installs.push(install); } @@ -3261,7 +3268,7 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { logger.info(`system add-on ${install.addon.id} has an upgrade listener, postponing upgrade set until restart`); resumeFn = () => { logger.info(`${install.addon.id} has resumed a previously postponed addon set`); - install.installLocation.resumeAddonSet(installs); + install.location.installer.resumeAddonSet(installs); }; } await install.postpone(resumeFn); @@ -3278,8 +3285,8 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { }; } - previousState = SystemAddonInstallLocation._loadAddonSet(); - SystemAddonInstallLocation._saveAddonSet(state); + previousState = SystemAddonInstaller._loadAddonSet(); + SystemAddonInstaller._saveAddonSet(state); let blockers = aAddons.filter( addon => AddonManagerPrivate.hasUpgradeListener(addon.id) @@ -3293,7 +3300,7 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { } catch (e) { // Roll back to previous upgrade set (if present) on restart. if (previousState) { - SystemAddonInstallLocation._saveAddonSet(previousState); + SystemAddonInstaller._saveAddonSet(previousState); } // Otherwise, roll back to built-in set on restart. // TODO try to do these restartlessly @@ -3317,7 +3324,7 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { async resumeAddonSet(installs) { async function resumeAddon(install) { install.state = AddonManager.STATE_DOWNLOADED; - install.installLocation.releaseStagingDir(); + install.location.installer.releaseStagingDir(); install.install(); } @@ -3341,7 +3348,7 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { * @returns {nsIFile} */ getTrashDir() { - let trashDir = getFile(DIR_TRASH, this._directory); + let trashDir = getFile(DIR_TRASH, this.dir); let trashDirExists = trashDir.exists(); try { if (trashDirExists) @@ -3375,25 +3382,24 @@ class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { try { flushJarCache(source); - transaction.moveUnder(source, this._directory); + transaction.moveUnder(source, this.dir); } finally { // It isn't ideal if this cleanup fails but it isn't worth rolling back // the install because of it. try { recursiveRemove(trashDir); } catch (e) { - logger.warn("Failed to remove trash directory when installing " + id, e); + logger.warn(`Failed to remove trash directory when installing ${id}`, e); } } - let newFile = getFile(source.leafName, this._directory); + let newFile = getFile(source.leafName, this.dir); try { newFile.lastModifiedTime = Date.now(); } catch (e) { logger.warn("failed to set lastModifiedTime on " + newFile.path, e); } - this._IDToFileMap[id] = newFile; return newFile; } @@ -3418,7 +3424,7 @@ var XPIInstall = { * The expected ID of the add-on. * @param {nsIFile} file * The XPI file to install the add-on from. - * @param {InstallLocation} location + * @param {XPIStateLocation} location * The install location to install the add-on to. * @returns {AddonInternal} * The installed Addon object, upon success. @@ -3430,29 +3436,25 @@ var XPIInstall = { throw new Error(`File file ${file.path} contains an add-on with an incorrect ID`); } - let existingEntry = null; - try { - existingEntry = location.getLocationForID(id); - } catch (e) { - } + let state = location.get(id); - if (existingEntry) { + if (state) { try { - let existingAddon = await loadManifestFromFile(existingEntry, location); + let existingAddon = await loadManifestFromFile(state.file, location); if (Services.vc.compare(addon.version, existingAddon.version) <= 0) return null; } catch (e) { // Bad add-on in the profile so just proceed and install over the top logger.warn("Profile contains an add-on with a bad or missing install " + - `manifest at ${existingEntry.path}, overwriting`, e); + `manifest at ${state.path}, overwriting`, e); } } else if (Services.prefs.getBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, false)) { return null; } // Install the add-on - addon._sourceBundle = location.installAddon({ id, source: file, action: "copy" }); + addon._sourceBundle = location.installer.installAddon({ id, source: file, action: "copy" }); if (Services.prefs.getBoolPref(PREF_DISTRO_ADDONS_PERMS, false)) { addon.userDisabled = true; if (!XPIProvider.newDistroAddons) { @@ -3462,7 +3464,7 @@ var XPIInstall = { } XPIStates.addAddon(addon); - logger.debug("Installed distribution add-on " + id); + logger.debug(`Installed distribution add-on ${id}`); Services.prefs.setBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, true); @@ -3477,13 +3479,13 @@ var XPIInstall = { * The expected ID of the add-on. * @param {object} metadata * The parsed metadata for the staged install. - * @param {InstallLocation} location + * @param {XPIStateLocation} location * The install location to install the add-on to. * @returns {AddonInternal} * The installed Addon object, upon success. */ async installStagedAddon(id, metadata, location) { - let source = getFile(`${id}.xpi`, location.getStagingDir()); + let source = getFile(`${id}.xpi`, location.installer.getStagingDir()); // Check that the directory's name is a valid ID. if (!gIDTest.test(id) || !source.exists() || !source.isFile()) { @@ -3516,7 +3518,7 @@ var XPIInstall = { } try { - addon._sourceBundle = location.installAddon({ + addon._sourceBundle = location.installer.installAddon({ id, source, existingAddonID: id, }); XPIStates.addAddon(addon); @@ -3532,10 +3534,12 @@ var XPIInstall = { }, async updateSystemAddons() { - let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS]; + let systemAddonLocation = XPIStates.getLocation(KEY_APP_SYSTEM_ADDONS); if (!systemAddonLocation) return; + let installer = systemAddonLocation.installer; + // Don't do anything in safe mode if (Services.appinfo.inSafeMode) return; @@ -3543,7 +3547,7 @@ var XPIInstall = { // Download the list of system add-ons let url = Services.prefs.getStringPref(PREF_SYSTEM_ADDON_UPDATE_URL, null); if (!url) { - await systemAddonLocation.cleanDirectories(); + await installer.cleanDirectories(); return; } @@ -3555,7 +3559,7 @@ var XPIInstall = { // If there was no list then do nothing. if (!res || !res.gmpAddons) { logger.info("No system add-ons list was returned."); - await systemAddonLocation.cleanDirectories(); + await installer.cleanDirectories(); return; } @@ -3582,7 +3586,7 @@ var XPIInstall = { let updatedAddons = addonMap(await XPIDatabase.getAddonsInLocation(KEY_APP_SYSTEM_ADDONS)); if (setMatches(addonList, updatedAddons)) { logger.info("Retaining existing updated system add-ons."); - await systemAddonLocation.cleanDirectories(); + await installer.cleanDirectories(); return; } @@ -3591,8 +3595,8 @@ var XPIInstall = { let defaultAddons = addonMap(await XPIDatabase.getAddonsInLocation(KEY_APP_SYSTEM_DEFAULTS)); if (setMatches(addonList, defaultAddons)) { logger.info("Resetting system add-ons."); - systemAddonLocation.resetAddonSet(); - await systemAddonLocation.cleanDirectories(); + installer.resetAddonSet(); + await installer.cleanDirectories(); return; } @@ -3641,7 +3645,7 @@ var XPIInstall = { return false; } - if (!systemAddonLocation.isValidAddon(item.addon)) + if (!installer.isValidAddon(item.addon)) return false; return true; @@ -3654,7 +3658,7 @@ var XPIInstall = { // Install into the install location logger.info("Installing new system add-on set"); - await systemAddonLocation.installAddonSet(Array.from(addonList.values()) + await installer.installAddonSet(Array.from(addonList.values()) .map(a => a.addon)); }, @@ -3752,7 +3756,7 @@ var XPIInstall = { * @returns {AddonInstall} */ async getInstallForURL(aUrl, aHash, aName, aIcons, aVersion, aBrowser) { - let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE]; + let location = XPIStates.getLocation(KEY_APP_PROFILE); let url = Services.io.newURI(aUrl); let options = { @@ -3825,7 +3829,7 @@ var XPIInstall = { } let addon = await loadManifestFromFile(aFile, installLocation); - installLocation.installAddon({ id: addon.id, source: aFile }); + installLocation.installer.installAddon({ id: addon.id, source: aFile }); if (addon.appDisabled) { let message = `Add-on ${addon.id} is not compatible with application version.`; @@ -3909,11 +3913,11 @@ var XPIInstall = { */ async uninstallAddon(aAddon, aForcePending) { if (!(aAddon.inDatabase)) - throw new Error("Cannot uninstall addon " + aAddon.id + " because it is not installed"); + throw new Error(`Cannot uninstall addon ${aAddon.id} because it is not installed`); - if (aAddon._installLocation.locked) - throw new Error("Cannot uninstall addon " + aAddon.id - + " from locked install location " + aAddon._installLocation.name); + if (aAddon.location.locked) + throw new Error(`Cannot uninstall addon ${aAddon.id} ` + + `from locked install location ${aAddon.location.name}`); if (aForcePending && aAddon.pendingUninstall) throw new Error("Add-on is already marked to be uninstalled"); @@ -3921,7 +3925,7 @@ var XPIInstall = { aAddon._hasResourceCache.clear(); if (aAddon._updateCheck) { - logger.debug("Cancel in-progress update check for " + aAddon.id); + logger.debug(`Cancel in-progress update check for ${aAddon.id}`); aAddon._updateCheck.cancel(); } @@ -3932,8 +3936,8 @@ var XPIInstall = { // that an uninstall is necessary on next startup. Temporary add-ons are // automatically uninstalled on shutdown anyway so there is no need to // do this for them. - if (aAddon._installLocation.name != KEY_APP_TEMPORARY) { - let stage = getFile(aAddon.id, aAddon._installLocation.getStagingDir()); + if (!aAddon.location.isTemporary) { + let stage = getFile(aAddon.id, aAddon.location.installer.getStagingDir()); if (!stage.exists()) stage.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); } @@ -3942,7 +3946,7 @@ var XPIInstall = { pendingUninstall: true }); Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); - let xpiState = XPIStates.getAddon(aAddon.location, aAddon.id); + let xpiState = aAddon.location.get(aAddon.id); if (xpiState) { xpiState.enabled = false; XPIStates.save(); @@ -3963,8 +3967,8 @@ var XPIInstall = { !!aForcePending); } - let existingAddon = XPIStates.findAddon(aAddon.id, loc => - loc.name != aAddon._installLocation.name); + let existingAddon = XPIStates.findAddon(aAddon.id, + loc => loc != aAddon.location); let bootstrap = XPIInternal.BootstrapScope.get(aAddon); if (!aForcePending) { @@ -3976,9 +3980,9 @@ var XPIInstall = { let uninstall = () => { XPIStates.disableAddon(aAddon.id); - aAddon._installLocation.uninstallAddon(aAddon.id); + aAddon.location.installer.uninstallAddon(aAddon.id); XPIDatabase.removeAddonMetadata(aAddon); - XPIStates.removeAddon(aAddon.location, aAddon.id); + aAddon.location.removeAddon(aAddon.id); AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper); if (existing) { @@ -3996,7 +4000,7 @@ var XPIInstall = { AddonManagerPrivate.callAddonListeners("onInstalled", existing.wrapper); } else { - XPIStates.removeAddon(aAddon.location, aAddon.id); + aAddon.location.removeAddon(aAddon.id); bootstrap.uninstall(); uninstall(); } @@ -4023,8 +4027,8 @@ var XPIInstall = { if (!aAddon.pendingUninstall) throw new Error("Add-on is not marked to be uninstalled"); - if (aAddon._installLocation.name != KEY_APP_TEMPORARY) - aAddon._installLocation.cleanStagingDir([aAddon.id]); + if (!aAddon.location.isTemporary) + aAddon.location.installer.cleanStagingDir([aAddon.id]); XPIDatabase.setAddonProperties(aAddon, { pendingUninstall: false @@ -4033,7 +4037,7 @@ var XPIInstall = { if (!aAddon.visible) return; - XPIStates.getAddon(aAddon.location, aAddon.id).syncWithDB(aAddon); + aAddon.location.get(aAddon.id).syncWithDB(aAddon); XPIStates.save(); Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); @@ -4052,6 +4056,6 @@ var XPIInstall = { AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, false); }, - MutableDirectoryInstallLocation, - SystemAddonInstallLocation, + DirectoryInstaller, + SystemAddonInstaller, }; diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 4db729ced5f2..527c6cd631f7 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -364,15 +364,12 @@ function canRunInSafeMode(aAddon) { // TODO product should make the call about temporary add-ons running // in safe mode. assuming for now that they are. - let location = aAddon._installLocation || null; + let location = aAddon.location || null; if (!location) { return false; } - if (location.name == KEY_APP_TEMPORARY) - return true; - - return location.isSystem; + return location.isTemporary || location.isSystem; } /** @@ -743,21 +740,55 @@ class XPIState { * * @param {string} name * The name of the install location (e.g., "app-profile"). - * @param {string?} path + * @param {string | nsIFile | null} path * The on-disk path of the install location. May be null for some * locations which do not map to a specific on-disk path. - * @param {object} [saved = {}] + * @param {integer} scope + * The scope of add-ons installed in this location. + * @param {object} [saved] * The persisted JSON state data to restore. */ class XPIStateLocation extends Map { - constructor(name, path, saved = {}) { + constructor(name, path, scope, saved) { super(); this.name = name; - this.path = path || saved.path || null; + this.scope = scope; + if (path instanceof Ci.nsIFile) { + this.dir = path; + this.path = path.path; + } else { + this.path = path; + this.dir = this.path && new nsIFile(this.path); + } + this.staged = {}; + this.changed = false; + + if (saved) { + this.restore(saved); + } + + this._installler = undefined; + } + + get installer() { + if (this._installer === undefined) { + this._installer = this.makeInstaller(); + } + return this._installer; + } + + makeInstaller() { + return null; + } + + restore(saved) { + if (!this.path && saved.path) { + this.path = saved.path; + this.dir = new nsIFile(this.path); + } this.staged = saved.staged || {}; this.changed = saved.changed || false; - this.dir = this.path && new nsIFile(this.path); for (let [id, data] of Object.entries(saved.addons || {})) { let xpiState = this._addState(id, data); @@ -765,7 +796,7 @@ class XPIStateLocation extends Map { // Make a note that this state was restored from saved data. But // only if this location hasn't moved since the last startup, // since that causes problems for new system add-on bundles. - if (!path || path == saved.path) { + if (!this.path || this.path == saved.path) { xpiState.wasRestored = true; } } @@ -825,6 +856,19 @@ class XPIStateLocation extends Map { XPIProvider.setTelemetry(addon.id, "location", this.name); } + /** + * Remove the XPIState for an add-on and save the new state. + * + * @param {string} aId + * The ID of the add-on. + */ + removeAddon(aId) { + if (this.has(aId)) { + this.delete(aId); + XPIStates.save(); + } + } + /** * Adds stub state data for the local file to the DB. * @@ -892,14 +936,451 @@ class XPIStateLocation extends Map { migrateAddon(id, state, bootstrapped) { this.set(id, XPIState.migrate(this, id, state, bootstrapped)); } + + /** + * Returns true if the given addon was installed in this location by a text + * file pointing to its real path. + * + * @param {string} aId + * The ID of the addon + * @returns {boolean} + */ + isLinkedAddon(aId) { + if (!this.dir) { + return true; + } + return this.has(aId) && !this.dir.contains(this.get(aId).file); + } + + get isTemporary() { + return false; + } + + get isSystem() { + return false; + } +} + +class TemporaryLocation extends XPIStateLocation { + /** + * @param {string} name + * The string identifier for the install location. + */ + constructor(name) { + super(name, null, null); + this.locked = false; + } + + makeInstaller() { + // Installs are a no-op. We only register that add-ons exist, and + // run them from their current location. + return { + installAddon() {}, + uninstallAddon() {}, + }; + } + + toJSON() { + return {}; + } + + readAddons() { + return new Map(); + } + + get isTemporary() { + return true; + } +} + +var TemporaryInstallLocation = new TemporaryLocation(KEY_APP_TEMPORARY); + +/** + * An object which identifies a directory install location for add-ons. The + * location consists of a directory which contains the add-ons installed in the + * location. + * + */ +class DirectoryLocation extends XPIStateLocation { + /** + * Each add-on installed in the location is either a directory containing the + * add-on's files or a text file containing an absolute path to the directory + * containing the add-ons files. The directory or text file must have the same + * name as the add-on's ID. + * + * @param {string} name + * The string identifier for the install location. + * @param {nsIFile} dir + * The directory for the install location. + * @param {integer} scope + * The scope of add-ons installed in this location. + * @param {boolean} [locked = true] + * If false, the location accepts new add-on installs. + */ + constructor(name, dir, scope, locked = true) { + super(name, dir, scope); + this.locked = locked; + this.initialized = false; + } + + makeInstaller() { + if (this.locked) { + return null; + } + return new XPIInstall.DirectoryInstaller(this); + } + + /** + * Reads a single-line file containing the path to a directory, and + * returns an nsIFile pointing to that directory, if successful. + * + * @param {nsIFile} aFile + * The file containing the directory path + * @returns {nsIFile?} + * An nsIFile object representing the linked directory, or null + * on error. + */ + _readLinkFile(aFile) { + let linkedDirectory; + if (aFile.isSymlink()) { + linkedDirectory = aFile.clone(); + try { + linkedDirectory.normalize(); + } catch (e) { + logger.warn(`Symbolic link ${aFile.path} points to a path ` + + `which does not exist`); + return null; + } + } else { + let fis = new FileInputStream(aFile, -1, -1, false); + let line = {}; + fis.QueryInterface(Ci.nsILineInputStream).readLine(line); + fis.close(); + + if (line.value) { + linkedDirectory = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + try { + linkedDirectory.initWithPath(line.value); + } catch (e) { + linkedDirectory.setRelativeDescriptor(aFile.parent, line.value); + } + } + } + + if (linkedDirectory) { + if (!linkedDirectory.exists()) { + logger.warn(`File pointer ${aFile.path} points to ${linkedDirectory.path} ` + + "which does not exist"); + return null; + } + + if (!linkedDirectory.isDirectory()) { + logger.warn(`File pointer ${aFile.path} points to ${linkedDirectory.path} ` + + "which is not a directory"); + return null; + } + + return linkedDirectory; + } + + logger.warn(`File pointer ${aFile.path} does not contain a path`); + return null; + } + + /** + * Finds all the add-ons installed in this location. + * + * @returns {Map} + * A map of add-ons present in this location. + */ + readAddons() { + let addons = new Map(); + + if (!this.dir) { + return addons; + } + this.initialized = true; + + // Use a snapshot of the directory contents to avoid possible issues with + // iterating over a directory while removing files from it (the YAFFS2 + // embedded filesystem has this issue, see bug 772238). + let entries = getDirectoryEntries(this.dir); + for (let entry of entries) { + let id = entry.leafName; + if (id == DIR_STAGE || id == DIR_TRASH) + continue; + + let isFile = id.toLowerCase().endsWith(".xpi"); + if (isFile) { + id = id.substring(0, id.length - 4); + } + + if (!gIDTest.test(id)) { + logger.debug("Ignoring file entry whose name is not a valid add-on ID: " + + entry.path); + continue; + } + + if (!isFile && (entry.isFile() || entry.isSymlink())) { + let newEntry = this._readLinkFile(entry); + if (!newEntry) { + logger.debug(`Deleting stale pointer file ${entry.path}`); + try { + entry.remove(true); + } catch (e) { + logger.warn(`Failed to remove stale pointer file ${entry.path}`, e); + // Failing to remove the stale pointer file is ignorable + } + continue; + } + + entry = newEntry; + } + + + addons.set(id, entry); + } + return addons; + } +} + +/** + * An object which identifies a built-in install location for add-ons, such + * as default system add-ons. + * + * This location should point either to a XPI, or a directory in a local build. + */ +class BuiltInLocation extends DirectoryLocation { + /** + * Read the manifest of allowed add-ons and build a mapping between ID and URI + * for each. + * + * @returns {Map} + * A map of add-ons present in this location. + */ + readAddons() { + let addons = new Map(); + + let manifest; + try { + let url = Services.io.newURI(BUILT_IN_ADDONS_URI); + let data = Cu.readUTF8URI(url); + manifest = JSON.parse(data); + } catch (e) { + logger.warn("List of valid built-in add-ons could not be parsed.", e); + return addons; + } + + if (!("system" in manifest)) { + logger.warn("No list of valid system add-ons found."); + return addons; + } + + for (let id of manifest.system) { + let file = this.dir.clone(); + file.append(`${id}.xpi`); + + // Only attempt to load unpacked directory if unofficial build. + if (!AppConstants.MOZILLA_OFFICIAL && !file.exists()) { + file = this.dir.clone(); + file.append(`${id}`); + } + + addons.set(id, file); + } + + return addons; + } + + get isSystem() { + return true; + } +} + +/** + * An object which identifies a directory install location for system add-ons + * updates. + */ +class SystemAddonLocation extends DirectoryLocation { + /** + * The location consists of a directory which contains the add-ons installed. + * + * @param {string} name + * The string identifier for the install location. + * @param {nsIFile} dir + * The directory for the install location. + * @param {integer} scope + * The scope of add-ons installed in this location. + * @param {boolean} resetSet + * True to throw away the current add-on set + */ + constructor(name, dir, scope, resetSet) { + let addonSet = SystemAddonLocation._loadAddonSet(); + let directory = null; + + // The system add-on update directory is stored in a pref. + // Therefore, this is looked up before calling the + // constructor on the superclass. + if (addonSet.directory) { + directory = getFile(addonSet.directory, dir); + logger.info(`SystemAddonLocation scanning directory ${directory.path}`); + } else { + logger.info("SystemAddonLocation directory is missing"); + } + + super(name, directory, scope, false); + + this._addonSet = addonSet; + this._baseDir = dir; + + if (resetSet) { + this.installer.resetAddonSet(); + } + } + + makeInstaller() { + if (this.locked) { + return null; + } + return new XPIInstall.SystemAddonInstaller(this); + } + + /** + * Reads the current set of system add-ons + * + * @returns {Object} + */ + static _loadAddonSet() { + try { + let setStr = Services.prefs.getStringPref(PREF_SYSTEM_ADDON_SET, null); + if (setStr) { + let addonSet = JSON.parse(setStr); + if ((typeof addonSet == "object") && addonSet.schema == 1) { + return addonSet; + } + } + } catch (e) { + logger.error("Malformed system add-on set, resetting."); + } + + return { schema: 1, addons: {} }; + } + + readAddons() { + // Updated system add-ons are ignored in safe mode + if (Services.appinfo.inSafeMode) { + return new Map(); + } + + let addons = super.readAddons(); + + // Strip out any unexpected add-ons from the list + for (let id of addons.keys()) { + if (!(id in this._addonSet.addons)) { + addons.delete(id); + } + } + + return addons; + } + + /** + * Tests whether updated system add-ons are expected. + * + * @returns {boolean} + */ + isActive() { + return this.dir != null; + } + + get isSystem() { + return true; + } +} + +/** + * An object that identifies a registry install location for add-ons. The location + * consists of a registry key which contains string values mapping ID to the + * path where an add-on is installed + * + */ +class WinRegLocation extends XPIStateLocation { + /** + * @param {string} name + * The string identifier for the install location. + * @param {integer} rootKey + * The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey). + * @param {integer} scope + * The scope of add-ons installed in this location. + */ + constructor(name, rootKey, scope) { + super(name, undefined, scope); + + this.locked = true; + this._rootKey = rootKey; + } + + /** + * Retrieves the path of this Application's data key in the registry. + */ + get _appKeyPath() { + let appVendor = Services.appinfo.vendor; + let appName = Services.appinfo.name; + + // XXX Thunderbird doesn't specify a vendor string + if (appVendor == "" && AppConstants.MOZ_APP_NAME == "thunderbird") + appVendor = "Mozilla"; + + return `SOFTWARE\\${appVendor}\\${appName}`; + } + + /** + * Read the registry and build a mapping between ID and path for each + * installed add-on. + * + * @returns {Map} + * A map of add-ons in this location. + */ + readAddons() { + let addons = new Map(); + + let path = `${this._appKeyPath}\\Extensions`; + let key = Cc["@mozilla.org/windows-registry-key;1"].createInstance(Ci.nsIWindowsRegKey); + + // Reading the registry may throw an exception, and that's ok. In error + // cases, we just leave ourselves in the empty state. + try { + key.open(this._rootKey, path, Ci.nsIWindowsRegKey.ACCESS_READ); + } catch (e) { + return addons; + } + + try { + let count = key.valueCount; + for (let i = 0; i < count; ++i) { + let id = key.getValueName(i); + let file = new nsIFile(key.readStringValue(id)); + if (!file.exists()) { + logger.warn(`Ignoring missing add-on in ${file.path}`); + continue; + } + + addons.set(id, file); + } + } finally { + key.close(); + } + + return addons; + } } /** * Keeps track of the state of XPI add-ons on the file system. */ var XPIStates = { - // Map(location name -> Map(add-on ID -> XPIState)) - db: null, + // Map(location-name -> XPIStateLocation) + db: new Map(), _jsonFile: null, @@ -913,10 +1394,8 @@ var XPIStates = { get size() { let count = 0; - if (this.db) { - for (let location of this.db.values()) { - count += location.size; - } + for (let location of this.locations()) { + count += location.size; } return count; }, @@ -1008,45 +1487,41 @@ var XPIStates = { * @returns {boolean} * True if anything has changed. */ - getInstallState(ignoreSideloads = true) { - if (!this.db) { - this.db = new Map(); - } - + scanForChanges(ignoreSideloads = true) { let oldState = this.initialStateData || this.loadExtensionState(); this.initialStateData = oldState; let changed = false; let oldLocations = new Set(Object.keys(oldState)); - for (let location of XPIProvider.installLocations) { - oldLocations.delete(location.name); + for (let loc of XPIStates.locations()) { + oldLocations.delete(loc.name); - // The results of scanning this location. - let loc = this.getLocation(location.name, location.path || null, - oldState[location.name] || undefined); + if (oldState[loc.name]) { + loc.restore(oldState[loc.name]); + } changed = changed || loc.changed; // Don't bother checking scopes where we don't accept side-loads. - if (ignoreSideloads && !(location.scope & gStartupScanScopes)) { + if (ignoreSideloads && !(loc.scope & gStartupScanScopes)) { continue; } - if (location.name == KEY_APP_TEMPORARY) { + if (loc.isTemporary) { continue; } let knownIds = new Set(loc.keys()); - for (let [id, file] of location.getAddonLocations(true)) { + for (let [id, file] of loc.readAddons()) { knownIds.delete(id); let xpiState = loc.get(id); if (!xpiState) { - logger.debug("New add-on ${id} in ${location}", {id, location: location.name}); + logger.debug("New add-on ${id} in ${loc}", {id, loc: loc.name}); changed = true; xpiState = loc.addFile(id, file); - if (!location.isSystem) { + if (!loc.isSystem) { this.sideLoadedAddons.set(id, xpiState); } } else { @@ -1056,12 +1531,12 @@ var XPIStates = { if (addonChanged) { changed = true; - logger.debug("Changed add-on ${id} in ${location}", {id, location: location.name}); + logger.debug("Changed add-on ${id} in ${loc}", {id, loc: loc.name}); } else { - logger.debug("Existing add-on ${id} in ${location}", {id, location: location.name}); + logger.debug("Existing add-on ${id} in ${loc}", {id, loc: loc.name}); } } - XPIProvider.setTelemetry(id, "location", location.name); + XPIProvider.setTelemetry(id, "location", loc.name); } // Anything left behind in oldState was removed from the file system. @@ -1075,41 +1550,39 @@ var XPIStates = { // was removed from the browser configuration. changed = changed || oldLocations.size > 0; - logger.debug("getInstallState changed: ${rv}, state: ${state}", + logger.debug("scanForChanges changed: ${rv}, state: ${state}", {rv: changed, state: this.db}); return changed; }, + locations() { + return this.db.values(); + }, + + /** + * @param {string} name + * The location name. + * @param {XPIStateLocation} location + * The location object. + */ + addLocation(name, location) { + if (this.db.has(name)) { + throw new Error(`Trying to add duplicate location: ${name}`); + } + this.db.set(name, location); + }, + /** * Get the Map of XPI states for a particular location. * * @param {string} name * The name of the install location. - * @param {string?} [path] - * The expected path of the location, if known. - * @param {Object?} [saved] - * The saved data for the location, as read from the - * addonStartup.json file. * * @returns {XPIStateLocation?} * (id -> XPIState) or null if there are no add-ons in the location. */ - getLocation(name, path, saved) { - let location = this.db.get(name); - - if (path && location && location.path != path) { - location = null; - saved = null; - } - - if (!location || (path && location.path != path)) { - let loc = XPIProvider.installLocationsByName[name]; - if (loc) { - location = new XPIStateLocation(name, path || loc.path || null, saved); - this.db.set(name, location); - } - } - return location; + getLocation(name) { + return this.db.get(name); }, /** @@ -1143,7 +1616,7 @@ var XPIStates = { findAddon(aId, aFilter = location => true) { // Fortunately the Map iterator returns in order of insertion, which is // also our highest -> lowest priority order. - for (let location of this.db.values()) { + for (let location of this.locations()) { if (!aFilter(location)) { continue; } @@ -1158,7 +1631,7 @@ var XPIStates = { * Iterates over the list of all enabled add-ons in any location. */ * enabledAddons() { - for (let location of this.db.values()) { + for (let location of this.locations()) { for (let entry of location.values()) { if (entry.enabled) { yield entry; @@ -1174,8 +1647,7 @@ var XPIStates = { * The add-on to add. */ addAddon(aAddon) { - let location = this.getLocation(aAddon._installLocation.name); - location.addAddon(aAddon); + aAddon.location.addAddon(aAddon); }, /** @@ -1197,7 +1669,7 @@ var XPIStates = { toJSON() { let data = {}; for (let [key, loc] of this.db.entries()) { - if (key != TemporaryInstallLocation.name && (loc.size || loc.hasStaged)) { + if (!loc.isTemporary && (loc.size || loc.hasStaged)) { data[key] = loc; } } @@ -1214,13 +1686,10 @@ var XPIStates = { * */ removeAddon(aLocation, aId) { - logger.debug("Removing XPIState for " + aLocation + ":" + aId); + logger.debug(`Removing XPIState for ${aLocation}: ${aId}`); let location = this.db.get(aLocation); if (location) { - location.delete(aId); - if (location.size == 0) { - this.db.delete(aLocation); - } + location.removeAddon(aId); this.save(); } }, @@ -1340,15 +1809,14 @@ class BootstrapScope { } } - let installLocation = addon._installLocation || null; let params = { id: addon.id, version: addon.version, installPath: this.file.clone(), resourceURI: getURIForResourceInFile(this.file, ""), signedState: addon.signedState, - temporarilyInstalled: installLocation == TemporaryInstallLocation, - builtIn: installLocation instanceof BuiltInInstallLocation, + temporarilyInstalled: addon.location.isTemporary, + builtIn: addon.location instanceof BuiltInLocation, }; if (aMethod == "startup" && addon.startupData) { @@ -1638,10 +2106,6 @@ var XPIProvider = { BOOTSTRAP_REASONS: Object.freeze(BOOTSTRAP_REASONS), - // An array of known install locations - installLocations: null, - // A dictionary of known install locations by name - installLocationsByName: null, // A Map of active addons to their bootstrapScope by ID activeAddons: new Map(), // True if the platform could have activated extensions @@ -1763,39 +2227,36 @@ var XPIProvider = { }, setupInstallLocations(aAppChanged) { - function DirectoryLocation(aName, aScope, aKey, aPaths, aLocked) { + function DirectoryLoc(aName, aScope, aKey, aPaths, aLocked) { try { var dir = FileUtils.getDir(aKey, aPaths); } catch (e) { return null; } - if (aLocked) { - return new DirectoryInstallLocation(aName, dir, aScope); - } - return new MutableDirectoryInstallLocation(aName, dir, aScope); + return new DirectoryLocation(aName, dir, aScope, aLocked); } - function BuiltInLocation(name, scope, key, paths) { + function BuiltInLoc(name, scope, key, paths) { try { var dir = FileUtils.getDir(key, paths); } catch (e) { return null; } - return new BuiltInInstallLocation(name, dir, scope); + return new BuiltInLocation(name, dir, scope); } - function SystemLocation(aName, aScope, aKey, aPaths) { + function SystemLoc(aName, aScope, aKey, aPaths) { try { var dir = FileUtils.getDir(aKey, aPaths); } catch (e) { return null; } - return new SystemAddonInstallLocation(aName, dir, aScope, aAppChanged !== false); + return new SystemAddonLocation(aName, dir, aScope, aAppChanged !== false); } - function RegistryLocation(aName, aScope, aKey) { + function RegistryLoc(aName, aScope, aKey) { if ("nsIWindowsRegKey" in Ci) { - return new WinRegInstallLocation(aName, Ci.nsIWindowsRegKey[aKey], aScope); + return new WinRegLocation(aName, Ci.nsIWindowsRegKey[aKey], aScope); } } @@ -1809,43 +2270,40 @@ var XPIProvider = { let locations = [ [() => TemporaryInstallLocation, TemporaryInstallLocation.name, null], - [DirectoryLocation, KEY_APP_PROFILE, AddonManager.SCOPE_PROFILE, + [DirectoryLoc, KEY_APP_PROFILE, AddonManager.SCOPE_PROFILE, KEY_PROFILEDIR, [DIR_EXTENSIONS], false], - [SystemLocation, KEY_APP_SYSTEM_ADDONS, AddonManager.SCOPE_PROFILE, + [SystemLoc, KEY_APP_SYSTEM_ADDONS, AddonManager.SCOPE_PROFILE, KEY_PROFILEDIR, [DIR_SYSTEM_ADDONS]], - [BuiltInLocation, KEY_APP_SYSTEM_DEFAULTS, AddonManager.SCOPE_PROFILE, + [BuiltInLoc, KEY_APP_SYSTEM_DEFAULTS, AddonManager.SCOPE_PROFILE, KEY_APP_FEATURES, []], - [DirectoryLocation, KEY_APP_SYSTEM_USER, AddonManager.SCOPE_USER, + [DirectoryLoc, KEY_APP_SYSTEM_USER, AddonManager.SCOPE_USER, "XREUSysExt", [Services.appinfo.ID], true], - [RegistryLocation, "winreg-app-user", AddonManager.SCOPE_USER, + [RegistryLoc, "winreg-app-user", AddonManager.SCOPE_USER, "ROOT_KEY_CURRENT_USER"], - [DirectoryLocation, KEY_APP_GLOBAL, AddonManager.SCOPE_APPLICATION, + [DirectoryLoc, KEY_APP_GLOBAL, AddonManager.SCOPE_APPLICATION, KEY_ADDON_APP_DIR, [DIR_EXTENSIONS], true], - [DirectoryLocation, KEY_APP_SYSTEM_SHARE, AddonManager.SCOPE_SYSTEM, + [DirectoryLoc, KEY_APP_SYSTEM_SHARE, AddonManager.SCOPE_SYSTEM, "XRESysSExtPD", [Services.appinfo.ID], true], - [DirectoryLocation, KEY_APP_SYSTEM_LOCAL, AddonManager.SCOPE_SYSTEM, + [DirectoryLoc, KEY_APP_SYSTEM_LOCAL, AddonManager.SCOPE_SYSTEM, "XRESysLExtPD", [Services.appinfo.ID], true], - [RegistryLocation, "winreg-app-global", AddonManager.SCOPE_SYSTEM, + [RegistryLoc, "winreg-app-global", AddonManager.SCOPE_SYSTEM, "ROOT_KEY_LOCAL_MACHINE"], ]; - this.installLocations = []; - this.installLocationsByName = {}; for (let [constructor, name, scope, ...args] of locations) { if (!scope || enabledScopes & scope) { try { let loc = constructor(name, scope, ...args); if (loc) { - this.installLocations.push(loc); - this.installLocationsByName[name] = loc; + XPIStates.addLocation(name, loc); } } catch (e) { logger.warn(`Failed to add ${constructor.name} install location ${name}`, e); @@ -1957,10 +2415,9 @@ var XPIProvider = { let reason = BOOTSTRAP_REASONS.APP_SHUTDOWN; if (addon._pendingDisable) { reason = BOOTSTRAP_REASONS.ADDON_DISABLE; - } else if (addon.location.name == KEY_APP_TEMPORARY) { + } else if (addon.location.isTemporary) { reason = BOOTSTRAP_REASONS.ADDON_UNINSTALL; - let existing = XPIStates.findAddon(addon.id, loc => - loc.name != TemporaryInstallLocation.name); + let existing = XPIStates.findAddon(addon.id, loc => !loc.isTemporary); if (existing) { reason = XPIInstall.newVersionReason(addon.version, existing.version); } @@ -2059,9 +2516,6 @@ var XPIProvider = { await XPIDatabase.asyncLoadDB(); } - this.installLocations = null; - this.installLocationsByName = null; - // This is needed to allow xpcshell tests to simulate a restart this.extensionsActive = false; @@ -2069,28 +2523,26 @@ var XPIProvider = { }, cleanupTemporaryAddons() { - let tempLocation = XPIStates.getLocation(TemporaryInstallLocation.name); - if (tempLocation) { - for (let [id, addon] of tempLocation.entries()) { - tempLocation.delete(id); + let tempLocation = TemporaryInstallLocation; + for (let [id, addon] of tempLocation.entries()) { + tempLocation.delete(id); - let bootstrap = BootstrapScope.get(addon); - let existing = XPIStates.findAddon(id, loc => loc != tempLocation); + let bootstrap = BootstrapScope.get(addon); + let existing = XPIStates.findAddon(id, loc => !loc.isTemporary); - let cleanup = () => { - TemporaryInstallLocation.uninstallAddon(id); - XPIStates.removeAddon(TemporaryInstallLocation.name, id); - }; + let cleanup = () => { + tempLocation.installer.uninstallAddon(id); + tempLocation.removeAddon(id); + }; - if (existing) { - bootstrap.update(existing, false, () => { - cleanup(); - XPIDatabase.makeAddonLocationVisible(id, existing.location.name); - }); - } else { - bootstrap.uninstall(); + if (existing) { + bootstrap.update(existing, false, () => { cleanup(); - } + XPIDatabase.makeAddonLocationVisible(id, existing.location); + }); + } else { + bootstrap.uninstall(); + cleanup(); } } }, @@ -2131,31 +2583,29 @@ var XPIProvider = { */ processPendingFileChanges(aManifests) { let changed = false; - for (let location of this.installLocations) { - aManifests[location.name] = {}; + for (let loc of XPIStates.locations()) { + aManifests[loc.name] = {}; // We can't install or uninstall anything in locked locations - if (location.locked) { + if (loc.locked) { continue; } - let state = XPIStates.getLocation(location.name); - let cleanNames = []; let promises = []; - for (let [id, metadata] of state.getStagedAddons()) { - state.unstageAddon(id); + for (let [id, metadata] of loc.getStagedAddons()) { + loc.unstageAddon(id); - aManifests[location.name][id] = null; + aManifests[loc.name][id] = null; promises.push( - XPIInstall.installStagedAddon(id, metadata, location).then( + XPIInstall.installStagedAddon(id, metadata, loc).then( addon => { - aManifests[location.name][id] = addon; + aManifests[loc.name][id] = addon; }, error => { - delete aManifests[location.name][id]; + delete aManifests[loc.name][id]; cleanNames.push(`${id}.xpi`); - logger.error(`Failed to install staged add-on ${id} in ${location.name}`, + logger.error(`Failed to install staged add-on ${id} in ${loc.name}`, error); })); } @@ -2167,7 +2617,7 @@ var XPIProvider = { try { if (cleanNames.length) { - location.cleanStagingDir(cleanNames); + loc.installer.cleanStagingDir(cleanNames); } } catch (e) { // Non-critical, just saves some perf on startup if we clean this up. @@ -2202,13 +2652,12 @@ var XPIProvider = { } let changed = false; - let profileLocation = this.installLocationsByName[KEY_APP_PROFILE]; + let profileLocation = XPIStates.getLocation(KEY_APP_PROFILE); let entries = distroDir.directoryEntries .QueryInterface(Ci.nsIDirectoryEnumerator); let entry; while ((entry = entries.nextFile)) { - let id = entry.leafName; if (id.endsWith(".xpi")) { id = id.slice(0, -4); @@ -2242,7 +2691,7 @@ var XPIProvider = { changed = true; } } catch (e) { - logger.error("Failed to install distribution add-on " + entry.path, e); + logger.error(`Failed to install distribution add-on ${entry.path}`, e); } } @@ -2300,7 +2749,7 @@ var XPIProvider = { updateReasons.push("appChanged"); } - let installChanged = XPIStates.getInstallState(aAppChanged === false); + let installChanged = XPIStates.scanForChanges(aAppChanged === false); if (installChanged) { updateReasons.push("directoryState"); } @@ -2400,7 +2849,7 @@ var XPIProvider = { * @returns {Promise>} */ async getNewSideloads() { - if (XPIStates.getInstallState(false)) { + if (XPIStates.scanForChanges(false)) { // We detected changes. Update the database to account for them. await XPIDatabase.asyncLoadDB(false); XPIDatabaseReconcile.processFileChanges({}, false); @@ -2560,7 +3009,7 @@ var XPIProvider = { if (aTypes && !aTypes.includes(addon.type)) { continue; } - let location = this.installLocationsByName[addon.location.name]; + let {location} = addon; let scope, isSystem; if (location) { ({scope, isSystem} = location); @@ -2681,528 +3130,24 @@ var XPIProvider = { }, }; -for (let meth of ["cancelUninstallAddon", "getInstallForFile", - "getInstallForURL", "getInstallsByTypes", +for (let meth of ["getInstallForFile", "getInstallForURL", "getInstallsByTypes", "installTemporaryAddon", "isInstallAllowed", - "isInstallEnabled", "uninstallAddon", - "updateSystemAddons"]) { + "isInstallEnabled", "updateSystemAddons"]) { XPIProvider[meth] = function() { return XPIInstall[meth](...arguments); }; } -function forwardInstallMethods(cls, methods) { - let {prototype} = cls; - for (let meth of methods) { - prototype[meth] = function() { - return XPIInstall[cls.name].prototype[meth].apply(this, arguments); - }; - } -} - -/** - * An object which identifies a directory install location for add-ons. The - * location consists of a directory which contains the add-ons installed in the - * location. - * - */ -class DirectoryInstallLocation { - /** - * Each add-on installed in the location is either a directory containing the - * add-on's files or a text file containing an absolute path to the directory - * containing the add-ons files. The directory or text file must have the same - * name as the add-on's ID. - * - * @param {string} aName - * The string identifier for the install location - * @param {nsIFile} aDirectory - * The nsIFile directory for the install location - * @param {integer} aScope - * The scope of add-ons installed in this location - */ - constructor(aName, aDirectory, aScope) { - this._name = aName; - this.locked = true; - this._directory = aDirectory; - this._scope = aScope; - this._IDToFileMap = {}; - this._linkedAddons = []; - - this.isSystem = (aName == KEY_APP_SYSTEM_ADDONS || - aName == KEY_APP_SYSTEM_DEFAULTS); - - if (!aDirectory || !aDirectory.exists()) - return; - if (!aDirectory.isDirectory()) - throw new Error("Location must be a directory."); - - this.initialized = false; - } - - get path() { - return this._directory && this._directory.path; - } - - /** - * Reads a directory linked to in a file. - * - * @param {nsIFile} aFile - * The file containing the directory path - * @returns {nsIFile?} - * An nsIFile object representing the linked directory, or null - * on error. - */ - _readDirectoryFromFile(aFile) { - let linkedDirectory; - if (aFile.isSymlink()) { - linkedDirectory = aFile.clone(); - try { - linkedDirectory.normalize(); - } catch (e) { - logger.warn("Symbolic link " + aFile.path + " points to a path" + - " which does not exist"); - return null; - } - } else { - let fis = new FileInputStream(aFile, -1, -1, false); - let line = { value: "" }; - fis.QueryInterface(Ci.nsILineInputStream).readLine(line); - fis.close(); - if (line.value) { - linkedDirectory = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); - try { - linkedDirectory.initWithPath(line.value); - } catch (e) { - linkedDirectory.setRelativeDescriptor(aFile.parent, line.value); - } - } - } - - if (linkedDirectory) { - if (!linkedDirectory.exists()) { - logger.warn("File pointer " + aFile.path + " points to " + linkedDirectory.path + - " which does not exist"); - return null; - } - - if (!linkedDirectory.isDirectory()) { - logger.warn("File pointer " + aFile.path + " points to " + linkedDirectory.path + - " which is not a directory"); - return null; - } - - return linkedDirectory; - } - - logger.warn("File pointer " + aFile.path + " does not contain a path"); - return null; - } - - /** - * Finds all the add-ons installed in this location. - * - * @param {boolean} [rescan = false] - * True if the directory should be re-scanned, even if it has - * already been initialized. - */ - _readAddons(rescan = false) { - if ((this.initialized && !rescan) || !this._directory) { - return; - } - this.initialized = true; - - // Use a snapshot of the directory contents to avoid possible issues with - // iterating over a directory while removing files from it (the YAFFS2 - // embedded filesystem has this issue, see bug 772238). - let entries = getDirectoryEntries(this._directory); - for (let entry of entries) { - let id = entry.leafName; - - if (id == DIR_STAGE || id == DIR_TRASH) - continue; - - let isFile = id.toLowerCase().endsWith(".xpi"); - if (isFile) { - id = id.substring(0, id.length - 4); - } - - if (!gIDTest.test(id)) { - logger.debug("Ignoring file entry whose name is not a valid add-on ID: " + - entry.path); - continue; - } - - if (!isFile && (entry.isFile() || entry.isSymlink())) { - let newEntry = this._readDirectoryFromFile(entry); - if (!newEntry) { - logger.debug("Deleting stale pointer file " + entry.path); - try { - entry.remove(true); - } catch (e) { - logger.warn("Failed to remove stale pointer file " + entry.path, e); - // Failing to remove the stale pointer file is ignorable - } - continue; - } - - entry = newEntry; - this._linkedAddons.push(id); - } - - this._IDToFileMap[id] = entry; - } - } - - /** - * Gets the name of this install location. - */ - get name() { - return this._name; - } - - /** - * Gets the scope of this install location. - */ - get scope() { - return this._scope; - } - - /** - * Gets an map of files for add-ons installed in this location. - * - * @param {boolean} [rescan = false] - * True if the directory should be re-scanned, even if it has - * already been initialized. - * - * @returns {Map} - * A map of all add-ons in the location, with each add-on's ID - * as the key and an nsIFile for its location as the value. - */ - getAddonLocations(rescan = false) { - this._readAddons(rescan); - - let locations = new Map(); - for (let id in this._IDToFileMap) { - locations.set(id, this._IDToFileMap[id].clone()); - } - return locations; - } - - /** - * Gets the directory that the add-on with the given ID is installed in. - * - * @param {string} aId - * The ID of the add-on - * @returns {nsIFile} - * @throws if the ID does not match any of the add-ons installed - */ - getLocationForID(aId) { - if (!(aId in this._IDToFileMap)) - this._readAddons(); - - if (aId in this._IDToFileMap) - return this._IDToFileMap[aId].clone(); - throw new Error("Unknown add-on ID " + aId); - } - - /** - * Returns true if the given addon was installed in this location by a text - * file pointing to its real path. - * - * @param {string} aId - * The ID of the addon - * @returns {boolean} - */ - isLinkedAddon(aId) { - return this._linkedAddons.includes(aId); - } -} - -/** - * An extension of DirectoryInstallLocation which adds methods to installing - * and removing add-ons from the directory at runtime. - */ -class MutableDirectoryInstallLocation extends DirectoryInstallLocation { - /** - * @param {string} aName - * The string identifier for the install location - * @param {nsIFile} aDirectory - * The nsIFile directory for the install location - * @param {integer} aScope - * The scope of add-ons installed in this location - */ - constructor(aName, aDirectory, aScope) { - super(aName, aDirectory, aScope); - - this.locked = false; - this._stagingDirLock = 0; - } -} -forwardInstallMethods(MutableDirectoryInstallLocation, - ["cleanStagingDir", "getStagingDir", "getTrashDir", - "installAddon", "releaseStagingDir", "requestStagingDir", - "uninstallAddon"]); - -/** - * An object which identifies a built-in install location for add-ons, such - * as default system add-ons. - * - * This location should point either to a XPI, or a directory in a local build. - */ -class BuiltInInstallLocation extends DirectoryInstallLocation { - /** - * Read the manifest of allowed add-ons and build a mapping between ID and URI - * for each. - */ - _readAddons() { - let manifest; - try { - let url = Services.io.newURI(BUILT_IN_ADDONS_URI); - let data = Cu.readUTF8URI(url); - manifest = JSON.parse(data); - } catch (e) { - logger.warn("List of valid built-in add-ons could not be parsed.", e); - return; - } - - if (!("system" in manifest)) { - logger.warn("No list of valid system add-ons found."); - return; - } - - for (let id of manifest.system) { - let file = new FileUtils.File(this._directory.path); - file.append(`${id}.xpi`); - - // Only attempt to load unpacked directory if unofficial build. - if (!AppConstants.MOZILLA_OFFICIAL && !file.exists()) { - file = new FileUtils.File(this._directory.path); - file.append(`${id}`); - } - - this._IDToFileMap[id] = file; - } - } -} - -/** - * An object which identifies a directory install location for system add-ons - * updates. - */ -class SystemAddonInstallLocation extends MutableDirectoryInstallLocation { - /** - * The location consists of a directory which contains the add-ons installed. - * - * @param {string} aName - * The string identifier for the install location - * @param {nsIFile} aDirectory - * The nsIFile directory for the install location - * @param {integer} aScope - * The scope of add-ons installed in this location - * @param {boolean} aResetSet - * True to throw away the current add-on set - */ - constructor(aName, aDirectory, aScope, aResetSet) { - let addonSet = SystemAddonInstallLocation._loadAddonSet(); - let directory = null; - - // The system add-on update directory is stored in a pref. - // Therefore, this is looked up before calling the - // constructor on the superclass. - if (addonSet.directory) { - directory = getFile(addonSet.directory, aDirectory); - logger.info("SystemAddonInstallLocation scanning directory " + directory.path); - } else { - logger.info("SystemAddonInstallLocation directory is missing"); - } - - super(aName, directory, aScope); - - this._addonSet = addonSet; - this._baseDir = aDirectory; - this._nextDir = null; - this._directory = directory; - - this._stagingDirLock = 0; - - if (aResetSet) { - this.resetAddonSet(); - } - - this.locked = false; - } - - /** - * Reads the current set of system add-ons - * - * @returns {Object} - */ - static _loadAddonSet() { - try { - let setStr = Services.prefs.getStringPref(PREF_SYSTEM_ADDON_SET, null); - if (setStr) { - let addonSet = JSON.parse(setStr); - if ((typeof addonSet == "object") && addonSet.schema == 1) { - return addonSet; - } - } - } catch (e) { - logger.error("Malformed system add-on set, resetting."); - } - - return { schema: 1, addons: {} }; - } - - getAddonLocations() { - // Updated system add-ons are ignored in safe mode - if (Services.appinfo.inSafeMode) { - return new Map(); - } - - let addons = super.getAddonLocations(); - - // Strip out any unexpected add-ons from the list - for (let id of addons.keys()) { - if (!(id in this._addonSet.addons)) { - addons.delete(id); - } - } - - return addons; - } - - /** - * Tests whether updated system add-ons are expected. - * - * @returns {boolean} - */ - isActive() { - return this._directory != null; - } -} - -forwardInstallMethods(SystemAddonInstallLocation, - ["cleanDirectories", "cleanStagingDir", "getStagingDir", - "getTrashDir", "installAddon", "installAddon", - "installAddonSet", "isValid", "isValidAddon", - "releaseStagingDir", "requestStagingDir", - "resetAddonSet", "resumeAddonSet", "uninstallAddon", - "uninstallAddon"]); - -/** An object which identifies an install location for temporary add-ons. - */ -const TemporaryInstallLocation = { locked: false, name: KEY_APP_TEMPORARY, - scope: AddonManager.SCOPE_TEMPORARY, - getAddonLocations: () => [], isLinkedAddon: () => false, installAddon: - () => {}, uninstallAddon: (aAddon) => {}, getStagingDir: () => {}, -}; - -/** - * An object that identifies a registry install location for add-ons. The location - * consists of a registry key which contains string values mapping ID to the - * path where an add-on is installed - * - */ -class WinRegInstallLocation extends DirectoryInstallLocation { - /** - * @param {string} aName - * The string identifier of this Install Location. - * @param {integer} aRootKey - * The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey). - * @param {integer} aScope - * The scope of add-ons installed in this location - */ - constructor(aName, aRootKey, aScope) { - super(aName, undefined, aScope); - - this.locked = true; - this._name = aName; - this._rootKey = aRootKey; - this._scope = aScope; - this._IDToFileMap = {}; - - let path = this._appKeyPath + "\\Extensions"; - let key = Cc["@mozilla.org/windows-registry-key;1"].createInstance(Ci.nsIWindowsRegKey); - - // Reading the registry may throw an exception, and that's ok. In error - // cases, we just leave ourselves in the empty state. - try { - key.open(this._rootKey, path, Ci.nsIWindowsRegKey.ACCESS_READ); - } catch (e) { - return; - } - - this._readAddons(key); - key.close(); - } - - /** - * Retrieves the path of this Application's data key in the registry. - */ - get _appKeyPath() { - let appVendor = Services.appinfo.vendor; - let appName = Services.appinfo.name; - - // XXX Thunderbird doesn't specify a vendor string - if (AppConstants.MOZ_APP_NAME == "thunderbird" && appVendor == "") - appVendor = "Mozilla"; - - // XULRunner-based apps may intentionally not specify a vendor - if (appVendor != "") - appVendor += "\\"; - - return "SOFTWARE\\" + appVendor + appName; - } - - /** - * Read the registry and build a mapping between ID and path for each - * installed add-on. - * - * @param {nsIWindowsRegKey} aKey - * The key that contains the ID to path mapping - */ - _readAddons(aKey) { - let count = aKey.valueCount; - for (let i = 0; i < count; ++i) { - let id = aKey.getValueName(i); - - let file = new nsIFile(aKey.readStringValue(id)); - - if (!file.exists()) { - logger.warn("Ignoring missing add-on in " + file.path); - continue; - } - - this._IDToFileMap[id] = file; - } - } - - /** - * Gets the name of this install location. - */ - get name() { - return this._name; - } - - /* - * @see DirectoryInstallLocation - */ - isLinkedAddon(aId) { - return true; - } -} - var XPIInternal = { BOOTSTRAP_REASONS, BootstrapScope, DB_SCHEMA, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, - KEY_APP_TEMPORARY, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, SIGNED_TYPES, - SystemAddonInstallLocation, + SystemAddonLocation, TEMPORARY_ADDON_SUFFIX, TOOLKIT_ID, TemporaryInstallLocation, diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js b/toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js index 37e7c0aab390..6474bbbd494e 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_XPIStates.js @@ -65,7 +65,7 @@ function checkChange(XS, aPath, aChange) { lastTimestamp += 10000; info("Touching file " + aPath.path + " with " + lastTimestamp); aPath.lastModifiedTime = lastTimestamp; - Assert.equal(XS.getInstallState(), aChange); + Assert.equal(XS.scanForChanges(), aChange); // Save the pref so we don't detect this change again XS.save(); } @@ -95,7 +95,7 @@ add_task(async function detect_touches() { let XS = getXS(); // Should be no changes detected here, because everything should start out up-to-date. - Assert.ok(!XS.getInstallState()); + Assert.ok(!XS.scanForChanges()); let states = XS.getLocation("app-profile"); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_delay_update_webextension.js b/toolkit/mozapps/extensions/test/xpcshell/test_delay_update_webextension.js index 83f7085ce79f..45a9c1a1fc49 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_delay_update_webextension.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_delay_update_webextension.js @@ -16,7 +16,7 @@ if (AppConstants.platform == "win" && AppConstants.DEBUG) { Services.prefs.setBoolPref("extensions.webextensions.remote", false); } -PromiseTestUtils.expectUncaughtRejection(/Message manager disconnected/); +PromiseTestUtils.whitelistRejectionsGlobally(/Message manager disconnected/); /* globals browser*/ From f8aeb3dbfc6028b933bdbdb1b906e4e675f01b12 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 19 May 2018 14:03:50 -0700 Subject: [PATCH 4/7] Bug 1462223: Remove unnecessary/unused bootstrap scope setup code. r=aswan This also removes the workerbootstrap test extension, which is no longer used, and contains the last references to the Worker and ChromeWorker bootstrap globals. MozReview-Commit-ID: 8YWReXMqX5W --HG-- extra : rebase_source : b0aa59b2b5e6a08f4be803e828bd507f894e4a19 --- .../test/extensions/bootstrap/bootstrap.js | 138 ------------------ .../test/extensions/bootstrap/install.rdf | 31 ---- dom/workers/test/extensions/bootstrap/jar.mn | 3 - .../test/extensions/bootstrap/moz.build | 11 -- .../test/extensions/bootstrap/worker.js | 7 - .../workerbootstrap-test@mozilla.org.xpi | Bin 7202 -> 583 bytes dom/workers/test/extensions/moz.build | 7 - .../mozbuild/codecoverage/lcov_rewriter.py | 1 - .../test/codecoverage/test_lcov_rewrite.py | 5 - testing/mochitest/runrobocop.py | 1 - .../extensions/internal/XPIProvider.jsm | 22 +-- .../test/xpcshell/test_bootstrap_globals.js | 2 - 12 files changed, 2 insertions(+), 226 deletions(-) delete mode 100644 dom/workers/test/extensions/bootstrap/bootstrap.js delete mode 100644 dom/workers/test/extensions/bootstrap/install.rdf delete mode 100644 dom/workers/test/extensions/bootstrap/jar.mn delete mode 100644 dom/workers/test/extensions/bootstrap/worker.js delete mode 100644 dom/workers/test/extensions/moz.build diff --git a/dom/workers/test/extensions/bootstrap/bootstrap.js b/dom/workers/test/extensions/bootstrap/bootstrap.js deleted file mode 100644 index 594afa0846cb..000000000000 --- a/dom/workers/test/extensions/bootstrap/bootstrap.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -ChromeUtils.import("resource://gre/modules/Services.jsm"); -ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); - -function testForExpectedSymbols(stage, data) { - const expectedSymbols = [ "Worker", "ChromeWorker" ]; - for (var symbol of expectedSymbols) { - Services.prefs.setBoolPref("workertest.bootstrap." + stage + "." + symbol, - symbol in this); - } -} - -var gWorkerAndCallback = { - _ensureStarted: function() { - if (!this._worker) { - throw new Error("Not yet started!"); - } - }, - - start: function(data) { - if (!this._worker) { - this._worker = new Worker("chrome://workerbootstrap/content/worker.js"); - this._worker.onerror = function(event) { - Cu.reportError(event.message); - event.preventDefault(); - }; - } - }, - - stop: function() { - if (this._worker) { - this._worker.terminate(); - delete this._worker; - } - }, - - set callback(val) { - this._ensureStarted(); - var callback = val.QueryInterface(Ci.nsIObserver); - if (this._callback != callback) { - if (callback) { - this._worker.onmessage = function(event) { - callback.observe(this, event.type, event.data); - }; - this._worker.onerror = function(event) { - callback.observe(this, event.type, event.message); - event.preventDefault(); - }; - } - else { - this._worker.onmessage = null; - this._worker.onerror = null; - } - this._callback = callback; - } - }, - - get callback() { - return this._callback; - }, - - postMessage: function(data) { - this._ensureStarted(); - this._worker.postMessage(data); - }, - - terminate: function() { - this._ensureStarted(); - this._worker.terminate(); - delete this._callback; - } -}; - -function WorkerTestBootstrap() { -} -WorkerTestBootstrap.prototype = { - observe: function(subject, topic, data) { - - gWorkerAndCallback.callback = subject; - - switch (topic) { - case "postMessage": - gWorkerAndCallback.postMessage(data); - break; - - case "terminate": - gWorkerAndCallback.terminate(); - break; - - default: - throw new Error("Unknown worker command"); - } - }, - - QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]) -}; - -var gFactory = { - register: function() { - var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); - - var classID = Components.ID("{36b5df0b-8dcf-4aa2-9c45-c51d871295f9}"); - var description = "WorkerTestBootstrap"; - var contractID = "@mozilla.org/test/workertestbootstrap;1"; - var factory = XPCOMUtils._getFactory(WorkerTestBootstrap); - - registrar.registerFactory(classID, description, contractID, factory); - - this.unregister = function() { - registrar.unregisterFactory(classID, factory); - delete this.unregister; - }; - } -}; - -function install(data, reason) { - testForExpectedSymbols("install"); -} - -function startup(data, reason) { - testForExpectedSymbols("startup"); - gFactory.register(); - gWorkerAndCallback.start(data); -} - -function shutdown(data, reason) { - testForExpectedSymbols("shutdown"); - gWorkerAndCallback.stop(); - gFactory.unregister(); -} - -function uninstall(data, reason) { - testForExpectedSymbols("uninstall"); -} diff --git a/dom/workers/test/extensions/bootstrap/install.rdf b/dom/workers/test/extensions/bootstrap/install.rdf deleted file mode 100644 index fdd9638cd527..000000000000 --- a/dom/workers/test/extensions/bootstrap/install.rdf +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - WorkerTestBootstrap - Worker functions for use in testing. - Mozilla - 2016.03.09 - workerbootstrap-test@mozilla.org - 2 - true - - - - {ec8030f7-c20a-464f-9b0e-13a3a9e97384} - 45.0 - * - - - - - - {aa3c5121-dab2-40e2-81ca-7ea25febc110} - 45.0 - * - - - - diff --git a/dom/workers/test/extensions/bootstrap/jar.mn b/dom/workers/test/extensions/bootstrap/jar.mn deleted file mode 100644 index a9c6251031e9..000000000000 --- a/dom/workers/test/extensions/bootstrap/jar.mn +++ /dev/null @@ -1,3 +0,0 @@ -workerbootstrap.jar: -% content workerbootstrap %content/ - content/worker.js (worker.js) diff --git a/dom/workers/test/extensions/bootstrap/moz.build b/dom/workers/test/extensions/bootstrap/moz.build index aec5c249c6dc..2bf7866ee0a1 100644 --- a/dom/workers/test/extensions/bootstrap/moz.build +++ b/dom/workers/test/extensions/bootstrap/moz.build @@ -4,17 +4,6 @@ # 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/. -XPI_NAME = 'workerbootstrap' - -JAR_MANIFESTS += ['jar.mn'] -USE_EXTENSION_MANIFEST = True -NO_JS_MANIFEST = True - -FINAL_TARGET_FILES += [ - 'bootstrap.js', - 'install.rdf', -] - TEST_HARNESS_FILES.testing.mochitest.extensions += [ 'workerbootstrap-test@mozilla.org.xpi', ] diff --git a/dom/workers/test/extensions/bootstrap/worker.js b/dom/workers/test/extensions/bootstrap/worker.js deleted file mode 100644 index 7346fc142c34..000000000000 --- a/dom/workers/test/extensions/bootstrap/worker.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -onmessage = function(event) { - postMessage(event.data); -} diff --git a/dom/workers/test/extensions/bootstrap/workerbootstrap-test@mozilla.org.xpi b/dom/workers/test/extensions/bootstrap/workerbootstrap-test@mozilla.org.xpi index 2dab975db0c8293b5e63a1e7a14bc632d253e890..5c9bc9e3abe8f85d1813c2b0d838ee343dbf9909 100644 GIT binary patch literal 583 zcmWIWW@Zs#W?6BOBJznYPOL7s(yft!JWAv3SIBrzvPuP7yLZ^*&E*#;te zK8xyAuTC*&%VAQww=L@J+UcvZ&L68>5_ZsSo94~1zu#xI966h{)OO95ISKd9RNPb8 zxzGBG>@U`1UTe1%PMh$f&o%Jx(|?RVJ2&0_(O&F4b=hST)!=ykT`tb&RHF9w~B3R+{N9l!*!+4 z>&$(zt+y6jWSYcf`Cj>|-AA$TMGsVX3YPQ!t~vF*Q(R}lv?xi{o4HP(TBgh^Z)Em= zccaAT@|?|&Ef2Q;*ZO>Drnt$wLIyY6nkwGrZua7|7G*^T$8%@p?i^2ivE)JG)(^_( zZZz#XEMe=xyw*;kXS!p@fA%A7PnN_cJ8~XBntSolCdG!$bDr;MDVm$#wg0rx^;dDL z^1T(GYAL$=9hg_}^I^~a`JId$KQFIl^}A5ay?+yT0N0Vtp#}50L*0z`Jmx*h%GhN- z^FamgoOMwdF4HE8uhzJ?t4sZf)mL+d?thE{-i%E4%(!Az1sJa&pwO_S5kw=!F&BdZ rNQ8lrL4u)Snh^WR_m>$P$~kz!G?X9U&B_K*!^jW|WLyCz7X}6Z)_3|} literal 7202 zcmbVx1yEec{`KGlcL)}OOOOoikPzJ6b&$c`0t5&a+!7pu1qc${-3c0fuB)1oK?2ag8;0FVJE&R|KoLfSo8bO68r3jnzPD)&lN zlvzq%oYlt86Krj5#Ny~=G@k(G8UIP#GiYo;7tRI@u{OCe>5dJE?lob+o_R5uXtYa> zhW=EC`dt}oUo)#p*1KoSps4@?WyTnqzRaPMagU*}lWpCN`k{tf=o{84y9QyX3mi4M z6|K5fxsXQ$ZYSCh&CxYXwEJlr1y&H5p{a5Fiob6l(PO|51No1-I1<7l#$L$L6r6Qf zY{+P4ut{1qlQM9rZ#?{_-TW!R!m%O)nX-+BPV8u~ShM)njg8^~SZ)dmNI4jOn{pI< z=XJP;L>#`eRplqrKf!{=Ab$=ToS_T;JwA%TZ+b`j?ix8s?9W^0`yRiA8E$~V(gz>3 zq$)NGrzy%~pkQnaVIMZ5=nGdcV1<+rk$ya%!qF17ms-du@KS=eGpNhB5nidf33sG) zhq;31dHxkZz!TDDWUraa&ab@2{YXsQe$1N_4d{UYVT{O!FJ+E zjD3CNgR$?P6WHKH5%hXv+9wrdEanO7IeTazVUZI&6&&e#nUoNS!q5GC(xxQE`NbtR zS)-EAP_vJAiwy>aU)P%!7%VEd0QhyY=?e%Vt)D1b4WR-C41QNsMd#G3m*i&s##O;s zd=7p{(!`GtXCXa~M3wVjJ4hpl_k^2)lRCkAL{%{4N+-Y*SSUYwOF9HL{P4S&lS08t zM#~}{N@o%Bm053T4=*IxF;^IzUn1TK3`U9G4_2K8k0b?I$bR>^@^&{$&VPYcj`Kvgz#OkvOJvB6d&=dHkn;PX8 z)szL@$CBT+%-boKET?iy5;3)f=+41}Su;Un2MAd4TC|(wDOTO_vtsXQrZY!tzVyR? zj}69l!hOw1?>h1>r$c1`>Frn&cz$N#vVBV@2k+&#bZ3-5JkVqF{~dFG!I6yri^EC#Z)KF zWkVMdi@wsc4ys^|IUY?R?>&rjoeyTS~coiz+XJ6y1>ZipNLb6P_jxGNM+-En^5bYWUo`2h=w zPVNlZ!@A!s*!2iLJ{$g%g0rBM(ABH<$APQ7BeG0uJK&~%q;AjZn%w!Q*6c~IW3Fa} z6hbwdO7ABelXz&sk06p)+OvopP!GGUPYA)!W&+ZN9IPTzF{MIT%3kttrv^NZwhL7o zRqExgN1!h1FFYPg$elD^Fo%C_4auAG6^xx}(+p{X=#kyDvNZTC*;LSIZC|tov#cCA zQ~@2;%ADv$VErjXOmM9o=Y`!=A=H) z)okD9wlqChlFJM2H7}MX@=J++ku{iMAD4T=Dsc^O(yxe|R~xDuuUV#7d1JCdShq~7!J|xt zut4uaXkslS?mMg2LBOnt$H4r;@|C4Od!4ZgE+1x8`g=JzN!b*@qswEufu{m!iwg#i zdNBGrZVD*~5yX7_N#(y~&SBImwZP1`g&o4RN&^V8vqIFAI~n!?nhqMvHw?FS#Di}r z-OYtv8?npGgXH`@Uk(>oe19h1%PnWNHG9P}W&P8R@M>;Ja2%AtshiZh8TR3Fj$FKE zh*$+~qdfdG-x{KL=L#=voO+ehdNc1gMF^lGPAw+eA zcBMLZBu_iX*1ya4!))}bL*JDZdoQJh#58>>Yh2G7At??HzFv1EnB7wo%2Ymop|u+S z0+gr`(ung4|La*{S!bD1ITUxJu%|+IcWogs*FG>*_!DG7=*JSV1gYNM&PwTN{!5lM zHT5cVgZ#^>VFPqIZ!|U728^^O5jULeNl6iX4GOVy&AAon@h0ZTnQ!$yLs<#D!1j0J z&DX8S%1*l5mMXR{T6NfGYg=-jK!*#{?az|VYvbhPR>sTMuLLb`8m%c-^5ctP>i`Ba zpFuMeP&l#M#lRiby_jH$v9K&i7H8GI@f#>^%0Fg@In-mWE93=m(Gu{|Vo_1C z!yjjli`9O#r_WeGc-@>elQo{;qA#19AQta}AZFs%JF3?J&jBP18Hijgv@$3?Wy)wn z>YMzq5I?Q4O`S0kPxx4i=tfu^_7Oa6sceyX-zCLyJr^>{d;bHslamwmm)W;X#3S+; z7Iay8{xua-3#~u0f2xIlM<+?q?OgO{`9WQkk=)E>N^f&<-JIwl#+l0RG1me9`EJt# z|6N=K6_%h}a8XAH^r@6xrL?eb4Z51dGmz>%3vm7l1 zMlB_Y3F`yjo<%Vxd@}svS}ZcwGvZ!)5?r<#GwWi=$8Ztr|1$5o8e@ zPBd0savDCA zV9{WzZQMNEA?qqIk@ySe)b<7gGp>CiMA0AdZ)yUQtn6Xr^TA`ct zatWc`eu1xE1|)+&ZX7Y0d-bs1SpIYvk8fdng+?)^!;vWc(M$TR(V&~sLAm;_XoOr> zFwR(b;h=Y7Hyd^DR+?6~wfQCtWyW<2M_fx#xq*sq33Dk_gA#cinu#HDUCw4)d&_kM zz-cK`9KhhGM3`riP;ynMSq&mV=jnb@EG@iqqqE~5+lnTcUt}DYEN;kiK_c0oq%fss zx)za)h^Eh@T4LmiNnm(Yh2ek_uAkrP?KuV3sw=jqCjP9f`a+Z|OK$6=*m#z?!m4SC zmIK|UnK>^YcN1+!is1Y3t^6`*!4ucj=6WPS_B7D3`Pw$%*+y-D*^1egBRXbs@LZ3M zi3R}ydMjl$WV;S~ifXHGgQW2`B)gLAlru%|71y(>jtm@YG8?E^jhDpP=Xn{>PKmH*Hc|hlKP-cnymhgA%0${t7<(uNULp zdWrh!tUfN0onz?Rxj6@M&HJ`=g%h*Q^;7wt+C*wq19bY*bp!V`m|=6m5L@J_Z5gv` zDoE8vjdTZ9R-Rv@r+AHHH*|EKVhndMk2e;D5jm6(`Ol|hP3A2q#n`>I?Du6oWC`oW z_~zhMYx~pp&?Wh$iH(zO``8jflhDx^CwhC(f@OHfCtZ3Qb)Ks%?R`orJM(Y;fejKH z<$;zql@oYrLFPRA?gh2V>L>GC9kGI|U0+_AC+3=5aVbwg)Euqwk`zy34=Z+1xx}xW zYEDT>yD0o;r-L|#B5@F(&-L~XSu^)0cYo@N%Jz=hbQ`nrF36k6I9)+qJ=;Z@fo0qk z`P_&f(G&ATyM*C~x$3A|PGNf>{;X{<60`K1XMF7BQa4f33xT6AK;5Xp_fIlAZ)#q&Ic(A;o8f>1i6_Ml4 zDf6`AzBXwDy)SFP6RPOK%RM@m!8*2;tsY$2hGI3?w}r>*&Dd zrMBw~Wz#CgGkAu+lnjb?5Jv}fN*aFI4m!cpL3}4h_|eIT7yd@tPE-b&2Pm)?AmxvUu~aD${ZpFl;G=O20uY7SUYoAINj%{`?%fjkxhHS*@WXNZXTf zIF;SNs{DX+sHVkLqi^bD;JdlAGgly}if$z;?`c~%Q(de4fdEL+*RQRbIYX0tQWAim z3vX}7j`gy&>e499a+v-?^{uvE(Dx{F0qo1;72WK~m;-03@X7PVMuctu@~i3vTl|8) zA8$=5nY}0lANLrJ;&m4>A}^#J)_*O|ySf%)i8Obg|1e;ZwWXxPtw7HqK%R#U%7@T->|j!G8Bm{D-!Y-3NIUGt|u!xb228mb@b zkC`RkmHJ{Iu{C}$0sU4=UQL;ycfdl;_Iz1*L6ZBVi6N{ccxKsPz>dapV{F(FUn!03A56SABR3M$c?b8B+zn z+=7Z(jy5NaO6ekTn`_A6{*V69&r1`?1x6?n`&zU6SES)Ra^&AQSn?+m2wyh#hw)rq zTs=97d-mqJuxDD?^(KBUYn1#kyKB-;CG)zep?Pjbc zgFv7l;dFjkDUB}F_gmBFl607HO?PH^(VDt5z1nPrG_B1mn75`jKvye%_zT)qW8_ntnly zb~ZGTCBeKyN&e1Kf1+s3nA#VTvR3b=m6p{RLmE!T&bD4pLoqyg`e2p%l|6{iYelm< zpA*rrA&@*O8QW{a9!iD_yA{mVnxX2JZgc;X&s)gx#ijMQXUpzLO3h)T<~(h<0A$IG z1}{;ofD@s{#Sy28#EPU?Ib(`%^@Blk6Jvi*=pjS^0M$Pu1i*P{C5w#}2<*rLva>b) zBU*}4BRIdb=K<%}P!m4j{2HzQ9{PK*gbwsuhC6CJcNz0lU$P7W4 zqz0Lk=pn3M<(Z|wLRcAC7^I&mN)FL~g~&}v;7%$8(NAr_0{)%)lfP^Gt3&^@qyMe; zhtYFyk9JKs-~t!`;K`juqTYdmZJnHrtgTrbL1upd{Kg#zRofy{*sz*5m~?-r_Zgzi zz>7^+Ywr~&SWcuPnM)S?M_7-6bj}t~<$ZJJRTS1h-y6A4*0mgu>@7)-$s*os@A>n9 z+FsUa<7bd(^eQU35_-9m^k;9n3k3C0kFR5OX?=JVE|IEI@uk392}53Wror%4Hw4wm z+OD$r=4lF|7u=90u@V301AimZr(IOqMxYHu5_rb>2EAdD_XetUs*|E)!vyg~BlV)3 z@{FZ}_?4PIBkLr|8=1$VR_1AbB_W@dW#?I6vY;qKrNeJOjL$}Yr;G`$&5TkF9!S&z(*U;>6Xz-#(#e_AV?2`{`%$H$hZ?anmc!Eza z+iz157z39l=m9uoksh*8MTcbIsxt}dM-uqvk}@2Jftwck8w$daL)iGjg@>NLKRgb+ zg<9I*^>j7tuZPEf4ObQ$v-dHBu~{Nm0S5ws(05A^(P3oFLB_C;`KbQxahZOI$z}~Y zO6fjn(^{CG4tDrzS}_Kol1TKg6e|Z$bI@YUfQn*TSdCOcaWIxd!jn~!W3q{v*D{FP zsfwMb1GH}^WJ%7s6a(fCZ=gP}gQrRL@#mR}zm`+#)-@=-SIBEmI4%M1oW}cAu&+kf zuK19-k(?h{X{k_$s5VQ#x!D?}eE8CH;t!2C8X3pk%Y4&Kg7H|HT;19?9%^Ge9-tT<$9_mke_&9%q{pu6{v#b7-`tSVgzP9_6<9GE?+yCTl z_pSJSNbddZ&vYM>mv=+zuZ6|0t@wAVcnHz&{O%t7&k#lb4gO!<@1K$Q)sgRQ?Sb*l z-=O~=CilSncQ*IHT;*&5dIV4 bud6*LGesE$#QQ&?-u+_lwyK_g#OnV6JxFV~ diff --git a/dom/workers/test/extensions/moz.build b/dom/workers/test/extensions/moz.build deleted file mode 100644 index a509696074de..000000000000 --- a/dom/workers/test/extensions/moz.build +++ /dev/null @@ -1,7 +0,0 @@ -# -*- Mode: python; 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/. - -DIRS += ['bootstrap'] diff --git a/python/mozbuild/mozbuild/codecoverage/lcov_rewriter.py b/python/mozbuild/mozbuild/codecoverage/lcov_rewriter.py index 242fa32b4285..31e1170fceac 100644 --- a/python/mozbuild/mozbuild/codecoverage/lcov_rewriter.py +++ b/python/mozbuild/mozbuild/codecoverage/lcov_rewriter.py @@ -576,7 +576,6 @@ class UrlFinder(object): dir_parts = parts[0].rsplit(app_name + '/', 1) url = mozpath.normpath(mozpath.join(self.topobjdir, 'dist', 'bin', dir_parts[1].lstrip('/'), parts[1].lstrip('/'))) elif '.xpi!' in url: - # e.g. file:///tmp/tmpMdo5gV.mozrunner/extensions/workerbootstrap-test@mozilla.org.xpi!/bootstrap.js # This matching mechanism is quite brittle and based on examples seen in the wild. # There's no rule to match the XPI name to the path in dist/xpi-stage. parts = url_obj.path.split('.xpi!', 1) diff --git a/python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py b/python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py index 7d00e5513ebc..ea5eb58f88d0 100644 --- a/python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py +++ b/python/mozbuild/mozbuild/test/codecoverage/test_lcov_rewrite.py @@ -297,10 +297,6 @@ class TestUrlFinder(unittest.TestCase): 'path4', None ], - 'dist/xpi-stage/workerbootstrap/bootstrap.js': [ - 'path5', - None - ], 'dist/bin/modules/osfile/osfile_async_worker.js': [ 'toolkit/components/osfile/modules/osfile_async_worker.js', None @@ -340,7 +336,6 @@ class TestUrlFinder(unittest.TestCase): ('jar:file:///home/worker/workspace/build/application/' + app_name + '/' + omnijar_name + '!/components/MainProcessSingleton.js', 'path1'), ('jar:file:///home/worker/workspace/build/application/' + app_name + '/browser/' + omnijar_name + '!/components/nsSessionStartup.js', 'path2'), ('jar:file:///home/worker/workspace/build/application/' + app_name + '/browser/features/firefox@getpocket.com.xpi!/bootstrap.js', 'path4'), - ('jar:file:///tmp/tmpMdo5gV.mozrunner/extensions/workerbootstrap-test@mozilla.org.xpi!/bootstrap.js', 'path5'), ] url_finder = lcov_rewriter.UrlFinder(self._chrome_map_file, '', '', []) diff --git a/testing/mochitest/runrobocop.py b/testing/mochitest/runrobocop.py index fe48b855e2a8..62a51a44b1bc 100644 --- a/testing/mochitest/runrobocop.py +++ b/testing/mochitest/runrobocop.py @@ -239,7 +239,6 @@ class RobocopTestRunner(MochitestDesktop): self.options.extensionsToExclude.extend([ 'mochikit@mozilla.org', - 'workerbootstrap-test@mozilla.org.xpi', 'indexedDB-test@mozilla.org.xpi', ]) diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 527c6cd631f7..2e9d4904c56b 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -1897,18 +1897,6 @@ class BootstrapScope { logger.debug(`Loading bootstrap scope from ${this.file.path}`); - let principal = Services.scriptSecurityManager.getSystemPrincipal(); - - if (!this.file.exists()) { - this.scope = - new Cu.Sandbox(principal, { sandboxName: this.file.path, - addonId: this.addon.id, - wantGlobalProperties: ["ChromeUtils"], - metadata: { addonID: this.addon.id } }); - logger.error(`Attempted to load bootstrap scope from missing directory ${this.file.path}`); - return; - } - if (isWebExtension(this.addon.type)) { this.scope = Extension.getBootstrapScope(this.addon.id, this.file); } else if (this.addon.type === "webextension-langpack") { @@ -1918,6 +1906,7 @@ class BootstrapScope { } else { let uri = getURIForResourceInFile(this.file, "bootstrap.js").spec; + let principal = Services.scriptSecurityManager.getSystemPrincipal(); this.scope = new Cu.Sandbox(principal, { sandboxName: uri, addonId: this.addon.id, @@ -1925,19 +1914,12 @@ class BootstrapScope { metadata: { addonID: this.addon.id, URI: uri } }); try { - // Copy the reason values from the global object into the bootstrap scope. - for (let name in BOOTSTRAP_REASONS) - this.scope[name] = BOOTSTRAP_REASONS[name]; + Object.assign(this.scope, BOOTSTRAP_REASONS); - // Add other stuff that extensions want. - Object.assign(this.scope, {Worker, ChromeWorker}); - - // Define a console for the add-on XPCOMUtils.defineLazyGetter( this.scope, "console", () => new ConsoleAPI({ consoleID: `addon/${this.addon.id}` })); - this.scope.__SCRIPT_URI_SPEC__ = uri; Services.scriptloader.loadSubScript(uri, this.scope); } catch (e) { logger.warn(`Error loading bootstrap.js for ${this.addon.id}`, e); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_globals.js b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_globals.js index a783e7ff2537..97bb3682384c 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_globals.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_globals.js @@ -8,8 +8,6 @@ ChromeUtils.import("resource://gre/modules/Services.jsm"); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); const EXPECTED_GLOBALS = [ - ["Worker", "function"], - ["ChromeWorker", "function"], ["console", "object"] ]; From 5ad7ba0bf1e97e7038a8ac754908171aeb2760bd Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Thu, 17 May 2018 15:06:48 -0700 Subject: [PATCH 5/7] Bug 1462483: Part 1 - Explicitly return a nsIDirectoryEnumerator from nsIFile::GetDirectoryEntries. r=froydnj All of our GetDirectoryEntries implementations return a nsIDirectoryEnumerator, and a lot of relies on this, and explicitly QIs to it. That gets a bit ugly, and in JS code, a bit expensive. We should just return a directory enumerator directly if that's part of the API contract. MozReview-Commit-ID: IUeEB1Ih1Wu --HG-- extra : rebase_source : 6fffb2d4b0f83db1fd270423a195379acef0dfe4 --- xpcom/io/FileDescriptorFile.cpp | 2 +- xpcom/io/nsIDirectoryEnumerator.idl | 3 ++- xpcom/io/nsIFile.idl | 22 +++++++++++++++++--- xpcom/io/nsLocalFileUnix.cpp | 5 ++--- xpcom/io/nsLocalFileWin.cpp | 31 ++++++++++++++++++++++++----- 5 files changed, 50 insertions(+), 13 deletions(-) diff --git a/xpcom/io/FileDescriptorFile.cpp b/xpcom/io/FileDescriptorFile.cpp index f916db239f9f..4b138710e929 100644 --- a/xpcom/io/FileDescriptorFile.cpp +++ b/xpcom/io/FileDescriptorFile.cpp @@ -451,7 +451,7 @@ FileDescriptorFile::CreateUnique(uint32_t aType, uint32_t aAttributes) } NS_IMETHODIMP -FileDescriptorFile::GetDirectoryEntries(nsISimpleEnumerator** aEntries) +FileDescriptorFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** aEntries) { return NS_ERROR_NOT_IMPLEMENTED; } diff --git a/xpcom/io/nsIDirectoryEnumerator.idl b/xpcom/io/nsIDirectoryEnumerator.idl index 7a1135fda902..9bf783a7437f 100644 --- a/xpcom/io/nsIDirectoryEnumerator.idl +++ b/xpcom/io/nsIDirectoryEnumerator.idl @@ -4,6 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsISupports.idl" +#include "nsISimpleEnumerator.idl" interface nsIFile; @@ -14,7 +15,7 @@ interface nsIFile; * enumeration is complete. */ [scriptable, uuid(31f7f4ae-6916-4f2d-a81e-926a4e3022ee)] -interface nsIDirectoryEnumerator : nsISupports +interface nsIDirectoryEnumerator : nsISimpleEnumerator { /** * Retrieves the next file in the sequence. The "nextFile" element is the diff --git a/xpcom/io/nsIFile.idl b/xpcom/io/nsIFile.idl index 31c7886cc8a1..d277cf775978 100644 --- a/xpcom/io/nsIFile.idl +++ b/xpcom/io/nsIFile.idl @@ -4,12 +4,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsISupports.idl" +#include "nsIDirectoryEnumerator.idl" %{C++ struct PRFileDesc; struct PRLibrary; #include #include "mozilla/Path.h" +#include "nsCOMPtr.h" #include "nsStringFwd.h" namespace mozilla { using PathString = nsTString; @@ -22,8 +24,6 @@ using PathSubstring = nsTSubstring; [ptr] native FILE(FILE); native PathString(mozilla::PathString); -interface nsISimpleEnumerator; - /** * An nsIFile is an abstract representation of a filename. It manages * filename encoding issues, pathname component separators ('/' vs. '\\' @@ -346,7 +346,23 @@ interface nsIFile : nsISupports * @throws NS_ERROR_FILE_NOT_DIRECTORY if the current nsIFile does * not specify a directory. */ - readonly attribute nsISimpleEnumerator directoryEntries; + [binaryname(DirectoryEntriesImpl)] + readonly attribute nsIDirectoryEnumerator directoryEntries; + + %{C++ + nsresult GetDirectoryEntries(nsISimpleEnumerator** aOut) + { + nsCOMPtr dirEnum; + nsresult rv = GetDirectoryEntries(getter_AddRefs(dirEnum)); + dirEnum.forget(aOut); + return rv; + }; + + nsresult GetDirectoryEntries(nsIDirectoryEnumerator** aOut) + { + return GetDirectoryEntriesImpl(aOut); + }; + %} /** * initWith[Native]Path diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp index 768f66b301ec..38271872e74f 100644 --- a/xpcom/io/nsLocalFileUnix.cpp +++ b/xpcom/io/nsLocalFileUnix.cpp @@ -88,8 +88,7 @@ using namespace mozilla; /* directory enumerator */ class nsDirEnumeratorUnix final - : public nsISimpleEnumerator - , public nsIDirectoryEnumerator + : public nsIDirectoryEnumerator { public: nsDirEnumeratorUnix(); @@ -1850,7 +1849,7 @@ nsLocalFile::SetFollowLinks(bool aFollowLinks) } NS_IMETHODIMP -nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator** aEntries) +nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** aEntries) { RefPtr dir = new nsDirEnumeratorUnix(); diff --git a/xpcom/io/nsLocalFileWin.cpp b/xpcom/io/nsLocalFileWin.cpp index 3b62e4835ea3..fd0f36b27fa5 100644 --- a/xpcom/io/nsLocalFileWin.cpp +++ b/xpcom/io/nsLocalFileWin.cpp @@ -230,13 +230,35 @@ private: nsString mResolvedPath; }; -class nsDriveEnumerator : public nsISimpleEnumerator +class nsDriveEnumerator : public nsIDirectoryEnumerator { public: nsDriveEnumerator(); NS_DECL_ISUPPORTS NS_DECL_NSISIMPLEENUMERATOR nsresult Init(); + + NS_IMETHOD GetNextFile(nsIFile** aResult) override + { + bool hasMore = false; + nsresult rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv) || !hasMore) { + return rv; + } + nsCOMPtr next; + rv = GetNext(getter_AddRefs(next)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr result = do_QueryInterface(next); + result.forget(aResult); + return NS_OK; + } + + NS_IMETHOD Close() override + { + return NS_OK; + } + private: virtual ~nsDriveEnumerator(); @@ -658,8 +680,7 @@ CloseDir(nsDir*& aDir) //----------------------------------------------------------------------------- class nsDirEnumerator final - : public nsISimpleEnumerator - , public nsIDirectoryEnumerator + : public nsIDirectoryEnumerator { private: ~nsDirEnumerator() @@ -3074,7 +3095,7 @@ nsLocalFile::SetFollowLinks(bool aFollowLinks) NS_IMETHODIMP -nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator** aEntries) +nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** aEntries) { nsresult rv; @@ -3511,7 +3532,7 @@ nsLocalFile::GetHashCode(uint32_t* aResult) return NS_OK; } -NS_IMPL_ISUPPORTS(nsDriveEnumerator, nsISimpleEnumerator) +NS_IMPL_ISUPPORTS(nsDriveEnumerator, nsIDirectoryEnumerator, nsISimpleEnumerator) nsDriveEnumerator::nsDriveEnumerator() { From 4501c2b5b9c2e788191bc037cc74f91c2f1db09c Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Thu, 17 May 2018 15:14:48 -0700 Subject: [PATCH 6/7] Bug 1462483: Part 2 - Cleanup directory iteration code. r=aswan MozReview-Commit-ID: 3VzaVPsD8VT --HG-- extra : rebase_source : 829a9e28d01eda2438595002db7814c31f6a9fa2 --- .../extensions/internal/AddonTestUtils.jsm | 58 +++++++++++-------- .../extensions/internal/XPIInstall.jsm | 53 ++--------------- .../extensions/internal/XPIProvider.jsm | 55 ++++++------------ 3 files changed, 58 insertions(+), 108 deletions(-) diff --git a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm index 638051cf0246..e45bb9f48c88 100644 --- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm +++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm @@ -375,11 +375,8 @@ var AddonTestUtils = { } // Check that the temporary directory is empty - var dirEntries = this.tempDir.directoryEntries - .QueryInterface(Ci.nsIDirectoryEnumerator); var entries = []; - while (dirEntries.hasMoreElements()) { - let {leafName} = dirEntries.nextFile; + for (let {leafName} of this.iterDirectory(this.tempDir)) { if (!ignoreEntries.has(leafName)) { entries.push(leafName); } @@ -387,8 +384,6 @@ var AddonTestUtils = { if (entries.length) throw new Error(`Found unexpected files in temporary directory: ${entries.join(", ")}`); - dirEntries.close(); - try { appDirForAddons.remove(true); } catch (ex) { @@ -399,20 +394,9 @@ var AddonTestUtils = { let featuresDir = this.profileDir.clone(); featuresDir.append("features"); // upgrade directories will be in UUID folders under features/ - let systemAddonDirs = []; - if (featuresDir.exists()) { - let featuresDirEntries = featuresDir.directoryEntries - .QueryInterface(Ci.nsIDirectoryEnumerator); - while (featuresDirEntries.hasMoreElements()) { - let entry = featuresDirEntries.getNext(); - entry.QueryInterface(Ci.nsIFile); - systemAddonDirs.push(entry); - } - - systemAddonDirs.map(dir => { - dir.append("stage"); - pathShouldntExist(dir); - }); + for (let dir of this.iterDirectory(featuresDir)) { + dir.append("stage"); + pathShouldntExist(dir); } // ensure no leftover files in the user addon location @@ -447,6 +431,33 @@ var AddonTestUtils = { }); }, + /** + * Iterates over the entries in a given directory. + * + * Fails silently if the given directory does not exist. + * + * @param {nsIFile} dir + * Directory to iterate. + */ + * iterDirectory(dir) { + let dirEnum; + try { + dirEnum = dir.directoryEntries; + let file; + while ((file = dirEnum.nextFile)) { + yield file; + } + } catch (e) { + if (dir.exists()) { + Cu.reportError(e); + } + } finally { + if (dirEnum) { + dirEnum.close(); + } + } + }, + /** * Creates a new HttpServer for testing, and begins listening on the * specified port. Automatically shuts down the server when the test @@ -1178,11 +1189,8 @@ var AddonTestUtils = { setExtensionModifiedTime(ext, time) { ext.lastModifiedTime = time; if (ext.isDirectory()) { - let entries = ext.directoryEntries - .QueryInterface(Ci.nsIDirectoryEnumerator); - while (entries.hasMoreElements()) - this.setExtensionModifiedTime(entries.nextFile, time); - entries.close(); + for (let file of this.iterDirectory(ext)) + this.setExtensionModifiedTime(file, time); } }, diff --git a/toolkit/mozapps/extensions/internal/XPIInstall.jsm b/toolkit/mozapps/extensions/internal/XPIInstall.jsm index 53f87be7a646..90327b05885f 100644 --- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm +++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm @@ -88,7 +88,7 @@ const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest"; const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest"; const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required"; -/* globals BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPI_PERMISSION, XPIStates, getExternalType, isTheme, isWebExtension */ +/* globals BOOTSTRAP_REASONS, KEY_APP_SYSTEM_ADDONS, KEY_APP_SYSTEM_DEFAULTS, PREF_BRANCH_INSTALLED_ADDON, PREF_SYSTEM_ADDON_SET, TEMPORARY_ADDON_SUFFIX, SIGNED_TYPES, TOOLKIT_ID, XPI_PERMISSION, XPIStates, getExternalType, isTheme, isWebExtension, iterDirectory */ const XPI_INTERNAL_SYMBOLS = [ "BOOTSTRAP_REASONS", "KEY_APP_SYSTEM_ADDONS", @@ -103,6 +103,7 @@ const XPI_INTERNAL_SYMBOLS = [ "getExternalType", "isTheme", "isWebExtension", + "iterDirectory", ]; for (let name of XPI_INTERNAL_SYMBOLS) { @@ -1095,7 +1096,7 @@ function recursiveRemove(aFile) { // iterating over a directory while removing files from it (the YAFFS2 // embedded filesystem has this issue, see bug 772238), and to remove // normal files before their resource forks on OSX (see bug 733436). - let entries = getDirectoryEntries(aFile, true); + let entries = Array.from(iterDirectory(aFile)); entries.forEach(recursiveRemove); try { @@ -1271,44 +1272,6 @@ SafeInstallOperation.prototype = { } }; -/** - * Gets a snapshot of directory entries. - * - * @param {nsIFile} aDir - * Directory to look at - * @param {boolean} aSortEntries - * True to sort entries by filename - * @returns {nsIFile[]} - * An files in the directory, or an empty array if aDir is not a - * readable directory. - */ -function getDirectoryEntries(aDir, aSortEntries) { - let dirEnum; - try { - dirEnum = aDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator); - let entries = []; - while (dirEnum.hasMoreElements()) - entries.push(dirEnum.nextFile); - - if (aSortEntries) { - entries.sort(function(a, b) { - return a.path > b.path ? -1 : 1; - }); - } - - return entries; - } catch (e) { - if (aDir.exists()) { - logger.warn("Can't iterate directory " + aDir.path, e); - } - return []; - } finally { - if (dirEnum) { - dirEnum.close(); - } - } -} - function getHashStringForCrypto(aCrypto) { // return the two-digit hexadecimal code for a byte let toHexString = charCode => ("0" + charCode.toString(16)).slice(-2); @@ -2841,13 +2804,9 @@ class DirectoryInstaller { if (this._stagingDirLock > 0) return; - let dirEntries = dir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator); - try { - if (dirEntries.nextFile) - return; - } finally { - dirEntries.close(); - } + // eslint-disable-next-line no-unused-vars + for (let file of iterDirectory(dir)) + return; try { setFilePermissions(dir, FileUtils.PERMS_DIRECTORY); diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 2e9d4904c56b..575aa9235d89 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -467,35 +467,25 @@ function buildJarURI(aJarfile, aPath) { } /** - * Gets a snapshot of directory entries. + * Iterates over the entries in a given directory. * - * @param {nsIURI} aDir - * Directory to look at - * @param {boolean} aSortEntries - * True to sort entries by filename - * @returns {Array} - * An array of nsIFile, or an empty array if aDir is not a readable directory + * Fails silently if the given directory does not exist. + * + * @param {nsIFile} aDir + * Directory to iterate. */ -function getDirectoryEntries(aDir, aSortEntries) { +function* iterDirectory(aDir) { let dirEnum; try { - dirEnum = aDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator); - let entries = []; - while (dirEnum.hasMoreElements()) - entries.push(dirEnum.nextFile); - - if (aSortEntries) { - entries.sort(function(a, b) { - return a.path > b.path ? -1 : 1; - }); + dirEnum = aDir.directoryEntries; + let file; + while ((file = dirEnum.nextFile)) { + yield file; } - - return entries; } catch (e) { if (aDir.exists()) { - logger.warn("Can't iterate directory " + aDir.path, e); + logger.warn(`Can't iterate directory ${aDir.path}`, e); } - return []; } finally { if (dirEnum) { dirEnum.close(); @@ -1104,8 +1094,7 @@ class DirectoryLocation extends XPIStateLocation { // Use a snapshot of the directory contents to avoid possible issues with // iterating over a directory while removing files from it (the YAFFS2 // embedded filesystem has this issue, see bug 772238). - let entries = getDirectoryEntries(this.dir); - for (let entry of entries) { + for (let entry of Array.from(iterDirectory(this.dir))) { let id = entry.leafName; if (id == DIR_STAGE || id == DIR_TRASH) continue; @@ -2627,8 +2616,6 @@ var XPIProvider = { let distroDir; try { distroDir = FileUtils.getDir(KEY_APP_DISTRIBUTION, [DIR_EXTENSIONS]); - if (!distroDir.isDirectory()) - return false; } catch (e) { return false; } @@ -2636,21 +2623,18 @@ var XPIProvider = { let changed = false; let profileLocation = XPIStates.getLocation(KEY_APP_PROFILE); - let entries = distroDir.directoryEntries - .QueryInterface(Ci.nsIDirectoryEnumerator); - let entry; - while ((entry = entries.nextFile)) { - let id = entry.leafName; + for (let file of iterDirectory(distroDir)) { + let id = file.leafName; if (id.endsWith(".xpi")) { id = id.slice(0, -4); } else { - logger.debug("Ignoring distribution add-on that isn't an XPI: " + entry.path); + logger.debug(`Ignoring distribution add-on that isn't an XPI: ${file.path}`); continue; } if (!gIDTest.test(id)) { logger.debug("Ignoring distribution add-on whose name is not a valid add-on ID: " + - entry.path); + file.path); continue; } @@ -2661,7 +2645,7 @@ var XPIProvider = { } try { - let addon = awaitPromise(XPIInstall.installDistributionAddon(id, entry, profileLocation)); + let addon = awaitPromise(XPIInstall.installDistributionAddon(id, file, profileLocation)); if (addon) { // aManifests may contain a copy of a newly installed add-on's manifest @@ -2673,12 +2657,10 @@ var XPIProvider = { changed = true; } } catch (e) { - logger.error(`Failed to install distribution add-on ${entry.path}`, e); + logger.error(`Failed to install distribution add-on ${file.path}`, e); } } - entries.close(); - return changed; }, @@ -3143,6 +3125,7 @@ var XPIInternal = { getURIForResourceInFile, isTheme, isWebExtension, + iterDirectory, }; var addonTypes = [ From 7ea7d29fbbf3cb79dee98c3e09e5f2fb5c909e09 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Sat, 19 May 2018 18:54:52 -0700 Subject: [PATCH 7/7] Bug 1462223: Follow-up: Ignore worker bootstrap test extension in unrelated test. r=bustage,test-only CLOSED TREE MozReview-Commit-ID: HoEEfC6CuLq --- .../extensions/test/browser/browser_checkAddonCompatibility.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/mozapps/extensions/test/browser/browser_checkAddonCompatibility.js b/toolkit/mozapps/extensions/test/browser/browser_checkAddonCompatibility.js index 383e532c3853..29b9ee6aefd1 100644 --- a/toolkit/mozapps/extensions/test/browser/browser_checkAddonCompatibility.js +++ b/toolkit/mozapps/extensions/test/browser/browser_checkAddonCompatibility.js @@ -18,7 +18,7 @@ async function test() { let allCompatible = true; for (let a of aAddons) { // Ignore plugins. - if (a.type == "plugin") + if (a.type == "plugin" || a.id == "workerbootstrap-test@mozilla.org") continue; ok(a.isCompatible, a.type + " " + a.name + " " + a.version + " should be compatible");