Bug 977766 - HTTP cache v2: selective deletion of data (by load context info), r=honzab, michal
authorMichal Novotny <michal.novotny@gmail.com>
Thu, 10 Apr 2014 12:47:20 +0200
changeset 197520 40a275481f3fc80aa024fc1707a7f8ef816fc0a8
parent 197519 8b7753f12156e4a7f17aad2abac42639a4005149
child 197521 ac06eacc22068523dfc8f5e0d2813e1e3ce085aa
push id486
push userasasaki@mozilla.com
push dateMon, 14 Jul 2014 18:39:42 +0000
treeherdermozilla-release@d33428174ff1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab, michal
bugs977766
milestone31.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 977766 - HTTP cache v2: selective deletion of data (by load context info), r=honzab, michal
netwerk/base/public/nsILoadContextInfo.idl
netwerk/cache2/CacheFileContextEvictor.cpp
netwerk/cache2/CacheFileContextEvictor.h
netwerk/cache2/CacheFileIOManager.cpp
netwerk/cache2/CacheFileIOManager.h
netwerk/cache2/CacheFileUtils.cpp
netwerk/cache2/CacheFileUtils.h
netwerk/cache2/CacheIndex.cpp
netwerk/cache2/CacheIndex.h
netwerk/cache2/CacheIndexContextIterator.cpp
netwerk/cache2/CacheIndexContextIterator.h
netwerk/cache2/CacheIndexIterator.cpp
netwerk/cache2/CacheIndexIterator.h
netwerk/cache2/CacheStorageService.cpp
netwerk/cache2/CacheStorageService.h
netwerk/cache2/moz.build
netwerk/test/unit/test_cache2-12-evict-disk.js
--- a/netwerk/base/public/nsILoadContextInfo.idl
+++ b/netwerk/base/public/nsILoadContextInfo.idl
@@ -62,10 +62,18 @@ interface nsILoadContextInfo : nsISuppor
   }
 
   bool IsAnonymous()
   {
     bool anon;
     GetIsAnonymous(&anon);
     return anon;
   }
