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
--- 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]