Skip to content

Commit

Permalink
feat: add WindowBuilder::on_download, closes #8157 (#8159)
Browse files Browse the repository at this point in the history
* on_download_started & on_download_completed setters

* macos: default handler fn if not set

* remove default macos handler

* doc comments

* unify hooks, change files

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
  • Loading branch information
pashokitsme and lucasfernog committed Dec 30, 2023
1 parent 27bad32 commit 29ced5c
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changes/on-download-hook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch:feat
---

Added `WindowBuilder::on_download` to handle download request events.
6 changes: 6 additions & 0 deletions .changes/runtime-on-download-hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri-runtime": patch:feat
"tauri-runtime-wry": patch:feat
---

Added download event closure via `PendingWindow::download_handler`.
26 changes: 22 additions & 4 deletions core/tauri-runtime-wry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ use tauri_runtime::{
webview::{WebviewIpcHandler, WindowBuilder, WindowBuilderBase},
window::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
CursorIcon, DetachedWindow, FileDropEvent, PendingWindow, RawWindow, WindowEvent,
CursorIcon, DetachedWindow, DownloadEvent, FileDropEvent, PendingWindow, RawWindow,
WindowEvent,
},
DeviceEventFilter, Dispatch, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result,
RunEvent, RunIteration, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent,
Expand Down Expand Up @@ -2719,8 +2720,6 @@ fn create_webview<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(
label,
ipc_handler,
url,
#[cfg(target_os = "android")]
on_webview_created,
..
} = pending;

Expand Down Expand Up @@ -2852,6 +2851,25 @@ fn create_webview<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(
});
}

