tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

CacheStorageService.cpp (77294B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "CacheLog.h"
      8 #include "CacheStorageService.h"
      9 #include "CacheFileIOManager.h"
     10 #include "CacheObserver.h"
     11 #include "CacheIndex.h"
     12 #include "CacheIndexIterator.h"
     13 #include "CacheStorage.h"
     14 #include "CacheEntry.h"
     15 #include "CacheFileUtils.h"
     16 
     17 #include "ErrorList.h"
     18 #include "nsICacheStorageVisitor.h"
     19 #include "nsIObserverService.h"
     20 #include "nsIFile.h"
     21 #include "nsIURI.h"
     22 #include "nsCOMPtr.h"
     23 #include "nsContentUtils.h"
     24 #include "nsNetCID.h"
     25 #include "nsNetUtil.h"
     26 #include "nsServiceManagerUtils.h"
     27 #include "nsXULAppAPI.h"
     28 #include "mozilla/AtomicBitfields.h"
     29 #include "mozilla/TimeStamp.h"
     30 #include "mozilla/DebugOnly.h"
     31 #include "mozilla/glean/NetwerkMetrics.h"
     32 #include "mozilla/Services.h"
     33 #include "mozilla/StoragePrincipalHelper.h"
     34 #include "mozilla/IntegerPrintfMacros.h"
     35 #include "mozilla/glean/NetwerkCache2Metrics.h"
     36 #include "mozilla/StaticPrefs_network.h"
     37 
     38 namespace mozilla::net {
     39 
     40 // static
     41 GlobalEntryTables* CacheStorageService::sGlobalEntryTables = nullptr;
     42 StaticMutex CacheStorageService::sLock;
     43 
     44 namespace {
     45 
     46 void AppendMemoryStorageTag(nsAutoCString& key) {
     47  // Using DEL as the very last ascii-7 character we can use in the list of
     48  // attributes
     49  key.Append('\x7f');
     50  key.Append(',');
     51 }
     52 
     53 }  // namespace
     54 
     55 CacheMemoryConsumer::CacheMemoryConsumer(uint32_t aFlags) {
     56  StoreFlags(aFlags);
     57 }
     58 
     59 void CacheMemoryConsumer::DoMemoryReport(uint32_t aCurrentSize) {
     60  if (!(LoadFlags() & DONT_REPORT) && CacheStorageService::Self()) {
     61    CacheStorageService::Self()->OnMemoryConsumptionChange(this, aCurrentSize);
     62  }
     63 }
     64 
     65 CacheStorageService::MemoryPool::MemoryPool(EType aType) : mType(aType) {}
     66 
     67 CacheStorageService::MemoryPool::~MemoryPool() {
     68  if (mMemorySize != 0) {
     69    NS_ERROR(
     70        "Network cache reported memory consumption is not at 0, probably "
     71        "leaking?");
     72  }
     73 }
     74 
     75 uint32_t CacheStorageService::MemoryPool::Limit() const {
     76  uint32_t limit = 0;
     77 
     78  switch (mType) {
     79    case DISK:
     80      limit = CacheObserver::MetadataMemoryLimit();
     81      break;
     82    case MEMORY:
     83      limit = CacheObserver::MemoryCacheCapacity();
     84      break;
     85    default:
     86      MOZ_CRASH("Bad pool type");
     87  }
     88 
     89  static const uint32_t kMaxLimit = 0x3FFFFF;
     90  if (limit > kMaxLimit) {
     91    LOG(("  a memory limit (%u) is unexpectedly high, clipping to %u", limit,
     92         kMaxLimit));
     93    limit = kMaxLimit;
     94  }
     95 
     96  return limit << 10;
     97 }
     98 
     99 NS_IMPL_ISUPPORTS(CacheStorageService, nsICacheStorageService,
    100                  nsIMemoryReporter, nsITimerCallback, nsICacheTesting,
    101                  nsINamed)
    102 
    103 CacheStorageService* CacheStorageService::sSelf = nullptr;
    104 
    105 CacheStorageService::CacheStorageService() {
    106  CacheFileIOManager::Init();
    107 
    108  MOZ_ASSERT(XRE_IsParentProcess());
    109  MOZ_ASSERT(!sSelf);
    110 
    111  sSelf = this;
    112  sGlobalEntryTables = new GlobalEntryTables();
    113 
    114  RegisterStrongMemoryReporter(this);
    115 }
    116 
    117 CacheStorageService::~CacheStorageService() {
    118  LOG(("CacheStorageService::~CacheStorageService"));
    119  sSelf = nullptr;
    120 }
    121 
    122 void CacheStorageService::Shutdown() {
    123  StaticMutexAutoLock lock(sLock);
    124 
    125  if (mShutdown) return;
    126 
    127  LOG(("CacheStorageService::Shutdown - start"));
    128 
    129  mShutdown = true;
    130 
    131  nsCOMPtr<nsIRunnable> event =
    132      NewRunnableMethod("net::CacheStorageService::ShutdownBackground", this,
    133                        &CacheStorageService::ShutdownBackground);
    134  Dispatch(event);
    135 
    136 #ifdef NS_FREE_PERMANENT_DATA
    137  sGlobalEntryTables->Clear();
    138  delete sGlobalEntryTables;
    139 #endif
    140  sGlobalEntryTables = nullptr;
    141 
    142  LOG(("CacheStorageService::Shutdown - done"));
    143 }
    144 
    145 void CacheStorageService::ShutdownBackground() {
    146  LOG(("CacheStorageService::ShutdownBackground - start"));
    147 
    148  MOZ_ASSERT(IsOnManagementThread());
    149 
    150  {
    151    StaticMutexAutoLock lock(sLock);
    152 
    153    // Cancel purge timer to avoid leaking.
    154    if (mPurgeTimer) {
    155      LOG(("  freeing the timer"));
    156      mPurgeTimer->Cancel();
    157    }
    158  }
    159 
    160 #ifdef NS_FREE_PERMANENT_DATA
    161  Pool(MemoryPool::EType::DISK).mManagedEntries.clear();
    162  Pool(MemoryPool::EType::MEMORY).mManagedEntries.clear();
    163 #endif
    164 
    165  LOG(("CacheStorageService::ShutdownBackground - done"));
    166 }
    167 
    168 // Internal management methods
    169 
    170 namespace CacheStorageServiceInternal {
    171 
    172 // WalkCacheRunnable
    173 // Base class for particular storage entries visiting
    174 class WalkCacheRunnable : public Runnable,
    175                          public CacheStorageService::EntryInfoCallback {
    176 protected:
    177  WalkCacheRunnable(nsICacheStorageVisitor* aVisitor, bool aVisitEntries)
    178      : Runnable("net::WalkCacheRunnable"),
    179        mService(CacheStorageService::Self()),
    180        mCallback(aVisitor) {
    181    MOZ_ASSERT(NS_IsMainThread());
    182    StoreNotifyStorage(true);
    183    StoreVisitEntries(aVisitEntries);
    184  }
    185 
    186  virtual ~WalkCacheRunnable() {
    187    if (mCallback) {
    188      ProxyReleaseMainThread("WalkCacheRunnable::mCallback", mCallback);
    189    }
    190  }
    191 
    192  RefPtr<CacheStorageService> mService;
    193  nsCOMPtr<nsICacheStorageVisitor> mCallback;
    194 
    195  uint64_t mSize{0};
    196 
    197  // clang-format off
    198  MOZ_ATOMIC_BITFIELDS(mAtomicBitfields, 8, (
    199    (bool, NotifyStorage, 1),
    200    (bool, VisitEntries, 1)
    201  ))
    202  // clang-format on
    203 
    204  Atomic<bool> mCancel{false};
    205 };
    206 
    207 // WalkMemoryCacheRunnable
    208 // Responsible to visit memory storage and walk
    209 // all entries on it asynchronously.
    210 class WalkMemoryCacheRunnable : public WalkCacheRunnable {
    211 public:
    212  WalkMemoryCacheRunnable(nsILoadContextInfo* aLoadInfo, bool aVisitEntries,
    213                          nsICacheStorageVisitor* aVisitor)
    214      : WalkCacheRunnable(aVisitor, aVisitEntries) {
    215    CacheFileUtils::AppendKeyPrefix(aLoadInfo, mContextKey);
    216    MOZ_ASSERT(NS_IsMainThread());
    217  }
    218 
    219  nsresult Walk() { return mService->Dispatch(this); }
    220 
    221 private:
    222  NS_IMETHOD Run() override {
    223    if (CacheStorageService::IsOnManagementThread()) {
    224      LOG(("WalkMemoryCacheRunnable::Run - collecting [this=%p]", this));
    225      // First, walk, count and grab all entries from the storage
    226 
    227      StaticMutexAutoLock lock(CacheStorageService::sLock);
    228 
    229      if (!CacheStorageService::IsRunning()) return NS_ERROR_NOT_INITIALIZED;
    230 
    231      // Count the entries to allocate the array memory all at once.
    232      size_t numEntries = 0;
    233      for (const auto& entries :
    234           CacheStorageService::sGlobalEntryTables->Values()) {
    235        if (entries->Type() != CacheEntryTable::MEMORY_ONLY) {
    236          continue;
    237        }
    238        numEntries += entries->Values().Count();
    239      }
    240      mEntryArray.SetCapacity(numEntries);
    241 
    242      // Collect the entries.
    243      for (const auto& entries :
    244           CacheStorageService::sGlobalEntryTables->Values()) {
    245        if (entries->Type() != CacheEntryTable::MEMORY_ONLY) {
    246          continue;
    247        }
    248 
    249        for (CacheEntry* entry : entries->Values()) {
    250          MOZ_ASSERT(!entry->IsUsingDisk());
    251 
    252          mSize += entry->GetMetadataMemoryConsumption();
    253 
    254          int64_t size;
    255          if (NS_SUCCEEDED(entry->GetDataSize(&size))) {
    256            mSize += size;
    257          }
    258          mEntryArray.AppendElement(entry);
    259        }
    260      }
    261 
    262      // Next, we dispatch to the main thread
    263    } else if (NS_IsMainThread()) {
    264      LOG(("WalkMemoryCacheRunnable::Run - notifying [this=%p]", this));
    265 
    266      if (LoadNotifyStorage()) {
    267        LOG(("  storage"));
    268 
    269        uint64_t capacity = CacheObserver::MemoryCacheCapacity();
    270        capacity <<= 10;  // kilobytes to bytes
    271 
    272        // Second, notify overall storage info
    273        mCallback->OnCacheStorageInfo(mEntryArray.Length(), mSize, capacity,
    274                                      nullptr);
    275        if (!LoadVisitEntries()) return NS_OK;  // done
    276 
    277        StoreNotifyStorage(false);
    278 
    279      } else {
    280        LOG(("  entry [left=%zu, canceled=%d]", mEntryArray.Length(),
    281             (bool)mCancel));
    282 
    283        // Third, notify each entry until depleted or canceled.
    284        if (mNextEntryIdx >= mEntryArray.Length() || mCancel) {
    285          mCallback->OnCacheEntryVisitCompleted();
    286          return NS_OK;  // done
    287        }
    288 
    289        // Grab the next entry.
    290        RefPtr<CacheEntry> entry = std::move(mEntryArray[mNextEntryIdx++]);
    291 
    292        // Invokes this->OnEntryInfo, that calls the callback with all
    293        // information of the entry.
    294        CacheStorageService::GetCacheEntryInfo(entry, this);
    295      }
    296    } else {
    297      MOZ_CRASH("Bad thread");
    298      return NS_ERROR_FAILURE;
    299    }
    300 
    301    NS_DispatchToMainThread(this);
    302    return NS_OK;
    303  }
    304 
    305  virtual ~WalkMemoryCacheRunnable() {
    306    if (mCallback) {
    307      ProxyReleaseMainThread("WalkMemoryCacheRunnable::mCallback", mCallback);
    308    }
    309  }
    310 
    311  virtual void OnEntryInfo(const nsACString& aURISpec,
    312                           const nsACString& aIdEnhance, int64_t aDataSize,
    313                           int64_t aAltDataSize, uint32_t aFetchCount,
    314                           uint32_t aLastModifiedTime, uint32_t aExpirationTime,
    315                           bool aPinned, nsILoadContextInfo* aInfo) override {
    316    nsresult rv;
    317 
    318    nsCOMPtr<nsIURI> uri;
    319    rv = NS_NewURI(getter_AddRefs(uri), aURISpec);
    320    if (NS_FAILED(rv)) {
    321      return;
    322    }
    323 
    324    rv = mCallback->OnCacheEntryInfo(uri, aIdEnhance, aDataSize, aAltDataSize,
    325                                     aFetchCount, aLastModifiedTime,
    326                                     aExpirationTime, aPinned, aInfo);
    327    if (NS_FAILED(rv)) {
    328      LOG(("  callback failed, canceling the walk"));
    329      mCancel = true;
    330    }
    331  }
    332 
    333 private:
    334  nsCString mContextKey;
    335  nsTArray<RefPtr<CacheEntry>> mEntryArray;
    336  size_t mNextEntryIdx{0};
    337 };
    338 
    339 // WalkDiskCacheRunnable
    340 // Using the cache index information to get the list of files per context.
    341 class WalkDiskCacheRunnable : public WalkCacheRunnable {
    342 public:
    343  WalkDiskCacheRunnable(nsILoadContextInfo* aLoadInfo, bool aVisitEntries,
    344                        nsICacheStorageVisitor* aVisitor)
    345      : WalkCacheRunnable(aVisitor, aVisitEntries),
    346        mLoadInfo(aLoadInfo),
    347        mPass(COLLECT_STATS),
    348        mCount(0) {}
    349 
    350  nsresult Walk() {
    351    // TODO, bug 998693
    352    // Initial index build should be forced here so that about:cache soon
    353    // after startup gives some meaningfull results.
    354 
    355    // Dispatch to the INDEX level in hope that very recent cache entries
    356    // information gets to the index list before we grab the index iterator
    357    // for the first time.  This tries to avoid miss of entries that has
    358    // been created right before the visit is required.
    359    RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
    360    NS_ENSURE_TRUE(thread, NS_ERROR_NOT_INITIALIZED);
    361 
    362    return thread->Dispatch(this, CacheIOThread::INDEX);
    363  }
    364 
    365 private:
    366  // Invokes OnCacheEntryInfo callback for each single found entry.
    367  // There is one instance of this class per one entry.
    368  class OnCacheEntryInfoRunnable : public Runnable {
    369   public:
    370    explicit OnCacheEntryInfoRunnable(WalkDiskCacheRunnable* aWalker)
    371        : Runnable("net::WalkDiskCacheRunnable::OnCacheEntryInfoRunnable"),
    372          mWalker(aWalker) {}
    373 
    374    NS_IMETHOD Run() override {
    375      MOZ_ASSERT(NS_IsMainThread());
    376 
    377      nsresult rv;
    378 
    379      nsCOMPtr<nsIURI> uri;
    380      rv = NS_NewURI(getter_AddRefs(uri), mURISpec);
    381      if (NS_FAILED(rv)) {
    382        return NS_OK;
    383      }
    384 
    385      rv = mWalker->mCallback->OnCacheEntryInfo(
    386          uri, mIdEnhance, mDataSize, mAltDataSize, mFetchCount,
    387          mLastModifiedTime, mExpirationTime, mPinned, mInfo);
    388      if (NS_FAILED(rv)) {
    389        mWalker->mCancel = true;
    390      }
    391 
    392      return NS_OK;
    393    }
    394 
    395    RefPtr<WalkDiskCacheRunnable> mWalker;
    396 
    397    nsCString mURISpec;
    398    nsCString mIdEnhance;
    399    int64_t mDataSize{0};
    400    int64_t mAltDataSize{0};
    401    uint32_t mFetchCount{0};
    402    uint32_t mLastModifiedTime{0};
    403    uint32_t mExpirationTime{0};
    404    bool mPinned{false};
    405    nsCOMPtr<nsILoadContextInfo> mInfo;
    406  };
    407 
    408  NS_IMETHOD Run() override {
    409    // The main loop
    410    nsresult rv;
    411 
    412    if (CacheStorageService::IsOnManagementThread()) {
    413      switch (mPass) {
    414        case COLLECT_STATS:
    415          // Get quickly the cache stats.
    416          uint32_t size;
    417          rv = CacheIndex::GetCacheStats(mLoadInfo, &size, &mCount);
    418          if (NS_FAILED(rv)) {
    419            if (LoadVisitEntries()) {
    420              // both onStorageInfo and onCompleted are expected
    421              NS_DispatchToMainThread(this);
    422            }
    423            return NS_DispatchToMainThread(this);
    424          }
    425 
    426          mSize = static_cast<uint64_t>(size) << 10;
    427 
    428          // Invoke onCacheStorageInfo with valid information.
    429          NS_DispatchToMainThread(this);
    430 
    431          if (!LoadVisitEntries()) {
    432            return NS_OK;  // done
    433          }
    434 
    435          mPass = ITERATE_METADATA;
    436          [[fallthrough]];
    437 
    438        case ITERATE_METADATA:
    439          // Now grab the context iterator.
    440          if (!mIter) {
    441            rv =
    442                CacheIndex::GetIterator(mLoadInfo, true, getter_AddRefs(mIter));
    443            if (NS_FAILED(rv)) {
    444              // Invoke onCacheEntryVisitCompleted now
    445              return NS_DispatchToMainThread(this);
    446            }
    447          }
    448 
    449          while (!mCancel && !CacheObserver::ShuttingDown()) {
    450            if (CacheIOThread::YieldAndRerun()) return NS_OK;
    451 
    452            SHA1Sum::Hash hash;
    453            rv = mIter->GetNextHash(&hash);
    454            if (NS_FAILED(rv)) break;  // done (or error?)
    455 
    456            // This synchronously invokes OnEntryInfo on this class where we
    457            // redispatch to the main thread for the consumer callback.
    458            CacheFileIOManager::GetEntryInfo(&hash, this);
    459          }
    460 
    461          // Invoke onCacheEntryVisitCompleted on the main thread
    462          NS_DispatchToMainThread(this);
    463      }
    464    } else if (NS_IsMainThread()) {
    465      if (LoadNotifyStorage()) {
    466        nsCOMPtr<nsIFile> dir;
    467        CacheFileIOManager::GetCacheDirectory(getter_AddRefs(dir));
    468        uint64_t capacity = CacheObserver::DiskCacheCapacity();
    469        capacity <<= 10;  // kilobytes to bytes
    470        mCallback->OnCacheStorageInfo(mCount, mSize, capacity, dir);
    471        StoreNotifyStorage(false);
    472      } else {
    473        mCallback->OnCacheEntryVisitCompleted();
    474      }
    475    } else {
    476      MOZ_CRASH("Bad thread");
    477      return NS_ERROR_FAILURE;
    478    }
    479 
    480    return NS_OK;
    481  }
    482 
    483  virtual void OnEntryInfo(const nsACString& aURISpec,
    484                           const nsACString& aIdEnhance, int64_t aDataSize,
    485                           int64_t aAltDataSize, uint32_t aFetchCount,
    486                           uint32_t aLastModifiedTime, uint32_t aExpirationTime,
    487                           bool aPinned, nsILoadContextInfo* aInfo) override {
    488    // Called directly from CacheFileIOManager::GetEntryInfo.
    489 
    490    // Invoke onCacheEntryInfo on the main thread for this entry.
    491    RefPtr<OnCacheEntryInfoRunnable> info = new OnCacheEntryInfoRunnable(this);
    492    info->mURISpec = aURISpec;
    493    info->mIdEnhance = aIdEnhance;
    494    info->mDataSize = aDataSize;
    495    info->mAltDataSize = aAltDataSize;
    496    info->mFetchCount = aFetchCount;
    497    info->mLastModifiedTime = aLastModifiedTime;
    498    info->mExpirationTime = aExpirationTime;
    499    info->mPinned = aPinned;
    500    info->mInfo = aInfo;
    501 
    502    NS_DispatchToMainThread(info);
    503  }
    504 
    505  RefPtr<nsILoadContextInfo> mLoadInfo;
    506  enum {
    507    // First, we collect stats for the load context.
    508    COLLECT_STATS,
    509 
    510    // Second, if demanded, we iterate over the entries gethered
    511    // from the iterator and call CacheFileIOManager::GetEntryInfo
    512    // for each found entry.
    513    ITERATE_METADATA,
    514  } mPass;
    515 
    516  RefPtr<CacheIndexIterator> mIter;
    517  uint32_t mCount;
    518 };
    519 
    520 }  // namespace CacheStorageServiceInternal
    521 
    522 void CacheStorageService::DropPrivateBrowsingEntries() {
    523  StaticMutexAutoLock lock(sLock);
    524 
    525  if (mShutdown) return;
    526 
    527  nsTArray<nsCString> keys;
    528  for (const nsACString& key : sGlobalEntryTables->Keys()) {
    529    nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(key);
    530    if (info && info->IsPrivate()) {
    531      keys.AppendElement(key);
    532    }
    533  }
    534 
    535  for (uint32_t i = 0; i < keys.Length(); ++i) {
    536    DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
    537  }
    538 }
    539 
    540 // Helper methods
    541 
    542 // static
    543 bool CacheStorageService::IsOnManagementThread() {
    544  RefPtr<CacheStorageService> service = Self();
    545  if (!service) return false;
    546 
    547  nsCOMPtr<nsIEventTarget> target = service->Thread();
    548  if (!target) return false;
    549 
    550  bool currentThread;
    551  nsresult rv = target->IsOnCurrentThread(&currentThread);
    552  return NS_SUCCEEDED(rv) && currentThread;
    553 }
    554 
    555 already_AddRefed<nsIEventTarget> CacheStorageService::Thread() const {
    556  return CacheFileIOManager::IOTarget();
    557 }
    558 
    559 nsresult CacheStorageService::Dispatch(nsIRunnable* aEvent) {
    560  RefPtr<CacheIOThread> cacheIOThread = CacheFileIOManager::IOThread();
    561  if (!cacheIOThread) return NS_ERROR_NOT_AVAILABLE;
    562 
    563  return cacheIOThread->Dispatch(aEvent, CacheIOThread::MANAGEMENT);
    564 }
    565 
    566 namespace CacheStorageEvictHelper {
    567 
    568 nsresult ClearStorage(bool const aPrivate, bool const aAnonymous,
    569                      OriginAttributes& aOa) {
    570  nsresult rv;
    571 
    572  aOa.SyncAttributesWithPrivateBrowsing(aPrivate);
    573  RefPtr<LoadContextInfo> info = GetLoadContextInfo(aAnonymous, aOa);
    574 
    575  nsCOMPtr<nsICacheStorage> storage;
    576  RefPtr<CacheStorageService> service = CacheStorageService::Self();
    577  NS_ENSURE_TRUE(service, NS_ERROR_FAILURE);
    578 
    579  // Clear disk storage
    580  rv = service->DiskCacheStorage(info, getter_AddRefs(storage));
    581  NS_ENSURE_SUCCESS(rv, rv);
    582  rv = storage->AsyncEvictStorage(nullptr);
    583  NS_ENSURE_SUCCESS(rv, rv);
    584 
    585  // Clear memory storage
    586  rv = service->MemoryCacheStorage(info, getter_AddRefs(storage));
    587  NS_ENSURE_SUCCESS(rv, rv);
    588  rv = storage->AsyncEvictStorage(nullptr);
    589  NS_ENSURE_SUCCESS(rv, rv);
    590 
    591  return NS_OK;
    592 }
    593 
    594 nsresult Run(OriginAttributes& aOa) {
    595  nsresult rv;
    596 
    597  // Clear all [private X anonymous] combinations
    598  rv = ClearStorage(false, false, aOa);
    599  NS_ENSURE_SUCCESS(rv, rv);
    600  rv = ClearStorage(false, true, aOa);
    601  NS_ENSURE_SUCCESS(rv, rv);
    602  rv = ClearStorage(true, false, aOa);
    603  NS_ENSURE_SUCCESS(rv, rv);
    604  rv = ClearStorage(true, true, aOa);
    605  NS_ENSURE_SUCCESS(rv, rv);
    606 
    607  return NS_OK;
    608 }
    609 
    610 }  // namespace CacheStorageEvictHelper
    611 
    612 // nsICacheStorageService
    613 
    614 NS_IMETHODIMP CacheStorageService::MemoryCacheStorage(
    615    nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
    616  NS_ENSURE_ARG(_retval);
    617 
    618  nsCOMPtr<nsICacheStorage> storage =
    619      new CacheStorage(aLoadContextInfo, false, false, false);
    620  storage.forget(_retval);
    621  return NS_OK;
    622 }
    623 
    624 NS_IMETHODIMP CacheStorageService::DiskCacheStorage(
    625    nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
    626  NS_ENSURE_ARG(_retval);
    627 
    628  // TODO save some heap granularity - cache commonly used storages.
    629 
    630  // When disk cache is disabled, still provide a storage, but just keep stuff
    631  // in memory.
    632  bool useDisk = CacheObserver::UseDiskCache();
    633 
    634  nsCOMPtr<nsICacheStorage> storage = new CacheStorage(
    635      aLoadContextInfo, useDisk, false /* size limit */, false /* don't pin */);
    636  storage.forget(_retval);
    637  return NS_OK;
    638 }
    639 
    640 NS_IMETHODIMP CacheStorageService::PinningCacheStorage(
    641    nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
    642  NS_ENSURE_ARG(aLoadContextInfo);
    643  NS_ENSURE_ARG(_retval);
    644 
    645  // When disk cache is disabled don't pretend we cache.
    646  if (!CacheObserver::UseDiskCache()) {
    647    return NS_ERROR_NOT_AVAILABLE;
    648  }
    649 
    650  nsCOMPtr<nsICacheStorage> storage =
    651      new CacheStorage(aLoadContextInfo, true /* use disk */,
    652                       true /* ignore size checks */, true /* pin */);
    653  storage.forget(_retval);
    654  return NS_OK;
    655 }
    656 
    657 NS_IMETHODIMP CacheStorageService::Clear() {
    658  nsresult rv;
    659 
    660  // Tell the index to block notification to AsyncGetDiskConsumption.
    661  // Will be allowed again from CacheFileContextEvictor::EvictEntries()
    662  // when all the context have been removed from disk.
    663  CacheIndex::OnAsyncEviction(true);
    664 
    665  StaticMutexAutoLock lock(sLock);
    666 
    667  {
    668    mozilla::MutexAutoLock forcedValidEntriesLock(mForcedValidEntriesLock);
    669    mForcedValidEntries.Clear();
    670  }
    671 
    672  NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
    673 
    674  const auto keys = ToTArray<nsTArray<nsCString>>(sGlobalEntryTables->Keys());
    675  for (const auto& key : keys) {
    676    DoomStorageEntries(key, nullptr, true, false, nullptr);
    677  }
    678 
    679  // Passing null as a load info means to evict all contexts.
    680  // EvictByContext() respects the entry pinning.  EvictAll() does not.
    681  rv = CacheFileIOManager::EvictByContext(nullptr, false, u""_ns);
    682  NS_ENSURE_SUCCESS(rv, rv);
    683 
    684  return NS_OK;
    685 }
    686 
    687 NS_IMETHODIMP CacheStorageService::ClearOriginsByPrincipal(
    688    nsIPrincipal* aPrincipal) {
    689  nsresult rv;
    690 
    691  if (NS_WARN_IF(!aPrincipal)) {
    692    return NS_ERROR_FAILURE;
    693  }
    694 
    695  nsAutoString origin;
    696  rv = nsContentUtils::GetWebExposedOriginSerialization(aPrincipal, origin);
    697  NS_ENSURE_SUCCESS(rv, rv);
    698  LOG(("CacheStorageService::ClearOriginsByPrincipal %s",
    699       NS_ConvertUTF16toUTF8(origin).get()));
    700 
    701  rv = ClearOriginInternal(origin, aPrincipal->OriginAttributesRef(), true);
    702  NS_ENSURE_SUCCESS(rv, rv);
    703 
    704  rv = ClearOriginInternal(origin, aPrincipal->OriginAttributesRef(), false);
    705  NS_ENSURE_SUCCESS(rv, rv);
    706 
    707  return NS_OK;
    708 }
    709 
    710 NS_IMETHODIMP CacheStorageService::ClearOriginsByOriginAttributes(
    711    const nsAString& aOriginAttributes) {
    712  nsresult rv;
    713  LOG(("CacheStorageService::ClearOriginsByOriginAttributes %s",
    714       NS_ConvertUTF16toUTF8(aOriginAttributes).get()));
    715 
    716  if (NS_WARN_IF(aOriginAttributes.IsEmpty())) {
    717    return NS_ERROR_FAILURE;
    718  }
    719 
    720  OriginAttributes oa;
    721  if (!oa.Init(aOriginAttributes)) {
    722    NS_ERROR("Could not parse the argument for OriginAttributes");
    723    return NS_ERROR_FAILURE;
    724  }
    725 
    726  rv = CacheStorageEvictHelper::Run(oa);
    727  NS_ENSURE_SUCCESS(rv, rv);
    728 
    729  return NS_OK;
    730 }
    731 
    732 static bool RemoveExactEntry(CacheEntryTable* aEntries, nsACString const& aKey,
    733                             CacheEntry* aEntry, bool aOverwrite) {
    734  RefPtr<CacheEntry> existingEntry;
    735  if (!aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
    736    LOG(("RemoveExactEntry [entry=%p already gone]", aEntry));
    737    return false;  // Already removed...
    738  }
    739 
    740  if (!aOverwrite && existingEntry != aEntry) {
    741    LOG(("RemoveExactEntry [entry=%p already replaced]", aEntry));
    742    return false;  // Already replaced...
    743  }
    744 
    745  LOG(("RemoveExactEntry [entry=%p removed]", aEntry));
    746  aEntries->Remove(aKey);
    747  return true;
    748 }
    749 
    750 NS_IMETHODIMP CacheStorageService::ClearBaseDomain(
    751    const nsAString& aBaseDomain) {
    752  LOG(("CacheStorageService::ClearBaseDomain %s",
    753       NS_ConvertUTF16toUTF8(aBaseDomain).get()));
    754  StaticMutexAutoLock lock(sLock);
    755  if (sGlobalEntryTables) {
    756    if (mShutdown) return NS_ERROR_NOT_AVAILABLE;
    757 
    758    nsCString cBaseDomain = NS_ConvertUTF16toUTF8(aBaseDomain);
    759 
    760    nsTArray<nsCString> keys;
    761    for (const auto& globalEntry : *sGlobalEntryTables) {
    762      // Match by partitionKey base domain. This should cover most cache entries
    763      // because we statically partition the cache. Most first party cache
    764      // entries will also have a partitionKey set where the partitionKey base
    765      // domain will match the entry URI base domain.
    766      const nsACString& key = globalEntry.GetKey();
    767      nsCOMPtr<nsILoadContextInfo> info =
    768          CacheFileUtils::ParseKey(globalEntry.GetKey());
    769 
    770      if (info &&
    771          StoragePrincipalHelper::PartitionKeyHasBaseDomain(
    772              info->OriginAttributesPtr()->mPartitionKey, aBaseDomain)) {
    773        keys.AppendElement(key);
    774        continue;
    775      }
    776 
    777      // If we didn't get a partitionKey match, try to match by entry URI. This
    778      // requires us to iterate over all entries.
    779      CacheEntryTable* table = globalEntry.GetWeak();
    780      MOZ_ASSERT(table);
    781 
    782      nsTArray<RefPtr<CacheEntry>> entriesToDelete;
    783 
    784      for (CacheEntry* entry : table->Values()) {
    785        nsCOMPtr<nsIURI> uri;
    786        nsresult rv = NS_NewURI(getter_AddRefs(uri), entry->GetURI());
    787        if (NS_WARN_IF(NS_FAILED(rv))) {
    788          continue;
    789        }
    790 
    791        nsAutoCString host;
    792        rv = uri->GetHost(host);
    793        // Some entries may not have valid hosts. We can skip them.
    794        if (NS_FAILED(rv) || host.IsEmpty()) {
    795          continue;
    796        }
    797 
    798        bool hasRootDomain = false;
    799        rv = HasRootDomain(host, cBaseDomain, &hasRootDomain);
    800        if (NS_WARN_IF(NS_FAILED(rv))) {
    801          continue;
    802        }
    803        if (hasRootDomain) {
    804          entriesToDelete.AppendElement(entry);
    805        }
    806      }
    807 
    808      // Clear individual matched entries.
    809      for (RefPtr<CacheEntry>& entry : entriesToDelete) {
    810        nsAutoCString entryKey;
    811        nsresult rv = entry->HashingKey(entryKey);
    812        if (NS_FAILED(rv)) {
    813          NS_ERROR("aEntry->HashingKey() failed?");
    814          return rv;
    815        }
    816 
    817        RemoveExactEntry(table, entryKey, entry, false /* don't overwrite */);
    818      }
    819    }
    820 
    821    // Clear matched keys.
    822    for (uint32_t i = 0; i < keys.Length(); ++i) {
    823      LOG(("CacheStorageService::ClearBaseDomain Dooming %s", keys[i].get()));
    824      DoomStorageEntries(keys[i], nullptr, true, false, nullptr);
    825    }
    826  }
    827 
    828  return CacheFileIOManager::EvictByContext(nullptr, false /* pinned */, u""_ns,
    829                                            aBaseDomain);
    830 }
    831 
    832 nsresult CacheStorageService::ClearOriginInternal(
    833    const nsAString& aOrigin, const OriginAttributes& aOriginAttributes,
    834    bool aAnonymous) {
    835  nsresult rv;
    836 
    837  RefPtr<LoadContextInfo> info =
    838      GetLoadContextInfo(aAnonymous, aOriginAttributes);
    839  if (NS_WARN_IF(!info)) {
    840    return NS_ERROR_FAILURE;
    841  }
    842 
    843  StaticMutexAutoLock lock(sLock);
    844 
    845  if (sGlobalEntryTables) {
    846    for (const auto& globalEntry : *sGlobalEntryTables) {
    847      bool matches = false;
    848      rv = CacheFileUtils::KeyMatchesLoadContextInfo(globalEntry.GetKey(), info,
    849                                                     &matches);
    850      NS_ENSURE_SUCCESS(rv, rv);
    851      if (!matches) {
    852        continue;
    853      }
    854 
    855      CacheEntryTable* table = globalEntry.GetWeak();
    856      MOZ_ASSERT(table);
    857 
    858      nsTArray<RefPtr<CacheEntry>> entriesToDelete;
    859 
    860      for (CacheEntry* entry : table->Values()) {
    861        nsCOMPtr<nsIURI> uri;
    862        rv = NS_NewURI(getter_AddRefs(uri), entry->GetURI());
    863        NS_ENSURE_SUCCESS(rv, rv);
    864 
    865        nsAutoString origin;
    866        rv = nsContentUtils::GetWebExposedOriginSerialization(uri, origin);
    867        NS_ENSURE_SUCCESS(rv, rv);
    868 
    869        if (origin != aOrigin) {
    870          continue;
    871        }
    872 
    873        entriesToDelete.AppendElement(entry);
    874      }
    875 
    876      for (RefPtr<CacheEntry>& entry : entriesToDelete) {
    877        nsAutoCString entryKey;
    878        rv = entry->HashingKey(entryKey);
    879        if (NS_FAILED(rv)) {
    880          NS_ERROR("aEntry->HashingKey() failed?");
    881          return rv;
    882        }
    883        MOZ_ASSERT_IF(info->IsPrivate(), !entry->IsUsingDisk());
    884        RemoveExactEntry(table, entryKey, entry, false /* don't overwrite */);
    885      }
    886    }
    887  }
    888 
    889  if (!info->IsPrivate()) {
    890    rv = CacheFileIOManager::EvictByContext(info, false /* pinned */, aOrigin);
    891  }
    892  NS_ENSURE_SUCCESS(rv, rv);
    893 
    894  return NS_OK;
    895 }
    896 
    897 NS_IMETHODIMP CacheStorageService::ClearOriginDictionary(nsIURI* aURI) {
    898  LOG(("CacheStorageService::ClearOriginDictionary"));
    899  // Note: due to cookie samesite rules, we need to clean for all ports
    900  DictionaryCache::RemoveDictionariesForOrigin(aURI);
    901  return NS_OK;
    902 }
    903 
    904 NS_IMETHODIMP CacheStorageService::ClearAllOriginDictionaries() {
    905  LOG(("CacheStorageService::ClearAllOriginDictionaries"));
    906  DictionaryCache::RemoveAllDictionaries();
    907  return NS_OK;
    908 }
    909 
    910 NS_IMETHODIMP CacheStorageService::PurgeFromMemory(uint32_t aWhat) {
    911  uint32_t what;
    912 
    913  switch (aWhat) {
    914    case PURGE_DISK_DATA_ONLY:
    915      what = CacheEntry::PURGE_DATA_ONLY_DISK_BACKED;
    916      break;
    917 
    918    case PURGE_DISK_ALL:
    919      what = CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED;
    920      break;
    921 
    922    case PURGE_EVERYTHING:
    923      what = CacheEntry::PURGE_WHOLE;
    924      break;
    925 
    926    default:
    927      return NS_ERROR_INVALID_ARG;
    928  }
    929 
    930  nsCOMPtr<nsIRunnable> event = new PurgeFromMemoryRunnable(this, what);
    931 
    932  return Dispatch(event);
    933 }
    934 
    935 NS_IMETHODIMP CacheStorageService::PurgeFromMemoryRunnable::Run() {
    936  if (NS_IsMainThread()) {
    937    nsCOMPtr<nsIObserverService> observerService =
    938        mozilla::services::GetObserverService();
    939    if (observerService) {
    940      observerService->NotifyObservers(
    941          nullptr, "cacheservice:purge-memory-pools", nullptr);
    942    }
    943 
    944    return NS_OK;
    945  }
    946 
    947  if (mService) {
    948    // Note that we seem to come here only in the case of "memory-pressure"
    949    // being notified (or in case of tests), so we start from purging in-memory
    950    // entries first and ignore minprogress for disk entries.
    951    // TODO not all flags apply to both pools.
    952    mService->Pool(MemoryPool::EType::MEMORY)
    953        .PurgeAll(mWhat, StaticPrefs::network_cache_purge_minprogress_memory());
    954    mService->Pool(MemoryPool::EType::DISK).PurgeAll(mWhat, 0);
    955    mService = nullptr;
    956  }
    957 
    958  NS_DispatchToMainThread(this);
    959  return NS_OK;
    960 }
    961 
    962 NS_IMETHODIMP CacheStorageService::AsyncGetDiskConsumption(
    963    nsICacheStorageConsumptionObserver* aObserver) {
    964  NS_ENSURE_ARG(aObserver);
    965 
    966  nsresult rv;
    967 
    968  rv = CacheIndex::AsyncGetDiskConsumption(aObserver);
    969  NS_ENSURE_SUCCESS(rv, rv);
    970 
    971  return NS_OK;
    972 }
    973 
    974 NS_IMETHODIMP CacheStorageService::GetIoTarget(nsIEventTarget** aEventTarget) {
    975  NS_ENSURE_ARG(aEventTarget);
    976 
    977  nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
    978  ioTarget.forget(aEventTarget);
    979 
    980  return NS_OK;
    981 }
    982 
    983 NS_IMETHODIMP CacheStorageService::AsyncVisitAllStorages(
    984    nsICacheStorageVisitor* aVisitor, bool aVisitEntries) {
    985  LOG(("CacheStorageService::AsyncVisitAllStorages [cb=%p]", aVisitor));
    986  NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
    987 
    988  // Walking the disk cache also walks the memory cache.
    989  RefPtr<CacheStorageServiceInternal::WalkDiskCacheRunnable> event =
    990      new CacheStorageServiceInternal::WalkDiskCacheRunnable(
    991          nullptr, aVisitEntries, aVisitor);
    992  return event->Walk();
    993 }
    994 
    995 // Methods used by CacheEntry for management of in-memory structures.
    996 
    997 void CacheStorageService::RegisterEntry(CacheEntry* aEntry) {
    998  MOZ_ASSERT(IsOnManagementThread());
    999 
   1000  if (mShutdown || !aEntry->CanRegister()) return;
   1001 
   1002  TelemetryRecordEntryCreation(aEntry);
   1003 
   1004  LOG(("CacheStorageService::RegisterEntry [entry=%p]", aEntry));
   1005 
   1006  MemoryPool& pool = Pool(aEntry->IsUsingDisk());
   1007  pool.mManagedEntries.insertBack(aEntry);
   1008 
   1009  aEntry->SetRegistered(true);
   1010 }
   1011 
   1012 void CacheStorageService::UnregisterEntry(CacheEntry* aEntry) {
   1013  MOZ_ASSERT(IsOnManagementThread());
   1014 
   1015  if (!aEntry->IsRegistered()) return;
   1016 
   1017  TelemetryRecordEntryRemoval(aEntry);
   1018 
   1019  LOG(("CacheStorageService::UnregisterEntry [entry=%p]", aEntry));
   1020 
   1021  MemoryPool& pool = Pool(aEntry->IsUsingDisk());
   1022  aEntry->removeFrom(pool.mManagedEntries);
   1023 
   1024  // Note: aEntry->CanRegister() since now returns false
   1025  aEntry->SetRegistered(false);
   1026 }
   1027 
   1028 static bool AddExactEntry(CacheEntryTable* aEntries, nsACString const& aKey,
   1029                          CacheEntry* aEntry, bool aOverwrite) {
   1030  RefPtr<CacheEntry> existingEntry;
   1031  if (!aOverwrite && aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
   1032    bool equals = existingEntry == aEntry;
   1033    LOG(("AddExactEntry [entry=%p equals=%d]", aEntry, equals));
   1034    return equals;  // Already there...
   1035  }
   1036 
   1037  LOG(("AddExactEntry [entry=%p put]", aEntry));
   1038  aEntries->InsertOrUpdate(aKey, RefPtr{aEntry});
   1039  return true;
   1040 }
   1041 
   1042 bool CacheStorageService::RemoveEntry(CacheEntry* aEntry,
   1043                                      bool aOnlyUnreferenced) {
   1044  LOG(("CacheStorageService::RemoveEntry [entry=%p]", aEntry));
   1045 
   1046  nsAutoCString entryKey;
   1047  nsresult rv = aEntry->HashingKey(entryKey);
   1048  if (NS_FAILED(rv)) {
   1049    NS_ERROR("aEntry->HashingKey() failed?");
   1050    return false;
   1051  }
   1052 
   1053  StaticMutexAutoLock lock(sLock);
   1054 
   1055  if (mShutdown) {
   1056    LOG(("  after shutdown"));
   1057    return false;
   1058  }
   1059 
   1060  if (aOnlyUnreferenced) {
   1061    if (aEntry->IsReferenced()) {
   1062      LOG(("  still referenced, not removing"));
   1063      return false;
   1064    }
   1065 
   1066    if (!aEntry->IsUsingDisk() &&
   1067        IsForcedValidEntry(aEntry->GetStorageID(), entryKey)) {
   1068      LOG(("  forced valid, not removing"));
   1069      return false;
   1070    }
   1071  }
   1072 
   1073  CacheEntryTable* entries;
   1074  if (sGlobalEntryTables->Get(aEntry->GetStorageID(), &entries)) {
   1075    RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
   1076  }
   1077 
   1078  nsAutoCString memoryStorageID(aEntry->GetStorageID());
   1079  AppendMemoryStorageTag(memoryStorageID);
   1080 
   1081  if (sGlobalEntryTables->Get(memoryStorageID, &entries)) {
   1082    RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
   1083  }
   1084 
   1085  return true;
   1086 }
   1087 
   1088 void CacheStorageService::RecordMemoryOnlyEntry(CacheEntry* aEntry,
   1089                                                bool aOnlyInMemory,
   1090                                                bool aOverwrite) {
   1091  LOG(
   1092      ("CacheStorageService::RecordMemoryOnlyEntry [entry=%p, memory=%d, "
   1093       "overwrite=%d]",
   1094       aEntry, aOnlyInMemory, aOverwrite));
   1095  // This method is responsible to put this entry to a special record hashtable
   1096  // that contains only entries that are stored in memory.
   1097  // Keep in mind that every entry, regardless of whether is in-memory-only or
   1098  // not is always recorded in the storage master hash table, the one identified
   1099  // by CacheEntry.StorageID().
   1100 
   1101  sLock.AssertCurrentThreadOwns();
   1102 
   1103  if (mShutdown) {
   1104    LOG(("  after shutdown"));
   1105    return;
   1106  }
   1107 
   1108  nsresult rv;
   1109 
   1110  nsAutoCString entryKey;
   1111  rv = aEntry->HashingKey(entryKey);
   1112  if (NS_FAILED(rv)) {
   1113    NS_ERROR("aEntry->HashingKey() failed?");
   1114    return;
   1115  }
   1116 
   1117  CacheEntryTable* entries = nullptr;
   1118  nsAutoCString memoryStorageID(aEntry->GetStorageID());
   1119  AppendMemoryStorageTag(memoryStorageID);
   1120 
   1121  if (!sGlobalEntryTables->Get(memoryStorageID, &entries)) {
   1122    if (!aOnlyInMemory) {
   1123      LOG(("  not recorded as memory only"));
   1124      return;
   1125    }
   1126 
   1127    entries = sGlobalEntryTables
   1128                  ->InsertOrUpdate(
   1129                      memoryStorageID,
   1130                      MakeUnique<CacheEntryTable>(CacheEntryTable::MEMORY_ONLY))
   1131                  .get();
   1132    LOG(("  new memory-only storage table for %s", memoryStorageID.get()));
   1133  }
   1134 
   1135  if (aOnlyInMemory) {
   1136    AddExactEntry(entries, entryKey, aEntry, aOverwrite);
   1137  } else {
   1138    RemoveExactEntry(entries, entryKey, aEntry, aOverwrite);
   1139  }
   1140 }
   1141 
   1142 // Checks if a cache entry is forced valid (will be loaded directly from cache
   1143 // without further validation) - see nsICacheEntry.idl for further details
   1144 bool CacheStorageService::IsForcedValidEntry(nsACString const& aContextKey,
   1145                                             nsACString const& aEntryKey) {
   1146  return IsForcedValidEntry(aContextKey + aEntryKey);
   1147 }
   1148 
   1149 bool CacheStorageService::IsForcedValidEntry(
   1150    nsACString const& aContextEntryKey) {
   1151  mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
   1152 
   1153  ForcedValidData data;
   1154 
   1155  if (!mForcedValidEntries.Get(aContextEntryKey, &data)) {
   1156    return false;
   1157  }
   1158 
   1159  if (data.validUntil.IsNull()) {
   1160    MOZ_ASSERT_UNREACHABLE("the timeStamp should never be null");
   1161    return false;
   1162  }
   1163 
   1164  // Entry timeout not reached yet
   1165  if (TimeStamp::NowLoRes() <= data.validUntil) {
   1166    return true;
   1167  }
   1168 
   1169  // Entry timeout has been reached
   1170  mForcedValidEntries.Remove(aContextEntryKey);
   1171 
   1172  return false;
   1173 }
   1174 
   1175 void CacheStorageService::MarkForcedValidEntryUse(nsACString const& aContextKey,
   1176                                                  nsACString const& aEntryKey) {
   1177  mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
   1178 
   1179  ForcedValidData data;
   1180 
   1181  if (!mForcedValidEntries.Get(aContextKey + aEntryKey, &data)) {
   1182    return;
   1183  }
   1184 
   1185  data.viewed = true;
   1186  mForcedValidEntries.InsertOrUpdate(aContextKey + aEntryKey, data);
   1187 }
   1188 
   1189 // Allows a cache entry to be loaded directly from cache without further
   1190 // validation - see nsICacheEntry.idl for further details
   1191 void CacheStorageService::ForceEntryValidFor(nsACString const& aContextKey,
   1192                                             nsACString const& aEntryKey,
   1193                                             uint32_t aSecondsToTheFuture) {
   1194  mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
   1195 
   1196  TimeStamp now = TimeStamp::NowLoRes();
   1197  ForcedValidEntriesPrune(now);
   1198 
   1199  ForcedValidData data;
   1200  data.validUntil = now + TimeDuration::FromSeconds(aSecondsToTheFuture);
   1201  data.viewed = false;
   1202 
   1203  mForcedValidEntries.InsertOrUpdate(aContextKey + aEntryKey, data);
   1204 }
   1205 
   1206 void CacheStorageService::RemoveEntryForceValid(nsACString const& aContextKey,
   1207                                                nsACString const& aEntryKey) {
   1208  mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
   1209 
   1210  LOG(("CacheStorageService::RemoveEntryForceValid context='%s' entryKey=%s",
   1211       aContextKey.BeginReading(), aEntryKey.BeginReading()));
   1212  mForcedValidEntries.Remove(aContextKey + aEntryKey);
   1213 }
   1214 
   1215 // Cleans out the old entries in mForcedValidEntries
   1216 void CacheStorageService::ForcedValidEntriesPrune(TimeStamp& now) {
   1217  static TimeDuration const oneMinute = TimeDuration::FromSeconds(60);
   1218  static TimeStamp dontPruneUntil = now + oneMinute;
   1219  if (now < dontPruneUntil) return;
   1220 
   1221  for (auto iter = mForcedValidEntries.Iter(); !iter.Done(); iter.Next()) {
   1222    if (iter.Data().validUntil < now) {
   1223      iter.Remove();
   1224    }
   1225  }
   1226  dontPruneUntil = now + oneMinute;
   1227 }
   1228 
   1229 void CacheStorageService::OnMemoryConsumptionChange(
   1230    CacheMemoryConsumer* aConsumer, uint32_t aCurrentMemoryConsumption) {
   1231  LOG(("CacheStorageService::OnMemoryConsumptionChange [consumer=%p, size=%u]",
   1232       aConsumer, aCurrentMemoryConsumption));
   1233 
   1234  uint32_t savedMemorySize = aConsumer->LoadReportedMemoryConsumption();
   1235  if (savedMemorySize == aCurrentMemoryConsumption) return;
   1236 
   1237  // Exchange saved size with current one.
   1238  aConsumer->StoreReportedMemoryConsumption(aCurrentMemoryConsumption);
   1239 
   1240  bool usingDisk = !(aConsumer->LoadFlags() & CacheMemoryConsumer::MEMORY_ONLY);
   1241  bool overLimit = Pool(usingDisk).OnMemoryConsumptionChange(
   1242      savedMemorySize, aCurrentMemoryConsumption);
   1243 
   1244  if (!overLimit) return;
   1245 
   1246  // It's likely the timer has already been set when we get here,
   1247  // check outside the lock to save resources.
   1248 #ifdef MOZ_TSAN
   1249  if (mPurgeTimerActive) {
   1250 #else
   1251  if (mPurgeTimer) {
   1252 #endif
   1253    return;
   1254  }
   1255 
   1256  // We don't know if this is called under the service lock or not,
   1257  // hence rather dispatch.
   1258  RefPtr<nsIEventTarget> cacheIOTarget = Thread();
   1259  if (!cacheIOTarget) return;
   1260 
   1261  // Dispatch as a priority task, we want to set the purge timer
   1262  // ASAP to prevent vain redispatch of this event.
   1263  nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
   1264      "net::CacheStorageService::SchedulePurgeOverMemoryLimit", this,
   1265      &CacheStorageService::SchedulePurgeOverMemoryLimit);
   1266  cacheIOTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
   1267 }
   1268 
   1269 bool CacheStorageService::MemoryPool::OnMemoryConsumptionChange(
   1270    uint32_t aSavedMemorySize, uint32_t aCurrentMemoryConsumption) {
   1271  mMemorySize -= aSavedMemorySize;
   1272  mMemorySize += aCurrentMemoryConsumption;
   1273 
   1274  LOG(("  mMemorySize=%u (+%u,-%u)", uint32_t(mMemorySize),
   1275       aCurrentMemoryConsumption, aSavedMemorySize));
   1276 
   1277  // Bypass purging when memory has not grew up significantly
   1278  if (aCurrentMemoryConsumption <= aSavedMemorySize) return false;
   1279 
   1280  return mMemorySize > Limit();
   1281 }
   1282 
   1283 void CacheStorageService::SchedulePurgeOverMemoryLimit() {
   1284  LOG(("CacheStorageService::SchedulePurgeOverMemoryLimit"));
   1285 
   1286  StaticMutexAutoLock lock(sLock);
   1287 
   1288  if (mShutdown) {
   1289    LOG(("  past shutdown"));
   1290    return;
   1291  }
   1292 
   1293  if (mPurgeTimer) {
   1294    LOG(("  timer already up"));
   1295    return;
   1296  }
   1297 
   1298  mPurgeTimer = NS_NewTimer();
   1299  if (mPurgeTimer) {
   1300 #ifdef MOZ_TSAN
   1301    mPurgeTimerActive = true;
   1302 #endif
   1303    nsresult rv;
   1304    rv = mPurgeTimer->InitWithCallback(this, 1000, nsITimer::TYPE_ONE_SHOT);
   1305    LOG(("  timer init rv=0x%08" PRIx32, static_cast<uint32_t>(rv)));
   1306  }
   1307 }
   1308 
   1309 NS_IMETHODIMP
   1310 CacheStorageService::Notify(nsITimer* aTimer) {
   1311  LOG(("CacheStorageService::Notify"));
   1312 
   1313  StaticMutexAutoLock lock(sLock);
   1314 
   1315  if (aTimer == mPurgeTimer) {
   1316 #ifdef MOZ_TSAN
   1317    mPurgeTimerActive = false;
   1318 #endif
   1319    mPurgeTimer = nullptr;
   1320 
   1321    if (!mShutdown) {
   1322      nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
   1323          "net::CacheStorageService::PurgeExpiredOrOverMemoryLimit", this,
   1324          &CacheStorageService::PurgeExpiredOrOverMemoryLimit);
   1325      Dispatch(event);
   1326    }
   1327  }
   1328 
   1329  return NS_OK;
   1330 }
   1331 
   1332 NS_IMETHODIMP
   1333 CacheStorageService::GetName(nsACString& aName) {
   1334  aName.AssignLiteral("CacheStorageService");
   1335  return NS_OK;
   1336 }
   1337 
   1338 void CacheStorageService::PurgeExpiredOrOverMemoryLimit() {
   1339  MOZ_ASSERT(IsOnManagementThread());
   1340 
   1341  LOG(("CacheStorageService::PurgeExpiredOrOverMemoryLimit"));
   1342 
   1343  if (mShutdown) return;
   1344 
   1345  static TimeDuration const kFourSeconds = TimeDuration::FromSeconds(4);
   1346  TimeStamp now = TimeStamp::NowLoRes();
   1347 
   1348  if (!mLastPurgeTime.IsNull() && now - mLastPurgeTime < kFourSeconds) {
   1349    LOG(("  bypassed, too soon"));
   1350    return;
   1351  }
   1352 
   1353  mLastPurgeTime = now;
   1354 
   1355  // We start purging memory entries first as we care more about RAM over
   1356  // disk space beeing freed in case we are interrupted.
   1357  Pool(MemoryPool::EType::MEMORY).PurgeExpiredOrOverMemoryLimit();
   1358  Pool(MemoryPool::EType::DISK).PurgeExpiredOrOverMemoryLimit();
   1359 }
   1360 
   1361 void CacheStorageService::MemoryPool::PurgeExpiredOrOverMemoryLimit() {
   1362  if (StaticPrefs::network_cache_purge_disable()) {
   1363    return;
   1364  }
   1365  TimeStamp start(TimeStamp::Now());
   1366 
   1367  uint32_t const memoryLimit = Limit();
   1368  size_t minprogress =
   1369      (mType == EType::DISK)
   1370          ? StaticPrefs::network_cache_purge_minprogress_disk()
   1371          : StaticPrefs::network_cache_purge_minprogress_memory();
   1372 
   1373  // We always purge expired entries, even if under our limit.
   1374  size_t numExpired = PurgeExpired(minprogress);
   1375  if (numExpired > 0) {
   1376    LOG(("  found and purged %zu expired entries", numExpired));
   1377  }
   1378  minprogress = (minprogress > numExpired) ? minprogress - numExpired : 0;
   1379 
   1380  // If we are still under pressure, purge LFU entries until we aren't.
   1381  if (mMemorySize > memoryLimit) {
   1382    // Do not enter PurgeByFrecency if we reached the minimum and are asked to
   1383    // deliver entries.
   1384    if (minprogress == 0 && CacheIOThread::YieldAndRerun()) {
   1385      return;
   1386    }
   1387 
   1388    auto r = PurgeByFrecency(minprogress);
   1389    if (MOZ_LIKELY(r.isOk())) {
   1390      size_t numPurged = r.unwrap();
   1391      LOG((
   1392          "  memory data consumption over the limit, abandoned %zu LFU entries",
   1393          numPurged));
   1394    } else {
   1395      // If we hit an error (OOM), do an emergency PurgeAll.
   1396      size_t numPurged = PurgeAll(CacheEntry::PURGE_WHOLE, minprogress);
   1397      LOG(
   1398          ("  memory data consumption over the limit, emergency purged all %zu "
   1399           "entries",
   1400           numPurged));
   1401    }
   1402  }
   1403 
   1404  LOG(("  purging took %1.2fms", (TimeStamp::Now() - start).ToMilliseconds()));
   1405 }
   1406 
   1407 // This function purges ALL expired entries.
   1408 size_t CacheStorageService::MemoryPool::PurgeExpired(size_t minprogress) {
   1409  MOZ_ASSERT(IsOnManagementThread());
   1410 
   1411  uint32_t now = NowInSeconds();
   1412 
   1413  size_t numPurged = 0;
   1414  // Scan for items to purge. mManagedEntries is not sorted but comparing just
   1415  // one integer should be faster than anything else, so go scan.
   1416  RefPtr<CacheEntry> entry = mManagedEntries.getFirst();
   1417  while (entry) {
   1418    // Get the next entry before we may be removed from our list.
   1419    RefPtr<CacheEntry> nextEntry = entry->getNext();
   1420 
   1421    if (entry->GetExpirationTime() <= now) {
   1422      // Purge will modify our mManagedEntries list but we are prepared for it.
   1423      if (entry->Purge(CacheEntry::PURGE_WHOLE)) {
   1424        numPurged++;
   1425        LOG(("  purged expired, entry=%p, exptime=%u (now=%u)", entry.get(),
   1426             entry->GetExpirationTime(), now));
   1427      }
   1428    }
   1429 
   1430    entry = std::move(nextEntry);
   1431 
   1432    // To have some progress even under load, we do the check only after
   1433    // purging at least minprogress items if under pressure.
   1434    if ((numPurged >= minprogress || mMemorySize <= Limit()) &&
   1435        CacheIOThread::YieldAndRerun()) {
   1436      break;
   1437    }
   1438  }
   1439 
   1440  return numPurged;
   1441 }
   1442 
   1443 Result<size_t, nsresult> CacheStorageService::MemoryPool::PurgeByFrecency(
   1444    size_t minprogress) {
   1445  MOZ_ASSERT(IsOnManagementThread());
   1446 
   1447  // Pretend the limit is 10% lower so that we get rid of more entries at one
   1448  // shot and save the sorting below.
   1449  uint32_t const memoryLimit = (uint32_t)(Limit() * 0.9);
   1450  if (mMemorySize <= memoryLimit) {
   1451    return 0;
   1452  }
   1453 
   1454  LOG(("MemoryPool::PurgeByFrecency, len=%zu", mManagedEntries.length()));
   1455 
   1456  // We want to have an array snapshot for sorting and iterating.
   1457  struct mayPurgeEntry {
   1458    RefPtr<CacheEntry> mEntry;
   1459    double mFrecency;
   1460 
   1461    explicit mayPurgeEntry(CacheEntry* aEntry) {
   1462      mEntry = aEntry;
   1463      mFrecency = aEntry->GetFrecency();
   1464    }
   1465 
   1466    bool operator<(const mayPurgeEntry& aOther) const {
   1467      return mFrecency < aOther.mFrecency;
   1468    }
   1469  };
   1470 
   1471  nsTArray<mayPurgeEntry> mayPurgeSorted;
   1472  if (!mayPurgeSorted.SetCapacity(mManagedEntries.length(),
   1473                                  mozilla::fallible)) {
   1474    return Err(NS_ERROR_OUT_OF_MEMORY);
   1475  }
   1476  {
   1477    StaticMutexAutoLock lock(CacheStorageService::Self()->Lock());
   1478 
   1479    for (const auto& entry : mManagedEntries) {
   1480      // Referenced items cannot be purged and we deliberately want to not
   1481      // look at '0' frecency entries, these are new entries and can be
   1482      // ignored.  Also, any dict: (CompressionDictionary) entries for an
   1483      // origin should not be purged unless empty - they will empty out as
   1484      // the cache entries referenced by them are purged until they are empty.
   1485      if (!entry->IsReferenced() && entry->GetFrecency() > 0.0 &&
   1486          (!entry->GetEnhanceID().EqualsLiteral("dict:") ||
   1487           entry->GetMetadataMemoryConsumption() == 0)) {
   1488        mayPurgeEntry copy(entry);
   1489        mayPurgeSorted.AppendElement(std::move(copy));
   1490      } else {
   1491        if (entry->GetEnhanceID().EqualsLiteral("dict:")) {
   1492          LOG(
   1493              ("*** Ignored Entry is a dictionary origin, metadata size %d, "
   1494               "referenced %d, Frecency %f",
   1495               entry->GetMetadataMemoryConsumption(), entry->IsReferenced(),
   1496               entry->GetFrecency()));
   1497        }
   1498      }
   1499    }
   1500  }
   1501  if (mayPurgeSorted.Length() == 0) {
   1502    return 0;
   1503  }
   1504  mayPurgeSorted.Sort();
   1505 
   1506  size_t numPurged = 0;
   1507 
   1508  for (auto& checkPurge : mayPurgeSorted) {
   1509    if (mMemorySize <= memoryLimit) {
   1510      break;
   1511    }
   1512 
   1513    // Ensure it's deleted immediately if purged so we can record the
   1514    // mMemorySize savings
   1515    RefPtr<CacheEntry> entry = std::move(checkPurge.mEntry);
   1516 
   1517    if (entry->Purge(CacheEntry::PURGE_WHOLE)) {
   1518      numPurged++;
   1519      LOG(("  abandoned (%d), entry=%p, frecency=%1.10f",
   1520           CacheEntry::PURGE_WHOLE, entry.get(), entry->GetFrecency()));
   1521    }
   1522 
   1523    if (numPurged >= minprogress && CacheIOThread::YieldAndRerun()) {
   1524      LOG(("MemoryPool::PurgeByFrecency interrupted"));
   1525      return numPurged;
   1526    }
   1527  }
   1528 
   1529  LOG(
   1530      ("MemoryPool::PurgeByFrecency done, purged %zu - mMemorySize %u, "
   1531       "memoryLimit %u",
   1532       numPurged, (uint32_t)mMemorySize, memoryLimit));
   1533 
   1534  return numPurged;
   1535 }
   1536 
   1537 size_t CacheStorageService::MemoryPool::PurgeAll(uint32_t aWhat,
   1538                                                 size_t minprogress) {
   1539  LOG(("CacheStorageService::MemoryPool::PurgeAll aWhat=%d", aWhat));
   1540  MOZ_ASSERT(IsOnManagementThread());
   1541 
   1542  size_t numPurged = 0;
   1543 
   1544  RefPtr<CacheEntry> entry = mManagedEntries.getFirst();
   1545  while (entry) {
   1546    if (numPurged >= minprogress && CacheIOThread::YieldAndRerun()) break;
   1547 
   1548    // Get the next entry before we may be removed from our list.
   1549    RefPtr<CacheEntry> nextEntry = entry->getNext();
   1550 
   1551    if (entry->Purge(aWhat)) {
   1552      numPurged++;
   1553      LOG(("  abandoned entry=%p", entry.get()));
   1554    }
   1555 
   1556    entry = std::move(nextEntry);
   1557  }
   1558 
   1559  return numPurged;
   1560 }
   1561 
   1562 // Methods exposed to and used by CacheStorage.
   1563 
   1564 nsresult CacheStorageService::AddStorageEntry(CacheStorage const* aStorage,
   1565                                              const nsACString& aURI,
   1566                                              const nsACString& aIdExtension,
   1567                                              uint32_t aFlags,
   1568                                              CacheEntryHandle** aResult) {
   1569  NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
   1570 
   1571  NS_ENSURE_ARG(aStorage);
   1572 
   1573  nsAutoCString contextKey;
   1574  CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
   1575 
   1576  return AddStorageEntry(contextKey, aURI, aIdExtension,
   1577                         aStorage->WriteToDisk(), aStorage->SkipSizeCheck(),
   1578                         aStorage->Pinning(), aFlags, aResult);
   1579 }
   1580 
   1581 nsresult CacheStorageService::AddStorageEntry(
   1582    const nsACString& aContextKey, const nsACString& aURI,
   1583    const nsACString& aIdExtension, bool aWriteToDisk, bool aSkipSizeCheck,
   1584    bool aPin, uint32_t aFlags, CacheEntryHandle** aResult) {
   1585  nsresult rv;
   1586 
   1587  nsAutoCString entryKey;
   1588  rv = CacheEntry::HashingKey(""_ns, aIdExtension, aURI, entryKey);
   1589  NS_ENSURE_SUCCESS(rv, rv);
   1590 
   1591  LOG(("CacheStorageService::AddStorageEntry [entryKey=%s, contextKey=%s]",
   1592       entryKey.get(), aContextKey.BeginReading()));
   1593 
   1594  RefPtr<CacheEntry> entry;
   1595  RefPtr<CacheEntryHandle> handle;
   1596 
   1597  {
   1598    StaticMutexAutoLock lock(sLock);
   1599 
   1600    NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
   1601 
   1602    // Ensure storage table
   1603    CacheEntryTable* const entries =
   1604        sGlobalEntryTables
   1605            ->LookupOrInsertWith(
   1606                aContextKey,
   1607                [&aContextKey] {
   1608                  LOG(("  new storage entries table for context '%s'",
   1609                       aContextKey.BeginReading()));
   1610                  return MakeUnique<CacheEntryTable>(
   1611                      CacheEntryTable::ALL_ENTRIES);
   1612                })
   1613            .get();
   1614 
   1615    bool entryExists = entries->Get(entryKey, getter_AddRefs(entry));
   1616    if (!entryExists && (aFlags & nsICacheStorage::OPEN_READONLY) &&
   1617        (aFlags & nsICacheStorage::OPEN_SECRETLY) &&
   1618        StaticPrefs::network_cache_bug1708673()) {
   1619      return NS_ERROR_CACHE_KEY_NOT_FOUND;
   1620    }
   1621    if (entryExists && (aFlags & nsICacheStorage::OPEN_COMPLETE_ONLY)) {
   1622      bool ready = false;
   1623      // We're looking for complete files, even if they're being revalidated
   1624      // (for dictionaries)
   1625      entry->GetReadyOrRevalidating(&ready);
   1626      if (!ready) {
   1627        return NS_ERROR_CACHE_KEY_NOT_FOUND;
   1628      }
   1629    }
   1630 
   1631    bool replace = aFlags & nsICacheStorage::OPEN_TRUNCATE;
   1632 
   1633    if (entryExists && !replace) {
   1634      // check whether we want to turn this entry to a memory-only.
   1635      if (MOZ_UNLIKELY(!aWriteToDisk) && MOZ_LIKELY(entry->IsUsingDisk())) {
   1636        LOG(("  entry is persistent but we want mem-only, replacing it"));
   1637        replace = true;
   1638      }
   1639    }
   1640 
   1641    // If truncate is demanded, delete and doom the current entry
   1642    if (entryExists && replace) {
   1643      entries->Remove(entryKey);
   1644 
   1645      LOG(("  dooming entry %p for %s because of OPEN_TRUNCATE", entry.get(),
   1646           entryKey.get()));
   1647      // On purpose called under the lock to prevent races of doom and open on
   1648      // I/O thread No need to remove from both memory-only and all-entries
   1649      // tables.  The new entry will overwrite the shadow entry in its ctor.
   1650      entry->DoomAlreadyRemoved();
   1651 
   1652      entry = nullptr;
   1653      entryExists = false;
   1654 
   1655      // Would only lead to deleting force-valid timestamp again.  We don't need
   1656      // the replace information anymore after this point anyway.
   1657      replace = false;
   1658    }
   1659 
   1660    // Ensure entry for the particular URL
   1661    if (!entryExists) {
   1662      // When replacing with a new entry, always remove the current force-valid
   1663      // timestamp, this is the only place to do it.
   1664      if (replace) {
   1665        RemoveEntryForceValid(aContextKey, entryKey);
   1666      }
   1667 
   1668      // Entry is not in the hashtable or has just been truncated...
   1669      entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk,
   1670                             aSkipSizeCheck, aPin);
   1671      entries->InsertOrUpdate(entryKey, RefPtr{entry});
   1672      LOG(("  new entry %p for %s", entry.get(), entryKey.get()));
   1673    }
   1674 
   1675    if (entry) {
   1676      // Here, if this entry was not for a long time referenced by any consumer,
   1677      // gets again first 'handles count' reference.
   1678      handle = entry->NewHandle();
   1679    }
   1680  }
   1681 
   1682  handle.forget(aResult);
   1683  return NS_OK;
   1684 }
   1685 
   1686 nsresult CacheStorageService::CheckStorageEntry(CacheStorage const* aStorage,
   1687                                                const nsACString& aURI,
   1688                                                const nsACString& aIdExtension,
   1689                                                bool* aResult) {
   1690  nsresult rv;
   1691 
   1692  nsAutoCString contextKey;
   1693  CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
   1694 
   1695  if (!aStorage->WriteToDisk()) {
   1696    AppendMemoryStorageTag(contextKey);
   1697  }
   1698 
   1699  LOG(("CacheStorageService::CheckStorageEntry [uri=%s, eid=%s, contextKey=%s]",
   1700       aURI.BeginReading(), aIdExtension.BeginReading(), contextKey.get()));
   1701 
   1702  {
   1703    StaticMutexAutoLock lock(sLock);
   1704 
   1705    NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
   1706 
   1707    nsAutoCString entryKey;
   1708    rv = CacheEntry::HashingKey(""_ns, aIdExtension, aURI, entryKey);
   1709    NS_ENSURE_SUCCESS(rv, rv);
   1710 
   1711    CacheEntryTable* entries;
   1712    if ((*aResult = sGlobalEntryTables->Get(contextKey, &entries)) &&
   1713        entries->GetWeak(entryKey, aResult)) {
   1714      LOG(("  found in hash tables"));
   1715      return NS_OK;
   1716    }
   1717  }
   1718 
   1719  if (!aStorage->WriteToDisk()) {
   1720    // Memory entry, nothing more to do.
   1721    LOG(("  not found in hash tables"));
   1722    return NS_OK;
   1723  }
   1724 
   1725  // Disk entry, not found in the hashtable, check the index.
   1726  nsAutoCString fileKey;
   1727  rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, fileKey);
   1728 
   1729  CacheIndex::EntryStatus status;
   1730  rv = CacheIndex::HasEntry(fileKey, &status);
   1731  if (NS_FAILED(rv) || status == CacheIndex::DO_NOT_KNOW) {
   1732    LOG(("  index doesn't know, rv=0x%08" PRIx32, static_cast<uint32_t>(rv)));
   1733    return NS_ERROR_NOT_AVAILABLE;
   1734  }
   1735 
   1736  *aResult = status == CacheIndex::EXISTS;
   1737  LOG(("  %sfound in index", *aResult ? "" : "not "));
   1738  return NS_OK;
   1739 }
   1740 
   1741 nsresult CacheStorageService::GetCacheIndexEntryAttrs(
   1742    CacheStorage const* aStorage, const nsACString& aURI,
   1743    const nsACString& aIdExtension, bool* aHasAltData, uint32_t* aFileSizeKb) {
   1744  nsresult rv;
   1745 
   1746  nsAutoCString contextKey;
   1747  CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
   1748 
   1749  LOG(
   1750      ("CacheStorageService::GetCacheIndexEntryAttrs [uri=%s, eid=%s, "
   1751       "contextKey=%s]",
   1752       aURI.BeginReading(), aIdExtension.BeginReading(), contextKey.get()));
   1753 
   1754  nsAutoCString fileKey;
   1755  rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, fileKey);
   1756  if (NS_FAILED(rv)) {
   1757    return rv;
   1758  }
   1759 
   1760  *aHasAltData = false;
   1761  *aFileSizeKb = 0;
   1762  auto closure = [&aHasAltData, &aFileSizeKb](const CacheIndexEntry* entry) {
   1763    *aHasAltData = entry->GetHasAltData();
   1764    *aFileSizeKb = entry->GetFileSize();
   1765  };
   1766 
   1767  CacheIndex::EntryStatus status;
   1768  rv = CacheIndex::HasEntry(fileKey, &status, closure);
   1769  if (NS_FAILED(rv)) {
   1770    return rv;
   1771  }
   1772 
   1773  if (status != CacheIndex::EXISTS) {
   1774    return NS_ERROR_CACHE_KEY_NOT_FOUND;
   1775  }
   1776 
   1777  return NS_OK;
   1778 }
   1779 
   1780 namespace {
   1781 
   1782 class CacheEntryDoomByKeyCallback : public CacheFileIOListener,
   1783                                    public nsIRunnable {
   1784 public:
   1785  NS_DECL_THREADSAFE_ISUPPORTS
   1786  NS_DECL_NSIRUNNABLE
   1787 
   1788  explicit CacheEntryDoomByKeyCallback(nsICacheEntryDoomCallback* aCallback)
   1789      : mCallback(aCallback), mResult(NS_ERROR_NOT_INITIALIZED) {}
   1790 
   1791 private:
   1792  virtual ~CacheEntryDoomByKeyCallback();
   1793 
   1794  NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override {
   1795    return NS_OK;
   1796  }
   1797  NS_IMETHOD OnDataWritten(CacheFileHandle* aHandle, const char* aBuf,
   1798                           nsresult aResult) override {
   1799    return NS_OK;
   1800  }
   1801  NS_IMETHOD OnDataRead(CacheFileHandle* aHandle, char* aBuf,
   1802                        nsresult aResult) override {
   1803    return NS_OK;
   1804  }
   1805  NS_IMETHOD OnFileDoomed(CacheFileHandle* aHandle, nsresult aResult) override;
   1806  NS_IMETHOD OnEOFSet(CacheFileHandle* aHandle, nsresult aResult) override {
   1807    return NS_OK;
   1808  }
   1809  NS_IMETHOD OnFileRenamed(CacheFileHandle* aHandle,
   1810                           nsresult aResult) override {
   1811    return NS_OK;
   1812  }
   1813 
   1814  nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
   1815  nsresult mResult;
   1816 };
   1817 
   1818 CacheEntryDoomByKeyCallback::~CacheEntryDoomByKeyCallback() {
   1819  if (mCallback) {
   1820    ProxyReleaseMainThread("CacheEntryDoomByKeyCallback::mCallback", mCallback);
   1821  }
   1822 }
   1823 
   1824 NS_IMETHODIMP CacheEntryDoomByKeyCallback::OnFileDoomed(
   1825    CacheFileHandle* aHandle, nsresult aResult) {
   1826  if (!mCallback) return NS_OK;
   1827 
   1828  mResult = aResult;
   1829  if (NS_IsMainThread()) {
   1830    Run();
   1831  } else {
   1832    NS_DispatchToMainThread(this);
   1833  }
   1834 
   1835  return NS_OK;
   1836 }
   1837 
   1838 NS_IMETHODIMP CacheEntryDoomByKeyCallback::Run() {
   1839  mCallback->OnCacheEntryDoomed(mResult);
   1840  return NS_OK;
   1841 }
   1842 
   1843 NS_IMPL_ISUPPORTS(CacheEntryDoomByKeyCallback, CacheFileIOListener,
   1844                  nsIRunnable);
   1845 
   1846 }  // namespace
   1847 
   1848 nsresult CacheStorageService::DoomStorageEntry(
   1849    CacheStorage const* aStorage, const nsACString& aURI,
   1850    const nsACString& aIdExtension, nsICacheEntryDoomCallback* aCallback) {
   1851  LOG(("CacheStorageService::DoomStorageEntry %s",
   1852       PromiseFlatCString(aURI).get()));
   1853 
   1854  NS_ENSURE_ARG(aStorage);
   1855 
   1856  nsAutoCString contextKey;
   1857  CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
   1858 
   1859  nsAutoCString entryKey;
   1860  nsresult rv = CacheEntry::HashingKey(""_ns, aIdExtension, aURI, entryKey);
   1861  NS_ENSURE_SUCCESS(rv, rv);
   1862 
   1863  RefPtr<CacheEntry> entry;
   1864  {
   1865    StaticMutexAutoLock lock(sLock);
   1866 
   1867    NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
   1868 
   1869    CacheEntryTable* entries;
   1870    if (sGlobalEntryTables->Get(contextKey, &entries)) {
   1871      if (entries->Get(entryKey, getter_AddRefs(entry))) {
   1872        if (aStorage->WriteToDisk() || !entry->IsUsingDisk()) {
   1873          // When evicting from disk storage, purge
   1874          // When evicting from memory storage and the entry is memory-only,
   1875          // purge
   1876          LOG(
   1877              ("  purging entry %p for %s [storage use disk=%d, entry use "
   1878               "disk=%d]",
   1879               entry.get(), entryKey.get(), aStorage->WriteToDisk(),
   1880               entry->IsUsingDisk()));
   1881          entries->Remove(entryKey);
   1882        } else {
   1883          // Otherwise, leave it
   1884          LOG(
   1885              ("  leaving entry %p for %s [storage use disk=%d, entry use "
   1886               "disk=%d]",
   1887               entry.get(), entryKey.get(), aStorage->WriteToDisk(),
   1888               entry->IsUsingDisk()));
   1889          entry = nullptr;
   1890        }
   1891      }
   1892    }
   1893 
   1894    if (!entry) {
   1895      RemoveEntryForceValid(contextKey, entryKey);
   1896    }
   1897  }
   1898 
   1899  if (entry) {
   1900    LOG(("  dooming entry %p for %s", entry.get(), entryKey.get()));
   1901    return entry->AsyncDoom(aCallback);
   1902  }
   1903 
   1904  LOG(("  no entry loaded for %s", entryKey.get()));
   1905 
   1906  if (aStorage->WriteToDisk()) {
   1907    nsAutoCString contextKey;
   1908    CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
   1909 
   1910    rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, entryKey);
   1911    NS_ENSURE_SUCCESS(rv, rv);
   1912 
   1913    LOG(("  dooming file only for %s", entryKey.get()));
   1914 
   1915    RefPtr<CacheEntryDoomByKeyCallback> callback(
   1916        new CacheEntryDoomByKeyCallback(aCallback));
   1917    rv = CacheFileIOManager::DoomFileByKey(entryKey, callback);
   1918    NS_ENSURE_SUCCESS(rv, rv);
   1919 
   1920    return NS_OK;
   1921  }
   1922 
   1923  class Callback : public Runnable {
   1924   public:
   1925    explicit Callback(nsICacheEntryDoomCallback* aCallback)
   1926        : mozilla::Runnable("Callback"), mCallback(aCallback) {}
   1927    NS_IMETHOD Run() override {
   1928      mCallback->OnCacheEntryDoomed(NS_ERROR_NOT_AVAILABLE);
   1929      return NS_OK;
   1930    }
   1931    nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
   1932  };
   1933 
   1934  if (aCallback) {
   1935    RefPtr<Runnable> callback = new Callback(aCallback);
   1936    return NS_DispatchToMainThread(callback);
   1937  }
   1938 
   1939  return NS_OK;
   1940 }
   1941 
   1942 nsresult CacheStorageService::DoomStorageEntries(
   1943    CacheStorage const* aStorage, nsICacheEntryDoomCallback* aCallback) {
   1944  LOG(("CacheStorageService::DoomStorageEntries"));
   1945 
   1946  NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
   1947  NS_ENSURE_ARG(aStorage);
   1948 
   1949  nsAutoCString contextKey;
   1950  CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
   1951 
   1952  StaticMutexAutoLock lock(sLock);
   1953 
   1954  return DoomStorageEntries(contextKey, aStorage->LoadInfo(),
   1955                            aStorage->WriteToDisk(), aStorage->Pinning(),
   1956                            aCallback);
   1957 }
   1958 
   1959 nsresult CacheStorageService::DoomStorageEntries(
   1960    const nsACString& aContextKey, nsILoadContextInfo* aContext,
   1961    bool aDiskStorage, bool aPinned, nsICacheEntryDoomCallback* aCallback) {
   1962  LOG(("CacheStorageService::DoomStorageEntries [context=%s]",
   1963       aContextKey.BeginReading()));
   1964 
   1965  sLock.AssertCurrentThreadOwns();
   1966 
   1967  NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
   1968 
   1969  nsAutoCString memoryStorageID(aContextKey);
   1970  AppendMemoryStorageTag(memoryStorageID);
   1971 
   1972  if (aDiskStorage) {
   1973    LOG(("  dooming disk+memory storage of %s", aContextKey.BeginReading()));
   1974 
   1975    // Walk one by one and remove entries according their pin status
   1976    CacheEntryTable *diskEntries, *memoryEntries;
   1977    if (sGlobalEntryTables->Get(aContextKey, &diskEntries)) {
   1978      sGlobalEntryTables->Get(memoryStorageID, &memoryEntries);
   1979 
   1980      for (auto iter = diskEntries->Iter(); !iter.Done(); iter.Next()) {
   1981        auto entry = iter.Data();
   1982        if (entry->DeferOrBypassRemovalOnPinStatus(aPinned)) {
   1983          continue;
   1984        }
   1985 
   1986        if (memoryEntries) {
   1987          RemoveExactEntry(memoryEntries, iter.Key(), entry, false);
   1988        }
   1989        iter.Remove();
   1990      }
   1991    }
   1992 
   1993    if (aContext && !aContext->IsPrivate()) {
   1994      LOG(("  dooming disk entries"));
   1995      CacheFileIOManager::EvictByContext(aContext, aPinned, u""_ns);
   1996    }
   1997  } else {
   1998    LOG(("  dooming memory-only storage of %s", aContextKey.BeginReading()));
   1999 
   2000    // Remove the memory entries table from the global tables.
   2001    // Since we store memory entries also in the disk entries table
   2002    // we need to remove the memory entries from the disk table one
   2003    // by one manually.
   2004    mozilla::UniquePtr<CacheEntryTable> memoryEntries;
   2005    sGlobalEntryTables->Remove(memoryStorageID, &memoryEntries);
   2006 
   2007    CacheEntryTable* diskEntries;
   2008    if (memoryEntries && sGlobalEntryTables->Get(aContextKey, &diskEntries)) {
   2009      for (const auto& memoryEntry : *memoryEntries) {
   2010        const auto& entry = memoryEntry.GetData();
   2011        RemoveExactEntry(diskEntries, memoryEntry.GetKey(), entry, false);
   2012      }
   2013    }
   2014  }
   2015 
   2016  {
   2017    mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
   2018 
   2019    if (aContext) {
   2020      for (auto iter = mForcedValidEntries.Iter(); !iter.Done(); iter.Next()) {
   2021        bool matches;
   2022        DebugOnly<nsresult> rv = CacheFileUtils::KeyMatchesLoadContextInfo(
   2023            iter.Key(), aContext, &matches);
   2024        MOZ_ASSERT(NS_SUCCEEDED(rv));
   2025 
   2026        if (matches) {
   2027          iter.Remove();
   2028        }
   2029      }
   2030    } else {
   2031      mForcedValidEntries.Clear();
   2032    }
   2033  }
   2034 
   2035  // An artificial callback.  This is a candidate for removal tho.  In the new
   2036  // cache any 'doom' or 'evict' function ensures that the entry or entries
   2037  // being doomed is/are not accessible after the function returns.  So there is
   2038  // probably no need for a callback - has no meaning.  But for compatibility
   2039  // with the old cache that is still in the tree we keep the API similar to be
   2040  // able to make tests as well as other consumers work for now.
   2041  class Callback : public Runnable {
   2042   public:
   2043    explicit Callback(nsICacheEntryDoomCallback* aCallback)
   2044        : mozilla::Runnable("Callback"), mCallback(aCallback) {}
   2045    NS_IMETHOD Run() override {
   2046      mCallback->OnCacheEntryDoomed(NS_OK);
   2047      return NS_OK;
   2048    }
   2049    nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
   2050  };
   2051 
   2052  if (aCallback) {
   2053    RefPtr<Runnable> callback = new Callback(aCallback);
   2054    return NS_DispatchToMainThread(callback);
   2055  }
   2056 
   2057  return NS_OK;
   2058 }
   2059 
   2060 nsresult CacheStorageService::WalkStorageEntries(
   2061    CacheStorage const* aStorage, bool aVisitEntries,
   2062    nsICacheStorageVisitor* aVisitor) {
   2063  LOG(("CacheStorageService::WalkStorageEntries [cb=%p, visitentries=%d]",
   2064       aVisitor, aVisitEntries));
   2065  NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
   2066 
   2067  NS_ENSURE_ARG(aStorage);
   2068 
   2069  if (aStorage->WriteToDisk()) {
   2070    RefPtr<CacheStorageServiceInternal::WalkDiskCacheRunnable> event =
   2071        new CacheStorageServiceInternal::WalkDiskCacheRunnable(
   2072            aStorage->LoadInfo(), aVisitEntries, aVisitor);
   2073    return event->Walk();
   2074  }
   2075 
   2076  RefPtr<CacheStorageServiceInternal::WalkMemoryCacheRunnable> event =
   2077      new CacheStorageServiceInternal::WalkMemoryCacheRunnable(
   2078          aStorage->LoadInfo(), aVisitEntries, aVisitor);
   2079  return event->Walk();
   2080 }
   2081 
   2082 void CacheStorageService::CacheFileDoomed(const nsACString& aKey,
   2083                                          nsILoadContextInfo* aLoadContextInfo,
   2084                                          const nsACString& aIdExtension,
   2085                                          const nsACString& aURISpec) {
   2086  nsAutoCString contextKey;
   2087  CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey);
   2088 
   2089  nsAutoCString entryKey;
   2090  CacheEntry::HashingKey(""_ns, aIdExtension, aURISpec, entryKey);
   2091 
   2092  StaticMutexAutoLock lock(sLock);
   2093 
   2094  if (mShutdown) {
   2095    return;
   2096  }
   2097 
   2098  CacheEntryTable* entries;
   2099  RefPtr<CacheEntry> entry;
   2100 
   2101  if (sGlobalEntryTables->Get(contextKey, &entries) &&
   2102      entries->Get(entryKey, getter_AddRefs(entry))) {
   2103    if (entry->IsFileDoomed()) {
   2104      // Need to remove under the lock to avoid possible race leading
   2105      // to duplication of the entry per its key.
   2106      RemoveExactEntry(entries, entryKey, entry, false);
   2107      entry->DoomAlreadyRemoved();
   2108    }
   2109 
   2110    // Entry found, but it's not the entry that has been found doomed
   2111    // by the lower eviction layer.  Just leave everything unchanged.
   2112    return;
   2113  }
   2114 
   2115  RemoveEntryForceValid(contextKey, entryKey);
   2116 }
   2117 
   2118 bool CacheStorageService::GetCacheEntryInfo(
   2119    nsILoadContextInfo* aLoadContextInfo, const nsACString& aIdExtension,
   2120    const nsACString& aURISpec, EntryInfoCallback* aCallback) {
   2121  nsAutoCString contextKey;
   2122  CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey);
   2123 
   2124  nsAutoCString entryKey;
   2125  CacheEntry::HashingKey(""_ns, aIdExtension, aURISpec, entryKey);
   2126 
   2127  RefPtr<CacheEntry> entry;
   2128  {
   2129    StaticMutexAutoLock lock(sLock);
   2130 
   2131    if (mShutdown) {
   2132      return false;
   2133    }
   2134 
   2135    CacheEntryTable* entries;
   2136    if (!sGlobalEntryTables->Get(contextKey, &entries)) {
   2137      return false;
   2138    }
   2139 
   2140    if (!entries->Get(entryKey, getter_AddRefs(entry))) {
   2141      return false;
   2142    }
   2143  }
   2144 
   2145  GetCacheEntryInfo(entry, aCallback);
   2146  return true;
   2147 }
   2148 
   2149 // static
   2150 void CacheStorageService::GetCacheEntryInfo(CacheEntry* aEntry,
   2151                                            EntryInfoCallback* aCallback) {
   2152  nsCString const uriSpec = aEntry->GetURI();
   2153  nsCString const enhanceId = aEntry->GetEnhanceID();
   2154 
   2155  nsAutoCString entryKey;
   2156  aEntry->HashingKeyWithStorage(entryKey);
   2157 
   2158  nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(entryKey);
   2159 
   2160  uint32_t dataSize;
   2161  if (NS_FAILED(aEntry->GetStorageDataSize(&dataSize))) {
   2162    dataSize = 0;
   2163  }
   2164  int64_t altDataSize;
   2165  if (NS_FAILED(aEntry->GetAltDataSize(&altDataSize))) {
   2166    altDataSize = 0;
   2167  }
   2168  uint32_t fetchCount;
   2169  if (NS_FAILED(aEntry->GetFetchCount(&fetchCount))) {
   2170    fetchCount = 0;
   2171  }
   2172  uint32_t lastModified;
   2173  if (NS_FAILED(aEntry->GetLastModified(&lastModified))) {
   2174    lastModified = 0;
   2175  }
   2176  uint32_t expirationTime;
   2177  if (NS_FAILED(aEntry->GetExpirationTime(&expirationTime))) {
   2178    expirationTime = 0;
   2179  }
   2180 
   2181  aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize, altDataSize, fetchCount,
   2182                         lastModified, expirationTime, aEntry->IsPinned(),
   2183                         info);
   2184 }
   2185 
   2186 // static
   2187 uint32_t CacheStorageService::CacheQueueSize(bool highPriority) {
   2188  RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
   2189  // The thread will be null at shutdown.
   2190  if (!thread) {
   2191    return 0;
   2192  }
   2193  return thread->QueueSize(highPriority);
   2194 }
   2195 
   2196 // Telemetry collection
   2197 
   2198 namespace {
   2199 
   2200 bool TelemetryEntryKey(CacheEntry const* entry, nsAutoCString& key) {
   2201  nsAutoCString entryKey;
   2202  nsresult rv = entry->HashingKey(entryKey);
   2203  if (NS_FAILED(rv)) return false;
   2204 
   2205  if (entry->GetStorageID().IsEmpty()) {
   2206    // Hopefully this will be const-copied, saves some memory
   2207    key = entryKey;
   2208  } else {
   2209    key.Assign(entry->GetStorageID());
   2210    key.Append(':');
   2211    key.Append(entryKey);
   2212  }
   2213 
   2214  return true;
   2215 }
   2216 
   2217 }  // namespace
   2218 
   2219 void CacheStorageService::TelemetryPrune(TimeStamp& now) {
   2220  static TimeDuration const oneMinute = TimeDuration::FromSeconds(60);
   2221  static TimeStamp dontPruneUntil = now + oneMinute;
   2222  if (now < dontPruneUntil) return;
   2223 
   2224  static TimeDuration const fifteenMinutes = TimeDuration::FromSeconds(900);
   2225  for (auto iter = mPurgeTimeStamps.Iter(); !iter.Done(); iter.Next()) {
   2226    if (now - iter.Data() > fifteenMinutes) {
   2227      // We are not interested in resurrection of entries after 15 minutes
   2228      // of time.  This is also the limit for the telemetry.
   2229      iter.Remove();
   2230    }
   2231  }
   2232  dontPruneUntil = now + oneMinute;
   2233 }
   2234 
   2235 void CacheStorageService::TelemetryRecordEntryCreation(
   2236    CacheEntry const* entry) {
   2237  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
   2238 
   2239  nsAutoCString key;
   2240  if (!TelemetryEntryKey(entry, key)) return;
   2241 
   2242  TimeStamp now = TimeStamp::NowLoRes();
   2243  TelemetryPrune(now);
   2244 
   2245  // When an entry is craeted (registered actually) we check if there is
   2246  // a timestamp marked when this very same cache entry has been removed
   2247  // (deregistered) because of over-memory-limit purging.  If there is such
   2248  // a timestamp found accumulate telemetry on how long the entry was away.
   2249  TimeStamp timeStamp;
   2250  if (!mPurgeTimeStamps.Get(key, &timeStamp)) return;
   2251 
   2252  mPurgeTimeStamps.Remove(key);
   2253 
   2254  glean::network::http_cache_entry_reload_time.AccumulateRawDuration(
   2255      TimeStamp::NowLoRes() - timeStamp);
   2256 }
   2257 
   2258 void CacheStorageService::TelemetryRecordEntryRemoval(CacheEntry* entry) {
   2259  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
   2260 
   2261  // Doomed entries must not be considered, we are only interested in purged
   2262  // entries.  Note that the mIsDoomed flag is always set before deregistration
   2263  // happens.
   2264  if (entry->IsDoomed()) return;
   2265 
   2266  nsAutoCString key;
   2267  if (!TelemetryEntryKey(entry, key)) return;
   2268 
   2269  // When an entry is removed (deregistered actually) we put a timestamp for
   2270  // this entry to the hashtable so that when the entry is created (registered)
   2271  // again we know how long it was away.  Also accumulate number of AsyncOpen
   2272  // calls on the entry, this tells us how efficiently the pool actually works.
   2273 
   2274  TimeStamp now = TimeStamp::NowLoRes();
   2275  TelemetryPrune(now);
   2276  mPurgeTimeStamps.InsertOrUpdate(key, now);
   2277 
   2278  glean::network::http_cache_entry_reuse_count.AccumulateSingleSample(
   2279      entry->UseCount());
   2280  if (Telemetry::CanRecordPrereleaseData()) {
   2281    glean::network::http_cache_entry_alive_time.AccumulateRawDuration(
   2282        TimeStamp::NowLoRes() - entry->LoadStart());
   2283  }
   2284 }
   2285 
   2286 // nsIMemoryReporter
   2287 
   2288 size_t CacheStorageService::SizeOfExcludingThis(
   2289    mozilla::MallocSizeOf mallocSizeOf) const {
   2290  sLock.AssertCurrentThreadOwns();
   2291 
   2292  size_t n = 0;
   2293  // The elemets are referenced by sGlobalEntryTables and are reported from
   2294  // there.
   2295 
   2296  // Entries reported manually in CacheStorageService::CollectReports callback
   2297  if (sGlobalEntryTables) {
   2298    n += sGlobalEntryTables->ShallowSizeOfIncludingThis(mallocSizeOf);
   2299  }
   2300  n += mPurgeTimeStamps.SizeOfExcludingThis(mallocSizeOf);
   2301 
   2302  return n;
   2303 }
   2304 
   2305 size_t CacheStorageService::SizeOfIncludingThis(
   2306    mozilla::MallocSizeOf mallocSizeOf) const {
   2307  return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
   2308 }
   2309 
   2310 NS_IMETHODIMP
   2311 CacheStorageService::CollectReports(nsIHandleReportCallback* aHandleReport,
   2312                                    nsISupports* aData, bool aAnonymize) {
   2313  StaticMutexAutoLock lock(sLock);
   2314  MOZ_COLLECT_REPORT("explicit/network/cache2/io", KIND_HEAP, UNITS_BYTES,
   2315                     CacheFileIOManager::SizeOfIncludingThis(MallocSizeOf),
   2316                     "Memory used by the cache IO manager.");
   2317 
   2318  MOZ_COLLECT_REPORT("explicit/network/cache2/index", KIND_HEAP, UNITS_BYTES,
   2319                     CacheIndex::SizeOfIncludingThis(MallocSizeOf),
   2320                     "Memory used by the cache index.");
   2321 
   2322  // Report the service instance, this doesn't report entries, done lower
   2323  MOZ_COLLECT_REPORT("explicit/network/cache2/service", KIND_HEAP, UNITS_BYTES,
   2324                     SizeOfIncludingThis(MallocSizeOf),
   2325                     "Memory used by the cache storage service.");
   2326 
   2327  // Report all entries, each storage separately (by the context key)
   2328  //
   2329  // References are:
   2330  // sGlobalEntryTables to N CacheEntryTable
   2331  // CacheEntryTable to N CacheEntry
   2332  // CacheEntry to 1 CacheFile
   2333  // CacheFile to
   2334  //   N CacheFileChunk (keeping the actual data)
   2335  //   1 CacheFileMetadata (keeping http headers etc.)
   2336  //   1 CacheFileOutputStream
   2337  //   N CacheFileInputStream
   2338  if (sGlobalEntryTables) {
   2339    for (const auto& globalEntry : *sGlobalEntryTables) {
   2340      CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
   2341 
   2342      CacheEntryTable* table = globalEntry.GetWeak();
   2343 
   2344      size_t size = 0;
   2345      mozilla::MallocSizeOf mallocSizeOf = CacheStorageService::MallocSizeOf;
   2346 
   2347      size += table->ShallowSizeOfIncludingThis(mallocSizeOf);
   2348      for (const auto& tableEntry : *table) {
   2349        size += tableEntry.GetKey().SizeOfExcludingThisIfUnshared(mallocSizeOf);
   2350 
   2351        // Bypass memory-only entries, those will be reported when iterating the
   2352        // memory only table. Memory-only entries are stored in both ALL_ENTRIES
   2353        // and MEMORY_ONLY hashtables.
   2354        RefPtr<mozilla::net::CacheEntry> const& entry = tableEntry.GetData();
   2355        if (table->Type() == CacheEntryTable::MEMORY_ONLY ||
   2356            entry->IsUsingDisk()) {
   2357          size += entry->SizeOfIncludingThis(mallocSizeOf);
   2358        }
   2359      }
   2360 
   2361      aHandleReport->Callback(
   2362          ""_ns,
   2363          nsPrintfCString(
   2364              "explicit/network/cache2/%s-storage(%s)",
   2365              table->Type() == CacheEntryTable::MEMORY_ONLY ? "memory" : "disk",
   2366              aAnonymize ? "<anonymized>"
   2367                         : globalEntry.GetKey().BeginReading()),
   2368          nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, size,
   2369          "Memory used by the cache storage."_ns, aData);
   2370    }
   2371  }
   2372 
   2373  return NS_OK;
   2374 }
   2375 
   2376 // nsICacheTesting
   2377 
   2378 NS_IMETHODIMP
   2379 CacheStorageService::IOThreadSuspender::Run() {
   2380  MonitorAutoLock mon(mMon);
   2381  while (!mSignaled) {
   2382    mon.Wait();
   2383  }
   2384  return NS_OK;
   2385 }
   2386 
   2387 void CacheStorageService::IOThreadSuspender::Notify() {
   2388  MonitorAutoLock mon(mMon);
   2389  mSignaled = true;
   2390  mon.Notify();
   2391 }
   2392 
   2393 NS_IMETHODIMP
   2394 CacheStorageService::SuspendCacheIOThread(uint32_t aLevel) {
   2395  RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
   2396  if (!thread) {
   2397    return NS_ERROR_NOT_AVAILABLE;
   2398  }
   2399 
   2400  MOZ_ASSERT(!mActiveIOSuspender);
   2401  mActiveIOSuspender = new IOThreadSuspender();
   2402  return thread->Dispatch(mActiveIOSuspender, aLevel);
   2403 }
   2404 
   2405 NS_IMETHODIMP
   2406 CacheStorageService::ResumeCacheIOThread() {
   2407  MOZ_ASSERT(mActiveIOSuspender);
   2408 
   2409  RefPtr<IOThreadSuspender> suspender;
   2410  suspender.swap(mActiveIOSuspender);
   2411  suspender->Notify();
   2412  return NS_OK;
   2413 }
   2414 
   2415 NS_IMETHODIMP
   2416 CacheStorageService::Flush(nsIObserver* aObserver) {
   2417  RefPtr<CacheIOThread> thread = CacheFileIOManager::IOThread();
   2418  if (!thread) {
   2419    return NS_ERROR_NOT_AVAILABLE;
   2420  }
   2421 
   2422  nsCOMPtr<nsIObserverService> observerService =
   2423      mozilla::services::GetObserverService();
   2424  if (!observerService) {
   2425    return NS_ERROR_NOT_AVAILABLE;
   2426  }
   2427 
   2428  // Adding as weak, the consumer is responsible to keep the reference
   2429  // until notified.
   2430  observerService->AddObserver(aObserver, "cacheservice:purge-memory-pools",
   2431                               false);
   2432 
   2433  // This runnable will do the purging and when done, notifies the above
   2434  // observer. We dispatch it to the CLOSE level, so all data writes scheduled
   2435  // up to this time will be done before this purging happens.
   2436  RefPtr<CacheStorageService::PurgeFromMemoryRunnable> r =
   2437      new CacheStorageService::PurgeFromMemoryRunnable(this,
   2438                                                       CacheEntry::PURGE_WHOLE);
   2439 
   2440  return thread->Dispatch(r, CacheIOThread::WRITE);
   2441 }
   2442 
   2443 NS_IMETHODIMP
   2444 CacheStorageService::ClearDictionaryCacheMemory() {
   2445  LOG(("CacheStorageService::ClearDictionaryCacheMemory"));
   2446  RefPtr<DictionaryCache> cache = DictionaryCache::GetInstance();
   2447  if (cache) {
   2448    cache->Clear();
   2449  }
   2450  return NS_OK;
   2451 }
   2452 
   2453 NS_IMETHODIMP
   2454 CacheStorageService::CorruptDictionaryHash(const nsACString& aURI) {
   2455  LOG(("CacheStorageService::CorruptDictionaryHash [uri=%s]",
   2456       PromiseFlatCString(aURI).get()));
   2457  RefPtr<DictionaryCache> cache = DictionaryCache::GetInstance();
   2458  if (cache) {
   2459    cache->CorruptHashForTesting(aURI);
   2460  }
   2461  return NS_OK;
   2462 }
   2463 
   2464 NS_IMETHODIMP
   2465 CacheStorageService::ClearDictionaryDataForTesting(const nsACString& aURI) {
   2466  LOG(("CacheStorageService::ClearDictionaryDataForTesting [uri=%s]",
   2467       PromiseFlatCString(aURI).get()));
   2468  RefPtr<DictionaryCache> cache = DictionaryCache::GetInstance();
   2469  if (cache) {
   2470    cache->ClearDictionaryDataForTesting(aURI);
   2471  }
   2472  return NS_OK;
   2473 }
   2474 
   2475 }  // namespace mozilla::net