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