LibWeb+LibWebView: Add an internal API to expire cookies with an offset

Cookies have a minimum expiry resolution of 1 second. So to test cookie
expiration, the test had to idle for at least a second, which is quite a
noticeable delay now that LibWeb tests are parallelized.

Instead, we can add an internal API to expire cookies with a time offset
to avoid this idle delay.
This commit is contained in:
Timothy Flynn 2024-10-13 08:56:46 -04:00 committed by Andreas Kling
commit e070ed5658
Notes: github-actions[bot] 2024-10-14 06:54:34 +00:00
13 changed files with 51 additions and 24 deletions

View file

@ -16,10 +16,10 @@ SameSite=Lax: "cookie=value"
SameSite=Strict: "cookie=value" SameSite=Strict: "cookie=value"
SameSite=None: "" SameSite=None: ""
Max-Age (before expiration): "cookie-max-age1=value; cookie-max-age2=value" Max-Age (before expiration): "cookie-max-age1=value; cookie-max-age2=value"
Expires (before expiration): "cookie-expires=value; cookie-max-age1=value; cookie-max-age2=value"
Max-Age (after expiration): "cookie-max-age2=value" Max-Age (after expiration): "cookie-max-age2=value"
Expires (after expiration): ""
Max-Age in past: "" Max-Age in past: ""
Expires (before expiration): "cookie-expires=value"
Expires (after expiration): ""
Expires in past: "" Expires in past: ""
Invalid expiry (date does not exist): "cookie=value" Invalid expiry (date does not exist): "cookie=value"
Invalid expiry (missing time): "cookie=value" Invalid expiry (missing time): "cookie=value"

View file

@ -122,13 +122,12 @@
deleteCookie("cookie"); deleteCookie("cookie");
}; };
const maxAgeTest1 = () => { const maxAgeTest = () => {
document.cookie = "cookie-max-age1=value; max-age=1"; document.cookie = "cookie-max-age1=value; max-age=1";
document.cookie = `cookie-max-age2=value; max-age=${"1".repeat(1024)}`; document.cookie = `cookie-max-age2=value; max-age=${"1".repeat(1024)}`;
printCookies("Max-Age (before expiration)"); printCookies("Max-Age (before expiration)");
};
const maxAgeTest2 = () => { internals.expireCookiesWithTimeOffset(2);
printCookies("Max-Age (after expiration)"); printCookies("Max-Age (after expiration)");
deleteCookie("cookie-max-age2"); deleteCookie("cookie-max-age2");
}; };
@ -139,15 +138,14 @@
printCookies("Max-Age in past"); printCookies("Max-Age in past");
}; };
const expiresTest1 = () => { const expiresTest = () => {
let expiry = new Date(Date.now() + 1000); let expiry = new Date(Date.now() + 1000);
expiry = expiry.toUTCString(); expiry = expiry.toUTCString();
document.cookie = `cookie-expires=value; expires=${expiry}`; document.cookie = `cookie-expires=value; expires=${expiry}`;
printCookies("Expires (before expiration)"); printCookies("Expires (before expiration)");
};
const expiresTest2 = () => { internals.expireCookiesWithTimeOffset(2);
printCookies("Expires (after expiration)"); printCookies("Expires (after expiration)");
}; };
@ -179,7 +177,7 @@
deleteCookie("cookie"); deleteCookie("cookie");
}; };
asyncTest(done => { test(() => {
basicTest(); basicTest();
multipleCookiesTest(); multipleCookiesTest();
@ -200,18 +198,11 @@
publicSuffixTest(); publicSuffixTest();
sameSiteTest(); sameSiteTest();
maxAgeTest1(); maxAgeTest();
expiresTest1(); maxAgeInPastTest();
setTimeout(() => { expiresTest();
maxAgeTest2(); expiresInPastTest();
expiresTest2(); invalidExpiryTest();
maxAgeInPastTest();
expiresInPastTest();
invalidExpiryTest();
done();
}, 1200);
}); });
</script> </script>

View file

@ -184,4 +184,9 @@ void Internals::simulate_drop(double x, double y)
page.handle_drag_and_drop_event(DragEvent::Type::Drop, position, position, UIEvents::MouseButton::Primary, 0, 0, {}); page.handle_drag_and_drop_event(DragEvent::Type::Drop, position, position, UIEvents::MouseButton::Primary, 0, 0, {});
} }
void Internals::expire_cookies_with_time_offset(WebIDL::LongLong seconds)
{
internals_page().client().page_did_expire_cookies_with_time_offset(AK::Duration::from_seconds(seconds));
}
} }

