Bug 594058 - invalidate cache by statting contents of extensions directory r=dtownsend r=bz a=bsmedberg

This commit is contained in:
Benedict Hsieh 2010-09-14 17:39:07 -07:00
parent b07a02bb8b
commit fb0a2b2373
11 changed files with 160 additions and 13 deletions

View File

@ -183,6 +183,9 @@ nsXULPrototypeCache::Observe(nsISupports* aSubject,
else if (!strcmp(aTopic, "chrome-flush-caches")) {
Flush();
}
else if (!strcmp(aTopic, "startupcache-invalidate")) {
AbortFastLoads();
}
else {
NS_WARNING("Unexpected observer topic.");
}

View File

@ -175,6 +175,9 @@ StartupCache::Init()
rv = mObserverService->AddObserver(mListener, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
rv = mObserverService->AddObserver(mListener, "startupcache-invalidate",
PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
rv = LoadArchive();
@ -420,6 +423,10 @@ StartupCacheListener::Observe(nsISupports *subject, const char* topic, const PRU
nsresult rv = NS_OK;
if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
StartupCache::gShutdownInitiated = PR_TRUE;
} else if (strcmp(topic, "startupcache-invalidate") == 0) {
StartupCache* sc = StartupCache::GetSingleton();
if (sc)
sc->InvalidateCache();
}
return rv;
}

View File

@ -888,6 +888,34 @@ function resultRows(aStatement) {
}
}
/**
* Returns the timestamp of the most recently modified file in a directory,
* or simply the file's own timestamp if it is not a directory.
*
* @param aFile
* A non-null nsIFile object
* @return Epoch time, as described above. 0 for an empty directory.
*/
function recursiveLastModifiedTime(aFile) {
if (aFile.isFile())
return aFile.lastModifiedTime;
if (aFile.isDirectory()) {
let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
let entry, time;
let maxTime = aFile.lastModifiedTime;
while (entry = entries.nextFile) {
time = recursiveLastModifiedTime(entry);
maxTime = Math.max(time, maxTime);
}
entries.close();
return maxTime;
}
// If the file is something else, just ignore it.
return 0;
}
/**
* A helpful wrapper around the prefs service that allows for default values
* when requested values aren't set.
@ -1296,9 +1324,10 @@ var XPIProvider = {
}
catch (e) { }
},
/**
* Gets the add-on states for an install location.
* Gets the add-on states for an install location.
* This function may be expensive because of the recursiveLastModifiedTime call.
*
* @param location
* The install location to retrieve the add-on states for
@ -1313,7 +1342,7 @@ var XPIProvider = {
let id = aLocation.getIDForLocation(file);
addonStates[id] = {
descriptor: file.persistentDescriptor,
mtime: file.lastModifiedTime
mtime: recursiveLastModifiedTime(file)
};
});
@ -1323,7 +1352,7 @@ var XPIProvider = {
/**
* Gets an array of install location states which uniquely describes all
* installed add-ons with the add-on's InstallLocation name and last modified
* time.
* time. This function may be expensive because of the getAddonStates() call.
*
* @return an array of add-on states for each install location. Each state
* is an object with a name property holding the location's name and
@ -1461,7 +1490,8 @@ var XPIProvider = {
/**
* Compares the add-ons that are currently installed to those that were
* known to be installed when the application last ran and applies any
* changes found to the database.
* changes found to the database. Also sends "startupcache-invalidate" signal to
* observerservice if it detects that data may have changed.
*
* @param aState
* The array of current install location states
@ -1869,6 +1899,12 @@ var XPIProvider = {
// Cache the new install location states
cache = JSON.stringify(this.getInstallLocationStates());
Services.prefs.setCharPref(PREF_INSTALL_CACHE, cache);
if (changed) {
// Init this, so it will get the notification.
let xulPrototypeCache = Cc["@mozilla.org/xul/xul-prototype-cache;1"].getService(Ci.nsISupports);
Services.obs.notifyObservers(null, "startupcache-invalidate", null);
}
return changed;
},
@ -4975,7 +5011,7 @@ AddonInstall.prototype = {
// Update the metadata in the database
this.addon._installLocation = this.installLocation;
this.addon.updateDate = file.lastModifiedTime;
this.addon.updateDate = recursiveLastModifiedTime(file);
this.addon.visible = true;
if (isUpgrade) {
XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,

View File

@ -0,0 +1,21 @@
<?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>bug594058@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>1</em:maxVersion>
</Description>
</em:targetApplication>
<em:name>bug 594058</em:name>
<em:description>stat-based invalidation</em:description>
<em:unpack>true</em:unpack>
</Description>
</RDF>

View File

@ -572,6 +572,23 @@ function writeInstallRDFForExtension(aData, aDir, aId, aExtraFile) {
return dir;
}
/**
* Sets the last modified time of the extension, usually to trigger an update
* of its metadata. If the extension is unpacked, this function assumes that
* the extension contains only the install.rdf file.
*
* @param aExt a file pointing to either the packed extension or its unpacked directory.
* @param aTime the time to which we set the lastModifiedTime of the extension
*/
function setExtensionModifiedTime(aExt, aTime) {
aExt.lastModifiedTime = aTime;
if (aExt.isDirectory()) {
aExt = aExt.clone();
aExt.append("install.rdf");
aExt.lastModifiedTime = aTime;
}
}
function registerDirectory(aKey, aDir) {
var dirProvider = {
getFile: function(aProp, aPersistent) {

View File

@ -24,7 +24,7 @@ function run_test() {
}, profileDir);
// Attempt to make this look like it was added some time in the past so
// the update makes the last modified time change.
dest.lastModifiedTime -= 5000;
setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000);
startupManager();

View File

@ -0,0 +1,62 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// This tests is modifying a file in an unpacked extension
// causes cache invalidation.
// Disables security checking our updates which haven't been signed
Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);
const Ci = Components.interfaces;
const extDir = gProfD.clone();
extDir.append("extensions");
/**
* Start the test by installing extensions.
*/
function run_test() {
do_test_pending();
let cachePurged = false;
let obs = AM_Cc["@mozilla.org/observer-service;1"].
getService(AM_Ci.nsIObserverService);
obs.addObserver({
observe: function(aSubject, aTopic, aData) {
cachePurged = true;
}
}, "startupcache-invalidate", false);
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
startupManager();
installAllFiles([do_get_addon("test_bug594058")], function() {
restartManager();
do_check_true(cachePurged);
cachePurged = false;
// Now, make it look like we've updated the file. First, start the EM
// so it records the bogus old time, then update the file and restart.
let extFile = extDir.clone();
let pastTime = extFile.lastModifiedTime - 5000;
extFile.append("bug594058@tests.mozilla.org");
setExtensionModifiedTime(extFile, pastTime);
let otherFile = extFile.clone();
otherFile.append("directory");
otherFile.lastModifiedTime = pastTime;
otherFile.append("file1");
otherFile.lastModifiedTime = pastTime;
restartManager();
cachePurged = false;
otherFile.lastModifiedTime = pastTime + 5000;
restartManager();
do_check_true(cachePurged);
cachePurged = false;
restartManager();
do_check_true(!cachePurged);
do_test_finished();
});
}

View File

@ -220,7 +220,7 @@ function run_test_5() {
var dest = writeInstallRDFForExtension(addon2, sourceDir, addon1.id);
// Make sure the modification time changes enough to be detected.
dest.lastModifiedTime -= 5000;
setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000);
restartManager();
@ -310,7 +310,7 @@ function run_test_8() {
var dest = writeInstallRDFForExtension(addon1_2, sourceDir);
// Make sure the modification time changes enough to be detected.
dest.lastModifiedTime -= 5000;
setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000);
restartManager();

View File

@ -120,7 +120,8 @@ function run_test_1() {
var dest = writeInstallRDFForExtension(addon2, profileDir);
// Attempt to make this look like it was added some time in the past so
// the change in run_test_2 makes the last modified time change.
dest.lastModifiedTime -= 5000;
setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000);
writeInstallRDFForExtension(addon3, profileDir);
writeInstallRDFForExtension(addon4, profileDir);
writeInstallRDFForExtension(addon5, profileDir);

View File

@ -75,7 +75,7 @@ function run_test() {
}],
name: "Test Addon 4",
}, globalDir);
dest.lastModifiedTime = gInstallTime;
setExtensionModifiedTime(dest, gInstallTime);
do_test_pending();
@ -133,7 +133,7 @@ function run_test_2() {
}],
name: "Test Addon 4",
}, globalDir);
dest.lastModifiedTime = gInstallTime;
setExtensionModifiedTime(dest, gInstallTime);
restartManager("2");
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
@ -172,7 +172,7 @@ function run_test_3() {
}],
name: "Test Addon 4",
}, globalDir);
dest.lastModifiedTime = gInstallTime;
setExtensionModifiedTime(dest, gInstallTime);
// Simulates a simple Build ID change, the platform deletes extensions.ini
// whenever the application is changed.