mod document;
mod input;
mod network;
mod page;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use super::transport::Transport;
use super::types::*;
use crate::error::Result;
use crate::page::HeldInputState;
type TargetRegistry = Arc<Mutex<HashMap<String, Arc<TargetState>>>>;
#[derive(Default)]
struct TargetState {
actions: Arc<crate::action::ActionState>,
held_input: Arc<tokio::sync::Mutex<HeldInputState>>,
headers: Arc<super::headers::HeaderState>,
monitor: tokio::sync::Mutex<Option<Arc<super::network_idle::NetworkMonitor>>>,
}
fn state_for_target(registry: &TargetRegistry, target_id: &str) -> Arc<TargetState> {
let mut targets = registry
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
targets.entry(target_id.to_owned()).or_default().clone()
}
pub struct Connection {
transport: Arc<Transport>,
targets: TargetRegistry,
}
impl Connection {
pub(crate) fn new(transport: Transport) -> Self {
Self {
transport: Arc::new(transport),
targets: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn transport(&self) -> &Arc<Transport> {
&self.transport
}
pub(crate) async fn version(&self) -> Result<BrowserGetVersionResult> {
self.transport
.send("Browser.getVersion", &BrowserGetVersion {})
.await
}
pub(crate) async fn create_target(
&self,
url: &str,
width: Option<u32>,
height: Option<u32>,
) -> Result<String> {
let result: TargetCreateTargetResult = self
.transport
.send(
"Target.createTarget",
&TargetCreateTarget {
url: url.to_string(),
width,
height,
},
)
.await?;
Ok(result.target_id)
}
pub(crate) async fn attach_to_target(&self, target_id: &str) -> Result<Session> {
let result: TargetAttachToTargetResult = self
.transport
.send(
"Target.attachToTarget",
&TargetAttachToTarget {
target_id: target_id.to_string(),
flatten: Some(true),
},
)
.await?;
Ok(Session {
transport: Arc::clone(&self.transport),
session_id: result.session_id,
target_id: target_id.to_string(),
target: state_for_target(&self.targets, target_id),
targets: Arc::clone(&self.targets),
})
}
pub(crate) async fn close_target(&self, target_id: &str) -> Result<bool> {
let result: TargetCloseTargetResult = self
.transport
.send(
"Target.closeTarget",
&TargetCloseTarget {
target_id: target_id.to_string(),
},
)
.await?;
if result.success {
self.remove_target(target_id).await;
}
Ok(result.success)
}
pub(crate) async fn get_targets(&self) -> Result<Vec<TargetInfo>> {
let result: TargetGetTargetsResult = self
.transport
.send("Target.getTargets", &TargetGetTargets {})
.await?;
Ok(result.target_infos)
}
async fn remove_target(&self, target_id: &str) {
let state = self
.targets
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.remove(target_id);
if let Some(state) = state {
if let Some(monitor) = state.monitor.lock().await.as_ref() {
monitor.closed();
}
}
}
pub(crate) async fn activate_target(&self, target_id: &str) -> Result<()> {
self.send_void(
"Target.activateTarget",
&TargetActivateTarget {
target_id: target_id.to_string(),
},
)
.await
}
async fn send_void<C: serde::Serialize>(&self, method: &str, params: &C) -> Result<()> {
self.transport
.send::<_, serde_json::Value>(method, params)
.await?;
Ok(())
}
pub(crate) async fn close(&self) -> Result<()> {
if let Err(error) = self.send_void("Browser.close", &BrowserClose {}).await {
tracing::debug!("Browser.close command did not complete: {}", error);
}
if !self
.transport
.wait_for_owned_exit(std::time::Duration::from_secs(5))
.await?
{
tracing::warn!("Chrome did not exit after Browser.close; terminating it");
}
self.transport.close().await
}
}
#[derive(Clone)]
pub struct Session {
transport: Arc<Transport>,
session_id: String,
target_id: String,
target: Arc<TargetState>,
targets: TargetRegistry,
}
impl Session {
pub(crate) async fn send_tracked(
&self,
method: &str,
params: &serde_json::Value,
dispatch: &super::transport::CommandDispatch,
) -> Result<serde_json::Value> {
self.target.headers.wait().await?;
self.transport
.send_to_session_with_dispatch(&self.session_id, method, params, dispatch)
.await
}
pub(crate) async fn navigation_guard(&self) -> Result<tokio::sync::OwnedMutexGuard<()>> {
self.target.headers.lock().await
}
pub(crate) fn action_state(&self) -> Arc<crate::action::ActionState> {
self.target.actions.clone()
}
pub fn session_id(&self) -> &str {
&self.session_id
}
pub fn target_id(&self) -> &str {
&self.target_id
}
pub fn transport(&self) -> &Arc<Transport> {
&self.transport
}
pub(crate) fn held_input(&self) -> Arc<tokio::sync::Mutex<HeldInputState>> {
Arc::clone(&self.target.held_input)
}
pub(crate) async fn monitor_network(&self, fresh: bool) -> Result<()> {
let mut monitor = self.target.monitor.lock().await;
if monitor.is_none() {
*monitor = Some(Arc::new(
super::network_idle::NetworkMonitor::start(&self.transport, &self.target_id, fresh)
.await?,
));
}
Ok(())
}
pub(crate) async fn wait_for_network_idle(&self, idle_ms: u64, timeout_ms: u64) -> Result<()> {
tokio::time::timeout(std::time::Duration::from_millis(timeout_ms), async {
self.target.headers.wait().await?;
let monitor = self.target.monitor.lock().await.clone().ok_or_else(|| {
crate::Error::cdp_msg("Network idle monitoring is not initialized")
})?;
monitor.wait(idle_ms, timeout_ms).await
})
.await
.map_err(|_| {
crate::Error::Timeout("Network did not become idle before the deadline".into())
})?
}
pub(crate) async fn attach_frame_target(&self, target_id: &str) -> Result<Session> {
let result: TargetAttachToTargetResult = self
.transport
.send(
"Target.attachToTarget",
&TargetAttachToTarget {
target_id: target_id.to_string(),
flatten: Some(true),
},
)
.await?;
Ok(Session {
transport: Arc::clone(&self.transport),
session_id: result.session_id,
target_id: target_id.to_string(),
target: state_for_target(&self.targets, target_id),
targets: Arc::clone(&self.targets),
})
}
pub(crate) async fn detach(&self) -> Result<()> {
self.transport
.send::<_, serde_json::Value>(
"Target.detachFromTarget",
&serde_json::json!({"sessionId":self.session_id}),
)
.await?;
Ok(())
}
pub async fn send<C, R>(&self, method: &str, params: &C) -> Result<R>
where
C: serde::Serialize,
R: serde::de::DeserializeOwned,
{
self.target.headers.wait().await?;
self.transport
.send_to_session(&self.session_id, method, params)
.await
}
async fn send_void<C: serde::Serialize>(&self, method: &str, params: &C) -> Result<()> {
self.send::<_, serde_json::Value>(method, params).await?;
Ok(())
}
}