+
+  bool Equals(nsILoadContextInfo *aOther)
+  {
+    return (IsPrivate() == aOther->IsPrivate() &&
+            AppId() == aOther->AppId() &&
+            IsInBrowserElement() == aOther->IsInBrowserElement() &&
+            IsAnonymous() == aOther->IsAnonymous());
+  }
 %}
 };
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileContextEvictor.cpp
@@ -0,0 +1,605 @@
+/* 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 "CacheLog.h"
+#include "CacheFileContextEvictor.h"
+#include "CacheFileIOManager.h"
+#include "CacheIndex.h"
+#include "CacheIndexIterator.h"
+#include "CacheFileUtils.h"
+#include "nsIFile.h"
+#include "LoadContextInfo.h"
+#include "nsThreadUtils.h"
+#include "nsString.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIDirectoryEnumerator.h"
+#include "mozilla/Base64.h"
+
+
+namespace mozilla {
+namespace net {
+
+const char kContextEvictionPrefix[] = "ce_";
+const uint32_t kContextEvictionPrefixLength =
+  sizeof(kContextEvictionPrefix) - 1;
+
+bool CacheFileContextEvictor::sDiskAlreadySearched = false;
+
+CacheFileContextEvictor::CacheFileContextEvictor()
+  : mEvicting(false)
+  , mIndexIsUpToDate(false)
+{
+  LOG(("CacheFileContextEvictor::CacheFileContextEvictor() [this=%p]", this));
+}
+
+CacheFileContextEvictor::~CacheFileContextEvictor()
+{
+  LOG(("CacheFileContextEvictor::~CacheFileContextEvictor() [this=%p]", this));
+}
+
+nsresult
+CacheFileContextEvictor::Init(nsIFile *aCacheDirectory)
+{
+  LOG(("CacheFileContextEvictor::Init()"));
+
+  nsresult rv;
+
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+  CacheIndex::IsUpToDate(&mIndexIsUpToDate);
+
+  mCacheDirectory = aCacheDirectory;
+
+  rv = aCacheDirectory->Clone(getter_AddRefs(mEntriesDir));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mEntriesDir->AppendNative(NS_LITERAL_CSTRING(kEntriesDir));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!sDiskAlreadySearched) {
+    LoadEvictInfoFromDisk();
+    if ((mEntries.Length() != 0) && mIndexIsUpToDate) {
+      CreateIterators();
+      StartEvicting();
+    }
+  }
+
+  return NS_OK;
+}
+
+uint32_t
+CacheFileContextEvictor::ContextsCount()
+{
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+  return mEntries.Length();
+}
+
+nsresult
+CacheFileContextEvictor::AddContext(nsILoadContextInfo *aLoadContextInfo)
+{
+  LOG(("CacheFileContextEvictor::AddContext() [this=%p, loadContextInfo=%p]",
+       this, aLoadContextInfo));
+
+  nsresult rv;
+
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+  CacheFileContextEvictorEntry *entry = nullptr;
+  for (uint32_t i = 0; i < mEntries.Length(); ++i) {
+    if (mEntries[i]->mInfo->Equals(aLoadContextInfo)) {
+      entry = mEntries[i];
+      break;
+    }
+  }
+
+  if (!entry) {
+    entry = new CacheFileContextEvictorEntry();
+    entry->mInfo = aLoadContextInfo;
+    mEntries.AppendElement(entry);
+  }
+
+  entry->mTimeStamp = PR_Now() / PR_USEC_PER_MSEC;
+
+  PersistEvictionInfoToDisk(aLoadContextInfo);
+
+  if (mIndexIsUpToDate) {
+    // Already existing context could be added again, in this case the iterator
+    // would be recreated. Close the old iterator explicitely.
+    if (entry->mIterator) {
+      entry->mIterator->Close();
+      entry->mIterator = nullptr;
+    }
+
+    rv = CacheIndex::GetIterator(aLoadContextInfo, false,
+                                 getter_AddRefs(entry->mIterator));
+    if (NS_FAILED(rv)) {
+      // This could probably happen during shutdown. Remove the entry from
+      // the array, but leave the info on the disk. No entry can be opened
+      // during shutdown and we'll load the eviction info on next start.
+      LOG(("CacheFileContextEvictor::AddContext() - Cannot get an iterator. "
+           "[rv=0x%08x]", rv));
+      mEntries.RemoveElement(entry);
+      return rv;
+    }
+
+    StartEvicting();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileContextEvictor::CacheIndexStateChanged()
+{
+  LOG(("CacheFileContextEvictor::CacheIndexStateChanged() [this=%p]", this));
+
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+  bool isUpToDate = false;
+  CacheIndex::IsUpToDate(&isUpToDate);
+  if (mEntries.Length() == 0) {
+    // Just save the state and exit, since there is nothing to do
+    mIndexIsUpToDate = isUpToDate;
+    return NS_OK;
+  }
+
+  if (!isUpToDate && !mIndexIsUpToDate) {
+    // Index is outdated and status has not changed, nothing to do.
+    return NS_OK;
+  }
+
+  if (isUpToDate && mIndexIsUpToDate) {
+    // Status has not changed, but make sure the eviction is running.
+    if (mEvicting) {
+      return NS_OK;
+    }
+
+    // We're not evicting, but we should be evicting?!
+    LOG(("CacheFileContextEvictor::CacheIndexStateChanged() - Index is up to "
+         "date, we have some context to evict but eviction is not running! "
+         "Starting now."));
+  }
+
+  mIndexIsUpToDate = isUpToDate;
+
+  if (mIndexIsUpToDate) {
+    CreateIterators();
+    StartEvicting();
+  } else {
+    CloseIterators();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileContextEvictor::WasEvicted(const nsACString &aKey, nsIFile *aFile,
+                                    bool *_retval)
+{
+  LOG(("CacheFileContextEvictor::WasEvicted() [key=%s]",
+       PromiseFlatCString(aKey).get()));
+
+  nsresult rv;
+
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+  nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(aKey);
+  MOZ_ASSERT(info);
+  if (!info) {
+    LOG(("CacheFileContextEvictor::WasEvicted() - Cannot parse key!"));
+    *_retval = false;
+    return NS_OK;
+  }
+
+  CacheFileContextEvictorEntry *entry = nullptr;
+  for (uint32_t i = 0; i < mEntries.Length(); ++i) {
+    if (info->Equals(mEntries[i]->mInfo)) {
+      entry = mEntries[i];
+      break;
+    }
+  }
+
+  if (!entry) {
+    LOG(("CacheFileContextEvictor::WasEvicted() - Didn't find equal context, "
+         "returning false."));
+    *_retval = false;
+    return NS_OK;
+  }
+
+  PRTime lastModifiedTime;
+  rv = aFile->GetLastModifiedTime(&lastModifiedTime);
+  if (NS_FAILED(rv)) {
+    LOG(("CacheFileContextEvictor::WasEvicted() - Cannot get last modified time"
+         ", returning false."));
+    *_retval = false;
+    return NS_OK;
+  }
+
+  *_retval = !(lastModifiedTime > entry->mTimeStamp);
+  LOG(("CacheFileContextEvictor::WasEvicted() - returning %s. [mTimeStamp=%lld,"
+       " lastModifiedTime=%lld]", *_retval ? "true" : "false",
+       mEntries[0]->mTimeStamp, lastModifiedTime));
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileContextEvictor::PersistEvictionInfoToDisk(
+  nsILoadContextInfo *aLoadContextInfo)
+{
+  LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() [this=%p, "
+       "loadContextInfo=%p]", this, aLoadContextInfo));
+
+  nsresult rv;
+
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+  nsCOMPtr<nsIFile> file;
+  rv = GetContextFile(aLoadContextInfo, getter_AddRefs(file));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+#ifdef PR_LOGGING
+  nsAutoCString path;
+  file->GetNativePath(path);
+#endif
+
+  PRFileDesc *fd;
+  rv = file->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600,
+                              &fd);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() - Creating file "
+         "failed! [path=%s, rv=0x%08x]", path.get(), rv));
+    return rv;
+  }
+
+  PR_Close(fd);
+
+  LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() - Successfully "
+       "created file. [path=%s]", path.get()));
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileContextEvictor::RemoveEvictInfoFromDisk(
+  nsILoadContextInfo *aLoadContextInfo)
+{
+  LOG(("CacheFileContextEvictor::RemoveEvictInfoFromDisk() [this=%p, "
+       "loadContextInfo=%p]", this, aLoadContextInfo));
+
+  nsresult rv;
+
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+  nsCOMPtr<nsIFile> file;
+  rv = GetContextFile(aLoadContextInfo, getter_AddRefs(file));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+#ifdef PR_LOGGING
+  nsAutoCString path;
+  file->GetNativePath(path);
+#endif
+
+  rv = file->Remove(false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    LOG(("CacheFileContextEvictor::RemoveEvictionInfoFromDisk() - Removing file"
+         " failed! [path=%s, rv=0x%08x]", path.get(), rv));
+    return rv;
+  }
+
+  LOG(("CacheFileContextEvictor::RemoveEvictionInfoFromDisk() - Successfully "
+       "removed file. [path=%s]", path.get()));
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileContextEvictor::LoadEvictInfoFromDisk()
+{
+  LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() [this=%p]", this));
+
+  nsresult rv;
+
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+  sDiskAlreadySearched = true;
+
+  nsCOMPtr<nsISimpleEnumerator> enumerator;
+  rv = mCacheDirectory->GetDirectoryEntries(getter_AddRefs(enumerator));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIDirectoryEnumerator> dirEnum = do_QueryInterface(enumerator, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  while (true) {
+    nsCOMPtr<nsIFile> file;
+    rv = dirEnum->GetNextFile(getter_AddRefs(file));
+    if (!file) {
+      break;
+    }
+
+    bool isDir = false;
+    file->IsDirectory(&isDir);
+    if (isDir) {
+      continue;
+    }
+
+    nsAutoCString leaf;
+    rv = file->GetNativeLeafName(leaf);
+    if (NS_FAILED(rv)) {
+      LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() - "
+           "GetNativeLeafName() failed! Skipping file."));
+      continue;
+    }
+
+    if (leaf.Length() < kContextEvictionPrefixLength) {
+      continue;
+    }
+
+    if (!StringBeginsWith(leaf, NS_LITERAL_CSTRING(kContextEvictionPrefix))) {
+      continue;
+    }
+
+    nsAutoCString encoded;
+    encoded = Substring(leaf, kContextEvictionPrefixLength);
+    encoded.ReplaceChar('-', '/');
+
+    nsAutoCString decoded;
+    rv = Base64Decode(encoded, decoded);
+    if (NS_FAILED(rv)) {
+      LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() - Base64 decoding "
+           "failed. Removing the file. [file=%s]", leaf.get()));
+      file->Remove(false);
+      continue;
+    }
+
+    nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(decoded);
+
+    if (!info) {
+      LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() - Cannot parse "
+           "context key, removing file. [contextKey=%s, file=%s]",
+           decoded.get(), leaf.get()));
+      file->Remove(false);
+      continue;
+    }
+
+    PRTime lastModifiedTime;
+    rv = file->GetLastModifiedTime(&lastModifiedTime);
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+
+    CacheFileContextEvictorEntry *entry = new CacheFileContextEvictorEntry();
+    entry->mInfo = info;
+    entry->mTimeStamp = lastModifiedTime;
+    mEntries.AppendElement(entry);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileContextEvictor::GetContextFile(nsILoadContextInfo *aLoadContextInfo,
+                                        nsIFile **_retval)
+{
+  nsresult rv;
+
+  nsAutoCString leafName;
+  leafName.Assign(NS_LITERAL_CSTRING(kContextEvictionPrefix));
+
+  nsAutoCString keyPrefix;
+  CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, keyPrefix);
+
+  // TODO: This hack is needed because current CacheFileUtils::ParseKey() can
+  // parse only the whole key and not just the key prefix generated by
+  // CacheFileUtils::CreateKeyPrefix(). This should be removed once bug #968593
+  // is fixed.
+  keyPrefix.Append(":foo");
+
+  nsAutoCString data64;
+  rv = Base64Encode(keyPrefix, data64);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Replace '/' with '-' since '/' cannot be part of the filename.
+  data64.ReplaceChar('/', '-');
+
+  leafName.Append(data64);
+
+  nsCOMPtr<nsIFile> file;
+  rv = mCacheDirectory->Clone(getter_AddRefs(file));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = file->AppendNative(leafName);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  file.swap(*_retval);
+  return NS_OK;
+}
+
+void
+CacheFileContextEvictor::CreateIterators()
+{
+  LOG(("CacheFileContextEvictor::CreateIterators() [this=%p]", this));
+
+  CloseIterators();
+
+  nsresult rv;
+
+  for (uint32_t i = 0; i < mEntries.Length(); ) {
+    rv = CacheIndex::GetIterator(mEntries[i]->mInfo, false,
+                                 getter_AddRefs(mEntries[i]->mIterator));
+    if (NS_FAILED(rv)) {
+      LOG(("CacheFileContextEvictor::CreateIterators() - Cannot get an iterator"
+           ". [rv=0x%08x]", rv));
+      mEntries.RemoveElementAt(i);
+      continue;
+    }
+
+    ++i;
+  }
+}
+
+void
+CacheFileContextEvictor::CloseIterators()
+{
+  LOG(("CacheFileContextEvictor::CloseIterators() [this=%p]", this));
+
+  for (uint32_t i = 0; i < mEntries.Length(); ++i) {
+    if (mEntries[i]->mIterator) {
+      mEntries[i]->mIterator->Close();
+      mEntries[i]->mIterator = nullptr;
+    }
+  }
+}
+
+void
+CacheFileContextEvictor::StartEvicting()
+{
+  LOG(("CacheFileContextEvictor::StartEvicting() [this=%p]", this));
+
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+  if (mEvicting) {
+    LOG(("CacheFileContextEvictor::StartEvicting() - already evicintg."));
+    return;
+  }
+
+  if (mEntries.Length() == 0) {
+    LOG(("CacheFileContextEvictor::StartEvicting() - no context to evict."));
+    return;
+  }
+
+  nsCOMPtr<nsIRunnable> ev;
+  ev = NS_NewRunnableMethod(this, &CacheFileContextEvictor::EvictEntries);
+
+  nsRefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
+
+  nsresult rv = ioThread->Dispatch(ev, CacheIOThread::EVICT);
+  if (NS_FAILED(rv)) {
+    LOG(("CacheFileContextEvictor::StartEvicting() - Cannot dispatch event to "
+         "IO thread. [rv=0x%08x]", rv));
+  }
+
+  mEvicting = true;
+}
+
+nsresult
+CacheFileContextEvictor::EvictEntries()
+{
+  LOG(("CacheFileContextEvictor::EvictEntries()"));
+
+  nsresult rv;
+
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+  mEvicting = false;
+
+  if (!mIndexIsUpToDate) {
+    LOG(("CacheFileContextEvictor::EvictEntries() - Stopping evicting due to "
+         "outdated index."));
+    return NS_OK;
+  }
+
+  while (true) {
+    if (CacheIOThread::YieldAndRerun()) {
+      LOG(("CacheFileContextEvictor::EvictEntries() - Breaking loop for higher "
+           "level events."));
+      mEvicting = true;
+      return NS_OK;
+    }
+
+    if (mEntries.Length() == 0) {
+      LOG(("CacheFileContextEvictor::EvictEntries() - Stopping evicting, there "
+           "is no context to evict."));
+      return NS_OK;
+    }
+
+    SHA1Sum::Hash hash;
+    rv = mEntries[0]->mIterator->GetNextHash(&hash);
+    if (rv == NS_ERROR_NOT_AVAILABLE) {
+      LOG(("CacheFileContextEvictor::EvictEntries() - No more entries left in "
+           "iterator. [iterator=%p, info=%p]", mEntries[0]->mIterator.get(),
+           mEntries[0]->mInfo.get()));
+      RemoveEvictInfoFromDisk(mEntries[0]->mInfo);
+      mEntries.RemoveElementAt(0);
+      continue;
+    } else if (NS_FAILED(rv)) {
+      LOG(("CacheFileContextEvictor::EvictEntries() - Iterator failed to "
+           "provide next hash (shutdown?), keeping eviction info on disk."
+           " [iterator=%p, info=%p]", mEntries[0]->mIterator.get(),
+           mEntries[0]->mInfo.get()));
+      mEntries.RemoveElementAt(0);
+      continue;
+    }
+
+    LOG(("CacheFileContextEvictor::EvictEntries() - Processing hash. "
+         "[hash=%08x%08x%08x%08x%08x, iterator=%p, info=%p]", LOGSHA1(&hash),
+         mEntries[0]->mIterator.get(), mEntries[0]->mInfo.get()));
+
+    nsRefPtr<CacheFileHandle> handle;
+    CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, false,
+                                                      getter_AddRefs(handle));
+    if (handle) {
+      // We doom any active handle in CacheFileIOManager::EvictByContext(), so
+      // this must be a new one. Skip it.
+      LOG(("CacheFileContextEvictor::EvictEntries() - Skipping entry since we "
+           "found an active handle. [handle=%p]", handle.get()));
+      continue;
+    }
+
+    nsAutoCString leafName;
+    CacheFileIOManager::HashToStr(&hash, leafName);
+
+    PRTime lastModifiedTime;
+    nsCOMPtr<nsIFile> file;
+    rv = mEntriesDir->Clone(getter_AddRefs(file));
+    if (NS_SUCCEEDED(rv)) {
+      rv = file->AppendNative(leafName);
+    }
+    if (NS_SUCCEEDED(rv)) {
+      rv = file->GetLastModifiedTime(&lastModifiedTime);
+    }
+    if (NS_FAILED(rv)) {
+      LOG(("CacheFileContextEvictor::EvictEntries() - Cannot get last modified "
+           "time, skipping entry."));
+      continue;
+    }
+
+    if (lastModifiedTime > mEntries[0]->mTimeStamp) {
+      LOG(("CacheFileContextEvictor::EvictEntries() - Skipping newer entry. "
+           "[mTimeStamp=%lld, lastModifiedTime=%lld]", mEntries[0]->mTimeStamp,
+           lastModifiedTime));
+      continue;
+    }
+
+    LOG(("CacheFileContextEvictor::EvictEntries - Removing entry."));
+    file->Remove(false);
+    CacheIndex::RemoveEntry(&hash);
+  }
+
+  NS_NOTREACHED("We should never get here");
+  return NS_OK;
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheFileContextEvictor.h
@@ -0,0 +1,91 @@
+/* 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 CacheFileContextEvictor__h__
+#define CacheFileContextEvictor__h__
+
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+
+class nsIFile;
+class nsILoadContextInfo;
+
+namespace mozilla {
+namespace net {
+
+class CacheIndexIterator;
+
+struct CacheFileContextEvictorEntry
+{
+  nsCOMPtr<nsILoadContextInfo> mInfo;
+  PRTime                       mTimeStamp; // in milliseconds
+  nsRefPtr<CacheIndexIterator> mIterator;
+};
+
+class CacheFileContextEvictor
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheFileContextEvictor)
+
+  CacheFileContextEvictor();
+  virtual ~CacheFileContextEvictor();
+
+  nsresult Init(nsIFile *aCacheDirectory);
+
+  // Returns number of contexts that are being evicted.
+  uint32_t ContextsCount();
+  // Start evicting given context.
+  nsresult AddContext(nsILoadContextInfo *aLoadContextInfo);
+  // CacheFileIOManager calls this method when CacheIndex's state changes. We
+  // check whether the index is up to date and start or stop evicting according
+  // to index's state.
+  nsresult CacheIndexStateChanged();
+  // CacheFileIOManager calls this method to check whether an entry file should
+  // be considered as evicted. It returns true when there is a matching context
+  // info to the given key and the last modified time of the entry file is
+  // earlier than the time stamp of the time when the context was added to the
+  // evictor.
+  nsresult WasEvicted(const nsACString &aKey, nsIFile *aFile, bool *_retval);
+
+private:
+  // Writes information about eviction of the given context to the disk. This is
+  // done for every context added to the evictor to be able to recover eviction
+  // after a shutdown or crash. When the context file is found after startup, we
+  // restore mTimeStamp from the last modified time of the file.
+  nsresult PersistEvictionInfoToDisk(nsILoadContextInfo *aLoadContextInfo);
+  // Once we are done with eviction for the given context, the eviction info is
+  // removed from the disk.
+  nsresult RemoveEvictInfoFromDisk(nsILoadContextInfo *aLoadContextInfo);
+  // Tries to load all contexts from the disk. This method is called just once
+  // after startup.
+  nsresult LoadEvictInfoFromDisk();
+  nsresult GetContextFile(nsILoadContextInfo *aLoadContextInfo,
+                          nsIFile **_retval);
+
+  void     CreateIterators();
+  void     CloseIterators();
+  void     StartEvicting();
+  nsresult EvictEntries();
+
+  // Whether eviction is in progress
+  bool mEvicting;
+  // Whether index is up to date. We wait with eviction until the index finishes
+  // update process when it is outdated. NOTE: We also stop eviction in progress
+  // when the index is found outdated, the eviction is restarted again once the
+  // update process finishes.
+  bool mIndexIsUpToDate;
+  // Whether we already tried to restore unfinished jobs from previous run after
+  // startup.
+  static bool sDiskAlreadySearched;
+  // Array of contexts being evicted.
+  nsTArray<nsAutoPtr<CacheFileContextEvictorEntry> > mEntries;
+  nsCOMPtr<nsIFile> mCacheDirectory;
+  nsCOMPtr<nsIFile> mEntriesDir;
+};
+
+} // net
+} // mozilla
+
+#endif
--- a/netwerk/cache2/CacheFileIOManager.cpp
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -9,16 +9,17 @@
 #include "CacheHashUtils.h"
 #include "CacheStorageService.h"
 #include "CacheIndex.h"
 #include "CacheFileUtils.h"
 #include "nsThreadUtils.h"
 #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 "nsISizeOf.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Services.h"
@@ -588,17 +589,17 @@ public:
       if (NS_SUCCEEDED(mRV)) {
         if (!mIOMan) {
           mRV = NS_ERROR_NOT_INITIALIZED;
         } else {
           if (mFlags & CacheFileIOManager::SPECIAL_FILE) {
             mRV = mIOMan->OpenSpecialFileInternal(mKey, mFlags,
                                                   getter_AddRefs(mHandle));
           } else {
-            mRV = mIOMan->OpenFileInternal(&mHash, mFlags,
+            mRV = mIOMan->OpenFileInternal(&mHash, mKey, mFlags,
                                            getter_AddRefs(mHandle));
           }
           mIOMan = nullptr;
           if (mHandle) {
             MOZ_EVENT_TRACER_NAME_OBJECT(mHandle.get(), mKey.get());
             if (mHandle->Key().IsEmpty()) {
               mHandle->Key() = mKey;
             }
@@ -1543,21 +1544,23 @@ CacheFileIOManager::OpenFile(const nsACS
     : CacheIOThread::OPEN);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash *aHash,
+                                     const nsACString &aKey,
                                      uint32_t aFlags,
                                      CacheFileHandle **_retval)
 {
   LOG(("CacheFileIOManager::OpenFileInternal() [hash=%08x%08x%08x%08x%08x, "
-       "flags=%d]", LOGSHA1(aHash), aFlags));
+       "key=%s, flags=%d]", LOGSHA1(aHash), PromiseFlatCString(aKey).get(),
+       aFlags));
 
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   nsresult rv;
 
   if (mShuttingDown) {
     return NS_ERROR_NOT_INITIALIZED;
   }
@@ -1610,16 +1613,32 @@ CacheFileIOManager::OpenFileInternal(con
     handle.swap(*_retval);
     return NS_OK;
   }
 
   bool exists;
   rv = file->Exists(&exists);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  if (exists && mContextEvictor) {
+    if (mContextEvictor->ContextsCount() == 0) {
+      mContextEvictor = nullptr;
+    } else {
+      bool wasEvicted = false;
+      mContextEvictor->WasEvicted(aKey, file, &wasEvicted);
+      if (wasEvicted) {
+        LOG(("CacheFileIOManager::OpenFileInternal() - Removing file since the "
+             "entry was evicted by EvictByContext()"));
+        exists = false;
+        file->Remove(false);
+        CacheIndex::RemoveEntry(aHash);
+      }
+    }
+  }
+
   if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (exists) {
@@ -2559,17 +2578,18 @@ CacheFileIOManager::EvictAllInternal()
 
   // Doom all active handles
   nsTArray<nsRefPtr<CacheFileHandle> > handles;
   mHandles.GetActiveHandles(&handles);
 
   for (uint32_t i = 0; i < handles.Length(); ++i) {
     rv = DoomFileInternal(handles[i]);
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
+      LOG(("CacheFileIOManager::EvictAllInternal() - Cannot doom handle "
+           "[handle=%p]", handles[i].get()));
     }
   }
 
   nsCOMPtr<nsIFile> file;
   rv = mCacheDirectory->Clone(getter_AddRefs(file));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -2594,16 +2614,154 @@ CacheFileIOManager::EvictAllInternal()
     return rv;
   }
 
   CacheIndex::RemoveAll();
 
   return NS_OK;
 }
 
+// static
+nsresult
+CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo)
+{
+  LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]",
+       aLoadContextInfo));
+
+  nsresult rv;
+  nsRefPtr<CacheFileIOManager> ioMan = gInstance;
+
+  if (!ioMan) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  nsCOMPtr<nsIRunnable> ev;
+  ev = NS_NewRunnableMethodWithArg<nsCOMPtr<nsILoadContextInfo> >
+         (ioMan, &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo);
+
+  rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo)
+{
+  LOG(("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, "
+       "anonymous=%u, inBrowser=%u, appId=%u]", aLoadContextInfo,
+       aLoadContextInfo->IsAnonymous(), aLoadContextInfo->IsInBrowserElement(),
+       aLoadContextInfo->AppId()));
+
+  nsresult rv;
+
+  MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+  MOZ_ASSERT(!aLoadContextInfo->IsPrivate());
+  if (aLoadContextInfo->IsPrivate()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  if (!mCacheDirectory) {
+    return NS_ERROR_FILE_INVALID_PATH;
+  }
+
+  if (mShuttingDown) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  if (!mTreeCreated) {
+    rv = CreateCacheTree();
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  }
+
+  // Doom all active handles that matches the load context
+  nsTArray<nsRefPtr<CacheFileHandle> > handles;
+  mHandles.GetActiveHandles(&handles);
+
+  for (uint32_t i = 0; i < handles.Length(); ++i) {
+    bool equals;
+    rv = CacheFileUtils::KeyMatchesLoadContextInfo(handles[i]->Key(),
+                                                   aLoadContextInfo,
+                                                   &equals);
+    if (NS_FAILED(rv)) {
+      LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot parse key in "
+           "handle! [handle=%p, key=%s]", handles[i].get(),
+           handles[i]->Key().get()));
+      MOZ_CRASH("Unexpected error!");
+    }
+
+    if (equals) {
+      rv = DoomFileInternal(handles[i]);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot doom handle"
+             " [handle=%p]", handles[i].get()));
+      }
+    }
+  }
+
+  if (!mContextEvictor) {
+    mContextEvictor = new CacheFileContextEvictor();
+    mContextEvictor->Init(mCacheDirectory);
+  }
+
+  mContextEvictor->AddContext(aLoadContextInfo);
+
+  return NS_OK;
+}
+
+// static
+nsresult
+CacheFileIOManager::CacheIndexStateChanged()
+{
+  LOG(("CacheFileIOManager::CacheIndexStateChanged()"));
+
+  nsresult rv;
+
+  // CacheFileIOManager lives longer than CacheIndex so gInstance must be
+  // non-null here.
+  MOZ_ASSERT(gInstance);
+
+  // We have to re-distatch even if we are on IO thread to prevent reentering
+  // the lock in CacheIndex
+  nsCOMPtr<nsIRunnable> ev;
+  ev = NS_NewRunnableMethod(
+    gInstance, &CacheFileIOManager::CacheIndexStateChangedInternal);
+
+  nsCOMPtr<nsIEventTarget> ioTarget = IOTarget();
+  MOZ_ASSERT(ioTarget);
+
+  rv = ioTarget->Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+CacheFileIOManager::CacheIndexStateChangedInternal()
+{
+  if (mShuttingDown) {
+    // ignore notification during shutdown
+    return NS_OK;
+  }
+
+  if (!mContextEvictor) {
+    return NS_OK;
+  }
+
+  mContextEvictor->CacheIndexStateChanged();
+  return NS_OK;
+}
+
 nsresult
 CacheFileIOManager::TrashDirectory(nsIFile *aFile)
 {
 #ifdef PR_LOGGING
   nsAutoCString path;
   aFile->GetNativePath(path);
 #endif
   LOG(("CacheFileIOManager::TrashDirectory() [file=%s]", path.get()));
@@ -3226,16 +3384,28 @@ CacheFileIOManager::CreateCacheTree()
   NS_ENSURE_SUCCESS(rv, rv);
 
   // ensure doomed directory exists
   rv = CheckAndCreateDir(mCacheDirectory, kDoomedDir, true);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mTreeCreated = true;
 
+  if (!mContextEvictor) {
+    nsRefPtr<CacheFileContextEvictor> contextEvictor;
+    contextEvictor = new CacheFileContextEvictor();
+
+    // Init() method will try to load unfinished contexts from the disk. Store
+    // the evictor as a member only when there is some unfinished job.
+    contextEvictor->Init(mCacheDirectory);
+    if (contextEvictor->ContextsCount()) {
+      contextEvictor.swap(mContextEvictor);
+    }
+  }
+
   StartRemovingTrash();
 
   return NS_OK;
 }
 
 nsresult
 CacheFileIOManager::OpenNSPRHandle(CacheFileHandle *aHandle, bool aCreate)
 {
--- a/netwerk/cache2/CacheFileIOManager.h
+++ b/netwerk/cache2/CacheFileIOManager.h
@@ -16,16 +16,17 @@
 #include "nsTHashtable.h"
 #include "prio.h"
 
 //#define DEBUG_HANDLES 1
 
 class nsIFile;
 class nsITimer;
 class nsIDirectoryEnumerator;
+class nsILoadContextInfo;
 
 namespace mozilla {
 namespace net {
 
 class CacheFile;
 #ifdef DEBUG_HANDLES
 class CacheFileHandlesEntry;
 #endif
@@ -169,16 +170,17 @@ private:
 
 ////////////////////////////////////////////////////////////////////////////////
 
 class OpenFileEvent;
 class CloseFileEvent;
 class ReadEvent;
 class WriteEvent;
 class MetadataWriteScheduleEvent;
+class CacheFileContextEvictor;
 
 #define CACHEFILEIOLISTENER_IID \
 { /* dcaf2ddc-17cf-4242-bca1-8c86936375a5 */       \
   0xdcaf2ddc,                                      \
   0x17cf,                                          \
   0x4242,                                          \
   {0xbc, 0xa1, 0x8c, 0x86, 0x93, 0x63, 0x75, 0xa5} \
 }
