playwright_rs/protocol/
debugger.rs1use crate::error::Result;
35use crate::server::channel::Channel;
36use crate::server::channel_owner::{
37 ChannelOwner, ChannelOwnerImpl, DisposeReason, ParentOrConnection,
38};
39use crate::server::connection::ConnectionLike;
40use parking_lot::Mutex;
41use serde_json::Value;
42use std::any::Any;
43use std::future::Future;
44use std::pin::Pin;
45use std::sync::Arc;
46
47type PausedHandlerFuture = Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>;
48type PausedHandler =
49 Arc<dyn Fn(Option<PausedDetails>) -> PausedHandlerFuture + Send + Sync + 'static>;
50
51#[derive(Debug, Clone, PartialEq, Eq)]
54#[non_exhaustive]
55pub struct PausedDetails {
56 pub location: PausedLocation,
58 pub title: String,
60 pub stack: Option<String>,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
66#[non_exhaustive]
67pub struct PausedLocation {
68 pub file: String,
69 pub line: Option<i64>,
70 pub column: Option<i64>,
71}
72
73#[derive(Clone)]
76pub struct Debugger {
77 base: ChannelOwnerImpl,
78 paused_details: Arc<Mutex<Option<PausedDetails>>>,
79 paused_handlers: Arc<Mutex<Vec<PausedHandler>>>,
80}
81
82impl Debugger {
83 pub fn new(
84 parent: ParentOrConnection,
85 type_name: String,
86 guid: Arc<str>,
87 initializer: Value,
88 ) -> Result<Self> {
89 Ok(Self {
90 base: ChannelOwnerImpl::new(parent, type_name, guid, initializer),
91 paused_details: Arc::new(Mutex::new(None)),
92 paused_handlers: Arc::new(Mutex::new(Vec::new())),
93 })
94 }
95
96 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
100 pub async fn request_pause(&self) -> Result<()> {
101 self.channel()
102 .send_no_result("requestPause", serde_json::json!({}))
103 .await
104 }
105
106 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
108 pub async fn resume(&self) -> Result<()> {
109 self.channel()
110 .send_no_result("resume", serde_json::json!({}))
111 .await
112 }
113
114 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
116 pub async fn next(&self) -> Result<()> {
117 self.channel()
118 .send_no_result("next", serde_json::json!({}))
119 .await
120 }
121
122 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
124 pub async fn run_to(&self, location: PausedLocation) -> Result<()> {
125 let mut loc = serde_json::json!({ "file": location.file });
126 if let Some(line) = location.line {
127 loc["line"] = serde_json::json!(line);
128 }
129 if let Some(column) = location.column {
130 loc["column"] = serde_json::json!(column);
131 }
132 self.channel()
133 .send_no_result("runTo", serde_json::json!({ "location": loc }))
134 .await
135 }
136
137 pub fn paused_details(&self) -> Option<PausedDetails> {
141 self.paused_details.lock().clone()
142 }
143
144 pub fn is_paused(&self) -> bool {
146 self.paused_details.lock().is_some()
147 }
148
149 pub fn on_paused_state_changed<F, Fut>(&self, handler: F)
153 where
154 F: Fn(Option<PausedDetails>) -> Fut + Send + Sync + 'static,
155 Fut: Future<Output = Result<()>> + Send + 'static,
156 {
157 let h: PausedHandler = Arc::new(move |d| -> PausedHandlerFuture { Box::pin(handler(d)) });
158 self.paused_handlers.lock().push(h);
159 }
160}
161
162fn parse_paused_details(params: &Value) -> Option<PausedDetails> {
163 let pd = params.get("pausedDetails")?;
164 if pd.is_null() {
165 return None;
166 }
167 let location = pd.get("location")?;
168 let file = location.get("file")?.as_str()?.to_string();
169 let line = location.get("line").and_then(|v| v.as_i64());
170 let column = location.get("column").and_then(|v| v.as_i64());
171 let title = pd.get("title")?.as_str()?.to_string();
172 let stack = pd.get("stack").and_then(|v| v.as_str()).map(String::from);
173 Some(PausedDetails {
174 location: PausedLocation { file, line, column },
175 title,
176 stack,
177 })
178}
179
180impl ChannelOwner for Debugger {
181 fn guid(&self) -> &str {
182 self.base.guid()
183 }
184 fn type_name(&self) -> &str {
185 self.base.type_name()
186 }
187 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
188 self.base.parent()
189 }
190 fn connection(&self) -> Arc<dyn ConnectionLike> {
191 self.base.connection()
192 }
193 fn initializer(&self) -> &Value {
194 self.base.initializer()
195 }
196 fn channel(&self) -> &Channel {
197 self.base.channel()
198 }
199 fn dispose(&self, reason: DisposeReason) {
200 self.base.dispose(reason)
201 }
202 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
203 self.base.adopt(child)
204 }
205 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
206 self.base.add_child(guid, child)
207 }
208 fn remove_child(&self, guid: &str) {
209 self.base.remove_child(guid)
210 }
211
212 fn on_event(&self, method: &str, params: Value) {
213 if method == "pausedStateChanged" {
214 let details = parse_paused_details(¶ms);
215 *self.paused_details.lock() = details.clone();
216 let handlers = self.paused_handlers.lock().clone();
217 for h in handlers {
218 let d = details.clone();
219 tokio::spawn(async move {
220 if let Err(e) = h(d).await {
221 tracing::warn!("Debugger paused-state handler error: {}", e);
222 }
223 });
224 }
225 }
226 self.base.on_event(method, params);
227 }
228
229 fn was_collected(&self) -> bool {
230 self.base.was_collected()
231 }
232
233 fn as_any(&self) -> &dyn Any {
234 self
235 }
236}
237
238impl std::fmt::Debug for Debugger {
239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 f.debug_struct("Debugger")
241 .field("guid", &self.guid())
242 .field("paused", &self.is_paused())
243 .finish()
244 }
245}