Backout bff74cecc67c, ffe0edb2aae7, b60b7c267cef, 6da154b43265, bcf6fd3ab9bb (bug 1182961 parts 1--5) for possible intermittent failures and performance problems.
authorNicholas Nethercote <nnethercote@mozilla.com>
Thu, 06 Aug 2015 16:30:47 -0700
changeset 288371 d7734e7adeb21c316cae38b88b83c411488e0563
parent 288370 11f2aec8187f0463c3f89f5e28698a90d1618acd
child 288372 a01bf3016c0824cc48f301c95507358d3bbdc5eb
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1182961
milestone42.0a1
backs outbff74cecc67cb09b03974e2fa3fd85a9ba873786
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
Backout bff74cecc67c, ffe0edb2aae7, b60b7c267cef, 6da154b43265, bcf6fd3ab9bb (bug 1182961 parts 1--5) for possible intermittent failures and performance problems.
netwerk/cache2/CacheFileIOManager.cpp
netwerk/cache2/CacheIndex.cpp
netwerk/cache2/CacheIndex.h
netwerk/cookie/nsCookieService.cpp
netwerk/cookie/nsCookieService.h
--- a/netwerk/cache2/CacheFileIOManager.cpp
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -422,38 +422,55 @@ CacheFileHandles::RemoveHandle(CacheFile
 
   if (entry->IsEmpty()) {
     LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
          "list is empty, removing entry %p", LOGSHA1(entry->Hash()), entry));
     mTable.RemoveEntry(*entry->Hash());
   }
 }
 
+static PLDHashOperator
+GetAllHandlesEnum(CacheFileHandles::HandleHashKey* aEntry, void *aClosure)
+{
+  nsTArray<nsRefPtr<CacheFileHandle> > *array =
+    static_cast<nsTArray<nsRefPtr<CacheFileHandle> > *>(aClosure);
+
+  aEntry->GetHandles(*array);
+  return PL_DHASH_NEXT;
+}
+
 void
 CacheFileHandles::GetAllHandles(nsTArray<nsRefPtr<CacheFileHandle> > *_retval)
 {
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
-  for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
-    iter.Get()->GetHandles(*_retval);
+  mTable.EnumerateEntries(&GetAllHandlesEnum, _retval);
+}
+
+static PLDHashOperator
+GetActiveHandlesEnum(CacheFileHandles::HandleHashKey* aEntry, void *aClosure)
+{
+  nsTArray<nsRefPtr<CacheFileHandle> > *array =
+    static_cast<nsTArray<nsRefPtr<CacheFileHandle> > *>(aClosure);
+
+  nsRefPtr<CacheFileHandle> handle = aEntry->GetNewestHandle();
+  MOZ_ASSERT(handle);
+
+  if (!handle->IsDoomed()) {
+    array->AppendElement(handle);
   }
+
+  return PL_DHASH_NEXT;
 }
 
 void
 CacheFileHandles::GetActiveHandles(
   nsTArray<nsRefPtr<CacheFileHandle> > *_retval)
 {
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
-  for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
-    nsRefPtr<CacheFileHandle> handle = iter.Get()->GetNewestHandle();
-    MOZ_ASSERT(handle);
-
-    if (!handle->IsDoomed()) {
-      _retval->AppendElement(handle);
-    }
-  }
+  mTable.EnumerateEntries(&GetActiveHandlesEnum, _retval);
 }
 
 void
 CacheFileHandles::ClearAll()
 {
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
   mTable.Clear();
 }
--- a/netwerk/cache2/CacheIndex.cpp
+++ b/netwerk/cache2/CacheIndex.cpp
@@ -624,17 +624,17 @@ CacheIndex::EnsureEntryExists(const SHA1
           // This could happen only if somebody copies files to the entries
           // directory while FF is running.
           LOG(("CacheIndex::EnsureEntryExists() - Cache file was added outside "
                "FF process! Update is needed."));
           index->mIndexNeedsUpdate = true;
         } else if (index->mState == READY ||
                    (entryRemoved && !entry->IsFresh())) {
           // Removed non-fresh entries can be present as a result of
-          // MergeJournal()
+          // ProcessJournalEntry()
           LOG(("CacheIndex::EnsureEntryExists() - Didn't find entry that should"
                " exist, update is needed"));
           index->mIndexNeedsUpdate = true;
         }
 
         if (!entry) {
           entry = index->mIndex.PutEntry(*aHash);
         }
