Bug 916052 - Adapt about:cache to the HTTP cache v2 API, r=michal+MattN
authorHonza Bambas <honzab.moz@firemni.cz>
Wed, 30 Apr 2014 12:39:18 +0200
changeset 181370 2557b51a47a0ea0a6f6ceec5877a76dd42221eb0
parent 181369 8a69388a1a2116e1b9c3b36e11bd78ba9b98d378
child 181371 1d72bfb3cbfb308991536a80011c729ec0d8dcd0
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersmichal
bugs916052
milestone32.0a1
Bug 916052 - Adapt about:cache to the HTTP cache v2 API, r=michal+MattN
browser/base/content/test/general/browser_save_private_link_perwindowpb.js
browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
browser/devtools/styleeditor/test/head.js
netwerk/cache2/AppCacheStorage.cpp
netwerk/cache2/CacheEntry.cpp
netwerk/cache2/CacheFile.cpp
netwerk/cache2/CacheFile.h
netwerk/cache2/CacheFileIOManager.cpp
netwerk/cache2/CacheFileIOManager.h
netwerk/cache2/CacheFileMetadata.cpp
netwerk/cache2/CacheFileMetadata.h
netwerk/cache2/CacheIndex.cpp
netwerk/cache2/CacheIndex.h
netwerk/cache2/CacheLog.h
netwerk/cache2/CacheStorageService.cpp
netwerk/cache2/CacheStorageService.h
netwerk/cache2/OldWrappers.cpp
netwerk/cache2/OldWrappers.h
netwerk/cache2/nsICacheEntry.idl
netwerk/cache2/nsICacheStorageVisitor.idl
netwerk/protocol/about/moz.build
netwerk/protocol/about/nsAboutCache.cpp
netwerk/protocol/about/nsAboutCache.h
netwerk/protocol/about/nsAboutCacheEntry.cpp
netwerk/protocol/about/nsAboutCacheEntry.h
netwerk/test/unit/head_cache2.js
netwerk/test/unit/test_cache2-05-visit.js
netwerk/test/unit/test_cache2-07-visit-memory.js
netwerk/test/unit/test_cache2-11-evict-memory.js
netwerk/test/unit/test_cache2-22-anon-visit.js
toolkit/components/aboutcache/content/aboutCache.js
toolkit/components/aboutcache/jar.mn
toolkit/components/aboutcache/moz.build
toolkit/components/moz.build
--- a/browser/base/content/test/general/browser_save_private_link_perwindowpb.js
+++ b/browser/base/content/test/general/browser_save_private_link_perwindowpb.js
@@ -15,20 +15,21 @@ function test() {
               .getService(Ci.nsICacheStorageService);
 
   function checkDiskCacheFor(filename, goon) {
     Visitor.prototype = {
       onCacheStorageInfo: function(num, consumption)
       {
         info("disk storage contains " + num + " entries");
       },
-      onCacheEntryInfo: function(entry)
+      onCacheEntryInfo: function(uri)
       {
-        info(entry.key);
-        is(entry.key.contains(filename), false, "web content present in disk cache");
+        var urispec = uri.asciiSpec;
+        info(urispec);
+        is(urispec.contains(filename), false, "web content present in disk cache");
       },
       onCacheEntryVisitCompleted: function()
       {
         goon();
       }
     };
     function Visitor() {}
 
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
@@ -82,20 +82,21 @@ function getStorageEntryCount(device, go
   default:
     throw "Unknown device " + device + " at getStorageEntryCount";
   }
 
   var visitor = {
     entryCount: 0,
     onCacheStorageInfo: function (aEntryCount, aConsumption) {
     },
-    onCacheEntryInfo: function(entry)
+    onCacheEntryInfo: function(uri)
     {
-      info(device + ":" + entry.key + "\n");
-      if (entry.key.match(/^http:\/\/example.org\//))
+      var urispec = uri.asciiSpec;
+      info(device + ":" + urispec + "\n");
+      if (urispec.match(/^http:\/\/example.org\//))
         ++this.entryCount;
     },
     onCacheEntryVisitCompleted: function()
     {
       goon(this.entryCount);
     }
   };
 
--- a/browser/devtools/styleeditor/test/head.js
+++ b/browser/devtools/styleeditor/test/head.js
@@ -80,20 +80,21 @@ function checkDiskCacheFor(host, done)
 {
   let foundPrivateData = false;
 
   Visitor.prototype = {
     onCacheStorageInfo: function(num, consumption)
     {
       info("disk storage contains " + num + " entries");
     },
-    onCacheEntryInfo: function(entry)
+    onCacheEntryInfo: function(uri)
     {
-      info(entry.key);
-      foundPrivateData |= entry.key.contains(host);
+      var urispec = uri.asciiSpec;
+      info(urispec);
+      foundPrivateData |= urispec.contains(host);
     },
     onCacheEntryVisitCompleted: function()
     {
       is(foundPrivateData, false, "web content present in disk cache");
       done();
     }
   };
   function Visitor() {}
--- a/netwerk/cache2/AppCacheStorage.cpp
+++ b/netwerk/cache2/AppCacheStorage.cpp
@@ -148,13 +148,24 @@ NS_IMETHODIMP AppCacheStorage::AsyncEvic
 NS_IMETHODIMP AppCacheStorage::AsyncVisitStorage(nsICacheStorageVisitor* aVisitor,
                                                  bool aVisitEntries)
 {
   if (!CacheStorageService::Self())
     return NS_ERROR_NOT_INITIALIZED;
 
   LOG(("AppCacheStorage::AsyncVisitStorage [this=%p, cb=%p]", this, aVisitor));
 
-  return NS_ERROR_NOT_IMPLEMENTED;
+  nsresult rv;
+
+  nsCOMPtr<nsICacheService> serv =
+    do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<_OldVisitCallbackWrapper> cb = new _OldVisitCallbackWrapper(
+    "offline", aVisitor, aVisitEntries, LoadInfo());
+  rv = serv->VisitEntries(cb);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
 }
 
 } // net
 } // mozilla
--- a/netwerk/cache2/CacheEntry.cpp
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -1160,16 +1160,23 @@ NS_IMETHODIMP CacheEntry::GetMetaDataEle
 
 NS_IMETHODIMP CacheEntry::SetMetaDataElement(const char * aKey, const char * aValue)
 {
   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
 
   return mFile->SetElement(aKey, aValue);
 }
 
+NS_IMETHODIMP CacheEntry::VisitMetaData(nsICacheEntryMetaDataVisitor *aVisitor)
+{
+  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+  return mFile->VisitMetaData(aVisitor);
+}
+
 NS_IMETHODIMP CacheEntry::MetaDataReady()
 {
   mozilla::MutexAutoLock lock(mLock);
 
   LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this, StateString(mState)));
 
   MOZ_ASSERT(mState > EMPTY);
 
@@ -1462,17 +1469,17 @@ void CacheEntry::BackgroundOp(uint32_t a
   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
 
   if (aOperations & Ops::FRECENCYUPDATE) {
     #ifndef M_LN2
     #define M_LN2 0.69314718055994530942
     #endif
 
     // Half-life is dynamic, in seconds.
-     static double half_life = CacheObserver::HalfLifeSeconds();
+    static double half_life = CacheObserver::HalfLifeSeconds();
     // Must convert from seconds to milliseconds since PR_Now() gives usecs.
     static double const decay = (M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC);
 
     double now_decay = static_cast<double>(PR_Now()) * decay;
 
     if (mFrecency == 0) {
       mFrecency = now_decay;
     }
--- a/netwerk/cache2/CacheFile.cpp
+++ b/netwerk/cache2/CacheFile.cpp
@@ -835,16 +835,27 @@ CacheFile::SetElement(const char *aKey, 
   MOZ_ASSERT(mMetadata);
   NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
 
   PostWriteTimer();
   return mMetadata->SetElement(aKey, aValue);
 }
 
 nsresult
+CacheFile::VisitMetaData(nsICacheEntryMetaDataVisitor *aVisitor)
+{
+  CacheFileAutoLock lock(this);
+  MOZ_ASSERT(mMetadata);
+  MOZ_ASSERT(mReady);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  return mMetadata->Visit(aVisitor);
+}
+
+nsresult
 CacheFile::ElementsSize(uint32_t *_retval)
 {
   CacheFileAutoLock lock(this);
 
   if (!mMetadata)
     return NS_ERROR_NOT_AVAILABLE;
 
   *_retval = mMetadata->ElementsSize();
--- a/netwerk/cache2/CacheFile.h
+++ b/netwerk/cache2/CacheFile.h
@@ -9,16 +9,17 @@
 #include "CacheFileIOManager.h"
 #include "CacheFileMetadata.h"
 #include "nsRefPtrHashtable.h"
 #include "nsClassHashtable.h"
 #include "mozilla/Mutex.h"
 
 class nsIInputStream;
 class nsIOutputStream;
+class nsICacheEntryMetaDataVisitor;
 
 namespace mozilla {
 namespace net {
 
 class CacheFileInputStream;
 class CacheFileOutputStream;
 class CacheOutputCloseListener;
 class MetadataWriteTimer;
@@ -80,16 +81,17 @@ public:
   NS_IMETHOD SetMemoryOnly();
   NS_IMETHOD Doom(CacheFileListener *aCallback);
 
   nsresult   ThrowMemoryCachedData();
 
   // metadata forwarders
   nsresult GetElement(const char *aKey, char **_retval);
   nsresult SetElement(const char *aKey, const char *aValue);
+  nsresult VisitMetaData(nsICacheEntryMetaDataVisitor *aVisitor);
   nsresult ElementsSize(uint32_t *_retval);
   nsresult SetExpirationTime(uint32_t aExpirationTime);
   nsresult GetExpirationTime(uint32_t *_retval);
   nsresult SetLastModified(uint32_t aLastModified);
   nsresult GetLastModified(uint32_t *_retval);
   nsresult SetFrecency(uint32_t aFrecency);
   nsresult GetFrecency(uint32_t *_retval);
   nsresult GetLastFetched(uint32_t *_retval);
--- a/netwerk/cache2/CacheFileIOManager.cpp
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -14,25 +14,27 @@
 #include "CacheFile.h"
 #include "CacheObserver.h"
 #include "nsIFile.h"
 #include "CacheFileContextEvictor.h"
 #include "nsITimer.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIDirectoryEnumerator.h"
 #include "nsIObserverService.h"
+#include "nsICacheStorageVisitor.h"
 #include "nsISizeOf.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Services.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "private/pprio.h"
 #include "mozilla/VisualEventTracer.h"
 #include "mozilla/Preferences.h"
+#include "nsNetUtil.h"
 
 // include files for ftruncate (or equivalent)
 #if defined(XP_UNIX)
 #include <unistd.h>
 #elif defined(XP_WIN)
 #include <windows.h>
 #undef CreateFile
 #undef CREATE_NEW
@@ -2207,16 +2209,102 @@ void CacheFileIOManager::GetCacheDirecto
   if (!ioMan) {
     return;
   }
 
   nsCOMPtr<nsIFile> file = ioMan->mCacheDirectory;
   file.forget(result);
 }
 
+// static
+nsresult
+CacheFileIOManager::GetEntryInfo(const SHA1Sum::Hash *aHash,
+                                 CacheStorageService::EntryInfoCallback *aCallback)
+{
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+  nsresult rv;
+
+  nsRefPtr<CacheFileIOManager> ioMan = gInstance;
+  if (!ioMan) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  nsAutoCString enhanceId;
+  nsAutoCString uriSpec;
+
+  nsRefPtr<CacheFileHandle> handle;
+  ioMan->mHandles.GetHandle(aHash, false, getter_AddRefs(handle));
+  if (handle) {
+    nsRefPtr<nsILoadContextInfo> info =
+      CacheFileUtils::ParseKey(handle->Key(), &enhanceId, &uriSpec);
+
+    MOZ_ASSERT(info);
+    if (!info) {
+      return NS_OK; // ignore
+    }
+
+    nsRefPtr<CacheStorageService> service = CacheStorageService::Self();
+    if (!service) {
+      return NS_ERROR_NOT_INITIALIZED;
+    }
+
+    // Invokes OnCacheEntryInfo when an existing entry is found
+    if (service->GetCacheEntryInfo(info, enhanceId, uriSpec, aCallback)) {
+      return NS_OK;
+    }
+
+    // When we are here, there is no existing entry and we need
+    // to synchrnously load metadata from a disk file.
+  }
+
+  // Locate the actual file
+  nsCOMPtr<nsIFile> file;
+  ioMan->GetFile(aHash, getter_AddRefs(file));
+
+  // Read metadata from the file synchronously
+  nsRefPtr<CacheFileMetadata> metadata = new CacheFileMetadata();
+  rv = metadata->SyncReadMetadata(file);
+  if (NS_FAILED(rv)) {
+    return NS_OK;
+  }
+
+  // Now get the context + enhance id + URL from the key.
+  nsAutoCString key;
+  metadata->GetKey(key);
+
+  nsRefPtr<nsILoadContextInfo> info =
+    CacheFileUtils::ParseKey(key, &enhanceId, &uriSpec);
+  MOZ_ASSERT(info);
+  if (!info) {
+    return NS_OK;
+  }
+
+  // Pick all data to pass to the callback.
+  int64_t dataSize = metadata->Offset();
+  uint32_t fetchCount;
+  if (NS_FAILED(metadata->GetFetchCount(&fetchCount))) {
+    fetchCount = 0;
+  }
+  uint32_t expirationTime;
+  if (NS_FAILED(metadata->GetExpirationTime(&expirationTime))) {
+    expirationTime = 0;
+  }
+  uint32_t lastModified;
+  if (NS_FAILED(metadata->GetLastModified(&lastModified))) {
+    lastModified = 0;
+  }
+
+  // Call directly on the callback.
+  aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize, fetchCount,
+                         lastModified, expirationTime);
+
+  return NS_OK;
+}
+
 static nsresult
 TruncFile(PRFileDesc *aFD, uint32_t aEOF)
 {
 #if defined(XP_UNIX)
   if (ftruncate(PR_FileDesc2NativeHandle(aFD), aEOF) != 0) {
     NS_ERROR("ftruncate failed");
     return NS_ERROR_FAILURE;
   }
--- a/netwerk/cache2/CacheFileIOManager.h
+++ b/netwerk/cache2/CacheFileIOManager.h
@@ -1,32 +1,34 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef CacheFileIOManager__h__
 #define CacheFileIOManager__h__
 
 #include "CacheIOThread.h"
+#include "CacheStorageService.h"
 #include "nsIEventTarget.h"
 #include "nsITimer.h"
 #include "nsCOMPtr.h"
 #include "mozilla/SHA1.h"
 #include "mozilla/TimeStamp.h"
 #include "nsTArray.h"
 #include "nsString.h"
 #include "nsTHashtable.h"
 #include "prio.h"
 
 //#define DEBUG_HANDLES 1
 
 class nsIFile;
 class nsITimer;
 class nsIDirectoryEnumerator;
 class nsILoadContextInfo;
+class nsICacheStorageVisitor;
 
 namespace mozilla {
 namespace net {
 
 class CacheFile;
 #ifdef DEBUG_HANDLES
 class CacheFileHandlesEntry;
 #endif
@@ -273,16 +275,23 @@ public:
 
   enum EEnumerateMode {
     ENTRIES,
     DOOMED
   };
 
   static void GetCacheDirectory(nsIFile** result);
 
+  // Calls synchronously OnEntryInfo for an entry with the given hash.
+  // Tries to find an existing entry in the service hashtables first, if not
+  // found, loads synchronously from disk file.
+  // Callable on the IO thread only.
+  static nsresult GetEntryInfo(const SHA1Sum::Hash *aHash,
+                               CacheStorageService::EntryInfoCallback *aCallback);
+
   // Memory reporting
   static size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
   static size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
 private:
   friend class CacheFileHandle;
   friend class CacheFileChunk;
   friend class CacheFile;
--- a/netwerk/cache2/CacheFileMetadata.cpp
+++ b/netwerk/cache2/CacheFileMetadata.cpp
@@ -6,16 +6,17 @@
 #include "CacheFileMetadata.h"
 
 #include "CacheFileIOManager.h"
 #include "nsICacheEntry.h"
 #include "CacheHashUtils.h"
 #include "CacheFileChunk.h"
 #include "CacheFileUtils.h"
 #include "nsILoadContextInfo.h"
+#include "nsICacheEntry.h" // for nsICacheEntryMetaDataVisitor
 #include "../cache/nsCacheUtils.h"
 #include "nsIFile.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/DebugOnly.h"
 #include "prnetdb.h"
 
 
 namespace mozilla {
@@ -299,17 +300,20 @@ CacheFileMetadata::SyncReadMetadata(nsIF
   MOZ_ASSERT(!mBuf);
   MOZ_ASSERT(!mWriteBuf);
   MOZ_ASSERT(mKey.IsEmpty());
 
   nsresult rv;
 
   int64_t fileSize;
   rv = aFile->GetFileSize(&fileSize);
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_FAILED(rv)) {
+    // Don't bloat the console
+    return rv;
+  }
 
   PRFileDesc *fd;
   rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0600, &fd);
   NS_ENSURE_SUCCESS(rv, rv);
 
   int64_t offset = PR_Seek64(fd, fileSize - sizeof(uint32_t), PR_SEEK_SET);
   if (offset == -1) {
     PR_Close(fd);
@@ -428,16 +432,38 @@ CacheFileMetadata::SetElement(const char
 
   // Update value
   memcpy(pos, aValue, valueSize);
   mElementsSize = newSize;
 
   return NS_OK;
 }
 
+nsresult
+CacheFileMetadata::Visit(nsICacheEntryMetaDataVisitor *aVisitor)
+{
+  const char *data = mBuf;
+  const char *limit = mBuf + mElementsSize;
+
+  while (data < limit) {
+    // Point to the value part
+    const char *value = data + strlen(data) + 1;
+    MOZ_ASSERT(value < limit, "Metadata elements corrupted");
+
+    aVisitor->OnMetaDataElement(data, value);
+
+    // Skip value part
+    data = value + strlen(value) + 1;
+  }
+
+  MOZ_ASSERT(data == limit, "Metadata elements corrupted");
+
+  return NS_OK;
+}
+
 CacheHash::Hash16_t
 CacheFileMetadata::GetHash(uint32_t aIndex)
 {
   MOZ_ASSERT(aIndex < mHashCount);
   return NetworkEndian::readUint16(&mHashArray[aIndex]);
 }
 
 nsresult
--- a/netwerk/cache2/CacheFileMetadata.h
+++ b/netwerk/cache2/CacheFileMetadata.h
@@ -8,26 +8,28 @@
 #include "CacheFileIOManager.h"
 #include "CacheStorageService.h"
 #include "CacheHashUtils.h"
 #include "CacheObserver.h"
 #include "mozilla/Endian.h"
 #include "nsAutoPtr.h"
 #include "nsString.h"
 
+class nsICacheEntryMetaDataVisitor;
+
 namespace mozilla {
 namespace net {
 
 // By multiplying with the current half-life we convert the frecency
 // to time independent of half-life value.  The range fits 32bits.
 // When decay time changes on next run of the browser, we convert
 // the frecency value to a correct internal representation again.
 // It might not be 100% accurate, but for the purpose it suffice.
 #define FRECENCY2INT(aFrecency) \
-  ((uint32_t)(aFrecency * CacheObserver::HalfLifeSeconds()))
+  ((uint32_t)((aFrecency) * CacheObserver::HalfLifeSeconds()))
 #define INT2FRECENCY(aInt) \
   ((double)(aInt) / (double)CacheObserver::HalfLifeSeconds())
 
 
 #pragma pack(push)
 #pragma pack(1)
 
 class CacheFileMetadataHeader {
@@ -124,16 +126,17 @@ public:
   nsresult SyncReadMetadata(nsIFile *aFile);
 
   bool     IsAnonymous() { return mAnonymous; }
   bool     IsInBrowser() { return mInBrowser; }
   uint32_t AppId()       { return mAppId; }
 
   const char * GetElement(const char *aKey);
   nsresult     SetElement(const char *aKey, const char *aValue);
+  nsresult     Visit(nsICacheEntryMetaDataVisitor *aVisitor);
 
   CacheHash::Hash16_t GetHash(uint32_t aIndex);
   nsresult            SetHash(uint32_t aIndex, CacheHash::Hash16_t aHash);
 
   nsresult SetExpirationTime(uint32_t aExpirationTime);
   nsresult GetExpirationTime(uint32_t *_retval);
   nsresult SetLastModified(uint32_t aLastModified);
   nsresult GetLastModified(uint32_t *_retval);
--- a/netwerk/cache2/CacheIndex.cpp
+++ b/netwerk/cache2/CacheIndex.cpp
@@ -1202,16 +1202,53 @@ CacheIndex::GetCacheSize(uint32_t *_retv
 
   *_retval = index->mIndexStats.Size();
   LOG(("CacheIndex::GetCacheSize() - returning %u", *_retval));
   return NS_OK;
 }
 
 // static
 nsresult
+CacheIndex::GetCacheStats(nsILoadContextInfo *aInfo, uint32_t *aSize, uint32_t *aCount)
+{
+  LOG(("CacheIndex::GetCacheStats() [info=%p]", aInfo));
+
+  nsRefPtr<CacheIndex> index = gInstance;
+
+  if (!index) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  CacheIndexAutoLock lock(index);
+
+  if (!index->IsIndexUsable()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (!aInfo) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  *aSize = 0;
+  *aCount = 0;
+
+  for (uint32_t i = 0; i < index->mFrecencyArray.Length(); ++i) {
+    CacheIndexRecord* record = index->mFrecencyArray[i];
+    if (!CacheIndexEntry::RecordMatchesLoadContextInfo(record, aInfo))
+      continue;
+
+    *aSize += CacheIndexEntry::GetFileSize(record);
+    ++*aCount;
+  }
+
+  return NS_OK;
+}
+
+// static
+nsresult
 CacheIndex::AsyncGetDiskConsumption(nsICacheStorageConsumptionObserver* aObserver)
 {
   LOG(("CacheIndex::AsyncGetDiskConsumption()"));
 
   nsRefPtr<CacheIndex> index = gInstance;
 
   if (!index) {
     return NS_ERROR_NOT_INITIALIZED;
--- a/netwerk/cache2/CacheIndex.h
+++ b/netwerk/cache2/CacheIndex.h
@@ -200,17 +200,21 @@ public:
       LOG(("CacheIndexEntry::SetFileSize() - FileSize is too large, "
            "truncating to %u", kFileSizeMask));
       aFileSize = kFileSizeMask;
     }
     mRec->mFlags &= ~kFileSizeMask;
     mRec->mFlags |= aFileSize;
   }
   // Returns filesize in kilobytes.
-  uint32_t GetFileSize() { return mRec->mFlags & kFileSizeMask; }
+  uint32_t GetFileSize() { return GetFileSize(mRec); }
+  static uint32_t GetFileSize(CacheIndexRecord *aRec)
+  {
+    return aRec->mFlags & kFileSizeMask;
+  }
   bool     IsFileEmpty() { return GetFileSize() == 0; }
 
   void WriteToBuf(void *aBuf)
   {
     CacheIndexRecord *dst = reinterpret_cast<CacheIndexRecord *>(aBuf);
 
     // Copy the whole record to the buffer.
     memcpy(aBuf, mRec, sizeof(CacheIndexRecord));
@@ -558,16 +562,20 @@ public:
   // Returns a hash of the least important entry that should be evicted if the
   // cache size is over limit and also returns a total number of all entries in
   // the index.
   static nsresult GetEntryForEviction(SHA1Sum::Hash *aHash, uint32_t *aCnt);
 
   // Returns cache size in kB.
   static nsresult GetCacheSize(uint32_t *_retval);
 
+  // Synchronously returns the disk occupation and number of entries per-context.
+  // Callable on any thread.
+  static nsresult GetCacheStats(nsILoadContextInfo *aInfo, uint32_t *aSize, uint32_t *aCount);
+
   // Asynchronously gets the disk cache size, used for display in the UI.
   static nsresult AsyncGetDiskConsumption(nsICacheStorageConsumptionObserver* aObserver);
 
   // Returns an iterator that returns entries matching a given context that were
   // present in the index at the time this method was called. If aAddNew is true
   // then the iterator will also return entries created after this call.
   // NOTE: When some entry is removed from index it is removed also from the
   // iterator regardless what aAddNew was passed.
--- a/netwerk/cache2/CacheLog.h
+++ b/netwerk/cache2/CacheLog.h
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef CacheLog__h__
-#define CacheLog__h__
+#ifndef Cache2Log__h__
+#define Cache2Log__h__
 
 #if defined(MOZ_LOGGING)
 #define FORCE_PR_LOG
 #endif
 
 #if defined(PR_LOG)
 #error "If nsCache.h #included it must come before any files that #include prlog.h"
 #endif
--- a/netwerk/cache2/CacheStorageService.cpp
+++ b/netwerk/cache2/CacheStorageService.cpp
@@ -2,34 +2,36 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CacheLog.h"
 #include "CacheStorageService.h"
 #include "CacheFileIOManager.h"
 #include "CacheObserver.h"
 #include "CacheIndex.h"
-
-#include "nsICacheStorageVisitor.h"
-#include "nsIObserverService.h"
+#include "CacheIndexIterator.h"
 #include "CacheStorage.h"
 #include "AppCacheStorage.h"
 #include "CacheEntry.h"
 #include "CacheFileUtils.h"
 
 #include "OldWrappers.h"
 #include "nsCacheService.h"
 #include "nsDeleteDir.h"
 
+#include "nsICacheStorageVisitor.h"
+#include "nsIObserverService.h"
 #include "nsIFile.h"
 #include "nsIURI.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsNetCID.h"
+#include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
+#include "nsWeakReference.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/VisualEventTracer.h"
 #include "mozilla/Services.h"
 
 namespace mozilla {
 namespace net {
 
@@ -158,126 +160,336 @@ void CacheStorageService::ShutdownBackgr
   Pool(true).mFrecencyArray.Clear();
   Pool(true).mExpirationArray.Clear();
 }
 
 // Internal management methods
 
 namespace { // anon
 
-// WalkRunnable
-// Responsible to visit the storage and walk all entries on it asynchronously
-
-class WalkRunnable : public nsRunnable
+// WalkCacheRunnable
+// Base class for particular storage entries visiting
+class WalkCacheRunnable : public nsRunnable
+                        , public CacheStorageService::EntryInfoCallback
 {
-public:
-  WalkRunnable(nsCSubstring const & aContextKey, bool aVisitEntries,
-               bool aUsingDisk,
-               nsICacheStorageVisitor* aVisitor)
-    : mContextKey(aContextKey)
+protected:
+  WalkCacheRunnable(nsICacheStorageVisitor* aVisitor,
+                    bool aVisitEntries)
+    : mService(CacheStorageService::Self())
     , mCallback(aVisitor)
     , mSize(0)
     , mNotifyStorage(true)
     , mVisitEntries(aVisitEntries)
-    , mUsingDisk(aUsingDisk)
   {
+  }
+
+  nsRefPtr<CacheStorageService> mService;
+  nsCOMPtr<nsICacheStorageVisitor> mCallback;
+
+  uint64_t mSize;
+
+  bool mNotifyStorage : 1;
+  bool mVisitEntries : 1;
+};
+
+// WalkMemoryCacheRunnable
+// Responsible to visit memory storage and walk
+// all entries on it asynchronously.
+class WalkMemoryCacheRunnable : public WalkCacheRunnable
+{
+public:
+  WalkMemoryCacheRunnable(nsILoadContextInfo *aLoadInfo,
+                          bool aVisitEntries,
+                          nsICacheStorageVisitor* aVisitor)
+    : WalkCacheRunnable(aVisitor, aVisitEntries)
+  {
+    CacheFileUtils::AppendKeyPrefix(aLoadInfo, mContextKey);
     MOZ_ASSERT(NS_IsMainThread());
   }
 
+  nsresult Walk()
+  {
+    return mService->Dispatch(this);
+  }
+
 private:
   NS_IMETHODIMP Run()
   {
     if (CacheStorageService::IsOnManagementThread()) {
-      LOG(("WalkRunnable::Run - collecting [this=%p, disk=%d]", this, (bool)mUsingDisk));
+      LOG(("WalkMemoryCacheRunnable::Run - collecting [this=%p]", this));
       // First, walk, count and grab all entries from the storage
-      // TODO
-      // - walk files on disk, when the storage is not private
-      //    - should create representative entries only for the time
-      //      of need
 
       mozilla::MutexAutoLock lock(CacheStorageService::Self()->Lock());
 
       if (!CacheStorageService::IsRunning())
         return NS_ERROR_NOT_INITIALIZED;
 
       CacheEntryTable* entries;
       if (sGlobalEntryTables->Get(mContextKey, &entries))
-        entries->EnumerateRead(&WalkRunnable::WalkStorage, this);
+        entries->EnumerateRead(&WalkMemoryCacheRunnable::WalkStorage, this);
 
       // Next, we dispatch to the main thread
-    }
-    else if (NS_IsMainThread()) {
-      LOG(("WalkRunnable::Run - notifying [this=%p, disk=%d]", this, (bool)mUsingDisk));
+    } else if (NS_IsMainThread()) {
+      LOG(("WalkMemoryCacheRunnable::Run - notifying [this=%p]", this));
+
       if (mNotifyStorage) {
         LOG(("  storage"));
+
         // Second, notify overall storage info
-        mCallback->OnCacheStorageInfo(mEntryArray.Length(), mSize);
+        mCallback->OnCacheStorageInfo(mEntryArray.Length(), mSize,
+                                      CacheObserver::MemoryCacheCapacity(), nullptr);
         if (!mVisitEntries)
           return NS_OK; // done
 
         mNotifyStorage = false;
-      }
-      else {
+
+      } else {
         LOG(("  entry [left=%d]", mEntryArray.Length()));
-        // Third, notify each entry until depleted.
+
+        // Third, notify each entry until depleted
         if (!mEntryArray.Length()) {
           mCallback->OnCacheEntryVisitCompleted();
           return NS_OK; // done
         }
 
-        mCallback->OnCacheEntryInfo(mEntryArray[0]);
+        // Grab the next entry
+        nsRefPtr<CacheEntry> entry = mEntryArray[0];
         mEntryArray.RemoveElementAt(0);
 
-        // Dispatch to the main thread again
+        // Invokes this->OnEntryInfo, that calls the callback with all
+        // information of the entry.
+        CacheStorageService::GetCacheEntryInfo(entry, this);
       }
-    }
-    else {
-      MOZ_ASSERT(false);
+    } else {
+      MOZ_CRASH("Bad thread");
       return NS_ERROR_FAILURE;
     }
 
     NS_DispatchToMainThread(this);
     return NS_OK;
   }
 
-  virtual ~WalkRunnable()
+  virtual ~WalkMemoryCacheRunnable()
   {
     if (mCallback)
       ProxyReleaseMainThread(mCallback);
   }
 
   static PLDHashOperator
   WalkStorage(const nsACString& aKey,
               CacheEntry* aEntry,
               void* aClosure)
   {
-    WalkRunnable* walker = static_cast<WalkRunnable*>(aClosure);
+    WalkMemoryCacheRunnable* walker =
+      static_cast<WalkMemoryCacheRunnable*>(aClosure);
 
-    if (!walker->mUsingDisk && aEntry->IsUsingDiskLocked())
+    // Ignore disk entries
+    if (aEntry->IsUsingDiskLocked())
       return PL_DHASH_NEXT;
 
     walker->mSize += aEntry->GetMetadataMemoryConsumption();
 
     int64_t size;
     if (NS_SUCCEEDED(aEntry->GetDataSize(&size)))
       walker->mSize += size;
 
     walker->mEntryArray.AppendElement(aEntry);
     return PL_DHASH_NEXT;
   }
 
+  virtual void OnEntryInfo(const nsACString & aURISpec, const nsACString & aIdEnhance,
+                           int64_t aDataSize, int32_t aFetchCount,
+                           uint32_t aLastModifiedTime, uint32_t aExpirationTime)
+  {
+    nsCOMPtr<nsIURI> uri;
+    nsresult rv = NS_NewURI(getter_AddRefs(uri), aURISpec);
+    if (NS_FAILED(rv))
+      return;
+
+    mCallback->OnCacheEntryInfo(uri, aIdEnhance, aDataSize, aFetchCount,
+                                aLastModifiedTime, aExpirationTime);
+  }
+
+private:
   nsCString mContextKey;
-  nsCOMPtr<nsICacheStorageVisitor> mCallback;
   nsTArray<nsRefPtr<CacheEntry> > mEntryArray;
+};
+
+// WalkDiskCacheRunnable
+// Using the cache index information to get the list of files per context.
+class WalkDiskCacheRunnable : public WalkCacheRunnable
+{
+public:
+  WalkDiskCacheRunnable(nsILoadContextInfo *aLoadInfo,
+                        bool aVisitEntries,
+                        nsICacheStorageVisitor* aVisitor)
+    : WalkCacheRunnable(aVisitor, aVisitEntries)
+    , mLoadInfo(aLoadInfo)
+    , mPass(COLLECT_STATS)
+  {
+  }
+
+  nsresult Walk()
+  {
+    // TODO, bug 998693
+    // Initial index build should be forced here so that about:cache soon
+    // after startup gives some meaningfull results.
+
+    // Dispatch to the INDEX level in hope that very recent cache entries
+    // information gets to the index list before we grab the index iterator
+    // for the first time.  This tries to avoid miss of entries that has
+    // been created right before the visit is required.
+    nsRefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
+    NS_ENSURE_TRUE(thread, NS_ERROR_NOT_INITIALIZED);
+
+    return thread->Dispatch(this, CacheIOThread::INDEX);
+  }
+
+private:
+  // Invokes OnCacheEntryInfo callback for each single found entry.
+  // There is one instance of this class per one entry.
+  class OnCacheEntryInfoRunnable : public nsRunnable
+  {
+  public:
+    OnCacheEntryInfoRunnable(WalkDiskCacheRunnable* aWalker)
+      : mWalker(aWalker)
+    {
+    }
+
+    NS_IMETHODIMP Run()
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+
+      nsCOMPtr<nsIURI> uri;
+      nsresult rv = NS_NewURI(getter_AddRefs(uri), mURISpec);
+      if (NS_FAILED(rv))
+        return NS_OK;
+
+      mWalker->mCallback->OnCacheEntryInfo(
+        uri, mIdEnhance, mDataSize, mFetchCount,
+        mLastModifiedTime, mExpirationTime);
+      return NS_OK;
+    }
+
+    nsRefPtr<WalkDiskCacheRunnable> mWalker;
+
+    nsCString mURISpec;
+    nsCString mIdEnhance;
+    int64_t mDataSize;
+    int32_t mFetchCount;
+    uint32_t mLastModifiedTime;
+    uint32_t mExpirationTime;
+  };
+
+  NS_IMETHODIMP Run()
+  {
+    // The main loop
+    nsresult rv;
 
-  uint64_t mSize;
+    if (CacheStorageService::IsOnManagementThread()) {
+      switch (mPass) {
+      case COLLECT_STATS:
+        // Get quickly the cache stats.
+        uint32_t size;
+        rv = CacheIndex::GetCacheStats(mLoadInfo, &size, &mCount);
+        if (NS_FAILED(rv)) {
+          if (mVisitEntries) {
+            // both onStorageInfo and onCompleted are expected
+            NS_DispatchToMainThread(this);
+          }
+          return NS_DispatchToMainThread(this);
+        }
+
+        mSize = size << 10;
+
+        // Invoke onCacheStorageInfo with valid information.
+        NS_DispatchToMainThread(this);
+
+        if (!mVisitEntries) {
+          return NS_OK; // done
+        }
+
+        mPass = ITERATE_METADATA;
+        // no break
+
+      case ITERATE_METADATA:
+        // Now grab the context iterator.
+        if (!mIter) {
+          rv = CacheIndex::GetIterator(mLoadInfo, true, getter_AddRefs(mIter));
+          if (NS_FAILED(rv)) {
+            // Invoke onCacheEntryVisitCompleted now
+            return NS_DispatchToMainThread(this);
+          }
+        }
+
+        while (true) {
+          if (CacheIOThread::YieldAndRerun())
+            return NS_OK;
+
+          SHA1Sum::Hash hash;
+          rv = mIter->GetNextHash(&hash);
+          if (NS_FAILED(rv))
+            break; // done (or error?)
+
+          // This synchronously invokes onCacheEntryInfo on this class where we
+          // redispatch to the main thread for the consumer callback.
+          CacheFileIOManager::GetEntryInfo(&hash, this);
+        }
 
-  bool mNotifyStorage : 1;
-  bool mVisitEntries : 1;
-  bool mUsingDisk : 1;
+        // Invoke onCacheEntryVisitCompleted on the main thread
+        NS_DispatchToMainThread(this);
+      }
+    } else if (NS_IsMainThread()) {
+      if (mNotifyStorage) {
+        nsCOMPtr<nsIFile> dir;
+        CacheFileIOManager::GetCacheDirectory(getter_AddRefs(dir));
+        mCallback->OnCacheStorageInfo(mCount, mSize, CacheObserver::DiskCacheCapacity(), dir);
+        mNotifyStorage = false;
+      } else {
+        mCallback->OnCacheEntryVisitCompleted();
+      }
+    } else {
+      MOZ_CRASH("Bad thread");
+      return NS_ERROR_FAILURE;
+    }
+
+    return NS_OK;
+  }
+
+  virtual void OnEntryInfo(const nsACString & aURISpec, const nsACString & aIdEnhance,
+                           int64_t aDataSize, int32_t aFetchCount,
+                           uint32_t aLastModifiedTime, uint32_t aExpirationTime)
+  {
+    // Called directly from CacheFileIOManager::GetEntryInfo.
+
+    // Invoke onCacheEntryInfo on the main thread for this entry.
+    nsRefPtr<OnCacheEntryInfoRunnable> info = new OnCacheEntryInfoRunnable(this);
+    info->mURISpec = aURISpec;
+    info->mIdEnhance = aIdEnhance;
+    info->mDataSize = aDataSize;
+    info->mFetchCount = aFetchCount;
+    info->mLastModifiedTime = aLastModifiedTime;
+    info->mExpirationTime = aExpirationTime;
+
+    NS_DispatchToMainThread(info);
+  }
+
+  nsRefPtr<nsILoadContextInfo> mLoadInfo;
+  enum {
+    // First, we collect stats for the load context.
+    COLLECT_STATS,
+
+    // Second, if demanded, we iterate over the entries gethered
+    // from the iterator and call CacheFileIOManager::GetEntryInfo
+    // for each found entry.
+    ITERATE_METADATA,
+  } mPass;
+
+  nsRefPtr<CacheIndexIterator> mIter;
+  uint32_t mCount;
 };
 
 PLDHashOperator CollectPrivateContexts(const nsACString& aKey,
                                        CacheEntryTable* aTable,
                                        void* aClosure)
 {
   nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(aKey);
   if (info && info->IsPrivate()) {
@@ -1259,22 +1471,25 @@ CacheStorageService::WalkStorageEntries(
                                         bool aVisitEntries,
                                         nsICacheStorageVisitor* aVisitor)
 {
   LOG(("CacheStorageService::WalkStorageEntries [cb=%p, visitentries=%d]", aVisitor, aVisitEntries));
   NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
 
   NS_ENSURE_ARG(aStorage);
 
-  nsAutoCString contextKey;
-  CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
+  if (aStorage->WriteToDisk()) {
+    nsRefPtr<WalkDiskCacheRunnable> event =
+      new WalkDiskCacheRunnable(aStorage->LoadInfo(), aVisitEntries, aVisitor);
+    return event->Walk();
+  }
 
-  nsRefPtr<WalkRunnable> event = new WalkRunnable(
-    contextKey, aVisitEntries, aStorage->WriteToDisk(), aVisitor);
-  return Dispatch(event);
+  nsRefPtr<WalkMemoryCacheRunnable> event =
+    new WalkMemoryCacheRunnable(aStorage->LoadInfo(), aVisitEntries, aVisitor);
+  return event->Walk();
 }
 
 void
 CacheStorageService::CacheFileDoomed(nsILoadContextInfo* aLoadContextInfo,
                                      const nsACString & aIdExtension,
                                      const nsACString & aURISpec)
 {
   nsAutoCString contextKey;
@@ -1303,16 +1518,83 @@ CacheStorageService::CacheFileDoomed(nsI
     return;
 
   // Need to remove under the lock to avoid possible race leading
   // to duplication of the entry per its key.
   RemoveExactEntry(entries, entryKey, entry, false);
   entry->DoomAlreadyRemoved();
 }
 
+bool
+CacheStorageService::GetCacheEntryInfo(nsILoadContextInfo* aLoadContextInfo,
+                                       const nsACString & aIdExtension,
+                                       const nsACString & aURISpec,
+                                       EntryInfoCallback *aCallback)
+{
+  nsAutoCString contextKey;
+  CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey);
+
+  nsAutoCString entryKey;
+  CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURISpec, entryKey);
+
+  nsRefPtr<CacheEntry> entry;
+  {
+    mozilla::MutexAutoLock lock(mLock);
+
+    if (mShutdown) {
+      return false;
+    }
+
+    CacheEntryTable* entries;
+    if (!sGlobalEntryTables->Get(contextKey, &entries)) {
+      return false;
+    }
+
+    if (!entries->Get(entryKey, getter_AddRefs(entry))) {
+      return false;
+    }
+  }
+
+  GetCacheEntryInfo(entry, aCallback);
+  return true;
+}
+
+// static
+void
+CacheStorageService::GetCacheEntryInfo(CacheEntry* aEntry,
+                                       EntryInfoCallback *aCallback)
+{
+  nsIURI* uri = aEntry->GetURI();
+  nsAutoCString uriSpec;
+  if (uri) {
+    uri->GetAsciiSpec(uriSpec);
+  }
+
+  nsCString const enhanceId = aEntry->GetEnhanceID();
+  uint32_t dataSize;
+  if (NS_FAILED(aEntry->GetStorageDataSize(&dataSize))) {
+    dataSize = 0;
+  }
+  int32_t fetchCount;
+  if (NS_FAILED(aEntry->GetFetchCount(&fetchCount))) {
+    fetchCount = 0;
+  }
+  uint32_t lastModified;
+  if (NS_FAILED(aEntry->GetLastModified(&lastModified))) {
+    lastModified = 0;
+  }
+  uint32_t expirationTime;
+  if (NS_FAILED(aEntry->GetExpirationTime(&expirationTime))) {
+    expirationTime = 0;
+  }
+
+  aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize,
+                         fetchCount, lastModified, expirationTime);
+}
+
 // nsIMemoryReporter
 
 size_t
 CacheStorageService::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
 {
   CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
 
   size_t n = 0;
--- a/netwerk/cache2/CacheStorageService.h
+++ b/netwerk/cache2/CacheStorageService.h
@@ -83,16 +83,29 @@ public:
   static CacheStorageService* Self() { return sSelf; }
   static nsISupports* SelfISupports() { return static_cast<nsICacheStorageService*>(Self()); }
   nsresult Dispatch(nsIRunnable* aEvent);
   static bool IsRunning() { return sSelf && !sSelf->mShutdown; }
   static bool IsOnManagementThread();
   already_AddRefed<nsIEventTarget> Thread() const;
   mozilla::Mutex& Lock() { return mLock; }
 
+  // Helper thread-safe interface to pass entry info, only difference from
+  // nsICacheStorageVisitor is that instead of nsIURI only the uri spec is
+  // passed.
+  class EntryInfoCallback {
+  public:
+    virtual void OnEntryInfo(const nsACString & aURISpec, const nsACString & aIdEnhance,
+                             int64_t aDataSize, int32_t aFetchCount,
+                             uint32_t aLastModifiedTime, uint32_t aExpirationTime) = 0;
+  };
+
+  // Invokes OnEntryInfo for the given aEntry, synchronously.
+  static void GetCacheEntryInfo(CacheEntry* aEntry, EntryInfoCallback *aVisitor);
+
   // Memory reporting
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
   MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
 
 private:
   virtual ~CacheStorageService();
   void ShutdownBackground();
@@ -174,16 +187,29 @@ private:
    * CacheFileIOManager uses this method to notify CacheStorageService that
    * an active entry was removed. This method is called even if the entry
    * removal was originated by CacheStorageService.
    */
   void CacheFileDoomed(nsILoadContextInfo* aLoadContextInfo,
                        const nsACString & aIdExtension,
                        const nsACString & aURISpec);
 
+  /**
+   * Tries to find an existing entry in the hashtables and synchronously call
+   * OnCacheEntryInfo of the aVisitor callback when found.
+   * @retuns
+   *   true, when the entry has been found that also implies the callbacks has
+   *        beem invoked
+   *   false, when an entry has not been found
+   */
+  bool GetCacheEntryInfo(nsILoadContextInfo* aLoadContextInfo,
+                         const nsACString & aIdExtension,
+                         const nsACString & aURISpec,
+                         EntryInfoCallback *aCallback);
+
 private:
   friend class CacheMemoryConsumer;
 
   /**
    * When memory consumption of this entry radically changes, this method
    * is called to reflect the size of allocated memory.  This call may purge
    * unspecified number of entries from memory (but not from disk).
    */
--- a/netwerk/cache2/OldWrappers.cpp
+++ b/netwerk/cache2/OldWrappers.cpp
@@ -1,29 +1,30 @@
 // Stuff to link the old imp to the new api - will go away!
 
 #include "CacheLog.h"
 #include "OldWrappers.h"
 #include "CacheStorage.h"
 #include "CacheStorageService.h"
 #include "LoadContextInfo.h"
+#include "nsCacheService.h"
 
 #include "nsIURI.h"
-#include "nsICacheService.h"
 #include "nsICacheSession.h"
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheService.h"
 #include "nsIStreamTransportService.h"
 #include "nsIFile.h"
 #include "nsICacheEntryDoomCallback.h"
 #include "nsICacheListener.h"
 #include "nsICacheStorageVisitor.h"
 
 #include "nsServiceManagerUtils.h"
 #include "nsNetCID.h"
+#include "nsNetUtil.h"
 #include "nsProxyRelease.h"
 #include "mozilla/Telemetry.h"
 
 static NS_DEFINE_CID(kStreamTransportServiceCID,
                      NS_STREAMTRANSPORTSERVICE_CID);
 
 static uint32_t const CHECK_MULTITHREADED = nsICacheStorage::CHECK_MULTITHREADED;
 
@@ -119,95 +120,165 @@ NS_IMETHODIMP DoomCallbackWrapper::OnCac
   if (!mCB)
     return NS_ERROR_NULL_POINTER;
 
   mCB->OnCacheEntryDoomed(status);
   mCB = nullptr;
   return NS_OK;
 }
 
+} // anon
+
+// _OldVisitCallbackWrapper
 // Receives visit callbacks from the old API and forwards it to the new API
 
-class VisitCallbackWrapper : public nsICacheVisitor
-{
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSICACHEVISITOR
+NS_IMPL_ISUPPORTS(_OldVisitCallbackWrapper, nsICacheVisitor)
 
-  VisitCallbackWrapper(char* const deviceID,
-                       nsICacheStorageVisitor* cb,
-                       bool visitEntries)
-  : mCB(cb)
-  , mVisitEntries(visitEntries)
-  , mDeviceID(deviceID)
-  {
-    MOZ_COUNT_CTOR(VisitCallbackWrapper);
+_OldVisitCallbackWrapper::~_OldVisitCallbackWrapper()
+{
+  if (!mHit) {
+    // The device has not been found, to not break the chain, simulate
+    // storage info callback.
+    mCB->OnCacheStorageInfo(0, 0, 0, nullptr);
   }
 
-private:
-  virtual ~VisitCallbackWrapper();
-  nsCOMPtr<nsICacheStorageVisitor> mCB;
-  bool mVisitEntries;
-  char* const mDeviceID;
-};
+  if (mVisitEntries) {
+    mCB->OnCacheEntryVisitCompleted();
+  }
 
-NS_IMPL_ISUPPORTS(VisitCallbackWrapper, nsICacheVisitor)
-
-VisitCallbackWrapper::~VisitCallbackWrapper()
-{
-  if (mVisitEntries)
-    mCB->OnCacheEntryVisitCompleted();
-
-  MOZ_COUNT_DTOR(VisitCallbackWrapper);
+  MOZ_COUNT_DTOR(_OldVisitCallbackWrapper);
 }
 
-NS_IMETHODIMP VisitCallbackWrapper::VisitDevice(const char * deviceID,
-                                                nsICacheDeviceInfo *deviceInfo,
-                                                bool *_retval)
+NS_IMETHODIMP _OldVisitCallbackWrapper::VisitDevice(const char * deviceID,
+                                                    nsICacheDeviceInfo *deviceInfo,
+                                                    bool *_retval)
 {
   if (!mCB)
     return NS_ERROR_NULL_POINTER;
 
   *_retval = false;
   if (strcmp(deviceID, mDeviceID)) {
     // Not the device we want to visit
     return NS_OK;
   }
 
+  mHit = true;
+
   nsresult rv;
 
-  uint32_t entryCount;
-  rv = deviceInfo->GetEntryCount(&entryCount);
+  uint32_t capacity;
+  rv = deviceInfo->GetMaximumSize(&capacity);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  uint32_t totalSize;
-  rv = deviceInfo->GetTotalSize(&totalSize);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIFile> dir;
+  if (!strcmp(mDeviceID, "disk")) {
+    nsCacheService::GetDiskCacheDirectory(getter_AddRefs(dir));
+  } else if (!strcmp(mDeviceID, "offline")) {
+    nsCacheService::GetAppCacheDirectory(getter_AddRefs(dir));
+  }
 
-  mCB->OnCacheStorageInfo(entryCount, totalSize);
+  if (mLoadInfo->IsAnonymous()) {
+    // Anonymous visiting reports 0, 0 since we cannot count that
+    // early the number of anon entries.
+    mCB->OnCacheStorageInfo(0, 0, capacity, dir);
+  } else {
+    // Non-anon visitor counts all non-anon + ALL ANON entries,
+    // there is no way to determine the number of entries when
+    // using the old cache APIs - there is no concept of anonymous
+    // storage.
+    uint32_t entryCount;
+    rv = deviceInfo->GetEntryCount(&entryCount);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    uint32_t totalSize;
+    rv = deviceInfo->GetTotalSize(&totalSize);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mCB->OnCacheStorageInfo(entryCount, totalSize, capacity, dir);
+  }
+
   *_retval = mVisitEntries;
-
   return NS_OK;
 }
 
-NS_IMETHODIMP VisitCallbackWrapper::VisitEntry(const char * deviceID,
-                                               nsICacheEntryInfo *entryInfo,
-                                               bool *_retval)
+NS_IMETHODIMP _OldVisitCallbackWrapper::VisitEntry(const char * deviceID,
+                                                   nsICacheEntryInfo *entryInfo,
+                                                   bool *_retval)
 {
   MOZ_ASSERT(!strcmp(deviceID, mDeviceID));
 
-  nsRefPtr<_OldCacheEntryWrapper> wrapper = new _OldCacheEntryWrapper(entryInfo);
-  nsresult rv = mCB->OnCacheEntryInfo(wrapper);
+  nsresult rv;
+
+  *_retval = true;
+
+  // Read all informative properties from the entry.
+  nsXPIDLCString clientId;
+  rv = entryInfo->GetClientID(getter_Copies(clientId));
+  if (NS_FAILED(rv))
+    return NS_OK;
+
+  if (mLoadInfo->IsPrivate() !=
+      StringBeginsWith(clientId, NS_LITERAL_CSTRING("HTTP-memory-only-PB"))) {
+    return NS_OK;
+  }
+
+  nsAutoCString cacheKey, enhanceId;
+  rv = entryInfo->GetKey(cacheKey);
+  if (NS_FAILED(rv))
+    return NS_OK;
+
+  if (StringBeginsWith(cacheKey, NS_LITERAL_CSTRING("anon&"))) {
+    if (!mLoadInfo->IsAnonymous())
+      return NS_OK;
+
+    cacheKey = Substring(cacheKey, 5, cacheKey.Length());
+  } else if (mLoadInfo->IsAnonymous()) {
+    return NS_OK;
+  }
+
+  if (StringBeginsWith(cacheKey, NS_LITERAL_CSTRING("id="))) {
+    int32_t uriSpecEnd = cacheKey.Find("&uri=");
+    if (uriSpecEnd == kNotFound) // Corrupted, ignore
+      return NS_OK;
+
+    enhanceId = Substring(cacheKey, 3, uriSpecEnd - 3);
+    cacheKey = Substring(cacheKey, uriSpecEnd + 1, cacheKey.Length());
+  }
+
+  if (StringBeginsWith(cacheKey, NS_LITERAL_CSTRING("uri="))) {
+    cacheKey = Substring(cacheKey, 4, cacheKey.Length());
+  }
+
+  nsCOMPtr<nsIURI> uri;
+  // cacheKey is strip of any prefixes
+  rv = NS_NewURI(getter_AddRefs(uri), cacheKey);
+  if (NS_FAILED(rv))
+    return NS_OK;
+
+  uint32_t dataSize;
+  if (NS_FAILED(entryInfo->GetDataSize(&dataSize)))
+    dataSize = 0;
+  int32_t fetchCount;
+  if (NS_FAILED(entryInfo->GetFetchCount(&fetchCount)))
+    fetchCount = 0;
+  uint32_t expirationTime;
+  if (NS_FAILED(entryInfo->GetExpirationTime(&expirationTime)))
+    expirationTime = 0;
+  uint32_t lastModified;
+  if (NS_FAILED(entryInfo->GetLastModified(&lastModified)))
+    lastModified = 0;
+
+  // Send them to the consumer.
+  rv = mCB->OnCacheEntryInfo(
+    uri, enhanceId, (int64_t)dataSize, fetchCount, lastModified, expirationTime);
+
   *_retval = NS_SUCCEEDED(rv);
-
   return NS_OK;
 }
 
-} // anon
-
-
 // _OldGetDiskConsumption
 
 //static
 nsresult _OldGetDiskConsumption::Get(nsICacheStorageConsumptionObserver* aCallback)
 {
   nsresult rv;
 
   nsCOMPtr<nsICacheService> serv =
@@ -402,16 +473,45 @@ NS_IMETHODIMP _OldCacheEntryWrapper::Has
 
   *aWriteAccess = !!(mode & nsICache::ACCESS_WRITE);
 
   LOG(("_OldCacheEntryWrapper::HasWriteAccess [this=%p, write-access=%d]", this, *aWriteAccess));
 
   return NS_OK;
 }
 
+namespace { // anon
+
+class MetaDataVisitorWrapper : public nsICacheMetaDataVisitor
+{
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSICACHEMETADATAVISITOR
+  MetaDataVisitorWrapper(nsICacheEntryMetaDataVisitor* cb) : mCB(cb) {}
+  virtual ~MetaDataVisitorWrapper() {}
+  nsCOMPtr<nsICacheEntryMetaDataVisitor> mCB;
+};
+
+NS_IMPL_ISUPPORTS(MetaDataVisitorWrapper, nsICacheMetaDataVisitor)
+
+NS_IMETHODIMP
+MetaDataVisitorWrapper::VisitMetaDataElement(char const * key,
+                                             char const * value,
+                                             bool *goon)
+{
+  *goon = true;
+  return mCB->OnMetaDataElement(key, value);
+}
+
+} // anon
+
+NS_IMETHODIMP _OldCacheEntryWrapper::VisitMetaData(nsICacheEntryMetaDataVisitor* cb)
+{
+  nsRefPtr<MetaDataVisitorWrapper> w = new MetaDataVisitorWrapper(cb);
+  return mOldDesc->VisitMetaData(w);
+}
 
 namespace { // anon
 
 nsresult
 GetCacheSessionNameForStoragePolicy(
         nsCSubstring const &scheme,
         nsCacheStoragePolicy storagePolicy,
         bool isPrivate,
@@ -951,46 +1051,33 @@ NS_IMETHODIMP _OldStorage::AsyncEvictSto
 
 NS_IMETHODIMP _OldStorage::AsyncVisitStorage(nsICacheStorageVisitor* aVisitor,
                                              bool aVisitEntries)
 {
   LOG(("_OldStorage::AsyncVisitStorage"));
 
   NS_ENSURE_ARG(aVisitor);
 
-  if (mLoadInfo->IsAnonymous()) {
-    // There is no concept of 'anonymous' storage in the old cache
-    // since anon cache entries are stored in 'non-anon' storage
-    // with a special prefix.
-    // Just fake we have 0 items with 0 consumption.  This at least
-    // prevents displaying double size in the advanced section of
-    // the Options dialog.
-    aVisitor->OnCacheStorageInfo(0, 0);
-    if (aVisitEntries)
-      aVisitor->OnCacheEntryVisitCompleted();
-    return NS_OK;
-  }
-
   nsresult rv;
 
   nsCOMPtr<nsICacheService> serv =
-      do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+    do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   char* deviceID;
   if (mAppCache || mOfflineStorage) {
     deviceID = const_cast<char*>("offline");
   } else if (!mWriteToDisk || mLoadInfo->IsPrivate()) {
     deviceID = const_cast<char*>("memory");
   } else {
     deviceID = const_cast<char*>("disk");
   }
 
-  nsRefPtr<VisitCallbackWrapper> cb = new VisitCallbackWrapper(
-    deviceID, aVisitor, aVisitEntries);
+  nsRefPtr<_OldVisitCallbackWrapper> cb = new _OldVisitCallbackWrapper(
+    deviceID, aVisitor, aVisitEntries, mLoadInfo);
   rv = serv->VisitEntries(cb);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 // Internal
 
--- a/netwerk/cache2/OldWrappers.h
+++ b/netwerk/cache2/OldWrappers.h
@@ -5,16 +5,17 @@
 
 #include "nsICacheEntry.h"
 #include "nsICacheListener.h"
 #include "nsICacheStorage.h"
 
 #include "nsCOMPtr.h"
 #include "nsICacheEntryOpenCallback.h"
 #include "nsICacheEntryDescriptor.h"
+#include "nsICacheStorageVisitor.h"
 #include "nsThreadUtils.h"
 #include "mozilla/TimeStamp.h"
 
 class nsIURI;
 class nsICacheEntryOpenCallback;
 class nsICacheStorageConsumptionObserver;
 class nsIApplicationCache;
 class nsILoadContextInfo;
@@ -35,16 +36,17 @@ public:
   NS_IMETHOD SetValid() { return NS_OK; }
   NS_IMETHOD MetaDataReady() { return NS_OK; }
   NS_IMETHOD Recreate(bool, nsICacheEntry**);
   NS_IMETHOD GetDataSize(int64_t *size);
   NS_IMETHOD OpenInputStream(int64_t offset, nsIInputStream * *_retval);
   NS_IMETHOD OpenOutputStream(int64_t offset, nsIOutputStream * *_retval);
   NS_IMETHOD MaybeMarkValid();
   NS_IMETHOD HasWriteAccess(bool aWriteOnly, bool *aWriteAccess);
+  NS_IMETHOD VisitMetaData(nsICacheEntryMetaDataVisitor*);
 
   _OldCacheEntryWrapper(nsICacheEntryDescriptor* desc);
   _OldCacheEntryWrapper(nsICacheEntryInfo* info);
 
   virtual ~_OldCacheEntryWrapper();
 
 private:
   _OldCacheEntryWrapper() MOZ_DELETE;
@@ -117,16 +119,43 @@ private:
 
   nsCOMPtr<nsILoadContextInfo> mLoadInfo;
   nsCOMPtr<nsIApplicationCache> mAppCache;
   bool const mWriteToDisk : 1;
   bool const mLookupAppCache : 1;
   bool const mOfflineStorage : 1;
 };
 
+class _OldVisitCallbackWrapper : public nsICacheVisitor
+{
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSICACHEVISITOR
+
+  _OldVisitCallbackWrapper(char const * deviceID,
+                           nsICacheStorageVisitor * cb,
+                           bool visitEntries,
+                           nsILoadContextInfo * aInfo)
+  : mCB(cb)
+  , mVisitEntries(visitEntries)
+  , mDeviceID(deviceID)
+  , mLoadInfo(aInfo)
+  , mHit(false)
+  {
+    MOZ_COUNT_CTOR(_OldVisitCallbackWrapper);
+  }
+
+private:
+  virtual ~_OldVisitCallbackWrapper();
+  nsCOMPtr<nsICacheStorageVisitor> mCB;
+  bool mVisitEntries;
+  char const * mDeviceID;
+  nsCOMPtr<nsILoadContextInfo> mLoadInfo;
+  bool mHit; // set to true when the device was found
+};
+
 class _OldGetDiskConsumption : public nsRunnable,
                                public nsICacheVisitor
 {
 public:
   static nsresult Get(nsICacheStorageConsumptionObserver* aCallback);
 
 private:
   _OldGetDiskConsumption(nsICacheStorageConsumptionObserver* aCallback);
--- a/netwerk/cache2/nsICacheEntry.idl
+++ b/netwerk/cache2/nsICacheEntry.idl
@@ -9,19 +9,19 @@ interface nsIOutputStream;
 interface nsICacheEntryDoomCallback;
 
 // ************************ REMOVE **********************
 typedef long nsCacheAccessMode;
 typedef long nsCacheStoragePolicy;
 
 interface nsICacheListener;
 interface nsIFile;
-interface nsICacheMetaDataVisitor;
+interface nsICacheEntryMetaDataVisitor;
 
-[scriptable, uuid(3058bf1e-5116-41cf-826b-e6981308d414)]
+[scriptable, uuid(972dc51d-df01-4b1e-b7f3-76dbcc603b1e)]
 interface nsICacheEntry : nsISupports
 {
   /**
    * Placeholder for the initial value of expiration time.
    */
   const unsigned long NO_EXPIRATION_TIME = 0xFFFFFFFF;
 
   /**
@@ -120,16 +120,26 @@ interface nsICacheEntry : nsISupports
    * Methods for accessing meta data.  Meta data is a table of key/value
    * string pairs.  The strings do not have to conform to any particular
    * charset, but they must be null terminated.
    */
   string getMetaDataElement(in string key);
   void   setMetaDataElement(in string key, in string value);
 
   /**
+   * Obtain the list of metadata keys this entry keeps.
+   *
+   * NOTE: The callback is invoked under the CacheFile's lock.  It means
+   * there should not be made any calls to the entry from the visitor and
+   * if the values need to be processed somehow, it's better to cache them
+   * and process outside the callback.
+   */
+  void visitMetaData(in nsICacheEntryMetaDataVisitor visitor);
+
+  /**
    * Claims that all metadata on this entry are up-to-date and this entry
    * now can be delivered to other waiting consumers.
    *
    * We need such method since metadata must be delivered synchronously.
    */
   void metaDataReady();
 
   /**
@@ -202,8 +212,21 @@ interface nsICacheEntry : nsISupports
    *    Consumer indicates whether write to the entry is allowed for it.
    *    Depends on implementation how the flag is handled.
    * @returns
    *    true when write access is acquired for this entry,
    *    false otherwise
    */
   boolean hasWriteAccess(in boolean aWriteAllowed);
 };
+
+/**
+ * Argument for nsICacheEntry.visitMetaData, provides access to all metadata
+ * keys and values stored on the entry.
+ */
+[scriptable, uuid(fea3e276-6ba5-4ceb-a581-807d1f43f6d0)]
+interface nsICacheEntryMetaDataVisitor : nsISupports
+{
+  /**
+   * Called over each key / value pair.
+   */
+  void onMetaDataElement(in string key, in string value);
+};
--- a/netwerk/cache2/nsICacheStorageVisitor.idl
+++ b/netwerk/cache2/nsICacheStorageVisitor.idl
@@ -1,23 +1,32 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
-interface nsICacheEntry;
+interface nsIURI;
+interface nsIFile;
 
-[scriptable, uuid(692dda47-3b21-4d0d-853a-f4d27cc324d0)]
+[scriptable, uuid(946bd799-9410-4945-9085-79c7fe019e83)]
 interface nsICacheStorageVisitor : nsISupports
 {
   /**
    */
-  void onCacheStorageInfo(in uint32_t aEntryCount, in uint64_t aConsumption);
+  void onCacheStorageInfo(in uint32_t aEntryCount,
+                          in uint64_t aConsumption,
+                          in uint64_t aCapacity,
+                          in nsIFile aDiskDirectory);
 
   /**
    */
-  void onCacheEntryInfo(in nsICacheEntry aEntry);
+  void onCacheEntryInfo(in nsIURI aURI,
+                        in ACString aIdEnhance,
+                        in int64_t aDataSize,
+                        in long aFetchCount,
+                        in uint32_t aLastModifiedTime,
+                        in uint32_t aExpirationTime);
 
   /**
    */
   void onCacheEntryVisitCompleted();
 };
--- a/netwerk/protocol/about/moz.build
+++ b/netwerk/protocol/about/moz.build
@@ -25,9 +25,10 @@ UNIFIED_SOURCES += [
 FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'necko'
 
 LOCAL_INCLUDES += [
     '../../base/src',
+    '../../cache2',
 ]
--- a/netwerk/protocol/about/nsAboutCache.cpp
+++ b/netwerk/protocol/about/nsAboutCache.cpp
@@ -6,335 +6,496 @@
 #include "nsAboutCache.h"
 #include "nsIInputStream.h"
 #include "nsIStorageStream.h"
 #include "nsIURI.h"
 #include "nsCOMPtr.h"
 #include "nsNetUtil.h"
 #include "nsEscape.h"
 #include "nsAboutProtocolUtils.h"
+#include "nsPrintfCString.h"
 
-#include "nsICacheService.h"
+#include "nsICacheStorageService.h"
+#include "nsICacheStorage.h"
+#include "CacheFileUtils.h"
+#include "CacheObserver.h"
 
-NS_IMPL_ISUPPORTS(nsAboutCache, nsIAboutModule, nsICacheVisitor)
+#include "nsThreadUtils.h"
+
+using namespace mozilla::net;
+
+NS_IMPL_ISUPPORTS(nsAboutCache, nsIAboutModule, nsICacheStorageVisitor)
 
 NS_IMETHODIMP
 nsAboutCache::NewChannel(nsIURI *aURI, nsIChannel **result)
 {
     NS_ENSURE_ARG_POINTER(aURI);
+
     nsresult rv;
-    uint32_t bytesWritten;
 
     *result = nullptr;
-    // Get the cache manager service
-    nsCOMPtr<nsICacheService> cacheService = 
-             do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+
+    nsCOMPtr<nsIInputStream> inputStream;
+    rv = NS_NewPipe(getter_AddRefs(inputStream), getter_AddRefs(mStream),
+                    16384, (uint32_t)-1,
+                    true, // non-blocking input
+                    false // blocking output
+    );
     if (NS_FAILED(rv)) return rv;
 
-    nsCOMPtr<nsIStorageStream> storageStream;
-    nsCOMPtr<nsIOutputStream> outputStream;
-
-    // Init: (block size, maximum length)
-    rv = NS_NewStorageStream(256, (uint32_t)-1, getter_AddRefs(storageStream));
-    if (NS_FAILED(rv)) return rv;
-
-    rv = storageStream->GetOutputStream(0, getter_AddRefs(outputStream));
+    nsAutoCString storageName;
+    rv = ParseURI(aURI, storageName);
     if (NS_FAILED(rv)) return rv;
 
-    mBuffer.AssignLiteral(
-      "<!DOCTYPE html>\n"
-      "<html>\n"
-      "<head>\n"
-      "  <title>Information about the Cache Service</title>\n"
-      "  <link rel=\"stylesheet\" "
-      "href=\"chrome://global/skin/about.css\" type=\"text/css\"/>\n"
-      "  <link rel=\"stylesheet\" "
-      "href=\"chrome://global/skin/aboutCache.css\" type=\"text/css\"/>\n"
-      "</head>\n"
-      "<body class=\"aboutPageWideContainer\">\n"
-      "<h1>Information about the Cache Service</h1>\n");
-
-    outputStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
-
-    rv = ParseURI(aURI, mDeviceID);
-    if (NS_FAILED(rv)) return rv;
-
-    mStream = outputStream;
-
-    // nsCacheService::VisitEntries calls nsMemoryCacheDevice::Visit,
-    // nsDiskCacheDevice::Visit and nsOfflineCacheDevice::Visit,
-    // each of which call
-    //   1. VisitDevice (for about:cache),
-    //   2. VisitEntry in a loop (for about:cache?device=disk etc.)
-    rv = cacheService->VisitEntries(this);
-    mBuffer.Truncate();
-    if (rv == NS_ERROR_NOT_AVAILABLE) {
-        mBuffer.AppendLiteral("<h2>The cache is disabled.</h2>\n");
-    }
-    else if (NS_FAILED(rv)) {
-        return rv;
+    mOverview = storageName.IsEmpty();
+    if (mOverview) {
+        // ...and visit all we can
+        mStorageList.AppendElement(NS_LITERAL_CSTRING("memory"));
+        mStorageList.AppendElement(NS_LITERAL_CSTRING("disk"));
+        mStorageList.AppendElement(NS_LITERAL_CSTRING("appcache"));
+    } else {
+        // ...and visit just the specified storage, entries will output too
+        mStorageList.AppendElement(storageName);
     }
 
-    if (!mDeviceID.IsEmpty()) {
-        mBuffer.AppendLiteral("</table>\n");
-    }
-    mBuffer.AppendLiteral("</body>\n"
-                          "</html>\n");
-    outputStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
-
-    nsCOMPtr<nsIInputStream> inStr;
-
-    rv = storageStream->NewInputStream(0, getter_AddRefs(inStr));
-    if (NS_FAILED(rv)) return rv;
+    // The entries header is added on encounter of the first entry
+    mEntriesHeaderAdded = false;
 
     nsCOMPtr<nsIChannel> channel;
-    rv = NS_NewInputStreamChannel(getter_AddRefs(channel), aURI, inStr,
+    rv = NS_NewInputStreamChannel(getter_AddRefs(channel), aURI, inputStream,
                                   NS_LITERAL_CSTRING("text/html"),
                                   NS_LITERAL_CSTRING("utf-8"));
     if (NS_FAILED(rv)) return rv;
 
+    mBuffer.AssignLiteral(
+        "<!DOCTYPE html>\n"
+        "<html>\n"
+        "<head>\n"
+        "  <title>Network Cache Storage Information</title>\n"
+        "  <meta charset=\"utf-8\">\n"
+        "  <link rel=\"stylesheet\" href=\"chrome://global/skin/about.css\"/>\n"
+        "  <link rel=\"stylesheet\" href=\"chrome://global/skin/aboutCache.css\"/>\n"
+        "  <script src=\"chrome://global/content/aboutCache.js\"></script>"
+        "</head>\n"
+        "<body class=\"aboutPageWideContainer\">\n"
+        "<h1>Information about the Network Cache Storage Service</h1>\n");
+
+    // Add the context switch controls
+    mBuffer.AppendLiteral(
+        "<label><input id='priv' type='checkbox'/> Private</label>\n"
+        "<label><input id='anon' type='checkbox'/> Anonymous</label>\n"
+    );
+
+    if (CacheObserver::UseNewCache()) {
+        // Visit scoping by browser and appid is not implemented for
+        // the old cache, simply don't add these controls.
+        // The appid/inbrowser entries are already mixed in the default
+        // view anyway.
+        mBuffer.AppendLiteral(
+            "<label><input id='appid' type='text' size='6'/> AppID</label>\n"
+            "<label><input id='inbrowser' type='checkbox'/> In Browser Element</label>\n"
+        );
+    }
+
+    mBuffer.AppendLiteral(
+        "<label><input id='submit' type='button' value='Update' onclick='navigate()'/></label>\n"
+    );
+
+    if (!mOverview) {
+        mBuffer.AppendLiteral("<a href=\"about:cache?storage=&amp;context=");
+        char* escapedContext = nsEscapeHTML(mContextString.get());
+        mBuffer.Append(escapedContext);
+        nsMemory::Free(escapedContext);
+        mBuffer.AppendLiteral("\">Back to overview</a>");
+    }
+
+    FlushBuffer();
+
+    // Kick it, this goes async.
+    rv = VisitNextStorage();
+    if (NS_FAILED(rv)) return rv;
+
     channel.forget(result);
-    return rv;
+    return NS_OK;
+}
+
+nsresult
+nsAboutCache::ParseURI(nsIURI * uri, nsACString & storage)
+{
+    //
+    // about:cache[?storage=<storage-name>[&context=<context-key>]]
+    //
+    nsresult rv;
+
+    nsAutoCString path;
+    rv = uri->GetPath(path);
+    if (NS_FAILED(rv)) return rv;
+
+    mContextString.Truncate();
+    mLoadInfo = CacheFileUtils::ParseKey(NS_LITERAL_CSTRING(""));
+    storage.Truncate();
+
+    nsACString::const_iterator start, valueStart, end;
+    path.BeginReading(start);
+    path.EndReading(end);
+
+    valueStart = end;
+    if (!FindInReadable(NS_LITERAL_CSTRING("?storage="), start, valueStart)) {
+        return NS_OK;
+    }
+
+    nsACString::const_iterator storageNameBegin = valueStart;
+
+    start = valueStart;
+    valueStart = end;
+    if (!FindInReadable(NS_LITERAL_CSTRING("&context="), start, valueStart))
+        start = end;
+
+    nsACString::const_iterator storageNameEnd = start;
+
+    mContextString = Substring(valueStart, end);
+    mLoadInfo = CacheFileUtils::ParseKey(mContextString);
+    storage.Assign(Substring(storageNameBegin, storageNameEnd));
+
+    return NS_OK;
+}
+
+nsresult
+nsAboutCache::VisitNextStorage()
+{
+    if (!mStorageList.Length())
+        return NS_ERROR_NOT_AVAILABLE;
+
+    mStorageName = mStorageList[0];
+    mStorageList.RemoveElementAt(0);
+
+    // Must re-dispatch since we cannot start another visit cycle
+    // from visitor callback.  The cache v1 service doesn't like it.
+    // TODO - mayhemer, bug 913828, remove this dispatch and call
+    // directly.
+    nsCOMPtr<nsRunnableMethod<nsAboutCache> > event =
+        NS_NewRunnableMethod(this, &nsAboutCache::FireVisitStorage);
+    return NS_DispatchToMainThread(event);
+}
+
+void
+nsAboutCache::FireVisitStorage()
+{
+    nsresult rv;
+
+    rv = VisitStorage(mStorageName);
+    if (NS_FAILED(rv)) {
+        if (mLoadInfo) {
+            mBuffer.Append(
+                nsPrintfCString("<p>Unrecognized storage name '%s' in about:cache URL</p>",
+                                mStorageName.get()));
+        } else {
+            mBuffer.Append(
+                nsPrintfCString("<p>Unrecognized context key '%s' in about:cache URL</p>",
+                                mContextString.get()));
+        }
+
+        FlushBuffer();
+
+        // Simulate finish of a visit cycle, this tries the next storage
+        // or closes the output stream (i.e. the UI loader will stop spinning)
+        OnCacheEntryVisitCompleted();
+    }
+}
+
+nsresult
+nsAboutCache::VisitStorage(nsACString const & storageName)
+{
+    nsresult rv;
+
+    rv = GetStorage(storageName, mLoadInfo, getter_AddRefs(mStorage));
+    if (NS_FAILED(rv)) return rv;
+
+    rv = mStorage->AsyncVisitStorage(this, !mOverview);
+    if (NS_FAILED(rv)) return rv;
+
+    return NS_OK;
+}
+
+//static
+nsresult
+nsAboutCache::GetStorage(nsACString const & storageName,
+                         nsILoadContextInfo* loadInfo,
+                         nsICacheStorage **storage)
+{
+    nsresult rv;
+
+    nsCOMPtr<nsICacheStorageService> cacheService =
+             do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+    if (NS_FAILED(rv)) return rv;
+
+    nsCOMPtr<nsICacheStorage> cacheStorage;
+    if (storageName == "disk") {
+        rv = cacheService->DiskCacheStorage(
+            loadInfo, false, getter_AddRefs(cacheStorage));
+    } else if (storageName == "memory") {
+        rv = cacheService->MemoryCacheStorage(
+            loadInfo, getter_AddRefs(cacheStorage));
+    } else if (storageName == "appcache") {
+        rv = cacheService->AppCacheStorage(
+            loadInfo, nullptr, getter_AddRefs(cacheStorage));
+    } else {
+        rv = NS_ERROR_UNEXPECTED;
+    }
+    if (NS_FAILED(rv)) return rv;
+
+    cacheStorage.forget(storage);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCache::OnCacheStorageInfo(uint32_t aEntryCount, uint64_t aConsumption,
+                                 uint64_t aCapacity, nsIFile * aDirectory)
+{
+    // We need mStream for this
+    if (!mStream) {
+        return NS_ERROR_FAILURE;
+    }
+
+    mBuffer.AssignLiteral("<h2>");
+    mBuffer.Append(mStorageName);
+    mBuffer.AppendLiteral("</h2>\n"
+                          "<table id=\"");
+    mBuffer.AppendLiteral("\">\n");
+
+    // Write out cache info
+    // Number of entries
+    mBuffer.AppendLiteral("  <tr>\n"
+                          "    <th>Number of entries:</th>\n"
+                          "    <td>");
+    mBuffer.AppendInt(aEntryCount);
+    mBuffer.AppendLiteral("</td>\n"
+                          "  </tr>\n");
+
+    // Maximum storage size
+    mBuffer.AppendLiteral("  <tr>\n"
+                          "    <th>Maximum storage size:</th>\n"
+                          "    <td>");
+    mBuffer.AppendInt(aCapacity / 1024);
+    mBuffer.AppendLiteral(" KiB</td>\n"
+                          "  </tr>\n");
+
+    // Storage in use
+    mBuffer.AppendLiteral("  <tr>\n"
+                          "    <th>Storage in use:</th>\n"
+                          "    <td>");
+    mBuffer.AppendInt(aConsumption / 1024);
+    mBuffer.AppendLiteral(" KiB</td>\n"
+                          "  </tr>\n");
+
+    // Storage disk location
+    mBuffer.AppendLiteral("  <tr>\n"
+                          "    <th>Storage disk location:</th>\n"
+                          "    <td>");
+    if (aDirectory) {
+        nsAutoString path;
+        aDirectory->GetPath(path);
+        mBuffer.Append(NS_ConvertUTF16toUTF8(path));
+    } else {
+        mBuffer.AppendLiteral("none, only stored in memory");
+    }
+    mBuffer.AppendLiteral("    </td>\n"
+                          "  </tr>\n");
+
+    if (mOverview) { // The about:cache case
+        if (aEntryCount != 0) { // Add the "List Cache Entries" link
+            mBuffer.AppendLiteral("  <tr>\n"
+                                  "    <th><a href=\"about:cache?storage=");
+            mBuffer.Append(mStorageName);
+            mBuffer.AppendLiteral("&amp;context=");
+            char* escapedContext = nsEscapeHTML(mContextString.get());
+            mBuffer.Append(escapedContext);
+            nsMemory::Free(escapedContext);
+            mBuffer.AppendLiteral("\">List Cache Entries</a></th>\n"
+                                  "  </tr>\n");
+        }
+    }
+
+    mBuffer.AppendLiteral("</table>\n");
+
+    // The entries header is added on encounter of the first entry
+    mEntriesHeaderAdded = false;
+
+    FlushBuffer();
+
+    if (mOverview) {
+        // OnCacheEntryVisitCompleted() is not called when we do not iterate
+        // cache entries.  Since this moves forward to the next storage in
+        // the list we want to visit, artificially call it here.
+        OnCacheEntryVisitCompleted();
+    }
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCache::OnCacheEntryInfo(nsIURI *aURI, const nsACString & aIdEnhance,
+                               int64_t aDataSize, int32_t aFetchCount,
+                               uint32_t aLastModified, uint32_t aExpirationTime)
+{
+    // We need mStream for this
+    if (!mStream) {
+        return NS_ERROR_FAILURE;
+    }
+
+    if (!mEntriesHeaderAdded) {
+        mBuffer.AppendLiteral("<hr/>\n"
+                              "<table id=\"entries\">\n"
+                              "  <colgroup>\n"
+                              "   <col id=\"col-key\">\n"
+                              "   <col id=\"col-dataSize\">\n"
+                              "   <col id=\"col-fetchCount\">\n"
+                              "   <col id=\"col-lastModified\">\n"
+                              "   <col id=\"col-expires\">\n"
+                              "  </colgroup>\n"
+                              "  <thead>\n"
+                              "    <tr>\n"
+                              "      <th>Key</th>\n"
+                              "      <th>Data size</th>\n"
+                              "      <th>Fetch count</th>\n"
+                              "      <th>Last Modifed</th>\n"
+                              "      <th>Expires</th>\n"
+                              "    </tr>\n"
+                              "  </thead>\n");
+        mEntriesHeaderAdded = true;
+    }
+
+    // Generate a about:cache-entry URL for this entry...
+
+    nsAutoCString url;
+    url.AssignLiteral("about:cache-entry?storage=");
+    url.Append(mStorageName);
+
+    url.AppendLiteral("&amp;context=");
+    char* escapedContext = nsEscapeHTML(mContextString.get());
+    url += escapedContext;
+    nsMemory::Free(escapedContext);
+
+    url.AppendLiteral("&amp;eid=");
+    char* escapedEID = nsEscapeHTML(aIdEnhance.BeginReading());
+    url += escapedEID;
+    nsMemory::Free(escapedEID);
+
+    nsAutoCString cacheUriSpec;
+    aURI->GetAsciiSpec(cacheUriSpec);
+    char* escapedCacheURI = nsEscapeHTML(cacheUriSpec.get());
+    url.AppendLiteral("&amp;uri=");
+    url += escapedCacheURI;
+
+    // Entry start...
+    mBuffer.AppendLiteral("  <tr>\n");
+
+    // URI
+    mBuffer.AppendLiteral("    <td><a href=\"");
+    mBuffer.Append(url);
+    mBuffer.AppendLiteral("\">");
+    if (!aIdEnhance.IsEmpty()) {
+        mBuffer.Append(aIdEnhance);
+        mBuffer.Append(':');
+    }
+    mBuffer.Append(escapedCacheURI);
+    mBuffer.AppendLiteral("</a></td>\n");
+
+    nsMemory::Free(escapedCacheURI);
+
+    // Content length
+    mBuffer.AppendLiteral("    <td>");
+    mBuffer.AppendInt(aDataSize);
+    mBuffer.AppendLiteral(" bytes</td>\n");
+
+    // Number of accesses
+    mBuffer.AppendLiteral("    <td>");
+    mBuffer.AppendInt(aFetchCount);
+    mBuffer.AppendLiteral("</td>\n");
+
+    // vars for reporting time
+    char buf[255];
+
+    // Last modified time
+    mBuffer.AppendLiteral("    <td>");
+    if (aLastModified) {
+        PrintTimeString(buf, sizeof(buf), aLastModified);
+        mBuffer.Append(buf);
+    } else {
+        mBuffer.AppendLiteral("No last modified time (bug 1000338)");
+    }
+    mBuffer.AppendLiteral("</td>\n");
+
+    // Expires time
+    mBuffer.AppendLiteral("    <td>");
+    if (aExpirationTime < 0xFFFFFFFF) {
+        PrintTimeString(buf, sizeof(buf), aExpirationTime);
+        mBuffer.Append(buf);
+    } else {
+        mBuffer.AppendLiteral("No expiration time");
+    }
+    mBuffer.AppendLiteral("</td>\n");
+
+    // Entry is done...
+    mBuffer.AppendLiteral("  </tr>\n");
+
+    FlushBuffer();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCache::OnCacheEntryVisitCompleted()
+{
+    if (!mStream) {
+        return NS_ERROR_FAILURE;
+    }
+
+    if (mEntriesHeaderAdded) {
+        mBuffer.AppendLiteral("</table>\n");
+    }
+
+    // Kick another storage visiting (from a storage that allows us.)
+    while (mStorageList.Length()) {
+        nsresult rv = VisitNextStorage();
+        if (NS_SUCCEEDED(rv)) {
+            // Expecting new round of OnCache* calls.
+            return NS_OK;
+        }
+    }
+
+    // We are done!
+    mBuffer.AppendLiteral("</body>\n"
+                          "</html>\n");
+    FlushBuffer();
+    mStream->Close();
+
+    return NS_OK;
+}
+
+void
+nsAboutCache::FlushBuffer()
+{
+    uint32_t bytesWritten;
+    mStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
+    mBuffer.Truncate();
 }
 
 NS_IMETHODIMP
 nsAboutCache::GetURIFlags(nsIURI *aURI, uint32_t *result)
 {
     *result = 0;
     return NS_OK;
 }
 
-NS_IMETHODIMP
-nsAboutCache::VisitDevice(const char *deviceID,
-                          nsICacheDeviceInfo *deviceInfo,
-                          bool *visitEntries)
-{
-    uint32_t bytesWritten, value, entryCount;
-    nsXPIDLCString str;
-
-    *visitEntries = false;
-
-    if (mDeviceID.IsEmpty() || mDeviceID.Equals(deviceID)) {
-
-        // We need mStream for this
-        if (!mStream)
-          return NS_ERROR_FAILURE;
-	
-        // Write out the Cache Name
-        deviceInfo->GetDescription(getter_Copies(str));
-
-        mBuffer.AssignLiteral("<h2>");
-        mBuffer.Append(str);
-        mBuffer.AppendLiteral("</h2>\n"
-                              "<table id=\"");
-        mBuffer.Append(deviceID);
-        mBuffer.AppendLiteral("\">\n");
-
-        // Write out cache info
-        // Number of entries
-        mBuffer.AppendLiteral("  <tr>\n"
-                              "    <th>Number of entries:</th>\n"
-                              "    <td>");
-        entryCount = 0;
-        deviceInfo->GetEntryCount(&entryCount);
-        mBuffer.AppendInt(entryCount);
-        mBuffer.AppendLiteral("</td>\n"
-                              "  </tr>\n");
-
-        // Maximum storage size
-        mBuffer.AppendLiteral("  <tr>\n"
-                              "    <th>Maximum storage size:</th>\n"
-                              "    <td>");
-        value = 0;
-        deviceInfo->GetMaximumSize(&value);
-        mBuffer.AppendInt(value/1024);
-        mBuffer.AppendLiteral(" KiB</td>\n"
-                              "  </tr>\n");
-
-        // Storage in use
-        mBuffer.AppendLiteral("  <tr>\n"
-                              "    <th>Storage in use:</th>\n"
-                              "    <td>");
-        value = 0;
-        deviceInfo->GetTotalSize(&value);
-        mBuffer.AppendInt(value/1024);
-        mBuffer.AppendLiteral(" KiB</td>\n"
-                              "  </tr>\n");
-
-        deviceInfo->GetUsageReport(getter_Copies(str));
-        mBuffer.Append(str);
-
-        if (mDeviceID.IsEmpty()) { // The about:cache case
-            if (entryCount != 0) { // Add the "List Cache Entries" link
-                mBuffer.AppendLiteral("  <tr>\n"
-                                      "    <th><a href=\"about:cache?device=");
-                mBuffer.Append(deviceID);
-                mBuffer.AppendLiteral("\">List Cache Entries</a></th>\n"
-                                      "  </tr>\n");
-            }
-            mBuffer.AppendLiteral("</table>\n");
-        } else { // The about:cache?device=disk etc. case
-            mBuffer.AppendLiteral("</table>\n");
-            if (entryCount != 0) {
-                *visitEntries = true;
-                mBuffer.AppendLiteral("<hr/>\n"
-                                      "<table id=\"entries\">\n"
-                                      "  <colgroup>\n"
-                                      "   <col id=\"col-key\">\n"
-                                      "   <col id=\"col-dataSize\">\n"
-                                      "   <col id=\"col-fetchCount\">\n"
-                                      "   <col id=\"col-lastModified\">\n"
-                                      "   <col id=\"col-expires\">\n"
-                                      "  </colgroup>\n"
-                                      "  <thead>\n"
-                                      "    <tr>\n"
-                                      "      <th>Key</th>\n"
-                                      "      <th>Data size</th>\n"
-                                      "      <th>Fetch count</th>\n"
-                                      "      <th>Last modified</th>\n"
-                                      "      <th>Expires</th>\n"
-                                      "    </tr>\n"
-                                      "  </thead>\n");
-            }
-        }
-
-        mStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
-    }
-
-    return NS_OK;
-}
-
-NS_IMETHODIMP
-nsAboutCache::VisitEntry(const char *deviceID,
-                         nsICacheEntryInfo *entryInfo,
-                         bool *visitNext)
-{
-    // We need mStream for this
-    if (!mStream)
-      return NS_ERROR_FAILURE;
-
-    nsresult        rv;
-    uint32_t        bytesWritten;
-    nsAutoCString   key;
-    nsXPIDLCString  clientID;
-    bool            streamBased;
-    
-    rv = entryInfo->GetKey(key);
-    if (NS_FAILED(rv))  return rv;
-
-    rv = entryInfo->GetClientID(getter_Copies(clientID));
-    if (NS_FAILED(rv))  return rv;
-
-    rv = entryInfo->IsStreamBased(&streamBased);
-    if (NS_FAILED(rv)) return rv;
-
-    // Generate a about:cache-entry URL for this entry...
-    nsAutoCString url;
-    url.AssignLiteral("about:cache-entry?client=");
-    url += clientID;
-    url.AppendLiteral("&amp;sb=");
-    url += streamBased ? '1' : '0';
-    url.AppendLiteral("&amp;key=");
-    char* escapedKey = nsEscapeHTML(key.get());
-    url += escapedKey; // key
-
-    // Entry start...
-    mBuffer.AssignLiteral("  <tr>\n");
-
-    // URI
-    mBuffer.AppendLiteral("    <td><a href=\"");
-    mBuffer.Append(url);
-    mBuffer.AppendLiteral("\">");
-    mBuffer.Append(escapedKey);
-    nsMemory::Free(escapedKey);
-    mBuffer.AppendLiteral("</a></td>\n");
-
-    // Content length
-    uint32_t length = 0;
-    entryInfo->GetDataSize(&length);
-    mBuffer.AppendLiteral("    <td>");
-    mBuffer.AppendInt(length);
-    mBuffer.AppendLiteral(" bytes</td>\n");
-
-    // Number of accesses
-    int32_t fetchCount = 0;
-    entryInfo->GetFetchCount(&fetchCount);
-    mBuffer.AppendLiteral("    <td>");
-    mBuffer.AppendInt(fetchCount);
-    mBuffer.AppendLiteral("</td>\n");
-
-    // vars for reporting time
-    char buf[255];
-    uint32_t t;
-
-    // Last modified time
-    mBuffer.AppendLiteral("    <td>");
-    entryInfo->GetLastModified(&t);
-    if (t) {
-        PrintTimeString(buf, sizeof(buf), t);
-        mBuffer.Append(buf);
-    } else
-        mBuffer.AppendLiteral("No last modified time");
-    mBuffer.AppendLiteral("</td>\n");
-
-    // Expires time
-    mBuffer.AppendLiteral("    <td>");
-    entryInfo->GetExpirationTime(&t);
-    if (t < 0xFFFFFFFF) {
-        PrintTimeString(buf, sizeof(buf), t);
-        mBuffer.Append(buf);
-    } else {
-        mBuffer.AppendLiteral("No expiration time");
-    }
-    mBuffer.AppendLiteral("</td>\n");
-
-    // Entry is done...
-    mBuffer.AppendLiteral("  </tr>\n");
-
-    mStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
-
-    *visitNext = true;
-    return NS_OK;
-}
-
-
-nsresult
-nsAboutCache::ParseURI(nsIURI * uri, nsCString &deviceID)
-{
-    //
-    // about:cache[?device=string]
-    //
-    nsresult rv;
-
-    deviceID.Truncate();
-
-    nsAutoCString path;
-    rv = uri->GetPath(path);
-    if (NS_FAILED(rv)) return rv;
-
-    nsACString::const_iterator start, valueStart, end;
-    path.BeginReading(start);
-    path.EndReading(end);
-
-    valueStart = end;
-    if (!FindInReadable(NS_LITERAL_CSTRING("?device="), start, valueStart))
-        return NS_OK;
-
-    deviceID.Assign(Substring(valueStart, end));
-    return NS_OK;
-}
-
-
+// static
 nsresult
 nsAboutCache::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
 {
     nsAboutCache* about = new nsAboutCache();
     if (about == nullptr)
         return NS_ERROR_OUT_OF_MEMORY;
     NS_ADDREF(about);
     nsresult rv = about->QueryInterface(aIID, aResult);
     NS_RELEASE(about);
     return rv;
 }
 
-
-
 ////////////////////////////////////////////////////////////////////////////////
--- a/netwerk/protocol/about/nsAboutCache.h
+++ b/netwerk/protocol/about/nsAboutCache.h
@@ -2,43 +2,83 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsAboutCache_h__
 #define nsAboutCache_h__
 
 #include "nsIAboutModule.h"
+#include "nsICacheStorageVisitor.h"
+#include "nsICacheStorage.h"
 
 #include "nsString.h"
 #include "nsIOutputStream.h"
+#include "nsILoadContextInfo.h"
 
-#include "nsICacheVisitor.h"
 #include "nsCOMPtr.h"
 
 class nsAboutCache : public nsIAboutModule 
-                   , public nsICacheVisitor
+                   , public nsICacheStorageVisitor
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIABOUTMODULE
-    NS_DECL_NSICACHEVISITOR
+    NS_DECL_NSICACHESTORAGEVISITOR
 
     nsAboutCache() {}
     virtual ~nsAboutCache() {}
 
     static nsresult
     Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
 
+    static nsresult
+    GetStorage(nsACString const & storageName, nsILoadContextInfo* loadInfo,
+               nsICacheStorage **storage);
+
 protected:
-    nsresult  ParseURI(nsIURI * uri, nsCString &deviceID);
+    nsresult ParseURI(nsIURI * uri, nsACString & storage);
+
+    // Finds a next storage we wish to visit (we use this method
+    // even there is a specified storage name, which is the only
+    // one in the list then.)  Posts FireVisitStorage() when found.
+    nsresult VisitNextStorage();
+    // Helper method that calls VisitStorage() for the current storage.
+    // When it fails, OnCacheEntryVisitCompleted is simlated to close
+    // the output stream and thus the about:cache channel.
+    void FireVisitStorage();
+    // Kiks the visit cycle for the given storage, names can be:
+    // "disk", "memory", "appcache"
+    // Note: any newly added storage type has to be manually handled here.
+    nsresult VisitStorage(nsACString const & storageName);
 
+    // Writes content of mBuffer to mStream and truncates
+    // the buffer.
+    void FlushBuffer();
+
+    // Whether we are showing overview status of all available
+    // storages.
+    bool mOverview;
+
+    // Flag initially false, that indicates the entries header has
+    // been added to the output HTML.
+    bool mEntriesHeaderAdded;
+
+    // The context we are working with.
+    nsCOMPtr<nsILoadContextInfo> mLoadInfo;
+    nsCString mContextString;
+
+    // The list of all storage names we want to visit
+    nsTArray<nsCString> mStorageList;
+    nsCString mStorageName;
+    nsCOMPtr<nsICacheStorage> mStorage;
+
+    // Output data buffering and streaming output
+    nsCString mBuffer;
     nsCOMPtr<nsIOutputStream> mStream;
-    nsCString                 mDeviceID;
-    nsCString mBuffer;
 };
 
 #define NS_ABOUT_CACHE_MODULE_CID                    \
 { /* 9158c470-86e4-11d4-9be2-00e09872a416 */         \
     0x9158c470,                                      \
     0x86e4,                                          \
     0x11d4,                                          \
     {0x9b, 0xe2, 0x00, 0xe0, 0x98, 0x72, 0xa4, 0x16} \
--- a/netwerk/protocol/about/nsAboutCacheEntry.cpp
+++ b/netwerk/protocol/about/nsAboutCacheEntry.cpp
@@ -1,24 +1,28 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsAboutCacheEntry.h"
-#include "nsICacheService.h"
-#include "nsICacheSession.h"
+#include "nsAboutCache.h"
+#include "nsICacheStorage.h"
+#include "CacheObserver.h"
 #include "nsNetUtil.h"
 #include "prprf.h"
 #include "nsEscape.h"
 #include "nsIAsyncInputStream.h"
 #include "nsIAsyncOutputStream.h"
 #include "nsAboutProtocolUtils.h"
+#include "nsInputStreamPump.h"
 #include <algorithm>
 
+using namespace mozilla::net;
+
 #define HEXDUMP_MAX_ROWS 16
 
 static void
 HexDump(uint32_t *state, const char *buf, int32_t n, nsCString &result)
 {
   char temp[16];
 
   const unsigned char *p;
@@ -69,17 +73,19 @@ HexDump(uint32_t *state, const char *buf
   }
 }
 
 //-----------------------------------------------------------------------------
 // nsAboutCacheEntry::nsISupports
 
 NS_IMPL_ISUPPORTS(nsAboutCacheEntry,
                   nsIAboutModule,
-                  nsICacheMetaDataVisitor)
+                  nsICacheEntryOpenCallback,
+                  nsICacheEntryMetaDataVisitor,
+                  nsIStreamListener)
 
 //-----------------------------------------------------------------------------
 // nsAboutCacheEntry::nsIAboutModule
 
 NS_IMETHODIMP
 nsAboutCacheEntry::NewChannel(nsIURI *uri, nsIChannel **result)
 {
     NS_ENSURE_ARG_POINTER(uri);
@@ -141,66 +147,192 @@ nsAboutCacheEntry::GetContentStream(nsIU
     inputStream.forget(result);
     return NS_OK;
 }
 
 nsresult
 nsAboutCacheEntry::OpenCacheEntry(nsIURI *uri)
 {
     nsresult rv;
-    nsAutoCString clientID, key;
-    bool streamBased = true;
 
-    rv = ParseURI(uri, clientID, streamBased, key);
+    rv = ParseURI(uri, mStorageName, getter_AddRefs(mLoadInfo),
+                       mEnhanceId, getter_AddRefs(mCacheURI));
     if (NS_FAILED(rv)) return rv;
 
-    nsCOMPtr<nsICacheService> serv =
-        do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+    if (!CacheObserver::UseNewCache() &&
+        mLoadInfo->IsPrivate() &&
+        mStorageName == NS_LITERAL_CSTRING("disk")) {
+        // The cache v1 is storing all private entries in the memory-only
+        // cache, so it would not be found in the v1 disk cache.
+        mStorageName = NS_LITERAL_CSTRING("memory");
+    }
+
+    return OpenCacheEntry();
+}
+
+nsresult
+nsAboutCacheEntry::OpenCacheEntry()
+{
+    nsresult rv;
+
+    nsCOMPtr<nsICacheStorage> storage;
+    rv = nsAboutCache::GetStorage(mStorageName, mLoadInfo, getter_AddRefs(storage));
+    if (NS_FAILED(rv)) return rv;
+
+    // Invokes OnCacheEntryAvailable()
+    rv = storage->AsyncOpenURI(mCacheURI, mEnhanceId,
+                               nsICacheStorage::OPEN_READONLY, this);
     if (NS_FAILED(rv)) return rv;
 
-    nsCOMPtr<nsICacheSession> session;
-    rv = serv->CreateSession(clientID.get(),
-                             nsICache::STORE_ANYWHERE,
-                             streamBased,
-                             getter_AddRefs(session));
+    return NS_OK;
+}
+
+nsresult
+nsAboutCacheEntry::ParseURI(nsIURI *uri,
+                            nsACString &storageName,
+                            nsILoadContextInfo **loadInfo,
+                            nsCString &enahnceID,
+                            nsIURI **cacheUri)
+{
+    //
+    // about:cache-entry?storage=[string]&contenxt=[string]&eid=[string]&uri=[string]
+    //
+    nsresult rv;
+
+    nsAutoCString path;
+    rv = uri->GetPath(path);
+    if (NS_FAILED(rv))
+        return rv;
+
+    nsACString::const_iterator keyBegin, keyEnd, valBegin, begin, end;
+    path.BeginReading(begin);
+    path.EndReading(end);
+
+    keyBegin = begin; keyEnd = end;
+    if (!FindInReadable(NS_LITERAL_CSTRING("?storage="), keyBegin, keyEnd))
+        return NS_ERROR_FAILURE;
+
+    valBegin = keyEnd; // the value of the storage key starts after the key
+
+    keyBegin = keyEnd; keyEnd = end;
+    if (!FindInReadable(NS_LITERAL_CSTRING("&context="), keyBegin, keyEnd))
+        return NS_ERROR_FAILURE;
+
+    storageName.Assign(Substring(valBegin, keyBegin));
+    valBegin = keyEnd; // the value of the context key starts after the key
+
+    keyBegin = keyEnd; keyEnd = end;
+    if (!FindInReadable(NS_LITERAL_CSTRING("&eid="), keyBegin, keyEnd))
+        return NS_ERROR_FAILURE;
+
+    nsAutoCString contextKey(Substring(valBegin, keyBegin));
+    valBegin = keyEnd; // the value of the eid key starts after the key
+
+    keyBegin = keyEnd; keyEnd = end;
+    if (!FindInReadable(NS_LITERAL_CSTRING("&uri="), keyBegin, keyEnd))
+        return NS_ERROR_FAILURE;
+
+    enahnceID.Assign(Substring(valBegin, keyBegin));
+
+    valBegin = keyEnd; // the value of the uri key starts after the key
+    nsAutoCString uriSpec(Substring(valBegin, end)); // uri is the last one
+
+    // Uf... parsing done, now get some objects from it...
+
+    nsCOMPtr<nsILoadContextInfo> info =
+      CacheFileUtils::ParseKey(contextKey);
+    if (!info)
+        return NS_ERROR_FAILURE;
+    info.forget(loadInfo);
+
+    rv = NS_NewURI(cacheUri, uriSpec);
+    if (NS_FAILED(rv))
+        return rv;
+
+    return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsICacheEntryOpenCallback implementation
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsAboutCacheEntry::OnCacheEntryCheck(nsICacheEntry *aEntry,
+                                     nsIApplicationCache *aApplicationCache,
+                                     uint32_t *result)
+{
+    *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::OnCacheEntryAvailable(nsICacheEntry *entry,
+                                         bool isNew,
+                                         nsIApplicationCache *aApplicationCache,
+                                         nsresult status)
+{
+    nsresult rv;
+
+    mWaitingForData = false;
+    if (entry) {
+        rv = WriteCacheEntryDescription(entry);
+    } else if (!CacheObserver::UseNewCache() &&
+               !mLoadInfo->IsPrivate() &&
+               mStorageName == NS_LITERAL_CSTRING("memory")) {
+        // If we were not able to find the entry in the memory storage
+        // try again in the disk storage.
+        // This is a workaround for cache v1: when an originally disk
+        // cache entry is recreated as memory-only, it's clientID doesn't
+        // change and we cannot find it in "HTTP-memory-only" session.
+        // "Disk" cache storage looks at "HTTP".
+        mStorageName = NS_LITERAL_CSTRING("disk");
+        rv = OpenCacheEntry();
+        if (NS_SUCCEEDED(rv)) {
+            return NS_OK;
+        }
+    } else {
+        rv = WriteCacheEntryUnavailable();
+    }
     if (NS_FAILED(rv)) return rv;
 
-    rv = session->SetDoomEntriesIfExpired(false);
-    if (NS_FAILED(rv)) return rv;
 
-    return session->AsyncOpenCacheEntry(key, nsICache::ACCESS_READ, this, true);
+    if (!mWaitingForData) {
+        // Data is not expected, close the output of content now.
+        CloseContent();
+    }
+
+    return NS_OK;
 }
 
-
 //-----------------------------------------------------------------------------
-// helper methods
+// Print-out helper methods
 //-----------------------------------------------------------------------------
 
 #define APPEND_ROW(label, value) \
     PR_BEGIN_MACRO \
     buffer.AppendLiteral("  <tr>\n" \
                          "    <th>"); \
     buffer.AppendLiteral(label); \
     buffer.AppendLiteral(":</th>\n" \
                          "    <td>"); \
     buffer.Append(value); \
     buffer.AppendLiteral("</td>\n" \
                          "  </tr>\n"); \
     PR_END_MACRO
 
 nsresult
-nsAboutCacheEntry::WriteCacheEntryDescription(nsICacheEntryDescriptor *descriptor)
+nsAboutCacheEntry::WriteCacheEntryDescription(nsICacheEntry *entry)
 {
     nsresult rv;
     nsCString buffer;
     uint32_t n;
 
     nsAutoCString str;
 
-    rv = descriptor->GetKey(str);
+    rv = entry->GetKey(str);
     if (NS_FAILED(rv)) return rv;
 
     buffer.SetCapacity(4096);
     buffer.AssignLiteral("<table>\n"
                          "  <tr>\n"
                          "    <th>key:</th>\n"
                          "    <td id=\"td-key\">");
 
@@ -219,244 +351,221 @@ nsAboutCacheEntry::WriteCacheEntryDescri
     char* escapedStr = nsEscapeHTML(str.get());
     if (NS_SUCCEEDED(rv) && !(isJS || isData)) {
         buffer.AppendLiteral("<a href=\"");
         buffer.Append(escapedStr);
         buffer.AppendLiteral("\">");
         buffer.Append(escapedStr);
         buffer.AppendLiteral("</a>");
         uri = 0;
+    } else {
+        buffer.Append(escapedStr);
     }
-    else
-        buffer.Append(escapedStr);
     nsMemory::Free(escapedStr);
     buffer.AppendLiteral("</td>\n"
                          "  </tr>\n");
 
     // temp vars for reporting
     char timeBuf[255];
     uint32_t u = 0;
     int32_t  i = 0;
     nsAutoCString s;
 
     // Fetch Count
     s.Truncate();
-    descriptor->GetFetchCount(&i);
+    entry->GetFetchCount(&i);
     s.AppendInt(i);
     APPEND_ROW("fetch count", s);
 
     // Last Fetched
-    descriptor->GetLastFetched(&u);
+    entry->GetLastFetched(&u);
     if (u) {
         PrintTimeString(timeBuf, sizeof(timeBuf), u);
         APPEND_ROW("last fetched", timeBuf);
     } else {
-        APPEND_ROW("last fetched", "No last fetch time");
+        APPEND_ROW("last fetched", "No last fetch time (bug 1000338)");
     }
 
     // Last Modified
-    descriptor->GetLastModified(&u);
+    entry->GetLastModified(&u);
     if (u) {
         PrintTimeString(timeBuf, sizeof(timeBuf), u);
         APPEND_ROW("last modified", timeBuf);
     } else {
-        APPEND_ROW("last modified", "No last modified time");
+        APPEND_ROW("last modified", "No last modified time (bug 1000338)");
     }
 
     // Expiration Time
-    descriptor->GetExpirationTime(&u);
+    entry->GetExpirationTime(&u);
     if (u < 0xFFFFFFFF) {
         PrintTimeString(timeBuf, sizeof(timeBuf), u);
         APPEND_ROW("expires", timeBuf);
     } else {
         APPEND_ROW("expires", "No expiration time");
     }
 
     // Data Size
     s.Truncate();
     uint32_t dataSize;
-    descriptor->GetStorageDataSize(&dataSize);
+    if (NS_FAILED(entry->GetStorageDataSize(&dataSize)))
+        dataSize = 0;
     s.AppendInt((int32_t)dataSize);     // XXX nsICacheEntryInfo interfaces should be fixed.
+    s.Append(NS_LITERAL_CSTRING(" B"));
     APPEND_ROW("Data size", s);
 
-    // Storage Policy
-
-    // XXX Stream Based?
-
-    // XXX Cache Device
-    // File on disk
-    nsCOMPtr<nsIFile> cacheFile;
-    rv = descriptor->GetFile(getter_AddRefs(cacheFile));
-    if (NS_SUCCEEDED(rv)) {
-        nsAutoString filePath;
-        cacheFile->GetPath(filePath);
-        APPEND_ROW("file on disk", NS_ConvertUTF16toUTF8(filePath));
-    }
-    else
-        APPEND_ROW("file on disk", "none");
+    // TODO - mayhemer
+    // Here used to be a link to the disk file (in the old cache for entries that
+    // did not fit any of the block files, in the new cache every time).
+    // I'd rather have a small set of buttons here to action on the entry:
+    // 1. save the content
+    // 2. save as a complete HTTP response (response head, headers, content)
+    // 3. doom the entry
+    // A new bug(s) should be filed here.
 
     // Security Info
     nsCOMPtr<nsISupports> securityInfo;
-    descriptor->GetSecurityInfo(getter_AddRefs(securityInfo));
+    entry->GetSecurityInfo(getter_AddRefs(securityInfo));
     if (securityInfo) {
         APPEND_ROW("Security", "This is a secure document.");
     } else {
         APPEND_ROW("Security",
                    "This document does not have any security info associated with it.");
     }
 
     buffer.AppendLiteral("</table>\n"
                          "<hr/>\n"
                          "<table>\n");
-    // Meta Data
-    // let's just look for some well known (HTTP) meta data tags, for now.
 
-    // Client ID
-    nsXPIDLCString str2;
-    descriptor->GetClientID(getter_Copies(str2));
-    if (!str2.IsEmpty())  APPEND_ROW("Client", str2);
-
-
-    mBuffer = &buffer;  // make it available for VisitMetaDataElement().
-    // nsCacheEntryDescriptor::VisitMetaData calls
-    // nsCacheEntry.h VisitMetaDataElements, which returns
-    // nsCacheMetaData::VisitElements, which calls
-    // nsAboutCacheEntry::VisitMetaDataElement (below) in a loop.
-    descriptor->VisitMetaData(this);
+    mBuffer = &buffer;  // make it available for OnMetaDataElement().
+    entry->VisitMetaData(this);
     mBuffer = nullptr;
 
     buffer.AppendLiteral("</table>\n");
     mOutputStream->Write(buffer.get(), buffer.Length(), &n);
-
     buffer.Truncate();
 
     // Provide a hexdump of the data
-    if (dataSize) { // don't draw an <hr> if the Data Size is 0.
-        nsCOMPtr<nsIInputStream> stream;
-        descriptor->OpenInputStream(0, getter_AddRefs(stream));
-        if (stream) {
-            buffer.AssignLiteral("<hr/>\n"
-                                 "<pre>");
-            uint32_t hexDumpState = 0;
-            char chunk[4096];
-            while(NS_SUCCEEDED(stream->Read(chunk, sizeof(chunk), &n)) && 
-                  n > 0) {
-                HexDump(&hexDumpState, chunk, n, buffer);
-                mOutputStream->Write(buffer.get(), buffer.Length(), &n);
-                buffer.Truncate();
-            }
-            buffer.AssignLiteral("</pre>\n");
-            mOutputStream->Write(buffer.get(), buffer.Length(), &n);
-      }
+    if (!dataSize) {
+        return NS_OK;
+    }
+
+    nsCOMPtr<nsIInputStream> stream;
+    entry->OpenInputStream(0, getter_AddRefs(stream));
+    if (!stream) {
+        return NS_OK;
     }
+
+    nsRefPtr<nsInputStreamPump> pump;
+    rv = nsInputStreamPump::Create(getter_AddRefs(pump), stream);
+    if (NS_FAILED(rv)) {
+        return NS_OK; // just ignore
+    }
+
+    rv = pump->AsyncRead(this, nullptr);
+    if (NS_FAILED(rv)) {
+        return NS_OK; // just ignore
+    }
+
+    mWaitingForData = true;
     return NS_OK;
 }
 
 nsresult
 nsAboutCacheEntry::WriteCacheEntryUnavailable()
 {
     uint32_t n;
     NS_NAMED_LITERAL_CSTRING(buffer,
         "The cache entry you selected is not available.");
     mOutputStream->Write(buffer.get(), buffer.Length(), &n);
     return NS_OK;
 }
 
-nsresult
-nsAboutCacheEntry::ParseURI(nsIURI *uri, nsCString &clientID,
-                            bool &streamBased, nsCString &key)
-{
-    //
-    // about:cache-entry?client=[string]&sb=[boolean]&key=[string]
-    //
-    nsresult rv;
-
-    nsAutoCString path;
-    rv = uri->GetPath(path);
-    if (NS_FAILED(rv)) return rv;
-
-    nsACString::const_iterator i1, i2, i3, end;
-    path.BeginReading(i1);
-    path.EndReading(end);
-
-    i2 = end;
-    if (!FindInReadable(NS_LITERAL_CSTRING("?client="), i1, i2))
-        return NS_ERROR_FAILURE;
-    // i2 points to the start of clientID
-
-    i1 = i2;
-    i3 = end;
-    if (!FindInReadable(NS_LITERAL_CSTRING("&sb="), i1, i3))
-        return NS_ERROR_FAILURE;
-    // i1 points to the end of clientID
-    // i3 points to the start of isStreamBased
-
-    clientID.Assign(Substring(i2, i1));
-
-    i1 = i3;
-    i2 = end;
-    if (!FindInReadable(NS_LITERAL_CSTRING("&key="), i1, i2))
-        return NS_ERROR_FAILURE;
-    // i1 points to the end of isStreamBased
-    // i2 points to the start of key
-
-    streamBased = FindCharInReadable('1', i3, i1);
-    key.Assign(Substring(i2, end));
-
-    return NS_OK;
-}
-
-
 //-----------------------------------------------------------------------------
-// nsICacheMetaDataVisitor implementation
+// nsICacheEntryMetaDataVisitor implementation
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
-nsAboutCacheEntry::VisitMetaDataElement(const char * key,
-                                        const char * value,
-                                        bool *     keepGoing)
+nsAboutCacheEntry::OnMetaDataElement(char const * key, char const * value)
 {
     mBuffer->AppendLiteral("  <tr>\n"
                            "    <th>");
     mBuffer->Append(key);
     mBuffer->AppendLiteral(":</th>\n"
                            "    <td>");
     char* escapedValue = nsEscapeHTML(value);
     mBuffer->Append(escapedValue);
     nsMemory::Free(escapedValue);
     mBuffer->AppendLiteral("</td>\n"
                            "  </tr>\n");
 
-    *keepGoing = true;
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
-// nsICacheListener implementation
+// nsIStreamListener implementation
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
-nsAboutCacheEntry::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry,
-                                         nsCacheAccessMode access,
-                                         nsresult status)
+nsAboutCacheEntry::OnStartRequest(nsIRequest *request, nsISupports *ctx)
 {
-    nsresult rv;
+    mHexDumpState = 0;
+
+    NS_NAMED_LITERAL_CSTRING(buffer, "<hr/>\n<pre>");
+    uint32_t n;
+    return mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+}
 
-    if (entry)
-        rv = WriteCacheEntryDescription(entry);
-    else
-        rv = WriteCacheEntryUnavailable();
-    if (NS_FAILED(rv)) return rv;
+NS_IMETHODIMP
+nsAboutCacheEntry::OnDataAvailable(nsIRequest *request, nsISupports *ctx,
+                                   nsIInputStream *aInputStream,
+                                   uint64_t aOffset,
+                                   uint32_t aCount)
+{
+    uint32_t n;
+    return aInputStream->ReadSegments(
+        &nsAboutCacheEntry::PrintCacheData, this, aCount, &n);
+}
+
+// static
+NS_METHOD
+nsAboutCacheEntry::PrintCacheData(nsIInputStream *aInStream,
+                                  void *aClosure,
+                                  const char *aFromSegment,
+                                  uint32_t aToOffset,
+                                  uint32_t aCount,
+                                  uint32_t *aWriteCount)
+{
+    nsAboutCacheEntry *a = static_cast<nsAboutCacheEntry*>(aClosure);
+
+    nsCString buffer;
+    HexDump(&a->mHexDumpState, aFromSegment, aCount, buffer);
 
     uint32_t n;
-    NS_NAMED_LITERAL_CSTRING(buffer, "</body>\n</html>\n");
-    mOutputStream->Write(buffer.get(), buffer.Length(), &n);
-    mOutputStream->Close();
-    mOutputStream = nullptr;
+    a->mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+
+    *aWriteCount = aCount;
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsAboutCacheEntry::OnCacheEntryDoomed(nsresult status)
+nsAboutCacheEntry::OnStopRequest(nsIRequest *request, nsISupports *ctx,
+                                 nsresult result)
 {
-    return NS_ERROR_NOT_IMPLEMENTED;
+    NS_NAMED_LITERAL_CSTRING(buffer, "</pre>\n");
+    uint32_t n;
+    mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+
+    CloseContent();
+
+    return NS_OK;
 }
+
+void
+nsAboutCacheEntry::CloseContent()
+{
+    NS_NAMED_LITERAL_CSTRING(buffer, "</body>\n</html>\n");
+    uint32_t n;
+    mOutputStream->Write(buffer.get(), buffer.Length(), &n);
+
+    mOutputStream->Close();
+    mOutputStream = nullptr;
+}
--- a/netwerk/protocol/about/nsAboutCacheEntry.h
+++ b/netwerk/protocol/about/nsAboutCacheEntry.h
@@ -2,51 +2,74 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsAboutCacheEntry_h__
 #define nsAboutCacheEntry_h__
 
 #include "nsIAboutModule.h"
-#include "nsICacheListener.h"
-#include "nsICacheEntryDescriptor.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheEntry.h"
 #include "nsCOMPtr.h"
 
 class nsIAsyncOutputStream;
 class nsIInputStream;
 class nsIURI;
 class nsCString;
 
 class nsAboutCacheEntry : public nsIAboutModule
-                        , public nsICacheMetaDataVisitor
-                        , public nsICacheListener
+                        , public nsICacheEntryOpenCallback
+                        , public nsICacheEntryMetaDataVisitor
+                        , public nsIStreamListener
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIABOUTMODULE
-    NS_DECL_NSICACHEMETADATAVISITOR
-    NS_DECL_NSICACHELISTENER
+    NS_DECL_NSICACHEENTRYOPENCALLBACK
+    NS_DECL_NSICACHEENTRYMETADATAVISITOR
+    NS_DECL_NSIREQUESTOBSERVER
+    NS_DECL_NSISTREAMLISTENER
 
     nsAboutCacheEntry()
         : mBuffer(nullptr)
+        , mWaitingForData(false)
+        , mHexDumpState(0)
     {}
 
     virtual ~nsAboutCacheEntry() {}
 
 private:
     nsresult GetContentStream(nsIURI *, nsIInputStream **);
     nsresult OpenCacheEntry(nsIURI *);
-    nsresult WriteCacheEntryDescription(nsICacheEntryDescriptor *);
+    nsresult OpenCacheEntry();
+    nsresult WriteCacheEntryDescription(nsICacheEntry *);
     nsresult WriteCacheEntryUnavailable();
-    nsresult ParseURI(nsIURI *, nsCString &, bool &, nsCString &);
+    nsresult ParseURI(nsIURI *uri, nsACString &storageName,
+                      nsILoadContextInfo **loadInfo,
+                      nsCString &enahnceID, nsIURI **cacheUri);
+    void CloseContent();
+
+    static NS_METHOD
+    PrintCacheData(nsIInputStream *aInStream,
+                   void *aClosure,
+                   const char *aFromSegment,
+                   uint32_t aToOffset,
+                   uint32_t aCount,
+                   uint32_t *aWriteCount);
 
 private:
+    nsAutoCString mStorageName, mEnhanceId;
+    nsCOMPtr<nsILoadContextInfo> mLoadInfo;
+    nsCOMPtr<nsIURI> mCacheURI;
+
     nsCString *mBuffer;
     nsCOMPtr<nsIAsyncOutputStream> mOutputStream;
+    bool mWaitingForData;
+    uint32_t mHexDumpState;
 };
 
 #define NS_ABOUT_CACHE_ENTRY_MODULE_CID              \
 { /* 7fa5237d-b0eb-438f-9e50-ca0166e63788 */         \
     0x7fa5237d,                                      \
     0xb0eb,                                          \
     0x438f,                                          \
     {0x9e, 0x50, 0xca, 0x01, 0x66, 0xe6, 0x37, 0x88} \
--- a/netwerk/test/unit/head_cache2.js
+++ b/netwerk/test/unit/head_cache2.js
@@ -284,19 +284,19 @@ VisitCallback.prototype =
     do_check_eq(this.num, num);
     if (newCacheBackEndUsed()) {
       // Consumption is as expected only in the new backend
       do_check_eq(this.consumption, consumption);
     }
     if (!this.entries)
       this.notify();
   },
-  onCacheEntryInfo: function(entry)
+  onCacheEntryInfo: function(aURI, aIdEnhance, aDataSize, aFetchCount, aLastModifiedTime, aExpirationTime)
   {
-    var key = entry.key;
+    var key = (aIdEnhance ? (aIdEnhance + ":") : "") + aURI.asciiSpec;
     LOG_C2(this, "onCacheEntryInfo: key=" + key);
 
     do_check_true(!!this.entries);
 
     var index = this.entries.indexOf(key);
     do_check_true(index > -1);
 
     this.entries.splice(index, 1);
--- a/netwerk/test/unit/test_cache2-05-visit.js
+++ b/netwerk/test/unit/test_cache2-05-visit.js
@@ -1,21 +1,26 @@
 function run_test()
 {
   do_get_profile();
 
   var storage = getCacheStorage("disk");
   var mc = new MultipleCallbacks(4, function() {
     syncWithCacheIOThread(function() {
+
+      var expectedConsumption = newCacheBackEndUsed()
+        ? 4096
+        : 48;
+
       storage.asyncVisitStorage(
         // Test should store 4 entries
-        new VisitCallback(4, 48, ["http://a/", "http://b/", "http://c/", "http://d/"], function() {
+        new VisitCallback(4, expectedConsumption, ["http://a/", "http://b/", "http://c/", "http://d/"], function() {
           storage.asyncVisitStorage(
             // Still 4 entries expected, now don't walk them
-            new VisitCallback(4, 48, null, function() {
+            new VisitCallback(4, expectedConsumption, null, function() {
               finish_cache2_test();
             }),
             false
           );
         }),
         true
       );
     });
--- a/netwerk/test/unit/test_cache2-07-visit-memory.js
+++ b/netwerk/test/unit/test_cache2-07-visit-memory.js
@@ -11,18 +11,18 @@ function run_test()
   var mc = new MultipleCallbacks(5, function() {
     // Check it's there by visiting the storage
     syncWithCacheIOThread(function() {
       var storage = getCacheStorage("memory");
       storage.asyncVisitStorage(
         new VisitCallback(1, 12, ["http://mem1/"], function() {
           storage = getCacheStorage("disk");
           storage.asyncVisitStorage(
-            // Previous tests should store 4 disk entries + 1 memory entry
-            new VisitCallback(5, 60, ["http://a/", "http://b/", "http://c/", "http://d/", "http://mem1/"], function() {
+            // Previous tests should store 4 disk entries
+            new VisitCallback(4, 4096, ["http://a/", "http://b/", "http://c/", "http://d/"], function() {
               finish_cache2_test();
             }),
             true
           );
         }),
         true
       );
     });
--- a/netwerk/test/unit/test_cache2-11-evict-memory.js
+++ b/netwerk/test/unit/test_cache2-11-evict-memory.js
@@ -4,18 +4,23 @@ function run_test()
 
   var storage = getCacheStorage("memory");
   var mc = new MultipleCallbacks(3, function() {
     storage.asyncEvictStorage(
       new EvictionCallback(true, function() {
         storage.asyncVisitStorage(
           new VisitCallback(0, 0, [], function() {
             var storage = getCacheStorage("disk");
+
+            var expectedConsumption = newCacheBackEndUsed()
+              ? 2048
+              : 24;
+
             storage.asyncVisitStorage(
-              new VisitCallback(2, 24, ["http://a/", "http://b/"], function() {
+              new VisitCallback(2, expectedConsumption, ["http://a/", "http://b/"], function() {
                 finish_cache2_test();
               }),
               true
             );
           }),
           true
         );
       })
--- a/netwerk/test/unit/test_cache2-22-anon-visit.js
+++ b/netwerk/test/unit/test_cache2-22-anon-visit.js
@@ -3,38 +3,38 @@ Components.utils.import('resource://gre/
 function run_test()
 {
   do_get_profile();
 
   function checkNewBackEnd()
   {
     var storage = getCacheStorage("disk", LoadContextInfo.default);
     storage.asyncVisitStorage(
-      new VisitCallback(1, 12, ["http://an2/"], function() {
+      new VisitCallback(1, 1024, ["http://an2/"], function() {
         storage = getCacheStorage("disk", LoadContextInfo.anonymous);
         storage.asyncVisitStorage(
-          new VisitCallback(1, 12, ["http://an2/"], function() {
+          new VisitCallback(1, 1024, ["http://an2/"], function() {
             finish_cache2_test();
           }),
           true
         );
       }),
       true
     );
   }
 
   function checkOldBackEnd()
   {
     syncWithCacheIOThread(function() {
       var storage = getCacheStorage("disk", LoadContextInfo.default);
       storage.asyncVisitStorage(
-        new VisitCallback(2, 24, ["http://an2/", "anon&uri=http://an2/"], function() {
+        new VisitCallback(2, 24, ["http://an2/"], function() {
           storage = getCacheStorage("disk", LoadContextInfo.anonymous);
           storage.asyncVisitStorage(
-            new VisitCallback(0, 0, [], function() {
+            new VisitCallback(0, 0, ["http://an2/"], function() {
               finish_cache2_test();
             }),
             true
           );
         }),
         true
       );
     });
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutcache/content/aboutCache.js
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// First, parse and save the incoming arguments ("?storage=name&context=key")
+// Note: window.location.search doesn't work with nsSimpleURIs used for about:* addresses.
+var search = window.location.href.match(/^.*\?(.*)$/);
+var searchParams = new URLSearchParams(search ? search[1] : '');
+var storage = searchParams.get('storage');
+var context = searchParams.get('context');
+
+// The context is in a format as used by the HTTP cache v2 back end
+var [context, isAnon, isInBrowser, appId, isPrivate] = context.match(/(a,)?(b,)?(i\d+,)?(p,)?/);
+if (appId)
+  appId = appId.match(/i(\d+),/)[1];
+
+
+function $(id) { return document.getElementById(id) || {}; }
+
+// Initialize the context UI controls at the start according what we got in the "context=" argument
+addEventListener('DOMContentLoaded', function() {
+  $('anon').checked = !!isAnon;
+  $('inbrowser').checked = !!isInBrowser;
+  $('appid').value = appId || '';
+  $('priv').checked = !!isPrivate;
+}, false);
+
+// When user presses the [Update] button, we build a new context key according the UI control
+// values and navigate to a new about:cache?storage=<name>&context=<key> URL.
+function navigate()
+{
+  context = '';
+  if ($('anon').checked)
+    context += 'a,';
+  if ($('inbrowser').checked)
+    context += 'b,';
+  if ($('appid').value)
+    context += 'i' + $('appid').value + ',';
+  if ($('priv').checked)
+    context += 'p,';
+
+  window.location.href = 'about:cache?storage=' + storage + '&context=' + context;
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutcache/jar.mn
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
++ content/global/aboutCache.js                         (content/aboutCache.js)
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutcache/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn']
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -4,16 +4,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # These component dirs are built for all apps (including suite)
 if CONFIG['MOZ_ENABLE_XREMOTE']:
     PARALLEL_DIRS += ['remote']
 
 PARALLEL_DIRS += [
+    'aboutcache',
     'aboutmemory',
     'alerts',
     'apppicker',
     'commandlines',
     'console',
     'contentprefs',
     'cookie',
     'crashmonitor',