Bug 1032254 - Generic way to pin reasource in the HTTP cache, r=michal
authorHonza Bambas <honzab.moz@firemni.cz>
Thu, 22 Oct 2015 12:11:00 +0200
changeset 306206 80eff2b52d144f170670d90fe98841cad2c0991c
parent 306205 94df1f873f5cb748643cfa13f7ed3c88489c7dcc
child 306207 c07f6d21fb7047d552b16b91ca66b621d8b597b0
push id1040
push userraliiev@mozilla.com
push dateMon, 29 Feb 2016 17:11:22 +0000
treeherdermozilla-release@8c3167321162 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmichal
bugs1032254
milestone44.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 1032254 - Generic way to pin reasource in the HTTP cache, r=michal * * * Bug NNNNNNN - message, r=reviewer
netwerk/base/nsICachingChannel.idl
netwerk/cache2/AppCacheStorage.cpp
netwerk/cache2/CacheEntry.cpp
netwerk/cache2/CacheEntry.h
netwerk/cache2/CacheFile.cpp
netwerk/cache2/CacheFile.h
netwerk/cache2/CacheFileChunk.cpp
netwerk/cache2/CacheFileChunk.h
netwerk/cache2/CacheFileContextEvictor.cpp
netwerk/cache2/CacheFileContextEvictor.h
netwerk/cache2/CacheFileIOManager.cpp
netwerk/cache2/CacheFileIOManager.h
netwerk/cache2/CacheFileInputStream.h
netwerk/cache2/CacheFileMetadata.cpp
netwerk/cache2/CacheFileMetadata.h
netwerk/cache2/CacheFileOutputStream.h
netwerk/cache2/CacheIndex.cpp
netwerk/cache2/CacheIndex.h
netwerk/cache2/CacheIndexIterator.h
netwerk/cache2/CacheStorage.cpp
netwerk/cache2/CacheStorage.h
netwerk/cache2/CacheStorageService.cpp
netwerk/cache2/CacheStorageService.h
netwerk/cache2/moz.build
netwerk/cache2/nsICacheStorageService.idl
netwerk/cache2/nsICacheTesting.idl
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/test/unit/head_cache.js
netwerk/test/unit/head_cache2.js
netwerk/test/unit/test_cache2-28-concurrent_read_resumable_entry_size_zero.js
netwerk/test/unit/test_cache2-29-concurrent_read_non-resumable_entry_size_zero.js
netwerk/test/unit/test_cache2-29a-concurrent_read_resumable_entry_size_zero.js
netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js
netwerk/test/unit/test_cache2-30a-entry-pinning.js
netwerk/test/unit/test_cache2-30b-pinning-storage-clear.js
netwerk/test/unit/test_cache2-30c-pinning-deferred-doom.js
netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js
netwerk/test/unit/test_cache_jar.js
netwerk/test/unit/xpcshell.ini
--- a/netwerk/base/nsICachingChannel.idl
+++ b/netwerk/base/nsICachingChannel.idl
@@ -12,17 +12,17 @@ interface nsIFile;
  * to affect its behavior with respect to how it uses the cache service.
  *
  * This interface provides:
  *   1) Support for "stream as file" semantics (for JAR and plugins).
  *   2) Support for "pinning" cached data in the cache (for printing and save-as).
  *   3) Support for uniquely identifying cached data in cases when the URL
  *      is insufficient (e.g., HTTP form submission).
  */
-[scriptable, uuid(436b939d-e391-48e5-ba64-ab0e496e3400)]
+[scriptable, uuid(dd1d6122-5ecf-4fe4-8f0f-995e7ab3121a)]
 interface nsICachingChannel : nsICacheInfoChannel
 {
     /**
      * Set/get the cache token... uniquely identifies the data in the cache.
      * Holding a reference to this token prevents the cached data from being
      * removed.
      * 
      * A cache token retrieved from a particular instance of nsICachingChannel
@@ -48,16 +48,21 @@ interface nsICachingChannel : nsICacheIn
     /**
      * Instructs the channel to only store the metadata of the entry, and not
      * the content. When reading an existing entry, this automatically sets
      * LOAD_ONLY_IF_MODIFIED flag.
      * Must be called before asyncOpen().
      */
     attribute boolean cacheOnlyMetadata;
 
+    /**
+     * Tells the channel to use the pinning storage.
+     */
+    attribute boolean pin;
+
     /**************************************************************************
      * Caching channel specific load flags:
      */
 
     /**
      * This load flag inhibits fetching from the net.  An error of
      * NS_ERROR_DOCUMENT_NOT_CACHED will be sent to the listener's
      * onStopRequest if network IO is necessary to complete the request.
--- a/netwerk/cache2/AppCacheStorage.cpp
+++ b/netwerk/cache2/AppCacheStorage.cpp
@@ -20,17 +20,17 @@
 
 namespace mozilla {
 namespace net {
 
 NS_IMPL_ISUPPORTS_INHERITED0(AppCacheStorage, CacheStorage)
 
 AppCacheStorage::AppCacheStorage(nsILoadContextInfo* aInfo,
                                  nsIApplicationCache* aAppCache)
-: CacheStorage(aInfo, true /* disk */, false /* lookup app cache */, false /* skip size check */)
+: CacheStorage(aInfo, true /* disk */, false /* lookup app cache */, false /* skip size check */, false /* pin */)
 , mAppCache(aAppCache)
 {
   MOZ_COUNT_CTOR(AppCacheStorage);
 }
 
 AppCacheStorage::~AppCacheStorage()
 {
   ProxyReleaseMainThread(mAppCache);
--- a/netwerk/cache2/CacheEntry.cpp
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -82,35 +82,55 @@ CacheEntry::Callback::Callback(CacheEntr
 , mCallback(aCallback)
 , mTargetThread(do_GetCurrentThread())
 , mReadOnly(aReadOnly)
 , mRevalidating(false)
 , mCheckOnAnyThread(aCheckOnAnyThread)
 , mRecheckAfterWrite(false)
 , mNotWanted(false)
 , mSecret(aSecret)
+, mDoomWhenFoundPinned(false)
+, mDoomWhenFoundNonPinned(false)
 {
   MOZ_COUNT_CTOR(CacheEntry::Callback);
 
   // The counter may go from zero to non-null only under the service lock
   // but here we expect it to be already positive.
   MOZ_ASSERT(mEntry->HandlesCount());
   mEntry->AddHandleRef();
 }
 
+CacheEntry::Callback::Callback(CacheEntry* aEntry, bool aDoomWhenFoundInPinStatus)
+: mEntry(aEntry)
+, mReadOnly(false)
+, mRevalidating(false)
+, mCheckOnAnyThread(true)
+, mRecheckAfterWrite(false)
+, mNotWanted(false)
+, mSecret(false)
+, mDoomWhenFoundPinned(aDoomWhenFoundInPinStatus == true)
+, mDoomWhenFoundNonPinned(aDoomWhenFoundInPinStatus == false)
+{
+  MOZ_COUNT_CTOR(CacheEntry::Callback);
+  MOZ_ASSERT(mEntry->HandlesCount());
+  mEntry->AddHandleRef();
+}
+
 CacheEntry::Callback::Callback(CacheEntry::Callback const &aThat)
 : mEntry(aThat.mEntry)
 , mCallback(aThat.mCallback)
 , mTargetThread(aThat.mTargetThread)
 , mReadOnly(aThat.mReadOnly)
 , mRevalidating(aThat.mRevalidating)
 , mCheckOnAnyThread(aThat.mCheckOnAnyThread)
 , mRecheckAfterWrite(aThat.mRecheckAfterWrite)
 , mNotWanted(aThat.mNotWanted)
 , mSecret(aThat.mSecret)
+, mDoomWhenFoundPinned(aThat.mDoomWhenFoundPinned)
+, mDoomWhenFoundNonPinned(aThat.mDoomWhenFoundNonPinned)
 {
   MOZ_COUNT_CTOR(CacheEntry::Callback);
 
   // The counter may go from zero to non-null only under the service lock
   // but here we expect it to be already positive.
   MOZ_ASSERT(mEntry->HandlesCount());
   mEntry->AddHandleRef();
 }
@@ -131,16 +151,30 @@ void CacheEntry::Callback::ExchangeEntry
   // The counter may go from zero to non-null only under the service lock
   // but here we expect it to be already positive.
   MOZ_ASSERT(aEntry->HandlesCount());
   aEntry->AddHandleRef();
   mEntry->ReleaseHandleRef();
   mEntry = aEntry;
 }
 
+bool CacheEntry::Callback::DeferDoom(bool *aDoom) const
+{
+  MOZ_ASSERT(mEntry->mPinningKnown);
+
+  if (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) || MOZ_UNLIKELY(mDoomWhenFoundPinned)) {
+    *aDoom = (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) && MOZ_LIKELY(!mEntry->mPinned)) ||
+             (MOZ_UNLIKELY(mDoomWhenFoundPinned) && MOZ_UNLIKELY(mEntry->mPinned));
+
+    return true;
+  }
+
+  return false;
+}
+
 nsresult CacheEntry::Callback::OnCheckThread(bool *aOnCheckThread) const
 {
   if (!mCheckOnAnyThread) {
     // Check we are on the target
     return mTargetThread->IsOnCurrentThread(aOnCheckThread);
   }
 
   // We can invoke check anywhere
@@ -159,30 +193,33 @@ NS_IMPL_ISUPPORTS(CacheEntry,
                   nsICacheEntry,
                   nsIRunnable,
                   CacheFileListener)
 
 CacheEntry::CacheEntry(const nsACString& aStorageID,
                        nsIURI* aURI,
                        const nsACString& aEnhanceID,
                        bool aUseDisk,
-                       bool aSkipSizeCheck)
+                       bool aSkipSizeCheck,
+                       bool aPin)
 : mFrecency(0)
 , mSortingExpirationTime(uint32_t(-1))
 , mLock("CacheEntry")
 , mFileStatus(NS_ERROR_NOT_INITIALIZED)
 , mURI(aURI)
 , mEnhanceID(aEnhanceID)
 , mStorageID(aStorageID)
 , mUseDisk(aUseDisk)
 , mSkipSizeCheck(aSkipSizeCheck)
-, mIsDoomed(false)
 , mSecurityInfoLoaded(false)
 , mPreventCallbacks(false)
 , mHasData(false)
+, mPinned(aPin)
+, mPinningKnown(false)
+, mIsDoomed(false)
 , mState(NOTLOADED)
 , mRegistration(NEVERREGISTERED)
 , mWriter(nullptr)
 , mPredictedDataSize(0)
 , mUseCount(0)
 , mReleaseThread(NS_GetCurrentThread())
 {
   MOZ_COUNT_CTOR(CacheEntry);
@@ -345,42 +382,45 @@ bool CacheEntry::Load(bool aTruncate, bo
   //    If there is or could be, doom that file.
   if ((!aTruncate || !mUseDisk) && NS_SUCCEEDED(rv)) {
     // Check the index right now to know we have or have not the entry
     // as soon as possible.
     CacheIndex::EntryStatus status;
     if (NS_SUCCEEDED(CacheIndex::HasEntry(fileKey, &status))) {
       switch (status) {
       case CacheIndex::DOES_NOT_EXIST:
-        LOG(("  entry doesn't exist according information from the index, truncating"));
+        // Doesn't apply to memory-only entries, Load() is called only once for them
+        // and never again for their session lifetime.
         if (!aTruncate && mUseDisk) {
+          LOG(("  entry doesn't exist according information from the index, truncating"));
           reportMiss = true;
+          aTruncate = true;
         }
-        aTruncate = true;
         break;
       case CacheIndex::EXISTS:
       case CacheIndex::DO_NOT_KNOW:
         if (!mUseDisk) {
-          LOG(("  entry open as memory-only, but there is (status=%d) a file, dooming it", status));
+          LOG(("  entry open as memory-only, but there is a file, status=%d, dooming it", status));
           CacheFileIOManager::DoomFileByKey(fileKey, nullptr);
         }
         break;
       }
     }
   }
 
   mFile = new CacheFile();
 
   BackgroundOp(Ops::REGISTER);
 
   bool directLoad = aTruncate || !mUseDisk;
   if (directLoad) {
     // mLoadStart will be used to calculate telemetry of life-time of this entry.
     // Low resulution is then enough.
     mLoadStart = TimeStamp::NowLoRes();
+    mPinningKnown = true;
   } else {
     mLoadStart = TimeStamp::Now();
   }
 
   {
     mozilla::MutexAutoUnlock unlock(mLock);
 
     if (reportMiss) {
@@ -390,16 +430,17 @@ bool CacheEntry::Load(bool aTruncate, bo
 
     LOG(("  performing load, file=%p", mFile.get()));
     if (NS_SUCCEEDED(rv)) {
       rv = mFile->Init(fileKey,
                        aTruncate,
                        !mUseDisk,
                        mSkipSizeCheck,
                        aPriority,
+                       mPinned,
                        directLoad ? nullptr : this);
     }
 
     if (NS_FAILED(rv)) {
       mFileStatus = rv;
       AsyncDoom(nullptr);
       return false;
     }
@@ -427,40 +468,46 @@ NS_IMETHODIMP CacheEntry::OnFileReady(ns
         CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
     } else {
       CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
         CacheFileUtils::DetailedCacheHitTelemetry::HIT, mLoadStart);
     }
   }
 
   // OnFileReady, that is the only code that can transit from LOADING
-  // to any follow-on state, can only be invoked ones on an entry,
-  // thus no need to lock.  Until this moment there is no consumer that
-  // could manipulate the entry state.
+  // to any follow-on state and can only be invoked ones on an entry.
+  // Until this moment there is no consumer that could manipulate
+  // the entry state.
+
   mozilla::MutexAutoLock lock(mLock);
 
   MOZ_ASSERT(mState == LOADING);
 
   mState = (aIsNew || NS_FAILED(aResult))
     ? EMPTY
     : READY;
 
   mFileStatus = aResult;
 
+  mPinned = mFile->IsPinned();;
+  mPinningKnown = true;
+  LOG(("  pinning=%d", mPinned));
+
   if (mState == READY) {
     mHasData = true;
 
     uint32_t frecency;
     mFile->GetFrecency(&frecency);
     // mFrecency is held in a double to increase computance precision.
     // It is ok to persist frecency only as a uint32 with some math involved.
     mFrecency = INT2FRECENCY(frecency);
   }
 
   InvokeCallbacks();
+
   return NS_OK;
 }
 
 NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult)
 {
   if (mDoomCallback) {
     RefPtr<DoomCallbackRunnable> event =
       new DoomCallbackRunnable(this, aResult);
@@ -478,23 +525,31 @@ already_AddRefed<CacheEntryHandle> Cache
   mLock.AssertCurrentThreadOwns();
 
   // Hold callbacks invocation, AddStorageEntry would invoke from doom prematurly
   mPreventCallbacks = true;
 
   RefPtr<CacheEntryHandle> handle;
   RefPtr<CacheEntry> newEntry;
   {
+    if (mPinned) {
+      MOZ_ASSERT(mUseDisk);
+      // We want to pin even no-store entries (the case we recreate a disk entry as
+      // a memory-only entry.)
+      aMemoryOnly = false;
+    }
+
     mozilla::MutexAutoUnlock unlock(mLock);
 
     // The following call dooms this entry (calls DoomAlreadyRemoved on us)
     nsresult rv = CacheStorageService::Self()->AddStorageEntry(
       GetStorageID(), GetURI(), GetEnhanceID(),
       mUseDisk && !aMemoryOnly,
       mSkipSizeCheck,
+      mPinned,
       true, // always create
       true, // truncate existing (this one)
       getter_AddRefs(handle));
 
     if (NS_SUCCEEDED(rv)) {
       newEntry = handle->Entry();
       LOG(("  exchanged entry %p by entry %p, rv=0x%08x", this, newEntry.get(), rv));
       newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
@@ -572,28 +627,42 @@ void CacheEntry::InvokeCallbacks()
 
   LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
 }
 
 bool CacheEntry::InvokeCallbacks(bool aReadOnly)
 {
   mLock.AssertCurrentThreadOwns();
 
+  RefPtr<CacheEntryHandle> recreatedHandle;
+
   uint32_t i = 0;
   while (i < mCallbacks.Length()) {
     if (mPreventCallbacks) {
       LOG(("  callbacks prevented!"));
       return false;
     }
 
     if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
       LOG(("  entry is being written/revalidated"));
       return false;
     }
 
+    bool recreate;
+    if (mCallbacks[i].DeferDoom(&recreate)) {
+      mCallbacks.RemoveElementAt(i);
+      if (!recreate) {
+        continue;
+      }
+
+      LOG(("  defer doom marker callback hit positive, recreating"));
+      recreatedHandle = ReopenTruncated(!mUseDisk, nullptr);
+      break;
+    }
+
     if (mCallbacks[i].mReadOnly != aReadOnly) {
       // Callback is not r/w or r/o, go to another one in line
       ++i;
       continue;
     }
 
     bool onCheckThread;
     nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread);
@@ -620,16 +689,22 @@ bool CacheEntry::InvokeCallbacks(bool aR
       // readers or potential writers would be unnecessarily kept from being
       // invoked.
       size_t pos = std::min(mCallbacks.Length(), static_cast<size_t>(i));
       mCallbacks.InsertElementAt(pos, callback);
       ++i;
     }
   }
 
+  if (recreatedHandle) {
+    // Must be released outside of the lock, enters InvokeCallback on the new entry
+    mozilla::MutexAutoUnlock unlock(mLock);
+    recreatedHandle = nullptr;
+  }
+
   return true;
 }
 
 bool CacheEntry::InvokeCallback(Callback & aCallback)
 {
   LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]",
     this, StateString(mState), aCallback.mCallback.get()));
 
@@ -986,16 +1061,23 @@ NS_IMETHODIMP CacheEntry::GetExpirationT
 
   return mFile->GetExpirationTime(aExpirationTime);
 }
 
 NS_IMETHODIMP CacheEntry::GetIsForcedValid(bool *aIsForcedValid)
 {
   NS_ENSURE_ARG(aIsForcedValid);
 
+  MOZ_ASSERT(mState > LOADING);
+
+  if (mPinned) {
+    *aIsForcedValid = true;
+    return NS_OK;
+  }
+
   nsAutoCString key;
 
   nsresult rv = HashingKeyWithStorage(key);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   *aIsForcedValid = CacheStorageService::Self()->IsForcedValidEntry(key);
@@ -1437,16 +1519,38 @@ void CacheEntry::SetRegistered(bool aReg
     mRegistration = REGISTERED;
   }
   else {
     MOZ_ASSERT(mRegistration == REGISTERED);
     mRegistration = DEREGISTERED;
   }
 }
 
