LibWebView: Let Settings clear browsing history

Route the existing Clear Browsing Data dialog through HistoryStore's
time-range deletion path as well. That makes the Settings action
remove visited pages from persisted history and from history-backed
address bar suggestions instead of only touching cache and site data.

Add a history checkbox to the dialog, thread its state through the
Settings WebUI message, and cover remove_entries_accessed_since() for
both transient and persisted stores in TestHistoryStore.
This commit is contained in:
Andreas Kling 2026-04-12 01:16:07 +02:00 committed by Andreas Kling
parent 1802a05bc2
commit c249f0324b
6 changed files with 71 additions and 2 deletions

View file

@ -609,6 +609,13 @@
<p class="description">Remove items that help pages load faster</p>
</label>
</div>
<div class="input-field-container">
<input id="clear-browsing-data-history" type="checkbox" value="" checked />
<label for="clear-browsing-data-history">
Browsing history
<p class="description">Remove visited pages from history and address bar suggestions</p>
</label>
</div>
<div class="input-field-container">
<input id="clear-browsing-data-site-data" type="checkbox" value="" checked />
<label for="clear-browsing-data-site-data">

View file

@ -16,6 +16,7 @@ const browsingDataTotalSize = document.querySelector("#browsing-data-total-size"
const clearBrowsingDataCachedFiles = document.querySelector("#clear-browsing-data-cached-files");
const clearBrowsingDataCachedFilesSize = document.querySelector("#clear-browsing-data-cached-files-size");
const clearBrowsingDataHistory = document.querySelector("#clear-browsing-data-history");
const clearBrowsingDataRemoveData = document.querySelector("#clear-browsing-data-remove-data");
const clearBrowsingDataSiteData = document.querySelector("#clear-browsing-data-site-data");
const clearBrowsingDataSiteDataSize = document.querySelector("#clear-browsing-data-site-data-size");
@ -135,10 +136,14 @@ clearBrowsingDataTimeRange.addEventListener("change", () => {
});
function setRemoveDataEnabledState() {
clearBrowsingDataRemoveData.disabled = !clearBrowsingDataCachedFiles.checked && !clearBrowsingDataSiteData.checked;
clearBrowsingDataRemoveData.disabled =
!clearBrowsingDataCachedFiles.checked &&
!clearBrowsingDataHistory.checked &&
!clearBrowsingDataSiteData.checked;
}
clearBrowsingDataCachedFiles.addEventListener("change", setRemoveDataEnabledState);
clearBrowsingDataHistory.addEventListener("change", setRemoveDataEnabledState);
clearBrowsingDataSiteData.addEventListener("change", setRemoveDataEnabledState);
clearBrowsingDataRemoveData.addEventListener("click", () => {
@ -147,6 +152,7 @@ clearBrowsingDataRemoveData.addEventListener("click", () => {
ladybird.sendMessage("clearBrowsingData", {
since: since?.epochMilliseconds,
cachedFiles: clearBrowsingDataCachedFiles.checked,
history: clearBrowsingDataHistory.checked,
siteData: clearBrowsingDataSiteData.checked,
});

View file

@ -828,6 +828,9 @@ void Application::clear_browsing_data(ClearBrowsingDataOptions const& options)
});
}
if (options.delete_history == ClearBrowsingDataOptions::Delete::Yes)
m_history_store->remove_entries_accessed_since(options.since);
if (options.delete_site_data == ClearBrowsingDataOptions::Delete::Yes) {
m_cookie_jar->expire_cookies_accessed_since(options.since);
m_storage_jar->remove_items_accessed_since(options.since);

View file

@ -124,6 +124,7 @@ public:
UnixDateTime since { UnixDateTime::earliest() };
Delete delete_cached_files { Delete::No };
Delete delete_history { Delete::No };
Delete delete_site_data { Delete::No };
};
void clear_browsing_data(ClearBrowsingDataOptions const&);

View file

@ -336,6 +336,10 @@ void SettingsUI::clear_browsing_data(JsonValue const& options)
? Application::ClearBrowsingDataOptions::Delete::Yes
: Application::ClearBrowsingDataOptions::Delete::No;
clear_browsing_data_options.delete_history = options.as_object().get_bool("history"sv).value_or(false)
? Application::ClearBrowsingDataOptions::Delete::Yes
: Application::ClearBrowsingDataOptions::Delete::No;
clear_browsing_data_options.delete_site_data = options.as_object().get_bool("siteData"sv).value_or(false)
? Application::ClearBrowsingDataOptions::Delete::Yes
: Application::ClearBrowsingDataOptions::Delete::No;

View file

@ -205,7 +205,23 @@ TEST_CASE(disabled_history_store_ignores_updates)
store->clear();
EXPECT(!store->entry_for_url(url).has_value());
EXPECT(store->autocomplete_suggestions("example"sv, 8).is_empty());
EXPECT(store->autocomplete_entries("example"sv, 8).is_empty());
}
TEST_CASE(history_entries_accessed_since_can_be_removed)
{
auto store = WebView::HistoryStore::create();
auto older_url = parse_url("https://older.example.com/"sv);
auto newer_url = parse_url("https://newer.example.com/"sv);
store->record_visit(older_url, "Older"_string, UnixDateTime::from_seconds_since_epoch(10));
store->record_visit(newer_url, "Newer"_string, UnixDateTime::from_seconds_since_epoch(20));
store->remove_entries_accessed_since(UnixDateTime::from_seconds_since_epoch(15));
EXPECT(store->entry_for_url(older_url).has_value());
EXPECT(!store->entry_for_url(newer_url).has_value());
}
TEST_CASE(persisted_history_survives_reopen)
@ -241,6 +257,38 @@ TEST_CASE(persisted_history_survives_reopen)
}
}
TEST_CASE(persisted_history_entries_accessed_since_can_be_removed)
{
auto database_directory = ByteString::formatted(
"{}/ladybird-history-store-remove-since-test-{}",
Core::StandardPaths::tempfile_directory(),
generate_random_uuid());
TRY_OR_FAIL(Core::Directory::create(database_directory, Core::Directory::CreateDirectories::Yes));
auto cleanup = ScopeGuard([&] {
MUST(FileSystem::remove(database_directory, FileSystem::RecursionMode::Allowed));
});
auto older_url = parse_url("https://older.example.com/"sv);
auto newer_url = parse_url("https://newer.example.com/"sv);
{
auto database = TRY_OR_FAIL(Database::Database::create(database_directory, "HistoryStore"sv));
auto store = TRY_OR_FAIL(WebView::HistoryStore::create(*database));
store->record_visit(older_url, "Older"_string, UnixDateTime::from_seconds_since_epoch(10));
store->record_visit(newer_url, "Newer"_string, UnixDateTime::from_seconds_since_epoch(20));
store->remove_entries_accessed_since(UnixDateTime::from_seconds_since_epoch(15));
}
{
auto database = TRY_OR_FAIL(Database::Database::create(database_directory, "HistoryStore"sv));
auto store = TRY_OR_FAIL(WebView::HistoryStore::create(*database));
EXPECT(store->entry_for_url(older_url).has_value());
EXPECT(!store->entry_for_url(newer_url).has_value());
}
}
TEST_CASE(persisted_history_autocomplete_ignores_scheme_and_www_boilerplate_prefixes)
{
auto database_directory = ByteString::formatted(