headless_chrome/browser/transport/
mod.rs1use std::collections::HashMap;
2use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
3use std::sync::mpsc;
4use std::sync::mpsc::Sender;
5use std::sync::mpsc::{Receiver, RecvTimeoutError, TryRecvError};
6use std::sync::Arc;
7use std::sync::Mutex;
8use std::time::Duration;
9
10use anyhow::Result;
11
12use thiserror::Error;
13
14use log::{error, info, trace, warn};
15
16use url::Url;
17use waiting_call_registry::WaitingCallRegistry;
18use web_socket_connection::WebSocketConnection;
19
20use crate::protocol::cdp::{types::Event, types::Method, Target};
21
22use crate::types::{parse_raw_message, parse_response, CallId, Message};
23
24use crate::util;
25
26mod waiting_call_registry;
27mod web_socket_connection;
28
29#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30pub struct SessionId(String);
31
32pub enum MethodDestination {
33 Target(SessionId),
34 Browser,
35}
36
37impl SessionId {
38 pub fn as_str(&self) -> &str {
39 &self.0
40 }
41}
42
43impl From<String> for SessionId {
44 fn from(session_id: String) -> Self {
45 Self(session_id)
46 }
47}
48
49#[derive(Debug, Eq, PartialEq, Hash)]
50enum ListenerId {
51 SessionId(SessionId),
52 Browser,
53}
54
55type Listeners = Arc<Mutex<HashMap<ListenerId, Sender<Event>>>>;
56
57#[derive(Debug)]
58pub struct Transport {
59 web_socket_connection: Arc<WebSocketConnection>,
60 waiting_call_registry: Arc<WaitingCallRegistry>,
61 listeners: Listeners,
62 open: Arc<AtomicBool>,
63 call_id_counter: Arc<AtomicU32>,
64 loop_shutdown_tx: Mutex<mpsc::SyncSender<()>>,
65 idle_browser_timeout: Duration,
66}
67
68#[derive(Debug, Error)]
69#[error("Unable to make method calls because underlying connection is closed")]
70pub struct ConnectionClosed {}
71
72impl Transport {
73 pub fn new(
74 ws_url: Url,
75 process_id: Option<u32>,
76 idle_browser_timeout: Duration,
77 ) -> Result<Self> {
78 let (messages_tx, messages_rx) = mpsc::channel();
79 let web_socket_connection =
80 Arc::new(WebSocketConnection::new(&ws_url, process_id, messages_tx)?);
81
82 let waiting_call_registry = Arc::new(WaitingCallRegistry::new());
83
84 let listeners = Arc::new(Mutex::new(HashMap::new()));
85
86 let open = Arc::new(AtomicBool::new(true));
87
88 let (shutdown_tx, shutdown_rx) = mpsc::sync_channel(100);
89
90 let guarded_shutdown_tx = Mutex::new(shutdown_tx);
91
92 Self::handle_incoming_messages(
93 messages_rx,
94 Arc::clone(&waiting_call_registry),
95 Arc::clone(&listeners),
96 Arc::clone(&open),
97 Arc::clone(&web_socket_connection),
98 shutdown_rx,
99 process_id,
100 idle_browser_timeout,
101 );
102
103 Ok(Self {
104 web_socket_connection,
105 waiting_call_registry,
106 listeners,
107 open,
108 call_id_counter: Arc::new(AtomicU32::new(0)),
109 loop_shutdown_tx: guarded_shutdown_tx,
110 idle_browser_timeout,
111 })
112 }
113
114 pub fn unique_call_id(&self) -> CallId {
117 self.call_id_counter.fetch_add(1, Ordering::SeqCst)
118 }
119
120 pub fn call_method<C>(
121 &self,
122 method: C,
123 destination: MethodDestination,
124 ) -> Result<C::ReturnObject>
125 where
126 C: Method + serde::Serialize,
127 {
128 if !self.open.load(Ordering::SeqCst) {
130 return Err(ConnectionClosed {}.into());
131 }
132 let call_id = self.unique_call_id();
133 let call = method.to_method_call(call_id);
134
135 let message_text = serde_json::to_string(&call)?;
136
137 let response_rx = self.waiting_call_registry.register_call(call.id);
138
139 match destination {
140 MethodDestination::Target(session_id) => {
141 let message = message_text.clone();
142 let target_method = Target::SendMessageToTarget {
143 target_id: None,
144 session_id: Some(session_id.0),
145 message,
146 };
147 trace!(
148 "Msg to tab: {}",
149 message_text.chars().take(300).collect::<String>()
150 );
151 if let Err(e) = self.call_method_on_browser(target_method) {
152 warn!("Failed to call method on browser: {:?}", e);
153 self.waiting_call_registry.unregister_call(call.id);
154 trace!("Unregistered callback: {:?}", call.id);
155 return Err(e);
156 }
157 }
158 MethodDestination::Browser => {
159 if let Err(e) = self.web_socket_connection.send_message(&message_text) {
160 self.waiting_call_registry.unregister_call(call.id);
161 return Err(e);
162 }
163 trace!("sent method call to browser via websocket");
164 }
165 }
166
167 let params_string = format!("{:?}", call.get_params());
168 trace!(
169 "waiting for response from call registry: {} {:?}",
170 &call_id,
171 params_string.chars().take(400).collect::<String>()
172 );
173
174 let response_result = util::Wait::new(self.idle_browser_timeout, Duration::from_millis(5))
175 .until(|| response_rx.try_recv().ok());
176 trace!("received response for: {} {:?}", &call_id, params_string);
177 parse_response::<C::ReturnObject>((response_result?)?)
178 }
179
180 pub fn call_method_on_target<C>(
181 &self,
182 session_id: SessionId,
183 method: C,
184 ) -> Result<C::ReturnObject>
185 where
186 C: Method + serde::Serialize,
187 {
188 self.call_method(method, MethodDestination::Target(session_id))
190 }
191
192 pub fn call_method_on_browser<C>(&self, method: C) -> Result<C::ReturnObject>
193 where
194 C: Method + serde::Serialize,
195 {
196 self.call_method(method, MethodDestination::Browser)
197 }
198
199 pub fn listen_to_browser_events(&self) -> Receiver<Event> {
200 let (events_tx, events_rx) = mpsc::channel();
201
202 let mut listeners = self.listeners.lock().unwrap();
203 listeners.insert(ListenerId::Browser, events_tx);
204
205 events_rx
206 }
207
208 pub fn listen_to_target_events(&self, session_id: SessionId) -> Receiver<Event> {
209 let (events_tx, events_rx) = mpsc::channel();
210
211 let mut listeners = self.listeners.lock().unwrap();
212 listeners.insert(ListenerId::SessionId(session_id), events_tx);
213
214 events_rx
215 }
216
217 pub fn shutdown(&self) {
218 self.web_socket_connection.shutdown();
219 let shutdown_tx = self.loop_shutdown_tx.lock().unwrap();
220 let _ = shutdown_tx.send(());
221 }
222
223 #[allow(clippy::too_many_arguments)]
224 fn handle_incoming_messages(
225 messages_rx: Receiver<Message>,
226 waiting_call_registry: Arc<WaitingCallRegistry>,
227 listeners: Listeners,
228 open: Arc<AtomicBool>,
229 conn: Arc<WebSocketConnection>,
230 shutdown_rx: Receiver<()>,
231 process_id: Option<u32>,
232 idle_browser_timeout: Duration,
233 ) {
234 trace!("Starting handle_incoming_messages");
235 std::thread::spawn(move || {
236 trace!("Inside handle_incoming_messages thread");
237 loop {
240 match shutdown_rx.try_recv() {
241 Ok(()) | Err(TryRecvError::Disconnected) => {
242 info!("Transport incoming message loop loop received shutdown message");
243 break;
244 }
245 Err(TryRecvError::Empty) => {}
246 }
247 match messages_rx.recv_timeout(idle_browser_timeout) {
248 Err(recv_timeout_error) => {
249 match recv_timeout_error {
250 RecvTimeoutError::Timeout => {
251 error!(
252 "Transport loop got a timeout while listening for messages (Chrome #{:?})",
253 process_id
254 );
255 }
256 RecvTimeoutError::Disconnected => {
257 error!(
258 "Transport loop got disconnected from WS's sender (Chrome #{:?})",
259 process_id
260 );
261 }
262 }
263 break;
264 }
265 Ok(message) => match message {
266 Message::ConnectionShutdown => {
267 info!("Received shutdown message");
268 break;
269 }
270 Message::Response(response_to_browser_method_call) => {
271 if waiting_call_registry
272 .resolve_call(response_to_browser_method_call)
273 .is_err()
274 {
275 warn!("The browser registered a call but then closed its receiving channel");
276 break;
277 }
278 }
279
280 Message::Event(browser_event) => match browser_event {
281 Event::ReceivedMessageFromTarget(target_message_event) => {
282 let session_id = target_message_event.params.session_id.into();
283 let raw_message = target_message_event.params.message;
284
285 let msg_res = parse_raw_message(&raw_message);
286 match msg_res {
287 Ok(target_message) => match target_message {
288 Message::Event(target_event) => {
289 if let Some(tx) = listeners
290 .lock()
291 .unwrap()
292 .get(&ListenerId::SessionId(session_id))
293 {
294 tx.send(target_event)
295 .expect("Couldn't send event to listener");
296 }
297 }
298
299 Message::Response(resp) => {
300 if waiting_call_registry.resolve_call(resp).is_err() {
301 warn!("The browser registered a call but then closed its receiving channel");
302 break;
303 }
304 }
305 Message::ConnectionShutdown => {}
306 },
307 Err(e) => {
308 trace!(
309 "Message from target isn't recognised: {:?} - {}",
310 &raw_message,
311 e,
312 );
313 }
314 }
315 }
316
317 _ => {
318 if let Some(tx) =
319 listeners.lock().unwrap().get(&ListenerId::Browser)
320 {
321 if let Err(err) = tx.send(browser_event.clone()) {
322 let event_string = format!("{browser_event:?}");
323 warn!(
324 "Couldn't send browser an event: {:?}\n{:?}",
325 event_string.chars().take(400).collect::<String>(),
326 err
327 );
328 break;
329 }
330 }
331 }
332 },
333 },
334 }
335 }
336
337 info!("Shutting down message handling loop");
338
339 conn.shutdown();
341
342 open.store(false, Ordering::SeqCst);
343 waiting_call_registry.cancel_outstanding_method_calls();
344 let mut listeners = listeners.lock().unwrap();
345 *listeners = HashMap::new();
346 info!("cleared listeners, I think");
347 });
348 }
349}
350
351impl Drop for Transport {
352 fn drop(&mut self) {
353 info!("dropping transport");
354 }
355}