use std::time::Duration;
use serde_json::{Value, json};
use tokio::sync::broadcast::error::RecvError;
use tokio::time::{Instant, timeout};
use super::core::CdpCore;
use super::tab::ChromiumTab;
use crate::Result;
pub struct ChildTarget {
pub target_id: String,
pub session_id: String,
pub kind: String,
pub url: String,
tab: ChromiumTab,
}
impl ChildTarget {
pub fn tab(&self) -> &ChromiumTab {
&self.tab
}
pub fn into_tab(self) -> ChromiumTab {
self.tab
}
pub fn url_contains(&self, s: &str) -> bool {
self.url.contains(s)
}
pub fn is_iframe(&self) -> bool {
self.kind == "iframe"
}
}
impl ChromiumTab {
pub async fn attach_oopifs(&self, settle: Duration) -> Result<Vec<ChildTarget>> {
let mut events = self.core.conn.subscribe();
self.enable_auto_attach().await?;
let mut out: Vec<ChildTarget> = Vec::new();
let deadline = Instant::now() + settle;
loop {
let remain = deadline.saturating_duration_since(Instant::now());
if remain.is_zero() {
break;
}
let ev = match timeout(remain, events.recv()).await {
Ok(Ok(ev)) => ev,
Ok(Err(RecvError::Lagged(_))) => continue,
Ok(Err(RecvError::Closed)) => break,
Err(_) => break, };
if ev.method != "Target.attachedToTarget" {
continue;
}
if let Some(child) = self.child_from_event(&ev.params) {
if !out.iter().any(|c| c.session_id == child.session_id) {
out.push(child);
}
}
}
Ok(out)
}
pub async fn wait_oopif(
&self,
url_substr: &str,
timeout_dur: Option<Duration>,
) -> Result<Option<ChildTarget>> {
let mut events = self.core.conn.subscribe();
self.enable_auto_attach().await?;
let dur = timeout_dur.unwrap_or_else(|| self.core.timeout());
let deadline = Instant::now() + dur;
loop {
let remain = deadline.saturating_duration_since(Instant::now());
if remain.is_zero() {
return Ok(None);
}
let ev = match timeout(remain, events.recv()).await {
Ok(Ok(ev)) => ev,
Ok(Err(RecvError::Lagged(_))) => continue,
Ok(Err(RecvError::Closed)) => return Ok(None),
Err(_) => return Ok(None),
};
if ev.method != "Target.attachedToTarget" {
continue;
}
if let Some(child) = self.child_from_event(&ev.params) {
if child.url.contains(url_substr) {
return Ok(Some(child));
}
}
}
}
async fn enable_auto_attach(&self) -> Result<()> {
self.core
.send(
"Target.setAutoAttach",
json!({
"autoAttach": true,
"waitForDebuggerOnStart": false,
"flatten": true,
}),
)
.await?;
Ok(())
}
fn child_from_event(&self, params: &Value) -> Option<ChildTarget> {
let session_id = params["sessionId"].as_str()?.to_string();
let ti = ¶ms["targetInfo"];
let target_id = ti["targetId"].as_str().unwrap_or_default().to_string();
let kind = ti["type"].as_str().unwrap_or_default().to_string();
let url = ti["url"].as_str().unwrap_or_default().to_string();
let core = CdpCore::new(
self.core.conn.clone(),
session_id.clone(),
target_id.clone(),
self.core.download_dir(),
self.core.browser_context_id.clone(),
);
Some(ChildTarget {
target_id,
session_id,
kind,
url,
tab: ChromiumTab::new(core),
})
}
}