Bug 818667 - Put HTTP cache into app jars. r=michal.novotny
authorJosh Matthews <josh@joshmatthews.net>
Fri, 28 Dec 2012 12:50:23 -0500
changeset 126270 83c45c2f712b3866800fe973b4ccf9a6f195f59c
parent 126269 b3e769659c3f7e858a95dcd1f1a90eef3b8ec5d6
child 126271 e19528d96c1aa51a5270f6e012ecf36a459136e7
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmichal
bugs818667
milestone20.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 818667 - Put HTTP cache into app jars. r=michal.novotny
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
netwerk/test/unit/test_cache_jar.js
netwerk/test/unit/xpcshell.ini
netwerk/test/unit_ipc/test_cache_jar_wrap.js
netwerk/test/unit_ipc/xpcshell.ini
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -72,32 +72,16 @@ AccumulateCacheHitTelemetry(Telemetry::I
     // MOZ_ASSERT(deviceHistogram != UNKNOWN_DEVICE || hitOrMiss == kCacheMissed);
 
     Telemetry::Accumulate(Telemetry::HTTP_CACHE_DISPOSITION_2, hitOrMiss);
     if (deviceHistogram != UNKNOWN_DEVICE) {
         Telemetry::Accumulate(deviceHistogram, hitOrMiss);
     }
 }
 
-const char *
-GetCacheSessionNameForStoragePolicy(nsCacheStoragePolicy storagePolicy,
-                                    bool isPrivate)
-{
-    MOZ_ASSERT(!isPrivate || storagePolicy == nsICache::STORE_IN_MEMORY);
-
-    switch (storagePolicy) {
-    case nsICache::STORE_IN_MEMORY:
-        return isPrivate ? "HTTP-memory-only-PB" : "HTTP-memory-only";
-    case nsICache::STORE_OFFLINE:
-        return "HTTP-offline";
-    default:
-        return "HTTP";
-    }
-}
-
 // Computes and returns a SHA1 hash of the input buffer. The input buffer
 // must be a null-terminated string.
 nsresult
 Hash(const char *buf, nsACString &hash)
 {
     nsresult rv;
       
     nsCOMPtr<nsICryptoHash> hasher
@@ -2475,27 +2459,44 @@ nsHttpChannel::OnOfflineCacheEntryAvaila
         }
     }
 
     bool usingSSL = false;
     (void) mURI->SchemeIs("https", &usingSSL);
     return OpenNormalCacheEntry(usingSSL);
 }
 
