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}