use anyhow::{Context, Result};
use chromiumoxide::browser::Browser;
use chromiumoxide::page::Page;
use std::path::PathBuf;
use tokio::task::JoinHandle;
use tracing::info;
pub struct BrowserWrapper {
browser: Browser,
handler: JoinHandle<()>,
user_data_dir: Option<PathBuf>,
}
impl BrowserWrapper {
pub(crate) fn new(browser: Browser, handler: JoinHandle<()>, user_data_dir: PathBuf) -> Self {
Self {
browser,
handler,
user_data_dir: Some(user_data_dir),
}
}
pub(crate) fn browser(&self) -> &Browser {
&self.browser
}
pub(crate) fn browser_mut(&mut self) -> &mut Browser {
&mut self.browser
}
pub fn cleanup_temp_dir(&mut self) {
if let Some(path) = self.user_data_dir.take() {
info!("Cleaning up temp directory: {}", path.display());
if let Err(e) = std::fs::remove_dir_all(&path) {
tracing::warn!(
"Failed to clean up temp directory {}: {}. Manual cleanup may be required.",
path.display(),
e
);
}
}
}
#[allow(dead_code)]
pub fn keep_temp_dir(mut self) {
self.user_data_dir = None;
}
}
impl Drop for BrowserWrapper {
fn drop(&mut self) {
info!("Dropping BrowserWrapper - aborting handler task");
self.handler.abort();
if let Some(ref dir) = self.user_data_dir {
tracing::warn!(
"BrowserWrapper dropped without explicit cleanup. \
Temp directory will be orphaned: {}. \
Call BrowserManager::shutdown() before dropping to ensure proper cleanup.",
dir.display()
);
}
}
}
pub async fn launch_browser() -> Result<(Browser, JoinHandle<()>, PathBuf)> {
info!("Launching main browser instance");
let config = crate::load_yaml_config().unwrap_or_default();
let user_data_dir = std::env::temp_dir().join(format!("kodegen_browser_main_{}", std::process::id()));
let (browser, handler) = crate::browser_setup::launch_browser(
config.browser.headless,
Some(user_data_dir.clone()),
config.browser.disable_security,
).await?;
Ok((browser, handler, user_data_dir))
}
pub async fn create_blank_page(wrapper: &BrowserWrapper) -> Result<Page> {
let page = wrapper
.browser()
.new_page("about:blank")
.await
.context("Failed to create blank page")?;
info!("Created blank page for stealth injection");
Ok(page)
}
pub async fn get_current_page(wrapper: &BrowserWrapper) -> Result<Page> {
let pages = wrapper
.browser()
.pages()
.await
.context("Failed to get browser pages")?;
pages
.into_iter()
.last()
.ok_or_else(|| anyhow::anyhow!("No page loaded. Call browser_navigate first."))
}