playwright_cdp/
browser.rs1use crate::browser_context::BrowserContext;
4use crate::browser_process::{resolve_ws_endpoint, BrowserProcess};
5use crate::cdp::connection::CdpConnection;
6use crate::cdp::session::CdpSession;
7use crate::error::Result;
8use crate::options::{ConnectOverCdpOptions, LaunchOptions};
9use crate::page::Page;
10use parking_lot::Mutex;
11use serde_json::json;
12use std::sync::Arc;
13
14#[derive(Clone)]
19pub struct Browser {
20 inner: Arc<BrowserInner>,
21}
22
23struct BrowserInner {
24 conn: Arc<CdpConnection>,
25 browser_session: CdpSession,
26 process: Mutex<Option<BrowserProcess>>,
27 contexts: Mutex<Vec<BrowserContext>>,
28}
29
30impl Browser {
31 pub async fn launch(opts: LaunchOptions) -> Result<Browser> {
33 let process = BrowserProcess::launch(&opts).await?;
34 let ws_url = process.ws_url().to_string();
35 let (conn, browser_session) = connect(&ws_url, None).await?;
36 Ok(Self {
37 inner: Arc::new(BrowserInner {
38 conn,
39 browser_session,
40 process: Mutex::new(Some(process)),
41 contexts: Mutex::new(Vec::new()),
42 }),
43 })
44 }
45
46 pub async fn connect_over_cdp(
49 endpoint: &str,
50 opts: Option<ConnectOverCdpOptions>,
51 ) -> Result<Browser> {
52 let ws_url = resolve_ws_endpoint(endpoint).await?;
53 let headers = opts.and_then(|o| o.headers);
54 let (conn, browser_session) = connect(&ws_url, headers).await?;
55 Ok(Self {
56 inner: Arc::new(BrowserInner {
57 conn,
58 browser_session,
59 process: Mutex::new(None),
60 contexts: Mutex::new(Vec::new()),
61 }),
62 })
63 }
64
65 pub fn connection(&self) -> &Arc<CdpConnection> {
67 &self.inner.conn
68 }
69
70 pub fn new_browser_cdp_session(&self) -> CdpSession {
73 CdpSession::browser(self.inner.conn.clone())
74 }
75
76 pub async fn new_context(&self) -> Result<BrowserContext> {
78 BrowserContext::create(self.clone(), None).await
79 }
80
81 pub async fn new_page(&self) -> Result<Page> {
83 let ctx = BrowserContext::default_for(self.clone());
84 ctx.new_page().await
85 }
86
87 pub async fn version(&self) -> Result<String> {
89 let v = self
90 .inner
91 .browser_session
92 .send("Browser.getVersion", json!({}))
93 .await?;
94 Ok(v.get("product")
95 .and_then(|x| x.as_str())
96 .unwrap_or("unknown")
97 .to_string())
98 }
99
100 pub fn is_connected(&self) -> bool {
102 true
106 }
107
108 pub fn browser_type(&self) -> crate::browser_type::BrowserType {
110 crate::browser_type::BrowserType::chromium()
111 }
112
113 pub fn contexts(&self) -> Vec<BrowserContext> {
115 self.inner.contexts.lock().clone()
116 }
117
118 pub fn on_disconnected<F, Fut>(&self, handler: F)
120 where
121 F: FnOnce() -> Fut + Send + 'static,
122 Fut: std::future::Future<Output = ()> + Send + 'static,
123 {
124 let mut rx = self.inner.conn.subscribe(None);
125 tokio::spawn(async move {
126 loop {
127 match rx.recv().await {
128 Err(tokio::sync::broadcast::error::RecvError::Closed) => {
129 handler().await;
130 return;
131 }
132 _ => {}
134 }
135 }
136 });
137 }
138
139 pub async fn close(&self) -> Result<()> {
141 let _ = self
143 .inner
144 .browser_session
145 .send("Browser.close", json!({}))
146 .await;
147 if let Some(mut process) = self.inner.process.lock().take() {
148 let _ = process.kill().await;
149 }
150 let _ = self.inner.conn.close().await;
151 Ok(())
152 }
153
154 pub(crate) fn browser_session(&self) -> &CdpSession {
156 &self.inner.browser_session
157 }
158
159 pub(crate) fn track_context(&self, ctx: BrowserContext) {
161 self.inner.contexts.lock().push(ctx);
162 }
163}
164
165impl Drop for BrowserInner {
166 fn drop(&mut self) {
167 }
169}
170
171async fn connect(
174 ws_url: &str,
175 headers: Option<std::collections::HashMap<String, String>>,
176) -> Result<(Arc<CdpConnection>, CdpSession)> {
177 let (conn, _browser_rx) = CdpConnection::connect(ws_url, headers).await?;
178 let session = CdpSession::browser(conn.clone());
179 let _ = session
182 .send(
183 "Target.setAutoAttach",
184 json!({"autoAttach": true, "waitForDebuggerOnStart": false, "flatten": true}),
185 )
186 .await;
187 Ok((conn, session))
188}