use std::collections::HashMap;
use std::sync::{Arc, Weak};
use futures::StreamExt;
use serde::Deserialize;
use tokio::sync::RwLock;
use tokio_util::sync::CancellationToken;
use tracing::{trace, warn};
use zendriver_transport::SessionHandle;
use crate::frame::Frame;
use crate::tab::TabInner;
#[derive(Debug, Deserialize)]
struct FrameAttachedEvent {
#[serde(rename = "frameId")]
frame_id: String,
#[serde(rename = "parentFrameId")]
parent_frame_id: Option<String>,
}
#[derive(Debug, Deserialize)]
struct FrameDetachedEvent {
#[serde(rename = "frameId")]
frame_id: String,
}
#[derive(Debug, Deserialize)]
struct FrameNavigatedEvent {
frame: NavigatedFrameInner,
}
#[derive(Debug, Deserialize)]
struct NavigatedFrameInner {
id: String,
#[serde(rename = "parentId", default)]
parent_id: Option<String>,
#[serde(default)]
url: String,
#[serde(default)]
name: Option<String>,
}
pub(crate) async fn run(
session: SessionHandle,
frames: Arc<RwLock<HashMap<String, Frame>>>,
tab_weak: Weak<TabInner>,
cancel: CancellationToken,
) {
let mut attached = session.subscribe::<FrameAttachedEvent>("Page.frameAttached");
let mut detached = session.subscribe::<FrameDetachedEvent>("Page.frameDetached");
let mut navigated = session.subscribe::<FrameNavigatedEvent>("Page.frameNavigated");
let enable_session = session.clone();
tokio::spawn(async move {
if let Err(e) = enable_session
.call("Page.enable", serde_json::json!({}))
.await
{
warn!(error = %e, "frame::lifecycle: Page.enable failed; frame events may be inactive");
}
});
loop {
tokio::select! {
() = cancel.cancelled() => {
trace!("frame::lifecycle: cancellation received, exiting");
return;
}
Some(ev) = attached.next() => {
let frame = Frame::new(
ev.frame_id.clone(),
ev.parent_frame_id.clone(),
String::new(),
None,
session.clone(),
tab_weak.clone(),
);
let mut map = frames.write().await;
if let Some(parent) = ev.parent_frame_id.as_deref() {
let stale: Vec<String> = map
.iter()
.filter_map(|(id, f)| {
if id == &ev.frame_id {
return None;
}
if f.inner.parent_frame_id.as_deref() != Some(parent) {
return None;
}
let url_slot = f.inner.url.try_read().ok()?;
if url_slot.is_empty() {
Some(id.clone())
} else {
None
}
})
.collect();
for id in stale {
map.remove(&id);
}
}
map.insert(ev.frame_id, frame);
}
Some(ev) = navigated.next() => {
let frame_id = ev.frame.id.clone();
let new_url = ev.frame.url.clone();
{
let map = frames.read().await;
if let Some(existing) = map.get(&frame_id) {
let mut url_slot = existing.inner.url.write().await;
*url_slot = new_url;
continue;
}
}
let frame = Frame::new(
frame_id.clone(),
ev.frame.parent_id,
ev.frame.url,
ev.frame.name,
session.clone(),
tab_weak.clone(),
);
frames.write().await.insert(frame_id, frame);
}
Some(ev) = detached.next() => {
frames.write().await.remove(&ev.frame_id);
}
else => {
trace!("frame::lifecycle: all event streams closed, exiting");
return;
}
}
}
}