From bb92e08fdcedffa6de61b6a46eeddeb8be6e2731 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 6 Mar 2026 14:58:18 -0600 Subject: [PATCH] Fix Chrome extensions not loading by forcing headed mode when extensions present (#652) * Fix Chrome extensions not loading by forcing headed mode when extensions present Fixes #640 * Restore wait_or_kill() and add tests for headless+extensions logic Restore the ChromeProcess::wait_or_kill() method that was accidentally removed. It is still referenced by BrowserProcess in browser.rs and is needed for graceful shutdown / cookie persistence (PR #650). Add unit tests verifying --headless=new is omitted when extensions are present. Co-Authored-By: Claude Opus 4.6 * Fix window-size leak in headed+extensions mode and remove unused channel option - Skip --window-size=1280,720 when extensions force headed mode (native) - Remove unexplained channel: 'chromium' from extensions launch path (TS) - Add window-size assertion to existing extension test Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: ctate <366502+ctate@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (cherry picked from commit 68cebe5192515844136284d0d11ef9fabfbe123a) --- cli/src/native/cdp/chrome.rs | 57 ++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/cli/src/native/cdp/chrome.rs b/cli/src/native/cdp/chrome.rs index 78570ef..8b49359 100644 --- a/cli/src/native/cdp/chrome.rs +++ b/cli/src/native/cdp/chrome.rs @@ -102,7 +102,14 @@ fn build_chrome_args(options: &LaunchOptions) -> Result { "--use-mock-keychain".to_string(), ]; - if options.headless { + let has_extensions = options + .extensions + .as_ref() + .map_or(false, |exts| !exts.is_empty()); + + // Extensions require headed mode in native Chrome (content scripts are not + // injected in headless mode). Skip --headless when extensions are loaded. + if options.headless && !has_extensions { args.push("--headless=new".to_string()); } @@ -145,7 +152,7 @@ fn build_chrome_args(options: &LaunchOptions) -> Result { .iter() .any(|a| a.starts_with("--start-maximized") || a.starts_with("--window-size=")); - if !has_window_size && options.headless { + if !has_window_size && options.headless && !has_extensions { args.push("--window-size=1280,720".to_string()); } @@ -764,6 +771,52 @@ mod tests { } } + #[test] + fn test_build_args_headless_with_extensions_skips_headless_flag() { + let opts = LaunchOptions { + headless: true, + extensions: Some(vec!["/tmp/my-ext".to_string()]), + ..Default::default() + }; + let result = build_chrome_args(&opts).unwrap(); + assert!( + !result.args.iter().any(|a| a.contains("--headless")), + "headless flag should be omitted when extensions are present" + ); + assert!( + !result.args.iter().any(|a| a.contains("--window-size")), + "window-size should be omitted when extensions force headed mode" + ); + assert!(result + .args + .iter() + .any(|a| a.starts_with("--load-extension="))); + if let Some(ref dir) = result.temp_user_data_dir { + let _ = std::fs::remove_dir_all(dir); + } + } + + #[test] + fn test_build_args_headed_with_extensions_no_headless_flag() { + let opts = LaunchOptions { + headless: false, + extensions: Some(vec!["/tmp/my-ext".to_string()]), + ..Default::default() + }; + let result = build_chrome_args(&opts).unwrap(); + assert!( + !result.args.iter().any(|a| a.contains("--headless")), + "headless flag should not be present in headed mode" + ); + assert!(result + .args + .iter() + .any(|a| a.starts_with("--load-extension="))); + if let Some(ref dir) = result.temp_user_data_dir { + let _ = std::fs::remove_dir_all(dir); + } + } + #[test] fn test_chrome_process_drop_cleans_temp_dir() { let dir = std::env::temp_dir().join(format!(