Bug 1356826: Part 2 - Don't scan directories for changes at startup without pref. r?rhelmer draft
authorKris Maglione <maglione.k@gmail.com>
Sat, 15 Apr 2017 19:48:29 -0700
changeset 566755 1dd5c7cd6e6dc71f2c458963420cf422650800c8
parent 566754 f91483c6a95a0d882099d53b404b57ec1c3f8066
child 566756 03d456aa5398206243202a48f9efbb4293866c78
push id55320
push usermaglione.k@gmail.com
push dateSun, 23 Apr 2017 05:18:40 +0000
reviewersrhelmer
bugs1356826
milestone55.0a1
Bug 1356826: Part 2 - Don't scan directories for changes at startup without pref. r?rhelmer MozReview-Commit-ID: KdzmJeHpdmU
browser/app/profile/firefox.js
toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -64,16 +64,18 @@ pref("extensions.hotfix.certs.2.sha1Fing
 pref("extensions.systemAddon.update.url", "https://aus5.mozilla.org/update/3/SystemAddons/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
 
 // Disable screenshots for now, Shield will enable this.
 pref("extensions.screenshots.system-disabled", true);
 
 // Disable add-ons that are not installed by the user in all scopes by default.
 // See the SCOPE constants in AddonManager.jsm for values to use here.
 pref("extensions.autoDisableScopes", 15);
+// Scopes to scan for changes at startup.
+pref("extensions.startupScanScopes", 0);
 
 // Add-on content security policies.
 pref("extensions.webextensions.base-content-security-policy", "script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; object-src 'self' https://* moz-extension: blob: filesystem:;");
 pref("extensions.webextensions.default-content-security-policy", "script-src 'self'; object-src 'self';");
 
 // Require signed add-ons by default
 pref("xpinstall.signatures.required", true);
 pref("xpinstall.signatures.devInfoURL", "https://wiki.mozilla.org/Addons/Extension_Signing");
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -238,16 +238,19 @@ var AddonTestUtils = {
     Services.prefs.setBoolPref("extensions.logging.enabled", true);
 
     // By default only load extensions from the profile install location
     Services.prefs.setIntPref("extensions.enabledScopes", AddonManager.SCOPE_PROFILE);
 
     // By default don't disable add-ons from any scope
     Services.prefs.setIntPref("extensions.autoDisableScopes", 0);
 
+    // And scan for changes at startup
+    Services.prefs.setIntPref("extensions.startupScanScopes", 15);
+
     // By default, don't cache add-ons in AddonRepository.jsm
     Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", false);
 
     // Disable the compatibility updates window by default
     Services.prefs.setBoolPref("extensions.showMismatchUI", false);
 
     // Point update checks to the local machine for fast failures
     Services.prefs.setCharPref("extensions.update.url", "http://127.0.0.1/updateURL");
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -113,16 +113,17 @@ const PREF_PENDING_OPERATIONS         = 
 const PREF_SKIN_SWITCHPENDING         = "extensions.dss.switchPending";
 const PREF_SKIN_TO_SELECT             = "extensions.lastSelectedSkin";
 const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
 const PREF_EM_UPDATE_URL              = "extensions.update.url";
 const PREF_EM_UPDATE_BACKGROUND_URL   = "extensions.update.background.url";
 const PREF_EM_ENABLED_ADDONS          = "extensions.enabledAddons";
 const PREF_EM_EXTENSION_FORMAT        = "extensions.";
 const PREF_EM_ENABLED_SCOPES          = "extensions.enabledScopes";
+const PREF_EM_STARTUP_SCAN_SCOPES     = "extensions.startupScanScopes";
 const PREF_EM_SHOW_MISMATCH_UI        = "extensions.showMismatchUI";
 const PREF_XPI_ENABLED                = "xpinstall.enabled";
 const PREF_XPI_WHITELIST_REQUIRED     = "xpinstall.whitelist.required";
 const PREF_XPI_DIRECT_WHITELISTED     = "xpinstall.whitelist.directRequest";
 const PREF_XPI_FILE_WHITELISTED       = "xpinstall.whitelist.fileRequest";
 // xpinstall.signatures.required only supported in dev builds
 const PREF_XPI_SIGNATURES_REQUIRED    = "xpinstall.signatures.required";
 const PREF_XPI_SIGNATURES_DEV_ROOT    = "xpinstall.signatures.dev-root";
@@ -320,16 +321,19 @@ const LOGGER_ID = "addons.xpi";
 // (Requires AddonManager.jsm)
 var logger = Log.repository.getLogger(LOGGER_ID);
 
 const LAZY_OBJECTS = ["XPIDatabase", "XPIDatabaseReconcile"];
 /* globals XPIDatabase, XPIDatabaseReconcile*/
 
 var gLazyObjectsLoaded = false;
 
+XPCOMUtils.defineLazyPreferenceGetter(this, "gStartupScanScopes",
+                                      PREF_EM_STARTUP_SCAN_SCOPES, 0);
+
 function loadLazyObjects() {
   let uri = "resource://gre/modules/addons/XPIProviderUtils.js";
   let scope = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), {
     sandboxName: uri,
     wantGlobalProperties: ["TextDecoder"],
   });
 
   let shared = {
@@ -2304,29 +2308,48 @@ this.XPIStates = {
     }
     logger.debug("Loaded add-on state from prefs: ${}", state);
     return state;
   },
 
   /**
    * Walk through all install locations, highest priority first,
    * comparing the on-disk state of extensions to what is stored in prefs.
+   *
+   * @param {bool} [ignoreSideloads = true]
+   *        If true, ignore changes in scopes where we don't accept
+   *        side-loads.
+   *
    * @return true if anything has changed.
    */
-  getInstallState() {
+  getInstallState(ignoreSideloads = true) {
     let oldState = this.loadExtensionState();
     let changed = false;
     this.db = new SerializableMap();
 
     for (let location of XPIProvider.installLocations) {
-      // The list of add-on like file/directory names in the install location.
-      let addons = location.getAddonLocations();
       // The results of scanning this location.
       let foundAddons = new SerializableMap();
 
+      // Don't bother checking scopes where we don't accept side-loads.
+      if (ignoreSideloads && !(location.scope & gStartupScanScopes)) {
+        if (location.name in oldState) {
+          for (let [id, state] of Object.entries(oldState[location.name])) {
+            foundAddons.set(id, new XPIState(state));
+          }
+
+          this.db.set(location.name, foundAddons);
+          delete oldState[location.name];
+        }
+        continue;
+      }
+
+      // The list of add-on like file/directory names in the install location.
+      let addons = location.getAddonLocations(!ignoreSideloads);
+
       // What our old state thinks should be in this location.
       let locState = {};
       if (location.name in oldState) {
         locState = oldState[location.name];
         // We've seen this location.
         delete oldState[location.name];
       }
 
@@ -3743,18 +3766,17 @@ this.XPIProvider = {
    * @param  aOldAppVersion
    *         The version of the application last run with this profile or null
    *         if it is a new profile or the version is unknown
    * @param  aOldPlatformVersion
    *         The version of the platform last run with this profile or null
    *         if it is a new profile or the version is unknown
    * @return true if a change requiring a restart was detected
    */
-  checkForChanges(aAppChanged, aOldAppVersion,
-                                                aOldPlatformVersion) {
+  checkForChanges(aAppChanged, aOldAppVersion, aOldPlatformVersion) {
     logger.debug("checkForChanges");
 
     // Keep track of whether and why we need to open and update the database at
     // startup time.
     let updateReasons = [];
     if (aAppChanged) {
       updateReasons.push("appChanged");
     }
@@ -3791,17 +3813,17 @@ this.XPIProvider = {
       updated = this.installDistributionAddons(manifests, aAppChanged);
       if (updated) {
         updateReasons.push("installDistributionAddons");
       }
     }
 
     // Telemetry probe added around getInstallState() to check perf
     let telemetryCaptureTime = Cu.now();
-    let installChanged = XPIStates.getInstallState();
+    let installChanged = XPIStates.getInstallState(aAppChanged === false);
     let telemetry = Services.telemetry;
     telemetry.getHistogramById("CHECK_ADDONS_MODIFIED_MS").add(Math.round(Cu.now() - telemetryCaptureTime));
     if (installChanged) {
       updateReasons.push("directoryState");
     }
 
     let haveAnyAddons = (XPIStates.size > 0);
 
@@ -8051,17 +8073,17 @@ class DirectoryInstallLocation {
     this._IDToFileMap = {};
     this._linkedAddons = [];
 
     if (!aDirectory || !aDirectory.exists())
       return;
     if (!aDirectory.isDirectory())
       throw new Error("Location must be a directory.");
 
-    this._readAddons();
+    this.initialized = false;
   }
 
   /**
    * Reads a directory linked to in a file.
    *
    * @param   file
    *          The file containing the directory path
    * @return  An nsIFile object representing the linked directory.
@@ -8115,17 +8137,22 @@ class DirectoryInstallLocation {
 
     logger.warn("File pointer " + aFile.path + " does not contain a path");
     return null;
   }
 
   /**
    * Finds all the add-ons installed in this location.
    */
-  _readAddons() {
+  _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)
@@ -8178,33 +8205,38 @@ class DirectoryInstallLocation {
    */
   get scope() {
     return this._scope;
   }
 
   /**
    * Gets an array of nsIFiles for add-ons installed in this location.
    */
-  getAddonLocations() {
+  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  aId
    *         The ID of the add-on
    * @return The 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.