{"record":{"id":"5b599a7cef05fa45","repo":"a-b-street/abstreet","slug":"window-history-failed","errorCode":null,"errorMessage":"window.history failed","messagePattern":"window\\.history failed","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"widgetry/src/tools/url.rs","lineNumber":120,"sourceCode":"}\n\n#[allow(unused_variables)]\nfn update_url(transform: Box<dyn Fn(String) -> String>) -> Result<()> {\n    #[cfg(target_arch = \"wasm32\")]\n    {\n        let window = web_sys::window().ok_or(anyhow!(\"no window?\"))?;\n        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<_>>();","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/a-b-street/abstreet/blob/0964f29315820c91b171b585eb51e300164e9197/widgetry/src/tools/url.rs#L102-L138","documentation":"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.","triggerScenarios":"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.","commonSituations":"Sandboxed iframe without allow-same-origin, restricted webview environments, browser extensions stripping History API.","solutions":["Remove restrictive sandbox attributes on the containing iframe","Test in a standard browser to confirm History API works","Catch and degrade gracefully: skip URL rewriting when history is unavailable"],"exampleFix":"// before: unconditional URL update\nmust_update_url(ctx, transform);\n// after: tolerate missing history\nif web_sys::window().map(|w| w.history().is_ok()).unwrap_or(false) {\n    must_update_url(ctx, transform);\n}","handlingStrategy":"fallback","validationCode":"let history_ok = web_sys::window().map(|w| w.history().is_ok()).unwrap_or(false);\nif !history_ok { log::warn!(\"History API unavailable; skipping URL update\"); }","typeGuard":"fn history_available(w: &web_sys::Window) -> bool { w.history().is_ok() }","tryCatchPattern":"match window.history() {\n    Ok(h) => { /* replace_state flow */ }\n    Err(e) => log::warn!(\"history unavailable: {:?}; URL not updated\", e.as_string()),\n}","preventionTips":["Avoid fully sandboxed iframes if the app needs URL rewriting","Treat History API as optional; make URL updates non-fatal","Verify History API support in embedded webviews used for testing"],"tags":["wasm","browser","history-api","web-sys"],"backgroundTag":"unsupported-operation","analyzedSha":"0964f29315820c91b171b585eb51e300164e9197","analyzedAt":"2026-09-13T18:02:03.421Z","contentChangedAt":"2026-09-13T18:02:03.421Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}