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 "SharedStyleSheetCache.h"
8 
9 #include "mozilla/MemoryReporting.h"
10 #include "mozilla/StoragePrincipalHelper.h"
11 #include "mozilla/StyleSheet.h"
12 #include "mozilla/css/SheetLoadData.h"
13 #include "mozilla/dom/ContentParent.h"
14 #include "mozilla/dom/Document.h"
15 #include "mozilla/ServoBindings.h"
16 #include "nsContentUtils.h"
17 #include "nsXULPrototypeCache.h"
18 
19 extern mozilla::LazyLogModule sCssLoaderLog;
20 
21 #define LOG(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, args)
22 
23 namespace mozilla {
24 
25 using css::SheetLoadData;
26 using SheetState = css::Loader::SheetState;
27 using LoadDataArray = css::Loader::LoadDataArray;
28 using IsAlternate = css::Loader::IsAlternate;
29 
30 SharedStyleSheetCache* SharedStyleSheetCache::sInstance;
31 
Clear(nsIPrincipal * aForPrincipal,const nsACString * aBaseDomain)32 void SharedStyleSheetCache::Clear(nsIPrincipal* aForPrincipal,
33                                   const nsACString* aBaseDomain) {
34   using ContentParent = dom::ContentParent;
35 
36   if (XRE_IsParentProcess()) {
37     auto forPrincipal = aForPrincipal ? Some(RefPtr(aForPrincipal)) : Nothing();
38     auto baseDomain = aBaseDomain ? Some(nsCString(*aBaseDomain)) : Nothing();
39 
40     for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
41       Unused << cp->SendClearStyleSheetCache(forPrincipal, baseDomain);
42     }
43   }
44 
45   if (!sInstance) {
46     return;
47   }
48 
49   // No filter, clear all.
50   if (!aForPrincipal && !aBaseDomain) {
51     sInstance->mCompleteSheets.Clear();
52     return;
53   }
54 
55   for (auto iter = sInstance->mCompleteSheets.Iter(); !iter.Done();
56        iter.Next()) {
57     const bool shouldRemove = [&] {
58       if (aForPrincipal && iter.Key().Principal()->Equals(aForPrincipal)) {
59         return true;
60       }
61       if (!aBaseDomain) {
62         return false;
63       }
64       // Clear by baseDomain.
65       nsIPrincipal* partitionPrincipal = iter.Key().PartitionPrincipal();
66 
67       // Clear entries with matching base domain. This includes entries
68       // which are partitioned under other top level sites (= have a
69       // partitionKey set).
70       nsAutoCString principalBaseDomain;
71       nsresult rv = partitionPrincipal->GetBaseDomain(principalBaseDomain);
72       if (NS_SUCCEEDED(rv) && principalBaseDomain.Equals(*aBaseDomain)) {
73         return true;
74       }
75 
76       // Clear entries partitioned under aBaseDomain.
77       return StoragePrincipalHelper::PartitionKeyHasBaseDomain(
78           partitionPrincipal->OriginAttributesRef().mPartitionKey,
79           *aBaseDomain);
80     }();
81 
82     if (shouldRemove) {
83       iter.Remove();
84     }
85   }
86 }
87 
Create()88 already_AddRefed<SharedStyleSheetCache> SharedStyleSheetCache::Create() {
89   MOZ_DIAGNOSTIC_ASSERT(!sInstance);
90   RefPtr<SharedStyleSheetCache> cache = new SharedStyleSheetCache();
91   sInstance = cache.get();
92   RegisterWeakMemoryReporter(cache.get());
93   return cache.forget();
94 }
95 
~SharedStyleSheetCache()96 SharedStyleSheetCache::~SharedStyleSheetCache() {
97   MOZ_DIAGNOSTIC_ASSERT(sInstance == this);
98   UnregisterWeakMemoryReporter(this);
99   sInstance = nullptr;
100 }
101 
NS_IMPL_ISUPPORTS(SharedStyleSheetCache,nsIMemoryReporter)102 NS_IMPL_ISUPPORTS(SharedStyleSheetCache, nsIMemoryReporter)
103 
104 MOZ_DEFINE_MALLOC_SIZE_OF(SharedStyleSheetCacheMallocSizeOf)
105 
106 NS_IMETHODIMP
107 SharedStyleSheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
108                                       nsISupports* aData, bool aAnonymize) {
109   MOZ_COLLECT_REPORT("explicit/layout/style-sheet-cache/document-shared",
110                      KIND_HEAP, UNITS_BYTES,
111                      SizeOfIncludingThis(SharedStyleSheetCacheMallocSizeOf),
112                      "Memory used for SharedStyleSheetCache to share style "
113                      "sheets across documents (not to be confused with "
114                      "GlobalStyleSheetCache)");
115   return NS_OK;
116 }
117 
CloneSheet(StyleSheet & aSheet)118 static RefPtr<StyleSheet> CloneSheet(StyleSheet& aSheet) {
119   return aSheet.Clone(nullptr, nullptr, nullptr, nullptr);
120 }
121 
AssertComplete(const StyleSheet & aSheet)122 static void AssertComplete(const StyleSheet& aSheet) {
123   // This sheet came from the XUL cache or SharedStyleSheetCache; it better be a
124   // complete sheet.
125   MOZ_ASSERT(aSheet.IsComplete(),
126              "Sheet thinks it's not complete while we think it is");
127 }
128 
AssertIncompleteSheetMatches(const SheetLoadData & aData,const SheetLoadDataHashKey & aKey)129 static void AssertIncompleteSheetMatches(const SheetLoadData& aData,
130                                          const SheetLoadDataHashKey& aKey) {
131   MOZ_ASSERT(aKey.Principal()->Equals(aData.mTriggeringPrincipal),
132              "Principals should be the same");
133   MOZ_ASSERT(!aData.mSheet->HasForcedUniqueInner(),
134              "CSSOM shouldn't allow access to incomplete sheets");
135 }
136 
Expired() const137 bool SharedStyleSheetCache::CompleteSheet::Expired() const {
138   return mExpirationTime &&
139          mExpirationTime <= nsContentUtils::SecondsFromPRTime(PR_Now());
140 }
141 
Lookup(css::Loader & aLoader,const SheetLoadDataHashKey & aKey,bool aSyncLoad)142 SharedStyleSheetCache::CacheResult SharedStyleSheetCache::Lookup(
143     css::Loader& aLoader, const SheetLoadDataHashKey& aKey, bool aSyncLoad) {
144   nsIURI* uri = aKey.URI();
145   LOG(("SharedStyleSheetCache::Lookup(%s)", uri->GetSpecOrDefault().get()));
146 
147   // Try to find first in the XUL prototype cache.
148   if (dom::IsChromeURI(uri)) {
149     nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
150     if (cache && cache->IsEnabled()) {
151       if (StyleSheet* sheet = cache->GetStyleSheet(uri)) {
152         LOG(("  From XUL cache: %p", sheet));
153         AssertComplete(*sheet);
154 
155         // See below, we always clone on insertion so we can guarantee the
156         // stylesheet is not modified.
157         MOZ_ASSERT(!sheet->HasForcedUniqueInner());
158 
159         // We need to check the parsing mode manually because the XUL cache only
160         // keys off the URI. But we should fix that!
161         if (sheet->ParsingMode() == aKey.ParsingMode()) {
162           aLoader.DidHitCompleteSheetCache(aKey, nullptr);
163           return {CloneSheet(*sheet), SheetState::Complete};
164         }
165 
166         LOG(("    Not cloning due to mismatched parsing mode"));
167       }
168     }
169   }
170 
171   // Now complete sheets.
172   if (auto lookup = mCompleteSheets.Lookup(aKey)) {
173     const CompleteSheet& completeSheet = lookup.Data();
174     // We can assert the stylesheet has not been modified, as we clone it on
175     // insertion.
176     StyleSheet& cachedSheet = *completeSheet.mSheet;
177     LOG(("  From completed: %p, bypass: %d, expired: %d", &cachedSheet,
178          aLoader.ShouldBypassCache(), completeSheet.Expired()));
179 
180     if ((!aLoader.ShouldBypassCache() && !completeSheet.Expired()) ||
181         aLoader.mLoadsPerformed.Contains(aKey)) {
182       LOG(
183           ("    Not expired yet, or previously loaded already in "
184            "that document"));
185 
186       AssertComplete(cachedSheet);
187       MOZ_ASSERT(cachedSheet.ParsingMode() == aKey.ParsingMode());
188       MOZ_ASSERT(!cachedSheet.HasForcedUniqueInner());
189       MOZ_ASSERT(!cachedSheet.HasModifiedRules());
190 
191       RefPtr<StyleSheet> clone = CloneSheet(cachedSheet);
192       MOZ_ASSERT(!clone->HasForcedUniqueInner());
193       MOZ_ASSERT(!clone->HasModifiedRules());
194 
195       aLoader.DidHitCompleteSheetCache(aKey, completeSheet.mUseCounters.get());
196       return {std::move(clone), SheetState::Complete};
197     }
198   }
199 
200   if (aSyncLoad) {
201     return {};
202   }
203 
204   if (SheetLoadData* data = mLoadingDatas.Get(aKey)) {
205     LOG(("  From loading: %p", data->mSheet.get()));
206     AssertIncompleteSheetMatches(*data, aKey);
207     return {CloneSheet(*data->mSheet), SheetState::Loading};
208   }
209 
210   if (SheetLoadData* data = mPendingDatas.GetWeak(aKey)) {
211     LOG(("  From pending: %p", data->mSheet.get()));
212     AssertIncompleteSheetMatches(*data, aKey);
213     return {CloneSheet(*data->mSheet), SheetState::Pending};
214   }
215 
216   return {};
217 }
218 
WillStartPendingLoad(SheetLoadData & aData)219 void SharedStyleSheetCache::WillStartPendingLoad(SheetLoadData& aData) {
220   SheetLoadData* curr = &aData;
221   do {
222     MOZ_DIAGNOSTIC_ASSERT(curr->mLoader->mPendingLoadCount,
223                           "Where did this pending load come from?");
224     --curr->mLoader->mPendingLoadCount;
225   } while ((curr = curr->mNext));
226 }
227 
CoalesceLoad(const SheetLoadDataHashKey & aKey,SheetLoadData & aNewLoad,SheetState aExistingLoadState)228 bool SharedStyleSheetCache::CoalesceLoad(const SheetLoadDataHashKey& aKey,
229                                          SheetLoadData& aNewLoad,
230                                          SheetState aExistingLoadState) {
231   MOZ_ASSERT(SheetLoadDataHashKey(aNewLoad).KeyEquals(aKey));
232   SheetLoadData* existingData = nullptr;
233   if (aExistingLoadState == SheetState::Loading) {
234     existingData = mLoadingDatas.Get(aKey);
235     MOZ_ASSERT(existingData, "CreateSheet lied about the state");
236   } else if (aExistingLoadState == SheetState::Pending) {
237     existingData = mPendingDatas.GetWeak(aKey);
238     MOZ_ASSERT(existingData, "CreateSheet lied about the state");
239   }
240 
241   if (!existingData) {
242     return false;
243   }
244 
245   if (aExistingLoadState == SheetState::Pending && !aNewLoad.ShouldDefer()) {
246     // Kick the load off; someone cares about it right away
247     RefPtr<SheetLoadData> removedData;
248     mPendingDatas.Remove(aKey, getter_AddRefs(removedData));
249     MOZ_ASSERT(removedData == existingData, "Bad loading table");
250 
251     WillStartPendingLoad(*removedData);
252 
253     // We insert to the front instead of the back, to keep the invariant that
254     // the front sheet always is the one that triggers the load.
255     aNewLoad.mNext = std::move(removedData);
256     LOG(("  Forcing load of pending data"));
257     return false;
258   }
259 
260   LOG(("  Glomming on to existing load"));
261   SheetLoadData* data = existingData;
262   while (data->mNext) {
263     data = data->mNext;
264   }
265   data->mNext = &aNewLoad;
266 
267   return true;
268 }
269 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const270 size_t SharedStyleSheetCache::SizeOfIncludingThis(
271     MallocSizeOf aMallocSizeOf) const {
272   size_t n = aMallocSizeOf(this);
273 
274   n += mCompleteSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
275   for (const auto& data : mCompleteSheets.Values()) {
276     n += data.mSheet->SizeOfIncludingThis(aMallocSizeOf);
277     n += aMallocSizeOf(data.mUseCounters.get());
278   }
279 
280   // Measurement of the following members may be added later if DMD finds it is
281   // worthwhile:
282   // - mLoadingDatas: transient, and should be small
283   // - mPendingDatas: transient, and should be small
284   return n;
285 }
286 
DeferSheetLoad(const SheetLoadDataHashKey & aKey,SheetLoadData & aData)287 void SharedStyleSheetCache::DeferSheetLoad(const SheetLoadDataHashKey& aKey,
288                                            SheetLoadData& aData) {
289   MOZ_ASSERT(SheetLoadDataHashKey(aData).KeyEquals(aKey));
290   MOZ_DIAGNOSTIC_ASSERT(!aData.mNext, "Should only defer loads once");
291 
292   aData.mMustNotify = true;
293   mPendingDatas.InsertOrUpdate(aKey, RefPtr{&aData});
294 }
295 
LoadStarted(const SheetLoadDataHashKey & aKey,SheetLoadData & aData)296 void SharedStyleSheetCache::LoadStarted(const SheetLoadDataHashKey& aKey,
297                                         SheetLoadData& aData) {
298   MOZ_ASSERT(aData.mURI, "No load required?");
299   MOZ_ASSERT(!aData.mIsLoading, "Already loading? How?");
300   MOZ_ASSERT(SheetLoadDataHashKey(aData).KeyEquals(aKey));
301   aData.mIsLoading = true;
302   mLoadingDatas.InsertOrUpdate(aKey, &aData);
303 }
304 
LoadCompleted(SharedStyleSheetCache * aCache,SheetLoadData & aData,nsresult aStatus)305 void SharedStyleSheetCache::LoadCompleted(SharedStyleSheetCache* aCache,
306                                           SheetLoadData& aData,
307                                           nsresult aStatus) {
308   // If aStatus is a failure we need to mark this data failed.  We also need to
309   // mark any ancestors of a failing data as failed and any sibling of a
310   // failing data as failed.  Note that SheetComplete is never called on a
311   // SheetLoadData that is the mNext of some other SheetLoadData.
312   nsresult cancelledStatus = aStatus;
313   if (NS_FAILED(aStatus)) {
314     css::Loader::MarkLoadTreeFailed(aData);
315   } else {
316     cancelledStatus = NS_BINDING_ABORTED;
317     SheetLoadData* data = &aData;
318     do {
319       if (data->mIsCancelled) {
320         // We only need to mark loads for this loader as cancelled, so as to not
321         // fire error events in unrelated documents.
322         css::Loader::MarkLoadTreeFailed(*data, data->mLoader);
323       }
324     } while ((data = data->mNext));
325   }
326 
327   // 8 is probably big enough for all our common cases.  It's not likely that
328   // imports will nest more than 8 deep, and multiple sheets with the same URI
329   // are rare.
330   AutoTArray<RefPtr<SheetLoadData>, 8> datasToNotify;
331   LoadCompletedInternal(aCache, aData, datasToNotify);
332 
333   // Now it's safe to go ahead and notify observers
334   for (RefPtr<SheetLoadData>& data : datasToNotify) {
335     auto status = data->mIsCancelled ? cancelledStatus : aStatus;
336     data->mLoader->NotifyObservers(*data, status);
337   }
338 }
339 
LoadCompletedInternal(SharedStyleSheetCache * aCache,SheetLoadData & aData,nsTArray<RefPtr<SheetLoadData>> & aDatasToNotify)340 void SharedStyleSheetCache::LoadCompletedInternal(
341     SharedStyleSheetCache* aCache, SheetLoadData& aData,
342     nsTArray<RefPtr<SheetLoadData>>& aDatasToNotify) {
343   if (aData.mIsLoading) {
344     MOZ_ASSERT(aCache);
345     SheetLoadDataHashKey key(aData);
346     Maybe<SheetLoadData*> loadingData = aCache->mLoadingDatas.Extract(key);
347     MOZ_DIAGNOSTIC_ASSERT(loadingData);
348     MOZ_DIAGNOSTIC_ASSERT(loadingData.value() == &aData);
349     Unused << loadingData;
350     aData.mIsLoading = false;
351   }
352 
353   // Go through and deal with the whole linked list.
354   SheetLoadData* data = &aData;
355   do {
356     MOZ_DIAGNOSTIC_ASSERT(!data->mSheetCompleteCalled);
357 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
358     data->mSheetCompleteCalled = true;
359 #endif
360 
361     if (!data->mSheetAlreadyComplete) {
362       // If mSheetAlreadyComplete, then the sheet could well be modified between
363       // when we posted the async call to SheetComplete and now, since the sheet
364       // was page-accessible during that whole time.
365 
366       // HasForcedUniqueInner() is okay if the sheet is constructed, because
367       // constructed sheets are always unique and they may be set to complete
368       // multiple times if their rules are replaced via Replace()
369       MOZ_ASSERT(data->mSheet->IsConstructed() ||
370                      !data->mSheet->HasForcedUniqueInner(),
371                  "should not get a forced unique inner during parsing");
372       // Insert the sheet into the tree now the sheet has loaded, but only if
373       // the sheet is still relevant, and if this is a top-level sheet.
374       const bool needInsertIntoTree = [&] {
375         if (!data->mLoader->GetDocument()) {
376           // Not a document load, nothing to do.
377           return false;
378         }
379         if (data->IsPreload()) {
380           // Preloads are not supposed to be observable.
381           return false;
382         }
383         if (data->mSheet->IsConstructed()) {
384           // Constructable sheets are not in the regular stylesheet tree.
385           return false;
386         }
387         if (data->mIsChildSheet) {
388           // A child sheet, those will get exposed from the parent, no need to
389           // insert them into the tree.
390           return false;
391         }
392         if (data->mOwningNode != data->mSheet->GetOwnerNode()) {
393           // The sheet was already removed from the tree and is no longer the
394           // current sheet of the owning node, we can bail.
395           return false;
396         }
397         return true;
398       }();
399 
400       if (needInsertIntoTree) {
401         data->mLoader->InsertSheetInTree(*data->mSheet, data->mOwningNode);
402       }
403       data->mSheet->SetComplete();
404       data->ScheduleLoadEventIfNeeded();
405     } else if (data->mSheet->IsApplicable()) {
406       if (dom::Document* doc = data->mLoader->GetDocument()) {
407         // We post these events for devtools, even though the applicable state
408         // has not actually changed, to make the cache not observable.
409         doc->PostStyleSheetApplicableStateChangeEvent(*data->mSheet);
410       }
411     }
412 
413     aDatasToNotify.AppendElement(data);
414 
415     NS_ASSERTION(!data->mParentData || data->mParentData->mPendingChildren != 0,
416                  "Broken pending child count on our parent");
417 
418     // If we have a parent, our parent is no longer being parsed, and
419     // we are the last pending child, then our load completion
420     // completes the parent too.  Note that the parent _can_ still be
421     // being parsed (eg if the child (us) failed to open the channel
422     // or some such).
423     if (data->mParentData && --(data->mParentData->mPendingChildren) == 0 &&
424         !data->mParentData->mIsBeingParsed) {
425       LoadCompletedInternal(aCache, *data->mParentData, aDatasToNotify);
426     }
427 
428     data = data->mNext;
429   } while (data);
430 
431   if (aCache) {
432     aCache->InsertIntoCompleteCacheIfNeeded(aData);
433   }
434 }
435 
InsertIntoCompleteCacheIfNeeded(SheetLoadData & aData)436 void SharedStyleSheetCache::InsertIntoCompleteCacheIfNeeded(
437     SheetLoadData& aData) {
438   MOZ_ASSERT(aData.mLoader->GetDocument(),
439              "We only cache document-associated sheets");
440   LOG(("SharedStyleSheetCache::InsertIntoCompleteCacheIfNeeded"));
441   // If we ever start doing this for failed loads, we'll need to adjust the
442   // PostLoadEvent code that thinks anything already complete must have loaded
443   // succesfully.
444   if (aData.mLoadFailed) {
445     LOG(("  Load failed, bailing"));
446     return;
447   }
448 
449   // If this sheet came from the cache already, there's no need to override
450   // anything.
451   if (aData.mSheetAlreadyComplete) {
452     LOG(("  Sheet came from the cache, bailing"));
453     return;
454   }
455 
456   if (!aData.mURI) {
457     LOG(("  Inline or constructable style sheet, bailing"));
458     // Inline sheet caching happens in Loader::mInlineSheets.
459     // Constructable sheets are not worth caching, they're always unique.
460     return;
461   }
462 
463   // We need to clone the sheet on insertion to the cache because otherwise the
464   // stylesheets can keep alive full windows alive via either their JS wrapper,
465   // or via StyleSheet::mRelevantGlobal.
466   //
467   // If this ever changes, then you also need to fix up the memory reporting in
468   // both SizeOfIncludingThis and nsXULPrototypeCache::CollectMemoryReports.
469   RefPtr<StyleSheet> sheet = CloneSheet(*aData.mSheet);
470 
471   if (dom::IsChromeURI(aData.mURI)) {
472     nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
473     if (cache && cache->IsEnabled()) {
474       if (!cache->GetStyleSheet(aData.mURI)) {
475         LOG(("  Putting sheet in XUL prototype cache"));
476         NS_ASSERTION(sheet->IsComplete(),
477                      "Should only be caching complete sheets");
478 
479         // NOTE: If we stop cloning sheets before insertion, we need to change
480         // nsXULPrototypeCache::CollectMemoryReports() to stop using
481         // SizeOfIncludingThis() because it will no longer own the sheets.
482         cache->PutStyleSheet(std::move(sheet));
483       }
484     }
485   } else {
486     LOG(("  Putting style sheet in shared cache: %s",
487          aData.mURI->GetSpecOrDefault().get()));
488     SheetLoadDataHashKey key(aData);
489     MOZ_ASSERT(sheet->IsComplete(), "Should only be caching complete sheets");
490 
491 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
492     for (const auto& entry : mCompleteSheets) {
493       if (!key.KeyEquals(entry.GetKey())) {
494         MOZ_DIAGNOSTIC_ASSERT(entry.GetData().mSheet != sheet,
495                               "Same sheet, different keys?");
496       } else {
497         MOZ_ASSERT(
498             entry.GetData().Expired() || aData.mLoader->ShouldBypassCache(),
499             "Overriding existing complete entry?");
500       }
501     }
502 #endif
503 
504     UniquePtr<StyleUseCounters> counters;
505     if (aData.mUseCounters) {
506       // TODO(emilio): Servo_UseCounters_Clone() or something?
507       counters = Servo_UseCounters_Create().Consume();
508       Servo_UseCounters_Merge(counters.get(), aData.mUseCounters.get());
509     }
510 
511     mCompleteSheets.InsertOrUpdate(
512         key, CompleteSheet{aData.mExpirationTime, std::move(counters),
513                            std::move(sheet)});
514   }
515 }
516 
StartDeferredLoadsForLoader(css::Loader & aLoader,StartLoads aStartLoads)517 void SharedStyleSheetCache::StartDeferredLoadsForLoader(
518     css::Loader& aLoader, StartLoads aStartLoads) {
519   using PendingLoad = css::Loader::PendingLoad;
520 
521   LoadDataArray arr;
522   for (auto iter = mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
523     bool startIt = false;
524     SheetLoadData* data = iter.Data();
525     do {
526       if (data->mLoader == &aLoader) {
527         // Note that we don't want to affect what the selected style set is, so
528         // use true for aHasAlternateRel.
529         if (aStartLoads != StartLoads::IfNonAlternate ||
530             aLoader.IsAlternateSheet(iter.Data()->mTitle, true) !=
531                 IsAlternate::Yes) {
532           startIt = true;
533           break;
534         }
535       }
536     } while ((data = data->mNext));
537     if (startIt) {
538       arr.AppendElement(std::move(iter.Data()));
539       iter.Remove();
540     }
541   }
542   for (auto& data : arr) {
543     WillStartPendingLoad(*data);
544     data->mLoader->LoadSheet(*data, SheetState::NeedsParser, PendingLoad::Yes);
545   }
546 }
547 
CancelDeferredLoadsForLoader(css::Loader & aLoader)548 void SharedStyleSheetCache::CancelDeferredLoadsForLoader(css::Loader& aLoader) {
549   LoadDataArray arr;
550 
551   for (auto iter = mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
552     RefPtr<SheetLoadData>& first = iter.Data();
553     SheetLoadData* prev = nullptr;
554     SheetLoadData* current = iter.Data();
555     do {
556       if (current->mLoader != &aLoader) {
557         prev = current;
558         current = current->mNext;
559         continue;
560       }
561       // Detach the load from the list, mark it as cancelled, and then below
562       // call SheetComplete on it.
563       RefPtr<SheetLoadData> strong =
564           prev ? std::move(prev->mNext) : std::move(first);
565       MOZ_ASSERT(strong == current);
566       if (prev) {
567         prev->mNext = std::move(strong->mNext);
568         current = prev->mNext;
569       } else {
570         first = std::move(strong->mNext);
571         current = first;
572       }
573       strong->mIsCancelled = true;
574       arr.AppendElement(std::move(strong));
575     } while (current);
576 
577     if (!first) {
578       iter.Remove();
579     }
580   }
581 
582   for (auto& data : arr) {
583     aLoader.SheetComplete(*data, NS_BINDING_ABORTED);
584   }
585 }
586 
CancelLoadsForLoader(css::Loader & aLoader)587 void SharedStyleSheetCache::CancelLoadsForLoader(css::Loader& aLoader) {
588   CancelDeferredLoadsForLoader(aLoader);
589 
590   // We can't stop in-progress loads because some other loader may care about
591   // them.
592   for (SheetLoadData* data : mLoadingDatas.Values()) {
593     MOZ_DIAGNOSTIC_ASSERT(data,
594                           "We weren't properly notified and the load was "
595                           "incorrectly dropped on the floor");
596     for (; data; data = data->mNext) {
597       if (data->mLoader == &aLoader) {
598         data->mIsCancelled = true;
599       }
600     }
601   }
602 }
603 
RegisterLoader(css::Loader & aLoader)604 void SharedStyleSheetCache::RegisterLoader(css::Loader& aLoader) {
605   MOZ_ASSERT(aLoader.GetDocument());
606   mLoaderPrincipalRefCnt.LookupOrInsert(aLoader.GetDocument()->NodePrincipal(),
607                                         0) += 1;
608 }
609 
UnregisterLoader(css::Loader & aLoader)610 void SharedStyleSheetCache::UnregisterLoader(css::Loader& aLoader) {
611   MOZ_ASSERT(aLoader.GetDocument());
612   nsIPrincipal* prin = aLoader.GetDocument()->NodePrincipal();
613   auto lookup = mLoaderPrincipalRefCnt.Lookup(prin);
614   MOZ_RELEASE_ASSERT(lookup);
615   MOZ_RELEASE_ASSERT(lookup.Data());
616   if (!--lookup.Data()) {
617     lookup.Remove();
618     // TODO(emilio): Do this off a timer or something maybe.
619     for (auto iter = mCompleteSheets.Iter(); !iter.Done(); iter.Next()) {
620       if (iter.Key().LoaderPrincipal()->Equals(prin)) {
621         iter.Remove();
622       }
623     }
624   }
625 }
626 
627 }  // namespace mozilla
628 
629 #undef LOG
630