Skip to content

Instantly share code, notes, and snippets.

@valenting
Created December 1, 2015 23:52
Show Gist options
  • Save valenting/d31c1add96a6bc73c190 to your computer and use it in GitHub Desktop.
Save valenting/d31c1add96a6bc73c190 to your computer and use it in GitHub Desktop.
# HG changeset patch
# User Valentin Gosu <[email protected]>
# Parent f97f55fc53b5d63e90ad875f3a75028ef4dc70bb
diff --git a/netwerk/cache2/CacheEntry.cpp b/netwerk/cache2/CacheEntry.cpp
--- a/netwerk/cache2/CacheEntry.cpp
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -1086,16 +1086,22 @@ NS_IMETHODIMP CacheEntry::GetIsForcedVal
}
*aIsForcedValid = CacheStorageService::Self()->IsForcedValidEntry(key);
LOG(("CacheEntry::GetIsForcedValid [this=%p, IsForcedValid=%d]", this, *aIsForcedValid));
return NS_OK;
}
+NS_IMETHODIMP CacheEntry::GetIsPinned(bool *aIsPinned)
+{
+ *aIsPinned = mPinned;
+ return NS_OK;
+}
+
NS_IMETHODIMP CacheEntry::ForceValidFor(uint32_t aSecondsToTheFuture)
{
LOG(("CacheEntry::ForceValidFor [this=%p, aSecondsToTheFuture=%d]", this, aSecondsToTheFuture));
nsAutoCString key;
nsresult rv = HashingKeyWithStorage(key);
if (NS_FAILED(rv)) {
return rv;
diff --git a/netwerk/cache2/OldWrappers.cpp b/netwerk/cache2/OldWrappers.cpp
--- a/netwerk/cache2/OldWrappers.cpp
+++ b/netwerk/cache2/OldWrappers.cpp
@@ -367,16 +367,22 @@ NS_IMETHODIMP
}
NS_IMETHODIMP _OldCacheEntryWrapper::GetIsForcedValid(bool *aIsForcedValid)
{
// Unused stub
return NS_ERROR_NOT_IMPLEMENTED;
}
+NS_IMETHODIMP _OldCacheEntryWrapper::GetIsPinned(bool *aIsPinned)
+{
+ // Unused stub
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
NS_IMETHODIMP _OldCacheEntryWrapper::ForceValidFor(uint32_t aSecondsToTheFuture)
{
// Unused stub
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMPL_ISUPPORTS(_OldCacheEntryWrapper, nsICacheEntry)
diff --git a/netwerk/cache2/OldWrappers.h b/netwerk/cache2/OldWrappers.h
--- a/netwerk/cache2/OldWrappers.h
+++ b/netwerk/cache2/OldWrappers.h
@@ -120,16 +120,17 @@ public:
nsresult GetDataSize(uint32_t *aDataSize)
{
return mOldInfo->GetDataSize(aDataSize);
}
NS_IMETHOD AsyncDoom(nsICacheEntryDoomCallback* listener) override;
NS_IMETHOD GetPersistent(bool *aPersistToDisk) override;
NS_IMETHOD GetIsForcedValid(bool *aIsForcedValid) override;
+ NS_IMETHOD GetIsPinned(bool *aIsPinned) override;
NS_IMETHOD ForceValidFor(uint32_t aSecondsToTheFuture) override;
NS_IMETHOD SetValid() override { return NS_OK; }
NS_IMETHOD MetaDataReady() override { return NS_OK; }
NS_IMETHOD Recreate(bool, nsICacheEntry**) override;
NS_IMETHOD GetDataSize(int64_t *size) override;
NS_IMETHOD OpenInputStream(int64_t offset, nsIInputStream * *_retval) override;
NS_IMETHOD OpenOutputStream(int64_t offset, nsIOutputStream * *_retval) override;
NS_IMETHOD MaybeMarkValid() override;
diff --git a/netwerk/cache2/nsICacheEntry.idl b/netwerk/cache2/nsICacheEntry.idl
--- a/netwerk/cache2/nsICacheEntry.idl
+++ b/netwerk/cache2/nsICacheEntry.idl
@@ -80,16 +80,21 @@ interface nsICacheEntry : nsISupports
/**
* The state variable for whether this entry is currently forced valid.
* Defaults to false for normal cache validation behavior, and will return
* true if the number of seconds set by forceValidFor() has yet to be reached.
*/
readonly attribute boolean isForcedValid;
/**
+ * This variable indicates if the cache entry is pinned or not.
+ */
+ readonly attribute boolean isPinned;
+
+ /**
* Open blocking input stream to cache data. Use the stream transport
* service to asynchronously read this stream on a background thread.
* The returned stream MAY implement nsISeekableStream.
*
* @param offset
* read starting from this offset into the cached data. an offset
* beyond the end of the stream has undefined consequences.
*
diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -3268,16 +3268,24 @@ nsHttpChannel::OnCacheEntryCheck(nsICach
NS_ENSURE_SUCCESS(rv,rv);
bool doValidation = false;
bool canAddImsHeader = true;
bool isForcedValid = false;
entry->GetIsForcedValid(&isForcedValid);
+ bool isPinned = false;
+ entry->GetIsPinned(&isPinned);
+
+ if (mCacheStorage && isPinned && (mLoadFlags & VALIDATE_ALWAYS)) {
+ // TODO: uncomment this line in order to trigger different bug
+ // isForcedValid = false;
+ }
+
// Cached entry is not the entity we request (see bug #633743)
if (ResponseWouldVary(entry)) {
LOG(("Validating based on Vary headers returning TRUE\n"));
canAddImsHeader = false;
doValidation = true;
}
// Check isForcedValid to see if it is possible to skip validation
// See netwerk/cache2/nsICacheEntry.idl for details
diff --git a/netwerk/test/unit/test_cache2-31e-deferred-update.js b/netwerk/test/unit/test_cache2-31e-deferred-update.js
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-31e-deferred-update.js
@@ -0,0 +1,198 @@
+Cu.import('resource://gre/modules/LoadContextInfo.jsm');
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var gRequestIndex = 0;
+function contentHandler(metadata, response)
+{
+ gRequestIndex++;
+
+ dump("## Response number "+gRequestIndex+"\n");
+
+ response.setHeader("Content-Type", 'application/package');
+ response.setHeader("ETag", '"'+gRequestIndex+'"');
+
+ var body = 'number:' + gRequestIndex;
+ response.bodyOutputStream.write(body, body.length);
+}
+
+var gStorage = null;
+var gHttpServer = null;
+
+XPCOMUtils.defineLazyGetter(this, "gPath", function() {
+ return "http://localhost:" + gHttpServer.identity.primaryPort + "/path";
+});
+
+function run_test()
+{
+ do_get_profile();
+
+ // setup test
+ gHttpServer = new HttpServer();
+ gHttpServer.registerPathHandler("/path", contentHandler);
+
+ gHttpServer.start(-1);
+
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
+ let principal = ssm.createCodebasePrincipal(createURI(gPath), {});
+ let loadContext = LoadContextInfo.custom(false, false, principal.originAttributes);
+ let cacheService = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ gStorage = cacheService.diskCacheStorage(loadContext, false);
+
+ add_test(simple_get);
+ add_test(test_update);
+
+ run_next_test();
+}
+
+function get_pinning_storage() {
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
+ let principal = ssm.createCodebasePrincipal(createURI(gPath), {});
+ let loadContext = LoadContextInfo.custom(false, false, principal.originAttributes);
+ let cacheService = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ let storage = cacheService.pinningCacheStorage(loadContext);
+ let pinningStorage = storage.QueryInterface(Ci.nsIPinningCacheStorage);
+ return pinningStorage;
+}
+
+function getChannelForURL(url, notificationCallbacks) {
+ let uri = createURI(url);
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ let principal = ssm.createCodebasePrincipal(uri, {});
+ let tmpChannel =
+ NetUtil.newChannel({
+ uri: url,
+ loadingPrincipal: principal,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
+ });
+
+ if (notificationCallbacks) {
+ // Use custom notificationCallbacks if any.
+ tmpChannel.notificationCallbacks = notificationCallbacks;
+ } else {
+ tmpChannel.notificationCallbacks =
+ new LoadContextCallback(principal.appId,
+ principal.isInBrowserElement,
+ false,
+ false);
+
+ }
+ return tmpChannel;
+}
+
+function simple_get() {
+ do_check_true(true);
+ let pinningStorage = get_pinning_storage();
+ let chan = getChannelForURL(gPath);
+ let cachingChannel = chan.QueryInterface(Ci.nsICachingChannel);
+ cachingChannel.cacheOnlyMetadata = true;
+ cachingChannel.cacheStorage = pinningStorage;
+ chan.asyncOpen({
+ onStartRequest: function(request, ctx) {
+ },
+ onDataAvailable: function(request, context, stream, offset, count) {
+ let content = read_stream(stream, count);
+ },
+ onStopRequest: function(request, ctx, status) {
+ run_next_test();
+ }
+ }, null);
+}
+
+// This test attempts to revalidate the pinned cache entry.
+// If it succeeds, at the end, the cache should contain a pinned cache entry
+// which is different from the previous one (different Etag)
+function test_update() {
+ let pinningStorage = get_pinning_storage();
+ pinningStorage.defer();
+
+ // Open previous cache entry
+ gStorage.asyncOpenURI(createURI(gPath), "", Ci.nsICacheStorage.OPEN_READONLY, {
+ onCacheEntryCheck: function() { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; },
+ onCacheEntryAvailable: function (entry, isnew, appcache, status) {
+
+ do_check_true(!!entry);
+
+ // Create new entry in deferred cache, and copy metadata
+ let pinnedEntry = pinningStorage.openTruncate(createURI(gPath), "");
+ entry.visitMetaData({
+ onMetaDataElement: function(key, value) {
+ pinnedEntry.setMetaDataElement(key,value);
+ }
+ });
+ pinnedEntry.metaDataReady();
+
+ // Open a new channel to download resource
+ let chan = getChannelForURL(gPath);
+ chan.loadFlags |= Ci.nsIRequest.VALIDATE_ALWAYS;
+ let cachingChannel = chan.QueryInterface(Ci.nsICachingChannel);
+ cachingChannel.cacheOnlyMetadata = true;
+ cachingChannel.cacheStorage = pinningStorage;
+
+ dump("About to open channel. Bug1: it stalls\n");
+ // Opening the channel should replace the cache entry with the new one
+ // after revalidation
+ chan.asyncOpen({
+ onStartRequest: function(request, ctx) {
+ dump("onStartRequest\n");
+ },
+ onDataAvailable: function(request, context, stream, offset, count) {
+ let content = read_stream(stream, count);
+ dump("content" + content+ "\n");
+ },
+ onStopRequest: function(request, ctx, status) {
+ dump("onStopRequest\n");
+
+ // Commit the storage.
+ pinningStorage.commit({
+ onCacheStorageCommitted: function(result) {
+ do_check_eq(result, Cr.NS_OK);
+
+ // Now check the entry can be opened.
+ check_open_entry();
+ }
+ });
+ }
+ }, null);
+ }
+ });
+}
+
+function check_open_entry() {
+ dump("Opening:" +gPath+"\n");
+ gStorage.asyncOpenURI(createURI(gPath), "", Ci.nsICacheStorage.OPEN_READONLY, {
+ onCacheEntryCheck: function() { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; },
+ onCacheEntryAvailable: function (entry, isnew, appcache, status) {
+ if (entry) {
+ dump("FOUND ENTRY " + entry.key + "\n");
+ entry.visitMetaData({
+ onMetaDataElement: function(key, value) {
+ dump("Metadata: "+key+":"+value+" [end]\n");
+ }
+ });
+ // Success!
+ run_next_test();
+ } else {
+ dump("NOT FOUND "+gPath+"\n")
+ dump("Bug2: asyncOpenURI did not find the pinned entry.\n");
+ }
+
+ gStorage.asyncVisitStorage({
+ onCacheStorageInfo: function(aEntryCount, aConsumption, aCapacity, aDiskDirectory) {
+
+ },
+ onCacheEntryInfo: function(aURI, aIdEnhance, aDataSize, aFetchCount, aLastModifiedTime, aExpirationTime, aPinned) {
+ dump("aURI: "+aURI.spec +" pinned: "+aPinned+"\n");
+ },
+ onCacheEntryVisitCompleted: function() {
+ }
+ }, true);
+
+ }
+ });
+}
+
diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -75,16 +75,17 @@ skip-if = true
[test_cache2-30a-entry-pinning.js]
[test_cache2-30b-pinning-storage-clear.js]
[test_cache2-30c-pinning-deferred-doom.js]
[test_cache2-30d-pinning-WasEvicted-API.js]
[test_cache2-31a-deferred-commit.js]
[test_cache2-31b-deferred-rollback.js]
[test_cache2-31c-deferred-open-truncate.js]
[test_cache2-31d-deferred-persistence-check.js]
+[test_cache2-31e-deferred-update.js]
[test_partial_response_entry_size_smart_shrink.js]
[test_304_responses.js]
[test_421.js]
[test_cacheForOfflineUse_no-store.js]
[test_307_redirect.js]
[test_NetUtil.js]
[test_URIs.js]
[test_URIs2.js]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment