Bug 1468501 - Implement a way to delete network cache by nsIPrincipal, r=mayhemer, r=michal
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 20 Jul 2018 13:57:18 +0200
changeset 485252 38e729d91ed2873b708bd4800b01b8433ffceabd
parent 485251 fac4d7589e5d24b9a9b182ec08a62ddf382c7864
child 485253 d4d2a2444ad149a8b86a615daf3a7c4bd8e88e8e
push id1815
push userffxbld-merge
push dateMon, 15 Oct 2018 10:40:45 +0000
treeherdermozilla-release@18d4c09e9378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmayhemer, michal
bugs1468501
milestone63.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 1468501 - Implement a way to delete network cache by nsIPrincipal, r=mayhemer, r=michal
netwerk/cache2/CacheFileContextEvictor.cpp
netwerk/cache2/CacheFileContextEvictor.h
netwerk/cache2/CacheFileIOManager.cpp
netwerk/cache2/CacheFileIOManager.h
netwerk/cache2/CacheStorageService.cpp
netwerk/cache2/CacheStorageService.h
netwerk/cache2/nsICacheStorageService.idl
netwerk/test/unit/test_cache2-32-clear-origin.js
netwerk/test/unit/xpcshell.ini
toolkit/components/cleardata/ClearDataService.js
--- a/netwerk/cache2/CacheFileContextEvictor.cpp
+++ b/netwerk/cache2/CacheFileContextEvictor.cpp
@@ -11,16 +11,17 @@
 #include "nsIFile.h"
 #include "LoadContextInfo.h"
 #include "nsThreadUtils.h"
 #include "nsString.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIDirectoryEnumerator.h"
 #include "mozilla/Base64.h"
 #include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/net/MozURL.h"
 
 
 namespace mozilla {
 namespace net {
 
 #define CONTEXT_EVICTION_PREFIX "ce_"
 const uint32_t kContextEvictionPrefixLength =
   sizeof(CONTEXT_EVICTION_PREFIX) - 1;
@@ -88,58 +89,62 @@ CacheFileContextEvictor::ContextsCount()
 {
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   return mEntries.Length();
 }
 
 nsresult
 CacheFileContextEvictor::AddContext(nsILoadContextInfo *aLoadContextInfo,
-                                    bool aPinned)
+                                    bool aPinned,
+                                    const nsAString& aOrigin)
 {
   LOG(("CacheFileContextEvictor::AddContext() [this=%p, loadContextInfo=%p, pinned=%d]",
        this, aLoadContextInfo, aPinned));
 
   nsresult rv;
 
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   CacheFileContextEvictorEntry *entry = nullptr;
   if (aLoadContextInfo) {
     for (uint32_t i = 0; i < mEntries.Length(); ++i) {
       if (mEntries[i]->mInfo &&
           mEntries[i]->mInfo->Equals(aLoadContextInfo) &&
-          mEntries[i]->mPinned == aPinned) {
+          mEntries[i]->mPinned == aPinned &&
+          mEntries[i]->mOrigin.Equals(aOrigin)) {
         entry = mEntries[i];
         break;
       }
     }
   } else {
     // Not providing load context info means we want to delete everything,
     // so let's not bother with any currently running context cleanups
     // for the same pinning state.
     for (uint32_t i = mEntries.Length(); i > 0;) {
       --i;
       if (mEntries[i]->mInfo && mEntries[i]->mPinned == aPinned) {
-        RemoveEvictInfoFromDisk(mEntries[i]->mInfo, mEntries[i]->mPinned);
+        RemoveEvictInfoFromDisk(mEntries[i]->mInfo, mEntries[i]->mPinned,
+                                mEntries[i]->mOrigin);
         mEntries.RemoveElementAt(i);
       }
     }
   }
 
   if (!entry) {
     entry = new CacheFileContextEvictorEntry();
     entry->mInfo = aLoadContextInfo;
     entry->mPinned = aPinned;
+    entry->mOrigin = aOrigin;
     mEntries.AppendElement(entry);
   }
 
   entry->mTimeStamp = PR_Now() / PR_USEC_PER_MSEC;
 
-  PersistEvictionInfoToDisk(aLoadContextInfo, aPinned);
+  PersistEvictionInfoToDisk(aLoadContextInfo, aPinned, aOrigin);
 
   if (mIndexIsUpToDate) {
     // Already existing context could be added again, in this case the iterator
     // would be recreated. Close the old iterator explicitely.
     if (entry->mIterator) {
       entry->mIterator->Close();
       entry->mIterator = nullptr;
     }
@@ -258,27 +263,28 @@ CacheFileContextEvictor::WasEvicted(cons
     }
   }
 
   return NS_OK;
 }
 
 nsresult
 CacheFileContextEvictor::PersistEvictionInfoToDisk(
-  nsILoadContextInfo *aLoadContextInfo, bool aPinned)
+  nsILoadContextInfo *aLoadContextInfo, bool aPinned,
+  const nsAString& aOrigin)
 {
   LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() [this=%p, "
        "loadContextInfo=%p]", this, aLoadContextInfo));
 
   nsresult rv;
 
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   nsCOMPtr<nsIFile> file;
-  rv = GetContextFile(aLoadContextInfo, aPinned, getter_AddRefs(file));
+  rv = GetContextFile(aLoadContextInfo, aPinned, aOrigin, getter_AddRefs(file));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCString path = file->HumanReadablePath();
 
   PRFileDesc *fd;
   rv = file->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600,
@@ -294,27 +300,27 @@ CacheFileContextEvictor::PersistEviction
   LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() - Successfully "
        "created file. [path=%s]", path.get()));
 
   return NS_OK;
 }
 
 nsresult
 CacheFileContextEvictor::RemoveEvictInfoFromDisk(
-  nsILoadContextInfo *aLoadContextInfo, bool aPinned)
+  nsILoadContextInfo *aLoadContextInfo, bool aPinned, const nsAString& aOrigin)
 {
   LOG(("CacheFileContextEvictor::RemoveEvictInfoFromDisk() [this=%p, "
        "loadContextInfo=%p]", this, aLoadContextInfo));
 
   nsresult rv;
 
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   nsCOMPtr<nsIFile> file;
-  rv = GetContextFile(aLoadContextInfo, aPinned, getter_AddRefs(file));
+  rv = GetContextFile(aLoadContextInfo, aPinned, aOrigin, getter_AddRefs(file));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCString path = file->HumanReadablePath();
 
   rv = file->Remove(false);
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -388,16 +394,26 @@ CacheFileContextEvictor::LoadEvictInfoFr
       continue;
     }
 
     bool pinned = decoded[0] == '\t';
     if (pinned) {
       decoded = Substring(decoded, 1);
     }
 
+    // Let's see if we have an origin.
+    nsAutoCString origin;
+    if (decoded.Contains('\t')) {
+      auto split = decoded.Split('\t');
+      MOZ_ASSERT(decoded.CountChar('\t') == 2);
+
+      origin = split.Get(0);
+      decoded = split.Get(1);
+    }
+
     nsCOMPtr<nsILoadContextInfo> info;
     if (!NS_LITERAL_CSTRING("*").Equals(decoded)) {
       // "*" is indication of 'delete all', info left null will pass
       // to CacheFileContextEvictor::AddContext and clear all the cache data.
       info = CacheFileUtils::ParseKey(decoded);
       if (!info) {
         LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() - Cannot parse "
              "context key, removing file. [contextKey=%s, file=%s]",
@@ -412,26 +428,28 @@ CacheFileContextEvictor::LoadEvictInfoFr
     rv = file->GetLastModifiedTime(&lastModifiedTime);
     if (NS_FAILED(rv)) {
       continue;
     }
 
     CacheFileContextEvictorEntry *entry = new CacheFileContextEvictorEntry();
     entry->mInfo = info;
     entry->mPinned = pinned;
+    CopyUTF8toUTF16(origin, entry->mOrigin);
     entry->mTimeStamp = lastModifiedTime;
     mEntries.AppendElement(entry);
   }
 
   return NS_OK;
 }
 
 nsresult
 CacheFileContextEvictor::GetContextFile(nsILoadContextInfo *aLoadContextInfo,
                                         bool aPinned,
+                                        const nsAString& aOrigin,
                                         nsIFile **_retval)
 {
   nsresult rv;
 
   nsAutoCString leafName;
   leafName.AssignLiteral(CONTEXT_EVICTION_PREFIX);
 
   nsAutoCString keyPrefix;
@@ -440,16 +458,20 @@ CacheFileContextEvictor::GetContextFile(
     // Tab is chosen because it can never be used as a context key tag.
     keyPrefix.Append('\t');
   }
   if (aLoadContextInfo) {
     CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, keyPrefix);
   } else {
     keyPrefix.Append('*');
   }
