headless_engine/browser/
engine.rs1use 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 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}