OLD | NEW |
| (Empty) |
1 /* | |
2 Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) | |
3 Copyright (C) 2001 Dirk Mueller (mueller@kde.org) | |
4 Copyright (C) 2002 Waldo Bastian (bastian@kde.org) | |
5 Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. | |
6 | |
7 This library is free software; you can redistribute it and/or | |
8 modify it under the terms of the GNU Library General Public | |
9 License as published by the Free Software Foundation; either | |
10 version 2 of the License, or (at your option) any later version. | |
11 | |
12 This library is distributed in the hope that it will be useful, | |
13 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 Library General Public License for more details. | |
16 | |
17 You should have received a copy of the GNU Library General Public License | |
18 along with this library; see the file COPYING.LIB. If not, write to | |
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
20 Boston, MA 02110-1301, USA. | |
21 */ | |
22 | |
23 #include "core/fetch/MemoryCache.h" | |
24 | |
25 #include "core/fetch/ResourceLoadingLog.h" | |
26 #include "platform/instrumentation/tracing/TraceEvent.h" | |
27 #include "platform/weborigin/SecurityOrigin.h" | |
28 #include "platform/weborigin/SecurityOriginHash.h" | |
29 #include "public/platform/Platform.h" | |
30 #include "wtf/Assertions.h" | |
31 #include "wtf/AutoReset.h" | |
32 #include "wtf/CurrentTime.h" | |
33 #include "wtf/MathExtras.h" | |
34 #include "wtf/text/CString.h" | |
35 | |
36 namespace blink { | |
37 | |
38 static Persistent<MemoryCache>* gMemoryCache; | |
39 | |
40 static const unsigned cDefaultCacheCapacity = 8192 * 1024; | |
41 static const int cMinDelayBeforeLiveDecodedPrune = 1; // Seconds. | |
42 static const double cMaxPruneDeferralDelay = 0.5; // Seconds. | |
43 | |
44 // Percentage of capacity toward which we prune, to avoid immediately pruning | |
45 // again. | |
46 static const float cTargetPrunePercentage = .95f; | |
47 | |
48 MemoryCache* memoryCache() { | |
49 DCHECK(WTF::isMainThread()); | |
50 if (!gMemoryCache) | |
51 gMemoryCache = new Persistent<MemoryCache>(MemoryCache::create()); | |
52 return gMemoryCache->get(); | |
53 } | |
54 | |
55 MemoryCache* replaceMemoryCacheForTesting(MemoryCache* cache) { | |
56 memoryCache(); | |
57 MemoryCache* oldCache = gMemoryCache->release(); | |
58 *gMemoryCache = cache; | |
59 MemoryCacheDumpProvider::instance()->setMemoryCache(cache); | |
60 return oldCache; | |
61 } | |
62 | |
63 DEFINE_TRACE(MemoryCacheEntry) { | |
64 visitor->template registerWeakMembers<MemoryCacheEntry, | |
65 &MemoryCacheEntry::clearResourceWeak>( | |
66 this); | |
67 } | |
68 | |
69 void MemoryCacheEntry::clearResourceWeak(Visitor* visitor) { | |
70 if (!m_resource || ThreadHeap::isHeapObjectAlive(m_resource)) | |
71 return; | |
72 memoryCache()->remove(m_resource.get()); | |
73 m_resource.clear(); | |
74 } | |
75 | |
76 inline MemoryCache::MemoryCache() | |
77 : m_inPruneResources(false), | |
78 m_prunePending(false), | |
79 m_maxPruneDeferralDelay(cMaxPruneDeferralDelay), | |
80 m_pruneTimeStamp(0.0), | |
81 m_pruneFrameTimeStamp(0.0), | |
82 m_lastFramePaintTimeStamp(0.0), | |
83 m_capacity(cDefaultCacheCapacity), | |
84 m_delayBeforeLiveDecodedPrune(cMinDelayBeforeLiveDecodedPrune), | |
85 m_size(0) { | |
86 MemoryCacheDumpProvider::instance()->setMemoryCache(this); | |
87 if (MemoryCoordinator::isLowEndDevice()) | |
88 MemoryCoordinator::instance().registerClient(this); | |
89 } | |
90 | |
91 MemoryCache* MemoryCache::create() { | |
92 return new MemoryCache; | |
93 } | |
94 | |
95 MemoryCache::~MemoryCache() { | |
96 if (m_prunePending) | |
97 Platform::current()->currentThread()->removeTaskObserver(this); | |
98 } | |
99 | |
100 DEFINE_TRACE(MemoryCache) { | |
101 visitor->trace(m_resourceMaps); | |
102 MemoryCacheDumpClient::trace(visitor); | |
103 MemoryCoordinatorClient::trace(visitor); | |
104 } | |
105 | |
106 KURL MemoryCache::removeFragmentIdentifierIfNeeded(const KURL& originalURL) { | |
107 if (!originalURL.hasFragmentIdentifier()) | |
108 return originalURL; | |
109 // Strip away fragment identifier from HTTP URLs. Data URLs must be | |
110 // unmodified. For file and custom URLs clients may expect resources to be | |
111 // unique even when they differ by the fragment identifier only. | |
112 if (!originalURL.protocolIsInHTTPFamily()) | |
113 return originalURL; | |
114 KURL url = originalURL; | |
115 url.removeFragmentIdentifier(); | |
116 return url; | |
117 } | |
118 | |
119 String MemoryCache::defaultCacheIdentifier() { | |
120 return emptyString(); | |
121 } | |
122 | |
123 MemoryCache::ResourceMap* MemoryCache::ensureResourceMap( | |
124 const String& cacheIdentifier) { | |
125 if (!m_resourceMaps.contains(cacheIdentifier)) { | |
126 ResourceMapIndex::AddResult result = | |
127 m_resourceMaps.add(cacheIdentifier, new ResourceMap); | |
128 CHECK(result.isNewEntry); | |
129 } | |
130 return m_resourceMaps.get(cacheIdentifier); | |
131 } | |
132 | |
133 void MemoryCache::add(Resource* resource) { | |
134 DCHECK(resource); | |
135 ResourceMap* resources = ensureResourceMap(resource->cacheIdentifier()); | |
136 addInternal(resources, MemoryCacheEntry::create(resource)); | |
137 RESOURCE_LOADING_DVLOG(1) << "MemoryCache::add Added " | |
138 << resource->url().getString() << ", resource " | |
139 << resource; | |
140 } | |
141 | |
142 void MemoryCache::addInternal(ResourceMap* resourceMap, | |
143 MemoryCacheEntry* entry) { | |
144 DCHECK(WTF::isMainThread()); | |
145 DCHECK(resourceMap); | |
146 | |
147 Resource* resource = entry->resource(); | |
148 if (!resource) | |
149 return; | |
150 DCHECK(resource->url().isValid()); | |
151 | |
152 KURL url = removeFragmentIdentifierIfNeeded(resource->url()); | |
153 ResourceMap::iterator it = resourceMap->find(url); | |
154 if (it != resourceMap->end()) { | |
155 Resource* oldResource = it->value->resource(); | |
156 CHECK_NE(oldResource, resource); | |
157 update(oldResource, oldResource->size(), 0); | |
158 } | |
159 resourceMap->set(url, entry); | |
160 update(resource, 0, resource->size()); | |
161 } | |
162 | |
163 void MemoryCache::remove(Resource* resource) { | |
164 DCHECK(WTF::isMainThread()); | |
165 DCHECK(resource); | |
166 RESOURCE_LOADING_DVLOG(1) << "Evicting resource " << resource << " for " | |
167 << resource->url().getString() << " from cache"; | |
168 TRACE_EVENT1("blink", "MemoryCache::evict", "resource", | |
169 resource->url().getString().utf8()); | |
170 | |
171 ResourceMap* resources = m_resourceMaps.get(resource->cacheIdentifier()); | |
172 if (!resources) | |
173 return; | |
174 | |
175 KURL url = removeFragmentIdentifierIfNeeded(resource->url()); | |
176 ResourceMap::iterator it = resources->find(url); | |
177 if (it == resources->end() || it->value->resource() != resource) | |
178 return; | |
179 removeInternal(resources, it); | |
180 } | |
181 | |
182 void MemoryCache::removeInternal(ResourceMap* resourceMap, | |
183 const ResourceMap::iterator& it) { | |
184 DCHECK(WTF::isMainThread()); | |
185 DCHECK(resourceMap); | |
186 | |
187 Resource* resource = it->value->resource(); | |
188 DCHECK(resource); | |
189 | |
190 update(resource, resource->size(), 0); | |
191 resourceMap->remove(it); | |
192 } | |
193 | |
194 bool MemoryCache::contains(const Resource* resource) const { | |
195 if (!resource || resource->url().isEmpty()) | |
196 return false; | |
197 const ResourceMap* resources = | |
198 m_resourceMaps.get(resource->cacheIdentifier()); | |
199 if (!resources) | |
200 return false; | |
201 KURL url = removeFragmentIdentifierIfNeeded(resource->url()); | |
202 MemoryCacheEntry* entry = resources->get(url); | |
203 return entry && resource == entry->resource(); | |
204 } | |
205 | |
206 Resource* MemoryCache::resourceForURL(const KURL& resourceURL) const { | |
207 return resourceForURL(resourceURL, defaultCacheIdentifier()); | |
208 } | |
209 | |
210 Resource* MemoryCache::resourceForURL(const KURL& resourceURL, | |
211 const String& cacheIdentifier) const { | |
212 DCHECK(WTF::isMainThread()); | |
213 if (!resourceURL.isValid() || resourceURL.isNull()) | |
214 return nullptr; | |
215 DCHECK(!cacheIdentifier.isNull()); | |
216 const ResourceMap* resources = m_resourceMaps.get(cacheIdentifier); | |
217 if (!resources) | |
218 return nullptr; | |
219 MemoryCacheEntry* entry = | |
220 resources->get(removeFragmentIdentifierIfNeeded(resourceURL)); | |
221 if (!entry) | |
222 return nullptr; | |
223 return entry->resource(); | |
224 } | |
225 | |
226 HeapVector<Member<Resource>> MemoryCache::resourcesForURL( | |
227 const KURL& resourceURL) const { | |
228 DCHECK(WTF::isMainThread()); | |
229 KURL url = removeFragmentIdentifierIfNeeded(resourceURL); | |
230 HeapVector<Member<Resource>> results; | |
231 for (const auto& resourceMapIter : m_resourceMaps) { | |
232 if (MemoryCacheEntry* entry = resourceMapIter.value->get(url)) { | |
233 Resource* resource = entry->resource(); | |
234 DCHECK(resource); | |
235 results.push_back(resource); | |
236 } | |
237 } | |
238 return results; | |
239 } | |
240 | |
241 void MemoryCache::pruneResources(PruneStrategy strategy) { | |
242 DCHECK(!m_prunePending); | |
243 const size_t sizeLimit = (strategy == MaximalPrune) ? 0 : capacity(); | |
244 if (m_size <= sizeLimit) | |
245 return; | |
246 | |
247 // Cut by a percentage to avoid immediately pruning again. | |
248 size_t targetSize = static_cast<size_t>(sizeLimit * cTargetPrunePercentage); | |
249 | |
250 for (const auto& resourceMapIter : m_resourceMaps) { | |
251 for (const auto& resourceIter : *resourceMapIter.value) { | |
252 Resource* resource = resourceIter.value->resource(); | |
253 DCHECK(resource); | |
254 if (resource->isLoaded() && resource->decodedSize()) { | |
255 // Check to see if the remaining resources are too new to prune. | |
256 double elapsedTime = | |
257 m_pruneFrameTimeStamp - resourceIter.value->m_lastDecodedAccessTime; | |
258 if (strategy == AutomaticPrune && | |
259 elapsedTime < m_delayBeforeLiveDecodedPrune) | |
260 continue; | |
261 resource->prune(); | |
262 if (m_size <= targetSize) | |
263 return; | |
264 } | |
265 } | |
266 } | |
267 } | |
268 | |
269 void MemoryCache::setCapacity(size_t totalBytes) { | |
270 m_capacity = totalBytes; | |
271 prune(); | |
272 } | |
273 | |
274 void MemoryCache::update(Resource* resource, size_t oldSize, size_t newSize) { | |
275 if (!contains(resource)) | |
276 return; | |
277 ptrdiff_t delta = newSize - oldSize; | |
278 DCHECK(delta >= 0 || m_size >= static_cast<size_t>(-delta)); | |
279 m_size += delta; | |
280 } | |
281 | |
282 void MemoryCache::removeURLFromCache(const KURL& url) { | |
283 HeapVector<Member<Resource>> resources = resourcesForURL(url); | |
284 for (Resource* resource : resources) | |
285 remove(resource); | |
286 } | |
287 | |
288 void MemoryCache::TypeStatistic::addResource(Resource* o) { | |
289 count++; | |
290 size += o->size(); | |
291 decodedSize += o->decodedSize(); | |
292 encodedSize += o->encodedSize(); | |
293 overheadSize += o->overheadSize(); | |
294 encodedSizeDuplicatedInDataURLs += | |
295 o->url().protocolIsData() ? o->encodedSize() : 0; | |
296 } | |
297 | |
298 MemoryCache::Statistics MemoryCache::getStatistics() const { | |
299 Statistics stats; | |
300 for (const auto& resourceMapIter : m_resourceMaps) { | |
301 for (const auto& resourceIter : *resourceMapIter.value) { | |
302 Resource* resource = resourceIter.value->resource(); | |
303 DCHECK(resource); | |
304 switch (resource->getType()) { | |
305 case Resource::Image: | |
306 stats.images.addResource(resource); | |
307 break; | |
308 case Resource::CSSStyleSheet: | |
309 stats.cssStyleSheets.addResource(resource); | |
310 break; | |
311 case Resource::Script: | |
312 stats.scripts.addResource(resource); | |
313 break; | |
314 case Resource::XSLStyleSheet: | |
315 stats.xslStyleSheets.addResource(resource); | |
316 break; | |
317 case Resource::Font: | |
318 stats.fonts.addResource(resource); | |
319 break; | |
320 default: | |
321 stats.other.addResource(resource); | |
322 break; | |
323 } | |
324 } | |
325 } | |
326 return stats; | |
327 } | |
328 | |
329 void MemoryCache::evictResources(EvictResourcePolicy policy) { | |
330 for (auto resourceMapIter = m_resourceMaps.begin(); | |
331 resourceMapIter != m_resourceMaps.end();) { | |
332 ResourceMap* resources = resourceMapIter->value.get(); | |
333 HeapVector<Member<MemoryCacheEntry>> unusedPreloads; | |
334 for (auto resourceIter = resources->begin(); | |
335 resourceIter != resources->end(); resourceIter = resources->begin()) { | |
336 DCHECK(resourceIter.get()); | |
337 DCHECK(resourceIter->value.get()); | |
338 DCHECK(resourceIter->value->resource()); | |
339 Resource* resource = resourceIter->value->resource(); | |
340 DCHECK(resource); | |
341 if (policy != EvictAllResources && resource->isUnusedPreload()) { | |
342 // Store unused preloads aside, so they could be added back later. | |
343 // That is in order to avoid the performance impact of iterating over | |
344 // the same resource multiple times. | |
345 unusedPreloads.push_back(resourceIter->value.get()); | |
346 } | |
347 removeInternal(resources, resourceIter); | |
348 } | |
349 for (const auto& unusedPreload : unusedPreloads) { | |
350 addInternal(resources, unusedPreload); | |
351 } | |
352 // We may iterate multiple times over resourceMaps with unused preloads. | |
353 // That's extremely unlikely to have any real-life performance impact. | |
354 if (!resources->size()) { | |
355 m_resourceMaps.remove(resourceMapIter); | |
356 resourceMapIter = m_resourceMaps.begin(); | |
357 } else { | |
358 ++resourceMapIter; | |
359 } | |
360 } | |
361 } | |
362 | |
363 void MemoryCache::prune() { | |
364 TRACE_EVENT0("renderer", "MemoryCache::prune()"); | |
365 | |
366 if (m_inPruneResources) | |
367 return; | |
368 if (m_size <= m_capacity) // Fast path. | |
369 return; | |
370 | |
371 // To avoid burdening the current thread with repetitive pruning jobs, pruning | |
372 // is postponed until the end of the current task. If it has been more than | |
373 // m_maxPruneDeferralDelay since the last prune, then we prune immediately. If | |
374 // the current thread's run loop is not active, then pruning will happen | |
375 // immediately only if it has been over m_maxPruneDeferralDelay since the last | |
376 // prune. | |
377 double currentTime = WTF::currentTime(); | |
378 if (m_prunePending) { | |
379 if (currentTime - m_pruneTimeStamp >= m_maxPruneDeferralDelay) { | |
380 pruneNow(currentTime, AutomaticPrune); | |
381 } | |
382 } else { | |
383 if (currentTime - m_pruneTimeStamp >= m_maxPruneDeferralDelay) { | |
384 pruneNow(currentTime, AutomaticPrune); // Delay exceeded, prune now. | |
385 } else { | |
386 // Defer. | |
387 Platform::current()->currentThread()->addTaskObserver(this); | |
388 m_prunePending = true; | |
389 } | |
390 } | |
391 } | |
392 | |
393 void MemoryCache::willProcessTask() {} | |
394 | |
395 void MemoryCache::didProcessTask() { | |
396 // Perform deferred pruning | |
397 DCHECK(m_prunePending); | |
398 pruneNow(WTF::currentTime(), AutomaticPrune); | |
399 } | |
400 | |
401 void MemoryCache::pruneAll() { | |
402 double currentTime = WTF::currentTime(); | |
403 pruneNow(currentTime, MaximalPrune); | |
404 } | |
405 | |
406 void MemoryCache::pruneNow(double currentTime, PruneStrategy strategy) { | |
407 if (m_prunePending) { | |
408 m_prunePending = false; | |
409 Platform::current()->currentThread()->removeTaskObserver(this); | |
410 } | |
411 | |
412 AutoReset<bool> reentrancyProtector(&m_inPruneResources, true); | |
413 | |
414 pruneResources(strategy); | |
415 m_pruneFrameTimeStamp = m_lastFramePaintTimeStamp; | |
416 m_pruneTimeStamp = currentTime; | |
417 } | |
418 | |
419 void MemoryCache::updateFramePaintTimestamp() { | |
420 m_lastFramePaintTimeStamp = currentTime(); | |
421 } | |
422 | |
423 bool MemoryCache::onMemoryDump(WebMemoryDumpLevelOfDetail levelOfDetail, | |
424 WebProcessMemoryDump* memoryDump) { | |
425 if (levelOfDetail == WebMemoryDumpLevelOfDetail::Background) { | |
426 Statistics stats = getStatistics(); | |
427 WebMemoryAllocatorDump* dump1 = | |
428 memoryDump->createMemoryAllocatorDump("web_cache/Image_resources"); | |
429 dump1->addScalar("size", "bytes", | |
430 stats.images.encodedSize + stats.images.overheadSize); | |
431 WebMemoryAllocatorDump* dump2 = memoryDump->createMemoryAllocatorDump( | |
432 "web_cache/CSS stylesheet_resources"); | |
433 dump2->addScalar("size", "bytes", stats.cssStyleSheets.encodedSize + | |
434 stats.cssStyleSheets.overheadSize); | |
435 WebMemoryAllocatorDump* dump3 = | |
436 memoryDump->createMemoryAllocatorDump("web_cache/Script_resources"); | |
437 dump3->addScalar("size", "bytes", | |
438 stats.scripts.encodedSize + stats.scripts.overheadSize); | |
439 WebMemoryAllocatorDump* dump4 = memoryDump->createMemoryAllocatorDump( | |
440 "web_cache/XSL stylesheet_resources"); | |
441 dump4->addScalar("size", "bytes", stats.xslStyleSheets.encodedSize + | |
442 stats.xslStyleSheets.overheadSize); | |
443 WebMemoryAllocatorDump* dump5 = | |
444 memoryDump->createMemoryAllocatorDump("web_cache/Font_resources"); | |
445 dump5->addScalar("size", "bytes", | |
446 stats.fonts.encodedSize + stats.fonts.overheadSize); | |
447 WebMemoryAllocatorDump* dump6 = | |
448 memoryDump->createMemoryAllocatorDump("web_cache/Other_resources"); | |
449 dump6->addScalar("size", "bytes", | |
450 stats.other.encodedSize + stats.other.overheadSize); | |
451 return true; | |
452 } | |
453 | |
454 for (const auto& resourceMapIter : m_resourceMaps) { | |
455 for (const auto& resourceIter : *resourceMapIter.value) { | |
456 Resource* resource = resourceIter.value->resource(); | |
457 resource->onMemoryDump(levelOfDetail, memoryDump); | |
458 } | |
459 } | |
460 return true; | |
461 } | |
462 | |
463 void MemoryCache::onMemoryPressure(WebMemoryPressureLevel level) { | |
464 pruneAll(); | |
465 } | |
466 | |
467 } // namespace blink | |
OLD | NEW |