LibWebView+RequestServer: Support clearing the HTTP disk cache

This is a bit of a blunt hammer, but this hooks an action to clear the
HTTP disk cache into the existing Clear Cache action. Upon invocation,
it stops all existing cache entries from making further progress, and
then deletes the entire cache index and all cache files.

In the future, we will of course want more fine-grained control over
cache deletion, e.g. via an about:history page.
This commit is contained in:
Timothy Flynn 2025-10-09 14:24:47 -04:00 committed by Andreas Kling
parent 42eaea1043
commit 163e8e5b44
Notes: github-actions[bot] 2025-10-14 11:41:38 +00:00
10 changed files with 84 additions and 10 deletions

View file

@ -824,7 +824,10 @@ void Application::initialize_actions()
m_debug_menu->add_separator();
m_debug_menu->add_action(Action::create("Collect Garbage"sv, ActionID::CollectGarbage, debug_request("collect-garbage"sv)));
m_debug_menu->add_action(Action::create("Clear Cache"sv, ActionID::ClearCache, debug_request("clear-cache"sv)));
m_debug_menu->add_action(Action::create("Clear Cache"sv, ActionID::ClearCache, [this, clear_memory_cache = debug_request("clear_cache")]() {
m_request_server_client->async_clear_cache();
clear_memory_cache();
}));
m_debug_menu->add_action(Action::create("Clear All Cookies"sv, ActionID::ClearCookies, [this]() { m_cookie_jar->clear_all_cookies(); }));
m_debug_menu->add_separator();

View file