@@ -843,17 +843,17 @@ CacheIndex::RemoveEntry(const SHA1Sum::H
           // This could happen only if somebody copies files to the entries
           // directory while FF is running.
           LOG(("CacheIndex::RemoveEntry() - Cache file was added outside FF "
                "process! Update is needed."));
           index->mIndexNeedsUpdate = true;
         } else if (index->mState == READY ||
                    (entryRemoved && !entry->IsFresh())) {
           // Removed non-fresh entries can be present as a result of
-          // MergeJournal()
+          // ProcessJournalEntry()
           LOG(("CacheIndex::RemoveEntry() - Didn't find entry that should exist"
                ", update is needed"));
           index->mIndexNeedsUpdate = true;
         }
       } else {
         if (entry) {
           if (!entry->IsDirty() && entry->IsFileEmpty()) {
             index->mIndex.RemoveEntry(*aHash);
@@ -1483,68 +1483,74 @@ CacheIndex::HasEntryChanged(CacheIndexEn
 
 void
 CacheIndex::ProcessPendingOperations()
 {
   LOG(("CacheIndex::ProcessPendingOperations()"));
 
   AssertOwnsLock();
 
-  for (auto iter = mPendingUpdates.Iter(); !iter.Done(); iter.Next()) {
-    CacheIndexEntryUpdate* update = iter.Get();
-
-    LOG(("CacheIndex::ProcessPendingOperations() [hash=%08x%08x%08x%08x%08x]",
-         LOGSHA1(update->Hash())));
-
-    MOZ_ASSERT(update->IsFresh());
-
-    CacheIndexEntry* entry = mIndex.GetEntry(*update->Hash());
-
-    {
-      CacheIndexEntryAutoManage emng(update->Hash(), this);
-      emng.DoNotSearchInUpdates();
-
-      if (update->IsRemoved()) {
-        if (entry) {
-          if (entry->IsRemoved()) {
-            MOZ_ASSERT(entry->IsFresh());
-            MOZ_ASSERT(entry->IsDirty());
-          } else if (!entry->IsDirty() && entry->IsFileEmpty()) {
-            // Entries with empty file are not stored in index on disk. Just
-            // remove the entry, but only in case the entry is not dirty, i.e.
-            // the entry file was empty when we wrote the index.
-            mIndex.RemoveEntry(*update->Hash());
-            entry = nullptr;
-          } else {
-            entry->MarkRemoved();
-            entry->MarkDirty();
-            entry->MarkFresh();
-          }
-        }
-      } else if (entry) {
-        // Some information in mIndex can be newer than in mPendingUpdates (see
-        // bug 1074832). This will copy just those values that were really
-        // updated.
-        update->ApplyUpdate(entry);
-      } else {
-        // There is no entry in mIndex, copy all information from
-        // mPendingUpdates to mIndex.
-        entry = mIndex.PutEntry(*update->Hash());
-        *entry = *update;
-      }
-    }
-
-    iter.Remove();
-  }
+  mPendingUpdates.EnumerateEntries(&CacheIndex::UpdateEntryInIndex, this);
 
   MOZ_ASSERT(mPendingUpdates.Count() == 0);
 
   EnsureCorrectStats();
 }
 
+// static
+PLDHashOperator
+CacheIndex::UpdateEntryInIndex(CacheIndexEntryUpdate *aEntry, void* aClosure)
+{
+  CacheIndex *index = static_cast<CacheIndex *>(aClosure);
+
+  LOG(("CacheFile::UpdateEntryInIndex() [hash=%08x%08x%08x%08x%08x]",
+       LOGSHA1(aEntry->Hash())));
+
+  MOZ_ASSERT(aEntry->IsFresh());
+
+  CacheIndexEntry *entry = index->mIndex.GetEntry(*aEntry->Hash());
+
+  CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
+  emng.DoNotSearchInUpdates();
+
+  if (aEntry->IsRemoved()) {
+    if (entry) {
+      if (entry->IsRemoved()) {
+        MOZ_ASSERT(entry->IsFresh());
+        MOZ_ASSERT(entry->IsDirty());
+      } else if (!entry->IsDirty() && entry->IsFileEmpty()) {
+        // Entries with empty file are not stored in index on disk. Just remove
+        // the entry, but only in case the entry is not dirty, i.e. the entry
+        // file was empty when we wrote the index.
+        index->mIndex.RemoveEntry(*aEntry->Hash());
+        entry = nullptr;
+      } else {
+        entry->MarkRemoved();
+        entry->MarkDirty();
+        entry->MarkFresh();
+      }
+    }
+
+    return PL_DHASH_REMOVE;
+  }
+
+  if (entry) {
+    // Some information in mIndex can be newer than in mPendingUpdates (see bug
+    // 1074832). This will copy just those values that were really updated.
+    aEntry->ApplyUpdate(entry);
+  } else {
+    // There is no entry in mIndex, copy all information from mPendingUpdates
+    // to mIndex.
+    entry = index->mIndex.PutEntry(*aEntry->Hash());
+    *entry = *aEntry;
+  }
+
+  return PL_DHASH_REMOVE;
+}
+
 bool
 CacheIndex::WriteIndexToDiskIfNeeded()
 {
   if (mState != READY || mShuttingDown) {
     return false;
   }
 
   if (!mLastDumpTime.IsNull() &&
@@ -1599,16 +1605,31 @@ CacheIndex::WriteIndexToDisk()
   NetworkEndian::writeUint32(&hdr->mTimeStamp,
                              static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC));
   NetworkEndian::writeUint32(&hdr->mIsDirty, 1);
 
   mRWBufPos = sizeof(CacheIndexHeader);
   mSkipEntries = 0;
 }
 
+namespace {
+
+struct WriteRecordsHelper
+{
+  char    *mBuf;
+  uint32_t mSkip;
+  uint32_t mProcessMax;
+  uint32_t mProcessed;
+#ifdef DEBUG
+  bool     mHasMore;
+#endif
+};
+
+} // namespace
+
 void
 CacheIndex::WriteRecords()
 {
   LOG(("CacheIndex::WriteRecords()"));
 
   nsresult rv;
 
   AssertOwnsLock();
@@ -1621,71 +1642,49 @@ CacheIndex::WriteRecords()
     fileOffset = sizeof(CacheIndexHeader);
     fileOffset += sizeof(CacheIndexRecord) * mSkipEntries;
   } else {
     MOZ_ASSERT(mRWBufPos == sizeof(CacheIndexHeader));
     fileOffset = 0;
   }
   uint32_t hashOffset = mRWBufPos;
 
-  char* buf = mRWBuf + mRWBufPos;
-  uint32_t skip = mSkipEntries;
-  uint32_t processMax = (mRWBufSize - mRWBufPos) / sizeof(CacheIndexRecord);
-  MOZ_ASSERT(processMax != 0 || mProcessEntries == 0); // TODO make sure we can write an empty index
-  uint32_t processed = 0;
+  WriteRecordsHelper data;
+  data.mBuf = mRWBuf + mRWBufPos;
+  data.mSkip = mSkipEntries;
+  data.mProcessMax = (mRWBufSize - mRWBufPos) / sizeof(CacheIndexRecord);
+  MOZ_ASSERT(data.mProcessMax != 0 || mProcessEntries == 0); // TODO make sure we can write an empty index
+  data.mProcessed = 0;
 #ifdef DEBUG
-  bool hasMore = false;
+  data.mHasMore = false;
 #endif
-  for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
-    CacheIndexEntry* entry = iter.Get();
-    if (entry->IsRemoved() ||
-        !entry->IsInitialized() ||
-        entry->IsFileEmpty()) {
-      continue;
-    }
-
-    if (skip) {
-      skip--;
-      continue;
-    }
-
-    if (processed == processMax) {
-  #ifdef DEBUG
-      hasMore = true;
-  #endif
-      break;
-    }
-
-    entry->WriteToBuf(buf);
-    buf += sizeof(CacheIndexRecord);
-    processed++;
-  }
-
-  MOZ_ASSERT(mRWBufPos != static_cast<uint32_t>(buf - mRWBuf) ||
+
+  mIndex.EnumerateEntries(&CacheIndex::CopyRecordsToRWBuf, &data);
+  MOZ_ASSERT(mRWBufPos != static_cast<uint32_t>(data.mBuf - mRWBuf) ||
              mProcessEntries == 0);
-  mRWBufPos = buf - mRWBuf;
-  mSkipEntries += processed;
+  mRWBufPos = data.mBuf - mRWBuf;
+  mSkipEntries += data.mProcessed;
   MOZ_ASSERT(mSkipEntries <= mProcessEntries);
 
   mRWHash->Update(mRWBuf + hashOffset, mRWBufPos - hashOffset);
 
   if (mSkipEntries == mProcessEntries) {
-    MOZ_ASSERT(!hasMore);
+    MOZ_ASSERT(!data.mHasMore);
 
     // We've processed all records
     if (mRWBufPos + sizeof(CacheHash::Hash32_t) > mRWBufSize) {
       // realloc buffer to spare another write cycle
       mRWBufSize = mRWBufPos + sizeof(CacheHash::Hash32_t);
       mRWBuf = static_cast<char *>(moz_xrealloc(mRWBuf, mRWBufSize));
     }
 
     NetworkEndian::writeUint32(mRWBuf + mRWBufPos, mRWHash->GetHash());
     mRWBufPos += sizeof(CacheHash::Hash32_t);
   } else {
-    MOZ_ASSERT(hasMore);
+    MOZ_ASSERT(data.mHasMore);
   }
 
   rv = CacheFileIOManager::Write(mIndexHandle, fileOffset, mRWBuf, mRWBufPos,
                                  mSkipEntries == mProcessEntries, false, this);
   if (NS_FAILED(rv)) {
     LOG(("CacheIndex::WriteRecords() - CacheFileIOManager::Write() failed "
          "synchronously [rv=0x%08x]", rv));
     FinishWrite(false);
@@ -1706,35 +1705,17 @@ CacheIndex::FinishWrite(bool aSucceeded)
   mIndexHandle = nullptr;
   mRWHash = nullptr;
   ReleaseBuffer();
 
   if (aSucceeded) {
     // Opening of the file must not be in progress if writing succeeded.
     MOZ_ASSERT(!mIndexFileOpener);
 
-    for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
-      CacheIndexEntry* entry = iter.Get();
-
-      bool remove = false;
-      {
-        CacheIndexEntryAutoManage emng(entry->Hash(), this);
-
-        if (entry->IsRemoved()) {
-          emng.DoNotSearchInIndex();
-          remove = true;
-        } else if (entry->IsDirty()) {
-          entry->ClearDirty();
-        }
-      }
-      if (remove) {
-        iter.Remove();
-      }
-    }
-
+    mIndex.EnumerateEntries(&CacheIndex::ApplyIndexChanges, this);
     mIndexOnDiskIsValid = true;
   } else {
     if (mIndexFileOpener) {
       // If opening of the file is still in progress (e.g. WRITE process was
       // canceled by RemoveAll()) then we need to cancel the opener to make sure
       // that OnFileOpenedInternal() won't be called.
       mIndexFileOpener->Cancel();
       mIndexFileOpener = nullptr;
@@ -1745,16 +1726,72 @@ CacheIndex::FinishWrite(bool aSucceeded)
   mIndexStats.Log();
 
   if (mState == WRITING) {
     ChangeState(READY);
     mLastDumpTime = TimeStamp::NowLoRes();
   }
 }
 
+// static
+PLDHashOperator
+CacheIndex::CopyRecordsToRWBuf(CacheIndexEntry *aEntry, void* aClosure)
+{
+  if (aEntry->IsRemoved()) {
+    return PL_DHASH_NEXT;
+  }
+
+  if (!aEntry->IsInitialized()) {
+    return PL_DHASH_NEXT;
+  }
+
+  if (aEntry->IsFileEmpty()) {
+    return PL_DHASH_NEXT;
+  }
+
+  WriteRecordsHelper *data = static_cast<WriteRecordsHelper *>(aClosure);
+  if (data->mSkip) {
+    data->mSkip--;
+    return PL_DHASH_NEXT;
+  }
+
+  if (data->mProcessed == data->mProcessMax) {
+#ifdef DEBUG
+    data->mHasMore = true;
+#endif
+    return PL_DHASH_STOP;
+  }
+
+  aEntry->WriteToBuf(data->mBuf);
+  data->mBuf += sizeof(CacheIndexRecord);
+  data->mProcessed++;
+
+  return PL_DHASH_NEXT;
+}
+
+// static
+PLDHashOperator
+CacheIndex::ApplyIndexChanges(CacheIndexEntry *aEntry, void* aClosure)
+{
+  CacheIndex *index = static_cast<CacheIndex *>(aClosure);
+
+  CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
+
+  if (aEntry->IsRemoved()) {
+    emng.DoNotSearchInIndex();
+    return PL_DHASH_REMOVE;
+  }
+
+  if (aEntry->IsDirty()) {
+    aEntry->ClearDirty();
+  }
+
+  return PL_DHASH_NEXT;
+}
+
 nsresult
 CacheIndex::GetFile(const nsACString &aName, nsIFile **_retval)
 {
   nsresult rv;
 
   nsCOMPtr<nsIFile> file;
   rv = mCacheDirectory->Clone(getter_AddRefs(file));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -1929,23 +1966,17 @@ CacheIndex::WriteLogToDisk()
   mIndexStats.Log();
 
   PRFileDesc *fd = nullptr;
   rv = logFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE,
                                  0600, &fd);
   NS_ENSURE_SUCCESS(rv, rv);
 
   WriteLogHelper wlh(fd);
-  for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
-    CacheIndexEntry* entry = iter.Get();
-    if (entry->IsRemoved() || entry->IsDirty()) {
-      wlh.AddEntry(entry);
-    }
-    iter.Remove();
-  }
+  mIndex.EnumerateEntries(&CacheIndex::WriteEntryToLog, &wlh);
 
   rv = wlh.Finish();
   PR_Close(fd);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = indexFile->OpenNSPRFileDesc(PR_RDWR, 0600, &fd);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1968,16 +1999,29 @@ CacheIndex::WriteLogToDisk()
   PR_Close(fd);
   if (bytesWritten != sizeof(CacheIndexHeader)) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
+// static
+PLDHashOperator
+CacheIndex::WriteEntryToLog(CacheIndexEntry *aEntry, void* aClosure)
+{
+  WriteLogHelper *wlh = static_cast<WriteLogHelper *>(aClosure);
+
+  if (aEntry->IsRemoved() || aEntry->IsDirty()) {
+    wlh->AddEntry(aEntry);
+  }
+
+  return PL_DHASH_REMOVE;
+}
+
 void
 CacheIndex::ReadIndexFromDisk()
 {
   LOG(("CacheIndex::ReadIndexFromDisk()"));
 
   nsresult rv;
 
   AssertOwnsLock();
@@ -2304,75 +2348,84 @@ CacheIndex::ParseJournal()
 
 void
 CacheIndex::MergeJournal()
 {
   LOG(("CacheIndex::MergeJournal()"));
 
   AssertOwnsLock();
 
-  for (auto iter = mTmpJournal.Iter(); !iter.Done(); iter.Next()) {
-    CacheIndexEntry* entry = iter.Get();
-
-    LOG(("CacheIndex::MergeJournal() [hash=%08x%08x%08x%08x%08x]",
-         LOGSHA1(entry->Hash())));
-
-    CacheIndexEntry* entry2 = mIndex.GetEntry(*entry->Hash());
-
-    CacheIndexEntryAutoManage emng(entry->Hash(), this);
-
-    if (entry->IsRemoved()) {
-      if (entry2) {
-        entry2->MarkRemoved();
-        entry2->MarkDirty();
-      }
-    } else {
-      if (!entry2) {
-        entry2 = mIndex.PutEntry(*entry->Hash());
-      }
-
-      *entry2 = *entry;
-      entry2->MarkDirty();
-    }
-
-    iter.Remove();
-  }
+  mTmpJournal.EnumerateEntries(&CacheIndex::ProcessJournalEntry, this);
 
   MOZ_ASSERT(mTmpJournal.Count() == 0);
 }
 
+// static
+PLDHashOperator
+CacheIndex::ProcessJournalEntry(CacheIndexEntry *aEntry, void* aClosure)
+{
+  CacheIndex *index = static_cast<CacheIndex *>(aClosure);
+
+  LOG(("CacheIndex::ProcessJournalEntry() [hash=%08x%08x%08x%08x%08x]",
+       LOGSHA1(aEntry->Hash())));
+
+  CacheIndexEntry *entry = index->mIndex.GetEntry(*aEntry->Hash());
+
+  CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
+
+  if (aEntry->IsRemoved()) {
+    if (entry) {
+      entry->MarkRemoved();
+      entry->MarkDirty();
+    }
+  } else {
+    if (!entry) {
+      entry = index->mIndex.PutEntry(*aEntry->Hash());
+    }
+
+    *entry = *aEntry;
+    entry->MarkDirty();
+  }
+
+  return PL_DHASH_REMOVE;
+}
+
 void
 CacheIndex::EnsureNoFreshEntry()
 {
 #ifdef DEBUG_STATS
   CacheIndexStats debugStats;
   debugStats.DisableLogging();
-  for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
-    debugStats.BeforeChange(nullptr);
-    debugStats.AfterChange(iter.Get());
-  }
+  mIndex.EnumerateEntries(&CacheIndex::SumIndexStats, &debugStats);
   MOZ_ASSERT(debugStats.Fresh() == 0);
 #endif
 }
 
 void
 CacheIndex::EnsureCorrectStats()
 {
 #ifdef DEBUG_STATS
   MOZ_ASSERT(mPendingUpdates.Count() == 0);
   CacheIndexStats debugStats;
   debugStats.DisableLogging();
-  for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
-    debugStats.BeforeChange(nullptr);
-    debugStats.AfterChange(iter.Get());
-  }
+  mIndex.EnumerateEntries(&CacheIndex::SumIndexStats, &debugStats);
   MOZ_ASSERT(debugStats == mIndexStats);
 #endif
 }
 
+// static
+PLDHashOperator
+CacheIndex::SumIndexStats(CacheIndexEntry *aEntry, void* aClosure)
+{
+  CacheIndexStats *stats = static_cast<CacheIndexStats *>(aClosure);
+  stats->BeforeChange(nullptr);
+  stats->AfterChange(aEntry);
+  return PL_DHASH_NEXT;
+}
+
 void
 CacheIndex::FinishRead(bool aSucceeded)
 {
   LOG(("CacheIndex::FinishRead() [succeeded=%d]", aSucceeded));
   AssertOwnsLock();
 
   MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == READING);
 
@@ -2419,17 +2472,17 @@ CacheIndex::FinishRead(bool aSucceeded)
     return;
   }
 
   if (!mIndexOnDiskIsValid) {
     MOZ_ASSERT(mTmpJournal.Count() == 0);
     EnsureNoFreshEntry();
     ProcessPendingOperations();
     // Remove all entries that we haven't seen during this session
-    RemoveNonFreshEntries();
+    mIndex.EnumerateEntries(&CacheIndex::RemoveNonFreshEntries, this);
     StartUpdatingIndex(true);
     return;
   }
 
   if (!mJournalReadSuccessfully) {
     mTmpJournal.Clear();
     EnsureNoFreshEntry();
     ProcessPendingOperations();
@@ -3007,46 +3060,44 @@ CacheIndex::FinishUpdate(bool aSucceeded
   if (mState == SHUTDOWN) {
     return;
   }
 
   if (mState == UPDATING && aSucceeded) {
     // If we've iterated over all entries successfully then all entries that
     // really exist on the disk are now marked as fresh. All non-fresh entries
     // don't exist anymore and must be removed from the index.
-    RemoveNonFreshEntries();
+    mIndex.EnumerateEntries(&CacheIndex::RemoveNonFreshEntries, this);
   }
 
   // Make sure we won't start update. If the build or update failed, there is no
   // reason to believe that it will succeed next time.
   mIndexNeedsUpdate = false;
 
   ChangeState(READY);
   mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately
 }
 
-void
-CacheIndex::RemoveNonFreshEntries()
+// static
+PLDHashOperator
+CacheIndex::RemoveNonFreshEntries(CacheIndexEntry *aEntry, void* aClosure)
 {
-  for (auto iter = mIndex.Iter(); !iter.Done(); iter.Next()) {
-    CacheIndexEntry* entry = iter.Get();
-    if (entry->IsFresh()) {
-      continue;
-    }
-
-    LOG(("CacheIndex::RemoveNonFreshEntries() - Removing entry. "
-         "[hash=%08x%08x%08x%08x%08x]", LOGSHA1(entry->Hash())));
-
-    {
-      CacheIndexEntryAutoManage emng(entry->Hash(), this);
-      emng.DoNotSearchInIndex();
-    }
-
-    iter.Remove();
+  if (aEntry->IsFresh()) {
+    return PL_DHASH_NEXT;
   }
+
+  LOG(("CacheFile::RemoveNonFreshEntries() - Removing entry. "
+       "[hash=%08x%08x%08x%08x%08x]", LOGSHA1(aEntry->Hash())));
+
+  CacheIndex *index = static_cast<CacheIndex *>(aClosure);
+
+  CacheIndexEntryAutoManage emng(aEntry->Hash(), index);
+  emng.DoNotSearchInIndex();
+
+  return PL_DHASH_REMOVE;
 }
 
 // static
 char const *
 CacheIndex::StateString(EState aState)
 {
   switch (aState) {
     case INITIAL:  return "INITIAL";
--- a/netwerk/cache2/CacheIndex.h
+++ b/netwerk/cache2/CacheIndex.h
@@ -718,16 +718,18 @@ private:
   // Checks whether any of the information about the entry has changed.
   static bool HasEntryChanged(CacheIndexEntry *aEntry,
                               const uint32_t  *aFrecency,
                               const uint32_t  *aExpirationTime,
                               const uint32_t  *aSize);
 
   // Merge all pending operations from mPendingUpdates into mIndex.
   void ProcessPendingOperations();
+  static PLDHashOperator UpdateEntryInIndex(CacheIndexEntryUpdate *aEntry,
+                                            void* aClosure);
 
   // Following methods perform writing of the index file.
   //
   // The index is written periodically, but not earlier than once in
   // kMinDumpInterval and there must be at least kMinUnwrittenChanges
   // differences between index on disk and in memory. Index is always first
   // written to a temporary file and the old index file is replaced when the
   // writing process succeeds.
@@ -738,29 +740,37 @@ private:
   // Starts writing of index file.
   void WriteIndexToDisk();
   // Serializes part of mIndex hashtable to the write buffer a writes the buffer
   // to the file.
   void WriteRecords();
   // Finalizes writing process.
   void FinishWrite(bool aSucceeded);
 
+  static PLDHashOperator CopyRecordsToRWBuf(CacheIndexEntry *aEntry,
+                                            void* aClosure);
+  static PLDHashOperator ApplyIndexChanges(CacheIndexEntry *aEntry,
+                                           void* aClosure);
+
   // Following methods perform writing of the journal during shutdown. All these
   // methods must be called only during shutdown since they write/delete files
   // directly on the main thread instead of using CacheFileIOManager that does
   // it asynchronously on IO thread. Journal contains only entries that are
   // dirty, i.e. changes that are not present in the index file on the disk.
   // When the log is written successfully, the dirty flag in index file is
   // cleared.
   nsresult GetFile(const nsACString &aName, nsIFile **_retval);
   nsresult RemoveFile(const nsACString &aName);
   void     RemoveIndexFromDisk();
   // Writes journal to the disk and clears dirty flag in index header.
   nsresult WriteLogToDisk();
 
+  static PLDHashOperator WriteEntryToLog(CacheIndexEntry *aEntry,
+                                         void* aClosure);
+
   // Following methods perform reading of the index from the disk.
   //
   // Index is read at startup just after initializing the CacheIndex. There are
   // 3 files used when manipulating with index: index file, journal file and
   // a temporary file. All files contain the hash of the data, so we can check
   // whether the content is valid and complete. Index file contains also a dirty
   // flag in the index header which is unset on a clean shutdown. During opening
   // and reading of the files we determine the status of the whole index from
@@ -805,19 +815,23 @@ private:
   // Merges entries from journal into mIndex.
   void MergeJournal();
   // In debug build this method checks that we have no fresh entry in mIndex
   // after we finish reading index and before we process pending operations.
   void EnsureNoFreshEntry();
   // In debug build this method is called after processing pending operations
   // to make sure mIndexStats contains correct information.
   void EnsureCorrectStats();
+  static PLDHashOperator SumIndexStats(CacheIndexEntry *aEntry, void* aClosure);
   // Finalizes reading process.
   void FinishRead(bool aSucceeded);
 
+  static PLDHashOperator ProcessJournalEntry(CacheIndexEntry *aEntry,
+                                             void* aClosure);
+
   // Following methods perform updating and building of the index.
   // Timer callback that starts update or build process.
   static void DelayedUpdate(nsITimer *aTimer, void *aClosure);
   // Posts timer event that start update or build process.
   nsresult ScheduleUpdateTimer(uint32_t aDelay);
   nsresult SetupDirectoryEnumerator();
   void InitEntryFromDiskData(CacheIndexEntry *aEntry,
                              CacheFileMetadata *aMetaData,
@@ -834,17 +848,18 @@ private:
   void StartUpdatingIndex(bool aRebuild);
   // Iterates through all files in entries directory that we didn't create/open
   // during this session and theirs last modified time is newer than timestamp
   // in the index header. Parses the files and adds the entries to the index.
   void UpdateIndex();
   // Finalizes update or build process.
   void FinishUpdate(bool aSucceeded);
 
-  void RemoveNonFreshEntries();
+  static PLDHashOperator RemoveNonFreshEntries(CacheIndexEntry *aEntry,
+                                               void* aClosure);
 
   enum EState {
     // Initial state in which the index is not usable
     // Possible transitions:
     //  -> READING
     INITIAL  = 0,
 
     // Index is being read from the disk.
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -1437,16 +1437,35 @@ nsCookieService::HandleCorruptDB(DBState
       mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener);
     }
     CleanupDefaultDBConnection();
     break;
   }
   }
 }
 
+static PLDHashOperator
+RebuildDBCallback(nsCookieEntry *aEntry,
+                  void          *aArg)
+{
+  mozIStorageBindingParamsArray* paramsArray =
+    static_cast<mozIStorageBindingParamsArray*>(aArg);
+
+  const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
+  for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+    nsCookie* cookie = cookies[i];
+
+    if (!cookie->IsSession()) {
+      bindCookieParameters(paramsArray, nsCookieKey(aEntry), cookie);
+    }
+  }
+
+  return PL_DHASH_NEXT;
+}
+
 void
 nsCookieService::RebuildCorruptDB(DBState* aDBState)
 {
   NS_ASSERTION(!aDBState->dbConn, "shouldn't have an open db connection");
   NS_ASSERTION(aDBState->corruptFlag == DBState::CLOSING_FOR_REBUILD,
     "should be in CLOSING_FOR_REBUILD state");
 
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
@@ -1490,28 +1509,17 @@ nsCookieService::RebuildCorruptDB(DBStat
   if (os) {
     os->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr);
   }
 
   // Enumerate the hash, and add cookies to the params array.
   mozIStorageAsyncStatement* stmt = aDBState->stmtInsert;
   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
   stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
-  for (auto iter = aDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
-    nsCookieEntry* entry = iter.Get();
-
-    const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
-    for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
-      nsCookie* cookie = cookies[i];
-
-      if (!cookie->IsSession()) {
-        bindCookieParameters(paramsArray, nsCookieKey(entry), cookie);
-      }
-    }
-  }
+  aDBState->hostTable.EnumerateEntries(RebuildDBCallback, paramsArray.get());
 
   // Make sure we've got something to write. If we don't, we're done.
   uint32_t length;
   paramsArray->GetLength(&length);
   if (length == 0) {
     COOKIE_LOGSTRING(LogLevel::Debug,
       ("RebuildCorruptDB(): nothing to write, rebuild complete"));
     mDefaultDBState->corruptFlag = DBState::OK;
@@ -1940,33 +1948,42 @@ nsCookieService::RemoveAll()
       HandleCorruptDB(mDefaultDBState);
     }
   }
 
   NotifyChanged(nullptr, MOZ_UTF16("cleared"));
   return NS_OK;
 }
 
+static PLDHashOperator
+COMArrayCallback(nsCookieEntry *aEntry,
+                 void          *aArg)
+{
+  nsCOMArray<nsICookie> *data = static_cast<nsCOMArray<nsICookie> *>(aArg);
+
+  const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
+  for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+    data->AppendObject(cookies[i]);
+  }
+
+  return PL_DHASH_NEXT;
+}
+
 NS_IMETHODIMP
 nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator)
 {
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   EnsureReadComplete();
 
   nsCOMArray<nsICookie> cookieList(mDBState->cookieCount);
-  for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
-    const nsCookieEntry::ArrayType& cookies = iter.Get()->GetCookies();
-    for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
-      cookieList.AppendObject(cookies[i]);
-    }
-  }
+  mDBState->hostTable.EnumerateEntries(COMArrayCallback, &cookieList);
 
   return NS_NewArrayEnumerator(aEnumerator, cookieList);
 }
 
 NS_IMETHODIMP
 nsCookieService::Add(const nsACString &aHost,
                      const nsACString &aPath,
                      const nsACString &aName,
@@ -3658,16 +3675,55 @@ nsCookieService::RemoveAllFromMemory()
 {
   // clearing the hashtable will call each nsCookieEntry's dtor,
   // which releases all their respective children.
   mDBState->hostTable.Clear();
   mDBState->cookieCount = 0;
   mDBState->cookieOldestTime = INT64_MAX;
 }
 
+// stores temporary data for enumerating over the hash entries,
+// since enumeration is done using callback functions
+struct nsPurgeData
+{
+  typedef nsTArray<nsListIter> ArrayType;
+
+  nsPurgeData(int64_t aCurrentTime,
+              int64_t aPurgeTime,
+              ArrayType &aPurgeList,
+              nsIMutableArray *aRemovedList,
+              mozIStorageBindingParamsArray *aParamsArray)
+   : currentTime(aCurrentTime)
+   , purgeTime(aPurgeTime)
+   , oldestTime(INT64_MAX)
+   , purgeList(aPurgeList)
+   , removedList(aRemovedList)
+   , paramsArray(aParamsArray)
+  {
+  }
+
+  // the current time, in seconds
+  int64_t currentTime;
+
+  // lastAccessed time older than which cookies are eligible for purge
+  int64_t purgeTime;
+
+  // lastAccessed time of the oldest cookie found during purge, to update our indicator
+  int64_t oldestTime;
+
+  // list of cookies over the age limit, for purging
+  ArrayType &purgeList;
+
+  // list of all cookies we've removed, for notification
+  nsIMutableArray *removedList;
+
+  // The array of parameters to be bound to the statement for deletion later.
+  mozIStorageBindingParamsArray *paramsArray;
+};
+
 // comparator class for lastaccessed times of cookies.
 class CompareCookiesByAge {
 public:
   bool Equals(const nsListIter &a, const nsListIter &b) const
   {
     return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() &&
            a.Cookie()->CreationTime() == b.Cookie()->CreationTime();
   }
@@ -3698,97 +3754,101 @@ public:
     // compare by entryclass pointer, then by index.
     if (a.entry != b.entry)
       return a.entry < b.entry;
 
     return a.index < b.index;
   }
 };
 
