| 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 "MemoryCache.h" |
| 25 | |
| 26 | #include "BitmapImage.h" |
| 27 | #include "CachedImage.h" |
| 28 | #include "CachedImageClient.h" |
| 29 | #include "CachedResource.h" |
| 30 | #include "CachedResourceHandle.h" |
| 31 | #include "Document.h" |
| 32 | #include "FrameLoader.h" |
| 33 | #include "FrameLoaderTypes.h" |
| 34 | #include "FrameView.h" |
| 35 | #include "Image.h" |
| 36 | #include "Logging.h" |
| 37 | #include "PublicSuffix.h" |
| 38 | #include "SharedBuffer.h" |
| 39 | #include "WorkerGlobalScope.h" |
| 40 | #include "WorkerLoaderProxy.h" |
| 41 | #include "WorkerThread.h" |
| 42 | #include <pal/Logging.h> |
| 43 | #include <stdio.h> |
| 44 | #include <wtf/MathExtras.h> |
| 45 | #include <wtf/NeverDestroyed.h> |
| 46 | #include <wtf/SetForScope.h> |
| 47 | #include <wtf/text/CString.h> |
| 48 | |
| 49 | namespace WebCore { |
| 50 | |
| 51 | static const int cDefaultCacheCapacity = 8192 * 1024; |
| 52 | static const Seconds cMinDelayBeforeLiveDecodedPrune { 1_s }; |
| 53 | static const float cTargetPrunePercentage = .95f; // Percentage of capacity toward which we prune, to avoid immediately pruning again. |
| 54 | |
| 55 | MemoryCache& MemoryCache::singleton() |
| 56 | { |
| 57 | ASSERT(WTF::isMainThread()); |
| 58 | static NeverDestroyed<MemoryCache> memoryCache; |
| 59 | return memoryCache; |
| 60 | } |
| 61 | |
| 62 | MemoryCache::MemoryCache() |
| 63 | : m_capacity(cDefaultCacheCapacity) |
| 64 | , m_maxDeadCapacity(cDefaultCacheCapacity) |
| 65 | , m_pruneTimer(*this, &MemoryCache::prune) |
| 66 | { |
| 67 | static_assert(sizeof(long long) > sizeof(unsigned), "Numerical overflow can happen when adjusting the size of the cached memory." ); |
| 68 | |
| 69 | static std::once_flag onceFlag; |
| 70 | std::call_once(onceFlag, [] { |
| 71 | PAL::registerNotifyCallback("com.apple.WebKit.showMemoryCache" , [] { |
| 72 | MemoryCache::singleton().dumpStats(); |
| 73 | MemoryCache::singleton().dumpLRULists(true); |
| 74 | }); |
| 75 | }); |
| 76 | } |
| 77 | |
| 78 | auto MemoryCache::sessionResourceMap(PAL::SessionID sessionID) const -> CachedResourceMap* |
| 79 | { |
| 80 | ASSERT(sessionID.isValid()); |
| 81 | return m_sessionResources.get(sessionID); |
| 82 | } |
| 83 | |
| 84 | auto MemoryCache::ensureSessionResourceMap(PAL::SessionID sessionID) -> CachedResourceMap& |
| 85 | { |
| 86 | ASSERT(sessionID.isValid()); |
| 87 | auto& map = m_sessionResources.add(sessionID, nullptr).iterator->value; |
| 88 | if (!map) |
| 89 | map = std::make_unique<CachedResourceMap>(); |
| 90 | return *map; |
| 91 | } |
| 92 | |
| 93 | bool MemoryCache::shouldRemoveFragmentIdentifier(const URL& originalURL) |
| 94 | { |
| 95 | if (!originalURL.hasFragmentIdentifier()) |
| 96 | return false; |
| 97 | // Strip away fragment identifier from HTTP URLs. |
| 98 | // Data URLs must be unmodified. For file and custom URLs clients may expect resources |
| 99 | // to be unique even when they differ by the fragment identifier only. |
| 100 | return originalURL.protocolIsInHTTPFamily(); |
| 101 | } |
| 102 | |
| 103 | URL MemoryCache::removeFragmentIdentifierIfNeeded(const URL& originalURL) |
| 104 | { |
| 105 | if (!shouldRemoveFragmentIdentifier(originalURL)) |
| 106 | return originalURL; |
| 107 | URL url = originalURL; |
| 108 | url.removeFragmentIdentifier(); |
| 109 | return url; |
| 110 | } |
| 111 | |
| 112 | bool MemoryCache::add(CachedResource& resource) |
| 113 | { |
| 114 | if (disabled()) |
| 115 | return false; |
| 116 | |
| 117 | ASSERT(WTF::isMainThread()); |
| 118 | |
| 119 | auto key = std::make_pair(resource.url(), resource.cachePartition()); |
| 120 | |
| 121 | ensureSessionResourceMap(resource.sessionID()).set(key, &resource); |
| 122 | resource.setInCache(true); |
| 123 | |
| 124 | resourceAccessed(resource); |
| 125 | |
| 126 | LOG(ResourceLoading, "MemoryCache::add Added '%.255s', resource %p\n" , resource.url().string().latin1().data(), &resource); |
| 127 | return true; |
| 128 | } |
| 129 | |
| 130 | void MemoryCache::revalidationSucceeded(CachedResource& revalidatingResource, const ResourceResponse& response) |
| 131 | { |
| 132 | ASSERT(response.source() == ResourceResponse::Source::MemoryCacheAfterValidation); |
| 133 | ASSERT(revalidatingResource.resourceToRevalidate()); |
| 134 | CachedResource& resource = *revalidatingResource.resourceToRevalidate(); |
| 135 | ASSERT(!resource.inCache()); |
| 136 | ASSERT(resource.isLoaded()); |
| 137 | |
| 138 | // Calling remove() can potentially delete revalidatingResource, which we use |
| 139 | // below. This mustn't be the case since revalidation means it is loaded |
| 140 | // and so canDelete() is false. |
| 141 | ASSERT(!revalidatingResource.canDelete()); |
| 142 | |
| 143 | remove(revalidatingResource); |
| 144 | |
| 145 | auto& resources = ensureSessionResourceMap(resource.sessionID()); |
| 146 | auto key = std::make_pair(resource.url(), resource.cachePartition()); |
| 147 | |
| 148 | ASSERT(!resources.get(key)); |
| 149 | resources.set(key, &resource); |
| 150 | resource.setInCache(true); |
| 151 | resource.updateResponseAfterRevalidation(response); |
| 152 | insertInLRUList(resource); |
| 153 | long long delta = resource.size(); |
| 154 | if (resource.decodedSize() && resource.hasClients()) |
| 155 | insertInLiveDecodedResourcesList(resource); |
| 156 | if (delta) |
| 157 | adjustSize(resource.hasClients(), delta); |
| 158 | |
| 159 | revalidatingResource.switchClientsToRevalidatedResource(); |
| 160 | ASSERT(!revalidatingResource.m_deleted); |
| 161 | // this deletes the revalidating resource |
| 162 | revalidatingResource.clearResourceToRevalidate(); |
| 163 | } |
| 164 | |
| 165 | void MemoryCache::revalidationFailed(CachedResource& revalidatingResource) |
| 166 | { |
| 167 | ASSERT(WTF::isMainThread()); |
| 168 | LOG(ResourceLoading, "Revalidation failed for %p" , &revalidatingResource); |
| 169 | ASSERT(revalidatingResource.resourceToRevalidate()); |
| 170 | revalidatingResource.clearResourceToRevalidate(); |
| 171 | } |
| 172 | |
| 173 | CachedResource* MemoryCache::resourceForRequest(const ResourceRequest& request, PAL::SessionID sessionID) |
| 174 | { |
| 175 | // FIXME: Change all clients to make sure HTTP(s) URLs have no fragment identifiers before calling here. |
| 176 | // CachedResourceLoader is now doing this. Add an assertion once all other clients are doing it too. |
| 177 | auto* resources = sessionResourceMap(sessionID); |
| 178 | if (!resources) |
| 179 | return nullptr; |
| 180 | return resourceForRequestImpl(request, *resources); |
| 181 | } |
| 182 | |
| 183 | CachedResource* MemoryCache::resourceForRequestImpl(const ResourceRequest& request, CachedResourceMap& resources) |
| 184 | { |
| 185 | ASSERT(WTF::isMainThread()); |
| 186 | URL url = removeFragmentIdentifierIfNeeded(request.url()); |
| 187 | |
| 188 | auto key = std::make_pair(url, request.cachePartition()); |
| 189 | return resources.get(key); |
| 190 | } |
| 191 | |
| 192 | unsigned MemoryCache::deadCapacity() const |
| 193 | { |
| 194 | // Dead resource capacity is whatever space is not occupied by live resources, bounded by an independent minimum and maximum. |
| 195 | unsigned capacity = m_capacity - std::min(m_liveSize, m_capacity); // Start with available capacity. |
| 196 | capacity = std::max(capacity, m_minDeadCapacity); // Make sure it's above the minimum. |
| 197 | capacity = std::min(capacity, m_maxDeadCapacity); // Make sure it's below the maximum. |
| 198 | return capacity; |
| 199 | } |
| 200 | |
| 201 | unsigned MemoryCache::liveCapacity() const |
| 202 | { |
| 203 | // Live resource capacity is whatever is left over after calculating dead resource capacity. |
| 204 | return m_capacity - deadCapacity(); |
| 205 | } |
| 206 | |
| 207 | static CachedImageClient& dummyCachedImageClient() |
| 208 | { |
| 209 | static NeverDestroyed<CachedImageClient> client; |
| 210 | return client; |
| 211 | } |
| 212 | |
| 213 | bool MemoryCache::addImageToCache(NativeImagePtr&& image, const URL& url, const String& domainForCachePartition, const PAL::SessionID& sessionID, const CookieJar* cookieJar) |
| 214 | { |
| 215 | ASSERT(image); |
| 216 | removeImageFromCache(url, domainForCachePartition); // Remove cache entry if it already exists. |
| 217 | |
| 218 | auto bitmapImage = BitmapImage::create(WTFMove(image), nullptr); |
| 219 | auto cachedImage = std::make_unique<CachedImage>(url, bitmapImage.ptr(), sessionID, cookieJar, domainForCachePartition); |
| 220 | |
| 221 | cachedImage->addClient(dummyCachedImageClient()); |
| 222 | cachedImage->setDecodedSize(bitmapImage->decodedSize()); |
| 223 | |
| 224 | return add(*cachedImage.release()); |
| 225 | } |
| 226 | |
| 227 | void MemoryCache::removeImageFromCache(const URL& url, const String& domainForCachePartition) |
| 228 | { |
| 229 | auto* resources = sessionResourceMap(PAL::SessionID::defaultSessionID()); |
| 230 | if (!resources) |
| 231 | return; |
| 232 | |
| 233 | auto key = std::make_pair(url, ResourceRequest::partitionName(domainForCachePartition)); |
| 234 | |
| 235 | CachedResource* resource = resources->get(key); |
| 236 | if (!resource) |
| 237 | return; |
| 238 | |
| 239 | // A resource exists and is not a manually cached image, so just remove it. |
| 240 | if (!is<CachedImage>(*resource) || !downcast<CachedImage>(*resource).isManuallyCached()) { |
| 241 | remove(*resource); |
| 242 | return; |
| 243 | } |
| 244 | |
| 245 | // Removing the last client of a CachedImage turns the resource |
| 246 | // into a dead resource which will eventually be evicted when |
| 247 | // dead resources are pruned. That might be immediately since |
| 248 | // removing the last client triggers a MemoryCache::prune, so the |
| 249 | // resource may be deleted after this call. |
| 250 | downcast<CachedImage>(*resource).removeClient(dummyCachedImageClient()); |
| 251 | } |
| 252 | |
| 253 | void MemoryCache::pruneLiveResources(bool shouldDestroyDecodedDataForAllLiveResources) |
| 254 | { |
| 255 | unsigned capacity = shouldDestroyDecodedDataForAllLiveResources ? 0 : liveCapacity(); |
| 256 | if (capacity && m_liveSize <= capacity) |
| 257 | return; |
| 258 | |
| 259 | unsigned targetSize = static_cast<unsigned>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again. |
| 260 | |
| 261 | pruneLiveResourcesToSize(targetSize, shouldDestroyDecodedDataForAllLiveResources); |
| 262 | } |
| 263 | |
| 264 | void MemoryCache::forEachResource(const WTF::Function<void(CachedResource&)>& function) |
| 265 | { |
| 266 | for (auto& unprotectedLRUList : m_allResources) { |
| 267 | for (auto& resource : copyToVector(*unprotectedLRUList)) |
| 268 | function(*resource); |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | void MemoryCache::forEachSessionResource(PAL::SessionID sessionID, const WTF::Function<void (CachedResource&)>& function) |
| 273 | { |
| 274 | auto it = m_sessionResources.find(sessionID); |
| 275 | if (it == m_sessionResources.end()) |
| 276 | return; |
| 277 | |
| 278 | for (auto& resource : copyToVector(it->value->values())) |
| 279 | function(*resource); |
| 280 | } |
| 281 | |
| 282 | void MemoryCache::destroyDecodedDataForAllImages() |
| 283 | { |
| 284 | MemoryCache::singleton().forEachResource([](CachedResource& resource) { |
| 285 | if (!resource.isImage()) |
| 286 | return; |
| 287 | |
| 288 | if (auto image = downcast<CachedImage>(resource).image()) |
| 289 | image->destroyDecodedData(); |
| 290 | }); |
| 291 | } |
| 292 | |
| 293 | void MemoryCache::pruneLiveResourcesToSize(unsigned targetSize, bool shouldDestroyDecodedDataForAllLiveResources) |
| 294 | { |
| 295 | if (m_inPruneResources) |
| 296 | return; |
| 297 | |
| 298 | LOG(ResourceLoading, "MemoryCache::pruneLiveResourcesToSize(%d, shouldDestroyDecodedDataForAllLiveResources = %d)" , targetSize, shouldDestroyDecodedDataForAllLiveResources); |
| 299 | |
| 300 | SetForScope<bool> reentrancyProtector(m_inPruneResources, true); |
| 301 | |
| 302 | MonotonicTime currentTime = FrameView::currentPaintTimeStamp(); |
| 303 | if (!currentTime) // In case prune is called directly, outside of a Frame paint. |
| 304 | currentTime = MonotonicTime::now(); |
| 305 | |
| 306 | // Destroy any decoded data in live objects that we can. |
| 307 | // Start from the head, since this is the least recently accessed of the objects. |
| 308 | |
| 309 | // The list might not be sorted by the m_lastDecodedAccessTime. The impact |
| 310 | // of this weaker invariant is minor as the below if statement to check the |
| 311 | // elapsedTime will evaluate to false as the currentTime will be a lot |
| 312 | // greater than the current->m_lastDecodedAccessTime. |
| 313 | // For more details see: https://bugs.webkit.org/show_bug.cgi?id=30209 |
| 314 | auto it = m_liveDecodedResources.begin(); |
| 315 | while (it != m_liveDecodedResources.end()) { |
| 316 | auto* current = *it; |
| 317 | |
| 318 | LOG(ResourceLoading, " live resource %p %.255s - loaded %d, decodedSize %u" , current, current->url().string().utf8().data(), current->isLoaded(), current->decodedSize()); |
| 319 | |
| 320 | // Increment the iterator now because the call to destroyDecodedData() below |
| 321 | // may cause a call to ListHashSet::remove() and invalidate the current |
| 322 | // iterator. Note that this is safe because unlike iteration of most |
| 323 | // WTF Hash data structures, iteration is guaranteed safe against mutation |
| 324 | // of the ListHashSet, except for removal of the item currently pointed to |
| 325 | // by a given iterator. |
| 326 | ++it; |
| 327 | |
| 328 | ASSERT(current->hasClients()); |
| 329 | if (current->isLoaded() && current->decodedSize()) { |
| 330 | // Check to see if the remaining resources are too new to prune. |
| 331 | Seconds elapsedTime = currentTime - current->m_lastDecodedAccessTime; |
| 332 | if (!shouldDestroyDecodedDataForAllLiveResources && elapsedTime < cMinDelayBeforeLiveDecodedPrune) { |
| 333 | LOG(ResourceLoading, " current time is less than min delay before pruning (%.3fms)" , elapsedTime.milliseconds()); |
| 334 | return; |
| 335 | } |
| 336 | |
| 337 | // Destroy our decoded data. This will remove us from m_liveDecodedResources, and possibly move us |
| 338 | // to a different LRU list in m_allResources. |
| 339 | current->destroyDecodedData(); |
| 340 | |
| 341 | if (targetSize && m_liveSize <= targetSize) |
| 342 | return; |
| 343 | } |
| 344 | } |
| 345 | } |
| 346 | |
| 347 | void MemoryCache::pruneDeadResources() |
| 348 | { |
| 349 | LOG(ResourceLoading, "MemoryCache::pruneDeadResources" ); |
| 350 | |
| 351 | unsigned capacity = deadCapacity(); |
| 352 | if (capacity && m_deadSize <= capacity) |
| 353 | return; |
| 354 | |
| 355 | unsigned targetSize = static_cast<unsigned>(capacity * cTargetPrunePercentage); // Cut by a percentage to avoid immediately pruning again. |
| 356 | pruneDeadResourcesToSize(targetSize); |
| 357 | } |
| 358 | |
| 359 | void MemoryCache::pruneDeadResourcesToSize(unsigned targetSize) |
| 360 | { |
| 361 | if (m_inPruneResources) |
| 362 | return; |
| 363 | |
| 364 | LOG(ResourceLoading, "MemoryCache::pruneDeadResourcesToSize(%d)" , targetSize); |
| 365 | |
| 366 | SetForScope<bool> reentrancyProtector(m_inPruneResources, true); |
| 367 | |
| 368 | if (targetSize && m_deadSize <= targetSize) |
| 369 | return; |
| 370 | |
| 371 | bool canShrinkLRULists = true; |
| 372 | for (int i = m_allResources.size() - 1; i >= 0; i--) { |
| 373 | // Make a copy of the LRUList first (and ref the resources) as calling |
| 374 | // destroyDecodedData() can alter the LRUList. |
| 375 | auto lruList = copyToVector(*m_allResources[i]); |
| 376 | |
| 377 | LOG(ResourceLoading, " lru list (size %lu) - flushing stage" , lruList.size()); |
| 378 | |
| 379 | // First flush all the decoded data in this queue. |
| 380 | // Remove from the head, since this is the least frequently accessed of the objects. |
| 381 | for (auto& resource : lruList) { |
| 382 | LOG(ResourceLoading, " lru resource %p - in cache %d, has clients %d, preloaded %d, loaded %d" , resource, resource->inCache(), resource->hasClients(), resource->isPreloaded(), resource->isLoaded()); |
| 383 | if (!resource->inCache()) |
| 384 | continue; |
| 385 | |
| 386 | if (!resource->hasClients() && !resource->isPreloaded() && resource->isLoaded()) { |
| 387 | // Destroy our decoded data. This will remove us from |
| 388 | // m_liveDecodedResources, and possibly move us to a different |
| 389 | // LRU list in m_allResources. |
| 390 | |
| 391 | LOG(ResourceLoading, " lru resource %p destroyDecodedData" , resource); |
| 392 | |
| 393 | resource->destroyDecodedData(); |
| 394 | |
| 395 | if (targetSize && m_deadSize <= targetSize) |
| 396 | return; |
| 397 | } |
| 398 | } |
| 399 | |
| 400 | LOG(ResourceLoading, " lru list (size %lu) - eviction stage" , lruList.size()); |
| 401 | |
| 402 | // Now evict objects from this list. |
| 403 | // Remove from the head, since this is the least frequently accessed of the objects. |
| 404 | for (auto& resource : lruList) { |
| 405 | LOG(ResourceLoading, " lru resource %p - in cache %d, has clients %d, preloaded %d, loaded %d" , resource, resource->inCache(), resource->hasClients(), resource->isPreloaded(), resource->isLoaded()); |
| 406 | if (!resource->inCache()) |
| 407 | continue; |
| 408 | |
| 409 | if (!resource->hasClients() && !resource->isPreloaded() && !resource->isCacheValidator()) { |
| 410 | remove(*resource); |
| 411 | if (targetSize && m_deadSize <= targetSize) |
| 412 | return; |
| 413 | } |
| 414 | } |
| 415 | |
| 416 | // Shrink the vector back down so we don't waste time inspecting |
| 417 | // empty LRU lists on future prunes. |
| 418 | if (!m_allResources[i]->isEmpty()) |
| 419 | canShrinkLRULists = false; |
| 420 | else if (canShrinkLRULists) |
| 421 | m_allResources.shrink(i); |
| 422 | } |
| 423 | } |
| 424 | |
| 425 | void MemoryCache::setCapacities(unsigned minDeadBytes, unsigned maxDeadBytes, unsigned totalBytes) |
| 426 | { |
| 427 | ASSERT(minDeadBytes <= maxDeadBytes); |
| 428 | ASSERT(maxDeadBytes <= totalBytes); |
| 429 | m_minDeadCapacity = minDeadBytes; |
| 430 | m_maxDeadCapacity = maxDeadBytes; |
| 431 | m_capacity = totalBytes; |
| 432 | prune(); |
| 433 | } |
| 434 | |
| 435 | void MemoryCache::remove(CachedResource& resource) |
| 436 | { |
| 437 | ASSERT(WTF::isMainThread()); |
| 438 | LOG(ResourceLoading, "Evicting resource %p for '%.255s' from cache" , &resource, resource.url().string().latin1().data()); |
| 439 | // The resource may have already been removed by someone other than our caller, |
| 440 | // who needed a fresh copy for a reload. See <http://bugs.webkit.org/show_bug.cgi?id=12479#c6>. |
| 441 | if (auto* resources = sessionResourceMap(resource.sessionID())) { |
| 442 | auto key = std::make_pair(resource.url(), resource.cachePartition()); |
| 443 | |
| 444 | if (resource.inCache()) { |
| 445 | // Remove resource from the resource map. |
| 446 | resources->remove(key); |
| 447 | resource.setInCache(false); |
| 448 | |
| 449 | // If the resource map is now empty, remove it from m_sessionResources. |
| 450 | if (resources->isEmpty()) |
| 451 | m_sessionResources.remove(resource.sessionID()); |
| 452 | |
| 453 | // Remove from the appropriate LRU list. |
| 454 | removeFromLRUList(resource); |
| 455 | removeFromLiveDecodedResourcesList(resource); |
| 456 | adjustSize(resource.hasClients(), -static_cast<long long>(resource.size())); |
| 457 | } else { |
| 458 | ASSERT(resources->get(key) != &resource); |
| 459 | LOG(ResourceLoading, " resource %p is not in cache" , &resource); |
| 460 | } |
| 461 | } |
| 462 | |
| 463 | resource.deleteIfPossible(); |
| 464 | } |
| 465 | |
| 466 | auto MemoryCache::lruListFor(CachedResource& resource) -> LRUList& |
| 467 | { |
| 468 | unsigned accessCount = std::max(resource.accessCount(), 1U); |
| 469 | unsigned queueIndex = WTF::fastLog2(resource.size() / accessCount); |
| 470 | #ifndef NDEBUG |
| 471 | resource.m_lruIndex = queueIndex; |
| 472 | #endif |
| 473 | |
| 474 | m_allResources.reserveCapacity(queueIndex + 1); |
| 475 | while (m_allResources.size() <= queueIndex) |
| 476 | m_allResources.uncheckedAppend(std::make_unique<LRUList>()); |
| 477 | return *m_allResources[queueIndex]; |
| 478 | } |
| 479 | |
| 480 | void MemoryCache::removeFromLRUList(CachedResource& resource) |
| 481 | { |
| 482 | // If we've never been accessed, then we're brand new and not in any list. |
| 483 | if (!resource.accessCount()) |
| 484 | return; |
| 485 | |
| 486 | #if !ASSERT_DISABLED |
| 487 | unsigned oldListIndex = resource.m_lruIndex; |
| 488 | #endif |
| 489 | |
| 490 | LRUList& list = lruListFor(resource); |
| 491 | |
| 492 | // Verify that the list we got is the list we want. |
| 493 | ASSERT(resource.m_lruIndex == oldListIndex); |
| 494 | |
| 495 | bool removed = list.remove(&resource); |
| 496 | ASSERT_UNUSED(removed, removed); |
| 497 | } |
| 498 | |
| 499 | void MemoryCache::insertInLRUList(CachedResource& resource) |
| 500 | { |
| 501 | ASSERT(resource.inCache()); |
| 502 | ASSERT(resource.accessCount() > 0); |
| 503 | |
| 504 | auto addResult = lruListFor(resource).add(&resource); |
| 505 | ASSERT_UNUSED(addResult, addResult.isNewEntry); |
| 506 | } |
| 507 | |
| 508 | void MemoryCache::resourceAccessed(CachedResource& resource) |
| 509 | { |
| 510 | ASSERT(resource.inCache()); |
| 511 | |
| 512 | // Need to make sure to remove before we increase the access count, since |
| 513 | // the queue will possibly change. |
| 514 | removeFromLRUList(resource); |
| 515 | |
| 516 | // If this is the first time the resource has been accessed, adjust the size of the cache to account for its initial size. |
| 517 | if (!resource.accessCount()) |
| 518 | adjustSize(resource.hasClients(), resource.size()); |
| 519 | |
| 520 | // Add to our access count. |
| 521 | resource.increaseAccessCount(); |
| 522 | |
| 523 | // Now insert into the new queue. |
| 524 | insertInLRUList(resource); |
| 525 | } |
| 526 | |
| 527 | void MemoryCache::removeResourcesWithOrigin(SecurityOrigin& origin) |
| 528 | { |
| 529 | String originPartition = ResourceRequest::partitionName(origin.host()); |
| 530 | |
| 531 | Vector<CachedResource*> resourcesWithOrigin; |
| 532 | for (auto& resources : m_sessionResources.values()) { |
| 533 | for (auto& keyValue : *resources) { |
| 534 | auto& resource = *keyValue.value; |
| 535 | auto& partitionName = keyValue.key.second; |
| 536 | if (partitionName == originPartition) { |
| 537 | resourcesWithOrigin.append(&resource); |
| 538 | continue; |
| 539 | } |
| 540 | auto resourceOrigin = SecurityOrigin::create(resource.url()); |
| 541 | if (resourceOrigin->equal(&origin)) |
| 542 | resourcesWithOrigin.append(&resource); |
| 543 | } |
| 544 | } |
| 545 | |
| 546 | for (auto* resource : resourcesWithOrigin) |
| 547 | remove(*resource); |
| 548 | } |
| 549 | |
| 550 | void MemoryCache::removeResourcesWithOrigins(PAL::SessionID sessionID, const HashSet<RefPtr<SecurityOrigin>>& origins) |
| 551 | { |
| 552 | auto* resourceMap = sessionResourceMap(sessionID); |
| 553 | if (!resourceMap) |
| 554 | return; |
| 555 | |
| 556 | HashSet<String> originPartitions; |
| 557 | |
| 558 | for (auto& origin : origins) |
| 559 | originPartitions.add(ResourceRequest::partitionName(origin->host())); |
| 560 | |
| 561 | Vector<CachedResource*> resourcesToRemove; |
| 562 | for (auto& keyValuePair : *resourceMap) { |
| 563 | auto& resource = *keyValuePair.value; |
| 564 | auto& partitionName = keyValuePair.key.second; |
| 565 | if (originPartitions.contains(partitionName)) { |
| 566 | resourcesToRemove.append(&resource); |
| 567 | continue; |
| 568 | } |
| 569 | if (origins.contains(SecurityOrigin::create(resource.url()).ptr())) |
| 570 | resourcesToRemove.append(&resource); |
| 571 | } |
| 572 | |
| 573 | for (auto& resource : resourcesToRemove) |
| 574 | remove(*resource); |
| 575 | } |
| 576 | |
| 577 | void MemoryCache::getOriginsWithCache(SecurityOriginSet& origins) |
| 578 | { |
| 579 | for (auto& resources : m_sessionResources.values()) { |
| 580 | for (auto& keyValue : *resources) { |
| 581 | auto& resource = *keyValue.value; |
| 582 | auto& partitionName = keyValue.key.second; |
| 583 | if (!partitionName.isEmpty()) |
| 584 | origins.add(SecurityOrigin::create("http"_s , partitionName, 0)); |
| 585 | else |
| 586 | origins.add(SecurityOrigin::create(resource.url())); |
| 587 | } |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | HashSet<RefPtr<SecurityOrigin>> MemoryCache::originsWithCache(PAL::SessionID sessionID) const |
| 592 | { |
| 593 | HashSet<RefPtr<SecurityOrigin>> origins; |
| 594 | |
| 595 | auto it = m_sessionResources.find(sessionID); |
| 596 | if (it != m_sessionResources.end()) { |
| 597 | for (auto& keyValue : *it->value) { |
| 598 | auto& resource = *keyValue.value; |
| 599 | auto& partitionName = keyValue.key.second; |
| 600 | if (!partitionName.isEmpty()) |
| 601 | origins.add(SecurityOrigin::create("http" , partitionName, 0)); |
| 602 | else |
| 603 | origins.add(SecurityOrigin::create(resource.url())); |
| 604 | } |
| 605 | } |
| 606 | |
| 607 | return origins; |
| 608 | } |
| 609 | |
| 610 | void MemoryCache::removeFromLiveDecodedResourcesList(CachedResource& resource) |
| 611 | { |
| 612 | m_liveDecodedResources.remove(&resource); |
| 613 | } |
| 614 | |
| 615 | void MemoryCache::insertInLiveDecodedResourcesList(CachedResource& resource) |
| 616 | { |
| 617 | // Make sure we aren't in the list already. |
| 618 | ASSERT(!m_liveDecodedResources.contains(&resource)); |
| 619 | m_liveDecodedResources.add(&resource); |
| 620 | } |
| 621 | |
| 622 | void MemoryCache::addToLiveResourcesSize(CachedResource& resource) |
| 623 | { |
| 624 | m_liveSize += resource.size(); |
| 625 | m_deadSize -= resource.size(); |
| 626 | } |
| 627 | |
| 628 | void MemoryCache::removeFromLiveResourcesSize(CachedResource& resource) |
| 629 | { |
| 630 | m_liveSize -= resource.size(); |
| 631 | m_deadSize += resource.size(); |
| 632 | } |
| 633 | |
| 634 | void MemoryCache::adjustSize(bool live, long long delta) |
| 635 | { |
| 636 | if (live) { |
| 637 | ASSERT(delta >= 0 || (static_cast<long long>(m_liveSize) + delta >= 0)); |
| 638 | m_liveSize += delta; |
| 639 | } else { |
| 640 | ASSERT(delta >= 0 || (static_cast<long long>(m_deadSize) + delta >= 0)); |
| 641 | m_deadSize += delta; |
| 642 | } |
| 643 | } |
| 644 | |
| 645 | void MemoryCache::removeRequestFromSessionCaches(ScriptExecutionContext& context, const ResourceRequest& request) |
| 646 | { |
| 647 | if (is<WorkerGlobalScope>(context)) { |
| 648 | downcast<WorkerGlobalScope>(context).thread().workerLoaderProxy().postTaskToLoader([request = request.isolatedCopy()] (ScriptExecutionContext& context) { |
| 649 | MemoryCache::removeRequestFromSessionCaches(context, request); |
| 650 | }); |
| 651 | return; |
| 652 | } |
| 653 | |
| 654 | auto& memoryCache = MemoryCache::singleton(); |
| 655 | for (auto& resources : memoryCache.m_sessionResources) { |
| 656 | if (CachedResource* resource = memoryCache.resourceForRequestImpl(request, *resources.value)) |
| 657 | memoryCache.remove(*resource); |
| 658 | } |
| 659 | } |
| 660 | |
| 661 | void MemoryCache::TypeStatistic::addResource(CachedResource& resource) |
| 662 | { |
| 663 | count++; |
| 664 | size += resource.size(); |
| 665 | liveSize += resource.hasClients() ? resource.size() : 0; |
| 666 | decodedSize += resource.decodedSize(); |
| 667 | } |
| 668 | |
| 669 | MemoryCache::Statistics MemoryCache::getStatistics() |
| 670 | { |
| 671 | Statistics stats; |
| 672 | |
| 673 | for (auto& resources : m_sessionResources.values()) { |
| 674 | for (auto* resource : resources->values()) { |
| 675 | switch (resource->type()) { |
| 676 | case CachedResource::Type::ImageResource: |
| 677 | stats.images.addResource(*resource); |
| 678 | break; |
| 679 | case CachedResource::Type::CSSStyleSheet: |
| 680 | stats.cssStyleSheets.addResource(*resource); |
| 681 | break; |
| 682 | case CachedResource::Type::Script: |
| 683 | stats.scripts.addResource(*resource); |
| 684 | break; |
| 685 | #if ENABLE(XSLT) |
| 686 | case CachedResource::Type::XSLStyleSheet: |
| 687 | stats.xslStyleSheets.addResource(*resource); |
| 688 | break; |
| 689 | #endif |
| 690 | #if ENABLE(SVG_FONTS) |
| 691 | case CachedResource::Type::SVGFontResource: |
| 692 | #endif |
| 693 | case CachedResource::Type::FontResource: |
| 694 | stats.fonts.addResource(*resource); |
| 695 | break; |
| 696 | default: |
| 697 | break; |
| 698 | } |
| 699 | } |
| 700 | } |
| 701 | return stats; |
| 702 | } |
| 703 | |
| 704 | void MemoryCache::setDisabled(bool disabled) |
| 705 | { |
| 706 | m_disabled = disabled; |
| 707 | if (!m_disabled) |
| 708 | return; |
| 709 | |
| 710 | while (!m_sessionResources.isEmpty()) { |
| 711 | auto& resources = *m_sessionResources.begin()->value; |
| 712 | ASSERT(!resources.isEmpty()); |
| 713 | remove(*resources.begin()->value); |
| 714 | } |
| 715 | } |
| 716 | |
| 717 | void MemoryCache::evictResources() |
| 718 | { |
| 719 | if (disabled()) |
| 720 | return; |
| 721 | |
| 722 | setDisabled(true); |
| 723 | setDisabled(false); |
| 724 | } |
| 725 | |
| 726 | void MemoryCache::evictResources(PAL::SessionID sessionID) |
| 727 | { |
| 728 | if (disabled()) |
| 729 | return; |
| 730 | |
| 731 | forEachSessionResource(sessionID, [this] (CachedResource& resource) { remove(resource); }); |
| 732 | |
| 733 | ASSERT(!m_sessionResources.contains(sessionID)); |
| 734 | } |
| 735 | |
| 736 | bool MemoryCache::needsPruning() const |
| 737 | { |
| 738 | return m_liveSize + m_deadSize > m_capacity || m_deadSize > m_maxDeadCapacity; |
| 739 | } |
| 740 | |
| 741 | void MemoryCache::prune() |
| 742 | { |
| 743 | if (!needsPruning()) |
| 744 | return; |
| 745 | |
| 746 | pruneDeadResources(); // Prune dead first, in case it was "borrowing" capacity from live. |
| 747 | pruneLiveResources(); |
| 748 | } |
| 749 | |
| 750 | void MemoryCache::pruneSoon() |
| 751 | { |
| 752 | if (m_pruneTimer.isActive()) |
| 753 | return; |
| 754 | if (!needsPruning()) |
| 755 | return; |
| 756 | m_pruneTimer.startOneShot(0_s); |
| 757 | } |
| 758 | |
| 759 | void MemoryCache::dumpStats() |
| 760 | { |
| 761 | Statistics s = getStatistics(); |
| 762 | WTFLogAlways("\nMemory Cache" ); |
| 763 | WTFLogAlways("%-13s %-13s %-13s %-13s %-13s\n" , "" , "Count" , "Size" , "LiveSize" , "DecodedSize" ); |
| 764 | WTFLogAlways("%-13s %-13s %-13s %-13s %-13s\n" , "-------------" , "-------------" , "-------------" , "-------------" , "-------------" ); |
| 765 | WTFLogAlways("%-13s %13d %13d %13d %13d\n" , "Images" , s.images.count, s.images.size, s.images.liveSize, s.images.decodedSize); |
| 766 | WTFLogAlways("%-13s %13d %13d %13d %13d\n" , "CSS" , s.cssStyleSheets.count, s.cssStyleSheets.size, s.cssStyleSheets.liveSize, s.cssStyleSheets.decodedSize); |
| 767 | #if ENABLE(XSLT) |
| 768 | WTFLogAlways("%-13s %13d %13d %13d %13d\n" , "XSL" , s.xslStyleSheets.count, s.xslStyleSheets.size, s.xslStyleSheets.liveSize, s.xslStyleSheets.decodedSize); |
| 769 | #endif |
| 770 | WTFLogAlways("%-13s %13d %13d %13d %13d\n" , "JavaScript" , s.scripts.count, s.scripts.size, s.scripts.liveSize, s.scripts.decodedSize); |
| 771 | WTFLogAlways("%-13s %13d %13d %13d %13d\n" , "Fonts" , s.fonts.count, s.fonts.size, s.fonts.liveSize, s.fonts.decodedSize); |
| 772 | WTFLogAlways("%-13s %-13s %-13s %-13s %-13s\n\n" , "-------------" , "-------------" , "-------------" , "-------------" , "-------------" ); |
| 773 | |
| 774 | unsigned countTotal = s.images.count + s.cssStyleSheets.count + s.scripts.count + s.fonts.count; |
| 775 | unsigned sizeTotal = s.images.size + s.cssStyleSheets.size + s.scripts.size + s.fonts.size; |
| 776 | unsigned liveSizeTotal = s.images.liveSize + s.cssStyleSheets.liveSize + s.scripts.liveSize + s.fonts.liveSize; |
| 777 | unsigned decodedSizeTotal = s.images.decodedSize + s.cssStyleSheets.decodedSize + s.scripts.decodedSize + s.fonts.decodedSize; |
| 778 | #if ENABLE(XSLT) |
| 779 | countTotal += s.xslStyleSheets.count; |
| 780 | sizeTotal += s.xslStyleSheets.size; |
| 781 | liveSizeTotal += s.xslStyleSheets.liveSize; |
| 782 | decodedSizeTotal += s.xslStyleSheets.decodedSize; |
| 783 | #endif |
| 784 | |
| 785 | WTFLogAlways("%-13s %13d %11.2fKB %11.2fKB %11.2fKB\n" , "Total" , countTotal, sizeTotal / 1024., liveSizeTotal / 1024., decodedSizeTotal / 1024.); |
| 786 | } |
| 787 | |
| 788 | void MemoryCache::dumpLRULists(bool includeLive) const |
| 789 | { |
| 790 | WTFLogAlways("LRU-SP lists in eviction order (Kilobytes decoded, Kilobytes encoded, Access count, Referenced):\n" ); |
| 791 | |
| 792 | int size = m_allResources.size(); |
| 793 | for (int i = size - 1; i >= 0; i--) { |
| 794 | WTFLogAlways("\nList %d:\n" , i); |
| 795 | for (auto* resource : *m_allResources[i]) { |
| 796 | if (includeLive || !resource->hasClients()) |
| 797 | WTFLogAlways(" %p %.255s %.1fK, %.1fK, accesses: %u, clients: %d\n" , resource, resource->url().string().utf8().data(), resource->decodedSize() / 1024.0f, (resource->encodedSize() + resource->overheadSize()) / 1024.0f, resource->accessCount(), resource->numberOfClients()); |
| 798 | } |
| 799 | } |
| 800 | } |
| 801 | |
| 802 | } // namespace WebCore |
| 803 | |