feat: expose api to run initialization scripts on all frames. (#13076)

* api!: expose api to run initialisation scripts on all frames.

* remove breaking change, add new api instead.

* Update .changes/init-script-on-all-frames.md

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>

* use struct `InitializationScript` instead of tuple

* Update crates/tauri-runtime/src/webview.rs

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>

* Apply suggestions from code review

* Update crates/tauri/src/webview/webview_window.rs

---------

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>
This commit is contained in:
Simon Laux
2025-04-02 02:41:34 +02:00
committed by GitHub
parent b154826881
commit 8cf662e34b
6 changed files with 199 additions and 19 deletions

View File

@@ -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`

View File

@@ -4557,7 +4557,8 @@ fn create_webview<T: UserEvent>(
));
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 {

View File

@@ -197,7 +197,15 @@ impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWebview<T, R> {
pub struct WebviewAttributes {
pub url: WebviewUrl,
pub user_agent: Option<String>,
pub initialization_scripts: Vec<String>,
/// 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<InitializationScript>,
pub data_directory: Option<PathBuf>,
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<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + 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,
}
}
}

View File

@@ -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<R: Runtime> WebviewManager<R> {
}
}
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<R: Runtime> WebviewManager<R> {
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")]

View File

@@ -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<R: Runtime> WebviewBuilder<R> {
/// 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
}

View File

@@ -765,9 +765,12 @@ impl<R: Runtime, M: Manager<R>> 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<R: Runtime, M: Manager<R>> 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 {