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
38namespace WebKit {
39
40namespace CacheStorage {
41using namespace WebCore::DOMCacheEngine;
42using namespace NetworkCache;
43
44static inline String cachesListFilename(const String& cachesRootPath)
45{
46 return FileSystem::pathByAppendingComponent(cachesRootPath, "cacheslist"_s);
47}
48
49static inline String cachesOriginFilename(const String& cachesRootPath)
50{
51 return FileSystem::pathByAppendingComponent(cachesRootPath, "origin"_s);
52}
53
54Ref<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
61Caches::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
69Caches::~Caches()
70{
71 ASSERT(m_pendingWritingCachesToDiskCallbacks.isEmpty());
72
73 if (m_quotaManager)
74 m_quotaManager->removeUser(*this);
75}
76
77void 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
86void 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
109void 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
123Optional<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
144void 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
206void 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
237void Caches::detach()
238{
239 m_engine = nullptr;
240 m_rootPath = { };
241 clearPendingWritingCachesToDiskCallbacks();
242}
243
244void 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
274void Caches::clearPendingWritingCachesToDiskCallbacks()
275{
276 auto pendingWritingCachesToDiskCallbacks = WTFMove(m_pendingWritingCachesToDiskCallbacks);
277 for (auto& callback : pendingWritingCachesToDiskCallbacks)
278 callback(Error::Internal);
279}
280
281Cache* 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
287Cache* 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
297void 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
334void 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
368bool 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
375void 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
392static 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
406static 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
428void 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
469void 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
498void 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
511void 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) mutable {
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
529void 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) mutable {
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
553void 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
586void 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
595void 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
606void Caches::resetSpaceUsed()
607{
608 m_size = 0;
609 if (m_quotaManager) {
610 m_quotaManager->removeUser(*this);
611 m_quotaManager->addUser(*this);
612 }
613}
614
615void 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
633bool Caches::isDirty(uint64_t updateCounter) const
634{
635 ASSERT(m_updateCounter >= updateCounter);
636 return m_updateCounter != updateCounter;
637}
638
639const 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
650void 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
672void 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
700uint64_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
711