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 "core/fetch/MemoryCache.h"
25
26#include "core/dom/CrossThreadTask.h"
27#include "core/fetch/ResourcePtr.h"
28#include "core/frame/FrameView.h"
29#include "core/workers/WorkerGlobalScope.h"
30#include "core/workers/WorkerLoaderProxy.h"
31#include "core/workers/WorkerThread.h"
32#include "platform/Logging.h"
33#include "platform/TraceEvent.h"
34#include "platform/weborigin/SecurityOrigin.h"
35#include "platform/weborigin/SecurityOriginHash.h"
36#include "public/platform/Platform.h"
37#include "wtf/Assertions.h"
38#include "wtf/CurrentTime.h"
39#include "wtf/MathExtras.h"
40#include "wtf/TemporaryChange.h"
41#include "wtf/text/CString.h"
42
43namespace blink {
44
45static OwnPtrWillBePersistent<MemoryCache>* gMemoryCache;
46
47static const unsigned cDefaultCacheCapacity = 8192 * 1024;
48static const unsigned cDeferredPruneDeadCapacityFactor = 2;
49static const int cMinDelayBeforeLiveDecodedPrune = 1; // Seconds.
50static const double cMaxPruneDeferralDelay = 0.5; // Seconds.
51static const float cTargetPrunePercentage = .95f; // Percentage of capacity toward which we prune, to avoid immediately pruning again.
52
53MemoryCache* memoryCache()
54{
55    ASSERT(WTF::isMainThread());
56    if (!gMemoryCache)
57        gMemoryCache = new OwnPtrWillBePersistent<MemoryCache>(MemoryCache::create());
58    return gMemoryCache->get();
59}
60
61PassOwnPtrWillBeRawPtr<MemoryCache> replaceMemoryCacheForTesting(PassOwnPtrWillBeRawPtr<MemoryCache> cache)
62{
63#if ENABLE(OILPAN)
64    // Move m_liveResources content to keep Resource objects alive.
65    for (HeapHashSet<Member<Resource> >::iterator i = memoryCache()->m_liveResources.begin();
66        i != memoryCache()->m_liveResources.end();
67        ++i) {
68        cache->m_liveResources.add(*i);
69    }
70    memoryCache()->m_liveResources.clear();
71#else
72    // Make sure we have non-empty gMemoryCache.
73    memoryCache();
74#endif
75    OwnPtrWillBeRawPtr<MemoryCache> oldCache = gMemoryCache->release();
76    *gMemoryCache = cache;
77    return oldCache.release();
78}
79
80void MemoryCacheEntry::trace(Visitor* visitor)
81{
82    visitor->trace(m_previousInLiveResourcesList);
83    visitor->trace(m_nextInLiveResourcesList);
84    visitor->trace(m_previousInAllResourcesList);
85    visitor->trace(m_nextInAllResourcesList);
86}
87
88void MemoryCacheLRUList::trace(Visitor* visitor)
89{
90    visitor->trace(m_head);
91    visitor->trace(m_tail);
92}
93
94inline MemoryCache::MemoryCache()
95    : m_inPruneResources(false)
96    , m_prunePending(false)
97    , m_maxPruneDeferralDelay(cMaxPruneDeferralDelay)
98    , m_capacity(cDefaultCacheCapacity)
99    , m_minDeadCapacity(0)
100    , m_maxDeadCapacity(cDefaultCacheCapacity)
101    , m_maxDeferredPruneDeadCapacity(cDeferredPruneDeadCapacityFactor * cDefaultCacheCapacity)
102    , m_delayBeforeLiveDecodedPrune(cMinDelayBeforeLiveDecodedPrune)
103    , m_liveSize(0)
104    , m_deadSize(0)
105#ifdef MEMORY_CACHE_STATS
106    , m_statsTimer(this, &MemoryCache::dumpStats)
107#endif
108{
109#ifdef MEMORY_CACHE_STATS
110    const double statsIntervalInSeconds = 15;
111    m_statsTimer.startRepeating(statsIntervalInSeconds, FROM_HERE);
112#endif
113    m_pruneTimeStamp = m_pruneFrameTimeStamp = FrameView::currentFrameTimeStamp();
114}
115
116PassOwnPtrWillBeRawPtr<MemoryCache> MemoryCache::create()
117{
118    return adoptPtrWillBeNoop(new MemoryCache());
119}
120
121MemoryCache::~MemoryCache()
122{
123    if (m_prunePending)
124        blink::Platform::current()->currentThread()->removeTaskObserver(this);
125}
126
127void MemoryCache::trace(Visitor* visitor)
128{
129#if ENABLE(OILPAN)
130    visitor->trace(m_allResources);
131    for (size_t i = 0; i < WTF_ARRAY_LENGTH(m_liveDecodedResources); ++i)
132        visitor->trace(m_liveDecodedResources[i]);
133    visitor->trace(m_resources);
134    visitor->trace(m_liveResources);
135#endif
136}
137
138KURL MemoryCache::removeFragmentIdentifierIfNeeded(const KURL& originalURL)
139{
140    if (!originalURL.hasFragmentIdentifier())
141        return originalURL;
142    // Strip away fragment identifier from HTTP URLs.
143    // Data URLs must be unmodified. For file and custom URLs clients may expect resources
144    // to be unique even when they differ by the fragment identifier only.
145    if (!originalURL.protocolIsInHTTPFamily())
146        return originalURL;
147    KURL url = originalURL;
148    url.removeFragmentIdentifier();
149    return url;
150}
151
152void MemoryCache::add(Resource* resource)
153{
154    ASSERT(WTF::isMainThread());
155    ASSERT(resource->url().isValid());
156    RELEASE_ASSERT(!m_resources.contains(resource->url()));
157    m_resources.set(resource->url().string(), MemoryCacheEntry::create(resource));
158    update(resource, 0, resource->size(), true);
159
160    WTF_LOG(ResourceLoading, "MemoryCache::add Added '%s', resource %p\n", resource->url().string().latin1().data(), resource);
161}
162
163void MemoryCache::replace(Resource* newResource, Resource* oldResource)
164{
165    if (MemoryCacheEntry* oldEntry = m_resources.get(oldResource->url()))
166        evict(oldEntry);
167    add(newResource);
168    if (newResource->decodedSize() && newResource->hasClients())
169        insertInLiveDecodedResourcesList(m_resources.get(newResource->url()));
170}
171
172void MemoryCache::remove(Resource* resource)
173{
174    // The resource may have already been removed by someone other than our caller,
175    // who needed a fresh copy for a reload.
176    if (!contains(resource))
177        return;
178    evict(m_resources.get(resource->url()));
179}
180
181bool MemoryCache::contains(const Resource* resource) const
182{
183    if (resource->url().isNull())
184        return false;
185    const MemoryCacheEntry* entry = m_resources.get(resource->url());
186    return entry && entry->m_resource == resource;
187}
188
189Resource* MemoryCache::resourceForURL(const KURL& resourceURL)
190{
191    ASSERT(WTF::isMainThread());
192    KURL url = removeFragmentIdentifierIfNeeded(resourceURL);
193    MemoryCacheEntry* entry = m_resources.get(url);
194    if (!entry)
195        return 0;
196    Resource* resource = entry->m_resource.get();
197    if (resource && !resource->lock()) {
198        ASSERT(!resource->hasClients());
199        bool didEvict = evict(entry);
200        ASSERT_UNUSED(didEvict, didEvict);
201        return 0;
202    }
203    return resource;
204}
205
206size_t MemoryCache::deadCapacity() const
207{
208    // Dead resource capacity is whatever space is not occupied by live resources, bounded by an independent minimum and maximum.
209    size_t capacity = m_capacity - std::min(m_liveSize, m_capacity); // Start with available capacity.
210    capacity = std::max(capacity, m_minDeadCapacity); // Make sure it's above the minimum.
211    capacity = std::min(capacity, m_maxDeadCapacity); // Make sure it's below the maximum.
212    return capacity;
213}
214
215size_t MemoryCache::liveCapacity() const
216{
217    // Live resource capacity is whatever is left over after calculating dead resource capacity.
218    return m_capacity - deadCapacity();
219}
220
221void MemoryCache::pruneLiveResources()
222{
223    ASSERT(!m_prunePending);
224    size_t capacity = liveCapacity();
225    if (!m_liveSize || (capacity && m_liveSize <= capacity))
226        return;
227
228    size_t targetSize = static_cast<size_t>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again.
229
230    // Destroy any decoded data in live objects that we can.
231    // Start from the tail, since this is the lowest priority
232    // and least recently accessed of the objects.
233
234    // The list might not be sorted by the m_lastDecodedFrameTimeStamp. The impact
235    // of this weaker invariant is minor as the below if statement to check the
236    // elapsedTime will evaluate to false as the current time will be a lot
237    // greater than the current->m_lastDecodedFrameTimeStamp.
238    // For more details see: https://bugs.webkit.org/show_bug.cgi?id=30209
239
240    // Start pruning from the lowest priority list.
241    for (int priority = MemoryCacheLiveResourcePriorityLow; priority <= MemoryCacheLiveResourcePriorityHigh; ++priority) {
242        MemoryCacheEntry* current = m_liveDecodedResources[priority].m_tail;
243        while (current) {
244            MemoryCacheEntry* previous = current->m_previousInLiveResourcesList;
245            ASSERT(current->m_resource->hasClients());
246            if (current->m_resource->isLoaded() && current->m_resource->decodedSize()) {
247                // Check to see if the remaining resources are too new to prune.
248                double elapsedTime = m_pruneFrameTimeStamp - current->m_lastDecodedAccessTime;
249                if (elapsedTime < m_delayBeforeLiveDecodedPrune)
250                    return;
251
252                // Destroy our decoded data if possible. This will remove us
253                // from m_liveDecodedResources, and possibly move us to a
254                // different LRU list in m_allResources.
255                current->m_resource->prune();
256
257                if (targetSize && m_liveSize <= targetSize)
258                    return;
259            }
260            current = previous;
261        }
262    }
263}
264
265void MemoryCache::pruneDeadResources()
266{
267    size_t capacity = deadCapacity();
268    if (!m_deadSize || (capacity && m_deadSize <= capacity))
269        return;
270
271    size_t targetSize = static_cast<size_t>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again.
272
273    int size = m_allResources.size();
274
275    // See if we have any purged resources we can evict.
276    for (int i = 0; i < size; i++) {
277        MemoryCacheEntry* current = m_allResources[i].m_tail;
278        while (current) {
279            MemoryCacheEntry* previous = current->m_previousInAllResourcesList;
280            // Main Resources in the cache are only substitue data that was
281            // precached and should not be evicted.
282            if (current->m_resource->wasPurged() && current->m_resource->canDelete()
283                && current->m_resource->type() != Resource::MainResource) {
284                ASSERT(!current->m_resource->hasClients());
285                ASSERT(!current->m_resource->isPreloaded());
286                bool wasEvicted = evict(current);
287                ASSERT_UNUSED(wasEvicted, wasEvicted);
288            }
289            current = previous;
290        }
291    }
292    if (targetSize && m_deadSize <= targetSize)
293        return;
294
295    bool canShrinkLRULists = true;
296    for (int i = size - 1; i >= 0; i--) {
297        // Remove from the tail, since this is the least frequently accessed of the objects.
298        MemoryCacheEntry* current = m_allResources[i].m_tail;
299
300        // First flush all the decoded data in this queue.
301        while (current) {
302            // Protect 'previous' so it can't get deleted during destroyDecodedData().
303            MemoryCacheEntry* previous = current->m_previousInAllResourcesList;
304            ASSERT(!previous || contains(previous->m_resource.get()));
305            if (!current->m_resource->hasClients() && !current->m_resource->isPreloaded() && current->m_resource->isLoaded()) {
306                // Destroy our decoded data. This will remove us from
307                // m_liveDecodedResources, and possibly move us to a different
308                // LRU list in m_allResources.
309                current->m_resource->prune();
310
311                if (targetSize && m_deadSize <= targetSize)
312                    return;
313            }
314            // Decoded data may reference other resources. Stop iterating if 'previous' somehow got
315            // kicked out of cache during destroyDecodedData().
316            if (previous && !contains(previous->m_resource.get()))
317                break;
318            current = previous;
319        }
320
321        // Now evict objects from this queue.
322        current = m_allResources[i].m_tail;
323        while (current) {
324            MemoryCacheEntry* previous = current->m_previousInAllResourcesList;
325            ASSERT(!previous || contains(previous->m_resource.get()));
326            if (!current->m_resource->hasClients() && !current->m_resource->isPreloaded()
327                && !current->m_resource->isCacheValidator() && current->m_resource->canDelete()
328                && current->m_resource->type() != Resource::MainResource) {
329                // Main Resources in the cache are only substitue data that was
330                // precached and should not be evicted.
331                bool wasEvicted = evict(current);
332                ASSERT_UNUSED(wasEvicted, wasEvicted);
333                if (targetSize && m_deadSize <= targetSize)
334                    return;
335            }
336            if (previous && !contains(previous->m_resource.get()))
337                break;
338            current = previous;
339        }
340
341        // Shrink the vector back down so we don't waste time inspecting
342        // empty LRU lists on future prunes.
343        if (m_allResources[i].m_head)
344            canShrinkLRULists = false;
345        else if (canShrinkLRULists)
346            m_allResources.resize(i);
347    }
348}
349
350void MemoryCache::setCapacities(size_t minDeadBytes, size_t maxDeadBytes, size_t totalBytes)
351{
352    ASSERT(minDeadBytes <= maxDeadBytes);
353    ASSERT(maxDeadBytes <= totalBytes);
354    m_minDeadCapacity = minDeadBytes;
355    m_maxDeadCapacity = maxDeadBytes;
356    m_maxDeferredPruneDeadCapacity = cDeferredPruneDeadCapacityFactor * maxDeadBytes;
357    m_capacity = totalBytes;
358    prune();
359}
360
361bool MemoryCache::evict(MemoryCacheEntry* entry)
362{
363    ASSERT(WTF::isMainThread());
364
365    Resource* resource = entry->m_resource.get();
366    bool canDelete = resource->canDelete();
367    WTF_LOG(ResourceLoading, "Evicting resource %p for '%s' from cache", resource, resource->url().string().latin1().data());
368    // The resource may have already been removed by someone other than our caller,
369    // who needed a fresh copy for a reload. See <http://bugs.webkit.org/show_bug.cgi?id=12479#c6>.
370    update(resource, resource->size(), 0, false);
371    removeFromLiveDecodedResourcesList(entry);
372
373    ResourceMap::iterator it = m_resources.find(resource->url());
374    ASSERT(it != m_resources.end());
375#if !ENABLE(OILPAN)
376    OwnPtr<MemoryCacheEntry> entryPtr;
377    entryPtr.swap(it->value);
378#endif
379    m_resources.remove(it);
380    return canDelete;
381}
382
383MemoryCacheLRUList* MemoryCache::lruListFor(unsigned accessCount, size_t size)
384{
385    ASSERT(accessCount > 0);
386    unsigned queueIndex = WTF::fastLog2(size / accessCount);
387    if (m_allResources.size() <= queueIndex)
388        m_allResources.grow(queueIndex + 1);
389    return &m_allResources[queueIndex];
390}
391
392void MemoryCache::removeFromLRUList(MemoryCacheEntry* entry, MemoryCacheLRUList* list)
393{
394#if ENABLE(ASSERT)
395    // Verify that we are in fact in this list.
396    bool found = false;
397    for (MemoryCacheEntry* current = list->m_head; current; current = current->m_nextInAllResourcesList) {
398        if (current == entry) {
399            found = true;
400            break;
401        }
402    }
403    ASSERT(found);
404#endif
405
406    MemoryCacheEntry* next = entry->m_nextInAllResourcesList;
407    MemoryCacheEntry* previous = entry->m_previousInAllResourcesList;
408    entry->m_nextInAllResourcesList = nullptr;
409    entry->m_previousInAllResourcesList = nullptr;
410
411    if (next)
412        next->m_previousInAllResourcesList = previous;
413    else
414        list->m_tail = previous;
415
416    if (previous)
417        previous->m_nextInAllResourcesList = next;
418    else
419        list->m_head = next;
420}
421
422void MemoryCache::insertInLRUList(MemoryCacheEntry* entry, MemoryCacheLRUList* list)
423{
424    ASSERT(!entry->m_nextInAllResourcesList && !entry->m_previousInAllResourcesList);
425
426    entry->m_nextInAllResourcesList = list->m_head;
427    list->m_head = entry;
428
429    if (entry->m_nextInAllResourcesList)
430        entry->m_nextInAllResourcesList->m_previousInAllResourcesList = entry;
431    else
432        list->m_tail = entry;
433
434#if ENABLE(ASSERT)
435    // Verify that we are in now in the list like we should be.
436    bool found = false;
437    for (MemoryCacheEntry* current = list->m_head; current; current = current->m_nextInAllResourcesList) {
438        if (current == entry) {
439            found = true;
440            break;
441        }
442    }
443    ASSERT(found);
444#endif
445}
446
447void MemoryCache::removeFromLiveDecodedResourcesList(MemoryCacheEntry* entry)
448{
449    // If we've never been accessed, then we're brand new and not in any list.
450    if (!entry->m_inLiveDecodedResourcesList)
451        return;
452    entry->m_inLiveDecodedResourcesList = false;
453
454    MemoryCacheLRUList* list = &m_liveDecodedResources[entry->m_liveResourcePriority];
455
456#if ENABLE(ASSERT)
457    // Verify that we are in fact in this list.
458    bool found = false;
459    for (MemoryCacheEntry* current = list->m_head; current; current = current->m_nextInLiveResourcesList) {
460        if (current == entry) {
461            found = true;
462            break;
463        }
464    }
465    ASSERT(found);
466#endif
467
468    MemoryCacheEntry* next = entry->m_nextInLiveResourcesList;
469    MemoryCacheEntry* previous = entry->m_previousInLiveResourcesList;
470
471    entry->m_nextInLiveResourcesList = nullptr;
472    entry->m_previousInLiveResourcesList = nullptr;
473
474    if (next)
475        next->m_previousInLiveResourcesList = previous;
476    else
477        list->m_tail = previous;
478
479    if (previous)
480        previous->m_nextInLiveResourcesList = next;
481    else
482        list->m_head = next;
483}
484
485void MemoryCache::insertInLiveDecodedResourcesList(MemoryCacheEntry* entry)
486{
487    // Make sure we aren't in the list already.
488    ASSERT(!entry->m_nextInLiveResourcesList && !entry->m_previousInLiveResourcesList && !entry->m_inLiveDecodedResourcesList);
489    entry->m_inLiveDecodedResourcesList = true;
490
491    MemoryCacheLRUList* list = &m_liveDecodedResources[entry->m_liveResourcePriority];
492    entry->m_nextInLiveResourcesList = list->m_head;
493    if (list->m_head)
494        list->m_head->m_previousInLiveResourcesList = entry;
495    list->m_head = entry;
496
497    if (!entry->m_nextInLiveResourcesList)
498        list->m_tail = entry;
499
500#if ENABLE(ASSERT)
501    // Verify that we are in now in the list like we should be.
502    bool found = false;
503    for (MemoryCacheEntry* current = list->m_head; current; current = current->m_nextInLiveResourcesList) {
504        if (current == entry) {
505            found = true;
506            break;
507        }
508    }
509    ASSERT(found);
510#endif
511}
512
513void MemoryCache::makeLive(Resource* resource)
514{
515    if (!contains(resource))
516        return;
517    ASSERT(m_deadSize >= resource->size());
518    m_liveSize += resource->size();
519    m_deadSize -= resource->size();
520}
521
522void MemoryCache::makeDead(Resource* resource)
523{
524    if (!contains(resource))
525        return;
526    m_liveSize -= resource->size();
527    m_deadSize += resource->size();
528    removeFromLiveDecodedResourcesList(m_resources.get(resource->url()));
529}
530
531void MemoryCache::update(Resource* resource, size_t oldSize, size_t newSize, bool wasAccessed)
532{
533    if (!contains(resource))
534        return;
535    MemoryCacheEntry* entry = m_resources.get(resource->url());
536
537    // The object must now be moved to a different queue, since either its size or its accessCount has been changed,
538    // and both of those are used to determine which LRU queue the resource should be in.
539    if (oldSize)
540        removeFromLRUList(entry, lruListFor(entry->m_accessCount, oldSize));
541    if (wasAccessed)
542        entry->m_accessCount++;
543    if (newSize)
544        insertInLRUList(entry, lruListFor(entry->m_accessCount, newSize));
545
546    ptrdiff_t delta = newSize - oldSize;
547    if (resource->hasClients()) {
548        ASSERT(delta >= 0 || m_liveSize >= static_cast<size_t>(-delta) );
549        m_liveSize += delta;
550    } else {
551        ASSERT(delta >= 0 || m_deadSize >= static_cast<size_t>(-delta) );
552        m_deadSize += delta;
553    }
554}
555
556void MemoryCache::updateDecodedResource(Resource* resource, UpdateReason reason, MemoryCacheLiveResourcePriority priority)
557{
558    if (!contains(resource))
559        return;
560    MemoryCacheEntry* entry = m_resources.get(resource->url());
561
562    removeFromLiveDecodedResourcesList(entry);
563    if (priority != MemoryCacheLiveResourcePriorityUnknown && priority != entry->m_liveResourcePriority)
564        entry->m_liveResourcePriority = priority;
565    if (resource->decodedSize() && resource->hasClients())
566        insertInLiveDecodedResourcesList(entry);
567
568    if (reason != UpdateForAccess)
569        return;
570
571    double timestamp = resource->isImage() ? FrameView::currentFrameTimeStamp() : 0.0;
572    if (!timestamp)
573        timestamp = currentTime();
574    entry->m_lastDecodedAccessTime = timestamp;
575}
576
577MemoryCacheLiveResourcePriority MemoryCache::priority(Resource* resource) const
578{
579    if (!contains(resource))
580        return MemoryCacheLiveResourcePriorityUnknown;
581    MemoryCacheEntry* entry = m_resources.get(resource->url());
582    return entry->m_liveResourcePriority;
583}
584
585void MemoryCache::removeURLFromCache(ExecutionContext* context, const KURL& url)
586{
587    if (context->isWorkerGlobalScope()) {
588        WorkerGlobalScope* workerGlobalScope = toWorkerGlobalScope(context);
589        workerGlobalScope->thread()->workerLoaderProxy().postTaskToLoader(createCrossThreadTask(&removeURLFromCacheInternal, url));
590        return;
591    }
592    removeURLFromCacheInternal(context, url);
593}
594
595void MemoryCache::removeURLFromCacheInternal(ExecutionContext*, const KURL& url)
596{
597    if (Resource* resource = memoryCache()->resourceForURL(url))
598        memoryCache()->remove(resource);
599}
600
601void MemoryCache::TypeStatistic::addResource(Resource* o)
602{
603    bool purged = o->wasPurged();
604    bool purgeable = o->isPurgeable() && !purged;
605    size_t pageSize = (o->encodedSize() + o->overheadSize() + 4095) & ~4095;
606    count++;
607    size += purged ? 0 : o->size();
608    liveSize += o->hasClients() ? o->size() : 0;
609    decodedSize += o->decodedSize();
610    encodedSize += o->encodedSize();
611    encodedSizeDuplicatedInDataURLs += o->url().protocolIsData() ? o->encodedSize() : 0;
612    purgeableSize += purgeable ? pageSize : 0;
613    purgedSize += purged ? pageSize : 0;
614}
615
616MemoryCache::Statistics MemoryCache::getStatistics()
617{
618    Statistics stats;
619    ResourceMap::iterator e = m_resources.end();
620    for (ResourceMap::iterator i = m_resources.begin(); i != e; ++i) {
621        Resource* resource = i->value->m_resource.get();
622        switch (resource->type()) {
623        case Resource::Image:
624            stats.images.addResource(resource);
625            break;
626        case Resource::CSSStyleSheet:
627            stats.cssStyleSheets.addResource(resource);
628            break;
629        case Resource::Script:
630            stats.scripts.addResource(resource);
631            break;
632        case Resource::XSLStyleSheet:
633            stats.xslStyleSheets.addResource(resource);
634            break;
635        case Resource::Font:
636            stats.fonts.addResource(resource);
637            break;
638        default:
639            stats.other.addResource(resource);
640            break;
641        }
642    }
643    return stats;
644}
645
646void MemoryCache::evictResources()
647{
648    for (;;) {
649        ResourceMap::iterator i = m_resources.begin();
650        if (i == m_resources.end())
651            break;
652        evict(i->value.get());
653    }
654}
655
656void MemoryCache::prune(Resource* justReleasedResource)
657{
658    TRACE_EVENT0("renderer", "MemoryCache::prune()");
659
660    if (m_inPruneResources)
661        return;
662    if (m_liveSize + m_deadSize <= m_capacity && m_maxDeadCapacity && m_deadSize <= m_maxDeadCapacity) // Fast path.
663        return;
664
665    // To avoid burdening the current thread with repetitive pruning jobs,
666    // pruning is postponed until the end of the current task. If it has
667    // been more than m_maxPruneDeferralDelay since the last prune,
668    // then we prune immediately.
669    // If the current thread's run loop is not active, then pruning will happen
670    // immediately only if it has been over m_maxPruneDeferralDelay
671    // since the last prune.
672    double currentTime = WTF::currentTime();
673    if (m_prunePending) {
674        if (currentTime - m_pruneTimeStamp >= m_maxPruneDeferralDelay) {
675            pruneNow(currentTime);
676        }
677    } else {
678        if (currentTime - m_pruneTimeStamp >= m_maxPruneDeferralDelay) {
679            pruneNow(currentTime); // Delay exceeded, prune now.
680        } else {
681            // Defer.
682            blink::Platform::current()->currentThread()->addTaskObserver(this);
683            m_prunePending = true;
684        }
685    }
686
687    if (m_prunePending && m_deadSize > m_maxDeferredPruneDeadCapacity && justReleasedResource) {
688        // The following eviction does not respect LRU order, but it can be done
689        // immediately in constant time, as opposed to pruneDeadResources, which
690        // we would rather defer because it is O(N), which would make tear-down of N
691        // objects O(N^2) if we pruned immediately. This immediate eviction is a
692        // safeguard against runaway memory consumption by dead resources
693        // while a prune is pending.
694        // Main Resources in the cache are only substitue data that was
695        // precached and should not be evicted.
696        if (contains(justReleasedResource) && justReleasedResource->type() != Resource::MainResource)
697            evict(m_resources.get(justReleasedResource->url()));
698
699        // As a last resort, prune immediately
700        if (m_deadSize > m_maxDeferredPruneDeadCapacity)
701            pruneNow(currentTime);
702    }
703}
704
705void MemoryCache::willProcessTask()
706{
707}
708
709void MemoryCache::didProcessTask()
710{
711    // Perform deferred pruning
712    ASSERT(m_prunePending);
713    pruneNow(WTF::currentTime());
714}
715
716void MemoryCache::pruneNow(double currentTime)
717{
718    if (m_prunePending) {
719        m_prunePending = false;
720        blink::Platform::current()->currentThread()->removeTaskObserver(this);
721    }
722
723    TemporaryChange<bool> reentrancyProtector(m_inPruneResources, true);
724    pruneDeadResources(); // Prune dead first, in case it was "borrowing" capacity from live.
725    pruneLiveResources();
726    m_pruneFrameTimeStamp = FrameView::currentFrameTimeStamp();
727    m_pruneTimeStamp = currentTime;
728}
729
730#if ENABLE(OILPAN)
731void MemoryCache::registerLiveResource(Resource& resource)
732{
733    ASSERT(!m_liveResources.contains(&resource));
734    m_liveResources.add(&resource);
735}
736
737void MemoryCache::unregisterLiveResource(Resource& resource)
738{
739    ASSERT(m_liveResources.contains(&resource));
740    m_liveResources.remove(&resource);
741}
742
743#else
744
745void MemoryCache::registerLiveResource(Resource&)
746{
747}
748
749void MemoryCache::unregisterLiveResource(Resource&)
750{
751}
752#endif
753
754#ifdef MEMORY_CACHE_STATS
755
756void MemoryCache::dumpStats(Timer<MemoryCache>*)
757{
758    Statistics s = getStatistics();
759    printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n", "", "Count", "Size", "LiveSize", "DecodedSize", "PurgeableSize", "PurgedSize");
760    printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------");
761    printf("%-13s %13d %13d %13d %13d %13d %13d\n", "Images", s.images.count, s.images.size, s.images.liveSize, s.images.decodedSize, s.images.purgeableSize, s.images.purgedSize);
762    printf("%-13s %13d %13d %13d %13d %13d %13d\n", "CSS", s.cssStyleSheets.count, s.cssStyleSheets.size, s.cssStyleSheets.liveSize, s.cssStyleSheets.decodedSize, s.cssStyleSheets.purgeableSize, s.cssStyleSheets.purgedSize);
763    printf("%-13s %13d %13d %13d %13d %13d %13d\n", "XSL", s.xslStyleSheets.count, s.xslStyleSheets.size, s.xslStyleSheets.liveSize, s.xslStyleSheets.decodedSize, s.xslStyleSheets.purgeableSize, s.xslStyleSheets.purgedSize);
764    printf("%-13s %13d %13d %13d %13d %13d %13d\n", "JavaScript", s.scripts.count, s.scripts.size, s.scripts.liveSize, s.scripts.decodedSize, s.scripts.purgeableSize, s.scripts.purgedSize);
765    printf("%-13s %13d %13d %13d %13d %13d %13d\n", "Fonts", s.fonts.count, s.fonts.size, s.fonts.liveSize, s.fonts.decodedSize, s.fonts.purgeableSize, s.fonts.purgedSize);
766    printf("%-13s %13d %13d %13d %13d %13d %13d\n", "Other", s.other.count, s.other.size, s.other.liveSize, s.other.decodedSize, s.other.purgeableSize, s.other.purgedSize);
767    printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n\n", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------");
768
769    printf("Duplication of encoded data from data URLs\n");
770    printf("%-13s %13d of %13d\n", "Images",     s.images.encodedSizeDuplicatedInDataURLs,         s.images.encodedSize);
771    printf("%-13s %13d of %13d\n", "CSS",        s.cssStyleSheets.encodedSizeDuplicatedInDataURLs, s.cssStyleSheets.encodedSize);
772    printf("%-13s %13d of %13d\n", "XSL",        s.xslStyleSheets.encodedSizeDuplicatedInDataURLs, s.xslStyleSheets.encodedSize);
773    printf("%-13s %13d of %13d\n", "JavaScript", s.scripts.encodedSizeDuplicatedInDataURLs,        s.scripts.encodedSize);
774    printf("%-13s %13d of %13d\n", "Fonts",      s.fonts.encodedSizeDuplicatedInDataURLs,          s.fonts.encodedSize);
775    printf("%-13s %13d of %13d\n", "Other",      s.other.encodedSizeDuplicatedInDataURLs,          s.other.encodedSize);
776}
777
778void MemoryCache::dumpLRULists(bool includeLive) const
779{
780    printf("LRU-SP lists in eviction order (Kilobytes decoded, Kilobytes encoded, Access count, Referenced, isPurgeable, wasPurged):\n");
781
782    int size = m_allResources.size();
783    for (int i = size - 1; i >= 0; i--) {
784        printf("\n\nList %d: ", i);
785        Resource* current = m_allResources[i].m_tail;
786        while (current) {
787            Resource* prev = current->m_prevInAllResourcesList;
788            if (includeLive || !current->hasClients())
789                printf("(%.1fK, %.1fK, %uA, %dR, %d, %d); ", current->decodedSize() / 1024.0f, (current->encodedSize() + current->overheadSize()) / 1024.0f, current->accessCount(), current->hasClients(), current->isPurgeable(), current->wasPurged());
790
791            current = prev;
792        }
793    }
794}
795
796#endif // MEMORY_CACHE_STATS
797
798} // namespace blink
799