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

window.history failed

Error message

window.history failed

What it means

update_url calls window.history() to obtain the History object for pushState-style URL rewriting. If the JS call throws, this error is thrown with the JS message or the 'window.history failed' fallback.

Solutions

  1. Remove restrictive sandbox attributes on the containing iframe
  2. Test in a standard browser to confirm History API works
  3. Catch and degrade gracefully: skip URL rewriting when history is unavailable

Example fix

// before: unconditional URL update
must_update_url(ctx, transform);
// after: tolerate missing history
if web_sys::window().map(|w| w.history().is_ok()).unwrap_or(false) {
    must_update_url(ctx, transform);
}
Defensive patterns

Strategy: fallback

Validate before calling

let history_ok = web_sys::window().map(|w| w.history().is_ok()).unwrap_or(false);
if !history_ok { log::warn!("History API unavailable; skipping URL update"); }

Type guard

fn history_available(w: &web_sys::Window) -> bool { w.history().is_ok() }

Try / catch

match window.history() {
    Ok(h) => { /* replace_state flow */ }
    Err(e) => log::warn!("history unavailable: {:?}; URL not updated", e.as_string()),
}

Prevention

When it happens

Trigger: window.history() throwing: rare, but occurs in sandboxed iframes where the History API is restricted, or in non-standard/embedded webviews lacking a History implementation.

Common situations: Sandboxed iframe without allow-same-origin, restricted webview environments, browser extensions stripping History API.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

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

}

#[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| {
                anyhow!(err
                    .as_string()
                    .unwrap_or("window.history.replace_state failed".to_string()))
            })?;
    }
    Ok(())
}

fn change_url_free_param(url: String, free_param: &str) -> String {
    // The URL parsing crates I checked had lots of dependencies and didn't even expose such a nice
    // API for doing this anyway.
    let url_parts = url.split('?').collect::<Vec<_>>();

View on GitHub (pinned to 0964f29315)