Bug 1839315: part 4) Link from `SheetLoadData::mWasAlternate` to spec. r=emilio DONTBUILD
[gecko.git] / layout / style / SharedStyleSheetCache.cpp
blob04b10594573e10c60d356b385fb769b62b3becfa
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 #include "SharedStyleSheetCache.h"
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"
19 extern mozilla::LazyLogModule sCssLoaderLog;
21 #define LOG(...) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
23 namespace mozilla {
25 NS_IMPL_ISUPPORTS(SharedStyleSheetCache, nsIMemoryReporter)
27 MOZ_DEFINE_MALLOC_SIZE_OF(SharedStyleSheetCacheMallocSizeOf)
29 SharedStyleSheetCache::SharedStyleSheetCache() = default;
31 void SharedStyleSheetCache::Init() { RegisterWeakMemoryReporter(this); }
33 SharedStyleSheetCache::~SharedStyleSheetCache() {
34 UnregisterWeakMemoryReporter(this);
37 void SharedStyleSheetCache::LoadCompleted(SharedStyleSheetCache* aCache,
38 StyleSheetLoadData& aData,
39 nsresult aStatus) {
40 // If aStatus is a failure we need to mark this data failed. We also need to
41 // mark any ancestors of a failing data as failed and any sibling of a
42 // failing data as failed. Note that SheetComplete is never called on a
43 // SheetLoadData that is the mNext of some other SheetLoadData.
44 nsresult cancelledStatus = aStatus;
45 if (NS_FAILED(aStatus)) {
46 css::Loader::MarkLoadTreeFailed(aData);
47 } else {
48 cancelledStatus = NS_BINDING_ABORTED;
49 css::SheetLoadData* data = &aData;
50 do {
51 if (data->IsCancelled()) {
52 // We only need to mark loads for this loader as cancelled, so as to not
53 // fire error events in unrelated documents.
54 css::Loader::MarkLoadTreeFailed(*data, data->mLoader);
56 } while ((data = data->mNext));
59 // 8 is probably big enough for all our common cases. It's not likely that
60 // imports will nest more than 8 deep, and multiple sheets with the same URI
61 // are rare.
62 AutoTArray<RefPtr<css::SheetLoadData>, 8> datasToNotify;
63 LoadCompletedInternal(aCache, aData, datasToNotify);
65 // Now it's safe to go ahead and notify observers
66 for (RefPtr<css::SheetLoadData>& data : datasToNotify) {
67 auto status = data->IsCancelled() ? cancelledStatus : aStatus;
68 data->mLoader->NotifyObservers(*data, status);
72 void SharedStyleSheetCache::InsertIfNeeded(css::SheetLoadData& aData) {
73 MOZ_ASSERT(aData.mLoader->IsDocumentAssociated(),
74 "We only cache document-associated sheets");
75 LOG("SharedStyleSheetCache::InsertIfNeeded");
76 // If we ever start doing this for failed loads, we'll need to adjust the
77 // PostLoadEvent code that thinks anything already complete must have loaded
78 // succesfully.
79 if (aData.mLoadFailed) {
80 LOG(" Load failed, bailing");
81 return;
84 // If this sheet came from the cache already, there's no need to override
85 // anything.
86 if (aData.mSheetAlreadyComplete) {
87 LOG(" Sheet came from the cache, bailing");
88 return;
91 if (!aData.mURI) {
92 LOG(" Inline or constructable style sheet, bailing");
93 // Inline sheet caching happens in Loader::mInlineSheets, where we still
94 // have the input text available.
95 // Constructable sheets are not worth caching, they're always unique.
96 return;
99 LOG(" Putting style sheet in shared cache: %s",
100 aData.mURI->GetSpecOrDefault().get());
101 Insert(aData);
104 void SharedStyleSheetCache::LoadCompletedInternal(
105 SharedStyleSheetCache* aCache, css::SheetLoadData& aData,
106 nsTArray<RefPtr<css::SheetLoadData>>& aDatasToNotify) {
107 if (aCache) {
108 aCache->LoadCompleted(aData);
111 // Go through and deal with the whole linked list.
112 auto* data = &aData;
113 do {
114 MOZ_RELEASE_ASSERT(!data->mSheetCompleteCalled);
115 data->mSheetCompleteCalled = true;
117 if (!data->mSheetAlreadyComplete) {
118 // If mSheetAlreadyComplete, then the sheet could well be modified between
119 // when we posted the async call to SheetComplete and now, since the sheet
120 // was page-accessible during that whole time.
122 // HasForcedUniqueInner() is okay if the sheet is constructed, because
123 // constructed sheets are always unique and they may be set to complete
124 // multiple times if their rules are replaced via Replace()
125 MOZ_ASSERT(data->mSheet->IsConstructed() ||
126 !data->mSheet->HasForcedUniqueInner(),
127 "should not get a forced unique inner during parsing");
128 // Insert the sheet into the tree now the sheet has loaded, but only if
129 // the sheet is still relevant, and if this is a top-level sheet.
130 const bool needInsertIntoTree = [&] {
131 if (!data->mLoader->GetDocument()) {
132 // Not a document load, nothing to do.
133 return false;
135 if (data->IsPreload()) {
136 // Preloads are not supposed to be observable.
137 return false;
139 if (data->mSheet->IsConstructed()) {
140 // Constructable sheets are not in the regular stylesheet tree.
141 return false;
143 if (data->mIsChildSheet) {
144 // A child sheet, those will get exposed from the parent, no need to
145 // insert them into the tree.
146 return false;
148 if (data->mOwningNodeBeforeLoadEvent != data->mSheet->GetOwnerNode()) {
149 // The sheet was already removed from the tree and is no longer the
150 // current sheet of the owning node, we can bail.
151 return false;
153 return true;
154 }();
156 if (needInsertIntoTree) {
157 data->mLoader->InsertSheetInTree(*data->mSheet);
159 data->mSheet->SetComplete();
160 } else if (data->mSheet->IsApplicable()) {
161 if (dom::Document* doc = data->mLoader->GetDocument()) {
162 // We post these events for devtools, even though the applicable state
163 // has not actually changed, to make the cache not observable.
164 doc->PostStyleSheetApplicableStateChangeEvent(*data->mSheet);
167 aDatasToNotify.AppendElement(data);
169 NS_ASSERTION(!data->mParentData || data->mParentData->mPendingChildren != 0,
170 "Broken pending child count on our parent");
172 // If we have a parent, our parent is no longer being parsed, and
173 // we are the last pending child, then our load completion
174 // completes the parent too. Note that the parent _can_ still be
175 // being parsed (eg if the child (us) failed to open the channel
176 // or some such).
177 if (data->mParentData && --(data->mParentData->mPendingChildren) == 0 &&
178 !data->mParentData->mIsBeingParsed) {
179 LoadCompletedInternal(aCache, *data->mParentData, aDatasToNotify);
182 data = data->mNext;
183 } while (data);
185 if (aCache) {
186 aCache->InsertIfNeeded(aData);
190 NS_IMETHODIMP
191 SharedStyleSheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
192 nsISupports* aData, bool aAnonymize) {
193 MOZ_COLLECT_REPORT("explicit/layout/style-sheet-cache/document-shared",
194 KIND_HEAP, UNITS_BYTES,
195 SizeOfIncludingThis(SharedStyleSheetCacheMallocSizeOf),
196 "Memory used for SharedStyleSheetCache to share style "
197 "sheets across documents (not to be confused with "
198 "GlobalStyleSheetCache)");
199 return NS_OK;
202 void SharedStyleSheetCache::Clear(nsIPrincipal* aForPrincipal,
203 const nsACString* aBaseDomain) {
204 using ContentParent = dom::ContentParent;
206 if (XRE_IsParentProcess()) {
207 auto forPrincipal = aForPrincipal ? Some(RefPtr(aForPrincipal)) : Nothing();
208 auto baseDomain = aBaseDomain ? Some(nsCString(*aBaseDomain)) : Nothing();
210 for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
211 Unused << cp->SendClearStyleSheetCache(forPrincipal, baseDomain);
215 if (sInstance) {
216 sInstance->ClearInProcess(aForPrincipal, aBaseDomain);
220 } // namespace mozilla
222 #undef LOG