Home | History | Annotate | Download | only in cache
      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/loader/cache/MemoryCache.h"
     25 
     26 #include <stdio.h>
     27 #include "core/dom/CrossThreadTask.h"
     28 #include "core/dom/Document.h"
     29 #include "core/loader/cache/Resource.h"
     30 #include "core/loader/cache/ResourcePtr.h"
     31 #include "core/page/FrameView.h"
     32 #include "core/platform/Logging.h"
     33 #include "core/workers/WorkerGlobalScope.h"
     34 #include "core/workers/WorkerLoaderProxy.h"
     35 #include "core/workers/WorkerThread.h"
     36 #include "weborigin/SecurityOrigin.h"
     37 #include "weborigin/SecurityOriginHash.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 using namespace std;
     45 
     46 namespace WebCore {
     47 
     48 static MemoryCache* gMemoryCache;
     49 
     50 static const int cDefaultCacheCapacity = 8192 * 1024;
     51 static const double cMinDelayBeforeLiveDecodedPrune = 1; // 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_capacity(cDefaultCacheCapacity)
     70     , m_minDeadCapacity(0)
     71     , m_maxDeadCapacity(cDefaultCacheCapacity)
     72     , m_liveSize(0)
     73     , m_deadSize(0)
     74     , m_delayBeforeLiveDecodedPrune(cMinDelayBeforeLiveDecodedPrune)
     75 #ifdef MEMORY_CACHE_STATS
     76     , m_statsTimer(this, &MemoryCache::dumpStats)
     77 #endif
     78 {
     79 #ifdef MEMORY_CACHE_STATS
     80     const double statsIntervalInSeconds = 15;
     81     m_statsTimer.startRepeating(statsIntervalInSeconds);
     82 #endif
     83 }
     84 
     85 KURL MemoryCache::removeFragmentIdentifierIfNeeded(const KURL& originalURL)
     86 {
     87     if (!originalURL.hasFragmentIdentifier())
     88         return originalURL;
     89     // Strip away fragment identifier from HTTP URLs.
     90     // Data URLs must be unmodified. For file and custom URLs clients may expect resources
     91     // to be unique even when they differ by the fragment identifier only.
     92     if (!originalURL.protocolIsInHTTPFamily())
     93         return originalURL;
     94     KURL url = originalURL;
     95     url.removeFragmentIdentifier();
     96     return url;
     97 }
     98 
     99 void MemoryCache::add(Resource* resource)
    100 {
    101     ASSERT(WTF::isMainThread());
    102     m_resources.set(resource->url(), resource);
    103     resource->setInCache(true);
    104     resource->updateForAccess();
    105 
    106     LOG(ResourceLoading, "MemoryCache::add Added '%s', resource %p\n", resource->url().string().latin1().data(), resource);
    107 }
    108 
    109 void MemoryCache::replace(Resource* newResource, Resource* oldResource)
    110 {
    111     evict(oldResource);
    112     ASSERT(!m_resources.get(newResource->url()));
    113     m_resources.set(newResource->url(), newResource);
    114     newResource->setInCache(true);
    115     insertInLRUList(newResource);
    116     int delta = newResource->size();
    117     if (newResource->decodedSize() && newResource->hasClients())
    118         insertInLiveDecodedResourcesList(newResource);
    119     if (delta)
    120         adjustSize(newResource->hasClients(), delta);
    121 }
    122 
    123 Resource* MemoryCache::resourceForURL(const KURL& resourceURL)
    124 {
    125     ASSERT(WTF::isMainThread());
    126     KURL url = removeFragmentIdentifierIfNeeded(resourceURL);
    127     Resource* resource = m_resources.get(url);
    128     if (resource && !resource->makePurgeable(false)) {
    129         ASSERT(!resource->hasClients());
    130         evict(resource);
    131         return 0;
    132     }
    133     return resource;
    134 }
    135 
    136 unsigned MemoryCache::deadCapacity() const
    137 {
    138     // Dead resource capacity is whatever space is not occupied by live resources, bounded by an independent minimum and maximum.
    139     unsigned capacity = m_capacity - min(m_liveSize, m_capacity); // Start with available capacity.
    140     capacity = max(capacity, m_minDeadCapacity); // Make sure it's above the minimum.
    141     capacity = min(capacity, m_maxDeadCapacity); // Make sure it's below the maximum.
    142     return capacity;
    143 }
    144 
    145 unsigned MemoryCache::liveCapacity() const
    146 {
    147     // Live resource capacity is whatever is left over after calculating dead resource capacity.
    148     return m_capacity - deadCapacity();
    149 }
    150 
    151 void MemoryCache::pruneLiveResources()
    152 {
    153     unsigned capacity = liveCapacity();
    154     if (!m_liveSize || (capacity && m_liveSize <= capacity))
    155         return;
    156 
    157     unsigned targetSize = static_cast<unsigned>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again.
    158 
    159     double currentTime = FrameView::currentPaintTimeStamp();
    160     if (!currentTime) // In case prune is called directly, outside of a Frame paint.
    161         currentTime = WTF::currentTime();
    162 
    163     // Destroy any decoded data in live objects that we can.
    164     // Start from the tail, since this is the lowest priority
    165     // and least recently accessed of the objects.
    166 
    167     // The list might not be sorted by the m_lastDecodedAccessTime. The impact
    168     // of this weaker invariant is minor as the below if statement to check the
    169     // elapsedTime will evaluate to false as the currentTime will be a lot
    170     // greater than the current->m_lastDecodedAccessTime.
    171     // For more details see: https://bugs.webkit.org/show_bug.cgi?id=30209
    172 
    173     // Start pruning from the lowest priority list.
    174     for (int priority = Resource::CacheLiveResourcePriorityLow; priority <= Resource::CacheLiveResourcePriorityHigh; ++priority) {
    175         Resource* current = m_liveDecodedResources[priority].m_tail;
    176         while (current) {
    177             Resource* prev = current->m_prevInLiveResourcesList;
    178             ASSERT(current->hasClients());
    179             if (current->isLoaded() && current->decodedSize()) {
    180                 // Check to see if the remaining resources are too new to prune.
    181                 double elapsedTime = currentTime - current->m_lastDecodedAccessTime;
    182                 if (elapsedTime < m_delayBeforeLiveDecodedPrune)
    183                     return;
    184 
    185                 // Destroy our decoded data. This will remove us from
    186                 // m_liveDecodedResources, and possibly move us to a different LRU
    187                 // list in m_allResources.
    188                 current->destroyDecodedData();
    189 
    190                 if (targetSize && m_liveSize <= targetSize)
    191                     return;
    192             }
    193             current = prev;
    194         }
    195     }
    196 }
    197 
    198 void MemoryCache::pruneDeadResources()
    199 {
    200     unsigned capacity = deadCapacity();
    201     if (!m_deadSize || (capacity && m_deadSize <= capacity))
    202         return;
    203 
    204     unsigned targetSize = static_cast<unsigned>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again.
    205 
    206     int size = m_allResources.size();
    207 
    208     // See if we have any purged resources we can evict.
    209     for (int i = 0; i < size; i++) {
    210         Resource* current = m_allResources[i].m_tail;
    211         while (current) {
    212             Resource* prev = current->m_prevInAllResourcesList;
    213             if (current->wasPurged()) {
    214                 ASSERT(!current->hasClients());
    215                 ASSERT(!current->isPreloaded());
    216                 evict(current);
    217             }
    218             current = prev;
    219         }
    220     }
    221     if (targetSize && m_deadSize <= targetSize)
    222         return;
    223 
    224     bool canShrinkLRULists = true;
    225     for (int i = size - 1; i >= 0; i--) {
    226         // Remove from the tail, since this is the least frequently accessed of the objects.
    227         Resource* current = m_allResources[i].m_tail;
    228 
    229         // First flush all the decoded data in this queue.
    230         while (current) {
    231             // Protect 'previous' so it can't get deleted during destroyDecodedData().
    232             ResourcePtr<Resource> previous = current->m_prevInAllResourcesList;
    233             ASSERT(!previous || previous->inCache());
    234             if (!current->hasClients() && !current->isPreloaded() && current->isLoaded()) {
    235                 // Destroy our decoded data. This will remove us from
    236                 // m_liveDecodedResources, and possibly move us to a different
    237                 // LRU list in m_allResources.
    238                 current->destroyDecodedData();
    239 
    240                 if (targetSize && m_deadSize <= targetSize)
    241                     return;
    242             }
    243             // Decoded data may reference other resources. Stop iterating if 'previous' somehow got
    244             // kicked out of cache during destroyDecodedData().
    245             if (previous && !previous->inCache())
    246                 break;
    247             current = previous.get();
    248         }
    249 
    250         // Now evict objects from this queue.
    251         current = m_allResources[i].m_tail;
    252         while (current) {
    253             ResourcePtr<Resource> previous = current->m_prevInAllResourcesList;
    254             ASSERT(!previous || previous->inCache());
    255             if (!current->hasClients() && !current->isPreloaded() && !current->isCacheValidator()) {
    256                 evict(current);
    257                 if (targetSize && m_deadSize <= targetSize)
    258                     return;
    259             }
    260             if (previous && !previous->inCache())
    261                 break;
    262             current = previous.get();
    263         }
    264 
    265         // Shrink the vector back down so we don't waste time inspecting
    266         // empty LRU lists on future prunes.
    267         if (m_allResources[i].m_head)
    268             canShrinkLRULists = false;
    269         else if (canShrinkLRULists)
    270             m_allResources.resize(i);
    271     }
    272 }
    273 
    274 void MemoryCache::setCapacities(unsigned minDeadBytes, unsigned maxDeadBytes, unsigned totalBytes)
    275 {
    276     ASSERT(minDeadBytes <= maxDeadBytes);
    277     ASSERT(maxDeadBytes <= totalBytes);
    278     m_minDeadCapacity = minDeadBytes;
    279     m_maxDeadCapacity = maxDeadBytes;
    280     m_capacity = totalBytes;
    281     prune();
    282 }
    283 
    284 void MemoryCache::evict(Resource* resource)
    285 {
    286     ASSERT(WTF::isMainThread());
    287     LOG(ResourceLoading, "Evicting resource %p for '%s' from cache", resource, resource->url().string().latin1().data());
    288     // The resource may have already been removed by someone other than our caller,
    289     // who needed a fresh copy for a reload. See <http://bugs.webkit.org/show_bug.cgi?id=12479#c6>.
    290     if (resource->inCache()) {
    291         // Remove from the resource map.
    292         m_resources.remove(resource->url());
    293         resource->setInCache(false);
    294 
    295         // Remove from the appropriate LRU list.
    296         removeFromLRUList(resource);
    297         removeFromLiveDecodedResourcesList(resource);
    298         adjustSize(resource->hasClients(), -static_cast<int>(resource->size()));
    299     } else
    300         ASSERT(m_resources.get(resource->url()) != resource);
    301 
    302     resource->deleteIfPossible();
    303 }
    304 
    305 MemoryCache::LRUList* MemoryCache::lruListFor(Resource* resource)
    306 {
    307     unsigned accessCount = max(resource->accessCount(), 1U);
    308     unsigned queueIndex = WTF::fastLog2(resource->size() / accessCount);
    309 #ifndef NDEBUG
    310     resource->m_lruIndex = queueIndex;
    311 #endif
    312     if (m_allResources.size() <= queueIndex)
    313         m_allResources.grow(queueIndex + 1);
    314     return &m_allResources[queueIndex];
    315 }
    316 
    317 void MemoryCache::removeFromLRUList(Resource* resource)
    318 {
    319     // If we've never been accessed, then we're brand new and not in any list.
    320     if (resource->accessCount() == 0)
    321         return;
    322 
    323 #if !ASSERT_DISABLED
    324     unsigned oldListIndex = resource->m_lruIndex;
    325 #endif
    326 
    327     LRUList* list = lruListFor(resource);
    328 
    329 #if !ASSERT_DISABLED
    330     // Verify that the list we got is the list we want.
    331     ASSERT(resource->m_lruIndex == oldListIndex);
    332 
    333     // Verify that we are in fact in this list.
    334     bool found = false;
    335     for (Resource* current = list->m_head; current; current = current->m_nextInAllResourcesList) {
    336         if (current == resource) {
    337             found = true;
    338             break;
    339         }
    340     }
    341     ASSERT(found);
    342 #endif
    343 
    344     Resource* next = resource->m_nextInAllResourcesList;
    345     Resource* prev = resource->m_prevInAllResourcesList;
    346 
    347     if (next == 0 && prev == 0 && list->m_head != resource)
    348         return;
    349 
    350     resource->m_nextInAllResourcesList = 0;
    351     resource->m_prevInAllResourcesList = 0;
    352 
    353     if (next)
    354         next->m_prevInAllResourcesList = prev;
    355     else if (list->m_tail == resource)
    356         list->m_tail = prev;
    357 
    358     if (prev)
    359         prev->m_nextInAllResourcesList = next;
    360     else if (list->m_head == resource)
    361         list->m_head = next;
    362 }
    363 
    364 void MemoryCache::insertInLRUList(Resource* resource)
    365 {
    366     // Make sure we aren't in some list already.
    367     ASSERT(!resource->m_nextInAllResourcesList && !resource->m_prevInAllResourcesList);
    368     ASSERT(resource->inCache());
    369     ASSERT(resource->accessCount() > 0);
    370 
    371     LRUList* list = lruListFor(resource);
    372 
    373     resource->m_nextInAllResourcesList = list->m_head;
    374     if (list->m_head)
    375         list->m_head->m_prevInAllResourcesList = resource;
    376     list->m_head = resource;
    377 
    378     if (!resource->m_nextInAllResourcesList)
    379         list->m_tail = resource;
    380 
    381 #if !ASSERT_DISABLED
    382     // Verify that we are in now in the list like we should be.
    383     list = lruListFor(resource);
    384     bool found = false;
    385     for (Resource* current = list->m_head; current; current = current->m_nextInAllResourcesList) {
    386         if (current == resource) {
    387             found = true;
    388             break;
    389         }
    390     }
    391     ASSERT(found);
    392 #endif
    393 
    394 }
    395 
    396 void MemoryCache::removeFromLiveDecodedResourcesList(Resource* resource)
    397 {
    398     // If we've never been accessed, then we're brand new and not in any list.
    399     if (!resource->m_inLiveDecodedResourcesList)
    400         return;
    401     resource->m_inLiveDecodedResourcesList = false;
    402 
    403     LRUList* list = &m_liveDecodedResources[resource->cacheLiveResourcePriority()];
    404 
    405 #if !ASSERT_DISABLED
    406     // Verify that we are in fact in this list.
    407     bool found = false;
    408     for (Resource* current = list->m_head; current; current = current->m_nextInLiveResourcesList) {
    409         if (current == resource) {
    410             found = true;
    411             break;
    412         }
    413     }
    414     ASSERT(found);
    415 #endif
    416 
    417     Resource* next = resource->m_nextInLiveResourcesList;
    418     Resource* prev = resource->m_prevInLiveResourcesList;
    419 
    420     if (!next && !prev && list->m_head != resource)
    421         return;
    422 
    423     resource->m_nextInLiveResourcesList = 0;
    424     resource->m_prevInLiveResourcesList = 0;
    425 
    426     if (next)
    427         next->m_prevInLiveResourcesList = prev;
    428     else if (list->m_tail == resource)
    429         list->m_tail = prev;
    430 
    431     if (prev)
    432         prev->m_nextInLiveResourcesList = next;
    433     else if (list->m_head == resource)
    434         list->m_head = next;
    435 }
    436 
    437 void MemoryCache::insertInLiveDecodedResourcesList(Resource* resource)
    438 {
    439     // Make sure we aren't in the list already.
    440     ASSERT(!resource->m_nextInLiveResourcesList && !resource->m_prevInLiveResourcesList && !resource->m_inLiveDecodedResourcesList);
    441     resource->m_inLiveDecodedResourcesList = true;
    442 
    443     LRUList* list = &m_liveDecodedResources[resource->cacheLiveResourcePriority()];
    444     resource->m_nextInLiveResourcesList = list->m_head;
    445     if (list->m_head)
    446         list->m_head->m_prevInLiveResourcesList = resource;
    447     list->m_head = resource;
    448 
    449     if (!resource->m_nextInLiveResourcesList)
    450         list->m_tail = resource;
    451 
    452 #if !ASSERT_DISABLED
    453     // Verify that we are in now in the list like we should be.
    454     bool found = false;
    455     for (Resource* current = list->m_head; current; current = current->m_nextInLiveResourcesList) {
    456         if (current == resource) {
    457             found = true;
    458             break;
    459         }
    460     }
    461     ASSERT(found);
    462 #endif
    463 
    464 }
    465 
    466 void MemoryCache::addToLiveResourcesSize(Resource* resource)
    467 {
    468     m_liveSize += resource->size();
    469     m_deadSize -= resource->size();
    470 }
    471 
    472 void MemoryCache::removeFromLiveResourcesSize(Resource* resource)
    473 {
    474     m_liveSize -= resource->size();
    475     m_deadSize += resource->size();
    476 }
    477 
    478 void MemoryCache::adjustSize(bool live, int delta)
    479 {
    480     if (live) {
    481         ASSERT(delta >= 0 || ((int)m_liveSize + delta >= 0));
    482         m_liveSize += delta;
    483     } else {
    484         ASSERT(delta >= 0 || ((int)m_deadSize + delta >= 0));
    485         m_deadSize += delta;
    486     }
    487 }
    488 
    489 void MemoryCache::removeURLFromCache(ScriptExecutionContext* context, const KURL& url)
    490 {
    491     if (context->isWorkerGlobalScope()) {
    492         WorkerGlobalScope* workerGlobalScope = toWorkerGlobalScope(context);
    493         workerGlobalScope->thread()->workerLoaderProxy().postTaskToLoader(createCallbackTask(&removeURLFromCacheInternal, url));
    494         return;
    495     }
    496     removeURLFromCacheInternal(context, url);
    497 }
    498 
    499 void MemoryCache::removeURLFromCacheInternal(ScriptExecutionContext*, const KURL& url)
    500 {
    501     if (Resource* resource = memoryCache()->resourceForURL(url))
    502         memoryCache()->remove(resource);
    503 }
    504 
    505 void MemoryCache::TypeStatistic::addResource(Resource* o)
    506 {
    507     bool purged = o->wasPurged();
    508     bool purgeable = o->isPurgeable() && !purged;
    509     int pageSize = (o->encodedSize() + o->overheadSize() + 4095) & ~4095;
    510     count++;
    511     size += purged ? 0 : o->size();
    512     liveSize += o->hasClients() ? o->size() : 0;
    513     decodedSize += o->decodedSize();
    514     encodedSize += o->encodedSize();
    515     encodedSizeDuplicatedInDataURLs += o->url().protocolIsData() ? o->encodedSize() : 0;
    516     purgeableSize += purgeable ? pageSize : 0;
    517     purgedSize += purged ? pageSize : 0;
    518 }
    519 
    520 MemoryCache::Statistics MemoryCache::getStatistics()
    521 {
    522     Statistics stats;
    523     ResourceMap::iterator e = m_resources.end();
    524     for (ResourceMap::iterator i = m_resources.begin(); i != e; ++i) {
    525         Resource* resource = i->value;
    526         switch (resource->type()) {
    527         case Resource::Image:
    528             stats.images.addResource(resource);
    529             break;
    530         case Resource::CSSStyleSheet:
    531             stats.cssStyleSheets.addResource(resource);
    532             break;
    533         case Resource::Script:
    534             stats.scripts.addResource(resource);
    535             break;
    536         case Resource::XSLStyleSheet:
    537             stats.xslStyleSheets.addResource(resource);
    538             break;
    539         case Resource::Font:
    540             stats.fonts.addResource(resource);
    541             break;
    542         default:
    543             stats.other.addResource(resource);
    544             break;
    545         }
    546     }
    547     return stats;
    548 }
    549 
    550 void MemoryCache::evictResources()
    551 {
    552     for (;;) {
    553         ResourceMap::iterator i = m_resources.begin();
    554         if (i == m_resources.end())
    555             break;
    556         evict(i->value);
    557     }
    558 }
    559 
    560 void MemoryCache::prune()
    561 {
    562     if (m_liveSize + m_deadSize <= m_capacity && m_maxDeadCapacity && m_deadSize <= m_maxDeadCapacity) // Fast path.
    563         return;
    564     if (m_inPruneResources)
    565         return;
    566     TemporaryChange<bool> reentrancyProtector(m_inPruneResources, true);
    567 
    568     pruneDeadResources(); // Prune dead first, in case it was "borrowing" capacity from live.
    569     pruneLiveResources();
    570 }
    571 
    572 
    573 #ifdef MEMORY_CACHE_STATS
    574 
    575 void MemoryCache::dumpStats(Timer<MemoryCache>*)
    576 {
    577     Statistics s = getStatistics();
    578     printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n", "", "Count", "Size", "LiveSize", "DecodedSize", "PurgeableSize", "PurgedSize");
    579     printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------");
    580     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);
    581     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);
    582     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);
    583     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);
    584     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);
    585     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);
    586     printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n\n", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------");
    587 
    588     printf("Duplication of encoded data from data URLs\n");
    589     printf("%-13s %13d of %13d\n", "Images",     s.images.encodedSizeDuplicatedInDataURLs,         s.images.encodedSize);
    590     printf("%-13s %13d of %13d\n", "CSS",        s.cssStyleSheets.encodedSizeDuplicatedInDataURLs, s.cssStyleSheets.encodedSize);
    591     printf("%-13s %13d of %13d\n", "XSL",        s.xslStyleSheets.encodedSizeDuplicatedInDataURLs, s.xslStyleSheets.encodedSize);
    592     printf("%-13s %13d of %13d\n", "JavaScript", s.scripts.encodedSizeDuplicatedInDataURLs,        s.scripts.encodedSize);
    593     printf("%-13s %13d of %13d\n", "Fonts",      s.fonts.encodedSizeDuplicatedInDataURLs,          s.fonts.encodedSize);
    594     printf("%-13s %13d of %13d\n", "Other",      s.other.encodedSizeDuplicatedInDataURLs,          s.other.encodedSize);
    595 }
    596 
    597 void MemoryCache::dumpLRULists(bool includeLive) const
    598 {
    599     printf("LRU-SP lists in eviction order (Kilobytes decoded, Kilobytes encoded, Access count, Referenced, isPurgeable, wasPurged):\n");
    600 
    601     int size = m_allResources.size();
    602     for (int i = size - 1; i >= 0; i--) {
    603         printf("\n\nList %d: ", i);
    604         Resource* current = m_allResources[i].m_tail;
    605         while (current) {
    606             Resource* prev = current->m_prevInAllResourcesList;
    607             if (includeLive || !current->hasClients())
    608                 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());
    609 
    610             current = prev;
    611         }
    612     }
    613 }
    614 
    615 #endif // MEMORY_CACHE_STATS
    616 
    617 } // namespace WebCore
    618