From 3e22b8ba4a62009f33470543dcfd9cfb3cbabbed Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 12 Jun 2025 11:45:50 +0800 Subject: [PATCH 1/5] feat: allow all frame init scripts in plugin --- crates/tauri-runtime/src/webview.rs | 10 +-- crates/tauri/src/manager/webview.rs | 54 +++++++-------- crates/tauri/src/plugin.rs | 103 ++++++++++++++++++++++++---- crates/tauri/src/webview/mod.rs | 4 +- 4 files changed, 124 insertions(+), 47 deletions(-) diff --git a/crates/tauri-runtime/src/webview.rs b/crates/tauri-runtime/src/webview.rs index fb186695fa68..330356b39b8b 100644 --- a/crates/tauri-runtime/src/webview.rs +++ b/crates/tauri-runtime/src/webview.rs @@ -376,11 +376,12 @@ impl WebviewAttributes { /// 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. + /// If you only want to run it in all frames, use [`Self::initialization_script_on_all_frames`] instead. /// /// ## Platform-specific /// - /// - **Android on Wry:** When [addDocumentStartJavaScript] is not supported, + /// - **Windows:** scripts are always added to subframes. + /// - **Android:** When [addDocumentStartJavaScript] is not supported, /// we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs). /// For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts. /// @@ -401,11 +402,12 @@ impl WebviewAttributes { /// 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. + /// If you only want to run it in the main frame, use [`Self::initialization_script`] instead. /// /// ## Platform-specific /// - /// - **Android on Wry:** When [addDocumentStartJavaScript] is not supported, + /// - **Windows:** scripts are always added to subframes. + /// - **Android:** When [addDocumentStartJavaScript] is not supported, /// we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs). /// For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts. /// diff --git a/crates/tauri/src/manager/webview.rs b/crates/tauri/src/manager/webview.rs index f9c0436b2570..a9022f31b015 100644 --- a/crates/tauri/src/manager/webview.rs +++ b/crates/tauri/src/manager/webview.rs @@ -143,13 +143,21 @@ impl WebviewManager { crate::Pattern::Isolation { schema, .. } => { crate::pattern::format_real_schema(schema, use_https_scheme) } - _ => "".to_string(), + _ => "".to_owned(), }, } .render_default(&Default::default())?; - let mut all_initialization_scripts: Vec = vec![]; - all_initialization_scripts.push( + let mut all_initialization_scripts: Vec = vec![]; + + fn main_frame_script(script: String) -> InitializationScript { + InitializationScript { + script, + for_main_frame_only: true, + } + } + + all_initialization_scripts.push(main_frame_script( r" Object.defineProperty(window, 'isTauri', { value: true, @@ -163,10 +171,10 @@ impl WebviewManager { }) } " - .to_string(), - ); - all_initialization_scripts.push(self.invoke_initialization_script.to_string()); - all_initialization_scripts.push(format!( + .to_owned(), + )); + all_initialization_scripts.push(main_frame_script(self.invoke_initialization_script.clone())); + all_initialization_scripts.push(main_frame_script(format!( r#" Object.defineProperty(window.__TAURI_INTERNALS__, 'metadata', {{ value: {{ @@ -177,45 +185,37 @@ impl WebviewManager { "#, current_window_label = serde_json::to_string(window_label)?, current_webview_label = serde_json::to_string(&label)?, - )); - all_initialization_scripts.push(self.initialization_script( + ))); + all_initialization_scripts.push(main_frame_script(self.initialization_script( app_manager, &ipc_init.into_string(), &pattern_init.into_string(), use_https_scheme, - )?); + )?)); - for plugin_init_script in plugin_init_scripts { - all_initialization_scripts.push(plugin_init_script.to_string()); - } + all_initialization_scripts.extend(plugin_init_scripts); #[cfg(feature = "isolation")] if let crate::Pattern::Isolation { schema, .. } = &*app_manager.pattern { - all_initialization_scripts.push( + all_initialization_scripts.push(main_frame_script( IsolationJavascript { isolation_src: &crate::pattern::format_real_schema(schema, use_https_scheme), style: tauri_utils::pattern::isolation::IFRAME_STYLE, } .render_default(&Default::default())? - .to_string(), - ); + .into_string(), + )); } if let Some(plugin_global_api_scripts) = &*app_manager.plugin_global_api_scripts { - for script in plugin_global_api_scripts.iter() { - all_initialization_scripts.push(script.to_string()); + for &script in plugin_global_api_scripts.iter() { + all_initialization_scripts.push(main_frame_script(script.to_owned())); } } - webview_attributes.initialization_scripts.splice( - 0..0, - all_initialization_scripts - .into_iter() - .map(|script| InitializationScript { - script, - for_main_frame_only: true, - }), - ); + // Prepend `all_initialization_scripts` to `webview_attributes.initialization_scripts` + all_initialization_scripts.extend(webview_attributes.initialization_scripts); + webview_attributes.initialization_scripts = all_initialization_scripts; pending.webview_attributes = webview_attributes; diff --git a/crates/tauri/src/plugin.rs b/crates/tauri/src/plugin.rs index e28caeecfb52..893a89f60572 100644 --- a/crates/tauri/src/plugin.rs +++ b/crates/tauri/src/plugin.rs @@ -18,6 +18,7 @@ use serde::{ }; use serde_json::Value as JsonValue; use tauri_macros::default_runtime; +use tauri_runtime::webview::InitializationScript; use thiserror::Error; use url::Url; @@ -50,15 +51,32 @@ pub trait Plugin: Send { /// Add 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, - /// it's recommended to check the `window.location` to guard your script from running on unexpected origins. - /// /// The script is wrapped into its own context with `(function () { /* your script here */ })();`, /// so global variables must be assigned to `window` instead of implicitly declared. + /// + /// This is executed only on the main frame. + /// If you only want to run it in all frames, use [`Plugin::initialization_script_2`] to set that to false. + /// + /// ## Platform-specific + /// + /// - **Windows:** scripts are always added to subframes. + /// - **Android:** When [addDocumentStartJavaScript] is not supported, + /// we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs). + /// For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts. + /// + /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E) + /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap) fn initialization_script(&self) -> Option { None } + // TODO: Change `initialization_script` to this in v3 + /// Same as [`Plugin::initialization_script_2`] but returns an [`InitializationScript`] instead + /// We plan to replace [`Plugin::initialization_script`] with this signature in v3 + fn initialization_script_2(&self) -> Option { + None + } + /// Callback invoked when the window is created. #[allow(unused_variables)] fn window_created(&mut self, window: Window) {} @@ -242,7 +260,7 @@ pub struct Builder { name: &'static str, invoke_handler: Box>, setup: Option>>, - js_init_script: Option, + js_init_script: Option, on_navigation: Box>, on_page_load: Box>, on_window_ready: Box>, @@ -305,14 +323,24 @@ impl Builder { /// Sets the provided JavaScript to 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, - /// it's recommended to check the `window.location` to guard your script from running on unexpected origins. - /// /// The script is wrapped into its own context with `(function () { /* your script here */ })();`, /// so global variables must be assigned to `window` instead of implicitly declared. /// /// Note that calling this function multiple times overrides previous values. /// + /// This is executed only on the main frame. + /// If you only want to run it in all frames, use [`Self::js_init_script_on_all_frames`] instead. + /// + /// ## Platform-specific + /// + /// - **Windows:** scripts are always added to subframes. + /// - **Android:** When [addDocumentStartJavaScript] is not supported, + /// we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs). + /// For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts. + /// + /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E) + /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap) + /// /// # Examples /// /// ```rust @@ -328,13 +356,45 @@ impl Builder { /// /// fn init() -> TauriPlugin { /// Builder::new("example") - /// .js_init_script(INIT_SCRIPT.to_string()) + /// .js_init_script(INIT_SCRIPT) /// .build() /// } /// ``` #[must_use] - pub fn js_init_script(mut self, js_init_script: String) -> Self { - self.js_init_script = Some(js_init_script); + pub fn js_init_script(mut self, js_init_script: impl Into) -> Self { + self.js_init_script = Some(InitializationScript { + script: js_init_script.into(), + for_main_frame_only: true, + }); + self + } + + /// Sets the provided JavaScript to 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, + /// it's recommended to check the `window.location` to guard your script from running on unexpected origins. + /// + /// Note that calling this function multiple times overrides previous values. + /// + /// 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::js_init_script`] instead. + /// + /// ## Platform-specific + /// + /// - **Windows:** scripts are always added to subframes. + /// - **Android:** When [addDocumentStartJavaScript] is not supported, + /// we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs). + /// For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts. + /// + /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E) + /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap) + #[must_use] + pub fn js_init_script_on_all_frames(mut self, js_init_script: impl Into) -> Self { + self.js_init_script = Some(InitializationScript { + script: js_init_script.into(), + for_main_frame_only: false, + }); self } @@ -695,7 +755,7 @@ pub struct TauriPlugin { app: Option>, invoke_handler: Box>, setup: Option>>, - js_init_script: Option, + js_init_script: Option, on_navigation: Box>, on_page_load: Box>, on_window_ready: Box>, @@ -751,6 +811,13 @@ impl Plugin for TauriPlugin { } fn initialization_script(&self) -> Option { + self + .js_init_script + .clone() + .map(|initialization_script| initialization_script.script) + } + + fn initialization_script_2(&self) -> Option { self.js_init_script.clone() } @@ -842,12 +909,20 @@ impl PluginStore { } /// Generates an initialization script from all plugins in the store. - pub(crate) fn initialization_script(&self) -> Vec { + pub(crate) fn initialization_script(&self) -> Vec { self .store .iter() - .filter_map(|p| p.initialization_script()) - .map(|script| format!("(function () {{ {script} }})();")) + .filter_map(|p| p.initialization_script_2()) + .map( + |InitializationScript { + script, + for_main_frame_only, + }| InitializationScript { + script: format!("(function () {{ {script} }})();"), + for_main_frame_only, + }, + ) .collect() } diff --git a/crates/tauri/src/webview/mod.rs b/crates/tauri/src/webview/mod.rs index f1c4dd609026..a8e29d7cee0e 100644 --- a/crates/tauri/src/webview/mod.rs +++ b/crates/tauri/src/webview/mod.rs @@ -638,7 +638,7 @@ impl WebviewBuilder { /// 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. + /// If you only want to run it in all frames, use [`Self::initialization_script_for_all_frames`] instead. /// /// ## Platform-specific /// @@ -698,7 +698,7 @@ fn main() { /// 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 the script in the main frame, use [Self::initialization_script] instead. + /// If you only want to run the script in the main frame, use [`Self::initialization_script`] instead. /// /// ## Platform-specific /// From e7a475b5bd6cce914d7376fbbe923996d4a91356 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 12 Jun 2025 19:07:23 +0800 Subject: [PATCH 2/5] Add change files --- .changes/plugin-all-frame-init-script.md | 5 +++++ .changes/plugin-js-init-script-into-string.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changes/plugin-all-frame-init-script.md create mode 100644 .changes/plugin-js-init-script-into-string.md diff --git a/.changes/plugin-all-frame-init-script.md b/.changes/plugin-all-frame-init-script.md new file mode 100644 index 000000000000..bdb00611a6ed --- /dev/null +++ b/.changes/plugin-all-frame-init-script.md @@ -0,0 +1,5 @@ +--- +tauri: "minor:feat" +--- + +Added `tauri::plugin::Builder::js_init_script_on_all_frames` that allows plugins to add initialization scripts that runs on all frames diff --git a/.changes/plugin-js-init-script-into-string.md b/.changes/plugin-js-init-script-into-string.md new file mode 100644 index 000000000000..e79cf282bad8 --- /dev/null +++ b/.changes/plugin-js-init-script-into-string.md @@ -0,0 +1,5 @@ +--- +tauri: "minor:enhance" +--- + +`tauri::plugin::Builder::js_init_script` now takes `impl Into` instead of `String` From 9f4a1ff4a58f348f1d0a347e1f3b7d349ca06832 Mon Sep 17 00:00:00 2001 From: Tony <68118705+Legend-Master@users.noreply.github.com> Date: Thu, 12 Jun 2025 19:30:42 +0800 Subject: [PATCH 3/5] Update crates/tauri/src/plugin.rs Co-authored-by: Fabian-Lars --- crates/tauri/src/plugin.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/tauri/src/plugin.rs b/crates/tauri/src/plugin.rs index 893a89f60572..605e449cfe26 100644 --- a/crates/tauri/src/plugin.rs +++ b/crates/tauri/src/plugin.rs @@ -361,6 +361,7 @@ impl Builder { /// } /// ``` #[must_use] + // TODO: Rename to initialization_script in v3 pub fn js_init_script(mut self, js_init_script: impl Into) -> Self { self.js_init_script = Some(InitializationScript { script: js_init_script.into(), From 19723bbe0d384cc2e93874c0fa01b54970f19960 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 12 Jun 2025 19:33:54 +0800 Subject: [PATCH 4/5] Default impl initialization_script_2 from 1 --- crates/tauri/src/plugin.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/tauri/src/plugin.rs b/crates/tauri/src/plugin.rs index 605e449cfe26..f810707e21cb 100644 --- a/crates/tauri/src/plugin.rs +++ b/crates/tauri/src/plugin.rs @@ -74,7 +74,12 @@ pub trait Plugin: Send { /// Same as [`Plugin::initialization_script_2`] but returns an [`InitializationScript`] instead /// We plan to replace [`Plugin::initialization_script`] with this signature in v3 fn initialization_script_2(&self) -> Option { - None + self + .initialization_script() + .map(|script| InitializationScript { + script, + for_main_frame_only: true, + }) } /// Callback invoked when the window is created. @@ -361,7 +366,7 @@ impl Builder { /// } /// ``` #[must_use] - // TODO: Rename to initialization_script in v3 + // TODO: Rename to `initialization_script` in v3 pub fn js_init_script(mut self, js_init_script: impl Into) -> Self { self.js_init_script = Some(InitializationScript { script: js_init_script.into(), From b0cf0299b9c054bae56ddf52e459b8d3d1d3726c Mon Sep 17 00:00:00 2001 From: Tony <68118705+Legend-Master@users.noreply.github.com> Date: Tue, 8 Jul 2025 21:35:41 +0800 Subject: [PATCH 5/5] Update crates/tauri/src/plugin.rs Co-authored-by: Lucas Fernandes Nogueira --- crates/tauri/src/plugin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/tauri/src/plugin.rs b/crates/tauri/src/plugin.rs index f810707e21cb..e7a346a5c6e4 100644 --- a/crates/tauri/src/plugin.rs +++ b/crates/tauri/src/plugin.rs @@ -71,7 +71,7 @@ pub trait Plugin: Send { } // TODO: Change `initialization_script` to this in v3 - /// Same as [`Plugin::initialization_script_2`] but returns an [`InitializationScript`] instead + /// Same as [`Plugin::initialization_script`] but returns an [`InitializationScript`] instead /// We plan to replace [`Plugin::initialization_script`] with this signature in v3 fn initialization_script_2(&self) -> Option { self