diff --git a/packages/desktop/src/config.rs b/packages/desktop/src/config.rs index 78d45bfe9..11c62a1a6 100644 --- a/packages/desktop/src/config.rs +++ b/packages/desktop/src/config.rs @@ -20,6 +20,10 @@ type CustomEventHandler = Box< ), >; +/// A function taking a URL and returning whether the webview should navigate to it or open it in +/// the browser. If missing in the config, all URLs will be allowed. +type NavigationHandler = Box bool + 'static>; + /// The closing behaviour of specific application window. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[non_exhaustive] @@ -70,6 +74,7 @@ pub struct Config { pub(crate) disable_dma_buf_on_wayland: bool, pub(crate) additional_windows_args: Option, pub(crate) tray_icon_show_window_on_click: bool, + pub(crate) navigation_handler: Option, #[allow(clippy::type_complexity)] pub(crate) on_window: Option, &mut VirtualDom) + 'static>>, @@ -124,6 +129,7 @@ impl Config { on_window: None, additional_windows_args: None, tray_icon_show_window_on_click: true, + navigation_handler: None, } } @@ -350,6 +356,13 @@ impl Config { self.tray_icon_show_window_on_click = show; self } + + /// Set a custom navigation handler for non-dioxus URLs. + /// Return true to allow navigation inside the webview, false to block. + pub fn with_navigation_handler(mut self, f: impl Fn(&str) -> bool + 'static) -> Self { + self.navigation_handler = Some(Box::new(f)); + self + } } impl Default for Config { diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index 9710721fc..a86a0b30f 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -353,6 +353,7 @@ impl WebviewInstance { } }; + let navigation_handler = cfg.navigation_handler.take(); let page_loaded = AtomicBool::new(false); let mut webview = WebViewBuilder::new_with_web_context(&mut web_context) @@ -367,25 +368,29 @@ impl WebviewInstance { .with_url("dioxus://index.html/") .with_ipc_handler(ipc_handler) .with_navigation_handler(move |var| { - // We don't want to allow any navigation - // We only want to serve the index file and assets + // Serve the index and assets. if var.starts_with("dioxus://") || var.starts_with("http://dioxus.") || var.starts_with("https://dioxus.") { // After the page has loaded once, don't allow any more navigation let page_loaded = page_loaded.swap(true, std::sync::atomic::Ordering::SeqCst); - !page_loaded - } else { - if var.starts_with("http://") - || var.starts_with("https://") - || var.starts_with("mailto:") - { - _ = webbrowser::open(&var); - } - false + return !page_loaded; } - }) // prevent all navigations + + // External links always open somewhere else. Prevents the webview from navigating + if var.starts_with("http://") + || var.starts_with("https://") + || var.starts_with("mailto:") + { + _ = webbrowser::open(&var); + return false; + } + + // By default, external links are allowed. This keeps things like iframes working. + // However, users can customize this to allow/disallow domains/routes/patterns. + navigation_handler.as_ref().map(|f| f(&var)).unwrap_or(true) + }) .with_asynchronous_custom_protocol(String::from("dioxus"), request_handler); // Enable https scheme on android, needed for secure context API, like the geolocation API