@@ -252,16 +254,17 @@ public:
   static nsresult TruncateSeekSetEOF(CacheFileHandle *aHandle,
                                      int64_t aTruncatePos, int64_t aEOFPos,
                                      CacheFileIOListener *aCallback);
   static nsresult RenameFile(CacheFileHandle *aHandle,
                              const nsACString &aNewName,
                              CacheFileIOListener *aCallback);
   static nsresult EvictIfOverLimit();
   static nsresult EvictAll();
+  static nsresult EvictByContext(nsILoadContextInfo *aLoadContextInfo);
 
   static nsresult InitIndexEntry(CacheFileHandle *aHandle,
                                  uint32_t         aAppId,
                                  bool             aAnonymous,
                                  bool             aInBrowser);
   static nsresult UpdateIndexEntry(CacheFileHandle *aHandle,
                                    const uint32_t  *aFrecency,
                                    const uint32_t  *aExpirationTime);
@@ -290,23 +293,25 @@ private:
   friend class WriteEvent;
   friend class DoomFileEvent;
   friend class DoomFileByKeyEvent;
   friend class ReleaseNSPRHandleEvent;
   friend class TruncateSeekSetEOFEvent;
   friend class RenameFileEvent;
   friend class CacheIndex;
   friend class MetadataWriteScheduleEvent;
