Bug 425792: Properly update and use expiration times when updating the offline cache. r/sr=biesi, b1.9=damons

This commit is contained in:
dcamp@mozilla.com 2008-04-08 22:22:32 -07:00
parent 49a650c61f
commit 299ba58c7b
8 changed files with 219 additions and 11 deletions

View File

@ -52,6 +52,7 @@ _TEST_FILES = \
test_missingFile.html \
test_simpleManifest.html \
test_identicalManifest.html \
test_changingManifest.html \
test_offlineIFrame.html \
badManifestMagic.cacheManifest \
badManifestMagic.cacheManifest^headers^ \
@ -60,6 +61,9 @@ _TEST_FILES = \
simpleManifest.cacheManifest \
simpleManifest.cacheManifest^headers^ \
simpleManifest.notmanifest \
changing1Sec.sjs \
changing1Hour.sjs \
changingManifest.sjs \
offlineChild.html \
$(NULL)

View File

@ -0,0 +1,8 @@
function handleRequest(request, response)
{
response.setStatusLine(request.httpVersion, 200, "Ok");
response.setHeader("Content-Type", "text/plain");
response.setHeader("Cache-Control", "max-age=3600");
response.write(Date.now());
}

View File

@ -0,0 +1,9 @@
function handleRequest(request, response)
{
response.setStatusLine(request.httpVersion, 200, "Ok");
response.setHeader("Content-Type", "text/plain");
response.setHeader("Cache-Control", "max-age=1");
response.write(Date.now());
}

View File

@ -0,0 +1,12 @@
function handleRequest(request, response)
{
response.setStatusLine(request.httpVersion, 200, "Ok");
response.setHeader("Content-Type", "text/cache-manifest");
response.setHeader("Cache-Control", "no-cache");
response.write("CACHE MANIFEST\n");
response.write("#" + Date.now() + "\n");
response.write("http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/changing1Hour.sjs\n");
response.write("http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/changing1Sec.sjs\n");
}

View File

