Stop using PassRefPtr in ShareableResource
[WebKit-https.git] / Source / WebKit2 / NetworkProcess / cache / NetworkCache.cpp
1 /*
2  * Copyright (C) 2014-2015 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 #if ENABLE(NETWORK_CACHE)
30
31 #include "Logging.h"
32 #include "NetworkCacheSpeculativeLoadManager.h"
33 #include "NetworkCacheStatistics.h"
34 #include "NetworkCacheStorage.h"
35 #include <WebCore/CacheValidation.h>
36 #include <WebCore/FileSystem.h>
37 #include <WebCore/HTTPHeaderNames.h>
38 #include <WebCore/NetworkStorageSession.h>
39 #include <WebCore/PlatformCookieJar.h>
40 #include <WebCore/ResourceRequest.h>
41 #include <WebCore/ResourceResponse.h>
42 #include <WebCore/SharedBuffer.h>
43 #include <wtf/MainThread.h>
44 #include <wtf/NeverDestroyed.h>
45 #include <wtf/RunLoop.h>
46 #include <wtf/text/StringBuilder.h>
47
48 #if PLATFORM(COCOA)
49 #include <notify.h>
50 #endif
51
52 namespace WebKit {
53 namespace NetworkCache {
54
55 static const AtomicString& resourceType()
56 {
57     ASSERT(WTF::isMainThread());
58     static NeverDestroyed<const AtomicString> resource("Resource", AtomicString::ConstructFromLiteral);
59     return resource;
60 }
61
62 Cache& singleton()
63 {
64     static NeverDestroyed<Cache> instance;
65     return instance;
66 }
67
68 #if PLATFORM(GTK)
69 static void dumpFileChanged(Cache* cache)
70 {
71     cache->dumpContentsToFile();
72 }
73 #endif
74
75 bool Cache::initialize(const String& cachePath, const Parameters& parameters)
76 {
77     m_storage = Storage::open(cachePath);
78
79 #if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
80     if (parameters.enableNetworkCacheSpeculativeRevalidation)
81         m_speculativeLoadManager = std::make_unique<SpeculativeLoadManager>(*m_storage);
82 #endif
83
84     if (parameters.enableEfficacyLogging)
85         m_statistics = Statistics::open(cachePath);
86
87 #if PLATFORM(COCOA)
88     // Triggers with "notifyutil -p com.apple.WebKit.Cache.dump".
89     if (m_storage) {
90         int token;
91         notify_register_dispatch("com.apple.WebKit.Cache.dump", &token, dispatch_get_main_queue(), ^(int) {
92             dumpContentsToFile();
93         });
94     }
95 #endif
96 #if PLATFORM(GTK)
97     // Triggers with "touch $cachePath/dump".
98     if (m_storage) {
99         CString dumpFilePath = WebCore::fileSystemRepresentation(WebCore::pathByAppendingComponent(m_storage->basePath(), "dump"));
100         GRefPtr<GFile> dumpFile = adoptGRef(g_file_new_for_path(dumpFilePath.data()));
101         GFileMonitor* monitor = g_file_monitor_file(dumpFile.get(), G_FILE_MONITOR_NONE, nullptr, nullptr);
102         g_signal_connect_swapped(monitor, "changed", G_CALLBACK(dumpFileChanged), this);
103     }
104 #endif
105
106     LOG(NetworkCache, "(NetworkProcess) opened cache storage, success %d", !!m_storage);
107     return !!m_storage;
108 }
109
110 void Cache::setCapacity(size_t maximumSize)
111 {
112     if (!m_storage)
113         return;
114     m_storage->setCapacity(maximumSize);
115 }
116
117 static Key makeCacheKey(const WebCore::ResourceRequest& request)
118 {
119 #if ENABLE(CACHE_PARTITIONING)
120     String partition = request.cachePartition();
121 #else
122     String partition;
123 #endif
124
125     // FIXME: This implements minimal Range header disk cache support. We don't parse
126     // ranges so only the same exact range request will be served from the cache.
127     String range = request.httpHeaderField(WebCore::HTTPHeaderName::Range);
128     return { partition, resourceType(), range, request.url().string() };
129 }
130
131 static bool cachePolicyAllowsExpired(WebCore::ResourceRequestCachePolicy policy)
132 {
133     switch (policy) {
134     case WebCore::ReturnCacheDataElseLoad:
135     case WebCore::ReturnCacheDataDontLoad:
136         return true;
137     case WebCore::UseProtocolCachePolicy:
138     case WebCore::ReloadIgnoringCacheData:
139         return false;
140     }
141     ASSERT_NOT_REACHED();
142     return false;
143 }
144
145 static bool responseHasExpired(const WebCore::ResourceResponse& response, std::chrono::system_clock::time_point timestamp, Optional<std::chrono::microseconds> maxStale)
146 {
147     if (response.cacheControlContainsNoCache())
148         return true;
149
150     auto age = WebCore::computeCurrentAge(response, timestamp);
151     auto lifetime = WebCore::computeFreshnessLifetimeForHTTPFamily(response, timestamp);
152
153     auto maximumStaleness = maxStale ? maxStale.value() : 0ms;
154     bool hasExpired = age - lifetime > maximumStaleness;
155
156 #ifndef LOG_DISABLED
157     if (hasExpired)
158         LOG(NetworkCache, "(NetworkProcess) needsRevalidation hasExpired age=%f lifetime=%f max-stale=%g", age, lifetime, maxStale);
159 #endif
160
161     return hasExpired;
162 }
163
164 static bool responseNeedsRevalidation(const WebCore::ResourceResponse& response, const WebCore::ResourceRequest& request, std::chrono::system_clock::time_point timestamp)
165 {
166     auto requestDirectives = WebCore::parseCacheControlDirectives(request.httpHeaderFields());
167     if (requestDirectives.noCache)
168         return true;
169     // For requests we ignore max-age values other than zero.
170     if (requestDirectives.maxAge && requestDirectives.maxAge.value() == 0ms)
171         return true;
172
173     return responseHasExpired(response, timestamp, requestDirectives.maxStale);
174 }
175
176 static UseDecision makeUseDecision(const Entry& entry, const WebCore::ResourceRequest& request)
177 {
178     // The request is conditional so we force revalidation from the network. We merely check the disk cache
179     // so we can update the cache entry.
180     if (request.isConditional() && !entry.redirectRequest())
181         return UseDecision::Validate;
182
183     if (!WebCore::verifyVaryingRequestHeaders(entry.varyingRequestHeaders(), request))
184         return UseDecision::NoDueToVaryingHeaderMismatch;
185
186     // We never revalidate in the case of a history navigation.
187     if (cachePolicyAllowsExpired(request.cachePolicy()))
188         return UseDecision::Use;
189
190     if (!responseNeedsRevalidation(entry.response(), request, entry.timeStamp()))
191         return UseDecision::Use;
192
193     if (!entry.response().hasCacheValidatorFields())
194         return UseDecision::NoDueToMissingValidatorFields;
195
196     return entry.redirectRequest() ? UseDecision::NoDueToExpiredRedirect : UseDecision::Validate;
197 }
198
199 static RetrieveDecision makeRetrieveDecision(const WebCore::ResourceRequest& request)
200 {
201     // FIXME: Support HEAD requests.
202     if (request.httpMethod() != "GET")
203         return RetrieveDecision::NoDueToHTTPMethod;
204     if (request.requester() == WebCore::ResourceRequest::Requester::Media)
205         return RetrieveDecision::NoDueToStreamingMedia;
206     if (request.cachePolicy() == WebCore::ReloadIgnoringCacheData && !request.isConditional())
207         return RetrieveDecision::NoDueToReloadIgnoringCache;
208
209     return RetrieveDecision::Yes;
210 }
211
212 // http://tools.ietf.org/html/rfc7231#page-48
213 static bool isStatusCodeCacheableByDefault(int statusCode)
214 {
215     switch (statusCode) {
216     case 200: // OK
217     case 203: // Non-Authoritative Information
218     case 204: // No Content
219     case 206: // Partial Content
220     case 300: // Multiple Choices
221     case 301: // Moved Permanently
222     case 404: // Not Found
223     case 405: // Method Not Allowed
224     case 410: // Gone
225     case 414: // URI Too Long
226     case 501: // Not Implemented
227         return true;
228     default:
229         return false;
230     }
231 }
232
233 static bool isStatusCodePotentiallyCacheable(int statusCode)
234 {
235     switch (statusCode) {
236     case 201: // Created
237     case 202: // Accepted
238     case 205: // Reset Content
239     case 302: // Found
240     case 303: // See Other
241     case 307: // Temporary redirect
242     case 403: // Forbidden
243     case 406: // Not Acceptable
244     case 415: // Unsupported Media Type
245         return true;
246     default:
247         return false;
248     }
249 }
250
251 static bool isMediaMIMEType(const String& mimeType)
252 {
253     if (mimeType.startsWith("video/", /*caseSensitive*/ false))
254         return true;
255     if (mimeType.startsWith("audio/", /*caseSensitive*/ false))
256         return true;
257     return false;
258 }
259
260 static StoreDecision makeStoreDecision(const WebCore::ResourceRequest& originalRequest, const WebCore::ResourceResponse& response)
261 {
262     if (!originalRequest.url().protocolIsInHTTPFamily() || !response.isHTTP())
263         return StoreDecision::NoDueToProtocol;
264
265     if (originalRequest.httpMethod() != "GET")
266         return StoreDecision::NoDueToHTTPMethod;
267
268     auto requestDirectives = WebCore::parseCacheControlDirectives(originalRequest.httpHeaderFields());
269     if (requestDirectives.noStore)
270         return StoreDecision::NoDueToNoStoreRequest;
271
272     if (response.cacheControlContainsNoStore())
273         return StoreDecision::NoDueToNoStoreResponse;
274
275     if (!isStatusCodeCacheableByDefault(response.httpStatusCode())) {
276         // http://tools.ietf.org/html/rfc7234#section-4.3.2
277         bool hasExpirationHeaders = response.expires() || response.cacheControlMaxAge();
278         bool expirationHeadersAllowCaching = isStatusCodePotentiallyCacheable(response.httpStatusCode()) && hasExpirationHeaders;
279         if (!expirationHeadersAllowCaching)
280             return StoreDecision::NoDueToHTTPStatusCode;
281     }
282
283     bool isMainResource = originalRequest.requester() == WebCore::ResourceRequest::Requester::Main;
284     bool storeUnconditionallyForHistoryNavigation = isMainResource || originalRequest.priority() == WebCore::ResourceLoadPriority::VeryHigh;
285     if (!storeUnconditionallyForHistoryNavigation) {
286         auto now = std::chrono::system_clock::now();
287         bool hasNonZeroLifetime = !response.cacheControlContainsNoCache() && WebCore::computeFreshnessLifetimeForHTTPFamily(response, now) > 0ms;
288
289         bool possiblyReusable = response.hasCacheValidatorFields() || hasNonZeroLifetime;
290         if (!possiblyReusable)
291             return StoreDecision::NoDueToUnlikelyToReuse;
292     }
293
294     // Media loaded via XHR is likely being used for MSE streaming (YouTube and Netflix for example).
295     // Streaming media fills the cache quickly and is unlikely to be reused.
296     // FIXME: We should introduce a separate media cache partition that doesn't affect other resources.
297     // FIXME: We should also make sure make the MSE paths are copy-free so we can use mapped buffers from disk effectively.
298     auto requester = originalRequest.requester();
299     bool isDefinitelyStreamingMedia = requester == WebCore::ResourceRequest::Requester::Media;
300     bool isLikelyStreamingMedia = requester == WebCore::ResourceRequest::Requester::XHR && isMediaMIMEType(response.mimeType());
301     if (isLikelyStreamingMedia || isDefinitelyStreamingMedia)
302         return StoreDecision::NoDueToStreamingMedia;
303
304     return StoreDecision::Yes;
305 }
306
307 void Cache::retrieve(const WebCore::ResourceRequest& request, const GlobalFrameID& frameID, std::function<void (std::unique_ptr<Entry>)>&& completionHandler)
308 {
309     ASSERT(isEnabled());
310     ASSERT(request.url().protocolIsInHTTPFamily());
311
312     LOG(NetworkCache, "(NetworkProcess) retrieving %s priority %d", request.url().string().ascii().data(), static_cast<int>(request.priority()));
313
314     if (m_statistics)
315         m_statistics->recordRetrievalRequest(frameID.first);
316
317     Key storageKey = makeCacheKey(request);
318
319 #if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
320     bool canUseSpeculativeRevalidation = m_speculativeLoadManager && !request.isConditional();
321     if (canUseSpeculativeRevalidation)
322         m_speculativeLoadManager->registerLoad(frameID, request, storageKey);
323 #endif
324
325     auto retrieveDecision = makeRetrieveDecision(request);
326     if (retrieveDecision != RetrieveDecision::Yes) {
327         if (m_statistics)
328             m_statistics->recordNotUsingCacheForRequest(frameID.first, storageKey, request, retrieveDecision);
329
330         completionHandler(nullptr);
331         return;
332     }
333
334 #if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
335     if (canUseSpeculativeRevalidation && m_speculativeLoadManager->retrieve(frameID, storageKey, request, [request, completionHandler](std::unique_ptr<Entry> entry) {
336         if (entry && WebCore::verifyVaryingRequestHeaders(entry->varyingRequestHeaders(), request))
337             completionHandler(WTFMove(entry));
338         else
339             completionHandler(nullptr);
340     }))
341         return;
342 #endif
343
344     auto startTime = std::chrono::system_clock::now();
345     auto priority = static_cast<unsigned>(request.priority());
346
347     m_storage->retrieve(storageKey, priority, [this, request, completionHandler = WTFMove(completionHandler), startTime, storageKey, frameID](std::unique_ptr<Storage::Record> record) {
348         if (!record) {
349             LOG(NetworkCache, "(NetworkProcess) not found in storage");
350
351             if (m_statistics)
352                 m_statistics->recordRetrievalFailure(frameID.first, storageKey, request);
353
354             completionHandler(nullptr);
355             return false;
356         }
357
358         ASSERT(record->key == storageKey);
359
360         auto entry = Entry::decodeStorageRecord(*record);
361
362         auto useDecision = entry ? makeUseDecision(*entry, request) : UseDecision::NoDueToDecodeFailure;
363         switch (useDecision) {
364         case UseDecision::Use:
365             break;
366         case UseDecision::Validate:
367             entry->setNeedsValidation(true);
368             break;
369         default:
370             entry = nullptr;
371         };
372
373 #if !LOG_DISABLED
374         auto elapsedMS = static_cast<int64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - startTime).count());
375         LOG(NetworkCache, "(NetworkProcess) retrieve complete useDecision=%d priority=%d time=%" PRIi64 "ms", static_cast<int>(useDecision), static_cast<int>(request.priority()), elapsedMS);
376 #endif
377         completionHandler(WTFMove(entry));
378
379         if (m_statistics)
380             m_statistics->recordRetrievedCachedEntry(frameID.first, storageKey, request, useDecision);
381         return useDecision != UseDecision::NoDueToDecodeFailure;
382     });
383 }
384
385 std::unique_ptr<Entry> Cache::store(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, RefPtr<WebCore::SharedBuffer>&& responseData, NoncopyableFunction<void (MappedBody&)>&& completionHandler)
386 {
387     ASSERT(isEnabled());
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);
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         if (m_statistics)
404             m_statistics->recordNotCachingResponse(key, storeDecision);
405
406         return nullptr;
407     }
408
409     auto cacheEntry = std::make_unique<Entry>(makeCacheKey(request), response, WTFMove(responseData), WebCore::collectVaryingRequestHeaders(request, response));
410     auto record = cacheEntry->encodeAsStorageRecord();
411
412     m_storage->store(record, [completionHandler = WTFMove(completionHandler)](const Data& bodyData) {
413         MappedBody mappedBody;
414 #if ENABLE(SHAREABLE_RESOURCE)
415         if (auto sharedMemory = bodyData.tryCreateSharedMemory()) {
416             mappedBody.shareableResource = ShareableResource::create(sharedMemory.releaseNonNull(), 0, bodyData.size());
417             ASSERT(mappedBody.shareableResource);
418             mappedBody.shareableResource->createHandle(mappedBody.shareableResourceHandle);
419         }
420 #endif
421         completionHandler(mappedBody);
422         LOG(NetworkCache, "(NetworkProcess) stored");
423     });
424
425     return cacheEntry;
426 }
427
428 std::unique_ptr<Entry> Cache::storeRedirect(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, const WebCore::ResourceRequest& redirectRequest)
429 {
430     ASSERT(isEnabled());
431
432     LOG(NetworkCache, "(NetworkProcess) storing redirect %s -> %s", request.url().string().latin1().data(), redirectRequest.url().string().latin1().data());
433
434     StoreDecision storeDecision = makeStoreDecision(request, response);
435     if (storeDecision != StoreDecision::Yes) {
436         LOG(NetworkCache, "(NetworkProcess) didn't store redirect, storeDecision=%d", static_cast<int>(storeDecision));
437         auto key = makeCacheKey(request);
438         if (m_statistics)
439             m_statistics->recordNotCachingResponse(key, storeDecision);
440
441         return nullptr;
442     }
443
444     std::unique_ptr<Entry> cacheEntry = std::make_unique<Entry>(makeCacheKey(request), response, redirectRequest, WebCore::collectVaryingRequestHeaders(request, response));
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 = std::make_unique<Entry>(existingEntry.key(), response, existingEntry.buffer(), WebCore::collectVaryingRequestHeaders(originalRequest, response));
461     auto updateRecord = updateEntry->encodeAsStorageRecord();
462
463     m_storage->store(updateRecord, { });
464
465     if (m_statistics)
466         m_statistics->recordRevalidationSuccess(frameID.first, existingEntry.key(), originalRequest);
467
468     return updateEntry;
469 }
470
471 void Cache::remove(const Key& key)
472 {
473     ASSERT(isEnabled());
474
475     m_storage->remove(key);
476 }
477
478 void Cache::remove(const WebCore::ResourceRequest& request)
479 {
480     remove(makeCacheKey(request));
481 }
482
483 void Cache::traverse(NoncopyableFunction<void (const TraversalEntry*)>&& traverseHandler)
484 {
485     ASSERT(isEnabled());
486
487     // Protect against clients making excessive traversal requests.
488     const unsigned maximumTraverseCount = 3;
489     if (m_traverseCount >= maximumTraverseCount) {
490         WTFLogAlways("Maximum parallel cache traverse count exceeded. Ignoring traversal request.");
491
492         RunLoop::main().dispatch([traverseHandler = WTFMove(traverseHandler)] {
493             traverseHandler(nullptr);
494         });
495         return;
496     }
497
498     ++m_traverseCount;
499
500     m_storage->traverse(resourceType(), 0, [this, traverseHandler = WTFMove(traverseHandler)](const Storage::Record* record, const Storage::RecordInfo& recordInfo) {
501         if (!record) {
502             --m_traverseCount;
503             traverseHandler(nullptr);
504             return;
505         }
506
507         auto entry = Entry::decodeStorageRecord(*record);
508         if (!entry)
509             return;
510
511         TraversalEntry traversalEntry { *entry, recordInfo };
512         traverseHandler(&traversalEntry);
513     });
514 }
515
516 String Cache::dumpFilePath() const
517 {
518     return WebCore::pathByAppendingComponent(m_storage->versionPath(), "dump.json");
519 }
520
521 void Cache::dumpContentsToFile()
522 {
523     if (!m_storage)
524         return;
525     auto fd = WebCore::openFile(dumpFilePath(), WebCore::OpenForWrite);
526     if (!WebCore::isHandleValid(fd))
527         return;
528     auto prologue = String("{\n\"entries\": [\n").utf8();
529     WebCore::writeToFile(fd, prologue.data(), prologue.length());
530
531     struct Totals {
532         unsigned count { 0 };
533         double worth { 0 };
534         size_t bodySize { 0 };
535     };
536     Totals totals;
537     auto flags = Storage::TraverseFlag::ComputeWorth | Storage::TraverseFlag::ShareCount;
538     size_t capacity = m_storage->capacity();
539     m_storage->traverse(resourceType(), flags, [fd, totals, capacity](const Storage::Record* record, const Storage::RecordInfo& info) mutable {
540         if (!record) {
541             StringBuilder epilogue;
542             epilogue.appendLiteral("{}\n],\n");
543             epilogue.appendLiteral("\"totals\": {\n");
544             epilogue.appendLiteral("\"capacity\": ");
545             epilogue.appendNumber(capacity);
546             epilogue.appendLiteral(",\n");
547             epilogue.appendLiteral("\"count\": ");
548             epilogue.appendNumber(totals.count);
549             epilogue.appendLiteral(",\n");
550             epilogue.appendLiteral("\"bodySize\": ");
551             epilogue.appendNumber(totals.bodySize);
552             epilogue.appendLiteral(",\n");
553             epilogue.appendLiteral("\"averageWorth\": ");
554             epilogue.appendNumber(totals.count ? totals.worth / totals.count : 0);
555             epilogue.appendLiteral("\n");
556             epilogue.appendLiteral("}\n}\n");
557             auto writeData = epilogue.toString().utf8();
558             WebCore::writeToFile(fd, writeData.data(), writeData.length());
559             WebCore::closeFile(fd);
560             return;
561         }
562         auto entry = Entry::decodeStorageRecord(*record);
563         if (!entry)
564             return;
565         ++totals.count;
566         totals.worth += info.worth;
567         totals.bodySize += info.bodySize;
568
569         StringBuilder json;
570         entry->asJSON(json, info);
571         json.appendLiteral(",\n");
572         auto writeData = json.toString().utf8();
573         WebCore::writeToFile(fd, writeData.data(), writeData.length());
574     });
575 }
576
577 void Cache::deleteDumpFile()
578 {
579     WorkQueue::create("com.apple.WebKit.Cache.delete")->dispatch([path = dumpFilePath().isolatedCopy()] {
580         WebCore::deleteFile(path);
581     });
582 }
583
584 void Cache::clear(std::chrono::system_clock::time_point modifiedSince, std::function<void ()>&& completionHandler)
585 {
586     LOG(NetworkCache, "(NetworkProcess) clearing cache");
587
588     if (m_statistics)
589         m_statistics->clear();
590
591     if (!m_storage) {
592         RunLoop::main().dispatch(WTFMove(completionHandler));
593         return;
594     }
595     String anyType;
596     m_storage->clear(anyType, modifiedSince, WTFMove(completionHandler));
597
598     deleteDumpFile();
599 }
600
601 void Cache::clear()
602 {
603     clear(std::chrono::system_clock::time_point::min(), nullptr);
604 }
605
606 String Cache::recordsPath() const
607 {
608     return m_storage ? m_storage->recordsPath() : String();
609 }
610
611 }
612 }
613
614 #endif