1mod download_handling;
8mod event_listener;
9mod page_handlers;
10mod types;
11
12use std::collections::HashMap;
13use std::future::Future;
14use std::path::PathBuf;
15use std::sync::Arc;
16use std::time::Duration;
17
18use tokio::sync::{oneshot, Mutex, RwLock};
19use tracing::{debug, warn};
20use viewpoint_cdp::CdpConnection;
21
22use super::console::ConsoleMessage;
23use super::dialog::Dialog;
24use super::download::Download;
25use super::file_chooser::FileChooser;
26use super::frame::Frame;
27use super::page_error::PageError as PageErrorInfo;
28use crate::error::PageError;
29
30pub use types::{
31 ConsoleHandler, DialogHandler, DownloadHandler, FileChooserHandler,
32 FrameAttachedHandler, FrameDetachedHandler, FrameNavigatedHandler, PageErrorHandler,
33};
34
35use download_handling::DownloadTracker;
36
37pub struct PageEventManager {
39 connection: Arc<CdpConnection>,
41 session_id: String,
43 dialog_handler: Arc<RwLock<Option<DialogHandler>>>,
45 download_handler: Arc<RwLock<Option<DownloadHandler>>>,
47 file_chooser_handler: Arc<RwLock<Option<FileChooserHandler>>>,
49 console_handler: Arc<RwLock<Option<ConsoleHandler>>>,
51 pageerror_handler: Arc<RwLock<Option<PageErrorHandler>>>,
53 frameattached_handler: Arc<RwLock<Option<FrameAttachedHandler>>>,
55 framenavigated_handler: Arc<RwLock<Option<FrameNavigatedHandler>>>,
57 framedetached_handler: Arc<RwLock<Option<FrameDetachedHandler>>>,
59 downloads: Arc<Mutex<HashMap<String, DownloadTracker>>>,
61 download_dir: PathBuf,
63 file_chooser_intercepted: Arc<RwLock<bool>>,
65 wait_for_dialog_tx: Arc<Mutex<Option<oneshot::Sender<Dialog>>>>,
67 wait_for_download_tx: Arc<Mutex<Option<oneshot::Sender<Download>>>>,
69 wait_for_file_chooser_tx: Arc<Mutex<Option<oneshot::Sender<FileChooser>>>>,
71 wait_for_console_tx: Arc<Mutex<Option<oneshot::Sender<ConsoleMessage>>>>,
73 wait_for_pageerror_tx: Arc<Mutex<Option<oneshot::Sender<PageErrorInfo>>>>,
75}
76
77impl std::fmt::Debug for PageEventManager {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 f.debug_struct("PageEventManager")
81 .field("session_id", &self.session_id)
82 .field("download_dir", &self.download_dir)
83 .finish_non_exhaustive()
84 }
85}
86
87impl PageEventManager {
88 pub fn new(connection: Arc<CdpConnection>, session_id: String) -> Self {
90 let download_dir = std::env::temp_dir().join("viewpoint-downloads");
91 let manager = Self {
92 connection: connection.clone(),
93 session_id: session_id.clone(),
94 dialog_handler: Arc::new(RwLock::new(None)),
95 download_handler: Arc::new(RwLock::new(None)),
96 file_chooser_handler: Arc::new(RwLock::new(None)),
97 console_handler: Arc::new(RwLock::new(None)),
98 pageerror_handler: Arc::new(RwLock::new(None)),
99 frameattached_handler: Arc::new(RwLock::new(None)),
100 framenavigated_handler: Arc::new(RwLock::new(None)),
101 framedetached_handler: Arc::new(RwLock::new(None)),
102 downloads: Arc::new(Mutex::new(HashMap::new())),
103 download_dir,
104 file_chooser_intercepted: Arc::new(RwLock::new(false)),
105 wait_for_dialog_tx: Arc::new(Mutex::new(None)),
106 wait_for_download_tx: Arc::new(Mutex::new(None)),
107 wait_for_file_chooser_tx: Arc::new(Mutex::new(None)),
108 wait_for_console_tx: Arc::new(Mutex::new(None)),
109 wait_for_pageerror_tx: Arc::new(Mutex::new(None)),
110 };
111
112 manager.start_event_listener();
114
115 manager
116 }
117
118 fn start_event_listener(&self) {
120 event_listener::start_event_listener(
121 self.connection.clone(),
122 self.session_id.clone(),
123 self.console_handler.clone(),
124 self.pageerror_handler.clone(),
125 self.dialog_handler.clone(),
126 self.frameattached_handler.clone(),
127 self.framenavigated_handler.clone(),
128 self.framedetached_handler.clone(),
129 self.wait_for_console_tx.clone(),
130 self.wait_for_pageerror_tx.clone(),
131 self.wait_for_dialog_tx.clone(),
132 self.download_handler.clone(),
134 self.downloads.clone(),
135 self.wait_for_download_tx.clone(),
136 );
137 }
138
139 pub async fn set_dialog_handler<F, Fut>(&self, handler: F)
141 where
142 F: Fn(Dialog) -> Fut + Send + Sync + 'static,
143 Fut: Future<Output = Result<(), PageError>> + Send + 'static,
144 {
145 let mut dialog_handler = self.dialog_handler.write().await;
146 *dialog_handler = Some(Box::new(move |dialog| {
147 Box::pin(handler(dialog))
148 }));
149 }
150
151 pub async fn remove_dialog_handler(&self) {
153 let mut dialog_handler = self.dialog_handler.write().await;
154 *dialog_handler = None;
155 }
156
157 pub async fn set_download_handler<F, Fut>(&self, handler: F)
159 where
160 F: Fn(Download) -> Fut + Send + Sync + 'static,
161 Fut: Future<Output = ()> + Send + 'static,
162 {
163 let mut download_handler = self.download_handler.write().await;
164 *download_handler = Some(Box::new(move |download| {
165 Box::pin(handler(download))
166 }));
167 }
168
169 pub async fn set_file_chooser_handler<F, Fut>(&self, handler: F)
171 where
172 F: Fn(FileChooser) -> Fut + Send + Sync + 'static,
173 Fut: Future<Output = ()> + Send + 'static,
174 {
175 let mut file_chooser_handler = self.file_chooser_handler.write().await;
176 *file_chooser_handler = Some(Box::new(move |chooser| {
177 Box::pin(handler(chooser))
178 }));
179 }
180
181 pub async fn set_console_handler<F, Fut>(&self, handler: F)
183 where
184 F: Fn(ConsoleMessage) -> Fut + Send + Sync + 'static,
185 Fut: Future<Output = ()> + Send + 'static,
186 {
187 let mut console_handler = self.console_handler.write().await;
188 *console_handler = Some(Box::new(move |message| {
189 Box::pin(handler(message))
190 }));
191 }
192
193 pub async fn remove_console_handler(&self) {
195 let mut console_handler = self.console_handler.write().await;
196 *console_handler = None;
197 }
198
199 pub async fn set_pageerror_handler<F, Fut>(&self, handler: F)
201 where
202 F: Fn(PageErrorInfo) -> Fut + Send + Sync + 'static,
203 Fut: Future<Output = ()> + Send + 'static,
204 {
205 let mut pageerror_handler = self.pageerror_handler.write().await;
206 *pageerror_handler = Some(Box::new(move |error| {
207 Box::pin(handler(error))
208 }));
209 }
210
211 pub async fn remove_pageerror_handler(&self) {
213 let mut pageerror_handler = self.pageerror_handler.write().await;
214 *pageerror_handler = None;
215 }
216
217 pub async fn set_frameattached_handler<F, Fut>(&self, handler: F)
219 where
220 F: Fn(Frame) -> Fut + Send + Sync + 'static,
221 Fut: Future<Output = ()> + Send + 'static,
222 {
223 let mut frameattached_handler = self.frameattached_handler.write().await;
224 *frameattached_handler = Some(Box::new(move |frame| {
225 Box::pin(handler(frame))
226 }));
227 }
228
229 pub async fn remove_frameattached_handler(&self) {
231 let mut frameattached_handler = self.frameattached_handler.write().await;
232 *frameattached_handler = None;
233 }
234
235 pub async fn set_framenavigated_handler<F, Fut>(&self, handler: F)
237 where
238 F: Fn(Frame) -> Fut + Send + Sync + 'static,
239 Fut: Future<Output = ()> + Send + 'static,
240 {
241 let mut framenavigated_handler = self.framenavigated_handler.write().await;
242 *framenavigated_handler = Some(Box::new(move |frame| {
243 Box::pin(handler(frame))
244 }));
245 }
246
247 pub async fn remove_framenavigated_handler(&self) {
249 let mut framenavigated_handler = self.framenavigated_handler.write().await;
250 *framenavigated_handler = None;
251 }
252
253 pub async fn set_framedetached_handler<F, Fut>(&self, handler: F)
255 where
256 F: Fn(Frame) -> Fut + Send + Sync + 'static,
257 Fut: Future<Output = ()> + Send + 'static,
258 {
259 let mut framedetached_handler = self.framedetached_handler.write().await;
260 *framedetached_handler = Some(Box::new(move |frame| {
261 Box::pin(handler(frame))
262 }));
263 }
264
265 pub async fn remove_framedetached_handler(&self) {
267 let mut framedetached_handler = self.framedetached_handler.write().await;
268 *framedetached_handler = None;
269 }
270
271 pub async fn handle_frame_attached(&self, frame: Frame) {
273 let handler = self.frameattached_handler.read().await;
274 if let Some(ref h) = *handler {
275 h(frame).await;
276 }
277 }
278
279 pub async fn handle_frame_navigated(&self, frame: Frame) {
281 let handler = self.framenavigated_handler.read().await;
282 if let Some(ref h) = *handler {
283 h(frame).await;
284 }
285 }
286
287 pub async fn handle_frame_detached(&self, frame: Frame) {
289 let handler = self.framedetached_handler.read().await;
290 if let Some(ref h) = *handler {
291 h(frame).await;
292 }
293 }
294
295 pub async fn set_intercept_file_chooser(&self, enabled: bool) -> Result<(), PageError> {
297 *self.file_chooser_intercepted.write().await = enabled;
298
299 self.connection
300 .send_command::<_, serde_json::Value>(
301 "Page.setInterceptFileChooserDialog",
302 Some(serde_json::json!({ "enabled": enabled })),
303 Some(&self.session_id),
304 )
305 .await?;
306
307 Ok(())
308 }
309
310 pub async fn set_download_behavior(&self, allow: bool) -> Result<(), PageError> {
312 if allow {
314 tokio::fs::create_dir_all(&self.download_dir).await.map_err(|e| {
315 PageError::EvaluationFailed(format!("Failed to create download directory: {e}"))
316 })?;
317 }
318
319 let behavior = if allow { "allow" } else { "deny" };
320
321 self.connection
322 .send_command::<_, serde_json::Value>(
323 "Browser.setDownloadBehavior",
324 Some(serde_json::json!({
325 "behavior": behavior,
326 "downloadPath": self.download_dir.to_string_lossy(),
327 "eventsEnabled": true,
328 })),
329 Some(&self.session_id),
330 )
331 .await?;
332
333 Ok(())
334 }
335
336 pub async fn handle_dialog_event(
338 &self,
339 dialog_type: viewpoint_cdp::protocol::DialogType,
340 message: String,
341 default_prompt: Option<String>,
342 ) {
343 let dialog = Dialog::new(
344 self.connection.clone(),
345 self.session_id.clone(),
346 dialog_type,
347 message,
348 default_prompt,
349 );
350
351 {
353 let mut waiter = self.wait_for_dialog_tx.lock().await;
354 if let Some(tx) = waiter.take() {
355 let _ = tx.send(dialog);
356 return;
357 }
358 }
359
360 let handler = self.dialog_handler.read().await;
362 if let Some(ref h) = *handler {
363 if let Err(e) = h(dialog).await {
364 warn!("Dialog handler failed: {}", e);
365 }
366 } else {
367 debug!("Auto-dismissing dialog (no handler registered)");
369 if let Err(e) = dialog.dismiss().await {
370 warn!("Failed to auto-dismiss dialog: {}", e);
371 }
372 }
373 }
374
375 pub async fn handle_download_begin(
377 &self,
378 guid: String,
379 suggested_filename: String,
380 url: String,
381 ) {
382 download_handling::handle_download_begin(
383 &self.connection,
384 &self.session_id,
385 &self.downloads,
386 &self.download_handler,
387 &self.wait_for_download_tx,
388 guid,
389 suggested_filename,
390 url,
391 )
392 .await;
393 }
394
395 pub async fn handle_download_progress(&self, guid: String, state: &str) {
397 download_handling::handle_download_progress(&self.downloads, guid, state).await;
398 }
399
400 pub async fn handle_file_chooser_event(
402 &self,
403 frame_id: String,
404 mode: viewpoint_cdp::protocol::page::FileChooserMode,
405 backend_node_id: Option<i32>,
406 ) {
407 download_handling::handle_file_chooser_event(
408 &self.connection,
409 &self.session_id,
410 &self.file_chooser_handler,
411 &self.wait_for_file_chooser_tx,
412 frame_id,
413 mode,
414 backend_node_id,
415 )
416 .await;
417 }
418
419 pub async fn wait_for_dialog(&self, timeout: Duration) -> Result<Dialog, PageError> {
421 let (tx, rx) = oneshot::channel();
422 {
423 let mut waiter = self.wait_for_dialog_tx.lock().await;
424 *waiter = Some(tx);
425 }
426
427 tokio::time::timeout(timeout, rx)
428 .await
429 .map_err(|_| PageError::EvaluationFailed("Timeout waiting for dialog".to_string()))?
430 .map_err(|_| PageError::EvaluationFailed("Dialog wait cancelled".to_string()))
431 }
432
433 pub async fn wait_for_download(&self, timeout: Duration) -> Result<Download, PageError> {
435 download_handling::wait_for_download(&self.wait_for_download_tx, timeout).await
436 }
437
438 pub async fn wait_for_file_chooser(&self, timeout: Duration) -> Result<FileChooser, PageError> {
440 download_handling::wait_for_file_chooser(&self.wait_for_file_chooser_tx, timeout).await
441 }
442
443 pub async fn wait_for_console(&self, timeout: Duration) -> Result<ConsoleMessage, PageError> {
445 let (tx, rx) = oneshot::channel();
446 {
447 let mut waiter = self.wait_for_console_tx.lock().await;
448 *waiter = Some(tx);
449 }
450
451 tokio::time::timeout(timeout, rx)
452 .await
453 .map_err(|_| PageError::EvaluationFailed("Timeout waiting for console message".to_string()))?
454 .map_err(|_| PageError::EvaluationFailed("Console wait cancelled".to_string()))
455 }
456
457 pub async fn wait_for_pageerror(&self, timeout: Duration) -> Result<PageErrorInfo, PageError> {
459 let (tx, rx) = oneshot::channel();
460 {
461 let mut waiter = self.wait_for_pageerror_tx.lock().await;
462 *waiter = Some(tx);
463 }
464
465 tokio::time::timeout(timeout, rx)
466 .await
467 .map_err(|_| PageError::EvaluationFailed("Timeout waiting for page error".to_string()))?
468 .map_err(|_| PageError::EvaluationFailed("Page error wait cancelled".to_string()))
469 }
470}