use crate::browser::views::{DialogInfo, DialogType};
use crate::error::Result;
use std::collections::VecDeque;
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Debug, Clone)]
pub struct DialogManager {
client: Arc<crate::browser::cdp::CdpClient>,
pending: Arc<Mutex<VecDeque<DialogInfo>>>,
auto_dismiss: Arc<Mutex<bool>>,
}
impl DialogManager {
pub fn new(client: Arc<crate::browser::cdp::CdpClient>) -> Self {
Self {
client,
pending: Arc::new(Mutex::new(VecDeque::new())),
auto_dismiss: Arc::new(Mutex::new(false)),
}
}
pub async fn enable(&self) -> Result<()> {
self.client
.send_command("Page.enable", serde_json::json!({}))
.await?;
let pending = Arc::clone(&self.pending);
let auto_dismiss = Arc::clone(&self.auto_dismiss);
let client = Arc::clone(&self.client);
let mut rx = self.client.subscribe_events().await?;
tokio::spawn(async move {
while let Ok(event) = rx.recv().await {
if event.method == "Page.javascriptDialogOpening" {
let dialog_type = event
.params
.get("type")
.and_then(|v| v.as_str())
.map(parse_dialog_type)
.unwrap_or(DialogType::Alert);
let message = event
.params
.get("message")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let default_prompt = event
.params
.get("defaultPrompt")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let url = event
.params
.get("url")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let info = DialogInfo {
dialog_type,
message,
default_prompt,
url,
};
let should_auto_dismiss = *auto_dismiss.lock().await;
if should_auto_dismiss {
let _ = client
.send_command(
"Page.handleJavaScriptDialog",
serde_json::json!({
"accept": false,
}),
)
.await;
} else {
pending.lock().await.push_back(info);
}
}
}
});
Ok(())
}
pub async fn accept(&self, prompt_text: Option<&str>) -> Result<()> {
let mut params = serde_json::json!({ "accept": true });
if let Some(text) = prompt_text {
params["promptText"] = serde_json::json!(text);
}
self.client
.send_command("Page.handleJavaScriptDialog", params)
.await?;
self.pending.lock().await.pop_front();
Ok(())
}
pub async fn dismiss(&self) -> Result<()> {
self.client
.send_command(
"Page.handleJavaScriptDialog",
serde_json::json!({ "accept": false }),
)
.await?;
self.pending.lock().await.pop_front();
Ok(())
}
pub async fn get_pending_dialogs(&self) -> Vec<DialogInfo> {
self.pending.lock().await.iter().cloned().collect()
}
pub async fn next_dialog(&self) -> Option<DialogInfo> {
self.pending.lock().await.pop_front()
}
pub async fn has_pending_dialog(&self) -> bool {
!self.pending.lock().await.is_empty()
}
pub async fn set_auto_dismiss(&self, enabled: bool) {
*self.auto_dismiss.lock().await = enabled;
}
pub async fn clear(&self) {
self.pending.lock().await.clear();
}
}
fn parse_dialog_type(s: &str) -> DialogType {
match s {
"confirm" => DialogType::Confirm,
"prompt" => DialogType::Prompt,
"beforeunload" => DialogType::Beforeunload,
_ => DialogType::Alert,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dialog_type_serialization() {
let alert = DialogType::Alert;
let json = serde_json::to_string(&alert).unwrap();
assert_eq!(json, "\"alert\"");
let confirm = DialogType::Confirm;
let json = serde_json::to_string(&confirm).unwrap();
assert_eq!(json, "\"confirm\"");
}
#[test]
fn test_dialog_info_creation() {
let info = DialogInfo {
dialog_type: DialogType::Prompt,
message: "Enter your name".to_string(),
default_prompt: Some("John".to_string()),
url: "https://example.com".to_string(),
};
assert_eq!(info.message, "Enter your name");
assert_eq!(info.default_prompt, Some("John".to_string()));
}
#[test]
fn test_parse_dialog_type() {
assert!(matches!(parse_dialog_type("alert"), DialogType::Alert));
assert!(matches!(parse_dialog_type("confirm"), DialogType::Confirm));
assert!(matches!(parse_dialog_type("prompt"), DialogType::Prompt));
assert!(matches!(parse_dialog_type("beforeunload"), DialogType::Beforeunload));
assert!(matches!(parse_dialog_type("unknown"), DialogType::Alert));
}
}