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