+static void
+GetAppInfo(nsIChannel* aChannel, uint32_t* aAppId, bool* aIsInBrowser)
+{
+    nsCOMPtr<nsILoadContext> loadContext;
+    NS_QueryNotificationCallbacks(aChannel, loadContext);
+    *aAppId = NECKO_NO_APP_ID;
+    *aIsInBrowser = false;
+    if (loadContext) {
+        loadContext->GetAppId(aAppId);
+        loadContext->GetIsInBrowserElement(aIsInBrowser);
+    }
+}
 
 nsresult
 nsHttpChannel::OpenNormalCacheEntry(bool usingSSL)
 {
     NS_ASSERTION(!mCacheEntry, "We have already mCacheEntry");
 
     nsresult rv;
 
+    uint32_t appId;
+    bool isInBrowser;
+    GetAppInfo(this, &appId, &isInBrowser);
+
     nsCacheStoragePolicy storagePolicy = DetermineStoragePolicy();
-    nsDependentCString clientID(
-        GetCacheSessionNameForStoragePolicy(storagePolicy, mPrivateBrowsing));
+    nsAutoCString clientID;
+    nsHttpHandler::GetCacheSessionNameForStoragePolicy(storagePolicy, mPrivateBrowsing,
+                                                       appId, isInBrowser, clientID);
 
     nsAutoCString cacheKey;
     GenerateCacheKey(mPostID, cacheKey);
 
     nsCacheAccessMode accessRequested;
     rv = DetermineCacheAccess(&accessRequested);
     if (NS_FAILED(rv))
         return rv;
@@ -5892,42 +5893,47 @@ void
 nsHttpChannel::DoInvalidateCacheEntry(const nsCString &key)
 {
     // NOTE:
     // Following comments 24,32 and 33 in bug #327765, we only care about
     // the cache in the protocol-handler, not the application cache.
     // The logic below deviates from the original logic in OpenCacheEntry on
     // one point by using only READ_ONLY access-policy. I think this is safe.
 
+    uint32_t appId;
+    bool isInBrowser;
+    GetAppInfo(this, &appId, &isInBrowser);
+
     // First, find session holding the cache-entry - use current storage-policy
     nsCacheStoragePolicy storagePolicy = DetermineStoragePolicy();
-    const char * clientID =
-        GetCacheSessionNameForStoragePolicy(storagePolicy, mPrivateBrowsing);
+    nsAutoCString clientID;
+    nsHttpHandler::GetCacheSessionNameForStoragePolicy(storagePolicy, mPrivateBrowsing,
+                                                       appId, isInBrowser, clientID);
 
     LOG(("DoInvalidateCacheEntry [channel=%p session=%s policy=%d key=%s]",
-         this, clientID, int(storagePolicy), key.get()));
+         this, clientID.get(), int(storagePolicy), key.get()));
 
     nsresult rv;
     nsCOMPtr<nsICacheService> serv =
         do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
     nsCOMPtr<nsICacheSession> session;
     if (NS_SUCCEEDED(rv)) {
-        rv = serv->CreateSession(clientID, storagePolicy,  
+        rv = serv->CreateSession(clientID.get(), storagePolicy,
                                  nsICache::STREAM_BASED,
                                  getter_AddRefs(session));
     }
     if (NS_SUCCEEDED(rv)) {
         rv = session->SetIsPrivate(mPrivateBrowsing);
     }
     if (NS_SUCCEEDED(rv)) {
         rv = session->DoomEntry(key, nullptr);
     }
 
     LOG(("DoInvalidateCacheEntry [channel=%p session=%s policy=%d key=%s rv=%d]",
-         this, clientID, int(storagePolicy), key.get(), int(rv)));
+         this, clientID.get(), int(storagePolicy), key.get(), int(rv)));
 }
 
 nsCacheStoragePolicy
 nsHttpChannel::DetermineStoragePolicy()
 {
     nsCacheStoragePolicy policy = nsICache::STORE_ANYWHERE;
     if (mPrivateBrowsing)
         policy = nsICache::STORE_IN_MEMORY;
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -31,16 +31,17 @@
 #include "nsReadableUtils.h"
 #include "nsQuickSort.h"
 #include "nsNetUtil.h"
 #include "nsIOService.h"
 #include "nsAsyncRedirectVerifyHelper.h"
 #include "nsSocketTransportService2.h"
 #include "nsAlgorithm.h"
 #include "ASpdySession.h"
+#include "mozIApplicationClearPrivateDataParams.h"
 
 #include "nsIXULAppInfo.h"
 
 #include "mozilla/net/NeckoChild.h"
 
 #if defined(XP_UNIX)
 #include <sys/utsname.h>
 #endif
@@ -320,16 +321,17 @@ nsHttpHandler::Init()
     if (mObserverService) {
         mObserverService->AddObserver(this, "profile-change-net-teardown", true);
         mObserverService->AddObserver(this, "profile-change-net-restore", true);
         mObserverService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
         mObserverService->AddObserver(this, "net:clear-active-logins", true);
         mObserverService->AddObserver(this, "net:prune-dead-connections", true);
         mObserverService->AddObserver(this, "net:failed-to-process-uri-content", true);
         mObserverService->AddObserver(this, "last-pb-context-exited", true);
+        mObserverService->AddObserver(this, "webapps-clear-data", true);
     }
 
     return NS_OK;
 }
 
 nsresult
 nsHttpHandler::InitConnectionMgr()
 {
@@ -1576,20 +1578,72 @@ nsHttpHandler::GetOscpu(nsACString &valu
 
 NS_IMETHODIMP
 nsHttpHandler::GetMisc(nsACString &value)
 {
     value = mMisc;
     return NS_OK;
 }
 
+/*static*/ void
+nsHttpHandler::GetCacheSessionNameForStoragePolicy(
+        nsCacheStoragePolicy storagePolicy,
+        bool isPrivate,
+        uint32_t appId,
+        bool inBrowser,
+        nsACString& sessionName)
+{
+    MOZ_ASSERT(!isPrivate || storagePolicy == nsICache::STORE_IN_MEMORY);
+
+    switch (storagePolicy) {
+        case nsICache::STORE_IN_MEMORY:
+            sessionName.AssignASCII(isPrivate ? "HTTP-memory-only-PB" : "HTTP-memory-only");
+            break;
+        case nsICache::STORE_OFFLINE:
+            sessionName.AssignLiteral("HTTP-offline");
+            break;
+        default:
+            sessionName.AssignLiteral("HTTP");
+            break;
+    }
+    if (appId != NECKO_NO_APP_ID || inBrowser) {
+        sessionName.Append('~');
+        sessionName.AppendInt(appId);
+        sessionName.Append('~');
+        sessionName.AppendInt(inBrowser);
+    }
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpHandler::nsIObserver
 //-----------------------------------------------------------------------------
 
+static void
+EvictCacheSession(nsCacheStoragePolicy aPolicy,
+                  bool aPrivateBrowsing,
+                  uint32_t aAppId,
+                  bool aInBrowser)
+{
+    nsAutoCString clientId;
+    nsHttpHandler::GetCacheSessionNameForStoragePolicy(aPolicy,
+                                                       aPrivateBrowsing,
+                                                       aAppId, aInBrowser,
+                                                       clientId);
+    nsCOMPtr<nsICacheService> serv =
+        do_GetService(NS_CACHESERVICE_CONTRACTID);
+    nsCOMPtr<nsICacheSession> session;
+    nsresult rv = serv->CreateSession(clientId.get(),
+                                      nsICache::STORE_ANYWHERE,
+                                      nsICache::STREAM_BASED,
+                                      getter_AddRefs(session));
+    if (NS_SUCCEEDED(rv) && session) {
+        session->EvictEntries();
+    }
+}
+
 NS_IMETHODIMP
 nsHttpHandler::Observe(nsISupports *subject,
                        const char *topic,
                        const PRUnichar *data)
 {
     LOG(("nsHttpHandler::Observe [topic=\"%s\"]\n", topic));
 
     if (strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
@@ -1630,16 +1684,55 @@ nsHttpHandler::Observe(nsISupports *subj
     else if (strcmp(topic, "net:failed-to-process-uri-content") == 0) {
         nsCOMPtr<nsIURI> uri = do_QueryInterface(subject);
         if (uri && mConnMgr)
             mConnMgr->ReportFailedToProcess(uri);
     }
     else if (strcmp(topic, "last-pb-context-exited") == 0) {
         mPrivateAuthCache.ClearAll();
     }
+    else if (strcmp(topic, "webapps-clear-data") == 0) {
+        nsCOMPtr<mozIApplicationClearPrivateDataParams> params =
+                do_QueryInterface(subject);
+        if (!params) {
+            NS_ERROR("'webapps-clear-data' notification's subject should be a mozIApplicationClearPrivateDataParams");
+            return NS_ERROR_UNEXPECTED;
+        }
+
+        uint32_t appId;
+        bool browserOnly;
+        nsresult rv = params->GetAppId(&appId);
+        NS_ENSURE_SUCCESS(rv, rv);
+        rv = params->GetBrowserOnly(&browserOnly);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        MOZ_ASSERT(appId != NECKO_UNKNOWN_APP_ID);
+
+        // Now we ensure that all unique session name combinations are cleared.
+        struct {
+            nsCacheStoragePolicy policy;
+            bool privateBrowsing;
+        } policies[] = { {nsICache::STORE_OFFLINE, false},
+                         {nsICache::STORE_IN_MEMORY, false},
+                         {nsICache::STORE_IN_MEMORY, true},
+                         {nsICache::STORE_ON_DISK, false} };
+
+        for (uint32_t i = 0; i < NS_ARRAY_LENGTH(policies); i++) {
+            EvictCacheSession(policies[i].policy,
+                              policies[i].privateBrowsing,
+                              appId, browserOnly);
+
+            if (!browserOnly) {
+                EvictCacheSession(policies[i].policy,
+                                  policies[i].privateBrowsing,
+                                  appId, true);
+            }
+        }
+
+    }
 
     return NS_OK;
 }
 
 // nsISpeculativeConnect
 
 NS_IMETHODIMP
 nsHttpHandler::SpeculativeConnect(nsIURI *aURI,
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -248,16 +248,22 @@ public:
     
     PRIntervalTime GetPipelineTimeout()   { return mPipelineReadTimeout; }
 
     mozilla::net::SpdyInformation *SpdyInfo() { return &mSpdyInfo; }
 
     // returns true in between Init and Shutdown states
     bool Active() { return mHandlerActive; }
 
+    static void GetCacheSessionNameForStoragePolicy(
+            nsCacheStoragePolicy storagePolicy,
+            bool isPrivate,
+            uint32_t appId,
+            bool inBrowser,
+            nsACString& sessionName);
 private:
 
     //
     // Useragent/prefs helper methods
     //
     void     BuildUserAgent();
     void     InitUserAgentComponents();
     void     PrefsChanged(nsIPrefBranch *prefs, const char *pref);
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_cache_jar.js
@@ -0,0 +1,108 @@
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var httpserv = null;
+var handlers_called = 0;
+
+function cached_handler(metadata, response) {
+  response.setHeader("Content-Type", "text/plain", false);
+  response.setHeader("Cache-Control", "max-age=10000", false);
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  var body = "0123456789";
+  response.bodyOutputStream.write(body, body.length);
+  handlers_called++;
+}
+
+function makeChan(url, appId, inBrowser) {
+  var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+  var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel);
+  chan.notificationCallbacks = {
+    appId: appId,
+    isInBrowserElement: inBrowser,
+    QueryInterface: function(iid) {
+      if (iid.equals(Ci.nsILoadContext))
+        return this;
+      throw Cr.NS_ERROR_NO_INTERFACE;
+    },
+    getInterface: function(iid) { return this.QueryInterface(iid); }
+  };
+  return chan;
+}
+
+var firstTests = [[0, false, 1], [0, true, 1], [1, false, 1], [1, true, 1]];
+var secondTests = [[0, false, 0], [0, true, 0], [1, false, 0], [1, true, 1]];
+var thirdTests = [[0, false, 0], [0, true, 0], [1, false, 1], [1, true, 1]];
+
+function run_all_tests() {
+  for (let test of firstTests) {
+    handlers_called = 0;
+    var chan = makeChan("http://localhost:4444/cached", test[0], test[1]);
+    chan.asyncOpen(new ChannelListener(doneFirstLoad, test[2]), null);
+    yield;
+  }
+
+  // We can't easily cause webapp data to be cleared from the child process, so skip
+  // the rest of these tests.
+  let procType = Cc["@mozilla.org/xre/runtime;1"].getService(Ci.nsIXULRuntime).processType;
+  if (procType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
+    return;
+
+  let subject = {
+    appId: 1,
+    browserOnly: true,
+    QueryInterface: XPCOMUtils.generateQI([Ci.mozIApplicationClearPrivateDataParams])
+  };
+  Services.obs.notifyObservers(subject, "webapps-clear-data", null);
+
+  for (let test of secondTests) {
+    handlers_called = 0;
+    var chan = makeChan("http://localhost:4444/cached", test[0], test[1]);
+    chan.asyncOpen(new ChannelListener(doneFirstLoad, test[2]), null);
+    yield;
+  }
+
+  subject = {
+    appId: 1,
+    browserOnly: false,
+    QueryInterface: XPCOMUtils.generateQI([Ci.mozIApplicationClearPrivateDataParams])
+  };
+  Services.obs.notifyObservers(subject, "webapps-clear-data", null);
+
+  for (let test of thirdTests) {
+    handlers_called = 0;
+    var chan = makeChan("http://localhost:4444/cached", test[0], test[1]);
+    chan.asyncOpen(new ChannelListener(doneFirstLoad, test[2]), null);
+    yield;
+  }
+}
+
+let gTests;
+function run_test() {
+  do_test_pending();
+  httpserv = new HttpServer();
+  httpserv.registerPathHandler("/cached", cached_handler);
+  httpserv.start(4444);
+  gTests = run_all_tests();
+  gTests.next();
+}
+
+function doneFirstLoad(req, buffer, expected) {
+  // Load it again, make sure it hits the cache
+  var chan = makeChan("http://localhost:4444/cached", 0, false);
+  chan.asyncOpen(new ChannelListener(doneSecondLoad, expected), null);
+}
+
+function doneSecondLoad(req, buffer, expected) {
+  do_check_eq(handlers_called, expected);
+  try {
+    gTests.next();
+  } catch (x) {
+    do_test_finished();
+  }
+}
\ No newline at end of file
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -89,16 +89,17 @@ fail-if = os == "android"
 [test_bug660066.js]
 [test_bug667907.js]
 [test_bug667818.js]
 [test_bug669001.js]
 [test_bug712914_secinfo_validation.js]
 [test_bug770243.js]
 [test_doomentry.js]
 [test_cacheflags.js]
+[test_cache_jar.js]
 [test_channel_close.js]
 [test_compareURIs.js]
 [test_compressappend.js]
 [test_content_encoding_gzip.js]
 [test_content_sniffer.js]
 [test_cookie_header.js]
 [test_cookiejars.js]
 [test_data_protocol.js]
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cache_jar_wrap.js
@@ -0,0 +1,3 @@
+function run_test() {
+  run_test_in_child("../unit/test_cache_jar.js");
+}
\ No newline at end of file
--- a/netwerk/test/unit_ipc/xpcshell.ini
+++ b/netwerk/test/unit_ipc/xpcshell.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 head = head_channels_clone.js
 tail = 
 
 [test_bug248970_cookie_wrap.js]
 [test_cacheflags_wrap.js]
+[test_cache_jar_wrap.js]
 [test_channel_close_wrap.js]
 [test_cookie_header_wrap.js]
 [test_cookiejars_wrap.js]
 [test_duplicate_headers_wrap.js]
 [test_event_sink_wrap.js]
 [test_head_wrap.js]
 [test_headers_wrap.js]
 [test_httpsuspend_wrap.js]