Bug 1032254 - Provide a way to pin resources in the http cache r=honzab
☠☠ backed out by 877e06cd774b ☠ ☠
authorValentin Gosu <valentin.gosu@gmail.com>
Thu, 30 Jul 2015 11:40:00 +0200
changeset 287234 fb2a27db76bc5a570c482cb900fa439402c74a9d
parent 287233 4a0e7714b3ce5c964598dda08b988ed67d4059ac
child 287235 6efdf94d0b97bb3130ebc869c066c13071f4a36c
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab
bugs1032254
milestone42.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 - Provide a way to pin resources in the http cache r=honzab
netwerk/base/nsICachingChannel.idl
netwerk/cache2/CacheEntry.cpp
netwerk/cache2/CacheEntry.h
netwerk/cache2/CacheFile.cpp
netwerk/cache2/CacheFile.h
netwerk/cache2/CacheFileContextEvictor.cpp
netwerk/cache2/CacheFileIOManager.cpp
netwerk/cache2/CacheFileIOManager.h
netwerk/cache2/CacheFileUtils.cpp
netwerk/cache2/CacheFileUtils.h
netwerk/cache2/CacheObserver.cpp
netwerk/cache2/CacheStorage.h
netwerk/cache2/CacheStorageService.cpp
netwerk/cache2/CacheStorageService.h
netwerk/cache2/PinningCacheStorage.cpp
netwerk/cache2/PinningCacheStorage.h
netwerk/cache2/moz.build
netwerk/cache2/nsICacheStorageService.idl
netwerk/protocol/http/PackagedAppService.cpp
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/test/unit/head_cache.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-30-app-pinning.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/CacheEntry.cpp
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -158,25 +158,27 @@ nsresult CacheEntry::Callback::OnAvailTh
 NS_IMPL_ISUPPORTS(CacheEntry,
                   nsICacheEntry,
                   nsIRunnable,
                   CacheFileListener)
 
 CacheEntry::CacheEntry(const nsACString& aStorageID,
                        nsIURI* aURI,
                        const nsACString& aEnhanceID,
-                       bool aUseDisk)
+                       bool aUseDisk,
+                       uint32_t aPinningAppId)
 : mFrecency(0)
 , mSortingExpirationTime(uint32_t(-1))
 , mLock("CacheEntry")
 , mFileStatus(NS_ERROR_NOT_INITIALIZED)
 , mURI(aURI)
 , mEnhanceID(aEnhanceID)
 , mStorageID(aStorageID)
 , mUseDisk(aUseDisk)
+, mPinningAppId(aPinningAppId)
 , mIsDoomed(false)
 , mSecurityInfoLoaded(false)
 , mPreventCallbacks(false)
 , mHasData(false)
 , mState(NOTLOADED)
 , mRegistration(NEVERREGISTERED)
 , mWriter(nullptr)
 , mPredictedDataSize(0)
