Bug 916052 - Adapt about:cache to the HTTP cache v2 API, r=michal+MattN
authorHonza Bambas <honzab.moz@firemni.cz>
Thu, 01 May 2014 13:28:12 +0200
changeset 201508 d4b2e930ecd52c6be982783ee83a7b0fc68a5918
parent 201458 7dc742da42106567618c91c0b2b4886e1135c572
child 201509 7f7abc547fb9fc435063572e9b36839dc0604d9e
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmichal
bugs916052
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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,84 @@
 /* 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"
+#include "nsTArray.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,29 @@
 /* -*- 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 "CacheFileUtils.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 +74,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 +148,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 +352,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,77 @@
 /* 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 "nsIStreamListener.h"
+#include "nsString.h"
 #include "nsCOMPtr.h"
 
 class nsIAsyncOutputStream;
 class nsIInputStream;
+class nsILoadContextInfo;
 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',