+bool CacheEntry::DeferOrBypassRemovalOnPinStatus(bool aPinned)
+{
+  LOG(("CacheEntry::DeferOrBypassRemovalOnPinStatus [this=%p]", this));
+
+  mozilla::MutexAutoLock lock(mLock);
+
+  if (mPinningKnown) {
+    LOG(("  pinned=%d, caller=%d", mPinned, aPinned));
+    // Bypass when the pin status of this entry doesn't match the pin status
+    // caller wants to remove
+    return mPinned != aPinned;
+  }
+
+  LOG(("  pinning unknown, caller=%d", aPinned));
+  // Oterwise, remember to doom after the status is determined for any
+  // callback opening the entry after this point...
+  Callback c(this, aPinned);
+  RememberCallback(c);
+  // ...and always bypass
+  return true;
+}
+
 bool CacheEntry::Purge(uint32_t aWhat)
 {
   LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
 
   MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
 
   switch (aWhat) {
   case PURGE_DATA_ONLY_DISK_BACKED:
@@ -1518,16 +1622,20 @@ void CacheEntry::PurgeAndDoom()
 void CacheEntry::DoomAlreadyRemoved()
 {
   LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
 
   mozilla::MutexAutoLock lock(mLock);
 
   mIsDoomed = true;
 
+  // Pretend pinning is know.  This entry is now doomed for good, so don't
+  // bother with defering doom because of unknown pinning state any more.
+  mPinningKnown = true;
+
   // This schedules dooming of the file, dooming is ensured to happen
   // sooner than demand to open the same file made after this point
   // so that we don't get this file for any newer opened entry(s).
   DoomFile();
 
   // Must force post here since may be indirectly called from
   // InvokeCallbacks of this entry and we don't want reentrancy here.
   BackgroundOp(Ops::CALLBACKS, true);
--- a/netwerk/cache2/CacheEntry.h
+++ b/netwerk/cache2/CacheEntry.h
@@ -50,17 +50,17 @@ class CacheEntry final : public nsICache
                        , public CacheFileListener
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSICACHEENTRY
   NS_DECL_NSIRUNNABLE
 
   CacheEntry(const nsACString& aStorageID, nsIURI* aURI, const nsACString& aEnhanceID,
-             bool aUseDisk, bool aSkipSizeCheck);
+             bool aUseDisk, bool aSkipSizeCheck, bool aPin);
 
   void AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags);
 
   CacheEntryHandle* NewHandle();
 
 public:
   uint32_t GetMetadataMemoryConsumption();
   nsCString const &GetStorageID() const { return mStorageID; }
@@ -87,16 +87,17 @@ public:
   TimeStamp const& LoadStart() const { return mLoadStart; }
 
   enum EPurge {
     PURGE_DATA_ONLY_DISK_BACKED,
     PURGE_WHOLE_ONLY_DISK_BACKED,
     PURGE_WHOLE,
   };
 
+  bool DeferOrBypassRemovalOnPinStatus(bool aPinned);
   bool Purge(uint32_t aWhat);
   void PurgeAndDoom();
   void DoomAlreadyRemoved();
 
   nsresult HashingKeyWithStorage(nsACString &aResult) const;
   nsresult HashingKey(nsACString &aResult) const;
 
   static nsresult HashingKey(nsCSubstring const& aStorageID,
@@ -131,36 +132,50 @@ private:
   // for writing it the first time gets released.  We must then invoke
   // waiting callbacks to not break the chain.
   class Callback
   {
   public:
     Callback(CacheEntry* aEntry,
              nsICacheEntryOpenCallback *aCallback,
              bool aReadOnly, bool aCheckOnAnyThread, bool aSecret);
+    // Special constructor for Callback objects added to the chain
+    // just to ensure proper defer dooming (recreation) of this entry.
+    Callback(CacheEntry* aEntry, bool aDoomWhenFoundInPinStatus);
     Callback(Callback const &aThat);
     ~Callback();
 
     // Called when this callback record changes it's owning entry,
     // mainly during recreation.
     void ExchangeEntry(CacheEntry* aEntry);
 
+    // Returns true when an entry is about to be "defer" doomed and this is
+    // a "defer" callback.
+    bool DeferDoom(bool *aDoom) const;
+
     // We are raising reference count here to take into account the pending
     // callback (that virtually holds a ref to this entry before it gets
     // it's pointer).
     RefPtr<CacheEntry> mEntry;
     nsCOMPtr<nsICacheEntryOpenCallback> mCallback;
     nsCOMPtr<nsIThread> mTargetThread;
     bool mReadOnly : 1;
     bool mRevalidating : 1;
     bool mCheckOnAnyThread : 1;
     bool mRecheckAfterWrite : 1;
     bool mNotWanted : 1;
     bool mSecret : 1;
 
+    // These are set only for the defer-doomer Callback instance inserted
+    // to the callback chain.  When any of these is set and also any of
+    // the corressponding flags on the entry is set, this callback will
+    // indicate (via DeferDoom()) the entry have to be recreated/doomed.
+    bool mDoomWhenFoundPinned : 1;
+    bool mDoomWhenFoundNonPinned : 1;
+
     nsresult OnCheckThread(bool *aOnCheckThread) const;
     nsresult OnAvailThread(bool *aOnAvailThread) const;
   };
 
   // Since OnCacheEntryAvailable must be invoked on the main thread
   // we need a runnable for it...
   class AvailableCallbackRunnable : public nsRunnable
   {
@@ -269,38 +284,42 @@ private:
   // When mFileStatus is read and found success it is ensured there is mFile and
   // that it is after a successful call to Init().
   ::mozilla::Atomic<nsresult, ::mozilla::ReleaseAcquire> mFileStatus;
   nsCOMPtr<nsIURI> mURI;
   nsCString mEnhanceID;
   nsCString mStorageID;
 
   // Whether it's allowed to persist the data to disk
-  bool const mUseDisk;
-
+  bool const mUseDisk : 1;
   // Whether it should skip max size check.
-  bool const mSkipSizeCheck;
-
-  // Set when entry is doomed with AsyncDoom() or DoomAlreadyRemoved().
-  // Left as a standalone flag to not bother with locking (there is no need).
-  bool mIsDoomed;
+  bool const mSkipSizeCheck : 1;
 
   // Following flags are all synchronized with the cache entry lock.
 
   // Whether security info has already been looked up in metadata.
   bool mSecurityInfoLoaded : 1;
   // Prevents any callback invocation
   bool mPreventCallbacks : 1;
   // true: after load and an existing file, or after output stream has been opened.
   //       note - when opening an input stream, and this flag is false, output stream
   //       is open along ; this makes input streams on new entries behave correctly
   //       when EOF is reached (WOULD_BLOCK is returned).
   // false: after load and a new file, or dropped to back to false when a writer
   //        fails to open an output stream.
   bool mHasData : 1;
+  // The indication of pinning this entry was open with
+  bool mPinned : 1;
+  // Whether the pinning state of the entry is known (equals to the actual state
+  // of the cache file)
+  bool mPinningKnown : 1;
+
+  // Set when entry is doomed with AsyncDoom() or DoomAlreadyRemoved().
+  // Left as a standalone flag to not bother with locking (there is no need).
+  bool mIsDoomed;
 
   static char const * StateString(uint32_t aState);
 
   enum EState {      // transiting to:
     NOTLOADED = 0,   // -> LOADING | EMPTY
     LOADING = 1,     // -> EMPTY | READY
     EMPTY = 2,       // -> WRITING
     WRITING = 3,     // -> EMPTY | READY
--- a/netwerk/cache2/CacheFile.cpp
+++ b/netwerk/cache2/CacheFile.cpp
@@ -97,17 +97,17 @@ public:
     mCallback->OnChunkAvailable(mRV, mChunkIdx, mChunk);
     return NS_OK;
   }
 
 protected:
   nsCOMPtr<CacheFileChunkListener> mCallback;
   nsresult                         mRV;
   uint32_t                         mChunkIdx;
-  RefPtr<CacheFileChunk>         mChunk;
+  RefPtr<CacheFileChunk>           mChunk;
 };
 
 
 class DoomFileHelper : public CacheFileIOListener
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
@@ -180,16 +180,17 @@ NS_INTERFACE_MAP_END_THREADSAFE
 
 CacheFile::CacheFile()
   : mLock("CacheFile.mLock")
   , mOpeningFile(false)
   , mReady(false)
   , mMemoryOnly(false)
   , mSkipSizeCheck(false)
   , mOpenAsMemoryOnly(false)
+  , mPinned(false)
   , mPriority(false)
   , mDataAccessed(false)
   , mDataIsDirty(false)
   , mWritingMetadata(false)
   , mPreloadWithoutInputStreams(true)
   , mPreloadChunkCount(0)
   , mStatus(NS_OK)
   , mDataSize(-1)
@@ -210,27 +211,31 @@ CacheFile::~CacheFile()
 }
 
 nsresult
 CacheFile::Init(const nsACString &aKey,
                 bool aCreateNew,
                 bool aMemoryOnly,
                 bool aSkipSizeCheck,
                 bool aPriority,
+                bool aPinned,
                 CacheFileListener *aCallback)
 {
   MOZ_ASSERT(!mListener);
   MOZ_ASSERT(!mHandle);
 
+  MOZ_ASSERT(!(aMemoryOnly && aPinned));
+
   nsresult rv;
 
   mKey = aKey;
   mOpenAsMemoryOnly = mMemoryOnly = aMemoryOnly;
   mSkipSizeCheck = aSkipSizeCheck;
   mPriority = aPriority;
+  mPinned = aPinned;
 
   // Some consumers (at least nsHTTPCompressConv) assume that Read() can read
   // such amount of data that was announced by Available().
   // CacheFileInputStream::Available() uses also preloaded chunks to compute
   // number of available bytes in the input stream, so we have to make sure the
   // preloadChunkCount won't change during CacheFile's lifetime since otherwise
   // we could potentially release some cached chunks that was used to calculate
   // available bytes but would not be available later during call to
@@ -239,62 +244,72 @@ CacheFile::Init(const nsACString &aKey,
 
   LOG(("CacheFile::Init() [this=%p, key=%s, createNew=%d, memoryOnly=%d, "
        "priority=%d, listener=%p]", this, mKey.get(), aCreateNew, aMemoryOnly,
        aPriority, aCallback));
 
   if (mMemoryOnly) {
     MOZ_ASSERT(!aCallback);
 
-    mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
+    mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, false, mKey);
     mReady = true;
     mDataSize = mMetadata->Offset();
     return NS_OK;
   }
   else {
     uint32_t flags;
     if (aCreateNew) {
       MOZ_ASSERT(!aCallback);
       flags = CacheFileIOManager::CREATE_NEW;
 
       // make sure we can use this entry immediately
-      mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
+      mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey);
       mReady = true;
       mDataSize = mMetadata->Offset();
     } else {
       flags = CacheFileIOManager::CREATE;
     }
 
     if (mPriority) {
       flags |= CacheFileIOManager::PRIORITY;
     }
 
+    if (mPinned) {
+      flags |= CacheFileIOManager::PINNED;
+    }
+
     mOpeningFile = true;
     mListener = aCallback;
     rv = CacheFileIOManager::OpenFile(mKey, flags, this);
     if (NS_FAILED(rv)) {
       mListener = nullptr;
       mOpeningFile = false;
 
+      if (mPinned) {
+        LOG(("CacheFile::Init() - CacheFileIOManager::OpenFile() failed "
+             "but we want to pin, fail the file opening. [this=%p]", this));
+        return NS_ERROR_NOT_AVAILABLE;
+      }
+
       if (aCreateNew) {
         NS_WARNING("Forcing memory-only entry since OpenFile failed");
         LOG(("CacheFile::Init() - CacheFileIOManager::OpenFile() failed "
              "synchronously. We can continue in memory-only mode since "
              "aCreateNew == true. [this=%p]", this));
 
         mMemoryOnly = true;
       }
       else if (rv == NS_ERROR_NOT_INITIALIZED) {
         NS_WARNING("Forcing memory-only entry since CacheIOManager isn't "
                    "initialized.");
         LOG(("CacheFile::Init() - CacheFileIOManager isn't initialized, "
              "initializing entry as memory-only. [this=%p]", this));
 
         mMemoryOnly = true;
-        mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
+        mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey);
         mReady = true;
         mDataSize = mMetadata->Offset();
 
         RefPtr<NotifyCacheFileListenerEvent> ev;
         ev = new NotifyCacheFileListenerEvent(aCallback, NS_OK, true);
         rv = NS_DispatchToCurrentThread(ev);
         NS_ENSURE_SUCCESS(rv, rv);
       }
@@ -477,54 +492,54 @@ CacheFile::OnFileOpened(CacheFileHandle 
     if (mMemoryOnly) {
       // We can be here only in case the entry was initilized as createNew and
       // SetMemoryOnly() was called.
 
       // Just don't store the handle into mHandle and exit
       autoDoom.mAlreadyDoomed = true;
       return NS_OK;
     }
-    else if (NS_FAILED(aResult)) {
+
+    if (NS_FAILED(aResult)) {
       if (mMetadata) {
         // This entry was initialized as createNew, just switch to memory-only
         // mode.
         NS_WARNING("Forcing memory-only entry since OpenFile failed");
         LOG(("CacheFile::OnFileOpened() - CacheFileIOManager::OpenFile() "
              "failed asynchronously. We can continue in memory-only mode since "
              "aCreateNew == true. [this=%p]", this));
 
         mMemoryOnly = true;
         return NS_OK;
       }
-      else if (aResult == NS_ERROR_FILE_INVALID_PATH) {
+
+      if (aResult == NS_ERROR_FILE_INVALID_PATH) {
         // CacheFileIOManager doesn't have mCacheDirectory, switch to
         // memory-only mode.
         NS_WARNING("Forcing memory-only entry since CacheFileIOManager doesn't "
                    "have mCacheDirectory.");
         LOG(("CacheFile::OnFileOpened() - CacheFileIOManager doesn't have "
              "mCacheDirectory, initializing entry as memory-only. [this=%p]",
              this));
 
         mMemoryOnly = true;
-        mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mKey);
+        mMetadata = new CacheFileMetadata(mOpenAsMemoryOnly, mPinned, mKey);
         mReady = true;
         mDataSize = mMetadata->Offset();
 
         isNew = true;
         retval = NS_OK;
-      }
-      else {
+      } else {
         // CacheFileIOManager::OpenFile() failed for another reason.
         isNew = false;
         retval = aResult;
       }
 
       mListener.swap(listener);
-    }
-    else {
+    } else {
       mHandle = aHandle;
       if (NS_FAILED(mStatus)) {
         CacheFileIOManager::DoomFile(mHandle, nullptr);
       }
 
       if (mMetadata) {
         InitIndexEntry();
 
@@ -578,16 +593,17 @@ nsresult
 CacheFile::OnMetadataRead(nsresult aResult)
 {
   MOZ_ASSERT(mListener);
 
   LOG(("CacheFile::OnMetadataRead() [this=%p, rv=0x%08x]", this, aResult));
 
   bool isNew = false;
   if (NS_SUCCEEDED(aResult)) {
+    mPinned = mMetadata->Pinned();
     mReady = true;
     mDataSize = mMetadata->Offset();
     if (mDataSize == 0 && mMetadata->ElementsSize() == 0) {
       isNew = true;
       mMetadata->MarkDirty();
     } else {
       CacheFileAutoLock lock(this);
       PreloadChunks(0);
@@ -1965,17 +1981,18 @@ CacheFile::InitIndexEntry()
     return NS_OK;
 
   nsresult rv;
 
   // Bug 1201042 - will pass OriginAttributes directly.
   rv = CacheFileIOManager::InitIndexEntry(mHandle,
                                           mMetadata->OriginAttributes().mAppId,
                                           mMetadata->IsAnonymous(),
-                                          mMetadata->OriginAttributes().mInBrowser);
+                                          mMetadata->OriginAttributes().mInBrowser,
+                                          mPinned);
   NS_ENSURE_SUCCESS(rv, rv);
 
   uint32_t expTime;
   mMetadata->GetExpirationTime(&expTime);
 
   uint32_t frecency;
   mMetadata->GetFrecency(&frecency);
 
--- a/netwerk/cache2/CacheFile.h
+++ b/netwerk/cache2/CacheFile.h
@@ -53,16 +53,17 @@ public:
 
   CacheFile();
 
   nsresult Init(const nsACString &aKey,
                 bool aCreateNew,
                 bool aMemoryOnly,
                 bool aSkipSizeCheck,
                 bool aPriority,
+                bool aPinned,
                 CacheFileListener *aCallback);
 
   NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk) override;
   NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk) override;
   NS_IMETHOD OnChunkAvailable(nsresult aResult, uint32_t aChunkIdx,
                               CacheFileChunk *aChunk) override;
   NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk) override;
 
@@ -98,16 +99,17 @@ public:
   nsresult GetFetchCount(uint32_t *_retval);
   // Called by upper layers to indicated the entry has been fetched,
   // i.e. delivered to the consumer.
   nsresult OnFetched();
 
   bool DataSize(int64_t* aSize);
   void Key(nsACString& aKey) { aKey = mKey; }
   bool IsDoomed();
+  bool IsPinned() const { return mPinned; }
   bool IsWriteInProgress();
 
   // Memory reporting
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
 private:
   friend class CacheFileIOManager;
@@ -191,28 +193,29 @@ private:
   nsresult InitIndexEntry();
 
   mozilla::Mutex mLock;
   bool           mOpeningFile;
   bool           mReady;
   bool           mMemoryOnly;
   bool           mSkipSizeCheck;
   bool           mOpenAsMemoryOnly;
+  bool           mPinned;
   bool           mPriority;
   bool           mDataAccessed;
   bool           mDataIsDirty;
   bool           mWritingMetadata;
   bool           mPreloadWithoutInputStreams;
   uint32_t       mPreloadChunkCount;
   nsresult       mStatus;
   int64_t        mDataSize;
   nsCString      mKey;
 
-  RefPtr<CacheFileHandle>    mHandle;
-  RefPtr<CacheFileMetadata>  mMetadata;
+  RefPtr<CacheFileHandle>      mHandle;
+  RefPtr<CacheFileMetadata>    mMetadata;
   nsCOMPtr<CacheFileListener>  mListener;
   nsCOMPtr<CacheFileIOListener>   mDoomAfterOpenListener;
 
   nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mChunks;
   nsClassHashtable<nsUint32HashKey, ChunkListeners> mChunkListeners;
   nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mCachedChunks;
 
   nsTArray<CacheFileInputStream*> mInputs;
--- a/netwerk/cache2/CacheFileChunk.cpp
+++ b/netwerk/cache2/CacheFileChunk.cpp
@@ -39,17 +39,17 @@ public:
     LOG(("NotifyUpdateListenerEvent::Run() [this=%p]", this));
 
     mCallback->OnChunkUpdated(mChunk);
     return NS_OK;
   }
 
 protected:
   nsCOMPtr<CacheFileChunkListener> mCallback;
-  RefPtr<CacheFileChunk>         mChunk;
+  RefPtr<CacheFileChunk>           mChunk;
 };
 
 bool
 CacheFileChunk::DispatchRelease()
 {
   if (NS_IsMainThread()) {
     return false;
   }
--- a/netwerk/cache2/CacheFileChunk.h
+++ b/netwerk/cache2/CacheFileChunk.h
@@ -145,17 +145,17 @@ private:
 
   char    *mBuf;
   uint32_t mBufSize;
 
   char               *mRWBuf;
   uint32_t            mRWBufSize;
   CacheHash::Hash16_t mReadHash;
 
-  RefPtr<CacheFile>              mFile; // is null if chunk is cached to
+  RefPtr<CacheFile>                mFile; // is null if chunk is cached to
                                           // prevent reference cycles
   nsCOMPtr<CacheFileChunkListener> mListener;
   nsTArray<ChunkListenerItem *>    mUpdateListeners;
   CacheFileUtils::ValidityMap      mValidityMap;
 };
 
 
 } // namespace net
--- a/netwerk/cache2/CacheFileContextEvictor.cpp
+++ b/netwerk/cache2/CacheFileContextEvictor.cpp
@@ -76,42 +76,59 @@ uint32_t
 CacheFileContextEvictor::ContextsCount()
 {
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   return mEntries.Length();
 }
 
 nsresult
-CacheFileContextEvictor::AddContext(nsILoadContextInfo *aLoadContextInfo)
+CacheFileContextEvictor::AddContext(nsILoadContextInfo *aLoadContextInfo,
+                                    bool aPinned)
 {
-  LOG(("CacheFileContextEvictor::AddContext() [this=%p, loadContextInfo=%p]",
-       this, aLoadContextInfo));
+  LOG(("CacheFileContextEvictor::AddContext() [this=%p, loadContextInfo=%p, pinned=%d]",
+       this, aLoadContextInfo, aPinned));
 
   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 (aLoadContextInfo) {
+    for (uint32_t i = 0; i < mEntries.Length(); ++i) {
+      if (mEntries[i]->mInfo &&
+          mEntries[i]->mInfo->Equals(aLoadContextInfo) &&
+          mEntries[i]->mPinned == aPinned) {
+        entry = mEntries[i];
+        break;
+      }
+    }
+  } else {
+    // Not providing load context info means we want to delete everything,
+    // so let's not bother with any currently running context cleanups
+    // for the same pinning state.
+    for (uint32_t i = mEntries.Length(); i > 0;) {
+      --i;
+      if (mEntries[i]->mInfo && mEntries[i]->mPinned == aPinned) {
+        RemoveEvictInfoFromDisk(mEntries[i]->mInfo, mEntries[i]->mPinned);
+        mEntries.RemoveElementAt(i);
+      }
     }
   }
 
   if (!entry) {
     entry = new CacheFileContextEvictorEntry();
     entry->mInfo = aLoadContextInfo;
+    entry->mPinned = aPinned;
     mEntries.AppendElement(entry);
   }
 
   entry->mTimeStamp = PR_Now() / PR_USEC_PER_MSEC;
 
-  PersistEvictionInfoToDisk(aLoadContextInfo);
+  PersistEvictionInfoToDisk(aLoadContextInfo, aPinned);
 
   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;
     }
@@ -175,78 +192,82 @@ CacheFileContextEvictor::CacheIndexState
     CloseIterators();
   }
 
   return NS_OK;
 }
 
 nsresult
 CacheFileContextEvictor::WasEvicted(const nsACString &aKey, nsIFile *aFile,
-                                    bool *_retval)
+                                    bool *aEvictedAsPinned, bool *aEvictedAsNonPinned)
 {
   LOG(("CacheFileContextEvictor::WasEvicted() [key=%s]",
        PromiseFlatCString(aKey).get()));
 
   nsresult rv;
 
+  *aEvictedAsPinned = false;
+  *aEvictedAsNonPinned = false;
+
   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;
+    CacheFileContextEvictorEntry *entry = mEntries[i];
+
+    if (entry->mInfo && !info->Equals(entry->mInfo)) {
+      continue;
+    }
+
+    PRTime lastModifiedTime;
+    rv = aFile->GetLastModifiedTime(&lastModifiedTime);
+    if (NS_FAILED(rv)) {
+      LOG(("CacheFileContextEvictor::WasEvicted() - Cannot get last modified time"
+            ", returning false."));
+      return NS_OK;
+    }
+
+    if (lastModifiedTime > entry->mTimeStamp) {
+      // File has been modified since context eviction.
+      continue;
+    }
+
+    LOG(("CacheFileContextEvictor::WasEvicted() - evicted [pinning=%d, "
+         "mTimeStamp=%lld, lastModifiedTime=%lld]",
+         entry->mPinned, entry->mTimeStamp, lastModifiedTime));
+
+    if (entry->mPinned) {
+      *aEvictedAsPinned = true;
+    } else {
+      *aEvictedAsNonPinned = true;
     }
   }
 
