web browser (Servo)
git clone https://git.lucas.co/cce-browser.git
fix: persist cookies on disk in the WPE backend
A persistent WebKitNetworkSession persists website data (localStorage,
IndexedDB, service workers) on its own, but the cookie store stays
memory-only until webkit_cookie_manager_set_persistent_storage() names a
file — so every launch started logged out of every cookie-backed site
(Google), while token-in-localStorage logins (Cloudflare) survived and
disguised the gap. Cookies now live in profile/cookies.sqlite, 0700 dir.
Also wire the clear_cookies relay in pump(): cce://cookies/clear set the
flag but nothing on the WPE side ever consumed it. It now clears the
session's cookies via the website data manager, mirroring ServoHost.
Verified in a shadow session: stable Google cookies (AEC,
SEARCH_SAMESITE) keep their values across a browser restart, proving the
jar is read back and sent, not just written.
Co-Authored-By: Claude Fable 5 <[email protected]>
WPE-PORT.md | 13 ++++++++++---
src/wpe/host.rs | 35 +++++++++++++++++++++++++++++++----
2 files changed, 41 insertions(+), 7 deletions(-)
diff --git a/WPE-PORT.md b/WPE-PORT.md
index 1f606e4..87ae6ce 100644
--- a/WPE-PORT.md
+++ b/WPE-PORT.md
@@ -278,9 +278,16 @@ been served one), but it is a far higher bar than the marketing page that earlie
used: a heavy JS application behind Cloudflare's own protection, reached through a
login. The engine-identity worry that motivated half this document has not materialised.
-**Cookies persist.** The login survives in WebKit's own origin-keyed store under
-`~/.local/state/cce/browser/profile/storage`. (`cookie_jar.json` beside it is Servo's
-format, now dead weight.)
+**Website data persists — but cookies did not, at first.** What survived restarts
+in the early days was WebKit's origin-keyed store under
+`~/.local/state/cce/browser/profile/storage` (localStorage, IndexedDB, service
+workers), which a persistent `WebKitNetworkSession` writes on its own — and which
+made this section originally claim "cookies persist". They didn't: WebKit's cookie
+store is memory-only until `webkit_cookie_manager_set_persistent_storage()` names a
+file, so cookie-backed logins (Google) evaporated with the process while
+token-in-localStorage logins (Cloudflare) survived, disguising the gap. Fixed
+2026-09-01: cookies now live in `profile/cookies.sqlite`. (`cookie_jar.json` beside
+it is Servo's format, now dead weight.)
**Three bugs found by use, none by testing:**
diff --git a/src/wpe/host.rs b/src/wpe/host.rs
index 253a396..71321ad 100644
--- a/src/wpe/host.rs
+++ b/src/wpe/host.rs
@@ -176,10 +176,13 @@ impl WebKitHost {
"wpe_display_connect failed"
);
- // Persisted profile: without a data directory WebKit keeps cookies
- // in memory only, so every launch starts logged out of every site.
- // Same location and the same 0700 reasoning as the Servo backend —
- // the jar holds live sessions.
+ // Persisted profile. The data directory persists website data
+ // (localStorage, IndexedDB, service workers) on its own, but the
+ // cookie store stays memory-only until it is explicitly given a
+ // file — the set_persistent_storage call below, without which
+ // every launch starts logged out of every site even though the
+ // rest of the profile survives. Same location and the same 0700
+ // reasoning as the Servo backend — the jar holds live sessions.
let profile = crate::pages::state_dir().join("profile");
let _ = std::fs::create_dir_all(&profile);
{
@@ -191,6 +194,12 @@ impl WebKitHost {
cstr(&profile.join("cache").to_string_lossy()),
);
let session = webkit_network_session_new(data_dir.as_ptr(), cache_dir.as_ptr());
+ let cookie_db = cstr(&profile.join("cookies.sqlite").to_string_lossy());
+ webkit_cookie_manager_set_persistent_storage(
+ webkit_network_session_get_cookie_manager(session),
+ cookie_db.as_ptr(),
+ WebKitCookiePersistentStorage::WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE,
+ );
let history = std::sync::Arc::new(crate::pages::History::load());
let bookmarks = std::sync::Arc::new(crate::pages::Bookmarks::load());
@@ -487,6 +496,24 @@ impl WebKitHost {
if let Some(p) = &self.poll {
p.drain();
}
+ // `cce://cookies/clear` runs on WebKit's fetch path and cannot reach
+ // the session from there, so it sets the flag and this acts on it —
+ // the same relay `ServoHost::pump` uses. Timespan 0 clears them all.
+ if self
+ .clear_cookies
+ .swap(false, std::sync::atomic::Ordering::SeqCst)
+ {
+ unsafe {
+ webkit_website_data_manager_clear(
+ webkit_network_session_get_website_data_manager(self.session),
+ WebKitWebsiteDataTypes::WEBKIT_WEBSITE_DATA_COOKIES,
+ 0,
+ std::ptr::null_mut(),
+ None,
+ std::ptr::null_mut(),
+ );
+ }
+ }
unsafe {
while g_main_context_iteration(std::ptr::null_mut(), 0) != 0 {}
}