1use std::sync::Arc;
12
13use chromiumoxide::browser::{Browser, BrowserConfig};
14use chromiumoxide::page::Page;
15use futures::StreamExt;
16use once_cell::sync::OnceCell;
17use tokio::sync::Mutex;
18
19#[derive(Clone)]
20pub struct BrowserManager {
21 inner: Arc<Inner>,
22}
23
24struct Inner {
25 cell: OnceCell<Arc<Mutex<Browser>>>,
26 init: Mutex<()>,
27}
28
29impl Default for BrowserManager {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35impl BrowserManager {
36 pub fn new() -> Self {
37 Self {
38 inner: Arc::new(Inner {
39 cell: OnceCell::new(),
40 init: Mutex::new(()),
41 }),
42 }
43 }
44
45 pub async fn browser(&self) -> Result<Arc<Mutex<Browser>>, String> {
47 if let Some(b) = self.inner.cell.get() {
48 return Ok(b.clone());
49 }
50 let _g = self.inner.init.lock().await;
51 if let Some(b) = self.inner.cell.get() {
52 return Ok(b.clone());
53 }
54 let config = BrowserConfig::builder()
55 .build()
56 .map_err(|e| format!("browser config: {e}"))?;
57 let (browser, mut handler) = Browser::launch(config).await.map_err(|e| {
58 format!(
59 "launch chromium: {e}. Install Chrome/Chromium or set CHROME env var to its path."
60 )
61 })?;
62
63 tokio::spawn(async move {
65 while let Some(_evt) = handler.next().await {
66 }
68 });
69
70 let arc = Arc::new(Mutex::new(browser));
71 let _ = self.inner.cell.set(arc.clone());
72 Ok(arc)
73 }
74
75 pub async fn open(&self, url: &str) -> Result<Page, String> {
77 let browser_arc = self.browser().await?;
78 let browser = browser_arc.lock().await;
79 let page = browser
80 .new_page(url)
81 .await
82 .map_err(|e| format!("new_page({url}): {e}"))?;
83 page.wait_for_navigation()
84 .await
85 .map_err(|e| format!("wait_for_navigation: {e}"))?;
86 Ok(page)
87 }
88}