Richard York

Web Developer, Author, and Artist

HTML5 Drag and Drop File Uploads in Safari 5

HTML5 Drag and Drop File Uploads in Safari 5

April 13, 2011 by Richard

Update: This doesn't work in the Windows version of Safari.

However, while that may be the case, let's examine a few facts:

  1. There is NO version of JavaScript drag and drop file uploading that does work in Safari on Windows. Maybe Flash or Java would work, but my point is, nothing purely JavaScript.
  2. Safari on Windows is not representative of the typical Safari user. Where it does concern the 'typical' Safari user, that user also uses a Mac. Windows people tend to use Chrome, Firefox or IE. Which isn't to say the Windows Safari user doesn't exist, that may be something YOU have to worry about, but it's not a consideration for ME. In any case, if you're testing in Safari on Windows and expect that to be representative of a Mac user experience, you're, how shall I say this politely, misguided.
  3. The point of the code presented here isn't just that it works in Safari 5 on a Mac (even if that was my central focus), but this code ALSO works in Firefox AND Chrome on Windows AND on Mac. Therefore, it is my opinion, that it is representative of a better approach than one that only works in Chrome or Firefox or Chrome and Firefox or even just Safari, for that matter. With a little work, you could take what I've presented here and make it work in a backwards compatible fashion with the 'older' HTML5 drag and drop upload method, the one where you have to build the POST request by hand. Don't ask me to do that for you, by the way.
  4. It is STILL incorrect to claim that 'HTML5' drag and drop uploading of files does not work in Safari 5. It is only true if you add the 'Windows' quantifier.

So, to recap, before you write in to piss and moan about Safari on Windows, consider the following... 1. I'm not a Windows user. 2. Most people that use Safari aren't Windows users. 3. Safari 5 functionality, while true, is the focus of this article, isn't the whole point of this article.

I honestly don't care one way or another what browser or OS you happen to like, again, that's not even the point.

After doing a bit of research to see where IE9 stands with regards to drag and drop file support, I found a stunningly large amount of people seem to think that drag and drop upload does not work in Safari 5. Well, this post is here to set the record straight, Safari 5 absolutely supports the new drag and drop file upload standard. Turns out IE9 dropped the ball on this, I'm afraid, and we'll have to wait for the next IE to maybe get drag and drop file uploads.

Prior to the release of Safari 5, this feature was supported in both Chrome and Firefox 3.6. However, the variant of the spec supported in Chrome and Firefox is a more complicated implementation, and with Safari 5, a simpler version of drag and drop file uploading was introduced that has since been implemented in both Firefox 4 and in Chrome.

The differences pan out to be that in Firefox and in Chrome, both browsers required you to essentially build an HTTP POST request from scratch, and the Safari implementation was streamlined to allow a much simpler API. I'm given to understand that Firefox 4 and newer versions of Chrome now support both methods, and Safari 6 will add support for the more complicated method.

This is what a working implementation looks like:

