Skip to content

Commit 29ced5c

Browse files
feat: add WindowBuilder::on_download, closes #8157 (#8159)
* 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>
1 parent 27bad32 commit 29ced5c

File tree

5 files changed

+163
-9
lines changed

5 files changed

+163
-9
lines changed

.changes/on-download-hook.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch:feat
3+
---
4+
5+
Added `WindowBuilder::on_download` to handle download request events.

.changes/runtime-on-download-hooks.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri-runtime": patch:feat
3+
"tauri-runtime-wry": patch:feat
4+
---
5+
6+
Added download event closure via `PendingWindow::download_handler`.

core/tauri-runtime-wry/src/lib.rs

+22-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ use tauri_runtime::{
1717
webview::{WebviewIpcHandler, WindowBuilder, WindowBuilderBase},
1818
window::{
1919
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
20-
CursorIcon, DetachedWindow, FileDropEvent, PendingWindow, RawWindow, WindowEvent,
20+
CursorIcon, DetachedWindow, DownloadEvent, FileDropEvent, PendingWindow, RawWindow,
21+
WindowEvent,
2122
},
2223
DeviceEventFilter, Dispatch, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result,
2324
RunEvent, RunIteration, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent,
@@ -2719,8 +2720,6 @@ fn create_webview<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(
27192720
label,
27202721
ipc_handler,
27212722
url,
2722-
#[cfg(target_os = "android")]
2723-
on_webview_created,
27242723
..
27252724
} = pending;
27262725

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

2854+
if let Some(download_handler) = pending.download_handler {
2855+
let download_handler_ = download_handler.clone();
2856+
webview_builder = webview_builder.with_download_started_handler(move |url, path| {
2857+
if let Ok(url) = url.parse() {
2858+
download_handler_(DownloadEvent::Requested {
2859+
url,
2860+
destination: path,
2861+
})
2862+
} else {
2863+
false
2864+
}
2865+
});
2866+
webview_builder = webview_builder.with_download_completed_handler(move |url, path, success| {
2867+
if let Ok(url) = url.parse() {
2868+
download_handler(DownloadEvent::Finished { url, path, success });
2869+
}
2870+
});
2871+
}
2872+
28552873
if let Some(page_load_handler) = pending.on_page_load_handler {
28562874
webview_builder = webview_builder.with_on_page_load_handler(move |event, url| {
28572875
let _ = Url::parse(&url).map(|url| {
@@ -2954,7 +2972,7 @@ fn create_webview<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(
29542972

29552973
#[cfg(target_os = "android")]
29562974
{
2957-
if let Some(on_webview_created) = on_webview_created {
2975+
if let Some(on_webview_created) = pending.on_webview_created {
29582976
webview_builder = webview_builder.on_webview_created(move |ctx| {
29592977
on_webview_created(tauri_runtime::window::CreationContext {
29602978
env: ctx.env,

core/tauri-runtime/src/window.rs

+29-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use std::{
1919
hash::{Hash, Hasher},
2020
marker::PhantomData,
2121
path::PathBuf,
22-
sync::mpsc::Sender,
22+
sync::{mpsc::Sender, Arc},
2323
};
2424

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

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

39+
type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync;
40+
41+
/// Download event.
42+
pub enum DownloadEvent<'a> {
43+
/// Download requested.
44+
Requested {
45+
/// The url being downloaded.
46+
url: Url,
47+
/// Represents where the file will be downloaded to.
48+
/// Can be used to set the download location by assigning a new path to it.
49+
/// The assigned path _must_ be absolute.
50+
destination: &'a mut PathBuf,
51+
},
52+
/// Download finished.
53+
Finished {
54+
/// The URL of the original download request.
55+
url: Url,
56+
/// Potentially representing the filesystem path the file was downloaded to.
57+
path: Option<PathBuf>,
58+
/// Indicates if the download succeeded or not.
59+
success: bool,
60+
},
61+
}
62+
3963
/// Kind of event for the page load handler.
4064
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4165
pub enum PageLoadEvent {
@@ -240,6 +264,8 @@ pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
240264
/// A handler to decide if incoming url is allowed to navigate.
241265
pub navigation_handler: Option<Box<NavigationHandler>>,
242266

267+
pub download_handler: Option<Arc<DownloadHandler>>,
268+
243269
/// The resolved URL to load on the webview.
244270
pub url: String,
245271

@@ -284,6 +310,7 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
284310
label,
285311
ipc_handler: None,
286312
navigation_handler: None,
313+
download_handler: None,
287314
url: "tauri://localhost".to_string(),
288315
#[cfg(target_os = "android")]
289316
on_webview_created: None,
@@ -313,6 +340,7 @@ impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
313340
label,
314341
ipc_handler: None,
315342
navigation_handler: None,
343+
download_handler: None,
316344
url: "tauri://localhost".to_string(),
317345
#[cfg(target_os = "android")]
318346
on_webview_created: None,

core/tauri/src/window/mod.rs

+101-4
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ use std::{
6464
pub(crate) type WebResourceRequestHandler =
6565
dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
6666
pub(crate) type NavigationHandler = dyn Fn(&Url) -> bool + Send;
67+
pub(crate) type DownloadHandler<R> = dyn Fn(Window<R>, DownloadEvent<'_>) -> bool + Send + Sync;
6768
pub(crate) type UriSchemeProtocolHandler =
6869
Box<dyn Fn(http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync>;
6970
pub(crate) type OnPageLoad<R> = dyn Fn(Window<R>, PageLoadPayload<'_>) + Send + Sync + 'static;
@@ -92,6 +93,38 @@ impl<'a> PageLoadPayload<'a> {
9293
}
9394
}
9495

96+
/// Download event for the [`WindowBuilder#method.on_download`] hook.
97+
#[non_exhaustive]
98+
pub enum DownloadEvent<'a> {
99+
/// Download requested.
100+
Requested {
101+
/// The url being downloaded.
102+
url: Url,
103+
/// Represents where the file will be downloaded to.
104+
/// Can be used to set the download location by assigning a new path to it.
105+
/// The assigned path _must_ be absolute.
106+
destination: &'a mut PathBuf,
107+
},
108+
/// Download finished.
109+
Finished {
110+
/// The URL of the original download request.
111+
url: Url,
112+
/// Potentially representing the filesystem path the file was downloaded to.
113+
///
114+
/// A value of `None` being passed instead of a `PathBuf` does not necessarily indicate that the download
115+
/// did not succeed, and may instead indicate some other failure - always check the third parameter if you need to
116+
/// know if the download succeeded.
117+
///
118+
/// ## Platform-specific:
119+
///
120+
/// - **macOS**: The second parameter indicating the path the file was saved to is always empty, due to API
121+
/// limitations.
122+
path: Option<PathBuf>,
123+
/// Indicates if the download succeeded or not.
124+
success: bool,
125+
},
126+
}
127+
95128
/// Monitor descriptor.
96129
#[derive(Debug, Clone, Serialize)]
97130
#[serde(rename_all = "camelCase")]
@@ -149,6 +182,7 @@ pub struct WindowBuilder<'a, R: Runtime> {
149182
pub(crate) webview_attributes: WebviewAttributes,
150183
web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
151184
navigation_handler: Option<Box<NavigationHandler>>,
185+
download_handler: Option<Arc<DownloadHandler<R>>>,
152186
on_page_load_handler: Option<Box<OnPageLoad<R>>>,
153187
#[cfg(desktop)]
154188
on_menu_event: Option<crate::app::GlobalMenuEventListener<Window<R>>>,
@@ -228,6 +262,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
228262
webview_attributes: WebviewAttributes::new(url),
229263
web_resource_request_handler: None,
230264
navigation_handler: None,
265+
download_handler: None,
231266
on_page_load_handler: None,
232267
#[cfg(desktop)]
233268
on_menu_event: None,
@@ -267,6 +302,7 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
267302
window_builder: <R::Dispatcher as Dispatch<EventLoopMessage>>::WindowBuilder::with_config(
268303
config,
269304
),
305+
download_handler: None,
270306
web_resource_request_handler: None,
271307
#[cfg(desktop)]
272308
menu: None,
@@ -353,6 +389,47 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
353389
self
354390
}
355391

392+
/// Set a download event handler to be notified when a download is requested or finished.
393+
///
394+
/// Returning `false` prevents the download from happening on a [`DownloadEvent::Requested`] event.
395+
///
396+
/// # Examples
397+
///
398+
/// ```rust,no_run
399+
/// use tauri::{
400+
/// utils::config::{Csp, CspDirectiveSources, WindowUrl},
401+
/// window::{DownloadEvent, WindowBuilder},
402+
/// };
403+
///
404+
/// tauri::Builder::default()
405+
/// .setup(|app| {
406+
/// WindowBuilder::new(app, "core", WindowUrl::App("index.html".into()))
407+
/// .on_download(|window, event| {
408+
/// match event {
409+
/// DownloadEvent::Requested { url, destination } => {
410+
/// println!("downloading {}", url);
411+
/// *destination = "/home/tauri/target/path".into();
412+
/// }
413+
/// DownloadEvent::Finished { url, path, success } => {
414+
/// println!("downloaded {} to {:?}, success: {}", url, path, success);
415+
/// }
416+
/// _ => (),
417+
/// }
418+
/// // let the download start
419+
/// true
420+
/// })
421+
/// .build()?;
422+
/// Ok(())
423+
/// });
424+
/// ```
425+
pub fn on_download<F: Fn(Window<R>, DownloadEvent<'_>) -> bool + Send + Sync + 'static>(
426+
mut self,
427+
f: F,
428+
) -> Self {
429+
self.download_handler.replace(Arc::new(f));
430+
self
431+
}
432+
356433
/// Defines a closure to be executed when a page load event is triggered.
357434
/// The event can be either [`PageLoadEvent::Started`] if the page has started loading
358435
/// or [`PageLoadEvent::Finished`] when the page finishes loading.
@@ -361,18 +438,16 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
361438
///
362439
/// ```rust,no_run
363440
/// use tauri::{
364-
/// utils::config::{Csp, CspDirectiveSources, WindowUrl},
441+
/// utils::config::WindowUrl,
365442
/// window::{PageLoadEvent, WindowBuilder},
366443
/// };
367-
/// use http::header::HeaderValue;
368-
/// use std::collections::HashMap;
369444
/// tauri::Builder::default()
370445
/// .setup(|app| {
371446
/// WindowBuilder::new(app, "core", WindowUrl::App("index.html".into()))
372447
/// .on_page_load(|window, payload| {
373448
/// match payload.event() {
374449
/// PageLoadEvent::Started => {
375-
/// println!("{} finished loading", payload.url());
450+
/// println!("{} started loading", payload.url());
376451
/// }
377452
/// PageLoadEvent::Finished => {
378453
/// println!("{} finished loading", payload.url());
@@ -444,6 +519,28 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
444519
pending.navigation_handler = self.navigation_handler.take();
445520
pending.web_resource_request_handler = self.web_resource_request_handler.take();
446521

522+
if let Some(download_handler) = self.download_handler.take() {
523+
let label = pending.label.clone();
524+
let manager = self.app_handle.manager.clone();
525+
pending.download_handler.replace(Arc::new(move |event| {
526+
if let Some(w) = manager.get_window(&label) {
527+
download_handler(
528+
w,
529+
match event {
530+
tauri_runtime::window::DownloadEvent::Requested { url, destination } => {
531+
DownloadEvent::Requested { url, destination }
532+
}
533+
tauri_runtime::window::DownloadEvent::Finished { url, path, success } => {
534+
DownloadEvent::Finished { url, path, success }
535+
}
536+
},
537+
)
538+
} else {
539+
false
540+
}
541+
}));
542+
}
543+
447544
if let Some(on_page_load_handler) = self.on_page_load_handler.take() {
448545
let label = pending.label.clone();
449546
let manager = self.app_handle.manager.clone();

0 commit comments

Comments
 (0)