-  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)
+  nsILoadContextInfo *aLoadContextInfo, bool aPinned)
 {
   LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() [this=%p, "
        "loadContextInfo=%p]", this, aLoadContextInfo));
 
   nsresult rv;
 
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   nsCOMPtr<nsIFile> file;
-  rv = GetContextFile(aLoadContextInfo, getter_AddRefs(file));
+  rv = GetContextFile(aLoadContextInfo, aPinned, getter_AddRefs(file));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsAutoCString path;
   file->GetNativePath(path);
 
   PRFileDesc *fd;
@@ -263,27 +284,27 @@ CacheFileContextEvictor::PersistEviction
   LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() - Successfully "
        "created file. [path=%s]", path.get()));
 
   return NS_OK;
 }
 
 nsresult
 CacheFileContextEvictor::RemoveEvictInfoFromDisk(
-  nsILoadContextInfo *aLoadContextInfo)
+  nsILoadContextInfo *aLoadContextInfo, bool aPinned)
 {
   LOG(("CacheFileContextEvictor::RemoveEvictInfoFromDisk() [this=%p, "
        "loadContextInfo=%p]", this, aLoadContextInfo));
 
   nsresult rv;
 
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   nsCOMPtr<nsIFile> file;
-  rv = GetContextFile(aLoadContextInfo, getter_AddRefs(file));
+  rv = GetContextFile(aLoadContextInfo, aPinned, getter_AddRefs(file));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsAutoCString path;
   file->GetNativePath(path);
 
   rv = file->Remove(false);
@@ -358,52 +379,73 @@ CacheFileContextEvictor::LoadEvictInfoFr
     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);
+    bool pinned = decoded[0] == '\t';
+    if (pinned) {
+      decoded = Substring(decoded, 1);
+    }
 
-    if (!info) {
-      LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() - Cannot parse "
-           "context key, removing file. [contextKey=%s, file=%s]",
-           decoded.get(), leaf.get()));
-      file->Remove(false);
-      continue;
+    nsCOMPtr<nsILoadContextInfo> info;
+    if (!NS_LITERAL_CSTRING("*").Equals(decoded)) {
+      // "*" is indication of 'delete all', info left null will pass
+      // to CacheFileContextEvictor::AddContext and clear all the cache data.
+      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->mPinned = pinned;
     entry->mTimeStamp = lastModifiedTime;
     mEntries.AppendElement(entry);
   }
 
   return NS_OK;
 }
 
 nsresult
 CacheFileContextEvictor::GetContextFile(nsILoadContextInfo *aLoadContextInfo,
+                                        bool aPinned,
                                         nsIFile **_retval)
 {
   nsresult rv;
 
   nsAutoCString leafName;
   leafName.AssignLiteral(CONTEXT_EVICTION_PREFIX);
 
   nsAutoCString keyPrefix;
-  CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, keyPrefix);
+  if (aPinned) {
+    // Mark pinned context files with a tab char at the start.
+    // Tab is chosen because it can never be used as a context key tag.
+    keyPrefix.Append('\t');
+  }
+  if (aLoadContextInfo) {
+    CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, keyPrefix);
+  } else {
+    keyPrefix.Append('*');
+  }
 
   nsAutoCString data64;
   rv = Base64Encode(keyPrefix, data64);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Replace '/' with '-' since '/' cannot be part of the filename.
@@ -525,17 +567,17 @@ CacheFileContextEvictor::EvictEntries()
     }
 
     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);
+      RemoveEvictInfoFromDisk(mEntries[0]->mInfo, mEntries[0]->mPinned);
       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);
@@ -552,16 +594,30 @@ CacheFileContextEvictor::EvictEntries()
     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;
     }
 
+    CacheIndex::EntryStatus status;
+    bool pinned;
+    rv = CacheIndex::HasEntry(hash, &status, &pinned);
+    // This must never fail, since eviction (this code) happens only when the index
+    // is up-to-date and thus the informatin is known.
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    if (pinned != mEntries[0]->mPinned) {
+      LOG(("CacheFileContextEvictor::EvictEntries() - Skipping entry since pinning "
+           "doesn't match [evicting pinned=%d, entry pinned=%d]",
+           mEntries[0]->mPinned, pinned));
+      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);
--- a/netwerk/cache2/CacheFileContextEvictor.h
+++ b/netwerk/cache2/CacheFileContextEvictor.h
@@ -15,16 +15,17 @@ class nsILoadContextInfo;
 namespace mozilla {
 namespace net {
 
 class CacheIndexIterator;
 
 struct CacheFileContextEvictorEntry
 {
   nsCOMPtr<nsILoadContextInfo> mInfo;
+  bool                         mPinned;
   PRTime                       mTimeStamp; // in milliseconds
   RefPtr<CacheIndexIterator> mIterator;
 };
 
 class CacheFileContextEvictor
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheFileContextEvictor)
@@ -35,41 +36,42 @@ private:
   virtual ~CacheFileContextEvictor();
 
 public:
   nsresult Init(nsIFile *aCacheDirectory);
 
   // Returns number of contexts that are being evicted.
   uint32_t ContextsCount();
   // Start evicting given context.
-  nsresult AddContext(nsILoadContextInfo *aLoadContextInfo);
+  nsresult AddContext(nsILoadContextInfo *aLoadContextInfo, bool aPinned);
   // 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);
+  nsresult WasEvicted(const nsACString &aKey, nsIFile *aFile,
+                      bool *aEvictedAsPinned, bool *aEvictedAsNonPinned);
 
 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);
+  nsresult PersistEvictionInfoToDisk(nsILoadContextInfo *aLoadContextInfo, bool aPinned);
   // Once we are done with eviction for the given context, the eviction info is
   // removed from the disk.
-  nsresult RemoveEvictInfoFromDisk(nsILoadContextInfo *aLoadContextInfo);
+  nsresult RemoveEvictInfoFromDisk(nsILoadContextInfo *aLoadContextInfo, bool aPinned);
   // Tries to load all contexts from the disk. This method is called just once
   // after startup.
   nsresult LoadEvictInfoFromDisk();
-  nsresult GetContextFile(nsILoadContextInfo *aLoadContextInfo,
+  nsresult GetContextFile(nsILoadContextInfo *aLoadContextInfo, bool aPinned,
                           nsIFile **_retval);
 
   void     CreateIterators();
   void     CloseIterators();
   void     StartEvicting();
   nsresult EvictEntries();
 
   // Whether eviction is in progress
--- a/netwerk/cache2/CacheFileIOManager.cpp
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -103,42 +103,48 @@ CacheFileHandle::Release()
 
   return count;
 }
 
 NS_INTERFACE_MAP_BEGIN(CacheFileHandle)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END_THREADSAFE
 
-CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority)
+CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority, PinningStatus aPinning)
   : mHash(aHash)
   , mPriority(aPriority)
   , mClosed(false)
   , mSpecialFile(false)
   , mInvalid(false)
   , mFileExists(false)
+  , mPinning(aPinning)
+  , mDoomWhenFoundPinned(false)
+  , mDoomWhenFoundNonPinned(false)
   , mFileSize(-1)
   , mFD(nullptr)
 {
   // If we initialize mDoomed in the initialization list, that initialization is
   // not guaranteeded to be atomic.  Whereas this assignment here is guaranteed
   // to be atomic.  TSan will see this (atomic) assignment and be satisfied
   // that cross-thread accesses to mIsDoomed are properly synchronized.
   mIsDoomed = false;
   LOG(("CacheFileHandle::CacheFileHandle() [this=%p, hash=%08x%08x%08x%08x%08x]"
        , this, LOGSHA1(aHash)));
 }
 
-CacheFileHandle::CacheFileHandle(const nsACString &aKey, bool aPriority)
+CacheFileHandle::CacheFileHandle(const nsACString &aKey, bool aPriority, PinningStatus aPinning)
   : mHash(nullptr)
   , mPriority(aPriority)
   , mClosed(false)
   , mSpecialFile(true)
   , mInvalid(false)
   , mFileExists(false)
+  , mPinning(aPinning)
+  , mDoomWhenFoundPinned(false)
+  , mDoomWhenFoundNonPinned(false)
   , mFileSize(-1)
   , mFD(nullptr)
   , mKey(aKey)
 {
   // See comment above about the initialization of mIsDoomed.
   mIsDoomed = false;
   LOG(("CacheFileHandle::CacheFileHandle() [this=%p, key=%s]", this,
        PromiseFlatCString(aKey).get()));
@@ -194,16 +200,42 @@ CacheFileHandle::FileSizeInK() const
     size = PR_UINT32_MAX;
   } else {
     size = static_cast<uint32_t>(size64);
   }
 
   return size;
 }
 
+bool
+CacheFileHandle::SetPinned(bool aPinned)
+{
+  LOG(("CacheFileHandle::SetPinned [this=%p, pinned=%d]", this, aPinned));
+
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
+  mPinning = aPinned
+    ? PinningStatus::PINNED
+    : PinningStatus::NON_PINNED;
+
+  if ((MOZ_UNLIKELY(mDoomWhenFoundPinned) && aPinned) ||
+      (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) && !aPinned)) {
+
+    LOG(("  dooming, when: pinned=%d, non-pinned=%d, found: pinned=%d",
+      bool(mDoomWhenFoundPinned), bool(mDoomWhenFoundNonPinned), aPinned));
+
+    mDoomWhenFoundPinned = false;
+    mDoomWhenFoundNonPinned = false;
+
+    return false;
+  }
+
+  return true;
+}
+
 // Memory reporting
 
 size_t
 CacheFileHandle::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
 {
   size_t n = 0;
   nsCOMPtr<nsISizeOf> sizeOf;
 
@@ -355,17 +387,17 @@ CacheFileHandles::GetHandle(const SHA1Su
 
   handle.forget(_retval);
   return NS_OK;
 }
 
 
 nsresult
 CacheFileHandles::NewHandle(const SHA1Sum::Hash *aHash,
-                            bool aPriority,
+                            bool aPriority, CacheFileHandle::PinningStatus aPinning,
                             CacheFileHandle **_retval)
 {
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
   MOZ_ASSERT(aHash);
 
 #ifdef DEBUG_HANDLES
   LOG(("CacheFileHandles::NewHandle() [hash=%08x%08x%08x%08x%08x]", LOGSHA1(aHash)));
 #endif
@@ -376,17 +408,17 @@ CacheFileHandles::NewHandle(const SHA1Su
 #ifdef DEBUG_HANDLES
   Log(entry);
 #endif
 
 #ifdef DEBUG
   entry->AssertHandlesState();
 #endif
 
-  RefPtr<CacheFileHandle> handle = new CacheFileHandle(entry->Hash(), aPriority);
+  RefPtr<CacheFileHandle> handle = new CacheFileHandle(entry->Hash(), aPriority, aPinning);
   entry->AddHandle(handle);
 
   LOG(("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x "
        "created new handle %p, entry=%p", LOGSHA1(aHash), handle.get(), entry));
 
   handle.forget(_retval);
   return NS_OK;
 }
@@ -571,27 +603,27 @@ public:
       }
       mIOMan = nullptr;
       if (mHandle) {
         if (mHandle->Key().IsEmpty()) {
           mHandle->Key() = mKey;
         }
       }
     }
+
     mCallback->OnFileOpened(mHandle, rv);
-
     return NS_OK;
   }
 
 protected:
   SHA1Sum::Hash                 mHash;
   uint32_t                      mFlags;
   nsCOMPtr<CacheFileIOListener> mCallback;
-  RefPtr<CacheFileIOManager>  mIOMan;
-  RefPtr<CacheFileHandle>     mHandle;
+  RefPtr<CacheFileIOManager>    mIOMan;
+  RefPtr<CacheFileHandle>       mHandle;
   nsCString                     mKey;
 };
 
 class ReadEvent : public nsRunnable {
 public:
   ReadEvent(CacheFileHandle *aHandle, int64_t aOffset, char *aBuf,
             int32_t aCount, CacheFileIOListener *aCallback)
     : mHandle(aHandle)
@@ -621,17 +653,17 @@ public:
         mHandle, mOffset, mBuf, mCount);
     }
 
     mCallback->OnDataRead(mHandle, mBuf, rv);
     return NS_OK;
   }
 
 protected:
-  RefPtr<CacheFileHandle>     mHandle;
+  RefPtr<CacheFileHandle>       mHandle;
   int64_t                       mOffset;
   char                         *mBuf;
   int32_t                       mCount;
   nsCOMPtr<CacheFileIOListener> mCallback;
 };
 
 class WriteEvent : public nsRunnable {
 public:
@@ -680,17 +712,17 @@ public:
       free(const_cast<char *>(mBuf));
       mBuf = nullptr;
     }
 
     return NS_OK;
   }
 
 protected:
-  RefPtr<CacheFileHandle>     mHandle;
+  RefPtr<CacheFileHandle>       mHandle;
   int64_t                       mOffset;
   const char                   *mBuf;
   int32_t                       mCount;
   bool                          mValidate : 1;
   bool                          mTruncate : 1;
   nsCOMPtr<CacheFileIOListener> mCallback;
 };
 
@@ -724,19 +756,19 @@ public:
     if (mCallback) {
       mCallback->OnFileDoomed(mHandle, rv);
     }
 
     return NS_OK;
   }
 
 protected:
-  nsCOMPtr<CacheFileIOListener> mCallback;
-  nsCOMPtr<nsIEventTarget>      mTarget;
-  RefPtr<CacheFileHandle>     mHandle;
+  nsCOMPtr<CacheFileIOListener>              mCallback;
+  nsCOMPtr<nsIEventTarget>                   mTarget;
+  RefPtr<CacheFileHandle>                    mHandle;
 };
 
 class DoomFileByKeyEvent : public nsRunnable {
 public:
   DoomFileByKeyEvent(const nsACString &aKey,
                      CacheFileIOListener *aCallback)
     : mCallback(aCallback)
   {
@@ -772,17 +804,17 @@ public:
     }
 
     return NS_OK;
   }
 
 protected:
   SHA1Sum::Hash                 mHash;
   nsCOMPtr<CacheFileIOListener> mCallback;
-  RefPtr<CacheFileIOManager>  mIOMan;
+  RefPtr<CacheFileIOManager>    mIOMan;
 };
 
 class ReleaseNSPRHandleEvent : public nsRunnable {
 public:
   explicit ReleaseNSPRHandleEvent(CacheFileHandle *aHandle)
     : mHandle(aHandle)
   {
     MOZ_COUNT_CTOR(ReleaseNSPRHandleEvent);
@@ -800,17 +832,17 @@ public:
     if (mHandle->mFD && !mHandle->IsClosed()) {
       CacheFileIOManager::gInstance->ReleaseNSPRHandleInternal(mHandle);
     }
 
     return NS_OK;
   }
 
 protected:
-  RefPtr<CacheFileHandle>     mHandle;
+  RefPtr<CacheFileHandle>       mHandle;
 };
 
 class TruncateSeekSetEOFEvent : public nsRunnable {
 public:
   TruncateSeekSetEOFEvent(CacheFileHandle *aHandle, int64_t aTruncatePos,
                           int64_t aEOFPos, CacheFileIOListener *aCallback)
     : mHandle(aHandle)
     , mTruncatePos(aTruncatePos)
@@ -841,17 +873,17 @@ public:
     if (mCallback) {
       mCallback->OnEOFSet(mHandle, rv);
     }
 
     return NS_OK;
   }
 
 protected:
-  RefPtr<CacheFileHandle>     mHandle;
+  RefPtr<CacheFileHandle>       mHandle;
   int64_t                       mTruncatePos;
   int64_t                       mEOFPos;
   nsCOMPtr<CacheFileIOListener> mCallback;
 };
 
 class RenameFileEvent : public nsRunnable {
 public:
   RenameFileEvent(CacheFileHandle *aHandle, const nsACString &aNewName,
@@ -884,29 +916,30 @@ public:
     if (mCallback) {
       mCallback->OnFileRenamed(mHandle, rv);
     }
 
     return NS_OK;
   }
 
 protected:
-  RefPtr<CacheFileHandle>     mHandle;
+  RefPtr<CacheFileHandle>       mHandle;
   nsCString                     mNewName;
   nsCOMPtr<CacheFileIOListener> mCallback;
 };
 
 class InitIndexEntryEvent : public nsRunnable {
 public:
   InitIndexEntryEvent(CacheFileHandle *aHandle, uint32_t aAppId,
-                      bool aAnonymous, bool aInBrowser)
+                      bool aAnonymous, bool aInBrowser, bool aPinning)
     : mHandle(aHandle)
     , mAppId(aAppId)
     , mAnonymous(aAnonymous)
     , mInBrowser(aInBrowser)