View file

@ -44,6 +44,8 @@ public:
void simulate_drag_move(double x, double y); void simulate_drag_move(double x, double y);
void simulate_drop(double x, double y); void simulate_drop(double x, double y);
void expire_cookies_with_time_offset(WebIDL::LongLong seconds);
private: private:
explicit Internals(JS::Realm&); explicit Internals(JS::Realm&);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;

View file

@ -33,4 +33,6 @@ interface Internals {
undefined simulateDragStart(double x, double y, DOMString mimeType, DOMString contents); undefined simulateDragStart(double x, double y, DOMString mimeType, DOMString contents);
undefined simulateDragMove(double x, double y); undefined simulateDragMove(double x, double y);
undefined simulateDrop(double x, double y); undefined simulateDrop(double x, double y);
undefined expireCookiesWithTimeOffset(long long seconds);
}; };

View file

@ -343,6 +343,7 @@ public:
virtual String page_did_request_cookie(URL::URL const&, Cookie::Source) { 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) { } virtual void page_did_set_cookie(URL::URL const&, Cookie::ParsedCookie const&, Cookie::Source) { }
virtual void page_did_update_cookie(Web::Cookie::Cookie) { } virtual void page_did_update_cookie(Web::Cookie::Cookie) { }
virtual void page_did_expire_cookies_with_time_offset(AK::Duration) { }
virtual void page_did_update_resource_count(i32) { } virtual void page_did_update_resource_count(i32) { }
struct NewWebViewResult { struct NewWebViewResult {
JS::GCPtr<Page> page; JS::GCPtr<Page> page;

View file

@ -213,6 +213,11 @@ Optional<Web::Cookie::Cookie> CookieJar::get_named_cookie(URL::URL const& url, S
return {}; return {};
} }
void CookieJar::expire_cookies_with_time_offset(AK::Duration offset)
{
m_transient_storage.purge_expired_cookies(offset);
}
// https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.1.2 // https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.1.2
Optional<String> CookieJar::canonicalize_domain(const URL::URL& url) Optional<String> CookieJar::canonicalize_domain(const URL::URL& url)
{ {
@ -643,12 +648,19 @@ Optional<Web::Cookie::Cookie> CookieJar::TransientStorage::get_cookie(CookieStor
return m_cookies.get(key); return m_cookies.get(key);
} }
UnixDateTime CookieJar::TransientStorage::purge_expired_cookies() UnixDateTime CookieJar::TransientStorage::purge_expired_cookies(Optional<AK::Duration> offset)
{ {
auto now = UnixDateTime::now(); auto now = UnixDateTime::now();
auto is_expired = [&](auto const&, auto const& cookie) { return cookie.expiry_time < now; }; if (offset.has_value()) {
now += *offset;
for (auto& cookie : m_dirty_cookies)
cookie.value.expiry_time -= *offset;
}
auto is_expired = [&](auto const&, auto const& cookie) { return cookie.expiry_time < now; };
m_cookies.remove_all_matching(is_expired); m_cookies.remove_all_matching(is_expired);
return now; return now;
} }

View file

@ -47,7 +47,7 @@ class CookieJar {
size_t size() const { return m_cookies.size(); } size_t size() const { return m_cookies.size(); }
UnixDateTime purge_expired_cookies(); UnixDateTime purge_expired_cookies(Optional<AK::Duration> offset = {});
auto take_dirty_cookies() { return move(m_dirty_cookies); } auto take_dirty_cookies() { return move(m_dirty_cookies); }
@ -94,6 +94,7 @@ public:
Vector<Web::Cookie::Cookie> get_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(URL::URL const& url);
Optional<Web::Cookie::Cookie> get_named_cookie(URL::URL const& url, StringView name); Optional<Web::Cookie::Cookie> get_named_cookie(URL::URL const& url, StringView name);
void expire_cookies_with_time_offset(AK::Duration);
private: private:
explicit CookieJar(Optional<PersistedStorage>); explicit CookieJar(Optional<PersistedStorage>);

View file

@ -445,6 +445,11 @@ void WebContentClient::did_update_cookie(Web::Cookie::Cookie const& cookie)
Application::cookie_jar().update_cookie(cookie); Application::cookie_jar().update_cookie(cookie);
} }
void WebContentClient::did_expire_cookies_with_time_offset(AK::Duration offset)
{
Application::cookie_jar().expire_cookies_with_time_offset(offset);
}
Messages::WebContentClient::DidRequestNewWebViewResponse WebContentClient::did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab const& activate_tab, Web::HTML::WebViewHints const& hints, Optional<u64> const& page_index) Messages::WebContentClient::DidRequestNewWebViewResponse WebContentClient::did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab const& activate_tab, Web::HTML::WebViewHints const& hints, Optional<u64> const& page_index)
{ {
if (auto view = view_for_page_id(page_id); view.has_value()) { if (auto view = view_for_page_id(page_id); view.has_value()) {

View file

@ -93,6 +93,7 @@ private:
virtual Messages::WebContentClient::DidRequestCookieResponse did_request_cookie(URL::URL const&, Web::Cookie::Source) override; virtual Messages::WebContentClient::DidRequestCookieResponse did_request_cookie(URL::URL const&, Web::Cookie::Source) override;
virtual void did_set_cookie(URL::URL const&, Web::Cookie::ParsedCookie const&, Web::Cookie::Source) override; virtual void did_set_cookie(URL::URL const&, Web::Cookie::ParsedCookie const&, Web::Cookie::Source) override;
virtual void did_update_cookie(Web::Cookie::Cookie const&) override; virtual void did_update_cookie(Web::Cookie::Cookie const&) override;
virtual void did_expire_cookies_with_time_offset(AK::Duration) override;
virtual Messages::WebContentClient::DidRequestNewWebViewResponse did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab const&, Web::HTML::WebViewHints const&, Optional<u64> const& page_index) override; virtual Messages::WebContentClient::DidRequestNewWebViewResponse did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab const&, Web::HTML::WebViewHints const&, Optional<u64> const& page_index) override;
virtual void did_request_activate_tab(u64 page_id) override; virtual void did_request_activate_tab(u64 page_id) override;
virtual void did_close_browsing_context(u64 page_id) override; virtual void did_close_browsing_context(u64 page_id) override;

View file

@ -502,6 +502,11 @@ void PageClient::page_did_update_cookie(Web::Cookie::Cookie cookie)
client().async_did_update_cookie(move(cookie)); client().async_did_update_cookie(move(cookie));
} }
void PageClient::page_did_expire_cookies_with_time_offset(AK::Duration offset)
{
client().async_did_expire_cookies_with_time_offset(offset);
}
void PageClient::page_did_update_resource_count(i32 count_waiting) void PageClient::page_did_update_resource_count(i32 count_waiting)
{ {
client().async_did_update_resource_count(m_id, count_waiting); client().async_did_update_resource_count(m_id, count_waiting);

View file

@ -146,6 +146,7 @@ private:
virtual String page_did_request_cookie(URL::URL const&, Web::Cookie::Source) 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; virtual void page_did_set_cookie(URL::URL const&, Web::Cookie::ParsedCookie const&, Web::Cookie::Source) override;
virtual void page_did_update_cookie(Web::Cookie::Cookie) override; virtual void page_did_update_cookie(Web::Cookie::Cookie) override;
virtual void page_did_expire_cookies_with_time_offset(AK::Duration) override;
virtual void page_did_update_resource_count(i32) override; virtual void page_did_update_resource_count(i32) override;
virtual NewWebViewResult page_did_request_new_web_view(Web::HTML::ActivateTab, Web::HTML::WebViewHints, Web::HTML::TokenizedFeature::NoOpener) override; virtual NewWebViewResult page_did_request_new_web_view(Web::HTML::ActivateTab, Web::HTML::WebViewHints, Web::HTML::TokenizedFeature::NoOpener) override;
virtual void page_did_request_activate_tab() override; virtual void page_did_request_activate_tab() override;

View file

@ -71,6 +71,7 @@ endpoint WebContentClient
did_request_cookie(URL::URL url, Web::Cookie::Source source) => (String 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) => () did_set_cookie(URL::URL url, Web::Cookie::ParsedCookie cookie, Web::Cookie::Source source) => ()
did_update_cookie(Web::Cookie::Cookie cookie) =| did_update_cookie(Web::Cookie::Cookie cookie) =|
did_expire_cookies_with_time_offset(AK::Duration offset) =|
did_update_resource_count(u64 page_id, i32 count_waiting) =| did_update_resource_count(u64 page_id, i32 count_waiting) =|
did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab activate_tab, Web::HTML::WebViewHints hints, Optional<u64> page_index) => (String handle) did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab activate_tab, Web::HTML::WebViewHints hints, Optional<u64> page_index) => (String handle)
did_request_activate_tab(u64 page_id) =| did_request_activate_tab(u64 page_id) =|