<html> <head> <title>Drag and Drop File Upload Demo</title> <script type='text/javascript' src='/Library/jQuery/jQuery.js?hFileLastModified=1364984544' charset='utf-8'></script> <script type='text/javascript'> var dnd = { ready : function() { $('div#uploadTarget') .bind( 'dragenter', function(e) { e.preventDefault(); e.stopPropagation(); } ) .bind( 'dragover', function(e) { e.preventDefault(); e.stopPropagation(); } ) .bind( 'drop', function(e) { if (e.originalEvent.dataTransfer.files.length) { e.preventDefault(); e.stopPropagation(); dnd.createFileQueue(e.originalEvent.dataTransfer.files); dnd.upload(e.originalEvent.dataTransfer.files); } } ); }, createFileQueue : function(files) { $('tbody#uploadQueue').find('tr').not('.uploadTemplate').remove(); $(files).each( function(key, file) { var tr = $('tr.uploadTemplate').clone(); tr.removeClass('uploadTemplate'); tr.attr('id', 'file-' + key); if (file.type.match(new RegExp('/image.*/')) && FileReader) { // Displaying a thumbnail doesn't seem to work in Safari var img = document.createElement('img'); img.file = value; img.classList.add('thumbnail'); tr.find('tr').eq(0).html(img); var reader = new FileReader(); reader.onload = (function(img) { return function(e) { img.src = e.target.result; }; })(img); reader.readAsDataURL(value); } tr.find('td').eq(1).html(file.name); tr.find('td').eq(2).html(dnd.getFileSize(file.size)); $('tbody#uploadQueue').append(tr); } ); }, upload : function(files) { // This is a work-around for Safari occaisonally hanging when doing a // file upload. For some reason, an additional HTTP request for a blank // page prior to sending the form will force Safari to work correctly. $.get('/file/blank.html'); var http = new XMLHttpRequest(); $('div#uploadProgress > div > div').css('width', 0); $('div#uploadComplete').fadeOut( 'normal', function() { $('div#uploadProgress').fadeIn(); } ); if (http.upload && http.upload.addEventListener) { http.upload.addEventListener( 'progress', function(e) { if (e.lengthComputable) { var progress = Math.round((e.loaded * 100) / e.total); var progressDiv = $('div#uploadProgress > div > div'); progressDiv.css('width', progress + '%'); } }, false ); http.upload.addEventListener( 'load', function(e) { $('div#uploadProgress > div > div') .css('width', '100%'); $('div#uploadProgress').fadeOut( 'normal', function() { $('div#uploadComplete').fadeIn(); } ); } ); } if (typeof(FormData) != 'undefined') { var form = new FormData(); form.append('path', '/'); for (var i = 0; i < files.length; i++) { form.append('file[]', files[i]); } http.open('POST', '/file/upload.php'); http.send(form); } else { alert('Your browser does not support standard HTML5 Drag and Drop'); } }, getFileSize : function(bytes) { switch (true) { case (bytes < Math.pow(2,10)): { return bytes + ' Bytes'; }; case (bytes >= Math.pow(2,10) && bytes < Math.pow(2,20)): { return Math.round(bytes / Math.pow(2,10)) +' KB'; }; case (bytes >= Math.pow(2,20) && bytes < Math.pow(2,30)): { return Math.round((bytes / Math.pow(2,20)) * 10) / 10 + ' MB'; }; case (bytes > Math.pow(2,30)): { return Math.round((bytes / Math.pow(2,30)) * 100) / 100 + ' GB'; }; } } }; $(document).ready(dnd.ready); </script> <style type="text/css"> body { font: 12px Helvetica, Arial, sans-serif; } div#uploadTarget { position: fixed; top: 0; right: 0; bottom: 0; left: 0; padding: 25px; } table { width: 500px; margin: 0 auto; border-collapse: collapse; border: 1px solid rgb(200, 200, 200); background: #fff; box-shadow: 0 7px 20px rgba(0, 0, 0, 0.6); -webkit-box-shadow: 0 7px 20px rgba(0, 0, 0, 0.6); -moz-box-shadow: 0 7px 20px rgba(0, 0, 0, 0.6); } th { background: rgb(222, 222, 222); border: 1px solid rgb(200, 200, 200); text-align: left; padding: 5px; } td { padding: 5px; border-top: 1px solid rgb(222, 222, 222); } tbody tr:nth-child(even) { background: rgb(244, 244, 244); } tr.uploadTemplate { display: none; } h1 { font-size: 14px; width: 500px; margin: auto; } p { margin: 4px auto 50px auto; width: 500px; } div#uploadProgress { margin: 5px auto 50px auto; width: 500px; } div#uploadComplete { margin: 5px auto 50px auto; width: 500px; display: none; } div#uploadProgress > div { display: inline-block; width: 393px; height: 16px; border: 1px solid rgb(128, 128, 128); position: relative; top: 5px; left: 5px; } div#uploadProgress > div > div { background: rgb(130, 171, 196); height: 16px; width: 0; } </style> </head> <body> <div id="uploadTarget"> <h1>Drag and Drop File Upload Demo</h1> <p> Drag some files from your desktop to the browser window, if your browser supports the updated drag and drop standard, you'll see each file listed in the table below. </p> <div id='uploadProgress'> Upload Progress: <div><div></div></div> </div> <div id='uploadComplete'> Upload Complete! </div> <table> <colgroup> <col style="width: 100px;" /> <col /> <col style="width: 100px;" /> </colgroup> <thead> <tr> <th>Icon</th> <th>File</th> <th>Size</th> </tr> </thead> <tbody id="uploadQueue"> <tr class="uploadTemplate"> <td></td> <td></td> <td></td> </tr> </tbody> </table> </div> </body> </html>

Both implementations feature the addition of the files object upon the dataTransfer object. The simplified implementation adds a new object called FormData, which then allows you to construct a POST request without having to create the raw HTTP headers yourself.

The form.append() method allows you to define arbitrary POST variables.

Safari's implementation does allow for multiple files to be uploaded in a drag and drop, contrary to some information I've read that suggests only a single file can be dropped. And as you can see in this demo, any element can become the target for the drop, eliminating the need for a <input type="file" /> element.

Safari's implementation has some strange bugs, however. Occasionally, I've noticed in my own use of this feature that your first drop might stall and not go through. Adding a simple asynchronous request to a blank page prior to completing the upload seems to defeat that bug.

Additionally, Safari's implementation is not as complete as Chrome's or Firefox's, in that it does not yet seem to provide a way to output the progress of the upload of each file as the upload is occurring. At least my testing has yet to yield a successful implementation of a JavaScript-only progress meter. UPDATE: So it turns out Safari DOES support implementing a JavaScript-only file upload progress meter. As it happens, I wasn't doing it correctly. The demo code has been updated to include a working progress meter, which I have also tested in Firefox and Chrome, and this progress meter works in those browsers as well.

Then, yet another drawback seems to be that displaying thumbnails of images being uploaded also doesn't seem to work in Safari. Both of these are no doubt desirable features, but I, for one, am extremely pleased to be able to upload multiple files at once through drag and drop without resorting to hacks that involve either Flash or Java.

A demo of the above code is located here.

Comments

  • May 11, 2011 james says:

    Hi Richard, thanks for the example, it's been very useful. I notice that you don't use 'dragleave'. I've tried adding it and although i've seen older examples where it appeared to work in Firefox 3.6 (on a Mac), it no longer appears to work in 4.0.1? Any idea if this is right? Cheers, James
  • May 11, 2011 Richard York says:

    The dragleave event wasn't necessary for this demo, since I didn't have anything I wanted to do when the cursor left the element. I typically use dragenter and dragleave to do something like change a folder icon to an open folder icon, or draw an outline around the drop target to give some visual indication that you can drop something on it. But I have used dragleave in the past though, and typically you need to call e.preventDefault() inside of it to do something useful with it. At least, that's the way I seem to be using it in other code, so I have to assume that it was necessary. Most of the drag and drop API and its many events have to have the e.preventDefault() method called whenever you want to do anything that deviates from the normal drag and drop behavior implemented in the browser. I can't imagine why Firefox would have 86'd that event though, the whole drag and drop API is a relic of Internet Explorer's original, and some would say, terrible implementation. The other browsers have copied it pretty much verbatim. When I researched this feature, I believed that Safari 5's streamlined implementation was the one that was going to be adopted by the other browsers and that the other browsers would probably end support for their initial, much more complicated implementation. e.g., the part where you have to build the POST request yourself, not the part where you have the nifty events that can monitor upload progress and display thumbnails. I guess a comprehensive implementation would take both old and new into account, it's pretty easy to detect the existence of the FormData object. If you have a link to some code, I'm happy to have a look.
  • May 17, 2011 james says:

    Hi Richard, looks like it's now been filed as a bug on bugzilla. Although you can drag and drop to upload images with safari, am i correct in thinking that safari doesn't actually support the File API, so it's not possible to just drag and then display an image without first uploading it? Or access the data of the file? Cheers, James
  • May 17, 2011 Richard York says:

    I don't think it does support the File API. The various bits of code out there that demonstrate how to display a thumbnail or how to create a pure JavaScript file upload progress meter haven't worked in my own tests. I've heard that Safari 6 will support that stuff, at least that's my assumption when I read other blogs' comments about Safari 6 supporting the drag and drop, but it's all here say, since I haven't tested a Webkit nightly for myself to verify these claims.
  • June 15, 2011 Carlos says:

    I used Safari 5.05 this morning to drag 11 .mp4 files into youtube - progress bars worked, preview images worked. It was astounding. Got me to thinking about the security layer in all this. What stops the web server extracting whatever it wants. I'm hoping my OS only permits upload of the files I drag. It's a thin layer of protection.
  • August 5, 2011 concincedd says:

    i'm developing an safari extension and, not being a mac user, use safari on a windows computer. so, it'll be nice to know what works where ie good to know what to disable for safari and what to keep as it might not work for me but for 'normal' safari users (html5 audio seems to be another of those).
  • August 25, 2011 Richard York says:

    Carlos: This is actually very secure. The browser does not permit uploading of any arbitrary file, only those that have been dropped onto the window. The dataTransfer property of an event does not exist (where it concerns file uploads) except in the 'drop' event, and at that, it is read-only. So, at least in theory, assuming the browser makers haven't left any security holes open, the browser can only see the files you've explicitly allowed it to through the action of dropping those files onto the browser window.
  • August 26, 2011 swhiteman says:

    I would contest strongly the idea that "you dragged it, you wanted it uploaded" is an acceptable conclusion in the security world. The very reason that Java drag-and-drop uploaders must be signed and allowed by the user is that they have [b]access to the local file system[/b]. If "you dragged it" had been seen as implicit user authorization, such applets would run without user intervention (which would have made our lives, as a media hosting company, much easer!). Further, the behavior reasonably expected by our users when they drag browser-viewable media into a browser tab is the file opens in the browser. You wanna see a JPG, drag it into Firefox. Now think about what happens if the tab is open to a page with a big drop area on it. Oops! Got your file on my server now. Again, we are in a perfect position to benefit from widespread drag-and-drop in the browser instead of having to use helper applets (which Apple broke recently anyway). But I don't like benefiting from something that has lazy security, because that just makes us look stupid/manipulative to our users. I am fully expecting d-n-d to require explicit user approval in future browser versions when people realize the can o' worms they've got here.
  • August 27, 2011 Richard York says:

    Swhiteman: My thoughts on the topic are based on my admittedly limited understanding of the spec. I do happen to think it's a reasonable implementation, and I may a bit biased, as I've long wanted such functionally to emmerge and be easy to use without having to jump through countless hoops. Perhaps the code to do such a thing should be signed and verified, but for that to work, the signing and verification process would need to be transparent and much easier to achieve, assuming you are a legitimate and valid entity. But this is the opinion of just one man, who has no say in any of this. If you truly have a problem with the security ramifications of the specification, then the onus is not on me to assuage your concerns, it is on the browser developers and the WHATWG/W3C working groups that defined the functionality. At least an action is required on the part of the user, and at least the access granted is limited only to the files dropped. All that aside, I, for one, am absolutely thrilled that the ability *finally* exists, without Java applets or Flash or ActiveX or any of that other nonsense. I can see your point-of-view, but I have a hard time defining the villain.
  • August 27, 2011 swhiteman says:

    Don't worry, I'm not expecting you to solve the security issue! We have been trying to bring it to the attention of the standards community and we aren't alone in this (others have blogged specifically about the security gap). As your blog is part of the chorus of untempered approval, though -- and you're wrong if you think there's no public effect on implementations sticking around -- I think it's right to post a counterpoint on the subject, even in a blog comment. In general, I would say that "at least" aren't good words to hear in a security summary. :) What needs to happen, IMO, is: [a] if the code is signed, permission is granted by either ad-hoc approval (script-by-script) or existing trusted certificate; or [b] if the code is not signed, permission is granted per-session, or permanently per-remote-host; or [c] a browser setting (default-off) turns on the feature for anywhere Again, if ever there were a company who should be thrilled about this ability, it's us. But it's hard for us to glad-hand about it; people are saying, "Finally, stick a fork in Java," but I doubt you'd feel as celebratory if applets hadn't required signing or approval since the beginning of time. An upload applet can be absurdly lean to download and the modern JRE is very fast. Without the security sandbox, Java might not be on as many people's kill list. And then there's the fact that Apple broke Java drag-and-drop in recent updates ("security measure"?) as they begin to phase out Apple-branded Java. So now that Java has been crippled, what a perfect time to be forced to use something else... overall, that's not as rosy an introductory picture as I would like for such an exciting technology. I hope you can understand that for those in the trenches of bulk uploading for a long while, it's a bit of a slap in the face security-wise and I'm not being disingenuous about the danger of having pages pick up data that users think is going to open in the browser. This is something that the API does not seem to consider at all, but until browsers stop opening dragged HTML, JPEG, MOV files (only accepting them over HTTP), this obviously will happen; the only question is whether the site that accepts the data will be a malicious one.

There are no comments posted at this time.

Leave a Comment

Simple HTML is allowed: <code>, <b>, <i>, <u>, <var>, <strong>, <em>, <blockquote>, <ul>, <ol>, <li>, <p> (no attributes). All spacing and line breaks in your comment will be preserved.

* All comments are moderated and are subject to approval.
Your comment will appear once it has been approved.
Posting multiple times will not expedite the approval process.

Archive

© Copyright 2014 Hot Toddy, All Rights Reserved.