Skip to content

Commit 8cf662e

Browse files
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>
1 parent b154826 commit 8cf662e

6 files changed

Lines changed: 199 additions & 19 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
tauri: minor:feat
3+
tauri-runtime: minor:feat
4+
---
5+
6+
- add API to run initialization scripts on all frames
7+
- `WebviewBuilder::initialization_script_on_all_frames`
8+
- `WebviewWindowBuilder::initialization_script_on_all_frames`
9+
- `WebviewAttributes::initialization_script_on_all_frames`

crates/tauri-runtime-wry/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4557,7 +4557,8 @@ fn create_webview<T: UserEvent>(
45574557
));
45584558

45594559
for script in webview_attributes.initialization_scripts {
4560-
webview_builder = webview_builder.with_initialization_script(&script);
4560+
webview_builder = webview_builder
4561+
.with_initialization_script_for_main_only(&script.script, script.for_main_frame_only);
45614562
}
45624563

45634564
for (scheme, protocol) in uri_scheme_protocols {

crates/tauri-runtime/src/webview.rs

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,15 @@ impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWebview<T, R> {
197197
pub struct WebviewAttributes {
198198
pub url: WebviewUrl,
199199
pub user_agent: Option<String>,
200-
pub initialization_scripts: Vec<String>,
200+
/// A list of initialization javascript scripts to run when loading new pages.
201+
/// When webview load a new page, this initialization code will be executed.
202+
/// It is guaranteed that code is executed before `window.onload`.
203+
///
204+
/// ## Platform-specific
205+
///
206+
/// - **Android on Wry:** The Android WebView does not provide an API for initialization scripts,
207+
/// so we prepend them to each HTML head. They are only implemented on custom protocol URLs.
208+
pub initialization_scripts: Vec<InitializationScript>,
201209
pub data_directory: Option<PathBuf>,
202210
pub drag_drop_handler_enabled: bool,
203211
pub clipboard: bool,
@@ -307,10 +315,46 @@ impl WebviewAttributes {
307315
self
308316
}
309317

310-
/// Sets the init script.
318+
/// Adds an init script for the main frame.
319+
///
320+
/// When webview load a new page, this initialization code will be executed.
321+
/// It is guaranteed that code is executed before `window.onload`.
322+
///
323+
/// This is executed only on the main frame.
324+
/// If you only want to run it in all frames, use [Self::initialization_script_on_all_frames] instead.
325+
///
326+
///
327+
/// ## Platform-specific
328+
///
329+
/// - **Android on Wry:** The Android WebView does not provide an API for initialization scripts,
330+
/// so we prepend them to each HTML head. They are only implemented on custom protocol URLs.
311331
#[must_use]
312332
pub fn initialization_script(mut self, script: &str) -> Self {
313-
self.initialization_scripts.push(script.to_string());
333+
self.initialization_scripts.push(InitializationScript {
334+
script: script.to_string(),
335+
for_main_frame_only: true,
336+
});
337+
self
338+
}
339+
340+
/// Adds an init script for all frames.
341+
///
342+
/// When webview load a new page, this initialization code will be executed.
343+
/// It is guaranteed that code is executed before `window.onload`.
344+
///
345+
/// This is executed on all frames, main frame and also sub frames.
346+
/// If you only want to run it in the main frame, use [Self::initialization_script] instead.
347+
///
348+
/// ## Platform-specific
349+
///
350+
/// - **Android on Wry:** The Android WebView does not provide an API for initialization scripts,
351+
/// so we prepend them to each HTML head. They are only implemented on custom protocol URLs.
352+
#[must_use]
353+
pub fn initialization_script_on_all_frames(mut self, script: &str) -> Self {
354+
self.initialization_scripts.push(InitializationScript {
355+
script: script.to_string(),
356+
for_main_frame_only: false,
357+
});
314358
self
315359
}
316360

@@ -499,3 +543,21 @@ impl WebviewAttributes {
499543

500544
/// IPC handler.
501545
pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + Send>;
546+
547+
/// An initialization script
548+
#[derive(Debug, Clone)]
549+
pub struct InitializationScript {
550+
/// The script to run
551+
pub script: String,
552+
/// Whether the script should be injected to main frame only
553+
pub for_main_frame_only: bool,
554+
}
555+
556+
impl InitializationScript {
557+
pub fn new(script: &str, for_main_frame_only: bool) -> Self {
558+
Self {
559+
script: script.to_owned(),
560+
for_main_frame_only,
561+
}
562+
}
563+
}

crates/tauri/src/manager/webview.rs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::{
1313
use serde::Serialize;
1414
use serialize_to_javascript::{default_template, DefaultTemplate, Template};
1515
use tauri_runtime::{
16-
webview::{DetachedWebview, PendingWebview},
16+
webview::{DetachedWebview, InitializationScript, PendingWebview},
1717
window::DragDropEvent,
1818
};
1919
use tauri_utils::config::WebviewUrl;
@@ -211,9 +211,15 @@ impl<R: Runtime> WebviewManager<R> {
211211
}
212212
}
213213

214-
webview_attributes
215-
.initialization_scripts
216-
.splice(0..0, all_initialization_scripts);
214+
webview_attributes.initialization_scripts.splice(
215+
0..0,
216+
all_initialization_scripts
217+
.into_iter()
218+
.map(|script| InitializationScript {
219+
script,
220+
for_main_frame_only: true,
221+
}),
222+
);
217223

218224
pending.webview_attributes = webview_attributes;
219225

@@ -527,13 +533,17 @@ impl<R: Runtime> WebviewManager<R> {
527533
os_name: &'a str,
528534
}
529535

530-
pending.webview_attributes.initialization_scripts.push(
531-
HotkeyZoom {
532-
os_name: std::env::consts::OS,
533-
}
534-
.render_default(&Default::default())?
535-
.into_string(),
536-
)
536+
pending
537+
.webview_attributes
538+
.initialization_scripts
539+
.push(InitializationScript {
540+
script: HotkeyZoom {
541+
os_name: std::env::consts::OS,
542+
}
543+
.render_default(&Default::default())?
544+
.into_string(),
545+
for_main_frame_only: true,
546+
})
537547
}
538548

539549
#[cfg(feature = "isolation")]

crates/tauri/src/webview/mod.rs

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use tauri_runtime::{
2020
WindowDispatch,
2121
};
2222
use tauri_runtime::{
23-
webview::{DetachedWebview, PendingWebview, WebviewAttributes},
23+
webview::{DetachedWebview, InitializationScript, PendingWebview, WebviewAttributes},
2424
WebviewDispatch,
2525
};
2626
pub use tauri_utils::config::Color;
@@ -634,9 +634,12 @@ impl<R: Runtime> WebviewBuilder<R> {
634634
/// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created,
635635
/// but before the HTML document has been parsed and before any other script included by the HTML document is run.
636636
///
637-
/// Since it runs on all top-level document and child frame page navigations,
637+
/// Since it runs on all top-level document navigations,
638638
/// it's recommended to check the `window.location` to guard your script from running on unexpected origins.
639639
///
640+
/// This is executed only on the main frame.
641+
/// If you only want to run it in all frames, use [Self::initialization_script_for_all_frames] instead.
642+
///
640643
/// # Examples
641644
///
642645
#[cfg_attr(
@@ -671,7 +674,60 @@ fn main() {
671674
self
672675
.webview_attributes
673676
.initialization_scripts
674-
.push(script.to_string());
677+
.push(InitializationScript {
678+
script: script.to_string(),
679+
for_main_frame_only: true,
680+
});
681+
self
682+
}
683+
684+
/// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created,
685+
/// but before the HTML document has been parsed and before any other script included by the HTML document is run.
686+
///
687+
/// Since it runs on all top-level document navigations and also child frame page navigations,
688+
/// it's recommended to check the `window.location` to guard your script from running on unexpected origins.
689+
///
690+
/// This is executed on all frames, main frame and also sub frames.
691+
/// If you only want to run it in the main frame, use [Self::initialization_script] instead.
692+
///
693+
/// # Examples
694+
///
695+
#[cfg_attr(
696+
feature = "unstable",
697+
doc = r####"
698+
```rust
699+
use tauri::{WindowBuilder, Runtime};
700+
701+
const INIT_SCRIPT: &str = r#"
702+
if (window.location.origin === 'https://tauri.app') {
703+
console.log("hello world from js init script");
704+
705+
window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };
706+
}
707+
"#;
708+
709+
fn main() {
710+
tauri::Builder::default()
711+
.setup(|app| {
712+
let window = tauri::window::WindowBuilder::new(app, "label").build()?;
713+
let webview_builder = tauri::webview::WebviewBuilder::new("label", tauri::WebviewUrl::App("index.html".into()))
714+
.initialization_script_for_all_frames(INIT_SCRIPT);
715+
let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?;
716+
Ok(())
717+
});
718+
}
719+
```
720+
"####
721+
)]
722+
#[must_use]
723+
pub fn initialization_script_for_all_frames(mut self, script: &str) -> Self {
724+
self
725+
.webview_attributes
726+
.initialization_scripts
727+
.push(InitializationScript {
728+
script: script.to_string(),
729+
for_main_frame_only: false,
730+
});
675731
self
676732
}
677733

crates/tauri/src/webview/webview_window.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -765,9 +765,12 @@ impl<R: Runtime, M: Manager<R>> WebviewWindowBuilder<'_, R, M> {
765765
/// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created,
766766
/// but before the HTML document has been parsed and before any other script included by the HTML document is run.
767767
///
768-
/// Since it runs on all top-level document and child frame page navigations,
768+
/// 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),
769769
/// it's recommended to check the `window.location` to guard your script from running on unexpected origins.
770770
///
771+
/// This is executed only on the main frame.
772+
/// If you only want to run it in all frames, use [Self::initialization_script_for_all_frames] instead.
773+
///
771774
/// # Examples
772775
///
773776
/// ```rust
@@ -797,6 +800,45 @@ impl<R: Runtime, M: Manager<R>> WebviewWindowBuilder<'_, R, M> {
797800
self
798801
}
799802

803+
/// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created,
804+
/// but before the HTML document has been parsed and before any other script included by the HTML document is run.
805+
///
806+
/// 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),
807+
/// it's recommended to check the `window.location` to guard your script from running on unexpected origins.
808+
///
809+
/// This is executed on all frames, main frame and also sub frames.
810+
/// If you only want to run it in the main frame, use [Self::initialization_script] instead.
811+
/// # Examples
812+
///
813+
/// ```rust
814+
/// use tauri::{WebviewWindowBuilder, Runtime};
815+
///
816+
/// const INIT_SCRIPT: &str = r#"
817+
/// if (window.location.origin === 'https://tauri.app') {
818+
/// console.log("hello world from js init script");
819+
///
820+
/// window.__MY_CUSTOM_PROPERTY__ = { foo: 'bar' };
821+
/// }
822+
/// "#;
823+
///
824+
/// fn main() {
825+
/// tauri::Builder::default()
826+
/// .setup(|app| {
827+
/// let webview = tauri::WebviewWindowBuilder::new(app, "label", tauri::WebviewUrl::App("index.html".into()))
828+
/// .initialization_script_for_all_frames(INIT_SCRIPT)
829+
/// .build()?;
830+
/// Ok(())
831+
/// });
832+
/// }
833+
/// ```
834+
#[must_use]
835+
pub fn initialization_script_for_all_frames(mut self, script: &str) -> Self {
836+
self.webview_builder = self
837+
.webview_builder
838+
.initialization_script_for_all_frames(script);
839+
self
840+
}
841+
800842
/// Set the user agent for the webview
801843
#[must_use]
802844
pub fn user_agent(mut self, user_agent: &str) -> Self {

0 commit comments

Comments
 (0)