viewpoint_core/context/scripts/
mod.rs

1//! Context-level init script management.
2
3use tracing::{debug, instrument};
4
5use crate::error::ContextError;
6
7use super::BrowserContext;
8
9impl BrowserContext {
10    /// Add a script to be evaluated before every new page load.
11    ///
12    /// The script will run before any scripts on the page, and will persist
13    /// for all pages created in this context (including popups).
14    ///
15    /// Unlike `page.add_init_script()`, this applies to all pages in the context,
16    /// not just a single page.
17    ///
18    /// # Example
19    ///
20    /// ```no_run
21    /// use viewpoint_core::Browser;
22    ///
23    /// # async fn example() -> Result<(), viewpoint_core::CoreError> {
24    /// let browser = Browser::launch().headless(true).launch().await?;
25    /// let context = browser.new_context().await?;
26    ///
27    /// // Mock navigator.webdriver for all pages
28    /// context.add_init_script(
29    ///     "Object.defineProperty(navigator, 'webdriver', { get: () => false })"
30    /// ).await?;
31    ///
32    /// // All new pages will have this script applied
33    /// let page = context.new_page().await?;
34    /// # Ok(())
35    /// # }
36    /// ```
37    ///
38    /// # Errors
39    ///
40    /// Returns an error if the context is closed.
41    #[instrument(level = "debug", skip(self, script), fields(script_len = script.as_ref().len()))]
42    pub async fn add_init_script(&self, script: impl AsRef<str>) -> Result<(), ContextError> {
43        if self.is_closed() {
44            return Err(ContextError::Closed);
45        }
46
47        let script_content = script.as_ref().to_string();
48        debug!("Adding context-level init script");
49
50        // Store the script for future pages
51        {
52            let mut scripts = self.init_scripts.write().await;
53            scripts.push(script_content.clone());
54        }
55
56        // Apply to existing pages
57        let pages = self.pages.read().await;
58        for page in pages.iter() {
59            if !page.session_id.is_empty() {
60                use viewpoint_cdp::protocol::page::AddScriptToEvaluateOnNewDocumentParams;
61
62                let _ = self.connection()
63                    .send_command::<_, viewpoint_cdp::protocol::page::AddScriptToEvaluateOnNewDocumentResult>(
64                        "Page.addScriptToEvaluateOnNewDocument",
65                        Some(AddScriptToEvaluateOnNewDocumentParams {
66                            source: script_content.clone(),
67                            world_name: None,
68                            include_command_line_api: None,
69                            run_immediately: None,
70                        }),
71                        Some(&page.session_id),
72                    )
73                    .await;
74            }
75        }
76
77        Ok(())
78    }
79
80    /// Add an init script from a file path.
81    ///
82    /// The file contents will be read and registered as an init script for all
83    /// pages in this context.
84    ///
85    /// # Example
86    ///
87    /// ```no_run
88    /// use viewpoint_core::Browser;
89    ///
90    /// # async fn example() -> Result<(), viewpoint_core::CoreError> {
91    /// let browser = Browser::launch().headless(true).launch().await?;
92    /// let context = browser.new_context().await?;
93    ///
94    /// context.add_init_script_path("./scripts/mock-auth.js").await?;
95    /// # Ok(())
96    /// # }
97    /// ```
98    ///
99    /// # Errors
100    ///
101    /// Returns an error if the file cannot be read or the context is closed.
102    #[instrument(level = "debug", skip(self), fields(path = %path.as_ref().display()))]
103    pub async fn add_init_script_path(
104        &self,
105        path: impl AsRef<std::path::Path>,
106    ) -> Result<(), ContextError> {
107        let content = tokio::fs::read_to_string(path.as_ref())
108            .await
109            .map_err(|e| ContextError::Internal(format!("Failed to read init script file: {e}")))?;
110
111        self.add_init_script(&content).await
112    }
113
114    /// Get all context-level init scripts.
115    ///
116    /// This returns the scripts that will be applied to all new pages.
117    pub async fn init_scripts(&self) -> Vec<String> {
118        self.init_scripts.read().await.clone()
119    }
120
121    /// Apply all context-level init scripts to a page session.
122    ///
123    /// This is called internally when a new page is created.
124    pub(crate) async fn apply_init_scripts_to_session(
125        &self,
126        session_id: &str,
127    ) -> Result<(), ContextError> {
128        let scripts = self.init_scripts.read().await;
129
130        for script in scripts.iter() {
131            use viewpoint_cdp::protocol::page::AddScriptToEvaluateOnNewDocumentParams;
132
133            self.connection()
134                .send_command::<_, viewpoint_cdp::protocol::page::AddScriptToEvaluateOnNewDocumentResult>(
135                    "Page.addScriptToEvaluateOnNewDocument",
136                    Some(AddScriptToEvaluateOnNewDocumentParams {
137                        source: script.clone(),
138                        world_name: None,
139                        include_command_line_api: None,
140                        run_immediately: None,
141                    }),
142                    Some(session_id),
143                )
144                .await?;
145        }
146
147        Ok(())
148    }
149}