Skip to content

Commit 66dd1d0

Browse files
Raillyfahimscirexzyz619963502zyz
authored
fix: keep profile and args on bare open (#2287)
* fix: keep profile and args on bare open Bare `open` sent a second launch envelope without the local launch options, so the daemon saw a new launch hash and relaunched on a temp user-data-dir. Merge the options into the bare `open` envelope instead, keeping its pin-tab and input-mode settings and its confirmation handling. The daemon also falls back to AGENT_BROWSER_PROFILE when an envelope has no profile, like executablePath. Fixes #1977 Co-Authored-By: Fahim Montasir Misbah <fahimscirex@tutanota.com> Co-Authored-By: Jony <13896935+zyz619963502zyz@users.noreply.github.com> * fix: limit the profile env fallback to plain local launches A daemon spawned with a profile injected it into CDP, auto-connect, provider and storage-state launches, which reject a profile. --------- Co-authored-by: Fahim Montasir Misbah <fahimscirex@tutanota.com> Co-authored-by: Jony <13896935+zyz619963502zyz@users.noreply.github.com>
1 parent 5c13439 commit 66dd1d0

2 files changed

Lines changed: 240 additions & 140 deletions

File tree

‎cli/src/main.rs‎

Lines changed: 193 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,133 @@ fn incompatible_launch_mode_error(flags: &Flags) -> Option<&'static str> {
321321
None
322322
}
323323

