use crate::browser::views::FileChooserInfo;
use crate::error::Result;
use std::collections::VecDeque;
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Debug, Clone)]
pub struct FileChooserManager {
client: Arc<crate::browser::cdp::CdpClient>,
pending: Arc<Mutex<VecDeque<FileChooserInfo>>>,
}
impl FileChooserManager {
pub fn new(client: Arc<crate::browser::cdp::CdpClient>) -> Self {
Self {
client,
pending: Arc::new(Mutex::new(VecDeque::new())),
}
}
pub async fn enable(&self) -> Result<()> {
self.client
.send_command("Page.enable", serde_json::json!({}))
.await?;
self.client
.send_command(
"Page.setInterceptFileChooserDialog",
serde_json::json!({ "enabled": true }),
)
.await?;
let pending = Arc::clone(&self.pending);
let mut rx = self.client.subscribe_events().await?;
tokio::spawn(async move {
while let Ok(event) = rx.recv().await {
if event.method == "Page.fileChooserOpened" {
let frame_id = event
.params
.get("frameId")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let backend_node_id = event
.params
.get("backendNodeId")
.and_then(|v| v.as_u64())
.unwrap_or(0) as u32;
let multiple = event
.params
.get("multiple")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let info = FileChooserInfo {
frame_id,
backend_node_id,
multiple,
};
pending.lock().await.push_back(info);
}
}
});
Ok(())
}
pub async fn disable(&self) -> Result<()> {
self.client
.send_command(
"Page.setInterceptFileChooserDialog",
serde_json::json!({ "enabled": false }),
)
.await?;
self.pending.lock().await.clear();
Ok(())
}
pub async fn set_files(&self, paths: Vec<String>) -> Result<()> {
let info = self
.pending
.lock()
.await
.pop_front()
.ok_or_else(|| crate::error::BrowsingError::Browser("No pending file chooser".to_string()))?;
self.client
.send_command(
"DOM.setFileInputFiles",
serde_json::json!({
"files": paths,
"backendNodeId": info.backend_node_id,
}),
)
.await?;
Ok(())
}
pub async fn get_pending(&self) -> Vec<FileChooserInfo> {
self.pending.lock().await.iter().cloned().collect()
}
pub async fn next(&self) -> Option<FileChooserInfo> {
self.pending.lock().await.pop_front()
}
pub async fn has_pending(&self) -> bool {
!self.pending.lock().await.is_empty()
}
pub async fn clear(&self) {
self.pending.lock().await.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_chooser_info_creation() {
let info = FileChooserInfo {
frame_id: "frame-1".to_string(),
backend_node_id: 42,
multiple: true,
};
assert_eq!(info.frame_id, "frame-1");
assert_eq!(info.backend_node_id, 42);
assert!(info.multiple);
}
#[test]
fn test_file_chooser_info_serialization() {
let info = FileChooserInfo {
frame_id: "frame-1".to_string(),
backend_node_id: 42,
multiple: false,
};
let json = serde_json::to_string(&info).unwrap();
assert!(json.contains("frame-1"));
assert!(json.contains("42"));
}
}