diff --git a/.changes/init-script-on-all-frames.md b/.changes/init-script-on-all-frames.md new file mode 100644 index 000000000..68253a799 --- /dev/null +++ b/.changes/init-script-on-all-frames.md @@ -0,0 +1,9 @@ +--- +tauri: minor:feat +tauri-runtime: minor:feat +--- + +- add API to run initialization scripts on all frames + - `WebviewBuilder::initialization_script_on_all_frames` + - `WebviewWindowBuilder::initialization_script_on_all_frames` + - `WebviewAttributes::initialization_script_on_all_frames` diff --git a/crates/tauri-runtime-wry/src/lib.rs b/crates/tauri-runtime-wry/src/lib.rs index 570e75505..b0d4ebd47 100644 --- a/crates/tauri-runtime-wry/src/lib.rs +++ b/crates/tauri-runtime-wry/src/lib.rs @@ -4557,7 +4557,8 @@ fn create_webview( )); for script in webview_attributes.initialization_scripts { - webview_builder = webview_builder.with_initialization_script(&script); + webview_builder = webview_builder + .with_initialization_script_for_main_only(&script.script, script.for_main_frame_only); } for (scheme, protocol) in uri_scheme_protocols { diff --git a/crates/tauri-runtime/src/webview.rs b/crates/tauri-runtime/src/webview.rs index 895c10141..a6d7bc24d 100644 --- a/crates/tauri-runtime/src/webview.rs +++ b/crates/tauri-runtime/src/webview.rs @@ -197,7 +197,15 @@ impl> PartialEq for DetachedWebview { pub struct WebviewAttributes { pub url: WebviewUrl, pub user_agent: Option, - pub initialization_scripts: Vec, + /// A list of initialization javascript scripts to run when loading new pages. + /// When webview load a new page, this initialization code will be executed. + /// It is guaranteed that code is executed before `window.onload`. + /// + /// ## Platform-specific + /// + /// - **Android on Wry:** The Android WebView does not provide an API for initialization scripts, + /// so we prepend them to each HTML head. They are only implemented on custom protocol URLs. + pub initialization_scripts: Vec, pub data_directory: Option, pub drag_drop_handler_enabled: bool, pub clipboard: bool, @@ -307,10 +315,46 @@ impl WebviewAttributes { self } - /// Sets the init script. + /// Adds an init script for the main frame. + /// + /// When webview load a new page, this initialization code will be executed. + /// It is guaranteed that code is executed before `window.onload`. + /// + /// This is executed only on the main frame. + /// If you only want to run it in all frames, use [Self::initialization_script_on_all_frames] instead. + /// + /// + /// ## Platform-specific + /// + /// - **Android on Wry:** The Android WebView does not provide an API for initialization scripts, + /// so we prepend them to each HTML head. They are only implemented on custom protocol URLs. #[must_use] pub fn initialization_script(mut self, script: &str) -> Self { - self.initialization_scripts.push(script.to_string()); + self.initialization_scripts.push(InitializationScript { + script: script.to_string(), + for_main_frame_only: true, + }); + self + } + + /// Adds an init script for all frames. + /// + /// When webview load a new page, this initialization code will be executed. + /// It is guaranteed that code is executed before `window.onload`. + /// + /// This is executed on all frames, main frame and also sub frames. + /// If you only want to run it in the main frame, use [Self::initialization_script] instead. + /// + /// ## Platform-specific + /// + /// - **Android on Wry:** The Android WebView does not provide an API for initialization scripts, + /// so we prepend them to each HTML head. They are only implemented on custom protocol URLs. + #[must_use] + pub fn initialization_script_on_all_frames(mut self, script: &str) -> Self { + self.initialization_scripts.push(InitializationScript { + script: script.to_string(), + for_main_frame_only: false, + }); self } @@ -499,3 +543,21 @@ impl WebviewAttributes { /// IPC handler. pub type WebviewIpcHandler = Box, Request) + Send>; + +/// An initialization script +#[derive(Debug, Clone)] +pub struct InitializationScript { + /// The script to run + pub script: String, + /// Whether the script should be injected to main frame only + pub for_main_frame_only: bool, +} + +impl InitializationScript { + pub fn new(script: &str, for_main_frame_only: bool) -> Self { + Self { + script: script.to_owned(), + for_main_frame_only, + } + } +} diff --git a/crates/tauri/src/manager/webview.rs b/crates/tauri/src/manager/webview.rs index 042ba47a8..f07bbd338 100644 --- a/crates/tauri/src/manager/webview.rs +++ b/crates/tauri/src/manager/webview.rs @@ -13,7 +13,7 @@ use std::{ use serde::Serialize; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; use tauri_runtime::{ - webview::{DetachedWebview, PendingWebview}, + webview::{DetachedWebview, InitializationScript, PendingWebview}, window::DragDropEvent, }; use tauri_utils::config::WebviewUrl; @@ -211,9 +211,15 @@ impl WebviewManager { } } - webview_attributes - .initialization_scripts - .splice(0..0, all_initialization_scripts); + webview_attributes.initialization_scripts.splice( + 0..0, + all_initialization_scripts + .into_iter() + .map(|script| InitializationScript { + script, + for_main_frame_only: true, + }), + ); pending.webview_attributes = webview_attributes; @@ -527,13 +533,17 @@ impl WebviewManager { os_name: &'a str, } - pending.webview_attributes.initialization_scripts.push( - HotkeyZoom { - os_name: std::env::consts::OS, - } - .render_default(&Default::default())? - .into_string(), - ) + pending + .webview_attributes + .initialization_scripts + .push(InitializationScript { + script: HotkeyZoom { + os_name: std::env::consts::OS, + } + .render_default(&Default::default())? + .into_string(), + for_main_frame_only: true, + }) } #[cfg(feature = "isolation")] diff --git a/crates/tauri/src/webview/mod.rs b/crates/tauri/src/webview/mod.rs index a7161f8ac..7b8aaecf0 100644 --- a/crates/tauri/src/webview/mod.rs +++ b/crates/tauri/src/webview/mod.rs @@ -20,7 +20,7 @@ use tauri_runtime::{ WindowDispatch, }; use tauri_runtime::{ - webview::{DetachedWebview, PendingWebview, WebviewAttributes}, + webview::{DetachedWebview, InitializationScript, PendingWebview, WebviewAttributes}, WebviewDispatch, }; pub use tauri_utils::config::Color; @@ -634,9 +634,12 @@ impl WebviewBuilder { /// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created, /// but before the HTML document has been parsed and before any other script included by the HTML document is run. /// - /// Since it runs on all top-level document and child frame page navigations, + /// Since it runs on all top-level document navigations, /// it's recommended to check the `window.location` to guard your script from running on unexpected origins. /// + /// This is executed only on the main frame. + /// If you only want to run it in all frames, use [Self::initialization_script_for_all_frames] instead. + /// /// # Examples /// #[cfg_attr( @@ -671,7 +674,60 @@ fn main() { self .webview_attributes .initialization_scripts - .push(script.to_string()); + .push(InitializationScript { + script: script.to_string(), + for_main_frame_only: true, + }); + self + } + + /// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created, + /// but before the HTML document has been parsed and before any other script included by the HTML document is run. + /// + /// Since it runs on all top-level document navigations and also child frame page navigations, + /// it's recommended to check the `window.location` to guard your script from running on unexpected origins. + /// + /// This is executed on all frames, main frame and also sub frames. + /// If you only want to run it in the main frame, use [Self::initialization_script] instead. + /// + /// # Examples + /// + #[cfg_attr( + feature = "unstable", + doc = r####" +```rust +use tauri::{WindowBuilder, Runtime}; + +const INIT_SCRIPT: &str = r#" + if (window.location.origin === 'https://tauri.app') { + console.log("hello world from js init script"); + + window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' }; + } +"#; + +fn main() { + tauri::Builder::default() + .setup(|app| { + let window = tauri::window::WindowBuilder::new(app, "label").build()?; + let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::App("index.html".into())) + .initialization_script_for_all_frames(INIT_SCRIPT); + let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?; + Ok(()) + }); +} +``` + "#### + )] + #[must_use] + pub fn initialization_script_for_all_frames(mut self, script: &str) -> Self { + self + .webview_attributes + .initialization_scripts + .push(InitializationScript { + script: script.to_string(), + for_main_frame_only: false, + }); self } diff --git a/crates/tauri/src/webview/webview_window.rs b/crates/tauri/src/webview/webview_window.rs index dc798efd2..993b54536 100644 --- a/crates/tauri/src/webview/webview_window.rs +++ b/crates/tauri/src/webview/webview_window.rs @@ -765,9 +765,12 @@ impl> WebviewWindowBuilder<'_, R, M> { /// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created, /// but before the HTML document has been parsed and before any other script included by the HTML document is run. /// - /// Since it runs on all top-level document and child frame page navigations, + /// Since it runs on all top-level document navigations (and also child frame page navigations, if you set `run_only_on_main_frame` to false), /// it's recommended to check the `window.location` to guard your script from running on unexpected origins. /// + /// This is executed only on the main frame. + /// If you only want to run it in all frames, use [Self::initialization_script_for_all_frames] instead. + /// /// # Examples /// /// ```rust @@ -797,6 +800,45 @@ impl> WebviewWindowBuilder<'_, R, M> { self } + /// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created, + /// but before the HTML document has been parsed and before any other script included by the HTML document is run. + /// + /// Since it runs on all top-level document navigastions (and also child frame page navigations, if you set `run_only_on_main_frame` to false), + /// it's recommended to check the `window.location` to guard your script from running on unexpected origins. + /// + /// This is executed on all frames, main frame and also sub frames. + /// If you only want to run it in the main frame, use [Self::initialization_script] instead. + /// # Examples + /// + /// ```rust + /// use tauri::{WebviewWindowBuilder, Runtime}; + /// + /// const INIT_SCRIPT: &str = r#" + /// if (window.location.origin === 'https://tauri.app') { + /// console.log("hello world from js init script"); + /// + /// window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' }; + /// } + /// "#; + /// + /// fn main() { + /// tauri::Builder::default() + /// .setup(|app| { + /// let webview = tauri::WebviewWindowBuilder::new(app, "label", tauri::WebviewUrl::App("index.html".into())) + /// .initialization_script_for_all_frames(INIT_SCRIPT) + /// .build()?; + /// Ok(()) + /// }); + /// } + /// ``` + #[must_use] + pub fn initialization_script_for_all_frames(mut self, script: &str) -> Self { + self.webview_builder = self + .webview_builder + .initialization_script_for_all_frames(script); + self + } + /// Set the user agent for the webview #[must_use] pub fn user_agent(mut self, user_agent: &str) -> Self {