Bug 557849: Make Addon.updateAutomatically persist and work. r=robstrong
authorDave Townsend <dtownsend@oxymoronical.com>
Tue, 20 Apr 2010 16:39:10 -0700
changeset 41586 bcb40f8e1160b9d6d04f59c1a9f066363f67f0b2
parent 41585 2425e0fb812df4fe8e0c0b585f6d23ac633fdc85
child 41587 c0df64379e4efed5b7e4e1089c086472cd4cc065
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersrobstrong
bugs557849, 559876
milestone1.9.3a5pre
Bug 557849: Make Addon.updateAutomatically persist and work. r=robstrong * * * Bug 559876: test_update.js is failing intermittently.
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/XPIProvider.jsm
toolkit/mozapps/extensions/test/addons/test_update8/install.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_update.rdf
toolkit/mozapps/extensions/test/xpcshell/test_update.js
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -272,25 +272,30 @@ var AddonManagerInternal = {
   backgroundUpdateCheck: function AMI_backgroundUpdateCheck() {
     if (!Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED))
       return;
 
     let scope = {};
     Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", scope);
     scope.LightweightThemeManager.updateCurrentTheme();
 
-    this.getAddonsByTypes(null, function getAddonsCallback(aAddons) {
+    this.getAllAddons(function getAddonsCallback(aAddons) {
       aAddons.forEach(function BUC_forEachCallback(aAddon) {
-        if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE) {
-          aAddon.findUpdates({
-            onUpdateAvailable: function BUC_onUpdateAvailable(aAddon, aInstall) {
+        // Check all add-ons for updates so that any compatibility updates will
+        // be applied
+        aAddon.findUpdates({
+          onUpdateAvailable: function BUC_onUpdateAvailable(aAddon, aInstall) {
+            // Start installing updates when the add-on can be updated and
+            // background updates should be applied.
+            if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE &&
+                aAddon.applyBackgroundUpdates) {
               aInstall.install();
             }
-          }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
-        }
+          }
+        }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
       });
     });
   },
 
   /**
    * Calls all registered InstallListeners with an event. Any parameters after
    * the extraListeners parameter are passed to the listener.
    *
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -397,16 +397,18 @@ function loadManifestFromRDF(aUri, aStre
 
   // Themes are disabled by default unless they are currently selected
   if (addon.type == "theme")
     addon.userDisabled = addon.internalName != XPIProvider.selectedSkin;
   else
     addon.userDisabled = false;
   addon.appDisabled = !isUsableAddon(addon);
 
+  addon.applyBackgroundUpdates = true;
+
   return addon;
 }
 
 /**
  * Loads an AddonInternal object from an add-on extracted in a directory.
  *
  * @param  aDir
  *         The nsIFile directory holding the add-on
@@ -1172,18 +1174,16 @@ var XPIProvider = {
             return true;
         }
 
         return false;
       }
 
       // Set the additional properties on the new AddonInternal
       newAddon._installLocation = aInstallLocation;
-      newAddon.userDisabled = aOldAddon.userDisabled;
-      newAddon.installDate = aOldAddon.installDate;
       newAddon.updateDate = aAddonState.mtime;
       newAddon.visible = !(newAddon.id in visibleAddons);
 
       // Update the database
       XPIDatabase.updateAddonMetadata(aOldAddon, newAddon, aAddonState.descriptor);
       if (newAddon.visible) {
         visibleAddons[newAddon.id] = newAddon;
         // If the old version was active and wasn't bootstrapped or the new
@@ -2177,17 +2177,18 @@ var XPIProvider = {
     if (aAddon.type == "theme" && aAddon.active)
       AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, false);
   }
 };
 
 const FIELDS_ADDON = "internal_id, id, location, version, type, internalName, " +
                      "updateURL, updateKey, optionsURL, aboutURL, iconURL, " +
                      "defaultLocale, visible, active, userDisabled, appDisabled, " +
-                     "pendingUninstall, descriptor, installDate, updateDate";
+                     "pendingUninstall, descriptor, installDate, updateDate, " +
+                     "applyBackgroundUpdates";
 
 // A helper function to simply log any errors that occur during async statements.
 function asyncErrorLogger(aError) {
   ERROR("SQL error " + aError.result + ": " + aError.message);
 }
 
 var XPIDatabase = {
   // true if the database connection has been opened
@@ -2213,17 +2214,17 @@ var XPIDatabase = {
     _readLocaleStrings: "SELECT locale_id, type, value FROM locale_strings " +
                         "WHERE locale_id=:id",
 
     addAddonMetadata_addon: "INSERT INTO addon VALUES (NULL, :id, :location, " +
                             ":version, :type, :internalName, :updateURL, " +
                             ":updateKey, :optionsURL, :aboutURL, :iconURL, " +
                             ":locale, :visible, :active, :userDisabled," +
                             " :appDisabled, 0, :descriptor, :installDate, " +
-                            ":updateDate)",
+                            ":updateDate, :applyBackgroundUpdates)",
     addAddonMetadata_addon_locale: "INSERT INTO addon_locale VALUES " +
                                    "(:internal_id, :name, :locale)",
     addAddonMetadata_locale: "INSERT INTO locale (name, description, creator, " +
                              "homepageURL) VALUES (:name, :description, " +
                              ":creator, :homepageURL)",
     addAddonMetadata_strings: "INSERT INTO locale_strings VALUES (:locale, " +
                               ":type, :value)",
     addAddonMetadata_targetApplication: "INSERT INTO targetApplication VALUES " +
@@ -2256,17 +2257,18 @@ var XPIDatabase = {
 
     makeAddonVisible: "UPDATE addon SET visible=1 WHERE internal_id=:internal_id",
     removeAddonMetadata: "DELETE FROM addon WHERE internal_id=:internal_id",
     // Equates to active = visible && !userDisabled && !appDisabled && !pendingUninstall
     setActiveAddons: "UPDATE addon SET active=MIN(visible, 1 - userDisabled, " +
                      "1 - appDisabled, 1 - pendingUninstall)",
     setAddonProperties: "UPDATE addon SET userDisabled=:userDisabled, " +
                         "appDisabled=:appDisabled, " +
-                        "pendingUninstall=:pendingUninstall WHERE " +
+                        "pendingUninstall=:pendingUninstall, " +
+                        "applyBackgroundUpdates=:applyBackgroundUpdates WHERE " +
                         "internal_id=:internal_id",
     updateTargetApplications: "UPDATE targetApplication SET " +
                               "minVersion=:minVersion, maxVersion=:maxVersion " +
                               "WHERE addon_internal_id=:internal_id AND id=:id",
   },
 
   /**
    * Opens a new connection to the database file.
@@ -2425,16 +2427,17 @@ var XPIDatabase = {
                                 "id TEXT, location TEXT, version TEXT, " +
                                 "type TEXT, internalName TEXT, updateURL TEXT, " +
                                 "updateKey TEXT, optionsURL TEXT, aboutURL TEXT, " +
                                 "iconURL TEXT, defaultLocale INTEGER, " +
                                 "visible INTEGER, active INTEGER, " +
                                 "userDisabled INTEGER, appDisabled INTEGER, " +
                                 "pendingUninstall INTEGER, descriptor TEXT, " +
                                 "installDate INTEGER, updateDate INTEGER, " +
+                                "applyBackgroundUpdates INTEGER, " +
                                 "UNIQUE (id, location)");
     this.connection.createTable("targetApplication",
                                 "addon_internal_id INTEGER, " +
                                 "id TEXT, minVersion TEXT, maxVersion TEXT, " +
                                 "UNIQUE (addon_internal_id, id)");
     this.connection.createTable("addon_locale",
                                 "addon_internal_id INTEGER, "+
                                 "locale TEXT, locale_id INTEGER, " +
@@ -2561,17 +2564,17 @@ var XPIDatabase = {
     addon._internal_id = aRow.internal_id;
     addon._installLocation = XPIProvider.installLocationsByName[aRow.location];
     addon._descriptor = aRow.descriptor;
     copyProperties(aRow, PROP_METADATA, addon);
     addon._defaultLocale = aRow.defaultLocale;
     addon.installDate = aRow.installDate;
     addon.updateDate = aRow.updateDate;
     ["visible", "active", "userDisabled", "appDisabled",
-     "pendingUninstall"].forEach(function(aProp) {
+     "pendingUninstall", "applyBackgroundUpdates"].forEach(function(aProp) {
       addon[aProp] = aRow[aProp] != 0;
     });
     this.addonCache[aRow.internal_id] = Components.utils.getWeakReference(addon);
     return addon;
   },
 
   /**
    * Asynchronously fetches additional metadata for a DBAddonInternal.
@@ -2711,17 +2714,17 @@ var XPIDatabase = {
     let location = aRow.getResultByName("location");
     addon._installLocation = XPIProvider.installLocationsByName[location];
     addon._descriptor = aRow.getResultByName("descriptor");
     copyRowProperties(aRow, PROP_METADATA, addon);
     addon._defaultLocale = aRow.getResultByName("defaultLocale");
     addon.installDate = aRow.getResultByName("installDate");
     addon.updateDate = aRow.getResultByName("updateDate");
     ["visible", "active", "userDisabled", "appDisabled",
-     "pendingUninstall"].forEach(function(aProp) {
+     "pendingUninstall", "applyBackgroundUpdates"].forEach(function(aProp) {
       addon[aProp] = aRow.getResultByName(aProp) != 0;
     });
     this.addonCache[internal_id] = Components.utils.getWeakReference(addon);
     return addon;
   },
 
   /**
    * Synchronously reads all install locations known about by the database. This
@@ -2985,17 +2988,18 @@ var XPIDatabase = {
     let stmt = this.getStatement("addAddonMetadata_addon");
 
     stmt.params.locale = insertLocale(aAddon.defaultLocale);
     stmt.params.location = aAddon._installLocation.name;
     stmt.params.descriptor = aDescriptor;
     stmt.params.installDate = aAddon.installDate;
     stmt.params.updateDate = aAddon.updateDate;
     copyProperties(aAddon, PROP_METADATA, stmt.params);
-    ["visible", "userDisabled", "appDisabled"].forEach(function(aProp) {
+    ["visible", "userDisabled", "appDisabled",
+     "applyBackgroundUpdates"].forEach(function(aProp) {
       stmt.params[aProp] = aAddon[aProp] ? 1 : 0;
     });
     stmt.params.active = (aAddon.visible && !aAddon.userDisabled &&
                           !aAddon.appDisabled) ? 1 : 0;
     stmt.execute();
     let internal_id = this.connection.lastInsertRowID;
 
     stmt = this.getStatement("addAddonMetadata_addon_locale");
@@ -3029,16 +3033,19 @@ var XPIDatabase = {
    * @param  aNewAddon
    *         The new AddonInternal to add
    * @param  aDescriptor
    *         The file descriptor of the add-on's directory
    */
   updateAddonMetadata: function XPIDB_updateAddonMetadata(aOldAddon, aNewAddon,
                                                           aDescriptor) {
     this.removeAddonMetadata(aOldAddon);
+    aNewAddon.userDisabled = aOldAddon.userDisabled;
+    aNewAddon.installDate = aOldAddon.installDate;
+    aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates;
     this.addAddonMetadata(aNewAddon, aDescriptor);
   },
 
   /**
    * Synchronously updates the target application entries for an add-on.
    *
    * @param  aAddon
    *         The DBAddonInternal being updated
@@ -3101,39 +3108,26 @@ var XPIDatabase = {
   setAddonProperties: function XPIDB_setAddonProperties(aAddon, aProperties) {
     function convertBoolean(value) {
       return value ? 1 : 0;
     }
 
     let stmt = this.getStatement("setAddonProperties");
     stmt.params.internal_id = aAddon._internal_id;
 
-    if ("userDisabled" in aProperties) {
-      stmt.params.userDisabled = convertBoolean(aProperties.userDisabled);
-      aAddon.userDisabled = aProperties.userDisabled;
-    }
-    else {
-      stmt.params.userDisabled = convertBoolean(aAddon.userDisabled);
-    }
-
-    if ("appDisabled" in aProperties) {
-      stmt.params.appDisabled = convertBoolean(aProperties.appDisabled);
-      aAddon.appDisabled = aProperties.appDisabled;
-    }
-    else {
-      stmt.params.appDisabled = convertBoolean(aAddon.appDisabled);
-    }
-
-    if ("pendingUninstall" in aProperties) {
-      stmt.params.pendingUninstall = convertBoolean(aProperties.pendingUninstall);
-      aAddon.pendingUninstall = aProperties.pendingUninstall;
-    }
-    else {
-      stmt.params.pendingUninstall = convertBoolean(aAddon.pendingUninstall);
-    }
+    ["userDisabled", "appDisabled", "pendingUninstall",
+     "applyBackgroundUpdates"].forEach(function(aProp) {
+      if (aProp in aProperties) {
+        stmt.params[aProp] = convertBoolean(aProperties[aProp]);
+        aAddon[aProp] = aProperties[aProp];
+      }
+      else {
+        stmt.params[aProp] = convertBoolean(aAddon[aProp]);
+      }
+    });
 
     stmt.execute();
   },
 
   /**
    * Synchronously pdates an add-on's active flag in the database.
    *
    * @param  aAddon
@@ -3817,17 +3811,16 @@ AddonInstall.prototype = {
         // Install the new add-on into its final directory
         let dir = this.installLocation.installAddon(this.addon.id, stagedAddon);
 
         // Update the metadata in the database
         this.addon._installLocation = this.installLocation;
         this.addon.updateDate = dir.lastModifiedTime;
         this.addon.visible = true;
         if (isUpgrade) {
-          this.addon.installDate = this.existingAddon.installDate;
           XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
                                           dir.persistentDescriptor);
         }
         else {
           this.addon.installDate = this.addon.updateDate;
           XPIDatabase.addAddonMetadata(this.addon, dir.persistentDescriptor);
         }
 
@@ -4309,22 +4302,23 @@ function AddonWrapper(aAddon) {
 
     });
   }, this);
 
   this.__defineGetter__("screenshots", function() {
     return [];
   });
 
-  this.__defineGetter__("updateAutomatically", function() {
-    return aAddon.updateAutomatically;
+  this.__defineGetter__("applyBackgroundUpdates", function() {
+    return aAddon.applyBackgroundUpdates;
   });
-  this.__defineSetter__("updateAutomatically", function(val) {
-    // TODO store this in the DB (bug 557849)
-    aAddon.updateAutomatically = val;
+  this.__defineSetter__("applyBackgroundUpdates", function(val) {
+    XPIDatabase.setAddonProperties(aAddon, {
+      applyBackgroundUpdates: val
+    });
   });
 
   this.__defineGetter__("install", function() {
     if (!("_install" in aAddon) || !aAddon._install)
       return null;
     return aAddon._install.wrapper;
   });
 
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_update8/install.rdf
@@ -0,0 +1,23 @@
+<?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>addon8@tests.mozilla.org</em:id>
+    <em:version>2.0</em:version>
+
+    <!-- Front End MetaData -->
+    <em:name>Test 8</em:name>
+    <em:description>Test Description</em:description>
+
+    <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>
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_update.rdf
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_update.rdf
@@ -118,9 +118,29 @@
                 <em:maxVersion>1</em:maxVersion>
               </Description>
             </em:targetApplication>
           </Description>
         </li>
       </Seq>
     </em:updates>
   </Description>
+
+  <Description about="urn:mozilla:extension:addon8@tests.mozilla.org">
+    <em:updates>
+      <Seq>
+        <li>
+          <Description>
+            <em:version>2.0</em:version>
+            <em:targetApplication>
+              <Description>
+                <em:id>xpcshell@tests.mozilla.org</em:id>
+                <em:minVersion>1</em:minVersion>
+                <em:maxVersion>1</em:maxVersion>
+                <em:updateLink>http://localhost:4444/addons/test_update8.xpi</em:updateLink>
+              </Description>
+            </em:targetApplication>
+          </Description>
+        </li>
+      </Seq>
+    </em:updates>
+  </Description>
 </RDF>
--- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js
@@ -79,16 +79,18 @@ function end_test() {
   testserver.stop(do_test_finished);
 }
 
 // Verify that an update is available and can be installed.
 function run_test_1() {
   AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
     do_check_neq(a1, null);
     do_check_eq(a1.version, "1.0");
+    do_check_true(a1.applyBackgroundUpdates);
+    a1.applyBackgroundUpdates = false;
 
     prepare_test({}, [
       "onNewInstall",
     ]);
 
     a1.findUpdates({
       onNoCompatibilityUpdateAvailable: function(addon) {
         do_throw("Should not have seen no compatibility update");
@@ -142,16 +144,17 @@ function check_test_2() {
     do_check_neq(olda1, null);
     do_check_eq(olda1.version, "1.0");
     restartManager(1);
 
     AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
       do_check_neq(a1, null);
       do_check_eq(a1.version, "2.0");
       do_check_true(isExtensionInAddonsList(profileDir, a1.id));
+      do_check_false(a1.applyBackgroundUpdates);
       a1.uninstall();
       restartManager(0);
 
       run_test_3();
     });
   });
 }
 
@@ -572,42 +575,43 @@ function run_test_8() {
                                  "addon2@tests.mozilla.org",
                                  "addon3@tests.mozilla.org",
                                  "addon4@tests.mozilla.org",
                                  "addon5@tests.mozilla.org",
                                  "addon6@tests.mozilla.org"],
                                  function([a1, a2, a3, a4, a5, a6]) {
       let count = 6;
 
+      function run_next_test() {
+        a1.uninstall();
+        a2.uninstall();
+        a3.uninstall();
+        a4.uninstall();
+        a5.uninstall();
+        a6.uninstall();
+
+        restartManager(0);
+        run_test_9();
+      }
+
       let compatListener = {
         onUpdateFinished: function(addon, error) {
           if (--count == 0)
-            end_test();
+            run_next_test();
         }
       };
 
       let updateListener = {
         onUpdateAvailable: function(addon, update) {
           // Dummy so the update checker knows we care about new versions
         },
 
         onUpdateFinished: function(addon, error) {
-          if (--count != 0)
-            return;
-
-          a1.uninstall();
-          a2.uninstall();
-          a3.uninstall();
-          a4.uninstall();
-          a5.uninstall();
-          a6.uninstall();
-
-          restartManager(0);
-
-          run_test_9();
+          if (--count == 0)
+            run_next_test();
         }
       };
 
       a1.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED);
       a2.findUpdates(compatListener, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
       a3.findUpdates(updateListener, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
       a4.findUpdates(updateListener, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2");
       a5.findUpdates(compatListener, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
@@ -737,11 +741,95 @@ function check_test_13() {
     do_check_neq(a7, null);
     do_check_true(a7.isActive);
     do_check_true(a7.isCompatible);
     do_check_false(a7.appDisabled);
 
     a7.uninstall();
     restartManager(0);
 
+    run_test_14();
+  });
+}
+
+// Test that background update checks doesn't update an add-on that isn't
+// allowed to update automatically.
+function run_test_14() {
+  // Have an add-on there that will be updated so we see some events from it
+  var dest = profileDir.clone();
+  dest.append("addon1@tests.mozilla.org");
+  writeInstallRDFToDir({
+    id: "addon1@tests.mozilla.org",
+    version: "1.0",
+    updateURL: "http://localhost:4444/data/test_update.rdf",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "1"
+    }],
+    name: "Test Addon 1",
+  }, dest);
+
+  dest = profileDir.clone();
+  dest.append("addon8@tests.mozilla.org");
+  writeInstallRDFToDir({
+    id: "addon8@tests.mozilla.org",
+    version: "1.0",
+    updateURL: "http://localhost:4444/data/test_update.rdf",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "1"
+    }],
+    name: "Test Addon 8",
+  }, dest);
+  restartManager(1);
+
+  AddonManager.getAddonByID("addon8@tests.mozilla.org", function(a8) {
+    a8.applyBackgroundUpdates = false;
+
+    // Note that the background check will find a new update for both add-ons
+    // but only start installing one of them
+    prepare_test({}, [
+      "onNewInstall",
+      "onDownloadStarted",
+      "onNewInstall",
+      "onDownloadEnded"
+    ], continue_test_14);
+  
+    // Fake a timer event
+    gInternalManager.notify(null);
+  });
+}
+
+function continue_test_14(install) {
+  do_check_neq(install.existingAddon, null);
+  do_check_eq(install.existingAddon.id, "addon1@tests.mozilla.org");
+
+  prepare_test({
+    "addon1@tests.mozilla.org": [
+      "onInstalling"
+    ]
+  }, [
+    "onInstallStarted",
+    "onInstallEnded",
+  ], check_test_14);
+}
+
+function check_test_14(install) {
+  do_check_eq(install.existingAddon.pendingUpgrade.install, install);
+
+  restartManager(1);
+  AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
+                               "addon8@tests.mozilla.org"], function([a1, a8]) {
+    do_check_neq(a1, null);
+    do_check_eq(a1.version, "2.0");
+    a1.uninstall();
+
+    do_check_neq(a8, null);
+    do_check_eq(a8.version, "1.0");
+    a8.uninstall();
+
+    restartManager(0);
+
     end_test();
   });
 }