use std::sync::Arc;
use headless_chrome::Browser;
use crate::pool::BrowserPoolInner;
use crate::tracked::TrackedBrowser;
pub struct BrowserHandle {
tracked: Option<Arc<TrackedBrowser>>,
pool: Arc<BrowserPoolInner>,
}
impl BrowserHandle {
pub(crate) fn new(tracked: Arc<TrackedBrowser>, pool: Arc<BrowserPoolInner>) -> Self {
Self {
tracked: Some(tracked),
pool,
}
}
pub fn id(&self) -> u64 {
self.tracked.as_ref().map(|t| t.id()).unwrap_or(0)
}
pub fn age(&self) -> std::time::Duration {
self.tracked.as_ref().map(|t| t.age()).unwrap_or_default()
}
pub fn age_minutes(&self) -> u64 {
self.tracked.as_ref().map(|t| t.age_minutes()).unwrap_or(0)
}
pub fn mark_unhealthy(&self) {
if let Some(tracked) = &self.tracked {
tracked.mark_unhealthy();
}
}
}
impl std::ops::Deref for BrowserHandle {
type Target = Browser;
fn deref(&self) -> &Self::Target {
self.tracked.as_ref().unwrap().browser()
}
}
impl Drop for BrowserHandle {
fn drop(&mut self) {
if let Some(tracked) = self.tracked.take() {
log::debug!(
"♻️ BrowserHandle {} being dropped, returning to pool...",
tracked.id()
);
BrowserPoolInner::return_browser(&self.pool, tracked);
}
}
}
impl std::fmt::Debug for BrowserHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.tracked {
Some(tracked) => f
.debug_struct("BrowserHandle")
.field("id", &tracked.id())
.field("age_minutes", &tracked.age_minutes())
.finish(),
None => f
.debug_struct("BrowserHandle")
.field("state", &"returned")
.finish(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::BrowserPoolConfig;
use crate::factory::mock::MockBrowserFactory;
use crate::pool::BrowserPoolInner;
fn create_test_pool_inner() -> Arc<BrowserPoolInner> {
Arc::new(BrowserPoolInner::new_for_test(
BrowserPoolConfig::default(),
Box::new(MockBrowserFactory::always_fails("test")),
tokio::runtime::Handle::current(),
))
}
#[tokio::test]
async fn test_handle_id_returns_zero_when_tracked_is_none() {
let handle = BrowserHandle {
tracked: None,
pool: create_test_pool_inner(),
};
assert_eq!(handle.id(), 0);
assert_eq!(handle.age_minutes(), 0);
assert_eq!(handle.age(), std::time::Duration::default());
}
#[tokio::test]
async fn test_handle_debug_when_returned() {
let handle = BrowserHandle {
tracked: None,
pool: create_test_pool_inner(),
};
let debug_output = format!("{:?}", handle);
assert!(debug_output.contains("returned"));
assert!(!debug_output.contains("age_minutes"));
}
#[test]
#[ignore = "Requires launching a real Chrome process for TrackedBrowser initialization"]
fn test_handle_debug_when_active() {
}
}