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__))
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
,
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
);
48 cancelledStatus
= NS_BINDING_ABORTED
;
49 css::SheetLoadData
* data
= &aData
;
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
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
79 if (aData
.mLoadFailed
) {
80 LOG(" Load failed, bailing");
84 // If this sheet came from the cache already, there's no need to override
86 if (aData
.mSheetAlreadyComplete
) {
87 LOG(" Sheet came from the cache, bailing");
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.
99 LOG(" Putting style sheet in shared cache: %s",
100 aData
.mURI
->GetSpecOrDefault().get());
104 void SharedStyleSheetCache::LoadCompletedInternal(
105 SharedStyleSheetCache
* aCache
, css::SheetLoadData
& aData
,
106 nsTArray
<RefPtr
<css::SheetLoadData
>>& aDatasToNotify
) {
108 aCache
->LoadCompleted(aData
);
111 // Go through and deal with the whole linked list.
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.
135 if (data
->IsPreload()) {
136 // Preloads are not supposed to be observable.
139 if (data
->mSheet
->IsConstructed()) {
140 // Constructable sheets are not in the regular stylesheet tree.
143 if (data
->mIsChildSheet
) {
144 // A child sheet, those will get exposed from the parent, no need to
145 // insert them into the tree.
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.
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
177 if (data
->mParentData
&& --(data
->mParentData
->mPendingChildren
) == 0 &&
178 !data
->mParentData
->mIsBeingParsed
) {
179 LoadCompletedInternal(aCache
, *data
->mParentData
, aDatasToNotify
);
186 aCache
->InsertIfNeeded(aData
);
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)");
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
);
216 sInstance
->ClearInProcess(aForPrincipal
, aBaseDomain
);
220 } // namespace mozilla