LibWeb: Implement CookieStore::get(name)

This commit is contained in:
Idan Horowitz 2025-08-05 23:05:45 +03:00 committed by Tim Flynn
commit 5545d38d7a
Notes: github-actions[bot] 2025-08-08 17:12:25 +00:00
14 changed files with 174 additions and 17 deletions

View file

@ -7,13 +7,20 @@
#include <LibWeb/Bindings/CookieStorePrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CookieStore/CookieStore.h>
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
#include <LibWeb/WebIDL/DOMException.h>
#include <LibWeb/WebIDL/Promise.h>
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 cookies name.
// 2. Let value be the result of running UTF-8 decode without BOM on cookies value.
// 3. Return «[ "name" → name, "value" → value ]»
return CookieListItem {
.name = cookie.name,
.value = cookie.value,
};
}
// https://cookiestore.spec.whatwg.org/#query-cookies
static Vector<CookieListItem> query_cookies(PageClient& client, URL::URL const& url, Optional<String> 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<CookieListItem> list;
// 3. For each cookie in cookie-list, run these steps:
for (auto const& cookie : cookie_list) {
// 1. Assert: cookies 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 cookies 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<WebIDL::Promise> CookieStore::get(String name)
{
auto& realm = this->realm();
// 1. Let settings be thiss relevant settings object.
auto const& settings = HTML::relevant_settings_object(*this);
// 2. Let origin be settingss 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 settingss 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;
}
}

View file

@ -6,19 +6,40 @@
#pragma once
#include <AK/Optional.h>
#include <AK/String.h>
#include <LibWeb/Bindings/CookieStorePrototype.h>
#include <LibWeb/DOM/EventTarget.h>
namespace Web::CookieStore {
// https://cookiestore.spec.whatwg.org/#dictdef-cookielistitem
struct CookieListItem {
Optional<String> name;
Optional<String> 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<WebIDL::Promise> 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<PageClient> m_client;
};
}
namespace Web::Bindings {
JS::Value cookie_list_item_to_value(JS::Realm&, CookieStore::CookieListItem const&);
}

View file

@ -3,10 +3,16 @@
#import <HTML/Window.idl>
#import <ServiceWorker/ServiceWorkerGlobalScope.idl>
[GenerateToValue]
dictionary CookieListItem {
USVString name;
USVString value;
};
// https://cookiestore.spec.whatwg.org/#cookiestore
[Exposed=(ServiceWorker,Window), SecureContext]
interface CookieStore : EventTarget {
Promise<CookieListItem?> get(USVString name);
};
// https://cookiestore.spec.whatwg.org/#Window

View file

@ -1130,7 +1130,7 @@ GC::Ref<CookieStore::CookieStore> Window::cookie_store()
// The cookieStore getter steps are to return thiss associated CookieStore.
if (!m_cookie_store)
m_cookie_store = realm.create<CookieStore::CookieStore>(realm);
m_cookie_store = realm.create<CookieStore::CookieStore>(realm, page().client());
return *m_cookie_store;
}

View file

@ -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<Web::Cookie::Cookie> page_did_request_all_cookies(URL::URL const&) { return {}; }
virtual Vector<Web::Cookie::Cookie> page_did_request_all_cookies_webdriver(URL::URL const&) { return {}; }
virtual Vector<Web::Cookie::Cookie> page_did_request_all_cookies_cookiestore(URL::URL const&) { return {}; }
virtual Optional<Web::Cookie::Cookie> 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) { }

View file

@ -93,7 +93,7 @@ GC::Ref<CookieStore::CookieStore> ServiceWorkerGlobalScope::cookie_store()
// The cookieStore getter steps are to return thiss associated CookieStore.
if (!m_cookie_store)
m_cookie_store = realm.create<CookieStore::CookieStore>(realm);
m_cookie_store = realm.create<CookieStore::CookieStore>(realm, page()->client());
return *m_cookie_store;
}

View file

@ -192,7 +192,7 @@ Vector<Web::Cookie::Cookie> CookieJar::get_all_cookies()
}
// https://w3c.github.io/webdriver/#dfn-associated-cookies
Vector<Web::Cookie::Cookie> CookieJar::get_all_cookies(URL::URL const& url)
Vector<Web::Cookie::Cookie> CookieJar::get_all_cookies_webdriver(URL::URL const& url)
{
auto domain = canonicalize_domain(url);
if (!domain.has_value())
@ -201,6 +201,15 @@ Vector<Web::Cookie::Cookie> CookieJar::get_all_cookies(URL::URL const& url)
return get_matching_cookies(url, domain.value(), Web::Cookie::Source::Http, MatchingCookiesSpecMode::WebDriver);
}
Vector<Web::Cookie::Cookie> 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<Web::Cookie::Cookie> CookieJar::get_named_cookie(URL::URL const& url, StringView name)
{
auto domain = canonicalize_domain(url);

View file

@ -93,7 +93,8 @@ public:
void dump_cookies();
void clear_all_cookies();
Vector<Web::Cookie::Cookie> get_all_cookies();
Vector<Web::Cookie::Cookie> get_all_cookies(URL::URL const& url);
Vector<Web::Cookie::Cookie> get_all_cookies_webdriver(URL::URL const& url);
Vector<Web::Cookie::Cookie> get_all_cookies_cookiestore(URL::URL const& url);
Optional<Web::Cookie::Cookie> get_named_cookie(URL::URL const& url, StringView name);
void expire_cookies_with_time_offset(AK::Duration);

View file

@ -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)

View file

@ -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;

View file

@ -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<Web::Cookie::Cookie> PageClient::page_did_request_all_cookies(URL::URL const& url)
Vector<Web::Cookie::Cookie> 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<Web::Cookie::Cookie> PageClient::page_did_request_all_cookies_cookiestore(URL::URL const& url)
{
return client().did_request_all_cookies_cookiestore(url);
}
Optional<Web::Cookie::Cookie> PageClient::page_did_request_named_cookie(URL::URL const& url, String const& name)

View file

@ -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<Web::Cookie::Cookie> page_did_request_all_cookies(URL::URL const&) override;
virtual Vector<Web::Cookie::Cookie> page_did_request_all_cookies_webdriver(URL::URL const&) override;
virtual Vector<Web::Cookie::Cookie> page_did_request_all_cookies_cookiestore(URL::URL const&) override;
virtual Optional<Web::Cookie::Cookie> 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;

View file

@ -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<Web::Cookie::Cookie> cookies)
did_request_all_cookies_webdriver(URL::URL url) => (Vector<Web::Cookie::Cookie> cookies)
did_request_all_cookies_cookiestore(URL::URL url) => (Vector<Web::Cookie::Cookie> cookies)
did_request_named_cookie(URL::URL url, String name) => (Optional<Web::Cookie::Cookie> 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) => ()

View file

@ -2148,7 +2148,7 @@ Messages::WebDriverClient::GetAllCookiesResponse WebDriverConnection::get_all_co
// 4. For each cookie in all associated cookies of the current browsing contexts 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<StringView> const& name)
// For each cookie among all associated cookies of the current browsing contexts 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) {