Skip to main content

bao_browser/
config.rs

1// @trace REQ-BRW-001  REQ-CDP-008: CDP and browser configuration structs
2use std::time::Duration;
3
4use bao_stealth::StealthProfile;
5
6use crate::permission::Permission;
7
8#[derive(Debug, Clone)]
9pub struct BaoConfig {
10    pub cdp_port: Option<u16>,
11    pub max_pages: usize,
12    pub idle_ttl: Duration,
13    pub default_viewport_width: u32,
14    pub default_viewport_height: u32,
15    pub stealth_profile: Option<StealthProfile>,
16    /// Servo `opts::ignore_certificate_errors` (WPT posture): skip peer
17    /// certificate verification failures for page/network fetches. Production
18    /// keeps `false` (fail closed); tests driving local self-signed TLS
19    /// fixtures (e.g. the U2 h2 e2e matrix) enable it. Process-global
20    /// (first `BaoRuntime::new` wins — same first-writer-wins semantics as
21    /// every other servo opt).
22    pub ignore_certificate_errors: bool,
23}
24
25impl Default for BaoConfig {
26    fn default() -> Self {
27        BaoConfig {
28            cdp_port: None,
29            max_pages: 50,
30            idle_ttl: Duration::from_secs(60),
31            default_viewport_width: 1920,
32            default_viewport_height: 1080,
33            stealth_profile: None,
34            ignore_certificate_errors: false,
35        }
36    }
37}
38
39impl BaoConfig {
40    pub fn validate(&self) -> Result<(), String> {
41        if self.max_pages < 1 {
42            return Err(format!("max_pages must be >= 1, got {}", self.max_pages));
43        }
44        if self.default_viewport_width < 800 {
45            return Err(format!(
46                "viewport_width must be >= 800, got {}",
47                self.default_viewport_width
48            ));
49        }
50        if self.default_viewport_height < 600 {
51            return Err(format!(
52                "viewport_height must be >= 600, got {}",
53                self.default_viewport_height
54            ));
55        }
56        Ok(())
57    }
58}
59
60#[derive(Debug, Clone, Default)]
61pub struct PageConfig {
62    pub url: Option<String>,
63    pub viewport_width: Option<u32>,
64    pub viewport_height: Option<u32>,
65    pub stealth_profile: Option<StealthProfile>,
66    pub permission: Option<Permission>,
67}
68
69#[derive(Debug, Clone)]
70pub struct BrowserConfig {
71    pub url: Option<String>,
72    pub cdp_port: u16,
73    pub viewport_width: u32,
74    pub viewport_height: u32,
75    pub headless: bool,
76    pub stealth_profile: Option<StealthProfile>,
77}
78
79impl Default for BrowserConfig {
80    fn default() -> Self {
81        BrowserConfig {
82            url: None,
83            cdp_port: 9222,
84            viewport_width: 1920,
85            viewport_height: 1080,
86            headless: true,
87            stealth_profile: None,
88        }
89    }
90}
91
92impl From<BrowserConfig> for BaoConfig {
93    fn from(bc: BrowserConfig) -> Self {
94        BaoConfig {
95            cdp_port: Some(bc.cdp_port),
96            max_pages: 50,
97            idle_ttl: Duration::from_secs(60),
98            default_viewport_width: bc.viewport_width,
99            default_viewport_height: bc.viewport_height,
100            stealth_profile: bc.stealth_profile,
101            // BrowserConfig has no certificate-relaxation surface —
102            // production conversion stays fail-closed.
103            ignore_certificate_errors: false,
104        }
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    // @trace REQ-BRW-001 [req:REQ-BRW-001,REQ-CDP-008] [level:unit]
111    use super::*;
112
113    #[test]
114    fn bao_config_default_values() {
115        let cfg = BaoConfig::default();
116        assert_eq!(cfg.cdp_port, None);
117        assert_eq!(cfg.max_pages, 50);
118        assert_eq!(cfg.idle_ttl, Duration::from_secs(60));
119        assert_eq!(cfg.default_viewport_width, 1920);
120        assert_eq!(cfg.default_viewport_height, 1080);
121        assert!(cfg.stealth_profile.is_none());
122    }
123
124    #[test]
125    fn validate_ok_with_defaults() {
126        assert!(BaoConfig::default().validate().is_ok());
127    }
128
129    #[test]
130    fn validate_fails_max_pages_zero() {
131        let mut cfg = BaoConfig::default();
132        cfg.max_pages = 0;
133        let err = cfg.validate().unwrap_err();
134        assert!(
135            err.contains("max_pages must be >= 1"),
136            "unexpected error: {err}"
137        );
138        assert!(err.contains("0"), "error should report the value 0: {err}");
139    }
140
141    #[test]
142    fn validate_fails_viewport_width_799() {
143        let mut cfg = BaoConfig::default();
144        cfg.default_viewport_width = 799;
145        let err = cfg.validate().unwrap_err();
146        assert!(
147            err.contains("viewport_width must be >= 800"),
148            "unexpected error: {err}"
149        );
150        assert!(
151            err.contains("799"),
152            "error should report the value 799: {err}"
153        );
154    }
155
156    #[test]
157    fn validate_fails_viewport_height_599() {
158        let mut cfg = BaoConfig::default();
159        cfg.default_viewport_height = 599;
160        let err = cfg.validate().unwrap_err();
161        assert!(
162            err.contains("viewport_height must be >= 600"),
163            "unexpected error: {err}"
164        );
165        assert!(
166            err.contains("599"),
167            "error should report the value 599: {err}"
168        );
169    }
170
171    #[test]
172    fn browser_config_default_values() {
173        let cfg = BrowserConfig::default();
174        assert_eq!(cfg.url, None);
175        assert_eq!(cfg.cdp_port, 9222);
176        assert_eq!(cfg.viewport_width, 1920);
177        assert_eq!(cfg.viewport_height, 1080);
178        assert!(cfg.headless);
179        assert!(cfg.stealth_profile.is_none());
180    }
181
182    #[test]
183    fn from_browser_config_preserves_cdp_port() {
184        let bc = BrowserConfig {
185            cdp_port: 9333,
186            ..Default::default()
187        };
188        let bao: BaoConfig = bc.into();
189        assert_eq!(bao.cdp_port, Some(9333));
190    }
191
192    #[test]
193    fn from_browser_config_preserves_stealth_profile_none() {
194        let bc = BrowserConfig::default();
195        let bao: BaoConfig = bc.into();
196        assert!(bao.stealth_profile.is_none());
197    }
198
199    #[test]
200    fn from_browser_config_maps_fields_correctly() {
201        let bc = BrowserConfig {
202            url: Some("https://example.com".into()),
203            cdp_port: 1234,
204            viewport_width: 1280,
205            viewport_height: 720,
206            headless: false,
207            stealth_profile: None,
208        };
209        let bao: BaoConfig = bc.into();
210        assert_eq!(bao.cdp_port, Some(1234));
211        assert_eq!(bao.max_pages, 50);
212        assert_eq!(bao.idle_ttl, Duration::from_secs(60));
213        assert_eq!(bao.default_viewport_width, 1280);
214        assert_eq!(bao.default_viewport_height, 720);
215        assert!(bao.stealth_profile.is_none());
216    }
217}