{"record":{"id":"2c8801cead5cb15f","repo":"a-b-street/abstreet","slug":"window-history-replace-state-failed","errorCode":null,"errorMessage":"window.history.replace_state failed","messagePattern":"window\\.history\\.replace_state failed","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"widgetry/src/tools/url.rs","lineNumber":127,"sourceCode":"        let url = window.location().href().map_err(|err| {\n            anyhow!(err\n                .as_string()\n                .unwrap_or(\"window.location.href failed\".to_string()))\n        })?;\n        let new_url = (transform)(url);\n\n        // Setting window.location.href may seem like the obvious thing to do, but that actually\n        // refreshes the page. This method just changes the URL and doesn't mess up history. See\n        // https://developer.mozilla.org/en-US/docs/Web/API/History_API/Working_with_the_History_API.\n        let history = window.history().map_err(|err| {\n            anyhow!(err\n                .as_string()\n                .unwrap_or(\"window.history failed\".to_string()))\n        })?;\n        history\n            .replace_state_with_url(&wasm_bindgen::JsValue::NULL, \"\", Some(&new_url))\n            .map_err(|err| {\n                anyhow!(err\n                    .as_string()\n                    .unwrap_or(\"window.history.replace_state failed\".to_string()))\n            })?;\n    }\n    Ok(())\n}\n\nfn change_url_free_param(url: String, free_param: &str) -> String {\n    // The URL parsing crates I checked had lots of dependencies and didn't even expose such a nice\n    // API for doing this anyway.\n    let url_parts = url.split('?').collect::<Vec<_>>();\n    if url_parts.len() == 1 {\n        return format!(\"{}?{}\", url, free_param);\n    }\n    let mut query_params = String::new();\n    let mut found_free = false;\n    let mut first = true;\n    for x in url_parts[1].split('&') {","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/a-b-street/abstreet/blob/0964f29315820c91b171b585eb51e300164e9197/widgetry/src/tools/url.rs#L109-L145","documentation":"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.","triggerScenarios":"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.","commonSituations":"Transform producing a cross-origin URL (absolute URL on another host), serving from file:// where URL manipulation is restricted, sandboxed iframes.","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"],"exampleFix":"// before: transform that yields a cross-origin URL\nlet new_url = format!(\"https://other-host.com{}\", path);\n// after: keep it same-origin\nlet new_url = format!(\"{}?zoom={}\", path, zoom);","handlingStrategy":"validation","validationCode":"// ensure the new URL is same-origin before calling replaceState\nlet base = window.location().origin().unwrap_or_default();\nanyhow::ensure!(new_url.starts_with(&base) || new_url.starts_with('/'), \"new URL must stay same-origin: {}\", new_url);","typeGuard":null,"tryCatchPattern":"if let Err(e) = history.replace_state_with_url(&JsValue::NULL, \"\", Some(&new_url)) {\n    log::warn!(\"replaceState failed: {:?}; staying on current URL\", e.as_string());\n}","preventionTips":["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"],"tags":["wasm","browser","history-api","same-origin-policy"],"backgroundTag":"unsupported-operation","analyzedSha":"0964f29315820c91b171b585eb51e300164e9197","analyzedAt":"2026-09-13T18:02:03.421Z","contentChangedAt":"2026-09-13T18:02:03.421Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}