Skip to main content

headless_engine/browser/
engine.rs

1use crate::browser::builder::BrowserBuilder;
2use crate::browser::tab::BrowserTab;
3use crate::network::fingerprint::DeviceProfile;
4use anyhow::{anyhow, Result};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct TabSummary {
10    pub id: String,
11    pub url: Option<String>,
12    pub profile: DeviceProfile,
13}
14
15pub struct BrowserEngine {
16    builder: BrowserBuilder,
17    tabs: HashMap<String, BrowserTab>,
18    tab_counter: usize,
19    default_tab_id: String,
20}
21
22impl BrowserEngine {
23    pub fn new() -> Result<Self> {
24        Self::with_builder(BrowserBuilder::new())
25    }
26
27    pub fn with_builder(builder: BrowserBuilder) -> Result<Self> {
28        let mut engine = Self {
29            builder: builder.clone(),
30            tabs: HashMap::new(),
31            tab_counter: 0,
32            default_tab_id: String::new(),
33        };
34
35        // Initialize default tab
36        let tab_id = engine.create_tab(Some(builder.profile))?;
37        engine.default_tab_id = tab_id;
38
39        Ok(engine)
40    }
41
42    pub fn create_tab(&mut self, profile: Option<DeviceProfile>) -> Result<String> {
43        self.tab_counter += 1;
44        let tab_id = format!("tab_{}", self.tab_counter);
45
46        let mut tab_builder = self.builder.clone();
47        if let Some(p) = profile {
48            tab_builder = tab_builder.profile(p);
49        }
50
51        let tab = tab_builder.build()?;
52        self.tabs.insert(tab_id.clone(), tab);
53
54        if self.default_tab_id.is_empty() {
55            self.default_tab_id = tab_id.clone();
56        }
57
58        Ok(tab_id)
59    }
60
61    pub fn get_tab(&self, tab_id: &str) -> Option<&BrowserTab> {
62        self.tabs.get(tab_id)
63    }
64
65    pub fn get_tab_mut(&mut self, tab_id: &str) -> Option<&mut BrowserTab> {
66        self.tabs.get_mut(tab_id)
67    }
68
69    pub fn close_tab(&mut self, tab_id: &str) -> bool {
70        let removed = self.tabs.remove(tab_id).is_some();
71        if self.default_tab_id == tab_id {
72            self.default_tab_id = self.tabs.keys().next().cloned().unwrap_or_default();
73        }
74        removed
75    }
76
77    pub fn list_tabs(&self) -> Vec<TabSummary> {
78        self.tabs
79            .iter()
80            .map(|(id, tab)| TabSummary {
81                id: id.clone(),
82                url: tab.current_url.clone(),
83                profile: tab.profile(),
84            })
85            .collect()
86    }
87
88    pub fn default_tab_mut(&mut self) -> Result<&mut BrowserTab> {
89        if self.default_tab_id.is_empty() || !self.tabs.contains_key(&self.default_tab_id) {
90            self.default_tab_id = self.create_tab(None)?;
91        }
92        self.tabs
93            .get_mut(&self.default_tab_id)
94            .ok_or_else(|| anyhow!("No active browser tab found"))
95    }
96}