Skip to content

Add support for more platforms via web browser fallback backend#309

Open
yeicor wants to merge 4 commits intoPolyMeilex:masterfrom
yeicor:feature/web-dialogs
Open

Add support for more platforms via web browser fallback backend#309
yeicor wants to merge 4 commits intoPolyMeilex:masterfrom
yeicor:feature/web-dialogs

Conversation

@yeicor
Copy link

@yeicor yeicor commented Feb 12, 2026

This PR adds support for building and running the rfd crate on additional targets by introducing a web fallback backend.

The web fallback works by spawning a local HTTP server and opening the system browser to present file dialogs. This approach enables broad cross-platform compatibility in environments where native backends are unavailable.

Demo: Android, running without any special permissions

output.mp4

⚠️ Note: Most of this code was written by LLMs and will require a thorough review.

Copilot AI review requested due to automatic review settings February 12, 2026 12:26
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds a web fallback backend for the rfd file dialog library, enabling support for platforms that lack native file dialog APIs (primarily Android and other non-standard platforms). The implementation uses a local HTTP server (Axum) combined with the system's web browser to present file dialogs to users.

Changes:

  • Adds a new web_fallback backend module with full dialog implementations (message, file picker, folder picker, and save dialogs)
  • Updates documentation to reflect support for additional platforms via web fallback
  • Adds platform-conditional dependencies for the fallback backend (axum, tokio, webbrowser, tempfile)

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/backend/web_fallback.rs New 1240-line backend implementation using HTTP server + browser approach for dialogs
src/backend.rs Added conditional compilation to include web_fallback module for unsupported platforms
src/lib.rs Updated feature matrix documentation to show "Others (Web)" column with full support
src/oneshot.rs Added #[allow(dead_code)] attributes (module unused by web_fallback, which uses tokio::sync::oneshot)
Cargo.toml Added platform-conditional dependencies for web fallback (webbrowser, tempfile, tokio, axum)
Cargo.lock Lock file updates for new dependencies
README.md Added "Others (via web fallback)" to supported platforms list

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +53 to +65
fn build_accept(filters: &[crate::file_dialog::Filter]) -> String {
let mut exts: Vec<String> = Vec::new();
for filter in filters {
for ext in &filter.extensions {
if !ext.is_empty() {
exts.push(format!(".{}", ext));
}
}
}
if exts.is_empty() {
String::new()
} else {
format!(r#"accept="{}""#, exts.join(","))
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build_accept function generates an HTML accept attribute by directly concatenating file extensions without escaping them (line 65). If a malicious or malformed filter contains special characters (quotes, angle brackets, etc.), it could break out of the attribute and inject arbitrary HTML/JavaScript. File extensions should be HTML-escaped or validated to contain only safe characters before being inserted into the attribute.

Copilot uses AI. Check for mistakes.

// Read the body with increased limit for many files
let read_start = Instant::now();
let body_bytes = match axum::body::to_bytes(request.into_body(), 500 * 1024 * 1024).await {
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 500MB body size limit (line 715) may be excessive for a file dialog operation and could enable denial-of-service attacks by allowing users to consume large amounts of memory. Consider whether this limit is necessary for the intended use case, and if a smaller limit (e.g., 100MB) would be more appropriate. Also consider adding configuration to make this limit adjustable.

Copilot uses AI. Check for mistakes.
Comment on lines +629 to +634
let (tx, rx) = oneshot::channel();
std::thread::spawn(move || {
let result = MessageDialogImpl::show(self);
let _ = tx.send(result);
});
Box::pin(async move { rx.await.unwrap_or(MessageDialogResult::Cancel) })
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The async implementation spawns a new thread for each dialog operation (line 630), which then creates a new tokio runtime. This can be inefficient for applications that show multiple dialogs. Consider using a shared tokio runtime or a thread pool, especially if the async operations are expected to be used frequently. The current approach could lead to resource exhaustion in scenarios with many concurrent dialogs.

Suggested change
let (tx, rx) = oneshot::channel();
std::thread::spawn(move || {
let result = MessageDialogImpl::show(self);
let _ = tx.send(result);
});
Box::pin(async move { rx.await.unwrap_or(MessageDialogResult::Cancel) })
Box::pin(async move {
match tokio::task::spawn_blocking(move || MessageDialogImpl::show(self)).await {
Ok(result) => result,
Err(_) => MessageDialogResult::Cancel,
}
})

Copilot uses AI. Check for mistakes.
Comment on lines +535 to +537
Err(_) => {
debug!("Web fallback: Timeout");
None
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a timeout occurs (line 535-537), the spawned server task (line 515) will continue running indefinitely since no shutdown signal can be sent (the shutdown_rx is consumed by the graceful_shutdown handler at line 511). This leads to a resource leak - the server will keep listening on the port even after the dialog has timed out and the runtime is dropped. Consider using a timeout-aware shutdown mechanism or ensuring the server task is properly cancelled.

Copilot uses AI. Check for mistakes.
Comment on lines +501 to +502
let url = format!("http://127.0.0.1:{}/", addr.port());
debug!("Web fallback: Server listening on {}", url);
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The web server has no CSRF protection. Although the server binds to localhost (127.0.0.1), a malicious website could potentially submit forms to the local server if it can predict the port or scan for it. While the risk is limited since the server is short-lived and port is random, consider adding a random token to URLs and form submissions, or implementing origin checking to prevent cross-site requests from attacking the local server.

Copilot uses AI. Check for mistakes.
Comment on lines +260 to +261
if let Ok(byte) = u8::from_str_radix(&hex, 16) {
result.push(byte as char);
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The url_decode function has a critical security vulnerability. Converting bytes directly to chars using byte as char (line 261) is incorrect for non-ASCII bytes and can lead to invalid UTF-8 sequences or security issues. URL-encoded bytes should be collected and then converted to a UTF-8 string. This could allow malformed input to bypass validation or cause unexpected behavior. Consider using a proper URL decoding library or implementing correct UTF-8 byte sequence handling.

Copilot uses AI. Check for mistakes.
yeicor and others added 3 commits February 12, 2026 13:45
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant