[WTF] Add makeUnique<T>, which ensures T is fast-allocated, makeUnique / makeUniqueWi...
[WebKit-https.git] / Source / WebKit / NetworkProcess / cache / NetworkCache.cpp
1 /*
2  * Copyright (C) 2014-2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "NetworkCache.h"
28
29 #include "Logging.h"
30 #include "NetworkCacheSpeculativeLoadManager.h"
31 #include "NetworkCacheStorage.h"
32 #include "NetworkProcess.h"
33 #include <WebCore/CacheValidation.h>
34 #include <WebCore/HTTPHeaderNames.h>
35 #include <WebCore/LowPowerModeNotifier.h>
36 #include <WebCore/NetworkStorageSession.h>
37 #include <WebCore/ResourceRequest.h>
38 #include <WebCore/ResourceResponse.h>
39 #include <WebCore/SharedBuffer.h>
40 #include <wtf/FileSystem.h>
41 #include <wtf/MainThread.h>
42 #include <wtf/NeverDestroyed.h>
43 #include <wtf/RunLoop.h>
44 #include <wtf/text/StringBuilder.h>
45
46 #if PLATFORM(COCOA)
47 #include <notify.h>
48 #endif
49
50 namespace WebKit {
51 namespace NetworkCache {
52
53 using namespace FileSystem;
54
55 static const AtomString& resourceType()
56 {
57     ASSERT(WTF::RunLoop::isMain());
58     static NeverDestroyed<const AtomString> resource("Resource", AtomString::ConstructFromLiteral);
59     return resource;
60 }
61
62 RefPtr<Cache> Cache::open(NetworkProcess& networkProcess, const String& cachePath, OptionSet<CacheOption> options)
63 {
64     auto storage = Storage::open(cachePath, options.contains(CacheOption::TestingMode) ? Storage::Mode::AvoidRandomness : Storage::Mode::Normal);
65
66     LOG(NetworkCache, "(NetworkProcess) opened cache storage, success %d", !!storage);
67
68     if (!storage)
69         return nullptr;
70
71     return adoptRef(*new Cache(networkProcess, storage.releaseNonNull(), options));
72 }
73
74 #if PLATFORM(GTK) || PLATFORM(WPE)
75 static void dumpFileChanged(Cache* cache)
76 {
77     cache->dumpContentsToFile();
78 }
79 #endif
80
81 Cache::Cache(NetworkProcess& networkProcess, Ref<Storage>&& storage, OptionSet<CacheOption> options)
82     : m_storage(WTFMove(storage))
83     , m_networkProcess(networkProcess)
84 {
85 #if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
86     if (options.contains(CacheOption::SpeculativeRevalidation)) {
87         m_lowPowerModeNotifier = makeUnique<WebCore::LowPowerModeNotifier>([this](bool isLowPowerModeEnabled) {
88             ASSERT(WTF::RunLoop::isMain());
89             if (isLowPowerModeEnabled)
90                 m_speculativeLoadManager = nullptr;
91             else {
92                 ASSERT(!m_speculativeLoadManager);
93                 m_speculativeLoadManager = makeUnique<SpeculativeLoadManager>(*this, m_storage.get());
94             }
95         });
96         if (!m_lowPowerModeNotifier->isLowPowerModeEnabled())
97             m_speculativeLoadManager = makeUnique<SpeculativeLoadManager>(*this, m_storage.get());
98     }
99 #endif
100
101     if (options.contains(CacheOption::RegisterNotify)) {
102 #if PLATFORM(COCOA)
103         // Triggers with "notifyutil -p com.apple.WebKit.Cache.dump".
104         int token;
105         notify_register_dispatch("com.apple.WebKit.Cache.dump", &token, dispatch_get_main_queue(), ^(int) {
106             dumpContentsToFile();
107         });
108 #endif
109 #if PLATFORM(GTK) || PLATFORM(WPE)
110         // Triggers with "touch $cachePath/dump".
111         CString dumpFilePath = fileSystemRepresentation(pathByAppendingComponent(m_storage->basePath(), "dump"));
112         GRefPtr<GFile> dumpFile = adoptGRef(g_file_new_for_path(dumpFilePath.data()));
113         GFileMonitor* monitor = g_file_monitor_file(dumpFile.get(), G_FILE_MONITOR_NONE, nullptr, nullptr);
114         g_signal_connect_swapped(monitor, "changed", G_CALLBACK(dumpFileChanged), this);
115 #endif
116     }
117 }
118
119 Cache::~Cache()
120 {
121 }
122
123 void Cache::setCapacity(size_t maximumSize)
124 {
125     m_storage->setCapacity(maximumSize);
126 }
127
128 Key Cache::makeCacheKey(const WebCore::ResourceRequest& request)
129 {
130     // FIXME: This implements minimal Range header disk cache support. We don't parse
131     // ranges so only the same exact range request will be served from the cache.
132     String range = request.httpHeaderField(WebCore::HTTPHeaderName::Range);
133     return { request.cachePartition(), resourceType(), range, request.url().string(), m_storage->salt() };
134 }
135
136 static bool cachePolicyAllowsExpired(WebCore::ResourceRequestCachePolicy policy)
137 {
138     switch (policy) {
139     case WebCore::ResourceRequestCachePolicy::ReturnCacheDataElseLoad:
140     case WebCore::ResourceRequestCachePolicy::ReturnCacheDataDontLoad:
141         return true;
142     case WebCore::ResourceRequestCachePolicy::UseProtocolCachePolicy:
143     case WebCore::ResourceRequestCachePolicy::ReloadIgnoringCacheData:
144     case WebCore::ResourceRequestCachePolicy::RefreshAnyCacheData:
145         return false;
146     case WebCore::ResourceRequestCachePolicy::DoNotUseAnyCache:
147         ASSERT_NOT_REACHED();
148         return false;
149     }
150     return false;
151 }
152
153 static bool responseHasExpired(const WebCore::ResourceResponse& response, WallTime timestamp, Optional<Seconds> maxStale)
154 {
155     if (response.cacheControlContainsNoCache())
156         return true;
157
158     auto age = WebCore::computeCurrentAge(response, timestamp);
159     auto lifetime = WebCore::computeFreshnessLifetimeForHTTPFamily(response, timestamp);
160
161     auto maximumStaleness = maxStale ? maxStale.value() : 0_ms;
162     bool hasExpired = age - lifetime > maximumStaleness;
163
164 #ifndef LOG_DISABLED
165     if (hasExpired)
166         LOG(NetworkCache, "(NetworkProcess) needsRevalidation hasExpired age=%f lifetime=%f max-stale=%g", age, lifetime, maxStale);
167 #endif
168
169     return hasExpired;
170 }
171
172 static bool responseNeedsRevalidation(const WebCore::ResourceResponse& response, const WebCore::ResourceRequest& request, WallTime timestamp)
173 {
174     auto requestDirectives = WebCore::parseCacheControlDirectives(request.httpHeaderFields());
175     if (requestDirectives.noCache)
176         return true;
177     // For requests we ignore max-age values other than zero.
178     if (requestDirectives.maxAge && requestDirectives.maxAge.value() == 0_ms)
179         return true;
180
181     return responseHasExpired(response, timestamp, requestDirectives.maxStale);
182 }
183
184 static UseDecision makeUseDecision(NetworkProcess& networkProcess, const Entry& entry, const WebCore::ResourceRequest& request)
185 {
186     // The request is conditional so we force revalidation from the network. We merely check the disk cache
187     // so we can update the cache entry.
188     if (request.isConditional() && !entry.redirectRequest())
189         return UseDecision::Validate;
190
191     if (!WebCore::verifyVaryingRequestHeaders(networkProcess.defaultStorageSession(), entry.varyingRequestHeaders(), request))
192         return UseDecision::NoDueToVaryingHeaderMismatch;
193
194     // We never revalidate in the case of a history navigation.
195     if (cachePolicyAllowsExpired(request.cachePolicy()))
196         return UseDecision::Use;
197
198     if (!responseNeedsRevalidation(entry.response(), request, entry.timeStamp()))
199         return UseDecision::Use;
200
201     if (!entry.response().hasCacheValidatorFields())
202         return UseDecision::NoDueToMissingValidatorFields;
203
204     return entry.redirectRequest() ? UseDecision::NoDueToExpiredRedirect : UseDecision::Validate;
205 }
206
207 static RetrieveDecision makeRetrieveDecision(const WebCore::ResourceRequest& request)
208 {
209     ASSERT(request.cachePolicy() != WebCore::ResourceRequestCachePolicy::DoNotUseAnyCache);
210
211     // FIXME: Support HEAD requests.
212     if (request.httpMethod() != "GET")
213         return RetrieveDecision::NoDueToHTTPMethod;
214     if (request.cachePolicy() == WebCore::ResourceRequestCachePolicy::ReloadIgnoringCacheData && !request.isConditional())
215         return RetrieveDecision::NoDueToReloadIgnoringCache;
216
217     return RetrieveDecision::Yes;
218 }
219
220 static bool isMediaMIMEType(const String& type)
221 {
222     return startsWithLettersIgnoringASCIICase(type, "video/") || startsWithLettersIgnoringASCIICase(type, "audio/");
223 }
224
225 static StoreDecision makeStoreDecision(const WebCore::ResourceRequest& originalRequest, const WebCore::ResourceResponse& response, size_t bodySize)
226 {
227     if (!originalRequest.url().protocolIsInHTTPFamily() || !response.isHTTP())
228         return StoreDecision::NoDueToProtocol;
229
230     if (originalRequest.httpMethod() != "GET")
231         return StoreDecision::NoDueToHTTPMethod;
232
233     auto requestDirectives = WebCore::parseCacheControlDirectives(originalRequest.httpHeaderFields());
234     if (requestDirectives.noStore)
235         return StoreDecision::NoDueToNoStoreRequest;
236
237     if (response.cacheControlContainsNoStore())
238         return StoreDecision::NoDueToNoStoreResponse;
239
240     if (!WebCore::isStatusCodeCacheableByDefault(response.httpStatusCode())) {
241         // http://tools.ietf.org/html/rfc7234#section-4.3.2
242         bool hasExpirationHeaders = response.expires() || response.cacheControlMaxAge();
243         bool expirationHeadersAllowCaching = WebCore::isStatusCodePotentiallyCacheable(response.httpStatusCode()) && hasExpirationHeaders;
244         if (!expirationHeadersAllowCaching)
245             return StoreDecision::NoDueToHTTPStatusCode;
246     }
247
248     bool isMainResource = originalRequest.requester() == WebCore::ResourceRequest::Requester::Main;
249     bool storeUnconditionallyForHistoryNavigation = isMainResource || originalRequest.priority() == WebCore::ResourceLoadPriority::VeryHigh;
250     if (!storeUnconditionallyForHistoryNavigation) {
251         auto now = WallTime::now();
252         bool hasNonZeroLifetime = !response.cacheControlContainsNoCache() && WebCore::computeFreshnessLifetimeForHTTPFamily(response, now) > 0_ms;
253
254         bool possiblyReusable = response.hasCacheValidatorFields() || hasNonZeroLifetime;
255         if (!possiblyReusable)
256             return StoreDecision::NoDueToUnlikelyToReuse;
257     }
258
259     // Media loaded via XHR is likely being used for MSE streaming (YouTube and Netflix for example).
260     // Streaming media fills the cache quickly and is unlikely to be reused.
261     // FIXME: We should introduce a separate media cache partition that doesn't affect other resources.
262     // FIXME: We should also make sure make the MSE paths are copy-free so we can use mapped buffers from disk effectively.
263     auto requester = originalRequest.requester();
264     bool isDefinitelyStreamingMedia = requester == WebCore::ResourceRequest::Requester::Media;
265     bool isLikelyStreamingMedia = requester == WebCore::ResourceRequest::Requester::XHR && isMediaMIMEType(response.mimeType());
266     if (isLikelyStreamingMedia || isDefinitelyStreamingMedia)
267         return StoreDecision::NoDueToStreamingMedia;
268
269     return StoreDecision::Yes;
270 }
271
272 #if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
273 static bool inline canRequestUseSpeculativeRevalidation(const WebCore::ResourceRequest& request)
274 {
275     if (request.isConditional())
276         return false;
277
278     if (request.requester() == WebCore::ResourceRequest::Requester::XHR || request.requester() == WebCore::ResourceRequest::Requester::Fetch)
279         return false;
280
281     switch (request.cachePolicy()) {
282     case WebCore::ResourceRequestCachePolicy::ReturnCacheDataElseLoad:
283     case WebCore::ResourceRequestCachePolicy::ReturnCacheDataDontLoad:
284     case WebCore::ResourceRequestCachePolicy::ReloadIgnoringCacheData:
285         return false;
286     case WebCore::ResourceRequestCachePolicy::UseProtocolCachePolicy:
287     case WebCore::ResourceRequestCachePolicy::RefreshAnyCacheData:
288         return true;
289     case WebCore::ResourceRequestCachePolicy::DoNotUseAnyCache:
290         ASSERT_NOT_REACHED();
291         return false;
292     }
293     return false;
294 }
295 #endif
296
297 void Cache::retrieve(const WebCore::ResourceRequest& request, const GlobalFrameID& frameID, RetrieveCompletionHandler&& completionHandler)
298 {
299     ASSERT(request.url().protocolIsInHTTPFamily());
300
301     LOG(NetworkCache, "(NetworkProcess) retrieving %s priority %d", request.url().string().ascii().data(), static_cast<int>(request.priority()));
302
303     Key storageKey = makeCacheKey(request);
304     auto priority = static_cast<unsigned>(request.priority());
305
306     RetrieveInfo info;
307     info.startTime = MonotonicTime::now();
308     info.priority = priority;
309
310 #if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
311     bool canUseSpeculativeRevalidation = m_speculativeLoadManager && canRequestUseSpeculativeRevalidation(request);
312     if (canUseSpeculativeRevalidation)
313         m_speculativeLoadManager->registerLoad(frameID, request, storageKey);
314 #endif
315
316     auto retrieveDecision = makeRetrieveDecision(request);
317     if (retrieveDecision != RetrieveDecision::Yes) {
318         completeRetrieve(WTFMove(completionHandler), nullptr, info);
319         return;
320     }
321
322 #if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
323     if (canUseSpeculativeRevalidation && m_speculativeLoadManager->canRetrieve(storageKey, request, frameID)) {
324         m_speculativeLoadManager->retrieve(storageKey, [networkProcess = makeRef(networkProcess()), request, completionHandler = WTFMove(completionHandler), info = WTFMove(info)](std::unique_ptr<Entry> entry) mutable {
325             info.wasSpeculativeLoad = true;
326             if (entry && WebCore::verifyVaryingRequestHeaders(networkProcess->defaultStorageSession(), entry->varyingRequestHeaders(), request))
327                 completeRetrieve(WTFMove(completionHandler), WTFMove(entry), info);
328             else
329                 completeRetrieve(WTFMove(completionHandler), nullptr, info);
330         });
331         return;
332     }
333 #endif
334
335     m_storage->retrieve(storageKey, priority, [request, completionHandler = WTFMove(completionHandler), info = WTFMove(info), storageKey, networkProcess = makeRef(networkProcess())](auto record, auto timings) mutable {
336         info.storageTimings = timings;
337
338         if (!record) {
339             LOG(NetworkCache, "(NetworkProcess) not found in storage");
340
341             completeRetrieve(WTFMove(completionHandler), nullptr, info);
342             return false;
343         }
344
345         ASSERT(record->key == storageKey);
346
347         auto entry = Entry::decodeStorageRecord(*record);
348
349         auto useDecision = entry ? makeUseDecision(networkProcess, *entry, request) : UseDecision::NoDueToDecodeFailure;
350         switch (useDecision) {
351         case UseDecision::Use:
352             break;
353         case UseDecision::Validate:
354             entry->setNeedsValidation(true);
355             break;
356         default:
357             entry = nullptr;
358         };
359
360 #if !LOG_DISABLED
361         auto elapsed = MonotonicTime::now() - info.startTime;
362         LOG(NetworkCache, "(NetworkProcess) retrieve complete useDecision=%d priority=%d time=%" PRIi64 "ms", static_cast<int>(useDecision), static_cast<int>(request.priority()), elapsed.millisecondsAs<int64_t>());
363 #endif
364         completeRetrieve(WTFMove(completionHandler), WTFMove(entry), info);
365
366         return useDecision != UseDecision::NoDueToDecodeFailure;
367     });
368 }
369
370 void Cache::completeRetrieve(RetrieveCompletionHandler&& handler, std::unique_ptr<Entry> entry, RetrieveInfo& info)
371 {
372     info.completionTime = MonotonicTime::now();
373     handler(WTFMove(entry), info);
374 }
375     
376 std::unique_ptr<Entry> Cache::makeEntry(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, RefPtr<WebCore::SharedBuffer>&& responseData)
377 {
378     return makeUnique<Entry>(makeCacheKey(request), response, WTFMove(responseData), WebCore::collectVaryingRequestHeaders(networkProcess().defaultStorageSession(), request, response));
379 }
380
381 std::unique_ptr<Entry> Cache::makeRedirectEntry(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, const WebCore::ResourceRequest& redirectRequest)
382 {
383     return makeUnique<Entry>(makeCacheKey(request), response, redirectRequest, WebCore::collectVaryingRequestHeaders(networkProcess().defaultStorageSession(), request, response));
384 }
385
386 std::unique_ptr<Entry> Cache::store(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, RefPtr<WebCore::SharedBuffer>&& responseData, Function<void(MappedBody&)>&& completionHandler)
387 {
388     ASSERT(responseData);
389
390     LOG(NetworkCache, "(NetworkProcess) storing %s, partition %s", request.url().string().latin1().data(), makeCacheKey(request).partition().latin1().data());
391
392     StoreDecision storeDecision = makeStoreDecision(request, response, responseData ? responseData->size() : 0);
393     if (storeDecision != StoreDecision::Yes) {
394         LOG(NetworkCache, "(NetworkProcess) didn't store, storeDecision=%d", static_cast<int>(storeDecision));
395         auto key = makeCacheKey(request);
396
397         auto isSuccessfulRevalidation = response.httpStatusCode() == 304;
398         if (!isSuccessfulRevalidation) {
399             // Make sure we don't keep a stale entry in the cache.
400             remove(key);
401         }
402
403         return nullptr;
404     }
405
406     auto cacheEntry = makeEntry(request, response, WTFMove(responseData));
407     auto record = cacheEntry->encodeAsStorageRecord();
408
409     m_storage->store(record, [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](const Data& bodyData) mutable {
410         MappedBody mappedBody;
411 #if ENABLE(SHAREABLE_RESOURCE)
412         if (auto sharedMemory = bodyData.tryCreateSharedMemory()) {
413             mappedBody.shareableResource = ShareableResource::create(sharedMemory.releaseNonNull(), 0, bodyData.size());
414             ASSERT(mappedBody.shareableResource);
415             mappedBody.shareableResource->createHandle(mappedBody.shareableResourceHandle);
416         }
417 #endif
418         completionHandler(mappedBody);
419         LOG(NetworkCache, "(NetworkProcess) stored");
420     });
421
422     return cacheEntry;
423 }
424
425 std::unique_ptr<Entry> Cache::storeRedirect(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, const WebCore::ResourceRequest& redirectRequest, Optional<Seconds> maxAgeCap)
426 {
427     LOG(NetworkCache, "(NetworkProcess) storing redirect %s -> %s", request.url().string().latin1().data(), redirectRequest.url().string().latin1().data());
428
429     StoreDecision storeDecision = makeStoreDecision(request, response, 0);
430     if (storeDecision != StoreDecision::Yes) {
431         LOG(NetworkCache, "(NetworkProcess) didn't store redirect, storeDecision=%d", static_cast<int>(storeDecision));
432         return nullptr;
433     }
434
435     auto cacheEntry = makeRedirectEntry(request, response, redirectRequest);
436
437 #if ENABLE(RESOURCE_LOAD_STATISTICS)
438     if (maxAgeCap) {
439         LOG(NetworkCache, "(NetworkProcess) capping max age for redirect %s -> %s", request.url().string().latin1().data(), redirectRequest.url().string().latin1().data());
440         cacheEntry->capMaxAge(maxAgeCap.value());
441     }
442 #else
443     UNUSED_PARAM(maxAgeCap);
444 #endif
445
446     auto record = cacheEntry->encodeAsStorageRecord();
447
448     m_storage->store(record, nullptr);
449     
450     return cacheEntry;
451 }
452
453 std::unique_ptr<Entry> Cache::update(const WebCore::ResourceRequest& originalRequest, const GlobalFrameID& frameID, const Entry& existingEntry, const WebCore::ResourceResponse& validatingResponse)
454 {
455     LOG(NetworkCache, "(NetworkProcess) updating %s", originalRequest.url().string().latin1().data());
456
457     WebCore::ResourceResponse response = existingEntry.response();
458     WebCore::updateResponseHeadersAfterRevalidation(response, validatingResponse);
459
460     auto updateEntry = makeUnique<Entry>(existingEntry.key(), response, existingEntry.buffer(), WebCore::collectVaryingRequestHeaders(networkProcess().defaultStorageSession(), originalRequest, response));
461     auto updateRecord = updateEntry->encodeAsStorageRecord();
462
463     m_storage->store(updateRecord, { });
464
465     return updateEntry;
466 }
467
468 void Cache::remove(const Key& key)
469 {
470     m_storage->remove(key);
471 }
472
473 void Cache::remove(const WebCore::ResourceRequest& request)
474 {
475     remove(makeCacheKey(request));
476 }
477
478 void Cache::remove(const Vector<Key>& keys, Function<void()>&& completionHandler)
479 {
480     m_storage->remove(keys, WTFMove(completionHandler));
481 }
482
483 void Cache::traverse(Function<void(const TraversalEntry*)>&& traverseHandler)
484 {
485     // Protect against clients making excessive traversal requests.
486     const unsigned maximumTraverseCount = 3;
487     if (m_traverseCount >= maximumTraverseCount) {
488         WTFLogAlways("Maximum parallel cache traverse count exceeded. Ignoring traversal request.");
489
490         RunLoop::main().dispatch([traverseHandler = WTFMove(traverseHandler)] () mutable {
491             traverseHandler(nullptr);
492         });
493         return;
494     }
495
496     ++m_traverseCount;
497
498     m_storage->traverse(resourceType(), { }, [this, protectedThis = makeRef(*this), traverseHandler = WTFMove(traverseHandler)] (const Storage::Record* record, const Storage::RecordInfo& recordInfo) mutable {
499         if (!record) {
500             --m_traverseCount;
501             traverseHandler(nullptr);
502             return;
503         }
504
505         auto entry = Entry::decodeStorageRecord(*record);
506         if (!entry)
507             return;
508
509         TraversalEntry traversalEntry { *entry, recordInfo };
510         traverseHandler(&traversalEntry);
511     });
512 }
513
514 String Cache::dumpFilePath() const
515 {
516     return pathByAppendingComponent(m_storage->versionPath(), "dump.json");
517 }
518
519 void Cache::dumpContentsToFile()
520 {
521     auto fd = openFile(dumpFilePath(), FileOpenMode::Write);
522     if (!isHandleValid(fd))
523         return;
524     auto prologue = String("{\n\"entries\": [\n").utf8();
525     writeToFile(fd, prologue.data(), prologue.length());
526
527     struct Totals {
528         unsigned count { 0 };
529         double worth { 0 };
530         size_t bodySize { 0 };
531     };
532     Totals totals;
533     auto flags = { Storage::TraverseFlag::ComputeWorth, Storage::TraverseFlag::ShareCount };
534     size_t capacity = m_storage->capacity();
535     m_storage->traverse(resourceType(), flags, [fd, totals, capacity](const Storage::Record* record, const Storage::RecordInfo& info) mutable {
536         if (!record) {
537             StringBuilder epilogue;
538             epilogue.appendLiteral("{}\n],\n");
539             epilogue.appendLiteral("\"totals\": {\n");
540             epilogue.appendLiteral("\"capacity\": ");
541             epilogue.appendNumber(capacity);
542             epilogue.appendLiteral(",\n");
543             epilogue.appendLiteral("\"count\": ");
544             epilogue.appendNumber(totals.count);
545             epilogue.appendLiteral(",\n");
546             epilogue.appendLiteral("\"bodySize\": ");
547             epilogue.appendNumber(totals.bodySize);
548             epilogue.appendLiteral(",\n");
549             epilogue.appendLiteral("\"averageWorth\": ");
550             epilogue.appendFixedPrecisionNumber(totals.count ? totals.worth / totals.count : 0);
551             epilogue.appendLiteral("\n");
552             epilogue.appendLiteral("}\n}\n");
553             auto writeData = epilogue.toString().utf8();
554             writeToFile(fd, writeData.data(), writeData.length());
555             closeFile(fd);
556             return;
557         }
558         auto entry = Entry::decodeStorageRecord(*record);
559         if (!entry)
560             return;
561         ++totals.count;
562         totals.worth += info.worth;
563         totals.bodySize += info.bodySize;
564
565         StringBuilder json;
566         entry->asJSON(json, info);
567         json.appendLiteral(",\n");
568         auto writeData = json.toString().utf8();
569         writeToFile(fd, writeData.data(), writeData.length());
570     });
571 }
572
573 void Cache::deleteDumpFile()
574 {
575     WorkQueue::create("com.apple.WebKit.Cache.delete")->dispatch([path = dumpFilePath().isolatedCopy()] {
576         deleteFile(path);
577     });
578 }
579
580 void Cache::clear(WallTime modifiedSince, Function<void()>&& completionHandler)
581 {
582     LOG(NetworkCache, "(NetworkProcess) clearing cache");
583
584     String anyType;
585     m_storage->clear(anyType, modifiedSince, WTFMove(completionHandler));
586
587     deleteDumpFile();
588 }
589
590 void Cache::clear()
591 {
592     clear(-WallTime::infinity(), nullptr);
593 }
594
595 String Cache::recordsPath() const
596 {
597     return m_storage->recordsPath();
598 }
599
600 void Cache::retrieveData(const DataKey& dataKey, Function<void(const uint8_t*, size_t)> completionHandler)
601 {
602     Key key { dataKey, m_storage->salt() };
603     m_storage->retrieve(key, 4, [completionHandler = WTFMove(completionHandler)] (auto record, auto) mutable {
604         if (!record || !record->body.size()) {
605             completionHandler(nullptr, 0);
606             return true;
607         }
608         completionHandler(record->body.data(), record->body.size());
609         return true;
610     });
611 }
612
613 void Cache::storeData(const DataKey& dataKey, const uint8_t* data, size_t size)
614 {
615     Key key { dataKey, m_storage->salt() };
616     Storage::Record record { key, WallTime::now(), { }, Data { data, size }, { } };
617     m_storage->store(record, { });
618 }
619
620 }
621 }