c532f1cd75fe2a508b7c184bae6f1554ff0492fb
[WebKit-https.git] / Source / WebKit / NetworkProcess / cache / CacheStorageEngineCaches.cpp
1 /*
2  * Copyright (C) 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "CacheStorageEngine.h"
28
29 #include "Logging.h"
30 #include "NetworkCacheCoders.h"
31 #include "NetworkCacheIOChannel.h"
32 #include <WebCore/SecurityOrigin.h>
33 #include <WebCore/StorageQuotaManager.h>
34 #include <wtf/RunLoop.h>
35 #include <wtf/UUID.h>
36 #include <wtf/text/StringBuilder.h>
37
38 namespace WebKit {
39
40 namespace CacheStorage {
41 using namespace WebCore::DOMCacheEngine;
42 using namespace NetworkCache;
43
44 static inline String cachesListFilename(const String& cachesRootPath)
45 {
46     return FileSystem::pathByAppendingComponent(cachesRootPath, "cacheslist"_s);
47 }
48
49 static inline String cachesOriginFilename(const String& cachesRootPath)
50 {
51     return FileSystem::pathByAppendingComponent(cachesRootPath, "origin"_s);
52 }
53
54 Ref<Caches> Caches::create(Engine& engine, WebCore::ClientOrigin&& origin, String&& rootPath, WebCore::StorageQuotaManager& quotaManager)
55 {
56     auto caches = adoptRef(*new Caches { engine, WTFMove(origin), WTFMove(rootPath), quotaManager });
57     quotaManager.addUser(caches.get());
58     return caches;
59 }
60
61 Caches::Caches(Engine& engine, WebCore::ClientOrigin&& origin, String&& rootPath, WebCore::StorageQuotaManager& quotaManager)
62     : m_engine(&engine)
63     , m_origin(WTFMove(origin))
64     , m_rootPath(WTFMove(rootPath))
65     , m_quotaManager(makeWeakPtr(quotaManager))
66 {
67 }
68
69 Caches::~Caches()
70 {
71     ASSERT(m_pendingWritingCachesToDiskCallbacks.isEmpty());
72
73     if (m_quotaManager)
74         m_quotaManager->removeUser(*this);
75 }
76
77 void Caches::whenInitialized(CompletionHandler<void()>&& callback)
78 {
79     initialize([callback = WTFMove(callback)](auto&& error) mutable {
80         if (error)
81             RELEASE_LOG_ERROR(CacheStorage, "Caches::initialize failed, reported space used will be zero");
82         callback();
83     });
84 }
85
86 void Caches::retrieveOriginFromDirectory(const String& folderPath, WorkQueue& queue, WTF::CompletionHandler<void(Optional<WebCore::ClientOrigin>&&)>&& completionHandler)
87 {
88     queue.dispatch([completionHandler = WTFMove(completionHandler), filename = cachesOriginFilename(folderPath)]() mutable {
89         if (!FileSystem::fileExists(filename)) {
90             RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler)]() mutable {
91                 completionHandler(WTF::nullopt);
92             });
93             return;
94         }
95
96         auto channel = IOChannel::open(filename, IOChannel::Type::Read);
97         channel->read(0, std::numeric_limits<size_t>::max(), nullptr, [completionHandler = WTFMove(completionHandler)](const Data& data, int error) mutable {
98             ASSERT(RunLoop::isMain());
99             if (error) {
100                 RELEASE_LOG_ERROR(CacheStorage, "Caches::retrieveOriginFromDirectory failed reading channel with error %d", error);
101                 completionHandler(WTF::nullopt);
102                 return;
103             }
104             completionHandler(readOrigin(data));
105         });
106     });
107 }
108
109 void Caches::storeOrigin(CompletionCallback&& completionHandler)
110 {
111     WTF::Persistence::Encoder encoder;
112     encoder << m_origin.topOrigin.protocol;
113     encoder << m_origin.topOrigin.host;
114     encoder << m_origin.topOrigin.port;
115     encoder << m_origin.clientOrigin.protocol;
116     encoder << m_origin.clientOrigin.host;
117     encoder << m_origin.clientOrigin.port;
118     m_engine->writeFile(cachesOriginFilename(m_rootPath), Data { encoder.buffer(), encoder.bufferSize() }, [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)] (Optional<Error>&& error) mutable {
119         completionHandler(WTFMove(error));
120     });
121 }
122
123 Optional<WebCore::ClientOrigin> Caches::readOrigin(const Data& data)
124 {
125     // FIXME: We should be able to use modern decoders for persistent data.
126     WebCore::SecurityOriginData topOrigin, clientOrigin;
127     WTF::Persistence::Decoder decoder(data.data(), data.size());
128
129     if (!decoder.decode(topOrigin.protocol))
130         return WTF::nullopt;
131     if (!decoder.decode(topOrigin.host))
132         return WTF::nullopt;
133     if (!decoder.decode(topOrigin.port))
134         return WTF::nullopt;
135     if (!decoder.decode(clientOrigin.protocol))
136         return WTF::nullopt;
137     if (!decoder.decode(clientOrigin.host))
138         return WTF::nullopt;
139     if (!decoder.decode(clientOrigin.port))
140         return WTF::nullopt;
141     return WebCore::ClientOrigin { WTFMove(topOrigin), WTFMove(clientOrigin) };
142 }
143
144 void Caches::initialize(WebCore::DOMCacheEngine::CompletionCallback&& callback)
145 {
146     if (m_isInitialized) {
147         callback(WTF::nullopt);
148         return;
149     }
150
151     if (m_rootPath.isNull()) {
152         makeDirty();
153         m_isInitialized = true;
154         callback(WTF::nullopt);
155         return;
156     }
157
158     if (m_storage) {
159         m_pendingInitializationCallbacks.append(WTFMove(callback));
160         return;
161     }
162
163     auto storage = Storage::open(m_rootPath, Storage::Mode::AvoidRandomness);
164     if (!storage) {
165         RELEASE_LOG_ERROR(CacheStorage, "Caches::initialize failed opening storage");
166         callback(Error::WriteDisk);
167         return;
168     }
169
170     m_pendingInitializationCallbacks.append(WTFMove(callback));
171     m_storage = storage.releaseNonNull();
172     m_storage->writeWithoutWaiting();
173
174     storeOrigin([this] (Optional<Error>&& error) mutable {
175         if (error) {
176             RELEASE_LOG_ERROR(CacheStorage, "Caches::initialize failed storing origin with error %d", static_cast<int>(*error));
177
178             auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks);
179             for (auto& callback : pendingCallbacks)
180                 callback(Error::WriteDisk);
181
182             m_storage = nullptr;
183             return;
184         }
185
186         readCachesFromDisk([this](Expected<Vector<Cache>, Error>&& result) mutable {
187             makeDirty();
188
189             if (!result.has_value()) {
190                 RELEASE_LOG_ERROR(CacheStorage, "Caches::initialize failed reading caches from disk with error %d", static_cast<int>(result.error()));
191
192                 auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks);
193                 for (auto& callback : pendingCallbacks)
194                     callback(result.error());
195
196                 m_storage = nullptr;
197                 return;
198             }
199             m_caches = WTFMove(result.value());
200
201             initializeSize();
202         });
203     });
204 }
205
206 void Caches::initializeSize()
207 {
208     if (!m_storage) {
209         auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks);
210         for (auto& callback : pendingCallbacks)
211             callback(Error::Internal);
212         return;
213     }
214
215     uint64_t size = 0;
216     m_storage->traverse({ }, { }, [protectedThis = makeRef(*this), this, protectedStorage = makeRef(*m_storage), size](const auto* storage, const auto& information) mutable {
217         if (!storage) {
218             if (m_pendingInitializationCallbacks.isEmpty()) {
219                 // Caches was cleared so let's not get initialized.
220                 m_storage = nullptr;
221                 return;
222             }
223             m_size = size;
224             m_isInitialized = true;
225             auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks);
226             for (auto& callback : pendingCallbacks)
227                 callback(WTF::nullopt);
228
229             return;
230         }
231         auto decoded = Cache::decodeRecordHeader(*storage);
232         if (decoded)
233             size += decoded->size;
234     });
235 }
236
237 void Caches::detach()
238 {
239     m_engine = nullptr;
240     m_rootPath = { };
241     clearPendingWritingCachesToDiskCallbacks();
242 }
243
244 void Caches::clear(CompletionHandler<void()>&& completionHandler)
245 {
246     if (m_isWritingCachesToDisk) {
247         m_pendingWritingCachesToDiskCallbacks.append([this, completionHandler = WTFMove(completionHandler)] (auto&& error) mutable {
248             this->clear(WTFMove(completionHandler));
249         });
250         return;
251     }
252
253     auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks);
254     for (auto& callback : pendingCallbacks)
255         callback(Error::Internal);
256
257     if (m_engine)
258         m_engine->removeFile(cachesListFilename(m_rootPath));
259     if (m_storage) {
260         m_storage->clear(String { }, -WallTime::infinity(), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)]() mutable {
261             ASSERT(RunLoop::isMain());
262             protectedThis->clearMemoryRepresentation();
263             completionHandler();
264         });
265         return;
266     }
267     clearMemoryRepresentation();
268     clearPendingWritingCachesToDiskCallbacks();
269     completionHandler();
270 }
271
272 void Caches::clearPendingWritingCachesToDiskCallbacks()
273 {
274     auto pendingWritingCachesToDiskCallbacks = WTFMove(m_pendingWritingCachesToDiskCallbacks);
275     for (auto& callback : pendingWritingCachesToDiskCallbacks)
276         callback(Error::Internal);
277 }
278
279 Cache* Caches::find(const String& name)
280 {
281     auto position = m_caches.findMatching([&](const auto& item) { return item.name() == name; });
282     return (position != notFound) ? &m_caches[position] : nullptr;
283 }
284
285 Cache* Caches::find(uint64_t identifier)
286 {
287     auto position = m_caches.findMatching([&](const auto& item) { return item.identifier() == identifier; });
288     if (position != notFound)
289         return &m_caches[position];
290
291     position = m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == identifier; });
292     return (position != notFound) ? &m_removedCaches[position] : nullptr;
293 }
294
295 void Caches::open(const String& name, CacheIdentifierCallback&& callback)
296 {
297     ASSERT(m_isInitialized);
298     ASSERT(m_engine);
299
300     if (m_isWritingCachesToDisk) {
301         m_pendingWritingCachesToDiskCallbacks.append([this, name, callback = WTFMove(callback)] (auto&& error) mutable {
302             if (error) {
303                 callback(makeUnexpected(error.value()));
304                 return;
305             }
306             this->open(name, WTFMove(callback));
307         });
308         return;
309     }
310
311     if (auto* cache = find(name)) {
312         cache->open([cacheIdentifier = cache->identifier(), callback = WTFMove(callback)](Optional<Error>&& error) mutable {
313             if (error) {
314                 callback(makeUnexpected(error.value()));
315                 return;
316             }
317             callback(CacheIdentifierOperationResult { cacheIdentifier, false });
318         });
319         return;
320     }
321
322     makeDirty();
323
324     uint64_t cacheIdentifier = m_engine->nextCacheIdentifier();
325     m_caches.append(Cache { *this, cacheIdentifier, Cache::State::Open, String { name }, createCanonicalUUIDString() });
326
327     writeCachesToDisk([callback = WTFMove(callback), cacheIdentifier](Optional<Error>&& error) mutable {
328         callback(CacheIdentifierOperationResult { cacheIdentifier, !!error });
329     });
330 }
331
332 void Caches::remove(uint64_t identifier, CacheIdentifierCallback&& callback)
333 {
334     ASSERT(m_isInitialized);
335     ASSERT(m_engine);
336
337     if (m_isWritingCachesToDisk) {
338         m_pendingWritingCachesToDiskCallbacks.append([this, identifier, callback = WTFMove(callback)] (auto&& error) mutable {
339             if (error) {
340                 callback(makeUnexpected(error.value()));
341                 return;
342             }
343             this->remove(identifier, WTFMove(callback));
344         });
345         return;
346     }
347
348     auto position = m_caches.findMatching([&](const auto& item) { return item.identifier() == identifier; });
349
350     if (position == notFound) {
351         ASSERT(m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == identifier; }) != notFound);
352         callback(CacheIdentifierOperationResult { 0, false });
353         return;
354     }
355
356     makeDirty();
357
358     m_removedCaches.append(WTFMove(m_caches[position]));
359     m_caches.remove(position);
360
361     writeCachesToDisk([callback = WTFMove(callback), identifier](Optional<Error>&& error) mutable {
362         callback(CacheIdentifierOperationResult { identifier, !!error });
363     });
364 }
365
366 bool Caches::hasActiveCache() const
367 {
368     if (m_removedCaches.size())
369         return true;
370     return m_caches.findMatching([](const auto& item) { return item.isActive(); }) != notFound;
371 }
372
373 void Caches::dispose(Cache& cache)
374 {
375     auto position = m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == cache.identifier(); });
376     if (position != notFound) {
377         if (m_storage)
378             m_storage->remove(cache.keys(), [] { });
379
380         m_removedCaches.remove(position);
381         return;
382     }
383     ASSERT(m_caches.findMatching([&](const auto& item) { return item.identifier() == cache.identifier(); }) != notFound);
384     cache.clearMemoryRepresentation();
385
386     if (!hasActiveCache())
387         clearMemoryRepresentation();
388 }
389
390 static inline Data encodeCacheNames(const Vector<Cache>& caches)
391 {
392     WTF::Persistence::Encoder encoder;
393
394     uint64_t size = caches.size();
395     encoder << size;
396     for (auto& cache : caches) {
397         encoder << cache.name();
398         encoder << cache.uniqueName();
399     }
400
401     return Data { encoder.buffer(), encoder.bufferSize() };
402 }
403
404 static inline Expected<Vector<std::pair<String, String>>, Error> decodeCachesNames(const Data& data)
405 {
406     WTF::Persistence::Decoder decoder(data.data(), data.size());
407     uint64_t count;
408     if (!decoder.decode(count))
409         return makeUnexpected(Error::ReadDisk);
410
411     Vector<std::pair<String, String>> names;
412     names.reserveInitialCapacity(count);
413     for (size_t index = 0; index < count; ++index) {
414         String name;
415         if (!decoder.decode(name))
416             return makeUnexpected(Error::ReadDisk);
417         String uniqueName;
418         if (!decoder.decode(uniqueName))
419             return makeUnexpected(Error::ReadDisk);
420
421         names.uncheckedAppend(std::pair<String, String> { WTFMove(name), WTFMove(uniqueName) });
422     }
423     return names;
424 }
425
426 void Caches::readCachesFromDisk(WTF::Function<void(Expected<Vector<Cache>, Error>&&)>&& callback)
427 {
428     ASSERT(m_engine);
429     ASSERT(!m_isInitialized);
430     ASSERT(m_caches.isEmpty());
431
432     if (!shouldPersist()) {
433         callback(Vector<Cache> { });
434         return;
435     }
436
437     auto filename = cachesListFilename(m_rootPath);
438     if (!FileSystem::fileExists(filename)) {
439         callback(Vector<Cache> { });
440         return;
441     }
442
443     m_engine->readFile(filename, [protectedThis = makeRef(*this), this, callback = WTFMove(callback)](const Data& data, int error) mutable {
444         if (!m_engine) {
445             callback(Vector<Cache> { });
446             return;
447         }
448
449         if (error) {
450             RELEASE_LOG_ERROR(CacheStorage, "Caches::readCachesFromDisk failed reading caches from disk with error %d", error);
451             callback(makeUnexpected(Error::ReadDisk));
452             return;
453         }
454
455         auto result = decodeCachesNames(data);
456         if (!result.has_value()) {
457             RELEASE_LOG_ERROR(CacheStorage, "Caches::decodeCachesNames failed decoding caches with error %d", static_cast<int>(result.error()));
458             callback(makeUnexpected(result.error()));
459             return;
460         }
461         callback(WTF::map(WTFMove(result.value()), [this] (auto&& pair) {
462             return Cache { *this, m_engine->nextCacheIdentifier(), Cache::State::Uninitialized, WTFMove(pair.first), WTFMove(pair.second) };
463         }));
464     });
465 }
466
467 void Caches::writeCachesToDisk(CompletionCallback&& callback)
468 {
469     ASSERT(!m_isWritingCachesToDisk);
470     ASSERT(m_isInitialized);
471     if (!shouldPersist()) {
472         callback(WTF::nullopt);
473         return;
474     }
475
476     ASSERT(m_engine);
477
478     if (m_caches.isEmpty()) {
479         m_engine->removeFile(cachesListFilename(m_rootPath));
480         callback(WTF::nullopt);
481         return;
482     }
483
484     m_isWritingCachesToDisk = true;
485     m_engine->writeFile(cachesListFilename(m_rootPath), encodeCacheNames(m_caches), [this, protectedThis = makeRef(*this), callback = WTFMove(callback)](Optional<Error>&& error) mutable {
486         m_isWritingCachesToDisk = false;
487         if (error)
488             RELEASE_LOG_ERROR(CacheStorage, "Caches::writeCachesToDisk failed writing caches to disk with error %d", static_cast<int>(*error));
489
490         callback(WTFMove(error));
491         while (!m_pendingWritingCachesToDiskCallbacks.isEmpty() && !m_isWritingCachesToDisk)
492             m_pendingWritingCachesToDiskCallbacks.takeFirst()(WTF::nullopt);
493     });
494 }
495
496 void Caches::readRecordsList(Cache& cache, NetworkCache::Storage::TraverseHandler&& callback)
497 {
498     ASSERT(m_isInitialized);
499
500     if (!m_storage) {
501         callback(nullptr, { });
502         return;
503     }
504     m_storage->traverse(cache.uniqueName(), { }, [protectedStorage = makeRef(*m_storage), callback = WTFMove(callback)](const auto* storage, const auto& information) {
505         callback(storage, information);
506     });
507 }
508
509 void Caches::requestSpace(uint64_t spaceRequired, WebCore::DOMCacheEngine::CompletionCallback&& callback)
510 {
511     if (!m_quotaManager) {
512         callback(Error::QuotaExceeded);
513         return;
514     }
515
516     m_quotaManager->requestSpace(spaceRequired, [callback = WTFMove(callback)](auto decision) {
517         switch (decision) {
518         case WebCore::StorageQuotaManager::Decision::Deny:
519             callback(Error::QuotaExceeded);
520             return;
521         case WebCore::StorageQuotaManager::Decision::Grant:
522             callback({ });
523         };
524     });
525 }
526
527 void Caches::writeRecord(const Cache& cache, const RecordInformation& recordInformation, Record&& record, uint64_t previousRecordSize, CompletionCallback&& callback)
528 {
529     ASSERT(m_isInitialized);
530
531     ASSERT(m_size >= previousRecordSize);
532     m_size += recordInformation.size;
533     m_size -= previousRecordSize;
534
535     if (!shouldPersist()) {
536         m_volatileStorage.set(recordInformation.key, WTFMove(record));
537         callback(WTF::nullopt);
538         return;
539     }
540
541     m_storage->store(Cache::encode(recordInformation, record), { }, [protectedStorage = makeRef(*m_storage), callback = WTFMove(callback)](int error) {
542         if (error) {
543             RELEASE_LOG_ERROR(CacheStorage, "Caches::writeRecord failed with error %d", error);
544             callback(Error::WriteDisk);
545             return;
546         }
547         callback(WTF::nullopt);
548     });
549 }
550
551 void Caches::readRecord(const NetworkCache::Key& key, WTF::Function<void(Expected<Record, Error>&&)>&& callback)
552 {
553     ASSERT(m_isInitialized);
554
555     if (!shouldPersist()) {
556         auto iterator = m_volatileStorage.find(key);
557         if (iterator == m_volatileStorage.end()) {
558             callback(makeUnexpected(Error::Internal));
559             return;
560         }
561         callback(iterator->value.copy());
562         return;
563     }
564
565     m_storage->retrieve(key, 4, [protectedStorage = makeRef(*m_storage), callback = WTFMove(callback)](std::unique_ptr<Storage::Record> storage, const Storage::Timings&) mutable {
566         if (!storage) {
567             RELEASE_LOG_ERROR(CacheStorage, "Caches::readRecord failed reading record from disk");
568             callback(makeUnexpected(Error::ReadDisk));
569             return false;
570         }
571
572         auto record = Cache::decode(*storage);
573         if (!record) {
574             RELEASE_LOG_ERROR(CacheStorage, "Caches::readRecord failed decoding record from disk");
575             callback(makeUnexpected(Error::ReadDisk));
576             return false;
577         }
578
579         callback(WTFMove(record.value()));
580         return true;
581     });
582 }
583
584 void Caches::removeRecord(const RecordInformation& record)
585 {
586     ASSERT(m_isInitialized);
587
588     ASSERT(m_size >= record.size);
589     m_size -= record.size;
590     removeCacheEntry(record.key);
591 }
592
593 void Caches::removeCacheEntry(const NetworkCache::Key& key)
594 {
595     ASSERT(m_isInitialized);
596
597     if (!shouldPersist()) {
598         m_volatileStorage.remove(key);
599         return;
600     }
601     m_storage->remove(key);
602 }
603
604 void Caches::clearMemoryRepresentation()
605 {
606     if (!m_isInitialized) {
607         ASSERT(!m_storage || !hasActiveCache() || !m_pendingInitializationCallbacks.isEmpty());
608         // m_storage might not be null in case Caches is being initialized. This is fine as nullify it below is a memory optimization.
609         m_caches.clear();
610         return;
611     }
612
613     makeDirty();
614     m_caches.clear();
615     m_isInitialized = false;
616
617     // Clear storages as a memory optimization.
618     m_storage = nullptr;
619     m_volatileStorage.clear();
620 }
621
622 bool Caches::isDirty(uint64_t updateCounter) const
623 {
624     ASSERT(m_updateCounter >= updateCounter);
625     return m_updateCounter != updateCounter;
626 }
627
628 const NetworkCache::Salt& Caches::salt() const
629 {
630     if (m_engine)
631         return m_engine->salt();
632
633     if (!m_volatileSalt)
634         m_volatileSalt = Salt { };
635
636     return m_volatileSalt.value();
637 }
638
639 void Caches::cacheInfos(uint64_t updateCounter, CacheInfosCallback&& callback)
640 {
641     if (m_isWritingCachesToDisk) {
642         m_pendingWritingCachesToDiskCallbacks.append([this, updateCounter, callback = WTFMove(callback)] (auto&& error) mutable {
643             if (error) {
644                 callback(makeUnexpected(error.value()));
645                 return;
646             }
647             this->cacheInfos(updateCounter, WTFMove(callback));
648         });
649         return;
650     }
651
652     Vector<CacheInfo> cacheInfos;
653     if (isDirty(updateCounter)) {
654         cacheInfos.reserveInitialCapacity(m_caches.size());
655         for (auto& cache : m_caches)
656             cacheInfos.uncheckedAppend(CacheInfo { cache.identifier(), cache.name() });
657     }
658     callback(CacheInfos { WTFMove(cacheInfos), m_updateCounter });
659 }
660
661 void Caches::appendRepresentation(StringBuilder& builder) const
662 {
663     builder.append("{ \"persistent\": [");
664
665     bool isFirst = true;
666     for (auto& cache : m_caches) {
667         if (!isFirst)
668             builder.append(", ");
669         isFirst = false;
670         builder.append("\"");
671         builder.append(cache.name());
672         builder.append("\"");
673     }
674
675     builder.append("], \"removed\": [");
676
677     isFirst = true;
678     for (auto& cache : m_removedCaches) {
679         if (!isFirst)
680             builder.append(", ");
681         isFirst = false;
682         builder.append("\"");
683         builder.append(cache.name());
684         builder.append("\"");
685     }
686     builder.append("]}\n");
687 }
688
689 uint64_t Caches::storageSize() const
690 {
691     ASSERT(m_isInitialized);
692     if (!shouldPersist())
693         return 0;
694     return m_storage->approximateSize();
695 }
696
697 } // namespace CacheStorage
698
699 } // namespace WebKit