@ -153,6 +153,11 @@ CacheEntryWriter::CacheEntryWriter(DiskCache& disk_cache, CacheIndex& index, u64
ErrorOr<void> CacheEntryWriter::write_data(ReadonlyBytes data)
{
if (m_marked_for_deletion) {
close_and_destory_cache_entry();
return Error::from_string_literal("Cache entry has been deleted");
}
if (auto result = m_file->write_until_depleted(data); result.is_error()) {
dbgln("\033[31;1mUnable to write to cache entry for{}\033[0m {}: {}", m_url, result.error());
@ -174,6 +179,9 @@ ErrorOr<void> CacheEntryWriter::flush()
{
ScopeGuard guard { [&]() { close_and_destory_cache_entry(); } };
if (m_marked_for_deletion)
return Error::from_string_literal("Cache entry has been deleted");
if (auto result = m_file->write_value(m_cache_footer); result.is_error()) {
dbgln("\033[31;1mUnable to flush cache entry for{}\033[0m {}: {}", m_url, result.error());
remove();
@ -272,6 +280,11 @@ void CacheEntryReader::pipe_to(int pipe_fd, Function<void(u64)> on_complete, Fun
m_on_pipe_complete = move(on_complete);
m_on_pipe_error = move(on_error);
if (m_marked_for_deletion) {
pipe_error(Error::from_string_literal("Cache entry has been deleted"));
return;
}
m_pipe_write_notifier = Core::Notifier::construct(m_pipe_fd, Core::NotificationType::Write);
m_pipe_write_notifier->set_enabled(false);
@ -285,19 +298,18 @@ void CacheEntryReader::pipe_to(int pipe_fd, Function<void(u64)> on_complete, Fun
void CacheEntryReader::pipe_without_blocking()
{
if (m_marked_for_deletion) {
pipe_error(Error::from_string_literal("Cache entry has been deleted"));
return;
}
auto result = Core::System::transfer_file_through_pipe(m_fd, m_pipe_fd, m_data_offset + m_bytes_piped, m_data_size - m_bytes_piped);
if (result.is_error()) {
if (result.error().code() != EAGAIN && result.error().code() != EWOULDBLOCK) {
dbgln("\033[31;1mError transferring cache to pipe for\033[0m {}: {}", m_url, result.error());
if (m_on_pipe_error)
m_on_pipe_error(m_bytes_piped);
close_and_destory_cache_entry();
} else {
if (result.error().code() != EAGAIN && result.error().code() != EWOULDBLOCK)
pipe_error(result.release_error());
else
m_pipe_write_notifier->set_enabled(true);
}
return;
}
@ -330,6 +342,16 @@ void CacheEntryReader::pipe_complete()
close_and_destory_cache_entry();
}
void CacheEntryReader::pipe_error(Error error)
{
dbgln("\033[31;1mError transferring cache to pipe for\033[0m {}: {}", m_url, error);
if (m_on_pipe_error)
m_on_pipe_error(m_bytes_piped);
close_and_destory_cache_entry();
}
ErrorOr<void> CacheEntryReader::read_and_validate_footer()
{
TRY(m_file->seek(m_data_offset + m_data_size, SeekMode::SetPosition));

View file

@ -57,6 +57,8 @@ public:
void remove();
void mark_for_deletion(Badge<DiskCache>) { m_marked_for_deletion = true; }
protected:
CacheEntry(DiskCache&, CacheIndex&, u64 cache_key, String url, LexicalPath, CacheHeader);
@ -72,6 +74,8 @@ protected:
CacheHeader m_cache_header;
CacheFooter m_cache_footer;
bool m_marked_for_deletion { false };
};
class CacheEntryWriter : public CacheEntry {
@ -107,6 +111,7 @@ private:
void pipe_without_blocking();
void pipe_complete();
void pipe_error(Error);
ErrorOr<void> read_and_validate_footer();

View file

@ -25,6 +25,7 @@ ErrorOr<CacheIndex> CacheIndex::create(Database::Database& database)
Statements statements {};
statements.insert_entry = TRY(database.prepare_statement("INSERT OR REPLACE INTO CacheIndex VALUES (?, ?, ?, ?, ?, ?);"sv));
statements.remove_entry = TRY(database.prepare_statement("DELETE FROM CacheIndex WHERE cache_key = ?;"sv));
statements.remove_all_entries = TRY(database.prepare_statement("DELETE FROM CacheIndex;"sv));
statements.select_entry = TRY(database.prepare_statement("SELECT * FROM CacheIndex WHERE cache_key = ?;"sv));
statements.update_last_access_time = TRY(database.prepare_statement("UPDATE CacheIndex SET last_access_time = ? WHERE cache_key = ?;"sv));
@ -60,6 +61,12 @@ void CacheIndex::remove_entry(u64 cache_key)
m_entries.remove(cache_key);
}
void CacheIndex::remove_all_entries()
{
m_database.execute_statement(m_statements.remove_all_entries, {});
m_entries.clear();
}
void CacheIndex::update_last_access_time(u64 cache_key)
{
auto entry = m_entries.get(cache_key);

View file

@ -33,6 +33,7 @@ public:
void create_entry(u64 cache_key, String url, u64 data_size, UnixDateTime request_time, UnixDateTime response_time);
void remove_entry(u64 cache_key);
void remove_all_entries();
Optional<Entry&> find_entry(u64 cache_key);
@ -42,6 +43,7 @@ private:
struct Statements {
Database::StatementID insert_entry { 0 };
Database::StatementID remove_entry { 0 };
Database::StatementID remove_all_entries { 0 };
Database::StatementID select_entry { 0 };
Database::StatementID update_last_access_time { 0 };
};

View file

@ -4,7 +4,9 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/DirIterator.h>
#include <LibCore/StandardPaths.h>
#include <LibFileSystem/FileSystem.h>
#include <LibURL/URL.h>
#include <RequestServer/Cache/DiskCache.h>
#include <RequestServer/Cache/Utilities.h>
@ -90,6 +92,28 @@ Optional<CacheEntryReader&> DiskCache::open_entry(URL::URL const& url, StringVie
return static_cast<CacheEntryReader&>(**m_open_cache_entries.get(address));
}
void DiskCache::clear_cache()
{
for (auto& [_, cache_entry] : m_open_cache_entries)
cache_entry->mark_for_deletion({});
m_index.remove_all_entries();
Core::DirIterator it { m_cache_directory.string(), Core::DirIterator::SkipDots };
size_t cache_entries { 0 };
while (it.has_next()) {
auto entry = it.next_full_path();
if (LexicalPath { entry }.title() == INDEX_DATABASE)
continue;
(void)FileSystem::remove(entry, FileSystem::RecursionMode::Disallowed);
++cache_entries;
}
dbgln("Cleared {} disk cache entries", cache_entries);
}
void DiskCache::cache_entry_closed(Badge<CacheEntry>, CacheEntry const& cache_entry)
{
auto address = reinterpret_cast<FlatPtr>(&cache_entry);

View file

@ -26,6 +26,7 @@ public:
Optional<CacheEntryWriter&> create_entry(URL::URL const&, StringView method, u32 status_code, Optional<String> reason_phrase, HTTP::HeaderMap const&, UnixDateTime request_time);
Optional<CacheEntryReader&> open_entry(URL::URL const&, StringView method);
void clear_cache();
LexicalPath const& cache_directory() { return m_cache_directory; }

View file

@ -861,6 +861,12 @@ void ConnectionFromClient::ensure_connection(URL::URL url, ::RequestServer::Cach
}
}
void ConnectionFromClient::clear_cache()
{
if (g_disk_cache.has_value())
g_disk_cache->clear_cache();
}
void ConnectionFromClient::websocket_connect(i64 websocket_id, URL::URL url, ByteString origin, Vector<ByteString> protocols, Vector<ByteString> extensions, HTTP::HeaderMap additional_request_headers)
{
auto host = url.serialized_host().to_byte_string();

View file

@ -49,6 +49,8 @@ private:
virtual Messages::RequestServer::SetCertificateResponse set_certificate(i32, ByteString, ByteString) override;
virtual void ensure_connection(URL::URL url, ::RequestServer::CacheLevel cache_level) override;
virtual void clear_cache() override;
virtual void websocket_connect(i64 websocket_id, URL::URL, ByteString, Vector<ByteString>, Vector<ByteString>, HTTP::HeaderMap) override;
virtual void websocket_send(i64 websocket_id, bool, ByteBuffer) override;
virtual void websocket_close(i64 websocket_id, u16, ByteString) override;

View file

@ -22,6 +22,8 @@ endpoint RequestServer
ensure_connection(URL::URL url, ::RequestServer::CacheLevel cache_level) =|
clear_cache() =|
// Websocket Connection API
websocket_connect(i64 websocket_id, URL::URL url, ByteString origin, Vector<ByteString> protocols, Vector<ByteString> extensions, HTTP::HeaderMap additional_request_headers) =|
websocket_send(i64 websocket_id, bool is_text, ByteBuffer data) =|