use crate::error::{BrowserError, Result};
use rightkit_process::{OwnedChild, OwnedCommand};
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::process::Stdio;
use std::time::Duration;
pub(crate) struct ChromeProc {
pub child: OwnedChild,
pub _watchdog: Option<OwnedChild>,
pub ws_url: String,
}
impl ChromeProc {
pub(crate) fn terminate_gracefully(&mut self, grace: Duration) {
if !matches!(self.child.try_wait(), Ok(Some(_))) {
#[cfg(unix)]
if self.child.wait_or_kill(grace).is_err() {
let _ = self.child.terminate_tree();
}
#[cfg(not(unix))]
{
let _ = grace;
let _ = self.child.terminate_tree();
}
}
if let Some(w) = self._watchdog.as_mut() {
let _ = w.terminate_tree();
}
}
}
#[cfg(windows)]
pub(crate) type CallerJob = Option<std::sync::Arc<std::os::windows::io::OwnedHandle>>;
#[cfg(not(windows))]
pub(crate) type CallerJob = Option<std::convert::Infallible>;
pub(crate) fn spawn_chrome(
exe: &Path,
args: &[String],
timeout: Duration,
job: CallerJob,
) -> Result<ChromeProc> {
let mut cmd = OwnedCommand::new(exe);
cmd.windows_hide();
#[cfg(windows)]
if let Some(job) = &job {
use std::os::windows::io::AsHandle;
cmd.windows_job(job.as_handle())
.map_err(|e| BrowserError::Launch(e.to_string()))?;
}
#[cfg(not(windows))]
let _ = job;
cmd.command_mut()
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::piped());
let mut child = cmd
.spawn()
.map_err(|e| BrowserError::Launch(e.to_string()))?;
let stderr = child
.take_stderr()
.ok_or_else(|| BrowserError::Launch("no stderr".into()))?;
let (tx, rx) = std::sync::mpsc::channel::<String>();
std::thread::spawn(move || {
for line in BufReader::new(stderr).lines().map_while(|l| l.ok()) {
if let Some(u) = line.split("DevTools listening on ").nth(1) {
let _ = tx.send(u.trim().to_string());
}
}
});
let ws_url = rx
.recv_timeout(timeout)
.map_err(|_| BrowserError::Launch("chrome did not report a DevTools endpoint".into()))?;
let watchdog = spawn_watchdog(child.id());
Ok(ChromeProc {
child,
_watchdog: watchdog,
ws_url,
})
}
#[cfg(unix)]
fn spawn_watchdog(chrome_pgid: u32) -> Option<OwnedChild> {
let script = r#"while kill -0 "$1" 2>/dev/null && kill -0 "$2" 2>/dev/null; do sleep 0.2; done
if kill -0 "$2" 2>/dev/null; then
kill -TERM -- "-$2" 2>/dev/null
i=0
while [ "$i" -lt 25 ] && kill -0 -- "-$2" 2>/dev/null; do sleep 0.2; i=$((i+1)); done
kill -KILL -- "-$2" 2>/dev/null
fi
exit 0"#;
let mut cmd = OwnedCommand::new("/bin/sh");
cmd.command_mut()
.args([
"-c",
script,
"rk-watchdog",
&std::process::id().to_string(),
&chrome_pgid.to_string(),
])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
cmd.spawn().ok()
}
#[cfg(not(unix))]
fn spawn_watchdog(_chrome_pid: u32) -> Option<OwnedChild> {
None
}