Bug 616628: Need to flush the zipreader caches before undoing a pending extension install. r=robstrong, a=blocks-beta9
authorDave Townsend <dtownsend@oxymoronical.com>
Wed, 08 Dec 2010 10:51:16 -0800
changeset 58902 56a617969b41e15e1e811a508239ec0a2944565e
parent 58901 93ad1a7be79192e2a45624a08173e934a0536fbd
child 58903 1655a76ea1880f530086c48b21f80ef93d190629
push idunknown
push userunknown
push dateunknown
reviewersrobstrong, blocks-beta9
bugs616628
milestone2.0b8pre
Bug 616628: Need to flush the zipreader caches before undoing a pending extension install. r=robstrong, a=blocks-beta9
toolkit/mozapps/extensions/XPIProvider.jsm
toolkit/mozapps/extensions/test/addons/test_cacheflush1/install.rdf
toolkit/mozapps/extensions/test/addons/test_cacheflush2/install.rdf
toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -1064,18 +1064,20 @@ function recursiveRemove(aFile) {
   aFile.permissions = aFile.isDirectory() ? FileUtils.PERMS_DIRECTORY
                                           : FileUtils.PERMS_FILE;
 
   try {
     aFile.remove(true);
     return;
   }
   catch (e) {
-    if (!aFile.isDirectory())
+    if (!aFile.isDirectory()) {
+      ERROR("Failed to remove file " + aFile.path, e);
       throw e;
+    }
   }
 
   let entry;
   let dirEntries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
   try {
     while (entry = dirEntries.nextFile)
       recursiveRemove(entry);
     aFile.remove(true);
@@ -1666,17 +1668,17 @@ var XPIProvider = {
       }
 
       if (stagedXPIDir.exists()) {
         try {
           recursiveRemove(stagedXPIDir);
         }
         catch (e) {
           // Non-critical, just saves some perf on startup if we clean this up.
-          LOG("Error removing XPI staging dir " + stagedXPIDir.path + ": " + e);
+          LOG("Error removing XPI staging dir " + stagedXPIDir.path, e);
         }
       }
 
       if (!stagingDir || !stagingDir.exists() || !stagingDir.isDirectory())
         return;
 
       entries = stagingDir.directoryEntries
                           .QueryInterface(Ci.nsIDirectoryEnumerator);
@@ -1769,17 +1771,17 @@ var XPIProvider = {
       }
       entries.close();
 
       try {
         recursiveRemove(stagingDir);
       }
       catch (e) {
         // Non-critical, just saves some perf on startup if we clean this up.
-        LOG("Error removing staging dir " + stagingDir.path + ": " + e);
+        LOG("Error removing staging dir " + stagingDir.path, e);
       }
     });
     return changed;
   },
 
   /**
    * Compares the add-ons that are currently installed to those that were
    * known to be installed when the application last ran and applies any
@@ -4936,16 +4938,19 @@ AddonInstall.prototype = {
       this.state = AddonManager.STATE_CANCELLED;
       XPIProvider.removeActiveInstall(this);
       AddonManagerPrivate.callInstallListeners("onDownloadCancelled",
                                                this.listeners, this.wrapper);
       this.removeTemporaryFile();
       break;
     case AddonManager.STATE_INSTALLED:
       LOG("Cancelling install of " + this.addon.id);
+      let xpi = this.installLocation.getStagingDir();
+      xpi.append(this.addon.id + ".xpi");
+      Services.obs.notifyObservers(xpi, "flush-cache-entry", null);
       cleanStagingDir(this.installLocation.getStagingDir(),
                       [this.addon.id, this.addon.id + ".xpi",
                        this.addon.id + ".json"]);
       this.state = AddonManager.STATE_CANCELLED;
       XPIProvider.removeActiveInstall(this);
 
       if (this.existingAddon) {
         delete this.existingAddon.pendingUpgrade;
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_cacheflush1/install.rdf
@@ -0,0 +1,22 @@
+<?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>2.0</em:version>
+
+    <!-- Front End MetaData -->
+    <em:name>File Pointer Test</em:name>
+
+    <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/addons/test_cacheflush2/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>addon2@tests.mozilla.org</em:id>
+    <em:version>2.0</em:version>
+
+    <!-- Front End MetaData -->
+    <em:name>File Pointer Test</em:name>
+    <em:bootstrap>true</em:bootstrap>
+
+    <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/test_cacheflush.js
@@ -0,0 +1,121 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This verifies that flushing the zipreader cache happens when appropriate
+
+var gExpectedFile = null;
+var gCacheFlushed = false;
+
+var CacheFlushObserver = {
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic != "flush-cache-entry")
+      return;
+
+    do_check_true(gExpectedFile != null);
+    do_check_true(aSubject instanceof AM_Ci.nsIFile);
+    do_check_eq(aSubject.path, gExpectedFile.path);
+    gCacheFlushed = true;
+    gExpectedFile = null;
+  }
+};
+
+function run_test() {
+  // This test only makes sense when leaving extensions packed
+  if (Services.prefs.getBoolPref("extensions.alwaysUnpack"))
+    return;
+
+  do_test_pending();
+  Services.obs.addObserver(CacheFlushObserver, "flush-cache-entry", false);
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "2");
+
+  startupManager();
+
+  run_test_1();
+}
+
+// Tests that the cache is flushed when cancelling a pending install
+function run_test_1() {
+  AddonManager.getInstallForFile(do_get_addon("test_cacheflush1"), function(aInstall) {
+    completeAllInstalls([aInstall], function() {
+      // We should flush the staged XPI when cancelling the install
+      gExpectedFile = gProfD.clone();
+      gExpectedFile.append("extensions");
+      gExpectedFile.append("staged");
+      gExpectedFile.append("addon1@tests.mozilla.org.xpi");
+      aInstall.cancel();
+
+      do_check_true(gCacheFlushed);
+      gCacheFlushed = false;
+
+      run_test_2();
+    });
+  });
+}
+
+// Tests that the cache is flushed when uninstalling an add-on
+function run_test_2() {
+  installAllFiles([do_get_addon("test_cacheflush1")], function() {
+    // Installing will flush the staged XPI during startup
+    gExpectedFile = gProfD.clone();
+    gExpectedFile.append("extensions");
+    gExpectedFile.append("staged");
+    gExpectedFile.append("addon1@tests.mozilla.org.xpi");
+    restartManager();
+    do_check_true(gCacheFlushed);
+    gCacheFlushed = false;
+
+    AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) {
+      // We should flush the installed XPI when uninstalling
+      gExpectedFile = gProfD.clone();
+      gExpectedFile.append("extensions");
+      gExpectedFile.append("addon1@tests.mozilla.org.xpi");
+
+      a1.uninstall();
+      do_check_false(gCacheFlushed);
+      restartManager();
+
+      run_test_3();
+    });
+  });
+}
+
+// Tests that the cache is flushed when installing a restartless add-on
+function run_test_3() {
+  AddonManager.getInstallForFile(do_get_addon("test_cacheflush2"), function(aInstall) {
+    aInstall.addListener({
+      onInstallStarted: function(aInstall) {
+        // We should flush the staged XPI when completing the install
+        gExpectedFile = gProfD.clone();
+        gExpectedFile.append("extensions");
+        gExpectedFile.append("staged");
+        gExpectedFile.append("addon2@tests.mozilla.org.xpi");
+      },
+
+      onInstallEnded: function(aInstall) {
+        do_check_true(gCacheFlushed);
+        gCacheFlushed = false;
+
+        run_test_4();
+      }
+    });
+
+    aInstall.install();
+  });
+}
+
+// Tests that the cache is flushed when uninstalling a restartless add-on
+function run_test_4() {
+  AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) {
+    // We should flush the installed XPI when uninstalling
+    gExpectedFile = gProfD.clone();
+    gExpectedFile.append("extensions");
+    gExpectedFile.append("addon2@tests.mozilla.org.xpi");
+
+    a2.uninstall();
+    do_check_true(gCacheFlushed);
+    gCacheFlushed = false;
+
+    do_test_finished();
+  });
+}