Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add WindowBuilder::on_download, closes #8157 #8159

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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