Skip to main content

headless_chrome/browser/transport/
mod.rs

1use 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    /// Returns a number based on thread-safe unique counter, incrementing it so that the
115    /// next CallId is different.
116    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        // TODO: use get_mut to get exclusive access for entire block... maybe.
129        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        // TODO: remove clone
189        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            // this iterator calls .recv() under the hood, so can block thread forever
238            // hence need for Connection Shutdown
239            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            // Need to do this because otherwise WS thread might block forever
340            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}