mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-27 20:58:16 +00:00
LibWeb+LibWebView: Implement emitting CookieChangeEvents
Some checks are pending
CI / macOS, arm64, Sanitizer, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer, Clang (push) Waiting to run
Package the js repl as a binary artifact / Linux, arm64 (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run
Some checks are pending
CI / macOS, arm64, Sanitizer, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer, Clang (push) Waiting to run
Package the js repl as a binary artifact / Linux, arm64 (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run
This commit is contained in:
parent
a72a0b3c2d
commit
81e3afd1fd
Notes:
github-actions[bot]
2025-08-08 17:11:07 +00:00
Author: https://github.com/IdanHo
Commit: 81e3afd1fd
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5766
Reviewed-by: https://github.com/trflynn89 ✅
7 changed files with 213 additions and 1 deletions
|
@ -9,6 +9,7 @@
|
|||
#include <LibWeb/Bindings/CookieStorePrototype.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Cookie/ParsedCookie.h>
|
||||
#include <LibWeb/CookieStore/CookieChangeEvent.h>
|
||||
#include <LibWeb/CookieStore/CookieStore.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
|
||||
|
@ -671,4 +672,171 @@ GC::Ref<WebIDL::Promise> CookieStore::delete_(CookieStoreDeleteOptions const& op
|
|||
return promise;
|
||||
}
|
||||
|
||||
void CookieStore::set_onchange(WebIDL::CallbackType* event_handler)
|
||||
{
|
||||
set_event_handler_attribute(HTML::EventNames::change, event_handler);
|
||||
}
|
||||
|
||||
WebIDL::CallbackType* CookieStore::onchange()
|
||||
{
|
||||
return event_handler_attribute(HTML::EventNames::change);
|
||||
}
|
||||
|
||||
// https://cookiestore.spec.whatwg.org/#cookie-change
|
||||
struct CookieChange {
|
||||
enum class Type {
|
||||
Changed,
|
||||
Deleted,
|
||||
};
|
||||
|
||||
Cookie::Cookie cookie;
|
||||
Type type;
|
||||
};
|
||||
|
||||
// https://cookiestore.spec.whatwg.org/#observable-changes
|
||||
static Vector<CookieChange> observable_changes(URL::URL const& url, Vector<Cookie::Cookie> const& changes)
|
||||
{
|
||||
// The observable changes for url are the set of cookie changes to cookies in a cookie store which meet the
|
||||
// requirements in step 1 of Cookies § Retrieval Algorithm’s steps to compute the "cookie-string from a given
|
||||
// cookie store" with url as request-uri, for a "non-HTTP" API.
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-14#name-retrieval-algorithm
|
||||
auto canonicalized_domain = Cookie::canonicalize_domain(url);
|
||||
if (!canonicalized_domain.has_value())
|
||||
return {};
|
||||
|
||||
// FIXME: The retrieval's same-site status is "same-site" if the Document's "site for cookies" is same-site with the
|
||||
// top-level origin as defined in Section 5.2.1 (otherwise it is "cross-site"), and the retrieval's type is "non-HTTP".
|
||||
auto is_same_site_retrieval = true;
|
||||
|
||||
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<CookieChange> observable_changes;
|
||||
for (auto const& cookie : changes) {
|
||||
// * Either:
|
||||
// The cookie's host-only-flag is true and the canonicalized host of the retrieval's URI is identical to
|
||||
// the cookie's domain.
|
||||
bool is_host_only_and_has_identical_domain = cookie.host_only && (canonicalized_domain.value() == cookie.domain);
|
||||
// Or:
|
||||
// The cookie's host-only-flag is false and the canonicalized host of the retrieval's URI domain-matches
|
||||
// the cookie's domain.
|
||||
bool is_not_host_only_and_domain_matches = !cookie.host_only && Web::Cookie::domain_matches(canonicalized_domain.value(), cookie.domain);
|
||||
|
||||
if (!is_host_only_and_has_identical_domain && !is_not_host_only_and_domain_matches)
|
||||
continue;
|
||||
|
||||
// * The retrieval's URI's path path-matches the cookie's path.
|
||||
if (!Cookie::path_matches(url.serialize_path(), cookie.path))
|
||||
continue;
|
||||
|
||||
// * If the cookie's secure-only-flag is true, then the retrieval's URI must denote a "secure" connection (as
|
||||
// defined by the user agent).
|
||||
if (cookie.secure && url.scheme() != "https"sv && url.scheme() != "wss"sv)
|
||||
continue;
|
||||
|
||||
// * If the cookie's http-only-flag is true, then exclude the cookie if the retrieval's type is "non-HTTP".
|
||||
if (cookie.http_only)
|
||||
continue;
|
||||
|
||||
// * If the cookie's same-site-flag is not "None" and the retrieval's same-site status is "cross-site", then
|
||||
// exclude the cookie unless all of the following conditions are met:
|
||||
// * The retrieval's type is "HTTP".
|
||||
// * The same-site-flag is "Lax" or "Default".
|
||||
// * The HTTP request associated with the retrieval uses a "safe" method.
|
||||
// * The target browsing context of the HTTP request associated with the retrieval is the active browsing context
|
||||
// or a top-level traversable.
|
||||
if (cookie.same_site != Cookie::SameSite::None && !is_same_site_retrieval)
|
||||
continue;
|
||||
|
||||
// A cookie change is a cookie and a type (either changed or deleted):
|
||||
// - A cookie which is removed due to an insertion of another cookie with the same name, domain, and path is ignored.
|
||||
// - A newly-created cookie which is not immediately evicted is considered changed.
|
||||
// - A newly-created cookie which is immediately evicted is considered deleted.
|
||||
// - A cookie which is otherwise evicted or removed is considered deleted
|
||||
observable_changes.append({ cookie, cookie.expiry_time < now ? CookieChange::Type::Deleted : CookieChange::Type::Changed });
|
||||
}
|
||||
|
||||
return observable_changes;
|
||||
}
|
||||
|
||||
struct PreparedLists {
|
||||
Vector<CookieListItem> changed_list;
|
||||
Vector<CookieListItem> deleted_list;
|
||||
};
|
||||
|
||||
// https://cookiestore.spec.whatwg.org/#prepare-lists
|
||||
static PreparedLists prepare_lists(Vector<CookieChange> const& changes)
|
||||
{
|
||||
// 1. Let changedList be a new list.
|
||||
Vector<CookieListItem> changed_list;
|
||||
|
||||
// 2. Let deletedList be a new list.
|
||||
Vector<CookieListItem> deleted_list;
|
||||
|
||||
// 3. For each change in changes, run these steps:
|
||||
for (auto const& change : changes) {
|
||||
// 1. Let item be the result of running create a CookieListItem from change’s cookie.
|
||||
auto item = create_a_cookie_list_item(change.cookie);
|
||||
|
||||
// 2. If change’s type is changed, then append item to changedList.
|
||||
if (change.type == CookieChange::Type::Changed)
|
||||
changed_list.append(move(item));
|
||||
|
||||
// 3. Otherwise, run these steps:
|
||||
else {
|
||||
// 1. Set item["value"] to undefined.
|
||||
item.value.clear();
|
||||
|
||||
// 2. Append item to deletedList.
|
||||
deleted_list.append(move(item));
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Return changedList and deletedList.
|
||||
return { move(changed_list), move(deleted_list) };
|
||||
}
|
||||
|
||||
// https://cookiestore.spec.whatwg.org/#process-cookie-changes
|
||||
void CookieStore::process_cookie_changes(Vector<Cookie::Cookie> const& all_changes)
|
||||
{
|
||||
auto& realm = this->realm();
|
||||
|
||||
// 1. Let url be window’s relevant settings object’s creation URL.
|
||||
auto url = HTML::relevant_settings_object(*this).creation_url;
|
||||
|
||||
// 2. Let changes be the observable changes for url.
|
||||
auto changes = observable_changes(url, all_changes);
|
||||
|
||||
// 3. If changes is empty, then continue.
|
||||
if (changes.is_empty())
|
||||
return;
|
||||
|
||||
// 4. Queue a global task on the DOM manipulation task source given window to fire a change event named "change"
|
||||
// with changes at window’s CookieStore.
|
||||
queue_global_task(HTML::Task::Source::DOMManipulation, realm.global_object(), GC::create_function(realm.heap(), [this, &realm, changes = move(changes)]() {
|
||||
HTML::TemporaryExecutionContext execution_context { realm };
|
||||
// https://cookiestore.spec.whatwg.org/#fire-a-change-event
|
||||
// 4. Let changedList and deletedList be the result of running prepare lists from changes.
|
||||
auto [changed_list, deleted_list] = prepare_lists(changes);
|
||||
|
||||
CookieChangeEventInit event_init = {};
|
||||
// 5. Set event’s changed attribute to changedList.
|
||||
event_init.changed = move(changed_list);
|
||||
|
||||
// 6. Set event’s deleted attribute to deletedList.
|
||||
event_init.deleted = move(deleted_list);
|
||||
|
||||
// 1. Let event be the result of creating an Event using CookieChangeEvent.
|
||||
// 2. Set event’s type attribute to type.
|
||||
auto event = CookieChangeEvent::create(realm, HTML::EventNames::change, event_init);
|
||||
|
||||
// 3. Set event’s bubbles and cancelable attributes to false.
|
||||
event->set_bubbles(false);
|
||||
event->set_cancelable(false);
|
||||
|
||||
// 7. Dispatch event at target.
|
||||
this->dispatch_event(event);
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -63,6 +63,11 @@ public:
|
|||
GC::Ref<WebIDL::Promise> delete_(String name);
|
||||
GC::Ref<WebIDL::Promise> delete_(CookieStoreDeleteOptions const&);
|
||||
|
||||
void set_onchange(WebIDL::CallbackType*);
|
||||
WebIDL::CallbackType* onchange();
|
||||
|
||||
void process_cookie_changes(Vector<Cookie::Cookie> const&);
|
||||
|
||||
private:
|
||||
CookieStore(JS::Realm&, PageClient&);
|
||||
|
||||
|
|
|
@ -53,6 +53,8 @@ interface CookieStore : EventTarget {
|
|||
|
||||
Promise<undefined> delete(USVString name);
|
||||
Promise<undefined> delete(CookieStoreDeleteOptions options);
|
||||
|
||||
[Exposed=Window] attribute EventHandler onchange;
|
||||
};
|
||||
|
||||
// https://cookiestore.spec.whatwg.org/#Window
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/Cookie/ParsedCookie.h>
|
||||
#include <LibWebView/CookieJar.h>
|
||||
#include <LibWebView/WebContentClient.h>
|
||||
|
||||
namespace WebView {
|
||||
|
||||
|
@ -585,9 +586,25 @@ void CookieJar::TransientStorage::set_cookies(Cookies cookies)
|
|||
purge_expired_cookies();
|
||||
}
|
||||
|
||||
static void notify_cookies_changed(Vector<Web::Cookie::Cookie> cookies)
|
||||
{
|
||||
WebContentClient::for_each_client([&](WebContentClient& client) {
|
||||
client.async_cookies_changed(move(cookies));
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
void CookieJar::TransientStorage::set_cookie(CookieStorageKey key, Web::Cookie::Cookie cookie)
|
||||
{
|
||||
auto now = UnixDateTime::now();
|
||||
// AD-HOC: Skip adding immediately-expiring cookies (i.e., only allow updating to immediately-expiring) to prevent firing deletion events for them
|
||||
// Spec issue: https://github.com/whatwg/cookiestore/issues/282
|
||||
if (cookie.expiry_time < now && !m_cookies.contains(key))
|
||||
return;
|
||||
m_cookies.set(key, cookie);
|
||||
// We skip notifying about updating expired cookies, as they will be notified as being expired immediately after instead
|
||||
if (cookie.expiry_time >= now)
|
||||
notify_cookies_changed({ cookie });
|
||||
m_dirty_cookies.set(move(key), move(cookie));
|
||||
}
|
||||
|
||||
|
@ -607,7 +624,14 @@ UnixDateTime CookieJar::TransientStorage::purge_expired_cookies(Optional<AK::Dur
|
|||
}
|
||||
|
||||
auto is_expired = [&](auto const&, auto const& cookie) { return cookie.expiry_time < now; };
|
||||
m_cookies.remove_all_matching(is_expired);
|
||||
auto removed_entries = m_cookies.take_all_matching(is_expired);
|
||||
if (!removed_entries.is_empty()) {
|
||||
Vector<Web::Cookie::Cookie> removed_cookies;
|
||||
removed_cookies.ensure_capacity(removed_entries.size());
|
||||
for (auto const& entry : removed_entries)
|
||||
removed_cookies.unchecked_append(move(entry.value));
|
||||
notify_cookies_changed(move(removed_cookies));
|
||||
}
|
||||
|
||||
return now;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <LibWeb/CSS/ComputedProperties.h>
|
||||
#include <LibWeb/CSS/Parser/ErrorReporter.h>
|
||||
#include <LibWeb/CSS/StyleComputer.h>
|
||||
#include <LibWeb/CookieStore/CookieStore.h>
|
||||
#include <LibWeb/DOM/Attr.h>
|
||||
#include <LibWeb/DOM/CharacterData.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
|
@ -1314,4 +1315,14 @@ void ConnectionFromClient::system_time_zone_changed()
|
|||
Unicode::clear_system_time_zone_cache();
|
||||
}
|
||||
|
||||
void ConnectionFromClient::cookies_changed(Vector<Web::Cookie::Cookie> cookies)
|
||||
{
|
||||
for (auto& navigable : Web::HTML::all_navigables()) {
|
||||
auto window = navigable->active_window();
|
||||
if (!window)
|
||||
return;
|
||||
window->cookie_store()->process_cookie_changes(cookies);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -156,6 +156,7 @@ private:
|
|||
virtual void paste(u64 page_id, String text) override;
|
||||
|
||||
virtual void system_time_zone_changed() override;
|
||||
virtual void cookies_changed(Vector<Web::Cookie::Cookie>) override;
|
||||
|
||||
NonnullOwnPtr<PageHost> m_page_host;
|
||||
|
||||
|
|
|
@ -128,4 +128,5 @@ endpoint WebContentServer
|
|||
set_user_style(u64 page_id, String source) =|
|
||||
|
||||
system_time_zone_changed() =|
|
||||
cookies_changed(Vector<Web::Cookie::Cookie> cookies) =|
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue