From 5545d38d7ab09adb2d27f0684727efd9a2b35f59 Mon Sep 17 00:00:00 2001 From: Idan Horowitz Date: Tue, 5 Aug 2025 23:05:45 +0300 Subject: [PATCH] LibWeb: Implement CookieStore::get(name) --- Libraries/LibWeb/CookieStore/CookieStore.cpp | 108 +++++++++++++++++- Libraries/LibWeb/CookieStore/CookieStore.h | 23 +++- Libraries/LibWeb/CookieStore/CookieStore.idl | 8 +- Libraries/LibWeb/HTML/Window.cpp | 2 +- Libraries/LibWeb/Page/Page.h | 3 +- .../ServiceWorkerGlobalScope.cpp | 2 +- Libraries/LibWebView/CookieJar.cpp | 11 +- Libraries/LibWebView/CookieJar.h | 3 +- Libraries/LibWebView/WebContentClient.cpp | 9 +- Libraries/LibWebView/WebContentClient.h | 3 +- Services/WebContent/PageClient.cpp | 9 +- Services/WebContent/PageClient.h | 3 +- Services/WebContent/WebContentClient.ipc | 3 +- Services/WebContent/WebDriverConnection.cpp | 4 +- 14 files changed, 174 insertions(+), 17 deletions(-) diff --git a/Libraries/LibWeb/CookieStore/CookieStore.cpp b/Libraries/LibWeb/CookieStore/CookieStore.cpp index b339f63bb09..683e3f4c989 100644 --- a/Libraries/LibWeb/CookieStore/CookieStore.cpp +++ b/Libraries/LibWeb/CookieStore/CookieStore.cpp @@ -7,13 +7,20 @@ #include #include #include +#include +#include +#include +#include +#include +#include namespace Web::CookieStore { GC_DEFINE_ALLOCATOR(CookieStore); -CookieStore::CookieStore(JS::Realm& realm) +CookieStore::CookieStore(JS::Realm& realm, PageClient& client) : DOM::EventTarget(realm) + , m_client(client) { } @@ -23,4 +30,103 @@ void CookieStore::initialize(JS::Realm& realm) Base::initialize(realm); } +void CookieStore::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + + visitor.visit(m_client); +} + +// https://cookiestore.spec.whatwg.org/#create-a-cookielistitem +static CookieListItem create_a_cookie_list_item(Cookie::Cookie const& cookie) +{ + // 1. Let name be the result of running UTF-8 decode without BOM on cookie’s name. + // 2. Let value be the result of running UTF-8 decode without BOM on cookie’s value. + // 3. Return «[ "name" → name, "value" → value ]» + return CookieListItem { + .name = cookie.name, + .value = cookie.value, + }; +} + +// https://cookiestore.spec.whatwg.org/#query-cookies +static Vector query_cookies(PageClient& client, URL::URL const& url, Optional const& name) +{ + // 1. Perform the steps defined in Cookies § Retrieval Model to compute the "cookie-string from a given cookie store" + // with url as request-uri. The cookie-string itself is ignored, but the intermediate cookie-list is used in subsequent steps. + // For the purposes of the steps, the cookie-string is being generated for a "non-HTTP" API. + auto cookie_list = client.page_did_request_all_cookies_cookiestore(url); + + // 2. Let list be a new list. + Vector list; + + // 3. For each cookie in cookie-list, run these steps: + for (auto const& cookie : cookie_list) { + // 1. Assert: cookie’s http-only-flag is false. + VERIFY(!cookie.http_only); + + // 2. If name is given, then run these steps: + if (name.has_value()) { + // 1. Let cookieName be the result of running UTF-8 decode without BOM on cookie’s name. + // 2. If cookieName does not equal name, then continue. + if (cookie.name != name.value()) + continue; + } + // 3. Let item be the result of running create a CookieListItem from cookie. + auto item = create_a_cookie_list_item(cookie); + + // 4. Append item to list. + list.append(move(item)); + } + + // 4. Return list. + return list; +} + +// https://cookiestore.spec.whatwg.org/#dom-cookiestore-get +GC::Ref CookieStore::get(String name) +{ + auto& realm = this->realm(); + + // 1. Let settings be this’s relevant settings object. + auto const& settings = HTML::relevant_settings_object(*this); + + // 2. Let origin be settings’s origin. + auto const& origin = settings.origin(); + + // 3. If origin is an opaque origin, then return a promise rejected with a "SecurityError" DOMException. + if (origin.is_opaque()) + return WebIDL::create_rejected_promise(realm, WebIDL::SecurityError::create(realm, "Document origin is opaque"_string)); + + // 4. Let url be settings’s creation URL. + auto url = settings.creation_url; + + // 5. Let p be a new promise. + auto promise = WebIDL::create_promise(realm); + + // 6. Run the following steps in parallel: + Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [&realm, client = m_client, promise, url = move(url), name = move(name)]() { + // 1. Let list be the results of running query cookies with url and name. + auto list = query_cookies(client, url, name); + + // AD-HOC: Queue a global task to perform the next steps + // Spec issue: https://github.com/whatwg/cookiestore/issues/239 + queue_global_task(HTML::Task::Source::Unspecified, realm.global_object(), GC::create_function(realm.heap(), [&realm, promise, list = move(list)]() { + HTML::TemporaryExecutionContext execution_context { realm }; + // 2. If list is failure, then reject p with a TypeError and abort these steps. + + // 3. If list is empty, then resolve p with null. + if (list.is_empty()) + WebIDL::resolve_promise(realm, promise, JS::js_null()); + + // 4. Otherwise, resolve p with the first item of list. + else + WebIDL::resolve_promise(realm, promise, Bindings::cookie_list_item_to_value(realm, list[0])); + })); + })); + + // 7. Return p. + return promise; +} + } diff --git a/Libraries/LibWeb/CookieStore/CookieStore.h b/Libraries/LibWeb/CookieStore/CookieStore.h index 2ce834ea1f4..ea84817db03 100644 --- a/Libraries/LibWeb/CookieStore/CookieStore.h +++ b/Libraries/LibWeb/CookieStore/CookieStore.h @@ -6,19 +6,40 @@ #pragma once +#include +#include +#include #include namespace Web::CookieStore { +// https://cookiestore.spec.whatwg.org/#dictdef-cookielistitem +struct CookieListItem { + Optional name; + Optional value; +}; + // https://cookiestore.spec.whatwg.org/#cookiestore class CookieStore final : public DOM::EventTarget { WEB_PLATFORM_OBJECT(CookieStore, DOM::EventTarget); GC_DECLARE_ALLOCATOR(CookieStore); +public: + GC::Ref get(String name); + private: - CookieStore(JS::Realm&); + CookieStore(JS::Realm&, PageClient&); virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Cell::Visitor&) override; + + GC::Ref m_client; }; } + +namespace Web::Bindings { + +JS::Value cookie_list_item_to_value(JS::Realm&, CookieStore::CookieListItem const&); + +} diff --git a/Libraries/LibWeb/CookieStore/CookieStore.idl b/Libraries/LibWeb/CookieStore/CookieStore.idl index 6c8edb119c8..6fa39e91862 100644 --- a/Libraries/LibWeb/CookieStore/CookieStore.idl +++ b/Libraries/LibWeb/CookieStore/CookieStore.idl @@ -3,10 +3,16 @@ #import #import +[GenerateToValue] +dictionary CookieListItem { + USVString name; + USVString value; +}; + // https://cookiestore.spec.whatwg.org/#cookiestore [Exposed=(ServiceWorker,Window), SecureContext] interface CookieStore : EventTarget { - + Promise get(USVString name); }; // https://cookiestore.spec.whatwg.org/#Window diff --git a/Libraries/LibWeb/HTML/Window.cpp b/Libraries/LibWeb/HTML/Window.cpp index 29f83417c30..f2acfb269f6 100644 --- a/Libraries/LibWeb/HTML/Window.cpp +++ b/Libraries/LibWeb/HTML/Window.cpp @@ -1130,7 +1130,7 @@ GC::Ref Window::cookie_store() // The cookieStore getter steps are to return this’s associated CookieStore. if (!m_cookie_store) - m_cookie_store = realm.create(realm); + m_cookie_store = realm.create(realm, page().client()); return *m_cookie_store; } diff --git a/Libraries/LibWeb/Page/Page.h b/Libraries/LibWeb/Page/Page.h index 7a15353874e..174c60825ed 100644 --- a/Libraries/LibWeb/Page/Page.h +++ b/Libraries/LibWeb/Page/Page.h @@ -352,7 +352,8 @@ public: virtual void page_did_request_set_prompt_text(String const&) { } virtual void page_did_request_accept_dialog() { } virtual void page_did_request_dismiss_dialog() { } - virtual Vector page_did_request_all_cookies(URL::URL const&) { return {}; } + virtual Vector page_did_request_all_cookies_webdriver(URL::URL const&) { return {}; } + virtual Vector page_did_request_all_cookies_cookiestore(URL::URL const&) { return {}; } virtual Optional page_did_request_named_cookie(URL::URL const&, String const&) { return {}; } virtual String page_did_request_cookie(URL::URL const&, Cookie::Source) { return {}; } virtual void page_did_set_cookie(URL::URL const&, Cookie::ParsedCookie const&, Cookie::Source) { } diff --git a/Libraries/LibWeb/ServiceWorker/ServiceWorkerGlobalScope.cpp b/Libraries/LibWeb/ServiceWorker/ServiceWorkerGlobalScope.cpp index a0116a25a98..ee4d3e28d48 100644 --- a/Libraries/LibWeb/ServiceWorker/ServiceWorkerGlobalScope.cpp +++ b/Libraries/LibWeb/ServiceWorker/ServiceWorkerGlobalScope.cpp @@ -93,7 +93,7 @@ GC::Ref ServiceWorkerGlobalScope::cookie_store() // The cookieStore getter steps are to return this’s associated CookieStore. if (!m_cookie_store) - m_cookie_store = realm.create(realm); + m_cookie_store = realm.create(realm, page()->client()); return *m_cookie_store; } diff --git a/Libraries/LibWebView/CookieJar.cpp b/Libraries/LibWebView/CookieJar.cpp index 5569ad81dfb..9ce08bdb236 100644 --- a/Libraries/LibWebView/CookieJar.cpp +++ b/Libraries/LibWebView/CookieJar.cpp @@ -192,7 +192,7 @@ Vector CookieJar::get_all_cookies() } // https://w3c.github.io/webdriver/#dfn-associated-cookies -Vector CookieJar::get_all_cookies(URL::URL const& url) +Vector CookieJar::get_all_cookies_webdriver(URL::URL const& url) { auto domain = canonicalize_domain(url); if (!domain.has_value()) @@ -201,6 +201,15 @@ Vector CookieJar::get_all_cookies(URL::URL const& url) return get_matching_cookies(url, domain.value(), Web::Cookie::Source::Http, MatchingCookiesSpecMode::WebDriver); } +Vector CookieJar::get_all_cookies_cookiestore(URL::URL const& url) +{ + auto domain = canonicalize_domain(url); + if (!domain.has_value()) + return {}; + + return get_matching_cookies(url, domain.value(), Web::Cookie::Source::NonHttp, MatchingCookiesSpecMode::RFC6265); +} + Optional CookieJar::get_named_cookie(URL::URL const& url, StringView name) { auto domain = canonicalize_domain(url); diff --git a/Libraries/LibWebView/CookieJar.h b/Libraries/LibWebView/CookieJar.h index e51c5a04b98..decc6a7b5d1 100644 --- a/Libraries/LibWebView/CookieJar.h +++ b/Libraries/LibWebView/CookieJar.h @@ -93,7 +93,8 @@ public: void dump_cookies(); void clear_all_cookies(); Vector get_all_cookies(); - Vector get_all_cookies(URL::URL const& url); + Vector get_all_cookies_webdriver(URL::URL const& url); + 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); diff --git a/Libraries/LibWebView/WebContentClient.cpp b/Libraries/LibWebView/WebContentClient.cpp index 1d5221e6c5f..4700d581f4a 100644 --- a/Libraries/LibWebView/WebContentClient.cpp +++ b/Libraries/LibWebView/WebContentClient.cpp @@ -485,9 +485,14 @@ void WebContentClient::did_change_favicon(u64 page_id, Gfx::ShareableBitmap favi } } -Messages::WebContentClient::DidRequestAllCookiesResponse WebContentClient::did_request_all_cookies(URL::URL url) +Messages::WebContentClient::DidRequestAllCookiesWebdriverResponse WebContentClient::did_request_all_cookies_webdriver(URL::URL url) { - return Application::cookie_jar().get_all_cookies(url); + return Application::cookie_jar().get_all_cookies_webdriver(url); +} + +Messages::WebContentClient::DidRequestAllCookiesCookiestoreResponse WebContentClient::did_request_all_cookies_cookiestore(URL::URL url) +{ + return Application::cookie_jar().get_all_cookies_cookiestore(url); } Messages::WebContentClient::DidRequestNamedCookieResponse WebContentClient::did_request_named_cookie(URL::URL url, String name) diff --git a/Libraries/LibWebView/WebContentClient.h b/Libraries/LibWebView/WebContentClient.h index df582c5dea8..f729146ba86 100644 --- a/Libraries/LibWebView/WebContentClient.h +++ b/Libraries/LibWebView/WebContentClient.h @@ -101,7 +101,8 @@ private: virtual void did_request_set_prompt_text(u64 page_id, String message) override; virtual void did_request_accept_dialog(u64 page_id) override; virtual void did_request_dismiss_dialog(u64 page_id) override; - virtual Messages::WebContentClient::DidRequestAllCookiesResponse did_request_all_cookies(URL::URL) override; + virtual Messages::WebContentClient::DidRequestAllCookiesWebdriverResponse did_request_all_cookies_webdriver(URL::URL) override; + virtual Messages::WebContentClient::DidRequestAllCookiesCookiestoreResponse did_request_all_cookies_cookiestore(URL::URL) override; virtual Messages::WebContentClient::DidRequestNamedCookieResponse did_request_named_cookie(URL::URL, String) override; virtual Messages::WebContentClient::DidRequestCookieResponse did_request_cookie(URL::URL, Web::Cookie::Source) override; virtual void did_set_cookie(URL::URL, Web::Cookie::ParsedCookie, Web::Cookie::Source) override; diff --git a/Services/WebContent/PageClient.cpp b/Services/WebContent/PageClient.cpp index 87c69b69e78..8265677ae90 100644 --- a/Services/WebContent/PageClient.cpp +++ b/Services/WebContent/PageClient.cpp @@ -482,9 +482,14 @@ void PageClient::page_did_change_favicon(Gfx::Bitmap const& favicon) client().async_did_change_favicon(m_id, favicon.to_shareable_bitmap()); } -Vector PageClient::page_did_request_all_cookies(URL::URL const& url) +Vector PageClient::page_did_request_all_cookies_webdriver(URL::URL const& url) { - return client().did_request_all_cookies(url); + return client().did_request_all_cookies_webdriver(url); +} + +Vector PageClient::page_did_request_all_cookies_cookiestore(URL::URL const& url) +{ + return client().did_request_all_cookies_cookiestore(url); } Optional PageClient::page_did_request_named_cookie(URL::URL const& url, String const& name) diff --git a/Services/WebContent/PageClient.h b/Services/WebContent/PageClient.h index 47f0586f704..da8a5e30cf6 100644 --- a/Services/WebContent/PageClient.h +++ b/Services/WebContent/PageClient.h @@ -143,7 +143,8 @@ private: virtual void page_did_request_accept_dialog() override; virtual void page_did_request_dismiss_dialog() override; virtual void page_did_change_favicon(Gfx::Bitmap const&) override; - virtual Vector page_did_request_all_cookies(URL::URL const&) override; + virtual Vector page_did_request_all_cookies_webdriver(URL::URL const&) override; + virtual Vector page_did_request_all_cookies_cookiestore(URL::URL const&) override; virtual Optional page_did_request_named_cookie(URL::URL const&, String const&) override; virtual String page_did_request_cookie(URL::URL const&, Web::Cookie::Source) override; virtual void page_did_set_cookie(URL::URL const&, Web::Cookie::ParsedCookie const&, Web::Cookie::Source) override; diff --git a/Services/WebContent/WebContentClient.ipc b/Services/WebContent/WebContentClient.ipc index 99f08f3e74a..7eb021a34cb 100644 --- a/Services/WebContent/WebContentClient.ipc +++ b/Services/WebContent/WebContentClient.ipc @@ -72,7 +72,8 @@ endpoint WebContentClient did_get_internal_page_info(u64 page_id, WebView::PageInfoType type, String info) =| did_change_favicon(u64 page_id, Gfx::ShareableBitmap favicon) =| - did_request_all_cookies(URL::URL url) => (Vector cookies) + did_request_all_cookies_webdriver(URL::URL url) => (Vector cookies) + did_request_all_cookies_cookiestore(URL::URL url) => (Vector cookies) did_request_named_cookie(URL::URL url, String name) => (Optional cookie) did_request_cookie(URL::URL url, Web::Cookie::Source source) => (String cookie) did_set_cookie(URL::URL url, Web::Cookie::ParsedCookie cookie, Web::Cookie::Source source) => () diff --git a/Services/WebContent/WebDriverConnection.cpp b/Services/WebContent/WebDriverConnection.cpp index 523a93f0525..d484e49ab82 100644 --- a/Services/WebContent/WebDriverConnection.cpp +++ b/Services/WebContent/WebDriverConnection.cpp @@ -2148,7 +2148,7 @@ Messages::WebDriverClient::GetAllCookiesResponse WebDriverConnection::get_all_co // 4. For each cookie in all associated cookies of the current browsing context’s active document: auto* document = current_browsing_context().active_document(); - for (auto const& cookie : current_browsing_context().page().client().page_did_request_all_cookies(document->url())) { + for (auto const& cookie : current_browsing_context().page().client().page_did_request_all_cookies_webdriver(document->url())) { // 1. Let serialized cookie be the result of serializing cookie. auto serialized_cookie = serialize_cookie(cookie); @@ -3070,7 +3070,7 @@ void WebDriverConnection::delete_cookies(Optional const& name) // For each cookie among all associated cookies of the current browsing context’s active document, un the substeps of the first matching condition: auto* document = current_browsing_context().active_document(); - for (auto& cookie : current_browsing_context().page().client().page_did_request_all_cookies(document->url())) { + for (auto& cookie : current_browsing_context().page().client().page_did_request_all_cookies_webdriver(document->url())) { // -> name is undefined // -> name is equal to cookie name if (!name.has_value() || name.value() == cookie.name) {