+  friend class CacheFileContextEvictor;
 
   virtual ~CacheFileIOManager();
 
   nsresult InitInternal();
   nsresult ShutdownInternal();
 
   nsresult OpenFileInternal(const SHA1Sum::Hash *aHash,
+                            const nsACString &aKey,
                             uint32_t aFlags,
                             CacheFileHandle **_retval);
   nsresult OpenSpecialFileInternal(const nsACString &aKey,
                                    uint32_t aFlags,
                                    CacheFileHandle **_retval);
   nsresult CloseHandleInternal(CacheFileHandle *aHandle);
   nsresult ReadInternal(CacheFileHandle *aHandle, int64_t aOffset,
                         char *aBuf, int32_t aCount);
@@ -317,16 +322,17 @@ private:
   nsresult ReleaseNSPRHandleInternal(CacheFileHandle *aHandle);
   nsresult TruncateSeekSetEOFInternal(CacheFileHandle *aHandle,
                                       int64_t aTruncatePos, int64_t aEOFPos);
   nsresult RenameFileInternal(CacheFileHandle *aHandle,
                               const nsACString &aNewName);
   nsresult EvictIfOverLimitInternal();
   nsresult OverLimitEvictionInternal();
   nsresult EvictAllInternal();
+  nsresult EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo);
 
   nsresult TrashDirectory(nsIFile *aFile);
   static void OnTrashTimer(nsITimer *aTimer, void *aClosure);
   nsresult StartRemovingTrash();
   nsresult RemoveTrashInternal();
   nsresult FindTrashDirToRemove();
 
   nsresult CreateFile(CacheFileHandle *aHandle);
@@ -345,16 +351,19 @@ private:
   // Removing all cache files during shutdown
   nsresult SyncRemoveDir(nsIFile *aFile, const char *aDir);
   void     SyncRemoveAllCacheFiles();
 
   nsresult ScheduleMetadataWriteInternal(CacheFile * aFile);
   nsresult UnscheduleMetadataWriteInternal(CacheFile * aFile);
   nsresult ShutdownMetadataWriteSchedulingInternal();
 
+  static nsresult CacheIndexStateChanged();
+  nsresult CacheIndexStateChangedInternal();
+
   // Memory reporting (private part)
   size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;
 
   static CacheFileIOManager           *gInstance;
   TimeStamp                            mStartTime;
   bool                                 mShuttingDown;
   nsRefPtr<CacheIOThread>              mIOThread;
   nsCOMPtr<nsIFile>                    mCacheDirectory;
@@ -365,14 +374,15 @@ private:
   nsTArray<nsRefPtr<CacheFile> >       mScheduledMetadataWrites;
   nsCOMPtr<nsITimer>                   mMetadataWritesTimer;
   bool                                 mOverLimitEvicting;
   bool                                 mRemovingTrashDirs;
   nsCOMPtr<nsITimer>                   mTrashTimer;
   nsCOMPtr<nsIFile>                    mTrashDir;
   nsCOMPtr<nsIDirectoryEnumerator>     mTrashDirEnumerator;
   nsTArray<nsCString>                  mFailedTrashDirs;
+  nsRefPtr<CacheFileContextEvictor>    mContextEvictor;
 };
 
 } // net
 } // mozilla
 
 #endif
--- a/netwerk/cache2/CacheFileUtils.cpp
+++ b/netwerk/cache2/CacheFileUtils.cpp
@@ -266,11 +266,25 @@ AppendTagWithValue(nsACString & aTarget,
         NS_LITERAL_CSTRING(","), NS_LITERAL_CSTRING(",,"));
       aTarget.Append(escapedValue);
     }
   }
 
   aTarget.Append(',');
 }
 
+nsresult
+KeyMatchesLoadContextInfo(const nsACString &aKey, nsILoadContextInfo *aInfo,
+                          bool *_retval)
+{
+  nsCOMPtr<nsILoadContextInfo> info = ParseKey(aKey);
+
+  if (!info) {
+    return NS_ERROR_FAILURE;
+  }
+
+  *_retval = info->Equals(aInfo);
+  return NS_OK;
+}
+
 } // CacheFileUtils
 } // net
 } // mozilla
--- a/netwerk/cache2/CacheFileUtils.h
+++ b/netwerk/cache2/CacheFileUtils.h
@@ -22,13 +22,18 @@ ParseKey(const nsCSubstring &aKey,
          nsCSubstring *aURISpec = nullptr);
 
 void
 AppendKeyPrefix(nsILoadContextInfo *aInfo, nsACString &_retval);
 
 void
 AppendTagWithValue(nsACString & aTarget, char const aTag, nsCSubstring const & aValue);
 
+nsresult
+KeyMatchesLoadContextInfo(const nsACString &aKey,
+                          nsILoadContextInfo *aInfo,
+                          bool *_retval);
+
 } // CacheFileUtils
 } // net
 } // mozilla
 
 #endif
--- a/netwerk/cache2/CacheIndex.cpp
+++ b/netwerk/cache2/CacheIndex.cpp
@@ -2,16 +2,18 @@
  * 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 "CacheIndex.h"
 
 #include "CacheLog.h"
 #include "CacheFileIOManager.h"
 #include "CacheFileMetadata.h"
