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 /// ```ignore
21 /// // Mock navigator.webdriver for all pages
22 /// context.add_init_script(
23 /// "Object.defineProperty(navigator, 'webdriver', { get: () => false })"
24 /// ).await?;
25 ///
26 /// // All new pages will have this script applied
27 /// let page = context.new_page().await?;
28 /// ```
29 ///
30 /// # Errors
31 ///
32 /// Returns an error if the context is closed.
33 #[instrument(level = "debug", skip(self, script), fields(script_len = script.as_ref().len()))]
34 pub async fn add_init_script(&self, script: impl AsRef<str>) -> Result<(), ContextError> {
35 if self.is_closed() {
36 return Err(ContextError::Closed);
37 }
38
39 let script_content = script.as_ref().to_string();
40 debug!("Adding context-level init script");
41
42 // Store the script for future pages
43 {
44 let mut scripts = self.init_scripts.write().await;
45 scripts.push(script_content.clone());
46 }
47
48 // Apply to existing pages
49 let pages = self.pages.read().await;
50 for page in pages.iter() {
51 if !page.session_id.is_empty() {
52 use viewpoint_cdp::protocol::page::AddScriptToEvaluateOnNewDocumentParams;
53
54 let _ = self.connection()
55 .send_command::<_, viewpoint_cdp::protocol::page::AddScriptToEvaluateOnNewDocumentResult>(
56 "Page.addScriptToEvaluateOnNewDocument",
57 Some(AddScriptToEvaluateOnNewDocumentParams {
58 source: script_content.clone(),
59 world_name: None,
60 include_command_line_api: None,
61 run_immediately: None,
62 }),
63 Some(&page.session_id),
64 )
65 .await;
66 }
67 }
68
69 Ok(())
70 }
71
72 /// Add an init script from a file path.
73 ///
74 /// The file contents will be read and registered as an init script for all
75 /// pages in this context.
76 ///
77 /// # Example
78 ///
79 /// ```ignore
80 /// context.add_init_script_path("./scripts/mock-auth.js").await?;
81 /// ```
82 ///
83 /// # Errors
84 ///
85 /// Returns an error if the file cannot be read or the context is closed.
86 #[instrument(level = "debug", skip(self), fields(path = %path.as_ref().display()))]
87 pub async fn add_init_script_path(&self, path: impl AsRef<std::path::Path>) -> Result<(), ContextError> {
88 let content = tokio::fs::read_to_string(path.as_ref()).await.map_err(|e| {
89 ContextError::Internal(format!("Failed to read init script file: {e}"))
90 })?;
91
92 self.add_init_script(&content).await
93 }
94
95 /// Get all context-level init scripts.
96 ///
97 /// This returns the scripts that will be applied to all new pages.
98 pub async fn init_scripts(&self) -> Vec<String> {
99 self.init_scripts.read().await.clone()
100 }
101
102 /// Apply all context-level init scripts to a page session.
103 ///
104 /// This is called internally when a new page is created.
105 pub(crate) async fn apply_init_scripts_to_session(&self, session_id: &str) -> Result<(), ContextError> {
106 let scripts = self.init_scripts.read().await;
107
108 for script in scripts.iter() {
109 use viewpoint_cdp::protocol::page::AddScriptToEvaluateOnNewDocumentParams;
110
111 self.connection()
112 .send_command::<_, viewpoint_cdp::protocol::page::AddScriptToEvaluateOnNewDocumentResult>(
113 "Page.addScriptToEvaluateOnNewDocument",
114 Some(AddScriptToEvaluateOnNewDocumentParams {
115 source: script.clone(),
116 world_name: None,
117 include_command_line_api: None,
118 run_immediately: None,
119 }),
120 Some(session_id),
121 )
122 .await?;
123 }
124
125 Ok(())
126 }
127}