/* * Copyright (c) 2026, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include namespace WebView { static constexpr size_t DEFAULT_HISTORY_PAGE_SIZE = 50; static constexpr size_t MAX_HISTORY_PAGE_SIZE = 100; static Optional site_key_for_entry(HistoryEntry const& entry) { auto parsed_url = URL::Parser::basic_parse(entry.url); if (!parsed_url.has_value()) return {}; if (!parsed_url->host().has_value() || parsed_url->host()->is_empty_host()) return {}; if (auto registrable_domain = parsed_url->host()->registrable_domain(); registrable_domain.has_value()) return registrable_domain.release_value(); return parsed_url->serialized_host(); } static JsonObject serialize_history_entry(HistoryEntry const& entry) { JsonObject serialized; serialized.set("url"sv, entry.url); serialized.set("title"sv, entry.title.value_or(String {})); serialized.set("faviconBase64Png"sv, entry.favicon_base64_png.value_or(String {})); serialized.set("visitCount"sv, entry.visit_count); serialized.set("lastVisitedTime"sv, entry.last_visited_time.milliseconds_since_epoch()); serialized.set("siteKey"sv, site_key_for_entry(entry).value_or(String {})); return serialized; } void HistoryUI::register_interfaces() { register_interface("loadHistoryEntries"sv, [this](auto const& data) { load_history_entries(data); }); register_interface("removeHistoryEntry"sv, [this](auto const& data) { remove_history_entry(data); }); register_interface("forgetHistorySite"sv, [this](auto const& data) { forget_history_site(data); }); } void HistoryUI::load_history_entries(JsonValue const& data) { if (!data.is_object()) return; auto const& object = data.as_object(); auto offset = object.get_integer("offset"sv).value_or(0); auto limit = object.get_integer("limit"sv); auto request_id = object.get_integer("requestId"sv).value_or(0); auto query = object.get_string("query"sv).value_or(String {}); if (limit.has_value()) limit = min(*limit, MAX_HISTORY_PAGE_SIZE); else limit = DEFAULT_HISTORY_PAGE_SIZE; auto entries = Application::history_store().list_entries(query, offset, *limit + 1); auto has_more = entries.size() > *limit; if (has_more) entries.resize(*limit); JsonArray serialized_entries; serialized_entries.ensure_capacity(entries.size()); for (auto const& entry : entries) serialized_entries.must_append(serialize_history_entry(entry)); JsonObject result; result.set("requestId"sv, request_id); result.set("query"sv, query); result.set("offset"sv, offset); result.set("hasMore"sv, has_more); result.set("entries"sv, move(serialized_entries)); async_send_message("loadHistoryEntries"sv, move(result)); } void HistoryUI::remove_history_entry(JsonValue const& data) { if (!data.is_object()) return; auto url = data.as_object().get_string("url"sv); if (!url.has_value()) return; auto parsed_url = URL::Parser::basic_parse(*url); if (!parsed_url.has_value()) return; Application::history_store().remove_entry_for_url(*parsed_url); } void HistoryUI::forget_history_site(JsonValue const& data) { if (!data.is_object()) return; auto url = data.as_object().get_string("url"sv); if (!url.has_value()) return; auto parsed_url = URL::Parser::basic_parse(*url); if (!parsed_url.has_value()) return; Application::history_store().remove_entries_for_same_site(*parsed_url); } }