1use crate::connection::{CdpEvent, Connection};
2use crate::error::{CdpError, Result};
3use crate::tab::Tab;
4use serde_json::Value;
5use std::time::Duration;
6use tokio::sync::broadcast;
7use tracing;
8
9pub struct Session {
11 conn: Connection,
12}
13
14impl std::fmt::Debug for Session {
15 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 f.debug_struct("Session").finish_non_exhaustive()
17 }
18}
19
20async fn wait_for_event(
23 rx: &mut broadcast::Receiver<CdpEvent>,
24 method: &str,
25 predicate: impl Fn(&CdpEvent) -> bool + Send,
26 timeout: Duration,
27) -> Result<CdpEvent> {
28 tokio::time::timeout(timeout, async move {
29 loop {
30 match rx.recv().await {
31 Ok(event) if event.method.as_str() == method && predicate(&event) => {
32 return Ok(event);
33 }
34 Ok(_) => continue,
35 Err(broadcast::error::RecvError::Closed) => {
36 return Err(CdpError::ConnectionFailed {
37 detail: "event channel closed while waiting".into(),
38 });
39 }
40 Err(broadcast::error::RecvError::Lagged(n)) => {
41 tracing::warn!("Event receiver lagged by {n} messages");
42 continue;
43 }
44 }
45 }
46 })
47 .await
48 .map_err(|_| CdpError::NavigationTimeout {
49 url: "unknown".into(),
50 timeout: timeout.as_secs(),
51 })?
52}
53
54impl Session {
55 pub async fn connect(ws_url: &str) -> Result<Self> {
57 let conn = Connection::connect(ws_url).await?;
58 Ok(Session { conn })
59 }
60
61 pub async fn create_tab(&self, url: &str) -> Result<Tab> {
63 Tab::create(self, url).await
64 }
65
66 pub async fn evaluate(&self, tab: &Tab, js: &str) -> Result<Value> {
68 tab.evaluate(self, js).await
69 }
70
71 pub async fn navigate(&self, tab: &Tab, url: &str) -> Result<()> {
73 let conn = &self.conn;
74 let sid = tab.session_id.as_deref();
75
76 conn.call("Page.enable", serde_json::json!({}), sid).await?;
78
79 conn.call(
81 "Page.setLifecycleEventsEnabled",
82 serde_json::json!({"enabled": true}),
83 sid,
84 )
85 .await?;
86
87 let mut rx = conn.event_rx();
89
90 conn.call("Page.navigate", serde_json::json!({"url": url}), sid)
92 .await?;
93
94 let result = wait_for_event(
96 &mut rx,
97 "Page.lifecycleEvent",
98 |evt| evt.params.get("name").and_then(|v| v.as_str()) == Some("networkIdle"),
99 Duration::from_secs(10),
100 )
101 .await;
102
103 match result {
105 Ok(_) => {}
106 Err(CdpError::NavigationTimeout { .. }) => {
107 tracing::warn!("Lifecycle event timeout, falling back to readyState polling");
108 for _ in 0..10 {
110 if let Ok(val) = conn
111 .call(
112 "Runtime.evaluate",
113 serde_json::json!({
114 "expression": "document.readyState",
115 "returnByValue": true
116 }),
117 sid,
118 )
119 .await
120 {
121 if val
122 .get("result")
123 .and_then(|r| r.get("value"))
124 .and_then(|v| v.as_str())
125 == Some("complete")
126 {
127 return Ok(());
128 }
129 }
130 tokio::time::sleep(Duration::from_millis(500)).await;
131 }
132 return Err(CdpError::NavigationTimeout {
133 url: url.to_string(),
134 timeout: 15,
135 });
136 }
137 Err(e) => return Err(e),
138 }
139
140 Ok(())
141 }
142
143 pub async fn wait_for<F>(
149 &self,
150 method: &str,
151 predicate: F,
152 timeout: Duration,
153 ) -> Result<CdpEvent>
154 where
155 F: Fn(&CdpEvent) -> bool + Send + 'static,
156 {
157 let mut rx = self.conn.event_rx();
158
159 tokio::time::timeout(timeout, async move {
160 loop {
161 match rx.recv().await {
162 Ok(event) if event.method.as_str() == method && predicate(&event) => {
163 return Ok(event);
164 }
165 Ok(_) => continue,
166 Err(broadcast::error::RecvError::Closed) => {
167 return Err(CdpError::ConnectionFailed {
168 detail: "event channel closed while waiting".into(),
169 });
170 }
171 Err(broadcast::error::RecvError::Lagged(n)) => {
172 tracing::warn!("Event receiver lagged by {n} messages");
173 continue;
174 }
175 }
176 }
177 })
178 .await
179 .map_err(|_| CdpError::CdpCallFailed {
180 method: format!("wait_for({method})"),
181 detail: format!("timeout after {timeout:?}"),
182 })?
183 }
184
185 pub async fn close_tab(&self, tab: Tab) -> Result<()> {
187 tab.close(self).await
188 }
189
190 pub async fn disconnect(self) -> Result<()> {
192 self.conn.close().await;
193 Ok(())
194 }
195
196 pub fn connection(&self) -> &Connection {
198 &self.conn
199 }
200}