+PLDHashOperator
+purgeCookiesCallback(nsCookieEntry *aEntry,
+                     void          *aArg)
+{
+  nsPurgeData &data = *static_cast<nsPurgeData*>(aArg);
+
+  const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
+  mozIStorageBindingParamsArray *array = data.paramsArray;
+  for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ) {
+    nsListIter iter(aEntry, i);
+    nsCookie *cookie = cookies[i];
+
+    // check if the cookie has expired
+    if (cookie->Expiry() <= data.currentTime) {
+      data.removedList->AppendElement(cookie, false);
+      COOKIE_LOGEVICTED(cookie, "Cookie expired");
+
+      // remove from list; do not increment our iterator
+      gCookieService->RemoveCookieFromList(iter, array);
+
+    } else {
+      // check if the cookie is over the age limit
+      if (cookie->LastAccessed() <= data.purgeTime) {
+        data.purgeList.AppendElement(iter);
+
+      } else if (cookie->LastAccessed() < data.oldestTime) {
+        // reset our indicator
+        data.oldestTime = cookie->LastAccessed();
+      }
+
+      ++i;
+    }
+  }
+  return PL_DHASH_NEXT;
+}
+
 // purges expired and old cookies in a batch operation.
 already_AddRefed<nsIArray>
 nsCookieService::PurgeCookies(int64_t aCurrentTimeInUsec)
 {
   NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty");
   EnsureReadComplete();
 
   uint32_t initialCookieCount = mDBState->cookieCount;
   COOKIE_LOGSTRING(LogLevel::Debug,
     ("PurgeCookies(): beginning purge with %ld cookies and %lld oldest age",
      mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime));
 
-  typedef nsAutoTArray<nsListIter, kMaxNumberOfCookies> PurgeList;
-  PurgeList purgeList;
+  nsAutoTArray<nsListIter, kMaxNumberOfCookies> purgeList;
 
   nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
 
   // Create a params array to batch the removals. This is OK here because
   // all the removals are in order, and there are no interleaved additions.
   mozIStorageAsyncStatement *stmt = mDBState->stmtDelete;
   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
   if (mDBState->dbConn) {
     stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
   }
 
-  int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
-  int64_t purgeTime = aCurrentTimeInUsec - mCookiePurgeAge;
-  int64_t oldestTime = INT64_MAX;
-
-  for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
-    nsCookieEntry* entry = iter.Get();
-
-    const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
-    for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ) {
-      nsListIter iter(entry, i);
-      nsCookie* cookie = cookies[i];
-
-      // check if the cookie has expired
-      if (cookie->Expiry() <= currentTime) {
-        removedList->AppendElement(cookie, false);
-        COOKIE_LOGEVICTED(cookie, "Cookie expired");
-
-        // remove from list; do not increment our iterator
-        gCookieService->RemoveCookieFromList(iter, paramsArray);
-
-      } else {
-        // check if the cookie is over the age limit
-        if (cookie->LastAccessed() <= purgeTime) {
-          purgeList.AppendElement(iter);
-
-        } else if (cookie->LastAccessed() < oldestTime) {
-          // reset our indicator
-          oldestTime = cookie->LastAccessed();
-        }
-
-        ++i;
-      }
-    }
-  }
+  nsPurgeData data(aCurrentTimeInUsec / PR_USEC_PER_SEC,
+    aCurrentTimeInUsec - mCookiePurgeAge, purgeList, removedList, paramsArray);
+  mDBState->hostTable.EnumerateEntries(purgeCookiesCallback, &data);
 
   uint32_t postExpiryCookieCount = mDBState->cookieCount;
 
   // now we have a list of iterators for cookies over the age limit.
   // sort them by age, and then we'll see how many to remove...
   purgeList.Sort(CompareCookiesByAge());
 
   // only remove old cookies until we reach the max cookie limit, no more.
   uint32_t excess = mDBState->cookieCount > mMaxNumberOfCookies ?
     mDBState->cookieCount - mMaxNumberOfCookies : 0;
   if (purgeList.Length() > excess) {
     // We're not purging everything in the list, so update our indicator.
-    oldestTime = purgeList[excess].Cookie()->LastAccessed();
+    data.oldestTime = purgeList[excess].Cookie()->LastAccessed();
 
     purgeList.SetLength(excess);
   }
 
   // sort the list again, this time grouping cookies with a common entryclass
   // together, and with ascending index. this allows us to iterate backwards
   // over the list removing cookies, without having to adjust indexes as we go.
   purgeList.Sort(CompareCookiesByIndex());
-  for (PurgeList::index_type i = purgeList.Length(); i--; ) {
+  for (nsPurgeData::ArrayType::index_type i = purgeList.Length(); i--; ) {
     nsCookie *cookie = purgeList[i].Cookie();
     removedList->AppendElement(cookie, false);
     COOKIE_LOGEVICTED(cookie, "Cookie too old");
 
     RemoveCookieFromList(purgeList[i], paramsArray);
   }
 
   // Update the database if we have entries to purge.
@@ -3800,17 +3860,17 @@ nsCookieService::PurgeCookies(int64_t aC
       NS_ASSERT_SUCCESS(rv);
       nsCOMPtr<mozIStoragePendingStatement> handle;
       rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle));
       NS_ASSERT_SUCCESS(rv);
     }
   }
 
   // reset the oldest time indicator
-  mDBState->cookieOldestTime = oldestTime;
+  mDBState->cookieOldestTime = data.oldestTime;
 
   COOKIE_LOGSTRING(LogLevel::Debug,
     ("PurgeCookies(): %ld expired; %ld purged; %ld remain; %lld oldest age",
      initialCookieCount - postExpiryCookieCount,
      postExpiryCookieCount - mDBState->cookieCount,
      mDBState->cookieCount,
      aCurrentTimeInUsec - mDBState->cookieOldestTime));
 
