Memory cache live resources repeatedly purged during painting
[WebKit-https.git] / Source / WebCore / loader / cache / MemoryCache.cpp
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 "config.h"
24 #include "MemoryCache.h"
25
26 #include "BitmapImage.h"
27 #include "CachedImage.h"
28 #include "CachedImageClient.h"
29 #include "CachedResource.h"
30 #include "CachedResourceHandle.h"
31 #include "Document.h"
32 #include "FrameLoader.h"
33 #include "FrameLoaderTypes.h"
34 #include "FrameView.h"
35 #include "Image.h"
36 #include "Logging.h"
37 #include "PublicSuffix.h"
38 #include "SharedBuffer.h"
39 #include "WorkerGlobalScope.h"
40 #include "WorkerLoaderProxy.h"
41 #include "WorkerThread.h"
42 #include <stdio.h>
43 #include <wtf/CurrentTime.h>
44 #include <wtf/MathExtras.h>
45 #include <wtf/NeverDestroyed.h>
46 #include <wtf/TemporaryChange.h>
47 #include <wtf/text/CString.h>
48
49 namespace WebCore {
50
51 static const int cDefaultCacheCapacity = 8192 * 1024;
52 static const double cMinDelayBeforeLiveDecodedPrune = 1; // Seconds.
53 static const float cTargetPrunePercentage = .95f; // Percentage of capacity toward which we prune, to avoid immediately pruning again.
54 static const auto defaultDecodedDataDeletionInterval = std::chrono::seconds { 0 };
55
56 MemoryCache& MemoryCache::singleton()
57 {
58     ASSERT(WTF::isMainThread());
59     static NeverDestroyed<MemoryCache> memoryCache;
60     return memoryCache;
61 }
62
63 MemoryCache::MemoryCache()
64     : m_disabled(false)
65     , m_inPruneResources(false)
66     , m_capacity(cDefaultCacheCapacity)
67     , m_minDeadCapacity(0)
68     , m_maxDeadCapacity(cDefaultCacheCapacity)
69     , m_deadDecodedDataDeletionInterval(defaultDecodedDataDeletionInterval)
70     , m_liveSize(0)
71     , m_deadSize(0)
72     , m_pruneTimer(*this, &MemoryCache::pruneTimerFired)
73 {
74 }
75
76 auto MemoryCache::sessionResourceMap(SessionID sessionID) const -> CachedResourceMap*
77 {
78     ASSERT(sessionID.isValid());
79     return m_sessionResources.get(sessionID);
80 }
81
82 auto MemoryCache::ensureSessionResourceMap(SessionID sessionID) -> CachedResourceMap&
83 {
84     ASSERT(sessionID.isValid());
85     auto& map = m_sessionResources.add(sessionID, nullptr).iterator->value;
86     if (!map)
87         map = std::make_unique<CachedResourceMap>();
88     return *map;
89 }
90
91 URL MemoryCache::removeFragmentIdentifierIfNeeded(const URL& originalURL)
92 {
93     if (!originalURL.hasFragmentIdentifier())
94         return originalURL;
95     // Strip away fragment identifier from HTTP URLs.
96     // Data URLs must be unmodified. For file and custom URLs clients may expect resources 
97     // to be unique even when they differ by the fragment identifier only.
98     if (!originalURL.protocolIsInHTTPFamily())
99         return originalURL;
100     URL url = originalURL;
101     url.removeFragmentIdentifier();
102     return url;
103 }
104
105 bool MemoryCache::add(CachedResource& resource)
106 {
107     if (disabled())
108         return false;
109
110     ASSERT(WTF::isMainThread());
111
112 #if ENABLE(CACHE_PARTITIONING)
113     auto key = std::make_pair(resource.url(), resource.cachePartition());
114 #else
115     auto& key = resource.url();
116 #endif
117     ensureSessionResourceMap(resource.sessionID()).set(key, &resource);
118     resource.setInCache(true);
119     
120     resourceAccessed(resource);
121     
122     LOG(ResourceLoading, "MemoryCache::add Added '%s', resource %p\n", resource.url().string().latin1().data(), &resource);
123     return true;
124 }
125
126 void MemoryCache::revalidationSucceeded(CachedResource& revalidatingResource, const ResourceResponse& response)
127 {
128     ASSERT(revalidatingResource.resourceToRevalidate());
129     CachedResource& resource = *revalidatingResource.resourceToRevalidate();
130     ASSERT(!resource.inCache());
131     ASSERT(resource.isLoaded());
132     ASSERT(revalidatingResource.inCache());
133
134     // Calling remove() can potentially delete revalidatingResource, which we use
135     // below. This mustn't be the case since revalidation means it is loaded
136     // and so canDelete() is false.
137     ASSERT(!revalidatingResource.canDelete());
138
139     remove(revalidatingResource);
140
141     auto& resources = ensureSessionResourceMap(resource.sessionID());
142 #if ENABLE(CACHE_PARTITIONING)
143     auto key = std::make_pair(resource.url(), resource.cachePartition());
144 #else
145     auto& key = resource.url();
146 #endif
147     ASSERT(!resources.get(key));
148     resources.set(key, &resource);
149     resource.setInCache(true);
150     resource.updateResponseAfterRevalidation(response);
151     insertInLRUList(resource);
152     int delta = resource.size();
153     if (resource.decodedSize() && resource.hasClients())
154         insertInLiveDecodedResourcesList(resource);
155     if (delta)
156         adjustSize(resource.hasClients(), delta);
157     
158     revalidatingResource.switchClientsToRevalidatedResource();
159     ASSERT(!revalidatingResource.m_deleted);
160     // this deletes the revalidating resource
161     revalidatingResource.clearResourceToRevalidate();
162 }
163
164 void MemoryCache::revalidationFailed(CachedResource& revalidatingResource)
165 {
166     ASSERT(WTF::isMainThread());
167     LOG(ResourceLoading, "Revalidation failed for %p", &revalidatingResource);
168     ASSERT(revalidatingResource.resourceToRevalidate());
169     revalidatingResource.clearResourceToRevalidate();
170 }
171
172 CachedResource* MemoryCache::resourceForRequest(const ResourceRequest& request, SessionID sessionID)
173 {
174     auto* resources = sessionResourceMap(sessionID);
175     if (!resources)
176         return nullptr;
177     return resourceForRequestImpl(request, *resources);
178 }
179
180 CachedResource* MemoryCache::resourceForRequestImpl(const ResourceRequest& request, CachedResourceMap& resources)
181 {
182     ASSERT(WTF::isMainThread());
183     URL url = removeFragmentIdentifierIfNeeded(request.url());
184
185 #if ENABLE(CACHE_PARTITIONING)
186     auto key = std::make_pair(url, request.cachePartition());
187 #else
188     auto& key = url;
189 #endif
190     return resources.get(key);
191 }
192
193 unsigned MemoryCache::deadCapacity() const 
194 {
195     // Dead resource capacity is whatever space is not occupied by live resources, bounded by an independent minimum and maximum.
196     unsigned capacity = m_capacity - std::min(m_liveSize, m_capacity); // Start with available capacity.
197     capacity = std::max(capacity, m_minDeadCapacity); // Make sure it's above the minimum.
198     capacity = std::min(capacity, m_maxDeadCapacity); // Make sure it's below the maximum.
199     return capacity;
200 }
201
202 unsigned MemoryCache::liveCapacity() const 
203
204     // Live resource capacity is whatever is left over after calculating dead resource capacity.
205     return m_capacity - deadCapacity();
206 }
207
208 #if USE(CG)
209 // FIXME: Remove the USE(CG) once we either make NativeImagePtr a smart pointer on all platforms or
210 // remove the usage of CFRetain() in MemoryCache::addImageToCache() so as to make the code platform-independent.
211 static CachedImageClient& dummyCachedImageClient()
212 {
213     static NeverDestroyed<CachedImageClient> client;
214     return client;
215 }
216
217 bool MemoryCache::addImageToCache(NativeImagePtr image, const URL& url, const String& domainForCachePartition)
218 {
219     ASSERT(image);
220     SessionID sessionID = SessionID::defaultSessionID();
221     removeImageFromCache(url, domainForCachePartition); // Remove cache entry if it already exists.
222
223     RefPtr<BitmapImage> bitmapImage = BitmapImage::create(image, nullptr);
224     if (!bitmapImage)
225         return false;
226
227     std::unique_ptr<CachedImage> cachedImage = std::make_unique<CachedImage>(url, bitmapImage.get(), CachedImage::ManuallyCached, sessionID);
228
229     // Actual release of the CGImageRef is done in BitmapImage.
230     CFRetain(image);
231     cachedImage->addClient(&dummyCachedImageClient());
232     cachedImage->setDecodedSize(bitmapImage->decodedSize());
233 #if ENABLE(CACHE_PARTITIONING)
234     cachedImage->resourceRequest().setDomainForCachePartition(domainForCachePartition);
235 #endif
236     return add(*cachedImage.release());
237 }
238
239 void MemoryCache::removeImageFromCache(const URL& url, const String& domainForCachePartition)
240 {
241     auto* resources = sessionResourceMap(SessionID::defaultSessionID());
242     if (!resources)
243         return;
244
245 #if ENABLE(CACHE_PARTITIONING)
246     auto key = std::make_pair(url, ResourceRequest::partitionName(domainForCachePartition));
247 #else
248     UNUSED_PARAM(domainForCachePartition);
249     auto& key = url;
250 #endif
251     CachedResource* resource = resources->get(key);
252     if (!resource)
253         return;
254
255     // A resource exists and is not a manually cached image, so just remove it.
256     if (!is<CachedImage>(*resource) || !downcast<CachedImage>(*resource).isManuallyCached()) {
257         remove(*resource);
258         return;
259     }
260
261     // Removing the last client of a CachedImage turns the resource
262     // into a dead resource which will eventually be evicted when
263     // dead resources are pruned. That might be immediately since
264     // removing the last client triggers a MemoryCache::prune, so the
265     // resource may be deleted after this call.
266     downcast<CachedImage>(*resource).removeClient(&dummyCachedImageClient());
267 }
268 #endif
269
270 void MemoryCache::pruneLiveResources(bool shouldDestroyDecodedDataForAllLiveResources)
271 {
272     unsigned capacity = shouldDestroyDecodedDataForAllLiveResources ? 0 : liveCapacity();
273     if (capacity && m_liveSize <= capacity)
274         return;
275
276     unsigned targetSize = static_cast<unsigned>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again.
277
278     pruneLiveResourcesToSize(targetSize, shouldDestroyDecodedDataForAllLiveResources);
279 }
280
281 void MemoryCache::pruneLiveResourcesToSize(unsigned targetSize, bool shouldDestroyDecodedDataForAllLiveResources)
282 {
283     if (m_inPruneResources)
284         return;
285     TemporaryChange<bool> reentrancyProtector(m_inPruneResources, true);
286
287     double currentTime = FrameView::currentPaintTimeStamp();
288     if (!currentTime) // In case prune is called directly, outside of a Frame paint.
289         currentTime = monotonicallyIncreasingTime();
290     
291     // Destroy any decoded data in live objects that we can.
292     // Start from the head, since this is the least recently accessed of the objects.
293
294     // The list might not be sorted by the m_lastDecodedAccessTime. The impact
295     // of this weaker invariant is minor as the below if statement to check the
296     // elapsedTime will evaluate to false as the currentTime will be a lot
297     // greater than the current->m_lastDecodedAccessTime.
298     // For more details see: https://bugs.webkit.org/show_bug.cgi?id=30209
299     auto it = m_liveDecodedResources.begin();
300     while (it != m_liveDecodedResources.end()) {
301         auto* current = *it;
302
303         // Increment the iterator now because the call to destroyDecodedData() below
304         // may cause a call to ListHashSet::remove() and invalidate the current
305         // iterator. Note that this is safe because unlike iteration of most
306         // WTF Hash data structures, iteration is guaranteed safe against mutation
307         // of the ListHashSet, except for removal of the item currently pointed to
308         // by a given iterator.
309         ++it;
310
311         ASSERT(current->hasClients());
312         if (current->isLoaded() && current->decodedSize()) {
313             // Check to see if the remaining resources are too new to prune.
314             double elapsedTime = currentTime - current->m_lastDecodedAccessTime;
315             if (!shouldDestroyDecodedDataForAllLiveResources && elapsedTime < cMinDelayBeforeLiveDecodedPrune)
316                 return;
317
318             if (current->decodedDataIsPurgeable())
319                 continue;
320
321             // Destroy our decoded data. This will remove us from m_liveDecodedResources, and possibly move us
322             // to a different LRU list in m_allResources.
323             current->destroyDecodedData();
324
325             if (targetSize && m_liveSize <= targetSize)
326                 return;
327         }
328     }
329 }
330
331 void MemoryCache::pruneDeadResources()
332 {
333     unsigned capacity = deadCapacity();
334     if (capacity && m_deadSize <= capacity)
335         return;
336
337     unsigned targetSize = static_cast<unsigned>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again.
338     pruneDeadResourcesToSize(targetSize);
339 }
340
341 void MemoryCache::pruneDeadResourcesToSize(unsigned targetSize)
342 {
343     if (m_inPruneResources)
344         return;
345     TemporaryChange<bool> reentrancyProtector(m_inPruneResources, true);
346  
347     if (targetSize && m_deadSize <= targetSize)
348         return;
349
350     bool canShrinkLRULists = true;
351     for (int i = m_allResources.size() - 1; i >= 0; i--) {
352         LRUList& list = *m_allResources[i];
353
354         // First flush all the decoded data in this queue.
355         // Remove from the head, since this is the least frequently accessed of the objects.
356         auto it = list.begin();
357         while (it != list.end()) {
358             CachedResource& current = **it;
359
360             // Increment the iterator now as the call to destroyDecodedData() below may
361             // invalidate the current iterator.
362             ++it;
363
364             // Protect 'next' so it can't get deleted during destroyDecodedData().
365             CachedResourceHandle<CachedResource> next = it != list.end() ? *it : nullptr;
366             ASSERT(!next || next->inCache());
367             if (!current.hasClients() && !current.isPreloaded() && current.isLoaded()) {
368                 // Destroy our decoded data. This will remove us from 
369                 // m_liveDecodedResources, and possibly move us to a different 
370                 // LRU list in m_allResources.
371                 current.destroyDecodedData();
372
373                 if (targetSize && m_deadSize <= targetSize)
374                     return;
375             }
376             // Decoded data may reference other resources. Stop iterating if 'next' somehow got
377             // kicked out of cache during destroyDecodedData().
378             if (next && !next->inCache())
379                 break;
380         }
381
382         // Now evict objects from this list.
383         // Remove from the head, since this is the least frequently accessed of the objects.
384         it = list.begin();
385         while (it != list.end()) {
386             CachedResource& current = **it;
387
388             // Increment the iterator now as the call to remove() below will
389             // invalidate the current iterator.
390             ++it;
391
392             CachedResourceHandle<CachedResource> next = it != list.end() ? *it : nullptr;
393             ASSERT(!next || next->inCache());
394             if (!current.hasClients() && !current.isPreloaded() && !current.isCacheValidator()) {
395                 remove(current);
396                 if (targetSize && m_deadSize <= targetSize)
397                     return;
398             }
399             if (next && !next->inCache())
400                 break;
401         }
402             
403         // Shrink the vector back down so we don't waste time inspecting
404         // empty LRU lists on future prunes.
405         if (!m_allResources[i]->isEmpty())
406             canShrinkLRULists = false;
407         else if (canShrinkLRULists)
408             m_allResources.shrink(i);
409     }
410 }
411
412 void MemoryCache::setCapacities(unsigned minDeadBytes, unsigned maxDeadBytes, unsigned totalBytes)
413 {
414     ASSERT(minDeadBytes <= maxDeadBytes);
415     ASSERT(maxDeadBytes <= totalBytes);
416     m_minDeadCapacity = minDeadBytes;
417     m_maxDeadCapacity = maxDeadBytes;
418     m_capacity = totalBytes;
419     prune();
420 }
421
422 void MemoryCache::remove(CachedResource& resource)
423 {
424     ASSERT(WTF::isMainThread());
425     LOG(ResourceLoading, "Evicting resource %p for '%s' from cache", &resource, resource.url().string().latin1().data());
426     // The resource may have already been removed by someone other than our caller,
427     // who needed a fresh copy for a reload. See <http://bugs.webkit.org/show_bug.cgi?id=12479#c6>.
428     if (auto* resources = sessionResourceMap(resource.sessionID())) {
429 #if ENABLE(CACHE_PARTITIONING)
430         auto key = std::make_pair(resource.url(), resource.cachePartition());
431 #else
432         auto& key = resource.url();
433 #endif
434         if (resource.inCache()) {
435             // Remove resource from the resource map.
436             resources->remove(key);
437             resource.setInCache(false);
438
439             // If the resource map is now empty, remove it from m_sessionResources.
440             if (resources->isEmpty())
441                 m_sessionResources.remove(resource.sessionID());
442
443             // Remove from the appropriate LRU list.
444             removeFromLRUList(resource);
445             removeFromLiveDecodedResourcesList(resource);
446             adjustSize(resource.hasClients(), -static_cast<int>(resource.size()));
447         } else
448             ASSERT(resources->get(key) != &resource);
449     }
450
451     resource.deleteIfPossible();
452 }
453
454 auto MemoryCache::lruListFor(CachedResource& resource) -> LRUList&
455 {
456     unsigned accessCount = std::max(resource.accessCount(), 1U);
457     unsigned queueIndex = WTF::fastLog2(resource.size() / accessCount);
458 #ifndef NDEBUG
459     resource.m_lruIndex = queueIndex;
460 #endif
461
462     m_allResources.reserveCapacity(queueIndex + 1);
463     while (m_allResources.size() <= queueIndex)
464         m_allResources.uncheckedAppend(std::make_unique<LRUList>());
465     return *m_allResources[queueIndex];
466 }
467
468 void MemoryCache::removeFromLRUList(CachedResource& resource)
469 {
470     // If we've never been accessed, then we're brand new and not in any list.
471     if (!resource.accessCount())
472         return;
473
474 #if !ASSERT_DISABLED
475     unsigned oldListIndex = resource.m_lruIndex;
476 #endif
477
478     LRUList& list = lruListFor(resource);
479
480     // Verify that the list we got is the list we want.
481     ASSERT(resource.m_lruIndex == oldListIndex);
482
483     bool removed = list.remove(&resource);
484     ASSERT_UNUSED(removed, removed);
485 }
486
487 void MemoryCache::insertInLRUList(CachedResource& resource)
488 {
489     ASSERT(resource.inCache());
490     ASSERT(resource.accessCount() > 0);
491     
492     auto addResult = lruListFor(resource).add(&resource);
493     ASSERT_UNUSED(addResult, addResult.isNewEntry);
494 }
495
496 void MemoryCache::resourceAccessed(CachedResource& resource)
497 {
498     ASSERT(resource.inCache());
499     
500     // Need to make sure to remove before we increase the access count, since
501     // the queue will possibly change.
502     removeFromLRUList(resource);
503     
504     // If this is the first time the resource has been accessed, adjust the size of the cache to account for its initial size.
505     if (!resource.accessCount())
506         adjustSize(resource.hasClients(), resource.size());
507     
508     // Add to our access count.
509     resource.increaseAccessCount();
510     
511     // Now insert into the new queue.
512     insertInLRUList(resource);
513 }
514
515 void MemoryCache::removeResourcesWithOrigin(SecurityOrigin& origin)
516 {
517 #if ENABLE(CACHE_PARTITIONING)
518     String originPartition = ResourceRequest::partitionName(origin.host());
519 #endif
520
521     Vector<CachedResource*> resourcesWithOrigin;
522     for (auto& resources : m_sessionResources.values()) {
523         for (auto& keyValue : *resources) {
524             auto& resource = *keyValue.value;
525 #if ENABLE(CACHE_PARTITIONING)
526             auto& partitionName = keyValue.key.second;
527             if (partitionName == originPartition) {
528                 resourcesWithOrigin.append(&resource);
529                 continue;
530             }
531 #endif
532             RefPtr<SecurityOrigin> resourceOrigin = SecurityOrigin::create(resource.url());
533             if (resourceOrigin->equal(&origin))
534                 resourcesWithOrigin.append(&resource);
535         }
536     }
537
538     for (auto* resource : resourcesWithOrigin)
539         remove(*resource);
540 }
541
542 void MemoryCache::removeResourcesWithOrigins(SessionID sessionID, const HashSet<RefPtr<SecurityOrigin>>& origins)
543 {
544     auto* resourceMap = sessionResourceMap(sessionID);
545     if (!resourceMap)
546         return;
547
548 #if ENABLE(CACHE_PARTITIONING)
549     HashSet<String> originPartitions;
550
551     for (auto& origin : origins)
552         originPartitions.add(ResourceRequest::partitionName(origin->host()));
553 #endif
554
555     Vector<CachedResource*> resourcesToRemove;
556     for (auto& keyValuePair : *resourceMap) {
557         auto& resource = *keyValuePair.value;
558
559 #if ENABLE(CACHE_PARTITIONING)
560         auto& partitionName = keyValuePair.key.second;
561         if (originPartitions.contains(partitionName)) {
562             resourcesToRemove.append(&resource);
563             continue;
564         }
565 #endif
566
567         if (origins.contains(SecurityOrigin::create(resource.url()).ptr()))
568             resourcesToRemove.append(&resource);
569     }
570
571     for (auto& resource : resourcesToRemove)
572         remove(*resource);
573 }
574
575 void MemoryCache::getOriginsWithCache(SecurityOriginSet& origins)
576 {
577 #if ENABLE(CACHE_PARTITIONING)
578     static NeverDestroyed<String> httpString("http");
579 #endif
580     for (auto& resources : m_sessionResources.values()) {
581         for (auto& keyValue : *resources) {
582             auto& resource = *keyValue.value;
583 #if ENABLE(CACHE_PARTITIONING)
584             auto& partitionName = keyValue.key.second;
585             if (!partitionName.isEmpty())
586                 origins.add(SecurityOrigin::create(httpString, partitionName, 0));
587             else
588 #endif
589             origins.add(SecurityOrigin::create(resource.url()));
590         }
591     }
592 }
593
594 HashSet<RefPtr<SecurityOrigin>> MemoryCache::originsWithCache(SessionID sessionID) const
595 {
596     HashSet<RefPtr<SecurityOrigin>> origins;
597
598     auto it = m_sessionResources.find(sessionID);
599     if (it != m_sessionResources.end()) {
600         for (auto& keyValue : *it->value) {
601             auto& resource = *keyValue.value;
602 #if ENABLE(CACHE_PARTITIONING)
603             auto& partitionName = keyValue.key.second;
604             if (!partitionName.isEmpty())
605                 origins.add(SecurityOrigin::create("http", partitionName, 0));
606             else
607 #endif
608             origins.add(SecurityOrigin::create(resource.url()));
609         }
610     }
611
612     return origins;
613 }
614
615 void MemoryCache::removeFromLiveDecodedResourcesList(CachedResource& resource)
616 {
617     m_liveDecodedResources.remove(&resource);
618 }
619
620 void MemoryCache::insertInLiveDecodedResourcesList(CachedResource& resource)
621 {
622     // Make sure we aren't in the list already.
623     ASSERT(!m_liveDecodedResources.contains(&resource));
624     m_liveDecodedResources.add(&resource);
625 }
626
627 void MemoryCache::addToLiveResourcesSize(CachedResource& resource)
628 {
629     m_liveSize += resource.size();
630     m_deadSize -= resource.size();
631 }
632
633 void MemoryCache::removeFromLiveResourcesSize(CachedResource& resource)
634 {
635     m_liveSize -= resource.size();
636     m_deadSize += resource.size();
637 }
638
639 void MemoryCache::adjustSize(bool live, int delta)
640 {
641     if (live) {
642         ASSERT(delta >= 0 || ((int)m_liveSize + delta >= 0));
643         m_liveSize += delta;
644     } else {
645         ASSERT(delta >= 0 || ((int)m_deadSize + delta >= 0));
646         m_deadSize += delta;
647     }
648 }
649
650 void MemoryCache::removeRequestFromSessionCaches(ScriptExecutionContext& context, const ResourceRequest& request)
651 {
652     if (is<WorkerGlobalScope>(context)) {
653         CrossThreadResourceRequestData* requestData = request.copyData().release();
654         downcast<WorkerGlobalScope>(context).thread().workerLoaderProxy().postTaskToLoader([requestData] (ScriptExecutionContext& context) {
655             auto request(ResourceRequest::adopt(std::unique_ptr<CrossThreadResourceRequestData>(requestData)));
656             MemoryCache::removeRequestFromSessionCaches(context, *request);
657         });
658         return;
659     }
660
661     auto& memoryCache = MemoryCache::singleton();
662     for (auto& resources : memoryCache.m_sessionResources) {
663         if (CachedResource* resource = memoryCache.resourceForRequestImpl(request, *resources.value))
664             memoryCache.remove(*resource);
665     }
666 }
667
668 void MemoryCache::TypeStatistic::addResource(CachedResource& resource)
669 {
670     count++;
671     size += resource.size();
672     liveSize += resource.hasClients() ? resource.size() : 0;
673     decodedSize += resource.decodedSize();
674 }
675
676 MemoryCache::Statistics MemoryCache::getStatistics()
677 {
678     Statistics stats;
679
680     for (auto& resources : m_sessionResources.values()) {
681         for (auto* resource : resources->values()) {
682             switch (resource->type()) {
683             case CachedResource::ImageResource:
684                 stats.images.addResource(*resource);
685                 break;
686             case CachedResource::CSSStyleSheet:
687                 stats.cssStyleSheets.addResource(*resource);
688                 break;
689             case CachedResource::Script:
690                 stats.scripts.addResource(*resource);
691                 break;
692 #if ENABLE(XSLT)
693             case CachedResource::XSLStyleSheet:
694                 stats.xslStyleSheets.addResource(*resource);
695                 break;
696 #endif
697 #if ENABLE(SVG_FONTS)
698             case CachedResource::SVGFontResource:
699 #endif
700             case CachedResource::FontResource:
701                 stats.fonts.addResource(*resource);
702                 break;
703             default:
704                 break;
705             }
706         }
707     }
708     return stats;
709 }
710
711 void MemoryCache::setDisabled(bool disabled)
712 {
713     m_disabled = disabled;
714     if (!m_disabled)
715         return;
716
717     while (!m_sessionResources.isEmpty()) {
718         auto& resources = *m_sessionResources.begin()->value;
719         ASSERT(!resources.isEmpty());
720         remove(*resources.begin()->value);
721     }
722 }
723
724 void MemoryCache::evictResources()
725 {
726     if (disabled())
727         return;
728
729     setDisabled(true);
730     setDisabled(false);
731 }
732
733 void MemoryCache::evictResources(SessionID sessionID)
734 {
735     if (disabled())
736         return;
737
738     auto it = m_sessionResources.find(sessionID);
739     if (it == m_sessionResources.end())
740         return;
741     auto& resources = *it->value;
742
743     for (int i = 0, size = resources.size(); i < size; ++i)
744         remove(*resources.begin()->value);
745
746     ASSERT(!m_sessionResources.contains(sessionID));
747 }
748
749 bool MemoryCache::needsPruning() const
750 {
751     return m_liveSize + m_deadSize > m_capacity || m_deadSize > m_maxDeadCapacity;
752 }
753
754 void MemoryCache::prune()
755 {
756     if (!needsPruning())
757         return;
758         
759     pruneDeadResources(); // Prune dead first, in case it was "borrowing" capacity from live.
760     pruneLiveResources();
761 }
762
763 void MemoryCache::pruneTimerFired()
764 {
765     prune();
766 }
767
768 void MemoryCache::pruneSoon()
769 {
770      if (m_pruneTimer.isActive())
771         return;
772      if (!needsPruning())
773          return;
774      m_pruneTimer.startOneShot(0);
775 }
776
777 #ifndef NDEBUG
778 void MemoryCache::dumpStats()
779 {
780     Statistics s = getStatistics();
781     printf("%-13s %-13s %-13s %-13s %-13s\n", "", "Count", "Size", "LiveSize", "DecodedSize");
782     printf("%-13s %-13s %-13s %-13s %-13s\n", "-------------", "-------------", "-------------", "-------------", "-------------");
783     printf("%-13s %13d %13d %13d %13d\n", "Images", s.images.count, s.images.size, s.images.liveSize, s.images.decodedSize);
784     printf("%-13s %13d %13d %13d %13d\n", "CSS", s.cssStyleSheets.count, s.cssStyleSheets.size, s.cssStyleSheets.liveSize, s.cssStyleSheets.decodedSize);
785 #if ENABLE(XSLT)
786     printf("%-13s %13d %13d %13d %13d\n", "XSL", s.xslStyleSheets.count, s.xslStyleSheets.size, s.xslStyleSheets.liveSize, s.xslStyleSheets.decodedSize);
787 #endif
788     printf("%-13s %13d %13d %13d %13d\n", "JavaScript", s.scripts.count, s.scripts.size, s.scripts.liveSize, s.scripts.decodedSize);
789     printf("%-13s %13d %13d %13d %13d\n", "Fonts", s.fonts.count, s.fonts.size, s.fonts.liveSize, s.fonts.decodedSize);
790     printf("%-13s %-13s %-13s %-13s %-13s\n\n", "-------------", "-------------", "-------------", "-------------", "-------------");
791 }
792
793 void MemoryCache::dumpLRULists(bool includeLive) const
794 {
795     printf("LRU-SP lists in eviction order (Kilobytes decoded, Kilobytes encoded, Access count, Referenced):\n");
796
797     int size = m_allResources.size();
798     for (int i = size - 1; i >= 0; i--) {
799         printf("\n\nList %d: ", i);
800         for (auto* resource : *m_allResources[i]) {
801             if (includeLive || !resource->hasClients())
802                 printf("(%.1fK, %.1fK, %uA, %dR); ", resource->decodedSize() / 1024.0f, (resource->encodedSize() + resource->overheadSize()) / 1024.0f, resource->accessCount(), resource->hasClients());
803         }
804     }
805 }
806 #endif
807
808 } // namespace WebCore