Cache API and IDB space usages should be initialized on first quota check
[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             protectedThis->resetSpaceUsed();
264             completionHandler();
265         });
266         return;
267     }
268     clearMemoryRepresentation();
269     resetSpaceUsed();
270     clearPendingWritingCachesToDiskCallbacks();
271     completionHandler();
272 }
273
274 void Caches::clearPendingWritingCachesToDiskCallbacks()
275 {
276     auto pendingWritingCachesToDiskCallbacks = WTFMove(m_pendingWritingCachesToDiskCallbacks);
277     for (auto& callback : pendingWritingCachesToDiskCallbacks)
278         callback(Error::Internal);
279 }
280
281 Cache* Caches::find(const String& name)
282 {
283     auto position = m_caches.findMatching([&](const auto& item) { return item.name() == name; });
284     return (position != notFound) ? &m_caches[position] : nullptr;
285 }
286
287 Cache* Caches::find(uint64_t identifier)
288 {
289     auto position = m_caches.findMatching([&](const auto& item) { return item.identifier() == identifier; });
290     if (position != notFound)
291         return &m_caches[position];
292
293     position = m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == identifier; });
294     return (position != notFound) ? &m_removedCaches[position] : nullptr;
295 }
296
297 void Caches::open(const String& name, CacheIdentifierCallback&& callback)
298 {
299     ASSERT(m_isInitialized);
300     ASSERT(m_engine);
301
302     if (m_isWritingCachesToDisk) {
303         m_pendingWritingCachesToDiskCallbacks.append([this, name, callback = WTFMove(callback)] (auto&& error) mutable {
304             if (error) {
305                 callback(makeUnexpected(error.value()));
306                 return;
307             }
308             this->open(name, WTFMove(callback));
309         });
310         return;
311     }
312
313     if (auto* cache = find(name)) {
314         cache->open([cacheIdentifier = cache->identifier(), callback = WTFMove(callback)](Optional<Error>&& error) mutable {
315             if (error) {
316                 callback(makeUnexpected(error.value()));
317                 return;
318             }
319             callback(CacheIdentifierOperationResult { cacheIdentifier, false });
320         });
321         return;
322     }
323
324     makeDirty();
325
326     uint64_t cacheIdentifier = m_engine->nextCacheIdentifier();
327     m_caches.append(Cache { *this, cacheIdentifier, Cache::State::Open, String { name }, createCanonicalUUIDString() });
328
329     writeCachesToDisk([callback = WTFMove(callback), cacheIdentifier](Optional<Error>&& error) mutable {
330         callback(CacheIdentifierOperationResult { cacheIdentifier, !!error });
331     });
332 }
333
334 void Caches::remove(uint64_t identifier, CacheIdentifierCallback&& callback)
335 {
336     ASSERT(m_isInitialized);
337     ASSERT(m_engine);
338
339     if (m_isWritingCachesToDisk) {
340         m_pendingWritingCachesToDiskCallbacks.append([this, identifier, callback = WTFMove(callback)] (auto&& error) mutable {
341             if (error) {
342                 callback(makeUnexpected(error.value()));
343                 return;
344             }
345             this->remove(identifier, WTFMove(callback));
346         });
347         return;
348     }
349
350     auto position = m_caches.findMatching([&](const auto& item) { return item.identifier() == identifier; });
351
352     if (position == notFound) {
353         ASSERT(m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == identifier; }) != notFound);
354         callback(CacheIdentifierOperationResult { 0, false });
355         return;
356     }
357
358     makeDirty();
359
360     m_removedCaches.append(WTFMove(m_caches[position]));
361     m_caches.remove(position);
362
363     writeCachesToDisk([callback = WTFMove(callback), identifier](Optional<Error>&& error) mutable {
364         callback(CacheIdentifierOperationResult { identifier, !!error });
365     });
366 }
367
368 bool Caches::hasActiveCache() const
369 {
370     if (m_removedCaches.size())
371         return true;
372     return m_caches.findMatching([](const auto& item) { return item.isActive(); }) != notFound;
373 }
374
375 void Caches::dispose(Cache& cache)
376 {
377     auto position = m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == cache.identifier(); });
378     if (position != notFound) {
379         if (m_storage)
380             m_storage->remove(cache.keys(), [] { });
381
382         m_removedCaches.remove(position);
383         return;
384     }
385     ASSERT(m_caches.findMatching([&](const auto& item) { return item.identifier() == cache.identifier(); }) != notFound);
386     cache.clearMemoryRepresentation();
387
388     if (!hasActiveCache())
389         clearMemoryRepresentation();
390 }
391
392 static inline Data encodeCacheNames(const Vector<Cache>& caches)
393 {
394     WTF::Persistence::Encoder encoder;
395
396     uint64_t size = caches.size();
397     encoder << size;
398     for (auto& cache : caches) {
399         encoder << cache.name();
400         encoder << cache.uniqueName();
401     }
402
403     return Data { encoder.buffer(), encoder.bufferSize() };
404 }
405
406 static inline Expected<Vector<std::pair<String, String>>, Error> decodeCachesNames(const Data& data)
407 {
408     WTF::Persistence::Decoder decoder(data.data(), data.size());
409     uint64_t count;
410     if (!decoder.decode(count))
411         return makeUnexpected(Error::ReadDisk);
412
413     Vector<std::pair<String, String>> names;
414     names.reserveInitialCapacity(count);
415     for (size_t index = 0; index < count; ++index) {
416         String name;
417         if (!decoder.decode(name))
418             return makeUnexpected(Error::ReadDisk);
419         String uniqueName;
420         if (!decoder.decode(uniqueName))
421             return makeUnexpected(Error::ReadDisk);
422
423         names.uncheckedAppend(std::pair<String, String> { WTFMove(name), WTFMove(uniqueName) });
424     }
425     return names;
426 }
427
428 void Caches::readCachesFromDisk(WTF::Function<void(Expected<Vector<Cache>, Error>&&)>&& callback)
429 {
430     ASSERT(m_engine);
431     ASSERT(!m_isInitialized);
432     ASSERT(m_caches.isEmpty());
433
434     if (!shouldPersist()) {
435         callback(Vector<Cache> { });
436         return;
437     }
438
439     auto filename = cachesListFilename(m_rootPath);
440     if (!FileSystem::fileExists(filename)) {
441         callback(Vector<Cache> { });
442         return;
443     }
444
445     m_engine->readFile(filename, [protectedThis = makeRef(*this), this, callback = WTFMove(callback)](const Data& data, int error) mutable {
446         if (!m_engine) {
447             callback(Vector<Cache> { });
448             return;
449         }
450
451         if (error) {
452             RELEASE_LOG_ERROR(CacheStorage, "Caches::readCachesFromDisk failed reading caches from disk with error %d", error);
453             callback(makeUnexpected(Error::ReadDisk));
454             return;
455         }
456
457         auto result = decodeCachesNames(data);
458         if (!result.has_value()) {
459             RELEASE_LOG_ERROR(CacheStorage, "Caches::decodeCachesNames failed decoding caches with error %d", static_cast<int>(result.error()));
460             callback(makeUnexpected(result.error()));
461             return;
462         }
463         callback(WTF::map(WTFMove(result.value()), [this] (auto&& pair) {
464             return Cache { *this, m_engine->nextCacheIdentifier(), Cache::State::Uninitialized, WTFMove(pair.first), WTFMove(pair.second) };
465         }));
466     });
467 }
468
469 void Caches::writeCachesToDisk(CompletionCallback&& callback)
470 {
471     ASSERT(!m_isWritingCachesToDisk);
472     ASSERT(m_isInitialized);
473     if (!shouldPersist()) {
474         callback(WTF::nullopt);
475         return;
476     }
477
478     ASSERT(m_engine);
479
480     if (m_caches.isEmpty()) {
481         m_engine->removeFile(cachesListFilename(m_rootPath));
482         callback(WTF::nullopt);
483         return;
484     }
485
486     m_isWritingCachesToDisk = true;
487     m_engine->writeFile(cachesListFilename(m_rootPath), encodeCacheNames(m_caches), [this, protectedThis = makeRef(*this), callback = WTFMove(callback)](Optional<Error>&& error) mutable {
488         m_isWritingCachesToDisk = false;
489         if (error)
490             RELEASE_LOG_ERROR(CacheStorage, "Caches::writeCachesToDisk failed writing caches to disk with error %d", static_cast<int>(*error));
491
492         callback(WTFMove(error));
493         while (!m_pendingWritingCachesToDiskCallbacks.isEmpty() && !m_isWritingCachesToDisk)
494             m_pendingWritingCachesToDiskCallbacks.takeFirst()(WTF::nullopt);
495     });
496 }
497
498 void Caches::readRecordsList(Cache& cache, NetworkCache::Storage::TraverseHandler&& callback)
499 {
500     ASSERT(m_isInitialized);
501
502     if (!m_storage) {
503         callback(nullptr, { });
504         return;
505     }
506     m_storage->traverse(cache.uniqueName(), { }, [protectedStorage = makeRef(*m_storage), callback = WTFMove(callback)](const auto* storage, const auto& information) {
507         callback(storage, information);
508     });
509 }
510
511 void Caches::requestSpace(uint64_t spaceRequired, WebCore::DOMCacheEngine::CompletionCallback&& callback)
512 {
513     if (!m_quotaManager) {
514         callback(Error::QuotaExceeded);
515         return;
516     }
517
518     m_quotaManager->requestSpace(spaceRequired, [callback = WTFMove(callback)](auto decision) {
519         switch (decision) {
520         case WebCore::StorageQuotaManager::Decision::Deny:
521             callback(Error::QuotaExceeded);
522             return;
523         case WebCore::StorageQuotaManager::Decision::Grant:
524             callback({ });
525         };
526     });
527 }
528
529 void Caches::writeRecord(const Cache& cache, const RecordInformation& recordInformation, Record&& record, uint64_t previousRecordSize, CompletionCallback&& callback)
530 {
531     ASSERT(m_isInitialized);
532
533     ASSERT(m_size >= previousRecordSize);
534     m_size += recordInformation.size;
535     m_size -= previousRecordSize;
536
537     if (!shouldPersist()) {
538         m_volatileStorage.set(recordInformation.key, WTFMove(record));
539         callback(WTF::nullopt);
540         return;
541     }
542
543     m_storage->store(Cache::encode(recordInformation, record), { }, [protectedStorage = makeRef(*m_storage), callback = WTFMove(callback)](int error) {
544         if (error) {
545             RELEASE_LOG_ERROR(CacheStorage, "Caches::writeRecord failed with error %d", error);
546             callback(Error::WriteDisk);
547             return;
548         }
549         callback(WTF::nullopt);
550     });
551 }
552
553 void Caches::readRecord(const NetworkCache::Key& key, WTF::Function<void(Expected<Record, Error>&&)>&& callback)
554 {
555     ASSERT(m_isInitialized);
556
557     if (!shouldPersist()) {
558         auto iterator = m_volatileStorage.find(key);
559         if (iterator == m_volatileStorage.end()) {
560             callback(makeUnexpected(Error::Internal));
561             return;
562         }
563         callback(iterator->value.copy());
564         return;
565     }
566
567     m_storage->retrieve(key, 4, [protectedStorage = makeRef(*m_storage), callback = WTFMove(callback)](std::unique_ptr<Storage::Record> storage, const Storage::Timings&) mutable {
568         if (!storage) {
569             RELEASE_LOG_ERROR(CacheStorage, "Caches::readRecord failed reading record from disk");
570             callback(makeUnexpected(Error::ReadDisk));
571             return false;
572         }
573
574         auto record = Cache::decode(*storage);
575         if (!record) {
576             RELEASE_LOG_ERROR(CacheStorage, "Caches::readRecord failed decoding record from disk");
577             callback(makeUnexpected(Error::ReadDisk));
578             return false;
579         }
580
581         callback(WTFMove(record.value()));
582         return true;
583     });
584 }
585
586 void Caches::removeRecord(const RecordInformation& record)
587 {
588     ASSERT(m_isInitialized);
589
590     ASSERT(m_size >= record.size);
591     m_size -= record.size;
592     removeCacheEntry(record.key);
593 }
594
595 void Caches::removeCacheEntry(const NetworkCache::Key& key)
596 {
597     ASSERT(m_isInitialized);
598
599     if (!shouldPersist()) {
600         m_volatileStorage.remove(key);
601         return;
602     }
603     m_storage->remove(key);
604 }
605
606 void Caches::resetSpaceUsed()
607 {
608     m_size = 0;
609     if (m_quotaManager) {
610         m_quotaManager->removeUser(*this);
611         m_quotaManager->addUser(*this);
612     }
613 }
614
615 void Caches::clearMemoryRepresentation()
616 {
617     if (!m_isInitialized) {
618         ASSERT(!m_storage || !hasActiveCache() || !m_pendingInitializationCallbacks.isEmpty());
619         // m_storage might not be null in case Caches is being initialized. This is fine as nullify it below is a memory optimization.
620         m_caches.clear();
621         return;
622     }
623
624     makeDirty();
625     m_caches.clear();
626     m_isInitialized = false;
627
628     // Clear storages as a memory optimization.
629     m_storage = nullptr;
630     m_volatileStorage.clear();
631 }
632
633 bool Caches::isDirty(uint64_t updateCounter) const
634 {
635     ASSERT(m_updateCounter >= updateCounter);
636     return m_updateCounter != updateCounter;
637 }
638
639 const NetworkCache::Salt& Caches::salt() const
640 {
641     if (m_engine)
642         return m_engine->salt();
643
644     if (!m_volatileSalt)
645         m_volatileSalt = Salt { };
646
647     return m_volatileSalt.value();
648 }
649
650 void Caches::cacheInfos(uint64_t updateCounter, CacheInfosCallback&& callback)
651 {
652     if (m_isWritingCachesToDisk) {
653         m_pendingWritingCachesToDiskCallbacks.append([this, updateCounter, callback = WTFMove(callback)] (auto&& error) mutable {
654             if (error) {
655                 callback(makeUnexpected(error.value()));
656                 return;
657             }
658             this->cacheInfos(updateCounter, WTFMove(callback));
659         });
660         return;
661     }
662
663     Vector<CacheInfo> cacheInfos;
664     if (isDirty(updateCounter)) {
665         cacheInfos.reserveInitialCapacity(m_caches.size());
666         for (auto& cache : m_caches)
667             cacheInfos.uncheckedAppend(CacheInfo { cache.identifier(), cache.name() });
668     }
669     callback(CacheInfos { WTFMove(cacheInfos), m_updateCounter });
670 }
671
672 void Caches::appendRepresentation(StringBuilder& builder) const
673 {
674     builder.append("{ \"persistent\": [");
675
676     bool isFirst = true;
677     for (auto& cache : m_caches) {
678         if (!isFirst)
679             builder.append(", ");
680         isFirst = false;
681         builder.append("\"");
682         builder.append(cache.name());
683         builder.append("\"");
684     }
685
686     builder.append("], \"removed\": [");
687
688     isFirst = true;
689     for (auto& cache : m_removedCaches) {
690         if (!isFirst)
691             builder.append(", ");
692         isFirst = false;
693         builder.append("\"");
694         builder.append(cache.name());
695         builder.append("\"");
696     }
697     builder.append("]}\n");
698 }
699
700 uint64_t Caches::storageSize() const
701 {
702     ASSERT(m_isInitialized);
703     if (!shouldPersist())
704         return 0;
705     return m_storage->approximateSize();
706 }
707
708 } // namespace CacheStorage
709
710 } // namespace WebKit