Browser Inconsistencies: File Inputs
Dec 13th, 2008 by Gordon | 9 Comments
Web browsers have come a long way over the last few years. Most of us depend on them everyday, and I'd bet that some people reading this make their living, as I do, by designing programs that function within web browsers. I could talk up all the great and wonderful features that they have to offer, but that would be boring, so I'm going to focus on what I see as one of the biggest flaws to modern web browsers, a flaw that has been around for quite a long time now and has never really been adequately addressed: file inputs.
The concept of what a file input does is simple. It allows you to select a file from your hard drive, and upload it to a web server along with other form data. I've included one below. So if you're writing an email with your favorite web-based email client, file inputs are what show up when you want to add attachments. And as they are now, they're horribly, horribly broken. Many browsers are just doing it wrong.
Look & Feel
They look and feel different across all the browsers. Internet Explorer (IE), Opera (OP), and Firefox (FF) display what looks like an ordinary text input, with a button just to the right to browse your hard drive. Webkit-based browsers, like Chrome (GC) and Safari (AS), simply show a button with some space reserved to the right of it to display the base name of the file you select. OP labels this button "Choose...", GC and AS use "Choose File," while IE and FF call it "Browse..." And while IE and OP allow you to manually type in the path to a file in the text box on the left, FF does not (since version 3). Oh, and CSS support is a bit shaky, too, at least in FF. You can't set the background color, border, padding, or width at all in FF (not guaranteed to be an exhaustive list of CSS attributes). Why couldn't they all just decide to use a standard file input? Why are the buttons labeled inconsistently? Why is there sometimes a text box, sometimes a text box you can type in, and sometimes no text box at all? And why on Earth can't I change the width of the damn thing in FF?
Also, there is another issue that some of the browsers chose to address, and others ignore. Imagine you're filling out a form, and you select a file to upload, but then you change your mind, and you want to clear out that file input. Sure, you might be able to press the "reset" button on the form, but that would have the unfortunate side effect of clearing out anything and everything else you typed. So how do you clear just the file input? You can do this in IE and OP simply by deleting whatever text there is that represents the file path. And you can also do this in GC by clicking the button and then hitting Cancel. But not in FF and AS. You're pretty much stuck there. If you click the button and press cancel in FF and AS, after having previously selected a file, that original file remains selected. There is no way to unselect it, short of reseting the whole form.
JavaScript
Secondly, file inputs are kind of the black sheep of the family when it comes to JavaScript (if the family is made up of DOM elements -- that'd be a strange family; I wonder if <blink> still shows up for family reunions on the holidays). Normally, you can read and set certain properties on DOM elements through JavaScript. For instance, if you have a text field defined as <input type="text" id="field0" />, you can read and write the contents of this field programmatically by using document.getElementById('field0').value. However, this is not the case with file inputs. For security reasons, you cannot programmatically set the value of a file input; you are only afforded read-only access. This actually makes a lot of sense. If write access to the value of a file input was permitted, nothing would stop malicious web developers from creating websites with hidden file inputs that set themselves to C:\private.dat and then steal files to their heart's content all behind the scenes, unbeknowst to Joe Internet. That would be a huge problem, and identity theft would then be like stealing candy from a baby. So I have no qualms with the fact that write access is strictly forbidden to file inputs across all browsers. That's a good thing.

What I do have qualms with, however, is that the read access is inconsistent. When you select a file (by pressing "Choose," "Choose File," or "Browse," or by typing it in manually, if you can), and then you try to read off the value of the file input through JavaScript, the browsers behave differently again. FF and OP will return only the base name of the file, while the rest will return the full file path. So, for instance, if I select C:\test.txt as the file to upload, and then read the value through JavaScript, FF and OP will return test.txt, while IE, AS, and GC will return C:\test.txt like they should. Why the inconsistency? I have a feeling that the FF and OP people had good intentions with this, thinking that by only showing the base name, you give no indication of the folder structure to developers, and thereby make it more "secure." That's my best guess, anyway. But frankly, I don't think this is really any sort of substantial security gain at all, plus I think that in general creating JavaScript inconsistencies is a horrible thing to do. Being able to read the directory of a file that the user has to hand-pick still does not give any access to any other file. And if by some fluke a hacker does manage to gain access to the client's file system--well then knowing the directory of one particular file isn't really going matter. If they already got that far, chances are they're going to take whatever they came for, with or without knowing about that one file the user selected. I think consistency is key, and I err on the side of less restrictions, so I think if a standard were to be proposed, it'd be better for browsers to return the full file path rather than just the base name.
Also, there's another little quirk about file inputs when you try to dynamically create them in IE. Admittedly, this is actually not unique to file inputs alone. Let's say you did this:
var input = document.createElement('input');
input.setAttribute('type', 'file');
input.name = 'field1';
document.body.appendChild(input);
Assume there are no elements named field1 present anywhere else on the page. What would you expect to get back if you asked for document.getElementsByName('field1')[0]? The file input we just created, right? Well, not in IE. Any other browser will handle this fine, but in IE, you have to create dynamic form elements in a very weird way to be able to use them in conjunction with getElementsByName. To update the code above to make it work in IE, without breaking it in the other browsers, we'd have to do this:
try {
var input = document.createElement('<input name="field1">');
} catch (e) {
var input = document.createElement('input');
}
input.setAttribute('type', 'file');
input.name = 'field1';
document.body.appendChild(input);
Why this is...I don't know. I just don't know. This is one of the many joys of working with JScript that I discovered early on. If you create the file input just by using the innerHTML property directly, then this isn't an issue. But I'm somewhat of a createElement/appendChild aficionado myself, so I've had to use this trick here and again in some projects.
Upload Progress
Lastly, I've always wondered why the browsers themselves have never offered a way to monitor the upload progress of these file inputs. The browsers have access to the local file system, so they know how large (in bytes) each file is. And since the browsers are the ones negotiating the form request, they've got to know how many bytes have been sent at any given moment. So how hard would it be to implement some kind of default progress bar? Or some sort of JavaScript API to query how much data has been sent? I've found myself in the situation (many times) where I'm uploading a large file, and after a significant amount of time has gone by, it's really hard to tell whether the file is still genuinely uploading or has errored out. So a progress bar would be fantastic.
Now I know that some solutions exist, but at this point most of them still seem like hacks. If I'm designing a page, I could use a Java applet or Flash script to create a pretty progress bar on the screen. Or, I could install this PHP extension that lets me keep track of how much data has been sent and query that through some sort of AJAX interface. (Although that extension doesn't work at all if you're running PHP through FastCGI...grumble snarl growl...) And there are similar solutions that use Perl, or Ruby, or what have you, I'm sure.

But these are all accessory structures that were only created because the primary structure was missing. By default, a browser should be able to tell you the progress of a file that is uploading, without having to rely on every individual web developer to implement one of the above solutions. So I wonder: would it really be that much of a stretch to add this to the JavaScript spec? Here's my idea: give each file input a property called noprogress, which is false by default. You could then add the attribute noprogress="noprogress" or set it to true directly via JavaScript to enable it. When it's false (default behavior), as soon as the form is submitted, it would show a small progress bar. This would appear wherever the file name had shown up before. And also, add in a JavaScript read-only value called currentProgress, that would return a numeric value between 0 and 1, representing the total percent that has been sent. It would only make sense to allow reading currentProgress from both individual file inputs, and their containing form (for an overall percent). That way, if a developer so chose, (s)he could turn off the default progress bar on the file inputs, and use this value to create a custom progress bar. Seeing as the browsers are going to have this data somewhere behind the scenes anyway, why not just open up access to it, and thereby continue to change the state of web development for the better?
Conclusion
So that's my rant. I've laid out these inconsistencies in tabular format for you here. I did not test any of this in the new IE8 because I don't feel like installing that yet. Plus it's probably pretty safe to assume that they haven't changed their behaviors on file inputs since version 7.
| AS 3.1 | FF 3.0 | GC 1.0 | IE 7.0 | OP 9.6 | |
|---|---|---|---|---|---|
| Button Label | Choose File | Browse... | Choose File | Browse... | Choose... |
| Text Box? | No | Yes | No | Yes | Yes |
| Type File Manually? | No | No | No | Yes | Yes |
| Clear File? | No | No | Yes | Yes | Yes |
| Value returns full path? | Yes | No | Yes | Yes | No |
| Change name attribute? | Yes | Yes | Yes | With a Hack | Yes |
| Obeys CSS rules | Yes | No | Yes | Yes | Yes |
| Drag and Drop? | Yes | With an Extension | Yes | No | No |
So at this time, I think Google Chrome is the winner. You get the full file path back from JavaScript, you can clear the input (i.e. unselect a file), and you can style it with CSS. It has the button-only interface, so if you prefer the text field interface, then Internet Explorer wins. You might have to do a bit of a JavaScript hack if you want to use the getElementsByName method with dynamic elements, but other than that it has essentially all the same features as Chrome. Firefox seems to be the biggest loser (i.e. fewest capabilities). Now if they could just be standardized to either the button interface or the text box interface, and then have their features/behaviors standardized as well. (And a progress monitor would be nice, too!) Unfortunately I don't imagine any of the browser developers have any of this very high on their agendas right now.
UPDATE: A commenter pointed out that certain browsers allow drag-and-drop functionality with the file inputs, so I've added this comparison to the table as well, just in case anyone's interested.
IE8, or at least, the beta version I have seen a while back restricts the value given to JavaScript to just the filename too.. Great fun.
Styling the Fileinput is somewhat possible with a little scripthack that makes it invisible, overlays it with a custom image and then moves the input field around on mouseover so your button is always under the pointer when a mouseclick occurs.. see http://www.shauninman.com/archive/2007/09/10/styling_file_inputs_with_css_and_the_dom for details..
Thanks for mentioning that. I didn't bring that up in this post, but I was indeed aware about these style "hacks" done with superimposed divs and 0% opacity on the input. But overall it's still disappointing that our only real solutions at this point, as developers, are "hacks." File inputs are such a core feature of browsers that you'd think they'd offer more features and be more standardized in general.
One reason that someone may not want full paths is that system usernames get leaked out to the site. Not a very good reason, but just a thought.
The main reason for not providing the full path is that it's not actually important or useful to the Javascript in the webpage or the remote webserver the file is being sent to, so there is not reason to provide it.
But there are plenty of reasons not to provide it, the user may give away important information with out realising it: usernames, actual full names, company names, directory names that contain secret informations(eg. new company product names) etc.
It's obvious to the user that they are sending a file, and that the file name will also be send, but it's not obvious that the directory structure would be sent.
eg. A girl recently uploaded pictures to me through my website, and in the process gave away her Mother's full name, which was in the directory structure.
- Jesse McNelis
I think it's a little unfair to assume that there are no cases where having the full file path could possibly be useful. What if you want to keep the standard file inputs on the page for compatibility issues, but also have a Java applet running that receives the file path from JavaScript and then does some (optional) preprocessing on the file? I don't think that's too big of a stretch to envision.
You're not mentioning another difference in file input behavior: Drag&Drop. Some browsers (AFAIK Webkit, Opera?, and FF with the DragDropUpload extension) allow you to drag a file from the file manager into the box so that the file path is quickly written into it. Very handy! no more browsing around dozens of folders (because it always starts from the root).
This feature should be in all browsers.
Hi, Gordon. Thanks for all your comments. There's a lot of useful stuff in here. We've been talking about a lot of these issues at Mozilla. But I thought that responding to some of your comments might help to put things in perspective.
First, I think that a lot of what you are running into is work that we've explicitly done to try and protect users when they are browsing the web. We have two sets of audiences, users and developers, and sometimes they tend to conflict with each other.
In the case of file upload, there are a few important things that I'd like to respond to:
1. Text Box
This is included because we want the user to know what file they have selected. It's pretty easy to accidentally pick the wrong file and we want the user to have the chance to pick up on that mistake. We might change it in the future to not look like a text box. The only reason that it looks like one right now is largely a function of history. It was easier for us to make the text box read only than it was to try and change it to something else. Especially since we didn't really know what that something else might look like. This might change in the future.
2. Typing in Manually
It turns out that it was too easy to trick people into typing in a dangerous filename or cutting and pasting something that they shouldn't. One of our developers has a good story about an interesting attack that someone came up with: by allowing key press events and switching focus back and forth between a captcha and the file control they were able to take the text in the captcha which said "house / extra cat / passport w#d" and turn it into /etc/passwd in the file control, and the user would upload their password file. They spent a lot of time trying to come up with specific ways to defeat attacks as they came up, but the attacks kept getting more and more creative. So they just removed the functionality -- it was just too dangerous. We're looking at ways to be able to type in filenames, but it will probably happen outside of the web page itself -- possibly in the file picker.
3. Clear File
The only reason this isn't included is because we haven't come up with a UI for it. Note that you can clear it in script if you want by setting .value="" from another button.
4. .value Returns Full Path: Once again, this is about protecting users. Specifically preventing information about directory structure from being leaked to web sites. On a huge number of machines the account name for the user is included in the full path name. And if you have the username that's half of what's required for a username + password attack.
One of our developers points out that sometimes there are other things you might not have wanted to have uploaded. For example "/home/sicking/my boss is stupid/report.doc" -- I wouldn't want that uploaded.
5. CSS Rules.
The only reason why this doesn't work is because we haven't figured out all of the interactions, they aren't defined in the standards and we're somewhat reluctant to go out and create that entirely on our own. And you can apply some CSS rules to the file control -- just not all of them.
6. Upload Progress.
You can actually get update information as an xmlhttprequest is completing. There's some information here:
https://developer.mozilla.org/En/Using_XMLHttpRequest#Monitoring_progress
And I'm told that you're able to upload data that was selected via the file upload control, but I'm embarrassed to admit that I can't find the document on it. I'll keep looking for it.
Thanks for your comments, Gordon. It's important feedback.
Chris,
Your reply was a nice surprise! I wasn't expecting to get anything back from anyone official! I just have two things left to say. First, you say you haven't come up with any UI for clearing the file yet--but why not just do what Chrome is doing? That is, if you click "Browse...", and then press Cancel, have it clear out the file automatically. It's maybe not the most intuitive UI, but at least it's something (until you come up with something better). I didn't think to try clearing the value through JavaScript (that's good to know), but that's still more of a solution geared toward developers and not toward users. The other thing I have left to say is about CSS. I can understand that you'd be a bit weary to just do whatever you wanted in place of nonexistent standards, but some of the simpler attributes (e.g. height/width), seem pretty straightforward (then again I've never developed a browser!).
And lastly, if you do manage to find the documentation for uploading data from a file input through an XMLHttpRequest, that would be fantastic. Thanks much!
Here's some more on uploading from a file input from Jonas Sicking. He says that you should be able to do something like this:
file = input.files[0];
xhr = new XMLHttpRequest;
xhr.open("POST", "hi.cgi");
xhr.onload = ...;
xhr.sendAsBinary(file.getAsBinary());
With the URL in my previous comment you should be able to get progress events as well.