Bug 579170: DB migration can apply compatibility information to the wrong version of an add-on. r=robstrong
authorDave Townsend <dtownsend@oxymoronical.com>
Fri, 16 Jul 2010 14:33:00 -0700
changeset 47839 cc53b4e4e27797ef6aa0e05c3d765a69bd4ea2cf
parent 47838 ac3a7fa063aa86e795652432543df6ac2d8b9003
child 47840 9712aa5f77e17cbe24fd906ccadb87689af761eb
push id14439
push userdtownsend@mozilla.com
push dateFri, 16 Jul 2010 21:36:11 +0000
treeherdermozilla-central@cc53b4e4e277 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrobstrong
bugs579170
milestone2.0b2pre
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 579170: DB migration can apply compatibility information to the wrong version of an add-on. r=robstrong
toolkit/mozapps/extensions/XPIProvider.jsm
toolkit/mozapps/extensions/test/xpcshell/data/test_migrate.rdf
toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js
toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -1581,18 +1581,24 @@ var XPIProvider = {
       // If there is migration data then apply it.
       if (aMigrateData) {
         // A theme's disabled state is determined by the selected theme
         // preference which is read in loadManifestFromRDF
         if (newAddon.type != "theme")
           newAddon.userDisabled = aMigrateData.userDisabled;
         if ("installDate" in aMigrateData)
           newAddon.installDate = aMigrateData.installDate;
-        if ("targetApplications" in aMigrateData)
-          newAddon.applyCompatibilityUpdate(aMigrateData, true);
+
+        // Some properties should only be migrated if the add-on hasn't changed.
+        // The version property isn't a perfect check for this but covers the
+        // vast majority of cases.
+        if (aMigrateData.version == newAddon.version) {
+          if ("targetApplications" in aMigrateData)
+            newAddon.applyCompatibilityUpdate(aMigrateData, true);
+        }
       }
 
       try {
         // Update the database.
         XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor);
       }
       catch (e) {
         // Failing to write the add-on into the database is non-fatal, the
@@ -2772,16 +2778,17 @@ var XPIDatabase = {
           let source = elements.getNext().QueryInterface(Ci.nsIRDFResource);
 
           let location = getRDFProperty(ds, source, "installLocation");
           if (location) {
             if (!(location in migrateData))
               migrateData[location] = {};
             let id = source.ValueUTF8.substring(PREFIX_ITEM_URI.length);
             migrateData[location][id] = {
+              version: getRDFProperty(ds, source, "version"),
               userDisabled: false,
               targetApplications: []
             }
 
             let disabled = getRDFProperty(ds, source, "userDisabled");
             if (disabled == "true" || disabled == "needs-disable")
               migrateData[location][id].userDisabled = true;
 
@@ -2802,22 +2809,24 @@ var XPIDatabase = {
       }
     }
     else {
       // Attempt to migrate data from a different (even future!) version of the
       // database
       try {
         var stmt = this.connection.createStatement("SELECT internal_id, id, " +
                                                    "location, userDisabled, " +
-                                                   "installDate FROM addon");
+                                                   "installDate, version " +
+                                                   "FROM addon");
         for (let row in resultRows(stmt)) {
           if (!(row.location in migrateData))
             migrateData[row.location] = {};
           migrateData[row.location][row.id] = {
             internal_id: row.internal_id,
+            version: row.version,
             installDate: row.installDate,
             userDisabled: row.userDisabled == 1,
             targetApplications: []
           };
         }
 
         var taStmt = this.connection.createStatement("SELECT id, minVersion, " +
                                                      "maxVersion FROM " +
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_migrate.rdf
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_migrate.rdf
@@ -1,16 +1,17 @@
 <?xml version="1.0"?>
 
 <!-- This is a copy of extensions.rdf from Firefox 3.5 including four
      test extensions. Addon1 was user enabled, addon2 was user disabled, addon3
      was pending user disable at the next restart and addon4 was pending user 
      enable at the next restart. Additionally addon1 and 2 have had
      compatibility updates applies to make them compatible with the app and
-     toolkit respectively, addon3 and 4 have not.
+     toolkit respectively, addon3 and 4 have not. addon5 is disabled however
+     at the same time as the migration a new version should be detected.
      It also contains two themes in the profile -->
 
 <RDF:RDF xmlns:NS1="http://www.mozilla.org/2004/em-rdf#"
          xmlns:NC="http://home.netscape.com/NC-rdf#"
          xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
   <RDF:Description RDF:about="rdf:#$w8dNC3"
                    NS1:id="xpcshell@tests.mozilla.org"
                    NS1:minVersion="1"
@@ -30,16 +31,20 @@
   <RDF:Description RDF:about="rdf:#$w8dNC2"
                    NS1:id="toolkit@mozilla.org"
                    NS1:minVersion="1"
                    NS1:maxVersion="1" />
   <RDF:Description RDF:about="rdf:#$w8dNC1"
                    NS1:id="toolkit@mozilla.org"
                    NS1:minVersion="1"
                    NS1:maxVersion="2" />
+  <RDF:Description RDF:about="rdf:#$w8dNC7"
+                   NS1:id="toolkit@mozilla.org"
+                   NS1:minVersion="1"
+                   NS1:maxVersion="2" />
   <RDF:Description RDF:about="rdf:#$oadNC1"
                    NS1:id="xpcshell@tests.mozilla.org"
                    NS1:minVersion="1"
                    NS1:maxVersion="2" />
   <RDF:Description RDF:about="urn:mozilla:item:addon1@tests.mozilla.org"
                    NS1:installLocation="app-profile"
                    NS1:version="1.0"
                    NS1:name="Test 1">
@@ -65,16 +70,24 @@
   <RDF:Description RDF:about="urn:mozilla:item:addon4@tests.mozilla.org"
                    NS1:installLocation="app-profile"
                    NS1:version="2.0"
                    NS1:name="Test 4"
                    NS1:userDisabled="needs-enable">
     <NS1:type NC:parseType="Integer">2</NS1:type>
     <NS1:targetApplication RDF:resource="rdf:#$w8dNC2"/>
   </RDF:Description>
+  <RDF:Description RDF:about="urn:mozilla:item:addon5@tests.mozilla.org"
+                   NS1:installLocation="app-profile"
+                   NS1:version="1.0"
+                   NS1:name="Test 5"
+                   NS1:userDisabled="true">
+    <NS1:type NC:parseType="Integer">2</NS1:type>
+    <NS1:targetApplication RDF:resource="rdf:#$w8dNC7"/>
+  </RDF:Description>
   <RDF:Description RDF:about="urn:mozilla:item:theme1@tests.mozilla.org"
                    NS1:installLocation="app-profile"
                    NS1:version="1.0"
                    NS1:name="Theme 2"
                    NS1:internalName="theme1/1.0">
     <NS1:type NC:parseType="Integer">4</NS1:type>
     <NS1:targetApplication RDF:resource="rdf:#$w8dNC5"/>
   </RDF:Description>
@@ -86,12 +99,13 @@
     <NS1:type NC:parseType="Integer">4</NS1:type>
     <NS1:targetApplication RDF:resource="rdf:#$w8dNC6"/>
   </RDF:Description>
   <RDF:Seq RDF:about="urn:mozilla:item:root">
     <RDF:li RDF:resource="urn:mozilla:item:addon1@tests.mozilla.org"/>
     <RDF:li RDF:resource="urn:mozilla:item:addon2@tests.mozilla.org"/>
     <RDF:li RDF:resource="urn:mozilla:item:addon3@tests.mozilla.org"/>
     <RDF:li RDF:resource="urn:mozilla:item:addon4@tests.mozilla.org"/>
+    <RDF:li RDF:resource="urn:mozilla:item:addon5@tests.mozilla.org"/>
     <RDF:li RDF:resource="urn:mozilla:item:theme1@tests.mozilla.org"/>
     <RDF:li RDF:resource="urn:mozilla:item:theme2@tests.mozilla.org"/>
   </RDF:Seq>
 </RDF:RDF>
--- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js
@@ -43,16 +43,27 @@ var addon4 = {
   name: "Test 4",
   targetApplications: [{
     id: "toolkit@mozilla.org",
     minVersion: "1",
     maxVersion: "1"
   }]
 };
 
+var addon5 = {
+  id: "addon5@tests.mozilla.org",
+  version: "2.0",
+  name: "Test 5",
+  targetApplications: [{
+    id: "toolkit@mozilla.org",
+    minVersion: "1",
+    maxVersion: "1"
+  }]
+};
+
 var theme1 = {
   id: "theme1@tests.mozilla.org",
   version: "1.0",
   name: "Theme 1",
   type: 4,
   internalName: "theme1/1.0",
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
@@ -89,16 +100,19 @@ function run_test() {
   writeInstallRDFToDir(addon2, dest);
   dest = profileDir.clone();
   dest.append("addon3@tests.mozilla.org");
   writeInstallRDFToDir(addon3, dest);
   dest = profileDir.clone();
   dest.append("addon4@tests.mozilla.org");
   writeInstallRDFToDir(addon4, dest);
   dest = profileDir.clone();
+  dest.append("addon5@tests.mozilla.org");
+  writeInstallRDFToDir(addon5, dest);
+  dest = profileDir.clone();
   dest.append("theme1@tests.mozilla.org");
   writeInstallRDFToDir(theme1, dest);
   dest = profileDir.clone();
   dest.append("theme2@tests.mozilla.org");
   writeInstallRDFToDir(theme2, dest);
 
   let old = do_get_file("data/test_migrate.rdf");
   old.copyTo(gProfD, "extensions.rdf");
@@ -106,19 +120,20 @@ function run_test() {
   // Theme state is determined by the selected theme pref
   Services.prefs.setCharPref("general.skins.selectedSkin", "theme1/1.0");
 
   startupManager();
   AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
                                "addon2@tests.mozilla.org",
                                "addon3@tests.mozilla.org",
                                "addon4@tests.mozilla.org",
+                               "addon5@tests.mozilla.org",
                                "theme1@tests.mozilla.org",
                                "theme2@tests.mozilla.org"], function([a1, a2,
-                                                                      a3, a4,
+                                                                      a3, a4, a5,
                                                                       t1, t2]) {
     // addon1 was user and app enabled in the old extensions.rdf
     do_check_neq(a1, null);
     do_check_false(a1.userDisabled);
     do_check_false(a1.appDisabled);
 
     // addon2 was user disabled and app enabled in the old extensions.rdf
     do_check_neq(a2, null);
@@ -130,16 +145,22 @@ function run_test() {
     do_check_true(a3.userDisabled);
     do_check_true(a3.appDisabled);
 
     // addon4 was pending user enable and app disabled in the old extensions.rdf
     do_check_neq(a4, null);
     do_check_false(a4.userDisabled);
     do_check_true(a4.appDisabled);
 
+    // addon5 was disabled and compatible but a new version has been installed
+    // since, it should still be disabled but should be incompatible
+    do_check_neq(a5, null);
+    do_check_true(a5.userDisabled);
+    do_check_true(a5.appDisabled);
+
     // Theme 1 was previously enabled
     do_check_neq(t1, null);
     do_check_false(t1.userDisabled);
     do_check_false(t1.appDisabled);
     do_check_false(hasFlag(t1.permissions, AddonManager.PERM_CAN_ENABLE));
 
     // Theme 2 was previously disabled
     do_check_neq(t1, null);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js
@@ -54,16 +54,27 @@ var addon5 = {
   name: "Test 5",
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
     minVersion: "0",
     maxVersion: "0"
   }]
 };
 
+var addon6 = {
+  id: "addon6@tests.mozilla.org",
+  version: "2.0",
+  name: "Test 6",
+  targetApplications: [{
+    id: "xpcshell@tests.mozilla.org",
+    minVersion: "0",
+    maxVersion: "0"
+  }]
+};
+
 const profileDir = gProfD.clone();
 profileDir.append("extensions");
 
 function run_test() {
   do_test_pending();
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
 
   var dest = profileDir.clone();
@@ -76,66 +87,81 @@ function run_test() {
   dest.append("addon3@tests.mozilla.org");
   writeInstallRDFToDir(addon3, dest);
   dest = profileDir.clone();
   dest.append("addon4@tests.mozilla.org");
   writeInstallRDFToDir(addon4, dest);
   dest = profileDir.clone();
   dest.append("addon5@tests.mozilla.org");
   writeInstallRDFToDir(addon5, dest);
+  dest = profileDir.clone();
+  dest.append("addon6@tests.mozilla.org");
+  writeInstallRDFToDir(addon6, dest);
 
   // Write out a minimal database
   let dbfile = gProfD.clone();
   dbfile.append("extensions.sqlite");
   let db = AM_Cc["@mozilla.org/storage/service;1"].
            getService(AM_Ci.mozIStorageService).
            openDatabase(dbfile);
   db.createTable("addon", "internal_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
-                          "id TEXT, location TEXT, active INTEGER, " +
+                          "id TEXT, location TEXT, version TEXT, active INTEGER, " +
                           "userDisabled INTEGER, installDate INTEGER");
   db.createTable("targetApplication", "addon_internal_id INTEGER, " +
                                       "id TEXT, minVersion TEXT, maxVersion TEXT");
   let stmt = db.createStatement("INSERT INTO addon VALUES (NULL, :id, :location, " +
-                                ":active, :userDisabled, :installDate)");
+                                ":version, :active, :userDisabled, :installDate)");
+
+  let internal_ids = {};
 
-  [["addon1@tests.mozilla.org", "app-profile", "1", "0", "0"],
-   ["addon2@tests.mozilla.org", "app-profile", "0", "1", "0"],
-   ["addon3@tests.mozilla.org", "app-profile", "1", "1", "0"],
-   ["addon4@tests.mozilla.org", "app-profile", "0", "0", "0"],
-   ["addon5@tests.mozilla.org", "app-profile", "1", "0", "0"]].forEach(function(a) {
+  [["addon1@tests.mozilla.org", "app-profile", "1.0", "1", "0", "0"],
+   ["addon2@tests.mozilla.org", "app-profile", "2.0", "0", "1", "0"],
+   ["addon3@tests.mozilla.org", "app-profile", "2.0", "1", "1", "0"],
+   ["addon4@tests.mozilla.org", "app-profile", "2.0", "0", "0", "0"],
+   ["addon5@tests.mozilla.org", "app-profile", "2.0", "1", "0", "0"],
+   ["addon6@tests.mozilla.org", "app-profile", "1.0", "0", "1", "0"]].forEach(function(a) {
     stmt.params.id = a[0];
     stmt.params.location = a[1];
-    stmt.params.active = a[2];
-    stmt.params.userDisabled = a[3];
-    stmt.params.installDate = a[4];
+    stmt.params.version = a[2];
+    stmt.params.active = a[3];
+    stmt.params.userDisabled = a[4];
+    stmt.params.installDate = a[5];
     stmt.execute();
+    internal_ids[a[0]] = db.lastInsertRowID;
   });
   stmt.finalize();
 
   // Add updated target application into for addon5
-  let internal_id = db.lastInsertRowID;
   stmt = db.createStatement("INSERT INTO targetApplication VALUES " +
                             "(:internal_id, :id, :minVersion, :maxVersion)");
-  stmt.params.internal_id = internal_id;
+  stmt.params.internal_id = internal_ids["addon5@tests.mozilla.org"];
+  stmt.params.id = "xpcshell@tests.mozilla.org";
+  stmt.params.minVersion = "0";
+  stmt.params.maxVersion = "1";
+  stmt.execute();
+
+  // Add updated target application into for addon6
+  stmt.params.internal_id = internal_ids["addon6@tests.mozilla.org"];
   stmt.params.id = "xpcshell@tests.mozilla.org";
   stmt.params.minVersion = "0";
   stmt.params.maxVersion = "1";
   stmt.execute();
   stmt.finalize();
 
   db.close();
   Services.prefs.setIntPref("extensions.databaseSchema", 100);
 
   startupManager();
   AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
                                "addon2@tests.mozilla.org",
                                "addon3@tests.mozilla.org",
                                "addon4@tests.mozilla.org",
-                               "addon5@tests.mozilla.org"],
-                               function([a1, a2, a3, a4, a5]) {
+                               "addon5@tests.mozilla.org",
+                               "addon6@tests.mozilla.org"],
+                               function([a1, a2, a3, a4, a5, a6]) {
     // addon1 was enabled in the database
     do_check_neq(a1, null);
     do_check_false(a1.userDisabled);
     do_check_false(a1.appDisabled);
     do_check_true(a1.isActive);
     // addon2 was disabled in the database
     do_check_neq(a2, null);
     do_check_true(a2.userDisabled);
@@ -152,10 +178,17 @@ function run_test() {
     do_check_false(a4.appDisabled);
     do_check_true(a4.isActive);
     // addon5 was enabled in the database but needed a compatibiltiy update
     do_check_neq(a5, null);
     do_check_false(a5.userDisabled);
     do_check_false(a5.appDisabled);
     do_check_true(a5.isActive);
     do_test_finished();
+    // addon6 was disabled and compatible but a new version has been installed
+    // since, it should still be disabled but should be incompatible
+    do_check_neq(a6, null);
+    do_check_true(a6.userDisabled);
+    do_check_true(a6.appDisabled);
+    do_check_false(a6.isActive);
+    do_test_finished();
   });
 }