tor-browser

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

SharedSubResourceCache.h (19976B)


      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 #ifndef mozilla_SharedSubResourceCache_h__
      8 #define mozilla_SharedSubResourceCache_h__
      9 
     10 // A cache that allows us to share subresources across documents. In order to
     11 // use it you need to provide some types, mainly:
     12 //
     13 // * Loader, which implements LoaderPrincipal() and allows you to key per
     14 //   principal. The idea is that this would be the
     15 //   {CSS,Script,Image}Loader object.
     16 //
     17 // * Key (self explanatory). We might want to introduce a common key to
     18 //   share the cache partitioning logic.
     19 //
     20 // * Value, which represents the final cached value. This is expected to
     21 //   be a StyleSheet / Stencil / imgRequestProxy.
     22 //
     23 // * LoadingValue, which must inherit from
     24 //   SharedSubResourceCacheLoadingValueBase (which contains the linked
     25 //   list and the state that the cache manages). It also must provide a
     26 //   ValueForCache() and ExpirationTime() members. For style, this is the
     27 //   SheetLoadData.
     28 
     29 #include "mozilla/MemoryReporting.h"
     30 #include "mozilla/PrincipalHashKey.h"
     31 #include "mozilla/RefPtr.h"
     32 #include "mozilla/StaticPtr.h"
     33 #include "mozilla/StoragePrincipalHelper.h"
     34 #include "mozilla/TimeStamp.h"
     35 #include "mozilla/WeakPtr.h"
     36 #include "mozilla/dom/CacheExpirationTime.h"
     37 #include "mozilla/dom/CacheablePerformanceTimingData.h"
     38 #include "mozilla/dom/Document.h"
     39 #include "nsContentUtils.h"
     40 #include "nsIMemoryReporter.h"
     41 #include "nsISupportsImpl.h"
     42 #include "nsRefPtrHashtable.h"
     43 #include "nsTHashMap.h"
     44 
     45 namespace mozilla {
     46 
     47 namespace net {
     48 class nsHttpResponseHead;
     49 }
     50 
     51 // A struct to hold the network-related metadata associated with the cache.
     52 //
     53 // When inserting a cache, the consumer should create this from the request and
     54 // make it available via
     55 // SharedSubResourceCacheLoadingValueBase::GetNetworkMetadata.
     56 //
     57 // When using a cache, the consumer can retrieve this from
     58 // SharedSubResourceCache::Result::mNetworkMetadata and use it for notifying
     59 // the observers once the necessary data becomes ready.
     60 // This struct is ref-counted in order to allow this usage.
     61 class SubResourceNetworkMetadataHolder {
     62 public:
     63  SubResourceNetworkMetadataHolder() = delete;
     64 
     65  explicit SubResourceNetworkMetadataHolder(nsIRequest* aRequest);
     66 
     67  const dom::CacheablePerformanceTimingData* GetPerfData() const {
     68    return mPerfData.ptrOr(nullptr);
     69  }
     70 
     71  const net::nsHttpResponseHead* GetResponseHead() const {
     72    return mResponseHead.get();
     73  }
     74 
     75  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SubResourceNetworkMetadataHolder)
     76 
     77 private:
     78  ~SubResourceNetworkMetadataHolder();
     79 
     80  mozilla::Maybe<dom::CacheablePerformanceTimingData> mPerfData;
     81  mozilla::UniquePtr<net::nsHttpResponseHead> mResponseHead;
     82 };
     83 
     84 enum class CachedSubResourceState {
     85  Miss,
     86  Loading,
     87  Pending,
     88  Complete,
     89 };
     90 
     91 template <typename Derived>
     92 struct SharedSubResourceCacheLoadingValueBase {
     93  // Whether we're in the "loading" hash table.
     94  RefPtr<Derived> mNext;
     95 
     96  virtual bool IsLoading() const = 0;
     97  virtual bool IsCancelled() const = 0;
     98  virtual bool IsSyncLoad() const = 0;
     99 
    100  virtual SubResourceNetworkMetadataHolder* GetNetworkMetadata() const = 0;
    101 
    102  virtual void StartLoading() = 0;
    103  virtual void SetLoadCompleted() = 0;
    104  virtual void OnCoalescedTo(const Derived& aExistingLoad) = 0;
    105  virtual void Cancel() = 0;
    106 
    107  // Return the next sub-resource which has the same key.
    108  Derived* GetNextSubResource() { return mNext; }
    109 
    110  ~SharedSubResourceCacheLoadingValueBase() {
    111    // Do this iteratively to avoid blowing up the stack.
    112    RefPtr<Derived> next = std::move(mNext);
    113    while (next) {
    114      next = std::move(next->mNext);
    115    }
    116  }
    117 };
    118 
    119 namespace SharedSubResourceCacheUtils {
    120 
    121 void AddPerformanceEntryForCache(
    122    const nsString& aEntryName, const nsString& aInitiatorType,
    123    const SubResourceNetworkMetadataHolder* aNetworkMetadata,
    124    TimeStamp aStartTime, TimeStamp aEndTime, dom::Document* aDocument);
    125 
    126 bool ShouldClearEntry(nsIURI* aEntryURI, nsIPrincipal* aEntryLoaderPrincipal,
    127                      nsIPrincipal* aEntryPartitionPrincipal,
    128                      const Maybe<bool>& aChrome,
    129                      const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal,
    130                      const Maybe<nsCString>& aSchemelessSite,
    131                      const Maybe<OriginAttributesPattern>& aPattern,
    132                      const Maybe<nsCString>& aURL);
    133 
    134 }  // namespace SharedSubResourceCacheUtils
    135 
    136 template <typename Traits, typename Derived>
    137 class SharedSubResourceCache {
    138 private:
    139  using Loader = typename Traits::Loader;
    140  using Key = typename Traits::Key;
    141  using Value = typename Traits::Value;
    142  using LoadingValue = typename Traits::LoadingValue;
    143  static Key KeyFromLoadingValue(const LoadingValue& aValue) {
    144    return Traits::KeyFromLoadingValue(aValue);
    145  }
    146 
    147  const Derived& AsDerived() const {
    148    return *static_cast<const Derived*>(this);
    149  }
    150  Derived& AsDerived() { return *static_cast<Derived*>(this); }
    151 
    152 public:
    153  SharedSubResourceCache(const SharedSubResourceCache&) = delete;
    154  SharedSubResourceCache(SharedSubResourceCache&&) = delete;
    155  SharedSubResourceCache() = default;
    156 
    157  static Derived* Get() {
    158    static_assert(
    159        std::is_base_of_v<SharedSubResourceCacheLoadingValueBase<LoadingValue>,
    160                          LoadingValue>);
    161 
    162    if (sSingleton) {
    163      return sSingleton.get();
    164    }
    165    MOZ_DIAGNOSTIC_ASSERT(!sSingleton);
    166    sSingleton = new Derived();
    167    sSingleton->Init();
    168    return sSingleton.get();
    169  }
    170 
    171  static void DeleteSingleton() { sSingleton = nullptr; }
    172 
    173 protected:
    174  struct CompleteSubResource {
    175    RefPtr<Value> mResource;
    176    RefPtr<SubResourceNetworkMetadataHolder> mNetworkMetadata;
    177    CacheExpirationTime mExpirationTime = CacheExpirationTime::Never();
    178    bool mWasSyncLoad = false;
    179 
    180    explicit CompleteSubResource(LoadingValue& aValue)
    181        : mResource(aValue.ValueForCache()),
    182          mNetworkMetadata(aValue.GetNetworkMetadata()),
    183          mExpirationTime(aValue.ExpirationTime()),
    184          mWasSyncLoad(aValue.IsSyncLoad()) {}
    185 
    186    inline bool Expired() const;
    187  };
    188 
    189 public:
    190  struct Result {
    191    Value* mCompleteValue = nullptr;
    192    RefPtr<SubResourceNetworkMetadataHolder> mNetworkMetadata;
    193 
    194    LoadingValue* mLoadingOrPendingValue = nullptr;
    195    CachedSubResourceState mState = CachedSubResourceState::Miss;
    196 
    197    constexpr Result() = default;
    198 
    199    explicit constexpr Result(const CompleteSubResource& aCompleteSubResource)
    200        : mCompleteValue(aCompleteSubResource.mResource.get()),
    201          mNetworkMetadata(aCompleteSubResource.mNetworkMetadata),
    202          mLoadingOrPendingValue(nullptr),
    203          mState(CachedSubResourceState::Complete) {}
    204 
    205    constexpr Result(LoadingValue* aLoadingOrPendingValue,
    206                     CachedSubResourceState aState)
    207        : mLoadingOrPendingValue(aLoadingOrPendingValue), mState(aState) {}
    208  };
    209 
    210  Result Lookup(Loader&, const Key&, bool aSyncLoad);
    211 
    212  // Tries to coalesce with an already existing load. The sheet state must be
    213  // the one that Lookup returned, if it returned a sheet.
    214  //
    215  // TODO(emilio): Maybe try to merge this with the lookup? Most consumers could
    216  // have a data there already.
    217  [[nodiscard]] bool CoalesceLoad(const Key&, LoadingValue& aNewLoad,
    218                                  CachedSubResourceState aExistingLoadState);
    219 
    220  size_t SizeOfExcludingThis(MallocSizeOf) const;
    221 
    222  // Puts the load into the "loading" set.
    223  void LoadStarted(const Key&, LoadingValue&);
    224 
    225  // Removes the load from the "loading" set if there.
    226  void LoadCompleted(LoadingValue&);
    227 
    228  // Inserts a value into the cache.
    229  void Insert(LoadingValue&);
    230 
    231  // Evict the specific cache.
    232  void Evict(const Key&);
    233 
    234  // Puts a load into the "pending" set.
    235  void DeferLoad(const Key&, LoadingValue&);
    236 
    237  template <typename Callback>
    238  void StartPendingLoadsForLoader(Loader&, const Callback& aShouldStartLoad);
    239  void CancelLoadsForLoader(Loader&);
    240 
    241  // Register a loader into the cache. This has the effect of keeping alive all
    242  // subresources for the origin of the loader's document until UnregisterLoader
    243  // is called.
    244  void RegisterLoader(Loader&);
    245 
    246  // Unregister a loader from the cache.
    247  //
    248  // If this is the loader for the last document of a given origin, then all the
    249  // subresources for that document will be removed from the cache. This needs
    250  // to be called when the document goes away, or when its principal changes.
    251  void UnregisterLoader(Loader&);
    252 
    253  void PrepareForShutdown();
    254 
    255  void ClearInProcess(const Maybe<bool>& aChrome,
    256                      const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal,
    257                      const Maybe<nsCString>& aSchemelessSite,
    258                      const Maybe<OriginAttributesPattern>& aPattern,
    259                      const Maybe<nsCString>& aURL);
    260 
    261 protected:
    262  void CancelPendingLoadsForLoader(Loader&);
    263 
    264  void WillStartPendingLoad(LoadingValue&);
    265 
    266  void EvictPrincipal(nsIPrincipal*);
    267 
    268  nsTHashMap<Key, CompleteSubResource> mComplete;
    269  nsRefPtrHashtable<Key, LoadingValue> mPending;
    270  // The SheetLoadData pointers in mLoadingDatas below are weak references that
    271  // get cleaned up when StreamLoader::OnStopRequest gets called.
    272  //
    273  // Note that we hold on to all sheet loads, even if in the end they happen not
    274  // to be cacheable.
    275  nsTHashMap<Key, WeakPtr<LoadingValue>> mLoading;
    276 
    277  // An origin-to-number-of-registered-documents count, in order to manage cache
    278  // eviction as described in RegisterLoader / UnregisterLoader.
    279  nsTHashMap<PrincipalHashKey, uint32_t> mLoaderPrincipalRefCnt;
    280 
    281 protected:
    282  // Lazily created in the first Get() call.
    283  // The singleton should be deleted by DeleteSingleton() during shutdown.
    284  inline static MOZ_GLOBINIT StaticRefPtr<Derived> sSingleton;
    285 };
    286 
    287 template <typename Traits, typename Derived>
    288 void SharedSubResourceCache<Traits, Derived>::ClearInProcess(
    289    const Maybe<bool>& aChrome, const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal,
    290    const Maybe<nsCString>& aSchemelessSite,
    291    const Maybe<OriginAttributesPattern>& aPattern,
    292    const Maybe<nsCString>& aURL) {
    293  MOZ_ASSERT(aSchemelessSite.isSome() == aPattern.isSome(),
    294             "Must pass both site and OA pattern.");
    295 
    296  if (!aChrome && !aPrincipal && !aSchemelessSite && !aURL) {
    297    mComplete.Clear();
    298    return;
    299  }
    300 
    301  for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) {
    302    if (SharedSubResourceCacheUtils::ShouldClearEntry(
    303            iter.Key().URI(), iter.Key().LoaderPrincipal(),
    304            iter.Key().PartitionPrincipal(), aChrome, aPrincipal,
    305            aSchemelessSite, aPattern, aURL)) {
    306      iter.Remove();
    307    }
    308  }
    309 }
    310 
    311 template <typename Traits, typename Derived>
    312 void SharedSubResourceCache<Traits, Derived>::RegisterLoader(Loader& aLoader) {
    313  mLoaderPrincipalRefCnt.LookupOrInsert(aLoader.LoaderPrincipal(), 0) += 1;
    314 }
    315 
    316 template <typename Traits, typename Derived>
    317 void SharedSubResourceCache<Traits, Derived>::UnregisterLoader(
    318    Loader& aLoader) {
    319  nsIPrincipal* prin = aLoader.LoaderPrincipal();
    320  auto lookup = mLoaderPrincipalRefCnt.Lookup(prin);
    321  MOZ_RELEASE_ASSERT(lookup);
    322  MOZ_RELEASE_ASSERT(lookup.Data());
    323  if (!--lookup.Data()) {
    324    lookup.Remove();
    325    // TODO(emilio): Do this off a timer or something maybe, though in practice
    326    // BFCache is good enough at keeping things alive.
    327    AsDerived().EvictPrincipal(prin);
    328  }
    329 }
    330 
    331 template <typename Traits, typename Derived>
    332 void SharedSubResourceCache<Traits, Derived>::EvictPrincipal(
    333    nsIPrincipal* aPrincipal) {
    334  for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) {
    335    if (iter.Key().LoaderPrincipal()->Equals(aPrincipal)) {
    336      iter.Remove();
    337    }
    338  }
    339 }
    340 
    341 template <typename Traits, typename Derived>
    342 void SharedSubResourceCache<Traits, Derived>::CancelPendingLoadsForLoader(
    343    Loader& aLoader) {
    344  AutoTArray<RefPtr<LoadingValue>, 10> arr;
    345 
    346  for (auto iter = mPending.Iter(); !iter.Done(); iter.Next()) {
    347    RefPtr<LoadingValue>& first = iter.Data();
    348    LoadingValue* prev = nullptr;
    349    LoadingValue* current = iter.Data();
    350    do {
    351      if (&current->Loader() != &aLoader) {
    352        prev = current;
    353        current = current->mNext;
    354        continue;
    355      }
    356      // Detach the load from the list, mark it as cancelled, and then below
    357      // call SheetComplete on it.
    358      RefPtr<LoadingValue> strong =
    359          prev ? std::move(prev->mNext) : std::move(first);
    360      MOZ_ASSERT(strong == current);
    361      if (prev) {
    362        prev->mNext = std::move(strong->mNext);
    363        current = prev->mNext;
    364      } else {
    365        first = std::move(strong->mNext);
    366        current = first;
    367      }
    368      arr.AppendElement(std::move(strong));
    369    } while (current);
    370 
    371    if (!first) {
    372      iter.Remove();
    373    }
    374  }
    375 
    376  for (auto& loading : arr) {
    377    loading->DidCancelLoad();
    378  }
    379 }
    380 
    381 template <typename Traits, typename Derived>
    382 void SharedSubResourceCache<Traits, Derived>::WillStartPendingLoad(
    383    LoadingValue& aData) {
    384  LoadingValue* curr = &aData;
    385  do {
    386    curr->Loader().WillStartPendingLoad();
    387  } while ((curr = curr->mNext));
    388 }
    389 
    390 template <typename Traits, typename Derived>
    391 void SharedSubResourceCache<Traits, Derived>::CancelLoadsForLoader(
    392    Loader& aLoader) {
    393  CancelPendingLoadsForLoader(aLoader);
    394 
    395  // We can't stop in-progress loads because some other loader may care about
    396  // them.
    397  for (LoadingValue* data : mLoading.Values()) {
    398    MOZ_DIAGNOSTIC_ASSERT(data,
    399                          "We weren't properly notified and the load was "
    400                          "incorrectly dropped on the floor");
    401    for (; data; data = data->mNext) {
    402      if (&data->Loader() == &aLoader) {
    403        data->Cancel();
    404        MOZ_ASSERT(data->IsCancelled());
    405      }
    406    }
    407  }
    408 }
    409 
    410 template <typename Traits, typename Derived>
    411 void SharedSubResourceCache<Traits, Derived>::DeferLoad(const Key& aKey,
    412                                                        LoadingValue& aValue) {
    413  MOZ_ASSERT(KeyFromLoadingValue(aValue).KeyEquals(aKey));
    414  MOZ_DIAGNOSTIC_ASSERT(!aValue.mNext, "Should only defer loads once");
    415 
    416  mPending.InsertOrUpdate(aKey, RefPtr{&aValue});
    417 }
    418 
    419 template <typename Traits, typename Derived>
    420 template <typename Callback>
    421 void SharedSubResourceCache<Traits, Derived>::StartPendingLoadsForLoader(
    422    Loader& aLoader, const Callback& aShouldStartLoad) {
    423  AutoTArray<RefPtr<LoadingValue>, 10> arr;
    424 
    425  for (auto iter = mPending.Iter(); !iter.Done(); iter.Next()) {
    426    bool startIt = false;
    427    {
    428      LoadingValue* data = iter.Data();
    429      do {
    430        if (&data->Loader() == &aLoader) {
    431          if (aShouldStartLoad(*data)) {
    432            startIt = true;
    433            break;
    434          }
    435        }
    436      } while ((data = data->mNext));
    437    }
    438    if (startIt) {
    439      arr.AppendElement(std::move(iter.Data()));
    440      iter.Remove();
    441    }
    442  }
    443  for (auto& data : arr) {
    444    WillStartPendingLoad(*data);
    445    data->StartPendingLoad();
    446  }
    447 }
    448 
    449 template <typename Traits, typename Derived>
    450 void SharedSubResourceCache<Traits, Derived>::Insert(LoadingValue& aValue) {
    451  auto key = KeyFromLoadingValue(aValue);
    452 #ifdef DEBUG
    453  // We only expect a complete entry to be overriding when:
    454  //  * It's expired.
    455  //  * We're explicitly bypassing the cache.
    456  //  * Our entry is a sync load that was completed after aValue started loading
    457  //    async.
    458  for (const auto& entry : mComplete) {
    459    if (key.KeyEquals(entry.GetKey())) {
    460      MOZ_ASSERT(entry.GetData().Expired() ||
    461                     aValue.Loader().ShouldBypassCache() ||
    462                     (entry.GetData().mWasSyncLoad && !aValue.IsSyncLoad()),
    463                 "Overriding existing complete entry?");
    464    }
    465  }
    466 #endif
    467 
    468  mComplete.InsertOrUpdate(key, CompleteSubResource(aValue));
    469 }
    470 
    471 template <typename Traits, typename Derived>
    472 void SharedSubResourceCache<Traits, Derived>::Evict(const Key& aKey) {
    473  (void)mComplete.Remove(aKey);
    474 }
    475 
    476 template <typename Traits, typename Derived>
    477 bool SharedSubResourceCache<Traits, Derived>::CoalesceLoad(
    478    const Key& aKey, LoadingValue& aNewLoad,
    479    CachedSubResourceState aExistingLoadState) {
    480  MOZ_ASSERT(KeyFromLoadingValue(aNewLoad).KeyEquals(aKey));
    481  // TODO(emilio): If aExistingLoadState is inconvenient, we could get rid of it
    482  // by paying two hash lookups...
    483  LoadingValue* existingLoad = nullptr;
    484  if (aExistingLoadState == CachedSubResourceState::Loading) {
    485    existingLoad = mLoading.Get(aKey);
    486    MOZ_ASSERT(existingLoad, "Caller lied about the state");
    487  } else if (aExistingLoadState == CachedSubResourceState::Pending) {
    488    existingLoad = mPending.GetWeak(aKey);
    489    MOZ_ASSERT(existingLoad, "Caller lied about the state");
    490  }
    491 
    492  if (!existingLoad) {
    493    return false;
    494  }
    495 
    496  if (aExistingLoadState == CachedSubResourceState::Pending &&
    497      !aNewLoad.ShouldDefer()) {
    498    // Kick the load off; someone cares about it right away
    499    RefPtr<LoadingValue> removedLoad;
    500    mPending.Remove(aKey, getter_AddRefs(removedLoad));
    501    MOZ_ASSERT(removedLoad == existingLoad, "Bad loading table");
    502 
    503    WillStartPendingLoad(*removedLoad);
    504 
    505    // We insert to the front instead of the back, to keep the invariant that
    506    // the front sheet always is the one that triggers the load.
    507    aNewLoad.mNext = std::move(removedLoad);
    508    return false;
    509  }
    510 
    511  LoadingValue* data = existingLoad;
    512  while (data->mNext) {
    513    data = data->mNext;
    514  }
    515  data->mNext = &aNewLoad;
    516 
    517  aNewLoad.OnCoalescedTo(*existingLoad);
    518  return true;
    519 }
    520 
    521 template <typename Traits, typename Derived>
    522 auto SharedSubResourceCache<Traits, Derived>::Lookup(Loader& aLoader,
    523                                                     const Key& aKey,
    524                                                     bool aSyncLoad) -> Result {
    525  // Now complete sheets.
    526  if (auto lookup = mComplete.Lookup(aKey)) {
    527    const CompleteSubResource& completeSubResource = lookup.Data();
    528    if ((!aLoader.ShouldBypassCache() && !completeSubResource.Expired()) ||
    529        aLoader.HasLoaded(aKey)) {
    530      return Result(completeSubResource);
    531    }
    532  }
    533 
    534  if (aSyncLoad) {
    535    return Result();
    536  }
    537 
    538  if (LoadingValue* data = mLoading.Get(aKey)) {
    539    return Result(data, CachedSubResourceState::Loading);
    540  }
    541 
    542  if (LoadingValue* data = mPending.GetWeak(aKey)) {
    543    return Result(data, CachedSubResourceState::Pending);
    544  }
    545 
    546  return {};
    547 }
    548 
    549 template <typename Traits, typename Derived>
    550 size_t SharedSubResourceCache<Traits, Derived>::SizeOfExcludingThis(
    551    MallocSizeOf aMallocSizeOf) const {
    552  size_t n = mComplete.ShallowSizeOfExcludingThis(aMallocSizeOf);
    553  for (const auto& data : mComplete.Values()) {
    554    n += data.mResource->SizeOfIncludingThis(aMallocSizeOf);
    555  }
    556 
    557  return n;
    558 }
    559 
    560 template <typename Traits, typename Derived>
    561 void SharedSubResourceCache<Traits, Derived>::LoadStarted(
    562    const Key& aKey, LoadingValue& aValue) {
    563  MOZ_DIAGNOSTIC_ASSERT(!aValue.IsLoading(), "Already loading? How?");
    564  MOZ_DIAGNOSTIC_ASSERT(KeyFromLoadingValue(aValue).KeyEquals(aKey));
    565  MOZ_DIAGNOSTIC_ASSERT(!mLoading.Contains(aKey), "Load not coalesced?");
    566  aValue.StartLoading();
    567  MOZ_ASSERT(aValue.IsLoading(), "Check that StartLoading is effectful.");
    568  mLoading.InsertOrUpdate(aKey, &aValue);
    569 }
    570 
    571 template <typename Traits, typename Derived>
    572 bool SharedSubResourceCache<Traits, Derived>::CompleteSubResource::Expired()
    573    const {
    574  return mExpirationTime.IsExpired();
    575 }
    576 
    577 template <typename Traits, typename Derived>
    578 void SharedSubResourceCache<Traits, Derived>::LoadCompleted(
    579    LoadingValue& aValue) {
    580  if (!aValue.IsLoading()) {
    581    return;
    582  }
    583  auto key = KeyFromLoadingValue(aValue);
    584  Maybe<LoadingValue*> value = mLoading.Extract(key);
    585  MOZ_DIAGNOSTIC_ASSERT(value);
    586  MOZ_DIAGNOSTIC_ASSERT(value.value() == &aValue);
    587  (void)value;
    588  aValue.SetLoadCompleted();
    589  MOZ_ASSERT(!aValue.IsLoading(), "Check that SetLoadCompleted is effectful.");
    590 }
    591 
    592 }  // namespace mozilla
    593 
    594 #endif