@ -4,6 +4,63 @@ netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var Cc = Components.classes;
var Ci = Components.interfaces;
const kNetBase = 2152398848; // 0x804B0000
var NS_ERROR_CACHE_KEY_NOT_FOUND = kNetBase + 61;
var NS_ERROR_CACHE_KEY_WAIT_FOR_VALIDATION = kNetBase + 64;
// Reading the contents of multiple cache entries asynchronously
function OfflineCacheContents(urls) {
this.urls = urls;
this.contents = {};
}
OfflineCacheContents.prototype = {
QueryInterface: function(iid) {
if (!iid.equals(Ci.nsISupports) &&
!iid.equals(Ci.nsICacheListener)) {
throw Cr.NS_ERROR_NO_INTERFACE;
}
return this;
},
onCacheEntryAvailable: function(desc, accessGranted, status) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
if (!desc) {
this.fetch(this.callback);
return;
}
var stream = desc.QueryInterface(Ci.nsICacheEntryDescriptor).openInputStream(0);
var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
.createInstance(Components.interfaces.nsIScriptableInputStream);
sstream.init(stream);
this.contents[desc.key] = sstream.read(sstream.available());
sstream.close();
desc.close();
this.fetch(this.callback);
},
fetch: function(callback)
{
this.callback = callback;
if (this.urls.length == 0) {
callback(this.contents);
return;
}
var url = this.urls.shift();
var self = this;
var cacheService = Cc["@mozilla.org/network/cache-service;1"]
.getService(Ci.nsICacheService);
var cacheSession = cacheService.createSession("HTTP-offline",
Ci.nsICache.STORE_OFFLINE,
true);
cacheSession.asyncOpenCacheEntry(url, Ci.nsICache.ACCESS_READ, this);
}
};
var OfflineTest = {
_slaveWindow: null,
@ -103,6 +160,11 @@ is: function(a, b, name)
return this._masterWindow.SimpleTest.is(a, b, name);
},
isnot: function(a, b, name)
{
return this._masterWindow.SimpleTest.isnot(a, b, name);
},
clear: function()
{
// Clear the ownership list
@ -179,7 +241,7 @@ priv: function(func)
var self = this;
return function() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
func();
func(arguments);
}
},
@ -191,7 +253,7 @@ checkCache: function(url, expectEntry)
Ci.nsICache.STORE_OFFLINE,
true);
try {
var entry = cacheSession.openCacheEntry(url, Ci.nsICache.ACCESS_READ, true);
var entry = cacheSession.openCacheEntry(url, Ci.nsICache.ACCESS_READ, false);
if (expectEntry) {
this.ok(true, url + " should exist in the offline cache");
} else {
@ -199,15 +261,19 @@ checkCache: function(url, expectEntry)
}
entry.close();
} catch (e) {
// this constant isn't in Components.results
const kNetBase = 2152398848; // 0x804B0000
var NS_ERROR_CACHE_KEY_NOT_FOUND = kNetBase + 61
if (e.result == NS_ERROR_CACHE_KEY_NOT_FOUND) {
if (expectEntry) {
this.ok(false, url + " should exist in the offline cache");
} else {
this.ok(true, url + " should not exist in the offline cache");
}
} else if (e.result == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
// There was a cache key that we couldn't access yet, that's good enough.
if (expectEntry) {
this.ok(true, url + " should exist in the offline cache");
} else {
this.ok(false, url + " should not exist in the offline cache");
}
} else {
throw e;
}

View File

@ -0,0 +1,85 @@
<html xmlns="http://www.w3.org/1999/xhtml" manifest="http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/changingManifest.sjs">
<head>
<title>changing manifest test</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
<script type="text/javascript">
var gGotChecking = false;
var gGotDownloading = false;
var g1SecUrl = "http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/changing1Sec.sjs";
var g1HourUrl = "http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/changing1Hour.sjs";
var gCacheContents = null;
function manifestUpdatedAgain()
{
OfflineTest.ok(gGotChecking, "Should get a checking event on the second update");
OfflineTest.ok(gGotDownloading, "Should get a downloading event on the second update");
// Get the initial contents of the first two files.
fetcher = new OfflineCacheContents([g1SecUrl, g1HourUrl]);
fetcher.fetch(function(contents) {
// Make sure the contents of the 1-second-expiration file have changed,
// but that the 1-hour-expiration has not.
OfflineTest.isnot(gCacheContents[g1SecUrl], contents[g1SecUrl], "1-second expiration should have changed");
OfflineTest.is(gCacheContents[g1HourUrl], contents[g1HourUrl], "1-hour expiration should not have changed");
OfflineTest.teardown();
OfflineTest.finish();
});
}
function failAndFinish(e) {
OfflineTest.ok(false, "Unexpected event: " + e.type);
OfflineTest.teardown();
OfflineTest.finish();
}
function manifestUpdated()
{
OfflineTest.ok(gGotChecking, "Should get a checking event");
OfflineTest.ok(gGotDownloading, "Should get a downloading event");
// Get the initial contents of the first two files.
fetcher = new OfflineCacheContents([g1SecUrl, g1HourUrl]);
fetcher.fetch(function(contents) {
gCacheContents = contents;
// Now make sure applicationCache.update() does what we expect.
applicationCache.oncached = OfflineTest.priv(manifestUpdatedAgain);
applicationCache.onnoupdate = failAndFinish;
gGotChecking = false;
gGotDownloading = false;
// The changing versions give out a new version each second,
// make sure it has time to grab a new version, and for the
// 1-second cache timeout to pass.
window.setTimeout("applicationCache.update()", 5000);
});
}
if (OfflineTest.setup()) {
applicationCache.onerror = failAndFinish;
applicationCache.onnoupdate = failAndFinish;
applicationCache.onchecking = function() { gGotChecking = true; };
applicationCache.ondownloading = function() { gGotDownloading = true; };
applicationCache.oncached = OfflineTest.priv(manifestUpdated);
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body>
</body>
</html>

View File

@ -1465,8 +1465,10 @@ nsCacheService::ActivateEntry(nsCacheRequest * request,
if (entry &&
((request->AccessRequested() == nsICache::ACCESS_WRITE) ||
(entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) &&
request->WillDoomEntriesIfExpired())))
((request->StoragePolicy() != nsICache::STORE_OFFLINE) &&
(entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) &&
request->WillDoomEntriesIfExpired()))))
{
// this is FORCE-WRITE request or the entry has expired
rv = DoomEntry_Internal(entry);

View File

@ -1540,10 +1540,11 @@ nsHttpChannel::UpdateExpirationTime()
{
NS_ENSURE_TRUE(mResponseHead, NS_ERROR_FAILURE);
nsresult rv;
PRUint32 expirationTime = 0;
if (!mResponseHead->MustValidate()) {
PRUint32 freshnessLifetime = 0;
nsresult rv;
rv = mResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
if (NS_FAILED(rv)) return rv;
@ -1569,7 +1570,16 @@ nsHttpChannel::UpdateExpirationTime()
expirationTime = now;
}
}
return mCacheEntry->SetExpirationTime(expirationTime);
rv = mCacheEntry->SetExpirationTime(expirationTime);
NS_ENSURE_SUCCESS(rv, rv);
if (mOfflineCacheEntry) {
rv = mOfflineCacheEntry->SetExpirationTime(expirationTime);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
// CheckCache is called from Connect after a cache entry has been opened for
@ -1631,10 +1641,11 @@ nsHttpChannel::CheckCache()
// Don't bother to validate LOAD_ONLY_FROM_CACHE items.
// Don't bother to validate items that are read-only,
// unless they are read-only because of INHIBIT_CACHING.
// unless they are read-only because of INHIBIT_CACHING or because
// we're updating the offline cache.
if (mLoadFlags & LOAD_ONLY_FROM_CACHE ||
(mCacheAccess == nsICache::ACCESS_READ &&
!(mLoadFlags & INHIBIT_CACHING))) {
!((mLoadFlags & INHIBIT_CACHING) || mCacheForOfflineUse))) {
mCachedContentIsValid = PR_TRUE;
return NS_OK;
}
@ -2054,6 +2065,17 @@ nsHttpChannel::InitOfflineCacheEntry()
return NS_OK;
}
// This entry's expiration time should match the main entry's expiration
// time. UpdateExpirationTime() will keep it in sync once the offline
// cache entry has been created.
if (mCacheEntry) {
PRUint32 expirationTime;
nsresult rv = mCacheEntry->GetExpirationTime(&expirationTime);
NS_ENSURE_SUCCESS(rv, rv);
mOfflineCacheEntry->SetExpirationTime(expirationTime);
}
return AddCacheEntryHeaders(mOfflineCacheEntry);
}