1/*
2    Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
3    Copyright (C) 2001 Dirk Mueller (mueller@kde.org)
4    Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
5    Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
6
7    This library is free software; you can redistribute it and/or
8    modify it under the terms of the GNU Library General Public
9    License as published by the Free Software Foundation; either
10    version 2 of the License, or (at your option) any later version.
11
12    This library is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Library General Public License for more details.
16
17    You should have received a copy of the GNU Library General Public License
18    along with this library; see the file COPYING.LIB.  If not, write to
19    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20    Boston, MA 02110-1301, USA.
21*/
22
23#include "config.h"
24#include "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
42using namespace std;
43
44namespace WebCore {
45
46static const int cDefaultCacheCapacity = 8192 * 1024;
47static const double cMinDelayBeforeLiveDecodedPrune = 1; // Seconds.
48static const float cTargetPrunePercentage = .95f; // Percentage of capacity toward which we prune, to avoid immediately pruning again.
49static const double cDefaultDecodedDataDeletionInterval = 0;
50
51Cache* cache()
52{
53    static Cache* staticCache = new Cache;
54    return staticCache;
55}
56
57Cache::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
70static 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
96CachedResource* 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
156CachedCSSStyleSheet* 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
185void 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
207void 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
233void Cache::revalidationFailed(CachedResource* revalidatingResource)
234{
235    ASSERT(revalidatingResource->resourceToRevalidate());
236    revalidatingResource->clearResourceToRevalidate();
237}
238
239CachedResource* 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
250unsigned 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
259unsigned 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
265void 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
309void 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
391void 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
401void 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
425void Cache::addDocLoader(DocLoader* docLoader)
426{
427    m_docLoaders.add(docLoader);
428}
429
430void Cache::removeDocLoader(DocLoader* docLoader)
431{
432    m_docLoaders.remove(docLoader);
433}
434
435static 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
453Cache::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
465void 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
512void 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
544void 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
563void 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
602void 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
630void Cache::addToLiveResourcesSize(CachedResource* resource)
631{
632    m_liveSize += resource->size();
633    m_deadSize -= resource->size();
634}
635
636void Cache::removeFromLiveResourcesSize(CachedResource* resource)
637{
638    m_liveSize -= resource->size();
639    m_deadSize += resource->size();
640}
641
642void 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
653void 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
666Cache::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
702void 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
717void 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
732void 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