viewpoint_core/context/page_management/
mod.rs

1//! Page creation and management within a browser context.
2
3use tracing::{debug, info, instrument};
4
5use viewpoint_cdp::protocol::target_domain::{GetTargetsParams, GetTargetsResult};
6
7use crate::context::page_factory;
8use crate::error::ContextError;
9use crate::page::Page;
10
11use super::{BrowserContext, PageInfo};
12
13impl BrowserContext {
14    /// Create a new page in this context.
15    ///
16    /// # Errors
17    ///
18    /// Returns an error if page creation fails.
19    #[instrument(level = "info", skip(self), fields(context_id = %self.context_id))]
20    pub async fn new_page(&self) -> Result<Page, ContextError> {
21        if self.closed {
22            return Err(ContextError::Closed);
23        }
24
25        info!("Creating new page");
26
27        // Create target and attach to it
28        let (create_result, attach_result) =
29            page_factory::create_and_attach_target(&self.connection, &self.context_id).await?;
30
31        let target_id = &create_result.target_id;
32        let session_id = &attach_result.session_id;
33
34        // Enable required CDP domains on the page
35        page_factory::enable_page_domains(&self.connection, session_id).await?;
36
37        // Apply emulation settings (viewport, touch, locale, etc.)
38        page_factory::apply_emulation_settings(&self.connection, session_id, &self.options).await?;
39
40        // Get the main frame ID
41        let frame_id = page_factory::get_main_frame_id(&self.connection, session_id).await?;
42
43        // Track the page
44        page_factory::track_page(
45            &self.pages,
46            create_result.target_id.clone(),
47            attach_result.session_id.clone(),
48        )
49        .await;
50
51        // Apply context-level init scripts to the new page
52        if let Err(e) = self.apply_init_scripts_to_session(session_id).await {
53            debug!("Failed to apply init scripts: {}", e);
54        }
55
56        info!(target_id = %target_id, session_id = %session_id, frame_id = %frame_id, "Page created successfully");
57
58        // Get the test ID attribute from context
59        let test_id_attr = self.test_id_attribute.read().await.clone();
60
61        // Convert context HTTP credentials to network auth credentials
62        let http_credentials = page_factory::convert_http_credentials(&self.options);
63
64        // Convert context proxy credentials to network auth proxy credentials
65        let proxy_credentials = page_factory::convert_proxy_credentials(&self.options);
66
67        // Get the next page index for this context
68        let page_index = self.next_page_index();
69
70        // Create page with or without video recording
71        let page = page_factory::create_page_instance(
72            self.connection.clone(),
73            create_result,
74            attach_result,
75            frame_id,
76            self.context_index,
77            page_index,
78            &self.options,
79            test_id_attr,
80            self.route_registry.clone(),
81            http_credentials,
82            proxy_credentials,
83        )
84        .await;
85
86        // Enable Fetch domain if there are context-level routes
87        // This ensures requests are intercepted for context routes
88        if let Err(e) = page.enable_fetch_for_context_routes().await {
89            debug!("Failed to enable Fetch for context routes: {}", e);
90        }
91
92        // Emit page event to registered handlers
93        self.event_manager.emit_page(page.clone_internal()).await;
94
95        Ok(page)
96    }
97
98    /// Get all pages in this context.
99    ///
100    /// # Errors
101    ///
102    /// Returns an error if querying targets fails.
103    pub async fn pages(&self) -> Result<Vec<PageInfo>, ContextError> {
104        if self.closed {
105            return Err(ContextError::Closed);
106        }
107
108        let result: GetTargetsResult = self
109            .connection
110            .send_command("Target.getTargets", Some(GetTargetsParams::default()), None)
111            .await?;
112
113        let pages: Vec<PageInfo> = result
114            .target_infos
115            .into_iter()
116            .filter(|t| {
117                // For the default context (empty string ID), match targets with no context ID
118                // or with an empty context ID
119                let matches_context = if self.context_id.is_empty() {
120                    // Default context: match targets without a context ID or with empty context ID
121                    t.browser_context_id.as_deref().is_none()
122                        || t.browser_context_id.as_deref() == Some("")
123                } else {
124                    // Named context: exact match
125                    t.browser_context_id.as_deref() == Some(&self.context_id)
126                };
127                matches_context && t.target_type == "page"
128            })
129            .map(|t| PageInfo {
130                target_id: t.target_id,
131                session_id: String::new(), // Would need to track sessions
132            })
133            .collect();
134
135        Ok(pages)
136    }
137}