Cache API and IDB space usages should be initialized on first quota check
[WebKit-https.git] / Source / WebKit / NetworkProcess / cache / CacheStorageEngine.cpp
index 93ca819..e6f9fc8 100644 (file)
 #include "config.h"
 #include "CacheStorageEngine.h"
 
+#include "Logging.h"
+#include "NetworkCacheFileSystem.h"
+#include "NetworkCacheIOChannel.h"
+#include "NetworkProcess.h"
+#include "WebsiteDataType.h"
 #include <WebCore/CacheQueryOptions.h>
+#include <WebCore/SecurityOrigin.h>
 #include <pal/SessionID.h>
-#include <wtf/MainThread.h>
+#include <wtf/CallbackAggregator.h>
 #include <wtf/NeverDestroyed.h>
+#include <wtf/text/StringBuilder.h>
 #include <wtf/text/StringHash.h>
 
-using namespace WebCore::DOMCache;
-
 namespace WebKit {
 
 namespace CacheStorage {
 
-static HashMap<PAL::SessionID, std::unique_ptr<Engine>>& globalEngineMap()
+using namespace WebCore::DOMCacheEngine;
+using namespace NetworkCache;
+
+String Engine::cachesRootPath(const WebCore::ClientOrigin& origin)
 {
-    static NeverDestroyed<HashMap<PAL::SessionID, std::unique_ptr<Engine>>> map;
-    return map;
+    if (!shouldPersist() || !m_salt)
+        return { };
+
+    Key key(origin.topOrigin.toString(), origin.clientOrigin.toString(), { }, { }, salt());
+    return FileSystem::pathByAppendingComponent(rootPath(), key.hashAsString());
 }
 
-Engine& Engine::from(PAL::SessionID sessionID)
+Engine::~Engine()
 {
-    if (sessionID == PAL::SessionID::defaultSessionID())
-        return defaultEngine();
+    for (auto& caches : m_caches.values())
+        caches->detach();
 
-    return *globalEngineMap().ensure(sessionID, [] {
-        return std::make_unique<Engine>();
-    }).iterator->value;
+    auto initializationCallbacks = WTFMove(m_initializationCallbacks);
+    for (auto& callback : initializationCallbacks)
+        callback(Error::Internal);
+
+    auto writeCallbacks = WTFMove(m_pendingWriteCallbacks);
+    for (auto& callback : writeCallbacks.values())
+        callback(Error::Internal);
+
+    auto readCallbacks = WTFMove(m_pendingReadCallbacks);
+    for (auto& callback : readCallbacks.values())
+        callback(Data { }, 1);
+}
+
+void Engine::from(NetworkProcess& networkProcess, PAL::SessionID sessionID, Function<void(Engine&)>&& callback)
+{
+    if (auto* engine = networkProcess.findCacheEngine(sessionID)) {
+        callback(*engine);
+        return;
+    }
+
+    networkProcess.cacheStorageRootPath(sessionID, [networkProcess = makeRef(networkProcess), sessionID, callback = WTFMove(callback)] (auto&& rootPath) mutable {
+        callback(networkProcess->ensureCacheEngine(sessionID, [&] {
+            return adoptRef(*new Engine { sessionID, networkProcess.get(), WTFMove(rootPath) });
+        }));
+    });
 }
 
-void Engine::destroyEngine(PAL::SessionID sessionID)
+void Engine::destroyEngine(NetworkProcess& networkProcess, PAL::SessionID sessionID)
 {
     ASSERT(sessionID != PAL::SessionID::defaultSessionID());
-    globalEngineMap().remove(sessionID);
+    networkProcess.removeCacheEngine(sessionID);
+}
+
+void Engine::fetchEntries(NetworkProcess& networkProcess, PAL::SessionID sessionID, bool shouldComputeSize, CompletionHandler<void(Vector<WebsiteData::Entry>)>&& completionHandler)
+{
+    from(networkProcess, sessionID, [shouldComputeSize, completionHandler = WTFMove(completionHandler)] (auto& engine) mutable {
+        engine.fetchEntries(shouldComputeSize, WTFMove(completionHandler));
+    });
+}
+
+void Engine::open(NetworkProcess& networkProcess, PAL::SessionID sessionID, WebCore::ClientOrigin&& origin, String&& cacheName, WebCore::DOMCacheEngine::CacheIdentifierCallback&& callback)
+{
+    from(networkProcess, sessionID, [origin = WTFMove(origin), cacheName = WTFMove(cacheName), callback = WTFMove(callback)](auto& engine) mutable {
+        engine.open(origin, cacheName, WTFMove(callback));
+    });
+}
+
+void Engine::remove(NetworkProcess& networkProcess, PAL::SessionID sessionID, uint64_t cacheIdentifier, WebCore::DOMCacheEngine::CacheIdentifierCallback&& callback)
+{
+    from(networkProcess, sessionID, [cacheIdentifier, callback = WTFMove(callback)](auto& engine) mutable {
+        engine.remove(cacheIdentifier, WTFMove(callback));
+    });
+}
+
+void Engine::retrieveCaches(NetworkProcess& networkProcess, PAL::SessionID sessionID, WebCore::ClientOrigin&& origin, uint64_t updateCounter, WebCore::DOMCacheEngine::CacheInfosCallback&& callback)
+{
+    from(networkProcess, sessionID, [origin = WTFMove(origin), updateCounter, callback = WTFMove(callback)](auto& engine) mutable {
+        engine.retrieveCaches(origin, updateCounter, WTFMove(callback));
+    });
+}
+
+
+void Engine::retrieveRecords(NetworkProcess& networkProcess, PAL::SessionID sessionID, uint64_t cacheIdentifier, URL&& url, WebCore::DOMCacheEngine::RecordsCallback&& callback)
+{
+    from(networkProcess, sessionID, [cacheIdentifier, url = WTFMove(url), callback = WTFMove(callback)](auto& engine) mutable {
+        engine.retrieveRecords(cacheIdentifier, WTFMove(url), WTFMove(callback));
+    });
+}
+
+void Engine::putRecords(NetworkProcess& networkProcess, PAL::SessionID sessionID, uint64_t cacheIdentifier, Vector<WebCore::DOMCacheEngine::Record>&& records, WebCore::DOMCacheEngine::RecordIdentifiersCallback&& callback)
+{
+    from(networkProcess, sessionID, [cacheIdentifier, records = WTFMove(records), callback = WTFMove(callback)](auto& engine) mutable {
+        engine.putRecords(cacheIdentifier, WTFMove(records), WTFMove(callback));
+    });
+}
+
+void Engine::deleteMatchingRecords(NetworkProcess& networkProcess, PAL::SessionID sessionID, uint64_t cacheIdentifier, WebCore::ResourceRequest&& request, WebCore::CacheQueryOptions&& options, WebCore::DOMCacheEngine::RecordIdentifiersCallback&& callback)
+{
+    from(networkProcess, sessionID, [cacheIdentifier, request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)](auto& engine) mutable {
+        engine.deleteMatchingRecords(cacheIdentifier, WTFMove(request), WTFMove(options), WTFMove(callback));
+    });
+}
+
+void Engine::lock(NetworkProcess& networkProcess, PAL::SessionID sessionID, uint64_t cacheIdentifier)
+{
+    from(networkProcess, sessionID, [cacheIdentifier](auto& engine) mutable {
+        engine.lock(cacheIdentifier);
+    });
+}
+
+void Engine::unlock(NetworkProcess& networkProcess, PAL::SessionID sessionID, uint64_t cacheIdentifier)
+{
+    from(networkProcess, sessionID, [cacheIdentifier](auto& engine) mutable {
+        engine.unlock(cacheIdentifier);
+    });
+}
+
+void Engine::clearMemoryRepresentation(NetworkProcess& networkProcess, PAL::SessionID sessionID, WebCore::ClientOrigin&& origin, WebCore::DOMCacheEngine::CompletionCallback&& callback)
+{
+    from(networkProcess, sessionID, [origin = WTFMove(origin), callback = WTFMove(callback)](auto& engine) mutable {
+        engine.clearMemoryRepresentation(origin, WTFMove(callback));
+    });
+}
+
+void Engine::representation(NetworkProcess& networkProcess, PAL::SessionID sessionID, CompletionHandler<void(String&&)>&& callback)
+{
+    from(networkProcess, sessionID, [callback = WTFMove(callback)](auto& engine) mutable {
+        callback(engine.representation());
+    });
+}
+
+void Engine::clearAllCaches(NetworkProcess& networkProcess, PAL::SessionID sessionID, CompletionHandler<void()>&& completionHandler)
+{
+    from(networkProcess, sessionID, [completionHandler = WTFMove(completionHandler)](auto& engine) mutable {
+        engine.clearAllCaches(WTFMove(completionHandler));
+    });
 }
 
-Engine& Engine::defaultEngine()
+void Engine::clearCachesForOrigin(NetworkProcess& networkProcess, PAL::SessionID sessionID, WebCore::SecurityOriginData&& originData, CompletionHandler<void()>&& completionHandler)
 {
-    static NeverDestroyed<std::unique_ptr<Engine>> defaultEngine = { std::make_unique<Engine>() };
-    return *defaultEngine.get();
+    from(networkProcess, sessionID, [originData = WTFMove(originData), completionHandler = WTFMove(completionHandler)](auto& engine) mutable {
+        engine.clearCachesForOrigin(originData, WTFMove(completionHandler));
+    });
 }
 
-void Engine::open(const String& origin, const String& cacheName, CacheIdentifierCallback&& callback)
+void Engine::initializeQuotaUser(NetworkProcess& networkProcess, PAL::SessionID sessionID, const WebCore::ClientOrigin& clientOrigin, CompletionHandler<void()>&& completionHandler)
 {
-    readCachesFromDisk(origin, [this, cacheName, callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable {
-        if (!cachesOrError.hasValue()) {
+    from(networkProcess, sessionID, [clientOrigin, completionHandler = WTFMove(completionHandler)](auto& engine) mutable {
+        engine.readCachesFromDisk(clientOrigin, [completionHandler = WTFMove(completionHandler)](auto&& cachesOrError) mutable {
+            completionHandler();
+        });
+    });
+}
+
+Engine::Engine(PAL::SessionID sessionID, NetworkProcess& process, String&& rootPath)
+    : m_sessionID(sessionID)
+    , m_networkProcess(makeWeakPtr(process))
+    , m_rootPath(WTFMove(rootPath))
+{
+    if (!m_rootPath.isNull())
+        m_ioQueue = WorkQueue::create("com.apple.WebKit.CacheStorageEngine.serialBackground", WorkQueue::Type::Serial, WorkQueue::QOS::Background);
+}
+
+void Engine::open(const WebCore::ClientOrigin& origin, const String& cacheName, CacheIdentifierCallback&& callback)
+{
+    readCachesFromDisk(origin, [cacheName, callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable {
+        if (!cachesOrError.has_value()) {
             callback(makeUnexpected(cachesOrError.error()));
             return;
         }
 
-        auto& caches = cachesOrError.value().get();
-
-        auto position = caches.findMatching([&](const auto& item) { return item.name == cacheName; });
-        if (position == notFound) {
-            uint64_t cacheIdentifier = ++m_nextCacheIdentifier;
-            caches.append(Cache { cacheIdentifier, cacheName, Vector<Record>(), 0 });
-            writeCachesToDisk([cacheIdentifier, callback = WTFMove(callback)](std::optional<Error>&& error) {
-                if (error) {
-                    callback(makeUnexpected(error.value()));
-                    return;
-                }
-                callback(cacheIdentifier);
-            });
-        } else
-            callback(caches[position].identifier);
-
+        cachesOrError.value().get().open(cacheName, WTFMove(callback));
     });
 }
 
 void Engine::remove(uint64_t cacheIdentifier, CacheIdentifierCallback&& callback)
 {
-    std::optional<Cache> removedCache;
+    Caches* cachesToModify = nullptr;
     for (auto& caches : m_caches.values()) {
-        auto position = caches.findMatching([&](const auto& item) { return item.identifier == cacheIdentifier; });
-        if (position != notFound) {
-            removedCache = WTFMove(caches[position]);
-            caches.remove(position);
+        auto* cacheToRemove = caches->find(cacheIdentifier);
+        if (cacheToRemove) {
+            cachesToModify = caches.get();
             break;
         }
     }
-    if (!removedCache) {
+    if (!cachesToModify) {
         callback(makeUnexpected(Error::Internal));
         return;
     }
-    m_removedCaches.append(WTFMove(removedCache.value()));
-    writeCachesToDisk([cacheIdentifier, callback = WTFMove(callback)](std::optional<Error>&& error) {
-        if (error) {
-            callback(makeUnexpected(error.value()));
-            return;
-        }
-        callback(cacheIdentifier);
-    });
+
+    cachesToModify->remove(cacheIdentifier, WTFMove(callback));
 }
 
-void Engine::retrieveCaches(const String& origin, CacheInfosCallback&& callback)
+void Engine::retrieveCaches(const WebCore::ClientOrigin& origin, uint64_t updateCounter, CacheInfosCallback&& callback)
 {
-    readCachesFromDisk(origin, [callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable {
-        if (!cachesOrError.hasValue()) {
+    readCachesFromDisk(origin, [updateCounter, callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable {
+        if (!cachesOrError.has_value()) {
             callback(makeUnexpected(cachesOrError.error()));
             return;
         }
 
-        auto& caches = cachesOrError.value().get();
-
-        Vector<CacheInfo> cachesInfo;
-        cachesInfo.reserveInitialCapacity(caches.size());
-        for (auto& cache : caches)
-            cachesInfo.uncheckedAppend(CacheInfo { cache.identifier, cache.name});
-
-        callback(WTFMove(cachesInfo));
+        cachesOrError.value().get().cacheInfos(updateCounter, WTFMove(callback));
     });
 }
 
-void Engine::retrieveRecords(uint64_t cacheIdentifier, RecordsCallback&& callback)
+void Engine::retrieveRecords(uint64_t cacheIdentifier, URL&& url, RecordsCallback&& callback)
 {
-    readCache(cacheIdentifier, [callback = WTFMove(callback)](CacheOrError&& result) mutable {
-        if (!result.hasValue()) {
+    readCache(cacheIdentifier, [url = WTFMove(url), callback = WTFMove(callback)](CacheOrError&& result) mutable {
+        if (!result.has_value()) {
             callback(makeUnexpected(result.error()));
             return;
         }
-        // FIXME: Pass records by reference.
-        auto& records = result.value().get().records;
-
-        Vector<Record> copy;
-        copy.reserveInitialCapacity(records.size());
-        for (auto& record : result.value().get().records)
-            copy.uncheckedAppend(record.copy());
-
-        callback(WTFMove(copy));
+        result.value().get().retrieveRecords(url, WTFMove(callback));
     });
 }
 
 void Engine::putRecords(uint64_t cacheIdentifier, Vector<Record>&& records, RecordIdentifiersCallback&& callback)
 {
-    readCache(cacheIdentifier, [this, cacheIdentifier, records = WTFMove(records), callback = WTFMove(callback)](CacheOrError&& result) mutable {
-        if (!result.hasValue()) {
+    readCache(cacheIdentifier, [records = WTFMove(records), callback = WTFMove(callback)](CacheOrError&& result) mutable {
+        if (!result.has_value()) {
             callback(makeUnexpected(result.error()));
             return;
         }
 
-        Cache& cache = result.value();
-
-        WebCore::CacheQueryOptions options;
-        Vector<uint64_t> recordIdentifiers;
-        recordIdentifiers.reserveInitialCapacity(records.size());
-        for (auto& record : records) {
-            auto matchingRecords = Engine::queryCache(cache.records, record.request, options);
-            if (matchingRecords.isEmpty()) {
-                record.identifier = ++cache.nextRecordIdentifier;
-                recordIdentifiers.uncheckedAppend(record.identifier);
-                cache.records.append(WTFMove(record));
-            } else {
-                auto identifier = matchingRecords[0];
-                auto position = cache.records.findMatching([&](const auto& item) { return item.identifier == identifier; });
-                ASSERT(position != notFound);
-                if (position != notFound) {
-                    auto& existingRecord = cache.records[position];
-                    recordIdentifiers.uncheckedAppend(identifier);
-                    existingRecord.responseHeadersGuard = record.responseHeadersGuard;
-                    existingRecord.response = WTFMove(record.response);
-                    existingRecord.responseBody = WTFMove(record.responseBody);
-                    ++existingRecord.updateResponseCounter;
-                }
-            }
-        }
-        writeCacheRecords(cacheIdentifier, WTFMove(recordIdentifiers), [callback = WTFMove(callback)](RecordIdentifiersOrError&& result) mutable {
-            callback(WTFMove(result));
-        });
+        result.value().get().put(WTFMove(records), WTFMove(callback));
     });
 }
 
 void Engine::deleteMatchingRecords(uint64_t cacheIdentifier, WebCore::ResourceRequest&& request, WebCore::CacheQueryOptions&& options, RecordIdentifiersCallback&& callback)
 {
-    readCache(cacheIdentifier, [this, cacheIdentifier, request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)](CacheOrError&& result) mutable {
-        if (!result.hasValue()) {
+    readCache(cacheIdentifier, [request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)](CacheOrError&& result) mutable {
+        if (!result.has_value()) {
             callback(makeUnexpected(result.error()));
             return;
         }
 
-        auto& currentRecords = result.value().get().records;
+        result.value().get().remove(WTFMove(request), WTFMove(options), WTFMove(callback));
+    });
+}
+
+void Engine::initialize(CompletionCallback&& callback)
+{
+    if (m_salt) {
+        callback(WTF::nullopt);
+        return;
+    }
 
-        auto recordsToRemove = queryCache(currentRecords, request, options);
-        if (recordsToRemove.isEmpty()) {
-            callback({ });
+    if (!shouldPersist()) {
+        m_salt = NetworkCache::Salt { };
+        callback(WTF::nullopt);
+        return;
+    }
+
+    bool shouldComputeSalt = m_initializationCallbacks.isEmpty();
+    m_initializationCallbacks.append(WTFMove(callback));
+
+    if (!shouldComputeSalt)
+        return;
+
+    String saltPath = FileSystem::pathByAppendingComponent(m_rootPath, "salt"_s);
+    m_ioQueue->dispatch([this, weakThis = makeWeakPtr(this), saltPath = WTFMove(saltPath)] () mutable {
+        FileSystem::makeAllDirectories(m_rootPath);
+        RunLoop::main().dispatch([this, weakThis = WTFMove(weakThis), salt = readOrMakeSalt(saltPath)]() mutable {
+            if (!weakThis)
+                return;
+
+            m_salt = WTFMove(salt);
+
+            auto callbacks = WTFMove(m_initializationCallbacks);
+            for (auto& callback : callbacks)
+                callback(m_salt ? WTF::nullopt : makeOptional(Error::WriteDisk));
+        });
+    });
+}
+
+void Engine::readCachesFromDisk(const WebCore::ClientOrigin& origin, CachesCallback&& callback)
+{
+    initialize([this, origin, callback = WTFMove(callback)](Optional<Error>&& error) mutable {
+        if (error) {
+            callback(makeUnexpected(error.value()));
             return;
         }
 
-        Vector<Record> recordsToKeep;
-        for (auto& record : currentRecords) {
-            if (recordsToRemove.findMatching([&](auto item) { return item == record.identifier; }) == notFound)
-                recordsToKeep.append(record.copy());
+        auto& caches = m_caches.ensure(origin, [&origin, this] {
+            auto path = cachesRootPath(origin);
+            return Caches::create(*this, WebCore::ClientOrigin { origin }, WTFMove(path), m_networkProcess->storageQuotaManager(m_sessionID, origin));
+        }).iterator->value;
+
+        if (caches->isInitialized()) {
+            callback(std::reference_wrapper<Caches> { *caches });
+            return;
         }
-        removeCacheRecords(cacheIdentifier, WTFMove(recordsToRemove), [this, cacheIdentifier, recordsToKeep = WTFMove(recordsToKeep), callback = WTFMove(callback)](RecordIdentifiersOrError&& result) mutable {
-            if (!result.hasValue()) {
-                callback(makeUnexpected(result.error()));
+
+        caches->initialize([callback = WTFMove(callback), caches = caches.copyRef()](Optional<Error>&& error) mutable {
+            if (error) {
+                callback(makeUnexpected(error.value()));
                 return;
             }
 
-            auto* writtenCache = cache(cacheIdentifier);
-            if (!writtenCache) {
-                callback(makeUnexpected(Error::Internal));
+            callback(std::reference_wrapper<Caches> { *caches });
+        });
+    });
+}
+
+void Engine::readCache(uint64_t cacheIdentifier, CacheCallback&& callback)
+{
+    auto* cache = this->cache(cacheIdentifier);
+    if (!cache) {
+        callback(makeUnexpected(Error::Internal));
+        return;
+    }
+    if (!cache->isOpened()) {
+        cache->open([this, protectedThis = makeRef(*this), cacheIdentifier, callback = WTFMove(callback)](Optional<Error>&& error) mutable {
+            if (error) {
+                callback(makeUnexpected(error.value()));
                 return;
             }
-            writtenCache->records = WTFMove(recordsToKeep);
 
-            callback(WTFMove(result.value()));
+            auto* cache = this->cache(cacheIdentifier);
+            if (!cache) {
+                callback(makeUnexpected(Error::Internal));
+                return;
+            }
+            ASSERT(cache->isOpened());
+            callback(std::reference_wrapper<Cache> { *cache });
         });
-    });
+        return;
+    }
+    callback(std::reference_wrapper<Cache> { *cache });
 }
 
-void Engine::writeCachesToDisk(Function<void(std::optional<Error>&&)>&& callback)
+Cache* Engine::cache(uint64_t cacheIdentifier)
 {
-    // FIXME: Implement writing.
-    callback(std::nullopt);
+    Cache* result = nullptr;
+    for (auto& caches : m_caches.values()) {
+        if ((result = caches->find(cacheIdentifier)))
+            break;
+    }
+    return result;
 }
 
-void Engine::readCachesFromDisk(const String& origin, CachesCallback&& callback)
+void Engine::writeFile(const String& filename, NetworkCache::Data&& data, WebCore::DOMCacheEngine::CompletionCallback&& callback)
 {
-    // FIXME: Implement reading.
+    if (!shouldPersist()) {
+        callback(WTF::nullopt);
+        return;
+    }
 
-    auto& caches = m_caches.ensure(origin, [] {
-        return Vector<Cache>();
-    }).iterator->value;
+    m_pendingWriteCallbacks.add(++m_pendingCallbacksCounter, WTFMove(callback));
+    m_ioQueue->dispatch([this, weakThis = makeWeakPtr(this), identifier = m_pendingCallbacksCounter, data = WTFMove(data), filename = filename.isolatedCopy()]() mutable {
+
+        String directoryPath = FileSystem::directoryName(filename);
+        if (!FileSystem::fileExists(directoryPath))
+            FileSystem::makeAllDirectories(directoryPath);
 
-    callback(std::reference_wrapper<Vector<Cache>> { caches });
+        auto channel = IOChannel::open(filename, IOChannel::Type::Create);
+        channel->write(0, data, nullptr, [this, weakThis = WTFMove(weakThis), identifier](int error) mutable {
+            ASSERT(RunLoop::isMain());
+            if (!weakThis)
+                return;
+
+            auto callback = m_pendingWriteCallbacks.take(identifier);
+            if (error) {
+                RELEASE_LOG_ERROR(CacheStorage, "CacheStorage::Engine::writeFile failed with error %d", error);
+
+                callback(Error::WriteDisk);
+                return;
+            }
+            callback(WTF::nullopt);
+        });
+    });
 }
 
-void Engine::readCache(uint64_t cacheIdentifier, CacheCallback&& callback)
+void Engine::readFile(const String& filename, CompletionHandler<void(const NetworkCache::Data&, int error)>&& callback)
 {
-    // FIXME: Implement reading.
-    auto* cache = this->cache(cacheIdentifier);
-    if (!cache) {
-        callback(makeUnexpected(Error::Internal));
+    if (!shouldPersist()) {
+        callback(Data { }, 0);
         return;
     }
-    callback(std::reference_wrapper<Cache> { *cache });
+
+    m_pendingReadCallbacks.add(++m_pendingCallbacksCounter, WTFMove(callback));
+    m_ioQueue->dispatch([this, weakThis = makeWeakPtr(this), identifier = m_pendingCallbacksCounter, filename = filename.isolatedCopy()]() mutable {
+        auto channel = IOChannel::open(filename, IOChannel::Type::Read);
+        if (channel->fileDescriptor() < 0) {
+            RunLoop::main().dispatch([this, weakThis = WTFMove(weakThis), identifier]() mutable {
+                if (!weakThis)
+                    return;
+
+                m_pendingReadCallbacks.take(identifier)(Data { }, 0);
+            });
+            return;
+        }
+
+        channel->read(0, std::numeric_limits<size_t>::max(), nullptr, [this, weakThis = WTFMove(weakThis), identifier](const Data& data, int error) mutable {
+            RELEASE_LOG_ERROR_IF(error, CacheStorage, "CacheStorage::Engine::readFile failed with error %d", error);
+
+            // FIXME: We should do the decoding in the background thread.
+            ASSERT(RunLoop::isMain());
+
+            if (!weakThis)
+                return;
+
+            m_pendingReadCallbacks.take(identifier)(data, error);
+        });
+    });
 }
 
-void Engine::writeCacheRecords(uint64_t cacheIdentifier, Vector<uint64_t>&& recordsIdentifiers, RecordIdentifiersCallback&& callback)
+void Engine::removeFile(const String& filename)
 {
-    // FIXME: Implement writing.
-    callback(WTFMove(recordsIdentifiers));
+    if (!shouldPersist())
+        return;
+
+    m_ioQueue->dispatch([filename = filename.isolatedCopy()]() mutable {
+        FileSystem::deleteFile(filename);
+    });
+}
+
+class ReadOriginsTaskCounter : public RefCounted<ReadOriginsTaskCounter> {
+public:
+    static Ref<ReadOriginsTaskCounter> create(WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& callback)
+    {
+        return adoptRef(*new ReadOriginsTaskCounter(WTFMove(callback)));
+    }
+
+    ~ReadOriginsTaskCounter()
+    {
+        m_callback(WTFMove(m_entries));
+    }
+
+    void addOrigin(WebCore::SecurityOriginData&& origin, uint64_t size)
+    {
+        m_entries.append(WebsiteData::Entry { WTFMove(origin), WebsiteDataType::DOMCache, size });
+    }
+
+private:
+    explicit ReadOriginsTaskCounter(WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& callback)
+        : m_callback(WTFMove(callback))
+    {
+    }
+
+    WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)> m_callback;
+    Vector<WebsiteData::Entry> m_entries;
+};
+
+void Engine::fetchEntries(bool shouldComputeSize, WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& completionHandler)
+{
+    if (!shouldPersist()) {
+        auto entries = WTF::map(m_caches, [] (auto& pair) {
+            return WebsiteData::Entry { pair.value->origin().clientOrigin, WebsiteDataType::DOMCache, 0 };
+        });
+        completionHandler(WTFMove(entries));
+        return;
+    }
+
+    auto taskCounter = ReadOriginsTaskCounter::create(WTFMove(completionHandler));
+    for (auto& folderPath : FileSystem::listDirectory(m_rootPath, "*")) {
+        if (!FileSystem::fileIsDirectory(folderPath, FileSystem::ShouldFollowSymbolicLinks::No))
+            continue;
+        Caches::retrieveOriginFromDirectory(folderPath, *m_ioQueue, [protectedThis = makeRef(*this), shouldComputeSize, taskCounter = taskCounter.copyRef()] (auto&& origin) mutable {
+            ASSERT(RunLoop::isMain());
+            if (!origin)
+                return;
+
+            if (!shouldComputeSize) {
+                taskCounter->addOrigin(WTFMove(origin->topOrigin), 0);
+                taskCounter->addOrigin(WTFMove(origin->clientOrigin), 0);
+                return;
+            }
+
+            protectedThis->readCachesFromDisk(origin.value(), [origin = origin.value(), taskCounter = WTFMove(taskCounter)] (CachesOrError&& result) mutable {
+                if (!result.has_value())
+                    return;
+                taskCounter->addOrigin(WTFMove(origin.topOrigin), 0);
+                taskCounter->addOrigin(WTFMove(origin.clientOrigin), result.value().get().storageSize());
+            });
+        });
+    }
 }
 
-void Engine::removeCacheRecords(uint64_t cacheIdentifier, Vector<uint64_t>&& recordsIdentifiers, RecordIdentifiersCallback&& callback)
+void Engine::clearAllCaches(CompletionHandler<void()>&& completionHandler)
 {
-    // FIXME: Implement writing.
-    callback(WTFMove(recordsIdentifiers));
+    ASSERT(RunLoop::isMain());
+
+    auto callbackAggregator = CallbackAggregator::create(WTFMove(completionHandler));
+
+    for (auto& caches : m_caches.values())
+        caches->clear([callbackAggregator = callbackAggregator.copyRef()] { });
+
+    if (!shouldPersist())
+        return;
+
+    clearAllCachesFromDisk([callbackAggregator = WTFMove(callbackAggregator)] { });
 }
 
-Cache* Engine::cache(uint64_t cacheIdentifier)
+void Engine::clearAllCachesFromDisk(CompletionHandler<void()>&& completionHandler)
 {
-    Cache* result = nullptr;
-    for (auto& caches : m_caches.values()) {
-        auto position = caches.findMatching([&](const auto& item) { return item.identifier == cacheIdentifier; });
-        if (position != notFound) {
-            result = &caches[position];
-            break;
+    ASSERT(RunLoop::isMain());
+
+    m_ioQueue->dispatch([path = m_rootPath.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
+        for (auto& filename : FileSystem::listDirectory(path, "*")) {
+            if (FileSystem::fileIsDirectory(filename, FileSystem::ShouldFollowSymbolicLinks::No))
+                deleteDirectoryRecursively(filename);
         }
+        RunLoop::main().dispatch(WTFMove(completionHandler));
+    });
+}
+
+void Engine::clearCachesForOrigin(const WebCore::SecurityOriginData& origin, CompletionHandler<void()>&& completionHandler)
+{
+    ASSERT(RunLoop::isMain());
+
+    auto callbackAggregator = CallbackAggregator::create(WTFMove(completionHandler));
+
+    for (auto& keyValue : m_caches) {
+        if (keyValue.key.topOrigin == origin || keyValue.key.clientOrigin == origin)
+            keyValue.value->clear([callbackAggregator = callbackAggregator.copyRef()] { });
     }
-    if (!result) {
-        auto position = m_removedCaches.findMatching([&](const auto& item) { return item.identifier == cacheIdentifier; });
-        if (position != notFound)
-            result = &m_removedCaches[position];
+
+    if (!shouldPersist())
+        return;
+
+    clearCachesForOriginFromDisk(origin, [callbackAggregator = WTFMove(callbackAggregator)] { });
+}
+
+void Engine::clearCachesForOriginFromDisk(const WebCore::SecurityOriginData& origin, CompletionHandler<void()>&& completionHandler)
+{
+    ASSERT(RunLoop::isMain());
+
+    auto callbackAggregator = CallbackAggregator::create(WTFMove(completionHandler));
+
+    for (auto& folderPath : FileSystem::listDirectory(m_rootPath, "*")) {
+        if (!FileSystem::fileIsDirectory(folderPath, FileSystem::ShouldFollowSymbolicLinks::No))
+            continue;
+        Caches::retrieveOriginFromDirectory(folderPath, *m_ioQueue, [this, protectedThis = makeRef(*this), origin, callbackAggregator = callbackAggregator.copyRef(), folderPath] (Optional<WebCore::ClientOrigin>&& folderOrigin) mutable {
+            if (!folderOrigin)
+                return;
+            if (folderOrigin->topOrigin != origin && folderOrigin->clientOrigin != origin)
+                return;
+
+            ASSERT(folderPath == cachesRootPath(*folderOrigin));
+            deleteDirectoryRecursivelyOnBackgroundThread(folderPath, [callbackAggregator = WTFMove(callbackAggregator)] { });
+        });
     }
-    return result;
 }
 
-Vector<uint64_t> Engine::queryCache(const Vector<Record>& records, const WebCore::ResourceRequest& request, const WebCore::CacheQueryOptions& options)
+void Engine::deleteDirectoryRecursivelyOnBackgroundThread(const String& path, CompletionHandler<void()>&& completionHandler)
 {
-    if (!options.ignoreMethod && request.httpMethod() != "GET")
-        return { };
+    ASSERT(RunLoop::isMain());
+
+    m_ioQueue->dispatch([path = path.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
+        deleteDirectoryRecursively(path);
+
+        RunLoop::main().dispatch(WTFMove(completionHandler));
+    });
+}
 
-    Vector<uint64_t> results;
-    for (const auto& record : records) {
-        if (WebCore::DOMCache::queryCacheMatch(request, record.request, record.response, options))
-            results.append(record.identifier);
+void Engine::clearMemoryRepresentation(const WebCore::ClientOrigin& origin, WebCore::DOMCacheEngine::CompletionCallback&& callback)
+{
+    readCachesFromDisk(origin, [callback = WTFMove(callback)](CachesOrError&& result) {
+        if (!result.has_value()) {
+            callback(result.error());
+            return;
+        }
+        result.value().get().clearMemoryRepresentation();
+        callback(WTF::nullopt);
+    });
+}
+
+void Engine::lock(uint64_t cacheIdentifier)
+{
+    auto& counter = m_cacheLocks.ensure(cacheIdentifier, []() {
+        return 0;
+    }).iterator->value;
+
+    ++counter;
+}
+
+void Engine::unlock(uint64_t cacheIdentifier)
+{
+    auto lockCount = m_cacheLocks.find(cacheIdentifier);
+    if (lockCount == m_cacheLocks.end())
+        return;
+
+    ASSERT(lockCount->value);
+    if (--lockCount->value)
+        return;
+
+    auto* cache = this->cache(cacheIdentifier);
+    if (!cache)
+        return;
+
+    cache->dispose();
+}
+
+String Engine::representation()
+{
+    bool isFirst = true;
+    StringBuilder builder;
+    builder.append("{ \"path\": \"");
+    builder.append(m_rootPath);
+    builder.append("\", \"origins\": [");
+    for (auto& keyValue : m_caches) {
+        if (!isFirst)
+            builder.append(",");
+        isFirst = false;
+
+        builder.append("\n{ \"origin\" : { \"topOrigin\" : \"");
+        builder.append(keyValue.key.topOrigin.toString());
+        builder.append("\", \"clientOrigin\": \"");
+        builder.append(keyValue.key.clientOrigin.toString());
+        builder.append("\" }, \"caches\" : ");
+        keyValue.value->appendRepresentation(builder);
+        builder.append("}");
     }
-    return results;
+    builder.append("]}");
+    return builder.toString();
 }
 
 } // namespace CacheStorage
 
 } // namespace WebKit
-