[BlinkOn21] Fixing Drag-and-Drop in Chromium: What Was Broken and What Changed
I gave a lightning talk at BlinkOn 21 on fixing drag-and-drop in Chromium. Drag-and-drop is one of the oldest and most intuitive interactions in graphical user interfaces, yet several long-standing gaps in Chromium made it unreliable when data crossed the boundary between the browser and other applications. This post summarizes the three problems I worked on and how each was fixed.
- Slides: Fixing Drag-and-Drop in Chromium: What Was Broken and What Changed
- Demo videos: X thread
Drag-and-Drop Issues in Chromium
The talk covered three distinct issues with the DataTransfer API:
text/uri-listdrag type — multiple URLs were concatenated into a single, invalid URL.DownloadURLdrag type — only a single URL was supported.- JavaScript
Fileobject — dragging a constructedFileout of the browser was not implemented, even though Firefox supports it.
1. text/uri-list: Multiple URLs Were Concatenated
When a web page calls setData('text/uri-list', uriList) with multiple URLs,
Chromium's internal data handling stripped the CRLF delimiters. As a result, when
the data was read back via getData('text/uri-list') during a drop event, the
result was a single, concatenated, and invalid URL string.
For example, this correct input:
https://mozilla.org\r\nhttps://webkit.org\r\nhttps://chromium.orgwas turned into this malformed URL:
https://mozilla.orghttps://webkit.orghttps://chromium.orgIn contrast, Firefox returns the correct, original string. This bug broke both internal drops (for example, to another Chromium window) and external drops (for example, onto the desktop or another application), because the receiving target could not parse the malformed URL. This issue has been fixed since M145.
Implementation Details
- Issue: text/uri-list: Dragging multiple URIs results in a single concatenated URL (41011768)
- Change lists:
2. DownloadURL: From a Single URL to a List
The DownloadURL drag type was introduced in Chromium in 2009 to support
downloading a URL to the local desktop via drag-and-drop. The idea goes back to a
WHATWG proposal, Proposal to drag virtual file out of browser:
if a draggable element contains a URL, dragging it out of the browser normally
copies only the URL value, but in many scenarios we actually want to download the
file the URL points to. DownloadURL makes that possible, and it has long been
used by apps such as Outlook and Gmail to download mail attachments to the user's
desktop.
The limitation was that DownloadURL only supports a single URL:
e.dataTransfer.setData(
"DownloadURL",
"image/png:myImage.png:https://example.com/path/to/image.png"
);To support multiple downloads in one drag, I introduced a new drag type,
DownloadURL-list, whose payload is a JSON array that can hold information for
multiple files:
const data = [
{
type: 'image/png',
name: 'file2.png',
url: 'http://example.com/file2.png'
},
{
type: 'image/jpeg',
name: 'file1.jpg',
url: 'http://example.com/file1.jpg'
}
];
event.dataTransfer.setData('DownloadURL-list', JSON.stringify(data));Implementation Details
- Issue: Multi files drag out to filesystem via DownloadURL (40736398)
- Design: Enabling Multi-File Drag-and-Drop in Chromium
- Explainer: Drag Multiple Virtual Files Out of Browser
- Chrome Platform Status: DownloadURL-list: A drag-and-drop type for multi-file downloads
- Change list: Support dragging multiple files via DownloadURL drag type (6625735)
3. JavaScript File Object: Delivering the Bytes
Theoretically, a JavaScript File object can be attached to the DataTransfer
API at dragstart and read back at drop:
source.addEventListener('dragstart', (e) => {
// Create a File object with text content.
const file = new File(['Hello from a JS File!'], 'hello.txt', {
type: 'text/plain'
});
// Add the File to the drag data.
e.dataTransfer.items.add(file);
e.dataTransfer.effectAllowed = 'copy';
});
dropZone.addEventListener('drop', async (e) => {
e.preventDefault();
const files = e.dataTransfer.files;
for (const file of files) {
const text = await file.text();
result.textContent += `${file.name} ${file.type} ${file.size} ${text}`;
}
});In practice, Chromium only dropped the file name as a long-missing fallback in
third_party/blink/renderer/core/clipboard/data_object.cc; the actual bytes were
discarded. Firefox, by contrast, already supports dragging and dropping
JavaScript-created File objects out of the browser, with no file-type
limitations.
I implemented the missing path for Windows, Linux, and macOS so that the bytes of
a constructed File are delivered to the OS drop target. The initial change
lists support image types only; support for other file types will be added after
they land. Once merged, this lets users drag a File out of Chromium and drop it
even onto Firefox.
Dragging a JavaScript File from Chromium and dropping it onto Firefox:
Dropping a constructed File onto an iframe in the same tab:
For more details on the design, see my earlier post, Design Doc: Support Dragging JS File Objects to Native Drop Targets.
Implementation Details
- Issues:
- Change lists:
- Live demo: https://joone.github.io/web/dnd/JS_file_object/
Demos
The demo videos above are also available in the X thread for this talk.