Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/plugin-all-frame-init-script.md
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions .changes/plugin-js-init-script-into-string.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
tauri: "minor:enhance"
---

`tauri::plugin::Builder::js_init_script` now takes `impl Into<String>` instead of `String`
10 changes: 6 additions & 4 deletions crates/tauri-runtime/src/webview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -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.
///
Expand Down
54 changes: 27 additions & 27 deletions crates/tauri/src/manager/webview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,21 @@ impl<R: Runtime> WebviewManager<R> {
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<String> = vec![];
all_initialization_scripts.push(
let mut all_initialization_scripts: Vec<InitializationScript> = 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,
Expand All @@ -163,10 +171,10 @@ impl<R: Runtime> WebviewManager<R> {
})
}
"
.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: {{
Expand All @@ -177,45 +185,37 @@ impl<R: Runtime> WebviewManager<R> {
"#,
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;

Expand Down
109 changes: 95 additions & 14 deletions crates/tauri/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -50,15 +51,37 @@ pub trait Plugin<R: Runtime>: 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<String> {
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<InitializationScript> {
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<R>) {}
Expand Down Expand Up @@ -242,7 +265,7 @@ pub struct Builder<R: Runtime, C: DeserializeOwned = ()> {
name: &'static str,
invoke_handler: Box<InvokeHandler<R>>,
setup: Option<Box<SetupHook<R, C>>>,
js_init_script: Option<String>,
js_init_script: Option<InitializationScript>,
on_navigation: Box<OnNavigation<R>>,
on_page_load: Box<OnPageLoad<R>>,
on_window_ready: Box<OnWindowReady<R>>,
Expand Down Expand Up @@ -305,14 +328,24 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
/// 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
Expand All @@ -328,13 +361,46 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
///
/// fn init<R: Runtime>() -> TauriPlugin<R> {
/// 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<String>) -> 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<String>) -> Self {
self.js_init_script = Some(InitializationScript {
script: js_init_script.into(),
for_main_frame_only: false,
});
self
}

Expand Down Expand Up @@ -695,7 +761,7 @@ pub struct TauriPlugin<R: Runtime, C: DeserializeOwned = ()> {
app: Option<AppHandle<R>>,
invoke_handler: Box<InvokeHandler<R>>,
setup: Option<Box<SetupHook<R, C>>>,
js_init_script: Option<String>,
js_init_script: Option<InitializationScript>,
on_navigation: Box<OnNavigation<R>>,
on_page_load: Box<OnPageLoad<R>>,
on_window_ready: Box<OnWindowReady<R>>,
Expand Down Expand Up @@ -751,6 +817,13 @@ impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
}

fn initialization_script(&self) -> Option<String> {
self
.js_init_script
.clone()
.map(|initialization_script| initialization_script.script)
}

fn initialization_script_2(&self) -> Option<InitializationScript> {
self.js_init_script.clone()
}

Expand Down Expand Up @@ -842,12 +915,20 @@ impl<R: Runtime> PluginStore<R> {
}

/// Generates an initialization script from all plugins in the store.
pub(crate) fn initialization_script(&self) -> Vec<String> {
pub(crate) fn initialization_script(&self) -> Vec<InitializationScript> {
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()
}

Expand Down
4 changes: 2 additions & 2 deletions crates/tauri/src/webview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ impl<R: Runtime> WebviewBuilder<R> {
/// 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
///
Expand Down Expand Up @@ -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
///
Expand Down
Loading