a-b-street/abstreet · warning · anyhow::Error

no window?

Error message

no window?

What it means

update_url reads window.location.href to rewrite the page URL; in wasm it needs the global Window object. web_sys::window() returned None, so the library throws 'no window?'. web_sys::window() is only Some in a browser main-thread context.

Solutions

  1. Only call must_update_url from the browser main thread with a real DOM
  2. Guard the call with a web_sys::window().is_some() check and skip URL updates otherwise
  3. Skip URL persistence in tests/workers — the function is already #[allow(unused_variables)] for non-wasm targets

Example fix

// before
must_update_url(ctx, |url| ...);
// after
if web_sys::window().is_some() {
    must_update_url(ctx, |url| ...);
}
Defensive patterns

Strategy: type-guard

Validate before calling

if web_sys::window().is_none() {
    log::warn!("no browser window; skipping URL update");
    return;
}

Type guard

fn has_window() -> bool { web_sys::window().is_some() }

Try / catch

if let Err(e) = update_url(transform) {
    log::warn!("URL update skipped: {:?}", e);
}

Prevention

When it happens

Trigger: Calling must_update_url/update_url outside a browser window context: on a non-main thread, in a worker, in Node/tests targeting wasm32, or in headless wasm runtimes without DOM.

Common situations: Unit tests running under wasm-bindgen-test without DOM, code executing in a Web Worker, calling URL updating during early init before the DOM is ready.

Related errors


AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13). Data as JSON: /api/errors/4d8d37a76fc31979. Report an issue: GitHub.

Appendix: source

Thrown at widgetry/src/tools/url.rs:108

        // But actually, the algebra shows we don't even need window_width. Easy!
        let cam_zoom = 1.0 / horiz_meters_per_pixel;

        Some((pt, cam_zoom))
    }
}

fn must_update_url(transform: Box<dyn Fn(String) -> String>) {
    if let Err(err) = update_url(transform) {
        warn!("Couldn't update URL: {}", err);
    }
}

#[allow(unused_variables)]
fn update_url(transform: Box<dyn Fn(String) -> String>) -> Result<()> {
    #[cfg(target_arch = "wasm32")]
    {
        let window = web_sys::window().ok_or(anyhow!("no window?"))?;
        let url = window.location().href().map_err(|err| {
            anyhow!(err
                .as_string()
                .unwrap_or("window.location.href failed".to_string()))
        })?;
        let new_url = (transform)(url);

        // Setting window.location.href may seem like the obvious thing to do, but that actually
        // refreshes the page. This method just changes the URL and doesn't mess up history. See
        // https://developer.mozilla.org/en-US/docs/Web/API/History_API/Working_with_the_History_API.
        let history = window.history().map_err(|err| {
            anyhow!(err
                .as_string()
                .unwrap_or("window.history failed".to_string()))
        })?;
        history
            .replace_state_with_url(&wasm_bindgen::JsValue::NULL, "", Some(&new_url))
            .map_err(|err| {

View on GitHub (pinned to 0964f29315)