@@ -336,17 +338,18 @@ bool CacheEntry::Load(bool aTruncate, bo
   bool reportMiss = false;
 
   // Check the index under two conditions for two states and take appropriate action:
   // 1. When this is a disk entry and not told to truncate, check there is a disk file.
   //    If not, set the 'truncate' flag to true so that this entry will open instantly
   //    as a new one.
   // 2. When this is a memory-only entry, check there is a disk file.
   //    If there is or could be, doom that file.
-  if ((!aTruncate || !mUseDisk) && NS_SUCCEEDED(rv)) {
+  if ((!aTruncate || !mUseDisk) && NS_SUCCEEDED(rv) &&
+      !mPinningAppId) {
     // 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"));
         if (!aTruncate && mUseDisk) {
@@ -386,16 +389,17 @@ bool CacheEntry::Load(bool aTruncate, bo
         CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
     }
 
     LOG(("  performing load, file=%p", mFile.get()));
     if (NS_SUCCEEDED(rv)) {
       rv = mFile->Init(fileKey,
                        aTruncate,
                        !mUseDisk,
+                       mPinningAppId,
                        aPriority,
                        directLoad ? nullptr : this);
     }
 
     if (NS_FAILED(rv)) {
       mFileStatus = rv;
       AsyncDoom(nullptr);
       return false;
@@ -481,16 +485,17 @@ already_AddRefed<CacheEntryHandle> Cache
   nsRefPtr<CacheEntry> newEntry;
   {
     mozilla::MutexAutoUnlock unlock(mLock);
 
     // The following call dooms this entry (calls DoomAlreadyRemoved on us)
     nsresult rv = CacheStorageService::Self()->AddStorageEntry(
       GetStorageID(), GetURI(), GetEnhanceID(),
       mUseDisk && !aMemoryOnly,
+      mPinningAppId,
       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);
--- 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 aUseDisk, uint32_t aPinningAppId);
 
   void AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags);
 
   CacheEntryHandle* NewHandle();
 
 public:
   uint32_t GetMetadataMemoryConsumption();
   nsCString const &GetStorageID() const { return mStorageID; }
@@ -271,16 +271,19 @@ private:
   ::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;
 
+  // AppId of an app that wants this entry be pinned
+  uint32_t const mPinningAppId;
+
   // 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;
 
   // Following flags are all synchronized with the cache entry lock.
 
   // Whether security info has already been looked up in metadata.
   bool mSecurityInfoLoaded : 1;
--- a/netwerk/cache2/CacheFile.cpp
+++ b/netwerk/cache2/CacheFile.cpp
@@ -207,16 +207,17 @@ CacheFile::~CacheFile()
     WriteMetadataIfNeededLocked(true);
   }
 }
 
 nsresult
 CacheFile::Init(const nsACString &aKey,
                 bool aCreateNew,
                 bool aMemoryOnly,
+                uint32_t aPinningAppID,
                 bool aPriority,
                 CacheFileListener *aCallback)
 {
   MOZ_ASSERT(!mListener);
   MOZ_ASSERT(!mHandle);
 
   nsresult rv;
 
@@ -261,17 +262,17 @@ CacheFile::Init(const nsACString &aKey,
     }
 
     if (mPriority) {
       flags |= CacheFileIOManager::PRIORITY;
     }
 
     mOpeningFile = true;
     mListener = aCallback;
-    rv = CacheFileIOManager::OpenFile(mKey, flags, this);
+    rv = CacheFileIOManager::OpenFile(mKey, aPinningAppID, flags, this);
     if (NS_FAILED(rv)) {
       mListener = nullptr;
       mOpeningFile = false;
 
       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 "
--- a/netwerk/cache2/CacheFile.h
+++ b/netwerk/cache2/CacheFile.h
@@ -51,16 +51,17 @@ class CacheFile final : public CacheFile
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
 
   CacheFile();
 
   nsresult Init(const nsACString &aKey,
                 bool aCreateNew,
                 bool aMemoryOnly,
+                uint32_t aPinningAppID,
                 bool aPriority,
                 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,20 @@ public:
   // 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 IsWriteInProgress();
+  CacheFileIOManager* Manager()
+  {
+    return mHandle ? mHandle->Manager() : nullptr;
+  }
 
   // Memory reporting
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
 private:
   friend class CacheFileIOManager;
   friend class CacheFileChunk;
--- a/netwerk/cache2/CacheFileContextEvictor.cpp
+++ b/netwerk/cache2/CacheFileContextEvictor.cpp
@@ -393,17 +393,17 @@ CacheFileContextEvictor::GetContextFile(
                                         nsIFile **_retval)
 {
   nsresult rv;
 
   nsAutoCString leafName;
   leafName.AssignLiteral(CONTEXT_EVICTION_PREFIX);
 
   nsAutoCString keyPrefix;
-  CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, keyPrefix);
+  CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, false, keyPrefix);
 
   nsAutoCString data64;
   rv = Base64Encode(keyPrefix, data64);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Replace '/' with '-' since '/' cannot be part of the filename.
--- a/netwerk/cache2/CacheFileIOManager.cpp
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -103,33 +103,35 @@ 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)
-  : mHash(aHash)
+CacheFileHandle::CacheFileHandle(CacheFileIOManager* aManager, const SHA1Sum::Hash *aHash, bool aPriority)
+  : mManager(aManager)
+  , mHash(aHash)
   , mIsDoomed(false)
   , mPriority(aPriority)
   , mClosed(false)
   , mSpecialFile(false)
   , mInvalid(false)
   , mFileExists(false)
   , mFileSize(-1)
   , mFD(nullptr)
 {
   LOG(("CacheFileHandle::CacheFileHandle() [this=%p, hash=%08x%08x%08x%08x%08x]"
        , this, LOGSHA1(aHash)));
 }
 
-CacheFileHandle::CacheFileHandle(const nsACString &aKey, bool aPriority)
-  : mHash(nullptr)
+CacheFileHandle::CacheFileHandle(CacheFileIOManager* aManager, const nsACString &aKey, bool aPriority)
+  : mManager(aManager)
+  , mHash(nullptr)
   , mIsDoomed(false)
   , mPriority(aPriority)
   , mClosed(false)
   , mSpecialFile(true)
   , mInvalid(false)
   , mFileExists(false)
   , mFileSize(-1)
   , mFD(nullptr)
@@ -140,19 +142,18 @@ CacheFileHandle::CacheFileHandle(const n
 }
 
 CacheFileHandle::~CacheFileHandle()
 {
   LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]", this));
 
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
 
-  nsRefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance;
-  if (!IsClosed() && ioMan) {
-    ioMan->CloseHandleInternal(this);
+  if (!IsClosed() && mManager) {
+    mManager->CloseHandleInternal(this);
   }
 }
 
 void
 CacheFileHandle::Log()
 {
   nsAutoCString leafName;
   if (mFile) {
@@ -290,17 +291,18 @@ CacheFileHandles::HandleHashKey::SizeOfE
 
   return n;
 }
 
 /******************************************************************************
  *  CacheFileHandles
  *****************************************************************************/
 
-CacheFileHandles::CacheFileHandles()
+CacheFileHandles::CacheFileHandles(CacheFileIOManager* aManager)
+  : mManager(aManager)
 {
   LOG(("CacheFileHandles::CacheFileHandles() [this=%p]", this));
   MOZ_COUNT_CTOR(CacheFileHandles);
 }
 
 CacheFileHandles::~CacheFileHandles()
 {
   LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]", this));
@@ -371,17 +373,17 @@ CacheFileHandles::NewHandle(const SHA1Su
 #ifdef DEBUG_HANDLES
   Log(entry);
 #endif
 
 #ifdef DEBUG
   entry->AssertHandlesState();
 #endif
 
-  nsRefPtr<CacheFileHandle> handle = new CacheFileHandle(entry->Hash(), aPriority);
+  nsRefPtr<CacheFileHandle> handle = new CacheFileHandle(mManager, entry->Hash(), aPriority);
   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;
 }
@@ -539,24 +541,25 @@ public:
 
 protected:
   mozilla::Mutex   *mLock;
   mozilla::CondVar *mCondVar;
 };
 
 class OpenFileEvent : public nsRunnable {
 public:
-  OpenFileEvent(const nsACString &aKey, uint32_t aFlags,
+  OpenFileEvent(CacheFileIOManager *aIOMan,
+                const nsACString &aKey, uint32_t aFlags,
                 CacheFileIOListener *aCallback)
     : mFlags(aFlags)
     , mCallback(aCallback)
+    , mIOMan(aIOMan)
     , mKey(aKey)
   {
     MOZ_COUNT_CTOR(OpenFileEvent);
-    mIOMan = CacheFileIOManager::gInstance;
   }
 
 protected:
   ~OpenFileEvent()
   {
     MOZ_COUNT_DTOR(OpenFileEvent);
   }
 
@@ -624,17 +627,17 @@ protected:
 public:
   NS_IMETHOD Run()
   {
     nsresult rv;
 
     if (mHandle->IsClosed()) {
       rv = NS_ERROR_NOT_INITIALIZED;
     } else {
-      rv = CacheFileIOManager::gInstance->ReadInternal(
+      rv = mHandle->Manager()->ReadInternal(
         mHandle, mOffset, mBuf, mCount);
     }
 
     mCallback->OnDataRead(mHandle, mBuf, rv);
     return NS_OK;
   }
 
 protected:
@@ -674,21 +677,21 @@ protected:
 public:
   NS_IMETHOD Run()
   {
     nsresult rv;
 
     if (mHandle->IsClosed()) {
       rv = NS_ERROR_NOT_INITIALIZED;
     } else {
-      rv = CacheFileIOManager::gInstance->WriteInternal(
+      rv = mHandle->Manager()->WriteInternal(
           mHandle, mOffset, mBuf, mCount, mValidate, mTruncate);
       if (NS_FAILED(rv) && !mCallback) {
         // No listener is going to handle the error, doom the file
-        CacheFileIOManager::gInstance->DoomFileInternal(mHandle);
+        mHandle->Manager()->DoomFileInternal(mHandle);
       }
     }
     if (mCallback) {
       mCallback->OnDataWritten(mHandle, mBuf, rv);
     } else {
       free(const_cast<char *>(mBuf));
       mBuf = nullptr;
     }
@@ -725,17 +728,17 @@ protected:
 public:
   NS_IMETHOD Run()
   {
     nsresult rv;
 
     if (mHandle->IsClosed()) {
       rv = NS_ERROR_NOT_INITIALIZED;
     } else {
-      rv = CacheFileIOManager::gInstance->DoomFileInternal(mHandle);
+      rv = mHandle->Manager()->DoomFileInternal(mHandle);
     }
 
     if (mCallback) {
       mCallback->OnFileDoomed(mHandle, rv);
     }
 
     return NS_OK;
   }
@@ -744,26 +747,28 @@ protected:
   nsCOMPtr<CacheFileIOListener> mCallback;
   nsCOMPtr<nsIEventTarget>      mTarget;
   nsRefPtr<CacheFileHandle>     mHandle;
 };
 
 class DoomFileByKeyEvent : public nsRunnable {
 public:
   DoomFileByKeyEvent(const nsACString &aKey,
+                     CacheFileIOManager *aManager,
                      CacheFileIOListener *aCallback)
     : mCallback(aCallback)
+    , mIOMan(aManager)
   {
     MOZ_COUNT_CTOR(DoomFileByKeyEvent);
 
     SHA1Sum sum;
     sum.update(aKey.BeginReading(), aKey.Length());
     sum.finish(mHash);
 
-    mIOMan = CacheFileIOManager::gInstance;
+    mIOMan = aManager;
   }
 
 protected:
   ~DoomFileByKeyEvent()
   {
     MOZ_COUNT_DTOR(DoomFileByKeyEvent);
   }
 
@@ -805,17 +810,17 @@ protected:
   {
     MOZ_COUNT_DTOR(ReleaseNSPRHandleEvent);
   }
 
 public:
   NS_IMETHOD Run()
   {
     if (mHandle->mFD && !mHandle->IsClosed()) {
-      CacheFileIOManager::gInstance->ReleaseNSPRHandleInternal(mHandle);
+      mHandle->Manager()->ReleaseNSPRHandleInternal(mHandle);
     }
 
     return NS_OK;
   }
 
 protected:
   nsRefPtr<CacheFileHandle>     mHandle;
 };
@@ -841,17 +846,17 @@ protected:
 public:
   NS_IMETHOD Run()
   {
     nsresult rv;
 
     if (mHandle->IsClosed()) {
       rv = NS_ERROR_NOT_INITIALIZED;
     } else {
-      rv = CacheFileIOManager::gInstance->TruncateSeekSetEOFInternal(
+      rv = mHandle->Manager()->TruncateSeekSetEOFInternal(
         mHandle, mTruncatePos, mEOFPos);
     }
 
     if (mCallback) {
       mCallback->OnEOFSet(mHandle, rv);
     }
 
     return NS_OK;
@@ -884,18 +889,17 @@ protected:
 public:
   NS_IMETHOD Run()
   {
     nsresult rv;
 
     if (mHandle->IsClosed()) {
       rv = NS_ERROR_NOT_INITIALIZED;
     } else {
-      rv = CacheFileIOManager::gInstance->RenameFileInternal(mHandle,
-                                                             mNewName);
+      rv = mHandle->Manager()->RenameFileInternal(mHandle, mNewName);
     }
 
     if (mCallback) {
       mCallback->OnFileRenamed(mHandle, rv);
     }
 
     return NS_OK;
   }
@@ -1016,57 +1020,55 @@ public:
     , mFile(aFile)
     , mIOMan(aManager)
   { }
 
   virtual ~MetadataWriteScheduleEvent() { }
 
   NS_IMETHOD Run()
   {
-    nsRefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance;
-    if (!ioMan) {
+    if (!mIOMan) {
       NS_WARNING("CacheFileIOManager already gone in MetadataWriteScheduleEvent::Run()");
       return NS_OK;
     }
 
     switch (mMode)
     {
     case SCHEDULE:
-      ioMan->ScheduleMetadataWriteInternal(mFile);
+      mIOMan->ScheduleMetadataWriteInternal(mFile);
       break;
     case UNSCHEDULE:
-      ioMan->UnscheduleMetadataWriteInternal(mFile);
+      mIOMan->UnscheduleMetadataWriteInternal(mFile);
       break;
     case SHUTDOWN:
-      ioMan->ShutdownMetadataWriteSchedulingInternal();
+      mIOMan->ShutdownMetadataWriteSchedulingInternal();
       break;
     }
     return NS_OK;
   }
 };
 
 CacheFileIOManager * CacheFileIOManager::gInstance = nullptr;
 
 NS_IMPL_ISUPPORTS(CacheFileIOManager, nsITimerCallback)
 
 CacheFileIOManager::CacheFileIOManager()
-  : mShuttingDown(false)
+  : mKind(UNKNOWN)
+  , mShuttingDown(false)
   , mTreeCreated(false)
+  , mHandles(this)
   , mOverLimitEvicting(false)
   , mRemovingTrashDirs(false)
 {
   LOG(("CacheFileIOManager::CacheFileIOManager [this=%p]", this));
-  MOZ_COUNT_CTOR(CacheFileIOManager);
-  MOZ_ASSERT(!gInstance, "multiple CacheFileIOManager instances!");
 }
 
 CacheFileIOManager::~CacheFileIOManager()
 {
   LOG(("CacheFileIOManager::~CacheFileIOManager [this=%p]", this));
-  MOZ_COUNT_DTOR(CacheFileIOManager);
 }
 
 // static
 nsresult
 CacheFileIOManager::Init()
 {
   LOG(("CacheFileIOManager::Init()"));
 
@@ -1077,35 +1079,72 @@ CacheFileIOManager::Init()
   }
 
   nsRefPtr<CacheFileIOManager> ioMan = new CacheFileIOManager();
 
   nsresult rv = ioMan->InitInternal();
   NS_ENSURE_SUCCESS(rv, rv);
 
   ioMan.swap(gInstance);
+
+  // Ensure pinning managers static to avoid concurrent thread initiation.
+  Pinning::Self();
+
   return NS_OK;
 }
 
 nsresult
 CacheFileIOManager::InitInternal()
 {
   nsresult rv;
 
+  mKind = GENERAL;
   mIOThread = new CacheIOThread();
 
   rv = mIOThread->Init();
   MOZ_ASSERT(NS_SUCCEEDED(rv), "Can't create background thread");
   NS_ENSURE_SUCCESS(rv, rv);
 
   mStartTime = TimeStamp::NowLoRes();
 
   return NS_OK;
 }
 
+nsresult
+CacheFileIOManager::InitAsPinning(uint32_t aAppId, nsIFile* aProfileDir)
+{
+  if (!aProfileDir || !gInstance) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  mKind = PINNING;
+
+  MOZ_ASSERT(!mCacheDirectory);
+  MOZ_ASSERT(!mIOThread);
+
+  nsresult rv;
+
+  rv = aProfileDir->Clone(getter_AddRefs(mCacheDirectory));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mCacheDirectory->Append(NS_LITERAL_STRING("cache2"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoString appIdString;
+  appIdString.AppendInt(aAppId);
+  rv = mCacheDirectory->Append(appIdString);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mIOThread = gInstance->mIOThread;
+
+  mStartTime = TimeStamp::NowLoRes();
+
+  return NS_OK;
+}
+
 // static
 nsresult
 CacheFileIOManager::Shutdown()
 {
   LOG(("CacheFileIOManager::Shutdown() [gInstance=%p]", gInstance));
 
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -1128,31 +1167,35 @@ CacheFileIOManager::Shutdown()
     DebugOnly<nsresult> rv;
     rv = gInstance->mIOThread->Dispatch(ev, CacheIOThread::CLOSE);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
     condVar.Wait();
   }
 
   MOZ_ASSERT(gInstance->mHandles.HandleCount() == 0);
   MOZ_ASSERT(gInstance->mHandlesByLastUsed.Length() == 0);
+  MOZ_ASSERT(Pinning::Self()->Length() == 0);
 
   if (gInstance->mIOThread) {
     gInstance->mIOThread->Shutdown();
   }
 
   CacheIndex::Shutdown();
 
   if (CacheObserver::ClearCacheOnShutdown()) {
     Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE2_SHUTDOWN_CLEAR_PRIVATE> totalTimer;
     gInstance->SyncRemoveAllCacheFiles();
+    // mayhemer: should this apply to pinning apps as well? followup sufficient...
   }
 
   nsRefPtr<CacheFileIOManager> ioMan;
   ioMan.swap(gInstance);
 
+  Pinning::Self()->Destroy();
+
   return NS_OK;
 }
 
 nsresult
 CacheFileIOManager::ShutdownInternal()
 {
   LOG(("CacheFileIOManager::ShutdownInternal() [this=%p]", this));
 
@@ -1179,17 +1222,18 @@ CacheFileIOManager::ShutdownInternal()
 
     // Remove file if entry is doomed or invalid
     if (h->mFileExists && (h->mIsDoomed || h->mInvalid)) {
       LOG(("CacheFileIOManager::ShutdownInternal() - Removing file from disk"));
       h->mFile->Remove(false);
     }
 
     if (!h->IsSpecialFile() && !h->mIsDoomed &&
-        (h->mInvalid || !h->mFileExists)) {
+        (h->mInvalid || !h->mFileExists) &&
+        mKind != PINNING) {
       CacheIndex::RemoveEntry(h->Hash());
     }
 
     // Remove the handle from mHandles/mSpecialHandles
     if (h->IsSpecialFile()) {
       mSpecialHandles.RemoveElement(h);
     } else {
       mHandles.RemoveHandle(h);
@@ -1210,16 +1254,18 @@ CacheFileIOManager::ShutdownInternal()
   MOZ_ASSERT(mHandles.HandleCount() == 0);
 
   // Release trash directory enumerator
   if (mTrashDirEnumerator) {
     mTrashDirEnumerator->Close();
     mTrashDirEnumerator = nullptr;
   }
 
+  Pinning::Self()->ShutdownInternal();
+
   return NS_OK;
 }
 
 // static
 nsresult
 CacheFileIOManager::OnProfile()
 {
   LOG(("CacheFileIOManager::OnProfile() [gInstance=%p]", gInstance));
@@ -1294,16 +1340,18 @@ CacheFileIOManager::OnProfile()
 
   ioMan->mCacheProfilelessDirectory.swap(profilelessDirectory);
 #endif
 
   if (ioMan->mCacheDirectory) {
     CacheIndex::Init(ioMan->mCacheDirectory);
   }
 
+  Pinning::Self()->OnProfile();
+
   return NS_OK;
 }
 
 // static
 already_AddRefed<nsIEventTarget>
 CacheFileIOManager::IOTarget()
 {
   nsCOMPtr<nsIEventTarget> target;
@@ -1360,19 +1408,21 @@ CacheFileIOManager::IsShutdown()
   }
   return gInstance->mShuttingDown;
 }
 
 // static
 nsresult
 CacheFileIOManager::ScheduleMetadataWrite(CacheFile * aFile)
 {
+  // This can freely go to the global IO manager, no need for distinction
+  // since the timer code calls on the file that delegates to its handle
+  // which is already correctly bound to the correct manager.
   nsRefPtr<CacheFileIOManager> ioMan = gInstance;
   NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
-
   NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED);
 
   nsRefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
     ioMan, aFile, MetadataWriteScheduleEvent::SCHEDULE);
   nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
   NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
   return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
 }
@@ -1404,17 +1454,16 @@ CacheFileIOManager::ScheduleMetadataWrit
 }
 
 // static
 nsresult
 CacheFileIOManager::UnscheduleMetadataWrite(CacheFile * aFile)
 {
   nsRefPtr<CacheFileIOManager> ioMan = gInstance;
   NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
-
   NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED);
 
   nsRefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
     ioMan, aFile, MetadataWriteScheduleEvent::UNSCHEDULE);
   nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
   NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
   return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
 }
@@ -1467,48 +1516,73 @@ CacheFileIOManager::ShutdownMetadataWrit
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CacheFileIOManager::Notify(nsITimer * aTimer)
 {
-  MOZ_ASSERT(IsOnIOThreadOrCeased());
-  MOZ_ASSERT(mMetadataWritesTimer == aTimer);
-
-  mMetadataWritesTimer = nullptr;
-
-  nsTArray<nsRefPtr<CacheFile> > files;
-  files.SwapElements(mScheduledMetadataWrites);
-  for (uint32_t i = 0; i < files.Length(); ++i) {
-    CacheFile * file = files[i];
-    file->WriteMetadataIfNeeded();
-  }
-
-  return NS_OK;
+  if (mTrashTimer == aTimer) {
+    LOG(("CacheFileIOManager, trash timer [this=%p]", this));
+
+    mTrashTimer = nullptr;
+    StartRemovingTrash();
+
+    return NS_OK;
+  }
+
+  if (mMetadataWritesTimer == aTimer) {
+    LOG(("CacheFileIOManager, metadata write timer [this=%p]", this));
+
+    MOZ_ASSERT(IsOnIOThreadOrCeased());
+    mMetadataWritesTimer = nullptr;
+
+    nsTArray<nsRefPtr<CacheFile> > files;
+    files.SwapElements(mScheduledMetadataWrites);
+    for (uint32_t i = 0; i < files.Length(); ++i) {
+      CacheFile * file = files[i];
+      file->WriteMetadataIfNeeded();
+    }
+
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(false, "Unexpected timer in CacheFileIOManager::Notify()!");
+  return NS_ERROR_UNEXPECTED;
 }
 
 // static
 nsresult
 CacheFileIOManager::OpenFile(const nsACString &aKey,
                              uint32_t aFlags, CacheFileIOListener *aCallback)
 {
+  return OpenFile(aKey, nsILoadContextInfo::NO_APP_ID, aFlags, aCallback);
+}
+
+// static
+nsresult
+CacheFileIOManager::OpenFile(const nsACString &aKey, uint32_t aPinningAppId,
+                             uint32_t aFlags, CacheFileIOListener *aCallback)
+{
   LOG(("CacheFileIOManager::OpenFile() [key=%s, flags=%d, listener=%p]",
        PromiseFlatCString(aKey).get(), aFlags, aCallback));
 
   nsresult rv;
   nsRefPtr<CacheFileIOManager> ioMan = gInstance;
+  if (aPinningAppId != nsILoadContextInfo::NO_APP_ID && ioMan) {
+    ioMan = Pinning::Self()->Get(aPinningAppId);
+  }
 
   if (!ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   bool priority = aFlags & CacheFileIOManager::PRIORITY;
-  nsRefPtr<OpenFileEvent> ev = new OpenFileEvent(aKey, aFlags, aCallback);
+  nsRefPtr<OpenFileEvent> ev = new OpenFileEvent(ioMan, aKey, aFlags, aCallback);
   rv = ioMan->mIOThread->Dispatch(ev, priority
     ? CacheIOThread::OPEN_PRIORITY
     : CacheIOThread::OPEN);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
@@ -1552,29 +1626,34 @@ CacheFileIOManager::OpenFileInternal(con
     rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
     NS_ENSURE_SUCCESS(rv, rv);
 
     bool exists;
     rv = file->Exists(&exists);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (exists) {
-      CacheIndex::RemoveEntry(aHash);
+      if (mKind != PINNING) {
+        CacheIndex::RemoveEntry(aHash);
+      }
 
       LOG(("CacheFileIOManager::OpenFileInternal() - Removing old file from "
            "disk"));
       rv = file->Remove(false);
       if (NS_FAILED(rv)) {
         NS_WARNING("Cannot remove old entry from the disk");
         LOG(("CacheFileIOManager::OpenFileInternal() - Removing old file failed"
              ". [rv=0x%08x]", rv));
       }
     }
 
-    CacheIndex::AddEntry(aHash);
+    if (mKind != PINNING) {
+      CacheIndex::AddEntry(aHash);
+    }
+
     handle->mFile.swap(file);
     handle->mFileSize = 0;
   }
 
   if (handle) {
     handle.swap(*_retval);
     return NS_OK;
   }
@@ -1589,17 +1668,20 @@ CacheFileIOManager::OpenFileInternal(con
     } else {
       bool wasEvicted = false;
       mContextEvictor->WasEvicted(aKey, file, &wasEvicted);
       if (wasEvicted) {
         LOG(("CacheFileIOManager::OpenFileInternal() - Removing file since the "
              "entry was evicted by EvictByContext()"));
         exists = false;
         file->Remove(false);
-        CacheIndex::RemoveEntry(aHash);
+
+        if (mKind != PINNING) {
+          CacheIndex::RemoveEntry(aHash);
+        }
       }
     }
   }
 
   if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
@@ -1607,21 +1689,25 @@ CacheFileIOManager::OpenFileInternal(con
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (exists) {
     rv = file->GetFileSize(&handle->mFileSize);
     NS_ENSURE_SUCCESS(rv, rv);
 
     handle->mFileExists = true;
 
-    CacheIndex::EnsureEntryExists(aHash);
+    if (mKind != PINNING) {
+      CacheIndex::EnsureEntryExists(aHash);
+    }
   } else {
     handle->mFileSize = 0;
 
-    CacheIndex::AddEntry(aHash);
+    if (mKind != PINNING) {
+      CacheIndex::AddEntry(aHash);
+    }
   }
 
   handle->mFile.swap(file);
   handle.swap(*_retval);
   return NS_OK;
 }
 
 nsresult
@@ -1659,17 +1745,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(this, aKey, aFlags & PRIORITY);
     mSpecialHandles.AppendElement(handle);
 
     bool exists;
     rv = file->Exists(&exists);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (exists) {
       LOG(("CacheFileIOManager::OpenSpecialFileInternal() - Removing file from "
@@ -1694,17 +1780,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(this, aKey, aFlags & PRIORITY);
   mSpecialHandles.AppendElement(handle);
 
   if (exists) {
     rv = file->GetFileSize(&handle->mFileSize);
     NS_ENSURE_SUCCESS(rv, rv);
 
     handle->mFileExists = true;
   } else {
@@ -1735,17 +1821,18 @@ CacheFileIOManager::CloseHandleInternal(
   // Delete the file if the entry was doomed or invalid
   if (aHandle->mIsDoomed || aHandle->mInvalid) {
     LOG(("CacheFileIOManager::CloseHandleInternal() - Removing file from "
          "disk"));
     aHandle->mFile->Remove(false);
   }
 
   if (!aHandle->IsSpecialFile() && !aHandle->mIsDoomed &&
-      (aHandle->mInvalid || !aHandle->mFileExists)) {
+      (aHandle->mInvalid || !aHandle->mFileExists) &&
+      mKind != PINNING) {
     CacheIndex::RemoveEntry(aHandle->Hash());
   }
 
   // Don't remove handles after shutdown
   if (!mShuttingDown) {
     if (aHandle->IsSpecialFile()) {
       mSpecialHandles.RemoveElement(aHandle);
     } else {
@@ -1761,17 +1848,17 @@ nsresult
 CacheFileIOManager::Read(CacheFileHandle *aHandle, int64_t aOffset,
                          char *aBuf, int32_t aCount,
                          CacheFileIOListener *aCallback)
 {
   LOG(("CacheFileIOManager::Read() [handle=%p, offset=%lld, count=%d, "
        "listener=%p]", aHandle, aOffset, aCount, aCallback));
 
   nsresult rv;
-  nsRefPtr<CacheFileIOManager> ioMan = gInstance;
+  nsRefPtr<CacheFileIOManager> ioMan = aHandle->Manager();
 
   if (aHandle->IsClosed() || !ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   nsRefPtr<ReadEvent> ev = new ReadEvent(aHandle, aOffset, aBuf, aCount,
                                          aCallback);
   rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
@@ -1828,17 +1915,17 @@ CacheFileIOManager::Write(CacheFileHandl
                           const char *aBuf, int32_t aCount, bool aValidate,
                           bool aTruncate, CacheFileIOListener *aCallback)
 {
   LOG(("CacheFileIOManager::Write() [handle=%p, offset=%lld, count=%d, "
        "validate=%d, truncate=%d, listener=%p]", aHandle, aOffset, aCount,
        aValidate, aTruncate, aCallback));
 
   nsresult rv;
-  nsRefPtr<CacheFileIOManager> ioMan = gInstance;
+  nsRefPtr<CacheFileIOManager> ioMan = aHandle->Manager();
 
   if (aHandle->IsClosed() || !ioMan) {
     if (!aCallback) {
       // When no callback is provided, CacheFileIOManager is responsible for
       // releasing the buffer. We must release it even in case of failure.
       free(const_cast<char *>(aBuf));
     }
     return NS_ERROR_NOT_INITIALIZED;
@@ -1945,17 +2032,17 @@ CacheFileIOManager::WriteInternal(CacheF
       if (aHandle->mFileSize < writeEnd) {
         aHandle->mFileSize = writeEnd;
       }
     }
 
     uint32_t newSizeInK = aHandle->FileSizeInK();
 
     if (oldSizeInK != newSizeInK && !aHandle->IsDoomed() &&
-        !aHandle->IsSpecialFile()) {
+        !aHandle->IsSpecialFile() && mKind != PINNING) {
       CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, &newSizeInK);
 
       if (oldSizeInK < newSizeInK) {
         EvictIfOverLimitInternal();
       }
     }
   }
 
@@ -1975,17 +2062,17 @@ CacheFileIOManager::WriteInternal(CacheF
 nsresult
 CacheFileIOManager::DoomFile(CacheFileHandle *aHandle,
                              CacheFileIOListener *aCallback)
 {
   LOG(("CacheFileIOManager::DoomFile() [handle=%p, listener=%p]",
        aHandle, aCallback));
 
   nsresult rv;
-  nsRefPtr<CacheFileIOManager> ioMan = gInstance;
+  nsRefPtr<CacheFileIOManager> ioMan = aHandle->Manager();
 
   if (aHandle->IsClosed() || !ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   nsRefPtr<DoomFileEvent> ev = new DoomFileEvent(aHandle, aCallback);
   rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
     ? CacheIOThread::OPEN_PRIORITY
@@ -2032,54 +2119,61 @@ CacheFileIOManager::DoomFileInternal(Cac
       aHandle->mFileExists = false;
       rv = NS_OK;
     } else {
       NS_ENSURE_SUCCESS(rv, rv);
       aHandle->mFile.swap(file);
     }
   }
 
-  if (!aHandle->IsSpecialFile()) {
+  if (!aHandle->IsSpecialFile() && mKind != PINNING) {
     CacheIndex::RemoveEntry(aHandle->Hash());
   }
 
   aHandle->mIsDoomed = true;
 
   if (!aHandle->IsSpecialFile()) {
     nsRefPtr<CacheStorageService> storageService = CacheStorageService::Self();
     if (storageService) {
-      nsAutoCString idExtension, url;
+      CacheFileUtils::KeyInfo keyInfo;
       nsCOMPtr<nsILoadContextInfo> info =
-        CacheFileUtils::ParseKey(aHandle->Key(), &idExtension, &url);
+        CacheFileUtils::ParseKey(aHandle->Key(), &keyInfo);
       MOZ_ASSERT(info);
       if (info) {
-        storageService->CacheFileDoomed(info, idExtension, url);
+        storageService->CacheFileDoomed(info, &keyInfo);
       }
     }
   }
 
   return NS_OK;
 }
 
 // static
 nsresult
 CacheFileIOManager::DoomFileByKey(const nsACString &aKey,
                                   CacheFileIOListener *aCallback)
 {
   LOG(("CacheFileIOManager::DoomFileByKey() [key=%s, listener=%p]",
        PromiseFlatCString(aKey).get(), aCallback));
 
+  CacheFileUtils::KeyInfo keyInfo;
+  nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(aKey, &keyInfo);
+
   nsresult rv;
   nsRefPtr<CacheFileIOManager> ioMan = gInstance;
 
+  if (keyInfo.mPinningStorage && ioMan) {
+    ioMan = Pinning::Self()->Get(info->AppId());
+  }
+
   if (!ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
-  nsRefPtr<DoomFileByKeyEvent> ev = new DoomFileByKeyEvent(aKey, aCallback);
+  nsRefPtr<DoomFileByKeyEvent> ev = new DoomFileByKeyEvent(aKey, ioMan, aCallback);
   rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash *aHash)
@@ -2126,29 +2220,31 @@ CacheFileIOManager::DoomFileByKeyInterna
        "disk"));
   rv = file->Remove(false);
   if (NS_FAILED(rv)) {
     NS_WARNING("Cannot remove old entry from the disk");
     LOG(("CacheFileIOManager::DoomFileByKeyInternal() - Removing file failed. "
          "[rv=0x%08x]", rv));
   }
 
-  CacheIndex::RemoveEntry(aHash);
+  if (mKind != PINNING) {
+    CacheIndex::RemoveEntry(aHash);
+  }
 
   return NS_OK;
 }
 
 // static
 nsresult
 CacheFileIOManager::ReleaseNSPRHandle(CacheFileHandle *aHandle)
 {
   LOG(("CacheFileIOManager::ReleaseNSPRHandle() [handle=%p]", aHandle));
 
   nsresult rv;
-  nsRefPtr<CacheFileIOManager> ioMan = gInstance;
+  nsRefPtr<CacheFileIOManager> ioMan = aHandle->Manager();
 
   if (aHandle->IsClosed() || !ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   nsRefPtr<ReleaseNSPRHandleEvent> ev = new ReleaseNSPRHandleEvent(aHandle);
   rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::CLOSE);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -2160,17 +2256,17 @@ nsresult
 CacheFileIOManager::ReleaseNSPRHandleInternal(CacheFileHandle *aHandle)
 {
   LOG(("CacheFileIOManager::ReleaseNSPRHandleInternal() [handle=%p]", aHandle));
 
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
   MOZ_ASSERT(aHandle->mFD);
 
   DebugOnly<bool> found;
-  found = mHandlesByLastUsed.RemoveElement(aHandle);
+  found = gInstance->mHandlesByLastUsed.RemoveElement(aHandle);
   MOZ_ASSERT(found);
 
   PR_Close(aHandle->mFD);
   aHandle->mFD = nullptr;
 
   return NS_OK;
 }
 
@@ -2179,17 +2275,17 @@ nsresult
 CacheFileIOManager::TruncateSeekSetEOF(CacheFileHandle *aHandle,
                                        int64_t aTruncatePos, int64_t aEOFPos,
                                        CacheFileIOListener *aCallback)
 {
   LOG(("CacheFileIOManager::TruncateSeekSetEOF() [handle=%p, truncatePos=%lld, "
        "EOFPos=%lld, listener=%p]", aHandle, aTruncatePos, aEOFPos, aCallback));
 
   nsresult rv;
-  nsRefPtr<CacheFileIOManager> ioMan = gInstance;
+  nsRefPtr<CacheFileIOManager> ioMan = aHandle->Manager();
 
   if (aHandle->IsClosed() || !ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   nsRefPtr<TruncateSeekSetEOFEvent> ev = new TruncateSeekSetEOFEvent(
                                            aHandle, aTruncatePos, aEOFPos,
                                            aCallback);
@@ -2233,42 +2329,42 @@ void CacheFileIOManager::GetProfilelessC
 nsresult
 CacheFileIOManager::GetEntryInfo(const SHA1Sum::Hash *aHash,
                                  CacheStorageService::EntryInfoCallback *aCallback)
 {
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   nsresult rv;
 
+  // mayhemer: This method should take aPinningAppId argument.
   nsRefPtr<CacheFileIOManager> ioMan = gInstance;
   if (!ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
-  nsAutoCString enhanceId;
-  nsAutoCString uriSpec;
+  CacheFileUtils::KeyInfo keyInfo;
 
   nsRefPtr<CacheFileHandle> handle;
   ioMan->mHandles.GetHandle(aHash, getter_AddRefs(handle));
   if (handle) {
     nsRefPtr<nsILoadContextInfo> info =
-      CacheFileUtils::ParseKey(handle->Key(), &enhanceId, &uriSpec);
+      CacheFileUtils::ParseKey(handle->Key(), &keyInfo);
 
     MOZ_ASSERT(info);
     if (!info) {
       return NS_OK; // ignore
     }
 
     nsRefPtr<CacheStorageService> service = CacheStorageService::Self();
     if (!service) {
       return NS_ERROR_NOT_INITIALIZED;
     }
 
     // Invokes OnCacheEntryInfo when an existing entry is found
-    if (service->GetCacheEntryInfo(info, enhanceId, uriSpec, aCallback)) {
+    if (service->GetCacheEntryInfo(info, &keyInfo, aCallback)) {
       return NS_OK;
     }
 
     // When we are here, there is no existing entry and we need
     // to synchrnously load metadata from a disk file.
   }
 
   // Locate the actual file
@@ -2282,17 +2378,17 @@ CacheFileIOManager::GetEntryInfo(const S
     return NS_OK;
   }
 
   // Now get the context + enhance id + URL from the key.
   nsAutoCString key;
   metadata->GetKey(key);
 
   nsRefPtr<nsILoadContextInfo> info =
-    CacheFileUtils::ParseKey(key, &enhanceId, &uriSpec);
+    CacheFileUtils::ParseKey(key, &keyInfo);
   MOZ_ASSERT(info);
   if (!info) {
     return NS_OK;
   }
 
   // Pick all data to pass to the callback.
   int64_t dataSize = metadata->Offset();
   uint32_t fetchCount;
@@ -2304,18 +2400,18 @@ CacheFileIOManager::GetEntryInfo(const S
     expirationTime = 0;
   }
   uint32_t lastModified;
   if (NS_FAILED(metadata->GetLastModified(&lastModified))) {
     lastModified = 0;
   }
 
   // Call directly on the callback.
-  aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize, fetchCount,
-                         lastModified, expirationTime);
+  aCallback->OnEntryInfo(keyInfo.mURISpec, keyInfo.mIdEnhance,
+                         dataSize, fetchCount, lastModified, expirationTime);
 
   return NS_OK;
 }
 
 nsresult
 CacheFileIOManager::TruncateSeekSetEOFInternal(CacheFileHandle *aHandle,
                                                int64_t aTruncatePos,
                                                int64_t aEOFPos)
@@ -2392,17 +2488,17 @@ nsresult
 CacheFileIOManager::RenameFile(CacheFileHandle *aHandle,
                                const nsACString &aNewName,
                                CacheFileIOListener *aCallback)
 {
   LOG(("CacheFileIOManager::RenameFile() [handle=%p, newName=%s, listener=%p]",
        aHandle, PromiseFlatCString(aNewName).get(), aCallback));
 
   nsresult rv;
-  nsRefPtr<CacheFileIOManager> ioMan = gInstance;
+  nsRefPtr<CacheFileIOManager> ioMan = aHandle->Manager();
 
   if (aHandle->IsClosed() || !ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (!aHandle->IsSpecialFile()) {
     return NS_ERROR_UNEXPECTED;
   }
@@ -2503,16 +2599,20 @@ nsresult
 CacheFileIOManager::EvictIfOverLimitInternal()
 {
   LOG(("CacheFileIOManager::EvictIfOverLimitInternal()"));
 
   nsresult rv;
 
   MOZ_ASSERT(mIOThread->IsCurrentThread());
 
+  if (mKind == PINNING) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
   if (mShuttingDown) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (mOverLimitEvicting) {
     LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Eviction already "
          "running."));
     return NS_OK;
@@ -2564,16 +2664,17 @@ CacheFileIOManager::EvictIfOverLimitInte
 nsresult
 CacheFileIOManager::OverLimitEvictionInternal()
 {
   LOG(("CacheFileIOManager::OverLimitEvictionInternal()"));
 
   nsresult rv;
 
   MOZ_ASSERT(mIOThread->IsCurrentThread());
+  MOZ_ASSERT(mKind != PINNING);
 
   // mOverLimitEvicting is accessed only on IO thread, so we can set it to false
   // here and set it to true again once we dispatch another event that will
   // continue with the eviction. The reason why we do so is that we can fail
   // early anywhere in this method and the variable will contain a correct
   // value. Otherwise we would need to set it to false on every failing place.
   mOverLimitEvicting = false;
 
@@ -2696,16 +2797,41 @@ CacheFileIOManager::EvictAll()
   rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
+// static
+nsresult
+CacheFileIOManager::EvictPinned(uint32_t aPinningAppId)
+{
+  LOG(("CacheFileIOManager::EvictPinned(appId=%d)", aPinningAppId));
+
+  nsRefPtr<CacheFileIOManager> ioMan = gInstance;
+  if (ioMan) {
+    ioMan = Pinning::Self()->Get(aPinningAppId);
+  }
+  if (!ioMan) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  nsCOMPtr<nsIRunnable> ev;
+  ev = NS_NewRunnableMethod(ioMan, &CacheFileIOManager::EvictAllInternal);
+
+  nsresult rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 namespace {
 
 class EvictionNotifierRunnable : public nsRunnable
 {
 public:
   NS_DECL_NSIRUNNABLE
 };
 
@@ -2719,17 +2845,17 @@ EvictionNotifierRunnable::Run()
   return NS_OK;
 }
 
 } // namespace
 
 nsresult
 CacheFileIOManager::EvictAllInternal()
 {
-  LOG(("CacheFileIOManager::EvictAllInternal()"));
+  LOG(("CacheFileIOManager::EvictAllInternal(), this=%p", this));
 
   nsresult rv;
 
   MOZ_ASSERT(mIOThread->IsCurrentThread());
 
   nsRefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
 
   if (!mCacheDirectory) {
@@ -2784,28 +2910,39 @@ CacheFileIOManager::EvictAllInternal()
   NS_DispatchToMainThread(r);
 
   // Create a new empty entries directory
   rv = CheckAndCreateDir(mCacheDirectory, ENTRIES_DIR, false);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  CacheIndex::RemoveAll();
+  if (mKind != PINNING) {
+    CacheIndex::RemoveAll();
+  }
 
   return NS_OK;
 }
 
 // static
 nsresult
-CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo)
+CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo,
+                                   bool aIsPinning)
 {
   LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]",
        aLoadContextInfo));
 
+  if (aIsPinning) {
+    // This is a kinda hacky workaround, we simply delete everything that
+    // the app has pinned with disrespect to the load context separation.
+    // This API is dependent on the index, but index is global and only
+    // works for the general manager.
+    return CacheFileIOManager::EvictPinned(aLoadContextInfo->AppId());
+  }
+
   nsresult rv;
   nsRefPtr<CacheFileIOManager> ioMan = gInstance;
 
   if (!ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   nsCOMPtr<nsIRunnable> ev;
@@ -2932,17 +3069,17 @@ CacheFileIOManager::CacheIndexStateChang
   return NS_OK;
 }
 
 nsresult
 CacheFileIOManager::TrashDirectory(nsIFile *aFile)
 {
   nsAutoCString path;
   aFile->GetNativePath(path);
-  LOG(("CacheFileIOManager::TrashDirectory() [file=%s]", path.get()));
+  LOG(("CacheFileIOManager::TrashDirectory() [this=%p, file=%s]", this, path.get()));
 
   nsresult rv;
 
   MOZ_ASSERT(mIOThread->IsCurrentThread());
   MOZ_ASSERT(mCacheDirectory);
 
   // When the directory is empty, it is cheaper to remove it directly instead of
   // using the trash mechanism.
@@ -2996,33 +3133,16 @@ CacheFileIOManager::TrashDirectory(nsIFi
 
   rv = dir->MoveToNative(nullptr, leaf);
   NS_ENSURE_SUCCESS(rv, rv);
 
   StartRemovingTrash();
   return NS_OK;
 }
 
-// static
-void
-CacheFileIOManager::OnTrashTimer(nsITimer *aTimer, void *aClosure)
-{
-  LOG(("CacheFileIOManager::OnTrashTimer() [timer=%p, closure=%p]", aTimer,
-       aClosure));
-
-  nsRefPtr<CacheFileIOManager> ioMan = gInstance;
-
-  if (!ioMan) {
-    return;
-  }
-
-  ioMan->mTrashTimer = nullptr;
-  ioMan->StartRemovingTrash();
-}
-
 nsresult
 CacheFileIOManager::StartRemovingTrash()
 {
   LOG(("CacheFileIOManager::StartRemovingTrash()"));
 
   nsresult rv;
 
   MOZ_ASSERT(mIOThread->IsCurrentThread());
@@ -3052,19 +3172,18 @@ CacheFileIOManager::StartRemovingTrash()
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIEventTarget> ioTarget = IOTarget();
     MOZ_ASSERT(ioTarget);
 
     rv = timer->SetTarget(ioTarget);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = timer->InitWithFuncCallback(CacheFileIOManager::OnTrashTimer, nullptr,
-                                     kRemoveTrashStartDelay - elapsed,
-                                     nsITimer::TYPE_ONE_SHOT);
+    rv = timer->InitWithCallback(this, kRemoveTrashStartDelay - elapsed,
+                                 nsITimer::TYPE_ONE_SHOT);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mTrashTimer.swap(timer);
     return NS_OK;
   }
 
   nsCOMPtr<nsIRunnable> ev;
   ev = NS_NewRunnableMethod(this,
@@ -3255,22 +3374,26 @@ CacheFileIOManager::InitIndexEntry(Cache
                                    uint32_t         aAppId,
                                    bool             aAnonymous,
                                    bool             aInBrowser)
 {
   LOG(("CacheFileIOManager::InitIndexEntry() [handle=%p, appId=%u, anonymous=%d"
        ", inBrowser=%d]", aHandle, aAppId, aAnonymous, aInBrowser));
 
   nsresult rv;
-  nsRefPtr<CacheFileIOManager> ioMan = gInstance;
+  nsRefPtr<CacheFileIOManager> ioMan = aHandle->Manager();
 
   if (aHandle->IsClosed() || !ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
+  if (ioMan->Kind() == PINNING) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
   if (aHandle->IsSpecialFile()) {
     return NS_ERROR_UNEXPECTED;
   }
 
   nsRefPtr<InitIndexEntryEvent> ev =
     new InitIndexEntryEvent(aHandle, aAppId, aAnonymous, aInBrowser);
   rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -3285,22 +3408,26 @@ CacheFileIOManager::UpdateIndexEntry(Cac
                                      const uint32_t  *aExpirationTime)
 {
   LOG(("CacheFileIOManager::UpdateIndexEntry() [handle=%p, frecency=%s, "
        "expirationTime=%s]", aHandle,
        aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
        aExpirationTime ? nsPrintfCString("%u", *aExpirationTime).get() : ""));
 
   nsresult rv;
-  nsRefPtr<CacheFileIOManager> ioMan = gInstance;
+  nsRefPtr<CacheFileIOManager> ioMan = aHandle->Manager();
 
   if (aHandle->IsClosed() || !ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
+  if (ioMan->Kind() == PINNING) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
   if (aHandle->IsSpecialFile()) {
     return NS_ERROR_UNEXPECTED;
   }
 
   nsRefPtr<UpdateIndexEntryEvent> ev =
     new UpdateIndexEntryEvent(aHandle, aFrecency, aExpirationTime);
   rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -3618,26 +3745,26 @@ CacheFileIOManager::CreateCacheTree()
   return NS_OK;
 }
 
 nsresult
 CacheFileIOManager::OpenNSPRHandle(CacheFileHandle *aHandle, bool aCreate)
 {
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
   MOZ_ASSERT(!aHandle->mFD);
-  MOZ_ASSERT(mHandlesByLastUsed.IndexOf(aHandle) == mHandlesByLastUsed.NoIndex);
-  MOZ_ASSERT(mHandlesByLastUsed.Length() <= kOpenHandlesLimit);
+  MOZ_ASSERT(gInstance->mHandlesByLastUsed.IndexOf(aHandle) == gInstance->mHandlesByLastUsed.NoIndex);
+  MOZ_ASSERT(gInstance->mHandlesByLastUsed.Length() <= kOpenHandlesLimit);
   MOZ_ASSERT((aCreate && !aHandle->mFileExists) ||
              (!aCreate && aHandle->mFileExists));
 
   nsresult rv;
 
-  if (mHandlesByLastUsed.Length() == kOpenHandlesLimit) {
+  if (gInstance->mHandlesByLastUsed.Length() == kOpenHandlesLimit) {
     // close handle that hasn't been used for the longest time
-    rv = ReleaseNSPRHandleInternal(mHandlesByLastUsed[0]);
+    rv = ReleaseNSPRHandleInternal(gInstance->mHandlesByLastUsed[0]);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (aCreate) {
     rv = aHandle->mFile->OpenNSPRFileDesc(
            PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &aHandle->mFD);
     if (rv == NS_ERROR_FILE_ALREADY_EXISTS ||  // error from nsLocalFileWin
         rv == NS_ERROR_FILE_NO_DEVICE_SPACE) { // error from nsLocalFileUnix
@@ -3684,31 +3811,31 @@ CacheFileIOManager::OpenNSPRHandle(Cache
     if (NS_ERROR_FILE_NOT_FOUND == rv) {
       LOG(("  file doesn't exists"));
       aHandle->mFileExists = false;
       return DoomFileInternal(aHandle);
     }
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  mHandlesByLastUsed.AppendElement(aHandle);
+  gInstance->mHandlesByLastUsed.AppendElement(aHandle);
   return NS_OK;
 }
 
 void
 CacheFileIOManager::NSPRHandleUsed(CacheFileHandle *aHandle)
 {
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
   MOZ_ASSERT(aHandle->mFD);
 
   DebugOnly<bool> found;
-  found = mHandlesByLastUsed.RemoveElement(aHandle);
+  found = gInstance->mHandlesByLastUsed.RemoveElement(aHandle);
   MOZ_ASSERT(found);
 
-  mHandlesByLastUsed.AppendElement(aHandle);
+  gInstance->mHandlesByLastUsed.AppendElement(aHandle);
 }
 
 nsresult
 CacheFileIOManager::SyncRemoveDir(nsIFile *aFile, const char *aDir)
 {
   nsresult rv;
   nsCOMPtr<nsIFile> file;
 
@@ -3838,16 +3965,20 @@ CacheFileIOManager::UpdateSmartCacheSize
   if (!CacheObserver::UseNewCache()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   if (!CacheObserver::SmartCacheSizeEnabled()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  if (mKind == PINNING) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   // Wait at least kSmartSizeUpdateInterval before recomputing smart size.
   static const TimeDuration kUpdateLimit =
     TimeDuration::FromMilliseconds(kSmartSizeUpdateInterval);
   if (!mLastSmartSizeTime.IsNull() &&
       (TimeStamp::NowLoRes() - mLastSmartSizeTime) < kUpdateLimit) {
     return NS_OK;
   }
 
@@ -3979,19 +4110,29 @@ CacheFileIOManager::SizeOfExcludingThisI
   sizeOf = do_QueryInterface(mTrashDir);
   if (sizeOf)
     n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
 
   for (uint32_t i = 0; i < mFailedTrashDirs.Length(); ++i) {
     n += mFailedTrashDirs[i].SizeOfExcludingThisIfUnshared(mallocSizeOf);
   }
 
+  if (mKind == GENERAL) {
+    Pinning::Self()->SizeOfIncludingThis(mallocSizeOf);
+  }
+
   return n;
 }
 
+size_t
+CacheFileIOManager::SizeOfIncludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const
+{
+  return mallocSizeOf(this) + SizeOfExcludingThisInternal(mallocSizeOf);
+}
+
 // static
 size_t
 CacheFileIOManager::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
 {
   if (!gInstance)
     return 0;
 
   return gInstance->SizeOfExcludingThisInternal(mallocSizeOf);
@@ -3999,10 +4140,133 @@ CacheFileIOManager::SizeOfExcludingThis(
 
 // static
 size_t
 CacheFileIOManager::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
 {
   return mallocSizeOf(gInstance) + SizeOfExcludingThis(mallocSizeOf);
 }
 
+// CacheFileIOManager::Pinning
+
+CacheFileIOManager::Pinning*
+CacheFileIOManager::Pinning::sSelf = nullptr;
+
+bool
+CacheFileIOManager::Pinning::sDestroyed = false;
+
+CacheFileIOManager::Pinning::Pinning()
+  : mLock("CacheFileIOManager::Pinning")
+{
+  MOZ_ASSERT(!sSelf);
+  sSelf = this;
+  MOZ_COUNT_CTOR(CacheFileIOManager::Pinning);
+}
+
+CacheFileIOManager::Pinning::~Pinning()
+{
+  MOZ_ASSERT(sSelf);
+  sSelf = nullptr;
+  MOZ_COUNT_DTOR(CacheFileIOManager::Pinning);
+}
+
+// static
+CacheFileIOManager::Pinning*
+CacheFileIOManager::Pinning::Self()
+{
+  MOZ_ASSERT(!sDestroyed);
+  if (!sSelf) {
+    sSelf = new Pinning();
+  }
+  return sSelf;
+}
+
+void
+CacheFileIOManager::Pinning::Destroy()
+{
+  delete sSelf;
+  sDestroyed = true;
+}
+
+nsresult
+CacheFileIOManager::Pinning::OnProfile()
+{
+  nsresult rv;
+
+  rv = NS_GetSpecialDirectory(
+    NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mRoamingProfileDir));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+already_AddRefed<CacheFileIOManager>
+CacheFileIOManager::Pinning::Get(uint32_t aAppId)
+{
+  mozilla::MutexAutoLock lock(mLock);
+
+  nsRefPtr<CacheFileIOManager> pinningIOMan;
+  if (!mTable.Get(aAppId, getter_AddRefs(pinningIOMan))) {
+    pinningIOMan = new CacheFileIOManager();
+    LOG(("CacheFileIOManager::Pinning::Get, new manager %p for appid=%u",
+         pinningIOMan.get(), aAppId));
+
+    nsresult rv = pinningIOMan->InitAsPinning(aAppId, mRoamingProfileDir);
+    if (NS_FAILED(rv)) {
+      return nullptr;
+    }
+    mTable.Put(aAppId, pinningIOMan);
+  }
+
+  return pinningIOMan.forget();
+}
+
+// static
+PLDHashOperator
+CacheFileIOManager::Pinning::Collect(uint32_t const& aAppId,
+                                     nsRefPtr<CacheFileIOManager>& aIoMan,
+                                     void* aClosure)
+{
+  nsTArray<nsRefPtr<CacheFileIOManager> >* managers =
+    static_cast<nsTArray<nsRefPtr<CacheFileIOManager> >*>(aClosure);
+
+  managers->AppendElement()->swap(aIoMan);
+  return PL_DHASH_REMOVE;
+}
+
+void
+CacheFileIOManager::Pinning::ShutdownInternal()
+{
+  nsTArray<nsRefPtr<CacheFileIOManager> > managers;
+  {
+    mozilla::MutexAutoLock lock(mLock);
+    mTable.Enumerate(&Pinning::Collect, &managers);
+  }
+
+  for (uint32_t i = 0; i < managers.Length(); ++i) {
+    CacheFileIOManager* ioMan = managers[i];
+    ioMan->ShutdownInternal();
+  }
+}
+
+static
+size_t CollectManagerMemory(uint32_t const & aAppId,
+                            nsRefPtr<mozilla::net::CacheFileIOManager> const & aManager,
+                            mozilla::MallocSizeOf mallocSizeOf,
+                            void * aClosure)
+{
+  return aManager->SizeOfIncludingThisInternal(mallocSizeOf);
+}
+
+size_t
+CacheFileIOManager::Pinning::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
+{
+  return mTable.SizeOfExcludingThis(&CollectManagerMemory, mallocSizeOf, nullptr);
+}
+
+size_t
+CacheFileIOManager::Pinning::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
+{
+  return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/cache2/CacheFileIOManager.h
+++ b/netwerk/cache2/CacheFileIOManager.h
@@ -10,69 +10,74 @@
 #include "nsIEventTarget.h"
 #include "nsITimer.h"
 #include "nsCOMPtr.h"
 #include "mozilla/SHA1.h"
 #include "mozilla/TimeStamp.h"
 #include "nsTArray.h"
 #include "nsString.h"
 #include "nsTHashtable.h"
+#include "nsRefPtrHashtable.h"
 #include "prio.h"
 
 //#define DEBUG_HANDLES 1
 
 class nsIFile;
 class nsITimer;
 class nsIDirectoryEnumerator;
 class nsILoadContextInfo;
 
 namespace mozilla {
 namespace net {
 
 class CacheFile;
+class CacheFileIOManager;
 #ifdef DEBUG_HANDLES
 class CacheFileHandlesEntry;
 #endif
 
 #define ENTRIES_DIR "entries"
 #define DOOMED_DIR  "doomed"
 #define TRASH_DIR   "trash"
 
 
 class CacheFileHandle : public nsISupports
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   bool DispatchRelease();
 
-  CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority);
-  CacheFileHandle(const nsACString &aKey, bool aPriority);
+  CacheFileHandle(CacheFileIOManager* aManager, const SHA1Sum::Hash *aHash, bool aPriority);
+  CacheFileHandle(CacheFileIOManager* aManager, const nsACString &aKey, bool aPriority);
   CacheFileHandle(const CacheFileHandle &aOther);
   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; }
+  CacheFileIOManager* Manager() const { return mManager; }
 
   // 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;
 
   virtual ~CacheFileHandle();
 
+  nsRefPtr<CacheFileIOManager> mManager;
+
   const SHA1Sum::Hash *mHash;
   bool                 mIsDoomed;
   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
@@ -81,17 +86,17 @@ private:
   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(CacheFileIOManager* aManager);
   ~CacheFileHandles();
 
   nsresult GetHandle(const SHA1Sum::Hash *aHash, CacheFileHandle **_retval);
   nsresult NewHandle(const SHA1Sum::Hash *aHash, bool aPriority, CacheFileHandle **_retval);
   void     RemoveHandle(CacheFileHandle *aHandlle);
   void     GetAllHandles(nsTArray<nsRefPtr<CacheFileHandle> > *_retval);
   void     GetActiveHandles(nsTArray<nsRefPtr<CacheFileHandle> > *_retval);
   void     ClearAll();
@@ -163,16 +168,17 @@ public:
     // only and CacheFileHandle removes itself from this table in its dtor
     // that may only be called on the same thread as we work with the hashtable
     // since we dispatch its Release() to this thread.
     nsTArray<CacheFileHandle*> mHandles;
   };
 
 private:
   nsTHashtable<HandleHashKey> mTable;
+  CacheFileIOManager* mManager;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 
 class OpenFileEvent;
 class ReadEvent;
 class WriteEvent;
 class MetadataWriteScheduleEvent;
@@ -213,38 +219,48 @@ public:
   enum {
     OPEN         = 0U,
     CREATE       = 1U,
     CREATE_NEW   = 2U,
     PRIORITY     = 4U,
     SPECIAL_FILE = 8U
   };
 
+  enum EKind {
+    UNKNOWN,
+    GENERAL,
+    PINNING
+  };
+
   CacheFileIOManager();
 
   static nsresult Init();
   static nsresult Shutdown();
   static nsresult OnProfile();
   static already_AddRefed<nsIEventTarget> IOTarget();
   static already_AddRefed<CacheIOThread> IOThread();
   static bool IsOnIOThread();
   static bool IsOnIOThreadOrCeased();
   static bool IsShutdown();
 
+  EKind Kind() const { return mKind; }
+
   // Make aFile's WriteMetadataIfNeeded be called automatically after
   // a short interval.
   static nsresult ScheduleMetadataWrite(CacheFile * aFile);
   // Remove aFile from the scheduling registry array.
   // WriteMetadataIfNeeded will not be automatically called.
   static nsresult UnscheduleMetadataWrite(CacheFile * aFile);
   // Shuts the scheduling off and flushes all pending metadata writes.
   static nsresult ShutdownMetadataWriteScheduling();
 
   static nsresult OpenFile(const nsACString &aKey,
                            uint32_t aFlags, CacheFileIOListener *aCallback);
+  static nsresult OpenFile(const nsACString &aKey, uint32_t aPinningAppId,
+                           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);
   static nsresult DoomFile(CacheFileHandle *aHandle,
                            CacheFileIOListener *aCallback);
@@ -254,17 +270,19 @@ public:
   static nsresult TruncateSeekSetEOF(CacheFileHandle *aHandle,
                                      int64_t aTruncatePos, int64_t aEOFPos,
                                      CacheFileIOListener *aCallback);
   static nsresult RenameFile(CacheFileHandle *aHandle,
                              const nsACString &aNewName,
                              CacheFileIOListener *aCallback);
   static nsresult EvictIfOverLimit();
   static nsresult EvictAll();
-  static nsresult EvictByContext(nsILoadContextInfo *aLoadContextInfo);
+  static nsresult EvictPinned(uint32_t aPinningAppId);
+  static nsresult EvictByContext(nsILoadContextInfo *aLoadContextInfo,
+                                 bool aIsPinning);
 
   static nsresult InitIndexEntry(CacheFileHandle *aHandle,
                                  uint32_t         aAppId,
                                  bool             aAnonymous,
                                  bool             aInBrowser);
   static nsresult UpdateIndexEntry(CacheFileHandle *aHandle,
                                    const uint32_t  *aFrecency,
                                    const uint32_t  *aExpirationTime);
@@ -287,16 +305,19 @@ public:
   // Callable on the IO thread only.
   static nsresult GetEntryInfo(const SHA1Sum::Hash *aHash,
                                CacheStorageService::EntryInfoCallback *aCallback);
 
   // Memory reporting
   static size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
   static size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
+  size_t SizeOfIncludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;
+  size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;
+
 private:
   friend class CacheFileHandle;
   friend class CacheFileChunk;
   friend class CacheFile;
   friend class ShutdownEvent;
   friend class OpenFileEvent;
   friend class CloseHandleEvent;
   friend class ReadEvent;
@@ -308,16 +329,18 @@ private:
   friend class RenameFileEvent;
   friend class CacheIndex;
   friend class MetadataWriteScheduleEvent;
   friend class CacheFileContextEvictor;
 
   virtual ~CacheFileIOManager();
 
   nsresult InitInternal();
+  nsresult InitAsPinning(uint32_t aAppId, nsIFile* aProfileDir);
+
   nsresult ShutdownInternal();
 
   nsresult OpenFileInternal(const SHA1Sum::Hash *aHash,
                             const nsACString &aKey,
                             uint32_t aFlags,
                             CacheFileHandle **_retval);
   nsresult OpenSpecialFileInternal(const nsACString &aKey,
                                    uint32_t aFlags,
@@ -371,34 +394,68 @@ private:
   nsresult CacheIndexStateChangedInternal();
 
   // Smart size calculation. UpdateSmartCacheSize() must be called on IO thread.
   // It is called in EvictIfOverLimitInternal() just before we decide whether to
   // start overlimit eviction or not and also in OverLimitEvictionInternal()
   // before we start an eviction loop.
   nsresult UpdateSmartCacheSize(int64_t aFreeSpace);
 
-  // Memory reporting (private part)
-  size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;
+  static CacheFileIOManager           *gInstance;
+  // Each pinning app has its own intance of an IO manager setup to point to
+  // the roaming part of the profile plus identification by the appid.
+  class Pinning
+  {
+    nsRefPtrHashtable<nsUint32HashKey, CacheFileIOManager> mTable;
+    mozilla::Mutex mLock;
+    nsCOMPtr<nsIFile> mRoamingProfileDir;
+
+    static Pinning* sSelf;
+    static bool sDestroyed;
 
-  static CacheFileIOManager           *gInstance;
+    static PLDHashOperator Collect(uint32_t const& aAppId,
+                                   nsRefPtr<CacheFileIOManager>& aIoMan,
+                                   void* aClosure);
+    static PLDHashOperator Copy(uint32_t const& aAppId,
+                                nsRefPtr<CacheFileIOManager>& aIoMan,
+                                void* aClosure);
+  public:
+    Pinning();
+    ~Pinning();
+
+    static Pinning* Self();
+    static void Destroy();
+
+    nsresult OnProfile();
+    already_AddRefed<CacheFileIOManager> Get(uint32_t aPinningAppId);
+    void ShutdownInternal();
+#ifdef DEBUG
+    uint32_t Length() { return mTable.Count(); }
+#endif
+
+    size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+    size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+  };
+
+  EKind mKind;
+
   TimeStamp                            mStartTime;
   bool                                 mShuttingDown;
   nsRefPtr<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 *>   mHandlesByLastUsed;
   nsTArray<CacheFileHandle *>          mSpecialHandles;
   nsTArray<nsRefPtr<CacheFile> >       mScheduledMetadataWrites;
   nsCOMPtr<nsITimer>                   mMetadataWritesTimer;
   bool                                 mOverLimitEvicting;
   bool                                 mRemovingTrashDirs;
   nsCOMPtr<nsITimer>                   mTrashTimer;
   nsCOMPtr<nsIFile>                    mTrashDir;
   nsCOMPtr<nsIDirectoryEnumerator>     mTrashDirEnumerator;
--- a/netwerk/cache2/CacheFileUtils.cpp
+++ b/netwerk/cache2/CacheFileUtils.cpp
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CacheLog.h"
 #include "CacheFileUtils.h"
+#include "CacheStorage.h"
 #include "LoadContextInfo.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsString.h"
 #include <algorithm>
 
 
 namespace mozilla {
@@ -22,16 +23,17 @@ namespace {
  */
 class KeyParser
 {
 public:
   KeyParser(nsACString::const_iterator aCaret, nsACString::const_iterator aEnd)
     : caret(aCaret)
     , end(aEnd)
     // Initialize attributes to their default values
+    , pinningStorage(false)
     , appId(nsILoadContextInfo::NO_APP_ID)
     , isPrivate(false)
     , isInBrowser(false)
     , isAnonymous(false)
     // Initialize the cache key to a zero length by default
     , cacheKey(aEnd)
     , lastTag(0)
   {
@@ -39,16 +41,17 @@ public:
 
 private:
   // Current character being parsed
   nsACString::const_iterator caret;
   // The end of the buffer
   nsACString::const_iterator const end;
 
   // Results
+  bool pinningStorage;
   uint32_t appId;
   bool isPrivate;
   bool isInBrowser;
   bool isAnonymous;
   nsCString idEnhance;
   // Position of the cache key, if present
   nsACString::const_iterator cacheKey;
 
@@ -71,16 +74,19 @@ private:
 
     switch (tag) {
     case ':':
       // last possible tag, when present there is the cacheKey following,
       // not terminated with ',' and no need to unescape.
       cacheKey = caret;
       caret = end;
       return true;
+    case 'P':
+      pinningStorage = true;
+      break;
     case 'p':
       isPrivate = true;
       break;
     case 'b':
       isInBrowser = true;
       break;
     case 'a':
       isAnonymous = true;
@@ -189,53 +195,68 @@ public:
     // cacheKey is either pointing to end or the position where the cache key is.
     result.Assign(Substring(cacheKey, end));
   }
 
   void IdEnhance(nsACString &result)
   {
     result.Assign(idEnhance);
   }
+
+  void PinningStorage(bool &result)
+  {
+    result = pinningStorage;
+  }
 };
 
 } // namespace
 
 already_AddRefed<nsILoadContextInfo>
 ParseKey(const nsCSubstring &aKey,
-         nsCSubstring *aIdEnhance,
-         nsCSubstring *aURISpec)
+         KeyInfo* aKeyInfo)
 {
   nsACString::const_iterator caret, end;
   aKey.BeginReading(caret);
   aKey.EndReading(end);
 
   KeyParser parser(caret, end);
   nsRefPtr<LoadContextInfo> info = parser.Parse();
 
-  if (info) {
-    if (aIdEnhance)
-      parser.IdEnhance(*aIdEnhance);
-    if (aURISpec)
-      parser.URISpec(*aURISpec);
+  if (info && aKeyInfo) {
+    parser.IdEnhance(aKeyInfo->mIdEnhance);
+    parser.URISpec(aKeyInfo->mURISpec);
+    parser.PinningStorage(aKeyInfo->mPinningStorage);
   }
 
   return info.forget();
 }
 
 void
-AppendKeyPrefix(nsILoadContextInfo* aInfo, nsACString &_retval)
+AppendKeyPrefix(CacheStorage const* aStorage, nsACString &_retval)
+{
+  AppendKeyPrefix(aStorage->LoadInfo(), aStorage->IsPinning(), _retval);
+}
+
+void
+AppendKeyPrefix(nsILoadContextInfo* aInfo,
+                bool aPin,
+                nsACString &_retval)
 {
   /**
    * This key is used to salt file hashes.  When form of the key is changed
    * cache entries will fail to find on disk.
    *
    * IMPORTANT NOTE:
    * Keep the attributes list sorted according their ASCII code.
    */
 
+  if (aPin) {
+    _retval.AppendLiteral("P,");
+  }
+
   if (aInfo->IsAnonymous()) {
     _retval.AppendLiteral("a,");
   }
 
   if (aInfo->IsInBrowserElement()) {
     _retval.AppendLiteral("b,");
   }
 
--- a/netwerk/cache2/CacheFileUtils.h
+++ b/netwerk/cache2/CacheFileUtils.h
@@ -6,31 +6,36 @@
 #define CacheFileUtils__h__
 
 #include "nsError.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/TimeStamp.h"
+#include "CacheStorageService.h"
 
 class nsILoadContextInfo;
 class nsACString;
 
 namespace mozilla {
 namespace net {
+
+class CacheStorage;
+
 namespace CacheFileUtils {
 
 already_AddRefed<nsILoadContextInfo>
 ParseKey(const nsCSubstring &aKey,
-         nsCSubstring *aIdEnhance = nullptr,
-         nsCSubstring *aURISpec = nullptr);
+         KeyInfo* aKeyInfo = nullptr);
 
 void
-AppendKeyPrefix(nsILoadContextInfo *aInfo, nsACString &_retval);
+AppendKeyPrefix(CacheStorage const* aStorage, nsACString &_retval);
+void
+AppendKeyPrefix(nsILoadContextInfo *aInfo, bool aPin, nsACString &_retval);
 
 void
 AppendTagWithValue(nsACString & aTarget, char const aTag, nsCSubstring const & aValue);
 
 nsresult
 KeyMatchesLoadContextInfo(const nsACString &aKey,
                           nsILoadContextInfo *aInfo,
                           bool *_retval);
--- a/netwerk/cache2/CacheObserver.cpp
+++ b/netwerk/cache2/CacheObserver.cpp
@@ -424,16 +424,20 @@ CacheStorageEvictHelper::Run(mozIApplica
   NS_ENSURE_SUCCESS(rv, rv);
   rv = ClearStorage(false, aBrowserOnly, true);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = ClearStorage(true, aBrowserOnly, false);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = ClearStorage(true, aBrowserOnly, true);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // And finally evict everything that this app has stored as pinned
+  rv = CacheFileIOManager::EvictPinned(mAppId);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 nsresult
 CacheStorageEvictHelper::ClearStorage(bool const aPrivate,
                                       bool const aInBrowser,
                                       bool const aAnonymous)
 {
--- a/netwerk/cache2/CacheStorage.h
+++ b/netwerk/cache2/CacheStorage.h
@@ -49,16 +49,21 @@ class CacheStorage : public nsICacheStor
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSICACHESTORAGE
 
 public:
   CacheStorage(nsILoadContextInfo* aInfo,
                bool aAllowDisk,
                bool aLookupAppCache);
 
+  virtual bool IsPinning() const
+  {
+    return false;
+  }
+
 protected:
   virtual ~CacheStorage();
 
   nsresult ChooseApplicationCache(nsIURI* aURI, nsIApplicationCache** aCache);
 
   nsRefPtr<LoadContextInfo> mLoadContextInfo;
   bool mWriteToDisk : 1;
   bool mLookupAppCache : 1;
--- a/netwerk/cache2/CacheStorageService.cpp
+++ b/netwerk/cache2/CacheStorageService.cpp
@@ -4,16 +4,17 @@
 
 #include "CacheLog.h"
 #include "CacheStorageService.h"
 #include "CacheFileIOManager.h"
 #include "CacheObserver.h"
 #include "CacheIndex.h"
 #include "CacheIndexIterator.h"
 #include "CacheStorage.h"
+#include "PinningCacheStorage.h"
 #include "AppCacheStorage.h"
 #include "CacheEntry.h"
 #include "CacheFileUtils.h"
 
 #include "OldWrappers.h"
 #include "nsCacheService.h"
 #include "nsDeleteDir.h"
 
@@ -204,22 +205,22 @@ protected:
 };
 
 // WalkMemoryCacheRunnable
 // Responsible to visit memory storage and walk
 // all entries on it asynchronously.
 class WalkMemoryCacheRunnable : public WalkCacheRunnable
 {
 public:
-  WalkMemoryCacheRunnable(nsILoadContextInfo *aLoadInfo,
+  WalkMemoryCacheRunnable(CacheStorage const *aStorage,
                           bool aVisitEntries,
                           nsICacheStorageVisitor* aVisitor)
     : WalkCacheRunnable(aVisitor, aVisitEntries)
   {
-    CacheFileUtils::AppendKeyPrefix(aLoadInfo, mContextKey);
+    CacheFileUtils::AppendKeyPrefix(aStorage, mContextKey);
     MOZ_ASSERT(NS_IsMainThread());
   }
 
   nsresult Walk()
   {
     return mService->Dispatch(this);
   }
 
@@ -530,18 +531,19 @@ void CacheStorageService::DropPrivateBro
   mozilla::MutexAutoLock lock(mLock);
 
   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);
+  for (uint32_t i = 0; i < keys.Length(); ++i) {
+    DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
+  }
 }
 
 namespace {
 
 class CleaupCacheDirectoriesRunnable : public nsRunnable
 {
 public:
   NS_DECL_NSIRUNNABLE
@@ -711,16 +713,33 @@ NS_IMETHODIMP CacheStorageService::DiskC
   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;
+  }
+
+  nsCOMPtr<nsICacheStorage> storage =
+    new mozilla::net::PinningCacheStorage(aLoadContextInfo);
+
+  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;
@@ -746,17 +765,17 @@ NS_IMETHODIMP CacheStorageService::Clear
       mozilla::MutexAutoLock lock(mLock);
 
       NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
 
       nsTArray<nsCString> keys;
       sGlobalEntryTables->EnumerateRead(&CollectContexts, &keys);
 
       for (uint32_t i = 0; i < keys.Length(); ++i)
-        DoomStorageEntries(keys[i], nullptr, true, nullptr);
+        DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
     }
 
     rv = CacheFileIOManager::EvictAll();
     NS_ENSURE_SUCCESS(rv, rv);
   } else {
     nsCOMPtr<nsICacheService> serv =
         do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -1331,28 +1350,34 @@ CacheStorageService::AddStorageEntry(Cac
                                      bool aReplace,
                                      CacheEntryHandle** aResult)
 {
   NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
 
   NS_ENSURE_ARG(aStorage);
 
   nsAutoCString contextKey;
-  CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
+  CacheFileUtils::AppendKeyPrefix(aStorage, contextKey);
+
+  uint32_t pinningAppId = aStorage->IsPinning()
+    ? aStorage->LoadInfo()->AppId()
+    : nsILoadContextInfo::NO_APP_ID;
 
   return AddStorageEntry(contextKey, aURI, aIdExtension,
-                         aStorage->WriteToDisk(), aCreateIfNotExist, aReplace,
+                         aStorage->WriteToDisk(), pinningAppId,
+                         aCreateIfNotExist, aReplace,
                          aResult);
 }
 
 nsresult
 CacheStorageService::AddStorageEntry(nsCSubstring const& aContextKey,
                                      nsIURI* aURI,
                                      const nsACString & aIdExtension,
                                      bool aWriteToDisk,
+                                     uint32_t aPinningAppId,
                                      bool aCreateIfNotExist,
                                      bool aReplace,
                                      CacheEntryHandle** aResult)
 {
   NS_ENSURE_ARG(aURI);
 
   nsresult rv;
 
@@ -1405,17 +1430,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);
+      entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk, aPinningAppId);
       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();
@@ -1430,17 +1455,17 @@ nsresult
 CacheStorageService::CheckStorageEntry(CacheStorage const* aStorage,
                                        nsIURI* aURI,
                                        const nsACString & aIdExtension,
                                        bool* aResult)
 {
   nsresult rv;
 
   nsAutoCString contextKey;
-  CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
+  CacheFileUtils::AppendKeyPrefix(aStorage, contextKey);
 
   if (!aStorage->WriteToDisk()) {
     AppendMemoryStorageID(contextKey);
   }
 
   if (LOG_ENABLED()) {
     nsAutoCString uriSpec;
     aURI->GetAsciiSpec(uriSpec);
@@ -1466,16 +1491,21 @@ CacheStorageService::CheckStorageEntry(C
   }
 
   if (!aStorage->WriteToDisk()) {
     // Memory entry, nothing more to do.
     LOG(("  not found in hash tables"));
     return NS_OK;
   }
 
+  if (aStorage->IsPinning()) {
+    LOG(("  not implemented for pinning entries"));
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   // Disk entry, not found in the hashtable, check the index.
   nsAutoCString fileKey;
   rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, fileKey);
 
   CacheIndex::EntryStatus status;
   rv = CacheIndex::HasEntry(fileKey, &status);
   if (NS_FAILED(rv) || status == CacheIndex::DO_NOT_KNOW) {
     LOG(("  index doesn't know, rv=0x%08x", rv));
@@ -1552,17 +1582,17 @@ CacheStorageService::DoomStorageEntry(Ca
                                       nsICacheEntryDoomCallback* aCallback)
 {
   LOG(("CacheStorageService::DoomStorageEntry"));
 
   NS_ENSURE_ARG(aStorage);
   NS_ENSURE_ARG(aURI);
 
   nsAutoCString contextKey;
-  CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
+  CacheFileUtils::AppendKeyPrefix(aStorage, contextKey);
 
   nsAutoCString entryKey;
   nsresult rv = CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURI, entryKey);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsRefPtr<CacheEntry> entry;
   {
     mozilla::MutexAutoLock lock(mLock);
@@ -1593,17 +1623,17 @@ CacheStorageService::DoomStorageEntry(Ca
     LOG(("  dooming entry %p for %s", entry.get(), entryKey.get()));
     return entry->AsyncDoom(aCallback);
   }
 
   LOG(("  no entry loaded for %s", entryKey.get()));
 
   if (aStorage->WriteToDisk()) {
     nsAutoCString contextKey;
-    CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
+    CacheFileUtils::AppendKeyPrefix(aStorage, contextKey);
 
     rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, entryKey);
     NS_ENSURE_SUCCESS(rv, rv);
 
     LOG(("  dooming file only for %s", entryKey.get()));
 
     nsRefPtr<CacheEntryDoomByKeyCallback> callback(
       new CacheEntryDoomByKeyCallback(aCallback));
@@ -1638,28 +1668,30 @@ CacheStorageService::DoomStorageEntries(
                                         nsICacheEntryDoomCallback* aCallback)
 {
   LOG(("CacheStorageService::DoomStorageEntries"));
 
   NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
   NS_ENSURE_ARG(aStorage);
 
   nsAutoCString contextKey;
-  CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
+  CacheFileUtils::AppendKeyPrefix(aStorage, contextKey);
 
   mozilla::MutexAutoLock lock(mLock);
 
   return DoomStorageEntries(contextKey, aStorage->LoadInfo(),
-                            aStorage->WriteToDisk(), aCallback);
+                            aStorage->WriteToDisk(),
+                            aStorage->IsPinning(), aCallback);
 }
 
 nsresult
 CacheStorageService::DoomStorageEntries(nsCSubstring const& aContextKey,
                                         nsILoadContextInfo* aContext,
                                         bool aDiskStorage,
+                                        bool aPinningStorage,
                                         nsICacheEntryDoomCallback* aCallback)
 {
   mLock.AssertCurrentThreadOwns();
 
   NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
 
   nsAutoCString memoryStorageID(aContextKey);
   AppendMemoryStorageID(memoryStorageID);
@@ -1668,17 +1700,17 @@ CacheStorageService::DoomStorageEntries(
     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);
 
     if (aContext && !aContext->IsPrivate()) {
       LOG(("  dooming disk entries"));
-      CacheFileIOManager::EvictByContext(aContext);
+      CacheFileIOManager::EvictByContext(aContext, aPinningStorage);
     }
   } else {
     LOG(("  dooming memory-only storage of %s", aContextKey.BeginReading()));
 
     class MemoryEntriesRemoval {
     public:
       static PLDHashOperator EvictEntry(const nsACString& aKey,
                                         CacheEntry* aEntry,
@@ -1735,37 +1767,41 @@ CacheStorageService::WalkStorageEntries(
                                         bool aVisitEntries,
                                         nsICacheStorageVisitor* aVisitor)
 {
   LOG(("CacheStorageService::WalkStorageEntries [cb=%p, visitentries=%d]", aVisitor, aVisitEntries));
   NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
 
   NS_ENSURE_ARG(aStorage);
 
+  if (aStorage->IsPinning()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   if (aStorage->WriteToDisk()) {
     nsRefPtr<WalkDiskCacheRunnable> event =
       new WalkDiskCacheRunnable(aStorage->LoadInfo(), aVisitEntries, aVisitor);
     return event->Walk();
   }
 
   nsRefPtr<WalkMemoryCacheRunnable> event =
-    new WalkMemoryCacheRunnable(aStorage->LoadInfo(), aVisitEntries, aVisitor);
+    new WalkMemoryCacheRunnable(aStorage, aVisitEntries, aVisitor);
   return event->Walk();
 }
 
 void
 CacheStorageService::CacheFileDoomed(nsILoadContextInfo* aLoadContextInfo,
-                                     const nsACString & aIdExtension,
-                                     const nsACString & aURISpec)
+                                     CacheFileUtils::KeyInfo* aKeyInfo)
 {
   nsAutoCString contextKey;
-  CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey);
+  CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, aKeyInfo->mPinningStorage, contextKey);
 
   nsAutoCString entryKey;
-  CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURISpec, entryKey);
+  CacheEntry::HashingKey(EmptyCString(),
+                         aKeyInfo->mIdEnhance, aKeyInfo->mURISpec, entryKey);
 
   mozilla::MutexAutoLock lock(mLock);
 
   if (mShutdown)
     return;
 
   CacheEntryTable* entries;
   if (!sGlobalEntryTables->Get(contextKey, &entries))
@@ -1784,25 +1820,25 @@ CacheStorageService::CacheFileDoomed(nsI
   // Need to remove under the lock to avoid possible race leading
   // to duplication of the entry per its key.
   RemoveExactEntry(entries, entryKey, entry, false);
   entry->DoomAlreadyRemoved();
 }
 
 bool
 CacheStorageService::GetCacheEntryInfo(nsILoadContextInfo* aLoadContextInfo,
-                                       const nsACString & aIdExtension,
-                                       const nsACString & aURISpec,
+                                       CacheFileUtils::KeyInfo* aKeyInfo,
                                        EntryInfoCallback *aCallback)
 {
   nsAutoCString contextKey;
-  CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey);
+  CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, aKeyInfo->mPinningStorage, contextKey);
 
   nsAutoCString entryKey;
-  CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURISpec, entryKey);
+  CacheEntry::HashingKey(EmptyCString(),
+                         aKeyInfo->mIdEnhance, aKeyInfo->mURISpec, entryKey);
 
   nsRefPtr<CacheEntry> entry;
   {
     mozilla::MutexAutoLock lock(mLock);
 
     if (mShutdown) {
       return false;
     }
--- a/netwerk/cache2/CacheStorageService.h
+++ b/netwerk/cache2/CacheStorageService.h
@@ -24,16 +24,27 @@ class nsICacheEntryDoomCallback;
 class nsICacheStorageVisitor;
 class nsIRunnable;
 class nsIThread;
 class nsIEventTarget;
 
 namespace mozilla {
 namespace net {
 
+namespace CacheFileUtils {
+
+class KeyInfo {
+public:
+  bool mPinningStorage;
+  nsCString mIdEnhance;
+  nsCString mURISpec;
+};
+
+} // CacheFileUtils
+
 class CacheStorageService;
 class CacheStorage;
 class CacheEntry;
 class CacheEntryHandle;
 
 class CacheMemoryConsumer
 {
 private:
@@ -224,30 +235,28 @@ private:
   friend class CacheFileIOManager;
 
   /**
    * CacheFileIOManager uses this method to notify CacheStorageService that
    * an active entry was removed. This method is called even if the entry
    * removal was originated by CacheStorageService.
    */
   void CacheFileDoomed(nsILoadContextInfo* aLoadContextInfo,
-                       const nsACString & aIdExtension,
-                       const nsACString & aURISpec);
+                       CacheFileUtils::KeyInfo* aKeyInfo);
 
   /**
    * Tries to find an existing entry in the hashtables and synchronously call
    * OnCacheEntryInfo of the aVisitor callback when found.
    * @retuns
    *   true, when the entry has been found that also implies the callbacks has
-   *        beem invoked
+   *        been invoked
    *   false, when an entry has not been found
    */
   bool GetCacheEntryInfo(nsILoadContextInfo* aLoadContextInfo,
-                         const nsACString & aIdExtension,
-                         const nsACString & aURISpec,
+                         CacheFileUtils::KeyInfo* aKeyInfo,
                          EntryInfoCallback *aCallback);
 
 private:
   friend class CacheMemoryConsumer;
 
   /**
    * When memory consumption of this entry radically changes, this method
    * is called to reflect the size of allocated memory.  This call may purge
@@ -268,21 +277,23 @@ private:
    * pool.
    */
   void PurgeOverMemoryLimit();
 
 private:
   nsresult DoomStorageEntries(nsCSubstring const& aContextKey,
                               nsILoadContextInfo* aContext,
                               bool aDiskStorage,
+                              bool aPinningStorage,
                               nsICacheEntryDoomCallback* aCallback);
   nsresult AddStorageEntry(nsCSubstring const& aContextKey,
                            nsIURI* aURI,
                            const nsACString & aIdExtension,
                            bool aWriteToDisk,
+                           uint32_t aPinningAppId,
                            bool aCreateIfNotExist,
                            bool aReplace,
                            CacheEntryHandle** aResult);
 
   static CacheStorageService* sSelf;
 
   mozilla::Mutex mLock;
   mozilla::Mutex mForcedValidEntriesLock;
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/PinningCacheStorage.cpp
@@ -0,0 +1,25 @@
+/* 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 "PinningCacheStorage.h"
+
+namespace mozilla {
+namespace net {
+
+bool PinningCacheStorage::IsPinning() const
+{
+  if (LoadInfo()->AppId() == nsILoadContextInfo::NO_APP_ID) {
+    return false;
+  }
+
+  if (LoadInfo()->IsPrivate()) {
+    return false;
+  }
+
+  // We are a non-private app load, pin!
+  return true;
+}
+
+} // net
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/cache2/PinningCacheStorage.h
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PinninCacheStorage__h__
+#define PinninCacheStorage__h__
+
+#include "CacheStorage.h"
+
+namespace mozilla {
+namespace net {
+
+class PinningCacheStorage : public CacheStorage
+{
+public:
+  PinningCacheStorage(nsILoadContextInfo* aInfo)
+    : CacheStorage(aInfo, true, false)
+  {
+  }
+
+  virtual bool IsPinning() const override;
+};
+
+} // net
+} // mozilla
+
+#endif
--- a/netwerk/cache2/moz.build
+++ b/netwerk/cache2/moz.build
@@ -35,16 +35,17 @@ UNIFIED_SOURCES += [
     'CacheIndexContextIterator.cpp',
     'CacheIndexIterator.cpp',
     'CacheIOThread.cpp',
     'CacheLog.cpp',
     'CacheObserver.cpp',
     'CacheStorage.cpp',
     'CacheStorageService.cpp',
     'OldWrappers.cpp',
+    'PinningCacheStorage.cpp',
 ]
 
 # AppCacheStorage.cpp cannot be built in unified mode because it uses plarena.h.
 SOURCES += [
     'AppCacheStorage.cpp',
 ]
 
 LOCAL_INCLUDES += [
--- 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(44de2fa4-1b0e-4cd3-9e32-211e936f721e)]
+[scriptable, uuid(6764d6a5-af42-4655-aef2-81d9efe72ae6)]
 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,24 @@ 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);
 
   /**
+   * Entries bound to an app will be persisted in a roaming part of the profile
+   * and won't unpersist until the app goes away.  Common web content will use
+   * disk cache storage, when under the private browsing mode, entries will be
+   * held in memory and evicted on limit.
+   */
+  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.
--- a/netwerk/protocol/http/PackagedAppService.cpp
+++ b/netwerk/protocol/http/PackagedAppService.cpp
@@ -37,17 +37,17 @@ LogURI(const char *aFunctionName, void *
     if (aURI) {
       aURI->GetAsciiSpec(spec);
     } else {
       spec = "(null)";
     }
 
     nsAutoCString prefix;
     if (aInfo) {
-      CacheFileUtils::AppendKeyPrefix(aInfo, prefix);
+      CacheFileUtils::AppendKeyPrefix(aInfo, false, prefix);
       prefix += ":";
     }
 
     LOG(("[%p] %s > %s%s\n", self, aFunctionName, prefix.get(), spec.get()));
   }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -622,17 +622,17 @@ PackagedAppService::RequestURI(nsIURI *a
 
   nsCOMPtr<nsIURI> packageURI;
   rv = GetPackageURI(aURI, getter_AddRefs(packageURI));
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   nsAutoCString key;
-  CacheFileUtils::AppendKeyPrefix(aInfo, key);
+  CacheFileUtils::AppendKeyPrefix(aInfo, false, key);
 
   {
     nsAutoCString spec;
     packageURI->GetAsciiSpec(spec);
     key += ":";
     key += spec;
   }
 
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -255,16 +255,17 @@ nsHttpChannel::nsHttpChannel()
     , mRequestTimeInitialized(false)
     , mCacheEntryIsReadOnly(false)
     , mCacheEntryIsWriteOnly(false)
     , mCacheEntriesToWaitFor(0)
     , mHasQueryString(0)
     , mConcurentCacheAccess(0)
     , mIsPartialRequest(0)
     , mHasAutoRedirectVetoNotifier(0)
+    , mPinCacheContent(0)
     , mIsPackagedAppResource(0)
     , mPushedStream(nullptr)
     , mLocalBlocklist(false)
     , mWarningReporter(nullptr)
     , mDidReval(false)
 {
     LOG(("Creating nsHttpChannel [this=%p]\n", this));
     mChannelCreationTime = PR_Now();
@@ -2842,16 +2843,20 @@ nsHttpChannel::OpenCacheEntry(bool isHtt
         rv = cacheStorageService->AppCacheStorage(info,
             mApplicationCache,
             getter_AddRefs(cacheStorage));
     }
     else if (PossiblyIntercepted() || 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) ||
@@ -6266,16 +6271,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_ASYNC_OPEN();
+
+    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
@@ -474,16 +474,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;
 
     nsTArray<nsContinueRedirectionFunc> mRedirectFuncStack;
 
--- 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);
 
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-30-app-pinning.js
@@ -0,0 +1,30 @@
+function run_test()
+{
+  do_get_profile();
+
+  var applci = LoadContextInfo.custom(false, false, 1001, false);
+
+  // Open for write, write
+  asyncOpenCacheEntry("http://a/", "pin", Ci.nsICacheStorage.OPEN_NORMALLY, applci,
+    new OpenCallback(NEW|WAITFORWRITE, "a1m", "a1d", function(entry) {
+      // Open for read and check
+      asyncOpenCacheEntry("http://a/", "pin", Ci.nsICacheStorage.OPEN_NORMALLY, applci,
+        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/", "pin", Ci.nsICacheStorage.OPEN_NORMALLY, applci,
+            new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+              finish_cache2_test();
+            })
+          );
+
+        })
+      );
+    })
+  );
+
+  do_test_pending();
+}
--- a/netwerk/test/unit/test_cache_jar.js
+++ b/netwerk/test/unit/test_cache_jar.js
@@ -96,17 +96,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
@@ -63,18 +63,19 @@ 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-30-app-pinning.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]