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 | |