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