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
49namespace WebCore {
50
51static const int cDefaultCacheCapacity = 8192 * 1024;
52static const Seconds cMinDelayBeforeLiveDecodedPrune { 1_s };
53static const float cTargetPrunePercentage = .95f; // Percentage of capacity toward which we prune, to avoid immediately pruning again.
54
55MemoryCache& MemoryCache::singleton()
56{
57 ASSERT(WTF::isMainThread());
58 static NeverDestroyed<MemoryCache> memoryCache;
59 return memoryCache;
60}
61
62MemoryCache::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
78auto MemoryCache::sessionResourceMap(PAL::SessionID sessionID) const -> CachedResourceMap*
79{
80 ASSERT(sessionID.isValid());
81 return m_sessionResources.get(sessionID);
82}
83
84auto 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
93bool 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
103URL 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
112bool 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
130void 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
165void 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
173CachedResource* 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
183CachedResource* 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
192unsigned 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
201unsigned 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
207static CachedImageClient& dummyCachedImageClient()
208{
209 static NeverDestroyed<CachedImageClient> client;
210 return client;
211}
212
213bool 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
227void 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
253void 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
264void 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
272void 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
282void 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
293void 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
347void 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
359void 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
425void 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
435void 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
466auto 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
480void 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
499void 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
508void 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
527void 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
550void 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
577void 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
591HashSet<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
610void MemoryCache::removeFromLiveDecodedResourcesList(CachedResource& resource)
611{
612 m_liveDecodedResources.remove(&resource);
613}
614
615void 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
622void MemoryCache::addToLiveResourcesSize(CachedResource& resource)
623{
624 m_liveSize += resource.size();
625 m_deadSize -= resource.size();
626}
627
628void MemoryCache::removeFromLiveResourcesSize(CachedResource& resource)
629{
630 m_liveSize -= resource.size();
631 m_deadSize += resource.size();
632}
633
634void 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
645void 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
661void 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
669MemoryCache::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
704void 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
717void MemoryCache::evictResources()
718{
719 if (disabled())
720 return;
721
722 setDisabled(true);
723 setDisabled(false);
724}
725
726void 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
736bool MemoryCache::needsPruning() const
737{
738 return m_liveSize + m_deadSize > m_capacity || m_deadSize > m_maxDeadCapacity;
739}
740
741void 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
750void MemoryCache::pruneSoon()
751{
752 if (m_pruneTimer.isActive())
753 return;
754 if (!needsPruning())
755 return;
756 m_pruneTimer.startOneShot(0_s);
757}
758
759void 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
788void 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