+#include "CacheIndexIterator.h"
+#include "CacheIndexContextIterator.h"
 #include "nsThreadUtils.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIDirectoryEnumerator.h"
 #include "nsISizeOf.h"
 #include "nsPrintfCString.h"
 #include "mozilla/DebugOnly.h"
 #include "prinrval.h"
 #include "nsIFile.h"
@@ -68,34 +70,36 @@ public:
     mIndex->mIndexStats.AfterChange(entry);
     if (!entry || !entry->IsInitialized() || entry->IsRemoved()) {
       entry = nullptr;
     }
 
     if (entry && !mOldRecord) {
       mIndex->InsertRecordToFrecencyArray(entry->mRec);
       mIndex->InsertRecordToExpirationArray(entry->mRec);
+      mIndex->AddRecordToIterators(entry->mRec);
     } else if (!entry && mOldRecord) {
       mIndex->RemoveRecordFromFrecencyArray(mOldRecord);
       mIndex->RemoveRecordFromExpirationArray(mOldRecord);
+      mIndex->RemoveRecordFromIterators(mOldRecord);
     } else if (entry && mOldRecord) {
       bool replaceFrecency = false;
       bool replaceExpiration = false;
 
       if (entry->mRec != mOldRecord) {
         // record has a different address, we have to replace it
         replaceFrecency = replaceExpiration = true;
+        mIndex->ReplaceRecordInIterators(mOldRecord, entry->mRec);
       } else {
         if (entry->mRec->mFrecency == 0 &&
             entry->mRec->mExpirationTime == nsICacheEntry::NO_EXPIRATION_TIME) {
           // This is a special case when we want to make sure that the entry is
           // placed at the end of the lists even when the values didn't change.
           replaceFrecency = replaceExpiration = true;
-        }
-        else {
+        } else {
           if (entry->mRec->mFrecency != mOldFrecency) {
             replaceFrecency = true;
           }
           if (entry->mRec->mExpirationTime != mOldExpirationTime) {
             replaceExpiration = true;
           }
         }
       }
@@ -152,80 +156,16 @@ private:
   nsRefPtr<CacheIndex> mIndex;
   CacheIndexRecord    *mOldRecord;
   uint32_t             mOldFrecency;
   uint32_t             mOldExpirationTime;
   bool                 mDoNotSearchInIndex;
   bool                 mDoNotSearchInUpdates;
 };
 
-class CacheIndexAutoLock {
-public:
-  CacheIndexAutoLock(CacheIndex *aIndex)
-    : mIndex(aIndex)
-    , mLocked(true)
-  {
-    mIndex->Lock();
-  }
-  ~CacheIndexAutoLock()
-  {
-    if (mLocked) {
-      mIndex->Unlock();
-    }
-  }
-  void Lock()
-  {
-    MOZ_ASSERT(!mLocked);
-    mIndex->Lock();
-    mLocked = true;
-  }
-  void Unlock()
-  {
-    MOZ_ASSERT(mLocked);
-    mIndex->Unlock();
-    mLocked = false;
-  }
-
-private:
-  nsRefPtr<CacheIndex> mIndex;
-  bool mLocked;
-};
-
-class CacheIndexAutoUnlock {
-public:
-  CacheIndexAutoUnlock(CacheIndex *aIndex)
-    : mIndex(aIndex)
-    , mLocked(false)
-  {
-    mIndex->Unlock();
-  }
-  ~CacheIndexAutoUnlock()
-  {
-    if (!mLocked) {
-      mIndex->Lock();
-    }
-  }
-  void Lock()
-  {
-    MOZ_ASSERT(!mLocked);
-    mIndex->Lock();
-    mLocked = true;
-  }
-  void Unlock()
-  {
-    MOZ_ASSERT(mLocked);
-    mIndex->Unlock();
-    mLocked = false;
-  }
-
-private:
-  nsRefPtr<CacheIndex> mIndex;
-  bool mLocked;
-};
-
 class FileOpenHelper : public CacheFileIOListener
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   FileOpenHelper(CacheIndex* aIndex)
     : mIndex(aIndex)
     , mCanceled(false)
@@ -325,25 +265,25 @@ CacheIndex::CacheIndex()
 CacheIndex::~CacheIndex()
 {
   LOG(("CacheIndex::~CacheIndex [this=%p]", this));
   MOZ_COUNT_DTOR(CacheIndex);
 
   ReleaseBuffer();
 }
 
-inline void
+void
 CacheIndex::Lock()
 {
   mLock.Lock();
 
   MOZ_ASSERT(!mIndexStats.StateLogged());
 }
 
-inline void
+void
 CacheIndex::Unlock()
 {
   MOZ_ASSERT(!mIndexStats.StateLogged());
 
   mLock.Unlock();
 }
 
 inline void
@@ -406,16 +346,28 @@ CacheIndex::PreShutdown()
   }
 
   CacheIndexAutoLock lock(index);
 
   LOG(("CacheIndex::PreShutdown() - [state=%d, indexOnDiskIsValid=%d, "
        "dontMarkIndexClean=%d]", index->mState, index->mIndexOnDiskIsValid,
        index->mDontMarkIndexClean));
 
+  LOG(("CacheIndex::PreShutdown() - Closing iterators."));
+  for (uint32_t i = 0; i < index->mIterators.Length(); ) {
+    rv = index->mIterators[i]->CloseInternal(NS_ERROR_FAILURE);
+    if (NS_FAILED(rv)) {
+      // CacheIndexIterator::CloseInternal() removes itself from mIteratos iff
+      // it returns success.
+      LOG(("CacheIndex::PreShutdown() - Failed to remove iterator %p. "
+           "[rv=0x%08x]", rv));
+      i++;
+    }
+  }
+
   index->mShuttingDown = true;
 
   if (index->mState == READY) {
     return NS_OK; // nothing to do
   }
 
   nsCOMPtr<nsIRunnable> event;
   event = NS_NewRunnableMethod(index, &CacheIndex::PreShutdownInternal);
@@ -1286,16 +1238,74 @@ CacheIndex::AsyncGetDiskConsumption(nsIC
 
   LOG(("CacheIndex::AsyncGetDiskConsumption - remembering callback"));
   // Will be called when the index get to the READY state.
   index->mDiskConsumptionObservers.AppendElement(observer);
 
   return NS_OK;
 }
 
