Skip to content

Take file drops on Linux in the shell, where the WebView cannot see them - #680

Merged
thcp merged 1 commit into
mainfrom
fix/linux-file-drop-672
Sep 23, 2026
Merged

thcp merged 1 commit into
mainfrom
fix/linux-file-drop-672

Conversation

@thcp

@thcp thcp commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

Closes #672

What was wrong

On the Linux desktop app, dropping a file anywhere turned the window into a bare media player. Dropping it on the URL box typed its file:// path into the box, and Process then failed with "URL must use http or https".

Both have one cause. WebKitGTK, the Linux webview, reports a drag from a file manager as text/uri-list and text/html, never as Files. The drop guard from #612 looks for Files, so it never recognised the drag, and WebKit's default ran instead: open the file, or type its path into a text box. Windows, macOS and Chromium all report Files, which is why the guard works there.

Widening the guard would not have fixed it. I measured it against a real drag into WebKitGTK 2.52. Even with the drop cancelled, the page receives no files and the path is blanked out. The page can stop the navigation but can never get the file.

The fix

On Linux the app takes the drop, not the page.

  • The main window's native drop handler is on for Linux only. Tauri consumes the drop before WebKit's default can run.
  • wry only claims drags that carry files, so the library's own drags between folders, lanes and the trash are untouched.
  • That was the reason the handler is off everywhere (fix(ui): fix drag-and-drop on Windows and catalog rail layout #24): on Windows it stops the page's own drags. It stays off on Windows and macOS.

The window is now built in setup from its existing tauri.conf.json entry, marked "create": false, with only that one setting changed per platform. A per-platform config file would have needed a second full copy of the window definition.

No command takes a file path from JavaScript. The page is a remote origin to Tauri, and dragout and download_to_path already follow this rule for that reason.

  • A drop is recorded against opaque ids, and the page is told only a name and size for each.
  • The page can read back a file the user dropped, once, by id, from the latest drop only.
  • Reads are capped at the upload limit.

The page waits on a command, not an event. A remote origin cannot listen to Tauri events without a capability that would expose every core event command. Signals are kept in a log that each caller reads from its own position, so a reloaded page's leftover call cannot swallow the next drop.

On the page, applyFiles is split into screening and staging.

  • A native drop is screened on names and sizes first, so files that will be refused are never read.
  • From there it follows the same path as the file picker, with the same messages.
  • A file that has disappeared before it is read is reported by name, in all ten languages.

The upload limit now appears in three languages, and a new test holds the server, the page and the app to the same number.

Verification

  • A real Tauri 2.11 app on Linux, built around this module, with genuine drags from a separate GTK process:
    • a drop on the page and a drop on a text box both arrive with the file's real bytes;
    • nothing navigates, and nothing is typed into the box;
    • reading the same file a second time is refused;
    • a drag using the library's own data types still lands on its target.
  • The Windows build still opens its main window.
  • Tests:
    • e2e: 238 pass, 9 new, covering the page's side against a stand-in app.
    • Rust: 110 tests pass on Windows and 120 on Linux, 11 of them new. fmt and clippy are clean on both.
    • i18n coverage is clean.
  • macOS could not be compiled locally, so PR CI covers it.

Dropping a file on the Linux desktop app turned the window into a bare media
player playing it, with only a right click and Back to get out. Dropping it
on the URL box typed its file:// URI into the box, and pressing Process then
failed with "URL must use http or https" (#672).

Both come from one place. WebKitGTK reports a drag from a GTK file manager as
text/uri-list and text/html, never as "Files", so the drop guard added in
#612, which keys on "Files", never recognised it and WebKit's default ran:
navigate to the file, or insert the URI into an editable target. The guard
works on Windows, macOS and in Chromium, which do report "Files", which is
why it was never seen there.

Widening the guard was not a fix. Measured against a real drag into
WebKitGTK 2.52, a cancelled drop reaches the page with no File objects, no
file items, and the path blanked out of text/uri-list. The page cannot get
the file at all, only stop the navigation.

So on Linux the main window's native drop handler is on and the drop is taken
in the shell. Tauri's handler returns true on a drop, which consumes it before
WebKit's default runs, and wry only claims drags carrying a file uri-list, so
the library's own HTML5 drags between folders, lanes and the trash are left
alone. That was the reason the handler is off everywhere (#24): on Windows,
WebView2 stops delivering HTML5 drags while it is on. It stays off on Windows
and macOS, where the page already gets the files.

The window is now built in setup from its tauri.conf.json entry, marked
"create": false, with that one field set per platform. A per-platform config
file would have needed a second full copy of the window, since its merge
replaces arrays rather than patching them.

No command takes a path from JavaScript. The page is a remote origin to
Tauri, so app commands are callable from it without an ACL, and dragout and
download_to_path already keep paths on the Rust side for that reason. A drop
is recorded against opaque ids; the page is told a name and a size for each,
and can read back a file the user dropped, once, by id. Only the latest drop
is readable, a folder or a vanished file is refused, and the read is capped
at the upload limit against the open handle so it cannot be raced past.

The page cannot listen to Tauri events without a remote capability, and
granting one would open every core event command to it. It already talks to
the shell only through commands, so it waits on next_drop_signal instead. The
signals are a log read by cursor rather than a queue that is popped: a reload
leaves the old page's call waiting with nobody to answer, and a popped queue
would hand it the next drop.

On the page, applyFiles is split into screening and staging. A native drop is
screened on its names and sizes first, so a file that will be refused as not
audio or too large is never read, and only accepted files are fetched, one at
a time, as raw bytes. From there it is the same path as the picker: same
refusals, same pill, same staging. A file that has gone by the time it is read
says so by name, in all ten languages.

The upload limit now exists in three languages, so a test holds the server,
the page and the shell to the same number. The first two were already
duplicated with only a comment saying they must match.

Verified in a real Tauri 2.11 shell around this module on Linux, driving
genuine XDND drags from a separate GTK process: a drop on the page and a drop
on a text box both arrive as ids with the file's real bytes, nothing
navigates, nothing is typed into the box, a second read of an id is refused,
and an internal drag with the library's own data types still lands on its
target. The Windows build still opens its window.
@thcp thcp mentioned this pull request Sep 23, 2026
2 tasks done
@thcp
thcp merged commit c29c042 into main Sep 23, 2026
13 checks passed
@thcp
thcp deleted the fix/linux-file-drop-672 branch September 23, 2026 12:50
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.

[Bug]: Drag'n'drop oddities

1 participant