viewpoint_core/context/page_management/
mod.rs1use std::sync::Arc;
4use std::time::Duration;
5
6use tokio::sync::oneshot;
7use tracing::{debug, info, instrument};
8
9use viewpoint_cdp::protocol::target_domain::{
10 CreateTargetParams, CreateTargetResult, GetTargetsParams, GetTargetsResult,
11};
12
13use crate::error::ContextError;
14use crate::page::Page;
15
16use super::{BrowserContext, PageInfo};
17
18impl BrowserContext {
19 #[instrument(level = "info", skip(self), fields(context_id = %self.context_id))]
29 pub async fn new_page(&self) -> Result<Page, ContextError> {
30 if self.closed {
31 return Err(ContextError::Closed);
32 }
33
34 info!("Creating new page");
35
36 let (tx, rx) = oneshot::channel::<Page>();
38 let tx = Arc::new(tokio::sync::Mutex::new(Some(tx)));
39
40 let tx_clone = tx.clone();
42 let handler_id = self
43 .event_manager
44 .on_page(move |page| {
45 let tx = tx_clone.clone();
46 async move {
47 let mut guard = tx.lock().await;
48 if let Some(sender) = guard.take() {
49 let _ = sender.send(page);
50 }
51 }
52 })
53 .await;
54
55 let create_result: Result<CreateTargetResult, _> = self
58 .connection
59 .send_command(
60 "Target.createTarget",
61 Some(CreateTargetParams {
62 url: "about:blank".to_string(),
63 width: None,
64 height: None,
65 browser_context_id: Some(self.context_id.clone()),
66 background: None,
67 new_window: None,
68 }),
69 None,
70 )
71 .await;
72
73 if let Err(e) = create_result {
75 self.event_manager.off_page(handler_id).await;
77 return Err(e.into());
78 }
79
80 let timeout_duration = Duration::from_secs(30);
82 let page_result = tokio::time::timeout(timeout_duration, rx).await;
83
84 self.event_manager.off_page(handler_id).await;
86
87 match page_result {
89 Ok(Ok(page)) => {
90 if let Err(e) = self.apply_init_scripts_to_session(page.session_id()).await {
92 debug!("Failed to apply init scripts: {}", e);
93 }
94
95 info!(
96 target_id = %page.target_id(),
97 session_id = %page.session_id(),
98 "Page created successfully"
99 );
100
101 Ok(page)
102 }
103 Ok(Err(_)) => Err(ContextError::Internal(
104 "Page channel closed unexpectedly".to_string(),
105 )),
106 Err(_) => Err(ContextError::Timeout {
107 operation: "new_page".to_string(),
108 duration: timeout_duration,
109 }),
110 }
111 }
112
113 pub async fn pages(&self) -> Result<Vec<PageInfo>, ContextError> {
119 if self.closed {
120 return Err(ContextError::Closed);
121 }
122
123 let result: GetTargetsResult = self
124 .connection
125 .send_command("Target.getTargets", Some(GetTargetsParams::default()), None)
126 .await?;
127
128 let pages: Vec<PageInfo> = result
129 .target_infos
130 .into_iter()
131 .filter(|t| {
132 let matches_context = if self.context_id.is_empty() {
135 t.browser_context_id.as_deref().is_none()
137 || t.browser_context_id.as_deref() == Some("")
138 } else {
139 t.browser_context_id.as_deref() == Some(&self.context_id)
141 };
142 matches_context && t.target_type == "page"
143 })
144 .map(|t| PageInfo {
145 target_id: t.target_id,
146 session_id: String::new(), })
148 .collect();
149
150 Ok(pages)
151 }
152}