Repository navigation
Accept a file dropped anywhere, and say why one is refused - #612
Merged
Merged
Conversation
added 2 commits
September 9, 2026 15:44
Dropping an audio file anywhere except the URL zone was left to the browser, which navigates to it. In a tab that is merely surprising. In the desktop shell there is no address bar and no back button, so the window is replaced by the webview's bare media player and the only way out is to quit the app, which is what the reporter hit with a .ogg. A file dropped anywhere in the window is now treated as a drop on the URL zone. Preventing the default is what stops the navigation; routing it to applyFiles is what makes the gesture do the obvious thing rather than nothing. The URL zone keeps its hover highlight, so it still shows where the file is going. Both handlers are guarded on the drag carrying "Files", so the library's own drags -- tracks between folders, into the lanes, onto the trash -- are untouched. Their handlers already bail before preventDefault when the drag is not theirs, so the two never see each other's gestures. A refused file now answers in the box it was aimed at, in red, rather than in the error panel. That panel is for a job that failed: it takes over a region and carries a retry button, and a file declined before anything started has nothing to retry. Both hard refusals use it, unsupported format and too large, because two different mechanisms for two refusals in the same gesture is how the next report gets written. The partial-skip case still uses the panel: files were accepted there, so it is a note rather than a refusal. The message clears on the next drop, on typing, and after six seconds, so it can never sit beside an armed file describing something else. Found while doing this, and worth knowing: static/css/widgets.css is not loaded by index.html and is referenced by nothing. The styling for this went in there first and was invisible, which the test caught only because it asserts the computed colour rather than the presence of a class. Left alone here; it wants its own look. tests/e2e/file-drop.spec.mjs covers a drop on the body arming the import, the same with a track open, the refusal being visible and red and hiding the input, a refusal clearing when something valid arrives or when the user types, and a library drag not being mistaken for a file. The navigation itself cannot be asserted: a synthetic drop never triggers the browser's real navigation, so a page.url() check passes with the fix removed and proves nothing. The tests assert the drop's default was cancelled instead, which is the mechanism that prevents it. Verified against the previous behaviour: four of the seven fail. Closes #584
The rule that hides the input while a refusal is showing used :has(). It reads better than the alternative, and a sibling combinator cannot do the job at all because the message follows the input in the markup. But this ships in three webviews and the oldest is the one that matters: WebKitGTK only grew :has() in 2.38, and Linux is exactly where #584 was reported from. On an older WebKitGTK the message and the input would both render and compete for the same flex space. Degraded rather than broken, and avoidable for one line. A class toggled alongside the message works in every engine. The codebase had no other :has() before this, so this keeps it at none. The rest of the change was already portable: HTML5 drag-and-drop with preventDefault is the same in WebView2, WKWebView and WebKitGTK, and dragDropEnabled is false in the single tauri.conf.json with no per-platform override, so the webview receives the events on all three.
thcp
added a commit
that referenced
this pull request
Sep 23, 2026
…hem (#680) 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. Co-authored-by: Thales <>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #584
The problem
Dropping an audio file anywhere except the URL zone was left to the browser, which navigates to it. In a tab that is merely surprising. In the desktop shell there is no address bar and no back button, so the window is replaced by the webview's bare media player and the only way out is to quit, which is exactly what @MetalMan1245 hit with a .ogg.
The fix
A file dropped anywhere in the window is now treated as a drop on the URL zone.
Preventing the default is what stops the navigation. Routing it to
applyFilesis what makes the gesture do the obvious thing instead of nothing, which is what you asked for and is the better answer than simply blocking it. The URL zone keeps its hover highlight, so it still shows where the file is going.Both handlers are guarded on the drag carrying
Files, so the library's own drags (tracks between folders, into the lanes, onto the trash) are untouched. Their handlers already bail beforepreventDefaultwhen the drag is not theirs, so the two never see each other's gestures.Refusals answer in the box
A file the importer declines now says so in the URL box, in red, rather than in the error panel. That panel is for a job that failed: it takes over a region and carries a retry button, and a file declined before anything started has nothing to retry.
Both hard refusals use it, unsupported format and too large, because two different mechanisms for two refusals in the same gesture is how the next report gets written. The partial-skip case still uses the panel, since files were accepted there and it is a note rather than a refusal.
It clears on the next drop, on typing, and after six seconds, so it can never sit beside an armed file describing something else.
No new i18n keys:
upload.unsupportedFormatandupload.fileTooLargealready exist in all ten tables.A note on the tests
The navigation itself cannot be asserted. A synthetic drop never triggers the browser's real navigation, so a
page.url()check passes with the fix ripped out and proves nothing. I wrote that test first and the mutation run caught it. The tests assert the drop's default was cancelled instead, which is the mechanism that actually prevents it.Verified against the previous behaviour: four of the seven fail.
Found in passing
static/css/widgets.cssis not loaded byindex.htmland is referenced by nothing in the repo. The styling for this went in there first and was completely invisible; the test caught it only because it asserts the computed colour rather than the presence of a class. Left alone here, but it wants a look.