Bug 1879449 [wpt PR 44489] - [wptrunner] Add `infrastructure/expected-fail/` test...
[gecko.git] / layout / style / SharedSubResourceCache.h
blob32919b8863a75813a2a0331ad28f1e5ee0c8de22
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
27 // SheetLoadData.
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"
39 namespace mozilla {
41 enum class CachedSubResourceState {
42 Miss,
43 Loading,
44 Pending,
45 Complete,
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);
64 while (next) {
65 next = std::move(next->mNext);
70 template <typename Traits, typename Derived>
71 class SharedSubResourceCache {
72 private:
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); }
86 public:
87 SharedSubResourceCache(const SharedSubResourceCache&) = delete;
88 SharedSubResourceCache(SharedSubResourceCache&&) = delete;
89 SharedSubResourceCache() = default;
91 static already_AddRefed<Derived> Get() {
92 static_assert(
93 std::is_base_of_v<SharedSubResourceCacheLoadingValueBase<LoadingValue>,
94 LoadingValue>);
96 if (sInstance) {
97 return do_AddRef(sInstance);
99 MOZ_DIAGNOSTIC_ASSERT(!sInstance);
100 RefPtr<Derived> cache = new Derived();
101 cache->Init();
102 sInstance = cache.get();
103 return cache.forget();
106 public:
107 struct Result {
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
143 // is called.
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);
156 protected:
157 void CancelPendingLoadsForLoader(Loader&);
159 ~SharedSubResourceCache() {
160 MOZ_DIAGNOSTIC_ASSERT(sInstance == this);
161 sInstance = nullptr;
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
180 // to be cacheable.
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;
187 protected:
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) {
195 mComplete.Clear();
196 return;
199 for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) {
200 const bool shouldRemove = [&] {
201 if (aForPrincipal && iter.Key().Principal()->Equals(aForPrincipal)) {
202 return true;
204 if (!aBaseDomain) {
205 return false;
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)) {
216 return true;
219 // Clear entries partitioned under aBaseDomain.
220 return StoragePrincipalHelper::PartitionKeyHasBaseDomain(
221 partitionPrincipal->OriginAttributesRef().mPartitionKey,
222 *aBaseDomain);
223 }();
225 if (shouldRemove) {
226 iter.Remove();
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(
238 Loader& aLoader) {
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()) {
244 lookup.Remove();
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)) {
248 iter.Remove();
254 template <typename Traits, typename Derived>
255 void SharedSubResourceCache<Traits, Derived>::CancelPendingLoadsForLoader(
256 Loader& aLoader) {
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();
263 do {
264 if (&current->Loader() != &aLoader) {
265 prev = current;
266 current = current->mNext;
267 continue;
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);
274 if (prev) {
275 prev->mNext = std::move(strong->mNext);
276 current = prev->mNext;
277 } else {
278 first = std::move(strong->mNext);
279 current = first;
281 arr.AppendElement(std::move(strong));
282 } while (current);
284 if (!first) {
285 iter.Remove();
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;
298 do {
299 curr->Loader().WillStartPendingLoad();
300 } while ((curr = curr->mNext));
303 template <typename Traits, typename Derived>
304 void SharedSubResourceCache<Traits, Derived>::CancelLoadsForLoader(
305 Loader& aLoader) {
306 CancelPendingLoadsForLoader(aLoader);
308 // We can't stop in-progress loads because some other loader may care about
309 // them.
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) {
316 data->Cancel();
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();
342 do {
343 if (&data->Loader() == &aLoader) {
344 if (aShouldStartLoad(*data)) {
345 startIt = true;
346 break;
349 } while ((data = data->mNext));
351 if (startIt) {
352 arr.AppendElement(std::move(iter.Data()));
353 iter.Remove();
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);
365 #ifdef DEBUG
366 // We only expect a complete entry to be overriding when:
367 // * It's expired.
368 // * We're explicitly bypassing the cache.
369 // * Our entry is a sync load that was completed after aValue started loading
370 // async.
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?");
379 #endif
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");
403 if (!existingLoad) {
404 return false;
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);
419 return false;
422 LoadingValue* data = existingLoad;
423 while (data->mNext) {
424 data = data->mNext;
426 data->mNext = &aNewLoad;
427 return true;
430 template <typename Traits, typename Derived>
431 auto SharedSubResourceCache<Traits, Derived>::Lookup(Loader& aLoader,
432 const Key& aKey,
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};
444 if (aSyncLoad) {
445 return {};
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};
456 return {};
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);
469 return n;
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()
485 const {
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()) {
494 return;
496 auto key = KeyFromLoadingValue(aValue);
497 Maybe<LoadingValue*> value = mLoading.Extract(key);
498 MOZ_DIAGNOSTIC_ASSERT(value);
499 MOZ_DIAGNOSTIC_ASSERT(value.value() == &aValue);
500 Unused << value;
501 aValue.SetLoadCompleted();
502 MOZ_ASSERT(!aValue.IsLoading(), "Check that SetLoadCompleted is effectful.");
505 } // namespace mozilla
507 #endif