summaryrefslogtreecommitdiffstats
path: root/layout/style/SharedStyleSheetCache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style/SharedStyleSheetCache.cpp')
-rw-r--r--layout/style/SharedStyleSheetCache.cpp604
1 files changed, 604 insertions, 0 deletions
diff --git a/layout/style/SharedStyleSheetCache.cpp b/layout/style/SharedStyleSheetCache.cpp
new file mode 100644
index 0000000000..39a7420693
--- /dev/null
+++ b/layout/style/SharedStyleSheetCache.cpp
@@ -0,0 +1,604 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SharedStyleSheetCache.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/css/SheetLoadData.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/ServoBindings.h"
+#include "nsContentUtils.h"
+#include "nsXULPrototypeCache.h"
+
+extern mozilla::LazyLogModule sCssLoaderLog;
+
+#define LOG(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, args)
+
+namespace mozilla {
+
+using css::SheetLoadData;
+using SheetState = css::Loader::SheetState;
+using LoadDataArray = css::Loader::LoadDataArray;
+using IsAlternate = css::Loader::IsAlternate;
+
+SharedStyleSheetCache* SharedStyleSheetCache::sInstance;
+
+void SharedStyleSheetCache::Clear(nsIPrincipal* aForPrincipal) {
+ using ContentParent = dom::ContentParent;
+
+ if (XRE_IsParentProcess()) {
+ auto forPrincipal = aForPrincipal ? Some(RefPtr(aForPrincipal)) : Nothing();
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ Unused << cp->SendClearStyleSheetCache(forPrincipal);
+ }
+ }
+
+ if (!sInstance) {
+ return;
+ }
+
+ if (!aForPrincipal) {
+ sInstance->mCompleteSheets.Clear();
+ return;
+ }
+
+ for (auto iter = sInstance->mCompleteSheets.Iter(); !iter.Done();
+ iter.Next()) {
+ if (iter.Key().Principal()->Equals(aForPrincipal)) {
+ iter.Remove();
+ }
+ }
+}
+
+already_AddRefed<SharedStyleSheetCache> SharedStyleSheetCache::Create() {
+ MOZ_DIAGNOSTIC_ASSERT(!sInstance);
+ RefPtr<SharedStyleSheetCache> cache = new SharedStyleSheetCache();
+ sInstance = cache.get();
+ RegisterWeakMemoryReporter(cache.get());
+ return cache.forget();
+}
+
+SharedStyleSheetCache::~SharedStyleSheetCache() {
+ MOZ_DIAGNOSTIC_ASSERT(sInstance == this);
+ UnregisterWeakMemoryReporter(this);
+ sInstance = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(SharedStyleSheetCache, nsIMemoryReporter)
+
+MOZ_DEFINE_MALLOC_SIZE_OF(SharedStyleSheetCacheMallocSizeOf)
+
+NS_IMETHODIMP
+SharedStyleSheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ MOZ_COLLECT_REPORT("explicit/layout/style-sheet-cache/document-shared",
+ KIND_HEAP, UNITS_BYTES,
+ SizeOfIncludingThis(SharedStyleSheetCacheMallocSizeOf),
+ "Memory used for SharedStyleSheetCache to share style "
+ "sheets across documents (not to be confused with "
+ "GlobalStyleSheetCache)");
+ return NS_OK;
+}
+
+static RefPtr<StyleSheet> CloneSheet(StyleSheet& aSheet) {
+ return aSheet.Clone(nullptr, nullptr, nullptr, nullptr);
+}
+
+static void AssertComplete(const StyleSheet& aSheet) {
+ // This sheet came from the XUL cache or SharedStyleSheetCache; it better be a
+ // complete sheet.
+ MOZ_ASSERT(aSheet.IsComplete(),
+ "Sheet thinks it's not complete while we think it is");
+}
+
+static void AssertIncompleteSheetMatches(const SheetLoadData& aData,
+ const SheetLoadDataHashKey& aKey) {
+ MOZ_ASSERT(aKey.Principal()->Equals(aData.mTriggeringPrincipal),
+ "Principals should be the same");
+ MOZ_ASSERT(!aData.mSheet->HasForcedUniqueInner(),
+ "CSSOM shouldn't allow access to incomplete sheets");
+}
+
+bool SharedStyleSheetCache::CompleteSheet::Expired() const {
+ return mExpirationTime &&
+ mExpirationTime <= nsContentUtils::SecondsFromPRTime(PR_Now());
+}
+
+SharedStyleSheetCache::CacheResult SharedStyleSheetCache::Lookup(
+ css::Loader& aLoader, const SheetLoadDataHashKey& aKey, bool aSyncLoad) {
+ nsIURI* uri = aKey.URI();
+ LOG(("SharedStyleSheetCache::Lookup(%s)", uri->GetSpecOrDefault().get()));
+
+ // Try to find first in the XUL prototype cache.
+ if (dom::IsChromeURI(uri)) {
+ nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
+ if (cache && cache->IsEnabled()) {
+ if (StyleSheet* sheet = cache->GetStyleSheet(uri)) {
+ LOG((" From XUL cache: %p", sheet));
+ AssertComplete(*sheet);
+
+ // See below, we always clone on insertion so we can guarantee the
+ // stylesheet is not modified.
+ MOZ_ASSERT(!sheet->HasForcedUniqueInner());
+
+ // We need to check the parsing mode manually because the XUL cache only
+ // keys off the URI. But we should fix that!
+ if (sheet->ParsingMode() == aKey.ParsingMode()) {
+ aLoader.DidHitCompleteSheetCache(aKey, nullptr);
+ return {CloneSheet(*sheet), SheetState::Complete};
+ }
+
+ LOG((" Not cloning due to mismatched parsing mode"));
+ }
+ }
+ }
+
+ // Now complete sheets.
+ if (auto lookup = mCompleteSheets.Lookup(aKey)) {
+ const CompleteSheet& completeSheet = lookup.Data();
+ // We can assert the stylesheet has not been modified, as we clone it on
+ // insertion.
+ StyleSheet& cachedSheet = *completeSheet.mSheet;
+ LOG((" From completed: %p, bypass: %d, expired: %d", &cachedSheet,
+ aLoader.ShouldBypassCache(), completeSheet.Expired()));
+
+ if ((!aLoader.ShouldBypassCache() && !completeSheet.Expired()) ||
+ aLoader.mLoadsPerformed.Contains(aKey)) {
+ LOG(
+ (" Not expired yet, or previously loaded already in "
+ "that document"));
+
+ AssertComplete(cachedSheet);
+ MOZ_ASSERT(cachedSheet.ParsingMode() == aKey.ParsingMode());
+ MOZ_ASSERT(!cachedSheet.HasForcedUniqueInner());
+ MOZ_ASSERT(!cachedSheet.HasModifiedRules());
+
+ RefPtr<StyleSheet> clone = CloneSheet(cachedSheet);
+ MOZ_ASSERT(!clone->HasForcedUniqueInner());
+ MOZ_ASSERT(!clone->HasModifiedRules());
+
+ aLoader.DidHitCompleteSheetCache(aKey, completeSheet.mUseCounters.get());
+ return {std::move(clone), SheetState::Complete};
+ }
+ }
+
+ if (aSyncLoad) {
+ return {};
+ }
+
+ if (SheetLoadData* data = mLoadingDatas.Get(aKey)) {
+ LOG((" From loading: %p", data->mSheet.get()));
+ AssertIncompleteSheetMatches(*data, aKey);
+ return {CloneSheet(*data->mSheet), SheetState::Loading};
+ }
+
+ if (SheetLoadData* data = mPendingDatas.GetWeak(aKey)) {
+ LOG((" From pending: %p", data->mSheet.get()));
+ AssertIncompleteSheetMatches(*data, aKey);
+ return {CloneSheet(*data->mSheet), SheetState::Pending};
+ }
+
+ return {};
+}
+
+void SharedStyleSheetCache::WillStartPendingLoad(SheetLoadData& aData) {
+ SheetLoadData* curr = &aData;
+ do {
+ MOZ_DIAGNOSTIC_ASSERT(curr->mLoader->mPendingLoadCount,
+ "Where did this pending load come from?");
+ --curr->mLoader->mPendingLoadCount;
+ } while ((curr = curr->mNext));
+}
+
+bool SharedStyleSheetCache::CoalesceLoad(const SheetLoadDataHashKey& aKey,
+ SheetLoadData& aNewLoad,
+ SheetState aExistingLoadState) {
+ MOZ_ASSERT(SheetLoadDataHashKey(aNewLoad).KeyEquals(aKey));
+ SheetLoadData* existingData = nullptr;
+ if (aExistingLoadState == SheetState::Loading) {
+ existingData = mLoadingDatas.Get(aKey);
+ MOZ_ASSERT(existingData, "CreateSheet lied about the state");
+ } else if (aExistingLoadState == SheetState::Pending) {
+ existingData = mPendingDatas.GetWeak(aKey);
+ MOZ_ASSERT(existingData, "CreateSheet lied about the state");
+ }
+
+ if (!existingData) {
+ return false;
+ }
+
+ if (aExistingLoadState == SheetState::Pending && !aNewLoad.ShouldDefer()) {
+ // Kick the load off; someone cares about it right away
+ RefPtr<SheetLoadData> removedData;
+ mPendingDatas.Remove(aKey, getter_AddRefs(removedData));
+ MOZ_ASSERT(removedData == existingData, "Bad loading table");
+
+ WillStartPendingLoad(*removedData);
+
+ // We insert to the front instead of the back, to keep the invariant that
+ // the front sheet always is the one that triggers the load.
+ aNewLoad.mNext = std::move(removedData);
+ LOG((" Forcing load of pending data"));
+ return false;
+ }
+
+ LOG((" Glomming on to existing load"));
+ SheetLoadData* data = existingData;
+ while (data->mNext) {
+ data = data->mNext;
+ }
+ data->mNext = &aNewLoad;
+
+ return true;
+}
+
+size_t SharedStyleSheetCache::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+
+ n += mCompleteSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mCompleteSheets.ConstIter(); !iter.Done(); iter.Next()) {
+ n += iter.Data().mSheet->SizeOfIncludingThis(aMallocSizeOf);
+ n += aMallocSizeOf(iter.Data().mUseCounters.get());
+ }
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mLoadingDatas: transient, and should be small
+ // - mPendingDatas: transient, and should be small
+ return n;
+}
+
+void SharedStyleSheetCache::DeferSheetLoad(const SheetLoadDataHashKey& aKey,
+ SheetLoadData& aData) {
+ MOZ_ASSERT(SheetLoadDataHashKey(aData).KeyEquals(aKey));
+ MOZ_DIAGNOSTIC_ASSERT(!aData.mNext, "Should only defer loads once");
+
+ aData.mMustNotify = true;
+ mPendingDatas.Put(aKey, RefPtr{&aData});
+}
+
+void SharedStyleSheetCache::LoadStarted(const SheetLoadDataHashKey& aKey,
+ SheetLoadData& aData) {
+ MOZ_ASSERT(aData.mURI, "No load required?");
+ MOZ_ASSERT(!aData.mIsLoading, "Already loading? How?");
+ MOZ_ASSERT(SheetLoadDataHashKey(aData).KeyEquals(aKey));
+ aData.mIsLoading = true;
+ mLoadingDatas.Put(aKey, &aData);
+}
+
+void SharedStyleSheetCache::LoadCompleted(SharedStyleSheetCache* aCache,
+ SheetLoadData& aData,
+ nsresult aStatus) {
+ // If aStatus is a failure we need to mark this data failed. We also need to
+ // mark any ancestors of a failing data as failed and any sibling of a
+ // failing data as failed. Note that SheetComplete is never called on a
+ // SheetLoadData that is the mNext of some other SheetLoadData.
+ nsresult cancelledStatus = aStatus;
+ if (NS_FAILED(aStatus)) {
+ css::Loader::MarkLoadTreeFailed(aData);
+ } else {
+ cancelledStatus = NS_BINDING_ABORTED;
+ SheetLoadData* data = &aData;
+ do {
+ if (data->mIsCancelled) {
+ // We only need to mark loads for this loader as cancelled, so as to not
+ // fire error events in unrelated documents.
+ css::Loader::MarkLoadTreeFailed(*data, data->mLoader);
+ }
+ } while ((data = data->mNext));
+ }
+
+ // 8 is probably big enough for all our common cases. It's not likely that
+ // imports will nest more than 8 deep, and multiple sheets with the same URI
+ // are rare.
+ AutoTArray<RefPtr<SheetLoadData>, 8> datasToNotify;
+ LoadCompletedInternal(aCache, aData, datasToNotify);
+
+ // Now it's safe to go ahead and notify observers
+ for (RefPtr<SheetLoadData>& data : datasToNotify) {
+ auto status = data->mIsCancelled ? cancelledStatus : aStatus;
+ data->mLoader->NotifyObservers(*data, status);
+ }
+}
+
+void SharedStyleSheetCache::LoadCompletedInternal(
+ SharedStyleSheetCache* aCache, SheetLoadData& aData,
+ nsTArray<RefPtr<SheetLoadData>>& aDatasToNotify) {
+ if (aData.mIsLoading) {
+ MOZ_ASSERT(aCache);
+ SheetLoadDataHashKey key(aData);
+ Maybe<SheetLoadData*> loadingData = aCache->mLoadingDatas.GetAndRemove(key);
+ MOZ_DIAGNOSTIC_ASSERT(loadingData);
+ MOZ_DIAGNOSTIC_ASSERT(loadingData.value() == &aData);
+ Unused << loadingData;
+ aData.mIsLoading = false;
+ }
+
+ // Go through and deal with the whole linked list.
+ SheetLoadData* data = &aData;
+ do {
+ MOZ_DIAGNOSTIC_ASSERT(!data->mSheetCompleteCalled);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ data->mSheetCompleteCalled = true;
+#endif
+
+ if (!data->mSheetAlreadyComplete) {
+ // If mSheetAlreadyComplete, then the sheet could well be modified between
+ // when we posted the async call to SheetComplete and now, since the sheet
+ // was page-accessible during that whole time.
+
+ // HasForcedUniqueInner() is okay if the sheet is constructed, because
+ // constructed sheets are always unique and they may be set to complete
+ // multiple times if their rules are replaced via Replace()
+ MOZ_ASSERT(data->mSheet->IsConstructed() ||
+ !data->mSheet->HasForcedUniqueInner(),
+ "should not get a forced unique inner during parsing");
+ // Insert the sheet into the tree now the sheet has loaded, but only if
+ // the sheet is still relevant, and if this is a top-level sheet.
+ const bool needInsertIntoTree = [&] {
+ if (StaticPrefs::dom_expose_incomplete_stylesheets()) {
+ // No need to do that, it's already done. This is technically a bit
+ // racy, but having to reload if you hit an in-progress load while
+ // switching the pref from about:config is not a big deal.
+ return false;
+ }
+ if (!data->mLoader->GetDocument()) {
+ // Not a document load, nothing to do.
+ return false;
+ }
+ if (data->mIsPreload != css::Loader::IsPreload::No) {
+ // Preloads are not supposed to be observable.
+ return false;
+ }
+ if (data->mSheet->IsConstructed()) {
+ // Constructable sheets are not in the regular stylesheet tree.
+ return false;
+ }
+ if (data->mIsChildSheet) {
+ // A child sheet, those will get exposed from the parent, no need to
+ // insert them into the tree.
+ return false;
+ }
+ if (data->mOwningNode != data->mSheet->GetOwnerNode()) {
+ // The sheet was already removed from the tree and is no longer the
+ // current sheet of the owning node, we can bail.
+ return false;
+ }
+ return true;
+ }();
+
+ if (needInsertIntoTree) {
+ data->mLoader->InsertSheetInTree(*data->mSheet, data->mOwningNode);
+ }
+ data->mSheet->SetComplete();
+ data->ScheduleLoadEventIfNeeded();
+ } else if (data->mSheet->IsApplicable()) {
+ if (dom::Document* doc = data->mLoader->GetDocument()) {
+ // We post these events for devtools, even though the applicable state
+ // has not actually changed, to make the cache not observable.
+ doc->PostStyleSheetApplicableStateChangeEvent(*data->mSheet);
+ }
+ }
+
+ aDatasToNotify.AppendElement(data);
+
+ NS_ASSERTION(!data->mParentData || data->mParentData->mPendingChildren != 0,
+ "Broken pending child count on our parent");
+
+ // If we have a parent, our parent is no longer being parsed, and
+ // we are the last pending child, then our load completion
+ // completes the parent too. Note that the parent _can_ still be
+ // being parsed (eg if the child (us) failed to open the channel
+ // or some such).
+ if (data->mParentData && --(data->mParentData->mPendingChildren) == 0 &&
+ !data->mParentData->mIsBeingParsed) {
+ LoadCompletedInternal(aCache, *data->mParentData, aDatasToNotify);
+ }
+
+ data = data->mNext;
+ } while (data);
+
+ if (aCache) {
+ aCache->InsertIntoCompleteCacheIfNeeded(aData);
+ }
+}
+
+void SharedStyleSheetCache::InsertIntoCompleteCacheIfNeeded(
+ SheetLoadData& aData) {
+ MOZ_ASSERT(aData.mLoader->GetDocument(),
+ "We only cache document-associated sheets");
+ LOG(("SharedStyleSheetCache::InsertIntoCompleteCacheIfNeeded"));
+ // If we ever start doing this for failed loads, we'll need to adjust the
+ // PostLoadEvent code that thinks anything already complete must have loaded
+ // succesfully.
+ if (aData.mLoadFailed) {
+ LOG((" Load failed, bailing"));
+ return;
+ }
+
+ // If this sheet came from the cache already, there's no need to override
+ // anything.
+ if (aData.mSheetAlreadyComplete) {
+ LOG((" Sheet came from the cache, bailing"));
+ return;
+ }
+
+ if (!aData.mURI) {
+ LOG((" Inline or constructable style sheet, bailing"));
+ // Inline sheet caching happens in Loader::mInlineSheets.
+ // Constructable sheets are not worth caching, they're always unique.
+ return;
+ }
+
+ // We need to clone the sheet on insertion to the cache because otherwise the
+ // stylesheets can keep alive full windows alive via either their JS wrapper,
+ // or via StyleSheet::mRelevantGlobal.
+ //
+ // If this ever changes, then you also need to fix up the memory reporting in
+ // both SizeOfIncludingThis and nsXULPrototypeCache::CollectMemoryReports.
+ RefPtr<StyleSheet> sheet = CloneSheet(*aData.mSheet);
+
+ if (dom::IsChromeURI(aData.mURI)) {
+ nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
+ if (cache && cache->IsEnabled()) {
+ if (!cache->GetStyleSheet(aData.mURI)) {
+ LOG((" Putting sheet in XUL prototype cache"));
+ NS_ASSERTION(sheet->IsComplete(),
+ "Should only be caching complete sheets");
+
+ // NOTE: If we stop cloning sheets before insertion, we need to change
+ // nsXULPrototypeCache::CollectMemoryReports() to stop using
+ // SizeOfIncludingThis() because it will no longer own the sheets.
+ cache->PutStyleSheet(std::move(sheet));
+ }
+ }
+ } else {
+ LOG((" Putting style sheet in shared cache: %s",
+ aData.mURI->GetSpecOrDefault().get()));
+ SheetLoadDataHashKey key(aData);
+ MOZ_ASSERT(sheet->IsComplete(), "Should only be caching complete sheets");
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ for (const auto& entry : mCompleteSheets) {
+ if (!key.KeyEquals(entry.GetKey())) {
+ MOZ_DIAGNOSTIC_ASSERT(entry.GetData().mSheet != sheet,
+ "Same sheet, different keys?");
+ } else {
+ MOZ_ASSERT(
+ entry.GetData().Expired() || aData.mLoader->ShouldBypassCache(),
+ "Overriding existing complete entry?");
+ }
+ }
+#endif
+
+ UniquePtr<StyleUseCounters> counters;
+ if (aData.mUseCounters) {
+ // TODO(emilio): Servo_UseCounters_Clone() or something?
+ counters = Servo_UseCounters_Create().Consume();
+ Servo_UseCounters_Merge(counters.get(), aData.mUseCounters.get());
+ }
+
+ mCompleteSheets.Put(
+ key, {aData.mExpirationTime, std::move(counters), std::move(sheet)});
+ }
+}
+
+void SharedStyleSheetCache::StartDeferredLoadsForLoader(
+ css::Loader& aLoader, StartLoads aStartLoads) {
+ using PendingLoad = css::Loader::PendingLoad;
+
+ LoadDataArray arr;
+ for (auto iter = mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
+ bool startIt = false;
+ SheetLoadData* data = iter.Data();
+ do {
+ if (data->mLoader == &aLoader) {
+ // Note that we don't want to affect what the selected style set is, so
+ // use true for aHasAlternateRel.
+ if (aStartLoads != StartLoads::IfNonAlternate ||
+ aLoader.IsAlternateSheet(iter.Data()->mTitle, true) !=
+ IsAlternate::Yes) {
+ startIt = true;
+ break;
+ }
+ }
+ } while ((data = data->mNext));
+ if (startIt) {
+ arr.AppendElement(std::move(iter.Data()));
+ iter.Remove();
+ }
+ }
+ for (auto& data : arr) {
+ WillStartPendingLoad(*data);
+ data->mLoader->LoadSheet(*data, SheetState::NeedsParser, PendingLoad::Yes);
+ }
+}
+
+void SharedStyleSheetCache::CancelDeferredLoadsForLoader(css::Loader& aLoader) {
+ LoadDataArray arr;
+
+ for (auto iter = mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<SheetLoadData>& first = iter.Data();
+ SheetLoadData* prev = nullptr;
+ SheetLoadData* current = iter.Data();
+ do {
+ if (current->mLoader != &aLoader) {
+ prev = current;
+ current = current->mNext;
+ continue;
+ }
+ // Detach the load from the list, mark it as cancelled, and then below
+ // call SheetComplete on it.
+ RefPtr<SheetLoadData> strong =
+ prev ? std::move(prev->mNext) : std::move(first);
+ MOZ_ASSERT(strong == current);
+ if (prev) {
+ prev->mNext = std::move(strong->mNext);
+ current = prev->mNext;
+ } else {
+ first = std::move(strong->mNext);
+ current = first;
+ }
+ strong->mIsCancelled = true;
+ arr.AppendElement(std::move(strong));
+ } while (current);
+
+ if (!first) {
+ iter.Remove();
+ }
+ }
+
+ for (auto& data : arr) {
+ aLoader.SheetComplete(*data, NS_BINDING_ABORTED);
+ }
+}
+
+void SharedStyleSheetCache::CancelLoadsForLoader(css::Loader& aLoader) {
+ CancelDeferredLoadsForLoader(aLoader);
+
+ // We can't stop in-progress loads because some other loader may care about
+ // them.
+ for (auto iter = mLoadingDatas.Iter(); !iter.Done(); iter.Next()) {
+ MOZ_DIAGNOSTIC_ASSERT(iter.Data(),
+ "We weren't properly notified and the load was "
+ "incorrectly dropped on the floor");
+ for (SheetLoadData* data = iter.Data(); data; data = data->mNext) {
+ if (data->mLoader == &aLoader) {
+ data->mIsCancelled = true;
+ }
+ }
+ }
+}
+
+void SharedStyleSheetCache::RegisterLoader(css::Loader& aLoader) {
+ MOZ_ASSERT(aLoader.GetDocument());
+ mLoaderPrincipalRefCnt.LookupForAdd(aLoader.GetDocument()->NodePrincipal())
+ .OrInsert([] { return 0; }) += 1;
+}
+
+void SharedStyleSheetCache::UnregisterLoader(css::Loader& aLoader) {
+ MOZ_ASSERT(aLoader.GetDocument());
+ nsIPrincipal* prin = aLoader.GetDocument()->NodePrincipal();
+ auto lookup = mLoaderPrincipalRefCnt.Lookup(prin);
+ MOZ_RELEASE_ASSERT(lookup);
+ MOZ_RELEASE_ASSERT(lookup.Data());
+ if (!--lookup.Data()) {
+ lookup.Remove();
+ // TODO(emilio): Do this off a timer or something maybe.
+ for (auto iter = mCompleteSheets.Iter(); !iter.Done(); iter.Next()) {
+ if (iter.Key().LoaderPrincipal()->Equals(prin)) {
+ iter.Remove();
+ }
+ }
+ }
+}
+
+} // namespace mozilla
+
+#undef LOG