Skip to main content

playwright_cdp/cdp/
session.rs

1//! A CDP session — a session-scoped command sender plus event stream.
2//!
3//! `session_id == None` is the browser-level session; `Some(sid)` targets a
4//! specific page/worker. This is the real CDP equivalent of Playwright's
5//! `CDPSession`, speaking the protocol directly rather than proxying through
6//! a driver.
7
8use crate::cdp::connection::CdpConnection;
9use crate::cdp::messages::CdpEvent;
10use crate::error::{Error, Result};
11use serde_json::Value;
12use std::sync::atomic::{AtomicU64, Ordering};
13use std::sync::Arc;
14use std::time::Duration;
15use tokio::sync::broadcast;
16
17/// Default per-command timeout (30s), matches Playwright.
18const DEFAULT_TIMEOUT_MS: u64 = 30_000;
19
20/// A session over a [`CdpConnection`].
21pub struct CdpSession {
22    conn: Arc<CdpConnection>,
23    session_id: Option<String>,
24    timeout_ms: AtomicU64,
25}
26
27impl CdpSession {
28    /// Browser-level session (no `sessionId`).
29    pub fn browser(conn: Arc<CdpConnection>) -> Self {
30        Self {
31            conn,
32            session_id: None,
33            timeout_ms: AtomicU64::new(DEFAULT_TIMEOUT_MS),
34        }
35    }
36
37    /// Target-level session for a known `sessionId`.
38    pub fn target(conn: Arc<CdpConnection>, session_id: impl Into<String>) -> Self {
39        Self {
40            conn,
41            session_id: Some(session_id.into()),
42            timeout_ms: AtomicU64::new(DEFAULT_TIMEOUT_MS),
43        }
44    }
45
46    /// The session id, or `None` for the browser-level session.
47    pub fn session_id(&self) -> Option<&str> {
48        self.session_id.as_deref()
49    }
50
51    pub fn set_default_timeout_ms(&self, ms: u64) {
52        self.timeout_ms.store(ms, Ordering::Relaxed);
53    }
54
55    /// Send a CDP command, awaiting its response with the session's default timeout.
56    pub async fn send(&self, method: &str, params: Value) -> Result<Value> {
57        self.send_with_timeout(method, params, self.current_timeout()).await
58    }
59
60    /// Send a CDP command with an explicit timeout.
61    pub async fn send_with_timeout(
62        &self,
63        method: &str,
64        params: Value,
65        timeout: Duration,
66    ) -> Result<Value> {
67        let fut = self.conn.send_command(self.session_id.as_deref(), method, params);
68        tokio::time::timeout(timeout, fut)
69            .await
70            .map_err(|_| {
71                Error::Timeout(format!("{method} timed out after {}ms", timeout.as_millis()))
72            })?
73    }
74
75    /// Send a CDP command with no timeout (e.g. for long navigations).
76    pub async fn send_no_timeout(&self, method: &str, params: Value) -> Result<Value> {
77        self.conn.send_command(self.session_id.as_deref(), method, params).await
78    }
79
80    fn current_timeout(&self) -> Duration {
81        Duration::from_millis(self.timeout_ms.load(Ordering::Relaxed))
82    }
83
84    /// Subscribe to all events on this session. Filter by `event.method` at the
85    /// call site. Subscribe *before* issuing the command whose events you want.
86    pub fn subscribe(&self) -> broadcast::Receiver<CdpEvent> {
87        self.conn.subscribe(self.session_id.clone())
88    }
89
90    /// Back-reference to the underlying connection (for cross-session coordination).
91    pub fn connection(&self) -> &Arc<CdpConnection> {
92        &self.conn
93    }
94}