@@ -3937,44 +3997,72 @@ nsCookieService::GetCookiesFromHost(cons
   const nsCookieEntry::ArrayType &cookies = entry->GetCookies();
   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
     cookieList.AppendObject(cookies[i]);
   }
 
   return NS_NewArrayEnumerator(aEnumerator, cookieList);
 }
 
+namespace {
+
+/**
+ * This structure is used as a in/out parameter when enumerating the cookies
+ * for an app.
+ * It will contain the app id and onlyBrowserElement flag information as input
+ * and will contain the array of matching cookies as output.
+ */
+struct GetCookiesForAppStruct {
+  uint32_t              appId;
+  bool                  onlyBrowserElement;
+  nsCOMArray<nsICookie> cookies;
+
+  GetCookiesForAppStruct() = delete;
+  GetCookiesForAppStruct(uint32_t aAppId, bool aOnlyBrowserElement)
+    : appId(aAppId)
+    , onlyBrowserElement(aOnlyBrowserElement)
+  {}
+};
+
+} // namespace
+
+/* static */ PLDHashOperator
+nsCookieService::GetCookiesForApp(nsCookieEntry* entry, void* arg)
+{
+  GetCookiesForAppStruct* data = static_cast<GetCookiesForAppStruct*>(arg);
+
+  if (entry->mAppId != data->appId ||
+      (data->onlyBrowserElement && !entry->mInBrowserElement)) {
+    return PL_DHASH_NEXT;
+  }
+
+  const nsCookieEntry::ArrayType& cookies = entry->GetCookies();
+
+  for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
+    data->cookies.AppendObject(cookies[i]);
+  }
+
+  return PL_DHASH_NEXT;
+}
+
 NS_IMETHODIMP
 nsCookieService::GetCookiesForApp(uint32_t aAppId, bool aOnlyBrowserElement,
                                   nsISimpleEnumerator** aEnumerator)
 {
   if (!mDBState) {
     NS_WARNING("No DBState! Profile already closed?");
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   NS_ENSURE_TRUE(aAppId != NECKO_UNKNOWN_APP_ID, NS_ERROR_INVALID_ARG);
 
-  nsCOMArray<nsICookie> cookies;
-  for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) {
-    nsCookieEntry* entry = iter.Get();
-
-    if (entry->mAppId != aAppId ||
-        (aOnlyBrowserElement && !entry->mInBrowserElement)) {
-      continue;
-    }
-
-    const nsCookieEntry::ArrayType& entryCookies = entry->GetCookies();
-
-    for (nsCookieEntry::IndexType i = 0; i < entryCookies.Length(); ++i) {
-      cookies.AppendObject(entryCookies[i]);
-    }
-  }
-
-  return NS_NewArrayEnumerator(aEnumerator, cookies);
+  GetCookiesForAppStruct data(aAppId, aOnlyBrowserElement);
+  mDBState->hostTable.EnumerateEntries(GetCookiesForApp, &data);
+
+  return NS_NewArrayEnumerator(aEnumerator, data.cookies);
 }
 
 NS_IMETHODIMP
 nsCookieService::RemoveCookiesForApp(uint32_t aAppId, bool aOnlyBrowserElement)
 {
   nsCOMPtr<nsISimpleEnumerator> enumerator;
   nsresult rv = GetCookiesForApp(aAppId, aOnlyBrowserElement,
                                  getter_AddRefs(enumerator));
--- a/netwerk/cookie/nsCookieService.h
+++ b/netwerk/cookie/nsCookieService.h
@@ -312,16 +312,22 @@ class nsCookieService final : public nsI
     void                          NotifyRejected(nsIURI *aHostURI);
     void                          NotifyThirdParty(nsIURI *aHostURI, bool aAccepted, nsIChannel *aChannel);
     void                          NotifyChanged(nsISupports *aSubject, const char16_t *aData);
     void                          NotifyPurged(nsICookie2* aCookie);
     already_AddRefed<nsIArray>    CreatePurgeList(nsICookie2* aCookie);
     void                          UpdateCookieOldestTime(DBState* aDBState, nsCookie* aCookie);
 
     /**
+     * This method is used to iterate the cookie hash table and select the ones
+     * that are part of a specific app.
+     */
+    static PLDHashOperator GetCookiesForApp(nsCookieEntry* entry, void* arg);
+
+    /**
      * This method is a helper that allows calling nsICookieManager::Remove()
      * with appId/inBrowserElement parameters.
      * NOTE: this could be added to a public interface if we happen to need it.
      */
     nsresult Remove(const nsACString& aHost, uint32_t aAppId,
                     bool aInBrowserElement, const nsACString& aName,
                     const nsACString& aPath, bool aBlocked);
 
@@ -345,16 +351,17 @@ class nsCookieService final : public nsI
     // cached prefs
     uint8_t                       mCookieBehavior; // BEHAVIOR_{ACCEPT, REJECTFOREIGN, REJECT, LIMITFOREIGN}
     bool                          mThirdPartySession;
     uint16_t                      mMaxNumberOfCookies;
     uint16_t                      mMaxCookiesPerHost;
     int64_t                       mCookiePurgeAge;
 
     // friends!
+    friend PLDHashOperator purgeCookiesCallback(nsCookieEntry *aEntry, void *aArg);
     friend class DBListenerErrorHandler;
     friend class ReadCookieDBListener;
     friend class CloseCookieDBListener;
 
     static nsCookieService*       GetSingleton();
     friend class mozilla::net::CookieServiceParent;
 };