+  if (!aOrigin.IsEmpty()) {
+    keyPrefix.Append('\t');
+    keyPrefix.Append(NS_ConvertUTF16toUTF8(aOrigin));
+  }
 
   nsAutoCString data64;
   rv = Base64Encode(keyPrefix, data64);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Replace '/' with '-' since '/' cannot be part of the filename.
@@ -511,17 +533,17 @@ CacheFileContextEvictor::CloseIterators(
 void
 CacheFileContextEvictor::StartEvicting()
 {
   LOG(("CacheFileContextEvictor::StartEvicting() [this=%p]", this));
 
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   if (mEvicting) {
-    LOG(("CacheFileContextEvictor::StartEvicting() - already evicintg."));
+    LOG(("CacheFileContextEvictor::StartEvicting() - already evicting."));
     return;
   }
 
   if (mEntries.Length() == 0) {
     LOG(("CacheFileContextEvictor::StartEvicting() - no context to evict."));
     return;
   }
 
@@ -585,17 +607,18 @@ CacheFileContextEvictor::EvictEntries()
     }
 
     SHA1Sum::Hash hash;
     rv = mEntries[0]->mIterator->GetNextHash(&hash);
     if (rv == NS_ERROR_NOT_AVAILABLE) {
       LOG(("CacheFileContextEvictor::EvictEntries() - No more entries left in "
            "iterator. [iterator=%p, info=%p]", mEntries[0]->mIterator.get(),
            mEntries[0]->mInfo.get()));
-      RemoveEvictInfoFromDisk(mEntries[0]->mInfo, mEntries[0]->mPinned);
+      RemoveEvictInfoFromDisk(mEntries[0]->mInfo, mEntries[0]->mPinned,
+                              mEntries[0]->mOrigin);
       mEntries.RemoveElementAt(0);
       continue;
     } else if (NS_FAILED(rv)) {
       LOG(("CacheFileContextEvictor::EvictEntries() - Iterator failed to "
            "provide next hash (shutdown?), keeping eviction info on disk."
            " [iterator=%p, info=%p]", mEntries[0]->mIterator.get(),
            mEntries[0]->mInfo.get()));
       mEntries.RemoveElementAt(0);
@@ -629,16 +652,57 @@ CacheFileContextEvictor::EvictEntries()
 
     if (pinned != mEntries[0]->mPinned) {
       LOG(("CacheFileContextEvictor::EvictEntries() - Skipping entry since pinning "
            "doesn't match [evicting pinned=%d, entry pinned=%d]",
            mEntries[0]->mPinned, pinned));
       continue;
     }
 
+    if (!mEntries[0]->mOrigin.IsEmpty()) {
+      nsCOMPtr<nsIFile> file;
+      CacheFileIOManager::gInstance->GetFile(&hash, getter_AddRefs(file));
+
+      // Read metadata from the file synchronously
+      RefPtr<CacheFileMetadata> metadata = new CacheFileMetadata();
+      rv = metadata->SyncReadMetadata(file);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        continue;
+      }
+
+      // Now get the context + enhance id + URL from the key.
+      nsAutoCString key;
+      metadata->GetKey(key);
+
+      nsAutoCString uriSpec;
+
+      RefPtr<nsILoadContextInfo> info =
+        CacheFileUtils::ParseKey(key, nullptr, &uriSpec);
+      MOZ_ASSERT(info);
+      if (!info) {
+        continue;
+      }
+
+      RefPtr<MozURL> url;
+      rv = MozURL::Init(getter_AddRefs(url), uriSpec);
+      if (NS_FAILED(rv)) {
+        LOG(("CacheFileContextEvictor::EvictEntries() - Skipping entry since MozURL "
+             "fails in the parsing of the uriSpec"));
+        continue;
+      }
+
+      nsAutoCString urlOrigin;
+      url->Origin(urlOrigin);
+      if (urlOrigin.Equals(NS_ConvertUTF16toUTF8(mEntries[0]->mOrigin))) {
+        LOG(("CacheFileContextEvictor::EvictEntries() - Skipping entry since origin "
+             "doesn't match"));
+        continue;
+      }
+    }
+
     nsAutoCString leafName;
     CacheFileIOManager::HashToStr(&hash, leafName);
 
     PRTime lastModifiedTime;
     nsCOMPtr<nsIFile> file;
     rv = mEntriesDir->Clone(getter_AddRefs(file));
     if (NS_SUCCEEDED(rv)) {
       rv = file->AppendNative(leafName);
--- a/netwerk/cache2/CacheFileContextEvictor.h
+++ b/netwerk/cache2/CacheFileContextEvictor.h
@@ -16,16 +16,17 @@ namespace mozilla {
 namespace net {
 
 class CacheIndexIterator;
 
 struct CacheFileContextEvictorEntry
 {
   nsCOMPtr<nsILoadContextInfo> mInfo;
   bool                         mPinned;
+  nsString                     mOrigin; // it can be empty
   PRTime                       mTimeStamp; // in milliseconds
   RefPtr<CacheIndexIterator> mIterator;
 };
 
 class CacheFileContextEvictor
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheFileContextEvictor)
@@ -36,18 +37,19 @@ private:
   virtual ~CacheFileContextEvictor();
 
 public:
   nsresult Init(nsIFile *aCacheDirectory);
   void Shutdown();
 
   // Returns number of contexts that are being evicted.
   uint32_t ContextsCount();
-  // Start evicting given context.
-  nsresult AddContext(nsILoadContextInfo *aLoadContextInfo, bool aPinned);
+  // Start evicting given context and an origin, if not empty.
+  nsresult AddContext(nsILoadContextInfo *aLoadContextInfo, bool aPinned,
+                      const nsAString& aOrigin);
   // CacheFileIOManager calls this method when CacheIndex's state changes. We
   // check whether the index is up to date and start or stop evicting according
   // to index's state.
   nsresult CacheIndexStateChanged();
   // CacheFileIOManager calls this method to check whether an entry file should
   // be considered as evicted. It returns true when there is a matching context
   // info to the given key and the last modified time of the entry file is
   // earlier than the time stamp of the time when the context was added to the
@@ -55,25 +57,27 @@ public:
   nsresult WasEvicted(const nsACString &aKey, nsIFile *aFile,
                       bool *aEvictedAsPinned, bool *aEvictedAsNonPinned);
 
 private:
   // Writes information about eviction of the given context to the disk. This is
   // done for every context added to the evictor to be able to recover eviction
   // after a shutdown or crash. When the context file is found after startup, we
   // restore mTimeStamp from the last modified time of the file.
-  nsresult PersistEvictionInfoToDisk(nsILoadContextInfo *aLoadContextInfo, bool aPinned);
+  nsresult PersistEvictionInfoToDisk(nsILoadContextInfo *aLoadContextInfo,
+                                     bool aPinned, const nsAString& aOrigin);
   // Once we are done with eviction for the given context, the eviction info is
   // removed from the disk.
-  nsresult RemoveEvictInfoFromDisk(nsILoadContextInfo *aLoadContextInfo, bool aPinned);
+  nsresult RemoveEvictInfoFromDisk(nsILoadContextInfo *aLoadContextInfo,
+                                   bool aPinned, const nsAString& aOrigin);
   // Tries to load all contexts from the disk. This method is called just once
   // after startup.
   nsresult LoadEvictInfoFromDisk();
   nsresult GetContextFile(nsILoadContextInfo *aLoadContextInfo, bool aPinned,
-                          nsIFile **_retval);
+                          const nsAString& aOrigin, nsIFile **_retval);
 
   void     CreateIterators();
   void     CloseIterators();
   void     StartEvicting();
   nsresult EvictEntries();
 
   // Whether eviction is in progress
   bool mEvicting;
--- a/netwerk/cache2/CacheFileIOManager.cpp
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -16,16 +16,17 @@
 #include "nsIFile.h"
 #include "CacheFileContextEvictor.h"
 #include "nsITimer.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIDirectoryEnumerator.h"
 #include "nsIObserverService.h"
 #include "nsICacheStorageVisitor.h"
 #include "nsISizeOf.h"
+#include "mozilla/net/MozURL.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Services.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "private/pprio.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Preferences.h"
@@ -3129,46 +3130,51 @@ CacheFileIOManager::EvictAllInternal()
 
   CacheIndex::RemoveAll();
 
   return NS_OK;
 }
 
 // static
 nsresult
-CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo, bool aPinned)
+CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo,
+                                   bool aPinned,
+                                   const nsAString& aOrigin)
 {
   LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]",
        aLoadContextInfo));
 
   nsresult rv;
   RefPtr<CacheFileIOManager> ioMan = gInstance;
 
   if (!ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   nsCOMPtr<nsIRunnable> ev;
-  ev = NewRunnableMethod<nsCOMPtr<nsILoadContextInfo>, bool>(
+  ev = NewRunnableMethod<nsCOMPtr<nsILoadContextInfo>, bool, nsString>(
     "net::CacheFileIOManager::EvictByContextInternal",
     ioMan,
     &CacheFileIOManager::EvictByContextInternal,
     aLoadContextInfo,
-    aPinned);
+    aPinned,
+    aOrigin);
 
   rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 nsresult
-CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo, bool aPinned)
+CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo,
+                                           bool aPinned,
+                                           const nsAString& aOrigin)
 {
   LOG(("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, pinned=%d]",
       aLoadContextInfo, aPinned));
 
   nsresult rv;
 
   if (aLoadContextInfo) {
     nsAutoCString suffix;
@@ -3201,35 +3207,49 @@ CacheFileIOManager::EvictByContextIntern
 
   if (!mTreeCreated) {
     rv = CreateCacheTree();
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
+  NS_ConvertUTF16toUTF8 origin(aOrigin);
+
   // Doom all active handles that matches the load context
   nsTArray<RefPtr<CacheFileHandle> > handles;
   mHandles.GetActiveHandles(&handles);
 
   for (uint32_t i = 0; i < handles.Length(); ++i) {
     CacheFileHandle* handle = handles[i];
 
-    if (aLoadContextInfo) {
-      bool equals;
-      rv = CacheFileUtils::KeyMatchesLoadContextInfo(handle->Key(),
-                                                     aLoadContextInfo,
-                                                     &equals);
+    nsAutoCString uriSpec;
+    RefPtr<nsILoadContextInfo> info =
+      CacheFileUtils::ParseKey(handle->Key(), nullptr, &uriSpec);
+    if (!info) {
+      LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot parse key in "
+           "handle! [handle=%p, key=%s]", handle, handle->Key().get()));
+      MOZ_CRASH("Unexpected error!");
+    }
+
+    if (aLoadContextInfo && !info->Equals(aLoadContextInfo)) {
+      continue;
+    }
+
+    if (!origin.IsEmpty()) {
+      RefPtr<MozURL> url;
+      rv = MozURL::Init(getter_AddRefs(url), uriSpec);
       if (NS_FAILED(rv)) {
-        LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot parse key in "
-             "handle! [handle=%p, key=%s]", handle, handle->Key().get()));
-        MOZ_CRASH("Unexpected error!");
+        continue;
       }
 
-      if (!equals) {
+      nsAutoCString urlOrigin;
+      url->Origin(urlOrigin);
+
+      if (!urlOrigin.Equals(origin)) {
         continue;
       }
     }
 
     // handle will be doomed only when pinning status is known and equal or
     // doom decision will be deferred until pinning status is determined.
     rv = DoomFileInternal(handle, aPinned
                                   ? CacheFileIOManager::DOOM_WHEN_PINNED
@@ -3245,17 +3265,17 @@ CacheFileIOManager::EvictByContextIntern
     NS_DispatchToMainThread(r);
   }
 
   if (!mContextEvictor) {
     mContextEvictor = new CacheFileContextEvictor();
     mContextEvictor->Init(mCacheDirectory);
   }
 
-  mContextEvictor->AddContext(aLoadContextInfo, aPinned);
+  mContextEvictor->AddContext(aLoadContextInfo, aPinned, aOrigin);
 
   return NS_OK;
 }
 
 // static
 nsresult
 CacheFileIOManager::CacheIndexStateChanged()
 {
--- a/netwerk/cache2/CacheFileIOManager.h
+++ b/netwerk/cache2/CacheFileIOManager.h
@@ -327,17 +327,18 @@ public:
                                      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,
-                                 bool aPinning);
+                                 bool aPinning,
+                                 const nsAString& aOrigin);
 
   static nsresult InitIndexEntry(CacheFileHandle *aHandle,
                                  OriginAttrsHash  aOriginAttrsHash,
                                  bool             aAnonymous,
                                  bool             aPinning);
   static nsresult UpdateIndexEntry(CacheFileHandle *aHandle,
                                    const uint32_t  *aFrecency,
                                    const uint32_t  *aExpirationTime,
@@ -412,17 +413,17 @@ private:
   nsresult TruncateSeekSetEOFInternal(CacheFileHandle *aHandle,
                                       int64_t aTruncatePos, int64_t aEOFPos);
   nsresult RenameFileInternal(CacheFileHandle *aHandle,
                               const nsACString &aNewName);
   nsresult EvictIfOverLimitInternal();
   nsresult OverLimitEvictionInternal();
   nsresult EvictAllInternal();
   nsresult EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo,
-                                  bool aPinning);
+                                  bool aPinning, const nsAString& aOrigin);
 
   nsresult TrashDirectory(nsIFile *aFile);
   static void OnTrashTimer(nsITimer *aTimer, void *aClosure);
   nsresult StartRemovingTrash();
   nsresult RemoveTrashInternal();
   nsresult FindTrashDirToRemove();
 
   nsresult CreateFile(CacheFileHandle *aHandle);
