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