Empty-cache menu item; catch two more ways an app can notify

Right-click now offers "Empty cache and reload". Caches only - never
cookies or local storage: emptying a cache is what you do when a page is
serving something stale, and signing you out while doing it would be a
different and much less welcome feature. The reload waits for the removal
to finish, or it would refill from what was being thrown away.

Google Chat could play its own alert sound and still reach none of this,
because some apps never construct a Notification - they hand it to their
service worker registration instead. ServiceWorkerRegistration.prototype
.showNotification now routes to the same place. A worker calling it from
its own context is still out of reach; a page calling it is not.

Also shims the badge API, which is the precise form of an unread count -
a number an app states outright rather than one scraped from its title.
WKWebView does not implement it, so an app calling it was talking to
nobody. Chat's title carries no count at all, which is why no counter
ever appeared for it.

The background-apps probe now reports all three signals per app, so which
one an app actually uses is a reading rather than a guess.
This commit is contained in:
2026-09-01 15:23:46 +02:00
parent 5eb9157b7e
commit 3aa9d9c3f7
4 changed files with 127 additions and 6 deletions
+82
View File
@@ -215,6 +215,42 @@ fn handle_sentinel(
}
}
// An app stating its own unread count outright. Absolute, unlike
// the title watcher's deltas, so it replaces rather than adds.
"badge" => {
let Some(total) = params.get("n").and_then(|n| n.parse::<u32>().ok()) else {
return;
};
let state = handle.state::<crate::commands::AppState>();
let showing = state.active.lock().unwrap().clone();
let previous = {
let mut counts = state.unread.lock().unwrap();
let previous = counts.get(&from).copied().unwrap_or(0);
if total == 0 {
counts.remove(&from);
} else {
counts.insert(from.clone(), total);
}
previous
};
let _ = handle.emit("unread-changed", crate::commands::unread_list(&state));
if total > previous && showing.as_deref() != Some(from.as_str()) {
let name = state
.cfg()
.app(&from)
.map(|a| a.name.clone())
.unwrap_or_else(|| "Work".into());
let n = total - previous;
let title = if n == 1 { "1 new".to_string() } else { format!("{n} new") };
notify(&handle, &from, &name, &title, &format!("{total} unread"), String::new());
}
}
"emptycache" => {
empty_cache(&handle, &from);
}
"manage" => {
let _ = handle.emit("manage-hidden", from.clone());
}
@@ -486,6 +522,52 @@ pub fn set_corner_radius(handle: &AppHandle, app_id: &str, radius: f64) {
#[cfg(not(target_os = "macos"))]
pub fn set_corner_radius(_: &AppHandle, _: &str, _: f64) {}
/// Throws away this app's caches and reloads it.
///
/// Caches only — never cookies or local storage. Emptying a browser's cache is
/// a thing you do when a page is serving something stale; signing you out of
/// the tool while you do it would be a different and much less welcome feature.
#[cfg(target_os = "macos")]
pub fn empty_cache(handle: &AppHandle, app_id: &str) {
use block2::RcBlock;
use objc2_foundation::{MainThreadMarker, NSDate, NSSet, NSString};
use objc2_web_kit::WKWebsiteDataStore;
let Some(wv) = handle.get_webview(&label_for(app_id)) else { return };
let handle = handle.clone();
let app_id = app_id.to_string();
let _ = wv.with_webview(move |_platform| unsafe {
let Some(mtm) = MainThreadMarker::new() else { return };
let types = NSSet::from_retained_slice(&[
NSString::from_str("WKWebsiteDataTypeDiskCache"),
NSString::from_str("WKWebsiteDataTypeMemoryCache"),
NSString::from_str("WKWebsiteDataTypeOfflineWebApplicationCache"),
NSString::from_str("WKWebsiteDataTypeFetchCache"),
NSString::from_str("WKWebsiteDataTypeServiceWorkerRegistrations"),
]);
let done = RcBlock::new(move || {
// Reloaded only once the cache is actually gone, or the reload
// would refill it from what we were trying to throw away.
if let Some(wv) = handle.get_webview(&label_for(&app_id)) {
let _ = wv.eval("location.reload(true)");
}
});
WKWebsiteDataStore::defaultDataStore(mtm)
.removeDataOfTypes_modifiedSince_completionHandler(
&types,
&NSDate::distantPast(),
&done,
);
});
}
#[cfg(not(target_os = "macos"))]
pub fn empty_cache(_: &AppHandle, _: &str) {}
/// Turns on WKWebView's two-finger back and forward swipes.
///
/// wry supports it but Tauri does not expose it, so it is set on the native