Fix #226: a refused Web Locks request no longer leaves the web app loading

This commit is contained in:
MarekSpodBiedry committed 2026-10-09 10:45:45 +02:00
1 parent c435d143de
commit 5fc85ee034
2 files changed
+23 -7

No files matched your search

+21 -5
View File
@@ -93,25 +93,41 @@ pub fn install_panic_hook() {
const TAB_LOCK: &str = "lightcraft-library";
/// Hold the library lock for as long as this page lives (Web Locks API). `Some(true)`: ours;
/// `Some(false)`: another tab of this site has it; `None`: the browser can't tell (no Web Locks).
/// `Some(false)`: another tab of this site has it; `None`: the browser can't tell (no Web Locks, or
/// it refused the request).
pub async fn acquire_tab_lock() -> Option<bool> {
let nav = Reflect::get(&js_sys::global(), &"navigator".into()).ok()?;
let locks = Reflect::get(&nav, &"locks".into()).ok().filter(|l| !l.is_undefined() && !l.is_null())?;
let request: js_sys::Function = Reflect::get(&locks, &"request".into()).ok()?.dyn_into().ok()?;
let opts = js_sys::Object::new();
Reflect::set(&opts, &"ifAvailable".into(), &true.into()).ok()?;
let granted = js_sys::Promise::new(&mut |resolve, _reject| {
let granted = js_sys::Promise::new(&mut |resolve, reject| {
let cb = Closure::once_into_js(move |lock: JsValue| -> JsValue {
let ours = !lock.is_null() && !lock.is_undefined();
let _ = resolve.call1(&JsValue::NULL, &ours.into());
// keep the lock until the page goes away: a promise that never settles
if ours { js_sys::Promise::new(&mut |_, _| {}).into() } else { JsValue::NULL }
});
if let Err(e) = request.call3(&locks, &TAB_LOCK.into(), &opts, &cb) {
log::warn!("Web Locks request failed: {e:?}");
// a refused request never calls `cb`: settle with its error instead of waiting forever
match request.call3(&locks, &TAB_LOCK.into(), &opts, &cb) {
Ok(pending) => {
let catch = Reflect::get(&pending, &"catch".into()).ok().and_then(|c| c.dyn_into::<js_sys::Function>().ok());
if let Some(catch) = catch {
let _ = catch.call1(&pending, &reject);
}
}
Err(e) => {
let _ = reject.call1(&JsValue::NULL, &e);
}
}
});
JsFuture::from(granted).await.ok().map(|v| v.is_truthy())
match JsFuture::from(granted).await {
Ok(v) => Some(v.is_truthy()),
Err(e) => {
log::warn!("Web Locks request failed, starting without the one-tab guard: {e:?}");
None
}
}
}
/// Offer a blob as a download named `name`.
+2 -2
View File
@@ -122,8 +122,8 @@ are the same in every version, so serve them with `Cache-Control: no-cache` (not
a reload); a notice says why.
- One tab at a time: the page holds a Web Lock (`navigator.locks`, `lightcraft-library`); a
second tab or window shows "LightCraft is already open in another tab" instead of loading
its own copy (two copies would overwrite each other's saves). Browsers without Web Locks
aren't protected.
its own copy (two copies would overwrite each other's saves). Browsers without Web Locks, or
that refuse the lock request, aren't protected: the app starts without the guard.
- If the stored library can't be opened, a notice says the session is temporary and nothing
is saved; the stored library is left as it was.
- A panic (wasm is built with `panic = "abort"`) replaces the page with a message saying the