feat(dialog) - Support fileAccessMode for open dialog (#3030) (#3136)

* feat(dialog) - Support fileAccessMode for open dialog (#3030)

On iOS, when trying to access a file that exists outside of the app sandbox, one of 2 things need to happen to be able to perform any operations on said file:

* A copy of the file needs to be made to the internal app sandbox
* The method startAccessingSecurityScopedResource needs to be called.

Previously, a copy of the file was always being made when a file was selected through the picker dialog.

While this did ensure there were no file access exceptions when reading from the file, it does not scale well for large files.

To resolve this, we now support `fileAccessMode`, which allows a file handle to be returned without copying the file to the app sandbox.

This MR only supports this change for iOS; MacOS has a different set of needs for security scoped resources.

See discussion in #3716 for more discussion of the difference between iOS and MacOS.
See MR #3185 to see how these scoped files will be accessible using security scoping.

* fmt, clippy

* use enum

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
Andrew de Waal
2026-01-14 03:35:06 -08:00
committed by GitHub
parent f3d75f7abb
commit d7a0bb325d
8 changed files with 183 additions and 68 deletions
+10 -2
View File
@@ -9,8 +9,9 @@ use tauri::{command, Manager, Runtime, State, Window};
use tauri_plugin_fs::FsExt;
use crate::{
Dialog, FileDialogBuilder, FilePath, MessageDialogBuilder, MessageDialogButtons,
MessageDialogKind, MessageDialogResult, PickerMode, Result, CANCEL, NO, OK, YES,
Dialog, FileAccessMode, FileDialogBuilder, FilePath, MessageDialogBuilder,
MessageDialogButtons, MessageDialogKind, MessageDialogResult, PickerMode, Result, CANCEL, NO,
OK, YES,
};
#[derive(Serialize)]
@@ -63,6 +64,10 @@ pub struct OpenDialogOptions {
#[serde(default)]
#[cfg_attr(mobile, allow(dead_code))]
picker_mode: Option<PickerMode>,
/// The file access mode of the dialog.
#[serde(default)]
#[cfg_attr(mobile, allow(dead_code))]
file_access_mode: Option<FileAccessMode>,
}
/// The options for the save dialog API.
@@ -141,6 +146,9 @@ pub(crate) async fn open<R: Runtime>(
let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect();
dialog_builder = dialog_builder.add_filter(filter.name, &extensions);
}
if let Some(file_access_mode) = options.file_access_mode {
dialog_builder = dialog_builder.set_file_access_mode(file_access_mode);
}
let res = if options.directory {
#[cfg(desktop)]
+20 -1
View File
@@ -53,6 +53,13 @@ pub enum PickerMode {
Video,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum FileAccessMode {
Copy,
Scoped,
}
pub(crate) const OK: &str = "Ok";
pub(crate) const CANCEL: &str = "Cancel";
pub(crate) const YES: &str = "Yes";
@@ -191,7 +198,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
commands::save,
commands::message,
commands::ask,
commands::confirm
commands::confirm,
])
.setup(|app, api| {
#[cfg(mobile)]
@@ -379,6 +386,7 @@ pub struct FileDialogBuilder<R: Runtime> {
pub(crate) title: Option<String>,
pub(crate) can_create_directories: Option<bool>,
pub(crate) picker_mode: Option<PickerMode>,
pub(crate) file_access_mode: Option<FileAccessMode>,
#[cfg(desktop)]
pub(crate) parent: Option<crate::desktop::WindowHandle>,
}
@@ -391,6 +399,7 @@ pub(crate) struct FileDialogPayload<'a> {
filters: &'a Vec<Filter>,
multiple: bool,
picker_mode: &'a Option<PickerMode>,
file_access_mode: &'a Option<FileAccessMode>,
}
// raw window handle :(
@@ -407,6 +416,7 @@ impl<R: Runtime> FileDialogBuilder<R> {
title: None,
can_create_directories: None,
picker_mode: None,
file_access_mode: None,
#[cfg(desktop)]
parent: None,
}
@@ -419,6 +429,7 @@ impl<R: Runtime> FileDialogBuilder<R> {
filters: &self.filters,
multiple,
picker_mode: &self.picker_mode,
file_access_mode: &self.file_access_mode,
}
}
@@ -488,6 +499,14 @@ impl<R: Runtime> FileDialogBuilder<R> {
self
}
/// Set the file access mode of the dialog.
/// This is only used on iOS.
/// On desktop and Android, this option is ignored.
pub fn set_file_access_mode(mut self, mode: FileAccessMode) -> Self {
self.file_access_mode.replace(mode);
self
}
/// Shows the dialog to select a single file.
///
/// This is not a blocking operation,