a-b-street/abstreet · warning · anyhow::Error
window.history.replace_state failed
Error message
window.history.replace_state failed
What it means
With the History object in hand, update_url calls replace_state_with_url to swap the URL without a page refresh. A JS exception from replaceState (commonly SecurityError) becomes this error via the 'window.history.replace_state failed' fallback string.
Solutions
- Ensure the transformed URL stays same-origin (keep path/query changes only, no host change)
- Inspect the transformed URL string for accidental absolute/cross-origin prefixes
- Serve over http(s) instead of file://
- Catch the error and log instead of failing the whole operation
Example fix
// before: transform that yields a cross-origin URL
let new_url = format!("https://other-host.com{}", path);
// after: keep it same-origin
let new_url = format!("{}?zoom={}", path, zoom); Defensive patterns
Strategy: validation
Validate before calling
// ensure the new URL is same-origin before calling replaceState
let base = window.location().origin().unwrap_or_default();
anyhow::ensure!(new_url.starts_with(&base) || new_url.starts_with('/'), "new URL must stay same-origin: {}", new_url); Try / catch
if let Err(e) = history.replace_state_with_url(&JsValue::NULL, "", Some(&new_url)) {
log::warn!("replaceState failed: {:?}; staying on current URL", e.as_string());
} Prevention
- Keep URL transforms path/query-only — never introduce a different host
- Test URL rewriting under the same origin policy of your deployment
- Prefer relative URLs in the transform closure
- Serve over http(s), not file://, where replaceState is restricted
When it happens
Trigger: history.replace_state_with_url throws: the new_url is not same-origin with the current document (SecurityError), or the browser refuses the state/URL argument.
Common situations: Transform producing a cross-origin URL (absolute URL on another host), serving from file:// where URL manipulation is restricted, sandboxed iframes.
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/2c8801cead5cb15f.
Report an issue: GitHub.
Appendix: source
Thrown at widgetry/src/tools/url.rs:127
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<_>>();
if url_parts.len() == 1 {
return format!("{}?{}", url, free_param);
}
let mut query_params = String::new();
let mut found_free = false;
let mut first = true;
for x in url_parts[1].split('&') {View on GitHub (pinned to 0964f29315)