From 54a222086d38c0e2067f1f3df213ecaece3ee050 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira <lucas@tauri.app> Date: Thu, 26 Oct 2023 11:35:01 -0300 Subject: [PATCH 1/3] refactor(core): implement on_page_load event using wry hook --- .changes/on-page-load-hook.md | 6 + .changes/refactor-on-page-load.md | 6 + core/tauri-runtime-wry/src/lib.rs | 15 ++ core/tauri-runtime/src/window.rs | 23 +- core/tauri/scripts/init.js | 15 +- core/tauri/src/app.rs | 25 +- core/tauri/src/manager.rs | 49 ++-- core/tauri/src/plugin.rs | 17 +- core/tauri/src/window/mod.rs | 234 +++++++++++------- examples/api/src-tauri/src/lib.rs | 32 ++- examples/multiwindow/index.html | 12 +- examples/multiwindow/main.rs | 14 +- examples/parent-window/main.rs | 18 +- .../jest/fixtures/app/src-tauri/src/main.rs | 18 +- 14 files changed, 292 insertions(+), 192 deletions(-) create mode 100644 .changes/on-page-load-hook.md create mode 100644 .changes/refactor-on-page-load.md diff --git a/.changes/on-page-load-hook.md b/.changes/on-page-load-hook.md new file mode 100644 index 000000000000..340a39bd553d --- /dev/null +++ b/.changes/on-page-load-hook.md @@ -0,0 +1,6 @@ +--- +"tauri-runtime": patch:feat +"tauri-runtime-wry": patch:feat +--- + +Added `on_page_load` hook for `PendingWindow`. diff --git a/.changes/refactor-on-page-load.md b/.changes/refactor-on-page-load.md new file mode 100644 index 000000000000..d241ac25bdd5 --- /dev/null +++ b/.changes/refactor-on-page-load.md @@ -0,0 +1,6 @@ +--- +"tauri": patch:breaking +--- + +Added `WindowBuilder::on_page_load` and refactored the `Builder::on_page_load` handler to take references. +The page load hook is now triggered for load started and finished events, to determine what triggered it see `PageLoadPayload::event`. diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index c9eef74d8f08..38b7abdb2752 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -2725,6 +2725,21 @@ fn create_webview<T: UserEvent, F: Fn(RawWindow) + Send + 'static>( .unwrap_or(true) }); } + + if let Some(page_load_handler) = pending.on_page_load_handler { + webview_builder = webview_builder.with_on_page_load_handler(move |event, url| { + let _ = Url::parse(&url).map(|url| { + page_load_handler( + url, + match event { + wry::webview::PageLoadEvent::Started => tauri_runtime::window::PageLoadEvent::Started, + wry::webview::PageLoadEvent::Finished => tauri_runtime::window::PageLoadEvent::Finished, + }, + ) + }); + }); + } + if let Some(user_agent) = webview_attributes.user_agent { webview_builder = webview_builder.with_user_agent(&user_agent); } diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index 2e15a2c22981..af07005c38c4 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -34,6 +34,17 @@ type WebResourceRequestHandler = type NavigationHandler = dyn Fn(&Url) -> bool + Send; +type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send; + +/// Kind of event for the page load handler. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PageLoadEvent { + /// Page started to load. + Started, + /// Page finished loading. + Finished, +} + /// UI scaling utilities. pub mod dpi; @@ -238,6 +249,8 @@ pub struct PendingWindow<T: UserEvent, R: Runtime<T>> { Option<Box<dyn Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send>>, pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>, + + pub on_page_load_handler: Option<Box<OnPageLoadHandler>>, } pub fn is_label_valid(label: &str) -> bool { @@ -270,11 +283,12 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> { uri_scheme_protocols: Default::default(), label, ipc_handler: None, - navigation_handler: Default::default(), + navigation_handler: None, url: "tauri://localhost".to_string(), #[cfg(target_os = "android")] on_webview_created: None, - web_resource_request_handler: Default::default(), + web_resource_request_handler: None, + on_page_load_handler: None, }) } } @@ -298,11 +312,12 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> { uri_scheme_protocols: Default::default(), label, ipc_handler: None, - navigation_handler: Default::default(), + navigation_handler: None, url: "tauri://localhost".to_string(), #[cfg(target_os = "android")] on_webview_created: None, - web_resource_request_handler: Default::default(), + web_resource_request_handler: None, + on_page_load_handler: None, }) } } diff --git a/core/tauri/scripts/init.js b/core/tauri/scripts/init.js index f9e0e66716e2..2636e666bc0e 100644 --- a/core/tauri/scripts/init.js +++ b/core/tauri/scripts/init.js @@ -12,21 +12,8 @@ __RAW_core_script__ __RAW_event_initialization_script__ - ;(function () { - __RAW_bundle_script__ - })() - if (window.ipc) { - window.__TAURI_INTERNALS__.invoke('__initialized', { - url: window.location.href - }) - } else { - window.addEventListener('DOMContentLoaded', function () { - window.__TAURI_INTERNALS__.invoke('__initialized', { - url: window.location.href - }) - }) - } + __RAW_bundle_script__ __RAW_plugin_initialization_script__ })() diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index fa993606971b..211e3eb9d5f7 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -19,6 +19,7 @@ use crate::{ sealed::{ManagerBase, RuntimeOrDispatch}, utils::config::Config, utils::{assets::Assets, Env}, + window::PageLoadPayload, Context, DeviceEventFilter, EventLoopMessage, Icon, Manager, Monitor, Runtime, Scopes, StateManager, Theme, Window, }; @@ -30,7 +31,6 @@ use crate::tray::{TrayIcon, TrayIconBuilder, TrayIconEvent, TrayIconId}; #[cfg(desktop)] use crate::window::WindowMenu; use raw_window_handle::HasRawDisplayHandle; -use serde::Deserialize; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; use tauri_macros::default_runtime; #[cfg(desktop)] @@ -68,20 +68,7 @@ pub(crate) type GlobalWindowEventListener<R> = Box<dyn Fn(GlobalWindowEvent<R>) pub type SetupHook<R> = Box<dyn FnOnce(&mut App<R>) -> Result<(), Box<dyn std::error::Error>> + Send>; /// A closure that is run once every time a window is created and loaded. -pub type OnPageLoad<R> = dyn Fn(Window<R>, PageLoadPayload) + Send + Sync + 'static; - -/// The payload for the [`OnPageLoad`] hook. -#[derive(Debug, Clone, Deserialize)] -pub struct PageLoadPayload { - url: String, -} - -impl PageLoadPayload { - /// The page URL. - pub fn url(&self) -> &str { - &self.url - } -} +pub type OnPageLoad<R> = dyn Fn(&Window<R>, &PageLoadPayload<'_>) + Send + Sync + 'static; /// Api exposed on the `ExitRequested` event. #[derive(Debug)] @@ -982,7 +969,7 @@ pub struct Builder<R: Runtime> { setup: SetupHook<R>, /// Page load hook. - on_page_load: Box<OnPageLoad<R>>, + on_page_load: Option<Arc<OnPageLoad<R>>>, /// windows to create when starting up. pending_windows: Vec<PendingWindow<EventLoopMessage, R>>, @@ -1040,7 +1027,7 @@ impl<R: Runtime> Builder<R> { .render_default(&Default::default()) .unwrap() .into_string(), - on_page_load: Box::new(|_, _| ()), + on_page_load: None, pending_windows: Default::default(), plugins: PluginStore::default(), uri_scheme_protocols: Default::default(), @@ -1130,9 +1117,9 @@ impl<R: Runtime> Builder<R> { #[must_use] pub fn on_page_load<F>(mut self, on_page_load: F) -> Self where - F: Fn(Window<R>, PageLoadPayload) + Send + Sync + 'static, + F: Fn(&Window<R>, &PageLoadPayload<'_>) + Send + Sync + 'static, { - self.on_page_load = Box::new(on_page_load); + self.on_page_load.replace(Arc::new(on_page_load)); self } diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index 2154b0979e81..e53a5b877f14 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -27,12 +27,8 @@ use tauri_utils::{ html::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN}, }; -use crate::event::EmitArgs; use crate::{ - app::{ - AppHandle, GlobalWindowEvent, GlobalWindowEventListener, OnPageLoad, PageLoadPayload, - UriSchemeResponder, - }, + app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener, OnPageLoad, UriSchemeResponder}, event::{assert_event_name_is_valid, Event, EventId, Listeners}, ipc::{Invoke, InvokeHandler, InvokeResponder}, pattern::PatternJavascript, @@ -52,6 +48,7 @@ use crate::{ Context, EventLoopMessage, Icon, Manager, Pattern, Runtime, Scopes, StateManager, Window, WindowEvent, }; +use crate::{event::EmitArgs, window::PageLoadPayload}; #[cfg(desktop)] use crate::app::GlobalMenuEventListener; @@ -232,7 +229,7 @@ pub struct InnerWindowManager<R: Runtime> { invoke_handler: Box<InvokeHandler<R>>, /// The page load hook, invoked when the webview performs a navigation. - on_page_load: Box<OnPageLoad<R>>, + on_page_load: Option<Arc<OnPageLoad<R>>>, config: Arc<Config>, assets: Arc<dyn Assets>, @@ -339,7 +336,7 @@ impl<R: Runtime> WindowManager<R> { #[allow(unused_mut)] mut context: Context<impl Assets>, plugins: PluginStore<R>, invoke_handler: Box<InvokeHandler<R>>, - on_page_load: Box<OnPageLoad<R>>, + on_page_load: Option<Arc<OnPageLoad<R>>>, uri_scheme_protocols: HashMap<String, Arc<UriSchemeProtocol<R>>>, state: StateManager, window_event_listeners: Vec<GlobalWindowEventListener<R>>, @@ -685,6 +682,32 @@ impl<R: Runtime> WindowManager<R> { registered_scheme_protocols.push("ipc".into()); } + let label = pending.label.clone(); + let manager = self.clone(); + let on_page_load_handler = pending.on_page_load_handler.take(); + pending + .on_page_load_handler + .replace(Box::new(move |url, event| { + let payload = PageLoadPayload { url: &url, event }; + + if let Some(w) = manager.get_window(&label) { + if let Some(on_page_load) = &manager.inner.on_page_load { + on_page_load(&w, &payload); + } + + manager + .inner + .plugins + .lock() + .unwrap() + .on_page_load(&w, &payload); + } + + if let Some(handler) = &on_page_load_handler { + handler(url, event); + } + })); + #[cfg(feature = "protocol-asset")] if !registered_scheme_protocols.contains(&"asset".into()) { let asset_scope = self.state().get::<crate::Scopes>().asset_protocol.clone(); @@ -869,16 +892,6 @@ impl<R: Runtime> WindowManager<R> { (self.inner.invoke_handler)(invoke) } - pub fn run_on_page_load(&self, window: Window<R>, payload: PageLoadPayload) { - (self.inner.on_page_load)(window.clone(), payload.clone()); - self - .inner - .plugins - .lock() - .expect("poisoned plugin store") - .on_page_load(window, payload); - } - pub fn extend_api(&self, plugin: &str, invoke: Invoke<R>) -> bool { self .inner @@ -1376,7 +1389,7 @@ mod test { context, PluginStore::default(), Box::new(|_| false), - Box::new(|_, _| ()), + None, Default::default(), StateManager::new(), Default::default(), diff --git a/core/tauri/src/plugin.rs b/core/tauri/src/plugin.rs index 9f6a14260fd1..87bb6e9aa48e 100644 --- a/core/tauri/src/plugin.rs +++ b/core/tauri/src/plugin.rs @@ -5,11 +5,12 @@ //! The Tauri plugin extension to expand Tauri functionality. use crate::{ - app::{PageLoadPayload, UriSchemeResponder}, + app::UriSchemeResponder, error::Error, ipc::{Invoke, InvokeHandler}, manager::UriSchemeProtocol, utils::config::PluginConfig, + window::PageLoadPayload, AppHandle, RunEvent, Runtime, Window, }; use serde::de::DeserializeOwned; @@ -62,7 +63,7 @@ pub trait Plugin<R: Runtime>: Send { /// Callback invoked when the webview performs a navigation to a page. #[allow(unused_variables)] - fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) {} + fn on_page_load(&mut self, window: &Window<R>, payload: &PageLoadPayload<'_>) {} /// Callback invoked when the event loop receives a new event. #[allow(unused_variables)] @@ -80,7 +81,7 @@ type SetupHook<R, C> = type OnWebviewReady<R> = dyn FnMut(Window<R>) + Send; type OnEvent<R> = dyn FnMut(&AppHandle<R>, &RunEvent) + Send; type OnNavigation<R> = dyn Fn(&Window<R>, &Url) -> bool + Send; -type OnPageLoad<R> = dyn FnMut(Window<R>, PageLoadPayload) + Send; +type OnPageLoad<R> = dyn FnMut(&Window<R>, &PageLoadPayload<'_>) + Send; type OnDrop<R> = dyn FnOnce(AppHandle<R>) + Send; /// A handle to a plugin. @@ -368,7 +369,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> { /// fn init<R: Runtime>() -> TauriPlugin<R> { /// Builder::new("example") /// .on_page_load(|window, payload| { - /// println!("Loaded URL {} in window {}", payload.url(), window.label()); + /// println!("{} URL {} in window {}", payload.event(), payload.url(), window.label()); /// }) /// .build() /// } @@ -376,7 +377,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> { #[must_use] pub fn on_page_load<F>(mut self, on_page_load: F) -> Self where - F: FnMut(Window<R>, PageLoadPayload) + Send + 'static, + F: FnMut(&Window<R>, &PageLoadPayload<'_>) + Send + 'static, { self.on_page_load = Box::new(on_page_load); self @@ -652,7 +653,7 @@ impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> { (self.on_navigation)(window, url) } - fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) { + fn on_page_load(&mut self, window: &Window<R>, payload: &PageLoadPayload<'_>) { (self.on_page_load)(window, payload) } @@ -750,11 +751,11 @@ impl<R: Runtime> PluginStore<R> { } /// Runs the on_page_load hook for all plugins in the store. - pub(crate) fn on_page_load(&mut self, window: Window<R>, payload: PageLoadPayload) { + pub(crate) fn on_page_load(&mut self, window: &Window<R>, payload: &PageLoadPayload<'_>) { self .store .iter_mut() - .for_each(|plugin| plugin.on_page_load(window.clone(), payload.clone())) + .for_each(|plugin| plugin.on_page_load(window, payload)) } /// Runs the on_event hook for all plugins in the store. diff --git a/core/tauri/src/window/mod.rs b/core/tauri/src/window/mod.rs index aa3122338265..6dd3fad58623 100644 --- a/core/tauri/src/window/mod.rs +++ b/core/tauri/src/window/mod.rs @@ -7,6 +7,7 @@ pub(crate) mod plugin; use http::HeaderMap; +pub use tauri_runtime::window::PageLoadEvent; pub use tauri_utils::{config::Color, WindowEffect as Effect, WindowEffectState as EffectState}; use url::Url; @@ -65,12 +66,32 @@ pub(crate) type WebResourceRequestHandler = pub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send; pub(crate) type UriSchemeProtocolHandler = Box<dyn Fn(http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync>; +pub(crate) type OnPageLoad<R> = dyn Fn(Window<R>, PageLoadPayload<'_>) + Send + Sync + 'static; #[derive(Clone, Serialize)] struct WindowCreatedEvent { label: String, } +/// The payload for the [`OnPageLoad`] hook. +#[derive(Debug, Clone)] +pub struct PageLoadPayload<'a> { + pub(crate) url: &'a Url, + pub(crate) event: PageLoadEvent, +} + +impl<'a> PageLoadPayload<'a> { + /// The page URL. + pub fn url(&self) -> &'a Url { + self.url + } + + /// The page load event. + pub fn event(&self) -> PageLoadEvent { + self.event + } +} + /// Monitor descriptor. #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] @@ -128,6 +149,7 @@ pub struct WindowBuilder<'a, R: Runtime> { pub(crate) webview_attributes: WebviewAttributes, web_resource_request_handler: Option<Box<WebResourceRequestHandler>>, navigation_handler: Option<Box<NavigationHandler>>, + on_page_load_handler: Option<Box<OnPageLoad<R>>>, #[cfg(desktop)] on_menu_event: Option<crate::app::GlobalMenuEventListener<Window<R>>>, } @@ -206,6 +228,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { webview_attributes: WebviewAttributes::new(url), web_resource_request_handler: None, navigation_handler: None, + on_page_load_handler: None, #[cfg(desktop)] on_menu_event: None, } @@ -250,6 +273,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { navigation_handler: None, #[cfg(desktop)] on_menu_event: None, + on_page_load_handler: None, }; builder @@ -329,6 +353,35 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { self } + /// Defines a closure to be executed when the webview navigates to a URL. Returning `false` cancels the navigation. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri::{ + /// utils::config::{Csp, CspDirectiveSources, WindowUrl}, + /// window::WindowBuilder, + /// }; + /// use http::header::HeaderValue; + /// use std::collections::HashMap; + /// tauri::Builder::default() + /// .setup(|app| { + /// WindowBuilder::new(app, "core", WindowUrl::App("index.html".into())) + /// .on_page_load(|window, payload| { + /// println!("{:?} {}", payload.event(), payload.url()); + /// }) + /// .build()?; + /// Ok(()) + /// }); + /// ``` + pub fn on_page_load<F: Fn(Window<R>, PageLoadPayload<'_>) + Send + Sync + 'static>( + mut self, + f: F, + ) -> Self { + self.on_page_load_handler.replace(Box::new(f)); + self + } + /// Registers a global menu event listener. /// /// Note that this handler is called for any menu event, @@ -381,6 +434,18 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { pending.navigation_handler = self.navigation_handler.take(); pending.web_resource_request_handler = self.web_resource_request_handler.take(); + if let Some(on_page_load_handler) = self.on_page_load_handler.take() { + let label = pending.label.clone(); + let manager = self.app_handle.manager.clone(); + pending + .on_page_load_handler + .replace(Box::new(move |url, event| { + if let Some(w) = manager.get_window(&label) { + on_page_load_handler(w, PageLoadPayload { url: &url, event }); + } + })); + } + let labels = self.manager.labels().into_iter().collect::<Vec<_>>(); let pending = self .manager @@ -2155,110 +2220,99 @@ impl<R: Runtime> Window<R> { request.error, ); - match request.cmd.as_str() { - "__initialized" => match request.body.deserialize() { - Ok(payload) => { - manager.run_on_page_load(self, payload); - resolver.resolve(()); - } - Err(e) => resolver.reject(e.to_string()), - }, - _ => { - #[cfg(mobile)] - let app_handle = self.app_handle.clone(); - - let message = InvokeMessage::new( - self, - manager.state(), - request.cmd.to_string(), - request.body, - request.headers, - ); - - let mut invoke = Invoke { - message, - resolver: resolver.clone(), - }; - - if !is_local && scope.is_none() { - invoke.resolver.reject(scope_not_found_error_message); - } else if request.cmd.starts_with("plugin:") { - let command = invoke.message.command.replace("plugin:", ""); - let mut tokens = command.split('|'); - // safe to unwrap: split always has a least one item - let plugin = tokens.next().unwrap(); - invoke.message.command = tokens - .next() - .map(|c| c.to_string()) - .unwrap_or_else(String::new); - - if !(is_local - || plugin == crate::ipc::channel::CHANNEL_PLUGIN_NAME - || scope - .map(|s| s.plugins().contains(&plugin.into())) - .unwrap_or(true)) - { - invoke.resolver.reject(IPC_SCOPE_DOES_NOT_ALLOW); - return; - } + #[cfg(mobile)] + let app_handle = self.app_handle.clone(); + + let message = InvokeMessage::new( + self, + manager.state(), + request.cmd.to_string(), + request.body, + request.headers, + ); - let command = invoke.message.command.clone(); + let mut invoke = Invoke { + message, + resolver: resolver.clone(), + }; + + if !is_local && scope.is_none() { + invoke.resolver.reject(scope_not_found_error_message); + } else if request.cmd.starts_with("plugin:") { + let command = invoke.message.command.replace("plugin:", ""); + let mut tokens = command.split('|'); + // safe to unwrap: split always has a least one item + let plugin = tokens.next().unwrap(); + invoke.message.command = tokens + .next() + .map(|c| c.to_string()) + .unwrap_or_else(String::new); + + if !(is_local + || plugin == crate::ipc::channel::CHANNEL_PLUGIN_NAME + || scope + .map(|s| s.plugins().contains(&plugin.into())) + .unwrap_or(true)) + { + invoke.resolver.reject(IPC_SCOPE_DOES_NOT_ALLOW); + return; + } + + let command = invoke.message.command.clone(); - #[cfg(mobile)] - let message = invoke.message.clone(); + #[cfg(mobile)] + let message = invoke.message.clone(); - #[allow(unused_mut)] - let mut handled = manager.extend_api(plugin, invoke); + #[allow(unused_mut)] + let mut handled = manager.extend_api(plugin, invoke); - #[cfg(mobile)] - { - if !handled { - handled = true; + #[cfg(mobile)] + { + if !handled { + handled = true; - fn load_channels<R: Runtime>(payload: &serde_json::Value, window: &Window<R>) { - if let serde_json::Value::Object(map) = payload { - for v in map.values() { - if let serde_json::Value::String(s) = v { - if s.starts_with(crate::ipc::channel::IPC_PAYLOAD_PREFIX) { - crate::ipc::Channel::load_from_ipc(window.clone(), s); - } - } + fn load_channels<R: Runtime>(payload: &serde_json::Value, window: &Window<R>) { + if let serde_json::Value::Object(map) = payload { + for v in map.values() { + if let serde_json::Value::String(s) = v { + if s.starts_with(crate::ipc::channel::IPC_PAYLOAD_PREFIX) { + crate::ipc::Channel::load_from_ipc(window.clone(), s); } } } - - let payload = message.payload.into_json(); - // initialize channels - load_channels(&payload, &message.window); - - let resolver_ = resolver.clone(); - if let Err(e) = crate::plugin::mobile::run_command( - plugin, - &app_handle, - message.command, - payload, - move |response| match response { - Ok(r) => resolver_.resolve(r), - Err(e) => resolver_.reject(e), - }, - ) { - resolver.reject(e.to_string()); - return; - } } } - if !handled { - resolver.reject(format!("Command {command} not found")); - } - } else { - let command = invoke.message.command.clone(); - let handled = manager.run_invoke_handler(invoke); - if !handled { - resolver.reject(format!("Command {command} not found")); + let payload = message.payload.into_json(); + // initialize channels + load_channels(&payload, &message.window); + + let resolver_ = resolver.clone(); + if let Err(e) = crate::plugin::mobile::run_command( + plugin, + &app_handle, + message.command, + payload, + move |response| match response { + Ok(r) => resolver_.resolve(r), + Err(e) => resolver_.reject(e), + }, + ) { + resolver.reject(e.to_string()); + return; } } } + + if !handled { + resolver.reject(format!("Command {command} not found")); + } + } else { + let command = invoke.message.command.clone(); + let handled = manager.run_invoke_handler(invoke); + if !handled { + resolver.reject(format!("Command {command} not found")); + } } } diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index 9c08b55ec587..5370b589dd01 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -12,7 +12,11 @@ mod cmd; mod tray; use serde::Serialize; -use tauri::{ipc::Channel, window::WindowBuilder, App, AppHandle, RunEvent, Runtime, WindowUrl}; +use tauri::{ + ipc::Channel, + window::{PageLoadEvent, WindowBuilder}, + App, AppHandle, RunEvent, Runtime, WindowUrl, +}; use tauri_plugin_sample::{PingRequest, SampleExt}; #[cfg(desktop)] @@ -122,18 +126,20 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>( Ok(()) }) - .on_page_load(|window, _| { - let window_ = window.clone(); - window.listen("js-event", move |event| { - println!("got js-event with message '{:?}'", event.payload()); - let reply = Reply { - data: "something else".to_string(), - }; - - window_ - .emit("rust-event", Some(reply)) - .expect("failed to emit"); - }); + .on_page_load(|window, payload| { + if payload.event() == PageLoadEvent::Finished { + let window_ = window.clone(); + window.listen("js-event", move |event| { + println!("got js-event with message '{:?}'", event.payload()); + let reply = Reply { + data: "something else".to_string(), + }; + + window_ + .emit("rust-event", Some(reply)) + .expect("failed to emit"); + }); + } }); #[allow(unused_mut)] diff --git a/examples/multiwindow/index.html b/examples/multiwindow/index.html index d415b1ae3322..70a5cb43e450 100644 --- a/examples/multiwindow/index.html +++ b/examples/multiwindow/index.html @@ -14,8 +14,8 @@ <div id="response"></div> <script> - const WebviewWindow = window.__TAURI__.window.WebviewWindow - const appWindow = window.__TAURI__.window.appWindow + const TauriWindow = window.__TAURI__.window.Window + const appWindow = window.__TAURI__.window.getCurrent() const windowLabel = appWindow.label const windowLabelContainer = document.getElementById('window-label') windowLabelContainer.innerText = 'This is the ' + windowLabel + ' window.' @@ -23,7 +23,7 @@ const container = document.getElementById('container') function createWindowMessageBtn(label) { - const tauriWindow = WebviewWindow.getByLabel(label) + const tauriWindow = TauriWindow.getByLabel(label) const button = document.createElement('button') button.innerText = 'Send message to ' + label button.addEventListener('click', function () { @@ -51,16 +51,16 @@ const createWindowButton = document.createElement('button') createWindowButton.innerHTML = 'Create window' createWindowButton.addEventListener('click', function () { - const webviewWindow = new WebviewWindow( + const tauriWindow = new TauriWindow( Math.random().toString().replace('.', ''), { tabbingIdentifier: windowLabel } ) - webviewWindow.once('tauri://created', function () { + tauriWindow.once('tauri://created', function () { responseContainer.innerHTML += 'Created new webview' }) - webviewWindow.once('tauri://error', function (e) { + tauriWindow.once('tauri://error', function (e) { responseContainer.innerHTML += 'Error creating new webview' }) }) diff --git a/examples/multiwindow/main.rs b/examples/multiwindow/main.rs index 6559d51e9760..dfc7fd81a6c6 100644 --- a/examples/multiwindow/main.rs +++ b/examples/multiwindow/main.rs @@ -4,15 +4,17 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use tauri::WindowBuilder; +use tauri::{window::PageLoadEvent, WindowBuilder}; fn main() { tauri::Builder::default() - .on_page_load(|window, _payload| { - let label = window.label().to_string(); - window.listen("clicked".to_string(), move |_payload| { - println!("got 'clicked' event on window '{label}'"); - }); + .on_page_load(|window, payload| { + if payload.event() == PageLoadEvent::Finished { + let label = window.label().to_string(); + window.listen("clicked".to_string(), move |_payload| { + println!("got 'clicked' event on window '{label}'"); + }); + } }) .setup(|app| { #[allow(unused_mut)] diff --git a/examples/parent-window/main.rs b/examples/parent-window/main.rs index a0bf7e00beb2..ed301e276626 100644 --- a/examples/parent-window/main.rs +++ b/examples/parent-window/main.rs @@ -4,7 +4,11 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use tauri::{command, window::WindowBuilder, Window, WindowUrl}; +use tauri::{ + command, + window::{PageLoadEvent, WindowBuilder}, + Window, WindowUrl, +}; #[command] async fn create_child_window(id: String, window: Window) { @@ -22,11 +26,13 @@ async fn create_child_window(id: String, window: Window) { fn main() { tauri::Builder::default() - .on_page_load(|window, _payload| { - let label = window.label().to_string(); - window.listen("clicked".to_string(), move |_payload| { - println!("got 'clicked' event on window '{label}'"); - }); + .on_page_load(|window, payload| { + if payload.event() == PageLoadEvent::Finished { + let label = window.label().to_string(); + window.listen("clicked".to_string(), move |_payload| { + println!("got 'clicked' event on window '{label}'"); + }); + } }) .invoke_handler(tauri::generate_handler![create_child_window]) .setup(|app| { diff --git a/tooling/cli/node/test/jest/fixtures/app/src-tauri/src/main.rs b/tooling/cli/node/test/jest/fixtures/app/src-tauri/src/main.rs index 482229eb1fa1..dc7b1060ae2d 100644 --- a/tooling/cli/node/test/jest/fixtures/app/src-tauri/src/main.rs +++ b/tooling/cli/node/test/jest/fixtures/app/src-tauri/src/main.rs @@ -9,14 +9,16 @@ fn exit(window: tauri::Window) { fn main() { tauri::Builder::default() - .on_page_load(|window, _| { - let window_ = window.clone(); - window.listen("hello".into(), move |_| { - window_ - .emit(&"reply".to_string(), Some("{ msg: 'TEST' }".to_string())) - .unwrap(); - }); - window.eval("window.onTauriInit()").unwrap(); + .on_page_load(|window, payload| { + if payload.event() == tauri::window::PageLoadEvent::Finished { + let window_ = window.clone(); + window.listen("hello".into(), move |_| { + window_ + .emit(&"reply".to_string(), Some("{ msg: 'TEST' }".to_string())) + .unwrap(); + }); + window.eval("window.onTauriInit()").unwrap(); + } }) .invoke_handler(tauri::generate_handler![exit]) .run(tauri::generate_context!()) From 18a9414070af3b7ee314d11cde550300dc93db36 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira <lucas@tauri.app> Date: Thu, 26 Oct 2023 12:58:39 -0300 Subject: [PATCH 2/3] fix doctest --- core/tauri/src/plugin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tauri/src/plugin.rs b/core/tauri/src/plugin.rs index 87bb6e9aa48e..9a601f24887c 100644 --- a/core/tauri/src/plugin.rs +++ b/core/tauri/src/plugin.rs @@ -369,7 +369,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> { /// fn init<R: Runtime>() -> TauriPlugin<R> { /// Builder::new("example") /// .on_page_load(|window, payload| { - /// println!("{} URL {} in window {}", payload.event(), payload.url(), window.label()); + /// println!("{:?} URL {} in window {}", payload.event(), payload.url(), window.label()); /// }) /// .build() /// } From 78e2b53ea546f17de701f031b4a298e9a9fe1af9 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira <lucas@tauri.app> Date: Fri, 27 Oct 2023 08:33:54 -0300 Subject: [PATCH 3/3] update docs --- core/tauri-config-schema/schema.json | 2 +- core/tauri-runtime/src/window.rs | 2 +- core/tauri-utils/src/config.rs | 2 +- core/tauri/src/lib.rs | 2 +- core/tauri/src/menu/mod.rs | 2 +- core/tauri/src/window/mod.rs | 17 +++++++++++++---- tooling/cli/schema.json | 2 +- 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/core/tauri-config-schema/schema.json b/core/tauri-config-schema/schema.json index a91fdf16eed6..b4a96b2847de 100644 --- a/core/tauri-config-schema/schema.json +++ b/core/tauri-config-schema/schema.json @@ -1198,7 +1198,7 @@ } }, "name": { - "description": "The name. Maps to `CFBundleTypeName` on macOS. Default to ext[0]", + "description": "The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`", "type": [ "string", "null" diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index af07005c38c4..6a436f56eab1 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -357,7 +357,7 @@ pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> { /// Name of the window pub label: String, - /// The [`Dispatch`](crate::Dispatch) associated with the window. + /// The [`Dispatch`] associated with the window. pub dispatcher: R::Dispatcher, } diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 88d18529ffe5..5d054c9a2f86 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -703,7 +703,7 @@ impl<'d> serde::Deserialize<'d> for AssociationExt { pub struct FileAssociation { /// File extensions to associate with this app. e.g. 'png' pub ext: Vec<AssociationExt>, - /// The name. Maps to `CFBundleTypeName` on macOS. Default to ext[0] + /// The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]` pub name: Option<String>, /// The association description. Windows-only. It is displayed on the `Type` column on Windows Explorer. pub description: Option<String>, diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index a3838444a18d..b1297ac975ae 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -696,7 +696,7 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> { /// If the state for the `T` type has previously been set, the state is unchanged and false is returned. Otherwise true is returned. /// /// Managed state can be retrieved by any command handler via the - /// [`State`](crate::State) guard. In particular, if a value of type `T` + /// [`State`] guard. In particular, if a value of type `T` /// is managed by Tauri, adding `State<T>` to the list of arguments in a /// command handler instructs Tauri to retrieve the managed value. /// Additionally, [`state`](Self#method.state) can be used to retrieve the value manually. diff --git a/core/tauri/src/menu/mod.rs b/core/tauri/src/menu/mod.rs index bdfef026836a..eaba4b52e730 100644 --- a/core/tauri/src/menu/mod.rs +++ b/core/tauri/src/menu/mod.rs @@ -47,7 +47,7 @@ impl From<muda::MenuEvent> for MenuEvent { } } -/// Application metadata for the [`PredefinedMenuItem::about`](crate::PredefinedMenuItem::about). +/// Application metadata for the [`PredefinedMenuItem::about`]. #[derive(Debug, Clone, Default)] pub struct AboutMetadata { /// Sets the application name. diff --git a/core/tauri/src/window/mod.rs b/core/tauri/src/window/mod.rs index 6dd3fad58623..38349526efca 100644 --- a/core/tauri/src/window/mod.rs +++ b/core/tauri/src/window/mod.rs @@ -73,7 +73,7 @@ struct WindowCreatedEvent { label: String, } -/// The payload for the [`OnPageLoad`] hook. +/// The payload for the [`WindowBuilder::on_page_load`] hook. #[derive(Debug, Clone)] pub struct PageLoadPayload<'a> { pub(crate) url: &'a Url, @@ -353,14 +353,16 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { self } - /// Defines a closure to be executed when the webview navigates to a URL. Returning `false` cancels the navigation. + /// Defines a closure to be executed when a page load event is triggered. + /// The event can be either [`PageLoadEvent::Started`] if the page has started loading + /// or [`PageLoadEvent::Finished`] when the page finishes loading. /// /// # Examples /// /// ```rust,no_run /// use tauri::{ /// utils::config::{Csp, CspDirectiveSources, WindowUrl}, - /// window::WindowBuilder, + /// window::{PageLoadEvent, WindowBuilder}, /// }; /// use http::header::HeaderValue; /// use std::collections::HashMap; @@ -368,7 +370,14 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> { /// .setup(|app| { /// WindowBuilder::new(app, "core", WindowUrl::App("index.html".into())) /// .on_page_load(|window, payload| { - /// println!("{:?} {}", payload.event(), payload.url()); + /// match payload.event() { + /// PageLoadEvent::Started => { + /// println!("{} finished loading", payload.url()); + /// } + /// PageLoadEvent::Finished => { + /// println!("{} finished loading", payload.url()); + /// } + /// } /// }) /// .build()?; /// Ok(()) diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index a91fdf16eed6..b4a96b2847de 100644 --- a/tooling/cli/schema.json +++ b/tooling/cli/schema.json @@ -1198,7 +1198,7 @@ } }, "name": { - "description": "The name. Maps to `CFBundleTypeName` on macOS. Default to ext[0]", + "description": "The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`", "type": [ "string", "null"