fix: resolve snapshot hang over remote CDP (WSS) connections (#792)

The CDP WebSocket client had three issues causing snapshot to hang
indefinitely when connected to remote browsers via WSS:

1. Binary WebSocket frames were silently dropped — remote CDP proxies
   (Browserless, Browserbase, etc.) may send large responses like
   Accessibility.getFullAXTree as Binary frames instead of Text frames.

2. Default tungstenite size limits (16 MiB frame / 64 MiB message)
   could be exceeded by large accessibility tree responses, causing the
   WebSocket connection to error out and the reader task to die.

3. When the reader task died, pending commands waited for the full
   30-second timeout instead of failing immediately.

Fixes #788

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
This commit is contained in:
Chris Tate
2026-03-14 15:46:08 -05:00
committed by GitHub
co-authored by ctate
parent 529b8acfbe
commit d4b9004a6d
+25 -4
View File
@@ -5,7 +5,7 @@ use std::sync::Arc;
use futures_util::{SinkExt, StreamExt};
use serde_json::Value;
use tokio::sync::{broadcast, oneshot, Mutex};
use tokio_tungstenite::connect_async;
use tokio_tungstenite::tungstenite::protocol::WebSocketConfig;
use tokio_tungstenite::tungstenite::Message;
use super::types::{CdpCommand, CdpEvent, CdpMessage};
@@ -40,9 +40,19 @@ pub struct CdpClient {
impl CdpClient {
pub async fn connect(url: &str) -> Result<Self, String> {
let (ws_stream, _) = connect_async(url)
.await
.map_err(|e| format!("CDP WebSocket connect failed: {}", e))?;
// Use unlimited message/frame sizes to handle large CDP responses
// (e.g. Accessibility.getFullAXTree) over remote WSS connections where
// proxies may produce frames exceeding the default 16 MiB limit.
let ws_config = WebSocketConfig {
max_message_size: None,
max_frame_size: None,
..Default::default()
};
let (ws_stream, _) =
tokio_tungstenite::connect_async_with_config(url, Some(ws_config), false)
.await
.map_err(|e| format!("CDP WebSocket connect failed: {}", e))?;
let (ws_tx, mut ws_rx) = ws_stream.split();
let ws_tx = Arc::new(Mutex::new(ws_tx));
@@ -57,8 +67,14 @@ impl CdpClient {
let reader_handle = tokio::spawn(async move {
while let Some(msg) = ws_rx.next().await {
// Accept both Text and Binary frames — remote CDP proxies
// (e.g. Browserless) may send responses as Binary frames.
let msg = match msg {
Ok(Message::Text(text)) => text,
Ok(Message::Binary(data)) => match String::from_utf8(data) {
Ok(text) => text,
Err(_) => continue,
},
Ok(Message::Close(_)) => break,
Ok(_) => continue,
Err(_) => break,
@@ -99,6 +115,11 @@ impl CdpClient {
let _ = event_tx_clone.send(event);
}
}
// Reader loop exited (connection closed or error). Drop all pending
// command senders so callers get an immediate channel-closed error
// instead of waiting for the 30-second timeout.
pending_clone.lock().await.clear();
});
Ok(Self {