324+
fn build_local_launch_command(flags: &Flags) -> serde_json::Value {
325+
let mut launch_cmd = json!({
326+
"id": gen_id(),
327+
"action": "launch",
328+
});
329+
// Only send headless when the user set it on this invocation. When
330+
// absent, the daemon falls back to its spawn-time AGENT_BROWSER_HEADED
331+
// env, so a follow-up command without --headed (common when env vars
332+
// like AGENT_BROWSER_ARGS force a launch command on every call) does
333+
// not flip a headed session back to headless and relaunch the browser
334+
// onto about:blank.
335+
if flags.headed || flags.cli_headed {
336+
launch_cmd["headless"] = json!(!flags.headed);
337+
}
338+
launch_cmd["plugins"] = json!(flags.plugins.clone());
339+
attach_restore_config_to_command(&mut launch_cmd, flags);
340+
341+
let cmd_obj = launch_cmd
342+
.as_object_mut()
343+
.expect("json! macro guarantees object type");
344+
345+
// Add executable path if specified
346+
if let Some(ref exec_path) = flags.executable_path {
347+
cmd_obj.insert("executablePath".to_string(), json!(exec_path));
348+
}
349+
350+
// Add profile path if specified
351+
if let Some(ref profile_path) = flags.profile {
352+
cmd_obj.insert("profile".to_string(), json!(profile_path));
353+
}
354+
355+
// Add state path if specified
356+
if let Some(ref state_path) = flags.state {
357+
cmd_obj.insert("storageState".to_string(), json!(state_path));
358+
}
359+
360+
if let Some(ref proxy_str) = flags.proxy {
361+
let parsed = parse_proxy(proxy_str);
362+
let mut proxy_obj = json!({ "server": parsed.server });
363+
if let Some(ref username) = parsed.username {
364+
proxy_obj["username"] = json!(username);
365+
}
366+
if let Some(ref password) = parsed.password {
367+
proxy_obj["password"] = json!(password);
368+
}
369+
if let Some(ref bypass) = flags.proxy_bypass {
370+
proxy_obj["bypass"] = json!(bypass);
371+
}
372+
cmd_obj.insert("proxy".to_string(), proxy_obj);
373+
}
374+
375+
if let Some(ref ua) = flags.user_agent {
376+
cmd_obj.insert("userAgent".to_string(), json!(ua));
377+
}
378+
379+
if let Some(ref a) = flags.args {
380+
cmd_obj.insert("args".to_string(), json!(split_launch_args(a)));
381+
}
382+
383+
if !flags.extensions.is_empty() {
384+
cmd_obj.insert("extensions".to_string(), json!(flags.extensions));
385+
}
386+
387+
if !flags.init_scripts.is_empty() {
388+
cmd_obj.insert("initScripts".to_string(), json!(flags.init_scripts));
389+
}
390+
391+
if !flags.enable.is_empty() {
392+
cmd_obj.insert("enable".to_string(), json!(flags.enable));
393+
}
394+
395+
if flags.ignore_https_errors {
396+
launch_cmd["ignoreHTTPSErrors"] = json!(true);
397+
}
398+
399+
attach_ca_cert_to_launch_command(&mut launch_cmd, flags);
400+
401+
if flags.allow_file_access {
402+
launch_cmd["allowFileAccess"] = json!(true);
403+
}
404+
405+
apply_hide_scrollbars_launch_option(
406+
&mut launch_cmd,
407+
flags.cli_hide_scrollbars,
408+
flags.hide_scrollbars,
409+
);
410+
411+
if flags.webgpu || flags.cli_webgpu {
412+
launch_cmd["webgpu"] = json!(flags.webgpu);
413+
}
414+
attach_webmcp_launch_option(&mut launch_cmd, flags);
415+
416+
// Env-only opt-out for automatic Xvfb; always stamped from the CLI's
417+
// fresh environment so both setting and unsetting the var take effect
418+
// on daemons spawned before the change.
419+
launch_cmd["noXvfb"] = json!(flags.no_xvfb);
420+
421+
if let Some(ref cs) = flags.color_scheme {
422+
launch_cmd["colorScheme"] = json!(cs);
423+
}
424+
425+
if let Some(ref dp) = flags.download_path {
426+
launch_cmd["downloadPath"] = json!(dp);
427+
}
428+
429+
attach_allowed_domains_to_launch_command(&mut launch_cmd, flags);
430+
431+
if let Some(ref engine) = flags.engine {
432+
launch_cmd["engine"] = json!(engine);
433+
}
434+
attach_obscura_proxy_bypass(&mut launch_cmd, flags);
435+
436+
launch_cmd
437+
}
438+
439+
fn merge_local_launch_config(cmd: &mut serde_json::Value, mut launch_cmd: serde_json::Value) {
440+
let launch_obj = launch_cmd
441+
.as_object_mut()
442+
.expect("json! macro guarantees object type");
443+
if let Some(cmd_obj) = cmd.as_object() {
444+
for (key, value) in cmd_obj {
445+
launch_obj.insert(key.clone(), value.clone());
446+
}
447+
}
448+
*cmd = launch_cmd;
449+
}
450+
324451
fn should_send_local_launch_config(flags: &Flags, command: &serde_json::Value) -> bool {
325452
(flags.headed
326453
|| flags.cli_headed
@@ -2098,144 +2225,41 @@ fn main() {
20982225

20992226
// Launch headed browser or configure browser options (without CDP or provider)
21002227
if should_send_local_launch_config(&flags, &cmd) {
2101-
let mut launch_cmd = json!({
2102-
"id": gen_id(),
2103-
"action": "launch",
2104-
});
2105-
// Only send headless when the user set it on this invocation. When
2106-
// absent, the daemon falls back to its spawn-time AGENT_BROWSER_HEADED
2107-
// env, so a follow-up command without --headed (common when env vars
2108-
// like AGENT_BROWSER_ARGS force a launch command on every call) does
2109-
// not flip a headed session back to headless and relaunch the browser
2110-
// onto about:blank.
2111-
if flags.headed || flags.cli_headed {
2112-
launch_cmd["headless"] = json!(!flags.headed);
2113-
}
2114-
launch_cmd["plugins"] = json!(flags.plugins.clone());
2115-
attach_restore_config_to_command(&mut launch_cmd, &flags);
2116-
2117-
let cmd_obj = launch_cmd
2118-
.as_object_mut()
2119-
.expect("json! macro guarantees object type");
2120-
2121-
// Add executable path if specified
2122-
if let Some(ref exec_path) = flags.executable_path {
2123-
cmd_obj.insert("executablePath".to_string(), json!(exec_path));
2124-
}
2125-
2126-
// Add profile path if specified
2127-
if let Some(ref profile_path) = flags.profile {
2128-
cmd_obj.insert("profile".to_string(), json!(profile_path));
2129-
}
2130-
2131-
// Add state path if specified
2132-
if let Some(ref state_path) = flags.state {
2133-
cmd_obj.insert("storageState".to_string(), json!(state_path));
2134-
}
2135-
2136-
if let Some(ref proxy_str) = flags.proxy {
2137-
let parsed = parse_proxy(proxy_str);
2138-
let mut proxy_obj = json!({ "server": parsed.server });
2139-
if let Some(ref username) = parsed.username {
2140-
proxy_obj["username"] = json!(username);
2141-
}
2142-
if let Some(ref password) = parsed.password {
2143-
proxy_obj["password"] = json!(password);
2144-
}
2145-
if let Some(ref bypass) = flags.proxy_bypass {
2146-
proxy_obj["bypass"] = json!(bypass);
2147-
}
2148-
cmd_obj.insert("proxy".to_string(), proxy_obj);
2149-
}
2150-
2151-
if let Some(ref ua) = flags.user_agent {
2152-
cmd_obj.insert("userAgent".to_string(), json!(ua));
2153-
}
2154-
2155-
if let Some(ref a) = flags.args {
2156-
cmd_obj.insert("args".to_string(), json!(split_launch_args(a)));
2157-
}
2158-
2159-
if !flags.extensions.is_empty() {
2160-
cmd_obj.insert("extensions".to_string(), json!(&flags.extensions));
2161-
}
2162-
2163-
if !flags.init_scripts.is_empty() {
2164-
cmd_obj.insert("initScripts".to_string(), json!(&flags.init_scripts));
2165-
}
2166-
2167-
if !flags.enable.is_empty() {
2168-
cmd_obj.insert("enable".to_string(), json!(&flags.enable));
2169-
}
2170-
2171-
if flags.ignore_https_errors {
2172-
launch_cmd["ignoreHTTPSErrors"] = json!(true);
2173-
}
2174-
2175-
attach_ca_cert_to_launch_command(&mut launch_cmd, &flags);
2176-
2177-
if flags.allow_file_access {
2178-
launch_cmd["allowFileAccess"] = json!(true);
2179-
}
2180-
2181-
apply_hide_scrollbars_launch_option(
2182-
&mut launch_cmd,
2183-
flags.cli_hide_scrollbars,
2184-
flags.hide_scrollbars,
2185-
);
2186-
2187-
if flags.webgpu || flags.cli_webgpu {
2188-
launch_cmd["webgpu"] = json!(flags.webgpu);
2189-
}
2190-
attach_webmcp_launch_option(&mut launch_cmd, &flags);
2191-
2192-
// Env-only opt-out for automatic Xvfb; always stamped from the CLI's
2193-
// fresh environment so both setting and unsetting the var take effect
2194-
// on daemons spawned before the change.
2195-
launch_cmd["noXvfb"] = json!(flags.no_xvfb);
2196-
2197-
if let Some(ref cs) = flags.color_scheme {
2198-
launch_cmd["colorScheme"] = json!(cs);
2199-
}
2200-
2201-
if let Some(ref dp) = flags.download_path {
2202-
launch_cmd["downloadPath"] = json!(dp);
2203-
}
2204-
2205-
attach_allowed_domains_to_launch_command(&mut launch_cmd, &flags);
2206-
2207-
if let Some(ref engine) = flags.engine {
2208-
launch_cmd["engine"] = json!(engine);
2209-
}
2210-
attach_obscura_proxy_bypass(&mut launch_cmd, &flags);
2211-
2212-
match send_command(launch_cmd, &flags.session) {
2213-
Ok(resp) if !resp.success => {
2214-
// Launch command failed (e.g., invalid state file, profile error)
2215-
let error_msg = resp
2216-
.error
2217-
.unwrap_or_else(|| "Browser launch failed".to_string());
2218-
if flags.json {
2219-
print_json_error_with_code(error_msg, resp.code.as_deref());
2220-
} else {
2221-
eprintln!("{} {}", color::error_indicator(), error_msg);
2228+
let launch_cmd = build_local_launch_command(&flags);
2229+
if cmd.get("action").and_then(|v| v.as_str()) == Some("launch") {
2230+
// Bare `open` is itself a launch, so it carries the local launch
2231+
// options instead of following them with a second envelope that
2232+
// would change the launch hash and relaunch without them.
2233+
merge_local_launch_config(&mut cmd, launch_cmd);
2234+
} else {
2235+
match send_command(launch_cmd, &flags.session) {
2236+
Ok(resp) if !resp.success => {
2237+
// Launch command failed (e.g., invalid state file, profile error)
2238+
let error_msg = resp
2239+
.error
2240+
.unwrap_or_else(|| "Browser launch failed".to_string());
2241+
if flags.json {
2242+
print_json_error_with_code(error_msg, resp.code.as_deref());
2243+
} else {
2244+
eprintln!("{} {}", color::error_indicator(), error_msg);
2245+
}
2246+
exit(1);
22222247
}
2223-
exit(1);
2224-
}
2225-
Err(e) => {
2226-
if flags.json {
2227-
print_json_error(e);
2228-
} else {
2229-
eprintln!(
2230-
"{} Could not configure browser: {}",
2231-
color::error_indicator(),
2232-
e
2233-
);
2248+
Err(e) => {
2249+
if flags.json {
2250+
print_json_error(e);
2251+
} else {
2252+
eprintln!(
2253+
"{} Could not configure browser: {}",
2254+
color::error_indicator(),
2255+
e
2256+
);
2257+
}
2258+
exit(1);
2259+
}
2260+
Ok(_) => {
2261+
// Launch succeeded
22342262
}
2235-
exit(1);
2236-
}
2237-
Ok(_) => {
2238-
// Launch succeeded
22392263
}
22402264
}
22412265
}
@@ -2523,6 +2547,39 @@ mod tests {
25232547
assert_eq!(command["inputMode"], "human");
25242548
}
25252549

2550+
#[test]
2551+
fn bare_open_carries_local_launch_config_and_session_settings() {
2552+
let args: Vec<String> = [
2553+
"--profile",
2554+
"/tmp/profile",
2555+
"--args",
2556+
"--lang=en-US",
2557+
"--pin-tab",
2558+
"--input-mode",
2559+
"smooth",
2560+
"open",
2561+
]
2562+
.into_iter()
2563+
.map(str::to_string)
2564+
.collect();
2565+
let flags = crate::flags::parse_flags(&args);
2566+
let mut command =
2567+
crate::commands::parse_command(&crate::flags::clean_args(&args), &flags).unwrap();
2568+
super::attach_input_mode(&mut command, &flags);
2569+
super::attach_pin_tab_to_command(&mut command, &flags);
2570+
let id = command["id"].clone();
2571+
assert!(super::should_send_local_launch_config(&flags, &command));
2572+
2573+
super::merge_local_launch_config(&mut command, super::build_local_launch_command(&flags));
2574+
2575+
assert_eq!(command["action"], "launch");
2576+
assert_eq!(command["id"], id);
2577+
assert_eq!(command["profile"], "/tmp/profile");
2578+
assert_eq!(command["args"], serde_json::json!(["--lang=en-US"]));
2579+
assert_eq!(command["pinTab"], true);
2580+
assert_eq!(command["defaultInputMode"], "smooth");
2581+
}
2582+
25262583
use super::*;
25272584

25282585
#[test]

0 commit comments

Comments
 (0)