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