From 398ae75f9a35dc9022dc44992d9955178093e07b Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 30 Apr 2024 20:05:56 -0400 Subject: [PATCH] Ladybird+LibWebView: Introduce a cache for cookies backed by SQL storage Now that the chrome process is a singleton on all platforms, we can safely add a cache to the CookieJar to greatly speed up access. The way this works is we read all cookies upfront from the database. As cookies are updated by the web, we store a list of "dirty" cookies that need to be flushed to the database. We do that synchronization every 30 seconds and at shutdown. There's plenty of room for improvement here, some of which is marked with FIXMEs in the CookieJar. Before these changes, in a SQL database populated with 300 cookies, browsing to https://twinings.co.uk/ WebContent spent: 19,806ms waiting for a get-cookie response 505ms waiting for a set-cookie response With these changes, it spends: 24ms waiting for a get-cookie response 15ms waiting for a set-cookie response --- .../AppKit/Application/ApplicationDelegate.h | 2 +- .../AppKit/Application/ApplicationDelegate.mm | 4 +- Ladybird/Qt/main.cpp | 4 +- Userland/Applications/Browser/main.cpp | 2 +- Userland/Libraries/LibWebView/CookieJar.cpp | 393 ++++++++---------- Userland/Libraries/LibWebView/CookieJar.h | 70 ++-- Userland/Utilities/headless-browser.cpp | 8 +- 7 files changed, 228 insertions(+), 255 deletions(-) diff --git a/Ladybird/AppKit/Application/ApplicationDelegate.h b/Ladybird/AppKit/Application/ApplicationDelegate.h index e1e349c848..6f127790ee 100644 --- a/Ladybird/AppKit/Application/ApplicationDelegate.h +++ b/Ladybird/AppKit/Application/ApplicationDelegate.h @@ -24,7 +24,7 @@ - (nullable instancetype)init:(Vector)initial_urls newTabPageURL:(URL::URL)new_tab_page_url - withCookieJar:(WebView::CookieJar)cookie_jar + withCookieJar:(NonnullOwnPtr)cookie_jar webContentOptions:(Ladybird::WebContentOptions const&)web_content_options webdriverContentIPCPath:(StringView)webdriver_content_ipc_path; diff --git a/Ladybird/AppKit/Application/ApplicationDelegate.mm b/Ladybird/AppKit/Application/ApplicationDelegate.mm index 59fd1ffd01..af8aae37dd 100644 --- a/Ladybird/AppKit/Application/ApplicationDelegate.mm +++ b/Ladybird/AppKit/Application/ApplicationDelegate.mm @@ -24,7 +24,7 @@ URL::URL m_new_tab_page_url; // This will always be populated, but we cannot have a non-default constructible instance variable. - Optional m_cookie_jar; + OwnPtr m_cookie_jar; Ladybird::WebContentOptions m_web_content_options; Optional m_webdriver_content_ipc_path; @@ -56,7 +56,7 @@ - (instancetype)init:(Vector)initial_urls newTabPageURL:(URL::URL)new_tab_page_url - withCookieJar:(WebView::CookieJar)cookie_jar + withCookieJar:(NonnullOwnPtr)cookie_jar webContentOptions:(Ladybird::WebContentOptions const&)web_content_options webdriverContentIPCPath:(StringView)webdriver_content_ipc_path { diff --git a/Ladybird/Qt/main.cpp b/Ladybird/Qt/main.cpp index cb26385baa..4ebe86c0c4 100644 --- a/Ladybird/Qt/main.cpp +++ b/Ladybird/Qt/main.cpp @@ -185,10 +185,10 @@ ErrorOr serenity_main(Main::Arguments arguments) }; chrome_process.on_new_window = [&](auto const& urls) { - app.new_window(sanitize_urls(urls), cookie_jar, web_content_options, webdriver_content_ipc_path); + app.new_window(sanitize_urls(urls), *cookie_jar, web_content_options, webdriver_content_ipc_path); }; - auto& window = app.new_window(sanitize_urls(raw_urls), cookie_jar, web_content_options, webdriver_content_ipc_path); + auto& window = app.new_window(sanitize_urls(raw_urls), *cookie_jar, web_content_options, webdriver_content_ipc_path); window.setWindowTitle("Ladybird"); if (Ladybird::Settings::the()->is_maximized()) { diff --git a/Userland/Applications/Browser/main.cpp b/Userland/Applications/Browser/main.cpp index 35264c2e8b..be028c8a41 100644 --- a/Userland/Applications/Browser/main.cpp +++ b/Userland/Applications/Browser/main.cpp @@ -217,7 +217,7 @@ ErrorOr serenity_main(Main::Arguments arguments) } auto cookie_jar = TRY(WebView::CookieJar::create(*database)); - auto window = Browser::BrowserWindow::construct(cookie_jar, sanitize_urls(specified_urls), man_file); + auto window = Browser::BrowserWindow::construct(*cookie_jar, sanitize_urls(specified_urls), man_file); chrome_process.on_new_tab = [&](auto const& raw_urls) { open_urls_from_client(*window, raw_urls, NewWindow::No); diff --git a/Userland/Libraries/LibWebView/CookieJar.cpp b/Userland/Libraries/LibWebView/CookieJar.cpp index a463ce0b4b..5192d6d1b6 100644 --- a/Userland/Libraries/LibWebView/CookieJar.cpp +++ b/Userland/Libraries/LibWebView/CookieJar.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -22,7 +21,9 @@ namespace WebView { -ErrorOr CookieJar::create(Database& database) +static constexpr auto DATABASE_SYNCHRONIZATION_TIMER = Duration::from_seconds(30); + +ErrorOr> CookieJar::create(Database& database) { Statements statements {}; @@ -55,38 +56,57 @@ ErrorOr CookieJar::create(Database& database) persistent=? WHERE ((name = ?) AND (domain = ?) AND (path = ?));)#"sv)); - statements.update_cookie_last_access_time = TRY(database.prepare_statement(R"#( - UPDATE Cookies SET last_access_time=? - WHERE ((name = ?) AND (domain = ?) AND (path = ?));)#"sv)); - statements.insert_cookie = TRY(database.prepare_statement("INSERT INTO Cookies VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"sv)); statements.expire_cookie = TRY(database.prepare_statement("DELETE FROM Cookies WHERE (expiry_time < ?);"sv)); - statements.select_cookie = TRY(database.prepare_statement("SELECT * FROM Cookies WHERE ((name = ?) AND (domain = ?) AND (path = ?));"sv)); statements.select_all_cookies = TRY(database.prepare_statement("SELECT * FROM Cookies;"sv)); - return CookieJar { PersistedStorage { database, move(statements) } }; + return adopt_own(*new CookieJar { PersistedStorage { database, statements } }); } -CookieJar CookieJar::create() +NonnullOwnPtr CookieJar::create() { - return CookieJar { TransientStorage {} }; + return adopt_own(*new CookieJar { OptionalNone {} }); } -CookieJar::CookieJar(PersistedStorage storage) - : m_storage(move(storage)) +CookieJar::CookieJar(Optional persisted_storage) + : m_persisted_storage(move(persisted_storage)) { - auto& persisted_storage = m_storage.get(); - persisted_storage.database.execute_statement(persisted_storage.statements.create_table, {}, {}, {}); + if (!m_persisted_storage.has_value()) + return; + + m_persisted_storage->database.execute_statement(m_persisted_storage->statements.create_table, {}, {}, {}); + + // FIXME: Make cookie retrieval lazy so we don't need to retrieve all cookies up front. + auto cookies = m_persisted_storage->select_all_cookies(); + m_transient_storage.set_cookies(move(cookies)); + + m_persisted_storage->synchronization_timer = Core::Timer::create_repeating( + static_cast(DATABASE_SYNCHRONIZATION_TIMER.to_milliseconds()), + [this]() { + auto now = m_transient_storage.purge_expired_cookies(); + m_persisted_storage->database.execute_statement(m_persisted_storage->statements.expire_cookie, {}, {}, {}, now); + + // FIXME: Implement "INSERT OR REPLACE" + for (auto const& it : m_transient_storage.take_inserted_cookies()) + m_persisted_storage->insert_cookie(it.value); + for (auto const& it : m_transient_storage.take_updated_cookies()) + m_persisted_storage->update_cookie(it.value); + }); + m_persisted_storage->synchronization_timer->start(); } -CookieJar::CookieJar(TransientStorage storage) - : m_storage(move(storage)) +CookieJar::~CookieJar() { + if (!m_persisted_storage.has_value()) + return; + + m_persisted_storage->synchronization_timer->stop(); + m_persisted_storage->synchronization_timer->on_timeout(); } String CookieJar::get_cookie(const URL::URL& url, Web::Cookie::Source source) { - purge_expired_cookies(); + m_transient_storage.purge_expired_cookies(); auto domain = canonicalize_domain(url); if (!domain.has_value()) @@ -120,36 +140,31 @@ void CookieJar::set_cookie(const URL::URL& url, Web::Cookie::ParsedCookie const& // however the whole ParsedCookie->Cookie conversion is skipped. void CookieJar::update_cookie(Web::Cookie::Cookie cookie) { - select_cookie_from_database( - move(cookie), + CookieStorageKey key { cookie.name, cookie.domain, cookie.path }; - // 11. If the cookie store contains a cookie with the same name, domain, and path as the newly created cookie: - [this](auto& cookie, auto old_cookie) { - // Update the creation-time of the newly created cookie to match the creation-time of the old-cookie. - cookie.creation_time = old_cookie.creation_time; + // 11. If the cookie store contains a cookie with the same name, domain, and path as the newly created cookie: + if (auto old_cookie = m_transient_storage.get_cookie(key); old_cookie.has_value()) { + // Update the creation-time of the newly created cookie to match the creation-time of the old-cookie. + cookie.creation_time = old_cookie->creation_time; - // Remove the old-cookie from the cookie store. - // NOTE: Rather than deleting then re-inserting this cookie, we update it in-place. - update_cookie_in_database(cookie); - }, + // Remove the old-cookie from the cookie store. + // NOTE: Rather than deleting then re-inserting this cookie, we update it in-place. + } - // 12. Insert the newly created cookie into the cookie store. - [this](auto cookie) { - insert_cookie_into_database(cookie); - }); + // 12. Insert the newly created cookie into the cookie store. + m_transient_storage.set_cookie(move(key), move(cookie)); + + m_transient_storage.purge_expired_cookies(); } void CookieJar::dump_cookies() { - static constexpr auto key_color = "\033[34;1m"sv; - static constexpr auto attribute_color = "\033[33m"sv; - static constexpr auto no_color = "\033[0m"sv; - StringBuilder builder; - size_t total_cookies { 0 }; - select_all_cookies_from_database([&](auto cookie) { - ++total_cookies; + m_transient_storage.for_each_cookie([&](auto const& cookie) { + static constexpr auto key_color = "\033[34;1m"sv; + static constexpr auto attribute_color = "\033[33m"sv; + static constexpr auto no_color = "\033[0m"sv; builder.appendff("{}{}{} - ", key_color, cookie.name, no_color); builder.appendff("{}{}{} - ", key_color, cookie.domain, no_color); @@ -166,15 +181,16 @@ void CookieJar::dump_cookies() builder.appendff("\t{}SameSite{} = {:s}\n", attribute_color, no_color, Web::Cookie::same_site_to_string(cookie.same_site)); }); - dbgln("{} cookies stored\n{}", total_cookies, builder.string_view()); + dbgln("{} cookies stored\n{}", m_transient_storage.size(), builder.string_view()); } Vector CookieJar::get_all_cookies() { Vector cookies; + cookies.ensure_capacity(m_transient_storage.size()); - select_all_cookies_from_database([&](auto cookie) { - cookies.append(move(cookie)); + m_transient_storage.for_each_cookie([&](auto const& cookie) { + cookies.unchecked_append(cookie); }); return cookies; @@ -310,7 +326,7 @@ void CookieJar::store_cookie(Web::Cookie::ParsedCookie const& parsed_cookie, con } else { // Set the cookie's persistent-flag to false. Set the cookie's expiry-time to the latest representable date. cookie.persistent = false; - cookie.expiry_time = UnixDateTime::latest(); + cookie.expiry_time = UnixDateTime::from_unix_time_parts(3000, 1, 1, 0, 0, 0, 0); } // 4. If the cookie-attribute-list contains an attribute with an attribute-name of "Domain": @@ -365,46 +381,37 @@ void CookieJar::store_cookie(Web::Cookie::ParsedCookie const& parsed_cookie, con if (source != Web::Cookie::Source::Http && cookie.http_only) return; - // Synchronize persisting the cookie - auto sync_promise = Core::Promise::construct(); + CookieStorageKey key { cookie.name, cookie.domain, cookie.path }; - select_cookie_from_database( - move(cookie), + // 11. If the cookie store contains a cookie with the same name, domain, and path as the newly created cookie: + if (auto const& old_cookie = m_transient_storage.get_cookie(key); old_cookie.has_value()) { + // If the newly created cookie was received from a "non-HTTP" API and the old-cookie's http-only-flag is set, abort these + // steps and ignore the newly created cookie entirely. + if (source != Web::Cookie::Source::Http && old_cookie->http_only) + return; - // 11. If the cookie store contains a cookie with the same name, domain, and path as the newly created cookie: - [this, source, sync_promise](auto& cookie, auto old_cookie) { - ScopeGuard guard { [&]() { sync_promise->resolve({}); } }; + // Update the creation-time of the newly created cookie to match the creation-time of the old-cookie. + cookie.creation_time = old_cookie->creation_time; - // If the newly created cookie was received from a "non-HTTP" API and the old-cookie's http-only-flag is set, abort these - // steps and ignore the newly created cookie entirely. - if (source != Web::Cookie::Source::Http && old_cookie.http_only) - return; + // Remove the old-cookie from the cookie store. + // NOTE: Rather than deleting then re-inserting this cookie, we update it in-place. + } - // Update the creation-time of the newly created cookie to match the creation-time of the old-cookie. - cookie.creation_time = old_cookie.creation_time; + // 12. Insert the newly created cookie into the cookie store. + m_transient_storage.set_cookie(move(key), move(cookie)); - // Remove the old-cookie from the cookie store. - // NOTE: Rather than deleting then re-inserting this cookie, we update it in-place. - update_cookie_in_database(cookie); - }, - - // 12. Insert the newly created cookie into the cookie store. - [this, sync_promise](auto cookie) { - insert_cookie_into_database(cookie); - sync_promise->resolve({}); - }); - - MUST(sync_promise->await()); + m_transient_storage.purge_expired_cookies(); } Vector CookieJar::get_matching_cookies(const URL::URL& url, StringView canonicalized_domain, Web::Cookie::Source source, MatchingCookiesSpecMode mode) { // https://tools.ietf.org/html/rfc6265#section-5.4 + auto now = UnixDateTime::now(); // 1. Let cookie-list be the set of cookies from the cookie store that meets all of the following requirements: Vector cookie_list; - select_all_cookies_from_database([&](auto cookie) { + m_transient_storage.for_each_cookie([&](auto& cookie) { // Either: The cookie's host-only-flag is true and the canonicalized request-host is identical to the cookie's domain. // Or: The cookie's host-only-flag is false and the canonicalized request-host domain-matches the cookie's domain. bool is_host_only_and_has_identical_domain = cookie.host_only && (canonicalized_domain == cookie.domain); @@ -426,20 +433,25 @@ Vector CookieJar::get_matching_cookies(const URL::URL& url, // NOTE: The WebDriver spec expects only step 1 above to be executed to match cookies. if (mode == MatchingCookiesSpecMode::WebDriver) { - cookie_list.append(move(cookie)); + cookie_list.append(cookie); return; } + // 3. Update the last-access-time of each cookie in the cookie-list to the current date and time. + // NOTE: We do this first so that both our internal storage and cookie-list are updated. + cookie.last_access_time = now; + // 2. The user agent SHOULD sort the cookie-list in the following order: // - Cookies with longer paths are listed before cookies with shorter paths. // - Among cookies that have equal-length path fields, cookies with earlier creation-times are listed before cookies with later creation-times. auto cookie_path_length = cookie.path.bytes().size(); auto cookie_creation_time = cookie.creation_time; - cookie_list.insert_before_matching(move(cookie), [cookie_path_length, cookie_creation_time](auto const& entry) { + cookie_list.insert_before_matching(cookie, [cookie_path_length, cookie_creation_time](auto const& entry) { if (cookie_path_length > entry.path.bytes().size()) { return true; - } else if (cookie_path_length == entry.path.bytes().size()) { + } + if (cookie_path_length == entry.path.bytes().size()) { if (cookie_creation_time < entry.creation_time) return true; } @@ -447,19 +459,9 @@ Vector CookieJar::get_matching_cookies(const URL::URL& url, }); }); - // NOTE: The WebDriver spec expects only step 1 above to be executed to match cookies. - if (mode == MatchingCookiesSpecMode::WebDriver) - return cookie_list; + if (mode != MatchingCookiesSpecMode::WebDriver) + m_transient_storage.purge_expired_cookies(); - // 3. Update the last-access-time of each cookie in the cookie-list to the current date and time. - auto now = UnixDateTime::now(); - - for (auto& cookie : cookie_list) { - cookie.last_access_time = now; - update_cookie_last_access_time_in_database(cookie); - } - - purge_expired_cookies(); return cookie_list; } @@ -527,161 +529,110 @@ static ErrorOr parse_cookie(ReadonlySpan row) return cookie; } -void CookieJar::insert_cookie_into_database(Web::Cookie::Cookie const& cookie) +void CookieJar::TransientStorage::set_cookies(Cookies cookies) { - m_storage.visit( - [&](PersistedStorage& storage) { - storage.database.execute_statement( - storage.statements.insert_cookie, {}, [this]() { purge_expired_cookies(); }, {}, - cookie.name, - cookie.value, - to_underlying(cookie.same_site), - cookie.creation_time, - cookie.last_access_time, - cookie.expiry_time, - cookie.domain, - cookie.path, - cookie.secure, - cookie.http_only, - cookie.host_only, - cookie.persistent); - }, - [&](TransientStorage& storage) { - CookieStorageKey key { cookie.name, cookie.domain, cookie.path }; - storage.set(key, cookie); - }); + m_cookies = move(cookies); + purge_expired_cookies(); } -void CookieJar::update_cookie_in_database(Web::Cookie::Cookie const& cookie) +void CookieJar::TransientStorage::set_cookie(CookieStorageKey key, Web::Cookie::Cookie cookie) { - m_storage.visit( - [&](PersistedStorage& storage) { - storage.database.execute_statement( - storage.statements.update_cookie, {}, [this]() { purge_expired_cookies(); }, {}, - cookie.value, - to_underlying(cookie.same_site), - cookie.creation_time, - cookie.last_access_time, - cookie.expiry_time, - cookie.secure, - cookie.http_only, - cookie.host_only, - cookie.persistent, - cookie.name, - cookie.domain, - cookie.path); - }, - [&](TransientStorage& storage) { - CookieStorageKey key { cookie.name, cookie.domain, cookie.path }; - storage.set(key, cookie); - }); -} + auto result = m_cookies.set(key, cookie); -void CookieJar::update_cookie_last_access_time_in_database(Web::Cookie::Cookie const& cookie) -{ - m_storage.visit( - [&](PersistedStorage& storage) { - storage.database.execute_statement( - storage.statements.update_cookie_last_access_time, - {}, {}, {}, - cookie.last_access_time, - cookie.name, - cookie.domain, - cookie.path); - }, - [&](TransientStorage& storage) { - CookieStorageKey key { cookie.name, cookie.domain, cookie.path }; - storage.set(key, cookie); - }); -} + switch (result) { + case HashSetResult::InsertedNewEntry: + m_inserted_cookies.set(move(key), move(cookie)); + break; -struct WrappedCookie : public RefCounted { - explicit WrappedCookie(Web::Cookie::Cookie cookie_) - : RefCounted() - , cookie(move(cookie_)) - { + case HashSetResult::ReplacedExistingEntry: + if (m_inserted_cookies.contains(key)) + m_inserted_cookies.set(move(key), move(cookie)); + else + m_updated_cookies.set(move(key), move(cookie)); + break; + + case HashSetResult::KeptExistingEntry: + VERIFY_NOT_REACHED(); + break; } - - Web::Cookie::Cookie cookie; - bool had_any_results { false }; -}; - -void CookieJar::select_cookie_from_database(Web::Cookie::Cookie cookie, OnCookieFound on_result, OnCookieNotFound on_complete_without_results) -{ - m_storage.visit( - [&](PersistedStorage& storage) { - auto wrapped_cookie = make_ref_counted(move(cookie)); - - storage.database.execute_statement( - storage.statements.select_cookie, - [on_result = move(on_result), wrapped_cookie = wrapped_cookie](auto row) { - if (auto selected_cookie = parse_cookie(row); selected_cookie.is_error()) - dbgln("Failed to parse cookie '{}': {}", selected_cookie.error(), row); - else - on_result(wrapped_cookie->cookie, selected_cookie.release_value()); - - wrapped_cookie->had_any_results = true; - }, - [on_complete_without_results = move(on_complete_without_results), wrapped_cookie = wrapped_cookie]() { - if (!wrapped_cookie->had_any_results) - on_complete_without_results(move(wrapped_cookie->cookie)); - }, - {}, - wrapped_cookie->cookie.name, - wrapped_cookie->cookie.domain, - wrapped_cookie->cookie.path); - }, - [&](TransientStorage& storage) { - CookieStorageKey key { cookie.name, cookie.domain, cookie.path }; - - if (auto it = storage.find(key); it != storage.end()) - on_result(cookie, it->value); - else - on_complete_without_results(cookie); - }); } -void CookieJar::select_all_cookies_from_database(OnSelectAllCookiesResult on_result) +Optional CookieJar::TransientStorage::get_cookie(CookieStorageKey const& key) { - // FIXME: Make surrounding APIs asynchronous. - m_storage.visit( - [&](PersistedStorage& storage) { - storage.database.execute_statement( - storage.statements.select_all_cookies, - [on_result = move(on_result)](auto row) { - if (auto cookie = parse_cookie(row); cookie.is_error()) - dbgln("Failed to parse cookie '{}': {}", cookie.error(), row); - else - on_result(cookie.release_value()); - }, - {}, - {}); - }, - [&](TransientStorage& storage) { - for (auto const& cookie : storage) - on_result(cookie.value); - }); + return m_cookies.get(key); } -void CookieJar::purge_expired_cookies() +UnixDateTime CookieJar::TransientStorage::purge_expired_cookies() { auto now = UnixDateTime::now(); + auto is_expired = [&](auto const&, auto const& cookie) { return cookie.expiry_time < now; }; - m_storage.visit( - [&](PersistedStorage& storage) { - storage.database.execute_statement(storage.statements.expire_cookie, {}, {}, {}, now); + m_cookies.remove_all_matching(is_expired); + m_inserted_cookies.remove_all_matching(is_expired); + m_updated_cookies.remove_all_matching(is_expired); + + return now; +} + +void CookieJar::PersistedStorage::insert_cookie(Web::Cookie::Cookie const& cookie) +{ + database.execute_statement( + statements.insert_cookie, + {}, {}, {}, + cookie.name, + cookie.value, + to_underlying(cookie.same_site), + cookie.creation_time, + cookie.last_access_time, + cookie.expiry_time, + cookie.domain, + cookie.path, + cookie.secure, + cookie.http_only, + cookie.host_only, + cookie.persistent); +} + +void CookieJar::PersistedStorage::update_cookie(Web::Cookie::Cookie const& cookie) +{ + database.execute_statement( + statements.update_cookie, + {}, {}, {}, + cookie.value, + to_underlying(cookie.same_site), + cookie.creation_time, + cookie.last_access_time, + cookie.expiry_time, + cookie.secure, + cookie.http_only, + cookie.host_only, + cookie.persistent, + cookie.name, + cookie.domain, + cookie.path); +} + +CookieJar::TransientStorage::Cookies CookieJar::PersistedStorage::select_all_cookies() +{ + HashMap cookies; + + auto add_cookie = [&](auto cookie) { + CookieStorageKey key { cookie.name, cookie.domain, cookie.path }; + cookies.set(move(key), move(cookie)); + }; + + database.execute_statement( + statements.select_all_cookies, + [&](auto row) { + if (auto cookie = parse_cookie(row); cookie.is_error()) + dbgln("Failed to parse cookie '{}': {}", cookie.error(), row); + else + add_cookie(cookie.release_value()); }, - [&](TransientStorage& storage) { - Vector keys_to_evict; + {}, + {}); - for (auto const& cookie : storage) { - if (cookie.value.expiry_time < now) - keys_to_evict.append(cookie.key); - } - - for (auto const& key : keys_to_evict) - storage.remove(key); - }); + return cookies; } } diff --git a/Userland/Libraries/LibWebView/CookieJar.h b/Userland/Libraries/LibWebView/CookieJar.h index 3762382b90..9f38de9977 100644 --- a/Userland/Libraries/LibWebView/CookieJar.h +++ b/Userland/Libraries/LibWebView/CookieJar.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -34,22 +35,53 @@ class CookieJar { SQL::StatementID create_table { 0 }; SQL::StatementID insert_cookie { 0 }; SQL::StatementID update_cookie { 0 }; - SQL::StatementID update_cookie_last_access_time { 0 }; SQL::StatementID expire_cookie { 0 }; - SQL::StatementID select_cookie { 0 }; SQL::StatementID select_all_cookies { 0 }; }; - struct PersistedStorage { - Database& database; - Statements statements; + class TransientStorage { + public: + using Cookies = HashMap; + + void set_cookies(Cookies); + void set_cookie(CookieStorageKey, Web::Cookie::Cookie); + Optional get_cookie(CookieStorageKey const&); + + size_t size() const { return m_cookies.size(); } + + UnixDateTime purge_expired_cookies(); + + auto take_inserted_cookies() { return move(m_inserted_cookies); } + auto take_updated_cookies() { return move(m_updated_cookies); } + + template + void for_each_cookie(Callback callback) + { + for (auto& it : m_cookies) + callback(it.value); + } + + private: + Cookies m_cookies; + Cookies m_inserted_cookies; + Cookies m_updated_cookies; }; - using TransientStorage = HashMap; + struct PersistedStorage { + void insert_cookie(Web::Cookie::Cookie const& cookie); + void update_cookie(Web::Cookie::Cookie const& cookie); + TransientStorage::Cookies select_all_cookies(); + + Database& database; + Statements statements; + RefPtr synchronization_timer {}; + }; public: - static ErrorOr create(Database&); - static CookieJar create(); + static ErrorOr> create(Database&); + static NonnullOwnPtr create(); + + ~CookieJar(); String get_cookie(const URL::URL& url, Web::Cookie::Source source); void set_cookie(const URL::URL& url, Web::Cookie::ParsedCookie const& parsed_cookie, Web::Cookie::Source source); @@ -60,8 +92,10 @@ public: Optional get_named_cookie(URL::URL const& url, StringView name); private: - explicit CookieJar(PersistedStorage); - explicit CookieJar(TransientStorage); + explicit CookieJar(Optional); + + AK_MAKE_NONCOPYABLE(CookieJar); + AK_MAKE_NONMOVABLE(CookieJar); static Optional canonicalize_domain(const URL::URL& url); static bool domain_matches(StringView string, StringView domain_string); @@ -76,20 +110,8 @@ private: void store_cookie(Web::Cookie::ParsedCookie const& parsed_cookie, const URL::URL& url, String canonicalized_domain, Web::Cookie::Source source); Vector get_matching_cookies(const URL::URL& url, StringView canonicalized_domain, Web::Cookie::Source source, MatchingCookiesSpecMode mode = MatchingCookiesSpecMode::RFC6265); - void insert_cookie_into_database(Web::Cookie::Cookie const& cookie); - void update_cookie_in_database(Web::Cookie::Cookie const& cookie); - void update_cookie_last_access_time_in_database(Web::Cookie::Cookie const& cookie); - - using OnCookieFound = Function; - using OnCookieNotFound = Function; - void select_cookie_from_database(Web::Cookie::Cookie cookie, OnCookieFound on_result, OnCookieNotFound on_complete_without_results); - - using OnSelectAllCookiesResult = Function; - void select_all_cookies_from_database(OnSelectAllCookiesResult on_result); - - void purge_expired_cookies(); - - Variant m_storage; + Optional m_persisted_storage; + TransientStorage m_transient_storage; }; } diff --git a/Userland/Utilities/headless-browser.cpp b/Userland/Utilities/headless-browser.cpp index 53001f2e4f..bc5a8756b2 100644 --- a/Userland/Utilities/headless-browser.cpp +++ b/Userland/Utilities/headless-browser.cpp @@ -164,7 +164,7 @@ public: } private: - HeadlessWebContentView(NonnullRefPtr database, WebView::CookieJar cookie_jar, RefPtr request_client = nullptr) + HeadlessWebContentView(NonnullRefPtr database, NonnullOwnPtr cookie_jar, RefPtr request_client = nullptr) : m_database(move(database)) , m_cookie_jar(move(cookie_jar)) , m_request_client(move(request_client)) @@ -183,11 +183,11 @@ private: }; on_get_cookie = [this](auto const& url, auto source) { - return m_cookie_jar.get_cookie(url, source); + return m_cookie_jar->get_cookie(url, source); }; on_set_cookie = [this](auto const& url, auto const& cookie, auto source) { - m_cookie_jar.set_cookie(url, cookie, source); + m_cookie_jar->set_cookie(url, cookie, source); }; on_request_worker_agent = [this]() { @@ -213,7 +213,7 @@ private: RefPtr>> m_pending_screenshot; NonnullRefPtr m_database; - WebView::CookieJar m_cookie_jar; + NonnullOwnPtr m_cookie_jar; RefPtr m_request_client; };