+    , mPinning(aPinning)
   {
     MOZ_COUNT_CTOR(InitIndexEntryEvent);
   }
 
 protected:
   ~InitIndexEntryEvent()
   {
     MOZ_COUNT_DTOR(InitIndexEntryEvent);
@@ -914,33 +947,34 @@ protected:
 
 public:
   NS_IMETHOD Run()
   {
     if (mHandle->IsClosed() || mHandle->IsDoomed()) {
       return NS_OK;
     }
 
-    CacheIndex::InitEntry(mHandle->Hash(), mAppId, mAnonymous, mInBrowser);
+    CacheIndex::InitEntry(mHandle->Hash(), mAppId, mAnonymous, mInBrowser, mPinning);
 
     // We cannot set the filesize before we init the entry. If we're opening
     // an existing entry file, frecency and expiration time will be set after
     // parsing the entry file, but we must set the filesize here since nobody is
     // going to set it if there is no write to the file.
     uint32_t sizeInK = mHandle->FileSizeInK();
     CacheIndex::UpdateEntry(mHandle->Hash(), nullptr, nullptr, &sizeInK);
 
     return NS_OK;
   }
 
 protected:
   RefPtr<CacheFileHandle> mHandle;
   uint32_t                  mAppId;
   bool                      mAnonymous;
   bool                      mInBrowser;
+  bool                      mPinning;
 };
 
 class UpdateIndexEntryEvent : public nsRunnable {
 public:
   UpdateIndexEntryEvent(CacheFileHandle *aHandle, const uint32_t *aFrecency,
                         const uint32_t *aExpirationTime)
     : mHandle(aHandle)
     , mHasFrecency(false)
@@ -1518,31 +1552,35 @@ CacheFileIOManager::OpenFileInternal(con
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (!mTreeCreated) {
     rv = CreateCacheTree();
     if (NS_FAILED(rv)) return rv;
   }
 
+  CacheFileHandle::PinningStatus pinning = aFlags & PINNED
+    ? CacheFileHandle::PinningStatus::PINNED
+    : CacheFileHandle::PinningStatus::NON_PINNED;
+
   nsCOMPtr<nsIFile> file;
   rv = GetFile(aHash, getter_AddRefs(file));
   NS_ENSURE_SUCCESS(rv, rv);
 
   RefPtr<CacheFileHandle> handle;
   mHandles.GetHandle(aHash, getter_AddRefs(handle));
 
   if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
     if (handle) {
       rv = DoomFileInternal(handle);
       NS_ENSURE_SUCCESS(rv, rv);
       handle = nullptr;
     }
 
-    rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
+    rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning, getter_AddRefs(handle));
     NS_ENSURE_SUCCESS(rv, rv);
 
     bool exists;
     rv = file->Exists(&exists);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (exists) {
       CacheIndex::RemoveEntry(aHash);
@@ -1562,44 +1600,55 @@ CacheFileIOManager::OpenFileInternal(con
     handle->mFileSize = 0;
   }
 
   if (handle) {
     handle.swap(*_retval);
     return NS_OK;
   }
 
-  bool exists;
+  bool exists, evictedAsPinned = false, evictedAsNonPinned = false;
   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);
-      }
+      mContextEvictor->WasEvicted(aKey, file, &evictedAsPinned, &evictedAsNonPinned);
     }
   }
 
   if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
+  if (exists) {
+    // For existing files we determine the pinning status later, after the metadata gets parsed.
+    pinning = CacheFileHandle::PinningStatus::UNKNOWN;
+  }
+
+  rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, pinning, getter_AddRefs(handle));
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (exists) {
+    // If this file has been found evicted through the context file evictor above for
+    // any of pinned or non-pinned state, these calls ensure we doom the handle ASAP
+    // we know the real pinning state after metadta has been parsed.  DoomFileInternal
+    // on the |handle| doesn't doom right now, since the pinning state is unknown
+    // and we pass down a pinning restriction.
+    if (evictedAsPinned) {
+      rv = DoomFileInternal(handle, DOOM_WHEN_PINNED);
+      MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv));
+    }
+    if (evictedAsNonPinned) {
+      rv = DoomFileInternal(handle, DOOM_WHEN_NON_PINNED);
+      MOZ_ASSERT(!handle->IsDoomed() && NS_SUCCEEDED(rv));
+    }
+
     rv = file->GetFileSize(&handle->mFileSize);
     NS_ENSURE_SUCCESS(rv, rv);
 
     handle->mFileExists = true;
 
     CacheIndex::EnsureEntryExists(aHash);
   } else {
     handle->mFileSize = 0;
@@ -1647,17 +1696,17 @@ CacheFileIOManager::OpenSpecialFileInter
 
   if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
     if (handle) {
       rv = DoomFileInternal(handle);
       NS_ENSURE_SUCCESS(rv, rv);
       handle = nullptr;
     }
 
-    handle = new CacheFileHandle(aKey, aFlags & PRIORITY);
+    handle = new CacheFileHandle(aKey, aFlags & PRIORITY, CacheFileHandle::PinningStatus::NON_PINNED);
     mSpecialHandles.AppendElement(handle);
 
     bool exists;
     rv = file->Exists(&exists);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (exists) {
       LOG(("CacheFileIOManager::OpenSpecialFileInternal() - Removing file from "
@@ -1682,17 +1731,17 @@ CacheFileIOManager::OpenSpecialFileInter
   bool exists;
   rv = file->Exists(&exists);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  handle = new CacheFileHandle(aKey, aFlags & PRIORITY);
+  handle = new CacheFileHandle(aKey, aFlags & PRIORITY, CacheFileHandle::PinningStatus::NON_PINNED);
   mSpecialHandles.AppendElement(handle);
 
   if (exists) {
     rv = file->GetFileSize(&handle->mFileSize);
     NS_ENSURE_SUCCESS(rv, rv);
 
     handle->mFileExists = true;
   } else {
@@ -1979,27 +2028,62 @@ CacheFileIOManager::DoomFile(CacheFileHa
     ? CacheIOThread::OPEN_PRIORITY
     : CacheIOThread::OPEN);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
-CacheFileIOManager::DoomFileInternal(CacheFileHandle *aHandle)
+CacheFileIOManager::DoomFileInternal(CacheFileHandle *aHandle,
+                                     PinningDoomRestriction aPinningDoomRestriction)
 {
   LOG(("CacheFileIOManager::DoomFileInternal() [handle=%p]", aHandle));
   aHandle->Log();
 
+  MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
+
   nsresult rv;
 
   if (aHandle->IsDoomed()) {
     return NS_OK;
   }
 
+  if (aPinningDoomRestriction > NO_RESTRICTION) {
+    switch (aHandle->mPinning) {
+    case CacheFileHandle::PinningStatus::NON_PINNED:
+      if (MOZ_LIKELY(aPinningDoomRestriction != DOOM_WHEN_NON_PINNED)) {
+        LOG(("  not dooming, it's a non-pinned handle"));
+        return NS_OK;
+      }
+      // Doom now
+      break;
+
+    case CacheFileHandle::PinningStatus::PINNED:
+      if (MOZ_UNLIKELY(aPinningDoomRestriction != DOOM_WHEN_PINNED)) {
+        LOG(("  not dooming, it's a pinned handle"));
+        return NS_OK;
+      }
+      // Doom now
+      break;
+
+    case CacheFileHandle::PinningStatus::UNKNOWN:
+      if (MOZ_LIKELY(aPinningDoomRestriction == DOOM_WHEN_NON_PINNED)) {
+        LOG(("  doom when non-pinned set"));
+        aHandle->mDoomWhenFoundNonPinned = true;
+      } else if (MOZ_UNLIKELY(aPinningDoomRestriction == DOOM_WHEN_PINNED)) {
+        LOG(("  doom when pinned set"));
+        aHandle->mDoomWhenFoundPinned = true;
+      }
+
+      LOG(("  pinning status not known, deferring doom decision"));
+      return NS_OK;
+    }
+  }
+
   if (aHandle->mFileExists) {
     // we need to move the current file to the doomed directory
     if (aHandle->mFD) {
       ReleaseNSPRHandleInternal(aHandle);
     }
 
     // find unused filename
     nsCOMPtr<nsIFile> file;
@@ -2779,59 +2863,70 @@ CacheFileIOManager::EvictAllInternal()
 
   CacheIndex::RemoveAll();
 
   return NS_OK;
 }
 
 // static
 nsresult
-CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo)
+CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo, bool aPinned)
 {
   LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]",
        aLoadContextInfo));
 
   nsresult rv;
   RefPtr<CacheFileIOManager> ioMan = gInstance;
 
   if (!ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   nsCOMPtr<nsIRunnable> ev;
-  ev = NS_NewRunnableMethodWithArg<nsCOMPtr<nsILoadContextInfo> >
-         (ioMan, &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo);
+  ev = NS_NewRunnableMethodWithArgs<nsCOMPtr<nsILoadContextInfo>, bool>
+         (ioMan, &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo, aPinned);
 
   rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 nsresult
-CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo)
+CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo, bool aPinned)
 {
-  nsAutoCString suffix;
-  aLoadContextInfo->OriginAttributesPtr()->CreateSuffix(suffix);
-  LOG(("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, "
-       "anonymous=%u, suffix=%s]", aLoadContextInfo, aLoadContextInfo->IsAnonymous(),
-       suffix.get()));
+  LOG(("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, pinned=%d]",
+      aLoadContextInfo, aPinned));
 
   nsresult rv;
 
-  MOZ_ASSERT(mIOThread->IsCurrentThread());
-
-  MOZ_ASSERT(!aLoadContextInfo->IsPrivate());
-  if (aLoadContextInfo->IsPrivate()) {
-    return NS_ERROR_INVALID_ARG;
+  if (aLoadContextInfo) {
+    nsAutoCString suffix;
+    aLoadContextInfo->OriginAttributesPtr()->CreateSuffix(suffix);
+    LOG(("  anonymous=%u, suffix=%s]", aLoadContextInfo->IsAnonymous(), suffix.get()));
+
+    MOZ_ASSERT(mIOThread->IsCurrentThread());
+
+    MOZ_ASSERT(!aLoadContextInfo->IsPrivate());
+    if (aLoadContextInfo->IsPrivate()) {
+      return NS_ERROR_INVALID_ARG;
+    }
   }
 
   if (!mCacheDirectory) {
+    // This is a kind of hack. Somebody called EvictAll() without a profile.
+    // This happens in xpcshell tests that use cache without profile. We need
+    // to notify observers in this case since the tests are waiting for it.
+    // Also notify for aPinned == true, those are interested as well.
+    if (!aLoadContextInfo) {
+      RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
+      NS_DispatchToMainThread(r);
+    }
     return NS_ERROR_FILE_INVALID_PATH;
   }
 
   if (mShuttingDown) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (!mTreeCreated) {
@@ -2841,42 +2936,56 @@ CacheFileIOManager::EvictByContextIntern
     }
   }
 
   // Doom all active handles that matches the load context
   nsTArray<RefPtr<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()));
+    CacheFileHandle* handle = handles[i];
+
+    if (aLoadContextInfo) {
+      bool equals;
+      rv = CacheFileUtils::KeyMatchesLoadContextInfo(handle->Key(),
+                                                     aLoadContextInfo,
+                                                     &equals);
+      if (NS_FAILED(rv)) {
+        LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot parse key in "
+             "handle! [handle=%p, key=%s]", handle, handle->Key().get()));
+        MOZ_CRASH("Unexpected error!");
+      }
+
+      if (!equals) {
+        continue;
       }
     }
+
+    // handle will be doomed only when pinning status is known and equal or
+    // doom decision will be deferred until pinning status is determined.
+    rv = DoomFileInternal(handle, aPinned
+                                  ? CacheFileIOManager::DOOM_WHEN_PINNED
+                                  : CacheFileIOManager::DOOM_WHEN_NON_PINNED);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot doom handle"
+            " [handle=%p]", handle));
+    }
+  }
+
+  if (!aLoadContextInfo) {
+    RefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
+    NS_DispatchToMainThread(r);
   }
 
   if (!mContextEvictor) {
     mContextEvictor = new CacheFileContextEvictor();
     mContextEvictor->Init(mCacheDirectory);
   }
 
-  mContextEvictor->AddContext(aLoadContextInfo);
+  mContextEvictor->AddContext(aLoadContextInfo, aPinned);
 
   return NS_OK;
 }
 
 // static
 nsresult
 CacheFileIOManager::CacheIndexStateChanged()
 {
@@ -3238,34 +3347,35 @@ CacheFileIOManager::FindTrashDirToRemove
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 // static
 nsresult
 CacheFileIOManager::InitIndexEntry(CacheFileHandle *aHandle,
                                    uint32_t         aAppId,
                                    bool             aAnonymous,
-                                   bool             aInBrowser)
+                                   bool             aInBrowser,
+                                   bool             aPinning)
 {
   LOG(("CacheFileIOManager::InitIndexEntry() [handle=%p, appId=%u, anonymous=%d"
-       ", inBrowser=%d]", aHandle, aAppId, aAnonymous, aInBrowser));
+       ", inBrowser=%d, pinned=%d]", aHandle, aAppId, aAnonymous, aInBrowser, aPinning));
 
   nsresult rv;
   RefPtr<CacheFileIOManager> ioMan = gInstance;
 
   if (aHandle->IsClosed() || !ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (aHandle->IsSpecialFile()) {
     return NS_ERROR_UNEXPECTED;
   }
 
   RefPtr<InitIndexEntryEvent> ev =
-    new InitIndexEntryEvent(aHandle, aAppId, aAnonymous, aInBrowser);
+    new InitIndexEntryEvent(aHandle, aAppId, aAnonymous, aInBrowser, aPinning);
   rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 // static
 nsresult
--- a/netwerk/cache2/CacheFileIOManager.h
+++ b/netwerk/cache2/CacheFileIOManager.h
@@ -24,45 +24,55 @@ class nsIFile;
 class nsITimer;
 class nsIDirectoryEnumerator;
 class nsILoadContextInfo;
 
 namespace mozilla {
 namespace net {
 
 class CacheFile;
+class CacheFileIOListener;
+
 #ifdef DEBUG_HANDLES
 class CacheFileHandlesEntry;
 #endif
 
 #define ENTRIES_DIR "entries"
 #define DOOMED_DIR  "doomed"
 #define TRASH_DIR   "trash"
 
 
 class CacheFileHandle : public nsISupports
 {
 public:
+  enum class PinningStatus : uint32_t {
+    UNKNOWN,
+    NON_PINNED,
+    PINNED
+  };
+
   NS_DECL_THREADSAFE_ISUPPORTS
   bool DispatchRelease();
 
-  CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority);
-  CacheFileHandle(const nsACString &aKey, bool aPriority);
-  CacheFileHandle(const CacheFileHandle &aOther);
+  CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority, PinningStatus aPinning);
+  CacheFileHandle(const nsACString &aKey, bool aPriority, PinningStatus aPinning);
   void Log();
   bool IsDoomed() const { return mIsDoomed; }
   const SHA1Sum::Hash *Hash() const { return mHash; }
   int64_t FileSize() const { return mFileSize; }
   uint32_t FileSizeInK() const;
   bool IsPriority() const { return mPriority; }
   bool FileExists() const { return mFileExists; }
   bool IsClosed() const { return mClosed; }
   bool IsSpecialFile() const { return mSpecialFile; }
   nsCString & Key() { return mKey; }
 
+  // Returns false when this handle has been doomed based on the pinning state update.
+  bool SetPinned(bool aPinned);
+
   // Memory reporting
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
 private:
   friend class CacheFileIOManager;
   friend class CacheFileHandles;
   friend class ReleaseNSPRHandleEvent;
@@ -74,29 +84,46 @@ private:
   bool                 mPriority;
   bool                 mClosed;
   bool                 mSpecialFile;
   bool                 mInvalid;
   bool                 mFileExists; // This means that the file should exists,
                                     // but it can be still deleted by OS/user
                                     // and then a subsequent OpenNSPRFileDesc()
                                     // will fail.
+
+  // For existing files this is always pre-set to UNKNOWN.  The status is udpated accordingly
+  // after the matadata has been parsed.
+  // For new files the flag is set according to which storage kind is opening
+  // the cache entry and remains so for the handle's lifetime.
+  // The status can only change from UNKNOWN (if set so initially) to one of PINNED or NON_PINNED
+  // and it stays unchanged afterwards.
+  // This status is only accessed on the IO thread.
+  PinningStatus        mPinning;
+  // Both initially false.  Can be raised to true only when this handle is to be doomed
+  // during the period when the pinning status is unknown.  After the pinning status
+  // determination we check these flags and possibly doom.
+  // These flags are only accessed on the IO thread.
+  bool                 mDoomWhenFoundPinned : 1;
+  bool                 mDoomWhenFoundNonPinned : 1;
+
   nsCOMPtr<nsIFile>    mFile;
   int64_t              mFileSize;
   PRFileDesc          *mFD;  // if null then the file doesn't exists on the disk
   nsCString            mKey;
 };
 
 class CacheFileHandles {
 public:
   CacheFileHandles();
   ~CacheFileHandles();
 
   nsresult GetHandle(const SHA1Sum::Hash *aHash, CacheFileHandle **_retval);
-  nsresult NewHandle(const SHA1Sum::Hash *aHash, bool aPriority, CacheFileHandle **_retval);
+  nsresult NewHandle(const SHA1Sum::Hash *aHash, bool aPriority,
+                     CacheFileHandle::PinningStatus aPinning, CacheFileHandle **_retval);
   void     RemoveHandle(CacheFileHandle *aHandlle);
   void     GetAllHandles(nsTArray<RefPtr<CacheFileHandle> > *_retval);
   void     GetActiveHandles(nsTArray<RefPtr<CacheFileHandle> > *_retval);
   void     ClearAll();
   uint32_t HandleCount();
 
 #ifdef DEBUG_HANDLES
   void     Log(CacheFileHandlesEntry *entry);
@@ -207,21 +234,22 @@ NS_DEFINE_STATIC_IID_ACCESSOR(CacheFileI
 
 class CacheFileIOManager : public nsITimerCallback
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSITIMERCALLBACK
 
   enum {
-    OPEN         = 0U,
-    CREATE       = 1U,
-    CREATE_NEW   = 2U,
-    PRIORITY     = 4U,
-    SPECIAL_FILE = 8U
+    OPEN         =  0U,
+    CREATE       =  1U,
+    CREATE_NEW   =  2U,
+    PRIORITY     =  4U,
+    SPECIAL_FILE =  8U,
+    PINNED       = 16U
   };
 
   CacheFileIOManager();
 
   static nsresult Init();
   static nsresult Shutdown();
   static nsresult OnProfile();
   static already_AddRefed<nsIEventTarget> IOTarget();
@@ -242,35 +270,50 @@ public:
   static nsresult OpenFile(const nsACString &aKey,
                            uint32_t aFlags, CacheFileIOListener *aCallback);
   static nsresult Read(CacheFileHandle *aHandle, int64_t aOffset,
                        char *aBuf, int32_t aCount,
                        CacheFileIOListener *aCallback);
   static nsresult Write(CacheFileHandle *aHandle, int64_t aOffset,
                         const char *aBuf, int32_t aCount, bool aValidate,
                         bool aTruncate, CacheFileIOListener *aCallback);
+  // PinningDoomRestriction:
+  // NO_RESTRICTION
+  //    no restriction is checked, the file is simply always doomed
+  // DOOM_WHEN_(NON)_PINNED, we branch based on the pinning status of the handle:
+  //   UNKNOWN: the handle is marked to be doomed when later found (non)pinned
+  //   PINNED/NON_PINNED: doom only when the restriction matches the pin status
+  //      and the handle has not yet been required to doom during the UNKNOWN
+  //      period
+  enum PinningDoomRestriction {
+    NO_RESTRICTION,
+    DOOM_WHEN_NON_PINNED,
+    DOOM_WHEN_PINNED
+  };
   static nsresult DoomFile(CacheFileHandle *aHandle,
                            CacheFileIOListener *aCallback);
   static nsresult DoomFileByKey(const nsACString &aKey,
                                 CacheFileIOListener *aCallback);
   static nsresult ReleaseNSPRHandle(CacheFileHandle *aHandle);
   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 EvictByContext(nsILoadContextInfo *aLoadContextInfo,
+                                 bool aPinning);
 
   static nsresult InitIndexEntry(CacheFileHandle *aHandle,
                                  uint32_t         aAppId,
                                  bool             aAnonymous,
-                                 bool             aInBrowser);
+                                 bool             aInBrowser,
+                                 bool             aPinning);
   static nsresult UpdateIndexEntry(CacheFileHandle *aHandle,
                                    const uint32_t  *aFrecency,
                                    const uint32_t  *aExpirationTime);
 
   static nsresult UpdateIndexEntry();
 
   enum EEnumerateMode {
     ENTRIES,
@@ -324,27 +367,29 @@ private:
                                    uint32_t aFlags,
                                    CacheFileHandle **_retval);
   nsresult CloseHandleInternal(CacheFileHandle *aHandle);
   nsresult ReadInternal(CacheFileHandle *aHandle, int64_t aOffset,
                         char *aBuf, int32_t aCount);
   nsresult WriteInternal(CacheFileHandle *aHandle, int64_t aOffset,
                          const char *aBuf, int32_t aCount, bool aValidate,
                          bool aTruncate);
-  nsresult DoomFileInternal(CacheFileHandle *aHandle);
+  nsresult DoomFileInternal(CacheFileHandle *aHandle,
+                            PinningDoomRestriction aPinningStatusRestriction = NO_RESTRICTION);
   nsresult DoomFileByKeyInternal(const SHA1Sum::Hash *aHash);
   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 EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo,
+                                  bool aPinning);
 
   nsresult TrashDirectory(nsIFile *aFile);
   static void OnTrashTimer(nsITimer *aTimer, void *aClosure);
   nsresult StartRemovingTrash();
   nsresult RemoveTrashInternal();
   nsresult FindTrashDirToRemove();
 
   nsresult CreateFile(CacheFileHandle *aHandle);
@@ -378,37 +423,37 @@ private:
   nsresult UpdateSmartCacheSize(int64_t aFreeSpace);
 
   // Memory reporting (private part)
   size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;
 
   static CacheFileIOManager           *gInstance;
   TimeStamp                            mStartTime;
   bool                                 mShuttingDown;
-  RefPtr<CacheIOThread>              mIOThread;
+  RefPtr<CacheIOThread>                mIOThread;
   nsCOMPtr<nsIFile>                    mCacheDirectory;
 #if defined(MOZ_WIDGET_ANDROID)
   // On Android we add the active profile directory name between the path
   // and the 'cache2' leaf name.  However, to delete any leftover data from
   // times before we were doing it, we still need to access the directory
   // w/o the profile name in the path.  Here it is stored.
   nsCOMPtr<nsIFile>                    mCacheProfilelessDirectory;
 #endif
   bool                                 mTreeCreated;
   CacheFileHandles                     mHandles;
   nsTArray<CacheFileHandle *>          mHandlesByLastUsed;
   nsTArray<CacheFileHandle *>          mSpecialHandles;
-  nsTArray<RefPtr<CacheFile> >       mScheduledMetadataWrites;
+  nsTArray<RefPtr<CacheFile> >         mScheduledMetadataWrites;
   nsCOMPtr<nsITimer>                   mMetadataWritesTimer;
   bool                                 mOverLimitEvicting;
   bool                                 mRemovingTrashDirs;
   nsCOMPtr<nsITimer>                   mTrashTimer;
   nsCOMPtr<nsIFile>                    mTrashDir;
   nsCOMPtr<nsIDirectoryEnumerator>     mTrashDirEnumerator;
   nsTArray<nsCString>                  mFailedTrashDirs;
-  RefPtr<CacheFileContextEvictor>    mContextEvictor;
+  RefPtr<CacheFileContextEvictor>      mContextEvictor;
   TimeStamp                            mLastSmartSizeTime;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif
--- a/netwerk/cache2/CacheFileInputStream.h
+++ b/netwerk/cache2/CacheFileInputStream.h
@@ -48,17 +48,17 @@ private:
   void EnsureCorrectChunk(bool aReleaseOnly);
 
   // CanRead returns negative value when output stream truncates the data before
   // the input stream's mPos.
   void CanRead(int64_t *aCanRead, const char **aBuf);
   void NotifyListener();
   void MaybeNotifyListener();
 
-  RefPtr<CacheFile>      mFile;
+  RefPtr<CacheFile>        mFile;
   RefPtr<CacheFileChunk> mChunk;
   int64_t                  mPos;
   bool                     mClosed;
   nsresult                 mStatus;
   bool                     mWaitingForUpdate;
   int64_t                  mListeningForChunk;
 
   nsCOMPtr<nsIInputStreamCallback> mCallback;
--- a/netwerk/cache2/CacheFileMetadata.cpp
+++ b/netwerk/cache2/CacheFileMetadata.cpp
@@ -30,18 +30,16 @@ namespace net {
 #define kInitialHashArraySize 1
 
 // Initial elements buffer size.
 #define kInitialBufSize 64
 
 // Max size of elements in bytes.
 #define kMaxElementsSize 64*1024
 
-#define kCacheEntryVersion 1
-
 #define NOW_SECONDS() (uint32_t(PR_Now() / PR_USEC_PER_SEC))
 
 NS_IMPL_ISUPPORTS(CacheFileMetadata, CacheFileIOListener)
 
 CacheFileMetadata::CacheFileMetadata(CacheFileHandle *aHandle, const nsACString &aKey)
   : CacheMemoryConsumer(NORMAL)
   , mHandle(aHandle)
   , mHashArray(nullptr)
@@ -66,17 +64,17 @@ CacheFileMetadata::CacheFileMetadata(Cac
   mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
   mKey = aKey;
 
   DebugOnly<nsresult> rv;
   rv = ParseKey(aKey);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 }
 
-CacheFileMetadata::CacheFileMetadata(bool aMemoryOnly, const nsACString &aKey)
+CacheFileMetadata::CacheFileMetadata(bool aMemoryOnly, bool aPinned, const nsACString &aKey)
   : CacheMemoryConsumer(aMemoryOnly ? MEMORY_ONLY : NORMAL)
   , mHandle(nullptr)
   , mHashArray(nullptr)
   , mHashArraySize(0)
   , mHashCount(0)
   , mOffset(0)
   , mBuf(nullptr)
   , mBufSize(0)
@@ -88,16 +86,19 @@ CacheFileMetadata::CacheFileMetadata(boo
   , mFirstRead(true)
 {
   LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, key=%s]",
        this, PromiseFlatCString(aKey).get()));
 
   MOZ_COUNT_CTOR(CacheFileMetadata);
   memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
   mMetaHdr.mVersion = kCacheEntryVersion;
+  if (aPinned) {
+    AddFlags(kCacheEntryIsPinned);
+  }
   mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
   mKey = aKey;
   mMetaHdr.mKeySize = mKey.Length();
 
   DebugOnly<nsresult> rv;
   rv = ParseKey(aKey);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 }
