mod api;
pub mod binding;
mod construction;
mod cookies;
mod emulation;
pub mod events;
mod har;
mod page_events;
mod page_factory;
mod page_management;
mod permissions;
pub mod routing;
mod routing_impl;
mod scripts;
pub mod storage;
mod storage_restore;
mod test_id;
pub mod trace;
mod tracing_access;
pub mod types;
mod weberror;
pub use cookies::ClearCookiesBuilder;
pub use emulation::SetGeolocationBuilder;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use tracing::{debug, info, instrument};
use viewpoint_cdp::CdpConnection;
use viewpoint_cdp::protocol::target_domain::DisposeBrowserContextParams;
use crate::error::ContextError;
pub use events::{ContextEventManager, HandlerId};
pub use storage::{StorageStateBuilder, StorageStateOptions};
use trace::TracingState;
pub use trace::{Tracing, TracingOptions};
pub use types::{
ColorScheme, ContextOptions, ContextOptionsBuilder, Cookie, ForcedColors, Geolocation,
HttpCredentials, IndexedDbDatabase, IndexedDbEntry, IndexedDbIndex, IndexedDbObjectStore,
LocalStorageEntry, Permission, ReducedMotion, SameSite, StorageOrigin, StorageState,
StorageStateSource, ViewportSize,
};
pub use weberror::WebErrorHandler;
pub use crate::page::page_error::WebError;
pub const DEFAULT_TEST_ID_ATTRIBUTE: &str = "data-testid";
pub struct BrowserContext {
connection: Arc<CdpConnection>,
context_id: String,
closed: bool,
owned: bool,
pages: Arc<RwLock<Vec<PageInfo>>>,
default_timeout: Duration,
default_navigation_timeout: Duration,
options: ContextOptions,
weberror_handler: Arc<RwLock<Option<WebErrorHandler>>>,
event_manager: Arc<ContextEventManager>,
route_registry: Arc<routing::ContextRouteRegistry>,
binding_registry: Arc<binding::ContextBindingRegistry>,
init_scripts: Arc<RwLock<Vec<String>>>,
test_id_attribute: Arc<RwLock<String>>,
har_recorder: Arc<RwLock<Option<crate::network::HarRecorder>>>,
tracing_state: Arc<RwLock<TracingState>>,
}
impl std::fmt::Debug for BrowserContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BrowserContext")
.field("context_id", &self.context_id)
.field("closed", &self.closed)
.field("owned", &self.owned)
.field("default_timeout", &self.default_timeout)
.field(
"default_navigation_timeout",
&self.default_navigation_timeout,
)
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone)]
pub struct PageInfo {
pub target_id: String,
pub session_id: String,
}
impl BrowserContext {
pub fn set_geolocation(&self, latitude: f64, longitude: f64) -> SetGeolocationBuilder<'_> {
SetGeolocationBuilder::new(self, latitude, longitude)
}
pub fn is_owned(&self) -> bool {
self.owned
}
pub fn is_default(&self) -> bool {
self.context_id.is_empty()
}
pub fn set_default_timeout(&mut self, timeout: Duration) {
self.default_timeout = timeout;
}
pub fn default_timeout(&self) -> Duration {
self.default_timeout
}
pub fn set_default_navigation_timeout(&mut self, timeout: Duration) {
self.default_navigation_timeout = timeout;
}
pub fn default_navigation_timeout(&self) -> Duration {
self.default_navigation_timeout
}
#[instrument(level = "info", skip(self), fields(context_id = %self.context_id, owned = self.owned))]
pub async fn close(&mut self) -> Result<(), ContextError> {
if self.closed {
debug!("Context already closed");
return Ok(());
}
info!("Closing browser context");
if let Some(recorder) = self.har_recorder.write().await.take() {
if let Err(e) = recorder.save().await {
debug!("Failed to auto-save HAR on close: {}", e);
} else {
debug!(path = %recorder.path().display(), "Auto-saved HAR on close");
}
}
self.event_manager.emit_close().await;
if self.owned && !self.context_id.is_empty() {
debug!("Disposing owned browser context");
self.connection
.send_command::<_, serde_json::Value>(
"Target.disposeBrowserContext",
Some(DisposeBrowserContextParams {
browser_context_id: self.context_id.clone(),
}),
None,
)
.await?;
} else {
debug!("Skipping dispose for external/default context");
}
self.event_manager.clear().await;
self.closed = true;
info!("Browser context closed");
Ok(())
}
pub fn id(&self) -> &str {
&self.context_id
}
pub fn is_closed(&self) -> bool {
self.closed
}
pub fn connection(&self) -> &Arc<CdpConnection> {
&self.connection
}
pub fn context_id(&self) -> &str {
&self.context_id
}
}