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/. */
7 #ifndef mozilla_SharedSubResourceCache_h__
8 #define mozilla_SharedSubResourceCache_h__
10 // A cache that allows us to share subresources across documents. In order to
11 // use it you need to provide some types, mainly:
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.
17 // * Key (self explanatory). We might want to introduce a common key to
18 // share the cache partitioning logic.
20 // * Value, which represents the final cached value. This is expected to
21 // be a StyleSheet / Stencil / imgRequestProxy.
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
29 #include "mozilla/PrincipalHashKey.h"
30 #include "mozilla/WeakPtr.h"
31 #include "nsTHashMap.h"
32 #include "nsIMemoryReporter.h"
33 #include "nsRefPtrHashtable.h"
34 #include "mozilla/MemoryReporting.h"
35 #include "mozilla/StoragePrincipalHelper.h"
36 #include "mozilla/dom/Document.h"
37 #include "nsContentUtils.h"
41 enum class CachedSubResourceState
{
48 template <typename Derived
>
49 struct SharedSubResourceCacheLoadingValueBase
{
50 // Whether we're in the "loading" hash table.
51 RefPtr
<Derived
> mNext
;
53 virtual bool IsLoading() const = 0;
54 virtual bool IsCancelled() const = 0;
55 virtual bool IsSyncLoad() const = 0;
57 virtual void StartLoading() = 0;
58 virtual void SetLoadCompleted() = 0;
59 virtual void Cancel() = 0;
61 ~SharedSubResourceCacheLoadingValueBase() {
62 // Do this iteratively to avoid blowing up the stack.
63 RefPtr
<Derived
> next
= std::move(mNext
);
65 next
= std::move(next
->mNext
);
70 template <typename Traits
, typename Derived
>
71 class SharedSubResourceCache
{
73 using Loader
= typename
Traits::Loader
;
74 using Key
= typename
Traits::Key
;
75 using Value
= typename
Traits::Value
;
76 using LoadingValue
= typename
Traits::LoadingValue
;
77 static Key
KeyFromLoadingValue(const LoadingValue
& aValue
) {
78 return Traits::KeyFromLoadingValue(aValue
);
81 const Derived
& AsDerived() const {
82 return *static_cast<const Derived
*>(this);
84 Derived
& AsDerived() { return *static_cast<Derived
*>(this); }
87 SharedSubResourceCache(const SharedSubResourceCache
&) = delete;
88 SharedSubResourceCache(SharedSubResourceCache
&&) = delete;
89 SharedSubResourceCache() = default;
91 static already_AddRefed
<Derived
> Get() {
93 std::is_base_of_v
<SharedSubResourceCacheLoadingValueBase
<LoadingValue
>,
97 return do_AddRef(sInstance
);
99 MOZ_DIAGNOSTIC_ASSERT(!sInstance
);
100 RefPtr
<Derived
> cache
= new Derived();
102 sInstance
= cache
.get();
103 return cache
.forget();
108 Value
* mCompleteValue
= nullptr;
109 LoadingValue
* mLoadingOrPendingValue
= nullptr;
110 CachedSubResourceState mState
= CachedSubResourceState::Miss
;
113 Result
Lookup(Loader
&, const Key
&, bool aSyncLoad
);
115 // Tries to coalesce with an already existing load. The sheet state must be
116 // the one that Lookup returned, if it returned a sheet.
118 // TODO(emilio): Maybe try to merge this with the lookup? Most consumers could
119 // have a data there already.
120 [[nodiscard
]] bool CoalesceLoad(const Key
&, LoadingValue
& aNewLoad
,
121 CachedSubResourceState aExistingLoadState
);
123 size_t SizeOfIncludingThis(MallocSizeOf
) const;
125 // Puts the load into the "loading" set.
126 void LoadStarted(const Key
&, LoadingValue
&);
128 // Removes the load from the "loading" set if there.
129 void LoadCompleted(LoadingValue
&);
131 // Inserts a value into the cache.
132 void Insert(LoadingValue
&);
134 // Puts a load into the "pending" set.
135 void DeferLoad(const Key
&, LoadingValue
&);
137 template <typename Callback
>
138 void StartPendingLoadsForLoader(Loader
&, const Callback
& aShouldStartLoad
);
139 void CancelLoadsForLoader(Loader
&);
141 // Register a loader into the cache. This has the effect of keeping alive all
142 // subresources for the origin of the loader's document until UnregisterLoader
144 void RegisterLoader(Loader
&);
146 // Unregister a loader from the cache.
148 // If this is the loader for the last document of a given origin, then all the
149 // subresources for that document will be removed from the cache. This needs
150 // to be called when the document goes away, or when its principal changes.
151 void UnregisterLoader(Loader
&);
153 void ClearInProcess(nsIPrincipal
* aForPrincipal
= nullptr,
154 const nsACString
* aBaseDomain
= nullptr);
157 void CancelPendingLoadsForLoader(Loader
&);
159 ~SharedSubResourceCache() {
160 MOZ_DIAGNOSTIC_ASSERT(sInstance
== this);
164 struct CompleteSubResource
{
165 RefPtr
<Value
> mResource
;
166 uint32_t mExpirationTime
= 0;
167 bool mWasSyncLoad
= false;
169 inline bool Expired() const;
172 void WillStartPendingLoad(LoadingValue
&);
174 nsTHashMap
<Key
, CompleteSubResource
> mComplete
;
175 nsRefPtrHashtable
<Key
, LoadingValue
> mPending
;
176 // The SheetLoadData pointers in mLoadingDatas below are weak references that
177 // get cleaned up when StreamLoader::OnStopRequest gets called.
179 // Note that we hold on to all sheet loads, even if in the end they happen not
181 nsTHashMap
<Key
, WeakPtr
<LoadingValue
>> mLoading
;
183 // An origin-to-number-of-registered-documents count, in order to manage cache
184 // eviction as described in RegisterLoader / UnregisterLoader.
185 nsTHashMap
<PrincipalHashKey
, uint32_t> mLoaderPrincipalRefCnt
;
188 inline static Derived
* sInstance
;
191 template <typename Traits
, typename Derived
>
192 void SharedSubResourceCache
<Traits
, Derived
>::ClearInProcess(
193 nsIPrincipal
* aForPrincipal
, const nsACString
* aBaseDomain
) {
194 if (!aForPrincipal
&& !aBaseDomain
) {
199 for (auto iter
= mComplete
.Iter(); !iter
.Done(); iter
.Next()) {
200 const bool shouldRemove
= [&] {
201 if (aForPrincipal
&& iter
.Key().Principal()->Equals(aForPrincipal
)) {
207 // Clear by baseDomain.
208 nsIPrincipal
* partitionPrincipal
= iter
.Key().PartitionPrincipal();
210 // Clear entries with matching base domain. This includes entries
211 // which are partitioned under other top level sites (= have a
212 // partitionKey set).
213 nsAutoCString principalBaseDomain
;
214 nsresult rv
= partitionPrincipal
->GetBaseDomain(principalBaseDomain
);
215 if (NS_SUCCEEDED(rv
) && principalBaseDomain
.Equals(*aBaseDomain
)) {
219 // Clear entries partitioned under aBaseDomain.
220 return StoragePrincipalHelper::PartitionKeyHasBaseDomain(
221 partitionPrincipal
->OriginAttributesRef().mPartitionKey
,
231 template <typename Traits
, typename Derived
>
232 void SharedSubResourceCache
<Traits
, Derived
>::RegisterLoader(Loader
& aLoader
) {
233 mLoaderPrincipalRefCnt
.LookupOrInsert(aLoader
.LoaderPrincipal(), 0) += 1;
236 template <typename Traits
, typename Derived
>
237 void SharedSubResourceCache
<Traits
, Derived
>::UnregisterLoader(
239 nsIPrincipal
* prin
= aLoader
.LoaderPrincipal();
240 auto lookup
= mLoaderPrincipalRefCnt
.Lookup(prin
);
241 MOZ_RELEASE_ASSERT(lookup
);
242 MOZ_RELEASE_ASSERT(lookup
.Data());
243 if (!--lookup
.Data()) {
245 // TODO(emilio): Do this off a timer or something maybe.
246 for (auto iter
= mComplete
.Iter(); !iter
.Done(); iter
.Next()) {
247 if (iter
.Key().LoaderPrincipal()->Equals(prin
)) {
254 template <typename Traits
, typename Derived
>
255 void SharedSubResourceCache
<Traits
, Derived
>::CancelPendingLoadsForLoader(
257 AutoTArray
<RefPtr
<LoadingValue
>, 10> arr
;
259 for (auto iter
= mPending
.Iter(); !iter
.Done(); iter
.Next()) {
260 RefPtr
<LoadingValue
>& first
= iter
.Data();
261 LoadingValue
* prev
= nullptr;
262 LoadingValue
* current
= iter
.Data();
264 if (¤t
->Loader() != &aLoader
) {
266 current
= current
->mNext
;
269 // Detach the load from the list, mark it as cancelled, and then below
270 // call SheetComplete on it.
271 RefPtr
<LoadingValue
> strong
=
272 prev
? std::move(prev
->mNext
) : std::move(first
);
273 MOZ_ASSERT(strong
== current
);
275 prev
->mNext
= std::move(strong
->mNext
);
276 current
= prev
->mNext
;
278 first
= std::move(strong
->mNext
);
281 arr
.AppendElement(std::move(strong
));
289 for (auto& loading
: arr
) {
290 loading
->DidCancelLoad();
294 template <typename Traits
, typename Derived
>
295 void SharedSubResourceCache
<Traits
, Derived
>::WillStartPendingLoad(
296 LoadingValue
& aData
) {
297 LoadingValue
* curr
= &aData
;
299 curr
->Loader().WillStartPendingLoad();
300 } while ((curr
= curr
->mNext
));
303 template <typename Traits
, typename Derived
>
304 void SharedSubResourceCache
<Traits
, Derived
>::CancelLoadsForLoader(
306 CancelPendingLoadsForLoader(aLoader
);
308 // We can't stop in-progress loads because some other loader may care about
310 for (LoadingValue
* data
: mLoading
.Values()) {
311 MOZ_DIAGNOSTIC_ASSERT(data
,
312 "We weren't properly notified and the load was "
313 "incorrectly dropped on the floor");
314 for (; data
; data
= data
->mNext
) {
315 if (&data
->Loader() == &aLoader
) {
317 MOZ_ASSERT(data
->IsCancelled());
323 template <typename Traits
, typename Derived
>
324 void SharedSubResourceCache
<Traits
, Derived
>::DeferLoad(const Key
& aKey
,
325 LoadingValue
& aValue
) {
326 MOZ_ASSERT(KeyFromLoadingValue(aValue
).KeyEquals(aKey
));
327 MOZ_DIAGNOSTIC_ASSERT(!aValue
.mNext
, "Should only defer loads once");
329 mPending
.InsertOrUpdate(aKey
, RefPtr
{&aValue
});
332 template <typename Traits
, typename Derived
>
333 template <typename Callback
>
334 void SharedSubResourceCache
<Traits
, Derived
>::StartPendingLoadsForLoader(
335 Loader
& aLoader
, const Callback
& aShouldStartLoad
) {
336 AutoTArray
<RefPtr
<LoadingValue
>, 10> arr
;
338 for (auto iter
= mPending
.Iter(); !iter
.Done(); iter
.Next()) {
339 bool startIt
= false;
341 LoadingValue
* data
= iter
.Data();
343 if (&data
->Loader() == &aLoader
) {
344 if (aShouldStartLoad(*data
)) {
349 } while ((data
= data
->mNext
));
352 arr
.AppendElement(std::move(iter
.Data()));
356 for (auto& data
: arr
) {
357 WillStartPendingLoad(*data
);
358 data
->StartPendingLoad();
362 template <typename Traits
, typename Derived
>
363 void SharedSubResourceCache
<Traits
, Derived
>::Insert(LoadingValue
& aValue
) {
364 auto key
= KeyFromLoadingValue(aValue
);
366 // We only expect a complete entry to be overriding when:
368 // * We're explicitly bypassing the cache.
369 // * Our entry is a sync load that was completed after aValue started loading
371 for (const auto& entry
: mComplete
) {
372 if (key
.KeyEquals(entry
.GetKey())) {
373 MOZ_ASSERT(entry
.GetData().Expired() ||
374 aValue
.Loader().ShouldBypassCache() ||
375 (entry
.GetData().mWasSyncLoad
&& !aValue
.IsSyncLoad()),
376 "Overriding existing complete entry?");
381 // TODO(emilio): Use counters!
382 mComplete
.InsertOrUpdate(
383 key
, CompleteSubResource
{aValue
.ValueForCache(), aValue
.ExpirationTime(),
384 aValue
.IsSyncLoad()});
387 template <typename Traits
, typename Derived
>
388 bool SharedSubResourceCache
<Traits
, Derived
>::CoalesceLoad(
389 const Key
& aKey
, LoadingValue
& aNewLoad
,
390 CachedSubResourceState aExistingLoadState
) {
391 MOZ_ASSERT(KeyFromLoadingValue(aNewLoad
).KeyEquals(aKey
));
392 // TODO(emilio): If aExistingLoadState is inconvenient, we could get rid of it
393 // by paying two hash lookups...
394 LoadingValue
* existingLoad
= nullptr;
395 if (aExistingLoadState
== CachedSubResourceState::Loading
) {
396 existingLoad
= mLoading
.Get(aKey
);
397 MOZ_ASSERT(existingLoad
, "Caller lied about the state");
398 } else if (aExistingLoadState
== CachedSubResourceState::Pending
) {
399 existingLoad
= mPending
.GetWeak(aKey
);
400 MOZ_ASSERT(existingLoad
, "Caller lied about the state");
407 if (aExistingLoadState
== CachedSubResourceState::Pending
&&
408 !aNewLoad
.ShouldDefer()) {
409 // Kick the load off; someone cares about it right away
410 RefPtr
<LoadingValue
> removedLoad
;
411 mPending
.Remove(aKey
, getter_AddRefs(removedLoad
));
412 MOZ_ASSERT(removedLoad
== existingLoad
, "Bad loading table");
414 WillStartPendingLoad(*removedLoad
);
416 // We insert to the front instead of the back, to keep the invariant that
417 // the front sheet always is the one that triggers the load.
418 aNewLoad
.mNext
= std::move(removedLoad
);
422 LoadingValue
* data
= existingLoad
;
423 while (data
->mNext
) {
426 data
->mNext
= &aNewLoad
;
430 template <typename Traits
, typename Derived
>
431 auto SharedSubResourceCache
<Traits
, Derived
>::Lookup(Loader
& aLoader
,
433 bool aSyncLoad
) -> Result
{
434 // Now complete sheets.
435 if (auto lookup
= mComplete
.Lookup(aKey
)) {
436 const CompleteSubResource
& completeSubResource
= lookup
.Data();
437 if ((!aLoader
.ShouldBypassCache() && !completeSubResource
.Expired()) ||
438 aLoader
.HasLoaded(aKey
)) {
439 return {completeSubResource
.mResource
.get(), nullptr,
440 CachedSubResourceState::Complete
};
448 if (LoadingValue
* data
= mLoading
.Get(aKey
)) {
449 return {nullptr, data
, CachedSubResourceState::Loading
};
452 if (LoadingValue
* data
= mPending
.GetWeak(aKey
)) {
453 return {nullptr, data
, CachedSubResourceState::Pending
};
459 template <typename Traits
, typename Derived
>
460 size_t SharedSubResourceCache
<Traits
, Derived
>::SizeOfIncludingThis(
461 MallocSizeOf aMallocSizeOf
) const {
462 size_t n
= aMallocSizeOf(&AsDerived());
464 n
+= mComplete
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
465 for (const auto& data
: mComplete
.Values()) {
466 n
+= data
.mResource
->SizeOfIncludingThis(aMallocSizeOf
);
472 template <typename Traits
, typename Derived
>
473 void SharedSubResourceCache
<Traits
, Derived
>::LoadStarted(
474 const Key
& aKey
, LoadingValue
& aValue
) {
475 MOZ_DIAGNOSTIC_ASSERT(!aValue
.IsLoading(), "Already loading? How?");
476 MOZ_DIAGNOSTIC_ASSERT(KeyFromLoadingValue(aValue
).KeyEquals(aKey
));
477 MOZ_DIAGNOSTIC_ASSERT(!mLoading
.Contains(aKey
), "Load not coalesced?");
478 aValue
.StartLoading();
479 MOZ_ASSERT(aValue
.IsLoading(), "Check that StartLoading is effectful.");
480 mLoading
.InsertOrUpdate(aKey
, &aValue
);
483 template <typename Traits
, typename Derived
>
484 bool SharedSubResourceCache
<Traits
, Derived
>::CompleteSubResource::Expired()
486 return mExpirationTime
&&
487 mExpirationTime
<= nsContentUtils::SecondsFromPRTime(PR_Now());
490 template <typename Traits
, typename Derived
>
491 void SharedSubResourceCache
<Traits
, Derived
>::LoadCompleted(
492 LoadingValue
& aValue
) {
493 if (!aValue
.IsLoading()) {
496 auto key
= KeyFromLoadingValue(aValue
);
497 Maybe
<LoadingValue
*> value
= mLoading
.Extract(key
);
498 MOZ_DIAGNOSTIC_ASSERT(value
);
499 MOZ_DIAGNOSTIC_ASSERT(value
.value() == &aValue
);
501 aValue
.SetLoadCompleted();
502 MOZ_ASSERT(!aValue
.IsLoading(), "Check that SetLoadCompleted is effectful.");
505 } // namespace mozilla