@@ -530,16 +531,39 @@ CacheFileMetadata::SetHash(uint32_t aInd
   NetworkEndian::writeUint16(&mHashArray[aIndex], aHash);
 
   DoMemoryReport(MemoryUsage());
 
   return NS_OK;
 }
 
 nsresult
+CacheFileMetadata::AddFlags(uint32_t aFlags)
+{
+  MarkDirty(false);
+  mMetaHdr.mFlags |= aFlags;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::RemoveFlags(uint32_t aFlags)
+{
+  MarkDirty(false);
+  mMetaHdr.mFlags &= ~aFlags;
+  return NS_OK;
+}
+
+nsresult
+CacheFileMetadata::GetFlags(uint32_t *_retval)
+{
+  *_retval = mMetaHdr.mFlags;
+  return NS_OK;
+}
+
+nsresult
 CacheFileMetadata::SetExpirationTime(uint32_t aExpirationTime)
 {
   LOG(("CacheFileMetadata::SetExpirationTime() [this=%p, expirationTime=%d]",
        this, aExpirationTime));
 
   MarkDirty(false);
   mMetaHdr.mExpirationTime = aExpirationTime;
   return NS_OK;
@@ -809,21 +833,26 @@ CacheFileMetadata::InitEmptyMetadata()
     mBufSize = 0;
   }
   mOffset = 0;
   mMetaHdr.mVersion = kCacheEntryVersion;
   mMetaHdr.mFetchCount = 0;
   mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
   mMetaHdr.mKeySize = mKey.Length();
 
+  // Deliberately not touching the "kCacheEntryIsPinned" flag.
+
   DoMemoryReport(MemoryUsage());
 
   // We're creating a new entry. If there is any old data truncate it.
-  if (mHandle && mHandle->FileExists() && mHandle->FileSize()) {
-    CacheFileIOManager::TruncateSeekSetEOF(mHandle, 0, 0, nullptr);
+  if (mHandle) {
+    mHandle->SetPinned(Pinned());
+    if (mHandle->FileExists() && mHandle->FileSize()) {
+      CacheFileIOManager::TruncateSeekSetEOF(mHandle, 0, 0, nullptr);
+    }
   }
 }
 
 nsresult
 CacheFileMetadata::ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset,
                                  bool aHaveKey)
 {
   LOG(("CacheFileMetadata::ParseMetadata() [this=%p, metaOffset=%d, "
@@ -848,22 +877,29 @@ CacheFileMetadata::ParseMetadata(uint32_
   if (keyOffset > metaposOffset) {
     LOG(("CacheFileMetadata::ParseMetadata() - Wrong keyOffset! [this=%p]",
          this));
     return NS_ERROR_FILE_CORRUPTED;
   }
 
   mMetaHdr.ReadFromBuf(mBuf + hdrOffset);
 
-  if (mMetaHdr.mVersion != kCacheEntryVersion) {
+  if (mMetaHdr.mVersion == 1) {
+    // Backward compatibility before we've added flags to the header
+    keyOffset -= sizeof(uint32_t);
+  } else if (mMetaHdr.mVersion != kCacheEntryVersion) {
     LOG(("CacheFileMetadata::ParseMetadata() - Not a version we understand to. "
          "[version=0x%x, this=%p]", mMetaHdr.mVersion, this));
     return NS_ERROR_UNEXPECTED;
   }
 
+  // Update the version stored in the header to make writes
+  // store the header in the current version form.
+  mMetaHdr.mVersion = kCacheEntryVersion;
+
   uint32_t elementsOffset = mMetaHdr.mKeySize + keyOffset + 1;
 
   if (elementsOffset > metaposOffset) {
     LOG(("CacheFileMetadata::ParseMetadata() - Wrong elementsOffset %d "
          "[this=%p]", elementsOffset, this));
     return NS_ERROR_FILE_CORRUPTED;
   }
 
@@ -912,16 +948,24 @@ CacheFileMetadata::ParseMetadata(uint32_
     return NS_ERROR_FILE_CORRUPTED;
   }
 
   // check elements
   rv = CheckElements(mBuf + elementsOffset, metaposOffset - elementsOffset);
   if (NS_FAILED(rv))
     return rv;
 
+  if (mHandle) {
+    if (!mHandle->SetPinned(Pinned())) {
+      LOG(("CacheFileMetadata::ParseMetadata() - handle was doomed for this "
+           "pinning state, truncate the file [this=%p, pinned=%d]", this, Pinned()));
+      return NS_ERROR_FILE_CORRUPTED;
+    }
+  }
+
   mHashArraySize = hashesLen;
   mHashCount = hashCount;
   if (mHashArraySize) {
     mHashArray = static_cast<CacheHash::Hash16_t *>(
                    moz_xmalloc(mHashArraySize));
     memcpy(mHashArray, mBuf + hashesOffset, mHashArraySize);
   }
 
--- a/netwerk/cache2/CacheFileMetadata.h
+++ b/netwerk/cache2/CacheFileMetadata.h
@@ -14,73 +14,90 @@
 #include "nsAutoPtr.h"
 #include "nsString.h"
 
 class nsICacheEntryMetaDataVisitor;
 
 namespace mozilla {
 namespace net {
 
+// Flags stored in CacheFileMetadataHeader.mFlags
+
+// Whether an entry is a pinned entry (created with
+// nsICacheStorageService.pinningCacheStorage.)
+static const uint32_t kCacheEntryIsPinned = 1 << 0;
+
 // By multiplying with the current half-life we convert the frecency
 // to time independent of half-life value.  The range fits 32bits.
 // When decay time changes on next run of the browser, we convert
 // the frecency value to a correct internal representation again.
 // It might not be 100% accurate, but for the purpose it suffice.
 #define FRECENCY2INT(aFrecency) \
   ((uint32_t)((aFrecency) * CacheObserver::HalfLifeSeconds()))
 #define INT2FRECENCY(aInt) \
   ((double)(aInt) / (double)CacheObserver::HalfLifeSeconds())
 
 
+#define kCacheEntryVersion 2
+
+
 #pragma pack(push)
 #pragma pack(1)
 
 class CacheFileMetadataHeader {
 public:
   uint32_t        mVersion;
   uint32_t        mFetchCount;
   uint32_t        mLastFetched;
   uint32_t        mLastModified;
   uint32_t        mFrecency;
   uint32_t        mExpirationTime;
   uint32_t        mKeySize;
+  uint32_t        mFlags;
 
   void WriteToBuf(void *aBuf)
   {
     EnsureCorrectClassSize();
 
     uint8_t* ptr = static_cast<uint8_t*>(aBuf);
+    MOZ_ASSERT(mVersion == kCacheEntryVersion);
     NetworkEndian::writeUint32(ptr, mVersion); ptr += sizeof(uint32_t);
     NetworkEndian::writeUint32(ptr, mFetchCount); ptr += sizeof(uint32_t);
     NetworkEndian::writeUint32(ptr, mLastFetched); ptr += sizeof(uint32_t);
     NetworkEndian::writeUint32(ptr, mLastModified); ptr += sizeof(uint32_t);
     NetworkEndian::writeUint32(ptr, mFrecency); ptr += sizeof(uint32_t);
     NetworkEndian::writeUint32(ptr, mExpirationTime); ptr += sizeof(uint32_t);
-    NetworkEndian::writeUint32(ptr, mKeySize);
+    NetworkEndian::writeUint32(ptr, mKeySize); ptr += sizeof(uint32_t);
+    NetworkEndian::writeUint32(ptr, mFlags);
   }
 
   void ReadFromBuf(const void *aBuf)
   {
     EnsureCorrectClassSize();
 
     const uint8_t* ptr = static_cast<const uint8_t*>(aBuf);
     mVersion = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
     mFetchCount = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
     mLastFetched = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
     mLastModified = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
     mFrecency = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
     mExpirationTime = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
-    mKeySize = BigEndian::readUint32(ptr);
+    mKeySize = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t);
+    if (mVersion >= kCacheEntryVersion) {
+      mFlags = BigEndian::readUint32(ptr);
+    } else {
+      mFlags = 0;
+    }
   }
 
   inline void EnsureCorrectClassSize()
   {
     static_assert((sizeof(mVersion) + sizeof(mFetchCount) +
       sizeof(mLastFetched) + sizeof(mLastModified) + sizeof(mFrecency) +
-      sizeof(mExpirationTime) + sizeof(mKeySize)) ==
+      sizeof(mExpirationTime) + sizeof(mKeySize)) + sizeof(mFlags) ==
       sizeof(CacheFileMetadataHeader),
       "Unexpected sizeof(CacheFileMetadataHeader)!");
   }
 };
 
 #pragma pack(pop)
 
 
@@ -109,39 +126,44 @@ class CacheFileMetadata : public CacheFi
                         , public CacheMemoryConsumer
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   CacheFileMetadata(CacheFileHandle *aHandle,
                     const nsACString &aKey);
   CacheFileMetadata(bool aMemoryOnly,
+                    bool aPinned,
                     const nsACString &aKey);
   CacheFileMetadata();
 
   void SetHandle(CacheFileHandle *aHandle);
 
   nsresult GetKey(nsACString &_retval);
 
   nsresult ReadMetadata(CacheFileMetadataListener *aListener);
   uint32_t CalcMetadataSize(uint32_t aElementsSize, uint32_t aHashCount);
   nsresult WriteMetadata(uint32_t aOffset,
                          CacheFileMetadataListener *aListener);
   nsresult SyncReadMetadata(nsIFile *aFile);
 
-  bool     IsAnonymous() { return mAnonymous; }
+  bool     IsAnonymous() const { return mAnonymous; }
   mozilla::OriginAttributes const & OriginAttributes() const { return mOriginAttributes; }
+  bool     Pinned() const      { return !!(mMetaHdr.mFlags & kCacheEntryIsPinned); }
 
   const char * GetElement(const char *aKey);
   nsresult     SetElement(const char *aKey, const char *aValue);
   nsresult     Visit(nsICacheEntryMetaDataVisitor *aVisitor);
 
   CacheHash::Hash16_t GetHash(uint32_t aIndex);
   nsresult            SetHash(uint32_t aIndex, CacheHash::Hash16_t aHash);
 
+  nsresult AddFlags(uint32_t aFlags);
+  nsresult RemoveFlags(uint32_t aFlags);
+  nsresult GetFlags(uint32_t *_retval);
   nsresult SetExpirationTime(uint32_t aExpirationTime);
   nsresult GetExpirationTime(uint32_t *_retval);
   nsresult SetFrecency(uint32_t aFrecency);
   nsresult GetFrecency(uint32_t *_retval);
   nsresult GetLastModified(uint32_t *_retval);
   nsresult GetLastFetched(uint32_t *_retval);
   nsresult GetFetchCount(uint32_t *_retval);
   // Called by upper layers to indicate the entry this metadata belongs
@@ -170,17 +192,17 @@ private:
   virtual ~CacheFileMetadata();
 
   void     InitEmptyMetadata();
   nsresult ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset, bool aHaveKey);
   nsresult CheckElements(const char *aBuf, uint32_t aSize);
   nsresult EnsureBuffer(uint32_t aSize);
   nsresult ParseKey(const nsACString &aKey);
 
-  RefPtr<CacheFileHandle>           mHandle;
+  RefPtr<CacheFileHandle>             mHandle;
   nsCString                           mKey;
   CacheHash::Hash16_t                *mHashArray;
   uint32_t                            mHashArraySize;
   uint32_t                            mHashCount;
   int64_t                             mOffset;
   char                               *mBuf; // used for parsing, then points
                                             // to elements
   uint32_t                            mBufSize;
--- a/netwerk/cache2/CacheFileOutputStream.h
+++ b/netwerk/cache2/CacheFileOutputStream.h
@@ -45,17 +45,17 @@ private:
   virtual ~CacheFileOutputStream();
 
   nsresult CloseWithStatusLocked(nsresult aStatus);
   void ReleaseChunk();
   void EnsureCorrectChunk(bool aReleaseOnly);
   void FillHole();
   void NotifyListener();
 
-  RefPtr<CacheFile>      mFile;
+  RefPtr<CacheFile>        mFile;
   RefPtr<CacheFileChunk> mChunk;
   RefPtr<CacheOutputCloseListener> mCloseListener;
   int64_t                  mPos;
   bool                     mClosed;
   nsresult                 mStatus;
 
   nsCOMPtr<nsIOutputStreamCallback> mCallback;
   uint32_t                          mCallbackFlags;
--- a/netwerk/cache2/CacheIndex.cpp
+++ b/netwerk/cache2/CacheIndex.cpp
@@ -700,21 +700,22 @@ CacheIndex::EnsureEntryExists(const SHA1
   return NS_OK;
 }
 
 // static
 nsresult
 CacheIndex::InitEntry(const SHA1Sum::Hash *aHash,
                       uint32_t             aAppId,
                       bool                 aAnonymous,
-                      bool                 aInBrowser)
+                      bool                 aInBrowser,
+                      bool                 aPinned)
 {
   LOG(("CacheIndex::InitEntry() [hash=%08x%08x%08x%08x%08x, appId=%u, "
-       "anonymous=%d, inBrowser=%d]", LOGSHA1(aHash), aAppId, aAnonymous,
-       aInBrowser));
+       "anonymous=%d, inBrowser=%d, pinned=%d]", LOGSHA1(aHash), aAppId,
+       aAnonymous, aInBrowser, aPinned));
 
   RefPtr<CacheIndex> index = gInstance;
 
   if (!index) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
@@ -794,20 +795,20 @@ CacheIndex::InitEntry(const SHA1Sum::Has
         updated->MarkFresh();
       } else {
         entry->InitNew();
         entry->MarkFresh();
       }
     }
 
     if (updated) {
-      updated->Init(aAppId, aAnonymous, aInBrowser);
+      updated->Init(aAppId, aAnonymous, aInBrowser, aPinned);
       updated->MarkDirty();
     } else {
-      entry->Init(aAppId, aAnonymous, aInBrowser);
+      entry->Init(aAppId, aAnonymous, aInBrowser, aPinned);
       entry->MarkDirty();
     }
   }
 
   index->StartUpdatingIndexIfNeeded();
   index->WriteIndexToDiskIfNeeded();
 
   return NS_OK;
