| OLD | NEW |
| (Empty) |
| 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 "core/fetch/MemoryCache.h" | |
| 24 | |
| 25 #include "core/fetch/ResourceLoadingLog.h" | |
| 26 #include "platform/instrumentation/tracing/TraceEvent.h" | |
| 27 #include "platform/weborigin/SecurityOrigin.h" | |
| 28 #include "platform/weborigin/SecurityOriginHash.h" | |
| 29 #include "public/platform/Platform.h" | |
| 30 #include "wtf/Assertions.h" | |
| 31 #include "wtf/AutoReset.h" | |
| 32 #include "wtf/CurrentTime.h" | |
| 33 #include "wtf/MathExtras.h" | |
| 34 #include "wtf/text/CString.h" | |
| 35 | |
| 36 namespace blink { | |
| 37 | |
| 38 static Persistent<MemoryCache>* gMemoryCache; | |
| 39 | |
| 40 static const unsigned cDefaultCacheCapacity = 8192 * 1024; | |
| 41 static const int cMinDelayBeforeLiveDecodedPrune = 1; // Seconds. | |
| 42 static const double cMaxPruneDeferralDelay = 0.5; // Seconds. | |
| 43 | |
| 44 // Percentage of capacity toward which we prune, to avoid immediately pruning | |
| 45 // again. | |
| 46 static const float cTargetPrunePercentage = .95f; | |
| 47 | |
| 48 MemoryCache* memoryCache() { | |
| 49 DCHECK(WTF::isMainThread()); | |
| 50 if (!gMemoryCache) | |
| 51 gMemoryCache = new Persistent<MemoryCache>(MemoryCache::create()); | |
| 52 return gMemoryCache->get(); | |
| 53 } | |
| 54 | |
| 55 MemoryCache* replaceMemoryCacheForTesting(MemoryCache* cache) { | |
| 56 memoryCache(); | |
| 57 MemoryCache* oldCache = gMemoryCache->release(); | |
| 58 *gMemoryCache = cache; | |
| 59 MemoryCacheDumpProvider::instance()->setMemoryCache(cache); | |
| 60 return oldCache; | |
| 61 } | |
| 62 | |
| 63 DEFINE_TRACE(MemoryCacheEntry) { | |
| 64 visitor->template registerWeakMembers<MemoryCacheEntry, | |
| 65 &MemoryCacheEntry::clearResourceWeak>( | |
| 66 this); | |
| 67 } | |
| 68 | |
| 69 void MemoryCacheEntry::clearResourceWeak(Visitor* visitor) { | |
| 70 if (!m_resource || ThreadHeap::isHeapObjectAlive(m_resource)) | |
| 71 return; | |
| 72 memoryCache()->remove(m_resource.get()); | |
| 73 m_resource.clear(); | |
| 74 } | |
| 75 | |
| 76 inline MemoryCache::MemoryCache() | |
| 77 : m_inPruneResources(false), | |
| 78 m_prunePending(false), | |
| 79 m_maxPruneDeferralDelay(cMaxPruneDeferralDelay), | |
| 80 m_pruneTimeStamp(0.0), | |
| 81 m_pruneFrameTimeStamp(0.0), | |
| 82 m_lastFramePaintTimeStamp(0.0), | |
| 83 m_capacity(cDefaultCacheCapacity), | |
| 84 m_delayBeforeLiveDecodedPrune(cMinDelayBeforeLiveDecodedPrune), | |
| 85 m_size(0) { | |
| 86 MemoryCacheDumpProvider::instance()->setMemoryCache(this); | |
| 87 if (MemoryCoordinator::isLowEndDevice()) | |
| 88 MemoryCoordinator::instance().registerClient(this); | |
| 89 } | |
| 90 | |
| 91 MemoryCache* MemoryCache::create() { | |
| 92 return new MemoryCache; | |
| 93 } | |
| 94 | |
| 95 MemoryCache::~MemoryCache() { | |
| 96 if (m_prunePending) | |
| 97 Platform::current()->currentThread()->removeTaskObserver(this); | |
| 98 } | |
| 99 | |
| 100 DEFINE_TRACE(MemoryCache) { | |
| 101 visitor->trace(m_resourceMaps); | |
| 102 MemoryCacheDumpClient::trace(visitor); | |
| 103 MemoryCoordinatorClient::trace(visitor); | |
| 104 } | |
| 105 | |
| 106 KURL MemoryCache::removeFragmentIdentifierIfNeeded(const KURL& originalURL) { | |
| 107 if (!originalURL.hasFragmentIdentifier()) | |
| 108 return originalURL; | |
| 109 // Strip away fragment identifier from HTTP URLs. Data URLs must be | |
| 110 // unmodified. For file and custom URLs clients may expect resources to be | |
| 111 // unique even when they differ by the fragment identifier only. | |
| 112 if (!originalURL.protocolIsInHTTPFamily()) | |
| 113 return originalURL; | |
| 114 KURL url = originalURL; | |
| 115 url.removeFragmentIdentifier(); | |
| 116 return url; | |
| 117 } | |
| 118 | |
| 119 String MemoryCache::defaultCacheIdentifier() { | |
| 120 return emptyString(); | |
| 121 } | |
| 122 | |
| 123 MemoryCache::ResourceMap* MemoryCache::ensureResourceMap( | |
| 124 const String& cacheIdentifier) { | |
| 125 if (!m_resourceMaps.contains(cacheIdentifier)) { | |
| 126 ResourceMapIndex::AddResult result = | |
| 127 m_resourceMaps.add(cacheIdentifier, new ResourceMap); | |
| 128 CHECK(result.isNewEntry); | |
| 129 } | |
| 130 return m_resourceMaps.get(cacheIdentifier); | |
| 131 } | |
| 132 | |
| 133 void MemoryCache::add(Resource* resource) { | |
| 134 DCHECK(resource); | |
| 135 ResourceMap* resources = ensureResourceMap(resource->cacheIdentifier()); | |
| 136 addInternal(resources, MemoryCacheEntry::create(resource)); | |
| 137 RESOURCE_LOADING_DVLOG(1) << "MemoryCache::add Added " | |
| 138 << resource->url().getString() << ", resource " | |
| 139 << resource; | |
| 140 } | |
| 141 | |
| 142 void MemoryCache::addInternal(ResourceMap* resourceMap, | |
| 143 MemoryCacheEntry* entry) { | |
| 144 DCHECK(WTF::isMainThread()); | |
| 145 DCHECK(resourceMap); | |
| 146 | |
| 147 Resource* resource = entry->resource(); | |
| 148 if (!resource) | |
| 149 return; | |
| 150 DCHECK(resource->url().isValid()); | |
| 151 | |
| 152 KURL url = removeFragmentIdentifierIfNeeded(resource->url()); | |
| 153 ResourceMap::iterator it = resourceMap->find(url); | |
| 154 if (it != resourceMap->end()) { | |
| 155 Resource* oldResource = it->value->resource(); | |
| 156 CHECK_NE(oldResource, resource); | |
| 157 update(oldResource, oldResource->size(), 0); | |
| 158 } | |
| 159 resourceMap->set(url, entry); | |
| 160 update(resource, 0, resource->size()); | |
| 161 } | |
| 162 | |
| 163 void MemoryCache::remove(Resource* resource) { | |
| 164 DCHECK(WTF::isMainThread()); | |
| 165 DCHECK(resource); | |
| 166 RESOURCE_LOADING_DVLOG(1) << "Evicting resource " << resource << " for " | |
| 167 << resource->url().getString() << " from cache"; | |
| 168 TRACE_EVENT1("blink", "MemoryCache::evict", "resource", | |
| 169 resource->url().getString().utf8()); | |
| 170 | |
| 171 ResourceMap* resources = m_resourceMaps.get(resource->cacheIdentifier()); | |
| 172 if (!resources) | |
| 173 return; | |
| 174 | |
| 175 KURL url = removeFragmentIdentifierIfNeeded(resource->url()); | |
| 176 ResourceMap::iterator it = resources->find(url); | |
| 177 if (it == resources->end() || it->value->resource() != resource) | |
| 178 return; | |
| 179 removeInternal(resources, it); | |
| 180 } | |
| 181 | |
| 182 void MemoryCache::removeInternal(ResourceMap* resourceMap, | |
| 183 const ResourceMap::iterator& it) { | |
| 184 DCHECK(WTF::isMainThread()); | |
| 185 DCHECK(resourceMap); | |
| 186 | |
| 187 Resource* resource = it->value->resource(); | |
| 188 DCHECK(resource); | |
| 189 | |
| 190 update(resource, resource->size(), 0); | |
| 191 resourceMap->remove(it); | |
| 192 } | |
| 193 | |
| 194 bool MemoryCache::contains(const Resource* resource) const { | |
| 195 if (!resource || resource->url().isEmpty()) | |
| 196 return false; | |
| 197 const ResourceMap* resources = | |
| 198 m_resourceMaps.get(resource->cacheIdentifier()); | |
| 199 if (!resources) | |
| 200 return false; | |
| 201 KURL url = removeFragmentIdentifierIfNeeded(resource->url()); | |
| 202 MemoryCacheEntry* entry = resources->get(url); | |
| 203 return entry && resource == entry->resource(); | |
| 204 } | |
| 205 | |
| 206 Resource* MemoryCache::resourceForURL(const KURL& resourceURL) const { | |
| 207 return resourceForURL(resourceURL, defaultCacheIdentifier()); | |
| 208 } | |
| 209 | |
| 210 Resource* MemoryCache::resourceForURL(const KURL& resourceURL, | |
| 211 const String& cacheIdentifier) const { | |
| 212 DCHECK(WTF::isMainThread()); | |
| 213 if (!resourceURL.isValid() || resourceURL.isNull()) | |
| 214 return nullptr; | |
| 215 DCHECK(!cacheIdentifier.isNull()); | |
| 216 const ResourceMap* resources = m_resourceMaps.get(cacheIdentifier); | |
| 217 if (!resources) | |
| 218 return nullptr; | |
| 219 MemoryCacheEntry* entry = | |
| 220 resources->get(removeFragmentIdentifierIfNeeded(resourceURL)); | |
| 221 if (!entry) | |
| 222 return nullptr; | |
| 223 return entry->resource(); | |
| 224 } | |
| 225 | |
| 226 HeapVector<Member<Resource>> MemoryCache::resourcesForURL( | |
| 227 const KURL& resourceURL) const { | |
| 228 DCHECK(WTF::isMainThread()); | |
| 229 KURL url = removeFragmentIdentifierIfNeeded(resourceURL); | |
| 230 HeapVector<Member<Resource>> results; | |
| 231 for (const auto& resourceMapIter : m_resourceMaps) { | |
| 232 if (MemoryCacheEntry* entry = resourceMapIter.value->get(url)) { | |
| 233 Resource* resource = entry->resource(); | |
| 234 DCHECK(resource); | |
| 235 results.push_back(resource); | |
| 236 } | |
| 237 } | |
| 238 return results; | |
| 239 } | |
| 240 | |
| 241 void MemoryCache::pruneResources(PruneStrategy strategy) { | |
| 242 DCHECK(!m_prunePending); | |
| 243 const size_t sizeLimit = (strategy == MaximalPrune) ? 0 : capacity(); | |
| 244 if (m_size <= sizeLimit) | |
| 245 return; | |
| 246 | |
| 247 // Cut by a percentage to avoid immediately pruning again. | |
| 248 size_t targetSize = static_cast<size_t>(sizeLimit * cTargetPrunePercentage); | |
| 249 | |
| 250 for (const auto& resourceMapIter : m_resourceMaps) { | |
| 251 for (const auto& resourceIter : *resourceMapIter.value) { | |
| 252 Resource* resource = resourceIter.value->resource(); | |
| 253 DCHECK(resource); | |
| 254 if (resource->isLoaded() && resource->decodedSize()) { | |
| 255 // Check to see if the remaining resources are too new to prune. | |
| 256 double elapsedTime = | |
| 257 m_pruneFrameTimeStamp - resourceIter.value->m_lastDecodedAccessTime; | |
| 258 if (strategy == AutomaticPrune && | |
| 259 elapsedTime < m_delayBeforeLiveDecodedPrune) | |
| 260 continue; | |
| 261 resource->prune(); | |
| 262 if (m_size <= targetSize) | |
| 263 return; | |
| 264 } | |
| 265 } | |
| 266 } | |
| 267 } | |
| 268 | |
| 269 void MemoryCache::setCapacity(size_t totalBytes) { | |
| 270 m_capacity = totalBytes; | |
| 271 prune(); | |
| 272 } | |
| 273 | |
| 274 void MemoryCache::update(Resource* resource, size_t oldSize, size_t newSize) { | |
| 275 if (!contains(resource)) | |
| 276 return; | |
| 277 ptrdiff_t delta = newSize - oldSize; | |
| 278 DCHECK(delta >= 0 || m_size >= static_cast<size_t>(-delta)); | |
| 279 m_size += delta; | |
| 280 } | |
| 281 | |
| 282 void MemoryCache::removeURLFromCache(const KURL& url) { | |
| 283 HeapVector<Member<Resource>> resources = resourcesForURL(url); | |
| 284 for (Resource* resource : resources) | |
| 285 remove(resource); | |
| 286 } | |
| 287 | |
| 288 void MemoryCache::TypeStatistic::addResource(Resource* o) { | |
| 289 count++; | |
| 290 size += o->size(); | |
| 291 decodedSize += o->decodedSize(); | |
| 292 encodedSize += o->encodedSize(); | |
| 293 overheadSize += o->overheadSize(); | |
| 294 encodedSizeDuplicatedInDataURLs += | |
| 295 o->url().protocolIsData() ? o->encodedSize() : 0; | |
| 296 } | |
| 297 | |
| 298 MemoryCache::Statistics MemoryCache::getStatistics() const { | |
| 299 Statistics stats; | |
| 300 for (const auto& resourceMapIter : m_resourceMaps) { | |
| 301 for (const auto& resourceIter : *resourceMapIter.value) { | |
| 302 Resource* resource = resourceIter.value->resource(); | |
| 303 DCHECK(resource); | |
| 304 switch (resource->getType()) { | |
| 305 case Resource::Image: | |
| 306 stats.images.addResource(resource); | |
| 307 break; | |
| 308 case Resource::CSSStyleSheet: | |
| 309 stats.cssStyleSheets.addResource(resource); | |
| 310 break; | |
| 311 case Resource::Script: | |
| 312 stats.scripts.addResource(resource); | |
| 313 break; | |
| 314 case Resource::XSLStyleSheet: | |
| 315 stats.xslStyleSheets.addResource(resource); | |
| 316 break; | |
| 317 case Resource::Font: | |
| 318 stats.fonts.addResource(resource); | |
| 319 break; | |
| 320 default: | |
| 321 stats.other.addResource(resource); | |
| 322 break; | |
| 323 } | |
| 324 } | |
| 325 } | |
| 326 return stats; | |
| 327 } | |
| 328 | |
| 329 void MemoryCache::evictResources(EvictResourcePolicy policy) { | |
| 330 for (auto resourceMapIter = m_resourceMaps.begin(); | |
| 331 resourceMapIter != m_resourceMaps.end();) { | |
| 332 ResourceMap* resources = resourceMapIter->value.get(); | |
| 333 HeapVector<Member<MemoryCacheEntry>> unusedPreloads; | |
| 334 for (auto resourceIter = resources->begin(); | |
| 335 resourceIter != resources->end(); resourceIter = resources->begin()) { | |
| 336 DCHECK(resourceIter.get()); | |
| 337 DCHECK(resourceIter->value.get()); | |
| 338 DCHECK(resourceIter->value->resource()); | |
| 339 Resource* resource = resourceIter->value->resource(); | |
| 340 DCHECK(resource); | |
| 341 if (policy != EvictAllResources && resource->isUnusedPreload()) { | |
| 342 // Store unused preloads aside, so they could be added back later. | |
| 343 // That is in order to avoid the performance impact of iterating over | |
| 344 // the same resource multiple times. | |
| 345 unusedPreloads.push_back(resourceIter->value.get()); | |
| 346 } | |
| 347 removeInternal(resources, resourceIter); | |
| 348 } | |
| 349 for (const auto& unusedPreload : unusedPreloads) { | |
| 350 addInternal(resources, unusedPreload); | |
| 351 } | |
| 352 // We may iterate multiple times over resourceMaps with unused preloads. | |
| 353 // That's extremely unlikely to have any real-life performance impact. | |
| 354 if (!resources->size()) { | |
| 355 m_resourceMaps.remove(resourceMapIter); | |
| 356 resourceMapIter = m_resourceMaps.begin(); | |
| 357 } else { | |
| 358 ++resourceMapIter; | |
| 359 } | |
| 360 } | |
| 361 } | |
| 362 | |
| 363 void MemoryCache::prune() { | |
| 364 TRACE_EVENT0("renderer", "MemoryCache::prune()"); | |
| 365 | |
| 366 if (m_inPruneResources) | |
| 367 return; | |
| 368 if (m_size <= m_capacity) // Fast path. | |
| 369 return; | |
| 370 | |
| 371 // To avoid burdening the current thread with repetitive pruning jobs, pruning | |
| 372 // is postponed until the end of the current task. If it has been more than | |
| 373 // m_maxPruneDeferralDelay since the last prune, then we prune immediately. If | |
| 374 // the current thread's run loop is not active, then pruning will happen | |
| 375 // immediately only if it has been over m_maxPruneDeferralDelay since the last | |
| 376 // prune. | |
| 377 double currentTime = WTF::currentTime(); | |
| 378 if (m_prunePending) { | |
| 379 if (currentTime - m_pruneTimeStamp >= m_maxPruneDeferralDelay) { | |
| 380 pruneNow(currentTime, AutomaticPrune); | |
| 381 } | |
| 382 } else { | |
| 383 if (currentTime - m_pruneTimeStamp >= m_maxPruneDeferralDelay) { | |
| 384 pruneNow(currentTime, AutomaticPrune); // Delay exceeded, prune now. | |
| 385 } else { | |
| 386 // Defer. | |
| 387 Platform::current()->currentThread()->addTaskObserver(this); | |
| 388 m_prunePending = true; | |
| 389 } | |
| 390 } | |
| 391 } | |
| 392 | |
| 393 void MemoryCache::willProcessTask() {} | |
| 394 | |
| 395 void MemoryCache::didProcessTask() { | |
| 396 // Perform deferred pruning | |
| 397 DCHECK(m_prunePending); | |
| 398 pruneNow(WTF::currentTime(), AutomaticPrune); | |
| 399 } | |
| 400 | |
| 401 void MemoryCache::pruneAll() { | |
| 402 double currentTime = WTF::currentTime(); | |
| 403 pruneNow(currentTime, MaximalPrune); | |
| 404 } | |
| 405 | |
| 406 void MemoryCache::pruneNow(double currentTime, PruneStrategy strategy) { | |
| 407 if (m_prunePending) { | |
| 408 m_prunePending = false; | |
| 409 Platform::current()->currentThread()->removeTaskObserver(this); | |
| 410 } | |
| 411 | |
| 412 AutoReset<bool> reentrancyProtector(&m_inPruneResources, true); | |
| 413 | |
| 414 pruneResources(strategy); | |
| 415 m_pruneFrameTimeStamp = m_lastFramePaintTimeStamp; | |
| 416 m_pruneTimeStamp = currentTime; | |
| 417 } | |
| 418 | |
| 419 void MemoryCache::updateFramePaintTimestamp() { | |
| 420 m_lastFramePaintTimeStamp = currentTime(); | |
| 421 } | |
| 422 | |
| 423 bool MemoryCache::onMemoryDump(WebMemoryDumpLevelOfDetail levelOfDetail, | |
| 424 WebProcessMemoryDump* memoryDump) { | |
| 425 if (levelOfDetail == WebMemoryDumpLevelOfDetail::Background) { | |
| 426 Statistics stats = getStatistics(); | |
| 427 WebMemoryAllocatorDump* dump1 = | |
| 428 memoryDump->createMemoryAllocatorDump("web_cache/Image_resources"); | |
| 429 dump1->addScalar("size", "bytes", | |
| 430 stats.images.encodedSize + stats.images.overheadSize); | |
| 431 WebMemoryAllocatorDump* dump2 = memoryDump->createMemoryAllocatorDump( | |
| 432 "web_cache/CSS stylesheet_resources"); | |
| 433 dump2->addScalar("size", "bytes", stats.cssStyleSheets.encodedSize + | |
| 434 stats.cssStyleSheets.overheadSize); | |
| 435 WebMemoryAllocatorDump* dump3 = | |
| 436 memoryDump->createMemoryAllocatorDump("web_cache/Script_resources"); | |
| 437 dump3->addScalar("size", "bytes", | |
| 438 stats.scripts.encodedSize + stats.scripts.overheadSize); | |
| 439 WebMemoryAllocatorDump* dump4 = memoryDump->createMemoryAllocatorDump( | |
| 440 "web_cache/XSL stylesheet_resources"); | |
| 441 dump4->addScalar("size", "bytes", stats.xslStyleSheets.encodedSize + | |
| 442 stats.xslStyleSheets.overheadSize); | |
| 443 WebMemoryAllocatorDump* dump5 = | |
| 444 memoryDump->createMemoryAllocatorDump("web_cache/Font_resources"); | |
| 445 dump5->addScalar("size", "bytes", | |
| 446 stats.fonts.encodedSize + stats.fonts.overheadSize); | |
| 447 WebMemoryAllocatorDump* dump6 = | |
| 448 memoryDump->createMemoryAllocatorDump("web_cache/Other_resources"); | |
| 449 dump6->addScalar("size", "bytes", | |
| 450 stats.other.encodedSize + stats.other.overheadSize); | |
| 451 return true; | |
| 452 } | |
| 453 | |
| 454 for (const auto& resourceMapIter : m_resourceMaps) { | |
| 455 for (const auto& resourceIter : *resourceMapIter.value) { | |
| 456 Resource* resource = resourceIter.value->resource(); | |
| 457 resource->onMemoryDump(levelOfDetail, memoryDump); | |
| 458 } | |
| 459 } | |
| 460 return true; | |
| 461 } | |
| 462 | |
| 463 void MemoryCache::onMemoryPressure(WebMemoryPressureLevel level) { | |
| 464 pruneAll(); | |
| 465 } | |
| 466 | |
| 467 } // namespace blink | |
| OLD | NEW |