Home | History | Annotate | Download | only in loader
      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 "Cache.h"
     25 
     26 #include "CachedCSSStyleSheet.h"
     27 #include "CachedFont.h"
     28 #include "CachedImage.h"
     29 #include "CachedScript.h"
     30 #include "CachedXSLStyleSheet.h"
     31 #include "DocLoader.h"
     32 #include "Document.h"
     33 #include "FrameLoader.h"
     34 #include "FrameLoaderTypes.h"
     35 #include "FrameView.h"
     36 #include "Image.h"
     37 #include "ResourceHandle.h"
     38 #include "SecurityOrigin.h"
     39 #include <stdio.h>
     40 #include <wtf/CurrentTime.h>
     41 
     42 using namespace std;
     43 
     44 namespace WebCore {
     45 
     46 static const int cDefaultCacheCapacity = 8192 * 1024;
     47 static const double cMinDelayBeforeLiveDecodedPrune = 1; // Seconds.
     48 static const float cTargetPrunePercentage = .95f; // Percentage of capacity toward which we prune, to avoid immediately pruning again.
     49 static const double cDefaultDecodedDataDeletionInterval = 0;
     50 
     51 Cache* cache()
     52 {
     53     static Cache* staticCache = new Cache;
     54     return staticCache;
     55 }
     56 
     57 Cache::Cache()
     58     : m_disabled(false)
     59     , m_pruneEnabled(true)
     60     , m_inPruneDeadResources(false)
     61     , m_capacity(cDefaultCacheCapacity)
     62     , m_minDeadCapacity(0)
     63     , m_maxDeadCapacity(cDefaultCacheCapacity)
     64     , m_deadDecodedDataDeletionInterval(cDefaultDecodedDataDeletionInterval)
     65     , m_liveSize(0)
     66     , m_deadSize(0)
     67 {
     68 }
     69 
     70 static CachedResource* createResource(CachedResource::Type type, const KURL& url, const String& charset)
     71 {
     72     switch (type) {
     73     case CachedResource::ImageResource:
     74         return new CachedImage(url.string());
     75     case CachedResource::CSSStyleSheet:
     76         return new CachedCSSStyleSheet(url.string(), charset);
     77     case CachedResource::Script:
     78         return new CachedScript(url.string(), charset);
     79     case CachedResource::FontResource:
     80         return new CachedFont(url.string());
     81 #if ENABLE(XSLT)
     82     case CachedResource::XSLStyleSheet:
     83         return new CachedXSLStyleSheet(url.string());
     84 #endif
     85 #if ENABLE(XBL)
     86     case CachedResource::XBLStyleSheet:
     87         return new CachedXBLDocument(url.string());
     88 #endif
     89     default:
     90         break;
     91     }
     92 
     93     return 0;
     94 }
     95 
     96 CachedResource* Cache::requestResource(DocLoader* docLoader, CachedResource::Type type, const KURL& url, const String& charset, bool requestIsPreload)
     97 {
     98     // FIXME: Do we really need to special-case an empty URL?
     99     // Would it be better to just go on with the cache code and let it fail later?
    100     if (url.isEmpty())
    101         return 0;
    102 
    103     // Look up the resource in our map.
    104     CachedResource* resource = resourceForURL(url.string());
    105 
    106     if (resource && requestIsPreload && !resource->isPreloaded())
    107         return 0;
    108 
    109     if (SecurityOrigin::restrictAccessToLocal() && !SecurityOrigin::canLoad(url, String(), docLoader->doc())) {
    110         Document* doc = docLoader->doc();
    111         if (doc && !requestIsPreload)
    112             FrameLoader::reportLocalLoadFailed(doc->frame(), url.string());
    113         return 0;
    114     }
    115 
    116     if (!resource) {
    117         // The resource does not exist. Create it.
    118         resource = createResource(type, url, charset);
    119         ASSERT(resource);
    120 
    121         // Pretend the resource is in the cache, to prevent it from being deleted during the load() call.
    122         // FIXME: CachedResource should just use normal refcounting instead.
    123         resource->setInCache(true);
    124 
    125         resource->load(docLoader);
    126 
    127         if (resource->errorOccurred()) {
    128             // We don't support immediate loads, but we do support immediate failure.
    129             // In that case we should to delete the resource now and return 0 because otherwise
    130             // it would leak if no ref/deref was ever done on it.
    131             resource->setInCache(false);
    132             delete resource;
    133             return 0;
    134         }
    135 
    136         if (!disabled())
    137             m_resources.set(url.string(), resource);  // The size will be added in later once the resource is loaded and calls back to us with the new size.
    138         else {
    139             // Kick the resource out of the cache, because the cache is disabled.
    140             resource->setInCache(false);
    141             resource->setDocLoader(docLoader);
    142         }
    143     }
    144 
    145     if (resource->type() != type)
    146         return 0;
    147 
    148     if (!disabled()) {
    149         // This will move the resource to the front of its LRU list and increase its access count.
    150         resourceAccessed(resource);
    151     }
    152 
    153     return resource;
    154 }
    155 
    156 CachedCSSStyleSheet* Cache::requestUserCSSStyleSheet(DocLoader* docLoader, const String& url, const String& charset)
    157 {
    158     CachedCSSStyleSheet* userSheet;
    159     if (CachedResource* existing = resourceForURL(url)) {
    160         if (existing->type() != CachedResource::CSSStyleSheet)
    161             return 0;
    162         userSheet = static_cast<CachedCSSStyleSheet*>(existing);
    163     } else {
    164         userSheet = new CachedCSSStyleSheet(url, charset);
    165 
    166         // Pretend the resource is in the cache, to prevent it from being deleted during the load() call.
    167         // FIXME: CachedResource should just use normal refcounting instead.
    168         userSheet->setInCache(true);
    169         // Don't load incrementally, skip load checks, don't send resource load callbacks.
    170         userSheet->load(docLoader, false, SkipSecurityCheck, false);
    171         if (!disabled())
    172             m_resources.set(url, userSheet);
    173         else
    174             userSheet->setInCache(false);
    175     }
    176 
    177     if (!disabled()) {
    178         // This will move the resource to the front of its LRU list and increase its access count.
    179         resourceAccessed(userSheet);
    180     }
    181 
    182     return userSheet;
    183 }
    184 
    185 void Cache::revalidateResource(CachedResource* resource, DocLoader* docLoader)
    186 {
    187     ASSERT(resource);
    188     ASSERT(resource->inCache());
    189     ASSERT(resource == m_resources.get(resource->url()));
    190     ASSERT(!disabled());
    191     if (resource->resourceToRevalidate())
    192         return;
    193     if (!resource->canUseCacheValidator()) {
    194         evict(resource);
    195         return;
    196     }
    197     const String& url = resource->url();
    198     CachedResource* newResource = createResource(resource->type(), KURL(ParsedURLString, url), resource->encoding());
    199     newResource->setResourceToRevalidate(resource);
    200     evict(resource);
    201     m_resources.set(url, newResource);
    202     newResource->setInCache(true);
    203     resourceAccessed(newResource);
    204     newResource->load(docLoader);
    205 }
    206 
    207 void Cache::revalidationSucceeded(CachedResource* revalidatingResource, const ResourceResponse& response)
    208 {
    209     CachedResource* resource = revalidatingResource->resourceToRevalidate();
    210     ASSERT(resource);
    211     ASSERT(!resource->inCache());
    212     ASSERT(resource->isLoaded());
    213     ASSERT(revalidatingResource->inCache());
    214 
    215     evict(revalidatingResource);
    216 
    217     ASSERT(!m_resources.get(resource->url()));
    218     m_resources.set(resource->url(), resource);
    219     resource->setInCache(true);
    220     resource->updateResponseAfterRevalidation(response);
    221     insertInLRUList(resource);
    222     int delta = resource->size();
    223     if (resource->decodedSize() && resource->hasClients())
    224         insertInLiveDecodedResourcesList(resource);
    225     if (delta)
    226         adjustSize(resource->hasClients(), delta);
    227 
    228     revalidatingResource->switchClientsToRevalidatedResource();
    229     // this deletes the revalidating resource
    230     revalidatingResource->clearResourceToRevalidate();
    231 }
    232 
    233 void Cache::revalidationFailed(CachedResource* revalidatingResource)
    234 {
    235     ASSERT(revalidatingResource->resourceToRevalidate());
    236     revalidatingResource->clearResourceToRevalidate();
    237 }
    238 
    239 CachedResource* Cache::resourceForURL(const String& url)
    240 {
    241     CachedResource* resource = m_resources.get(url);
    242     if (resource && !resource->makePurgeable(false)) {
    243         ASSERT(!resource->hasClients());
    244         evict(resource);
    245         return 0;
    246     }
    247     return resource;
    248 }
    249 
    250 unsigned Cache::deadCapacity() const
    251 {
    252     // Dead resource capacity is whatever space is not occupied by live resources, bounded by an independent minimum and maximum.
    253     unsigned capacity = m_capacity - min(m_liveSize, m_capacity); // Start with available capacity.
    254     capacity = max(capacity, m_minDeadCapacity); // Make sure it's above the minimum.
    255     capacity = min(capacity, m_maxDeadCapacity); // Make sure it's below the maximum.
    256     return capacity;
    257 }
    258 
    259 unsigned Cache::liveCapacity() const
    260 {
    261     // Live resource capacity is whatever is left over after calculating dead resource capacity.
    262     return m_capacity - deadCapacity();
    263 }
    264 
    265 void Cache::pruneLiveResources()
    266 {
    267     if (!m_pruneEnabled)
    268         return;
    269 
    270     unsigned capacity = liveCapacity();
    271     if (capacity && m_liveSize <= capacity)
    272         return;
    273 
    274     unsigned targetSize = static_cast<unsigned>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again.
    275     double currentTime = FrameView::currentPaintTimeStamp();
    276     if (!currentTime) // In case prune is called directly, outside of a Frame paint.
    277         currentTime = WTF::currentTime();
    278 
    279     // Destroy any decoded data in live objects that we can.
    280     // Start from the tail, since this is the least recently accessed of the objects.
    281 
    282     // The list might not be sorted by the m_lastDecodedAccessTime. The impact
    283     // of this weaker invariant is minor as the below if statement to check the
    284     // elapsedTime will evaluate to false as the currentTime will be a lot
    285     // greater than the current->m_lastDecodedAccessTime.
    286     // For more details see: https://bugs.webkit.org/show_bug.cgi?id=30209
    287     CachedResource* current = m_liveDecodedResources.m_tail;
    288     while (current) {
    289         CachedResource* prev = current->m_prevInLiveResourcesList;
    290         ASSERT(current->hasClients());
    291         if (current->isLoaded() && current->decodedSize()) {
    292             // Check to see if the remaining resources are too new to prune.
    293             double elapsedTime = currentTime - current->m_lastDecodedAccessTime;
    294             if (elapsedTime < cMinDelayBeforeLiveDecodedPrune)
    295                 return;
    296 
    297             // Destroy our decoded data. This will remove us from
    298             // m_liveDecodedResources, and possibly move us to a different LRU
    299             // list in m_allResources.
    300             current->destroyDecodedData();
    301 
    302             if (targetSize && m_liveSize <= targetSize)
    303                 return;
    304         }
    305         current = prev;
    306     }
    307 }
    308 
    309 void Cache::pruneDeadResources()
    310 {
    311     if (!m_pruneEnabled)
    312         return;
    313 
    314     unsigned capacity = deadCapacity();
    315     if (capacity && m_deadSize <= capacity)
    316         return;
    317 
    318     unsigned targetSize = static_cast<unsigned>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again.
    319     int size = m_allResources.size();
    320 
    321     if (!m_inPruneDeadResources) {
    322         // See if we have any purged resources we can evict.
    323         for (int i = 0; i < size; i++) {
    324             CachedResource* current = m_allResources[i].m_tail;
    325             while (current) {
    326                 CachedResource* prev = current->m_prevInAllResourcesList;
    327                 if (current->wasPurged()) {
    328                     ASSERT(!current->hasClients());
    329                     ASSERT(!current->isPreloaded());
    330                     evict(current);
    331                 }
    332                 current = prev;
    333             }
    334         }
    335         if (targetSize && m_deadSize <= targetSize)
    336             return;
    337     }
    338 
    339     bool canShrinkLRULists = true;
    340     m_inPruneDeadResources = true;
    341     for (int i = size - 1; i >= 0; i--) {
    342         // Remove from the tail, since this is the least frequently accessed of the objects.
    343         CachedResource* current = m_allResources[i].m_tail;
    344 
    345         // First flush all the decoded data in this queue.
    346         while (current) {
    347             CachedResource* prev = current->m_prevInAllResourcesList;
    348             if (!current->hasClients() && !current->isPreloaded() && current->isLoaded()) {
    349                 // Destroy our decoded data. This will remove us from
    350                 // m_liveDecodedResources, and possibly move us to a different
    351                 // LRU list in m_allResources.
    352                 current->destroyDecodedData();
    353 
    354                 if (targetSize && m_deadSize <= targetSize) {
    355                     m_inPruneDeadResources = false;
    356                     return;
    357                 }
    358             }
    359             current = prev;
    360         }
    361 
    362         // Now evict objects from this queue.
    363         current = m_allResources[i].m_tail;
    364         while (current) {
    365             CachedResource* prev = current->m_prevInAllResourcesList;
    366             if (!current->hasClients() && !current->isPreloaded() && !current->isCacheValidator()) {
    367                 evict(current);
    368                 // If evict() caused pruneDeadResources() to be re-entered, bail out. This can happen when removing an
    369                 // SVG CachedImage that has subresources.
    370                 if (!m_inPruneDeadResources)
    371                     return;
    372 
    373                 if (targetSize && m_deadSize <= targetSize) {
    374                     m_inPruneDeadResources = false;
    375                     return;
    376                 }
    377             }
    378             current = prev;
    379         }
    380 
    381         // Shrink the vector back down so we don't waste time inspecting
    382         // empty LRU lists on future prunes.
    383         if (m_allResources[i].m_head)
    384             canShrinkLRULists = false;
    385         else if (canShrinkLRULists)
    386             m_allResources.resize(i);
    387     }
    388     m_inPruneDeadResources = false;
    389 }
    390 
    391 void Cache::setCapacities(unsigned minDeadBytes, unsigned maxDeadBytes, unsigned totalBytes)
    392 {
    393     ASSERT(minDeadBytes <= maxDeadBytes);
    394     ASSERT(maxDeadBytes <= totalBytes);
    395     m_minDeadCapacity = minDeadBytes;
    396     m_maxDeadCapacity = maxDeadBytes;
    397     m_capacity = totalBytes;
    398     prune();
    399 }
    400 
    401 void Cache::evict(CachedResource* resource)
    402 {
    403     // The resource may have already been removed by someone other than our caller,
    404     // who needed a fresh copy for a reload. See <http://bugs.webkit.org/show_bug.cgi?id=12479#c6>.
    405     if (resource->inCache()) {
    406         // Remove from the resource map.
    407         m_resources.remove(resource->url());
    408         resource->setInCache(false);
    409 
    410         // Remove from the appropriate LRU list.
    411         removeFromLRUList(resource);
    412         removeFromLiveDecodedResourcesList(resource);
    413 
    414         // Subtract from our size totals.
    415         int delta = -static_cast<int>(resource->size());
    416         if (delta)
    417             adjustSize(resource->hasClients(), delta);
    418     } else
    419         ASSERT(m_resources.get(resource->url()) != resource);
    420 
    421     if (resource->canDelete())
    422         delete resource;
    423 }
    424 
    425 void Cache::addDocLoader(DocLoader* docLoader)
    426 {
    427     m_docLoaders.add(docLoader);
    428 }
    429 
    430 void Cache::removeDocLoader(DocLoader* docLoader)
    431 {
    432     m_docLoaders.remove(docLoader);
    433 }
    434 
    435 static inline unsigned fastLog2(unsigned i)
    436 {
    437     unsigned log2 = 0;
    438     if (i & (i - 1))
    439         log2 += 1;
    440     if (i >> 16)
    441         log2 += 16, i >>= 16;
    442     if (i >> 8)
    443         log2 += 8, i >>= 8;
    444     if (i >> 4)
    445         log2 += 4, i >>= 4;
    446     if (i >> 2)
    447         log2 += 2, i >>= 2;
    448     if (i >> 1)
    449         log2 += 1;
    450     return log2;
    451 }
    452 
    453 Cache::LRUList* Cache::lruListFor(CachedResource* resource)
    454 {
    455     unsigned accessCount = max(resource->accessCount(), 1U);
    456     unsigned queueIndex = fastLog2(resource->size() / accessCount);
    457 #ifndef NDEBUG
    458     resource->m_lruIndex = queueIndex;
    459 #endif
    460     if (m_allResources.size() <= queueIndex)
    461         m_allResources.grow(queueIndex + 1);
    462     return &m_allResources[queueIndex];
    463 }
    464 
    465 void Cache::removeFromLRUList(CachedResource* resource)
    466 {
    467     // If we've never been accessed, then we're brand new and not in any list.
    468     if (resource->accessCount() == 0)
    469         return;
    470 
    471 #if !ASSERT_DISABLED
    472     unsigned oldListIndex = resource->m_lruIndex;
    473 #endif
    474 
    475     LRUList* list = lruListFor(resource);
    476 
    477 #if !ASSERT_DISABLED
    478     // Verify that the list we got is the list we want.
    479     ASSERT(resource->m_lruIndex == oldListIndex);
    480 
    481     // Verify that we are in fact in this list.
    482     bool found = false;
    483     for (CachedResource* current = list->m_head; current; current = current->m_nextInAllResourcesList) {
    484         if (current == resource) {
    485             found = true;
    486             break;
    487         }
    488     }
    489     ASSERT(found);
    490 #endif
    491 
    492     CachedResource* next = resource->m_nextInAllResourcesList;
    493     CachedResource* prev = resource->m_prevInAllResourcesList;
    494 
    495     if (next == 0 && prev == 0 && list->m_head != resource)
    496         return;
    497 
    498     resource->m_nextInAllResourcesList = 0;
    499     resource->m_prevInAllResourcesList = 0;
    500 
    501     if (next)
    502         next->m_prevInAllResourcesList = prev;
    503     else if (list->m_tail == resource)
    504         list->m_tail = prev;
    505 
    506     if (prev)
    507         prev->m_nextInAllResourcesList = next;
    508     else if (list->m_head == resource)
    509         list->m_head = next;
    510 }
    511 
    512 void Cache::insertInLRUList(CachedResource* resource)
    513 {
    514     // Make sure we aren't in some list already.
    515     ASSERT(!resource->m_nextInAllResourcesList && !resource->m_prevInAllResourcesList);
    516     ASSERT(resource->inCache());
    517     ASSERT(resource->accessCount() > 0);
    518 
    519     LRUList* list = lruListFor(resource);
    520 
    521     resource->m_nextInAllResourcesList = list->m_head;
    522     if (list->m_head)
    523         list->m_head->m_prevInAllResourcesList = resource;
    524     list->m_head = resource;
    525 
    526     if (!resource->m_nextInAllResourcesList)
    527         list->m_tail = resource;
    528 
    529 #ifndef NDEBUG
    530     // Verify that we are in now in the list like we should be.
    531     list = lruListFor(resource);
    532     bool found = false;
    533     for (CachedResource* current = list->m_head; current; current = current->m_nextInAllResourcesList) {
    534         if (current == resource) {
    535             found = true;
    536             break;
    537         }
    538     }
    539     ASSERT(found);
    540 #endif
    541 
    542 }
    543 
    544 void Cache::resourceAccessed(CachedResource* resource)
    545 {
    546     ASSERT(resource->inCache());
    547 
    548     // Need to make sure to remove before we increase the access count, since
    549     // the queue will possibly change.
    550     removeFromLRUList(resource);
    551 
    552     // If this is the first time the resource has been accessed, adjust the size of the cache to account for its initial size.
    553     if (!resource->accessCount())
    554         adjustSize(resource->hasClients(), resource->size());
    555 
    556     // Add to our access count.
    557     resource->increaseAccessCount();
    558 
    559     // Now insert into the new queue.
    560     insertInLRUList(resource);
    561 }
    562 
    563 void Cache::removeFromLiveDecodedResourcesList(CachedResource* resource)
    564 {
    565     // If we've never been accessed, then we're brand new and not in any list.
    566     if (!resource->m_inLiveDecodedResourcesList)
    567         return;
    568     resource->m_inLiveDecodedResourcesList = false;
    569 
    570 #ifndef NDEBUG
    571     // Verify that we are in fact in this list.
    572     bool found = false;
    573     for (CachedResource* current = m_liveDecodedResources.m_head; current; current = current->m_nextInLiveResourcesList) {
    574         if (current == resource) {
    575             found = true;
    576             break;
    577         }
    578     }
    579     ASSERT(found);
    580 #endif
    581 
    582     CachedResource* next = resource->m_nextInLiveResourcesList;
    583     CachedResource* prev = resource->m_prevInLiveResourcesList;
    584 
    585     if (next == 0 && prev == 0 && m_liveDecodedResources.m_head != resource)
    586         return;
    587 
    588     resource->m_nextInLiveResourcesList = 0;
    589     resource->m_prevInLiveResourcesList = 0;
    590 
    591     if (next)
    592         next->m_prevInLiveResourcesList = prev;
    593     else if (m_liveDecodedResources.m_tail == resource)
    594         m_liveDecodedResources.m_tail = prev;
    595 
    596     if (prev)
    597         prev->m_nextInLiveResourcesList = next;
    598     else if (m_liveDecodedResources.m_head == resource)
    599         m_liveDecodedResources.m_head = next;
    600 }
    601 
    602 void Cache::insertInLiveDecodedResourcesList(CachedResource* resource)
    603 {
    604     // Make sure we aren't in the list already.
    605     ASSERT(!resource->m_nextInLiveResourcesList && !resource->m_prevInLiveResourcesList && !resource->m_inLiveDecodedResourcesList);
    606     resource->m_inLiveDecodedResourcesList = true;
    607 
    608     resource->m_nextInLiveResourcesList = m_liveDecodedResources.m_head;
    609     if (m_liveDecodedResources.m_head)
    610         m_liveDecodedResources.m_head->m_prevInLiveResourcesList = resource;
    611     m_liveDecodedResources.m_head = resource;
    612 
    613     if (!resource->m_nextInLiveResourcesList)
    614         m_liveDecodedResources.m_tail = resource;
    615 
    616 #ifndef NDEBUG
    617     // Verify that we are in now in the list like we should be.
    618     bool found = false;
    619     for (CachedResource* current = m_liveDecodedResources.m_head; current; current = current->m_nextInLiveResourcesList) {
    620         if (current == resource) {
    621             found = true;
    622             break;
    623         }
    624     }
    625     ASSERT(found);
    626 #endif
    627 
    628 }
    629 
    630 void Cache::addToLiveResourcesSize(CachedResource* resource)
    631 {
    632     m_liveSize += resource->size();
    633     m_deadSize -= resource->size();
    634 }
    635 
    636 void Cache::removeFromLiveResourcesSize(CachedResource* resource)
    637 {
    638     m_liveSize -= resource->size();
    639     m_deadSize += resource->size();
    640 }
    641 
    642 void Cache::adjustSize(bool live, int delta)
    643 {
    644     if (live) {
    645         ASSERT(delta >= 0 || ((int)m_liveSize + delta >= 0));
    646         m_liveSize += delta;
    647     } else {
    648         ASSERT(delta >= 0 || ((int)m_deadSize + delta >= 0));
    649         m_deadSize += delta;
    650     }
    651 }
    652 
    653 void Cache::TypeStatistic::addResource(CachedResource* o)
    654 {
    655     bool purged = o->wasPurged();
    656     bool purgeable = o->isPurgeable() && !purged;
    657     int pageSize = (o->encodedSize() + o->overheadSize() + 4095) & ~4095;
    658     count++;
    659     size += purged ? 0 : o->size();
    660     liveSize += o->hasClients() ? o->size() : 0;
    661     decodedSize += o->decodedSize();
    662     purgeableSize += purgeable ? pageSize : 0;
    663     purgedSize += purged ? pageSize : 0;
    664 }
    665 
    666 Cache::Statistics Cache::getStatistics()
    667 {
    668     Statistics stats;
    669     CachedResourceMap::iterator e = m_resources.end();
    670     for (CachedResourceMap::iterator i = m_resources.begin(); i != e; ++i) {
    671         CachedResource* resource = i->second;
    672         switch (resource->type()) {
    673         case CachedResource::ImageResource:
    674             stats.images.addResource(resource);
    675             break;
    676         case CachedResource::CSSStyleSheet:
    677             stats.cssStyleSheets.addResource(resource);
    678             break;
    679         case CachedResource::Script:
    680             stats.scripts.addResource(resource);
    681             break;
    682 #if ENABLE(XSLT)
    683         case CachedResource::XSLStyleSheet:
    684             stats.xslStyleSheets.addResource(resource);
    685             break;
    686 #endif
    687         case CachedResource::FontResource:
    688             stats.fonts.addResource(resource);
    689             break;
    690 #if ENABLE(XBL)
    691         case CachedResource::XBL:
    692             stats.xblDocs.addResource(resource)
    693             break;
    694 #endif
    695         default:
    696             break;
    697         }
    698     }
    699     return stats;
    700 }
    701 
    702 void Cache::setDisabled(bool disabled)
    703 {
    704     m_disabled = disabled;
    705     if (!m_disabled)
    706         return;
    707 
    708     for (;;) {
    709         CachedResourceMap::iterator i = m_resources.begin();
    710         if (i == m_resources.end())
    711             break;
    712         evict(i->second);
    713     }
    714 }
    715 
    716 #ifndef NDEBUG
    717 void Cache::dumpStats()
    718 {
    719     Statistics s = getStatistics();
    720     printf("%-11s %-11s %-11s %-11s %-11s %-11s %-11s\n", "", "Count", "Size", "LiveSize", "DecodedSize", "PurgeableSize", "PurgedSize");
    721     printf("%-11s %-11s %-11s %-11s %-11s %-11s %-11s\n", "-----------", "-----------", "-----------", "-----------", "-----------", "-----------", "-----------");
    722     printf("%-11s %11d %11d %11d %11d %11d %11d\n", "Images", s.images.count, s.images.size, s.images.liveSize, s.images.decodedSize, s.images.purgeableSize, s.images.purgedSize);
    723     printf("%-11s %11d %11d %11d %11d %11d %11d\n", "CSS", s.cssStyleSheets.count, s.cssStyleSheets.size, s.cssStyleSheets.liveSize, s.cssStyleSheets.decodedSize, s.cssStyleSheets.purgeableSize, s.cssStyleSheets.purgedSize);
    724 #if ENABLE(XSLT)
    725     printf("%-11s %11d %11d %11d %11d %11d %11d\n", "XSL", s.xslStyleSheets.count, s.xslStyleSheets.size, s.xslStyleSheets.liveSize, s.xslStyleSheets.decodedSize, s.xslStyleSheets.purgeableSize, s.xslStyleSheets.purgedSize);
    726 #endif
    727     printf("%-11s %11d %11d %11d %11d %11d %11d\n", "JavaScript", s.scripts.count, s.scripts.size, s.scripts.liveSize, s.scripts.decodedSize, s.scripts.purgeableSize, s.scripts.purgedSize);
    728     printf("%-11s %11d %11d %11d %11d %11d %11d\n", "Fonts", s.fonts.count, s.fonts.size, s.fonts.liveSize, s.fonts.decodedSize, s.fonts.purgeableSize, s.fonts.purgedSize);
    729     printf("%-11s %-11s %-11s %-11s %-11s %-11s %-11s\n\n", "-----------", "-----------", "-----------", "-----------", "-----------", "-----------", "-----------");
    730 }
    731 
    732 void Cache::dumpLRULists(bool includeLive) const
    733 {
    734     printf("LRU-SP lists in eviction order (Kilobytes decoded, Kilobytes encoded, Access count, Referenced):\n");
    735 
    736     int size = m_allResources.size();
    737     for (int i = size - 1; i >= 0; i--) {
    738         printf("\n\nList %d: ", i);
    739         CachedResource* current = m_allResources[i].m_tail;
    740         while (current) {
    741             CachedResource* prev = current->m_prevInAllResourcesList;
    742             if (includeLive || !current->hasClients())
    743                 printf("(%.1fK, %.1fK, %uA, %dR); ", current->decodedSize() / 1024.0f, (current->encodedSize() + current->overheadSize()) / 1024.0f, current->accessCount(), current->hasClients());
    744             current = prev;
    745         }
    746     }
    747 }
    748 #endif
    749 
    750 } // namespace WebCore
    751