@@ -1103,36 +1104,47 @@ CacheIndex::RemoveAll()
     file->Remove(false);
   }
 
   return NS_OK;
 }
 
 // static
 nsresult
-CacheIndex::HasEntry(const nsACString &aKey, EntryStatus *_retval)
+CacheIndex::HasEntry(const nsACString &aKey, EntryStatus *_retval, bool *_pinned)
 {
   LOG(("CacheIndex::HasEntry() [key=%s]", PromiseFlatCString(aKey).get()));
 
+  SHA1Sum sum;
+  SHA1Sum::Hash hash;
+  sum.update(aKey.BeginReading(), aKey.Length());
+  sum.finish(hash);
+
+  return HasEntry(hash, _retval, _pinned);
+}
+
+// static
+nsresult
+CacheIndex::HasEntry(const SHA1Sum::Hash &hash, EntryStatus *_retval, bool *_pinned)
+{
   RefPtr<CacheIndex> index = gInstance;
 
   if (!index) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   CacheIndexAutoLock lock(index);
 
   if (!index->IsIndexUsable()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  SHA1Sum sum;
-  SHA1Sum::Hash hash;
-  sum.update(aKey.BeginReading(), aKey.Length());
-  sum.finish(hash);
+  if (_pinned) {
+    *_pinned = false;
+  }
 
   const CacheIndexEntry *entry = nullptr;
 
   switch (index->mState) {
     case READING:
     case WRITING:
       entry = index->mPendingUpdates.GetEntry(hash);
       // no break
@@ -1158,16 +1170,19 @@ CacheIndex::HasEntry(const nsACString &a
     if (entry->IsRemoved()) {
       if (entry->IsFresh()) {
         *_retval = DOES_NOT_EXIST;
       } else {
         *_retval = DO_NOT_KNOW;
       }
     } else {
       *_retval = EXISTS;
+      if (_pinned && entry->IsPinned()) {
+        *_pinned = true;
+      }
     }
   }
 
   LOG(("CacheIndex::HasEntry() - result is %u", *_retval));
   return NS_OK;
 }
 
 // static
@@ -1188,25 +1203,29 @@ CacheIndex::GetEntryForEviction(bool aIg
   if (!index->IsIndexUsable()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   SHA1Sum::Hash hash;
   bool foundEntry = false;
   uint32_t i;
 
-  // find first non-forced valid entry with the lowest frecency
+  // find first non-forced valid and unpinned entry with the lowest frecency
   index->mFrecencyArray.Sort(FrecencyComparator());
   for (i = 0; i < index->mFrecencyArray.Length(); ++i) {
     memcpy(&hash, &index->mFrecencyArray[i]->mHash, sizeof(SHA1Sum::Hash));
 
     if (IsForcedValidEntry(&hash)) {
       continue;
     }
 
+    if (CacheIndexEntry::IsPinned(index->mFrecencyArray[i])) {
+      continue;
+    }
+
     if (aIgnoreEmptyEntries &&
         !CacheIndexEntry::GetFileSize(index->mFrecencyArray[i])) {
       continue;
     }
 
     foundEntry = true;
     break;
   }
@@ -2570,17 +2589,18 @@ CacheIndex::InitEntryFromDiskData(CacheI
 {
   aEntry->InitNew();
   aEntry->MarkDirty();
   aEntry->MarkFresh();
 
   // Bug 1201042 - will pass OriginAttributes directly.
   aEntry->Init(aMetaData->OriginAttributes().mAppId,
                aMetaData->IsAnonymous(),
-               aMetaData->OriginAttributes().mInBrowser);
+               aMetaData->OriginAttributes().mInBrowser,
+               aMetaData->Pinned());
 
   uint32_t expirationTime;
   aMetaData->GetExpirationTime(&expirationTime);
   aEntry->SetExpirationTime(expirationTime);
 
   uint32_t frecency;
   aMetaData->GetFrecency(&frecency);
   aEntry->SetFrecency(frecency);
--- a/netwerk/cache2/CacheIndex.h
+++ b/netwerk/cache2/CacheIndex.h
@@ -143,32 +143,35 @@ public:
   void InitNew()
   {
     mRec->mFrecency = 0;
     mRec->mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
     mRec->mAppId = nsILoadContextInfo::NO_APP_ID;
     mRec->mFlags = 0;
   }
 
-  void Init(uint32_t aAppId, bool aAnonymous, bool aInBrowser)
+  void Init(uint32_t aAppId, bool aAnonymous, bool aInBrowser, bool aPinned)
   {
     MOZ_ASSERT(mRec->mFrecency == 0);
     MOZ_ASSERT(mRec->mExpirationTime == nsICacheEntry::NO_EXPIRATION_TIME);
     MOZ_ASSERT(mRec->mAppId == nsILoadContextInfo::NO_APP_ID);
     // When we init the entry it must be fresh and may be dirty
     MOZ_ASSERT((mRec->mFlags & ~kDirtyMask) == kFreshMask);
 
     mRec->mAppId = aAppId;
     mRec->mFlags |= kInitializedMask;
     if (aAnonymous) {
       mRec->mFlags |= kAnonymousMask;
     }
     if (aInBrowser) {
       mRec->mFlags |= kInBrowserMask;
     }
+    if (aPinned) {
+      mRec->mFlags |= kPinnedMask;
+    }
   }
 
   const SHA1Sum::Hash * Hash() const { return &mRec->mHash; }
 
   bool IsInitialized() const { return !!(mRec->mFlags & kInitializedMask); }
 
   uint32_t AppId() const { return mRec->mAppId; }
   bool     Anonymous() const { return !!(mRec->mFlags & kAnonymousMask); }
@@ -179,16 +182,18 @@ public:
 
   bool IsDirty() const { return !!(mRec->mFlags & kDirtyMask); }
   void MarkDirty() { mRec->mFlags |= kDirtyMask; }
   void ClearDirty() { mRec->mFlags &= ~kDirtyMask; }
 
   bool IsFresh() const { return !!(mRec->mFlags & kFreshMask); }
   void MarkFresh() { mRec->mFlags |= kFreshMask; }
 
+  bool IsPinned() const { return !!(mRec->mFlags & kPinnedMask); }
+
   void     SetFrecency(uint32_t aFrecency) { mRec->mFrecency = aFrecency; }
   uint32_t GetFrecency() const { return mRec->mFrecency; }
 
   void     SetExpirationTime(uint32_t aExpirationTime)
   {
     mRec->mExpirationTime = aExpirationTime;
   }
   uint32_t GetExpirationTime() const { return mRec->mExpirationTime; }
@@ -205,16 +210,20 @@ public:
     mRec->mFlags |= aFileSize;
   }
   // Returns filesize in kilobytes.
   uint32_t GetFileSize() const { return GetFileSize(mRec); }
   static uint32_t GetFileSize(CacheIndexRecord *aRec)
   {
     return aRec->mFlags & kFileSizeMask;
   }
+  static uint32_t IsPinned(CacheIndexRecord *aRec)
+  {
+    return aRec->mFlags & kPinnedMask;
+  }
   bool     IsFileEmpty() const { return GetFileSize() == 0; }
 
   void WriteToBuf(void *aBuf)
   {
     CacheIndexRecord *dst = reinterpret_cast<CacheIndexRecord *>(aBuf);
 
     // Copy the whole record to the buffer.
     memcpy(aBuf, mRec, sizeof(CacheIndexRecord));
@@ -296,17 +305,20 @@ private:
   // information in index file on disk.
   static const uint32_t kDirtyMask       = 0x08000000;
 
   // This flag is set when the information about the entry is fresh, i.e.
   // we've created or opened this entry during this session, or we've seen
   // this entry during update or build process.
   static const uint32_t kFreshMask       = 0x04000000;
 
-  static const uint32_t kReservedMask    = 0x03000000;
+  // Indicates a pinned entry.
+  static const uint32_t kPinnedMask      = 0x02000000;
+
+  static const uint32_t kReservedMask    = 0x01000000;
 
   // FileSize in kilobytes
   static const uint32_t kFileSizeMask    = 0x00FFFFFF;
 
   nsAutoPtr<CacheIndexRecord> mRec;
 };
 
 class CacheIndexEntryUpdate : public CacheIndexEntry
@@ -605,17 +617,18 @@ public:
   // index is outdated.
   static nsresult EnsureEntryExists(const SHA1Sum::Hash *aHash);
 
   // Initialize the entry. It MUST be present in index. Call to AddEntry() or
   // EnsureEntryExists() must precede the call to this method.
   static nsresult InitEntry(const SHA1Sum::Hash *aHash,
                             uint32_t             aAppId,
                             bool                 aAnonymous,
-                            bool                 aInBrowser);
+                            bool                 aInBrowser,
+                            bool                 aPinned);
 
   // Remove entry from index. The entry should be present in index.
   static nsresult RemoveEntry(const SHA1Sum::Hash *aHash);
 
   // Update some information in entry. The entry MUST be present in index and
   // MUST be initialized. Call to AddEntry() or EnsureEntryExists() and to
   // InitEntry() must precede the call to this method.
   // Pass nullptr if the value didn't change.
@@ -630,22 +643,26 @@ public:
   enum EntryStatus {
     EXISTS         = 0,
     DOES_NOT_EXIST = 1,
     DO_NOT_KNOW    = 2
   };
 
   // Returns status of the entry in index for the given key. It can be called
   // on any thread.
-  static nsresult HasEntry(const nsACString &aKey, EntryStatus *_retval);
+  // If _pinned is non-null, it's filled with pinning status of the entry.
+  static nsresult HasEntry(const nsACString &aKey, EntryStatus *_retval,
+                           bool *_pinned = nullptr);
+  static nsresult HasEntry(const SHA1Sum::Hash &hash, EntryStatus *_retval,
+                           bool *_pinned = nullptr);
 
   // Returns a hash of the least important entry that should be evicted if the
   // cache size is over limit and also returns a total number of all entries in
-  // the index minus the number of forced valid entries that we encounter
-  // when searching (see below)
+  // the index minus the number of forced valid entries and unpinned entries
+  // that we encounter when searching (see below)
   static nsresult GetEntryForEviction(bool aIgnoreEmptyEntries, SHA1Sum::Hash *aHash, uint32_t *aCnt);
 
   // Checks if a cache entry is currently forced valid. Used to prevent an entry
   // (that has been forced valid) from being evicted when the cache size reaches
   // its limit.
   static bool IsForcedValidEntry(const SHA1Sum::Hash *aHash);
 
   // Returns cache size in kB.
@@ -964,31 +981,31 @@ private:
   uint32_t                  mSkipEntries;
   // Number of entries that should be written to disk. This is number of entries
   // in hashtable that are initialized and are not marked as removed when writing
   // begins.
   uint32_t                  mProcessEntries;
   char                     *mRWBuf;
   uint32_t                  mRWBufSize;
   uint32_t                  mRWBufPos;
-  RefPtr<CacheHash>       mRWHash;
+  RefPtr<CacheHash>         mRWHash;
 
   // Reading of journal succeeded if true.
   bool                      mJournalReadSuccessfully;
 
   // Handle used for writing and reading index file.
   RefPtr<CacheFileHandle> mIndexHandle;
   // Handle used for reading journal file.
   RefPtr<CacheFileHandle> mJournalHandle;
   // Used to check the existence of the file during reading process.
   RefPtr<CacheFileHandle> mTmpHandle;
 
-  RefPtr<FileOpenHelper>  mIndexFileOpener;
-  RefPtr<FileOpenHelper>  mJournalFileOpener;
-  RefPtr<FileOpenHelper>  mTmpFileOpener;
+  RefPtr<FileOpenHelper>    mIndexFileOpener;
+  RefPtr<FileOpenHelper>    mJournalFileOpener;
+  RefPtr<FileOpenHelper>    mTmpFileOpener;
 
   // Directory enumerator used when building and updating index.
   nsCOMPtr<nsIDirectoryEnumerator> mDirEnumerator;
 
   // Main index hashtable.
   nsTHashtable<CacheIndexEntry> mIndex;
 
   // We cannot add, remove or change any entry in mIndex in states READING and
--- a/netwerk/cache2/CacheIndexIterator.h
+++ b/netwerk/cache2/CacheIndexIterator.h
@@ -44,17 +44,17 @@ protected:
   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;
-  RefPtr<CacheIndex>         mIndex;
+  RefPtr<CacheIndex>           mIndex;
   nsTArray<CacheIndexRecord *> mRecords;
   bool                         mAddNew;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif
--- a/netwerk/cache2/CacheStorage.cpp
+++ b/netwerk/cache2/CacheStorage.cpp
@@ -21,21 +21,23 @@
 namespace mozilla {
 namespace net {
 
 NS_IMPL_ISUPPORTS(CacheStorage, nsICacheStorage)
 
 CacheStorage::CacheStorage(nsILoadContextInfo* aInfo,
                            bool aAllowDisk,
                            bool aLookupAppCache,
-                           bool aSkipSizeCheck)
+                           bool aSkipSizeCheck,
+                           bool aPinning)
 : mLoadContextInfo(GetLoadContextInfo(aInfo))
 , mWriteToDisk(aAllowDisk)
 , mLookupAppCache(aLookupAppCache)
 , mSkipSizeCheck(aSkipSizeCheck)
+, mPinning(aPinning)
 {
 }
 
 CacheStorage::~CacheStorage()
 {
 }
 
 NS_IMETHODIMP CacheStorage::AsyncOpenURI(nsIURI *aURI,
--- a/netwerk/cache2/CacheStorage.h
+++ b/netwerk/cache2/CacheStorage.h
@@ -48,31 +48,34 @@ class CacheStorage : public nsICacheStor
 {
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSICACHESTORAGE
 
 public:
   CacheStorage(nsILoadContextInfo* aInfo,
                bool aAllowDisk,
                bool aLookupAppCache,
-               bool aSkipSizeCheck);
+               bool aSkipSizeCheck,
+               bool aPinning);
 
 protected:
   virtual ~CacheStorage();
 
   nsresult ChooseApplicationCache(nsIURI* aURI, nsIApplicationCache** aCache);
 
   RefPtr<LoadContextInfo> mLoadContextInfo;
   bool mWriteToDisk : 1;
   bool mLookupAppCache : 1;
   bool mSkipSizeCheck: 1;
+  bool mPinning : 1;
 
 public:
   nsILoadContextInfo* LoadInfo() const { return mLoadContextInfo; }
   bool WriteToDisk() const { return mWriteToDisk && !mLoadContextInfo->IsPrivate(); }
   bool LookupAppCache() const { return mLookupAppCache; }
   bool SkipSizeCheck() const { return mSkipSizeCheck; }
+  bool Pinning() const { return mPinning; }
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif
--- a/netwerk/cache2/CacheStorageService.cpp
+++ b/netwerk/cache2/CacheStorageService.cpp
@@ -98,17 +98,18 @@ CacheStorageService::MemoryPool::Limit()
 
   MOZ_CRASH("Bad pool type");
   return 0;
 }
 
 NS_IMPL_ISUPPORTS(CacheStorageService,
                   nsICacheStorageService,
                   nsIMemoryReporter,
-                  nsITimerCallback)
+                  nsITimerCallback,
+                  nsICacheTesting)
 
 CacheStorageService* CacheStorageService::sSelf = nullptr;
 
 CacheStorageService::CacheStorageService()
 : mLock("CacheStorageService.mLock")
 , mForcedValidEntriesLock("CacheStorageService.mForcedValidEntriesLock")
 , mShutdown(false)
 , mDiskPool(MemoryPool::DISK)
@@ -531,17 +532,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], nullptr, true, nullptr);
+    DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
 }
 
 namespace {
 
 class CleaupCacheDirectoriesRunnable : public nsRunnable
 {
 public:
   NS_DECL_NSIRUNNABLE
@@ -676,17 +677,17 @@ nsresult CacheStorageService::Dispatch(n
 NS_IMETHODIMP CacheStorageService::MemoryCacheStorage(nsILoadContextInfo *aLoadContextInfo,
                                                       nsICacheStorage * *_retval)
 {
   NS_ENSURE_ARG(aLoadContextInfo);
   NS_ENSURE_ARG(_retval);
 
   nsCOMPtr<nsICacheStorage> storage;
   if (CacheObserver::UseNewCache()) {
-    storage = new CacheStorage(aLoadContextInfo, false, false, false);
+    storage = new CacheStorage(aLoadContextInfo, false, false, false, false);
   }
   else {
     storage = new _OldStorage(aLoadContextInfo, false, false, false, nullptr);
   }
 
   storage.forget(_retval);
   return NS_OK;
 }
@@ -701,26 +702,47 @@ NS_IMETHODIMP CacheStorageService::DiskC
   // TODO save some heap granularity - cache commonly used storages.
 
   // When disk cache is disabled, still provide a storage, but just keep stuff
   // in memory.
   bool useDisk = CacheObserver::UseDiskCache();
 
   nsCOMPtr<nsICacheStorage> storage;
   if (CacheObserver::UseNewCache()) {
-    storage = new CacheStorage(aLoadContextInfo, useDisk, aLookupAppCache, false);
+    storage = new CacheStorage(aLoadContextInfo, useDisk, aLookupAppCache, false /* size limit */, false /* don't pin */);
   }
   else {
     storage = new _OldStorage(aLoadContextInfo, useDisk, aLookupAppCache, false, nullptr);
   }
 
   storage.forget(_retval);
   return NS_OK;
 }
 
+NS_IMETHODIMP CacheStorageService::PinningCacheStorage(nsILoadContextInfo *aLoadContextInfo,
+                                                       nsICacheStorage * *_retval)
+{
+  NS_ENSURE_ARG(aLoadContextInfo);
+  NS_ENSURE_ARG(_retval);
+
+  if (!CacheObserver::UseNewCache()) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  // When disk cache is disabled don't pretend we cache.
+  if (!CacheObserver::UseDiskCache()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsCOMPtr<nsICacheStorage> storage = new CacheStorage(
+    aLoadContextInfo, true /* use disk */, false /* no appcache */, true /* ignore size checks */, true /* pin */);
+  storage.forget(_retval);
+  return NS_OK;
+}
+
 NS_IMETHODIMP CacheStorageService::AppCacheStorage(nsILoadContextInfo *aLoadContextInfo,
                                                    nsIApplicationCache *aApplicationCache,
                                                    nsICacheStorage * *_retval)
 {
   NS_ENSURE_ARG(aLoadContextInfo);
   NS_ENSURE_ARG(_retval);
 
   nsCOMPtr<nsICacheStorage> storage;
@@ -740,17 +762,17 @@ NS_IMETHODIMP CacheStorageService::AppCa
 NS_IMETHODIMP CacheStorageService::SynthesizedCacheStorage(nsILoadContextInfo *aLoadContextInfo,
                                                            nsICacheStorage * *_retval)
 {
   NS_ENSURE_ARG(aLoadContextInfo);
   NS_ENSURE_ARG(_retval);
 
   nsCOMPtr<nsICacheStorage> storage;
   if (CacheObserver::UseNewCache()) {
-    storage = new CacheStorage(aLoadContextInfo, false, false, true /* skip size checks for synthesized cache */);
+    storage = new CacheStorage(aLoadContextInfo, false, false, true /* skip size checks for synthesized cache */, false /* no pinning */);
   }
   else {
     storage = new _OldStorage(aLoadContextInfo, false, false, false, nullptr);
   }
 
   storage.forget(_retval);
   return NS_OK;
 }
@@ -763,22 +785,25 @@ 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], nullptr, true, nullptr);
+      for (uint32_t i = 0; i < keys.Length(); ++i) {
+        DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
+      }
+
+      // Passing null as a load info means to evict all contexts.
+      // EvictByContext() respects the entry pinning.  EvictAll() does not.
+      rv = CacheFileIOManager::EvictByContext(nullptr, false);
+      NS_ENSURE_SUCCESS(rv, rv);
     }
-
-    rv = CacheFileIOManager::EvictAll();
-    NS_ENSURE_SUCCESS(rv, rv);
   } else {
     nsCOMPtr<nsICacheService> serv =
         do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = serv->EvictEntries(nsICache::STORE_ANYWHERE);
     NS_ENSURE_SUCCESS(rv, rv);
   }