+// static
+nsresult
+CacheIndex::GetIterator(nsILoadContextInfo *aInfo, bool aAddNew,
+                        CacheIndexIterator **_retval)
+{
+  LOG(("CacheIndex::GetIterator() [info=%p, addNew=%d]", aInfo, aAddNew));
+
+  nsRefPtr<CacheIndex> index = gInstance;
+
+  if (!index) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  CacheIndexAutoLock lock(index);
+
+  if (!index->IsIndexUsable()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsRefPtr<CacheIndexIterator> iter;
+  if (aInfo) {
+    iter = new CacheIndexContextIterator(index, aAddNew, aInfo);
+  } else {
+    iter = new CacheIndexIterator(index, aAddNew);
+  }
+
+  iter->AddRecords(index->mFrecencyArray);
+
+  index->mIterators.AppendElement(iter);
+  iter.swap(*_retval);
+  return NS_OK;
+}
+
+// static
+nsresult
+CacheIndex::IsUpToDate(bool *_retval)
+{
+  LOG(("CacheIndex::IsUpToDate()"));
+
+  nsRefPtr<CacheIndex> index = gInstance;
+
+  if (!index) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  CacheIndexAutoLock lock(index);
+
+  if (!index->IsIndexUsable()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  *_retval = (index->mState == READY || index->mState == WRITING) &&
+             !index->mIndexNeedsUpdate && !index->mShuttingDown;
+
+  LOG(("CacheIndex::IsUpToDate() - returning %p", *_retval));
+  return NS_OK;
+}
+
 bool
 CacheIndex::IsIndexUsable()
 {
   MOZ_ASSERT(mState != INITIAL);
 
   switch (mState) {
     case INITIAL:
     case SHUTDOWN:
@@ -3007,16 +3017,20 @@ CacheIndex::ChangeState(EState aNewState
   // entries.
   if (!mShuttingDown && !mRemovingAll && aNewState != SHUTDOWN &&
       (mState == READING || mState == BUILDING || mState == UPDATING))  {
     CacheFileIOManager::EvictIfOverLimit();
   }
 
   mState = aNewState;
 
+  if (mState != SHUTDOWN) {
+    CacheFileIOManager::CacheIndexStateChanged();
+  }
+
   if (mState == READY && mDiskConsumptionObservers.Length()) {
     for (uint32_t i = 0; i < mDiskConsumptionObservers.Length(); ++i) {
       DiskConsumptionObserver* o = mDiskConsumptionObservers[i];
       // Safe to call under the lock.  We always post to the main thread.
       o->OnDiskConsumption(mIndexStats.Size() << 10);
     }
 
     mDiskConsumptionObservers.Clear();
@@ -3125,16 +3139,58 @@ CacheIndex::RemoveRecordFromExpirationAr
 {
   LOG(("CacheIndex::RemoveRecordFromExpirationArray() [record=%p]", aRecord));
 
   DebugOnly<bool> removed;
   removed = mExpirationArray.RemoveElement(aRecord);
   MOZ_ASSERT(removed);
 }
 
+void
+CacheIndex::AddRecordToIterators(CacheIndexRecord *aRecord)
+{
+  AssertOwnsLock();
+
+  for (uint32_t i = 0; i < mIterators.Length(); ++i) {
+    // Add a new record only when iterator is supposed to be updated.
+    if (mIterators[i]->ShouldBeNewAdded()) {
+      mIterators[i]->AddRecord(aRecord);
+    }
+  }
+}
+
+void
+CacheIndex::RemoveRecordFromIterators(CacheIndexRecord *aRecord)
+{
+  AssertOwnsLock();
+
+  for (uint32_t i = 0; i < mIterators.Length(); ++i) {
+    // Remove the record from iterator always, it makes no sence to return
+    // non-existing entries. Also the pointer to the record is no longer valid
+    // once the entry is removed from index.
+    mIterators[i]->RemoveRecord(aRecord);
+  }
+}
+
+void
+CacheIndex::ReplaceRecordInIterators(CacheIndexRecord *aOldRecord,
+                                     CacheIndexRecord *aNewRecord)
+{
+  AssertOwnsLock();
+
+  for (uint32_t i = 0; i < mIterators.Length(); ++i) {
+    // We have to replace the record always since the pointer is no longer
+    // valid after this point. NOTE: Replacing the record doesn't mean that
+    // a new entry was added, it just means that the data in the entry was
+    // changed (e.g. a file size) and we had to track this change in
+    // mPendingUpdates since mIndex was read-only.
+    mIterators[i]->ReplaceRecord(aOldRecord, aNewRecord);
+  }
+}
+
 nsresult
 CacheIndex::Run()
 {
   LOG(("CacheIndex::Run()"));
 
   CacheIndexAutoLock lock(this);
 
   if (!IsIndexUsable()) {
--- a/netwerk/cache2/CacheIndex.h
+++ b/netwerk/cache2/CacheIndex.h
@@ -29,16 +29,17 @@ class nsITimer;
 #define DEBUG_STATS 1
 #endif
 
 namespace mozilla {
 namespace net {
 
 class CacheFileMetadata;
 class FileOpenHelper;
+class CacheIndexIterator;
 
 typedef struct {
   // Version of the index. The index must be ignored and deleted when the file
   // on disk was written with a newer version.
   uint32_t mVersion;
 
   // Timestamp of time when the last successful write of the index started.
   // During update process we use this timestamp for a quick validation of entry
@@ -245,16 +246,29 @@ public:
     LOG(("CacheIndexEntry::Log() [this=%p, hash=%08x%08x%08x%08x%08x, fresh=%u,"
          " initialized=%u, removed=%u, dirty=%u, anonymous=%u, inBrowser=%u, "
          "appId=%u, frecency=%u, expirationTime=%u, size=%u]",
          this, LOGSHA1(mRec->mHash), IsFresh(), IsInitialized(), IsRemoved(),
          IsDirty(), Anonymous(), InBrowser(), AppId(), GetFrecency(),
          GetExpirationTime(), GetFileSize()));
   }
 
+  static bool RecordMatchesLoadContextInfo(CacheIndexRecord *aRec,
+                                           nsILoadContextInfo *aInfo)
+  {
+    if (!aInfo->IsPrivate() &&
+        aInfo->AppId() == aRec->mAppId &&
+        aInfo->IsAnonymous() == !!(aRec->mFlags & kAnonymousMask) &&
+        aInfo->IsInBrowserElement() == !!(aRec->mFlags & kInBrowserMask)) {
+      return true;
+    }
+
+    return false;
+  }
+
   // Memory reporting
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
   {
     return mallocSizeOf(mRec.get());
   }
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
   {
@@ -547,25 +561,38 @@ public:
   static nsresult GetEntryForEviction(SHA1Sum::Hash *aHash, uint32_t *aCnt);
 
   // Returns cache size in kB.
   static nsresult GetCacheSize(uint32_t *_retval);
 
   // 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.
+  static nsresult GetIterator(nsILoadContextInfo *aInfo, bool aAddNew,
+                              CacheIndexIterator **_retval);
+
+  // Returns true if we _think_ that the index is up to date. I.e. the state is
+  // READY or WRITING and mIndexNeedsUpdate as well as mShuttingDown is false.
+  static nsresult IsUpToDate(bool *_retval);
+
   // Memory reporting
   static size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
   static size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
 private:
   friend class CacheIndexEntryAutoManage;
   friend class CacheIndexAutoLock;
   friend class CacheIndexAutoUnlock;
   friend class FileOpenHelper;
+  friend class CacheIndexIterator;
 
   virtual ~CacheIndex();
 
   NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult);
   nsresult   OnFileOpenedInternal(FileOpenHelper *aOpener,
                                   CacheFileHandle *aHandle, nsresult aResult);
   NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
                            nsresult aResult);
@@ -794,16 +821,22 @@ private:
   void ReleaseBuffer();
 
   // Methods used by CacheIndexEntryAutoManage to keep the arrays up to date.
   void InsertRecordToFrecencyArray(CacheIndexRecord *aRecord);
   void InsertRecordToExpirationArray(CacheIndexRecord *aRecord);
   void RemoveRecordFromFrecencyArray(CacheIndexRecord *aRecord);
   void RemoveRecordFromExpirationArray(CacheIndexRecord *aRecord);
 
+  // Methods used by CacheIndexEntryAutoManage to keep the iterators up to date.
+  void AddRecordToIterators(CacheIndexRecord *aRecord);
+  void RemoveRecordFromIterators(CacheIndexRecord *aRecord);
+  void ReplaceRecordInIterators(CacheIndexRecord *aOldRecord,
+                                CacheIndexRecord *aNewRecord);
+
   // Memory reporting (private part)
   size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;
 
   static CacheIndex *gInstance;
 
   nsCOMPtr<nsIFile> mCacheDirectory;
 
   mozilla::Mutex mLock;
@@ -897,16 +930,18 @@ private:
   // for an entry to evict, we first try to find an expired entry. If there is
   // no expired entry, we take the entry with lowest valid frecency. Zero
   // frecency is an initial value and such entries are stored at the end of the
   // array. Uninitialized entries and entries marked as deleted are not present
   // in these arrays.
   nsTArray<CacheIndexRecord *>  mFrecencyArray;
   nsTArray<CacheIndexRecord *>  mExpirationArray;
 
+  nsTArray<CacheIndexIterator *> mIterators;
+
   class DiskConsumptionObserver : public nsRunnable
   {
   public:
     static DiskConsumptionObserver* Init(nsICacheStorageConsumptionObserver* aObserver)
     {
       nsWeakPtr observer = do_GetWeakReference(aObserver);
       if (!observer)
         return nullptr;
@@ -942,13 +977,76 @@ private:
     nsWeakPtr mObserver;
     int64_t mSize;
   };
 
   // List of async observers that want to get disk consumption information
   nsTArray<nsRefPtr<DiskConsumptionObserver> > mDiskConsumptionObservers;
 };
 
+class CacheIndexAutoLock {
+public:
+  CacheIndexAutoLock(CacheIndex *aIndex)
+    : mIndex(aIndex)
+    , mLocked(true)
+  {
+    mIndex->Lock();
+  }
+  ~CacheIndexAutoLock()
+  {
+    if (mLocked) {
+      mIndex->Unlock();
+    }
+  }
+  void Lock()
+  {
+    MOZ_ASSERT(!mLocked);
+    mIndex->Lock();
+    mLocked = true;
+  }
+  void Unlock()
+  {
+    MOZ_ASSERT(mLocked);
+    mIndex->Unlock();
+    mLocked = false;
+  }
+
+private:
+  nsRefPtr<CacheIndex> mIndex;
+  bool mLocked;
+};
+
+class CacheIndexAutoUnlock {
+public:
+  CacheIndexAutoUnlock(CacheIndex *aIndex)
+    : mIndex(aIndex)
+    , mLocked(false)
+  {
+    mIndex->Unlock();
+  }
+  ~CacheIndexAutoUnlock()
+  {
+    if (!mLocked) {
+      mIndex->Lock();
+    }
+  }
+  void Lock()
+  {
+    MOZ_ASSERT(!mLocked);
+    mIndex->Lock();
+    mLocked = true;
+  }
+  void Unlock()
+  {
+    MOZ_ASSERT(mLocked);
+    mIndex->Unlock();
+    mLocked = false;
+  }
+
+private:
+  nsRefPtr<CacheIndex> mIndex;
+  bool mLocked;
+};
 
 } // net
 } // mozilla
 
 #endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheIndexContextIterator.cpp
@@ -0,0 +1,45 @@
+/* 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 "CacheLog.h"
+#include "CacheIndexContextIterator.h"
+#include "CacheIndex.h"
+#include "nsString.h"
+
+
+namespace mozilla {
+namespace net {
+
+CacheIndexContextIterator::CacheIndexContextIterator(CacheIndex *aIndex,
+                                                     bool aAddNew,
+                                                     nsILoadContextInfo *aInfo)
+  : CacheIndexIterator(aIndex, aAddNew)
+  , mInfo(aInfo)
+{
+}
+
+CacheIndexContextIterator::~CacheIndexContextIterator()
+{
+}
+
+void
+CacheIndexContextIterator::AddRecord(CacheIndexRecord *aRecord)
+{
+  if (CacheIndexEntry::RecordMatchesLoadContextInfo(aRecord, mInfo)) {
+    CacheIndexIterator::AddRecord(aRecord);
+  }
+}
+
+void
+CacheIndexContextIterator::AddRecords(
+  const nsTArray<CacheIndexRecord *> &aRecords)
+{
+  // We need to add one by one so that those with wrong context are ignored.
+  for (uint32_t i = 0; i < aRecords.Length(); ++i) {
+    AddRecord(aRecords[i]);
+  }
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheIndexContextIterator.h
@@ -0,0 +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/. */
+
+#ifndef CacheIndexContextIterator__h__
+#define CacheIndexContextIterator__h__
+
+#include "CacheIndexIterator.h"
+
+class nsILoadContextInfo;
+
+namespace mozilla {
+namespace net {
+
+class CacheIndexContextIterator : public CacheIndexIterator
+{
+public:
+  CacheIndexContextIterator(CacheIndex *aIndex, bool aAddNew,
+                            nsILoadContextInfo *aInfo);
+  virtual ~CacheIndexContextIterator();
+
+private:
+  virtual void AddRecord(CacheIndexRecord *aRecord);
+  virtual void AddRecords(const nsTArray<CacheIndexRecord *> &aRecords);
+
+  nsCOMPtr<nsILoadContextInfo> mInfo;
+};
+
+} // net
+} // mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheIndexIterator.cpp
@@ -0,0 +1,126 @@
+/* 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 "CacheLog.h"
+#include "CacheIndexIterator.h"
+#include "CacheIndex.h"
+#include "nsString.h"
+#include "mozilla/DebugOnly.h"
+
+
+namespace mozilla {
+namespace net {
+
+CacheIndexIterator::CacheIndexIterator(CacheIndex *aIndex, bool aAddNew)
+  : mStatus(NS_OK)
+  , mIndex(aIndex)
+  , mAddNew(aAddNew)
+{
+  LOG(("CacheIndexIterator::CacheIndexIterator() [this=%p]", this));
+}
+
+CacheIndexIterator::~CacheIndexIterator()
+{
+  LOG(("CacheIndexIterator::~CacheIndexIterator() [this=%p]", this));
+
+  Close();
+}
+
+nsresult
+CacheIndexIterator::GetNextHash(SHA1Sum::Hash *aHash)
+{
+  LOG(("CacheIndexIterator::GetNextHash() [this=%p]", this));
+
+  CacheIndexAutoLock lock(mIndex);
+
+  if (NS_FAILED(mStatus)) {
+    return mStatus;
+  }
+
+  if (!mRecords.Length()) {
+    CloseInternal(NS_ERROR_NOT_AVAILABLE);
+    return mStatus;
+  }
+
+  memcpy(aHash, mRecords[mRecords.Length() - 1]->mHash, sizeof(SHA1Sum::Hash));
+  mRecords.RemoveElementAt(mRecords.Length() - 1);
+
+  return NS_OK;
+}
+
+nsresult
+CacheIndexIterator::Close()
+{
+  LOG(("CacheIndexIterator::Close() [this=%p]", this));
+
+  CacheIndexAutoLock lock(mIndex);
+
+  return CloseInternal(NS_ERROR_NOT_AVAILABLE);
+}
+
+nsresult
+CacheIndexIterator::CloseInternal(nsresult aStatus)
+{
+  LOG(("CacheIndexIterator::CloseInternal() [this=%p, status=0x%08x]", this,
+       aStatus));
+
+  // Make sure status will be a failure
+  MOZ_ASSERT(NS_FAILED(aStatus));
+  if (NS_SUCCEEDED(aStatus)) {
+    aStatus = NS_ERROR_UNEXPECTED;
+  }
+
+  if (NS_FAILED(mStatus)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  DebugOnly<bool> removed = mIndex->mIterators.RemoveElement(this);
+  MOZ_ASSERT(removed);
+  mStatus = aStatus;
+
+  return NS_OK;
+}
+
+void
+CacheIndexIterator::AddRecord(CacheIndexRecord *aRecord)
+{
+  LOG(("CacheIndexIterator::AddRecord() [this=%p, record=%p]", this, aRecord));
+
+  mRecords.AppendElement(aRecord);
+}
+
+void
+CacheIndexIterator::AddRecords(const nsTArray<CacheIndexRecord *> &aRecords)
+{
+  LOG(("CacheIndexIterator::AddRecords() [this=%p]", this));
+
+  mRecords.AppendElements(aRecords);
+}
+
+bool
+CacheIndexIterator::RemoveRecord(CacheIndexRecord *aRecord)
+{
+  LOG(("CacheIndexIterator::RemoveRecord() [this=%p, record=%p]", this,
+       aRecord));
+
+  return mRecords.RemoveElement(aRecord);
+}
+
+bool
+CacheIndexIterator::ReplaceRecord(CacheIndexRecord *aOldRecord,
+                                  CacheIndexRecord *aNewRecord)
+{
+  LOG(("CacheIndexIterator::ReplaceRecord() [this=%p, oldRecord=%p, "
+       "newRecord=%p]", this, aOldRecord, aNewRecord));
+
+  if (RemoveRecord(aOldRecord)) {
+    AddRecord(aNewRecord);
+    return true;
+  }
+
+  return false;
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/CacheIndexIterator.h
@@ -0,0 +1,57 @@
+/* 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 CacheIndexIterator__h__
+#define CacheIndexIterator__h__
+
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "mozilla/SHA1.h"
+
+namespace mozilla {
+namespace net {
+
+class CacheIndex;
+struct CacheIndexRecord;
+
+class CacheIndexIterator
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheIndexIterator)
+
+  CacheIndexIterator(CacheIndex *aIndex, bool aAddNew);
+  virtual ~CacheIndexIterator();
+
+  // Returns a hash of a next entry. If there is no entry NS_ERROR_NOT_AVAILABLE
+  // is returned and the iterator is closed. Other error is returned when the
+  // iterator is closed for other reason, e.g. shutdown.
+  nsresult GetNextHash(SHA1Sum::Hash *aHash);
+
+  // Closes the iterator. This means the iterator is removed from the list of
+  // iterators in CacheIndex.
+  nsresult Close();
+
+protected:
+  friend class CacheIndex;
+
+  nsresult CloseInternal(nsresult aStatus);
+
+  bool ShouldBeNewAdded() { return mAddNew; }
+  virtual void AddRecord(CacheIndexRecord *aRecord);
+  virtual void AddRecords(const nsTArray<CacheIndexRecord *> &aRecords);
+  bool RemoveRecord(CacheIndexRecord *aRecord);
+  bool ReplaceRecord(CacheIndexRecord *aOldRecord,
+                     CacheIndexRecord *aNewRecord);
+
+  nsresult                     mStatus;
+  nsRefPtr<CacheIndex>         mIndex;
+  nsTArray<CacheIndexRecord *> mRecords;
+  bool                         mAddNew;
+};
+
+} // net
+} // mozilla
+
+#endif
--- a/netwerk/cache2/CacheStorageService.cpp
+++ b/netwerk/cache2/CacheStorageService.cpp
@@ -158,107 +158,16 @@ void CacheStorageService::ShutdownBackgr
   Pool(true).mFrecencyArray.Clear();
   Pool(true).mExpirationArray.Clear();
 }
 
 // Internal management methods
 
 namespace { // anon
 
-// EvictionRunnable
-// Responsible for purgin and unregistring entries (loaded) in memory
-
-class EvictionRunnable : public nsRunnable
-{
-public:
-  EvictionRunnable(nsCSubstring const & aContextKey, TCacheEntryTable* aEntries,
-                   bool aUsingDisk,
-                   nsICacheEntryDoomCallback* aCallback)
-    : mContextKey(aContextKey)
-    , mEntries(aEntries)
-    , mCallback(aCallback)
-    , mUsingDisk(aUsingDisk) { }
-
-  NS_IMETHOD Run()
-  {
-    LOG(("EvictionRunnable::Run [this=%p, disk=%d]", this, mUsingDisk));
-    if (CacheStorageService::IsOnManagementThread()) {
-      if (mUsingDisk) {
-        // TODO for non private entries:
-        // - rename/move all files to TRASH, block shutdown
-        // - start the TRASH removal process
-        // - may also be invoked from the main thread...
-      }
-
-      if (mEntries) {
-        // Process only a limited number of entries during a single loop to
-        // prevent block of the management thread.
-        mBatch = 50;
-        mEntries->Enumerate(&EvictionRunnable::EvictEntry, this);
-      }
-
-      // Anything left?  Process in a separate invokation.
-      if (mEntries && mEntries->Count())
-        NS_DispatchToCurrentThread(this);
-      else if (mCallback)
-        NS_DispatchToMainThread(this); // TODO - we may want caller thread
-    }
-    else if (NS_IsMainThread()) {
-      mCallback->OnCacheEntryDoomed(NS_OK);
-    }
-    else {
-      MOZ_ASSERT(false, "Not main or cache management thread");
-    }
-
-    return NS_OK;
-  }
-
-private:
-  virtual ~EvictionRunnable()
-  {
-    if (mCallback)
-      ProxyReleaseMainThread(mCallback);
-  }
-
-  static PLDHashOperator EvictEntry(const nsACString& aKey,
-                                    nsRefPtr<CacheEntry>& aEntry,
-                                    void* aClosure)
-  {
-    EvictionRunnable* evictor = static_cast<EvictionRunnable*>(aClosure);
-
-    LOG(("  evicting entry=%p", aEntry.get()));
-
-    // HACK ...
-    // in-mem-only should only be Purge(WHOLE)'ed
-    // on-disk may use the same technique I think, disk eviction runs independently
-    if (!evictor->mUsingDisk) {
-      // When evicting memory-only entries we have to remove them from
-      // the master table as well.  PurgeAndDoom() enters the service
-      // management lock.
-      aEntry->PurgeAndDoom();
-    }
-    else {
-      // Disk (+memory-only) entries are already removed from the master
-      // hash table, save locking here!
-      aEntry->DoomAlreadyRemoved();
-    }
-
-    if (!--evictor->mBatch)
-      return PLDHashOperator(PL_DHASH_REMOVE | PL_DHASH_STOP);
-
-    return PL_DHASH_REMOVE;
-  }
-
-  nsCString mContextKey;
-  nsAutoPtr<TCacheEntryTable> mEntries;
-  nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
-  uint32_t mBatch;
-  bool mUsingDisk;
-};
-
 // WalkRunnable
 // Responsible to visit the storage and walk all entries on it asynchronously
 
 class WalkRunnable : public nsRunnable
 {
 public:
   WalkRunnable(nsCSubstring const & aContextKey, bool aVisitEntries,
                bool aUsingDisk,
@@ -397,17 +306,17 @@ void CacheStorageService::DropPrivateBro
 
   if (mShutdown)
     return;
 
   nsTArray<nsCString> keys;
   sGlobalEntryTables->EnumerateRead(&CollectPrivateContexts, &keys);
 
   for (uint32_t i = 0; i < keys.Length(); ++i)
-    DoomStorageEntries(keys[i], true, nullptr);
+    DoomStorageEntries(keys[i], nullptr, true, nullptr);
 }
 
 // static
 void CacheStorageService::WipeCacheDirectory(uint32_t aVersion)
 {
   nsCOMPtr<nsIFile> cacheDir;
   switch (aVersion) {
   case 0:
@@ -531,17 +440,17 @@ NS_IMETHODIMP CacheStorageService::Clear
       mozilla::MutexAutoLock lock(mLock);
 
       NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
 
       nsTArray<nsCString> keys;
       sGlobalEntryTables->EnumerateRead(&CollectContexts, &keys);
 
       for (uint32_t i = 0; i < keys.Length(); ++i)
-        DoomStorageEntries(keys[i], true, nullptr);
+        DoomStorageEntries(keys[i], nullptr, true, nullptr);
     }
 
     rv = CacheFileIOManager::EvictAll();
     NS_ENSURE_SUCCESS(rv, rv);
   } else {
     nsCOMPtr<nsICacheService> serv =
         do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -1169,41 +1078,24 @@ CacheStorageService::AddStorageEntry(nsC
 
     bool entryExists = entries->Get(entryKey, getter_AddRefs(entry));
 
     // check whether the file is already doomed
     if (entryExists && entry->IsFileDoomed() && !aReplace) {
       aReplace = true;
     }
 
-    // Check entry that is memory-only is also in related memory-only hashtable.
-    // If not, it has been evicted and we will truncate it ; doom is pending for it,
-    // this consumer just made it sooner then the entry has actually been removed
-    // from the master hash table.
-    // (This can be bypassed when entry is about to be replaced anyway.)
-    if (entryExists && !entry->IsUsingDiskLocked() && !aReplace) {
-      nsAutoCString memoryStorageID(aContextKey);
-      AppendMemoryStorageID(memoryStorageID);
-      CacheEntryTable* memoryEntries;
-      aReplace = sGlobalEntryTables->Get(memoryStorageID, &memoryEntries) &&
-                 memoryEntries->GetWeak(entryKey) != entry;
-
-#ifdef MOZ_LOGGING
-      if (aReplace) {
-        LOG(("  memory-only entry %p for %s already doomed, replacing", entry.get(), entryKey.get()));
-      }
-#endif
-    }
-
     // If truncate is demanded, delete and doom the current entry
     if (entryExists && aReplace) {
       entries->Remove(entryKey);
 
       LOG(("  dooming entry %p for %s because of OPEN_TRUNCATE", entry.get(), entryKey.get()));
       // On purpose called under the lock to prevent races of doom and open on I/O thread
+      // No need to remove from both memory-only and all-entries tables.  The new entry
+      // will overwrite the shadow entry in its ctor.
       entry->DoomAlreadyRemoved();
 
       entry = nullptr;
       entryExists = false;
     }
 
     if (entryExists && entry->SetUsingDisk(aWriteToDisk)) {
       RecordMemoryOnlyEntry(entry, !aWriteToDisk, true /* overwrite */);
@@ -1353,52 +1245,97 @@ CacheStorageService::DoomStorageEntries(
   NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
   NS_ENSURE_ARG(aStorage);
 
   nsAutoCString contextKey;
   CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
 
   mozilla::MutexAutoLock lock(mLock);
 
-  return DoomStorageEntries(contextKey, aStorage->WriteToDisk(), aCallback);
+  return DoomStorageEntries(contextKey, aStorage->LoadInfo(),
+                            aStorage->WriteToDisk(), aCallback);
 }
 
 nsresult
 CacheStorageService::DoomStorageEntries(nsCSubstring const& aContextKey,
+                                        nsILoadContextInfo* aContext,
                                         bool aDiskStorage,
                                         nsICacheEntryDoomCallback* aCallback)
 {
   mLock.AssertCurrentThreadOwns();
 
   NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
 
   nsAutoCString memoryStorageID(aContextKey);
   AppendMemoryStorageID(memoryStorageID);
 
-  nsAutoPtr<CacheEntryTable> entries;
   if (aDiskStorage) {
     LOG(("  dooming disk+memory storage of %s", aContextKey.BeginReading()));
-    // Grab all entries in this storage
-    sGlobalEntryTables->RemoveAndForget(aContextKey, entries);
-    // Just remove the memory-only records table
+
+    // Just remove all entries, CacheFileIOManager will take care of the files.
+    sGlobalEntryTables->Remove(aContextKey);
     sGlobalEntryTables->Remove(memoryStorageID);
-  }
-  else {
+
+    if (aContext && !aContext->IsPrivate()) {
+      LOG(("  dooming disk entries"));
+      CacheFileIOManager::EvictByContext(aContext);
+    }
+  } else {
     LOG(("  dooming memory-only storage of %s", aContextKey.BeginReading()));
-    // Grab the memory-only records table, EvictionRunnable will safely remove
-    // entries one by one from the master hashtable on the background management
-    // thread.  Code at AddStorageEntry ensures a new entry will always replace
-    // memory only entries that EvictionRunnable yet didn't manage to remove.
-    sGlobalEntryTables->RemoveAndForget(memoryStorageID, entries);
+
+    class MemoryEntriesRemoval {
+    public:
+      static PLDHashOperator EvictEntry(const nsACString& aKey,
+                                        CacheEntry* aEntry,
+                                        void* aClosure)
+      {
+        CacheEntryTable* entries = static_cast<CacheEntryTable*>(aClosure);
+        nsCString key(aKey);
+        RemoveExactEntry(entries, key, aEntry, false);
+        return PL_DHASH_NEXT;
+      }
+    };
+
+    // Remove the memory entries table from the global tables.
+    // Since we store memory entries also in the disk entries table
+    // we need to remove the memory entries from the disk table one
+    // by one manually.
+    nsAutoPtr<CacheEntryTable> memoryEntries;
+    sGlobalEntryTables->RemoveAndForget(memoryStorageID, memoryEntries);
+
+    CacheEntryTable* entries;
+    sGlobalEntryTables->Get(aContextKey, &entries);
+    if (memoryEntries && entries)
+      memoryEntries->EnumerateRead(&MemoryEntriesRemoval::EvictEntry, entries);
   }
 
-  nsRefPtr<EvictionRunnable> evict = new EvictionRunnable(
-    aContextKey, entries.forget(), aDiskStorage, aCallback);
+  // An artificial callback.  This is a candidate for removal tho.  In the new
+  // cache any 'doom' or 'evict' function ensures that the entry or entries
+  // being doomed is/are not accessible after the function returns.  So there is
+  // probably no need for a callback - has no meaning.  But for compatibility
+  // with the old cache that is still in the tree we keep the API similar to be
+  // able to make tests as well as other consumers work for now.
+  class Callback : public nsRunnable
+  {
+  public:
+    Callback(nsICacheEntryDoomCallback* aCallback) : mCallback(aCallback) { }
+    NS_IMETHODIMP Run()
+    {
+      mCallback->OnCacheEntryDoomed(NS_OK);
+      return NS_OK;
+    }
+    nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
+  };
 
-  return Dispatch(evict);
+  if (aCallback) {
+    nsRefPtr<nsRunnable> callback = new Callback(aCallback);
+    return NS_DispatchToCurrentThread(callback);
+  }
+
+  return NS_OK;
 }
 
 nsresult
 CacheStorageService::WalkStorageEntries(CacheStorage const* aStorage,
                                         bool aVisitEntries,
                                         nsICacheStorageVisitor* aVisitor)
 {
   LOG(("CacheStorageService::WalkStorageEntries [cb=%p, visitentries=%d]", aVisitor, aVisitEntries));
--- a/netwerk/cache2/CacheStorageService.h
+++ b/netwerk/cache2/CacheStorageService.h
@@ -200,16 +200,17 @@ private:
    * Called on the management thread, removes all expired and then least used
    * entries from the memory, first from the disk pool and then from the memory
    * pool.
    */
   void PurgeOverMemoryLimit();
 
 private:
   nsresult DoomStorageEntries(nsCSubstring const& aContextKey,
+                              nsILoadContextInfo* aContext,
                               bool aDiskStorage,
                               nsICacheEntryDoomCallback* aCallback);
   nsresult AddStorageEntry(nsCSubstring const& aContextKey,
                            nsIURI* aURI,
                            const nsACString & aIdExtension,
                            bool aWriteToDisk,
                            bool aCreateIfNotExist,
                            bool aReplace,
--- a/netwerk/cache2/moz.build
+++ b/netwerk/cache2/moz.build
@@ -29,22 +29,25 @@ UNIFIED_SOURCES += [
 # AppCacheStorage.cpp cannot be built in unified mode because it uses plarena.h.
 # The rest of these files cannot be built in unified mode because they rely oni
 # including prlog.h after nsCache.h.
 SOURCES += [
     'AppCacheStorage.cpp',
     'CacheEntry.cpp',
     'CacheFile.cpp',
     'CacheFileChunk.cpp',
+    'CacheFileContextEvictor.cpp',
     'CacheFileInputStream.cpp',
     'CacheFileIOManager.cpp',
     'CacheFileMetadata.cpp',
     'CacheFileOutputStream.cpp',
     'CacheFileUtils.cpp',
     'CacheIndex.cpp',
+    'CacheIndexContextIterator.cpp',
+    'CacheIndexIterator.cpp',
     'CacheLog.cpp',
     'CacheStorage.cpp',
     'CacheStorageService.cpp',
     'OldWrappers.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '../base/src',
--- a/netwerk/test/unit/test_cache2-12-evict-disk.js
+++ b/netwerk/test/unit/test_cache2-12-evict-disk.js
@@ -43,19 +43,19 @@ function run_test()
         new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
           mc.fired();
         })
       );
     })
   );
 
   asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
-    new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+    new OpenCallback(NEW, "b1m", "b1d", function(entry) {
       asyncOpenCacheEntry("http://b/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
-        new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+        new OpenCallback(NORMAL, "b1m", "b1d", function(entry) {
           mc.fired();
         })
       );
     })
   );
 
   do_test_pending();
 }