blob: 07772a6950968d4062392bb21dfb9bd09393d430 [file] [log] [blame]
/*
* Copyright (C) 2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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 <wtf/CallbackAggregator.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/Scope.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/StringHash.h>
namespace WebKit {
namespace CacheStorage {
using namespace WebCore::DOMCacheEngine;
using namespace NetworkCache;
static Lock globalSizeFileLock;
String Engine::cachesRootPath(const WebCore::ClientOrigin& origin)
{
if (!shouldPersist() || !m_salt)
return { };
Key key(origin.topOrigin.toString(), origin.clientOrigin.toString(), { }, { }, salt());
return FileSystem::pathByAppendingComponent(rootPath(), key.hashAsString());
}
Engine::~Engine()
{
for (auto& caches : m_caches.values())
caches->detach();
auto pendingClearCallbacks = WTFMove(m_pendingClearCallbacks);
for (auto& callback : pendingClearCallbacks)
callback(Error::Internal);
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(NetworkProcess& networkProcess, PAL::SessionID sessionID)
{
#if !USE(SOUP)
// Soup based ports destroy the default session right before the process exits to avoid leaking
// network resources like the cookies database.
ASSERT(sessionID != PAL::SessionID::defaultSessionID());
#endif
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));
});
}
void Engine::clearCachesForOrigin(NetworkProcess& networkProcess, PAL::SessionID sessionID, WebCore::SecurityOriginData&& originData, CompletionHandler<void()>&& completionHandler)
{
from(networkProcess, sessionID, [originData = WTFMove(originData), completionHandler = WTFMove(completionHandler)](auto& engine) mutable {
engine.clearCachesForOrigin(originData, WTFMove(completionHandler));
});
}
static uint64_t getDirectorySize(const String& directoryPath)
{
ASSERT(!isMainThread());
uint64_t directorySize = 0;
Deque<String> paths;
paths.append(directoryPath);
while (!paths.isEmpty()) {
auto path = paths.takeFirst();
if (FileSystem::fileIsDirectory(path, FileSystem::ShouldFollowSymbolicLinks::No)) {
auto newPaths = FileSystem::listDirectory(path, "*"_s);
for (auto& newPath : newPaths) {
// Files in /Blobs directory are hard link.
auto fileName = FileSystem::lastComponentOfPathIgnoringTrailingSlash(newPath);
if (fileName == "Blobs")
continue;
paths.append(newPath);
}
continue;
}
long long fileSize = 0;
FileSystem::getFileSize(path, fileSize);
directorySize += fileSize;
}
return directorySize;
}
uint64_t Engine::diskUsage(const String& rootPath, const WebCore::ClientOrigin& origin)
{
ASSERT(!isMainThread());
if (rootPath.isEmpty())
return 0;
String saltPath = FileSystem::pathByAppendingComponent(rootPath, "salt"_s);
auto salt = readOrMakeSalt(saltPath);
if (!salt)
return 0;
Key key(origin.topOrigin.toString(), origin.clientOrigin.toString(), { }, { }, *salt);
String directoryPath = FileSystem::pathByAppendingComponent(rootPath, key.hashAsString());
String sizeFilePath = Caches::cachesSizeFilename(directoryPath);
if (auto recordedSize = readSizeFile(sizeFilePath))
return *recordedSize;
return getDirectorySize(directoryPath);
}
void Engine::requestSpace(const ClientOrigin& origin, uint64_t spaceRequested, CompletionHandler<void(WebCore::StorageQuotaManager::Decision)>&& callback)
{
ASSERT(isMainThread());
if (!m_networkProcess)
callback(WebCore::StorageQuotaManager::Decision::Deny);
RefPtr<WebCore::StorageQuotaManager> storageQuotaManager = m_networkProcess->storageQuotaManager(m_sessionID, origin);
if (!storageQuotaManager)
callback(WebCore::StorageQuotaManager::Decision::Deny);
storageQuotaManager->requestSpaceOnMainThread(spaceRequested, WTFMove(callback));
}
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;
}
cachesOrError.value().get().open(cacheName, WTFMove(callback));
});
}
void Engine::remove(uint64_t cacheIdentifier, CacheIdentifierCallback&& callback)
{
Caches* cachesToModify = nullptr;
for (auto& caches : m_caches.values()) {
auto* cacheToRemove = caches->find(cacheIdentifier);
if (cacheToRemove) {
cachesToModify = caches.get();
break;
}
}
if (!cachesToModify) {
callback(makeUnexpected(Error::Internal));
return;
}
cachesToModify->remove(cacheIdentifier, WTFMove(callback));
}
void Engine::retrieveCaches(const WebCore::ClientOrigin& origin, uint64_t updateCounter, CacheInfosCallback&& callback)
{
readCachesFromDisk(origin, [updateCounter, callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable {
if (!cachesOrError.has_value()) {
callback(makeUnexpected(cachesOrError.error()));
return;
}
cachesOrError.value().get().cacheInfos(updateCounter, WTFMove(callback));
});
}
void Engine::retrieveRecords(uint64_t cacheIdentifier, URL&& url, RecordsCallback&& callback)
{
readCache(cacheIdentifier, [url = WTFMove(url), callback = WTFMove(callback)](CacheOrError&& result) mutable {
if (!result.has_value()) {
callback(makeUnexpected(result.error()));
return;
}
result.value().get().retrieveRecords(url, WTFMove(callback));
});
}
void Engine::putRecords(uint64_t cacheIdentifier, Vector<Record>&& records, RecordIdentifiersCallback&& callback)
{
readCache(cacheIdentifier, [records = WTFMove(records), callback = WTFMove(callback)](CacheOrError&& result) mutable {
if (!result.has_value()) {
callback(makeUnexpected(result.error()));
return;
}
result.value().get().put(WTFMove(records), WTFMove(callback));
});
}
void Engine::deleteMatchingRecords(uint64_t cacheIdentifier, WebCore::ResourceRequest&& request, WebCore::CacheQueryOptions&& options, RecordIdentifiersCallback&& callback)
{
readCache(cacheIdentifier, [request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)](CacheOrError&& result) mutable {
if (!result.has_value()) {
callback(makeUnexpected(result.error()));
return;
}
result.value().get().remove(WTFMove(request), WTFMove(options), WTFMove(callback));
});
}
void Engine::initialize(CompletionCallback&& callback)
{
if (m_clearTaskCounter || !m_pendingClearCallbacks.isEmpty()) {
m_pendingClearCallbacks.append(WTFMove(callback));
return;
}
if (m_salt) {
callback(WTF::nullopt);
return;
}
if (!shouldPersist()) {
m_salt = NetworkCache::Salt { };
callback(WTF::nullopt);
return;
}
bool shouldComputeSalt = m_initializationCallbacks.isEmpty();
m_initializationCallbacks.append(WTFMove(callback));
if (!shouldComputeSalt)
return;
m_ioQueue->dispatch([this, weakThis = makeWeakPtr(this), rootPath = m_rootPath.isolatedCopy()] () mutable {
FileSystem::makeAllDirectories(rootPath);
String saltPath = FileSystem::pathByAppendingComponent(rootPath, "salt"_s);
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;
}
auto& caches = m_caches.ensure(origin, [&origin, this] {
auto path = cachesRootPath(origin);
return Caches::create(*this, WebCore::ClientOrigin { origin }, WTFMove(path));
}).iterator->value;
if (caches->isInitialized()) {
callback(std::reference_wrapper<Caches> { *caches });
return;
}
caches->initialize([callback = WTFMove(callback), caches = caches.copyRef()](Optional<Error>&& error) mutable {
if (error) {
callback(makeUnexpected(error.value()));
return;
}
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;
}
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 });
}
Cache* Engine::cache(uint64_t cacheIdentifier)
{
Cache* result = nullptr;
for (auto& caches : m_caches.values()) {
if ((result = caches->find(cacheIdentifier)))
break;
}
return result;
}
void Engine::writeFile(const String& filename, NetworkCache::Data&& data, WebCore::DOMCacheEngine::CompletionCallback&& callback)
{
if (!shouldPersist()) {
callback(WTF::nullopt);
return;
}
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);
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::readFile(const String& filename, CompletionHandler<void(const NetworkCache::Data&, int error)>&& callback)
{
if (!shouldPersist()) {
callback(Data { }, 0);
return;
}
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->isOpened()) {
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::removeFile(const String& filename)
{
if (!shouldPersist())
return;
m_ioQueue->dispatch([filename = filename.isolatedCopy()]() mutable {
FileSystem::deleteFile(filename);
});
}
void Engine::writeSizeFile(const String& path, uint64_t size, CompletionHandler<void()>&& completionHandler)
{
CompletionHandlerCallingScope completionHandlerCaller(WTFMove(completionHandler));
if (!shouldPersist())
return;
m_ioQueue->dispatch([path = path.isolatedCopy(), size, completionHandlerCaller = WTFMove(completionHandlerCaller)]() mutable {
LockHolder locker(globalSizeFileLock);
auto fileHandle = FileSystem::openFile(path, FileSystem::FileOpenMode::Write);
auto closeFileHandler = makeScopeExit([&] {
FileSystem::closeFile(fileHandle);
});
if (!FileSystem::isHandleValid(fileHandle))
return;
FileSystem::truncateFile(fileHandle, 0);
FileSystem::writeToFile(fileHandle, String::number(size).utf8().data(), String::number(size).utf8().length());
RunLoop::main().dispatch([completionHandlerCaller = WTFMove(completionHandlerCaller)]() mutable { });
});
}
Optional<uint64_t> Engine::readSizeFile(const String& path)
{
ASSERT(!RunLoop::isMain());
LockHolder locker(globalSizeFileLock);
auto fileHandle = FileSystem::openFile(path, FileSystem::FileOpenMode::Read);
auto closeFileHandle = makeScopeExit([&] {
FileSystem::closeFile(fileHandle);
});
if (!FileSystem::isHandleValid(fileHandle))
return WTF::nullopt;
long long fileSize = 0;
if (!FileSystem::getFileSize(path, fileSize) || !fileSize)
return WTF::nullopt;
size_t bytesToRead;
if (!WTF::convertSafely(fileSize, bytesToRead))
return WTF::nullopt;
Vector<unsigned char> buffer(bytesToRead);
size_t totalBytesRead = FileSystem::readFromFile(fileHandle, reinterpret_cast<char*>(buffer.data()), buffer.size());
if (totalBytesRead != bytesToRead)
return WTF::nullopt;
return charactersToUIntStrict(buffer.data(), totalBytesRead);
}
class ReadOriginsTaskCounter : public RefCounted<ReadOriginsTaskCounter> {
public:
static Ref<ReadOriginsTaskCounter> create(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(CompletionHandler<void(Vector<WebsiteData::Entry>)>&& callback)
: m_callback(WTFMove(callback))
{
}
CompletionHandler<void(Vector<WebsiteData::Entry>)> m_callback;
Vector<WebsiteData::Entry> m_entries;
};
void Engine::getDirectories(CompletionHandler<void(const Vector<String>&)>&& completionHandler)
{
m_ioQueue->dispatch([path = m_rootPath.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
Vector<String> folderPaths;
for (auto& filename : FileSystem::listDirectory(path, "*")) {
if (FileSystem::fileIsDirectory(filename, FileSystem::ShouldFollowSymbolicLinks::No))
folderPaths.append(filename.isolatedCopy());
}
RunLoop::main().dispatch([folderPaths = WTFMove(folderPaths), completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(folderPaths);
});
});
}
void Engine::fetchEntries(bool shouldComputeSize, 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;
}
getDirectories([this, weakThis = makeWeakPtr(this), path = m_rootPath.isolatedCopy(), shouldComputeSize, completionHandler = WTFMove(completionHandler)](const auto& folderPaths) mutable {
if (!weakThis)
return completionHandler({ });
fetchDirectoryEntries(shouldComputeSize, folderPaths, WTFMove(completionHandler));
});
}
void Engine::fetchDirectoryEntries(bool shouldComputeSize, const Vector<String>& folderPaths, CompletionHandler<void(Vector<WebsiteData::Entry>)>&& completionHandler)
{
auto taskCounter = ReadOriginsTaskCounter::create(WTFMove(completionHandler));
for (auto& folderPath : folderPaths) {
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());
});
});
}
}
CompletionHandler<void()> Engine::createClearTask(CompletionHandler<void()>&& completionHandler)
{
++m_clearTaskCounter;
return [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler();
if (!--m_clearTaskCounter) {
auto callbacks = WTFMove(m_pendingClearCallbacks);
for (auto& callback : callbacks)
initialize(WTFMove(callback));
}
};
}
void Engine::clearAllCaches(CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
auto callbackAggregator = CallbackAggregator::create([this, completionHandler = createClearTask(WTFMove(completionHandler))]() mutable {
if (!this->shouldPersist())
return completionHandler();
this->clearAllCachesFromDisk(WTFMove(completionHandler));
});
for (auto& caches : m_caches.values())
caches->clear([callbackAggregator = callbackAggregator.copyRef()] { });
}
void Engine::clearAllCachesFromDisk(CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
m_ioQueue->dispatch([path = m_rootPath.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
LockHolder locker(globalSizeFileLock);
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([this, origin, completionHandler = createClearTask(WTFMove(completionHandler))]() mutable {
if (!this->shouldPersist())
return completionHandler();
this->clearCachesForOriginFromDisk(origin, [completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler();
});
});
for (auto& keyValue : m_caches) {
if (keyValue.key.topOrigin == origin || keyValue.key.clientOrigin == origin)
keyValue.value->clear([callbackAggregator = callbackAggregator.copyRef()] { });
}
}
void Engine::clearCachesForOriginFromDisk(const WebCore::SecurityOriginData& origin, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
getDirectories([this, weakThis = makeWeakPtr(this), origin, completionHandler = WTFMove(completionHandler)](const auto& folderPaths) mutable {
if (!weakThis)
return completionHandler();
clearCachesForOriginFromDirectories(folderPaths, origin, WTFMove(completionHandler));
});
}
void Engine::clearCachesForOriginFromDirectories(const Vector<String>& folderPaths, const WebCore::SecurityOriginData& origin, CompletionHandler<void()>&& completionHandler)
{
auto callbackAggregator = CallbackAggregator::create(WTFMove(completionHandler));
for (auto& folderPath : folderPaths) {
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)] { });
});
}
}
void Engine::deleteDirectoryRecursivelyOnBackgroundThread(const String& path, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
m_ioQueue->dispatch([path = path.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
LockHolder locker(globalSizeFileLock);
deleteDirectoryRecursively(path);
RunLoop::main().dispatch(WTFMove(completionHandler));
});
}
void Engine::clearMemoryRepresentation(const WebCore::ClientOrigin& origin, WebCore::DOMCacheEngine::CompletionCallback&& callback)
{
readCachesFromDisk(origin, [callback = WTFMove(callback)](CachesOrError&& result) mutable {
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("}");
}
builder.append("]}");
return builder.toString();
}
} // namespace CacheStorage
} // namespace WebKit