if let Some(download_handler) = pending.download_handler {
let download_handler_ = download_handler.clone();
webview_builder = webview_builder.with_download_started_handler(move |url, path| {
if let Ok(url) = url.parse() {
download_handler_(DownloadEvent::Requested {
url,
destination: path,
})
} else {
false
}
});
webview_builder = webview_builder.with_download_completed_handler(move |url, path, success| {
if let Ok(url) = url.parse() {
download_handler(DownloadEvent::Finished { url, path, success });
}
});
}

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| {
Expand Down Expand Up @@ -2954,7 +2972,7 @@ fn create_webview<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(

#[cfg(target_os = "android")]
{
if let Some(on_webview_created) = on_webview_created {
if let Some(on_webview_created) = pending.on_webview_created {
webview_builder = webview_builder.on_webview_created(move |ctx| {
on_webview_created(tauri_runtime::window::CreationContext {
env: ctx.env,
Expand Down
30 changes: 29 additions & 1 deletion core/tauri-runtime/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::{
hash::{Hash, Hasher},
marker::PhantomData,
path::PathBuf,
sync::mpsc::Sender,
sync::{mpsc::Sender, Arc},
};

use self::dpi::PhysicalPosition;
Expand All @@ -36,6 +36,30 @@ type NavigationHandler = dyn Fn(&Url) -> bool + Send;

type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send;

type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync;

/// Download event.
pub enum DownloadEvent<'a> {
/// Download requested.
Requested {
/// The url being downloaded.
url: Url,
/// Represents where the file will be downloaded to.
/// Can be used to set the download location by assigning a new path to it.
/// The assigned path _must_ be absolute.
destination: &'a mut PathBuf,
},
/// Download finished.
Finished {
/// The URL of the original download request.
url: Url,
/// Potentially representing the filesystem path the file was downloaded to.
path: Option<PathBuf>,
/// Indicates if the download succeeded or not.
success: bool,
},
}

/// Kind of event for the page load handler.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PageLoadEvent {
Expand Down Expand Up @@ -240,6 +264,8 @@ pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
/// A handler to decide if incoming url is allowed to navigate.
pub navigation_handler: Option<Box<NavigationHandler>>,

pub download_handler: Option<Arc<DownloadHandler>>,

/// The resolved URL to load on the webview.
pub url: String,

Expand Down Expand Up @@ -284,6 +310,7 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
label,
ipc_handler: None,
navigation_handler: None,
download_handler: None,
url: "tauri://localhost".to_string(),
#[cfg(target_os = "android")]
on_webview_created: None,
Expand Down Expand Up @@ -313,6 +340,7 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
label,
ipc_handler: None,
navigation_handler: None,
download_handler: None,
url: "tauri://localhost".to_string(),
#[cfg(target_os = "android")]
on_webview_created: None,
Expand Down
105 changes: 101 additions & 4 deletions core/tauri/src/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ use std::{
pub(crate) type WebResourceRequestHandler =
dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
pub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send;
pub(crate) type DownloadHandler<R> = dyn Fn(Window<R>, DownloadEvent<'_>) -> bool + Send + Sync;
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;
Expand Down Expand Up @@ -92,6 +93,38 @@ impl<'a> PageLoadPayload<'a> {
}
}

/// Download event for the [`WindowBuilder#method.on_download`] hook.
#[non_exhaustive]
pub enum DownloadEvent<'a> {
/// Download requested.
Requested {
/// The url being downloaded.
url: Url,
/// Represents where the file will be downloaded to.
/// Can be used to set the download location by assigning a new path to it.
/// The assigned path _must_ be absolute.
destination: &'a mut PathBuf,
},
/// Download finished.
Finished {
/// The URL of the original download request.
url: Url,
/// Potentially representing the filesystem path the file was downloaded to.
///
/// A value of `None` being passed instead of a `PathBuf` does not necessarily indicate that the download
/// did not succeed, and may instead indicate some other failure - always check the third parameter if you need to
/// know if the download succeeded.
///
/// ## Platform-specific:
///
/// - **macOS**: The second parameter indicating the path the file was saved to is always empty, due to API
/// limitations.
path: Option<PathBuf>,
/// Indicates if the download succeeded or not.
success: bool,
},
}

/// Monitor descriptor.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -149,6 +182,7 @@ pub struct WindowBuilder<'a, R: Runtime> {
pub(crate) webview_attributes: WebviewAttributes,
web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
navigation_handler: Option<Box<NavigationHandler>>,
download_handler: Option<Arc<DownloadHandler<R>>>,
on_page_load_handler: Option<Box<OnPageLoad<R>>>,
#[cfg(desktop)]
on_menu_event: Option<crate::app::GlobalMenuEventListener<Window<R>>>,
Expand Down Expand Up @@ -228,6 +262,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
webview_attributes: WebviewAttributes::new(url),
web_resource_request_handler: None,
navigation_handler: None,
download_handler: None,
on_page_load_handler: None,
#[cfg(desktop)]
on_menu_event: None,
Expand Down Expand Up @@ -267,6 +302,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
window_builder: <R::Dispatcher as Dispatch<EventLoopMessage>>::WindowBuilder::with_config(
config,
),
download_handler: None,
web_resource_request_handler: None,
#[cfg(desktop)]
menu: None,
Expand Down Expand Up @@ -353,6 +389,47 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
self
}

/// Set a download event handler to be notified when a download is requested or finished.
///
/// Returning `false` prevents the download from happening on a [`DownloadEvent::Requested`] event.
///
/// # Examples
///
/// ```rust,no_run
/// use tauri::{
/// utils::config::{Csp, CspDirectiveSources, WindowUrl},
/// window::{DownloadEvent, WindowBuilder},
/// };
///
/// tauri::Builder::default()
/// .setup(|app| {
/// WindowBuilder::new(app, "core", WindowUrl::App("index.html".into()))
/// .on_download(|window, event| {
/// match event {
/// DownloadEvent::Requested { url, destination } => {
/// println!("downloading {}", url);
/// *destination = "/home/tauri/target/path".into();
/// }
/// DownloadEvent::Finished { url, path, success } => {
/// println!("downloaded {} to {:?}, success: {}", url, path, success);
/// }
/// _ => (),
/// }
/// // let the download start
/// true
/// })
/// .build()?;
/// Ok(())
/// });
/// ```
pub fn on_download<F: Fn(Window<R>, DownloadEvent<'_>) -> bool + Send + Sync + 'static>(
mut self,
f: F,
) -> Self {
self.download_handler.replace(Arc::new(f));
self
}

/// 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.
Expand All @@ -361,18 +438,16 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
///
/// ```rust,no_run
/// use tauri::{
/// utils::config::{Csp, CspDirectiveSources, WindowUrl},
/// utils::config::WindowUrl,
/// window::{PageLoadEvent, 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| {
/// match payload.event() {
/// PageLoadEvent::Started => {
/// println!("{} finished loading", payload.url());
/// println!("{} started loading", payload.url());
/// }
/// PageLoadEvent::Finished => {
/// println!("{} finished loading", payload.url());
Expand Down Expand Up @@ -444,6 +519,28 @@ 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(download_handler) = self.download_handler.take() {
let label = pending.label.clone();
let manager = self.app_handle.manager.clone();
pending.download_handler.replace(Arc::new(move |event| {
if let Some(w) = manager.get_window(&label) {
download_handler(
w,
match event {
tauri_runtime::window::DownloadEvent::Requested { url, destination } => {
DownloadEvent::Requested { url, destination }
}
tauri_runtime::window::DownloadEvent::Finished { url, path, success } => {
DownloadEvent::Finished { url, path, success }
}
},
)
} else {
false
}
}));
}

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();
Expand Down

0 comments on commit 29ced5c

Please sign in to comment.