Bug 655254: When an add-on moves to a different directory but doesn't change in any other way we should keep updated compatibility information. r=robstrong
authorDave Townsend <dtownsend@oxymoronical.com>
Fri, 10 Jun 2011 13:10:48 -0700
changeset 70884 fbeb460473f5
parent 70883 28217403cd02
child 70892 6da2c7c5c170
push id20436
push userdtownsend@mozilla.com
push date2011-06-10 21:17 +0000
treeherdermozilla-central@fbeb460473f5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrobstrong
bugs655254
milestone7.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 655254: When an add-on moves to a different directory but doesn't change in any other way we should keep updated compatibility information. r=robstrong
toolkit/mozapps/extensions/XPIProvider.jsm
toolkit/mozapps/extensions/test/addons/test_bug655254/install.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_bug655254.rdf
toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -2176,17 +2176,18 @@ var XPIProvider = {
    * @param  aMigrateData
    *         an object generated from a previous version of the database
    *         holding information about what add-ons were previously userDisabled
    *         and updated compatibility information if present
    * @param  aActiveBundles
    *         When performing recovery after startup this will be an array of
    *         persistent descriptors of add-ons that are known to be active,
    *         otherwise it will be null
-   * @return true if a change requiring a restart was detected
+   * @return a boolean indicating if a change requiring flushing the caches was
+   *         detected
    */
   processFileChanges: function XPI_processFileChanges(aState, aManifests,
                                                       aUpdateCompatibility,
                                                       aOldAppVersion,
                                                       aOldPlatformVersion,
                                                       aMigrateData,
                                                       aActiveBundles) {
     let visibleAddons = {};
@@ -2200,17 +2201,17 @@ var XPIProvider = {
      *
      * @param  aInstallLocation
      *         The install location containing the add-on
      * @param  aOldAddon
      *         The AddonInternal as it appeared the last time the application
      *         ran
      * @param  aAddonState
      *         The new state of the add-on
-     * @return true if restarting the application is required to complete
+     * @return a boolean indicating if flushing caches is required to complete
      *         changing this add-on
      */
     function updateMetadata(aInstallLocation, aOldAddon, aAddonState) {
       LOG("Add-on " + aOldAddon.id + " modified in " + aInstallLocation.name);
 
       // Check if there is an updated install manifest for this add-on
       let newAddon = aManifests[aInstallLocation.name][aOldAddon.id];
 
@@ -2272,30 +2273,61 @@ var XPIProvider = {
         // Otherwise the caches will need to be invalidated
         return true;
       }
 
       return false;
     }
 
     /**
+     * Updates an add-on's descriptor for when the add-on has moved in the
+     * filesystem but hasn't changed in any other way.
+     *
+     * @param  aInstallLocation
+     *         The install location containing the add-on
+     * @param  aOldAddon
+     *         The AddonInternal as it appeared the last time the application
+     *         ran
+     * @param  aAddonState
+     *         The new state of the add-on
+     * @return a boolean indicating if flushing caches is required to complete
+     *         changing this add-on
+     */
+    function updateDescriptor(aInstallLocation, aOldAddon, aAddonState) {
+      LOG("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor);
+
+      aOldAddon._descriptor = aAddonState.descriptor;
+      aOldAddon.visible = !(aOldAddon.id in visibleAddons);
+
+      // Update the database
+      XPIDatabase.setAddonDescriptor(aOldAddon, aAddonState.descriptor);
+      if (aOldAddon.visible) {
+        visibleAddons[aOldAddon.id] = aOldAddon;
+
+        return true;
+      }
+
+      return false;
+    }
+
+    /**
      * Called when no change has been detected for an add-on's metadata. The
      * add-on may have become visible due to other add-ons being removed or
      * the add-on may need to be updated when the application version has
      * changed.
      *
      * @param  aInstallLocation
      *         The install location containing the add-on
      * @param  aOldAddon
      *         The AddonInternal as it appeared the last time the application
      *         ran
      * @param  aAddonState
      *         The new state of the add-on
-     * @return a boolean indicating if restarting the application is required
-     *         to complete changing this add-on
+     * @return a boolean indicating if flushing caches is required to complete
+     *         changing this add-on
      */
     function updateVisibilityAndCompatibility(aInstallLocation, aOldAddon,
                                               aAddonState) {
       let changed = false;
 
       // This add-ons metadata has not changed but it may have become visible
       if (!(aOldAddon.id in visibleAddons)) {
         visibleAddons[aOldAddon.id] = aOldAddon;
@@ -2394,18 +2426,18 @@ var XPIProvider = {
     /**
      * Called when an add-on has been removed.
      *
      * @param  aInstallLocation
      *         The install location containing the add-on
      * @param  aOldAddon
      *         The AddonInternal as it appeared the last time the application
      *         ran
-     * @return a boolean indicating if restarting the application is required
-     *         to complete changing this add-on
+     * @return a boolean indicating if flushing caches is required to complete
+     *         changing this add-on
      */
     function removeMetadata(aInstallLocation, aOldAddon) {
       // This add-on has disappeared
       LOG("Add-on " + aOldAddon.id + " removed from " + aInstallLocation.name);
       XPIDatabase.removeAddonMetadata(aOldAddon);
       if (aOldAddon.active) {
 
         // Enable the default theme if the previously active theme has been
@@ -2428,18 +2460,18 @@ var XPIProvider = {
      *         The install location containing the add-on
      * @param  aId
      *         The ID of the add-on
      * @param  aAddonState
      *         The new state of the add-on
      * @param  aMigrateData
      *         If during startup the database had to be upgraded this will
      *         contain data that used to be held about this add-on
-     * @return a boolean indicating if restarting the application is required
-     *         to complete changing this add-on
+     * @return a boolean indicating if flushing caches is required to complete
+     *         changing this add-on
      */
     function addMetadata(aInstallLocation, aId, aAddonState, aMigrateData) {
       LOG("New add-on " + aId + " installed in " + aInstallLocation.name);
 
       let newAddon = null;
       // Check the updated manifests lists for the install location, If there
       // is no manifest for the add-on ID then newAddon will be undefined
       if (aInstallLocation.name in aManifests)
@@ -2601,27 +2633,29 @@ var XPIProvider = {
             let addonState = addonStates[aOldAddon.id];
             delete addonStates[aOldAddon.id];
 
             // Remember add-ons that were inactive during startup
             if (aOldAddon.visible && !aOldAddon.active)
               XPIProvider.inactiveAddonIDs.push(aOldAddon.id);
 
             // The add-on has changed if the modification time has changed, or
-            // the directory it is installed in has changed or we have an
-            // updated manifest for it. Also reload the metadata for add-ons
-            // in the application directory when the application version has
-            // changed
+            // we have an updated manifest for it. Also reload the metadata for
+            // add-ons in the application directory when the application version
+            // has changed
             if (aOldAddon.id in aManifests[installLocation.name] ||
                 aOldAddon.updateDate != addonState.mtime ||
-                aOldAddon._descriptor != addonState.descriptor ||
                 (aUpdateCompatibility && installLocation.name == KEY_APP_GLOBAL)) {
               changed = updateMetadata(installLocation, aOldAddon, addonState) ||
                         changed;
             }
+            else if (aOldAddon._descriptor != addonState.descriptor) {
+              changed = updateDescriptor(installLocation, aOldAddon, addonState) ||
+                        changed;
+            }
             else {
               changed = updateVisibilityAndCompatibility(installLocation,
                                                          aOldAddon, addonState) ||
                         changed;
             }
             if (aOldAddon.visible && aOldAddon._installLocation.name != KEY_APP_GLOBAL)
               XPIProvider.allAppGlobal = false;
           }
@@ -3877,16 +3911,18 @@ var XPIDatabase = {
     setActiveAddons: "UPDATE addon SET active=MIN(visible, 1 - userDisabled, " +
                      "1 - softDisabled, 1 - appDisabled)",
     setAddonProperties: "UPDATE addon SET userDisabled=:userDisabled, " +
                         "appDisabled=:appDisabled, " +
                         "softDisabled=:softDisabled, " +
                         "pendingUninstall=:pendingUninstall, " +
                         "applyBackgroundUpdates=:applyBackgroundUpdates WHERE " +
                         "internal_id=:internal_id",
+    setAddonDescriptor: "UPDATE addon SET descriptor=:descriptor WHERE " +
+                        "internal_id=:internal_id",
     updateTargetApplications: "UPDATE targetApplication SET " +
                               "minVersion=:minVersion, maxVersion=:maxVersion " +
                               "WHERE addon_internal_id=:internal_id AND id=:id",
 
     createSavepoint: "SAVEPOINT 'default'",
     releaseSavepoint: "RELEASE SAVEPOINT 'default'",
     rollbackSavepoint: "ROLLBACK TO SAVEPOINT 'default'"
   },
@@ -5124,17 +5160,33 @@ var XPIDatabase = {
     else {
       stmt.params.applyBackgroundUpdates = aAddon.applyBackgroundUpdates;
     }
 
     executeStatement(stmt);
   },
 
   /**
-   * Synchronously pdates an add-on's active flag in the database.
+   * Synchronously sets the file descriptor for an add-on.
+   *
+   * @param  aAddon
+   *         The DBAddonInternal being updated
+   * @param  aProperties
+   *         A dictionary of properties to set
+   */
+  setAddonDescriptor: function XPIDB_setAddonDescriptor(aAddon, aDescriptor) {
+    let stmt = this.getStatement("setAddonDescriptor");
+    stmt.params.internal_id = aAddon._internal_id;
+    stmt.params.descriptor = aDescriptor;
+
+    executeStatement(stmt);
+  },
+
+  /**
+   * Synchronously updates an add-on's active flag in the database.
    *
    * @param  aAddon
    *         The DBAddonInternal to update
    */
   updateAddonActive: function XPIDB_updateAddonActive(aAddon) {
     LOG("Updating add-on state");
 
     let stmt = this.getStatement("updateAddonActive");
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_bug655254/install.rdf
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>addon1@tests.mozilla.org</em:id>
+    <em:version>1.0</em:version>
+    <em:name>Test 1</em:name>
+    <em:updateURL>http://localhost:4444/data/test_bug655254.rdf</em:updateURL>
+    <em:targetApplication>
+      <Description>
+        <em:id>xpcshell@tests.mozilla.org</em:id>
+        <em:minVersion>1</em:minVersion>
+        <em:maxVersion>1</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+  </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug655254.rdf
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+         xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <RDF:Description about="urn:mozilla:extension:addon1@tests.mozilla.org">
+    <em:updates>
+      <RDF:Seq>
+        <RDF:li>
+          <RDF:Description>
+            <em:version>1</em:version>
+            <em:targetApplication>
+              <RDF:Description>
+                <em:id>xpcshell@tests.mozilla.org</em:id>
+                <em:minVersion>1</em:minVersion>
+                <em:maxVersion>2</em:maxVersion>
+              </RDF:Description>
+            </em:targetApplication>
+          </RDF:Description>
+        </RDF:li>
+      </RDF:Seq>
+    </em:updates>
+  </RDF:Description>
+
+</RDF:RDF>
+
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This verifies that moving an extension in the filesystem without any other
+// change still keeps updated compatibility information
+
+// The test extension uses an insecure update url.
+Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);
+// Enable loading extensions from the user and system scopes
+Services.prefs.setIntPref("extensions.enabledScopes",
+                          AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_USER);
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "1.9.2");
+
+do_load_httpd_js();
+var testserver;
+
+var userDir = gProfD.clone();
+userDir.append("extensions2");
+userDir.append(gAppInfo.ID);
+
+var dirProvider = {
+  getFile: function(aProp, aPersistent) {
+    aPersistent.value = false;
+    if (aProp == "XREUSysExt")
+      return userDir.parent;
+    return null;
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([AM_Ci.nsIDirectoryServiceProvider,
+                                         AM_Ci.nsISupports])
+};
+Services.dirsvc.registerProvider(dirProvider);
+
+var addon1 = {
+  id: "addon1@tests.mozilla.org",
+  version: "1.0",
+  name: "Test 1",
+  updateURL: "http://localhost:4444/data/test_bug655254.rdf",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "1",
+    maxVersion: "1"
+  }]
+};
+
+// Set up the profile
+function run_test() {
+  do_test_pending();
+
+  // Create and configure the HTTP server.
+  testserver = new nsHttpServer();
+  testserver.registerDirectory("/data/", do_get_file("data"));
+  testserver.start(4444);
+
+  var time = Date.now();
+  var dir = writeInstallRDFForExtension(addon1, userDir);
+  setExtensionModifiedTime(dir, time);
+
+  startupManager();
+
+  AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+    do_check_neq(a1, null);
+    do_check_true(a1.appDisabled);
+    do_check_false(a1.isActive);
+    do_check_false(isExtensionInAddonsList(userDir, a1.id));
+
+    a1.findUpdates({
+      onUpdateFinished: function() {
+        restartManager();
+
+        AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+          do_check_neq(a1, null);
+          do_check_false(a1.appDisabled);
+          do_check_true(a1.isActive);
+          do_check_true(isExtensionInAddonsList(userDir, a1.id));
+
+          shutdownManager();
+
+          userDir.parent.moveTo(gProfD, "extensions3");
+          userDir = gProfD.clone();
+          userDir.append("extensions3");
+          userDir.append(gAppInfo.ID);
+          do_check_true(userDir.exists());
+
+          startupManager(false);
+
+          AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+            do_check_neq(a1, null);
+            do_check_false(a1.appDisabled);
+            do_check_true(a1.isActive);
+            do_check_true(isExtensionInAddonsList(userDir, a1.id));
+
+            testserver.stop(do_test_finished);
+          });
+        });
+      }
+    }, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+  });
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -49,16 +49,17 @@ tail =
 [test_bug587088.js]
 [test_bug594058.js]
 [test_bug595081.js]
 [test_bug595573.js]
 [test_bug596607.js]
 [test_bug616841.js]
 [test_bug619730.js]
 [test_bug620837.js]
+[test_bug655254.js]
 [test_cacheflush.js]
 [test_checkcompatibility.js]
 [test_corrupt.js]
 [test_disable.js]
 [test_distribution.js]
 [test_dss.js]
 [test_duplicateplugins.js]
 [test_error.js]