From 5b305ccede1a1c9dcf5a6cf81ee31fd76925f0b1 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Sun, 13 Sep 2026 20:57:49 -0700 Subject: [PATCH] Find Windows browsers with mixed-case environment names Resolve ProgramFiles and ProgramFiles(x86) case-insensitively and retain their system fallbacks even when LOCALAPPDATA is present. This lets native component capture discover the installed browser in Windows CI. AI assistance: OpenAI Codex. --- crates/browser/src/discovery.rs | 41 +++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/crates/browser/src/discovery.rs b/crates/browser/src/discovery.rs index 50513a113..774b1fa85 100644 --- a/crates/browser/src/discovery.rs +++ b/crates/browser/src/discovery.rs @@ -85,18 +85,7 @@ pub fn standard_candidates(env: &HashMap) -> Vec { "Microsoft\\Edge\\Application\\msedge.exe", "BraveSoftware\\Brave-Browser\\Application\\brave.exe", ]; - let mut roots: Vec = Vec::new(); - for key in ["PROGRAMFILES", "PROGRAMFILES(X86)", "LOCALAPPDATA"] { - if let Some(v) = env.get(key) { - if !v.is_empty() { - roots.push(PathBuf::from(v)); - } - } - } - if roots.is_empty() { - roots.push(PathBuf::from("C:\\Program Files")); - roots.push(PathBuf::from("C:\\Program Files (x86)")); - } + let roots = windows_roots(env); for r in rel { for root in &roots { out.push(root.join(r)); @@ -140,6 +129,20 @@ pub fn standard_candidates(env: &HashMap) -> Vec { out } +// Windows environment names are case-insensitive, but std::env::vars keeps +// their original casing when collected into a Rust HashMap. LOCALAPPDATA alone +// must not suppress the Program Files locations where system browsers live. +fn windows_roots(env: &HashMap) -> Vec { + let value = |name: &str| env.iter().find(|(key, value)| + key.eq_ignore_ascii_case(name) && !value.is_empty()).map(|(_, value)| value); + let mut roots = vec![ + PathBuf::from(value("ProgramFiles").map(String::as_str).unwrap_or(r"C:\Program Files")), + PathBuf::from(value("ProgramFiles(x86)").map(String::as_str).unwrap_or(r"C:\Program Files (x86)")), + ]; + if let Some(local) = value("LOCALAPPDATA") { roots.push(PathBuf::from(local)); } + roots +} + #[cfg(test)] mod tests { use super::*; @@ -162,6 +165,20 @@ mod tests { assert_eq!(find_browser(&env).unwrap(), me); } + #[test] + fn windows_roots_preserve_system_locations_with_mixed_case_environment() { + let env = HashMap::from([ + ("ProgramFiles".into(), r"D:\Apps".into()), + ("ProgramFiles(x86)".into(), r"D:\Apps32".into()), + ("LOCALAPPDATA".into(), r"C:\Users\runner\AppData\Local".into()), + ]); + assert_eq!(windows_roots(&env), vec![PathBuf::from(r"D:\Apps"), PathBuf::from(r"D:\Apps32"), PathBuf::from(r"C:\Users\runner\AppData\Local")]); + let partial = HashMap::from([("LOCALAPPDATA".into(), r"C:\Users\runner\AppData\Local".into())]); + let roots = windows_roots(&partial); + assert_eq!(roots[0], PathBuf::from(r"C:\Program Files")); + assert_eq!(roots[1], PathBuf::from(r"C:\Program Files (x86)")); + } + #[test] fn candidates_are_ordered_chrome_first() { let env = HashMap::new();