From 938fb624f5cc0f2a4499ea67cd30b014a18a6526 Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Wed, 21 Apr 2021 15:43:11 -0300 Subject: [PATCH] feat(core): expose custom protocol handler APIs (#1553) --- .changes/multi-custom-protocols.md | 5 + core/tauri/src/runtime/app.rs | 34 ++++++- core/tauri/src/runtime/flavors/wry.rs | 133 ++++++++++++++++---------- core/tauri/src/runtime/manager.rs | 18 +++- core/tauri/src/runtime/webview.rs | 17 +++- core/tauri/src/runtime/window.rs | 7 +- 6 files changed, 152 insertions(+), 62 deletions(-) create mode 100644 .changes/multi-custom-protocols.md diff --git a/.changes/multi-custom-protocols.md b/.changes/multi-custom-protocols.md new file mode 100644 index 000000000..eaf8fe25c --- /dev/null +++ b/.changes/multi-custom-protocols.md @@ -0,0 +1,5 @@ +--- +"tauri": patch +--- + +Adds APIs to determine global and window-specific custom protocol handlers. diff --git a/core/tauri/src/runtime/app.rs b/core/tauri/src/runtime/app.rs index 34eaa1a93..2be27a06d 100644 --- a/core/tauri/src/runtime/app.rs +++ b/core/tauri/src/runtime/app.rs @@ -7,14 +7,19 @@ use crate::{ hooks::{InvokeHandler, InvokeMessage, OnPageLoad, PageLoadPayload, SetupHook}, plugin::{Plugin, PluginStore}, runtime::{ - flavors::wry::Wry, manager::WindowManager, tag::Tag, webview::Attributes, - window::PendingWindow, Dispatch, Runtime, + flavors::wry::Wry, + manager::{Args, WindowManager}, + tag::Tag, + webview::{Attributes, CustomProtocol}, + window::PendingWindow, + Dispatch, Runtime, }, sealed::{ManagerBase, RuntimeOrDispatch}, Context, Manager, Params, Window, }; -use crate::runtime::manager::Args; +use std::{collections::HashMap, sync::Arc}; + #[cfg(feature = "updater")] use crate::updater; @@ -118,6 +123,9 @@ where /// All passed plugins plugins: PluginStore>, + + /// The custom protocols available to all windows. + custom_protocols: HashMap>, } impl Builder @@ -135,6 +143,7 @@ where on_page_load: Box::new(|_, _| ()), pending_windows: Default::default(), plugins: PluginStore::default(), + custom_protocols: Default::default(), } } @@ -183,6 +192,24 @@ where self } + /// Adds a custom protocol available to all windows. + pub fn custom_protocol< + N: Into, + H: Fn(&str) -> crate::Result> + Send + Sync + 'static, + >( + mut self, + name: N, + handler: H, + ) -> Self { + self.custom_protocols.insert( + name.into(), + Arc::new(CustomProtocol { + handler: Box::new(handler), + }), + ); + self + } + /// Runs the configured Tauri application. pub fn run(mut self, context: Context) -> crate::Result<()> { let manager = WindowManager::with_handlers( @@ -190,6 +217,7 @@ where self.plugins, self.invoke_handler, self.on_page_load, + self.custom_protocols, ); // set up all the windows defined in the config diff --git a/core/tauri/src/runtime/flavors/wry.rs b/core/tauri/src/runtime/flavors/wry.rs index b2aab7088..cea348fb2 100644 --- a/core/tauri/src/runtime/flavors/wry.rs +++ b/core/tauri/src/runtime/flavors/wry.rs @@ -8,15 +8,19 @@ use crate::{ api::config::WindowConfig, runtime::{ webview::{ - Attributes, AttributesBase, CustomProtocol, FileDropEvent, FileDropHandler, RpcRequest, - WebviewRpcHandler, + Attributes, AttributesBase, FileDropEvent, FileDropHandler, RpcRequest, WebviewRpcHandler, }, window::{DetachedWindow, PendingWindow}, Dispatch, Params, Runtime, }, Icon, }; -use std::{convert::TryFrom, path::PathBuf}; +use std::{ + collections::HashMap, + convert::TryFrom, + path::PathBuf, + sync::{Arc, Mutex}, +}; #[cfg(target_os = "windows")] use crate::api::path::{resolve_path, BaseDirectory}; @@ -41,8 +45,15 @@ impl TryFrom for WryIcon { } } -impl AttributesBase for wry::Attributes {} -impl Attributes for wry::Attributes { +/// Wry attributes. +#[derive(Default, Clone)] +pub struct WryAttributes { + attributes: wry::Attributes, + custom_protocols: Arc>>, +} + +impl AttributesBase for WryAttributes {} +impl Attributes for WryAttributes { type Icon = WryIcon; fn new() -> Self { @@ -50,7 +61,7 @@ impl Attributes for wry::Attributes { } fn with_config(config: WindowConfig) -> Self { - let mut webview = wry::Attributes::default() + let mut webview = WryAttributes::default() .title(config.title.to_string()) .width(config.width) .height(config.height) @@ -108,106 +119,132 @@ impl Attributes for wry::Attributes { } fn initialization_script(mut self, init: &str) -> Self { - self.initialization_scripts.push(init.to_string()); + self + .attributes + .initialization_scripts + .push(init.to_string()); self } fn x(mut self, x: f64) -> Self { - self.x = Some(x); + self.attributes.x = Some(x); self } fn y(mut self, y: f64) -> Self { - self.y = Some(y); + self.attributes.y = Some(y); self } fn width(mut self, width: f64) -> Self { - self.width = width; + self.attributes.width = width; self } fn height(mut self, height: f64) -> Self { - self.height = height; + self.attributes.height = height; self } fn min_width(mut self, min_width: f64) -> Self { - self.min_width = Some(min_width); + self.attributes.min_width = Some(min_width); self } fn min_height(mut self, min_height: f64) -> Self { - self.min_height = Some(min_height); + self.attributes.min_height = Some(min_height); self } fn max_width(mut self, max_width: f64) -> Self { - self.max_width = Some(max_width); + self.attributes.max_width = Some(max_width); self } fn max_height(mut self, max_height: f64) -> Self { - self.max_height = Some(max_height); + self.attributes.max_height = Some(max_height); self } fn resizable(mut self, resizable: bool) -> Self { - self.resizable = resizable; + self.attributes.resizable = resizable; self } fn title>(mut self, title: S) -> Self { - self.title = title.into(); + self.attributes.title = title.into(); self } fn fullscreen(mut self, fullscreen: bool) -> Self { - self.fullscreen = fullscreen; + self.attributes.fullscreen = fullscreen; self } fn maximized(mut self, maximized: bool) -> Self { - self.maximized = maximized; + self.attributes.maximized = maximized; self } fn visible(mut self, visible: bool) -> Self { - self.visible = visible; + self.attributes.visible = visible; self } fn transparent(mut self, transparent: bool) -> Self { - self.transparent = transparent; + self.attributes.transparent = transparent; self } fn decorations(mut self, decorations: bool) -> Self { - self.decorations = decorations; + self.attributes.decorations = decorations; self } fn always_on_top(mut self, always_on_top: bool) -> Self { - self.always_on_top = always_on_top; + self.attributes.always_on_top = always_on_top; self } fn icon(mut self, icon: Self::Icon) -> Self { - self.icon = Some(icon.0); + self.attributes.icon = Some(icon.0); self } fn has_icon(&self) -> bool { - self.icon.is_some() + self.attributes.icon.is_some() } fn user_data_path(mut self, user_data_path: Option) -> Self { - self.user_data_path = user_data_path; + self.attributes.user_data_path = user_data_path; self } fn url(mut self, url: String) -> Self { - self.url.replace(url); + self.attributes.url.replace(url); + self + } + + fn has_custom_protocol(&self, name: &str) -> bool { + self.custom_protocols.lock().unwrap().contains_key(name) + } + + fn custom_protocol< + N: Into, + H: Fn(&str) -> crate::Result> + Send + Sync + 'static, + >( + self, + name: N, + handler: H, + ) -> Self { + let name = name.into(); + self.custom_protocols.lock().unwrap().insert( + name.clone(), + wry::CustomProtocol { + name, + handler: Box::new(move |data| (handler)(data).map_err(|_| wry::Error::InitScriptError)), + }, + ); self } @@ -245,7 +282,7 @@ pub struct WryDispatcher { impl Dispatch for WryDispatcher { type Runtime = Wry; type Icon = WryIcon; - type Attributes = wry::Attributes; + type Attributes = WryAttributes; fn create_window>( &mut self, @@ -254,7 +291,6 @@ impl Dispatch for WryDispatcher { let PendingWindow { attributes, rpc_handler, - custom_protocol, file_drop_handler, label, .. @@ -268,14 +304,20 @@ impl Dispatch for WryDispatcher { let file_drop_handler = file_drop_handler .map(|handler| create_file_drop_handler(proxy.clone(), label.clone(), handler)); + let custom_protocols = { + let mut lock = attributes + .custom_protocols + .lock() + .expect("poisoned custom protocols"); + std::mem::take(&mut *lock) + }; + let window = self .application .add_window_with_configs( - attributes, + attributes.attributes, rpc_handler, - custom_protocol - .map(create_custom_protocol) - .unwrap_or_default(), + custom_protocols.into_iter().map(|(_, p)| p).collect(), file_drop_handler, ) .map_err(|_| crate::Error::CreateWebview)?; @@ -463,7 +505,6 @@ impl Runtime for Wry { let PendingWindow { attributes, rpc_handler, - custom_protocol, file_drop_handler, label, .. @@ -477,14 +518,20 @@ impl Runtime for Wry { let file_drop_handler = file_drop_handler .map(|handler| create_file_drop_handler(proxy.clone(), label.clone(), handler)); + let custom_protocols = { + let mut lock = attributes + .custom_protocols + .lock() + .expect("poisoned custom protocols"); + std::mem::take(&mut *lock) + }; + let window = self .inner .add_window_with_configs( - attributes, + attributes.attributes, rpc_handler, - custom_protocol - .map(create_custom_protocol) - .unwrap_or_default(), + custom_protocols.into_iter().map(|(_, p)| p).collect(), file_drop_handler, ) .map_err(|_| crate::Error::CreateWebview)?; @@ -542,13 +589,3 @@ fn create_file_drop_handler>( ) }) } - -/// Create a wry custom protocol from a tauri custom protocol. -fn create_custom_protocol(custom_protocol: CustomProtocol) -> Vec { - vec![wry::CustomProtocol { - name: custom_protocol.name.clone(), - handler: Box::new(move |data| { - (custom_protocol.handler)(data).map_err(|_| wry::Error::InitScriptError) - }), - }] -} diff --git a/core/tauri/src/runtime/manager.rs b/core/tauri/src/runtime/manager.rs index 9bfcd9231..952b60a9d 100644 --- a/core/tauri/src/runtime/manager.rs +++ b/core/tauri/src/runtime/manager.rs @@ -49,6 +49,8 @@ pub struct InnerWindowManager { /// A list of salts that are valid for the current application. salts: Mutex>, package_info: PackageInfo, + /// The custom protocols available to all windows. + custom_protocols: HashMap>, } /// A [Zero Sized Type] marker representing a full [`Params`]. @@ -100,6 +102,7 @@ impl WindowManager

{ plugins: PluginStore

, invoke_handler: Box>, on_page_load: Box>, + custom_protocols: HashMap>, ) -> Self { Self { inner: Arc::new(InnerWindowManager { @@ -113,6 +116,7 @@ impl WindowManager

{ default_window_icon: context.default_window_icon, salts: Mutex::default(), package_info: context.package_info, + custom_protocols, }), _marker: Args::default(), } @@ -173,6 +177,17 @@ impl WindowManager

{ } } + for (name, protocol) in &self.inner.custom_protocols { + if !attributes.has_custom_protocol(name) { + let protocol = protocol.clone(); + attributes = attributes.custom_protocol(name.clone(), move |p| (protocol.handler)(p)); + } + } + + if !attributes.has_custom_protocol("tauri") { + attributes = attributes.custom_protocol("tauri", self.prepare_custom_protocol().handler); + } + // If we are on windows use App Data Local as webview temp dir // to prevent any bundled application to failed. // Fix: https://github.com/tauri-apps/tauri/issues/1365 @@ -227,7 +242,6 @@ impl WindowManager

{ let assets = self.inner.assets.clone(); let bundle_identifier = self.inner.config.tauri.bundle.identifier.clone(); CustomProtocol { - name: "tauri".into(), handler: Box::new(move |path| { let mut path = path .split('?') @@ -367,6 +381,7 @@ mod test { PluginStore::default(), Box::new(|_| ()), Box::new(|_, _| ()), + Default::default(), ); #[cfg(custom_protocol)] @@ -433,7 +448,6 @@ impl WindowManager

{ let label = pending.label.clone(); pending.attributes = self.prepare_attributes(attributes, url, label, pending_labels)?; pending.rpc_handler = Some(self.prepare_rpc_handler()); - pending.custom_protocol = Some(self.prepare_custom_protocol()); } else { pending.attributes = attributes.url(url); } diff --git a/core/tauri/src/runtime/webview.rs b/core/tauri/src/runtime/webview.rs index cfcfa5857..ea67489f9 100644 --- a/core/tauri/src/runtime/webview.rs +++ b/core/tauri/src/runtime/webview.rs @@ -92,6 +92,19 @@ pub trait Attributes: AttributesBase { /// Sets the webview url. fn url(self, url: String) -> Self; + /// Whether the custom protocol handler is defined or not. + fn has_custom_protocol(&self, name: &str) -> bool; + + /// Adds a custom protocol handler. + fn custom_protocol< + N: Into, + H: Fn(&str) -> crate::Result> + Send + Sync + 'static, + >( + self, + name: N, + handler: H, + ) -> Self; + /// The full attributes. fn build(self) -> Self; } @@ -106,10 +119,8 @@ pub struct RpcRequest { /// Uses a custom handler to resolve file requests pub struct CustomProtocol { - /// Name of the protocol - pub name: String, /// Handler for protocol - pub handler: Box crate::Result> + Send>, + pub handler: Box crate::Result> + Send + Sync>, } /// The file drop event payload. diff --git a/core/tauri/src/runtime/window.rs b/core/tauri/src/runtime/window.rs index d480d4afb..5df5a5937 100644 --- a/core/tauri/src/runtime/window.rs +++ b/core/tauri/src/runtime/window.rs @@ -11,7 +11,7 @@ use crate::{ hooks::{InvokeMessage, InvokePayload, PageLoadPayload}, runtime::{ tag::ToJavascript, - webview::{CustomProtocol, FileDropHandler, WebviewRpcHandler}, + webview::{FileDropHandler, WebviewRpcHandler}, Dispatch, Runtime, }, sealed::{ManagerBase, RuntimeOrDispatch}, @@ -38,9 +38,6 @@ pub struct PendingWindow { /// How to handle RPC calls on the webview window. pub rpc_handler: Option>, - /// How to handle custom protocols for the webview window. - pub custom_protocol: Option, - /// How to handle a file dropping onto the webview window. pub file_drop_handler: Option>, } @@ -57,7 +54,6 @@ impl PendingWindow { label, url, rpc_handler: None, - custom_protocol: None, file_drop_handler: None, } } @@ -71,7 +67,6 @@ impl PendingWindow { label, url, rpc_handler: None, - custom_protocol: None, file_drop_handler: None, } }