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` 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 1df052f59db0..36e1cd011091 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..e7a346a5c6e4 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,37 @@ 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`] but returns an [`InitializationScript`] instead + /// We plan to replace [`Plugin::initialization_script`] with this signature in v3 + fn initialization_script_2(&self) -> Option { + self + .initialization_script() + .map(|script| InitializationScript { + script, + for_main_frame_only: true, + }) + } + /// Callback invoked when the window is created. #[allow(unused_variables)] fn window_created(&mut self, window: Window) {} @@ -242,7 +265,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 +328,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 +361,46 @@ 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); + // 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(), + 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 +761,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 +817,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 +915,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 ///