viewpoint_core/context/mod.rs
1//! Browser context management.
2
3mod api;
4pub mod binding;
5mod construction;
6mod cookies;
7mod emulation;
8pub mod events;
9mod har;
10mod page_events;
11mod page_factory;
12mod page_management;
13mod permissions;
14pub mod routing;
15mod routing_impl;
16mod scripts;
17pub mod storage;
18mod storage_restore;
19mod test_id;
20pub mod trace;
21mod tracing_access;
22pub mod types;
23mod weberror;
24
25pub use cookies::ClearCookiesBuilder;
26pub use emulation::SetGeolocationBuilder;
27
28// HashMap is used in emulation.rs
29use std::sync::Arc;
30use std::time::Duration;
31
32use tokio::sync::RwLock;
33use tracing::{debug, info, instrument};
34
35use viewpoint_cdp::CdpConnection;
36use viewpoint_cdp::protocol::target_domain::DisposeBrowserContextParams;
37
38use crate::error::ContextError;
39
40pub use events::{ContextEventManager, HandlerId};
41pub use storage::{StorageStateBuilder, StorageStateOptions};
42use trace::TracingState;
43pub use trace::{Tracing, TracingOptions};
44pub use types::{
45 ColorScheme, ContextOptions, ContextOptionsBuilder, Cookie, ForcedColors, Geolocation,
46 HttpCredentials, IndexedDbDatabase, IndexedDbEntry, IndexedDbIndex, IndexedDbObjectStore,
47 LocalStorageEntry, Permission, ReducedMotion, SameSite, StorageOrigin, StorageState,
48 StorageStateSource, ViewportSize,
49};
50pub use weberror::WebErrorHandler;
51// Re-export WebError for context-level usage
52pub use crate::page::page_error::WebError;
53
54/// Default test ID attribute name.
55pub const DEFAULT_TEST_ID_ATTRIBUTE: &str = "data-testid";
56
57/// An isolated browser context.
58///
59/// Browser contexts are similar to incognito windows - they have their own
60/// cookies, cache, and storage that are isolated from other contexts.
61///
62/// # Features
63///
64/// - **Cookie Management**: Add, get, and clear cookies
65/// - **Storage State**: Save and restore browser state
66/// - **Permissions**: Grant permissions like geolocation, camera, etc.
67/// - **Geolocation**: Mock browser location
68/// - **HTTP Credentials**: Basic/Digest authentication
69/// - **Extra Headers**: Add headers to all requests
70/// - **Offline Mode**: Simulate network offline conditions
71/// - **Event Handling**: Listen for page creation and context close events
72/// - **Init Scripts**: Scripts that run before every page load
73/// - **Custom Test ID**: Configure which attribute is used for test IDs
74///
75/// # Ownership
76///
77/// Contexts can be either "owned" (created by us) or "external" (discovered when
78/// connecting to an existing browser). When closing an external context, the
79/// underlying browser context is not disposed - only our connection to it is closed.
80pub struct BrowserContext {
81 /// CDP connection.
82 connection: Arc<CdpConnection>,
83 /// Browser context ID.
84 context_id: String,
85 /// Whether the context has been closed.
86 closed: bool,
87 /// Whether we own this context (created it) vs discovered it.
88 /// Owned contexts are disposed when closed; external contexts are not.
89 owned: bool,
90 /// Created pages (weak tracking for `pages()` method).
91 pages: Arc<RwLock<Vec<PageInfo>>>,
92 /// Default timeout for actions.
93 default_timeout: Duration,
94 /// Default timeout for navigation.
95 default_navigation_timeout: Duration,
96 /// Context options used to create this context.
97 options: ContextOptions,
98 /// Web error handler.
99 weberror_handler: Arc<RwLock<Option<WebErrorHandler>>>,
100 /// Event manager for context-level events.
101 event_manager: Arc<ContextEventManager>,
102 /// Context-level route registry.
103 route_registry: Arc<routing::ContextRouteRegistry>,
104 /// Context-level binding registry.
105 binding_registry: Arc<binding::ContextBindingRegistry>,
106 /// Init scripts to run on every page load.
107 init_scripts: Arc<RwLock<Vec<String>>>,
108 /// Custom test ID attribute name (defaults to "data-testid").
109 test_id_attribute: Arc<RwLock<String>>,
110 /// HAR recorder for capturing network traffic.
111 har_recorder: Arc<RwLock<Option<crate::network::HarRecorder>>>,
112 /// Shared tracing state for persistent tracing across `tracing()` calls.
113 tracing_state: Arc<RwLock<TracingState>>,
114}
115
116// Manual Debug implementation since WebErrorHandler doesn't implement Debug
117impl std::fmt::Debug for BrowserContext {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 f.debug_struct("BrowserContext")
120 .field("context_id", &self.context_id)
121 .field("closed", &self.closed)
122 .field("owned", &self.owned)
123 .field("default_timeout", &self.default_timeout)
124 .field(
125 "default_navigation_timeout",
126 &self.default_navigation_timeout,
127 )
128 .finish_non_exhaustive()
129 }
130}
131
132/// Information about a page in the context.
133#[derive(Debug, Clone)]
134pub struct PageInfo {
135 /// Target ID.
136 pub target_id: String,
137 /// Session ID (may be empty if not tracked).
138 pub session_id: String,
139}
140
141impl BrowserContext {
142 // Construction methods (new, with_options, from_existing, apply_options) are in construction.rs
143
144 // Page management methods (new_page, pages) are in page_management.rs
145
146 // Cookie methods are in cookies.rs
147
148 // Storage state methods are in storage.rs
149
150 // Permissions methods are in permissions.rs
151
152 // =========================================================================
153 // Geolocation
154 // =========================================================================
155
156 /// Set the geolocation.
157 ///
158 /// # Example
159 ///
160 /// ```no_run
161 /// use viewpoint_core::BrowserContext;
162 ///
163 /// # async fn example(context: &BrowserContext) -> Result<(), viewpoint_core::CoreError> {
164 /// // San Francisco
165 /// context.set_geolocation(37.7749, -122.4194).await?;
166 /// # Ok(())
167 /// # }
168 /// ```
169 ///
170 /// # Errors
171 ///
172 /// Returns an error if setting geolocation fails.
173 pub fn set_geolocation(&self, latitude: f64, longitude: f64) -> SetGeolocationBuilder<'_> {
174 SetGeolocationBuilder::new(self, latitude, longitude)
175 }
176
177 // clear_geolocation, set_extra_http_headers, set_offline are in emulation.rs
178
179 // =========================================================================
180 // Ownership and Status
181 // =========================================================================
182
183 /// Check if this context is owned (created by us) or external.
184 ///
185 /// External contexts are discovered when connecting to an already-running browser.
186 /// They are not disposed when closed.
187 pub fn is_owned(&self) -> bool {
188 self.owned
189 }
190
191 /// Check if this is the default browser context.
192 ///
193 /// The default context represents the browser's main profile and has an empty ID.
194 pub fn is_default(&self) -> bool {
195 self.context_id.is_empty()
196 }
197
198 // =========================================================================
199 // Timeout Configuration
200 // =========================================================================
201
202 /// Set the default timeout for actions.
203 ///
204 /// This timeout is used for actions like clicking, typing, etc.
205 pub fn set_default_timeout(&mut self, timeout: Duration) {
206 self.default_timeout = timeout;
207 }
208
209 /// Get the default timeout for actions.
210 pub fn default_timeout(&self) -> Duration {
211 self.default_timeout
212 }
213
214 /// Set the default navigation timeout.
215 ///
216 /// This timeout is used for navigation operations like goto, reload, etc.
217 pub fn set_default_navigation_timeout(&mut self, timeout: Duration) {
218 self.default_navigation_timeout = timeout;
219 }
220
221 /// Get the default navigation timeout.
222 pub fn default_navigation_timeout(&self) -> Duration {
223 self.default_navigation_timeout
224 }
225
226 // Init script methods are in scripts.rs
227
228 // =========================================================================
229 // Context Lifecycle
230 // =========================================================================
231
232 /// Close this browser context and all its pages.
233 ///
234 /// For contexts we created (owned), this disposes the context via CDP.
235 /// For external contexts (discovered when connecting to an existing browser),
236 /// this only closes our connection without disposing the context.
237 ///
238 /// # Errors
239 ///
240 /// Returns an error if closing fails.
241 #[instrument(level = "info", skip(self), fields(context_id = %self.context_id, owned = self.owned))]
242 pub async fn close(&mut self) -> Result<(), ContextError> {
243 if self.closed {
244 debug!("Context already closed");
245 return Ok(());
246 }
247
248 info!("Closing browser context");
249
250 // Auto-save HAR if recording is active
251 if let Some(recorder) = self.har_recorder.write().await.take() {
252 if let Err(e) = recorder.save().await {
253 debug!("Failed to auto-save HAR on close: {}", e);
254 } else {
255 debug!(path = %recorder.path().display(), "Auto-saved HAR on close");
256 }
257 }
258
259 // Emit close event before cleanup
260 self.event_manager.emit_close().await;
261
262 // Only dispose the context if we own it
263 // External contexts (from connecting to existing browser) should not be disposed
264 if self.owned && !self.context_id.is_empty() {
265 debug!("Disposing owned browser context");
266 self.connection
267 .send_command::<_, serde_json::Value>(
268 "Target.disposeBrowserContext",
269 Some(DisposeBrowserContextParams {
270 browser_context_id: self.context_id.clone(),
271 }),
272 None,
273 )
274 .await?;
275 } else {
276 debug!("Skipping dispose for external/default context");
277 }
278
279 // Clear all event handlers
280 self.event_manager.clear().await;
281
282 self.closed = true;
283 info!("Browser context closed");
284 Ok(())
285 }
286
287 /// Get the context ID.
288 pub fn id(&self) -> &str {
289 &self.context_id
290 }
291
292 /// Check if this context has been closed.
293 pub fn is_closed(&self) -> bool {
294 self.closed
295 }
296
297 /// Get a reference to the CDP connection.
298 pub fn connection(&self) -> &Arc<CdpConnection> {
299 &self.connection
300 }
301
302 /// Get the context ID.
303 pub fn context_id(&self) -> &str {
304 &self.context_id
305 }
306
307 // Web error event methods are in weberror.rs (on_weberror, off_weberror)
308
309 // Page and close event methods are in page_events.rs (on_page, off_page, on_close, off_close, wait_for_page)
310
311 // Context-level routing methods are in routing_impl.rs
312
313 // HAR recording methods are in har.rs
314
315 // Exposed function methods are in binding.rs (expose_function, remove_exposed_function)
316
317 // API request context methods are in api.rs (request, sync_cookies_from_api)
318
319 // Tracing method is in tracing_access.rs
320
321 // Test ID attribute methods are in test_id.rs
322}
323
324// ClearCookiesBuilder is in cookies.rs
325// SetGeolocationBuilder is in emulation.rs