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(
92 "Page.navigate",
93 serde_json::json!({"url": url}),
94 sid,
95 )
96 .await?;
97
98 let result = wait_for_event(
100 &mut rx,
101 "Page.lifecycleEvent",
102 |evt| evt.params.get("name").and_then(|v| v.as_str()) == Some("networkIdle"),
103 Duration::from_secs(10),
104 )
105 .await;
106
107 match result {
109 Ok(_) => {}
110 Err(CdpError::NavigationTimeout { .. }) => {
111 tracing::warn!("Lifecycle event timeout, falling back to readyState polling");
112 for _ in 0..10 {
114 if let Ok(val) = conn
115 .call(
116 "Runtime.evaluate",
117 serde_json::json!({
118 "expression": "document.readyState",
119 "returnByValue": true
120 }),
121 sid,
122 )
123 .await
124 {
125 if val
126 .get("result")
127 .and_then(|r| r.get("value"))
128 .and_then(|v| v.as_str())
129 == Some("complete")
130 {
131 return Ok(());
132 }
133 }
134 tokio::time::sleep(Duration::from_millis(500)).await;
135 }
136 return Err(CdpError::NavigationTimeout {
137 url: url.to_string(),
138 timeout: 15,
139 });
140 }
141 Err(e) => return Err(e),
142 }
143
144 Ok(())
145 }
146
147 pub async fn wait_for<F>(
153 &self,
154 method: &str,
155 predicate: F,
156 timeout: Duration,
157 ) -> Result<CdpEvent>
158 where
159 F: Fn(&CdpEvent) -> bool + Send + 'static,
160 {
161 let mut rx = self.conn.event_rx();
162
163 tokio::time::timeout(timeout, async move {
164 loop {
165 match rx.recv().await {
166 Ok(event) if event.method.as_str() == method && predicate(&event) => {
167 return Ok(event);
168 }
169 Ok(_) => continue,
170 Err(broadcast::error::RecvError::Closed) => {
171 return Err(CdpError::ConnectionFailed {
172 detail: "event channel closed while waiting".into(),
173 });
174 }
175 Err(broadcast::error::RecvError::Lagged(n)) => {
176 tracing::warn!("Event receiver lagged by {n} messages");
177 continue;
178 }
179 }
180 }
181 })
182 .await
183 .map_err(|_| CdpError::CdpCallFailed {
184 method: format!("wait_for({method})"),
185 detail: format!("timeout after {timeout:?}"),
186 })?
187 }
188
189 pub async fn close_tab(&self, tab: Tab) -> Result<()> {
191 tab.close(self).await
192 }
193
194 pub async fn disconnect(self) -> Result<()> {
196 self.conn.close().await;
197 Ok(())
198 }
199
200 pub fn connection(&self) -> &Connection {
202 &self.conn
203 }
204}