linera-io/linera-protocol · error

we need to run in a document context

Error message

we need to run in a document context

What it means

The @linera/client wallet lock is built on the browser Web Locks API, reached through `web_sys::window()`. window() returns Option and is None outside a window/document context: Web Workers (their global object is not `window`), Node.js and other non-browser WASM runtimes, SSR, and DOM-less test runners. `.expect("we need to run in a document context")` panics as soon as try_acquire is called in such a context, before any lock request is made.

Source

Thrown at web/@linera/client/src/lock.rs:106

                            })
                        } else {
                            release.call0(&JsValue::NULL).unwrap_throw();
                            None
                        };

                        resolve
                            .call1(
                                &JsValue::NULL,
                                &serde_wasm_bindgen::to_value(&value).unwrap_throw(),
                            )
                            .unwrap_throw();

                        std::mem::forget(value);
                    })
                });

            let _: js_sys::Promise = web_sys::window()
                .expect("we need to run in a document context")
                .navigator()
                .locks()
                .request_with_options_and_callback(
                    name,
                    &options,
                    callback.as_ref().unchecked_ref(),
                );

            callback.forget();
        }))
        .await
        .unwrap_throw();

        serde_wasm_bindgen::from_value::<Option<Self>>(value)
            .unwrap_throw()
            .ok_or_else(|| Error::Contended { name: name.into() })
    }
}

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Run the locking code on the main browser thread / a real browser environment (a document context)
  2. Guard the call: check web_sys::window().is_some() (and that navigator.locks exists) and skip or use a fallback when absent
  3. In tests, run wasm-bindgen-test with a browser target (chrome) instead of node
  4. If Web Locks is unavailable, fall back to a storage/BroadcastChannel-based mutual exclusion for the wallet

Example fix

// before
let lock = Lock::try_acquire("wallet").await?; // panics in workers/node: no `window`

// after
let lock = if web_locks_available() {
    Some(Lock::try_acquire("wallet").await?)
} else {
    None // degrade gracefully outside a document context
};
Defensive patterns

Strategy: type-guard

Type guard

fn web_locks_available() -> bool {
    web_sys::window().is_some_and(|w| {
        js_sys::Reflect::has(&w.navigator(), &wasm_bindgen::JsValue::from_str("locks"))
            .unwrap_or(false)
    })
}

Prevention

When it happens

Trigger: Calling Lock::try_acquire from code running in a Web Worker; executing the client WASM under Node (e.g. wasm-bindgen-test with the nodejs target); SSR/prerendering that imports and runs the wallet module; unit tests under jsdom where window exists but navigator.locks is missing.

Common situations: Moving wallet logic into a worker for background signing; CI unit tests running under node instead of a headless browser; older browsers without navigator.locks (pre-Chrome 69 / Firefox 96 / Safari 15.4) failing the follow-up calls; SSR frameworks pulling in the browser client bundle.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/a7646d121e6cfb5c. Report an issue: GitHub.