--- a/netwerk/cache2/CacheStorageService.cpp
+++ b/netwerk/cache2/CacheStorageService.cpp
@@ -19,16 +19,17 @@
 #include "nsCacheService.h"
 #include "nsDeleteDir.h"
 
 #include "nsICacheStorageVisitor.h"
 #include "nsIObserverService.h"
 #include "nsIFile.h"
 #include "nsIURI.h"
 #include "nsCOMPtr.h"
+#include "nsContentUtils.h"
 #include "nsAutoPtr.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
 #include "nsWeakReference.h"
 #include "nsXULAppAPI.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/DebugOnly.h"
@@ -790,17 +791,127 @@ NS_IMETHODIMP CacheStorageService::Clear
   }
 
   for (uint32_t i = 0; i < keys.Length(); ++i) {
     DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
   }
 
   // Passing null as a load info means to evict all contexts.
   // EvictByContext() respects the entry pinning.  EvictAll() does not.
-  rv = CacheFileIOManager::EvictByContext(nullptr, false);
+  rv = CacheFileIOManager::EvictByContext(nullptr, false, EmptyString());
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::ClearOrigin(nsIPrincipal* aPrincipal)
+{
+  nsresult rv;
+
+  if (NS_WARN_IF(!aPrincipal)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsAutoString origin;
+  rv = nsContentUtils::GetUTFOrigin(aPrincipal, origin);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = ClearOriginInternal(origin, aPrincipal->OriginAttributesRef(), true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = ClearOriginInternal(origin, aPrincipal->OriginAttributesRef(), false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+static bool
+RemoveExactEntry(CacheEntryTable* aEntries,
+                 nsACString const& aKey,
+                 CacheEntry* aEntry,
+                 bool aOverwrite)
+{
+  RefPtr<CacheEntry> existingEntry;
+  if (!aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
+    LOG(("RemoveExactEntry [entry=%p already gone]", aEntry));
+    return false; // Already removed...
+  }
+
+  if (!aOverwrite && existingEntry != aEntry) {
+    LOG(("RemoveExactEntry [entry=%p already replaced]", aEntry));
+    return false; // Already replaced...
+  }
+
+  LOG(("RemoveExactEntry [entry=%p removed]", aEntry));
+  aEntries->Remove(aKey);
+  return true;
+}
+
+nsresult
+CacheStorageService::ClearOriginInternal(const nsAString& aOrigin,
+                                         const OriginAttributes& aOriginAttributes,
+                                         bool aAnonymous)
+{
+  nsresult rv;
+
+  RefPtr<LoadContextInfo> info =
+    GetLoadContextInfo(aAnonymous, aOriginAttributes);
+  if (NS_WARN_IF(!info)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mozilla::MutexAutoLock lock(mLock);
+
+  if (sGlobalEntryTables) {
+    for (auto iter = sGlobalEntryTables->Iter(); !iter.Done(); iter.Next()) {
+      bool matches = false;
+      rv = CacheFileUtils::KeyMatchesLoadContextInfo(iter.Key(), info,
+                                                     &matches);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (!matches) {
+        continue;
+      }
+
+      CacheEntryTable* table = iter.UserData();
+      MOZ_ASSERT(table);
+
+      nsTArray<RefPtr<CacheEntry>> entriesToDelete;
+
+      for (auto entryIter = table->Iter(); !entryIter.Done(); entryIter.Next()) {
+        CacheEntry* entry = entryIter.UserData();
+
+        nsCOMPtr<nsIURI> uri;
+        rv = NS_NewURI(getter_AddRefs(uri), entry->GetURI());
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        nsAutoString origin;
+        rv = nsContentUtils::GetUTFOrigin(uri, origin);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        if (origin != aOrigin) {
+          continue;
+        }
+
+        entriesToDelete.AppendElement(entry);
+      }
+
+      for (RefPtr<CacheEntry>& entry : entriesToDelete) {
+        nsAutoCString entryKey;
+        rv = entry->HashingKey(entryKey);
+        if (NS_FAILED(rv)) {
+          NS_ERROR("aEntry->HashingKey() failed?");
+          return rv;
+        }
+
+        RemoveExactEntry(table, entryKey, entry, false /* don't overwrite */);
+      }
+    }
+  }
+
+  rv = CacheFileIOManager::EvictByContext(info, false /* pinned */, aOrigin);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP CacheStorageService::PurgeFromMemory(uint32_t aWhat)
 {
   uint32_t what;
@@ -971,38 +1082,16 @@ AddExactEntry(CacheEntryTable* aEntries,
     return equals; // Already there...
   }
 
   LOG(("AddExactEntry [entry=%p put]", aEntry));
   aEntries->Put(aKey, aEntry);
   return true;
 }
 
-static bool
-RemoveExactEntry(CacheEntryTable* aEntries,
-                 nsACString const& aKey,
-                 CacheEntry* aEntry,
-                 bool aOverwrite)
-{
-  RefPtr<CacheEntry> existingEntry;
-  if (!aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
-    LOG(("RemoveExactEntry [entry=%p already gone]", aEntry));
-    return false; // Already removed...
-  }
-
-  if (!aOverwrite && existingEntry != aEntry) {
-    LOG(("RemoveExactEntry [entry=%p already replaced]", aEntry));
-    return false; // Already replaced...
-  }
-
-  LOG(("RemoveExactEntry [entry=%p removed]", aEntry));
-  aEntries->Remove(aKey);
-  return true;
-}
-
 bool
 CacheStorageService::RemoveEntry(CacheEntry* aEntry, bool aOnlyUnreferenced)
 {
   LOG(("CacheStorageService::RemoveEntry [entry=%p]", aEntry));
 
   nsAutoCString entryKey;
   nsresult rv = aEntry->HashingKey(entryKey);
   if (NS_FAILED(rv)) {
@@ -1839,17 +1928,17 @@ CacheStorageService::DoomStorageEntries(
           RemoveExactEntry(memoryEntries, iter.Key(), entry, false);
         }
         iter.Remove();
       }
     }
 
     if (aContext && !aContext->IsPrivate()) {
       LOG(("  dooming disk entries"));
-      CacheFileIOManager::EvictByContext(aContext, aPinned);
+      CacheFileIOManager::EvictByContext(aContext, aPinned, EmptyString());
     }
   } else {
     LOG(("  dooming memory-only storage of %s", aContextKey.BeginReading()));
 
     // Remove the memory entries table from the global tables.
     // Since we store memory entries also in the disk entries table
     // we need to remove the memory entries from the disk table one
     // by one manually.
--- a/netwerk/cache2/CacheStorageService.h
+++ b/netwerk/cache2/CacheStorageService.h
@@ -25,16 +25,19 @@
 class nsIURI;
 class nsICacheEntryDoomCallback;
 class nsICacheStorageVisitor;
 class nsIRunnable;
 class nsIThread;
 class nsIEventTarget;
 
 namespace mozilla {
+
+class OriginAttributes;
+
 namespace net {
 
 class CacheStorageService;
 class CacheStorage;
 class CacheEntry;
 class CacheEntryHandle;
 
 class CacheMemoryConsumer
@@ -306,16 +309,20 @@ private:
                            const nsACString & aURI,
                            const nsACString & aIdExtension,
                            bool aWriteToDisk,
                            bool aSkipSizeCheck,
                            bool aPin,
                            bool aReplace,
                            CacheEntryHandle** aResult);
 
+  nsresult ClearOriginInternal(const nsAString& aOrigin,
+                               const mozilla::OriginAttributes& aOriginAttributes,
+                               bool aAnonymous);
+
   static CacheStorageService* sSelf;
 
   mozilla::Mutex mLock;
   mozilla::Mutex mForcedValidEntriesLock;
 
   bool mShutdown;
 
   // Accessible only on the service thread
--- a/netwerk/cache2/nsICacheStorageService.idl
+++ b/netwerk/cache2/nsICacheStorageService.idl
@@ -5,16 +5,17 @@
 #include "nsISupports.idl"
 
 interface nsICacheStorage;
 interface nsILoadContextInfo;
 interface nsIApplicationCache;
 interface nsIEventTarget;
 interface nsICacheStorageConsumptionObserver;
 interface nsICacheStorageVisitor;
+interface nsIPrincipal;
 
 /**
  * Provides access to particual cache storages of the network URI cache.
  */
 [scriptable, uuid(ae29c44b-fbc3-4552-afaf-0a157ce771e7)]
 interface nsICacheStorageService : nsISupports
 {
   /**
@@ -66,16 +67,24 @@ interface nsICacheStorageService : nsISu
   /**
    * Get storage for synthesized cache entries that we currently use for ServiceWorker interception in non-e10s mode.
    *
    * This cache storage has no limits on file size to allow the ServiceWorker to intercept large files.
    */
   nsICacheStorage synthesizedCacheStorage(in nsILoadContextInfo aLoadContextInfo);
 
   /**
+   * Evict any cache entry having the same origin of aPrincipal.
+   *
+   * @param aPrincipal
+   *   The principal to compare the entries with.
+   */
+  void clearOrigin(in nsIPrincipal aPrincipal);
+
+  /**
    * Evict the whole cache.
    */
   void clear();
 
   /**
    * Purge only data of disk backed entries.  Metadata are left for
    * performance purposes.
    */
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-32-clear-origin.js
@@ -0,0 +1,40 @@
+const URL = "http://example.net";
+const URL2 = "http://foo.bar";
+
+function run_test()
+{
+  do_get_profile();
+
+  asyncOpenCacheEntry(URL + "/a", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+    new OpenCallback(NEW, "e1m", "e1d", function(entry) {
+      asyncOpenCacheEntry(URL + "/a", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+        new OpenCallback(NORMAL, "e1m", "e1d", function(entry) {
+          asyncOpenCacheEntry(URL2 + "/a", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+            new OpenCallback(NEW, "f1m", "f1d", function(entry) {
+              asyncOpenCacheEntry(URL2 + "/a", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+                new OpenCallback(NORMAL, "f1m", "f1d", function(entry) {
+                  var url = Services.io.newURI(URL);
+                  var principal = Services.scriptSecurityManager.createCodebasePrincipal(url, {});
+
+                  get_cache_service().clearOrigin(principal);
+
+                  asyncOpenCacheEntry(URL + "/a", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+                    new OpenCallback(NEW, "e1m", "e1d", function(entry) {
+                      asyncOpenCacheEntry(URL2 + "/a", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
+                        new OpenCallback(NORMAL, "f1m", "f1d", function(entry) {
+                          finish_cache2_test();
+                        })
+                      );
+                    })
+                  );
+                })
+              );
+            })
+          );
+        })
+      );
+    })
+  );
+
+  do_test_pending();
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -73,16 +73,17 @@ skip-if = true
 [test_cache2-29c-concurrent_read_half-interrupted.js]
 [test_cache2-29d-concurrent_read_half-corrupted-206.js]
 [test_cache2-29e-concurrent_read_half-non-206-response.js]
 [test_cache2-30a-entry-pinning.js]
 [test_cache2-30b-pinning-storage-clear.js]
 [test_cache2-30c-pinning-deferred-doom.js]
 [test_cache2-30d-pinning-WasEvicted-API.js]
 [test_cache2-31-visit-all.js]
+[test_cache2-32-clear-origin.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]
 # Intermittent time-outs on Android, bug 1285020
--- a/toolkit/components/cleardata/ClearDataService.js
+++ b/toolkit/components/cleardata/ClearDataService.js
@@ -81,16 +81,23 @@ const CookieCleaner = {
 
       aResolve();
     });
   },
 
 };
 
 const NetworkCacheCleaner = {
+  deleteByPrincipal(aPrincipal) {
+    return new Promise(aResolve => {
+      Services.cache2.asyncClearOrigin(aPrincipal);
+      aResolve();
+    });
+  },
+
   deleteAll() {
     return new Promise(aResolve => {
       Services.cache2.clear();
       aResolve();
     });
   },
 };