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