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 /*
8 * cache of re-usable nsCSSRuleProcessors for given sets of style sheets
9 */
10
11 #include "RuleProcessorCache.h"
12
13 #include <algorithm>
14 #include "mozilla/CSSStyleSheet.h"
15 #include "nsCSSRuleProcessor.h"
16 #include "nsThreadUtils.h"
17
18 using namespace mozilla;
19
NS_IMPL_ISUPPORTS(RuleProcessorCache,nsIMemoryReporter)20 NS_IMPL_ISUPPORTS(RuleProcessorCache, nsIMemoryReporter)
21
22 MOZ_DEFINE_MALLOC_SIZE_OF(RuleProcessorCacheMallocSizeOf)
23
24 NS_IMETHODIMP
25 RuleProcessorCache::CollectReports(nsIHandleReportCallback* aHandleReport,
26 nsISupports* aData, bool aAnonymize) {
27 MOZ_COLLECT_REPORT("explicit/layout/rule-processor-cache", KIND_HEAP,
28 UNITS_BYTES,
29 SizeOfIncludingThis(RuleProcessorCacheMallocSizeOf),
30 "Memory used for cached rule processors.");
31
32 return NS_OK;
33 }
34
~RuleProcessorCache()35 RuleProcessorCache::~RuleProcessorCache() {
36 UnregisterWeakMemoryReporter(this);
37
38 for (Entry& e : mEntries) {
39 for (DocumentEntry& de : e.mDocumentEntries) {
40 if (de.mRuleProcessor->GetExpirationState()->IsTracked()) {
41 mExpirationTracker.RemoveObject(de.mRuleProcessor);
42 }
43 de.mRuleProcessor->SetInRuleProcessorCache(false);
44 }
45 }
46 }
47
InitMemoryReporter()48 void RuleProcessorCache::InitMemoryReporter() {
49 RegisterWeakMemoryReporter(this);
50 }
51
EnsureGlobal()52 /* static */ bool RuleProcessorCache::EnsureGlobal() {
53 MOZ_ASSERT(NS_IsMainThread());
54
55 if (gShutdown) {
56 return false;
57 }
58
59 if (!gRuleProcessorCache) {
60 gRuleProcessorCache = new RuleProcessorCache;
61 gRuleProcessorCache->InitMemoryReporter();
62 }
63 return true;
64 }
65
RemoveSheet(CSSStyleSheet * aSheet)66 /* static */ void RuleProcessorCache::RemoveSheet(CSSStyleSheet* aSheet) {
67 if (!EnsureGlobal()) {
68 return;
69 }
70 gRuleProcessorCache->DoRemoveSheet(aSheet);
71 }
72
73 #ifdef DEBUG
HasRuleProcessor(nsCSSRuleProcessor * aRuleProcessor)74 /* static */ bool RuleProcessorCache::HasRuleProcessor(
75 nsCSSRuleProcessor* aRuleProcessor) {
76 if (!EnsureGlobal()) {
77 return false;
78 }
79 return gRuleProcessorCache->DoHasRuleProcessor(aRuleProcessor);
80 }
81 #endif
82
RemoveRuleProcessor(nsCSSRuleProcessor * aRuleProcessor)83 /* static */ void RuleProcessorCache::RemoveRuleProcessor(
84 nsCSSRuleProcessor* aRuleProcessor) {
85 if (!EnsureGlobal()) {
86 return;
87 }
88 gRuleProcessorCache->DoRemoveRuleProcessor(aRuleProcessor);
89 }
90
GetRuleProcessor(const nsTArray<CSSStyleSheet * > & aSheets,nsPresContext * aPresContext)91 /* static */ nsCSSRuleProcessor* RuleProcessorCache::GetRuleProcessor(
92 const nsTArray<CSSStyleSheet*>& aSheets, nsPresContext* aPresContext) {
93 if (!EnsureGlobal()) {
94 return nullptr;
95 }
96 return gRuleProcessorCache->DoGetRuleProcessor(aSheets, aPresContext);
97 }
98
PutRuleProcessor(const nsTArray<CSSStyleSheet * > & aSheets,nsTArray<css::DocumentRule * > && aDocumentRulesInSheets,const nsDocumentRuleResultCacheKey & aCacheKey,nsCSSRuleProcessor * aRuleProcessor)99 /* static */ void RuleProcessorCache::PutRuleProcessor(
100 const nsTArray<CSSStyleSheet*>& aSheets,
101 nsTArray<css::DocumentRule*>&& aDocumentRulesInSheets,
102 const nsDocumentRuleResultCacheKey& aCacheKey,
103 nsCSSRuleProcessor* aRuleProcessor) {
104 if (!EnsureGlobal()) {
105 return;
106 }
107 gRuleProcessorCache->DoPutRuleProcessor(aSheets, Move(aDocumentRulesInSheets),
108 aCacheKey, aRuleProcessor);
109 }
110
StartTracking(nsCSSRuleProcessor * aRuleProcessor)111 /* static */ void RuleProcessorCache::StartTracking(
112 nsCSSRuleProcessor* aRuleProcessor) {
113 if (!EnsureGlobal()) {
114 return;
115 }
116 return gRuleProcessorCache->DoStartTracking(aRuleProcessor);
117 }
118
StopTracking(nsCSSRuleProcessor * aRuleProcessor)119 /* static */ void RuleProcessorCache::StopTracking(
120 nsCSSRuleProcessor* aRuleProcessor) {
121 if (!EnsureGlobal()) {
122 return;
123 }
124 return gRuleProcessorCache->DoStopTracking(aRuleProcessor);
125 }
126
DoRemoveSheet(CSSStyleSheet * aSheet)127 void RuleProcessorCache::DoRemoveSheet(CSSStyleSheet* aSheet) {
128 auto last = std::remove_if(mEntries.begin(), mEntries.end(),
129 HasSheet_ThenRemoveRuleProcessors(this, aSheet));
130 mEntries.TruncateLength(last - mEntries.begin());
131 }
132
DoGetRuleProcessor(const nsTArray<CSSStyleSheet * > & aSheets,nsPresContext * aPresContext)133 nsCSSRuleProcessor* RuleProcessorCache::DoGetRuleProcessor(
134 const nsTArray<CSSStyleSheet*>& aSheets, nsPresContext* aPresContext) {
135 for (Entry& e : mEntries) {
136 if (e.mSheets == aSheets) {
137 for (DocumentEntry& de : e.mDocumentEntries) {
138 if (de.mCacheKey.Matches(aPresContext, e.mDocumentRulesInSheets)) {
139 return de.mRuleProcessor;
140 }
141 }
142 // Entry::mSheets is unique; if we matched aSheets but didn't
143 // find a matching DocumentEntry, we won't find one later in
144 // mEntries.
145 return nullptr;
146 }
147 }
148 return nullptr;
149 }
150
DoPutRuleProcessor(const nsTArray<CSSStyleSheet * > & aSheets,nsTArray<css::DocumentRule * > && aDocumentRulesInSheets,const nsDocumentRuleResultCacheKey & aCacheKey,nsCSSRuleProcessor * aRuleProcessor)151 void RuleProcessorCache::DoPutRuleProcessor(
152 const nsTArray<CSSStyleSheet*>& aSheets,
153 nsTArray<css::DocumentRule*>&& aDocumentRulesInSheets,
154 const nsDocumentRuleResultCacheKey& aCacheKey,
155 nsCSSRuleProcessor* aRuleProcessor) {
156 MOZ_ASSERT(!aRuleProcessor->IsInRuleProcessorCache());
157
158 Entry* entry = nullptr;
159 for (Entry& e : mEntries) {
160 if (e.mSheets == aSheets) {
161 entry = &e;
162 break;
163 }
164 }
165
166 if (!entry) {
167 entry = mEntries.AppendElement();
168 entry->mSheets = aSheets;
169 entry->mDocumentRulesInSheets = aDocumentRulesInSheets;
170 for (CSSStyleSheet* sheet : aSheets) {
171 sheet->SetInRuleProcessorCache();
172 }
173 } else {
174 MOZ_ASSERT(entry->mDocumentRulesInSheets == aDocumentRulesInSheets,
175 "DocumentRule array shouldn't have changed");
176 }
177
178 #ifdef DEBUG
179 for (DocumentEntry& de : entry->mDocumentEntries) {
180 MOZ_ASSERT(de.mCacheKey != aCacheKey,
181 "should not have duplicate document cache keys");
182 }
183 #endif
184
185 DocumentEntry* documentEntry = entry->mDocumentEntries.AppendElement();
186 documentEntry->mCacheKey = aCacheKey;
187 documentEntry->mRuleProcessor = aRuleProcessor;
188 aRuleProcessor->SetInRuleProcessorCache(true);
189 }
190
191 #ifdef DEBUG
DoHasRuleProcessor(nsCSSRuleProcessor * aRuleProcessor)192 bool RuleProcessorCache::DoHasRuleProcessor(
193 nsCSSRuleProcessor* aRuleProcessor) {
194 for (Entry& e : mEntries) {
195 for (DocumentEntry& de : e.mDocumentEntries) {
196 if (de.mRuleProcessor == aRuleProcessor) {
197 return true;
198 }
199 }
200 }
201 return false;
202 }
203 #endif
204
DoRemoveRuleProcessor(nsCSSRuleProcessor * aRuleProcessor)205 void RuleProcessorCache::DoRemoveRuleProcessor(
206 nsCSSRuleProcessor* aRuleProcessor) {
207 MOZ_ASSERT(aRuleProcessor->IsInRuleProcessorCache());
208
209 aRuleProcessor->SetInRuleProcessorCache(false);
210 mExpirationTracker.RemoveObjectIfTracked(aRuleProcessor);
211 for (Entry& e : mEntries) {
212 for (size_t i = 0; i < e.mDocumentEntries.Length(); i++) {
213 if (e.mDocumentEntries[i].mRuleProcessor == aRuleProcessor) {
214 e.mDocumentEntries.RemoveElementAt(i);
215 return;
216 }
217 }
218 }
219
220 MOZ_ASSERT_UNREACHABLE("should have found rule processor");
221 }
222
DoStartTracking(nsCSSRuleProcessor * aRuleProcessor)223 void RuleProcessorCache::DoStartTracking(nsCSSRuleProcessor* aRuleProcessor) {
224 mExpirationTracker.AddObject(aRuleProcessor);
225 }
226
DoStopTracking(nsCSSRuleProcessor * aRuleProcessor)227 void RuleProcessorCache::DoStopTracking(nsCSSRuleProcessor* aRuleProcessor) {
228 mExpirationTracker.RemoveObjectIfTracked(aRuleProcessor);
229 }
230
SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)231 size_t RuleProcessorCache::SizeOfIncludingThis(
232 mozilla::MallocSizeOf aMallocSizeOf) {
233 size_t n = aMallocSizeOf(this);
234
235 int count = 0;
236 n += mEntries.ShallowSizeOfExcludingThis(aMallocSizeOf);
237 for (Entry& e : mEntries) {
238 n += e.mDocumentEntries.ShallowSizeOfExcludingThis(aMallocSizeOf);
239 for (DocumentEntry& de : e.mDocumentEntries) {
240 count++;
241 n += de.mRuleProcessor->SizeOfIncludingThis(aMallocSizeOf);
242 }
243 }
244
245 return n;
246 }
247
RemoveObjectIfTracked(nsCSSRuleProcessor * aRuleProcessor)248 void RuleProcessorCache::ExpirationTracker::RemoveObjectIfTracked(
249 nsCSSRuleProcessor* aRuleProcessor) {
250 if (aRuleProcessor->GetExpirationState()->IsTracked()) {
251 RemoveObject(aRuleProcessor);
252 }
253 }
254
255 bool RuleProcessorCache::gShutdown = false;
256 mozilla::StaticRefPtr<RuleProcessorCache>
257 RuleProcessorCache::gRuleProcessorCache;
258