@@ -808,16 +833,39 @@ NS_IMETHODIMP CacheStorageService::Purge
   }
 
   nsCOMPtr<nsIRunnable> event =
     new PurgeFromMemoryRunnable(this, what);
 
   return Dispatch(event);
 }
 
+NS_IMETHODIMP CacheStorageService::PurgeFromMemoryRunnable::Run()
+{
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    if (observerService) {
+      observerService->NotifyObservers(nullptr, "cacheservice:purge-memory-pools", nullptr);
+    }
+
+    return NS_OK;
+  }
+
+  if (mService) {
+    // TODO not all flags apply to both pools
+    mService->Pool(true).PurgeAll(mWhat);
+    mService->Pool(false).PurgeAll(mWhat);
+    mService = nullptr;
+  }
+
+  NS_DispatchToMainThread(this);
+  return NS_OK;
+}
+
 NS_IMETHODIMP CacheStorageService::AsyncGetDiskConsumption(
   nsICacheStorageConsumptionObserver* aObserver)
 {
   NS_ENSURE_ARG(aObserver);
 
   nsresult rv;
 
   if (CacheObserver::UseNewCache()) {
@@ -919,17 +967,17 @@ CacheStorageService::UnregisterEntry(Cac
   MOZ_ASSERT(mShutdown || (removedFrecency && removedExpiration));
 
   // Note: aEntry->CanRegister() since now returns false
   aEntry->SetRegistered(false);
 }
 
 static bool
 AddExactEntry(CacheEntryTable* aEntries,
-              nsCString const& aKey,
+              nsACString const& aKey,
               CacheEntry* aEntry,
               bool aOverwrite)
 {
   RefPtr<CacheEntry> existingEntry;
   if (!aOverwrite && aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
     bool equals = existingEntry == aEntry;
     LOG(("AddExactEntry [entry=%p equals=%d]", aEntry, equals));
     return equals; // Already there...
@@ -937,17 +985,17 @@ AddExactEntry(CacheEntryTable* aEntries,
 
   LOG(("AddExactEntry [entry=%p put]", aEntry));
   aEntries->Put(aKey, aEntry);
   return true;
 }
 
 static bool
 RemoveExactEntry(CacheEntryTable* aEntries,
-                 nsCString const& aKey,
+                 nsACString const& aKey,
                  CacheEntry* aEntry,
                  bool aOverwrite)
 {
   RefPtr<CacheEntry> existingEntry;
   if (!aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
     LOG(("RemoveExactEntry [entry=%p already gone]", aEntry));
     return false; // Already removed...
   }
@@ -1352,27 +1400,30 @@ CacheStorageService::AddStorageEntry(Cac
   NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
 
   NS_ENSURE_ARG(aStorage);
 
   nsAutoCString contextKey;
   CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
 
   return AddStorageEntry(contextKey, aURI, aIdExtension,
-                         aStorage->WriteToDisk(), aStorage->SkipSizeCheck(),
+                         aStorage->WriteToDisk(),
+                         aStorage->SkipSizeCheck(),
+                         aStorage->Pinning(),
                          aCreateIfNotExist, aReplace,
                          aResult);
 }
 
 nsresult
 CacheStorageService::AddStorageEntry(nsCSubstring const& aContextKey,
                                      nsIURI* aURI,
                                      const nsACString & aIdExtension,
                                      bool aWriteToDisk,
                                      bool aSkipSizeCheck,
+                                     bool aPin,
                                      bool aCreateIfNotExist,
                                      bool aReplace,
                                      CacheEntryHandle** aResult)
 {
   NS_ENSURE_ARG(aURI);
 
   nsresult rv;
 
@@ -1397,22 +1448,18 @@ CacheStorageService::AddStorageEntry(nsC
       entries = new CacheEntryTable(CacheEntryTable::ALL_ENTRIES);
       sGlobalEntryTables->Put(aContextKey, entries);
       LOG(("  new storage entries table for context '%s'", aContextKey.BeginReading()));
     }
 
     bool entryExists = entries->Get(entryKey, getter_AddRefs(entry));
 
     if (entryExists && !aReplace) {
-      // check whether the file is already doomed or we want to turn this entry
-      // to a memory-only.
-      if (MOZ_UNLIKELY(entry->IsFileDoomed())) {
-        LOG(("  file already doomed, replacing the entry"));
-        aReplace = true;
-      } else if (MOZ_UNLIKELY(!aWriteToDisk) && MOZ_LIKELY(entry->IsUsingDisk())) {
+      // check whether we want to turn this entry to a memory-only.
+      if (MOZ_UNLIKELY(!aWriteToDisk) && MOZ_LIKELY(entry->IsUsingDisk())) {
         LOG(("  entry is persistnet but we want mem-only, replacing it"));
         aReplace = true;
       }
     }
 
     // If truncate is demanded, delete and doom the current entry
     if (entryExists && aReplace) {
       entries->Remove(entryKey);
@@ -1425,17 +1472,17 @@ CacheStorageService::AddStorageEntry(nsC
 
       entry = nullptr;
       entryExists = false;
     }
 
     // Ensure entry for the particular URL, if not read/only
     if (!entryExists && (aCreateIfNotExist || aReplace)) {
       // Entry is not in the hashtable or has just been truncated...
-      entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk, aSkipSizeCheck);
+      entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk, aSkipSizeCheck, aPin);
       entries->Put(entryKey, entry);
       LOG(("  new entry %p for %s", entry.get(), entryKey.get()));
     }
 
     if (entry) {
       // Here, if this entry was not for a long time referenced by any consumer,
       // gets again first 'handles count' reference.
       handle = entry->NewHandle();
@@ -1663,70 +1710,79 @@ CacheStorageService::DoomStorageEntries(
   NS_ENSURE_ARG(aStorage);
 
   nsAutoCString contextKey;
   CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
 
   mozilla::MutexAutoLock lock(mLock);
 
   return DoomStorageEntries(contextKey, aStorage->LoadInfo(),
-                            aStorage->WriteToDisk(), aCallback);
+                            aStorage->WriteToDisk(), aStorage->Pinning(),
+                            aCallback);
 }
 
 nsresult
 CacheStorageService::DoomStorageEntries(nsCSubstring const& aContextKey,
                                         nsILoadContextInfo* aContext,
                                         bool aDiskStorage,
+                                        bool aPinned,
                                         nsICacheEntryDoomCallback* aCallback)
 {
+  LOG(("CacheStorageService::DoomStorageEntries [context=%s]", aContextKey.BeginReading()));
+
   mLock.AssertCurrentThreadOwns();
 
   NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
 
   nsAutoCString memoryStorageID(aContextKey);
   AppendMemoryStorageID(memoryStorageID);
 
   if (aDiskStorage) {
     LOG(("  dooming disk+memory storage of %s", aContextKey.BeginReading()));
 
-    // Just remove all entries, CacheFileIOManager will take care of the files.
-    sGlobalEntryTables->Remove(aContextKey);
-    sGlobalEntryTables->Remove(memoryStorageID);
+    // Walk one by one and remove entries according their pin status
+    CacheEntryTable *diskEntries, *memoryEntries;
+    if (sGlobalEntryTables->Get(aContextKey, &diskEntries)) {
+      sGlobalEntryTables->Get(memoryStorageID, &memoryEntries);
+
+      for (auto iter = diskEntries->Iter(); !iter.Done(); iter.Next()) {
+        auto entry = iter.Data();
+        if (entry->DeferOrBypassRemovalOnPinStatus(aPinned)) {
+          continue;
+        }
+
+        if (memoryEntries) {
+          RemoveExactEntry(memoryEntries, iter.Key(), entry, false);
+        }
+        iter.Remove();
+      }
+    }
 
     if (aContext && !aContext->IsPrivate()) {
       LOG(("  dooming disk entries"));
-      CacheFileIOManager::EvictByContext(aContext);
+      CacheFileIOManager::EvictByContext(aContext, aPinned);
     }
   } else {
     LOG(("  dooming memory-only storage of %s", aContextKey.BeginReading()));
 
-    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);
+    CacheEntryTable* diskEntries;
+    sGlobalEntryTables->Get(aContextKey, &diskEntries);
+    if (memoryEntries && diskEntries) {
+      for (auto iter = memoryEntries->Iter(); !iter.Done(); iter.Next()) {
+        auto entry = iter.Data();
+        RemoveExactEntry(diskEntries, iter.Key(), entry, false);
+      }
+    }
   }
 
   // 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.
@@ -1793,19 +1849,16 @@ CacheStorageService::CacheFileDoomed(nsI
 
   RefPtr<CacheEntry> entry;
   if (!entries->Get(entryKey, getter_AddRefs(entry)))
     return;
 
   if (!entry->IsFileDoomed())
     return;
 
-  if (entry->IsReferenced())
-    return;
-
   // Need to remove under the lock to avoid possible race leading
   // to duplication of the entry per its key.
   RemoveExactEntry(entries, entryKey, entry, false);
   entry->DoomAlreadyRemoved();
 }
 
 bool
 CacheStorageService::GetCacheEntryInfo(nsILoadContextInfo* aLoadContextInfo,
@@ -2105,10 +2158,51 @@ CacheStorageService::CollectReports(nsIM
     data.mHandleReport = aHandleReport;
     data.mData = aData;
     sGlobalEntryTables->EnumerateRead(&ReportStorageMemory, &data);
   }
 
   return NS_OK;
 }
 
+// nsICacheTesting
+
+NS_IMETHODIMP
+CacheStorageService::IOThreadSuspender::Run()
+{
+  MonitorAutoLock mon(mMon);
+  mon.Wait();
+  return NS_OK;
+}
+
+void
+CacheStorageService::IOThreadSuspender::Notify()
+{
+  MonitorAutoLock mon(mMon);
+  mon.Notify();
+}
+
+NS_IMETHODIMP
+CacheStorageService::SuspendCacheIOThread(uint32_t aLevel)
+{
+  RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
+  if (!thread) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  MOZ_ASSERT(!mActiveIOSuspender);
+  mActiveIOSuspender = new IOThreadSuspender();
+  return thread->Dispatch(mActiveIOSuspender, aLevel);
+}
+
+NS_IMETHODIMP
+CacheStorageService::ResumeCacheIOThread()
+{
+  MOZ_ASSERT(mActiveIOSuspender);
+
+  RefPtr<IOThreadSuspender> suspender;
+  suspender.swap(mActiveIOSuspender);
+  suspender->Notify();
+  return NS_OK;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/cache2/CacheStorageService.h
+++ b/netwerk/cache2/CacheStorageService.h
@@ -2,18 +2,19 @@
  * 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 CacheStorageService__h__
 #define CacheStorageService__h__
 
 #include "nsICacheStorageService.h"
 #include "nsIMemoryReporter.h"
+#include "nsITimer.h"
+#include "nsICacheTesting.h"
 
-#include "nsITimer.h"
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "nsProxyRelease.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/TimeStamp.h"
@@ -60,22 +61,24 @@ protected:
   explicit CacheMemoryConsumer(uint32_t aFlags);
   ~CacheMemoryConsumer() { DoMemoryReport(0); }
   void DoMemoryReport(uint32_t aCurrentSize);
 };
 
 class CacheStorageService final : public nsICacheStorageService
                                 , public nsIMemoryReporter
                                 , public nsITimerCallback
+                                , public nsICacheTesting
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSICACHESTORAGESERVICE
   NS_DECL_NSIMEMORYREPORTER
   NS_DECL_NSITIMERCALLBACK
+  NS_DECL_NSICACHETESTING
 
   CacheStorageService();
 
   void Shutdown();
   void DropPrivateBrowsingEntries();
 
   // Takes care of deleting any pending trashes for both cache1 and cache2
   // as well as the cache directory of an inactive cache version when requested.
@@ -268,22 +271,24 @@ private:
    * pool.
    */
   void PurgeOverMemoryLimit();
 
 private:
   nsresult DoomStorageEntries(nsCSubstring const& aContextKey,
                               nsILoadContextInfo* aContext,
                               bool aDiskStorage,
+                              bool aPin,
                               nsICacheEntryDoomCallback* aCallback);
   nsresult AddStorageEntry(nsCSubstring const& aContextKey,
                            nsIURI* aURI,
                            const nsACString & aIdExtension,
                            bool aWriteToDisk,
                            bool aSkipSizeCheck,
+                           bool aPin,
                            bool aCreateIfNotExist,
                            bool aReplace,
                            CacheEntryHandle** aResult);
 
   static CacheStorageService* sSelf;
 
   mozilla::Mutex mLock;
   mozilla::Mutex mForcedValidEntriesLock;
@@ -339,33 +344,42 @@ private:
   {
   public:
     PurgeFromMemoryRunnable(CacheStorageService* aService, uint32_t aWhat)
       : mService(aService), mWhat(aWhat) { }
 
   private:
     virtual ~PurgeFromMemoryRunnable() { }
 
-    NS_IMETHOD Run()
-    {
-      // TODO not all flags apply to both pools
-      mService->Pool(true).PurgeAll(mWhat);
-      mService->Pool(false).PurgeAll(mWhat);
-      return NS_OK;
-    }
+    NS_IMETHOD Run() override;
 
     RefPtr<CacheStorageService> mService;
     uint32_t mWhat;
   };
 
   // Used just for telemetry purposes, accessed only on the management thread.
   // Note: not included in the memory reporter, this is not expected to be huge
   // and also would be complicated to report since reporting happens on the main
   // thread but this table is manipulated on the management thread.
   nsDataHashtable<nsCStringHashKey, mozilla::TimeStamp> mPurgeTimeStamps;
+
+  // nsICacheTesting
+  class IOThreadSuspender : public nsRunnable
+  {
+  public:
+    IOThreadSuspender() : mMon("IOThreadSuspender") { }
+    void Notify();
+  private:
+    virtual ~IOThreadSuspender() { }
+    NS_IMETHOD Run() override;
+
+    Monitor mMon;
+  };
+
+  RefPtr<IOThreadSuspender> mActiveIOSuspender;
 };
 
 template<class T>
 void ProxyRelease(nsCOMPtr<T> &object, nsIThread* thread)
 {
   T* release;
   object.forget(&release);
 
--- a/netwerk/cache2/moz.build
+++ b/netwerk/cache2/moz.build
@@ -6,16 +6,17 @@
 
 XPIDL_SOURCES += [
     'nsICacheEntry.idl',
     'nsICacheEntryDoomCallback.idl',
     'nsICacheEntryOpenCallback.idl',
     'nsICacheStorage.idl',
     'nsICacheStorageService.idl',
     'nsICacheStorageVisitor.idl',
+    'nsICacheTesting.idl',
 ]
 
 XPIDL_MODULE = 'necko_cache2'
 
 EXPORTS += [
     'CacheObserver.h',
     'CacheStorageService.h',
 ]
--- a/netwerk/cache2/nsICacheStorageService.idl
+++ b/netwerk/cache2/nsICacheStorageService.idl
@@ -8,17 +8,17 @@ interface nsICacheStorage;
 interface nsILoadContextInfo;
 interface nsIApplicationCache;
 interface nsIEventTarget;
 interface nsICacheStorageConsumptionObserver;
 
 /**
  * Provides access to particual cache storages of the network URI cache.
  */
-[scriptable, uuid(9c9dc1d6-533e-4716-9ad8-11e08c3763b3)]
+[scriptable, uuid(ae29c44b-fbc3-4552-afaf-0a157ce771e7)]
 interface nsICacheStorageService : nsISupports
 {
   /**
    * Get storage where entries will only remain in memory, never written
    * to the disk.
    *
    * NOTE: Any existing disk entry for [URL|id-extension] will be doomed
    * prior opening an entry using this memory-only storage.  Result of
@@ -38,16 +38,23 @@ interface nsICacheStorageService : nsISu
    * @param aLookupAppCache
    *    When set true (for top level document loading channels) app cache will
    *    be first to check on to find entries in.
    */
   nsICacheStorage diskCacheStorage(in nsILoadContextInfo aLoadContextInfo,
                                    in bool aLookupAppCache);
 
   /**
+   * Get storage where entries will be written to disk and marked as pinned.
+   * These pinned entries are immune to over limit eviction and call of clear()
+   * on this service.
+   */
+  nsICacheStorage pinningCacheStorage(in nsILoadContextInfo aLoadContextInfo);
+
+  /**
    * Get storage for a specified application cache obtained using some different
    * mechanism.
    *
    * @param aLoadContextInfo
    *    Mandatory reference to a load context information.
    * @param aApplicationCache
    *    Optional reference to an existing appcache.  When left null, this will
    *    work with offline cache as a whole.
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/nsICacheTesting.idl
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This is an internal interface used only for testing purposes.
+ *
+ * THIS IS NOT AN API TO BE USED BY EXTENSIONS! ONLY USED BY MOZILLA TESTS.
+ */
+[scriptable, builtinclass, uuid(4e8ba935-92e1-4a74-944b-b1a2f02a7480)]
+interface nsICacheTesting : nsISupports
+{
+  void suspendCacheIOThread(in uint32_t aLevel);
+  void resumeCacheIOThread();
+};
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -257,16 +257,17 @@ nsHttpChannel::nsHttpChannel()
     , mRequestTimeInitialized(false)
     , mCacheEntryIsReadOnly(false)
     , mCacheEntryIsWriteOnly(false)
     , mCacheEntriesToWaitFor(0)
     , mHasQueryString(0)
     , mConcurentCacheAccess(0)
     , mIsPartialRequest(0)
     , mHasAutoRedirectVetoNotifier(0)
+    , mPinCacheContent(0)
     , mIsPackagedAppResource(0)
     , mIsCorsPreflightDone(0)
     , mPushedStream(nullptr)
     , mLocalBlocklist(false)
     , mWarningReporter(nullptr)
     , mDidReval(false)
 {
     LOG(("Creating nsHttpChannel [this=%p]\n", this));
@@ -2979,16 +2980,20 @@ nsHttpChannel::OpenCacheEntry(bool isHtt
     } else if (PossiblyIntercepted()) {
         // The synthesized cache has less restrictions on file size and so on.
         rv = cacheStorageService->SynthesizedCacheStorage(info,
             getter_AddRefs(cacheStorage));
     } else if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
         rv = cacheStorageService->MemoryCacheStorage(info, // ? choose app cache as well...
             getter_AddRefs(cacheStorage));
     }
+    else if (mPinCacheContent) {
+        rv = cacheStorageService->PinningCacheStorage(info,
+            getter_AddRefs(cacheStorage));
+    }
     else {
         rv = cacheStorageService->DiskCacheStorage(info,
             !mPostID && (mChooseApplicationCache || (mLoadFlags & LOAD_CHECK_OFFLINE_CACHE)),
             getter_AddRefs(cacheStorage));
     }
     NS_ENSURE_SUCCESS(rv, rv);
 
     if ((mClassOfService & nsIClassOfService::Leader) ||
@@ -6429,16 +6434,36 @@ nsHttpChannel::SetCacheOnlyMetadata(bool
     mCacheOnlyMetadata = aOnlyMetadata;
     if (aOnlyMetadata) {
         mLoadFlags |= LOAD_ONLY_IF_MODIFIED;
     }
 
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsHttpChannel::GetPin(bool *aPin)
+{
+    NS_ENSURE_ARG(aPin);
+    *aPin = mPinCacheContent;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetPin(bool aPin)
+{
+    LOG(("nsHttpChannel::SetPin [this=%p pin=%d]\n",
+        this, aPin));
+
+    ENSURE_CALLED_BEFORE_CONNECT();
+
+    mPinCacheContent = aPin;
+    return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsIResumableChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpChannel::ResumeAt(uint64_t aStartPos,
                         const nsACString& aEntityID)
 {
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -492,16 +492,19 @@ private:
     // when true, after we finish read from cache we must check all data
     // had been loaded from cache. If not, then an error has to be propagated
     // to the consumer.
     uint32_t                          mConcurentCacheAccess : 1;
     // whether the request is setup be byte-range
     uint32_t                          mIsPartialRequest : 1;
     // true iff there is AutoRedirectVetoNotifier on the stack
     uint32_t                          mHasAutoRedirectVetoNotifier : 1;
+    // consumers set this to true to use cache pinning, this has effect
+    // only when the channel is in an app context (load context has an appid)
+    uint32_t                          mPinCacheContent : 1;
     // Whether fetching the content is meant to be handled by the
     // packaged app service, which behaves like a caching layer.
     // Upon successfully fetching the package, the resource will be placed in
     // the cache, and served by calling OnCacheEntryAvailable.
     uint32_t                          mIsPackagedAppResource : 1;
     // True if CORS preflight has been performed
     uint32_t                          mIsCorsPreflightDone : 1;
 
--- a/netwerk/test/unit/head_cache.js
+++ b/netwerk/test/unit/head_cache.js
@@ -45,16 +45,17 @@ function createURI(urispec)
 function getCacheStorage(where, lci, appcache)
 {
   if (!lci) lci = LoadContextInfo.default;
   var svc = get_cache_service();
   switch (where) {
     case "disk": return svc.diskCacheStorage(lci, false);
     case "memory": return svc.memoryCacheStorage(lci);
     case "appcache": return svc.appCacheStorage(lci, appcache);
+    case "pin": return svc.pinningCacheStorage(lci);
   }
   return null;
 }
 
 function asyncOpenCacheEntry(key, where, flags, lci, callback, appcache)
 {
   key = createURI(key);
 
--- a/netwerk/test/unit/head_cache2.js
+++ b/netwerk/test/unit/head_cache2.js
@@ -44,16 +44,18 @@ const NOTWANTED =       1 << 11;
 // Tell the cache to wait for the entry to be completely written first
 const COMPLETE =        1 << 12;
 // Don't write meta/data and don't set valid in the callback, consumer will do it manually
 const DONTFILL =        1 << 13;
 // Used in combination with METAONLY, don't call setValid() on the entry after metadata has been set
 const DONTSETVALID =    1 << 14;
 // Notify before checking the data, useful for proper callback ordering checks
 const NOTIFYBEFOREREAD = 1 << 15;
+// It's allowed to not get an existing entry (result of opening is undetermined)
+const MAYBE_NEW =       1 << 16;
 
 var log_c2 = true;
 function LOG_C2(o, m)
 {
   if (!log_c2) return;
   if (!m)
     dump("TEST-INFO | CACHE2: " + o + "\n");
   else
@@ -145,16 +147,20 @@ OpenCallback.prototype =
       return Ci.nsICacheEntryOpenCallback.RECHECK_AFTER_WRITE_FINISHED;
     }
 
     LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_WANTED");
     return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
   },
   onCacheEntryAvailable: function(entry, isnew, appCache, status)
   {
+    if ((this.behavior & MAYBE_NEW) && isnew) {
+      this.behavior |= NEW;
+    }
+
     LOG_C2(this, "onCacheEntryAvailable, " + this.behavior);
     do_check_true(!this.onAvailPassed);
     this.onAvailPassed = true;
 
     do_check_eq(isnew, !!(this.behavior & NEW));
 
     if (this.behavior & (NOTFOUND|NOTWANTED)) {
       do_check_eq(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
@@ -202,19 +208,23 @@ OpenCallback.prototype =
           entry.close();
           if (self.behavior & WAITFORWRITE)
             self.goon(entry);
 
           return;
         }
         do_execute_soon(function() { // emulate more network latency
           if (self.behavior & DOOMED) {
+            LOG_C2(self, "checking doom state");
             try {
               var os = entry.openOutputStream(0);
-              do_check_true(false);
+              // Unfortunately, in the undetermined state we cannot even check whether the entry
+              // is actually doomed or not.
+              os.close();
+              do_check_true(!!(self.behavior & MAYBE_NEW));
             } catch (ex) {
               do_check_true(true);
             }
             if (self.behavior & WAITFORWRITE)
               self.goon(entry);
             return;
           }
 
@@ -253,19 +263,19 @@ OpenCallback.prototype =
         entry.close();
       });
     }
   },
   selfCheck: function()
   {
     LOG_C2(this, "selfCheck");
 
-    do_check_true(this.onCheckPassed);
+    do_check_true(this.onCheckPassed || (this.behavior & MAYBE_NEW));
     do_check_true(this.onAvailPassed);
-    do_check_true(this.onDataCheckPassed);
+    do_check_true(this.onDataCheckPassed || (this.behavior & MAYBE_NEW));
   },
   throwAndNotify: function(entry)
   {
     LOG_C2(this, "Throwing");
     var self = this;
     do_execute_soon(function() {
       LOG_C2(self, "Notifying");
       self.goon(entry);
@@ -381,16 +391,20 @@ MultipleCallbacks.prototype =
     if (--this.pending == 0)
     {
       var self = this;
       if (this.delayed)
         do_execute_soon(function() { self.goon(); });
       else
         this.goon();
     }
+  },
+  add: function()
+  {
+    ++this.pending;
   }
 }
 
 function MultipleCallbacks(number, goon, delayed)
 {
   this.pending = number;
   this.goon = goon;
   this.delayed = delayed;
rename from netwerk/test/unit/test_cache2-28-concurrent_read_resumable_entry_size_zero.js
rename to netwerk/test/unit/test_cache2-29a-concurrent_read_resumable_entry_size_zero.js
rename from netwerk/test/unit/test_cache2-29-concurrent_read_non-resumable_entry_size_zero.js
rename to netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30a-entry-pinning.js
@@ -0,0 +1,28 @@
+function run_test()
+{
+  do_get_profile();
+
+  // Open for write, write
+  asyncOpenCacheEntry("http://a/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, LoadContextInfo.default,
+    new OpenCallback(NEW|WAITFORWRITE, "a1m", "a1d", function(entry) {
+      // Open for read and check
+      asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
+        new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+
+          // Now clear the whole cache
+          get_cache_service().clear();
+
+          // The pinned entry should be intact
+          asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, LoadContextInfo.default,
+            new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+              finish_cache2_test();
+            })
+          );
+
+        })
+      );
+    })
+  );
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30b-pinning-storage-clear.js
@@ -0,0 +1,34 @@
+function run_test()
+{
+  do_get_profile();
+  var lci = LoadContextInfo.default;
+
+  // Open a pinned entry for write, write
+  asyncOpenCacheEntry("http://a/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
+    new OpenCallback(NEW|WAITFORWRITE, "a1m", "a1d", function(entry) {
+
+      // Now clear the disk storage, that should leave the pinned  entry in the cache
+      var diskStorage = getCacheStorage("disk", lci);
+      diskStorage.asyncEvictStorage(null);
+
+      // Open for read and check, it should still be there
+      asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+        new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+
+          // Now clear the pinning storage, entry should be gone
+          var pinningStorage = getCacheStorage("pin", lci);
+          pinningStorage.asyncEvictStorage(null);
+
+          asyncOpenCacheEntry("http://a/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+            new OpenCallback(NEW, "", "", function(entry) {
+              finish_cache2_test();
+            })
+          );
+
+        })
+      );
+    })
+  );
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30c-pinning-deferred-doom.js
@@ -0,0 +1,129 @@
+/*
+
+This is a complex test checking the internal "deferred doom" functionality in both CacheEntry and CacheFileHandle.
+
+- We create a batch of 10 non-pinned and 10 pinned entries, write something to them.
+- Then we purge them from memory, so they have to reload from disk.
+- After that the IO thread is suspended not to process events on the READ (3) level.  This forces opening operation and eviction
+  sync operations happen before we know actual pinning status of already cached entries.
+- We async-open the same batch of the 10+10 entries again, all should open as existing with the expected, previously stored
+  content
+- After all these entries are made to open, we clear the cache.  This does some synchronous operations on the entries
+  being open and also on the handles being in an already open state (but before the entry metadata has started to be read.)
+  Expected is to leave the pinned entries only.
+- Now, we resume the IO thread, so it start reading.  One could say this is a hack, but this can very well happen in reality
+  on slow disk or when a large number of entries is about to be open at once.  Suspending the IO thread is just doing this
+  simulation is a fully deterministic way and actually very easily and elegantly.
+- After the resume we want to open all those 10+10 entries once again (no purgin involved this time.).  It is expected
+  to open all the pinning entries intact and loose all the non-pinned entries (get them as new and empty again.)
+
+*/
+
+const kENTRYCOUNT = 10;
+
+function log_(msg) { if (true) dump(">>>>>>>>>>>>> " + msg + "\n"); }
+
+function run_test()
+{
+  do_get_profile();
+  var lci = LoadContextInfo.default;
+  var testingInterface = get_cache_service().QueryInterface(Ci.nsICacheTesting);
+  do_check_true(testingInterface);
+
+  var mc = new MultipleCallbacks(1, function() {
+    // (2)
+
+    mc = new MultipleCallbacks(1, finish_cache2_test);
+    // Release all references to cache entries so that they can be purged
+    // Calling gc() four times is needed to force it to actually release
+    // entries that are obviously unreferenced.  Yeah, I know, this is wacky...
+    gc();
+    gc();
+    do_execute_soon(() => {
+      gc();
+      gc();
+      log_("purging");
+
+      // Invokes cacheservice:purge-memory-pools when done.
+      get_cache_service().purgeFromMemory(Ci.nsICacheStorageService.PURGE_EVERYTHING); // goes to (3)
+    });
+  }, true);
+
+  // (1), here we start
+
+  var i;
+  for (i = 0; i < kENTRYCOUNT; ++i) {
+    log_("first set of opens");
+
+    // Callbacks 1-20
+    mc.add();
+    asyncOpenCacheEntry("http://pinned" + i + "/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
+      new OpenCallback(NEW|WAITFORWRITE, "m" + i, "p" + i, function(entry) { mc.fired(); }));
+
+    mc.add();
+    asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
+      new OpenCallback(NEW|WAITFORWRITE, "m" + i, "d" + i, function(entry) { mc.fired(); }));
+  }
+
+  mc.fired(); // Goes to (2)
+
+  var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+  os.addObserver({
+    observe: function(subject, topic, data)
+    {
+      // (3)
+
+      log_("after purge, second set of opens");
+      // Prevent the I/O thread from reading the data.  We first want to schedule clear of the cache.
+      // This deterministically emulates a slow hard drive.
+      testingInterface.suspendCacheIOThread(3);
+
+      // All entries should load
+      // Callbacks 21-40
+      for (i = 0; i < kENTRYCOUNT; ++i) {
+        mc.add();
+        asyncOpenCacheEntry("http://pinned" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+          new OpenCallback(NORMAL, "m" + i, "p" + i, function(entry) { mc.fired(); }));
+
+        // Unfortunately we cannot ensure that entries existing in the cache will be delivered to the consumer
+        // when soon after are evicted by some cache API call.  It's better to not ensure getting an entry
+        // than allowing to get an entry that was just evicted from the cache.  Entries may be delievered
+        // as new, but are already doomed.  Output stream cannot be openned, or the file handle is already
+        // writing to a doomed file.
+        //
+        // The API now just ensures that entries removed by any of the cache eviction APIs are never more
+        // available to consumers.
+        mc.add();
+        asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+          new OpenCallback(MAYBE_NEW|DOOMED, "m" + i, "d" + i, function(entry) { mc.fired(); }));
+      }
+
+      log_("clearing");
+      // Now clear everything except pinned, all entries are in state of reading
+      get_cache_service().clear();
+      log_("cleared");
+
+      // Resume reading the cache data, only now the pinning status on entries will be discovered,
+      // the deferred dooming code will trigger.
+      testingInterface.resumeCacheIOThread();
+
+      log_("third set of opens");
+      // Now open again.  Pinned entries should be there, disk entries should be the renewed entries.
+      // Callbacks 41-60
+      for (i = 0; i < kENTRYCOUNT; ++i) {
+        mc.add();
+        asyncOpenCacheEntry("http://pinned" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+          new OpenCallback(NORMAL, "m" + i, "p" + i, function(entry) { mc.fired(); }));
+
+        mc.add();
+        asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+          new OpenCallback(NEW, "m2" + i, "d2" + i, function(entry) { mc.fired(); }));
+      }
+
+      mc.fired(); // Finishes this test
+    }
+  }, "cacheservice:purge-memory-pools", false);
+
+
+  do_test_pending();
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js
@@ -0,0 +1,107 @@
+/*
+
+This test exercises the CacheFileContextEvictor::WasEvicted API and code using it.
+
+- We store 10+10 (pinned and non-pinned) entries to the cache, wait for them being written.
+- Then we purge the memory pools.
+- Now the IO thread is suspended on the EVICT (8) level to prevent actual deletion of the files.
+- Index is disabled.
+- We do clear() of the cache, this creates the "ce_*" file and posts to the EVICT level
+  the eviction loop mechanics.
+- We open again those 10+10 entries previously stored.
+- IO is resumed
+- We expect to get all the pinned and
+  loose all the non-pinned (common) entries.
+
+*/
+
+const kENTRYCOUNT = 10;
+
+function log_(msg) { if (true) dump(">>>>>>>>>>>>> " + msg + "\n"); }
+
+function run_test()
+{
+  do_get_profile();
+  var lci = LoadContextInfo.default;
+  var testingInterface = get_cache_service().QueryInterface(Ci.nsICacheTesting);
+  do_check_true(testingInterface);
+
+  var mc = new MultipleCallbacks(1, function() {
+    // (2)
+
+    mc = new MultipleCallbacks(1, finish_cache2_test);
+    // Release all references to cache entries so that they can be purged
+    // Calling gc() four times is needed to force it to actually release
+    // entries that are obviously unreferenced.  Yeah, I know, this is wacky...
+    gc();
+    gc();
+    do_execute_soon(() => {
+      gc();
+      gc();
+      log_("purging");
+
+      // Invokes cacheservice:purge-memory-pools when done.
+      get_cache_service().purgeFromMemory(Ci.nsICacheStorageService.PURGE_EVERYTHING); // goes to (3)
+    });
+  }, true);
+
+  // (1), here we start
+
+  var i;
+  for (i = 0; i < kENTRYCOUNT; ++i) {
+    log_("first set of opens");
+
+    // Callbacks 1-20
+    mc.add();
+    asyncOpenCacheEntry("http://pinned" + i + "/", "pin", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
+      new OpenCallback(NEW|WAITFORWRITE, "m" + i, "p" + i, function(entry) { mc.fired(); }));
+
+    mc.add();
+    asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_TRUNCATE, lci,
+      new OpenCallback(NEW|WAITFORWRITE, "m" + i, "d" + i, function(entry) { mc.fired(); }));
+  }
+
+  mc.fired(); // Goes to (2)
+
+  var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+  os.addObserver({
+    observe: function(subject, topic, data)
+    {
+      // (3)
+
+      log_("after purge");
+      // Prevent the I/O thread from evicting physically the data.  We first want to re-open the entries.
+      // This deterministically emulates a slow hard drive.
+      testingInterface.suspendCacheIOThread(8);
+
+      log_("clearing");
+      // Now clear everything except pinned.  Stores the "ce_*" file and schedules background eviction.
+      get_cache_service().clear();
+      log_("cleared");
+
+      log_("second set of opens");
+      // Now open again.  Pinned entries should be there, disk entries should be the renewed entries.
+      // Callbacks 21-40
+      for (i = 0; i < kENTRYCOUNT; ++i) {
+        mc.add();
+        asyncOpenCacheEntry("http://pinned" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+          new OpenCallback(NORMAL, "m" + i, "p" + i, function(entry) { mc.fired(); }));
+
+        mc.add();
+        asyncOpenCacheEntry("http://common" + i + "/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, lci,
+          new OpenCallback(NEW, "m2" + i, "d2" + i, function(entry) { mc.fired(); }));
+      }
+
+      // Resume IO, this will just pop-off the CacheFileContextEvictor::EvictEntries() because of
+      // an early check on CacheIOThread::YieldAndRerun() in that method.
+      // CacheFileIOManager::OpenFileInternal should now run and CacheFileContextEvictor::WasEvicted
+      // should be checked on.
+      testingInterface.resumeCacheIOThread();
+
+      mc.fired(); // Finishes this test
+    }
+  }, "cacheservice:purge-memory-pools", false);
+
+
+  do_test_pending();
+}
--- a/netwerk/test/unit/test_cache_jar.js
+++ b/netwerk/test/unit/test_cache_jar.js
@@ -94,17 +94,18 @@ function run_test() {
   httpserv.registerPathHandler("/cached", cached_handler);
   httpserv.start(-1);
   gTests = run_all_tests();
   gTests.next();
 }
 
 function doneFirstLoad(req, buffer, expected) {
   // Load it again, make sure it hits the cache
-  var chan = makeChan(URL, 0, false);
+  var nc = req.notificationCallbacks.getInterface(Ci.nsILoadContext);
+  var chan = makeChan(URL, nc.appId, nc.isInBrowserElement);
   chan.asyncOpen(new ChannelListener(doneSecondLoad, expected), null);
 }
 
 function doneSecondLoad(req, buffer, expected) {
   do_check_eq(handlers_called, expected);
   try {
     gTests.next();
   } catch (x) {
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -65,18 +65,22 @@ support-files =
 skip-if = os == "android"
 [test_cache2-27-force-valid-for.js]
 [test_cache2-28-last-access-attrs.js]
 # This test will be fixed in bug 1067931
 skip-if = true
 [test_cache2-28a-OPEN_SECRETLY.js]
 # This test will be fixed in bug 1067931
 skip-if = true
-[test_cache2-28-concurrent_read_resumable_entry_size_zero.js]
-[test_cache2-29-concurrent_read_non-resumable_entry_size_zero.js]
+[test_cache2-29a-concurrent_read_resumable_entry_size_zero.js]
+[test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js]
+[test_cache2-30a-entry-pinning.js]
+[test_cache2-30b-pinning-storage-clear.js]
+[test_cache2-30c-pinning-deferred-doom.js]
+[test_cache2-30d-pinning-WasEvicted-API.js]
 [test_partial_response_entry_size_smart_shrink.js]
 [test_304_responses.js]
 [test_421.js]
 [test_cacheForOfflineUse_no-store.js]
 [test_307_redirect.js]
 [test_NetUtil.js]
 [test_URIs.js]
 [test_URIs2.js]