From 790b9bd36a1b071d6f1f743a2b2a1c25cf033198 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Wed, 10 Jun 2026 17:37:14 +0200 Subject: [PATCH] LibWebView: Add async cookie deletion internals Add Internals.deleteAllCookies(), backed by an async WebContent to browser request and ack pair. CookieJar can now clear transient and persisted cookies. Note that we only delete all cookies associated with the current URL so that tests are able to run in parallel with one another without impacting shared cookie state. --- Libraries/LibWeb/Internals/Internals.cpp | 10 +++++++ Libraries/LibWeb/Internals/Internals.h | 1 + Libraries/LibWeb/Internals/Internals.idl | 1 + Libraries/LibWeb/Page/Page.h | 1 + Libraries/LibWebView/CookieJar.cpp | 8 ++++++ Libraries/LibWebView/CookieJar.h | 1 + Libraries/LibWebView/WebContentClient.cpp | 6 +++++ Libraries/LibWebView/WebContentClient.h | 1 + Services/WebContent/ConnectionFromClient.cpp | 6 +++++ Services/WebContent/ConnectionFromClient.h | 1 + Services/WebContent/PageClient.cpp | 26 +++++++++++++++++++ Services/WebContent/PageClient.h | 4 +++ Services/WebContent/WebContentClient.ipc | 1 + Services/WebContent/WebContentServer.ipc | 1 + .../wpt-import/resources/testdriver-vendor.js | 4 +++ 15 files changed, 72 insertions(+) diff --git a/Libraries/LibWeb/Internals/Internals.cpp b/Libraries/LibWeb/Internals/Internals.cpp index 339e509c17..1d325944f2 100644 --- a/Libraries/LibWeb/Internals/Internals.cpp +++ b/Libraries/LibWeb/Internals/Internals.cpp @@ -478,6 +478,16 @@ void Internals::expire_cookies_with_time_offset(WebIDL::LongLong seconds) page().client().page_did_expire_cookies_with_time_offset(AK::Duration::from_seconds(seconds)); } +GC::Ref Internals::delete_all_cookies() +{ + auto& realm = this->realm(); + auto promise = WebIDL::create_promise(realm); + auto const& document = as(HTML::relevant_global_object(*this)).associated_document(); + + page().client().page_did_delete_all_cookies(document.url(), promise); + return promise; +} + bool Internals::set_http_memory_cache_enabled(bool enabled) { auto was_enabled = Web::Fetch::Fetching::http_memory_cache_enabled(); diff --git a/Libraries/LibWeb/Internals/Internals.h b/Libraries/LibWeb/Internals/Internals.h index 1aaee21fef..dd65bafc94 100644 --- a/Libraries/LibWeb/Internals/Internals.h +++ b/Libraries/LibWeb/Internals/Internals.h @@ -82,6 +82,7 @@ public: void simulate_drop(double x, double y); void expire_cookies_with_time_offset(WebIDL::LongLong seconds); + GC::Ref delete_all_cookies(); bool set_http_memory_cache_enabled(bool enabled); WebIDL::ExceptionOr set_content_blockers(String const& patterns); diff --git a/Libraries/LibWeb/Internals/Internals.idl b/Libraries/LibWeb/Internals/Internals.idl index 4f8c0e2103..a8e19b0cab 100644 --- a/Libraries/LibWeb/Internals/Internals.idl +++ b/Libraries/LibWeb/Internals/Internals.idl @@ -70,6 +70,7 @@ interface Internals { undefined simulateDrop(double x, double y); undefined expireCookiesWithTimeOffset(long long seconds); + Promise deleteAllCookies(); boolean setHttpMemoryCacheEnabled(boolean enabled); undefined setContentBlockers(DOMString patterns); diff --git a/Libraries/LibWeb/Page/Page.h b/Libraries/LibWeb/Page/Page.h index a18cb2cc32..00d71d3ffa 100644 --- a/Libraries/LibWeb/Page/Page.h +++ b/Libraries/LibWeb/Page/Page.h @@ -481,6 +481,7 @@ public: virtual void page_did_set_cookie(URL::URL const&, HTTP::Cookie::ParsedCookie const&, HTTP::Cookie::Source) { } virtual void page_did_update_cookie(HTTP::Cookie::Cookie const&) { } virtual void page_did_expire_cookies_with_time_offset(AK::Duration) { } + virtual void page_did_delete_all_cookies(URL::URL const&, GC::Ref) { } virtual void page_did_store_hsts_policy(String const&, HTTP::HSTS::ParsedHSTSPolicy const&) { } virtual bool page_did_is_known_hsts_host(String const&) { return false; } virtual Optional page_did_request_storage_item([[maybe_unused]] Web::StorageAPI::StorageEndpointType storage_endpoint, [[maybe_unused]] String const& storage_key, [[maybe_unused]] String const& bottle_key) { return {}; } diff --git a/Libraries/LibWebView/CookieJar.cpp b/Libraries/LibWebView/CookieJar.cpp index 96c9c93dfb..3963bfec65 100644 --- a/Libraries/LibWebView/CookieJar.cpp +++ b/Libraries/LibWebView/CookieJar.cpp @@ -540,6 +540,14 @@ void CookieJar::expire_cookies_with_time_offset(AK::Duration offset) m_transient_storage.purge_expired_cookies(offset); } +void CookieJar::delete_all_cookies(URL::URL const& url) +{ + for (auto& cookie : get_all_cookies_webdriver(url)) { + cookie.expiry_time = UnixDateTime::earliest(); + update_cookie(move(cookie)); + } +} + void CookieJar::expire_cookies_accessed_since(UnixDateTime since) { m_transient_storage.expire_and_purge_cookies_accessed_since(since); diff --git a/Libraries/LibWebView/CookieJar.h b/Libraries/LibWebView/CookieJar.h index b51ac233cc..7d8c5427c4 100644 --- a/Libraries/LibWebView/CookieJar.h +++ b/Libraries/LibWebView/CookieJar.h @@ -48,6 +48,7 @@ public: Vector get_all_cookies_cookiestore(URL::URL const& url); Optional get_named_cookie(URL::URL const& url, StringView name); void expire_cookies_with_time_offset(AK::Duration); + void delete_all_cookies(URL::URL const&); void expire_cookies_accessed_since(UnixDateTime since); Requests::CacheSizes estimate_storage_size_accessed_since(UnixDateTime since) const; diff --git a/Libraries/LibWebView/WebContentClient.cpp b/Libraries/LibWebView/WebContentClient.cpp index d3a6d8d303..95e7c6235f 100644 --- a/Libraries/LibWebView/WebContentClient.cpp +++ b/Libraries/LibWebView/WebContentClient.cpp @@ -982,6 +982,12 @@ void WebContentClient::did_expire_cookies_with_time_offset(AK::Duration offset) Application::cookie_jar().expire_cookies_with_time_offset(offset); } +void WebContentClient::did_request_delete_all_cookies(u64 page_id, u64 request_id, URL::URL url) +{ + Application::cookie_jar().delete_all_cookies(url); + async_did_delete_all_cookies(page_id, request_id); +} + void WebContentClient::did_store_hsts_policy(String domain, HTTP::HSTS::ParsedHSTSPolicy policy) { Application::hsts_store().store_policy(domain, policy); diff --git a/Libraries/LibWebView/WebContentClient.h b/Libraries/LibWebView/WebContentClient.h index 09a255904c..5bb116cbf7 100644 --- a/Libraries/LibWebView/WebContentClient.h +++ b/Libraries/LibWebView/WebContentClient.h @@ -153,6 +153,7 @@ private: virtual void did_set_cookie(URL::URL, HTTP::Cookie::ParsedCookie, HTTP::Cookie::Source) override; virtual void did_update_cookie(HTTP::Cookie::Cookie) override; virtual void did_expire_cookies_with_time_offset(AK::Duration) override; + virtual void did_request_delete_all_cookies(u64 page_id, u64 request_id, URL::URL) override; virtual void did_store_hsts_policy(String, HTTP::HSTS::ParsedHSTSPolicy) override; virtual Messages::WebContentClient::DidIsKnownHstsHostResponse did_is_known_hsts_host(String) override; virtual Messages::WebContentClient::DidRequestStorageItemResponse did_request_storage_item(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key, String bottle_key) override; diff --git a/Services/WebContent/ConnectionFromClient.cpp b/Services/WebContent/ConnectionFromClient.cpp index c1f3b5aabf..f7d0ed139f 100644 --- a/Services/WebContent/ConnectionFromClient.cpp +++ b/Services/WebContent/ConnectionFromClient.cpp @@ -2053,6 +2053,12 @@ void ConnectionFromClient::retrieved_clipboard_entries(u64 page_id, u64 request_ page->page().retrieved_clipboard_entries(request_id, move(items)); } +void ConnectionFromClient::did_delete_all_cookies(u64 page_id, u64 request_id) +{ + if (auto page = this->page(page_id); page.has_value()) + page->did_delete_all_cookies(request_id); +} + void ConnectionFromClient::toggle_media_play_state(u64 page_id) { if (auto page = this->page(page_id); page.has_value()) diff --git a/Services/WebContent/ConnectionFromClient.h b/Services/WebContent/ConnectionFromClient.h index 4085d4598e..a63a255381 100644 --- a/Services/WebContent/ConnectionFromClient.h +++ b/Services/WebContent/ConnectionFromClient.h @@ -147,6 +147,7 @@ private: virtual void set_window_size(u64 page_id, Web::DevicePixelSize) override; virtual void did_update_window_rect(u64 page_id) override; virtual void handle_file_return(u64 page_id, i32 error, Optional file, i32 request_id) override; + virtual void did_delete_all_cookies(u64 page_id, u64 request_id) override; virtual void set_system_visibility_state(u64 page_id, Web::HTML::VisibilityState) override; virtual void reset_zoom(u64 page_id) override; diff --git a/Services/WebContent/PageClient.cpp b/Services/WebContent/PageClient.cpp index 8979c22671..f4e393a966 100644 --- a/Services/WebContent/PageClient.cpp +++ b/Services/WebContent/PageClient.cpp @@ -32,11 +32,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -110,6 +112,8 @@ void PageClient::visit_edges(JS::Cell::Visitor& visitor) Base::visit_edges(visitor); visitor.visit(m_page); visitor.visit(m_top_level_document_console_client); + for (auto& promise : m_pending_delete_all_cookies_promises) + visitor.visit(promise.value); m_pending_dom_mutations.for_each([&](auto& pending_mutation) { visitor.visit(pending_mutation.target); }); @@ -634,6 +638,28 @@ void PageClient::page_did_expire_cookies_with_time_offset(AK::Duration offset) document->reset_cookie_version(); } +void PageClient::page_did_delete_all_cookies(URL::URL const& url, GC::Ref promise) +{ + auto request_id = m_next_delete_all_cookies_request_id++; + m_pending_delete_all_cookies_promises.set(request_id, promise); + client().async_did_request_delete_all_cookies(m_id, request_id, url); + + if (auto* document = page().top_level_browsing_context().active_document()) + document->reset_cookie_version(); +} + +void PageClient::did_delete_all_cookies(u64 request_id) +{ + auto maybe_promise = m_pending_delete_all_cookies_promises.take(request_id); + if (!maybe_promise.has_value()) + return; + + auto promise = maybe_promise.release_value(); + auto& realm = promise->promise()->shape().realm(); + Web::HTML::TemporaryExecutionContext execution_context { realm, Web::HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; + Web::WebIDL::resolve_promise(realm, promise); +} + void PageClient::page_did_store_hsts_policy(String const& domain, HTTP::HSTS::ParsedHSTSPolicy const& policy) { client().async_did_store_hsts_policy(domain, policy); diff --git a/Services/WebContent/PageClient.h b/Services/WebContent/PageClient.h index 99196e2799..4eb01d7551 100644 --- a/Services/WebContent/PageClient.h +++ b/Services/WebContent/PageClient.h @@ -112,6 +112,7 @@ public: void queue_screenshot_task(Optional node_id); void send_current_needs_beforeunload_check(); void clear_pending_dom_mutations(); + void did_delete_all_cookies(u64 request_id); private: struct PendingDOMMutation { @@ -179,6 +180,7 @@ private: virtual void page_did_set_cookie(URL::URL const&, HTTP::Cookie::ParsedCookie const&, HTTP::Cookie::Source) override; virtual void page_did_update_cookie(HTTP::Cookie::Cookie const&) override; virtual void page_did_expire_cookies_with_time_offset(AK::Duration) override; + virtual void page_did_delete_all_cookies(URL::URL const&, GC::Ref) override; virtual void page_did_store_hsts_policy(String const&, HTTP::HSTS::ParsedHSTSPolicy const&) override; virtual bool page_did_is_known_hsts_host(String const&) override; virtual Optional page_did_request_storage_item(Web::StorageAPI::StorageEndpointType storage_endpoint, String const& storage_key, String const& bottle_key) override; @@ -235,6 +237,8 @@ private: double m_zoom_level { 1.0 }; double m_maximum_frames_per_second { 60.0 }; u64 m_id { 0 }; + u64 m_next_delete_all_cookies_request_id { 1 }; + HashMap> m_pending_delete_all_cookies_promises; bool m_has_focus { true }; Web::CSS::PreferredColorScheme m_preferred_color_scheme { Web::CSS::PreferredColorScheme::Auto }; diff --git a/Services/WebContent/WebContentClient.ipc b/Services/WebContent/WebContentClient.ipc index f19c497f3c..59419fe280 100644 --- a/Services/WebContent/WebContentClient.ipc +++ b/Services/WebContent/WebContentClient.ipc @@ -96,6 +96,7 @@ endpoint WebContentClient did_set_cookie(URL::URL url, HTTP::Cookie::ParsedCookie cookie, HTTP::Cookie::Source source) => () did_update_cookie(HTTP::Cookie::Cookie cookie) =| did_expire_cookies_with_time_offset(AK::Duration offset) =| + did_request_delete_all_cookies(u64 page_id, u64 request_id, URL::URL url) =| did_store_hsts_policy(String domain, HTTP::HSTS::ParsedHSTSPolicy policy) =| did_is_known_hsts_host(String domain) => (bool result) diff --git a/Services/WebContent/WebContentServer.ipc b/Services/WebContent/WebContentServer.ipc index 11b9036a58..3c43ef68d5 100644 --- a/Services/WebContent/WebContentServer.ipc +++ b/Services/WebContent/WebContentServer.ipc @@ -138,6 +138,7 @@ endpoint WebContentServer reset_zoom(u64 page_id) =| handle_file_return(u64 page_id, i32 error, Optional file, i32 request_id) =| + did_delete_all_cookies(u64 page_id, u64 request_id) =| set_system_visibility_state(u64 page_id, Web::HTML::VisibilityState visibility_state) =| diff --git a/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js b/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js index 4afab4489a..4bb213f575 100644 --- a/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js +++ b/Tests/LibWeb/Text/input/wpt-import/resources/testdriver-vendor.js @@ -21,6 +21,10 @@ window.test_driver_internal.get_computed_role = async function(element) { return await window.internals.getComputedRole(element); }; +window.test_driver_internal.delete_all_cookies = function(context) { + return window.internals.deleteAllCookies(); +}; + window.test_driver_internal.action_sequence = function(actions, context) { // Modifier key codes from WebDriver spec const SHIFT = "\uE008";